From b443b4420e170f4bdeea45dae96c513e23032388 Mon Sep 17 00:00:00 2001 From: archive Date: Wed, 7 Apr 1999 00:00:00 +0000 Subject: [PATCH] as released 1999-04-07 --- cl_dll/MOTD.cpp | 141 + cl_dll/ammo.cpp | 1177 +++++++ cl_dll/ammo.h | 62 + cl_dll/ammo_secondary.cpp | 159 + cl_dll/ammohistory.cpp | 190 ++ cl_dll/ammohistory.h | 144 + cl_dll/battery.cpp | 138 + cl_dll/cdll_int.cpp | 147 + cl_dll/cl_dll.dsp | 247 ++ cl_dll/cl_dll.dsw | 37 + cl_dll/cl_dll.h | 39 + cl_dll/cl_dll.mak | 1245 +++++++ cl_dll/death.cpp | 223 ++ cl_dll/flashlight.cpp | 149 + cl_dll/geiger.cpp | 184 ++ cl_dll/health.cpp | 475 +++ cl_dll/health.h | 127 + cl_dll/hud.cpp | 319 ++ cl_dll/hud.h | 611 ++++ cl_dll/hud_msg.cpp | 105 + cl_dll/hud_redraw.cpp | 259 ++ cl_dll/hud_update.cpp | 45 + cl_dll/menu.cpp | 174 + cl_dll/message.cpp | 463 +++ cl_dll/mssccprj.scc | 4 + cl_dll/parsemsg.cpp | 166 + cl_dll/parsemsg.h | 40 + cl_dll/readme.txt | 107 + cl_dll/saytext.cpp | 231 ++ cl_dll/scoreboard.cpp | 528 +++ cl_dll/status_icons.cpp | 149 + cl_dll/statusbar.cpp | 252 ++ cl_dll/text_message.cpp | 207 ++ cl_dll/train.cpp | 85 + cl_dll/util.cpp | 45 + cl_dll/util.h | 141 + cl_dll/util_vector.h | 121 + dlls/activity.h | 109 + dlls/activitymap.h | 97 + dlls/airtank.cpp | 118 + dlls/animating.cpp | 313 ++ dlls/animation.cpp | 521 +++ dlls/animation.h | 47 + dlls/basemonster.h | 94 + dlls/bmodels.cpp | 958 ++++++ dlls/buttons.cpp | 1276 ++++++++ dlls/cbase.cpp | 727 ++++ dlls/cbase.h | 784 +++++ dlls/cdll_dll.h | 46 + dlls/client.cpp | 729 +++++ dlls/client.h | 41 + dlls/combat.cpp | 1666 ++++++++++ dlls/crossbow.cpp | 600 ++++ dlls/crowbar.cpp | 334 ++ dlls/decals.h | 75 + dlls/doors.cpp | 1026 ++++++ dlls/doors.h | 33 + dlls/effects.cpp | 2264 +++++++++++++ dlls/effects.h | 209 ++ dlls/egon.cpp | 623 ++++ dlls/enginecallback.h | 123 + dlls/explode.cpp | 273 ++ dlls/explode.h | 32 + dlls/extdll.h | 69 + dlls/func_break.cpp | 998 ++++++ dlls/func_break.h | 74 + dlls/func_tank.cpp | 1035 ++++++ dlls/game.cpp | 876 +++++ dlls/game.h | 42 + dlls/gamerules.cpp | 335 ++ dlls/gamerules.h | 359 ++ dlls/gauss.cpp | 857 +++++ dlls/ggrenade.cpp | 488 +++ dlls/globals.cpp | 39 + dlls/glock.cpp | 297 ++ dlls/h_ai.cpp | 198 ++ dlls/h_battery.cpp | 200 ++ dlls/h_cycler.cpp | 471 +++ dlls/h_export.cpp | 55 + dlls/handgrenade.cpp | 245 ++ dlls/healthkit.cpp | 259 ++ dlls/hornet.cpp | 426 +++ dlls/hornet.h | 58 + dlls/hornetgun.cpp | 293 ++ dlls/items.cpp | 335 ++ dlls/items.h | 29 + dlls/lights.cpp | 199 ++ dlls/maprules.cpp | 918 ++++++ dlls/maprules.h | 22 + dlls/monsterevent.h | 34 + dlls/monsters.h | 88 + dlls/mortar.cpp | 323 ++ dlls/mp.def | 5 + dlls/mp.dsp | 550 ++++ dlls/mp.dsw | 37 + dlls/mp5.cpp | 415 +++ dlls/mpstubb.cpp | 264 ++ dlls/multiplay_gamerules.cpp | 1056 ++++++ dlls/nodes.h | 54 + dlls/pathcorner.cpp | 428 +++ dlls/plane.cpp | 60 + dlls/plane.h | 43 + dlls/plats.cpp | 2258 +++++++++++++ dlls/player.cpp | 5090 +++++++++++++++++++++++++++++ dlls/player.h | 289 ++ dlls/python.cpp | 338 ++ dlls/rpg.cpp | 695 ++++ dlls/satchel.cpp | 526 +++ dlls/saverestore.h | 170 + dlls/schedule.h | 31 + dlls/scriptevent.h | 29 + dlls/shotgun.cpp | 443 +++ 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/squeakgrenade.cpp | 595 ++++ dlls/subs.cpp | 559 ++++ dlls/talkmonster.h | 26 + dlls/teamplay_gamerules.cpp | 552 ++++ dlls/teamplay_gamerules.h | 57 + dlls/trains.h | 124 + dlls/triggers.cpp | 2429 ++++++++++++++ dlls/tripmine.cpp | 538 +++ dlls/util.cpp | 2350 +++++++++++++ dlls/util.h | 524 +++ dlls/vector.h | 112 + dlls/weapons.cpp | 1446 ++++++++ dlls/weapons.h | 450 +++ dlls/world.cpp | 737 +++++ dlls/xen.cpp | 584 ++++ engine/ANORMS.H | 177 + engine/PROGS.H | 145 + engine/cdll_int.h | 185 ++ engine/const.h | 761 +++++ engine/custom.h | 76 + engine/customentity.h | 82 + engine/cvardef.h | 34 + engine/eiface.h | 373 +++ engine/progdefs.h | 176 + engine/shake.h | 55 + engine/studio.h | 356 ++ utils/bspinfo/bspinfo.c | 48 + utils/bspinfo/bspinfo.dsp | 124 + utils/bspinfo/bspinfo.dsw | 37 + utils/bspinfo/bspinfo.mak | 253 ++ utils/bspinfo/mssccprj.scc | 4 + utils/common/bspfile.c | 709 ++++ utils/common/bspfile.h | 332 ++ utils/common/cmdlib.c | 1022 ++++++ utils/common/cmdlib.h | 149 + utils/common/lbmlib.c | 744 +++++ utils/common/lbmlib.h | 57 + utils/common/mathlib.c | 351 ++ utils/common/mathlib.h | 89 + 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/makels/makels.cpp | 173 + utils/makels/makels.dsp | 100 + utils/makels/makels.dsw | 37 + utils/makels/makels.mak | 212 ++ utils/makels/mssccprj.scc | 4 + utils/mdlviewer/mdlviewer.cpp | 268 ++ utils/mdlviewer/mdlviewer.dsp | 122 + utils/mdlviewer/mdlviewer.dsw | 37 + 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.mak | 196 ++ utils/mkmovie/mssccprj.scc | 4 + utils/qbsp2/bsp5.h | 267 ++ utils/qbsp2/cull.c | 28 + utils/qbsp2/gldraw.c | 164 + utils/qbsp2/merge.c | 282 ++ utils/qbsp2/mssccprj.scc | 7 + 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/qbsp2.mak | 466 +++ utils/qbsp2/solidbsp.c | 984 ++++++ utils/qbsp2/surfaces.c | 474 +++ utils/qbsp2/tjunc.c | 520 +++ utils/qbsp2/writebsp.c | 289 ++ utils/qbsp2/writebsp.mak | 179 + utils/qcsg/brush.c | 987 ++++++ utils/qcsg/csg.h | 140 + utils/qcsg/gldraw.c | 154 + utils/qcsg/map.c | 286 ++ utils/qcsg/mssccprj.scc | 4 + utils/qcsg/qcsg.c | 867 +++++ utils/qcsg/qcsg.dsp | 168 + utils/qcsg/qcsg.dsw | 37 + utils/qcsg/qcsg.mak | 605 ++++ utils/qcsg/textures.c | 505 +++ utils/qlumpy/mssccprj.scc | 4 + utils/qlumpy/qlumpy.c | 430 +++ utils/qlumpy/qlumpy.dsp | 140 + utils/qlumpy/qlumpy.dsw | 37 + utils/qlumpy/qlumpy.h | 26 + utils/qlumpy/qlumpy.mak | 412 +++ utils/qlumpy/quakegrb.c | 841 +++++ utils/qrad/lightmap.c | 1709 ++++++++++ utils/qrad/mssccprj.scc | 4 + utils/qrad/qrad.c | 1569 +++++++++ utils/qrad/qrad.dsp | 164 + utils/qrad/qrad.dsw | 37 + utils/qrad/qrad.h | 154 + utils/qrad/qrad.mak | 576 ++++ utils/qrad/trace.c | 320 ++ utils/qrad/vismat.c | 569 ++++ utils/readme.txt | 24 + utils/smdlexp/mssccprj.scc | 4 + utils/smdlexp/smdlexp.cpp | 951 ++++++ utils/smdlexp/smdlexp.def | 8 + utils/smdlexp/smdlexp.dsp | 135 + utils/smdlexp/smdlexp.dsw | 33 + utils/smdlexp/smdlexp.mak | 325 ++ utils/smdlexp/smdlexp.rc | 147 + utils/smdlexp/smedefs.h | 174 + utils/smdlexp/smexprc.h | 21 + utils/sprgen/mssccprj.scc | 4 + utils/sprgen/sprgen.c | 617 ++++ utils/sprgen/sprgen.dsp | 128 + utils/sprgen/sprgen.dsw | 37 + utils/sprgen/sprgen.mak | 335 ++ utils/sprgen/spritegn.h | 104 + utils/studiomdl/bmpread.c | 100 + utils/studiomdl/mssccprj.scc | 4 + utils/studiomdl/studiomdl.c | 3326 +++++++++++++++++++ utils/studiomdl/studiomdl.dsp | 156 + utils/studiomdl/studiomdl.dsw | 37 + utils/studiomdl/studiomdl.h | 403 +++ utils/studiomdl/studiomdl.mak | 442 +++ utils/studiomdl/tristrip.c | 352 ++ utils/studiomdl/write.c | 649 ++++ utils/visx2/flow.c | 687 ++++ utils/visx2/mssccprj.scc | 4 + utils/visx2/soundpvs.c | 156 + utils/visx2/vis.c | 527 +++ utils/visx2/vis.dsp | 153 + utils/visx2/vis.dsw | 37 + utils/visx2/vis.h | 141 + utils/visx2/vis.mak | 493 +++ utils/xwad/xwad.c | 157 + 261 files changed, 97134 insertions(+) create mode 100644 cl_dll/MOTD.cpp 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/cdll_int.cpp create mode 100644 cl_dll/cl_dll.dsp create mode 100644 cl_dll/cl_dll.dsw create mode 100644 cl_dll/cl_dll.h create mode 100644 cl_dll/cl_dll.mak create mode 100644 cl_dll/death.cpp create mode 100644 cl_dll/flashlight.cpp create mode 100644 cl_dll/geiger.cpp create mode 100644 cl_dll/health.cpp create mode 100644 cl_dll/health.h create mode 100644 cl_dll/hud.cpp create mode 100644 cl_dll/hud.h create mode 100644 cl_dll/hud_msg.cpp create mode 100644 cl_dll/hud_redraw.cpp create mode 100644 cl_dll/hud_update.cpp create mode 100644 cl_dll/menu.cpp create mode 100644 cl_dll/message.cpp create mode 100644 cl_dll/mssccprj.scc create mode 100644 cl_dll/parsemsg.cpp create mode 100644 cl_dll/parsemsg.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/status_icons.cpp create mode 100644 cl_dll/statusbar.cpp create mode 100644 cl_dll/text_message.cpp create mode 100644 cl_dll/train.cpp create mode 100644 cl_dll/util.cpp create mode 100644 cl_dll/util.h create mode 100644 cl_dll/util_vector.h create mode 100644 dlls/activity.h create mode 100644 dlls/activitymap.h 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/basemonster.h create mode 100644 dlls/bmodels.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/crossbow.cpp create mode 100644 dlls/crowbar.cpp create mode 100644 dlls/decals.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/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/gauss.cpp create mode 100644 dlls/ggrenade.cpp create mode 100644 dlls/globals.cpp create mode 100644 dlls/glock.cpp create mode 100644 dlls/h_ai.cpp create mode 100644 dlls/h_battery.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/healthkit.cpp create mode 100644 dlls/hornet.cpp create mode 100644 dlls/hornet.h create mode 100644 dlls/hornetgun.cpp create mode 100644 dlls/items.cpp create mode 100644 dlls/items.h 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/monsters.h create mode 100644 dlls/mortar.cpp create mode 100644 dlls/mp.def create mode 100644 dlls/mp.dsp create mode 100644 dlls/mp.dsw create mode 100644 dlls/mp5.cpp create mode 100644 dlls/mpstubb.cpp create mode 100644 dlls/multiplay_gamerules.cpp create mode 100644 dlls/nodes.h 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/python.cpp create mode 100644 dlls/rpg.cpp create mode 100644 dlls/satchel.cpp create mode 100644 dlls/saverestore.h create mode 100644 dlls/schedule.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/squeakgrenade.cpp create mode 100644 dlls/subs.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/trains.h create mode 100644 dlls/triggers.cpp create mode 100644 dlls/tripmine.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/xen.cpp create mode 100644 engine/ANORMS.H create mode 100644 engine/PROGS.H create mode 100644 engine/cdll_int.h create mode 100644 engine/const.h create mode 100644 engine/custom.h create mode 100644 engine/customentity.h create mode 100644 engine/cvardef.h create mode 100644 engine/eiface.h create mode 100644 engine/progdefs.h create mode 100644 engine/shake.h create mode 100644 engine/studio.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/bspinfo/bspinfo.mak create mode 100644 utils/bspinfo/mssccprj.scc create mode 100644 utils/common/bspfile.c create mode 100644 utils/common/bspfile.h create mode 100644 utils/common/cmdlib.c create mode 100644 utils/common/cmdlib.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/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/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/makels/mssccprj.scc 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.mak create mode 100644 utils/mkmovie/mssccprj.scc 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/merge.c create mode 100644 utils/qbsp2/mssccprj.scc 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/qbsp2.mak 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/qbsp2/writebsp.mak 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/map.c create mode 100644 utils/qcsg/mssccprj.scc 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/qcsg.mak create mode 100644 utils/qcsg/textures.c create mode 100644 utils/qlumpy/mssccprj.scc create mode 100644 utils/qlumpy/qlumpy.c 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/qlumpy.mak create mode 100644 utils/qlumpy/quakegrb.c create mode 100644 utils/qrad/lightmap.c create mode 100644 utils/qrad/mssccprj.scc 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.mak create mode 100644 utils/qrad/trace.c create mode 100644 utils/qrad/vismat.c create mode 100644 utils/readme.txt create mode 100644 utils/smdlexp/mssccprj.scc 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.mak 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/mssccprj.scc 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/sprgen.mak create mode 100644 utils/sprgen/spritegn.h create mode 100644 utils/studiomdl/bmpread.c create mode 100644 utils/studiomdl/mssccprj.scc 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/studiomdl.mak create mode 100644 utils/studiomdl/tristrip.c create mode 100644 utils/studiomdl/write.c create mode 100644 utils/visx2/flow.c create mode 100644 utils/visx2/mssccprj.scc 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/visx2/vis.mak create mode 100644 utils/xwad/xwad.c diff --git a/cl_dll/MOTD.cpp b/cl_dll/MOTD.cpp new file mode 100644 index 0000000..903a914 --- /dev/null +++ b/cl_dll/MOTD.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. +* +****/ +// +// MOTD.cpp +// +// for displaying a server-sent message of the day +// + +#include "hud.h" +#include "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", "6", 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_flActiveTill = 0; +} + +#define LINE_HEIGHT 13 + +int CHudMOTD :: Draw( float fTime ) +{ + // Draw MOTD line-by-line + + if ( m_flActiveTill < gHUD.m_flTime ) + { // finished with MOTD, disable it + m_szMOTD[0] = 0; + m_iLines = 0; + m_iFlags &= ~HUD_ACTIVE; + return 1; + } + + // cap activetill time to the display time + m_flActiveTill = min( gHUD.m_flTime + MOTD_DISPLAY_TIME, m_flActiveTill ); + + // 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 = CVAR_GET_FLOAT( "motd_display_time" ); + + m_flActiveTill = gHUD.m_flTime + 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/ammo.cpp b/cl_dll/ammo.cpp new file mode 100644 index 0000000..f08f155 --- /dev/null +++ b/cl_dll/ammo.cpp @@ -0,0 +1,1177 @@ +/*** +* +* 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. +* +****/ +// +// Ammo.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "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; + +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); + + 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; + + if ( (gpActiveSel == NULL) || (gpActiveSel == (WEAPON *)1) || (iSlot != gpActiveSel->iSlot) ) + { + PlaySound( "common/wpn_hudon.wav", 1 ); + p = GetFirstPos( iSlot ); + + if ( p && CVAR_GET_FLOAT( "hud_fastswitch" ) > 0 ) // 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 ); + return; + } + } + } + else + { + PlaySound("common/wpn_moveselect.wav", 1); + if ( gpActiveSel ) + p = GetNextActivePos( gpActiveSel->iSlot, gpActiveSel->iSlotPos ); + if ( !p ) + p = GetFirstPos( iSlot ); + } + + + if ( !p ) // if no selection found, just display the weapon list + gpActiveSel = (WEAPON *)1; + 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) +{ + 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..6e6ec2e --- /dev/null +++ b/cl_dll/ammo.h @@ -0,0 +1,62 @@ +/*** +* +* 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 __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..fdfefdf --- /dev/null +++ b/cl_dll/ammo_secondary.cpp @@ -0,0 +1,159 @@ +/*** +* +* 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. +* +****/ +// +// ammo_secondary.cpp +// +// implementation of CHudAmmoSecondary class +// + +#include "hud.h" +#include "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..69130fd --- /dev/null +++ b/cl_dll/ammohistory.cpp @@ -0,0 +1,190 @@ +/*** +* +* 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.cpp +// + + +#include "hud.h" +#include "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..2d61045 --- /dev/null +++ b/cl_dll/ammohistory.h @@ -0,0 +1,144 @@ +/*** +* +* 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 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..c09128a --- /dev/null +++ b/cl_dll/battery.cpp @@ -0,0 +1,138 @@ +/*** +* +* 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. +* +****/ +// +// battery.cpp +// +// implementation of CHudBattery class +// + +#include "hud.h" +#include "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); + + 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/cdll_int.cpp b/cl_dll/cdll_int.cpp new file mode 100644 index 0000000..4781344 --- /dev/null +++ b/cl_dll/cdll_int.cpp @@ -0,0 +1,147 @@ +/*** +* +* 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 "util.h" +#include + +#define DLLEXPORT __declspec( dllexport ) + +cl_enginefunc_t gEngfuncs; +CHud gHUD; + + +/* +========================== + Initialize + +Called when the DLL is first loaded. +========================== +*/ +extern "C" +{ +int DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ); +int DLLEXPORT HUD_VidInit( void ); +int DLLEXPORT HUD_Init( void ); +int DLLEXPORT HUD_Redraw( float flTime, int intermission ); +int DLLEXPORT HUD_UpdateClientData( client_data_t *cdata, float flTime ); +int DLLEXPORT HUD_Reset ( void ); +} + + +int DLLEXPORT 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)); + + return 1; +} + + +/* +========================== + HUD_VidInit + +Called when the game initializes +and whenever the vid_mode is changed +so the HUD can reinitialize itself. +========================== +*/ + +int DLLEXPORT HUD_VidInit( void ) +{ + gHUD.VidInit(); + return 1; +} + +/* +========================== + HUD_Init + +Called whenever the client connects +to a server. Reinitializes all +the hud variables. +========================== +*/ + +int DLLEXPORT HUD_Init( void ) +{ + gHUD.Init(); + + return 1; +} + + +/* +========================== + HUD_Redraw + +called every screen frame to +redraw the HUD. +=========================== +*/ + +int DLLEXPORT 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 DLLEXPORT 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 DLLEXPORT HUD_Reset( void ) +{ + gHUD.VidInit(); + return 1; +} \ No newline at end of file diff --git a/cl_dll/cl_dll.dsp b/cl_dll/cl_dll.dsp new file mode 100644 index 0000000..0a60106 --- /dev/null +++ b/cl_dll/cl_dll.dsp @@ -0,0 +1,247 @@ +# 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/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 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 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /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 /nologo /subsystem:windows /dll /map /machine:I386 /out:".\Release\client.dll" +# Begin Custom Build - Copying to \half-life\mp\cl_dlls +TargetDir=.\Release +InputPath=.\Release\client.dll +SOURCE="$(InputPath)" + +"\half-life\mp\cl_dlls\client.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\client.dll \half-life\mp\cl_dlls + +# 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 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 /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /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 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 /out:".\Debug\client.dll" +# Begin Custom Build - Copying to \half-life\mp\cl_dlls +TargetDir=.\Debug +InputPath=.\Debug\client.dll +SOURCE="$(InputPath)" + +"\half-life\mp\cl_dlls\client.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\client.dll \half-life\mp\cl_dlls + +# 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 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=.\death.cpp +# End Source File +# Begin Source File + +SOURCE=.\flashlight.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_update.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=.\MOTD.cpp +# End Source File +# Begin Source File + +SOURCE=.\parsemsg.cpp +# End Source File +# Begin Source File + +SOURCE=.\parsemsg.h +# End Source File +# Begin Source File + +SOURCE=.\saytext.cpp +# End Source File +# Begin Source File + +SOURCE=.\scoreboard.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=.\text_message.cpp +# End Source File +# Begin Source File + +SOURCE=.\train.cpp +# End Source File +# Begin Source File + +SOURCE=.\util.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=.\cl_dll.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=.\util.h +# End Source File +# Begin Source File + +SOURCE=.\util_vector.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/cl_dll/cl_dll.dsw b/cl_dll/cl_dll.dsw new file mode 100644 index 0000000..b4d58e9 --- /dev/null +++ b/cl_dll/cl_dll.dsw @@ -0,0 +1,37 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "cl_dll"=.\cl_dll.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/GoldSrc/cl_dll", HGEBAAAA + . + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + "$/GoldSrc/cl_dll", HGEBAAAA + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/cl_dll/cl_dll.h b/cl_dll/cl_dll.h new file mode 100644 index 0000000..23d3a7f --- /dev/null +++ b/cl_dll/cl_dll.h @@ -0,0 +1,39 @@ +/*** +* +* 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_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" +#define EXPORT _declspec( dllexport ) + +#include "../engine/cdll_int.h" +#include "../dlls/cdll_dll.h" + +extern cl_enginefunc_t gEngfuncs; diff --git a/cl_dll/cl_dll.mak b/cl_dll/cl_dll.mak new file mode 100644 index 0000000..491e06d --- /dev/null +++ b/cl_dll/cl_dll.mak @@ -0,0 +1,1245 @@ +# Microsoft Developer Studio Generated NMAKE File, Format Version 4.20 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 + +!IF "$(CFG)" == "" +CFG=cl_dll - Win32 Debug +!MESSAGE No configuration specified. Defaulting to cl_dll - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "cl_dll - Win32 Release" && "$(CFG)" != "cl_dll - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!MESSAGE You can specify a configuration when running NMAKE on this makefile +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "cl_dll.mak" CFG="cl_dll - Win32 Debug" +!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 +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF +################################################################################ +# Begin Project +# PROP Target_Last_Scanned "cl_dll - Win32 Debug" +MTL=mktyplib.exe +RSC=rc.exe +CPP=cl.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 Target_Dir "" +OUTDIR=.\Release +INTDIR=.\Release + +ALL : "$(OUTDIR)\client.dll" "..\..\valve\cl_dlls\client.dll" + +CLEAN : + -@erase "$(INTDIR)\ammo.obj" + -@erase "$(INTDIR)\ammo_secondary.obj" + -@erase "$(INTDIR)\ammohistory.obj" + -@erase "$(INTDIR)\battery.obj" + -@erase "$(INTDIR)\cdll_int.obj" + -@erase "$(INTDIR)\death.obj" + -@erase "$(INTDIR)\flashlight.obj" + -@erase "$(INTDIR)\geiger.obj" + -@erase "$(INTDIR)\health.obj" + -@erase "$(INTDIR)\hud.obj" + -@erase "$(INTDIR)\hud_msg.obj" + -@erase "$(INTDIR)\hud_redraw.obj" + -@erase "$(INTDIR)\hud_update.obj" + -@erase "$(INTDIR)\menu.obj" + -@erase "$(INTDIR)\message.obj" + -@erase "$(INTDIR)\MOTD.obj" + -@erase "$(INTDIR)\parsemsg.obj" + -@erase "$(INTDIR)\saytext.obj" + -@erase "$(INTDIR)\scoreboard.obj" + -@erase "$(INTDIR)\status_icons.obj" + -@erase "$(INTDIR)\statusbar.obj" + -@erase "$(INTDIR)\text_message.obj" + -@erase "$(INTDIR)\train.obj" + -@erase "$(INTDIR)\util.obj" + -@erase "$(INTDIR)\vc40.pdb" + -@erase "$(OUTDIR)\client.dll" + -@erase "$(OUTDIR)\client.exp" + -@erase "$(OUTDIR)\client.lib" + -@erase "$(OUTDIR)\client.map" + -@erase "..\..\valve\cl_dlls\client.dll" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +# ADD BASE CPP /nologo /MT /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +# ADD CPP /nologo /MT /W3 /GX /Zi /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS" /YX /c +CPP_PROJ=/nologo /MT /W3 /GX /Zi /O2 /D "WIN32" /D "NDEBUG" /D "_WINDOWS"\ + /Fp"$(INTDIR)/cl_dll.pch" /YX /Fo"$(INTDIR)/" /Fd"$(INTDIR)/" /c +CPP_OBJS=.\Release/ +CPP_SBRS=.\. +# ADD BASE MTL /nologo /D "NDEBUG" /win32 +# ADD MTL /nologo /D "NDEBUG" /win32 +MTL_PROJ=/nologo /D "NDEBUG" /win32 +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/cl_dll.bsc" +BSC32_SBRS= \ + +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 /nologo /subsystem:windows /dll /map /machine:I386 /out:"Release/client.dll" +LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib\ + advapi32.lib shell32.lib ole32.lib /nologo /subsystem:windows /dll\ + /incremental:no /pdb:"$(OUTDIR)/client.pdb" /map:"$(INTDIR)/client.map"\ + /machine:I386 /out:"$(OUTDIR)/client.dll" /implib:"$(OUTDIR)/client.lib" +LINK32_OBJS= \ + "$(INTDIR)\ammo.obj" \ + "$(INTDIR)\ammo_secondary.obj" \ + "$(INTDIR)\ammohistory.obj" \ + "$(INTDIR)\battery.obj" \ + "$(INTDIR)\cdll_int.obj" \ + "$(INTDIR)\death.obj" \ + "$(INTDIR)\flashlight.obj" \ + "$(INTDIR)\geiger.obj" \ + "$(INTDIR)\health.obj" \ + "$(INTDIR)\hud.obj" \ + "$(INTDIR)\hud_msg.obj" \ + "$(INTDIR)\hud_redraw.obj" \ + "$(INTDIR)\hud_update.obj" \ + "$(INTDIR)\menu.obj" \ + "$(INTDIR)\message.obj" \ + "$(INTDIR)\MOTD.obj" \ + "$(INTDIR)\parsemsg.obj" \ + "$(INTDIR)\saytext.obj" \ + "$(INTDIR)\scoreboard.obj" \ + "$(INTDIR)\status_icons.obj" \ + "$(INTDIR)\statusbar.obj" \ + "$(INTDIR)\text_message.obj" \ + "$(INTDIR)\train.obj" \ + "$(INTDIR)\util.obj" + +"$(OUTDIR)\client.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +# Begin Custom Build - Copying to \quiver\valve\cl_dlls +TargetDir=.\Release +InputPath=.\Release\client.dll +SOURCE=$(InputPath) + +"\quiver\valve\cl_dlls\client.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\client.dll \quiver\valve\cl_dlls + +# 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 Target_Dir "" +OUTDIR=.\Debug +INTDIR=.\Debug + +ALL : "$(OUTDIR)\client.dll" "$(OUTDIR)\cl_dll.bsc"\ + "..\..\valve\cl_dlls\client.dll" + +CLEAN : + -@erase "$(INTDIR)\ammo.obj" + -@erase "$(INTDIR)\ammo.sbr" + -@erase "$(INTDIR)\ammo_secondary.obj" + -@erase "$(INTDIR)\ammo_secondary.sbr" + -@erase "$(INTDIR)\ammohistory.obj" + -@erase "$(INTDIR)\ammohistory.sbr" + -@erase "$(INTDIR)\battery.obj" + -@erase "$(INTDIR)\battery.sbr" + -@erase "$(INTDIR)\cdll_int.obj" + -@erase "$(INTDIR)\cdll_int.sbr" + -@erase "$(INTDIR)\death.obj" + -@erase "$(INTDIR)\death.sbr" + -@erase "$(INTDIR)\flashlight.obj" + -@erase "$(INTDIR)\flashlight.sbr" + -@erase "$(INTDIR)\geiger.obj" + -@erase "$(INTDIR)\geiger.sbr" + -@erase "$(INTDIR)\health.obj" + -@erase "$(INTDIR)\health.sbr" + -@erase "$(INTDIR)\hud.obj" + -@erase "$(INTDIR)\hud.sbr" + -@erase "$(INTDIR)\hud_msg.obj" + -@erase "$(INTDIR)\hud_msg.sbr" + -@erase "$(INTDIR)\hud_redraw.obj" + -@erase "$(INTDIR)\hud_redraw.sbr" + -@erase "$(INTDIR)\hud_update.obj" + -@erase "$(INTDIR)\hud_update.sbr" + -@erase "$(INTDIR)\menu.obj" + -@erase "$(INTDIR)\menu.sbr" + -@erase "$(INTDIR)\message.obj" + -@erase "$(INTDIR)\message.sbr" + -@erase "$(INTDIR)\MOTD.obj" + -@erase "$(INTDIR)\MOTD.sbr" + -@erase "$(INTDIR)\parsemsg.obj" + -@erase "$(INTDIR)\parsemsg.sbr" + -@erase "$(INTDIR)\saytext.obj" + -@erase "$(INTDIR)\saytext.sbr" + -@erase "$(INTDIR)\scoreboard.obj" + -@erase "$(INTDIR)\scoreboard.sbr" + -@erase "$(INTDIR)\status_icons.obj" + -@erase "$(INTDIR)\status_icons.sbr" + -@erase "$(INTDIR)\statusbar.obj" + -@erase "$(INTDIR)\statusbar.sbr" + -@erase "$(INTDIR)\text_message.obj" + -@erase "$(INTDIR)\text_message.sbr" + -@erase "$(INTDIR)\train.obj" + -@erase "$(INTDIR)\train.sbr" + -@erase "$(INTDIR)\util.obj" + -@erase "$(INTDIR)\util.sbr" + -@erase "$(INTDIR)\vc40.idb" + -@erase "$(INTDIR)\vc40.pdb" + -@erase "$(OUTDIR)\cl_dll.bsc" + -@erase "$(OUTDIR)\client.dll" + -@erase "$(OUTDIR)\client.exp" + -@erase "$(OUTDIR)\client.ilk" + -@erase "$(OUTDIR)\client.lib" + -@erase "$(OUTDIR)\client.pdb" + -@erase "..\..\valve\cl_dlls\client.dll" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +# 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 /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /D "_MBCS" /FR /YX /c +CPP_PROJ=/nologo /G5 /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D\ + "_WINDOWS" /D "_MBCS" /FR"$(INTDIR)/" /Fp"$(INTDIR)/cl_dll.pch" /YX\ + /Fo"$(INTDIR)/" /Fd"$(INTDIR)/" /c +CPP_OBJS=.\Debug/ +CPP_SBRS=.\Debug/ +# ADD BASE MTL /nologo /D "_DEBUG" /win32 +# ADD MTL /nologo /D "_DEBUG" /win32 +MTL_PROJ=/nologo /D "_DEBUG" /win32 +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/cl_dll.bsc" +BSC32_SBRS= \ + "$(INTDIR)\ammo.sbr" \ + "$(INTDIR)\ammo_secondary.sbr" \ + "$(INTDIR)\ammohistory.sbr" \ + "$(INTDIR)\battery.sbr" \ + "$(INTDIR)\cdll_int.sbr" \ + "$(INTDIR)\death.sbr" \ + "$(INTDIR)\flashlight.sbr" \ + "$(INTDIR)\geiger.sbr" \ + "$(INTDIR)\health.sbr" \ + "$(INTDIR)\hud.sbr" \ + "$(INTDIR)\hud_msg.sbr" \ + "$(INTDIR)\hud_redraw.sbr" \ + "$(INTDIR)\hud_update.sbr" \ + "$(INTDIR)\menu.sbr" \ + "$(INTDIR)\message.sbr" \ + "$(INTDIR)\MOTD.sbr" \ + "$(INTDIR)\parsemsg.sbr" \ + "$(INTDIR)\saytext.sbr" \ + "$(INTDIR)\scoreboard.sbr" \ + "$(INTDIR)\status_icons.sbr" \ + "$(INTDIR)\statusbar.sbr" \ + "$(INTDIR)\text_message.sbr" \ + "$(INTDIR)\train.sbr" \ + "$(INTDIR)\util.sbr" + +"$(OUTDIR)\cl_dll.bsc" : "$(OUTDIR)" $(BSC32_SBRS) + $(BSC32) @<< + $(BSC32_FLAGS) $(BSC32_SBRS) +<< + +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 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 /out:"Debug/client.dll" +LINK32_FLAGS=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 /incremental:yes\ + /pdb:"$(OUTDIR)/client.pdb" /debug /machine:I386 /out:"$(OUTDIR)/client.dll"\ + /implib:"$(OUTDIR)/client.lib" +LINK32_OBJS= \ + "$(INTDIR)\ammo.obj" \ + "$(INTDIR)\ammo_secondary.obj" \ + "$(INTDIR)\ammohistory.obj" \ + "$(INTDIR)\battery.obj" \ + "$(INTDIR)\cdll_int.obj" \ + "$(INTDIR)\death.obj" \ + "$(INTDIR)\flashlight.obj" \ + "$(INTDIR)\geiger.obj" \ + "$(INTDIR)\health.obj" \ + "$(INTDIR)\hud.obj" \ + "$(INTDIR)\hud_msg.obj" \ + "$(INTDIR)\hud_redraw.obj" \ + "$(INTDIR)\hud_update.obj" \ + "$(INTDIR)\menu.obj" \ + "$(INTDIR)\message.obj" \ + "$(INTDIR)\MOTD.obj" \ + "$(INTDIR)\parsemsg.obj" \ + "$(INTDIR)\saytext.obj" \ + "$(INTDIR)\scoreboard.obj" \ + "$(INTDIR)\status_icons.obj" \ + "$(INTDIR)\statusbar.obj" \ + "$(INTDIR)\text_message.obj" \ + "$(INTDIR)\train.obj" \ + "$(INTDIR)\util.obj" + +"$(OUTDIR)\client.dll" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +# Begin Custom Build - Copying to \quiver\valve\cl_dlls +TargetDir=.\Debug +InputPath=.\Debug\client.dll +SOURCE=$(InputPath) + +"\quiver\valve\cl_dlls\client.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetDir)\client.dll \quiver\valve\cl_dlls + +# End Custom Build + +!ENDIF + +.c{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.c{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +################################################################################ +# Begin Target + +# Name "cl_dll - Win32 Release" +# Name "cl_dll - Win32 Debug" + +!IF "$(CFG)" == "cl_dll - Win32 Release" + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + +!ENDIF + +################################################################################ +# Begin Source File + +SOURCE=.\cl_dll.h + +!IF "$(CFG)" == "cl_dll - Win32 Release" + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\cdll_int.cpp + +!IF "$(CFG)" == "cl_dll - Win32 Release" + +DEP_CPP_CDLL_=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +"$(INTDIR)\cdll_int.obj" : $(SOURCE) $(DEP_CPP_CDLL_) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + +DEP_CPP_CDLL_=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +"$(INTDIR)\cdll_int.obj" : $(SOURCE) $(DEP_CPP_CDLL_) "$(INTDIR)" + +"$(INTDIR)\cdll_int.sbr" : $(SOURCE) $(DEP_CPP_CDLL_) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\hud_redraw.cpp + +!IF "$(CFG)" == "cl_dll - Win32 Release" + +DEP_CPP_HUD_R=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +"$(INTDIR)\hud_redraw.obj" : $(SOURCE) $(DEP_CPP_HUD_R) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + +DEP_CPP_HUD_R=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +"$(INTDIR)\hud_redraw.obj" : $(SOURCE) $(DEP_CPP_HUD_R) "$(INTDIR)" + +"$(INTDIR)\hud_redraw.sbr" : $(SOURCE) $(DEP_CPP_HUD_R) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\hud.cpp +DEP_CPP_HUD_C=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\hud.obj" : $(SOURCE) $(DEP_CPP_HUD_C) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\hud.obj" : $(SOURCE) $(DEP_CPP_HUD_C) "$(INTDIR)" + +"$(INTDIR)\hud.sbr" : $(SOURCE) $(DEP_CPP_HUD_C) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\parsemsg.cpp + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\parsemsg.obj" : $(SOURCE) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\parsemsg.obj" : $(SOURCE) "$(INTDIR)" + +"$(INTDIR)\parsemsg.sbr" : $(SOURCE) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\hud_msg.cpp + +!IF "$(CFG)" == "cl_dll - Win32 Release" + +DEP_CPP_HUD_M=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +"$(INTDIR)\hud_msg.obj" : $(SOURCE) $(DEP_CPP_HUD_M) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + +DEP_CPP_HUD_M=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +"$(INTDIR)\hud_msg.obj" : $(SOURCE) $(DEP_CPP_HUD_M) "$(INTDIR)" + +"$(INTDIR)\hud_msg.sbr" : $(SOURCE) $(DEP_CPP_HUD_M) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\hud_update.cpp + +!IF "$(CFG)" == "cl_dll - Win32 Release" + +DEP_CPP_HUD_U=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +"$(INTDIR)\hud_update.obj" : $(SOURCE) $(DEP_CPP_HUD_U) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + +DEP_CPP_HUD_U=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +"$(INTDIR)\hud_update.obj" : $(SOURCE) $(DEP_CPP_HUD_U) "$(INTDIR)" + +"$(INTDIR)\hud_update.sbr" : $(SOURCE) $(DEP_CPP_HUD_U) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\util.h + +!IF "$(CFG)" == "cl_dll - Win32 Release" + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\parsemsg.h + +!IF "$(CFG)" == "cl_dll - Win32 Release" + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\hud.h + +!IF "$(CFG)" == "cl_dll - Win32 Release" + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\ammo.cpp +DEP_CPP_AMMO_=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\ammohistory.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\ammo.obj" : $(SOURCE) $(DEP_CPP_AMMO_) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\ammo.obj" : $(SOURCE) $(DEP_CPP_AMMO_) "$(INTDIR)" + +"$(INTDIR)\ammo.sbr" : $(SOURCE) $(DEP_CPP_AMMO_) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\geiger.cpp +DEP_CPP_GEIGE=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\geiger.obj" : $(SOURCE) $(DEP_CPP_GEIGE) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\geiger.obj" : $(SOURCE) $(DEP_CPP_GEIGE) "$(INTDIR)" + +"$(INTDIR)\geiger.sbr" : $(SOURCE) $(DEP_CPP_GEIGE) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\health.cpp +DEP_CPP_HEALT=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\health.obj" : $(SOURCE) $(DEP_CPP_HEALT) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\health.obj" : $(SOURCE) $(DEP_CPP_HEALT) "$(INTDIR)" + +"$(INTDIR)\health.sbr" : $(SOURCE) $(DEP_CPP_HEALT) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\battery.cpp +DEP_CPP_BATTE=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\battery.obj" : $(SOURCE) $(DEP_CPP_BATTE) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\battery.obj" : $(SOURCE) $(DEP_CPP_BATTE) "$(INTDIR)" + +"$(INTDIR)\battery.sbr" : $(SOURCE) $(DEP_CPP_BATTE) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\util.cpp +DEP_CPP_UTIL_=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\util.obj" : $(SOURCE) $(DEP_CPP_UTIL_) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\util.obj" : $(SOURCE) $(DEP_CPP_UTIL_) "$(INTDIR)" + +"$(INTDIR)\util.sbr" : $(SOURCE) $(DEP_CPP_UTIL_) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\util_vector.h + +!IF "$(CFG)" == "cl_dll - Win32 Release" + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\train.cpp +DEP_CPP_TRAIN=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\train.obj" : $(SOURCE) $(DEP_CPP_TRAIN) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\train.obj" : $(SOURCE) $(DEP_CPP_TRAIN) "$(INTDIR)" + +"$(INTDIR)\train.sbr" : $(SOURCE) $(DEP_CPP_TRAIN) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\flashlight.cpp +DEP_CPP_FLASH=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\flashlight.obj" : $(SOURCE) $(DEP_CPP_FLASH) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\flashlight.obj" : $(SOURCE) $(DEP_CPP_FLASH) "$(INTDIR)" + +"$(INTDIR)\flashlight.sbr" : $(SOURCE) $(DEP_CPP_FLASH) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\message.cpp +DEP_CPP_MESSA=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\message.obj" : $(SOURCE) $(DEP_CPP_MESSA) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\message.obj" : $(SOURCE) $(DEP_CPP_MESSA) "$(INTDIR)" + +"$(INTDIR)\message.sbr" : $(SOURCE) $(DEP_CPP_MESSA) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\scoreboard.cpp +DEP_CPP_SCORE=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\scoreboard.obj" : $(SOURCE) $(DEP_CPP_SCORE) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\scoreboard.obj" : $(SOURCE) $(DEP_CPP_SCORE) "$(INTDIR)" + +"$(INTDIR)\scoreboard.sbr" : $(SOURCE) $(DEP_CPP_SCORE) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\MOTD.cpp +DEP_CPP_MOTD_=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\MOTD.obj" : $(SOURCE) $(DEP_CPP_MOTD_) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\MOTD.obj" : $(SOURCE) $(DEP_CPP_MOTD_) "$(INTDIR)" + +"$(INTDIR)\MOTD.sbr" : $(SOURCE) $(DEP_CPP_MOTD_) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\ammohistory.cpp +DEP_CPP_AMMOH=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\ammohistory.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\ammohistory.obj" : $(SOURCE) $(DEP_CPP_AMMOH) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\ammohistory.obj" : $(SOURCE) $(DEP_CPP_AMMOH) "$(INTDIR)" + +"$(INTDIR)\ammohistory.sbr" : $(SOURCE) $(DEP_CPP_AMMOH) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\saytext.cpp +DEP_CPP_SAYTE=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\saytext.obj" : $(SOURCE) $(DEP_CPP_SAYTE) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\saytext.obj" : $(SOURCE) $(DEP_CPP_SAYTE) "$(INTDIR)" + +"$(INTDIR)\saytext.sbr" : $(SOURCE) $(DEP_CPP_SAYTE) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\death.cpp +DEP_CPP_DEATH=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\death.obj" : $(SOURCE) $(DEP_CPP_DEATH) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\death.obj" : $(SOURCE) $(DEP_CPP_DEATH) "$(INTDIR)" + +"$(INTDIR)\death.sbr" : $(SOURCE) $(DEP_CPP_DEATH) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\menu.cpp +DEP_CPP_MENU_=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\menu.obj" : $(SOURCE) $(DEP_CPP_MENU_) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\menu.obj" : $(SOURCE) $(DEP_CPP_MENU_) "$(INTDIR)" + +"$(INTDIR)\menu.sbr" : $(SOURCE) $(DEP_CPP_MENU_) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\ammo_secondary.cpp +DEP_CPP_AMMO_S=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\ammo_secondary.obj" : $(SOURCE) $(DEP_CPP_AMMO_S) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\ammo_secondary.obj" : $(SOURCE) $(DEP_CPP_AMMO_S) "$(INTDIR)" + +"$(INTDIR)\ammo_secondary.sbr" : $(SOURCE) $(DEP_CPP_AMMO_S) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\statusbar.cpp +DEP_CPP_STATU=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\statusbar.obj" : $(SOURCE) $(DEP_CPP_STATU) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\statusbar.obj" : $(SOURCE) $(DEP_CPP_STATU) "$(INTDIR)" + +"$(INTDIR)\statusbar.sbr" : $(SOURCE) $(DEP_CPP_STATU) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\text_message.cpp +DEP_CPP_TEXT_=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\text_message.obj" : $(SOURCE) $(DEP_CPP_TEXT_) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\text_message.obj" : $(SOURCE) $(DEP_CPP_TEXT_) "$(INTDIR)" + +"$(INTDIR)\text_message.sbr" : $(SOURCE) $(DEP_CPP_TEXT_) "$(INTDIR)" + + +!ENDIF + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=.\status_icons.cpp +DEP_CPP_STATUS=\ + "..\dlls\cdll_dll.h"\ + "..\engine\cdll_int.h"\ + ".\ammo.h"\ + ".\cl_dll.h"\ + ".\health.h"\ + ".\hud.h"\ + ".\parsemsg.h"\ + ".\util.h"\ + ".\util_vector.h"\ + + +!IF "$(CFG)" == "cl_dll - Win32 Release" + + +"$(INTDIR)\status_icons.obj" : $(SOURCE) $(DEP_CPP_STATUS) "$(INTDIR)" + + +!ELSEIF "$(CFG)" == "cl_dll - Win32 Debug" + + +"$(INTDIR)\status_icons.obj" : $(SOURCE) $(DEP_CPP_STATUS) "$(INTDIR)" + +"$(INTDIR)\status_icons.sbr" : $(SOURCE) $(DEP_CPP_STATUS) "$(INTDIR)" + + +!ENDIF + +# End Source File +# End Target +# End Project +################################################################################ diff --git a/cl_dll/death.cpp b/cl_dll/death.cpp new file mode 100644 index 0000000..42a4e63 --- /dev/null +++ b/cl_dll/death.cpp @@ -0,0 +1,223 @@ +/*** +* +* 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 "util.h" +#include "parsemsg.h" + +#include +#include + + +DECLARE_MESSAGE( m_DeathNotice, DeathMsg ); + +struct DeathNoticeItem { + char szKiller[MAX_PLAYER_NAME_LENGTH]; + char szVictim[MAX_PLAYER_NAME_LENGTH]; + int iId; // the index number of the associated sprite + int iSuicide; + int iTeamKill; + float flDisplayTime; +}; + +#define MAX_DEATHNOTICES 4 +static int DEATHNOTICE_DISPLAY_TIME = 6; + +#define DEATHNOTICE_TOP 20 + +DeathNoticeItem rgDeathNoticeList[ MAX_DEATHNOTICES + 1 ]; + + +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 = 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 + 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 + 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 ); + + gHUD.m_Scoreboard.DeathMsg( killer, victim ); + + for ( int 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(); + + char *killer_name = gHUD.m_Scoreboard.m_PlayerInfoList[ killer ].name; + char *victim_name = gHUD.m_Scoreboard.m_PlayerInfoList[ victim ].name; + if ( !killer_name ) + killer_name = ""; + if ( !victim_name ) + victim_name = ""; + + strncpy( rgDeathNoticeList[i].szKiller, killer_name, MAX_PLAYER_NAME_LENGTH ); + strncpy( rgDeathNoticeList[i].szVictim, victim_name, MAX_PLAYER_NAME_LENGTH ); + + 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/cl_dll/flashlight.cpp b/cl_dll/flashlight.cpp new file mode 100644 index 0000000..69361a9 --- /dev/null +++ b/cl_dll/flashlight.cpp @@ -0,0 +1,149 @@ +/*** +* +* 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. +* +****/ +// +// flashlight.cpp +// +// implementation of CHudFlashlight class +// + +#include "hud.h" +#include "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..21d0abc --- /dev/null +++ b/cl_dll/geiger.cpp @@ -0,0 +1,184 @@ +/*** +* +* 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. +* +****/ +// +// Geiger.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "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/health.cpp b/cl_dll/health.cpp new file mode 100644 index 0000000..df238cf --- /dev/null +++ b/cl_dll/health.cpp @@ -0,0 +1,475 @@ +/*** +* +* 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 "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. + 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 + 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 ); + 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..59e401e --- /dev/null +++ b/cl_dll/health.h @@ -0,0 +1,127 @@ +/*** +* +* 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. +* +****/ + +#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; + +private: + HSPRITE m_hSprite; + HSPRITE m_hDamage; + + DAMAGE_IMAGE m_dmg[NUM_DMG_TYPES]; + int m_bitsDamage; + float m_fFade; + void GetPainColor( int &r, int &g, int &b ); + 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/cl_dll/hud.cpp b/cl_dll/hud.cpp new file mode 100644 index 0000000..d47ec66 --- /dev/null +++ b/cl_dll/hud.cpp @@ -0,0 +1,319 @@ +/*** +* +* 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.cpp +// +// implementation of CHud class +// + +#include "hud.h" +#include "util.h" +#include +#include +#include "parsemsg.h" + +extern client_sprite_t *GetSpriteList(client_sprite_t *pList, const char *psz, int iRes, int iCount); + +//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 ); +} + + +// 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 ); + + m_iLogo = 0; + m_iFOV = 0; + + CVAR_CREATE( "zoom_sensitivity_ratio", "1.2", 0 ); + CVAR_CREATE( "default_fov", "90", 0 ); + + 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_Scoreboard.Init(); + m_MOTD.Init(); + m_StatusBar.Init(); + m_DeathNotice.Init(); + m_AmmoSecondary.Init(); + m_TextMessage.Init(); + m_StatusIcons.Init(); + + m_SayText.Init(); + m_Menu.Init(); + + 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; +} + +// 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; + + 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; + for ( int 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_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(); +} + +int CHud::MsgFunc_Logo(const char *pszName, int iSize, void *pbuf) +{ + BEGIN_READ( pbuf, iSize ); + + // update Train data + m_iLogo = READ_BYTE(); + + return 1; +} + +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 = CVAR_GET_FLOAT("sensitivity") * ((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; +} + + diff --git a/cl_dll/hud.h b/cl_dll/hud.h new file mode 100644 index 0000000..8b03efb --- /dev/null +++ b/cl_dll/hud.h @@ -0,0 +1,611 @@ +/*** +* +* 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 + +typedef struct rect_s +{ + int left, right, top, bottom; +} wrect_t; + +#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 + +// +//----------------------------------------------------- +// +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; +}; + + +// +//----------------------------------------------------- +// +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 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: + enum { MAX_MOTD_LENGTH = 241, }; + static int MOTD_DISPLAY_TIME; + char m_szMOTD[ MAX_MOTD_LENGTH ]; + float m_flActiveTill; + 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, + }; + + 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 +}; + +// +//----------------------------------------------------- +// +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 ); + + enum { + MAX_PLAYERS = 64, + MAX_TEAMS = 64, + MAX_TEAM_NAME = 16, + }; + + struct extra_player_info_t { + short frags; + short deaths; + 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; + }; + + hud_player_info_t m_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine + extra_player_info_t m_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll + team_info_t m_TeamInfo[MAX_TEAMS+1]; + + 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 +}; + +// +//----------------------------------------------------- +// +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 ); + 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 ); + char *LocaliseTextString( const char *msg, char *dst_buffer, int buffer_size ); + 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]; + +}; + + +// +//----------------------------------------------------- +// + +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: + + 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; + + 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]*/ + +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; + 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() : 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); + +}; + +extern CHud gHUD; diff --git a/cl_dll/hud_msg.cpp b/cl_dll/hud_msg.cpp new file mode 100644 index 0000000..9bc6cd3 --- /dev/null +++ b/cl_dll/hud_msg.cpp @@ -0,0 +1,105 @@ +/*** +* +* 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 "util.h" +#include "parsemsg.h" + +/// 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 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 ); + 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..d52c242 --- /dev/null +++ b/cl_dll/hud_redraw.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. +* +****/ +// +// hud_redraw.cpp +// +#include +#include "hud.h" +#include "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 +}; + + +// Think +void CHud::Think(void) +{ + 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( CVAR_GET_FLOAT( "default_fov" ), 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 + for ( char *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/cl_dll/hud_update.cpp b/cl_dll/hud_update.cpp new file mode 100644 index 0000000..18536ae --- /dev/null +++ b/cl_dll/hud_update.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. +* +****/ +// +// hud_update.cpp +// + +#include +#include "hud.h" +#include "util.h" +#include + + + +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 = cdata->iKeyBits; + m_iWeaponBits = cdata->iWeaponBits; + gHUD.Think(); + + cdata->iKeyBits = m_iKeyBits; + cdata->fov = m_iFOV; + cdata->view_idlescale = m_iConcussionEffect; + + if ( m_flMouseSensitivity ) + cdata->mouse_sensitivity = m_flMouseSensitivity; + + // return 1 if in anything in the client_data struct has been changed, 0 otherwise + return 1; +} + + diff --git a/cl_dll/menu.cpp b/cl_dll/menu.cpp new file mode 100644 index 0000000..5e78dfb --- /dev/null +++ b/cl_dll/menu.cpp @@ -0,0 +1,174 @@ +/*** +* +* 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. +* +****/ +// +// menu.cpp +// +// generic menu handler +// +#include "hud.h" +#include "util.h" +#include "parsemsg.h" + +#include +#include + +#define MAX_MENU_STRING 512 +char g_szMenuString[MAX_MENU_STRING]; +char g_szPrelocalisedMenuString[MAX_MENU_STRING]; + +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 ( gHUD.m_Scoreboard.m_iShowscoresHeld ) + return 1; + + // draw the menu, along the left-hand side of the screen + + // count the number of newlines + int nlc = 0; + for ( int 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 ) +{ + 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 ) ); + } + + 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..80c8dd6 --- /dev/null +++ b/cl_dll/message.cpp @@ -0,0 +1,463 @@ +/*** +* +* 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 "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/cl_dll/mssccprj.scc b/cl_dll/mssccprj.scc new file mode 100644 index 0000000..f943c7b --- /dev/null +++ b/cl_dll/mssccprj.scc @@ -0,0 +1,4 @@ +SCC = This is a Source Code Control file + +[cl_dll.mak] +SCC_Project_Name = "$/HLStandardSDK/SourceCode/cl_dll", NUWHAAAA diff --git a/cl_dll/parsemsg.cpp b/cl_dll/parsemsg.cpp new file mode 100644 index 0000000..d7070de --- /dev/null +++ b/cl_dll/parsemsg.cpp @@ -0,0 +1,166 @@ +/*** +* +* 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 +// +typedef unsigned char byte; +#define true 1 + +static byte *gpBuf; +static int giSize; +static int giRead; +static int 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)); +} + diff --git a/cl_dll/parsemsg.h b/cl_dll/parsemsg.h new file mode 100644 index 0000000..e741563 --- /dev/null +++ b/cl_dll/parsemsg.h @@ -0,0 +1,40 @@ +/*** +* +* 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 +// + +#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 ); + + + + + + + + + diff --git a/cl_dll/readme.txt b/cl_dll/readme.txt new file mode 100644 index 0000000..23d561b --- /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..34a66c8 --- /dev/null +++ b/cl_dll/saytext.cpp @@ -0,0 +1,231 @@ +/*** +* +* 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 "util.h" +#include "parsemsg.h" + +#include +#include + +#define MAX_LINES 5 +#define MAX_CHARS_PER_LINE 128 /* 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 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 ); +} + +int CHudSayText :: VidInit( void ) +{ + return 1; +} + + +void ScrollTextUp( void ) +{ + ConsolePrint( g_szLineBuffer[0] ); // move the first line into the console buffer + memmove( g_szLineBuffer[0], g_szLineBuffer[1], sizeof(g_szLineBuffer) - sizeof(g_szLineBuffer[0]) ); // overwrite the first line + + if ( g_szLineBuffer[0][0] == ' ' ) // also scroll up following lines + { + g_szLineBuffer[0][0] = 2; + ScrollTextUp(); + } +} + +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] ) + 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 ); + + return 1; +} + +void CHudSayText :: SayTextPrint( const char *pszBuf, int iBufSize ) +{ + // find an empty string slot + for ( int i = 0; i < MAX_LINES; i++ ) + { + if ( ! *g_szLineBuffer[i] ) + break; + } + if ( i == MAX_LINES ) + { + // force scroll buffer up + ScrollTextUp(); + i = MAX_LINES - 1; + } + + 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++ ) + { + 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 + for ( int j = 0; j < MAX_LINES; j++ ) + { + if ( ! *g_szLineBuffer[j] ) + break; + } + if ( j == MAX_LINES ) + { + j = MAX_LINES - 1; + } + + // 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/cl_dll/scoreboard.cpp b/cl_dll/scoreboard.cpp new file mode 100644 index 0000000..79ddafc --- /dev/null +++ b/cl_dll/scoreboard.cpp @@ -0,0 +1,528 @@ +/*** +* +* 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 "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 messages & commands here + HOOK_COMMAND( "+showscores", ShowScores ); + HOOK_COMMAND( "-showscores", HideScores ); + + HOOK_MESSAGE( ScoreInfo ); + HOOK_MESSAGE( TeamScore ); + HOOK_MESSAGE( TeamInfo ); + + InitHUDData(); + + return 1; +} + + +int CHudScoreboard :: VidInit( void ) +{ + // Load sprites here + + return 1; +} + +void CHudScoreboard :: InitHUDData( void ) +{ + memset( m_PlayerExtraInfo, 0, sizeof m_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/cl_dll/status_icons.cpp b/cl_dll/status_icons.cpp new file mode 100644 index 0000000..371594e --- /dev/null +++ b/cl_dll/status_icons.cpp @@ -0,0 +1,149 @@ +/*** +* +* 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. +* +****/ +// +// status_icons.cpp +// +#include "hud.h" +#include "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 + for ( int 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/cl_dll/statusbar.cpp b/cl_dll/statusbar.cpp new file mode 100644 index 0000000..e9d74c6 --- /dev/null +++ b/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 "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, &gHUD.m_Scoreboard.m_PlayerInfoList[indexval] ); + if ( gHUD.m_Scoreboard.m_PlayerInfoList[indexval].name != NULL ) + { + strncpy( szRepString, gHUD.m_Scoreboard.m_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/cl_dll/text_message.cpp b/cl_dll/text_message.cpp new file mode 100644 index 0000000..7cb1f60 --- /dev/null +++ b/cl_dll/text_message.cpp @@ -0,0 +1,207 @@ +/*** +* +* 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. +* +****/ +// +// text_message.cpp +// +// implementation of CHudTextMessage class +// +// this class routes messages through titles.txt for localisation +// + +#include "hud.h" +#include "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'; 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(); + + static char szBuf[6][128]; + char *msg_text = LookupString( READ_STRING(), &msg_dest ); + msg_text = strcpy( szBuf[0], msg_text ); + + // keep reading strings and using C format strings for subsituting the strings into the localised text string + char *sstr1 = LookupString( READ_STRING() ); + sstr1 = strcpy( szBuf[1], sstr1 ); + StripEndNewlineFromString( sstr1 ); // these strings are meant for subsitution into the main strings, so cull the automatic end newlines + char *sstr2 = LookupString( READ_STRING() ); + sstr2 = strcpy( szBuf[2], sstr2 ); + StripEndNewlineFromString( sstr2 ); + char *sstr3 = LookupString( READ_STRING() ); + sstr3 = strcpy( szBuf[3], sstr3 ); + StripEndNewlineFromString( sstr3 ); + char *sstr4 = LookupString( READ_STRING() ); + sstr4 = strcpy( szBuf[4], sstr4 ); + StripEndNewlineFromString( sstr4 ); + char *psz = szBuf[5]; + + switch ( msg_dest ) + { + case HUD_PRINTCENTER: + sprintf( psz, 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 + sprintf( psz+1, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + + case HUD_PRINTTALK: + sprintf( psz, msg_text, sstr1, sstr2, sstr3, sstr4 ); + gHUD.m_SayText.SayTextPrint( ConvertCRtoNL( psz ), 128 ); + break; + + case HUD_PRINTCONSOLE: + sprintf( psz, msg_text, sstr1, sstr2, sstr3, sstr4 ); + ConsolePrint( ConvertCRtoNL( psz ) ); + break; + } + + return 1; +} diff --git a/cl_dll/train.cpp b/cl_dll/train.cpp new file mode 100644 index 0000000..6cbe340 --- /dev/null +++ b/cl_dll/train.cpp @@ -0,0 +1,85 @@ +/*** +* +* 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. +* +****/ +// +// Train.cpp +// +// implementation of CHudAmmo class +// + +#include "hud.h" +#include "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/util.cpp b/cl_dll/util.cpp new file mode 100644 index 0000000..41e080b --- /dev/null +++ b/cl_dll/util.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. +* +****/ +// +// util.cpp +// +// implementation of class-less helper functions +// + +#include "STDIO.H" +#include "STDLIB.H" +#include "MATH.H" + +#include "hud.h" +#include "util.h" +#include + + + +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.h b/cl_dll/util.h new file mode 100644 index 0000000..2cda99d --- /dev/null +++ b/cl_dll/util.h @@ -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. +* +****/ +// +// util.h +// + +#include + +#include "../engine/cvardef.h" + +// 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 void CVAR_CREATE( const char *cv, const char *val, const int flags ) { 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];} + +// 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/util_vector.h b/cl_dll/util_vector.h new file mode 100644 index 0000000..5b28f6f --- /dev/null +++ b/cl_dll/util_vector.h @@ -0,0 +1,121 @@ +/*** +* +* 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. +* +****/ +// 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 int func_t; // +typedef 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/dlls/activity.h b/dlls/activity.h new file mode 100644 index 0000000..b44de8b --- /dev/null +++ b/dlls/activity.h @@ -0,0 +1,109 @@ +/*** +* +* 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 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..b02c666 --- /dev/null +++ b/dlls/activitymap.h @@ -0,0 +1,97 @@ +/*** +* +* 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. +* +****/ + +#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/airtank.cpp b/dlls/airtank.cpp new file mode 100644 index 0000000..5559222 --- /dev/null +++ b/dlls/airtank.cpp @@ -0,0 +1,118 @@ +/*** +* +* 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 "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( TankTouch ); + SetThink( 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..28adfc1 --- /dev/null +++ b/dlls/animating.cpp @@ -0,0 +1,313 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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/dlls/animation.cpp b/dlls/animation.cpp new file mode 100644 index 0000000..3ae95b1 --- /dev/null +++ b/dlls/animation.cpp @@ -0,0 +1,521 @@ +/*** +* +* 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 +#include + +// hack into header files that we can ship +typedef int qboolean; +typedef unsigned char byte; +#include "../utils/common/mathlib.h" +#include "const.h" +#include "progs.h" +#include "progdefs.h" +#include "eiface.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 + for (int 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; +} \ No newline at end of file diff --git a/dlls/animation.h b/dlls/animation.h new file mode 100644 index 0000000..db6898a --- /dev/null +++ b/dlls/animation.h @@ -0,0 +1,47 @@ +/*** +* +* 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 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/basemonster.h b/dlls/basemonster.h new file mode 100644 index 0000000..24f09af --- /dev/null +++ b/dlls/basemonster.h @@ -0,0 +1,94 @@ +/*** +* +* 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 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/dlls/bmodels.cpp b/dlls/bmodels.cpp new file mode 100644 index 0000000..f0dadac --- /dev/null +++ b/dlls/bmodels.cpp @@ -0,0 +1,958 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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( 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( 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( 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 ( 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( 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( 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 ( 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 ( 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 ( 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( 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( SUB_CallUseToggle ); + pev->nextthink = gpGlobals->time + 0.1; + } + pev->speed = 0; + SetUse( PendulumUse ); + + if ( FBitSet( pev->spawnflags, SF_PENDULUM_SWING ) ) + { + SetTouch ( 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( 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( 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/buttons.cpp b/dlls/buttons.cpp new file mode 100644 index 0000000..cb8ec9b --- /dev/null +++ b/dlls/buttons.cpp @@ -0,0 +1,1276 @@ +/*** +* +* 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. +* +****/ +/* + +===== buttons.cpp ======================================================== + + button-related code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "saverestore.h" +#include "doors.h" + + +#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(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(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 ( 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( ButtonTouch ); + } + else + { + SetTouch ( NULL ); + SetUse ( 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 ( 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( 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( ButtonTouch ); + } + else + { + pev->nextthink = pev->ltime + m_flWait; + SetThink( 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( 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( ButtonTouch ); + +// reset think for a sparking button + if ( FBitSet ( pev->spawnflags, SF_BUTTON_SPARK_IF_OFF ) ) + { + SetThink ( 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 ( ButtonUse ); + } + else // touchable button + SetTouch( 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( 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( 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(SparkThink); // start sparking + SetUse(SparkStop); // set up +USE to stop sparking + } + else + SetUse(SparkStart); + } + else + SetThink(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(SparkStop); + SetThink(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(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..af8d01a --- /dev/null +++ b/dlls/cbase.cpp @@ -0,0 +1,727 @@ +/*** +* +* 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 "saverestore.h" +#include "client.h" +#include "decals.h" +#include "gamerules.h" +#include "game.h" + +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 + + 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) +}; + +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 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( 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/dlls/cbase.h b/dlls/cbase.h new file mode 100644 index 0000000..5f75258 --- /dev/null +++ b/dlls/cbase.h @@ -0,0 +1,784 @@ +/*** +* +* 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. +* +****/ +/* + +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 "saverestore.h" +#include "schedule.h" + +#ifndef MONSTEREVENT_H +#include "monsterevent.h" +#endif + +// C functions for external declarations that call the appropriate C++ methods + +#define EXPORT _declspec( dllexport ) + +extern "C" EXPORT int GetEntityAPI( 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; } + +#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 ) + { + if (pFunction && !NAME_FOR_FUNCTION((unsigned long)(pFunction)) ) + ALERT( at_error, "No EXPORT: %s:%s (%08lx)\n", STRING(pev->classname), name, (unsigned long)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 ); +}; + + + +// 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..619324b --- /dev/null +++ b/dlls/cdll_dll.h @@ -0,0 +1,46 @@ +/*** +* +* 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_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 \ No newline at end of file diff --git a/dlls/client.cpp b/dlls/client.cpp new file mode 100644 index 0000000..126b7f2 --- /dev/null +++ b/dlls/client.cpp @@ -0,0 +1,729 @@ +/*** +* +* 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 "soundent.h" +#include "gamerules.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; + + +/* + * 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(); + + 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() ; +} + +//// 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 + for ( char *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; + + if ( teamonly && g_pGameRules->PlayerRelationship(client, CBaseEntity::Instance(pEntity)) != GR_TEAMMATE ) + continue; + + MESSAGE_BEGIN( MSG_ONE, gmsgSayText, NULL, client->pev ); + WRITE_BYTE( ENTINDEX(client->edict()) ); + 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 ); +} + + +/* +=========== +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, "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 + 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(); + + UTIL_LogPrintf( "\"%s<%i>\" changed name to \"%s<%i>\"\n", STRING( pEntity->v.netname ), GETPLAYERUSERID( pEntity ), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ), GETPLAYERUSERID( pEntity ) ); + } + + g_pGameRules->ClientUserInfoChanged( GetClassPtr((CBasePlayer *)&pEntity->v), infobuffer ); +} + + +void ServerActivate( edict_t *pEdictList, int edictCount, int clientMax ) +{ + int i; + CBaseEntity *pClass; + + // 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) ); + } + } +} + + +/* +================ +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.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 "Half-Life"; +} + +/* +================ +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( ); +} diff --git a/dlls/client.h b/dlls/client.h new file mode 100644 index 0000000..c2f9d52 --- /dev/null +++ b/dlls/client.h @@ -0,0 +1,41 @@ +/*** +* +* 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 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 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 ); + +#endif // CLIENT_H diff --git a/dlls/combat.cpp b/dlls/combat.cpp new file mode 100644 index 0000000..98f9195 --- /dev/null +++ b/dlls/combat.cpp @@ -0,0 +1,1666 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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 ( 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 ( 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 ( 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 ( 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 (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 ( 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 ( WaitTillLand ); + SetTouch ( 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. +================ +*/ +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: + 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; + /* + case BULLET_12MM: + WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE(MSG_BROADCAST, TE_RAILTRAIL); + WRITE_COORD(MSG_BROADCAST, vecSrc.x); + WRITE_COORD(MSG_BROADCAST, vecSrc.y); + WRITE_COORD(MSG_BROADCAST, vecSrc.z); + WRITE_COORD(MSG_BROADCAST, tr.vecEndPos.x); + WRITE_COORD(MSG_BROADCAST, tr.vecEndPos.y); + WRITE_COORD(MSG_BROADCAST, tr.vecEndPos.z); + 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); + if ( !tracer ) + { + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + } + break; + + case BULLET_PLAYER_MP5: + pEntity->TraceAttack(pevAttacker, gSkillData.plrDmgMP5, vecDir, &tr, DMG_BULLET); + if ( !tracer ) + { + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + } + break; + + case BULLET_PLAYER_BUCKSHOT: + // make distance based! + pEntity->TraceAttack(pevAttacker, gSkillData.plrDmgBuckshot, vecDir, &tr, DMG_BULLET); + if ( !tracer ) + { + // TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + } + break; + + case BULLET_PLAYER_357: + pEntity->TraceAttack(pevAttacker, gSkillData.plrDmg357, vecDir, &tr, DMG_BULLET); + if ( !tracer ) + { + TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); + DecalGunshot( &tr, iBulletType ); + } + 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/dlls/crossbow.cpp b/dlls/crossbow.cpp new file mode 100644 index 0000000..7020a72 --- /dev/null +++ b/dlls/crossbow.cpp @@ -0,0 +1,600 @@ +/*** +* +* 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. +* +****/ +#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" + +#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( BoltTouch ); + SetThink( 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_WEAPON, "weapons/xbow_hitbod1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/xbow_hitbod2.wav", 1, ATTN_NORM); break; + } + + if ( !g_pGameRules->IsMultiplayer() ) + { + Killed( pev, GIB_NEVER ); + } + } + else + { + EMIT_SOUND_DYN(ENT(pev), CHAN_WEAPON, "weapons/xbow_hit1.wav", RANDOM_FLOAT(0.95, 1.0), ATTN_NORM, 0, 98 + RANDOM_LONG(0,7)); + + SetThink( 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( 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); +} + + +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 +}; + + +class CCrossbow : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( ) { return 3; } + int GetItemInfo(ItemInfo *p); + + void FireBolt( void ); + void FireSniperBolt( void ); + void PrimaryAttack( void ); + void SecondaryAttack( void ); + int AddToPlayer( CBasePlayer *pPlayer ); + BOOL Deploy( ); + void Holster( ); + void Reload( void ); + void WeaponIdle( void ); + + int m_fInZoom; // don't save this +}; +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" ); +} + + +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( ) +{ + m_fInReload = FALSE;// cancel any reload in progress. + + if ( m_fInZoom ) + { + SecondaryAttack( ); + } + + m_pPlayer->m_flNextAttack = gpGlobals->time + 0.5; + if (m_iClip) + SendWeaponAnim( CROSSBOW_HOLSTER1 ); + else + SendWeaponAnim( CROSSBOW_HOLSTER2 ); +} + +void CCrossbow::PrimaryAttack( void ) +{ + if ( m_fInZoom && g_pGameRules->IsMultiplayer() ) + { + FireSniperBolt(); + return; + } + + FireBolt(); +} + +// this function only gets called in multiplayer +void CCrossbow::FireSniperBolt() +{ + m_flNextPrimaryAttack = gpGlobals->time + 0.75; + + if (m_iClip == 0) + { + PlayEmptySound( ); + return; + } + + TraceResult tr; + + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + m_iClip--; + + // make twang sound + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/xbow_fire1.wav", RANDOM_FLOAT(0.95, 1.0), ATTN_NORM, 0, 93 + RANDOM_LONG(0,0xF)); + + if (m_iClip) + { + 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)); + SendWeaponAnim( CROSSBOW_FIRE1 ); + } + else if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] == 0) + { + SendWeaponAnim( CROSSBOW_FIRE3 ); + } + + // 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); + + if ( tr.pHit->v.takedamage ) + { + switch( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND( tr.pHit, CHAN_BODY, "weapons/xbow_hitbod1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND( tr.pHit, CHAN_BODY, "weapons/xbow_hitbod2.wav", 1, ATTN_NORM); break; + } + + ClearMultiDamage( ); + CBaseEntity::Instance(tr.pHit)->TraceAttack(m_pPlayer->pev, 120, vecDir, &tr, DMG_BULLET | DMG_NEVERGIB ); + ApplyMultiDamage( pev, m_pPlayer->pev ); + } + else + { + // create a bolt + CCrossbowBolt *pBolt = CCrossbowBolt::BoltCreate(); + pBolt->pev->origin = tr.vecEndPos - vecDir * 10; + pBolt->pev->angles = UTIL_VecToAngles( vecDir ); + pBolt->pev->solid = SOLID_NOT; + pBolt->SetTouch( NULL ); + pBolt->SetThink( SUB_Remove ); + + EMIT_SOUND( pBolt->edict(), CHAN_WEAPON, "weapons/xbow_hit1.wav", RANDOM_FLOAT(0.95, 1.0), ATTN_NORM ); + + if (UTIL_PointContents(tr.vecEndPos) != CONTENTS_WATER) + { + UTIL_Sparks( tr.vecEndPos ); + } + + if ( FClassnameIs( tr.pHit, "worldspawn" ) ) + { + // let the bolt sit around for a while if it hit static architecture + pBolt->pev->nextthink = gpGlobals->time + 5.0; + } + else + { + pBolt->pev->nextthink = gpGlobals->time; + } + } +} + +void CCrossbow::FireBolt() +{ + TraceResult tr; + + if (m_iClip == 0) + { + PlayEmptySound( ); + return; + } + + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + + m_iClip--; + + // make twang sound + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/xbow_fire1.wav", RANDOM_FLOAT(0.95, 1.0), ATTN_NORM, 0, 93 + RANDOM_LONG(0,0xF)); + + if (m_iClip) + { + + 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)); + SendWeaponAnim( CROSSBOW_FIRE1 ); + } + else if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] == 0) + { + SendWeaponAnim( CROSSBOW_FIRE3 ); + } + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector anglesAim = m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle; + UTIL_MakeVectors( anglesAim ); + + // Vector vecSrc = pev->origin + gpGlobals->v_up * 16 + gpGlobals->v_forward * 20 + gpGlobals->v_right * 4; + anglesAim.x = -anglesAim.x; + Vector vecSrc = m_pPlayer->GetGunPosition( ) - gpGlobals->v_up * 2; + Vector vecDir = gpGlobals->v_forward; + + //CBaseEntity *pBolt = CBaseEntity::Create( "crossbow_bolt", vecSrc, anglesAim, m_pPlayer->edict() ); + 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; + + 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 = gpGlobals->time + 0.75; + + m_flNextSecondaryAttack = gpGlobals->time + 0.75; + if (m_iClip != 0) + m_flTimeWeaponIdle = gpGlobals->time + 5.0; + else + m_flTimeWeaponIdle = 0.75; + + m_pPlayer->pev->punchangle.x -= 2; +} + + +void CCrossbow::SecondaryAttack() +{ + if (m_fInZoom) + { + m_pPlayer->m_iFOV = 0; // 0 means reset to default fov + m_fInZoom = 0; + } + else + { + m_pPlayer->m_iFOV = 20; + m_fInZoom = 1; + } + + pev->nextthink = gpGlobals->time + 0.1; + m_flNextSecondaryAttack = gpGlobals->time + 1.0; +} + + +void CCrossbow::Reload( void ) +{ + if ( m_fInZoom ) + { + 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 < gpGlobals->time) + { + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.75) + { + if (m_iClip) + { + SendWeaponAnim( CROSSBOW_IDLE1 ); + } + else + { + SendWeaponAnim( CROSSBOW_IDLE2 ); + } + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + } + else + { + if (m_iClip) + { + SendWeaponAnim( CROSSBOW_FIDGET1 ); + m_flTimeWeaponIdle = gpGlobals->time + 90.0 / 30.0; + } + else + { + SendWeaponAnim( CROSSBOW_FIDGET2 ); + m_flTimeWeaponIdle = gpGlobals->time + 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 \ No newline at end of file diff --git a/dlls/crowbar.cpp b/dlls/crowbar.cpp new file mode 100644 index 0000000..9c1a465 --- /dev/null +++ b/dlls/crowbar.cpp @@ -0,0 +1,334 @@ +/*** +* +* 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 "nodes.h" +#include "player.h" +#include "gamerules.h" + + +#define CROWBAR_BODYHIT_VOLUME 128 +#define CROWBAR_WALLHIT_VOLUME 512 + +class CCrowbar : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 1; } + void EXPORT SwingAgain( void ); + void EXPORT Smack( void ); + int GetItemInfo(ItemInfo *p); + + void PrimaryAttack( void ); + int Swing( int fFirst ); + BOOL Deploy( void ); + void Holster( void ); + int m_iSwing; + TraceResult m_trHit; +}; +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"); +} + +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( ) +{ + m_pPlayer->m_flNextAttack = gpGlobals->time + 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( 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 ); + + 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) + } + } + + if ( tr.flFraction >= 1.0 ) + { + if (fFirst) + { + // miss + switch( (m_iSwing++) % 3 ) + { + case 0: + SendWeaponAnim( CROWBAR_ATTACK1MISS ); break; + case 1: + SendWeaponAnim( CROWBAR_ATTACK2MISS ); break; + case 2: + SendWeaponAnim( CROWBAR_ATTACK3MISS ); break; + } + m_flNextPrimaryAttack = gpGlobals->time + 0.5; + // play wiff or swish sound + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/cbar_miss1.wav", 1, ATTN_NORM, 0, 94 + RANDOM_LONG(0,0xF)); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + } + } + else + { + // hit + fDidHit = TRUE; + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + 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 ); + + ClearMultiDamage( ); + if ( (m_flNextPrimaryAttack + 1 < gpGlobals->time) || 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 ); + + m_flNextPrimaryAttack = gpGlobals->time + 0.25; + + // 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_WEAPON, "weapons/cbar_hitbod1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/cbar_hitbod2.wav", 1, ATTN_NORM); break; + case 2: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "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: + //UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "weapons/cbar_hit1.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + 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: + //UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "weapons/cbar_hit2.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + 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; + SetThink( Smack ); + pev->nextthink = gpGlobals->time + 0.2; + + m_pPlayer->m_iWeaponVolume = flVol * CROWBAR_WALLHIT_VOLUME; + } + return fDidHit; +} + + + diff --git a/dlls/decals.h b/dlls/decals.h new file mode 100644 index 0000000..bfb3ed5 --- /dev/null +++ b/dlls/decals.h @@ -0,0 +1,75 @@ +/*** +* +* 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 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/doors.cpp b/dlls/doors.cpp new file mode 100644 index 0000000..5877252 --- /dev/null +++ b/dlls/doors.cpp @@ -0,0 +1,1026 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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( 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( 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( DoorTouch ); + } + else + { + // In flWait seconds, DoorGoDown will fire, unless wait is -1, then door stays open + pev->nextthink = pev->ltime + m_flWait; + SetThink( 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( 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( 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( 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 ); + } + +} \ No newline at end of file diff --git a/dlls/doors.h b/dlls/doors.h new file mode 100644 index 0000000..a4e7b62 --- /dev/null +++ b/dlls/doors.h @@ -0,0 +1,33 @@ +/*** +* +* 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 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..1d3d4d2 --- /dev/null +++ b/dlls/effects.cpp @@ -0,0 +1,2264 @@ +/*** +* +* 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 "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( 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( 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( SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + pev->dmgtime = gpGlobals->time; + + if ( ServerSide() ) + { + SetThink( NULL ); + if ( pev->dmg > 0 ) + { + SetThink( 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( ToggleUse ); + } + } + else + { + m_active = 0; + if ( !FStringNull(pev->targetname) ) + { + SetUse( StrikeUse ); + } + if ( FStringNull(pev->targetname) || FBitSet(pev->spawnflags, SF_BEAM_STARTON) ) + { + SetThink( 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( 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 ) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + if ( IsPointEntity( pStart ) || IsPointEntity( pEnd ) ) + { + if ( pev->spawnflags & SF_BEAM_RING) + { + // don't work + return; + } + 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( SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; // Remove model & collisions + Precache( ); + + SetThink( 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( 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( 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( 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 ( 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( 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( 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 (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 ( 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 ( SUB_Remove ); + pev->nextthink = gpGlobals->time; +} \ No newline at end of file diff --git a/dlls/effects.h b/dlls/effects.h new file mode 100644 index 0000000..9b5430b --- /dev/null +++ b/dlls/effects.h @@ -0,0 +1,209 @@ +/*** +* +* 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 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(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(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..46e3265 --- /dev/null +++ b/dlls/egon.cpp @@ -0,0 +1,623 @@ +/*** +* +* 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. +* +****/ +#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 +}; + + +class CEgon : public CBasePlayerWeapon +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 4; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + BOOL Deploy( void ); + void Holster( void ); + + void CreateEffect( void ); + void UpdateEffect( const Vector &startPoint, const Vector &endPoint, float timeBlend ); + void DestroyEffect( void ); + + void EndAttack( void ); + void Attack( void ); + void PrimaryAttack( void ); + void WeaponIdle( void ); + static int g_fireAnims1[]; + static int g_fireAnims2[]; + + float m_flAmmoUseTime;// since we use < 1 point of ammo per update, we subtract ammo on a timer. + + float GetPulseInterval( void ); + float GetDischargeInterval( void ); + + void Fire( const Vector &vecOrigSrc, const Vector &vecDir ); + + BOOL HasAmmo( void ) + { + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + return FALSE; + return TRUE; + } + + void 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; + } + + enum EGON_FIRESTATE { FIRE_OFF, FIRE_CHARGE }; + enum EGON_FIREMODE { FIRE_NARROW, FIRE_WIDE}; + +private: + float m_shootTime; + CBeam *m_pBeam; + CBeam *m_pNoise; + CSprite *m_pSprite; + EGON_FIRESTATE m_fireState; + EGON_FIREMODE m_fireMode; + float m_shakeTime; + BOOL m_deployed; +}; + +LINK_ENTITY_TO_CLASS( weapon_egon, CEgon ); + +int CEgon::g_fireAnims1[] = { EGON_FIRE1, EGON_FIRE2, EGON_FIRE3, EGON_FIRE4 }; +int CEgon::g_fireAnims2[] = { EGON_ALTFIRECYCLE }; + + +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 ); + + +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"); +} + + +BOOL CEgon::Deploy( void ) +{ + m_deployed = FALSE; + 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( void ) +{ + m_pPlayer->m_flNextAttack = gpGlobals->time + 0.5; + // m_flTimeWeaponIdle = gpGlobals->time + UTIL_RandomFloat ( 10, 15 ); + SendWeaponAnim( EGON_HOLSTER ); + + if ( m_fireState != FIRE_OFF ) + 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.25 +//#define EGON_DISCHARGE_INTERVAL 0.5 + +#define EGON_PULSE_INTERVAL 0.1 +#define EGON_DISCHARGE_INTERVAL 0.1 + +float CEgon::GetPulseInterval( void ) +{ + if ( g_pGameRules->IsMultiplayer() ) + { + return 0.1; + } + + return EGON_PULSE_INTERVAL; +} + +float CEgon::GetDischargeInterval( void ) +{ + if ( g_pGameRules->IsMultiplayer() ) + { + return 0.1; + } + + return EGON_DISCHARGE_INTERVAL; +} + +void CEgon::Attack( void ) +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + if ( 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( ); + + switch( m_fireState ) + { + case FIRE_OFF: + { + if ( !HasAmmo() ) + { + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->time + 0.25; + PlayEmptySound( ); + return; + } + + m_flAmmoUseTime = gpGlobals->time;// start using ammo ASAP. + + SendWeaponAnim( g_fireAnims1[ RANDOM_LONG(0,ARRAYSIZE(g_fireAnims1)-1) ] ); + m_shakeTime = 0; + + m_pPlayer->m_iWeaponVolume = EGON_PRIMARY_VOLUME; + m_flTimeWeaponIdle = gpGlobals->time + 0.1; + m_shootTime = gpGlobals->time + 2; + + if ( m_fireMode == FIRE_WIDE ) + { + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, EGON_SOUND_STARTUP, 0.98, ATTN_NORM, 0, 125 ); + } + else + { + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, EGON_SOUND_STARTUP, 0.9, ATTN_NORM, 0, 100 ); + } + + pev->dmgtime = gpGlobals->time + GetPulseInterval(); + m_fireState = FIRE_CHARGE; + } + break; + + case FIRE_CHARGE: + { + Fire( vecSrc, vecAiming ); + m_pPlayer->m_iWeaponVolume = EGON_PRIMARY_VOLUME; + + if ( m_shootTime != 0 && gpGlobals->time > m_shootTime ) + { + if ( m_fireMode == FIRE_WIDE ) + { + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_STATIC, EGON_SOUND_RUN, 0.98, ATTN_NORM, 0, 125 ); + } + else + { + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_STATIC, EGON_SOUND_RUN, 0.9, ATTN_NORM, 0, 100 ); + } + + m_shootTime = 0; + } + if ( !HasAmmo() ) + { + EndAttack(); + m_fireState = FIRE_OFF; + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->time + 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; + + 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; + } + } + + float timedist; + + switch ( m_fireMode ) + { + case FIRE_NARROW: + 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(); + } + timedist = ( pev->dmgtime - gpGlobals->time ) / GetPulseInterval(); + break; + + case FIRE_WIDE: + 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; + } + } + 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 ) +{ + 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 ); +} + + +void CEgon::CreateEffect( void ) +{ + 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_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_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; + + 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 ); + } +} + + +void CEgon::DestroyEffect( void ) +{ + 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; + } +} + + +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 = gpGlobals->time + RANDOM_FLOAT(10,15); + } + else + { + iAnim = EGON_FIDGET1; + m_flTimeWeaponIdle = gpGlobals->time + 3; + } + + SendWeaponAnim( iAnim ); + m_deployed = TRUE; +} + + + +void CEgon::EndAttack( void ) +{ + STOP_SOUND( ENT(m_pPlayer->pev), CHAN_STATIC, EGON_SOUND_RUN ); + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, EGON_SOUND_OFF, 0.98, ATTN_NORM, 0, 100); + m_fireState = FIRE_OFF; + m_flTimeWeaponIdle = gpGlobals->time + 2.0; + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->time + 0.5; + 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..d8c8a6a --- /dev/null +++ b/dlls/enginecallback.h @@ -0,0 +1,123 @@ +/*** +* +* 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 ENGINECALLBACK_H +#define ENGINECALLBACK_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) + +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) + +#endif //ENGINECALLBACK_H \ No newline at end of file diff --git a/dlls/explode.cpp b/dlls/explode.cpp new file mode 100644 index 0000000..c6146d6 --- /dev/null +++ b/dlls/explode.cpp @@ -0,0 +1,273 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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( 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..26d08aa --- /dev/null +++ b/dlls/explode.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. +* +****/ +#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..dc8aeb8 --- /dev/null +++ b/dlls/extdll.h @@ -0,0 +1,69 @@ +/*** +* +* 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 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 + +// Prevent tons of unused windows definitions +#define WIN32_LEAN_AND_MEAN +#define NOWINRES +#define NOSERVICE +#define NOMCX +#define NOIME +#include "WINDOWS.H" + +// 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 int func_t; // +typedef 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 "progs.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/func_break.cpp b/dlls/func_break.cpp new file mode 100644 index 0000000..6c3d0c2 --- /dev/null +++ b/dlls/func_break.cpp @@ -0,0 +1,998 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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( 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 ( 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( 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..f478036 --- /dev/null +++ b/dlls/func_break.h @@ -0,0 +1,74 @@ +/*** +* +* 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 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..543c12a --- /dev/null +++ b/dlls/func_tank.cpp @@ -0,0 +1,1035 @@ +/*** +* +* 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 "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/dlls/game.cpp b/dlls/game.cpp new file mode 100644 index 0000000..fdccdc9 --- /dev/null +++ b/dlls/game.cpp @@ -0,0 +1,876 @@ +/*** +* +* 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 "eiface.h" +#include "util.h" +#include "game.h" + + +cvar_t displaysoundlist = {"displaysoundlist","0"}; +cvar_t mapcyclefile = {"mapcyclefile","mapcycle.txt"}; +cvar_t servercfgfile = {"servercfgfile","server.cfg"}; +cvar_t lservercfgfile = {"lservercfgfile","listenserver.cfg"}; + +// 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 footsteps = {"mp_footsteps","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 }; + +//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: + + CVAR_REGISTER (&displaysoundlist); + CVAR_REGISTER (&mapcyclefile); + CVAR_REGISTER (&servercfgfile); + CVAR_REGISTER (&lservercfgfile); + + CVAR_REGISTER (&teamplay); + CVAR_REGISTER (&fraglimit); + CVAR_REGISTER (&timelimit); + + CVAR_REGISTER (&friendlyfire); + CVAR_REGISTER (&falldamage); + CVAR_REGISTER (&weaponstay); + CVAR_REGISTER (&forcerespawn); + CVAR_REGISTER (&footsteps); + 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" ); +} + diff --git a/dlls/game.h b/dlls/game.h new file mode 100644 index 0000000..f2332bf --- /dev/null +++ b/dlls/game.h @@ -0,0 +1,42 @@ +/*** +* +* 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 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 footsteps; +extern cvar_t flashlight; +extern cvar_t aimcrosshair; +extern cvar_t decalfrequency; +extern cvar_t teamlist; +extern cvar_t teamoverride; +extern cvar_t defaultteam; + +#endif // GAME_H diff --git a/dlls/gamerules.cpp b/dlls/gamerules.cpp new file mode 100644 index 0000000..23d24dd --- /dev/null +++ b/dlls/gamerules.cpp @@ -0,0 +1,335 @@ +/*** +* +* 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. +* +****/ +//========================================================= +// 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 ) +{ + 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 + { + 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/dlls/gamerules.h b/dlls/gamerules.h new file mode 100644 index 0000000..081b65b --- /dev/null +++ b/dlls/gamerules.h @@ -0,0 +1,359 @@ +/*** +* +* 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. +* +****/ +//========================================================= +// 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 ); + +// 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/gauss.cpp b/dlls/gauss.cpp new file mode 100644 index 0000000..8734fcf --- /dev/null +++ b/dlls/gauss.cpp @@ -0,0 +1,857 @@ +/*** +* +* 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. +* +****/ +#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 +}; + +class CGauss : public CBasePlayerWeapon +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 4; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + BOOL Deploy( void ); + void Holster( void ); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + void WeaponIdle( void ); + + int m_fInAttack; + float m_flStartCharge; + float m_flPlayAftershock; + void StartFire( void ); + void Fire( Vector vecOrigSrc, Vector vecDirShooting, float flDamage ); + float GetFullChargeTime( void ); + int m_iBalls; + int m_iGlow; + int m_iBeam; + int m_iSoundState; // don't save this + + float m_flNextAmmoBurn;// while charging, when to absorb another unit of player's ammo? + + // was this weapon just fired primary or secondary? + // we need to know so we can pick the right set of effects. + BOOL m_fPrimaryFire; +}; +LINK_ENTITY_TO_CLASS( weapon_gauss, CGauss ); + + +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 ); + + +float CGauss::GetFullChargeTime( void ) +{ + if ( g_pGameRules->IsMultiplayer() ) + { + return 1.5; + } + + return 4; +} + +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" ); +} + +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( ) +{ + return DefaultDeploy( "models/v_gauss.mdl", "models/p_gauss.mdl", GAUSS_DRAW, "gauss" ); +} + + +void CGauss::Holster( ) +{ + m_pPlayer->m_flNextAttack = gpGlobals->time + 0.5; + // m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + SendWeaponAnim( GAUSS_HOLSTER ); + m_fInAttack = 0; + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + + +void CGauss::PrimaryAttack() +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->time + 0.15; + return; + } + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] < 2) + { + PlayEmptySound( ); + m_pPlayer->m_flNextAttack = gpGlobals->time + 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 = gpGlobals->time + 1.0; + m_pPlayer->m_flNextAttack = gpGlobals->time + 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 = gpGlobals->time + 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 = gpGlobals->time + 0.5; + return; + } + + m_fPrimaryFire = FALSE; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--;// take one ammo just to start the spin + m_flNextAmmoBurn = gpGlobals->time; + + // spin up + m_pPlayer->m_iWeaponVolume = GAUSS_PRIMARY_CHARGE_VOLUME; + + SendWeaponAnim( GAUSS_SPINUP ); + m_fInAttack = 1; + m_flTimeWeaponIdle = gpGlobals->time + 0.5; + m_flStartCharge = gpGlobals->time; + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "ambience/pulsemachine.wav",1.0 , ATTN_NORM, 0, 110 ); + m_iSoundState = SND_CHANGE_PITCH; + } + else if (m_fInAttack == 1) + { + if (m_flTimeWeaponIdle < gpGlobals->time) + { + SendWeaponAnim( GAUSS_SPIN ); + m_fInAttack = 2; + } + } + else + { + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] == 0 ) + { + // out of ammo! force the gun to fire + StartFire(); + m_fInAttack = 0; + m_flTimeWeaponIdle = gpGlobals->time + 1.0; + m_pPlayer->m_flNextAttack = gpGlobals->time + 1; + return; + } + + // during the charging process, eat one bit of ammo every once in a while + if ( gpGlobals->time > m_flNextAmmoBurn && m_flNextAmmoBurn != -1 ) + { + if ( g_pGameRules->IsMultiplayer() ) + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_flNextAmmoBurn = gpGlobals->time + 0.1; + } + else + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_flNextAmmoBurn = gpGlobals->time + 0.3; + } + } + + if ( gpGlobals->time - m_flStartCharge >= GetFullChargeTime() ) + { + // don't eat any more ammo after gun is fully charged. + m_flNextAmmoBurn = -1; + } + + int pitch = (gpGlobals->time - 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 ); + + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "ambience/pulsemachine.wav", 1.0, ATTN_NORM, m_iSoundState, pitch); + + m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions + + m_pPlayer->m_iWeaponVolume = GAUSS_PRIMARY_CHARGE_VOLUME; + + // m_flTimeWeaponIdle = gpGlobals->time + 0.1; + if (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 = gpGlobals->time + 1.0; + m_pPlayer->m_flNextAttack = gpGlobals->time + 1.0; + m_pPlayer->TakeDamage( VARS(eoNullEntity), VARS(eoNullEntity), 50, DMG_SHOCK ); + + UTIL_ScreenFade( m_pPlayer, Vector(255,128,0), 2, 0.5, 128, FFADE_IN ); + 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_flStartCharge > GetFullChargeTime()) + { + flDamage = 200; + } + else + { + flDamage = 200 * ((gpGlobals->time - m_flStartCharge) / GetFullChargeTime() ); + } + + if ( m_fPrimaryFire ) + { + // fixed damage on primary attack + flDamage = gSkillData.plrDmgGauss; + m_pPlayer->pev->punchangle.x = -2;// punch now, after building aim vector + } + + if (m_fInAttack != 3) + { + //ALERT ( at_console, "Time:%f Damage:%f\n", gpGlobals->time - m_flStartCharge, flDamage ); + + 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->IsDeathmatch() ) + { + // in deathmatch, gauss can pop you up into the air. Not in single play. + m_pPlayer->pev->velocity.z = flZVel; + } + + SendWeaponAnim( GAUSS_FIRE2 ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + } + STOP_SOUND( ENT(m_pPlayer->pev), CHAN_WEAPON, "ambience/pulsemachine.wav" ); + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/gauss2.wav", 0.5 + flDamage * (1.0 / 400.0), ATTN_NORM, 0, 85 + RANDOM_LONG(0,0x1f)); + // time until aftershock 'static discharge' sound + m_flPlayAftershock = gpGlobals->time + RANDOM_FLOAT(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 ); + + /* + 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 ); + + 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; + + Vector tmpSrc = vecSrc + gpGlobals->v_up * -8 + gpGlobals->v_right * 3; + + // don't draw beam until the damn thing looks like it's coming out of the barrel + // draw beam + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, tr.vecEndPos ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( m_pPlayer->entindex() + 0x1000 ); + WRITE_COORD( tr.vecEndPos.x); + WRITE_COORD( tr.vecEndPos.y); + WRITE_COORD( tr.vecEndPos.z); + WRITE_SHORT( m_iBeam ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 1 ); // life + + if ( m_fPrimaryFire ) + { + WRITE_BYTE( 10 ); // width + } + else + { + WRITE_BYTE( 25 ); // width + } + + WRITE_BYTE( 0 ); // noise + + if ( m_fPrimaryFire ) + { + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 0 ); // r, g, b + + WRITE_BYTE( 128 ); // brightness + } + else + { + // secondary shot is always white, and intensity based on charge + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + + WRITE_BYTE( flDamage ); // brightness + } + + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + nTotal += 26; + } + else + { + // draw beam + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, vecSrc ); + 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_iBeam ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 1 ); // life + + if ( m_fPrimaryFire ) + { + WRITE_BYTE( 10 ); // width + } + else + { + WRITE_BYTE( 25 ); // width + } + + WRITE_BYTE( 0 ); // noise + + if ( m_fPrimaryFire ) + { + // primary shot always looks full intensity + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 0 ); // r, g, b + + WRITE_BYTE( 128 ); // brightness + } + else + { + // secondary shot is always white, and intensity based on charge + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + + WRITE_BYTE( flDamage ); // brightness + } + + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + nTotal += 26; + } + + if (pEntity->pev->takedamage) + { + ClearMultiDamage(); + pEntity->TraceAttack( m_pPlayer->pev, flDamage, vecDir, &tr, DMG_BULLET ); + ApplyMultiDamage(m_pPlayer->pev, m_pPlayer->pev); + } + + // ALERT( at_console, "%s\n", STRING( pEntity->pev->classname )); + + 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 ); + + // bounce wall glow + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, tr.vecEndPos ); + WRITE_BYTE( TE_GLOWSPRITE ); + WRITE_COORD( tr.vecEndPos.x); // pos + WRITE_COORD( tr.vecEndPos.y); + WRITE_COORD( tr.vecEndPos.z); + WRITE_SHORT( m_iGlow ); // model + WRITE_BYTE( flDamage * n * 0.5 ); // life * 10 + WRITE_BYTE( 2 ); // size * 10 + WRITE_BYTE( flDamage * n ); // brightness + MESSAGE_END(); + + nTotal += 13; + + // balls + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, tr.vecEndPos ); + WRITE_BYTE( TE_SPRITETRAIL );// TE_RAILTRAIL); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + WRITE_COORD( tr.vecEndPos.x + tr.vecPlaneNormal.x ); + WRITE_COORD( tr.vecEndPos.y + tr.vecPlaneNormal.y ); + WRITE_COORD( tr.vecEndPos.z + tr.vecPlaneNormal.z ); + WRITE_SHORT( m_iBalls ); // model + WRITE_BYTE( n * flDamage * 0.3 ); // count + WRITE_BYTE( 10 ); // life * 10 + WRITE_BYTE( RANDOM_LONG( 1, 2 ) ); // size * 10 + WRITE_BYTE( 10 ); // amplitude * 0.1 + WRITE_BYTE( 20 ); // speed * 100 + MESSAGE_END(); + + nTotal += 21; + + // lose energy + if (n == 0) n = 0.1; + flDamage = flDamage * (1 - n); + } + else + { + // tunnel + DecalGunshot( &tr, BULLET_MONSTER_12MM ); + + // entry wall glow + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, tr.vecEndPos ); + WRITE_BYTE( TE_GLOWSPRITE ); + WRITE_COORD( tr.vecEndPos.x); // pos + WRITE_COORD( tr.vecEndPos.y); + WRITE_COORD( tr.vecEndPos.z); + WRITE_SHORT( m_iGlow ); // model + WRITE_BYTE( 60 ); // life * 10 + WRITE_BYTE( 10 ); // size * 10 + WRITE_BYTE( flDamage ); // brightness + MESSAGE_END(); + 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 ); + + // absorption balls + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, tr.vecEndPos ); + WRITE_BYTE( TE_SPRITETRAIL );// TE_RAILTRAIL); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + WRITE_COORD( tr.vecEndPos.x - vecDir.x ); + WRITE_COORD( tr.vecEndPos.y - vecDir.y ); + WRITE_COORD( tr.vecEndPos.z - vecDir.z ); + WRITE_SHORT( m_iBalls ); // model + WRITE_BYTE( 3 ); // count + WRITE_BYTE( 10 ); // life * 10 + WRITE_BYTE( RANDOM_LONG( 1, 2 ) ); // size * 10 + WRITE_BYTE( 10 ); // amplitude * 0.1 + WRITE_BYTE( 1 ); // speed * 100 + MESSAGE_END(); + nTotal += 21; + + // exit blast damage + m_pPlayer->RadiusDamage( beam_tr.vecEndPos + vecDir * 8, pev, m_pPlayer->pev, flDamage, CLASS_NONE, DMG_BLAST ); + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); + + DecalGunshot( &beam_tr, BULLET_MONSTER_12MM ); + nTotal += 19; + + // exit wall glow + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, beam_tr.vecEndPos ); + WRITE_BYTE( TE_GLOWSPRITE ); + WRITE_COORD( beam_tr.vecEndPos.x); // pos + WRITE_COORD( beam_tr.vecEndPos.y); + WRITE_COORD( beam_tr.vecEndPos.z); + WRITE_SHORT( m_iGlow ); // model + WRITE_BYTE( 60 ); // life * 10 + WRITE_BYTE( 10 ); // size * 10 + WRITE_BYTE( flDamage ); // brightness + MESSAGE_END(); + nTotal += 13; + + // balls + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, beam_tr.vecEndPos ); + WRITE_BYTE( TE_SPRITETRAIL );// TE_RAILTRAIL); + WRITE_COORD( beam_tr.vecEndPos.x ); + WRITE_COORD( beam_tr.vecEndPos.y ); + WRITE_COORD( beam_tr.vecEndPos.z ); + WRITE_COORD( beam_tr.vecEndPos.x + vecDir.x ); + WRITE_COORD( beam_tr.vecEndPos.y + vecDir.y ); + WRITE_COORD( beam_tr.vecEndPos.z + vecDir.z ); + WRITE_SHORT( m_iBalls ); // model + WRITE_BYTE( flDamage * 0.3 ); // count + WRITE_BYTE( 10 ); // life * 10 + WRITE_BYTE( RANDOM_LONG( 1, 2 ) ); // size * 10 + WRITE_BYTE( 20 ); // amplitude * 0.1 + WRITE_BYTE( 40 ); // speed * 100 + MESSAGE_END(); + nTotal += 21; + + vecSrc = beam_tr.vecEndPos + vecDir; + } + } + else + { + //ALERT( at_console, "blocked %f\n", n ); + flDamage = 0; + } + } + else + { + //ALERT( at_console, "blocked solid\n" ); + + if ( m_fPrimaryFire ) + { + // slug doesn't punch through ever with primary + // fire, so leave a little glowy bit and make some balls + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, tr.vecEndPos ); + WRITE_BYTE( TE_GLOWSPRITE ); + WRITE_COORD( tr.vecEndPos.x); // pos + WRITE_COORD( tr.vecEndPos.y); + WRITE_COORD( tr.vecEndPos.z); + WRITE_SHORT( m_iGlow ); // model + WRITE_BYTE( 20 ); // life * 10 + WRITE_BYTE( 3 ); // size * 10 + WRITE_BYTE( 200 ); // brightness + MESSAGE_END(); + + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, tr.vecEndPos ); + WRITE_BYTE( TE_SPRITETRAIL );// TE_RAILTRAIL); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + WRITE_COORD( tr.vecEndPos.x + tr.vecPlaneNormal.x ); + WRITE_COORD( tr.vecEndPos.y + tr.vecPlaneNormal.y ); + WRITE_COORD( tr.vecEndPos.z + tr.vecPlaneNormal.z ); + WRITE_SHORT( m_iBalls ); // model + WRITE_BYTE( 8 ); // count + WRITE_BYTE( 6 ); // life * 10 + WRITE_BYTE( RANDOM_LONG( 1, 2 ) ); // size * 10 + WRITE_BYTE( 10 ); // amplitude * 0.1 + WRITE_BYTE( 20 ); // speed * 100 + MESSAGE_END(); + } + + flDamage = 0; + } + + } + } + else + { + vecSrc = tr.vecEndPos + vecDir; + pentIgnore = ENT( pEntity->pev ); + } + } + // ALERT( at_console, "%d bytes\n", nTotal ); +} + + + + +void CGauss::WeaponIdle( void ) +{ + ResetEmptySound( ); + + // play aftershock static discharge + if (m_flPlayAftershock && 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_flPlayAftershock = 0.0; + } + + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + if (m_fInAttack != 0) + { + StartFire(); + m_fInAttack = 0; + m_flTimeWeaponIdle = gpGlobals->time + 2.0; + } + else + { + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.5) + { + iAnim = GAUSS_IDLE; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + } + else if (flRand <= 0.75) + { + iAnim = GAUSS_IDLE2; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + } + else + { + iAnim = GAUSS_FIDGET; + m_flTimeWeaponIdle = gpGlobals->time + 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 \ No newline at end of file diff --git a/dlls/ggrenade.cpp b/dlls/ggrenade.cpp new file mode 100644 index 0000000..c85c324 --- /dev/null +++ b/dlls/ggrenade.cpp @@ -0,0 +1,488 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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( 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( Detonate ); + pev->nextthink = gpGlobals->time; +} + +void CGrenade::PreDetonate( void ) +{ + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, 400, 0.3 ); + + SetThink( 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( 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( DangerSoundThink ); + pGrenade->pev->nextthink = gpGlobals->time; + + // Tumble in air + pGrenade->pev->avelocity.x = RANDOM_FLOAT ( -100, -500 ); + + // Explode on contact + pGrenade->SetTouch( 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( 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( 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( SUB_DoNothing ); + pGrenade->SetUse( DetonateUse ); + pGrenade->SetTouch( 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..b5c734b --- /dev/null +++ b/dlls/globals.cpp @@ -0,0 +1,39 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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..dfc575a --- /dev/null +++ b/dlls/glock.cpp @@ -0,0 +1,297 @@ +/*** +* +* 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 "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 ); + int m_iShell; +}; +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 +} + +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 (m_iClip != 0) + SendWeaponAnim( GLOCK_SHOOT ); + else + SendWeaponAnim( GLOCK_SHOOT_EMPTY ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + 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 ); + + // silenced + if (pev->body == 1) + { + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = DIM_GUN_FLASH; + + 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; + } + } + else + { + // non-silenced + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + 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)); + } + + 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 ); + + m_pPlayer->pev->punchangle.x -= 2; +} + + +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/h_ai.cpp b/dlls/h_ai.cpp new file mode 100644 index 0000000..80d63cf --- /dev/null +++ b/dlls/h_ai.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. +* +****/ +/* + + 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/dlls/h_battery.cpp b/dlls/h_battery.cpp new file mode 100644 index 0000000..de31953 --- /dev/null +++ b/dlls/h_battery.cpp @@ -0,0 +1,200 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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(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( 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(Recharge); + } + else + SetThink( SUB_DoNothing ); +} \ No newline at end of file diff --git a/dlls/h_cycler.cpp b/dlls/h_cycler.cpp new file mode 100644 index 0000000..c47b61f --- /dev/null +++ b/dlls/h_cycler.cpp @@ -0,0 +1,471 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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( void ); + 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( DefaultTouch ); +} + + + +BOOL CWeaponCycler::Deploy( ) +{ + m_pPlayer->pev->viewmodel = m_iszModel; + m_pPlayer->m_flNextAttack = gpGlobals->time + 1.0; + SendWeaponAnim( 0 ); + m_iClip = 0; + return TRUE; +} + + +void CWeaponCycler::Holster( ) +{ + m_pPlayer->m_flNextAttack = gpGlobals->time + 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..22399eb --- /dev/null +++ b/dlls/h_export.cpp @@ -0,0 +1,55 @@ +/*** +* +* 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. +* +****/ +/* + +===== h_export.cpp ======================================================== + + Entity classes exported by Halflife. + +*/ + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" + + +// 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; +} + +// Holds engine functionality callbacks +enginefuncs_t g_engfuncs; +globalvars_t *gpGlobals; + +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..fef93f4 --- /dev/null +++ b/dlls/handgrenade.cpp @@ -0,0 +1,245 @@ +/*** +* +* 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 "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 +}; + + +class CHandGrenade : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 5; } + int GetItemInfo(ItemInfo *p); + + void PrimaryAttack( void ); + BOOL Deploy( void ); + BOOL CanHolster( void ); + void Holster( void ); + void WeaponIdle( void ); + float m_flStartThrow; + float m_flReleaseThrow; +}; +LINK_ENTITY_TO_CLASS( weapon_handgrenade, CHandGrenade ); + + +void CHandGrenade::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_HANDGRENADE; + SET_MODEL(ENT(pev), "models/w_grenade.mdl"); + + pev->dmg = gSkillData.plrDmgHandGrenade; + + 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( ) +{ + m_pPlayer->m_flNextAttack = gpGlobals->time + 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 = gpGlobals->time + 0.5; + } +} + + +void CHandGrenade::WeaponIdle( void ) +{ + if (m_flReleaseThrow == 0) + m_flReleaseThrow = gpGlobals->time; + + if (m_flTimeWeaponIdle > gpGlobals->time) + 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_flStartThrow = 0; + m_flNextPrimaryAttack = gpGlobals->time + 0.5; + m_flTimeWeaponIdle = gpGlobals->time + 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 = gpGlobals->time + 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 = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + m_flReleaseThrow = -1; + return; + } + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.75) + { + iAnim = HANDGRENADE_IDLE; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 );// how long till we do this again. + } + else + { + iAnim = HANDGRENADE_FIDGET; + m_flTimeWeaponIdle = gpGlobals->time + 75.0 / 30.0; + } + + SendWeaponAnim( iAnim ); + } +} + + + + diff --git a/dlls/healthkit.cpp b/dlls/healthkit.cpp new file mode 100644 index 0000000..10d3b42 --- /dev/null +++ b/dlls/healthkit.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. +* +****/ +#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(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( 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(Recharge); + } + else + SetThink( SUB_DoNothing ); +} \ No newline at end of file diff --git a/dlls/hornet.cpp b/dlls/hornet.cpp new file mode 100644 index 0000000..1eaaef1 --- /dev/null +++ b/dlls/hornet.cpp @@ -0,0 +1,426 @@ +/*** +* +* 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. +* +****/ +//========================================================= +// 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( DieTouch ); + SetThink( StartTrack ); + + edict_t *pSoundEnt = pev->owner; + if ( !pSoundEnt ) + pSoundEnt = edict(); + + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND( pSoundEnt, CHAN_WEAPON, "agrunt/ag_fire1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND( pSoundEnt, CHAN_WEAPON, "agrunt/ag_fire2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND( pSoundEnt, CHAN_WEAPON, "agrunt/ag_fire3.wav", 1, ATTN_NORM); break; + } + + 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( TrackTouch ); + SetThink( TrackTarget ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +//========================================================= +// StartDart - starts a hornet out just flying straight. +//========================================================= +void CHornet :: StartDart ( void ) +{ + IgniteTrail(); + + SetTouch( DartTouch ); + + SetThink( 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( 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 ( 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..2145c76 --- /dev/null +++ b/dlls/hornet.h @@ -0,0 +1,58 @@ +/*** +* +* 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. +* +****/ +//========================================================= +// 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..226194e --- /dev/null +++ b/dlls/hornetgun.cpp @@ -0,0 +1,293 @@ +/*** +* +* 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. +* +****/ +#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 +}; + +class CHgun : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 4; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + BOOL Deploy( void ); + BOOL IsUseable( void ); + void Holster( void ); + void Reload( void ); + void WeaponIdle( void ); + float m_flNextAnimTime; + + float m_flRechargeTime; + + int m_iFirePhase;// don't save me. +}; +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"); + + UTIL_PrecacheOther("hornet"); +} + +int CHgun::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + if ( g_pGameRules->IsMultiplayer() ) + { + // in multiplayer, all hivehands come full. + pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] = HORNET_MAX_CARRY; + } + + 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( ) +{ + m_pPlayer->m_flNextAttack = gpGlobals->time + 0.5; + // m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + 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; + } + + 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_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_flRechargeTime = gpGlobals->time + 0.5; + + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = DIM_GUN_FLASH; + + SendWeaponAnim( HGUN_SHOOT ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_flNextPrimaryAttack = m_flNextPrimaryAttack + 0.25; + + if (m_flNextPrimaryAttack < gpGlobals->time) + { + m_flNextPrimaryAttack = gpGlobals->time + 0.25; + } + + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); +} + + + +void CHgun::SecondaryAttack( void ) +{ + Reload(); + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + { + return; + } + + 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_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = DIM_GUN_FLASH; + + m_flRechargeTime = gpGlobals->time + 0.5; + + SendWeaponAnim( HGUN_SHOOT ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->time + 0.1; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + m_pPlayer->pev->punchangle.x = RANDOM_FLOAT( 0, 2 ); +} + + +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 > gpGlobals->time) + return; + + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.75) + { + iAnim = HGUN_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + 30.0 / 16 * (2); + } + else if (flRand <= 0.875) + { + iAnim = HGUN_FIDGETSWAY; + m_flTimeWeaponIdle = gpGlobals->time + 40.0 / 16.0; + } + else + { + iAnim = HGUN_FIDGETSHAKE; + m_flTimeWeaponIdle = gpGlobals->time + 35.0 / 16.0; + } + SendWeaponAnim( iAnim ); +} + +#endif \ No newline at end of file diff --git a/dlls/items.cpp b/dlls/items.cpp new file mode 100644 index 0000000..ccafa6d --- /dev/null +++ b/dlls/items.cpp @@ -0,0 +1,335 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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 + + 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..d4c00e8 --- /dev/null +++ b/dlls/items.h @@ -0,0 +1,29 @@ +/*** +* +* 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 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/lights.cpp b/dlls/lights.cpp new file mode 100644 index 0000000..4695aa7 --- /dev/null +++ b/dlls/lights.cpp @@ -0,0 +1,199 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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( "cl_skycolor_r", szColor ); + sprintf( szColor, "%d", g ); + CVAR_SET_STRING( "cl_skycolor_g", szColor ); + sprintf( szColor, "%d", b ); + CVAR_SET_STRING( "cl_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( "cl_skyvec_x", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.y ); + CVAR_SET_STRING( "cl_skyvec_y", szVector ); + sprintf( szVector, "%f", gpGlobals->v_forward.z ); + CVAR_SET_STRING( "cl_skyvec_z", szVector ); + + CLight::Spawn( ); +} diff --git a/dlls/maprules.cpp b/dlls/maprules.cpp new file mode 100644 index 0000000..20f8b85 --- /dev/null +++ b/dlls/maprules.cpp @@ -0,0 +1,918 @@ +/*** +* +* 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. +* +****/ + +// ------------------------------------------- +// +// 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..8b4867c --- /dev/null +++ b/dlls/maprules.h @@ -0,0 +1,22 @@ +/*** +* +* 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 MAPRULES_H +#define MAPRULES_H + + + +#endif // MAPRULES_H + diff --git a/dlls/monsterevent.h b/dlls/monsterevent.h new file mode 100644 index 0000000..34de446 --- /dev/null +++ b/dlls/monsterevent.h @@ -0,0 +1,34 @@ +/*** +* +* 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 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/monsters.h b/dlls/monsters.h new file mode 100644 index 0000000..fa24dac --- /dev/null +++ b/dlls/monsters.h @@ -0,0 +1,88 @@ +/*** +* +* 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 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/dlls/mortar.cpp b/dlls/mortar.cpp new file mode 100644 index 0000000..64dbeb1 --- /dev/null +++ b/dlls/mortar.cpp @@ -0,0 +1,323 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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( 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( 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( 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 \ No newline at end of file diff --git a/dlls/mp.def b/dlls/mp.def new file mode 100644 index 0000000..5ae6f18 --- /dev/null +++ b/dlls/mp.def @@ -0,0 +1,5 @@ +LIBRARY mp +EXPORTS + GiveFnptrsToDll @1 +SECTIONS + .data READ WRITE diff --git a/dlls/mp.dsp b/dlls/mp.dsp new file mode 100644 index 0000000..5f7ceab --- /dev/null +++ b/dlls/mp.dsp @@ -0,0 +1,550 @@ +# 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 "mp - Win32 Profile" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE + +# Begin Project +# PROP AllowPerConfigDependencies 0 +# PROP Scc_ProjName ""$/SDKSrc/Public/dlls", NVGBAAAA" +# 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 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 "..\engine" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "VALVE_DLL" /YX /FD /c +# SUBTRACT CPP /Fr +# 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\mp\dlls +TargetPath=.\Releasemp\mp.dll +TargetName=mp +InputPath=.\Releasemp\mp.dll +SOURCE="$(InputPath)" + +"$(TargetName)" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetPath) \half-life\mp\dlls + +# 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" /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 \half-life\mp\dlls +TargetPath=.\debugmp\mp.dll +TargetName=mp +InputPath=.\debugmp\mp.dll +SOURCE="$(InputPath)" + +"$(TargetName)" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + copy $(TargetPath) \half-life\mp\dlls + +# End Custom Build + +!ELSEIF "$(CFG)" == "mp - Win32 Profile" + +# PROP BASE Use_MFC 0 +# PROP BASE Use_Debug_Libraries 0 +# PROP BASE Output_Dir ".\mp___Win" +# PROP BASE Intermediate_Dir ".\mp___Win" +# PROP BASE Target_Dir "" +# PROP Use_MFC 0 +# PROP Use_Debug_Libraries 0 +# PROP Output_Dir ".\Profilemp" +# PROP Intermediate_Dir ".\Profilemp" +# PROP Target_Dir "" +# ADD BASE CPP /nologo /G5 /MT /W3 /GX /Zi /O2 /I "..\engine" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /YX /c +# SUBTRACT BASE CPP /Fr +# ADD CPP /nologo /G5 /MT /W3 /GX /Zi /O2 /I "..\engine" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "VALVE_DLL" /YX /FD /c +# SUBTRACT CPP /Fr +# 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 /nologo /subsystem:windows /dll /debug /machine:I386 /def:".\mp.def" +# SUBTRACT BASE LINK32 /profile +# 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 /profile /debug /machine:I386 /def:".\mp.def" +# Begin Custom Build - Copying to \half-life\mp\dlls +TargetDir=.\Profilemp +InputPath=.\Profilemp\mp.dll +SOURCE="$(InputPath)" + +BuildCmds= \ + copy $(TargetDir)\mp.dll \half-life\mp\dlls \ + copy $(TargetDir)\mp.map \half-life\mp\dlls \ + + +"\half-life\mp\dlls\mp.dll" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) + +"\half-life\mp\dlls\mp.map" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" + $(BuildCmds) +# End Custom Build + +!ENDIF + +# Begin Target + +# Name "mp - Win32 Release" +# Name "mp - Win32 Debug" +# Name "mp - Win32 Profile" +# 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=.\crossbow.cpp +# End Source File +# Begin Source File + +SOURCE=.\crowbar.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=.\egon.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=.\gauss.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=.\glock.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=.\handgrenade.cpp +# End Source File +# Begin Source File + +SOURCE=.\healthkit.cpp +# End Source File +# Begin Source File + +SOURCE=.\hornet.cpp +# End Source File +# Begin Source File + +SOURCE=.\hornetgun.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=.\mp5.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=.\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=.\python.cpp +# End Source File +# Begin Source File + +SOURCE=.\rpg.cpp +# End Source File +# Begin Source File + +SOURCE=.\satchel.cpp +# End Source File +# Begin Source File + +SOURCE=.\shotgun.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=.\soundent.cpp +# End Source File +# Begin Source File + +SOURCE=.\spectator.cpp +# End Source File +# Begin Source File + +SOURCE=.\squeakgrenade.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=.\tripmine.cpp +# End Source File +# Begin Source File + +SOURCE=.\util.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=.\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=.\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/dlls/mp.dsw b/dlls/mp.dsw new file mode 100644 index 0000000..71212df --- /dev/null +++ b/dlls/mp.dsw @@ -0,0 +1,37 @@ +Microsoft Developer Studio Workspace File, Format Version 6.00 +# WARNING: DO NOT EDIT OR DELETE THIS WORKSPACE FILE! + +############################################################################### + +Project: "mp"=.\mp.dsp - Package Owner=<4> + +Package=<5> +{{{ + begin source code control + "$/SDKSrc/Public/dlls", NVGBAAAA + . + end source code control +}}} + +Package=<4> +{{{ +}}} + +############################################################################### + +Global: + +Package=<5> +{{{ + begin source code control + "$/SDKSrc/Public/dlls", NVGBAAAA + . + end source code control +}}} + +Package=<3> +{{{ +}}} + +############################################################################### + diff --git a/dlls/mp5.cpp b/dlls/mp5.cpp new file mode 100644 index 0000000..4b362d1 --- /dev/null +++ b/dlls/mp5.cpp @@ -0,0 +1,415 @@ +/*** +* +* 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 "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, +}; + + +class CMP5 : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 3; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + int SecondaryAmmoIndex( void ); + BOOL Deploy( void ); + void Reload( void ); + void WeaponIdle( void ); + float m_flNextAnimTime; + int m_iShell; +}; +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("items/guncock1.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"); +} + +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 = gpGlobals->time + 0.15; + return; + } + + if (m_iClip <= 0) + { + PlayEmptySound(); + m_flNextPrimaryAttack = gpGlobals->time + 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; + + + if (1 || m_flNextAnimTime < gpGlobals->time) + { + SendWeaponAnim( MP5_FIRE1 + RANDOM_LONG(0,2)); + m_flNextAnimTime = gpGlobals->time + 0.2; + } + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + switch( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM, 0, 94 + RANDOM_LONG(0,0xf)); break; + case 1: EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM, 0, 94 + RANDOM_LONG(0,0xf)); break; +// case 2: EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/hks3.wav", 1, ATTN_NORM); break; + } + + 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 * 20 + + gpGlobals->v_right * 4, vecShellVelocity, pev->angles.y, m_iShell, TE_BOUNCE_SHELL); + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + if ( g_pGameRules->IsDeathmatch() ) + { + // optimized multiplayer. Widened to make it easier to hit a moving player + m_pPlayer->FireBullets( 1, vecSrc, vecAiming, VECTOR_CONE_6DEGREES, 8192, BULLET_PLAYER_MP5, 2 ); + } + else + { + // single player spread + m_pPlayer->FireBullets( 1, vecSrc, vecAiming, VECTOR_CONE_3DEGREES, 8192, BULLET_PLAYER_MP5, 2 ); + } + + 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 = m_flNextPrimaryAttack + 0.1; + if (m_flNextPrimaryAttack < gpGlobals->time) + m_flNextPrimaryAttack = gpGlobals->time + 0.1; + + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + + m_pPlayer->pev->punchangle.x = RANDOM_FLOAT( -2, 2 ); +} + + + +void CMP5::SecondaryAttack( void ) +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = gpGlobals->time + 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 = gpGlobals->time + 0.2; + + m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType]--; + + SendWeaponAnim( MP5_LAUNCH ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + if ( RANDOM_LONG(0,1) ) + { + // play this sound through BODY channel so we can hear it if player didn't stop firing MP3 + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/glauncher.wav", 0.8, ATTN_NORM); + } + else + { + // play this sound through BODY channel so we can hear it if player didn't stop firing MP3 + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/glauncher2.wav", 0.8, ATTN_NORM); + } + + 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 ); + + m_flNextPrimaryAttack = gpGlobals->time + 1; + m_flNextSecondaryAttack = gpGlobals->time + 1; + m_flTimeWeaponIdle = gpGlobals->time + 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); + + m_pPlayer->pev->punchangle.x -= 10; +} + +void CMP5::Reload( void ) +{ + DefaultReload( MP5_MAX_CLIP, MP5_RELOAD, 1.5 ); +} + + + +void CMP5::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + if (m_flTimeWeaponIdle > gpGlobals->time) + 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 = gpGlobals->time + RANDOM_FLOAT ( 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..48e8f96 --- /dev/null +++ b/dlls/mpstubb.cpp @@ -0,0 +1,264 @@ +/*** +* +* 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 "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..51e9c5d --- /dev/null +++ b/dlls/multiplay_gamerules.cpp @@ -0,0 +1,1056 @@ +/*** +* +* 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" + +extern DLL_GLOBAL CGameRules *g_pGameRules; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; + +#define ITEM_RESPAWN_TIME 30 +#define WEAPON_RESPAWN_TIME 20 +#define AMMO_RESPAWN_TIME 20 + +//********************************************************* +// Rules for the half-life multiplayer game. +//********************************************************* + +CHalfLifeMultiplay :: CHalfLifeMultiplay() +{ + 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() ) + { + // 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 + +//========================================================= +//========================================================= +void CHalfLifeMultiplay :: Think ( void ) +{ + ///// Check game rules ///// + + 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; + + if ( flTimeLimit != 0 && gpGlobals->time >= flTimeLimit ) + { + GoToIntermission(); + return; + } + + if ( flFragLimit ) + { + // 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; + } + } + } +} + + +//========================================================= +//========================================================= +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 ] ) +{ + 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" ) ); + + UTIL_LogPrintf( "\"%s<%i>\" has entered the game\n", STRING( pl->pev->netname ), 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 ); + 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 ); + 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>\" disconnected\n", STRING( pPlayer->pev->netname ), GETPLAYERUSERID( pPlayer->edict() ) ); + + 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 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 ( 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 ); + + 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 ); + 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 ); + 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 + UTIL_LogPrintf( "\"%s<%i>\" killed self with %s\n", STRING( pVictim->pev->netname ), GETPLAYERUSERID( pVictim->edict() ), killer_weapon_name ); + } + else if ( pKiller->flags & FL_CLIENT ) + { + UTIL_LogPrintf( "\"%s<%i>\" killed \"%s<%i>\" with %s\n", STRING( pKiller->netname ), + GETPLAYERUSERID( ENT(pKiller) ), + STRING( pVictim->pev->netname ), + GETPLAYERUSERID( pVictim->edict() ), + killer_weapon_name ); + } + else + { // killed by the world + UTIL_LogPrintf( "\"%s<%i>\" killed by world with %s\n", STRING( pVictim->pev->netname ), GETPLAYERUSERID( pVictim->edict() ), killer_weapon_name ); + } + +// 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 ( 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 ) +{ + if ( CVAR_GET_FLOAT( "mp_footsteps" ) == 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; +} + +void CHalfLifeMultiplay :: ChangeLevel( void ) +{ + char szNextMap[32]; + char szFirstMapInList[32]; + strcpy( szFirstMapInList, "hldm1" ); // the absolute default level is hldm1 + + // find the map to change to + + char *mapcfile = (char*)CVAR_GET_STRING( "mapcyclefile" ); + ASSERT( mapcfile != NULL ); + strcpy( szNextMap, STRING(gpGlobals->mapname) ); + strcpy( szFirstMapInList, STRING(gpGlobals->mapname) ); + + int length; + char *pFileList; + char *aFileList = pFileList = (char*)LOAD_FILE_FOR_ME( mapcfile, &length ); + if ( pFileList && length ) + { + // the first map name in the file becomes the default + sscanf( pFileList, " %32s", szNextMap ); + if ( IS_MAP_VALID( szNextMap ) ) + strcpy( szFirstMapInList, szNextMap ); + + // keep pulling mapnames out of the list until the current mapname + // if the current mapname isn't found, load the first map in the list + BOOL next_map_is_it = FALSE; + while ( 1 ) + { + while ( *pFileList && isspace( *pFileList ) ) pFileList++; // skip over any whitespace + if ( !(*pFileList) ) + break; + + char cBuf[32]; + int ret = sscanf( pFileList, " %32s", cBuf ); + // Check the map name is valid + if ( ret != 1 || *cBuf < 13 ) + break; + + if ( next_map_is_it ) + { + // check that it is a valid map file + if ( IS_MAP_VALID( cBuf ) ) + { + strcpy( szNextMap, cBuf ); + break; + } + } + + if ( FStrEq( cBuf, STRING(gpGlobals->mapname) ) ) + { // we've found our map; next map is the one to change to + next_map_is_it = TRUE; + } + + pFileList += strlen( cBuf ); + } + + FREE_FILE( aFileList ); + } + + if ( !IS_MAP_VALID(szNextMap) ) + strcpy( szNextMap, szFirstMapInList ); + + g_fGameOver = TRUE; + + ALERT( at_console, "CHANGE LEVEL: %s\n", szNextMap ); + + CHANGE_LEVEL( szNextMap, NULL ); +} + +#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/dlls/nodes.h b/dlls/nodes.h new file mode 100644 index 0000000..eb73baf --- /dev/null +++ b/dlls/nodes.h @@ -0,0 +1,54 @@ +/*** +* +* 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. +* +****/ +//========================================================= +// 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 \ No newline at end of file diff --git a/dlls/pathcorner.cpp b/dlls/pathcorner.cpp new file mode 100644 index 0000000..f9e56c3 --- /dev/null +++ b/dlls/pathcorner.cpp @@ -0,0 +1,428 @@ +/*** +* +* 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. +* +****/ +// +// ========================== 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..c96bf46 --- /dev/null +++ b/dlls/plane.cpp @@ -0,0 +1,60 @@ +/*** +* +* 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 "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..ea2aa33 --- /dev/null +++ b/dlls/plane.h @@ -0,0 +1,43 @@ +/*** +* +* 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 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..013f164 --- /dev/null +++ b/dlls/plats.cpp @@ -0,0 +1,2258 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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( 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(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(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( 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( 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( 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( 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( 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) + { + 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); + } +} + + +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( 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( 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( 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( Next ); + } +} + + +void CFuncTrackTrain::OverrideReset( void ) +{ + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( 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( 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"); + +} + +// 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( 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( 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( 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( CallHitBottom ); + m_toggle_state = TS_GOING_DOWN; + AngularMove( m_start, pev->speed ); + } + else + { + CFuncPlat :: GoDown(); + SetMoveDone( 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( CallHitTop ); + AngularMove( m_end, pev->speed ); + } + else + { + // If ROTMOVE, move & rotate + CFuncPlat :: GoUp(); + SetMoveDone( 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( 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( 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( 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..e37d4e9 --- /dev/null +++ b/dlls/player.cpp @@ -0,0 +1,5090 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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" + +// #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 ); +int MapTextureTypeStepType(char chTextureType); + +// 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; + +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; + return pev->origin + pev->view_ofs; +} + +//========================================================= +// 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; + } + + + // 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, NULL, 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; + + 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; + + 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 ); + + pev->modelindex = g_ulModelIndexPlayer; // don't use eyes + +#if !defined(DUCKFIX) + pev->view_ofs = Vector(0,0,-8); +#endif + pev->deadflag = DEAD_DYING; + pev->solid = SOLID_NOT; + 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 ) + { + GibMonster(); // This clears pev->model + pev->effects |= EF_NODRAW; + return; + } + + DeathSound(); + + pev->angles.x = 0; + pev->angles.z = 0; + + SetThink(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_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)) + { + // 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; + } + + 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)) + { + // 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; + } + } + + 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); +} + + +// +// check for a jump-out-of-water +// +void CBasePlayer::CheckWaterJump( ) +{ + if ( IsOnLadder() ) // Don't water jump if on a ladders? (This prevents the bug where + return; // you bounce off water when you are facing it on a ladder). + + Vector vecStart = pev->origin; + vecStart.z += 8; + + UTIL_MakeVectors(pev->angles); + gpGlobals->v_forward.z = 0; + gpGlobals->v_forward = gpGlobals->v_forward.Normalize(); + + Vector vecEnd = vecStart + gpGlobals->v_forward * 24; + + TraceResult tr; + UTIL_TraceLine(vecStart, vecEnd, ignore_monsters, ENT(pev)/*pentIgnore*/, &tr); + if (tr.flFraction < 1) // solid at waist + { + vecStart.z += pev->maxs.z - 8; + vecEnd = vecStart + gpGlobals->v_forward * 24; + pev->movedir = tr.vecPlaneNormal * -50; + UTIL_TraceLine(vecStart, vecEnd, ignore_monsters, ENT(pev)/*pentIgnore*/, &tr); + if (tr.flFraction == 1) // open at eye level + { + SetBits(pev->flags, FL_WATERJUMP); + pev->velocity.z = 225; + pev->teleport_time = gpGlobals->time + 2; // safety net + } + } +} + + +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++; + if ( m_iRespawnFrames < 60 ) // 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; + + BOOL fAnyButtonDown = (pev->button); + + // 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_afPhysicsFlags |= PFLAG_OBSERVER; + + 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; + pev->modelindex = 0; + UTIL_SetOrigin( pev, vecPosition ); +} + +// +// 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() +{ + float flScale; + 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) + { + switch ((int)pev->watertype) + { + case CONTENT_WATER: flScale = 100; break; + case CONTENT_SLIME: flScale = 80; break; + default: flScale = 50; break; + } + + pev->velocity.z = flScale; + + // play swiming sound + if (m_flSwimTime < gpGlobals->time) + { + m_flSwimTime = gpGlobals->time + 1; + 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; + } + } + 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 ); + + // play step sound for current texture at full volume + + PlayStepSound(MapTextureTypeStepType(m_chTextureType), 1.0); + + 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! +// ALERT ( at_console, "LongJump!" ); + + // play longjump 'servo' sound + if ( RANDOM_LONG(0,1) ) + { + ; // UNDONE: EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain2.wav", 1, ATTN_NORM); + } + else + { + ; // UNDONE: EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_pain4.wav", 1, ATTN_NORM); + } + + pev->punchangle.x = -5; + pev->velocity = gpGlobals->v_forward * (PLAYER_LONGJUMP_SPEED * 1.6); + pev->velocity.z = sqrt( 2 * 800 * 56.0 ); // jump 56 units + SetAnimation( PLAYER_SUPERJUMP ); + } + else + {// ducking jump + pev->velocity.z = sqrt( 2 * 800 * 45.0 ); // jump 45 units + } + } + else + { + pev->velocity.z = sqrt( 2 * 800 * 45.0 ); // jump 45 units + } + + // 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; + } +} + +// It takes 0.4 seconds to duck +#define TIME_TO_DUCK 0.4 + +// This is a little more complicated now than it used to be, but I think for the better - +// The player's origin no longer moves when ducking. If you start ducking while standing on ground, +// your view offset is non-linearly blended to the ducking position and then your hull is changed. +// In air, duck works just like it used to - you pick up your feet (more like tucking). +// PFLAG_DUCKING was added to track the state where the player is in the process of ducking, but hasn't +// reached the crouched position yet. +// +// A few of nice side effects of this are: +// a) On ground status does not change when ducking (good on trains, etc) +// b) origin stays constant +// c) Superjump can begin while still in run animation (nice when looking at others in multiplayer) +// +void CBasePlayer::Duck( ) +{ + float time, duckFraction; + + if (pev->button & IN_DUCK) + { +#if !defined(DUCKFIX) + if ( (m_afButtonPressed & IN_DUCK) && !(pev->flags & FL_DUCKING) ) + { + m_flDuckTime = gpGlobals->time; + SetBits(m_afPhysicsFlags,PFLAG_DUCKING); // We are in the process of ducking + } + time = (gpGlobals->time - m_flDuckTime); + + // if we're not already done ducking + if ( FBitSet( m_afPhysicsFlags, PFLAG_DUCKING ) ) + { + // Finish ducking immediately if duck time is over or not on ground + if ( time >= TIME_TO_DUCK || !(pev->flags & FL_ONGROUND) ) + { + // HACKHACK - Fudge for collision bug - no time to fix this properly + if ( pev->flags & FL_ONGROUND ) + { + pev->origin = pev->origin - (VEC_DUCK_HULL_MIN - VEC_HULL_MIN); + FixPlayerCrouchStuck( edict() ); + } + + + UTIL_SetOrigin( pev, pev->origin ); + + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + pev->view_ofs = VEC_DUCK_VIEW; + SetBits(pev->flags,FL_DUCKING); // Hull is duck hull + ClearBits(m_afPhysicsFlags,PFLAG_DUCKING); // Done ducking (don't ease-in when the player hits the ground) + } + else + { + // Calc parametric time + duckFraction = UTIL_SplineFraction( time, (1.0/TIME_TO_DUCK) ); + pev->view_ofs = ((VEC_DUCK_VIEW - (VEC_DUCK_HULL_MIN - VEC_HULL_MIN)) * duckFraction) + (VEC_VIEW * (1-duckFraction)); + } + } +#endif + SetAnimation( PLAYER_WALK ); + } + else + { +#if !defined(DUCKFIX) + TraceResult trace; + Vector newOrigin = pev->origin; + + if ( pev->flags & FL_ONGROUND ) + newOrigin = newOrigin + (VEC_DUCK_HULL_MIN - VEC_HULL_MIN); + + UTIL_TraceHull( newOrigin, newOrigin, dont_ignore_monsters, human_hull, ENT(pev), &trace ); + + if ( !trace.fStartSolid ) + { + ClearBits(pev->flags,FL_DUCKING); + ClearBits(m_afPhysicsFlags,PFLAG_DUCKING); + pev->view_ofs = VEC_VIEW; + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + pev->origin = newOrigin; + } +#endif + } +} + +// +// 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 ); + 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) +{ + 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; + + PlayStepSound(step, fvol); + } +} + + +#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(); + + if (pev->waterlevel == 2) + CheckWaterJump(); + else if ( pev->waterlevel == 0 ) + { + pev->flags &= ~FL_WATERJUMP; // Clear this if out of water + } + + 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(); + + // 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 +int gmsgGeigerRange; + +#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 ) + return; // intermission or finale + + if (!IsAlive()) + return; + + // 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 + + switch ( RANDOM_LONG(0,1) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_fallpain2.wav", 1, ATTN_NORM); + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM); + } + + 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 ); + } + + 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(); + + // play step sound for current texture + PlayStepSound(MapTextureTypeStepType(m_chTextureType), fvol); + + // knock the screen around a little bit, temporary effect + pev->punchangle.x = m_flFallVelocity * 0.018; // punch x axis + + if ( pev->punchangle.x > 8 ) + { + pev->punchangle.x = 8; + } + + if ( RANDOM_LONG(0,1) ) + { + pev->punchangle.z = m_flFallVelocity * 0.009; + } + + } + + 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(); + + // 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_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; + m_bitsHUDDamage = -1; + m_bitsDamageType = 0; + m_afPhysicsFlags = 0; + m_fLongJump = FALSE;// no longjump module. + 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 = gpGlobals->time; + 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; + + 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; + + 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", 5 ); + 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); + + 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 ( 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 ( 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(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 ( gpGlobals->time < m_flNextAttack ) + { + 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 ( gpGlobals->time < m_flNextAttack ) + 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; + 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) + { + int iHealth = max( pev->health, 0 ); // make sure that no negative health values are sent + + // 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; +} + + +//========================================================= +// 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( MessageThink ); +} + + +void CRevertSaved :: MessageThink( void ) +{ + UTIL_ShowMessageAll( STRING(pev->message) ); + float nextThink = LoadTime() - MessageTime(); + if ( nextThink > 0 ) + { + pev->nextthink = gpGlobals->time + nextThink; + SetThink( 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..037ec55 --- /dev/null +++ b/dlls/player.h @@ -0,0 +1,289 @@ +/*** +* +* 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; + +class CBasePlayer : public CBaseMonster +{ +public: + 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 CheckWaterJump( 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 ); +}; + +#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/python.cpp b/dlls/python.cpp new file mode 100644 index 0000000..1439c40 --- /dev/null +++ b/dlls/python.cpp @@ -0,0 +1,338 @@ +/*** +* +* 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. +* +****/ +#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 +}; + +class CPython : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 2; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + void PrimaryAttack( void ); + void SecondaryAttack( void ); + BOOL Deploy( void ); + void Holster( void ); + void Reload( void ); + void WeaponIdle( void ); + float m_flSoundDelay; + + BOOL m_fInZoom;// don't save this. +}; +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"); +} + +BOOL CPython::Deploy( ) +{ + if ( g_pGameRules->IsMultiplayer() ) + { + // enable laser sight geometry. + pev->body = 1; + } + else + { + pev->body = 0; + } + + return DefaultDeploy( "models/v_357.mdl", "models/p_357.mdl", PYTHON_DRAW, "python" ); +} + + +void CPython::Holster( ) +{ + m_fInReload = FALSE;// cancel any reload in progress. + + if ( m_fInZoom ) + { + SecondaryAttack(); + } + + m_pPlayer->m_flNextAttack = gpGlobals->time + 1.0; + m_flTimeWeaponIdle = gpGlobals->time + 10 + RANDOM_FLOAT ( 0, 5 ); + SendWeaponAnim( PYTHON_HOLSTER ); +} + +void CPython::SecondaryAttack( void ) +{ + if ( !g_pGameRules->IsMultiplayer() ) + { + return; + } + + if ( m_fInZoom ) + { + m_fInZoom = FALSE; + m_pPlayer->m_iFOV = 0; // 0 means reset to default fov + } + else + { + m_fInZoom = TRUE; + m_pPlayer->m_iFOV = 40; + } + + m_flNextSecondaryAttack = gpGlobals->time + 0.5; +} + +void CPython::PrimaryAttack() +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = gpGlobals->time + 0.15; + return; + } + + if (m_iClip <= 0) + { + if (!m_fFireOnEmpty) + Reload( ); + else + { + // if (m_iClip == 0) + //PlayEmptySound( ); + + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); + m_flNextPrimaryAttack = gpGlobals->time + 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 = BRIGHT_GUN_FLASH; + + m_iClip--; + +/* + if (m_iClip || m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] == 0) +*/ + SendWeaponAnim( PYTHON_FIRE1 ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); +/* + else + Reload( ); +*/ + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + switch(RANDOM_LONG(0,1)) + { + case 0: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_shot1.wav", RANDOM_FLOAT(0.8, 0.9), ATTN_NORM); + break; + case 1: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_shot2.wav", RANDOM_FLOAT(0.8, 0.9), ATTN_NORM); + break; + } + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + m_pPlayer->FireBullets( 1, vecSrc, vecAiming, VECTOR_CONE_1DEGREES, 8192, BULLET_PLAYER_357, 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 = gpGlobals->time + 0.75; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + + m_pPlayer->pev->punchangle.x -= 10; +} + + +void CPython::Reload( void ) +{ + if ( m_fInZoom ) + { + m_fInZoom = FALSE; + m_pPlayer->m_iFOV = 0; // 0 means reset to default fov + } + + if (DefaultReload( 6, PYTHON_RELOAD, 2.0 )) + { + m_flSoundDelay = gpGlobals->time + 1.5; + } +} + + +void CPython::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + + // ALERT( at_console, "%.2f\n", gpGlobals->time - m_flSoundDelay ); + if (m_flSoundDelay != 0 && m_flSoundDelay <= gpGlobals->time) + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_reload1.wav", RANDOM_FLOAT(0.8, 0.9), ATTN_NORM); + m_flSoundDelay = 0; + + /* + for (int i = 0; i < 6; i++) + { + EjectBrass ( m_pPlayer->pev->origin, + Vector( RANDOM_FLOAT( -10.0, 10.0 ), RANDOM_FLOAT( -10.0, 10.0 ), (float)0.0 ), + m_pPlayer->pev->angles.y, TE_BOUNCE_SHELL); + } + */ + } + + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.5) + { + iAnim = PYTHON_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + (70.0/30.0); + } + else if (flRand <= 0.7) + { + iAnim = PYTHON_IDLE2; + m_flTimeWeaponIdle = gpGlobals->time + (60.0/30.0); + } + else if (flRand <= 0.9) + { + iAnim = PYTHON_IDLE3; + m_flTimeWeaponIdle = gpGlobals->time + (88.0/30.0); + } + else + { + iAnim = PYTHON_FIDGET; + m_flTimeWeaponIdle = gpGlobals->time + (170.0/30.0); + } + SendWeaponAnim( iAnim ); +} + + + +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/rpg.cpp b/dlls/rpg.cpp new file mode 100644 index 0000000..605dc08 --- /dev/null +++ b/dlls/rpg.cpp @@ -0,0 +1,695 @@ +/*** +* +* 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. +* +****/ +#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 +}; + + +class CLaserSpot : public CBaseEntity +{ + void Spawn( void ); + void Precache( void ); + + int ObjectCaps( void ) { return FCAP_DONT_SAVE; } + +public: + void Suspend( float flSuspendTime ); + void EXPORT Revive( void ); + + static CLaserSpot *CreateSpot( void ); +}; +LINK_ENTITY_TO_CLASS( laser_spot, CLaserSpot ); + + +class CRpg : public CBasePlayerWeapon +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + void Reload( void ); + int iItemSlot( void ) { return 4; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + BOOL Deploy( void ); + BOOL CanHolster( void ); + void Holster( void ); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + void WeaponIdle( void ); + + void UpdateSpot( void ); + BOOL ShouldWeaponIdle( void ) { return TRUE; }; + + CLaserSpot *m_pSpot; + int m_fSpotActive; + int m_cActiveRockets;// how many missiles in flight from this launcher right now? + +}; +LINK_ENTITY_TO_CLASS( weapon_rpg, CRpg ); + +TYPEDESCRIPTION CRpg::m_SaveData[] = +{ + DEFINE_FIELD( CRpg, m_fSpotActive, FIELD_INTEGER ), + DEFINE_FIELD( CRpg, m_cActiveRockets, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CRpg, CBasePlayerWeapon ); + +//========================================================= +//========================================================= +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( 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"); +}; + + + + + +class CRpgRocket : public CGrenade +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + void Spawn( void ); + void Precache( void ); + void EXPORT FollowThink( void ); + void EXPORT IgniteThink( void ); + void EXPORT RocketTouch( CBaseEntity *pOther ); + static CRpgRocket *CreateRpgRocket( Vector vecOrigin, Vector vecAngles, CBaseEntity *pOwner, CRpg *pLauncher ); + + int m_iTrail; + float m_flIgniteTime; + CRpg *m_pLauncher;// pointer back to the launcher that fired me. +}; +LINK_ENTITY_TO_CLASS( rpg_rocket, CRpgRocket ); + +TYPEDESCRIPTION CRpgRocket::m_SaveData[] = +{ + DEFINE_FIELD( CRpgRocket, m_flIgniteTime, FIELD_TIME ), + DEFINE_FIELD( CRpgRocket, m_pLauncher, FIELD_CLASSPTR ), +}; +IMPLEMENT_SAVERESTORE( CRpgRocket, CGrenade ); + +//========================================================= +//========================================================= +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( IgniteThink ); + SetTouch( 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) + + + +/* + WRITE_BYTE( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( MSG_BROADCAST, TE_BEAMFOLLOW); + WRITE_SHORT(entindex()); // entity + WRITE_SHORT(MSG_BROADCAST, m_iTrail ); // model + WRITE_BYTE( MSG_BROADCAST, 40 ); // life + WRITE_BYTE( MSG_BROADCAST, 5 ); // width + WRITE_BYTE( MSG_BROADCAST, 224 ); // r, g, b + WRITE_BYTE( MSG_BROADCAST, 224 ); // r, g, b + WRITE_BYTE( MSG_BROADCAST, 255 ); // r, g, b + WRITE_BYTE( MSG_BROADCAST, 255 ); // brightness +*/ + m_flIgniteTime = gpGlobals->time; + + // set to follow laser spot + SetThink( 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; +} + + + + + + + + +void CRpg::Reload( void ) +{ + int iResult; + + if ( m_iClip == 1 ) + { + // don't bother with any of this if don't need to reload. + 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 = gpGlobals->time + 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; + } + + if (m_pSpot && m_fSpotActive) + { + m_pSpot->Suspend( 2.1 ); + m_flNextSecondaryAttack = gpGlobals->time + 2.1; + } + + if (m_iClip == 0) + { + iResult = DefaultReload( RPG_MAX_CLIP, RPG_RELOAD, 2 ); + } + + if (iResult) + { + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + } +} + +void CRpg::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_RPG; + + SET_MODEL(ENT(pev), "models/w_rpg.mdl"); + m_fSpotActive = 1; + + if ( g_pGameRules->IsMultiplayer() ) + { + // 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 +} + + +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( ) +{ + m_fInReload = FALSE;// cancel any reload in progress. + + m_pPlayer->m_flNextAttack = gpGlobals->time + 0.5; + // m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + SendWeaponAnim( RPG_HOLSTER1 ); + if (m_pSpot) + { + m_pSpot->Killed( NULL, GIB_NEVER ); + m_pSpot = NULL; + } +} + + + +void CRpg::PrimaryAttack() +{ + if (m_iClip) + { + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + + SendWeaponAnim( RPG_FIRE2 ); + + // 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 ); + + // 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) + + + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/rocketfire1.wav", 0.9, ATTN_NORM ); + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/glauncher.wav", 0.7, ATTN_NORM ); + + m_iClip--; + //m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + m_flNextPrimaryAttack = gpGlobals->time + 1.5; + m_flTimeWeaponIdle = gpGlobals->time + 1.5; + m_pPlayer->pev->punchangle.x -= 5; + } + else + { + PlayEmptySound( ); + } + UpdateSpot( ); +} + + +void CRpg::SecondaryAttack() +{ + m_fSpotActive = ! m_fSpotActive; + + if (!m_fSpotActive && m_pSpot) + { + m_pSpot->Killed( NULL, GIB_NORMAL ); + m_pSpot = NULL; + } + + m_flNextSecondaryAttack = gpGlobals->time + 0.2; +} + + +void CRpg::WeaponIdle( void ) +{ + UpdateSpot( ); + + ResetEmptySound( ); + + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.75 || m_fSpotActive) + { + if ( m_iClip == 0 ) + iAnim = RPG_IDLE_UL; + else + iAnim = RPG_IDLE; + + m_flTimeWeaponIdle = gpGlobals->time + 90.0 / 15.0; + } + else + { + if ( m_iClip == 0 ) + iAnim = RPG_FIDGET_UL; + else + iAnim = RPG_FIDGET; + + m_flTimeWeaponIdle = gpGlobals->time + 3.0; + } + + SendWeaponAnim( iAnim ); + } + else + { + m_flTimeWeaponIdle = gpGlobals->time + 1; + } +} + + + +void CRpg::UpdateSpot( void ) +{ + 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 ); + + // ALERT( "%f %f\n", gpGlobals->v_forward.y, vecAiming.y ); + + /* + float a = gpGlobals->v_forward.y * vecAiming.y + gpGlobals->v_forward.x * vecAiming.x; + m_pPlayer->pev->punchangle.y = acos( a ) * (180 / M_PI); + + ALERT( at_console, "%f\n", a ); + */ + + UTIL_SetOrigin( m_pSpot->pev, tr.vecEndPos ); + } +} + + +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; + + if ( g_pGameRules->IsMultiplayer() ) + { + // 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 \ No newline at end of file diff --git a/dlls/satchel.cpp b/dlls/satchel.cpp new file mode 100644 index 0000000..18fdbe3 --- /dev/null +++ b/dlls/satchel.cpp @@ -0,0 +1,526 @@ +/*** +* +* 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. +* +****/ +#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( SatchelSlide ); + SetUse( DetonateUse ); + SetThink( 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; + } +} + +class CSatchel : public CBasePlayerWeapon +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 5; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + void PrimaryAttack( void ); + void SecondaryAttack( void ); + int AddDuplicate( CBasePlayerItem *pOriginal ); + BOOL CanDeploy( void ); + BOOL Deploy( void ); + BOOL IsUseable( void ); + + void Holster( void ); + void WeaponIdle( void ); + void Throw( void ); + int m_chargeReady; +}; +LINK_ENTITY_TO_CLASS( weapon_satchel, CSatchel ); + +TYPEDESCRIPTION CSatchel::m_SaveData[] = +{ + DEFINE_FIELD( CSatchel, m_chargeReady, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CSatchel, CBasePlayerWeapon ); + +//========================================================= +// CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal +//========================================================= +int CSatchel::AddDuplicate( CBasePlayerItem *pOriginal ) +{ + CSatchel *pSatchel; + + if ( g_pGameRules->IsMultiplayer() ) + { + 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( ) +{ + if (m_chargeReady) + { + m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_satchel_radio.mdl"); + m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_satchel_radio.mdl"); + SendWeaponAnim( SATCHEL_RADIO_DRAW ); + // use hivehand animations + strcpy( m_pPlayer->m_szAnimExtention, "hive" ); + } + else + { + m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_satchel.mdl"); + m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_satchel.mdl"); + SendWeaponAnim( SATCHEL_DRAW ); + // use tripmine animations + strcpy( m_pPlayer->m_szAnimExtention, "trip" ); + } + + m_pPlayer->m_flNextAttack = gpGlobals->time + 1.0; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + return TRUE; +} + + +void CSatchel::Holster( ) +{ + m_pPlayer->m_flNextAttack = gpGlobals->time + 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; + } + } + } + + if (m_chargeReady == 1) + { + // play buzzer sound + } + else + { + // play click sound + } + + m_chargeReady = 2; + m_flNextPrimaryAttack = gpGlobals->time + 0.5; + m_flNextSecondaryAttack = gpGlobals->time + 0.5; + m_flTimeWeaponIdle = gpGlobals->time + 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; + + 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"); + SendWeaponAnim( SATCHEL_RADIO_DRAW ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_chargeReady = 1; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + m_flNextPrimaryAttack = gpGlobals->time + 1.0; + m_flNextSecondaryAttack = gpGlobals->time + 0.5; + } +} + + +void CSatchel::WeaponIdle( void ) +{ + if (m_flTimeWeaponIdle > gpGlobals->time) + 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; + } + + m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_satchel.mdl"); + m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_satchel.mdl"); + SendWeaponAnim( SATCHEL_DRAW ); + + // use tripmine animations + strcpy( m_pPlayer->m_szAnimExtention, "trip" ); + + m_flNextPrimaryAttack = gpGlobals->time + 0.5; + m_flNextSecondaryAttack = gpGlobals->time + 0.5; + m_chargeReady = 0; + break; + } + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 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 \ No newline at end of file diff --git a/dlls/saverestore.h b/dlls/saverestore.h new file mode 100644 index 0000000..a0aaf54 --- /dev/null +++ b/dlls/saverestore.h @@ -0,0 +1,170 @@ +/*** +* +* 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. +* +****/ +// 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, const int *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.h b/dlls/schedule.h new file mode 100644 index 0000000..10f36ad --- /dev/null +++ b/dlls/schedule.h @@ -0,0 +1,31 @@ +/*** +* +* 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. +* +****/ +//========================================================= +// 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/dlls/scriptevent.h b/dlls/scriptevent.h new file mode 100644 index 0000000..d660e4b --- /dev/null +++ b/dlls/scriptevent.h @@ -0,0 +1,29 @@ +/*** +* +* 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 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..b19816f --- /dev/null +++ b/dlls/shotgun.cpp @@ -0,0 +1,443 @@ +/*** +* +* 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 "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 +}; + +class CShotgun : public CBasePlayerWeapon +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int iItemSlot( ) { return 3; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + BOOL Deploy( ); + void Reload( void ); + void WeaponIdle( void ); + int m_fInReload; + float m_flNextReload; + int m_iShell; + float m_flPumpTime; +}; +LINK_ENTITY_TO_CLASS( weapon_shotgun, CShotgun ); + + +TYPEDESCRIPTION CShotgun::m_SaveData[] = +{ + DEFINE_FIELD( CShotgun, m_flNextReload, FIELD_TIME ), + DEFINE_FIELD( CShotgun, m_fInReload, 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 ); + + + +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 +} + +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 = gpGlobals->time + 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--; + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + SendWeaponAnim( SHOTGUN_FIRE ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + 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 ( m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_up * -12 + gpGlobals->v_forward * 20 + gpGlobals->v_right * 4 , vecShellVelocity, pev->angles.y, m_iShell, TE_BOUNCE_SHOTSHELL); + + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/sbarrel1.wav", RANDOM_FLOAT(0.95, 1.0), ATTN_NORM, 0, 93 + RANDOM_LONG(0,0x1f)); + + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + if ( g_pGameRules->IsDeathmatch() ) + { + // altered deathmatch spread + m_pPlayer->FireBullets( 4, vecSrc, vecAiming, VECTOR_CONE_DM_SHOTGUN, 2048, BULLET_PLAYER_BUCKSHOT, 0 ); + } + else + { + // regular old, untouched spread. + m_pPlayer->FireBullets( 6, vecSrc, vecAiming, VECTOR_CONE_10DEGREES, 2048, BULLET_PLAYER_BUCKSHOT, 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 = gpGlobals->time + 0.75; + m_flNextSecondaryAttack = gpGlobals->time + 0.75; + if (m_iClip != 0) + m_flTimeWeaponIdle = gpGlobals->time + 5.0; + else + m_flTimeWeaponIdle = 0.75; + m_fInReload = 0; + + m_pPlayer->pev->punchangle.x -= 5; +} + + +void CShotgun::SecondaryAttack( void ) +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = gpGlobals->time + 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; + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + SendWeaponAnim( SHOTGUN_FIRE2 ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + 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 ( m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_up * -12 + gpGlobals->v_forward * 20 + gpGlobals->v_right * 4 , vecShellVelocity, pev->angles.y, m_iShell, TE_BOUNCE_SHOTSHELL); + vecShellVelocity = m_pPlayer->pev->velocity + + gpGlobals->v_right * RANDOM_FLOAT(50,70) + + gpGlobals->v_up * RANDOM_FLOAT(100,150) + + gpGlobals->v_forward * 25; + EjectBrass ( m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_up * -12 + gpGlobals->v_forward * 20 + gpGlobals->v_right * 4 , vecShellVelocity, pev->angles.y, m_iShell, TE_BOUNCE_SHOTSHELL); + + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/dbarrel1.wav", RANDOM_FLOAT(0.98, 1.0), ATTN_NORM, 0, 85 + RANDOM_LONG(0,0x1f)); + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + if ( g_pGameRules->IsDeathmatch() ) + { + // tuned for deathmatch + m_pPlayer->FireBullets( 8, vecSrc, vecAiming, VECTOR_CONE_DM_DOUBLESHOTGUN, 2048, BULLET_PLAYER_BUCKSHOT, 0 ); + } + else + { + // untouched default single player + m_pPlayer->FireBullets( 12, vecSrc, vecAiming, VECTOR_CONE_10DEGREES, 2048, BULLET_PLAYER_BUCKSHOT, 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 = gpGlobals->time + 1.5; + m_flNextSecondaryAttack = gpGlobals->time + 1.5; + if (m_iClip != 0) + m_flTimeWeaponIdle = gpGlobals->time + 6.0; + else + m_flTimeWeaponIdle = 1.5; + + m_fInReload = 0; + + m_pPlayer->pev->punchangle.x -= 10; +} + + +void CShotgun::Reload( void ) +{ + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 || m_iClip == SHOTGUN_MAX_CLIP) + return; + + if (m_flNextReload > gpGlobals->time) + return; + + // don't reload until recoil is done + if (m_flNextPrimaryAttack > gpGlobals->time) + return; + + // check to see if we're ready to reload + if (m_fInReload == 0) + { + SendWeaponAnim( SHOTGUN_START_RELOAD ); + m_fInReload = 1; + m_pPlayer->m_flNextAttack = gpGlobals->time + 0.6; + m_flTimeWeaponIdle = gpGlobals->time + 0.6; + m_flNextPrimaryAttack = gpGlobals->time + 1.0; + m_flNextSecondaryAttack = gpGlobals->time + 1.0; + return; + } + else if (m_fInReload == 1) + { + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + // was waiting for gun to move to side + m_fInReload = 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 = gpGlobals->time + 0.5; + m_flTimeWeaponIdle = gpGlobals->time + 0.5; + } + else + { + // Add them to the clip + m_iClip += 1; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= 1; + m_fInReload = 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 < gpGlobals->time) + { + if (m_iClip == 0 && m_fInReload == 0 && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + Reload( ); + } + else if (m_fInReload != 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_fInReload = 0; + m_flTimeWeaponIdle = gpGlobals->time + 1.5; + } + } + else + { + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.8) + { + iAnim = SHOTGUN_IDLE_DEEP; + m_flTimeWeaponIdle = gpGlobals->time + (60.0/12.0);// * RANDOM_LONG(2, 5); + } + else if (flRand <= 0.95) + { + iAnim = SHOTGUN_IDLE; + m_flTimeWeaponIdle = gpGlobals->time + (20.0/9.0); + } + else + { + iAnim = SHOTGUN_IDLE4; + m_flTimeWeaponIdle = gpGlobals->time + (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..160757a --- /dev/null +++ b/dlls/singleplay_gamerules.cpp @@ -0,0 +1,328 @@ +/*** +* +* 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 "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..6718db5 --- /dev/null +++ b/dlls/skill.cpp @@ -0,0 +1,47 @@ +/*** +* +* 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. +* +****/ +//========================================================= +// 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..3b0cf9e --- /dev/null +++ b/dlls/skill.h @@ -0,0 +1,147 @@ +/*** +* +* 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. +* +****/ +//========================================================= +// 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..32b4943 --- /dev/null +++ b/dlls/sound.cpp @@ -0,0 +1,1982 @@ +/*** +* +* 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. +* +****/ +//========================================================= +// sound.cpp +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "player.h" +#include "talkmonster.h" +#include "gamerules.h" + + +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( 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(RampThink); + pev->nextthink = 0; + + // allow on/off switching via 'use' function. + + SetUse ( 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( SUB_Remove ); + return; + } + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_NONE; + + + SetThink(SpeakerThink); + pev->nextthink = 0.0; + + // allow on/off switching via 'use' function. + + SetUse ( 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 ); +} \ No newline at end of file diff --git a/dlls/soundent.cpp b/dlls/soundent.cpp new file mode 100644 index 0000000..ff16d49 --- /dev/null +++ b/dlls/soundent.cpp @@ -0,0 +1,379 @@ +/*** +* +* 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 "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..9753aa3 --- /dev/null +++ b/dlls/soundent.h @@ -0,0 +1,95 @@ +/*** +* +* 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. +* +****/ +//========================================================= +// 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..28002eb --- /dev/null +++ b/dlls/spectator.cpp @@ -0,0 +1,149 @@ +/*** +* +* 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. +* +****/ +// 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..f4c4ef3 --- /dev/null +++ b/dlls/spectator.h @@ -0,0 +1,27 @@ +/*** +* +* 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. +* +****/ +// Spectator.h + +class CBaseSpectator : public CBaseEntity +{ +public: + void Spawn(); + void SpectatorConnect(void); + void SpectatorDisconnect(void); + void SpectatorThink(void); + +private: + void SpectatorImpulseCommand(void); +}; \ No newline at end of file diff --git a/dlls/squeakgrenade.cpp b/dlls/squeakgrenade.cpp new file mode 100644 index 0000000..657a746 --- /dev/null +++ b/dlls/squeakgrenade.cpp @@ -0,0 +1,595 @@ +/*** +* +* 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. +* +****/ +#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 +}; + +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( SuperBounceTouch ); + SetThink( 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( 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. +} + + + +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( void ); + void WeaponIdle( void ); + int m_fJustThrown; +}; +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"); +} + + +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( ) +{ + m_pPlayer->m_flNextAttack = gpGlobals->time + 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; + + // find place to toss monster + UTIL_TraceLine( m_pPlayer->pev->origin + gpGlobals->v_forward * 16, m_pPlayer->pev->origin + gpGlobals->v_forward * 64, dont_ignore_monsters, NULL, &tr ); + + if (tr.fAllSolid == 0 && tr.fStartSolid == 0 && tr.flFraction > 0.25) + { + SendWeaponAnim( SQUEAK_THROW ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + 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; + + // 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 = gpGlobals->time + 0.3; + m_flTimeWeaponIdle = gpGlobals->time + 1.0; + } + } +} + + +void CSqueak::SecondaryAttack( void ) +{ + +} + + +void CSqueak::WeaponIdle( void ) +{ + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + if (m_fJustThrown) + { + m_fJustThrown = 0; + + if ( !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] ) + { + RetireWeapon(); + return; + } + + SendWeaponAnim( SQUEAK_UP ); + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + return; + } + + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.75) + { + iAnim = SQUEAK_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + 30.0 / 16 * (2); + } + else if (flRand <= 0.875) + { + iAnim = SQUEAK_FIDGETFIT; + m_flTimeWeaponIdle = gpGlobals->time + 70.0 / 16.0; + } + else + { + iAnim = SQUEAK_FIDGETNIP; + m_flTimeWeaponIdle = gpGlobals->time + 80.0 / 16.0; + } + SendWeaponAnim( iAnim ); +} + +#endif \ No newline at end of file diff --git a/dlls/subs.cpp b/dlls/subs.cpp new file mode 100644 index 0000000..87c024a --- /dev/null +++ b/dlls/subs.cpp @@ -0,0 +1,559 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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( 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( 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( 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.h b/dlls/talkmonster.h new file mode 100644 index 0000000..01ebedd --- /dev/null +++ b/dlls/talkmonster.h @@ -0,0 +1,26 @@ +/*** +* +* 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 TALKMONSTER_H +#define TALKMONSTER_H + +class CTalkMonster : public CBaseMonster +{ +public: + static float g_talkWaitTime; + +}; + +#endif //TALKMONSTER_H diff --git a/dlls/teamplay_gamerules.cpp b/dlls/teamplay_gamerules.cpp new file mode 100644 index 0000000..d648a15 --- /dev/null +++ b/dlls/teamplay_gamerules.cpp @@ -0,0 +1,552 @@ +/*** +* +* 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 "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(); +} + +void CHalfLifeTeamplay :: Think ( void ) +{ + ///// Check game rules ///// + + if ( g_fGameOver ) // someone else quit the game already + { + CHalfLifeMultiplay::Think(); + return; + } + + float flTimeLimit = CVAR_GET_FLOAT("mp_timelimit") * 60; + + if ( flTimeLimit != 0 && gpGlobals->time >= flTimeLimit ) + { + GoToIntermission(); + return; + } + + float flFragLimit = fraglimit.value; + if ( flFragLimit ) + { + // check if any team is over the frag limit + for ( int i = 0; i < num_teams; i++ ) + { + if ( team_scores[i] >= flFragLimit ) + { + GoToIntermission(); + return; + } + } + } +} + +//========================================================= +// 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 ( 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>\" changed to team %s\n", STRING( pPlayer->pev->netname ), GETPLAYERUSERID( pPlayer->edict() ), 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/dlls/teamplay_gamerules.h b/dlls/teamplay_gamerules.h new file mode 100644 index 0000000..54bf402 --- /dev/null +++ b/dlls/teamplay_gamerules.h @@ -0,0 +1,57 @@ +/*** +* +* 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.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( 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/dlls/trains.h b/dlls/trains.h new file mode 100644 index 0000000..449e852 --- /dev/null +++ b/dlls/trains.h @@ -0,0 +1,124 @@ +/*** +* +* 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 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; +}; + +#endif diff --git a/dlls/triggers.cpp b/dlls/triggers.cpp new file mode 100644 index 0000000..f66f692 --- /dev/null +++ b/dlls/triggers.cpp @@ -0,0 +1,2429 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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( 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 ( ManagerUse ); + SetThink ( 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 ( 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 ( 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( 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 pause\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 ( HurtTouch ); + + if ( !FStringNull ( pev->targetname ) ) + { + SetUse ( ToggleUse ); + } + else + { + SetUse ( NULL ); + } + + if (m_bitsDamageInflict & DMG_RADIATION) + { + SetThink ( 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( 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( 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( 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 = + (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( 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 ( UseChangeLevel ); + } + InitTrigger(); + if ( !(pev->spawnflags & SF_CHANGELEVEL_USEONLY) ) + SetTouch( 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( 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( 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( 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 ( EndSectionUse ); + // If it is a "use only" trigger, then don't set the touch function. + if ( ! (pev->spawnflags & SF_ENDSECTION_USEONLY) ) + SetTouch( 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( 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( 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..8ffa2ae --- /dev/null +++ b/dlls/tripmine.cpp @@ -0,0 +1,538 @@ +/*** +* +* 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 "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, +}; + + + +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( 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( 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( 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( 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( 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( 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( 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 ); +} + +class CTripmine : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 5; } + int GetItemInfo(ItemInfo *p); + void SetObjectCollisionBox( void ) + { + //!!!BUGBUG - fix the model! + pev->absmin = pev->origin + Vector(-16, -16, -5); + pev->absmax = pev->origin + Vector(16, 16, 28); + } + + void PrimaryAttack( void ); + BOOL Deploy( void ); + void Holster( void ); + void WeaponIdle( void ); +}; +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; + + if ( !g_pGameRules->IsDeathmatch() ) + { + 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" ); +} + +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( ) +{ + m_pPlayer->m_flNextAttack = gpGlobals->time + 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 ); + + if (tr.flFraction < 1.0) + { + // ALERT( at_console, "hit %f\n", tr.flFraction ); + + 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() ); + + CTripmineGrenade *pMine = (CTripmineGrenade *)pEnt; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0) + { + SendWeaponAnim( TRIPMINE_DRAW ); + } + else + { + // no more mines! + RetireWeapon(); + return; + } + } + else + { + // ALERT( at_console, "no deploy\n" ); + } + } + else + { + + } + + m_flNextPrimaryAttack = gpGlobals->time + 0.3; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); +} + +void CTripmine::WeaponIdle( void ) +{ + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0) + { + SendWeaponAnim( TRIPMINE_DRAW ); + } + else + { + RetireWeapon(); + return; + } + + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.25) + { + iAnim = TRIPMINE_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + 90.0 / 30.0; + } + else if (flRand <= 0.75) + { + iAnim = TRIPMINE_IDLE2; + m_flTimeWeaponIdle = gpGlobals->time + 60.0 / 30.0; + } + else + { + iAnim = TRIPMINE_FIDGET; + m_flTimeWeaponIdle = gpGlobals->time + 100.0 / 30.0; + } + + SendWeaponAnim( iAnim ); + +} + + + + diff --git a/dlls/util.cpp b/dlls/util.cpp new file mode 100644 index 0000000..a15016d --- /dev/null +++ b/dlls/util.cpp @@ -0,0 +1,2350 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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" + + +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 + +// 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 ) +{ + 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 + 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; +} + +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, const int *data, int count ) +{ + const char *functionName; + + functionName = NAME_FOR_FUNCTION( *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, (int *)(char *)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..065068f --- /dev/null +++ b/dlls/util.h @@ -0,0 +1,524 @@ +/*** +* +* 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. +* +****/ +// +// 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 + (int)offset) +#define MAKE_STRING(str) ((int)str - (int)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 DLLEXPORT MethodName( void ) +#define GLOBAL_METHOD(funcname) void 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" _declspec( 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 ); +} + +// 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_ADDANGLE 38 // [vec3] add this angle to the view angle +#define SVC_NEWUSERMSG 39 +#define SVC_CROSSHAIRANGLE 50 +#define SVC_SOUNDFADE 51 +#define SVC_CLIENTMAXSPEED 52 + + +// 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) ] diff --git a/dlls/vector.h b/dlls/vector.h new file mode 100644 index 0000000..282e3b9 --- /dev/null +++ b/dlls/vector.h @@ -0,0 +1,112 @@ +/*** +* +* 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 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 \ No newline at end of file diff --git a/dlls/weapons.cpp b/dlls/weapons.cpp new file mode 100644 index 0000000..a6f2db1 --- /dev/null +++ b/dlls/weapons.cpp @@ -0,0 +1,1446 @@ +/*** +* +* 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. +* +****/ +/* + +===== 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[] = +{ + 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( DefaultTouch ); + SetThink( 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 (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( 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? +} + +void CBasePlayerWeapon::ItemPostFrame( void ) +{ + if ((m_fInReload) && (m_pPlayer->m_flNextAttack <= gpGlobals->time)) + { + // 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) && (m_flNextSecondaryAttack <= gpGlobals->time)) + { + 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 <= gpGlobals->time)) + { + 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 < gpGlobals->time ) + { + // weapon isn't useable, switch. + if ( !(iFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) && g_pGameRules->GetNextBestWeapon( m_pPlayer, this ) ) + { + m_flNextPrimaryAttack = 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 < 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(SUB_Remove); + pev->nextthink = gpGlobals->time + .1; +} + +void CBasePlayerItem::Kill( void ) +{ + SetTouch( NULL ); + SetThink(SUB_Remove); + pev->nextthink = gpGlobals->time + .1; +} + +void CBasePlayerItem::Holster( void ) +{ + 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 ) +{ + int state = 0; + if ( pPlayer->m_pActiveItem == this ) + { + if ( pPlayer->m_fOnTarget ) + state = WEAPON_IS_ONTARGET; + else + state = 1; + } + + if ( !pPlayer->m_fWeapon || pPlayer->m_pActiveItem != pPlayer->m_pClientActiveItem || m_iClip != m_iClientClip || state != m_iClientWeaponState || pPlayer->m_iFOV != pPlayer->m_iClientFOV ) + { + 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 ) +{ + 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 ) +{ + 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 ); + + m_pPlayer->m_flNextAttack = gpGlobals->time + 0.5; + m_flTimeWeaponIdle = gpGlobals->time + 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 = gpGlobals->time + 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( void ) +{ + 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( 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( 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( 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(SUB_Remove); + pev->nextthink = gpGlobals->time + .1; + } + } + else if (gEvilImpulse101) + { + // evil impulse 101 hack, kill always + SetTouch( NULL ); + SetThink(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(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); +} + + diff --git a/dlls/weapons.h b/dlls/weapons.h new file mode 100644 index 0000000..0d09c23 --- /dev/null +++ b/dlls/weapons.h @@ -0,0 +1,450 @@ +/*** +* +* 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 WEAPONS_H +#define WEAPONS_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<skin < 0 || (gpGlobals->deathmatch && FBitSet( pev->spawnflags, SF_DECAL_NOTINDEATHMATCH )) ) + { + REMOVE_ENTITY(ENT(pev)); + return; + } + + if ( FStringNull ( pev->targetname ) ) + { + SetThink( 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 ( SUB_DoNothing ); + SetUse(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( 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" ); + } + +// +// 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( 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/xen.cpp b/dlls/xen.cpp new file mode 100644 index 0000000..055a18c --- /dev/null +++ b/dlls/xen.cpp @@ -0,0 +1,584 @@ +/*** +* +* 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 "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/engine/ANORMS.H b/engine/ANORMS.H new file mode 100644 index 0000000..55e95be --- /dev/null +++ b/engine/ANORMS.H @@ -0,0 +1,177 @@ +/*** +* +* 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. +* +****/ + +{-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/PROGS.H b/engine/PROGS.H new file mode 100644 index 0000000..2a9e430 --- /dev/null +++ b/engine/PROGS.H @@ -0,0 +1,145 @@ +/*** +* +* 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 PROGS_H +#define PROGS_H + +#include "progdefs.h" + +#ifndef EIFACE_H +// Forward declare this type to avoid problems +typedef struct saverestore_s SAVERESTOREDATA; +#endif + +// Entity state is used for the baseline and for delta compression of a packet of +// entities that is sent to a client. +typedef struct +{ + int entityType; // Normal or Custom to know how to parse the entity. + int number; // Index into cl_entities array for this entity. + int flags; // The delta compression bit header. + + vec3_t origin; + vec3_t angles; + + int modelindex; + int sequence; + float frame; + int colormap; + short skin; + short solid; + int effects; + float scale; + + // render information + int rendermode; + int renderamt; + color24 rendercolor; + int renderfx; + + // Added for entity delta compression + //vec3_t msg_origins[2]; + //vec3_t msg_angles[2]; + + int movetype; + float animtime; + float framerate; + int body; + byte controller[4]; + byte blending[4]; + vec3_t velocity; + + vec3_t mins; // Send bbox down to client for use during prediction. + vec3_t maxs; + + int aiment; +} entity_state_t; + +#define MAX_ENT_LEAFS 24 +typedef struct edict_s +{ + qboolean free; + int serialnumber; + link_t area; // linked to a division node or leaf + + int num_leafs; + short leafnums[MAX_ENT_LEAFS]; + + entity_state_t baseline; + + 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 +} edict_t; +#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); + +char *ED_NewString (const char *string); +// returns a copy of the string allocated from the server's string heap + +void ED_Print (edict_t *ed); +void ED_Write (SAVERESTOREDATA *save, edict_t *ed); +char *ED_ParseEdict (char *data, edict_t *ent); + + +//void ED_WriteGlobals ( SAVERESTOREDATA *save ); +//void ED_ParseGlobals (char *data); + +void ED_LoadFromFile (char *data); + +//define EDICT_NUM(n) ((edict_t *)(sv.edicts+ (n)*pr_edict_size)) +//define NUM_FOR_EDICT(e) (((byte *)(e) - sv.edicts)/pr_edict_size) + +edict_t *EDICT_NUM(int n); +int NUM_FOR_EDICT(const edict_t *e); + +#define EDICT_TO_PROG(e) ((byte *)e - (byte *)sv.edicts) +#define PROG_TO_EDICT(e) ((edict_t *)((byte *)sv.edicts + e)) + +//============================================================================ + +#if 0 +#define G_FLOAT(o) (pr_globals[o]) +#define G_INT(o) (*(int *)&pr_globals[o]) +#define G_EDICT(o) ((edict_t *)((byte *)sv.edicts+ *(int *)&pr_globals[o])) +#define G_EDICTNUM(o) NUM_FOR_EDICT(G_EDICT(o)) +#define G_VECTOR(o) (&pr_globals[o]) +#define G_STRING(o) (pr_strings + *(string_t *)&pr_globals[o]) +#define G_FUNCTION(o) (*(func_t *)&pr_globals[o]) + +#define E_FLOAT(e,o) (((float*)&e->v)[o]) +#define E_INT(e,o) (*(int *)&((float*)&e->v)[o]) +#define E_VECTOR(e,o) (&((float*)&e->v)[o]) +#endif +#define E_STRING(e,o) (pr_strings + *(string_t *)&((char *)&e->v)[o]) + +extern int type_size[8]; + +void ED_PrintEdicts (void); +void ED_PrintNum (int ent); + + +#endif // PROGS_H \ No newline at end of file diff --git a/engine/cdll_int.h b/engine/cdll_int.h new file mode 100644 index 0000000..73f7d51 --- /dev/null +++ b/engine/cdll_int.h @@ -0,0 +1,185 @@ +/*** +* +* 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 + +// 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 +{ + 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 + float viewheight; + float maxspeed; + vec3_t viewangles; + vec3_t punchangle; + int iKeyBits; // Keyboard bits + int iWeaponBits; + float fov; // field of view + float view_idlescale; // view shake/rotate + float mouse_sensitivity; + +} 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 +{ + 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; + +typedef struct +{ + char *name; + short ping; + byte thisplayer; // TRUE if this is the calling player + + // stuff that's unused at the moment, but should be done + byte spectator; + byte packetloss; + + char *model; + short topcolor; + short bottomcolor; + +} hud_player_info_t; + + +// this is by no means complete, or even accurate +typedef struct cl_enginefuncs_s +{ + // sprite handlers + HSPRITE (*pfnSPR_Load) ( const char *szPicName ); + int (*pfnSPR_Frames) ( HSPRITE hPic ); + int (*pfnSPR_Height) ( HSPRITE hPic, int frame ); + int (*pfnSPR_Width) ( HSPRITE hPic, int frame ); + void (*pfnSPR_Set) ( HSPRITE hPic, int r, int g, int b ); + void (*pfnSPR_Draw) ( int frame, int x, int y, const wrect_t *prc); + void (*pfnSPR_DrawHoles) ( int frame, int x, int y, const wrect_t *prc ); + void (*pfnSPR_DrawAdditive) ( int frame, int x, int y, const wrect_t *prc ); + void (*pfnSPR_EnableScissor) ( int x, int y, int width, int height ); + void (*pfnSPR_DisableScissor)( void ); + client_sprite_t *(*pfnSPR_GetList) ( char *psz, int *piCount); + + // screen handlers + void (*pfnFillRGBA) ( int x, int y, int width, int height, int r, int g, int b, int a); + int (*pfnGetScreenInfo) ( SCREENINFO *pscrinfo); + void (*pfnSetCrosshair) ( HSPRITE hspr, wrect_t rc, int r, int g, int b); + + // cvar handlers + int (*pfnRegisterVariable) ( char *szName, char *szValue, int flags ); + float (*pfnGetCvarFloat) ( char *szName ); + char* (*pfnGetCvarString) ( char *szName ); + + // command handlers + int (*pfnAddCommand) ( char *cmd_name, void (*function)(void) ); + int (*pfnHookUserMsg) ( char *szMsgName, pfnUserMsgHook pfn ); + int (*pfnServerCmd) ( char *szCmdString ); + int (*pfnClientCmd) ( char *szCmdString ); + + void (*pfnGetPlayerInfo) ( int ent_num, hud_player_info_t *pinfo ); + + // sound handlers + void (*pfnPlaySoundByName) ( char *szSound, float volume ); + void (*pfnPlaySoundByIndex) ( int iSound, float volume ); + + // vector helpers + void (*pfnAngleVectors) (const float * vecAngles, float * forward, float * right, float * up); + + // text message system + client_textmessage_t *(*pfnTextMessageGet) ( const char *pName ); + int (*pfnDrawCharacter) ( int x, int y, int number, int r, int g, int b ); + int (*pfnDrawConsoleString) ( int x, int y, char *string ); + void (*pfnDrawConsoleStringLen) ( const char *string, int *length, int *height ); + void (*pfnConsolePrint) ( const char *string ); + void (*pfnCenterPrint) ( const char *string ); + +} cl_enginefunc_t; + + +// ! duplicate macro's! really bad coding practice +// buttons +#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_ALT2 (1 << 15) + + +#define CLDLL_INTERFACE_VERSION 6 + +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_HudRedraw( int intermission ); + + +#endif // CDLL_INT_H diff --git a/engine/const.h b/engine/const.h new file mode 100644 index 0000000..b992521 --- /dev/null +++ b/engine/const.h @@ -0,0 +1,761 @@ +/*** +* +* 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 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_ITEM (1<<8) // NOT USED +#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_ARCHIVE_OVERRIDE (1<<20) // NOT USED +#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 + +// +// 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) + +#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_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 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 + + +// 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_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 + + +// 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_PASSABLE 8 // Train is not solid -- used to make water trains + +// buttons +#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_ALT2 (1 << 15) + +// 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!) +}; + + +typedef int func_t; +typedef 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; + +#pragma pack(push,2) +typedef struct +{ + unsigned short r, g, b, a; +} PackedColorVec; +#pragma pack(pop) + +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/engine/custom.h b/engine/custom.h new file mode 100644 index 0000000..c594026 --- /dev/null +++ b/engine/custom.h @@ -0,0 +1,76 @@ +/*** +* +* 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. +* +****/ +// Customization.h + +#ifndef INC_CUSTOMIZATION +#define INC_CUSTOMIZATION + +#define MAX_QPATH 64 // Must match value in quakedefs.h + +///////////////// +// Customization +// passed to pfnPlayerCustomization +// For automatic downloading. +typedef enum +{ + t_sound, + t_skin, + t_model, + t_decal, + t_generic +} resourcetype_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. +// MD5 Hash +typedef struct +{ + unsigned int buf[4]; + unsigned int bits[2]; + unsigned char in[64]; +} MD5Context_t; + +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. + + 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; + +#endif // !Customization \ No newline at end of file diff --git a/engine/customentity.h b/engine/customentity.h new file mode 100644 index 0000000..1a73c64 --- /dev/null +++ b/engine/customentity.h @@ -0,0 +1,82 @@ +/*** +* +* 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 CUSTOMENTITY_H +#define CUSTOMENTITY_H + + +// Custom Entities +#define CUSTOM_TYPE ( (1<>CUSTOM_SHIFT) +#define SET_CUSTOM_TYPE(customType) ( ((customType<>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/cvardef.h b/engine/cvardef.h new file mode 100644 index 0000000..9d471f8 --- /dev/null +++ b/engine/cvardef.h @@ -0,0 +1,34 @@ +/*** +* +* 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 CVARDEF_H +#define CVARDEF_H + +#define FCVAR_ARCHIVE 1 // set to cause it to be saved to vars.rc +#define FCVAR_USERINFO 2 // changes the client's info string +#define FCVAR_SERVER 4 // notifies players when changed +#define FCVAR_EXTDLL 8 // defined by external DLL +#define FCVAR_CLIENTDLL 16 // defined by the client dll +#define FCVAR_PROTECTED 32 // 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 64 // This cvar cannot be changed by clients connected to a multiplayer server. + +typedef struct cvar_s +{ + char *name; + char *string; + int flags; + float value; + struct cvar_s *next; +} cvar_t; +#endif \ No newline at end of file diff --git a/engine/eiface.h b/engine/eiface.h new file mode 100644 index 0000000..7bcc46d --- /dev/null +++ b/engine/eiface.h @@ -0,0 +1,373 @@ +/*** +* +* 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 EIFACE_H +#define EIFACE_H + +#ifdef HLDEMO_BUILD +#define INTERFACE_VERSION 001 +#else // !HLDEMO_BUILD, i.e., regular version of HL +#define INTERFACE_VERSION 138 +#endif // !HLDEMO_BUILD + +#include "custom.h" + +#include "cvardef.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 +// + + +#define DLLEXPORT __stdcall + +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; + + +// 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; + +typedef unsigned long CRC32_t; + +// 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) (FILE *pfile, char *szFmt, ...); + void* (*pfnPvAllocEntPrivateData) (edict_t *pEdict, long cb); + void* (*pfnPvEntPrivateData) (edict_t *pEdict); + void (*pfnFreeEntPrivateData) (edict_t *pEdict); + const char* (*pfnSzFromIndex) (int iString); + int (*pfnAllocString) (const char *szValue); + entvars_t* (*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) (entvars_t* 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 ); + unsigned long (*pfnFunctionFromName) ( const char *pName ); + const char *(*pfnNameForFunction) ( unsigned long 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); + long (*pfnRandomLong) (long lLow, long 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? +} enginefuncs_t; +// be sure to bump INTERFACE_VERSION if you add a new function + +// Passed to pfnKeyValue +typedef struct KeyValueData_s +{ + char *szClassName; // in: entity classname + char *szKeyName; // in: name of key + char *szValue; // in: value of key + long 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 +{ + 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 + +} SAVERESTOREDATA; + +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; + +#ifndef offsetof +#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 +{ + void (*pfnGameInit) ( void ); // Initialize the game (one-time call from Host_Init() ) + 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 ); // JOHN: client 'cmd' commands are passed through to the DLL + void (*pfnClientUserInfoChanged)( edict_t *pEntity, char *infobuffer ); + + void (*pfnServerActivate) ( edict_t *pEdictList, int edictCount, int clientMax ); + + void (*pfnPlayerPreThink) ( edict_t *pEntity ); + void (*pfnPlayerPostThink) ( edict_t *pEntity ); + + void (*pfnStartFrame) ( void ); + void (*pfnParmsNewLevel) ( void ); + void (*pfnParmsChangeLevel) ( void ); + + const char *(*pfnGetGameDescription)( void ); // Returns string describing current .dll. E.g., TeamFotrress 2, Half-Life + // More descriptive than just com_gamedir. + + void (*pfnPlayerCustomization) ( edict_t *pEntity, customization_t *pCustom ); // Notify dll about a player customization. + + // Spectator funcs + void (*pfnSpectatorConnect) ( edict_t *pEntity ); + void (*pfnSpectatorDisconnect) ( edict_t *pEntity ); + void (*pfnSpectatorThink) ( edict_t *pEntity ); +} DLL_FUNCTIONS; + +extern DLL_FUNCTIONS gEntityInterface; + +typedef int (*APIFUNCTION)( DLL_FUNCTIONS *pFunctionTable, int interfaceVersion ); + + +#endif EIFACE_H +; diff --git a/engine/progdefs.h b/engine/progdefs.h new file mode 100644 index 0000000..87295d3 --- /dev/null +++ b/engine/progdefs.h @@ -0,0 +1,176 @@ +/*** +* +* 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 PROGDEFS_H +#define PROGDEFS_H + + +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 +{ + 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) + 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; +} entvars_t; + + +#endif // PROGDEFS_H \ No newline at end of file diff --git a/engine/shake.h b/engine/shake.h new file mode 100644 index 0000000..031533a --- /dev/null +++ b/engine/shake.h @@ -0,0 +1,55 @@ +/*** +* +* 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 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( vec3_t origin, vec3_t 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 + +// 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..381f8ed --- /dev/null +++ b/engine/studio.h @@ -0,0 +1,356 @@ +/*** +* +* 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 _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 256 // total animation sequences +#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 4 +#define MAXSTUDIOANIMATIONS 512 // per sequence +#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; + +#ifndef ZONE_H +typedef void *cache_user_t; +#endif + +// demand loaded sequence groups +typedef struct +{ + char label[32]; // textual name + char name[64]; // file name + cache_user_t cache; // cache index pointer + int 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 +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 + +// 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 \ No newline at end of file diff --git a/utils/bspinfo/bspinfo.c b/utils/bspinfo/bspinfo.c new file mode 100644 index 0000000..545f605 --- /dev/null +++ b/utils/bspinfo/bspinfo.c @@ -0,0 +1,48 @@ +/*** +* +* 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 "bspfile.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..2e0e97d --- /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/bspinfo/bspinfo.mak b/utils/bspinfo/bspinfo.mak new file mode 100644 index 0000000..2413570 --- /dev/null +++ b/utils/bspinfo/bspinfo.mak @@ -0,0 +1,253 @@ +# Microsoft Developer Studio Generated NMAKE File, Format Version 4.20 +# ** DO NOT EDIT ** + +# TARGTYPE "Win32 (x86) Console Application" 0x0103 + +!IF "$(CFG)" == "" +CFG=bspinfo - Win32 Debug +!MESSAGE No configuration specified. Defaulting to bspinfo - Win32 Debug. +!ENDIF + +!IF "$(CFG)" != "bspinfo - Win32 Release" && "$(CFG)" !=\ + "bspinfo - Win32 Debug" +!MESSAGE Invalid configuration "$(CFG)" specified. +!MESSAGE You can specify a configuration when running NMAKE on this makefile +!MESSAGE by defining the macro CFG on the command line. For example: +!MESSAGE +!MESSAGE NMAKE /f "bspinfo.mak" CFG="bspinfo - Win32 Debug" +!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 +!ERROR An invalid configuration is specified. +!ENDIF + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF +################################################################################ +# Begin Project +# PROP Target_Last_Scanned "bspinfo - Win32 Debug" +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 "" +OUTDIR=.\Release +INTDIR=.\Release + +ALL : "$(OUTDIR)\bspinfo.exe" + +CLEAN : + -@erase "$(INTDIR)\bspfile.obj" + -@erase "$(INTDIR)\bspinfo.obj" + -@erase "$(INTDIR)\cmdlib.obj" + -@erase "$(INTDIR)\scriplib.obj" + -@erase "$(OUTDIR)\bspinfo.exe" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +# 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 /c +CPP_PROJ=/nologo /ML /GX /O2 /I "..\common" /D "WIN32" /D "NDEBUG" /D\ + "_CONSOLE" /Fp"$(INTDIR)/bspinfo.pch" /YX /Fo"$(INTDIR)/" /c +CPP_OBJS=.\Release/ +CPP_SBRS=.\. +# ADD BASE RSC /l 0x409 /d "NDEBUG" +# ADD RSC /l 0x409 /d "NDEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/bspinfo.bsc" +BSC32_SBRS= \ + +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 +LINK32_FLAGS=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 /incremental:no\ + /pdb:"$(OUTDIR)/bspinfo.pdb" /machine:I386 /out:"$(OUTDIR)/bspinfo.exe" +LINK32_OBJS= \ + "$(INTDIR)\bspfile.obj" \ + "$(INTDIR)\bspinfo.obj" \ + "$(INTDIR)\cmdlib.obj" \ + "$(INTDIR)\scriplib.obj" + +"$(OUTDIR)\bspinfo.exe" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!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 "" +OUTDIR=.\Debug +INTDIR=.\Debug + +ALL : "$(OUTDIR)\bspinfo.exe" + +CLEAN : + -@erase "$(INTDIR)\bspfile.obj" + -@erase "$(INTDIR)\bspinfo.obj" + -@erase "$(INTDIR)\cmdlib.obj" + -@erase "$(INTDIR)\scriplib.obj" + -@erase "$(INTDIR)\vc40.idb" + -@erase "$(INTDIR)\vc40.pdb" + -@erase "$(OUTDIR)\bspinfo.exe" + -@erase "$(OUTDIR)\bspinfo.ilk" + -@erase "$(OUTDIR)\bspinfo.pdb" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +# 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 /c +CPP_PROJ=/nologo /MLd /Gm /GX /Zi /Od /I "..\common" /D "WIN32" /D "_DEBUG" /D\ + "_CONSOLE" /Fp"$(INTDIR)/bspinfo.pch" /YX /Fo"$(INTDIR)/" /Fd"$(INTDIR)/" /c +CPP_OBJS=.\Debug/ +CPP_SBRS=.\. +# ADD BASE RSC /l 0x409 /d "_DEBUG" +# ADD RSC /l 0x409 /d "_DEBUG" +BSC32=bscmake.exe +# ADD BASE BSC32 /nologo +# ADD BSC32 /nologo +BSC32_FLAGS=/nologo /o"$(OUTDIR)/bspinfo.bsc" +BSC32_SBRS= \ + +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 +LINK32_FLAGS=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 /incremental:yes\ + /pdb:"$(OUTDIR)/bspinfo.pdb" /debug /machine:I386 /out:"$(OUTDIR)/bspinfo.exe" +LINK32_OBJS= \ + "$(INTDIR)\bspfile.obj" \ + "$(INTDIR)\bspinfo.obj" \ + "$(INTDIR)\cmdlib.obj" \ + "$(INTDIR)\scriplib.obj" + +"$(OUTDIR)\bspinfo.exe" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +!ENDIF + +.c{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_OBJS)}.obj: + $(CPP) $(CPP_PROJ) $< + +.c{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cpp{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +.cxx{$(CPP_SBRS)}.sbr: + $(CPP) $(CPP_PROJ) $< + +################################################################################ +# Begin Target + +# Name "bspinfo - Win32 Release" +# Name "bspinfo - Win32 Debug" + +!IF "$(CFG)" == "bspinfo - Win32 Release" + +!ELSEIF "$(CFG)" == "bspinfo - Win32 Debug" + +!ENDIF + +################################################################################ +# Begin Source File + +SOURCE=.\bspinfo.c +DEP_CPP_BSPIN=\ + "..\common\bspfile.h"\ + "..\common\cmdlib.h"\ + "..\common\mathlib.h"\ + + +"$(INTDIR)\bspinfo.obj" : $(SOURCE) $(DEP_CPP_BSPIN) "$(INTDIR)" + + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=..\common\scriplib.c +DEP_CPP_SCRIP=\ + "..\common\cmdlib.h"\ + "..\common\scriplib.h"\ + + +"$(INTDIR)\scriplib.obj" : $(SOURCE) $(DEP_CPP_SCRIP) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=..\common\cmdlib.c +DEP_CPP_CMDLI=\ + "..\common\cmdlib.h"\ + {$(INCLUDE)}"\sys\stat.h"\ + {$(INCLUDE)}"\sys\types.h"\ + + +"$(INTDIR)\cmdlib.obj" : $(SOURCE) $(DEP_CPP_CMDLI) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +# End Source File +################################################################################ +# Begin Source File + +SOURCE=..\common\bspfile.c +DEP_CPP_BSPFI=\ + "..\common\bspfile.h"\ + "..\common\cmdlib.h"\ + "..\common\mathlib.h"\ + "..\common\scriplib.h"\ + + +"$(INTDIR)\bspfile.obj" : $(SOURCE) $(DEP_CPP_BSPFI) "$(INTDIR)" + $(CPP) $(CPP_PROJ) $(SOURCE) + + +# End Source File +# End Target +# End Project +################################################################################ diff --git a/utils/bspinfo/mssccprj.scc b/utils/bspinfo/mssccprj.scc new file mode 100644 index 0000000..249ea30 --- /dev/null +++ b/utils/bspinfo/mssccprj.scc @@ -0,0 +1,4 @@ +SCC = This is a Source Code Control file + +[bspinfo.mak] +SCC_Project_Name = "$/HLStandardSDK/SourceCode/utils/bspinfo", NBXHAAAA diff --git a/utils/common/bspfile.c b/utils/common/bspfile.c new file mode 100644 index 0000000..f5a6524 --- /dev/null +++ b/utils/common/bspfile.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 "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..c15fc5d --- /dev/null +++ b/utils/common/bspfile.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..356dfe0 --- /dev/null +++ b/utils/common/cmdlib.c @@ -0,0 +1,1022 @@ +/*** +* +* 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. +* +****/ + +// 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]; + + +/* +================= +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; + +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/lbmlib.c b/utils/common/lbmlib.c new file mode 100644 index 0000000..61af1cb --- /dev/null +++ b/utils/common/lbmlib.c @@ -0,0 +1,744 @@ +/*** +* +* 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. +* +****/ + +// 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..bdb032e --- /dev/null +++ b/utils/common/mathlib.h @@ -0,0 +1,89 @@ +/*** +* +* 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 __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/movie.h b/utils/common/movie.h new file mode 100644 index 0000000..8594905 --- /dev/null +++ b/utils/common/movie.h @@ -0,0 +1,36 @@ +/*** +* +* 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 _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..eef7e8b --- /dev/null +++ b/utils/common/polylib.c @@ -0,0 +1,708 @@ +/*** +* +* 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 "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..cda1fc0 --- /dev/null +++ b/utils/common/polylib.h @@ -0,0 +1,36 @@ +/*** +* +* 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. +* +****/ + + +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..c5e6f0d --- /dev/null +++ b/utils/common/scriplib.c @@ -0,0 +1,268 @@ +/*** +* +* 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. +* +****/ + +// 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..9a20cf5 --- /dev/null +++ b/utils/common/scriplib.h @@ -0,0 +1,33 @@ +/*** +* +* 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. +* +****/ + +// 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..b5dfae2 --- /dev/null +++ b/utils/common/threads.c @@ -0,0 +1,330 @@ +/*** +* +* 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" +#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..d1a131e --- /dev/null +++ b/utils/common/trilib.h @@ -0,0 +1,21 @@ +/*** +* +* 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. +* +****/ + +// +// 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..a53fba3 --- /dev/null +++ b/utils/common/wadlib.c @@ -0,0 +1,339 @@ +/*** +* +* 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. +* +****/ + +// 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..6e065e4 --- /dev/null +++ b/utils/common/wadlib.h @@ -0,0 +1,63 @@ +/*** +* +* 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. +* +****/ + +// 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/makels/makels.cpp b/utils/makels/makels.cpp new file mode 100644 index 0000000..c59596d --- /dev/null +++ b/utils/makels/makels.cpp @@ -0,0 +1,173 @@ +/*** +* +* 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 +#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