From 030f9987fd8d6e543de2ff492d9cf91031774d4c Mon Sep 17 00:00:00 2001 From: archive Date: Mon, 23 Dec 2002 00:00:00 +0000 Subject: [PATCH] as released 2002-12-23 --- Network/delta.lst | 10 +- cl_dll/GameStudioModelRenderer.cpp | 2 +- cl_dll/GameStudioModelRenderer.h | 2 +- cl_dll/GameStudioModelRenderer_Sample.cpp | 2 +- cl_dll/GameStudioModelRenderer_Sample.h | 2 +- cl_dll/StudioModelRenderer.cpp | 29 +- cl_dll/StudioModelRenderer.h | 2 +- cl_dll/ammo.cpp | 49 +- cl_dll/ammo.h | 2 +- cl_dll/ammo_secondary.cpp | 2 +- cl_dll/ammohistory.cpp | 2 +- cl_dll/ammohistory.h | 2 +- cl_dll/battery.cpp | 2 +- cl_dll/camera.h | 2 +- cl_dll/cdll_int.cpp | 22 +- cl_dll/cl_dll.dsp | 16 +- cl_dll/cl_dll.h | 2 +- cl_dll/cl_util.h | 6 +- cl_dll/com_weapons.cpp | 2 +- cl_dll/com_weapons.h | 2 +- cl_dll/death.cpp | 10 +- cl_dll/demo.cpp | 2 +- cl_dll/demo.h | 2 +- cl_dll/entity.cpp | 17 +- cl_dll/ev_common.cpp | 13 +- cl_dll/ev_hldm.cpp | 30 +- cl_dll/ev_hldm.h | 2 +- cl_dll/events.cpp | 2 +- cl_dll/eventscripts.h | 2 +- cl_dll/flashlight.cpp | 2 +- cl_dll/geiger.cpp | 2 +- cl_dll/health.cpp | 2 +- cl_dll/health.h | 2 +- cl_dll/hl/hl_baseentity.cpp | 82 +- cl_dll/hl/hl_events.cpp | 2 +- cl_dll/hl/hl_objects.cpp | 2 +- cl_dll/hl/hl_weapons.cpp | 24 +- cl_dll/hud.cpp | 19 +- cl_dll/hud.h | 13 +- cl_dll/hud_iface.h | 2 +- cl_dll/hud_msg.cpp | 9 +- cl_dll/hud_redraw.cpp | 36 +- cl_dll/hud_servers.cpp | 2 +- cl_dll/hud_servers.h | 2 +- cl_dll/hud_servers_priv.h | 2 +- cl_dll/hud_spectator.cpp | 1228 +++++-- cl_dll/hud_spectator.h | 45 +- cl_dll/hud_update.cpp | 2 +- cl_dll/in_camera.cpp | 4 +- cl_dll/in_defs.h | 2 +- cl_dll/input.cpp | 10 +- cl_dll/inputw32.cpp | 2 +- cl_dll/kbutton.h | 2 +- cl_dll/menu.cpp | 2 +- cl_dll/message.cpp | 22 +- cl_dll/overview.cpp | 2 +- cl_dll/overview.h | 2 +- cl_dll/parsemsg.cpp | 2 +- cl_dll/parsemsg.h | 2 +- cl_dll/saytext.cpp | 16 +- cl_dll/soundsystem.cpp | 2 +- cl_dll/status_icons.cpp | 4 +- cl_dll/statusbar.cpp | 14 +- cl_dll/studio_util.cpp | 2 +- cl_dll/studio_util.h | 2 +- cl_dll/text_message.cpp | 2 +- cl_dll/tf_defs.h | 66 +- cl_dll/train.cpp | 2 +- cl_dll/tri.cpp | 2 +- cl_dll/util.cpp | 2 +- cl_dll/util_vector.h | 2 +- cl_dll/vgui_ClassMenu.cpp | 3 +- cl_dll/vgui_ConsolePanel.cpp | 2 +- cl_dll/vgui_ConsolePanel.h | 2 +- cl_dll/vgui_ControlConfigPanel.cpp | 2 +- cl_dll/vgui_ControlConfigPanel.h | 2 +- cl_dll/vgui_CustomObjects.cpp | 114 +- cl_dll/vgui_MOTDWindow.cpp | 2 +- cl_dll/vgui_SchemeManager.cpp | 4 +- cl_dll/vgui_SchemeManager.h | 2 +- cl_dll/vgui_ScorePanel.cpp | 39 +- cl_dll/vgui_ScorePanel.h | 14 +- cl_dll/vgui_ServerBrowser.cpp | 2 +- cl_dll/vgui_ServerBrowser.h | 2 +- cl_dll/vgui_SpectatorPanel.cpp | 383 ++ cl_dll/vgui_SpectatorPanel.h | 102 + cl_dll/vgui_TeamFortressViewport.cpp | 492 ++- cl_dll/vgui_TeamFortressViewport.h | 488 ++- cl_dll/vgui_int.cpp | 2 +- cl_dll/vgui_int.h | 2 +- cl_dll/vgui_teammenu.cpp | 3 +- cl_dll/view.cpp | 1041 ++++-- cl_dll/view.h | 2 +- cl_dll/wrect.h | 2 +- common/beamdef.h | 2 +- common/cl_entity.h | 2 +- common/com_model.h | 2 +- common/con_nprint.h | 2 +- common/const.h | 7 +- common/crc.h | 2 +- common/cvardef.h | 2 +- common/demo_api.h | 2 +- common/director_cmds.h | 2 +- common/dlight.h | 4 +- common/dll_state.h | 3 +- common/engine_launcher_api.h | 2 +- common/entity_state.h | 2 +- common/entity_types.h | 2 +- common/event_api.h | 2 +- common/event_args.h | 2 +- common/event_flags.h | 2 +- common/exefuncs.h | 4 +- common/hltv.h | 61 +- common/in_buttons.h | 2 +- common/interface.cpp | 6 +- common/interface.h | 2 +- common/ivoicetweak.h | 2 +- common/mathlib.h | 9 +- common/net_api.h | 2 +- common/netadr.h | 2 +- common/nowin.h | 2 +- common/particledef.h | 2 +- common/pmtrace.h | 2 +- common/qfont.h | 2 +- common/r_efx.h | 2 +- {cl_dll => common}/r_studioint.h | 34 +- common/ref_params.h | 2 +- common/screenfade.h | 2 +- common/studio_event.h | 2 +- common/triangleapi.h | 6 +- common/usercmd.h | 2 +- common/weaponinfo.h | 2 +- dedicated/Dedicated.dsp | 32 +- dedicated/Makefile | 9 +- dedicated/conproc.cpp | 2 +- dedicated/conproc.h | 2 +- dedicated/dedicated.h | 4 +- dedicated/engine.cpp | 37 +- dedicated/enginecallback.h | 2 +- dedicated/exports.h | 2 +- dedicated/md5.cpp | 324 -- dedicated/md5.h | 14 - dedicated/resource.h | 2 +- dedicated/sys_ded.cpp | 442 +-- dedicated/sys_ded.h | 4 +- dlls/AI_BaseNPC_Schedule.cpp | 1514 ++++++++ dlls/Makefile | 46 +- dlls/Wxdebug.cpp | 395 ++ dlls/activity.h | 2 +- dlls/activitymap.h | 2 +- dlls/aflock.cpp | 910 +++++ dlls/agrunt.cpp | 1186 ++++++ dlls/airtank.cpp | 2 +- dlls/animating.cpp | 2 +- dlls/animation.cpp | 2 +- dlls/animation.h | 2 +- dlls/apache.cpp | 1050 ++++++ dlls/barnacle.cpp | 428 +++ dlls/barney.cpp | 841 +++++ dlls/basemonster.h | 349 +- dlls/bigmomma.cpp | 1251 +++++++ dlls/bloater.cpp | 219 ++ dlls/bmodels.cpp | 2 +- dlls/bullsquid.cpp | 1275 +++++++ dlls/buttons.cpp | 2 +- dlls/cbase.cpp | 2 +- dlls/cbase.h | 2 +- dlls/cdll_dll.h | 2 +- dlls/client.cpp | 46 +- dlls/client.h | 2 +- dlls/combat.cpp | 2 +- dlls/controller.cpp | 1427 ++++++++ dlls/crossbow.cpp | 2 +- dlls/crowbar.cpp | 2 +- dlls/decals.h | 2 +- dlls/defaultai.cpp | 1232 +++++++ dlls/defaultai.h | 98 + dlls/doors.cpp | 2 +- dlls/doors.h | 2 +- dlls/effects.cpp | 2 +- dlls/effects.h | 2 +- dlls/egon.cpp | 2 +- dlls/enginecallback.h | 4 +- dlls/explode.cpp | 2 +- dlls/explode.h | 2 +- dlls/extdll.h | 2 +- dlls/flyingmonster.cpp | 281 ++ dlls/flyingmonster.h | 53 + dlls/func_break.cpp | 2 +- dlls/func_break.h | 2 +- dlls/func_tank.cpp | 2 +- dlls/game.cpp | 2 +- dlls/game.h | 2 +- dlls/gamerules.cpp | 2 +- dlls/gamerules.h | 2 +- dlls/gargantua.cpp | 1368 +++++++ dlls/gauss.cpp | 11 +- dlls/genericmonster.cpp | 140 + dlls/ggrenade.cpp | 2 +- dlls/globals.cpp | 2 +- dlls/glock.cpp | 325 ++ dlls/gman.cpp | 237 ++ dlls/h_ai.cpp | 2 +- dlls/h_battery.cpp | 2 +- dlls/h_cine.cpp | 241 ++ dlls/h_cycler.cpp | 2 +- dlls/h_export.cpp | 2 +- dlls/handgrenade.cpp | 2 +- dlls/hassassin.cpp | 1015 ++++++ dlls/headcrab.cpp | 555 +++ dlls/healthkit.cpp | 2 +- dlls/hgrunt.cpp | 2517 +++++++++++++ dlls/{mp.def => hl.def} | 2 +- dlls/{mp.dsp => hl.dsp} | 245 +- dlls/hlgl.def | 15 + dlls/hornet.cpp | 2 +- dlls/hornet.h | 2 +- dlls/hornetgun.cpp | 6 +- dlls/houndeye.cpp | 1304 +++++++ dlls/ichthyosaur.cpp | 1108 ++++++ dlls/islave.cpp | 866 +++++ dlls/items.cpp | 2 +- dlls/items.h | 2 +- dlls/leech.cpp | 723 ++++ dlls/lights.cpp | 2 +- dlls/maprules.cpp | 2 +- dlls/maprules.h | 2 +- dlls/monsterevent.h | 2 +- dlls/monstermaker.cpp | 292 ++ dlls/monsters.cpp | 3448 ++++++++++++++++++ dlls/monsters.h | 105 +- dlls/monsterstate.cpp | 234 ++ dlls/mortar.cpp | 2 +- dlls/mp5.cpp | 2 +- dlls/mpstubb.cpp | 2 +- dlls/multiplay_gamerules.cpp | 60 +- dlls/nihilanth.cpp | 1836 ++++++++++ dlls/nodes.cpp | 3640 +++++++++++++++++++ dlls/nodes.h | 348 +- dlls/osprey.cpp | 805 ++++ dlls/pathcorner.cpp | 2 +- dlls/plane.cpp | 2 +- dlls/plane.h | 2 +- dlls/plats.cpp | 2 +- dlls/player.cpp | 138 +- dlls/player.h | 27 +- dlls/playermonster.cpp | 122 + dlls/python.cpp | 2 +- dlls/rat.cpp | 98 + dlls/roach.cpp | 460 +++ dlls/rpg.cpp | 2 +- dlls/satchel.cpp | 2 +- dlls/saverestore.h | 2 +- dlls/schedule.cpp | 1514 ++++++++ dlls/schedule.h | 269 +- dlls/scientist.cpp | 1428 ++++++++ dlls/scripted.cpp | 1260 +++++++ dlls/scripted.h | 107 + dlls/scriptevent.h | 2 +- dlls/shotgun.cpp | 2 +- dlls/singleplay_gamerules.cpp | 2 +- dlls/skill.cpp | 2 +- dlls/skill.h | 2 +- dlls/sound.cpp | 2 +- dlls/soundent.cpp | 2 +- dlls/soundent.h | 2 +- dlls/spectator.cpp | 2 +- dlls/spectator.h | 2 +- dlls/squad.h | 20 + dlls/squadmonster.cpp | 623 ++++ dlls/squadmonster.h | 120 + dlls/squeakgrenade.cpp | 2 +- dlls/stats.cpp | 156 + dlls/subs.cpp | 2 +- dlls/talkmonster.cpp | 1472 ++++++++ dlls/talkmonster.h | 171 +- dlls/teamplay_gamerules.cpp | 6 +- dlls/teamplay_gamerules.h | 2 +- dlls/tempmonster.cpp | 117 + dlls/tentacle.cpp | 1044 ++++++ dlls/trains.h | 2 +- dlls/triggers.cpp | 7 +- dlls/tripmine.cpp | 2 +- dlls/turret.cpp | 1305 +++++++ dlls/util.cpp | 2 +- dlls/util.h | 8 +- dlls/vector.h | 2 +- dlls/weapons.cpp | 2 +- dlls/weapons.h | 2 +- dlls/world.cpp | 2 +- dlls/wpn_shared/hl_wpn_glock.cpp | 2 +- dlls/wxdebug.h | 137 + dlls/xen.cpp | 2 +- dlls/zombie.cpp | 346 ++ engine/anorms.h | 2 +- engine/cdll_int.h | 16 +- engine/custom.h | 9 +- engine/customentity.h | 2 +- engine/edict.h | 2 +- engine/eiface.h | 4 +- engine/keydefs.h | 2 +- engine/progdefs.h | 2 +- engine/progs.h | 2 +- engine/shake.h | 2 +- engine/studio.h | 2 +- game_shared/bitvec.h | 2 +- game_shared/vgui_checkbutton2.cpp | 2 +- game_shared/vgui_checkbutton2.h | 2 +- game_shared/vgui_defaultinputsignal.h | 2 +- game_shared/vgui_grid.cpp | 2 +- game_shared/vgui_grid.h | 2 +- game_shared/vgui_helpers.cpp | 2 +- game_shared/vgui_helpers.h | 2 +- game_shared/vgui_listbox.cpp | 2 +- game_shared/vgui_listbox.h | 2 +- game_shared/vgui_loadtga.cpp | 2 +- game_shared/vgui_loadtga.h | 2 +- game_shared/vgui_scrollbar2.cpp | 2 +- game_shared/vgui_scrollbar2.h | 2 +- game_shared/vgui_slider2.cpp | 2 +- game_shared/vgui_slider2.h | 2 +- game_shared/voice_banmgr.cpp | 2 +- game_shared/voice_banmgr.h | 2 +- game_shared/voice_common.h | 2 +- game_shared/voice_gamemgr.cpp | 23 +- game_shared/voice_gamemgr.h | 6 +- game_shared/voice_status.cpp | 82 +- game_shared/voice_status.h | 11 +- game_shared/voice_vgui_tweakdlg.cpp | 2 +- game_shared/voice_vgui_tweakdlg.h | 2 +- pm_shared/pm_debug.c | 24 +- pm_shared/pm_debug.h | 2 +- pm_shared/pm_defs.h | 12 +- pm_shared/pm_info.h | 4 +- pm_shared/pm_materials.h | 2 +- pm_shared/pm_math.c | 20 +- pm_shared/pm_movevars.h | 2 +- pm_shared/pm_shared.c | 267 +- pm_shared/pm_shared.h | 11 +- utils/bspinfo/bspinfo.c | 2 +- utils/common/bspfile.c | 2 +- utils/common/bspfile.h | 2 +- utils/common/cmdlib.c | 2 +- utils/common/cmdlib.h | 2 +- utils/common/lbmlib.c | 2 +- utils/common/lbmlib.h | 2 +- utils/common/mathlib.c | 2 +- utils/common/mathlib.h | 2 +- utils/common/movie.h | 2 +- utils/common/polylib.c | 2 +- utils/common/polylib.h | 2 +- utils/common/scriplib.c | 2 +- utils/common/scriplib.h | 2 +- utils/common/threads.c | 2 +- utils/common/threads.h | 2 +- utils/common/trilib.c | 2 +- utils/common/trilib.h | 2 +- utils/common/wadlib.c | 2 +- utils/common/wadlib.h | 2 +- utils/light/light.c | 2 +- utils/light/light.dsp | 1 - utils/light/light.h | 2 +- utils/light/ltface.c | 2 +- utils/light/trace.c | 2 +- utils/makefont/makefont.cpp | 2 +- utils/makels/makels.cpp | 2 +- utils/mdlviewer/mdlviewer.cpp | 2 +- utils/mdlviewer/mdlviewer.dsp | 4 +- utils/mdlviewer/mdlviewer.dsw | 10 +- utils/mdlviewer/mdlviewer.h | 2 +- utils/mdlviewer/studio_render.cpp | 2 +- utils/mdlviewer/studio_utils.cpp | 2 +- utils/mkmovie/mkmovie.c | 2 +- utils/procinfo/lib/win32_vc6/procinfo.lib | Bin 2468 -> 2440 bytes utils/procinfo/procinfo.h | 2 +- utils/qbsp2/bsp5.h | 2 +- utils/qbsp2/cull.c | 2 +- utils/qbsp2/gldraw.c | 2 +- utils/qbsp2/merge.c | 2 +- utils/qbsp2/nodraw.c | 2 +- utils/qbsp2/outside.c | 2 +- utils/qbsp2/portals.c | 2 +- utils/qbsp2/qbsp.c | 2 +- utils/qbsp2/solidbsp.c | 2 +- utils/qbsp2/surfaces.c | 2 +- utils/qbsp2/tjunc.c | 2 +- utils/qbsp2/writebsp.c | 2 +- utils/qcsg/brush.c | 2 +- utils/qcsg/csg.h | 4 +- utils/qcsg/gldraw.c | 2 +- utils/qcsg/hullfile.c | 2 +- utils/qcsg/map.c | 2 +- utils/qcsg/qcsg.c | 2 +- utils/qcsg/textures.c | 2 +- utils/qlumpy/qlumpy.c | 2 +- utils/qlumpy/qlumpy.h | 2 +- utils/qlumpy/quakegrb.c | 2 +- utils/qrad/lightmap.c | 16 +- utils/qrad/qrad.c | 2 +- utils/qrad/qrad.h | 2 +- utils/qrad/trace.c | 2 +- utils/qrad/vismat.c | 2 +- utils/smdlexp/smdlexp.cpp | 2 +- utils/smdlexp/smedefs.h | 2 +- utils/sprgen/sprgen.c | 2 +- utils/sprgen/spritegn.h | 2 +- utils/studiomdl/bmpread.c | 2 +- utils/studiomdl/studiomdl.c | 61 +- utils/studiomdl/studiomdl.h | 2 +- utils/studiomdl/tristrip.c | 2 +- utils/studiomdl/write.c | 2 +- utils/vgui/include/VGUI.h | 2 +- utils/vgui/include/VGUI_ActionSignal.h | 2 +- utils/vgui/include/VGUI_App.h | 2 +- utils/vgui/include/VGUI_Bitmap.h | 2 +- utils/vgui/include/VGUI_BitmapTGA.h | 2 +- utils/vgui/include/VGUI_Border.h | 2 +- utils/vgui/include/VGUI_BorderLayout.h | 2 +- utils/vgui/include/VGUI_BorderPair.h | 2 +- utils/vgui/include/VGUI_BuildGroup.h | 2 +- utils/vgui/include/VGUI_Button.h | 2 +- utils/vgui/include/VGUI_ButtonController.h | 2 +- utils/vgui/include/VGUI_ButtonGroup.h | 2 +- utils/vgui/include/VGUI_ChangeSignal.h | 2 +- utils/vgui/include/VGUI_CheckButton.h | 2 +- utils/vgui/include/VGUI_Color.h | 2 +- utils/vgui/include/VGUI_ComboKey.h | 2 +- utils/vgui/include/VGUI_ConfigWizard.h | 2 +- utils/vgui/include/VGUI_Cursor.h | 2 +- utils/vgui/include/VGUI_Dar.h | 2 +- utils/vgui/include/VGUI_DataInputStream.h | 2 +- utils/vgui/include/VGUI_Desktop.h | 2 +- utils/vgui/include/VGUI_DesktopIcon.h | 2 +- utils/vgui/include/VGUI_EditPanel.h | 2 +- utils/vgui/include/VGUI_EtchedBorder.h | 2 +- utils/vgui/include/VGUI_FileInputStream.h | 2 +- utils/vgui/include/VGUI_FlowLayout.h | 2 +- utils/vgui/include/VGUI_FocusChangeSignal.h | 2 +- utils/vgui/include/VGUI_FocusNavGroup.h | 2 +- utils/vgui/include/VGUI_Font.h | 2 +- utils/vgui/include/VGUI_Frame.h | 2 +- utils/vgui/include/VGUI_FrameSignal.h | 2 +- utils/vgui/include/VGUI_GridLayout.h | 2 +- utils/vgui/include/VGUI_HeaderPanel.h | 2 +- utils/vgui/include/VGUI_Image.h | 2 +- utils/vgui/include/VGUI_ImagePanel.h | 2 +- utils/vgui/include/VGUI_InputSignal.h | 2 +- utils/vgui/include/VGUI_InputStream.h | 2 +- utils/vgui/include/VGUI_IntChangeSignal.h | 2 +- utils/vgui/include/VGUI_IntLabel.h | 2 +- utils/vgui/include/VGUI_KeyCode.h | 2 +- utils/vgui/include/VGUI_Label.h | 2 +- utils/vgui/include/VGUI_Layout.h | 2 +- utils/vgui/include/VGUI_LayoutInfo.h | 2 +- utils/vgui/include/VGUI_LineBorder.h | 2 +- utils/vgui/include/VGUI_ListPanel.h | 2 +- utils/vgui/include/VGUI_LoweredBorder.h | 2 +- utils/vgui/include/VGUI_Menu.h | 2 +- utils/vgui/include/VGUI_MenuItem.h | 2 +- utils/vgui/include/VGUI_MenuSeparator.h | 2 +- utils/vgui/include/VGUI_MessageBox.h | 2 +- utils/vgui/include/VGUI_MiniApp.h | 2 +- utils/vgui/include/VGUI_MouseCode.h | 2 +- utils/vgui/include/VGUI_Panel.h | 2 +- utils/vgui/include/VGUI_PopupMenu.h | 2 +- utils/vgui/include/VGUI_ProgressBar.h | 2 +- utils/vgui/include/VGUI_RadioButton.h | 2 +- utils/vgui/include/VGUI_RaisedBorder.h | 2 +- utils/vgui/include/VGUI_RepaintSignal.h | 2 +- utils/vgui/include/VGUI_Scheme.h | 2 +- utils/vgui/include/VGUI_ScrollBar.h | 2 +- utils/vgui/include/VGUI_ScrollPanel.h | 2 +- utils/vgui/include/VGUI_Slider.h | 2 +- utils/vgui/include/VGUI_StackLayout.h | 2 +- utils/vgui/include/VGUI_String.h | 2 +- utils/vgui/include/VGUI_Surface.h | 2 +- utils/vgui/include/VGUI_SurfaceBase.h | 2 +- utils/vgui/include/VGUI_SurfaceGL.h | 2 +- utils/vgui/include/VGUI_TabPanel.h | 2 +- utils/vgui/include/VGUI_TablePanel.h | 2 +- utils/vgui/include/VGUI_TaskBar.h | 2 +- utils/vgui/include/VGUI_TextEntry.h | 2 +- utils/vgui/include/VGUI_TextGrid.h | 2 +- utils/vgui/include/VGUI_TextImage.h | 2 +- utils/vgui/include/VGUI_TextPanel.h | 2 +- utils/vgui/include/VGUI_TickSignal.h | 2 +- utils/vgui/include/VGUI_ToggleButton.h | 2 +- utils/vgui/include/VGUI_TreeFolder.h | 2 +- utils/vgui/include/VGUI_WizardPanel.h | 2 +- utils/vgui/lib/win32_vc6/vgui.lib | Bin 420978 -> 416680 bytes utils/visx2/flow.c | 2 +- utils/visx2/soundpvs.c | 2 +- utils/visx2/vis.c | 2 +- utils/visx2/vis.h | 2 +- 494 files changed, 49197 insertions(+), 2573 deletions(-) create mode 100644 cl_dll/vgui_SpectatorPanel.cpp create mode 100644 cl_dll/vgui_SpectatorPanel.h rename {cl_dll => common}/r_studioint.h (80%) delete mode 100644 dedicated/md5.cpp delete mode 100644 dedicated/md5.h create mode 100644 dlls/AI_BaseNPC_Schedule.cpp create mode 100644 dlls/Wxdebug.cpp create mode 100644 dlls/aflock.cpp create mode 100644 dlls/agrunt.cpp create mode 100644 dlls/apache.cpp create mode 100644 dlls/barnacle.cpp create mode 100644 dlls/barney.cpp create mode 100644 dlls/bigmomma.cpp create mode 100644 dlls/bloater.cpp create mode 100644 dlls/bullsquid.cpp create mode 100644 dlls/controller.cpp create mode 100644 dlls/defaultai.cpp create mode 100644 dlls/defaultai.h create mode 100644 dlls/flyingmonster.cpp create mode 100644 dlls/flyingmonster.h create mode 100644 dlls/gargantua.cpp create mode 100644 dlls/genericmonster.cpp create mode 100644 dlls/glock.cpp create mode 100644 dlls/gman.cpp create mode 100644 dlls/h_cine.cpp create mode 100644 dlls/hassassin.cpp create mode 100644 dlls/headcrab.cpp create mode 100644 dlls/hgrunt.cpp rename dlls/{mp.def => hl.def} (78%) rename dlls/{mp.dsp => hl.dsp} (66%) create mode 100644 dlls/hlgl.def create mode 100644 dlls/houndeye.cpp create mode 100644 dlls/ichthyosaur.cpp create mode 100644 dlls/islave.cpp create mode 100644 dlls/leech.cpp create mode 100644 dlls/monstermaker.cpp create mode 100644 dlls/monsters.cpp create mode 100644 dlls/monsterstate.cpp create mode 100644 dlls/nihilanth.cpp create mode 100644 dlls/nodes.cpp create mode 100644 dlls/osprey.cpp create mode 100644 dlls/playermonster.cpp create mode 100644 dlls/rat.cpp create mode 100644 dlls/roach.cpp create mode 100644 dlls/schedule.cpp create mode 100644 dlls/scientist.cpp create mode 100644 dlls/scripted.cpp create mode 100644 dlls/scripted.h create mode 100644 dlls/squad.h create mode 100644 dlls/squadmonster.cpp create mode 100644 dlls/squadmonster.h create mode 100644 dlls/stats.cpp create mode 100644 dlls/talkmonster.cpp create mode 100644 dlls/tempmonster.cpp create mode 100644 dlls/tentacle.cpp create mode 100644 dlls/turret.cpp create mode 100644 dlls/wxdebug.h create mode 100644 dlls/zombie.cpp diff --git a/Network/delta.lst b/Network/delta.lst index 1334a8a..796d509 100644 --- a/Network/delta.lst +++ b/Network/delta.lst @@ -61,15 +61,14 @@ clientdata_t none DEFINE_DELTA( vuser4[2], DT_SIGNED | DT_FLOAT, 10, 1.0 ), DEFINE_DELTA( fuser1, DT_SIGNED | DT_FLOAT, 22, 128.0 ), - DEFINE_DELTA( fuser2, DT_SIGNED | DT_FLOAT, 2, 128.0 ), - DEFINE_DELTA( fuser3, DT_SIGNED | DT_FLOAT, 2, 128.0 ), + DEFINE_DELTA( fuser2, DT_SIGNED | DT_FLOAT, 10, 128.0 ), + DEFINE_DELTA( fuser3, DT_SIGNED | DT_FLOAT, 10, 128.0 ), DEFINE_DELTA( fuser4, DT_SIGNED | DT_FLOAT, 2, 128.0 ) } entity_state_t gamedll Entity_Encode { - // DEFINE_DELTA( animtime, DT_TIMEWINDOW_8, 8, 1.0 ), - DEFINE_DELTA( animtime, DT_TIMEWINDOW_BIG, 8, 100.0 ), + DEFINE_DELTA( animtime, DT_TIMEWINDOW_8, 8, 1.0 ), DEFINE_DELTA( frame, DT_FLOAT, 10, 4.0 ), DEFINE_DELTA( origin[0], DT_SIGNED | DT_FLOAT, 16, 8.0 ), DEFINE_DELTA( angles[0], DT_ANGLE, 16, 1.0 ), @@ -130,8 +129,7 @@ entity_state_t gamedll Entity_Encode entity_state_player_t gamedll Player_Encode { - // DEFINE_DELTA( animtime, DT_TIMEWINDOW_8, 8, 1.0 ), - DEFINE_DELTA( animtime, DT_TIMEWINDOW_BIG, 8, 100.0 ), + DEFINE_DELTA( animtime, DT_TIMEWINDOW_8, 8, 1.0 ), DEFINE_DELTA( frame, DT_FLOAT, 8, 1.0 ), DEFINE_DELTA( origin[0], DT_SIGNED | DT_FLOAT, 18, 32.0 ), DEFINE_DELTA( angles[0], DT_ANGLE, 16, 1.0 ), diff --git a/cl_dll/GameStudioModelRenderer.cpp b/cl_dll/GameStudioModelRenderer.cpp index 488970c..56e6823 100644 --- a/cl_dll/GameStudioModelRenderer.cpp +++ b/cl_dll/GameStudioModelRenderer.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/GameStudioModelRenderer.h b/cl_dll/GameStudioModelRenderer.h index 077efb3..a9d2eff 100644 --- a/cl_dll/GameStudioModelRenderer.h +++ b/cl_dll/GameStudioModelRenderer.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/GameStudioModelRenderer_Sample.cpp b/cl_dll/GameStudioModelRenderer_Sample.cpp index edf0170..5ea50b5 100644 --- a/cl_dll/GameStudioModelRenderer_Sample.cpp +++ b/cl_dll/GameStudioModelRenderer_Sample.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/GameStudioModelRenderer_Sample.h b/cl_dll/GameStudioModelRenderer_Sample.h index 05bb988..bf5cc73 100644 --- a/cl_dll/GameStudioModelRenderer_Sample.h +++ b/cl_dll/GameStudioModelRenderer_Sample.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/StudioModelRenderer.cpp b/cl_dll/StudioModelRenderer.cpp index 30d2dd9..e051fa9 100644 --- a/cl_dll/StudioModelRenderer.cpp +++ b/cl_dll/StudioModelRenderer.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -885,6 +885,11 @@ void CStudioModelRenderer::StudioSetupBones ( void ) // calc gait animation if (m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0) { + if (m_pPlayerInfo->gaitsequence >= m_pStudioHeader->numseq) + { + m_pPlayerInfo->gaitsequence = 0; + } + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pPlayerInfo->gaitsequence; panim = StudioGetAnim( m_pRenderModel, pseqdesc ); @@ -913,7 +918,10 @@ void CStudioModelRenderer::StudioSetupBones ( void ) if ( IEngineStudio.IsHardware() ) { ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); - ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + + // MatrixCopy should be faster... + //ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + MatrixCopy( (*m_pbonetransform)[i], (*m_plighttransform)[i] ); } else { @@ -1021,7 +1029,10 @@ void CStudioModelRenderer::StudioMergeBones ( model_t *m_pSubModel ) if ( IEngineStudio.IsHardware() ) { ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_pbonetransform)[i]); - ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + + // MatrixCopy should be faster... + //ConcatTransforms ((*m_protationmatrix), bonematrix, (*m_plighttransform)[i]); + MatrixCopy( (*m_pbonetransform)[i], (*m_plighttransform)[i] ); } else { @@ -1239,6 +1250,11 @@ void CStudioModelRenderer::StudioProcessGait( entity_state_t *pplayer ) int iBlend; float flYaw; // view direction relative to movement + if (m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq) + { + m_pCurrentEntity->curstate.sequence = 0; + } + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pCurrentEntity->curstate.sequence; StudioPlayerBlend( pseqdesc, &iBlend, &m_pCurrentEntity->angles[PITCH] ); @@ -1296,6 +1312,11 @@ void CStudioModelRenderer::StudioProcessGait( entity_state_t *pplayer ) m_pCurrentEntity->angles[YAW] += 360; m_pCurrentEntity->latched.prevangles[YAW] = m_pCurrentEntity->angles[YAW]; + if (pplayer->gaitsequence >= m_pStudioHeader->numseq) + { + pplayer->gaitsequence = 0; + } + pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence; // calc gait frame @@ -1439,11 +1460,11 @@ int CStudioModelRenderer::StudioDrawPlayer( int flags, entity_state_t *pplayer ) // get remap colors m_nTopColor = m_pPlayerInfo->topcolor; + m_nBottomColor = m_pPlayerInfo->bottomcolor; if (m_nTopColor < 0) m_nTopColor = 0; if (m_nTopColor > 360) m_nTopColor = 360; - m_nBottomColor = m_pPlayerInfo->bottomcolor; if (m_nBottomColor < 0) m_nBottomColor = 0; if (m_nBottomColor > 360) diff --git a/cl_dll/StudioModelRenderer.h b/cl_dll/StudioModelRenderer.h index c94f802..51eaaa6 100644 --- a/cl_dll/StudioModelRenderer.h +++ b/cl_dll/StudioModelRenderer.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/ammo.cpp b/cl_dll/ammo.cpp index 58cd12b..5084e08 100644 --- a/cl_dll/ammo.cpp +++ b/cl_dll/ammo.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -21,6 +21,7 @@ #include "hud.h" #include "cl_util.h" #include "parsemsg.h" +#include "pm_shared.h" #include #include @@ -538,6 +539,9 @@ int CHudAmmo::MsgFunc_HideWeapon( const char *pszName, int iSize, void *pbuf ) gHUD.m_iHideHUDDisplay = READ_BYTE(); + if (gEngfuncs.IsSpectateOnly()) + return 1; + if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_WEAPONS | HIDEHUD_ALL ) ) { static wrect_t nullrc; @@ -581,14 +585,17 @@ int CHudAmmo::MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf ) return 0; } - // Is player dead??? - if ((iId == -1) && (iClip == -1)) + if ( g_iUser1 != OBS_IN_EYE ) { - gHUD.m_fPlayerDead = TRUE; - gpActiveSel = NULL; - return 1; + // Is player dead??? + if ((iId == -1) && (iClip == -1)) + { + gHUD.m_fPlayerDead = TRUE; + gpActiveSel = NULL; + return 1; + } + gHUD.m_fPlayerDead = FALSE; } - gHUD.m_fPlayerDead = FALSE; WEAPON *pWeapon = gWR.GetWeapon( iId ); @@ -606,22 +613,20 @@ int CHudAmmo::MsgFunc_CurWeapon(const char *pszName, int iSize, void *pbuf ) 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); - } + if ( gHUD.m_iFOV >= 90 ) + { // normal crosshairs + if (fOnTarget && m_pWeapon->hAutoaim) + SetCrosshair(m_pWeapon->hAutoaim, m_pWeapon->rcAutoaim, 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); - } + 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; //!!! diff --git a/cl_dll/ammo.h b/cl_dll/ammo.h index ad2b556..ad730cc 100644 --- a/cl_dll/ammo.h +++ b/cl_dll/ammo.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/ammo_secondary.cpp b/cl_dll/ammo_secondary.cpp index df8bb7f..2ca07d2 100644 --- a/cl_dll/ammo_secondary.cpp +++ b/cl_dll/ammo_secondary.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/ammohistory.cpp b/cl_dll/ammohistory.cpp index 11db04a..bfe7005 100644 --- a/cl_dll/ammohistory.cpp +++ b/cl_dll/ammohistory.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/ammohistory.h b/cl_dll/ammohistory.h index 5b52c3e..74bf43f 100644 --- a/cl_dll/ammohistory.h +++ b/cl_dll/ammohistory.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/battery.cpp b/cl_dll/battery.cpp index 75613a2..0ec4554 100644 --- a/cl_dll/battery.cpp +++ b/cl_dll/battery.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/camera.h b/cl_dll/camera.h index da40cc1..af1591d 100644 --- a/cl_dll/camera.h +++ b/cl_dll/camera.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/cdll_int.cpp b/cl_dll/cdll_int.cpp index ffd1dc1..ee2a20f 100644 --- a/cl_dll/cdll_int.cpp +++ b/cl_dll/cdll_int.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -55,18 +55,18 @@ extern "C" { int DLLEXPORT Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ); int DLLEXPORT HUD_VidInit( void ); -int DLLEXPORT HUD_Init( void ); +void 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 ); +void DLLEXPORT HUD_Reset ( void ); void DLLEXPORT HUD_PlayerMove( struct playermove_s *ppmove, int server ); void DLLEXPORT HUD_PlayerMoveInit( struct playermove_s *ppmove ); char DLLEXPORT HUD_PlayerMoveTexture( char *name ); -int DLLEXPORT HUD_ConnectionlessPacket( struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); +int DLLEXPORT HUD_ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ); int DLLEXPORT HUD_GetHullBounds( int hullnumber, float *mins, float *maxs ); void DLLEXPORT HUD_Frame( double time ); void DLLEXPORT HUD_VoiceStatus(int entindex, qboolean bTalking); -void DLLEXPORT HUD_DirectorEvent(unsigned char command, unsigned int firstObject, unsigned int secondObject, unsigned int flags); +void DLLEXPORT HUD_DirectorMessage( int iSize, void *pbuf ); } /* @@ -110,7 +110,7 @@ HUD_ConnectionlessPacket size of the response_buffer, so you must zero it out if you choose not to respond. ================================ */ -int DLLEXPORT HUD_ConnectionlessPacket( struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) +int DLLEXPORT HUD_ConnectionlessPacket( const struct netadr_s *net_from, const char *args, char *response_buffer, int *response_buffer_size ) { // Parse stuff from args int max_buffer_size = *response_buffer_size; @@ -183,12 +183,11 @@ the hud variables. ========================== */ -int DLLEXPORT HUD_Init( void ) +void DLLEXPORT HUD_Init( void ) { InitInput(); gHUD.Init(); Scheme_Init(); - return 1; } @@ -237,10 +236,9 @@ Called at start and end of demos to restore to "non"HUD state. ========================== */ -int DLLEXPORT HUD_Reset( void ) +void DLLEXPORT HUD_Reset( void ) { gHUD.VidInit(); - return 1; } /* @@ -280,9 +278,9 @@ Called when a director event message was received ========================== */ -void DLLEXPORT HUD_DirectorEvent(unsigned char command, unsigned int firstObject, unsigned int secondObject, unsigned int flags) +void DLLEXPORT HUD_DirectorMessage( int iSize, void *pbuf ) { - gHUD.m_Spectator.DirectorEvent(command, firstObject, secondObject, flags); + gHUD.m_Spectator.DirectorMessage( iSize, pbuf ); } diff --git a/cl_dll/cl_dll.dsp b/cl_dll/cl_dll.dsp index 1c76e99..280392a 100644 --- a/cl_dll/cl_dll.dsp +++ b/cl_dll/cl_dll.dsp @@ -23,8 +23,8 @@ CFG=cl_dll - Win32 Release # Begin Project # PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName ""$/GoldSrc/cl_dll", HGEBAAAA" -# PROP Scc_LocalPath "." +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" CPP=cl.exe MTL=midl.exe RSC=rc.exe @@ -189,10 +189,6 @@ SOURCE=..\game_shared\voice_banmgr.cpp SOURCE=..\game_shared\voice_status.cpp # End Source File -# Begin Source File - -SOURCE=..\game_shared\voice_vgui_tweakdlg.cpp -# End Source File # End Group # Begin Source File @@ -417,6 +413,10 @@ SOURCE=.\vgui_ServerBrowser.cpp # End Source File # Begin Source File +SOURCE=.\vgui_SpectatorPanel.cpp +# End Source File +# Begin Source File + SOURCE=.\vgui_TeamFortressViewport.cpp # End Source File # Begin Source File @@ -573,6 +573,10 @@ SOURCE=.\vgui_ServerBrowser.h # End Source File # Begin Source File +SOURCE=.\vgui_SpectatorPanel.h +# End Source File +# Begin Source File + SOURCE=.\vgui_TeamFortressViewport.h # End Source File # Begin Source File diff --git a/cl_dll/cl_dll.h b/cl_dll/cl_dll.h index d0b6731..2bb3d1a 100644 --- a/cl_dll/cl_dll.h +++ b/cl_dll/cl_dll.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/cl_util.h b/cl_dll/cl_util.h index c35b31c..f72d589 100644 --- a/cl_dll/cl_util.h +++ b/cl_dll/cl_util.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -68,8 +68,8 @@ inline struct cvar_s *CVAR_CREATE( const char *cv, const char *val, const int fl #define ScreenWidth (gHUD.m_scrinfo.iWidth) // Use this to set any co-ords in 640x480 space -#define XRES(x) ((x) * ((float)ScreenWidth / 640)) -#define YRES(y) ((y) * ((float)ScreenHeight / 480)) +#define XRES(x) ((int)(float(x) * ((float)ScreenWidth / 640.0f) + 0.5f)) +#define YRES(y) ((int)(float(y) * ((float)ScreenHeight / 480.0f) + 0.5f)) // use this to project world coordinates to screen coordinates #define XPROJECT(x) ( (1.0f+(x))*ScreenWidth*0.5f ) diff --git a/cl_dll/com_weapons.cpp b/cl_dll/com_weapons.cpp index 6e64c43..66b1c82 100644 --- a/cl_dll/com_weapons.cpp +++ b/cl_dll/com_weapons.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/com_weapons.h b/cl_dll/com_weapons.h index 3d2efe5..e2fd274 100644 --- a/cl_dll/com_weapons.h +++ b/cl_dll/com_weapons.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/death.cpp b/cl_dll/death.cpp index a3012cc..208f587 100644 --- a/cl_dll/death.cpp +++ b/cl_dll/death.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -41,7 +41,7 @@ struct DeathNoticeItem { #define MAX_DEATHNOTICES 4 static int DEATHNOTICE_DISPLAY_TIME = 6; -#define DEATHNOTICE_TOP 20 +#define DEATHNOTICE_TOP 32 DeathNoticeItem rgDeathNoticeList[ MAX_DEATHNOTICES + 1 ]; @@ -49,6 +49,7 @@ float g_ColorBlue[3] = { 0.6, 0.8, 1.0 }; float g_ColorRed[3] = { 1.0, 0.25, 0.25 }; float g_ColorGreen[3] = { 0.6, 1.0, 0.6 }; float g_ColorYellow[3] = { 1.0, 0.7, 0.0 }; +float g_ColorGrey[3] = { 0.8, 0.8, 0.8 }; float *GetClientColor( int clientIndex ) { @@ -58,6 +59,9 @@ float *GetClientColor( int clientIndex ) case 2: return g_ColorRed; case 3: return g_ColorYellow; case 4: return g_ColorGreen; + case 0: return g_ColorYellow; + + default : return g_ColorGrey; } return NULL; @@ -111,7 +115,7 @@ int CHudDeathNotice :: Draw( float flTime ) if ( gViewPort && gViewPort->AllowedToPrintText() ) { // Draw the death notice - y = DEATHNOTICE_TOP + (20 * i); //!!! + y = YRES(DEATHNOTICE_TOP) + 2 + (20 * i); //!!! int id = (rgDeathNoticeList[i].iId == -1) ? m_HUD_d_skull : rgDeathNoticeList[i].iId; x = ScreenWidth - ConsoleStringLen(rgDeathNoticeList[i].szVictim) - (gHUD.GetSpriteRect(id).right - gHUD.GetSpriteRect(id).left); diff --git a/cl_dll/demo.cpp b/cl_dll/demo.cpp index 42cf432..6968b38 100644 --- a/cl_dll/demo.cpp +++ b/cl_dll/demo.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/demo.h b/cl_dll/demo.h index 58cc829..c5a5057 100644 --- a/cl_dll/demo.h +++ b/cl_dll/demo.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/entity.cpp b/cl_dll/entity.cpp index 2b0eef0..6a209c8 100644 --- a/cl_dll/entity.cpp +++ b/cl_dll/entity.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -18,7 +18,7 @@ #include "event_api.h" #include "pm_defs.h" #include "pmtrace.h" - +#include "pm_shared.h" #define DLLEXPORT __declspec( dllexport ) @@ -59,13 +59,16 @@ int DLLEXPORT HUD_AddEntity( int type, struct cl_entity_s *ent, const char *mode break; } // each frame every entity passes this function, so the overview hooks it to filter the overview entities - if (gEngfuncs.IsSpectateOnly()) + // in spectator mode: + // each frame every entity passes this function, so the overview hooks + // it to filter the overview entities + + if ( g_iUser1 ) { gHUD.m_Spectator.AddOverviewEntity( type, ent, modelname ); - if ( ( gHUD.m_Spectator.m_iMainMode == MAIN_IN_EYE || - gHUD.m_Spectator.m_iInsetMode == INSET_IN_EYE ) && - ent->index == gHUD.m_Spectator.m_iObserverTarget ) + if ( ( g_iUser1 == OBS_IN_EYE || gHUD.m_Spectator.m_pip->value == INSET_IN_EYE ) && + ent->index == g_iUser2 ) return 0; // don't draw the player we are following in eye } @@ -158,7 +161,6 @@ void DLLEXPORT HUD_ProcessPlayerState( struct entity_state_s *dst, const struct g_iUser1 = src->iuser1; g_iUser2 = src->iuser2; g_iUser3 = src->iuser3; - } } @@ -534,7 +536,6 @@ void DLLEXPORT HUD_CreateEntities( void ) Beams(); #endif - // Add in any game specific objects Game_AddObjects(); diff --git a/cl_dll/ev_common.cpp b/cl_dll/ev_common.cpp index 723d296..224633d 100644 --- a/cl_dll/ev_common.cpp +++ b/cl_dll/ev_common.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -23,7 +23,9 @@ #include "eventscripts.h" #include "event_api.h" +#include "pm_shared.h" +#define IS_FIRSTPERSON_SPEC ( g_iUser1 == OBS_IN_EYE || (g_iUser1 && (gHUD.m_Spectator.m_pip->value == INSET_IN_EYE)) ) /* ================= GetEntity @@ -84,7 +86,11 @@ Is the entity == the local player */ qboolean EV_IsLocal( int idx ) { - return gEngfuncs.pEventAPI->EV_IsLocal( idx - 1 ) ? true : false; + // check if we are in some way in first person spec mode + if ( IS_FIRSTPERSON_SPEC ) + return (g_iUser2 == idx); + else + return gEngfuncs.pEventAPI->EV_IsLocal( idx - 1 ) ? true : false; } /* @@ -106,7 +112,8 @@ void EV_GetGunPosition( event_args_t *args, float *pos, float *origin ) if ( EV_IsPlayer( idx ) ) { - if ( EV_IsLocal( idx ) ) + // in spec mode use entity viewheigh, not own + if ( EV_IsLocal( idx ) && !IS_FIRSTPERSON_SPEC ) { // Grab predicted result for local player gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); diff --git a/cl_dll/ev_hldm.cpp b/cl_dll/ev_hldm.cpp index b685c62..ad9ba30 100644 --- a/cl_dll/ev_hldm.cpp +++ b/cl_dll/ev_hldm.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -32,6 +32,11 @@ #include +#include "r_studioint.h" +#include "com_model.h" + +extern engine_studio_api_t IEngineStudio; + static int tracerCount[ 32 ]; extern "C" char PM_FindTextureType( char *name ); @@ -831,6 +836,8 @@ void EV_StopPreviousGauss( int idx ) gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_WEAPON, "ambience/pulsemachine.wav" ); } +extern float g_flApplyVel; + void EV_FireGauss( event_args_t *args ) { int idx; @@ -880,6 +887,10 @@ void EV_FireGauss( event_args_t *args ) { V_PunchAxis( 0, -2.0 ); gEngfuncs.pEventAPI->EV_WeaponAnimation( GAUSS_FIRE2, 2 ); + + if ( m_fPrimaryFire == false ) + g_flApplyVel = flDamage; + } gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/gauss2.wav", 0.5 + flDamage * (1.0 / 400.0), ATTN_NORM, 0, 85 + gEngfuncs.pfnRandomLong( 0, 0x1f ) ); @@ -1434,13 +1445,24 @@ void EV_EgonFire( event_args_t *args ) gEngfuncs.pEventAPI->EV_PopPMStates(); int iBeamModelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( EGON_BEAM_SPRITE ); + + float r = 50.0f; + float g = 50.0f; + float b = 125.0f; + + if ( IEngineStudio.IsHardware() ) + { + r /= 100.0f; + g /= 100.0f; + } + - pBeam = gEngfuncs.pEfxAPI->R_BeamEntPoint ( idx | 0x1000, tr.endpos, iBeamModelIndex, 99999, 3.5, 0.2, 0.075, 5, 0, 0.15, 5, 5, 25.5 ); + pBeam = gEngfuncs.pEfxAPI->R_BeamEntPoint ( idx | 0x1000, tr.endpos, iBeamModelIndex, 99999, 3.5, 0.2, 0.7, 55, 0, 0, r, g, b ); if ( pBeam ) - pBeam->flags |= FBEAM_SINENOISE; + pBeam->flags |= ( FBEAM_SINENOISE ); - pBeam2 = gEngfuncs.pEfxAPI->R_BeamEntPoint ( idx | 0x1000, tr.endpos, iBeamModelIndex, 99999, 5.0, 0.08, 0.1, 2.5, 0, 0.15, 5, 5, 25.5 ); + pBeam2 = gEngfuncs.pEfxAPI->R_BeamEntPoint ( idx | 0x1000, tr.endpos, iBeamModelIndex, 99999, 5.0, 0.08, 0.7, 25, 0, 0, r, g, b ); } } } diff --git a/cl_dll/ev_hldm.h b/cl_dll/ev_hldm.h index c2c4819..567e687 100644 --- a/cl_dll/ev_hldm.h +++ b/cl_dll/ev_hldm.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/events.cpp b/cl_dll/events.cpp index 839f926..65c842b 100644 --- a/cl_dll/events.cpp +++ b/cl_dll/events.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/eventscripts.h b/cl_dll/eventscripts.h index 9653628..e8b145b 100644 --- a/cl_dll/eventscripts.h +++ b/cl_dll/eventscripts.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/flashlight.cpp b/cl_dll/flashlight.cpp index 2f08d5d..dab2421 100644 --- a/cl_dll/flashlight.cpp +++ b/cl_dll/flashlight.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/geiger.cpp b/cl_dll/geiger.cpp index 409abc7..6f5f984 100644 --- a/cl_dll/geiger.cpp +++ b/cl_dll/geiger.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/health.cpp b/cl_dll/health.cpp index 890044a..f5835e4 100644 --- a/cl_dll/health.cpp +++ b/cl_dll/health.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/health.h b/cl_dll/health.h index fc4d521..41717e7 100644 --- a/cl_dll/health.h +++ b/cl_dll/health.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/hl/hl_baseentity.cpp b/cl_dll/hl/hl_baseentity.cpp index 3521de4..547d553 100644 --- a/cl_dll/hl/hl_baseentity.cpp +++ b/cl_dll/hl/hl_baseentity.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -110,22 +110,78 @@ void CSprite::Expand( float scaleSpeed, float fadeSpeed ) { } CBaseEntity* CBaseMonster :: CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ) { return NULL; } +void CBaseMonster :: Eat ( float flFullDuration ) { } +BOOL CBaseMonster :: FShouldEat ( void ) { return TRUE; } +void CBaseMonster :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) { } +void CBaseMonster :: BarnacleVictimReleased ( void ) { } +void CBaseMonster :: Listen ( void ) { } +float CBaseMonster :: FLSoundVolume ( CSound *pSound ) { return 0.0; } +BOOL CBaseMonster :: FValidateHintType ( short sHint ) { return FALSE; } void CBaseMonster :: Look ( int iDistance ) { } +int CBaseMonster :: ISoundMask ( void ) { return 0; } +CSound* CBaseMonster :: PBestSound ( void ) { return NULL; } +CSound* CBaseMonster :: PBestScent ( void ) { return NULL; } float CBaseAnimating :: StudioFrameAdvance ( float flInterval ) { return 0.0; } +void CBaseMonster :: MonsterThink ( void ) { } +void CBaseMonster :: MonsterUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { } +int CBaseMonster :: IgnoreConditions ( void ) { return 0; } +void CBaseMonster :: RouteClear ( void ) { } +void CBaseMonster :: RouteNew ( void ) { } +BOOL CBaseMonster :: FRouteClear ( void ) { return FALSE; } +BOOL CBaseMonster :: FRefreshRoute ( void ) { return 0; } +BOOL CBaseMonster::MoveToEnemy( Activity movementAct, float waitTime ) { return FALSE; } +BOOL CBaseMonster::MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ) { return FALSE; } +BOOL CBaseMonster::MoveToTarget( Activity movementAct, float waitTime ) { return FALSE; } +BOOL CBaseMonster::MoveToNode( Activity movementAct, float waitTime, const Vector &goal ) { return FALSE; } +int ShouldSimplify( int routeType ) { return TRUE; } +void CBaseMonster :: RouteSimplify( CBaseEntity *pTargetEnt ) { } +BOOL CBaseMonster :: FBecomeProne ( void ) { return TRUE; } +BOOL CBaseMonster :: CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckMeleeAttack1 ( float flDot, float flDist ) { return FALSE; } +BOOL CBaseMonster :: CheckMeleeAttack2 ( float flDot, float flDist ) { return FALSE; } +void CBaseMonster :: CheckAttacks ( CBaseEntity *pTarget, float flDist ) { } +BOOL CBaseMonster :: FCanCheckAttacks ( void ) { return FALSE; } +int CBaseMonster :: CheckEnemy ( CBaseEntity *pEnemy ) { return 0; } +void CBaseMonster :: PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ) { } +BOOL CBaseMonster :: PopEnemy( ) { return FALSE; } +void CBaseMonster :: SetActivity ( Activity NewActivity ) { } +void CBaseMonster :: SetSequenceByName ( char *szSequence ) { } +int CBaseMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) { return 0; } +float CBaseMonster :: OpenDoorAndWait( entvars_t *pevDoor ) { return 0.0; } +void CBaseMonster :: AdvanceRoute ( float distance ) { } +int CBaseMonster :: RouteClassify( int iMoveFlag ) { return 0; } +BOOL CBaseMonster :: BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ) { return FALSE; } +void CBaseMonster :: InsertWaypoint ( Vector vecLocation, int afMoveFlags ) { } +BOOL CBaseMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ) { return FALSE; } +void CBaseMonster :: Move ( float flInterval ) { } +BOOL CBaseMonster:: ShouldAdvanceRoute( float flWaypointDist ) { return FALSE; } +void CBaseMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) { } +void CBaseMonster :: MonsterInit ( void ) { } +void CBaseMonster :: MonsterInitThink ( void ) { } +void CBaseMonster :: StartMonster ( void ) { } +void CBaseMonster :: MovementComplete( void ) { } +int CBaseMonster::TaskIsRunning( void ) { return 0; } int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) { return 0; } +BOOL CBaseMonster :: FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) { return FALSE; } +BOOL CBaseMonster :: BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) { return FALSE; } CBaseEntity *CBaseMonster :: BestVisibleEnemy ( void ) { return NULL; } BOOL CBaseMonster :: FInViewCone ( CBaseEntity *pEntity ) { return FALSE; } BOOL CBaseMonster :: FInViewCone ( Vector *pOrigin ) { return FALSE; } BOOL CBaseEntity :: FVisible ( CBaseEntity *pEntity ) { return FALSE; } BOOL CBaseEntity :: FVisible ( const Vector &vecOrigin ) { return FALSE; } void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) { } +float CBaseMonster::FlYawDiff ( void ) { return 0.0; } float CBaseMonster::ChangeYaw ( int yawSpeed ) { return 0; } +float CBaseMonster::VecToYaw ( Vector vecDir ) { return 0.0; } int CBaseAnimating :: LookupActivity ( int activity ) { return 0; } int CBaseAnimating :: LookupActivityHeaviest ( int activity ) { return 0; } +void CBaseMonster :: SetEyePosition ( void ) { } int CBaseAnimating :: LookupSequence ( const char *label ) { return 0; } void CBaseAnimating :: ResetSequenceInfo ( ) { } BOOL CBaseAnimating :: GetSequenceFlags( ) { return FALSE; } void CBaseAnimating :: DispatchAnimEvents ( float flInterval ) { } +void CBaseMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) { } float CBaseAnimating :: SetBoneController ( int iController, float flValue ) { return 0.0; } void CBaseAnimating :: InitBoneControllers ( void ) { } float CBaseAnimating :: SetBlending ( int iBlender, float flValue ) { return 0; } @@ -135,16 +191,30 @@ int CBaseAnimating :: FindTransition( int iEndingSequence, int iGoalSequence, in void CBaseAnimating :: GetAutomovement( Vector &origin, Vector &angles, float flInterval ) { } void CBaseAnimating :: SetBodygroup( int iGroup, int iValue ) { } int CBaseAnimating :: GetBodygroup( int iGroup ) { return 0; } +Vector CBaseMonster :: GetGunPosition( void ) { return g_vecZero; } void CBaseEntity::TraceAttack(entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } void CBaseEntity::FireBullets(ULONG cShots, Vector vecSrc, Vector vecDirShooting, Vector vecSpread, float flDistance, int iBulletType, int iTracerFreq, int iDamage, entvars_t *pevAttacker ) { } void CBaseEntity :: TraceBleed( float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) { } void CBaseMonster :: MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ) { } +BOOL CBaseMonster :: FGetNodeRoute ( Vector vecDest ) { return TRUE; } +int CBaseMonster :: FindHintNode ( void ) { return NO_NODE; } void CBaseMonster::ReportAIState( void ) { } void CBaseMonster :: KeyValue( KeyValueData *pkvd ) { } BOOL CBaseMonster :: FCheckAITrigger ( void ) { return FALSE; } +int CBaseMonster :: CanPlaySequence( BOOL fDisregardMonsterState, int interruptLevel ) { return FALSE; } +BOOL CBaseMonster :: FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ) { return FALSE; } +Vector CBaseMonster :: ShootAtEnemy( const Vector &shootOrigin ) { return g_vecZero; } +BOOL CBaseMonster :: FacingIdeal( void ) { return FALSE; } +BOOL CBaseMonster :: FCanActiveIdle ( void ) { return FALSE; } +void CBaseMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) { } +void CBaseMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) { } +void CBaseMonster::SentenceStop( void ) { } void CBaseMonster::CorpseFallThink( void ) { } void CBaseMonster :: MonsterInitDead( void ) { } +BOOL CBaseMonster :: BBoxFlat ( void ) { return TRUE; } +BOOL CBaseMonster :: GetEnemy ( void ) { return FALSE; } void CBaseMonster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } +CBaseEntity* CBaseMonster :: DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng ) { return NULL; } BOOL CBaseMonster :: ShouldFadeOnDeath( void ) { return FALSE; } void CBaseMonster :: RadiusDamage(entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) { } void CBaseMonster :: RadiusDamage( Vector vecSrc, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) { } @@ -153,16 +223,26 @@ void CBaseMonster :: GibMonster( void ) { } BOOL CBaseMonster :: HasHumanGibs( void ) { return FALSE; } BOOL CBaseMonster :: HasAlienGibs( void ) { return FALSE; } Activity CBaseMonster :: GetDeathActivity ( void ) { return ACT_DIE_HEADSHOT; } +MONSTERSTATE CBaseMonster :: GetIdealState ( void ) { return MONSTERSTATE_ALERT; } +Schedule_t* CBaseMonster :: GetScheduleOfType ( int Type ) { return NULL; } +Schedule_t *CBaseMonster :: GetSchedule ( void ) { return NULL; } +void CBaseMonster :: RunTask ( Task_t *pTask ) { } +void CBaseMonster :: StartTask ( Task_t *pTask ) { } +Schedule_t *CBaseMonster::ScheduleFromName( const char *pName ) { return NULL;} void CBaseMonster::BecomeDead( void ) {} +void CBaseMonster :: RunAI ( void ) {} void CBaseMonster :: Killed( entvars_t *pevAttacker, int iGib ) {} int CBaseMonster :: TakeHealth (float flHealth, int bitsDamageType) { return 0; } int CBaseMonster :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { return 0; } +int CBaseMonster::Restore( class CRestore & ) { return 1; } +int CBaseMonster::Save( class CSave & ) { return 1; } int TrainSpeed(int iSpeed, int iMax) { return 0; } void CBasePlayer :: DeathSound( void ) { } int CBasePlayer :: TakeHealth( float flHealth, int bitsDamageType ) { return 0; } void CBasePlayer :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) { } int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { return 0; } +void CBasePlayer::PackDeadPlayerItems( void ) { } void CBasePlayer::RemoveAllItems( BOOL removeSuit ) { } void CBasePlayer::SetAnimation( PLAYER_ANIM playerAnim ) { } void CBasePlayer::WaterMove() { } diff --git a/cl_dll/hl/hl_events.cpp b/cl_dll/hl/hl_events.cpp index bf2f63c..5ad4459 100644 --- a/cl_dll/hl/hl_events.cpp +++ b/cl_dll/hl/hl_events.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/hl/hl_objects.cpp b/cl_dll/hl/hl_objects.cpp index 7b388a9..e95dfae 100644 --- a/cl_dll/hl/hl_objects.cpp +++ b/cl_dll/hl/hl_objects.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/hl/hl_weapons.cpp b/cl_dll/hl/hl_weapons.cpp index e5492bc..820e49f 100644 --- a/cl_dll/hl/hl_weapons.cpp +++ b/cl_dll/hl/hl_weapons.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -32,6 +32,7 @@ #include "../demo.h" extern globalvars_t *gpGlobals; +extern int g_iUser1; // Pool of client side entities/entvars_t static entvars_t ev[ 32 ]; @@ -45,6 +46,9 @@ static globalvars_t Globals; static CBasePlayerWeapon *g_pWpns[ 32 ]; +float g_flApplyVel = 0.0; +int g_irunninggausspred = 0; + vec3_t previousorigin; // HLDM Weapon placeholder entities. @@ -213,6 +217,7 @@ BOOL CBasePlayerWeapon :: DefaultDeploy( char *szViewModel, char *szWeaponModel, SendWeaponAnim( iAnim, skiplocal, body ); + g_irunninggausspred = false; m_pPlayer->m_flNextAttack = 0.5; m_flTimeWeaponIdle = 1.0; return TRUE; @@ -256,6 +261,7 @@ Put away weapon void CBasePlayerWeapon::Holster( int skiplocal /* = 0 */ ) { m_fInReload = FALSE; // cancel any reload in progress. + g_irunninggausspred = false; m_pPlayer->pev->viewmodel = 0; } @@ -452,6 +458,8 @@ void CBasePlayer::Killed( entvars_t *pevAttacker, int iGib ) // Holster weapon immediately, to allow it to cleanup if ( m_pActiveItem ) m_pActiveItem->Holster( ); + + g_irunninggausspred = false; } /* @@ -464,6 +472,8 @@ void CBasePlayer::Spawn( void ) { if (m_pActiveItem) m_pActiveItem->Deploy( ); + + g_irunninggausspred = false; } /* @@ -859,7 +869,8 @@ void HUD_WeaponsPostThink( local_state_s *from, local_state_s *to, usercmd_t *cm // Don't go firing anything if we have died. // Or if we don't have a weapon model deployed - if ( ( player.pev->deadflag != ( DEAD_DISCARDBODY + 1 ) ) && !CL_IsDead() && player.pev->viewmodel ) + if ( ( player.pev->deadflag != ( DEAD_DISCARDBODY + 1 ) ) && + !CL_IsDead() && player.pev->viewmodel && !g_iUser1 ) { if ( player.m_flNextAttack <= 0 ) { @@ -1045,7 +1056,6 @@ void HUD_WeaponsPostThink( local_state_s *from, local_state_s *to, usercmd_t *cm g_finalstate = NULL; } - /* ===================== HUD_PostRunCmd @@ -1072,6 +1082,14 @@ void _DLLEXPORT HUD_PostRunCmd( struct local_state_s *from, struct local_state_s to->client.fov = g_lastFOV; } + if ( g_irunninggausspred == 1 ) + { + Vector forward; + gEngfuncs.pfnAngleVectors( v_angles, forward, NULL, NULL ); + to->client.velocity = to->client.velocity - forward * g_flApplyVel * 5; + g_irunninggausspred = false; + } + // All games can use FOV state g_lastFOV = to->client.fov; } diff --git a/cl_dll/hud.cpp b/cl_dll/hud.cpp index 5401dfb..e06b11d 100644 --- a/cl_dll/hud.cpp +++ b/cl_dll/hud.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -103,6 +103,12 @@ int __MsgFunc_InitHUD(const char *pszName, int iSize, void *pbuf) return 1; } +int __MsgFunc_ViewMode(const char *pszName, int iSize, void *pbuf) +{ + gHUD.MsgFunc_ViewMode( pszName, iSize, pbuf ); + return 1; +} + int __MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf) { return gHUD.MsgFunc_SetFOV( pszName, iSize, pbuf ); @@ -123,7 +129,7 @@ void __CmdFunc_OpenCommandMenu(void) { if ( gViewPort ) { - gViewPort->ShowCommandMenu(); + gViewPort->ShowCommandMenu( gViewPort->m_StandardMenu ); } } @@ -266,6 +272,7 @@ void CHud :: Init( void ) HOOK_MESSAGE( ResetHUD ); HOOK_MESSAGE( GameMode ); HOOK_MESSAGE( InitHUD ); + HOOK_MESSAGE( ViewMode ); HOOK_MESSAGE( SetFOV ); HOOK_MESSAGE( Concuss ); @@ -304,7 +311,7 @@ void CHud :: Init( void ) CVAR_CREATE( "zoom_sensitivity_ratio", "1.2", 0 ); default_fov = CVAR_CREATE( "default_fov", "90", 0 ); m_pCvarStealMouse = CVAR_CREATE( "hud_capturemouse", "1", FCVAR_ARCHIVE ); - + m_pCvarDraw = CVAR_CREATE( "hud_draw", "1", FCVAR_ARCHIVE ); cl_lw = gEngfuncs.pfnGetCvarPointer( "cl_lw" ); m_pSpriteList = NULL; @@ -327,6 +334,8 @@ void CHud :: Init( void ) m_Ammo.Init(); m_Health.Init(); + m_SayText.Init(); + m_Spectator.Init(); m_Geiger.Init(); m_Train.Init(); m_Battery.Init(); @@ -338,9 +347,7 @@ void CHud :: Init( void ) m_TextMessage.Init(); m_StatusIcons.Init(); GetClientVoiceMgr()->Init(&g_VoiceStatusHelper, (vgui::Panel**)&gViewPort); - m_Spectator.Init(); - m_SayText.Init(); m_Menu.Init(); ServersInit(); @@ -474,6 +481,7 @@ void CHud :: VidInit( void ) m_Ammo.VidInit(); m_Health.VidInit(); + m_Spectator.VidInit(); m_Geiger.VidInit(); m_Train.VidInit(); m_Battery.VidInit(); @@ -487,7 +495,6 @@ void CHud :: VidInit( void ) m_TextMessage.VidInit(); m_StatusIcons.VidInit(); GetClientVoiceMgr()->VidInit(); - m_Spectator.VidInit(); } int CHud::MsgFunc_Logo(const char *pszName, int iSize, void *pbuf) diff --git a/cl_dll/hud.h b/cl_dll/hud.h index 7194097..7d49e53 100644 --- a/cl_dll/hud.h +++ b/cl_dll/hud.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -373,6 +373,12 @@ public: int MsgFunc_SayText( const char *pszName, int iSize, void *pbuf ); void SayTextPrint( const char *pszBuf, int iBufSize, int clientIndex = -1 ); void EnsureTextFitsInOneLineAndWrapIfHaveTo( int line ); +friend class CHudSpectator; + +private: + + struct cvar_s * m_HUD_saytext; + struct cvar_s * m_HUD_saytext_time; }; // @@ -477,6 +483,7 @@ public: int YPosition( float y, int height ); void MessageAdd( const char *pName, float time ); + void MessageAdd(client_textmessage_t * newMessage ); void MessageDrawScan( client_textmessage_t *pMessage, float time ); void MessageScanStart( void ); void MessageScanNextChar( void ); @@ -564,6 +571,7 @@ public: int m_Teamplay; int m_iRes; cvar_t *m_pCvarStealMouse; + cvar_t *m_pCvarDraw; int m_iFontHeight; int DrawHudNumber(int x, int y, int iFlags, int iNumber, int r, int g, int b ); @@ -596,6 +604,7 @@ public: CHudAmmo m_Ammo; CHudHealth m_Health; + CHudSpectator m_Spectator; CHudGeiger m_Geiger; CHudBattery m_Battery; CHudTrain m_Train; @@ -608,7 +617,6 @@ public: CHudAmmoSecondary m_AmmoSecondary; CHudTextMessage m_TextMessage; CHudStatusIcons m_StatusIcons; - CHudSpectator m_Spectator; void Init( void ); void VidInit( void ); @@ -625,6 +633,7 @@ public: int _cdecl MsgFunc_Logo(const char *pszName, int iSize, void *pbuf); int _cdecl MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf); void _cdecl MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ); + void _cdecl MsgFunc_ViewMode( const char *pszName, int iSize, void *pbuf ); int _cdecl MsgFunc_SetFOV(const char *pszName, int iSize, void *pbuf); int _cdecl MsgFunc_Concuss( const char *pszName, int iSize, void *pbuf ); diff --git a/cl_dll/hud_iface.h b/cl_dll/hud_iface.h index 87b9861..d08763c 100644 --- a/cl_dll/hud_iface.h +++ b/cl_dll/hud_iface.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/hud_msg.cpp b/cl_dll/hud_msg.cpp index ebe08ba..0505d17 100644 --- a/cl_dll/hud_msg.cpp +++ b/cl_dll/hud_msg.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -51,6 +51,13 @@ int CHud :: MsgFunc_ResetHUD(const char *pszName, int iSize, void *pbuf ) return 1; } +void CAM_ToFirstPerson(void); + +void CHud :: MsgFunc_ViewMode( const char *pszName, int iSize, void *pbuf ) +{ + CAM_ToFirstPerson(); +} + void CHud :: MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ) { // prepare all hud data diff --git a/cl_dll/hud_redraw.cpp b/cl_dll/hud_redraw.cpp index 143817f..903fbdd 100644 --- a/cl_dll/hud_redraw.cpp +++ b/cl_dll/hud_redraw.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -104,12 +104,15 @@ int CHud :: Redraw( float flTime, int intermission ) m_iIntermission = intermission; gViewPort->HideCommandMenu(); gViewPort->HideScoreBoard(); + gViewPort->UpdateSpectatorPanel(); } else if ( !m_iIntermission && intermission ) { + m_iIntermission = intermission; gViewPort->HideCommandMenu(); gViewPort->HideVGUIMenu(); gViewPort->ShowScoreBoard(); + gViewPort->UpdateSpectatorPanel(); // Take a screenshot if the client's got the cvar set if ( CVAR_GET_FLOAT( "hud_takesshots" ) != 0 ) @@ -128,22 +131,25 @@ int CHud :: Redraw( float flTime, int intermission ) // if no redrawing is necessary // return 0; - HUDLIST *pList = m_pHudList; - - while (pList) + if ( m_pCvarDraw->value ) { - 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 ); - } + HUDLIST *pList = m_pHudList; - pList = pList->pNext; + 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? diff --git a/cl_dll/hud_servers.cpp b/cl_dll/hud_servers.cpp index 89c7b4e..8bd42d3 100644 --- a/cl_dll/hud_servers.cpp +++ b/cl_dll/hud_servers.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/hud_servers.h b/cl_dll/hud_servers.h index 2626916..02bc855 100644 --- a/cl_dll/hud_servers.h +++ b/cl_dll/hud_servers.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/hud_servers_priv.h b/cl_dll/hud_servers_priv.h index 91a616b..1919a0a 100644 --- a/cl_dll/hud_servers_priv.h +++ b/cl_dll/hud_servers_priv.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/hud_spectator.cpp b/cl_dll/hud_spectator.cpp index 0732183..1df426a 100644 --- a/cl_dll/hud_spectator.cpp +++ b/cl_dll/hud_spectator.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -10,44 +10,125 @@ #include "cl_entity.h" #include "triangleapi.h" #include "vgui_TeamFortressViewport.h" +#include "vgui_SpectatorPanel.h" #include "hltv.h" #include "pm_shared.h" +#include "pm_defs.h" +#include "pmtrace.h" +#include "parsemsg.h" #include "entity_types.h" // these are included for the math functions #include "com_model.h" +#include "demo_api.h" +#include "event_api.h" #include "studio_util.h" +#include "screenfade.h" + #pragma warning(disable: 4244) - -extern "C" unsigned int uiDirectorFlags; // from pm_shared.c -extern "C" float vecNewViewOrigin[3]; -extern "C" float vecNewViewAngles[3]; -extern "C" int iHasNewViewOrigin; extern "C" int iJumpSpectator; extern "C" float vJumpOrigin[3]; -extern "C" float vJumpAngles[3]; +extern "C" float vJumpAngles[3]; + extern void V_GetInEyePos(int entity, float * origin, float * angles ); -extern vec3_t v_angles; +extern void V_ResetChaseCam(); +extern void V_GetChasePos(int target, float * cl_angles, float * origin, float * angles); +extern void VectorAngles( const float *forward, float *angles ); +extern "C" void NormalizeAngles( float *angles ); +extern float * GetClientColor( int clientIndex ); + +extern vec3_t v_origin; // last view origin +extern vec3_t v_angles; // last view angle +extern vec3_t v_cl_angles; // last client/mouse angle +extern vec3_t v_sim_org; // last sim origin void SpectatorMode(void) { - if ( !gEngfuncs.IsSpectateOnly() ) - { - gEngfuncs.Con_Printf( "specmode only in HLTV mode available.\n" ); - return; - } - + + if ( gEngfuncs.Cmd_Argc() <= 1 ) { - gEngfuncs.Con_Printf( "usage: specmode < modenumber >\n" ); + gEngfuncs.Con_Printf( "usage: spec_mode
[]\n" ); return; } - gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv(1) ), INSET_OFF ); + // SetModes() will decide if command is executed on server or local + if ( gEngfuncs.Cmd_Argc() == 2 ) + gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv(1) ), -1 ); + else if ( gEngfuncs.Cmd_Argc() == 3 ) + gHUD.m_Spectator.SetModes( atoi( gEngfuncs.Cmd_Argv(1) ), atoi( gEngfuncs.Cmd_Argv(2) ) ); +} + +void SpectatorSpray(void) +{ + vec3_t forward; + char string[128]; + + if ( !gEngfuncs.IsSpectateOnly() ) + return; + + AngleVectors(v_angles,forward,NULL,NULL); + VectorScale(forward, 128, forward); + VectorAdd(forward, v_origin, forward); + pmtrace_t * trace = gEngfuncs.PM_TraceLine( v_origin, forward, PM_TRACELINE_PHYSENTSONLY, 2, -1 ); + if ( trace->fraction != 1.0 ) + { + sprintf(string, "drc_spray %.2f %.2f %.2f %i", + trace->endpos[0], trace->endpos[1], trace->endpos[2], trace->ent ); + gEngfuncs.pfnServerCmd(string); + } + +} +void SpectatorHelp(void) +{ + if ( gViewPort ) + { + gViewPort->ShowVGUIMenu( MENU_SPECHELP ); + } + else + { + char *text = CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help_Text" ); + + if ( text ) + { + while ( *text ) + { + if ( *text != 13 ) + gEngfuncs.Con_Printf( "%c", *text ); + text++; + } + } + } +} + +void SpectatorMenu( void ) +{ + if ( gEngfuncs.Cmd_Argc() <= 1 ) + { + gEngfuncs.Con_Printf( "usage: spec_menu <0|1>\n" ); + return; + } + + gViewPort->m_pSpectatorPanel->ShowMenu( atoi( gEngfuncs.Cmd_Argv(1))!=0 ); +} + +void ToggleScores( void ) +{ + if ( gViewPort ) + { + if (gViewPort->IsScoreBoardVisible() ) + { + gViewPort->HideScoreBoard(); + } + else + { + gViewPort->ShowScoreBoard(); + } + } } //----------------------------------------------------------------------------- @@ -59,16 +140,32 @@ int CHudSpectator::Init() m_iFlags |= HUD_ACTIVE; m_flNextObserverInput = 0.0f; - m_iObserverTarget = 0; m_zoomDelta = 0.0f; m_moveDelta = 0.0f; + m_chatEnabled = (gHUD.m_SayText.m_HUD_saytext->value!=0); + iJumpSpectator = 0; + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); - m_iMainMode = 0; - m_iInsetMode = 0; + m_lastPrimaryObject = m_lastSecondaryObject = 0; + + gEngfuncs.pfnAddCommand ("spec_mode", SpectatorMode ); + gEngfuncs.pfnAddCommand ("spec_decal", SpectatorSpray ); + gEngfuncs.pfnAddCommand ("spec_help", SpectatorHelp ); + gEngfuncs.pfnAddCommand ("spec_menu", SpectatorMenu ); + gEngfuncs.pfnAddCommand ("togglescores", ToggleScores ); + + m_drawnames = gEngfuncs.pfnRegisterVariable("spec_drawnames","1",0); + m_drawcone = gEngfuncs.pfnRegisterVariable("spec_drawcone","1",0); + m_drawstatus = gEngfuncs.pfnRegisterVariable("spec_drawstatus","1",0); + m_autoDirector = gEngfuncs.pfnRegisterVariable("spec_autodirector","1",0); + m_pip = gEngfuncs.pfnRegisterVariable("spec_pip","1",0); - m_drawnames = gEngfuncs.pfnRegisterVariable( "cl_drawnames", "1", FCVAR_ARCHIVE ); - gEngfuncs.pfnAddCommand ("specmode", SpectatorMode ); + if ( !m_drawnames || !m_drawcone || !m_drawstatus || !m_autoDirector || !m_pip) + { + gEngfuncs.Con_Printf("ERROR! Couldn't register all spectator variables.\n"); + return 0; + } return 1; } @@ -105,6 +202,131 @@ void UTIL_StringToVector( float * pVector, const char *pString ) } } +int UTIL_FindEntityInMap(char * name, float * origin, float * angle) +{ + int n,found = 0; + char keyname[256]; + char token[1024]; + + cl_entity_t * pEnt = gEngfuncs.GetEntityByIndex( 0 ); // get world model + + if ( !pEnt ) return 0; + + if ( !pEnt->model ) return 0; + + char * data = pEnt->model->entities; + + while (data) + { + data = gEngfuncs.COM_ParseFile(data, token); + + if ( (token[0] == '}') || (token[0]==0) ) + break; + + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + } + + if (token[0] != '{') + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: expected {\n"); + return 0; + } + + // we parse the first { now parse entities properties + + while ( 1 ) + { + // parse key + data = gEngfuncs.COM_ParseFile(data, token); + if (token[0] == '}') + break; // finish parsing this entity + + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + }; + + strcpy (keyname, token); + + // another hack to fix keynames with trailing spaces + n = strlen(keyname); + while (n && keyname[n-1] == ' ') + { + keyname[n-1] = 0; + n--; + } + + // parse value + data = gEngfuncs.COM_ParseFile(data, token); + if (!data) + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: EOF without closing brace\n"); + return 0; + }; + + if (token[0] == '}') + { + gEngfuncs.Con_DPrintf("UTIL_FindEntityInMap: closing brace without data"); + return 0; + } + + if (!strcmp(keyname,"classname")) + { + if (!strcmp(token, name )) + { + found = 1; // thats our entity + } + }; + + if( !strcmp( keyname, "angle" ) ) + { + float y = atof( token ); + + if (y >= 0) + { + angle[0] = 0.0f; + angle[1] = y; + } + else if ((int)y == -1) + { + angle[0] = -90.0f; + angle[1] = 0.0f;; + } + else + { + angle[0] = 90.0f; + angle[1] = 0.0f; + } + + angle[2] = 0.0f; + } + + if( !strcmp( keyname, "angles" ) ) + { + UTIL_StringToVector(angle, token); + } + + if (!strcmp(keyname,"origin")) + { + UTIL_StringToVector(origin, token); + + }; + + } // while (1) + + if (found) + return 1; + + } + + return 0; // we search all entities, but didn't found the correct + +} + //----------------------------------------------------------------------------- // SetSpectatorStartPosition(): // Get valid map position and 'beam' spectator to this position @@ -112,77 +334,29 @@ void UTIL_StringToVector( float * pVector, const char *pString ) void CHudSpectator::SetSpectatorStartPosition() { - float position[3] = {0,0,0}; + // search for info_player start + if ( UTIL_FindEntityInMap( "trigger_camera", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; - cl_entity_t * pEnt = gEngfuncs.GetEntityByIndex( 0 ); // get world model + else if ( UTIL_FindEntityInMap( "info_player_start", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; - if ( pEnt ) + else if ( UTIL_FindEntityInMap( "info_player_deathmatch", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; + + else if ( UTIL_FindEntityInMap( "info_player_coop", m_cameraOrigin, m_cameraAngles ) ) + iJumpSpectator = 1; + else { - char *data; - char keyname[256]; - int n; - char token[1024]; - - - for ( data = gEngfuncs.COM_ParseFile( pEnt->model->entities, token) ; // cl.worldmodel->entities - *data && *token != '}'; - data = gEngfuncs.COM_ParseFile(data, token) ) // TODO check this for null pointer crashes - { - - if (token[0] != '{') - return; - - while (1) - { - // parse key - data = gEngfuncs.COM_ParseFile(data, token); - if (token[0] == '}') - break; - if (!data) - return; - - strcpy (keyname, token); - // another hack to fix keynames with trailing spaces - n = strlen(keyname); - while (n && keyname[n-1] == ' ') - { - keyname[n-1] = 0; - n--; - } - - // parse value - data = gEngfuncs.COM_ParseFile(data, token); - - if (!data) - return; - - if (token[0] == '}') - return; - - if (!strcmp(keyname,"classname")) - { - if (!strcmp(token,"info_player_start")) - { - // origin should already be in SpectatorPos - break; - } - }; - - if (!strcmp(keyname,"origin")) - { - UTIL_StringToVector(position, token); - - }; - - } // while (1) - } + // jump to 0,0,0 if no better position was found + VectorCopy(vec3_origin, m_cameraOrigin); + VectorCopy(vec3_origin, m_cameraAngles); } + + VectorCopy(m_cameraOrigin, vJumpOrigin); + VectorCopy(m_cameraAngles, vJumpAngles); - VectorCopy ( position, vJumpOrigin ); - VectorCopy ( position, vecNewViewOrigin ); - - iHasNewViewOrigin = 1; - iJumpSpectator = 1; + iJumpSpectator = 1; // jump anyway } //----------------------------------------------------------------------------- @@ -212,14 +386,14 @@ int CHudSpectator::Draw(float flTime) int lx; char string[256]; - float red, green, blue; + float * color; // draw only in spectator mode - if ( gEngfuncs.IsSpectateOnly()!=1 ) - return 1; + if ( !g_iUser1 ) + return 0; // if user pressed zoom, aplly changes - if ( m_zoomDelta != 0.0f && m_iMainMode != MAIN_ROAMING ) + if ( (m_zoomDelta != 0.0f) && ( g_iUser1 == OBS_MAP_FREE ) ) { m_mapZoom += m_zoomDelta; @@ -231,20 +405,19 @@ int CHudSpectator::Draw(float flTime) } // if user moves in map mode, change map origin - if ( m_moveDelta != 0.0f && m_iMainMode != MAIN_ROAMING ) + if ( (m_moveDelta != 0.0f) && (g_iUser1 != OBS_ROAMING) ) { vec3_t right; - AngleVectors(m_mapAngles, NULL, right, NULL); + AngleVectors(v_angles, NULL, right, NULL); VectorNormalize(right); VectorScale(right, m_moveDelta, right ); VectorAdd( m_mapOrigin, right, m_mapOrigin ) } - - + // Only draw the icon names only if map mode is in Main Mode - if ( m_iMainMode != MAIN_MAP_FREE ) + if ( g_iUser1 < OBS_MAP_FREE ) return 1; if ( !m_drawnames->value ) @@ -262,7 +435,7 @@ int CHudSpectator::Draw(float flTime) continue; // check if name would be in inset window - if ( m_iInsetMode != INSET_OFF ) + if ( m_pip->value != INSET_OFF ) { if ( m_vPlayerPos[i][0] > XRES( m_OverviewData.insetWindowX ) && m_vPlayerPos[i][1] > YRES( m_OverviewData.insetWindowY ) && @@ -271,21 +444,14 @@ int CHudSpectator::Draw(float flTime) ) continue; } - // hack in some team colors - switch (g_PlayerExtraInfo[i+1].teamnumber) - { - // blue and red teams are swapped in CS and TFC - case 2 : red = 1.0f; green = 0.4f; blue = 0.4f; break; // Team Red - case 1 : red = 0.4f; green = 0.4f; blue = 1.0f; break; // Team Blue - default : red = 0.8f; green = 0.8f; blue = 0.8f; break; // Unkown Team, grey - } + color = GetClientColor( i+1 ); // draw the players name and health underneath sprintf(string, "%s", g_PlayerInfoList[i+1].name ); lx = strlen(string)*3; // 3 is avg. character length :) - gEngfuncs.pfnDrawSetTextColor( red, green, blue ); + gEngfuncs.pfnDrawSetTextColor( color[0], color[1], color[2] ); DrawConsoleString( m_vPlayerPos[i][0]-lx,m_vPlayerPos[i][1], string); } @@ -294,39 +460,141 @@ int CHudSpectator::Draw(float flTime) } - -void CHudSpectator::DirectorEvent(unsigned char command, unsigned int firstObject, unsigned int secondObject, unsigned int flags) +void CHudSpectator::DirectorMessage( int iSize, void *pbuf ) { - switch ( command ) + float value; + char * string; + + BEGIN_READ( pbuf, iSize ); + + int cmd = READ_BYTE(); + + switch ( cmd ) // director command byte { - case HLTV_ACTIVE : // we are connected to a proxy or listening to a multicast stream + case DRC_CMD_START : // now we have to do some things clientside, since the proxy doesn't know our mod g_iPlayerClass = 0; g_iTeamNumber = 0; - iJumpSpectator = 0; - m_iMainMode = m_iInsetMode = 0; - m_iObserverTarget = m_lastPrimaryObject = m_lastSecondaryObject = 0; - m_flNextObserverInput = 0.0f; - memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); - SetModes(MAIN_CHASE_FREE, INSET_OFF); - ParseOverviewFile(); - LoadMapSprites(); - SetSpectatorStartPosition(); + + // fake a InitHUD & ResetHUD message + gHUD.MsgFunc_InitHUD(NULL,0, NULL); + gHUD.MsgFunc_ResetHUD(NULL, 0, NULL); + break; - case HLTV_CAMERA : if ( g_iUser1 == OBS_DIRECTED ) + case DRC_CMD_EVENT : + m_lastPrimaryObject = READ_WORD(); + m_lastSecondaryObject = READ_WORD(); + m_iObserverFlags = READ_LONG(); + + if ( m_autoDirector->value ) { - m_iObserverTarget = g_iUser2 = firstObject; - g_iUser3 = secondObject; + if ( (g_iUser2 != m_lastPrimaryObject) || (g_iUser3 != m_lastSecondaryObject) ) + V_ResetChaseCam(); + + g_iUser2 = m_lastPrimaryObject; + g_iUser3 = m_lastSecondaryObject; } - m_lastPrimaryObject = firstObject; - m_lastSecondaryObject = secondObject; - uiDirectorFlags = flags; // gEngfuncs.Con_Printf("Director Camera: %i %i\n", firstObject, secondObject); break; - default : gEngfuncs.Con_DPrintf("CHudSpectator::DirectorEvent: unknown director command.\n"); + case DRC_CMD_MODE : + if ( m_autoDirector->value ) + { + SetModes( READ_BYTE(), -1 ); + } + break; + + case DRC_CMD_CAMERA : + if ( m_autoDirector->value ) + { + vJumpOrigin[0] = READ_COORD(); // position + vJumpOrigin[1] = READ_COORD(); + vJumpOrigin[2] = READ_COORD(); + + vJumpAngles[0] = READ_COORD(); // view angle + vJumpAngles[1] = READ_COORD(); + vJumpAngles[2] = READ_COORD(); + + gEngfuncs.SetViewAngles( vJumpAngles ); + + iJumpSpectator = 1; + } + break; + + case DRC_CMD_MESSAGE: + { + client_textmessage_t * msg = &m_HUDMessages[m_lastHudMessage]; + + msg->effect = READ_BYTE(); // effect + + UnpackRGB( (int&)msg->r1, (int&)msg->g1, (int&)msg->b1, READ_LONG() ); // color + msg->r2 = msg->r1; + msg->g2 = msg->g1; + msg->b2 = msg->b1; + msg->a2 = msg->a1 = 0xFF; // not transparent + + msg->x = READ_FLOAT(); // x pos + msg->y = READ_FLOAT(); // y pos + + msg->fadein = READ_FLOAT(); // fadein + msg->fadeout = READ_FLOAT(); // fadeout + msg->holdtime = READ_FLOAT(); // holdtime + msg->fxtime = READ_FLOAT(); // fxtime; + + strncpy( m_HUDMessageText[m_lastHudMessage], READ_STRING(), 128 ); + m_HUDMessageText[m_lastHudMessage][127]=0; // text + + msg->pMessage = m_HUDMessageText[m_lastHudMessage]; + msg->pName = "HUD_MESSAGE"; + + gHUD.m_Message.MessageAdd( msg ); + + m_lastHudMessage++; + m_lastHudMessage %= MAX_SPEC_HUD_MESSAGES; + + } + + break; + + case DRC_CMD_SOUND : + string = READ_STRING(); + value = READ_FLOAT(); + + // gEngfuncs.Con_Printf("DRC_CMD_FX_SOUND: %s %.2f\n", string, value ); + gEngfuncs.pEventAPI->EV_PlaySound(0, v_origin, CHAN_BODY, string, value, ATTN_NORM, 0, PITCH_NORM ); + + break; + + case DRC_CMD_TIMESCALE : + value = READ_FLOAT(); + break; + + + + case DRC_CMD_STATUS: + READ_LONG(); // total number of spectator slots + m_iSpectatorNumber = READ_LONG(); // total number of spectator + READ_WORD(); // total number of relay proxies + + gViewPort->UpdateSpectatorPanel(); + break; + + case DRC_CMD_BANNER: + // gEngfuncs.Con_DPrintf("GUI: Banner %s\n",READ_STRING() ); // name of banner tga eg gfx/temp/7454562234563475.tga + gViewPort->m_pSpectatorPanel->m_TopBanner->LoadImage( READ_STRING() ); + gViewPort->UpdateSpectatorPanel(); + break; + + case DRC_CMD_FADE: + break; + + case DRC_CMD_STUFFTEXT: + ClientCmd( READ_STRING() ); + break; + + default : gEngfuncs.Con_DPrintf("CHudSpectator::DirectorMessage: unknown command %i.\n", cmd ); } } @@ -336,13 +604,24 @@ void CHudSpectator::FindNextPlayer(bool bReverse) // only a subset of the players. e.g. Make it check the target's team. int iStart; - if ( m_iObserverTarget ) - iStart = m_iObserverTarget; + cl_entity_t * pEnt = NULL; + + // if we are NOT in HLTV mode, spectator targets are set on server + if ( !gEngfuncs.IsSpectateOnly() ) + { + char cmdstring[32]; + // forward command to server + sprintf(cmdstring,"follownext %i",bReverse?1:0); + gEngfuncs.pfnServerCmd(cmdstring); + return; + } + + if ( g_iUser2 ) + iStart = g_iUser2; else iStart = 1; - - m_iObserverTarget = 0; + g_iUser2 = 0; int iCurrent = iStart; @@ -362,220 +641,243 @@ void CHudSpectator::FindNextPlayer(bool bReverse) if (iCurrent < 1) iCurrent = MAX_PLAYERS; - cl_entity_t *pEnt = gEngfuncs.GetEntityByIndex( iCurrent ); + pEnt = gEngfuncs.GetEntityByIndex( iCurrent ); if ( !IsActivePlayer( pEnt ) ) continue; // MOD AUTHORS: Add checks on target here. - m_iObserverTarget = iCurrent; + g_iUser2 = iCurrent; break; } while ( iCurrent != iStart ); // Did we find a target? - if ( m_iObserverTarget ) + if ( !g_iUser2 ) { - // Store the target in pev so the physics DLL can get to it - if ( m_iMainMode != MAIN_ROAMING ) - { - g_iUser2 = m_iObserverTarget; - g_iUser3 = 0; - } - - gEngfuncs.Con_Printf("Now Tracking %s\n", g_PlayerInfoList[m_iObserverTarget].name ); + gEngfuncs.Con_DPrintf( "No observer targets.\n" ); + // take save camera position + VectorCopy(m_cameraOrigin, vJumpOrigin); + VectorCopy(m_cameraAngles, vJumpAngles); } else { - gEngfuncs.Con_Printf( "No observer targets.\n" ); + // use new entity position for roaming + VectorCopy ( pEnt->origin, vJumpOrigin ); + VectorCopy ( pEnt->angles, vJumpAngles ); } + iJumpSpectator = 1; } void CHudSpectator::HandleButtonsDown( int ButtonPressed ) { double time = gEngfuncs.GetClientTime(); - int newMainMode = m_iMainMode; - int newInsetMode = m_iInsetMode; + int newMainMode = g_iUser1; + int newInsetMode = m_pip->value; // gEngfuncs.Con_Printf(" HandleButtons:%i\n", ButtonPressed ); - - // Slow down mouse clicks - if ( m_flNextObserverInput > time || gEngfuncs.IsSpectateOnly()!=1 ) + if ( !gViewPort ) return; - // changing target or chase mode not in overviewmode without inset window + //Not in intermission. + if ( gHUD.m_iIntermission ) + return; - // Jump changes main window modes - if ( ButtonPressed & IN_JUMP ) - { - newMainMode+=1; - if (newMainMode > MAIN_MAP_FREE ) - newMainMode = MAIN_CHASE_FREE; // Main window can't be turned off - } + if ( !g_iUser1 ) + return; // dont do anything if not in spectator mode - // Duck changes inset window mode + // don't handle buttons during normal demo playback + if ( gEngfuncs.pDemoAPI->IsPlayingback() && !gEngfuncs.IsSpectateOnly() ) + return; + // Slow down mouse clicks. + if ( m_flNextObserverInput > time ) + return; + + // enable spectator screen if ( ButtonPressed & IN_DUCK ) + gViewPort->m_pSpectatorPanel->ShowMenu(!gViewPort->m_pSpectatorPanel->m_menuVisible); + + // 'Use' changes inset window mode + if ( ButtonPressed & IN_USE ) { - newInsetMode+=1; - if ( newInsetMode > INSET_MAP_FREE ) - newInsetMode = INSET_OFF; // inset window can be turned off + newInsetMode = ToggleInset(true); } - - // Attack moves to the next player - if ( ButtonPressed & (IN_ATTACK | IN_ATTACK2) ) - { - // in any mode change now from directed to users mode - g_iUser1 = OBS_CHASE_FREE; // leave directed sub mode - FindNextPlayer( (ButtonPressed & IN_ATTACK2) ? true:false ); + // if not in HLTV mode, buttons are handled server side + if ( gEngfuncs.IsSpectateOnly() ) + { + // changing target or chase mode not in overviewmode without inset window - if ( m_iMainMode == MAIN_ROAMING && m_iObserverTarget ) + // Jump changes main window modes + if ( ButtonPressed & IN_JUMP ) { - cl_entity_t * ent = gEngfuncs.GetEntityByIndex( m_iObserverTarget ); - VectorCopy ( ent->origin, vJumpOrigin ); - VectorCopy ( ent->angles, vJumpAngles ); - gEngfuncs.SetViewAngles( vJumpAngles ); - iJumpSpectator = 1; - + if ( g_iUser1 == OBS_CHASE_LOCKED ) + newMainMode = OBS_CHASE_FREE; + + else if ( g_iUser1 == OBS_CHASE_FREE ) + newMainMode = OBS_IN_EYE; + + else if ( g_iUser1 == OBS_IN_EYE ) + newMainMode = OBS_ROAMING; + + else if ( g_iUser1 == OBS_ROAMING ) + newMainMode = OBS_MAP_FREE; + + else if ( g_iUser1 == OBS_MAP_FREE ) + newMainMode = OBS_MAP_CHASE; + + else + newMainMode = OBS_CHASE_FREE; // don't use OBS_CHASE_LOCKED anymore + } + + // Attack moves to the next player + if ( ButtonPressed & (IN_ATTACK | IN_ATTACK2) ) + { + FindNextPlayer( (ButtonPressed & IN_ATTACK2) ? true:false ); + + if ( g_iUser1 == OBS_ROAMING ) + { + gEngfuncs.SetViewAngles( vJumpAngles ); + iJumpSpectator = 1; + + } + // lease directed mode if player want to see another player + m_autoDirector->value = 0.0f; } - } - if ( ButtonPressed & IN_FORWARD ) - m_zoomDelta = 0.01f; + SetModes(newMainMode, newInsetMode); - if ( ButtonPressed & IN_BACK ) - m_zoomDelta = -0.01f; - - if ( ButtonPressed & IN_MOVELEFT ) - m_moveDelta = -12.0f; + if ( g_iUser1 == OBS_MAP_FREE ) + { + if ( ButtonPressed & IN_FORWARD ) + m_zoomDelta = 0.01f; - if ( ButtonPressed & IN_MOVERIGHT ) - m_moveDelta = 12.0f; + if ( ButtonPressed & IN_BACK ) + m_zoomDelta = -0.01f; + + if ( ButtonPressed & IN_MOVELEFT ) + m_moveDelta = -12.0f; - if ( newMainMode != m_iMainMode || newInsetMode != m_iInsetMode ) - SetModes(newMainMode, newInsetMode); + if ( ButtonPressed & IN_MOVERIGHT ) + m_moveDelta = 12.0f; + } m_flNextObserverInput = time + 0.2; } void CHudSpectator::HandleButtonsUp( int ButtonPressed ) { - if ( gEngfuncs.IsSpectateOnly() != 1 ) + if ( !gViewPort ) return; + if ( !gViewPort->m_pSpectatorPanel->isVisible() ) + return; // dont do anything if not in spectator mode + if ( ButtonPressed & (IN_FORWARD | IN_BACK) ) m_zoomDelta = 0.0f; if ( ButtonPressed & (IN_MOVELEFT | IN_MOVERIGHT) ) m_moveDelta = 0.0f; } + void CHudSpectator::SetModes(int iNewMainMode, int iNewInsetMode) { - char string[32]; - static wrect_t crosshairRect; + // if value == -1 keep old value + if ( iNewMainMode == -1 ) + iNewMainMode = g_iUser1; - if ( iNewMainMode < MAIN_CHASE_LOCKED || iNewMainMode > MAIN_MAP_FREE ) + if ( iNewInsetMode == -1 ) + iNewInsetMode = m_pip->value; + + // inset mode is handled only clients side + m_pip->value = iNewInsetMode; + + if ( iNewMainMode < OBS_CHASE_LOCKED || iNewMainMode > OBS_MAP_CHASE ) { gEngfuncs.Con_Printf("Invalid spectator mode.\n"); return; } - - // Just abort if we're changing to the mode we're already in - if (iNewInsetMode != m_iInsetMode) - { - m_iInsetMode = iNewInsetMode; - sprintf(string, "%c#Spec_Mode_Inset%d", HUD_PRINTCENTER, m_iInsetMode); - gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); - } - + // main modes ettings will override inset window settings - if ( iNewMainMode != m_iMainMode ) + if ( iNewMainMode != g_iUser1 ) { + // if we are NOT in HLTV mode, main spectator mode is set on server + if ( !gEngfuncs.IsSpectateOnly() ) + { + return; + } + + if ( !g_iUser2 && (iNewMainMode !=OBS_ROAMING ) ) // make sure we have a target + { + // choose last Director object if still available + if ( IsActivePlayer( gEngfuncs.GetEntityByIndex( m_lastPrimaryObject ) ) ) + { + g_iUser2 = m_lastPrimaryObject; + g_iUser3 = m_lastSecondaryObject; + } + else + FindNextPlayer(false); // find any target + } + switch ( iNewMainMode ) { - case MAIN_CHASE_FREE : g_iUser1 = OBS_DIRECTED; + case OBS_CHASE_LOCKED: g_iUser1 = OBS_CHASE_LOCKED; break; - case MAIN_ROAMING : g_iUser1 = OBS_ROAMING; - - if ( m_iMainMode == MAIN_CHASE_FREE ) + case OBS_CHASE_FREE : g_iUser1 = OBS_CHASE_FREE; + break; + + case OBS_ROAMING : // jump to current vJumpOrigin/angle + g_iUser1 = OBS_ROAMING; + if ( g_iUser2 ) { - VectorCopy ( vecNewViewOrigin, vJumpOrigin ); - VectorCopy ( vecNewViewAngles, vJumpAngles ); + V_GetChasePos( g_iUser2, v_cl_angles, vJumpOrigin, vJumpAngles ); gEngfuncs.SetViewAngles( vJumpAngles ); iJumpSpectator = 1; } - - if ( m_iMainMode == MAIN_IN_EYE ) - { - V_GetInEyePos( m_iObserverTarget, vJumpOrigin, vJumpAngles ); - gEngfuncs.SetViewAngles( vJumpAngles ); - iJumpSpectator = 1; - } - - g_iUser2 = m_iObserverTarget = 0; - g_iUser3 = 0; break; - case MAIN_IN_EYE : g_iUser1 = OBS_DIRECTED; + case OBS_IN_EYE : g_iUser1 = OBS_IN_EYE; break; - case MAIN_MAP_FREE : g_iUser1 = OBS_DIRECTED; + case OBS_MAP_FREE : g_iUser1 = OBS_MAP_FREE; + // reset user values + m_mapZoom = m_OverviewData.zoom; + m_mapOrigin = m_OverviewData.origin; + break; + + case OBS_MAP_CHASE : g_iUser1 = OBS_MAP_CHASE; // reset user values m_mapZoom = m_OverviewData.zoom; m_mapOrigin = m_OverviewData.origin; break; } - m_iMainMode = iNewMainMode; - sprintf(string, "%c#Spec_Mode%d", HUD_PRINTCENTER, m_iMainMode); - gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); - - if ( g_iUser1 == OBS_DIRECTED && m_iObserverTarget == 0 ) + if ( (g_iUser1 == OBS_IN_EYE) || (g_iUser1 == OBS_ROAMING) ) { - // choose last Director object if still available - if ( IsActivePlayer( gEngfuncs.GetEntityByIndex( m_lastPrimaryObject ) ) ) - { - g_iUser2 = m_iObserverTarget = m_lastPrimaryObject; - g_iUser3 = m_lastSecondaryObject; - } - } - - // enable or disable crosshair - if ( m_iMainMode == MAIN_ROAMING || m_iMainMode == MAIN_IN_EYE ) - { - crosshairRect.left = 24; - crosshairRect.top = 0; - crosshairRect.right = 48; - crosshairRect.bottom = 24; + m_crosshairRect.left = 24; + m_crosshairRect.top = 0; + m_crosshairRect.right = 48; + m_crosshairRect.bottom = 24; - SetCrosshair( m_hCrosshair, crosshairRect, 255, 255, 255 ); + SetCrosshair( m_hCrosshair, m_crosshairRect, 255, 255, 255 ); } else { - memset( &crosshairRect,0,sizeof(crosshairRect) ); - SetCrosshair( 0, crosshairRect, 0, 0, 0 ); + memset( &m_crosshairRect,0,sizeof(m_crosshairRect) ); + SetCrosshair( 0, m_crosshairRect, 0, 0, 0 ); } + char string[128]; + sprintf(string, "#Spec_Mode%d", g_iUser1 ); + sprintf(string, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( string )); + gHUD.m_TextMessage.MsgFunc_TextMsg(NULL, strlen(string)+1, string ); } - // disallow same inset mode as main mode: - - if ( ( m_iMainMode != MAIN_MAP_FREE ) && ( m_iInsetMode == INSET_CHASE_FREE || m_iInsetMode == INSET_IN_EYE ) ) - { - // both would show in World picures - SetModes( m_iMainMode, INSET_MAP_FREE ); - } - - if ( ( m_iMainMode == MAIN_MAP_FREE ) && ( m_iInsetMode == INSET_MAP_FREE ) ) - { - // both would show map views - SetModes( m_iMainMode, INSET_OFF ); - } - gViewPort->UpdateSpectatorMenu(); + gViewPort->UpdateSpectatorPanel(); } @@ -612,7 +914,12 @@ bool CHudSpectator::ParseOverviewFile( ) m_OverviewData.zoom = 1.0f; m_OverviewData.layers = 0; m_OverviewData.layersHeights[0] = 0.0f; - strcpy(levelname, gEngfuncs.pfnGetLevelName() + 5); + strcpy( m_OverviewData.map, gEngfuncs.pfnGetLevelName() ); + + if ( strlen( m_OverviewData.map ) == 0 ) + return false; // not active yet + + strcpy(levelname, m_OverviewData.map + 5); levelname[strlen(levelname)-4] = 0; sprintf(filename, "overviews/%s.txt", levelname ); @@ -624,121 +931,122 @@ bool CHudSpectator::ParseOverviewFile( ) gEngfuncs.Con_Printf("Couldn't open file %s. Using default values for overiew mode.\n", filename ); return false; } - else + + + while (true) { - while (true) + pfile = gEngfuncs.COM_ParseFile(pfile, token); + + if (!pfile) + break; + + if ( !stricmp( token, "global" ) ) { + // parse the global data pfile = gEngfuncs.COM_ParseFile(pfile, token); - - if (!pfile) - break; - - if ( !stricmp( token, "global" ) ) + if ( stricmp( token, "{" ) ) { - // parse the global data - pfile = gEngfuncs.COM_ParseFile(pfile, token); - if ( stricmp( token, "{" ) ) - { - gEngfuncs.Con_Printf("Error parsing overview file %s. (expected { )\n", filename ); - return false; - } - - pfile = gEngfuncs.COM_ParseFile(pfile,token); - - while (stricmp( token, "}") ) - { - if ( !stricmp( token, "zoom" ) ) - { - pfile = gEngfuncs.COM_ParseFile(pfile,token); - m_OverviewData.zoom = atof( token ); - } - else if ( !stricmp( token, "origin" ) ) - { - pfile = gEngfuncs.COM_ParseFile(pfile, token); - m_OverviewData.origin[0] = atof( token ); - pfile = gEngfuncs.COM_ParseFile(pfile,token); - m_OverviewData.origin[1] = atof( token ); - pfile = gEngfuncs.COM_ParseFile(pfile, token); - m_OverviewData.origin[2] = atof( token ); - } - else if ( !stricmp( token, "rotated" ) ) - { - pfile = gEngfuncs.COM_ParseFile(pfile,token); - m_OverviewData.rotated = atoi( token ); - } - else if ( !stricmp( token, "inset" ) ) - { - pfile = gEngfuncs.COM_ParseFile(pfile,token); - m_OverviewData.insetWindowX = atof( token ); - pfile = gEngfuncs.COM_ParseFile(pfile,token); - m_OverviewData.insetWindowY = atof( token ); - pfile = gEngfuncs.COM_ParseFile(pfile,token); - m_OverviewData.insetWindowWidth = atof( token ); - pfile = gEngfuncs.COM_ParseFile(pfile,token); - m_OverviewData.insetWindowHeight = atof( token ); - - } - else - { - gEngfuncs.Con_Printf("Error parsing overview file %s. (%s unkown)\n", filename, token ); - return false; - } - - pfile = gEngfuncs.COM_ParseFile(pfile,token); // parse next token - - } + gEngfuncs.Con_Printf("Error parsing overview file %s. (expected { )\n", filename ); + return false; } - else if ( !stricmp( token, "layer" ) ) + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + while (stricmp( token, "}") ) { - // parse a layer data - - if ( m_OverviewData.layers == OVERVIEW_MAX_LAYERS ) + if ( !stricmp( token, "zoom" ) ) { - gEngfuncs.Con_Printf("Error parsing overview file %s. ( too many layers )\n", filename ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.zoom = atof( token ); + } + else if ( !stricmp( token, "origin" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile, token); + m_OverviewData.origin[0] = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.origin[1] = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile, token); + m_OverviewData.origin[2] = atof( token ); + } + else if ( !stricmp( token, "rotated" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.rotated = atoi( token ); + } + else if ( !stricmp( token, "inset" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowX = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowY = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowWidth = atof( token ); + pfile = gEngfuncs.COM_ParseFile(pfile,token); + m_OverviewData.insetWindowHeight = atof( token ); + + } + else + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (%s unkown)\n", filename, token ); return false; } - pfile = gEngfuncs.COM_ParseFile(pfile,token); - - - if ( stricmp( token, "{" ) ) - { - gEngfuncs.Con_Printf("Error parsing overview file %s. (expected { )\n", filename ); - return false; - } - - pfile = gEngfuncs.COM_ParseFile(pfile,token); - - while (stricmp( token, "}") ) - { - if ( !stricmp( token, "image" ) ) - { - pfile = gEngfuncs.COM_ParseFile(pfile,token); - strcpy(m_OverviewData.layersImages[ m_OverviewData.layers ], token); - - - } - else if ( !stricmp( token, "height" ) ) - { - pfile = gEngfuncs.COM_ParseFile(pfile,token); - height = atof(token); - m_OverviewData.layersHeights[ m_OverviewData.layers ] = height; - } - else - { - gEngfuncs.Con_Printf("Error parsing overview file %s. (%s unkown)\n", filename, token ); - return false; - } - - pfile = gEngfuncs.COM_ParseFile(pfile,token); // parse next token - } - - m_OverviewData.layers++; + pfile = gEngfuncs.COM_ParseFile(pfile,token); // parse next token } } + else if ( !stricmp( token, "layer" ) ) + { + // parse a layer data + + if ( m_OverviewData.layers == OVERVIEW_MAX_LAYERS ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. ( too many layers )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + + if ( stricmp( token, "{" ) ) + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (expected { )\n", filename ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); + + while (stricmp( token, "}") ) + { + if ( !stricmp( token, "image" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + strcpy(m_OverviewData.layersImages[ m_OverviewData.layers ], token); + + + } + else if ( !stricmp( token, "height" ) ) + { + pfile = gEngfuncs.COM_ParseFile(pfile,token); + height = atof(token); + m_OverviewData.layersHeights[ m_OverviewData.layers ] = height; + } + else + { + gEngfuncs.Con_Printf("Error parsing overview file %s. (%s unkown)\n", filename, token ); + return false; + } + + pfile = gEngfuncs.COM_ParseFile(pfile,token); // parse next token + } + + m_OverviewData.layers++; + + } } + gEngfuncs.COM_FreeFile( pfile ); + m_mapZoom = m_OverviewData.zoom; m_mapOrigin = m_OverviewData.origin; @@ -784,7 +1092,7 @@ void CHudSpectator::DrawOverviewLayer() xs = m_OverviewData.origin[0]; ys = m_OverviewData.origin[1]; - z = ( 90.0f - m_mapAngles[0] ) / 90.0f; + z = ( 90.0f - v_angles[0] ) / 90.0f; z *= m_OverviewData.layersHeights[0]; // gOverviewData.z_min - 32; // i = r_overviewTexture + ( layer*OVERVIEW_X_TILES*OVERVIEW_Y_TILES ); @@ -883,37 +1191,16 @@ void CHudSpectator::DrawOverviewLayer() } } - - -void CHudSpectator::GetMapPosition( float * returnvec ) -{ - vec3_t forward; - vec3_t zScaledTarget; - - zScaledTarget[0] = m_mapOrigin[0]; - zScaledTarget[1] = m_mapOrigin[1]; - zScaledTarget[2] = m_mapOrigin[2] * (( 90.0f - m_mapAngles[0] ) / 90.0f ); - - - AngleVectors(m_mapAngles, forward, NULL, NULL); - - VectorNormalize(forward); - VectorScale(forward, -( 4096.0f / m_mapZoom ), forward ); - - VectorAdd( zScaledTarget, forward, returnvec ) -} - - void CHudSpectator::DrawOverviewEntities() { int i,ir,ig,ib; struct model_s *hSpriteModel; - vec3_t origin,point, forward, right, left, up, world, screen, offset; + vec3_t origin, angles, point, forward, right, left, up, world, screen, offset; float x,y,z, r,g,b, sizeScale = 4.0f; cl_entity_t * ent; float rmatrix[3][4]; // transformation matrix - float zScale = (90.0f - m_mapAngles[0] ) / 90.0f; + float zScale = (90.0f - v_angles[0] ) / 90.0f; z = m_OverviewData.layersHeights[0] * zScale; @@ -1040,27 +1327,44 @@ void CHudSpectator::DrawOverviewEntities() m_vPlayerPos[playerNum][2] = 1; // mark player as visible } - if ( m_iInsetMode == INSET_OFF ) + if ( !m_pip->value || !m_drawcone->value ) return; - if ( m_iInsetMode == INSET_IN_EYE || m_iMainMode == MAIN_IN_EYE ) - { - V_GetInEyePos(m_iObserverTarget, vecNewViewOrigin, vecNewViewAngles ); - } + // get current camera position and angle + if ( m_pip->value == INSET_IN_EYE || g_iUser1 == OBS_IN_EYE ) + { + V_GetInEyePos( g_iUser2, origin, angles ); + } + else if ( m_pip->value == INSET_CHASE_FREE || g_iUser1 == OBS_CHASE_FREE ) + { + V_GetChasePos( g_iUser2, v_cl_angles, origin, angles ); + } + else if ( g_iUser1 == OBS_ROAMING ) + { + VectorCopy( v_sim_org, origin ); + VectorCopy( v_cl_angles, angles ); + } + else + V_GetChasePos( g_iUser2, NULL, origin, angles ); + + // draw camera sprite - x = vecNewViewOrigin[0]; - y = vecNewViewOrigin[1]; - z = vecNewViewOrigin[2]; + x = origin[0]; + y = origin[1]; + z = origin[2]; + + angles[0] = 0; // always show horizontal camera sprite hSpriteModel = (struct model_s *)gEngfuncs.GetSpritePointer( m_hsprCamera ); gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); gEngfuncs.pTriAPI->SpriteTexture( hSpriteModel, 0 ); + gEngfuncs.pTriAPI->Color4f( r, g, b, 1.0 ); - AngleVectors(vecNewViewAngles, forward, NULL, NULL ); + AngleVectors(angles, forward, NULL, NULL ); VectorScale (forward, 512.0f, forward); offset[0] = 0.0f; @@ -1087,21 +1391,19 @@ void CHudSpectator::DrawOverviewEntities() } -extern void VectorAngles( const float *forward, float *angles ); -extern "C" void NormalizeAngles( float *angles ); void CHudSpectator::DrawOverview() { - // don't draw it in developer mode - if ( gEngfuncs.IsSpectateOnly() != 1) + // draw only in sepctator mode + if ( !g_iUser1 ) return; // Only draw the overview if Map Mode is selected for this view - if ( m_iDrawCycle == 0 && m_iMainMode != MAIN_MAP_FREE ) + if ( m_iDrawCycle == 0 && ( (g_iUser1 != OBS_MAP_FREE) && (g_iUser1 != OBS_MAP_CHASE) ) ) return; - if ( m_iDrawCycle == 1 && m_iInsetMode != INSET_MAP_FREE ) + if ( m_iDrawCycle == 1 && m_pip->value < INSET_MAP_FREE ) return; DrawOverviewLayer(); @@ -1181,3 +1483,141 @@ bool CHudSpectator::AddOverviewEntityToList(HSPRITE sprite, cl_entity_t *ent, do return false; // maximum overview entities reached } +void CHudSpectator::CheckSettings() +{ + // disallow same inset mode as main mode: + + m_pip->value = (int)m_pip->value; + + if ( ( g_iUser1 < OBS_MAP_FREE ) && ( m_pip->value == INSET_CHASE_FREE || m_pip->value == INSET_IN_EYE ) ) + { + // otherwise both would show in World picures + m_pip->value = INSET_MAP_FREE; + } + + if ( ( g_iUser1 >= OBS_MAP_FREE ) && ( m_pip->value >= INSET_MAP_FREE ) ) + { + // both would show map views + m_pip->value = INSET_CHASE_FREE; + } + + // disble in intermission screen + if ( gHUD.m_iIntermission ) + m_pip->value = INSET_OFF; + + // check chat mode + if ( m_chatEnabled != (gHUD.m_SayText.m_HUD_saytext->value!=0) ) + { + // hud_saytext changed + m_chatEnabled = (gHUD.m_SayText.m_HUD_saytext->value!=0); + + if ( gEngfuncs.IsSpectateOnly() ) + { + // tell proxy our new chat mode + char chatcmd[32]; + sprintf(chatcmd, "ignoremsg %i", m_chatEnabled?0:1 ); + gEngfuncs.pfnServerCmd(chatcmd); + } + } + + // HL/TFC has no oberserver corsshair, so set it client side + if ( (g_iUser1 == OBS_IN_EYE) || (g_iUser1 == OBS_ROAMING) ) + { + m_crosshairRect.left = 24; + m_crosshairRect.top = 0; + m_crosshairRect.right = 48; + m_crosshairRect.bottom = 24; + + SetCrosshair( m_hCrosshair, m_crosshairRect, 255, 255, 255 ); + } + else + { + memset( &m_crosshairRect,0,sizeof(m_crosshairRect) ); + SetCrosshair( 0, m_crosshairRect, 0, 0, 0 ); + } + + + + // if we are a real player on server don't allow inset window + // in First Person mode since this is our resticted forcecamera mode 2 + // team number 3 = SPECTATOR see player.h + + if ( ( (g_iTeamNumber == 1) || (g_iTeamNumber == 2)) && (g_iUser1 == OBS_IN_EYE) ) + m_pip->value = INSET_OFF; + + // draw small border around inset view, adjust upper black bar + gViewPort->m_pSpectatorPanel->EnableInsetView( m_pip->value != INSET_OFF ); +} + +int CHudSpectator::ToggleInset(bool allowOff) +{ + int newInsetMode = (int)m_pip->value + 1; + + if ( g_iUser1 < OBS_MAP_FREE ) + { + if ( newInsetMode > INSET_MAP_CHASE ) + { + if (allowOff) + newInsetMode = INSET_OFF; + else + newInsetMode = INSET_MAP_FREE; + } + + if ( newInsetMode == INSET_CHASE_FREE ) + newInsetMode = INSET_MAP_FREE; + } + else + { + if ( newInsetMode > INSET_IN_EYE ) + { + if (allowOff) + newInsetMode = INSET_OFF; + else + newInsetMode = INSET_CHASE_FREE; + } + } + + return newInsetMode; +} +void CHudSpectator::Reset() +{ + // Reset HUD + if ( strcmp( m_OverviewData.map, gEngfuncs.pfnGetLevelName() ) ) + { + // update level overview if level changed + ParseOverviewFile(); + LoadMapSprites(); + } + + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); + + SetSpectatorStartPosition(); +} + +void CHudSpectator::InitHUDData() +{ + m_lastPrimaryObject = m_lastSecondaryObject = 0; + m_flNextObserverInput = 0.0f; + m_lastHudMessage = 0; + m_iSpectatorNumber = 0; + iJumpSpectator = 0; + g_iUser1 = g_iUser2 = 0; + + memset( &m_OverviewData, 0, sizeof(m_OverviewData)); + memset( &m_OverviewEntities, 0, sizeof(m_OverviewEntities)); + + if ( gEngfuncs.IsSpectateOnly() || gEngfuncs.pDemoAPI->IsPlayingback() ) + m_autoDirector->value = 1.0f; + else + m_autoDirector->value = 0.0f; + + Reset(); + + SetModes( OBS_CHASE_FREE, INSET_OFF ); + + g_iUser2 = 0; // fake not target until first camera command + + // reset HUD FOV + gHUD.m_iFOV = CVAR_GET_FLOAT("default_fov"); +} + diff --git a/cl_dll/hud_spectator.h b/cl_dll/hud_spectator.h index 1c994a9..7f1a361 100644 --- a/cl_dll/hud_spectator.h +++ b/cl_dll/hud_spectator.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -11,17 +11,16 @@ #include "cl_entity.h" -#define MAIN_CHASE_LOCKED 1 -#define MAIN_CHASE_FREE 2 -#define MAIN_ROAMING 3 -#define MAIN_IN_EYE 4 -#define MAIN_MAP_FREE 5 #define INSET_OFF 0 #define INSET_CHASE_FREE 1 #define INSET_IN_EYE 2 #define INSET_MAP_FREE 3 +#define INSET_MAP_CHASE 4 + +#define MAX_SPEC_HUD_MESSAGES 8 + #define OVERVIEW_TILE_SIZE 128 // don't change this @@ -32,6 +31,7 @@ //----------------------------------------------------------------------------- typedef struct overviewInfo_s { + char map[64]; // cl.levelname or empty vec3_t origin; // center of map float zoom; // zoom of map images int layers; // how may layers do we have @@ -57,6 +57,10 @@ typedef struct overviewEntity_s { class CHudSpectator : public CHudBase { public: + void Reset(); + int ToggleInset(bool allowOff); + void CheckSettings(); + void InitHUDData( void ); bool AddOverviewEntityToList( HSPRITE sprite, cl_entity_t * ent, double killTime); void DeathMessage(int victim); bool AddOverviewEntity( int type, struct cl_entity_s *ent, const char *modelname ); @@ -72,26 +76,38 @@ public: void HandleButtonsDown(int ButtonPressed); void HandleButtonsUp(int ButtonPressed); void FindNextPlayer( bool bReverse ); - void DirectorEvent(unsigned char command, unsigned int firstObject, unsigned int secondObject, unsigned int flags); + void DirectorMessage( int iSize, void *pbuf ); void SetSpectatorStartPosition(); int Init(); int VidInit(); int Draw(float flTime); - int m_iMainMode; - int m_iInsetMode; int m_iDrawCycle; + client_textmessage_t m_HUDMessages[MAX_SPEC_HUD_MESSAGES]; + char m_HUDMessageText[MAX_SPEC_HUD_MESSAGES][128]; + int m_lastHudMessage; overviewInfo_t m_OverviewData; overviewEntity_t m_OverviewEntities[MAX_OVERVIEW_ENTITIES]; - int m_iObserverTarget; - + int m_iObserverFlags; + int m_iSpectatorNumber; + float m_mapZoom; // zoom the user currently uses vec3_t m_mapOrigin; // origin where user rotates around - vec3_t m_mapAngles; // cuurent map view angles + cvar_t * m_drawnames; + cvar_t * m_drawcone; + cvar_t * m_drawstatus; + cvar_t * m_autoDirector; + cvar_t * m_pip; + + + qboolean m_chatEnabled; + + vec3_t m_cameraOrigin; // a help camera + vec3_t m_cameraAngles; // and it's angles + private: - cvar_t * m_drawnames; vec3_t m_vPlayerPos[MAX_PLAYERS]; HSPRITE m_hsprPlayerBlue; HSPRITE m_hsprPlayerRed; @@ -102,6 +118,9 @@ private: HSPRITE m_hsprUnkownMap; HSPRITE m_hsprBeam; HSPRITE m_hCrosshair; + + wrect_t m_crosshairRect; + struct model_s * m_MapSprite; // each layer image is saved in one sprite, where each tile is a sprite frame float m_flNextObserverInput; float m_zoomDelta; diff --git a/cl_dll/hud_update.cpp b/cl_dll/hud_update.cpp index 7d298f0..b3da672 100644 --- a/cl_dll/hud_update.cpp +++ b/cl_dll/hud_update.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/in_camera.cpp b/cl_dll/in_camera.cpp index 98bd35c..96dbdc7 100644 --- a/cl_dll/in_camera.cpp +++ b/cl_dll/in_camera.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -612,7 +612,7 @@ void CAM_EndDistance(void) int DLLEXPORT CL_IsThirdPerson( void ) { - return cam_thirdperson ? 1 : 0; + return (cam_thirdperson ? 1 : 0) || (g_iUser1 && (g_iUser2 == gEngfuncs.GetLocalPlayer()->index) ); } void DLLEXPORT CL_CameraOffset( float *ofs ) diff --git a/cl_dll/in_defs.h b/cl_dll/in_defs.h index 91ae195..f559189 100644 --- a/cl_dll/in_defs.h +++ b/cl_dll/in_defs.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/input.cpp b/cl_dll/input.cpp index 79ef0fd..ab9efba 100644 --- a/cl_dll/input.cpp +++ b/cl_dll/input.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -461,12 +461,16 @@ extern void __CmdFunc_InputPlayerSpecial(void); void IN_Attack2Down(void) { KeyDown(&in_attack2); - __CmdFunc_InputPlayerSpecial(); + gHUD.m_Spectator.HandleButtonsDown( IN_ATTACK2 ); } void IN_Attack2Up(void) {KeyUp(&in_attack2);} -void IN_UseDown (void) {KeyDown(&in_use);} +void IN_UseDown (void) +{ + KeyDown(&in_use); + gHUD.m_Spectator.HandleButtonsDown( IN_USE ); +} void IN_UseUp (void) {KeyUp(&in_use);} void IN_JumpDown (void) { diff --git a/cl_dll/inputw32.cpp b/cl_dll/inputw32.cpp index b00bd4f..7176746 100644 --- a/cl_dll/inputw32.cpp +++ b/cl_dll/inputw32.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/kbutton.h b/cl_dll/kbutton.h index 34e44fb..a772b3d 100644 --- a/cl_dll/kbutton.h +++ b/cl_dll/kbutton.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/menu.cpp b/cl_dll/menu.cpp index 7e5f6d6..1f0196b 100644 --- a/cl_dll/menu.cpp +++ b/cl_dll/menu.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/message.cpp b/cl_dll/message.cpp index 4714f0b..61ea55a 100644 --- a/cl_dll/message.cpp +++ b/cl_dll/message.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -514,3 +514,23 @@ int CHudMessage::MsgFunc_GameTitle( const char *pszName, int iSize, void *pbuf return 1; } + +void CHudMessage::MessageAdd(client_textmessage_t * newMessage ) +{ + m_parms.time = gHUD.m_flTime; + + // Turn on drawing + if ( !(m_iFlags & HUD_ACTIVE) ) + m_iFlags |= HUD_ACTIVE; + + for ( int i = 0; i < maxHUDMessages; i++ ) + { + if ( !m_pMessages[i] ) + { + m_pMessages[i] = newMessage; + m_startTime[i] = gHUD.m_flTime; + return; + } + } + +} diff --git a/cl_dll/overview.cpp b/cl_dll/overview.cpp index 5d24816..dd7741f 100644 --- a/cl_dll/overview.cpp +++ b/cl_dll/overview.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/overview.h b/cl_dll/overview.h index e87de10..7f0502e 100644 --- a/cl_dll/overview.h +++ b/cl_dll/overview.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/parsemsg.cpp b/cl_dll/parsemsg.cpp index 8a7167b..bdfa413 100644 --- a/cl_dll/parsemsg.cpp +++ b/cl_dll/parsemsg.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/parsemsg.h b/cl_dll/parsemsg.h index 113f52d..ab8ff15 100644 --- a/cl_dll/parsemsg.h +++ b/cl_dll/parsemsg.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/saytext.cpp b/cl_dll/saytext.cpp index 15737dd..7f24cf4 100644 --- a/cl_dll/saytext.cpp +++ b/cl_dll/saytext.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -55,7 +55,8 @@ int CHudSayText :: Init( void ) InitHUDData(); - CVAR_CREATE( "hud_saytext_time", "5", 0 ); + m_HUD_saytext = gEngfuncs.pfnRegisterVariable( "hud_saytext", "1", 0 ); + m_HUD_saytext_time = gEngfuncs.pfnRegisterVariable( "hud_saytext_time", "5", 0 ); m_iFlags |= HUD_INTERMISSION; // is always drawn during an intermission @@ -98,20 +99,20 @@ int CHudSayText :: Draw( float flTime ) { int y = Y_START; - if ( gViewPort && gViewPort->AllowedToPrintText() == FALSE ) + if ( ( gViewPort && gViewPort->AllowedToPrintText() == FALSE) || !m_HUD_saytext->value ) return 1; // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset - flScrollTime = min( flScrollTime, flTime + SCROLL_SPEED ); + flScrollTime = min( flScrollTime, flTime + m_HUD_saytext_time->value ); // make sure the scrolltime is within reasonable bounds, to guard against the clock being reset - flScrollTime = min( flScrollTime, flTime + SCROLL_SPEED ); + flScrollTime = min( flScrollTime, flTime + m_HUD_saytext_time->value ); if ( flScrollTime <= flTime ) { if ( *g_szLineBuffer[0] ) { - flScrollTime = flTime + SCROLL_SPEED; + flScrollTime = flTime + m_HUD_saytext_time->value; // push the console up ScrollTextUp(); } @@ -214,8 +215,7 @@ void CHudSayText :: SayTextPrint( const char *pszBuf, int iBufSize, int clientIn // Set scroll time if ( i == 0 ) { - SCROLL_SPEED = CVAR_GET_FLOAT( "hud_saytext_time" ); - flScrollTime = gHUD.m_flTime + SCROLL_SPEED; + flScrollTime = gHUD.m_flTime + m_HUD_saytext_time->value; } m_iFlags |= HUD_ACTIVE; diff --git a/cl_dll/soundsystem.cpp b/cl_dll/soundsystem.cpp index fa05526..a25084c 100644 --- a/cl_dll/soundsystem.cpp +++ b/cl_dll/soundsystem.cpp @@ -1,4 +1,4 @@ -//======== (C) Copyright 1996-2001 Valve, L.L.C. All rights reserved. ======== +//======== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. ======== // // The copyright to the contents herein is the property of Valve, L.L.C. // The contents may be used and/or copied only with the written permission of diff --git a/cl_dll/status_icons.cpp b/cl_dll/status_icons.cpp index 50fd9d0..5bbcec8 100644 --- a/cl_dll/status_icons.cpp +++ b/cl_dll/status_icons.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -53,6 +53,8 @@ void CHudStatusIcons::Reset( void ) // Draw status icons along the left-hand side of the screen int CHudStatusIcons::Draw( float flTime ) { + if (gEngfuncs.IsSpectateOnly()) + return 1; // find starting position to draw from, along right-hand side of screen int x = 5; int y = ScreenHeight / 2; diff --git a/cl_dll/statusbar.cpp b/cl_dll/statusbar.cpp index e44e1e9..28018da 100644 --- a/cl_dll/statusbar.cpp +++ b/cl_dll/statusbar.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -184,20 +184,16 @@ int CHudStatusBar :: Draw( float fTime ) m_bReparseString = FALSE; } + int Y_START = ScreenHeight - YRES(32 + 4); + // 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 - 55; - else - Y_START = ScreenHeight - 45; - - int x = 5; - int y = Y_START - ( TextHeight * i ); // draw along bottom of screen + int x = 4; + int y = Y_START - ( 4 + TextHeight * i ); // draw along bottom of screen // let user set status ID bar centering if ( (i == STATUSBAR_ID_LINE) && CVAR_GET_FLOAT("hud_centerid") ) diff --git a/cl_dll/studio_util.cpp b/cl_dll/studio_util.cpp index 869537f..ab14d96 100644 --- a/cl_dll/studio_util.cpp +++ b/cl_dll/studio_util.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/studio_util.h b/cl_dll/studio_util.h index f349d59..e1cb980 100644 --- a/cl_dll/studio_util.h +++ b/cl_dll/studio_util.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/text_message.cpp b/cl_dll/text_message.cpp index cd30c25..c18dd25 100644 --- a/cl_dll/text_message.cpp +++ b/cl_dll/text_message.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/tf_defs.h b/cl_dll/tf_defs.h index 42a7963..f178c55 100644 --- a/cl_dll/tf_defs.h +++ b/cl_dll/tf_defs.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -177,6 +177,8 @@ #define PC_LASTCLASS 12 // Use this as the high-boundary for any loops // through the playerclass. +#define SENTRY_COLOR 10 // will be in the PC_RANDOM slot for team colors + // These are just for the scanner #define SCAN_SENTRY 13 #define SCAN_GOALITEM 14 @@ -361,14 +363,6 @@ enum #define TF_SPY_SILENTDIE 199 -/*==================================================*/ -/* Colors */ -/*==================================================*/ -#define TEAM1_COLOR 150 -#define TEAM2_COLOR 250 -#define TEAM3_COLOR 45 -#define TEAM4_COLOR 100 - /*==================================================*/ /* Defines for the ENGINEER's Building ability */ /*==================================================*/ @@ -676,7 +670,7 @@ enum #define PC_SOLDIER_INITAMMO_ROCKET 10 #define PC_SOLDIER_GRENADE_TYPE_1 GR_TYPE_NORMAL #define PC_SOLDIER_GRENADE_TYPE_2 GR_TYPE_NAIL -#define PC_SOLDIER_GRENADE_INIT_1 4 +#define PC_SOLDIER_GRENADE_INIT_1 2 #define PC_SOLDIER_GRENADE_INIT_2 1 #define PC_SOLDIER_TF_ITEMS 0 @@ -711,8 +705,8 @@ enum #define PC_DEMOMAN_INITAMMO_DETPACK 1 #define PC_DEMOMAN_GRENADE_TYPE_1 GR_TYPE_NORMAL #define PC_DEMOMAN_GRENADE_TYPE_2 GR_TYPE_MIRV -#define PC_DEMOMAN_GRENADE_INIT_1 4 -#define PC_DEMOMAN_GRENADE_INIT_2 4 +#define PC_DEMOMAN_GRENADE_INIT_1 2 +#define PC_DEMOMAN_GRENADE_INIT_2 2 #define PC_DEMOMAN_TF_ITEMS 0 // Class Details for COMBAT MEDIC @@ -739,7 +733,7 @@ enum #define PC_MEDIC_INITAMMO_MEDIKIT 50 #define PC_MEDIC_GRENADE_TYPE_1 GR_TYPE_NORMAL #define PC_MEDIC_GRENADE_TYPE_2 GR_TYPE_CONCUSSION -#define PC_MEDIC_GRENADE_INIT_1 3 +#define PC_MEDIC_GRENADE_INIT_1 2 #define PC_MEDIC_GRENADE_INIT_2 2 #define PC_MEDIC_TF_ITEMS 0 #define PC_MEDIC_REGEN_TIME 3 // Number of seconds between each regen. @@ -767,7 +761,7 @@ enum #define PC_HVYWEAP_INITAMMO_ROCKET 0 #define PC_HVYWEAP_GRENADE_TYPE_1 GR_TYPE_NORMAL #define PC_HVYWEAP_GRENADE_TYPE_2 GR_TYPE_MIRV -#define PC_HVYWEAP_GRENADE_INIT_1 4 +#define PC_HVYWEAP_GRENADE_INIT_1 2 #define PC_HVYWEAP_GRENADE_INIT_2 1 #define PC_HVYWEAP_TF_ITEMS 0 #define PC_HVYWEAP_CELL_USAGE 7 // Amount of cells spent to power up assault cannon @@ -796,7 +790,7 @@ enum #define PC_PYRO_INITAMMO_ROCKET 5 #define PC_PYRO_GRENADE_TYPE_1 GR_TYPE_NORMAL #define PC_PYRO_GRENADE_TYPE_2 GR_TYPE_NAPALM -#define PC_PYRO_GRENADE_INIT_1 1 +#define PC_PYRO_GRENADE_INIT_1 2 #define PC_PYRO_GRENADE_INIT_2 4 #define PC_PYRO_TF_ITEMS 0 #define PC_PYRO_ROCKET_USAGE 3 // Number of rockets per incendiary cannon shot @@ -980,7 +974,7 @@ enum /*==========================================================================*/ /* Flamethrower */ /*==========================================================================*/ -#define FLAME_PLYRMAXTIME 4.5 // lifetime in seconds of a flame on a player +#define FLAME_PLYRMAXTIME 5.0 // lifetime in seconds of a flame on a player #define FLAME_MAXBURNTIME 8 // lifetime in seconds of a flame on the world (big ones) #define NAPALM_MAXBURNTIME 20 // lifetime in seconds of flame from a napalm grenade #define FLAME_MAXPLYRFLAMES 4 // maximum number of flames on a player @@ -990,6 +984,7 @@ enum #define FLAME_DAMAGE_TIME 1 // Interval between damage burns from flames #define FLAME_EFFECT_TIME 0.2 // frequency at which we display flame effects. #define FLAME_THINK_TIME 0.1 // Seconds between times the flame checks burn +#define PER_FLAME_DAMAGE 2 // Damage taken per second per flame by burning players /*==================================================*/ /* CTF Support defines */ @@ -1114,6 +1109,7 @@ float already_chosen_map; #define MENU_CLASSHELP2 7 #define MENU_REPEATHELP 8 +#define MENU_SPECHELP 9 #define MENU_SPY 12 @@ -1147,10 +1143,12 @@ float already_chosen_map; #define TF_TIMER_BUILD 11 #define TF_TIMER_CHECKBUILDDISTANCE 12 #define TF_TIMER_DISGUISE 13 +#define TF_TIMER_DISPENSERREFILL 14 // Non Player timers #define TF_TIMER_RETURNITEM 100 #define TF_TIMER_DELAYEDGOAL 101 +#define TF_TIMER_ENDROUND 102 //============================ // Teamscore printing @@ -1159,6 +1157,14 @@ float already_chosen_map; #define TS_PRINT_LONG_TO_ALL 3 #ifndef TF_DEFS_ONLY + +typedef struct +{ + int topColor; + int bottomColor; +} team_color_t; + + /*==================================================*/ /* GLOBAL VARIABLES */ /*==================================================*/ @@ -1167,7 +1173,9 @@ extern float number_of_teams; // number of teams supported by the map extern int illegalclasses[5]; // Illegal playerclasses for all teams extern int civilianteams; // Bitfield holding Civilian teams extern Vector rgbcolors[5]; // RGB colors for each of the 4 teams -extern int teamcolors[5]; // Colours for each of the 4 teams + +extern team_color_t teamcolors[5][PC_LASTCLASS]; // Colors for each of the 4 teams + extern int teamscores[5]; // Goal Score of each team extern int g_iOrderedTeams[5]; // Teams ordered into order of winners->losers extern int teamfrags[5]; // Total Frags for each team @@ -1196,6 +1204,7 @@ extern float old_grens; extern float flagem_checked; extern float flNextEqualisationCalc; extern BOOL cease_fire; +extern BOOL no_cease_fire_text; extern BOOL initial_cease_fire; extern BOOL last_cease_fire; // Autokick stuff @@ -1206,7 +1215,8 @@ extern float deathmsg; // Global, which is set before every T_Damage, to indica extern char *sTeamSpawnNames[]; extern char *sClassNames[]; -extern char *sClassModelFiles[]; +extern char *sNewClassModelFiles[]; +extern char *sOldClassModelFiles[]; extern char *sClassModels[]; extern char *sClassCfgs[]; extern char *sGrenadeNames[]; @@ -1286,6 +1296,7 @@ extern cvar_t tfc_spam_limit; // at this many points, gag the spammer extern cvar_t tfc_clanbattle, tfc_clanbattle_prematch, tfc_prematch, tfc_clanbattle_ceasefire, tfc_balance_teams, tfc_balance_scores; extern cvar_t tfc_clanbattle_locked, tfc_birthday, tfc_autokick_kills, tfc_fragscoring, tfc_autokick_time, tfc_adminpwd; extern cvar_t weaponstay, footsteps, flashlight, aimcrosshair, falldamage, teamplay; +extern cvar_t allow_spectators; /*==========================================================================*/ class CTFFlame : public CBaseMonster @@ -1336,7 +1347,11 @@ class CTFSpawn : public CBaseEntity { public: void Spawn( void ); + void Activate( void ); int Classify ( void ) { return CLASS_TFSPAWN; } + BOOL CheckTeam( int iTeamNo ); + + EHANDLE m_pTeamCheck; }; class CTFDetect : public CBaseEntity @@ -1353,6 +1368,21 @@ public: void EXPORT DeathTouch( CBaseEntity *pOther ); }; +class CTeamCheck : public CBaseDelay +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + BOOL TeamMatches( int iTeam ); +}; + +class CTeamSet : public CBaseDelay +{ +public: + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + #endif // TF_DEFS_ONLY #endif // __TF_DEFS_H diff --git a/cl_dll/train.cpp b/cl_dll/train.cpp index 0a4d604..d16a104 100644 --- a/cl_dll/train.cpp +++ b/cl_dll/train.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/tri.cpp b/cl_dll/tri.cpp index 8729035..fc7fb8f 100644 --- a/cl_dll/tri.cpp +++ b/cl_dll/tri.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/util.cpp b/cl_dll/util.cpp index d8fe4c7..f2e3f31 100644 --- a/cl_dll/util.cpp +++ b/cl_dll/util.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/util_vector.h b/cl_dll/util_vector.h index 05da19e..4a1ba9c 100644 --- a/cl_dll/util_vector.h +++ b/cl_dll/util_vector.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/vgui_ClassMenu.cpp b/cl_dll/vgui_ClassMenu.cpp index 4fece0c..ba94fd0 100644 --- a/cl_dll/vgui_ClassMenu.cpp +++ b/cl_dll/vgui_ClassMenu.cpp @@ -1,4 +1,4 @@ -//=========== (C) Copyright 1996-2001 Valve, L.L.C. All rights reserved. =========== +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== // // The copyright to the contents herein is the property of Valve, L.L.C. // The contents may be used and/or copied only with the written permission of @@ -235,6 +235,7 @@ CClassMenuPanel::CClassMenuPanel(int iTrans, int iRemoveMe, int x,int y,int wide } m_pClassInfoPanel[i]->setSize( maxX , maxY ); + if (pfile) gEngfuncs.COM_FreeFile( pfile ); //m_pClassInfoPanel[i]->setBorder(new LineBorder()); } diff --git a/cl_dll/vgui_ConsolePanel.cpp b/cl_dll/vgui_ConsolePanel.cpp index 2eb0de6..a8b304c 100644 --- a/cl_dll/vgui_ConsolePanel.cpp +++ b/cl_dll/vgui_ConsolePanel.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/vgui_ConsolePanel.h b/cl_dll/vgui_ConsolePanel.h index 9c4a107..84821f7 100644 --- a/cl_dll/vgui_ConsolePanel.h +++ b/cl_dll/vgui_ConsolePanel.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/vgui_ControlConfigPanel.cpp b/cl_dll/vgui_ControlConfigPanel.cpp index 2086c3e..0fd5264 100644 --- a/cl_dll/vgui_ControlConfigPanel.cpp +++ b/cl_dll/vgui_ControlConfigPanel.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/vgui_ControlConfigPanel.h b/cl_dll/vgui_ControlConfigPanel.h index 0e85b67..15ca3fd 100644 --- a/cl_dll/vgui_ControlConfigPanel.h +++ b/cl_dll/vgui_ControlConfigPanel.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/vgui_CustomObjects.cpp b/cl_dll/vgui_CustomObjects.cpp index fd24b3d..441a50e 100644 --- a/cl_dll/vgui_CustomObjects.cpp +++ b/cl_dll/vgui_CustomObjects.cpp @@ -1,4 +1,4 @@ -//=========== (C) Copyright 1996-2001 Valve, L.L.C. All rights reserved. =========== +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== // // The copyright to the contents herein is the property of Valve, L.L.C. // The contents may be used and/or copied only with the written permission of @@ -78,14 +78,25 @@ CommandButton::CommandButton( const char* text,int x,int y,int wide,int tall, bo { m_iPlayerClass = 0; m_bNoHighlight = bNoHighlight; + m_bFlat = false; Init(); setText( text ); } -CommandButton::CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall) : Button("",x,y,wide,tall) +CommandButton::CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall, bool bFlat) : Button("",x,y,wide,tall) { m_iPlayerClass = iPlayerClass; m_bNoHighlight = false; + m_bFlat = bFlat; + Init(); + setText( text ); +} + +CommandButton::CommandButton(const char *text, int x, int y, int wide, int tall, bool bNoHighlight, bool bFlat) : Button("",x,y,wide,tall) +{ + m_iPlayerClass = 0; + m_bFlat = bFlat; + m_bNoHighlight = bNoHighlight; Init(); setText( text ); } @@ -120,7 +131,14 @@ void CommandButton::RecalculateText( void ) if ( m_cBoundKey != 0 ) { - sprintf( szBuf, " %c %s", m_cBoundKey, m_sMainText ); + if ( m_cBoundKey == (char)255 ) + { + strcpy( szBuf, m_sMainText ); + } + else + { + sprintf( szBuf, " %c %s", m_cBoundKey, m_sMainText ); + } szBuf[MAX_BUTTON_SIZE-1] = 0; } else @@ -192,16 +210,28 @@ void CommandButton::paint() void CommandButton::paintBackground() { - if ( isArmed() ) + if ( m_bFlat ) { - // Orange highlight background - drawSetColor( Scheme::sc_primary2 ); - drawFilledRect(0,0,_size[0],_size[1]); + if ( isArmed() ) + { + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } } + else + { + if ( isArmed() ) + { + // Orange highlight background + drawSetColor( Scheme::sc_primary2 ); + drawFilledRect(0,0,_size[0],_size[1]); + } - // Orange Border - drawSetColor( Scheme::sc_secondary1 ); - drawOutlinedRect(0,0,_size[0],_size[1]); + // Orange Border + drawSetColor( Scheme::sc_secondary1 ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } } //----------------------------------------------------------------------------- @@ -310,16 +340,61 @@ CImageLabel::CImageLabel( const char* pImageName,int x,int y,int wide,int tall ) // Image size int CImageLabel::getImageWide( void ) { - int iXSize, iYSize; - m_pTGA->getSize( iXSize, iYSize ); - return iXSize; + if( m_pTGA ) + { + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iXSize; + } + else + { + return 1; + } } int CImageLabel::getImageTall( void ) { - int iXSize, iYSize; - m_pTGA->getSize( iXSize, iYSize ); - return iYSize; + if( m_pTGA ) + { + int iXSize, iYSize; + m_pTGA->getSize( iXSize, iYSize ); + return iYSize; + } + else + { + return 1; + } +} + +void CImageLabel::LoadImage(const char * pImageName) +{ + if ( m_pTGA ) + delete m_pTGA; + + // Load the Image + m_pTGA = LoadTGAForRes(pImageName); + + if ( m_pTGA == NULL ) + { + // we didn't find a matching image file for this resolution + // try to load file resolution independent + + char sz[256]; + sprintf(sz, "%s/%s",gEngfuncs.pfnGetGameDirectory(), pImageName ); + FileInputStream* fis = new FileInputStream( sz, false ); + m_pTGA = new BitmapTGA(fis,true); + fis->close(); + } + + if ( m_pTGA == NULL ) + return; // unable to load image + + int w,t; + + m_pTGA->getSize( w, t ); + + setSize( XRES (w),YRES (t) ); + setImage( m_pTGA ); } //=========================================================== @@ -327,7 +402,12 @@ int CImageLabel::getImageTall( void ) void CCommandMenu::paintBackground() { // Transparent black background - drawSetColor(Scheme::sc_primary3); + + if ( m_iSpectCmdMenu ) + drawSetColor( 0, 0, 0, 64 ); + else + drawSetColor(Scheme::sc_primary3); + drawFilledRect(0,0,_size[0],_size[1]); } diff --git a/cl_dll/vgui_MOTDWindow.cpp b/cl_dll/vgui_MOTDWindow.cpp index 297af6f..b6f46dd 100644 --- a/cl_dll/vgui_MOTDWindow.cpp +++ b/cl_dll/vgui_MOTDWindow.cpp @@ -1,4 +1,4 @@ -//=========== (C) Copyright 1996-2001 Valve, L.L.C. All rights reserved. =========== +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== // // The copyright to the contents herein is the property of Valve, L.L.C. // The contents may be used and/or copied only with the written permission of diff --git a/cl_dll/vgui_SchemeManager.cpp b/cl_dll/vgui_SchemeManager.cpp index ad972c2..571dca3 100644 --- a/cl_dll/vgui_SchemeManager.cpp +++ b/cl_dll/vgui_SchemeManager.cpp @@ -1,4 +1,4 @@ -//=========== (C) Copyright 1996-2001 Valve, L.L.C. All rights reserved. =========== +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== // // The copyright to the contents herein is the property of Valve, L.L.C. // The contents may be used and/or copied only with the written permission of @@ -397,6 +397,8 @@ buildDefaultFont: { sprintf(fontFilename, "gfx\\vgui\\fonts\\%d_%s.tga", m_xRes, m_pSchemeList[i].schemeName); pFontData = gEngfuncs.COM_LoadFile( fontFilename, 5, &fontFileLength ); + if(!pFontData) + gEngfuncs.Con_Printf("Missing bitmap font: %s\n", fontFilename); } m_pSchemeList[i].font = new vgui::Font( diff --git a/cl_dll/vgui_SchemeManager.h b/cl_dll/vgui_SchemeManager.h index 12a2855..45c2a06 100644 --- a/cl_dll/vgui_SchemeManager.h +++ b/cl_dll/vgui_SchemeManager.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/vgui_ScorePanel.cpp b/cl_dll/vgui_ScorePanel.cpp index cecfee3..bea050d 100644 --- a/cl_dll/vgui_ScorePanel.cpp +++ b/cl_dll/vgui_ScorePanel.cpp @@ -1,4 +1,4 @@ -//=========== (C) Copyright 1996-2001 Valve, L.L.C. All rights reserved. =========== +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== // // The copyright to the contents herein is the property of Valve, L.L.C. // The contents may be used and/or copied only with the written permission of @@ -28,6 +28,7 @@ #include "vgui_ScorePanel.h" #include "..\game_shared\vgui_helpers.h" #include "..\game_shared\vgui_loadtga.h" +#include "vgui_SpectatorPanel.h" hud_player_info_t g_PlayerInfoList[MAX_PLAYERS+1]; // player info from the engine extra_player_info_t g_PlayerExtraInfo[MAX_PLAYERS+1]; // additional player info sent directly to the client dll @@ -155,7 +156,6 @@ ScorePanel::ScorePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) m_HeaderGrid.SetColumnWidth(i, xwide); m_HeaderGrid.SetEntry(i, 0, &m_HeaderLabels[i]); - m_HeaderLabels[i].setBgColor(0,0,0,255); m_HeaderLabels[i].setBgColor(0,0,0,255); m_HeaderLabels[i].setFgColor(Scheme::sc_primary1); m_HeaderLabels[i].setFont(smallfont); @@ -219,6 +219,16 @@ ScorePanel::ScorePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) m_HitTestPanel.setBounds(0, 0, wide, tall); m_HitTestPanel.addInputSignal(this); + m_pCloseButton = new CommandButton( "x", wide-XRES(12 + 4), YRES(2), XRES( 12 ) , YRES( 12 ) ); + m_pCloseButton->setParent( this ); + m_pCloseButton->addActionSignal( new CMenuHandler_StringCommandWatch( "-showscores", true ) ); + m_pCloseButton->setBgColor(0,0,0,255); + m_pCloseButton->setFgColor( 255, 255, 255, 0 ); + m_pCloseButton->setFont(tfont); + m_pCloseButton->setBoundKey( (char)255 ); + m_pCloseButton->setContentAlignment(Label::a_center); + + Initialize(); } @@ -237,6 +247,11 @@ void ScorePanel::Initialize( void ) memset( g_TeamInfo, 0, sizeof g_TeamInfo ); } +bool HACK_GetPlayerUniqueID( int iPlayer, char playerID[16] ) +{ + return !!gEngfuncs.GetPlayerUniqueID( iPlayer, playerID ); +} + //----------------------------------------------------------------------------- // Purpose: Recalculate the internal scoreboard data //----------------------------------------------------------------------------- @@ -271,6 +286,15 @@ void ScorePanel::Update() m_PlayerList.SetScrollRange(m_iRows); FillGrid(); + + if ( gViewPort->m_pSpectatorPanel->m_menuVisible ) + { + m_pCloseButton->setVisible ( true ); + } + else + { + m_pCloseButton->setVisible ( false ); + } } //----------------------------------------------------------------------------- @@ -917,10 +941,6 @@ void ScorePanel::MouseOverCell(int row, int col) if (pl_info->thisplayer && !gEngfuncs.IsSpectateOnly() ) return; - // only act on audible players - if (!GetClientVoiceMgr()->IsPlayerAudible(m_iSortedRows[row])) - return; - // setup the new highlight m_pCurrentHighlightLabel = label; m_iHighlightRow = row; @@ -973,6 +993,13 @@ void CLabelHeader::paint() // get size of the panel and the image if (_image) { + Color imgColor; + getFgColor( imgColor ); + if( _useFgColorAsImageColor ) + { + _image->setColor( imgColor ); + } + _image->getSize(iwide, itall); calcAlignment(iwide, itall, x, y); _image->setPos(x, y); diff --git a/cl_dll/vgui_ScorePanel.h b/cl_dll/vgui_ScorePanel.h index 5bc4ab4..f7a8543 100644 --- a/cl_dll/vgui_ScorePanel.h +++ b/cl_dll/vgui_ScorePanel.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -178,17 +178,17 @@ public: Color color(r,g,b,a); _dualImage->setColor(color); _dualImage->setColor2(color); - if (_image && _useFgColorAsImageColor) - { - _image->setColor(color); - } repaint(); } void setFgColor(Scheme::SchemeColor sc) { + int r, g, b, a; Label::setFgColor(sc); - _dualImage->setColor(sc); + Label::getFgColor( r, g, b, a ); + + // Call the r,g,b,a version so it sets the color in the dualImage.. + setFgColor( r, g, b, a ); } void setFont(Font *font) @@ -261,7 +261,7 @@ private: CLabelHeader m_PlayerEntries[NUM_COLUMNS][NUM_ROWS]; // Labels for the grid entries. ScorePanel::HitTestPanel m_HitTestPanel; - + CommandButton *m_pCloseButton; CLabelHeader* GetPlayerEntry(int x, int y) {return &m_PlayerEntries[x][y];} public: diff --git a/cl_dll/vgui_ServerBrowser.cpp b/cl_dll/vgui_ServerBrowser.cpp index 21d723e..6e90259 100644 --- a/cl_dll/vgui_ServerBrowser.cpp +++ b/cl_dll/vgui_ServerBrowser.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/vgui_ServerBrowser.h b/cl_dll/vgui_ServerBrowser.h index 4ab3f44..43da4f4 100644 --- a/cl_dll/vgui_ServerBrowser.h +++ b/cl_dll/vgui_ServerBrowser.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/vgui_SpectatorPanel.cpp b/cl_dll/vgui_SpectatorPanel.cpp new file mode 100644 index 0000000..582a319 --- /dev/null +++ b/cl_dll/vgui_SpectatorPanel.cpp @@ -0,0 +1,383 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// vgui_SpectatorPanel.cpp: implementation of the SpectatorPanel class. +// +////////////////////////////////////////////////////////////////////// + +#include "hud.h" +#include "cl_util.h" +#include "const.h" +#include "entity_state.h" +#include "cl_entity.h" +#include "pm_shared.h" +#include "vgui_TeamFortressViewport.h" +#include "vgui_SpectatorPanel.h" +#include "vgui_scorepanel.h" + +#define PANEL_HEIGHT 32 + + +#define BANNER_WIDTH 256 +#define BANNER_HEIGHT 64 + + +#define OPTIONS_BUTTON_X 96 +#define CAMOPTIONS_BUTTON_X 200 + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +SpectatorPanel::SpectatorPanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall) +{ +} + +SpectatorPanel::~SpectatorPanel() +{ + +} + +void SpectatorPanel::ActionSignal(int cmd) +{ + switch (cmd) + { + case SPECTATOR_PANEL_CMD_NONE : break; + + case SPECTATOR_PANEL_CMD_OPTIONS : gViewPort->ShowCommandMenu( gViewPort->m_SpectatorOptionsMenu ); + break; + + case SPECTATOR_PANEL_CMD_NEXTPLAYER : gHUD.m_Spectator.FindNextPlayer(true); + break; + + case SPECTATOR_PANEL_CMD_PREVPLAYER : gHUD.m_Spectator.FindNextPlayer(false); + break; + + case SPECTATOR_PANEL_CMD_HIDEMENU : ShowMenu(false); + break; + + case SPECTATOR_PANEL_CMD_CAMERA : gViewPort->ShowCommandMenu( gViewPort->m_SpectatorCameraMenu ); + break; + + case SPECTATOR_PANEL_CMD_TOGGLE_INSET : gHUD.m_Spectator.SetModes( -1, + gHUD.m_Spectator.ToggleInset(false) ); + break; + + + default : gEngfuncs.Con_DPrintf("Unknown SpectatorPanel ActionSingal %i.\n",cmd); break; + } + +} + + +void SpectatorPanel::Initialize() +{ + int x,y,wide,tall; + + getBounds(x,y,wide,tall); + + CSchemeManager * pSchemes = gViewPort->GetSchemeManager(); + + SchemeHandle_t hSmallScheme = pSchemes->getSchemeHandle( "Team Info Text" ); + + m_TopBorder = new CTransparentPanel(64, 0, 0, ScreenWidth, YRES(PANEL_HEIGHT)); + m_TopBorder->setParent(this); + + m_BottomBorder = new CTransparentPanel(64, 0, ScreenHeight - YRES(32), ScreenWidth, YRES(PANEL_HEIGHT)); + m_BottomBorder->setParent(this); + + setPaintBackgroundEnabled(false); + + m_ExtraInfo = new Label( "Extra Info", 0, 0, wide, YRES(PANEL_HEIGHT) ); + m_ExtraInfo->setParent(m_TopBorder); + m_ExtraInfo->setFont( pSchemes->getFont(hSmallScheme) ); + + m_ExtraInfo->setPaintBackgroundEnabled(false); + m_ExtraInfo->setFgColor( 143, 143, 54, 0 ); + m_ExtraInfo->setContentAlignment( vgui::Label::a_west ); + + + + m_TimerImage = new CImageLabel( "timer", 0, 0, 14, 14 ); + m_TimerImage->setParent(m_TopBorder); + + m_TopBanner = new CImageLabel( "banner", 0, 0, XRES(BANNER_WIDTH), YRES(BANNER_HEIGHT) ); + m_TopBanner->setParent(this); + + m_CurrentTime = new Label( "00:00", 0, 0, wide, YRES(PANEL_HEIGHT) ); + m_CurrentTime->setParent(m_TopBorder); + m_CurrentTime->setFont( pSchemes->getFont(hSmallScheme) ); + m_CurrentTime->setPaintBackgroundEnabled(false); + m_CurrentTime->setFgColor( 143, 143, 54, 0 ); + m_CurrentTime->setContentAlignment( vgui::Label::a_west ); + + m_Separator = new Panel( 0, 0, XRES( 64 ), YRES( 96 )); + m_Separator->setParent( m_TopBorder ); + m_Separator->setFgColor( 59, 58, 34, 48 ); + m_Separator->setBgColor( 59, 58, 34, 48 ); + + for ( int j= 0; j < TEAM_NUMBER; j++ ) + { + m_TeamScores[j] = new Label( " ", 0, 0, wide, YRES(PANEL_HEIGHT) ); + m_TeamScores[j]->setParent( m_TopBorder ); + m_TeamScores[j]->setFont( pSchemes->getFont(hSmallScheme) ); + m_TeamScores[j]->setPaintBackgroundEnabled(false); + m_TeamScores[j]->setFgColor( 143, 143, 54, 0 ); + m_TeamScores[j]->setContentAlignment( vgui::Label::a_west ); + m_TeamScores[j]->setVisible ( false ); + } + + + // Initialize command buttons. + m_OptionButton = new ColorButton( CHudTextMessage::BufferedLocaliseTextString( "#SPECT_OPTIONS" ), XRES(15), YRES(6), XRES(OPTIONS_BUTTON_X), YRES(20), false, false ); + m_OptionButton->setParent( m_BottomBorder ); + m_OptionButton->setContentAlignment( vgui::Label::a_center ); + m_OptionButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_OptionButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_OPTIONS) ); + m_OptionButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_OptionButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_OptionButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_OptionButton->setArmedColor ( 194, 202, 54, 0 ); + + m_CamButton = new ColorButton( CHudTextMessage::BufferedLocaliseTextString( "#CAM_OPTIONS" ), ScreenWidth - ( XRES ( CAMOPTIONS_BUTTON_X ) + 15 ), YRES(6), XRES ( CAMOPTIONS_BUTTON_X ), YRES(20), false, false ); + m_CamButton->setParent( m_BottomBorder ); + m_CamButton->setContentAlignment( vgui::Label::a_center ); + m_CamButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_CamButton->addActionSignal( new CSpectatorHandler_Command( this, SPECTATOR_PANEL_CMD_CAMERA ) ); + m_CamButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_CamButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_CamButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_CamButton->setArmedColor ( 194, 202, 54, 0 ); + + m_PrevPlayerButton= new ColorButton("<", XRES( 15 + OPTIONS_BUTTON_X + 15 ), YRES(6), XRES(24), YRES(20), false, false ); + m_PrevPlayerButton->setParent( m_BottomBorder ); + m_PrevPlayerButton->setContentAlignment( vgui::Label::a_center ); + m_PrevPlayerButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_PrevPlayerButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_PREVPLAYER) ); + m_PrevPlayerButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_PrevPlayerButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_PrevPlayerButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_PrevPlayerButton->setArmedColor ( 194, 202, 54, 0 ); + + m_NextPlayerButton= new ColorButton(">", (ScreenWidth - (XRES ( CAMOPTIONS_BUTTON_X ) + 15)) - XRES ( 24 + 15 ), YRES(6), XRES(24), YRES(20),false, false ); + m_NextPlayerButton->setParent( m_BottomBorder ); + m_NextPlayerButton->setContentAlignment( vgui::Label::a_center ); + m_NextPlayerButton->setBoundKey( (char)255 ); // special no bound to avoid leading spaces in name + m_NextPlayerButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_NEXTPLAYER) ); + m_NextPlayerButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_NextPlayerButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_NextPlayerButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_NextPlayerButton->setArmedColor ( 194, 202, 54, 0 ); + + // Initialize the bottom title. + + float flLabelSize = ( (ScreenWidth - (XRES ( CAMOPTIONS_BUTTON_X ) + 15)) - XRES ( 24 + 15 ) ) - XRES( (15 + OPTIONS_BUTTON_X + 15) + 38 ); + + m_BottomMainLabel = new Label( "Spectator Bottom", XRES( ( 15 + OPTIONS_BUTTON_X + 15 ) + 31 ), YRES(6), flLabelSize, YRES(20) ); + m_BottomMainLabel->setParent(m_BottomBorder); + m_BottomMainLabel->setPaintBackgroundEnabled(false); + m_BottomMainLabel->setFgColor( Scheme::sc_primary1 ); + m_BottomMainLabel->setContentAlignment( vgui::Label::a_center ); + m_BottomMainLabel->setBorder( new LineBorder( Color( 59, 58, 34, 48 ) ) ); + + m_InsetViewButton = new ColorButton("", XRES(2), YRES(2), XRES(240), YRES(180), false, false ); + m_InsetViewButton->setParent( this ); + m_InsetViewButton->setBoundKey( (char)255 ); + m_InsetViewButton->addActionSignal( new CSpectatorHandler_Command(this,SPECTATOR_PANEL_CMD_TOGGLE_INSET) ); + m_InsetViewButton->setUnArmedBorderColor ( 59, 58, 34, 48 ); + m_InsetViewButton->setArmedBorderColor ( 194, 202, 54, 0 ); + m_InsetViewButton->setUnArmedColor ( 143, 143, 54, 0 ); + m_InsetViewButton->setArmedColor ( 194, 202, 54, 0 ); + + + m_menuVisible = false; + m_insetVisible = false; +// m_HideButton->setVisible(false); + m_CamButton->setVisible(false); + m_OptionButton->setVisible(false); + m_NextPlayerButton->setVisible(false); + m_PrevPlayerButton->setVisible(false); + m_TopBanner->setVisible( false ); + m_ExtraInfo->setVisible( false ); + m_Separator->setVisible( false ); + m_TimerImage->setVisible( false ); + +} + +void SpectatorPanel::ShowMenu(bool isVisible) +{ +// m_HideButton->setVisible(isVisible); m_HideButton->setArmed( false ); + m_OptionButton->setVisible(isVisible); m_OptionButton->setArmed( false ); + m_CamButton->setVisible(isVisible); m_CamButton->setArmed( false ); + m_NextPlayerButton->setVisible(isVisible); m_NextPlayerButton->setArmed( false ); + m_PrevPlayerButton->setVisible(isVisible); m_PrevPlayerButton->setArmed( false ); + + if ( !isVisible ) + { + int iLabelSizeX, iLabelSizeY; + m_BottomMainLabel->getSize( iLabelSizeX, iLabelSizeY ); + m_BottomMainLabel->setPos( ( ScreenWidth / 2 ) - (iLabelSizeX/2), YRES(6) ); + } + else + m_BottomMainLabel->setPos( XRES( ( 15 + OPTIONS_BUTTON_X + 15 ) + 31 ), YRES(6) ); + + if ( !isVisible ) + { + gViewPort->HideCommandMenu(); + + // if switching from visible menu to invisible menu, show help text + if ( m_menuVisible && this->isVisible() ) + { + char string[ 64 ]; + + _snprintf( string, sizeof( string ) - 1, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( "#Spec_Duck" ) ); + string[ sizeof( string ) - 1 ] = '\0'; + + gHUD.m_TextMessage.MsgFunc_TextMsg( NULL, strlen( string ) + 1, string ); + } + } + + m_menuVisible = isVisible; + + gViewPort->UpdateCursorState(); +} + + +const char *GetSpectatorLabel ( int iMode ) +{ + switch ( iMode ) + { + case OBS_CHASE_LOCKED: + return "#OBS_CHASE_LOCKED"; + + case OBS_CHASE_FREE: + return "#OBS_CHASE_FREE"; + + case OBS_ROAMING: + return "#OBS_ROAMING"; + + case OBS_IN_EYE: + return "#OBS_IN_EYE"; + + case OBS_MAP_FREE: + return "#OBS_MAP_FREE"; + + case OBS_MAP_CHASE: + return "#OBS_MAP_CHASE"; + + case OBS_NONE: + default: + return "#OBS_NONE"; + } + + return ""; +} + +void SpectatorPanel::EnableInsetView(bool isEnabled) +{ + int x = gHUD.m_Spectator.m_OverviewData.insetWindowX; + int y = gHUD.m_Spectator.m_OverviewData.insetWindowY; + int wide = gHUD.m_Spectator.m_OverviewData.insetWindowWidth; + int tall = gHUD.m_Spectator.m_OverviewData.insetWindowHeight; + int offset = x + wide + 2; + + if ( isEnabled ) + { + // short black bar to see full inset + m_TopBorder->setBounds( XRES(offset), 0, XRES(640 - offset ), YRES(PANEL_HEIGHT) ); + + if ( gEngfuncs.IsSpectateOnly() ) + { + m_TopBanner->setVisible( true ); + m_TopBanner->setPos( XRES(offset), 0 ); + } + else + m_TopBanner->setVisible( false ); + + m_InsetViewButton->setBounds( XRES( x ), YRES( y ), + XRES( wide ), YRES( tall ) ); + m_InsetViewButton->setVisible(true); + } + else + { + // full black bar, no inset border + // show banner only in real HLTV mode + if ( gEngfuncs.IsSpectateOnly() ) + { + m_TopBanner->setVisible( true ); + m_TopBanner->setPos( 0,0 ); + } + else + m_TopBanner->setVisible( false ); + + m_TopBorder->setBounds( 0, 0, ScreenWidth, YRES(PANEL_HEIGHT) ); + + m_InsetViewButton->setVisible(false); + } + + m_insetVisible = isEnabled; + + Update(); + + m_CamButton->setText( CHudTextMessage::BufferedLocaliseTextString( GetSpectatorLabel( g_iUser1 ) ) ); +} + + + + +void SpectatorPanel::Update() +{ + int iTextWidth, iTextHeight; + int iTimeHeight, iTimeWidth; + int offset,j; + + if ( m_insetVisible ) + offset = gHUD.m_Spectator.m_OverviewData.insetWindowX + gHUD.m_Spectator.m_OverviewData.insetWindowWidth + 2; + else + offset = 0; + + bool visible = gHUD.m_Spectator.m_drawstatus->value != 0; + + m_ExtraInfo->setVisible( visible ); + m_TimerImage->setVisible( visible ); + m_CurrentTime->setVisible( visible ); + m_Separator->setVisible( visible ); + + for ( j= 0; j < TEAM_NUMBER; j++ ) + m_TeamScores[j]->setVisible( visible ); + + if ( !visible ) + return; + + m_ExtraInfo->getTextSize( iTextWidth, iTextHeight ); + m_CurrentTime->getTextSize( iTimeWidth, iTimeHeight ); + + iTimeWidth += XRES ( 14 ); // +timer icon + iTimeWidth += ( 4-(iTimeWidth%4) ); + + if ( iTimeWidth > iTextWidth ) + iTextWidth = iTimeWidth; + + int xPos = ScreenWidth - ( iTextWidth + XRES ( 4 + offset ) ); + + m_ExtraInfo->setBounds( xPos, YRES( 1 ), iTextWidth, iTextHeight ); + + m_TimerImage->setBounds( xPos, YRES( 2 ) + iTextHeight , XRES(14), YRES(14) ); + + m_CurrentTime->setBounds( xPos + XRES ( 14 + 1 ), YRES( 2 ) + iTextHeight , iTimeWidth, iTimeHeight ); + + m_Separator->setPos( ScreenWidth - ( iTextWidth + XRES ( 4+2+4+offset ) ) , YRES( 1 ) ); + m_Separator->setSize( XRES( 4 ), YRES( PANEL_HEIGHT - 2 ) ); + + for ( j= 0; j < TEAM_NUMBER; j++ ) + { + int iwidth, iheight; + + m_TeamScores[j]->getTextSize( iwidth, iheight ); + m_TeamScores[j]->setBounds( ScreenWidth - ( iTextWidth + XRES ( 4+2+4+2+offset ) + iwidth ), YRES( 1 ) + ( iheight * j ), iwidth, iheight ); + } +} diff --git a/cl_dll/vgui_SpectatorPanel.h b/cl_dll/vgui_SpectatorPanel.h new file mode 100644 index 0000000..d77e900 --- /dev/null +++ b/cl_dll/vgui_SpectatorPanel.h @@ -0,0 +1,102 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: +// +// $NoKeywords: $ +//============================================================================= + +// vgui_SpectatorPanel.h: interface for the SpectatorPanel class. +// +////////////////////////////////////////////////////////////////////// + +#ifndef SPECTATORPANEL_H +#define SPECTATORPANEL_H + +#include +#include +#include + +using namespace vgui; + +#define SPECTATOR_PANEL_CMD_NONE 0 + +#define SPECTATOR_PANEL_CMD_OPTIONS 1 +#define SPECTATOR_PANEL_CMD_PREVPLAYER 2 +#define SPECTATOR_PANEL_CMD_NEXTPLAYER 3 +#define SPECTATOR_PANEL_CMD_HIDEMENU 4 +#define SPECTATOR_PANEL_CMD_TOGGLE_INSET 5 +#define SPECTATOR_PANEL_CMD_CAMERA 6 + +#define TEAM_NUMBER 2 + +class SpectatorPanel : public Panel //, public vgui::CDefaultInputSignal +{ + +public: + SpectatorPanel(int x,int y,int wide,int tall); + virtual ~SpectatorPanel(); + + void ActionSignal(int cmd); + + // InputSignal overrides. +public: + void Initialize(); + void Update(); + + + +public: + + void EnableInsetView(bool isEnabled); + void ShowMenu(bool isVisible); + + + ColorButton * m_OptionButton; +// CommandButton * m_HideButton; + ColorButton * m_PrevPlayerButton; + ColorButton * m_NextPlayerButton; + ColorButton * m_CamButton; + + CTransparentPanel * m_TopBorder; + CTransparentPanel * m_BottomBorder; + + ColorButton *m_InsetViewButton; + + Label *m_BottomMainLabel; + CImageLabel *m_TimerImage; + Label *m_CurrentTime; + Label *m_ExtraInfo; + Panel *m_Separator; + + Label *m_TeamScores[TEAM_NUMBER]; + + CImageLabel *m_TopBanner; + + bool m_menuVisible; + bool m_insetVisible; +}; + + + +class CSpectatorHandler_Command : public ActionSignal +{ + +private: + SpectatorPanel * m_pFather; + int m_cmd; + +public: + CSpectatorHandler_Command( SpectatorPanel * panel, int cmd ) + { + m_pFather = panel; + m_cmd = cmd; + } + + virtual void actionPerformed( Panel * panel ) + { + m_pFather->ActionSignal(m_cmd); + } +}; + + +#endif // !defined SPECTATORPANEL_H diff --git a/cl_dll/vgui_TeamFortressViewport.cpp b/cl_dll/vgui_TeamFortressViewport.cpp index b25312b..20331c2 100644 --- a/cl_dll/vgui_TeamFortressViewport.cpp +++ b/cl_dll/vgui_TeamFortressViewport.cpp @@ -1,4 +1,4 @@ -//=========== (C) Copyright 1996-2001 Valve, L.L.C. All rights reserved. =========== +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== // // The copyright to the contents herein is the property of Valve, L.L.C. // The contents may be used and/or copied only with the written permission of @@ -53,6 +53,7 @@ #include "vgui_TeamFortressViewport.h" #include "vgui_ServerBrowser.h" #include "vgui_ScorePanel.h" +#include "vgui_SpectatorPanel.h" extern int g_iVisibleMouse; class CCommandMenu; @@ -75,6 +76,7 @@ int g_iUser3; void IN_ResetMouse( void ); extern CMenuPanel *CMessageWindowPanel_Create( const char *szMOTD, const char *szTitle, int iShadeFullscreen, int iRemoveMe, int x, int y, int wide, int tall ); +extern float * GetClientColor( int clientIndex ); using namespace vgui; @@ -275,25 +277,22 @@ CommandButton *CCommandMenu::FindButtonWithSubmenu( CCommandMenu *pSubMenu ) } // Recalculate the visible buttons -bool CCommandMenu::RecalculateVisibles( int iNewYPos, bool bHideAll ) +bool CCommandMenu::RecalculateVisibles( int iYOffset, bool bHideAll ) { - int iCurrentY = 0; - int iXPos, iYPos; - bool bHasButton = false; - - if (iNewYPos) - setPos( _pos[0], iNewYPos ); + int i, iCurrentY = 0; + int iVisibleButtons = 0; // Cycle through all the buttons in this menu, and see which will be visible - for (int i = 0; i < m_iButtons; i++) + for (i = 0; i < m_iButtons; i++) { int iClass = m_aButtons[i]->GetPlayerClass(); + if ( (iClass && iClass != g_iPlayerClass ) || ( m_aButtons[i]->IsNotValid() ) || bHideAll ) { m_aButtons[i]->setVisible( false ); if ( m_aButtons[i]->GetSubMenu() != NULL ) { - (m_aButtons[i]->GetSubMenu())->RecalculateVisibles( _pos[1] + iCurrentY, true ); + (m_aButtons[i]->GetSubMenu())->RecalculateVisibles( 0, true ); } } else @@ -301,7 +300,7 @@ bool CCommandMenu::RecalculateVisibles( int iNewYPos, bool bHideAll ) // If it's got a submenu, force it to check visibilities if ( m_aButtons[i]->GetSubMenu() != NULL ) { - if ( !(m_aButtons[i]->GetSubMenu())->RecalculateVisibles( _pos[1] + iCurrentY, false ) ) + if ( !(m_aButtons[i]->GetSubMenu())->RecalculateVisibles( 0 , false ) ) { // The submenu had no visible buttons, so don't display this button m_aButtons[i]->setVisible( false ); @@ -310,53 +309,88 @@ bool CCommandMenu::RecalculateVisibles( int iNewYPos, bool bHideAll ) } m_aButtons[i]->setVisible( true ); - - // Make sure it's at the right Y position - m_aButtons[i]->getPos( iXPos, iYPos ); - m_aButtons[i]->setPos( iXPos, iCurrentY ); - - iCurrentY += BUTTON_SIZE_Y - 1; - bHasButton = true; + iVisibleButtons++; } } // Set Size - setSize( _size[0], iCurrentY + 1 ); + setSize( _size[0], (iVisibleButtons * (m_flButtonSizeY-1)) + 1 ); - return bHasButton; + if ( iYOffset ) + { + m_iYOffset = iYOffset; + } + + for (i = 0; i < m_iButtons; i++) + { + if ( m_aButtons[i]->isVisible() ) + { + if ( m_aButtons[i]->GetSubMenu() != NULL ) + (m_aButtons[i]->GetSubMenu())->RecalculateVisibles( iCurrentY + m_iYOffset, false ); + + + // Make sure it's at the right Y position + // m_aButtons[i]->getPos( iXPos, iYPos ); + + if ( m_iDirection ) + { + m_aButtons[i]->setPos( 0, (iVisibleButtons-1) * (m_flButtonSizeY-1) - iCurrentY ); + } + else + { + m_aButtons[i]->setPos( 0, iCurrentY ); + } + + iCurrentY += (m_flButtonSizeY-1); + } + } + + return iVisibleButtons?true:false; } // Make sure all submenus can fit on the screen void CCommandMenu::RecalculatePositions( int iYOffset ) { - int iNewYPos = _pos[1] + iYOffset; + int iTop; int iAdjust = 0; + m_iYOffset+= iYOffset; + + if ( m_iDirection ) + iTop = ScreenHeight - (m_iYOffset + _size[1] ); + else + iTop = m_iYOffset; + + if ( iTop < 0 ) + iTop = 0; + // Calculate if this is going to fit onscreen, and shuffle it up if it won't - int iBottom = iNewYPos + _size[1]; + int iBottom = iTop + _size[1]; + if ( iBottom > ScreenHeight ) { // Move in increments of button sizes while (iAdjust < (iBottom - ScreenHeight)) { - iAdjust += BUTTON_SIZE_Y - 1; + iAdjust += m_flButtonSizeY - 1; } - iNewYPos -= iAdjust; + + iTop -= iAdjust; // Make sure it doesn't move off the top of the screen (the menu's too big to fit it all) - if ( iNewYPos < 0 ) + if ( iTop < 0 ) { - iAdjust -= (0 - iNewYPos); - iNewYPos = 0; + iAdjust -= (0 - iTop); + iTop = 0; } } + setPos( _pos[0], iTop ); + // We need to force all menus below this one to update their positions now, because they // might have submenus riding off buttons in this menu that have just shifted. for (int i = 0; i < m_iButtons; i++) m_aButtons[i]->UpdateSubMenus( iAdjust ); - - setPos( _pos[0], iNewYPos ); } @@ -385,23 +419,26 @@ void CCommandMenu::MakeVisible( CCommandMenu *pChildMenu ) //================================================================ // CreateSubMenu -CCommandMenu *TeamFortressViewport::CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu ) +CCommandMenu *TeamFortressViewport::CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu, int iYOffset, int iXOffset ) { int iXPos = 0; int iYPos = 0; int iWide = CMENU_SIZE_X; int iTall = 0; + int iDirection = 0; if (pParentMenu) { - iXPos = pParentMenu->GetXOffset() + CMENU_SIZE_X - 1; - iYPos = pParentMenu->GetYOffset() + BUTTON_SIZE_Y * (m_pCurrentCommandMenu->GetNumButtons() - 1); + iXPos = m_pCurrentCommandMenu->GetXOffset() + (CMENU_SIZE_X - 1) + iXOffset; + iYPos = m_pCurrentCommandMenu->GetYOffset() + iYOffset; + iDirection = pParentMenu->GetDirection(); } - CCommandMenu *pMenu = new CCommandMenu(pParentMenu, iXPos, iYPos, iWide, iTall ); + CCommandMenu *pMenu = new CCommandMenu(pParentMenu, iDirection, iXPos, iYPos, iWide, iTall ); pMenu->setParent(this); pButton->AddSubMenu( pMenu ); pButton->setFont( Scheme::sf_primary3 ); + pMenu->m_flButtonSizeY = m_pCurrentCommandMenu->m_flButtonSizeY; // Create the Submenu-open signal InputSignal *pISignal = new CMenuHandler_PopupSubMenuInput(pButton, pMenu); @@ -481,7 +518,7 @@ TeamFortressViewport::TeamFortressViewport(int x,int y,int wide,int tall) : Pane m_pTeamMenu = NULL; m_pClassMenu = NULL; m_pScoreBoard = NULL; - m_pSpectatorMenu = NULL; + m_pSpectatorPanel = NULL; m_pCurrentMenu = NULL; m_pCurrentCommandMenu = NULL; @@ -540,10 +577,16 @@ TeamFortressViewport::TeamFortressViewport(int x,int y,int wide,int tall) : Pane // VGUI MENUS CreateTeamMenu(); CreateClassMenu(); - CreateScoreBoard(); - CreateCommandMenu(); - CreateServerBrowser(); CreateSpectatorMenu(); + CreateScoreBoard(); + // Init command menus + m_iNumMenus = 0; + m_iCurrentTeamNumber = m_iUser1 = m_iUser2 = m_iUser3 = 0; + + m_StandardMenu = CreateCommandMenu("commandmenu.txt", 0, CMENU_TOP, false, CMENU_SIZE_X, BUTTON_SIZE_Y, 0 ); + m_SpectatorOptionsMenu = CreateCommandMenu("spectatormenu.txt", 1, YRES(32), true, CMENU_SIZE_X, BUTTON_SIZE_Y / 2, 0 ); // above bottom bar, flat design + m_SpectatorCameraMenu = CreateCommandMenu("spectcammenu.txt", 1, YRES(32), true, XRES( 200 ), BUTTON_SIZE_Y / 2, ScreenWidth - ( XRES ( 200 ) + 15 ) ); // above bottom bar, flat design + CreateServerBrowser(); } //----------------------------------------------------------------------------- @@ -565,10 +608,10 @@ void TeamFortressViewport::Initialize( void ) m_pScoreBoard->Initialize(); HideScoreBoard(); } - if (m_pSpectatorMenu) + if (m_pSpectatorPanel) { // Spectator menu doesn't need initializing - m_pSpectatorMenu->setVisible( false ); + m_pSpectatorPanel->setVisible( false ); } // Make sure all menus are hidden @@ -579,6 +622,7 @@ void TeamFortressViewport::Initialize( void ) m_iGotAllMOTD = true; m_iRandomPC = false; m_flScoreBoardLastUpdated = 0; + m_flSpectatorPanelLastUpdated = 0; // reset player info g_iPlayerClass = 0; @@ -598,25 +642,31 @@ void TeamFortressViewport::Initialize( void ) class CException; //----------------------------------------------------------------------------- // Purpose: Read the Command Menu structure from the txt file and create the menu. +// Returns Index of menu in m_pCommandMenus //----------------------------------------------------------------------------- -void TeamFortressViewport::CreateCommandMenu( void ) +int TeamFortressViewport::CreateCommandMenu( char * menuFile, int direction, int yOffset, bool flatDesign, float flButtonSizeX, float flButtonSizeY, int xOffset ) { // COMMAND MENU - // Create the root of the Command Menu - m_pCommandMenus[0] = new CCommandMenu(NULL, 0, CMENU_TOP, CMENU_SIZE_X, 300); // This will be resized once we know how many items are in it - m_pCommandMenus[0]->setParent(this); - m_pCommandMenus[0]->setVisible(false); - m_iNumMenus = 1; - m_iCurrentTeamNumber = m_iUser1 = m_iUser2 = m_iUser3 = 0; + // Create the root of this new Command Menu + + int newIndex = m_iNumMenus; + + m_pCommandMenus[newIndex] = new CCommandMenu(NULL, direction, xOffset, yOffset, flButtonSizeX, 300); // This will be resized once we know how many items are in it + m_pCommandMenus[newIndex]->setParent(this); + m_pCommandMenus[newIndex]->setVisible(false); + m_pCommandMenus[newIndex]->m_flButtonSizeY = flButtonSizeY; + m_pCommandMenus[newIndex]->m_iSpectCmdMenu = direction; + + m_iNumMenus++; // Read Command Menu from the txt file char token[1024]; - char *pfile = (char*)gEngfuncs.COM_LoadFile("commandmenu.txt", 5, NULL); + char *pfile = (char*)gEngfuncs.COM_LoadFile( menuFile, 5, NULL); if (!pfile) { - gEngfuncs.Con_DPrintf( "Unable to open commandmenu.txt\n"); + gEngfuncs.Con_DPrintf( "Unable to open %s\n", menuFile); SetCurrentCommandMenu( NULL ); - return; + return newIndex; } try @@ -629,7 +679,7 @@ try gHUD.m_TextMessage.LocaliseTextString( "#DetpackSet_For50Seconds", m_sDetpackStrings[2], MAX_BUTTON_SIZE ); // Now start parsing the menu structure - m_pCurrentCommandMenu = m_pCommandMenus[0]; + m_pCurrentCommandMenu = m_pCommandMenus[newIndex]; char szLastButtonText[32] = "file start"; pfile = gEngfuncs.COM_ParseFile(pfile, token); while ( ( strlen ( token ) > 0 ) && ( m_iNumMenus < MAX_MENUS ) ) @@ -645,16 +695,18 @@ try char szMap[MAX_MAPNAME] = ""; int iPlayerClass = 0; int iCustom = false; - int iTeamOnly = 0; + int iTeamOnly = -1; + int iToggle = 0; + int iButtonY; bool bGetExtraToken = true; CommandButton *pButton = NULL; // We should never be here without a Command Menu if (!m_pCurrentCommandMenu) { - gEngfuncs.Con_Printf("Error in Commandmenu.txt file after '%s'.\n", szLastButtonText ); + gEngfuncs.Con_Printf("Error in %s file after '%s'.\n",menuFile, szLastButtonText ); m_iInitialized = false; - return; + return newIndex; } // token should already be the bound key, or the custom name @@ -688,6 +740,12 @@ try // Get the next token pfile = gEngfuncs.COM_ParseFile(pfile, token); } + else if ( !strncmp(cCustom, "TOGGLE", 6) ) + { + iToggle = true; + // Get the next token + pfile = gEngfuncs.COM_ParseFile(pfile, token); + } else { // See if it's a Class @@ -722,10 +780,12 @@ try strncpy( cCommand, token, cCommandLength ); cCommand[cCommandLength - 1] = '\0'; + iButtonY = (BUTTON_SIZE_Y-1) * m_pCurrentCommandMenu->GetNumButtons(); + // Custom button handling if ( iCustom ) { - pButton = CreateCustomButton( cText, cCommand ); + pButton = CreateCustomButton( cText, cCommand, iButtonY ); // Get the next token to see if we're a menu pfile = gEngfuncs.COM_ParseFile(pfile, token); @@ -742,17 +802,28 @@ try else if ( szMap[0] != '\0' ) { // create a map button - pButton = new MapButton(szMap, cText,0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y); + pButton = new MapButton(szMap, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY ); } - else if ( iTeamOnly ) + else if ( iTeamOnly != -1) { // button that only shows up if the player is on team iTeamOnly - pButton = new TeamOnlyCommandButton( iTeamOnly, cText,0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y ); + pButton = new TeamOnlyCommandButton( iTeamOnly, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + } + else if ( iToggle && direction == 0 ) + { + pButton = new ToggleCommandButton( cCommand, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + } + else if ( direction == 1 ) + { + if ( iToggle ) + pButton = new SpectToggleButton( cCommand, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); + else + pButton = new SpectButton( iPlayerClass, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY ); } else { // normal button - pButton = new CommandButton( iPlayerClass, cText,0, BUTTON_SIZE_Y * m_pCurrentCommandMenu->GetNumButtons(), CMENU_SIZE_X, BUTTON_SIZE_Y ); + pButton = new CommandButton( iPlayerClass, cText, xOffset, iButtonY, flButtonSizeX, flButtonSizeY, flatDesign ); } // add the button into the command menu @@ -771,12 +842,12 @@ try { if ( m_iNumMenus >= MAX_MENUS ) { - gEngfuncs.Con_Printf( "Too many menus in commandmenu.txt past '%s'\n", szLastButtonText ); + gEngfuncs.Con_Printf( "Too many menus in %s past '%s'\n",menuFile, szLastButtonText ); } else { // Create the menu - m_pCommandMenus[m_iNumMenus] = CreateSubMenu(pButton, m_pCurrentCommandMenu); + m_pCommandMenus[m_iNumMenus] = CreateSubMenu(pButton, m_pCurrentCommandMenu, iButtonY ); m_pCurrentCommandMenu = m_pCommandMenus[m_iNumMenus]; m_iNumMenus++; } @@ -784,7 +855,10 @@ try else if ( !iCustom ) { // Create the button and attach it to the current menu - pButton->addActionSignal(new CMenuHandler_StringCommand(cCommand)); + if ( iToggle ) + pButton->addActionSignal(new CMenuHandler_ToggleCvar(cCommand)); + else + pButton->addActionSignal(new CMenuHandler_StringCommand(cCommand)); // Create an input signal that'll popup the current menu pButton->addInputSignal( new CMenuHandler_PopupSubMenuInput(pButton, m_pCurrentCommandMenu) ); } @@ -808,7 +882,7 @@ catch( CException *e ) //e->Delete(); e = NULL; m_iInitialized = false; - return; + return newIndex; } SetCurrentMenu( NULL ); @@ -816,6 +890,7 @@ catch( CException *e ) gEngfuncs.COM_FreeFile( pfile ); m_iInitialized = true; + return newIndex; } //----------------------------------------------------------------------------- @@ -823,10 +898,10 @@ catch( CException *e ) // maps a command to them // Output : CCommandMenu //----------------------------------------------------------------------------- -CCommandMenu *TeamFortressViewport::CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText ) +CCommandMenu *TeamFortressViewport::CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText, int iYOffset, int iXOffset ) { // create the submenu, under which the class choices will be listed - CCommandMenu *pMenu = CreateSubMenu( pButton, pParentMenu ); + CCommandMenu *pMenu = CreateSubMenu( pButton, pParentMenu, iYOffset, iXOffset ); m_pCommandMenus[m_iNumMenus] = pMenu; m_iNumMenus++; @@ -851,7 +926,7 @@ CCommandMenu *TeamFortressViewport::CreateDisguiseSubmenu( CommandButton *pButto // *pButtonName - // Output : CommandButton //----------------------------------------------------------------------------- -CommandButton *TeamFortressViewport::CreateCustomButton( char *pButtonText, char *pButtonName ) +CommandButton *TeamFortressViewport::CreateCustomButton( char *pButtonText, char *pButtonName, int iYOffset ) { CommandButton *pButton = NULL; CCommandMenu *pMenu = NULL; @@ -863,7 +938,7 @@ CommandButton *TeamFortressViewport::CreateCustomButton( char *pButtonText, char pButton = new CommandButton(pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); // Create the submenu - pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu); + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu, iYOffset ); m_pCommandMenus[m_iNumMenus] = pMenu; m_iNumMenus++; @@ -894,7 +969,7 @@ CommandButton *TeamFortressViewport::CreateCustomButton( char *pButtonText, char pButton = new ClassButton(-1, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y, false); // ChangeClass Submenu - pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu); + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu, iYOffset ); m_pCommandMenus[m_iNumMenus] = pMenu; m_iNumMenus++; @@ -968,19 +1043,19 @@ CommandButton *TeamFortressViewport::CreateCustomButton( char *pButtonText, char { // Create the disguise enemy button, which active only if there are 2 teams pButton = new DisguiseButton(DISGUISE_TEAM2, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); - CreateDisguiseSubmenu( pButton, m_pCurrentCommandMenu, "disguise_enemy" ); + CreateDisguiseSubmenu( pButton, m_pCurrentCommandMenu, "disguise_enemy", iYOffset); } else if ( !strcmp( pButtonName, "!DISGUISEFRIENDLY" ) ) { // Create the disguise friendly button, which active only if there are 1 or 2 teams pButton = new DisguiseButton(DISGUISE_TEAM1 | DISGUISE_TEAM2, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); - CreateDisguiseSubmenu( pButton, m_pCurrentCommandMenu, "disguise_friendly" ); + CreateDisguiseSubmenu( pButton, m_pCurrentCommandMenu, "disguise_friendly", iYOffset ); } else if ( !strcmp( pButtonName, "!DISGUISE" ) ) { // Create the Disguise button pButton = new DisguiseButton( DISGUISE_TEAM3 | DISGUISE_TEAM4, pButtonText, 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); - CCommandMenu *pDisguiseMenu = CreateSubMenu( pButton, m_pCurrentCommandMenu ); + CCommandMenu *pDisguiseMenu = CreateSubMenu( pButton, m_pCurrentCommandMenu, iYOffset ); m_pCommandMenus[m_iNumMenus] = pDisguiseMenu; m_iNumMenus++; @@ -991,10 +1066,11 @@ CommandButton *TeamFortressViewport::CreateCustomButton( char *pButtonText, char m_pDisguiseButtons[i] = new DisguiseButton( ((i < 4) ? DISGUISE_TEAM3 : 0) | DISGUISE_TEAM4, "Disguise", 0, BUTTON_SIZE_Y, CMENU_SIZE_X, BUTTON_SIZE_Y); pDisguiseMenu->AddButton( m_pDisguiseButtons[i] ); + m_pDisguiseButtons[i]->setParentMenu( pDisguiseMenu ); char sz[256]; sprintf( sz, "disguise %d", i ); - CreateDisguiseSubmenu( m_pDisguiseButtons[i], pDisguiseMenu, sz ); + CreateDisguiseSubmenu( m_pDisguiseButtons[i], pDisguiseMenu, sz, iYOffset, CMENU_SIZE_X - 1 ); } } // Start setting a Detpack @@ -1004,7 +1080,7 @@ CommandButton *TeamFortressViewport::CreateCustomButton( char *pButtonText, char pButton = new DetpackButton(2, pButtonText, 0, BUTTON_SIZE_Y * 2, CMENU_SIZE_X, BUTTON_SIZE_Y); // Create the submenu - pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu); + pMenu = CreateSubMenu(pButton, m_pCurrentCommandMenu, iYOffset ); m_pCommandMenus[m_iNumMenus] = pMenu; m_iNumMenus++; @@ -1123,17 +1199,17 @@ void TeamFortressViewport::ToggleServerBrowser() } //======================================================================= -void TeamFortressViewport::ShowCommandMenu() +void TeamFortressViewport::ShowCommandMenu(int menuIndex) { if (!m_iInitialized) return; - // Not visible while undefined - if (g_iPlayerClass == 0) + //Already have a menu open. + if ( m_pCurrentMenu ) return; // is the command menu open? - if ( m_pCurrentCommandMenu ) + if ( m_pCurrentCommandMenu == m_pCommandMenus[menuIndex] ) { HideCommandMenu(); return; @@ -1144,10 +1220,10 @@ void TeamFortressViewport::ShowCommandMenu() return; // Recalculate visible menus - UpdateCommandMenu(); + UpdateCommandMenu( menuIndex ); HideVGUIMenu(); - SetCurrentCommandMenu( m_pCommandMenus[0] ); + SetCurrentCommandMenu( m_pCommandMenus[menuIndex] ); m_flMenuOpenTime = gHUD.m_flTime; UpdateCursorState(); @@ -1185,14 +1261,24 @@ void TeamFortressViewport::InputSignalHideCommandMenu() //----------------------------------------------------------------------------- // Purpose: Hides the command menu //----------------------------------------------------------------------------- -void TeamFortressViewport::HideCommandMenu( void ) +void TeamFortressViewport::HideCommandMenu() { if (!m_iInitialized) return; - if ( m_pCommandMenus[0] ) + if ( m_pCommandMenus[m_StandardMenu] ) { - m_pCommandMenus[0]->ClearButtonsOfArmedState(); + m_pCommandMenus[m_StandardMenu]->ClearButtonsOfArmedState(); + } + + if ( m_pCommandMenus[m_SpectatorOptionsMenu] ) + { + m_pCommandMenus[m_SpectatorOptionsMenu]->ClearButtonsOfArmedState(); + } + + if ( m_pCommandMenus[m_SpectatorCameraMenu] ) + { + m_pCommandMenus[m_SpectatorCameraMenu]->ClearButtonsOfArmedState(); } m_flMenuOpenTime = 0.0f; @@ -1257,7 +1343,7 @@ void TeamFortressViewport::InputPlayerSpecial( void ) if ( g_iPlayerClass == PC_ENGINEER || g_iPlayerClass == PC_SPY ) { - ShowCommandMenu(); + ShowCommandMenu( gViewPort->m_StandardMenu ); if ( m_pCurrentCommandMenu ) { @@ -1283,74 +1369,142 @@ void TeamFortressViewport::SetCurrentCommandMenu( CCommandMenu *pNewMenu ) m_pCurrentCommandMenu->MakeVisible( NULL ); } -void TeamFortressViewport::UpdateCommandMenu() +void TeamFortressViewport::UpdateCommandMenu(int menuIndex) { - m_pCommandMenus[0]->RecalculateVisibles( 0, false ); - m_pCommandMenus[0]->RecalculatePositions( 0 ); + m_pCommandMenus[menuIndex]->RecalculateVisibles( 0, false ); + m_pCommandMenus[menuIndex]->RecalculatePositions( 0 ); } -void TeamFortressViewport::UpdateSpectatorMenu() -{ - char helpString1[128]; - char helpString2[128]; - int mode; +void COM_FileBase ( const char *in, char *out); +void TeamFortressViewport::UpdateSpectatorPanel() +{ m_iUser1 = g_iUser1; m_iUser2 = g_iUser2; m_iUser3 = g_iUser3; - - if (!m_pSpectatorMenu) + + if (!m_pSpectatorPanel) return; - if ( gEngfuncs.IsSpectateOnly() ) + if ( g_iUser1 && gHUD.m_pCvarDraw->value && !gHUD.m_iIntermission) // don't draw in dev_overview mode { - mode = gHUD.m_Spectator.m_iMainMode; // spec mode is set client side + char bottomText[128]; + char helpString2[128]; + char tempString[128]; + char * name; + int player = 0; - sprintf(helpString2, "#Spec_Only_Help"); - } - else - { - // spec mode is given by server - mode = m_iUser1; - // set normal help text - sprintf(helpString2, "#Spec_Help" ); - } + // check if spectator combinations are still valid + gHUD.m_Spectator.CheckSettings(); - if ( mode && ( gEngfuncs.IsSpectateOnly() != 2) ) // don't draw in dev_overview mode - { - m_pSpectatorMenu->setVisible( true ); + if ( !m_pSpectatorPanel->isVisible() ) + { + m_pSpectatorPanel->setVisible( true ); // show spectator panel, but + m_pSpectatorPanel->ShowMenu( false ); // dsiable all menus/buttons + + _snprintf( tempString, sizeof( tempString ) - 1, "%c%s", HUD_PRINTCENTER, CHudTextMessage::BufferedLocaliseTextString( "#Spec_Duck" ) ); + tempString[ sizeof( tempString ) - 1 ] = '\0'; + + gHUD.m_TextMessage.MsgFunc_TextMsg( NULL, strlen( tempString ) + 1, tempString ); + } + + sprintf(bottomText,"#Spec_Mode%d", g_iUser1 ); + sprintf(helpString2,"#Spec_Mode%d", g_iUser1 ); + + if ( gEngfuncs.IsSpectateOnly() ) + strcat(helpString2, " - HLTV"); // check if we're locked onto a target, show the player's name - if ( (m_iUser2 > 0) && (m_iUser2 <= gEngfuncs.GetMaxClients()) ) + if ( (g_iUser2 > 0) && (g_iUser2 <= gEngfuncs.GetMaxClients()) && (g_iUser1 != OBS_ROAMING) ) { - // Locked onto a target, show the player's name + player = g_iUser2; + } + + // special case in free map and inset off, don't show names + if ( (g_iUser1 == OBS_MAP_FREE) && !gHUD.m_Spectator.m_pip->value ) + name = NULL; + else + name = g_PlayerInfoList[player].name; + + // create player & health string + if ( player && name ) + { + strcpy( bottomText, name ); + } + + // in first person mode colorize player names + if ( (g_iUser1 == OBS_IN_EYE) && player ) + { + float * color = GetClientColor( player ); + int r = color[0]*255; + int g = color[1]*255; + int b = color[2]*255; + // set team color, a bit transparent + m_pSpectatorPanel->m_BottomMainLabel->setFgColor(r,g,b,0); + } + else + { // restore GUI color + m_pSpectatorPanel->m_BottomMainLabel->setFgColor( 143, 143, 54, 0 ); + } - if ( g_PlayerInfoList[ m_iUser2 ].name != NULL ) - sprintf(helpString1, "#Spec_Mode%d : %s", mode, g_PlayerInfoList[ m_iUser2 ].name ); + // add sting auto if we are in auto directed mode + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + char tempString[128]; + sprintf(tempString, "#Spec_Auto %s", helpString2); + strcpy( helpString2, tempString ); + } - else - sprintf(helpString1, "#Spec_Mode%d", mode ); + m_pSpectatorPanel->m_BottomMainLabel->setText( CHudTextMessage::BufferedLocaliseTextString( bottomText ) ); + + + // update extra info field + char szText[64]; + + if ( gEngfuncs.IsSpectateOnly() ) + { + // in HLTV mode show number of spectators + _snprintf( szText, 63, "%s: %d", CHudTextMessage::BufferedLocaliseTextString( "#Spectators" ), gHUD.m_Spectator.m_iSpectatorNumber ); } else { - sprintf(helpString1, "#Spec_Mode%d", mode); + // otherwise show map name + char szMapName[64]; + COM_FileBase( gEngfuncs.pfnGetLevelName(), szMapName ); + + _snprintf ( szText, 63, "%s: %s",CHudTextMessage::BufferedLocaliseTextString( "#Spec_Map" ), szMapName ); } - if ( m_iUser1 == OBS_DIRECTED ) - { - char tempString[128]; - sprintf(tempString, "#Directed %s", helpString1); - strcpy( helpString1, tempString ); - } + szText[63] = 0; + + m_pSpectatorPanel->m_ExtraInfo->setText ( szText ); - m_pSpectatorLabel->setText( CHudTextMessage::BufferedLocaliseTextString( helpString1 ) ); - m_pSpectatorHelpLabel->setText( CHudTextMessage::BufferedLocaliseTextString( helpString2 ) ); + /* + int timer = (int)( gHUD.m_roundTimer.m_flTimeEnd - gHUD.m_flTime ); + + if ( timer < 0 ) + timer = 0; + + _snprintf ( szText, 63, "%d:%02d\n", (timer / 60), (timer % 60) ); + + szText[63] = 0; + + m_pSpectatorPanel->m_CurrentTime->setText( szText ); */ + + // update spectator panel + gViewPort->m_pSpectatorPanel->Update(); } else { - m_pSpectatorMenu->setVisible( false ); + if ( m_pSpectatorPanel->isVisible() ) + { + m_pSpectatorPanel->setVisible( false ); + m_pSpectatorPanel->ShowMenu( false ); // dsiable all menus/buttons + } } + + m_flSpectatorPanelLastUpdated = gHUD.m_flTime + 1.0; // update every seconds } //====================================================================== @@ -1503,6 +1657,17 @@ CMenuPanel* TeamFortressViewport::CreateTextWindow( int iTextToShow ) cText = pfile; } } + else if ( iTextToShow == SHOW_SPECHELP ) + { + CHudTextMessage::LocaliseTextString( "#Spec_Help_Title", cTitle, MAX_TITLE_LENGTH ); + cTitle[MAX_TITLE_LENGTH-1] = 0; + + char *pfile = CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help_Text" ); + if ( pfile ) + { + cText = pfile; + } + } // if we're in the game (ie. have selected a class), flag the menu to be only grayed in the dialog box, instead of full screen CMenuPanel *pMOTDPanel = CMessageWindowPanel_Create( cText, cTitle, g_iPlayerClass == PC_UNDEFINED, false, 0, 0, ScreenWidth, ScreenHeight ); @@ -1561,6 +1726,9 @@ void TeamFortressViewport::ShowVGUIMenu( int iMenu ) pNewMenu = CreateTextWindow( SHOW_CLASSDESC ); break; + case MENU_SPECHELP: + pNewMenu = CreateTextWindow( SHOW_SPECHELP ); + break; case MENU_CLASS: pNewMenu = ShowClassMenu(); break; @@ -1686,6 +1854,7 @@ void TeamFortressViewport::CreateClassMenu() m_pClassMenu->setVisible( false ); } +//====================================================================================== //====================================================================================== // SPECTATOR MENU //====================================================================================== @@ -1693,49 +1862,10 @@ void TeamFortressViewport::CreateClassMenu() void TeamFortressViewport::CreateSpectatorMenu() { // Create the Panel - m_pSpectatorMenu = new CTransparentPanel(100, 0, ScreenHeight - YRES(60), ScreenWidth, YRES(60)); - m_pSpectatorMenu->setParent(this); - m_pSpectatorMenu->setVisible(false); - - // Get the scheme used for the Titles - CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); - - // schemes - SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); - SchemeHandle_t hHelpText = pSchemes->getSchemeHandle( "Primary Button Text" ); - - // color schemes - int r, g, b, a; - - // Create the title - m_pSpectatorLabel = new Label( "Spectator", 0, 0, ScreenWidth, YRES(25) ); - m_pSpectatorLabel->setParent( m_pSpectatorMenu ); - m_pSpectatorLabel->setFont( pSchemes->getFont(hTitleScheme) ); - pSchemes->getFgColor( hTitleScheme, r, g, b, a ); - m_pSpectatorLabel->setFgColor( r, g, b, a ); - pSchemes->getBgColor( hTitleScheme, r, g, b, a ); - m_pSpectatorLabel->setBgColor( r, g, b, 255 ); - m_pSpectatorLabel->setContentAlignment( vgui::Label::a_north ); - - // Create the Help - m_pSpectatorHelpLabel = new Label( CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help" ), 0, YRES(25), ScreenWidth, YRES(15) ); - m_pSpectatorHelpLabel->setParent( m_pSpectatorMenu ); - m_pSpectatorHelpLabel->setFont( pSchemes->getFont(hHelpText) ); - pSchemes->getFgColor( hHelpText, r, g, b, a ); - m_pSpectatorHelpLabel->setFgColor( r, g, b, a ); - pSchemes->getBgColor( hHelpText, r, g, b, a ); - m_pSpectatorHelpLabel->setBgColor( r, g, b, 255 ); - m_pSpectatorHelpLabel->setContentAlignment( vgui::Label::a_north ); - - Label *pLabel = new Label( CHudTextMessage::BufferedLocaliseTextString( "#Spec_Help2" ), 0, YRES(40), ScreenWidth, YRES(20) ); - pLabel->setParent( m_pSpectatorMenu ); - pLabel->setFont( pSchemes->getFont(hHelpText) ); - pSchemes->getFgColor( hHelpText, r, g, b, a ); - pLabel->setFgColor( r, g, b, a ); - pSchemes->getBgColor( hHelpText, r, g, b, a ); - pLabel->setBgColor( r, g, b, 255 ); - pLabel->setContentAlignment( vgui::Label::a_center ); - + m_pSpectatorPanel = new SpectatorPanel(0, 0, ScreenWidth, ScreenHeight); + m_pSpectatorPanel->setParent(this); + m_pSpectatorPanel->setVisible(false); + m_pSpectatorPanel->Initialize(); } //====================================================================================== @@ -1756,7 +1886,7 @@ void TeamFortressViewport::UpdateOnPlayerInfo() void TeamFortressViewport::UpdateCursorState() { // Need cursor if any VGUI window is up - if ( m_pCurrentMenu || m_pTeamMenu->isVisible() || m_pServerBrowser->isVisible() || GetClientVoiceMgr()->IsInSquelchMode() ) + if ( m_pSpectatorPanel->m_menuVisible || m_pCurrentMenu || m_pTeamMenu->isVisible() || m_pServerBrowser->isVisible() || GetClientVoiceMgr()->IsInSquelchMode() ) { g_iVisibleMouse = true; App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::SchemeCursor::scu_arrow) ); @@ -1773,7 +1903,12 @@ void TeamFortressViewport::UpdateCursorState() } } - IN_ResetMouse(); + // Don't reset mouse in demo playback + if ( !gEngfuncs.pDemoAPI->IsPlayingback() ) + { + IN_ResetMouse(); + } + g_iVisibleMouse = false; App::getInstance()->setCursorOveride( App::getInstance()->getScheme()->getCursor(Scheme::SchemeCursor::scu_none) ); } @@ -1807,7 +1942,7 @@ void TeamFortressViewport::paintBackground() // See if the command menu is visible and needs recalculating due to some external change if ( g_iTeamNumber != m_iCurrentTeamNumber ) { - UpdateCommandMenu(); + UpdateCommandMenu( m_StandardMenu ); if ( m_pClassMenu ) { @@ -1819,16 +1954,16 @@ void TeamFortressViewport::paintBackground() if ( g_iPlayerClass != m_iCurrentPlayerClass ) { - UpdateCommandMenu(); + UpdateCommandMenu( m_StandardMenu ); m_iCurrentPlayerClass = g_iPlayerClass; } // See if the Spectator Menu needs to be update - // no update if only second target (m_iUser3) changes - if ( g_iUser1 != m_iUser1 || g_iUser2 != m_iUser2 ) + if ( ( g_iUser1 != m_iUser1 || g_iUser2 != m_iUser2 ) || + ( m_flSpectatorPanelLastUpdated < gHUD.m_flTime ) ) { - UpdateSpectatorMenu(); + UpdateSpectatorPanel(); } // Update the Scoreboard, if it's visible @@ -1987,7 +2122,7 @@ int TeamFortressViewport::MsgFunc_ValClass(const char *pszName, int iSize, void m_iValidClasses[i] = READ_SHORT(); // Force the menu to update - UpdateCommandMenu(); + UpdateCommandMenu( m_StandardMenu ); return 1; } @@ -2031,7 +2166,7 @@ int TeamFortressViewport::MsgFunc_Feign(const char *pszName, int iSize, void *pb m_iIsFeigning = READ_BYTE(); // Force the menu to update - UpdateCommandMenu(); + UpdateCommandMenu( m_StandardMenu ); return 1; } @@ -2043,7 +2178,7 @@ int TeamFortressViewport::MsgFunc_Detpack(const char *pszName, int iSize, void * m_iIsSettingDetpack = READ_BYTE(); // Force the menu to update - UpdateCommandMenu(); + UpdateCommandMenu( m_StandardMenu ); return 1; } @@ -2081,7 +2216,8 @@ int TeamFortressViewport::MsgFunc_MOTD( const char *pszName, int iSize, void *pb strncat( m_szMOTD, READ_STRING(), roomInArray >= 0 ? roomInArray : 0 ); m_szMOTD[ sizeof(m_szMOTD)-1 ] = '\0'; - if ( m_iGotAllMOTD ) + // don't show MOTD for HLTV spectators + if ( m_iGotAllMOTD && !gEngfuncs.IsSpectateOnly() ) { ShowVGUIMenu( MENU_INTRO ); } @@ -2096,7 +2232,7 @@ int TeamFortressViewport::MsgFunc_BuildSt( const char *pszName, int iSize, void m_iBuildState = READ_BYTE(); // Force the menu to update - UpdateCommandMenu(); + UpdateCommandMenu( m_StandardMenu ); return 1; } @@ -2223,7 +2359,7 @@ int TeamFortressViewport::MsgFunc_AllowSpec( const char *pszName, int iSize, voi m_iAllowSpectators = READ_BYTE(); // Force the menu to update - UpdateCommandMenu(); + UpdateCommandMenu( m_StandardMenu ); // If the team menu is up, update it too if (m_pTeamMenu) diff --git a/cl_dll/vgui_TeamFortressViewport.h b/cl_dll/vgui_TeamFortressViewport.h index a74d67b..c87258a 100644 --- a/cl_dll/vgui_TeamFortressViewport.h +++ b/cl_dll/vgui_TeamFortressViewport.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -38,6 +38,7 @@ using namespace vgui; class Cursor; class ScorePanel; +class SpectatorPanel; class CCommandMenu; class CommandLabel; class CommandButton; @@ -114,6 +115,7 @@ public: BitmapTGA *m_pTGA; public: + void LoadImage(const char * pImageName); CImageLabel( const char* pImageName,int x,int y ); CImageLabel( const char* pImageName,int x,int y,int wide,int tall ); @@ -158,6 +160,7 @@ class CommandButton : public Button { private: int m_iPlayerClass; + bool m_bFlat; // Submenus under this button CCommandMenu *m_pSubMenu; @@ -175,9 +178,10 @@ public: bool m_bNoHighlight; public: + CommandButton(const char* text,int x,int y,int wide,int tall, bool bNoHighlight, bool bFlat); // Constructors CommandButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight = false); - CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall); + CommandButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall, bool bFlat ); void Init( void ); @@ -212,6 +216,142 @@ public: char getBoundKey( void ); }; +class ColorButton : public CommandButton +{ +private: + + Color *ArmedColor; + Color *UnArmedColor; + + Color *ArmedBorderColor; + Color *UnArmedBorderColor; + +public: + ColorButton( const char* text,int x,int y,int wide,int tall, bool bNoHighlight, bool bFlat ) : + CommandButton( text, x, y, wide, tall, bNoHighlight, bFlat ) + { + ArmedColor = NULL; + UnArmedColor = NULL; + ArmedBorderColor = NULL; + UnArmedBorderColor = NULL; + } + + + virtual void paintBackground() + { + int r, g, b, a; + Color bgcolor; + + if ( isArmed() ) + { + // Highlight background + /* getBgColor(bgcolor); + bgcolor.getColor(r, g, b, a); + drawSetColor( r,g,b,a ); + drawFilledRect(0,0,_size[0],_size[1]);*/ + + if ( ArmedBorderColor ) + { + ArmedBorderColor->getColor( r, g, b, a); + drawSetColor( r, g, b, a ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } + } + else + { + if ( UnArmedBorderColor ) + { + UnArmedBorderColor->getColor( r, g, b, a); + drawSetColor( r, g, b, a ); + drawOutlinedRect(0,0,_size[0],_size[1]); + } + } + } + void paint() + { + int r, g, b, a; + if ( isArmed() ) + { + if (ArmedColor) + { + ArmedColor->getColor(r, g, b, a); + setFgColor(r, g, b, a); + } + else + setFgColor( Scheme::sc_secondary2 ); + } + else + { + if (UnArmedColor) + { + UnArmedColor->getColor(r, g, b, a); + setFgColor(r, g, b, a); + } + else + setFgColor( Scheme::sc_primary1 ); + } + + Button::paint(); + } + + void setArmedColor ( int r, int g, int b, int a ) + { + ArmedColor = new Color( r, g, b, a ); + } + + void setUnArmedColor ( int r, int g, int b, int a ) + { + UnArmedColor = new Color( r, g, b, a ); + } + + void setArmedBorderColor ( int r, int g, int b, int a ) + { + ArmedBorderColor = new Color( r, g, b, a ); + } + + void setUnArmedBorderColor ( int r, int g, int b, int a ) + { + UnArmedBorderColor = new Color( r, g, b, a ); + } +}; + +class SpectButton : public CommandButton +{ +private: + +public: + SpectButton( int iPlayerClass, const char* text,int x,int y,int wide,int tall ) : + CommandButton( text, x, y, wide, tall, false) + { + Init(); + + setText( text ); + } + + virtual void paintBackground() + { + if ( isArmed()) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint() + { + + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + Button::paint(); + } +}; //============================================================ // Command Menus class CCommandMenu : public Panel @@ -225,6 +365,8 @@ private: CommandButton *m_aButtons[ MAX_BUTTONS ]; int m_iButtons; + // opens menu from top to bottom (0 = default), or from bottom to top (1)? + int m_iDirection; public: CCommandMenu( CCommandMenu *pParentMenu, int x,int y,int wide,int tall ) : Panel(x,y,wide,tall) { @@ -232,8 +374,20 @@ public: m_iXOffset = x; m_iYOffset = y; m_iButtons = 0; + m_iDirection = 0; } + CCommandMenu( CCommandMenu *pParentMenu, int direction, int x,int y,int wide,int tall ) : Panel(x,y,wide,tall) + { + m_pParentMenu = pParentMenu; + m_iXOffset = x; + m_iYOffset = y; + m_iButtons = 0; + m_iDirection = direction; + } + + float m_flButtonSizeY; + int m_iSpectCmdMenu; void AddButton( CommandButton *pButton ); bool RecalculateVisibles( int iNewYPos, bool bHideAll ); void RecalculatePositions( int iYOffset ); @@ -242,6 +396,7 @@ public: CCommandMenu *GetParentMenu() { return m_pParentMenu; }; int GetXOffset() { return m_iXOffset; }; int GetYOffset() { return m_iYOffset; }; + int GetDirection() { return m_iDirection; }; int GetNumButtons() { return m_iButtons; }; CommandButton *FindButtonWithSubmenu( CCommandMenu *pSubMenu ); @@ -266,6 +421,7 @@ private: CCommandMenu *m_pCurrentCommandMenu; float m_flMenuOpenTime; float m_flScoreBoardLastUpdated; + float m_flSpectatorPanelLastUpdated; int m_iNumMenus; int m_iCurrentTeamNumber; int m_iCurrentPlayerClass; @@ -296,10 +452,6 @@ private: // Server Browser ServerBrowser *m_pServerBrowser; - // Spectator "menu" - CTransparentPanel *m_pSpectatorMenu; - Label *m_pSpectatorLabel; - Label *m_pSpectatorHelpLabel; int m_iAllowSpectators; // Data for specific sections of the Command Menu @@ -319,24 +471,24 @@ public: TeamFortressViewport(int x,int y,int wide,int tall); void Initialize( void ); - void CreateCommandMenu( void ); - void CreateScoreBoard( void ); - void CreateServerBrowser( void ); - CommandButton *CreateCustomButton( char *pButtonText, char *pButtonName ); - CCommandMenu *CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText ); + int CreateCommandMenu( char * menuFile, int direction, int yOffset, bool flatDesign, float flButtonSizeX, float flButtonSizeY, int xOffset ); + void CreateScoreBoard( void ); + void CreateServerBrowser( void ); + CommandButton * CreateCustomButton( char *pButtonText, char * pButtonName, int iYOffset ); + CCommandMenu * CreateDisguiseSubmenu( CommandButton *pButton, CCommandMenu *pParentMenu, const char *commandText, int iYOffset, int iXOffset = 0 ); void UpdateCursorState( void ); - void UpdateCommandMenu( void ); + void UpdateCommandMenu(int menuIndex); void UpdateOnPlayerInfo( void ); void UpdateHighlights( void ); - void UpdateSpectatorMenu( void ); + void UpdateSpectatorPanel( void ); int KeyInput( int down, int keynum, const char *pszCurrentBinding ); void InputPlayerSpecial( void ); void GetAllPlayersInfo( void ); void DeathMsg( int killer, int victim ); - void ShowCommandMenu( void ); + void ShowCommandMenu(int menuIndex); void InputSignalHideCommandMenu( void ); void HideCommandMenu( void ); void SetCurrentCommandMenu( CCommandMenu *pNewMenu ); @@ -356,7 +508,7 @@ public: CMenuPanel* CreateTextWindow( int iTextToShow ); - CCommandMenu *CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu ); + CCommandMenu *CreateSubMenu( CommandButton *pButton, CCommandMenu *pParentMenu, int iYOffset, int iXOffset = 0 ); // Data Handlers int GetValidClasses(int iTeam) { return m_iValidClasses[iTeam]; }; @@ -398,8 +550,12 @@ public: // VGUI Menus CMenuPanel *m_pCurrentMenu; CTeamMenuPanel *m_pTeamMenu; + int m_StandardMenu; // indexs in m_pCommandMenus + int m_SpectatorOptionsMenu; + int m_SpectatorCameraMenu; CClassMenuPanel *m_pClassMenu; ScorePanel *m_pScoreBoard; + SpectatorPanel * m_pSpectatorPanel; char m_szServerName[ MAX_SERVERNAME_LENGTH ]; }; @@ -551,6 +707,7 @@ public: #define SHOW_MAPBRIEFING 1 #define SHOW_CLASSDESC 2 #define SHOW_MOTD 3 +#define SHOW_SPECHELP 4 class CMenuHandler_TextWindow : public ActionSignal { @@ -576,6 +733,29 @@ public: } }; +class CMenuHandler_ToggleCvar : public ActionSignal +{ +private: + struct cvar_s * m_cvar; + +public: + CMenuHandler_ToggleCvar( char * cvarname ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + } + + virtual void actionPerformed(Panel* panel) + { + if ( m_cvar->value ) + m_cvar->value = 0.0f; + else + m_cvar->value = 1.0f; + + gViewPort->UpdateSpectatorPanel(); + } + + +}; class CDragNDropHandler : public InputSignal { private: @@ -942,8 +1122,8 @@ private: int m_iTeamNum; public: - TeamOnlyCommandButton( int iTeamNum, const char* text,int x,int y,int wide,int tall ) : - CommandButton( text, x, y, wide, tall ), m_iTeamNum(iTeamNum) {} + TeamOnlyCommandButton( int iTeamNum, const char* text,int x,int y,int wide,int tall, bool flat ) : + CommandButton( text, x, y, wide, tall, false, flat ), m_iTeamNum(iTeamNum) {} virtual int IsNotValid() { @@ -952,8 +1132,282 @@ public: return CommandButton::IsNotValid(); } + + virtual void paintBackground() + { + if ( isArmed() ) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint( void ) + { + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + Button::paint(); + } }; +//----------------------------------------------------------------------------- +// Purpose: CommandButton which is only displayed if the player is on team X +//----------------------------------------------------------------------------- +class ToggleCommandButton : public CommandButton, public InputSignal +{ +private: + struct cvar_s * m_cvar; + CImageLabel * pLabelOn; + CImageLabel * pLabelOff; + + +public: + ToggleCommandButton( const char* cvarname, const char* text,int x,int y,int wide,int tall, bool flat ) : + CommandButton( text, x, y, wide, tall, false, flat ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + + // Put a > to show it's a submenu + pLabelOn = new CImageLabel( "checked", 0, 0 ); + pLabelOn->setParent(this); + pLabelOn->addInputSignal(this); + + pLabelOff = new CImageLabel( "unchecked", 0, 0 ); + pLabelOff->setParent(this); + pLabelOff->setEnabled(true); + pLabelOff->addInputSignal(this); + + int textwide, texttall; + getTextSize( textwide, texttall); + + // Reposition + pLabelOn->setPos( textwide, (tall - pLabelOn->getTall()) / 2 ); + + pLabelOff->setPos( textwide, (tall - pLabelOff->getTall()) / 2 ); + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + } + + virtual void cursorEntered(Panel* panel) + { + CommandButton::cursorEntered(); + } + + virtual void cursorExited(Panel* panel) + { + CommandButton::cursorExited(); + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + doClick(); + }; + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; + + virtual void paint( void ) + { + if ( !m_cvar ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(false); + } + else if ( m_cvar->value ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(true); + } + else + { + pLabelOff->setVisible(true); + pLabelOn->setVisible(false); + } + + CommandButton::paint(); + + } +}; +class SpectToggleButton : public CommandButton, public InputSignal +{ +private: + struct cvar_s * m_cvar; + CImageLabel * pLabelOn; + +public: + SpectToggleButton( const char* cvarname, const char* text,int x,int y,int wide,int tall, bool flat ) : + CommandButton( text, x, y, wide, tall, false, flat ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + + // Put a > to show it's a submenu + pLabelOn = new CImageLabel( "checked", 0, 0 ); + pLabelOn->setParent(this); + pLabelOn->addInputSignal(this); + + + int textwide, texttall; + getTextSize( textwide, texttall); + + // Reposition + pLabelOn->setPos( textwide, (tall - pLabelOn->getTall()) / 2 ); + } + + virtual void cursorEntered(Panel* panel) + { + CommandButton::cursorEntered(); + } + + virtual void cursorExited(Panel* panel) + { + CommandButton::cursorExited(); + } + + virtual void mousePressed(MouseCode code,Panel* panel) + { + doClick(); + }; + + virtual void cursorMoved(int x,int y,Panel* panel) {}; + + virtual void mouseDoublePressed(MouseCode code,Panel* panel) {}; + virtual void mouseReleased(MouseCode code,Panel* panel) {}; + virtual void mouseWheeled(int delta,Panel* panel) {}; + virtual void keyPressed(KeyCode code,Panel* panel) {}; + virtual void keyTyped(KeyCode code,Panel* panel) {}; + virtual void keyReleased(KeyCode code,Panel* panel) {}; + virtual void keyFocusTicked(Panel* panel) {}; + + virtual void paintBackground() + { + if ( isArmed() ) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint( void ) + { + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + if ( !m_cvar ) + { + pLabelOn->setVisible(false); + } + else if ( m_cvar->value ) + { + pLabelOn->setVisible(true); + } + else + { + pLabelOn->setVisible(false); + } + + Button::paint(); + } +}; + +/* +class SpectToggleButton : public ToggleCommandButton +{ +private: + struct cvar_s * m_cvar; + CImageLabel * pLabelOn; + CImageLabel * pLabelOff; + +public: + + SpectToggleButton( const char* cvarname, const char* text,int x,int y,int wide,int tall, bool flat ) : + ToggleCommandButton( cvarname, text, x, y, wide, tall, flat, TRUE ) + { + m_cvar = gEngfuncs.pfnGetCvarPointer( cvarname ); + + // Put a > to show it's a submenu + pLabelOn = new CImageLabel( "checked", 0, 0 ); + pLabelOn->setParent(this); + pLabelOn->addInputSignal(this); + + pLabelOff = new CImageLabel( "unchecked", 0, 0 ); + pLabelOff->setParent(this); + pLabelOff->setEnabled(true); + pLabelOff->addInputSignal(this); + + int textwide, texttall; + getTextSize( textwide, texttall); + + // Reposition + pLabelOn->setPos( textwide, (tall - pLabelOn->getTall()) / 2 ); + + pLabelOff->setPos( textwide, (tall - pLabelOff->getTall()) / 2 ); + + // Set text color to orange + setFgColor(Scheme::sc_primary1); + } + + virtual void paintBackground() + { + if ( isArmed()) + { + drawSetColor( 143,143, 54, 125 ); + drawFilledRect( 5, 0,_size[0] - 5,_size[1]); + } + } + + virtual void paint() + { + + if ( isArmed() ) + { + setFgColor( 194, 202, 54, 0 ); + } + else + { + setFgColor( 143, 143, 54, 15 ); + } + + if ( !m_cvar ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(false); + } + else if ( m_cvar->value ) + { + pLabelOff->setVisible(false); + pLabelOn->setVisible(true); + } + else + { + pLabelOff->setVisible(true); + pLabelOn->setVisible(false); + } + + Button::paint(); + } +}; +*/ //============================================================ // Panel that can be dragged around class DragNDropPanel : public Panel diff --git a/cl_dll/vgui_int.cpp b/cl_dll/vgui_int.cpp index f2913ca..7d7bdaa 100644 --- a/cl_dll/vgui_int.cpp +++ b/cl_dll/vgui_int.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/vgui_int.h b/cl_dll/vgui_int.h index 74c1d1e..b5302b2 100644 --- a/cl_dll/vgui_int.h +++ b/cl_dll/vgui_int.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/vgui_teammenu.cpp b/cl_dll/vgui_teammenu.cpp index ca24a65..a137079 100644 --- a/cl_dll/vgui_teammenu.cpp +++ b/cl_dll/vgui_teammenu.cpp @@ -1,4 +1,4 @@ -//=========== (C) Copyright 1996-2001 Valve, L.L.C. All rights reserved. =========== +//=========== (C) Copyright 1996-2002 Valve, L.L.C. All rights reserved. =========== // // The copyright to the contents herein is the property of Valve, L.L.C. // The contents may be used and/or copied only with the written permission of @@ -303,6 +303,7 @@ void CTeamMenuPanel::Update( void ) int iXSize, iYSize; m_pBriefing->getTextImage()->getTextSize( iXSize, iYSize ); m_pBriefing->setSize( iXSize, iYSize ); + gEngfuncs.COM_FreeFile( pfile ); } m_bUpdatedMapName = true; diff --git a/cl_dll/view.cpp b/cl_dll/view.cpp index c9e19d2..02af24a 100644 --- a/cl_dll/view.cpp +++ b/cl_dll/view.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -19,10 +19,12 @@ #include "in_defs.h" // PITCH YAW ROLL #include "pm_movevars.h" #include "pm_shared.h" +#include "pm_defs.h" +#include "event_api.h" #include "pmtrace.h" #include "screenfade.h" #include "shake.h" - +#include "hltv.h" // Spectator Mode extern "C" @@ -46,15 +48,25 @@ extern "C" void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ); void PM_ParticleLine( float *start, float *end, int pcolor, float life, float vert); - int PM_GetInfo( int ent ); - void InterpolateAngles( float *start, float *end, float *output, float frac ); - float AngleBetweenVectors( float * v1, float * v2 ); + int PM_GetVisEntInfo( int ent ); + int PM_GetPhysEntInfo( int ent ); + void InterpolateAngles( float * start, float * end, float * output, float frac ); + void NormalizeAngles( float * angles ); + float Distance(const float * v1, const float * v2); + float AngleBetweenVectors( const float * v1, const float * v2 ); + float vJumpOrigin[3]; + float vJumpAngles[3]; } void V_DropPunchAngle ( float frametime, float *ev_punchangle ); void VectorAngles( const float *forward, float *angles ); +#include "r_studioint.h" +#include "com_model.h" + +extern engine_studio_api_t IEngineStudio; + /* The view is allowed to move slightly from it's true position for bobbing, but if it exceeds 8 pixels linear distance (spherical, not box), the list of @@ -67,7 +79,15 @@ extern cvar_t *chase_active; extern cvar_t *scr_ofsx, *scr_ofsy, *scr_ofsz; extern cvar_t *cl_vsmoothing; -vec3_t v_origin, v_angles; +#define CAM_MODE_RELAX 1 +#define CAM_MODE_FOCUS 2 + +vec3_t v_origin, v_angles, v_cl_angles, v_sim_org, v_lastAngles; +float v_frametime, v_lastDistance; +float v_cameraRelaxAngle = 5.0f; +float v_cameraFocusAngle = 35.0f; +int v_cameraMode = CAM_MODE_FOCUS; +qboolean v_resetCamera = 1; vec3_t ev_punchangle; @@ -82,6 +102,7 @@ cvar_t *cl_bobcycle; cvar_t *cl_bob; cvar_t *cl_bobup; cvar_t *cl_waterdist; +cvar_t *cl_chasedist; // These cvars are not registered (so users can't cheat), so set the ->value field directly // Register these cvars in V_Init() if needed for easy tweaking @@ -94,8 +115,8 @@ cvar_t v_ipitch_level = {"v_ipitch_level", "0.3", 0, 0.3}; float v_idlescale; // used by TFC for concussion grenade effect -/* //============================================================================= +/* void V_NormalizeAngles( float *angles ) { int i; @@ -161,8 +182,6 @@ float V_CalcBob ( struct ref_params_s *pparams ) static float lasttime; vec3_t vel; - if ( pparams->spectator || iIsSpectator ) - return 0; if ( pparams->onground == -1 || pparams->time == lasttime ) @@ -309,8 +328,6 @@ void V_DriftPitch ( struct ref_params_s *pparams ) move = pparams->frametime * pd.pitchvel; pd.pitchvel += pparams->frametime * v_centerspeed->value; -//Con_Printf ("move: %f (%f)\n", move, pparams->frametime); - if (delta > 0) { if (move > delta) @@ -418,16 +435,6 @@ void V_CalcIntermissionRefdef ( struct ref_params_s *pparams ) cl_entity_t *ent, *view; float old; -// don't allow cheats in multiplayer -#if !defined( _DEBUG ) - if ( pparams->maxclients > 1 ) - { - gEngfuncs.Cvar_SetValue ("scr_ofsx", 0); - gEngfuncs.Cvar_SetValue ("scr_ofsy", 0); - gEngfuncs.Cvar_SetValue ("scr_ofsz", 0); - } -#endif - // ent is the player model ( visible when out of body ) ent = gEngfuncs.GetLocalPlayer(); @@ -445,8 +452,16 @@ void V_CalcIntermissionRefdef ( struct ref_params_s *pparams ) V_AddIdle ( pparams ); + if ( gEngfuncs.IsSpectateOnly() ) + { + // in HLTV we must go to 'intermission' position by ourself + VectorCopy( gHUD.m_Spectator.m_cameraOrigin, pparams->vieworg ); + VectorCopy( gHUD.m_Spectator.m_cameraAngles, pparams->viewangles ); + } + v_idlescale = old; + v_cl_angles = pparams->cl_viewangles; v_origin = pparams->vieworg; v_angles = pparams->viewangles; } @@ -483,25 +498,20 @@ void V_CalcNormalRefdef ( struct ref_params_s *pparams ) static float oldz = 0; static float lasttime; - static float lastang[3]; - vec3_t angdelta; - vec3_t camAngles, camForward, camRight, camUp; cl_entity_t *pwater; - // don't allow cheats in multiplayer - if ( pparams->maxclients > 1 ) - { - scr_ofsx->value = 0.0; - scr_ofsy->value = 0.0; - scr_ofsz->value = 0.0; - } - - V_DriftPitch ( pparams ); - // ent is the player model ( visible when out of body ) - ent = gEngfuncs.GetLocalPlayer(); + if ( gEngfuncs.IsSpectateOnly() ) + { + ent = gEngfuncs.GetEntityByIndex( g_iUser2 ); + } + else + { + // ent is the player model ( visible when out of body ) + ent = gEngfuncs.GetLocalPlayer(); + } // view is the weapon model (only visible from inside body ) view = gEngfuncs.GetViewModel(); @@ -510,78 +520,11 @@ void V_CalcNormalRefdef ( struct ref_params_s *pparams ) // model origin for the view bob = V_CalcBob ( pparams ); - // Observer angle capturing and smoothing - if ( iHasNewViewOrigin ) - { - // Get the angles from the physics code - VectorCopy( vecNewViewOrigin, pparams->vieworg ); - VectorCopy( vecNewViewOrigin, pparams->simorg ); - } - // refresh position VectorCopy ( pparams->simorg, pparams->vieworg ); pparams->vieworg[2] += ( bob ); VectorAdd( pparams->vieworg, pparams->viewheight, pparams->vieworg ); - // Observer angle capturing and smoothing - if ( iHasNewViewAngles ) - { - // Get the angles from the physics code - VectorCopy( vecNewViewAngles, pparams->cl_viewangles ); - } - - VectorSubtract( pparams->cl_viewangles, lastang, angdelta ); - if ( Length( angdelta ) != 0.0 ) - { - VectorCopy( pparams->cl_viewangles, ViewInterp.Angles[ ViewInterp.CurrentAngle & ORIGIN_MASK ] ); - ViewInterp.AngleTime[ ViewInterp.CurrentAngle & ORIGIN_MASK ] = pparams->time; - ViewInterp.CurrentAngle++; - - VectorCopy( pparams->cl_viewangles, lastang ); - } - - if ( cl_vsmoothing && cl_vsmoothing->value && ( iIsSpectator & SPEC_SMOOTH_ANGLES ) ) - { - int foundidx; - int i; - float t; - - if ( cl_vsmoothing->value < 0.0 ) - { - gEngfuncs.Cvar_SetValue( "cl_vsmoothing", 0.0 ); - } - - t = pparams->time - cl_vsmoothing->value; - - for ( i = 1; i < ORIGIN_MASK; i++ ) - { - foundidx = ViewInterp.CurrentAngle - 1 - i; - if ( ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ] <= t ) - break; - } - - if ( i < ORIGIN_MASK && ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ] != 0.0 ) - { - // Interpolate - double dt; - - dt = ViewInterp.AngleTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ]; - if ( dt > 0.0 ) - { - double frac; - - frac = ( t - ViewInterp.AngleTime[ foundidx & ORIGIN_MASK] ) / dt; - frac = min( 1.0, frac ); - - // interpolate angles - InterpolateAngles( ViewInterp.Angles[ foundidx & ORIGIN_MASK ], ViewInterp.Angles[ (foundidx + 1) & ORIGIN_MASK ], pparams->cl_viewangles, frac ); - - VectorCopy( pparams->cl_viewangles, vecNewViewAngles ); - } - } - } - - VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); gEngfuncs.V_CalcShake(); @@ -665,9 +608,13 @@ void V_CalcNormalRefdef ( struct ref_params_s *pparams ) AngleVectors ( angles, pparams->forward, pparams->right, pparams->up ); - for ( i=0 ; i<3 ; i++ ) + // don't allow cheats in multiplayer + if ( pparams->maxclients <= 1 ) { - pparams->vieworg[i] += scr_ofsx->value*pparams->forward[i] + scr_ofsy->value*pparams->right[i] + scr_ofsz->value*pparams->up[i]; + for ( i=0 ; i<3 ; i++ ) + { + pparams->vieworg[i] += scr_ofsx->value*pparams->forward[i] + scr_ofsy->value*pparams->right[i] + scr_ofsz->value*pparams->up[i]; + } } // Treating cam_ofs[2] as the distance @@ -790,7 +737,7 @@ void V_CalcNormalRefdef ( struct ref_params_s *pparams ) // Smooth out whole view in multiplayer when on trains, lifts if ( cl_vsmoothing && cl_vsmoothing->value && - ( ( iIsSpectator & SPEC_SMOOTH_ORIGIN ) || (pparams->smoothing && ( pparams->maxclients > 1 ) ) ) ) + ( pparams->smoothing && ( pparams->maxclients > 1 ) ) ) { int foundidx; int i; @@ -835,7 +782,6 @@ void V_CalcNormalRefdef ( struct ref_params_s *pparams ) VectorAdd( pparams->vieworg, delta, pparams->vieworg ); VectorAdd( view->origin, delta, view->origin ); - VectorCopy( pparams->simorg, vecNewViewOrigin ); } } } @@ -843,7 +789,8 @@ void V_CalcNormalRefdef ( struct ref_params_s *pparams ) // Store off v_angles before munging for third person v_angles = pparams->viewangles; - + v_lastAngles = pparams->viewangles; +// v_cl_angles = pparams->cl_viewangles; // keep old user mouse angles ! if ( CL_IsThirdPerson() ) { VectorCopy( camAngles, pparams->viewangles); @@ -883,23 +830,481 @@ void V_CalcNormalRefdef ( struct ref_params_s *pparams ) lasttime = pparams->time; v_origin = pparams->vieworg; +} + +void V_SmoothInterpolateAngles( float * startAngle, float * endAngle, float * finalAngle, float degreesPerSec ) +{ + float absd,frac,d,threshhold; + + NormalizeAngles( startAngle ); + NormalizeAngles( endAngle ); + + for ( int i = 0 ; i < 3 ; i++ ) + { + d = endAngle[i] - startAngle[i]; + + if ( d > 180.0f ) + { + d -= 360.0f; + } + else if ( d < -180.0f ) + { + d += 360.0f; + } + + absd = fabs(d); + + if ( absd > 0.01f ) + { + frac = degreesPerSec * v_frametime; + + threshhold= degreesPerSec / 4; + + if ( absd < threshhold ) + { + float h = absd / threshhold; + h *= h; + frac*= h; // slow down last degrees + } + + if ( frac > absd ) + { + finalAngle[i] = endAngle[i]; + } + else + { + if ( d>0) + finalAngle[i] = startAngle[i] + frac; + else + finalAngle[i] = startAngle[i] - frac; + } + } + else + { + finalAngle[i] = endAngle[i]; + } + + } + + NormalizeAngles( finalAngle ); +} + +// Get the origin of the Observer based around the target's position and angles +void V_GetChaseOrigin( float * angles, float * origin, float distance, float * returnvec ) +{ + vec3_t vecEnd; + vec3_t forward; + vec3_t vecStart; + pmtrace_t * trace; + int maxLoops = 8; + + int ignoreent = -1; // first, ignore no entity + + cl_entity_t * ent = NULL; + + // Trace back from the target using the player's view angles + AngleVectors(angles, forward, NULL, NULL); + + VectorScale(forward,-1,forward); + + VectorCopy( origin, vecStart ); + + VectorMA(vecStart, distance , forward, vecEnd); + + while ( maxLoops > 0) + { + trace = gEngfuncs.PM_TraceLine( vecStart, vecEnd, PM_TRACELINE_PHYSENTSONLY, 2, ignoreent ); + + // WARNING! trace->ent is is the number in physent list not the normal entity number + + if ( trace->ent <= 0) + break; // we hit the world or nothing, stop trace + + ent = gEngfuncs.GetEntityByIndex( PM_GetPhysEntInfo( trace->ent ) ); + + if ( ent == NULL ) + break; + + // hit non-player solid BSP , stop here + if ( ent->curstate.solid == SOLID_BSP && !ent->player ) + break; + + // if close enought to end pos, stop, otherwise continue trace + if( Distance(trace->endpos, vecEnd ) < 1.0f ) + { + break; + } + else + { + ignoreent = trace->ent; // ignore last hit entity + VectorCopy( trace->endpos, vecStart); + } + + maxLoops--; + } + +/* if ( ent ) + { + gEngfuncs.Con_Printf("Trace loops %i , entity %i, model %s, solid %i\n",(8-maxLoops),ent->curstate.number, ent->model->name , ent->curstate.solid ); + } */ + + VectorMA( trace->endpos, 4, trace->plane.normal, returnvec ); + + v_lastDistance = Distance(trace->endpos, origin); // real distance without offset +} + +/*void V_GetDeathCam(cl_entity_t * ent1, cl_entity_t * ent2, float * angle, float * origin) +{ + float newAngle[3]; float newOrigin[3]; + + float distance = 168.0f; + + v_lastDistance+= v_frametime * 96.0f; // move unit per seconds back + + if ( v_resetCamera ) + v_lastDistance = 64.0f; + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + newOrigin[2]+= 17; // head level of living player + + // get new angle towards second target + if ( ent2 ) + { + VectorSubtract( ent2->origin, ent1->origin, newAngle ); + VectorAngles( newAngle, newAngle ); + newAngle[0] = -newAngle[0]; + } + else + { + // if no second target is given, look down to dead player + newAngle[0] = 90.0f; + newAngle[1] = 0.0f; + newAngle[2] = 0; + } + + // and smooth view + V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 120.0f ); + + V_GetChaseOrigin( angle, newOrigin, distance, origin ); + + VectorCopy(angle, v_lastAngles); +}*/ + +void V_GetSingleTargetCam(cl_entity_t * ent1, float * angle, float * origin) +{ + float newAngle[3]; float newOrigin[3]; + + int flags = gHUD.m_Spectator.m_iObserverFlags; + + // see is target is a dead player + qboolean deadPlayer = ent1->player && (ent1->curstate.solid == SOLID_NOT); + + float dfactor = ( flags & DRC_FLAG_DRAMATIC )? -1.0f : 1.0f; + + float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; + + // go away in final scenes or if player just died + if ( flags & DRC_FLAG_FINAL ) + distance*=2.0f; + else if ( deadPlayer ) + distance*=1.5f; + + // let v_lastDistance float smoothly away + v_lastDistance+= v_frametime * 32.0f; // move unit per seconds back + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + { + if ( deadPlayer ) + newOrigin[2]+= 2; //laying on ground + else + newOrigin[2]+= 17; // head level of living player + + } + else + newOrigin[2]+= 8; // object, tricky, must be above bomb in CS + + // we have no second target, choose view direction based on + // show front of primary target + VectorCopy(ent1->angles, newAngle); + + // show dead players from front, normal players back + if ( flags & DRC_FLAG_FACEPLAYER ) + newAngle[1]+= 180.0f; + + + newAngle[0]+= 12.5f * dfactor; // lower angle if dramatic + + // if final scene (bomb), show from real high pos + if ( flags & DRC_FLAG_FINAL ) + newAngle[0] = 22.5f; + + // choose side of object/player + if ( flags & DRC_FLAG_SIDE ) + newAngle[1]+=22.5f; + else + newAngle[1]-=22.5f; + + V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 120.0f ); + + // HACK, if player is dead don't clip against his dead body, can't check this + V_GetChaseOrigin( angle, newOrigin, distance, origin ); +} + +float MaxAngleBetweenAngles( float * a1, float * a2 ) +{ + float d, maxd = 0.0f; + + NormalizeAngles( a1 ); + NormalizeAngles( a2 ); + + for ( int i = 0 ; i < 3 ; i++ ) + { + d = a2[i] - a1[i]; + if ( d > 180 ) + { + d -= 360; + } + else if ( d < -180 ) + { + d += 360; + } + + d = fabs(d); + + if ( d > maxd ) + maxd=d; + } + + return maxd; +} + +void V_GetDoubleTargetsCam(cl_entity_t * ent1, cl_entity_t * ent2,float * angle, float * origin) +{ + float newAngle[3]; float newOrigin[3]; float tempVec[3]; + + int flags = gHUD.m_Spectator.m_iObserverFlags; + + float dfactor = ( flags & DRC_FLAG_DRAMATIC )? -1.0f : 1.0f; + + float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; + + // go away in final scenes or if player just died + if ( flags & DRC_FLAG_FINAL ) + distance*=2.0f; + + // let v_lastDistance float smoothly away + v_lastDistance+= v_frametime * 32.0f; // move unit per seconds back + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + newOrigin[2]+= 17; // head level of living player + else + newOrigin[2]+= 8; // object, tricky, must be above bomb in CS + + // get new angle towards second target + VectorSubtract( ent2->origin, ent1->origin, newAngle ); + + VectorAngles( newAngle, newAngle ); + newAngle[0] = -newAngle[0]; + + // set angle diffrent in Dramtaic scenes + newAngle[0]+= 12.5f * dfactor; // lower angle if dramatic + + if ( flags & DRC_FLAG_SIDE ) + newAngle[1]+=22.5f; + else + newAngle[1]-=22.5f; + + float d = MaxAngleBetweenAngles( v_lastAngles, newAngle ); + + if ( ( d < v_cameraFocusAngle) && ( v_cameraMode == CAM_MODE_RELAX ) ) + { + // difference is to small and we are in relax camera mode, keep viewangles + VectorCopy(v_lastAngles, newAngle ); + } + else if ( (d < v_cameraRelaxAngle) && (v_cameraMode == CAM_MODE_FOCUS) ) + { + // we catched up with our target, relax again + v_cameraMode = CAM_MODE_RELAX; + } + else + { + // target move too far away, focus camera again + v_cameraMode = CAM_MODE_FOCUS; + } + + // and smooth view, if not a scene cut + if ( v_resetCamera || (v_cameraMode == CAM_MODE_RELAX) ) + { + VectorCopy( newAngle, angle ); + } + else + { + V_SmoothInterpolateAngles( v_lastAngles, newAngle, angle, 180.0f ); + } + + V_GetChaseOrigin( newAngle, newOrigin, distance, origin ); + + // move position up, if very close at target + if ( v_lastDistance < 64.0f ) + origin[2]+= 16.0f*( 1.0f - (v_lastDistance / 64.0f ) ); + + // calculate angle to second target + VectorSubtract( ent2->origin, origin, tempVec ); + VectorAngles( tempVec, tempVec ); + tempVec[0] = -tempVec[0]; + + /* take middle between two viewangles + InterpolateAngles( newAngle, tempVec, newAngle, 0.5f); */ + + } -void V_GetInEyePos(int entity, float * origin, float * angles ) +void V_GetDirectedChasePosition(cl_entity_t * ent1, cl_entity_t * ent2,float * angle, float * origin) { - cl_entity_t * ent = gEngfuncs.GetEntityByIndex( entity ); + + if ( v_resetCamera ) + { + v_lastDistance = 4096.0f; + // v_cameraMode = CAM_MODE_FOCUS; + } + + if ( ( ent2 == (cl_entity_t*)0xFFFFFFFF ) || ( ent1->player && (ent1->curstate.solid == SOLID_NOT) ) ) + { + // we have no second target or player just died + V_GetSingleTargetCam(ent1, angle, origin); + } + else if ( ent2 ) + { + // keep both target in view + V_GetDoubleTargetsCam( ent1, ent2, angle, origin ); + } + else + { + // second target disappeard somehow (dead) + + // keep last good viewangle + float newOrigin[3]; + + int flags = gHUD.m_Spectator.m_iObserverFlags; + + float dfactor = ( flags & DRC_FLAG_DRAMATIC )? -1.0f : 1.0f; + + float distance = 112.0f + ( 16.0f * dfactor ); // get close if dramatic; + + // go away in final scenes or if player just died + if ( flags & DRC_FLAG_FINAL ) + distance*=2.0f; + + // let v_lastDistance float smoothly away + v_lastDistance+= v_frametime * 32.0f; // move unit per seconds back + + if ( distance > v_lastDistance ) + distance = v_lastDistance; + + VectorCopy(ent1->origin, newOrigin); + + if ( ent1->player ) + newOrigin[2]+= 17; // head level of living player + else + newOrigin[2]+= 8; // object, tricky, must be above bomb in CS + + V_GetChaseOrigin( angle, newOrigin, distance, origin ); + } + + VectorCopy(angle, v_lastAngles); +} + +void V_GetChasePos(int target, float * cl_angles, float * origin, float * angles) +{ + cl_entity_t * ent = NULL; + + if ( target ) + { + ent = gEngfuncs.GetEntityByIndex( target ); + }; + + if (!ent) + { + // just copy a save in-map position + VectorCopy ( vJumpAngles, angles ); + VectorCopy ( vJumpOrigin, origin ); + return; + } + + + + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + if ( g_iUser3 ) + V_GetDirectedChasePosition( ent, gEngfuncs.GetEntityByIndex( g_iUser3 ), + angles, origin ); + else + V_GetDirectedChasePosition( ent, ( cl_entity_t*)0xFFFFFFFF, + angles, origin ); + } + else + { + if ( cl_angles == NULL ) // no mouse angles given, use entity angles ( locked mode ) + { + VectorCopy ( ent->angles, angles); + angles[0]*=-1; + } + else + VectorCopy ( cl_angles, angles); + + + VectorCopy ( ent->origin, origin); + + origin[2]+= 28; // DEFAULT_VIEWHEIGHT - some offset + + V_GetChaseOrigin( angles, origin, cl_chasedist->value, origin ); + } + + v_resetCamera = false; +} + +void V_ResetChaseCam() +{ + v_resetCamera = true; +} + + +void V_GetInEyePos(int target, float * origin, float * angles ) +{ + if ( !target) + { + // just copy a save in-map position + VectorCopy ( vJumpAngles, angles ); + VectorCopy ( vJumpOrigin, origin ); + return; + }; + + + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( target ); if ( !ent ) return; - if ( !ent->player || g_PlayerInfoList[entity].name == NULL ) - return; - VectorCopy ( ent->origin, origin ); VectorCopy ( ent->angles, angles ); - angles[0]*=-M_PI; + angles[PITCH]*=-3.0f; // see CL_ProcessEntityUpdate() if ( ent->curstate.solid == SOLID_NOT ) { @@ -914,211 +1319,238 @@ void V_GetInEyePos(int entity, float * origin, float * angles ) origin[2]+= 28; // DEFAULT_VIEWHEIGHT } +void V_GetMapFreePosition( float * cl_angles, float * origin, float * angles ) +{ + vec3_t forward; + vec3_t zScaledTarget; + + VectorCopy(cl_angles, angles); + + // modify angles since we don't wanna see map's bottom + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + + zScaledTarget[0] = gHUD.m_Spectator.m_mapOrigin[0]; + zScaledTarget[1] = gHUD.m_Spectator.m_mapOrigin[1]; + zScaledTarget[2] = gHUD.m_Spectator.m_mapOrigin[2] * (( 90.0f - angles[0] ) / 90.0f ); + + + AngleVectors(angles, forward, NULL, NULL); + + VectorNormalize(forward); + + VectorMA(zScaledTarget, -( 4096.0f / gHUD.m_Spectator.m_mapZoom ), forward , origin); +} + +void V_GetMapChasePosition(int target, float * cl_angles, float * origin, float * angles) +{ + vec3_t forward; + + if ( target ) + { + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( target ); + + if ( gHUD.m_Spectator.m_autoDirector->value ) + { + // this is done to get the angles made by director mode + V_GetChasePos(target, cl_angles, origin, angles); + VectorCopy(ent->origin, origin); + + // keep fix chase angle horizontal + angles[0] = 45.0f; + } + else + { + VectorCopy(cl_angles, angles); + VectorCopy(ent->origin, origin); + + // modify angles since we don't wanna see map's bottom + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + } + } + else + { + // keep out roaming position, but modify angles + VectorCopy(cl_angles, angles); + angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); + } + + origin[2] *= (( 90.0f - angles[0] ) / 90.0f ); + angles[2] = 0.0f; // don't roll angle (if chased player is dead) + + AngleVectors(angles, forward, NULL, NULL); + + VectorNormalize(forward); + + VectorMA(origin, -1536, forward, origin); +} + +int V_FindViewModelByWeaponModel(int weaponindex) +{ + + static char * modelmap[][2] = { + { "models/p_crossbow.mdl", "models/v_crossbow.mdl" }, + { "models/p_crowbar.mdl", "models/v_crowbar.mdl" }, + { "models/p_egon.mdl", "models/v_egon.mdl" }, + { "models/p_gauss.mdl", "models/v_gauss.mdl" }, + { "models/p_9mmhandgun.mdl", "models/v_9mmhandgun.mdl" }, + { "models/p_grenade.mdl", "models/v_grenade.mdl" }, + { "models/p_hgun.mdl", "models/v_hgun.mdl" }, + { "models/p_9mmAR.mdl", "models/v_9mmAR.mdl" }, + { "models/p_357.mdl", "models/v_357.mdl" }, + { "models/p_rpg.mdl", "models/v_rpg.mdl" }, + { "models/p_shotgun.mdl", "models/v_shotgun.mdl" }, + { "models/p_squeak.mdl", "models/v_squeak.mdl" }, + { "models/p_tripmine.mdl", "models/v_tripmine.mdl" }, + { "models/p_satchel_radio.mdl", "models/v_satchel_radio.mdl"}, + { "models/p_satchel.mdl", "models/v_satchel.mdl" }, + { NULL, NULL } }; + + struct model_s * weaponModel = IEngineStudio.GetModelByIndex( weaponindex ); + + if ( weaponModel ) + { + int len = strlen( weaponModel->name ); + int i = 0; + + while ( modelmap[i] != NULL ) + { + if ( !strnicmp( weaponModel->name, modelmap[i][0], len ) ) + { + return gEngfuncs.pEventAPI->EV_FindModelIndex( modelmap[i][1] ); + } + i++; + } + + return 0; + } + else + return 0; + +} + + /* ================== V_CalcSpectatorRefdef ================== */ -void V_CalcSpectatorRefdef ( struct ref_params_s *pparams ) +void V_CalcSpectatorRefdef ( struct ref_params_s * pparams ) { - cl_entity_t *ent, *view; - vec3_t angles; - static viewinterp_t ViewInterp; + static vec3_t velocity ( 0.0f, 0.0f, 0.0f); - static float lasttime; - - static float lastang[3]; - static float lastorg[3]; - - vec3_t delta; - - - // ent is the player model ( visible when out of body ) - ent = gEngfuncs.GetLocalPlayer(); - - // view is the weapon model (only visible from inside body ) - view = gEngfuncs.GetViewModel(); - - - // refresh position - VectorCopy ( pparams->simorg, pparams->vieworg ); - - // done all the spectator smoothing only once in the first frame - - // Observer angle capturing and smoothing - if ( iHasNewViewOrigin ) - { - // Get the angles from the physics code - VectorCopy( vecNewViewOrigin, pparams->vieworg ); - } - else - { - // otherwise copy normal vieworigin into vecNewViewOrigin - VectorCopy( pparams->vieworg, vecNewViewOrigin ); - } - - VectorCopy ( pparams->cl_viewangles, pparams->viewangles ); - - // Observer angle capturing and smoothing - if ( iHasNewViewAngles ) - { - // Get the angles from the physics code - VectorCopy( vecNewViewAngles, pparams->viewangles ); - } - else - { - // otherwise copy normal viewangle into vewNewViewAngles - VectorCopy( pparams->viewangles, vecNewViewAngles); - } - - // do the smoothing only once per frame - if (pparams->nextView == 0) - { - // smooth angles - - VectorSubtract( pparams->viewangles, lastang, delta ); - if ( Length( delta ) != 0.0f ) - { - VectorCopy( pparams->viewangles, ViewInterp.Angles[ ViewInterp.CurrentAngle & ORIGIN_MASK ] ); - ViewInterp.AngleTime[ ViewInterp.CurrentAngle & ORIGIN_MASK ] = pparams->time; - ViewInterp.CurrentAngle++; - VectorCopy( pparams->viewangles, lastang ); - } - - if ( cl_vsmoothing && cl_vsmoothing->value && ( iIsSpectator & SPEC_SMOOTH_ANGLES ) ) - { - int foundidx; - int i; - float t; - - t = pparams->time - cl_vsmoothing->value; - - for ( i = 1; i < ORIGIN_MASK; i++ ) - { - foundidx = ViewInterp.CurrentAngle - 1 - i; - if ( ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ] <= t ) - break; - } - - if ( i < ORIGIN_MASK && ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ] != 0.0 ) - { - // Interpolate - double dt; - float da; - vec3_t v1,v2; - - AngleVectors( ViewInterp.Angles[ foundidx & ORIGIN_MASK ], v1, NULL, NULL ); - AngleVectors( ViewInterp.Angles[ (foundidx + 1) & ORIGIN_MASK ], v2, NULL, NULL ); - da = AngleBetweenVectors( v1, v2 ); - - dt = ViewInterp.AngleTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.AngleTime[ foundidx & ORIGIN_MASK ]; - - if ( dt > 0.0 && ( da < 22.5f) ) - { - double frac; - - frac = ( t - ViewInterp.AngleTime[ foundidx & ORIGIN_MASK] ) / dt; - frac = min( 1.0, frac ); - - // interpolate angles - InterpolateAngles( ViewInterp.Angles[ foundidx & ORIGIN_MASK ], ViewInterp.Angles[ (foundidx + 1) & ORIGIN_MASK ], vecNewViewAngles, frac ); - VectorCopy( vecNewViewAngles, pparams->viewangles ); - } - } - } - - // smooth origin + static int lastWeaponModelIndex = 0; + static int lastViewModelIndex = 0; - VectorSubtract( pparams->vieworg, lastorg, delta ); + cl_entity_t * ent = gEngfuncs.GetEntityByIndex( g_iUser2 ); + + pparams->onlyClientDraw = false; - if ( Length( delta ) != 0.0 ) + // refresh position + VectorCopy ( pparams->simorg, v_sim_org ); + + // get old values + VectorCopy ( pparams->cl_viewangles, v_cl_angles ); + VectorCopy ( pparams->viewangles, v_angles ); + VectorCopy ( pparams->vieworg, v_origin ); + + if ( ( g_iUser1 == OBS_IN_EYE || gHUD.m_Spectator.m_pip->value == INSET_IN_EYE ) && ent ) + { + // calculate player velocity + float timeDiff = ent->curstate.msg_time - ent->prevstate.msg_time; + + if ( timeDiff > 0 ) { - VectorCopy( pparams->vieworg, ViewInterp.Origins[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] ); - ViewInterp.OriginTime[ ViewInterp.CurrentOrigin & ORIGIN_MASK ] = pparams->time; - ViewInterp.CurrentOrigin++; + vec3_t distance; + VectorSubtract(ent->prevstate.origin, ent->curstate.origin, distance); + VectorScale(distance, 1/timeDiff, distance ); - VectorCopy( pparams->vieworg, lastorg ); + velocity[0] = velocity[0]*0.9f + distance[0]*0.1f; + velocity[1] = velocity[1]*0.9f + distance[1]*0.1f; + velocity[2] = velocity[2]*0.9f + distance[2]*0.1f; + + VectorCopy(velocity, pparams->simvel); } - if ( cl_vsmoothing && cl_vsmoothing->value && ( iIsSpectator & SPEC_SMOOTH_ORIGIN ) ) + // predict missing client data and set weapon model ( in HLTV mode or inset in eye mode ) + if ( gEngfuncs.IsSpectateOnly() ) { - int foundidx; - int i; - float t; + V_GetInEyePos( g_iUser2, pparams->simorg, pparams->cl_viewangles ); - t = pparams->time - cl_vsmoothing->value; + pparams->health = 1; - for ( i = 1; i < ORIGIN_MASK; i++ ) + cl_entity_t * gunModel = gEngfuncs.GetViewModel(); + + if ( lastWeaponModelIndex != ent->curstate.weaponmodel ) { - foundidx = ViewInterp.CurrentOrigin - 1 - i; - if ( ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] <= t ) - break; - } + // weapon model changed - if ( i < ORIGIN_MASK && ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ] != 0.0 ) - { - // Interpolate - vec3_t delta; - double frac; - double dt; - vec3_t neworg; - - dt = ViewInterp.OriginTime[ (foundidx + 1) & ORIGIN_MASK ] - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK ]; - if ( dt > 0.0 ) + lastWeaponModelIndex = ent->curstate.weaponmodel; + lastViewModelIndex = V_FindViewModelByWeaponModel( lastWeaponModelIndex ); + if ( lastViewModelIndex ) { - frac = ( t - ViewInterp.OriginTime[ foundidx & ORIGIN_MASK] ) / dt; - frac = min( 1.0, frac ); - VectorSubtract( ViewInterp.Origins[ ( foundidx + 1 ) & ORIGIN_MASK ], ViewInterp.Origins[ foundidx & ORIGIN_MASK ], delta ); - VectorMA( ViewInterp.Origins[ foundidx & ORIGIN_MASK ], frac, delta, neworg ); - - // Dont interpolate large changes - if ( Length( delta ) < 64 ) - { - VectorSubtract( neworg, pparams->simorg, delta ); - - VectorAdd( pparams->vieworg, delta, pparams->vieworg ); - VectorCopy( pparams->vieworg, vecNewViewOrigin ); - } + gEngfuncs.pfnWeaponAnim(0,0); // reset weapon animation + } + else + { + // model not found + gunModel->model = NULL; // disable weapon model + lastWeaponModelIndex = lastViewModelIndex = 0; } } + + if ( lastViewModelIndex ) + { + gunModel->model = IEngineStudio.GetModelByIndex( lastViewModelIndex ); + gunModel->curstate.modelindex = lastViewModelIndex; + gunModel->curstate.frame = 0; + gunModel->curstate.colormap = 0; + gunModel->index = g_iUser2; + } + else + { + gunModel->model = NULL; // disable weaopn model + } } - } + else + { + // only get viewangles from entity + VectorCopy ( ent->angles, pparams->cl_viewangles ); + pparams->cl_viewangles[PITCH]*=-3.0f; // see CL_ProcessEntityUpdate() + } + } - - - - lasttime = pparams->time; - - view->model = NULL; + v_frametime = pparams->frametime; if ( pparams->nextView == 0 ) { - // first renderer cycle + // first renderer cycle, full screen - switch (gHUD.m_Spectator.m_iMainMode) + switch ( g_iUser1 ) { - case MAIN_MAP_FREE : pparams->onlyClientDraw = true; - - angles = pparams->cl_viewangles; - angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); - - VectorCopy ( angles, gHUD.m_Spectator.m_mapAngles ); - - gHUD.m_Spectator.GetMapPosition( pparams->vieworg ); - VectorCopy ( angles, pparams->viewangles ); + case OBS_CHASE_LOCKED: V_GetChasePos( g_iUser2, NULL, v_origin, v_angles ); + break; + + case OBS_CHASE_FREE: V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; + + case OBS_ROAMING : VectorCopy (v_cl_angles, v_angles); + VectorCopy (v_sim_org, v_origin); + break; + + case OBS_IN_EYE : V_CalcNormalRefdef ( pparams ); break; - - case MAIN_IN_EYE : V_GetInEyePos( gHUD.m_Spectator.m_iObserverTarget, - pparams->vieworg, pparams->viewangles ); + case OBS_MAP_FREE : pparams->onlyClientDraw = true; + V_GetMapFreePosition( v_cl_angles, v_origin, v_angles ); break; - default : pparams->onlyClientDraw = false; + case OBS_MAP_CHASE : pparams->onlyClientDraw = true; + V_GetMapChasePosition( g_iUser2, v_cl_angles, v_origin, v_angles ); break; } - if ( gHUD.m_Spectator.m_iInsetMode != INSET_OFF ) + if ( gHUD.m_Spectator.m_pip->value ) pparams->nextView = 1; // force a second renderer view gHUD.m_Spectator.m_iDrawCycle = 0; @@ -1126,7 +1558,7 @@ void V_CalcSpectatorRefdef ( struct ref_params_s *pparams ) } else { - // second renderer cycle + // second renderer cycle, inset window // set inset parameters pparams->viewport[0] = XRES(gHUD.m_Spectator.m_OverviewData.insetWindowX); // change viewport to inset window @@ -1134,36 +1566,42 @@ void V_CalcSpectatorRefdef ( struct ref_params_s *pparams ) pparams->viewport[2] = XRES(gHUD.m_Spectator.m_OverviewData.insetWindowWidth); pparams->viewport[3] = YRES(gHUD.m_Spectator.m_OverviewData.insetWindowHeight); pparams->nextView = 0; // on further view - pparams->onlyClientDraw = false; // override some settings in certain modes - switch (gHUD.m_Spectator.m_iInsetMode) + switch ( (int)gHUD.m_Spectator.m_pip->value ) { - case INSET_MAP_FREE : pparams->onlyClientDraw = true; - - angles = pparams->cl_viewangles; - angles[0] = 51.25f + 38.75f*(angles[0]/90.0f); - - VectorCopy ( angles, gHUD.m_Spectator.m_mapAngles ); + case INSET_CHASE_FREE : V_GetChasePos( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; - gHUD.m_Spectator.GetMapPosition( pparams->vieworg ); - - VectorCopy ( angles, pparams->viewangles ); + case INSET_IN_EYE : V_CalcNormalRefdef ( pparams ); break; - case INSET_IN_EYE : V_GetInEyePos( gHUD.m_Spectator.m_iObserverTarget, - pparams->vieworg, pparams->viewangles ); - + case INSET_MAP_FREE : pparams->onlyClientDraw = true; + V_GetMapFreePosition( v_cl_angles, v_origin, v_angles ); + break; + + case INSET_MAP_CHASE : pparams->onlyClientDraw = true; + + if ( g_iUser1 == OBS_ROAMING ) + V_GetMapChasePosition( 0, v_cl_angles, v_origin, v_angles ); + else + V_GetMapChasePosition( g_iUser2, v_cl_angles, v_origin, v_angles ); + break; } gHUD.m_Spectator.m_iDrawCycle = 1; } - v_angles = pparams->viewangles; - v_origin = pparams->vieworg; + // write back new values into pparams + VectorCopy ( v_cl_angles, pparams->cl_viewangles ); + VectorCopy ( v_angles, pparams->viewangles ) + VectorCopy ( v_origin, pparams->vieworg ); + } + + void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ) { // intermission / finale rendering @@ -1171,7 +1609,7 @@ void DLLEXPORT V_CalcRefdef( struct ref_params_s *pparams ) { V_CalcIntermissionRefdef ( pparams ); } - else if ( pparams->spectator ) + else if ( pparams->spectator || g_iUser1 ) // g_iUser true if in spectator mode { V_CalcSpectatorRefdef ( pparams ); } @@ -1248,6 +1686,7 @@ void V_Init (void) cl_bob = gEngfuncs.pfnRegisterVariable( "cl_bob","0.01", 0 );// best default for my experimental gun wag (sjb) cl_bobup = gEngfuncs.pfnRegisterVariable( "cl_bobup","0.5", 0 ); cl_waterdist = gEngfuncs.pfnRegisterVariable( "cl_waterdist","4", 0 ); + cl_chasedist = gEngfuncs.pfnRegisterVariable( "cl_chasedist","112", 0 ); } @@ -1322,7 +1761,7 @@ void V_Move( int mx, int my ) if ( tr.fraction != 1.0 && tr.ent != 0 ) { - hitent = PM_GetInfo( tr.ent ); + hitent = PM_GetPhysEntInfo( tr.ent ); PM_ParticleLine( (float *)&v_origin, (float *)&tr.endpos, 5, 1.0, 0.0 ); } else diff --git a/cl_dll/view.h b/cl_dll/view.h index 2590d54..acd0bfb 100644 --- a/cl_dll/view.h +++ b/cl_dll/view.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/cl_dll/wrect.h b/cl_dll/wrect.h index db879f6..620b816 100644 --- a/cl_dll/wrect.h +++ b/cl_dll/wrect.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/common/beamdef.h b/common/beamdef.h index 99059ae..f493391 100644 --- a/common/beamdef.h +++ b/common/beamdef.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/cl_entity.h b/common/cl_entity.h index 9cab3c9..b8fd373 100644 --- a/common/cl_entity.h +++ b/common/cl_entity.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/com_model.h b/common/com_model.h index 80f4d05..d8fcff4 100644 --- a/common/com_model.h +++ b/common/com_model.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/common/con_nprint.h b/common/con_nprint.h index 24426e2..caf4000 100644 --- a/common/con_nprint.h +++ b/common/con_nprint.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/const.h b/common/const.h index 6cc298f..e2cf6bc 100644 --- a/common/const.h +++ b/common/const.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -594,6 +594,10 @@ */ #define CONTENTS_LADDER -16 +#define CONTENT_FLYFIELD -17 +#define CONTENT_GRAVITY_FLYFIELD -18 +#define CONTENT_FOG -19 + #define CONTENT_EMPTY -1 #define CONTENT_SOLID -2 #define CONTENT_WATER -3 @@ -631,6 +635,7 @@ // Trains #define SF_TRAIN_WAIT_RETRIGGER 1 +#define SF_TRAIN_START_ON 4 // Train is initially moving #define SF_TRAIN_PASSABLE 8 // Train is not solid -- used to make water trains // buttons diff --git a/common/crc.h b/common/crc.h index d2215e6..15fdb0d 100644 --- a/common/crc.h +++ b/common/crc.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/cvardef.h b/common/cvardef.h index f7294f4..9e34363 100644 --- a/common/cvardef.h +++ b/common/cvardef.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/demo_api.h b/common/demo_api.h index 73a60e2..be20cc1 100644 --- a/common/demo_api.h +++ b/common/demo_api.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/director_cmds.h b/common/director_cmds.h index c5e7bdc..da99929 100644 --- a/common/director_cmds.h +++ b/common/director_cmds.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/common/dlight.h b/common/dlight.h index 372148a..0abc857 100644 --- a/common/dlight.h +++ b/common/dlight.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -18,7 +18,7 @@ #pragma once #endif -typedef struct +typedef struct dlight_s { vec3_t origin; float radius; diff --git a/common/dll_state.h b/common/dll_state.h index 5d0feda..a929c79 100644 --- a/common/dll_state.h +++ b/common/dll_state.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -17,6 +17,7 @@ #define DLL_NORMAL 0 // User hit Esc or something. #define DLL_QUIT 4 // Quit now +#define DLL_RESTART 6 // Switch to launcher for linux, does a quit but returns 1 // DLL Substate info ( not relevant ) #define ENG_NORMAL (1<<0) diff --git a/common/engine_launcher_api.h b/common/engine_launcher_api.h index 5d21c1c..6c81c8b 100644 --- a/common/engine_launcher_api.h +++ b/common/engine_launcher_api.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/common/entity_state.h b/common/entity_state.h index 01fbbd2..4625848 100644 --- a/common/entity_state.h +++ b/common/entity_state.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/entity_types.h b/common/entity_types.h index 10178a8..2ab96e3 100644 --- a/common/entity_types.h +++ b/common/entity_types.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/event_api.h b/common/event_api.h index 147eb7a..9e4a1fe 100644 --- a/common/event_api.h +++ b/common/event_api.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/event_args.h b/common/event_args.h index 554b313..8c41076 100644 --- a/common/event_args.h +++ b/common/event_args.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/event_flags.h b/common/event_flags.h index ba09ab7..d601e43 100644 --- a/common/event_flags.h +++ b/common/event_flags.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/exefuncs.h b/common/exefuncs.h index 0969947..353c015 100644 --- a/common/exefuncs.h +++ b/common/exefuncs.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -43,6 +43,8 @@ typedef struct exefuncs_s void (*unused23)(void); void (*unused24)(void); void (*unused25)(void); + void (*unused26)(void); + void (*unused27)(void); } exefuncs_t; #endif diff --git a/common/hltv.h b/common/hltv.h index f9901df..ff00d24 100644 --- a/common/hltv.h +++ b/common/hltv.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -8,37 +8,50 @@ // hltv.h // all shared consts between server, clients and proxy +#ifndef HLTV_H +#define HLTV_H + #define TYPE_CLIENT 0 // client is a normal HL client (default) #define TYPE_PROXY 1 // client is another proxy -#define TYPE_DIRECTOR 2 // client is a director #define TYPE_COMMENTATOR 3 // client is a commentator - - -#define HLTV_ACTIVE 0 // tells client that he's an spectator and will get director command +#define TYPE_DEMO 4 // client is a demo file +// sub commands of svc_hltv: +#define HLTV_ACTIVE 0 // tells client that he's an spectator and will get director commands #define HLTV_STATUS 1 // send status infos about proxy -#define HLTV_CAMERA 2 // set the actual director camera position -#define HLTV_EVENT 3 // informs the dircetor about ann important game event +#define HLTV_LISTEN 2 // tell client to listen to a multicast stream +// sub commands of svc_director: +#define DRC_CMD_NONE 0 // NULL director command +#define DRC_CMD_START 1 // start director mode +#define DRC_CMD_EVENT 2 // informs about director command +#define DRC_CMD_MODE 3 // switches camera modes +#define DRC_CMD_CAMERA 4 // sets camera registers +#define DRC_CMD_TIMESCALE 5 // sets time scale +#define DRC_CMD_MESSAGE 6 // send HUD centerprint +#define DRC_CMD_SOUND 7 // plays a particular sound +#define DRC_CMD_STATUS 8 // status info about broadcast +#define DRC_CMD_BANNER 9 // banner file name for HLTV gui +#define DRC_CMD_FADE 10 // send screen fade command +#define DRC_CMD_SHAKE 11 // send screen shake command +#define DRC_CMD_STUFFTEXT 12 // like the normal svc_stufftext but as director command -#define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) -#define DRC_FLAG_SIDE (1<<4) -#define DRC_FLAG_DRAMATIC (1<<5) +#define DRC_CMD_LAST 12 -// commands of the director API function CallDirectorProc(...) - -#define DRCAPI_NOP 0 // no operation -#define DRCAPI_ACTIVE 1 // de/acivates director mode in engine -#define DRCAPI_STATUS 2 // request proxy information -#define DRCAPI_SETCAM 3 // set camera n to given position and angle -#define DRCAPI_GETCAM 4 // request camera n position and angle -#define DRCAPI_DIRTIME 5 // set director time -#define DRCAPI_DIRSCALE 6 // set time scale -#define DRCAPI_SETVIEWMODE 7 // overview or 4 cameras -#define DRCAPI_SETOVERVIEWPARAMS 8 // sets parameter for overview mode -#define DRCAPI_SETFOCUS 9 // set the camera which has the input focus -#define DRCAPI_GETTARGETS 10 // queries engine for player list -#define DRCAPI_SETVIEWPOINTS 11 // gives engine all waypoints +// HLTV_EVENT event flags +#define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) +#define DRC_FLAG_SIDE (1<<4) // +#define DRC_FLAG_DRAMATIC (1<<5) // is a dramatic scene +#define DRC_FLAG_SLOWMOTION (1<<6) // would look good in SloMo +#define DRC_FLAG_FACEPLAYER (1<<7) // player is doning something (reload/defuse bomb etc) +#define DRC_FLAG_INTRO (1<<8) // is a introduction scene +#define DRC_FLAG_FINAL (1<<9) // is a final scene +#define DRC_FLAG_NO_RANDOM (1<<10) // don't randomize event data +#define MAX_DIRECTOR_CMD_PARAMETERS 4 +#define MAX_DIRECTOR_CMD_STRING 128 + + +#endif // HLTV_H diff --git a/common/in_buttons.h b/common/in_buttons.h index 151851a..7ed5650 100644 --- a/common/in_buttons.h +++ b/common/in_buttons.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/interface.cpp b/common/interface.cpp index 10f5051..980d7ff 100644 --- a/common/interface.cpp +++ b/common/interface.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -11,6 +11,8 @@ #ifndef _WIN32 // LINUX #include +#include // getcwd +#include // sprintf #endif @@ -142,7 +144,7 @@ CreateInterfaceFn Sys_GetFactory( HINTERFACEMODULE hModule ) if(!hModule) return NULL; - return dlsym( (void *)hModule, CREATEINTERFACE_PROCNAME ); + return (CreateInterfaceFn)dlsym( (void *)hModule, CREATEINTERFACE_PROCNAME ); } #endif diff --git a/common/interface.h b/common/interface.h index c8d2ba3..9b8e340 100644 --- a/common/interface.h +++ b/common/interface.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/common/ivoicetweak.h b/common/ivoicetweak.h index 9c39a5f..9bdb2bd 100644 --- a/common/ivoicetweak.h +++ b/common/ivoicetweak.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/common/mathlib.h b/common/mathlib.h index 36996c5..9fe0bff 100644 --- a/common/mathlib.h +++ b/common/mathlib.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -124,9 +124,16 @@ void AngleMatrix (const vec3_t angles, float (*matrix)[4] ); void AngleIMatrix (const vec3_t angles, float (*matrix)[4] ); void VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out); +void NormalizeAngles( vec3_t angles ); +void InterpolateAngles( vec3_t start, vec3_t end, vec3_t output, float frac ); +float AngleBetweenVectors( const vec3_t v1, const vec3_t v2 ); + + void VectorMatrix( vec3_t forward, vec3_t right, vec3_t up); void VectorAngles( const vec3_t forward, vec3_t angles ); +int InvertMatrix( const float * m, float *out ); + int BoxOnPlaneSide (vec3_t emins, vec3_t emaxs, struct mplane_s *plane); float anglemod(float a); diff --git a/common/net_api.h b/common/net_api.h index eda3073..832568e 100644 --- a/common/net_api.h +++ b/common/net_api.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/common/netadr.h b/common/netadr.h index a0aba04..5cbf0ea 100644 --- a/common/netadr.h +++ b/common/netadr.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/nowin.h b/common/nowin.h index ccfb7e7..d386fb9 100644 --- a/common/nowin.h +++ b/common/nowin.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/common/particledef.h b/common/particledef.h index 218fe48..b1356d2 100644 --- a/common/particledef.h +++ b/common/particledef.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/pmtrace.h b/common/pmtrace.h index 7e3e77c..8d33780 100644 --- a/common/pmtrace.h +++ b/common/pmtrace.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/qfont.h b/common/qfont.h index 4d57866..14d846e 100644 --- a/common/qfont.h +++ b/common/qfont.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/r_efx.h b/common/r_efx.h index dae73dc..3db1084 100644 --- a/common/r_efx.h +++ b/common/r_efx.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/cl_dll/r_studioint.h b/common/r_studioint.h similarity index 80% rename from cl_dll/r_studioint.h rename to common/r_studioint.h index 4a76fb4..17e20b3 100644 --- a/cl_dll/r_studioint.h +++ b/common/r_studioint.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -103,6 +103,20 @@ typedef struct engine_studio_api_s void ( *GL_SetRenderMode ) ( int mode ); } engine_studio_api_t; +typedef struct server_studio_api_s +{ + // Allocate number*size bytes and zero it + void *( *Mem_Calloc ) ( int number, size_t size ); + // Check to see if pointer is in the cache + void *( *Cache_Check ) ( struct cache_user_s *c ); + // Load file into cache ( can be swapped out on demand ) + void ( *LoadCacheFile ) ( char *path, struct cache_user_s *cu ); + // Retrieve pointer to studio model data block from a model + void *( *Mod_Extradata ) ( struct model_s *mod ); +} server_studio_api_t; + + +// client blending typedef struct r_studio_interface_s { int version; @@ -112,4 +126,22 @@ typedef struct r_studio_interface_s extern r_studio_interface_t *pStudioAPI; +// server blending +#define SV_BLENDING_INTERFACE_VERSION 1 + +typedef struct sv_blending_interface_s +{ + int version; + + void ( *SV_StudioSetupBones )( struct model_s *pModel, + float frame, + int sequence, + const vec3_t angles, + const vec3_t origin, + const byte *pcontroller, + const byte *pblending, + int iBone, + const edict_t *pEdict ); +} sv_blending_interface_t; + #endif // R_STUDIOINT_H \ No newline at end of file diff --git a/common/ref_params.h b/common/ref_params.h index f7f41d1..10e8837 100644 --- a/common/ref_params.h +++ b/common/ref_params.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/screenfade.h b/common/screenfade.h index cc4e508..d1d7126 100644 --- a/common/screenfade.h +++ b/common/screenfade.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/common/studio_event.h b/common/studio_event.h index 62b3287..5ecf24f 100644 --- a/common/studio_event.h +++ b/common/studio_event.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/triangleapi.h b/common/triangleapi.h index dc9772c..7a4679f 100644 --- a/common/triangleapi.h +++ b/common/triangleapi.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -14,7 +14,9 @@ ****/ #if !defined( TRIANGLEAPIH ) #define TRIANGLEAPIH +#ifdef _WIN32 #pragma once +#endif typedef enum { @@ -50,6 +52,8 @@ typedef struct triangleapi_s int ( *SpriteTexture ) ( struct model_s *pSpriteModel, int frame ); int ( *WorldToScreen ) ( float *world, float *screen ); // Returns 1 if it's z clipped void ( *Fog ) ( float flFogColor[3], float flStart, float flEnd, int bOn ); //Works just like GL_FOG, flFogColor is r/g/b. + void ( *ScreenToWorld ) ( float *screen, float *world ); + } triangleapi_t; #endif // !TRIANGLEAPIH diff --git a/common/usercmd.h b/common/usercmd.h index eb6b8d2..ab88ddb 100644 --- a/common/usercmd.h +++ b/common/usercmd.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/common/weaponinfo.h b/common/weaponinfo.h index 8571046..6808aeb 100644 --- a/common/weaponinfo.h +++ b/common/weaponinfo.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dedicated/Dedicated.dsp b/dedicated/Dedicated.dsp index 8d61c5c..2e9eddb 100644 --- a/dedicated/Dedicated.dsp +++ b/dedicated/Dedicated.dsp @@ -23,8 +23,8 @@ CFG=Dedicated - Win32 Release # Begin Project # PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName ""$/GoldSrc/dedicated", DJEBAAAA" -# PROP Scc_LocalPath "." +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" CPP=cl.exe MTL=midl.exe RSC=rc.exe @@ -55,16 +55,6 @@ 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 /machine:I386 # ADD LINK32 wsock32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib ..\utils\procinfo\lib\win32_vc6\procinfo.lib /nologo /subsystem:windows /machine:I386 /out:".\Release/hlds.exe" # SUBTRACT LINK32 /map /debug -# Begin Custom Build -ProjDir=. -InputPath=.\Release\hlds.exe -InputName=hlds -SOURCE="$(InputPath)" - -"$(ProjDir)\..\..\$(InputName).exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" - xcopy $(InputPath) $(ProjDir)\..\..\ /R /Q - -# End Custom Build !ELSEIF "$(CFG)" == "Dedicated - Win32 Debug" @@ -91,16 +81,6 @@ BSC32=bscmake.exe 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 /debug /machine:I386 # ADD LINK32 wsock32.lib kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib odbccp32.lib winmm.lib ..\utils\procinfo\lib\win32_vc6\procinfo.lib /nologo /subsystem:windows /map /debug /machine:I386 /out:".\Debug/hlds.exe" -# Begin Custom Build -ProjDir=. -InputPath=.\Debug\hlds.exe -InputName=hlds -SOURCE="$(InputPath)" - -"$(ProjDir)\..\..\$(InputName).exe" : $(SOURCE) "$(INTDIR)" "$(OUTDIR)" - xcopy $(InputPath) $(ProjDir)\..\..\ /R /Q - -# End Custom Build !ENDIF @@ -125,10 +105,6 @@ SOURCE=.\engine.cpp # End Source File # Begin Source File -SOURCE=.\md5.cpp -# End Source File -# Begin Source File - SOURCE=.\sys_ded.cpp # End Source File # End Group @@ -149,10 +125,6 @@ SOURCE=.\dedicated.h # End Source File # Begin Source File -SOURCE=.\md5.h -# End Source File -# Begin Source File - SOURCE=.\resource.h # End Source File # Begin Source File diff --git a/dedicated/Makefile b/dedicated/Makefile index 43a47ad..42b4a81 100644 --- a/dedicated/Makefile +++ b/dedicated/Makefile @@ -1,5 +1,5 @@ # -# hlds_run (front end for hlds_l) Makefile for Linux i386 +# hlds (front end for hlds_l) Makefile for Linux i386 # # May 2000, Leon Hartwig (hartwig@valvesoftware.com) # @@ -40,16 +40,15 @@ $(OBJDIR)/%.o: $(SRCDIR)/%.cpp OBJ = \ $(OBJDIR)/sys_ded.o \ - $(OBJDIR)/engine.o \ - $(OBJDIR)/md5.o + $(OBJDIR)/engine.o -hlds_run : neat $(OBJ) +hlds : neat $(OBJ) $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $(OBJ) neat: -mkdir $(OBJDIR) clean: -rm -f $(OBJ) - -rm -f hlds_run + -rm -f hlds spotless: clean -rm -r $(OBJDIR) diff --git a/dedicated/conproc.cpp b/dedicated/conproc.cpp index 0cd710c..0a42c9b 100644 --- a/dedicated/conproc.cpp +++ b/dedicated/conproc.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/dedicated/conproc.h b/dedicated/conproc.h index c276835..0376ce8 100644 --- a/dedicated/conproc.h +++ b/dedicated/conproc.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/dedicated/dedicated.h b/dedicated/dedicated.h index e370034..e10fcee 100644 --- a/dedicated/dedicated.h +++ b/dedicated/dedicated.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -15,10 +15,8 @@ void Eng_Unload ( void); void Eng_SetState ( int ); void Eng_SetSubState ( int ); - char *CheckParm ( const char *psz, char **ppszValue = (char **)0 ); - extern int gDLLState; extern int gDLLStateInfo; diff --git a/dedicated/engine.cpp b/dedicated/engine.cpp index 8810161..ea81e03 100644 --- a/dedicated/engine.cpp +++ b/dedicated/engine.cpp @@ -1,16 +1,17 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // // $NoKeywords: $ //============================================================================= - +#include #ifdef _WIN32 #include +#define snprintf _snprintf #else -#include #include #include +#include // exit() #endif #include "dedicated.h" @@ -28,8 +29,15 @@ long ghMod = 0; static engine_api_t nullapi; engine_api_t engineapi = nullapi; +extern SleepType Sys_Sleep; // setup by sys_ded.cpp + typedef int (*engine_api_func)( int version, int size, struct engine_api_s *api ); +#ifdef _WIN32 +// This is a big chunk of uninitialized memory that we reserve for loading blobs into. +char g_rgchBlob[0x03800000]; +#endif // _WIN32 + /* ============== Eng_LoadFunctions @@ -120,7 +128,7 @@ int Eng_Load( const char *cmdline, struct exefuncs_s *pef, int memory, void *pme #if defined( _DEBUG ) char *p; - if ( psz && !stricmp( psz, "sw.dll" ) && CheckParm( "-force", &p ) && p ) + if ( psz && !stricmp( psz, "swds.dll" ) && CheckParm( "-force", &p ) && p ) { psz = p; } @@ -164,7 +172,7 @@ int Eng_Load( const char *cmdline, struct exefuncs_s *pef, int memory, void *pme Eng_SetSubState( iSubMode ); - strcpy( szLastDLL, psz ); + snprintf( szLastDLL, sizeof( szLastDLL ), "%s", psz ); ghMod = hMod; @@ -232,6 +240,14 @@ int Eng_Frame( int fForce, double time ) PostQuitMessage(0); #else exit( 0 ); +#endif + break; + case DLL_RESTART: + Eng_Unload(); +#ifdef _WIN32 + PostQuitMessage(1); +#else + exit( 1 ); #endif break; default: @@ -277,7 +293,7 @@ int Eng_Frame( int fForce, double time ) } } - if ( gDLLState == DLL_CLOSE ) + if ( gDLLState == DLL_CLOSE || gDLLState == DLL_RESTART) { static int bQuitting = 0; @@ -297,7 +313,14 @@ int Eng_Frame( int fForce, double time ) #ifdef _WIN32 PostQuitMessage(0); #else - exit( 0 ); + if ( gDLLState == DLL_RESTART ) + { + exit( 1 ); + } + else + { + exit( 0 ); + } #endif } diff --git a/dedicated/enginecallback.h b/dedicated/enginecallback.h index 8f60454..cef4c64 100644 --- a/dedicated/enginecallback.h +++ b/dedicated/enginecallback.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/dedicated/exports.h b/dedicated/exports.h index 301647f..01e209a 100644 --- a/dedicated/exports.h +++ b/dedicated/exports.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/dedicated/md5.cpp b/dedicated/md5.cpp deleted file mode 100644 index da652e6..0000000 --- a/dedicated/md5.cpp +++ /dev/null @@ -1,324 +0,0 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ -// -// Purpose: -// -// $NoKeywords: $ -//============================================================================= - -/////////////////////////// -// md5.cpp : MD5 hashing functions -// -#include -#include -#include -#include "md5.h" - -// MD5 Hash -typedef struct -{ - unsigned int buf[ 4 ]; - unsigned int bits[ 2 ]; - unsigned char in[ 64 ]; -} MD5Context_t; - -/* The four core functions - F1 is optimized somewhat */ -/* #define F1(x, y, z) (x & y | ~x & z) */ -#define F1(x, y, z) (z ^ (x & (y ^ z))) -#define F2(x, y, z) F1(z, x, y) -#define F3(x, y, z) (x ^ y ^ z) -#define F4(x, y, z) (y ^ (x | ~z)) - -/* This is the central step in the MD5 algorithm. */ -#define MD5STEP(f, w, x, y, z, data, s) ( w += f(x, y, z) + data, w = w<>(32-s), w += x ) - -/* -================= -MD5Transform - -The core of the MD5 algorithm, this alters an existing MD5 hash to -reflect the addition of 16 longwords of new data. MD5Update blocks -the data and converts bytes into longwords for this routine. -================= -*/ -void MD5Transform( unsigned int buf[4], unsigned int const in[16] ) -{ - register unsigned int a, b, c, d; - - a = buf[0]; - b = buf[1]; - c = buf[2]; - d = buf[3]; - - MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); - MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); - MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); - MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); - MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); - MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); - MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); - MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); - MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); - MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); - MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); - MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); - MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); - MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); - MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); - MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); - - MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); - MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); - MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); - MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); - MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); - MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); - MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); - MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); - MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); - MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); - MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); - MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); - MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); - MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); - MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); - MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); - - MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); - MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); - MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); - MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); - MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); - MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); - MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); - MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); - MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); - MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); - MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); - MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); - MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); - MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); - MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); - MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); - - MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); - MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); - MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); - MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); - MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); - MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); - MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); - MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); - MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); - MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); - MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); - MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); - MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); - MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); - MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); - MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); - - buf[0] += a; - buf[1] += b; - buf[2] += c; - buf[3] += d; -} - -/* -================== -MD5Init - -Start MD5 accumulation. Set bit count to 0 and buffer to mysterious initialization constants. -================== -*/ -void MD5Init(MD5Context_t *ctx) -{ - ctx->buf[0] = 0x67452301; - ctx->buf[1] = 0xefcdab89; - ctx->buf[2] = 0x98badcfe; - ctx->buf[3] = 0x10325476; - - ctx->bits[0] = 0; - ctx->bits[1] = 0; -} - -/* -=================== -MD5Update - -Update context to reflect the concatenation of another buffer full of bytes. -=================== -*/ -void MD5Update( MD5Context_t *ctx, unsigned char const *buf, unsigned int len ) -{ - unsigned int t; - - /* Update bitcount */ - - t = ctx->bits[0]; - if ((ctx->bits[0] = t + ((unsigned int) len << 3)) < t) - ctx->bits[1]++; /* Carry from low to high */ - ctx->bits[1] += len >> 29; - - t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */ - - /* Handle any leading odd-sized chunks */ - - if (t) - { - unsigned char *p = (unsigned char *) ctx->in + t; - - t = 64 - t; - if (len < t) - { - memcpy(p, buf, len); - return; - } - memcpy(p, buf, t); - //byteReverse(ctx->in, 16); - MD5Transform(ctx->buf, (unsigned int *) ctx->in); - buf += t; - len -= t; - } - /* Process data in 64-byte chunks */ - - while (len >= 64) - { - memcpy(ctx->in, buf, 64); - //byteReverse(ctx->in, 16); - MD5Transform(ctx->buf, (unsigned int *) ctx->in); - buf += 64; - len -= 64; - } - - /* Handle any remaining bytes of data. */ - memcpy(ctx->in, buf, len); -} - -/* -=============== -MD5Final - -Final wrapup - pad to 64-byte boundary with the bit pattern -1 0* (64-bit count of bits processed, MSB-first) -=============== -*/ -void MD5Final(unsigned char digest[16], MD5Context_t *ctx) -{ - unsigned count; - unsigned char *p; - - /* Compute number of bytes mod 64 */ - count = (ctx->bits[0] >> 3) & 0x3F; - - /* Set the first char of padding to 0x80. This is safe since there is - always at least one byte free */ - p = ctx->in + count; - *p++ = 0x80; - - /* Bytes of padding needed to make 64 bytes */ - count = 64 - 1 - count; - - /* Pad out to 56 mod 64 */ - if (count < 8) - { - /* Two lots of padding: Pad the first block to 64 bytes */ - memset(p, 0, count); - //byteReverse(ctx->in, 16); - MD5Transform(ctx->buf, (unsigned int *) ctx->in); - - /* Now fill the next block with 56 bytes */ - memset(ctx->in, 0, 56); - } - else - { - /* Pad block to 56 bytes */ - memset(p, 0, count - 8); - } - //byteReverse(ctx->in, 14); - - /* Append length in bits and transform */ - ((unsigned int *) ctx->in)[14] = ctx->bits[0]; - ((unsigned int *) ctx->in)[15] = ctx->bits[1]; - - MD5Transform(ctx->buf, (unsigned int *) ctx->in); - //byteReverse((unsigned char *) ctx->buf, 4); - memcpy(digest, ctx->buf, 16); - memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ -} - -/* -=================== -MD5_Hash_File - - ================== -*/ -int MD5_Hash_File(unsigned char digest[16], char *pszFileName ) -{ - FILE *fp; - unsigned char chunk[1024]; - int bytesread; - MD5Context_t ctx; - int size; - - memset( &ctx, 0, sizeof(MD5Context_t) ); - - MD5Init( &ctx ); - - fp = fopen( pszFileName, "rb" ); - if ( !fp ) - return 0; - - fseek ( fp, 0, SEEK_END ); - size = ftell ( fp ); - fseek ( fp, 0, SEEK_SET ); - - if ( size <= 0 ) - { - fclose ( fp ); - return 0; - } - - // Now read in 1K chunks - while ( size > 0 ) - { - if ( size > 1024 ) - { - bytesread = fread(chunk, 1, 1024, fp); - } - else - { - bytesread = fread(chunk, 1, size, fp); - } - - // If any data was received, CRC it. - if ( bytesread > 0 ) - { - size -= bytesread; - MD5Update(&ctx, chunk, bytesread); - } - - // We we are end of file, break loop and return - if ( feof( fp ) ) - { - break; - } - - // If there was a disk error, indicate failure. - if ( ferror(fp) ) - { - fclose(fp); - return 0; - } - } - - if ( fp ) - { - fclose(fp); - } - - MD5Final( digest, &ctx ); - - return 1; -} - - diff --git a/dedicated/md5.h b/dedicated/md5.h deleted file mode 100644 index f8739a6..0000000 --- a/dedicated/md5.h +++ /dev/null @@ -1,14 +0,0 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ -// -// Purpose: -// -// $NoKeywords: $ -//============================================================================= - -#if !defined( MD5H ) -#define MD5H -#pragma once - -int MD5_Hash_File(unsigned char digest[16], char *pszFileName ); - -#endif \ No newline at end of file diff --git a/dedicated/resource.h b/dedicated/resource.h index 4ea452b..0eb2765 100644 --- a/dedicated/resource.h +++ b/dedicated/resource.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/dedicated/sys_ded.cpp b/dedicated/sys_ded.cpp index c702b12..46f9ced 100644 --- a/dedicated/sys_ded.cpp +++ b/dedicated/sys_ded.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -7,16 +7,23 @@ #ifdef _WIN32 #include +#define snprintf _snprintf +#define vsnprintf _vsnprintf #else -#include #include #include #include +#include #include #include #include +#include +#include +#include #endif +//#define FRAMERATE // define me to have hlds print out what it thinks the framerate is + #include #include @@ -24,34 +31,26 @@ #include "conproc.h" #include "dedicated.h" #include "exefuncs.h" -#include "crc.h" #include "dll_state.h" #include "enginecallback.h" -#include "md5.h" #if defined( _WIN32 ) #include "../utils/procinfo/procinfo.h" #endif #ifdef _WIN32 -static const char *g_pszengine = "sw.dll"; +static HANDLE hinput; +static HANDLE houtput; + +static const char *g_pszengine = "swds.dll"; #else static const char *g_pszengine = "engine_i386.so"; static char g_szEXEName[ 256 ]; #endif -#define MINIMUM_WIN_MEMORY 0x0c00000 -#define MAXIMUM_WIN_MEMORY 0x2000000 // max 32 mb -#define FIFTEEN_MEGS ( 15 * 1024 * 1024 ) - -#ifdef _WIN32 -static HANDLE hinput; -static HANDLE houtput; -#endif - // System Memory & Size static unsigned char *gpMemBase = NULL; -static int giMemSize = 0x1800000; // 24 Mb Linux heapsize +static int giMemSize = 0x2000000; // 32 Mb default heapsize exefuncs_t ef; @@ -62,7 +61,10 @@ static long hDLLThirdParty = 0L; char *gpszCmdLine = NULL; -void Sys_Sleep( int msec ) +SleepType Sys_Sleep; + + +void Sys_Sleep_Old( int msec ) { #ifdef _WIN32 Sleep( msec ); @@ -70,6 +72,90 @@ void Sys_Sleep( int msec ) usleep(msec * 1000); #endif } +#if !defined ( _WIN32 ) + +typedef int (*NET_Sleep_t)( void ); +NET_Sleep_t NET_Sleep =NULL; +extern long ghMod; // from engine.cpp + +void Sys_Sleep_Net( int msec ) +{ + if ( NET_Sleep != NULL) + { + NET_Sleep(); + } + else + { + Sys_Sleep_Old(msec); // NET_Sleep isn't hooked yet, fallback to the old method + } +} + +volatile char paused=0; // this checks if pause has run yet, tell the compiler it can change at any time +// for Sys_Sleep_Timer() +void alarmFunc(int num) { + signal( SIGALRM, alarmFunc ); // reset the signal handler + if(!paused) { // paused is 0, the timer has fired before the pause was called... Lets queue it again + struct itimerval itim; + itim.it_interval.tv_sec = 0; + itim.it_interval.tv_usec = 0; + itim.it_value.tv_sec = 0; + itim.it_value.tv_usec = 1000; // get it to run again real soon + setitimer( ITIMER_REAL, &itim, 0 ); + } + +} +#endif + +void Sys_Sleep_Timer( int msec ) +{ +#ifdef _WIN32 + Sleep( msec ); +#else +// linux runs on a 100Hz scheduling clock, so the minimum latency from +// usleep is 10msec. However, people want lower latency than this.. +// +// There are a few solutions, one is to use the realtime scheduler in the +// kernel BUT this needs root privelleges to start. It also can play +// unfriendly with other programs. + +// Another solution is to use software timers, they use the RTC of the +// system and are accurate to microseconds (or so). + +// timers, via setitimer() are used here + + struct itimerval tm; + + tm.it_value.tv_sec=msec/1000; // convert msec to seconds + tm.it_value.tv_usec=(msec%1000)*1E3; // get the number of msecs and change to micros + tm.it_interval.tv_sec = 0; + tm.it_interval.tv_usec = 0; + + paused=0; + if( setitimer(ITIMER_REAL,&tm,NULL)==0) + { // set the timer to trigger + pause(); // wait for the signal + } + paused=1; + +#endif +} + + +void Sys_Sleep_Select( int msec ) +{ +#ifdef _WIN32 + Sleep( msec ); +#else // _WIN32 + struct timeval tv; + + // Assumes msec < 1000 + tv.tv_sec = 0; + tv.tv_usec = 1000 * msec; + + select( 1, NULL, NULL, NULL, &tv ); +#endif // _WIN32 +} + void *Sys_GetProcAddress( long library, const char *name ) { @@ -96,7 +182,7 @@ long Sys_LoadLibrary( char *lib ) if (cwd[strlen(cwd)-1] == '/') cwd[strlen(cwd)-1] = 0; - sprintf(absolute_lib, "%s/%s", cwd, lib); + snprintf(absolute_lib, sizeof(absolute_lib), "%s/%s", cwd, lib); hDll = dlopen( absolute_lib, RTLD_NOW ); if ( !hDll ) @@ -182,7 +268,7 @@ void UpdateStatus( int force ) tLast = tCurrent; - sprintf( szPrompt, "%.1f fps %2i(%2i spec)/%2i on %16s", (float)fps, n, spec, nMax, szMap); + snprintf( szPrompt, sizeof(szPrompt), "%.1f fps %2i(%2i spec)/%2i on %16s", (float)fps, n, spec, nMax, szMap); WriteStatusText( szPrompt ); } @@ -237,7 +323,7 @@ void Sys_Printf(char *fmt, ...) char szText[1024]; va_start (argptr, fmt); - vsprintf (szText, fmt, argptr); + vsnprintf (szText, sizeof(szText), fmt, argptr); va_end (argptr); // Get Current text and append it. @@ -261,115 +347,6 @@ void Load3rdParty( void ) } } -/* -============ -StripExtension - -Strips the extension off a filename. Works backward to insure stripping -of extensions only, and not parts of the path that might contain a -period (i.e. './hlds_run'). -============ -*/ -void StripExtension (char *in, char *out) -{ - char * in_current = in + strlen(in); - char * out_current = out + strlen(in); - int found_extension = 0; - - while (in_current >= in) { - if ((found_extension == 0) && (*in_current == '.')) { - *out_current = 0; - found_extension = 1; - } - else { - if ((*in_current == '/') || (*in_current == '\\')) - found_extension = 1; - - *out_current = *in_current; - } - - in_current--; - out_current--; - } -} - -/* -============== -CheckExeChecksum - -Simple self-crc check -============== -*/ -int CheckExeChecksum( void ) -{ - unsigned char g_MD5[16]; - char szFileName[ 256 ]; - unsigned int newdat = 0; - unsigned int olddat; - char datfile[ 256 ]; - - // Get our filename - if ( !Sys_GetExecutableName( szFileName ) ) - { - return 0; - } - - // compute raw 16 byte hash value - if ( !MD5_Hash_File( g_MD5, szFileName ) ) - { - return 0; - } - - StripExtension( szFileName, datfile ); - - strcat( datfile, ".dat" ); - - // Check .dat file ( or write a new one if running with -newdat ) - FILE *fp = fopen( datfile, "rb" ); - if ( !fp || CheckParm( "-newdat" ) ) // No existing file, or we are asked to create a new one - { - if ( fp ) - { - fclose( fp ); - } - - newdat = *(unsigned int *)&g_MD5[0]; - fp = fopen ( datfile, "wb" ); - if ( fp ) - { - fwrite( &newdat, sizeof( unsigned int ), 1, fp ); - fclose( fp ); - } - } - else - { - int bOk = 0; - - if ( fread( &newdat, sizeof( unsigned int ), 1, fp ) == 1 ) - bOk = 1; - - fclose( fp ); - - if ( bOk ) - { - olddat = *(unsigned int *)&g_MD5[0]; - if ( olddat != newdat ) - { - const char *pmsg = "Your Half-Life executable appears to have been modified. Please check your system for viruses and then re-install Half-Life."; - -#ifdef _WIN32 - MessageBox( NULL, pmsg, "Half-Life Dedicated Server", MB_OK ); -#else - printf( "%s\n", pmsg ); -#endif - return 0; - } - } - } - - return 1; -} - /* ============== EF_VID_ForceUnlockedAndReturnState @@ -450,11 +427,6 @@ int InitInstance( void ) { Load3rdParty(); -#if !defined( _DEBUG ) - if ( !CheckExeChecksum() ) - return 0; -#endif - Eng_SetState( DLL_INACTIVE ); memset( &ef, 0, sizeof( ef ) ); @@ -557,40 +529,45 @@ char *Sys_ConsoleInput (void) #else char *Sys_ConsoleInput(void) { - char ch; - int len; - fd_set fdset; - struct timeval timeout; - char szInput[256]; - char iInput = 0; + struct timeval tvTimeout; + fd_set fdSet; + unsigned char ch; + + FD_ZERO( &fdSet ); + FD_SET( STDIN_FILENO, &fdSet ); - FD_ZERO(&fdset); - FD_SET(0, &fdset); // stdin - timeout.tv_sec = 0; - timeout.tv_usec = 0; - if (select (1, &fdset, NULL, NULL, &timeout) == -1 || !FD_ISSET(0, &fdset)) + tvTimeout.tv_sec = 0; + tvTimeout.tv_usec = 0; + + if ( select( 1, &fdSet, NULL, NULL, &tvTimeout ) == -1 || !FD_ISSET( STDIN_FILENO, &fdSet ) ) return NULL; - while (read(0,&ch,1)) + console_textlen = 0; + + // We're going to shove a newline onto the end of the input later in + // ProcessConsoleInput(), so only accept 254 chars instead of 255. -LH + + while ( read( STDIN_FILENO, &ch, 1 ) ) { - if (iInput >= 255) - continue; - - if (ch == 10) + if ( ( ch == 10 ) || ( console_textlen == 254 ) ) { - char *pszret = NULL; + // For commands longer than we can accept, consume the remainder of the input buffer + if ( ( console_textlen == 254 ) && ( ch != 10 ) ) + { + while ( read( STDIN_FILENO, &ch, 1 ) ) + { + if ( ch == 10 ) + break; + } + } - //Null terminate string and return if we have anything - szInput[iInput] = 0; - - if (iInput > 0) - pszret = szInput; - - iInput = 0; - return pszret; + //Null terminate string and return + console_text[ console_textlen ] = 0; + console_textlen = 0; + return console_text; } - szInput[iInput++] = ch; + console_text[ console_textlen++ ] = ch; } return NULL; @@ -618,8 +595,8 @@ void WriteStatusText( char *szText ) wAttrib[i] = FOREGROUND_RED | FOREGROUND_INTENSITY; } - memset( szFullLine, 0, 81 ); - strcpy( szFullLine, szText ); + memset( szFullLine, 0, sizeof(szFullLine) ); + snprintf( szFullLine, sizeof( szFullLine ), "%s", szText ); coord.X = 0; coord.Y = 0; @@ -688,7 +665,7 @@ void ProcessConsoleInput( void ) if (s) { char szBuf[ 256 ]; - sprintf( szBuf, "%s\n", s ); + snprintf( szBuf, sizeof(szBuf), "%s\n", s ); engineapi.Cbuf_AddText ( szBuf ); } } while (s); @@ -702,39 +679,6 @@ GameInit int GameInit(void) { char *p; -#ifdef _WIN32 - MEMORYSTATUS Buffer; - - memset( &Buffer, 0, sizeof( Buffer ) ); - Buffer.dwLength = sizeof( MEMORYSTATUS ); - - GlobalMemoryStatus ( &Buffer ); - - // take the greater of all the available memory or half the total memory, - // but at least 10 Mb and no more than 32 Mb, unless they explicitly - // request otherwise - giMemSize = Buffer.dwTotalPhys; - - if ( giMemSize < FIFTEEN_MEGS ) - { - return 0; - } - - if ( giMemSize < (int)( Buffer.dwTotalPhys >> 1 ) ) - { - giMemSize = (int)( Buffer.dwTotalPhys >> 1 ); - } - - // At least 10 mb, even if we have to swap a lot. - if (giMemSize <= MINIMUM_WIN_MEMORY) - { - giMemSize = MINIMUM_WIN_MEMORY; - } - else if (giMemSize > MAXIMUM_WIN_MEMORY) - { - giMemSize = MAXIMUM_WIN_MEMORY; - } -#endif // Command line override if ( (CheckParm ("-heapsize", &p ) ) && p ) @@ -742,12 +686,37 @@ int GameInit(void) giMemSize = atoi( p ) * 1024; } - // Command line to force running with minimal memory. - if (CheckParm ("-minmemory", NULL)) + Sys_Sleep=Sys_Sleep_Old; + +#if !defined ( _WIN32 ) + char *pPingType; + int type; + if ( (CheckParm ("-pingboost", &pPingType)) && pPingType ) { - giMemSize = MINIMUM_WIN_MEMORY; + type=atoi( pPingType ); + switch( type ) { + case 1: + //printf("Using timer method\n"); + signal(SIGALRM,alarmFunc); + Sys_Sleep=Sys_Sleep_Timer; + break; + case 2: + Sys_Sleep = Sys_Sleep_Select; + break; + case 3: + Sys_Sleep = Sys_Sleep_Net; + // we Sys_GetProcAddress NET_Sleep() from + //engine_i386.so later in this function + break; + default: // just in case + Sys_Sleep=Sys_Sleep_Old; + break; + } + } +#endif + // Try and allocated it #ifdef _WIN32 gpMemBase = (unsigned char *)::GlobalAlloc( GMEM_FIXED, giMemSize ); @@ -781,6 +750,14 @@ int GameInit(void) return 0; } +#if !defined ( _WIN32 ) + if ( type == 3 ) + { + NET_Sleep=(NET_Sleep_t)Sys_GetProcAddress(ghMod,"NET_Sleep_Timeout"); + //printf("Net_Sleep:%p\n",NET_Sleep); + } +#endif + Eng_SetState( DLL_ACTIVE ); return 1; @@ -808,6 +785,25 @@ void GameShutdown( void ) } #ifdef _WIN32 + +BOOL gbAppHasBeenTerminated = FALSE; +BOOL CtrlHandler(DWORD fdwCtrlType) +{ + switch ( fdwCtrlType ) + { + case CTRL_C_EVENT: + case CTRL_CLOSE_EVENT: + case CTRL_BREAK_EVENT: + case CTRL_LOGOFF_EVENT: + case CTRL_SHUTDOWN_EVENT: + gbAppHasBeenTerminated = TRUE; + return TRUE; + + default: + return FALSE; + } +} + /* ============== WinMain @@ -822,6 +818,8 @@ int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdL // Store off command line for argument searching gpszCmdLine = strdup( GetCommandLine() ); + Sys_Sleep=Sys_Sleep_Old; // win32 doesn't have pingbooster options :) + if ( !InitInstance() ) { goto cleanup; @@ -852,6 +850,9 @@ int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdL double newtime; double dtime; + if ( gbAppHasBeenTerminated ) + break; + // Try to allow other apps to get some CPU Sys_Sleep( 1 ); @@ -868,6 +869,9 @@ int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdL dtime = newtime - oldtime; + if ( gbAppHasBeenTerminated ) + break; + if ( dtime > 0.001 ) break; @@ -875,8 +879,12 @@ int PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdL Sys_Sleep( 1 ); } + while ( ::PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) ) { + if ( gbAppHasBeenTerminated ) + break; + if (!::GetMessage( &msg, NULL, 0, 0)) { bDone = 1; @@ -962,10 +970,11 @@ int main(int argc, char **argv) int iret = -1; #ifdef _DEBUG - strcpy(g_szEXEName, "hlds_run.dbg" ); + snprintf(g_szEXEName, sizeof( g_szEXEName ), "hlds_run.dbg" ); #else - strcpy(g_szEXEName, *argv); + snprintf(g_szEXEName, sizeof( g_szEXEName ), "%s", *argv ); #endif + // Store off command line for argument searching BuildCmdLine(argc, argv); gpszCmdLine = strdup( GetCommandLine() ); @@ -998,7 +1007,29 @@ int main(int argc, char **argv) double newtime; double dtime; - // Try to allow other apps to get some CPU +#ifdef FRAMERATE + static int frameNumber=0,frameRate=0; + static struct timeval beforeTime; + if(frameRate==0) { + gettimeofday(&beforeTime,NULL); + } + + frameRate++; + + if(frameRate%1000==0) { + struct timeval afterTime; + float timediff; + + gettimeofday(&afterTime,NULL); + timediff= (float)(afterTime.tv_sec-beforeTime.tv_sec)+ + ((float)(afterTime.tv_usec-beforeTime.tv_usec))/1E6; + + printf("Frame Rate:%f\n",(float)frameRate/timediff); + frameRate=0; + + } +#endif + Sys_Sleep( 1 ); if ( !engineapi.Sys_FloatTime ) @@ -1021,14 +1052,9 @@ int main(int argc, char **argv) Sys_Sleep( 1 ); } - Eng_Frame( 0, dtime ); + ProcessConsoleInput(); - p = Sys_ConsoleInput(); - if ( p ) - { - engineapi.Cbuf_AddText( p ); - engineapi.Cbuf_AddText( "\n" ); - } + Eng_Frame( 0, dtime ); oldtime = newtime; } diff --git a/dedicated/sys_ded.h b/dedicated/sys_ded.h index 75de525..9deba6f 100644 --- a/dedicated/sys_ded.h +++ b/dedicated/sys_ded.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -11,10 +11,10 @@ #pragma once #endif +typedef void (*SleepType)(int); long Sys_LoadLibrary( char *lib ); void Sys_FreeLibrary( long library ); void *Sys_GetProcAddress( long library, const char *name ); -void Sys_Sleep( int msec ); void Sys_Printf(char *fmt, ...); void Sys_ErrorMessage( int level, const char *msg ); diff --git a/dlls/AI_BaseNPC_Schedule.cpp b/dlls/AI_BaseNPC_Schedule.cpp new file mode 100644 index 0000000..9b4fe6c --- /dev/null +++ b/dlls/AI_BaseNPC_Schedule.cpp @@ -0,0 +1,1514 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// schedule.cpp - functions and data pertaining to the +// monsters' AI scheduling system. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "animation.h" +#include "scripted.h" +#include "nodes.h" +#include "defaultai.h" +#include "soundent.h" + +extern CGraph WorldGraph; + +//========================================================= +// FHaveSchedule - Returns TRUE if monster's m_pSchedule +// is anything other than NULL. +//========================================================= +BOOL CBaseMonster :: FHaveSchedule( void ) +{ + if ( m_pSchedule == NULL ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +// ClearSchedule - blanks out the caller's schedule pointer +// and index. +//========================================================= +void CBaseMonster :: ClearSchedule( void ) +{ + m_iTaskStatus = TASKSTATUS_NEW; + m_pSchedule = NULL; + m_iScheduleIndex = 0; +} + +//========================================================= +// FScheduleDone - Returns TRUE if the caller is on the +// last task in the schedule +//========================================================= +BOOL CBaseMonster :: FScheduleDone ( void ) +{ + ASSERT( m_pSchedule != NULL ); + + if ( m_iScheduleIndex == m_pSchedule->cTasks ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// ChangeSchedule - replaces the monster's schedule pointer +// with the passed pointer, and sets the ScheduleIndex back +// to 0 +//========================================================= +void CBaseMonster :: ChangeSchedule ( Schedule_t *pNewSchedule ) +{ + ASSERT( pNewSchedule != NULL ); + + m_pSchedule = pNewSchedule; + m_iScheduleIndex = 0; + m_iTaskStatus = TASKSTATUS_NEW; + m_afConditions = 0;// clear all of the conditions + m_failSchedule = SCHED_NONE; + + if ( m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND && !(m_pSchedule->iSoundMask) ) + { + ALERT ( at_aiconsole, "COND_HEAR_SOUND with no sound mask!\n" ); + } + else if ( m_pSchedule->iSoundMask && !(m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND) ) + { + ALERT ( at_aiconsole, "Sound mask without COND_HEAR_SOUND!\n" ); + } + +#if _DEBUG + if ( !ScheduleFromName( pNewSchedule->pName ) ) + { + ALERT( at_console, "Schedule %s not in table!!!\n", pNewSchedule->pName ); + } +#endif + +// this is very useful code if you can isolate a test case in a level with a single monster. It will notify +// you of every schedule selection the monster makes. +#if 0 + if ( FClassnameIs( pev, "monster_human_grunt" ) ) + { + Task_t *pTask = GetTask(); + + if ( pTask ) + { + const char *pName = NULL; + + if ( m_pSchedule ) + { + pName = m_pSchedule->pName; + } + else + { + pName = "No Schedule"; + } + + if ( !pName ) + { + pName = "Unknown"; + } + + ALERT( at_aiconsole, "%s: picked schedule %s\n", STRING( pev->classname ), pName ); + } + } +#endif// 0 + +} + +//========================================================= +// NextScheduledTask - increments the ScheduleIndex +//========================================================= +void CBaseMonster :: NextScheduledTask ( void ) +{ + ASSERT( m_pSchedule != NULL ); + + m_iTaskStatus = TASKSTATUS_NEW; + m_iScheduleIndex++; + + if ( FScheduleDone() ) + { + // just completed last task in schedule, so make it invalid by clearing it. + SetConditions( bits_COND_SCHEDULE_DONE ); + //ClearSchedule(); + } +} + +//========================================================= +// IScheduleFlags - returns an integer with all Conditions +// bits that are currently set and also set in the current +// schedule's Interrupt mask. +//========================================================= +int CBaseMonster :: IScheduleFlags ( void ) +{ + if( !m_pSchedule ) + { + return 0; + } + + // strip off all bits excepts the ones capable of breaking this schedule. + return m_afConditions & m_pSchedule->iInterruptMask; +} + +//========================================================= +// FScheduleValid - returns TRUE as long as the current +// schedule is still the proper schedule to be executing, +// taking into account all conditions +//========================================================= +BOOL CBaseMonster :: FScheduleValid ( void ) +{ + if ( m_pSchedule == NULL ) + { + // schedule is empty, and therefore not valid. + return FALSE; + } + + if ( HasConditions( m_pSchedule->iInterruptMask | bits_COND_SCHEDULE_DONE | bits_COND_TASK_FAILED ) ) + { +#ifdef DEBUG + if ( HasConditions ( bits_COND_TASK_FAILED ) && m_failSchedule == SCHED_NONE ) + { + // fail! Send a visual indicator. + ALERT ( at_aiconsole, "Schedule: %s Failed\n", m_pSchedule->pName ); + + Vector tmp = pev->origin; + tmp.z = pev->absmax.z + 16; + UTIL_Sparks( tmp ); + } +#endif // DEBUG + + // some condition has interrupted the schedule, or the schedule is done + return FALSE; + } + + return TRUE; +} + +//========================================================= +// MaintainSchedule - does all the per-think schedule maintenance. +// ensures that the monster leaves this function with a valid +// schedule! +//========================================================= +void CBaseMonster :: MaintainSchedule ( void ) +{ + Schedule_t *pNewSchedule; + int i; + + // UNDONE: Tune/fix this 10... This is just here so infinite loops are impossible + for ( i = 0; i < 10; i++ ) + { + if ( m_pSchedule != NULL && TaskIsComplete() ) + { + NextScheduledTask(); + } + + // validate existing schedule + if ( !FScheduleValid() || m_MonsterState != m_IdealMonsterState ) + { + // if we come into this block of code, the schedule is going to have to be changed. + // if the previous schedule was interrupted by a condition, GetIdealState will be + // called. Else, a schedule finished normally. + + // Notify the monster that his schedule is changing + ScheduleChange(); + + // Call GetIdealState if we're not dead and one or more of the following... + // - in COMBAT state with no enemy (it died?) + // - conditions bits (excluding SCHEDULE_DONE) indicate interruption, + // - schedule is done but schedule indicates it wants GetIdealState called + // after successful completion (by setting bits_COND_SCHEDULE_DONE in iInterruptMask) + // DEAD & SCRIPT are not suggestions, they are commands! + if ( m_IdealMonsterState != MONSTERSTATE_DEAD && + (m_IdealMonsterState != MONSTERSTATE_SCRIPT || m_IdealMonsterState == m_MonsterState) ) + { + if ( (m_afConditions && !HasConditions(bits_COND_SCHEDULE_DONE)) || + (m_pSchedule && (m_pSchedule->iInterruptMask & bits_COND_SCHEDULE_DONE)) || + ((m_MonsterState == MONSTERSTATE_COMBAT) && (m_hEnemy == NULL)) ) + { + GetIdealState(); + } + } + if ( HasConditions( bits_COND_TASK_FAILED ) && m_MonsterState == m_IdealMonsterState ) + { + if ( m_failSchedule != SCHED_NONE ) + pNewSchedule = GetScheduleOfType( m_failSchedule ); + else + pNewSchedule = GetScheduleOfType( SCHED_FAIL ); + // schedule was invalid because the current task failed to start or complete + ALERT ( at_aiconsole, "Schedule Failed at %d!\n", m_iScheduleIndex ); + ChangeSchedule( pNewSchedule ); + } + else + { + SetState( m_IdealMonsterState ); + if ( m_MonsterState == MONSTERSTATE_SCRIPT || m_MonsterState == MONSTERSTATE_DEAD ) + pNewSchedule = CBaseMonster::GetSchedule(); + else + pNewSchedule = GetSchedule(); + ChangeSchedule( pNewSchedule ); + } + } + + if ( m_iTaskStatus == TASKSTATUS_NEW ) + { + Task_t *pTask = GetTask(); + ASSERT( pTask != NULL ); + TaskBegin(); + StartTask( pTask ); + } + + // UNDONE: Twice?!!! + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } + + if ( !TaskIsComplete() && m_iTaskStatus != TASKSTATUS_NEW ) + break; + } + + if ( TaskIsRunning() ) + { + Task_t *pTask = GetTask(); + ASSERT( pTask != NULL ); + RunTask( pTask ); + } + + // UNDONE: We have to do this so that we have an animation set to blend to if RunTask changes the animation + // RunTask() will always change animations at the end of a script! + // Don't do this twice + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } +} + +//========================================================= +// RunTask +//========================================================= +void CBaseMonster :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TURN_RIGHT: + case TASK_TURN_LEFT: + { + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + + case TASK_PLAY_SEQUENCE_FACE_ENEMY: + case TASK_PLAY_SEQUENCE_FACE_TARGET: + { + CBaseEntity *pTarget; + + if ( pTask->iTask == TASK_PLAY_SEQUENCE_FACE_TARGET ) + pTarget = m_hTargetEnt; + else + pTarget = m_hEnemy; + if ( pTarget ) + { + pev->ideal_yaw = UTIL_VecToYaw( pTarget->pev->origin - pev->origin ); + ChangeYaw( pev->yaw_speed ); + } + if ( m_fSequenceFinished ) + TaskComplete(); + } + break; + + case TASK_PLAY_SEQUENCE: + case TASK_PLAY_ACTIVE_IDLE: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + break; + } + + + case TASK_FACE_ENEMY: + { + MakeIdealYaw( m_vecEnemyLKP ); + + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_FACE_HINTNODE: + case TASK_FACE_LASTPOSITION: + case TASK_FACE_TARGET: + case TASK_FACE_IDEAL: + case TASK_FACE_ROUTE: + { + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_PVS: + { + if ( !FNullEnt(FIND_CLIENT_IN_PVS(edict())) ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_INDEFINITE: + { + // don't do anything. + break; + } + case TASK_WAIT: + case TASK_WAIT_RANDOM: + { + if ( gpGlobals->time >= m_flWaitFinished ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_FACE_ENEMY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if ( gpGlobals->time >= m_flWaitFinished ) + { + TaskComplete(); + } + break; + } + case TASK_MOVE_TO_TARGET_RANGE: + { + float distance; + + if ( m_hTargetEnt == NULL ) + TaskFail(); + else + { + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Re-evaluate when you think your finished, or the target has moved too far + if ( (distance < pTask->flData) || (m_vecMoveGoal - m_hTargetEnt->pev->origin).Length() > pTask->flData * 0.5 ) + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + FRefreshRoute(); + } + + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( distance < pTask->flData ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + else if ( distance < 190 && m_movementActivity != ACT_WALK ) + m_movementActivity = ACT_WALK; + else if ( distance >= 270 && m_movementActivity != ACT_RUN ) + m_movementActivity = ACT_RUN; + } + + break; + } + case TASK_WAIT_FOR_MOVEMENT: + { + if (MovementIsComplete()) + { + TaskComplete(); + RouteClear(); // Stop moving + } + break; + } + case TASK_DIE: + { + if ( m_fSequenceFinished && pev->frame >= 255 ) + { + pev->deadflag = DEAD_DEAD; + + SetThink ( NULL ); + StopAnimation(); + + if ( !BBoxFlat() ) + { + // a bit of a hack. If a corpses' bbox is positioned such that being left solid so that it can be attacked will + // block the player on a slope or stairs, the corpse is made nonsolid. +// pev->solid = SOLID_NOT; + UTIL_SetSize ( pev, Vector ( -4, -4, 0 ), Vector ( 4, 4, 1 ) ); + } + else // !!!HACKHACK - put monster in a thin, wide bounding box until we fix the solid type/bounding volume problem + UTIL_SetSize ( pev, Vector ( pev->mins.x, pev->mins.y, pev->mins.z ), Vector ( pev->maxs.x, pev->maxs.y, pev->mins.z + 1 ) ); + + if ( ShouldFadeOnDeath() ) + { + // this monster was created by a monstermaker... fade the corpse out. + SUB_StartFadeOut(); + } + else + { + // body is gonna be around for a while, so have it stink for a bit. + CSoundEnt::InsertSound ( bits_SOUND_CARCASS, pev->origin, 384, 30 ); + } + } + break; + } + case TASK_RANGE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK2_NOTURN: + case TASK_RANGE_ATTACK2_NOTURN: + case TASK_RELOAD_NOTURN: + { + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + } + case TASK_RANGE_ATTACK1: + case TASK_MELEE_ATTACK1: + case TASK_MELEE_ATTACK2: + case TASK_RANGE_ATTACK2: + case TASK_SPECIAL_ATTACK1: + case TASK_SPECIAL_ATTACK2: + case TASK_RELOAD: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw ( pev->yaw_speed ); + + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + } + case TASK_SMALL_FLINCH: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + } + break; + case TASK_WAIT_FOR_SCRIPT: + { + if ( m_pCine->m_iDelay <= 0 && gpGlobals->time >= m_pCine->m_startTime ) + { + TaskComplete(); + m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszPlay, TRUE ); + if ( m_fSequenceFinished ) + ClearSchedule(); + pev->framerate = 1.0; + //ALERT( at_aiconsole, "Script %s has begun for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) ); + } + break; + } + case TASK_PLAY_SCRIPT: + { + if (m_fSequenceFinished) + { + m_pCine->SequenceDone( this ); + } + break; + } + } +} + +//========================================================= +// SetTurnActivity - measures the difference between the way +// the monster is facing and determines whether or not to +// select one of the 180 turn animations. +//========================================================= +void CBaseMonster :: SetTurnActivity ( void ) +{ + float flYD; + flYD = FlYawDiff(); + + if ( flYD <= -45 && LookupActivity ( ACT_TURN_RIGHT ) != ACTIVITY_NOT_AVAILABLE ) + {// big right turn + m_IdealActivity = ACT_TURN_RIGHT; + } + else if ( flYD > 45 && LookupActivity ( ACT_TURN_LEFT ) != ACTIVITY_NOT_AVAILABLE ) + {// big left turn + m_IdealActivity = ACT_TURN_LEFT; + } +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. +//========================================================= +void CBaseMonster :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TURN_RIGHT: + { + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + pev->ideal_yaw = UTIL_AngleMod( flCurrentYaw - pTask->flData ); + SetTurnActivity(); + break; + } + case TASK_TURN_LEFT: + { + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + pev->ideal_yaw = UTIL_AngleMod( flCurrentYaw + pTask->flData ); + SetTurnActivity(); + break; + } + case TASK_REMEMBER: + { + Remember ( (int)pTask->flData ); + TaskComplete(); + break; + } + case TASK_FORGET: + { + Forget ( (int)pTask->flData ); + TaskComplete(); + break; + } + case TASK_FIND_HINTNODE: + { + m_iHintNode = FindHintNode(); + + if ( m_iHintNode != NO_NODE ) + { + TaskComplete(); + } + else + { + TaskFail(); + } + break; + } + case TASK_STORE_LASTPOSITION: + { + m_vecLastPosition = pev->origin; + TaskComplete(); + break; + } + case TASK_CLEAR_LASTPOSITION: + { + m_vecLastPosition = g_vecZero; + TaskComplete(); + break; + } + case TASK_CLEAR_HINTNODE: + { + m_iHintNode = NO_NODE; + TaskComplete(); + break; + } + case TASK_STOP_MOVING: + { + if ( m_IdealActivity == m_movementActivity ) + { + m_IdealActivity = GetStoppedActivity(); + } + + RouteClear(); + TaskComplete(); + break; + } + case TASK_PLAY_SEQUENCE_FACE_ENEMY: + case TASK_PLAY_SEQUENCE_FACE_TARGET: + case TASK_PLAY_SEQUENCE: + { + m_IdealActivity = ( Activity )( int )pTask->flData; + break; + } + case TASK_PLAY_ACTIVE_IDLE: + { + // monsters verify that they have a sequence for the node's activity BEFORE + // moving towards the node, so it's ok to just set the activity without checking here. + m_IdealActivity = ( Activity )WorldGraph.m_pNodes[ m_iHintNode ].m_sHintActivity; + break; + } + case TASK_SET_SCHEDULE: + { + Schedule_t *pNewSchedule; + + pNewSchedule = GetScheduleOfType( (int)pTask->flData ); + + if ( pNewSchedule ) + { + ChangeSchedule( pNewSchedule ); + } + else + { + TaskFail(); + } + + break; + } + case TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, pTask->flData ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_FAR_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, pTask->flData, CoverRadius() ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, CoverRadius() ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_COVER_FROM_ENEMY: + { + entvars_t *pevCover; + + if ( m_hEnemy == NULL ) + { + // Find cover from self if no enemy available + pevCover = pev; +// TaskFail(); +// return; + } + else + pevCover = m_hEnemy->pev; + + if ( FindLateralCover( pevCover->origin, pevCover->view_ofs ) ) + { + // try lateral first + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else if ( FindCover( pevCover->origin, pevCover->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_COVER_FROM_ORIGIN: + { + if ( FindCover( pev->origin, pev->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no cover! + TaskFail(); + } + } + break; + case TASK_FIND_COVER_FROM_BEST_SOUND: + { + CSound *pBestSound; + + pBestSound = PBestSound(); + + ASSERT( pBestSound != NULL ); + /* + if ( pBestSound && FindLateralCover( pBestSound->m_vecOrigin, g_vecZero ) ) + { + // try lateral first + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + */ + + if ( pBestSound && FindCover( pBestSound->m_vecOrigin, g_vecZero, pBestSound->m_iVolume, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no coverwhatsoever. or no sound in list + TaskFail(); + } + break; + } + case TASK_FACE_HINTNODE: + { + pev->ideal_yaw = WorldGraph.m_pNodes[ m_iHintNode ].m_flHintYaw; + SetTurnActivity(); + break; + } + + case TASK_FACE_LASTPOSITION: + MakeIdealYaw ( m_vecLastPosition ); + SetTurnActivity(); + break; + + case TASK_FACE_TARGET: + if ( m_hTargetEnt != NULL ) + { + MakeIdealYaw ( m_hTargetEnt->pev->origin ); + SetTurnActivity(); + } + else + TaskFail(); + break; + case TASK_FACE_ENEMY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + SetTurnActivity(); + break; + } + case TASK_FACE_IDEAL: + { + SetTurnActivity(); + break; + } + case TASK_FACE_ROUTE: + { + if (FRouteClear()) + { + ALERT(at_aiconsole, "No route to face!\n"); + TaskFail(); + } + else + { + MakeIdealYaw(m_Route[m_iRouteIndex].vecLocation); + SetTurnActivity(); + } + break; + } + case TASK_WAIT_PVS: + case TASK_WAIT_INDEFINITE: + { + // don't do anything. + break; + } + case TASK_WAIT: + case TASK_WAIT_FACE_ENEMY: + {// set a future time that tells us when the wait is over. + m_flWaitFinished = gpGlobals->time + pTask->flData; + break; + } + case TASK_WAIT_RANDOM: + {// set a future time that tells us when the wait is over. + m_flWaitFinished = gpGlobals->time + RANDOM_FLOAT( 0.1, pTask->flData ); + break; + } + case TASK_MOVE_TO_TARGET_RANGE: + { + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + if ( !MoveToTarget( ACT_WALK, 2 ) ) + TaskFail(); + } + break; + } + case TASK_RUN_TO_TARGET: + case TASK_WALK_TO_TARGET: + { + Activity newActivity; + + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + if ( pTask->iTask == TASK_WALK_TO_TARGET ) + newActivity = ACT_WALK; + else + newActivity = ACT_RUN; + // This monster can't do this! + if ( LookupActivity( newActivity ) == ACTIVITY_NOT_AVAILABLE ) + TaskComplete(); + else + { + if ( m_hTargetEnt == NULL || !MoveToTarget( newActivity, 2 ) ) + { + TaskFail(); + ALERT( at_aiconsole, "%s Failed to reach target!!!\n", STRING(pev->classname) ); + RouteClear(); + } + } + } + TaskComplete(); + break; + } + case TASK_CLEAR_MOVE_WAIT: + { + m_flMoveWaitFinished = gpGlobals->time; + TaskComplete(); + break; + } + case TASK_MELEE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK1: + { + m_IdealActivity = ACT_MELEE_ATTACK1; + break; + } + case TASK_MELEE_ATTACK2_NOTURN: + case TASK_MELEE_ATTACK2: + { + m_IdealActivity = ACT_MELEE_ATTACK2; + break; + } + case TASK_RANGE_ATTACK1_NOTURN: + case TASK_RANGE_ATTACK1: + { + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + case TASK_RANGE_ATTACK2_NOTURN: + case TASK_RANGE_ATTACK2: + { + m_IdealActivity = ACT_RANGE_ATTACK2; + break; + } + case TASK_RELOAD_NOTURN: + case TASK_RELOAD: + { + m_IdealActivity = ACT_RELOAD; + break; + } + case TASK_SPECIAL_ATTACK1: + { + m_IdealActivity = ACT_SPECIAL_ATTACK1; + break; + } + case TASK_SPECIAL_ATTACK2: + { + m_IdealActivity = ACT_SPECIAL_ATTACK2; + break; + } + case TASK_SET_ACTIVITY: + { + m_IdealActivity = (Activity)(int)pTask->flData; + TaskComplete(); + break; + } + case TASK_GET_PATH_TO_ENEMY_LKP: + { + if ( BuildRoute ( m_vecEnemyLKP, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, 0, (m_vecEnemyLKP - pev->origin).Length() )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemyLKP failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( BuildRoute ( pEnemy->pev->origin, bits_MF_TO_ENEMY, pEnemy ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( pEnemy->pev->origin, pEnemy->pev->view_ofs, 0, (pEnemy->pev->origin - pev->origin).Length() )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY_CORPSE: + { + UTIL_MakeVectors( pev->angles ); + if ( BuildRoute ( m_vecEnemyLKP - gpGlobals->v_forward * 64, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else + { + ALERT ( at_aiconsole, "GetPathToEnemyCorpse failed!!\n" ); + TaskFail(); + } + } + break; + case TASK_GET_PATH_TO_SPOT: + { + CBaseEntity *pPlayer = CBaseEntity::Instance( FIND_ENTITY_BY_CLASSNAME( NULL, "player" ) ); + if ( BuildRoute ( m_vecMoveGoal, bits_MF_TO_LOCATION, pPlayer ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + + case TASK_GET_PATH_TO_TARGET: + { + RouteClear(); + if ( m_hTargetEnt != NULL && MoveToTarget( m_movementActivity, 1 ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_HINTNODE:// for active idles! + { + if ( MoveToLocation( m_movementActivity, 2, WorldGraph.m_pNodes[ m_iHintNode ].m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToHintNode failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_LASTPOSITION: + { + m_vecMoveGoal = m_vecLastPosition; + + if ( MoveToLocation( m_movementActivity, 2, m_vecMoveGoal ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToLastPosition failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_BESTSOUND: + { + CSound *pSound; + + pSound = PBestSound(); + + if ( pSound && MoveToLocation( m_movementActivity, 2, pSound->m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestSound failed!!\n" ); + TaskFail(); + } + break; + } +case TASK_GET_PATH_TO_BESTSCENT: + { + CSound *pScent; + + pScent = PBestScent(); + + if ( pScent && MoveToLocation( m_movementActivity, 2, pScent->m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestScent failed!!\n" ); + + TaskFail(); + } + break; + } + case TASK_RUN_PATH: + { + // UNDONE: This is in some default AI and some monsters can't run? -- walk instead? + if ( LookupActivity( ACT_RUN ) != ACTIVITY_NOT_AVAILABLE ) + { + m_movementActivity = ACT_RUN; + } + else + { + m_movementActivity = ACT_WALK; + } + TaskComplete(); + break; + } + case TASK_WALK_PATH: + { + if ( pev->movetype == MOVETYPE_FLY ) + { + m_movementActivity = ACT_FLY; + } + if ( LookupActivity( ACT_WALK ) != ACTIVITY_NOT_AVAILABLE ) + { + m_movementActivity = ACT_WALK; + } + else + { + m_movementActivity = ACT_RUN; + } + TaskComplete(); + break; + } + case TASK_STRAFE_PATH: + { + Vector2D vec2DirToPoint; + Vector2D vec2RightSide; + + // to start strafing, we have to first figure out if the target is on the left side or right side + UTIL_MakeVectors ( pev->angles ); + + vec2DirToPoint = ( m_Route[ 0 ].vecLocation - pev->origin ).Make2D().Normalize(); + vec2RightSide = gpGlobals->v_right.Make2D().Normalize(); + + if ( DotProduct ( vec2DirToPoint, vec2RightSide ) > 0 ) + { + // strafe right + m_movementActivity = ACT_STRAFE_RIGHT; + } + else + { + // strafe left + m_movementActivity = ACT_STRAFE_LEFT; + } + TaskComplete(); + break; + } + + + case TASK_WAIT_FOR_MOVEMENT: + { + if (FRouteClear()) + { + TaskComplete(); + } + break; + } + + case TASK_EAT: + { + Eat( pTask->flData ); + TaskComplete(); + break; + } + case TASK_SMALL_FLINCH: + { + m_IdealActivity = GetSmallFlinchActivity(); + break; + } + case TASK_DIE: + { + RouteClear(); + + m_IdealActivity = GetDeathActivity(); + + pev->deadflag = DEAD_DYING; + break; + } + case TASK_SOUND_WAKE: + { + AlertSound(); + TaskComplete(); + break; + } + case TASK_SOUND_DIE: + { + DeathSound(); + TaskComplete(); + break; + } + case TASK_SOUND_IDLE: + { + IdleSound(); + TaskComplete(); + break; + } + case TASK_SOUND_PAIN: + { + PainSound(); + TaskComplete(); + break; + } + case TASK_SOUND_DEATH: + { + DeathSound(); + TaskComplete(); + break; + } + case TASK_SOUND_ANGRY: + { + // sounds are complete as soon as we get here, cause we've already played them. + ALERT ( at_aiconsole, "SOUND\n" ); + TaskComplete(); + break; + } + case TASK_WAIT_FOR_SCRIPT: + { + if (m_pCine->m_iszIdle) + { + m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszIdle, FALSE ); + if (FStrEq( STRING(m_pCine->m_iszIdle), STRING(m_pCine->m_iszPlay))) + { + pev->framerate = 0; + } + } + else + m_IdealActivity = ACT_IDLE; + + break; + } + case TASK_PLAY_SCRIPT: + { + pev->movetype = MOVETYPE_FLY; + ClearBits(pev->flags, FL_ONGROUND); + m_scriptState = SCRIPT_PLAYING; + break; + } + case TASK_ENABLE_SCRIPT: + { + m_pCine->DelayStart( 0 ); + TaskComplete(); + break; + } + case TASK_PLANT_ON_SCRIPT: + { + if ( m_hTargetEnt != NULL ) + { + pev->origin = m_hTargetEnt->pev->origin; // Plant on target + } + + TaskComplete(); + break; + } + case TASK_FACE_SCRIPT: + { + if ( m_hTargetEnt != NULL ) + { + pev->ideal_yaw = UTIL_AngleMod( m_hTargetEnt->pev->angles.y ); + } + + TaskComplete(); + m_IdealActivity = ACT_IDLE; + RouteClear(); + break; + } + + case TASK_SUGGEST_STATE: + { + m_IdealMonsterState = (MONSTERSTATE)(int)pTask->flData; + TaskComplete(); + break; + } + + case TASK_SET_FAIL_SCHEDULE: + m_failSchedule = (int)pTask->flData; + TaskComplete(); + break; + + case TASK_CLEAR_FAIL_SCHEDULE: + m_failSchedule = SCHED_NONE; + TaskComplete(); + break; + + default: + { + ALERT ( at_aiconsole, "No StartTask entry for %d\n", (SHARED_TASKS)pTask->iTask ); + break; + } + } +} + +//========================================================= +// GetTask - returns a pointer to the current +// scheduled task. NULL if there's a problem. +//========================================================= +Task_t *CBaseMonster :: GetTask ( void ) +{ + if ( m_iScheduleIndex < 0 || m_iScheduleIndex >= m_pSchedule->cTasks ) + { + // m_iScheduleIndex is not within valid range for the monster's current schedule. + return NULL; + } + else + { + return &m_pSchedule->pTasklist[ m_iScheduleIndex ]; + } +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CBaseMonster :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_PRONE: + { + return GetScheduleOfType( SCHED_BARNACLE_VICTIM_GRAB ); + break; + } + case MONSTERSTATE_NONE: + { + ALERT ( at_aiconsole, "MONSTERSTATE IS NONE!\n" ); + break; + } + case MONSTERSTATE_IDLE: + { + if ( HasConditions ( bits_COND_HEAR_SOUND ) ) + { + return GetScheduleOfType( SCHED_ALERT_FACE ); + } + else if ( FRouteClear() ) + { + // no valid route! + return GetScheduleOfType( SCHED_IDLE_STAND ); + } + else + { + // valid route. Get moving + return GetScheduleOfType( SCHED_IDLE_WALK ); + } + break; + } + case MONSTERSTATE_ALERT: + { + if ( HasConditions( bits_COND_ENEMY_DEAD ) && LookupActivity( ACT_VICTORY_DANCE ) != ACTIVITY_NOT_AVAILABLE ) + { + return GetScheduleOfType ( SCHED_VICTORY_DANCE ); + } + + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE) ) + { + if ( fabs( FlYawDiff() ) < (1.0 - m_flFieldOfView) * 60 ) // roughly in the correct direction + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ORIGIN ); + } + else + { + return GetScheduleOfType( SCHED_ALERT_SMALL_FLINCH ); + } + } + + else if ( HasConditions ( bits_COND_HEAR_SOUND ) ) + { + return GetScheduleOfType( SCHED_ALERT_FACE ); + } + else + { + return GetScheduleOfType( SCHED_ALERT_STAND ); + } + break; + } + case MONSTERSTATE_COMBAT: + { + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // clear the current (dead) enemy and try to find another. + m_hEnemy = NULL; + + if ( GetEnemy() ) + { + ClearConditions( bits_COND_ENEMY_DEAD ); + return GetSchedule(); + } + else + { + SetState( MONSTERSTATE_ALERT ); + return GetSchedule(); + } + } + + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + return GetScheduleOfType ( SCHED_WAKE_ANGRY ); + } + else if (HasConditions(bits_COND_LIGHT_DAMAGE) && !HasMemory( bits_MEMORY_FLINCHED) ) + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + else if ( !HasConditions(bits_COND_SEE_ENEMY) ) + { + // we can't see the enemy + if ( !HasConditions(bits_COND_ENEMY_OCCLUDED) ) + { + // enemy is unseen, but not occluded! + // turn to face enemy + return GetScheduleOfType( SCHED_COMBAT_FACE ); + } + else + { + // chase! + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + } + else + { + // we can see the enemy + if ( HasConditions(bits_COND_CAN_RANGE_ATTACK1) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + if ( HasConditions(bits_COND_CAN_RANGE_ATTACK2) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + if ( HasConditions(bits_COND_CAN_MELEE_ATTACK1) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + } + if ( HasConditions(bits_COND_CAN_MELEE_ATTACK2) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK2 ); + } + if ( !HasConditions(bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1) ) + { + // if we can see enemy but can't use either attack type, we must need to get closer to enemy + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + else if ( !FacingIdeal() ) + { + //turn + return GetScheduleOfType( SCHED_COMBAT_FACE ); + } + else + { + ALERT ( at_aiconsole, "No suitable combat schedule!\n" ); + } + } + break; + } + case MONSTERSTATE_DEAD: + { + return GetScheduleOfType( SCHED_DIE ); + break; + } + case MONSTERSTATE_SCRIPT: + { + ASSERT( m_pCine != NULL ); + if ( !m_pCine ) + { + ALERT( at_aiconsole, "Script failed for %s\n", STRING(pev->classname) ); + CineCleanup(); + return GetScheduleOfType( SCHED_IDLE_STAND ); + } + + return GetScheduleOfType( SCHED_AISCRIPT ); + } + default: + { + ALERT ( at_aiconsole, "Invalid State for GetSchedule!\n" ); + break; + } + } + + return &slError[ 0 ]; +} diff --git a/dlls/Makefile b/dlls/Makefile index 3ba1339..374be65 100644 --- a/dlls/Makefile +++ b/dlls/Makefile @@ -1,10 +1,10 @@ # -# Half-Life StandardSDK 2.2 mp_i386.so Makefile for i386 Linux +# Half-Life Full SDK 2.3 hl_i386.so Makefile for x86 Linux # -# October 2001 by Leon Hartwig (hartwig@valvesoftware.com) +# October 2002 by Leon Hartwig (hartwig@valvesoftware.com) # -DLLNAME=mp +DLLNAME=hl ARCH=i386 @@ -65,64 +65,102 @@ $(PM_SHARED_OBJDIR)/%.o: $(PM_SHARED_SRCDIR)/%.c $(DO_CC) OBJ = \ + $(DLL_OBJDIR)/aflock.o \ + $(DLL_OBJDIR)/agrunt.o \ $(DLL_OBJDIR)/airtank.o \ $(DLL_OBJDIR)/animating.o \ $(DLL_OBJDIR)/animation.o \ + $(DLL_OBJDIR)/apache.o \ + $(DLL_OBJDIR)/barnacle.o \ + $(DLL_OBJDIR)/barney.o \ + $(DLL_OBJDIR)/bigmomma.o \ + $(DLL_OBJDIR)/bloater.o \ $(DLL_OBJDIR)/bmodels.o \ + $(DLL_OBJDIR)/bullsquid.o \ $(DLL_OBJDIR)/buttons.o \ $(DLL_OBJDIR)/cbase.o \ $(DLL_OBJDIR)/client.o \ $(DLL_OBJDIR)/combat.o \ + $(DLL_OBJDIR)/controller.o \ $(DLL_OBJDIR)/crossbow.o \ $(DLL_OBJDIR)/crowbar.o \ + $(DLL_OBJDIR)/defaultai.o \ $(DLL_OBJDIR)/doors.o \ $(DLL_OBJDIR)/effects.o \ $(DLL_OBJDIR)/egon.o \ $(DLL_OBJDIR)/explode.o \ + $(DLL_OBJDIR)/flyingmonster.o \ $(DLL_OBJDIR)/func_break.o \ $(DLL_OBJDIR)/func_tank.o \ $(DLL_OBJDIR)/game.o \ $(DLL_OBJDIR)/gamerules.o \ + $(DLL_OBJDIR)/gargantua.o \ $(DLL_OBJDIR)/gauss.o \ + $(DLL_OBJDIR)/genericmonster.o \ $(DLL_OBJDIR)/ggrenade.o \ $(DLL_OBJDIR)/globals.o \ + $(DLL_OBJDIR)/gman.o \ $(DLL_OBJDIR)/h_ai.o \ $(DLL_OBJDIR)/h_battery.o \ + $(DLL_OBJDIR)/h_cine.o \ $(DLL_OBJDIR)/h_cycler.o \ $(DLL_OBJDIR)/h_export.o \ $(DLL_OBJDIR)/handgrenade.o \ + $(DLL_OBJDIR)/hassassin.o \ + $(DLL_OBJDIR)/headcrab.o \ $(DLL_OBJDIR)/healthkit.o \ + $(DLL_OBJDIR)/hgrunt.o \ $(DLL_OBJDIR)/hornet.o \ $(DLL_OBJDIR)/hornetgun.o \ + $(DLL_OBJDIR)/houndeye.o \ + $(DLL_OBJDIR)/ichthyosaur.o \ + $(DLL_OBJDIR)/islave.o \ $(DLL_OBJDIR)/items.o \ + $(DLL_OBJDIR)/leech.o \ $(DLL_OBJDIR)/lights.o \ $(DLL_OBJDIR)/maprules.o \ + $(DLL_OBJDIR)/monstermaker.o \ + $(DLL_OBJDIR)/monsters.o \ + $(DLL_OBJDIR)/monsterstate.o \ $(DLL_OBJDIR)/mortar.o \ $(DLL_OBJDIR)/mp5.o \ - $(DLL_OBJDIR)/mpstubb.o \ $(DLL_OBJDIR)/multiplay_gamerules.o \ + $(DLL_OBJDIR)/nihilanth.o \ + $(DLL_OBJDIR)/nodes.o \ + $(DLL_OBJDIR)/osprey.o \ $(DLL_OBJDIR)/pathcorner.o \ $(DLL_OBJDIR)/plane.o \ $(DLL_OBJDIR)/plats.o \ $(DLL_OBJDIR)/player.o \ $(DLL_OBJDIR)/python.o \ + $(DLL_OBJDIR)/rat.o \ + $(DLL_OBJDIR)/roach.o \ $(DLL_OBJDIR)/rpg.o \ $(DLL_OBJDIR)/satchel.o \ + $(DLL_OBJDIR)/schedule.o \ + $(DLL_OBJDIR)/scientist.o \ + $(DLL_OBJDIR)/scripted.o \ $(DLL_OBJDIR)/shotgun.o \ $(DLL_OBJDIR)/singleplay_gamerules.o \ $(DLL_OBJDIR)/skill.o \ $(DLL_OBJDIR)/sound.o \ $(DLL_OBJDIR)/soundent.o \ $(DLL_OBJDIR)/spectator.o \ + $(DLL_OBJDIR)/squadmonster.o \ $(DLL_OBJDIR)/squeakgrenade.o \ $(DLL_OBJDIR)/subs.o \ + $(DLL_OBJDIR)/talkmonster.o \ $(DLL_OBJDIR)/teamplay_gamerules.o \ + $(DLL_OBJDIR)/tempmonster.o \ + $(DLL_OBJDIR)/tentacle.o \ $(DLL_OBJDIR)/triggers.o \ $(DLL_OBJDIR)/tripmine.o \ + $(DLL_OBJDIR)/turret.o \ $(DLL_OBJDIR)/util.o \ $(DLL_OBJDIR)/weapons.o \ $(DLL_OBJDIR)/world.o \ $(DLL_OBJDIR)/xen.o \ + $(DLL_OBJDIR)/zombie.o \ $(WPN_SHARED_OBJDIR)/hl_wpn_glock.o \ $(GAME_SHARED_OBJDIR)/voice_gamemgr.o \ $(PM_SHARED_OBJDIR)/pm_debug.o \ diff --git a/dlls/Wxdebug.cpp b/dlls/Wxdebug.cpp new file mode 100644 index 0000000..d3902d6 --- /dev/null +++ b/dlls/Wxdebug.cpp @@ -0,0 +1,395 @@ +//==========================================================================; +// +// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF ANY +// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR +// PURPOSE. +// +// Copyright (c) 1992 - 1997 Microsoft Corporation. All Rights Reserved. +// +//--------------------------------------------------------------------------; + + +// For every module and executable we store a debugging level and flags +// for the types of output that are desired. Constants for the types are +// defined in WXDEBUG.H and more can be added. +// The keys are stored in the registry under the +// HKEY_LOCAL_MACHINE\SOFTWARE\Debug\\Type and +// HKEY_LOCAL_MACHINE\SOFTWARE\Debug\\Level key values +// +// There are also global values under SOFTWARE\Debug\Global which are loaded +// after the module-specific values. The Types specified there are OR'ed with +// the module specific types and m_dwLevel is set to the greater of the global +// and the module specific settings. + +#include +#include + +#include "extdll.h" +#include "util.h" +#include "wxdebug.h" + +#include + +#ifdef _DEBUG + +void WINAPI DbgInitModuleName(void); +void WINAPI DbgInitModuleSettings(void); +void WINAPI DbgInitGlobalSettings(void); +void WINAPI DbgInitLogTo(HKEY hKey); +void WINAPI DbgInitKeyLevels(HKEY hKey, DWORD *pdwTypes, DWORD *pdwLevel); + + + +const INT iDEBUGINFO = 512; // Used to format strings + +HINSTANCE m_hInst; // Module instance handle +TCHAR m_ModuleName[iDEBUGINFO]; // Cut down module name +//CRITICAL_SECTION m_CSDebug; // Controls access to list +BOOL m_bInit = FALSE; // Have we been initialised +HANDLE m_hOutput = INVALID_HANDLE_VALUE; // Optional output written here +DWORD m_dwTypes = 0; +DWORD m_dwLevel = 0; + +const TCHAR *m_pBaseKey = TEXT("SOFTWARE\\Debug"); +const TCHAR *m_pGlobalKey = TEXT("GLOBAL"); +TCHAR *pKeyNames[] = +{ + TEXT("Types"), + TEXT("Level") +}; + + +// DbgInitialize +// This sets the instance handle that the debug library uses to find +// the module's file name from the Win32 GetModuleFileName function +void WINAPI DbgInitialise(HINSTANCE hInst) +{ + if (!m_bInit) + { + //InitializeCriticalSection(&m_CSDebug); + m_bInit = TRUE; + m_hInst = hInst; + DbgInitModuleName(); + DbgInitModuleSettings(); + DbgInitGlobalSettings(); + } +} + + +// DbgTerminate +// This is called to clear up any resources the debug library uses - at the +// moment we delete our critical section and the handle of the output file. +void WINAPI DbgTerminate() +{ + if (m_bInit) + { + if (m_hOutput != INVALID_HANDLE_VALUE) + { + DBGASSERTEXECUTE(CloseHandle(m_hOutput)); + m_hOutput = INVALID_HANDLE_VALUE; + } + //DeleteCriticalSection(&m_CSDebug); + m_bInit = FALSE; + } +} + + +// DbgInitModuleName +// Initialise the module file name +void WINAPI DbgInitModuleName() +{ + TCHAR FullName[iDEBUGINFO]; // Load the full path and module name + TCHAR *pName; // Searches from the end for a backslash + + GetModuleFileName(m_hInst,FullName,iDEBUGINFO); + pName = _tcsrchr(FullName,'\\'); + if (pName == NULL) + { + pName = FullName; + } + else + { + pName++; + } + lstrcpy(m_ModuleName,pName); +} + + +// DbgInitModuleSettings +// Retrieve the module-specific settings +void WINAPI DbgInitModuleSettings() +{ + LONG lReturn; // Create key return value + TCHAR szInfo[iDEBUGINFO]; // Constructs key names + HKEY hModuleKey; // Module key handle + + // Construct the base key name + wsprintf(szInfo,TEXT("%s\\%s"),m_pBaseKey,m_ModuleName); + + // Create or open the key for this module + lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD)0, // Reserved value + NULL, // Address of class name + (DWORD)0, // Special options flags + KEY_ALL_ACCESS, // Desired security access + NULL, // Key security descriptor + &hModuleKey, // Opened handle buffer + NULL); // What really happened + + if (lReturn != ERROR_SUCCESS) + { + DbgLogInfo(LOG_ERROR, 0, TEXT("Could not access module key")); + return; + } + + DbgInitLogTo(hModuleKey); + DbgInitKeyLevels(hModuleKey, &m_dwTypes, &m_dwLevel); + RegCloseKey(hModuleKey); +} + + +// DbgInitGlobalSettings +// This is called by DbgInitialize to read the global debug settings for +// Level and Type from the registry. The Types are OR'ed together and m_dwLevel +// is set to the greater of the global and module-specific values. +void WINAPI DbgInitGlobalSettings() +{ + LONG lReturn; // Create key return value + TCHAR szInfo[iDEBUGINFO]; // Constructs key names + HKEY hGlobalKey; // Global override key + DWORD dwTypes = 0; + DWORD dwLevel = 0; + + // Construct the global base key name + wsprintf(szInfo,TEXT("%s\\%s"),m_pBaseKey,m_pGlobalKey); + + // Create or open the key for this module + lReturn = RegCreateKeyEx(HKEY_LOCAL_MACHINE, // Handle of an open key + szInfo, // Address of subkey name + (DWORD)0, // Reserved value + NULL, // Address of class name + (DWORD)0, // Special options flags + KEY_ALL_ACCESS, // Desired security access + NULL, // Key security descriptor + &hGlobalKey, // Opened handle buffer + NULL); // What really happened + + if (lReturn != ERROR_SUCCESS) + { + DbgLogInfo(LOG_ERROR, 0, TEXT("Could not access GLOBAL module key")); + return; + } + + DbgInitKeyLevels(hGlobalKey, &dwTypes, &dwLevel); + RegCloseKey(hGlobalKey); + + m_dwTypes |= dwTypes; + if (dwLevel > m_dwLevel) + m_dwLevel = dwLevel; +} + + +// DbgInitLogTo +// Called by DbgInitModuleSettings to setup alternate logging destinations +void WINAPI DbgInitLogTo(HKEY hKey) +{ + LONG lReturn; + DWORD dwKeyType; + DWORD dwKeySize; + TCHAR szFile[MAX_PATH] = {0}; + static const TCHAR cszKey[] = TEXT("LogToFile"); + + dwKeySize = MAX_PATH; + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + cszKey, // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE) szFile, // Returns the field's value + &dwKeySize); // Number of bytes transferred + + // create an empty key if it does not already exist + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_SZ) + { + dwKeySize = 1; + lReturn = RegSetValueEx( + hKey, // Handle of an open key + cszKey, // Address of subkey name + (DWORD) 0, // Reserved field + REG_SZ, // Type of the key field + (PBYTE)szFile, // Value for the field + dwKeySize); // Size of the field buffer + } + + // if an output-to was specified. try to open it. + if (m_hOutput != INVALID_HANDLE_VALUE) + { + DBGASSERTEXECUTE(CloseHandle(m_hOutput)); + m_hOutput = INVALID_HANDLE_VALUE; + } + if (szFile[0] != 0) + { + if (!lstrcmpi(szFile, TEXT("Console"))) + { + m_hOutput = GetStdHandle(STD_OUTPUT_HANDLE); + if (m_hOutput == INVALID_HANDLE_VALUE) + { + AllocConsole(); + m_hOutput = GetStdHandle(STD_OUTPUT_HANDLE); + } + SetConsoleTitle (TEXT("Valve Debug Output")); + } else if (szFile[0] && + lstrcmpi(szFile, TEXT("Debug")) && + lstrcmpi(szFile, TEXT("Debugger")) && + lstrcmpi(szFile, TEXT("Deb"))) + { + m_hOutput = CreateFile(szFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (INVALID_HANDLE_VALUE != m_hOutput) + { + static const TCHAR cszBar[] = TEXT("\r\n\r\n=====DbgInitialize()=====\r\n\r\n"); + SetFilePointer (m_hOutput, 0, NULL, FILE_END); + DbgOutString (cszBar); + } + } + } +} + + +// DbgInitKeyLevels +// This is called by DbgInitModuleSettings and DbgInitGlobalSettings to read +// settings for Types and Level from the registry +void WINAPI DbgInitKeyLevels(HKEY hKey, DWORD *pdwTypes, DWORD *pdwLevel) +{ + LONG lReturn; // Create key return value + DWORD dwKeySize; // Size of the key value + DWORD dwKeyType; // Receives it's type + + // Get the Types value + dwKeySize = sizeof(DWORD); + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + pKeyNames[0], // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE)pdwTypes, // Returns the field's value + &dwKeySize ); // Number of bytes transferred + + // If either the key was not available or it was not a DWORD value + // then we ensure only the high priority debug logging is output + // but we try and update the field to a zero filled DWORD value + + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_DWORD) + { + *pdwTypes = 0; + lReturn = RegSetValueEx( + hKey, // Handle of an open key + pKeyNames[0], // Address of subkey name + (DWORD)0, // Reserved field + REG_DWORD, // Type of the key field + (PBYTE)pdwTypes, // Value for the field + sizeof(DWORD)); // Size of the field buffer + + if (lReturn != ERROR_SUCCESS) + { + DbgLogInfo(LOG_ERROR, 0, TEXT("Could not create subkey %s"),pKeyNames[0]); + *pdwTypes = 0; + } + } + + // Get the Level value + dwKeySize = sizeof(DWORD); + lReturn = RegQueryValueEx( + hKey, // Handle to an open key + pKeyNames[1], // Subkey name derivation + NULL, // Reserved field + &dwKeyType, // Returns the field type + (LPBYTE)pdwLevel, // Returns the field's value + &dwKeySize ); // Number of bytes transferred + + // If either the key was not available or it was not a DWORD value + // then we ensure only the high priority debug logging is output + // but we try and update the field to a zero filled DWORD value + + if (lReturn != ERROR_SUCCESS || dwKeyType != REG_DWORD) + { + *pdwLevel = 0; + lReturn = RegSetValueEx( + hKey, // Handle of an open key + pKeyNames[1], // Address of subkey name + (DWORD)0, // Reserved field + REG_DWORD, // Type of the key field + (PBYTE)pdwLevel, // Value for the field + sizeof(DWORD)); // Size of the field buffer + + if (lReturn != ERROR_SUCCESS) + { + DbgLogInfo(LOG_ERROR, 0, TEXT("Could not create subkey %s"),pKeyNames[1]); + *pdwLevel = 0; + } + } +} + + +// DbgOutString +void WINAPI DbgOutString(LPCTSTR psz) +{ + if (!m_bInit) + return; + if (m_hOutput != INVALID_HANDLE_VALUE) { + UINT cb = lstrlen(psz); + DWORD dw; + WriteFile (m_hOutput, psz, cb, &dw, NULL); + } else { + OutputDebugString (psz); + } +} + + +// DbgLogInfo +// Print a formatted string to the debugger prefixed with this module's name +// Because the debug code is linked statically every module loaded will +// have its own copy of this code. It therefore helps if the module name is +// included on the output so that the offending code can be easily found +void WINAPI DbgLogInfo(DWORD Type, DWORD Level, const TCHAR *pFormat,...) +{ + if (!m_bInit) + return; + // Check the current level for this type combination */ + if (((Type & m_dwTypes) == 0) || (m_dwLevel < Level)) + return; + + TCHAR szInfo[2000]; + + // Format the variable length parameter list + + va_list va; + va_start(va, pFormat); + + //lstrcpy(szInfo, m_ModuleName); + //lstrcat(szInfo, TEXT(": ")); + wvsprintf(szInfo /* + lstrlen(szInfo) */, pFormat, va); + //lstrcat(szInfo, TEXT("\r\n")); + DbgOutString(szInfo); + + va_end(va); +} + + +// DbgKernelAssert +// If we are executing as a pure kernel filter we cannot display message +// boxes to the user, this provides an alternative which puts the error +// condition on the debugger output with a suitable eye catching message +void WINAPI DbgKernelAssert(const TCHAR *pCondition, const TCHAR *pFileName, INT iLine) +{ + if (!m_bInit) + return; + DbgLogInfo(LOG_ERROR, 0, TEXT(m_ModuleName)); + DbgLogInfo(LOG_ERROR, 0, TEXT(": Assertion FAILED (%s) at line %d in file %s\r\n"), pCondition, iLine, pFileName); + DebugBreak(); +} + +#endif // _DEBUG + + diff --git a/dlls/activity.h b/dlls/activity.h index 5c33ac8..37c82b6 100644 --- a/dlls/activity.h +++ b/dlls/activity.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/activitymap.h b/dlls/activitymap.h index 6cfe2a4..b72c4e4 100644 --- a/dlls/activitymap.h +++ b/dlls/activitymap.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/aflock.cpp b/dlls/aflock.cpp new file mode 100644 index 0000000..ab6139c --- /dev/null +++ b/dlls/aflock.cpp @@ -0,0 +1,910 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "squadmonster.h" + +#define AFLOCK_MAX_RECRUIT_RADIUS 1024 +#define AFLOCK_FLY_SPEED 125 +#define AFLOCK_TURN_RATE 75 +#define AFLOCK_ACCELERATE 10 +#define AFLOCK_CHECK_DIST 192 +#define AFLOCK_TOO_CLOSE 100 +#define AFLOCK_TOO_FAR 256 + +//========================================================= +//========================================================= +class CFlockingFlyerFlock : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void SpawnFlock( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + // Sounds are shared by the flock + static void PrecacheFlockSounds( void ); + + int m_cFlockSize; + float m_flFlockRadius; +}; + +TYPEDESCRIPTION CFlockingFlyerFlock::m_SaveData[] = +{ + DEFINE_FIELD( CFlockingFlyerFlock, m_cFlockSize, FIELD_INTEGER ), + DEFINE_FIELD( CFlockingFlyerFlock, m_flFlockRadius, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CFlockingFlyerFlock, CBaseMonster ); + +//========================================================= +//========================================================= +class CFlockingFlyer : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SpawnCommonCode( void ); + void EXPORT IdleThink( void ); + void BoidAdvanceFrame( void ); + void EXPORT FormFlock( void ); + void EXPORT Start( void ); + void EXPORT FlockLeaderThink( void ); + void EXPORT FlockFollowerThink( void ); + void EXPORT FallHack( void ); + void MakeSound( void ); + void AlertFlock( void ); + void SpreadFlock( void ); + void SpreadFlock2( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + void Poop ( void ); + BOOL FPathBlocked( void ); + //void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int IsLeader( void ) { return m_pSquadLeader == this; } + int InSquad( void ) { return m_pSquadLeader != NULL; } + int SquadCount( void ); + void SquadRemove( CFlockingFlyer *pRemove ); + void SquadUnlink( void ); + void SquadAdd( CFlockingFlyer *pAdd ); + void SquadDisband( void ); + + CFlockingFlyer *m_pSquadLeader; + CFlockingFlyer *m_pSquadNext; + BOOL m_fTurning;// is this boid turning? + BOOL m_fCourseAdjust;// followers set this flag TRUE to override flocking while they avoid something + BOOL m_fPathBlocked;// TRUE if there is an obstacle ahead + Vector m_vecReferencePoint;// last place we saw leader + Vector m_vecAdjustedVelocity;// adjusted velocity (used when fCourseAdjust is TRUE) + float m_flGoalSpeed; + float m_flLastBlockedTime; + float m_flFakeBlockedTime; + float m_flAlertTime; + float m_flFlockNextSoundTime; +}; +LINK_ENTITY_TO_CLASS( monster_flyer, CFlockingFlyer ); +LINK_ENTITY_TO_CLASS( monster_flyer_flock, CFlockingFlyerFlock ); + +TYPEDESCRIPTION CFlockingFlyer::m_SaveData[] = +{ + DEFINE_FIELD( CFlockingFlyer, m_pSquadLeader, FIELD_CLASSPTR ), + DEFINE_FIELD( CFlockingFlyer, m_pSquadNext, FIELD_CLASSPTR ), + DEFINE_FIELD( CFlockingFlyer, m_fTurning, FIELD_BOOLEAN ), + DEFINE_FIELD( CFlockingFlyer, m_fCourseAdjust, FIELD_BOOLEAN ), + DEFINE_FIELD( CFlockingFlyer, m_fPathBlocked, FIELD_BOOLEAN ), + DEFINE_FIELD( CFlockingFlyer, m_vecReferencePoint, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CFlockingFlyer, m_vecAdjustedVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CFlockingFlyer, m_flGoalSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFlockingFlyer, m_flLastBlockedTime, FIELD_TIME ), + DEFINE_FIELD( CFlockingFlyer, m_flFakeBlockedTime, FIELD_TIME ), + DEFINE_FIELD( CFlockingFlyer, m_flAlertTime, FIELD_TIME ), +// DEFINE_FIELD( CFlockingFlyer, m_flFlockNextSoundTime, FIELD_TIME ), // don't need to save +}; + +IMPLEMENT_SAVERESTORE( CFlockingFlyer, CBaseMonster ); + +//========================================================= +//========================================================= +void CFlockingFlyerFlock :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "iFlockSize")) + { + m_cFlockSize = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "flFlockRadius")) + { + m_flFlockRadius = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } +} + +//========================================================= +//========================================================= +void CFlockingFlyerFlock :: Spawn( ) +{ + Precache( ); + SpawnFlock(); + + REMOVE_ENTITY(ENT(pev)); // dump the spawn ent +} + +//========================================================= +//========================================================= +void CFlockingFlyerFlock :: Precache( ) +{ + //PRECACHE_MODEL("models/aflock.mdl"); + PRECACHE_MODEL("models/boid.mdl"); + + PrecacheFlockSounds(); +} + + +void CFlockingFlyerFlock :: PrecacheFlockSounds( void ) +{ + PRECACHE_SOUND("boid/boid_alert1.wav" ); + PRECACHE_SOUND("boid/boid_alert2.wav" ); + + PRECACHE_SOUND("boid/boid_idle1.wav" ); + PRECACHE_SOUND("boid/boid_idle2.wav" ); +} + +//========================================================= +//========================================================= +void CFlockingFlyerFlock :: SpawnFlock( void ) +{ + float R = m_flFlockRadius; + int iCount; + Vector vecSpot; + CFlockingFlyer *pBoid, *pLeader; + + pLeader = pBoid = NULL; + + for ( iCount = 0 ; iCount < m_cFlockSize ; iCount++ ) + { + pBoid = GetClassPtr( (CFlockingFlyer *)NULL ); + + if ( !pLeader ) + { + // make this guy the leader. + pLeader = pBoid; + + pLeader->m_pSquadLeader = pLeader; + pLeader->m_pSquadNext = NULL; + } + + vecSpot.x = RANDOM_FLOAT( -R, R ); + vecSpot.y = RANDOM_FLOAT( -R, R ); + vecSpot.z = RANDOM_FLOAT( 0, 16 ); + vecSpot = pev->origin + vecSpot; + + UTIL_SetOrigin(pBoid->pev, vecSpot); + pBoid->pev->movetype = MOVETYPE_FLY; + pBoid->SpawnCommonCode(); + pBoid->pev->flags &= ~FL_ONGROUND; + pBoid->pev->velocity = g_vecZero; + pBoid->pev->angles = pev->angles; + + pBoid->pev->frame = 0; + pBoid->pev->nextthink = gpGlobals->time + 0.2; + pBoid->SetThink( CFlockingFlyer :: IdleThink ); + + if ( pBoid != pLeader ) + { + pLeader->SquadAdd( pBoid ); + } + } +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: Spawn( ) +{ + Precache( ); + SpawnCommonCode(); + + pev->frame = 0; + pev->nextthink = gpGlobals->time + 0.1; + SetThink( IdleThink ); +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: Precache( ) +{ + //PRECACHE_MODEL("models/aflock.mdl"); + PRECACHE_MODEL("models/boid.mdl"); + CFlockingFlyerFlock::PrecacheFlockSounds(); +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: MakeSound( void ) +{ + if ( m_flAlertTime > gpGlobals->time ) + { + // make agitated sounds + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "boid/boid_alert1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "boid/boid_alert2.wav", 1, ATTN_NORM ); break; + } + + return; + } + + // make normal sound + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "boid/boid_idle1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "boid/boid_idle2.wav", 1, ATTN_NORM ); break; + } +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: Killed( entvars_t *pevAttacker, int iGib ) +{ + CFlockingFlyer *pSquad; + + pSquad = (CFlockingFlyer *)m_pSquadLeader; + + while ( pSquad ) + { + pSquad->m_flAlertTime = gpGlobals->time + 15; + pSquad = (CFlockingFlyer *)pSquad->m_pSquadNext; + } + + if ( m_pSquadLeader ) + { + m_pSquadLeader->SquadRemove( this ); + } + + pev->deadflag = DEAD_DEAD; + + pev->framerate = 0; + pev->effects = EF_NOINTERP; + + UTIL_SetSize( pev, Vector(0,0,0), Vector(0,0,0) ); + pev->movetype = MOVETYPE_TOSS; + + SetThink ( FallHack ); + pev->nextthink = gpGlobals->time + 0.1; +} + +void CFlockingFlyer :: FallHack( void ) +{ + if ( pev->flags & FL_ONGROUND ) + { + if ( !FClassnameIs ( pev->groundentity, "worldspawn" ) ) + { + pev->flags &= ~FL_ONGROUND; + pev->nextthink = gpGlobals->time + 0.1; + } + else + { + pev->velocity = g_vecZero; + SetThink( NULL ); + } + } +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: SpawnCommonCode( ) +{ + pev->deadflag = DEAD_NO; + pev->classname = MAKE_STRING("monster_flyer"); + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + pev->takedamage = DAMAGE_NO; + pev->health = 1; + + m_fPathBlocked = FALSE;// obstacles will be detected + m_flFieldOfView = 0.2; + + //SET_MODEL(ENT(pev), "models/aflock.mdl"); + SET_MODEL(ENT(pev), "models/boid.mdl"); + +// UTIL_SetSize(pev, Vector(0,0,0), Vector(0,0,0)); + UTIL_SetSize(pev, Vector(-5,-5,0), Vector(5,5,2)); +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: BoidAdvanceFrame ( ) +{ + float flapspeed = (pev->speed - pev->armorvalue) / AFLOCK_ACCELERATE; + pev->armorvalue = pev->armorvalue * .8 + pev->speed * .2; + + if (flapspeed < 0) flapspeed = -flapspeed; + if (flapspeed < 0.25) flapspeed = 0.25; + if (flapspeed > 1.9) flapspeed = 1.9; + + pev->framerate = flapspeed; + + // lean + pev->avelocity.x = - (pev->angles.x + flapspeed * 5); + + // bank + pev->avelocity.z = - (pev->angles.z + pev->avelocity.y); + + // pev->framerate = flapspeed; + StudioFrameAdvance( 0.1 ); +} + +//========================================================= +//========================================================= +void CFlockingFlyer :: IdleThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.2; + + // see if there's a client in the same pvs as the monster + if ( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + { + SetThink( Start ); + pev->nextthink = gpGlobals->time + 0.1; + } +} + +//========================================================= +// Start - player enters the pvs, so get things going. +//========================================================= +void CFlockingFlyer :: Start( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if ( IsLeader() ) + { + SetThink( FlockLeaderThink ); + } + else + { + SetThink( FlockFollowerThink ); + } + +/* + Vector vecTakeOff; + vecTakeOff = Vector ( 0 , 0 , 0 ); + + vecTakeOff.z = 50 + RANDOM_FLOAT ( 0, 100 ); + vecTakeOff.x = 20 - RANDOM_FLOAT ( 0, 40); + vecTakeOff.y = 20 - RANDOM_FLOAT ( 0, 40); + + pev->velocity = vecTakeOff; + + + pev->speed = pev->velocity.Length(); + pev->sequence = 0; +*/ + SetActivity ( ACT_FLY ); + ResetSequenceInfo( ); + BoidAdvanceFrame( ); + + pev->speed = AFLOCK_FLY_SPEED;// no delay! +} + +//========================================================= +// Leader boid calls this to form a flock from surrounding boids +//========================================================= +void CFlockingFlyer :: FormFlock( void ) +{ + if ( !InSquad() ) + { + // I am my own leader + m_pSquadLeader = this; + m_pSquadNext = NULL; + int squadCount = 1; + + CBaseEntity *pEntity = NULL; + + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, AFLOCK_MAX_RECRUIT_RADIUS )) != NULL) + { + CBaseMonster *pRecruit = pEntity->MyMonsterPointer( ); + + if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_pCine ) + { + // Can we recruit this guy? + if ( FClassnameIs ( pRecruit->pev, "monster_flyer" ) ) + { + squadCount++; + SquadAdd( (CFlockingFlyer *)pRecruit ); + } + } + } + } + + SetThink( IdleThink );// now that flock is formed, go to idle and wait for a player to come along. + pev->nextthink = gpGlobals->time; +} + +//========================================================= +// Searches for boids that are too close and pushes them away +//========================================================= +void CFlockingFlyer :: SpreadFlock( ) +{ + Vector vecDir; + float flSpeed;// holds vector magnitude while we fiddle with the direction + + CFlockingFlyer *pList = m_pSquadLeader; + while ( pList ) + { + if ( pList != this && ( pev->origin - pList->pev->origin ).Length() <= AFLOCK_TOO_CLOSE ) + { + // push the other away + vecDir = ( pList->pev->origin - pev->origin ); + vecDir = vecDir.Normalize(); + + // store the magnitude of the other boid's velocity, and normalize it so we + // can average in a course that points away from the leader. + flSpeed = pList->pev->velocity.Length(); + pList->pev->velocity = pList->pev->velocity.Normalize(); + pList->pev->velocity = ( pList->pev->velocity + vecDir ) * 0.5; + pList->pev->velocity = pList->pev->velocity * flSpeed; + } + + pList = pList->m_pSquadNext; + } +} + +//========================================================= +// Alters the caller's course if he's too close to others +// +// This function should **ONLY** be called when Caller's velocity is normalized!! +//========================================================= +void CFlockingFlyer :: SpreadFlock2 ( ) +{ + Vector vecDir; + + CFlockingFlyer *pList = m_pSquadLeader; + while ( pList ) + { + if ( pList != this && ( pev->origin - pList->pev->origin ).Length() <= AFLOCK_TOO_CLOSE ) + { + vecDir = ( pev->origin - pList->pev->origin ); + vecDir = vecDir.Normalize(); + + pev->velocity = (pev->velocity + vecDir); + } + + pList = pList->m_pSquadNext; + } +} + +//========================================================= +// FBoidPathBlocked - returns TRUE if there is an obstacle ahead +//========================================================= +BOOL CFlockingFlyer :: FPathBlocked( ) +{ + TraceResult tr; + Vector vecDist;// used for general measurements + Vector vecDir;// used for general measurements + BOOL fBlocked; + + if ( m_flFakeBlockedTime > gpGlobals->time ) + { + m_flLastBlockedTime = gpGlobals->time; + return TRUE; + } + + // use VELOCITY, not angles, not all boids point the direction they are flying + //vecDir = UTIL_VecToAngles( pevBoid->velocity ); + UTIL_MakeVectors ( pev->angles ); + + fBlocked = FALSE;// assume the way ahead is clear + + // check for obstacle ahead + UTIL_TraceLine(pev->origin, pev->origin + gpGlobals->v_forward * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + m_flLastBlockedTime = gpGlobals->time; + fBlocked = TRUE; + } + + // extra wide checks + UTIL_TraceLine(pev->origin + gpGlobals->v_right * 12, pev->origin + gpGlobals->v_right * 12 + gpGlobals->v_forward * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + m_flLastBlockedTime = gpGlobals->time; + fBlocked = TRUE; + } + + UTIL_TraceLine(pev->origin - gpGlobals->v_right * 12, pev->origin - gpGlobals->v_right * 12 + gpGlobals->v_forward * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + m_flLastBlockedTime = gpGlobals->time; + fBlocked = TRUE; + } + + if ( !fBlocked && gpGlobals->time - m_flLastBlockedTime > 6 ) + { + // not blocked, and it's been a few seconds since we've actually been blocked. + m_flFakeBlockedTime = gpGlobals->time + RANDOM_LONG(1, 3); + } + + return fBlocked; +} + + +//========================================================= +// Leader boids use this think every tenth +//========================================================= +void CFlockingFlyer :: FlockLeaderThink( void ) +{ + TraceResult tr; + Vector vecDist;// used for general measurements + Vector vecDir;// used for general measurements + int cProcessed = 0;// keep track of how many other boids we've processed + float flLeftSide; + float flRightSide; + + + pev->nextthink = gpGlobals->time + 0.1; + + UTIL_MakeVectors ( pev->angles ); + + // is the way ahead clear? + if ( !FPathBlocked () ) + { + // if the boid is turning, stop the trend. + if ( m_fTurning ) + { + m_fTurning = FALSE; + pev->avelocity.y = 0; + } + + m_fPathBlocked = FALSE; + + if (pev->speed <= AFLOCK_FLY_SPEED ) + pev->speed+= 5; + + pev->velocity = gpGlobals->v_forward * pev->speed; + + BoidAdvanceFrame( ); + + return; + } + + // IF we get this far in the function, the leader's path is blocked! + m_fPathBlocked = TRUE; + + if ( !m_fTurning)// something in the way and boid is not already turning to avoid + { + // measure clearance on left and right to pick the best dir to turn + UTIL_TraceLine(pev->origin, pev->origin + gpGlobals->v_right * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + vecDist = (tr.vecEndPos - pev->origin); + flRightSide = vecDist.Length(); + + UTIL_TraceLine(pev->origin, pev->origin - gpGlobals->v_right * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + vecDist = (tr.vecEndPos - pev->origin); + flLeftSide = vecDist.Length(); + + // turn right if more clearance on right side + if ( flRightSide > flLeftSide ) + { + pev->avelocity.y = -AFLOCK_TURN_RATE; + m_fTurning = TRUE; + } + // default to left turn :) + else if ( flLeftSide > flRightSide ) + { + pev->avelocity.y = AFLOCK_TURN_RATE; + m_fTurning = TRUE; + } + else + { + // equidistant. Pick randomly between left and right. + m_fTurning = TRUE; + + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + pev->avelocity.y = AFLOCK_TURN_RATE; + } + else + { + pev->avelocity.y = -AFLOCK_TURN_RATE; + } + } + } + SpreadFlock( ); + + pev->velocity = gpGlobals->v_forward * pev->speed; + + // check and make sure we aren't about to plow into the ground, don't let it happen + UTIL_TraceLine(pev->origin, pev->origin - gpGlobals->v_up * 16, ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0 && pev->velocity.z < 0 ) + pev->velocity.z = 0; + + // maybe it did, though. + if ( FBitSet (pev->flags, FL_ONGROUND) ) + { + UTIL_SetOrigin (pev, pev->origin + Vector ( 0 , 0 , 1 ) ); + pev->velocity.z = 0; + } + + if ( m_flFlockNextSoundTime < gpGlobals->time ) + { + MakeSound(); + m_flFlockNextSoundTime = gpGlobals->time + RANDOM_FLOAT( 1, 3 ); + } + + BoidAdvanceFrame( ); + + return; +} + +//========================================================= +// follower boids execute this code when flocking +//========================================================= +void CFlockingFlyer :: FlockFollowerThink( void ) +{ + TraceResult tr; + Vector vecDist; + Vector vecDir; + Vector vecDirToLeader; + float flDistToLeader; + + pev->nextthink = gpGlobals->time + 0.1; + + if ( IsLeader() || !InSquad() ) + { + // the leader has been killed and this flyer suddenly finds himself the leader. + SetThink ( FlockLeaderThink ); + return; + } + + vecDirToLeader = ( m_pSquadLeader->pev->origin - pev->origin ); + flDistToLeader = vecDirToLeader.Length(); + + // match heading with leader + pev->angles = m_pSquadLeader->pev->angles; + + // + // We can see the leader, so try to catch up to it + // + if ( FInViewCone ( m_pSquadLeader ) ) + { + // if we're too far away, speed up + if ( flDistToLeader > AFLOCK_TOO_FAR ) + { + m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 1.5; + } + + // if we're too close, slow down + else if ( flDistToLeader < AFLOCK_TOO_CLOSE ) + { + m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 0.5; + } + } + else + { + // wait up! the leader isn't out in front, so we slow down to let him pass + m_flGoalSpeed = m_pSquadLeader->pev->velocity.Length() * 0.5; + } + + SpreadFlock2(); + + pev->speed = pev->velocity.Length(); + pev->velocity = pev->velocity.Normalize(); + + // if we are too far from leader, average a vector towards it into our current velocity + if ( flDistToLeader > AFLOCK_TOO_FAR ) + { + vecDirToLeader = vecDirToLeader.Normalize(); + pev->velocity = (pev->velocity + vecDirToLeader) * 0.5; + } + + // clamp speeds and handle acceleration + if ( m_flGoalSpeed > AFLOCK_FLY_SPEED * 2 ) + { + m_flGoalSpeed = AFLOCK_FLY_SPEED * 2; + } + + if ( pev->speed < m_flGoalSpeed ) + { + pev->speed += AFLOCK_ACCELERATE; + } + else if ( pev->speed > m_flGoalSpeed ) + { + pev->speed -= AFLOCK_ACCELERATE; + } + + pev->velocity = pev->velocity * pev->speed; + + BoidAdvanceFrame( ); +} + +/* + // Is this boid's course blocked? + if ( FBoidPathBlocked (pev) ) + { + // course is still blocked from last time. Just keep flying along adjusted + // velocity + if ( m_fCourseAdjust ) + { + pev->velocity = m_vecAdjustedVelocity * pev->speed; + return; + } + else // set course adjust flag and calculate adjusted velocity + { + m_fCourseAdjust = TRUE; + + // use VELOCITY, not angles, not all boids point the direction they are flying + //vecDir = UTIL_VecToAngles( pev->velocity ); + //UTIL_MakeVectors ( vecDir ); + + UTIL_MakeVectors ( pev->angles ); + + // measure clearance on left and right to pick the best dir to turn + UTIL_TraceLine(pev->origin, pev->origin + gpGlobals->v_right * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + vecDist = (tr.vecEndPos - pev->origin); + flRightSide = vecDist.Length(); + + UTIL_TraceLine(pev->origin, pev->origin - gpGlobals->v_right * AFLOCK_CHECK_DIST, ignore_monsters, ENT(pev), &tr); + vecDist = (tr.vecEndPos - pev->origin); + flLeftSide = vecDist.Length(); + + // slide right if more clearance on right side + if ( flRightSide > flLeftSide ) + { + m_vecAdjustedVelocity = gpGlobals->v_right; + } + // else slide left + else + { + m_vecAdjustedVelocity = gpGlobals->v_right * -1; + } + } + return; + } + + // if we make it this far, boids path is CLEAR! + m_fCourseAdjust = FALSE; +*/ + + +//========================================================= +// +// SquadUnlink(), Unlink the squad pointers. +// +//========================================================= +void CFlockingFlyer :: SquadUnlink( void ) +{ + m_pSquadLeader = NULL; + m_pSquadNext = NULL; +} + +//========================================================= +// +// SquadAdd(), add pAdd to my squad +// +//========================================================= +void CFlockingFlyer :: SquadAdd( CFlockingFlyer *pAdd ) +{ + ASSERT( pAdd!=NULL ); + ASSERT( !pAdd->InSquad() ); + ASSERT( this->IsLeader() ); + + pAdd->m_pSquadNext = m_pSquadNext; + m_pSquadNext = pAdd; + pAdd->m_pSquadLeader = this; +} +//========================================================= +// +// SquadRemove(), remove pRemove from my squad. +// If I am pRemove, promote m_pSquadNext to leader +// +//========================================================= +void CFlockingFlyer :: SquadRemove( CFlockingFlyer *pRemove ) +{ + ASSERT( pRemove!=NULL ); + ASSERT( this->IsLeader() ); + ASSERT( pRemove->m_pSquadLeader == this ); + + if ( SquadCount() > 2 ) + { + // Removing the leader, promote m_pSquadNext to leader + if ( pRemove == this ) + { + CFlockingFlyer *pLeader = m_pSquadNext; + + // copy the enemy LKP to the new leader + pLeader->m_vecEnemyLKP = m_vecEnemyLKP; + + if ( pLeader ) + { + CFlockingFlyer *pList = pLeader; + + while ( pList ) + { + pList->m_pSquadLeader = pLeader; + pList = pList->m_pSquadNext; + } + + } + SquadUnlink(); + } + else // removing a node + { + CFlockingFlyer *pList = this; + + // Find the node before pRemove + while ( pList->m_pSquadNext != pRemove ) + { + // assert to test valid list construction + ASSERT( pList->m_pSquadNext != NULL ); + pList = pList->m_pSquadNext; + } + // List validity + ASSERT( pList->m_pSquadNext == pRemove ); + + // Relink without pRemove + pList->m_pSquadNext = pRemove->m_pSquadNext; + + // Unlink pRemove + pRemove->SquadUnlink(); + } + } + else + SquadDisband(); +} +//========================================================= +// +// SquadCount(), return the number of members of this squad +// callable from leaders & followers +// +//========================================================= +int CFlockingFlyer :: SquadCount( void ) +{ + CFlockingFlyer *pList = m_pSquadLeader; + int squadCount = 0; + while ( pList ) + { + squadCount++; + pList = pList->m_pSquadNext; + } + + return squadCount; +} + +//========================================================= +// +// SquadDisband(), Unlink all squad members +// +//========================================================= +void CFlockingFlyer :: SquadDisband( void ) +{ + CFlockingFlyer *pList = m_pSquadLeader; + CFlockingFlyer *pNext; + + while ( pList ) + { + pNext = pList->m_pSquadNext; + pList->SquadUnlink(); + pList = pNext; + } +} diff --git a/dlls/agrunt.cpp b/dlls/agrunt.cpp new file mode 100644 index 0000000..ff31e8c --- /dev/null +++ b/dlls/agrunt.cpp @@ -0,0 +1,1186 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Agrunt - Dominant, warlike alien grunt monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "squadmonster.h" +#include "weapons.h" +#include "soundent.h" +#include "hornet.h" + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_AGRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, + SCHED_AGRUNT_THREAT_DISPLAY, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_AGRUNT_SETUP_HIDE_ATTACK = LAST_COMMON_TASK + 1, + TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE, +}; + +int iAgruntMuzzleFlash; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define AGRUNT_AE_HORNET1 ( 1 ) +#define AGRUNT_AE_HORNET2 ( 2 ) +#define AGRUNT_AE_HORNET3 ( 3 ) +#define AGRUNT_AE_HORNET4 ( 4 ) +#define AGRUNT_AE_HORNET5 ( 5 ) +// some events are set up in the QC file that aren't recognized by the code yet. +#define AGRUNT_AE_PUNCH ( 6 ) +#define AGRUNT_AE_BITE ( 7 ) + +#define AGRUNT_AE_LEFT_FOOT ( 10 ) +#define AGRUNT_AE_RIGHT_FOOT ( 11 ) + +#define AGRUNT_AE_LEFT_PUNCH ( 12 ) +#define AGRUNT_AE_RIGHT_PUNCH ( 13 ) + + + +#define AGRUNT_MELEE_DIST 100 + +class CAGrunt : public CSquadMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed ( void ); + int Classify ( void ); + int ISoundMask ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -32, -32, 0 ); + pev->absmax = pev->origin + Vector( 32, 32, 85 ); + } + + Schedule_t* GetSchedule ( void ); + Schedule_t* GetScheduleOfType ( int Type ); + BOOL FCanCheckAttacks ( void ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + void StartTask ( Task_t *pTask ); + void AlertSound( void ); + void DeathSound ( void ); + void PainSound ( void ); + void AttackSound ( void ); + void PrescheduleThink ( void ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + int IRelationship( CBaseEntity *pTarget ); + void StopTalking ( void ); + BOOL ShouldSpeak( void ); + CUSTOM_SCHEDULES; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + static const char *pAttackSounds[]; + static const char *pDieSounds[]; + static const char *pPainSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + + BOOL m_fCanHornetAttack; + float m_flNextHornetAttackCheck; + + float m_flNextPainTime; + + // three hacky fields for speech stuff. These don't really need to be saved. + float m_flNextSpeakTime; + float m_flNextWordTime; + int m_iLastWord; +}; +LINK_ENTITY_TO_CLASS( monster_alien_grunt, CAGrunt ); + +TYPEDESCRIPTION CAGrunt::m_SaveData[] = +{ + DEFINE_FIELD( CAGrunt, m_fCanHornetAttack, FIELD_BOOLEAN ), + DEFINE_FIELD( CAGrunt, m_flNextHornetAttackCheck, FIELD_TIME ), + DEFINE_FIELD( CAGrunt, m_flNextPainTime, FIELD_TIME ), + DEFINE_FIELD( CAGrunt, m_flNextSpeakTime, FIELD_TIME ), + DEFINE_FIELD( CAGrunt, m_flNextWordTime, FIELD_TIME ), + DEFINE_FIELD( CAGrunt, m_iLastWord, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CAGrunt, CSquadMonster ); + +const char *CAGrunt::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CAGrunt::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CAGrunt::pAttackSounds[] = +{ + "agrunt/ag_attack1.wav", + "agrunt/ag_attack2.wav", + "agrunt/ag_attack3.wav", +}; + +const char *CAGrunt::pDieSounds[] = +{ + "agrunt/ag_die1.wav", + "agrunt/ag_die4.wav", + "agrunt/ag_die5.wav", +}; + +const char *CAGrunt::pPainSounds[] = +{ + "agrunt/ag_pain1.wav", + "agrunt/ag_pain2.wav", + "agrunt/ag_pain3.wav", + "agrunt/ag_pain4.wav", + "agrunt/ag_pain5.wav", +}; + +const char *CAGrunt::pIdleSounds[] = +{ + "agrunt/ag_idle1.wav", + "agrunt/ag_idle2.wav", + "agrunt/ag_idle3.wav", + "agrunt/ag_idle4.wav", +}; + +const char *CAGrunt::pAlertSounds[] = +{ + "agrunt/ag_alert1.wav", + "agrunt/ag_alert3.wav", + "agrunt/ag_alert4.wav", + "agrunt/ag_alert5.wav", +}; + +//========================================================= +// IRelationship - overridden because Human Grunts are +// Alien Grunt's nemesis. +//========================================================= +int CAGrunt::IRelationship ( CBaseEntity *pTarget ) +{ + if ( FClassnameIs( pTarget->pev, "monster_human_grunt" ) ) + { + return R_NM; + } + + return CSquadMonster :: IRelationship( pTarget ); +} + +//========================================================= +// ISoundMask +//========================================================= +int CAGrunt :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_DANGER; +} + +//========================================================= +// TraceAttack +//========================================================= +void CAGrunt :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( ptr->iHitgroup == 10 && (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_CLUB))) + { + // hit armor + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,10) < 1) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1, 2) ); + pev->dmgtime = gpGlobals->time; + } + + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + Vector vecTracerDir = vecDir; + + vecTracerDir.x += RANDOM_FLOAT( -0.3, 0.3 ); + vecTracerDir.y += RANDOM_FLOAT( -0.3, 0.3 ); + vecTracerDir.z += RANDOM_FLOAT( -0.3, 0.3 ); + + vecTracerDir = vecTracerDir * -512; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, ptr->vecEndPos ); + WRITE_BYTE( TE_TRACER ); + WRITE_COORD( ptr->vecEndPos.x ); + WRITE_COORD( ptr->vecEndPos.y ); + WRITE_COORD( ptr->vecEndPos.z ); + + WRITE_COORD( vecTracerDir.x ); + WRITE_COORD( vecTracerDir.y ); + WRITE_COORD( vecTracerDir.z ); + MESSAGE_END(); + } + + flDamage -= 20; + if (flDamage <= 0) + flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated + } + else + { + SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage);// a little surface blood. + TraceBleed( flDamage, vecDir, ptr, bitsDamageType ); + } + + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + +//========================================================= +// StopTalking - won't speak again for 10-20 seconds. +//========================================================= +void CAGrunt::StopTalking( void ) +{ + m_flNextWordTime = m_flNextSpeakTime = gpGlobals->time + 10 + RANDOM_LONG(0, 10); +} + +//========================================================= +// ShouldSpeak - Should this agrunt be talking? +//========================================================= +BOOL CAGrunt::ShouldSpeak( void ) +{ + if ( m_flNextSpeakTime > gpGlobals->time ) + { + // my time to talk is still in the future. + return FALSE; + } + + if ( pev->spawnflags & SF_MONSTER_GAG ) + { + if ( m_MonsterState != MONSTERSTATE_COMBAT ) + { + // if gagged, don't talk outside of combat. + // if not going to talk because of this, put the talk time + // into the future a bit, so we don't talk immediately after + // going into combat + m_flNextSpeakTime = gpGlobals->time + 3; + return FALSE; + } + } + + return TRUE; +} + +//========================================================= +// PrescheduleThink +//========================================================= +void CAGrunt :: PrescheduleThink ( void ) +{ + if ( ShouldSpeak() ) + { + if ( m_flNextWordTime < gpGlobals->time ) + { + int num = -1; + + do + { + num = RANDOM_LONG(0,ARRAYSIZE(pIdleSounds)-1); + } while( num == m_iLastWord ); + + m_iLastWord = num; + + // play a new sound + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pIdleSounds[ num ], 1.0, ATTN_NORM ); + + // is this word our last? + if ( RANDOM_LONG( 1, 10 ) <= 1 ) + { + // stop talking. + StopTalking(); + } + else + { + m_flNextWordTime = gpGlobals->time + RANDOM_FLOAT( 0.5, 1 ); + } + } + } +} + +//========================================================= +// DieSound +//========================================================= +void CAGrunt :: DeathSound ( void ) +{ + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pDieSounds[RANDOM_LONG(0,ARRAYSIZE(pDieSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// AlertSound +//========================================================= +void CAGrunt :: AlertSound ( void ) +{ + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pAlertSounds[RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// AttackSound +//========================================================= +void CAGrunt :: AttackSound ( void ) +{ + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pAttackSounds[RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// PainSound +//========================================================= +void CAGrunt :: PainSound ( void ) +{ + if ( m_flNextPainTime > gpGlobals->time ) + { + return; + } + + m_flNextPainTime = gpGlobals->time + 0.6; + + StopTalking(); + + EMIT_SOUND ( ENT(pev), CHAN_VOICE, pPainSounds[RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1)], 1.0, ATTN_NORM ); +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CAGrunt :: Classify ( void ) +{ + return CLASS_ALIEN_MILITARY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CAGrunt :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 110; + break; + default: ys = 100; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CAGrunt :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case AGRUNT_AE_HORNET1: + case AGRUNT_AE_HORNET2: + case AGRUNT_AE_HORNET3: + case AGRUNT_AE_HORNET4: + case AGRUNT_AE_HORNET5: + { + // m_vecEnemyLKP should be center of enemy body + Vector vecArmPos, vecArmDir; + Vector vecDirToEnemy; + Vector angDir; + + if (HasConditions( bits_COND_SEE_ENEMY)) + { + vecDirToEnemy = ( ( m_vecEnemyLKP ) - pev->origin ); + angDir = UTIL_VecToAngles( vecDirToEnemy ); + vecDirToEnemy = vecDirToEnemy.Normalize(); + } + else + { + angDir = pev->angles; + UTIL_MakeAimVectors( angDir ); + vecDirToEnemy = gpGlobals->v_forward; + } + + pev->effects = EF_MUZZLEFLASH; + + // make angles +-180 + if (angDir.x > 180) + { + angDir.x = angDir.x - 360; + } + + SetBlending( 0, angDir.x ); + GetAttachment( 0, vecArmPos, vecArmDir ); + + vecArmPos = vecArmPos + vecDirToEnemy * 32; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecArmPos ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( vecArmPos.x ); // pos + WRITE_COORD( vecArmPos.y ); + WRITE_COORD( vecArmPos.z ); + WRITE_SHORT( iAgruntMuzzleFlash ); // model + WRITE_BYTE( 6 ); // size * 10 + WRITE_BYTE( 128 ); // brightness + MESSAGE_END(); + + CBaseEntity *pHornet = CBaseEntity::Create( "hornet", vecArmPos, UTIL_VecToAngles( vecDirToEnemy ), edict() ); + UTIL_MakeVectors ( pHornet->pev->angles ); + pHornet->pev->velocity = gpGlobals->v_forward * 300; + + + + switch ( RANDOM_LONG ( 0 , 2 ) ) + { + case 0: EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, "agrunt/ag_fire1.wav", 1.0, ATTN_NORM, 0, 100 ); break; + case 1: EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, "agrunt/ag_fire2.wav", 1.0, ATTN_NORM, 0, 100 ); break; + case 2: EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, "agrunt/ag_fire3.wav", 1.0, ATTN_NORM, 0, 100 ); break; + } + + CBaseMonster *pHornetMonster = pHornet->MyMonsterPointer(); + + if ( pHornetMonster ) + { + pHornetMonster->m_hEnemy = m_hEnemy; + } + } + break; + + case AGRUNT_AE_LEFT_FOOT: + switch (RANDOM_LONG(0,1)) + { + // left foot + case 0: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder2.wav", 1, ATTN_NORM, 0, 70 ); break; + case 1: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder4.wav", 1, ATTN_NORM, 0, 70 ); break; + } + break; + case AGRUNT_AE_RIGHT_FOOT: + // right foot + switch (RANDOM_LONG(0,1)) + { + case 0: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder1.wav", 1, ATTN_NORM, 0, 70 ); break; + case 1: EMIT_SOUND_DYN ( ENT(pev), CHAN_BODY, "player/pl_ladder3.wav", 1, ATTN_NORM, 0 ,70); break; + } + break; + + case AGRUNT_AE_LEFT_PUNCH: + { + CBaseEntity *pHurt = CheckTraceHullAttack( AGRUNT_MELEE_DIST, gSkillData.agruntDmgPunch, DMG_CLUB ); + + if ( pHurt ) + { + pHurt->pev->punchangle.y = -25; + pHurt->pev->punchangle.x = 8; + + // OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above. + if ( pHurt->IsPlayer() ) + { + // this is a player. Knock him around. + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * 250; + } + + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + Vector vecArmPos, vecArmAng; + GetAttachment( 0, vecArmPos, vecArmAng ); + SpawnBlood(vecArmPos, pHurt->BloodColor(), 25);// a little surface blood. + } + else + { + // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + } + break; + + case AGRUNT_AE_RIGHT_PUNCH: + { + CBaseEntity *pHurt = CheckTraceHullAttack( AGRUNT_MELEE_DIST, gSkillData.agruntDmgPunch, DMG_CLUB ); + + if ( pHurt ) + { + pHurt->pev->punchangle.y = 25; + pHurt->pev->punchangle.x = 8; + + // OK to use gpGlobals without calling MakeVectors, cause CheckTraceHullAttack called it above. + if ( pHurt->IsPlayer() ) + { + // this is a player. Knock him around. + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * -250; + } + + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + Vector vecArmPos, vecArmAng; + GetAttachment( 0, vecArmPos, vecArmAng ); + SpawnBlood(vecArmPos, pHurt->BloodColor(), 25);// a little surface blood. + } + else + { + // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + } + break; + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CAGrunt :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/agrunt.mdl"); + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.agruntHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = 0; + m_afCapability |= bits_CAP_SQUAD; + + m_HackedGunPos = Vector( 24, 64, 48 ); + + m_flNextSpeakTime = m_flNextWordTime = gpGlobals->time + 10 + RANDOM_LONG(0, 10); + + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CAGrunt :: Precache() +{ + int i; + + PRECACHE_MODEL("models/agrunt.mdl"); + + for ( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackHitSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackMissSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pIdleSounds ); i++ ) + PRECACHE_SOUND((char *)pIdleSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pDieSounds ); i++ ) + PRECACHE_SOUND((char *)pDieSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); + + + PRECACHE_SOUND( "hassault/hw_shoot1.wav" ); + + iAgruntMuzzleFlash = PRECACHE_MODEL( "sprites/muz4.spr" ); + + UTIL_PrecacheOther( "hornet" ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +//========================================================= +// Fail Schedule +//========================================================= +Task_t tlAGruntFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slAGruntFail[] = +{ + { + tlAGruntFail, + ARRAYSIZE ( tlAGruntFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1, + 0, + "AGrunt Fail" + }, +}; + +//========================================================= +// Combat Fail Schedule +//========================================================= +Task_t tlAGruntCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slAGruntCombatFail[] = +{ + { + tlAGruntCombatFail, + ARRAYSIZE ( tlAGruntCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1, + 0, + "AGrunt Combat Fail" + }, +}; + +//========================================================= +// Standoff schedule. Used in combat when a monster is +// hiding in cover or the enemy has moved out of sight. +// Should we look around in this schedule? +//========================================================= +Task_t tlAGruntStandoff[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, +}; + +Schedule_t slAGruntStandoff[] = +{ + { + tlAGruntStandoff, + ARRAYSIZE ( tlAGruntStandoff ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Agrunt Standoff" + } +}; + +//========================================================= +// Suppress +//========================================================= +Task_t tlAGruntSuppressHornet[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slAGruntSuppress[] = +{ + { + tlAGruntSuppressHornet, + ARRAYSIZE ( tlAGruntSuppressHornet ), + 0, + 0, + "AGrunt Suppress Hornet", + }, +}; + +//========================================================= +// primary range attacks +//========================================================= +Task_t tlAGruntRangeAttack1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slAGruntRangeAttack1[] = +{ + { + tlAGruntRangeAttack1, + ARRAYSIZE ( tlAGruntRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE, + + 0, + "AGrunt Range Attack1" + }, +}; + + +Task_t tlAGruntHiddenRangeAttack1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_STANDOFF }, + { TASK_AGRUNT_SETUP_HIDE_ATTACK, 0 }, + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, 0 }, + { TASK_RANGE_ATTACK1_NOTURN, (float)0 }, +}; + +Schedule_t slAGruntHiddenRangeAttack[] = +{ + { + tlAGruntHiddenRangeAttack1, + ARRAYSIZE ( tlAGruntHiddenRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AGrunt Hidden Range Attack1" + }, +}; + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlAGruntTakeCoverFromEnemy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slAGruntTakeCoverFromEnemy[] = +{ + { + tlAGruntTakeCoverFromEnemy, + ARRAYSIZE ( tlAGruntTakeCoverFromEnemy ), + bits_COND_NEW_ENEMY, + 0, + "AGruntTakeCoverFromEnemy" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlAGruntVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_AGRUNT_THREAT_DISPLAY }, + { TASK_WAIT, (float)0.2 }, + { TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, + { TASK_PLAY_SEQUENCE, (float)ACT_THREAT_DISPLAY }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, +}; + +Schedule_t slAGruntVictoryDance[] = +{ + { + tlAGruntVictoryDance, + ARRAYSIZE ( tlAGruntVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "AGruntVictoryDance" + }, +}; + +//========================================================= +//========================================================= +Task_t tlAGruntThreatDisplay[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_THREAT_DISPLAY }, +}; + +Schedule_t slAGruntThreatDisplay[] = +{ + { + tlAGruntThreatDisplay, + ARRAYSIZE ( tlAGruntThreatDisplay ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + + bits_SOUND_PLAYER | + bits_SOUND_COMBAT | + bits_SOUND_WORLD, + "AGruntThreatDisplay" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CAGrunt ) +{ + slAGruntFail, + slAGruntCombatFail, + slAGruntStandoff, + slAGruntSuppress, + slAGruntRangeAttack1, + slAGruntHiddenRangeAttack, + slAGruntTakeCoverFromEnemy, + slAGruntVictoryDance, + slAGruntThreatDisplay, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CAGrunt, CSquadMonster ); + +//========================================================= +// FCanCheckAttacks - this is overridden for alien grunts +// because they can use their smart weapons against unseen +// enemies. Base class doesn't attack anyone it can't see. +//========================================================= +BOOL CAGrunt :: FCanCheckAttacks ( void ) +{ + if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + else + { + return FALSE; + } +} + +//========================================================= +// CheckMeleeAttack1 - alien grunts zap the crap out of +// any enemy that gets too close. +//========================================================= +BOOL CAGrunt :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( HasConditions ( bits_COND_SEE_ENEMY ) && flDist <= AGRUNT_MELEE_DIST && flDot >= 0.6 && m_hEnemy != NULL ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack1 +// +// !!!LATER - we may want to load balance this. Several +// tracelines are done, so we may not want to do this every +// server frame. Definitely not while firing. +//========================================================= +BOOL CAGrunt :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( gpGlobals->time < m_flNextHornetAttackCheck ) + { + return m_fCanHornetAttack; + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && flDist >= AGRUNT_MELEE_DIST && flDist <= 1024 && flDot >= 0.5 && NoFriendlyFire() ) + { + TraceResult tr; + Vector vecArmPos, vecArmDir; + + // verify that a shot fired from the gun will hit the enemy before the world. + // !!!LATER - we may wish to do something different for projectile weapons as opposed to instant-hit + UTIL_MakeVectors( pev->angles ); + GetAttachment( 0, vecArmPos, vecArmDir ); +// UTIL_TraceLine( vecArmPos, vecArmPos + gpGlobals->v_forward * 256, ignore_monsters, ENT(pev), &tr); + UTIL_TraceLine( vecArmPos, m_hEnemy->BodyTarget(vecArmPos), dont_ignore_monsters, ENT(pev), &tr); + + if ( tr.flFraction == 1.0 || tr.pHit == m_hEnemy->edict() ) + { + m_flNextHornetAttackCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 ); + m_fCanHornetAttack = TRUE; + return m_fCanHornetAttack; + } + } + + m_flNextHornetAttackCheck = gpGlobals->time + 0.2;// don't check for half second if this check wasn't successful + m_fCanHornetAttack = FALSE; + return m_fCanHornetAttack; +} + +//========================================================= +// StartTask +//========================================================= +void CAGrunt :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_AGRUNT_GET_PATH_TO_ENEMY_CORPSE: + { + UTIL_MakeVectors( pev->angles ); + if ( BuildRoute ( m_vecEnemyLKP - gpGlobals->v_forward * 50, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else + { + ALERT ( at_aiconsole, "AGruntGetPathToEnemyCorpse failed!!\n" ); + TaskFail(); + } + } + break; + + case TASK_AGRUNT_SETUP_HIDE_ATTACK: + // alien grunt shoots hornets back out into the open from a concealed location. + // try to find a spot to throw that gives the smart weapon a good chance of finding the enemy. + // ideally, this spot is along a line that is perpendicular to a line drawn from the agrunt to the enemy. + + CBaseMonster *pEnemyMonsterPtr; + + pEnemyMonsterPtr = m_hEnemy->MyMonsterPointer(); + + if ( pEnemyMonsterPtr ) + { + Vector vecCenter; + TraceResult tr; + BOOL fSkip; + + fSkip = FALSE; + vecCenter = Center(); + + UTIL_VecToAngles( m_vecEnemyLKP - pev->origin ); + + UTIL_TraceLine( Center() + gpGlobals->v_forward * 128, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + MakeIdealYaw ( pev->origin + gpGlobals->v_right * 128 ); + fSkip = TRUE; + TaskComplete(); + } + + if ( !fSkip ) + { + UTIL_TraceLine( Center() - gpGlobals->v_forward * 128, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + MakeIdealYaw ( pev->origin - gpGlobals->v_right * 128 ); + fSkip = TRUE; + TaskComplete(); + } + } + + if ( !fSkip ) + { + UTIL_TraceLine( Center() + gpGlobals->v_forward * 256, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + MakeIdealYaw ( pev->origin + gpGlobals->v_right * 256 ); + fSkip = TRUE; + TaskComplete(); + } + } + + if ( !fSkip ) + { + UTIL_TraceLine( Center() - gpGlobals->v_forward * 256, m_vecEnemyLKP, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction == 1.0 ) + { + MakeIdealYaw ( pev->origin - gpGlobals->v_right * 256 ); + fSkip = TRUE; + TaskComplete(); + } + } + + if ( !fSkip ) + { + TaskFail(); + } + } + else + { + ALERT ( at_aiconsole, "AGRunt - no enemy monster ptr!!!\n" ); + TaskFail(); + } + break; + + default: + CSquadMonster :: StartTask ( pTask ); + break; + } +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CAGrunt :: GetSchedule ( void ) +{ + if ( HasConditions(bits_COND_HEAR_SOUND) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + { + // dangerous sound nearby! + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + } + + switch ( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + return GetScheduleOfType( SCHED_WAKE_ANGRY ); + } + + // zap player! + if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + AttackSound();// this is a total hack. Should be parto f the schedule + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } + + if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + + // can attack + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot ( bits_SLOTS_AGRUNT_HORNET ) ) + { + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + if ( OccupySlot ( bits_SLOT_AGRUNT_CHASE ) ) + { + return GetScheduleOfType ( SCHED_CHASE_ENEMY ); + } + + return GetScheduleOfType ( SCHED_STANDOFF ); + } + } + + return CSquadMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CAGrunt :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + return &slAGruntTakeCoverFromEnemy[ 0 ]; + break; + + case SCHED_RANGE_ATTACK1: + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + { + //normal attack + return &slAGruntRangeAttack1[ 0 ]; + } + else + { + // attack an unseen enemy + // return &slAGruntHiddenRangeAttack[ 0 ]; + return &slAGruntRangeAttack1[ 0 ]; + } + break; + + case SCHED_AGRUNT_THREAT_DISPLAY: + return &slAGruntThreatDisplay[ 0 ]; + break; + + case SCHED_AGRUNT_SUPPRESS: + return &slAGruntSuppress[ 0 ]; + break; + + case SCHED_STANDOFF: + return &slAGruntStandoff[ 0 ]; + break; + + case SCHED_VICTORY_DANCE: + return &slAGruntVictoryDance[ 0 ]; + break; + + case SCHED_FAIL: + // no fail schedule specified, so pick a good generic one. + { + if ( m_hEnemy != NULL ) + { + // I have an enemy + // !!!LATER - what if this enemy is really far away and i'm chasing him? + // this schedule will make me stop, face his last known position for 2 + // seconds, and then try to move again + return &slAGruntCombatFail[ 0 ]; + } + + return &slAGruntFail[ 0 ]; + } + break; + + } + + return CSquadMonster :: GetScheduleOfType( Type ); +} + diff --git a/dlls/airtank.cpp b/dlls/airtank.cpp index 9e87d9e..a356744 100644 --- a/dlls/airtank.cpp +++ b/dlls/airtank.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/animating.cpp b/dlls/animating.cpp index a0d29c5..6afc395 100644 --- a/dlls/animating.cpp +++ b/dlls/animating.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/animation.cpp b/dlls/animation.cpp index 211b388..bc7a135 100644 --- a/dlls/animation.cpp +++ b/dlls/animation.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/animation.h b/dlls/animation.h index d9810d7..7728167 100644 --- a/dlls/animation.h +++ b/dlls/animation.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/apache.cpp b/dlls/apache.cpp new file mode 100644 index 0000000..2b8d4ed --- /dev/null +++ b/dlls/apache.cpp @@ -0,0 +1,1050 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef OEM_BUILD + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "effects.h" + +extern DLL_GLOBAL int g_iSkillLevel; + +#define SF_WAITFORTRIGGER (0x04 | 0x40) // UNDONE: Fix! +#define SF_NOWRECKAGE 0x08 + +class CApache : public CBaseMonster +{ + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int Classify( void ) { return CLASS_HUMAN_MILITARY; }; + int BloodColor( void ) { return DONT_BLEED; } + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -300, -300, -172); + pev->absmax = pev->origin + Vector(300, 300, 8); + } + + void EXPORT HuntThink( void ); + void EXPORT FlyTouch( CBaseEntity *pOther ); + void EXPORT CrashTouch( CBaseEntity *pOther ); + void EXPORT DyingThink( void ); + void EXPORT StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT NullThink( void ); + + void ShowDamage( void ); + void Flight( void ); + void FireRocket( void ); + BOOL FireGun( void ); + + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + int m_iRockets; + float m_flForce; + float m_flNextRocket; + + Vector m_vecTarget; + Vector m_posTarget; + + Vector m_vecDesired; + Vector m_posDesired; + + Vector m_vecGoal; + + Vector m_angGun; + float m_flLastSeen; + float m_flPrevSeen; + + int m_iSoundState; // don't save this + + int m_iSpriteTexture; + int m_iExplode; + int m_iBodyGibs; + + float m_flGoalSpeed; + + int m_iDoSmokePuff; + CBeam *m_pBeam; +}; +LINK_ENTITY_TO_CLASS( monster_apache, CApache ); + +TYPEDESCRIPTION CApache::m_SaveData[] = +{ + DEFINE_FIELD( CApache, m_iRockets, FIELD_INTEGER ), + DEFINE_FIELD( CApache, m_flForce, FIELD_FLOAT ), + DEFINE_FIELD( CApache, m_flNextRocket, FIELD_TIME ), + DEFINE_FIELD( CApache, m_vecTarget, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_posTarget, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CApache, m_vecDesired, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_posDesired, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CApache, m_vecGoal, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_angGun, FIELD_VECTOR ), + DEFINE_FIELD( CApache, m_flLastSeen, FIELD_TIME ), + DEFINE_FIELD( CApache, m_flPrevSeen, FIELD_TIME ), +// DEFINE_FIELD( CApache, m_iSoundState, FIELD_INTEGER ), // Don't save, precached +// DEFINE_FIELD( CApache, m_iSpriteTexture, FIELD_INTEGER ), +// DEFINE_FIELD( CApache, m_iExplode, FIELD_INTEGER ), +// DEFINE_FIELD( CApache, m_iBodyGibs, FIELD_INTEGER ), + DEFINE_FIELD( CApache, m_pBeam, FIELD_CLASSPTR ), + DEFINE_FIELD( CApache, m_flGoalSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CApache, m_iDoSmokePuff, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CApache, CBaseMonster ); + + +void CApache :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/apache.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, -64 ), Vector( 32, 32, 0 ) ); + UTIL_SetOrigin( pev, pev->origin ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_AIM; + pev->health = gSkillData.apacheHealth; + + m_flFieldOfView = -0.707; // 270 degrees + + pev->sequence = 0; + ResetSequenceInfo( ); + pev->frame = RANDOM_LONG(0, 0xFF); + + InitBoneControllers(); + + if (pev->spawnflags & SF_WAITFORTRIGGER) + { + SetUse( StartupUse ); + } + else + { + SetThink( HuntThink ); + SetTouch( FlyTouch ); + pev->nextthink = gpGlobals->time + 1.0; + } + + m_iRockets = 10; +} + + +void CApache::Precache( void ) +{ + PRECACHE_MODEL("models/apache.mdl"); + + PRECACHE_SOUND("apache/ap_rotor1.wav"); + PRECACHE_SOUND("apache/ap_rotor2.wav"); + PRECACHE_SOUND("apache/ap_rotor3.wav"); + PRECACHE_SOUND("apache/ap_whine1.wav"); + + PRECACHE_SOUND("weapons/mortarhit.wav"); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/white.spr" ); + + PRECACHE_SOUND("turret/tu_fire1.wav"); + + PRECACHE_MODEL("sprites/lgtning.spr"); + + m_iExplode = PRECACHE_MODEL( "sprites/fexplo.spr" ); + m_iBodyGibs = PRECACHE_MODEL( "models/metalplategibs_green.mdl" ); + + UTIL_PrecacheOther( "hvr_rocket" ); +} + + + +void CApache::NullThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.5; +} + + +void CApache::StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( HuntThink ); + SetTouch( FlyTouch ); + pev->nextthink = gpGlobals->time + 0.1; + SetUse( NULL ); +} + +void CApache :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->gravity = 0.3; + + STOP_SOUND( ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav" ); + + UTIL_SetSize( pev, Vector( -32, -32, -64), Vector( 32, 32, 0) ); + SetThink( DyingThink ); + SetTouch( CrashTouch ); + pev->nextthink = gpGlobals->time + 0.1; + pev->health = 0; + pev->takedamage = DAMAGE_NO; + + if (pev->spawnflags & SF_NOWRECKAGE) + { + m_flNextRocket = gpGlobals->time + 4.0; + } + else + { + m_flNextRocket = gpGlobals->time + 15.0; + } +} + +void CApache :: DyingThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + pev->avelocity = pev->avelocity * 1.02; + + // still falling? + if (m_flNextRocket > gpGlobals->time ) + { + // random explosions + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( RANDOM_LONG(0,29) + 30 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + // lots of smoke + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 100 ); // scale * 10 + WRITE_BYTE( 10 ); // framerate + MESSAGE_END(); + + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( 400 ); + WRITE_COORD( 400 ); + WRITE_COORD( 132 ); + + // velocity + WRITE_COORD( pev->velocity.x ); + WRITE_COORD( pev->velocity.y ); + WRITE_COORD( pev->velocity.z ); + + // randomization + WRITE_BYTE( 50 ); + + // Model + WRITE_SHORT( m_iBodyGibs ); //model id# + + // # of shards + WRITE_BYTE( 4 ); // let client decide + + // duration + WRITE_BYTE( 30 );// 3.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + // don't stop it we touch a entity + pev->flags &= ~FL_ONGROUND; + pev->nextthink = gpGlobals->time + 0.2; + return; + } + else + { + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 300 ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 8 ); // framerate + MESSAGE_END(); + */ + + // fireball + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 256 ); + WRITE_SHORT( m_iExplode ); + WRITE_BYTE( 120 ); // scale * 10 + WRITE_BYTE( 255 ); // brightness + MESSAGE_END(); + + // big smoke + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 512 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 5 ); // framerate + MESSAGE_END(); + + // blast circle + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 2000 ); // reach damage radius over .2 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 4 ); // life + WRITE_BYTE( 32 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 192 ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + EMIT_SOUND(ENT(pev), CHAN_STATIC, "weapons/mortarhit.wav", 1.0, 0.3); + + RadiusDamage( pev->origin, pev, pev, 300, CLASS_NONE, DMG_BLAST ); + + if (/*!(pev->spawnflags & SF_NOWRECKAGE) && */(pev->flags & FL_ONGROUND)) + { + CBaseEntity *pWreckage = Create( "cycler_wreckage", pev->origin, pev->angles ); + // SET_MODEL( ENT(pWreckage->pev), STRING(pev->model) ); + UTIL_SetSize( pWreckage->pev, Vector( -200, -200, -128 ), Vector( 200, 200, -32 ) ); + pWreckage->pev->frame = pev->frame; + pWreckage->pev->sequence = pev->sequence; + pWreckage->pev->framerate = 0; + pWreckage->pev->dmgtime = gpGlobals->time + 5; + } + + // gibs + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 64); + + // size + WRITE_COORD( 400 ); + WRITE_COORD( 400 ); + WRITE_COORD( 128 ); + + // velocity + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + WRITE_COORD( 200 ); + + // randomization + WRITE_BYTE( 30 ); + + // Model + WRITE_SHORT( m_iBodyGibs ); //model id# + + // # of shards + WRITE_BYTE( 200 ); + + // duration + WRITE_BYTE( 200 );// 10.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + SetThink( SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + } +} + + +void CApache::FlyTouch( CBaseEntity *pOther ) +{ + // bounce if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + + // UNDONE, do a real bounce + pev->velocity = pev->velocity + tr.vecPlaneNormal * (pev->velocity.Length() + 200); + } +} + + +void CApache::CrashTouch( CBaseEntity *pOther ) +{ + // only crash if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + SetTouch( NULL ); + m_flNextRocket = gpGlobals->time; + pev->nextthink = gpGlobals->time; + } +} + + + +void CApache :: GibMonster( void ) +{ + // EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200); +} + + +void CApache :: HuntThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + ShowDamage( ); + + if ( m_pGoalEnt == NULL && !FStringNull(pev->target) )// this monster has a target + { + m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ) ); + if (m_pGoalEnt) + { + m_posDesired = m_pGoalEnt->pev->origin; + UTIL_MakeAimVectors( m_pGoalEnt->pev->angles ); + m_vecGoal = gpGlobals->v_forward; + } + } + + // if (m_hEnemy == NULL) + { + Look( 4092 ); + m_hEnemy = BestVisibleEnemy( ); + } + + // generic speed up + if (m_flGoalSpeed < 800) + m_flGoalSpeed += 5; + + if (m_hEnemy != NULL) + { + // ALERT( at_console, "%s\n", STRING( m_hEnemy->pev->classname ) ); + if (FVisible( m_hEnemy )) + { + if (m_flLastSeen < gpGlobals->time - 5) + m_flPrevSeen = gpGlobals->time; + m_flLastSeen = gpGlobals->time; + m_posTarget = m_hEnemy->Center( ); + } + else + { + m_hEnemy = NULL; + } + } + + m_vecTarget = (m_posTarget - pev->origin).Normalize(); + + float flLength = (pev->origin - m_posDesired).Length(); + + if (m_pGoalEnt) + { + // ALERT( at_console, "%.0f\n", flLength ); + + if (flLength < 128) + { + m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( m_pGoalEnt->pev->target ) ); + if (m_pGoalEnt) + { + m_posDesired = m_pGoalEnt->pev->origin; + UTIL_MakeAimVectors( m_pGoalEnt->pev->angles ); + m_vecGoal = gpGlobals->v_forward; + flLength = (pev->origin - m_posDesired).Length(); + } + } + } + else + { + m_posDesired = pev->origin; + } + + if (flLength > 250) // 500 + { + // float flLength2 = (m_posTarget - pev->origin).Length() * (1.5 - DotProduct((m_posTarget - pev->origin).Normalize(), pev->velocity.Normalize() )); + // if (flLength2 < flLength) + if (m_flLastSeen + 90 > gpGlobals->time && DotProduct( (m_posTarget - pev->origin).Normalize(), (m_posDesired - pev->origin).Normalize( )) > 0.25) + { + m_vecDesired = (m_posTarget - pev->origin).Normalize( ); + } + else + { + m_vecDesired = (m_posDesired - pev->origin).Normalize( ); + } + } + else + { + m_vecDesired = m_vecGoal; + } + + Flight( ); + + // ALERT( at_console, "%.0f %.0f %.0f\n", gpGlobals->time, m_flLastSeen, m_flPrevSeen ); + if ((m_flLastSeen + 1 > gpGlobals->time) && (m_flPrevSeen + 2 < gpGlobals->time)) + { + if (FireGun( )) + { + // slow down if we're fireing + if (m_flGoalSpeed > 400) + m_flGoalSpeed = 400; + } + + // don't fire rockets and gun on easy mode + if (g_iSkillLevel == SKILL_EASY) + m_flNextRocket = gpGlobals->time + 10.0; + } + + UTIL_MakeAimVectors( pev->angles ); + Vector vecEst = (gpGlobals->v_forward * 800 + pev->velocity).Normalize( ); + // ALERT( at_console, "%d %d %d %4.2f\n", pev->angles.x < 0, DotProduct( pev->velocity, gpGlobals->v_forward ) > -100, m_flNextRocket < gpGlobals->time, DotProduct( m_vecTarget, vecEst ) ); + + if ((m_iRockets % 2) == 1) + { + FireRocket( ); + m_flNextRocket = gpGlobals->time + 0.5; + if (m_iRockets <= 0) + { + m_flNextRocket = gpGlobals->time + 10; + m_iRockets = 10; + } + } + else if (pev->angles.x < 0 && DotProduct( pev->velocity, gpGlobals->v_forward ) > -100 && m_flNextRocket < gpGlobals->time) + { + if (m_flLastSeen + 60 > gpGlobals->time) + { + if (m_hEnemy != NULL) + { + // make sure it's a good shot + if (DotProduct( m_vecTarget, vecEst) > .965) + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, pev->origin + vecEst * 4096, ignore_monsters, edict(), &tr ); + if ((tr.vecEndPos - m_posTarget).Length() < 512) + FireRocket( ); + } + } + else + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, pev->origin + vecEst * 4096, dont_ignore_monsters, edict(), &tr ); + // just fire when close + if ((tr.vecEndPos - m_posTarget).Length() < 512) + FireRocket( ); + } + } + } +} + + +void CApache :: Flight( void ) +{ + // tilt model 5 degrees + Vector vecAdj = Vector( 5.0, 0, 0 ); + + // estimate where I'll be facing in one seconds + UTIL_MakeAimVectors( pev->angles + pev->avelocity * 2 + vecAdj); + // Vector vecEst1 = pev->origin + pev->velocity + gpGlobals->v_up * m_flForce - Vector( 0, 0, 384 ); + // float flSide = DotProduct( m_posDesired - vecEst1, gpGlobals->v_right ); + + float flSide = DotProduct( m_vecDesired, gpGlobals->v_right ); + + if (flSide < 0) + { + if (pev->avelocity.y < 60) + { + pev->avelocity.y += 8; // 9 * (3.0/2.0); + } + } + else + { + if (pev->avelocity.y > -60) + { + pev->avelocity.y -= 8; // 9 * (3.0/2.0); + } + } + pev->avelocity.y *= 0.98; + + // estimate where I'll be in two seconds + UTIL_MakeAimVectors( pev->angles + pev->avelocity * 1 + vecAdj); + Vector vecEst = pev->origin + pev->velocity * 2.0 + gpGlobals->v_up * m_flForce * 20 - Vector( 0, 0, 384 * 2 ); + + // add immediate force + UTIL_MakeAimVectors( pev->angles + vecAdj); + pev->velocity.x += gpGlobals->v_up.x * m_flForce; + pev->velocity.y += gpGlobals->v_up.y * m_flForce; + pev->velocity.z += gpGlobals->v_up.z * m_flForce; + // add gravity + pev->velocity.z -= 38.4; // 32ft/sec + + + float flSpeed = pev->velocity.Length(); + float flDir = DotProduct( Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, 0 ), Vector( pev->velocity.x, pev->velocity.y, 0 ) ); + if (flDir < 0) + flSpeed = -flSpeed; + + float flDist = DotProduct( m_posDesired - vecEst, gpGlobals->v_forward ); + + // float flSlip = DotProduct( pev->velocity, gpGlobals->v_right ); + float flSlip = -DotProduct( m_posDesired - vecEst, gpGlobals->v_right ); + + // fly sideways + if (flSlip > 0) + { + if (pev->angles.z > -30 && pev->avelocity.z > -15) + pev->avelocity.z -= 4; + else + pev->avelocity.z += 2; + } + else + { + + if (pev->angles.z < 30 && pev->avelocity.z < 15) + pev->avelocity.z += 4; + else + pev->avelocity.z -= 2; + } + + // sideways drag + pev->velocity.x = pev->velocity.x * (1.0 - fabs( gpGlobals->v_right.x ) * 0.05); + pev->velocity.y = pev->velocity.y * (1.0 - fabs( gpGlobals->v_right.y ) * 0.05); + pev->velocity.z = pev->velocity.z * (1.0 - fabs( gpGlobals->v_right.z ) * 0.05); + + // general drag + pev->velocity = pev->velocity * 0.995; + + // apply power to stay correct height + if (m_flForce < 80 && vecEst.z < m_posDesired.z) + { + m_flForce += 12; + } + else if (m_flForce > 30) + { + if (vecEst.z > m_posDesired.z) + m_flForce -= 8; + } + + // pitch forward or back to get to target + if (flDist > 0 && flSpeed < m_flGoalSpeed /* && flSpeed < flDist */ && pev->angles.x + pev->avelocity.x > -40) + { + // ALERT( at_console, "F " ); + // lean forward + pev->avelocity.x -= 12.0; + } + else if (flDist < 0 && flSpeed > -50 && pev->angles.x + pev->avelocity.x < 20) + { + // ALERT( at_console, "B " ); + // lean backward + pev->avelocity.x += 12.0; + } + else if (pev->angles.x + pev->avelocity.x > 0) + { + // ALERT( at_console, "f " ); + pev->avelocity.x -= 4.0; + } + else if (pev->angles.x + pev->avelocity.x < 0) + { + // ALERT( at_console, "b " ); + pev->avelocity.x += 4.0; + } + + // ALERT( at_console, "%.0f %.0f : %.0f %.0f : %.0f %.0f : %.0f\n", pev->origin.x, pev->velocity.x, flDist, flSpeed, pev->angles.x, pev->avelocity.x, m_flForce ); + // ALERT( at_console, "%.0f %.0f : %.0f %0.f : %.0f\n", pev->origin.z, pev->velocity.z, vecEst.z, m_posDesired.z, m_flForce ); + + // make rotor, engine sounds + if (m_iSoundState == 0) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav", 1.0, 0.3, 0, 110 ); + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", 0.5, 0.2, 0, 110 ); + + m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions + } + else + { + CBaseEntity *pPlayer = NULL; + + pPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + // UNDONE: this needs to send different sounds to every player for multiplayer. + if (pPlayer) + { + + float pitch = DotProduct( pev->velocity - pPlayer->pev->velocity, (pPlayer->pev->origin - pev->origin).Normalize() ); + + pitch = (int)(100 + pitch / 50.0); + + if (pitch > 250) + pitch = 250; + if (pitch < 50) + pitch = 50; + if (pitch == 100) + pitch = 101; + + float flVol = (m_flForce / 100.0) + .1; + if (flVol > 1.0) + flVol = 1.0; + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor2.wav", 1.0, 0.3, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + } + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", flVol, 0.2, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + + // ALERT( at_console, "%.0f %.2f\n", pitch, flVol ); + } +} + + +void CApache :: FireRocket( void ) +{ + static float side = 1.0; + static int count; + + if (m_iRockets <= 0) + return; + + UTIL_MakeAimVectors( pev->angles ); + Vector vecSrc = pev->origin + 1.5 * (gpGlobals->v_forward * 21 + gpGlobals->v_right * 70 * side + gpGlobals->v_up * -79); + + switch( m_iRockets % 5) + { + case 0: vecSrc = vecSrc + gpGlobals->v_right * 10; break; + case 1: vecSrc = vecSrc - gpGlobals->v_right * 10; break; + case 2: vecSrc = vecSrc + gpGlobals->v_up * 10; break; + case 3: vecSrc = vecSrc - gpGlobals->v_up * 10; break; + case 4: break; + } + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x ); + WRITE_COORD( vecSrc.y ); + WRITE_COORD( vecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + + CBaseEntity *pRocket = CBaseEntity::Create( "hvr_rocket", vecSrc, pev->angles, edict() ); + if (pRocket) + pRocket->pev->velocity = pev->velocity + gpGlobals->v_forward * 100; + + m_iRockets--; + + side = - side; +} + + + +BOOL CApache :: FireGun( ) +{ + UTIL_MakeAimVectors( pev->angles ); + + Vector posGun, angGun; + GetAttachment( 1, posGun, angGun ); + + Vector vecTarget = (m_posTarget - posGun).Normalize( ); + + Vector vecOut; + + vecOut.x = DotProduct( gpGlobals->v_forward, vecTarget ); + vecOut.y = -DotProduct( gpGlobals->v_right, vecTarget ); + vecOut.z = DotProduct( gpGlobals->v_up, vecTarget ); + + Vector angles = UTIL_VecToAngles (vecOut); + + angles.x = -angles.x; + if (angles.y > 180) + angles.y = angles.y - 360; + if (angles.y < -180) + angles.y = angles.y + 360; + if (angles.x > 180) + angles.x = angles.x - 360; + if (angles.x < -180) + angles.x = angles.x + 360; + + if (angles.x > m_angGun.x) + m_angGun.x = min( angles.x, m_angGun.x + 12 ); + if (angles.x < m_angGun.x) + m_angGun.x = max( angles.x, m_angGun.x - 12 ); + if (angles.y > m_angGun.y) + m_angGun.y = min( angles.y, m_angGun.y + 12 ); + if (angles.y < m_angGun.y) + m_angGun.y = max( angles.y, m_angGun.y - 12 ); + + m_angGun.y = SetBoneController( 0, m_angGun.y ); + m_angGun.x = SetBoneController( 1, m_angGun.x ); + + Vector posBarrel, angBarrel; + GetAttachment( 0, posBarrel, angBarrel ); + Vector vecGun = (posBarrel - posGun).Normalize( ); + + if (DotProduct( vecGun, vecTarget ) > 0.98) + { +#if 1 + FireBullets( 1, posGun, vecGun, VECTOR_CONE_4DEGREES, 8192, BULLET_MONSTER_12MM, 1 ); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "turret/tu_fire1.wav", 1, 0.3); +#else + static float flNext; + TraceResult tr; + UTIL_TraceLine( posGun, posGun + vecGun * 8192, dont_ignore_monsters, ENT( pev ), &tr ); + + if (!m_pBeam) + { + m_pBeam = CBeam::BeamCreate( "sprites/lgtning.spr", 80 ); + m_pBeam->PointEntInit( pev->origin, entindex( ) ); + m_pBeam->SetEndAttachment( 1 ); + m_pBeam->SetColor( 255, 180, 96 ); + m_pBeam->SetBrightness( 192 ); + } + + if (flNext < gpGlobals->time) + { + flNext = gpGlobals->time + 0.5; + m_pBeam->SetStartPos( tr.vecEndPos ); + } +#endif + return TRUE; + } + else + { + if (m_pBeam) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } + } + return FALSE; +} + + + +void CApache :: ShowDamage( void ) +{ + if (m_iDoSmokePuff > 0 || RANDOM_LONG(0,99) > pev->health) + { + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z - 32 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,9) + 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + if (m_iDoSmokePuff > 0) + m_iDoSmokePuff--; +} + + +int CApache :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (pevInflictor->owner == edict()) + return 0; + + if (bitsDamageType & DMG_BLAST) + { + flDamage *= 2; + } + + /* + if ( (bitsDamageType & DMG_BULLET) && flDamage > 50) + { + // clip bullet damage at 50 + flDamage = 50; + } + */ + + // ALERT( at_console, "%.0f\n", flDamage ); + return CBaseEntity::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + + +void CApache::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // ALERT( at_console, "%d %.0f\n", ptr->iHitgroup, flDamage ); + + // ignore blades + if (ptr->iHitgroup == 6 && (bitsDamageType & (DMG_ENERGYBEAM|DMG_BULLET|DMG_CLUB))) + return; + + // hit hard, hits cockpit, hits engines + if (flDamage > 50 || ptr->iHitgroup == 1 || ptr->iHitgroup == 2) + { + // ALERT( at_console, "%.0f\n", flDamage ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + m_iDoSmokePuff = 3 + (flDamage / 5.0); + } + else + { + // do half damage in the body + // AddMultiDamage( pevAttacker, this, flDamage / 2.0, bitsDamageType ); + UTIL_Ricochet( ptr->vecEndPos, 2.0 ); + } +} + + + + + +class CApacheHVR : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + void EXPORT IgniteThink( void ); + void EXPORT AccelerateThink( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iTrail; + Vector m_vecForward; +}; +LINK_ENTITY_TO_CLASS( hvr_rocket, CApacheHVR ); + +TYPEDESCRIPTION CApacheHVR::m_SaveData[] = +{ +// DEFINE_FIELD( CApacheHVR, m_iTrail, FIELD_INTEGER ), // Dont' save, precache + DEFINE_FIELD( CApacheHVR, m_vecForward, FIELD_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CApacheHVR, CGrenade ); + +void CApacheHVR :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/HVR.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( IgniteThink ); + SetTouch( ExplodeTouch ); + + UTIL_MakeAimVectors( pev->angles ); + m_vecForward = gpGlobals->v_forward; + pev->gravity = 0.5; + + pev->nextthink = gpGlobals->time + 0.1; + + pev->dmg = 150; +} + + +void CApacheHVR :: Precache( void ) +{ + PRECACHE_MODEL("models/HVR.mdl"); + m_iTrail = PRECACHE_MODEL("sprites/smoke.spr"); + PRECACHE_SOUND ("weapons/rocket1.wav"); +} + + +void CApacheHVR :: IgniteThink( void ) +{ + // pev->movetype = MOVETYPE_TOSS; + + // pev->movetype = MOVETYPE_FLY; + pev->effects |= EF_LIGHT; + + // make rocket sound + EMIT_SOUND( ENT(pev), CHAN_VOICE, "weapons/rocket1.wav", 1, 0.5 ); + + // rocket trail + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + + WRITE_BYTE( TE_BEAMFOLLOW ); + WRITE_SHORT(entindex()); // entity + WRITE_SHORT(m_iTrail ); // model + WRITE_BYTE( 15 ); // life + WRITE_BYTE( 5 ); // width + WRITE_BYTE( 224 ); // r, g, b + WRITE_BYTE( 224 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + + MESSAGE_END(); // move PHS/PVS data sending into here (SEND_ALL, SEND_PVS, SEND_PHS) + + // set to accelerate + SetThink( AccelerateThink ); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CApacheHVR :: AccelerateThink( void ) +{ + // check world boundaries + if (pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + UTIL_Remove( this ); + return; + } + + // accelerate + float flSpeed = pev->velocity.Length(); + if (flSpeed < 1800) + { + pev->velocity = pev->velocity + m_vecForward * 200; + } + + // re-aim + pev->angles = UTIL_VecToAngles( pev->velocity ); + + pev->nextthink = gpGlobals->time + 0.1; +} + + +#endif \ No newline at end of file diff --git a/dlls/barnacle.cpp b/dlls/barnacle.cpp new file mode 100644 index 0000000..67b1b94 --- /dev/null +++ b/dlls/barnacle.cpp @@ -0,0 +1,428 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// barnacle - stationary ceiling mounted 'fishing' monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +#define BARNACLE_BODY_HEIGHT 44 // how 'tall' the barnacle's model is. +#define BARNACLE_PULL_SPEED 8 +#define BARNACLE_KILL_VICTIM_DELAY 5 // how many seconds after pulling prey in to gib them. + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BARNACLE_AE_PUKEGIB 2 + +class CBarnacle : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + CBaseEntity *TongueTouchEnt ( float *pflLength ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void EXPORT BarnacleThink ( void ); + void EXPORT WaitTillDead ( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flAltitude; + float m_flKillVictimTime; + int m_cGibs;// barnacle loads up on gibs each time it kills something. + BOOL m_fTongueExtended; + BOOL m_fLiftingPrey; + float m_flTongueAdj; +}; +LINK_ENTITY_TO_CLASS( monster_barnacle, CBarnacle ); + +TYPEDESCRIPTION CBarnacle::m_SaveData[] = +{ + DEFINE_FIELD( CBarnacle, m_flAltitude, FIELD_FLOAT ), + DEFINE_FIELD( CBarnacle, m_flKillVictimTime, FIELD_TIME ), + DEFINE_FIELD( CBarnacle, m_cGibs, FIELD_INTEGER ),// barnacle loads up on gibs each time it kills something. + DEFINE_FIELD( CBarnacle, m_fTongueExtended, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarnacle, m_fLiftingPrey, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarnacle, m_flTongueAdj, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBarnacle, CBaseMonster ); + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBarnacle :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CBarnacle :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BARNACLE_AE_PUKEGIB: + CGib::SpawnRandomGibs( pev, 1, 1 ); + break; + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CBarnacle :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/barnacle.mdl"); + UTIL_SetSize( pev, Vector(-16, -16, -32), Vector(16, 16, 0) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_AIM; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = EF_INVLIGHT; // take light from the ceiling + pev->health = 25; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flKillVictimTime = 0; + m_cGibs = 0; + m_fLiftingPrey = FALSE; + m_flTongueAdj = -100; + + InitBoneControllers(); + + SetActivity ( ACT_IDLE ); + + SetThink ( BarnacleThink ); + pev->nextthink = gpGlobals->time + 0.5; + + UTIL_SetOrigin ( pev, pev->origin ); +} + +int CBarnacle::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( bitsDamageType & DMG_CLUB ) + { + flDamage = pev->health; + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +//========================================================= +void CBarnacle :: BarnacleThink ( void ) +{ + CBaseEntity *pTouchEnt; + CBaseMonster *pVictim; + float flLength; + + pev->nextthink = gpGlobals->time + 0.1; + + if ( m_hEnemy != NULL ) + { +// barnacle has prey. + + if ( !m_hEnemy->IsAlive() ) + { + // someone (maybe even the barnacle) killed the prey. Reset barnacle. + m_fLiftingPrey = FALSE;// indicate that we're not lifting prey. + m_hEnemy = NULL; + return; + } + + if ( m_fLiftingPrey ) + { + if ( m_hEnemy != NULL && m_hEnemy->pev->deadflag != DEAD_NO ) + { + // crap, someone killed the prey on the way up. + m_hEnemy = NULL; + m_fLiftingPrey = FALSE; + return; + } + + // still pulling prey. + Vector vecNewEnemyOrigin = m_hEnemy->pev->origin; + vecNewEnemyOrigin.x = pev->origin.x; + vecNewEnemyOrigin.y = pev->origin.y; + + // guess as to where their neck is + vecNewEnemyOrigin.x -= 6 * cos(m_hEnemy->pev->angles.y * M_PI/180.0); + vecNewEnemyOrigin.y -= 6 * sin(m_hEnemy->pev->angles.y * M_PI/180.0); + + m_flAltitude -= BARNACLE_PULL_SPEED; + vecNewEnemyOrigin.z += BARNACLE_PULL_SPEED; + + if ( fabs( pev->origin.z - ( vecNewEnemyOrigin.z + m_hEnemy->pev->view_ofs.z - 8 ) ) < BARNACLE_BODY_HEIGHT ) + { + // prey has just been lifted into position ( if the victim origin + eye height + 8 is higher than the bottom of the barnacle, it is assumed that the head is within barnacle's body ) + m_fLiftingPrey = FALSE; + + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_bite3.wav", 1, ATTN_NORM ); + + pVictim = m_hEnemy->MyMonsterPointer(); + + m_flKillVictimTime = gpGlobals->time + 10;// now that the victim is in place, the killing bite will be administered in 10 seconds. + + if ( pVictim ) + { + pVictim->BarnacleVictimBitten( pev ); + SetActivity ( ACT_EAT ); + } + } + + UTIL_SetOrigin ( m_hEnemy->pev, vecNewEnemyOrigin ); + } + else + { + // prey is lifted fully into feeding position and is dangling there. + + pVictim = m_hEnemy->MyMonsterPointer(); + + if ( m_flKillVictimTime != -1 && gpGlobals->time > m_flKillVictimTime ) + { + // kill! + if ( pVictim ) + { + pVictim->TakeDamage ( pev, pev, pVictim->pev->health, DMG_SLASH | DMG_ALWAYSGIB ); + m_cGibs = 3; + } + + return; + } + + // bite prey every once in a while + if ( pVictim && ( RANDOM_LONG(0,49) == 0 ) ) + { + switch ( RANDOM_LONG(0,2) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew3.wav", 1, ATTN_NORM ); break; + } + + pVictim->BarnacleVictimBitten( pev ); + } + + } + } + else + { +// barnacle has no prey right now, so just idle and check to see if anything is touching the tongue. + + // If idle and no nearby client, don't think so often + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(1,1.5); // Stagger a bit to keep barnacles from thinking on the same frame + + if ( m_fSequenceFinished ) + {// this is done so barnacle will fidget. + SetActivity ( ACT_IDLE ); + m_flTongueAdj = -100; + } + + if ( m_cGibs && RANDOM_LONG(0,99) == 1 ) + { + // cough up a gib. + CGib::SpawnRandomGibs( pev, 1, 1 ); + m_cGibs--; + + switch ( RANDOM_LONG(0,2) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew2.wav", 1, ATTN_NORM ); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_chew3.wav", 1, ATTN_NORM ); break; + } + } + + pTouchEnt = TongueTouchEnt( &flLength ); + + if ( pTouchEnt != NULL && m_fTongueExtended ) + { + // tongue is fully extended, and is touching someone. + if ( pTouchEnt->FBecomeProne() ) + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_alert2.wav", 1, ATTN_NORM ); + + SetSequenceByName ( "attack1" ); + m_flTongueAdj = -20; + + m_hEnemy = pTouchEnt; + + pTouchEnt->pev->movetype = MOVETYPE_FLY; + pTouchEnt->pev->velocity = g_vecZero; + pTouchEnt->pev->basevelocity = g_vecZero; + pTouchEnt->pev->origin.x = pev->origin.x; + pTouchEnt->pev->origin.y = pev->origin.y; + + m_fLiftingPrey = TRUE;// indicate that we should be lifting prey. + m_flKillVictimTime = -1;// set this to a bogus time while the victim is lifted. + + m_flAltitude = (pev->origin.z - pTouchEnt->EyePosition().z); + } + } + else + { + // calculate a new length for the tongue to be clear of anything else that moves under it. + if ( m_flAltitude < flLength ) + { + // if tongue is higher than is should be, lower it kind of slowly. + m_flAltitude += BARNACLE_PULL_SPEED; + m_fTongueExtended = FALSE; + } + else + { + m_flAltitude = flLength; + m_fTongueExtended = TRUE; + } + + } + + } + + // ALERT( at_console, "tounge %f\n", m_flAltitude + m_flTongueAdj ); + SetBoneController( 0, -(m_flAltitude + m_flTongueAdj) ); + StudioFrameAdvance( 0.1 ); +} + +//========================================================= +// Killed. +//========================================================= +void CBarnacle :: Killed( entvars_t *pevAttacker, int iGib ) +{ + CBaseMonster *pVictim; + + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + + if ( m_hEnemy != NULL ) + { + pVictim = m_hEnemy->MyMonsterPointer(); + + if ( pVictim ) + { + pVictim->BarnacleVictimReleased(); + } + } + +// CGib::SpawnRandomGibs( pev, 4, 1 ); + + switch ( RANDOM_LONG ( 0, 1 ) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_die1.wav", 1, ATTN_NORM ); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_WEAPON, "barnacle/bcl_die3.wav", 1, ATTN_NORM ); break; + } + + SetActivity ( ACT_DIESIMPLE ); + SetBoneController( 0, 0 ); + + StudioFrameAdvance( 0.1 ); + + pev->nextthink = gpGlobals->time + 0.1; + SetThink ( WaitTillDead ); +} + +//========================================================= +//========================================================= +void CBarnacle :: WaitTillDead ( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + float flInterval = StudioFrameAdvance( 0.1 ); + DispatchAnimEvents ( flInterval ); + + if ( m_fSequenceFinished ) + { + // death anim finished. + StopAnimation(); + SetThink ( NULL ); + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBarnacle :: Precache() +{ + PRECACHE_MODEL("models/barnacle.mdl"); + + PRECACHE_SOUND("barnacle/bcl_alert2.wav");//happy, lifting food up + PRECACHE_SOUND("barnacle/bcl_bite3.wav");//just got food to mouth + PRECACHE_SOUND("barnacle/bcl_chew1.wav"); + PRECACHE_SOUND("barnacle/bcl_chew2.wav"); + PRECACHE_SOUND("barnacle/bcl_chew3.wav"); + PRECACHE_SOUND("barnacle/bcl_die1.wav" ); + PRECACHE_SOUND("barnacle/bcl_die3.wav" ); +} + +//========================================================= +// TongueTouchEnt - does a trace along the barnacle's tongue +// to see if any entity is touching it. Also stores the length +// of the trace in the int pointer provided. +//========================================================= +#define BARNACLE_CHECK_SPACING 8 +CBaseEntity *CBarnacle :: TongueTouchEnt ( float *pflLength ) +{ + TraceResult tr; + float length; + + // trace once to hit architecture and see if the tongue needs to change position. + UTIL_TraceLine ( pev->origin, pev->origin - Vector ( 0 , 0 , 2048 ), ignore_monsters, ENT(pev), &tr ); + length = fabs( pev->origin.z - tr.vecEndPos.z ); + if ( pflLength ) + { + *pflLength = length; + } + + Vector delta = Vector( BARNACLE_CHECK_SPACING, BARNACLE_CHECK_SPACING, 0 ); + Vector mins = pev->origin - delta; + Vector maxs = pev->origin + delta; + maxs.z = pev->origin.z; + mins.z -= length; + + CBaseEntity *pList[10]; + int count = UTIL_EntitiesInBox( pList, 10, mins, maxs, (FL_CLIENT|FL_MONSTER) ); + if ( count ) + { + for ( int i = 0; i < count; i++ ) + { + // only clients and monsters + if ( pList[i] != this && IRelationship( pList[i] ) > R_NO && pList[ i ]->pev->deadflag == DEAD_NO ) // this ent is one of our enemies. Barnacle tries to eat it. + { + return pList[i]; + } + } + } + + return NULL; +} diff --git a/dlls/barney.cpp b/dlls/barney.cpp new file mode 100644 index 0000000..76eba8d --- /dev/null +++ b/dlls/barney.cpp @@ -0,0 +1,841 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// monster template +//========================================================= +// UNDONE: Holster weapon? + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "talkmonster.h" +#include "schedule.h" +#include "defaultai.h" +#include "scripted.h" +#include "weapons.h" +#include "soundent.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +// first flag is barney dying for scripted sequences? +#define BARNEY_AE_DRAW ( 2 ) +#define BARNEY_AE_SHOOT ( 3 ) +#define BARNEY_AE_HOLSTER ( 4 ) + +#define BARNEY_BODY_GUNHOLSTERED 0 +#define BARNEY_BODY_GUNDRAWN 1 +#define BARNEY_BODY_GUNGONE 2 + +class CBarney : public CTalkMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int ISoundMask( void ); + void BarneyFirePistol( void ); + void AlertSound( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void RunTask( Task_t *pTask ); + void StartTask( Task_t *pTask ); + virtual int ObjectCaps( void ) { return CTalkMonster :: ObjectCaps() | FCAP_IMPULSE_USE; } + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + + void DeclineFollowing( void ); + + // Override these to set behavior + Schedule_t *GetScheduleOfType ( int Type ); + Schedule_t *GetSchedule ( void ); + MONSTERSTATE GetIdealState ( void ); + + void DeathSound( void ); + void PainSound( void ); + + void TalkInit( void ); + + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + void Killed( entvars_t *pevAttacker, int iGib ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BOOL m_fGunDrawn; + float m_painTime; + float m_checkAttackTime; + BOOL m_lastAttackCheck; + + // UNDONE: What is this for? It isn't used? + float m_flPlayerDamage;// how much pain has the player inflicted on me? + + CUSTOM_SCHEDULES; +}; + +LINK_ENTITY_TO_CLASS( monster_barney, CBarney ); + +TYPEDESCRIPTION CBarney::m_SaveData[] = +{ + DEFINE_FIELD( CBarney, m_fGunDrawn, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarney, m_painTime, FIELD_TIME ), + DEFINE_FIELD( CBarney, m_checkAttackTime, FIELD_TIME ), + DEFINE_FIELD( CBarney, m_lastAttackCheck, FIELD_BOOLEAN ), + DEFINE_FIELD( CBarney, m_flPlayerDamage, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CBarney, CTalkMonster ); + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlBaFollow[] = +{ + { TASK_MOVE_TO_TARGET_RANGE,(float)128 }, // Move within 128 of target ent (client) + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slBaFollow[] = +{ + { + tlBaFollow, + ARRAYSIZE ( tlBaFollow ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "Follow" + }, +}; + +//========================================================= +// BarneyDraw- much better looking draw schedule for when +// barney knows who he's gonna attack. +//========================================================= +Task_t tlBarneyEnemyDraw[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, 0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float) ACT_ARM }, +}; + +Schedule_t slBarneyEnemyDraw[] = +{ + { + tlBarneyEnemyDraw, + ARRAYSIZE ( tlBarneyEnemyDraw ), + 0, + 0, + "Barney Enemy Draw" + } +}; + +Task_t tlBaFaceTarget[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE }, +}; + +Schedule_t slBaFaceTarget[] = +{ + { + tlBaFaceTarget, + ARRAYSIZE ( tlBaFaceTarget ), + bits_COND_CLIENT_PUSH | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "FaceTarget" + }, +}; + + +Task_t tlIdleBaStand[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, // repick IDLESTAND every two seconds. + { TASK_TLK_HEADRESET, (float)0 }, // reset head position +}; + +Schedule_t slIdleBaStand[] = +{ + { + tlIdleBaStand, + ARRAYSIZE ( tlIdleBaStand ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "IdleStand" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CBarney ) +{ + slBaFollow, + slBarneyEnemyDraw, + slBaFaceTarget, + slIdleBaStand, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CBarney, CTalkMonster ); + +void CBarney :: StartTask( Task_t *pTask ) +{ + CTalkMonster::StartTask( pTask ); +} + +void CBarney :: RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + if (m_hEnemy != NULL && (m_hEnemy->IsPlayer())) + { + pev->framerate = 1.5; + } + CTalkMonster::RunTask( pTask ); + break; + default: + CTalkMonster::RunTask( pTask ); + break; + } +} + + + + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CBarney :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_CARCASS | + bits_SOUND_MEAT | + bits_SOUND_GARBAGE | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBarney :: Classify ( void ) +{ + return CLASS_PLAYER_ALLY; +} + +//========================================================= +// ALertSound - barney says "Freeze!" +//========================================================= +void CBarney :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + if ( FOkToSpeak() ) + { + PlaySentence( "BA_ATTACK", RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + } + } + +} +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CBarney :: SetYawSpeed ( void ) +{ + int ys; + + ys = 0; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 70; + break; + case ACT_WALK: + ys = 70; + break; + case ACT_RUN: + ys = 90; + break; + default: + ys = 70; + break; + } + + pev->yaw_speed = ys; +} + + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CBarney :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist <= 1024 && flDot >= 0.5 ) + { + if ( gpGlobals->time > m_checkAttackTime ) + { + TraceResult tr; + + Vector shootOrigin = pev->origin + Vector( 0, 0, 55 ); + CBaseEntity *pEnemy = m_hEnemy; + Vector shootTarget = ( (pEnemy->BodyTarget( shootOrigin ) - pEnemy->pev->origin) + m_vecEnemyLKP ); + UTIL_TraceLine( shootOrigin, shootTarget, dont_ignore_monsters, ENT(pev), &tr ); + m_checkAttackTime = gpGlobals->time + 1; + if ( tr.flFraction == 1.0 || (tr.pHit != NULL && CBaseEntity::Instance(tr.pHit) == pEnemy) ) + m_lastAttackCheck = TRUE; + else + m_lastAttackCheck = FALSE; + m_checkAttackTime = gpGlobals->time + 1.5; + } + return m_lastAttackCheck; + } + return FALSE; +} + + +//========================================================= +// BarneyFirePistol - shoots one round from the pistol at +// the enemy barney is facing. +//========================================================= +void CBarney :: BarneyFirePistol ( void ) +{ + Vector vecShootOrigin; + + UTIL_MakeVectors(pev->angles); + vecShootOrigin = pev->origin + Vector( 0, 0, 55 ); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); + pev->effects = EF_MUZZLEFLASH; + + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_2DEGREES, 1024, BULLET_MONSTER_9MM ); + + int pitchShift = RANDOM_LONG( 0, 20 ); + + // Only shift about half the time + if ( pitchShift > 10 ) + pitchShift = 0; + else + pitchShift -= 5; + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "barney/ba_attack2.wav", 1, ATTN_NORM, 0, 100 + pitchShift ); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + + // UNDONE: Reload? + m_cAmmoLoaded--;// take away a bullet! +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CBarney :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BARNEY_AE_SHOOT: + BarneyFirePistol(); + break; + + case BARNEY_AE_DRAW: + // barney's bodygroup switches here so he can pull gun from holster + pev->body = BARNEY_BODY_GUNDRAWN; + m_fGunDrawn = TRUE; + break; + + case BARNEY_AE_HOLSTER: + // change bodygroup to replace gun in holster + pev->body = BARNEY_BODY_GUNHOLSTERED; + m_fGunDrawn = FALSE; + break; + + default: + CTalkMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CBarney :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/barney.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->health = gSkillData.barneyHealth; + pev->view_ofs = Vector ( 0, 0, 50 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so npc will notice player and say hello + m_MonsterState = MONSTERSTATE_NONE; + + pev->body = 0; // gun in holster + m_fGunDrawn = FALSE; + + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + MonsterInit(); + SetUse( FollowerUse ); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBarney :: Precache() +{ + PRECACHE_MODEL("models/barney.mdl"); + + PRECACHE_SOUND("barney/ba_attack1.wav" ); + PRECACHE_SOUND("barney/ba_attack2.wav" ); + + PRECACHE_SOUND("barney/ba_pain1.wav"); + PRECACHE_SOUND("barney/ba_pain2.wav"); + PRECACHE_SOUND("barney/ba_pain3.wav"); + + PRECACHE_SOUND("barney/ba_die1.wav"); + PRECACHE_SOUND("barney/ba_die2.wav"); + PRECACHE_SOUND("barney/ba_die3.wav"); + + // every new barney must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + CTalkMonster::Precache(); +} + +// Init talk data +void CBarney :: TalkInit() +{ + + CTalkMonster::TalkInit(); + + // scientists speach group names (group names are in sentences.txt) + + m_szGrp[TLK_ANSWER] = "BA_ANSWER"; + m_szGrp[TLK_QUESTION] = "BA_QUESTION"; + m_szGrp[TLK_IDLE] = "BA_IDLE"; + m_szGrp[TLK_STARE] = "BA_STARE"; + m_szGrp[TLK_USE] = "BA_OK"; + m_szGrp[TLK_UNUSE] = "BA_WAIT"; + m_szGrp[TLK_STOP] = "BA_STOP"; + + m_szGrp[TLK_NOSHOOT] = "BA_SCARED"; + m_szGrp[TLK_HELLO] = "BA_HELLO"; + + m_szGrp[TLK_PLHURT1] = "!BA_CUREA"; + m_szGrp[TLK_PLHURT2] = "!BA_CUREB"; + m_szGrp[TLK_PLHURT3] = "!BA_CUREC"; + + m_szGrp[TLK_PHELLO] = NULL; //"BA_PHELLO"; // UNDONE + m_szGrp[TLK_PIDLE] = NULL; //"BA_PIDLE"; // UNDONE + m_szGrp[TLK_PQUESTION] = "BA_PQUEST"; // UNDONE + + m_szGrp[TLK_SMELL] = "BA_SMELL"; + + m_szGrp[TLK_WOUND] = "BA_WOUND"; + m_szGrp[TLK_MORTAL] = "BA_MORTAL"; + + // get voice for head - just one barney voice for now + m_voicePitch = 100; +} + + +static BOOL IsFacing( entvars_t *pevTest, const Vector &reference ) +{ + Vector vecDir = (reference - pevTest->origin); + vecDir.z = 0; + vecDir = vecDir.Normalize(); + Vector forward, angle; + angle = pevTest->v_angle; + angle.x = 0; + UTIL_MakeVectorsPrivate( angle, forward, NULL, NULL ); + // He's facing me, he meant it + if ( DotProduct( forward, vecDir ) > 0.96 ) // +/- 15 degrees or so + { + return TRUE; + } + return FALSE; +} + + +int CBarney :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + // make sure friends talk about it if player hurts talkmonsters... + int ret = CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); + if ( !IsAlive() || pev->deadflag == DEAD_DYING ) + return ret; + + if ( m_MonsterState != MONSTERSTATE_PRONE && (pevAttacker->flags & FL_CLIENT) ) + { + m_flPlayerDamage += flDamage; + + // This is a heurstic to determine if the player intended to harm me + // If I have an enemy, we can't establish intent (may just be crossfire) + if ( m_hEnemy == NULL ) + { + // If the player was facing directly at me, or I'm already suspicious, get mad + if ( (m_afMemory & bits_MEMORY_SUSPICIOUS) || IsFacing( pevAttacker, pev->origin ) ) + { + // Alright, now I'm pissed! + PlaySentence( "BA_MAD", 4, VOL_NORM, ATTN_NORM ); + + Remember( bits_MEMORY_PROVOKED ); + StopFollowing( TRUE ); + } + else + { + // Hey, be careful with that + PlaySentence( "BA_SHOT", 4, VOL_NORM, ATTN_NORM ); + Remember( bits_MEMORY_SUSPICIOUS ); + } + } + else if ( !(m_hEnemy->IsPlayer()) && pev->deadflag == DEAD_NO ) + { + PlaySentence( "BA_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + } + + return ret; +} + + +//========================================================= +// PainSound +//========================================================= +void CBarney :: PainSound ( void ) +{ + if (gpGlobals->time < m_painTime) + return; + + m_painTime = gpGlobals->time + RANDOM_FLOAT(0.5, 0.75); + + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_pain2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_pain3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CBarney :: DeathSound ( void ) +{ + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_die1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_die2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "barney/ba_die3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + + +void CBarney::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + switch( ptr->iHitgroup) + { + case HITGROUP_CHEST: + case HITGROUP_STOMACH: + if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST)) + { + flDamage = flDamage / 2; + } + break; + case 10: + if (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_CLUB)) + { + flDamage -= 20; + if (flDamage <= 0) + { + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + flDamage = 0.01; + } + } + // always a head shot + ptr->iHitgroup = HITGROUP_HEAD; + break; + } + + CTalkMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +void CBarney::Killed( entvars_t *pevAttacker, int iGib ) +{ + if ( pev->body < BARNEY_BODY_GUNGONE ) + {// drop the gun! + Vector vecGunPos; + Vector vecGunAngles; + + pev->body = BARNEY_BODY_GUNGONE; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun = DropItem( "weapon_9mmhandgun", vecGunPos, vecGunAngles ); + } + + SetUse( NULL ); + CTalkMonster::Killed( pevAttacker, iGib ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +Schedule_t* CBarney :: GetScheduleOfType ( int Type ) +{ + Schedule_t *psched; + + switch( Type ) + { + case SCHED_ARM_WEAPON: + if ( m_hEnemy != NULL ) + { + // face enemy, then draw. + return slBarneyEnemyDraw; + } + break; + + // Hook these to make a looping schedule + case SCHED_TARGET_FACE: + // call base class default so that barney will talk + // when 'used' + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slBaFaceTarget; // override this for different target face behavior + else + return psched; + + case SCHED_TARGET_CHASE: + return slBaFollow; + + case SCHED_IDLE_STAND: + // call base class default so that scientist will talk + // when standing during idle + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + { + // just look straight ahead. + return slIdleBaStand; + } + else + return psched; + } + + return CTalkMonster::GetScheduleOfType( Type ); +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CBarney :: GetSchedule ( void ) +{ + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + if ( HasConditions( bits_COND_ENEMY_DEAD ) && FOkToSpeak() ) + { + PlaySentence( "BA_KILL", 4, VOL_NORM, ATTN_NORM ); + } + + switch( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + // always act surprized with a new enemy + if ( HasConditions( bits_COND_NEW_ENEMY ) && HasConditions( bits_COND_LIGHT_DAMAGE) ) + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + + // wait for one schedule to draw gun + if (!m_fGunDrawn ) + return GetScheduleOfType( SCHED_ARM_WEAPON ); + + if ( HasConditions( bits_COND_HEAVY_DAMAGE ) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) + { + // flinch if hurt + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + + if ( m_hEnemy == NULL && IsFollowing() ) + { + if ( !m_hTargetEnt->IsAlive() ) + { + // UNDONE: Comment about the recently dead player here? + StopFollowing( FALSE ); + break; + } + else + { + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) + { + return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); + } + return GetScheduleOfType( SCHED_TARGET_FACE ); + } + } + + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) + { + return GetScheduleOfType( SCHED_MOVE_AWAY ); + } + + // try to say something about smells + TrySmellTalk(); + break; + } + + return CTalkMonster::GetSchedule(); +} + +MONSTERSTATE CBarney :: GetIdealState ( void ) +{ + return CTalkMonster::GetIdealState(); +} + + + +void CBarney::DeclineFollowing( void ) +{ + PlaySentence( "BA_POK", 2, VOL_NORM, ATTN_NORM ); +} + + + + + +//========================================================= +// DEAD BARNEY PROP +// +// Designer selects a pose in worldcraft, 0 through num_poses-1 +// this value is added to what is selected as the 'first dead pose' +// among the monster's normal animations. All dead poses must +// appear sequentially in the model file. Be sure and set +// the m_iFirstPose properly! +// +//========================================================= +class CDeadBarney : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_PLAYER_ALLY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[3]; +}; + +char *CDeadBarney::m_szPoses[] = { "lying_on_back", "lying_on_side", "lying_on_stomach" }; + +void CDeadBarney::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_barney_dead, CDeadBarney ); + +//========================================================= +// ********** DeadBarney SPAWN ********** +//========================================================= +void CDeadBarney :: Spawn( ) +{ + PRECACHE_MODEL("models/barney.mdl"); + SET_MODEL(ENT(pev), "models/barney.mdl"); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead barney with bad pose\n" ); + } + // Corpses have less health + pev->health = 8;//gSkillData.barneyHealth; + + MonsterInitDead(); +} + + diff --git a/dlls/basemonster.h b/dlls/basemonster.h index ff1d847..04fbce9 100644 --- a/dlls/basemonster.h +++ b/dlls/basemonster.h @@ -1,94 +1,339 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. * ****/ + #ifndef BASEMONSTER_H #define BASEMONSTER_H +// +// generic Monster +// class CBaseMonster : public CBaseToggle { +private: + int m_afConditions; + public: - 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 + typedef enum + { + SCRIPT_PLAYING = 0, // Playing the sequence + SCRIPT_WAIT, // Waiting on everyone in the script to be ready + SCRIPT_CLEANUP, // Cancelling the script / cleaning up + SCRIPT_WALK_TO_MARK, + SCRIPT_RUN_TO_MARK, + } SCRIPTSTATE; + + + + // these fields have been added in the process of reworking the state machine. (sjb) + EHANDLE m_hEnemy; // the entity that the monster is fighting. + EHANDLE m_hTargetEnt; // the entity that the monster is trying to reach + EHANDLE m_hOldEnemy[ MAX_OLD_ENEMIES ]; + Vector m_vecOldEnemy[ MAX_OLD_ENEMIES ]; + + float m_flFieldOfView;// width of monster's field of view ( dot product ) + float m_flWaitFinished;// if we're told to wait, this is the time that the wait will be over. + float m_flMoveWaitFinished; + + Activity m_Activity;// what the monster is doing (animation) + Activity m_IdealActivity;// monster should switch to this activity + + int m_LastHitGroup; // the last body region that took damage + + MONSTERSTATE m_MonsterState;// monster's current state + MONSTERSTATE m_IdealMonsterState;// monster should change to this state + + int m_iTaskStatus; + Schedule_t *m_pSchedule; + int m_iScheduleIndex; + + WayPoint_t m_Route[ ROUTE_SIZE ]; // Positions of movement + int m_movementGoal; // Goal that defines route + int m_iRouteIndex; // index into m_Route[] + float m_moveWaitTime; // How long I should wait for something to move + + Vector m_vecMoveGoal; // kept around for node graph moves, so we know our ultimate goal + Activity m_movementActivity; // When moving, set this activity + + int m_iAudibleList; // first index of a linked list of sounds that the monster can hear. + int m_afSoundTypes; + + Vector m_vecLastPosition;// monster sometimes wants to return to where it started after an operation. + + int m_iHintNode; // this is the hint node that the monster is moving towards or performing active idle on. + + int m_afMemory; + + int m_iMaxHealth;// keeps track of monster's maximum health value (for re-healing, etc) + Vector m_vecEnemyLKP;// last known position of enemy. (enemy's origin) + int m_cAmmoLoaded; // how much ammo is in the weapon (used to trigger reload anim sequences) + + int m_afCapability;// tells us what a monster can/can't do. + + float m_flNextAttack; // cannot attack again until this time + + int m_bitsDamageType; // what types of damage has monster (player) taken + BYTE m_rgbTimeBasedDamage[CDMG_TIMEBASED]; + + int m_lastDamageAmount;// how much damage did monster (player) last take + // time based damage counters, decr. 1 per 2 seconds + int m_bloodColor; // color of blood particless + + int m_failSchedule; // Schedule type to choose if current schedule fails + + float m_flHungryTime;// set this is a future time to stop the monster from eating for a while. + + float m_flDistTooFar; // if enemy farther away than this, bits_COND_ENEMY_TOOFAR set in CheckEnemy + float m_flDistLook; // distance monster sees (Default 2048) + + int m_iTriggerCondition;// for scripted AI, this is the condition that will cause the activation of the monster's TriggerTarget + string_t m_iszTriggerTarget;// name of target that should be fired. + + Vector m_HackedGunPos; // HACK until we can query end of gun + +// Scripted sequence Info + SCRIPTSTATE m_scriptState; // internal cinematic state + CCineMonster *m_pCine; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; void KeyValue( KeyValueData *pkvd ); - void MakeIdealYaw( Vector vecTarget ); +// monster use function + void EXPORT MonsterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT CorpseUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +// overrideable Monster member functions + + virtual int BloodColor( void ) { return m_bloodColor; } + + virtual CBaseMonster *MyMonsterPointer( void ) { return this; } + virtual void Look ( int iDistance );// basic sight function for monsters + virtual void RunAI ( void );// core ai function! + void Listen ( void ); + + virtual BOOL IsAlive( void ) { return (pev->deadflag != DEAD_DEAD); } + virtual BOOL ShouldFadeOnDeath( void ); + +// Basic Monster AI functions virtual float ChangeYaw ( int speed ); - virtual BOOL HasHumanGibs( void ); - virtual BOOL HasAlienGibs( void ); - virtual void FadeMonster( void ); // Called instead of GibMonster() when gibs are disabled - virtual void GibMonster( void ); + float VecToYaw( Vector vecDir ); + float FlYawDiff ( void ); + + float DamageForce( float damage ); + +// stuff written for new state machine + virtual void MonsterThink( void ); + void EXPORT CallMonsterThink( void ) { this->MonsterThink(); } + virtual int IRelationship ( CBaseEntity *pTarget ); + virtual void MonsterInit ( void ); + virtual void MonsterInitDead( void ); // Call after animation/pose is set up + virtual void BecomeDead( void ); + void EXPORT CorpseFallThink( void ); + + void EXPORT MonsterInitThink ( void ); + virtual void StartMonster ( void ); + virtual CBaseEntity* BestVisibleEnemy ( void );// finds best visible enemy for attack + virtual BOOL FInViewCone ( CBaseEntity *pEntity );// see if pEntity is in monster's view cone + virtual BOOL FInViewCone ( Vector *pOrigin );// see if given location is in monster's view cone + virtual void HandleAnimEvent( MonsterEvent_t *pEvent ); + + virtual int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist );// check validity of a straight move through space + virtual void Move( float flInterval = 0.1 ); + virtual void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + virtual BOOL ShouldAdvanceRoute( float flWaypointDist ); + + virtual Activity GetStoppedActivity( void ) { return ACT_IDLE; } + virtual void Stop( void ) { m_IdealActivity = GetStoppedActivity(); } + + // This will stop animation until you call ResetSequenceInfo() at some point in the future + inline void StopAnimation( void ) { pev->framerate = 0; } + + // these functions will survey conditions and set appropriate conditions bits for attack types. + virtual BOOL CheckRangeAttack1( float flDot, float flDist ); + virtual BOOL CheckRangeAttack2( float flDot, float flDist ); + virtual BOOL CheckMeleeAttack1( float flDot, float flDist ); + virtual BOOL CheckMeleeAttack2( float flDot, float flDist ); + + BOOL FHaveSchedule( void ); + BOOL FScheduleValid ( void ); + void ClearSchedule( void ); + BOOL FScheduleDone ( void ); + void ChangeSchedule ( Schedule_t *pNewSchedule ); + void NextScheduledTask ( void ); + Schedule_t *ScheduleInList( const char *pName, Schedule_t **pList, int listCount ); + + virtual Schedule_t *ScheduleFromName( const char *pName ); + static Schedule_t *m_scheduleList[]; + + void MaintainSchedule ( void ); + virtual void StartTask ( Task_t *pTask ); + virtual void RunTask ( Task_t *pTask ); + virtual Schedule_t *GetScheduleOfType( int Type ); + virtual Schedule_t *GetSchedule( void ); + virtual void ScheduleChange( void ) {} + // virtual int CanPlaySequence( void ) { return ((m_pCine == NULL) && (m_MonsterState == MONSTERSTATE_NONE || m_MonsterState == MONSTERSTATE_IDLE || m_IdealMonsterState == MONSTERSTATE_IDLE)); } + virtual int CanPlaySequence( BOOL fDisregardState, int interruptLevel ); + virtual int CanPlaySentence( BOOL fDisregardState ) { return IsAlive(); } + virtual void PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ); + virtual void PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ); + + virtual void SentenceStop( void ); + + Task_t *GetTask ( void ); + virtual MONSTERSTATE GetIdealState ( void ); + virtual void SetActivity ( Activity NewActivity ); + void SetSequenceByName ( char *szSequence ); + void SetState ( MONSTERSTATE State ); + virtual void ReportAIState( void ); + + void CheckAttacks ( CBaseEntity *pTarget, float flDist ); + virtual int CheckEnemy ( CBaseEntity *pEnemy ); + void PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ); + BOOL PopEnemy( void ); + + BOOL FGetNodeRoute ( Vector vecDest ); + + inline void TaskComplete( void ) { if ( !HasConditions(bits_COND_TASK_FAILED) ) m_iTaskStatus = TASKSTATUS_COMPLETE; } + void MovementComplete( void ); + inline void TaskFail( void ) { SetConditions(bits_COND_TASK_FAILED); } + inline void TaskBegin( void ) { m_iTaskStatus = TASKSTATUS_RUNNING; } + int TaskIsRunning( void ); + inline int TaskIsComplete( void ) { return (m_iTaskStatus == TASKSTATUS_COMPLETE); } + inline int MovementIsComplete( void ) { return (m_movementGoal == MOVEGOAL_NONE); } + + int IScheduleFlags ( void ); + BOOL FRefreshRoute( void ); + BOOL FRouteClear ( void ); + void RouteSimplify( CBaseEntity *pTargetEnt ); + void AdvanceRoute ( float distance ); + virtual BOOL FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ); + void MakeIdealYaw( Vector vecTarget ); + virtual void SetYawSpeed ( void ) { return; };// allows different yaw_speeds for each activity + BOOL BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ); + virtual BOOL BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ); + int RouteClassify( int iMoveFlag ); + void InsertWaypoint ( Vector vecLocation, int afMoveFlags ); + + BOOL FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ); + virtual BOOL FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ); + virtual BOOL FValidateCover ( const Vector &vecCoverLocation ) { return TRUE; }; + virtual float CoverRadius( void ) { return 784; } // Default cover radius + + virtual BOOL FCanCheckAttacks ( void ); + virtual void CheckAmmo( void ) { return; }; + virtual int IgnoreConditions ( void ); + + inline void SetConditions( int iConditions ) { m_afConditions |= iConditions; } + inline void ClearConditions( int iConditions ) { m_afConditions &= ~iConditions; } + inline BOOL HasConditions( int iConditions ) { if ( m_afConditions & iConditions ) return TRUE; return FALSE; } + inline BOOL HasAllConditions( int iConditions ) { if ( (m_afConditions & iConditions) == iConditions ) return TRUE; return FALSE; } + + virtual BOOL FValidateHintType( short sHint ); + int FindHintNode ( void ); + virtual BOOL FCanActiveIdle ( void ); + void SetTurnActivity ( void ); + float FLSoundVolume ( CSound *pSound ); + + BOOL MoveToNode( Activity movementAct, float waitTime, const Vector &goal ); + BOOL MoveToTarget( Activity movementAct, float waitTime ); + BOOL MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ); + BOOL MoveToEnemy( Activity movementAct, float waitTime ); + + // Returns the time when the door will be open + float OpenDoorAndWait( entvars_t *pevDoor ); + + virtual int ISoundMask( void ); + virtual CSound* PBestSound ( void ); + virtual CSound* PBestScent ( void ); + virtual float HearingSensitivity( void ) { return 1.0; }; + + BOOL FBecomeProne ( void ); + virtual void BarnacleVictimBitten( entvars_t *pevBarnacle ); + virtual void BarnacleVictimReleased( void ); + + void SetEyePosition ( void ); + + BOOL FShouldEat( void );// see if a monster is 'hungry' + void Eat ( float flFullDuration );// make the monster 'full' for a while. + + CBaseEntity *CheckTraceHullAttack( float flDist, int iDamage, int iDmgType ); + BOOL FacingIdeal( void ); + + BOOL FCheckAITrigger( void );// checks and, if necessary, fires the monster's trigger target. + BOOL NoFriendlyFire( void ); + + BOOL BBoxFlat( void ); + + // PrescheduleThink + virtual void PrescheduleThink( void ) { return; }; + + BOOL GetEnemy ( void ); + void MakeDamageBloodDecal ( int cCount, float flNoise, TraceResult *ptr, const Vector &vecDir ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + // combat functions + float UpdateTarget ( entvars_t *pevTarget ); virtual Activity GetDeathActivity ( void ); Activity GetSmallFlinchActivity( void ); - virtual void BecomeDead( void ); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + virtual void GibMonster( 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 BOOL HasHumanGibs( void ); + virtual BOOL HasAlienGibs( void ); + virtual void FadeMonster( void ); // Called instead of GibMonster() when gibs are disabled + + Vector ShootAtEnemy( const Vector &shootOrigin ); + virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ) * 0.75 + EyePosition() * 0.25; }; // position to shoot at + + virtual Vector GetGunPosition( void ); + virtual int TakeHealth( float flHealth, int bitsDamageType ); virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); int DeadTakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); - 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 ); + virtual int IsMoving( void ) { return m_movementGoal != MOVEGOAL_NONE; } - 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; } + void RouteClear( void ); + void RouteNew( void ); + + virtual void DeathSound ( void ) { return; }; + virtual void AlertSound ( void ) { return; }; + virtual void IdleSound ( void ) { return; }; + virtual void PainSound ( void ) { return; }; + + virtual void StopFollowing( BOOL clearSchedule ) {} inline void Remember( int iMemory ) { m_afMemory |= iMemory; } inline void Forget( int iMemory ) { m_afMemory &= ~iMemory; } inline BOOL HasMemory( int iMemory ) { if ( m_afMemory & iMemory ) return TRUE; return FALSE; } inline BOOL HasAllMemories( int iMemory ) { if ( (m_afMemory & iMemory) == iMemory ) return TRUE; return FALSE; } - // 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); } + BOOL ExitScriptedSequence( ); + BOOL CineCleanup( ); + CBaseEntity* DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng );// drop an item. }; -#endif + +#endif // BASEMONSTER_H diff --git a/dlls/bigmomma.cpp b/dlls/bigmomma.cpp new file mode 100644 index 0000000..ca7a1bf --- /dev/null +++ b/dlls/bigmomma.cpp @@ -0,0 +1,1251 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +//========================================================= +// monster template +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "decals.h" +#include "weapons.h" +#include "game.h" + +#define SF_INFOBM_RUN 0x0001 +#define SF_INFOBM_WAIT 0x0002 + +// AI Nodes for Big Momma +class CInfoBM : public CPointEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData* pkvd ); + + // name in pev->targetname + // next in pev->target + // radius in pev->scale + // health in pev->health + // Reach target in pev->message + // Reach delay in pev->speed + // Reach sequence in pev->netname + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_preSequence; +}; + +LINK_ENTITY_TO_CLASS( info_bigmomma, CInfoBM ); + +TYPEDESCRIPTION CInfoBM::m_SaveData[] = +{ + DEFINE_FIELD( CInfoBM, m_preSequence, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CInfoBM, CPointEntity ); + +void CInfoBM::Spawn( void ) +{ +} + + +void CInfoBM::KeyValue( KeyValueData* pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "radius")) + { + pev->scale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "reachdelay")) + { + pev->speed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "reachtarget")) + { + pev->message = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "reachsequence")) + { + pev->netname = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "presequence")) + { + m_preSequence = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +//========================================================= +// Mortar shot entity +//========================================================= +class CBMortar : public CBaseEntity +{ +public: + void Spawn( void ); + + static CBMortar *Shoot( edict_t *pOwner, Vector vecStart, Vector vecVelocity ); + void Touch( CBaseEntity *pOther ); + void EXPORT Animate( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( bmortar, CBMortar ); + +TYPEDESCRIPTION CBMortar::m_SaveData[] = +{ + DEFINE_FIELD( CBMortar, m_maxFrame, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CBMortar, CBaseEntity ); + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BIG_AE_STEP1 1 // Footstep left +#define BIG_AE_STEP2 2 // Footstep right +#define BIG_AE_STEP3 3 // Footstep back left +#define BIG_AE_STEP4 4 // Footstep back right +#define BIG_AE_SACK 5 // Sack slosh +#define BIG_AE_DEATHSOUND 6 // Death sound + +#define BIG_AE_MELEE_ATTACKBR 8 // Leg attack +#define BIG_AE_MELEE_ATTACKBL 9 // Leg attack +#define BIG_AE_MELEE_ATTACK1 10 // Leg attack +#define BIG_AE_MORTAR_ATTACK1 11 // Launch a mortar +#define BIG_AE_LAY_CRAB 12 // Lay a headcrab +#define BIG_AE_JUMP_FORWARD 13 // Jump up and forward +#define BIG_AE_SCREAM 14 // alert sound +#define BIG_AE_PAIN_SOUND 15 // pain sound +#define BIG_AE_ATTACK_SOUND 16 // attack sound +#define BIG_AE_BIRTH_SOUND 17 // birth sound +#define BIG_AE_EARLY_TARGET 50 // Fire target early + + + +// User defined conditions +#define bits_COND_NODE_SEQUENCE ( bits_COND_SPECIAL1 ) // pev->netname contains the name of a sequence to play + +// Attack distance constants +#define BIG_ATTACKDIST 170 +#define BIG_MORTARDIST 800 +#define BIG_MAXCHILDREN 20 // Max # of live headcrab children + + +#define bits_MEMORY_CHILDPAIR (bits_MEMORY_CUSTOM1) +#define bits_MEMORY_ADVANCE_NODE (bits_MEMORY_CUSTOM2) +#define bits_MEMORY_COMPLETED_NODE (bits_MEMORY_CUSTOM3) +#define bits_MEMORY_FIRED_NODE (bits_MEMORY_CUSTOM4) + +int gSpitSprite, gSpitDebrisSprite; +Vector VecCheckSplatToss( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float maxHeight ); +void MortarSpray( const Vector &position, const Vector &direction, int spriteModel, int count ); + + +// UNDONE: +// +#define BIG_CHILDCLASS "monster_babycrab" + +class CBigMomma : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void Activate( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + void RunTask( Task_t *pTask ); + void StartTask( Task_t *pTask ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType( int Type ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + + void NodeStart( int iszNextNode ); + void NodeReach( void ); + BOOL ShouldGoToNode( void ); + + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void LayHeadcrab( void ); + + int GetNodeSequence( void ) + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget ) + { + return pTarget->pev->netname; // netname holds node sequence + } + return 0; + } + + + int GetNodePresequence( void ) + { + CInfoBM *pTarget = (CInfoBM *)(CBaseEntity *)m_hTargetEnt; + if ( pTarget ) + { + return pTarget->m_preSequence; + } + return 0; + } + + float GetNodeDelay( void ) + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget ) + { + return pTarget->pev->speed; // Speed holds node delay + } + return 0; + } + + float GetNodeRange( void ) + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget ) + { + return pTarget->pev->scale; // Scale holds node delay + } + return 1e6; + } + + float GetNodeYaw( void ) + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget ) + { + if ( pTarget->pev->angles.y != 0 ) + return pTarget->pev->angles.y; + } + return pev->angles.y; + } + + // Restart the crab count on each new level + void OverrideReset( void ) + { + m_crabCount = 0; + } + + void DeathNotice( entvars_t *pevChild ); + + BOOL CanLayCrab( void ) + { + if ( m_crabTime < gpGlobals->time && m_crabCount < BIG_MAXCHILDREN ) + { + // Don't spawn crabs inside each other + Vector mins = pev->origin - Vector( 32, 32, 0 ); + Vector maxs = pev->origin + Vector( 32, 32, 0 ); + + CBaseEntity *pList[2]; + int count = UTIL_EntitiesInBox( pList, 2, mins, maxs, FL_MONSTER ); + for ( int i = 0; i < count; i++ ) + { + if ( pList[i] != this ) // Don't hurt yourself! + return FALSE; + } + return TRUE; + } + + return FALSE; + } + + void LaunchMortar( void ); + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -95, -95, 0 ); + pev->absmax = pev->origin + Vector( 95, 95, 190 ); + } + + BOOL CheckMeleeAttack1( float flDot, float flDist ); // Slash + BOOL CheckMeleeAttack2( float flDot, float flDist ); // Lay a crab + BOOL CheckRangeAttack1( float flDot, float flDist ); // Mortar launch + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pChildDieSounds[]; + static const char *pSackSounds[]; + static const char *pDeathSounds[]; + static const char *pAttackSounds[]; + static const char *pAttackHitSounds[]; + static const char *pBirthSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pFootSounds[]; + + CUSTOM_SCHEDULES; + +private: + float m_nodeTime; + float m_crabTime; + float m_mortarTime; + float m_painSoundTime; + int m_crabCount; +}; +LINK_ENTITY_TO_CLASS( monster_bigmomma, CBigMomma ); + +TYPEDESCRIPTION CBigMomma::m_SaveData[] = +{ + DEFINE_FIELD( CBigMomma, m_nodeTime, FIELD_TIME ), + DEFINE_FIELD( CBigMomma, m_crabTime, FIELD_TIME ), + DEFINE_FIELD( CBigMomma, m_mortarTime, FIELD_TIME ), + DEFINE_FIELD( CBigMomma, m_painSoundTime, FIELD_TIME ), + DEFINE_FIELD( CBigMomma, m_crabCount, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CBigMomma, CBaseMonster ); + +const char *CBigMomma::pChildDieSounds[] = +{ + "gonarch/gon_childdie1.wav", + "gonarch/gon_childdie2.wav", + "gonarch/gon_childdie3.wav", +}; + +const char *CBigMomma::pSackSounds[] = +{ + "gonarch/gon_sack1.wav", + "gonarch/gon_sack2.wav", + "gonarch/gon_sack3.wav", +}; + +const char *CBigMomma::pDeathSounds[] = +{ + "gonarch/gon_die1.wav", +}; + +const char *CBigMomma::pAttackSounds[] = +{ + "gonarch/gon_attack1.wav", + "gonarch/gon_attack2.wav", + "gonarch/gon_attack3.wav", +}; +const char *CBigMomma::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CBigMomma::pBirthSounds[] = +{ + "gonarch/gon_birth1.wav", + "gonarch/gon_birth2.wav", + "gonarch/gon_birth3.wav", +}; + +const char *CBigMomma::pAlertSounds[] = +{ + "gonarch/gon_alert1.wav", + "gonarch/gon_alert2.wav", + "gonarch/gon_alert3.wav", +}; + +const char *CBigMomma::pPainSounds[] = +{ + "gonarch/gon_pain2.wav", + "gonarch/gon_pain4.wav", + "gonarch/gon_pain5.wav", +}; + +const char *CBigMomma::pFootSounds[] = +{ + "gonarch/gon_step1.wav", + "gonarch/gon_step2.wav", + "gonarch/gon_step3.wav", +}; + + + +void CBigMomma :: KeyValue( KeyValueData *pkvd ) +{ +#if 0 + if (FStrEq(pkvd->szKeyName, "volume")) + { + m_volume = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else +#endif + CBaseMonster::KeyValue( pkvd ); +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBigMomma :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CBigMomma :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 100; + break; + default: + ys = 90; + } + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CBigMomma :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BIG_AE_MELEE_ATTACKBR: + case BIG_AE_MELEE_ATTACKBL: + case BIG_AE_MELEE_ATTACK1: + { + Vector forward, right; + + UTIL_MakeVectorsPrivate( pev->angles, forward, right, NULL ); + + Vector center = pev->origin + forward * 128; + Vector mins = center - Vector( 64, 64, 0 ); + Vector maxs = center + Vector( 64, 64, 64 ); + + CBaseEntity *pList[8]; + int count = UTIL_EntitiesInBox( pList, 8, mins, maxs, FL_MONSTER|FL_CLIENT ); + CBaseEntity *pHurt = NULL; + + for ( int i = 0; i < count && !pHurt; i++ ) + { + if ( pList[i] != this ) + { + if ( pList[i]->pev->owner != edict() ) + pHurt = pList[i]; + } + } + + if ( pHurt ) + { + pHurt->TakeDamage( pev, pev, gSkillData.bigmommaDmgSlash, DMG_CRUSH | DMG_SLASH ); + pHurt->pev->punchangle.x = 15; + switch( pEvent->event ) + { + case BIG_AE_MELEE_ATTACKBR: + pHurt->pev->velocity = pHurt->pev->velocity + (forward * 150) + Vector(0,0,250) - (right * 200); + break; + + case BIG_AE_MELEE_ATTACKBL: + pHurt->pev->velocity = pHurt->pev->velocity + (forward * 150) + Vector(0,0,250) + (right * 200); + break; + + case BIG_AE_MELEE_ATTACK1: + pHurt->pev->velocity = pHurt->pev->velocity + (forward * 220) + Vector(0,0,200); + break; + } + + pHurt->pev->flags &= ~FL_ONGROUND; + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pAttackHitSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + } + break; + + case BIG_AE_SCREAM: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAlertSounds ); + break; + + case BIG_AE_PAIN_SOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); + break; + + case BIG_AE_ATTACK_SOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackSounds ); + break; + + case BIG_AE_BIRTH_SOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pBirthSounds ); + break; + + case BIG_AE_SACK: + if ( RANDOM_LONG(0,100) < 30 ) + EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pSackSounds ); + break; + + case BIG_AE_DEATHSOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pDeathSounds ); + break; + + case BIG_AE_STEP1: // Footstep left + case BIG_AE_STEP3: // Footstep back left + EMIT_SOUND_ARRAY_DYN( CHAN_ITEM, pFootSounds ); + break; + + case BIG_AE_STEP4: // Footstep back right + case BIG_AE_STEP2: // Footstep right + EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pFootSounds ); + break; + + case BIG_AE_MORTAR_ATTACK1: + LaunchMortar(); + break; + + case BIG_AE_LAY_CRAB: + LayHeadcrab(); + break; + + case BIG_AE_JUMP_FORWARD: + ClearBits( pev->flags, FL_ONGROUND ); + + UTIL_SetOrigin (pev, pev->origin + Vector ( 0 , 0 , 1) );// take him off ground so engine doesn't instantly reset onground + UTIL_MakeVectors ( pev->angles ); + + pev->velocity = (gpGlobals->v_forward * 200) + gpGlobals->v_up * 500; + break; + + case BIG_AE_EARLY_TARGET: + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( pTarget && pTarget->pev->message ) + FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 ); + Remember( bits_MEMORY_FIRED_NODE ); + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +void CBigMomma :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + if ( ptr->iHitgroup != 1 ) + { + // didn't hit the sack? + + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,10) < 1) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1, 2) ); + pev->dmgtime = gpGlobals->time; + } + + flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated + } + else if ( gpGlobals->time > m_painSoundTime ) + { + m_painSoundTime = gpGlobals->time + RANDOM_LONG(1, 3); + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); + } + + + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +int CBigMomma :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Don't take any acid damage -- BigMomma's mortar is acid + if ( bitsDamageType & DMG_ACID ) + flDamage = 0; + + if ( !HasMemory(bits_MEMORY_PATH_FINISHED) ) + { + if ( pev->health <= flDamage ) + { + pev->health = flDamage + 1; + Remember( bits_MEMORY_ADVANCE_NODE | bits_MEMORY_COMPLETED_NODE ); + ALERT( at_aiconsole, "BM: Finished node health!!!\n" ); + } + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CBigMomma :: LayHeadcrab( void ) +{ + CBaseEntity *pChild = CBaseEntity::Create( BIG_CHILDCLASS, pev->origin, pev->angles, edict() ); + + pChild->pev->spawnflags |= SF_MONSTER_FALL_TO_GROUND; + + // Is this the second crab in a pair? + if ( HasMemory( bits_MEMORY_CHILDPAIR ) ) + { + m_crabTime = gpGlobals->time + RANDOM_FLOAT( 5, 10 ); + Forget( bits_MEMORY_CHILDPAIR ); + } + else + { + m_crabTime = gpGlobals->time + RANDOM_FLOAT( 0.5, 2.5 ); + Remember( bits_MEMORY_CHILDPAIR ); + } + + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,100), ignore_monsters, edict(), &tr); + UTIL_DecalTrace( &tr, DECAL_MOMMABIRTH ); + + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pBirthSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + m_crabCount++; +} + + + +void CBigMomma::DeathNotice( entvars_t *pevChild ) +{ + if ( m_crabCount > 0 ) // Some babies may cross a transition, but we reset the count then + m_crabCount--; + if ( IsAlive() ) + { + // Make the "my baby's dead" noise! + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pChildDieSounds ); + } +} + + +void CBigMomma::LaunchMortar( void ) +{ + m_mortarTime = gpGlobals->time + RANDOM_FLOAT( 2, 15 ); + + Vector startPos = pev->origin; + startPos.z += 180; + + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pSackSounds), 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + CBMortar *pBomb = CBMortar::Shoot( edict(), startPos, pev->movedir ); + pBomb->pev->gravity = 1.0; + MortarSpray( startPos, Vector(0,0,1), gSpitSprite, 24 ); +} + +//========================================================= +// Spawn +//========================================================= +void CBigMomma :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/big_mom.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = 150 * gSkillData.bigmommaHealthFactor; + pev->view_ofs = Vector ( 0, 0, 128 );// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.3;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBigMomma :: Precache() +{ + PRECACHE_MODEL("models/big_mom.mdl"); + + PRECACHE_SOUND_ARRAY( pChildDieSounds ); + PRECACHE_SOUND_ARRAY( pSackSounds ); + PRECACHE_SOUND_ARRAY( pDeathSounds ); + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pAttackHitSounds ); + PRECACHE_SOUND_ARRAY( pBirthSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); + PRECACHE_SOUND_ARRAY( pFootSounds ); + + UTIL_PrecacheOther( BIG_CHILDCLASS ); + + // TEMP: Squid + PRECACHE_MODEL("sprites/mommaspit.spr");// spit projectile. + gSpitSprite = PRECACHE_MODEL("sprites/mommaspout.spr");// client side spittle. + gSpitDebrisSprite = PRECACHE_MODEL("sprites/mommablob.spr" ); + + PRECACHE_SOUND( "bullchicken/bc_acid1.wav" ); + PRECACHE_SOUND( "bullchicken/bc_spithit1.wav" ); + PRECACHE_SOUND( "bullchicken/bc_spithit2.wav" ); +} + + +void CBigMomma::Activate( void ) +{ + if ( m_hTargetEnt == NULL ) + Remember( bits_MEMORY_ADVANCE_NODE ); // Start 'er up +} + + +void CBigMomma::NodeStart( int iszNextNode ) +{ + pev->netname = iszNextNode; + + CBaseEntity *pTarget = NULL; + + if ( pev->netname ) + { + edict_t *pentTarget = FIND_ENTITY_BY_TARGETNAME ( NULL, STRING(pev->netname) ); + + if ( !FNullEnt(pentTarget) ) + pTarget = Instance( pentTarget ); + } + + + if ( !pTarget ) + { + ALERT( at_aiconsole, "BM: Finished the path!!\n" ); + Remember( bits_MEMORY_PATH_FINISHED ); + return; + } + Remember( bits_MEMORY_ON_PATH ); + m_hTargetEnt = pTarget; +} + + +void CBigMomma::NodeReach( void ) +{ + CBaseEntity *pTarget = m_hTargetEnt; + + Forget( bits_MEMORY_ADVANCE_NODE ); + + if ( !pTarget ) + return; + + if ( pTarget->pev->health ) + pev->max_health = pev->health = pTarget->pev->health * gSkillData.bigmommaHealthFactor; + + if ( !HasMemory( bits_MEMORY_FIRED_NODE ) ) + { + if ( pTarget->pev->message ) + FireTargets( STRING(pTarget->pev->message), this, this, USE_TOGGLE, 0 ); + } + Forget( bits_MEMORY_FIRED_NODE ); + + pev->netname = pTarget->pev->target; + if ( pTarget->pev->health == 0 ) + Remember( bits_MEMORY_ADVANCE_NODE ); // Move on if no health at this node +} + + + // Slash +BOOL CBigMomma::CheckMeleeAttack1( float flDot, float flDist ) +{ + if (flDot >= 0.7) + { + if ( flDist <= BIG_ATTACKDIST ) + return TRUE; + } + return FALSE; +} + + +// Lay a crab +BOOL CBigMomma::CheckMeleeAttack2( float flDot, float flDist ) +{ + return CanLayCrab(); +} + + +// Mortar launch +BOOL CBigMomma::CheckRangeAttack1( float flDot, float flDist ) +{ + if ( flDist <= BIG_MORTARDIST && m_mortarTime < gpGlobals->time ) + { + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy ) + { + Vector startPos = pev->origin; + startPos.z += 180; + pev->movedir = VecCheckSplatToss( pev, startPos, pEnemy->BodyTarget( pev->origin ), RANDOM_FLOAT( 150, 500 ) ); + if ( pev->movedir != g_vecZero ) + return TRUE; + } + } + return FALSE; +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +enum +{ + SCHED_BIG_NODE = LAST_COMMON_SCHEDULE + 1, + SCHED_NODE_FAIL, +}; + +enum +{ + TASK_MOVE_TO_NODE_RANGE = LAST_COMMON_TASK + 1, // Move within node range + TASK_FIND_NODE, // Find my next node + TASK_PLAY_NODE_PRESEQUENCE, // Play node pre-script + TASK_PLAY_NODE_SEQUENCE, // Play node script + TASK_PROCESS_NODE, // Fire targets, etc. + TASK_WAIT_NODE, // Wait at the node + TASK_NODE_DELAY, // Delay walking toward node for a bit. You've failed to get there + TASK_NODE_YAW, // Get the best facing direction for this node +}; + + +Task_t tlBigNode[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_NODE_FAIL }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_NODE, (float)0 }, // Find my next node + { TASK_PLAY_NODE_PRESEQUENCE,(float)0 }, // Play the pre-approach sequence if any + { TASK_MOVE_TO_NODE_RANGE, (float)0 }, // Move within node range + { TASK_STOP_MOVING, (float)0 }, + { TASK_NODE_YAW, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WAIT_NODE, (float)0 }, // Wait for node delay + { TASK_PLAY_NODE_SEQUENCE, (float)0 }, // Play the sequence if one exists + { TASK_PROCESS_NODE, (float)0 }, // Fire targets, etc. + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slBigNode[] = +{ + { + tlBigNode, + ARRAYSIZE ( tlBigNode ), + 0, + 0, + "Big Node" + }, +}; + + +Task_t tlNodeFail[] = +{ + { TASK_NODE_DELAY, (float)10 }, // Try to do something else for 10 seconds + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slNodeFail[] = +{ + { + tlNodeFail, + ARRAYSIZE ( tlNodeFail ), + 0, + 0, + "NodeFail" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CBigMomma ) +{ + slBigNode, + slNodeFail, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CBigMomma, CBaseMonster ); + + + + +Schedule_t *CBigMomma::GetScheduleOfType( int Type ) +{ + switch( Type ) + { + case SCHED_BIG_NODE: + return slBigNode; + break; + + case SCHED_NODE_FAIL: + return slNodeFail; + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + + +BOOL CBigMomma::ShouldGoToNode( void ) +{ + if ( HasMemory( bits_MEMORY_ADVANCE_NODE ) ) + { + if ( m_nodeTime < gpGlobals->time ) + return TRUE; + } + return FALSE; +} + + + +Schedule_t *CBigMomma::GetSchedule( void ) +{ + if ( ShouldGoToNode() ) + { + return GetScheduleOfType( SCHED_BIG_NODE ); + } + + return CBaseMonster::GetSchedule(); +} + + +void CBigMomma::StartTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_FIND_NODE: + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( !HasMemory( bits_MEMORY_ADVANCE_NODE ) ) + { + if ( pTarget ) + pev->netname = m_hTargetEnt->pev->target; + } + NodeStart( pev->netname ); + TaskComplete(); + ALERT( at_aiconsole, "BM: Found node %s\n", STRING(pev->netname) ); + } + break; + + case TASK_NODE_DELAY: + m_nodeTime = gpGlobals->time + pTask->flData; + TaskComplete(); + ALERT( at_aiconsole, "BM: FAIL! Delay %.2f\n", pTask->flData ); + break; + + case TASK_PROCESS_NODE: + ALERT( at_aiconsole, "BM: Reached node %s\n", STRING(pev->netname) ); + NodeReach(); + TaskComplete(); + break; + + case TASK_PLAY_NODE_PRESEQUENCE: + case TASK_PLAY_NODE_SEQUENCE: + { + int sequence; + if ( pTask->iTask == TASK_PLAY_NODE_SEQUENCE ) + sequence = GetNodeSequence(); + else + sequence = GetNodePresequence(); + + ALERT( at_aiconsole, "BM: Playing node sequence %s\n", STRING(sequence) ); + if ( sequence ) + { + sequence = LookupSequence( STRING( sequence ) ); + if ( sequence != -1 ) + { + pev->sequence = sequence; + pev->frame = 0; + ResetSequenceInfo( ); + ALERT( at_aiconsole, "BM: Sequence %s\n", STRING(GetNodeSequence()) ); + return; + } + } + TaskComplete(); + } + break; + + case TASK_NODE_YAW: + pev->ideal_yaw = GetNodeYaw(); + TaskComplete(); + break; + + case TASK_WAIT_NODE: + m_flWait = gpGlobals->time + GetNodeDelay(); + if ( m_hTargetEnt->pev->spawnflags & SF_INFOBM_WAIT ) + ALERT( at_aiconsole, "BM: Wait at node %s forever\n", STRING(pev->netname) ); + else + ALERT( at_aiconsole, "BM: Wait at node %s for %.2f\n", STRING(pev->netname), GetNodeDelay() ); + break; + + + case TASK_MOVE_TO_NODE_RANGE: + { + CBaseEntity *pTarget = m_hTargetEnt; + if ( !pTarget ) + TaskFail(); + else + { + if ( (pTarget->pev->origin - pev->origin).Length() < GetNodeRange() ) + TaskComplete(); + else + { + Activity act = ACT_WALK; + if ( pTarget->pev->spawnflags & SF_INFOBM_RUN ) + act = ACT_RUN; + + m_vecMoveGoal = pTarget->pev->origin; + if ( !MoveToTarget( act, 2 ) ) + { + TaskFail(); + } + } + } + } + ALERT( at_aiconsole, "BM: Moving to node %s\n", STRING(pev->netname) ); + + break; + + case TASK_MELEE_ATTACK1: + // Play an attack sound here + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAttackSounds), 1.0, ATTN_NORM, 0, PITCH_NORM ); + CBaseMonster::StartTask( pTask ); + break; + + default: + CBaseMonster::StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CBigMomma::RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_MOVE_TO_NODE_RANGE: + { + float distance; + + if ( m_hTargetEnt == NULL ) + TaskFail(); + else + { + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( (distance < GetNodeRange()) || MovementIsComplete() ) + { + ALERT( at_aiconsole, "BM: Reached node!\n" ); + TaskComplete(); + RouteClear(); // Stop moving + } + } + } + + break; + + case TASK_WAIT_NODE: + if ( m_hTargetEnt != NULL && (m_hTargetEnt->pev->spawnflags & SF_INFOBM_WAIT) ) + return; + + if ( gpGlobals->time > m_flWaitFinished ) + TaskComplete(); + ALERT( at_aiconsole, "BM: The WAIT is over!\n" ); + break; + + case TASK_PLAY_NODE_PRESEQUENCE: + case TASK_PLAY_NODE_SEQUENCE: + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + + default: + CBaseMonster::RunTask( pTask ); + break; + } +} + + + +Vector VecCheckSplatToss( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float maxHeight ) +{ + TraceResult tr; + Vector vecMidPoint;// halfway point between Spot1 and Spot2 + Vector vecApex;// highest point + Vector vecScale; + Vector vecGrenadeVel; + Vector vecTemp; + float flGravity = g_psv_gravity->value; + + // calculate the midpoint and apex of the 'triangle' + vecMidPoint = vecSpot1 + (vecSpot2 - vecSpot1) * 0.5; + UTIL_TraceLine(vecMidPoint, vecMidPoint + Vector(0,0,maxHeight), ignore_monsters, ENT(pev), &tr); + vecApex = tr.vecEndPos; + + UTIL_TraceLine(vecSpot1, vecApex, dont_ignore_monsters, ENT(pev), &tr); + if (tr.flFraction != 1.0) + { + // fail! + return g_vecZero; + } + + // Don't worry about actually hitting the target, this won't hurt us! + + // How high should the grenade travel (subtract 15 so the grenade doesn't hit the ceiling)? + float height = (vecApex.z - vecSpot1.z) - 15; + // How fast does the grenade need to travel to reach that height given gravity? + float speed = sqrt( 2 * flGravity * height ); + + // How much time does it take to get there? + float time = speed / flGravity; + vecGrenadeVel = (vecSpot2 - vecSpot1); + vecGrenadeVel.z = 0; + float distance = vecGrenadeVel.Length(); + + // Travel half the distance to the target in that time (apex is at the midpoint) + vecGrenadeVel = vecGrenadeVel * ( 0.5 / time ); + // Speed to offset gravity at the desired height + vecGrenadeVel.z = speed; + + return vecGrenadeVel; +} + + + + +// --------------------------------- +// +// Mortar +// +// --------------------------------- +void MortarSpray( const Vector &position, const Vector &direction, int spriteModel, int count ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, position ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( position.x); // pos + WRITE_COORD( position.y); + WRITE_COORD( position.z); + WRITE_COORD( direction.x); // dir + WRITE_COORD( direction.y); + WRITE_COORD( direction.z); + WRITE_SHORT( spriteModel ); // model + WRITE_BYTE ( count ); // count + WRITE_BYTE ( 130 ); // speed + WRITE_BYTE ( 80 ); // noise ( client will divide by 100 ) + MESSAGE_END(); +} + + +// UNDONE: right now this is pretty much a copy of the squid spit with minor changes to the way it does damage +void CBMortar:: Spawn( void ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->classname = MAKE_STRING( "bmortar" ); + + pev->solid = SOLID_BBOX; + pev->rendermode = kRenderTransAlpha; + pev->renderamt = 255; + + SET_MODEL(ENT(pev), "sprites/mommaspit.spr"); + pev->frame = 0; + pev->scale = 0.5; + + UTIL_SetSize( pev, Vector( 0, 0, 0), Vector(0, 0, 0) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + pev->dmgtime = gpGlobals->time + 0.4; +} + +void CBMortar::Animate( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if ( gpGlobals->time > pev->dmgtime ) + { + pev->dmgtime = gpGlobals->time + 0.2; + MortarSpray( pev->origin, -pev->velocity.Normalize(), gSpitSprite, 3 ); + } + if ( pev->frame++ ) + { + if ( pev->frame > m_maxFrame ) + { + pev->frame = 0; + } + } +} + +CBMortar *CBMortar::Shoot( edict_t *pOwner, Vector vecStart, Vector vecVelocity ) +{ + CBMortar *pSpit = GetClassPtr( (CBMortar *)NULL ); + pSpit->Spawn(); + + UTIL_SetOrigin( pSpit->pev, vecStart ); + pSpit->pev->velocity = vecVelocity; + pSpit->pev->owner = pOwner; + pSpit->pev->scale = 2.5; + pSpit->SetThink ( Animate ); + pSpit->pev->nextthink = gpGlobals->time + 0.1; + + return pSpit; +} + + +void CBMortar::Touch( CBaseEntity *pOther ) +{ + TraceResult tr; + int iPitch; + + // splat sound + iPitch = RANDOM_FLOAT( 90, 110 ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_acid1.wav", 1, ATTN_NORM, 0, iPitch ); + + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } + + if ( pOther->IsBSPModel() ) + { + + // make a splat on the wall + UTIL_TraceLine( pev->origin, pev->origin + pev->velocity * 10, dont_ignore_monsters, ENT( pev ), &tr ); + UTIL_DecalTrace(&tr, DECAL_MOMMASPLAT); + } + else + { + tr.vecEndPos = pev->origin; + tr.vecPlaneNormal = -1 * pev->velocity.Normalize(); + } + // make some flecks + MortarSpray( tr.vecEndPos, tr.vecPlaneNormal, gSpitSprite, 24 ); + + entvars_t *pevOwner = NULL; + if ( pev->owner ) + pevOwner = VARS(pev->owner); + + RadiusDamage( pev->origin, pev, pevOwner, gSkillData.bigmommaDmgBlast, gSkillData.bigmommaRadiusBlast, CLASS_NONE, DMG_ACID ); + UTIL_Remove( this ); +} + +#endif diff --git a/dlls/bloater.cpp b/dlls/bloater.cpp new file mode 100644 index 0000000..889b866 --- /dev/null +++ b/dlls/bloater.cpp @@ -0,0 +1,219 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Bloater +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BLOATER_AE_ATTACK_MELEE1 0x01 + + +class CBloater : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + void AttackSnd( void ); + + // No range attacks + BOOL CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } + BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); +}; + +LINK_ENTITY_TO_CLASS( monster_bloater, CBloater ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBloater :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CBloater :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + pev->yaw_speed = ys; +} + +int CBloater :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CBloater :: PainSound( void ) +{ +#if 0 + int pitch = 95 + RANDOM_LONG(0,9); + + switch (RANDOM_LONG(0,5)) + { + case 0: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_pain1.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 1: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_pain2.wav", 1.0, ATTN_NORM, 0, pitch); + break; + default: + break; + } +#endif +} + +void CBloater :: AlertSound( void ) +{ +#if 0 + int pitch = 95 + RANDOM_LONG(0,9); + + switch (RANDOM_LONG(0,2)) + { + case 0: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_alert10.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 1: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_alert20.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 2: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_alert30.wav", 1.0, ATTN_NORM, 0, pitch); + break; + } +#endif +} + +void CBloater :: IdleSound( void ) +{ +#if 0 + int pitch = 95 + RANDOM_LONG(0,9); + + switch (RANDOM_LONG(0,2)) + { + case 0: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_idle1.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 1: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_idle2.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 2: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_idle3.wav", 1.0, ATTN_NORM, 0, pitch); + break; + } +#endif +} + +void CBloater :: AttackSnd( void ) +{ +#if 0 + int pitch = 95 + RANDOM_LONG(0,9); + + switch (RANDOM_LONG(0,1)) + { + case 0: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_attack1.wav", 1.0, ATTN_NORM, 0, pitch); + break; + case 1: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "zombie/zo_attack2.wav", 1.0, ATTN_NORM, 0, pitch); + break; + } +#endif +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CBloater :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BLOATER_AE_ATTACK_MELEE1: + { + // do stuff for this event. + AttackSnd(); + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CBloater :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/floater.mdl"); + UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + pev->spawnflags |= FL_FLY; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = 40; + pev->view_ofs = VEC_VIEW;// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBloater :: Precache() +{ + PRECACHE_MODEL("models/floater.mdl"); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + diff --git a/dlls/bmodels.cpp b/dlls/bmodels.cpp index cd0dba7..cfb41bb 100644 --- a/dlls/bmodels.cpp +++ b/dlls/bmodels.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/bullsquid.cpp b/dlls/bullsquid.cpp new file mode 100644 index 0000000..c8518b6 --- /dev/null +++ b/dlls/bullsquid.cpp @@ -0,0 +1,1275 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// bullsquid - big, spotty tentacle-mouthed meanie. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "nodes.h" +#include "effects.h" +#include "decals.h" +#include "soundent.h" +#include "game.h" + +#define SQUID_SPRINT_DIST 256 // how close the squid has to get before starting to sprint and refusing to swerve + +int iSquidSpitSprite; + + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_SQUID_HURTHOP = LAST_COMMON_SCHEDULE + 1, + SCHED_SQUID_SMELLFOOD, + SCHED_SQUID_SEECRAB, + SCHED_SQUID_EAT, + SCHED_SQUID_SNIFF_AND_EAT, + SCHED_SQUID_WALLOW, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_SQUID_HOPTURN = LAST_COMMON_TASK + 1, +}; + +//========================================================= +// Bullsquid's spit projectile +//========================================================= +class CSquidSpit : public CBaseEntity +{ +public: + void Spawn( void ); + + static void Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + void Touch( CBaseEntity *pOther ); + void EXPORT Animate( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_maxFrame; +}; + +LINK_ENTITY_TO_CLASS( squidspit, CSquidSpit ); + +TYPEDESCRIPTION CSquidSpit::m_SaveData[] = +{ + DEFINE_FIELD( CSquidSpit, m_maxFrame, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CSquidSpit, CBaseEntity ); + +void CSquidSpit:: Spawn( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->classname = MAKE_STRING( "squidspit" ); + + pev->solid = SOLID_BBOX; + pev->rendermode = kRenderTransAlpha; + pev->renderamt = 255; + + SET_MODEL(ENT(pev), "sprites/bigspit.spr"); + pev->frame = 0; + pev->scale = 0.5; + + UTIL_SetSize( pev, Vector( 0, 0, 0), Vector(0, 0, 0) ); + + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; +} + +void CSquidSpit::Animate( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if ( pev->frame++ ) + { + if ( pev->frame > m_maxFrame ) + { + pev->frame = 0; + } + } +} + +void CSquidSpit::Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CSquidSpit *pSpit = GetClassPtr( (CSquidSpit *)NULL ); + pSpit->Spawn(); + + UTIL_SetOrigin( pSpit->pev, vecStart ); + pSpit->pev->velocity = vecVelocity; + pSpit->pev->owner = ENT(pevOwner); + + pSpit->SetThink ( Animate ); + pSpit->pev->nextthink = gpGlobals->time + 0.1; +} + +void CSquidSpit :: Touch ( CBaseEntity *pOther ) +{ + TraceResult tr; + int iPitch; + + // splat sound + iPitch = RANDOM_FLOAT( 90, 110 ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_acid1.wav", 1, ATTN_NORM, 0, iPitch ); + + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_spithit2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } + + if ( !pOther->pev->takedamage ) + { + + // make a splat on the wall + UTIL_TraceLine( pev->origin, pev->origin + pev->velocity * 10, dont_ignore_monsters, ENT( pev ), &tr ); + UTIL_DecalTrace(&tr, DECAL_SPIT1 + RANDOM_LONG(0,1)); + + // make some flecks + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, tr.vecEndPos ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( tr.vecEndPos.x); // pos + WRITE_COORD( tr.vecEndPos.y); + WRITE_COORD( tr.vecEndPos.z); + WRITE_COORD( tr.vecPlaneNormal.x); // dir + WRITE_COORD( tr.vecPlaneNormal.y); + WRITE_COORD( tr.vecPlaneNormal.z); + WRITE_SHORT( iSquidSpitSprite ); // model + WRITE_BYTE ( 5 ); // count + WRITE_BYTE ( 30 ); // speed + WRITE_BYTE ( 80 ); // noise ( client will divide by 100 ) + MESSAGE_END(); + } + else + { + pOther->TakeDamage ( pev, pev, gSkillData.bullsquidDmgSpit, DMG_GENERIC ); + } + + SetThink ( SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define BSQUID_AE_SPIT ( 1 ) +#define BSQUID_AE_BITE ( 2 ) +#define BSQUID_AE_BLINK ( 3 ) +#define BSQUID_AE_TAILWHIP ( 4 ) +#define BSQUID_AE_HOP ( 5 ) +#define BSQUID_AE_THROW ( 6 ) + +class CBullsquid : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int ISoundMask( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void IdleSound( void ); + void PainSound( void ); + void DeathSound( void ); + void AlertSound ( void ); + void AttackSound( void ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckMeleeAttack2 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + void RunAI( void ); + BOOL FValidateHintType ( short sHint ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + int IRelationship ( CBaseEntity *pTarget ); + int IgnoreConditions ( void ); + MONSTERSTATE GetIdealState ( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + BOOL m_fCanThreatDisplay;// this is so the squid only does the "I see a headcrab!" dance one time. + + float m_flLastHurtTime;// we keep track of this, because if something hurts a squid, it will forget about its love of headcrabs for a while. + float m_flNextSpitTime;// last time the bullsquid used the spit attack. +}; +LINK_ENTITY_TO_CLASS( monster_bullchicken, CBullsquid ); + +TYPEDESCRIPTION CBullsquid::m_SaveData[] = +{ + DEFINE_FIELD( CBullsquid, m_fCanThreatDisplay, FIELD_BOOLEAN ), + DEFINE_FIELD( CBullsquid, m_flLastHurtTime, FIELD_TIME ), + DEFINE_FIELD( CBullsquid, m_flNextSpitTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CBullsquid, CBaseMonster ); + +//========================================================= +// IgnoreConditions +//========================================================= +int CBullsquid::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ( gpGlobals->time - m_flLastHurtTime <= 20 ) + { + // haven't been hurt in 20 seconds, so let the squid care about stink. + iIgnore = bits_COND_SMELL | bits_COND_SMELL_FOOD; + } + + if ( m_hEnemy != NULL ) + { + if ( FClassnameIs( m_hEnemy->pev, "monster_headcrab" ) ) + { + // (Unless after a tasty headcrab) + iIgnore = bits_COND_SMELL | bits_COND_SMELL_FOOD; + } + } + + + return iIgnore; +} + +//========================================================= +// IRelationship - overridden for bullsquid so that it can +// be made to ignore its love of headcrabs for a while. +//========================================================= +int CBullsquid::IRelationship ( CBaseEntity *pTarget ) +{ + if ( gpGlobals->time - m_flLastHurtTime < 5 && FClassnameIs ( pTarget->pev, "monster_headcrab" ) ) + { + // if squid has been hurt in the last 5 seconds, and is getting relationship for a headcrab, + // tell squid to disregard crab. + return R_NO; + } + + return CBaseMonster :: IRelationship ( pTarget ); +} + +//========================================================= +// TakeDamage - overridden for bullsquid so we can keep track +// of how much time has passed since it was last injured +//========================================================= +int CBullsquid :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + float flDist; + Vector vecApex; + + // if the squid is running, has an enemy, was hurt by the enemy, hasn't been hurt in the last 3 seconds, and isn't too close to the enemy, + // it will swerve. (whew). + if ( m_hEnemy != NULL && IsMoving() && pevAttacker == m_hEnemy->pev && gpGlobals->time - m_flLastHurtTime > 3 ) + { + flDist = ( pev->origin - m_hEnemy->pev->origin ).Length2D(); + + if ( flDist > SQUID_SPRINT_DIST ) + { + flDist = ( pev->origin - m_Route[ m_iRouteIndex ].vecLocation ).Length2D();// reusing flDist. + + if ( FTriangulate( pev->origin, m_Route[ m_iRouteIndex ].vecLocation, flDist * 0.5, m_hEnemy, &vecApex ) ) + { + InsertWaypoint( vecApex, bits_MF_TO_DETOUR | bits_MF_DONT_SIMPLIFY ); + } + } + } + + if ( !FClassnameIs ( pevAttacker, "monster_headcrab" ) ) + { + // don't forget about headcrabs if it was a headcrab that hurt the squid. + m_flLastHurtTime = gpGlobals->time; + } + + return CBaseMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CBullsquid :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( IsMoving() && flDist >= 512 ) + { + // squid will far too far behind if he stops running to spit at this distance from the enemy. + return FALSE; + } + + if ( flDist > 64 && flDist <= 784 && flDot >= 0.5 && gpGlobals->time >= m_flNextSpitTime ) + { + if ( m_hEnemy != NULL ) + { + if ( fabs( pev->origin.z - m_hEnemy->pev->origin.z ) > 256 ) + { + // don't try to spit at someone up really high or down really low. + return FALSE; + } + } + + if ( IsMoving() ) + { + // don't spit again for a long time, resume chasing enemy. + m_flNextSpitTime = gpGlobals->time + 5; + } + else + { + // not moving, so spit again pretty soon. + m_flNextSpitTime = gpGlobals->time + 0.5; + } + + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CheckMeleeAttack1 - bullsquid is a big guy, so has a longer +// melee range than most monsters. This is the tailwhip attack +//========================================================= +BOOL CBullsquid :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( m_hEnemy->pev->health <= gSkillData.bullsquidDmgWhip && flDist <= 85 && flDot >= 0.7 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack2 - bullsquid is a big guy, so has a longer +// melee range than most monsters. This is the bite attack. +// this attack will not be performed if the tailwhip attack +// is valid. +//========================================================= +BOOL CBullsquid :: CheckMeleeAttack2 ( float flDot, float flDist ) +{ + if ( flDist <= 85 && flDot >= 0.7 && !HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) // The player & bullsquid can be as much as their bboxes + { // apart (48 * sqrt(3)) and he can still attack (85 is a little more than 48*sqrt(3)) + return TRUE; + } + return FALSE; +} + +//========================================================= +// FValidateHintType +//========================================================= +BOOL CBullsquid :: FValidateHintType ( short sHint ) +{ + int i; + + static short sSquidHints[] = + { + HINT_WORLD_HUMAN_BLOOD, + }; + + for ( i = 0 ; i < ARRAYSIZE ( sSquidHints ) ; i++ ) + { + if ( sSquidHints[ i ] == sHint ) + { + return TRUE; + } + } + + ALERT ( at_aiconsole, "Couldn't validate hint type" ); + return FALSE; +} + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CBullsquid :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_CARCASS | + bits_SOUND_MEAT | + bits_SOUND_GARBAGE | + bits_SOUND_PLAYER; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CBullsquid :: Classify ( void ) +{ + return CLASS_ALIEN_PREDATOR; +} + +//========================================================= +// IdleSound +//========================================================= +#define SQUID_ATTN_IDLE (float)1.5 +void CBullsquid :: IdleSound ( void ) +{ + switch ( RANDOM_LONG(0,4) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle1.wav", 1, SQUID_ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle2.wav", 1, SQUID_ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle3.wav", 1, SQUID_ATTN_IDLE ); + break; + case 3: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle4.wav", 1, SQUID_ATTN_IDLE ); + break; + case 4: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle5.wav", 1, SQUID_ATTN_IDLE ); + break; + } +} + +//========================================================= +// PainSound +//========================================================= +void CBullsquid :: PainSound ( void ) +{ + int iPitch = RANDOM_LONG( 85, 120 ); + + switch ( RANDOM_LONG(0,3) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 2: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain3.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 3: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_pain4.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } +} + +//========================================================= +// AlertSound +//========================================================= +void CBullsquid :: AlertSound ( void ) +{ + int iPitch = RANDOM_LONG( 140, 160 ); + + switch ( RANDOM_LONG ( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "bullchicken/bc_idle2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CBullsquid :: SetYawSpeed ( void ) +{ + int ys; + + ys = 0; + + switch ( m_Activity ) + { + case ACT_WALK: ys = 90; break; + case ACT_RUN: ys = 90; break; + case ACT_IDLE: ys = 90; break; + case ACT_RANGE_ATTACK1: ys = 90; break; + default: + ys = 90; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CBullsquid :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case BSQUID_AE_SPIT: + { + Vector vecSpitOffset; + Vector vecSpitDir; + + UTIL_MakeVectors ( pev->angles ); + + // !!!HACKHACK - the spot at which the spit originates (in front of the mouth) was measured in 3ds and hardcoded here. + // we should be able to read the position of bones at runtime for this info. + vecSpitOffset = ( gpGlobals->v_right * 8 + gpGlobals->v_forward * 37 + gpGlobals->v_up * 23 ); + vecSpitOffset = ( pev->origin + vecSpitOffset ); + vecSpitDir = ( ( m_hEnemy->pev->origin + m_hEnemy->pev->view_ofs ) - vecSpitOffset ).Normalize(); + + vecSpitDir.x += RANDOM_FLOAT( -0.05, 0.05 ); + vecSpitDir.y += RANDOM_FLOAT( -0.05, 0.05 ); + vecSpitDir.z += RANDOM_FLOAT( -0.05, 0 ); + + + // do stuff for this event. + AttackSound(); + + // spew the spittle temporary ents. + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpitOffset ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( vecSpitOffset.x); // pos + WRITE_COORD( vecSpitOffset.y); + WRITE_COORD( vecSpitOffset.z); + WRITE_COORD( vecSpitDir.x); // dir + WRITE_COORD( vecSpitDir.y); + WRITE_COORD( vecSpitDir.z); + WRITE_SHORT( iSquidSpitSprite ); // model + WRITE_BYTE ( 15 ); // count + WRITE_BYTE ( 210 ); // speed + WRITE_BYTE ( 25 ); // noise ( client will divide by 100 ) + MESSAGE_END(); + + CSquidSpit::Shoot( pev, vecSpitOffset, vecSpitDir * 900 ); + } + break; + + case BSQUID_AE_BITE: + { + // SOUND HERE! + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.bullsquidDmgBite, DMG_SLASH ); + + if ( pHurt ) + { + //pHurt->pev->punchangle.z = -15; + //pHurt->pev->punchangle.x = -45; + pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_forward * 100; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 100; + } + } + break; + + case BSQUID_AE_TAILWHIP: + { + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.bullsquidDmgWhip, DMG_CLUB | DMG_ALWAYSGIB ); + if ( pHurt ) + { + pHurt->pev->punchangle.z = -20; + pHurt->pev->punchangle.x = 20; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * 200; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 100; + } + } + break; + + case BSQUID_AE_BLINK: + { + // close eye. + pev->skin = 1; + } + break; + + case BSQUID_AE_HOP: + { + float flGravity = g_psv_gravity->value; + + // throw the squid up into the air on this frame. + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + pev->flags -= FL_ONGROUND; + } + + // jump into air for 0.8 (24/30) seconds +// pev->velocity.z += (0.875 * flGravity) * 0.5; + pev->velocity.z += (0.625 * flGravity) * 0.5; + } + break; + + case BSQUID_AE_THROW: + { + int iPitch; + + // squid throws its prey IF the prey is a client. + CBaseEntity *pHurt = CheckTraceHullAttack( 70, 0, 0 ); + + + if ( pHurt ) + { + // croonchy bite sound + iPitch = RANDOM_FLOAT( 90, 110 ); + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_bite2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "bullchicken/bc_bite3.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } + + + //pHurt->pev->punchangle.x = RANDOM_LONG(0,34) - 5; + //pHurt->pev->punchangle.z = RANDOM_LONG(0,49) - 25; + //pHurt->pev->punchangle.y = RANDOM_LONG(0,89) - 45; + + // screeshake transforms the viewmodel as well as the viewangle. No problems with seeing the ends of the viewmodels. + UTIL_ScreenShake( pHurt->pev->origin, 25.0, 1.5, 0.7, 2 ); + + if ( pHurt->IsPlayer() ) + { + UTIL_MakeVectors( pev->angles ); + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 300 + gpGlobals->v_up * 300; + } + } + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CBullsquid :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/bullsquid.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.bullsquidHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + m_fCanThreatDisplay = TRUE; + m_flNextSpitTime = gpGlobals->time; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CBullsquid :: Precache() +{ + PRECACHE_MODEL("models/bullsquid.mdl"); + + PRECACHE_MODEL("sprites/bigspit.spr");// spit projectile. + + iSquidSpitSprite = PRECACHE_MODEL("sprites/tinyspit.spr");// client side spittle. + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + PRECACHE_SOUND("bullchicken/bc_attack2.wav"); + PRECACHE_SOUND("bullchicken/bc_attack3.wav"); + + PRECACHE_SOUND("bullchicken/bc_die1.wav"); + PRECACHE_SOUND("bullchicken/bc_die2.wav"); + PRECACHE_SOUND("bullchicken/bc_die3.wav"); + + PRECACHE_SOUND("bullchicken/bc_idle1.wav"); + PRECACHE_SOUND("bullchicken/bc_idle2.wav"); + PRECACHE_SOUND("bullchicken/bc_idle3.wav"); + PRECACHE_SOUND("bullchicken/bc_idle4.wav"); + PRECACHE_SOUND("bullchicken/bc_idle5.wav"); + + PRECACHE_SOUND("bullchicken/bc_pain1.wav"); + PRECACHE_SOUND("bullchicken/bc_pain2.wav"); + PRECACHE_SOUND("bullchicken/bc_pain3.wav"); + PRECACHE_SOUND("bullchicken/bc_pain4.wav"); + + PRECACHE_SOUND("bullchicken/bc_attackgrowl.wav"); + PRECACHE_SOUND("bullchicken/bc_attackgrowl2.wav"); + PRECACHE_SOUND("bullchicken/bc_attackgrowl3.wav"); + + PRECACHE_SOUND("bullchicken/bc_acid1.wav"); + + PRECACHE_SOUND("bullchicken/bc_bite2.wav"); + PRECACHE_SOUND("bullchicken/bc_bite3.wav"); + + PRECACHE_SOUND("bullchicken/bc_spithit1.wav"); + PRECACHE_SOUND("bullchicken/bc_spithit2.wav"); + +} + +//========================================================= +// DeathSound +//========================================================= +void CBullsquid :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_die1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_die2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_die3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// AttackSound +//========================================================= +void CBullsquid :: AttackSound ( void ) +{ + switch ( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "bullchicken/bc_attack2.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "bullchicken/bc_attack3.wav", 1, ATTN_NORM ); + break; + } +} + + +//======================================================== +// RunAI - overridden for bullsquid because there are things +// that need to be checked every think. +//======================================================== +void CBullsquid :: RunAI ( void ) +{ + // first, do base class stuff + CBaseMonster :: RunAI(); + + if ( pev->skin != 0 ) + { + // close eye if it was open. + pev->skin = 0; + } + + if ( RANDOM_LONG(0,39) == 0 ) + { + pev->skin = 1; + } + + if ( m_hEnemy != NULL && m_Activity == ACT_RUN ) + { + // chasing enemy. Sprint for last bit + if ( (pev->origin - m_hEnemy->pev->origin).Length2D() < SQUID_SPRINT_DIST ) + { + pev->framerate = 1.25; + } + } + +} + +//======================================================== +// AI Schedules Specific to this monster +//========================================================= + +// primary range attack +Task_t tlSquidRangeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slSquidRangeAttack1[] = +{ + { + tlSquidRangeAttack1, + ARRAYSIZE ( tlSquidRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "Squid Range Attack1" + }, +}; + +// Chase enemy schedule +Task_t tlSquidChaseEnemy1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 },// !!!OEM - this will stop nasty squid oscillation. + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slSquidChaseEnemy[] = +{ + { + tlSquidChaseEnemy1, + ARRAYSIZE ( tlSquidChaseEnemy1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_SMELL_FOOD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_TASK_FAILED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_MEAT, + "Squid Chase Enemy" + }, +}; + +Task_t tlSquidHurtHop[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SOUND_WAKE, (float)0 }, + { TASK_SQUID_HOPTURN, (float)0 }, + { TASK_FACE_ENEMY, (float)0 },// in case squid didn't turn all the way in the air. +}; + +Schedule_t slSquidHurtHop[] = +{ + { + tlSquidHurtHop, + ARRAYSIZE ( tlSquidHurtHop ), + 0, + 0, + "SquidHurtHop" + } +}; + +Task_t tlSquidSeeCrab[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SOUND_WAKE, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_EXCITED }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slSquidSeeCrab[] = +{ + { + tlSquidSeeCrab, + ARRAYSIZE ( tlSquidSeeCrab ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "SquidSeeCrab" + } +}; + +// squid walks to something tasty and eats it. +Task_t tlSquidEat[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the squid can't get to the food + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSCENT, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_EAT, (float)50 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slSquidEat[] = +{ + { + tlSquidEat, + ARRAYSIZE( tlSquidEat ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY , + + // even though HEAR_SOUND/SMELL FOOD doesn't break this schedule, we need this mask + // here or the monster won't detect these sounds at ALL while running this schedule. + bits_SOUND_MEAT | + bits_SOUND_CARCASS, + "SquidEat" + } +}; + +// this is a bit different than just Eat. We use this schedule when the food is far away, occluded, or behind +// the squid. This schedule plays a sniff animation before going to the source of food. +Task_t tlSquidSniffAndEat[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the squid can't get to the food + { TASK_PLAY_SEQUENCE, (float)ACT_DETECT_SCENT }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSCENT, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_PLAY_SEQUENCE, (float)ACT_EAT }, + { TASK_EAT, (float)50 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slSquidSniffAndEat[] = +{ + { + tlSquidSniffAndEat, + ARRAYSIZE( tlSquidSniffAndEat ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY , + + // even though HEAR_SOUND/SMELL FOOD doesn't break this schedule, we need this mask + // here or the monster won't detect these sounds at ALL while running this schedule. + bits_SOUND_MEAT | + bits_SOUND_CARCASS, + "SquidSniffAndEat" + } +}; + +// squid does this to stinky things. +Task_t tlSquidWallow[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the squid can't get to the stinkiness + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSCENT, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_INSPECT_FLOOR}, + { TASK_EAT, (float)50 },// keeps squid from eating or sniffing anything else for a while. + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slSquidWallow[] = +{ + { + tlSquidWallow, + ARRAYSIZE( tlSquidWallow ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY , + + // even though HEAR_SOUND/SMELL FOOD doesn't break this schedule, we need this mask + // here or the monster won't detect these sounds at ALL while running this schedule. + bits_SOUND_GARBAGE, + + "SquidWallow" + } +}; + +DEFINE_CUSTOM_SCHEDULES( CBullsquid ) +{ + slSquidRangeAttack1, + slSquidChaseEnemy, + slSquidHurtHop, + slSquidSeeCrab, + slSquidEat, + slSquidSniffAndEat, + slSquidWallow +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CBullsquid, CBaseMonster ); + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t *CBullsquid :: GetSchedule( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + { + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE) ) + { + return GetScheduleOfType ( SCHED_SQUID_HURTHOP ); + } + + if ( HasConditions(bits_COND_SMELL_FOOD) ) + { + CSound *pSound; + + pSound = PBestScent(); + + if ( pSound && (!FInViewCone ( &pSound->m_vecOrigin ) || !FVisible ( pSound->m_vecOrigin )) ) + { + // scent is behind or occluded + return GetScheduleOfType( SCHED_SQUID_SNIFF_AND_EAT ); + } + + // food is right out in the open. Just go get it. + return GetScheduleOfType( SCHED_SQUID_EAT ); + } + + if ( HasConditions(bits_COND_SMELL) ) + { + // there's something stinky. + CSound *pSound; + + pSound = PBestScent(); + if ( pSound ) + return GetScheduleOfType( SCHED_SQUID_WALLOW); + } + + break; + } + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + if ( m_fCanThreatDisplay && IRelationship( m_hEnemy ) == R_HT ) + { + // this means squid sees a headcrab! + m_fCanThreatDisplay = FALSE;// only do the headcrab dance once per lifetime. + return GetScheduleOfType ( SCHED_SQUID_SEECRAB ); + } + else + { + return GetScheduleOfType ( SCHED_WAKE_ANGRY ); + } + } + + if ( HasConditions(bits_COND_SMELL_FOOD) ) + { + CSound *pSound; + + pSound = PBestScent(); + + if ( pSound && (!FInViewCone ( &pSound->m_vecOrigin ) || !FVisible ( pSound->m_vecOrigin )) ) + { + // scent is behind or occluded + return GetScheduleOfType( SCHED_SQUID_SNIFF_AND_EAT ); + } + + // food is right out in the open. Just go get it. + return GetScheduleOfType( SCHED_SQUID_EAT ); + } + + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + if ( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } + + if ( HasConditions( bits_COND_CAN_MELEE_ATTACK2 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK2 ); + } + + return GetScheduleOfType ( SCHED_CHASE_ENEMY ); + + break; + } + } + + return CBaseMonster :: GetSchedule(); +} + +//========================================================= +// GetScheduleOfType +//========================================================= +Schedule_t* CBullsquid :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_RANGE_ATTACK1: + return &slSquidRangeAttack1[ 0 ]; + break; + case SCHED_SQUID_HURTHOP: + return &slSquidHurtHop[ 0 ]; + break; + case SCHED_SQUID_SEECRAB: + return &slSquidSeeCrab[ 0 ]; + break; + case SCHED_SQUID_EAT: + return &slSquidEat[ 0 ]; + break; + case SCHED_SQUID_SNIFF_AND_EAT: + return &slSquidSniffAndEat[ 0 ]; + break; + case SCHED_SQUID_WALLOW: + return &slSquidWallow[ 0 ]; + break; + case SCHED_CHASE_ENEMY: + return &slSquidChaseEnemy[ 0 ]; + break; + } + + return CBaseMonster :: GetScheduleOfType ( Type ); +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. OVERRIDDEN for bullsquid because it needs to +// know explicitly when the last attempt to chase the enemy +// failed, since that impacts its attack choices. +//========================================================= +void CBullsquid :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_MELEE_ATTACK2: + { + switch ( RANDOM_LONG ( 0, 2 ) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "bullchicken/bc_attackgrowl3.wav", 1, ATTN_NORM ); + break; + } + + CBaseMonster :: StartTask ( pTask ); + break; + } + case TASK_SQUID_HOPTURN: + { + SetActivity ( ACT_HOP ); + MakeIdealYaw ( m_vecEnemyLKP ); + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + if ( BuildRoute ( m_hEnemy->pev->origin, bits_MF_TO_ENEMY, m_hEnemy ) ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + else + { + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + default: + { + CBaseMonster :: StartTask ( pTask ); + break; + } + } +} + +//========================================================= +// RunTask +//========================================================= +void CBullsquid :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_SQUID_HOPTURN: + { + MakeIdealYaw( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if ( m_fSequenceFinished ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + default: + { + CBaseMonster :: RunTask( pTask ); + break; + } + } +} + + +//========================================================= +// GetIdealState - Overridden for Bullsquid to deal with +// the feature that makes it lose interest in headcrabs for +// a while if something injures it. +//========================================================= +MONSTERSTATE CBullsquid :: GetIdealState ( void ) +{ + int iConditions; + + iConditions = IScheduleFlags(); + + // If no schedule conditions, the new ideal state is probably the reason we're in here. + switch ( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + /* + COMBAT goes to ALERT upon death of enemy + */ + { + if ( m_hEnemy != NULL && ( iConditions & bits_COND_LIGHT_DAMAGE || iConditions & bits_COND_HEAVY_DAMAGE ) && FClassnameIs( m_hEnemy->pev, "monster_headcrab" ) ) + { + // if the squid has a headcrab enemy and something hurts it, it's going to forget about the crab for a while. + m_hEnemy = NULL; + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + break; + } + } + + m_IdealMonsterState = CBaseMonster :: GetIdealState(); + + return m_IdealMonsterState; +} + diff --git a/dlls/buttons.cpp b/dlls/buttons.cpp index 42fc043..e493267 100644 --- a/dlls/buttons.cpp +++ b/dlls/buttons.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/cbase.cpp b/dlls/cbase.cpp index 8196e52..73c63ae 100644 --- a/dlls/cbase.cpp +++ b/dlls/cbase.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/cbase.h b/dlls/cbase.h index 0c36ae6..d3e13d0 100644 --- a/dlls/cbase.h +++ b/dlls/cbase.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/cdll_dll.h b/dlls/cdll_dll.h index f6ed329..2293a27 100644 --- a/dlls/cdll_dll.h +++ b/dlls/cdll_dll.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/client.cpp b/dlls/client.cpp index fc278a8..ec3bedc 100644 --- a/dlls/client.cpp +++ b/dlls/client.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -199,6 +199,9 @@ void ClientPutInServer( edict_t *pEntity ) pPlayer->pev->effects |= EF_NOINTERP; } +#include "voice_gamemgr.h" +extern CVoiceGameMgr g_VoiceGameMgr; + //// HOST_SAY // String comes in as // say blah blah blah @@ -220,6 +223,13 @@ void Host_Say( edict_t *pEntity, int teamonly ) if ( CMD_ARGC() == 0 ) return; + entvars_t *pev = &pEntity->v; + CBasePlayer* player = GetClassPtr((CBasePlayer *)pev); + + //Not yet. + if ( player->m_flNextChatTime > gpGlobals->time ) + return; + if ( !stricmp( pcmd, cpSay) || !stricmp( pcmd, cpSayTeam ) ) { if ( CMD_ARGC() >= 2 ) @@ -278,6 +288,9 @@ void Host_Say( edict_t *pEntity, int teamonly ) strcat( text, p ); strcat( text, "\n" ); + + player->m_flNextChatTime = gpGlobals->time + CHAT_INTERVAL; + // loop through all players // Start with the first player. // This may return the world in single player if the client types something between levels or during spawn @@ -295,6 +308,10 @@ void Host_Say( edict_t *pEntity, int teamonly ) if ( !(client->IsNetClient()) ) // Not a client ? (should never be true) continue; + // can the receiver hear the sender? or has he muted him? + if ( g_VoiceGameMgr.PlayerHasBlockedPlayer( client, player ) ) + continue; + if ( teamonly && g_pGameRules->PlayerRelationship(client, CBaseEntity::Instance(pEntity)) != GR_TEAMMATE ) continue; @@ -323,20 +340,20 @@ void Host_Say( edict_t *pEntity, int teamonly ) // team match? if ( g_teamplay ) { - UTIL_LogPrintf( "\"%s<%i><%u><%s>\" %s \"%s\"\n", + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" %s \"%s\"\n", STRING( pEntity->v.netname ), GETPLAYERUSERID( pEntity ), - GETPLAYERWONID( pEntity ), + GETPLAYERAUTHID( pEntity ), g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pEntity ), "model" ), temp, p ); } else { - UTIL_LogPrintf( "\"%s<%i><%u><%i>\" %s \"%s\"\n", + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" %s \"%s\"\n", STRING( pEntity->v.netname ), GETPLAYERUSERID( pEntity ), - GETPLAYERWONID( pEntity ), + GETPLAYERAUTHID( pEntity ), GETPLAYERUSERID( pEntity ), temp, p ); @@ -372,6 +389,10 @@ void ClientCommand( edict_t *pEntity ) { Host_Say( pEntity, 1 ); } + else if ( FStrEq(pcmd, "fullupdate" ) ) + { + GetClassPtr((CBasePlayer *)pev)->ForceClientDllUpdate(); + } else if ( FStrEq(pcmd, "give" ) ) { if ( g_flWeaponCheat != 0.0) @@ -409,6 +430,13 @@ void ClientCommand( edict_t *pEntity ) { GetClassPtr((CBasePlayer *)pev)->SelectLastItem(); } + else if ( FStrEq( pcmd, "spectate" ) && (pev->flags & FL_PROXY) ) // added for proxy support + { + CBasePlayer * pPlayer = GetClassPtr((CBasePlayer *)pev); + + edict_t *pentSpawnSpot = g_pGameRules->GetPlayerSpawnSpot( pPlayer ); + pPlayer->StartObserver( pev->origin, VARS(pentSpawnSpot)->angles); + } 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 @@ -473,19 +501,19 @@ void ClientUserInfoChanged( edict_t *pEntity, char *infobuffer ) // team match? if ( g_teamplay ) { - UTIL_LogPrintf( "\"%s<%i><%u><%s>\" changed name to \"%s\"\n", + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" changed name to \"%s\"\n", STRING( pEntity->v.netname ), GETPLAYERUSERID( pEntity ), - GETPLAYERWONID( pEntity ), + GETPLAYERAUTHID( pEntity ), g_engfuncs.pfnInfoKeyValue( infobuffer, "model" ), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); } else { - UTIL_LogPrintf( "\"%s<%i><%u><%i>\" changed name to \"%s\"\n", + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" changed name to \"%s\"\n", STRING( pEntity->v.netname ), GETPLAYERUSERID( pEntity ), - GETPLAYERWONID( pEntity ), + GETPLAYERAUTHID( pEntity ), GETPLAYERUSERID( pEntity ), g_engfuncs.pfnInfoKeyValue( infobuffer, "name" ) ); } diff --git a/dlls/client.h b/dlls/client.h index 2c3d09e..2e0b38e 100644 --- a/dlls/client.h +++ b/dlls/client.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/combat.cpp b/dlls/combat.cpp index 616d39c..5f57b2c 100644 --- a/dlls/combat.cpp +++ b/dlls/combat.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/controller.cpp b/dlls/controller.cpp new file mode 100644 index 0000000..8cf0272 --- /dev/null +++ b/dlls/controller.cpp @@ -0,0 +1,1427 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +//========================================================= +// CONTROLLER +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "effects.h" +#include "schedule.h" +#include "weapons.h" +#include "squadmonster.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define CONTROLLER_AE_HEAD_OPEN 1 +#define CONTROLLER_AE_BALL_SHOOT 2 +#define CONTROLLER_AE_SMALL_SHOOT 3 +#define CONTROLLER_AE_POWERUP_FULL 4 +#define CONTROLLER_AE_POWERUP_HALF 5 + +#define CONTROLLER_FLINCH_DELAY 2 // at most one flinch every n secs + +class CController : public CSquadMonster +{ +public: + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void RunAI( void ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); // balls + BOOL CheckRangeAttack2 ( float flDot, float flDist ); // head + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); // block, throw + Schedule_t* GetSchedule ( void ); + Schedule_t* GetScheduleOfType ( int Type ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + CUSTOM_SCHEDULES; + + void Stop( void ); + void Move ( float flInterval ); + int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ); + void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + void SetActivity ( Activity NewActivity ); + BOOL ShouldAdvanceRoute( float flWaypointDist ); + int LookupFloat( ); + + float m_flNextFlinch; + + float m_flShootTime; + float m_flShootEnd; + + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + void AttackSound( void ); + void DeathSound( void ); + + static const char *pAttackSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pDeathSounds[]; + + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + CSprite *m_pBall[2]; // hand balls + int m_iBall[2]; // how bright it should be + float m_iBallTime[2]; // when it should be that color + int m_iBallCurrent[2]; // current brightness + + Vector m_vecEstVelocity; + + Vector m_velocity; + int m_fInCombat; +}; + +LINK_ENTITY_TO_CLASS( monster_alien_controller, CController ); + +TYPEDESCRIPTION CController::m_SaveData[] = +{ + DEFINE_ARRAY( CController, m_pBall, FIELD_CLASSPTR, 2 ), + DEFINE_ARRAY( CController, m_iBall, FIELD_INTEGER, 2 ), + DEFINE_ARRAY( CController, m_iBallTime, FIELD_TIME, 2 ), + DEFINE_ARRAY( CController, m_iBallCurrent, FIELD_INTEGER, 2 ), + DEFINE_FIELD( CController, m_vecEstVelocity, FIELD_VECTOR ), +}; +IMPLEMENT_SAVERESTORE( CController, CSquadMonster ); + + +const char *CController::pAttackSounds[] = +{ + "controller/con_attack1.wav", + "controller/con_attack2.wav", + "controller/con_attack3.wav", +}; + +const char *CController::pIdleSounds[] = +{ + "controller/con_idle1.wav", + "controller/con_idle2.wav", + "controller/con_idle3.wav", + "controller/con_idle4.wav", + "controller/con_idle5.wav", +}; + +const char *CController::pAlertSounds[] = +{ + "controller/con_alert1.wav", + "controller/con_alert2.wav", + "controller/con_alert3.wav", +}; + +const char *CController::pPainSounds[] = +{ + "controller/con_pain1.wav", + "controller/con_pain2.wav", + "controller/con_pain3.wav", +}; + +const char *CController::pDeathSounds[] = +{ + "controller/con_die1.wav", + "controller/con_die2.wav", +}; + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CController :: Classify ( void ) +{ + return CLASS_ALIEN_MILITARY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CController :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + pev->yaw_speed = ys; +} + +int CController :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +void CController::Killed( entvars_t *pevAttacker, int iGib ) +{ + // shut off balls + /* + m_iBall[0] = 0; + m_iBallTime[0] = gpGlobals->time + 4.0; + m_iBall[1] = 0; + m_iBallTime[1] = gpGlobals->time + 4.0; + */ + + // fade balls + if (m_pBall[0]) + { + m_pBall[0]->SUB_StartFadeOut(); + m_pBall[0] = NULL; + } + if (m_pBall[1]) + { + m_pBall[1]->SUB_StartFadeOut(); + m_pBall[1] = NULL; + } + + CSquadMonster::Killed( pevAttacker, iGib ); +} + + +void CController::GibMonster( void ) +{ + // delete balls + if (m_pBall[0]) + { + UTIL_Remove( m_pBall[0] ); + m_pBall[0] = NULL; + } + if (m_pBall[1]) + { + UTIL_Remove( m_pBall[1] ); + m_pBall[1] = NULL; + } + CSquadMonster::GibMonster( ); +} + + + + +void CController :: PainSound( void ) +{ + if (RANDOM_LONG(0,5) < 2) + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); +} + +void CController :: AlertSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAlertSounds ); +} + +void CController :: IdleSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pIdleSounds ); +} + +void CController :: AttackSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAttackSounds ); +} + +void CController :: DeathSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pDeathSounds ); +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CController :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case CONTROLLER_AE_HEAD_OPEN: + { + Vector vecStart, angleGun; + + GetAttachment( 0, vecStart, angleGun ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment + WRITE_COORD( vecStart.x ); // origin + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( 1 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 20 ); // life * 10 + WRITE_COORD( -32 ); // decay + MESSAGE_END(); + + m_iBall[0] = 192; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 255; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + + } + break; + + case CONTROLLER_AE_BALL_SHOOT: + { + Vector vecStart, angleGun; + + GetAttachment( 0, vecStart, angleGun ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment + WRITE_COORD( 0 ); // origin + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + WRITE_COORD( 32 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 32 ); // decay + MESSAGE_END(); + + CBaseMonster *pBall = (CBaseMonster*)Create( "controller_head_ball", vecStart, pev->angles, edict() ); + + pBall->pev->velocity = Vector( 0, 0, 32 ); + pBall->m_hEnemy = m_hEnemy; + + m_iBall[0] = 0; + m_iBall[1] = 0; + } + break; + + case CONTROLLER_AE_SMALL_SHOOT: + { + AttackSound( ); + m_flShootTime = gpGlobals->time; + m_flShootEnd = m_flShootTime + atoi( pEvent->options ) / 15.0; + } + break; + case CONTROLLER_AE_POWERUP_FULL: + { + m_iBall[0] = 255; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 255; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + } + break; + case CONTROLLER_AE_POWERUP_HALF: + { + m_iBall[0] = 192; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 192; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + } + break; + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CController :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/controller.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 )); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + pev->flags |= FL_FLY; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = gSkillData.controllerHealth; + pev->view_ofs = Vector( 0, 0, -2 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_FULL;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CController :: Precache() +{ + PRECACHE_MODEL("models/controller.mdl"); + + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pIdleSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); + PRECACHE_SOUND_ARRAY( pDeathSounds ); + + PRECACHE_MODEL( "sprites/xspark4.spr"); + + UTIL_PrecacheOther( "controller_energy_ball" ); + UTIL_PrecacheOther( "controller_head_ball" ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + +// Chase enemy schedule +Task_t tlControllerChaseEnemy[] = +{ + { TASK_GET_PATH_TO_ENEMY, (float)128 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + +}; + +Schedule_t slControllerChaseEnemy[] = +{ + { + tlControllerChaseEnemy, + ARRAYSIZE ( tlControllerChaseEnemy ), + bits_COND_NEW_ENEMY | + bits_COND_TASK_FAILED, + 0, + "ControllerChaseEnemy" + }, +}; + + + +Task_t tlControllerStrafe[] = +{ + { TASK_WAIT, (float)0.2 }, + { TASK_GET_PATH_TO_ENEMY, (float)128 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slControllerStrafe[] = +{ + { + tlControllerStrafe, + ARRAYSIZE ( tlControllerStrafe ), + bits_COND_NEW_ENEMY, + 0, + "ControllerStrafe" + }, +}; + + +Task_t tlControllerTakeCover[] = +{ + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slControllerTakeCover[] = +{ + { + tlControllerTakeCover, + ARRAYSIZE ( tlControllerTakeCover ), + bits_COND_NEW_ENEMY, + 0, + "ControllerTakeCover" + }, +}; + + +Task_t tlControllerFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slControllerFail[] = +{ + { + tlControllerFail, + ARRAYSIZE ( tlControllerFail ), + 0, + 0, + "ControllerFail" + }, +}; + + + +DEFINE_CUSTOM_SCHEDULES( CController ) +{ + slControllerChaseEnemy, + slControllerStrafe, + slControllerTakeCover, + slControllerFail, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CController, CSquadMonster ); + + + +//========================================================= +// StartTask +//========================================================= +void CController :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + CSquadMonster :: StartTask ( pTask ); + break; + case TASK_GET_PATH_TO_ENEMY_LKP: + { + if (BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, pTask->flData, (m_vecEnemyLKP - pev->origin).Length() + 1024 )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemyLKP failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy == NULL ) + { + TaskFail(); + return; + } + + if (BuildNearestRoute( pEnemy->pev->origin, pEnemy->pev->view_ofs, pTask->flData, (pEnemy->pev->origin - pev->origin).Length() + 1024 )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + default: + CSquadMonster :: StartTask ( pTask ); + break; + } +} + + +Vector Intersect( Vector vecSrc, Vector vecDst, Vector vecMove, float flSpeed ) +{ + Vector vecTo = vecDst - vecSrc; + + float a = DotProduct( vecMove, vecMove ) - flSpeed * flSpeed; + float b = 0 * DotProduct(vecTo, vecMove); // why does this work? + float c = DotProduct( vecTo, vecTo ); + + float t; + if (a == 0) + { + t = c / (flSpeed * flSpeed); + } + else + { + t = b * b - 4 * a * c; + t = sqrt( t ) / (2.0 * a); + float t1 = -b +t; + float t2 = -b -t; + + if (t1 < 0 || t2 < t1) + t = t2; + else + t = t1; + } + + // ALERT( at_console, "Intersect %f\n", t ); + + if (t < 0.1) + t = 0.1; + if (t > 10.0) + t = 10.0; + + Vector vecHit = vecTo + vecMove * t; + return vecHit.Normalize( ) * flSpeed; +} + + +int CController::LookupFloat( ) +{ + if (m_velocity.Length( ) < 32.0) + { + return LookupSequence( "up" ); + } + + UTIL_MakeAimVectors( pev->angles ); + float x = DotProduct( gpGlobals->v_forward, m_velocity ); + float y = DotProduct( gpGlobals->v_right, m_velocity ); + float z = DotProduct( gpGlobals->v_up, m_velocity ); + + if (fabs(x) > fabs(y) && fabs(x) > fabs(z)) + { + if (x > 0) + return LookupSequence( "forward"); + else + return LookupSequence( "backward"); + } + else if (fabs(y) > fabs(z)) + { + if (y > 0) + return LookupSequence( "right"); + else + return LookupSequence( "left"); + } + else + { + if (z > 0) + return LookupSequence( "up"); + else + return LookupSequence( "down"); + } +} + + +//========================================================= +// RunTask +//========================================================= +void CController :: RunTask ( Task_t *pTask ) +{ + + if (m_flShootEnd > gpGlobals->time) + { + Vector vecHand, vecAngle; + + GetAttachment( 2, vecHand, vecAngle ); + + while (m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->time) + { + Vector vecSrc = vecHand + pev->velocity * (m_flShootTime - gpGlobals->time); + Vector vecDir; + + if (m_hEnemy != NULL) + { + if (HasConditions( bits_COND_SEE_ENEMY )) + { + m_vecEstVelocity = m_vecEstVelocity * 0.5 + m_hEnemy->pev->velocity * 0.5; + } + else + { + m_vecEstVelocity = m_vecEstVelocity * 0.8; + } + vecDir = Intersect( vecSrc, m_hEnemy->BodyTarget( pev->origin ), m_vecEstVelocity, gSkillData.controllerSpeedBall ); + float delta = 0.03490; // +-2 degree + vecDir = vecDir + Vector( RANDOM_FLOAT( -delta, delta ), RANDOM_FLOAT( -delta, delta ), RANDOM_FLOAT( -delta, delta ) ) * gSkillData.controllerSpeedBall; + + vecSrc = vecSrc + vecDir * (gpGlobals->time - m_flShootTime); + CBaseMonster *pBall = (CBaseMonster*)Create( "controller_energy_ball", vecSrc, pev->angles, edict() ); + pBall->pev->velocity = vecDir; + } + m_flShootTime += 0.2; + } + + if (m_flShootTime > m_flShootEnd) + { + m_iBall[0] = 64; + m_iBallTime[0] = m_flShootEnd; + m_iBall[1] = 64; + m_iBallTime[1] = m_flShootEnd; + m_fInCombat = FALSE; + } + } + + switch ( pTask->iTask ) + { + case TASK_WAIT_FOR_MOVEMENT: + case TASK_WAIT: + case TASK_WAIT_FACE_ENEMY: + case TASK_WAIT_PVS: + MakeIdealYaw( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if (m_fSequenceFinished) + { + m_fInCombat = FALSE; + } + + CSquadMonster :: RunTask ( pTask ); + + if (!m_fInCombat) + { + if (HasConditions ( bits_COND_CAN_RANGE_ATTACK1 )) + { + pev->sequence = LookupActivity( ACT_RANGE_ATTACK1 ); + pev->frame = 0; + ResetSequenceInfo( ); + m_fInCombat = TRUE; + } + else if (HasConditions ( bits_COND_CAN_RANGE_ATTACK2 )) + { + pev->sequence = LookupActivity( ACT_RANGE_ATTACK2 ); + pev->frame = 0; + ResetSequenceInfo( ); + m_fInCombat = TRUE; + } + else + { + int iFloat = LookupFloat( ); + if (m_fSequenceFinished || iFloat != pev->sequence) + { + pev->sequence = iFloat; + pev->frame = 0; + ResetSequenceInfo( ); + } + } + } + break; + default: + CSquadMonster :: RunTask ( pTask ); + break; + } +} + + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CController :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + break; + + case MONSTERSTATE_ALERT: + break; + + case MONSTERSTATE_COMBAT: + { + Vector vecTmp = Intersect( Vector( 0, 0, 0 ), Vector( 100, 4, 7 ), Vector( 2, 10, -3 ), 20.0 ); + + // dead enemy + if ( HasConditions ( bits_COND_LIGHT_DAMAGE ) ) + { + // m_iFrustration++; + } + if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) + { + // m_iFrustration++; + } + } + break; + } + + return CSquadMonster :: GetSchedule(); +} + + + +//========================================================= +//========================================================= +Schedule_t* CController :: GetScheduleOfType ( int Type ) +{ + // ALERT( at_console, "%d\n", m_iFrustration ); + switch ( Type ) + { + case SCHED_CHASE_ENEMY: + return slControllerChaseEnemy; + case SCHED_RANGE_ATTACK1: + return slControllerStrafe; + case SCHED_RANGE_ATTACK2: + case SCHED_MELEE_ATTACK1: + case SCHED_MELEE_ATTACK2: + case SCHED_TAKE_COVER_FROM_ENEMY: + return slControllerTakeCover; + case SCHED_FAIL: + return slControllerFail; + } + + return CBaseMonster :: GetScheduleOfType( Type ); +} + + + + + +//========================================================= +// CheckRangeAttack1 - shoot a bigass energy ball out of their head +// +//========================================================= +BOOL CController :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDot > 0.5 && flDist > 256 && flDist <= 2048 ) + { + return TRUE; + } + return FALSE; +} + + +BOOL CController :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if ( flDot > 0.5 && flDist > 64 && flDist <= 2048 ) + { + return TRUE; + } + return FALSE; +} + + +BOOL CController :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + return FALSE; +} + + +void CController :: SetActivity ( Activity NewActivity ) +{ + CBaseMonster::SetActivity( NewActivity ); + + switch ( m_Activity) + { + case ACT_WALK: + m_flGroundSpeed = 100; + break; + default: + m_flGroundSpeed = 100; + break; + } +} + + + +//========================================================= +// RunAI +//========================================================= +void CController :: RunAI( void ) +{ + CBaseMonster :: RunAI(); + Vector vecStart, angleGun; + + if ( HasMemory( bits_MEMORY_KILLED ) ) + return; + + for (int i = 0; i < 2; i++) + { + if (m_pBall[i] == NULL) + { + m_pBall[i] = CSprite::SpriteCreate( "sprites/xspark4.spr", pev->origin, TRUE ); + m_pBall[i]->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_pBall[i]->SetAttachment( edict(), (i + 3) ); + m_pBall[i]->SetScale( 1.0 ); + } + + float t = m_iBallTime[i] - gpGlobals->time; + if (t > 0.1) + t = 0.1 / t; + else + t = 1.0; + + m_iBallCurrent[i] += (m_iBall[i] - m_iBallCurrent[i]) * t; + + m_pBall[i]->SetBrightness( m_iBallCurrent[i] ); + + GetAttachment( i + 2, vecStart, angleGun ); + UTIL_SetOrigin( m_pBall[i]->pev, vecStart ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 * (i + 3) ); // entity, attachment + WRITE_COORD( vecStart.x ); // origin + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( m_iBallCurrent[i] / 8 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 5 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + } +} + + +extern void DrawRoute( entvars_t *pev, WayPoint_t *m_Route, int m_iRouteIndex, int r, int g, int b ); + +void CController::Stop( void ) +{ + m_IdealActivity = GetStoppedActivity(); +} + + +#define DIST_TO_CHECK 200 +void CController :: Move ( float flInterval ) +{ + float flWaypointDist; + float flCheckDist; + float flDist;// how far the lookahead check got before hitting an object. + float flMoveDist; + Vector vecDir; + Vector vecApex; + CBaseEntity *pTargetEnt; + + // Don't move if no valid route + if ( FRouteClear() ) + { + ALERT( at_aiconsole, "Tried to move with no route!\n" ); + TaskFail(); + return; + } + + if ( m_flMoveWaitFinished > gpGlobals->time ) + return; + +// Debug, test movement code +#if 0 +// if ( CVAR_GET_FLOAT("stopmove" ) != 0 ) + { + if ( m_movementGoal == MOVEGOAL_ENEMY ) + RouteSimplify( m_hEnemy ); + else + RouteSimplify( m_hTargetEnt ); + FRefreshRoute(); + return; + } +#else +// Debug, draw the route +// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 0, 255 ); +#endif + + // if the monster is moving directly towards an entity (enemy for instance), we'll set this pointer + // to that entity for the CheckLocalMove and Triangulate functions. + pTargetEnt = NULL; + + if (m_flGroundSpeed == 0) + { + m_flGroundSpeed = 100; + // TaskFail( ); + // return; + } + + flMoveDist = m_flGroundSpeed * flInterval; + + do + { + // local move to waypoint. + vecDir = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Normalize(); + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length(); + + // MakeIdealYaw ( m_Route[ m_iRouteIndex ].vecLocation ); + // ChangeYaw ( pev->yaw_speed ); + + // if the waypoint is closer than CheckDist, CheckDist is the dist to waypoint + if ( flWaypointDist < DIST_TO_CHECK ) + { + flCheckDist = flWaypointDist; + } + else + { + flCheckDist = DIST_TO_CHECK; + } + + if ( (m_Route[ m_iRouteIndex ].iType & (~bits_MF_NOT_TO_MASK)) == bits_MF_TO_ENEMY ) + { + // only on a PURE move to enemy ( i.e., ONLY MF_TO_ENEMY set, not MF_TO_ENEMY and DETOUR ) + pTargetEnt = m_hEnemy; + } + else if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_TARGETENT ) + { + pTargetEnt = m_hTargetEnt; + } + + // !!!BUGBUG - CheckDist should be derived from ground speed. + // If this fails, it should be because of some dynamic entity blocking this guy. + // We've already checked this path, so we should wait and time out if the entity doesn't move + flDist = 0; + if ( CheckLocalMove ( pev->origin, pev->origin + vecDir * flCheckDist, pTargetEnt, &flDist ) != LOCALMOVE_VALID ) + { + CBaseEntity *pBlocker; + + // Can't move, stop + Stop(); + // Blocking entity is in global trace_ent + pBlocker = CBaseEntity::Instance( gpGlobals->trace_ent ); + if (pBlocker) + { + DispatchBlocked( edict(), pBlocker->edict() ); + } + if ( pBlocker && m_moveWaitTime > 0 && pBlocker->IsMoving() && !pBlocker->IsPlayer() && (gpGlobals->time-m_flMoveWaitFinished) > 3.0 ) + { + // Can we still move toward our target? + if ( flDist < m_flGroundSpeed ) + { + // Wait for a second + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime; + // ALERT( at_aiconsole, "Move %s!!!\n", STRING( pBlocker->pev->classname ) ); + return; + } + } + else + { + // try to triangulate around whatever is in the way. + if ( FTriangulate( pev->origin, m_Route[ m_iRouteIndex ].vecLocation, flDist, pTargetEnt, &vecApex ) ) + { + InsertWaypoint( vecApex, bits_MF_TO_DETOUR ); + RouteSimplify( pTargetEnt ); + } + else + { + ALERT ( at_aiconsole, "Couldn't Triangulate\n" ); + Stop(); + if ( m_moveWaitTime > 0 ) + { + FRefreshRoute(); + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime * 0.5; + } + else + { + TaskFail(); + ALERT( at_aiconsole, "Failed to move!\n" ); + //ALERT( at_aiconsole, "%f, %f, %f\n", pev->origin.z, (pev->origin + (vecDir * flCheckDist)).z, m_Route[m_iRouteIndex].vecLocation.z ); + } + return; + } + } + } + + // UNDONE: this is a hack to quit moving farther than it has looked ahead. + if (flCheckDist < flMoveDist) + { + MoveExecute( pTargetEnt, vecDir, flCheckDist / m_flGroundSpeed ); + + // ALERT( at_console, "%.02f\n", flInterval ); + AdvanceRoute( flWaypointDist ); + flMoveDist -= flCheckDist; + } + else + { + MoveExecute( pTargetEnt, vecDir, flMoveDist / m_flGroundSpeed ); + + if ( ShouldAdvanceRoute( flWaypointDist - flMoveDist ) ) + { + AdvanceRoute( flWaypointDist ); + } + flMoveDist = 0; + } + + if ( MovementIsComplete() ) + { + Stop(); + RouteClear(); + } + } while (flMoveDist > 0 && flCheckDist > 0); + + // cut corner? + if (flWaypointDist < 128) + { + if ( m_movementGoal == MOVEGOAL_ENEMY ) + RouteSimplify( m_hEnemy ); + else + RouteSimplify( m_hTargetEnt ); + FRefreshRoute(); + + if (m_flGroundSpeed > 100) + m_flGroundSpeed -= 40; + } + else + { + if (m_flGroundSpeed < 400) + m_flGroundSpeed += 10; + } +} + + + +BOOL CController:: ShouldAdvanceRoute( float flWaypointDist ) +{ + if ( flWaypointDist <= 32 ) + { + return TRUE; + } + + return FALSE; +} + + +int CController :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + TraceResult tr; + + UTIL_TraceHull( vecStart + Vector( 0, 0, 32), vecEnd + Vector( 0, 0, 32), dont_ignore_monsters, large_hull, edict(), &tr ); + + // ALERT( at_console, "%.0f %.0f %.0f : ", vecStart.x, vecStart.y, vecStart.z ); + // ALERT( at_console, "%.0f %.0f %.0f\n", vecEnd.x, vecEnd.y, vecEnd.z ); + + if (pflDist) + { + *pflDist = ( (tr.vecEndPos - Vector( 0, 0, 32 )) - vecStart ).Length();// get the distance. + } + + // ALERT( at_console, "check %d %d %f\n", tr.fStartSolid, tr.fAllSolid, tr.flFraction ); + if (tr.fStartSolid || tr.flFraction < 1.0) + { + if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) + return LOCALMOVE_VALID; + return LOCALMOVE_INVALID; + } + + return LOCALMOVE_VALID; +} + + +void CController::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + if ( m_IdealActivity != m_movementActivity ) + m_IdealActivity = m_movementActivity; + + // ALERT( at_console, "move %.4f %.4f %.4f : %f\n", vecDir.x, vecDir.y, vecDir.z, flInterval ); + + // float flTotal = m_flGroundSpeed * pev->framerate * flInterval; + // UTIL_MoveToOrigin ( ENT(pev), m_Route[ m_iRouteIndex ].vecLocation, flTotal, MOVE_STRAFE ); + + m_velocity = m_velocity * 0.8 + m_flGroundSpeed * vecDir * 0.2; + + UTIL_MoveToOrigin ( ENT(pev), pev->origin + m_velocity, m_velocity.Length() * flInterval, MOVE_STRAFE ); + +} + + + + +//========================================================= +// Controller bouncy ball attack +//========================================================= +class CControllerHeadBall : public CBaseMonster +{ + void Spawn( void ); + void Precache( void ); + void EXPORT HuntThink( void ); + void EXPORT DieThink( void ); + void EXPORT BounceTouch( CBaseEntity *pOther ); + void MovetoTarget( Vector vecTarget ); + void Crawl( void ); + int m_iTrail; + int m_flNextAttack; + Vector m_vecIdeal; + EHANDLE m_hOwner; +}; +LINK_ENTITY_TO_CLASS( controller_head_ball, CControllerHeadBall ); + + + +void CControllerHeadBall :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "sprites/xspark4.spr"); + pev->rendermode = kRenderTransAdd; + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->renderamt = 255; + pev->scale = 2.0; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( HuntThink ); + SetTouch( BounceTouch ); + + m_vecIdeal = Vector( 0, 0, 0 ); + + pev->nextthink = gpGlobals->time + 0.1; + + m_hOwner = Instance( pev->owner ); + pev->dmgtime = gpGlobals->time; +} + + +void CControllerHeadBall :: Precache( void ) +{ + PRECACHE_MODEL("sprites/xspark1.spr"); + PRECACHE_SOUND("debris/zap4.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); +} + + +void CControllerHeadBall :: HuntThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + pev->renderamt -= 5; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( pev->renderamt / 16 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 255 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 2 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + + // check world boundaries + if (gpGlobals->time - pev->dmgtime > 5 || pev->renderamt < 64 || m_hEnemy == NULL || m_hOwner == NULL || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + SetTouch( NULL ); + UTIL_Remove( this ); + return; + } + + MovetoTarget( m_hEnemy->Center( ) ); + + if ((m_hEnemy->Center() - pev->origin).Length() < 64) + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, m_hEnemy->Center(), dont_ignore_monsters, ENT(pev), &tr ); + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + if (pEntity != NULL && pEntity->pev->takedamage) + { + ClearMultiDamage( ); + pEntity->TraceAttack( m_hOwner->pev, gSkillData.controllerDmgZap, pev->velocity, &tr, DMG_SHOCK ); + ApplyMultiDamage( pev, m_hOwner->pev ); + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); + + m_flNextAttack = gpGlobals->time + 3.0; + + SetThink( DieThink ); + pev->nextthink = gpGlobals->time + 0.3; + } + + // Crawl( ); +} + + +void CControllerHeadBall :: DieThink( void ) +{ + UTIL_Remove( this ); +} + + +void CControllerHeadBall :: MovetoTarget( Vector vecTarget ) +{ + // accelerate + float flSpeed = m_vecIdeal.Length(); + if (flSpeed == 0) + { + m_vecIdeal = pev->velocity; + flSpeed = m_vecIdeal.Length(); + } + + if (flSpeed > 400) + { + m_vecIdeal = m_vecIdeal.Normalize( ) * 400; + } + m_vecIdeal = m_vecIdeal + (vecTarget - pev->origin).Normalize() * 100; + pev->velocity = m_vecIdeal; +} + + + +void CControllerHeadBall :: Crawl( void ) +{ + + Vector vecAim = Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ) ).Normalize( ); + Vector vecPnt = pev->origin + pev->velocity * 0.3 + vecAim * 64; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( vecPnt.x); + WRITE_COORD( vecPnt.y); + WRITE_COORD( vecPnt.z); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); +} + + +void CControllerHeadBall::BounceTouch( CBaseEntity *pOther ) +{ + Vector vecDir = m_vecIdeal.Normalize( ); + + TraceResult tr = UTIL_GetGlobalTrace( ); + + float n = -DotProduct(tr.vecPlaneNormal, vecDir); + + vecDir = 2.0 * tr.vecPlaneNormal * n + vecDir; + + m_vecIdeal = vecDir * m_vecIdeal.Length(); +} + + + + +class CControllerZapBall : public CBaseMonster +{ + void Spawn( void ); + void Precache( void ); + void EXPORT AnimateThink( void ); + void EXPORT ExplodeTouch( CBaseEntity *pOther ); + + EHANDLE m_hOwner; +}; +LINK_ENTITY_TO_CLASS( controller_energy_ball, CControllerZapBall ); + + +void CControllerZapBall :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "sprites/xspark4.spr"); + pev->rendermode = kRenderTransAdd; + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->renderamt = 255; + pev->scale = 0.5; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( AnimateThink ); + SetTouch( ExplodeTouch ); + + m_hOwner = Instance( pev->owner ); + pev->dmgtime = gpGlobals->time; // keep track of when ball spawned + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CControllerZapBall :: Precache( void ) +{ + PRECACHE_MODEL("sprites/xspark4.spr"); + // PRECACHE_SOUND("debris/zap4.wav"); + // PRECACHE_SOUND("weapons/electro4.wav"); +} + + +void CControllerZapBall :: AnimateThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + pev->frame = ((int)pev->frame + 1) % 11; + + if (gpGlobals->time - pev->dmgtime > 5 || pev->velocity.Length() < 10) + { + SetTouch( NULL ); + UTIL_Remove( this ); + } +} + + +void CControllerZapBall::ExplodeTouch( CBaseEntity *pOther ) +{ + if (pOther->pev->takedamage) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + + entvars_t *pevOwner; + if (m_hOwner != NULL) + { + pevOwner = m_hOwner->pev; + } + else + { + pevOwner = pev; + } + + ClearMultiDamage( ); + pOther->TraceAttack(pevOwner, gSkillData.controllerDmgBall, pev->velocity.Normalize(), &tr, DMG_ENERGYBEAM ); + ApplyMultiDamage( pevOwner, pevOwner ); + + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "weapons/electro4.wav", 0.3, ATTN_NORM, 0, RANDOM_LONG( 90, 99 ) ); + + } + + UTIL_Remove( this ); +} + + + +#endif // !OEM && !HLDEMO diff --git a/dlls/crossbow.cpp b/dlls/crossbow.cpp index 0678c22..5fe0a78 100644 --- a/dlls/crossbow.cpp +++ b/dlls/crossbow.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/crowbar.cpp b/dlls/crowbar.cpp index 926dce9..f301de7 100644 --- a/dlls/crowbar.cpp +++ b/dlls/crowbar.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/decals.h b/dlls/decals.h index 47d99c2..0f8ff4e 100644 --- a/dlls/decals.h +++ b/dlls/decals.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/defaultai.cpp b/dlls/defaultai.cpp new file mode 100644 index 0000000..efc0087 --- /dev/null +++ b/dlls/defaultai.cpp @@ -0,0 +1,1232 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Default behaviors. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "defaultai.h" +#include "soundent.h" +#include "nodes.h" +#include "scripted.h" + +//========================================================= +// Fail +//========================================================= +Task_t tlFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slFail[] = +{ + { + tlFail, + ARRAYSIZE ( tlFail ), + bits_COND_CAN_ATTACK, + 0, + "Fail" + }, +}; + +//========================================================= +// Idle Schedules +//========================================================= +Task_t tlIdleStand1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)5 },// repick IDLESTAND every five seconds. gives us a chance to pick an active idle, fidget, etc. +}; + +Schedule_t slIdleStand[] = +{ + { + tlIdleStand1, + ARRAYSIZE ( tlIdleStand1 ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL_FOOD | + bits_COND_SMELL | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags + bits_SOUND_WORLD | + bits_SOUND_PLAYER | + bits_SOUND_DANGER | + + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "IdleStand" + }, +}; + +Schedule_t slIdleTrigger[] = +{ + { + tlIdleStand1, + ARRAYSIZE ( tlIdleStand1 ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Trigger" + }, +}; + + +Task_t tlIdleWalk1[] = +{ + { TASK_WALK_PATH, (float)9999 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slIdleWalk[] = +{ + { + tlIdleWalk1, + ARRAYSIZE ( tlIdleWalk1 ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL_FOOD | + bits_COND_SMELL | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags + + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "Idle Walk" + }, +}; + +//========================================================= +// Ambush - monster stands in place and waits for a new +// enemy, or chance to attack an existing enemy. +//========================================================= +Task_t tlAmbush[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_INDEFINITE, (float)0 }, +}; + +Schedule_t slAmbush[] = +{ + { + tlAmbush, + ARRAYSIZE ( tlAmbush ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED, + + 0, + "Ambush" + }, +}; + +//========================================================= +// ActiveIdle schedule - !!!BUGBUG - if this schedule doesn't +// complete on its own, the monster's HintNode will not be +// cleared, and the rest of the monster's group will avoid +// that node because they think the group member that was +// previously interrupted is still using that node to active +// idle. +///========================================================= +Task_t tlActiveIdle[] = +{ + { TASK_FIND_HINTNODE, (float)0 }, + { TASK_GET_PATH_TO_HINTNODE, (float)0 }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_HINTNODE, (float)0 }, + { TASK_PLAY_ACTIVE_IDLE, (float)0 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, + { TASK_CLEAR_HINTNODE, (float)0 }, +}; + +Schedule_t slActiveIdle[] = +{ + { + tlActiveIdle, + ARRAYSIZE( tlActiveIdle ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_HEAR_SOUND, + + bits_SOUND_COMBAT | + bits_SOUND_WORLD | + bits_SOUND_PLAYER | + bits_SOUND_DANGER, + "Active Idle" + } +}; + +//========================================================= +// Wake Schedules +//========================================================= +Task_t tlWakeAngry1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_SOUND_WAKE, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, +}; + +Schedule_t slWakeAngry[] = +{ + { + tlWakeAngry1, + ARRAYSIZE ( tlWakeAngry1 ), + 0, + 0, + "Wake Angry" + } +}; + +//========================================================= +// AlertFace Schedules +//========================================================= +Task_t tlAlertFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_IDEAL, (float)0 }, +}; + +Schedule_t slAlertFace[] = +{ + { + tlAlertFace1, + ARRAYSIZE ( tlAlertFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED, + 0, + "Alert Face" + }, +}; + +//========================================================= +// AlertSmallFlinch Schedule - shot, but didn't see attacker, +// flinch then face +//========================================================= +Task_t tlAlertSmallFlinch[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_REMEMBER, (float)bits_MEMORY_FLINCHED }, + { TASK_SMALL_FLINCH, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_ALERT_FACE }, +}; + +Schedule_t slAlertSmallFlinch[] = +{ + { + tlAlertSmallFlinch, + ARRAYSIZE ( tlAlertSmallFlinch ), + 0, + 0, + "Alert Small Flinch" + }, +}; + +//========================================================= +// AlertIdle Schedules +//========================================================= +Task_t tlAlertStand1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)20 }, + { TASK_SUGGEST_STATE, (float)MONSTERSTATE_IDLE }, +}; + +Schedule_t slAlertStand[] = +{ + { + tlAlertStand1, + ARRAYSIZE ( tlAlertStand1 ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_SMELL | + bits_COND_SMELL_FOOD | + bits_COND_HEAR_SOUND, + + bits_SOUND_COMBAT |// sound flags + bits_SOUND_WORLD | + bits_SOUND_PLAYER | + bits_SOUND_DANGER | + + bits_SOUND_MEAT |// scent flags + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "Alert Stand" + }, +}; + +//========================================================= +// InvestigateSound - sends a monster to the location of the +// sound that was just heard, to check things out. +//========================================================= +Task_t tlInvestigateSound[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_GET_PATH_TO_BESTSOUND, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_IDLE }, + { TASK_WAIT, (float)10 }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slInvestigateSound[] = +{ + { + tlInvestigateSound, + ARRAYSIZE ( tlInvestigateSound ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "InvestigateSound" + }, +}; + +//========================================================= +// CombatIdle Schedule +//========================================================= +Task_t tlCombatStand1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_INDEFINITE, (float)0 }, +}; + +Schedule_t slCombatStand[] = +{ + { + tlCombatStand1, + ARRAYSIZE ( tlCombatStand1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_ATTACK, + 0, + "Combat Stand" + }, +}; + +//========================================================= +// CombatFace Schedule +//========================================================= +Task_t tlCombatFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slCombatFace[] = +{ + { + tlCombatFace1, + ARRAYSIZE ( tlCombatFace1 ), + bits_COND_CAN_ATTACK | + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Standoff schedule. Used in combat when a monster is +// hiding in cover or the enemy has moved out of sight. +// Should we look around in this schedule? +//========================================================= +Task_t tlStandoff[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, +}; + +Schedule_t slStandoff[] = +{ + { + tlStandoff, + ARRAYSIZE ( tlStandoff ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_ENEMY_DEAD | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Standoff" + } +}; + +//========================================================= +// Arm weapon (draw gun) +//========================================================= +Task_t tlArmWeapon[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float) ACT_ARM } +}; + +Schedule_t slArmWeapon[] = +{ + { + tlArmWeapon, + ARRAYSIZE ( tlArmWeapon ), + 0, + 0, + "Arm Weapon" + } +}; + +//========================================================= +// reload schedule +//========================================================= +Task_t tlReload[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, float(ACT_RELOAD) }, +}; + +Schedule_t slReload[] = +{ + { + tlReload, + ARRAYSIZE ( tlReload ), + bits_COND_HEAVY_DAMAGE, + 0, + "Reload" + } +}; + +//========================================================= +// Attack Schedules +//========================================================= + +// primary range attack +Task_t tlRangeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slRangeAttack1[] = +{ + { + tlRangeAttack1, + ARRAYSIZE ( tlRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack1" + }, +}; + +// secondary range attack +Task_t tlRangeAttack2[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, +}; + +Schedule_t slRangeAttack2[] = +{ + { + tlRangeAttack2, + ARRAYSIZE ( tlRangeAttack2 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack2" + }, +}; + +// primary melee attack +Task_t tlPrimaryMeleeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MELEE_ATTACK1, (float)0 }, +}; + +Schedule_t slPrimaryMeleeAttack[] = +{ + { + tlPrimaryMeleeAttack1, + ARRAYSIZE ( tlPrimaryMeleeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED, + 0, + "Primary Melee Attack" + }, +}; + +// secondary melee attack +Task_t tlSecondaryMeleeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MELEE_ATTACK2, (float)0 }, +}; + +Schedule_t slSecondaryMeleeAttack[] = +{ + { + tlSecondaryMeleeAttack1, + ARRAYSIZE ( tlSecondaryMeleeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED, + 0, + "Secondary Melee Attack" + }, +}; + +// special attack1 +Task_t tlSpecialAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SPECIAL_ATTACK1, (float)0 }, +}; + +Schedule_t slSpecialAttack1[] = +{ + { + tlSpecialAttack1, + ARRAYSIZE ( tlSpecialAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Special Attack1" + }, +}; + +// special attack2 +Task_t tlSpecialAttack2[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SPECIAL_ATTACK2, (float)0 }, +}; + +Schedule_t slSpecialAttack2[] = +{ + { + tlSpecialAttack2, + ARRAYSIZE ( tlSpecialAttack2 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Special Attack2" + }, +}; + +// Chase enemy schedule +Task_t tlChaseEnemy1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_CHASE_ENEMY_FAILED }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slChaseEnemy[] = +{ + { + tlChaseEnemy1, + ARRAYSIZE ( tlChaseEnemy1 ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_TASK_FAILED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Chase Enemy" + }, +}; + + +// Chase enemy failure schedule +Task_t tlChaseEnemyFailed[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, +// { TASK_TURN_LEFT, (float)179 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slChaseEnemyFailed[] = +{ + { + tlChaseEnemyFailed, + ARRAYSIZE ( tlChaseEnemyFailed ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "tlChaseEnemyFailed" + }, +}; + + +//========================================================= +// small flinch, played when minor damage is taken. +//========================================================= +Task_t tlSmallFlinch[] = +{ + { TASK_REMEMBER, (float)bits_MEMORY_FLINCHED }, + { TASK_STOP_MOVING, 0 }, + { TASK_SMALL_FLINCH, 0 }, +}; + +Schedule_t slSmallFlinch[] = +{ + { + tlSmallFlinch, + ARRAYSIZE ( tlSmallFlinch ), + 0, + 0, + "Small Flinch" + }, +}; + +//========================================================= +// Die! +//========================================================= +Task_t tlDie1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SOUND_DIE, (float)0 }, + { TASK_DIE, (float)0 }, +}; + +Schedule_t slDie[] = +{ + { + tlDie1, + ARRAYSIZE( tlDie1 ), + 0, + 0, + "Die" + }, +}; + +//========================================================= +// Victory Dance +//========================================================= +Task_t tlVictoryDance[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, + { TASK_WAIT, (float)0 }, +}; + +Schedule_t slVictoryDance[] = +{ + { + tlVictoryDance, + ARRAYSIZE( tlVictoryDance ), + 0, + 0, + "Victory Dance" + }, +}; + +//========================================================= +// BarnacleVictimGrab - barnacle tongue just hit the monster, +// so play a hit animation, then play a cycling pull animation +// as the creature is hoisting the monster. +//========================================================= +Task_t tlBarnacleVictimGrab[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_BARNACLE_HIT }, + { TASK_SET_ACTIVITY, (float)ACT_BARNACLE_PULL }, + { TASK_WAIT_INDEFINITE, (float)0 },// just cycle barnacle pull anim while barnacle hoists. +}; + +Schedule_t slBarnacleVictimGrab[] = +{ + { + tlBarnacleVictimGrab, + ARRAYSIZE ( tlBarnacleVictimGrab ), + 0, + 0, + "Barnacle Victim" + } +}; + +//========================================================= +// BarnacleVictimChomp - barnacle has pulled the prey to its +// mouth. Victim should play the BARNCLE_CHOMP animation +// once, then loop the BARNACLE_CHEW animation indefinitely +//========================================================= +Task_t tlBarnacleVictimChomp[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_BARNACLE_CHOMP }, + { TASK_SET_ACTIVITY, (float)ACT_BARNACLE_CHEW }, + { TASK_WAIT_INDEFINITE, (float)0 },// just cycle barnacle pull anim while barnacle hoists. +}; + +Schedule_t slBarnacleVictimChomp[] = +{ + { + tlBarnacleVictimChomp, + ARRAYSIZE ( tlBarnacleVictimChomp ), + 0, + 0, + "Barnacle Chomp" + } +}; + + +// Universal Error Schedule +Task_t tlError[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_WAIT_INDEFINITE, (float)0 }, +}; + +Schedule_t slError[] = +{ + { + tlError, + ARRAYSIZE ( tlError ), + 0, + 0, + "Error" + }, +}; + +Task_t tlScriptedWalk[] = +{ + { TASK_WALK_TO_TARGET, (float)TARGET_MOVE_SCRIPTED }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLANT_ON_SCRIPT, (float)0 }, + { TASK_FACE_SCRIPT, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_ENABLE_SCRIPT, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, +}; + +Schedule_t slWalkToScript[] = +{ + { + tlScriptedWalk, + ARRAYSIZE ( tlScriptedWalk ), + SCRIPT_BREAK_CONDITIONS, + 0, + "WalkToScript" + }, +}; + + +Task_t tlScriptedRun[] = +{ + { TASK_RUN_TO_TARGET, (float)TARGET_MOVE_SCRIPTED }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_PLANT_ON_SCRIPT, (float)0 }, + { TASK_FACE_SCRIPT, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_ENABLE_SCRIPT, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, +}; + +Schedule_t slRunToScript[] = +{ + { + tlScriptedRun, + ARRAYSIZE ( tlScriptedRun ), + SCRIPT_BREAK_CONDITIONS, + 0, + "RunToScript" + }, +}; + +Task_t tlScriptedWait[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, +}; + +Schedule_t slWaitScript[] = +{ + { + tlScriptedWait, + ARRAYSIZE ( tlScriptedWait ), + SCRIPT_BREAK_CONDITIONS, + 0, + "WaitForScript" + }, +}; + +Task_t tlScriptedFace[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_SCRIPT, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WAIT_FOR_SCRIPT, (float)0 }, + { TASK_PLAY_SCRIPT, (float)0 }, +}; + +Schedule_t slFaceScript[] = +{ + { + tlScriptedFace, + ARRAYSIZE ( tlScriptedFace ), + SCRIPT_BREAK_CONDITIONS, + 0, + "FaceScript" + }, +}; + +//========================================================= +// Cower - this is what is usually done when attempts +// to escape danger fail. +//========================================================= +Task_t tlCower[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_COWER }, +}; + +Schedule_t slCower[] = +{ + { + tlCower, + ARRAYSIZE ( tlCower ), + 0, + 0, + "Cower" + }, +}; + +//========================================================= +// move away from where you're currently standing. +//========================================================= +Task_t tlTakeCoverFromOrigin[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ORIGIN, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slTakeCoverFromOrigin[] = +{ + { + tlTakeCoverFromOrigin, + ARRAYSIZE ( tlTakeCoverFromOrigin ), + bits_COND_NEW_ENEMY, + 0, + "TakeCoverFromOrigin" + }, +}; + +//========================================================= +// hide from the loudest sound source +//========================================================= +Task_t tlTakeCoverFromBestSound[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slTakeCoverFromBestSound[] = +{ + { + tlTakeCoverFromBestSound, + ARRAYSIZE ( tlTakeCoverFromBestSound ), + bits_COND_NEW_ENEMY, + 0, + "TakeCoverFromBestSound" + }, +}; + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlTakeCoverFromEnemy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, +// { TASK_TURN_LEFT, (float)179 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slTakeCoverFromEnemy[] = +{ + { + tlTakeCoverFromEnemy, + ARRAYSIZE ( tlTakeCoverFromEnemy ), + bits_COND_NEW_ENEMY, + 0, + "tlTakeCoverFromEnemy" + }, +}; + +Schedule_t *CBaseMonster::m_scheduleList[] = +{ + slIdleStand, + slIdleTrigger, + slIdleWalk, + slAmbush, + slActiveIdle, + slWakeAngry, + slAlertFace, + slAlertSmallFlinch, + slAlertStand, + slInvestigateSound, + slCombatStand, + slCombatFace, + slStandoff, + slArmWeapon, + slReload, + slRangeAttack1, + slRangeAttack2, + slPrimaryMeleeAttack, + slSecondaryMeleeAttack, + slSpecialAttack1, + slSpecialAttack2, + slChaseEnemy, + slChaseEnemyFailed, + slSmallFlinch, + slDie, + slVictoryDance, + slBarnacleVictimGrab, + slBarnacleVictimChomp, + slError, + slWalkToScript, + slRunToScript, + slWaitScript, + slFaceScript, + slCower, + slTakeCoverFromOrigin, + slTakeCoverFromBestSound, + slTakeCoverFromEnemy, + slFail +}; + +Schedule_t *CBaseMonster::ScheduleFromName( const char *pName ) +{ + return ScheduleInList( pName, m_scheduleList, ARRAYSIZE(m_scheduleList) ); +} + + +Schedule_t *CBaseMonster :: ScheduleInList( const char *pName, Schedule_t **pList, int listCount ) +{ + int i; + + if ( !pName ) + { + ALERT( at_console, "%s set to unnamed schedule!\n", STRING(pev->classname) ); + return NULL; + } + + + for ( i = 0; i < listCount; i++ ) + { + if ( !pList[i]->pName ) + { + ALERT( at_console, "Unnamed schedule!\n" ); + continue; + } + if ( stricmp( pName, pList[i]->pName ) == 0 ) + return pList[i]; + } + return NULL; +} + +//========================================================= +// GetScheduleOfType - returns a pointer to one of the +// monster's available schedules of the indicated type. +//========================================================= +Schedule_t* CBaseMonster :: GetScheduleOfType ( int Type ) +{ +// ALERT ( at_console, "Sched Type:%d\n", Type ); + switch ( Type ) + { + // This is the schedule for scripted sequences AND scripted AI + case SCHED_AISCRIPT: + { + ASSERT( m_pCine != NULL ); + if ( !m_pCine ) + { + ALERT( at_aiconsole, "Script failed for %s\n", STRING(pev->classname) ); + CineCleanup(); + return GetScheduleOfType( SCHED_IDLE_STAND ); + } +// else +// ALERT( at_aiconsole, "Starting script %s for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) ); + + switch ( m_pCine->m_fMoveTo ) + { + case 0: + case 4: + return slWaitScript; + case 1: + return slWalkToScript; + case 2: + return slRunToScript; + case 5: + return slFaceScript; + } + break; + } + case SCHED_IDLE_STAND: + { + if ( RANDOM_LONG(0,14) == 0 && FCanActiveIdle() ) + { + return &slActiveIdle[ 0 ]; + } + + return &slIdleStand[ 0 ]; + } + case SCHED_IDLE_WALK: + { + return &slIdleWalk[ 0 ]; + } + case SCHED_WAIT_TRIGGER: + { + return &slIdleTrigger[ 0 ]; + } + case SCHED_WAKE_ANGRY: + { + return &slWakeAngry[ 0 ]; + } + case SCHED_ALERT_FACE: + { + return &slAlertFace[ 0 ]; + } + case SCHED_ALERT_STAND: + { + return &slAlertStand[ 0 ]; + } + case SCHED_COMBAT_STAND: + { + return &slCombatStand[ 0 ]; + } + case SCHED_COMBAT_FACE: + { + return &slCombatFace[ 0 ]; + } + case SCHED_CHASE_ENEMY: + { + return &slChaseEnemy[ 0 ]; + } + case SCHED_CHASE_ENEMY_FAILED: + { + return &slFail[ 0 ]; + } + case SCHED_SMALL_FLINCH: + { + return &slSmallFlinch[ 0 ]; + } + case SCHED_ALERT_SMALL_FLINCH: + { + return &slAlertSmallFlinch[ 0 ]; + } + case SCHED_RELOAD: + { + return &slReload[ 0 ]; + } + case SCHED_ARM_WEAPON: + { + return &slArmWeapon[ 0 ]; + } + case SCHED_STANDOFF: + { + return &slStandoff[ 0 ]; + } + case SCHED_RANGE_ATTACK1: + { + return &slRangeAttack1[ 0 ]; + } + case SCHED_RANGE_ATTACK2: + { + return &slRangeAttack2[ 0 ]; + } + case SCHED_MELEE_ATTACK1: + { + return &slPrimaryMeleeAttack[ 0 ]; + } + case SCHED_MELEE_ATTACK2: + { + return &slSecondaryMeleeAttack[ 0 ]; + } + case SCHED_SPECIAL_ATTACK1: + { + return &slSpecialAttack1[ 0 ]; + } + case SCHED_SPECIAL_ATTACK2: + { + return &slSpecialAttack2[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slTakeCoverFromBestSound[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_ENEMY: + { + return &slTakeCoverFromEnemy[ 0 ]; + } + case SCHED_COWER: + { + return &slCower[ 0 ]; + } + case SCHED_AMBUSH: + { + return &slAmbush[ 0 ]; + } + case SCHED_BARNACLE_VICTIM_GRAB: + { + return &slBarnacleVictimGrab[ 0 ]; + } + case SCHED_BARNACLE_VICTIM_CHOMP: + { + return &slBarnacleVictimChomp[ 0 ]; + } + case SCHED_INVESTIGATE_SOUND: + { + return &slInvestigateSound[ 0 ]; + } + case SCHED_DIE: + { + return &slDie[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_ORIGIN: + { + return &slTakeCoverFromOrigin[ 0 ]; + } + case SCHED_VICTORY_DANCE: + { + return &slVictoryDance[ 0 ]; + } + case SCHED_FAIL: + { + return slFail; + } + default: + { + ALERT ( at_console, "GetScheduleOfType()\nNo CASE for Schedule Type %d!\n", Type ); + + return &slIdleStand[ 0 ]; + break; + } + } + + return NULL; +} diff --git a/dlls/defaultai.h b/dlls/defaultai.h new file mode 100644 index 0000000..271ac7a --- /dev/null +++ b/dlls/defaultai.h @@ -0,0 +1,98 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef DEFAULTAI_H +#define DEFAULTAI_H + +//========================================================= +// Failed +//========================================================= +extern Schedule_t slFail[]; + +//========================================================= +// Idle Schedules +//========================================================= +extern Schedule_t slIdleStand[]; +extern Schedule_t slIdleTrigger[]; +extern Schedule_t slIdleWalk[]; + +//========================================================= +// Wake Schedules +//========================================================= +extern Schedule_t slWakeAngry[]; + +//========================================================= +// AlertTurn Schedules +//========================================================= +extern Schedule_t slAlertFace[]; + +//========================================================= +// AlertIdle Schedules +//========================================================= +extern Schedule_t slAlertStand[]; + +//========================================================= +// CombatIdle Schedule +//========================================================= +extern Schedule_t slCombatStand[]; + +//========================================================= +// CombatFace Schedule +//========================================================= +extern Schedule_t slCombatFace[]; + +//========================================================= +// reload schedule +//========================================================= +extern Schedule_t slReload[]; + +//========================================================= +// Attack Schedules +//========================================================= + +extern Schedule_t slRangeAttack1[]; +extern Schedule_t slRangeAttack2[]; + +extern Schedule_t slTakeCoverFromBestSound[]; + +// primary melee attack +extern Schedule_t slMeleeAttack[]; + +// Chase enemy schedule +extern Schedule_t slChaseEnemy[]; + +//========================================================= +// small flinch, used when a relatively minor bit of damage +// is inflicted. +//========================================================= +extern Schedule_t slSmallFlinch[]; + +//========================================================= +// Die! +//========================================================= +extern Schedule_t slDie[]; + +//========================================================= +// Universal Error Schedule +//========================================================= +extern Schedule_t slError[]; + +//========================================================= +// Scripted sequences +//========================================================= +extern Schedule_t slWalkToScript[]; +extern Schedule_t slRunToScript[]; +extern Schedule_t slWaitScript[]; + +#endif // DEFAULTAI_H diff --git a/dlls/doors.cpp b/dlls/doors.cpp index 66ea430..08511d7 100644 --- a/dlls/doors.cpp +++ b/dlls/doors.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/doors.h b/dlls/doors.h index dcf571c..55a853f 100644 --- a/dlls/doors.h +++ b/dlls/doors.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/effects.cpp b/dlls/effects.cpp index 04239d2..5d7a1b3 100644 --- a/dlls/effects.cpp +++ b/dlls/effects.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/effects.h b/dlls/effects.h index 4fb7d3f..1464d6a 100644 --- a/dlls/effects.h +++ b/dlls/effects.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/egon.cpp b/dlls/egon.cpp index 46211c2..43cde79 100644 --- a/dlls/egon.cpp +++ b/dlls/egon.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/enginecallback.h b/dlls/enginecallback.h index 19eb34b..aad3724 100644 --- a/dlls/enginecallback.h +++ b/dlls/enginecallback.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -67,7 +67,7 @@ extern enginefuncs_t g_engfuncs; #define CRC32_FINAL (*g_engfuncs.pfnCRC32_Final) #define RANDOM_LONG (*g_engfuncs.pfnRandomLong) #define RANDOM_FLOAT (*g_engfuncs.pfnRandomFloat) -#define GETPLAYERWONID (*g_engfuncs.pfnGetPlayerWONId) +#define GETPLAYERAUTHID (*g_engfuncs.pfnGetPlayerAuthId) inline void MESSAGE_BEGIN( int msg_dest, int msg_type, const float *pOrigin = NULL, edict_t *ed = NULL ) { (*g_engfuncs.pfnMessageBegin)(msg_dest, msg_type, pOrigin, ed); diff --git a/dlls/explode.cpp b/dlls/explode.cpp index c6146d6..55dcb1c 100644 --- a/dlls/explode.cpp +++ b/dlls/explode.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1999, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/explode.h b/dlls/explode.h index 4ddeeb6..3d8c410 100644 --- a/dlls/explode.h +++ b/dlls/explode.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/extdll.h b/dlls/extdll.h index b93cba9..31e4855 100644 --- a/dlls/extdll.h +++ b/dlls/extdll.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/flyingmonster.cpp b/dlls/flyingmonster.cpp new file mode 100644 index 0000000..9a0774f --- /dev/null +++ b/dlls/flyingmonster.cpp @@ -0,0 +1,281 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "flyingmonster.h" + +#define FLYING_AE_FLAP (8) +#define FLYING_AE_FLAPSOUND (9) + + +extern DLL_GLOBAL edict_t *g_pBodyQueueHead; + +int CFlyingMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + // UNDONE: need to check more than the endpoint + if (FBitSet(pev->flags, FL_SWIM) && (UTIL_PointContents(vecEnd) != CONTENTS_WATER)) + { + // ALERT(at_aiconsole, "can't swim out of water\n"); + return FALSE; + } + + TraceResult tr; + + UTIL_TraceHull( vecStart + Vector( 0, 0, 32 ), vecEnd + Vector( 0, 0, 32 ), dont_ignore_monsters, large_hull, edict(), &tr ); + + // ALERT( at_console, "%.0f %.0f %.0f : ", vecStart.x, vecStart.y, vecStart.z ); + // ALERT( at_console, "%.0f %.0f %.0f\n", vecEnd.x, vecEnd.y, vecEnd.z ); + + if (pflDist) + { + *pflDist = ( (tr.vecEndPos - Vector( 0, 0, 32 )) - vecStart ).Length();// get the distance. + } + + // ALERT( at_console, "check %d %d %f\n", tr.fStartSolid, tr.fAllSolid, tr.flFraction ); + if (tr.fStartSolid || tr.flFraction < 1.0) + { + if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) + return LOCALMOVE_VALID; + return LOCALMOVE_INVALID; + } + + return LOCALMOVE_VALID; +} + + +BOOL CFlyingMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ) +{ + return CBaseMonster::FTriangulate( vecStart, vecEnd, flDist, pTargetEnt, pApex ); +} + + +Activity CFlyingMonster :: GetStoppedActivity( void ) +{ + if ( pev->movetype != MOVETYPE_FLY ) // UNDONE: Ground idle here, IDLE may be something else + return ACT_IDLE; + + return ACT_HOVER; +} + + +void CFlyingMonster :: Stop( void ) +{ + Activity stopped = GetStoppedActivity(); + if ( m_IdealActivity != stopped ) + { + m_flightSpeed = 0; + m_IdealActivity = stopped; + } + pev->angles.z = 0; + pev->angles.x = 0; + m_vecTravel = g_vecZero; +} + + +float CFlyingMonster :: ChangeYaw( int speed ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + { + float diff = FlYawDiff(); + float target = 0; + + if ( m_IdealActivity != GetStoppedActivity() ) + { + if ( diff < -20 ) + target = 90; + else if ( diff > 20 ) + target = -90; + } + pev->angles.z = UTIL_Approach( target, pev->angles.z, 220.0 * gpGlobals->frametime ); + } + return CBaseMonster::ChangeYaw( speed ); +} + + +void CFlyingMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->movetype = MOVETYPE_STEP; + ClearBits( pev->flags, FL_ONGROUND ); + pev->angles.z = 0; + pev->angles.x = 0; + CBaseMonster::Killed( pevAttacker, iGib ); +} + + +void CFlyingMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case FLYING_AE_FLAP: + m_flightSpeed = 400; + break; + + case FLYING_AE_FLAPSOUND: + if ( m_pFlapSound ) + EMIT_SOUND( edict(), CHAN_BODY, m_pFlapSound, 1, ATTN_NORM ); + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + + +void CFlyingMonster :: Move( float flInterval ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + m_flGroundSpeed = m_flightSpeed; + CBaseMonster::Move( flInterval ); +} + + +BOOL CFlyingMonster:: ShouldAdvanceRoute( float flWaypointDist ) +{ + // Get true 3D distance to the goal so we actually reach the correct height + if ( m_Route[ m_iRouteIndex ].iType & bits_MF_IS_GOAL ) + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length(); + + if ( flWaypointDist <= 64 + (m_flGroundSpeed * gpGlobals->frametime) ) + return TRUE; + + return FALSE; +} + + +void CFlyingMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + { + if ( gpGlobals->time - m_stopTime > 1.0 ) + { + if ( m_IdealActivity != m_movementActivity ) + { + m_IdealActivity = m_movementActivity; + m_flGroundSpeed = m_flightSpeed = 200; + } + } + Vector vecMove = pev->origin + (( vecDir + (m_vecTravel * m_momentum) ).Normalize() * (m_flGroundSpeed * flInterval)); + + if ( m_IdealActivity != m_movementActivity ) + { + m_flightSpeed = UTIL_Approach( 100, m_flightSpeed, 75 * gpGlobals->frametime ); + if ( m_flightSpeed < 100 ) + m_stopTime = gpGlobals->time; + } + else + m_flightSpeed = UTIL_Approach( 20, m_flightSpeed, 300 * gpGlobals->frametime ); + + if ( CheckLocalMove ( pev->origin, vecMove, pTargetEnt, NULL ) ) + { + m_vecTravel = (vecMove - pev->origin); + m_vecTravel = m_vecTravel.Normalize(); + UTIL_MoveToOrigin(ENT(pev), vecMove, (m_flGroundSpeed * flInterval), MOVE_STRAFE); + } + else + { + m_IdealActivity = GetStoppedActivity(); + m_stopTime = gpGlobals->time; + m_vecTravel = g_vecZero; + } + } + else + CBaseMonster::MoveExecute( pTargetEnt, vecDir, flInterval ); +} + + +float CFlyingMonster::CeilingZ( const Vector &position ) +{ + TraceResult tr; + + Vector minUp = position; + Vector maxUp = position; + maxUp.z += 4096.0; + + UTIL_TraceLine(position, maxUp, ignore_monsters, NULL, &tr); + if (tr.flFraction != 1.0) + maxUp.z = tr.vecEndPos.z; + + if ((pev->flags) & FL_SWIM) + { + return UTIL_WaterLevel( position, minUp.z, maxUp.z ); + } + return maxUp.z; +} + +BOOL CFlyingMonster::ProbeZ( const Vector &position, const Vector &probe, float *pFraction) +{ + int conPosition = UTIL_PointContents(position); + if ( (((pev->flags) & FL_SWIM) == FL_SWIM) ^ (conPosition == CONTENTS_WATER)) + { + // SWIMING & !WATER + // or FLYING & WATER + // + *pFraction = 0.0; + return TRUE; // We hit a water boundary because we are where we don't belong. + } + int conProbe = UTIL_PointContents(probe); + if (conProbe == conPosition) + { + // The probe is either entirely inside the water (for fish) or entirely + // outside the water (for birds). + // + *pFraction = 1.0; + return FALSE; + } + + Vector ProbeUnit = (probe-position).Normalize(); + float ProbeLength = (probe-position).Length(); + float maxProbeLength = ProbeLength; + float minProbeLength = 0; + + float diff = maxProbeLength - minProbeLength; + while (diff > 1.0) + { + float midProbeLength = minProbeLength + diff/2.0; + Vector midProbeVec = midProbeLength * ProbeUnit; + if (UTIL_PointContents(position+midProbeVec) == conPosition) + { + minProbeLength = midProbeLength; + } + else + { + maxProbeLength = midProbeLength; + } + diff = maxProbeLength - minProbeLength; + } + *pFraction = minProbeLength/ProbeLength; + + return TRUE; +} + +float CFlyingMonster::FloorZ( const Vector &position ) +{ + TraceResult tr; + + Vector down = position; + down.z -= 2048; + + UTIL_TraceLine( position, down, ignore_monsters, NULL, &tr ); + + if ( tr.flFraction != 1.0 ) + return tr.vecEndPos.z; + + return down.z; +} + diff --git a/dlls/flyingmonster.h b/dlls/flyingmonster.h new file mode 100644 index 0000000..752f56e --- /dev/null +++ b/dlls/flyingmonster.h @@ -0,0 +1,53 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +// Base class for flying monsters. This overrides the movement test & execution code from CBaseMonster + +#ifndef FLYINGMONSTER_H +#define FLYINGMONSTER_H + +class CFlyingMonster : public CBaseMonster +{ +public: + int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist );// check validity of a straight move through space + BOOL FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ); + Activity GetStoppedActivity( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + void Stop( void ); + float ChangeYaw( int speed ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + void Move( float flInterval = 0.1 ); + BOOL ShouldAdvanceRoute( float flWaypointDist ); + + inline void SetFlyingMomentum( float momentum ) { m_momentum = momentum; } + inline void SetFlyingFlapSound( const char *pFlapSound ) { m_pFlapSound = pFlapSound; } + inline void SetFlyingSpeed( float speed ) { m_flightSpeed = speed; } + float CeilingZ( const Vector &position ); + float FloorZ( const Vector &position ); + BOOL ProbeZ( const Vector &position, const Vector &probe, float *pFraction ); + + + // UNDONE: Save/restore this stuff!!! +protected: + Vector m_vecTravel; // Current direction + float m_flightSpeed; // Current flight speed (decays when not flapping or gliding) + float m_stopTime; // Last time we stopped (to avoid switching states too soon) + float m_momentum; // Weight for desired vs. momentum velocity + const char *m_pFlapSound; +}; + + +#endif //FLYINGMONSTER_H + diff --git a/dlls/func_break.cpp b/dlls/func_break.cpp index 1e48689..096bde7 100644 --- a/dlls/func_break.cpp +++ b/dlls/func_break.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/func_break.h b/dlls/func_break.h index 7591675..2441f75 100644 --- a/dlls/func_break.h +++ b/dlls/func_break.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/func_tank.cpp b/dlls/func_tank.cpp index 085db64..459d21a 100644 --- a/dlls/func_tank.cpp +++ b/dlls/func_tank.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/game.cpp b/dlls/game.cpp index 67e3329..b4b6da7 100644 --- a/dlls/game.cpp +++ b/dlls/game.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/game.h b/dlls/game.h index 31f4e34..7bd9dce 100644 --- a/dlls/game.h +++ b/dlls/game.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/gamerules.cpp b/dlls/gamerules.cpp index a9b4831..34bb491 100644 --- a/dlls/gamerules.cpp +++ b/dlls/gamerules.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/gamerules.h b/dlls/gamerules.h index 9d97f87..2e85304 100644 --- a/dlls/gamerules.h +++ b/dlls/gamerules.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/gargantua.cpp b/dlls/gargantua.cpp new file mode 100644 index 0000000..1c23d43 --- /dev/null +++ b/dlls/gargantua.cpp @@ -0,0 +1,1368 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef OEM_BUILD + +//========================================================= +// Gargantua +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "schedule.h" +#include "customentity.h" +#include "weapons.h" +#include "effects.h" +#include "soundent.h" +#include "decals.h" +#include "explode.h" +#include "func_break.h" + +//========================================================= +// Gargantua Monster +//========================================================= +const float GARG_ATTACKDIST = 80.0; + +// Garg animation events +#define GARG_AE_SLASH_LEFT 1 +//#define GARG_AE_BEAM_ATTACK_RIGHT 2 // No longer used +#define GARG_AE_LEFT_FOOT 3 +#define GARG_AE_RIGHT_FOOT 4 +#define GARG_AE_STOMP 5 +#define GARG_AE_BREATHE 6 + + +// Gargantua is immune to any damage but this +#define GARG_DAMAGE (DMG_ENERGYBEAM|DMG_CRUSH|DMG_MORTAR|DMG_BLAST) +#define GARG_EYE_SPRITE_NAME "sprites/gargeye1.spr" +#define GARG_BEAM_SPRITE_NAME "sprites/xbeam3.spr" +#define GARG_BEAM_SPRITE2 "sprites/xbeam3.spr" +#define GARG_STOMP_SPRITE_NAME "sprites/gargeye1.spr" +#define GARG_STOMP_BUZZ_SOUND "weapons/mine_charge.wav" +#define GARG_FLAME_LENGTH 330 +#define GARG_GIB_MODEL "models/metalplategibs.mdl" + +#define ATTN_GARG (ATTN_NORM) + +#define STOMP_SPRITE_COUNT 10 + +int gStompSprite = 0, gGargGibModel = 0; +void SpawnExplosion( Vector center, float randomRange, float time, int magnitude ); + +class CSmoker; + +// Spiral Effect +class CSpiral : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); + int ObjectCaps( void ) { return FCAP_DONT_SAVE; } + static CSpiral *Create( const Vector &origin, float height, float radius, float duration ); +}; +LINK_ENTITY_TO_CLASS( streak_spiral, CSpiral ); + + +class CStomp : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); + static CStomp *StompCreate( const Vector &origin, const Vector &end, float speed ); + +private: +// UNDONE: re-use this sprite list instead of creating new ones all the time +// CSprite *m_pSprites[ STOMP_SPRITE_COUNT ]; +}; + +LINK_ENTITY_TO_CLASS( garg_stomp, CStomp ); +CStomp *CStomp::StompCreate( const Vector &origin, const Vector &end, float speed ) +{ + CStomp *pStomp = GetClassPtr( (CStomp *)NULL ); + + pStomp->pev->origin = origin; + Vector dir = (end - origin); + pStomp->pev->scale = dir.Length(); + pStomp->pev->movedir = dir.Normalize(); + pStomp->pev->speed = speed; + pStomp->Spawn(); + + return pStomp; +} + +void CStomp::Spawn( void ) +{ + pev->nextthink = gpGlobals->time; + pev->classname = MAKE_STRING("garg_stomp"); + pev->dmgtime = gpGlobals->time; + + pev->framerate = 30; + pev->model = MAKE_STRING(GARG_STOMP_SPRITE_NAME); + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; + EMIT_SOUND_DYN( edict(), CHAN_BODY, GARG_STOMP_BUZZ_SOUND, 1, ATTN_NORM, 0, PITCH_NORM * 0.55); +} + + +#define STOMP_INTERVAL 0.025 + +void CStomp::Think( void ) +{ + TraceResult tr; + + pev->nextthink = gpGlobals->time + 0.1; + + // Do damage for this frame + Vector vecStart = pev->origin; + vecStart.z += 30; + Vector vecEnd = vecStart + (pev->movedir * pev->speed * gpGlobals->frametime); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit && tr.pHit != pev->owner ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + entvars_t *pevOwner = pev; + if ( pev->owner ) + pevOwner = VARS(pev->owner); + + if ( pEntity ) + pEntity->TakeDamage( pev, pevOwner, gSkillData.gargantuaDmgStomp, DMG_SONIC ); + } + + // Accelerate the effect + pev->speed = pev->speed + (gpGlobals->frametime) * pev->framerate; + pev->framerate = pev->framerate + (gpGlobals->frametime) * 1500; + + // Move and spawn trails + while ( gpGlobals->time - pev->dmgtime > STOMP_INTERVAL ) + { + pev->origin = pev->origin + pev->movedir * pev->speed * STOMP_INTERVAL; + for ( int i = 0; i < 2; i++ ) + { + CSprite *pSprite = CSprite::SpriteCreate( GARG_STOMP_SPRITE_NAME, pev->origin, TRUE ); + if ( pSprite ) + { + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,500), ignore_monsters, edict(), &tr ); + pSprite->pev->origin = tr.vecEndPos; + pSprite->pev->velocity = Vector(RANDOM_FLOAT(-200,200),RANDOM_FLOAT(-200,200),175); + // pSprite->AnimateAndDie( RANDOM_FLOAT( 8.0, 12.0 ) ); + pSprite->pev->nextthink = gpGlobals->time + 0.3; + pSprite->SetThink( SUB_Remove ); + pSprite->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxFadeFast ); + } + } + pev->dmgtime += STOMP_INTERVAL; + // Scale has the "life" of this effect + pev->scale -= STOMP_INTERVAL * pev->speed; + if ( pev->scale <= 0 ) + { + // Life has run out + UTIL_Remove(this); + STOP_SOUND( edict(), CHAN_BODY, GARG_STOMP_BUZZ_SOUND ); + } + + } +} + + +void StreakSplash( const Vector &origin, const Vector &direction, int color, int count, int speed, int velocityRange ) +{ + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, origin ); + WRITE_BYTE( TE_STREAK_SPLASH ); + WRITE_COORD( origin.x ); // origin + WRITE_COORD( origin.y ); + WRITE_COORD( origin.z ); + WRITE_COORD( direction.x ); // direction + WRITE_COORD( direction.y ); + WRITE_COORD( direction.z ); + WRITE_BYTE( color ); // Streak color 6 + WRITE_SHORT( count ); // count + WRITE_SHORT( speed ); + WRITE_SHORT( velocityRange ); // Random velocity modifier + MESSAGE_END(); +} + + +class CGargantua : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + BOOL CheckMeleeAttack1( float flDot, float flDist ); // Swipe + BOOL CheckMeleeAttack2( float flDot, float flDist ); // Flames + BOOL CheckRangeAttack1( float flDot, float flDist ); // Stomp attack + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -80, -80, 0 ); + pev->absmax = pev->origin + Vector( 80, 80, 214 ); + } + + Schedule_t *GetScheduleOfType( int Type ); + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + + void PrescheduleThink( void ); + + void Killed( entvars_t *pevAttacker, int iGib ); + void DeathEffect( void ); + + void EyeOff( void ); + void EyeOn( int level ); + void EyeUpdate( void ); + void Leap( void ); + void StompAttack( void ); + void FlameCreate( void ); + void FlameUpdate( void ); + void FlameControls( float angleX, float angleY ); + void FlameDestroy( void ); + inline BOOL FlameIsOn( void ) { return m_pFlame[0] != NULL; } + + void FlameDamage( Vector vecStart, Vector vecEnd, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CUSTOM_SCHEDULES; + +private: + static const char *pAttackHitSounds[]; + static const char *pBeamAttackSounds[]; + static const char *pAttackMissSounds[]; + static const char *pRicSounds[]; + static const char *pFootSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pAttackSounds[]; + static const char *pStompSounds[]; + static const char *pBreatheSounds[]; + + CBaseEntity* GargantuaCheckTraceHullAttack(float flDist, int iDamage, int iDmgType); + + CSprite *m_pEyeGlow; // Glow around the eyes + CBeam *m_pFlame[4]; // Flame beams + + int m_eyeBrightness; // Brightness target + float m_seeTime; // Time to attack (when I see the enemy, I set this) + float m_flameTime; // Time of next flame attack + float m_painSoundTime; // Time of next pain sound + float m_streakTime; // streak timer (don't send too many) + float m_flameX; // Flame thrower aim + float m_flameY; +}; + +LINK_ENTITY_TO_CLASS( monster_gargantua, CGargantua ); + +TYPEDESCRIPTION CGargantua::m_SaveData[] = +{ + DEFINE_FIELD( CGargantua, m_pEyeGlow, FIELD_CLASSPTR ), + DEFINE_FIELD( CGargantua, m_eyeBrightness, FIELD_INTEGER ), + DEFINE_FIELD( CGargantua, m_seeTime, FIELD_TIME ), + DEFINE_FIELD( CGargantua, m_flameTime, FIELD_TIME ), + DEFINE_FIELD( CGargantua, m_streakTime, FIELD_TIME ), + DEFINE_FIELD( CGargantua, m_painSoundTime, FIELD_TIME ), + DEFINE_ARRAY( CGargantua, m_pFlame, FIELD_CLASSPTR, 4 ), + DEFINE_FIELD( CGargantua, m_flameX, FIELD_FLOAT ), + DEFINE_FIELD( CGargantua, m_flameY, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CGargantua, CBaseMonster ); + +const char *CGargantua::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CGargantua::pBeamAttackSounds[] = +{ + "garg/gar_flameoff1.wav", + "garg/gar_flameon1.wav", + "garg/gar_flamerun1.wav", +}; + + +const char *CGargantua::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CGargantua::pRicSounds[] = +{ +#if 0 + "weapons/ric1.wav", + "weapons/ric2.wav", + "weapons/ric3.wav", + "weapons/ric4.wav", + "weapons/ric5.wav", +#else + "debris/metal4.wav", + "debris/metal6.wav", + "weapons/ric4.wav", + "weapons/ric5.wav", +#endif +}; + +const char *CGargantua::pFootSounds[] = +{ + "garg/gar_step1.wav", + "garg/gar_step2.wav", +}; + + +const char *CGargantua::pIdleSounds[] = +{ + "garg/gar_idle1.wav", + "garg/gar_idle2.wav", + "garg/gar_idle3.wav", + "garg/gar_idle4.wav", + "garg/gar_idle5.wav", +}; + + +const char *CGargantua::pAttackSounds[] = +{ + "garg/gar_attack1.wav", + "garg/gar_attack2.wav", + "garg/gar_attack3.wav", +}; + +const char *CGargantua::pAlertSounds[] = +{ + "garg/gar_alert1.wav", + "garg/gar_alert2.wav", + "garg/gar_alert3.wav", +}; + +const char *CGargantua::pPainSounds[] = +{ + "garg/gar_pain1.wav", + "garg/gar_pain2.wav", + "garg/gar_pain3.wav", +}; + +const char *CGargantua::pStompSounds[] = +{ + "garg/gar_stomp1.wav", +}; + +const char *CGargantua::pBreatheSounds[] = +{ + "garg/gar_breathe1.wav", + "garg/gar_breathe2.wav", + "garg/gar_breathe3.wav", +}; +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +#if 0 +enum +{ + SCHED_ = LAST_COMMON_SCHEDULE + 1, +}; +#endif + +enum +{ + TASK_SOUND_ATTACK = LAST_COMMON_TASK + 1, + TASK_FLAME_SWEEP, +}; + +Task_t tlGargFlame[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SOUND_ATTACK, (float)0 }, + // { TASK_PLAY_SEQUENCE, (float)ACT_SIGNAL1 }, + { TASK_SET_ACTIVITY, (float)ACT_MELEE_ATTACK2 }, + { TASK_FLAME_SWEEP, (float)4.5 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slGargFlame[] = +{ + { + tlGargFlame, + ARRAYSIZE ( tlGargFlame ), + 0, + 0, + "GargFlame" + }, +}; + + +// primary melee attack +Task_t tlGargSwipe[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MELEE_ATTACK1, (float)0 }, +}; + +Schedule_t slGargSwipe[] = +{ + { + tlGargSwipe, + ARRAYSIZE ( tlGargSwipe ), + bits_COND_CAN_MELEE_ATTACK2, + 0, + "GargSwipe" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CGargantua ) +{ + slGargFlame, + slGargSwipe, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CGargantua, CBaseMonster ); + + +void CGargantua::EyeOn( int level ) +{ + m_eyeBrightness = level; +} + + +void CGargantua::EyeOff( void ) +{ + m_eyeBrightness = 0; +} + + +void CGargantua::EyeUpdate( void ) +{ + if ( m_pEyeGlow ) + { + m_pEyeGlow->pev->renderamt = UTIL_Approach( m_eyeBrightness, m_pEyeGlow->pev->renderamt, 26 ); + if ( m_pEyeGlow->pev->renderamt == 0 ) + m_pEyeGlow->pev->effects |= EF_NODRAW; + else + m_pEyeGlow->pev->effects &= ~EF_NODRAW; + UTIL_SetOrigin( m_pEyeGlow->pev, pev->origin ); + } +} + + +void CGargantua::StompAttack( void ) +{ + TraceResult trace; + + UTIL_MakeVectors( pev->angles ); + Vector vecStart = pev->origin + Vector(0,0,60) + 35 * gpGlobals->v_forward; + Vector vecAim = ShootAtEnemy( vecStart ); + Vector vecEnd = (vecAim * 1024) + vecStart; + + UTIL_TraceLine( vecStart, vecEnd, ignore_monsters, edict(), &trace ); + CStomp::StompCreate( vecStart, trace.vecEndPos, 0 ); + UTIL_ScreenShake( pev->origin, 12.0, 100.0, 2.0, 1000 ); + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pStompSounds[ RANDOM_LONG(0,ARRAYSIZE(pStompSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM + RANDOM_LONG(-10,10) ); + + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,20), ignore_monsters, edict(), &trace ); + if ( trace.flFraction < 1.0 ) + UTIL_DecalTrace( &trace, DECAL_GARGSTOMP1 ); +} + + +void CGargantua :: FlameCreate( void ) +{ + int i; + Vector posGun, angleGun; + TraceResult trace; + + UTIL_MakeVectors( pev->angles ); + + for ( i = 0; i < 4; i++ ) + { + if ( i < 2 ) + m_pFlame[i] = CBeam::BeamCreate( GARG_BEAM_SPRITE_NAME, 240 ); + else + m_pFlame[i] = CBeam::BeamCreate( GARG_BEAM_SPRITE2, 140 ); + if ( m_pFlame[i] ) + { + int attach = i%2; + // attachment is 0 based in GetAttachment + GetAttachment( attach+1, posGun, angleGun ); + + Vector vecEnd = (gpGlobals->v_forward * GARG_FLAME_LENGTH) + posGun; + UTIL_TraceLine( posGun, vecEnd, dont_ignore_monsters, edict(), &trace ); + + m_pFlame[i]->PointEntInit( trace.vecEndPos, entindex() ); + if ( i < 2 ) + m_pFlame[i]->SetColor( 255, 130, 90 ); + else + m_pFlame[i]->SetColor( 0, 120, 255 ); + m_pFlame[i]->SetBrightness( 190 ); + m_pFlame[i]->SetFlags( BEAM_FSHADEIN ); + m_pFlame[i]->SetScrollRate( 20 ); + // attachment is 1 based in SetEndAttachment + m_pFlame[i]->SetEndAttachment( attach + 2 ); + CSoundEnt::InsertSound( bits_SOUND_COMBAT, posGun, 384, 0.3 ); + } + } + EMIT_SOUND_DYN ( edict(), CHAN_BODY, pBeamAttackSounds[ 1 ], 1.0, ATTN_NORM, 0, PITCH_NORM ); + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pBeamAttackSounds[ 2 ], 1.0, ATTN_NORM, 0, PITCH_NORM ); +} + + +void CGargantua :: FlameControls( float angleX, float angleY ) +{ + if ( angleY < -180 ) + angleY += 360; + else if ( angleY > 180 ) + angleY -= 360; + + if ( angleY < -45 ) + angleY = -45; + else if ( angleY > 45 ) + angleY = 45; + + m_flameX = UTIL_ApproachAngle( angleX, m_flameX, 4 ); + m_flameY = UTIL_ApproachAngle( angleY, m_flameY, 8 ); + SetBoneController( 0, m_flameY ); + SetBoneController( 1, m_flameX ); +} + + +void CGargantua :: FlameUpdate( void ) +{ + int i; + static float offset[2] = { 60, -60 }; + TraceResult trace; + Vector vecStart, angleGun; + BOOL streaks = FALSE; + + for ( i = 0; i < 2; i++ ) + { + if ( m_pFlame[i] ) + { + Vector vecAim = pev->angles; + vecAim.x += m_flameX; + vecAim.y += m_flameY; + + UTIL_MakeVectors( vecAim ); + + GetAttachment( i+1, vecStart, angleGun ); + Vector vecEnd = vecStart + (gpGlobals->v_forward * GARG_FLAME_LENGTH); // - offset[i] * gpGlobals->v_right; + + UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, edict(), &trace ); + + m_pFlame[i]->SetStartPos( trace.vecEndPos ); + m_pFlame[i+2]->SetStartPos( (vecStart * 0.6) + (trace.vecEndPos * 0.4) ); + + if ( trace.flFraction != 1.0 && gpGlobals->time > m_streakTime ) + { + StreakSplash( trace.vecEndPos, trace.vecPlaneNormal, 6, 20, 50, 400 ); + streaks = TRUE; + UTIL_DecalTrace( &trace, DECAL_SMALLSCORCH1 + RANDOM_LONG(0,2) ); + } + // RadiusDamage( trace.vecEndPos, pev, pev, gSkillData.gargantuaDmgFire, CLASS_ALIEN_MONSTER, DMG_BURN ); + FlameDamage( vecStart, trace.vecEndPos, pev, pev, gSkillData.gargantuaDmgFire, CLASS_ALIEN_MONSTER, DMG_BURN ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 * (i + 2) ); // entity, attachment + WRITE_COORD( vecStart.x ); // origin + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( RANDOM_FLOAT( 32, 48 ) ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 255 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 2 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + } + } + if ( streaks ) + m_streakTime = gpGlobals->time; +} + + + +void CGargantua :: FlameDamage( Vector vecStart, Vector vecEnd, entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int iClassIgnore, int bitsDamageType ) +{ + CBaseEntity *pEntity = NULL; + TraceResult tr; + float flAdjustedDamage; + Vector vecSpot; + + Vector vecMid = (vecStart + vecEnd) * 0.5; + + float searchRadius = (vecStart - vecMid).Length(); + + Vector vecAim = (vecEnd - vecStart).Normalize( ); + + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, vecMid, searchRadius )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + // UNDONE: this should check a damage mask, not an ignore + if ( iClassIgnore != CLASS_NONE && pEntity->Classify() == iClassIgnore ) + {// houndeyes don't hurt other houndeyes with their attack + continue; + } + + vecSpot = pEntity->BodyTarget( vecMid ); + + float dist = DotProduct( vecAim, vecSpot - vecMid ); + if (dist > searchRadius) + dist = searchRadius; + else if (dist < -searchRadius) + dist = searchRadius; + + Vector vecSrc = vecMid + dist * vecAim; + + UTIL_TraceLine ( vecSrc, vecSpot, dont_ignore_monsters, ENT(pev), &tr ); + + if ( tr.flFraction == 1.0 || tr.pHit == pEntity->edict() ) + {// the explosion can 'see' this entity, so hurt them! + // decrease damage for an ent that's farther from the flame. + dist = ( vecSrc - tr.vecEndPos ).Length(); + + if (dist > 64) + { + flAdjustedDamage = flDamage - (dist - 64) * 0.4; + if (flAdjustedDamage <= 0) + continue; + } + else + { + flAdjustedDamage = flDamage; + } + + // ALERT( at_console, "hit %s\n", STRING( pEntity->pev->classname ) ); + if (tr.flFraction != 1.0) + { + ClearMultiDamage( ); + pEntity->TraceAttack( pevInflictor, flAdjustedDamage, (tr.vecEndPos - vecSrc).Normalize( ), &tr, bitsDamageType ); + ApplyMultiDamage( pevInflictor, pevAttacker ); + } + else + { + pEntity->TakeDamage ( pevInflictor, pevAttacker, flAdjustedDamage, bitsDamageType ); + } + } + } + } +} + + +void CGargantua :: FlameDestroy( void ) +{ + int i; + + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pBeamAttackSounds[ 0 ], 1.0, ATTN_NORM, 0, PITCH_NORM ); + for ( i = 0; i < 4; i++ ) + { + if ( m_pFlame[i] ) + { + UTIL_Remove( m_pFlame[i] ); + m_pFlame[i] = NULL; + } + } +} + + +void CGargantua :: PrescheduleThink( void ) +{ + if ( !HasConditions( bits_COND_SEE_ENEMY ) ) + { + m_seeTime = gpGlobals->time + 5; + EyeOff(); + } + else + EyeOn( 200 ); + + EyeUpdate(); +} + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGargantua :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CGargantua :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 60; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 180; + break; + case ACT_WALK: + case ACT_RUN: + ys = 60; + break; + + default: + ys = 60; + break; + } + + pev->yaw_speed = ys; +} + + +//========================================================= +// Spawn +//========================================================= +void CGargantua :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/garg.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = gSkillData.gargantuaHealth; + //pev->view_ofs = Vector ( 0, 0, 96 );// taken from mdl file + m_flFieldOfView = -0.2;// width of forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); + + m_pEyeGlow = CSprite::SpriteCreate( GARG_EYE_SPRITE_NAME, pev->origin, FALSE ); + m_pEyeGlow->SetTransparency( kRenderGlow, 255, 255, 255, 0, kRenderFxNoDissipation ); + m_pEyeGlow->SetAttachment( edict(), 1 ); + EyeOff(); + m_seeTime = gpGlobals->time + 5; + m_flameTime = gpGlobals->time + 2; +} + + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CGargantua :: Precache() +{ + int i; + + PRECACHE_MODEL("models/garg.mdl"); + PRECACHE_MODEL( GARG_EYE_SPRITE_NAME ); + PRECACHE_MODEL( GARG_BEAM_SPRITE_NAME ); + PRECACHE_MODEL( GARG_BEAM_SPRITE2 ); + gStompSprite = PRECACHE_MODEL( GARG_STOMP_SPRITE_NAME ); + gGargGibModel = PRECACHE_MODEL( GARG_GIB_MODEL ); + PRECACHE_SOUND( GARG_STOMP_BUZZ_SOUND ); + + for ( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackHitSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pBeamAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pBeamAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackMissSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pRicSounds ); i++ ) + PRECACHE_SOUND((char *)pRicSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pFootSounds ); i++ ) + PRECACHE_SOUND((char *)pFootSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pIdleSounds ); i++ ) + PRECACHE_SOUND((char *)pIdleSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pStompSounds ); i++ ) + PRECACHE_SOUND((char *)pStompSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pBreatheSounds ); i++ ) + PRECACHE_SOUND((char *)pBreatheSounds[i]); +} + + +void CGargantua::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + ALERT( at_aiconsole, "CGargantua::TraceAttack\n"); + + if ( !IsAlive() ) + { + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + return; + } + + // UNDONE: Hit group specific damage? + if ( bitsDamageType & (GARG_DAMAGE|DMG_BLAST) ) + { + if ( m_painSoundTime < gpGlobals->time ) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM ); + m_painSoundTime = gpGlobals->time + RANDOM_FLOAT( 2.5, 4 ); + } + } + + bitsDamageType &= GARG_DAMAGE; + + if ( bitsDamageType == 0) + { + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,100) < 20) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT(0.5,1.5) ); + pev->dmgtime = gpGlobals->time; +// if ( RANDOM_LONG(0,100) < 25 ) +// EMIT_SOUND_DYN( ENT(pev), CHAN_BODY, pRicSounds[ RANDOM_LONG(0,ARRAYSIZE(pRicSounds)-1) ], 1.0, ATTN_NORM, 0, PITCH_NORM ); + } + flDamage = 0; + } + + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + +} + + + +int CGargantua::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + ALERT( at_aiconsole, "CGargantua::TakeDamage\n"); + + if ( IsAlive() ) + { + if ( !(bitsDamageType & GARG_DAMAGE) ) + flDamage *= 0.01; + if ( bitsDamageType & DMG_BLAST ) + SetConditions( bits_COND_LIGHT_DAMAGE ); + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +void CGargantua::DeathEffect( void ) +{ + int i; + UTIL_MakeVectors(pev->angles); + Vector deathPos = pev->origin + gpGlobals->v_forward * 100; + + // Create a spiral of streaks + CSpiral::Create( deathPos, (pev->absmax.z - pev->absmin.z) * 0.6, 125, 1.5 ); + + Vector position = pev->origin; + position.z += 32; + for ( i = 0; i < 7; i+=2 ) + { + SpawnExplosion( position, 70, (i * 0.3), 60 + (i*20) ); + position.z += 15; + } + + CBaseEntity *pSmoker = CBaseEntity::Create( "env_smoker", pev->origin, g_vecZero, NULL ); + pSmoker->pev->health = 1; // 1 smoke balls + pSmoker->pev->scale = 46; // 4.6X normal size + pSmoker->pev->dmg = 0; // 0 radial distribution + pSmoker->pev->nextthink = gpGlobals->time + 2.5; // Start in 2.5 seconds +} + + +void CGargantua::Killed( entvars_t *pevAttacker, int iGib ) +{ + EyeOff(); + UTIL_Remove( m_pEyeGlow ); + m_pEyeGlow = NULL; + CBaseMonster::Killed( pevAttacker, GIB_NEVER ); +} + +//========================================================= +// CheckMeleeAttack1 +// Garg swipe attack +// +//========================================================= +BOOL CGargantua::CheckMeleeAttack1( float flDot, float flDist ) +{ +// ALERT(at_aiconsole, "CheckMelee(%f, %f)\n", flDot, flDist); + + if (flDot >= 0.7) + { + if (flDist <= GARG_ATTACKDIST) + return TRUE; + } + return FALSE; +} + + +// Flame thrower madness! +BOOL CGargantua::CheckMeleeAttack2( float flDot, float flDist ) +{ +// ALERT(at_aiconsole, "CheckMelee(%f, %f)\n", flDot, flDist); + + if ( gpGlobals->time > m_flameTime ) + { + if (flDot >= 0.8 && flDist > GARG_ATTACKDIST) + { + if ( flDist <= GARG_FLAME_LENGTH ) + return TRUE; + } + } + return FALSE; +} + + +//========================================================= +// CheckRangeAttack1 +// flDot is the cos of the angle of the cone within which +// the attack can occur. +//========================================================= +// +// Stomp attack +// +//========================================================= +BOOL CGargantua::CheckRangeAttack1( float flDot, float flDist ) +{ + if ( gpGlobals->time > m_seeTime ) + { + if (flDot >= 0.7 && flDist > GARG_ATTACKDIST) + { + return TRUE; + } + } + return FALSE; +} + + + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CGargantua::HandleAnimEvent(MonsterEvent_t *pEvent) +{ + switch( pEvent->event ) + { + case GARG_AE_SLASH_LEFT: + { + // HACKHACK!!! + CBaseEntity *pHurt = GargantuaCheckTraceHullAttack( GARG_ATTACKDIST + 10.0, gSkillData.gargantuaDmgSlash, DMG_SLASH ); + if (pHurt) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.x = -30; // pitch + pHurt->pev->punchangle.y = -30; // yaw + pHurt->pev->punchangle.z = 30; // roll + //UTIL_MakeVectors(pev->angles); // called by CheckTraceHullAttack + pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_right * 100; + } + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 50 + RANDOM_LONG(0,15) ); + } + else // Play a random attack miss sound + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 50 + RANDOM_LONG(0,15) ); + + Vector forward; + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + } + break; + + case GARG_AE_RIGHT_FOOT: + case GARG_AE_LEFT_FOOT: + UTIL_ScreenShake( pev->origin, 4.0, 3.0, 1.0, 750 ); + EMIT_SOUND_DYN ( edict(), CHAN_BODY, pFootSounds[ RANDOM_LONG(0,ARRAYSIZE(pFootSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM + RANDOM_LONG(-10,10) ); + break; + + case GARG_AE_STOMP: + StompAttack(); + m_seeTime = gpGlobals->time + 12; + break; + + case GARG_AE_BREATHE: + EMIT_SOUND_DYN ( edict(), CHAN_VOICE, pBreatheSounds[ RANDOM_LONG(0,ARRAYSIZE(pBreatheSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM + RANDOM_LONG(-10,10) ); + break; + + default: + CBaseMonster::HandleAnimEvent(pEvent); + break; + } +} + + +//========================================================= +// CheckTraceHullAttack - expects a length to trace, amount +// of damage to do, and damage type. Returns a pointer to +// the damaged entity in case the monster wishes to do +// other stuff to the victim (punchangle, etc) +// Used for many contact-range melee attacks. Bites, claws, etc. + +// Overridden for Gargantua because his swing starts lower as +// a percentage of his height (otherwise he swings over the +// players head) +//========================================================= +CBaseEntity* CGargantua::GargantuaCheckTraceHullAttack(float flDist, int iDamage, int iDmgType) +{ + TraceResult tr; + + UTIL_MakeVectors( pev->angles ); + Vector vecStart = pev->origin; + vecStart.z += 64; + Vector vecEnd = vecStart + (gpGlobals->v_forward * flDist) - (gpGlobals->v_up * flDist * 0.3); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if ( iDamage > 0 ) + { + pEntity->TakeDamage( pev, pev, iDamage, iDmgType ); + } + + return pEntity; + } + + return NULL; +} + + +Schedule_t *CGargantua::GetScheduleOfType( int Type ) +{ + // HACKHACK - turn off the flames if they are on and garg goes scripted / dead + if ( FlameIsOn() ) + FlameDestroy(); + + switch( Type ) + { + case SCHED_MELEE_ATTACK2: + return slGargFlame; + case SCHED_MELEE_ATTACK1: + return slGargSwipe; + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + + +void CGargantua::StartTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_FLAME_SWEEP: + FlameCreate(); + m_flWaitFinished = gpGlobals->time + pTask->flData; + m_flameTime = gpGlobals->time + 6; + m_flameX = 0; + m_flameY = 0; + break; + + case TASK_SOUND_ATTACK: + if ( RANDOM_LONG(0,100) < 30 ) + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 1.0, ATTN_GARG, 0, PITCH_NORM ); + TaskComplete(); + break; + + case TASK_DIE: + m_flWaitFinished = gpGlobals->time + 1.6; + DeathEffect(); + // FALL THROUGH + default: + CBaseMonster::StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CGargantua::RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_DIE: + if ( gpGlobals->time > m_flWaitFinished ) + { + pev->renderfx = kRenderFxExplode; + pev->rendercolor.x = 255; + pev->rendercolor.y = 0; + pev->rendercolor.z = 0; + StopAnimation(); + pev->nextthink = gpGlobals->time + 0.15; + SetThink( SUB_Remove ); + int i; + int parts = MODEL_FRAMES( gGargGibModel ); + for ( i = 0; i < 10; i++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( GARG_GIB_MODEL ); + + int bodyPart = 0; + if ( parts > 1 ) + bodyPart = RANDOM_LONG( 0, pev->body-1 ); + + pGib->pev->body = bodyPart; + pGib->m_bloodColor = BLOOD_COLOR_YELLOW; + pGib->m_material = matNone; + pGib->pev->origin = pev->origin; + pGib->pev->velocity = UTIL_RandomBloodVector() * RANDOM_FLOAT( 300, 500 ); + pGib->pev->nextthink = gpGlobals->time + 1.25; + pGib->SetThink( SUB_FadeOut ); + } + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + + // size + WRITE_COORD( 200 ); + WRITE_COORD( 200 ); + WRITE_COORD( 128 ); + + // velocity + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + + // randomization + WRITE_BYTE( 200 ); + + // Model + WRITE_SHORT( gGargGibModel ); //model id# + + // # of shards + WRITE_BYTE( 50 ); + + // duration + WRITE_BYTE( 20 );// 3.0 seconds + + // flags + + WRITE_BYTE( BREAK_FLESH ); + MESSAGE_END(); + + return; + } + else + CBaseMonster::RunTask(pTask); + break; + + case TASK_FLAME_SWEEP: + if ( gpGlobals->time > m_flWaitFinished ) + { + FlameDestroy(); + TaskComplete(); + FlameControls( 0, 0 ); + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + } + else + { + BOOL cancel = FALSE; + + Vector angles = g_vecZero; + + FlameUpdate(); + CBaseEntity *pEnemy = m_hEnemy; + if ( pEnemy ) + { + Vector org = pev->origin; + org.z += 64; + Vector dir = pEnemy->BodyTarget(org) - org; + angles = UTIL_VecToAngles( dir ); + angles.x = -angles.x; + angles.y -= pev->angles.y; + if ( dir.Length() > 400 ) + cancel = TRUE; + } + if ( fabs(angles.y) > 60 ) + cancel = TRUE; + + if ( cancel ) + { + m_flWaitFinished -= 0.5; + m_flameTime -= 0.5; + } + // FlameControls( angles.x + 2 * sin(gpGlobals->time*8), angles.y + 28 * sin(gpGlobals->time*8.5) ); + FlameControls( angles.x, angles.y ); + } + break; + + default: + CBaseMonster::RunTask( pTask ); + break; + } +} + + +class CSmoker : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); +}; + +LINK_ENTITY_TO_CLASS( env_smoker, CSmoker ); + +void CSmoker::Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + pev->nextthink = gpGlobals->time; + pev->solid = SOLID_NOT; + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + pev->effects |= EF_NODRAW; + pev->angles = g_vecZero; +} + + +void CSmoker::Think( void ) +{ + // lots of smoke + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -pev->dmg, pev->dmg )); + WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -pev->dmg, pev->dmg )); + WRITE_COORD( pev->origin.z); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(pev->scale, pev->scale * 1.1) ); + WRITE_BYTE( RANDOM_LONG(8,14) ); // framerate + MESSAGE_END(); + + pev->health--; + if ( pev->health > 0 ) + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(0.1, 0.2); + else + UTIL_Remove( this ); +} + + +void CSpiral::Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + pev->nextthink = gpGlobals->time; + pev->solid = SOLID_NOT; + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + pev->effects |= EF_NODRAW; + pev->angles = g_vecZero; +} + + +CSpiral *CSpiral::Create( const Vector &origin, float height, float radius, float duration ) +{ + if ( duration <= 0 ) + return NULL; + + CSpiral *pSpiral = GetClassPtr( (CSpiral *)NULL ); + pSpiral->Spawn(); + pSpiral->pev->dmgtime = pSpiral->pev->nextthink; + pSpiral->pev->origin = origin; + pSpiral->pev->scale = radius; + pSpiral->pev->dmg = height; + pSpiral->pev->speed = duration; + pSpiral->pev->health = 0; + pSpiral->pev->angles = g_vecZero; + + return pSpiral; +} + +#define SPIRAL_INTERVAL 0.1 //025 + +void CSpiral::Think( void ) +{ + float time = gpGlobals->time - pev->dmgtime; + + while ( time > SPIRAL_INTERVAL ) + { + Vector position = pev->origin; + Vector direction = Vector(0,0,1); + + float fraction = 1.0 / pev->speed; + + float radius = (pev->scale * pev->health) * fraction; + + position.z += (pev->health * pev->dmg) * fraction; + pev->angles.y = (pev->health * 360 * 8) * fraction; + UTIL_MakeVectors( pev->angles ); + position = position + gpGlobals->v_forward * radius; + direction = (direction + gpGlobals->v_forward).Normalize(); + + StreakSplash( position, Vector(0,0,1), RANDOM_LONG(8,11), 20, RANDOM_LONG(50,150), 400 ); + + // Jeez, how many counters should this take ? :) + pev->dmgtime += SPIRAL_INTERVAL; + pev->health += SPIRAL_INTERVAL; + time -= SPIRAL_INTERVAL; + } + + pev->nextthink = gpGlobals->time; + + if ( pev->health >= pev->speed ) + UTIL_Remove( this ); +} + + +// HACKHACK Cut and pasted from explode.cpp +void SpawnExplosion( Vector center, float randomRange, float time, int magnitude ) +{ + KeyValueData kvd; + char buf[128]; + + center.x += RANDOM_FLOAT( -randomRange, randomRange ); + center.y += RANDOM_FLOAT( -randomRange, randomRange ); + + CBaseEntity *pExplosion = CBaseEntity::Create( "env_explosion", center, g_vecZero, NULL ); + sprintf( buf, "%3d", magnitude ); + kvd.szKeyName = "iMagnitude"; + kvd.szValue = buf; + pExplosion->KeyValue( &kvd ); + pExplosion->pev->spawnflags |= SF_ENVEXPLOSION_NODAMAGE; + + pExplosion->Spawn(); + pExplosion->SetThink( CBaseEntity::SUB_CallUseToggle ); + pExplosion->pev->nextthink = gpGlobals->time + time; +} + + + +#endif \ No newline at end of file diff --git a/dlls/gauss.cpp b/dlls/gauss.cpp index 57e7564..d89a41c 100644 --- a/dlls/gauss.cpp +++ b/dlls/gauss.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -57,6 +57,10 @@ float CGauss::GetFullChargeTime( void ) return 4; } +#ifdef CLIENT_DLL +extern int g_irunninggausspred; +#endif + void CGauss::Spawn( ) { Precache( ); @@ -373,6 +377,11 @@ void CGauss::Fire( Vector vecOrigSrc, Vector vecDir, float flDamage ) pentIgnore = ENT( m_pPlayer->pev ); +#ifdef CLIENT_DLL + if ( m_fPrimaryFire == false ) + g_irunninggausspred = true; +#endif + // The main firing event is sent unreliably so it won't be delayed. PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usGaussFire, 0.0, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, flDamage, 0.0, 0, 0, m_fPrimaryFire ? 1 : 0, 0 ); diff --git a/dlls/genericmonster.cpp b/dlls/genericmonster.cpp new file mode 100644 index 0000000..8f40be6 --- /dev/null +++ b/dlls/genericmonster.cpp @@ -0,0 +1,140 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Generic Monster - purely for scripted sequence work. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +// For holograms, make them not solid so the player can walk through them +#define SF_GENERICMONSTER_NOTSOLID 4 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CGenericMonster : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int ISoundMask ( void ); +}; +LINK_ENTITY_TO_CLASS( monster_generic, CGenericMonster ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGenericMonster :: Classify ( void ) +{ + return CLASS_PLAYER_ALLY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CGenericMonster :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 90; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CGenericMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 0: + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// ISoundMask - generic monster can't hear. +//========================================================= +int CGenericMonster :: ISoundMask ( void ) +{ + return NULL; +} + +//========================================================= +// Spawn +//========================================================= +void CGenericMonster :: Spawn() +{ + Precache(); + + SET_MODEL( ENT(pev), STRING(pev->model) ); + +/* + if ( FStrEq( STRING(pev->model), "models/player.mdl" ) ) + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + else + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); +*/ + + if ( FStrEq( STRING(pev->model), "models/player.mdl" ) || FStrEq( STRING(pev->model), "models/holo.mdl" ) ) + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + else + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->health = 8; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); + + if ( pev->spawnflags & SF_GENERICMONSTER_NOTSOLID ) + { + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CGenericMonster :: Precache() +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= diff --git a/dlls/ggrenade.cpp b/dlls/ggrenade.cpp index 9c52a0f..bed9129 100644 --- a/dlls/ggrenade.cpp +++ b/dlls/ggrenade.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/globals.cpp b/dlls/globals.cpp index 2cfa7f3..be31ef9 100644 --- a/dlls/globals.cpp +++ b/dlls/globals.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/glock.cpp b/dlls/glock.cpp new file mode 100644 index 0000000..b0a04b9 --- /dev/null +++ b/dlls/glock.cpp @@ -0,0 +1,325 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + +enum glock_e { + GLOCK_IDLE1 = 0, + GLOCK_IDLE2, + GLOCK_IDLE3, + GLOCK_SHOOT, + GLOCK_SHOOT_EMPTY, + GLOCK_RELOAD, + GLOCK_RELOAD_NOT_EMPTY, + GLOCK_DRAW, + GLOCK_HOLSTER, + GLOCK_ADD_SILENCER +}; + +class CGlock : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 2; } + int GetItemInfo(ItemInfo *p); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + void GlockFire( float flSpread, float flCycleTime, BOOL fUseAutoAim ); + BOOL Deploy( void ); + void Reload( void ); + void WeaponIdle( void ); + +private: + int m_iShell; + + + unsigned short m_usFireGlock1; + unsigned short m_usFireGlock2; +}; +LINK_ENTITY_TO_CLASS( weapon_glock, CGlock ); +LINK_ENTITY_TO_CLASS( weapon_9mmhandgun, CGlock ); + + +void CGlock::Spawn( ) +{ + pev->classname = MAKE_STRING("weapon_9mmhandgun"); // hack to allow for old names + Precache( ); + m_iId = WEAPON_GLOCK; + SET_MODEL(ENT(pev), "models/w_9mmhandgun.mdl"); + + m_iDefaultAmmo = GLOCK_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CGlock::Precache( void ) +{ + PRECACHE_MODEL("models/v_9mmhandgun.mdl"); + PRECACHE_MODEL("models/w_9mmhandgun.mdl"); + PRECACHE_MODEL("models/p_9mmhandgun.mdl"); + + m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell + + PRECACHE_SOUND("items/9mmclip1.wav"); + PRECACHE_SOUND("items/9mmclip2.wav"); + + PRECACHE_SOUND ("weapons/pl_gun1.wav");//silenced handgun + PRECACHE_SOUND ("weapons/pl_gun2.wav");//silenced handgun + PRECACHE_SOUND ("weapons/pl_gun3.wav");//handgun + + m_usFireGlock1 = PRECACHE_EVENT( 1, "events/glock1.sc" ); + m_usFireGlock2 = PRECACHE_EVENT( 1, "events/glock2.sc" ); +} + +int CGlock::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "9mm"; + p->iMaxAmmo1 = _9MM_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = GLOCK_MAX_CLIP; + p->iSlot = 1; + p->iPosition = 0; + p->iFlags = 0; + p->iId = m_iId = WEAPON_GLOCK; + p->iWeight = GLOCK_WEIGHT; + + return 1; +} + +BOOL CGlock::Deploy( ) +{ + // pev->body = 1; + return DefaultDeploy( "models/v_9mmhandgun.mdl", "models/p_9mmhandgun.mdl", GLOCK_DRAW, "onehanded" ); +} + +void CGlock::SecondaryAttack( void ) +{ + GlockFire( 0.1, 0.2, FALSE ); +} + +void CGlock::PrimaryAttack( void ) +{ + GlockFire( 0.01, 0.3, TRUE ); +} + +void CGlock::GlockFire( float flSpread , float flCycleTime, BOOL fUseAutoAim ) +{ + if (m_iClip <= 0) + { + if (m_fFireOnEmpty) + { + PlayEmptySound(); + m_flNextPrimaryAttack = gpGlobals->time + 0.2; + } + + return; + } + + m_iClip--; + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + +#if defined ( OLD_WEAPONS ) + if (m_iClip != 0) + SendWeaponAnim( GLOCK_SHOOT ); + else + SendWeaponAnim( GLOCK_SHOOT_EMPTY ); +#endif + + if ( fUseAutoAim ) + { + PLAYBACK_EVENT_FULL( 0, m_pPlayer->edict(), m_usFireGlock1, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, ( m_iClip == 0 ) ? 1 : 0, 0 ); + } + else + { + PLAYBACK_EVENT_FULL( 0, m_pPlayer->edict(), m_usFireGlock2, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 0, 0, ( m_iClip == 0 ) ? 1 : 0, 0 ); + } + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + +#if defined ( OLD_WEAPONS ) + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + + Vector vecShellVelocity = m_pPlayer->pev->velocity + + gpGlobals->v_right * RANDOM_FLOAT(50,70) + + gpGlobals->v_up * RANDOM_FLOAT(100,150) + + gpGlobals->v_forward * 25; + EjectBrass ( pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_up * -12 + gpGlobals->v_forward * 32 + gpGlobals->v_right * 6 , vecShellVelocity, pev->angles.y, m_iShell, TE_BOUNCE_SHELL ); +#endif + + // silenced + if (pev->body == 1) + { + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = DIM_GUN_FLASH; +#if defined ( OLD_WEAPONS ) + switch(RANDOM_LONG(0,1)) + { + case 0: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/pl_gun1.wav", RANDOM_FLOAT(0.9, 1.0), ATTN_NORM); + break; + case 1: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/pl_gun2.wav", RANDOM_FLOAT(0.9, 1.0), ATTN_NORM); + break; + } +#endif + } + else + { + // non-silenced + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; +#if defined ( OLD_WEAPONS ) + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/pl_gun3.wav", RANDOM_FLOAT(0.92, 1.0), ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); +#endif + } + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming; + + if ( fUseAutoAim ) + { + vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + } + else + { + vecAiming = gpGlobals->v_forward; + } + + m_pPlayer->FireBullets( 1, vecSrc, vecAiming, Vector( flSpread, flSpread, flSpread ), 8192, BULLET_PLAYER_9MM, 0 ); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->time + flCycleTime; + + if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + +#if defined ( OLD_WEAPONS ) + m_pPlayer->pev->punchangle.x -= 2; +#endif +} + + +void CGlock::Reload( void ) +{ + int iResult; + + if (m_iClip == 0) + iResult = DefaultReload( 17, GLOCK_RELOAD, 1.5 ); + else + iResult = DefaultReload( 18, GLOCK_RELOAD_NOT_EMPTY, 1.5 ); + + if (iResult) + { + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + } +} + + + +void CGlock::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + // only idle if the slid isn't back + if (m_iClip != 0) + { + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.3 + 0 * 0.75) + { + iAnim = GLOCK_IDLE3; + m_flTimeWeaponIdle = gpGlobals->time + 49.0 / 16; + } + else if (flRand <= 0.6 + 0 * 0.875) + { + iAnim = GLOCK_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + 60.0 / 16.0; + } + else + { + iAnim = GLOCK_IDLE2; + m_flTimeWeaponIdle = gpGlobals->time + 40.0 / 16.0; + } + SendWeaponAnim( iAnim ); + } +} + + + + + + + + +class CGlockAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + SET_MODEL(ENT(pev), "models/w_9mmclip.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + PRECACHE_MODEL ("models/w_9mmclip.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_GLOCKCLIP_GIVE, "9mm", _9MM_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_glockclip, CGlockAmmo ); +LINK_ENTITY_TO_CLASS( ammo_9mmclip, CGlockAmmo ); + + + + + + + + + + + + + + + diff --git a/dlls/gman.cpp b/dlls/gman.cpp new file mode 100644 index 0000000..77a1bd8 --- /dev/null +++ b/dlls/gman.cpp @@ -0,0 +1,237 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// GMan - misunderstood servant of the people +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "weapons.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CGMan : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int ISoundMask ( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + void PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ); + + EHANDLE m_hPlayer; + EHANDLE m_hTalkTarget; + float m_flTalkTime; +}; +LINK_ENTITY_TO_CLASS( monster_gman, CGMan ); + + +TYPEDESCRIPTION CGMan::m_SaveData[] = +{ + DEFINE_FIELD( CGMan, m_hTalkTarget, FIELD_EHANDLE ), + DEFINE_FIELD( CGMan, m_flTalkTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CGMan, CBaseMonster ); + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGMan :: Classify ( void ) +{ + return CLASS_NONE; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CGMan :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 90; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CGMan :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 0: + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// ISoundMask - generic monster can't hear. +//========================================================= +int CGMan :: ISoundMask ( void ) +{ + return NULL; +} + +//========================================================= +// Spawn +//========================================================= +void CGMan :: Spawn() +{ + Precache(); + + SET_MODEL( ENT(pev), "models/gman.mdl" ); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = DONT_BLEED; + pev->health = 100; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CGMan :: Precache() +{ + PRECACHE_MODEL( "models/gman.mdl" ); +} + + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + +void CGMan :: StartTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_WAIT: + if (m_hPlayer == NULL) + { + m_hPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + } + break; + } + CBaseMonster::StartTask( pTask ); +} + +void CGMan :: RunTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_WAIT: + // look at who I'm talking to + if (m_flTalkTime > gpGlobals->time && m_hTalkTarget != NULL) + { + float yaw = VecToYaw(m_hTalkTarget->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + // turn towards vector + SetBoneController( 0, yaw ); + } + // look at player, but only if playing a "safe" idle animation + else if (m_hPlayer != NULL && pev->sequence == 0) + { + float yaw = VecToYaw(m_hPlayer->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + // turn towards vector + SetBoneController( 0, yaw ); + } + else + { + SetBoneController( 0, 0 ); + } + CBaseMonster::RunTask( pTask ); + break; + default: + SetBoneController( 0, 0 ); + CBaseMonster::RunTask( pTask ); + break; + } +} + + +//========================================================= +// Override all damage +//========================================================= +int CGMan :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + pev->health = pev->max_health / 2; // always trigger the 50% damage aitrigger + + if ( flDamage > 0 ) + { + SetConditions(bits_COND_LIGHT_DAMAGE); + } + + if ( flDamage >= 20 ) + { + SetConditions(bits_COND_HEAVY_DAMAGE); + } + return TRUE; +} + + +void CGMan::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + + +void CGMan::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) +{ + CBaseMonster::PlayScriptedSentence( pszSentence, duration, volume, attenuation, bConcurrent, pListener ); + + m_flTalkTime = gpGlobals->time + duration; + m_hTalkTarget = pListener; +} diff --git a/dlls/h_ai.cpp b/dlls/h_ai.cpp index edd03a0..f24a988 100644 --- a/dlls/h_ai.cpp +++ b/dlls/h_ai.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/h_battery.cpp b/dlls/h_battery.cpp index f44813c..3f2a9a2 100644 --- a/dlls/h_battery.cpp +++ b/dlls/h_battery.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/h_cine.cpp b/dlls/h_cine.cpp new file mode 100644 index 0000000..7f68192 --- /dev/null +++ b/dlls/h_cine.cpp @@ -0,0 +1,241 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== h_cine.cpp ======================================================== + + The Halflife hard coded "scripted sequence". + + I'm pretty sure all this code is obsolete + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "decals.h" + + +class CLegacyCineMonster : public CBaseMonster +{ +public: + void CineSpawn( char *szModel ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT CineThink( void ); + void Pain( void ); + void Die( void ); +}; + +class CCineScientist : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine-scientist.mdl"); } +}; +class CCine2Scientist : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine2-scientist.mdl"); } +}; +class CCinePanther : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine-panther.mdl"); } +}; + +class CCineBarney : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine-barney.mdl"); } +}; + +class CCine2HeavyWeapons : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine2_hvyweapons.mdl"); } +}; + +class CCine2Slave : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine2_slave.mdl"); } +}; + +class CCine3Scientist : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine3-scientist.mdl"); } +}; + +class CCine3Barney : public CLegacyCineMonster +{ +public: + void Spawn( void ) { CineSpawn("models/cine3-barney.mdl"); } +}; + +// +// ********** Scientist SPAWN ********** +// + +LINK_ENTITY_TO_CLASS( monster_cine_scientist, CCineScientist ); +LINK_ENTITY_TO_CLASS( monster_cine_panther, CCinePanther ); +LINK_ENTITY_TO_CLASS( monster_cine_barney, CCineBarney ); +LINK_ENTITY_TO_CLASS( monster_cine2_scientist, CCine2Scientist ); +LINK_ENTITY_TO_CLASS( monster_cine2_hvyweapons, CCine2HeavyWeapons ); +LINK_ENTITY_TO_CLASS( monster_cine2_slave, CCine2Slave ); +LINK_ENTITY_TO_CLASS( monster_cine3_scientist, CCine3Scientist ); +LINK_ENTITY_TO_CLASS( monster_cine3_barney, CCine3Barney ); + +// +// ********** Scientist SPAWN ********** +// + +void CLegacyCineMonster :: CineSpawn( char *szModel ) +{ + PRECACHE_MODEL(szModel); + SET_MODEL(ENT(pev), szModel); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 64)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + pev->effects = 0; + pev->health = 1; + pev->yaw_speed = 10; + + // ugly alpha hack, can't set ints from the bsp. + pev->sequence = (int)pev->impulse; + ResetSequenceInfo( ); + pev->framerate = 0.0; + + m_bloodColor = BLOOD_COLOR_RED; + + // if no targetname, start now + if ( FStringNull(pev->targetname) ) + { + SetThink( CineThink ); + pev->nextthink += 1.0; + } +} + + +// +// CineStart +// +void CLegacyCineMonster :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->animtime = 0; // reset the sequence + SetThink( CineThink ); + pev->nextthink = gpGlobals->time; +} + +// +// ********** Scientist DIE ********** +// +void CLegacyCineMonster :: Die( void ) +{ + SetThink( SUB_Remove ); +} + +// +// ********** Scientist PAIN ********** +// +void CLegacyCineMonster :: Pain( void ) +{ + EMIT_SOUND(ENT(pev), CHAN_VOICE, "player/pain3.wav", 1, ATTN_NORM); +} + +void CLegacyCineMonster :: CineThink( void ) +{ + // DBG_CheckMonsterData(pev); + + // Emit particles from origin (double check animator's placement of model) + // THIS is a test feature + //UTIL_ParticleEffect(pev->origin, g_vecZero, 255, 20); + + if (!pev->animtime) + ResetSequenceInfo( ); + + pev->nextthink = gpGlobals->time + 1.0; + + if (pev->spawnflags != 0 && m_fSequenceFinished) + { + Die(); + return; + } + + StudioFrameAdvance ( ); +} + +// +// cine_blood +// +// e3/prealpha only. +class CCineBlood : public CBaseEntity +{ +public: + void Spawn( void ); + void EXPORT BloodStart ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT BloodGush ( void ); +}; + +LINK_ENTITY_TO_CLASS( cine_blood, CCineBlood ); + + +void CCineBlood :: BloodGush ( void ) +{ + Vector vecSplatDir; + TraceResult tr; + pev->nextthink = gpGlobals->time + 0.1; + + UTIL_MakeVectors(pev->angles); + if ( pev->health-- < 0 ) + REMOVE_ENTITY(ENT(pev)); +// CHANGE_METHOD ( ENT(pev), em_think, SUB_Remove ); + + if ( RANDOM_FLOAT ( 0 , 1 ) < 0.7 )// larger chance of globs + { + UTIL_BloodDrips( pev->origin, UTIL_RandomBloodVector(), BLOOD_COLOR_RED, 10 ); + } + else// slim chance of geyser + { + UTIL_BloodStream( pev->origin, UTIL_RandomBloodVector(), BLOOD_COLOR_RED, RANDOM_LONG(50, 150) ); + } + + if ( RANDOM_FLOAT ( 0, 1 ) < 0.75 ) + {// decals the floor with blood. + vecSplatDir = Vector ( 0 , 0 , -1 ); + vecSplatDir = vecSplatDir + (RANDOM_FLOAT(-1,1) * 0.6 * gpGlobals->v_right) + (RANDOM_FLOAT(-1,1) * 0.6 * gpGlobals->v_forward);// randomize a bit + UTIL_TraceLine( pev->origin + Vector ( 0, 0 , 64) , pev->origin + vecSplatDir * 256, ignore_monsters, ENT(pev), &tr); + if ( tr.flFraction != 1.0 ) + { + // Decal with a bloodsplat + UTIL_BloodDecalTrace( &tr, BLOOD_COLOR_RED ); + } + } +} + +void CCineBlood :: BloodStart ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( BloodGush ); + pev->nextthink = gpGlobals->time;// now! +} + +void CCineBlood :: Spawn ( void ) +{ + pev->solid = SOLID_NOT; + SetUse ( BloodStart ); + pev->health = 20;//hacked health to count iterations +} + diff --git a/dlls/h_cycler.cpp b/dlls/h_cycler.cpp index 2f43d41..a306359 100644 --- a/dlls/h_cycler.cpp +++ b/dlls/h_cycler.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/h_export.cpp b/dlls/h_export.cpp index f29d0bf..96a57fd 100644 --- a/dlls/h_export.cpp +++ b/dlls/h_export.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/handgrenade.cpp b/dlls/handgrenade.cpp index 44c9998..007ad66 100644 --- a/dlls/handgrenade.cpp +++ b/dlls/handgrenade.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/hassassin.cpp b/dlls/hassassin.cpp new file mode 100644 index 0000000..da8f677 --- /dev/null +++ b/dlls/hassassin.cpp @@ -0,0 +1,1015 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +//========================================================= +// hassassin - Human assassin, fast and stealthy +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "squadmonster.h" +#include "weapons.h" +#include "soundent.h" +#include "game.h" + +extern DLL_GLOBAL int g_iSkillLevel; + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_ASSASSIN_EXPOSED = LAST_COMMON_SCHEDULE + 1,// cover was blown. + SCHED_ASSASSIN_JUMP, // fly through the air + SCHED_ASSASSIN_JUMP_ATTACK, // fly through the air and shoot + SCHED_ASSASSIN_JUMP_LAND, // hit and run away +}; + +//========================================================= +// monster-specific tasks +//========================================================= + +enum +{ + TASK_ASSASSIN_FALL_TO_GROUND = LAST_COMMON_TASK + 1, // falling and waiting to hit ground +}; + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define ASSASSIN_AE_SHOOT1 1 +#define ASSASSIN_AE_TOSS1 2 +#define ASSASSIN_AE_JUMP 3 + + +#define bits_MEMORY_BADJUMP (bits_MEMORY_CUSTOM1) + +class CHAssassin : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed ( void ); + int Classify ( void ); + int ISoundMask ( void); + void Shoot( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + Schedule_t* GetSchedule ( void ); + Schedule_t* GetScheduleOfType ( int Type ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); // jump + // BOOL CheckMeleeAttack2 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); // shoot + BOOL CheckRangeAttack2 ( float flDot, float flDist ); // throw grenade + void StartTask ( Task_t *pTask ); + void RunAI( void ); + void RunTask ( Task_t *pTask ); + void DeathSound ( void ); + void IdleSound ( void ); + CUSTOM_SCHEDULES; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + float m_flLastShot; + float m_flDiviation; + + float m_flNextJump; + Vector m_vecJumpVelocity; + + float m_flNextGrenadeCheck; + Vector m_vecTossVelocity; + BOOL m_fThrowGrenade; + + int m_iTargetRanderamt; + + int m_iFrustration; + + int m_iShell; +}; +LINK_ENTITY_TO_CLASS( monster_human_assassin, CHAssassin ); + + +TYPEDESCRIPTION CHAssassin::m_SaveData[] = +{ + DEFINE_FIELD( CHAssassin, m_flLastShot, FIELD_TIME ), + DEFINE_FIELD( CHAssassin, m_flDiviation, FIELD_FLOAT ), + + DEFINE_FIELD( CHAssassin, m_flNextJump, FIELD_TIME ), + DEFINE_FIELD( CHAssassin, m_vecJumpVelocity, FIELD_VECTOR ), + + DEFINE_FIELD( CHAssassin, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CHAssassin, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CHAssassin, m_fThrowGrenade, FIELD_BOOLEAN ), + + DEFINE_FIELD( CHAssassin, m_iTargetRanderamt, FIELD_INTEGER ), + DEFINE_FIELD( CHAssassin, m_iFrustration, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CHAssassin, CBaseMonster ); + + +//========================================================= +// DieSound +//========================================================= +void CHAssassin :: DeathSound ( void ) +{ +} + +//========================================================= +// IdleSound +//========================================================= +void CHAssassin :: IdleSound ( void ) +{ +} + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CHAssassin :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHAssassin :: Classify ( void ) +{ + return CLASS_HUMAN_MILITARY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CHAssassin :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 360; + break; + default: + ys = 360; + break; + } + + pev->yaw_speed = ys; +} + + +//========================================================= +// Shoot +//========================================================= +void CHAssassin :: Shoot ( void ) +{ + if (m_hEnemy == NULL) + { + return; + } + + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + if (m_flLastShot + 2 < gpGlobals->time) + { + m_flDiviation = 0.10; + } + else + { + m_flDiviation -= 0.01; + if (m_flDiviation < 0.02) + m_flDiviation = 0.02; + } + m_flLastShot = gpGlobals->time; + + UTIL_MakeVectors ( pev->angles ); + + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass ( pev->origin + gpGlobals->v_up * 32 + gpGlobals->v_forward * 12, vecShellVelocity, pev->angles.y, m_iShell, TE_BOUNCE_SHELL); + FireBullets(1, vecShootOrigin, vecShootDir, Vector( m_flDiviation, m_flDiviation, m_flDiviation ), 2048, BULLET_MONSTER_9MM ); // shoot +-8 degrees + + switch(RANDOM_LONG(0,1)) + { + case 0: + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/pl_gun1.wav", RANDOM_FLOAT(0.6, 0.8), ATTN_NORM); + break; + case 1: + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/pl_gun2.wav", RANDOM_FLOAT(0.6, 0.8), ATTN_NORM); + break; + } + + pev->effects |= EF_MUZZLEFLASH; + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); + + m_cAmmoLoaded--; +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CHAssassin :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case ASSASSIN_AE_SHOOT1: + Shoot( ); + break; + case ASSASSIN_AE_TOSS1: + { + UTIL_MakeVectors( pev->angles ); + CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector (0, 0, 32), m_vecTossVelocity, 2.0 ); + + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + m_fThrowGrenade = FALSE; + // !!!LATER - when in a group, only try to throw grenade if ordered. + } + break; + case ASSASSIN_AE_JUMP: + { + // ALERT( at_console, "jumping"); + UTIL_MakeAimVectors( pev->angles ); + pev->movetype = MOVETYPE_TOSS; + pev->flags &= ~FL_ONGROUND; + pev->velocity = m_vecJumpVelocity; + m_flNextJump = gpGlobals->time + 3.0; + } + return; + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHAssassin :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/hassassin.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + pev->health = gSkillData.hassassinHealth; + m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = bits_CAP_MELEE_ATTACK1 | bits_CAP_DOORS_GROUP; + pev->friction = 1; + + m_HackedGunPos = Vector( 0, 24, 48 ); + + m_iTargetRanderamt = 20; + pev->renderamt = 20; + pev->rendermode = kRenderTransTexture; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHAssassin :: Precache() +{ + PRECACHE_MODEL("models/hassassin.mdl"); + + PRECACHE_SOUND("weapons/pl_gun1.wav"); + PRECACHE_SOUND("weapons/pl_gun2.wav"); + + PRECACHE_SOUND("debris/beamstart1.wav"); + + m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell +} + + + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +//========================================================= +// Fail Schedule +//========================================================= +Task_t tlAssassinFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + // { TASK_WAIT_PVS, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_CHASE_ENEMY }, +}; + +Schedule_t slAssassinFail[] = +{ + { + tlAssassinFail, + ARRAYSIZE ( tlAssassinFail ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + "AssassinFail" + }, +}; + + +//========================================================= +// Enemy exposed Agrunt's cover +//========================================================= +Task_t tlAssassinExposed[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_JUMP }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slAssassinExposed[] = +{ + { + tlAssassinExposed, + ARRAYSIZE ( tlAssassinExposed ), + bits_COND_CAN_MELEE_ATTACK1, + 0, + "AssassinExposed", + }, +}; + + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlAssassinTakeCoverFromEnemy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slAssassinTakeCoverFromEnemy[] = +{ + { + tlAssassinTakeCoverFromEnemy, + ARRAYSIZE ( tlAssassinTakeCoverFromEnemy ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinTakeCoverFromEnemy" + }, +}; + + +//========================================================= +// Take cover from enemy! Tries lateral cover before node +// cover! +//========================================================= +Task_t tlAssassinTakeCoverFromEnemy2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_WAIT, (float)0.2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK2 }, + { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, +}; + +Schedule_t slAssassinTakeCoverFromEnemy2[] = +{ + { + tlAssassinTakeCoverFromEnemy2, + ARRAYSIZE ( tlAssassinTakeCoverFromEnemy2 ), + bits_COND_NEW_ENEMY | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinTakeCoverFromEnemy2" + }, +}; + + +//========================================================= +// hide from the loudest sound source +//========================================================= +Task_t tlAssassinTakeCoverFromBestSound[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MELEE_ATTACK1 }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slAssassinTakeCoverFromBestSound[] = +{ + { + tlAssassinTakeCoverFromBestSound, + ARRAYSIZE ( tlAssassinTakeCoverFromBestSound ), + bits_COND_NEW_ENEMY, + 0, + "AssassinTakeCoverFromBestSound" + }, +}; + + + + + +//========================================================= +// AlertIdle Schedules +//========================================================= +Task_t tlAssassinHide[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_SET_SCHEDULE, (float)SCHED_CHASE_ENEMY }, +}; + +Schedule_t slAssassinHide[] = +{ + { + tlAssassinHide, + ARRAYSIZE ( tlAssassinHide ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_FEAR | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinHide" + }, +}; + + + +//========================================================= +// HUNT Schedules +//========================================================= +Task_t tlAssassinHunt[] = +{ + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slAssassinHunt[] = +{ + { + tlAssassinHunt, + ARRAYSIZE ( tlAssassinHunt ), + bits_COND_NEW_ENEMY | + // bits_COND_SEE_ENEMY | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "AssassinHunt" + }, +}; + + +//========================================================= +// Jumping Schedules +//========================================================= +Task_t tlAssassinJump[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_HOP }, + { TASK_SET_SCHEDULE, (float)SCHED_ASSASSIN_JUMP_ATTACK }, +}; + +Schedule_t slAssassinJump[] = +{ + { + tlAssassinJump, + ARRAYSIZE ( tlAssassinJump ), + 0, + 0, + "AssassinJump" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlAssassinJumpAttack[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_JUMP_LAND }, + // { TASK_SET_ACTIVITY, (float)ACT_FLY }, + { TASK_ASSASSIN_FALL_TO_GROUND, (float)0 }, +}; + + +Schedule_t slAssassinJumpAttack[] = +{ + { + tlAssassinJumpAttack, + ARRAYSIZE ( tlAssassinJumpAttack ), + 0, + 0, + "AssassinJumpAttack" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlAssassinJumpLand[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ASSASSIN_EXPOSED }, + // { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MELEE_ATTACK1 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_REMEMBER, (float)bits_MEMORY_BADJUMP }, + { TASK_FIND_NODE_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_FORGET, (float)bits_MEMORY_BADJUMP }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 }, +}; + +Schedule_t slAssassinJumpLand[] = +{ + { + tlAssassinJumpLand, + ARRAYSIZE ( tlAssassinJumpLand ), + 0, + 0, + "AssassinJumpLand" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CHAssassin ) +{ + slAssassinFail, + slAssassinExposed, + slAssassinTakeCoverFromEnemy, + slAssassinTakeCoverFromEnemy2, + slAssassinTakeCoverFromBestSound, + slAssassinHide, + slAssassinHunt, + slAssassinJump, + slAssassinJumpAttack, + slAssassinJumpLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHAssassin, CBaseMonster ); + + +//========================================================= +// CheckMeleeAttack1 - jump like crazy if the enemy gets too close. +//========================================================= +BOOL CHAssassin :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( m_flNextJump < gpGlobals->time && (flDist <= 128 || HasMemory( bits_MEMORY_BADJUMP )) && m_hEnemy != NULL ) + { + TraceResult tr; + + Vector vecDest = pev->origin + Vector( RANDOM_FLOAT( -64, 64), RANDOM_FLOAT( -64, 64 ), 160 ); + + UTIL_TraceHull( pev->origin + Vector( 0, 0, 36 ), vecDest + Vector( 0, 0, 36 ), dont_ignore_monsters, human_hull, ENT(pev), &tr); + + if ( tr.fStartSolid || tr.flFraction < 1.0) + { + return FALSE; + } + + float flGravity = g_psv_gravity->value; + + float time = sqrt( 160 / (0.5 * flGravity)); + float speed = flGravity * time / 160; + m_vecJumpVelocity = (vecDest - pev->origin) * speed; + + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack1 - drop a cap in their ass +// +//========================================================= +BOOL CHAssassin :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist > 64 && flDist <= 2048 /* && flDot >= 0.5 */ /* && NoFriendlyFire() */ ) + { + TraceResult tr; + + Vector vecSrc = GetGunPosition(); + + // verify that a bullet fired from the gun will hit the enemy before the world. + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), dont_ignore_monsters, ENT(pev), &tr); + + if ( tr.flFraction == 1 || tr.pHit == m_hEnemy->edict() ) + { + return TRUE; + } + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 - toss grenade is enemy gets in the way and is too close. +//========================================================= +BOOL CHAssassin :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + m_fThrowGrenade = FALSE; + if ( !FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) ) + { + // don't throw grenades at anything that isn't on the ground! + return FALSE; + } + + // don't get grenade happy unless the player starts to piss you off + if ( m_iFrustration <= 2) + return FALSE; + + if ( m_flNextGrenadeCheck < gpGlobals->time && !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 512 /* && flDot >= 0.5 */ /* && NoFriendlyFire() */ ) + { + Vector vecToss = VecCheckThrow( pev, GetGunPosition( ), m_hEnemy->Center(), flDist, 0.5 ); // use dist as speed to get there in 1 second + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + + return TRUE; + } + } + + return FALSE; +} + + +//========================================================= +// RunAI +//========================================================= +void CHAssassin :: RunAI( void ) +{ + CBaseMonster :: RunAI(); + + // always visible if moving + // always visible is not on hard + if (g_iSkillLevel != SKILL_HARD || m_hEnemy == NULL || pev->deadflag != DEAD_NO || m_Activity == ACT_RUN || m_Activity == ACT_WALK || !(pev->flags & FL_ONGROUND)) + m_iTargetRanderamt = 255; + else + m_iTargetRanderamt = 20; + + if (pev->renderamt > m_iTargetRanderamt) + { + if (pev->renderamt == 255) + { + EMIT_SOUND (ENT(pev), CHAN_BODY, "debris/beamstart1.wav", 0.2, ATTN_NORM ); + } + + pev->renderamt = max( pev->renderamt - 50, m_iTargetRanderamt ); + pev->rendermode = kRenderTransTexture; + } + else if (pev->renderamt < m_iTargetRanderamt) + { + pev->renderamt = min( pev->renderamt + 50, m_iTargetRanderamt ); + if (pev->renderamt == 255) + pev->rendermode = kRenderNormal; + } + + if (m_Activity == ACT_RUN || m_Activity == ACT_WALK) + { + static int iStep = 0; + iStep = ! iStep; + if (iStep) + { + switch( RANDOM_LONG( 0, 3 ) ) + { + case 0: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step1.wav", 0.5, ATTN_NORM); break; + case 1: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step3.wav", 0.5, ATTN_NORM); break; + case 2: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step2.wav", 0.5, ATTN_NORM); break; + case 3: EMIT_SOUND( ENT(pev), CHAN_BODY, "player/pl_step4.wav", 0.5, ATTN_NORM); break; + } + } + } +} + + +//========================================================= +// StartTask +//========================================================= +void CHAssassin :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK2: + if (!m_fThrowGrenade) + { + TaskComplete( ); + } + else + { + CBaseMonster :: StartTask ( pTask ); + } + break; + case TASK_ASSASSIN_FALL_TO_GROUND: + break; + default: + CBaseMonster :: StartTask ( pTask ); + break; + } +} + + +//========================================================= +// RunTask +//========================================================= +void CHAssassin :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_ASSASSIN_FALL_TO_GROUND: + MakeIdealYaw( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if (m_fSequenceFinished) + { + if (pev->velocity.z > 0) + { + pev->sequence = LookupSequence( "fly_up" ); + } + else if (HasConditions ( bits_COND_SEE_ENEMY )) + { + pev->sequence = LookupSequence( "fly_attack" ); + pev->frame = 0; + } + else + { + pev->sequence = LookupSequence( "fly_down" ); + pev->frame = 0; + } + + ResetSequenceInfo( ); + SetYawSpeed(); + } + if (pev->flags & FL_ONGROUND) + { + // ALERT( at_console, "on ground\n"); + TaskComplete( ); + } + break; + default: + CBaseMonster :: RunTask ( pTask ); + break; + } +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CHAssassin :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + case MONSTERSTATE_ALERT: + { + if ( HasConditions ( bits_COND_HEAR_SOUND )) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + if ( pSound && (pSound->m_iType & bits_SOUND_COMBAT) ) + { + return GetScheduleOfType( SCHED_INVESTIGATE_SOUND ); + } + } + } + break; + + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + // flying? + if ( pev->movetype == MOVETYPE_TOSS) + { + if (pev->flags & FL_ONGROUND) + { + // ALERT( at_console, "landed\n"); + // just landed + pev->movetype = MOVETYPE_STEP; + return GetScheduleOfType ( SCHED_ASSASSIN_JUMP_LAND ); + } + else + { + // ALERT( at_console, "jump\n"); + // jump or jump/shoot + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType ( SCHED_ASSASSIN_JUMP ); + else + return GetScheduleOfType ( SCHED_ASSASSIN_JUMP_ATTACK ); + } + } + + if ( HasConditions ( bits_COND_HEAR_SOUND )) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + } + + if ( HasConditions ( bits_COND_LIGHT_DAMAGE ) ) + { + m_iFrustration++; + } + if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) + { + m_iFrustration++; + } + + // jump player! + if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + // ALERT( at_console, "melee attack 1\n"); + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } + + // throw grenade + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) ) + { + // ALERT( at_console, "range attack 2\n"); + return GetScheduleOfType ( SCHED_RANGE_ATTACK2 ); + } + + // spotted + if ( HasConditions ( bits_COND_SEE_ENEMY ) && HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + // ALERT( at_console, "exposed\n"); + m_iFrustration++; + return GetScheduleOfType ( SCHED_ASSASSIN_EXPOSED ); + } + + // can attack + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + // ALERT( at_console, "range attack 1\n"); + m_iFrustration = 0; + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + if ( HasConditions ( bits_COND_SEE_ENEMY ) ) + { + // ALERT( at_console, "face\n"); + return GetScheduleOfType ( SCHED_COMBAT_FACE ); + } + + // new enemy + if ( HasConditions ( bits_COND_NEW_ENEMY ) ) + { + // ALERT( at_console, "take cover\n"); + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + + // ALERT( at_console, "stand\n"); + return GetScheduleOfType ( SCHED_ALERT_STAND ); + } + break; + } + + return CBaseMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CHAssassin :: GetScheduleOfType ( int Type ) +{ + // ALERT( at_console, "%d\n", m_iFrustration ); + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + if (pev->health > 30) + return slAssassinTakeCoverFromEnemy; + else + return slAssassinTakeCoverFromEnemy2; + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + return slAssassinTakeCoverFromBestSound; + case SCHED_ASSASSIN_EXPOSED: + return slAssassinExposed; + case SCHED_FAIL: + if (m_MonsterState == MONSTERSTATE_COMBAT) + return slAssassinFail; + break; + case SCHED_ALERT_STAND: + if (m_MonsterState == MONSTERSTATE_COMBAT) + return slAssassinHide; + break; + case SCHED_CHASE_ENEMY: + return slAssassinHunt; + case SCHED_MELEE_ATTACK1: + if (pev->flags & FL_ONGROUND) + { + if (m_flNextJump > gpGlobals->time) + { + // can't jump yet, go ahead and fail + return slAssassinFail; + } + else + { + return slAssassinJump; + } + } + else + { + return slAssassinJumpAttack; + } + case SCHED_ASSASSIN_JUMP: + case SCHED_ASSASSIN_JUMP_ATTACK: + return slAssassinJumpAttack; + case SCHED_ASSASSIN_JUMP_LAND: + return slAssassinJumpLand; + } + + return CBaseMonster :: GetScheduleOfType( Type ); +} + +#endif \ No newline at end of file diff --git a/dlls/headcrab.cpp b/dlls/headcrab.cpp new file mode 100644 index 0000000..9adc877 --- /dev/null +++ b/dlls/headcrab.cpp @@ -0,0 +1,555 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// headcrab.cpp - tiny, jumpy alien parasite +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "game.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HC_AE_JUMPATTACK ( 2 ) + +Task_t tlHCRangeAttack1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_WAIT_RANDOM, (float)0.5 }, +}; + +Schedule_t slHCRangeAttack1[] = +{ + { + tlHCRangeAttack1, + ARRAYSIZE ( tlHCRangeAttack1 ), + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "HCRangeAttack1" + }, +}; + +Task_t tlHCRangeAttack1Fast[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slHCRangeAttack1Fast[] = +{ + { + tlHCRangeAttack1Fast, + ARRAYSIZE ( tlHCRangeAttack1Fast ), + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "HCRAFast" + }, +}; + +class CHeadCrab : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void RunTask ( Task_t *pTask ); + void StartTask ( Task_t *pTask ); + void SetYawSpeed ( void ); + void EXPORT LeapTouch ( CBaseEntity *pOther ); + Vector Center( void ); + Vector BodyTarget( const Vector &posSrc ); + void PainSound( void ); + void DeathSound( void ); + void IdleSound( void ); + void AlertSound( void ); + void PrescheduleThink( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + virtual float GetDamageAmount( void ) { return gSkillData.headcrabDmgBite; } + virtual int GetVoicePitch( void ) { return 100; } + virtual float GetSoundVolue( void ) { return 1.0; } + Schedule_t* GetScheduleOfType ( int Type ); + + CUSTOM_SCHEDULES; + + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pAttackSounds[]; + static const char *pDeathSounds[]; + static const char *pBiteSounds[]; +}; +LINK_ENTITY_TO_CLASS( monster_headcrab, CHeadCrab ); + +DEFINE_CUSTOM_SCHEDULES( CHeadCrab ) +{ + slHCRangeAttack1, + slHCRangeAttack1Fast, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHeadCrab, CBaseMonster ); + +const char *CHeadCrab::pIdleSounds[] = +{ + "headcrab/hc_idle1.wav", + "headcrab/hc_idle2.wav", + "headcrab/hc_idle3.wav", +}; +const char *CHeadCrab::pAlertSounds[] = +{ + "headcrab/hc_alert1.wav", +}; +const char *CHeadCrab::pPainSounds[] = +{ + "headcrab/hc_pain1.wav", + "headcrab/hc_pain2.wav", + "headcrab/hc_pain3.wav", +}; +const char *CHeadCrab::pAttackSounds[] = +{ + "headcrab/hc_attack1.wav", + "headcrab/hc_attack2.wav", + "headcrab/hc_attack3.wav", +}; + +const char *CHeadCrab::pDeathSounds[] = +{ + "headcrab/hc_die1.wav", + "headcrab/hc_die2.wav", +}; + +const char *CHeadCrab::pBiteSounds[] = +{ + "headcrab/hc_headbite.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHeadCrab :: Classify ( void ) +{ + return CLASS_ALIEN_PREY; +} + +//========================================================= +// Center - returns the real center of the headcrab. The +// bounding box is much larger than the actual creature so +// this is needed for targeting +//========================================================= +Vector CHeadCrab :: Center ( void ) +{ + return Vector( pev->origin.x, pev->origin.y, pev->origin.z + 6 ); +} + + +Vector CHeadCrab :: BodyTarget( const Vector &posSrc ) +{ + return Center( ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CHeadCrab :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 30; + break; + case ACT_RUN: + case ACT_WALK: + ys = 20; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 60; + break; + case ACT_RANGE_ATTACK1: + ys = 30; + break; + default: + ys = 30; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CHeadCrab :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case HC_AE_JUMPATTACK: + { + ClearBits( pev->flags, FL_ONGROUND ); + + UTIL_SetOrigin (pev, pev->origin + Vector ( 0 , 0 , 1) );// take him off ground so engine doesn't instantly reset onground + UTIL_MakeVectors ( pev->angles ); + + Vector vecJumpDir; + if (m_hEnemy != NULL) + { + float gravity = g_psv_gravity->value; + if (gravity <= 1) + gravity = 1; + + // How fast does the headcrab need to travel to reach that height given gravity? + float height = (m_hEnemy->pev->origin.z + m_hEnemy->pev->view_ofs.z - pev->origin.z); + if (height < 16) + height = 16; + float speed = sqrt( 2 * gravity * height ); + float time = speed / gravity; + + // Scale the sideways velocity to get there at the right time + vecJumpDir = (m_hEnemy->pev->origin + m_hEnemy->pev->view_ofs - pev->origin); + vecJumpDir = vecJumpDir * ( 1.0 / time ); + + // Speed to offset gravity at the desired height + vecJumpDir.z = speed; + + // Don't jump too far/fast + float distance = vecJumpDir.Length(); + + if (distance > 650) + { + vecJumpDir = vecJumpDir * ( 650.0 / distance ); + } + } + else + { + // jump hop, don't care where + vecJumpDir = Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, gpGlobals->v_up.z ) * 350; + } + + int iSound = RANDOM_LONG(0,2); + if ( iSound != 0 ) + EMIT_SOUND_DYN( edict(), CHAN_VOICE, pAttackSounds[iSound], GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + + pev->velocity = vecJumpDir; + m_flNextAttack = gpGlobals->time + 2; + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHeadCrab :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/headcrab.mdl"); + UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.headcrabHealth; + pev->view_ofs = Vector ( 0, 0, 20 );// position of the eyes relative to monster's origin. + pev->yaw_speed = 5;//!!! should we put this in the monster's changeanim function since turn rates may vary with state/anim? + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHeadCrab :: Precache() +{ + PRECACHE_SOUND_ARRAY(pIdleSounds); + PRECACHE_SOUND_ARRAY(pAlertSounds); + PRECACHE_SOUND_ARRAY(pPainSounds); + PRECACHE_SOUND_ARRAY(pAttackSounds); + PRECACHE_SOUND_ARRAY(pDeathSounds); + PRECACHE_SOUND_ARRAY(pBiteSounds); + + PRECACHE_MODEL("models/headcrab.mdl"); +} + + +//========================================================= +// RunTask +//========================================================= +void CHeadCrab :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + case TASK_RANGE_ATTACK2: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + SetTouch( NULL ); + m_IdealActivity = ACT_IDLE; + } + break; + } + default: + { + CBaseMonster :: RunTask(pTask); + } + } +} + +//========================================================= +// LeapTouch - this is the headcrab's touch function when it +// is in the air +//========================================================= +void CHeadCrab :: LeapTouch ( CBaseEntity *pOther ) +{ + if ( !pOther->pev->takedamage ) + { + return; + } + + if ( pOther->Classify() == Classify() ) + { + return; + } + + // Don't hit if back on ground + if ( !FBitSet( pev->flags, FL_ONGROUND ) ) + { + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY(pBiteSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + + pOther->TakeDamage( pev, pev, GetDamageAmount(), DMG_SLASH ); + } + + SetTouch( NULL ); +} + +//========================================================= +// PrescheduleThink +//========================================================= +void CHeadCrab :: PrescheduleThink ( void ) +{ + // make the crab coo a little bit in combat state + if ( m_MonsterState == MONSTERSTATE_COMBAT && RANDOM_FLOAT( 0, 5 ) < 0.1 ) + { + IdleSound(); + } +} + +void CHeadCrab :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + { + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, pAttackSounds[0], GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); + m_IdealActivity = ACT_RANGE_ATTACK1; + SetTouch ( LeapTouch ); + break; + } + default: + { + CBaseMonster :: StartTask( pTask ); + } + } +} + + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CHeadCrab :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist <= 256 && flDot >= 0.65 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 +//========================================================= +BOOL CHeadCrab :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; + // BUGBUG: Why is this code here? There is no ACT_RANGE_ATTACK2 animation. I've disabled it for now. +#if 0 + if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist > 64 && flDist <= 256 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +#endif +} + +int CHeadCrab :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Don't take any acid damage -- BigMomma's mortar is acid + if ( bitsDamageType & DMG_ACID ) + flDamage = 0; + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// IdleSound +//========================================================= +#define CRAB_ATTN_IDLE (float)1.5 +void CHeadCrab :: IdleSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pIdleSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// AlertSound +//========================================================= +void CHeadCrab :: AlertSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pAlertSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// AlertSound +//========================================================= +void CHeadCrab :: PainSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pPainSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +//========================================================= +// DeathSound +//========================================================= +void CHeadCrab :: DeathSound ( void ) +{ + EMIT_SOUND_DYN( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY(pDeathSounds), GetSoundVolue(), ATTN_IDLE, 0, GetVoicePitch() ); +} + +Schedule_t* CHeadCrab :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_RANGE_ATTACK1: + { + return &slHCRangeAttack1[ 0 ]; + } + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + + +class CBabyCrab : public CHeadCrab +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed ( void ); + float GetDamageAmount( void ) { return gSkillData.headcrabDmgBite * 0.3; } + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + Schedule_t* GetScheduleOfType ( int Type ); + virtual int GetVoicePitch( void ) { return PITCH_NORM + RANDOM_LONG(40,50); } + virtual float GetSoundVolue( void ) { return 0.8; } +}; +LINK_ENTITY_TO_CLASS( monster_babycrab, CBabyCrab ); + +void CBabyCrab :: Spawn( void ) +{ + CHeadCrab::Spawn(); + SET_MODEL(ENT(pev), "models/baby_headcrab.mdl"); + pev->rendermode = kRenderTransTexture; + pev->renderamt = 192; + UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); + + pev->health = gSkillData.headcrabHealth * 0.25; // less health than full grown +} + +void CBabyCrab :: Precache( void ) +{ + PRECACHE_MODEL( "models/baby_headcrab.mdl" ); + CHeadCrab::Precache(); +} + + +void CBabyCrab :: SetYawSpeed ( void ) +{ + pev->yaw_speed = 120; +} + + +BOOL CBabyCrab :: CheckRangeAttack1( float flDot, float flDist ) +{ + if ( pev->flags & FL_ONGROUND ) + { + if ( pev->groundentity && (pev->groundentity->v.flags & (FL_CLIENT|FL_MONSTER)) ) + return TRUE; + + // A little less accurate, but jump from closer + if ( flDist <= 180 && flDot >= 0.55 ) + return TRUE; + } + + return FALSE; +} + + +Schedule_t* CBabyCrab :: GetScheduleOfType ( int Type ) +{ + switch( Type ) + { + case SCHED_FAIL: // If you fail, try to jump! + if ( m_hEnemy != NULL ) + return slHCRangeAttack1Fast; + break; + + case SCHED_RANGE_ATTACK1: + { + return slHCRangeAttack1Fast; + } + break; + } + + return CHeadCrab::GetScheduleOfType( Type ); +} diff --git a/dlls/healthkit.cpp b/dlls/healthkit.cpp index f855b29..73931f2 100644 --- a/dlls/healthkit.cpp +++ b/dlls/healthkit.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/hgrunt.cpp b/dlls/hgrunt.cpp new file mode 100644 index 0000000..0d2ca6c --- /dev/null +++ b/dlls/hgrunt.cpp @@ -0,0 +1,2517 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// hgrunt +//========================================================= + +//========================================================= +// Hit groups! +//========================================================= +/* + + 1 - Head + 2 - Stomach + 3 - Gun + +*/ + + +#include "extdll.h" +#include "plane.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "squadmonster.h" +#include "weapons.h" +#include "talkmonster.h" +#include "soundent.h" +#include "effects.h" +#include "customentity.h" + +int g_fGruntQuestion; // true if an idle grunt asked a question. Cleared when someone answers. + +extern DLL_GLOBAL int g_iSkillLevel; + +//========================================================= +// monster-specific DEFINE's +//========================================================= +#define GRUNT_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! +#define GRUNT_VOL 0.35 // volume of grunt sounds +#define GRUNT_ATTN ATTN_NORM // attenutation of grunt sentences +#define HGRUNT_LIMP_HEALTH 20 +#define HGRUNT_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a grunt with a single headshot. +#define HGRUNT_NUM_HEADS 2 // how many grunt heads are there? +#define HGRUNT_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill +#define HGRUNT_SENTENCE_VOLUME (float)0.35 // volume of grunt sentences + +#define HGRUNT_9MMAR ( 1 << 0) +#define HGRUNT_HANDGRENADE ( 1 << 1) +#define HGRUNT_GRENADELAUNCHER ( 1 << 2) +#define HGRUNT_SHOTGUN ( 1 << 3) + +#define HEAD_GROUP 1 +#define HEAD_GRUNT 0 +#define HEAD_COMMANDER 1 +#define HEAD_SHOTGUN 2 +#define HEAD_M203 3 +#define GUN_GROUP 2 +#define GUN_MP5 0 +#define GUN_SHOTGUN 1 +#define GUN_NONE 2 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HGRUNT_AE_RELOAD ( 2 ) +#define HGRUNT_AE_KICK ( 3 ) +#define HGRUNT_AE_BURST1 ( 4 ) +#define HGRUNT_AE_BURST2 ( 5 ) +#define HGRUNT_AE_BURST3 ( 6 ) +#define HGRUNT_AE_GREN_TOSS ( 7 ) +#define HGRUNT_AE_GREN_LAUNCH ( 8 ) +#define HGRUNT_AE_GREN_DROP ( 9 ) +#define HGRUNT_AE_CAUGHT_ENEMY ( 10) // grunt established sight with an enemy (player only) that had previously eluded the squad. +#define HGRUNT_AE_DROP_GUN ( 11) // grunt (probably dead) is dropping his mp5. + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_GRUNT_SUPPRESS = LAST_COMMON_SCHEDULE + 1, + SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE,// move to a location to set up an attack against the enemy. (usually when a friendly is in the way). + SCHED_GRUNT_COVER_AND_RELOAD, + SCHED_GRUNT_SWEEP, + SCHED_GRUNT_FOUND_ENEMY, + SCHED_GRUNT_REPEL, + SCHED_GRUNT_REPEL_ATTACK, + SCHED_GRUNT_REPEL_LAND, + SCHED_GRUNT_WAIT_FACE_ENEMY, + SCHED_GRUNT_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. + SCHED_GRUNT_ELOF_FAIL, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_GRUNT_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, + TASK_GRUNT_SPEAK_SENTENCE, + TASK_GRUNT_CHECK_FIRE, +}; + +//========================================================= +// monster-specific conditions +//========================================================= +#define bits_COND_GRUNT_NOFIRE ( bits_COND_SPECIAL1 ) + +class CHGrunt : public CSquadMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed ( void ); + int Classify ( void ); + int ISoundMask ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL FCanCheckAttacks ( void ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + void CheckAmmo ( void ); + void SetActivity ( Activity NewActivity ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + void DeathSound( void ); + void PainSound( void ); + void IdleSound ( void ); + Vector GetGunPosition( void ); + void Shoot ( void ); + void Shotgun ( void ); + void PrescheduleThink ( void ); + void GibMonster( void ); + void SpeakSentence( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + CBaseEntity *Kick( void ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + int IRelationship ( CBaseEntity *pTarget ); + + BOOL FOkToSpeak( void ); + void JustSpoke( void ); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + // checking the feasibility of a grenade toss is kind of costly, so we do it every couple of seconds, + // not every server frame. + float m_flNextGrenadeCheck; + float m_flNextPainTime; + float m_flLastEnemySightTime; + + Vector m_vecTossVelocity; + + BOOL m_fThrowGrenade; + BOOL m_fStanding; + BOOL m_fFirstEncounter;// only put on the handsign show in the squad's first encounter. + int m_cClipSize; + + int m_voicePitch; + + int m_iBrassShell; + int m_iShotgunShell; + + int m_iSentence; + + static const char *pGruntSentences[]; +}; + +LINK_ENTITY_TO_CLASS( monster_human_grunt, CHGrunt ); + +TYPEDESCRIPTION CHGrunt::m_SaveData[] = +{ + DEFINE_FIELD( CHGrunt, m_flNextGrenadeCheck, FIELD_TIME ), + DEFINE_FIELD( CHGrunt, m_flNextPainTime, FIELD_TIME ), +// DEFINE_FIELD( CHGrunt, m_flLastEnemySightTime, FIELD_TIME ), // don't save, go to zero + DEFINE_FIELD( CHGrunt, m_vecTossVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CHGrunt, m_fThrowGrenade, FIELD_BOOLEAN ), + DEFINE_FIELD( CHGrunt, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CHGrunt, m_fFirstEncounter, FIELD_BOOLEAN ), + DEFINE_FIELD( CHGrunt, m_cClipSize, FIELD_INTEGER ), + DEFINE_FIELD( CHGrunt, m_voicePitch, FIELD_INTEGER ), +// DEFINE_FIELD( CShotgun, m_iBrassShell, FIELD_INTEGER ), +// DEFINE_FIELD( CShotgun, m_iShotgunShell, FIELD_INTEGER ), + DEFINE_FIELD( CHGrunt, m_iSentence, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CHGrunt, CSquadMonster ); + +const char *CHGrunt::pGruntSentences[] = +{ + "HG_GREN", // grenade scared grunt + "HG_ALERT", // sees player + "HG_MONSTER", // sees monster + "HG_COVER", // running to cover + "HG_THROW", // about to throw grenade + "HG_CHARGE", // running out to get the enemy + "HG_TAUNT", // say rude things +}; + +enum +{ + HGRUNT_SENT_NONE = -1, + HGRUNT_SENT_GREN = 0, + HGRUNT_SENT_ALERT, + HGRUNT_SENT_MONSTER, + HGRUNT_SENT_COVER, + HGRUNT_SENT_THROW, + HGRUNT_SENT_CHARGE, + HGRUNT_SENT_TAUNT, +} HGRUNT_SENTENCE_TYPES; + +//========================================================= +// Speak Sentence - say your cued up sentence. +// +// Some grunt sentences (take cover and charge) rely on actually +// being able to execute the intended action. It's really lame +// when a grunt says 'COVER ME' and then doesn't move. The problem +// is that the sentences were played when the decision to TRY +// to move to cover was made. Now the sentence is played after +// we know for sure that there is a valid path. The schedule +// may still fail but in most cases, well after the grunt has +// started moving. +//========================================================= +void CHGrunt :: SpeakSentence( void ) +{ + if ( m_iSentence == HGRUNT_SENT_NONE ) + { + // no sentence cued up. + return; + } + + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), pGruntSentences[ m_iSentence ], HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } +} + +//========================================================= +// IRelationship - overridden because Alien Grunts are +// Human Grunt's nemesis. +//========================================================= +int CHGrunt::IRelationship ( CBaseEntity *pTarget ) +{ + if ( FClassnameIs( pTarget->pev, "monster_alien_grunt" ) || ( FClassnameIs( pTarget->pev, "monster_gargantua" ) ) ) + { + return R_NM; + } + + return CSquadMonster::IRelationship( pTarget ); +} + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CHGrunt :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if ( GetBodygroup( 2 ) != 2 ) + {// throw a gun if the grunt has one + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun; + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + pGun = DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else + { + pGun = DropItem( "weapon_9mmAR", vecGunPos, vecGunAngles ); + } + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + + if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + { + pGun = DropItem( "ammo_ARgrenades", vecGunPos, vecGunAngles ); + if ( pGun ) + { + pGun->pev->velocity = Vector (RANDOM_FLOAT(-100,100), RANDOM_FLOAT(-100,100), RANDOM_FLOAT(200,300)); + pGun->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 200, 400 ), 0 ); + } + } + } + + CBaseMonster :: GibMonster(); +} + +//========================================================= +// ISoundMask - Overidden for human grunts because they +// hear the DANGER sound that is made by hand grenades and +// other dangerous items. +//========================================================= +int CHGrunt :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_DANGER; +} + +//========================================================= +// someone else is talking - don't speak +//========================================================= +BOOL CHGrunt :: FOkToSpeak( void ) +{ +// if someone else is talking, don't speak + if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) + return FALSE; + + if ( pev->spawnflags & SF_MONSTER_GAG ) + { + if ( m_MonsterState != MONSTERSTATE_COMBAT ) + { + // no talking outside of combat if gagged. + return FALSE; + } + } + + // if player is not in pvs, don't speak +// if (FNullEnt(FIND_CLIENT_IN_PVS(edict()))) +// return FALSE; + + return TRUE; +} + +//========================================================= +//========================================================= +void CHGrunt :: JustSpoke( void ) +{ + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(1.5, 2.0); + m_iSentence = HGRUNT_SENT_NONE; +} + +//========================================================= +// PrescheduleThink - this function runs after conditions +// are collected and before scheduling code is run. +//========================================================= +void CHGrunt :: PrescheduleThink ( void ) +{ + if ( InSquad() && m_hEnemy != NULL ) + { + if ( HasConditions ( bits_COND_SEE_ENEMY ) ) + { + // update the squad's last enemy sighting time. + MySquadLeader()->m_flLastEnemySightTime = gpGlobals->time; + } + else + { + if ( gpGlobals->time - MySquadLeader()->m_flLastEnemySightTime > 5 ) + { + // been a while since we've seen the enemy + MySquadLeader()->m_fEnemyEluded = TRUE; + } + } + } +} + +//========================================================= +// FCanCheckAttacks - this is overridden for human grunts +// because they can throw/shoot grenades when they can't see their +// target and the base class doesn't check attacks if the monster +// cannot see its enemy. +// +// !!!BUGBUG - this gets called before a 3-round burst is fired +// which means that a friendly can still be hit with up to 2 rounds. +// ALSO, grenades will not be tossed if there is a friendly in front, +// this is a bad bug. Friendly machine gun fire avoidance +// will unecessarily prevent the throwing of a grenade as well. +//========================================================= +BOOL CHGrunt :: FCanCheckAttacks ( void ) +{ + if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CHGrunt :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + CBaseMonster *pEnemy; + + if ( m_hEnemy != NULL ) + { + pEnemy = m_hEnemy->MyMonsterPointer(); + + if ( !pEnemy ) + { + return FALSE; + } + } + + if ( flDist <= 64 && flDot >= 0.7 && + pEnemy->Classify() != CLASS_ALIEN_BIOWEAPON && + pEnemy->Classify() != CLASS_PLAYER_BIOWEAPON ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack1 - overridden for HGrunt, cause +// FCanCheckAttacks() doesn't disqualify all attacks based +// on whether or not the enemy is occluded because unlike +// the base class, the HGrunt can attack when the enemy is +// occluded (throw grenade over wall, etc). We must +// disqualify the machine gun attack if the enemy is occluded. +//========================================================= +BOOL CHGrunt :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 2048 && flDot >= 0.5 && NoFriendlyFire() ) + { + TraceResult tr; + + if ( !m_hEnemy->IsPlayer() && flDist <= 64 ) + { + // kick nonclients, but don't shoot at them. + return FALSE; + } + + Vector vecSrc = GetGunPosition(); + + // verify that a bullet fired from the gun will hit the enemy before the world. + UTIL_TraceLine( vecSrc, m_hEnemy->BodyTarget(vecSrc), ignore_monsters, ignore_glass, ENT(pev), &tr); + + if ( tr.flFraction == 1.0 ) + { + return TRUE; + } + } + + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 - this checks the Grunt's grenade +// attack. +//========================================================= +BOOL CHGrunt :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if (! FBitSet(pev->weapons, (HGRUNT_HANDGRENADE | HGRUNT_GRENADELAUNCHER))) + { + return FALSE; + } + + // if the grunt isn't moving, it's ok to check. + if ( m_flGroundSpeed != 0 ) + { + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + // assume things haven't changed too much since last time + if (gpGlobals->time < m_flNextGrenadeCheck ) + { + return m_fThrowGrenade; + } + + if ( !FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) && m_hEnemy->pev->waterlevel == 0 && m_vecEnemyLKP.z > pev->absmax.z ) + { + //!!!BUGBUG - we should make this check movetype and make sure it isn't FLY? Players who jump a lot are unlikely to + // be grenaded. + // don't throw grenades at anything that isn't on the ground! + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + Vector vecTarget; + + if (FBitSet( pev->weapons, HGRUNT_HANDGRENADE)) + { + // find feet + if (RANDOM_LONG(0,1)) + { + // magically know where they are + vecTarget = Vector( m_hEnemy->pev->origin.x, m_hEnemy->pev->origin.y, m_hEnemy->pev->absmin.z ); + } + else + { + // toss it to where you last saw them + vecTarget = m_vecEnemyLKP; + } + // vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin); + // estimate position + // vecTarget = vecTarget + m_hEnemy->pev->velocity * 2; + } + else + { + // find target + // vecTarget = m_hEnemy->BodyTarget( pev->origin ); + vecTarget = m_vecEnemyLKP + (m_hEnemy->BodyTarget( pev->origin ) - m_hEnemy->pev->origin); + // estimate position + if (HasConditions( bits_COND_SEE_ENEMY)) + vecTarget = vecTarget + ((vecTarget - pev->origin).Length() / gSkillData.hgruntGrenadeSpeed) * m_hEnemy->pev->velocity; + } + + // are any of my squad members near the intended grenade impact area? + if ( InSquad() ) + { + if (SquadMemberInRange( vecTarget, 256 )) + { + // crap, I might blow my own guy up. Don't throw a grenade and don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + } + } + + if ( ( vecTarget - pev->origin ).Length2D() <= 256 ) + { + // crap, I don't want to blow myself up + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + m_fThrowGrenade = FALSE; + return m_fThrowGrenade; + } + + + if (FBitSet( pev->weapons, HGRUNT_HANDGRENADE)) + { + Vector vecToss = VecCheckToss( pev, GetGunPosition(), vecTarget, 0.5 ); + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time; // 1/3 second. + } + else + { + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + } + } + else + { + Vector vecToss = VecCheckThrow( pev, GetGunPosition(), vecTarget, gSkillData.hgruntGrenadeSpeed, 0.5 ); + + if ( vecToss != g_vecZero ) + { + m_vecTossVelocity = vecToss; + + // throw a hand grenade + m_fThrowGrenade = TRUE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 0.3; // 1/3 second. + } + else + { + // don't throw + m_fThrowGrenade = FALSE; + // don't check again for a while. + m_flNextGrenadeCheck = gpGlobals->time + 1; // one full second. + } + } + + + + return m_fThrowGrenade; +} + + +//========================================================= +// TraceAttack - make sure we're not taking it in the helmet +//========================================================= +void CHGrunt :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // check for helmet shot + if (ptr->iHitgroup == 11) + { + // make sure we're wearing one + if (GetBodygroup( 1 ) == HEAD_GRUNT && (bitsDamageType & (DMG_BULLET | DMG_SLASH | DMG_BLAST | DMG_CLUB))) + { + // absorb damage + flDamage -= 20; + if (flDamage <= 0) + { + UTIL_Ricochet( ptr->vecEndPos, 1.0 ); + flDamage = 0.01; + } + } + // it's head shot anyways + ptr->iHitgroup = HITGROUP_HEAD; + } + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +//========================================================= +// TakeDamage - overridden for the grunt because the grunt +// needs to forget that he is in cover if he's hurt. (Obviously +// not in a safe place anymore). +//========================================================= +int CHGrunt :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + Forget( bits_MEMORY_INCOVER ); + + return CSquadMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CHGrunt :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 150; + break; + case ACT_RUN: + ys = 150; + break; + case ACT_WALK: + ys = 180; + break; + case ACT_RANGE_ATTACK1: + ys = 120; + break; + case ACT_RANGE_ATTACK2: + ys = 120; + break; + case ACT_MELEE_ATTACK1: + ys = 120; + break; + case ACT_MELEE_ATTACK2: + ys = 120; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 180; + break; + case ACT_GLIDE: + case ACT_FLY: + ys = 30; + break; + default: + ys = 90; + break; + } + + pev->yaw_speed = ys; +} + +void CHGrunt :: IdleSound( void ) +{ + if (FOkToSpeak() && (g_fGruntQuestion || RANDOM_LONG(0,1))) + { + if (!g_fGruntQuestion) + { + // ask question or make statement + switch (RANDOM_LONG(0,2)) + { + case 0: // check in + SENTENCEG_PlayRndSz(ENT(pev), "HG_CHECK", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fGruntQuestion = 1; + break; + case 1: // question + SENTENCEG_PlayRndSz(ENT(pev), "HG_QUEST", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fGruntQuestion = 2; + break; + case 2: // statement + SENTENCEG_PlayRndSz(ENT(pev), "HG_IDLE", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + } + else + { + switch (g_fGruntQuestion) + { + case 1: // check in + SENTENCEG_PlayRndSz(ENT(pev), "HG_CLEAR", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + case 2: // question + SENTENCEG_PlayRndSz(ENT(pev), "HG_ANSWER", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + g_fGruntQuestion = 0; + } + JustSpoke(); + } +} + +//========================================================= +// CheckAmmo - overridden for the grunt because he actually +// uses ammo! (base class doesn't) +//========================================================= +void CHGrunt :: CheckAmmo ( void ) +{ + if ( m_cAmmoLoaded <= 0 ) + { + SetConditions(bits_COND_NO_AMMO_LOADED); + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHGrunt :: Classify ( void ) +{ + return CLASS_HUMAN_MILITARY; +} + +//========================================================= +//========================================================= +CBaseEntity *CHGrunt :: Kick( void ) +{ + TraceResult tr; + + UTIL_MakeVectors( pev->angles ); + Vector vecStart = pev->origin; + vecStart.z += pev->size.z * 0.5; + Vector vecEnd = vecStart + (gpGlobals->v_forward * 70); + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + return pEntity; + } + + return NULL; +} + +//========================================================= +// GetGunPosition return the end of the barrel +//========================================================= + +Vector CHGrunt :: GetGunPosition( ) +{ + if (m_fStanding ) + { + return pev->origin + Vector( 0, 0, 60 ); + } + else + { + return pev->origin + Vector( 0, 0, 48 ); + } +} + +//========================================================= +// Shoot +//========================================================= +void CHGrunt :: Shoot ( void ) +{ + if (m_hEnemy == NULL) + { + return; + } + + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + UTIL_MakeVectors ( pev->angles ); + + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass ( vecShootOrigin - vecShootDir * 24, vecShellVelocity, pev->angles.y, m_iBrassShell, TE_BOUNCE_SHELL); + FireBullets(1, vecShootOrigin, vecShootDir, VECTOR_CONE_10DEGREES, 2048, BULLET_MONSTER_MP5 ); // shoot +-5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + +//========================================================= +// Shoot +//========================================================= +void CHGrunt :: Shotgun ( void ) +{ + if (m_hEnemy == NULL) + { + return; + } + + Vector vecShootOrigin = GetGunPosition(); + Vector vecShootDir = ShootAtEnemy( vecShootOrigin ); + + UTIL_MakeVectors ( pev->angles ); + + Vector vecShellVelocity = gpGlobals->v_right * RANDOM_FLOAT(40,90) + gpGlobals->v_up * RANDOM_FLOAT(75,200) + gpGlobals->v_forward * RANDOM_FLOAT(-40, 40); + EjectBrass ( vecShootOrigin - vecShootDir * 24, vecShellVelocity, pev->angles.y, m_iShotgunShell, TE_BOUNCE_SHOTSHELL); + FireBullets(gSkillData.hgruntShotgunPellets, vecShootOrigin, vecShootDir, VECTOR_CONE_15DEGREES, 2048, BULLET_PLAYER_BUCKSHOT, 0 ); // shoot +-7.5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CHGrunt :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case HGRUNT_AE_DROP_GUN: + { + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( GUN_GROUP, GUN_NONE ); + + // now spawn a gun. + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else + { + DropItem( "weapon_9mmAR", vecGunPos, vecGunAngles ); + } + if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + { + DropItem( "ammo_ARgrenades", BodyTarget( pev->origin ), vecGunAngles ); + } + + } + break; + + case HGRUNT_AE_RELOAD: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "hgrunt/gr_reload1.wav", 1, ATTN_NORM ); + m_cAmmoLoaded = m_cClipSize; + ClearConditions(bits_COND_NO_AMMO_LOADED); + break; + + case HGRUNT_AE_GREN_TOSS: + { + UTIL_MakeVectors( pev->angles ); + // CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 34 + Vector (0, 0, 32), m_vecTossVelocity, 3.5 ); + CGrenade::ShootTimed( pev, GetGunPosition(), m_vecTossVelocity, 3.5 ); + + m_fThrowGrenade = FALSE; + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + // !!!LATER - when in a group, only try to throw grenade if ordered. + } + break; + + case HGRUNT_AE_GREN_LAUNCH: + { + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/glauncher.wav", 0.8, ATTN_NORM); + CGrenade::ShootContact( pev, GetGunPosition(), m_vecTossVelocity ); + m_fThrowGrenade = FALSE; + if (g_iSkillLevel == SKILL_HARD) + m_flNextGrenadeCheck = gpGlobals->time + RANDOM_FLOAT( 2, 5 );// wait a random amount of time before shooting again + else + m_flNextGrenadeCheck = gpGlobals->time + 6;// wait six seconds before even looking again to see if a grenade can be thrown. + } + break; + + case HGRUNT_AE_GREN_DROP: + { + UTIL_MakeVectors( pev->angles ); + CGrenade::ShootTimed( pev, pev->origin + gpGlobals->v_forward * 17 - gpGlobals->v_right * 27 + gpGlobals->v_up * 6, g_vecZero, 3 ); + } + break; + + case HGRUNT_AE_BURST1: + { + if ( FBitSet( pev->weapons, HGRUNT_9MMAR )) + { + Shoot(); + + // the first round of the three round burst plays the sound and puts a sound in the world sound list. + if ( RANDOM_LONG(0,1) ) + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "hgrunt/gr_mgun1.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "hgrunt/gr_mgun2.wav", 1, ATTN_NORM ); + } + } + else + { + Shotgun( ); + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/sbarrel1.wav", 1, ATTN_NORM ); + } + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + } + break; + + case HGRUNT_AE_BURST2: + case HGRUNT_AE_BURST3: + Shoot(); + break; + + case HGRUNT_AE_KICK: + { + CBaseEntity *pHurt = Kick(); + + if ( pHurt ) + { + // SOUND HERE! + UTIL_MakeVectors( pev->angles ); + pHurt->pev->punchangle.x = 15; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100 + gpGlobals->v_up * 50; + pHurt->TakeDamage( pev, pev, gSkillData.hgruntDmgKick, DMG_CLUB ); + } + } + break; + + case HGRUNT_AE_CAUGHT_ENEMY: + { + if ( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz(ENT(pev), "HG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + + } + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHGrunt :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/hgrunt.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + pev->health = gSkillData.hgruntHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextGrenadeCheck = gpGlobals->time + 1; + m_flNextPainTime = gpGlobals->time; + m_iSentence = HGRUNT_SENT_NONE; + + m_afCapability = bits_CAP_SQUAD | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + m_fEnemyEluded = FALSE; + m_fFirstEncounter = TRUE;// this is true when the grunt spawns, because he hasn't encountered an enemy yet. + + m_HackedGunPos = Vector ( 0, 0, 55 ); + + if (pev->weapons == 0) + { + // initialize to original values + pev->weapons = HGRUNT_9MMAR | HGRUNT_HANDGRENADE; + // pev->weapons = HGRUNT_SHOTGUN; + // pev->weapons = HGRUNT_9MMAR | HGRUNT_GRENADELAUNCHER; + } + + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + SetBodygroup( GUN_GROUP, GUN_SHOTGUN ); + m_cClipSize = 8; + } + else + { + m_cClipSize = GRUNT_CLIP_SIZE; + } + m_cAmmoLoaded = m_cClipSize; + + if (RANDOM_LONG( 0, 99 ) < 80) + pev->skin = 0; // light skin + else + pev->skin = 1; // dark skin + + if (FBitSet( pev->weapons, HGRUNT_SHOTGUN )) + { + SetBodygroup( HEAD_GROUP, HEAD_SHOTGUN); + } + else if (FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER )) + { + SetBodygroup( HEAD_GROUP, HEAD_M203 ); + pev->skin = 1; // alway dark skin + } + + CTalkMonster::g_talkWaitTime = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHGrunt :: Precache() +{ + PRECACHE_MODEL("models/hgrunt.mdl"); + + PRECACHE_SOUND( "hgrunt/gr_mgun1.wav" ); + PRECACHE_SOUND( "hgrunt/gr_mgun2.wav" ); + + PRECACHE_SOUND( "hgrunt/gr_die1.wav" ); + PRECACHE_SOUND( "hgrunt/gr_die2.wav" ); + PRECACHE_SOUND( "hgrunt/gr_die3.wav" ); + + PRECACHE_SOUND( "hgrunt/gr_pain1.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain2.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain3.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain4.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain5.wav" ); + + PRECACHE_SOUND( "hgrunt/gr_reload1.wav" ); + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + + PRECACHE_SOUND( "weapons/sbarrel1.wav" ); + + PRECACHE_SOUND("zombie/claw_miss2.wav");// because we use the basemonster SWIPE animation event + + // get voice pitch + if (RANDOM_LONG(0,1)) + m_voicePitch = 109 + RANDOM_LONG(0,7); + else + m_voicePitch = 100; + + m_iBrassShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell + m_iShotgunShell = PRECACHE_MODEL ("models/shotgunshell.mdl"); +} + +//========================================================= +// start task +//========================================================= +void CHGrunt :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_GRUNT_CHECK_FIRE: + if ( !NoFriendlyFire() ) + { + SetConditions( bits_COND_GRUNT_NOFIRE ); + } + TaskComplete(); + break; + + case TASK_GRUNT_SPEAK_SENTENCE: + SpeakSentence(); + TaskComplete(); + break; + + case TASK_WALK_PATH: + case TASK_RUN_PATH: + // grunt no longer assumes he is covered if he moves + Forget( bits_MEMORY_INCOVER ); + CSquadMonster ::StartTask( pTask ); + break; + + case TASK_RELOAD: + m_IdealActivity = ACT_RELOAD; + break; + + case TASK_GRUNT_FACE_TOSS_DIR: + break; + + case TASK_FACE_IDEAL: + case TASK_FACE_ENEMY: + CSquadMonster :: StartTask( pTask ); + if (pev->movetype == MOVETYPE_FLY) + { + m_IdealActivity = ACT_GLIDE; + } + break; + + default: + CSquadMonster :: StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CHGrunt :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_GRUNT_FACE_TOSS_DIR: + { + // project a point along the toss vector and turn to face that point. + MakeIdealYaw( pev->origin + m_vecTossVelocity * 64 ); + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + default: + { + CSquadMonster :: RunTask( pTask ); + break; + } + } +} + +//========================================================= +// PainSound +//========================================================= +void CHGrunt :: PainSound ( void ) +{ + if ( gpGlobals->time > m_flNextPainTime ) + { +#if 0 + if ( RANDOM_LONG(0,99) < 5 ) + { + // pain sentences are rare + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz(ENT(pev), "HG_PAIN", HGRUNT_SENTENCE_VOLUME, ATTN_NORM, 0, PITCH_NORM); + JustSpoke(); + return; + } + } +#endif + switch ( RANDOM_LONG(0,6) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain5.wav", 1, ATTN_NORM ); + break; + case 3: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain1.wav", 1, ATTN_NORM ); + break; + case 4: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_pain2.wav", 1, ATTN_NORM ); + break; + } + + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CHGrunt :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_die1.wav", 1, ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_die2.wav", 1, ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "hgrunt/gr_die3.wav", 1, ATTN_IDLE ); + break; + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +//========================================================= +// GruntFail +//========================================================= +Task_t tlGruntFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntFail[] = +{ + { + tlGruntFail, + ARRAYSIZE ( tlGruntFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + 0, + "Grunt Fail" + }, +}; + +//========================================================= +// Grunt Combat Fail +//========================================================= +Task_t tlGruntCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGruntCombatFail[] = +{ + { + tlGruntCombatFail, + ARRAYSIZE ( tlGruntCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Grunt Combat Fail" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlGruntVictoryDance[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_GET_PATH_TO_ENEMY_CORPSE, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_VICTORY_DANCE }, +}; + +Schedule_t slGruntVictoryDance[] = +{ + { + tlGruntVictoryDance, + ARRAYSIZE ( tlGruntVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "GruntVictoryDance" + }, +}; + +//========================================================= +// Establish line of fire - move to a position that allows +// the grunt to attack. +//========================================================= +Task_t tlGruntEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slGruntEstablishLineOfFire[] = +{ + { + tlGruntEstablishLineOfFire, + ARRAYSIZE ( tlGruntEstablishLineOfFire ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntEstablishLineOfFire" + }, +}; + +//========================================================= +// GruntFoundEnemy - grunt established sight with an enemy +// that was hiding from the squad. +//========================================================= +Task_t tlGruntFoundEnemy[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_SIGNAL1 }, +}; + +Schedule_t slGruntFoundEnemy[] = +{ + { + tlGruntFoundEnemy, + ARRAYSIZE ( tlGruntFoundEnemy ), + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntFoundEnemy" + }, +}; + +//========================================================= +// GruntCombatFace Schedule +//========================================================= +Task_t tlGruntCombatFace1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_WAIT, (float)1.5 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_SWEEP }, +}; + +Schedule_t slGruntCombatFace[] = +{ + { + tlGruntCombatFace1, + ARRAYSIZE ( tlGruntCombatFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Suppressing fire - don't stop shooting until the clip is +// empty or grunt gets hurt. +//========================================================= +Task_t tlGruntSignalSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSignalSuppress[] = +{ + { + tlGruntSignalSuppress, + ARRAYSIZE ( tlGruntSignalSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "SignalSuppress" + }, +}; + +Task_t tlGruntSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntSuppress[] = +{ + { + tlGruntSuppress, + ARRAYSIZE ( tlGruntSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Suppress" + }, +}; + + +//========================================================= +// grunt wait in cover - we don't allow danger or the ability +// to attack to break a grunt's run to cover schedule, but +// when a grunt is in cover, we do want them to attack if they can. +//========================================================= +Task_t tlGruntWaitInCover[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slGruntWaitInCover[] = +{ + { + tlGruntWaitInCover, + ARRAYSIZE ( tlGruntWaitInCover ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + + bits_SOUND_DANGER, + "GruntWaitInCover" + }, +}; + +//========================================================= +// run to cover. +// !!!BUGBUG - set a decent fail schedule here. +//========================================================= +Task_t tlGruntTakeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GRUNT_TAKECOVER_FAILED }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_GRUNT_SPEAK_SENTENCE, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntTakeCover[] = +{ + { + tlGruntTakeCover1, + ARRAYSIZE ( tlGruntTakeCover1 ), + 0, + 0, + "TakeCover" + }, +}; + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntGrenadeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)99 }, + { TASK_FIND_FAR_NODE_COVER_FROM_ENEMY, (float)384 }, + { TASK_PLAY_SEQUENCE, (float)ACT_SPECIAL_ATTACK1 }, + { TASK_CLEAR_MOVE_WAIT, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGruntGrenadeCover[] = +{ + { + tlGruntGrenadeCover1, + ARRAYSIZE ( tlGruntGrenadeCover1 ), + 0, + 0, + "GrenadeCover" + }, +}; + + +//========================================================= +// drop grenade then run to cover. +//========================================================= +Task_t tlGruntTossGrenadeCover1[] = +{ + { TASK_FACE_ENEMY, (float)0 }, + { TASK_RANGE_ATTACK2, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slGruntTossGrenadeCover[] = +{ + { + tlGruntTossGrenadeCover1, + ARRAYSIZE ( tlGruntTossGrenadeCover1 ), + 0, + 0, + "TossGrenadeCover" + }, +}; + +//========================================================= +// hide from the loudest sound source (to run from grenade) +//========================================================= +Task_t tlGruntTakeCoverFromBestSound[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_COWER },// duck and cover if cannot move from explosion + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_BEST_SOUND, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_TURN_LEFT, (float)179 }, +}; + +Schedule_t slGruntTakeCoverFromBestSound[] = +{ + { + tlGruntTakeCoverFromBestSound, + ARRAYSIZE ( tlGruntTakeCoverFromBestSound ), + 0, + 0, + "GruntTakeCoverFromBestSound" + }, +}; + +//========================================================= +// Grunt reload schedule +//========================================================= +Task_t tlGruntHideReload[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RELOAD }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_REMEMBER, (float)bits_MEMORY_INCOVER }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RELOAD }, +}; + +Schedule_t slGruntHideReload[] = +{ + { + tlGruntHideReload, + ARRAYSIZE ( tlGruntHideReload ), + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GruntHideReload" + } +}; + +//========================================================= +// Do a turning sweep of the area +//========================================================= +Task_t tlGruntSweep[] = +{ + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slGruntSweep[] = +{ + { + tlGruntSweep, + ARRAYSIZE ( tlGruntSweep ), + + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_WORLD |// sound flags + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + + "Grunt Sweep" + }, +}; + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack1A[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1A[] = +{ + { + tlGruntRangeAttack1A, + ARRAYSIZE ( tlGruntRangeAttack1A ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND | + bits_COND_GRUNT_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Range Attack1A" + }, +}; + + +//========================================================= +// primary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack1B[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_IDLE_ANGRY }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GRUNT_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGruntRangeAttack1B[] = +{ + { + tlGruntRangeAttack1B, + ARRAYSIZE ( tlGruntRangeAttack1B ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_GRUNT_NOFIRE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack1B" + }, +}; + +//========================================================= +// secondary range attack. Overriden because base class stops attacking when the enemy is occluded. +// grunt's grenade toss requires the enemy be occluded. +//========================================================= +Task_t tlGruntRangeAttack2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_GRUNT_FACE_TOSS_DIR, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_RANGE_ATTACK2 }, + { TASK_SET_SCHEDULE, (float)SCHED_GRUNT_WAIT_FACE_ENEMY },// don't run immediately after throwing grenade. +}; + +Schedule_t slGruntRangeAttack2[] = +{ + { + tlGruntRangeAttack2, + ARRAYSIZE ( tlGruntRangeAttack2 ), + 0, + 0, + "RangeAttack2" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepel[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_GLIDE }, +}; + +Schedule_t slGruntRepel[] = +{ + { + tlGruntRepel, + ARRAYSIZE ( tlGruntRepel ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel" + }, +}; + + +//========================================================= +// repel +//========================================================= +Task_t tlGruntRepelAttack[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_FLY }, +}; + +Schedule_t slGruntRepelAttack[] = +{ + { + tlGruntRepelAttack, + ARRAYSIZE ( tlGruntRepelAttack ), + bits_COND_ENEMY_OCCLUDED, + 0, + "Repel Attack" + }, +}; + +//========================================================= +// repel land +//========================================================= +Task_t tlGruntRepelLand[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_LAND }, + { TASK_GET_PATH_TO_LASTPOSITION,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_CLEAR_LASTPOSITION, (float)0 }, +}; + +Schedule_t slGruntRepelLand[] = +{ + { + tlGruntRepelLand, + ARRAYSIZE ( tlGruntRepelLand ), + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER, + "Repel Land" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CHGrunt ) +{ + slGruntFail, + slGruntCombatFail, + slGruntVictoryDance, + slGruntEstablishLineOfFire, + slGruntFoundEnemy, + slGruntCombatFace, + slGruntSignalSuppress, + slGruntSuppress, + slGruntWaitInCover, + slGruntTakeCover, + slGruntGrenadeCover, + slGruntTossGrenadeCover, + slGruntTakeCoverFromBestSound, + slGruntHideReload, + slGruntSweep, + slGruntRangeAttack1A, + slGruntRangeAttack1B, + slGruntRangeAttack2, + slGruntRepel, + slGruntRepelAttack, + slGruntRepelLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHGrunt, CSquadMonster ); + +//========================================================= +// SetActivity +//========================================================= +void CHGrunt :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_RANGE_ATTACK1: + // grunt is either shooting standing or shooting crouched + if (FBitSet( pev->weapons, HGRUNT_9MMAR)) + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing_mp5" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching_mp5" ); + } + } + else + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "standing_shotgun" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouching_shotgun" ); + } + } + break; + case ACT_RANGE_ATTACK2: + // grunt is going to a secondary long range attack. This may be a thrown + // grenade or fired grenade, we must determine which and pick proper sequence + if ( pev->weapons & HGRUNT_HANDGRENADE ) + { + // get toss anim + iSequence = LookupSequence( "throwgrenade" ); + } + else + { + // get launch anim + iSequence = LookupSequence( "launchgrenade" ); + } + break; + case ACT_RUN: + if ( pev->health <= HGRUNT_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_RUN_HURT ); + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_WALK: + if ( pev->health <= HGRUNT_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_WALK_HURT ); + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_IDLE: + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + NewActivity = ACT_IDLE_ANGRY; + } + iSequence = LookupActivity ( NewActivity ); + break; + default: + iSequence = LookupActivity ( NewActivity ); + break; + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + SetYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_console, "%s has no sequence for act:%d\n", STRING(pev->classname), NewActivity ); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} + +//========================================================= +// Get Schedule! +//========================================================= +Schedule_t *CHGrunt :: GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = HGRUNT_SENT_NONE; + + // flying? If PRONE, barnacle has me. IF not, it's assumed I am rapelling. + if ( pev->movetype == MOVETYPE_FLY && m_MonsterState != MONSTERSTATE_PRONE ) + { + if (pev->flags & FL_ONGROUND) + { + // just landed + pev->movetype = MOVETYPE_STEP; + return GetScheduleOfType ( SCHED_GRUNT_REPEL_LAND ); + } + else + { + // repel down a rope, + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + return GetScheduleOfType ( SCHED_GRUNT_REPEL_ATTACK ); + else + return GetScheduleOfType ( SCHED_GRUNT_REPEL ); + } + } + + // grunts place HIGH priority on running away from danger sounds. + if ( HasConditions(bits_COND_HEAR_SOUND) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound) + { + if (pSound->m_iType & bits_SOUND_DANGER) + { + // dangerous sound nearby! + + //!!!KELLY - currently, this is the grunt's signal that a grenade has landed nearby, + // and the grunt should find cover from the blast + // good place for "SHIT!" or some other colorful verbal indicator of dismay. + // It's not safe to play a verbal order here "Scatter", etc cause + // this may only affect a single individual in a squad. + + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "HG_GREN", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + /* + if (!HasConditions( bits_COND_SEE_ENEMY ) && ( pSound->m_iType & (bits_SOUND_PLAYER | bits_SOUND_COMBAT) )) + { + MakeIdealYaw( pSound->m_vecOrigin ); + } + */ + } + } + switch ( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + +// new enemy + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + if ( InSquad() ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + + if ( !IsLeader() ) + { + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + //!!!KELLY - the leader of a squad of grunts has just seen the player or a + // monster and has made it the squad's enemy. You + // can check pev->flags for FL_CLIENT to determine whether this is the player + // or a monster. He's going to immediately start + // firing, though. If you'd like, we can make an alternate "first sight" + // schedule where the leader plays a handsign anim + // that gives us enough time to hear a short sentence or spoken command + // before he starts pluggin away. + if (FOkToSpeak())// && RANDOM_LONG(0,1)) + { + if ((m_hEnemy != NULL) && m_hEnemy->IsPlayer()) + // player + SENTENCEG_PlayRndSz( ENT(pev), "HG_ALERT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + else if ((m_hEnemy != NULL) && + (m_hEnemy->Classify() != CLASS_PLAYER_ALLY) && + (m_hEnemy->Classify() != CLASS_HUMAN_PASSIVE) && + (m_hEnemy->Classify() != CLASS_MACHINE)) + // monster + SENTENCEG_PlayRndSz( ENT(pev), "HG_MONST", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + + JustSpoke(); + } + + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_GRUNT_SUPPRESS ); + } + else + { + return GetScheduleOfType ( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + } +// no ammo + else if ( HasConditions ( bits_COND_NO_AMMO_LOADED ) ) + { + //!!!KELLY - this individual just realized he's out of bullet ammo. + // He's going to try to find cover to run to and reload, but rarely, if + // none is available, he'll drop and reload in the open here. + return GetScheduleOfType ( SCHED_GRUNT_COVER_AND_RELOAD ); + } + +// damaged just a little + else if ( HasConditions( bits_COND_LIGHT_DAMAGE ) ) + { + // if hurt: + // 90% chance of taking cover + // 10% chance of flinch. + int iPercent = RANDOM_LONG(0,99); + + if ( iPercent <= 90 && m_hEnemy != NULL ) + { + // only try to take cover if we actually have an enemy! + + //!!!KELLY - this grunt was hit and is going to run to cover. + if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = HGRUNT_SENT_COVER; + //JustSpoke(); + } + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + else + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + } +// can kick + else if ( HasConditions ( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_MELEE_ATTACK1 ); + } +// can grenade launch + + else if ( FBitSet( pev->weapons, HGRUNT_GRENADELAUNCHER) && HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // shoot a grenade if you can + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } +// can shoot + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + if ( InSquad() ) + { + // if the enemy has eluded the squad and a squad member has just located the enemy + // and the enemy does not see the squad member, issue a call to the squad to waste a + // little time and give the player a chance to turn. + if ( MySquadLeader()->m_fEnemyEluded && !HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + MySquadLeader()->m_fEnemyEluded = FALSE; + return GetScheduleOfType ( SCHED_GRUNT_FOUND_ENEMY ); + } + } + + if ( OccupySlot ( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + // try to take an available ENGAGE slot + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + else if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + // throw a grenade if can and no engage slots are available + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else + { + // hide! + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } +// can't see enemy + else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + //!!!KELLY - this grunt is about to throw or fire a grenade at the player. Great place for "fire in the hole" "frag out" etc + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "HG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else if ( OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + //!!!KELLY - grunt cannot see the enemy and has just decided to + // charge the enemy's position. + if (FOkToSpeak())// && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_CHARGE", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + m_iSentence = HGRUNT_SENT_CHARGE; + //JustSpoke(); + } + + return GetScheduleOfType( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + else + { + //!!!KELLY - grunt is going to stay put for a couple seconds to see if + // the enemy wanders back out into the open, or approaches the + // grunt's covered position. Good place for a taunt, I guess? + if (FOkToSpeak() && RANDOM_LONG(0,1)) + { + SENTENCEG_PlayRndSz( ENT(pev), "HG_TAUNT", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE ); + } + } + } + + // no special cases here, call the base class + return CSquadMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CHGrunt :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + { + if ( InSquad() ) + { + if ( g_iSkillLevel == SKILL_HARD && HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) && OccupySlot( bits_SLOTS_HGRUNT_GRENADE ) ) + { + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), "HG_THROW", HGRUNT_SENTENCE_VOLUME, GRUNT_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return slGruntTossGrenadeCover; + } + else + { + return &slGruntTakeCover[ 0 ]; + } + } + else + { + if ( RANDOM_LONG(0,1) ) + { + return &slGruntTakeCover[ 0 ]; + } + else + { + return &slGruntGrenadeCover[ 0 ]; + } + } + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slGruntTakeCoverFromBestSound[ 0 ]; + } + case SCHED_GRUNT_TAKECOVER_FAILED: + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot( bits_SLOTS_HGRUNT_ENGAGE ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType ( SCHED_FAIL ); + } + break; + case SCHED_GRUNT_ELOF_FAIL: + { + // human grunt is unable to move to a position that allows him to attack the enemy. + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + case SCHED_GRUNT_ESTABLISH_LINE_OF_FIRE: + { + return &slGruntEstablishLineOfFire[ 0 ]; + } + break; + case SCHED_RANGE_ATTACK1: + { + // randomly stand or crouch + if (RANDOM_LONG(0,9) == 0) + m_fStanding = RANDOM_LONG(0,1); + + if (m_fStanding) + return &slGruntRangeAttack1B[ 0 ]; + else + return &slGruntRangeAttack1A[ 0 ]; + } + case SCHED_RANGE_ATTACK2: + { + return &slGruntRangeAttack2[ 0 ]; + } + case SCHED_COMBAT_FACE: + { + return &slGruntCombatFace[ 0 ]; + } + case SCHED_GRUNT_WAIT_FACE_ENEMY: + { + return &slGruntWaitInCover[ 0 ]; + } + case SCHED_GRUNT_SWEEP: + { + return &slGruntSweep[ 0 ]; + } + case SCHED_GRUNT_COVER_AND_RELOAD: + { + return &slGruntHideReload[ 0 ]; + } + case SCHED_GRUNT_FOUND_ENEMY: + { + return &slGruntFoundEnemy[ 0 ]; + } + case SCHED_VICTORY_DANCE: + { + if ( InSquad() ) + { + if ( !IsLeader() ) + { + return &slGruntFail[ 0 ]; + } + } + + return &slGruntVictoryDance[ 0 ]; + } + case SCHED_GRUNT_SUPPRESS: + { + if ( m_hEnemy->IsPlayer() && m_fFirstEncounter ) + { + m_fFirstEncounter = FALSE;// after first encounter, leader won't issue handsigns anymore when he has a new enemy + return &slGruntSignalSuppress[ 0 ]; + } + else + { + return &slGruntSuppress[ 0 ]; + } + } + case SCHED_FAIL: + { + if ( m_hEnemy != NULL ) + { + // grunt has an enemy, so pick a different default fail schedule most likely to help recover. + return &slGruntCombatFail[ 0 ]; + } + + return &slGruntFail[ 0 ]; + } + case SCHED_GRUNT_REPEL: + { + if (pev->velocity.z > -128) + pev->velocity.z -= 32; + return &slGruntRepel[ 0 ]; + } + case SCHED_GRUNT_REPEL_ATTACK: + { + if (pev->velocity.z > -128) + pev->velocity.z -= 32; + return &slGruntRepelAttack[ 0 ]; + } + case SCHED_GRUNT_REPEL_LAND: + { + return &slGruntRepelLand[ 0 ]; + } + default: + { + return CSquadMonster :: GetScheduleOfType ( Type ); + } + } +} + + +//========================================================= +// CHGruntRepel - when triggered, spawns a monster_human_grunt +// repelling down a line. +//========================================================= + +class CHGruntRepel : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + int m_iSpriteTexture; // Don't save, precache +}; + +LINK_ENTITY_TO_CLASS( monster_grunt_repel, CHGruntRepel ); + +void CHGruntRepel::Spawn( void ) +{ + Precache( ); + pev->solid = SOLID_NOT; + + SetUse( RepelUse ); +} + +void CHGruntRepel::Precache( void ) +{ + UTIL_PrecacheOther( "monster_human_grunt" ); + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); +} + +void CHGruntRepel::RepelUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, -4096.0), dont_ignore_monsters, ENT(pev), &tr); + /* + if ( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP) + return NULL; + */ + + CBaseEntity *pEntity = Create( "monster_human_grunt", pev->origin, pev->angles ); + CBaseMonster *pGrunt = pEntity->MyMonsterPointer( ); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); + pGrunt->SetActivity( ACT_GLIDE ); + // UNDONE: position? + pGrunt->m_vecLastPosition = tr.vecEndPos; + + CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); + pBeam->PointEntInit( pev->origin + Vector(0,0,112), pGrunt->entindex() ); + pBeam->SetFlags( BEAM_FSOLID ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetThink( SUB_Remove ); + pBeam->pev->nextthink = gpGlobals->time + -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5; + + UTIL_Remove( this ); +} + + + +//========================================================= +// DEAD HGRUNT PROP +//========================================================= +class CDeadHGrunt : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_MILITARY; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static char *m_szPoses[3]; +}; + +char *CDeadHGrunt::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" }; + +void CDeadHGrunt::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +LINK_ENTITY_TO_CLASS( monster_hgrunt_dead, CDeadHGrunt ); + +//========================================================= +// ********** DeadHGrunt SPAWN ********** +//========================================================= +void CDeadHGrunt :: Spawn( void ) +{ + PRECACHE_MODEL("models/hgrunt.mdl"); + SET_MODEL(ENT(pev), "models/hgrunt.mdl"); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead hgrunt with bad pose\n" ); + } + + // Corpses have less health + pev->health = 8; + + // map old bodies onto new bodies + switch( pev->body ) + { + case 0: // Grunt with Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_GRUNT ); + SetBodygroup( GUN_GROUP, GUN_MP5 ); + break; + case 1: // Commander with Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_COMMANDER ); + SetBodygroup( GUN_GROUP, GUN_MP5 ); + break; + case 2: // Grunt no Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_GRUNT ); + SetBodygroup( GUN_GROUP, GUN_NONE ); + break; + case 3: // Commander no Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_COMMANDER ); + SetBodygroup( GUN_GROUP, GUN_NONE ); + break; + } + + MonsterInitDead(); +} diff --git a/dlls/mp.def b/dlls/hl.def similarity index 78% rename from dlls/mp.def rename to dlls/hl.def index 5ae6f18..c009191 100644 --- a/dlls/mp.def +++ b/dlls/hl.def @@ -1,4 +1,4 @@ -LIBRARY mp +LIBRARY hl EXPORTS GiveFnptrsToDll @1 SECTIONS diff --git a/dlls/mp.dsp b/dlls/hl.dsp similarity index 66% rename from dlls/mp.dsp rename to dlls/hl.dsp index 17392a3..2e4882b 100644 --- a/dlls/mp.dsp +++ b/dlls/hl.dsp @@ -1,36 +1,36 @@ -# Microsoft Developer Studio Project File - Name="mp" - Package Owner=<4> +# Microsoft Developer Studio Project File - Name="hl" - Package Owner=<4> # Microsoft Developer Studio Generated Build File, Format Version 6.00 # ** DO NOT EDIT ** # TARGTYPE "Win32 (x86) Dynamic-Link Library" 0x0102 -CFG=mp - Win32 Release +CFG=hl - Win32 Release !MESSAGE This is not a valid makefile. To build this project using NMAKE, !MESSAGE use the Export Makefile command and run !MESSAGE -!MESSAGE NMAKE /f "mp.mak". +!MESSAGE NMAKE /f "hl.mak". !MESSAGE !MESSAGE You can specify a configuration when running NMAKE !MESSAGE by defining the macro CFG on the command line. For example: !MESSAGE -!MESSAGE NMAKE /f "mp.mak" CFG="mp - Win32 Release" +!MESSAGE NMAKE /f "hl.mak" CFG="hl - 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 "hl - Win32 Release" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "hl - Win32 Debug" (based on "Win32 (x86) Dynamic-Link Library") +!MESSAGE "hl - Win32 Profile" (based on "Win32 (x86) Dynamic-Link Library") !MESSAGE # Begin Project # PROP AllowPerConfigDependencies 0 -# PROP Scc_ProjName ""$/SDK/Standard/dlls", NVGBAAAA" -# PROP Scc_LocalPath "." +# PROP Scc_ProjName "" +# PROP Scc_LocalPath "" CPP=cl.exe MTL=midl.exe RSC=rc.exe -!IF "$(CFG)" == "mp - Win32 Release" +!IF "$(CFG)" == "hl - Win32 Release" # PROP BASE Use_MFC 0 # PROP BASE Use_Debug_Libraries 0 @@ -39,13 +39,11 @@ RSC=rc.exe # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 0 -# PROP Output_Dir ".\Releasemp" -# PROP Intermediate_Dir ".\Releasemp" -# PROP Ignore_Export_Lib 0 +# PROP Output_Dir ".\Releasehl" +# PROP Intermediate_Dir ".\Releasehl" # 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 "..\dlls" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\game_shared" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "VALVE_DLL" /D "CLIENT_WEAPONS" /YX /FD /c -# SUBTRACT CPP /Fr +# ADD CPP /nologo /G5 /MT /W3 /Zi /O2 /I "..\dlls" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\game_shared" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /D "CLIENT_WEAPONS" /Fr /YX /FD /c # ADD BASE MTL /nologo /D "NDEBUG" /win32 # ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x409 /d "NDEBUG" @@ -55,23 +53,24 @@ BSC32=bscmake.exe # 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" +# ADD LINK32 kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib /nologo /subsystem:windows /dll /map /debug /machine:I386 /def:".\hl.def" # SUBTRACT LINK32 /profile -!ELSEIF "$(CFG)" == "mp - Win32 Debug" +!ELSEIF "$(CFG)" == "hl - 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 Output_Dir ".\hl___Win" +# PROP BASE Intermediate_Dir ".\hl___Win" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 1 -# PROP Output_Dir ".\debugmp" -# PROP Intermediate_Dir ".\debugmp" +# PROP Output_Dir ".\debughl" +# PROP Intermediate_Dir ".\debughl" +# PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /MTd /W3 /Gm /GX /Zi /Od /D "WIN32" /D "_DEBUG" /D "_WINDOWS" /YX /c -# ADD CPP /nologo /G5 /MTd /W3 /Gm /GX /ZI /Od /I "..\dlls" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\game_shared" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "VALVE_DLL" /D "CLIENT_WEAPONS" /FR /YX /FD /c +# ADD CPP /nologo /G5 /MTd /W3 /Gm /ZI /Od /I "..\dlls" /I "..\engine" /I "..\common" /I "..\game_shared" /I "..\pm_shared" /I "..\\" /D "_DEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /D "CLIENT_WEAPONS" /FR /YX /FD /c # ADD BASE MTL /nologo /D "_DEBUG" /win32 # ADD MTL /nologo /D "_DEBUG" /mktyplib203 /win32 # ADD BASE RSC /l 0x409 /d "_DEBUG" @@ -81,24 +80,24 @@ BSC32=bscmake.exe # 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" +# ADD LINK32 user32.lib advapi32.lib /nologo /subsystem:windows /dll /debug /machine:I386 /def:".\hl.def" /implib:".\Debug\hl.lib" # SUBTRACT LINK32 /profile -!ELSEIF "$(CFG)" == "mp - Win32 Profile" +!ELSEIF "$(CFG)" == "hl - 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 Output_Dir ".\hl___Win" +# PROP BASE Intermediate_Dir ".\hl___Win" # PROP BASE Target_Dir "" # PROP Use_MFC 0 # PROP Use_Debug_Libraries 0 -# PROP Output_Dir ".\Profilemp" -# PROP Intermediate_Dir ".\Profilemp" +# PROP Output_Dir ".\Profilehl" +# PROP Intermediate_Dir ".\Profilehl" # 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 "..\dlls" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\game_shared" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "VALVE_DLL" /D "CLIENT_WEAPONS" /YX /FD /c +# ADD CPP /nologo /G5 /MT /W3 /Zi /O2 /I "..\dlls" /I "..\engine" /I "..\common" /I "..\pm_shared" /I "..\game_shared" /D "NDEBUG" /D "WIN32" /D "_WINDOWS" /D "QUIVER" /D "VOXEL" /D "QUAKE2" /D "VALVE_DLL" /D "CLIENT_WEAPONS" /YX /FD /c # SUBTRACT CPP /Fr # ADD BASE MTL /nologo /D "NDEBUG" /win32 # ADD MTL /nologo /D "NDEBUG" /mktyplib203 /win32 @@ -108,22 +107,30 @@ 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" +# 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:".\hl.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" +# 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:".\hl.def" !ENDIF # Begin Target -# Name "mp - Win32 Release" -# Name "mp - Win32 Debug" -# Name "mp - Win32 Profile" +# Name "hl - Win32 Release" +# Name "hl - Win32 Debug" +# Name "hl - 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=.\aflock.cpp +# End Source File +# Begin Source File + +SOURCE=.\agrunt.cpp +# End Source File +# Begin Source File + SOURCE=.\airtank.cpp # End Source File # Begin Source File @@ -136,10 +143,34 @@ SOURCE=.\animation.cpp # End Source File # Begin Source File +SOURCE=.\apache.cpp +# End Source File +# Begin Source File + +SOURCE=.\barnacle.cpp +# End Source File +# Begin Source File + +SOURCE=.\barney.cpp +# End Source File +# Begin Source File + +SOURCE=.\bigmomma.cpp +# End Source File +# Begin Source File + +SOURCE=.\bloater.cpp +# End Source File +# Begin Source File + SOURCE=.\bmodels.cpp # End Source File # Begin Source File +SOURCE=.\bullsquid.cpp +# End Source File +# Begin Source File + SOURCE=.\buttons.cpp # End Source File # Begin Source File @@ -156,6 +187,10 @@ SOURCE=.\combat.cpp # End Source File # Begin Source File +SOURCE=.\controller.cpp +# End Source File +# Begin Source File + SOURCE=.\crossbow.cpp # End Source File # Begin Source File @@ -164,6 +199,10 @@ SOURCE=.\crowbar.cpp # End Source File # Begin Source File +SOURCE=.\defaultai.cpp +# End Source File +# Begin Source File + SOURCE=.\doors.cpp # End Source File # Begin Source File @@ -180,6 +219,10 @@ SOURCE=.\explode.cpp # End Source File # Begin Source File +SOURCE=.\flyingmonster.cpp +# End Source File +# Begin Source File + SOURCE=.\func_break.cpp # End Source File # Begin Source File @@ -196,10 +239,18 @@ SOURCE=.\gamerules.cpp # End Source File # Begin Source File +SOURCE=.\gargantua.cpp +# End Source File +# Begin Source File + SOURCE=.\gauss.cpp # End Source File # Begin Source File +SOURCE=.\genericmonster.cpp +# End Source File +# Begin Source File + SOURCE=.\ggrenade.cpp # End Source File # Begin Source File @@ -208,6 +259,10 @@ SOURCE=.\globals.cpp # End Source File # Begin Source File +SOURCE=.\gman.cpp +# End Source File +# Begin Source File + SOURCE=.\h_ai.cpp # End Source File # Begin Source File @@ -216,6 +271,10 @@ SOURCE=.\h_battery.cpp # End Source File # Begin Source File +SOURCE=.\h_cine.cpp +# End Source File +# Begin Source File + SOURCE=.\h_cycler.cpp # End Source File # Begin Source File @@ -228,10 +287,22 @@ SOURCE=.\handgrenade.cpp # End Source File # Begin Source File +SOURCE=.\hassassin.cpp +# End Source File +# Begin Source File + +SOURCE=.\headcrab.cpp +# End Source File +# Begin Source File + SOURCE=.\healthkit.cpp # End Source File # Begin Source File +SOURCE=.\hgrunt.cpp +# End Source File +# Begin Source File + SOURCE=.\wpn_shared\hl_wpn_glock.cpp # End Source File # Begin Source File @@ -244,7 +315,15 @@ SOURCE=.\hornetgun.cpp # End Source File # Begin Source File -SOURCE=..\common\interface.cpp +SOURCE=.\houndeye.cpp +# End Source File +# Begin Source File + +SOURCE=.\ichthyosaur.cpp +# End Source File +# Begin Source File + +SOURCE=.\islave.cpp # End Source File # Begin Source File @@ -252,6 +331,10 @@ SOURCE=.\items.cpp # End Source File # Begin Source File +SOURCE=.\leech.cpp +# End Source File +# Begin Source File + SOURCE=.\lights.cpp # End Source File # Begin Source File @@ -260,6 +343,18 @@ SOURCE=.\maprules.cpp # End Source File # Begin Source File +SOURCE=.\monstermaker.cpp +# End Source File +# Begin Source File + +SOURCE=.\monsters.cpp +# End Source File +# Begin Source File + +SOURCE=.\monsterstate.cpp +# End Source File +# Begin Source File + SOURCE=.\mortar.cpp # End Source File # Begin Source File @@ -268,11 +363,19 @@ SOURCE=.\mp5.cpp # End Source File # Begin Source File -SOURCE=.\mpstubb.cpp +SOURCE=.\multiplay_gamerules.cpp # End Source File # Begin Source File -SOURCE=.\multiplay_gamerules.cpp +SOURCE=.\nihilanth.cpp +# End Source File +# Begin Source File + +SOURCE=.\nodes.cpp +# End Source File +# Begin Source File + +SOURCE=.\osprey.cpp # End Source File # Begin Source File @@ -308,6 +411,14 @@ SOURCE=.\python.cpp # End Source File # Begin Source File +SOURCE=.\rat.cpp +# End Source File +# Begin Source File + +SOURCE=.\roach.cpp +# End Source File +# Begin Source File + SOURCE=.\rpg.cpp # End Source File # Begin Source File @@ -316,6 +427,18 @@ SOURCE=.\satchel.cpp # End Source File # Begin Source File +SOURCE=.\schedule.cpp +# End Source File +# Begin Source File + +SOURCE=.\scientist.cpp +# End Source File +# Begin Source File + +SOURCE=.\scripted.cpp +# End Source File +# Begin Source File + SOURCE=.\shotgun.cpp # End Source File # Begin Source File @@ -340,6 +463,10 @@ SOURCE=.\spectator.cpp # End Source File # Begin Source File +SOURCE=.\squadmonster.cpp +# End Source File +# Begin Source File + SOURCE=.\squeakgrenade.cpp # End Source File # Begin Source File @@ -348,10 +475,22 @@ SOURCE=.\subs.cpp # End Source File # Begin Source File +SOURCE=.\talkmonster.cpp +# End Source File +# Begin Source File + SOURCE=.\teamplay_gamerules.cpp # End Source File # Begin Source File +SOURCE=.\tempmonster.cpp +# End Source File +# Begin Source File + +SOURCE=.\tentacle.cpp +# End Source File +# Begin Source File + SOURCE=.\triggers.cpp # End Source File # Begin Source File @@ -360,6 +499,10 @@ SOURCE=.\tripmine.cpp # End Source File # Begin Source File +SOURCE=.\turret.cpp +# End Source File +# Begin Source File + SOURCE=.\util.cpp # End Source File # Begin Source File @@ -378,6 +521,10 @@ SOURCE=.\world.cpp SOURCE=.\xen.cpp # End Source File +# Begin Source File + +SOURCE=.\zombie.cpp +# End Source File # End Group # Begin Group "Header Files" @@ -416,6 +563,10 @@ SOURCE=.\decals.h # End Source File # Begin Source File +SOURCE=.\defaultai.h +# End Source File +# Begin Source File + SOURCE=.\doors.h # End Source File # Begin Source File @@ -424,6 +575,10 @@ SOURCE=.\effects.h # End Source File # Begin Source File +SOURCE=..\engine\eiface.h +# End Source File +# Begin Source File + SOURCE=.\enginecallback.h # End Source File # Begin Source File @@ -436,11 +591,11 @@ SOURCE=.\extdll.h # End Source File # Begin Source File -SOURCE=.\func_break.h +SOURCE=.\flyingmonster.h # End Source File # Begin Source File -SOURCE=.\game.h +SOURCE=.\func_break.h # End Source File # Begin Source File @@ -456,10 +611,6 @@ 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 @@ -512,6 +663,10 @@ SOURCE=.\schedule.h # End Source File # Begin Source File +SOURCE=.\scripted.h +# End Source File +# Begin Source File + SOURCE=.\scriptevent.h # End Source File # Begin Source File @@ -528,6 +683,10 @@ SOURCE=.\spectator.h # End Source File # Begin Source File +SOURCE=.\squadmonster.h +# End Source File +# Begin Source File + SOURCE=.\talkmonster.h # End Source File # Begin Source File diff --git a/dlls/hlgl.def b/dlls/hlgl.def new file mode 100644 index 0000000..883f3e5 --- /dev/null +++ b/dlls/hlgl.def @@ -0,0 +1,15 @@ +LIBRARY hlgl +EXPORTS + GiveFnptrsToDll @1 + GetEntityInterfaces @2 + SetChangeParms @3 + SetNewParms @4 + ClientKill @5 + PutClientInServer @6 + PlayerPreThink @7 + PlayerPostThink @8 + ClientConnect @9 + ClientDisconnect @10 + StartFrame @11 +SECTIONS + .data READ WRITE diff --git a/dlls/hornet.cpp b/dlls/hornet.cpp index 8013868..288a7db 100644 --- a/dlls/hornet.cpp +++ b/dlls/hornet.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/hornet.h b/dlls/hornet.h index c0eee3a..f069c3d 100644 --- a/dlls/hornet.h +++ b/dlls/hornet.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/hornetgun.cpp b/dlls/hornetgun.cpp index 9c58fbe..76b4889 100644 --- a/dlls/hornetgun.cpp +++ b/dlls/hornetgun.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -258,7 +258,7 @@ void CHgun::SecondaryAttack( void ) m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.1; - m_flTimeWeaponIdle = gpGlobals->time + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); } @@ -279,7 +279,7 @@ void CHgun::WeaponIdle( void ) { Reload( ); - if (m_flTimeWeaponIdle > gpGlobals->time) + if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) return; int iAnim; diff --git a/dlls/houndeye.cpp b/dlls/houndeye.cpp new file mode 100644 index 0000000..e8bfacf --- /dev/null +++ b/dlls/houndeye.cpp @@ -0,0 +1,1304 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Houndeye - spooky sonic dog. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "animation.h" +#include "nodes.h" +#include "squadmonster.h" +#include "soundent.h" +#include "game.h" + +extern CGraph WorldGraph; + +// houndeye does 20 points of damage spread over a sphere 384 units in diameter, and each additional +// squad member increases the BASE damage by 110%, per the spec. +#define HOUNDEYE_MAX_SQUAD_SIZE 4 +#define HOUNDEYE_MAX_ATTACK_RADIUS 384 +#define HOUNDEYE_SQUAD_BONUS (float)1.1 + +#define HOUNDEYE_EYE_FRAMES 4 // how many different switchable maps for the eye + +#define HOUNDEYE_SOUND_STARTLE_VOLUME 128 // how loud a sound has to be to badly scare a sleeping houndeye + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_HOUND_CLOSE_EYE = LAST_COMMON_TASK + 1, + TASK_HOUND_OPEN_EYE, + TASK_HOUND_THREAT_DISPLAY, + TASK_HOUND_FALL_ASLEEP, + TASK_HOUND_WAKE_UP, + TASK_HOUND_HOP_BACK +}; + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_HOUND_AGITATED = LAST_COMMON_SCHEDULE + 1, + SCHED_HOUND_HOP_RETREAT, + SCHED_HOUND_FAIL, +}; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HOUND_AE_WARN 1 +#define HOUND_AE_STARTATTACK 2 +#define HOUND_AE_THUMP 3 +#define HOUND_AE_ANGERSOUND1 4 +#define HOUND_AE_ANGERSOUND2 5 +#define HOUND_AE_HOPBACK 6 +#define HOUND_AE_CLOSE_EYE 7 + +class CHoundeye : public CSquadMonster +{ +public: + void Spawn( void ); + void Precache( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void SetYawSpeed ( void ); + void WarmUpSound ( void ); + void AlertSound( void ); + void DeathSound( void ); + void WarnSound( void ); + void PainSound( void ); + void IdleSound( void ); + void StartTask( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + void SonicAttack( void ); + void PrescheduleThink( void ); + void SetActivity ( Activity NewActivity ); + void WriteBeamColor ( void ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL FValidateHintType ( short sHint ); + BOOL FCanActiveIdle ( void ); + Schedule_t *GetScheduleOfType ( int Type ); + Schedule_t *CHoundeye :: GetSchedule( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + int m_iSpriteTexture; + BOOL m_fAsleep;// some houndeyes sleep in idle mode if this is set, the houndeye is lying down + BOOL m_fDontBlink;// don't try to open/close eye if this bit is set! + Vector m_vecPackCenter; // the center of the pack. The leader maintains this by averaging the origins of all pack members. +}; +LINK_ENTITY_TO_CLASS( monster_houndeye, CHoundeye ); + +TYPEDESCRIPTION CHoundeye::m_SaveData[] = +{ + DEFINE_FIELD( CHoundeye, m_iSpriteTexture, FIELD_INTEGER ), + DEFINE_FIELD( CHoundeye, m_fAsleep, FIELD_BOOLEAN ), + DEFINE_FIELD( CHoundeye, m_fDontBlink, FIELD_BOOLEAN ), + DEFINE_FIELD( CHoundeye, m_vecPackCenter, FIELD_POSITION_VECTOR ), +}; + +IMPLEMENT_SAVERESTORE( CHoundeye, CSquadMonster ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHoundeye :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// FValidateHintType +//========================================================= +BOOL CHoundeye :: FValidateHintType ( short sHint ) +{ + int i; + + static short sHoundHints[] = + { + HINT_WORLD_MACHINERY, + HINT_WORLD_BLINKING_LIGHT, + HINT_WORLD_HUMAN_BLOOD, + HINT_WORLD_ALIEN_BLOOD, + }; + + for ( i = 0 ; i < ARRAYSIZE ( sHoundHints ) ; i++ ) + { + if ( sHoundHints[ i ] == sHint ) + { + return TRUE; + } + } + + ALERT ( at_aiconsole, "Couldn't validate hint type" ); + return FALSE; +} + + +//========================================================= +// FCanActiveIdle +//========================================================= +BOOL CHoundeye :: FCanActiveIdle ( void ) +{ + if ( InSquad() ) + { + CSquadMonster *pSquadLeader = MySquadLeader(); + + for (int i = 0; i < MAX_SQUAD_MEMBERS;i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + + if ( pMember != NULL && pMember != this && pMember->m_iHintNode != NO_NODE ) + { + // someone else in the group is active idling right now! + return FALSE; + } + } + + return TRUE; + } + + return TRUE; +} + + +//========================================================= +// CheckRangeAttack1 - overridden for houndeyes so that they +// try to get within half of their max attack radius before +// attacking, so as to increase their chances of doing damage. +//========================================================= +BOOL CHoundeye :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist <= ( HOUNDEYE_MAX_ATTACK_RADIUS * 0.5 ) && flDot >= 0.3 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CHoundeye :: SetYawSpeed ( void ) +{ + int ys; + + ys = 90; + + switch ( m_Activity ) + { + case ACT_CROUCHIDLE://sleeping! + ys = 0; + break; + case ACT_IDLE: + ys = 60; + break; + case ACT_WALK: + ys = 90; + break; + case ACT_RUN: + ys = 90; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 90; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// SetActivity +//========================================================= +void CHoundeye :: SetActivity ( Activity NewActivity ) +{ + int iSequence; + + if ( NewActivity == m_Activity ) + return; + + if ( m_MonsterState == MONSTERSTATE_COMBAT && NewActivity == ACT_IDLE && RANDOM_LONG(0,1) ) + { + // play pissed idle. + iSequence = LookupSequence( "madidle" ); + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // In case someone calls this with something other than the ideal activity + m_IdealActivity = m_Activity; + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + pev->sequence = iSequence; // Set to the reset anim (if it's there) + pev->frame = 0; // FIX: frame counter shouldn't be reset when its the same activity as before + ResetSequenceInfo(); + SetYawSpeed(); + } + } + else + { + CSquadMonster :: SetActivity ( NewActivity ); + } +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CHoundeye :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch ( pEvent->event ) + { + case HOUND_AE_WARN: + // do stuff for this event. + WarnSound(); + break; + + case HOUND_AE_STARTATTACK: + WarmUpSound(); + break; + + case HOUND_AE_HOPBACK: + { + float flGravity = g_psv_gravity->value; + + pev->flags &= ~FL_ONGROUND; + + pev->velocity = gpGlobals->v_forward * -200; + pev->velocity.z += (0.6 * flGravity) * 0.5; + + break; + } + + case HOUND_AE_THUMP: + // emit the shockwaves + SonicAttack(); + break; + + case HOUND_AE_ANGERSOUND1: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "houndeye/he_pain3.wav", 1, ATTN_NORM); + break; + + case HOUND_AE_ANGERSOUND2: + EMIT_SOUND(ENT(pev), CHAN_VOICE, "houndeye/he_pain1.wav", 1, ATTN_NORM); + break; + + case HOUND_AE_CLOSE_EYE: + if ( !m_fDontBlink ) + { + pev->skin = HOUNDEYE_EYE_FRAMES - 1; + } + break; + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHoundeye :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/houndeye.mdl"); + UTIL_SetSize(pev, Vector ( -16, -16, 0 ), Vector ( 16, 16, 36 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_YELLOW; + pev->effects = 0; + pev->health = gSkillData.houndeyeHealth; + pev->yaw_speed = 5;//!!! should we put this in the monster's changeanim function since turn rates may vary with state/anim? + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_fAsleep = FALSE; // everyone spawns awake + m_fDontBlink = FALSE; + m_afCapability |= bits_CAP_SQUAD; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHoundeye :: Precache() +{ + PRECACHE_MODEL("models/houndeye.mdl"); + + PRECACHE_SOUND("houndeye/he_alert1.wav"); + PRECACHE_SOUND("houndeye/he_alert2.wav"); + PRECACHE_SOUND("houndeye/he_alert3.wav"); + + PRECACHE_SOUND("houndeye/he_die1.wav"); + PRECACHE_SOUND("houndeye/he_die2.wav"); + PRECACHE_SOUND("houndeye/he_die3.wav"); + + PRECACHE_SOUND("houndeye/he_idle1.wav"); + PRECACHE_SOUND("houndeye/he_idle2.wav"); + PRECACHE_SOUND("houndeye/he_idle3.wav"); + + PRECACHE_SOUND("houndeye/he_hunt1.wav"); + PRECACHE_SOUND("houndeye/he_hunt2.wav"); + PRECACHE_SOUND("houndeye/he_hunt3.wav"); + + PRECACHE_SOUND("houndeye/he_pain1.wav"); + PRECACHE_SOUND("houndeye/he_pain3.wav"); + PRECACHE_SOUND("houndeye/he_pain4.wav"); + PRECACHE_SOUND("houndeye/he_pain5.wav"); + + PRECACHE_SOUND("houndeye/he_attack1.wav"); + PRECACHE_SOUND("houndeye/he_attack3.wav"); + + PRECACHE_SOUND("houndeye/he_blast1.wav"); + PRECACHE_SOUND("houndeye/he_blast2.wav"); + PRECACHE_SOUND("houndeye/he_blast3.wav"); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/shockwave.spr" ); +} + +//========================================================= +// IdleSound +//========================================================= +void CHoundeye :: IdleSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_idle3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// IdleSound +//========================================================= +void CHoundeye :: WarmUpSound ( void ) +{ + switch ( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "houndeye/he_attack1.wav", 0.7, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "houndeye/he_attack3.wav", 0.7, ATTN_NORM ); + break; + } +} + +//========================================================= +// WarnSound +//========================================================= +void CHoundeye :: WarnSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_hunt3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// AlertSound +//========================================================= +void CHoundeye :: AlertSound ( void ) +{ + + if ( InSquad() && !IsLeader() ) + { + return; // only leader makes ALERT sound. + } + + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_alert3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CHoundeye :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die2.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_die3.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// PainSound +//========================================================= +void CHoundeye :: PainSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain3.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain4.wav", 1, ATTN_NORM ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "houndeye/he_pain5.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// WriteBeamColor - writes a color vector to the network +// based on the size of the group. +//========================================================= +void CHoundeye :: WriteBeamColor ( void ) +{ + BYTE bRed, bGreen, bBlue; + + if ( InSquad() ) + { + switch ( SquadCount() ) + { + case 2: + // no case for 0 or 1, cause those are impossible for monsters in Squads. + bRed = 101; + bGreen = 133; + bBlue = 221; + break; + case 3: + bRed = 67; + bGreen = 85; + bBlue = 255; + break; + case 4: + bRed = 62; + bGreen = 33; + bBlue = 211; + break; + default: + ALERT ( at_aiconsole, "Unsupported Houndeye SquadSize!\n" ); + bRed = 188; + bGreen = 220; + bBlue = 255; + break; + } + } + else + { + // solo houndeye - weakest beam + bRed = 188; + bGreen = 220; + bBlue = 255; + } + + WRITE_BYTE( bRed ); + WRITE_BYTE( bGreen ); + WRITE_BYTE( bBlue ); +} + + +//========================================================= +// SonicAttack +//========================================================= +void CHoundeye :: SonicAttack ( void ) +{ + float flAdjustedDamage; + float flDist; + + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "houndeye/he_blast3.wav", 1, ATTN_NORM); break; + } + + // blast circles + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16 + HOUNDEYE_MAX_ATTACK_RADIUS / .2); // reach damage radius over .3 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 2 ); // life + WRITE_BYTE( 16 ); // width + WRITE_BYTE( 0 ); // noise + + WriteBeamColor(); + + WRITE_BYTE( 255 ); //brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16 + ( HOUNDEYE_MAX_ATTACK_RADIUS / 2 ) / .2); // reach damage radius over .3 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 2 ); // life + WRITE_BYTE( 16 ); // width + WRITE_BYTE( 0 ); // noise + + WriteBeamColor(); + + WRITE_BYTE( 255 ); //brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + + CBaseEntity *pEntity = NULL; + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, HOUNDEYE_MAX_ATTACK_RADIUS )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + if ( !FClassnameIs(pEntity->pev, "monster_houndeye") ) + {// houndeyes don't hurt other houndeyes with their attack + + // houndeyes do FULL damage if the ent in question is visible. Half damage otherwise. + // This means that you must get out of the houndeye's attack range entirely to avoid damage. + // Calculate full damage first + + if ( SquadCount() > 1 ) + { + // squad gets attack bonus. + flAdjustedDamage = gSkillData.houndeyeDmgBlast + gSkillData.houndeyeDmgBlast * ( HOUNDEYE_SQUAD_BONUS * ( SquadCount() - 1 ) ); + } + else + { + // solo + flAdjustedDamage = gSkillData.houndeyeDmgBlast; + } + + flDist = (pEntity->Center() - pev->origin).Length(); + + flAdjustedDamage -= ( flDist / HOUNDEYE_MAX_ATTACK_RADIUS ) * flAdjustedDamage; + + if ( !FVisible( pEntity ) ) + { + if ( pEntity->IsPlayer() ) + { + // if this entity is a client, and is not in full view, inflict half damage. We do this so that players still + // take the residual damage if they don't totally leave the houndeye's effective radius. We restrict it to clients + // so that monsters in other parts of the level don't take the damage and get pissed. + flAdjustedDamage *= 0.5; + } + else if ( !FClassnameIs( pEntity->pev, "func_breakable" ) && !FClassnameIs( pEntity->pev, "func_pushable" ) ) + { + // do not hurt nonclients through walls, but allow damage to be done to breakables + flAdjustedDamage = 0; + } + } + + //ALERT ( at_aiconsole, "Damage: %f\n", flAdjustedDamage ); + + if (flAdjustedDamage > 0 ) + { + pEntity->TakeDamage ( pev, pev, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB ); + } + } + } + } +} + +//========================================================= +// start task +//========================================================= +void CHoundeye :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_HOUND_FALL_ASLEEP: + { + m_fAsleep = TRUE; // signal that hound is lying down (must stand again before doing anything else!) + m_iTaskStatus = TASKSTATUS_COMPLETE; + break; + } + case TASK_HOUND_WAKE_UP: + { + m_fAsleep = FALSE; // signal that hound is standing again + m_iTaskStatus = TASKSTATUS_COMPLETE; + break; + } + case TASK_HOUND_OPEN_EYE: + { + m_fDontBlink = FALSE; // turn blinking back on and that code will automatically open the eye + m_iTaskStatus = TASKSTATUS_COMPLETE; + break; + } + case TASK_HOUND_CLOSE_EYE: + { + pev->skin = 0; + m_fDontBlink = TRUE; // tell blink code to leave the eye alone. + break; + } + case TASK_HOUND_THREAT_DISPLAY: + { + m_IdealActivity = ACT_IDLE_ANGRY; + break; + } + case TASK_HOUND_HOP_BACK: + { + m_IdealActivity = ACT_LEAP; + break; + } + case TASK_RANGE_ATTACK1: + { + m_IdealActivity = ACT_RANGE_ATTACK1; + +/* + if ( InSquad() ) + { + // see if there is a battery to connect to. + CSquadMonster *pSquad = m_pSquadLeader; + + while ( pSquad ) + { + if ( pSquad->m_iMySlot == bits_SLOT_HOUND_BATTERY ) + { + // draw a beam. + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( ENTINDEX( this->edict() ) ); + WRITE_SHORT( ENTINDEX( pSquad->edict() ) ); + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // framestart + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 10 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 10 ); // noise + WRITE_BYTE( 0 ); // r, g, b + WRITE_BYTE( 50 ); // r, g, b + WRITE_BYTE( 250); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 30 ); // speed + MESSAGE_END(); + break; + } + + pSquad = pSquad->m_pSquadNext; + } + } +*/ + + break; + } + case TASK_SPECIAL_ATTACK1: + { + m_IdealActivity = ACT_SPECIAL_ATTACK1; + break; + } + case TASK_GUARD: + { + m_IdealActivity = ACT_GUARD; + break; + } + default: + { + CSquadMonster :: StartTask(pTask); + break; + } + } +} + +//========================================================= +// RunTask +//========================================================= +void CHoundeye :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_HOUND_THREAT_DISPLAY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw ( pev->yaw_speed ); + + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + + break; + } + case TASK_HOUND_CLOSE_EYE: + { + if ( pev->skin < HOUNDEYE_EYE_FRAMES - 1 ) + { + pev->skin++; + } + break; + } + case TASK_HOUND_HOP_BACK: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + break; + } + case TASK_SPECIAL_ATTACK1: + { + pev->skin = RANDOM_LONG(0, HOUNDEYE_EYE_FRAMES - 1); + + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw ( pev->yaw_speed ); + + float life; + life = ((255 - pev->frame) / (pev->framerate * m_flFrameRate)); + if (life < 0.1) life = 0.1; + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_IMPLOSION); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 16); + WRITE_BYTE( 50 * life + 100); // radius + WRITE_BYTE( pev->frame / 25.0 ); // count + WRITE_BYTE( life * 10 ); // life + MESSAGE_END(); + + if ( m_fSequenceFinished ) + { + SonicAttack(); + TaskComplete(); + } + + break; + } + default: + { + CSquadMonster :: RunTask(pTask); + break; + } + } +} + +//========================================================= +// PrescheduleThink +//========================================================= +void CHoundeye::PrescheduleThink ( void ) +{ + // if the hound is mad and is running, make hunt noises. + if ( m_MonsterState == MONSTERSTATE_COMBAT && m_Activity == ACT_RUN && RANDOM_FLOAT( 0, 1 ) < 0.2 ) + { + WarnSound(); + } + + // at random, initiate a blink if not already blinking or sleeping + if ( !m_fDontBlink ) + { + if ( ( pev->skin == 0 ) && RANDOM_LONG(0,0x7F) == 0 ) + {// start blinking! + pev->skin = HOUNDEYE_EYE_FRAMES - 1; + } + else if ( pev->skin != 0 ) + {// already blinking + pev->skin--; + } + } + + // if you are the leader, average the origins of each pack member to get an approximate center. + if ( IsLeader() ) + { + CSquadMonster *pSquadMember; + int iSquadCount = 0; + + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + pSquadMember = MySquadMember(i); + + if (pSquadMember) + { + iSquadCount++; + m_vecPackCenter = m_vecPackCenter + pSquadMember->pev->origin; + } + } + + m_vecPackCenter = m_vecPackCenter / iSquadCount; + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlHoundGuardPack[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_GUARD, (float)0 }, +}; + +Schedule_t slHoundGuardPack[] = +{ + { + tlHoundGuardPack, + ARRAYSIZE ( tlHoundGuardPack ), + bits_COND_SEE_HATE | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_PROVOKED | + bits_COND_HEAR_SOUND, + + bits_SOUND_COMBAT |// sound flags + bits_SOUND_WORLD | + bits_SOUND_MEAT | + bits_SOUND_PLAYER, + "GuardPack" + }, +}; + +// primary range attack +Task_t tlHoundYell1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_HOUND_AGITATED }, +}; + +Task_t tlHoundYell2[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slHoundRangeAttack[] = +{ + { + tlHoundYell1, + ARRAYSIZE ( tlHoundYell1 ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundRangeAttack1" + }, + { + tlHoundYell2, + ARRAYSIZE ( tlHoundYell2 ), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundRangeAttack2" + }, +}; + +// lie down and fall asleep +Task_t tlHoundSleep[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_RANDOM, (float)5 }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, + { TASK_HOUND_FALL_ASLEEP, (float)0 }, + { TASK_WAIT_RANDOM, (float)25 }, + { TASK_HOUND_CLOSE_EYE, (float)0 }, + //{ TASK_WAIT, (float)10 }, + //{ TASK_WAIT_RANDOM, (float)10 }, +}; + +Schedule_t slHoundSleep[] = +{ + { + tlHoundSleep, + ARRAYSIZE ( tlHoundSleep ), + bits_COND_HEAR_SOUND | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_NEW_ENEMY, + + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_WORLD, + "Hound Sleep" + }, +}; + +// wake and stand up lazily +Task_t tlHoundWakeLazy[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_HOUND_OPEN_EYE, (float)0 }, + { TASK_WAIT_RANDOM, (float)2.5 }, + { TASK_PLAY_SEQUENCE, (float)ACT_STAND }, + { TASK_HOUND_WAKE_UP, (float)0 }, +}; + +Schedule_t slHoundWakeLazy[] = +{ + { + tlHoundWakeLazy, + ARRAYSIZE ( tlHoundWakeLazy ), + 0, + 0, + "WakeLazy" + }, +}; + +// wake and stand up with great urgency! +Task_t tlHoundWakeUrgent[] = +{ + { TASK_HOUND_OPEN_EYE, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_HOP }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_HOUND_WAKE_UP, (float)0 }, +}; + +Schedule_t slHoundWakeUrgent[] = +{ + { + tlHoundWakeUrgent, + ARRAYSIZE ( tlHoundWakeUrgent ), + 0, + 0, + "WakeUrgent" + }, +}; + + +Task_t tlHoundSpecialAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SPECIAL_ATTACK1, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_IDLE_ANGRY }, +}; + +Schedule_t slHoundSpecialAttack1[] = +{ + { + tlHoundSpecialAttack1, + ARRAYSIZE ( tlHoundSpecialAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED, + + 0, + "Hound Special Attack1" + }, +}; + +Task_t tlHoundAgitated[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_THREAT_DISPLAY, 0 }, +}; + +Schedule_t slHoundAgitated[] = +{ + { + tlHoundAgitated, + ARRAYSIZE ( tlHoundAgitated ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Hound Agitated" + }, +}; + +Task_t tlHoundHopRetreat[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_HOP_BACK, 0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TAKE_COVER_FROM_ENEMY }, +}; + +Schedule_t slHoundHopRetreat[] = +{ + { + tlHoundHopRetreat, + ARRAYSIZE ( tlHoundHopRetreat ), + 0, + 0, + "Hound Hop Retreat" + }, +}; + +// hound fails in combat with client in the PVS +Task_t tlHoundCombatFailPVS[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_THREAT_DISPLAY, 0 }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slHoundCombatFailPVS[] = +{ + { + tlHoundCombatFailPVS, + ARRAYSIZE ( tlHoundCombatFailPVS ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundCombatFailPVS" + }, +}; + +// hound fails in combat with no client in the PVS. Don't keep peeping! +Task_t tlHoundCombatFailNoPVS[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_HOUND_THREAT_DISPLAY, 0 }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_PVS, 0 }, +}; + +Schedule_t slHoundCombatFailNoPVS[] = +{ + { + tlHoundCombatFailNoPVS, + ARRAYSIZE ( tlHoundCombatFailNoPVS ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "HoundCombatFailNoPVS" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CHoundeye ) +{ + slHoundGuardPack, + slHoundRangeAttack, + &slHoundRangeAttack[ 1 ], + slHoundSleep, + slHoundWakeLazy, + slHoundWakeUrgent, + slHoundSpecialAttack1, + slHoundAgitated, + slHoundHopRetreat, + slHoundCombatFailPVS, + slHoundCombatFailNoPVS, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CHoundeye, CSquadMonster ); + +//========================================================= +// GetScheduleOfType +//========================================================= +Schedule_t* CHoundeye :: GetScheduleOfType ( int Type ) +{ + if ( m_fAsleep ) + { + // if the hound is sleeping, must wake and stand! + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pWakeSound; + + pWakeSound = PBestSound(); + ASSERT( pWakeSound != NULL ); + if ( pWakeSound ) + { + MakeIdealYaw ( pWakeSound->m_vecOrigin ); + + if ( FLSoundVolume ( pWakeSound ) >= HOUNDEYE_SOUND_STARTLE_VOLUME ) + { + // awakened by a loud sound + return &slHoundWakeUrgent[ 0 ]; + } + } + // sound was not loud enough to scare the bejesus out of houndeye + return &slHoundWakeLazy[ 0 ]; + } + else if ( HasConditions( bits_COND_NEW_ENEMY ) ) + { + // get up fast, to fight. + return &slHoundWakeUrgent[ 0 ]; + } + + else + { + // hound is waking up on its own + return &slHoundWakeLazy[ 0 ]; + } + } + switch ( Type ) + { + case SCHED_IDLE_STAND: + { + // we may want to sleep instead of stand! + if ( InSquad() && !IsLeader() && !m_fAsleep && RANDOM_LONG(0,29) < 1 ) + { + return &slHoundSleep[ 0 ]; + } + else + { + return CSquadMonster :: GetScheduleOfType( Type ); + } + } + case SCHED_RANGE_ATTACK1: + { + return &slHoundRangeAttack[ 0 ]; +/* + if ( InSquad() ) + { + return &slHoundRangeAttack[ RANDOM_LONG( 0, 1 ) ]; + } + + return &slHoundRangeAttack[ 1 ]; +*/ + } + case SCHED_SPECIAL_ATTACK1: + { + return &slHoundSpecialAttack1[ 0 ]; + } + case SCHED_GUARD: + { + return &slHoundGuardPack[ 0 ]; + } + case SCHED_HOUND_AGITATED: + { + return &slHoundAgitated[ 0 ]; + } + case SCHED_HOUND_HOP_RETREAT: + { + return &slHoundHopRetreat[ 0 ]; + } + case SCHED_FAIL: + { + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + if ( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + { + // client in PVS + return &slHoundCombatFailPVS[ 0 ]; + } + else + { + // client has taken off! + return &slHoundCombatFailNoPVS[ 0 ]; + } + } + else + { + return CSquadMonster :: GetScheduleOfType ( Type ); + } + } + default: + { + return CSquadMonster :: GetScheduleOfType ( Type ); + } + } +} + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t *CHoundeye :: GetSchedule( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + { +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + if ( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + if ( RANDOM_FLOAT( 0 , 1 ) <= 0.4 ) + { + TraceResult tr; + UTIL_MakeVectors( pev->angles ); + UTIL_TraceHull( pev->origin, pev->origin + gpGlobals->v_forward * -128, dont_ignore_monsters, head_hull, ENT( pev ), &tr ); + + if ( tr.flFraction == 1.0 ) + { + // it's clear behind, so the hound will jump + return GetScheduleOfType ( SCHED_HOUND_HOP_RETREAT ); + } + } + + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + if ( OccupySlot ( bits_SLOTS_HOUND_ATTACK ) ) + { + return GetScheduleOfType ( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType ( SCHED_HOUND_AGITATED ); + } + break; + } + } + + return CSquadMonster :: GetSchedule(); +} diff --git a/dlls/ichthyosaur.cpp b/dlls/ichthyosaur.cpp new file mode 100644 index 0000000..b88abf1 --- /dev/null +++ b/dlls/ichthyosaur.cpp @@ -0,0 +1,1108 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +//========================================================= +// icthyosaur - evin, satan fish monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "flyingmonster.h" +#include "nodes.h" +#include "soundent.h" +#include "animation.h" +#include "effects.h" +#include "weapons.h" + +#define SEARCH_RETRY 16 + +#define ICHTHYOSAUR_SPEED 150 + +extern CGraph WorldGraph; + +#define EYE_MAD 0 +#define EYE_BASE 1 +#define EYE_CLOSED 2 +#define EYE_BACK 3 +#define EYE_LOOK 4 + + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +// UNDONE: Save/restore here +class CIchthyosaur : public CFlyingMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + CUSTOM_SCHEDULES; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + + void Killed( entvars_t *pevAttacker, int iGib ); + void BecomeDead( void ); + + void EXPORT CombatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT BiteTouch( CBaseEntity *pOther ); + + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + + float ChangeYaw( int speed ); + Activity GetStoppedActivity( void ); + + void Move( float flInterval ); + void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + void MonsterThink( void ); + void Stop( void ); + void Swim( void ); + Vector DoProbe(const Vector &Probe); + + float VectorToPitch( const Vector &vec); + float FlPitchDiff( void ); + float ChangePitch( int speed ); + + Vector m_SaveVelocity; + float m_idealDist; + + float m_flBlink; + + float m_flEnemyTouched; + BOOL m_bOnAttack; + + float m_flMaxSpeed; + float m_flMinSpeed; + float m_flMaxDist; + + CBeam *m_pBeam; + + float m_flNextAlert; + + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pAttackSounds[]; + static const char *pBiteSounds[]; + static const char *pDieSounds[]; + static const char *pPainSounds[]; + + void IdleSound( void ); + void AlertSound( void ); + void AttackSound( void ); + void BiteSound( void ); + void DeathSound( void ); + void PainSound( void ); +}; + +LINK_ENTITY_TO_CLASS( monster_ichthyosaur, CIchthyosaur ); + +TYPEDESCRIPTION CIchthyosaur::m_SaveData[] = +{ + DEFINE_FIELD( CIchthyosaur, m_SaveVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CIchthyosaur, m_idealDist, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flBlink, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flEnemyTouched, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_bOnAttack, FIELD_BOOLEAN ), + DEFINE_FIELD( CIchthyosaur, m_flMaxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flMinSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( CIchthyosaur, m_flNextAlert, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CIchthyosaur, CFlyingMonster ); + + +const char *CIchthyosaur::pIdleSounds[] = +{ + "ichy/ichy_idle1.wav", + "ichy/ichy_idle2.wav", + "ichy/ichy_idle3.wav", + "ichy/ichy_idle4.wav", +}; + +const char *CIchthyosaur::pAlertSounds[] = +{ + "ichy/ichy_alert2.wav", + "ichy/ichy_alert3.wav", +}; + +const char *CIchthyosaur::pAttackSounds[] = +{ + "ichy/ichy_attack1.wav", + "ichy/ichy_attack2.wav", +}; + +const char *CIchthyosaur::pBiteSounds[] = +{ + "ichy/ichy_bite1.wav", + "ichy/ichy_bite2.wav", +}; + +const char *CIchthyosaur::pPainSounds[] = +{ + "ichy/ichy_pain2.wav", + "ichy/ichy_pain3.wav", + "ichy/ichy_pain5.wav", +}; + +const char *CIchthyosaur::pDieSounds[] = +{ + "ichy/ichy_die2.wav", + "ichy/ichy_die4.wav", +}; + +#define EMIT_ICKY_SOUND( chan, array ) \ + EMIT_SOUND_DYN ( ENT(pev), chan , array [ RANDOM_LONG(0,ARRAYSIZE( array )-1) ], 1.0, 0.6, 0, RANDOM_LONG(95,105) ); + + +void CIchthyosaur :: IdleSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pIdleSounds ); +} + +void CIchthyosaur :: AlertSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pAlertSounds ); +} + +void CIchthyosaur :: AttackSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pAttackSounds ); +} + +void CIchthyosaur :: BiteSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_WEAPON, pBiteSounds ); +} + +void CIchthyosaur :: DeathSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pDieSounds ); +} + +void CIchthyosaur :: PainSound( void ) +{ + EMIT_ICKY_SOUND( CHAN_VOICE, pPainSounds ); +} + +//========================================================= +// monster-specific tasks and states +//========================================================= +enum +{ + TASK_ICHTHYOSAUR_CIRCLE_ENEMY = LAST_COMMON_TASK + 1, + TASK_ICHTHYOSAUR_SWIM, + TASK_ICHTHYOSAUR_FLOAT, +}; + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +static Task_t tlSwimAround[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_WALK }, + { TASK_ICHTHYOSAUR_SWIM, 0.0 }, +}; + +static Schedule_t slSwimAround[] = +{ + { + tlSwimAround, + ARRAYSIZE(tlSwimAround), + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_SEE_ENEMY | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + bits_SOUND_PLAYER | + bits_SOUND_COMBAT, + "SwimAround" + }, +}; + +static Task_t tlSwimAgitated[] = +{ + { TASK_STOP_MOVING, (float) 0 }, + { TASK_SET_ACTIVITY, (float)ACT_RUN }, + { TASK_WAIT, (float)2.0 }, +}; + +static Schedule_t slSwimAgitated[] = +{ + { + tlSwimAgitated, + ARRAYSIZE(tlSwimAgitated), + 0, + 0, + "SwimAgitated" + }, +}; + + +static Task_t tlCircleEnemy[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_WALK }, + { TASK_ICHTHYOSAUR_CIRCLE_ENEMY, 0.0 }, +}; + +static Schedule_t slCircleEnemy[] = +{ + { + tlCircleEnemy, + ARRAYSIZE(tlCircleEnemy), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK1, + 0, + "CircleEnemy" + }, +}; + + +Task_t tlTwitchDie[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SOUND_DIE, (float)0 }, + { TASK_DIE, (float)0 }, + { TASK_ICHTHYOSAUR_FLOAT, (float)0 }, +}; + +Schedule_t slTwitchDie[] = +{ + { + tlTwitchDie, + ARRAYSIZE( tlTwitchDie ), + 0, + 0, + "Die" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES(CIchthyosaur) +{ + slSwimAround, + slSwimAgitated, + slCircleEnemy, + slTwitchDie, +}; +IMPLEMENT_CUSTOM_SCHEDULES(CIchthyosaur, CFlyingMonster); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CIchthyosaur :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CIchthyosaur :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( flDot >= 0.7 && m_flEnemyTouched > gpGlobals->time - 0.2 ) + { + return TRUE; + } + return FALSE; +} + +void CIchthyosaur::BiteTouch( CBaseEntity *pOther ) +{ + // bite if we hit who we want to eat + if ( pOther == m_hEnemy ) + { + m_flEnemyTouched = gpGlobals->time; + m_bOnAttack = TRUE; + } +} + +void CIchthyosaur::CombatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_bOnAttack ) ) + return; + + if (m_bOnAttack) + { + m_bOnAttack = 0; + } + else + { + m_bOnAttack = 1; + } +} + +//========================================================= +// CheckRangeAttack1 - swim in for a chomp +// +//========================================================= +BOOL CIchthyosaur :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDot > -0.7 && (m_bOnAttack || ( flDist <= 192 && m_idealDist <= 192))) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CIchthyosaur :: SetYawSpeed ( void ) +{ + pev->yaw_speed = 100; +} + + + +//========================================================= +// Killed - overrides CFlyingMonster. +// +void CIchthyosaur :: Killed( entvars_t *pevAttacker, int iGib ) +{ + CBaseMonster::Killed( pevAttacker, iGib ); + pev->velocity = Vector( 0, 0, 0 ); +} + +void CIchthyosaur::BecomeDead( void ) +{ + pev->takedamage = DAMAGE_YES;// don't let autoaim aim at corpses. + + // give the corpse half of the monster's original maximum health. + pev->health = pev->max_health / 2; + pev->max_health = 5; // max_health now becomes a counter for how many blood decals the corpse can place. +} + +#define ICHTHYOSAUR_AE_SHAKE_RIGHT 1 +#define ICHTHYOSAUR_AE_SHAKE_LEFT 2 + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CIchthyosaur :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + int bDidAttack = FALSE; + switch( pEvent->event ) + { + case ICHTHYOSAUR_AE_SHAKE_RIGHT: + case ICHTHYOSAUR_AE_SHAKE_LEFT: + { + if (m_hEnemy != NULL && FVisible( m_hEnemy )) + { + CBaseEntity *pHurt = m_hEnemy; + + if (m_flEnemyTouched < gpGlobals->time - 0.2 && (m_hEnemy->BodyTarget( pev->origin ) - pev->origin).Length() > (32+16+32)) + break; + + Vector vecShootDir = ShootAtEnemy( pev->origin ); + UTIL_MakeAimVectors ( pev->angles ); + + if (DotProduct( vecShootDir, gpGlobals->v_forward ) > 0.707) + { + m_bOnAttack = TRUE; + pHurt->pev->punchangle.z = -18; + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_right * 300; + if (pHurt->IsPlayer()) + { + pHurt->pev->angles.x += RANDOM_FLOAT( -35, 35 ); + pHurt->pev->angles.y += RANDOM_FLOAT( -90, 90 ); + pHurt->pev->angles.z = 0; + pHurt->pev->fixangle = TRUE; + } + pHurt->TakeDamage( pev, pev, gSkillData.ichthyosaurDmgShake, DMG_SLASH ); + } + } + BiteSound(); + + bDidAttack = TRUE; + } + break; + default: + CFlyingMonster::HandleAnimEvent( pEvent ); + break; + } + + if (bDidAttack) + { + Vector vecSrc = pev->origin + gpGlobals->v_forward * 32; + UTIL_Bubbles( vecSrc - Vector( 8, 8, 8 ), vecSrc + Vector( 8, 8, 8 ), 16 ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CIchthyosaur :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/icky.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, -32 ), Vector( 32, 32, 32 ) ); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_FLY; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = gSkillData.ichthyosaurHealth; + pev->view_ofs = Vector ( 0, 0, 16 ); + m_flFieldOfView = VIEW_FIELD_WIDE; + m_MonsterState = MONSTERSTATE_NONE; + SetBits(pev->flags, FL_SWIM); + SetFlyingSpeed( ICHTHYOSAUR_SPEED ); + SetFlyingMomentum( 2.5 ); // Set momentum constant + + m_afCapability = bits_CAP_RANGE_ATTACK1 | bits_CAP_SWIM; + + MonsterInit(); + + SetTouch( BiteTouch ); + SetUse( CombatUse ); + + m_idealDist = 384; + m_flMinSpeed = 80; + m_flMaxSpeed = 300; + m_flMaxDist = 384; + + Vector Forward; + UTIL_MakeVectorsPrivate(pev->angles, Forward, 0, 0); + pev->velocity = m_flightSpeed * Forward.Normalize(); + m_SaveVelocity = pev->velocity; +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CIchthyosaur :: Precache() +{ + PRECACHE_MODEL("models/icky.mdl"); + + PRECACHE_SOUND_ARRAY( pIdleSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pBiteSounds ); + PRECACHE_SOUND_ARRAY( pDieSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); +} + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t* CIchthyosaur::GetSchedule() +{ + // ALERT( at_console, "GetSchedule( )\n" ); + switch(m_MonsterState) + { + case MONSTERSTATE_IDLE: + m_flightSpeed = 80; + return GetScheduleOfType( SCHED_IDLE_WALK ); + + case MONSTERSTATE_ALERT: + m_flightSpeed = 150; + return GetScheduleOfType( SCHED_IDLE_WALK ); + + case MONSTERSTATE_COMBAT: + m_flMaxSpeed = 400; + // eat them + if ( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + } + // chase them down and eat them + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + if ( HasConditions( bits_COND_HEAVY_DAMAGE ) ) + { + m_bOnAttack = TRUE; + } + if ( pev->health < pev->max_health - 20 ) + { + m_bOnAttack = TRUE; + } + + return GetScheduleOfType( SCHED_STANDOFF ); + } + + return CFlyingMonster :: GetSchedule(); +} + + +//========================================================= +//========================================================= +Schedule_t* CIchthyosaur :: GetScheduleOfType ( int Type ) +{ + // ALERT( at_console, "GetScheduleOfType( %d ) %d\n", Type, m_bOnAttack ); + switch ( Type ) + { + case SCHED_IDLE_WALK: + return slSwimAround; + case SCHED_STANDOFF: + return slCircleEnemy; + case SCHED_FAIL: + return slSwimAgitated; + case SCHED_DIE: + return slTwitchDie; + case SCHED_CHASE_ENEMY: + AttackSound( ); + } + + return CBaseMonster :: GetScheduleOfType( Type ); +} + + + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. +//========================================================= +void CIchthyosaur::StartTask(Task_t *pTask) +{ + switch (pTask->iTask) + { + case TASK_ICHTHYOSAUR_CIRCLE_ENEMY: + break; + case TASK_ICHTHYOSAUR_SWIM: + break; + case TASK_SMALL_FLINCH: + if (m_idealDist > 128) + { + m_flMaxDist = 512; + m_idealDist = 512; + } + else + { + m_bOnAttack = TRUE; + } + CFlyingMonster::StartTask(pTask); + break; + + case TASK_ICHTHYOSAUR_FLOAT: + pev->skin = EYE_BASE; + SetSequenceByName( "bellyup" ); + break; + + default: + CFlyingMonster::StartTask(pTask); + break; + } +} + +void CIchthyosaur :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_ICHTHYOSAUR_CIRCLE_ENEMY: + if (m_hEnemy == NULL) + { + TaskComplete( ); + } + else if (FVisible( m_hEnemy )) + { + Vector vecFrom = m_hEnemy->EyePosition( ); + + Vector vecDelta = (pev->origin - vecFrom).Normalize( ); + Vector vecSwim = CrossProduct( vecDelta, Vector( 0, 0, 1 ) ).Normalize( ); + + if (DotProduct( vecSwim, m_SaveVelocity ) < 0) + vecSwim = vecSwim * -1.0; + + Vector vecPos = vecFrom + vecDelta * m_idealDist + vecSwim * 32; + + // ALERT( at_console, "vecPos %.0f %.0f %.0f\n", vecPos.x, vecPos.y, vecPos.z ); + + TraceResult tr; + + UTIL_TraceHull( vecFrom, vecPos, ignore_monsters, large_hull, m_hEnemy->edict(), &tr ); + + if (tr.flFraction > 0.5) + vecPos = tr.vecEndPos; + + m_SaveVelocity = m_SaveVelocity * 0.8 + 0.2 * (vecPos - pev->origin).Normalize() * m_flightSpeed; + + // ALERT( at_console, "m_SaveVelocity %.2f %.2f %.2f\n", m_SaveVelocity.x, m_SaveVelocity.y, m_SaveVelocity.z ); + + if (HasConditions( bits_COND_ENEMY_FACING_ME ) && m_hEnemy->FVisible( this )) + { + m_flNextAlert -= 0.1; + + if (m_idealDist < m_flMaxDist) + { + m_idealDist += 4; + } + if (m_flightSpeed > m_flMinSpeed) + { + m_flightSpeed -= 2; + } + else if (m_flightSpeed < m_flMinSpeed) + { + m_flightSpeed += 2; + } + if (m_flMinSpeed < m_flMaxSpeed) + { + m_flMinSpeed += 0.5; + } + } + else + { + m_flNextAlert += 0.1; + + if (m_idealDist > 128) + { + m_idealDist -= 4; + } + if (m_flightSpeed < m_flMaxSpeed) + { + m_flightSpeed += 4; + } + } + // ALERT( at_console, "%.0f\n", m_idealDist ); + } + else + { + m_flNextAlert = gpGlobals->time + 0.2; + } + + if (m_flNextAlert < gpGlobals->time) + { + // ALERT( at_console, "AlertSound()\n"); + AlertSound( ); + m_flNextAlert = gpGlobals->time + RANDOM_FLOAT( 3, 5 ); + } + + break; + case TASK_ICHTHYOSAUR_SWIM: + if (m_fSequenceFinished) + { + TaskComplete( ); + } + break; + case TASK_DIE: + if ( m_fSequenceFinished ) + { + pev->deadflag = DEAD_DEAD; + + TaskComplete( ); + } + break; + + case TASK_ICHTHYOSAUR_FLOAT: + pev->angles.x = UTIL_ApproachAngle( 0, pev->angles.x, 20 ); + pev->velocity = pev->velocity * 0.8; + if (pev->waterlevel > 1 && pev->velocity.z < 64) + { + pev->velocity.z += 8; + } + else + { + pev->velocity.z -= 8; + } + // ALERT( at_console, "%f\n", pev->velocity.z ); + break; + + default: + CFlyingMonster :: RunTask ( pTask ); + break; + } +} + + + +float CIchthyosaur::VectorToPitch( const Vector &vec ) +{ + float pitch; + if (vec.z == 0 && vec.x == 0) + pitch = 0; + else + { + pitch = (int) (atan2(vec.z, sqrt(vec.x*vec.x+vec.y*vec.y)) * 180 / M_PI); + if (pitch < 0) + pitch += 360; + } + return pitch; +} + +//========================================================= +void CIchthyosaur::Move(float flInterval) +{ + CFlyingMonster::Move( flInterval ); +} + +float CIchthyosaur::FlPitchDiff( void ) +{ + float flPitchDiff; + float flCurrentPitch; + + flCurrentPitch = UTIL_AngleMod( pev->angles.z ); + + if ( flCurrentPitch == pev->idealpitch ) + { + return 0; + } + + flPitchDiff = pev->idealpitch - flCurrentPitch; + + if ( pev->idealpitch > flCurrentPitch ) + { + if (flPitchDiff >= 180) + flPitchDiff = flPitchDiff - 360; + } + else + { + if (flPitchDiff <= -180) + flPitchDiff = flPitchDiff + 360; + } + return flPitchDiff; +} + +float CIchthyosaur :: ChangePitch( int speed ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + { + float diff = FlPitchDiff(); + float target = 0; + if ( m_IdealActivity != GetStoppedActivity() ) + { + if (diff < -20) + target = 45; + else if (diff > 20) + target = -45; + } + pev->angles.x = UTIL_Approach(target, pev->angles.x, 220.0 * 0.1 ); + } + return 0; +} + +float CIchthyosaur::ChangeYaw( int speed ) +{ + if ( pev->movetype == MOVETYPE_FLY ) + { + float diff = FlYawDiff(); + float target = 0; + + if ( m_IdealActivity != GetStoppedActivity() ) + { + if ( diff < -20 ) + target = 20; + else if ( diff > 20 ) + target = -20; + } + pev->angles.z = UTIL_Approach( target, pev->angles.z, 220.0 * 0.1 ); + } + return CFlyingMonster::ChangeYaw( speed ); +} + + +Activity CIchthyosaur:: GetStoppedActivity( void ) +{ + if ( pev->movetype != MOVETYPE_FLY ) // UNDONE: Ground idle here, IDLE may be something else + return ACT_IDLE; + return ACT_WALK; +} + +void CIchthyosaur::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + m_SaveVelocity = vecDir * m_flightSpeed; +} + + +void CIchthyosaur::MonsterThink ( void ) +{ + CFlyingMonster::MonsterThink( ); + + if (pev->deadflag == DEAD_NO) + { + if (m_MonsterState != MONSTERSTATE_SCRIPT) + { + Swim( ); + + // blink the eye + if (m_flBlink < gpGlobals->time) + { + pev->skin = EYE_CLOSED; + if (m_flBlink + 0.2 < gpGlobals->time) + { + m_flBlink = gpGlobals->time + RANDOM_FLOAT( 3, 4 ); + if (m_bOnAttack) + pev->skin = EYE_MAD; + else + pev->skin = EYE_BASE; + } + } + } + } +} + +void CIchthyosaur :: Stop( void ) +{ + if (!m_bOnAttack) + m_flightSpeed = 80.0; +} + +void CIchthyosaur::Swim( ) +{ + int retValue = 0; + + Vector start = pev->origin; + + Vector Angles; + Vector Forward, Right, Up; + + if (FBitSet( pev->flags, FL_ONGROUND)) + { + pev->angles.x = 0; + pev->angles.y += RANDOM_FLOAT( -45, 45 ); + ClearBits( pev->flags, FL_ONGROUND ); + + Angles = Vector( -pev->angles.x, pev->angles.y, pev->angles.z ); + UTIL_MakeVectorsPrivate(Angles, Forward, Right, Up); + + pev->velocity = Forward * 200 + Up * 200; + + return; + } + + if (m_bOnAttack && m_flightSpeed < m_flMaxSpeed) + { + m_flightSpeed += 40; + } + if (m_flightSpeed < 180) + { + if (m_IdealActivity == ACT_RUN) + SetActivity( ACT_WALK ); + if (m_IdealActivity == ACT_WALK) + pev->framerate = m_flightSpeed / 150.0; + // ALERT( at_console, "walk %.2f\n", pev->framerate ); + } + else + { + if (m_IdealActivity == ACT_WALK) + SetActivity( ACT_RUN ); + if (m_IdealActivity == ACT_RUN) + pev->framerate = m_flightSpeed / 150.0; + // ALERT( at_console, "run %.2f\n", pev->framerate ); + } + +/* + if (!m_pBeam) + { + m_pBeam = CBeam::BeamCreate( "sprites/laserbeam.spr", 80 ); + m_pBeam->PointEntInit( pev->origin + m_SaveVelocity, entindex( ) ); + m_pBeam->SetEndAttachment( 1 ); + m_pBeam->SetColor( 255, 180, 96 ); + m_pBeam->SetBrightness( 192 ); + } +*/ +#define PROBE_LENGTH 150 + Angles = UTIL_VecToAngles( m_SaveVelocity ); + Angles.x = -Angles.x; + UTIL_MakeVectorsPrivate(Angles, Forward, Right, Up); + + Vector f, u, l, r, d; + f = DoProbe(start + PROBE_LENGTH * Forward); + r = DoProbe(start + PROBE_LENGTH/3 * Forward+Right); + l = DoProbe(start + PROBE_LENGTH/3 * Forward-Right); + u = DoProbe(start + PROBE_LENGTH/3 * Forward+Up); + d = DoProbe(start + PROBE_LENGTH/3 * Forward-Up); + + Vector SteeringVector = f+r+l+u+d; + m_SaveVelocity = (m_SaveVelocity + SteeringVector/2).Normalize(); + + Angles = Vector( -pev->angles.x, pev->angles.y, pev->angles.z ); + UTIL_MakeVectorsPrivate(Angles, Forward, Right, Up); + // ALERT( at_console, "%f : %f\n", Angles.x, Forward.z ); + + float flDot = DotProduct( Forward, m_SaveVelocity ); + if (flDot > 0.5) + pev->velocity = m_SaveVelocity = m_SaveVelocity * m_flightSpeed; + else if (flDot > 0) + pev->velocity = m_SaveVelocity = m_SaveVelocity * m_flightSpeed * (flDot + 0.5); + else + pev->velocity = m_SaveVelocity = m_SaveVelocity * 80; + + // ALERT( at_console, "%.0f %.0f\n", m_flightSpeed, pev->velocity.Length() ); + + + // ALERT( at_console, "Steer %f %f %f\n", SteeringVector.x, SteeringVector.y, SteeringVector.z ); + +/* + m_pBeam->SetStartPos( pev->origin + pev->velocity ); + m_pBeam->RelinkBeam( ); +*/ + + // ALERT( at_console, "speed %f\n", m_flightSpeed ); + + Angles = UTIL_VecToAngles( m_SaveVelocity ); + + // Smooth Pitch + // + if (Angles.x > 180) + Angles.x = Angles.x - 360; + pev->angles.x = UTIL_Approach(Angles.x, pev->angles.x, 50 * 0.1 ); + if (pev->angles.x < -80) pev->angles.x = -80; + if (pev->angles.x > 80) pev->angles.x = 80; + + // Smooth Yaw and generate Roll + // + float turn = 360; + // ALERT( at_console, "Y %.0f %.0f\n", Angles.y, pev->angles.y ); + + if (fabs(Angles.y - pev->angles.y) < fabs(turn)) + { + turn = Angles.y - pev->angles.y; + } + if (fabs(Angles.y - pev->angles.y + 360) < fabs(turn)) + { + turn = Angles.y - pev->angles.y + 360; + } + if (fabs(Angles.y - pev->angles.y - 360) < fabs(turn)) + { + turn = Angles.y - pev->angles.y - 360; + } + + float speed = m_flightSpeed * 0.1; + + // ALERT( at_console, "speed %.0f %f\n", turn, speed ); + if (fabs(turn) > speed) + { + if (turn < 0.0) + { + turn = -speed; + } + else + { + turn = speed; + } + } + pev->angles.y += turn; + pev->angles.z -= turn; + pev->angles.y = fmod((pev->angles.y + 360.0), 360.0); + + static float yaw_adj; + + yaw_adj = yaw_adj * 0.8 + turn; + + // ALERT( at_console, "yaw %f : %f\n", turn, yaw_adj ); + + SetBoneController( 0, -yaw_adj / 4.0 ); + + // Roll Smoothing + // + turn = 360; + if (fabs(Angles.z - pev->angles.z) < fabs(turn)) + { + turn = Angles.z - pev->angles.z; + } + if (fabs(Angles.z - pev->angles.z + 360) < fabs(turn)) + { + turn = Angles.z - pev->angles.z + 360; + } + if (fabs(Angles.z - pev->angles.z - 360) < fabs(turn)) + { + turn = Angles.z - pev->angles.z - 360; + } + speed = m_flightSpeed/2 * 0.1; + if (fabs(turn) < speed) + { + pev->angles.z += turn; + } + else + { + if (turn < 0.0) + { + pev->angles.z -= speed; + } + else + { + pev->angles.z += speed; + } + } + if (pev->angles.z < -20) pev->angles.z = -20; + if (pev->angles.z > 20) pev->angles.z = 20; + + UTIL_MakeVectorsPrivate( Vector( -Angles.x, Angles.y, Angles.z ), Forward, Right, Up); + + // UTIL_MoveToOrigin ( ENT(pev), pev->origin + Forward * speed, speed, MOVE_STRAFE ); +} + + +Vector CIchthyosaur::DoProbe(const Vector &Probe) +{ + Vector WallNormal = Vector(0,0,-1); // WATER normal is Straight Down for fish. + float frac; + BOOL bBumpedSomething = ProbeZ(pev->origin, Probe, &frac); + + TraceResult tr; + TRACE_MONSTER_HULL(edict(), pev->origin, Probe, dont_ignore_monsters, edict(), &tr); + if ( tr.fAllSolid || tr.flFraction < 0.99 ) + { + if (tr.flFraction < 0.0) tr.flFraction = 0.0; + if (tr.flFraction > 1.0) tr.flFraction = 1.0; + if (tr.flFraction < frac) + { + frac = tr.flFraction; + bBumpedSomething = TRUE; + WallNormal = tr.vecPlaneNormal; + } + } + + if (bBumpedSomething && (m_hEnemy == NULL || tr.pHit != m_hEnemy->edict())) + { + Vector ProbeDir = Probe - pev->origin; + + Vector NormalToProbeAndWallNormal = CrossProduct(ProbeDir, WallNormal); + Vector SteeringVector = CrossProduct( NormalToProbeAndWallNormal, ProbeDir); + + float SteeringForce = m_flightSpeed * (1-frac) * (DotProduct(WallNormal.Normalize(), m_SaveVelocity.Normalize())); + if (SteeringForce < 0.0) + { + SteeringForce = -SteeringForce; + } + SteeringVector = SteeringForce * SteeringVector.Normalize(); + + return SteeringVector; + } + return Vector(0, 0, 0); +} + +#endif \ No newline at end of file diff --git a/dlls/islave.cpp b/dlls/islave.cpp new file mode 100644 index 0000000..037001c --- /dev/null +++ b/dlls/islave.cpp @@ -0,0 +1,866 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Alien slave monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "squadmonster.h" +#include "schedule.h" +#include "effects.h" +#include "weapons.h" +#include "soundent.h" + +extern DLL_GLOBAL int g_iSkillLevel; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define ISLAVE_AE_CLAW ( 1 ) +#define ISLAVE_AE_CLAWRAKE ( 2 ) +#define ISLAVE_AE_ZAP_POWERUP ( 3 ) +#define ISLAVE_AE_ZAP_SHOOT ( 4 ) +#define ISLAVE_AE_ZAP_DONE ( 5 ) + +#define ISLAVE_MAX_BEAMS 8 + +class CISlave : public CSquadMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int ISoundMask( void ); + int Classify ( void ); + int IRelationship( CBaseEntity *pTarget ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + void CallForHelp( char *szClassname, float flDist, EHANDLE hEnemy, Vector &vecLocation ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + + void DeathSound( void ); + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + + void Killed( entvars_t *pevAttacker, int iGib ); + + void StartTask ( Task_t *pTask ); + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + CUSTOM_SCHEDULES; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void ClearBeams( ); + void ArmBeam( int side ); + void WackBeam( int side, CBaseEntity *pEntity ); + void ZapBeam( int side ); + void BeamGlow( void ); + + int m_iBravery; + + CBeam *m_pBeam[ISLAVE_MAX_BEAMS]; + + int m_iBeams; + float m_flNextAttack; + + int m_voicePitch; + + EHANDLE m_hDead; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + static const char *pPainSounds[]; + static const char *pDeathSounds[]; +}; +LINK_ENTITY_TO_CLASS( monster_alien_slave, CISlave ); +LINK_ENTITY_TO_CLASS( monster_vortigaunt, CISlave ); + + +TYPEDESCRIPTION CISlave::m_SaveData[] = +{ + DEFINE_FIELD( CISlave, m_iBravery, FIELD_INTEGER ), + + DEFINE_ARRAY( CISlave, m_pBeam, FIELD_CLASSPTR, ISLAVE_MAX_BEAMS ), + DEFINE_FIELD( CISlave, m_iBeams, FIELD_INTEGER ), + DEFINE_FIELD( CISlave, m_flNextAttack, FIELD_TIME ), + + DEFINE_FIELD( CISlave, m_voicePitch, FIELD_INTEGER ), + + DEFINE_FIELD( CISlave, m_hDead, FIELD_EHANDLE ), + +}; + +IMPLEMENT_SAVERESTORE( CISlave, CSquadMonster ); + + + + +const char *CISlave::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CISlave::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CISlave::pPainSounds[] = +{ + "aslave/slv_pain1.wav", + "aslave/slv_pain2.wav", +}; + +const char *CISlave::pDeathSounds[] = +{ + "aslave/slv_die1.wav", + "aslave/slv_die2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CISlave :: Classify ( void ) +{ + return CLASS_ALIEN_MILITARY; +} + + +int CISlave::IRelationship( CBaseEntity *pTarget ) +{ + if ( (pTarget->IsPlayer()) ) + if ( (pev->spawnflags & SF_MONSTER_WAIT_UNTIL_PROVOKED ) && ! (m_afMemory & bits_MEMORY_PROVOKED )) + return R_NO; + return CBaseMonster::IRelationship( pTarget ); +} + + +void CISlave :: CallForHelp( char *szClassname, float flDist, EHANDLE hEnemy, Vector &vecLocation ) +{ + // ALERT( at_aiconsole, "help " ); + + // skip ones not on my netname + if ( FStringNull( pev->netname )) + return; + + CBaseEntity *pEntity = NULL; + + while ((pEntity = UTIL_FindEntityByString( pEntity, "netname", STRING( pev->netname ))) != NULL) + { + float d = (pev->origin - pEntity->pev->origin).Length(); + if (d < flDist) + { + CBaseMonster *pMonster = pEntity->MyMonsterPointer( ); + if (pMonster) + { + pMonster->m_afMemory |= bits_MEMORY_PROVOKED; + pMonster->PushEnemy( hEnemy, vecLocation ); + } + } + } +} + + +//========================================================= +// ALertSound - scream +//========================================================= +void CISlave :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + SENTENCEG_PlayRndSz(ENT(pev), "SLV_ALERT", 0.85, ATTN_NORM, 0, m_voicePitch); + + CallForHelp( "monster_alien_slave", 512, m_hEnemy, m_vecEnemyLKP ); + } +} + +//========================================================= +// IdleSound +//========================================================= +void CISlave :: IdleSound( void ) +{ + if (RANDOM_LONG( 0, 2 ) == 0) + { + SENTENCEG_PlayRndSz(ENT(pev), "SLV_IDLE", 0.85, ATTN_NORM, 0, m_voicePitch); + } + +#if 0 + int side = RANDOM_LONG( 0, 1 ) * 2 - 1; + + ClearBeams( ); + ArmBeam( side ); + + UTIL_MakeAimVectors( pev->angles ); + Vector vecSrc = pev->origin + gpGlobals->v_right * 2 * side; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecSrc.x); // X + WRITE_COORD(vecSrc.y); // Y + WRITE_COORD(vecSrc.z); // Z + WRITE_BYTE( 8 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 96 ); // b + WRITE_BYTE( 10 ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "debris/zap1.wav", 1, ATTN_NORM, 0, 100 ); +#endif +} + +//========================================================= +// PainSound +//========================================================= +void CISlave :: PainSound( void ) +{ + if (RANDOM_LONG( 0, 2 ) == 0) + { + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } +} + +//========================================================= +// DieSound +//========================================================= + +void CISlave :: DeathSound( void ) +{ + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pDeathSounds[ RANDOM_LONG(0,ARRAYSIZE(pDeathSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); +} + + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CISlave :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + + +void CISlave::Killed( entvars_t *pevAttacker, int iGib ) +{ + ClearBeams( ); + CSquadMonster::Killed( pevAttacker, iGib ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CISlave :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_WALK: + ys = 50; + break; + case ACT_RUN: + ys = 70; + break; + case ACT_IDLE: + ys = 50; + break; + default: + ys = 90; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CISlave :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + // ALERT( at_console, "event %d : %f\n", pEvent->event, pev->frame ); + switch( pEvent->event ) + { + case ISLAVE_AE_CLAW: + { + // SOUND HERE! + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.slaveDmgClaw, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = -18; + pHurt->pev->punchangle.x = 5; + } + // Play a random attack hit sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } + else + { + // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } + } + break; + + case ISLAVE_AE_CLAWRAKE: + { + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.slaveDmgClawrake, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = -18; + pHurt->pev->punchangle.x = 5; + } + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } + else + { + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } + } + break; + + case ISLAVE_AE_ZAP_POWERUP: + { + // speed up attack when on hard + if (g_iSkillLevel == SKILL_HARD) + pev->framerate = 1.5; + + UTIL_MakeAimVectors( pev->angles ); + + if (m_iBeams == 0) + { + Vector vecSrc = pev->origin + gpGlobals->v_forward * 2; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(vecSrc.x); // X + WRITE_COORD(vecSrc.y); // Y + WRITE_COORD(vecSrc.z); // Z + WRITE_BYTE( 12 ); // radius * 0.1 + WRITE_BYTE( 255 ); // r + WRITE_BYTE( 180 ); // g + WRITE_BYTE( 96 ); // b + WRITE_BYTE( 20 / pev->framerate ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); + + } + if (m_hDead != NULL) + { + WackBeam( -1, m_hDead ); + WackBeam( 1, m_hDead ); + } + else + { + ArmBeam( -1 ); + ArmBeam( 1 ); + BeamGlow( ); + } + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "debris/zap4.wav", 1, ATTN_NORM, 0, 100 + m_iBeams * 10 ); + pev->skin = m_iBeams / 2; + } + break; + + case ISLAVE_AE_ZAP_SHOOT: + { + ClearBeams( ); + + if (m_hDead != NULL) + { + Vector vecDest = m_hDead->pev->origin + Vector( 0, 0, 38 ); + TraceResult trace; + UTIL_TraceHull( vecDest, vecDest, dont_ignore_monsters, human_hull, m_hDead->edict(), &trace ); + + if ( !trace.fStartSolid ) + { + CBaseEntity *pNew = Create( "monster_alien_slave", m_hDead->pev->origin, m_hDead->pev->angles ); + CBaseMonster *pNewMonster = pNew->MyMonsterPointer( ); + pNew->pev->spawnflags |= 1; + WackBeam( -1, pNew ); + WackBeam( 1, pNew ); + UTIL_Remove( m_hDead ); + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "hassault/hw_shoot1.wav", 1, ATTN_NORM, 0, RANDOM_LONG( 130, 160 ) ); + + /* + CBaseEntity *pEffect = Create( "test_effect", pNew->Center(), pev->angles ); + pEffect->Use( this, this, USE_ON, 1 ); + */ + break; + } + } + ClearMultiDamage(); + + UTIL_MakeAimVectors( pev->angles ); + + ZapBeam( -1 ); + ZapBeam( 1 ); + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "hassault/hw_shoot1.wav", 1, ATTN_NORM, 0, RANDOM_LONG( 130, 160 ) ); + // STOP_SOUND( ENT(pev), CHAN_WEAPON, "debris/zap4.wav" ); + ApplyMultiDamage(pev, pev); + + m_flNextAttack = gpGlobals->time + RANDOM_FLOAT( 0.5, 4.0 ); + } + break; + + case ISLAVE_AE_ZAP_DONE: + { + ClearBeams( ); + } + break; + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// CheckRangeAttack1 - normal beam attack +//========================================================= +BOOL CISlave :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if (m_flNextAttack > gpGlobals->time) + { + return FALSE; + } + + return CSquadMonster::CheckRangeAttack1( flDot, flDist ); +} + +//========================================================= +// CheckRangeAttack2 - check bravery and try to resurect dead comrades +//========================================================= +BOOL CISlave :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; + + if (m_flNextAttack > gpGlobals->time) + { + return FALSE; + } + + m_hDead = NULL; + m_iBravery = 0; + + CBaseEntity *pEntity = NULL; + while ((pEntity = UTIL_FindEntityByClassname( pEntity, "monster_alien_slave" )) != NULL) + { + TraceResult tr; + + UTIL_TraceLine( EyePosition( ), pEntity->EyePosition( ), ignore_monsters, ENT(pev), &tr ); + if (tr.flFraction == 1.0 || tr.pHit == pEntity->edict()) + { + if (pEntity->pev->deadflag == DEAD_DEAD) + { + float d = (pev->origin - pEntity->pev->origin).Length(); + if (d < flDist) + { + m_hDead = pEntity; + flDist = d; + } + m_iBravery--; + } + else + { + m_iBravery++; + } + } + } + if (m_hDead != NULL) + return TRUE; + else + return FALSE; +} + + +//========================================================= +// StartTask +//========================================================= +void CISlave :: StartTask ( Task_t *pTask ) +{ + ClearBeams( ); + + CSquadMonster :: StartTask ( pTask ); +} + + +//========================================================= +// Spawn +//========================================================= +void CISlave :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/islave.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + pev->health = gSkillData.slaveHealth; + pev->view_ofs = Vector ( 0, 0, 64 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so npc will notice player and say hello + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_RANGE_ATTACK2 | bits_CAP_DOORS_GROUP; + + m_voicePitch = RANDOM_LONG( 85, 110 ); + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CISlave :: Precache() +{ + int i; + + PRECACHE_MODEL("models/islave.mdl"); + PRECACHE_MODEL("sprites/lgtning.spr"); + PRECACHE_SOUND("debris/zap1.wav"); + PRECACHE_SOUND("debris/zap4.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); + PRECACHE_SOUND("hassault/hw_shoot1.wav"); + PRECACHE_SOUND("zombie/zo_pain2.wav"); + PRECACHE_SOUND("headcrab/hc_headbite.wav"); + PRECACHE_SOUND("weapons/cbar_miss1.wav"); + + for ( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackHitSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackMissSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pDeathSounds ); i++ ) + PRECACHE_SOUND((char *)pDeathSounds[i]); + + UTIL_PrecacheOther( "test_effect" ); +} + + +//========================================================= +// TakeDamage - get provoked when injured +//========================================================= + +int CISlave :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + // don't slash one of your own + if ((bitsDamageType & DMG_SLASH) && pevAttacker && IRelationship( Instance(pevAttacker) ) < R_DL) + return 0; + + m_afMemory |= bits_MEMORY_PROVOKED; + return CSquadMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + + +void CISlave::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if (bitsDamageType & DMG_SHOCK) + return; + + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + + +// primary range attack +Task_t tlSlaveAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slSlaveAttack1[] = +{ + { + tlSlaveAttack1, + ARRAYSIZE ( tlSlaveAttack1 ), + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND | + bits_COND_HEAVY_DAMAGE, + + bits_SOUND_DANGER, + "Slave Range Attack1" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CISlave ) +{ + slSlaveAttack1, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CISlave, CSquadMonster ); + + +//========================================================= +//========================================================= +Schedule_t *CISlave :: GetSchedule( void ) +{ + ClearBeams( ); + +/* + if (pev->spawnflags) + { + pev->spawnflags = 0; + return GetScheduleOfType( SCHED_RELOAD ); + } +*/ + + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + if ( pSound->m_iType & bits_SOUND_COMBAT ) + m_afMemory |= bits_MEMORY_PROVOKED; + } + + switch (m_MonsterState) + { + case MONSTERSTATE_COMBAT: +// dead enemy + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + + if (pev->health < 20 || m_iBravery < 0) + { + if (!HasConditions( bits_COND_CAN_MELEE_ATTACK1 )) + { + m_failSchedule = SCHED_CHASE_ENEMY; + if (HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + if ( HasConditions ( bits_COND_SEE_ENEMY ) && HasConditions ( bits_COND_ENEMY_FACING_ME ) ) + { + // ALERT( at_console, "exposed\n"); + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } + } + break; + } + return CSquadMonster::GetSchedule( ); +} + + +Schedule_t *CISlave :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_FAIL: + if (HasConditions( bits_COND_CAN_MELEE_ATTACK1 )) + { + return CSquadMonster :: GetScheduleOfType( SCHED_MELEE_ATTACK1 ); ; + } + break; + case SCHED_RANGE_ATTACK1: + return slSlaveAttack1; + case SCHED_RANGE_ATTACK2: + return slSlaveAttack1; + } + return CSquadMonster :: GetScheduleOfType( Type ); +} + + +//========================================================= +// ArmBeam - small beam from arm to nearby geometry +//========================================================= + +void CISlave :: ArmBeam( int side ) +{ + TraceResult tr; + float flDist = 1.0; + + if (m_iBeams >= ISLAVE_MAX_BEAMS) + return; + + UTIL_MakeAimVectors( pev->angles ); + Vector vecSrc = pev->origin + gpGlobals->v_up * 36 + gpGlobals->v_right * side * 16 + gpGlobals->v_forward * 32; + + for (int i = 0; i < 3; i++) + { + Vector vecAim = gpGlobals->v_right * side * RANDOM_FLOAT( 0, 1 ) + gpGlobals->v_up * RANDOM_FLOAT( -1, 1 ); + TraceResult tr1; + UTIL_TraceLine ( vecSrc, vecSrc + vecAim * 512, dont_ignore_monsters, ENT( pev ), &tr1); + if (flDist > tr1.flFraction) + { + tr = tr1; + flDist = tr.flFraction; + } + } + + // Couldn't find anything close enough + if ( flDist == 1.0 ) + return; + + DecalGunshot( &tr, BULLET_PLAYER_CROWBAR ); + + m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.spr", 30 ); + if (!m_pBeam[m_iBeams]) + return; + + m_pBeam[m_iBeams]->PointEntInit( tr.vecEndPos, entindex( ) ); + m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); + // m_pBeam[m_iBeams]->SetColor( 180, 255, 96 ); + m_pBeam[m_iBeams]->SetColor( 96, 128, 16 ); + m_pBeam[m_iBeams]->SetBrightness( 64 ); + m_pBeam[m_iBeams]->SetNoise( 80 ); + m_iBeams++; +} + + +//========================================================= +// BeamGlow - brighten all beams +//========================================================= +void CISlave :: BeamGlow( ) +{ + int b = m_iBeams * 32; + if (b > 255) + b = 255; + + for (int i = 0; i < m_iBeams; i++) + { + if (m_pBeam[i]->GetBrightness() != 255) + { + m_pBeam[i]->SetBrightness( b ); + } + } +} + + +//========================================================= +// WackBeam - regenerate dead colleagues +//========================================================= +void CISlave :: WackBeam( int side, CBaseEntity *pEntity ) +{ + Vector vecDest; + float flDist = 1.0; + + if (m_iBeams >= ISLAVE_MAX_BEAMS) + return; + + if (pEntity == NULL) + return; + + m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.spr", 30 ); + if (!m_pBeam[m_iBeams]) + return; + + m_pBeam[m_iBeams]->PointEntInit( pEntity->Center(), entindex( ) ); + m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); + m_pBeam[m_iBeams]->SetColor( 180, 255, 96 ); + m_pBeam[m_iBeams]->SetBrightness( 255 ); + m_pBeam[m_iBeams]->SetNoise( 80 ); + m_iBeams++; +} + +//========================================================= +// ZapBeam - heavy damage directly forward +//========================================================= +void CISlave :: ZapBeam( int side ) +{ + Vector vecSrc, vecAim; + TraceResult tr; + CBaseEntity *pEntity; + + if (m_iBeams >= ISLAVE_MAX_BEAMS) + return; + + vecSrc = pev->origin + gpGlobals->v_up * 36; + vecAim = ShootAtEnemy( vecSrc ); + float deflection = 0.01; + vecAim = vecAim + side * gpGlobals->v_right * RANDOM_FLOAT( 0, deflection ) + gpGlobals->v_up * RANDOM_FLOAT( -deflection, deflection ); + UTIL_TraceLine ( vecSrc, vecSrc + vecAim * 1024, dont_ignore_monsters, ENT( pev ), &tr); + + m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.spr", 50 ); + if (!m_pBeam[m_iBeams]) + return; + + m_pBeam[m_iBeams]->PointEntInit( tr.vecEndPos, entindex( ) ); + m_pBeam[m_iBeams]->SetEndAttachment( side < 0 ? 2 : 1 ); + m_pBeam[m_iBeams]->SetColor( 180, 255, 96 ); + m_pBeam[m_iBeams]->SetBrightness( 255 ); + m_pBeam[m_iBeams]->SetNoise( 20 ); + m_iBeams++; + + pEntity = CBaseEntity::Instance(tr.pHit); + if (pEntity != NULL && pEntity->pev->takedamage) + { + pEntity->TraceAttack( pev, gSkillData.slaveDmgZap, vecAim, &tr, DMG_SHOCK ); + } + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); +} + + +//========================================================= +// ClearBeams - remove all beams +//========================================================= +void CISlave :: ClearBeams( ) +{ + for (int i = 0; i < ISLAVE_MAX_BEAMS; i++) + { + if (m_pBeam[i]) + { + UTIL_Remove( m_pBeam[i] ); + m_pBeam[i] = NULL; + } + } + m_iBeams = 0; + pev->skin = 0; + + STOP_SOUND( ENT(pev), CHAN_WEAPON, "debris/zap4.wav" ); +} diff --git a/dlls/items.cpp b/dlls/items.cpp index c1e8110..449be85 100644 --- a/dlls/items.cpp +++ b/dlls/items.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/items.h b/dlls/items.h index dbfbe5f..04905fc 100644 --- a/dlls/items.h +++ b/dlls/items.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/leech.cpp b/dlls/leech.cpp new file mode 100644 index 0000000..4f1ebc9 --- /dev/null +++ b/dlls/leech.cpp @@ -0,0 +1,723 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// leech - basic little swimming monster +//========================================================= +// +// UNDONE: +// DONE:Steering force model for attack +// DONE:Attack animation control / damage +// DONE:Establish range of up/down motion and steer around vertical obstacles +// DONE:Re-evaluate height periodically +// DONE:Fall (MOVETYPE_TOSS) and play different anim if out of water +// Test in complex room (c2a3?) +// DONE:Sounds? - Kelly will fix +// Blood cloud? Hurt effect? +// Group behavior? +// DONE:Save/restore +// Flop animation - just bind to ACT_TWITCH +// Fix fatal push into wall case +// +// Try this on a bird +// Try this on a model with hulls/tracehull? +// + + +#include "float.h" +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" + + + + +// Animation events +#define LEECH_AE_ATTACK 1 +#define LEECH_AE_FLOP 2 + + +// Movement constants + +#define LEECH_ACCELERATE 10 +#define LEECH_CHECK_DIST 45 +#define LEECH_SWIM_SPEED 50 +#define LEECH_SWIM_ACCEL 80 +#define LEECH_SWIM_DECEL 10 +#define LEECH_TURN_RATE 90 +#define LEECH_SIZEX 10 +#define LEECH_FRAMETIME 0.1 + + + +#define DEBUG_BEAMS 0 + +#if DEBUG_BEAMS +#include "effects.h" +#endif + + +class CLeech : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + + void EXPORT SwimThink( void ); + void EXPORT DeadThink( void ); + void Touch( CBaseEntity *pOther ) + { + if ( pOther->IsPlayer() ) + { + // If the client is pushing me, give me some base velocity + if ( gpGlobals->trace_ent && gpGlobals->trace_ent == edict() ) + { + pev->basevelocity = pOther->pev->velocity; + pev->flags |= FL_BASEVELOCITY; + } + } + } + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector(-8,-8,0); + pev->absmax = pev->origin + Vector(8,8,2); + } + + void AttackSound( void ); + void AlertSound( void ); + void UpdateMotion( void ); + float ObstacleDistance( CBaseEntity *pTarget ); + void MakeVectors( void ); + void RecalculateWaterlevel( void ); + void SwitchLeechState( void ); + + // Base entity functions + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int BloodColor( void ) { return DONT_BLEED; } + void Killed( entvars_t *pevAttacker, int iGib ); + void Activate( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + int Classify( void ) { return CLASS_INSECT; } + int IRelationship( CBaseEntity *pTarget ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + static const char *pAttackSounds[]; + static const char *pAlertSounds[]; + +private: + // UNDONE: Remove unused boid vars, do group behavior + float m_flTurning;// is this boid turning? + BOOL m_fPathBlocked;// TRUE if there is an obstacle ahead + float m_flAccelerate; + float m_obstacle; + float m_top; + float m_bottom; + float m_height; + float m_waterTime; + float m_sideTime; // Timer to randomly check clearance on sides + float m_zTime; + float m_stateTime; + float m_attackSoundTime; + +#if DEBUG_BEAMS + CBeam *m_pb; + CBeam *m_pt; +#endif +}; + + + +LINK_ENTITY_TO_CLASS( monster_leech, CLeech ); + +TYPEDESCRIPTION CLeech::m_SaveData[] = +{ + DEFINE_FIELD( CLeech, m_flTurning, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_fPathBlocked, FIELD_BOOLEAN ), + DEFINE_FIELD( CLeech, m_flAccelerate, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_obstacle, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_top, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_bottom, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_height, FIELD_FLOAT ), + DEFINE_FIELD( CLeech, m_waterTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_sideTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_zTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_stateTime, FIELD_TIME ), + DEFINE_FIELD( CLeech, m_attackSoundTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CLeech, CBaseMonster ); + + +const char *CLeech::pAttackSounds[] = +{ + "leech/leech_bite1.wav", + "leech/leech_bite2.wav", + "leech/leech_bite3.wav", +}; + +const char *CLeech::pAlertSounds[] = +{ + "leech/leech_alert1.wav", + "leech/leech_alert2.wav", +}; + + +void CLeech::Spawn( void ) +{ + Precache(); + SET_MODEL(ENT(pev), "models/leech.mdl"); + // Just for fun + // SET_MODEL(ENT(pev), "models/icky.mdl"); + +// UTIL_SetSize( pev, g_vecZero, g_vecZero ); + UTIL_SetSize( pev, Vector(-1,-1,0), Vector(1,1,2)); + // Don't push the minz down too much or the water check will fail because this entity is really point-sized + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + SetBits(pev->flags, FL_SWIM); + pev->health = gSkillData.leechHealth; + + m_flFieldOfView = -0.5; // 180 degree FOV + m_flDistLook = 750; + MonsterInit(); + SetThink( SwimThink ); + SetUse( NULL ); + SetTouch( NULL ); + pev->view_ofs = g_vecZero; + + m_flTurning = 0; + m_fPathBlocked = FALSE; + SetActivity( ACT_SWIM ); + SetState( MONSTERSTATE_IDLE ); + m_stateTime = gpGlobals->time + RANDOM_FLOAT( 1, 5 ); +} + + +void CLeech::Activate( void ) +{ + RecalculateWaterlevel(); +} + + + +void CLeech::RecalculateWaterlevel( void ) +{ + // Calculate boundaries + Vector vecTest = pev->origin - Vector(0,0,400); + + TraceResult tr; + + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + if ( tr.flFraction != 1.0 ) + m_bottom = tr.vecEndPos.z + 1; + else + m_bottom = vecTest.z; + + m_top = UTIL_WaterLevel( pev->origin, pev->origin.z, pev->origin.z + 400 ) - 1; + + // Chop off 20% of the outside range + float newBottom = m_bottom * 0.8 + m_top * 0.2; + m_top = m_bottom * 0.2 + m_top * 0.8; + m_bottom = newBottom; + m_height = RANDOM_FLOAT( m_bottom, m_top ); + m_waterTime = gpGlobals->time + RANDOM_FLOAT( 5, 7 ); +} + + +void CLeech::SwitchLeechState( void ) +{ + m_stateTime = gpGlobals->time + RANDOM_FLOAT( 3, 6 ); + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + m_hEnemy = NULL; + SetState( MONSTERSTATE_IDLE ); + // We may be up against the player, so redo the side checks + m_sideTime = 0; + } + else + { + Look( m_flDistLook ); + CBaseEntity *pEnemy = BestVisibleEnemy(); + if ( pEnemy && pEnemy->pev->waterlevel != 0 ) + { + m_hEnemy = pEnemy; + SetState( MONSTERSTATE_COMBAT ); + m_stateTime = gpGlobals->time + RANDOM_FLOAT( 18, 25 ); + AlertSound(); + } + } +} + + +int CLeech::IRelationship( CBaseEntity *pTarget ) +{ + if ( pTarget->IsPlayer() ) + return R_DL; + return CBaseMonster::IRelationship( pTarget ); +} + + + +void CLeech::AttackSound( void ) +{ + if ( gpGlobals->time > m_attackSoundTime ) + { + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 1.0, ATTN_NORM, 0, PITCH_NORM ); + m_attackSoundTime = gpGlobals->time + 0.5; + } +} + + +void CLeech::AlertSound( void ) +{ + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAlertSounds[ RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1) ], 1.0, ATTN_NORM * 0.5, 0, PITCH_NORM ); +} + + +void CLeech::Precache( void ) +{ + int i; + + //PRECACHE_MODEL("models/icky.mdl"); + PRECACHE_MODEL("models/leech.mdl"); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); +} + + +int CLeech::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + pev->velocity = g_vecZero; + + // Nudge the leech away from the damage + if ( pevInflictor ) + { + pev->velocity = (pev->origin - pevInflictor->origin).Normalize() * 25; + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +void CLeech::HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case LEECH_AE_ATTACK: + AttackSound(); + CBaseEntity *pEnemy; + + pEnemy = m_hEnemy; + if ( pEnemy != NULL ) + { + Vector dir, face; + + UTIL_MakeVectorsPrivate( pev->angles, face, NULL, NULL ); + face.z = 0; + dir = (pEnemy->pev->origin - pev->origin); + dir.z = 0; + dir = dir.Normalize(); + face = face.Normalize(); + + + if ( DotProduct(dir, face) > 0.9 ) // Only take damage if the leech is facing the prey + pEnemy->TakeDamage( pev, pev, gSkillData.leechDmgBite, DMG_SLASH ); + } + m_stateTime -= 2; + break; + + case LEECH_AE_FLOP: + // Play flop sound + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + + +void CLeech::MakeVectors( void ) +{ + Vector tmp = pev->angles; + tmp.x = -tmp.x; + UTIL_MakeVectors ( tmp ); +} + + +// +// ObstacleDistance - returns normalized distance to obstacle +// +float CLeech::ObstacleDistance( CBaseEntity *pTarget ) +{ + TraceResult tr; + Vector vecTest; + + // use VELOCITY, not angles, not all boids point the direction they are flying + //Vector vecDir = UTIL_VecToAngles( pev->velocity ); + MakeVectors(); + + // check for obstacle ahead + vecTest = pev->origin + gpGlobals->v_forward * LEECH_CHECK_DIST; + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + + if ( tr.fStartSolid ) + { + pev->speed = -LEECH_SWIM_SPEED * 0.5; +// ALERT( at_console, "Stuck from (%f %f %f) to (%f %f %f)\n", pev->oldorigin.x, pev->oldorigin.y, pev->oldorigin.z, pev->origin.x, pev->origin.y, pev->origin.z ); +// UTIL_SetOrigin( pev, pev->oldorigin ); + } + + if ( tr.flFraction != 1.0 ) + { + if ( (pTarget == NULL || tr.pHit != pTarget->edict()) ) + { + return tr.flFraction; + } + else + { + if ( fabs(m_height - pev->origin.z) > 10 ) + return tr.flFraction; + } + } + + if ( m_sideTime < gpGlobals->time ) + { + // extra wide checks + vecTest = pev->origin + gpGlobals->v_right * LEECH_SIZEX * 2 + gpGlobals->v_forward * LEECH_CHECK_DIST; + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + if (tr.flFraction != 1.0) + return tr.flFraction; + + vecTest = pev->origin - gpGlobals->v_right * LEECH_SIZEX * 2 + gpGlobals->v_forward * LEECH_CHECK_DIST; + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + if (tr.flFraction != 1.0) + return tr.flFraction; + + // Didn't hit either side, so stop testing for another 0.5 - 1 seconds + m_sideTime = gpGlobals->time + RANDOM_FLOAT(0.5,1); + } + return 1.0; +} + + +void CLeech::DeadThink( void ) +{ + if ( m_fSequenceFinished ) + { + if ( m_Activity == ACT_DIEFORWARD ) + { + SetThink( NULL ); + StopAnimation(); + return; + } + else if ( pev->flags & FL_ONGROUND ) + { + pev->solid = SOLID_NOT; + SetActivity(ACT_DIEFORWARD); + } + } + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; + + // Apply damage velocity, but keep out of the walls + if ( pev->velocity.x != 0 || pev->velocity.y != 0 ) + { + TraceResult tr; + + // Look 0.5 seconds ahead + UTIL_TraceLine(pev->origin, pev->origin + pev->velocity * 0.5, missile, edict(), &tr); + if (tr.flFraction != 1.0) + { + pev->velocity.x = 0; + pev->velocity.y = 0; + } + } +} + + + +void CLeech::UpdateMotion( void ) +{ + float flapspeed = (pev->speed - m_flAccelerate) / LEECH_ACCELERATE; + m_flAccelerate = m_flAccelerate * 0.8 + pev->speed * 0.2; + + if (flapspeed < 0) + flapspeed = -flapspeed; + flapspeed += 1.0; + if (flapspeed < 0.5) + flapspeed = 0.5; + if (flapspeed > 1.9) + flapspeed = 1.9; + + pev->framerate = flapspeed; + + if ( !m_fPathBlocked ) + pev->avelocity.y = pev->ideal_yaw; + else + pev->avelocity.y = pev->ideal_yaw * m_obstacle; + + if ( pev->avelocity.y > 150 ) + m_IdealActivity = ACT_TURN_LEFT; + else if ( pev->avelocity.y < -150 ) + m_IdealActivity = ACT_TURN_RIGHT; + else + m_IdealActivity = ACT_SWIM; + + // lean + float targetPitch, delta; + delta = m_height - pev->origin.z; + + if ( delta < -10 ) + targetPitch = -30; + else if ( delta > 10 ) + targetPitch = 30; + else + targetPitch = 0; + + pev->angles.x = UTIL_Approach( targetPitch, pev->angles.x, 60 * LEECH_FRAMETIME ); + + // bank + pev->avelocity.z = - (pev->angles.z + (pev->avelocity.y * 0.25)); + + if ( m_MonsterState == MONSTERSTATE_COMBAT && HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + m_IdealActivity = ACT_MELEE_ATTACK1; + + // Out of water check + if ( !pev->waterlevel ) + { + pev->movetype = MOVETYPE_TOSS; + m_IdealActivity = ACT_TWITCH; + pev->velocity = g_vecZero; + + // Animation will intersect the floor if either of these is non-zero + pev->angles.z = 0; + pev->angles.x = 0; + + if ( pev->framerate < 1.0 ) + pev->framerate = 1.0; + } + else if ( pev->movetype == MOVETYPE_TOSS ) + { + pev->movetype = MOVETYPE_FLY; + pev->flags &= ~FL_ONGROUND; + RecalculateWaterlevel(); + m_waterTime = gpGlobals->time + 2; // Recalc again soon, water may be rising + } + + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } + float flInterval = StudioFrameAdvance(); + DispatchAnimEvents ( flInterval ); + +#if DEBUG_BEAMS + if ( !m_pb ) + m_pb = CBeam::BeamCreate( "sprites/laserbeam.spr", 5 ); + if ( !m_pt ) + m_pt = CBeam::BeamCreate( "sprites/laserbeam.spr", 5 ); + m_pb->PointsInit( pev->origin, pev->origin + gpGlobals->v_forward * LEECH_CHECK_DIST ); + m_pt->PointsInit( pev->origin, pev->origin - gpGlobals->v_right * (pev->avelocity.y*0.25) ); + if ( m_fPathBlocked ) + { + float color = m_obstacle * 30; + if ( m_obstacle == 1.0 ) + color = 0; + if ( color > 255 ) + color = 255; + m_pb->SetColor( 255, (int)color, (int)color ); + } + else + m_pb->SetColor( 255, 255, 0 ); + m_pt->SetColor( 0, 0, 255 ); +#endif +} + + +void CLeech::SwimThink( void ) +{ + TraceResult tr; + float flLeftSide; + float flRightSide; + float targetSpeed; + float targetYaw = 0; + CBaseEntity *pTarget; + + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + { + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(1,1.5); + pev->velocity = g_vecZero; + return; + } + else + pev->nextthink = gpGlobals->time + 0.1; + + targetSpeed = LEECH_SWIM_SPEED; + + if ( m_waterTime < gpGlobals->time ) + RecalculateWaterlevel(); + + if ( m_stateTime < gpGlobals->time ) + SwitchLeechState(); + + ClearConditions( bits_COND_CAN_MELEE_ATTACK1 ); + switch( m_MonsterState ) + { + case MONSTERSTATE_COMBAT: + pTarget = m_hEnemy; + if ( !pTarget ) + SwitchLeechState(); + else + { + // Chase the enemy's eyes + m_height = pTarget->pev->origin.z + pTarget->pev->view_ofs.z - 5; + // Clip to viable water area + if ( m_height < m_bottom ) + m_height = m_bottom; + else if ( m_height > m_top ) + m_height = m_top; + Vector location = pTarget->pev->origin - pev->origin; + location.z += (pTarget->pev->view_ofs.z); + if ( location.Length() < 40 ) + SetConditions( bits_COND_CAN_MELEE_ATTACK1 ); + // Turn towards target ent + targetYaw = UTIL_VecToYaw( location ); + + targetYaw = UTIL_AngleDiff( targetYaw, UTIL_AngleMod( pev->angles.y ) ); + + if ( targetYaw < (-LEECH_TURN_RATE*0.75) ) + targetYaw = (-LEECH_TURN_RATE*0.75); + else if ( targetYaw > (LEECH_TURN_RATE*0.75) ) + targetYaw = (LEECH_TURN_RATE*0.75); + else + targetSpeed *= 2; + } + + break; + + default: + if ( m_zTime < gpGlobals->time ) + { + float newHeight = RANDOM_FLOAT( m_bottom, m_top ); + m_height = 0.5 * m_height + 0.5 * newHeight; + m_zTime = gpGlobals->time + RANDOM_FLOAT( 1, 4 ); + } + if ( RANDOM_LONG( 0, 100 ) < 10 ) + targetYaw = RANDOM_LONG( -30, 30 ); + pTarget = NULL; + // oldorigin test + if ( (pev->origin - pev->oldorigin).Length() < 1 ) + { + // If leech didn't move, there must be something blocking it, so try to turn + m_sideTime = 0; + } + + break; + } + + m_obstacle = ObstacleDistance( pTarget ); + pev->oldorigin = pev->origin; + if ( m_obstacle < 0.1 ) + m_obstacle = 0.1; + + // is the way ahead clear? + if ( m_obstacle == 1.0 ) + { + // if the leech is turning, stop the trend. + if ( m_flTurning != 0 ) + { + m_flTurning = 0; + } + + m_fPathBlocked = FALSE; + pev->speed = UTIL_Approach( targetSpeed, pev->speed, LEECH_SWIM_ACCEL * LEECH_FRAMETIME ); + pev->velocity = gpGlobals->v_forward * pev->speed; + + } + else + { + m_obstacle = 1.0 / m_obstacle; + // IF we get this far in the function, the leader's path is blocked! + m_fPathBlocked = TRUE; + + if ( m_flTurning == 0 )// something in the way and leech is not already turning to avoid + { + Vector vecTest; + // measure clearance on left and right to pick the best dir to turn + vecTest = pev->origin + (gpGlobals->v_right * LEECH_SIZEX) + (gpGlobals->v_forward * LEECH_CHECK_DIST); + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + flRightSide = tr.flFraction; + + vecTest = pev->origin + (gpGlobals->v_right * -LEECH_SIZEX) + (gpGlobals->v_forward * LEECH_CHECK_DIST); + UTIL_TraceLine(pev->origin, vecTest, missile, edict(), &tr); + flLeftSide = tr.flFraction; + + // turn left, right or random depending on clearance ratio + float delta = (flRightSide - flLeftSide); + if ( delta > 0.1 || (delta > -0.1 && RANDOM_LONG(0,100)<50) ) + m_flTurning = -LEECH_TURN_RATE; + else + m_flTurning = LEECH_TURN_RATE; + } + pev->speed = UTIL_Approach( -(LEECH_SWIM_SPEED*0.5), pev->speed, LEECH_SWIM_DECEL * LEECH_FRAMETIME * m_obstacle ); + pev->velocity = gpGlobals->v_forward * pev->speed; + } + pev->ideal_yaw = m_flTurning + targetYaw; + UpdateMotion(); +} + + +void CLeech::Killed(entvars_t *pevAttacker, int iGib) +{ + Vector vecSplatDir; + TraceResult tr; + + //ALERT(at_aiconsole, "Leech: killed\n"); + // tell owner ( if any ) that we're dead.This is mostly for MonsterMaker functionality. + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if (pOwner) + pOwner->DeathNotice(pev); + + // When we hit the ground, play the "death_end" activity + if ( pev->waterlevel ) + { + pev->angles.z = 0; + pev->angles.x = 0; + pev->origin.z += 1; + pev->avelocity = g_vecZero; + if ( RANDOM_LONG( 0, 99 ) < 70 ) + pev->avelocity.y = RANDOM_LONG( -720, 720 ); + + pev->gravity = 0.02; + ClearBits(pev->flags, FL_ONGROUND); + SetActivity( ACT_DIESIMPLE ); + } + else + SetActivity( ACT_DIEFORWARD ); + + pev->movetype = MOVETYPE_TOSS; + pev->takedamage = DAMAGE_NO; + SetThink( DeadThink ); +} + + diff --git a/dlls/lights.cpp b/dlls/lights.cpp index c24755b..147dfdf 100644 --- a/dlls/lights.cpp +++ b/dlls/lights.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/maprules.cpp b/dlls/maprules.cpp index f5f7f69..9402f31 100644 --- a/dlls/maprules.cpp +++ b/dlls/maprules.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/maprules.h b/dlls/maprules.h index 1591a03..975dafa 100644 --- a/dlls/maprules.h +++ b/dlls/maprules.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/monsterevent.h b/dlls/monsterevent.h index ee2d634..58357e1 100644 --- a/dlls/monsterevent.h +++ b/dlls/monsterevent.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/monstermaker.cpp b/dlls/monstermaker.cpp new file mode 100644 index 0000000..1b4b62a --- /dev/null +++ b/dlls/monstermaker.cpp @@ -0,0 +1,292 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Monster Maker - this is an entity that creates monsters +// in the game. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "saverestore.h" + +// Monstermaker spawnflags +#define SF_MONSTERMAKER_START_ON 1 // start active ( if has targetname ) +#define SF_MONSTERMAKER_CYCLIC 4 // drop one monster every time fired. +#define SF_MONSTERMAKER_MONSTERCLIP 8 // Children are blocked by monsterclip + +//========================================================= +// MonsterMaker - this ent creates monsters during the game. +//========================================================= +class CMonsterMaker : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT CyclicUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT MakerThink ( void ); + void DeathNotice ( entvars_t *pevChild );// monster maker children use this to tell the monster maker that they have died. + void MakeMonster( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + string_t m_iszMonsterClassname;// classname of the monster(s) that will be created. + + int m_cNumMonsters;// max number of monsters this ent can create + + + int m_cLiveChildren;// how many monsters made by this monster maker that are currently alive + int m_iMaxLiveChildren;// max number of monsters that this maker may have out at one time. + + float m_flGround; // z coord of the ground under me, used to make sure no monsters are under the maker when it drops a new child + + BOOL m_fActive; + BOOL m_fFadeChildren;// should we make the children fadeout? +}; + +LINK_ENTITY_TO_CLASS( monstermaker, CMonsterMaker ); + +TYPEDESCRIPTION CMonsterMaker::m_SaveData[] = +{ + DEFINE_FIELD( CMonsterMaker, m_iszMonsterClassname, FIELD_STRING ), + DEFINE_FIELD( CMonsterMaker, m_cNumMonsters, FIELD_INTEGER ), + DEFINE_FIELD( CMonsterMaker, m_cLiveChildren, FIELD_INTEGER ), + DEFINE_FIELD( CMonsterMaker, m_flGround, FIELD_FLOAT ), + DEFINE_FIELD( CMonsterMaker, m_iMaxLiveChildren, FIELD_INTEGER ), + DEFINE_FIELD( CMonsterMaker, m_fActive, FIELD_BOOLEAN ), + DEFINE_FIELD( CMonsterMaker, m_fFadeChildren, FIELD_BOOLEAN ), +}; + + +IMPLEMENT_SAVERESTORE( CMonsterMaker, CBaseMonster ); + +void CMonsterMaker :: KeyValue( KeyValueData *pkvd ) +{ + + if ( FStrEq(pkvd->szKeyName, "monstercount") ) + { + m_cNumMonsters = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "m_imaxlivechildren") ) + { + m_iMaxLiveChildren = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if ( FStrEq(pkvd->szKeyName, "monstertype") ) + { + m_iszMonsterClassname = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + + +void CMonsterMaker :: Spawn( ) +{ + pev->solid = SOLID_NOT; + + m_cLiveChildren = 0; + Precache(); + if ( !FStringNull ( pev->targetname ) ) + { + if ( pev->spawnflags & SF_MONSTERMAKER_CYCLIC ) + { + SetUse ( CyclicUse );// drop one monster each time we fire + } + else + { + SetUse ( ToggleUse );// so can be turned on/off + } + + if ( FBitSet ( pev->spawnflags, SF_MONSTERMAKER_START_ON ) ) + {// start making monsters as soon as monstermaker spawns + m_fActive = TRUE; + SetThink ( MakerThink ); + } + else + {// wait to be activated. + m_fActive = FALSE; + SetThink ( SUB_DoNothing ); + } + } + else + {// no targetname, just start. + pev->nextthink = gpGlobals->time + m_flDelay; + m_fActive = TRUE; + SetThink ( MakerThink ); + } + + if ( m_cNumMonsters == 1 ) + { + m_fFadeChildren = FALSE; + } + else + { + m_fFadeChildren = TRUE; + } + + m_flGround = 0; +} + +void CMonsterMaker :: Precache( void ) +{ + CBaseMonster::Precache(); + + UTIL_PrecacheOther( STRING( m_iszMonsterClassname ) ); +} + +//========================================================= +// MakeMonster- this is the code that drops the monster +//========================================================= +void CMonsterMaker::MakeMonster( void ) +{ + edict_t *pent; + entvars_t *pevCreate; + + if ( m_iMaxLiveChildren > 0 && m_cLiveChildren >= m_iMaxLiveChildren ) + {// not allowed to make a new one yet. Too many live ones out right now. + return; + } + + if ( !m_flGround ) + { + // set altitude. Now that I'm activated, any breakables, etc should be out from under me. + TraceResult tr; + + UTIL_TraceLine ( pev->origin, pev->origin - Vector ( 0, 0, 2048 ), ignore_monsters, ENT(pev), &tr ); + m_flGround = tr.vecEndPos.z; + } + + Vector mins = pev->origin - Vector( 34, 34, 0 ); + Vector maxs = pev->origin + Vector( 34, 34, 0 ); + maxs.z = pev->origin.z; + mins.z = m_flGround; + + CBaseEntity *pList[2]; + int count = UTIL_EntitiesInBox( pList, 2, mins, maxs, FL_CLIENT|FL_MONSTER ); + if ( count ) + { + // don't build a stack of monsters! + return; + } + + pent = CREATE_NAMED_ENTITY( m_iszMonsterClassname ); + + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "NULL Ent in MonsterMaker!\n" ); + return; + } + + // If I have a target, fire! + if ( !FStringNull ( pev->target ) ) + { + // delay already overloaded for this entity, so can't call SUB_UseTargets() + FireTargets( STRING(pev->target), this, this, USE_TOGGLE, 0 ); + } + + pevCreate = VARS( pent ); + pevCreate->origin = pev->origin; + pevCreate->angles = pev->angles; + SetBits( pevCreate->spawnflags, SF_MONSTER_FALL_TO_GROUND ); + + // Children hit monsterclip brushes + if ( pev->spawnflags & SF_MONSTERMAKER_MONSTERCLIP ) + SetBits( pevCreate->spawnflags, SF_MONSTER_HITMONSTERCLIP ); + + DispatchSpawn( ENT( pevCreate ) ); + pevCreate->owner = edict(); + + if ( !FStringNull( pev->netname ) ) + { + // if I have a netname (overloaded), give the child monster that name as a targetname + pevCreate->targetname = pev->netname; + } + + m_cLiveChildren++;// count this monster + m_cNumMonsters--; + + if ( m_cNumMonsters == 0 ) + { + // Disable this forever. Don't kill it because it still gets death notices + SetThink( NULL ); + SetUse( NULL ); + } +} + +//========================================================= +// CyclicUse - drops one monster from the monstermaker +// each time we call this. +//========================================================= +void CMonsterMaker::CyclicUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + MakeMonster(); +} + +//========================================================= +// ToggleUse - activates/deactivates the monster maker +//========================================================= +void CMonsterMaker :: ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_fActive ) ) + return; + + if ( m_fActive ) + { + m_fActive = FALSE; + SetThink ( NULL ); + } + else + { + m_fActive = TRUE; + SetThink ( MakerThink ); + } + + pev->nextthink = gpGlobals->time; +} + +//========================================================= +// MakerThink - creates a new monster every so often +//========================================================= +void CMonsterMaker :: MakerThink ( void ) +{ + pev->nextthink = gpGlobals->time + m_flDelay; + + MakeMonster(); +} + + +//========================================================= +//========================================================= +void CMonsterMaker :: DeathNotice ( entvars_t *pevChild ) +{ + // ok, we've gotten the deathnotice from our child, now clear out its owner if we don't want it to fade. + m_cLiveChildren--; + + if ( !m_fFadeChildren ) + { + pevChild->owner = NULL; + } +} + + diff --git a/dlls/monsters.cpp b/dlls/monsters.cpp new file mode 100644 index 0000000..e9e9443 --- /dev/null +++ b/dlls/monsters.cpp @@ -0,0 +1,3448 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== monsters.cpp ======================================================== + + Monster-related utility code + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" +#include "saverestore.h" +#include "weapons.h" +#include "scripted.h" +#include "squadmonster.h" +#include "decals.h" +#include "soundent.h" +#include "gamerules.h" + +#define MONSTER_CUT_CORNER_DIST 8 // 8 means the monster's bounding box is contained without the box of the node in WC + + +Vector VecBModelOrigin( entvars_t* pevBModel ); + +extern DLL_GLOBAL BOOL g_fDrawLines; +extern DLL_GLOBAL short g_sModelIndexLaser;// holds the index for the laser beam +extern DLL_GLOBAL short g_sModelIndexLaserDot;// holds the index for the laser beam dot + +extern CGraph WorldGraph;// the world node graph + + + +// Global Savedata for monster +// UNDONE: Save schedule data? Can this be done? We may +// lose our enemy pointer or other data (goal ent, target, etc) +// that make the current schedule invalid, perhaps it's best +// to just pick a new one when we start up again. +TYPEDESCRIPTION CBaseMonster::m_SaveData[] = +{ + DEFINE_FIELD( CBaseMonster, m_hEnemy, FIELD_EHANDLE ), + DEFINE_FIELD( CBaseMonster, m_hTargetEnt, FIELD_EHANDLE ), + DEFINE_ARRAY( CBaseMonster, m_hOldEnemy, FIELD_EHANDLE, MAX_OLD_ENEMIES ), + DEFINE_ARRAY( CBaseMonster, m_vecOldEnemy, FIELD_POSITION_VECTOR, MAX_OLD_ENEMIES ), + DEFINE_FIELD( CBaseMonster, m_flFieldOfView, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flWaitFinished, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_flMoveWaitFinished, FIELD_TIME ), + + DEFINE_FIELD( CBaseMonster, m_Activity, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_IdealActivity, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_LastHitGroup, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_MonsterState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_IdealMonsterState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iTaskStatus, FIELD_INTEGER ), + + //Schedule_t *m_pSchedule; + + DEFINE_FIELD( CBaseMonster, m_iScheduleIndex, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afConditions, FIELD_INTEGER ), + //WayPoint_t m_Route[ ROUTE_SIZE ]; +// DEFINE_FIELD( CBaseMonster, m_movementGoal, FIELD_INTEGER ), +// DEFINE_FIELD( CBaseMonster, m_iRouteIndex, FIELD_INTEGER ), +// DEFINE_FIELD( CBaseMonster, m_moveWaitTime, FIELD_FLOAT ), + + DEFINE_FIELD( CBaseMonster, m_vecMoveGoal, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_movementActivity, FIELD_INTEGER ), + + // int m_iAudibleList; // first index of a linked list of sounds that the monster can hear. +// DEFINE_FIELD( CBaseMonster, m_afSoundTypes, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_vecLastPosition, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_iHintNode, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afMemory, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iMaxHealth, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_vecEnemyLKP, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseMonster, m_cAmmoLoaded, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_afCapability, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_bitsDamageType, FIELD_INTEGER ), + DEFINE_ARRAY( CBaseMonster, m_rgbTimeBasedDamage, FIELD_CHARACTER, CDMG_TIMEBASED ), + DEFINE_FIELD( CBaseMonster, m_bloodColor, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_failSchedule, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseMonster, m_flHungryTime, FIELD_TIME ), + DEFINE_FIELD( CBaseMonster, m_flDistTooFar, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_flDistLook, FIELD_FLOAT ), + DEFINE_FIELD( CBaseMonster, m_iTriggerCondition, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_iszTriggerTarget, FIELD_STRING ), + + DEFINE_FIELD( CBaseMonster, m_HackedGunPos, FIELD_VECTOR ), + + DEFINE_FIELD( CBaseMonster, m_scriptState, FIELD_INTEGER ), + DEFINE_FIELD( CBaseMonster, m_pCine, FIELD_CLASSPTR ), +}; + +//IMPLEMENT_SAVERESTORE( CBaseMonster, CBaseToggle ); +int CBaseMonster::Save( CSave &save ) +{ + if ( !CBaseToggle::Save(save) ) + return 0; + return save.WriteFields( "CBaseMonster", this, m_SaveData, ARRAYSIZE(m_SaveData) ); +} + +int CBaseMonster::Restore( CRestore &restore ) +{ + if ( !CBaseToggle::Restore(restore) ) + return 0; + int status = restore.ReadFields( "CBaseMonster", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + // We don't save/restore routes yet + RouteClear(); + + // We don't save/restore schedules yet + m_pSchedule = NULL; + m_iTaskStatus = TASKSTATUS_NEW; + + // Reset animation + m_Activity = ACT_RESET; + + // If we don't have an enemy, clear conditions like see enemy, etc. + if ( m_hEnemy == NULL ) + m_afConditions = 0; + + return status; +} + + +//========================================================= +// Eat - makes a monster full for a little while. +//========================================================= +void CBaseMonster :: Eat ( float flFullDuration ) +{ + m_flHungryTime = gpGlobals->time + flFullDuration; +} + +//========================================================= +// FShouldEat - returns true if a monster is hungry. +//========================================================= +BOOL CBaseMonster :: FShouldEat ( void ) +{ + if ( m_flHungryTime > gpGlobals->time ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +// BarnacleVictimBitten - called +// by Barnacle victims when the barnacle pulls their head +// into its mouth +//========================================================= +void CBaseMonster :: BarnacleVictimBitten ( entvars_t *pevBarnacle ) +{ + Schedule_t *pNewSchedule; + + pNewSchedule = GetScheduleOfType( SCHED_BARNACLE_VICTIM_CHOMP ); + + if ( pNewSchedule ) + { + ChangeSchedule( pNewSchedule ); + } +} + +//========================================================= +// BarnacleVictimReleased - called by barnacle victims when +// the host barnacle is killed. +//========================================================= +void CBaseMonster :: BarnacleVictimReleased ( void ) +{ + m_IdealMonsterState = MONSTERSTATE_IDLE; + + pev->velocity = g_vecZero; + pev->movetype = MOVETYPE_STEP; +} + +//========================================================= +// Listen - monsters dig through the active sound list for +// any sounds that may interest them. (smells, too!) +//========================================================= +void CBaseMonster :: Listen ( void ) +{ + int iSound; + int iMySounds; + float hearingSensitivity; + CSound *pCurrentSound; + + m_iAudibleList = SOUNDLIST_EMPTY; + ClearConditions(bits_COND_HEAR_SOUND | bits_COND_SMELL | bits_COND_SMELL_FOOD); + m_afSoundTypes = 0; + + iMySounds = ISoundMask(); + + if ( m_pSchedule ) + { + //!!!WATCH THIS SPOT IF YOU ARE HAVING SOUND RELATED BUGS! + // Make sure your schedule AND personal sound masks agree! + iMySounds &= m_pSchedule->iSoundMask; + } + + iSound = CSoundEnt::ActiveList(); + + // UNDONE: Clear these here? + ClearConditions( bits_COND_HEAR_SOUND | bits_COND_SMELL_FOOD | bits_COND_SMELL ); + hearingSensitivity = HearingSensitivity( ); + + while ( iSound != SOUNDLIST_EMPTY ) + { + pCurrentSound = CSoundEnt::SoundPointerForIndex( iSound ); + + if ( pCurrentSound && + ( pCurrentSound->m_iType & iMySounds ) && + ( pCurrentSound->m_vecOrigin - EarPosition() ).Length() <= pCurrentSound->m_iVolume * hearingSensitivity ) + + //if ( ( g_pSoundEnt->m_SoundPool[ iSound ].m_iType & iMySounds ) && ( g_pSoundEnt->m_SoundPool[ iSound ].m_vecOrigin - EarPosition()).Length () <= g_pSoundEnt->m_SoundPool[ iSound ].m_iVolume * hearingSensitivity ) + { + // the monster cares about this sound, and it's close enough to hear. + //g_pSoundEnt->m_SoundPool[ iSound ].m_iNextAudible = m_iAudibleList; + pCurrentSound->m_iNextAudible = m_iAudibleList; + + if ( pCurrentSound->FIsSound() ) + { + // this is an audible sound. + SetConditions( bits_COND_HEAR_SOUND ); + } + else + { + // if not a sound, must be a smell - determine if it's just a scent, or if it's a food scent +// if ( g_pSoundEnt->m_SoundPool[ iSound ].m_iType & ( bits_SOUND_MEAT | bits_SOUND_CARCASS ) ) + if ( pCurrentSound->m_iType & ( bits_SOUND_MEAT | bits_SOUND_CARCASS ) ) + { + // the detected scent is a food item, so set both conditions. + // !!!BUGBUG - maybe a virtual function to determine whether or not the scent is food? + SetConditions( bits_COND_SMELL_FOOD ); + SetConditions( bits_COND_SMELL ); + } + else + { + // just a normal scent. + SetConditions( bits_COND_SMELL ); + } + } + +// m_afSoundTypes |= g_pSoundEnt->m_SoundPool[ iSound ].m_iType; + m_afSoundTypes |= pCurrentSound->m_iType; + + m_iAudibleList = iSound; + } + +// iSound = g_pSoundEnt->m_SoundPool[ iSound ].m_iNext; + iSound = pCurrentSound->m_iNext; + } +} + +//========================================================= +// FLSoundVolume - subtracts the volume of the given sound +// from the distance the sound source is from the caller, +// and returns that value, which is considered to be the 'local' +// volume of the sound. +//========================================================= +float CBaseMonster :: FLSoundVolume ( CSound *pSound ) +{ + return ( pSound->m_iVolume - ( ( pSound->m_vecOrigin - pev->origin ).Length() ) ); +} + +//========================================================= +// FValidateHintType - tells use whether or not the monster cares +// about the type of Hint Node given +//========================================================= +BOOL CBaseMonster :: FValidateHintType ( short sHint ) +{ + return FALSE; +} + +//========================================================= +// Look - Base class monster function to find enemies or +// food by sight. iDistance is distance ( in units ) that the +// monster can see. +// +// Sets the sight bits of the m_afConditions mask to indicate +// which types of entities were sighted. +// Function also sets the Looker's m_pLink +// to the head of a link list that contains all visible ents. +// (linked via each ent's m_pLink field) +// +//========================================================= +void CBaseMonster :: Look ( int iDistance ) +{ + int iSighted = 0; + + // DON'T let visibility information from last frame sit around! + ClearConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR | bits_COND_SEE_NEMESIS | bits_COND_SEE_CLIENT); + + m_pLink = NULL; + + CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with + + // See no evil if prisoner is set + if ( !FBitSet( pev->spawnflags, SF_MONSTER_PRISONER ) ) + { + CBaseEntity *pList[100]; + + Vector delta = Vector( iDistance, iDistance, iDistance ); + + // Find only monsters/clients in box, NOT limited to PVS + int count = UTIL_EntitiesInBox( pList, 100, pev->origin - delta, pev->origin + delta, FL_CLIENT|FL_MONSTER ); + for ( int i = 0; i < count; i++ ) + { + pSightEnt = pList[i]; + // !!!temporarily only considering other monsters and clients, don't see prisoners + if ( pSightEnt != this && + !FBitSet( pSightEnt->pev->spawnflags, SF_MONSTER_PRISONER ) && + pSightEnt->pev->health > 0 ) + { + // the looker will want to consider this entity + // don't check anything else about an entity that can't be seen, or an entity that you don't care about. + if ( IRelationship( pSightEnt ) != R_NO && FInViewCone( pSightEnt ) && !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && FVisible( pSightEnt ) ) + { + if ( pSightEnt->IsPlayer() ) + { + if ( pev->spawnflags & SF_MONSTER_WAIT_TILL_SEEN ) + { + CBaseMonster *pClient; + + pClient = pSightEnt->MyMonsterPointer(); + // don't link this client in the list if the monster is wait till seen and the player isn't facing the monster + if ( pSightEnt && !pClient->FInViewCone( this ) ) + { + // we're not in the player's view cone. + continue; + } + else + { + // player sees us, become normal now. + pev->spawnflags &= ~SF_MONSTER_WAIT_TILL_SEEN; + } + } + + // if we see a client, remember that (mostly for scripted AI) + iSighted |= bits_COND_SEE_CLIENT; + } + + pSightEnt->m_pLink = m_pLink; + m_pLink = pSightEnt; + + if ( pSightEnt == m_hEnemy ) + { + // we know this ent is visible, so if it also happens to be our enemy, store that now. + iSighted |= bits_COND_SEE_ENEMY; + } + + // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when + // we see monsters other than the Enemy. + switch ( IRelationship ( pSightEnt ) ) + { + case R_NM: + iSighted |= bits_COND_SEE_NEMESIS; + break; + case R_HT: + iSighted |= bits_COND_SEE_HATE; + break; + case R_DL: + iSighted |= bits_COND_SEE_DISLIKE; + break; + case R_FR: + iSighted |= bits_COND_SEE_FEAR; + break; + case R_AL: + break; + default: + ALERT ( at_aiconsole, "%s can't assess %s\n", STRING(pev->classname), STRING(pSightEnt->pev->classname ) ); + break; + } + } + } + } + } + + SetConditions( iSighted ); +} + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CBaseMonster :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER; +} + +//========================================================= +// PBestSound - returns a pointer to the sound the monster +// should react to. Right now responds only to nearest sound. +//========================================================= +CSound* CBaseMonster :: PBestSound ( void ) +{ + int iThisSound; + int iBestSound = -1; + float flBestDist = 8192;// so first nearby sound will become best so far. + float flDist; + CSound *pSound; + + iThisSound = m_iAudibleList; + + if ( iThisSound == SOUNDLIST_EMPTY ) + { + ALERT ( at_aiconsole, "ERROR! monster %s has no audible sounds!\n", STRING(pev->classname) ); +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; + } + + while ( iThisSound != SOUNDLIST_EMPTY ) + { + pSound = CSoundEnt::SoundPointerForIndex( iThisSound ); + + if ( pSound && pSound->FIsSound() ) + { + flDist = ( pSound->m_vecOrigin - EarPosition()).Length(); + + if ( flDist < flBestDist ) + { + iBestSound = iThisSound; + flBestDist = flDist; + } + } + + iThisSound = pSound->m_iNextAudible; + } + if ( iBestSound >= 0 ) + { + pSound = CSoundEnt::SoundPointerForIndex( iBestSound ); + return pSound; + } +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; +} + +//========================================================= +// PBestScent - returns a pointer to the scent the monster +// should react to. Right now responds only to nearest scent +//========================================================= +CSound* CBaseMonster :: PBestScent ( void ) +{ + int iThisScent; + int iBestScent = -1; + float flBestDist = 8192;// so first nearby smell will become best so far. + float flDist; + CSound *pSound; + + iThisScent = m_iAudibleList;// smells are in the sound list. + + if ( iThisScent == SOUNDLIST_EMPTY ) + { + ALERT ( at_aiconsole, "ERROR! PBestScent() has empty soundlist!\n" ); +#if _DEBUG + ALERT( at_error, "NULL Return from PBestSound\n" ); +#endif + return NULL; + } + + while ( iThisScent != SOUNDLIST_EMPTY ) + { + pSound = CSoundEnt::SoundPointerForIndex( iThisScent ); + + if ( pSound->FIsScent() ) + { + flDist = ( pSound->m_vecOrigin - pev->origin ).Length(); + + if ( flDist < flBestDist ) + { + iBestScent = iThisScent; + flBestDist = flDist; + } + } + + iThisScent = pSound->m_iNextAudible; + } + if ( iBestScent >= 0 ) + { + pSound = CSoundEnt::SoundPointerForIndex( iBestScent ); + + return pSound; + } +#if _DEBUG + ALERT( at_error, "NULL Return from PBestScent\n" ); +#endif + return NULL; +} + + + +//========================================================= +// Monster Think - calls out to core AI functions and handles this +// monster's specific animation events +//========================================================= +void CBaseMonster :: MonsterThink ( void ) +{ + pev->nextthink = gpGlobals->time + 0.1;// keep monster thinking. + + + RunAI(); + + float flInterval = StudioFrameAdvance( ); // animate +// start or end a fidget +// This needs a better home -- switching animations over time should be encapsulated on a per-activity basis +// perhaps MaintainActivity() or a ShiftAnimationOverTime() or something. + if ( m_MonsterState != MONSTERSTATE_SCRIPT && m_MonsterState != MONSTERSTATE_DEAD && m_Activity == ACT_IDLE && m_fSequenceFinished ) + { + int iSequence; + + if ( m_fSequenceLoops ) + { + // animation does loop, which means we're playing subtle idle. Might need to + // fidget. + iSequence = LookupActivity ( m_Activity ); + } + else + { + // animation that just ended doesn't loop! That means we just finished a fidget + // and should return to our heaviest weighted idle (the subtle one) + iSequence = LookupActivityHeaviest ( m_Activity ); + } + if ( iSequence != ACTIVITY_NOT_AVAILABLE ) + { + pev->sequence = iSequence; // Set to new anim (if it's there) + ResetSequenceInfo( ); + } + } + + DispatchAnimEvents( flInterval ); + + if ( !MovementIsComplete() ) + { + Move( flInterval ); + } +#if _DEBUG + else + { + if ( !TaskIsRunning() && !TaskIsComplete() ) + ALERT( at_error, "Schedule stalled!!\n" ); + } +#endif +} + +//========================================================= +// CBaseMonster - USE - will make a monster angry at whomever +// activated it. +//========================================================= +void CBaseMonster :: MonsterUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + m_IdealMonsterState = MONSTERSTATE_ALERT; +} + +//========================================================= +// Ignore conditions - before a set of conditions is allowed +// to interrupt a monster's schedule, this function removes +// conditions that we have flagged to interrupt the current +// schedule, but may not want to interrupt the schedule every +// time. (Pain, for instance) +//========================================================= +int CBaseMonster :: IgnoreConditions ( void ) +{ + int iIgnoreConditions = 0; + + if ( !FShouldEat() ) + { + // not hungry? Ignore food smell. + iIgnoreConditions |= bits_COND_SMELL_FOOD; + } + + if ( m_MonsterState == MONSTERSTATE_SCRIPT && m_pCine ) + iIgnoreConditions |= m_pCine->IgnoreConditions(); + + return iIgnoreConditions; +} + +//========================================================= +// RouteClear - zeroes out the monster's route array and goal +//========================================================= +void CBaseMonster :: RouteClear ( void ) +{ + RouteNew(); + m_movementGoal = MOVEGOAL_NONE; + m_movementActivity = ACT_IDLE; + Forget( bits_MEMORY_MOVE_FAILED ); +} + +//========================================================= +// Route New - clears out a route to be changed, but keeps +// goal intact. +//========================================================= +void CBaseMonster :: RouteNew ( void ) +{ + m_Route[ 0 ].iType = 0; + m_iRouteIndex = 0; +} + +//========================================================= +// FRouteClear - returns TRUE is the Route is cleared out +// ( invalid ) +//========================================================= +BOOL CBaseMonster :: FRouteClear ( void ) +{ + if ( m_Route[ m_iRouteIndex ].iType == 0 || m_movementGoal == MOVEGOAL_NONE ) + return TRUE; + + return FALSE; +} + +//========================================================= +// FRefreshRoute - after calculating a path to the monster's +// target, this function copies as many waypoints as possible +// from that path to the monster's Route array +//========================================================= +BOOL CBaseMonster :: FRefreshRoute ( void ) +{ + CBaseEntity *pPathCorner; + int i; + BOOL returnCode; + + RouteNew(); + + returnCode = FALSE; + + switch( m_movementGoal ) + { + case MOVEGOAL_PATHCORNER: + { + // monster is on a path_corner loop + pPathCorner = m_pGoalEnt; + i = 0; + + while ( pPathCorner && i < ROUTE_SIZE ) + { + m_Route[ i ].iType = bits_MF_TO_PATHCORNER; + m_Route[ i ].vecLocation = pPathCorner->pev->origin; + + pPathCorner = pPathCorner->GetNextTarget(); + + // Last path_corner in list? + if ( !pPathCorner ) + m_Route[i].iType |= bits_MF_IS_GOAL; + + i++; + } + } + returnCode = TRUE; + break; + + case MOVEGOAL_ENEMY: + returnCode = BuildRoute( m_vecEnemyLKP, bits_MF_TO_ENEMY, m_hEnemy ); + break; + + case MOVEGOAL_LOCATION: + returnCode = BuildRoute( m_vecMoveGoal, bits_MF_TO_LOCATION, NULL ); + break; + + case MOVEGOAL_TARGETENT: + if (m_hTargetEnt != NULL) + { + returnCode = BuildRoute( m_hTargetEnt->pev->origin, bits_MF_TO_TARGETENT, m_hTargetEnt ); + } + break; + + case MOVEGOAL_NODE: + returnCode = FGetNodeRoute( m_vecMoveGoal ); +// if ( returnCode ) +// RouteSimplify( NULL ); + break; + } + + return returnCode; +} + + +BOOL CBaseMonster::MoveToEnemy( Activity movementAct, float waitTime ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_ENEMY; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToLocation( Activity movementAct, float waitTime, const Vector &goal ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_LOCATION; + m_vecMoveGoal = goal; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToTarget( Activity movementAct, float waitTime ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_TARGETENT; + return FRefreshRoute(); +} + + +BOOL CBaseMonster::MoveToNode( Activity movementAct, float waitTime, const Vector &goal ) +{ + m_movementActivity = movementAct; + m_moveWaitTime = waitTime; + + m_movementGoal = MOVEGOAL_NODE; + m_vecMoveGoal = goal; + return FRefreshRoute(); +} + + +#ifdef _DEBUG +void DrawRoute( entvars_t *pev, WayPoint_t *m_Route, int m_iRouteIndex, int r, int g, int b ) +{ + int i; + + if ( m_Route[m_iRouteIndex].iType == 0 ) + { + ALERT( at_aiconsole, "Can't draw route!\n" ); + return; + } + +// UTIL_ParticleEffect ( m_Route[ m_iRouteIndex ].vecLocation, g_vecZero, 255, 25 ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.x ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.y ); + WRITE_COORD( m_Route[ m_iRouteIndex ].vecLocation.z ); + + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 16 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( r ); // r, g, b + WRITE_BYTE( g ); // r, g, b + WRITE_BYTE( b ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + for ( i = m_iRouteIndex ; i < ROUTE_SIZE - 1; i++ ) + { + if ( (m_Route[ i ].iType & bits_MF_IS_GOAL) || (m_Route[ i+1 ].iType == 0) ) + break; + + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( m_Route[ i ].vecLocation.x ); + WRITE_COORD( m_Route[ i ].vecLocation.y ); + WRITE_COORD( m_Route[ i ].vecLocation.z ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.x ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.y ); + WRITE_COORD( m_Route[ i + 1 ].vecLocation.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 1 ); // life + WRITE_BYTE( 8 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( r ); // r, g, b + WRITE_BYTE( g ); // r, g, b + WRITE_BYTE( b ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + +// UTIL_ParticleEffect ( m_Route[ i ].vecLocation, g_vecZero, 255, 25 ); + } +} +#endif + + +int ShouldSimplify( int routeType ) +{ + routeType &= ~bits_MF_IS_GOAL; + + if ( (routeType == bits_MF_TO_PATHCORNER) || (routeType & bits_MF_DONT_SIMPLIFY) ) + return FALSE; + return TRUE; +} + +//========================================================= +// RouteSimplify +// +// Attempts to make the route more direct by cutting out +// unnecessary nodes & cutting corners. +// +//========================================================= +void CBaseMonster :: RouteSimplify( CBaseEntity *pTargetEnt ) +{ + // BUGBUG: this doesn't work 100% yet + int i, count, outCount; + Vector vecStart; + WayPoint_t outRoute[ ROUTE_SIZE * 2 ]; // Any points except the ends can turn into 2 points in the simplified route + + count = 0; + + for ( i = m_iRouteIndex; i < ROUTE_SIZE; i++ ) + { + if ( !m_Route[i].iType ) + break; + else + count++; + if ( m_Route[i].iType & bits_MF_IS_GOAL ) + break; + } + // Can't simplify a direct route! + if ( count < 2 ) + { +// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 0, 255 ); + return; + } + + outCount = 0; + vecStart = pev->origin; + for ( i = 0; i < count-1; i++ ) + { + // Don't eliminate path_corners + if ( !ShouldSimplify( m_Route[m_iRouteIndex+i].iType ) ) + { + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + outCount++; + } + else if ( CheckLocalMove ( vecStart, m_Route[m_iRouteIndex+i+1].vecLocation, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + // Skip vert + continue; + } + else + { + Vector vecTest, vecSplit; + + // Halfway between this and next + vecTest = (m_Route[m_iRouteIndex+i+1].vecLocation + m_Route[m_iRouteIndex+i].vecLocation) * 0.5; + + // Halfway between this and previous + vecSplit = (m_Route[m_iRouteIndex+i].vecLocation + vecStart) * 0.5; + + int iType = (m_Route[m_iRouteIndex+i].iType | bits_MF_TO_DETOUR) & ~bits_MF_NOT_TO_MASK; + if ( CheckLocalMove ( vecStart, vecTest, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + outRoute[outCount].iType = iType; + outRoute[outCount].vecLocation = vecTest; + } + else if ( CheckLocalMove ( vecSplit, vecTest, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + outRoute[outCount].iType = iType; + outRoute[outCount].vecLocation = vecSplit; + outRoute[outCount+1].iType = iType; + outRoute[outCount+1].vecLocation = vecTest; + outCount++; // Adding an extra point + } + else + { + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + } + } + // Get last point + vecStart = outRoute[ outCount ].vecLocation; + outCount++; + } + ASSERT( i < count ); + outRoute[outCount] = m_Route[ m_iRouteIndex + i ]; + outCount++; + + // Terminate + outRoute[outCount].iType = 0; + ASSERT( outCount < (ROUTE_SIZE*2) ); + +// Copy the simplified route, disable for testing + m_iRouteIndex = 0; + for ( i = 0; i < ROUTE_SIZE && i < outCount; i++ ) + { + m_Route[i] = outRoute[i]; + } + + // Terminate route + if ( i < ROUTE_SIZE ) + m_Route[i].iType = 0; + +// Debug, test movement code +#if 0 +// if ( CVAR_GET_FLOAT( "simplify" ) != 0 ) + DrawRoute( pev, outRoute, 0, 255, 0, 0 ); +// else + DrawRoute( pev, m_Route, m_iRouteIndex, 0, 255, 0 ); +#endif +} + +//========================================================= +// FBecomeProne - tries to send a monster into PRONE state. +// right now only used when a barnacle snatches someone, so +// may have some special case stuff for that. +//========================================================= +BOOL CBaseMonster :: FBecomeProne ( void ) +{ + if ( FBitSet ( pev->flags, FL_ONGROUND ) ) + { + pev->flags -= FL_ONGROUND; + } + + m_IdealMonsterState = MONSTERSTATE_PRONE; + return TRUE; +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CBaseMonster :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist > 64 && flDist <= 784 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 +//========================================================= +BOOL CBaseMonster :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if ( flDist > 64 && flDist <= 512 && flDot >= 0.5 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CBaseMonster :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb) + if ( flDist <= 64 && flDot >= 0.7 && m_hEnemy != NULL && FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack2 +//========================================================= +BOOL CBaseMonster :: CheckMeleeAttack2 ( float flDot, float flDist ) +{ + if ( flDist <= 64 && flDot >= 0.7 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckAttacks - sets all of the bits for attacks that the +// monster is capable of carrying out on the passed entity. +//========================================================= +void CBaseMonster :: CheckAttacks ( CBaseEntity *pTarget, float flDist ) +{ + Vector2D vec2LOS; + float flDot; + + UTIL_MakeVectors ( pev->angles ); + + vec2LOS = ( pTarget->pev->origin - pev->origin ).Make2D(); + vec2LOS = vec2LOS.Normalize(); + + flDot = DotProduct (vec2LOS , gpGlobals->v_forward.Make2D() ); + + // we know the enemy is in front now. We'll find which attacks the monster is capable of by + // checking for corresponding Activities in the model file, then do the simple checks to validate + // those attack types. + + // Clear all attack conditions + ClearConditions( bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_RANGE_ATTACK2 | bits_COND_CAN_MELEE_ATTACK1 |bits_COND_CAN_MELEE_ATTACK2 ); + + if ( m_afCapability & bits_CAP_RANGE_ATTACK1 ) + { + if ( CheckRangeAttack1 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_RANGE_ATTACK1 ); + } + if ( m_afCapability & bits_CAP_RANGE_ATTACK2 ) + { + if ( CheckRangeAttack2 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_RANGE_ATTACK2 ); + } + if ( m_afCapability & bits_CAP_MELEE_ATTACK1 ) + { + if ( CheckMeleeAttack1 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_MELEE_ATTACK1 ); + } + if ( m_afCapability & bits_CAP_MELEE_ATTACK2 ) + { + if ( CheckMeleeAttack2 ( flDot, flDist ) ) + SetConditions( bits_COND_CAN_MELEE_ATTACK2 ); + } +} + +//========================================================= +// CanCheckAttacks - prequalifies a monster to do more fine +// checking of potential attacks. +//========================================================= +BOOL CBaseMonster :: FCanCheckAttacks ( void ) +{ + if ( HasConditions(bits_COND_SEE_ENEMY) && !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CheckEnemy - part of the Condition collection process, +// gets and stores data and conditions pertaining to a monster's +// enemy. Returns TRUE if Enemy LKP was updated. +//========================================================= +int CBaseMonster :: CheckEnemy ( CBaseEntity *pEnemy ) +{ + float flDistToEnemy; + int iUpdatedLKP;// set this to TRUE if you update the EnemyLKP in this function. + + iUpdatedLKP = FALSE; + ClearConditions ( bits_COND_ENEMY_FACING_ME ); + + if ( !FVisible( pEnemy ) ) + { + ASSERT(!HasConditions(bits_COND_SEE_ENEMY)); + SetConditions( bits_COND_ENEMY_OCCLUDED ); + } + else + ClearConditions( bits_COND_ENEMY_OCCLUDED ); + + if ( !pEnemy->IsAlive() ) + { + SetConditions ( bits_COND_ENEMY_DEAD ); + ClearConditions( bits_COND_SEE_ENEMY | bits_COND_ENEMY_OCCLUDED ); + return FALSE; + } + + Vector vecEnemyPos = pEnemy->pev->origin; + // distance to enemy's origin + flDistToEnemy = ( vecEnemyPos - pev->origin ).Length(); + vecEnemyPos.z += pEnemy->pev->size.z * 0.5; + // distance to enemy's head + float flDistToEnemy2 = (vecEnemyPos - pev->origin).Length(); + if (flDistToEnemy2 < flDistToEnemy) + flDistToEnemy = flDistToEnemy2; + else + { + // distance to enemy's feet + vecEnemyPos.z -= pEnemy->pev->size.z; + float flDistToEnemy2 = (vecEnemyPos - pev->origin).Length(); + if (flDistToEnemy2 < flDistToEnemy) + flDistToEnemy = flDistToEnemy2; + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + { + CBaseMonster *pEnemyMonster; + + iUpdatedLKP = TRUE; + m_vecEnemyLKP = pEnemy->pev->origin; + + pEnemyMonster = pEnemy->MyMonsterPointer(); + + if ( pEnemyMonster ) + { + if ( pEnemyMonster->FInViewCone ( this ) ) + { + SetConditions ( bits_COND_ENEMY_FACING_ME ); + } + else + ClearConditions( bits_COND_ENEMY_FACING_ME ); + } + + if (pEnemy->pev->velocity != Vector( 0, 0, 0)) + { + // trail the enemy a bit + m_vecEnemyLKP = m_vecEnemyLKP - pEnemy->pev->velocity * RANDOM_FLOAT( -0.05, 0 ); + } + else + { + // UNDONE: use pev->oldorigin? + } + } + else if ( !HasConditions(bits_COND_ENEMY_OCCLUDED|bits_COND_SEE_ENEMY) && ( flDistToEnemy <= 256 ) ) + { + // if the enemy is not occluded, and unseen, that means it is behind or beside the monster. + // if the enemy is near enough the monster, we go ahead and let the monster know where the + // enemy is. + iUpdatedLKP = TRUE; + m_vecEnemyLKP = pEnemy->pev->origin; + } + + if ( flDistToEnemy >= m_flDistTooFar ) + { + // enemy is very far away from monster + SetConditions( bits_COND_ENEMY_TOOFAR ); + } + else + ClearConditions( bits_COND_ENEMY_TOOFAR ); + + if ( FCanCheckAttacks() ) + { + CheckAttacks ( m_hEnemy, flDistToEnemy ); + } + + if ( m_movementGoal == MOVEGOAL_ENEMY ) + { + for ( int i = m_iRouteIndex; i < ROUTE_SIZE; i++ ) + { + if ( m_Route[ i ].iType == (bits_MF_IS_GOAL|bits_MF_TO_ENEMY) ) + { + // UNDONE: Should we allow monsters to override this distance (80?) + if ( (m_Route[ i ].vecLocation - m_vecEnemyLKP).Length() > 80 ) + { + // Refresh + FRefreshRoute(); + return iUpdatedLKP; + } + } + } + } + + return iUpdatedLKP; +} + +//========================================================= +// PushEnemy - remember the last few enemies, always remember the player +//========================================================= +void CBaseMonster :: PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ) +{ + int i; + + if (pEnemy == NULL) + return; + + // UNDONE: blah, this is bad, we should use a stack but I'm too lazy to code one. + for (i = 0; i < MAX_OLD_ENEMIES; i++) + { + if (m_hOldEnemy[i] == pEnemy) + return; + if (m_hOldEnemy[i] == NULL) // someone died, reuse their slot + break; + } + if (i >= MAX_OLD_ENEMIES) + return; + + m_hOldEnemy[i] = pEnemy; + m_vecOldEnemy[i] = vecLastKnownPos; +} + +//========================================================= +// PopEnemy - try remembering the last few enemies +//========================================================= +BOOL CBaseMonster :: PopEnemy( ) +{ + // UNDONE: blah, this is bad, we should use a stack but I'm too lazy to code one. + for (int i = MAX_OLD_ENEMIES - 1; i >= 0; i--) + { + if (m_hOldEnemy[i] != NULL) + { + if (m_hOldEnemy[i]->IsAlive( )) // cheat and know when they die + { + m_hEnemy = m_hOldEnemy[i]; + m_vecEnemyLKP = m_vecOldEnemy[i]; + // ALERT( at_console, "remembering\n"); + return TRUE; + } + else + { + m_hOldEnemy[i] = NULL; + } + } + } + return FALSE; +} + +//========================================================= +// SetActivity +//========================================================= +void CBaseMonster :: SetActivity ( Activity NewActivity ) +{ + int iSequence; + + iSequence = LookupActivity ( NewActivity ); + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + // don't reset frame between walk and run + if ( !(m_Activity == ACT_WALK || m_Activity == ACT_RUN) || !(NewActivity == ACT_WALK || NewActivity == ACT_RUN)) + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + SetYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_aiconsole, "%s has no sequence for act:%d\n", STRING(pev->classname), NewActivity ); + pev->sequence = 0; // Set to the reset anim (if it's there) + } + + m_Activity = NewActivity; // Go ahead and set this so it doesn't keep trying when the anim is not present + + // In case someone calls this with something other than the ideal activity + m_IdealActivity = m_Activity; + + +} + +//========================================================= +// SetSequenceByName +//========================================================= +void CBaseMonster :: SetSequenceByName ( char *szSequence ) +{ + int iSequence; + + iSequence = LookupSequence ( szSequence ); + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence > ACTIVITY_NOT_AVAILABLE ) + { + if ( pev->sequence != iSequence || !m_fSequenceLoops ) + { + pev->frame = 0; + } + + pev->sequence = iSequence; // Set to the reset anim (if it's there) + ResetSequenceInfo( ); + SetYawSpeed(); + } + else + { + // Not available try to get default anim + ALERT ( at_aiconsole, "%s has no sequence named:%f\n", STRING(pev->classname), szSequence ); + pev->sequence = 0; // Set to the reset anim (if it's there) + } +} + +//========================================================= +// CheckLocalMove - returns TRUE if the caller can walk a +// straight line from its current origin to the given +// location. If so, don't use the node graph! +// +// if a valid pointer to a int is passed, the function +// will fill that int with the distance that the check +// reached before hitting something. THIS ONLY HAPPENS +// IF THE LOCAL MOVE CHECK FAILS! +// +// !!!PERFORMANCE - should we try to load balance this? +// DON"T USE SETORIGIN! +//========================================================= +#define LOCAL_STEP_SIZE 16 +int CBaseMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + Vector vecStartPos;// record monster's position before trying the move + float flYaw; + float flDist; + float flStep, stepSize; + int iReturn; + + vecStartPos = pev->origin; + + + flYaw = UTIL_VecToYaw ( vecEnd - vecStart );// build a yaw that points to the goal. + flDist = ( vecEnd - vecStart ).Length2D();// get the distance. + iReturn = LOCALMOVE_VALID;// assume everything will be ok. + + // move the monster to the start of the local move that's to be checked. + UTIL_SetOrigin( pev, vecStart );// !!!BUGBUG - won't this fire triggers? - nope, SetOrigin doesn't fire + + if ( !(pev->flags & (FL_FLY|FL_SWIM)) ) + { + DROP_TO_FLOOR( ENT( pev ) );//make sure monster is on the floor! + } + + //pev->origin.z = vecStartPos.z;//!!!HACKHACK + +// pev->origin = vecStart; + +/* + if ( flDist > 1024 ) + { + // !!!PERFORMANCE - this operation may be too CPU intensive to try checks this large. + // We don't lose much here, because a distance this great is very likely + // to have something in the way. + + // since we've actually moved the monster during the check, undo the move. + pev->origin = vecStartPos; + return FALSE; + } +*/ + // this loop takes single steps to the goal. + for ( flStep = 0 ; flStep < flDist ; flStep += LOCAL_STEP_SIZE ) + { + stepSize = LOCAL_STEP_SIZE; + + if ( (flStep + LOCAL_STEP_SIZE) >= (flDist-1) ) + stepSize = (flDist - flStep) - 1; + +// UTIL_ParticleEffect ( pev->origin, g_vecZero, 255, 25 ); + + if ( !WALK_MOVE( ENT(pev), flYaw, stepSize, WALKMOVE_CHECKONLY ) ) + {// can't take the next step, fail! + + if ( pflDist != NULL ) + { + *pflDist = flStep; + } + if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) + { + // if this step hits target ent, the move is legal. + iReturn = LOCALMOVE_VALID; + break; + } + else + { + // If we're going toward an entity, and we're almost getting there, it's OK. +// if ( pTarget && fabs( flDist - iStep ) < LOCAL_STEP_SIZE ) +// fReturn = TRUE; +// else + iReturn = LOCALMOVE_INVALID; + break; + } + + } + } + + if ( iReturn == LOCALMOVE_VALID && !(pev->flags & (FL_FLY|FL_SWIM) ) && (!pTarget || (pTarget->pev->flags & FL_ONGROUND)) ) + { + // The monster can move to a spot UNDER the target, but not to it. Don't try to triangulate, go directly to the node graph. + // UNDONE: Magic # 64 -- this used to be pev->size.z but that won't work for small creatures like the headcrab + if ( fabs(vecEnd.z - pev->origin.z) > 64 ) + { + iReturn = LOCALMOVE_INVALID_DONT_TRIANGULATE; + } + } + /* + // uncommenting this block will draw a line representing the nearest legal move. + WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + WRITE_COORD(MSG_BROADCAST, pev->origin.x); + WRITE_COORD(MSG_BROADCAST, pev->origin.y); + WRITE_COORD(MSG_BROADCAST, pev->origin.z); + WRITE_COORD(MSG_BROADCAST, vecStart.x); + WRITE_COORD(MSG_BROADCAST, vecStart.y); + WRITE_COORD(MSG_BROADCAST, vecStart.z); + */ + + // since we've actually moved the monster during the check, undo the move. + UTIL_SetOrigin( pev, vecStartPos ); + + return iReturn; +} + + +float CBaseMonster :: OpenDoorAndWait( entvars_t *pevDoor ) +{ + float flTravelTime = 0; + + //ALERT(at_aiconsole, "A door. "); + CBaseEntity *pcbeDoor = CBaseEntity::Instance(pevDoor); + if (pcbeDoor && !pcbeDoor->IsLockedByMaster()) + { + //ALERT(at_aiconsole, "unlocked! "); + pcbeDoor->Use(this, this, USE_ON, 0.0); + //ALERT(at_aiconsole, "pevDoor->nextthink = %d ms\n", (int)(1000*pevDoor->nextthink)); + //ALERT(at_aiconsole, "pevDoor->ltime = %d ms\n", (int)(1000*pevDoor->ltime)); + //ALERT(at_aiconsole, "pev-> nextthink = %d ms\n", (int)(1000*pev->nextthink)); + //ALERT(at_aiconsole, "pev->ltime = %d ms\n", (int)(1000*pev->ltime)); + flTravelTime = pevDoor->nextthink - pevDoor->ltime; + //ALERT(at_aiconsole, "Waiting %d ms\n", (int)(1000*flTravelTime)); + if ( pcbeDoor->pev->targetname ) + { + edict_t *pentTarget = NULL; + for (;;) + { + pentTarget = FIND_ENTITY_BY_TARGETNAME( pentTarget, STRING(pcbeDoor->pev->targetname)); + + if ( VARS( pentTarget ) != pcbeDoor->pev ) + { + if (FNullEnt(pentTarget)) + break; + + if ( FClassnameIs ( pentTarget, STRING(pcbeDoor->pev->classname) ) ) + { + CBaseEntity *pDoor = Instance(pentTarget); + if ( pDoor ) + pDoor->Use(this, this, USE_ON, 0.0); + } + } + } + } + } + + return gpGlobals->time + flTravelTime; +} + + +//========================================================= +// AdvanceRoute - poorly named function that advances the +// m_iRouteIndex. If it goes beyond ROUTE_SIZE, the route +// is refreshed. +//========================================================= +void CBaseMonster :: AdvanceRoute ( float distance ) +{ + + if ( m_iRouteIndex == ROUTE_SIZE - 1 ) + { + // time to refresh the route. + if ( !FRefreshRoute() ) + { + ALERT ( at_aiconsole, "Can't Refresh Route!!\n" ); + } + } + else + { + if ( ! (m_Route[ m_iRouteIndex ].iType & bits_MF_IS_GOAL) ) + { + // If we've just passed a path_corner, advance m_pGoalEnt + if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_PATHCORNER ) + m_pGoalEnt = m_pGoalEnt->GetNextTarget(); + + // IF both waypoints are nodes, then check for a link for a door and operate it. + // + if ( (m_Route[m_iRouteIndex].iType & bits_MF_TO_NODE) == bits_MF_TO_NODE + && (m_Route[m_iRouteIndex+1].iType & bits_MF_TO_NODE) == bits_MF_TO_NODE) + { + //ALERT(at_aiconsole, "SVD: Two nodes. "); + + int iSrcNode = WorldGraph.FindNearestNode(m_Route[m_iRouteIndex].vecLocation, this ); + int iDestNode = WorldGraph.FindNearestNode(m_Route[m_iRouteIndex+1].vecLocation, this ); + + int iLink; + WorldGraph.HashSearch(iSrcNode, iDestNode, iLink); + + if ( iLink >= 0 && WorldGraph.m_pLinkPool[iLink].m_pLinkEnt != NULL ) + { + //ALERT(at_aiconsole, "A link. "); + if ( WorldGraph.HandleLinkEnt ( iSrcNode, WorldGraph.m_pLinkPool[iLink].m_pLinkEnt, m_afCapability, CGraph::NODEGRAPH_DYNAMIC ) ) + { + //ALERT(at_aiconsole, "usable."); + entvars_t *pevDoor = WorldGraph.m_pLinkPool[iLink].m_pLinkEnt; + if (pevDoor) + { + m_flMoveWaitFinished = OpenDoorAndWait( pevDoor ); +// ALERT( at_aiconsole, "Wating for door %.2f\n", m_flMoveWaitFinished-gpGlobals->time ); + } + } + } + //ALERT(at_aiconsole, "\n"); + } + m_iRouteIndex++; + } + else // At goal!!! + { + if ( distance < m_flGroundSpeed * 0.2 /* FIX */ ) + { + MovementComplete(); + } + } + } +} + + +int CBaseMonster :: RouteClassify( int iMoveFlag ) +{ + int movementGoal; + + movementGoal = MOVEGOAL_NONE; + + if ( iMoveFlag & bits_MF_TO_TARGETENT ) + movementGoal = MOVEGOAL_TARGETENT; + else if ( iMoveFlag & bits_MF_TO_ENEMY ) + movementGoal = MOVEGOAL_ENEMY; + else if ( iMoveFlag & bits_MF_TO_PATHCORNER ) + movementGoal = MOVEGOAL_PATHCORNER; + else if ( iMoveFlag & bits_MF_TO_NODE ) + movementGoal = MOVEGOAL_NODE; + else if ( iMoveFlag & bits_MF_TO_LOCATION ) + movementGoal = MOVEGOAL_LOCATION; + + return movementGoal; +} + +//========================================================= +// BuildRoute +//========================================================= +BOOL CBaseMonster :: BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ) +{ + float flDist; + Vector vecApex; + int iLocalMove; + + RouteNew(); + m_movementGoal = RouteClassify( iMoveFlag ); + +// so we don't end up with no moveflags + m_Route[ 0 ].vecLocation = vecGoal; + m_Route[ 0 ].iType = iMoveFlag | bits_MF_IS_GOAL; + +// check simple local move + iLocalMove = CheckLocalMove( pev->origin, vecGoal, pTarget, &flDist ); + + if ( iLocalMove == LOCALMOVE_VALID ) + { + // monster can walk straight there! + return TRUE; + } +// try to triangulate around any obstacles. + else if ( iLocalMove != LOCALMOVE_INVALID_DONT_TRIANGULATE && FTriangulate( pev->origin, vecGoal, flDist, pTarget, &vecApex ) ) + { + // there is a slightly more complicated path that allows the monster to reach vecGoal + m_Route[ 0 ].vecLocation = vecApex; + m_Route[ 0 ].iType = (iMoveFlag | bits_MF_TO_DETOUR); + + m_Route[ 1 ].vecLocation = vecGoal; + m_Route[ 1 ].iType = iMoveFlag | bits_MF_IS_GOAL; + + /* + WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + WRITE_COORD(MSG_BROADCAST, vecApex.x ); + WRITE_COORD(MSG_BROADCAST, vecApex.y ); + WRITE_COORD(MSG_BROADCAST, vecApex.z ); + WRITE_COORD(MSG_BROADCAST, vecApex.x ); + WRITE_COORD(MSG_BROADCAST, vecApex.y ); + WRITE_COORD(MSG_BROADCAST, vecApex.z + 128 ); + */ + + RouteSimplify( pTarget ); + return TRUE; + } + +// last ditch, try nodes + if ( FGetNodeRoute( vecGoal ) ) + { +// ALERT ( at_console, "Can get there on nodes\n" ); + m_vecMoveGoal = vecGoal; + RouteSimplify( pTarget ); + return TRUE; + } + + // b0rk + return FALSE; +} + + +//========================================================= +// InsertWaypoint - Rebuilds the existing route so that the +// supplied vector and moveflags are the first waypoint in +// the route, and fills the rest of the route with as much +// of the pre-existing route as possible +//========================================================= +void CBaseMonster :: InsertWaypoint ( Vector vecLocation, int afMoveFlags ) +{ + int i, type; + + + // we have to save some Index and Type information from the real + // path_corner or node waypoint that the monster was trying to reach. This makes sure that data necessary + // to refresh the original path exists even in the new waypoints that don't correspond directy to a path_corner + // or node. + type = afMoveFlags | (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK); + + for ( i = ROUTE_SIZE-1; i > 0; i-- ) + m_Route[i] = m_Route[i-1]; + + m_Route[ m_iRouteIndex ].vecLocation = vecLocation; + m_Route[ m_iRouteIndex ].iType = type; +} + +//========================================================= +// FTriangulate - tries to overcome local obstacles by +// triangulating a path around them. +// +// iApexDist is how far the obstruction that we are trying +// to triangulate around is from the monster. +//========================================================= +BOOL CBaseMonster :: FTriangulate ( const Vector &vecStart , const Vector &vecEnd, float flDist, CBaseEntity *pTargetEnt, Vector *pApex ) +{ + Vector vecDir; + Vector vecForward; + Vector vecLeft;// the spot we'll try to triangulate to on the left + Vector vecRight;// the spot we'll try to triangulate to on the right + Vector vecTop;// the spot we'll try to triangulate to on the top + Vector vecBottom;// the spot we'll try to triangulate to on the bottom + Vector vecFarSide;// the spot that we'll move to after hitting the triangulated point, before moving on to our normal goal. + int i; + float sizeX, sizeZ; + + // If the hull width is less than 24, use 24 because CheckLocalMove uses a min of + // 24. + sizeX = pev->size.x; + if (sizeX < 24.0) + sizeX = 24.0; + else if (sizeX > 48.0) + sizeX = 48.0; + sizeZ = pev->size.z; + //if (sizeZ < 24.0) + // sizeZ = 24.0; + + vecForward = ( vecEnd - vecStart ).Normalize(); + + Vector vecDirUp(0,0,1); + vecDir = CrossProduct ( vecForward, vecDirUp); + + // start checking right about where the object is, picking two equidistant starting points, one on + // the left, one on the right. As we progress through the loop, we'll push these away from the obstacle, + // hoping to find a way around on either side. pev->size.x is added to the ApexDist in order to help select + // an apex point that insures that the monster is sufficiently past the obstacle before trying to turn back + // onto its original course. + + vecLeft = pev->origin + ( vecForward * ( flDist + sizeX ) ) - vecDir * ( sizeX * 3 ); + vecRight = pev->origin + ( vecForward * ( flDist + sizeX ) ) + vecDir * ( sizeX * 3 ); + if (pev->movetype == MOVETYPE_FLY) + { + vecTop = pev->origin + (vecForward * flDist) + (vecDirUp * sizeZ * 3); + vecBottom = pev->origin + (vecForward * flDist) - (vecDirUp * sizeZ * 3); + } + + vecFarSide = m_Route[ m_iRouteIndex ].vecLocation; + + vecDir = vecDir * sizeX * 2; + if (pev->movetype == MOVETYPE_FLY) + vecDirUp = vecDirUp * sizeZ * 2; + + for ( i = 0 ; i < 8; i++ ) + { +// Debug, Draw the triangulation +#if 0 + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecRight.x ); + WRITE_COORD( vecRight.y ); + WRITE_COORD( vecRight.z ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecLeft.x ); + WRITE_COORD( vecLeft.y ); + WRITE_COORD( vecLeft.z ); + MESSAGE_END(); +#endif + +#if 0 + if (pev->movetype == MOVETYPE_FLY) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecTop.x ); + WRITE_COORD( vecTop.y ); + WRITE_COORD( vecTop.z ); + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( vecBottom.x ); + WRITE_COORD( vecBottom.y ); + WRITE_COORD( vecBottom.z ); + MESSAGE_END(); + } +#endif + + if ( CheckLocalMove( pev->origin, vecRight, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecRight, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecRight; + } + + return TRUE; + } + } + if ( CheckLocalMove( pev->origin, vecLeft, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecLeft, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecLeft; + } + + return TRUE; + } + } + + if (pev->movetype == MOVETYPE_FLY) + { + if ( CheckLocalMove( pev->origin, vecTop, pTargetEnt, NULL ) == LOCALMOVE_VALID) + { + if ( CheckLocalMove ( vecTop, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecTop; + //ALERT(at_aiconsole, "triangulate over\n"); + } + + return TRUE; + } + } +#if 1 + if ( CheckLocalMove( pev->origin, vecBottom, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( CheckLocalMove ( vecBottom, vecFarSide, pTargetEnt, NULL ) == LOCALMOVE_VALID ) + { + if ( pApex ) + { + *pApex = vecBottom; + //ALERT(at_aiconsole, "triangulate under\n"); + } + + return TRUE; + } + } +#endif + } + + vecRight = vecRight + vecDir; + vecLeft = vecLeft - vecDir; + if (pev->movetype == MOVETYPE_FLY) + { + vecTop = vecTop + vecDirUp; + vecBottom = vecBottom - vecDirUp; + } + } + + return FALSE; +} + +//========================================================= +// Move - take a single step towards the next ROUTE location +//========================================================= +#define DIST_TO_CHECK 200 + +void CBaseMonster :: Move ( float flInterval ) +{ + float flWaypointDist; + float flCheckDist; + float flDist;// how far the lookahead check got before hitting an object. + Vector vecDir; + Vector vecApex; + CBaseEntity *pTargetEnt; + + // Don't move if no valid route + if ( FRouteClear() ) + { + // If we still have a movement goal, then this is probably a route truncated by SimplifyRoute() + // so refresh it. + if ( m_movementGoal == MOVEGOAL_NONE || !FRefreshRoute() ) + { + ALERT( at_aiconsole, "Tried to move with no route!\n" ); + TaskFail(); + return; + } + } + + if ( m_flMoveWaitFinished > gpGlobals->time ) + return; + +// Debug, test movement code +#if 0 +// if ( CVAR_GET_FLOAT("stopmove" ) != 0 ) + { + if ( m_movementGoal == MOVEGOAL_ENEMY ) + RouteSimplify( m_hEnemy ); + else + RouteSimplify( m_hTargetEnt ); + FRefreshRoute(); + return; + } +#else +// Debug, draw the route +// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 200, 0 ); +#endif + + // if the monster is moving directly towards an entity (enemy for instance), we'll set this pointer + // to that entity for the CheckLocalMove and Triangulate functions. + pTargetEnt = NULL; + + // local move to waypoint. + vecDir = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Normalize(); + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length2D(); + + MakeIdealYaw ( m_Route[ m_iRouteIndex ].vecLocation ); + ChangeYaw ( pev->yaw_speed ); + + // if the waypoint is closer than CheckDist, CheckDist is the dist to waypoint + if ( flWaypointDist < DIST_TO_CHECK ) + { + flCheckDist = flWaypointDist; + } + else + { + flCheckDist = DIST_TO_CHECK; + } + + if ( (m_Route[ m_iRouteIndex ].iType & (~bits_MF_NOT_TO_MASK)) == bits_MF_TO_ENEMY ) + { + // only on a PURE move to enemy ( i.e., ONLY MF_TO_ENEMY set, not MF_TO_ENEMY and DETOUR ) + pTargetEnt = m_hEnemy; + } + else if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_TARGETENT ) + { + pTargetEnt = m_hTargetEnt; + } + + // !!!BUGBUG - CheckDist should be derived from ground speed. + // If this fails, it should be because of some dynamic entity blocking this guy. + // We've already checked this path, so we should wait and time out if the entity doesn't move + flDist = 0; + if ( CheckLocalMove ( pev->origin, pev->origin + vecDir * flCheckDist, pTargetEnt, &flDist ) != LOCALMOVE_VALID ) + { + CBaseEntity *pBlocker; + + // Can't move, stop + Stop(); + // Blocking entity is in global trace_ent + pBlocker = CBaseEntity::Instance( gpGlobals->trace_ent ); + if (pBlocker) + { + DispatchBlocked( edict(), pBlocker->edict() ); + } + + if ( pBlocker && m_moveWaitTime > 0 && pBlocker->IsMoving() && !pBlocker->IsPlayer() && (gpGlobals->time-m_flMoveWaitFinished) > 3.0 ) + { + // Can we still move toward our target? + if ( flDist < m_flGroundSpeed ) + { + // No, Wait for a second + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime; + return; + } + // Ok, still enough room to take a step + } + else + { + // try to triangulate around whatever is in the way. + if ( FTriangulate( pev->origin, m_Route[ m_iRouteIndex ].vecLocation, flDist, pTargetEnt, &vecApex ) ) + { + InsertWaypoint( vecApex, bits_MF_TO_DETOUR ); + RouteSimplify( pTargetEnt ); + } + else + { +// ALERT ( at_aiconsole, "Couldn't Triangulate\n" ); + Stop(); + // Only do this once until your route is cleared + if ( m_moveWaitTime > 0 && !(m_afMemory & bits_MEMORY_MOVE_FAILED) ) + { + FRefreshRoute(); + if ( FRouteClear() ) + { + TaskFail(); + } + else + { + // Don't get stuck + if ( (gpGlobals->time - m_flMoveWaitFinished) < 0.2 ) + Remember( bits_MEMORY_MOVE_FAILED ); + + m_flMoveWaitFinished = gpGlobals->time + 0.1; + } + } + else + { + TaskFail(); + ALERT( at_aiconsole, "%s Failed to move (%d)!\n", STRING(pev->classname), HasMemory( bits_MEMORY_MOVE_FAILED ) ); + //ALERT( at_aiconsole, "%f, %f, %f\n", pev->origin.z, (pev->origin + (vecDir * flCheckDist)).z, m_Route[m_iRouteIndex].vecLocation.z ); + } + return; + } + } + } + + // close enough to the target, now advance to the next target. This is done before actually reaching + // the target so that we get a nice natural turn while moving. + if ( ShouldAdvanceRoute( flWaypointDist ) )///!!!BUGBUG- magic number + { + AdvanceRoute( flWaypointDist ); + } + + // Might be waiting for a door + if ( m_flMoveWaitFinished > gpGlobals->time ) + { + Stop(); + return; + } + + // UNDONE: this is a hack to quit moving farther than it has looked ahead. + if (flCheckDist < m_flGroundSpeed * flInterval) + { + flInterval = flCheckDist / m_flGroundSpeed; + // ALERT( at_console, "%.02f\n", flInterval ); + } + MoveExecute( pTargetEnt, vecDir, flInterval ); + + if ( MovementIsComplete() ) + { + Stop(); + RouteClear(); + } +} + + +BOOL CBaseMonster:: ShouldAdvanceRoute( float flWaypointDist ) +{ + if ( flWaypointDist <= MONSTER_CUT_CORNER_DIST ) + { + // ALERT( at_console, "cut %f\n", flWaypointDist ); + return TRUE; + } + + return FALSE; +} + + +void CBaseMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ +// float flYaw = UTIL_VecToYaw ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin );// build a yaw that points to the goal. +// WALK_MOVE( ENT(pev), flYaw, m_flGroundSpeed * flInterval, WALKMOVE_NORMAL ); + if ( m_IdealActivity != m_movementActivity ) + m_IdealActivity = m_movementActivity; + + float flTotal = m_flGroundSpeed * pev->framerate * flInterval; + float flStep; + while (flTotal > 0.001) + { + // don't walk more than 16 units or stairs stop working + flStep = min( 16.0, flTotal ); + UTIL_MoveToOrigin ( ENT(pev), m_Route[ m_iRouteIndex ].vecLocation, flStep, MOVE_NORMAL ); + flTotal -= flStep; + } + // ALERT( at_console, "dist %f\n", m_flGroundSpeed * pev->framerate * flInterval ); +} + + +//========================================================= +// MonsterInit - after a monster is spawned, it needs to +// be dropped into the world, checked for mobility problems, +// and put on the proper path, if any. This function does +// all of those things after the monster spawns. Any +// initialization that should take place for all monsters +// goes here. +//========================================================= +void CBaseMonster :: MonsterInit ( void ) +{ + if (!g_pGameRules->FAllowMonsters()) + { + pev->flags |= FL_KILLME; // Post this because some monster code modifies class data after calling this function +// REMOVE_ENTITY(ENT(pev)); + return; + } + + // Set fields common to all monsters + pev->effects = 0; + pev->takedamage = DAMAGE_AIM; + pev->ideal_yaw = pev->angles.y; + pev->max_health = pev->health; + pev->deadflag = DEAD_NO; + m_IdealMonsterState = MONSTERSTATE_IDLE;// Assume monster will be idle, until proven otherwise + + m_IdealActivity = ACT_IDLE; + + SetBits (pev->flags, FL_MONSTER); + if ( pev->spawnflags & SF_MONSTER_HITMONSTERCLIP ) + pev->flags |= FL_MONSTERCLIP; + + ClearSchedule(); + RouteClear(); + InitBoneControllers( ); // FIX: should be done in Spawn + + m_iHintNode = NO_NODE; + + m_afMemory = MEMORY_CLEAR; + + m_hEnemy = NULL; + + m_flDistTooFar = 1024.0; + m_flDistLook = 2048.0; + + // set eye position + SetEyePosition(); + + SetThink( MonsterInitThink ); + pev->nextthink = gpGlobals->time + 0.1; + SetUse ( MonsterUse ); +} + +//========================================================= +// MonsterInitThink - Calls StartMonster. Startmonster is +// virtual, but this function cannot be +//========================================================= +void CBaseMonster :: MonsterInitThink ( void ) +{ + StartMonster(); +} + +//========================================================= +// StartMonster - final bit of initization before a monster +// is turned over to the AI. +//========================================================= +void CBaseMonster :: StartMonster ( void ) +{ + // update capabilities + if ( LookupActivity ( ACT_RANGE_ATTACK1 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_RANGE_ATTACK1; + } + if ( LookupActivity ( ACT_RANGE_ATTACK2 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_RANGE_ATTACK2; + } + if ( LookupActivity ( ACT_MELEE_ATTACK1 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_MELEE_ATTACK1; + } + if ( LookupActivity ( ACT_MELEE_ATTACK2 ) != ACTIVITY_NOT_AVAILABLE ) + { + m_afCapability |= bits_CAP_MELEE_ATTACK2; + } + + // Raise monster off the floor one unit, then drop to floor + if ( pev->movetype != MOVETYPE_FLY && !FBitSet( pev->spawnflags, SF_MONSTER_FALL_TO_GROUND ) ) + { + pev->origin.z += 1; + DROP_TO_FLOOR ( ENT(pev) ); + // Try to move the monster to make sure it's not stuck in a brush. + if (!WALK_MOVE ( ENT(pev), 0, 0, WALKMOVE_NORMAL ) ) + { + ALERT(at_error, "Monster %s stuck in wall--level design error", STRING(pev->classname)); + pev->effects = EF_BRIGHTFIELD; + } + } + else + { + pev->flags &= ~FL_ONGROUND; + } + + if ( !FStringNull(pev->target) )// this monster has a target + { + // Find the monster's initial target entity, stash it + m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING( pev->target ) ) ); + + if ( !m_pGoalEnt ) + { + ALERT(at_error, "ReadyMonster()--%s couldn't find target %s", STRING(pev->classname), STRING(pev->target)); + } + else + { + // Monster will start turning towards his destination + MakeIdealYaw ( m_pGoalEnt->pev->origin ); + + // JAY: How important is this error message? Big Momma doesn't obey this rule, so I took it out. +#if 0 + // At this point, we expect only a path_corner as initial goal + if (!FClassnameIs( m_pGoalEnt->pev, "path_corner")) + { + ALERT(at_warning, "ReadyMonster--monster's initial goal '%s' is not a path_corner", STRING(pev->target)); + } +#endif + + // set the monster up to walk a path corner path. + // !!!BUGBUG - this is a minor bit of a hack. + // JAYJAY + m_movementGoal = MOVEGOAL_PATHCORNER; + + if ( pev->movetype == MOVETYPE_FLY ) + m_movementActivity = ACT_FLY; + else + m_movementActivity = ACT_WALK; + + if ( !FRefreshRoute() ) + { + ALERT ( at_aiconsole, "Can't Create Route!\n" ); + } + SetState( MONSTERSTATE_IDLE ); + ChangeSchedule( GetScheduleOfType( SCHED_IDLE_WALK ) ); + } + } + + //SetState ( m_IdealMonsterState ); + //SetActivity ( m_IdealActivity ); + + // Delay drop to floor to make sure each door in the level has had its chance to spawn + // Spread think times so that they don't all happen at the same time (Carmack) + SetThink ( CallMonsterThink ); + pev->nextthink += RANDOM_FLOAT(0.1, 0.4); // spread think times. + + if ( !FStringNull(pev->targetname) )// wait until triggered + { + SetState( MONSTERSTATE_IDLE ); + // UNDONE: Some scripted sequence monsters don't have an idle? + SetActivity( ACT_IDLE ); + ChangeSchedule( GetScheduleOfType( SCHED_WAIT_TRIGGER ) ); + } +} + + +void CBaseMonster :: MovementComplete( void ) +{ + switch( m_iTaskStatus ) + { + case TASKSTATUS_NEW: + case TASKSTATUS_RUNNING: + m_iTaskStatus = TASKSTATUS_RUNNING_TASK; + break; + + case TASKSTATUS_RUNNING_MOVEMENT: + TaskComplete(); + break; + + case TASKSTATUS_RUNNING_TASK: + ALERT( at_error, "Movement completed twice!\n" ); + break; + + case TASKSTATUS_COMPLETE: + break; + } + m_movementGoal = MOVEGOAL_NONE; +} + + +int CBaseMonster::TaskIsRunning( void ) +{ + if ( m_iTaskStatus != TASKSTATUS_COMPLETE && + m_iTaskStatus != TASKSTATUS_RUNNING_MOVEMENT ) + return 1; + + return 0; +} + +//========================================================= +// IRelationship - returns an integer that describes the +// relationship between two types of monster. +//========================================================= +int CBaseMonster::IRelationship ( CBaseEntity *pTarget ) +{ + static int iEnemy[14][14] = + { // NONE MACH PLYR HPASS HMIL AMIL APASS AMONST APREY APRED INSECT PLRALY PBWPN ABWPN + /*NONE*/ { R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO, R_NO, R_NO }, + /*MACHINE*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_DL, R_DL, R_DL }, + /*PLAYER*/ { R_NO ,R_DL ,R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO, R_DL, R_DL }, + /*HUMANPASSIVE*/{ R_NO ,R_NO ,R_AL ,R_AL ,R_HT ,R_FR ,R_NO ,R_HT ,R_DL ,R_FR ,R_NO ,R_AL, R_NO, R_NO }, + /*HUMANMILITAR*/{ R_NO ,R_NO ,R_HT ,R_DL ,R_NO ,R_HT ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_HT, R_NO, R_NO }, + /*ALIENMILITAR*/{ R_NO ,R_DL ,R_HT ,R_DL ,R_HT ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPASSIVE*/{ R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO, R_NO, R_NO }, + /*ALIENMONSTER*/{ R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPREY */{ R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_NO ,R_FR ,R_NO ,R_DL, R_NO, R_NO }, + /*ALIENPREDATO*/{ R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO ,R_NO ,R_HT ,R_DL ,R_NO ,R_DL, R_NO, R_NO }, + /*INSECT*/ { R_FR ,R_FR ,R_FR ,R_FR ,R_FR ,R_NO ,R_FR ,R_FR ,R_FR ,R_FR ,R_NO ,R_FR, R_NO, R_NO }, + /*PLAYERALLY*/ { R_NO ,R_DL ,R_AL ,R_AL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_NO, R_NO, R_NO }, + /*PBIOWEAPON*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_DL ,R_NO ,R_DL, R_NO, R_DL }, + /*ABIOWEAPON*/ { R_NO ,R_NO ,R_DL ,R_DL ,R_DL ,R_AL ,R_NO ,R_DL ,R_DL ,R_NO ,R_NO ,R_DL, R_DL, R_NO } + }; + + return iEnemy[ Classify() ][ pTarget->Classify() ]; +} + +//========================================================= +// FindCover - tries to find a nearby node that will hide +// the caller from its enemy. +// +// If supplied, search will return a node at least as far +// away as MinDist, but no farther than MaxDist. +// if MaxDist isn't supplied, it defaults to a reasonable +// value +//========================================================= +// UNDONE: Should this find the nearest node? + +//float CGraph::PathLength( int iStart, int iDest, int iHull, int afCapMask ) + +BOOL CBaseMonster :: FindCover ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) +{ + int i; + int iMyHullIndex; + int iMyNode; + int iThreatNode; + float flDist; + Vector vecLookersOffset; + TraceResult tr; + + if ( !flMaxDist ) + { + // user didn't supply a MaxDist, so work up a crazy one. + flMaxDist = 784; + } + + if ( flMinDist > 0.5 * flMaxDist) + { +#if _DEBUG + ALERT ( at_console, "FindCover MinDist (%.0f) too close to MaxDist (%.0f)\n", flMinDist, flMaxDist ); +#endif + flMinDist = 0.5 * flMaxDist; + } + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + { + ALERT ( at_aiconsole, "Graph not ready for findcover!\n" ); + return FALSE; + } + + iMyNode = WorldGraph.FindNearestNode( pev->origin, this ); + iThreatNode = WorldGraph.FindNearestNode ( vecThreat, this ); + iMyHullIndex = WorldGraph.HullIndex( this ); + + if ( iMyNode == NO_NODE ) + { + ALERT ( at_aiconsole, "FindCover() - %s has no nearest node!\n", STRING(pev->classname)); + return FALSE; + } + if ( iThreatNode == NO_NODE ) + { + // ALERT ( at_aiconsole, "FindCover() - Threat has no nearest node!\n" ); + iThreatNode = iMyNode; + // return FALSE; + } + + vecLookersOffset = vecThreat + vecViewOffset;// calculate location of enemy's eyes + + // we'll do a rough sample to find nodes that are relatively nearby + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastCoverSearch) % WorldGraph.m_cNodes; + + CNode &node = WorldGraph.Node( nodeNumber ); + WorldGraph.m_iLastCoverSearch = nodeNumber + 1; // next monster that searches for cover node will start where we left off here. + + // could use an optimization here!! + flDist = ( pev->origin - node.m_vecOrigin ).Length(); + + // DON'T do the trace check on a node that is farther away than a node that we've already found to + // provide cover! Also make sure the node is within the mins/maxs of the search. + if ( flDist >= flMinDist && flDist < flMaxDist ) + { + UTIL_TraceLine ( node.m_vecOrigin + vecViewOffset, vecLookersOffset, ignore_monsters, ignore_glass, ENT(pev), &tr ); + + // if this node will block the threat's line of sight to me... + if ( tr.flFraction != 1.0 ) + { + // ..and is also closer to me than the threat, or the same distance from myself and the threat the node is good. + if ( ( iMyNode == iThreatNode ) || WorldGraph.PathLength( iMyNode, nodeNumber, iMyHullIndex, m_afCapability ) <= WorldGraph.PathLength( iThreatNode, nodeNumber, iMyHullIndex, m_afCapability ) ) + { + if ( FValidateCover ( node.m_vecOrigin ) && MoveToLocation( ACT_RUN, 0, node.m_vecOrigin ) ) + { + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( node.m_vecOrigin.x ); + WRITE_COORD( node.m_vecOrigin.y ); + WRITE_COORD( node.m_vecOrigin.z ); + + WRITE_COORD( vecLookersOffset.x ); + WRITE_COORD( vecLookersOffset.y ); + WRITE_COORD( vecLookersOffset.z ); + MESSAGE_END(); + */ + + return TRUE; + } + } + } + } + } + return FALSE; +} + + +//========================================================= +// BuildNearestRoute - tries to build a route as close to the target +// as possible, even if there isn't a path to the final point. +// +// If supplied, search will return a node at least as far +// away as MinDist from vecThreat, but no farther than MaxDist. +// if MaxDist isn't supplied, it defaults to a reasonable +// value +//========================================================= +BOOL CBaseMonster :: BuildNearestRoute ( Vector vecThreat, Vector vecViewOffset, float flMinDist, float flMaxDist ) +{ + int i; + int iMyHullIndex; + int iMyNode; + float flDist; + Vector vecLookersOffset; + TraceResult tr; + + if ( !flMaxDist ) + { + // user didn't supply a MaxDist, so work up a crazy one. + flMaxDist = 784; + } + + if ( flMinDist > 0.5 * flMaxDist) + { +#if _DEBUG + ALERT ( at_console, "FindCover MinDist (%.0f) too close to MaxDist (%.0f)\n", flMinDist, flMaxDist ); +#endif + flMinDist = 0.5 * flMaxDist; + } + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + { + ALERT ( at_aiconsole, "Graph not ready for BuildNearestRoute!\n" ); + return FALSE; + } + + iMyNode = WorldGraph.FindNearestNode( pev->origin, this ); + iMyHullIndex = WorldGraph.HullIndex( this ); + + if ( iMyNode == NO_NODE ) + { + ALERT ( at_aiconsole, "BuildNearestRoute() - %s has no nearest node!\n", STRING(pev->classname)); + return FALSE; + } + + vecLookersOffset = vecThreat + vecViewOffset;// calculate location of enemy's eyes + + // we'll do a rough sample to find nodes that are relatively nearby + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastCoverSearch) % WorldGraph.m_cNodes; + + CNode &node = WorldGraph.Node( nodeNumber ); + WorldGraph.m_iLastCoverSearch = nodeNumber + 1; // next monster that searches for cover node will start where we left off here. + + // can I get there? + if (WorldGraph.NextNodeInRoute( iMyNode, nodeNumber, iMyHullIndex, 0 ) != iMyNode) + { + flDist = ( vecThreat - node.m_vecOrigin ).Length(); + + // is it close? + if ( flDist > flMinDist && flDist < flMaxDist) + { + // can I see where I want to be from there? + UTIL_TraceLine( node.m_vecOrigin + pev->view_ofs, vecLookersOffset, ignore_monsters, edict(), &tr ); + + if (tr.flFraction == 1.0) + { + // try to actually get there + if ( BuildRoute ( node.m_vecOrigin, bits_MF_TO_LOCATION, NULL ) ) + { + flMaxDist = flDist; + m_vecMoveGoal = node.m_vecOrigin; + return TRUE; // UNDONE: keep looking for something closer! + } + } + } + } + } + + return FALSE; +} + + + +//========================================================= +// BestVisibleEnemy - this functions searches the link +// list whose head is the caller's m_pLink field, and returns +// a pointer to the enemy entity in that list that is nearest the +// caller. +// +// !!!UNDONE - currently, this only returns the closest enemy. +// we'll want to consider distance, relationship, attack types, back turned, etc. +//========================================================= +CBaseEntity *CBaseMonster :: BestVisibleEnemy ( void ) +{ + CBaseEntity *pReturn; + CBaseEntity *pNextEnt; + int iNearest; + int iDist; + int iBestRelationship; + + iNearest = 8192;// so first visible entity will become the closest. + pNextEnt = m_pLink; + pReturn = NULL; + iBestRelationship = R_NO; + + while ( pNextEnt != NULL ) + { + if ( pNextEnt->IsAlive() ) + { + if ( IRelationship( pNextEnt) > iBestRelationship ) + { + // this entity is disliked MORE than the entity that we + // currently think is the best visible enemy. No need to do + // a distance check, just get mad at this one for now. + iBestRelationship = IRelationship ( pNextEnt ); + iNearest = ( pNextEnt->pev->origin - pev->origin ).Length(); + pReturn = pNextEnt; + } + else if ( IRelationship( pNextEnt) == iBestRelationship ) + { + // this entity is disliked just as much as the entity that + // we currently think is the best visible enemy, so we only + // get mad at it if it is closer. + iDist = ( pNextEnt->pev->origin - pev->origin ).Length(); + + if ( iDist <= iNearest ) + { + iNearest = iDist; + iBestRelationship = IRelationship ( pNextEnt ); + pReturn = pNextEnt; + } + } + } + + pNextEnt = pNextEnt->m_pLink; + } + + return pReturn; +} + + +//========================================================= +// MakeIdealYaw - gets a yaw value for the caller that would +// face the supplied vector. Value is stuffed into the monster's +// ideal_yaw +//========================================================= +void CBaseMonster :: MakeIdealYaw( Vector vecTarget ) +{ + Vector vecProjection; + + // strafing monster needs to face 90 degrees away from its goal + if ( m_movementActivity == ACT_STRAFE_LEFT ) + { + vecProjection.x = -vecTarget.y; + vecProjection.y = vecTarget.x; + + pev->ideal_yaw = UTIL_VecToYaw( vecProjection - pev->origin ); + } + else if ( m_movementActivity == ACT_STRAFE_RIGHT ) + { + vecProjection.x = vecTarget.y; + vecProjection.y = vecTarget.x; + + pev->ideal_yaw = UTIL_VecToYaw( vecProjection - pev->origin ); + } + else + { + pev->ideal_yaw = UTIL_VecToYaw ( vecTarget - pev->origin ); + } +} + +//========================================================= +// FlYawDiff - returns the difference ( in degrees ) between +// monster's current yaw and ideal_yaw +// +// Positive result is left turn, negative is right turn +//========================================================= +float CBaseMonster::FlYawDiff ( void ) +{ + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + + if ( flCurrentYaw == pev->ideal_yaw ) + { + return 0; + } + + + return UTIL_AngleDiff( pev->ideal_yaw, flCurrentYaw ); +} + + +//========================================================= +// Changeyaw - turns a monster towards its ideal_yaw +//========================================================= +float CBaseMonster::ChangeYaw ( int yawSpeed ) +{ + float ideal, current, move, speed; + + current = UTIL_AngleMod( pev->angles.y ); + ideal = pev->ideal_yaw; + if (current != ideal) + { + speed = (float)yawSpeed * gpGlobals->frametime * 10; + move = ideal - current; + + if (ideal > current) + { + if (move >= 180) + move = move - 360; + } + else + { + if (move <= -180) + move = move + 360; + } + + if (move > 0) + {// turning to the monster's left + if (move > speed) + move = speed; + } + else + {// turning to the monster's right + if (move < -speed) + move = -speed; + } + + pev->angles.y = UTIL_AngleMod (current + move); + + // turn head in desired direction only if they have a turnable head + if (m_afCapability & bits_CAP_TURN_HEAD) + { + float yaw = pev->ideal_yaw - pev->angles.y; + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + // yaw *= 0.8; + SetBoneController( 0, yaw ); + } + } + else + move = 0; + + return move; +} + +//========================================================= +// VecToYaw - turns a directional vector into a yaw value +// that points down that vector. +//========================================================= +float CBaseMonster::VecToYaw ( Vector vecDir ) +{ + if (vecDir.x == 0 && vecDir.y == 0 && vecDir.z == 0) + return pev->angles.y; + + return UTIL_VecToYaw( vecDir ); +} + + +//========================================================= +// SetEyePosition +// +// queries the monster's model for $eyeposition and copies +// that vector to the monster's view_ofs +// +//========================================================= +void CBaseMonster :: SetEyePosition ( void ) +{ + Vector vecEyePosition; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + GetEyePosition( pmodel, vecEyePosition ); + + pev->view_ofs = vecEyePosition; + + if ( pev->view_ofs == g_vecZero ) + { + ALERT ( at_aiconsole, "%s has no view_ofs!\n", STRING ( pev->classname ) ); + } +} + +void CBaseMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SCRIPT_EVENT_DEAD: + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + pev->deadflag = DEAD_DYING; + // Kill me now! (and fade out when CineCleanup() is called) +#if _DEBUG + ALERT( at_aiconsole, "Death event: %s\n", STRING(pev->classname) ); +#endif + pev->health = 0; + } +#if _DEBUG + else + ALERT( at_aiconsole, "INVALID death event:%s\n", STRING(pev->classname) ); +#endif + break; + case SCRIPT_EVENT_NOT_DEAD: + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + pev->deadflag = DEAD_NO; + // This is for life/death sequences where the player can determine whether a character is dead or alive after the script + pev->health = pev->max_health; + } + break; + + case SCRIPT_EVENT_SOUND: // Play a named wave file + EMIT_SOUND( edict(), CHAN_BODY, pEvent->options, 1.0, ATTN_IDLE ); + break; + + case SCRIPT_EVENT_SOUND_VOICE: + EMIT_SOUND( edict(), CHAN_VOICE, pEvent->options, 1.0, ATTN_IDLE ); + break; + + case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 33% of the time + if (RANDOM_LONG(0,2) == 0) + break; + // fall through... + case SCRIPT_EVENT_SENTENCE: // Play a named sentence group + SENTENCEG_PlayRndSz( edict(), pEvent->options, 1.0, ATTN_IDLE, 0, 100 ); + break; + + case SCRIPT_EVENT_FIREEVENT: // Fire a trigger + FireTargets( pEvent->options, this, this, USE_TOGGLE, 0 ); + break; + + case SCRIPT_EVENT_NOINTERRUPT: // Can't be interrupted from now on + if ( m_pCine ) + m_pCine->AllowInterrupt( FALSE ); + break; + + case SCRIPT_EVENT_CANINTERRUPT: // OK to interrupt now + if ( m_pCine ) + m_pCine->AllowInterrupt( TRUE ); + break; + +#if 0 + case SCRIPT_EVENT_INAIR: // Don't DROP_TO_FLOOR() + case SCRIPT_EVENT_ENDANIMATION: // Set ending animation sequence to + break; +#endif + + case MONSTER_EVENT_BODYDROP_HEAVY: + if ( pev->flags & FL_ONGROUND ) + { + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_BODY, "common/bodydrop3.wav", 1, ATTN_NORM, 0, 90 ); + } + else + { + EMIT_SOUND_DYN( ENT(pev), CHAN_BODY, "common/bodydrop4.wav", 1, ATTN_NORM, 0, 90 ); + } + } + break; + + case MONSTER_EVENT_BODYDROP_LIGHT: + if ( pev->flags & FL_ONGROUND ) + { + if ( RANDOM_LONG( 0, 1 ) == 0 ) + { + EMIT_SOUND( ENT(pev), CHAN_BODY, "common/bodydrop3.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT(pev), CHAN_BODY, "common/bodydrop4.wav", 1, ATTN_NORM ); + } + } + break; + + case MONSTER_EVENT_SWISHSOUND: + { + // NO MONSTER may use this anim event unless that monster's precache precaches this sound!!! + EMIT_SOUND( ENT(pev), CHAN_BODY, "zombie/claw_miss2.wav", 1, ATTN_NORM ); + break; + } + + default: + ALERT( at_aiconsole, "Unhandled animation event %d for %s\n", pEvent->event, STRING(pev->classname) ); + break; + + } +} + + +// Combat + +Vector CBaseMonster :: GetGunPosition( ) +{ + UTIL_MakeVectors(pev->angles); + + // Vector vecSrc = pev->origin + gpGlobals->v_forward * 10; + //vecSrc.z = pevShooter->absmin.z + pevShooter->size.z * 0.7; + //vecSrc.z = pev->origin.z + (pev->view_ofs.z - 4); + Vector vecSrc = pev->origin + + gpGlobals->v_forward * m_HackedGunPos.y + + gpGlobals->v_right * m_HackedGunPos.x + + gpGlobals->v_up * m_HackedGunPos.z; + + return vecSrc; +} + + + + + +//========================================================= +// NODE GRAPH +//========================================================= + + + + + +//========================================================= +// FGetNodeRoute - tries to build an entire node path from +// the callers origin to the passed vector. If this is +// possible, ROUTE_SIZE waypoints will be copied into the +// callers m_Route. TRUE is returned if the operation +// succeeds (path is valid) or FALSE if failed (no path +// exists ) +//========================================================= +BOOL CBaseMonster :: FGetNodeRoute ( Vector vecDest ) +{ + int iPath[ MAX_PATH_SIZE ]; + int iSrcNode, iDestNode; + int iResult; + int i; + int iNumToCopy; + + iSrcNode = WorldGraph.FindNearestNode ( pev->origin, this ); + iDestNode = WorldGraph.FindNearestNode ( vecDest, this ); + + if ( iSrcNode == -1 ) + { + // no node nearest self +// ALERT ( at_aiconsole, "FGetNodeRoute: No valid node near self!\n" ); + return FALSE; + } + else if ( iDestNode == -1 ) + { + // no node nearest target +// ALERT ( at_aiconsole, "FGetNodeRoute: No valid node near target!\n" ); + return FALSE; + } + + // valid src and dest nodes were found, so it's safe to proceed with + // find shortest path + int iNodeHull = WorldGraph.HullIndex( this ); // make this a monster virtual function + iResult = WorldGraph.FindShortestPath ( iPath, iSrcNode, iDestNode, iNodeHull, m_afCapability ); + + if ( !iResult ) + { +#if 1 + ALERT ( at_aiconsole, "No Path from %d to %d!\n", iSrcNode, iDestNode ); + return FALSE; +#else + BOOL bRoutingSave = WorldGraph.m_fRoutingComplete; + WorldGraph.m_fRoutingComplete = FALSE; + iResult = WorldGraph.FindShortestPath(iPath, iSrcNode, iDestNode, iNodeHull, m_afCapability); + WorldGraph.m_fRoutingComplete = bRoutingSave; + if ( !iResult ) + { + ALERT ( at_aiconsole, "No Path from %d to %d!\n", iSrcNode, iDestNode ); + return FALSE; + } + else + { + ALERT ( at_aiconsole, "Routing is inconsistent!" ); + } +#endif + } + + // there's a valid path within iPath now, so now we will fill the route array + // up with as many of the waypoints as it will hold. + + // don't copy ROUTE_SIZE entries if the path returned is shorter + // than ROUTE_SIZE!!! + if ( iResult < ROUTE_SIZE ) + { + iNumToCopy = iResult; + } + else + { + iNumToCopy = ROUTE_SIZE; + } + + for ( i = 0 ; i < iNumToCopy; i++ ) + { + m_Route[ i ].vecLocation = WorldGraph.m_pNodes[ iPath[ i ] ].m_vecOrigin; + m_Route[ i ].iType = bits_MF_TO_NODE; + } + + if ( iNumToCopy < ROUTE_SIZE ) + { + m_Route[ iNumToCopy ].vecLocation = vecDest; + m_Route[ iNumToCopy ].iType |= bits_MF_IS_GOAL; + } + + return TRUE; +} + +//========================================================= +// FindHintNode +//========================================================= +int CBaseMonster :: FindHintNode ( void ) +{ + int i; + TraceResult tr; + + if ( !WorldGraph.m_fGraphPresent ) + { + ALERT ( at_aiconsole, "find_hintnode: graph not ready!\n" ); + return NO_NODE; + } + + if ( WorldGraph.m_iLastActiveIdleSearch >= WorldGraph.m_cNodes ) + { + WorldGraph.m_iLastActiveIdleSearch = 0; + } + + for ( i = 0; i < WorldGraph.m_cNodes ; i++ ) + { + int nodeNumber = (i + WorldGraph.m_iLastActiveIdleSearch) % WorldGraph.m_cNodes; + CNode &node = WorldGraph.Node( nodeNumber ); + + if ( node.m_sHintType ) + { + // this node has a hint. Take it if it is visible, the monster likes it, and the monster has an animation to match the hint's activity. + if ( FValidateHintType ( node.m_sHintType ) ) + { + if ( !node.m_sHintActivity || LookupActivity ( node.m_sHintActivity ) != ACTIVITY_NOT_AVAILABLE ) + { + UTIL_TraceLine ( pev->origin + pev->view_ofs, node.m_vecOrigin + pev->view_ofs, ignore_monsters, ENT(pev), &tr ); + + if ( tr.flFraction == 1.0 ) + { + WorldGraph.m_iLastActiveIdleSearch = nodeNumber + 1; // next monster that searches for hint nodes will start where we left off. + return nodeNumber;// take it! + } + } + } + } + } + + WorldGraph.m_iLastActiveIdleSearch = 0;// start at the top of the list for the next search. + + return NO_NODE; +} + + +void CBaseMonster::ReportAIState( void ) +{ + ALERT_TYPE level = at_console; + + static const char *pStateNames[] = { "None", "Idle", "Combat", "Alert", "Hunt", "Prone", "Scripted", "Dead" }; + + ALERT( level, "%s: ", STRING(pev->classname) ); + if ( (int)m_MonsterState < ARRAYSIZE(pStateNames) ) + ALERT( level, "State: %s, ", pStateNames[m_MonsterState] ); + int i = 0; + while ( activity_map[i].type != 0 ) + { + if ( activity_map[i].type == (int)m_Activity ) + { + ALERT( level, "Activity %s, ", activity_map[i].name ); + break; + } + i++; + } + + if ( m_pSchedule ) + { + const char *pName = NULL; + pName = m_pSchedule->pName; + if ( !pName ) + pName = "Unknown"; + ALERT( level, "Schedule %s, ", pName ); + Task_t *pTask = GetTask(); + if ( pTask ) + ALERT( level, "Task %d (#%d), ", pTask->iTask, m_iScheduleIndex ); + } + else + ALERT( level, "No Schedule, " ); + + if ( m_hEnemy != NULL ) + ALERT( level, "\nEnemy is %s", STRING(m_hEnemy->pev->classname) ); + else + ALERT( level, "No enemy" ); + + if ( IsMoving() ) + { + ALERT( level, " Moving " ); + if ( m_flMoveWaitFinished > gpGlobals->time ) + ALERT( level, ": Stopped for %.2f. ", m_flMoveWaitFinished - gpGlobals->time ); + else if ( m_IdealActivity == GetStoppedActivity() ) + ALERT( level, ": In stopped anim. " ); + } + + CSquadMonster *pSquadMonster = MySquadMonsterPointer(); + + if ( pSquadMonster ) + { + if ( !pSquadMonster->InSquad() ) + { + ALERT ( level, "not " ); + } + + ALERT ( level, "In Squad, " ); + + if ( !pSquadMonster->IsLeader() ) + { + ALERT ( level, "not " ); + } + + ALERT ( level, "Leader." ); + } + + ALERT( level, "\n" ); + ALERT( level, "Yaw speed:%3.1f,Health: %3.1f\n", pev->yaw_speed, pev->health ); + if ( pev->spawnflags & SF_MONSTER_PRISONER ) + ALERT( level, " PRISONER! " ); + if ( pev->spawnflags & SF_MONSTER_PREDISASTER ) + ALERT( level, " Pre-Disaster! " ); + ALERT( level, "\n" ); +} + +//========================================================= +// KeyValue +// +// !!! netname entvar field is used in squadmonster for groupname!!! +//========================================================= +void CBaseMonster :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "TriggerTarget")) + { + m_iszTriggerTarget = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "TriggerCondition") ) + { + m_iTriggerCondition = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CBaseToggle::KeyValue( pkvd ); + } +} + +//========================================================= +// FCheckAITrigger - checks the monster's AI Trigger Conditions, +// if there is a condition, then checks to see if condition is +// met. If yes, the monster's TriggerTarget is fired. +// +// Returns TRUE if the target is fired. +//========================================================= +BOOL CBaseMonster :: FCheckAITrigger ( void ) +{ + BOOL fFireTarget; + + if ( m_iTriggerCondition == AITRIGGER_NONE ) + { + // no conditions, so this trigger is never fired. + return FALSE; + } + + fFireTarget = FALSE; + + switch ( m_iTriggerCondition ) + { + case AITRIGGER_SEEPLAYER_ANGRY_AT_PLAYER: + if ( m_hEnemy != NULL && m_hEnemy->IsPlayer() && HasConditions ( bits_COND_SEE_ENEMY ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_SEEPLAYER_UNCONDITIONAL: + if ( HasConditions ( bits_COND_SEE_CLIENT ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_SEEPLAYER_NOT_IN_COMBAT: + if ( HasConditions ( bits_COND_SEE_CLIENT ) && + m_MonsterState != MONSTERSTATE_COMBAT && + m_MonsterState != MONSTERSTATE_PRONE && + m_MonsterState != MONSTERSTATE_SCRIPT) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_TAKEDAMAGE: + if ( m_afConditions & ( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_DEATH: + if ( pev->deadflag != DEAD_NO ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HALFHEALTH: + if ( IsAlive() && pev->health <= ( pev->max_health / 2 ) ) + { + fFireTarget = TRUE; + } + break; +/* + + // !!!UNDONE - no persistant game state that allows us to track these two. + + case AITRIGGER_SQUADMEMBERDIE: + break; + case AITRIGGER_SQUADLEADERDIE: + break; +*/ + case AITRIGGER_HEARWORLD: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_WORLD ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HEARPLAYER: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_PLAYER ) + { + fFireTarget = TRUE; + } + break; + case AITRIGGER_HEARCOMBAT: + if ( m_afConditions & bits_COND_HEAR_SOUND && m_afSoundTypes & bits_SOUND_COMBAT ) + { + fFireTarget = TRUE; + } + break; + } + + if ( fFireTarget ) + { + // fire the target, then set the trigger conditions to NONE so we don't fire again + ALERT ( at_aiconsole, "AI Trigger Fire Target\n" ); + FireTargets( STRING( m_iszTriggerTarget ), this, this, USE_TOGGLE, 0 ); + m_iTriggerCondition = AITRIGGER_NONE; + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CanPlaySequence - determines whether or not the monster +// can play the scripted sequence or AI sequence that is +// trying to possess it. If DisregardState is set, the monster +// will be sucked into the script no matter what state it is +// in. ONLY Scripted AI ents should allow this. +//========================================================= +int CBaseMonster :: CanPlaySequence( BOOL fDisregardMonsterState, int interruptLevel ) +{ + if ( m_pCine || !IsAlive() || m_MonsterState == MONSTERSTATE_PRONE ) + { + // monster is already running a scripted sequence or dead! + return FALSE; + } + + if ( fDisregardMonsterState ) + { + // ok to go, no matter what the monster state. (scripted AI) + return TRUE; + } + + if ( m_MonsterState == MONSTERSTATE_NONE || m_MonsterState == MONSTERSTATE_IDLE || m_IdealMonsterState == MONSTERSTATE_IDLE ) + { + // ok to go, but only in these states + return TRUE; + } + + if ( m_MonsterState == MONSTERSTATE_ALERT && interruptLevel >= SS_INTERRUPT_BY_NAME ) + return TRUE; + + // unknown situation + return FALSE; +} + + +//========================================================= +// FindLateralCover - attempts to locate a spot in the world +// directly to the left or right of the caller that will +// conceal them from view of pSightEnt +//========================================================= +#define COVER_CHECKS 5// how many checks are made +#define COVER_DELTA 48// distance between checks + +BOOL CBaseMonster :: FindLateralCover ( const Vector &vecThreat, const Vector &vecViewOffset ) +{ + TraceResult tr; + Vector vecBestOnLeft; + Vector vecBestOnRight; + Vector vecLeftTest; + Vector vecRightTest; + Vector vecStepRight; + int i; + + UTIL_MakeVectors ( pev->angles ); + vecStepRight = gpGlobals->v_right * COVER_DELTA; + vecStepRight.z = 0; + + vecLeftTest = vecRightTest = pev->origin; + + for ( i = 0 ; i < COVER_CHECKS ; i++ ) + { + vecLeftTest = vecLeftTest - vecStepRight; + vecRightTest = vecRightTest + vecStepRight; + + // it's faster to check the SightEnt's visibility to the potential spot than to check the local move, so we do that first. + UTIL_TraceLine( vecThreat + vecViewOffset, vecLeftTest + pev->view_ofs, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if (tr.flFraction != 1.0) + { + if ( FValidateCover ( vecLeftTest ) && CheckLocalMove( pev->origin, vecLeftTest, NULL, NULL ) == LOCALMOVE_VALID ) + { + if ( MoveToLocation( ACT_RUN, 0, vecLeftTest ) ) + { + return TRUE; + } + } + } + + // it's faster to check the SightEnt's visibility to the potential spot than to check the local move, so we do that first. + UTIL_TraceLine(vecThreat + vecViewOffset, vecRightTest + pev->view_ofs, ignore_monsters, ignore_glass, ENT(pev)/*pentIgnore*/, &tr); + + if ( tr.flFraction != 1.0 ) + { + if ( FValidateCover ( vecRightTest ) && CheckLocalMove( pev->origin, vecRightTest, NULL, NULL ) == LOCALMOVE_VALID ) + { + if ( MoveToLocation( ACT_RUN, 0, vecRightTest ) ) + { + return TRUE; + } + } + } + } + + return FALSE; +} + + +Vector CBaseMonster :: ShootAtEnemy( const Vector &shootOrigin ) +{ + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy ) + { + return ( (pEnemy->BodyTarget( shootOrigin ) - pEnemy->pev->origin) + m_vecEnemyLKP - shootOrigin ).Normalize(); + } + else + return gpGlobals->v_forward; +} + + + +//========================================================= +// FacingIdeal - tells us if a monster is facing its ideal +// yaw. Created this function because many spots in the +// code were checking the yawdiff against this magic +// number. Nicer to have it in one place if we're gonna +// be stuck with it. +//========================================================= +BOOL CBaseMonster :: FacingIdeal( void ) +{ + if ( fabs( FlYawDiff() ) <= 0.006 )//!!!BUGBUG - no magic numbers!!! + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// FCanActiveIdle +//========================================================= +BOOL CBaseMonster :: FCanActiveIdle ( void ) +{ + /* + if ( m_MonsterState == MONSTERSTATE_IDLE && m_IdealMonsterState == MONSTERSTATE_IDLE && !IsMoving() ) + { + return TRUE; + } + */ + return FALSE; +} + + +void CBaseMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) +{ + if ( pszSentence && IsAlive() ) + { + if ( pszSentence[0] == '!' ) + EMIT_SOUND_DYN( edict(), CHAN_VOICE, pszSentence, volume, attenuation, 0, PITCH_NORM ); + else + SENTENCEG_PlayRndSz( edict(), pszSentence, volume, attenuation, 0, PITCH_NORM ); + } +} + + +void CBaseMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) +{ + PlaySentence( pszSentence, duration, volume, attenuation ); +} + + +void CBaseMonster::SentenceStop( void ) +{ + EMIT_SOUND( edict(), CHAN_VOICE, "common/null.wav", 1.0, ATTN_IDLE ); +} + + +void CBaseMonster::CorpseFallThink( void ) +{ + if ( pev->flags & FL_ONGROUND ) + { + SetThink ( NULL ); + + SetSequenceBox( ); + UTIL_SetOrigin( pev, pev->origin );// link into world. + } + else + pev->nextthink = gpGlobals->time + 0.1; +} + +// Call after animation/pose is set up +void CBaseMonster :: MonsterInitDead( void ) +{ + InitBoneControllers(); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_TOSS;// so he'll fall to ground + + pev->frame = 0; + ResetSequenceInfo( ); + pev->framerate = 0; + + // Copy health + pev->max_health = pev->health; + pev->deadflag = DEAD_DEAD; + + UTIL_SetSize(pev, g_vecZero, g_vecZero ); + UTIL_SetOrigin( pev, pev->origin ); + + // Setup health counters, etc. + BecomeDead(); + SetThink( CorpseFallThink ); + pev->nextthink = gpGlobals->time + 0.5; +} + +//========================================================= +// BBoxIsFlat - check to see if the monster's bounding box +// is lying flat on a surface (traces from all four corners +// are same length.) +//========================================================= +BOOL CBaseMonster :: BBoxFlat ( void ) +{ + TraceResult tr; + Vector vecPoint; + float flXSize, flYSize; + float flLength; + float flLength2; + + flXSize = pev->size.x / 2; + flYSize = pev->size.y / 2; + + vecPoint.x = pev->origin.x + flXSize; + vecPoint.y = pev->origin.y + flYSize; + vecPoint.z = pev->origin.z; + + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength = (vecPoint - tr.vecEndPos).Length(); + + vecPoint.x = pev->origin.x - flXSize; + vecPoint.y = pev->origin.y - flYSize; + + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + vecPoint.x = pev->origin.x - flXSize; + vecPoint.y = pev->origin.y + flYSize; + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + vecPoint.x = pev->origin.x + flXSize; + vecPoint.y = pev->origin.y - flYSize; + UTIL_TraceLine ( vecPoint, vecPoint - Vector ( 0, 0, 100 ), ignore_monsters, ENT(pev), &tr ); + flLength2 = (vecPoint - tr.vecEndPos).Length(); + if ( flLength2 > flLength ) + { + return FALSE; + } + flLength = flLength2; + + return TRUE; +} + +//========================================================= +// Get Enemy - tries to find the best suitable enemy for the monster. +//========================================================= +BOOL CBaseMonster :: GetEnemy ( void ) +{ + CBaseEntity *pNewEnemy; + + if ( HasConditions(bits_COND_SEE_HATE | bits_COND_SEE_DISLIKE | bits_COND_SEE_NEMESIS) ) + { + pNewEnemy = BestVisibleEnemy(); + + if ( pNewEnemy != m_hEnemy && pNewEnemy != NULL) + { + // DO NOT mess with the monster's m_hEnemy pointer unless the schedule the monster is currently running will be interrupted + // by COND_NEW_ENEMY. This will eliminate the problem of monsters getting a new enemy while they are in a schedule that doesn't care, + // and then not realizing it by the time they get to a schedule that does. I don't feel this is a good permanent fix. + + if ( m_pSchedule ) + { + if ( m_pSchedule->iInterruptMask & bits_COND_NEW_ENEMY ) + { + PushEnemy( m_hEnemy, m_vecEnemyLKP ); + SetConditions(bits_COND_NEW_ENEMY); + m_hEnemy = pNewEnemy; + m_vecEnemyLKP = m_hEnemy->pev->origin; + } + // if the new enemy has an owner, take that one as well + if (pNewEnemy->pev->owner != NULL) + { + CBaseEntity *pOwner = GetMonsterPointer( pNewEnemy->pev->owner ); + if ( pOwner && (pOwner->pev->flags & FL_MONSTER) && IRelationship( pOwner ) != R_NO ) + PushEnemy( pOwner, m_vecEnemyLKP ); + } + } + } + } + + // remember old enemies + if (m_hEnemy == NULL && PopEnemy( )) + { + if ( m_pSchedule ) + { + if ( m_pSchedule->iInterruptMask & bits_COND_NEW_ENEMY ) + { + SetConditions(bits_COND_NEW_ENEMY); + } + } + } + + if ( m_hEnemy != NULL ) + { + // monster has an enemy. + return TRUE; + } + + return FALSE;// monster has no enemy +} + + +//========================================================= +// DropItem - dead monster drops named item +//========================================================= +CBaseEntity* CBaseMonster :: DropItem ( char *pszItemName, const Vector &vecPos, const Vector &vecAng ) +{ + if ( !pszItemName ) + { + ALERT ( at_console, "DropItem() - No item name!\n" ); + return NULL; + } + + CBaseEntity *pItem = CBaseEntity::Create( pszItemName, vecPos, vecAng, edict() ); + + if ( pItem ) + { + // do we want this behavior to be default?! (sjb) + pItem->pev->velocity = pev->velocity; + pItem->pev->avelocity = Vector ( 0, RANDOM_FLOAT( 0, 100 ), 0 ); + return pItem; + } + else + { + ALERT ( at_console, "DropItem() - Didn't create!\n" ); + return FALSE; + } + +} + + +BOOL CBaseMonster :: ShouldFadeOnDeath( void ) +{ + // if flagged to fade out or I have an owner (I came from a monster spawner) + if ( (pev->spawnflags & SF_MONSTER_FADECORPSE) || !FNullEnt( pev->owner ) ) + return TRUE; + + return FALSE; +} diff --git a/dlls/monsters.h b/dlls/monsters.h index b0f5d51..75c945f 100644 --- a/dlls/monsters.h +++ b/dlls/monsters.h @@ -1,15 +1,15 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. * ****/ #ifndef MONSTERS_H @@ -24,6 +24,11 @@ */ +// CHECKLOCALMOVE result types +#define LOCALMOVE_INVALID 0 // move is not possible +#define LOCALMOVE_INVALID_DONT_TRIANGULATE 1 // move is not possible, don't try to triangulate +#define LOCALMOVE_VALID 2 // move is possible + // Hit Group standards #define HITGROUP_GENERIC 0 #define HITGROUP_HEAD 1 @@ -35,6 +40,35 @@ #define HITGROUP_RIGHTLEG 7 +// Monster Spawnflags +#define SF_MONSTER_WAIT_TILL_SEEN 1// spawnflag that makes monsters wait until player can see them before attacking. +#define SF_MONSTER_GAG 2 // no idle noises from this monster +#define SF_MONSTER_HITMONSTERCLIP 4 +// 8 +#define SF_MONSTER_PRISONER 16 // monster won't attack anyone, no one will attacke him. +// 32 +// 64 +#define SF_MONSTER_WAIT_FOR_SCRIPT 128 //spawnflag that makes monsters wait to check for attacking until the script is done or they've been attacked +#define SF_MONSTER_PREDISASTER 256 //this is a predisaster scientist or barney. Influences how they speak. +#define SF_MONSTER_FADECORPSE 512 // Fade out corpse after death +#define SF_MONSTER_FALL_TO_GROUND 0x80000000 + +// specialty spawnflags +#define SF_MONSTER_TURRET_AUTOACTIVATE 32 +#define SF_MONSTER_TURRET_STARTINACTIVE 64 +#define SF_MONSTER_WAIT_UNTIL_PROVOKED 64 // don't attack the player unless provoked + + + +// MoveToOrigin stuff +#define MOVE_START_TURN_DIST 64 // when this far away from moveGoal, start turning to face next goal +#define MOVE_STUCK_DIST 32 // if a monster can't step this far, it is stuck. + + +// MoveToOrigin stuff +#define MOVE_NORMAL 0// normal move in the direction monster is facing +#define MOVE_STRAFE 1// moves in direction specified, no matter which way monster is facing + // spawn flags 256 and above are already taken by the engine extern void UTIL_MoveToOrigin( edict_t* pent, const Vector &vecGoal, float flDist, int iMoveType ); @@ -59,7 +93,50 @@ BOOL FBoxVisible ( entvars_t *pevLooker, entvars_t *pevTarget, Vector &vecTarget #define R_NM 3// (NEMESIS) A monster Will ALWAYS attack its nemsis, no matter what +// these bits represent the monster's memory +#define MEMORY_CLEAR 0 +#define bits_MEMORY_PROVOKED ( 1 << 0 )// right now only used for houndeyes. +#define bits_MEMORY_INCOVER ( 1 << 1 )// monster knows it is in a covered position. +#define bits_MEMORY_SUSPICIOUS ( 1 << 2 )// Ally is suspicious of the player, and will move to provoked more easily +#define bits_MEMORY_PATH_FINISHED ( 1 << 3 )// Finished monster path (just used by big momma for now) +#define bits_MEMORY_ON_PATH ( 1 << 4 )// Moving on a path +#define bits_MEMORY_MOVE_FAILED ( 1 << 5 )// Movement has already failed +#define bits_MEMORY_FLINCHED ( 1 << 6 )// Has already flinched #define bits_MEMORY_KILLED ( 1 << 7 )// HACKHACK -- remember that I've already called my Killed() +#define bits_MEMORY_CUSTOM4 ( 1 << 28 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM3 ( 1 << 29 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM2 ( 1 << 30 ) // Monster-specific memory +#define bits_MEMORY_CUSTOM1 ( 1 << 31 ) // Monster-specific memory + +// trigger conditions for scripted AI +// these MUST match the CHOICES interface in halflife.fgd for the base monster +enum +{ + AITRIGGER_NONE = 0, + AITRIGGER_SEEPLAYER_ANGRY_AT_PLAYER, + AITRIGGER_TAKEDAMAGE, + AITRIGGER_HALFHEALTH, + AITRIGGER_DEATH, + AITRIGGER_SQUADMEMBERDIE, + AITRIGGER_SQUADLEADERDIE, + AITRIGGER_HEARWORLD, + AITRIGGER_HEARPLAYER, + AITRIGGER_HEARCOMBAT, + AITRIGGER_SEEPLAYER_UNCONDITIONAL, + AITRIGGER_SEEPLAYER_NOT_IN_COMBAT, +}; +/* + 0 : "No Trigger" + 1 : "See Player" + 2 : "Take Damage" + 3 : "50% Health Remaining" + 4 : "Death" + 5 : "Squad Member Dead" + 6 : "Squad Leader Dead" + 7 : "Hear World" + 8 : "Hear Player" + 9 : "Hear Combat" +*/ // // A gib is a chunk of a body, or a piece of wood/metal/rocks/etc. @@ -85,4 +162,22 @@ public: }; +#define CUSTOM_SCHEDULES\ + virtual Schedule_t *ScheduleFromName( const char *pName );\ + static Schedule_t *m_scheduleList[]; + +#define DEFINE_CUSTOM_SCHEDULES(derivedClass)\ + Schedule_t *derivedClass::m_scheduleList[] = + +#define IMPLEMENT_CUSTOM_SCHEDULES(derivedClass, baseClass)\ + Schedule_t *derivedClass::ScheduleFromName( const char *pName )\ + {\ + Schedule_t *pSchedule = ScheduleInList( pName, m_scheduleList, ARRAYSIZE(m_scheduleList) );\ + if ( !pSchedule )\ + return baseClass::ScheduleFromName(pName);\ + return pSchedule;\ + } + + + #endif //MONSTERS_H diff --git a/dlls/monsterstate.cpp b/dlls/monsterstate.cpp new file mode 100644 index 0000000..aca95b3 --- /dev/null +++ b/dlls/monsterstate.cpp @@ -0,0 +1,234 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// monsterstate.cpp - base class monster functions for +// controlling core AI. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" +#include "saverestore.h" +#include "soundent.h" + +//========================================================= +// SetState +//========================================================= +void CBaseMonster :: SetState ( MONSTERSTATE State ) +{ +/* + if ( State != m_MonsterState ) + { + ALERT ( at_aiconsole, "State Changed to %d\n", State ); + } +*/ + + switch( State ) + { + + // Drop enemy pointers when going to idle + case MONSTERSTATE_IDLE: + + if ( m_hEnemy != NULL ) + { + m_hEnemy = NULL;// not allowed to have an enemy anymore. + ALERT ( at_aiconsole, "Stripped\n" ); + } + break; + } + + m_MonsterState = State; + m_IdealMonsterState = State; +} + +//========================================================= +// RunAI +//========================================================= +void CBaseMonster :: RunAI ( void ) +{ + // to test model's eye height + //UTIL_ParticleEffect ( pev->origin + pev->view_ofs, g_vecZero, 255, 10 ); + + // IDLE sound permitted in ALERT state is because monsters were silent in ALERT state. Only play IDLE sound in IDLE state + // once we have sounds for that state. + if ( ( m_MonsterState == MONSTERSTATE_IDLE || m_MonsterState == MONSTERSTATE_ALERT ) && RANDOM_LONG(0,99) == 0 && !(pev->flags & SF_MONSTER_GAG) ) + { + IdleSound(); + } + + if ( m_MonsterState != MONSTERSTATE_NONE && + m_MonsterState != MONSTERSTATE_PRONE && + m_MonsterState != MONSTERSTATE_DEAD )// don't bother with this crap if monster is prone. + { + // collect some sensory Condition information. + // don't let monsters outside of the player's PVS act up, or most of the interesting + // things will happen before the player gets there! + // UPDATE: We now let COMBAT state monsters think and act fully outside of player PVS. This allows the player to leave + // an area where monsters are fighting, and the fight will continue. + if ( !FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) || ( m_MonsterState == MONSTERSTATE_COMBAT ) ) + { + Look( m_flDistLook ); + Listen();// check for audible sounds. + + // now filter conditions. + ClearConditions( IgnoreConditions() ); + + GetEnemy(); + } + + // do these calculations if monster has an enemy. + if ( m_hEnemy != NULL ) + { + CheckEnemy( m_hEnemy ); + } + + CheckAmmo(); + } + + FCheckAITrigger(); + + PrescheduleThink(); + + MaintainSchedule(); + + // if the monster didn't use these conditions during the above call to MaintainSchedule() or CheckAITrigger() + // we throw them out cause we don't want them sitting around through the lifespan of a schedule + // that doesn't use them. + m_afConditions &= ~( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ); +} + +//========================================================= +// GetIdealState - surveys the Conditions information available +// and finds the best new state for a monster. +//========================================================= +MONSTERSTATE CBaseMonster :: GetIdealState ( void ) +{ + int iConditions; + + iConditions = IScheduleFlags(); + + // If no schedule conditions, the new ideal state is probably the reason we're in here. + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + + /* + IDLE goes to ALERT upon hearing a sound + -IDLE goes to ALERT upon being injured + IDLE goes to ALERT upon seeing food + -IDLE goes to COMBAT upon sighting an enemy + IDLE goes to HUNT upon smelling food + */ + { + if ( iConditions & bits_COND_NEW_ENEMY ) + { + // new enemy! This means an idle monster has seen someone it dislikes, or + // that a monster in combat has found a more suitable target to attack + m_IdealMonsterState = MONSTERSTATE_COMBAT; + } + else if ( iConditions & bits_COND_LIGHT_DAMAGE ) + { + MakeIdealYaw ( m_vecEnemyLKP ); + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + else if ( iConditions & bits_COND_HEAVY_DAMAGE ) + { + MakeIdealYaw ( m_vecEnemyLKP ); + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + else if ( iConditions & bits_COND_HEAR_SOUND ) + { + CSound *pSound; + + pSound = PBestSound(); + ASSERT( pSound != NULL ); + if ( pSound ) + { + MakeIdealYaw ( pSound->m_vecOrigin ); + if ( pSound->m_iType & (bits_SOUND_COMBAT|bits_SOUND_DANGER) ) + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + } + else if ( iConditions & (bits_COND_SMELL | bits_COND_SMELL_FOOD) ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + } + + break; + } + case MONSTERSTATE_ALERT: + /* + ALERT goes to IDLE upon becoming bored + -ALERT goes to COMBAT upon sighting an enemy + ALERT goes to HUNT upon hearing a noise + */ + { + if ( iConditions & (bits_COND_NEW_ENEMY|bits_COND_SEE_ENEMY) ) + { + // see an enemy we MUST attack + m_IdealMonsterState = MONSTERSTATE_COMBAT; + } + else if ( iConditions & bits_COND_HEAR_SOUND ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + CSound *pSound = PBestSound(); + ASSERT( pSound != NULL ); + if ( pSound ) + MakeIdealYaw ( pSound->m_vecOrigin ); + } + break; + } + case MONSTERSTATE_COMBAT: + /* + COMBAT goes to HUNT upon losing sight of enemy + COMBAT goes to ALERT upon death of enemy + */ + { + if ( m_hEnemy == NULL ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + // pev->effects = EF_BRIGHTFIELD; + ALERT ( at_aiconsole, "***Combat state with no enemy!\n" ); + } + break; + } + case MONSTERSTATE_HUNT: + /* + HUNT goes to ALERT upon seeing food + HUNT goes to ALERT upon being injured + HUNT goes to IDLE if goal touched + HUNT goes to COMBAT upon seeing enemy + */ + { + break; + } + case MONSTERSTATE_SCRIPT: + if ( iConditions & (bits_COND_TASK_FAILED|bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE) ) + { + ExitScriptedSequence(); // This will set the ideal state + } + break; + + case MONSTERSTATE_DEAD: + m_IdealMonsterState = MONSTERSTATE_DEAD; + break; + } + + return m_IdealMonsterState; +} + diff --git a/dlls/mortar.cpp b/dlls/mortar.cpp index eb44416..e23d7b6 100644 --- a/dlls/mortar.cpp +++ b/dlls/mortar.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/mp5.cpp b/dlls/mp5.cpp index 780da82..8ee2ce8 100644 --- a/dlls/mp5.cpp +++ b/dlls/mp5.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/mpstubb.cpp b/dlls/mpstubb.cpp index f31eb4f..673c51b 100644 --- a/dlls/mpstubb.cpp +++ b/dlls/mpstubb.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/multiplay_gamerules.cpp b/dlls/multiplay_gamerules.cpp index 4f14434..2e2cab7 100644 --- a/dlls/multiplay_gamerules.cpp +++ b/dlls/multiplay_gamerules.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -26,6 +26,7 @@ #include "game.h" #include "items.h" #include "voice_gamemgr.h" +#include "hltv.h" extern DLL_GLOBAL CGameRules *g_pGameRules; extern DLL_GLOBAL BOOL g_fGameOver; @@ -198,8 +199,8 @@ void CHalfLifeMultiplay :: Think ( void ) { // bounds check int time = (int)CVAR_GET_FLOAT( "mp_chattime" ); - if ( time < 10 ) - CVAR_SET_STRING( "mp_chattime", "10" ); + if ( time < 1 ) + CVAR_SET_STRING( "mp_chattime", "1" ); else if ( time > MAX_INTERMISSION_TIME ) CVAR_SET_STRING( "mp_chattime", UTIL_dtos1( MAX_INTERMISSION_TIME ) ); @@ -419,18 +420,18 @@ void CHalfLifeMultiplay :: InitHUD( CBasePlayer *pl ) // team match? if ( g_teamplay ) { - UTIL_LogPrintf( "\"%s<%i><%u><%s>\" entered the game\n", + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" entered the game\n", STRING( pl->pev->netname ), GETPLAYERUSERID( pl->edict() ), - GETPLAYERWONID( pl->edict() ), + GETPLAYERAUTHID( pl->edict() ), g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pl->edict() ), "model" ) ); } else { - UTIL_LogPrintf( "\"%s<%i><%u><%i>\" entered the game\n", + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" entered the game\n", STRING( pl->pev->netname ), GETPLAYERUSERID( pl->edict() ), - GETPLAYERWONID( pl->edict() ), + GETPLAYERAUTHID( pl->edict() ), GETPLAYERUSERID( pl->edict() ) ); } @@ -488,18 +489,18 @@ void CHalfLifeMultiplay :: ClientDisconnected( edict_t *pClient ) // team match? if ( g_teamplay ) { - UTIL_LogPrintf( "\"%s<%i><%u><%s>\" disconnected\n", + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" disconnected\n", STRING( pPlayer->pev->netname ), GETPLAYERUSERID( pPlayer->edict() ), - GETPLAYERWONID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pPlayer->edict() ), "model" ) ); } else { - UTIL_LogPrintf( "\"%s<%i><%u><%i>\" disconnected\n", + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" disconnected\n", STRING( pPlayer->pev->netname ), GETPLAYERUSERID( pPlayer->edict() ), - GETPLAYERWONID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), GETPLAYERUSERID( pPlayer->edict() ) ); } @@ -741,19 +742,19 @@ void CHalfLifeMultiplay::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, // team match? if ( g_teamplay ) { - UTIL_LogPrintf( "\"%s<%i><%u><%s>\" committed suicide with \"%s\"\n", + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\"\n", STRING( pVictim->pev->netname ), GETPLAYERUSERID( pVictim->edict() ), - GETPLAYERWONID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), killer_weapon_name ); } else { - UTIL_LogPrintf( "\"%s<%i><%u><%i>\" committed suicide with \"%s\"\n", + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"%s\"\n", STRING( pVictim->pev->netname ), GETPLAYERUSERID( pVictim->edict() ), - GETPLAYERWONID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), GETPLAYERUSERID( pVictim->edict() ), killer_weapon_name ); } @@ -763,27 +764,27 @@ void CHalfLifeMultiplay::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, // team match? if ( g_teamplay ) { - UTIL_LogPrintf( "\"%s<%i><%u><%s>\" killed \"%s<%i><%u><%s>\" with \"%s\"\n", + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" killed \"%s<%i><%s><%s>\" with \"%s\"\n", STRING( pKiller->netname ), GETPLAYERUSERID( ENT(pKiller) ), - GETPLAYERWONID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( ENT(pKiller) ), "model" ), STRING( pVictim->pev->netname ), GETPLAYERUSERID( pVictim->edict() ), - GETPLAYERWONID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), killer_weapon_name ); } else { - UTIL_LogPrintf( "\"%s<%i><%u><%i>\" killed \"%s<%i><%u><%i>\" with \"%s\"\n", + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" killed \"%s<%i><%s><%i>\" with \"%s\"\n", STRING( pKiller->netname ), GETPLAYERUSERID( ENT(pKiller) ), - GETPLAYERWONID( ENT(pKiller) ), + GETPLAYERAUTHID( ENT(pKiller) ), GETPLAYERUSERID( ENT(pKiller) ), STRING( pVictim->pev->netname ), GETPLAYERUSERID( pVictim->edict() ), - GETPLAYERWONID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), GETPLAYERUSERID( pVictim->edict() ), killer_weapon_name ); } @@ -795,26 +796,27 @@ void CHalfLifeMultiplay::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, // team match? if ( g_teamplay ) { - UTIL_LogPrintf( "\"%s<%i><%u><%s>\" committed suicide with \"%s\" (world)\n", + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" committed suicide with \"%s\" (world)\n", STRING( pVictim->pev->netname ), GETPLAYERUSERID( pVictim->edict() ), - GETPLAYERWONID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), g_engfuncs.pfnInfoKeyValue( g_engfuncs.pfnGetInfoKeyBuffer( pVictim->edict() ), "model" ), killer_weapon_name ); } else { - UTIL_LogPrintf( "\"%s<%i><%u><%i>\" committed suicide with \"%s\" (world)\n", + UTIL_LogPrintf( "\"%s<%i><%s><%i>\" committed suicide with \"%s\" (world)\n", STRING( pVictim->pev->netname ), GETPLAYERUSERID( pVictim->edict() ), - GETPLAYERWONID( pVictim->edict() ), + GETPLAYERAUTHID( pVictim->edict() ), GETPLAYERUSERID( pVictim->edict() ), killer_weapon_name ); } } - MESSAGE_BEGIN( MSG_SPEC, SVC_HLTV ); - WRITE_BYTE ( DRC_EVENT ); // player killed + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_EVENT ); // player killed WRITE_SHORT( ENTINDEX(pVictim->edict()) ); // index number of primary entity if (pevInflictor) WRITE_SHORT( ENTINDEX(ENT(pevInflictor)) ); // index number of secondary entity @@ -1145,8 +1147,8 @@ void CHalfLifeMultiplay :: GoToIntermission( void ) // bounds check int time = (int)CVAR_GET_FLOAT( "mp_chattime" ); - if ( time < 10 ) - CVAR_SET_STRING( "mp_chattime", "10" ); + if ( time < 1 ) + CVAR_SET_STRING( "mp_chattime", "1" ); else if ( time > MAX_INTERMISSION_TIME ) CVAR_SET_STRING( "mp_chattime", UTIL_dtos1( MAX_INTERMISSION_TIME ) ); diff --git a/dlls/nihilanth.cpp b/dlls/nihilanth.cpp new file mode 100644 index 0000000..061c9a5 --- /dev/null +++ b/dlls/nihilanth.cpp @@ -0,0 +1,1836 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "effects.h" + +#define N_SCALE 15 +#define N_SPHERES 20 + +class CNihilanth : public CBaseMonster +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int Classify( void ) { return CLASS_ALIEN_MILITARY; }; + int BloodColor( void ) { return BLOOD_COLOR_YELLOW; } + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -16 * N_SCALE, -16 * N_SCALE, -48 * N_SCALE ); + pev->absmax = pev->origin + Vector( 16 * N_SCALE, 16 * N_SCALE, 28 * N_SCALE ); + } + + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void EXPORT StartupThink( void ); + void EXPORT HuntThink( void ); + void EXPORT CrashTouch( CBaseEntity *pOther ); + void EXPORT DyingThink( void ); + void EXPORT StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT NullThink( void ); + void EXPORT CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void FloatSequence( void ); + void NextActivity( void ); + + void Flight( void ); + + BOOL AbsorbSphere( void ); + BOOL EmitSphere( void ); + void TargetSphere( USE_TYPE useType, float value ); + CBaseEntity *RandomTargetname( const char *szName ); + void ShootBalls( void ); + void MakeFriend( Vector vecPos ); + + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + + void PainSound( void ); + void DeathSound( void ); + + static const char *pAttackSounds[]; // vocalization: play sometimes when he launches an attack + static const char *pBallSounds[]; // the sound of the lightening ball launch + static const char *pShootSounds[]; // grunting vocalization: play sometimes when he launches an attack + static const char *pRechargeSounds[]; // vocalization: play when he recharges + static const char *pLaughSounds[]; // vocalization: play sometimes when hit and still has lots of health + static const char *pPainSounds[]; // vocalization: play sometimes when hit and has much less health and no more chargers + static const char *pDeathSounds[]; // vocalization: play as he dies + + // x_teleattack1.wav the looping sound of the teleport attack ball. + + float m_flForce; + + float m_flNextPainSound; + + Vector m_velocity; + Vector m_avelocity; + + Vector m_vecTarget; + Vector m_posTarget; + + Vector m_vecDesired; + Vector m_posDesired; + + float m_flMinZ; + float m_flMaxZ; + + Vector m_vecGoal; + + float m_flLastSeen; + float m_flPrevSeen; + + int m_irritation; + + int m_iLevel; + int m_iTeleport; + + EHANDLE m_hRecharger; + + EHANDLE m_hSphere[N_SPHERES]; + int m_iActiveSpheres; + + float m_flAdj; + + CSprite *m_pBall; + + char m_szRechargerTarget[64]; + char m_szDrawUse[64]; + char m_szTeleportUse[64]; + char m_szTeleportTouch[64]; + char m_szDeadUse[64]; + char m_szDeadTouch[64]; + + float m_flShootEnd; + float m_flShootTime; + + EHANDLE m_hFriend[3]; +}; + +LINK_ENTITY_TO_CLASS( monster_nihilanth, CNihilanth ); + +TYPEDESCRIPTION CNihilanth::m_SaveData[] = +{ + DEFINE_FIELD( CNihilanth, m_flForce, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanth, m_flNextPainSound, FIELD_TIME ), + DEFINE_FIELD( CNihilanth, m_velocity, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_avelocity, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_vecTarget, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_posTarget, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CNihilanth, m_vecDesired, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_posDesired, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CNihilanth, m_flMinZ, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanth, m_flMaxZ, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanth, m_vecGoal, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanth, m_flLastSeen, FIELD_TIME ), + DEFINE_FIELD( CNihilanth, m_flPrevSeen, FIELD_TIME ), + DEFINE_FIELD( CNihilanth, m_irritation, FIELD_INTEGER ), + DEFINE_FIELD( CNihilanth, m_iLevel, FIELD_INTEGER ), + DEFINE_FIELD( CNihilanth, m_iTeleport, FIELD_INTEGER ), + DEFINE_FIELD( CNihilanth, m_hRecharger, FIELD_EHANDLE ), + DEFINE_ARRAY( CNihilanth, m_hSphere, FIELD_EHANDLE, N_SPHERES ), + DEFINE_FIELD( CNihilanth, m_iActiveSpheres, FIELD_INTEGER ), + DEFINE_FIELD( CNihilanth, m_flAdj, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanth, m_pBall, FIELD_CLASSPTR ), + DEFINE_ARRAY( CNihilanth, m_szRechargerTarget, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szDrawUse, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szTeleportUse, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szTeleportTouch, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szDeadUse, FIELD_CHARACTER, 64 ), + DEFINE_ARRAY( CNihilanth, m_szDeadTouch, FIELD_CHARACTER, 64 ), + DEFINE_FIELD( CNihilanth, m_flShootEnd, FIELD_TIME ), + DEFINE_FIELD( CNihilanth, m_flShootTime, FIELD_TIME ), + DEFINE_ARRAY( CNihilanth, m_hFriend, FIELD_EHANDLE, 3 ), +}; + +IMPLEMENT_SAVERESTORE( CNihilanth, CBaseMonster ); + +class CNihilanthHVR : public CBaseMonster +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + + void CircleInit( CBaseEntity *pTarget ); + void AbsorbInit( void ); + void TeleportInit( CNihilanth *pOwner, CBaseEntity *pEnemy, CBaseEntity *pTarget, CBaseEntity *pTouch ); + void GreenBallInit( void ); + void ZapInit( CBaseEntity *pEnemy ); + + void EXPORT HoverThink( void ); + BOOL CircleTarget( Vector vecTarget ); + void EXPORT DissipateThink( void ); + + void EXPORT ZapThink( void ); + void EXPORT TeleportThink( void ); + void EXPORT TeleportTouch( CBaseEntity *pOther ); + + void EXPORT RemoveTouch( CBaseEntity *pOther ); + void EXPORT BounceTouch( CBaseEntity *pOther ); + void EXPORT ZapTouch( CBaseEntity *pOther ); + + CBaseEntity *RandomClassname( const char *szName ); + + // void EXPORT SphereUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void MovetoTarget( Vector vecTarget ); + virtual void Crawl( void ); + + void Zap( void ); + void Teleport( void ); + + float m_flIdealVel; + Vector m_vecIdeal; + CNihilanth *m_pNihilanth; + EHANDLE m_hTouch; + int m_nFrames; +}; + +LINK_ENTITY_TO_CLASS( nihilanth_energy_ball, CNihilanthHVR ); + + +TYPEDESCRIPTION CNihilanthHVR::m_SaveData[] = +{ + DEFINE_FIELD( CNihilanthHVR, m_flIdealVel, FIELD_FLOAT ), + DEFINE_FIELD( CNihilanthHVR, m_vecIdeal, FIELD_VECTOR ), + DEFINE_FIELD( CNihilanthHVR, m_pNihilanth, FIELD_CLASSPTR ), + DEFINE_FIELD( CNihilanthHVR, m_hTouch, FIELD_EHANDLE ), + DEFINE_FIELD( CNihilanthHVR, m_nFrames, FIELD_INTEGER ), +}; + + +IMPLEMENT_SAVERESTORE( CNihilanthHVR, CBaseMonster ); + + +//========================================================= +// Nihilanth, final Boss monster +//========================================================= + +const char *CNihilanth::pAttackSounds[] = +{ + "X/x_attack1.wav", + "X/x_attack2.wav", + "X/x_attack3.wav", +}; + +const char *CNihilanth::pBallSounds[] = +{ + "X/x_ballattack1.wav", +}; + +const char *CNihilanth::pShootSounds[] = +{ + "X/x_shoot1.wav", +}; + +const char *CNihilanth::pRechargeSounds[] = +{ + "X/x_recharge1.wav", + "X/x_recharge2.wav", + "X/x_recharge3.wav", +}; + +const char *CNihilanth::pLaughSounds[] = +{ + "X/x_laugh1.wav", + "X/x_laugh2.wav", +}; + +const char *CNihilanth::pPainSounds[] = +{ + "X/x_pain1.wav", + "X/x_pain2.wav", +}; + +const char *CNihilanth::pDeathSounds[] = +{ + "X/x_die1.wav", +}; + + +void CNihilanth :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(edict(), "models/nihilanth.mdl"); + // UTIL_SetSize(pev, Vector( -300, -300, 0), Vector(300, 300, 512)); + UTIL_SetSize(pev, Vector( -32, -32, 0), Vector(32, 32, 64)); + UTIL_SetOrigin( pev, pev->origin ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_AIM; + pev->health = gSkillData.nihilanthHealth; + pev->view_ofs = Vector( 0, 0, 300 ); + + m_flFieldOfView = -1; // 360 degrees + + pev->sequence = 0; + ResetSequenceInfo( ); + + InitBoneControllers(); + + SetThink( StartupThink ); + pev->nextthink = gpGlobals->time + 0.1; + + m_vecDesired = Vector( 1, 0, 0 ); + m_posDesired = Vector( pev->origin.x, pev->origin.y, 512 ); + + m_iLevel = 1; + m_iTeleport = 1; + + if (m_szRechargerTarget[0] == '\0') strcpy( m_szRechargerTarget, "n_recharger" ); + if (m_szDrawUse[0] == '\0') strcpy( m_szDrawUse, "n_draw" ); + if (m_szTeleportUse[0] == '\0') strcpy( m_szTeleportUse, "n_leaving" ); + if (m_szTeleportTouch[0] == '\0') strcpy( m_szTeleportTouch, "n_teleport" ); + if (m_szDeadUse[0] == '\0') strcpy( m_szDeadUse, "n_dead" ); + if (m_szDeadTouch[0] == '\0') strcpy( m_szDeadTouch, "n_ending" ); + + // near death + /* + m_iTeleport = 10; + m_iLevel = 10; + m_irritation = 2; + pev->health = 100; + */ +} + + +void CNihilanth::Precache( void ) +{ + PRECACHE_MODEL("models/nihilanth.mdl"); + PRECACHE_MODEL("sprites/lgtning.spr"); + UTIL_PrecacheOther( "nihilanth_energy_ball" ); + UTIL_PrecacheOther( "monster_alien_controller" ); + UTIL_PrecacheOther( "monster_alien_slave" ); + + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pBallSounds ); + PRECACHE_SOUND_ARRAY( pShootSounds ); + PRECACHE_SOUND_ARRAY( pRechargeSounds ); + PRECACHE_SOUND_ARRAY( pLaughSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); + PRECACHE_SOUND_ARRAY( pDeathSounds ); + PRECACHE_SOUND("debris/beamstart7.wav"); +} + + + +void CNihilanth :: PainSound( void ) +{ + if (m_flNextPainSound > gpGlobals->time) + return; + + m_flNextPainSound = gpGlobals->time + RANDOM_FLOAT( 2, 5 ); + + if (pev->health > gSkillData.nihilanthHealth / 2) + { + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pLaughSounds ), 1.0, 0.2 ); + } + else if (m_irritation >= 2) + { + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pPainSounds ), 1.0, 0.2 ); + } +} + +void CNihilanth :: DeathSound( void ) +{ + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pDeathSounds ), 1.0, 0.1 ); +} + + +void CNihilanth::NullThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.5; +} + + +void CNihilanth::StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( HuntThink ); + pev->nextthink = gpGlobals->time + 0.1; + SetUse( CommandUse ); +} + + +void CNihilanth::StartupThink( void ) +{ + m_irritation = 0; + m_flAdj = 512; + + CBaseEntity *pEntity; + + pEntity = UTIL_FindEntityByTargetname( NULL, "n_min"); + if (pEntity) + m_flMinZ = pEntity->pev->origin.z; + else + m_flMinZ = -4096; + + pEntity = UTIL_FindEntityByTargetname( NULL, "n_max"); + if (pEntity) + m_flMaxZ = pEntity->pev->origin.z; + else + m_flMaxZ = 4096; + + m_hRecharger = this; + for (int i = 0; i < N_SPHERES; i++) + { + EmitSphere( ); + } + m_hRecharger = NULL; + + SetThink( HuntThink); + SetUse( CommandUse ); + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CNihilanth :: Killed( entvars_t *pevAttacker, int iGib ) +{ + CBaseMonster::Killed( pevAttacker, iGib ); +} + +void CNihilanth :: DyingThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + DispatchAnimEvents( ); + StudioFrameAdvance( ); + + if (pev->deadflag == DEAD_NO) + { + DeathSound( ); + pev->deadflag = DEAD_DYING; + + m_posDesired.z = m_flMaxZ; + } + + if (pev->deadflag == DEAD_DYING) + { + Flight( ); + + if (fabs( pev->origin.z - m_flMaxZ ) < 16) + { + pev->velocity = Vector( 0, 0, 0 ); + FireTargets( m_szDeadUse, this, this, USE_ON, 1.0 ); + pev->deadflag = DEAD_DEAD; + } + } + + if (m_fSequenceFinished) + { + pev->avelocity.y += RANDOM_FLOAT( -100, 100 ); + if (pev->avelocity.y < -100) + pev->avelocity.y = -100; + if (pev->avelocity.y > 100) + pev->avelocity.y = 100; + + pev->sequence = LookupSequence( "die1" ); + } + + if (m_pBall) + { + if (m_pBall->pev->renderamt > 0) + { + m_pBall->pev->renderamt = max( 0, m_pBall->pev->renderamt - 2); + } + else + { + UTIL_Remove( m_pBall ); + m_pBall = NULL; + } + } + + Vector vecDir, vecSrc, vecAngles; + + UTIL_MakeAimVectors( pev->angles ); + int iAttachment = RANDOM_LONG( 1, 4 ); + + do { + vecDir = Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 )); + } while (DotProduct( vecDir, vecDir) > 1.0); + + switch( RANDOM_LONG( 1, 4 )) + { + case 1: // head + vecDir.z = fabs( vecDir.z ) * 0.5; + vecDir = vecDir + 2 * gpGlobals->v_up; + break; + case 2: // eyes + if (DotProduct( vecDir, gpGlobals->v_forward ) < 0) + vecDir = vecDir * -1; + + vecDir = vecDir + 2 * gpGlobals->v_forward; + break; + case 3: // left hand + if (DotProduct( vecDir, gpGlobals->v_right ) > 0) + vecDir = vecDir * -1; + vecDir = vecDir - 2 * gpGlobals->v_right; + break; + case 4: // right hand + if (DotProduct( vecDir, gpGlobals->v_right ) < 0) + vecDir = vecDir * -1; + vecDir = vecDir + 2 * gpGlobals->v_right; + break; + } + + GetAttachment( iAttachment - 1, vecSrc, vecAngles ); + + TraceResult tr; + + UTIL_TraceLine( vecSrc, vecSrc + vecDir * 4096, ignore_monsters, ENT(pev), &tr ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() + 0x1000 * iAttachment ); + WRITE_COORD( tr.vecEndPos.x); + WRITE_COORD( tr.vecEndPos.y); + WRITE_COORD( tr.vecEndPos.z); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 5 ); // life + WRITE_BYTE( 100 ); // width + WRITE_BYTE( 120 ); // noise + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 255); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + GetAttachment( 0, vecSrc, vecAngles ); + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = Vector ( RANDOM_FLOAT( -0.7, 0.7 ), RANDOM_FLOAT( -0.7, 0.7 ), 1.0 ) * 600.0; + pEntity->GreenBallInit( ); + + return; +} + + + +void CNihilanth::CrashTouch( CBaseEntity *pOther ) +{ + // only crash if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + SetTouch( NULL ); + pev->nextthink = gpGlobals->time; + } +} + + + +void CNihilanth :: GibMonster( void ) +{ + // EMIT_SOUND_DYN(edict(), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200); +} + + + +void CNihilanth :: FloatSequence( void ) +{ + if (m_irritation >= 2) + { + pev->sequence = LookupSequence( "float_open" ); + } + else if (m_avelocity.y > 30) + { + pev->sequence = LookupSequence( "walk_r" ); + } + else if (m_avelocity.y < -30) + { + pev->sequence = LookupSequence( "walk_l" ); + } + else if (m_velocity.z > 30) + { + pev->sequence = LookupSequence( "walk_u" ); + } + else if (m_velocity.z < -30) + { + pev->sequence = LookupSequence( "walk_d" ); + } + else + { + pev->sequence = LookupSequence( "float" ); + } +} + + +void CNihilanth :: ShootBalls( void ) +{ + if (m_flShootEnd > gpGlobals->time) + { + Vector vecHand, vecAngle; + + while (m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->time) + { + if (m_hEnemy != NULL) + { + Vector vecSrc, vecDir; + CNihilanthHVR *pEntity; + + GetAttachment( 2, vecHand, vecAngle ); + vecSrc = vecHand + pev->velocity * (m_flShootTime - gpGlobals->time); + // vecDir = (m_posTarget - vecSrc).Normalize( ); + vecDir = (m_posTarget - pev->origin).Normalize( ); + vecSrc = vecSrc + vecDir * (gpGlobals->time - m_flShootTime); + pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = vecDir * 200.0; + pEntity->ZapInit( m_hEnemy ); + + GetAttachment( 3, vecHand, vecAngle ); + vecSrc = vecHand + pev->velocity * (m_flShootTime - gpGlobals->time); + // vecDir = (m_posTarget - vecSrc).Normalize( ); + vecDir = (m_posTarget - pev->origin).Normalize( ); + vecSrc = vecSrc + vecDir * (gpGlobals->time - m_flShootTime); + pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = vecDir * 200.0; + pEntity->ZapInit( m_hEnemy ); + } + m_flShootTime += 0.2; + } + } +} + + +void CNihilanth :: MakeFriend( Vector vecStart ) +{ + int i; + + for (i = 0; i < 3; i++) + { + if (m_hFriend[i] != NULL && !m_hFriend[i]->IsAlive()) + { + if (pev->rendermode == kRenderNormal) // don't do it if they are already fading + m_hFriend[i]->MyMonsterPointer()->FadeMonster( ); + m_hFriend[i] = NULL; + } + + if (m_hFriend[i] == NULL) + { + if (RANDOM_LONG(0, 1) == 0) + { + int iNode = WorldGraph.FindNearestNode ( vecStart, bits_NODE_AIR ); + if (iNode != NO_NODE) + { + CNode &node = WorldGraph.Node( iNode ); + TraceResult tr; + UTIL_TraceHull( node.m_vecOrigin + Vector( 0, 0, 32 ), node.m_vecOrigin + Vector( 0, 0, 32 ), dont_ignore_monsters, large_hull, NULL, &tr ); + if (tr.fStartSolid == 0) + m_hFriend[i] = Create("monster_alien_controller", node.m_vecOrigin, pev->angles ); + } + } + else + { + int iNode = WorldGraph.FindNearestNode ( vecStart, bits_NODE_LAND | bits_NODE_WATER ); + if (iNode != NO_NODE) + { + CNode &node = WorldGraph.Node( iNode ); + TraceResult tr; + UTIL_TraceHull( node.m_vecOrigin + Vector( 0, 0, 36 ), node.m_vecOrigin + Vector( 0, 0, 36 ), dont_ignore_monsters, human_hull, NULL, &tr ); + if (tr.fStartSolid == 0) + m_hFriend[i] = Create("monster_alien_slave", node.m_vecOrigin, pev->angles ); + } + } + if (m_hFriend[i] != NULL) + { + EMIT_SOUND( m_hFriend[i]->edict(), CHAN_WEAPON, "debris/beamstart7.wav", 1.0, ATTN_NORM ); + } + + return; + } + } +} + + +void CNihilanth :: NextActivity( ) +{ + UTIL_MakeAimVectors( pev->angles ); + + if (m_irritation >= 2) + { + if (m_pBall == NULL) + { + m_pBall = CSprite::SpriteCreate( "sprites/tele1.spr", pev->origin, TRUE ); + if (m_pBall) + { + m_pBall->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_pBall->SetAttachment( edict(), 1 ); + m_pBall->SetScale( 4.0 ); + m_pBall->pev->framerate = 10.0; + m_pBall->TurnOn( ); + } + } + + if (m_pBall) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 200 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + } + } + + if ((pev->health < gSkillData.nihilanthHealth / 2 || m_iActiveSpheres < N_SPHERES / 2) && m_hRecharger == NULL && m_iLevel <= 9) + { + char szName[64]; + + CBaseEntity *pEnt = NULL; + CBaseEntity *pRecharger = NULL; + float flDist = 8192; + + sprintf(szName, "%s%d", m_szRechargerTarget, m_iLevel ); + + while ((pEnt = UTIL_FindEntityByTargetname( pEnt, szName )) != NULL) + { + float flLocal = (pEnt->pev->origin - pev->origin).Length(); + if (flLocal < flDist) + { + flDist = flLocal; + pRecharger = pEnt; + } + } + + if (pRecharger) + { + m_hRecharger = pRecharger; + m_posDesired = Vector( pev->origin.x, pev->origin.y, pRecharger->pev->origin.z ); + m_vecDesired = (pRecharger->pev->origin - m_posDesired).Normalize( ); + m_vecDesired.z = 0; + m_vecDesired = m_vecDesired.Normalize(); + } + else + { + m_hRecharger = NULL; + ALERT( at_aiconsole, "nihilanth can't find %s\n", szName ); + m_iLevel++; + if (m_iLevel > 9) + m_irritation = 2; + } + } + + float flDist = (m_posDesired - pev->origin).Length(); + float flDot = DotProduct( m_vecDesired, gpGlobals->v_forward ); + + if (m_hRecharger != NULL) + { + // at we at power up yet? + if (flDist < 128.0) + { + int iseq = LookupSequence( "recharge" ); + + if (iseq != pev->sequence) + { + char szText[64]; + + sprintf( szText, "%s%d", m_szDrawUse, m_iLevel ); + FireTargets( szText, this, this, USE_ON, 1.0 ); + + ALERT( at_console, "fireing %s\n", szText ); + } + pev->sequence = LookupSequence( "recharge" ); + } + else + { + FloatSequence( ); + } + return; + } + + if (m_hEnemy != NULL && !m_hEnemy->IsAlive()) + { + m_hEnemy = NULL; + } + + if (m_flLastSeen + 15 < gpGlobals->time) + { + m_hEnemy = NULL; + } + + if (m_hEnemy == NULL) + { + Look( 4096 ); + m_hEnemy = BestVisibleEnemy( ); + } + + if (m_hEnemy != NULL && m_irritation != 0) + { + if (m_flLastSeen + 5 > gpGlobals->time && flDist < 256 && flDot > 0) + { + if (m_irritation >= 2 && pev->health < gSkillData.nihilanthHealth / 2.0) + { + pev->sequence = LookupSequence( "attack1_open" ); + } + else + { + if (RANDOM_LONG(0, 1 ) == 0) + { + pev->sequence = LookupSequence( "attack1" ); // zap + } + else + { + char szText[64]; + + sprintf( szText, "%s%d", m_szTeleportTouch, m_iTeleport ); + CBaseEntity *pTouch = UTIL_FindEntityByTargetname( NULL, szText ); + + sprintf( szText, "%s%d", m_szTeleportUse, m_iTeleport ); + CBaseEntity *pTrigger = UTIL_FindEntityByTargetname( NULL, szText ); + + if (pTrigger != NULL || pTouch != NULL) + { + pev->sequence = LookupSequence( "attack2" ); // teleport + } + else + { + m_iTeleport++; + pev->sequence = LookupSequence( "attack1" ); // zap + } + } + } + return; + } + } + + FloatSequence( ); +} + +void CNihilanth :: HuntThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + DispatchAnimEvents( ); + StudioFrameAdvance( ); + + ShootBalls( ); + + // if dead, force cancelation of current animation + if (pev->health <= 0) + { + SetThink( DyingThink ); + m_fSequenceFinished = TRUE; + return; + } + + // ALERT( at_console, "health %.0f\n", pev->health ); + + // if damaged, try to abosorb some spheres + if (pev->health < gSkillData.nihilanthHealth && AbsorbSphere( )) + { + pev->health += gSkillData.nihilanthHealth / N_SPHERES; + } + + // get new sequence + if (m_fSequenceFinished) + { + // if (!m_fSequenceLoops) + pev->frame = 0; + NextActivity( ); + ResetSequenceInfo( ); + pev->framerate = 2.0 - 1.0 * (pev->health / gSkillData.nihilanthHealth); + } + + // look for current enemy + if (m_hEnemy != NULL && m_hRecharger == NULL) + { + if (FVisible( m_hEnemy )) + { + if (m_flLastSeen < gpGlobals->time - 5) + m_flPrevSeen = gpGlobals->time; + m_flLastSeen = gpGlobals->time; + m_posTarget = m_hEnemy->pev->origin; + m_vecTarget = (m_posTarget - pev->origin).Normalize(); + m_vecDesired = m_vecTarget; + m_posDesired = Vector( pev->origin.x, pev->origin.y, m_posTarget.z + m_flAdj ); + } + else + { + m_flAdj = min( m_flAdj + 10, 1000 ); + } + } + + // don't go too high + if (m_posDesired.z > m_flMaxZ) + m_posDesired.z = m_flMaxZ; + + // don't go too low + if (m_posDesired.z < m_flMinZ) + m_posDesired.z = m_flMinZ; + + Flight( ); +} + + + +void CNihilanth :: Flight( void ) +{ + // estimate where I'll be facing in one seconds + UTIL_MakeAimVectors( pev->angles + m_avelocity ); + // Vector vecEst1 = pev->origin + m_velocity + gpGlobals->v_up * m_flForce - Vector( 0, 0, 384 ); + // float flSide = DotProduct( m_posDesired - vecEst1, gpGlobals->v_right ); + + float flSide = DotProduct( m_vecDesired, gpGlobals->v_right ); + + if (flSide < 0) + { + if (m_avelocity.y < 180) + { + m_avelocity.y += 6; // 9 * (3.0/2.0); + } + } + else + { + if (m_avelocity.y > -180) + { + m_avelocity.y -= 6; // 9 * (3.0/2.0); + } + } + m_avelocity.y *= 0.98; + + // estimate where I'll be in two seconds + Vector vecEst = pev->origin + m_velocity * 2.0 + gpGlobals->v_up * m_flForce * 20; + + // add immediate force + UTIL_MakeAimVectors( pev->angles ); + m_velocity.x += gpGlobals->v_up.x * m_flForce; + m_velocity.y += gpGlobals->v_up.y * m_flForce; + m_velocity.z += gpGlobals->v_up.z * m_flForce; + + + float flSpeed = m_velocity.Length(); + float flDir = DotProduct( Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, 0 ), Vector( m_velocity.x, m_velocity.y, 0 ) ); + if (flDir < 0) + flSpeed = -flSpeed; + + float flDist = DotProduct( m_posDesired - vecEst, gpGlobals->v_forward ); + + // sideways drag + m_velocity.x = m_velocity.x * (1.0 - fabs( gpGlobals->v_right.x ) * 0.05); + m_velocity.y = m_velocity.y * (1.0 - fabs( gpGlobals->v_right.y ) * 0.05); + m_velocity.z = m_velocity.z * (1.0 - fabs( gpGlobals->v_right.z ) * 0.05); + + // general drag + m_velocity = m_velocity * 0.995; + + // apply power to stay correct height + if (m_flForce < 100 && vecEst.z < m_posDesired.z) + { + m_flForce += 10; + } + else if (m_flForce > -100 && vecEst.z > m_posDesired.z) + { + if (vecEst.z > m_posDesired.z) + m_flForce -= 10; + } + + UTIL_SetOrigin( pev, pev->origin + m_velocity * 0.1 ); + pev->angles = pev->angles + m_avelocity * 0.1; + + // ALERT( at_console, "%5.0f %5.0f : %4.0f : %3.0f : %2.0f\n", m_posDesired.z, pev->origin.z, m_velocity.z, m_avelocity.y, m_flForce ); +} + + +BOOL CNihilanth :: AbsorbSphere( void ) +{ + for (int i = 0; i < N_SPHERES; i++) + { + if (m_hSphere[i] != NULL) + { + CNihilanthHVR *pSphere = (CNihilanthHVR *)((CBaseEntity *)m_hSphere[i]); + pSphere->AbsorbInit( ); + m_hSphere[i] = NULL; + m_iActiveSpheres--; + return TRUE; + } + } + return FALSE; +} + + +BOOL CNihilanth :: EmitSphere( void ) +{ + m_iActiveSpheres = 0; + int empty = 0; + + for (int i = 0; i < N_SPHERES; i++) + { + if (m_hSphere[i] != NULL) + { + m_iActiveSpheres++; + } + else + { + empty = i; + } + } + + if (m_iActiveSpheres >= N_SPHERES) + return FALSE; + + Vector vecSrc = m_hRecharger->pev->origin; + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = pev->origin - vecSrc; + pEntity->CircleInit( this ); + + m_hSphere[empty] = pEntity; + return TRUE; +} + + +void CNihilanth :: TargetSphere( USE_TYPE useType, float value ) +{ + CBaseMonster *pSphere; + for (int i = 0; i < N_SPHERES; i++) + { + if (m_hSphere[i] != NULL) + { + pSphere = m_hSphere[i]->MyMonsterPointer(); + if (pSphere->m_hEnemy == NULL) + break; + } + } + if (i == N_SPHERES) + { + return; + } + + Vector vecSrc, vecAngles; + GetAttachment( 2, vecSrc, vecAngles ); + UTIL_SetOrigin( pSphere->pev, vecSrc ); + pSphere->Use( this, this, useType, value ); + pSphere->pev->velocity = m_vecDesired * RANDOM_FLOAT( 50, 100 ) + Vector( RANDOM_FLOAT( -50, 50 ), RANDOM_FLOAT( -50, 50 ), RANDOM_FLOAT( -50, 50 ) ); +} + + + +void CNihilanth :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 1: // shoot + break; + case 2: // zen + if (m_hEnemy != NULL) + { + if (RANDOM_LONG(0,4) == 0) + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pAttackSounds ), 1.0, 0.2 ); + + EMIT_SOUND( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY( pBallSounds ), 1.0, 0.2 ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x3000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x4000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + m_flShootTime = gpGlobals->time; + m_flShootEnd = gpGlobals->time + 1.0; + } + break; + case 3: // prayer + if (m_hEnemy != NULL) + { + char szText[32]; + + sprintf( szText, "%s%d", m_szTeleportTouch, m_iTeleport ); + CBaseEntity *pTouch = UTIL_FindEntityByTargetname( NULL, szText ); + + sprintf( szText, "%s%d", m_szTeleportUse, m_iTeleport ); + CBaseEntity *pTrigger = UTIL_FindEntityByTargetname( NULL, szText ); + + if (pTrigger != NULL || pTouch != NULL) + { + EMIT_SOUND( edict(), CHAN_VOICE, RANDOM_SOUND_ARRAY( pAttackSounds ), 1.0, 0.2 ); + + Vector vecSrc, vecAngles; + GetAttachment( 2, vecSrc, vecAngles ); + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = pev->origin - vecSrc; + pEntity->TeleportInit( this, m_hEnemy, pTrigger, pTouch ); + } + else + { + m_iTeleport++; // unexpected failure + + EMIT_SOUND( edict(), CHAN_WEAPON, RANDOM_SOUND_ARRAY( pBallSounds ), 1.0, 0.2 ); + + ALERT( at_aiconsole, "nihilanth can't target %s\n", szText ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x3000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x4000 ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + m_flShootTime = gpGlobals->time; + m_flShootEnd = gpGlobals->time + 1.0; + } + } + break; + case 4: // get a sphere + { + if (m_hRecharger != NULL) + { + if (!EmitSphere( )) + { + m_hRecharger = NULL; + } + } + } + break; + case 5: // start up sphere machine + { + EMIT_SOUND( edict(), CHAN_VOICE , RANDOM_SOUND_ARRAY( pRechargeSounds ), 1.0, 0.2 ); + } + break; + case 6: + if (m_hEnemy != NULL) + { + Vector vecSrc, vecAngles; + GetAttachment( 2, vecSrc, vecAngles ); + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = pev->origin - vecSrc; + pEntity->ZapInit( m_hEnemy ); + } + break; + case 7: + /* + Vector vecSrc, vecAngles; + GetAttachment( 0, vecSrc, vecAngles ); + CNihilanthHVR *pEntity = (CNihilanthHVR *)Create( "nihilanth_energy_ball", vecSrc, pev->angles, edict() ); + pEntity->pev->velocity = Vector ( RANDOM_FLOAT( -0.7, 0.7 ), RANDOM_FLOAT( -0.7, 0.7 ), 1.0 ) * 600.0; + pEntity->GreenBallInit( ); + */ + break; + } +} + + + +void CNihilanth::CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + switch (useType) + { + case USE_OFF: + { + CBaseEntity *pTouch = UTIL_FindEntityByTargetname( NULL, m_szDeadTouch ); + if ( pTouch && m_hEnemy != NULL ) + pTouch->Touch( m_hEnemy ); + } + break; + case USE_ON: + if (m_irritation == 0) + { + m_irritation = 1; + } + break; + case USE_SET: + break; + case USE_TOGGLE: + break; + } +} + + +int CNihilanth :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (pevInflictor->owner == edict()) + return 0; + + if (flDamage >= pev->health) + { + pev->health = 1; + if (m_irritation != 3) + return 0; + } + + PainSound( ); + + pev->health -= flDamage; + return 0; +} + + + +void CNihilanth::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if (m_irritation == 3) + m_irritation = 2; + + if (m_irritation == 2 && ptr->iHitgroup == 2 && flDamage > 2) + m_irritation = 3; + + if (m_irritation != 3) + { + Vector vecBlood = (ptr->vecEndPos - pev->origin).Normalize( ); + + UTIL_BloodStream( ptr->vecEndPos, vecBlood, BloodColor(), flDamage + (100 - 100 * (pev->health / gSkillData.nihilanthHealth))); + } + + // SpawnBlood(ptr->vecEndPos, BloodColor(), flDamage * 5.0);// a little surface blood. + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + + + +CBaseEntity *CNihilanth::RandomTargetname( const char *szName ) +{ + int total = 0; + + CBaseEntity *pEntity = NULL; + CBaseEntity *pNewEntity = NULL; + while ((pNewEntity = UTIL_FindEntityByTargetname( pNewEntity, szName )) != NULL) + { + total++; + if (RANDOM_LONG(0,total-1) < 1) + pEntity = pNewEntity; + } + return pEntity; +} + + + + + + + + + +//========================================================= +// Controller bouncy ball attack +//========================================================= + + + +void CNihilanthHVR :: Spawn( void ) +{ + Precache( ); + + pev->rendermode = kRenderTransAdd; + pev->renderamt = 255; + pev->scale = 3.0; +} + + +void CNihilanthHVR :: Precache( void ) +{ + PRECACHE_MODEL("sprites/flare6.spr"); + PRECACHE_MODEL("sprites/nhth1.spr"); + PRECACHE_MODEL("sprites/exit1.spr"); + PRECACHE_MODEL("sprites/tele1.spr"); + PRECACHE_MODEL("sprites/animglow01.spr"); + PRECACHE_MODEL("sprites/xspark4.spr"); + PRECACHE_MODEL("sprites/muzzleflash3.spr"); + PRECACHE_SOUND("debris/zap4.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); + PRECACHE_SOUND("x/x_teleattack1.wav"); +} + + + +void CNihilanthHVR :: CircleInit( CBaseEntity *pTarget ) +{ + pev->movetype = MOVETYPE_NOCLIP; + pev->solid = SOLID_NOT; + + // SET_MODEL(edict(), "sprites/flare6.spr"); + // pev->scale = 3.0; + // SET_MODEL(edict(), "sprites/xspark4.spr"); + SET_MODEL(edict(), "sprites/muzzleflash3.spr"); + pev->rendercolor.x = 255; + pev->rendercolor.y = 224; + pev->rendercolor.z = 192; + pev->scale = 2.0; + m_nFrames = 1; + pev->renderamt = 255; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( HoverThink ); + SetTouch( BounceTouch ); + pev->nextthink = gpGlobals->time + 0.1; + + m_hTargetEnt = pTarget; +} + + +CBaseEntity *CNihilanthHVR::RandomClassname( const char *szName ) +{ + int total = 0; + + CBaseEntity *pEntity = NULL; + CBaseEntity *pNewEntity = NULL; + while ((pNewEntity = UTIL_FindEntityByClassname( pNewEntity, szName )) != NULL) + { + total++; + if (RANDOM_LONG(0,total-1) < 1) + pEntity = pNewEntity; + } + return pEntity; +} + +void CNihilanthHVR :: HoverThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if (m_hTargetEnt != NULL) + { + CircleTarget( m_hTargetEnt->pev->origin + Vector( 0, 0, 16 * N_SCALE ) ); + } + else + { + UTIL_Remove( this ); + } + + + if (RANDOM_LONG( 0, 99 ) < 5) + { +/* + CBaseEntity *pOther = RandomClassname( STRING(pev->classname) ); + + if (pOther && pOther != this) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( this->entindex() ); + WRITE_SHORT( pOther->entindex() ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framestart + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 10 ); // life + WRITE_BYTE( 80 ); // width + WRITE_BYTE( 80 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 30 ); // speed + MESSAGE_END(); + } +*/ +/* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( this->entindex() ); + WRITE_SHORT( m_hTargetEnt->entindex() + 0x1000 ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framestart + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 10 ); // life + WRITE_BYTE( 80 ); // width + WRITE_BYTE( 80 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 30 ); // speed + MESSAGE_END(); +*/ + } + + pev->frame = ((int)pev->frame + 1) % m_nFrames; +} + + + + +void CNihilanthHVR :: ZapInit( CBaseEntity *pEnemy ) +{ + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(edict(), "sprites/nhth1.spr"); + + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->scale = 2.0; + + pev->velocity = (pEnemy->pev->origin - pev->origin).Normalize() * 200; + + m_hEnemy = pEnemy; + SetThink( ZapThink ); + SetTouch( ZapTouch ); + pev->nextthink = gpGlobals->time + 0.1; + + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, "debris/zap4.wav", 1, ATTN_NORM, 0, 100 ); +} + +void CNihilanthHVR :: ZapThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.05; + + // check world boundaries + if (m_hEnemy == NULL || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + SetTouch( NULL ); + UTIL_Remove( this ); + return; + } + + if (pev->velocity.Length() < 2000) + { + pev->velocity = pev->velocity * 1.2; + } + + + // MovetoTarget( m_hEnemy->Center( ) ); + + if ((m_hEnemy->Center() - pev->origin).Length() < 256) + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, m_hEnemy->Center(), dont_ignore_monsters, edict(), &tr ); + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + if (pEntity != NULL && pEntity->pev->takedamage) + { + ClearMultiDamage( ); + pEntity->TraceAttack( pev, gSkillData.nihilanthZap, pev->velocity, &tr, DMG_SHOCK ); + ApplyMultiDamage( pev, pev ); + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 20 ); // noise + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 196 ); // r, g, b + WRITE_BYTE( 255); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + + UTIL_EmitAmbientSound( edict(), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); + + SetTouch( NULL ); + UTIL_Remove( this ); + pev->nextthink = gpGlobals->time + 0.2; + return; + } + + pev->frame = (int)(pev->frame + 1) % 11; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 128 ); // radius + WRITE_BYTE( 128 ); // R + WRITE_BYTE( 128 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 128 ); // decay + MESSAGE_END(); + + // Crawl( ); +} + + +void CNihilanthHVR::ZapTouch( CBaseEntity *pOther ) +{ + UTIL_EmitAmbientSound( edict(), pev->origin, "weapons/electro4.wav", 1.0, ATTN_NORM, 0, RANDOM_LONG( 90, 95 ) ); + + RadiusDamage( pev, pev, 50, CLASS_NONE, DMG_SHOCK ); + pev->velocity = pev->velocity * 0; + + /* + for (int i = 0; i < 10; i++) + { + Crawl( ); + } + */ + + SetTouch( NULL ); + UTIL_Remove( this ); + pev->nextthink = gpGlobals->time + 0.2; +} + + + +void CNihilanthHVR :: TeleportInit( CNihilanth *pOwner, CBaseEntity *pEnemy, CBaseEntity *pTarget, CBaseEntity *pTouch ) +{ + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->velocity.z *= 0.2; + + SET_MODEL(edict(), "sprites/exit1.spr"); + + m_pNihilanth = pOwner; + m_hEnemy = pEnemy; + m_hTargetEnt = pTarget; + m_hTouch = pTouch; + + SetThink( TeleportThink ); + SetTouch( TeleportTouch ); + pev->nextthink = gpGlobals->time + 0.1; + + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, "x/x_teleattack1.wav", 1, 0.2, 0, 100 ); +} + + +void CNihilanthHVR :: GreenBallInit( ) +{ + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->scale = 1.0; + + SET_MODEL(edict(), "sprites/exit1.spr"); + + SetTouch( RemoveTouch ); +} + + +void CNihilanthHVR :: TeleportThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + // check world boundaries + if (m_hEnemy == NULL || !m_hEnemy->IsAlive() || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + STOP_SOUND(edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); + UTIL_Remove( this ); + return; + } + + if ((m_hEnemy->Center() - pev->origin).Length() < 128) + { + STOP_SOUND(edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); + UTIL_Remove( this ); + + if (m_hTargetEnt != NULL) + m_hTargetEnt->Use( m_hEnemy, m_hEnemy, USE_ON, 1.0 ); + + if ( m_hTouch != NULL && m_hEnemy != NULL ) + m_hTouch->Touch( m_hEnemy ); + } + else + { + MovetoTarget( m_hEnemy->Center( ) ); + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( 256 ); // radius + WRITE_BYTE( 0 ); // R + WRITE_BYTE( 255 ); // G + WRITE_BYTE( 0 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 256 ); // decay + MESSAGE_END(); + + pev->frame = (int)(pev->frame + 1) % 20; +} + + +void CNihilanthHVR :: AbsorbInit( void ) +{ + SetThink( DissipateThink ); + pev->renderamt = 255; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTS ); + WRITE_SHORT( this->entindex() ); + WRITE_SHORT( m_hTargetEnt->entindex() + 0x1000 ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framestart + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 50 ); // life + WRITE_BYTE( 80 ); // width + WRITE_BYTE( 80 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 30 ); // speed + MESSAGE_END(); +} + +void CNihilanthHVR::TeleportTouch( CBaseEntity *pOther ) +{ + CBaseEntity *pEnemy = m_hEnemy; + + if (pOther == pEnemy) + { + if (m_hTargetEnt != NULL) + m_hTargetEnt->Use( pEnemy, pEnemy, USE_ON, 1.0 ); + + if (m_hTouch != NULL && pEnemy != NULL ) + m_hTouch->Touch( pEnemy ); + } + else + { + m_pNihilanth->MakeFriend( pev->origin ); + } + + SetTouch( NULL ); + STOP_SOUND(edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); + UTIL_Remove( this ); +} + + +void CNihilanthHVR :: DissipateThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->scale > 5.0) + UTIL_Remove( this ); + + pev->renderamt -= 2; + pev->scale += 0.1; + + if (m_hTargetEnt != NULL) + { + CircleTarget( m_hTargetEnt->pev->origin + Vector( 0, 0, 4096 ) ); + } + else + { + UTIL_Remove( this ); + } + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( pev->renderamt ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 2 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); +} + + +BOOL CNihilanthHVR :: CircleTarget( Vector vecTarget ) +{ + BOOL fClose = FALSE; + + Vector vecDest = vecTarget; + Vector vecEst = pev->origin + pev->velocity * 0.5; + Vector vecSrc = pev->origin; + vecDest.z = 0; + vecEst.z = 0; + vecSrc.z = 0; + float d1 = (vecDest - vecSrc).Length() - 24 * N_SCALE; + float d2 = (vecDest - vecEst).Length() - 24 * N_SCALE; + + if (m_vecIdeal == Vector( 0, 0, 0 )) + { + m_vecIdeal = pev->velocity; + } + + if (d1 < 0 && d2 <= d1) + { + // ALERT( at_console, "too close\n"); + m_vecIdeal = m_vecIdeal - (vecDest - vecSrc).Normalize() * 50; + } + else if (d1 > 0 && d2 >= d1) + { + // ALERT( at_console, "too far\n"); + m_vecIdeal = m_vecIdeal + (vecDest - vecSrc).Normalize() * 50; + } + pev->avelocity.z = d1 * 20; + + if (d1 < 32) + { + fClose = TRUE; + } + + m_vecIdeal = m_vecIdeal + Vector( RANDOM_FLOAT( -2, 2 ), RANDOM_FLOAT( -2, 2 ), RANDOM_FLOAT( -2, 2 )); + m_vecIdeal = Vector( m_vecIdeal.x, m_vecIdeal.y, 0 ).Normalize( ) * 200 + /* + Vector( -m_vecIdeal.y, m_vecIdeal.x, 0 ).Normalize( ) * 32 */ + + Vector( 0, 0, m_vecIdeal.z ); + // m_vecIdeal = m_vecIdeal + Vector( -m_vecIdeal.y, m_vecIdeal.x, 0 ).Normalize( ) * 2; + + // move up/down + d1 = vecTarget.z - pev->origin.z; + if (d1 > 0 && m_vecIdeal.z < 200) + m_vecIdeal.z += 20; + else if (d1 < 0 && m_vecIdeal.z > -200) + m_vecIdeal.z -= 20; + + pev->velocity = m_vecIdeal; + + // ALERT( at_console, "%.0f %.0f %.0f\n", m_vecIdeal.x, m_vecIdeal.y, m_vecIdeal.z ); + return fClose; +} + + +void CNihilanthHVR :: MovetoTarget( Vector vecTarget ) +{ + if (m_vecIdeal == Vector( 0, 0, 0 )) + { + m_vecIdeal = pev->velocity; + } + + // accelerate + float flSpeed = m_vecIdeal.Length(); + if (flSpeed > 300) + { + m_vecIdeal = m_vecIdeal.Normalize( ) * 300; + } + m_vecIdeal = m_vecIdeal + (vecTarget - pev->origin).Normalize() * 300; + pev->velocity = m_vecIdeal; +} + + + + +void CNihilanthHVR :: Crawl( void ) +{ + + Vector vecAim = Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ) ).Normalize( ); + Vector vecPnt = pev->origin + pev->velocity * 0.2 + vecAim * 128; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( vecPnt.x); + WRITE_COORD( vecPnt.y); + WRITE_COORD( vecPnt.z); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 80 ); // noise + WRITE_BYTE( 64 ); // r, g, b + WRITE_BYTE( 128 ); // r, g, b + WRITE_BYTE( 255); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); +} + + +void CNihilanthHVR::RemoveTouch( CBaseEntity *pOther ) +{ + STOP_SOUND(edict(), CHAN_WEAPON, "x/x_teleattack1.wav" ); + UTIL_Remove( this ); +} + +void CNihilanthHVR::BounceTouch( CBaseEntity *pOther ) +{ + Vector vecDir = m_vecIdeal.Normalize( ); + + TraceResult tr = UTIL_GetGlobalTrace( ); + + float n = -DotProduct(tr.vecPlaneNormal, vecDir); + + vecDir = 2.0 * tr.vecPlaneNormal * n + vecDir; + + m_vecIdeal = vecDir * m_vecIdeal.Length(); +} + + + +#endif \ No newline at end of file diff --git a/dlls/nodes.cpp b/dlls/nodes.cpp new file mode 100644 index 0000000..6b01750 --- /dev/null +++ b/dlls/nodes.cpp @@ -0,0 +1,3640 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// nodes.cpp - AI node tree stuff. +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "nodes.h" +#include "animation.h" +#include "doors.h" + +#define HULL_STEP_SIZE 16// how far the test hull moves on each step +#define NODE_HEIGHT 8 // how high to lift nodes off the ground after we drop them all (make stair/ramp mapping easier) + +// to help eliminate node clutter by level designers, this is used to cap how many other nodes +// any given node is allowed to 'see' in the first stage of graph creation "LinkVisibleNodes()". +#define MAX_NODE_INITIAL_LINKS 128 +#define MAX_NODES 1024 + +extern DLL_GLOBAL edict_t *g_pBodyQueueHead; + +Vector VecBModelOrigin( entvars_t* pevBModel ); + +CGraph WorldGraph; + +LINK_ENTITY_TO_CLASS( info_node, CNodeEnt ); +LINK_ENTITY_TO_CLASS( info_node_air, CNodeEnt ); +#ifdef __linux__ +#include +#define CreateDirectory(p, n) mkdir(p, 0777) +#endif +//========================================================= +// CGraph - InitGraph - prepares the graph for use. Frees any +// memory currently in use by the world graph, NULLs +// all pointers, and zeros the node count. +//========================================================= +void CGraph :: InitGraph( void) +{ + + // Make the graph unavailable + // + m_fGraphPresent = FALSE; + m_fGraphPointersSet = FALSE; + m_fRoutingComplete = FALSE; + + // Free the link pool + // + if ( m_pLinkPool ) + { + free ( m_pLinkPool ); + m_pLinkPool = NULL; + } + + // Free the node info + // + if ( m_pNodes ) + { + free ( m_pNodes ); + m_pNodes = NULL; + } + + if ( m_di ) + { + free ( m_di ); + m_di = NULL; + } + + // Free the routing info. + // + if ( m_pRouteInfo ) + { + free ( m_pRouteInfo ); + m_pRouteInfo = NULL; + } + + if (m_pHashLinks) + { + free(m_pHashLinks); + m_pHashLinks = NULL; + } + + // Zero node and link counts + // + m_cNodes = 0; + m_cLinks = 0; + m_nRouteInfo = 0; + + m_iLastActiveIdleSearch = 0; + m_iLastCoverSearch = 0; +} + +//========================================================= +// CGraph - AllocNodes - temporary function that mallocs a +// reasonable number of nodes so we can build the path which +// will be saved to disk. +//========================================================= +int CGraph :: AllocNodes ( void ) +{ +// malloc all of the nodes + WorldGraph.m_pNodes = (CNode *)calloc ( sizeof ( CNode ), MAX_NODES ); + +// could not malloc space for all the nodes! + if ( !WorldGraph.m_pNodes ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d nodes!\n", WorldGraph.m_cNodes ); + return FALSE; + } + + return TRUE; +} + +//========================================================= +// CGraph - LinkEntForLink - sometimes the ent that blocks +// a path is a usable door, in which case the monster just +// needs to face the door and fire it. In other cases, the +// monster needs to operate a button or lever to get the +// door to open. This function will return a pointer to the +// button if the monster needs to hit a button to open the +// door, or returns a pointer to the door if the monster +// need only use the door. +// +// pNode is the node the monster will be standing on when it +// will need to stop and trigger the ent. +//========================================================= +entvars_t* CGraph :: LinkEntForLink ( CLink *pLink, CNode *pNode ) +{ + edict_t *pentSearch; + edict_t *pentTrigger; + entvars_t *pevTrigger; + entvars_t *pevLinkEnt; + TraceResult tr; + + pevLinkEnt = pLink->m_pLinkEnt; + if ( !pevLinkEnt ) + return NULL; + + pentSearch = NULL;// start search at the top of the ent list. + + if ( FClassnameIs ( pevLinkEnt, "func_door" ) || FClassnameIs ( pevLinkEnt, "func_door_rotating" ) ) + { + + ///!!!UNDONE - check for TOGGLE or STAY open doors here. If a door is in the way, and is + // TOGGLE or STAY OPEN, even monsters that can't open doors can go that way. + + if ( ( pevLinkEnt->spawnflags & SF_DOOR_USE_ONLY ) ) + {// door is use only, so the door is all the monster has to worry about + return pevLinkEnt; + } + + while ( 1 ) + { + pentTrigger = FIND_ENTITY_BY_TARGET ( pentSearch, STRING( pevLinkEnt->targetname ) );// find the button or trigger + + if ( FNullEnt( pentTrigger ) ) + {// no trigger found + + // right now this is a problem among auto-open doors, or any door that opens through the use + // of a trigger brush. Trigger brushes have no models, and don't show up in searches. Just allow + // monsters to open these sorts of doors for now. + return pevLinkEnt; + } + + pentSearch = pentTrigger; + pevTrigger = VARS( pentTrigger ); + + if ( FClassnameIs(pevTrigger, "func_button") || FClassnameIs(pevTrigger, "func_rot_button" ) ) + {// only buttons are handled right now. + + // trace from the node to the trigger, make sure it's one we can see from the node. + // !!!HACKHACK Use bodyqueue here cause there are no ents we really wish to ignore! + UTIL_TraceLine ( pNode->m_vecOrigin, VecBModelOrigin( pevTrigger ), ignore_monsters, g_pBodyQueueHead, &tr ); + + + if ( VARS(tr.pHit) == pevTrigger ) + {// good to go! + return VARS( tr.pHit ); + } + } + } + } + else + { + ALERT ( at_aiconsole, "Unsupported PathEnt:\n'%s'\n", STRING ( pevLinkEnt->classname ) ); + return NULL; + } +} + +//========================================================= +// CGraph - HandleLinkEnt - a brush ent is between two +// nodes that would otherwise be able to see each other. +// Given the monster's capability, determine whether +// or not the monster can go this way. +//========================================================= +int CGraph :: HandleLinkEnt ( int iNode, entvars_t *pevLinkEnt, int afCapMask, NODEQUERY queryType ) +{ + edict_t *pentWorld; + CBaseEntity *pDoor; + TraceResult tr; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + if ( FNullEnt ( pevLinkEnt ) ) + { + ALERT ( at_aiconsole, "dead path ent!\n" ); + return TRUE; + } + pentWorld = NULL; + +// func_door + if ( FClassnameIs( pevLinkEnt, "func_door" ) || FClassnameIs( pevLinkEnt, "func_door_rotating" ) ) + {// ent is a door. + + pDoor = ( CBaseEntity::Instance( pevLinkEnt ) ); + + if ( ( pevLinkEnt->spawnflags & SF_DOOR_USE_ONLY ) ) + {// door is use only. + + if ( ( afCapMask & bits_CAP_OPEN_DOORS ) ) + {// let monster right through if he can open doors + return TRUE; + } + else + { + // monster should try for it if the door is open and looks as if it will stay that way + if ( pDoor->GetToggleState()== TS_AT_TOP && ( pevLinkEnt->spawnflags & SF_DOOR_NO_AUTO_RETURN ) ) + { + return TRUE; + } + + return FALSE; + } + } + else + {// door must be opened with a button or trigger field. + + // monster should try for it if the door is open and looks as if it will stay that way + if ( pDoor->GetToggleState() == TS_AT_TOP && ( pevLinkEnt->spawnflags & SF_DOOR_NO_AUTO_RETURN ) ) + { + return TRUE; + } + if ( ( afCapMask & bits_CAP_OPEN_DOORS ) ) + { + if ( !( pevLinkEnt->spawnflags & SF_DOOR_NOMONSTERS ) || queryType == NODEGRAPH_STATIC ) + return TRUE; + } + + return FALSE; + } + } +// func_breakable + else if ( FClassnameIs( pevLinkEnt, "func_breakable" ) && queryType == NODEGRAPH_STATIC ) + { + return TRUE; + } + else + { + ALERT ( at_aiconsole, "Unhandled Ent in Path %s\n", STRING( pevLinkEnt->classname ) ); + return FALSE; + } + + return FALSE; +} + +#if 0 +//========================================================= +// FindNearestLink - finds the connection (line) nearest +// the given point. Returns FALSE if fails, or TRUE if it +// has stuffed the index into the nearest link pool connection +// into the passed int pointer, and a BOOL telling whether or +// not the point is along the line into the passed BOOL pointer. +//========================================================= +int CGraph :: FindNearestLink ( const Vector &vecTestPoint, int *piNearestLink, BOOL *pfAlongLine ) +{ + int i, j;// loops + + int iNearestLink;// index into the link pool, this is the nearest node at any time. + float flMinDist;// the distance of of the nearest case so far + float flDistToLine;// the distance of the current test case + + BOOL fCurrentAlongLine; + BOOL fSuccess; + + //float flConstant;// line constant + Vector vecSpot1, vecSpot2; + Vector2D vec2Spot1, vec2Spot2, vec2TestPoint; + Vector2D vec2Normal;// line normal + Vector2D vec2Line; + + TraceResult tr; + + iNearestLink = -1;// prepare for failure + fSuccess = FALSE; + + flMinDist = 9999;// anything will be closer than this + +// go through all of the nodes, and each node's connections + int cSkip = 0;// how many links proper pairing allowed us to skip + int cChecked = 0;// how many links were checked + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + vecSpot1 = m_pNodes[ i ].m_vecOrigin; + + if ( m_pNodes[ i ].m_cNumLinks <= 0 ) + {// this shouldn't happen! + ALERT ( at_aiconsole, "**Node %d has no links\n", i ); + continue; + } + + for ( j = 0 ; j < m_pNodes[ i ].m_cNumLinks ; j++ ) + { + /* + !!!This optimization only works when the node graph consists of properly linked pairs. + if ( INodeLink ( i, j ) <= i ) + { + // since we're going through the nodes in order, don't check + // any connections whose second node is lower in the list + // than the node we're currently working with. This eliminates + // redundant checks. + cSkip++; + continue; + } + */ + + vecSpot2 = PNodeLink ( i, j )->m_vecOrigin; + + // these values need a little attention now and then, or sometimes ramps cause trouble. + if ( fabs ( vecSpot1.z - vecTestPoint.z ) > 48 && fabs ( vecSpot2.z - vecTestPoint.z ) > 48 ) + { + // if both endpoints of the line are 32 units or more above or below the monster, + // the monster won't be able to get to them, so we do a bit of trivial rejection here. + // this may change if monsters are allowed to jump down. + // + // !!!LATER: some kind of clever X/Y hashing should be used here, too + continue; + } + +// now we have two endpoints for a line segment that we've not already checked. +// since all lines that make it this far are within -/+ 32 units of the test point's +// Z Plane, we can get away with doing the point->line check in 2d. + + cChecked++; + + vec2Spot1 = vecSpot1.Make2D(); + vec2Spot2 = vecSpot2.Make2D(); + vec2TestPoint = vecTestPoint.Make2D(); + + // get the line normal. + vec2Line = ( vec2Spot1 - vec2Spot2 ).Normalize(); + vec2Normal.x = -vec2Line.y; + vec2Normal.y = vec2Line.x; + + if ( DotProduct ( vec2Line, ( vec2TestPoint - vec2Spot1 ) ) > 0 ) + {// point outside of line + flDistToLine = ( vec2TestPoint - vec2Spot1 ).Length(); + fCurrentAlongLine = FALSE; + } + else if ( DotProduct ( vec2Line, ( vec2TestPoint - vec2Spot2 ) ) < 0 ) + {// point outside of line + flDistToLine = ( vec2TestPoint - vec2Spot2 ).Length(); + fCurrentAlongLine = FALSE; + } + else + {// point inside line + flDistToLine = fabs( DotProduct ( vec2TestPoint - vec2Spot2, vec2Normal ) ); + fCurrentAlongLine = TRUE; + } + + if ( flDistToLine < flMinDist ) + {// just found a line nearer than any other so far + + UTIL_TraceLine ( vecTestPoint, SourceNode( i, j ).m_vecOrigin, ignore_monsters, g_pBodyQueueHead, &tr ); + + if ( tr.flFraction != 1.0 ) + {// crap. can't see the first node of this link, try to see the other + + UTIL_TraceLine ( vecTestPoint, DestNode( i, j ).m_vecOrigin, ignore_monsters, g_pBodyQueueHead, &tr ); + if ( tr.flFraction != 1.0 ) + {// can't use this link, cause can't see either node! + continue; + } + + } + + fSuccess = TRUE;// we know there will be something to return. + flMinDist = flDistToLine; + iNearestLink = m_pNodes [ i ].m_iFirstLink + j; + *piNearestLink = m_pNodes[ i ].m_iFirstLink + j; + *pfAlongLine = fCurrentAlongLine; + } + } + } + +/* + if ( fSuccess ) + { + WRITE_BYTE(MSG_BROADCAST, SVC_TEMPENTITY); + WRITE_BYTE(MSG_BROADCAST, TE_SHOWLINE); + + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.x ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.y ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iSrcNode ].m_vecOrigin.z + NODE_HEIGHT); + + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.x ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.y ); + WRITE_COORD(MSG_BROADCAST, m_pNodes[ m_pLinkPool[ iNearestLink ].m_iDestNode ].m_vecOrigin.z + NODE_HEIGHT); + } +*/ + + ALERT ( at_aiconsole, "%d Checked\n", cChecked ); + return fSuccess; +} + +#endif + +int CGraph::HullIndex( const CBaseEntity *pEntity ) +{ + if ( pEntity->pev->movetype == MOVETYPE_FLY) + return NODE_FLY_HULL; + + if ( pEntity->pev->mins == Vector( -12, -12, 0 ) ) + return NODE_SMALL_HULL; + else if ( pEntity->pev->mins == VEC_HUMAN_HULL_MIN ) + return NODE_HUMAN_HULL; + else if ( pEntity->pev->mins == Vector ( -32, -32, 0 ) ) + return NODE_LARGE_HULL; + +// ALERT ( at_aiconsole, "Unknown Hull Mins!\n" ); + return NODE_HUMAN_HULL; +} + + +int CGraph::NodeType( const CBaseEntity *pEntity ) +{ + if ( pEntity->pev->movetype == MOVETYPE_FLY) + { + if (pEntity->pev->waterlevel != 0) + { + return bits_NODE_WATER; + } + else + { + return bits_NODE_AIR; + } + } + return bits_NODE_LAND; +} + + +// Sum up graph weights on the path from iStart to iDest to determine path length +float CGraph::PathLength( int iStart, int iDest, int iHull, int afCapMask ) +{ + float distance = 0; + int iNext; + + int iMaxLoop = m_cNodes; + + int iCurrentNode = iStart; + int iCap = CapIndex( afCapMask ); + + while (iCurrentNode != iDest) + { + if (iMaxLoop-- <= 0) + { + ALERT( at_console, "Route Failure\n" ); + return 0; + } + + iNext = NextNodeInRoute( iCurrentNode, iDest, iHull, iCap ); + if (iCurrentNode == iNext) + { + //ALERT(at_aiconsole, "SVD: Can't get there from here..\n"); + return 0; + } + + int iLink; + HashSearch(iCurrentNode, iNext, iLink); + if (iLink < 0) + { + ALERT(at_console, "HashLinks is broken from %d to %d.\n", iCurrentNode, iDest); + return 0; + } + CLink &link = Link(iLink); + distance += link.m_flWeight; + + iCurrentNode = iNext; + } + + return distance; +} + + +// Parse the routing table at iCurrentNode for the next node on the shortest path to iDest +int CGraph::NextNodeInRoute( int iCurrentNode, int iDest, int iHull, int iCap ) +{ + int iNext = iCurrentNode; + int nCount = iDest+1; + char *pRoute = m_pRouteInfo + m_pNodes[ iCurrentNode ].m_pNextBestNode[iHull][iCap]; + + // Until we decode the next best node + // + while (nCount > 0) + { + char ch = *pRoute++; + //ALERT(at_aiconsole, "C(%d)", ch); + if (ch < 0) + { + // Sequence phrase + // + ch = -ch; + if (nCount <= ch) + { + iNext = iDest; + nCount = 0; + //ALERT(at_aiconsole, "SEQ: iNext/iDest=%d\n", iNext); + } + else + { + //ALERT(at_aiconsole, "SEQ: nCount + ch (%d + %d)\n", nCount, ch); + nCount = nCount - ch; + } + } + else + { + //ALERT(at_aiconsole, "C(%d)", *pRoute); + + // Repeat phrase + // + if (nCount <= ch+1) + { + iNext = iCurrentNode + *pRoute; + if (iNext >= m_cNodes) iNext -= m_cNodes; + else if (iNext < 0) iNext += m_cNodes; + nCount = 0; + //ALERT(at_aiconsole, "REP: iNext=%d\n", iNext); + } + else + { + //ALERT(at_aiconsole, "REP: nCount - ch+1 (%d - %d+1)\n", nCount, ch); + nCount = nCount - ch - 1; + } + pRoute++; + } + } + + return iNext; +} + + +//========================================================= +// CGraph - FindShortestPath +// +// accepts a capability mask (afCapMask), and will only +// find a path usable by a monster with those capabilities +// returns the number of nodes copied into supplied array +//========================================================= +int CGraph :: FindShortestPath ( int *piPath, int iStart, int iDest, int iHull, int afCapMask) +{ + int iVisitNode; + int iCurrentNode; + int iNumPathNodes; + int iHullMask; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + if ( iStart < 0 || iStart > m_cNodes ) + {// The start node is bad? + ALERT ( at_aiconsole, "Can't build a path, iStart is %d!\n", iStart ); + return FALSE; + } + + if (iStart == iDest) + { + piPath[0] = iStart; + piPath[1] = iDest; + return 2; + } + + // Is routing information present. + // + if (m_fRoutingComplete) + { + int iCap = CapIndex( afCapMask ); + + iNumPathNodes = 0; + piPath[iNumPathNodes++] = iStart; + iCurrentNode = iStart; + int iNext; + + //ALERT(at_aiconsole, "GOAL: %d to %d\n", iStart, iDest); + + // Until we arrive at the destination + // + while (iCurrentNode != iDest) + { + iNext = NextNodeInRoute( iCurrentNode, iDest, iHull, iCap ); + if (iCurrentNode == iNext) + { + //ALERT(at_aiconsole, "SVD: Can't get there from here..\n"); + return 0; + break; + } + if (iNumPathNodes >= MAX_PATH_SIZE) + { + //ALERT(at_aiconsole, "SVD: Don't return the entire path.\n"); + break; + } + piPath[iNumPathNodes++] = iNext; + iCurrentNode = iNext; + } + //ALERT( at_aiconsole, "SVD: Path with %d nodes.\n", iNumPathNodes); + } + else + { + CQueuePriority queue; + + switch( iHull ) + { + case NODE_SMALL_HULL: + iHullMask = bits_LINK_SMALL_HULL; + break; + case NODE_HUMAN_HULL: + iHullMask = bits_LINK_HUMAN_HULL; + break; + case NODE_LARGE_HULL: + iHullMask = bits_LINK_LARGE_HULL; + break; + case NODE_FLY_HULL: + iHullMask = bits_LINK_FLY_HULL; + break; + } + + // Mark all the nodes as unvisited. + // + for ( int i = 0; i < m_cNodes; i++) + { + m_pNodes[ i ].m_flClosestSoFar = -1.0; + } + + m_pNodes[ iStart ].m_flClosestSoFar = 0.0; + m_pNodes[ iStart ].m_iPreviousNode = iStart;// tag this as the origin node + queue.Insert( iStart, 0.0 );// insert start node + + while ( !queue.Empty() ) + { + // now pull a node out of the queue + float flCurrentDistance; + iCurrentNode = queue.Remove(flCurrentDistance); + + // For straight-line weights, the following Shortcut works. For arbitrary weights, + // it doesn't. + // + if (iCurrentNode == iDest) break; + + CNode *pCurrentNode = &m_pNodes[ iCurrentNode ]; + + for ( i = 0 ; i < pCurrentNode->m_cNumLinks ; i++ ) + {// run through all of this node's neighbors + + iVisitNode = INodeLink ( iCurrentNode, i ); + if ( ( m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_afLinkInfo & iHullMask ) != iHullMask ) + {// monster is too large to walk this connection + //ALERT ( at_aiconsole, "fat ass %d/%d\n",m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_afLinkInfo, iMonsterHull ); + continue; + } + // check the connection from the current node to the node we're about to mark visited and push into the queue + if ( m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_pLinkEnt != NULL ) + {// there's a brush ent in the way! Don't mark this node or put it into the queue unless the monster can negotiate it + + if ( !HandleLinkEnt ( iCurrentNode, m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i ].m_pLinkEnt, afCapMask, NODEGRAPH_STATIC ) ) + {// monster should not try to go this way. + continue; + } + } + float flOurDistance = flCurrentDistance + m_pLinkPool[ m_pNodes[ iCurrentNode ].m_iFirstLink + i].m_flWeight; + if ( m_pNodes[ iVisitNode ].m_flClosestSoFar < -0.5 + || flOurDistance < m_pNodes[ iVisitNode ].m_flClosestSoFar - 0.001 ) + { + m_pNodes[iVisitNode].m_flClosestSoFar = flOurDistance; + m_pNodes[iVisitNode].m_iPreviousNode = iCurrentNode; + + queue.Insert ( iVisitNode, flOurDistance ); + } + } + } + if ( m_pNodes[iDest].m_flClosestSoFar < -0.5 ) + {// Destination is unreachable, no path found. + return 0; + } + + // the queue is not empty + + // now we must walk backwards through the m_iPreviousNode field, and count how many connections there are in the path + iCurrentNode = iDest; + iNumPathNodes = 1;// count the dest + + while ( iCurrentNode != iStart ) + { + iNumPathNodes++; + iCurrentNode = m_pNodes[ iCurrentNode ].m_iPreviousNode; + } + + iCurrentNode = iDest; + for ( i = iNumPathNodes - 1 ; i >= 0 ; i-- ) + { + piPath[ i ] = iCurrentNode; + iCurrentNode = m_pNodes [ iCurrentNode ].m_iPreviousNode; + } + } + +#if 0 + + if (m_fRoutingComplete) + { + // This will draw the entire path that was generated for the monster. + + for ( int i = 0 ; i < iNumPathNodes - 1 ; i++ ) + { + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ piPath[ i ] ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ piPath[ i + 1 ] ].m_vecOrigin.z + NODE_HEIGHT ); + MESSAGE_END(); + } + } + +#endif +#if 0 // MAZE map + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ 4 ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ 9 ].m_vecOrigin.z + NODE_HEIGHT ); + MESSAGE_END(); +#endif + + return iNumPathNodes; +} + +inline ULONG Hash(void *p, int len) +{ + CRC32_t ulCrc; + CRC32_INIT(&ulCrc); + CRC32_PROCESS_BUFFER(&ulCrc, p, len); + return CRC32_FINAL(ulCrc); +} + +void inline CalcBounds(int &Lower, int &Upper, int Goal, int Best) +{ + int Temp = 2*Goal - Best; + if (Best > Goal) + { + Lower = max(0, Temp); + Upper = Best; + } + else + { + Upper = min(255, Temp); + Lower = Best; + } +} + +// Convert from [-8192,8192] to [0, 255] +// +inline int CALC_RANGE(int x, int lower, int upper) +{ + return NUM_RANGES*(x-lower)/((upper-lower+1)); +} + + +void inline UpdateRange(int &minValue, int &maxValue, int Goal, int Best) +{ + int Lower, Upper; + CalcBounds(Lower, Upper, Goal, Best); + if (Upper < maxValue) maxValue = Upper; + if (minValue < Lower) minValue = Lower; +} + +void CGraph :: CheckNode(Vector vecOrigin, int iNode) +{ + // Have we already seen this point before?. + // + if (m_di[iNode].m_CheckedEvent == m_CheckedCounter) return; + m_di[iNode].m_CheckedEvent = m_CheckedCounter; + + float flDist = ( vecOrigin - m_pNodes[ iNode ].m_vecOriginPeek ).Length(); + + if ( flDist < m_flShortest ) + { + TraceResult tr; + + // make sure that vecOrigin can trace to this node! + UTIL_TraceLine ( vecOrigin, m_pNodes[ iNode ].m_vecOriginPeek, ignore_monsters, 0, &tr ); + + if ( tr.flFraction == 1.0 ) + { + m_iNearest = iNode; + m_flShortest = flDist; + + UpdateRange(m_minX, m_maxX, CALC_RANGE(vecOrigin.x, m_RegionMin[0], m_RegionMax[0]), m_pNodes[iNode].m_Region[0]); + UpdateRange(m_minY, m_maxY, CALC_RANGE(vecOrigin.y, m_RegionMin[1], m_RegionMax[1]), m_pNodes[iNode].m_Region[1]); + UpdateRange(m_minZ, m_maxZ, CALC_RANGE(vecOrigin.z, m_RegionMin[2], m_RegionMax[2]), m_pNodes[iNode].m_Region[2]); + + // From maxCircle, calculate maximum bounds box. All points must be + // simultaneously inside all bounds of the box. + // + m_minBoxX = CALC_RANGE(vecOrigin.x - flDist, m_RegionMin[0], m_RegionMax[0]); + m_maxBoxX = CALC_RANGE(vecOrigin.x + flDist, m_RegionMin[0], m_RegionMax[0]); + m_minBoxY = CALC_RANGE(vecOrigin.y - flDist, m_RegionMin[1], m_RegionMax[1]); + m_maxBoxY = CALC_RANGE(vecOrigin.y + flDist, m_RegionMin[1], m_RegionMax[1]); + m_minBoxZ = CALC_RANGE(vecOrigin.z - flDist, m_RegionMin[2], m_RegionMax[2]); + m_maxBoxZ = CALC_RANGE(vecOrigin.z + flDist, m_RegionMin[2], m_RegionMax[2]); + } + } +} + +//========================================================= +// CGraph - FindNearestNode - returns the index of the node nearest +// the given vector -1 is failure (couldn't find a valid +// near node ) +//========================================================= +int CGraph :: FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ) +{ + return FindNearestNode( vecOrigin, NodeType( pEntity ) ); +} + +int CGraph :: FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ) +{ + int i; + TraceResult tr; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return -1; + } + + // Check with the cache + // + ULONG iHash = (CACHE_SIZE-1) & Hash((void *)(const float *)vecOrigin, sizeof(vecOrigin)); + if (m_Cache[iHash].v == vecOrigin) + { + //ALERT(at_aiconsole, "Cache Hit.\n"); + return m_Cache[iHash].n; + } + else + { + //ALERT(at_aiconsole, "Cache Miss.\n"); + } + + // Mark all points as unchecked. + // + m_CheckedCounter++; + if (m_CheckedCounter == 0) + { + for (int i = 0; i < m_cNodes; i++) + { + m_di[i].m_CheckedEvent = 0; + } + m_CheckedCounter++; + } + + m_iNearest = -1; + m_flShortest = 999999.0; // just a big number. + + // If we can find a visible point, then let CalcBounds set the limits, but if + // we have no visible point at all to start with, then don't restrict the limits. + // +#if 1 + m_minX = 0; m_maxX = 255; + m_minY = 0; m_maxY = 255; + m_minZ = 0; m_maxZ = 255; + m_minBoxX = 0; m_maxBoxX = 255; + m_minBoxY = 0; m_maxBoxY = 255; + m_minBoxZ = 0; m_maxBoxZ = 255; +#else + m_minBoxX = CALC_RANGE(vecOrigin.x - flDist, m_RegionMin[0], m_RegionMax[0]); + m_maxBoxX = CALC_RANGE(vecOrigin.x + flDist, m_RegionMin[0], m_RegionMax[0]); + m_minBoxY = CALC_RANGE(vecOrigin.y - flDist, m_RegionMin[1], m_RegionMax[1]); + m_maxBoxY = CALC_RANGE(vecOrigin.y + flDist, m_RegionMin[1], m_RegionMax[1]); + m_minBoxZ = CALC_RANGE(vecOrigin.z - flDist, m_RegionMin[2], m_RegionMax[2]); + m_maxBoxZ = CALC_RANGE(vecOrigin.z + flDist, m_RegionMin[2], m_RegionMax[2]) + CalcBounds(m_minX, m_maxX, CALC_RANGE(vecOrigin.x, m_RegionMin[0], m_RegionMax[0]), m_pNodes[m_iNearest].m_Region[0]); + CalcBounds(m_minY, m_maxY, CALC_RANGE(vecOrigin.y, m_RegionMin[1], m_RegionMax[1]), m_pNodes[m_iNearest].m_Region[1]); + CalcBounds(m_minZ, m_maxZ, CALC_RANGE(vecOrigin.z, m_RegionMin[2], m_RegionMax[2]), m_pNodes[m_iNearest].m_Region[2]); +#endif + + int halfX = (m_minX+m_maxX)/2; + int halfY = (m_minY+m_maxY)/2; + int halfZ = (m_minZ+m_maxZ)/2; + + int j; + + for (i = halfX; i >= m_minX; i--) + { + for (j = m_RangeStart[0][i]; j <= m_RangeEnd[0][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[0]].m_afNodeInfo & afNodeTypes)) continue; + + int rgY = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[1]; + if (rgY > m_maxBoxY) break; + if (rgY < m_minBoxY) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[2]; + if (rgZ < m_minBoxZ) continue; + if (rgZ > m_maxBoxZ) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[0]); + } + } + + for (i = max(m_minY,halfY+1); i <= m_maxY; i++) + { + for (j = m_RangeStart[1][i]; j <= m_RangeEnd[1][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[1]].m_afNodeInfo & afNodeTypes)) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[2]; + if (rgZ > m_maxBoxZ) break; + if (rgZ < m_minBoxZ) continue; + int rgX = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[0]; + if (rgX < m_minBoxX) continue; + if (rgX > m_maxBoxX) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[1]); + } + } + + for (i = min(m_maxZ,halfZ); i >= m_minZ; i--) + { + for (j = m_RangeStart[2][i]; j <= m_RangeEnd[2][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[2]].m_afNodeInfo & afNodeTypes)) continue; + + int rgX = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[0]; + if (rgX > m_maxBoxX) break; + if (rgX < m_minBoxX) continue; + int rgY = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[1]; + if (rgY < m_minBoxY) continue; + if (rgY > m_maxBoxY) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[2]); + } + } + + for (i = max(m_minX,halfX+1); i <= m_maxX; i++) + { + for (j = m_RangeStart[0][i]; j <= m_RangeEnd[0][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[0]].m_afNodeInfo & afNodeTypes)) continue; + + int rgY = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[1]; + if (rgY > m_maxBoxY) break; + if (rgY < m_minBoxY) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[0]].m_Region[2]; + if (rgZ < m_minBoxZ) continue; + if (rgZ > m_maxBoxZ) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[0]); + } + } + + for (i = min(m_maxY,halfY); i >= m_minY; i--) + { + for (j = m_RangeStart[1][i]; j <= m_RangeEnd[1][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[1]].m_afNodeInfo & afNodeTypes)) continue; + + int rgZ = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[2]; + if (rgZ > m_maxBoxZ) break; + if (rgZ < m_minBoxZ) continue; + int rgX = m_pNodes[m_di[j].m_SortedBy[1]].m_Region[0]; + if (rgX < m_minBoxX) continue; + if (rgX > m_maxBoxX) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[1]); + } + } + + for (i = max(m_minZ,halfZ+1); i <= m_maxZ; i++) + { + for (j = m_RangeStart[2][i]; j <= m_RangeEnd[2][i]; j++) + { + if (!(m_pNodes[m_di[j].m_SortedBy[2]].m_afNodeInfo & afNodeTypes)) continue; + + int rgX = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[0]; + if (rgX > m_maxBoxX) break; + if (rgX < m_minBoxX) continue; + int rgY = m_pNodes[m_di[j].m_SortedBy[2]].m_Region[1]; + if (rgY < m_minBoxY) continue; + if (rgY > m_maxBoxY) continue; + CheckNode(vecOrigin, m_di[j].m_SortedBy[2]); + } + } + +#if 0 + // Verify our answers. + // + int iNearestCheck = -1; + m_flShortest = 8192;// find nodes within this radius + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + float flDist = ( vecOrigin - m_pNodes[ i ].m_vecOriginPeek ).Length(); + + if ( flDist < m_flShortest ) + { + // make sure that vecOrigin can trace to this node! + UTIL_TraceLine ( vecOrigin, m_pNodes[ i ].m_vecOriginPeek, ignore_monsters, 0, &tr ); + + if ( tr.flFraction == 1.0 ) + { + iNearestCheck = i; + m_flShortest = flDist; + } + } + } + + if (iNearestCheck != m_iNearest) + { + ALERT( at_aiconsole, "NOT closest %d(%f,%f,%f) %d(%f,%f,%f).\n", + iNearestCheck, + m_pNodes[iNearestCheck].m_vecOriginPeek.x, + m_pNodes[iNearestCheck].m_vecOriginPeek.y, + m_pNodes[iNearestCheck].m_vecOriginPeek.z, + m_iNearest, + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.x), + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.y), + (m_iNearest == -1?0.0:m_pNodes[m_iNearest].m_vecOriginPeek.z)); + } + if (m_iNearest == -1) + { + ALERT(at_aiconsole, "All that work for nothing.\n"); + } +#endif + m_Cache[iHash].v = vecOrigin; + m_Cache[iHash].n = m_iNearest; + return m_iNearest; +} + +//========================================================= +// CGraph - ShowNodeConnections - draws a line from the given node +// to all connected nodes +//========================================================= +void CGraph :: ShowNodeConnections ( int iNode ) +{ + Vector vecSpot; + CNode *pNode; + CNode *pLinkNode; + int i; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return; + } + + if ( iNode < 0 ) + { + ALERT( at_aiconsole, "Can't show connections for node %d\n", iNode ); + return; + } + + pNode = &m_pNodes[ iNode ]; + + UTIL_ParticleEffect( pNode->m_vecOrigin, g_vecZero, 255, 20 );// show node position + + if ( pNode->m_cNumLinks <= 0 ) + {// no connections! + ALERT ( at_aiconsole, "**No Connections!\n" ); + } + + for ( i = 0 ; i < pNode->m_cNumLinks ; i++ ) + { + + pLinkNode = &Node( NodeLink( iNode, i).m_iDestNode ); + vecSpot = pLinkNode->m_vecOrigin; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.x ); + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.y ); + WRITE_COORD( m_pNodes[ iNode ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + NODE_HEIGHT ); + MESSAGE_END(); + + } +} + +//========================================================= +// CGraph - LinkVisibleNodes - the first, most basic +// function of node graph creation, this connects every +// node to every other node that it can see. Expects a +// pointer to an empty connection pool and a file pointer +// to write progress to. Returns the total number of initial +// links. +// +// If there's a problem with this process, the index +// of the offending node will be written to piBadNode +//========================================================= +int CGraph :: LinkVisibleNodes ( CLink *pLinkPool, FILE *file, int *piBadNode ) +{ + int i,j,z; + edict_t *pTraceEnt; + int cTotalLinks, cLinksThisNode, cMaxInitialLinks; + TraceResult tr; + + // !!!BUGBUG - this function returns 0 if there is a problem in the middle of connecting the graph + // it also returns 0 if none of the nodes in a level can see each other. piBadNode is ALWAYS read + // by BuildNodeGraph() if this function returns a 0, so make sure that it doesn't get some random + // number back. + *piBadNode = 0; + + + if ( m_cNodes <= 0 ) + { + ALERT ( at_aiconsole, "No Nodes!\n" ); + return FALSE; + } + + // if the file pointer is bad, don't blow up, just don't write the + // file. + if ( !file ) + { + ALERT ( at_aiconsole, "**LinkVisibleNodes:\ncan't write to file." ); + } + else + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "LinkVisibleNodes - Initial Connections\n" ); + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + + cTotalLinks = 0;// start with no connections + + // to keep track of the maximum number of initial links any node had so far. + // this lets us keep an eye on MAX_NODE_INITIAL_LINKS to ensure that we are + // being generous enough. + cMaxInitialLinks = 0; + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + cLinksThisNode = 0;// reset this count for each node. + + if ( file ) + { + fprintf ( file, "Node #%4d:\n\n", i ); + } + + for ( z = 0 ; z < MAX_NODE_INITIAL_LINKS ; z++ ) + {// clear out the important fields in the link pool for this node + pLinkPool [ cTotalLinks + z ].m_iSrcNode = i;// so each link knows which node it originates from + pLinkPool [ cTotalLinks + z ].m_iDestNode = 0; + pLinkPool [ cTotalLinks + z ].m_pLinkEnt = NULL; + } + + m_pNodes [ i ].m_iFirstLink = cTotalLinks; + + // now build a list of every other node that this node can see + for ( j = 0 ; j < m_cNodes ; j++ ) + { + if ( j == i ) + {// don't connect to self! + continue; + } + +#if 0 + + if ( (m_pNodes[ i ].m_afNodeInfo & bits_NODE_WATER) != (m_pNodes[ j ].m_afNodeInfo & bits_NODE_WATER) ) + { + // don't connect water nodes to air nodes or land nodes. It just wouldn't be prudent at this juncture. + continue; + } +#else + if ( (m_pNodes[ i ].m_afNodeInfo & bits_NODE_GROUP_REALM) != (m_pNodes[ j ].m_afNodeInfo & bits_NODE_GROUP_REALM) ) + { + // don't connect air nodes to water nodes to land nodes. It just wouldn't be prudent at this juncture. + continue; + } +#endif + + tr.pHit = NULL;// clear every time so we don't get stuck with last trace's hit ent + pTraceEnt = 0; + + UTIL_TraceLine ( m_pNodes[ i ].m_vecOrigin, + m_pNodes[ j ].m_vecOrigin, + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + + if ( tr.fStartSolid ) + continue; + + if ( tr.flFraction != 1.0 ) + {// trace hit a brush ent, trace backwards to make sure that this ent is the only thing in the way. + + pTraceEnt = tr.pHit;// store the ent that the trace hit, for comparison + + UTIL_TraceLine ( m_pNodes[ j ].m_vecOrigin, + m_pNodes[ i ].m_vecOrigin, + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + +// there is a solid_bsp ent in the way of these two nodes, so we must record several things about in order to keep +// track of it in the pathfinding code, as well as through save and restore of the node graph. ANY data that is manipulated +// as part of the process of adding a LINKENT to a connection here must also be done in CGraph::SetGraphPointers, where reloaded +// graphs are prepared for use. + if ( tr.pHit == pTraceEnt && !FClassnameIs( tr.pHit, "worldspawn" ) ) + { + // get a pointer + pLinkPool [ cTotalLinks ].m_pLinkEnt = VARS( tr.pHit ); + + // record the modelname, so that we can save/load node trees + memcpy( pLinkPool [ cTotalLinks ].m_szLinkEntModelname, STRING( VARS(tr.pHit)->model ), 4 ); + + // set the flag for this ent that indicates that it is attached to the world graph + // if this ent is removed from the world, it must also be removed from the connections + // that it formerly blocked. + if ( !FBitSet( VARS( tr.pHit )->flags, FL_GRAPHED ) ) + { + VARS( tr.pHit )->flags += FL_GRAPHED; + } + } + else + {// even if the ent wasn't there, these nodes couldn't be connected. Skip. + continue; + } + } + + if ( file ) + { + fprintf ( file, "%4d", j ); + + if ( !FNullEnt( pLinkPool[ cTotalLinks ].m_pLinkEnt ) ) + {// record info about the ent in the way, if any. + fprintf ( file, " Entity on connection: %s, name: %s Model: %s", STRING( VARS( pTraceEnt )->classname ), STRING ( VARS( pTraceEnt )->targetname ), STRING ( VARS(tr.pHit)->model ) ); + } + + fprintf ( file, "\n", j ); + } + + pLinkPool [ cTotalLinks ].m_iDestNode = j; + cLinksThisNode++; + cTotalLinks++; + + // If we hit this, either a level designer is placing too many nodes in the same area, or + // we need to allow for a larger initial link pool. + if ( cLinksThisNode == MAX_NODE_INITIAL_LINKS ) + { + ALERT ( at_aiconsole, "**LinkVisibleNodes:\nNode %d has NodeLinks > MAX_NODE_INITIAL_LINKS", i ); + fprintf ( file, "** NODE %d HAS NodeLinks > MAX_NODE_INITIAL_LINKS **\n", i ); + *piBadNode = i; + return FALSE; + } + else if ( cTotalLinks > MAX_NODE_INITIAL_LINKS * m_cNodes ) + {// this is paranoia + ALERT ( at_aiconsole, "**LinkVisibleNodes:\nTotalLinks > MAX_NODE_INITIAL_LINKS * NUMNODES" ); + *piBadNode = i; + return FALSE; + } + + if ( cLinksThisNode == 0 ) + { + fprintf ( file, "**NO INITIAL LINKS**\n" ); + } + + // record the connection info in the link pool + WorldGraph.m_pNodes [ i ].m_cNumLinks = cLinksThisNode; + + // keep track of the most initial links ANY node had, so we can figure out + // if we have a large enough default link pool + if ( cLinksThisNode > cMaxInitialLinks ) + { + cMaxInitialLinks = cLinksThisNode; + } + } + + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + } + + fprintf ( file, "\n%4d Total Initial Connections - %4d Maximum connections for a single node.\n", cTotalLinks, cMaxInitialLinks ); + fprintf ( file, "----------------------------------------------------------------------------\n\n\n" ); + + return cTotalLinks; +} + +//========================================================= +// CGraph - RejectInlineLinks - expects a pointer to a link +// pool, and a pointer to and already-open file ( if you +// want status reports written to disk ). RETURNS the number +// of connections that were rejected +//========================================================= +int CGraph :: RejectInlineLinks ( CLink *pLinkPool, FILE *file ) +{ + int i,j,k; + + int cRejectedLinks; + + BOOL fRestartLoop;// have to restart the J loop if we eliminate a link. + + CNode *pSrcNode; + CNode *pCheckNode;// the node we are testing for (one of pSrcNode's connections) + CNode *pTestNode;// the node we are checking against ( also one of pSrcNode's connections) + + float flDistToTestNode, flDistToCheckNode; + + Vector2D vec2DirToTestNode, vec2DirToCheckNode; + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "InLine Rejection:\n" ); + fprintf ( file, "----------------------------------------------------------------------------\n" ); + } + + cRejectedLinks = 0; + + for ( i = 0 ; i < m_cNodes ; i++ ) + { + pSrcNode = &m_pNodes[ i ]; + + if ( file ) + { + fprintf ( file, "Node %3d:\n", i ); + } + + for ( j = 0 ; j < pSrcNode->m_cNumLinks ; j++ ) + { + pCheckNode = &m_pNodes[ pLinkPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode ]; + + vec2DirToCheckNode = ( pCheckNode->m_vecOrigin - pSrcNode->m_vecOrigin ).Make2D(); + flDistToCheckNode = vec2DirToCheckNode.Length(); + vec2DirToCheckNode = vec2DirToCheckNode.Normalize(); + + pLinkPool[ pSrcNode->m_iFirstLink + j ].m_flWeight = flDistToCheckNode; + + fRestartLoop = FALSE; + for ( k = 0 ; k < pSrcNode->m_cNumLinks && !fRestartLoop ; k++ ) + { + if ( k == j ) + {// don't check against same node + continue; + } + + pTestNode = &m_pNodes [ pLinkPool[ pSrcNode->m_iFirstLink + k ].m_iDestNode ]; + + vec2DirToTestNode = ( pTestNode->m_vecOrigin - pSrcNode->m_vecOrigin ).Make2D(); + + flDistToTestNode = vec2DirToTestNode.Length(); + vec2DirToTestNode = vec2DirToTestNode.Normalize(); + + if ( DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) >= 0.998 ) + { + // there's a chance that TestNode intersects the line to CheckNode. If so, we should disconnect the link to CheckNode. + if ( flDistToTestNode < flDistToCheckNode ) + { + if ( file ) + { + fprintf ( file, "REJECTED NODE %3d through Node %3d, Dot = %8f\n", pLinkPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode, pLinkPool[ pSrcNode->m_iFirstLink + k ].m_iDestNode, DotProduct ( vec2DirToCheckNode, vec2DirToTestNode ) ); + } + + pLinkPool[ pSrcNode->m_iFirstLink + j ] = pLinkPool[ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + pSrcNode->m_cNumLinks--; + j--; + + cRejectedLinks++;// keeping track of how many links are cut, so that we can return that value. + + fRestartLoop = TRUE; + } + } + } + } + + if ( file ) + { + fprintf ( file, "----------------------------------------------------------------------------\n\n" ); + } + } + + return cRejectedLinks; +} + +//========================================================= +// TestHull is a modelless clip hull that verifies reachable +// nodes by walking from every node to each of it's connections +//========================================================= +class CTestHull : public CBaseMonster +{ + +public: + void Spawn( entvars_t *pevMasterNode ); + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + void EXPORT CallBuildNodeGraph ( void ); + void BuildNodeGraph ( void ); + void EXPORT ShowBadNode ( void ); + void EXPORT DropDelay ( void ); + void EXPORT PathFind ( void ); + + Vector vecBadNodeOrigin; +}; + +LINK_ENTITY_TO_CLASS( testhull, CTestHull ); + +//========================================================= +// CTestHull::Spawn +//========================================================= +void CTestHull :: Spawn( entvars_t *pevMasterNode ) +{ + SET_MODEL(ENT(pev), "models/player.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + pev->effects = 0; + pev->health = 50; + pev->yaw_speed = 8; + + if ( WorldGraph.m_fGraphPresent ) + {// graph loaded from disk, so we don't need the test hull + SetThink ( SUB_Remove ); + pev->nextthink = gpGlobals->time; + } + else + { + SetThink ( DropDelay ); + pev->nextthink = gpGlobals->time + 1; + } + + // Make this invisible + // UNDONE: Shouldn't we just use EF_NODRAW? This doesn't need to go to the client. + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; +} + +//========================================================= +// TestHull::DropDelay - spawns TestHull on top of +// the 0th node and drops it to the ground. +//========================================================= +void CTestHull::DropDelay ( void ) +{ + UTIL_CenterPrintAll( "Node Graph out of Date. Rebuilding..." ); + + UTIL_SetOrigin ( VARS(pev), WorldGraph.m_pNodes[ 0 ].m_vecOrigin ); + + SetThink ( CallBuildNodeGraph ); + + pev->nextthink = gpGlobals->time + 1; +} + +//========================================================= +// nodes start out as ents in the world. As they are spawned, +// the node info is recorded then the ents are discarded. +//========================================================= +void CNodeEnt :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "hinttype")) + { + m_sHintType = (short)atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + + if (FStrEq(pkvd->szKeyName, "activity")) + { + m_sHintActivity = (short)atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +//========================================================= +//========================================================= +void CNodeEnt :: Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT;// always solid_not + + if ( WorldGraph.m_fGraphPresent ) + {// graph loaded from disk, so discard all these node ents as soon as they spawn + REMOVE_ENTITY( edict() ); + return; + } + + if ( WorldGraph.m_cNodes == 0 ) + {// this is the first node to spawn, spawn the test hull entity that builds and walks the node tree + CTestHull *pHull = GetClassPtr((CTestHull *)NULL); + pHull->Spawn( pev ); + } + + if ( WorldGraph.m_cNodes >= MAX_NODES ) + { + ALERT ( at_aiconsole, "cNodes > MAX_NODES\n" ); + return; + } + + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_vecOriginPeek = + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_vecOrigin = pev->origin; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_flHintYaw = pev->angles.y; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_sHintType = m_sHintType; + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_sHintActivity = m_sHintActivity; + + if (FClassnameIs( pev, "info_node_air" )) + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_afNodeInfo = bits_NODE_AIR; + else + WorldGraph.m_pNodes[ WorldGraph.m_cNodes ].m_afNodeInfo = 0; + + WorldGraph.m_cNodes++; + + REMOVE_ENTITY( edict() ); +} + +//========================================================= +// CTestHull - ShowBadNode - makes a bad node fizzle. When +// there's a problem with node graph generation, the test +// hull will be placed up the bad node's location and will generate +// particles +//========================================================= +void CTestHull :: ShowBadNode( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->angles.y = pev->angles.y + 4; + + UTIL_MakeVectors ( pev->angles ); + + UTIL_ParticleEffect ( pev->origin, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin + gpGlobals->v_forward * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin - gpGlobals->v_forward * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin + gpGlobals->v_right * 64, g_vecZero, 255, 25 ); + UTIL_ParticleEffect ( pev->origin - gpGlobals->v_right * 64, g_vecZero, 255, 25 ); + + pev->nextthink = gpGlobals->time + 0.1; +} + +extern BOOL gTouchDisabled; +void CTestHull::CallBuildNodeGraph( void ) +{ + // TOUCH HACK -- Don't allow this entity to call anyone's "touch" function + gTouchDisabled = TRUE; + BuildNodeGraph(); + gTouchDisabled = FALSE; + // Undo TOUCH HACK +} + +//========================================================= +// BuildNodeGraph - think function called by the empty walk +// hull that is spawned by the first node to spawn. This +// function links all nodes that can see each other, then +// eliminates all inline links, then uses a monster-sized +// hull that walks between each node and each of its links +// to ensure that a monster can actually fit through the space +//========================================================= +void CTestHull :: BuildNodeGraph( void ) +{ + TraceResult tr; + FILE *file; + + char szNrpFilename [MAX_PATH];// text node report filename + + CLink *pTempPool; // temporary link pool + + CNode *pSrcNode;// node we're currently working with + CNode *pDestNode;// the other node in comparison operations + + BOOL fSkipRemainingHulls;//if smallest hull can't fit, don't check any others + BOOL fPairsValid;// are all links in the graph evenly paired? + + int i, j, hull; + + int iBadNode;// this is the node that caused graph generation to fail + + int cMaxInitialLinks = 0; + int cMaxValidLinks = 0; + + int iPoolIndex = 0; + int cPoolLinks;// number of links in the pool. + + Vector vecDirToCheckNode; + Vector vecDirToTestNode; + Vector vecStepCheckDir; + Vector vecTraceSpot; + Vector vecSpot; + + Vector2D vec2DirToCheckNode; + Vector2D vec2DirToTestNode; + Vector2D vec2StepCheckDir; + Vector2D vec2TraceSpot; + Vector2D vec2Spot; + + float flYaw;// use this stuff to walk the hull between nodes + float flDist; + int step; + + SetThink ( SUB_Remove );// no matter what happens, the hull gets rid of itself. + pev->nextthink = gpGlobals->time; + +// malloc a swollen temporary connection pool that we trim down after we know exactly how many connections there are. + pTempPool = (CLink *)calloc ( sizeof ( CLink ) , ( WorldGraph.m_cNodes * MAX_NODE_INITIAL_LINKS ) ); + if ( !pTempPool ) + { + ALERT ( at_aiconsole, "**Could not malloc TempPool!\n" ); + return; + } + + + // make sure directories have been made + GET_GAME_DIR( szNrpFilename ); + strcat( szNrpFilename, "/maps" ); + CreateDirectory( szNrpFilename, NULL ); + strcat( szNrpFilename, "/graphs" ); + CreateDirectory( szNrpFilename, NULL ); + + strcat( szNrpFilename, "/" ); + strcat( szNrpFilename, STRING( gpGlobals->mapname ) ); + strcat( szNrpFilename, ".nrp" ); + + file = fopen ( szNrpFilename, "w+" ); + + if ( !file ) + {// file error + ALERT ( at_aiconsole, "Couldn't create %s!\n", szNrpFilename ); + + if ( pTempPool ) + { + free ( pTempPool ); + } + + return; + } + + fprintf( file, "Node Graph Report for map: %s.bsp\n", STRING(gpGlobals->mapname) ); + fprintf ( file, "%d Total Nodes\n\n", WorldGraph.m_cNodes ); + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + {// print all node numbers and their locations to the file. + WorldGraph.m_pNodes[ i ].m_cNumLinks = 0; + WorldGraph.m_pNodes[ i ].m_iFirstLink = 0; + memset(WorldGraph.m_pNodes[ i ].m_pNextBestNode, 0, sizeof(WorldGraph.m_pNodes[ i ].m_pNextBestNode)); + + fprintf ( file, "Node# %4d\n", i ); + fprintf ( file, "Location %4d,%4d,%4d\n",(int)WorldGraph.m_pNodes[ i ].m_vecOrigin.x, (int)WorldGraph.m_pNodes[ i ].m_vecOrigin.y, (int)WorldGraph.m_pNodes[ i ].m_vecOrigin.z ); + fprintf ( file, "HintType: %4d\n", WorldGraph.m_pNodes[ i ].m_sHintType ); + fprintf ( file, "HintActivity: %4d\n", WorldGraph.m_pNodes[ i ].m_sHintActivity ); + fprintf ( file, "HintYaw: %4f\n", WorldGraph.m_pNodes[ i ].m_flHintYaw ); + fprintf ( file, "-------------------------------------------------------------------------------\n" ); + } + fprintf ( file, "\n\n" ); + + + // Automatically recognize WATER nodes and drop the LAND nodes to the floor. + // + for ( i = 0; i < WorldGraph.m_cNodes; i++) + { + if (WorldGraph.m_pNodes[ i ].m_afNodeInfo & bits_NODE_AIR) + { + // do nothing + } + else if (UTIL_PointContents(WorldGraph.m_pNodes[ i ].m_vecOrigin) == CONTENTS_WATER) + { + WorldGraph.m_pNodes[ i ].m_afNodeInfo |= bits_NODE_WATER; + } + else + { + WorldGraph.m_pNodes[ i ].m_afNodeInfo |= bits_NODE_LAND; + + // trace to the ground, then pop up 8 units and place node there to make it + // easier for them to connect (think stairs, chairs, and bumps in the floor). + // After the routing is done, push them back down. + // + TraceResult tr; + + UTIL_TraceLine ( WorldGraph.m_pNodes[i].m_vecOrigin, + WorldGraph.m_pNodes[i].m_vecOrigin - Vector ( 0, 0, 384 ), + ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &tr ); + + // This trace is ONLY used if we hit an entity flagged with FL_WORLDBRUSH + TraceResult trEnt; + UTIL_TraceLine ( WorldGraph.m_pNodes[i].m_vecOrigin, + WorldGraph.m_pNodes[i].m_vecOrigin - Vector ( 0, 0, 384 ), + dont_ignore_monsters, + g_pBodyQueueHead,//!!!HACKHACK no real ent to supply here, using a global we don't care about + &trEnt ); + + + // Did we hit something closer than the floor? + if ( trEnt.flFraction < tr.flFraction ) + { + // If it was a world brush entity, copy the node location + if ( trEnt.pHit && (trEnt.pHit->v.flags & FL_WORLDBRUSH) ) + tr.vecEndPos = trEnt.vecEndPos; + } + + WorldGraph.m_pNodes[i].m_vecOriginPeek.z = + WorldGraph.m_pNodes[i].m_vecOrigin.z = tr.vecEndPos.z + NODE_HEIGHT; + } + } + + cPoolLinks = WorldGraph.LinkVisibleNodes( pTempPool, file, &iBadNode ); + + if ( !cPoolLinks ) + { + ALERT ( at_aiconsole, "**ConnectVisibleNodes FAILED!\n" ); + + SetThink ( ShowBadNode );// send the hull off to show the offending node. + //pev->solid = SOLID_NOT; + pev->origin = WorldGraph.m_pNodes[ iBadNode ].m_vecOrigin; + + if ( pTempPool ) + { + free ( pTempPool ); + } + + if ( file ) + {// close the file + fclose ( file ); + } + + return; + } + +// send the walkhull to all of this node's connections now. We'll do this here since +// so much of it relies on being able to control the test hull. + fprintf ( file, "----------------------------------------------------------------------------\n" ); + fprintf ( file, "Walk Rejection:\n"); + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + pSrcNode = &WorldGraph.m_pNodes[ i ]; + + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "Node %4d:\n\n", i ); + + for ( j = 0 ; j < pSrcNode->m_cNumLinks ; j++ ) + { + // assume that all hulls can walk this link, then eliminate the ones that can't. + pTempPool [ pSrcNode->m_iFirstLink + j ].m_afLinkInfo = bits_LINK_SMALL_HULL | bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL | bits_LINK_FLY_HULL; + + + // do a check for each hull size. + + // if we can't fit a tiny hull through a connection, no other hulls with fit either, so we + // should just fall out of the loop. Do so by setting the SkipRemainingHulls flag. + fSkipRemainingHulls = FALSE; + for ( hull = 0 ; hull < MAX_NODE_HULLS; hull++ ) + { + if (fSkipRemainingHulls && (hull == NODE_HUMAN_HULL || hull == NODE_LARGE_HULL)) // skip the remaining walk hulls + continue; + + switch ( hull ) + { + case NODE_SMALL_HULL: + UTIL_SetSize(pev, Vector(-12, -12, 0), Vector(12, 12, 24)); + break; + case NODE_HUMAN_HULL: + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + break; + case NODE_LARGE_HULL: + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + break; + case NODE_FLY_HULL: + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + // UTIL_SetSize(pev, Vector(0, 0, 0), Vector(0, 0, 0)); + break; + } + + UTIL_SetOrigin ( pev, pSrcNode->m_vecOrigin );// place the hull on the node + + if ( !FBitSet ( pev->flags, FL_ONGROUND ) ) + { + ALERT ( at_aiconsole, "OFFGROUND!\n" ); + } + + // now build a yaw that points to the dest node, and get the distance. + if ( j < 0 ) + { + ALERT ( at_aiconsole, "**** j = %d ****\n", j ); + if ( pTempPool ) + { + free ( pTempPool ); + } + + if ( file ) + {// close the file + fclose ( file ); + } + return; + } + + pDestNode = &WorldGraph.m_pNodes [ pTempPool[ pSrcNode->m_iFirstLink + j ].m_iDestNode ]; + + vecSpot = pDestNode->m_vecOrigin; + //vecSpot.z = pev->origin.z; + + if (hull < NODE_FLY_HULL) + { + int SaveFlags = pev->flags; + int MoveMode = WALKMOVE_WORLDONLY; + if (pSrcNode->m_afNodeInfo & bits_NODE_WATER) + { + pev->flags |= FL_SWIM; + MoveMode = WALKMOVE_NORMAL; + } + + flYaw = UTIL_VecToYaw ( pDestNode->m_vecOrigin - pev->origin ); + + flDist = ( vecSpot - pev->origin ).Length2D(); + + int fWalkFailed = FALSE; + + // in this loop we take tiny steps from the current node to the nodes that it links to, one at a time. + // pev->angles.y = flYaw; + for ( step = 0 ; step < flDist && !fWalkFailed ; step += HULL_STEP_SIZE ) + { + float stepSize = HULL_STEP_SIZE; + + if ( (step + stepSize) >= (flDist-1) ) + stepSize = (flDist - step) - 1; + + if ( !WALK_MOVE( ENT(pev), flYaw, stepSize, MoveMode ) ) + {// can't take the next step + + fWalkFailed = TRUE; + break; + } + } + + if (!fWalkFailed && (pev->origin - vecSpot).Length() > 64) + { + // ALERT( at_console, "bogus walk\n"); + // we thought we + fWalkFailed = TRUE; + } + + if (fWalkFailed) + { + + //pTempPool[ pSrcNode->m_iFirstLink + j ] = pTempPool [ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + + // now me must eliminate the hull that couldn't walk this connection + switch ( hull ) + { + case NODE_SMALL_HULL: // if this hull can't fit, nothing can, so drop the connection + fprintf ( file, "NODE_SMALL_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~(bits_LINK_SMALL_HULL | bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL); + fSkipRemainingHulls = TRUE;// don't bother checking larger hulls + break; + case NODE_HUMAN_HULL: + fprintf ( file, "NODE_HUMAN_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~(bits_LINK_HUMAN_HULL | bits_LINK_LARGE_HULL); + fSkipRemainingHulls = TRUE;// don't bother checking larger hulls + break; + case NODE_LARGE_HULL: + fprintf ( file, "NODE_LARGE_HULL step %f\n", step ); + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~bits_LINK_LARGE_HULL; + break; + } + } + pev->flags = SaveFlags; + } + else + { + TraceResult tr; + + UTIL_TraceHull( pSrcNode->m_vecOrigin + Vector( 0, 0, 32 ), pDestNode->m_vecOriginPeek + Vector( 0, 0, 32 ), ignore_monsters, large_hull, ENT( pev ), &tr ); + if (tr.fStartSolid || tr.flFraction < 1.0) + { + pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo &= ~bits_LINK_FLY_HULL; + } + } + } + + if (pTempPool[ pSrcNode->m_iFirstLink + j ].m_afLinkInfo == 0) + { + fprintf ( file, "Rejected Node %3d - Unreachable by ", pTempPool [ pSrcNode->m_iFirstLink + j ].m_iDestNode ); + pTempPool[ pSrcNode->m_iFirstLink + j ] = pTempPool [ pSrcNode->m_iFirstLink + ( pSrcNode->m_cNumLinks - 1 ) ]; + fprintf ( file, "Any Hull\n" ); + + pSrcNode->m_cNumLinks--; + cPoolLinks--;// we just removed a link, so decrement the total number of links in the pool. + j--; + } + + } + } + fprintf ( file, "-------------------------------------------------------------------------------\n\n\n"); + + cPoolLinks -= WorldGraph.RejectInlineLinks ( pTempPool, file ); + +// now malloc a pool just large enough to hold the links that are actually used + WorldGraph.m_pLinkPool = (CLink *) calloc ( sizeof ( CLink ), cPoolLinks ); + + if ( !WorldGraph.m_pLinkPool ) + {// couldn't make the link pool! + ALERT ( at_aiconsole, "Couldn't malloc LinkPool!\n" ); + if ( pTempPool ) + { + free ( pTempPool ); + } + if ( file ) + {// close the file + fclose ( file ); + } + + return; + } + WorldGraph.m_cLinks = cPoolLinks; + +//copy only the used portions of the TempPool into the graph's link pool + int iFinalPoolIndex = 0; + int iOldFirstLink; + + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + iOldFirstLink = WorldGraph.m_pNodes[ i ].m_iFirstLink;// store this, because we have to re-assign it before entering the copy loop + + WorldGraph.m_pNodes[ i ].m_iFirstLink = iFinalPoolIndex; + + for ( j = 0 ; j < WorldGraph.m_pNodes[ i ].m_cNumLinks ; j++ ) + { + WorldGraph.m_pLinkPool[ iFinalPoolIndex++ ] = pTempPool[ iOldFirstLink + j ]; + } + } + + + // Node sorting numbers linked nodes close to each other + // + WorldGraph.SortNodes(); + + // This is used for HashSearch + // + WorldGraph.BuildLinkLookups(); + + fPairsValid = TRUE; // assume that the connection pairs are all valid to start + + fprintf ( file, "\n\n-------------------------------------------------------------------------------\n"); + fprintf ( file, "Link Pairings:\n"); + +// link integrity check. The idea here is that if Node A links to Node B, node B should +// link to node A. If not, we have a situation that prevents us from using a basic +// optimization in the FindNearestLink function. + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + for ( j = 0 ; j < WorldGraph.m_pNodes[ i ].m_cNumLinks ; j++ ) + { + int iLink; + WorldGraph.HashSearch(WorldGraph.INodeLink(i,j), i, iLink); + if (iLink < 0) + { + fPairsValid = FALSE;// unmatched link pair. + fprintf ( file, "WARNING: Node %3d does not connect back to Node %3d\n", WorldGraph.INodeLink(i, j), i); + } + } + } + + // !!!LATER - if all connections are properly paired, when can enable an optimization in the pathfinding code + // (in the find nearest line function) + if ( fPairsValid ) + { + fprintf ( file, "\nAll Connections are Paired!\n"); + } + + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "\n\n-------------------------------------------------------------------------------\n"); + fprintf ( file, "Total Number of Connections in Pool: %d\n", cPoolLinks ); + fprintf ( file, "-------------------------------------------------------------------------------\n"); + fprintf ( file, "Connection Pool: %d bytes\n", sizeof ( CLink ) * cPoolLinks ); + fprintf ( file, "-------------------------------------------------------------------------------\n"); + + + ALERT ( at_aiconsole, "%d Nodes, %d Connections\n", WorldGraph.m_cNodes, cPoolLinks ); + + // This is used for FindNearestNode + // + WorldGraph.BuildRegionTables(); + + + // Push all of the LAND nodes down to the ground now. Leave the water and air nodes alone. + // + for ( i = 0 ; i < WorldGraph.m_cNodes ; i++ ) + { + if ((WorldGraph.m_pNodes[ i ].m_afNodeInfo & bits_NODE_LAND)) + { + WorldGraph.m_pNodes[ i ].m_vecOrigin.z -= NODE_HEIGHT; + } + } + + + if ( pTempPool ) + {// free the temp pool + free ( pTempPool ); + } + + if ( file ) + { + fclose ( file ); + } + + // We now have some graphing capabilities. + // + WorldGraph.m_fGraphPresent = TRUE;//graph is in memory. + WorldGraph.m_fGraphPointersSet = TRUE;// since the graph was generated, the pointers are ready + WorldGraph.m_fRoutingComplete = FALSE; // Optimal routes aren't computed, yet. + + // Compute and compress the routing information. + // + WorldGraph.ComputeStaticRoutingTables(); + +// save the node graph for this level + WorldGraph.FSaveGraph( (char *)STRING( gpGlobals->mapname ) ); + ALERT( at_console, "Done.\n"); +} + + +//========================================================= +// returns a hardcoded path. +//========================================================= +void CTestHull :: PathFind ( void ) +{ + int iPath[ 50 ]; + int iPathSize; + int i; + CNode *pNode, *pNextNode; + + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return; + } + + iPathSize = WorldGraph.FindShortestPath ( iPath, 0, 19, 0, 0 ); // UNDONE use hull constant + + if ( !iPathSize ) + { + ALERT ( at_aiconsole, "No Path!\n" ); + return; + } + + ALERT ( at_aiconsole, "%d\n", iPathSize ); + + pNode = &WorldGraph.m_pNodes[ iPath [ 0 ] ]; + + for ( i = 0 ; i < iPathSize - 1 ; i++ ) + { + + pNextNode = &WorldGraph.m_pNodes[ iPath [ i + 1 ] ]; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SHOWLINE); + + WRITE_COORD( pNode->m_vecOrigin.x ); + WRITE_COORD( pNode->m_vecOrigin.y ); + WRITE_COORD( pNode->m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( pNextNode->m_vecOrigin.x); + WRITE_COORD( pNextNode->m_vecOrigin.y); + WRITE_COORD( pNextNode->m_vecOrigin.z + NODE_HEIGHT); + MESSAGE_END(); + + pNode = pNextNode; + } + +} + + +//========================================================= +// CStack Constructor +//========================================================= +CStack :: CStack( void ) +{ + m_level = 0; +} + +//========================================================= +// pushes a value onto the stack +//========================================================= +void CStack :: Push( int value ) +{ + if ( m_level >= MAX_STACK_NODES ) + { + printf("Error!\n"); + return; + } + m_stack[m_level] = value; + m_level++; +} + +//========================================================= +// pops a value off of the stack +//========================================================= +int CStack :: Pop( void ) +{ + if ( m_level <= 0 ) + return -1; + + m_level--; + return m_stack[ m_level ]; +} + +//========================================================= +// returns the value on the top of the stack +//========================================================= +int CStack :: Top ( void ) +{ + return m_stack[ m_level - 1 ]; +} + +//========================================================= +// copies every element on the stack into an array LIFO +//========================================================= +void CStack :: CopyToArray ( int *piArray ) +{ + int i; + + for ( i = 0 ; i < m_level ; i++ ) + { + piArray[ i ] = m_stack[ i ]; + } +} + +//========================================================= +// CQueue constructor +//========================================================= +CQueue :: CQueue( void ) +{ + m_cSize = 0; + m_head = 0; + m_tail = -1; +} + +//========================================================= +// inserts a value into the queue +//========================================================= +void CQueue :: Insert ( int iValue, float fPriority ) +{ + + if ( Full() ) + { + printf ( "Queue is full!\n" ); + return; + } + + m_tail++; + + if ( m_tail == MAX_STACK_NODES ) + {//wrap around + m_tail = 0; + } + + m_queue[ m_tail ].Id = iValue; + m_queue[ m_tail ].Priority = fPriority; + m_cSize++; +} + +//========================================================= +// removes a value from the queue (FIFO) +//========================================================= +int CQueue :: Remove ( float &fPriority ) +{ + if ( m_head == MAX_STACK_NODES ) + {// wrap + m_head = 0; + } + + m_cSize--; + fPriority = m_queue[ m_head ].Priority; + return m_queue[ m_head++ ].Id; +} + +//========================================================= +// CQueue constructor +//========================================================= +CQueuePriority :: CQueuePriority( void ) +{ + m_cSize = 0; +} + +//========================================================= +// inserts a value into the priority queue +//========================================================= +void CQueuePriority :: Insert( int iValue, float fPriority ) +{ + + if ( Full() ) + { + printf ( "Queue is full!\n" ); + return; + } + + m_heap[ m_cSize ].Priority = fPriority; + m_heap[ m_cSize ].Id = iValue; + m_cSize++; + Heap_SiftUp(); +} + +//========================================================= +// removes the smallest item from the priority queue +// +//========================================================= +int CQueuePriority :: Remove( float &fPriority ) +{ + int iReturn = m_heap[ 0 ].Id; + fPriority = m_heap[ 0 ].Priority; + + m_cSize--; + + m_heap[ 0 ] = m_heap[ m_cSize ]; + + Heap_SiftDown(0); + return iReturn; +} + +#define HEAP_LEFT_CHILD(x) (2*(x)+1) +#define HEAP_RIGHT_CHILD(x) (2*(x)+2) +#define HEAP_PARENT(x) (((x)-1)/2) + +void CQueuePriority::Heap_SiftDown(int iSubRoot) +{ + int parent = iSubRoot; + int child = HEAP_LEFT_CHILD(parent); + + struct tag_HEAP_NODE Ref = m_heap[ parent ]; + + while (child < m_cSize) + { + int rightchild = HEAP_RIGHT_CHILD(parent); + if (rightchild < m_cSize) + { + if ( m_heap[ rightchild ].Priority < m_heap[ child ].Priority ) + { + child = rightchild; + } + } + if ( Ref.Priority <= m_heap[ child ].Priority ) + break; + + m_heap[ parent ] = m_heap[ child ]; + parent = child; + child = HEAP_LEFT_CHILD(parent); + } + m_heap[ parent ] = Ref; +} + +void CQueuePriority::Heap_SiftUp(void) +{ + int child = m_cSize-1; + while (child) + { + int parent = HEAP_PARENT(child); + if ( m_heap[ parent ].Priority <= m_heap[ child ].Priority ) + break; + + struct tag_HEAP_NODE Tmp; + Tmp = m_heap[ child ]; + m_heap[ child ] = m_heap[ parent ]; + m_heap[ parent ] = Tmp; + + child = parent; + } +} + +//========================================================= +// CGraph - FLoadGraph - attempts to load a node graph from disk. +// if the current level is maps/snar.bsp, maps/graphs/snar.nod +// will be loaded. If file cannot be loaded, the node tree +// will be created and saved to disk. +//========================================================= +int CGraph :: FLoadGraph ( char *szMapName ) +{ + char szFilename[MAX_PATH]; + int iVersion; + int length; + byte *aMemFile; + byte *pMemFile; + + // make sure the directories have been made + char szDirName[MAX_PATH]; + GET_GAME_DIR( szDirName ); + strcat( szDirName, "/maps" ); + CreateDirectory( szDirName, NULL ); + strcat( szDirName, "/graphs" ); + CreateDirectory( szDirName, NULL ); + + strcpy ( szFilename, "maps/graphs/" ); + strcat ( szFilename, szMapName ); + strcat( szFilename, ".nod" ); + + pMemFile = aMemFile = LOAD_FILE_FOR_ME(szFilename, &length); + + if ( !aMemFile ) + { + return FALSE; + } + else + { + // Read the graph version number + // + length -= sizeof(int); + if (length < 0) goto ShortFile; + memcpy(&iVersion, pMemFile, sizeof(int)); + pMemFile += sizeof(int); + + if ( iVersion != GRAPH_VERSION ) + { + // This file was written by a different build of the dll! + // + ALERT ( at_aiconsole, "**ERROR** Graph version is %d, expected %d\n",iVersion, GRAPH_VERSION ); + goto ShortFile; + } + + // Read the graph class + // + length -= sizeof(CGraph); + if (length < 0) goto ShortFile; + memcpy(this, pMemFile, sizeof(CGraph)); + pMemFile += sizeof(CGraph); + + // Set the pointers to zero, just in case we run out of memory. + // + m_pNodes = NULL; + m_pLinkPool = NULL; + m_di = NULL; + m_pRouteInfo = NULL; + m_pHashLinks = NULL; + + + // Malloc for the nodes + // + m_pNodes = ( CNode * )calloc ( sizeof ( CNode ), m_cNodes ); + + if ( !m_pNodes ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d nodes!\n", m_cNodes ); + goto NoMemory; + } + + // Read in all the nodes + // + length -= sizeof(CNode) * m_cNodes; + if (length < 0) goto ShortFile; + memcpy(m_pNodes, pMemFile, sizeof(CNode)*m_cNodes); + pMemFile += sizeof(CNode) * m_cNodes; + + + // Malloc for the link pool + // + m_pLinkPool = ( CLink * )calloc ( sizeof ( CLink ), m_cLinks ); + + if ( !m_pLinkPool ) + { + ALERT ( at_aiconsole, "**ERROR**\nCouldn't malloc %d link!\n", m_cLinks ); + goto NoMemory; + } + + // Read in all the links + // + length -= sizeof(CLink)*m_cLinks; + if (length < 0) goto ShortFile; + memcpy(m_pLinkPool, pMemFile, sizeof(CLink)*m_cLinks); + pMemFile += sizeof(CLink)*m_cLinks; + + // Malloc for the sorting info. + // + m_di = (DIST_INFO *)calloc( sizeof(DIST_INFO), m_cNodes ); + if ( !m_di ) + { + ALERT ( at_aiconsole, "***ERROR**\nCouldn't malloc %d entries sorting nodes!\n", m_cNodes ); + goto NoMemory; + } + + // Read it in. + // + length -= sizeof(DIST_INFO)*m_cNodes; + if (length < 0) goto ShortFile; + memcpy(m_di, pMemFile, sizeof(DIST_INFO)*m_cNodes); + pMemFile += sizeof(DIST_INFO)*m_cNodes; + + // Malloc for the routing info. + // + m_fRoutingComplete = FALSE; + m_pRouteInfo = (char *)calloc( sizeof(char), m_nRouteInfo ); + if ( !m_pRouteInfo ) + { + ALERT ( at_aiconsole, "***ERROR**\nCounldn't malloc %d route bytes!\n", m_nRouteInfo ); + goto NoMemory; + } + m_CheckedCounter = 0; + for (int i = 0; i < m_cNodes; i++) + { + m_di[i].m_CheckedEvent = 0; + } + + // Read in the route information. + // + length -= sizeof(char)*m_nRouteInfo; + if (length < 0) goto ShortFile; + memcpy(m_pRouteInfo, pMemFile, sizeof(char)*m_nRouteInfo); + pMemFile += sizeof(char)*m_nRouteInfo; + m_fRoutingComplete = TRUE;; + + // malloc for the hash links + // + m_pHashLinks = (short *)calloc(sizeof(short), m_nHashLinks); + if (!m_pHashLinks) + { + ALERT ( at_aiconsole, "***ERROR**\nCounldn't malloc %d hash link bytes!\n", m_nHashLinks ); + goto NoMemory; + } + + // Read in the hash link information + // + length -= sizeof(short)*m_nHashLinks; + if (length < 0) goto ShortFile; + memcpy(m_pHashLinks, pMemFile, sizeof(short)*m_nHashLinks); + pMemFile += sizeof(short)*m_nHashLinks; + + // Set the graph present flag, clear the pointers set flag + // + m_fGraphPresent = TRUE; + m_fGraphPointersSet = FALSE; + + FREE_FILE(aMemFile); + + if (length != 0) + { + ALERT ( at_aiconsole, "***WARNING***:Node graph was longer than expected by %d bytes.!\n", length); + } + + return TRUE; + } + +ShortFile: +NoMemory: + FREE_FILE(aMemFile); + return FALSE; +} + +//========================================================= +// CGraph - FSaveGraph - It's not rocket science. +// this WILL overwrite existing files. +//========================================================= +int CGraph :: FSaveGraph ( char *szMapName ) +{ + + int iVersion = GRAPH_VERSION; + char szFilename[MAX_PATH]; + FILE *file; + + if ( !m_fGraphPresent || !m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_aiconsole, "Graph not ready!\n" ); + return FALSE; + } + + // make sure directories have been made + GET_GAME_DIR( szFilename ); + strcat( szFilename, "/maps" ); + CreateDirectory( szFilename, NULL ); + strcat( szFilename, "/graphs" ); + CreateDirectory( szFilename, NULL ); + + strcat( szFilename, "/" ); + strcat( szFilename, szMapName ); + strcat( szFilename, ".nod" ); + + file = fopen ( szFilename, "wb" ); + + ALERT ( at_aiconsole, "Created: %s\n", szFilename ); + + if ( !file ) + {// couldn't create + ALERT ( at_aiconsole, "Couldn't Create: %s\n", szFilename ); + return FALSE; + } + else + { + // write the version + fwrite ( &iVersion, sizeof ( int ), 1, file ); + + // write the CGraph class + fwrite ( this, sizeof ( CGraph ), 1, file ); + + // write the nodes + fwrite ( m_pNodes, sizeof ( CNode ), m_cNodes, file ); + + // write the links + fwrite ( m_pLinkPool, sizeof ( CLink ), m_cLinks, file ); + + fwrite ( m_di, sizeof(DIST_INFO), m_cNodes, file ); + + // Write the route info. + // + if ( m_pRouteInfo && m_nRouteInfo ) + { + fwrite ( m_pRouteInfo, sizeof( char ), m_nRouteInfo, file ); + } + + if (m_pHashLinks && m_nHashLinks) + { + fwrite(m_pHashLinks, sizeof(short), m_nHashLinks, file); + } + fclose ( file ); + return TRUE; + } +} + +//========================================================= +// CGraph - FSetGraphPointers - Takes the modelnames of +// all of the brush ents that block connections in the node +// graph and resolves them into pointers to those entities. +// this is done after loading the graph from disk, whereupon +// the pointers are not valid. +//========================================================= +int CGraph :: FSetGraphPointers ( void ) +{ + int i; + edict_t *pentLinkEnt; + + for ( i = 0 ; i < m_cLinks ; i++ ) + {// go through all of the links + + if ( m_pLinkPool[ i ].m_pLinkEnt != NULL ) + { + char name[5]; + // when graphs are saved, any valid pointers are will be non-zero, signifying that we should + // reset those pointers upon reloading. Any pointers that were NULL when the graph was saved + // will be NULL when reloaded, and will ignored by this function. + + // m_szLinkEntModelname is not necessarily NULL terminated (so we can store it in a more alignment-friendly 4 bytes) + memcpy( name, m_pLinkPool[ i ].m_szLinkEntModelname, 4 ); + name[4] = 0; + pentLinkEnt = FIND_ENTITY_BY_STRING( NULL, "model", name ); + + if ( FNullEnt ( pentLinkEnt ) ) + { + // the ent isn't around anymore? Either there is a major problem, or it was removed from the world + // ( like a func_breakable that's been destroyed or something ). Make sure that LinkEnt is null. + ALERT ( at_aiconsole, "**Could not find model %s\n", name ); + m_pLinkPool[ i ].m_pLinkEnt = NULL; + } + else + { + m_pLinkPool[ i ].m_pLinkEnt = VARS( pentLinkEnt ); + + if ( !FBitSet( m_pLinkPool[ i ].m_pLinkEnt->flags, FL_GRAPHED ) ) + { + m_pLinkPool[ i ].m_pLinkEnt->flags += FL_GRAPHED; + } + } + } + } + + // the pointers are now set. + m_fGraphPointersSet = TRUE; + return TRUE; +} + +//========================================================= +// CGraph - CheckNODFile - this function checks the date of +// the BSP file that was just loaded and the date of the a +// ssociated .NOD file. If the NOD file is not present, or +// is older than the BSP file, we rebuild it. +// +// returns FALSE if the .NOD file doesn't qualify and needs +// to be rebuilt. +// +// !!!BUGBUG - the file times we get back are 20 hours ahead! +// since this happens consistantly, we can still correctly +// determine which of the 2 files is newer. This needs fixed, +// though. ( I now suspect that we are getting GMT back from +// these functions and must compensate for local time ) (sjb) +//========================================================= +int CGraph :: CheckNODFile ( char *szMapName ) +{ + int retValue; + + char szBspFilename[MAX_PATH]; + char szGraphFilename[MAX_PATH]; + + + strcpy ( szBspFilename, "maps/" ); + strcat ( szBspFilename, szMapName ); + strcat ( szBspFilename, ".bsp" ); + + strcpy ( szGraphFilename, "maps/graphs/" ); + strcat ( szGraphFilename, szMapName ); + strcat ( szGraphFilename, ".nod" ); + + retValue = TRUE; + + int iCompare; + if (COMPARE_FILE_TIME(szBspFilename, szGraphFilename, &iCompare)) + { + if ( iCompare > 0 ) + {// BSP file is newer. + ALERT ( at_aiconsole, ".NOD File will be updated\n\n" ); + retValue = FALSE; + } + } + else + { + retValue = FALSE; + } + + return retValue; +} + +#define ENTRY_STATE_EMPTY -1 + +struct tagNodePair +{ + short iSrc; + short iDest; +}; + +void CGraph::HashInsert(int iSrcNode, int iDestNode, int iKey) +{ + struct tagNodePair np; + + np.iSrc = iSrcNode; + np.iDest = iDestNode; + CRC32_t dwHash; + CRC32_INIT(&dwHash); + CRC32_PROCESS_BUFFER(&dwHash, &np, sizeof(np)); + dwHash = CRC32_FINAL(dwHash); + + int di = m_HashPrimes[dwHash&15]; + int i = (dwHash >> 4) % m_nHashLinks; + while (m_pHashLinks[i] != ENTRY_STATE_EMPTY) + { + i += di; + if (i >= m_nHashLinks) i -= m_nHashLinks; + } + m_pHashLinks[i] = iKey; +} + +void CGraph::HashSearch(int iSrcNode, int iDestNode, int &iKey) +{ + struct tagNodePair np; + + np.iSrc = iSrcNode; + np.iDest = iDestNode; + CRC32_t dwHash; + CRC32_INIT(&dwHash); + CRC32_PROCESS_BUFFER(&dwHash, &np, sizeof(np)); + dwHash = CRC32_FINAL(dwHash); + + int di = m_HashPrimes[dwHash&15]; + int i = (dwHash >> 4) % m_nHashLinks; + while (m_pHashLinks[i] != ENTRY_STATE_EMPTY) + { + CLink &link = Link(m_pHashLinks[i]); + if (iSrcNode == link.m_iSrcNode && iDestNode == link.m_iDestNode) + { + break; + } + else + { + i += di; + if (i >= m_nHashLinks) i -= m_nHashLinks; + } + } + iKey = m_pHashLinks[i]; +} + +#define NUMBER_OF_PRIMES 177 + +int Primes[NUMBER_OF_PRIMES] = +{ 1, 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, +71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, +157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, +241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, +347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, +439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, +547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, +643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, +751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, +859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, +977, 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, 1039, 0 }; + +void CGraph::HashChoosePrimes(int TableSize) +{ + int LargestPrime = TableSize/2; + if (LargestPrime > Primes[NUMBER_OF_PRIMES-2]) + { + LargestPrime = Primes[NUMBER_OF_PRIMES-2]; + } + int Spacing = LargestPrime/16; + + // Pick a set primes that are evenly spaced from (0 to LargestPrime) + // We divide this interval into 16 equal sized zones. We want to find + // one prime number that best represents that zone. + // + for (int iZone = 1, iPrime = 0; iPrime < 16; iZone += Spacing) + { + // Search for a prime number that is less than the target zone + // number given by iZone. + // + int Lower = Primes[0]; + for (int jPrime = 0; Primes[jPrime] != 0; jPrime++) + { + if (jPrime != 0 && TableSize % Primes[jPrime] == 0) continue; + int Upper = Primes[jPrime]; + if (Lower <= iZone && iZone <= Upper) + { + // Choose the closest lower prime number. + // + if (iZone - Lower <= Upper - iZone) + { + m_HashPrimes[iPrime++] = Lower; + } + else + { + m_HashPrimes[iPrime++] = Upper; + } + break; + } + Lower = Upper; + } + } + + // Alternate negative and positive numbers + // + for (iPrime = 0; iPrime < 16; iPrime += 2) + { + m_HashPrimes[iPrime] = TableSize-m_HashPrimes[iPrime]; + } + + // Shuffle the set of primes to reduce correlation with bits in + // hash key. + // + for (iPrime = 0; iPrime < 16-1; iPrime++) + { + int Pick = RANDOM_LONG(0, 15-iPrime); + int Temp = m_HashPrimes[Pick]; + m_HashPrimes[Pick] = m_HashPrimes[15-iPrime]; + m_HashPrimes[15-iPrime] = Temp; + } +} + +// Renumber nodes so that nodes that link together are together. +// +#define UNNUMBERED_NODE -1 +void CGraph::SortNodes(void) +{ + // We are using m_iPreviousNode to be the new node number. + // After assigning new node numbers to everything, we move + // things and patchup the links. + // + int iNodeCnt = 0; + m_pNodes[0].m_iPreviousNode = iNodeCnt++; + for (int i = 1; i < m_cNodes; i++) + { + m_pNodes[i].m_iPreviousNode = UNNUMBERED_NODE; + } + + for (i = 0; i < m_cNodes; i++) + { + // Run through all of this node's neighbors + // + for (int j = 0 ; j < m_pNodes[i].m_cNumLinks; j++ ) + { + int iDestNode = INodeLink(i, j); + if (m_pNodes[iDestNode].m_iPreviousNode == UNNUMBERED_NODE) + { + m_pNodes[iDestNode].m_iPreviousNode = iNodeCnt++; + } + } + } + + // Assign remaining node numbers to unlinked nodes. + // + for (i = 0; i < m_cNodes; i++) + { + if (m_pNodes[i].m_iPreviousNode == UNNUMBERED_NODE) + { + m_pNodes[i].m_iPreviousNode = iNodeCnt++; + } + } + + // Alter links to reflect new node numbers. + // + for (i = 0; i < m_cLinks; i++) + { + m_pLinkPool[i].m_iSrcNode = m_pNodes[m_pLinkPool[i].m_iSrcNode].m_iPreviousNode; + m_pLinkPool[i].m_iDestNode = m_pNodes[m_pLinkPool[i].m_iDestNode].m_iPreviousNode; + } + + // Rearrange nodes to reflect new node numbering. + // + for (i = 0; i < m_cNodes; i++) + { + while (m_pNodes[i].m_iPreviousNode != i) + { + // Move current node off to where it should be, and bring + // that other node back into the current slot. + // + int iDestNode = m_pNodes[i].m_iPreviousNode; + CNode TempNode = m_pNodes[iDestNode]; + m_pNodes[iDestNode] = m_pNodes[i]; + m_pNodes[i] = TempNode; + } + } +} + +void CGraph::BuildLinkLookups(void) +{ + m_nHashLinks = 3*m_cLinks/2 + 3; + + HashChoosePrimes(m_nHashLinks); + m_pHashLinks = (short *)calloc(sizeof(short), m_nHashLinks); + if (!m_pHashLinks) + { + ALERT(at_aiconsole, "Couldn't allocated Link Lookup Table.\n"); + return; + } + for (int i = 0; i < m_nHashLinks; i++) + { + m_pHashLinks[i] = ENTRY_STATE_EMPTY; + } + + for (i = 0; i < m_cLinks; i++) + { + CLink &link = Link(i); + HashInsert(link.m_iSrcNode, link.m_iDestNode, i); + } +#if 0 + for (i = 0; i < m_cLinks; i++) + { + CLink &link = Link(i); + int iKey; + HashSearch(link.m_iSrcNode, link.m_iDestNode, iKey); + if (iKey != i) + { + ALERT(at_aiconsole, "HashLinks don't match (%d versus %d)\n", i, iKey); + } + } +#endif +} + +void CGraph::BuildRegionTables(void) +{ + if (m_di) free(m_di); + + // Go ahead and setup for range searching the nodes for FindNearestNodes + // + m_di = (DIST_INFO *)calloc(sizeof(DIST_INFO), m_cNodes); + if (!m_di) + { + ALERT(at_aiconsole, "Couldn't allocated node ordering array.\n"); + return; + } + + // Calculate regions for all the nodes. + // + // + for (int i = 0; i < 3; i++) + { + m_RegionMin[i] = 999999999.0; // just a big number out there; + m_RegionMax[i] = -999999999.0; // just a big number out there; + } + for (i = 0; i < m_cNodes; i++) + { + if (m_pNodes[i].m_vecOrigin.x < m_RegionMin[0]) + m_RegionMin[0] = m_pNodes[i].m_vecOrigin.x; + if (m_pNodes[i].m_vecOrigin.y < m_RegionMin[1]) + m_RegionMin[1] = m_pNodes[i].m_vecOrigin.y; + if (m_pNodes[i].m_vecOrigin.z < m_RegionMin[2]) + m_RegionMin[2] = m_pNodes[i].m_vecOrigin.z; + + if (m_pNodes[i].m_vecOrigin.x > m_RegionMax[0]) + m_RegionMax[0] = m_pNodes[i].m_vecOrigin.x; + if (m_pNodes[i].m_vecOrigin.y > m_RegionMax[1]) + m_RegionMax[1] = m_pNodes[i].m_vecOrigin.y; + if (m_pNodes[i].m_vecOrigin.z > m_RegionMax[2]) + m_RegionMax[2] = m_pNodes[i].m_vecOrigin.z; + } + for (i = 0; i < m_cNodes; i++) + { + m_pNodes[i].m_Region[0] = CALC_RANGE(m_pNodes[i].m_vecOrigin.x, m_RegionMin[0], m_RegionMax[0]); + m_pNodes[i].m_Region[1] = CALC_RANGE(m_pNodes[i].m_vecOrigin.y, m_RegionMin[1], m_RegionMax[1]); + m_pNodes[i].m_Region[2] = CALC_RANGE(m_pNodes[i].m_vecOrigin.z, m_RegionMin[2], m_RegionMax[2]); + } + + for (i = 0; i < 3; i++) + { + for (int j = 0; j < NUM_RANGES; j++) + { + m_RangeStart[i][j] = 255; + m_RangeEnd[i][j] = 0; + } + for (j = 0; j < m_cNodes; j++) + { + m_di[j].m_SortedBy[i] = j; + } + + for (j = 0; j < m_cNodes - 1; j++) + { + int jNode = m_di[j].m_SortedBy[i]; + int jCodeX = m_pNodes[jNode].m_Region[0]; + int jCodeY = m_pNodes[jNode].m_Region[1]; + int jCodeZ = m_pNodes[jNode].m_Region[2]; + int jCode; + switch (i) + { + case 0: + jCode = (jCodeX << 16) + (jCodeY << 8) + jCodeZ; + break; + case 1: + jCode = (jCodeY << 16) + (jCodeZ << 8) + jCodeX; + break; + case 2: + jCode = (jCodeZ << 16) + (jCodeX << 8) + jCodeY; + break; + } + + for (int k = j+1; k < m_cNodes; k++) + { + int kNode = m_di[k].m_SortedBy[i]; + int kCodeX = m_pNodes[kNode].m_Region[0]; + int kCodeY = m_pNodes[kNode].m_Region[1]; + int kCodeZ = m_pNodes[kNode].m_Region[2]; + int kCode; + switch (i) + { + case 0: + kCode = (kCodeX << 16) + (kCodeY << 8) + kCodeZ; + break; + case 1: + kCode = (kCodeY << 16) + (kCodeZ << 8) + kCodeX; + break; + case 2: + kCode = (kCodeZ << 16) + (kCodeX << 8) + kCodeY; + break; + } + + if (kCode < jCode) + { + // Swap j and k entries. + // + int Tmp = m_di[j].m_SortedBy[i]; + m_di[j].m_SortedBy[i] = m_di[k].m_SortedBy[i]; + m_di[k].m_SortedBy[i] = Tmp; + } + } + } + } + + // Generate lookup tables. + // + for (i = 0; i < m_cNodes; i++) + { + int CodeX = m_pNodes[m_di[i].m_SortedBy[0]].m_Region[0]; + int CodeY = m_pNodes[m_di[i].m_SortedBy[1]].m_Region[1]; + int CodeZ = m_pNodes[m_di[i].m_SortedBy[2]].m_Region[2]; + + if (i < m_RangeStart[0][CodeX]) + { + m_RangeStart[0][CodeX] = i; + } + if (i < m_RangeStart[1][CodeY]) + { + m_RangeStart[1][CodeY] = i; + } + if (i < m_RangeStart[2][CodeZ]) + { + m_RangeStart[2][CodeZ] = i; + } + if (m_RangeEnd[0][CodeX] < i) + { + m_RangeEnd[0][CodeX] = i; + } + if (m_RangeEnd[1][CodeY] < i) + { + m_RangeEnd[1][CodeY] = i; + } + if (m_RangeEnd[2][CodeZ] < i) + { + m_RangeEnd[2][CodeZ] = i; + } + } + + // Initialize the cache. + // + memset(m_Cache, 0, sizeof(m_Cache)); +} + +void CGraph :: ComputeStaticRoutingTables( void ) +{ + int nRoutes = m_cNodes*m_cNodes; +#define FROM_TO(x,y) ((x)*m_cNodes+(y)) + short *Routes = new short[nRoutes]; + + int *pMyPath = new int[m_cNodes]; + unsigned short *BestNextNodes = new unsigned short[m_cNodes]; + char *pRoute = new char[m_cNodes*2]; + + + if (Routes && pMyPath && BestNextNodes && pRoute) + { + int nTotalCompressedSize = 0; + for (int iHull = 0; iHull < MAX_NODE_HULLS; iHull++) + { + for (int iCap = 0; iCap < 2; iCap++) + { + int iCapMask; + switch (iCap) + { + case 0: + iCapMask = 0; + break; + + case 1: + iCapMask = bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + break; + } + + + // Initialize Routing table to uncalculated. + // + for (int iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + Routes[FROM_TO(iFrom, iTo)] = -1; + } + } + + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = m_cNodes-1; iTo >= 0; iTo--) + { + if (Routes[FROM_TO(iFrom, iTo)] != -1) continue; + + int cPathSize = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + + // Use the computed path to update the routing table. + // + if (cPathSize > 1) + { + for (int iNode = 0; iNode < cPathSize-1; iNode++) + { + int iStart = pMyPath[iNode]; + int iNext = pMyPath[iNode+1]; + for (int iNode1 = iNode+1; iNode1 < cPathSize; iNode1++) + { + int iEnd = pMyPath[iNode1]; + Routes[FROM_TO(iStart, iEnd)] = iNext; + } + } +#if 0 + // Well, at first glance, this should work, but actually it's safer + // to be told explictly that you can take a series of node in a + // particular direction. Some links don't appear to have links in + // the opposite direction. + // + for (iNode = cPathSize-1; iNode >= 1; iNode--) + { + int iStart = pMyPath[iNode]; + int iNext = pMyPath[iNode-1]; + for (int iNode1 = iNode-1; iNode1 >= 0; iNode1--) + { + int iEnd = pMyPath[iNode1]; + Routes[FROM_TO(iStart, iEnd)] = iNext; + } + } +#endif + } + else + { + Routes[FROM_TO(iFrom, iTo)] = iFrom; + Routes[FROM_TO(iTo, iFrom)] = iTo; + } + } + } + + for (iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + BestNextNodes[iTo] = Routes[FROM_TO(iFrom, iTo)]; + } + + // Compress this node's routing table. + // + int iLastNode = 9999999; // just really big. + int cSequence = 0; + int cRepeats = 0; + int CompressedSize = 0; + char *p = pRoute; + for (int i = 0; i < m_cNodes; i++) + { + BOOL CanRepeat = ((BestNextNodes[i] == iLastNode) && cRepeats < 127); + BOOL CanSequence = (BestNextNodes[i] == i && cSequence < 128); + + if (cRepeats) + { + if (CanRepeat) + { + cRepeats++; + } + else + { + // Emit the repeat phrase. + // + CompressedSize += 2; // (count-1, iLastNode-i) + *p++ = cRepeats - 1; + int a = iLastNode - iFrom; + int b = iLastNode - iFrom + m_cNodes; + int c = iLastNode - iFrom - m_cNodes; + if (-128 <= a && a <= 127) + { + *p++ = a; + } + else if (-128 <= b && b <= 127) + { + *p++ = b; + } + else if (-128 <= c && c <= 127) + { + *p++ = c; + } + else + { + ALERT( at_aiconsole, "Nodes need sorting (%d,%d)!\n", iLastNode, iFrom); + } + cRepeats = 0; + + if (CanSequence) + { + // Start a sequence. + // + cSequence++; + } + else + { + // Start another repeat. + // + cRepeats++; + } + } + } + else if (cSequence) + { + if (CanSequence) + { + cSequence++; + } + else + { + // It may be advantageous to combine + // a single-entry sequence phrase with the + // next repeat phrase. + // + if (cSequence == 1 && CanRepeat) + { + // Combine with repeat phrase. + // + cRepeats = 2; + cSequence = 0; + } + else + { + // Emit the sequence phrase. + // + CompressedSize += 1; // (-count) + *p++ = -cSequence; + cSequence = 0; + + // Start a repeat sequence. + // + cRepeats++; + } + } + } + else + { + if (CanSequence) + { + // Start a sequence phrase. + // + cSequence++; + } + else + { + // Start a repeat sequence. + // + cRepeats++; + } + } + iLastNode = BestNextNodes[i]; + } + if (cRepeats) + { + // Emit the repeat phrase. + // + CompressedSize += 2; + *p++ = cRepeats - 1; +#if 0 + iLastNode = iFrom + *pRoute; + if (iLastNode >= m_cNodes) iLastNode -= m_cNodes; + else if (iLastNode < 0) iLastNode += m_cNodes; +#endif + int a = iLastNode - iFrom; + int b = iLastNode - iFrom + m_cNodes; + int c = iLastNode - iFrom - m_cNodes; + if (-128 <= a && a <= 127) + { + *p++ = a; + } + else if (-128 <= b && b <= 127) + { + *p++ = b; + } + else if (-128 <= c && c <= 127) + { + *p++ = c; + } + else + { + ALERT( at_aiconsole, "Nodes need sorting (%d,%d)!\n", iLastNode, iFrom); + } + } + if (cSequence) + { + // Emit the Sequence phrase. + // + CompressedSize += 1; + *p++ = -cSequence; + } + + // Go find a place to store this thing and point to it. + // + int nRoute = p - pRoute; + if (m_pRouteInfo) + { + for (int i = 0; i < m_nRouteInfo - nRoute; i++) + { + if (memcmp(m_pRouteInfo + i, pRoute, nRoute) == 0) + { + break; + } + } + if (i < m_nRouteInfo - nRoute) + { + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = i; + } + else + { + char *Tmp = (char *)calloc(sizeof(char), (m_nRouteInfo + nRoute)); + memcpy(Tmp, m_pRouteInfo, m_nRouteInfo); + free(m_pRouteInfo); + m_pRouteInfo = Tmp; + memcpy(m_pRouteInfo + m_nRouteInfo, pRoute, nRoute); + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = m_nRouteInfo; + m_nRouteInfo += nRoute; + nTotalCompressedSize += CompressedSize; + } + } + else + { + m_nRouteInfo = nRoute; + m_pRouteInfo = (char *)calloc(sizeof(char), nRoute); + memcpy(m_pRouteInfo, pRoute, nRoute); + m_pNodes[ iFrom ].m_pNextBestNode[iHull][iCap] = 0; + nTotalCompressedSize += CompressedSize; + } + } + } + } + ALERT( at_aiconsole, "Size of Routes = %d\n", nTotalCompressedSize); + } + if (Routes) delete Routes; + if (BestNextNodes) delete BestNextNodes; + if (pRoute) delete pRoute; + if (pMyPath) delete pMyPath; + Routes = 0; + BestNextNodes = 0; + pRoute = 0; + pMyPath = 0; + +#if 0 + TestRoutingTables(); +#endif + m_fRoutingComplete = TRUE; +} + +// Test those routing tables. Doesn't really work, yet. +// +void CGraph :: TestRoutingTables( void ) +{ + int *pMyPath = new int[m_cNodes]; + int *pMyPath2 = new int[m_cNodes]; + if (pMyPath && pMyPath2) + { + for (int iHull = 0; iHull < MAX_NODE_HULLS; iHull++) + { + for (int iCap = 0; iCap < 2; iCap++) + { + int iCapMask; + switch (iCap) + { + case 0: + iCapMask = 0; + break; + + case 1: + iCapMask = bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + break; + } + + for (int iFrom = 0; iFrom < m_cNodes; iFrom++) + { + for (int iTo = 0; iTo < m_cNodes; iTo++) + { + m_fRoutingComplete = FALSE; + int cPathSize1 = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + m_fRoutingComplete = TRUE; + int cPathSize2 = FindShortestPath(pMyPath2, iFrom, iTo, iHull, iCapMask); + + // Unless we can look at the entire path, we can verify that it's correct. + // + if (cPathSize2 == MAX_PATH_SIZE) continue; + + // Compare distances. + // +#if 1 + float flDistance1 = 0.0; + for (int i = 0; i < cPathSize1-1; i++) + { + // Find the link from pMyPath[i] to pMyPath[i+1] + // + if (pMyPath[i] == pMyPath[i+1]) continue; + int iVisitNode; + BOOL bFound = FALSE; + for (int iLink = 0; iLink < m_pNodes[pMyPath[i]].m_cNumLinks; iLink++) + { + iVisitNode = INodeLink ( pMyPath[i], iLink ); + if (iVisitNode == pMyPath[i+1]) + { + flDistance1 += m_pLinkPool[ m_pNodes[ pMyPath[i] ].m_iFirstLink + iLink].m_flWeight; + bFound = TRUE; + break; + } + } + if (!bFound) + { + ALERT(at_aiconsole, "No link.\n"); + } + } + + float flDistance2 = 0.0; + for (i = 0; i < cPathSize2-1; i++) + { + // Find the link from pMyPath2[i] to pMyPath2[i+1] + // + if (pMyPath2[i] == pMyPath2[i+1]) continue; + int iVisitNode; + BOOL bFound = FALSE; + for (int iLink = 0; iLink < m_pNodes[pMyPath2[i]].m_cNumLinks; iLink++) + { + iVisitNode = INodeLink ( pMyPath2[i], iLink ); + if (iVisitNode == pMyPath2[i+1]) + { + flDistance2 += m_pLinkPool[ m_pNodes[ pMyPath2[i] ].m_iFirstLink + iLink].m_flWeight; + bFound = TRUE; + break; + } + } + if (!bFound) + { + ALERT(at_aiconsole, "No link.\n"); + } + } + if (fabs(flDistance1 - flDistance2) > 0.10) + { +#else + if (cPathSize1 != cPathSize2 || memcmp(pMyPath, pMyPath2, sizeof(int)*cPathSize1) != 0) + { +#endif + ALERT(at_aiconsole, "Routing is inconsistent!!!\n"); + ALERT(at_aiconsole, "(%d to %d |%d/%d)1:", iFrom, iTo, iHull, iCap); + for (int i = 0; i < cPathSize1; i++) + { + ALERT(at_aiconsole, "%d ", pMyPath[i]); + } + ALERT(at_aiconsole, "\n(%d to %d |%d/%d)2:", iFrom, iTo, iHull, iCap); + for (i = 0; i < cPathSize2; i++) + { + ALERT(at_aiconsole, "%d ", pMyPath2[i]); + } + ALERT(at_aiconsole, "\n"); + m_fRoutingComplete = FALSE; + cPathSize1 = FindShortestPath(pMyPath, iFrom, iTo, iHull, iCapMask); + m_fRoutingComplete = TRUE; + cPathSize2 = FindShortestPath(pMyPath2, iFrom, iTo, iHull, iCapMask); + goto EnoughSaid; + } + } + } + } + } + } + +EnoughSaid: + + if (pMyPath) delete pMyPath; + if (pMyPath2) delete pMyPath2; + pMyPath = 0; + pMyPath2 = 0; +} + + + + + + + + + +//========================================================= +// CNodeViewer - Draws a graph of the shorted path from all nodes +// to current location (typically the player). It then draws +// as many connects as it can per frame, trying not to overflow the buffer +//========================================================= +class CNodeViewer : public CBaseEntity +{ +public: + void Spawn( void ); + + int m_iBaseNode; + int m_iDraw; + int m_nVisited; + int m_aFrom[128]; + int m_aTo[128]; + int m_iHull; + int m_afNodeType; + Vector m_vecColor; + + void FindNodeConnections( int iNode ); + void AddNode( int iFrom, int iTo ); + void EXPORT DrawThink( void ); + +}; +LINK_ENTITY_TO_CLASS( node_viewer, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_human, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_fly, CNodeViewer ); +LINK_ENTITY_TO_CLASS( node_viewer_large, CNodeViewer ); + +void CNodeViewer::Spawn( ) +{ + if ( !WorldGraph.m_fGraphPresent || !WorldGraph.m_fGraphPointersSet ) + {// protect us in the case that the node graph isn't available or built + ALERT ( at_console, "Graph not ready!\n" ); + UTIL_Remove( this ); + return; + } + + + if (FClassnameIs( pev, "node_viewer_fly")) + { + m_iHull = NODE_FLY_HULL; + m_afNodeType = bits_NODE_AIR; + m_vecColor = Vector( 160, 100, 255 ); + } + else if (FClassnameIs( pev, "node_viewer_large")) + { + m_iHull = NODE_LARGE_HULL; + m_afNodeType = bits_NODE_LAND | bits_NODE_WATER; + m_vecColor = Vector( 100, 255, 160 ); + } + else + { + m_iHull = NODE_HUMAN_HULL; + m_afNodeType = bits_NODE_LAND | bits_NODE_WATER; + m_vecColor = Vector( 255, 160, 100 ); + } + + + m_iBaseNode = WorldGraph.FindNearestNode ( pev->origin, m_afNodeType ); + + if ( m_iBaseNode < 0 ) + { + ALERT( at_console, "No nearby node\n" ); + return; + } + + m_nVisited = 0; + + ALERT( at_aiconsole, "basenode %d\n", m_iBaseNode ); + + if (WorldGraph.m_cNodes < 128) + { + for (int i = 0; i < WorldGraph.m_cNodes; i++) + { + AddNode( i, WorldGraph.NextNodeInRoute( i, m_iBaseNode, m_iHull, 0 )); + } + } + else + { + // do a depth traversal + FindNodeConnections( m_iBaseNode ); + + int start = 0; + int end; + do { + end = m_nVisited; + // ALERT( at_console, "%d :", m_nVisited ); + for (end = m_nVisited; start < end; start++) + { + FindNodeConnections( m_aFrom[start] ); + FindNodeConnections( m_aTo[start] ); + } + } while (end != m_nVisited); + } + + ALERT( at_aiconsole, "%d nodes\n", m_nVisited ); + + m_iDraw = 0; + SetThink( DrawThink ); + pev->nextthink = gpGlobals->time; +} + + +void CNodeViewer :: FindNodeConnections ( int iNode ) +{ + AddNode( iNode, WorldGraph.NextNodeInRoute( iNode, m_iBaseNode, m_iHull, 0 )); + for ( int i = 0 ; i < WorldGraph.m_pNodes[ iNode ].m_cNumLinks ; i++ ) + { + CLink *pToLink = &WorldGraph.NodeLink( iNode, i); + AddNode( pToLink->m_iDestNode, WorldGraph.NextNodeInRoute( pToLink->m_iDestNode, m_iBaseNode, m_iHull, 0 )); + } +} + +void CNodeViewer::AddNode( int iFrom, int iTo ) +{ + if (m_nVisited >= 128) + { + return; + } + else + { + if (iFrom == iTo) + return; + + for (int i = 0; i < m_nVisited; i++) + { + if (m_aFrom[i] == iFrom && m_aTo[i] == iTo) + return; + if (m_aFrom[i] == iTo && m_aTo[i] == iFrom) + return; + } + m_aFrom[m_nVisited] = iFrom; + m_aTo[m_nVisited] = iTo; + m_nVisited++; + } +} + + +void CNodeViewer :: DrawThink( void ) +{ + pev->nextthink = gpGlobals->time; + + for (int i = 0; i < 10; i++) + { + if (m_iDraw == m_nVisited) + { + UTIL_Remove( this ); + return; + } + + extern short g_sModelIndexLaser; + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMPOINTS ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.x ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.y ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aFrom[m_iDraw] ].m_vecOrigin.z + NODE_HEIGHT ); + + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.x ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.y ); + WRITE_COORD( WorldGraph.m_pNodes[ m_aTo[m_iDraw] ].m_vecOrigin.z + NODE_HEIGHT ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 250 ); // life + WRITE_BYTE( 40 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( m_vecColor.x ); // r, g, b + WRITE_BYTE( m_vecColor.y ); // r, g, b + WRITE_BYTE( m_vecColor.z ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + m_iDraw++; + } +} + + diff --git a/dlls/nodes.h b/dlls/nodes.h index 4a18cb1..6b6739d 100644 --- a/dlls/nodes.h +++ b/dlls/nodes.h @@ -1,54 +1,374 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. * ****/ //========================================================= // nodes.h //========================================================= -#ifndef NODES_H -#define NODES_H +//========================================================= +// DEFINE +//========================================================= +#define MAX_STACK_NODES 100 +#define NO_NODE -1 +#define MAX_NODE_HULLS 4 -#define bits_NODE_GROUP_REALM 1 +#define bits_NODE_LAND ( 1 << 0 ) // Land node, so nudge if necessary. +#define bits_NODE_AIR ( 1 << 1 ) // Air node, don't nudge. +#define bits_NODE_WATER ( 1 << 2 ) // Water node, don't nudge. +#define bits_NODE_GROUP_REALM (bits_NODE_LAND | bits_NODE_AIR | bits_NODE_WATER) + +//========================================================= +// Instance of a node. +//========================================================= +class CNode +{ +public: + Vector m_vecOrigin;// location of this node in space + Vector m_vecOriginPeek; // location of this node (LAND nodes are NODE_HEIGHT higher). + BYTE m_Region[3]; // Which of 256 regions do each of the coordinate belong? + int m_afNodeInfo;// bits that tell us more about this location + + int m_cNumLinks; // how many links this node has + int m_iFirstLink;// index of this node's first link in the link pool. + + // Where to start looking in the compressed routing table (offset into m_pRouteInfo). + // (4 hull sizes -- smallest to largest + fly/swim), and secondly, door capability. + // + int m_pNextBestNode[MAX_NODE_HULLS][2]; + + // Used in finding the shortest path. m_fClosestSoFar is -1 if not visited. + // Then it is the distance to the source. If another path uses this node + // and has a closer distance, then m_iPreviousNode is also updated. + // + float m_flClosestSoFar; // Used in finding the shortest path. + int m_iPreviousNode; + + short m_sHintType;// there is something interesting in the world at this node's position + short m_sHintActivity;// there is something interesting in the world at this node's position + float m_flHintYaw;// monster on this node should face this yaw to face the hint. +}; + +//========================================================= +// CLink - A link between 2 nodes +//========================================================= +#define bits_LINK_SMALL_HULL ( 1 << 0 )// headcrab box can fit through this connection +#define bits_LINK_HUMAN_HULL ( 1 << 1 )// player box can fit through this connection +#define bits_LINK_LARGE_HULL ( 1 << 2 )// big box can fit through this connection +#define bits_LINK_FLY_HULL ( 1 << 3 )// a flying big box can fit through this connection +#define bits_LINK_DISABLED ( 1 << 4 )// link is not valid when the set + +#define NODE_SMALL_HULL 0 +#define NODE_HUMAN_HULL 1 +#define NODE_LARGE_HULL 2 +#define NODE_FLY_HULL 3 class CLink { public: + int m_iSrcNode;// the node that 'owns' this link ( keeps us from having to make reverse lookups ) + int m_iDestNode;// the node on the other end of the link. + entvars_t *m_pLinkEnt;// the entity that blocks this connection (doors, etc) + + // m_szLinkEntModelname is not necessarily NULL terminated (so we can store it in a more alignment-friendly 4 bytes) + char m_szLinkEntModelname[ 4 ];// the unique name of the brush model that blocks the connection (this is kept for save/restore) + + int m_afLinkInfo;// information about this link + float m_flWeight;// length of the link line segment }; +typedef struct +{ + int m_SortedBy[3]; + int m_CheckedEvent; +} DIST_INFO; + +typedef struct +{ + Vector v; + short n; // Nearest node or -1 if no node found. +} CACHE_ENTRY; + +//========================================================= +// CGraph +//========================================================= +#define GRAPH_VERSION (int)16// !!!increment this whever graph/node/link classes change, to obsolesce older disk files. class CGraph { public: + +// the graph has two flags, and should not be accessed unless both flags are TRUE! BOOL m_fGraphPresent;// is the graph in memory? BOOL m_fGraphPointersSet;// are the entity pointers for the graph all set? + BOOL m_fRoutingComplete; // are the optimal routes computed, yet? - int m_cLinks;// total number of links + CNode *m_pNodes;// pointer to the memory block that contains all node info CLink *m_pLinkPool;// big list of all node connections + char *m_pRouteInfo; // compressed routing information the nodes use. + int m_cNodes;// total number of nodes + int m_cLinks;// total number of links + int m_nRouteInfo; // size of m_pRouteInfo in bytes. + + // Tables for making nearest node lookup faster. SortedBy provided nodes in a + // order of a particular coordinate. Instead of doing a binary search, RangeStart + // and RangeEnd let you get to the part of SortedBy that you are interested in. + // + // Once you have a point of interest, the only way you'll find a closer point is + // if at least one of the coordinates is closer than the ones you have now. So we + // search each range. After the search is exhausted, we know we have the closest + // node. + // +#define CACHE_SIZE 128 +#define NUM_RANGES 256 + DIST_INFO *m_di; // This is m_cNodes long, but the entries don't correspond to CNode entries. + int m_RangeStart[3][NUM_RANGES]; + int m_RangeEnd[3][NUM_RANGES]; + float m_flShortest; + int m_iNearest; + int m_minX, m_minY, m_minZ, m_maxX, m_maxY, m_maxZ; + int m_minBoxX, m_minBoxY, m_minBoxZ, m_maxBoxX, m_maxBoxY, m_maxBoxZ; + int m_CheckedCounter; + float m_RegionMin[3], m_RegionMax[3]; // The range of nodes. + CACHE_ENTRY m_Cache[CACHE_SIZE]; + + + int m_HashPrimes[16]; + short *m_pHashLinks; + int m_nHashLinks; + + + // kinda sleazy. In order to allow variety in active idles for monster groups in a room with more than one node, + // we keep track of the last node we searched from and store it here. Subsequent searches by other monsters will pick + // up where the last search stopped. + int m_iLastActiveIdleSearch; + + // another such system used to track the search for cover nodes, helps greatly with two monsters trying to get to the same node. + int m_iLastCoverSearch; + + // functions to create the graph + int LinkVisibleNodes ( CLink *pLinkPool, FILE *file, int *piBadNode ); + int RejectInlineLinks ( CLink *pLinkPool, FILE *file ); + int FindShortestPath ( int *piPath, int iStart, int iDest, int iHull, int afCapMask); + int FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ); + int FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ); + //int FindNearestLink ( const Vector &vecTestPoint, int *piNearestLink, BOOL *pfAlongLine ); + float PathLength( int iStart, int iDest, int iHull, int afCapMask ); + int NextNodeInRoute( int iCurrentNode, int iDest, int iHull, int iCap ); + + enum NODEQUERY { NODEGRAPH_DYNAMIC, NODEGRAPH_STATIC }; + // A static query means we're asking about the possiblity of handling this entity at ANY time + // A dynamic query means we're asking about it RIGHT NOW. So we should query the current state + int HandleLinkEnt ( int iNode, entvars_t *pevLinkEnt, int afCapMask, NODEQUERY queryType ); + entvars_t* LinkEntForLink ( CLink *pLink, CNode *pNode ); + void ShowNodeConnections ( int iNode ); void InitGraph( void ); int AllocNodes ( void ); int CheckNODFile(char *szMapName); int FLoadGraph(char *szMapName); + int FSaveGraph(char *szMapName); int FSetGraphPointers(void); - void ShowNodeConnections ( int iNode ); - int FindNearestNode ( const Vector &vecOrigin, CBaseEntity *pEntity ); - int FindNearestNode ( const Vector &vecOrigin, int afNodeTypes ); + void CheckNode(Vector vecOrigin, int iNode); + + void BuildRegionTables(void); + void ComputeStaticRoutingTables(void); + void TestRoutingTables(void); + + void HashInsert(int iSrcNode, int iDestNode, int iKey); + void HashSearch(int iSrcNode, int iDestNode, int &iKey); + void HashChoosePrimes(int TableSize); + void BuildLinkLookups(void); + + void SortNodes(void); + + int HullIndex( const CBaseEntity *pEntity ); // what hull the monster uses + int NodeType( const CBaseEntity *pEntity ); // what node type the monster uses + inline int CapIndex( int afCapMask ) + { + if (afCapMask & (bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE)) + return 1; + return 0; + } + + + inline CNode &Node( int i ) + { +#ifdef _DEBUG + if ( !m_pNodes || i < 0 || i > m_cNodes ) + ALERT( at_error, "Bad Node!\n" ); +#endif + return m_pNodes[i]; + } + + inline CLink &Link( int i ) + { +#ifdef _DEBUG + if ( !m_pLinkPool || i < 0 || i > m_cLinks ) + ALERT( at_error, "Bad link!\n" ); +#endif + return m_pLinkPool[i]; + } + + inline CLink &NodeLink( int iNode, int iLink ) + { + return Link( Node( iNode ).m_iFirstLink + iLink ); + } + + inline CLink &NodeLink( const CNode &node, int iLink ) + { + return Link( node.m_iFirstLink + iLink ); + } + + inline int INodeLink ( int iNode, int iLink ) + { + return NodeLink( iNode, iLink ).m_iDestNode; + } + +#if 0 + inline CNode &SourceNode( int iNode, int iLink ) + { + return Node( NodeLink( iNode, iLink ).m_iSrcNode ); + } + + inline CNode &DestNode( int iNode, int iLink ) + { + return Node( NodeLink( iNode, iLink ).m_iDestNode ); + } + + inline CNode *PNodeLink ( int iNode, int iLink ) + { + return &DestNode( iNode, iLink ); + } +#endif +}; + +//========================================================= +// Nodes start out as ents in the level. The node graph +// is built, then these ents are discarded. +//========================================================= +class CNodeEnt : public CBaseEntity +{ + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + short m_sHintType; + short m_sHintActivity; +}; + + +//========================================================= +// CStack - last in, first out. +//========================================================= +class CStack +{ +public: + CStack( void ); + void Push( int value ); + int Pop( void ); + int Top( void ); + int Empty( void ) { return m_level==0; } + int Size( void ) { return m_level; } + void CopyToArray ( int *piArray ); + +private: + int m_stack[ MAX_STACK_NODES ]; + int m_level; +}; + + +//========================================================= +// CQueue - first in, first out. +//========================================================= +class CQueue +{ +public: + + CQueue( void );// constructor + inline int Full ( void ) { return ( m_cSize == MAX_STACK_NODES ); } + inline int Empty ( void ) { return ( m_cSize == 0 ); } + //inline int Tail ( void ) { return ( m_queue[ m_tail ] ); } + inline int Size ( void ) { return ( m_cSize ); } + void Insert( int, float ); + int Remove( float & ); + +private: + int m_cSize; + struct tag_QUEUE_NODE + { + int Id; + float Priority; + } m_queue[ MAX_STACK_NODES ]; + int m_head; + int m_tail; +}; + +//========================================================= +// CQueuePriority - Priority queue (smallest item out first). +// +//========================================================= +class CQueuePriority +{ +public: + + CQueuePriority( void );// constructor + inline int Full ( void ) { return ( m_cSize == MAX_STACK_NODES ); } + inline int Empty ( void ) { return ( m_cSize == 0 ); } + //inline int Tail ( float & ) { return ( m_queue[ m_tail ].Id ); } + inline int Size ( void ) { return ( m_cSize ); } + void Insert( int, float ); + int Remove( float &); + +private: + int m_cSize; + struct tag_HEAP_NODE + { + int Id; + float Priority; + } m_heap[ MAX_STACK_NODES ]; + void Heap_SiftDown(int); + void Heap_SiftUp(void); }; -extern CGraph WorldGraph; +//========================================================= +// hints - these MUST coincide with the HINTS listed under +// info_node in the FGD file! +//========================================================= +enum +{ + HINT_NONE = 0, + HINT_WORLD_DOOR, + HINT_WORLD_WINDOW, + HINT_WORLD_BUTTON, + HINT_WORLD_MACHINERY, + HINT_WORLD_LEDGE, + HINT_WORLD_LIGHT_SOURCE, + HINT_WORLD_HEAT_SOURCE, + HINT_WORLD_BLINKING_LIGHT, + HINT_WORLD_BRIGHT_COLORS, + HINT_WORLD_HUMAN_BLOOD, + HINT_WORLD_ALIEN_BLOOD, -#endif // NODES_H \ No newline at end of file + HINT_TACTICAL_EXIT = 100, + HINT_TACTICAL_VANTAGE, + HINT_TACTICAL_AMBUSH, + + HINT_STUKA_PERCH = 300, + HINT_STUKA_LANDING, +}; + +extern CGraph WorldGraph; diff --git a/dlls/osprey.cpp b/dlls/osprey.cpp new file mode 100644 index 0000000..435107e --- /dev/null +++ b/dlls/osprey.cpp @@ -0,0 +1,805 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "effects.h" +#include "customentity.h" + +typedef struct +{ + int isValid; + EHANDLE hGrunt; + Vector vecOrigin; + Vector vecAngles; +} t_ospreygrunt; + + + +#define SF_WAITFORTRIGGER 0x40 + + +#define MAX_CARRY 24 + +class COsprey : public CBaseMonster +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + void Spawn( void ); + void Precache( void ); + int Classify( void ) { return CLASS_MACHINE; }; + int BloodColor( void ) { return DONT_BLEED; } + void Killed( entvars_t *pevAttacker, int iGib ); + + void UpdateGoal( void ); + BOOL HasDead( void ); + void EXPORT FlyThink( void ); + void EXPORT DeployThink( void ); + void Flight( void ); + void EXPORT HitTouch( CBaseEntity *pOther ); + void EXPORT FindAllThink( void ); + void EXPORT HoverThink( void ); + CBaseMonster *MakeGrunt( Vector vecSrc ); + void EXPORT CrashTouch( CBaseEntity *pOther ); + void EXPORT DyingThink( void ); + void EXPORT CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + // int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + void ShowDamage( void ); + + CBaseEntity *m_pGoalEnt; + Vector m_vel1; + Vector m_vel2; + Vector m_pos1; + Vector m_pos2; + Vector m_ang1; + Vector m_ang2; + float m_startTime; + float m_dTime; + + Vector m_velocity; + + float m_flIdealtilt; + float m_flRotortilt; + + float m_flRightHealth; + float m_flLeftHealth; + + int m_iUnits; + EHANDLE m_hGrunt[MAX_CARRY]; + Vector m_vecOrigin[MAX_CARRY]; + EHANDLE m_hRepel[4]; + + int m_iSoundState; + int m_iSpriteTexture; + + int m_iPitch; + + int m_iExplode; + int m_iTailGibs; + int m_iBodyGibs; + int m_iEngineGibs; + + int m_iDoLeftSmokePuff; + int m_iDoRightSmokePuff; +}; + +LINK_ENTITY_TO_CLASS( monster_osprey, COsprey ); + +TYPEDESCRIPTION COsprey::m_SaveData[] = +{ + DEFINE_FIELD( COsprey, m_pGoalEnt, FIELD_CLASSPTR ), + DEFINE_FIELD( COsprey, m_vel1, FIELD_VECTOR ), + DEFINE_FIELD( COsprey, m_vel2, FIELD_VECTOR ), + DEFINE_FIELD( COsprey, m_pos1, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( COsprey, m_pos2, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( COsprey, m_ang1, FIELD_VECTOR ), + DEFINE_FIELD( COsprey, m_ang2, FIELD_VECTOR ), + + DEFINE_FIELD( COsprey, m_startTime, FIELD_TIME ), + DEFINE_FIELD( COsprey, m_dTime, FIELD_FLOAT ), + DEFINE_FIELD( COsprey, m_velocity, FIELD_VECTOR ), + + DEFINE_FIELD( COsprey, m_flIdealtilt, FIELD_FLOAT ), + DEFINE_FIELD( COsprey, m_flRotortilt, FIELD_FLOAT ), + + DEFINE_FIELD( COsprey, m_flRightHealth, FIELD_FLOAT ), + DEFINE_FIELD( COsprey, m_flLeftHealth, FIELD_FLOAT ), + + DEFINE_FIELD( COsprey, m_iUnits, FIELD_INTEGER ), + DEFINE_ARRAY( COsprey, m_hGrunt, FIELD_EHANDLE, MAX_CARRY ), + DEFINE_ARRAY( COsprey, m_vecOrigin, FIELD_POSITION_VECTOR, MAX_CARRY ), + DEFINE_ARRAY( COsprey, m_hRepel, FIELD_EHANDLE, 4 ), + + // DEFINE_FIELD( COsprey, m_iSoundState, FIELD_INTEGER ), + // DEFINE_FIELD( COsprey, m_iSpriteTexture, FIELD_INTEGER ), + // DEFINE_FIELD( COsprey, m_iPitch, FIELD_INTEGER ), + + DEFINE_FIELD( COsprey, m_iDoLeftSmokePuff, FIELD_INTEGER ), + DEFINE_FIELD( COsprey, m_iDoRightSmokePuff, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( COsprey, CBaseMonster ); + + +void COsprey :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/osprey.mdl"); + UTIL_SetSize(pev, Vector( -400, -400, -100), Vector(400, 400, 32)); + UTIL_SetOrigin( pev, pev->origin ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_YES; + m_flRightHealth = 200; + m_flLeftHealth = 200; + pev->health = 400; + + m_flFieldOfView = 0; // 180 degrees + + pev->sequence = 0; + ResetSequenceInfo( ); + pev->frame = RANDOM_LONG(0,0xFF); + + InitBoneControllers(); + + SetThink( FindAllThink ); + SetUse( CommandUse ); + + if (!(pev->spawnflags & SF_WAITFORTRIGGER)) + { + pev->nextthink = gpGlobals->time + 1.0; + } + + m_pos2 = pev->origin; + m_ang2 = pev->angles; + m_vel2 = pev->velocity; +} + + +void COsprey::Precache( void ) +{ + UTIL_PrecacheOther( "monster_human_grunt" ); + + PRECACHE_MODEL("models/osprey.mdl"); + PRECACHE_MODEL("models/HVR.mdl"); + + PRECACHE_SOUND("apache/ap_rotor4.wav"); + PRECACHE_SOUND("weapons/mortarhit.wav"); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); + + m_iExplode = PRECACHE_MODEL( "sprites/fexplo.spr" ); + m_iTailGibs = PRECACHE_MODEL( "models/osprey_tailgibs.mdl" ); + m_iBodyGibs = PRECACHE_MODEL( "models/osprey_bodygibs.mdl" ); + m_iEngineGibs = PRECACHE_MODEL( "models/osprey_enginegibs.mdl" ); +} + +void COsprey::CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->nextthink = gpGlobals->time + 0.1; +} + +void COsprey :: FindAllThink( void ) +{ + CBaseEntity *pEntity = NULL; + + m_iUnits = 0; + while (m_iUnits < MAX_CARRY && (pEntity = UTIL_FindEntityByClassname( pEntity, "monster_human_grunt" )) != NULL) + { + if (pEntity->IsAlive()) + { + m_hGrunt[m_iUnits] = pEntity; + m_vecOrigin[m_iUnits] = pEntity->pev->origin; + m_iUnits++; + } + } + + if (m_iUnits == 0) + { + ALERT( at_console, "osprey error: no grunts to resupply\n"); + UTIL_Remove( this ); + return; + } + SetThink( FlyThink ); + pev->nextthink = gpGlobals->time + 0.1; + m_startTime = gpGlobals->time; +} + + +void COsprey :: DeployThink( void ) +{ + UTIL_MakeAimVectors( pev->angles ); + + Vector vecForward = gpGlobals->v_forward; + Vector vecRight = gpGlobals->v_right; + Vector vecUp = gpGlobals->v_up; + + Vector vecSrc; + + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin + Vector( 0, 0, -4096.0), ignore_monsters, ENT(pev), &tr); + CSoundEnt::InsertSound ( bits_SOUND_DANGER, tr.vecEndPos, 400, 0.3 ); + + vecSrc = pev->origin + vecForward * 32 + vecRight * 100 + vecUp * -96; + m_hRepel[0] = MakeGrunt( vecSrc ); + + vecSrc = pev->origin + vecForward * -64 + vecRight * 100 + vecUp * -96; + m_hRepel[1] = MakeGrunt( vecSrc ); + + vecSrc = pev->origin + vecForward * 32 + vecRight * -100 + vecUp * -96; + m_hRepel[2] = MakeGrunt( vecSrc ); + + vecSrc = pev->origin + vecForward * -64 + vecRight * -100 + vecUp * -96; + m_hRepel[3] = MakeGrunt( vecSrc ); + + SetThink( HoverThink ); + pev->nextthink = gpGlobals->time + 0.1; +} + + + +BOOL COsprey :: HasDead( ) +{ + for (int i = 0; i < m_iUnits; i++) + { + if (m_hGrunt[i] == NULL || !m_hGrunt[i]->IsAlive()) + { + return TRUE; + } + else + { + m_vecOrigin[i] = m_hGrunt[i]->pev->origin; // send them to where they died + } + } + return FALSE; +} + + +CBaseMonster *COsprey :: MakeGrunt( Vector vecSrc ) +{ + CBaseEntity *pEntity; + CBaseMonster *pGrunt; + + TraceResult tr; + UTIL_TraceLine( vecSrc, vecSrc + Vector( 0, 0, -4096.0), dont_ignore_monsters, ENT(pev), &tr); + if ( tr.pHit && Instance( tr.pHit )->pev->solid != SOLID_BSP) + return NULL; + + for (int i = 0; i < m_iUnits; i++) + { + if (m_hGrunt[i] == NULL || !m_hGrunt[i]->IsAlive()) + { + if (m_hGrunt[i] != NULL && m_hGrunt[i]->pev->rendermode == kRenderNormal) + { + m_hGrunt[i]->SUB_StartFadeOut( ); + } + pEntity = Create( "monster_human_grunt", vecSrc, pev->angles ); + pGrunt = pEntity->MyMonsterPointer( ); + pGrunt->pev->movetype = MOVETYPE_FLY; + pGrunt->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); + pGrunt->SetActivity( ACT_GLIDE ); + + CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); + pBeam->PointEntInit( vecSrc + Vector(0,0,112), pGrunt->entindex() ); + pBeam->SetFlags( BEAM_FSOLID ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetThink( SUB_Remove ); + pBeam->pev->nextthink = gpGlobals->time + -4096.0 * tr.flFraction / pGrunt->pev->velocity.z + 0.5; + + // ALERT( at_console, "%d at %.0f %.0f %.0f\n", i, m_vecOrigin[i].x, m_vecOrigin[i].y, m_vecOrigin[i].z ); + pGrunt->m_vecLastPosition = m_vecOrigin[i]; + m_hGrunt[i] = pGrunt; + return pGrunt; + } + } + // ALERT( at_console, "none dead\n"); + return NULL; +} + + +void COsprey :: HoverThink( void ) +{ + int i; + for (i = 0; i < 4; i++) + { + if (m_hRepel[i] != NULL && m_hRepel[i]->pev->health > 0 && !(m_hRepel[i]->pev->flags & FL_ONGROUND)) + { + break; + } + } + + if (i == 4) + { + m_startTime = gpGlobals->time; + SetThink( FlyThink ); + } + + pev->nextthink = gpGlobals->time + 0.1; + UTIL_MakeAimVectors( pev->angles ); + ShowDamage( ); +} + + +void COsprey::UpdateGoal( ) +{ + if (m_pGoalEnt) + { + m_pos1 = m_pos2; + m_ang1 = m_ang2; + m_vel1 = m_vel2; + m_pos2 = m_pGoalEnt->pev->origin; + m_ang2 = m_pGoalEnt->pev->angles; + UTIL_MakeAimVectors( Vector( 0, m_ang2.y, 0 ) ); + m_vel2 = gpGlobals->v_forward * m_pGoalEnt->pev->speed; + + m_startTime = m_startTime + m_dTime; + m_dTime = 2.0 * (m_pos1 - m_pos2).Length() / (m_vel1.Length() + m_pGoalEnt->pev->speed); + + if (m_ang1.y - m_ang2.y < -180) + { + m_ang1.y += 360; + } + else if (m_ang1.y - m_ang2.y > 180) + { + m_ang1.y -= 360; + } + + if (m_pGoalEnt->pev->speed < 400) + m_flIdealtilt = 0; + else + m_flIdealtilt = -90; + } + else + { + ALERT( at_console, "osprey missing target"); + } +} + + +void COsprey::FlyThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if ( m_pGoalEnt == NULL && !FStringNull(pev->target) )// this monster has a target + { + m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING( pev->target ) ) ); + UpdateGoal( ); + } + + if (gpGlobals->time > m_startTime + m_dTime) + { + if (m_pGoalEnt->pev->speed == 0) + { + SetThink( DeployThink ); + } + do { + m_pGoalEnt = CBaseEntity::Instance( FIND_ENTITY_BY_TARGETNAME ( NULL, STRING( m_pGoalEnt->pev->target ) ) ); + } while (m_pGoalEnt->pev->speed < 400 && !HasDead()); + UpdateGoal( ); + } + + Flight( ); + ShowDamage( ); +} + + +void COsprey::Flight( ) +{ + float t = (gpGlobals->time - m_startTime); + float scale = 1.0 / m_dTime; + + float f = UTIL_SplineFraction( t * scale, 1.0 ); + + Vector pos = (m_pos1 + m_vel1 * t) * (1.0 - f) + (m_pos2 - m_vel2 * (m_dTime - t)) * f; + Vector ang = (m_ang1) * (1.0 - f) + (m_ang2) * f; + m_velocity = m_vel1 * (1.0 - f) + m_vel2 * f; + + UTIL_SetOrigin( pev, pos ); + pev->angles = ang; + UTIL_MakeAimVectors( pev->angles ); + float flSpeed = DotProduct( gpGlobals->v_forward, m_velocity ); + + // float flSpeed = DotProduct( gpGlobals->v_forward, pev->velocity ); + + float m_flIdealtilt = (160 - flSpeed) / 10.0; + + // ALERT( at_console, "%f %f\n", flSpeed, flIdealtilt ); + if (m_flRotortilt < m_flIdealtilt) + { + m_flRotortilt += 0.5; + if (m_flRotortilt > 0) + m_flRotortilt = 0; + } + if (m_flRotortilt > m_flIdealtilt) + { + m_flRotortilt -= 0.5; + if (m_flRotortilt < -90) + m_flRotortilt = -90; + } + SetBoneController( 0, m_flRotortilt ); + + + if (m_iSoundState == 0) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor4.wav", 1.0, 0.15, 0, 110 ); + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", 0.5, 0.2, 0, 110 ); + + m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions + } + else + { + CBaseEntity *pPlayer = NULL; + + pPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + // UNDONE: this needs to send different sounds to every player for multiplayer. + if (pPlayer) + { + float pitch = DotProduct( m_velocity - pPlayer->pev->velocity, (pPlayer->pev->origin - pev->origin).Normalize() ); + + pitch = (int)(100 + pitch / 75.0); + + if (pitch > 250) + pitch = 250; + if (pitch < 50) + pitch = 50; + + if (pitch == 100) + pitch = 101; + + if (pitch != m_iPitch) + { + m_iPitch = pitch; + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_rotor4.wav", 1.0, 0.15, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + // ALERT( at_console, "%.0f\n", pitch ); + } + } + // EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "apache/ap_whine1.wav", flVol, 0.2, SND_CHANGE_PITCH | SND_CHANGE_VOL, pitch); + } + +} + + +void COsprey::HitTouch( CBaseEntity *pOther ) +{ + pev->nextthink = gpGlobals->time + 2.0; +} + + +/* +int COsprey::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if (m_flRotortilt <= -90) + { + m_flRotortilt = 0; + } + else + { + m_flRotortilt -= 45; + } + SetBoneController( 0, m_flRotortilt ); + return 0; +} +*/ + + + +void COsprey :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->movetype = MOVETYPE_TOSS; + pev->gravity = 0.3; + pev->velocity = m_velocity; + pev->avelocity = Vector( RANDOM_FLOAT( -20, 20 ), 0, RANDOM_FLOAT( -50, 50 ) ); + STOP_SOUND( ENT(pev), CHAN_STATIC, "apache/ap_rotor4.wav" ); + + UTIL_SetSize( pev, Vector( -32, -32, -64), Vector( 32, 32, 0) ); + SetThink( DyingThink ); + SetTouch( CrashTouch ); + pev->nextthink = gpGlobals->time + 0.1; + pev->health = 0; + pev->takedamage = DAMAGE_NO; + + m_startTime = gpGlobals->time + 4.0; +} + +void COsprey::CrashTouch( CBaseEntity *pOther ) +{ + // only crash if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + SetTouch( NULL ); + m_startTime = gpGlobals->time; + pev->nextthink = gpGlobals->time; + m_velocity = pev->velocity; + } +} + + +void COsprey :: DyingThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + pev->avelocity = pev->avelocity * 1.02; + + // still falling? + if (m_startTime > gpGlobals->time ) + { + UTIL_MakeAimVectors( pev->angles ); + ShowDamage( ); + + Vector vecSpot = pev->origin + pev->velocity * 0.2; + + // random explosions + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( vecSpot.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( RANDOM_LONG(0,29) + 30 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + // lots of smoke + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSpot.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( vecSpot.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 100 ); // scale * 10 + WRITE_BYTE( 10 ); // framerate + MESSAGE_END(); + + + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( 800 ); + WRITE_COORD( 800 ); + WRITE_COORD( 132 ); + + // velocity + WRITE_COORD( pev->velocity.x ); + WRITE_COORD( pev->velocity.y ); + WRITE_COORD( pev->velocity.z ); + + // randomization + WRITE_BYTE( 50 ); + + // Model + WRITE_SHORT( m_iTailGibs ); //model id# + + // # of shards + WRITE_BYTE( 8 ); // let client decide + + // duration + WRITE_BYTE( 200 );// 10.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + + + // don't stop it we touch a entity + pev->flags &= ~FL_ONGROUND; + pev->nextthink = gpGlobals->time + 0.2; + return; + } + else + { + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 512 ); + WRITE_SHORT( m_iExplode ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 10 ); // framerate + MESSAGE_END(); + */ + + // gibs + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_SPRITE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 512 ); + WRITE_SHORT( m_iExplode ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 255 ); // brightness + MESSAGE_END(); + + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 300 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 250 ); // scale * 10 + WRITE_BYTE( 6 ); // framerate + MESSAGE_END(); + */ + + // blast circle + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 2000 ); // reach damage radius over .2 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 4 ); // life + WRITE_BYTE( 32 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 192 ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + EMIT_SOUND(ENT(pev), CHAN_STATIC, "weapons/mortarhit.wav", 1.0, 0.3); + + RadiusDamage( pev->origin, pev, pev, 300, CLASS_NONE, DMG_BLAST ); + + // gibs + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 64); + + // size + WRITE_COORD( 800 ); + WRITE_COORD( 800 ); + WRITE_COORD( 128 ); + + // velocity + WRITE_COORD( m_velocity.x ); + WRITE_COORD( m_velocity.y ); + WRITE_COORD( fabs( m_velocity.z ) * 0.25 ); + + // randomization + WRITE_BYTE( 40 ); + + // Model + WRITE_SHORT( m_iBodyGibs ); //model id# + + // # of shards + WRITE_BYTE( 128 ); + + // duration + WRITE_BYTE( 200 );// 10.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + UTIL_Remove( this ); + } +} + + +void COsprey :: ShowDamage( void ) +{ + if (m_iDoLeftSmokePuff > 0 || RANDOM_LONG(0,99) > m_flLeftHealth) + { + Vector vecSrc = pev->origin + gpGlobals->v_right * -340; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x ); + WRITE_COORD( vecSrc.y ); + WRITE_COORD( vecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,9) + 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + if (m_iDoLeftSmokePuff > 0) + m_iDoLeftSmokePuff--; + } + if (m_iDoRightSmokePuff > 0 || RANDOM_LONG(0,99) > m_flRightHealth) + { + Vector vecSrc = pev->origin + gpGlobals->v_right * 340; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSrc ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x ); + WRITE_COORD( vecSrc.y ); + WRITE_COORD( vecSrc.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( RANDOM_LONG(0,9) + 20 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + if (m_iDoRightSmokePuff > 0) + m_iDoRightSmokePuff--; + } +} + + +void COsprey::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // ALERT( at_console, "%d %.0f\n", ptr->iHitgroup, flDamage ); + + // only so much per engine + if (ptr->iHitgroup == 3) + { + if (m_flRightHealth < 0) + return; + else + m_flRightHealth -= flDamage; + m_iDoLeftSmokePuff = 3 + (flDamage / 5.0); + } + + if (ptr->iHitgroup == 2) + { + if (m_flLeftHealth < 0) + return; + else + m_flLeftHealth -= flDamage; + m_iDoRightSmokePuff = 3 + (flDamage / 5.0); + } + + // hit hard, hits cockpit, hits engines + if (flDamage > 50 || ptr->iHitgroup == 1 || ptr->iHitgroup == 2 || ptr->iHitgroup == 3) + { + // ALERT( at_console, "%.0f\n", flDamage ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } + else + { + UTIL_Sparks( ptr->vecEndPos ); + } +} + + + + + diff --git a/dlls/pathcorner.cpp b/dlls/pathcorner.cpp index 7bb0998..b721672 100644 --- a/dlls/pathcorner.cpp +++ b/dlls/pathcorner.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/plane.cpp b/dlls/plane.cpp index 7c2d3dc..ff5518c 100644 --- a/dlls/plane.cpp +++ b/dlls/plane.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/plane.h b/dlls/plane.h index f2d0030..a54f245 100644 --- a/dlls/plane.h +++ b/dlls/plane.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/plats.cpp b/dlls/plats.cpp index b56f7a0..a566d7f 100644 --- a/dlls/plats.cpp +++ b/dlls/plats.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/player.cpp b/dlls/player.cpp index b249286..3dbe22c 100644 --- a/dlls/player.cpp +++ b/dlls/player.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -34,6 +34,7 @@ #include "decals.h" #include "gamerules.h" #include "game.h" +#include "hltv.h" // #define DUCKFIX @@ -183,6 +184,11 @@ int gmsgShowMenu = 0; int gmsgGeigerRange = 0; int gmsgTeamNames = 0; +int gmsgStatusText = 0; +int gmsgStatusValue = 0; + + + void LinkUserMessages( void ) { // Already taken care of? @@ -224,6 +230,10 @@ void LinkUserMessages( void ) gmsgFade = REG_USER_MSG("ScreenFade", sizeof(ScreenFade)); gmsgAmmoX = REG_USER_MSG("AmmoX", 2); gmsgTeamNames = REG_USER_MSG( "TeamNames", -1 ); + + gmsgStatusText = REG_USER_MSG("StatusText", -1); + gmsgStatusValue = REG_USER_MSG("StatusValue", 3); + } LINK_ENTITY_TO_CLASS( player, CBasePlayer ); @@ -503,8 +513,9 @@ int CBasePlayer :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, } // tell director about it - MESSAGE_BEGIN( MSG_SPEC, SVC_HLTV ); - WRITE_BYTE ( DRC_EVENT ); // take damage event + MESSAGE_BEGIN( MSG_SPEC, SVC_DIRECTOR ); + WRITE_BYTE ( 9 ); // command length in bytes + WRITE_BYTE ( DRC_CMD_EVENT ); // take damage event WRITE_SHORT( ENTINDEX(this->edict()) ); // index number of primary entity WRITE_SHORT( ENTINDEX(ENT(pevInflictor)) ); // index number of secondary entity WRITE_LONG( 5 ); // eventflags (priority and flags) @@ -1641,6 +1652,110 @@ void CBasePlayer::AddPointsToTeam( int score, BOOL bAllowNegativeScore ) } } +//Player ID +void CBasePlayer::InitStatusBar() +{ + m_flStatusBarDisappearDelay = 0; + m_SbarString1[0] = m_SbarString0[0] = 0; +} + +void CBasePlayer::UpdateStatusBar() +{ + int newSBarState[ SBAR_END ]; + char sbuf0[ SBAR_STRING_SIZE ]; + char sbuf1[ SBAR_STRING_SIZE ]; + + memset( newSBarState, 0, sizeof(newSBarState) ); + strcpy( sbuf0, m_SbarString0 ); + strcpy( sbuf1, m_SbarString1 ); + + // Find an ID Target + TraceResult tr; + UTIL_MakeVectors( pev->v_angle + pev->punchangle ); + Vector vecSrc = EyePosition(); + Vector vecEnd = vecSrc + (gpGlobals->v_forward * MAX_ID_RANGE); + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, edict(), &tr); + + if (tr.flFraction != 1.0) + { + if ( !FNullEnt( tr.pHit ) ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + if (pEntity->Classify() == CLASS_PLAYER ) + { + newSBarState[ SBAR_ID_TARGETNAME ] = ENTINDEX( pEntity->edict() ); + strcpy( sbuf1, "1 %p1\n2 Health: %i2%%\n3 Armor: %i3%%" ); + + // allies and medics get to see the targets health + if ( g_pGameRules->PlayerRelationship( this, pEntity ) == GR_TEAMMATE ) + { + newSBarState[ SBAR_ID_TARGETHEALTH ] = 100 * (pEntity->pev->health / pEntity->pev->max_health); + newSBarState[ SBAR_ID_TARGETARMOR ] = pEntity->pev->armorvalue; //No need to get it % based since 100 it's the max. + } + + m_flStatusBarDisappearDelay = gpGlobals->time + 1.0; + } + } + else if ( m_flStatusBarDisappearDelay > gpGlobals->time ) + { + // hold the values for a short amount of time after viewing the object + newSBarState[ SBAR_ID_TARGETNAME ] = m_izSBarState[ SBAR_ID_TARGETNAME ]; + newSBarState[ SBAR_ID_TARGETHEALTH ] = m_izSBarState[ SBAR_ID_TARGETHEALTH ]; + newSBarState[ SBAR_ID_TARGETARMOR ] = m_izSBarState[ SBAR_ID_TARGETARMOR ]; + } + } + + BOOL bForceResend = FALSE; + + if ( strcmp( sbuf0, m_SbarString0 ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStatusText, NULL, pev ); + WRITE_BYTE( 0 ); + WRITE_STRING( sbuf0 ); + MESSAGE_END(); + + strcpy( m_SbarString0, sbuf0 ); + + // make sure everything's resent + bForceResend = TRUE; + } + + if ( strcmp( sbuf1, m_SbarString1 ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStatusText, NULL, pev ); + WRITE_BYTE( 1 ); + WRITE_STRING( sbuf1 ); + MESSAGE_END(); + + strcpy( m_SbarString1, sbuf1 ); + + // make sure everything's resent + bForceResend = TRUE; + } + + // Check values and send if they don't match + for (int i = 1; i < SBAR_END; i++) + { + if ( newSBarState[i] != m_izSBarState[i] || bForceResend ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgStatusValue, NULL, pev ); + WRITE_BYTE( i ); + WRITE_SHORT( newSBarState[i] ); + MESSAGE_END(); + + m_izSBarState[i] = newSBarState[i]; + } + } +} + + + + + + + + #define CLIMB_SHAKE_FREQUENCY 22 // how many frames in between screen shakes when climbing #define MAX_CLIMB_SPEED 200 // fastest vertical climbing speed possible @@ -2730,6 +2845,8 @@ void CBasePlayer::Spawn( void ) } m_lastx = m_lasty = 0; + + m_flNextChatTime = gpGlobals->time; g_pGameRules->PlayerSpawn( this ); } @@ -3206,6 +3323,7 @@ void CBasePlayer :: ForceClientDllUpdate( void ) m_iTrain |= TRAIN_NEW; // Force new train message. m_fWeapon = FALSE; // Force weapon send m_fKnownItem = FALSE; // Force weaponinit messages. + m_fInitHUD = TRUE; // Force HUD gmsgResetHUD message // Now force all the necessary messages // to be sent. @@ -3285,9 +3403,7 @@ void CBasePlayer::ImpulseCommands( ) } break; - case 204: // Demo recording, update client dll specific data again. - ForceClientDllUpdate(); - break; + default: // check all of the cheat impulse commands now CheatImpulseCommands( iImpulse ); @@ -3764,7 +3880,10 @@ void CBasePlayer :: UpdateClientData( void ) FireTargets( "game_playerjoin", this, this, USE_TOGGLE, 0 ); } } + FireTargets( "game_playerspawn", this, this, USE_TOGGLE, 0 ); + + InitStatusBar(); } if ( m_iHideHUD != m_iClientHideHUD ) @@ -3957,6 +4076,13 @@ void CBasePlayer :: UpdateClientData( void ) // Cache and client weapon change m_pClientActiveItem = m_pActiveItem; m_iClientFOV = m_iFOV; + + // Update Status Bar + if ( m_flNextSBarUpdateTime < gpGlobals->time ) + { + UpdateStatusBar(); + m_flNextSBarUpdateTime = gpGlobals->time + 0.2; + } } diff --git a/dlls/player.h b/dlls/player.h index 103b8f2..cbf9580 100644 --- a/dlls/player.h +++ b/dlls/player.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -72,6 +72,19 @@ typedef enum PLAYER_ATTACK1, } PLAYER_ANIM; +#define MAX_ID_RANGE 2048 +#define SBAR_STRING_SIZE 128 + +enum sbar_data +{ + SBAR_ID_TARGETNAME = 1, + SBAR_ID_TARGETHEALTH, + SBAR_ID_TARGETARMOR, + SBAR_END, +}; + +#define CHAT_INTERVAL 1.0f + class CBasePlayer : public CBaseMonster { public: @@ -285,6 +298,18 @@ public: float m_flAmmoStartCharge; float m_flPlayAftershock; float m_flNextAmmoBurn;// while charging, when to absorb another unit of player's ammo? + + //Player ID + void InitStatusBar( void ); + void UpdateStatusBar( void ); + int m_izSBarState[ SBAR_END ]; + float m_flNextSBarUpdateTime; + float m_flStatusBarDisappearDelay; + char m_SbarString0[ SBAR_STRING_SIZE ]; + char m_SbarString1[ SBAR_STRING_SIZE ]; + + float m_flNextChatTime; + }; #define AUTOAIM_2DEGREES 0.0348994967025 diff --git a/dlls/playermonster.cpp b/dlls/playermonster.cpp new file mode 100644 index 0000000..bb0a86a --- /dev/null +++ b/dlls/playermonster.cpp @@ -0,0 +1,122 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: New version of the slider bar +// +// $NoKeywords: $ +//============================================================================= + +//========================================================= +// playermonster - for scripted sequence use. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +// For holograms, make them not solid so the player can walk through them +#define SF_MONSTERPLAYER_NOTSOLID 4 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CPlayerMonster : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int ISoundMask ( void ); +}; +LINK_ENTITY_TO_CLASS( monster_player, CPlayerMonster ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CPlayerMonster :: Classify ( void ) +{ + return CLASS_PLAYER_ALLY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CPlayerMonster :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 90; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CPlayerMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 0: + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// ISoundMask - player monster can't hear. +//========================================================= +int CPlayerMonster :: ISoundMask ( void ) +{ + return NULL; +} + +//========================================================= +// Spawn +//========================================================= +void CPlayerMonster :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/player.mdl"); + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->health = 8; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + + MonsterInit(); + if ( pev->spawnflags & SF_MONSTERPLAYER_NOTSOLID ) + { + pev->solid = SOLID_NOT; + pev->takedamage = DAMAGE_NO; + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CPlayerMonster :: Precache() +{ + PRECACHE_MODEL("models/player.mdl"); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= diff --git a/dlls/python.cpp b/dlls/python.cpp index 2e61076..89d6231 100644 --- a/dlls/python.cpp +++ b/dlls/python.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/rat.cpp b/dlls/rat.cpp new file mode 100644 index 0000000..0d4c8fb --- /dev/null +++ b/dlls/rat.cpp @@ -0,0 +1,98 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// rat - environmental monster +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CRat : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); +}; +LINK_ENTITY_TO_CLASS( monster_rat, CRat ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CRat :: Classify ( void ) +{ + return CLASS_INSECT; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CRat :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 45; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// Spawn +//========================================================= +void CRat :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/bigrat.mdl"); + UTIL_SetSize( pev, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->health = 8; + pev->view_ofs = Vector ( 0, 0, 6 );// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CRat :: Precache() +{ + PRECACHE_MODEL("models/bigrat.mdl"); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= diff --git a/dlls/roach.cpp b/dlls/roach.cpp new file mode 100644 index 0000000..3c13bf1 --- /dev/null +++ b/dlls/roach.cpp @@ -0,0 +1,460 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// cockroach +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "soundent.h" +#include "decals.h" + +#define ROACH_IDLE 0 +#define ROACH_BORED 1 +#define ROACH_SCARED_BY_ENT 2 +#define ROACH_SCARED_BY_LIGHT 3 +#define ROACH_SMELL_FOOD 4 +#define ROACH_EAT 5 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +class CRoach : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + void EXPORT MonsterThink ( void ); + void Move ( float flInterval ); + void PickNewDest ( int iCondition ); + void EXPORT Touch ( CBaseEntity *pOther ); + void Killed( entvars_t *pevAttacker, int iGib ); + + float m_flLastLightLevel; + float m_flNextSmellTime; + int Classify ( void ); + void Look ( int iDistance ); + int ISoundMask ( void ); + + // UNDONE: These don't necessarily need to be save/restored, but if we add more data, it may + BOOL m_fLightHacked; + int m_iMode; + // ----------------------------- +}; +LINK_ENTITY_TO_CLASS( monster_cockroach, CRoach ); + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CRoach :: ISoundMask ( void ) +{ + return bits_SOUND_CARCASS | bits_SOUND_MEAT; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CRoach :: Classify ( void ) +{ + return CLASS_INSECT; +} + +//========================================================= +// Touch +//========================================================= +void CRoach :: Touch ( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + if ( pOther->pev->velocity == g_vecZero || !pOther->IsPlayer() ) + { + return; + } + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 );//move up a bit, and trace down. + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -24 ), ignore_monsters, ENT(pev), & tr); + + // This isn't really blood. So you don't have to screen it out based on violence levels (UTIL_ShouldShowBlood()) + UTIL_DecalTrace( &tr, DECAL_YBLOOD1 +RANDOM_LONG(0,5) ); + + TakeDamage( pOther->pev, pOther->pev, pev->health, DMG_CRUSH ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CRoach :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + + pev->yaw_speed = ys; +} + +//========================================================= +// Spawn +//========================================================= +void CRoach :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/roach.mdl"); + UTIL_SetSize( pev, Vector( -1, -1, 0 ), Vector( 1, 1, 2 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_YELLOW; + pev->effects = 0; + pev->health = 1; + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); + SetActivity ( ACT_IDLE ); + + pev->view_ofs = Vector ( 0, 0, 1 );// position of the eyes relative to monster's origin. + pev->takedamage = DAMAGE_YES; + m_fLightHacked = FALSE; + m_flLastLightLevel = -1; + m_iMode = ROACH_IDLE; + m_flNextSmellTime = gpGlobals->time; +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CRoach :: Precache() +{ + PRECACHE_MODEL("models/roach.mdl"); + + PRECACHE_SOUND("roach/rch_die.wav"); + PRECACHE_SOUND("roach/rch_walk.wav"); + PRECACHE_SOUND("roach/rch_smash.wav"); +} + + +//========================================================= +// Killed. +//========================================================= +void CRoach :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->solid = SOLID_NOT; + + //random sound + if ( RANDOM_LONG(0,4) == 1 ) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "roach/rch_die.wav", 0.8, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + } + else + { + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "roach/rch_smash.wav", 0.7, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + } + + CSoundEnt::InsertSound ( bits_SOUND_WORLD, pev->origin, 128, 1 ); + + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if ( pOwner ) + { + pOwner->DeathNotice( pev ); + } + UTIL_Remove( this ); +} + +//========================================================= +// MonsterThink, overridden for roaches. +//========================================================= +void CRoach :: MonsterThink( void ) +{ + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + pev->nextthink = gpGlobals->time + RANDOM_FLOAT(1,1.5); + else + pev->nextthink = gpGlobals->time + 0.1;// keep monster thinking + + float flInterval = StudioFrameAdvance( ); // animate + + if ( !m_fLightHacked ) + { + // if light value hasn't been collection for the first time yet, + // suspend the creature for a second so the world finishes spawning, then we'll collect the light level. + pev->nextthink = gpGlobals->time + 1; + m_fLightHacked = TRUE; + return; + } + else if ( m_flLastLightLevel < 0 ) + { + // collect light level for the first time, now that all of the lightmaps in the roach's area have been calculated. + m_flLastLightLevel = GETENTITYILLUM( ENT( pev ) ); + } + + switch ( m_iMode ) + { + case ROACH_IDLE: + case ROACH_EAT: + { + // if not moving, sample environment to see if anything scary is around. Do a radius search 'look' at random. + if ( RANDOM_LONG(0,3) == 1 ) + { + Look( 150 ); + if (HasConditions(bits_COND_SEE_FEAR)) + { + // if see something scary + //ALERT ( at_aiconsole, "Scared\n" ); + Eat( 30 + ( RANDOM_LONG(0,14) ) );// roach will ignore food for 30 to 45 seconds + PickNewDest( ROACH_SCARED_BY_ENT ); + SetActivity ( ACT_WALK ); + } + else if ( RANDOM_LONG(0,149) == 1 ) + { + // if roach doesn't see anything, there's still a chance that it will move. (boredom) + //ALERT ( at_aiconsole, "Bored\n" ); + PickNewDest( ROACH_BORED ); + SetActivity ( ACT_WALK ); + + if ( m_iMode == ROACH_EAT ) + { + // roach will ignore food for 30 to 45 seconds if it got bored while eating. + Eat( 30 + ( RANDOM_LONG(0,14) ) ); + } + } + } + + // don't do this stuff if eating! + if ( m_iMode == ROACH_IDLE ) + { + if ( FShouldEat() ) + { + Listen(); + } + + if ( GETENTITYILLUM( ENT(pev) ) > m_flLastLightLevel ) + { + // someone turned on lights! + //ALERT ( at_console, "Lights!\n" ); + PickNewDest( ROACH_SCARED_BY_LIGHT ); + SetActivity ( ACT_WALK ); + } + else if ( HasConditions(bits_COND_SMELL_FOOD) ) + { + CSound *pSound; + + pSound = CSoundEnt::SoundPointerForIndex( m_iAudibleList ); + + // roach smells food and is just standing around. Go to food unless food isn't on same z-plane. + if ( pSound && abs( pSound->m_vecOrigin.z - pev->origin.z ) <= 3 ) + { + PickNewDest( ROACH_SMELL_FOOD ); + SetActivity ( ACT_WALK ); + } + } + } + + break; + } + case ROACH_SCARED_BY_LIGHT: + { + // if roach was scared by light, then stop if we're over a spot at least as dark as where we started! + if ( GETENTITYILLUM( ENT( pev ) ) <= m_flLastLightLevel ) + { + SetActivity ( ACT_IDLE ); + m_flLastLightLevel = GETENTITYILLUM( ENT ( pev ) );// make this our new light level. + } + break; + } + } + + if ( m_flGroundSpeed != 0 ) + { + Move( flInterval ); + } +} + +//========================================================= +// Picks a new spot for roach to run to.( +//========================================================= +void CRoach :: PickNewDest ( int iCondition ) +{ + Vector vecNewDir; + Vector vecDest; + float flDist; + + m_iMode = iCondition; + + if ( m_iMode == ROACH_SMELL_FOOD ) + { + // find the food and go there. + CSound *pSound; + + pSound = CSoundEnt::SoundPointerForIndex( m_iAudibleList ); + + if ( pSound ) + { + m_Route[ 0 ].vecLocation.x = pSound->m_vecOrigin.x + ( 3 - RANDOM_LONG(0,5) ); + m_Route[ 0 ].vecLocation.y = pSound->m_vecOrigin.y + ( 3 - RANDOM_LONG(0,5) ); + m_Route[ 0 ].vecLocation.z = pSound->m_vecOrigin.z; + m_Route[ 0 ].iType = bits_MF_TO_LOCATION; + m_movementGoal = RouteClassify( m_Route[ 0 ].iType ); + return; + } + } + + do + { + // picks a random spot, requiring that it be at least 128 units away + // else, the roach will pick a spot too close to itself and run in + // circles. this is a hack but buys me time to work on the real monsters. + vecNewDir.x = RANDOM_FLOAT( -1, 1 ); + vecNewDir.y = RANDOM_FLOAT( -1, 1 ); + flDist = 256 + ( RANDOM_LONG(0,255) ); + vecDest = pev->origin + vecNewDir * flDist; + + } while ( ( vecDest - pev->origin ).Length2D() < 128 ); + + m_Route[ 0 ].vecLocation.x = vecDest.x; + m_Route[ 0 ].vecLocation.y = vecDest.y; + m_Route[ 0 ].vecLocation.z = pev->origin.z; + m_Route[ 0 ].iType = bits_MF_TO_LOCATION; + m_movementGoal = RouteClassify( m_Route[ 0 ].iType ); + + if ( RANDOM_LONG(0,9) == 1 ) + { + // every once in a while, a roach will play a skitter sound when they decide to run + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "roach/rch_walk.wav", 1, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + } +} + +//========================================================= +// roach's move function +//========================================================= +void CRoach :: Move ( float flInterval ) +{ + float flWaypointDist; + Vector vecApex; + + // local move to waypoint. + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length2D(); + MakeIdealYaw ( m_Route[ m_iRouteIndex ].vecLocation ); + + ChangeYaw ( pev->yaw_speed ); + UTIL_MakeVectors( pev->angles ); + + if ( RANDOM_LONG(0,7) == 1 ) + { + // randomly check for blocked path.(more random load balancing) + if ( !WALK_MOVE( ENT(pev), pev->ideal_yaw, 4, WALKMOVE_NORMAL ) ) + { + // stuck, so just pick a new spot to run off to + PickNewDest( m_iMode ); + } + } + + WALK_MOVE( ENT(pev), pev->ideal_yaw, m_flGroundSpeed * flInterval, WALKMOVE_NORMAL ); + + // if the waypoint is closer than step size, then stop after next step (ok for roach to overshoot) + if ( flWaypointDist <= m_flGroundSpeed * flInterval ) + { + // take truncated step and stop + + SetActivity ( ACT_IDLE ); + m_flLastLightLevel = GETENTITYILLUM( ENT ( pev ) );// this is roach's new comfortable light level + + if ( m_iMode == ROACH_SMELL_FOOD ) + { + m_iMode = ROACH_EAT; + } + else + { + m_iMode = ROACH_IDLE; + } + } + + if ( RANDOM_LONG(0,149) == 1 && m_iMode != ROACH_SCARED_BY_LIGHT && m_iMode != ROACH_SMELL_FOOD ) + { + // random skitter while moving as long as not on a b-line to get out of light or going to food + PickNewDest( FALSE ); + } +} + +//========================================================= +// Look - overriden for the roach, which can virtually see +// 360 degrees. +//========================================================= +void CRoach :: Look ( int iDistance ) +{ + CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with + CBaseEntity *pPreviousEnt;// the last entity added to the link list + int iSighted = 0; + + // DON'T let visibility information from last frame sit around! + ClearConditions( bits_COND_SEE_HATE |bits_COND_SEE_DISLIKE | bits_COND_SEE_ENEMY | bits_COND_SEE_FEAR ); + + // don't let monsters outside of the player's PVS act up, or most of the interesting + // things will happen before the player gets there! + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + { + return; + } + + m_pLink = NULL; + pPreviousEnt = this; + + // Does sphere also limit itself to PVS? + // Examine all entities within a reasonable radius + // !!!PERFORMANCE - let's trivially reject the ent list before radius searching! + while ((pSightEnt = UTIL_FindEntityInSphere( pSightEnt, pev->origin, iDistance )) != NULL) + { + // only consider ents that can be damaged. !!!temporarily only considering other monsters and clients + if ( pSightEnt->IsPlayer() || FBitSet ( pSightEnt->pev->flags, FL_MONSTER ) ) + { + if ( /*FVisible( pSightEnt ) &&*/ !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && pSightEnt->pev->health > 0 ) + { + // NULL the Link pointer for each ent added to the link list. If other ents follow, the will overwrite + // this value. If this ent happens to be the last, the list will be properly terminated. + pPreviousEnt->m_pLink = pSightEnt; + pSightEnt->m_pLink = NULL; + pPreviousEnt = pSightEnt; + + // don't add the Enemy's relationship to the conditions. We only want to worry about conditions when + // we see monsters other than the Enemy. + switch ( IRelationship ( pSightEnt ) ) + { + case R_FR: + iSighted |= bits_COND_SEE_FEAR; + break; + case R_NO: + break; + default: + ALERT ( at_console, "%s can't asses %s\n", STRING(pev->classname), STRING(pSightEnt->pev->classname ) ); + break; + } + } + } + } + SetConditions( iSighted ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + diff --git a/dlls/rpg.cpp b/dlls/rpg.cpp index c8a0287..dcda157 100644 --- a/dlls/rpg.cpp +++ b/dlls/rpg.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/satchel.cpp b/dlls/satchel.cpp index 825db37..7c5ff45 100644 --- a/dlls/satchel.cpp +++ b/dlls/satchel.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/saverestore.h b/dlls/saverestore.h index fe1aaaa..c774b62 100644 --- a/dlls/saverestore.h +++ b/dlls/saverestore.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/schedule.cpp b/dlls/schedule.cpp new file mode 100644 index 0000000..9b4fe6c --- /dev/null +++ b/dlls/schedule.cpp @@ -0,0 +1,1514 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// schedule.cpp - functions and data pertaining to the +// monsters' AI scheduling system. +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "animation.h" +#include "scripted.h" +#include "nodes.h" +#include "defaultai.h" +#include "soundent.h" + +extern CGraph WorldGraph; + +//========================================================= +// FHaveSchedule - Returns TRUE if monster's m_pSchedule +// is anything other than NULL. +//========================================================= +BOOL CBaseMonster :: FHaveSchedule( void ) +{ + if ( m_pSchedule == NULL ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +// ClearSchedule - blanks out the caller's schedule pointer +// and index. +//========================================================= +void CBaseMonster :: ClearSchedule( void ) +{ + m_iTaskStatus = TASKSTATUS_NEW; + m_pSchedule = NULL; + m_iScheduleIndex = 0; +} + +//========================================================= +// FScheduleDone - Returns TRUE if the caller is on the +// last task in the schedule +//========================================================= +BOOL CBaseMonster :: FScheduleDone ( void ) +{ + ASSERT( m_pSchedule != NULL ); + + if ( m_iScheduleIndex == m_pSchedule->cTasks ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// ChangeSchedule - replaces the monster's schedule pointer +// with the passed pointer, and sets the ScheduleIndex back +// to 0 +//========================================================= +void CBaseMonster :: ChangeSchedule ( Schedule_t *pNewSchedule ) +{ + ASSERT( pNewSchedule != NULL ); + + m_pSchedule = pNewSchedule; + m_iScheduleIndex = 0; + m_iTaskStatus = TASKSTATUS_NEW; + m_afConditions = 0;// clear all of the conditions + m_failSchedule = SCHED_NONE; + + if ( m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND && !(m_pSchedule->iSoundMask) ) + { + ALERT ( at_aiconsole, "COND_HEAR_SOUND with no sound mask!\n" ); + } + else if ( m_pSchedule->iSoundMask && !(m_pSchedule->iInterruptMask & bits_COND_HEAR_SOUND) ) + { + ALERT ( at_aiconsole, "Sound mask without COND_HEAR_SOUND!\n" ); + } + +#if _DEBUG + if ( !ScheduleFromName( pNewSchedule->pName ) ) + { + ALERT( at_console, "Schedule %s not in table!!!\n", pNewSchedule->pName ); + } +#endif + +// this is very useful code if you can isolate a test case in a level with a single monster. It will notify +// you of every schedule selection the monster makes. +#if 0 + if ( FClassnameIs( pev, "monster_human_grunt" ) ) + { + Task_t *pTask = GetTask(); + + if ( pTask ) + { + const char *pName = NULL; + + if ( m_pSchedule ) + { + pName = m_pSchedule->pName; + } + else + { + pName = "No Schedule"; + } + + if ( !pName ) + { + pName = "Unknown"; + } + + ALERT( at_aiconsole, "%s: picked schedule %s\n", STRING( pev->classname ), pName ); + } + } +#endif// 0 + +} + +//========================================================= +// NextScheduledTask - increments the ScheduleIndex +//========================================================= +void CBaseMonster :: NextScheduledTask ( void ) +{ + ASSERT( m_pSchedule != NULL ); + + m_iTaskStatus = TASKSTATUS_NEW; + m_iScheduleIndex++; + + if ( FScheduleDone() ) + { + // just completed last task in schedule, so make it invalid by clearing it. + SetConditions( bits_COND_SCHEDULE_DONE ); + //ClearSchedule(); + } +} + +//========================================================= +// IScheduleFlags - returns an integer with all Conditions +// bits that are currently set and also set in the current +// schedule's Interrupt mask. +//========================================================= +int CBaseMonster :: IScheduleFlags ( void ) +{ + if( !m_pSchedule ) + { + return 0; + } + + // strip off all bits excepts the ones capable of breaking this schedule. + return m_afConditions & m_pSchedule->iInterruptMask; +} + +//========================================================= +// FScheduleValid - returns TRUE as long as the current +// schedule is still the proper schedule to be executing, +// taking into account all conditions +//========================================================= +BOOL CBaseMonster :: FScheduleValid ( void ) +{ + if ( m_pSchedule == NULL ) + { + // schedule is empty, and therefore not valid. + return FALSE; + } + + if ( HasConditions( m_pSchedule->iInterruptMask | bits_COND_SCHEDULE_DONE | bits_COND_TASK_FAILED ) ) + { +#ifdef DEBUG + if ( HasConditions ( bits_COND_TASK_FAILED ) && m_failSchedule == SCHED_NONE ) + { + // fail! Send a visual indicator. + ALERT ( at_aiconsole, "Schedule: %s Failed\n", m_pSchedule->pName ); + + Vector tmp = pev->origin; + tmp.z = pev->absmax.z + 16; + UTIL_Sparks( tmp ); + } +#endif // DEBUG + + // some condition has interrupted the schedule, or the schedule is done + return FALSE; + } + + return TRUE; +} + +//========================================================= +// MaintainSchedule - does all the per-think schedule maintenance. +// ensures that the monster leaves this function with a valid +// schedule! +//========================================================= +void CBaseMonster :: MaintainSchedule ( void ) +{ + Schedule_t *pNewSchedule; + int i; + + // UNDONE: Tune/fix this 10... This is just here so infinite loops are impossible + for ( i = 0; i < 10; i++ ) + { + if ( m_pSchedule != NULL && TaskIsComplete() ) + { + NextScheduledTask(); + } + + // validate existing schedule + if ( !FScheduleValid() || m_MonsterState != m_IdealMonsterState ) + { + // if we come into this block of code, the schedule is going to have to be changed. + // if the previous schedule was interrupted by a condition, GetIdealState will be + // called. Else, a schedule finished normally. + + // Notify the monster that his schedule is changing + ScheduleChange(); + + // Call GetIdealState if we're not dead and one or more of the following... + // - in COMBAT state with no enemy (it died?) + // - conditions bits (excluding SCHEDULE_DONE) indicate interruption, + // - schedule is done but schedule indicates it wants GetIdealState called + // after successful completion (by setting bits_COND_SCHEDULE_DONE in iInterruptMask) + // DEAD & SCRIPT are not suggestions, they are commands! + if ( m_IdealMonsterState != MONSTERSTATE_DEAD && + (m_IdealMonsterState != MONSTERSTATE_SCRIPT || m_IdealMonsterState == m_MonsterState) ) + { + if ( (m_afConditions && !HasConditions(bits_COND_SCHEDULE_DONE)) || + (m_pSchedule && (m_pSchedule->iInterruptMask & bits_COND_SCHEDULE_DONE)) || + ((m_MonsterState == MONSTERSTATE_COMBAT) && (m_hEnemy == NULL)) ) + { + GetIdealState(); + } + } + if ( HasConditions( bits_COND_TASK_FAILED ) && m_MonsterState == m_IdealMonsterState ) + { + if ( m_failSchedule != SCHED_NONE ) + pNewSchedule = GetScheduleOfType( m_failSchedule ); + else + pNewSchedule = GetScheduleOfType( SCHED_FAIL ); + // schedule was invalid because the current task failed to start or complete + ALERT ( at_aiconsole, "Schedule Failed at %d!\n", m_iScheduleIndex ); + ChangeSchedule( pNewSchedule ); + } + else + { + SetState( m_IdealMonsterState ); + if ( m_MonsterState == MONSTERSTATE_SCRIPT || m_MonsterState == MONSTERSTATE_DEAD ) + pNewSchedule = CBaseMonster::GetSchedule(); + else + pNewSchedule = GetSchedule(); + ChangeSchedule( pNewSchedule ); + } + } + + if ( m_iTaskStatus == TASKSTATUS_NEW ) + { + Task_t *pTask = GetTask(); + ASSERT( pTask != NULL ); + TaskBegin(); + StartTask( pTask ); + } + + // UNDONE: Twice?!!! + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } + + if ( !TaskIsComplete() && m_iTaskStatus != TASKSTATUS_NEW ) + break; + } + + if ( TaskIsRunning() ) + { + Task_t *pTask = GetTask(); + ASSERT( pTask != NULL ); + RunTask( pTask ); + } + + // UNDONE: We have to do this so that we have an animation set to blend to if RunTask changes the animation + // RunTask() will always change animations at the end of a script! + // Don't do this twice + if ( m_Activity != m_IdealActivity ) + { + SetActivity ( m_IdealActivity ); + } +} + +//========================================================= +// RunTask +//========================================================= +void CBaseMonster :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TURN_RIGHT: + case TASK_TURN_LEFT: + { + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + + case TASK_PLAY_SEQUENCE_FACE_ENEMY: + case TASK_PLAY_SEQUENCE_FACE_TARGET: + { + CBaseEntity *pTarget; + + if ( pTask->iTask == TASK_PLAY_SEQUENCE_FACE_TARGET ) + pTarget = m_hTargetEnt; + else + pTarget = m_hEnemy; + if ( pTarget ) + { + pev->ideal_yaw = UTIL_VecToYaw( pTarget->pev->origin - pev->origin ); + ChangeYaw( pev->yaw_speed ); + } + if ( m_fSequenceFinished ) + TaskComplete(); + } + break; + + case TASK_PLAY_SEQUENCE: + case TASK_PLAY_ACTIVE_IDLE: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + break; + } + + + case TASK_FACE_ENEMY: + { + MakeIdealYaw( m_vecEnemyLKP ); + + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_FACE_HINTNODE: + case TASK_FACE_LASTPOSITION: + case TASK_FACE_TARGET: + case TASK_FACE_IDEAL: + case TASK_FACE_ROUTE: + { + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_PVS: + { + if ( !FNullEnt(FIND_CLIENT_IN_PVS(edict())) ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_INDEFINITE: + { + // don't do anything. + break; + } + case TASK_WAIT: + case TASK_WAIT_RANDOM: + { + if ( gpGlobals->time >= m_flWaitFinished ) + { + TaskComplete(); + } + break; + } + case TASK_WAIT_FACE_ENEMY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if ( gpGlobals->time >= m_flWaitFinished ) + { + TaskComplete(); + } + break; + } + case TASK_MOVE_TO_TARGET_RANGE: + { + float distance; + + if ( m_hTargetEnt == NULL ) + TaskFail(); + else + { + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Re-evaluate when you think your finished, or the target has moved too far + if ( (distance < pTask->flData) || (m_vecMoveGoal - m_hTargetEnt->pev->origin).Length() > pTask->flData * 0.5 ) + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + FRefreshRoute(); + } + + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( distance < pTask->flData ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + else if ( distance < 190 && m_movementActivity != ACT_WALK ) + m_movementActivity = ACT_WALK; + else if ( distance >= 270 && m_movementActivity != ACT_RUN ) + m_movementActivity = ACT_RUN; + } + + break; + } + case TASK_WAIT_FOR_MOVEMENT: + { + if (MovementIsComplete()) + { + TaskComplete(); + RouteClear(); // Stop moving + } + break; + } + case TASK_DIE: + { + if ( m_fSequenceFinished && pev->frame >= 255 ) + { + pev->deadflag = DEAD_DEAD; + + SetThink ( NULL ); + StopAnimation(); + + if ( !BBoxFlat() ) + { + // a bit of a hack. If a corpses' bbox is positioned such that being left solid so that it can be attacked will + // block the player on a slope or stairs, the corpse is made nonsolid. +// pev->solid = SOLID_NOT; + UTIL_SetSize ( pev, Vector ( -4, -4, 0 ), Vector ( 4, 4, 1 ) ); + } + else // !!!HACKHACK - put monster in a thin, wide bounding box until we fix the solid type/bounding volume problem + UTIL_SetSize ( pev, Vector ( pev->mins.x, pev->mins.y, pev->mins.z ), Vector ( pev->maxs.x, pev->maxs.y, pev->mins.z + 1 ) ); + + if ( ShouldFadeOnDeath() ) + { + // this monster was created by a monstermaker... fade the corpse out. + SUB_StartFadeOut(); + } + else + { + // body is gonna be around for a while, so have it stink for a bit. + CSoundEnt::InsertSound ( bits_SOUND_CARCASS, pev->origin, 384, 30 ); + } + } + break; + } + case TASK_RANGE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK2_NOTURN: + case TASK_RANGE_ATTACK2_NOTURN: + case TASK_RELOAD_NOTURN: + { + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + } + case TASK_RANGE_ATTACK1: + case TASK_MELEE_ATTACK1: + case TASK_MELEE_ATTACK2: + case TASK_RANGE_ATTACK2: + case TASK_SPECIAL_ATTACK1: + case TASK_SPECIAL_ATTACK2: + case TASK_RELOAD: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw ( pev->yaw_speed ); + + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + break; + } + case TASK_SMALL_FLINCH: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + } + break; + case TASK_WAIT_FOR_SCRIPT: + { + if ( m_pCine->m_iDelay <= 0 && gpGlobals->time >= m_pCine->m_startTime ) + { + TaskComplete(); + m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszPlay, TRUE ); + if ( m_fSequenceFinished ) + ClearSchedule(); + pev->framerate = 1.0; + //ALERT( at_aiconsole, "Script %s has begun for %s\n", STRING( m_pCine->m_iszPlay ), STRING(pev->classname) ); + } + break; + } + case TASK_PLAY_SCRIPT: + { + if (m_fSequenceFinished) + { + m_pCine->SequenceDone( this ); + } + break; + } + } +} + +//========================================================= +// SetTurnActivity - measures the difference between the way +// the monster is facing and determines whether or not to +// select one of the 180 turn animations. +//========================================================= +void CBaseMonster :: SetTurnActivity ( void ) +{ + float flYD; + flYD = FlYawDiff(); + + if ( flYD <= -45 && LookupActivity ( ACT_TURN_RIGHT ) != ACTIVITY_NOT_AVAILABLE ) + {// big right turn + m_IdealActivity = ACT_TURN_RIGHT; + } + else if ( flYD > 45 && LookupActivity ( ACT_TURN_LEFT ) != ACTIVITY_NOT_AVAILABLE ) + {// big left turn + m_IdealActivity = ACT_TURN_LEFT; + } +} + +//========================================================= +// Start task - selects the correct activity and performs +// any necessary calculations to start the next task on the +// schedule. +//========================================================= +void CBaseMonster :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TURN_RIGHT: + { + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + pev->ideal_yaw = UTIL_AngleMod( flCurrentYaw - pTask->flData ); + SetTurnActivity(); + break; + } + case TASK_TURN_LEFT: + { + float flCurrentYaw; + + flCurrentYaw = UTIL_AngleMod( pev->angles.y ); + pev->ideal_yaw = UTIL_AngleMod( flCurrentYaw + pTask->flData ); + SetTurnActivity(); + break; + } + case TASK_REMEMBER: + { + Remember ( (int)pTask->flData ); + TaskComplete(); + break; + } + case TASK_FORGET: + { + Forget ( (int)pTask->flData ); + TaskComplete(); + break; + } + case TASK_FIND_HINTNODE: + { + m_iHintNode = FindHintNode(); + + if ( m_iHintNode != NO_NODE ) + { + TaskComplete(); + } + else + { + TaskFail(); + } + break; + } + case TASK_STORE_LASTPOSITION: + { + m_vecLastPosition = pev->origin; + TaskComplete(); + break; + } + case TASK_CLEAR_LASTPOSITION: + { + m_vecLastPosition = g_vecZero; + TaskComplete(); + break; + } + case TASK_CLEAR_HINTNODE: + { + m_iHintNode = NO_NODE; + TaskComplete(); + break; + } + case TASK_STOP_MOVING: + { + if ( m_IdealActivity == m_movementActivity ) + { + m_IdealActivity = GetStoppedActivity(); + } + + RouteClear(); + TaskComplete(); + break; + } + case TASK_PLAY_SEQUENCE_FACE_ENEMY: + case TASK_PLAY_SEQUENCE_FACE_TARGET: + case TASK_PLAY_SEQUENCE: + { + m_IdealActivity = ( Activity )( int )pTask->flData; + break; + } + case TASK_PLAY_ACTIVE_IDLE: + { + // monsters verify that they have a sequence for the node's activity BEFORE + // moving towards the node, so it's ok to just set the activity without checking here. + m_IdealActivity = ( Activity )WorldGraph.m_pNodes[ m_iHintNode ].m_sHintActivity; + break; + } + case TASK_SET_SCHEDULE: + { + Schedule_t *pNewSchedule; + + pNewSchedule = GetScheduleOfType( (int)pTask->flData ); + + if ( pNewSchedule ) + { + ChangeSchedule( pNewSchedule ); + } + else + { + TaskFail(); + } + + break; + } + case TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, pTask->flData ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_FAR_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, pTask->flData, CoverRadius() ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_NODE_COVER_FROM_ENEMY: + { + if ( m_hEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( FindCover( m_hEnemy->pev->origin, m_hEnemy->pev->view_ofs, 0, CoverRadius() ) ) + { + // try for cover farther than the FLData from the schedule. + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_COVER_FROM_ENEMY: + { + entvars_t *pevCover; + + if ( m_hEnemy == NULL ) + { + // Find cover from self if no enemy available + pevCover = pev; +// TaskFail(); +// return; + } + else + pevCover = m_hEnemy->pev; + + if ( FindLateralCover( pevCover->origin, pevCover->view_ofs ) ) + { + // try lateral first + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else if ( FindCover( pevCover->origin, pevCover->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no coverwhatsoever. + TaskFail(); + } + break; + } + case TASK_FIND_COVER_FROM_ORIGIN: + { + if ( FindCover( pev->origin, pev->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no cover! + TaskFail(); + } + } + break; + case TASK_FIND_COVER_FROM_BEST_SOUND: + { + CSound *pBestSound; + + pBestSound = PBestSound(); + + ASSERT( pBestSound != NULL ); + /* + if ( pBestSound && FindLateralCover( pBestSound->m_vecOrigin, g_vecZero ) ) + { + // try lateral first + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + */ + + if ( pBestSound && FindCover( pBestSound->m_vecOrigin, g_vecZero, pBestSound->m_iVolume, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + pTask->flData; + TaskComplete(); + } + else + { + // no coverwhatsoever. or no sound in list + TaskFail(); + } + break; + } + case TASK_FACE_HINTNODE: + { + pev->ideal_yaw = WorldGraph.m_pNodes[ m_iHintNode ].m_flHintYaw; + SetTurnActivity(); + break; + } + + case TASK_FACE_LASTPOSITION: + MakeIdealYaw ( m_vecLastPosition ); + SetTurnActivity(); + break; + + case TASK_FACE_TARGET: + if ( m_hTargetEnt != NULL ) + { + MakeIdealYaw ( m_hTargetEnt->pev->origin ); + SetTurnActivity(); + } + else + TaskFail(); + break; + case TASK_FACE_ENEMY: + { + MakeIdealYaw ( m_vecEnemyLKP ); + SetTurnActivity(); + break; + } + case TASK_FACE_IDEAL: + { + SetTurnActivity(); + break; + } + case TASK_FACE_ROUTE: + { + if (FRouteClear()) + { + ALERT(at_aiconsole, "No route to face!\n"); + TaskFail(); + } + else + { + MakeIdealYaw(m_Route[m_iRouteIndex].vecLocation); + SetTurnActivity(); + } + break; + } + case TASK_WAIT_PVS: + case TASK_WAIT_INDEFINITE: + { + // don't do anything. + break; + } + case TASK_WAIT: + case TASK_WAIT_FACE_ENEMY: + {// set a future time that tells us when the wait is over. + m_flWaitFinished = gpGlobals->time + pTask->flData; + break; + } + case TASK_WAIT_RANDOM: + {// set a future time that tells us when the wait is over. + m_flWaitFinished = gpGlobals->time + RANDOM_FLOAT( 0.1, pTask->flData ); + break; + } + case TASK_MOVE_TO_TARGET_RANGE: + { + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + if ( !MoveToTarget( ACT_WALK, 2 ) ) + TaskFail(); + } + break; + } + case TASK_RUN_TO_TARGET: + case TASK_WALK_TO_TARGET: + { + Activity newActivity; + + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + if ( pTask->iTask == TASK_WALK_TO_TARGET ) + newActivity = ACT_WALK; + else + newActivity = ACT_RUN; + // This monster can't do this! + if ( LookupActivity( newActivity ) == ACTIVITY_NOT_AVAILABLE ) + TaskComplete(); + else + { + if ( m_hTargetEnt == NULL || !MoveToTarget( newActivity, 2 ) ) + { + TaskFail(); + ALERT( at_aiconsole, "%s Failed to reach target!!!\n", STRING(pev->classname) ); + RouteClear(); + } + } + } + TaskComplete(); + break; + } + case TASK_CLEAR_MOVE_WAIT: + { + m_flMoveWaitFinished = gpGlobals->time; + TaskComplete(); + break; + } + case TASK_MELEE_ATTACK1_NOTURN: + case TASK_MELEE_ATTACK1: + { + m_IdealActivity = ACT_MELEE_ATTACK1; + break; + } + case TASK_MELEE_ATTACK2_NOTURN: + case TASK_MELEE_ATTACK2: + { + m_IdealActivity = ACT_MELEE_ATTACK2; + break; + } + case TASK_RANGE_ATTACK1_NOTURN: + case TASK_RANGE_ATTACK1: + { + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + case TASK_RANGE_ATTACK2_NOTURN: + case TASK_RANGE_ATTACK2: + { + m_IdealActivity = ACT_RANGE_ATTACK2; + break; + } + case TASK_RELOAD_NOTURN: + case TASK_RELOAD: + { + m_IdealActivity = ACT_RELOAD; + break; + } + case TASK_SPECIAL_ATTACK1: + { + m_IdealActivity = ACT_SPECIAL_ATTACK1; + break; + } + case TASK_SPECIAL_ATTACK2: + { + m_IdealActivity = ACT_SPECIAL_ATTACK2; + break; + } + case TASK_SET_ACTIVITY: + { + m_IdealActivity = (Activity)(int)pTask->flData; + TaskComplete(); + break; + } + case TASK_GET_PATH_TO_ENEMY_LKP: + { + if ( BuildRoute ( m_vecEnemyLKP, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, 0, (m_vecEnemyLKP - pev->origin).Length() )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemyLKP failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy == NULL ) + { + TaskFail(); + return; + } + + if ( BuildRoute ( pEnemy->pev->origin, bits_MF_TO_ENEMY, pEnemy ) ) + { + TaskComplete(); + } + else if (BuildNearestRoute( pEnemy->pev->origin, pEnemy->pev->view_ofs, 0, (pEnemy->pev->origin - pev->origin).Length() )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY_CORPSE: + { + UTIL_MakeVectors( pev->angles ); + if ( BuildRoute ( m_vecEnemyLKP - gpGlobals->v_forward * 64, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else + { + ALERT ( at_aiconsole, "GetPathToEnemyCorpse failed!!\n" ); + TaskFail(); + } + } + break; + case TASK_GET_PATH_TO_SPOT: + { + CBaseEntity *pPlayer = CBaseEntity::Instance( FIND_ENTITY_BY_CLASSNAME( NULL, "player" ) ); + if ( BuildRoute ( m_vecMoveGoal, bits_MF_TO_LOCATION, pPlayer ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + + case TASK_GET_PATH_TO_TARGET: + { + RouteClear(); + if ( m_hTargetEnt != NULL && MoveToTarget( m_movementActivity, 1 ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_HINTNODE:// for active idles! + { + if ( MoveToLocation( m_movementActivity, 2, WorldGraph.m_pNodes[ m_iHintNode ].m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToHintNode failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_LASTPOSITION: + { + m_vecMoveGoal = m_vecLastPosition; + + if ( MoveToLocation( m_movementActivity, 2, m_vecMoveGoal ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToLastPosition failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_BESTSOUND: + { + CSound *pSound; + + pSound = PBestSound(); + + if ( pSound && MoveToLocation( m_movementActivity, 2, pSound->m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestSound failed!!\n" ); + TaskFail(); + } + break; + } +case TASK_GET_PATH_TO_BESTSCENT: + { + CSound *pScent; + + pScent = PBestScent(); + + if ( pScent && MoveToLocation( m_movementActivity, 2, pScent->m_vecOrigin ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToBestScent failed!!\n" ); + + TaskFail(); + } + break; + } + case TASK_RUN_PATH: + { + // UNDONE: This is in some default AI and some monsters can't run? -- walk instead? + if ( LookupActivity( ACT_RUN ) != ACTIVITY_NOT_AVAILABLE ) + { + m_movementActivity = ACT_RUN; + } + else + { + m_movementActivity = ACT_WALK; + } + TaskComplete(); + break; + } + case TASK_WALK_PATH: + { + if ( pev->movetype == MOVETYPE_FLY ) + { + m_movementActivity = ACT_FLY; + } + if ( LookupActivity( ACT_WALK ) != ACTIVITY_NOT_AVAILABLE ) + { + m_movementActivity = ACT_WALK; + } + else + { + m_movementActivity = ACT_RUN; + } + TaskComplete(); + break; + } + case TASK_STRAFE_PATH: + { + Vector2D vec2DirToPoint; + Vector2D vec2RightSide; + + // to start strafing, we have to first figure out if the target is on the left side or right side + UTIL_MakeVectors ( pev->angles ); + + vec2DirToPoint = ( m_Route[ 0 ].vecLocation - pev->origin ).Make2D().Normalize(); + vec2RightSide = gpGlobals->v_right.Make2D().Normalize(); + + if ( DotProduct ( vec2DirToPoint, vec2RightSide ) > 0 ) + { + // strafe right + m_movementActivity = ACT_STRAFE_RIGHT; + } + else + { + // strafe left + m_movementActivity = ACT_STRAFE_LEFT; + } + TaskComplete(); + break; + } + + + case TASK_WAIT_FOR_MOVEMENT: + { + if (FRouteClear()) + { + TaskComplete(); + } + break; + } + + case TASK_EAT: + { + Eat( pTask->flData ); + TaskComplete(); + break; + } + case TASK_SMALL_FLINCH: + { + m_IdealActivity = GetSmallFlinchActivity(); + break; + } + case TASK_DIE: + { + RouteClear(); + + m_IdealActivity = GetDeathActivity(); + + pev->deadflag = DEAD_DYING; + break; + } + case TASK_SOUND_WAKE: + { + AlertSound(); + TaskComplete(); + break; + } + case TASK_SOUND_DIE: + { + DeathSound(); + TaskComplete(); + break; + } + case TASK_SOUND_IDLE: + { + IdleSound(); + TaskComplete(); + break; + } + case TASK_SOUND_PAIN: + { + PainSound(); + TaskComplete(); + break; + } + case TASK_SOUND_DEATH: + { + DeathSound(); + TaskComplete(); + break; + } + case TASK_SOUND_ANGRY: + { + // sounds are complete as soon as we get here, cause we've already played them. + ALERT ( at_aiconsole, "SOUND\n" ); + TaskComplete(); + break; + } + case TASK_WAIT_FOR_SCRIPT: + { + if (m_pCine->m_iszIdle) + { + m_pCine->StartSequence( (CBaseMonster *)this, m_pCine->m_iszIdle, FALSE ); + if (FStrEq( STRING(m_pCine->m_iszIdle), STRING(m_pCine->m_iszPlay))) + { + pev->framerate = 0; + } + } + else + m_IdealActivity = ACT_IDLE; + + break; + } + case TASK_PLAY_SCRIPT: + { + pev->movetype = MOVETYPE_FLY; + ClearBits(pev->flags, FL_ONGROUND); + m_scriptState = SCRIPT_PLAYING; + break; + } + case TASK_ENABLE_SCRIPT: + { + m_pCine->DelayStart( 0 ); + TaskComplete(); + break; + } + case TASK_PLANT_ON_SCRIPT: + { + if ( m_hTargetEnt != NULL ) + { + pev->origin = m_hTargetEnt->pev->origin; // Plant on target + } + + TaskComplete(); + break; + } + case TASK_FACE_SCRIPT: + { + if ( m_hTargetEnt != NULL ) + { + pev->ideal_yaw = UTIL_AngleMod( m_hTargetEnt->pev->angles.y ); + } + + TaskComplete(); + m_IdealActivity = ACT_IDLE; + RouteClear(); + break; + } + + case TASK_SUGGEST_STATE: + { + m_IdealMonsterState = (MONSTERSTATE)(int)pTask->flData; + TaskComplete(); + break; + } + + case TASK_SET_FAIL_SCHEDULE: + m_failSchedule = (int)pTask->flData; + TaskComplete(); + break; + + case TASK_CLEAR_FAIL_SCHEDULE: + m_failSchedule = SCHED_NONE; + TaskComplete(); + break; + + default: + { + ALERT ( at_aiconsole, "No StartTask entry for %d\n", (SHARED_TASKS)pTask->iTask ); + break; + } + } +} + +//========================================================= +// GetTask - returns a pointer to the current +// scheduled task. NULL if there's a problem. +//========================================================= +Task_t *CBaseMonster :: GetTask ( void ) +{ + if ( m_iScheduleIndex < 0 || m_iScheduleIndex >= m_pSchedule->cTasks ) + { + // m_iScheduleIndex is not within valid range for the monster's current schedule. + return NULL; + } + else + { + return &m_pSchedule->pTasklist[ m_iScheduleIndex ]; + } +} + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CBaseMonster :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_PRONE: + { + return GetScheduleOfType( SCHED_BARNACLE_VICTIM_GRAB ); + break; + } + case MONSTERSTATE_NONE: + { + ALERT ( at_aiconsole, "MONSTERSTATE IS NONE!\n" ); + break; + } + case MONSTERSTATE_IDLE: + { + if ( HasConditions ( bits_COND_HEAR_SOUND ) ) + { + return GetScheduleOfType( SCHED_ALERT_FACE ); + } + else if ( FRouteClear() ) + { + // no valid route! + return GetScheduleOfType( SCHED_IDLE_STAND ); + } + else + { + // valid route. Get moving + return GetScheduleOfType( SCHED_IDLE_WALK ); + } + break; + } + case MONSTERSTATE_ALERT: + { + if ( HasConditions( bits_COND_ENEMY_DEAD ) && LookupActivity( ACT_VICTORY_DANCE ) != ACTIVITY_NOT_AVAILABLE ) + { + return GetScheduleOfType ( SCHED_VICTORY_DANCE ); + } + + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE) ) + { + if ( fabs( FlYawDiff() ) < (1.0 - m_flFieldOfView) * 60 ) // roughly in the correct direction + { + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ORIGIN ); + } + else + { + return GetScheduleOfType( SCHED_ALERT_SMALL_FLINCH ); + } + } + + else if ( HasConditions ( bits_COND_HEAR_SOUND ) ) + { + return GetScheduleOfType( SCHED_ALERT_FACE ); + } + else + { + return GetScheduleOfType( SCHED_ALERT_STAND ); + } + break; + } + case MONSTERSTATE_COMBAT: + { + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // clear the current (dead) enemy and try to find another. + m_hEnemy = NULL; + + if ( GetEnemy() ) + { + ClearConditions( bits_COND_ENEMY_DEAD ); + return GetSchedule(); + } + else + { + SetState( MONSTERSTATE_ALERT ); + return GetSchedule(); + } + } + + if ( HasConditions(bits_COND_NEW_ENEMY) ) + { + return GetScheduleOfType ( SCHED_WAKE_ANGRY ); + } + else if (HasConditions(bits_COND_LIGHT_DAMAGE) && !HasMemory( bits_MEMORY_FLINCHED) ) + { + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + else if ( !HasConditions(bits_COND_SEE_ENEMY) ) + { + // we can't see the enemy + if ( !HasConditions(bits_COND_ENEMY_OCCLUDED) ) + { + // enemy is unseen, but not occluded! + // turn to face enemy + return GetScheduleOfType( SCHED_COMBAT_FACE ); + } + else + { + // chase! + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + } + else + { + // we can see the enemy + if ( HasConditions(bits_COND_CAN_RANGE_ATTACK1) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + if ( HasConditions(bits_COND_CAN_RANGE_ATTACK2) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + if ( HasConditions(bits_COND_CAN_MELEE_ATTACK1) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK1 ); + } + if ( HasConditions(bits_COND_CAN_MELEE_ATTACK2) ) + { + return GetScheduleOfType( SCHED_MELEE_ATTACK2 ); + } + if ( !HasConditions(bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1) ) + { + // if we can see enemy but can't use either attack type, we must need to get closer to enemy + return GetScheduleOfType( SCHED_CHASE_ENEMY ); + } + else if ( !FacingIdeal() ) + { + //turn + return GetScheduleOfType( SCHED_COMBAT_FACE ); + } + else + { + ALERT ( at_aiconsole, "No suitable combat schedule!\n" ); + } + } + break; + } + case MONSTERSTATE_DEAD: + { + return GetScheduleOfType( SCHED_DIE ); + break; + } + case MONSTERSTATE_SCRIPT: + { + ASSERT( m_pCine != NULL ); + if ( !m_pCine ) + { + ALERT( at_aiconsole, "Script failed for %s\n", STRING(pev->classname) ); + CineCleanup(); + return GetScheduleOfType( SCHED_IDLE_STAND ); + } + + return GetScheduleOfType( SCHED_AISCRIPT ); + } + default: + { + ALERT ( at_aiconsole, "Invalid State for GetSchedule!\n" ); + break; + } + } + + return &slError[ 0 ]; +} diff --git a/dlls/schedule.h b/dlls/schedule.h index 64fc479..f73e895 100644 --- a/dlls/schedule.h +++ b/dlls/schedule.h @@ -1,31 +1,290 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. * ****/ //========================================================= // Scheduling //========================================================= + #ifndef SCHEDULE_H #define SCHEDULE_H +#define TASKSTATUS_NEW 0 // Just started +#define TASKSTATUS_RUNNING 1 // Running task & movement +#define TASKSTATUS_RUNNING_MOVEMENT 2 // Just running movement +#define TASKSTATUS_RUNNING_TASK 3 // Just running task +#define TASKSTATUS_COMPLETE 4 // Completed, get next task + + +//========================================================= +// These are the schedule types +//========================================================= +typedef enum +{ + SCHED_NONE = 0, + SCHED_IDLE_STAND, + SCHED_IDLE_WALK, + SCHED_WAKE_ANGRY, + SCHED_WAKE_CALLED, + SCHED_ALERT_FACE, + SCHED_ALERT_SMALL_FLINCH, + SCHED_ALERT_BIG_FLINCH, + SCHED_ALERT_STAND, + SCHED_INVESTIGATE_SOUND, + SCHED_COMBAT_FACE, + SCHED_COMBAT_STAND, + SCHED_CHASE_ENEMY, + SCHED_CHASE_ENEMY_FAILED, + SCHED_VICTORY_DANCE, + SCHED_TARGET_FACE, + SCHED_TARGET_CHASE, + SCHED_SMALL_FLINCH, + SCHED_TAKE_COVER_FROM_ENEMY, + SCHED_TAKE_COVER_FROM_BEST_SOUND, + SCHED_TAKE_COVER_FROM_ORIGIN, + SCHED_COWER, // usually a last resort! + SCHED_MELEE_ATTACK1, + SCHED_MELEE_ATTACK2, + SCHED_RANGE_ATTACK1, + SCHED_RANGE_ATTACK2, + SCHED_SPECIAL_ATTACK1, + SCHED_SPECIAL_ATTACK2, + SCHED_STANDOFF, + SCHED_ARM_WEAPON, + SCHED_RELOAD, + SCHED_GUARD, + SCHED_AMBUSH, + SCHED_DIE, + SCHED_WAIT_TRIGGER, + SCHED_FOLLOW, + SCHED_SLEEP, + SCHED_WAKE, + SCHED_BARNACLE_VICTIM_GRAB, + SCHED_BARNACLE_VICTIM_CHOMP, + SCHED_AISCRIPT, + SCHED_FAIL, + + LAST_COMMON_SCHEDULE // Leave this at the bottom +} SCHEDULE_TYPE; + +//========================================================= +// These are the shared tasks +//========================================================= +typedef enum +{ + TASK_INVALID = 0, + TASK_WAIT, + TASK_WAIT_FACE_ENEMY, + TASK_WAIT_PVS, + TASK_SUGGEST_STATE, + TASK_WALK_TO_TARGET, + TASK_RUN_TO_TARGET, + TASK_MOVE_TO_TARGET_RANGE, + TASK_GET_PATH_TO_ENEMY, + TASK_GET_PATH_TO_ENEMY_LKP, + TASK_GET_PATH_TO_ENEMY_CORPSE, + TASK_GET_PATH_TO_LEADER, + TASK_GET_PATH_TO_SPOT, + TASK_GET_PATH_TO_TARGET, + TASK_GET_PATH_TO_HINTNODE, + TASK_GET_PATH_TO_LASTPOSITION, + TASK_GET_PATH_TO_BESTSOUND, + TASK_GET_PATH_TO_BESTSCENT, + TASK_RUN_PATH, + TASK_WALK_PATH, + TASK_STRAFE_PATH, + TASK_CLEAR_MOVE_WAIT, + TASK_STORE_LASTPOSITION, + TASK_CLEAR_LASTPOSITION, + TASK_PLAY_ACTIVE_IDLE, + TASK_FIND_HINTNODE, + TASK_CLEAR_HINTNODE, + TASK_SMALL_FLINCH, + TASK_FACE_IDEAL, + TASK_FACE_ROUTE, + TASK_FACE_ENEMY, + TASK_FACE_HINTNODE, + TASK_FACE_TARGET, + TASK_FACE_LASTPOSITION, + TASK_RANGE_ATTACK1, + TASK_RANGE_ATTACK2, + TASK_MELEE_ATTACK1, + TASK_MELEE_ATTACK2, + TASK_RELOAD, + TASK_RANGE_ATTACK1_NOTURN, + TASK_RANGE_ATTACK2_NOTURN, + TASK_MELEE_ATTACK1_NOTURN, + TASK_MELEE_ATTACK2_NOTURN, + TASK_RELOAD_NOTURN, + TASK_SPECIAL_ATTACK1, + TASK_SPECIAL_ATTACK2, + TASK_CROUCH, + TASK_STAND, + TASK_GUARD, + TASK_STEP_LEFT, + TASK_STEP_RIGHT, + TASK_STEP_FORWARD, + TASK_STEP_BACK, + TASK_DODGE_LEFT, + TASK_DODGE_RIGHT, + TASK_SOUND_ANGRY, + TASK_SOUND_DEATH, + TASK_SET_ACTIVITY, + TASK_SET_SCHEDULE, + TASK_SET_FAIL_SCHEDULE, + TASK_CLEAR_FAIL_SCHEDULE, + TASK_PLAY_SEQUENCE, + TASK_PLAY_SEQUENCE_FACE_ENEMY, + TASK_PLAY_SEQUENCE_FACE_TARGET, + TASK_SOUND_IDLE, + TASK_SOUND_WAKE, + TASK_SOUND_PAIN, + TASK_SOUND_DIE, + TASK_FIND_COVER_FROM_BEST_SOUND,// tries lateral cover first, then node cover + TASK_FIND_COVER_FROM_ENEMY,// tries lateral cover first, then node cover + TASK_FIND_LATERAL_COVER_FROM_ENEMY, + TASK_FIND_NODE_COVER_FROM_ENEMY, + TASK_FIND_NEAR_NODE_COVER_FROM_ENEMY,// data for this one is the MAXIMUM acceptable distance to the cover. + TASK_FIND_FAR_NODE_COVER_FROM_ENEMY,// data for this one is there MINIMUM aceptable distance to the cover. + TASK_FIND_COVER_FROM_ORIGIN, + TASK_EAT, + TASK_DIE, + TASK_WAIT_FOR_SCRIPT, + TASK_PLAY_SCRIPT, + TASK_ENABLE_SCRIPT, + TASK_PLANT_ON_SCRIPT, + TASK_FACE_SCRIPT, + TASK_WAIT_RANDOM, + TASK_WAIT_INDEFINITE, + TASK_STOP_MOVING, + TASK_TURN_LEFT, + TASK_TURN_RIGHT, + TASK_REMEMBER, + TASK_FORGET, + TASK_WAIT_FOR_MOVEMENT, // wait until MovementIsComplete() + LAST_COMMON_TASK, // LEAVE THIS AT THE BOTTOM!! (sjb) +} SHARED_TASKS; + + +// These go in the flData member of the TASK_WALK_TO_TARGET, TASK_RUN_TO_TARGET +enum +{ + TARGET_MOVE_NORMAL = 0, + TARGET_MOVE_SCRIPTED = 1, +}; + + +// A goal should be used for a task that requires several schedules to complete. +// The goal index should indicate which schedule (ordinally) the monster is running. +// That way, when tasks fail, the AI can make decisions based on the context of the +// current goal and sequence rather than just the current schedule. +enum +{ + GOAL_ATTACK_ENEMY, + GOAL_MOVE, + GOAL_TAKE_COVER, + GOAL_MOVE_TARGET, + GOAL_EAT, +}; + +// an array of tasks is a task list +// an array of schedules is a schedule list +struct Task_t +{ + + int iTask; + float flData; +}; + +struct Schedule_t +{ + + Task_t *pTasklist; + int cTasks; + int iInterruptMask;// a bit mask of conditions that can interrupt this schedule + + // a more specific mask that indicates which TYPES of sounds will interrupt the schedule in the + // event that the schedule is broken by COND_HEAR_SOUND + int iSoundMask; + const char *pName; +}; + +// an array of waypoints makes up the monster's route. +// !!!LATER- this declaration doesn't belong in this file. +struct WayPoint_t +{ + Vector vecLocation; + int iType; +}; + +// these MoveFlag values are assigned to a WayPoint's TYPE in order to demonstrate the +// type of movement the monster should use to get there. +#define bits_MF_TO_TARGETENT ( 1 << 0 ) // local move to targetent. +#define bits_MF_TO_ENEMY ( 1 << 1 ) // local move to enemy +#define bits_MF_TO_COVER ( 1 << 2 ) // local move to a hiding place +#define bits_MF_TO_DETOUR ( 1 << 3 ) // local move to detour point. +#define bits_MF_TO_PATHCORNER ( 1 << 4 ) // local move to a path corner +#define bits_MF_TO_NODE ( 1 << 5 ) // local move to a node +#define bits_MF_TO_LOCATION ( 1 << 6 ) // local move to an arbitrary point +#define bits_MF_IS_GOAL ( 1 << 7 ) // this waypoint is the goal of the whole move. +#define bits_MF_DONT_SIMPLIFY ( 1 << 8 ) // Don't let the route code simplify this waypoint + +// If you define any flags that aren't _TO_ flags, add them here so we can mask +// them off when doing compares. +#define bits_MF_NOT_TO_MASK (bits_MF_IS_GOAL | bits_MF_DONT_SIMPLIFY) + +#define MOVEGOAL_NONE (0) +#define MOVEGOAL_TARGETENT (bits_MF_TO_TARGETENT) +#define MOVEGOAL_ENEMY (bits_MF_TO_ENEMY) +#define MOVEGOAL_PATHCORNER (bits_MF_TO_PATHCORNER) +#define MOVEGOAL_LOCATION (bits_MF_TO_LOCATION) +#define MOVEGOAL_NODE (bits_MF_TO_NODE) + +// these bits represent conditions that may befall the monster, of which some are allowed +// to interrupt certain schedules. +#define bits_COND_NO_AMMO_LOADED ( 1 << 0 ) // weapon needs to be reloaded! #define bits_COND_SEE_HATE ( 1 << 1 ) // see something that you hate #define bits_COND_SEE_FEAR ( 1 << 2 ) // see something that you are afraid of #define bits_COND_SEE_DISLIKE ( 1 << 3 ) // see something that you dislike #define bits_COND_SEE_ENEMY ( 1 << 4 ) // target entity is in full view. +#define bits_COND_ENEMY_OCCLUDED ( 1 << 5 ) // target entity occluded by the world +#define bits_COND_SMELL_FOOD ( 1 << 6 ) +#define bits_COND_ENEMY_TOOFAR ( 1 << 7 ) #define bits_COND_LIGHT_DAMAGE ( 1 << 8 ) // hurt a little #define bits_COND_HEAVY_DAMAGE ( 1 << 9 ) // hurt a lot +#define bits_COND_CAN_RANGE_ATTACK1 ( 1 << 10) +#define bits_COND_CAN_MELEE_ATTACK1 ( 1 << 11) +#define bits_COND_CAN_RANGE_ATTACK2 ( 1 << 12) +#define bits_COND_CAN_MELEE_ATTACK2 ( 1 << 13) +// #define bits_COND_CAN_RANGE_ATTACK3 ( 1 << 14) +#define bits_COND_PROVOKED ( 1 << 15) +#define bits_COND_NEW_ENEMY ( 1 << 16) +#define bits_COND_HEAR_SOUND ( 1 << 17) // there is an interesting sound +#define bits_COND_SMELL ( 1 << 18) // there is an interesting scent +#define bits_COND_ENEMY_FACING_ME ( 1 << 19) // enemy is facing me +#define bits_COND_ENEMY_DEAD ( 1 << 20) // enemy was killed. If you get this in combat, try to find another enemy. If you get it in alert, victory dance. #define bits_COND_SEE_CLIENT ( 1 << 21) // see a client #define bits_COND_SEE_NEMESIS ( 1 << 22) // see my nemesis +#define bits_COND_SPECIAL1 ( 1 << 28) // Defined by individual monster +#define bits_COND_SPECIAL2 ( 1 << 29) // Defined by individual monster + +#define bits_COND_TASK_FAILED ( 1 << 30) +#define bits_COND_SCHEDULE_DONE ( 1 << 31) + + +#define bits_COND_ALL_SPECIAL (bits_COND_SPECIAL1 | bits_COND_SPECIAL2) + +#define bits_COND_CAN_ATTACK (bits_COND_CAN_RANGE_ATTACK1 | bits_COND_CAN_MELEE_ATTACK1 | bits_COND_CAN_RANGE_ATTACK2 | bits_COND_CAN_MELEE_ATTACK2) #endif // SCHEDULE_H diff --git a/dlls/scientist.cpp b/dlls/scientist.cpp new file mode 100644 index 0000000..ab37d9c --- /dev/null +++ b/dlls/scientist.cpp @@ -0,0 +1,1428 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// human scientist (passive lab worker) +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "talkmonster.h" +#include "schedule.h" +#include "defaultai.h" +#include "scripted.h" +#include "animation.h" +#include "soundent.h" + + +#define NUM_SCIENTIST_HEADS 4 // four heads available for scientist model +enum { HEAD_GLASSES = 0, HEAD_EINSTEIN = 1, HEAD_LUTHER = 2, HEAD_SLICK = 3 }; + +enum +{ + SCHED_HIDE = LAST_TALKMONSTER_SCHEDULE + 1, + SCHED_FEAR, + SCHED_PANIC, + SCHED_STARTLE, + SCHED_TARGET_CHASE_SCARED, + SCHED_TARGET_FACE_SCARED, +}; + +enum +{ + TASK_SAY_HEAL = LAST_TALKMONSTER_TASK + 1, + TASK_HEAL, + TASK_SAY_FEAR, + TASK_RUN_PATH_SCARED, + TASK_SCREAM, + TASK_RANDOM_SCREAM, + TASK_MOVE_TO_TARGET_RANGE_SCARED, +}; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define SCIENTIST_AE_HEAL ( 1 ) +#define SCIENTIST_AE_NEEDLEON ( 2 ) +#define SCIENTIST_AE_NEEDLEOFF ( 3 ) + +//======================================================= +// Scientist +//======================================================= + +class CScientist : public CTalkMonster +{ +public: + void Spawn( void ); + void Precache( void ); + + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void RunTask( Task_t *pTask ); + void StartTask( Task_t *pTask ); + int ObjectCaps( void ) { return CTalkMonster :: ObjectCaps() | FCAP_IMPULSE_USE; } + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + virtual int FriendNumber( int arrayNumber ); + void SetActivity ( Activity newActivity ); + Activity GetStoppedActivity( void ); + int ISoundMask( void ); + void DeclineFollowing( void ); + + float CoverRadius( void ) { return 1200; } // Need more room for cover because scientists want to get far away! + BOOL DisregardEnemy( CBaseEntity *pEnemy ) { return !pEnemy->IsAlive() || (gpGlobals->time - m_fearTime) > 15; } + + BOOL CanHeal( void ); + void Heal( void ); + void Scream( void ); + + // Override these to set behavior + Schedule_t *GetScheduleOfType ( int Type ); + Schedule_t *GetSchedule ( void ); + MONSTERSTATE GetIdealState ( void ); + + void DeathSound( void ); + void PainSound( void ); + + void TalkInit( void ); + + void Killed( entvars_t *pevAttacker, int iGib ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CUSTOM_SCHEDULES; + +private: + float m_painTime; + float m_healTime; + float m_fearTime; +}; + +LINK_ENTITY_TO_CLASS( monster_scientist, CScientist ); + +TYPEDESCRIPTION CScientist::m_SaveData[] = +{ + DEFINE_FIELD( CScientist, m_painTime, FIELD_TIME ), + DEFINE_FIELD( CScientist, m_healTime, FIELD_TIME ), + DEFINE_FIELD( CScientist, m_fearTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CScientist, CTalkMonster ); + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlFollow[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_CANT_FOLLOW }, // If you fail, bail out of follow + { TASK_MOVE_TO_TARGET_RANGE,(float)128 }, // Move within 128 of target ent (client) +// { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slFollow[] = +{ + { + tlFollow, + ARRAYSIZE ( tlFollow ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_COMBAT | + bits_SOUND_DANGER, + "Follow" + }, +}; + +Task_t tlFollowScared[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_CHASE },// If you fail, follow normally + { TASK_MOVE_TO_TARGET_RANGE_SCARED,(float)128 }, // Move within 128 of target ent (client) +// { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE_SCARED }, +}; + +Schedule_t slFollowScared[] = +{ + { + tlFollowScared, + ARRAYSIZE ( tlFollowScared ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + bits_SOUND_DANGER, + "FollowScared" + }, +}; + +Task_t tlFaceTargetScared[] = +{ + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE_SCARED }, +}; + +Schedule_t slFaceTargetScared[] = +{ + { + tlFaceTargetScared, + ARRAYSIZE ( tlFaceTargetScared ), + bits_COND_HEAR_SOUND | + bits_COND_NEW_ENEMY, + bits_SOUND_DANGER, + "FaceTargetScared" + }, +}; + +Task_t tlStopFollowing[] = +{ + { TASK_CANT_FOLLOW, (float)0 }, +}; + +Schedule_t slStopFollowing[] = +{ + { + tlStopFollowing, + ARRAYSIZE ( tlStopFollowing ), + 0, + 0, + "StopFollowing" + }, +}; + + +Task_t tlHeal[] = +{ + { TASK_MOVE_TO_TARGET_RANGE,(float)50 }, // Move within 60 of target ent (client) + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_CHASE }, // If you fail, catch up with that guy! (change this to put syringe away and then chase) + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SAY_HEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_ARM }, // Whip out the needle + { TASK_HEAL, (float)0 }, // Put it in the player + { TASK_PLAY_SEQUENCE_FACE_TARGET, (float)ACT_DISARM }, // Put away the needle +}; + +Schedule_t slHeal[] = +{ + { + tlHeal, + ARRAYSIZE ( tlHeal ), + 0, // Don't interrupt or he'll end up running around with a needle all the time + 0, + "Heal" + }, +}; + + +Task_t tlFaceTarget[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE }, +}; + +Schedule_t slFaceTarget[] = +{ + { + tlFaceTarget, + ARRAYSIZE ( tlFaceTarget ), + bits_COND_CLIENT_PUSH | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + bits_SOUND_COMBAT | + bits_SOUND_DANGER, + "FaceTarget" + }, +}; + + +Task_t tlSciPanic[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SCREAM, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_EXCITED }, // This is really fear-stricken excitement + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slSciPanic[] = +{ + { + tlSciPanic, + ARRAYSIZE ( tlSciPanic ), + 0, + 0, + "SciPanic" + }, +}; + + +Task_t tlIdleSciStand[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, // repick IDLESTAND every two seconds. + { TASK_TLK_HEADRESET, (float)0 }, // reset head position +}; + +Schedule_t slIdleSciStand[] = +{ + { + tlIdleSciStand, + ARRAYSIZE ( tlIdleSciStand ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_CLIENT_PUSH | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "IdleSciStand" + + }, +}; + + +Task_t tlScientistCover[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! + { TASK_STOP_MOVING, (float)0 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_RUN_PATH_SCARED, (float)0 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_SET_SCHEDULE, (float)SCHED_HIDE }, +}; + +Schedule_t slScientistCover[] = +{ + { + tlScientistCover, + ARRAYSIZE ( tlScientistCover ), + bits_COND_NEW_ENEMY, + 0, + "ScientistCover" + }, +}; + + + +Task_t tlScientistHide[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_CROUCH }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, // FIXME: This looks lame + { TASK_WAIT_RANDOM, (float)10.0 }, +}; + +Schedule_t slScientistHide[] = +{ + { + tlScientistHide, + ARRAYSIZE ( tlScientistHide ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_SEE_ENEMY | + bits_COND_SEE_HATE | + bits_COND_SEE_FEAR | + bits_COND_SEE_DISLIKE, + bits_SOUND_DANGER, + "ScientistHide" + }, +}; + + +Task_t tlScientistStartle[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PANIC }, // If you fail, just panic! + { TASK_RANDOM_SCREAM, (float)0.3 }, // Scream 30% of the time + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_RANDOM_SCREAM, (float)0.1 }, // Scream again 10% of the time + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCHIDLE }, + { TASK_WAIT_RANDOM, (float)1.0 }, +}; + +Schedule_t slScientistStartle[] = +{ + { + tlScientistStartle, + ARRAYSIZE ( tlScientistStartle ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_HATE | + bits_COND_SEE_FEAR | + bits_COND_SEE_DISLIKE, + 0, + "ScientistStartle" + }, +}; + + + +Task_t tlFear[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SAY_FEAR, (float)0 }, +// { TASK_PLAY_SEQUENCE, (float)ACT_FEAR_DISPLAY }, +}; + +Schedule_t slFear[] = +{ + { + tlFear, + ARRAYSIZE ( tlFear ), + bits_COND_NEW_ENEMY, + 0, + "Fear" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CScientist ) +{ + slFollow, + slFaceTarget, + slIdleSciStand, + slFear, + slScientistCover, + slScientistHide, + slScientistStartle, + slHeal, + slStopFollowing, + slSciPanic, + slFollowScared, + slFaceTargetScared, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CScientist, CTalkMonster ); + + +void CScientist::DeclineFollowing( void ) +{ + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( "SC_POK", 2, VOL_NORM, ATTN_NORM ); +} + + +void CScientist :: Scream( void ) +{ + if ( FOkToSpeak() ) + { + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( "SC_SCREAM", RANDOM_FLOAT(3, 6), VOL_NORM, ATTN_NORM ); + } +} + + +Activity CScientist::GetStoppedActivity( void ) +{ + if ( m_hEnemy != NULL ) + return ACT_EXCITED; + return CTalkMonster::GetStoppedActivity(); +} + + +void CScientist :: StartTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_SAY_HEAL: +// if ( FOkToSpeak() ) + Talk( 2 ); + m_hTalkTarget = m_hTargetEnt; + PlaySentence( "SC_HEAL", 2, VOL_NORM, ATTN_IDLE ); + + TaskComplete(); + break; + + case TASK_SCREAM: + Scream(); + TaskComplete(); + break; + + case TASK_RANDOM_SCREAM: + if ( RANDOM_FLOAT( 0, 1 ) < pTask->flData ) + Scream(); + TaskComplete(); + break; + + case TASK_SAY_FEAR: + if ( FOkToSpeak() ) + { + Talk( 2 ); + m_hTalkTarget = m_hEnemy; + if ( m_hEnemy->IsPlayer() ) + PlaySentence( "SC_PLFEAR", 5, VOL_NORM, ATTN_NORM ); + else + PlaySentence( "SC_FEAR", 5, VOL_NORM, ATTN_NORM ); + } + TaskComplete(); + break; + + case TASK_HEAL: + m_IdealActivity = ACT_MELEE_ATTACK1; + break; + + case TASK_RUN_PATH_SCARED: + m_movementActivity = ACT_RUN_SCARED; + break; + + case TASK_MOVE_TO_TARGET_RANGE_SCARED: + { + if ( (m_hTargetEnt->pev->origin - pev->origin).Length() < 1 ) + TaskComplete(); + else + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + if ( !MoveToTarget( ACT_WALK_SCARED, 0.5 ) ) + TaskFail(); + } + } + break; + + default: + CTalkMonster::StartTask( pTask ); + break; + } +} + +void CScientist :: RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RUN_PATH_SCARED: + if ( MovementIsComplete() ) + TaskComplete(); + if ( RANDOM_LONG(0,31) < 8 ) + Scream(); + break; + + case TASK_MOVE_TO_TARGET_RANGE_SCARED: + { + if ( RANDOM_LONG(0,63)< 8 ) + Scream(); + + if ( m_hEnemy == NULL ) + { + TaskFail(); + } + else + { + float distance; + + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + // Re-evaluate when you think your finished, or the target has moved too far + if ( (distance < pTask->flData) || (m_vecMoveGoal - m_hTargetEnt->pev->origin).Length() > pTask->flData * 0.5 ) + { + m_vecMoveGoal = m_hTargetEnt->pev->origin; + distance = ( m_vecMoveGoal - pev->origin ).Length2D(); + FRefreshRoute(); + } + + // Set the appropriate activity based on an overlapping range + // overlap the range to prevent oscillation + if ( distance < pTask->flData ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + else if ( distance < 190 && m_movementActivity != ACT_WALK_SCARED ) + m_movementActivity = ACT_WALK_SCARED; + else if ( distance >= 270 && m_movementActivity != ACT_RUN_SCARED ) + m_movementActivity = ACT_RUN_SCARED; + } + } + break; + + case TASK_HEAL: + if ( m_fSequenceFinished ) + { + TaskComplete(); + } + else + { + if ( TargetDistance() > 90 ) + TaskComplete(); + pev->ideal_yaw = UTIL_VecToYaw( m_hTargetEnt->pev->origin - pev->origin ); + ChangeYaw( pev->yaw_speed ); + } + break; + default: + CTalkMonster::RunTask( pTask ); + break; + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CScientist :: Classify ( void ) +{ + return CLASS_HUMAN_PASSIVE; +} + + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CScientist :: SetYawSpeed ( void ) +{ + int ys; + + ys = 90; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 120; + break; + case ACT_WALK: + ys = 180; + break; + case ACT_RUN: + ys = 150; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 120; + break; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CScientist :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SCIENTIST_AE_HEAL: // Heal my target (if within range) + Heal(); + break; + case SCIENTIST_AE_NEEDLEON: + { + int oldBody = pev->body; + pev->body = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 1; + } + break; + case SCIENTIST_AE_NEEDLEOFF: + { + int oldBody = pev->body; + pev->body = (oldBody % NUM_SCIENTIST_HEADS) + NUM_SCIENTIST_HEADS * 0; + } + break; + + default: + CTalkMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CScientist :: Spawn( void ) +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/scientist.mdl"); + UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->health = gSkillData.scientistHealth; + pev->view_ofs = Vector ( 0, 0, 50 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_WIDE; // NOTE: we need a wide field of view so scientists will notice player and say hello + m_MonsterState = MONSTERSTATE_NONE; + +// m_flDistTooFar = 256.0; + + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_OPEN_DOORS | bits_CAP_AUTO_DOORS | bits_CAP_USE; + + // White hands + pev->skin = 0; + + if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS-1);// pick a head, any head + } + + // Luther is black, make his hands black + if ( pev->body == HEAD_LUTHER ) + pev->skin = 1; + + MonsterInit(); + SetUse( FollowerUse ); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CScientist :: Precache( void ) +{ + PRECACHE_MODEL("models/scientist.mdl"); + PRECACHE_SOUND("scientist/sci_pain1.wav"); + PRECACHE_SOUND("scientist/sci_pain2.wav"); + PRECACHE_SOUND("scientist/sci_pain3.wav"); + PRECACHE_SOUND("scientist/sci_pain4.wav"); + PRECACHE_SOUND("scientist/sci_pain5.wav"); + + // every new scientist must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + + CTalkMonster::Precache(); +} + +// Init talk data +void CScientist :: TalkInit() +{ + + CTalkMonster::TalkInit(); + + // scientist will try to talk to friends in this order: + + m_szFriends[0] = "monster_scientist"; + m_szFriends[1] = "monster_sitting_scientist"; + m_szFriends[2] = "monster_barney"; + + // scientists speach group names (group names are in sentences.txt) + + m_szGrp[TLK_ANSWER] = "SC_ANSWER"; + m_szGrp[TLK_QUESTION] = "SC_QUESTION"; + m_szGrp[TLK_IDLE] = "SC_IDLE"; + m_szGrp[TLK_STARE] = "SC_STARE"; + m_szGrp[TLK_USE] = "SC_OK"; + m_szGrp[TLK_UNUSE] = "SC_WAIT"; + m_szGrp[TLK_STOP] = "SC_STOP"; + m_szGrp[TLK_NOSHOOT] = "SC_SCARED"; + m_szGrp[TLK_HELLO] = "SC_HELLO"; + + m_szGrp[TLK_PLHURT1] = "!SC_CUREA"; + m_szGrp[TLK_PLHURT2] = "!SC_CUREB"; + m_szGrp[TLK_PLHURT3] = "!SC_CUREC"; + + m_szGrp[TLK_PHELLO] = "SC_PHELLO"; + m_szGrp[TLK_PIDLE] = "SC_PIDLE"; + m_szGrp[TLK_PQUESTION] = "SC_PQUEST"; + m_szGrp[TLK_SMELL] = "SC_SMELL"; + + m_szGrp[TLK_WOUND] = "SC_WOUND"; + m_szGrp[TLK_MORTAL] = "SC_MORTAL"; + + // get voice for head + switch (pev->body % 3) + { + default: + case HEAD_GLASSES: m_voicePitch = 105; break; //glasses + case HEAD_EINSTEIN: m_voicePitch = 100; break; //einstein + case HEAD_LUTHER: m_voicePitch = 95; break; //luther + case HEAD_SLICK: m_voicePitch = 100; break;//slick + } +} + +int CScientist :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + + if ( pevInflictor && pevInflictor->flags & FL_CLIENT ) + { + Remember( bits_MEMORY_PROVOKED ); + StopFollowing( TRUE ); + } + + // make sure friends talk about it if player hurts scientist... + return CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. In the base class implementation, +// monsters care about all sounds, but no scents. +//========================================================= +int CScientist :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + +//========================================================= +// PainSound +//========================================================= +void CScientist :: PainSound ( void ) +{ + if (gpGlobals->time < m_painTime ) + return; + + m_painTime = gpGlobals->time + RANDOM_FLOAT(0.5, 0.75); + + switch (RANDOM_LONG(0,4)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 3: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain4.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 4: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain5.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CScientist :: DeathSound ( void ) +{ + PainSound(); +} + + +void CScientist::Killed( entvars_t *pevAttacker, int iGib ) +{ + SetUse( NULL ); + CTalkMonster::Killed( pevAttacker, iGib ); +} + + +void CScientist :: SetActivity ( Activity newActivity ) +{ + int iSequence; + + iSequence = LookupActivity ( newActivity ); + + // Set to the desired anim, or default anim if the desired is not present + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + newActivity = ACT_IDLE; + CTalkMonster::SetActivity( newActivity ); +} + + +Schedule_t* CScientist :: GetScheduleOfType ( int Type ) +{ + Schedule_t *psched; + + switch( Type ) + { + // Hook these to make a looping schedule + case SCHED_TARGET_FACE: + // call base class default so that scientist will talk + // when 'used' + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slFaceTarget; // override this for different target face behavior + else + return psched; + + case SCHED_TARGET_CHASE: + return slFollow; + + case SCHED_CANT_FOLLOW: + return slStopFollowing; + + case SCHED_PANIC: + return slSciPanic; + + case SCHED_TARGET_CHASE_SCARED: + return slFollowScared; + + case SCHED_TARGET_FACE_SCARED: + return slFaceTargetScared; + + case SCHED_IDLE_STAND: + // call base class default so that scientist will talk + // when standing during idle + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slIdleSciStand; + else + return psched; + + case SCHED_HIDE: + return slScientistHide; + + case SCHED_STARTLE: + return slScientistStartle; + + case SCHED_FEAR: + return slFear; + } + + return CTalkMonster::GetScheduleOfType( Type ); +} + +Schedule_t *CScientist :: GetSchedule ( void ) +{ + // so we don't keep calling through the EHANDLE stuff + CBaseEntity *pEnemy = m_hEnemy; + + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound && (pSound->m_iType & bits_SOUND_DANGER) ) + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ); + } + + switch( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( pEnemy ) + { + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + m_fearTime = gpGlobals->time; + else if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert + { + m_hEnemy = NULL; + pEnemy = NULL; + } + } + + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) + { + // flinch if hurt + return GetScheduleOfType( SCHED_SMALL_FLINCH ); + } + + // Cower when you hear something scary + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + { + CSound *pSound; + pSound = PBestSound(); + + ASSERT( pSound != NULL ); + if ( pSound ) + { + if ( pSound->m_iType & (bits_SOUND_DANGER | bits_SOUND_COMBAT) ) + { + if ( gpGlobals->time - m_fearTime > 3 ) // Only cower every 3 seconds or so + { + m_fearTime = gpGlobals->time; // Update last fear + return GetScheduleOfType( SCHED_STARTLE ); // This will just duck for a second + } + } + } + } + + // Behavior for following the player + if ( IsFollowing() ) + { + if ( !m_hTargetEnt->IsAlive() ) + { + // UNDONE: Comment about the recently dead player here? + StopFollowing( FALSE ); + break; + } + + int relationship = R_NO; + + // Nothing scary, just me and the player + if ( pEnemy != NULL ) + relationship = IRelationship( pEnemy ); + + // UNDONE: Model fear properly, fix R_FR and add multiple levels of fear + if ( relationship != R_DL && relationship != R_HT ) + { + // If I'm already close enough to my target + if ( TargetDistance() <= 128 ) + { + if ( CanHeal() ) // Heal opportunistically + return slHeal; + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move + return GetScheduleOfType( SCHED_MOVE_AWAY_FOLLOW ); + } + return GetScheduleOfType( SCHED_TARGET_FACE ); // Just face and follow. + } + else // UNDONE: When afraid, scientist won't move out of your way. Keep This? If not, write move away scared + { + if ( HasConditions( bits_COND_NEW_ENEMY ) ) // I just saw something new and scary, react + return GetScheduleOfType( SCHED_FEAR ); // React to something scary + return GetScheduleOfType( SCHED_TARGET_FACE_SCARED ); // face and follow, but I'm scared! + } + } + + if ( HasConditions( bits_COND_CLIENT_PUSH ) ) // Player wants me to move + return GetScheduleOfType( SCHED_MOVE_AWAY ); + + // try to say something about smells + TrySmellTalk(); + break; + case MONSTERSTATE_COMBAT: + if ( HasConditions( bits_COND_NEW_ENEMY ) ) + return slFear; // Point and scream! + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + return slScientistCover; // Take Cover + + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + return slTakeCoverFromBestSound; // Cower and panic from the scary sound! + + return slScientistCover; // Run & Cower + break; + } + + return CTalkMonster::GetSchedule(); +} + +MONSTERSTATE CScientist :: GetIdealState ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + case MONSTERSTATE_IDLE: + if ( HasConditions( bits_COND_NEW_ENEMY ) ) + { + if ( IsFollowing() ) + { + int relationship = IRelationship( m_hEnemy ); + if ( relationship != R_FR || relationship != R_HT && !HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + // Don't go to combat if you're following the player + m_IdealMonsterState = MONSTERSTATE_ALERT; + return m_IdealMonsterState; + } + StopFollowing( TRUE ); + } + } + else if ( HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ) ) + { + // Stop following if you take damage + if ( IsFollowing() ) + StopFollowing( TRUE ); + } + break; + + case MONSTERSTATE_COMBAT: + { + CBaseEntity *pEnemy = m_hEnemy; + if ( pEnemy != NULL ) + { + if ( DisregardEnemy( pEnemy ) ) // After 15 seconds of being hidden, return to alert + { + // Strip enemy when going to alert + m_IdealMonsterState = MONSTERSTATE_ALERT; + m_hEnemy = NULL; + return m_IdealMonsterState; + } + // Follow if only scared a little + if ( m_hTargetEnt != NULL ) + { + m_IdealMonsterState = MONSTERSTATE_ALERT; + return m_IdealMonsterState; + } + + if ( HasConditions ( bits_COND_SEE_ENEMY ) ) + { + m_fearTime = gpGlobals->time; + m_IdealMonsterState = MONSTERSTATE_COMBAT; + return m_IdealMonsterState; + } + + } + } + break; + } + + return CTalkMonster::GetIdealState(); +} + + +BOOL CScientist::CanHeal( void ) +{ + if ( (m_healTime > gpGlobals->time) || (m_hTargetEnt == NULL) || (m_hTargetEnt->pev->health > (m_hTargetEnt->pev->max_health * 0.5)) ) + return FALSE; + + return TRUE; +} + +void CScientist::Heal( void ) +{ + if ( !CanHeal() ) + return; + + Vector target = m_hTargetEnt->pev->origin - pev->origin; + if ( target.Length() > 100 ) + return; + + m_hTargetEnt->TakeHealth( gSkillData.scientistHeal, DMG_GENERIC ); + // Don't heal again for 1 minute + m_healTime = gpGlobals->time + 60; +} + +int CScientist::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 1, 2, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + + +//========================================================= +// Dead Scientist PROP +//========================================================= +class CDeadScientist : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify ( void ) { return CLASS_HUMAN_PASSIVE; } + + void KeyValue( KeyValueData *pkvd ); + int m_iPose;// which sequence to display + static char *m_szPoses[7]; +}; +char *CDeadScientist::m_szPoses[] = { "lying_on_back", "lying_on_stomach", "dead_sitting", "dead_hang", "dead_table1", "dead_table2", "dead_table3" }; + +void CDeadScientist::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "pose")) + { + m_iPose = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} +LINK_ENTITY_TO_CLASS( monster_scientist_dead, CDeadScientist ); + +// +// ********** DeadScientist SPAWN ********** +// +void CDeadScientist :: Spawn( ) +{ + PRECACHE_MODEL("models/scientist.mdl"); + SET_MODEL(ENT(pev), "models/scientist.mdl"); + + pev->effects = 0; + pev->sequence = 0; + // Corpses have less health + pev->health = 8;//gSkillData.scientistHealth; + + m_bloodColor = BLOOD_COLOR_RED; + + if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS-1);// pick a head, any head + } + // Luther is black, make his hands black + if ( pev->body == HEAD_LUTHER ) + pev->skin = 1; + else + pev->skin = 0; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead scientist with bad pose\n" ); + } + + // pev->skin += 2; // use bloody skin -- UNDONE: Turn this back on when we have a bloody skin again! + MonsterInitDead(); +} + + +//========================================================= +// Sitting Scientist PROP +//========================================================= + +class CSittingScientist : public CScientist // kdb: changed from public CBaseMonster so he can speak +{ +public: + void Spawn( void ); + void Precache( void ); + + void EXPORT SittingThink( void ); + int Classify ( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + virtual void SetAnswerQuestion( CTalkMonster *pSpeaker ); + int FriendNumber( int arrayNumber ); + + int FIdleSpeak ( void ); + int m_baseSequence; + int m_headTurn; + float m_flResponseDelay; +}; + +LINK_ENTITY_TO_CLASS( monster_sitting_scientist, CSittingScientist ); +TYPEDESCRIPTION CSittingScientist::m_SaveData[] = +{ + // Don't need to save/restore m_baseSequence (recalced) + DEFINE_FIELD( CSittingScientist, m_headTurn, FIELD_INTEGER ), + DEFINE_FIELD( CSittingScientist, m_flResponseDelay, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CSittingScientist, CScientist ); + +// animation sequence aliases +typedef enum +{ +SITTING_ANIM_sitlookleft, +SITTING_ANIM_sitlookright, +SITTING_ANIM_sitscared, +SITTING_ANIM_sitting2, +SITTING_ANIM_sitting3 +} SITTING_ANIM; + + +// +// ********** Scientist SPAWN ********** +// +void CSittingScientist :: Spawn( ) +{ + PRECACHE_MODEL("models/scientist.mdl"); + SET_MODEL(ENT(pev), "models/scientist.mdl"); + Precache(); + InitBoneControllers(); + + UTIL_SetSize(pev, Vector(-14, -14, 0), Vector(14, 14, 36)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + pev->effects = 0; + pev->health = 50; + + m_bloodColor = BLOOD_COLOR_RED; + m_flFieldOfView = VIEW_FIELD_WIDE; // indicates the width of this monster's forward view cone ( as a dotproduct result ) + + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD; + + SetBits(pev->spawnflags, SF_MONSTER_PREDISASTER); // predisaster only! + + if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, NUM_SCIENTIST_HEADS-1);// pick a head, any head + } + // Luther is black, make his hands black + if ( pev->body == HEAD_LUTHER ) + pev->skin = 1; + + m_baseSequence = LookupSequence( "sitlookleft" ); + pev->sequence = m_baseSequence + RANDOM_LONG(0,4); + ResetSequenceInfo( ); + + SetThink (SittingThink); + pev->nextthink = gpGlobals->time + 0.1; + + DROP_TO_FLOOR ( ENT(pev) ); +} + +void CSittingScientist :: Precache( void ) +{ + m_baseSequence = LookupSequence( "sitlookleft" ); + TalkInit(); +} + +//========================================================= +// ID as a passive human +//========================================================= +int CSittingScientist :: Classify ( void ) +{ + return CLASS_HUMAN_PASSIVE; +} + + +int CSittingScientist::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 2, 1, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + + + +//========================================================= +// sit, do stuff +//========================================================= +void CSittingScientist :: SittingThink( void ) +{ + CBaseEntity *pent; + + StudioFrameAdvance( ); + + // try to greet player + if (FIdleHello()) + { + pent = FindNearestFriend(TRUE); + if (pent) + { + float yaw = VecToYaw(pent->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + if (yaw > 0) + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookleft; + else + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookright; + + ResetSequenceInfo( ); + pev->frame = 0; + SetBoneController( 0, 0 ); + } + } + else if (m_fSequenceFinished) + { + int i = RANDOM_LONG(0,99); + m_headTurn = 0; + + if (m_flResponseDelay && gpGlobals->time > m_flResponseDelay) + { + // respond to question + IdleRespond(); + pev->sequence = m_baseSequence + SITTING_ANIM_sitscared; + m_flResponseDelay = 0; + } + else if (i < 30) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitting3; + + // turn towards player or nearest friend and speak + + if (!FBitSet(m_bitsSaid, bit_saidHelloPlayer)) + pent = FindNearestFriend(TRUE); + else + pent = FindNearestFriend(FALSE); + + if (!FIdleSpeak() || !pent) + { + m_headTurn = RANDOM_LONG(0,8) * 10 - 40; + pev->sequence = m_baseSequence + SITTING_ANIM_sitting3; + } + else + { + // only turn head if we spoke + float yaw = VecToYaw(pent->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + if (yaw > 0) + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookleft; + else + pev->sequence = m_baseSequence + SITTING_ANIM_sitlookright; + + //ALERT(at_console, "sitting speak\n"); + } + } + else if (i < 60) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitting3; + m_headTurn = RANDOM_LONG(0,8) * 10 - 40; + if (RANDOM_LONG(0,99) < 5) + { + //ALERT(at_console, "sitting speak2\n"); + FIdleSpeak(); + } + } + else if (i < 80) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitting2; + } + else if (i < 100) + { + pev->sequence = m_baseSequence + SITTING_ANIM_sitscared; + } + + ResetSequenceInfo( ); + pev->frame = 0; + SetBoneController( 0, m_headTurn ); + } + pev->nextthink = gpGlobals->time + 0.1; +} + +// prepare sitting scientist to answer a question +void CSittingScientist :: SetAnswerQuestion( CTalkMonster *pSpeaker ) +{ + m_flResponseDelay = gpGlobals->time + RANDOM_FLOAT(3, 4); + m_hTalkTarget = (CBaseMonster *)pSpeaker; +} + + +//========================================================= +// FIdleSpeak +// ask question of nearby friend, or make statement +//========================================================= +int CSittingScientist :: FIdleSpeak ( void ) +{ + // try to start a conversation, or make statement + int pitch; + + if (!FOkToSpeak()) + return FALSE; + + // set global min delay for next conversation + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2); + + pitch = GetVoicePitch(); + + // if there is a friend nearby to speak to, play sentence, set friend's response time, return + + // try to talk to any standing or sitting scientists nearby + CBaseEntity *pentFriend = FindNearestFriend(FALSE); + + if (pentFriend && RANDOM_LONG(0,1)) + { + CTalkMonster *pTalkMonster = GetClassPtr((CTalkMonster *)pentFriend->pev); + pTalkMonster->SetAnswerQuestion( this ); + + IdleHeadTurn(pentFriend->pev->origin); + SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_PQUESTION], 1.0, ATTN_IDLE, 0, pitch ); + // set global min delay for next conversation + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2); + return TRUE; + } + + // otherwise, play an idle statement + if (RANDOM_LONG(0,1)) + { + SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_PIDLE], 1.0, ATTN_IDLE, 0, pitch ); + // set global min delay for next conversation + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(4.8, 5.2); + return TRUE; + } + + // never spoke + CTalkMonster::g_talkWaitTime = 0; + return FALSE; +} diff --git a/dlls/scripted.cpp b/dlls/scripted.cpp new file mode 100644 index 0000000..9661f78 --- /dev/null +++ b/dlls/scripted.cpp @@ -0,0 +1,1260 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + + +===== scripted.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" + +#ifndef ANIMATION_H +#include "animation.h" +#endif + +#ifndef SAVERESTORE_H +#include "saverestore.h" +#endif + +#include "schedule.h" +#include "scripted.h" +#include "defaultai.h" + + + +/* +classname "scripted_sequence" +targetname "me" - there can be more than one with the same name, and they act in concert +target "the_entity_I_want_to_start_playing" or "class entity_classname" will pick the closest inactive scientist +play "name_of_sequence" +idle "name of idle sequence to play before starting" +donetrigger "whatever" - can be any other triggerable entity such as another sequence, train, door, or a special case like "die" or "remove" +moveto - if set the monster first moves to this nodes position +range # - only search this far to find the target +spawnflags - (stop if blocked, stop if player seen) +*/ + + +// +// Cache user-entity-field values until spawn is called. +// + +void CCineMonster :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_iszIdle")) + { + m_iszIdle = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszPlay")) + { + m_iszPlay = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iszEntity")) + { + m_iszEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_fMoveTo")) + { + m_fMoveTo = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flRepeat")) + { + m_flRepeat = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_flRadius")) + { + m_flRadius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "m_iFinishSchedule")) + { + m_iFinishSchedule = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + { + CBaseMonster::KeyValue( pkvd ); + } +} + +TYPEDESCRIPTION CCineMonster::m_SaveData[] = +{ + DEFINE_FIELD( CCineMonster, m_iszIdle, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_iszPlay, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_iszEntity, FIELD_STRING ), + DEFINE_FIELD( CCineMonster, m_fMoveTo, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_flRepeat, FIELD_FLOAT ), + DEFINE_FIELD( CCineMonster, m_flRadius, FIELD_FLOAT ), + + DEFINE_FIELD( CCineMonster, m_iDelay, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_startTime, FIELD_TIME ), + + DEFINE_FIELD( CCineMonster, m_saved_movetype, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_saved_solid, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_saved_effects, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_iFinishSchedule, FIELD_INTEGER ), + DEFINE_FIELD( CCineMonster, m_interruptable, FIELD_BOOLEAN ), +}; + + +IMPLEMENT_SAVERESTORE( CCineMonster, CBaseMonster ); + +LINK_ENTITY_TO_CLASS( scripted_sequence, CCineMonster ); +#define CLASSNAME "scripted_sequence" + +LINK_ENTITY_TO_CLASS( aiscripted_sequence, CCineAI ); + + +void CCineMonster :: Spawn( void ) +{ + // pev->solid = SOLID_TRIGGER; + // UTIL_SetSize(pev, Vector(-8, -8, -8), Vector(8, 8, 8)); + pev->solid = SOLID_NOT; + + + // REMOVE: The old side-effect +#if 0 + if ( m_iszIdle ) + m_fMoveTo = 4; +#endif + + // if no targetname, start now + if ( FStringNull(pev->targetname) || !FStringNull( m_iszIdle ) ) + { + SetThink( CineThink ); + pev->nextthink = gpGlobals->time + 1.0; + // Wait to be used? + if ( pev->targetname ) + m_startTime = gpGlobals->time + 1E6; + } + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + m_interruptable = FALSE; + else + m_interruptable = TRUE; +} + +//========================================================= +// FCanOverrideState - returns FALSE, scripted sequences +// cannot possess entities regardless of state. +//========================================================= +BOOL CCineMonster :: FCanOverrideState( void ) +{ + if ( pev->spawnflags & SF_SCRIPT_OVERRIDESTATE ) + return TRUE; + return FALSE; +} + +//========================================================= +// FCanOverrideState - returns true because scripted AI can +// possess entities regardless of their state. +//========================================================= +BOOL CCineAI :: FCanOverrideState( void ) +{ + return TRUE; +} + + +// +// CineStart +// +void CCineMonster :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // do I already know who I should use + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if ( pTarget ) + { + // am I already playing the script? + if ( pTarget->m_scriptState == SCRIPT_PLAYING ) + return; + + m_startTime = gpGlobals->time + 0.05; + } + else + { + // if not, try finding them + SetThink( CineThink ); + pev->nextthink = gpGlobals->time; + } +} + + +// This doesn't really make sense since only MOVETYPE_PUSH get 'Blocked' events +void CCineMonster :: Blocked( CBaseEntity *pOther ) +{ + +} + +void CCineMonster :: Touch( CBaseEntity *pOther ) +{ +/* + ALERT( at_aiconsole, "Cine Touch\n" ); + if (m_pentTarget && OFFSET(pOther->pev) == OFFSET(m_pentTarget)) + { + CBaseMonster *pTarget = GetClassPtr((CBaseMonster *)VARS(m_pentTarget)); + pTarget->m_monsterState == MONSTERSTATE_SCRIPT; + } +*/ +} + + +/* + entvars_t *pevOther = VARS( gpGlobals->other ); + + if ( !FBitSet ( pevOther->flags , FL_MONSTER ) ) + {// touched by a non-monster. + return; + } + + pevOther->origin.z += 1; + + if ( FBitSet ( pevOther->flags, FL_ONGROUND ) ) + {// clear the onground so physics don't bitch + pevOther->flags -= FL_ONGROUND; + } + + // toss the monster! + pevOther->velocity = pev->movedir * pev->speed; + pevOther->velocity.z += m_flHeight; + + + pev->solid = SOLID_NOT;// kill the trigger for now !!!UNDONE +} +*/ + + +// +// ********** Cinematic DIE ********** +// +void CCineMonster :: Die( void ) +{ + SetThink( SUB_Remove ); +} + +// +// ********** Cinematic PAIN ********** +// +void CCineMonster :: Pain( void ) +{ + +} + +// +// ********** Cinematic Think ********** +// + +// find a viable entity +int CCineMonster :: FindEntity( void ) +{ + edict_t *pentTarget; + + pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity)); + m_hTargetEnt = NULL; + CBaseMonster *pTarget = NULL; + + while (!FNullEnt(pentTarget)) + { + if ( FBitSet( VARS(pentTarget)->flags, FL_MONSTER )) + { + pTarget = GetMonsterPointer( pentTarget ); + if ( pTarget && pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_BY_NAME ) ) + { + m_hTargetEnt = pTarget; + return TRUE; + } + ALERT( at_console, "Found %s, but can't play!\n", STRING(m_iszEntity) ); + } + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + pTarget = NULL; + } + + if ( !pTarget ) + { + CBaseEntity *pEntity = NULL; + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, m_flRadius )) != NULL) + { + if (FClassnameIs( pEntity->pev, STRING(m_iszEntity))) + { + if ( FBitSet( pEntity->pev->flags, FL_MONSTER )) + { + pTarget = pEntity->MyMonsterPointer( ); + if ( pTarget && pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_IDLE ) ) + { + m_hTargetEnt = pTarget; + return TRUE; + } + } + } + } + } + pTarget = NULL; + m_hTargetEnt = NULL; + return FALSE; +} + +// make the entity enter a scripted sequence +void CCineMonster :: PossessEntity( void ) +{ + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if ( pTarget ) + { + + // FindEntity() just checked this! +#if 0 + if ( !pTarget->CanPlaySequence( FCanOverrideState() ) ) + { + ALERT( at_aiconsole, "Can't possess entity %s\n", STRING(pTarget->pev->classname) ); + return; + } +#endif + + pTarget->m_pGoalEnt = this; + pTarget->m_pCine = this; + pTarget->m_hTargetEnt = this; + + m_saved_movetype = pTarget->pev->movetype; + m_saved_solid = pTarget->pev->solid; + m_saved_effects = pTarget->pev->effects; + pTarget->pev->effects |= pev->effects; + + switch (m_fMoveTo) + { + case 0: + pTarget->m_scriptState = SCRIPT_WAIT; + break; + + case 1: + pTarget->m_scriptState = SCRIPT_WALK_TO_MARK; + DelayStart( 1 ); + break; + + case 2: + pTarget->m_scriptState = SCRIPT_RUN_TO_MARK; + DelayStart( 1 ); + break; + + case 4: + UTIL_SetOrigin( pTarget->pev, pev->origin ); + pTarget->pev->ideal_yaw = pev->angles.y; + pTarget->pev->avelocity = Vector( 0, 0, 0 ); + pTarget->pev->velocity = Vector( 0, 0, 0 ); + pTarget->pev->effects |= EF_NOINTERP; + pTarget->pev->angles.y = pev->angles.y; + pTarget->m_scriptState = SCRIPT_WAIT; + m_startTime = gpGlobals->time + 1E6; + // UNDONE: Add a flag to do this so people can fixup physics after teleporting monsters + // pTarget->pev->flags &= ~FL_ONGROUND; + break; + } +// ALERT( at_aiconsole, "\"%s\" found and used (INT: %s)\n", STRING( pTarget->pev->targetname ), FBitSet(pev->spawnflags, SF_SCRIPT_NOINTERRUPT)?"No":"Yes" ); + + pTarget->m_IdealMonsterState = MONSTERSTATE_SCRIPT; + if (m_iszIdle) + { + StartSequence( pTarget, m_iszIdle, FALSE ); + if (FStrEq( STRING(m_iszIdle), STRING(m_iszPlay))) + { + pTarget->pev->framerate = 0; + } + } + } +} + +// make the entity carry out the scripted sequence instructions, but without +// destroying the monster's state. +void CCineAI :: PossessEntity( void ) +{ + Schedule_t *pNewSchedule; + + CBaseEntity *pEntity = m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if ( pTarget ) + { + if ( !pTarget->CanPlaySequence( FCanOverrideState(), SS_INTERRUPT_AI ) ) + { + ALERT( at_aiconsole, "(AI)Can't possess entity %s\n", STRING(pTarget->pev->classname) ); + return; + } + + pTarget->m_pGoalEnt = this; + pTarget->m_pCine = this; + pTarget->m_hTargetEnt = this; + + m_saved_movetype = pTarget->pev->movetype; + m_saved_solid = pTarget->pev->solid; + m_saved_effects = pTarget->pev->effects; + pTarget->pev->effects |= pev->effects; + + switch (m_fMoveTo) + { + case 0: + case 5: + pTarget->m_scriptState = SCRIPT_WAIT; + break; + + case 1: + pTarget->m_scriptState = SCRIPT_WALK_TO_MARK; + break; + + case 2: + pTarget->m_scriptState = SCRIPT_RUN_TO_MARK; + break; + + case 4: + // zap the monster instantly to the site of the script entity. + UTIL_SetOrigin( pTarget->pev, pev->origin ); + pTarget->pev->ideal_yaw = pev->angles.y; + pTarget->pev->avelocity = Vector( 0, 0, 0 ); + pTarget->pev->velocity = Vector( 0, 0, 0 ); + pTarget->pev->effects |= EF_NOINTERP; + pTarget->pev->angles.y = pev->angles.y; + pTarget->m_scriptState = SCRIPT_WAIT; + m_startTime = gpGlobals->time + 1E6; + // UNDONE: Add a flag to do this so people can fixup physics after teleporting monsters + pTarget->pev->flags &= ~FL_ONGROUND; + break; + default: + ALERT ( at_aiconsole, "aiscript: invalid Move To Position value!" ); + break; + } + + ALERT( at_aiconsole, "\"%s\" found and used\n", STRING( pTarget->pev->targetname ) ); + + pTarget->m_IdealMonsterState = MONSTERSTATE_SCRIPT; + +/* + if (m_iszIdle) + { + StartSequence( pTarget, m_iszIdle, FALSE ); + if (FStrEq( STRING(m_iszIdle), STRING(m_iszPlay))) + { + pTarget->pev->framerate = 0; + } + } +*/ + // Already in a scripted state? + if ( pTarget->m_MonsterState == MONSTERSTATE_SCRIPT ) + { + pNewSchedule = pTarget->GetScheduleOfType( SCHED_AISCRIPT ); + pTarget->ChangeSchedule( pNewSchedule ); + } + } +} + +void CCineMonster :: CineThink( void ) +{ + if (FindEntity()) + { + PossessEntity( ); + ALERT( at_aiconsole, "script \"%s\" using monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + } + else + { + CancelScript( ); + ALERT( at_aiconsole, "script \"%s\" can't find monster \"%s\"\n", STRING( pev->targetname ), STRING( m_iszEntity ) ); + pev->nextthink = gpGlobals->time + 1.0; + } +} + + +// lookup a sequence name and setup the target monster to play it +BOOL CCineMonster :: StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ) +{ + if ( !iszSeq && completeOnEmpty ) + { + SequenceDone( pTarget ); + return FALSE; + } + + pTarget->pev->sequence = pTarget->LookupSequence( STRING( iszSeq ) ); + if (pTarget->pev->sequence == -1) + { + ALERT( at_error, "%s: unknown scripted sequence \"%s\"\n", STRING( pTarget->pev->targetname ), STRING( iszSeq) ); + pTarget->pev->sequence = 0; + // return FALSE; + } + +#if 0 + char *s; + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + s = "No"; + else + s = "Yes"; + + ALERT( at_console, "%s (%s): started \"%s\":INT:%s\n", STRING( pTarget->pev->targetname ), STRING( pTarget->pev->classname ), STRING( iszSeq), s ); +#endif + + pTarget->pev->frame = 0; + pTarget->ResetSequenceInfo( ); + return TRUE; +} + +// lookup a sequence name and setup the target monster to play it +// overridden for CCineAI because it's ok for them to not have an animation sequence +// for the monster to play. For a regular Scripted Sequence, that situation is an error. +BOOL CCineAI :: StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ) +{ + if ( iszSeq == 0 && completeOnEmpty ) + { + // no sequence was provided. Just let the monster proceed, however, we still have to fire any Sequence target + // and remove any non-repeatable CineAI entities here ( because there is code elsewhere that handles those tasks, but + // not until the animation sequence is finished. We have to manually take care of these things where there is no sequence. + + SequenceDone ( pTarget ); + + return TRUE; + } + + pTarget->pev->sequence = pTarget->LookupSequence( STRING( iszSeq ) ); + + if (pTarget->pev->sequence == -1) + { + ALERT( at_error, "%s: unknown aiscripted sequence \"%s\"\n", STRING( pTarget->pev->targetname ), STRING( iszSeq) ); + pTarget->pev->sequence = 0; + // return FALSE; + } + + pTarget->pev->frame = 0; + pTarget->ResetSequenceInfo( ); + return TRUE; +} + +//========================================================= +// SequenceDone - called when a scripted sequence animation +// sequence is done playing ( or when an AI Scripted Sequence +// doesn't supply an animation sequence to play ). Expects +// the CBaseMonster pointer to the monster that the sequence +// possesses. +//========================================================= +void CCineMonster :: SequenceDone ( CBaseMonster *pMonster ) +{ + //ALERT( at_aiconsole, "Sequence %s finished\n", STRING( m_pCine->m_iszPlay ) ); + + if ( !( pev->spawnflags & SF_SCRIPT_REPEATABLE ) ) + { + SetThink( SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + } + + // This is done so that another sequence can take over the monster when triggered by the first + + pMonster->CineCleanup(); + + FixScriptMonsterSchedule( pMonster ); + + // This may cause a sequence to attempt to grab this guy NOW, so we have to clear him out + // of the existing sequence + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); +} + +//========================================================= +// When a monster finishes a scripted sequence, we have to +// fix up its state and schedule for it to return to a +// normal AI monster. +// +// Scripted sequences just dirty the Schedule and drop the +// monster in Idle State. +//========================================================= +void CCineMonster :: FixScriptMonsterSchedule( CBaseMonster *pMonster ) +{ + if ( pMonster->m_IdealMonsterState != MONSTERSTATE_DEAD ) + pMonster->m_IdealMonsterState = MONSTERSTATE_IDLE; + pMonster->ClearSchedule(); +} + +//========================================================= +// When a monster finishes a scripted sequence, we have to +// fix up its state and schedule for it to return to a +// normal AI monster. +// +// AI Scripted sequences will, depending on what the level +// designer selects: +// +// -Dirty the monster's schedule and drop out of the +// sequence in their current state. +// +// -Select a specific AMBUSH schedule, regardless of state. +//========================================================= +void CCineAI :: FixScriptMonsterSchedule( CBaseMonster *pMonster ) +{ + switch ( m_iFinishSchedule ) + { + case SCRIPT_FINISHSCHED_DEFAULT: + pMonster->ClearSchedule(); + break; + case SCRIPT_FINISHSCHED_AMBUSH: + pMonster->ChangeSchedule( pMonster->GetScheduleOfType( SCHED_AMBUSH ) ); + break; + default: + ALERT ( at_aiconsole, "FixScriptMonsterSchedule - no case!\n" ); + pMonster->ClearSchedule(); + break; + } +} + +BOOL CBaseMonster :: ExitScriptedSequence( ) +{ + if ( pev->deadflag == DEAD_DYING ) + { + // is this legal? + // BUGBUG -- This doesn't call Killed() + m_IdealMonsterState = MONSTERSTATE_DEAD; + return FALSE; + } + + if (m_pCine) + { + m_pCine->CancelScript( ); + } + + return TRUE; +} + + +void CCineMonster::AllowInterrupt( BOOL fAllow ) +{ + if ( pev->spawnflags & SF_SCRIPT_NOINTERRUPT ) + return; + m_interruptable = fAllow; +} + + +BOOL CCineMonster::CanInterrupt( void ) +{ + if ( !m_interruptable ) + return FALSE; + + CBaseEntity *pTarget = m_hTargetEnt; + + if ( pTarget != NULL && pTarget->pev->deadflag == DEAD_NO ) + return TRUE; + + return FALSE; +} + + +int CCineMonster::IgnoreConditions( void ) +{ + if ( CanInterrupt() ) + return 0; + return SCRIPT_BREAK_CONDITIONS; +} + + +void ScriptEntityCancel( edict_t *pentCine ) +{ + // make sure they are a scripted_sequence + if (FClassnameIs( pentCine, CLASSNAME )) + { + CCineMonster *pCineTarget = GetClassPtr((CCineMonster *)VARS(pentCine)); + // make sure they have a monster in mind for the script + CBaseEntity *pEntity = pCineTarget->m_hTargetEnt; + CBaseMonster *pTarget = NULL; + if ( pEntity ) + pTarget = pEntity->MyMonsterPointer(); + + if (pTarget) + { + // make sure their monster is actually playing a script + if ( pTarget->m_MonsterState == MONSTERSTATE_SCRIPT ) + { + // tell them do die + pTarget->m_scriptState = CCineMonster::SCRIPT_CLEANUP; + // do it now + pTarget->CineCleanup( ); + } + } + } +} + + +// find all the cinematic entities with my targetname and stop them from playing +void CCineMonster :: CancelScript( void ) +{ + ALERT( at_aiconsole, "Cancelling script: %s\n", STRING(m_iszPlay) ); + + if ( !pev->targetname ) + { + ScriptEntityCancel( edict() ); + return; + } + + edict_t *pentCineTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(pev->targetname)); + + while (!FNullEnt(pentCineTarget)) + { + ScriptEntityCancel( pentCineTarget ); + pentCineTarget = FIND_ENTITY_BY_TARGETNAME(pentCineTarget, STRING(pev->targetname)); + } +} + + +// find all the cinematic entities with my targetname and tell them to wait before starting +void CCineMonster :: DelayStart( int state ) +{ + edict_t *pentCine = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(pev->targetname)); + + while (!FNullEnt(pentCine)) + { + if (FClassnameIs( pentCine, "scripted_sequence" )) + { + CCineMonster *pTarget = GetClassPtr((CCineMonster *)VARS(pentCine)); + if (state) + { + pTarget->m_iDelay++; + } + else + { + pTarget->m_iDelay--; + if (pTarget->m_iDelay <= 0) + pTarget->m_startTime = gpGlobals->time + 0.05; + } + } + pentCine = FIND_ENTITY_BY_TARGETNAME(pentCine, STRING(pev->targetname)); + } +} + + + +// Find an entity that I'm interested in and precache the sounds he'll need in the sequence. +void CCineMonster :: Activate( void ) +{ + edict_t *pentTarget; + CBaseMonster *pTarget; + + // The entity name could be a target name or a classname + // Check the targetname + pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity)); + pTarget = NULL; + + while (!pTarget && !FNullEnt(pentTarget)) + { + if ( FBitSet( VARS(pentTarget)->flags, FL_MONSTER )) + { + pTarget = GetMonsterPointer( pentTarget ); + } + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + } + + // If no entity with that targetname, check the classname + if ( !pTarget ) + { + pentTarget = FIND_ENTITY_BY_CLASSNAME(NULL, STRING(m_iszEntity)); + while (!pTarget && !FNullEnt(pentTarget)) + { + pTarget = GetMonsterPointer( pentTarget ); + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + } + } + // Found a compatible entity + if ( pTarget ) + { + void *pmodel; + pmodel = GET_MODEL_PTR( pTarget->edict() ); + if ( pmodel ) + { + // Look through the event list for stuff to precache + SequencePrecache( pmodel, STRING( m_iszIdle ) ); + SequencePrecache( pmodel, STRING( m_iszPlay ) ); + } + } +} + + +BOOL CBaseMonster :: CineCleanup( ) +{ + CCineMonster *pOldCine = m_pCine; + + // am I linked to a cinematic? + if (m_pCine) + { + // okay, reset me to what it thought I was before + m_pCine->m_hTargetEnt = NULL; + pev->movetype = m_pCine->m_saved_movetype; + pev->solid = m_pCine->m_saved_solid; + pev->effects = m_pCine->m_saved_effects; + } + else + { + // arg, punt + pev->movetype = MOVETYPE_STEP;// this is evil + pev->solid = SOLID_SLIDEBOX; + } + m_pCine = NULL; + m_hTargetEnt = NULL; + m_pGoalEnt = NULL; + if (pev->deadflag == DEAD_DYING) + { + // last frame of death animation? + pev->health = 0; + pev->framerate = 0.0; + pev->solid = SOLID_NOT; + SetState( MONSTERSTATE_DEAD ); + pev->deadflag = DEAD_DEAD; + UTIL_SetSize( pev, pev->mins, Vector(pev->maxs.x, pev->maxs.y, pev->mins.z + 2) ); + + if ( pOldCine && FBitSet( pOldCine->pev->spawnflags, SF_SCRIPT_LEAVECORPSE ) ) + { + SetUse( NULL ); // BUGBUG -- This doesn't call Killed() + SetThink( NULL ); // This will probably break some stuff + SetTouch( NULL ); + } + else + SUB_StartFadeOut(); // SetThink( SUB_DoNothing ); + // This turns off animation & physics in case their origin ends up stuck in the world or something + StopAnimation(); + pev->movetype = MOVETYPE_NONE; + pev->effects |= EF_NOINTERP; // Don't interpolate either, assume the corpse is positioned in its final resting place + return FALSE; + } + + // If we actually played a sequence + if ( pOldCine && pOldCine->m_iszPlay ) + { + if ( !(pOldCine->pev->spawnflags & SF_SCRIPT_NOSCRIPTMOVEMENT) ) + { + // reset position + Vector new_origin, new_angle; + GetBonePosition( 0, new_origin, new_angle ); + + // Figure out how far they have moved + // We can't really solve this problem because we can't query the movement of the origin relative + // to the sequence. We can get the root bone's position as we do here, but there are + // cases where the root bone is in a different relative position to the entity's origin + // before/after the sequence plays. So we are stuck doing this: + + // !!!HACKHACK: Float the origin up and drop to floor because some sequences have + // irregular motion that can't be properly accounted for. + + // UNDONE: THIS SHOULD ONLY HAPPEN IF WE ACTUALLY PLAYED THE SEQUENCE. + Vector oldOrigin = pev->origin; + + // UNDONE: ugly hack. Don't move monster if they don't "seem" to move + // this really needs to be done with the AX,AY,etc. flags, but that aren't consistantly + // being set, so animations that really do move won't be caught. + if ((oldOrigin - new_origin).Length2D() < 8.0) + new_origin = oldOrigin; + + pev->origin.x = new_origin.x; + pev->origin.y = new_origin.y; + pev->origin.z += 1; + + pev->flags |= FL_ONGROUND; + int drop = DROP_TO_FLOOR( ENT(pev) ); + + // Origin in solid? Set to org at the end of the sequence + if ( drop < 0 ) + pev->origin = oldOrigin; + else if ( drop == 0 ) // Hanging in air? + { + pev->origin.z = new_origin.z; + pev->flags &= ~FL_ONGROUND; + } + // else entity hit floor, leave there + + // pEntity->pev->origin.z = new_origin.z + 5.0; // damn, got to fix this + + UTIL_SetOrigin( pev, pev->origin ); + pev->effects |= EF_NOINTERP; + } + + // We should have some animation to put these guys in, but for now it's idle. + // Due to NOINTERP above, there won't be any blending between this anim & the sequence + m_Activity = ACT_RESET; + } + // set them back into a normal state + pev->enemy = NULL; + if ( pev->health > 0 ) + m_IdealMonsterState = MONSTERSTATE_IDLE; // m_previousState; + else + { + // Dropping out because he got killed + // Can't call killed() no attacker and weirdness (late gibbing) may result + m_IdealMonsterState = MONSTERSTATE_DEAD; + SetConditions( bits_COND_LIGHT_DAMAGE ); + pev->deadflag = DEAD_DYING; + FCheckAITrigger(); + pev->deadflag = DEAD_NO; + } + + + // SetAnimation( m_MonsterState ); + ClearBits(pev->spawnflags, SF_MONSTER_WAIT_FOR_SCRIPT ); + + return TRUE; +} + + + + +class CScriptedSentence : public CBaseToggle +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT FindThink( void ); + void EXPORT DelayThink( void ); + int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + CBaseMonster *FindEntity( void ); + BOOL AcceptableSpeaker( CBaseMonster *pMonster ); + BOOL StartSentence( CBaseMonster *pTarget ); + + +private: + int m_iszSentence; // string index for idle animation + int m_iszEntity; // entity that is wanted for this sentence + float m_flRadius; // range to search + float m_flDuration; // How long the sentence lasts + float m_flRepeat; // repeat rate + float m_flAttenuation; + float m_flVolume; + BOOL m_active; + int m_iszListener; // name of entity to look at while talking +}; + +#define SF_SENTENCE_ONCE 0x0001 +#define SF_SENTENCE_FOLLOWERS 0x0002 // only say if following player +#define SF_SENTENCE_INTERRUPT 0x0004 // force talking except when dead +#define SF_SENTENCE_CONCURRENT 0x0008 // allow other people to keep talking + +TYPEDESCRIPTION CScriptedSentence::m_SaveData[] = +{ + DEFINE_FIELD( CScriptedSentence, m_iszSentence, FIELD_STRING ), + DEFINE_FIELD( CScriptedSentence, m_iszEntity, FIELD_STRING ), + DEFINE_FIELD( CScriptedSentence, m_flRadius, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flDuration, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flRepeat, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flAttenuation, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_flVolume, FIELD_FLOAT ), + DEFINE_FIELD( CScriptedSentence, m_active, FIELD_BOOLEAN ), + DEFINE_FIELD( CScriptedSentence, m_iszListener, FIELD_STRING ), +}; + + +IMPLEMENT_SAVERESTORE( CScriptedSentence, CBaseToggle ); + +LINK_ENTITY_TO_CLASS( scripted_sentence, CScriptedSentence ); + +void CScriptedSentence :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sentence")) + { + m_iszSentence = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "entity")) + { + m_iszEntity = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "duration")) + { + m_flDuration = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "radius")) + { + m_flRadius = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "refire")) + { + m_flRepeat = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if(FStrEq(pkvd->szKeyName, "attenuation")) + { + pev->impulse = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if(FStrEq(pkvd->szKeyName, "volume")) + { + m_flVolume = atof( pkvd->szValue ) * 0.1; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "listener")) + { + m_iszListener = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + + +void CScriptedSentence :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !m_active ) + return; +// ALERT( at_console, "Firing sentence: %s\n", STRING(m_iszSentence) ); + SetThink( FindThink ); + pev->nextthink = gpGlobals->time; +} + + +void CScriptedSentence :: Spawn( void ) +{ + pev->solid = SOLID_NOT; + + m_active = TRUE; + // if no targetname, start now + if ( !pev->targetname ) + { + SetThink( FindThink ); + pev->nextthink = gpGlobals->time + 1.0; + } + + switch( pev->impulse ) + { + case 1: // Medium radius + m_flAttenuation = ATTN_STATIC; + break; + + case 2: // Large radius + m_flAttenuation = ATTN_NORM; + break; + + case 3: //EVERYWHERE + m_flAttenuation = ATTN_NONE; + break; + + default: + case 0: // Small radius + m_flAttenuation = ATTN_IDLE; + break; + } + pev->impulse = 0; + + // No volume, use normal + if ( m_flVolume <= 0 ) + m_flVolume = 1.0; +} + + +void CScriptedSentence :: FindThink( void ) +{ + CBaseMonster *pMonster = FindEntity(); + if ( pMonster ) + { + StartSentence( pMonster ); + if ( pev->spawnflags & SF_SENTENCE_ONCE ) + UTIL_Remove( this ); + SetThink( DelayThink ); + pev->nextthink = gpGlobals->time + m_flDuration + m_flRepeat; + m_active = FALSE; +// ALERT( at_console, "%s: found monster %s\n", STRING(m_iszSentence), STRING(m_iszEntity) ); + } + else + { +// ALERT( at_console, "%s: can't find monster %s\n", STRING(m_iszSentence), STRING(m_iszEntity) ); + pev->nextthink = gpGlobals->time + m_flRepeat + 0.5; + } +} + + +void CScriptedSentence :: DelayThink( void ) +{ + m_active = TRUE; + if ( !pev->targetname ) + pev->nextthink = gpGlobals->time + 0.1; + SetThink( FindThink ); +} + + +BOOL CScriptedSentence :: AcceptableSpeaker( CBaseMonster *pMonster ) +{ + if ( pMonster ) + { + if ( pev->spawnflags & SF_SENTENCE_FOLLOWERS ) + { + if ( pMonster->m_hTargetEnt == NULL || !FClassnameIs(pMonster->m_hTargetEnt->pev, "player") ) + return FALSE; + } + BOOL override; + if ( pev->spawnflags & SF_SENTENCE_INTERRUPT ) + override = TRUE; + else + override = FALSE; + if ( pMonster->CanPlaySentence( override ) ) + return TRUE; + } + return FALSE; +} + + +CBaseMonster *CScriptedSentence :: FindEntity( void ) +{ + edict_t *pentTarget; + CBaseMonster *pMonster; + + + pentTarget = FIND_ENTITY_BY_TARGETNAME(NULL, STRING(m_iszEntity)); + pMonster = NULL; + + while (!FNullEnt(pentTarget)) + { + pMonster = GetMonsterPointer( pentTarget ); + if ( pMonster != NULL ) + { + if ( AcceptableSpeaker( pMonster ) ) + return pMonster; +// ALERT( at_console, "%s (%s), not acceptable\n", STRING(pMonster->pev->classname), STRING(pMonster->pev->targetname) ); + } + pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, STRING(m_iszEntity)); + } + + CBaseEntity *pEntity = NULL; + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, m_flRadius )) != NULL) + { + if (FClassnameIs( pEntity->pev, STRING(m_iszEntity))) + { + if ( FBitSet( pEntity->pev->flags, FL_MONSTER )) + { + pMonster = pEntity->MyMonsterPointer( ); + if ( AcceptableSpeaker( pMonster ) ) + return pMonster; + } + } + } + + return NULL; +} + + +BOOL CScriptedSentence :: StartSentence( CBaseMonster *pTarget ) +{ + if ( !pTarget ) + { + ALERT( at_aiconsole, "Not Playing sentence %s\n", STRING(m_iszSentence) ); + return NULL; + } + + BOOL bConcurrent = FALSE; + if ( !(pev->spawnflags & SF_SENTENCE_CONCURRENT) ) + bConcurrent = TRUE; + + CBaseEntity *pListener = NULL; + if (!FStringNull(m_iszListener)) + { + float radius = m_flRadius; + + if ( FStrEq( STRING(m_iszListener ), "player" ) ) + radius = 4096; // Always find the player + + pListener = UTIL_FindEntityGeneric( STRING( m_iszListener ), pTarget->pev->origin, radius ); + } + + pTarget->PlayScriptedSentence( STRING(m_iszSentence), m_flDuration, m_flVolume, m_flAttenuation, bConcurrent, pListener ); + ALERT( at_aiconsole, "Playing sentence %s (%.1f)\n", STRING(m_iszSentence), m_flDuration ); + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); + return TRUE; +} + + + + + +/* + +*/ + + +//========================================================= +// Furniture - this is the cool comment I cut-and-pasted +//========================================================= +class CFurniture : public CBaseMonster +{ +public: + void Spawn ( void ); + void Die( void ); + int Classify ( void ); + virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } +}; + + +LINK_ENTITY_TO_CLASS( monster_furniture, CFurniture ); + + +//========================================================= +// Furniture is killed +//========================================================= +void CFurniture :: Die ( void ) +{ + SetThink ( SUB_Remove ); + pev->nextthink = gpGlobals->time; +} + +//========================================================= +// This used to have something to do with bees flying, but +// now it only initializes moving furniture in scripted sequences +//========================================================= +void CFurniture :: Spawn( ) +{ + PRECACHE_MODEL((char *)STRING(pev->model)); + SET_MODEL(ENT(pev), STRING(pev->model)); + + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_BBOX; + pev->health = 80000; + pev->takedamage = DAMAGE_AIM; + pev->effects = 0; + pev->yaw_speed = 0; + pev->sequence = 0; + pev->frame = 0; + +// pev->nextthink += 1.0; +// SetThink (WalkMonsterDelay); + + ResetSequenceInfo( ); + pev->frame = 0; + MonsterInit(); +} + +//========================================================= +// ID's Furniture as neutral (noone will attack it) +//========================================================= +int CFurniture::Classify ( void ) +{ + return CLASS_NONE; +} + + diff --git a/dlls/scripted.h b/dlls/scripted.h new file mode 100644 index 0000000..67a2eb1 --- /dev/null +++ b/dlls/scripted.h @@ -0,0 +1,107 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#ifndef SCRIPTED_H +#define SCRIPTED_H + +#ifndef SCRIPTEVENT_H +#include "scriptevent.h" +#endif + +#define SF_SCRIPT_WAITTILLSEEN 1 +#define SF_SCRIPT_EXITAGITATED 2 +#define SF_SCRIPT_REPEATABLE 4 +#define SF_SCRIPT_LEAVECORPSE 8 +//#define SF_SCRIPT_INTERPOLATE 16 // don't use, old bug +#define SF_SCRIPT_NOINTERRUPT 32 +#define SF_SCRIPT_OVERRIDESTATE 64 +#define SF_SCRIPT_NOSCRIPTMOVEMENT 128 + +#define SCRIPT_BREAK_CONDITIONS (bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE) + +enum SS_INTERRUPT +{ + SS_INTERRUPT_IDLE = 0, + SS_INTERRUPT_BY_NAME, + SS_INTERRUPT_AI, +}; + +// when a monster finishes an AI scripted sequence, we can choose +// a schedule to place them in. These defines are the aliases to +// resolve worldcraft input to real schedules (sjb) +#define SCRIPT_FINISHSCHED_DEFAULT 0 +#define SCRIPT_FINISHSCHED_AMBUSH 1 + +class CCineMonster : public CBaseMonster +{ +public: + void Spawn( void ); + virtual void KeyValue( KeyValueData *pkvd ); + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void Blocked( CBaseEntity *pOther ); + virtual void Touch( CBaseEntity *pOther ); + virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + virtual void Activate( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // void EXPORT CineSpawnThink( void ); + void EXPORT CineThink( void ); + void Pain( void ); + void Die( void ); + void DelayStart( int state ); + BOOL FindEntity( void ); + virtual void PossessEntity( void ); + + void ReleaseEntity( CBaseMonster *pEntity ); + void CancelScript( void ); + virtual BOOL StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ); + virtual BOOL FCanOverrideState ( void ); + void SequenceDone ( CBaseMonster *pMonster ); + virtual void FixScriptMonsterSchedule( CBaseMonster *pMonster ); + BOOL CanInterrupt( void ); + void AllowInterrupt( BOOL fAllow ); + int IgnoreConditions( void ); + + int m_iszIdle; // string index for idle animation + int m_iszPlay; // string index for scripted animation + int m_iszEntity; // entity that is wanted for this script + int m_fMoveTo; + int m_iFinishSchedule; + float m_flRadius; // range to search + float m_flRepeat; // repeat rate + + int m_iDelay; + float m_startTime; + + int m_saved_movetype; + int m_saved_solid; + int m_saved_effects; +// Vector m_vecOrigOrigin; + BOOL m_interruptable; +}; + +class CCineAI : public CCineMonster +{ + BOOL StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL completeOnEmpty ); + void PossessEntity( void ); + BOOL FCanOverrideState ( void ); + virtual void FixScriptMonsterSchedule( CBaseMonster *pMonster ); +}; + + +#endif //SCRIPTED_H diff --git a/dlls/scriptevent.h b/dlls/scriptevent.h index 0f33f26..9a02bd6 100644 --- a/dlls/scriptevent.h +++ b/dlls/scriptevent.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/shotgun.cpp b/dlls/shotgun.cpp index 77130f9..dfa084b 100644 --- a/dlls/shotgun.cpp +++ b/dlls/shotgun.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/singleplay_gamerules.cpp b/dlls/singleplay_gamerules.cpp index 96f9d74..b71b96a 100644 --- a/dlls/singleplay_gamerules.cpp +++ b/dlls/singleplay_gamerules.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/skill.cpp b/dlls/skill.cpp index bb94a6e..7c0b852 100644 --- a/dlls/skill.cpp +++ b/dlls/skill.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/skill.h b/dlls/skill.h index 8246b61..4434071 100644 --- a/dlls/skill.h +++ b/dlls/skill.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/sound.cpp b/dlls/sound.cpp index cce1af2..174e5f9 100644 --- a/dlls/sound.cpp +++ b/dlls/sound.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/soundent.cpp b/dlls/soundent.cpp index edc17af..93c70a7 100644 --- a/dlls/soundent.cpp +++ b/dlls/soundent.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/soundent.h b/dlls/soundent.h index 5294ff6..2393cb4 100644 --- a/dlls/soundent.h +++ b/dlls/soundent.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/spectator.cpp b/dlls/spectator.cpp index 1df2f4d..9d6bef9 100644 --- a/dlls/spectator.cpp +++ b/dlls/spectator.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/spectator.h b/dlls/spectator.h index 0383723..f832621 100644 --- a/dlls/spectator.h +++ b/dlls/spectator.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/squad.h b/dlls/squad.h new file mode 100644 index 0000000..5f9f249 --- /dev/null +++ b/dlls/squad.h @@ -0,0 +1,20 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: New version of the slider bar +// +// $NoKeywords: $ +//============================================================================= + +//========================================================= +// squad.h +//========================================================= + +// these are special group roles that are assigned to members when the group is formed. +// the reason these are explicitly assigned and tasks like throwing grenades to flush out +// enemies is that it's bad to have two members trying to flank left at the same time, but +// ok to have two throwing grenades at the same time. When a squad member cannot attack the +// enemy, it will choose to execute its special role. +#define bits_SQUAD_FLANK_LEFT ( 1 << 0 ) +#define bits_SQUAD_FLANK_RIGHT ( 1 << 1 ) +#define bits_SQUAD_ADVANCE ( 1 << 2 ) +#define bits_SQUAD_FLUSH_ATTACK ( 1 << 3 ) \ No newline at end of file diff --git a/dlls/squadmonster.cpp b/dlls/squadmonster.cpp new file mode 100644 index 0000000..d0a65ba --- /dev/null +++ b/dlls/squadmonster.cpp @@ -0,0 +1,623 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Squadmonster functions +//========================================================= +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "nodes.h" +#include "monsters.h" +#include "animation.h" +#include "saverestore.h" +#include "squadmonster.h" +#include "plane.h" + +//========================================================= +// Save/Restore +//========================================================= +TYPEDESCRIPTION CSquadMonster::m_SaveData[] = +{ + DEFINE_FIELD( CSquadMonster, m_hSquadLeader, FIELD_EHANDLE ), + DEFINE_ARRAY( CSquadMonster, m_hSquadMember, FIELD_EHANDLE, MAX_SQUAD_MEMBERS - 1 ), + + // DEFINE_FIELD( CSquadMonster, m_afSquadSlots, FIELD_INTEGER ), // these need to be reset after transitions! + DEFINE_FIELD( CSquadMonster, m_fEnemyEluded, FIELD_BOOLEAN ), + DEFINE_FIELD( CSquadMonster, m_flLastEnemySightTime, FIELD_TIME ), + + DEFINE_FIELD( CSquadMonster, m_iMySlot, FIELD_INTEGER ), + + +}; + +IMPLEMENT_SAVERESTORE( CSquadMonster, CBaseMonster ); + + +//========================================================= +// OccupySlot - if any slots of the passed slots are +// available, the monster will be assigned to one. +//========================================================= +BOOL CSquadMonster :: OccupySlot( int iDesiredSlots ) +{ + int i; + int iMask; + int iSquadSlots; + + if ( !InSquad() ) + { + return TRUE; + } + + if ( SquadEnemySplit() ) + { + // if the squad members aren't all fighting the same enemy, slots are disabled + // so that a squad member doesn't get stranded unable to engage his enemy because + // all of the attack slots are taken by squad members fighting other enemies. + m_iMySlot = bits_SLOT_SQUAD_SPLIT; + return TRUE; + } + + CSquadMonster *pSquadLeader = MySquadLeader(); + + if ( !( iDesiredSlots ^ pSquadLeader->m_afSquadSlots ) ) + { + // none of the desired slots are available. + return FALSE; + } + + iSquadSlots = pSquadLeader->m_afSquadSlots; + + for ( i = 0; i < NUM_SLOTS; i++ ) + { + iMask = 1<m_afSquadSlots |= iMask; + m_iMySlot = iMask; +// ALERT ( at_aiconsole, "Took slot %d - %d\n", i, m_hSquadLeader->m_afSquadSlots ); + return TRUE; + } + } + } + + return FALSE; +} + +//========================================================= +// VacateSlot +//========================================================= +void CSquadMonster :: VacateSlot() +{ + if ( m_iMySlot != bits_NO_SLOT && InSquad() ) + { +// ALERT ( at_aiconsole, "Vacated Slot %d - %d\n", m_iMySlot, m_hSquadLeader->m_afSquadSlots ); + MySquadLeader()->m_afSquadSlots &= ~m_iMySlot; + m_iMySlot = bits_NO_SLOT; + } +} + +//========================================================= +// ScheduleChange +//========================================================= +void CSquadMonster :: ScheduleChange ( void ) +{ + VacateSlot(); +} + +//========================================================= +// Killed +//========================================================= +void CSquadMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + VacateSlot(); + + if ( InSquad() ) + { + MySquadLeader()->SquadRemove( this ); + } + + CBaseMonster :: Killed ( pevAttacker, iGib ); +} + +// These functions are still awaiting conversion to CSquadMonster + + +//========================================================= +// +// SquadRemove(), remove pRemove from my squad. +// If I am pRemove, promote m_pSquadNext to leader +// +//========================================================= +void CSquadMonster :: SquadRemove( CSquadMonster *pRemove ) +{ + ASSERT( pRemove!=NULL ); + ASSERT( this->IsLeader() ); + ASSERT( pRemove->m_hSquadLeader == this ); + + // If I'm the leader, get rid of my squad + if (pRemove == MySquadLeader()) + { + for (int i = 0; i < MAX_SQUAD_MEMBERS-1;i++) + { + CSquadMonster *pMember = MySquadMember(i); + if (pMember) + { + pMember->m_hSquadLeader = NULL; + m_hSquadMember[i] = NULL; + } + } + } + else + { + CSquadMonster *pSquadLeader = MySquadLeader(); + if (pSquadLeader) + { + for (int i = 0; i < MAX_SQUAD_MEMBERS-1;i++) + { + if (pSquadLeader->m_hSquadMember[i] == this) + { + pSquadLeader->m_hSquadMember[i] = NULL; + break; + } + } + } + } + + pRemove->m_hSquadLeader = NULL; +} + +//========================================================= +// +// SquadAdd(), add pAdd to my squad +// +//========================================================= +BOOL CSquadMonster :: SquadAdd( CSquadMonster *pAdd ) +{ + ASSERT( pAdd!=NULL ); + ASSERT( !pAdd->InSquad() ); + ASSERT( this->IsLeader() ); + + for (int i = 0; i < MAX_SQUAD_MEMBERS-1; i++) + { + if (m_hSquadMember[i] == NULL) + { + m_hSquadMember[i] = pAdd; + pAdd->m_hSquadLeader = this; + return TRUE; + } + } + return FALSE; + // should complain here +} + + +//========================================================= +// +// SquadPasteEnemyInfo - called by squad members that have +// current info on the enemy so that it can be stored for +// members who don't have current info. +// +//========================================================= +void CSquadMonster :: SquadPasteEnemyInfo ( void ) +{ + CSquadMonster *pSquadLeader = MySquadLeader( ); + if (pSquadLeader) + pSquadLeader->m_vecEnemyLKP = m_vecEnemyLKP; +} + +//========================================================= +// +// SquadCopyEnemyInfo - called by squad members who don't +// have current info on the enemy. Reads from the same fields +// in the leader's data that other squad members write to, +// so the most recent data is always available here. +// +//========================================================= +void CSquadMonster :: SquadCopyEnemyInfo ( void ) +{ + CSquadMonster *pSquadLeader = MySquadLeader( ); + if (pSquadLeader) + m_vecEnemyLKP = pSquadLeader->m_vecEnemyLKP; +} + +//========================================================= +// +// SquadMakeEnemy - makes everyone in the squad angry at +// the same entity. +// +//========================================================= +void CSquadMonster :: SquadMakeEnemy ( CBaseEntity *pEnemy ) +{ + if (!InSquad()) + return; + + if ( !pEnemy ) + { + ALERT ( at_console, "ERROR: SquadMakeEnemy() - pEnemy is NULL!\n" ); + return; + } + + CSquadMonster *pSquadLeader = MySquadLeader( ); + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + if (pMember) + { + // reset members who aren't activly engaged in fighting + if (pMember->m_hEnemy != pEnemy && !pMember->HasConditions( bits_COND_SEE_ENEMY)) + { + if ( pMember->m_hEnemy != NULL) + { + // remember their current enemy + pMember->PushEnemy( pMember->m_hEnemy, pMember->m_vecEnemyLKP ); + } + // give them a new enemy + pMember->m_hEnemy = pEnemy; + pMember->m_vecEnemyLKP = pEnemy->pev->origin; + pMember->SetConditions ( bits_COND_NEW_ENEMY ); + } + } + } +} + + +//========================================================= +// +// SquadCount(), return the number of members of this squad +// callable from leaders & followers +// +//========================================================= +int CSquadMonster :: SquadCount( void ) +{ + if (!InSquad()) + return 0; + + CSquadMonster *pSquadLeader = MySquadLeader(); + int squadCount = 0; + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + if (pSquadLeader->MySquadMember(i) != NULL) + squadCount++; + } + + return squadCount; +} + + +//========================================================= +// +// SquadRecruit(), get some monsters of my classification and +// link them as a group. returns the group size +// +//========================================================= +int CSquadMonster :: SquadRecruit( int searchRadius, int maxMembers ) +{ + int squadCount; + int iMyClass = Classify();// cache this monster's class + + + // Don't recruit if I'm already in a group + if ( InSquad() ) + return 0; + + if ( maxMembers < 2 ) + return 0; + + // I am my own leader + m_hSquadLeader = this; + squadCount = 1; + + CBaseEntity *pEntity = NULL; + + if ( !FStringNull( pev->netname ) ) + { + // I have a netname, so unconditionally recruit everyone else with that name. + pEntity = UTIL_FindEntityByString( pEntity, "netname", STRING( pev->netname ) ); + while ( pEntity ) + { + CSquadMonster *pRecruit = pEntity->MySquadMonsterPointer(); + + if ( pRecruit ) + { + if ( !pRecruit->InSquad() && pRecruit->Classify() == iMyClass && pRecruit != this ) + { + // minimum protection here against user error.in worldcraft. + if (!SquadAdd( pRecruit )) + break; + squadCount++; + } + } + + pEntity = UTIL_FindEntityByString( pEntity, "netname", STRING( pev->netname ) ); + } + } + else + { + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, searchRadius )) != NULL) + { + CSquadMonster *pRecruit = pEntity->MySquadMonsterPointer( ); + + if ( pRecruit && pRecruit != this && pRecruit->IsAlive() && !pRecruit->m_pCine ) + { + // Can we recruit this guy? + if ( !pRecruit->InSquad() && pRecruit->Classify() == iMyClass && + ( (iMyClass != CLASS_ALIEN_MONSTER) || FStrEq(STRING(pev->classname), STRING(pRecruit->pev->classname))) && + FStringNull( pRecruit->pev->netname ) ) + { + TraceResult tr; + UTIL_TraceLine( pev->origin + pev->view_ofs, pRecruit->pev->origin + pev->view_ofs, ignore_monsters, pRecruit->edict(), &tr );// try to hit recruit with a traceline. + if ( tr.flFraction == 1.0 ) + { + if (!SquadAdd( pRecruit )) + break; + + squadCount++; + } + } + } + } + } + + // no single member squads + if (squadCount == 1) + { + m_hSquadLeader = NULL; + } + + return squadCount; +} + +//========================================================= +// CheckEnemy +//========================================================= +int CSquadMonster :: CheckEnemy ( CBaseEntity *pEnemy ) +{ + int iUpdatedLKP; + + iUpdatedLKP = CBaseMonster :: CheckEnemy ( m_hEnemy ); + + // communicate with squad members about the enemy IF this individual has the same enemy as the squad leader. + if ( InSquad() && (CBaseEntity *)m_hEnemy == MySquadLeader()->m_hEnemy ) + { + if ( iUpdatedLKP ) + { + // have new enemy information, so paste to the squad. + SquadPasteEnemyInfo(); + } + else + { + // enemy unseen, copy from the squad knowledge. + SquadCopyEnemyInfo(); + } + } + + return iUpdatedLKP; +} + +//========================================================= +// StartMonster +//========================================================= +void CSquadMonster :: StartMonster( void ) +{ + CBaseMonster :: StartMonster(); + + if ( ( m_afCapability & bits_CAP_SQUAD ) && !InSquad() ) + { + if ( !FStringNull( pev->netname ) ) + { + // if I have a groupname, I can only recruit if I'm flagged as leader + if ( !( pev->spawnflags & SF_SQUADMONSTER_LEADER ) ) + { + return; + } + } + + // try to form squads now. + int iSquadSize = SquadRecruit( 1024, 4 ); + + if ( iSquadSize ) + { + ALERT ( at_aiconsole, "Squad of %d %s formed\n", iSquadSize, STRING( pev->classname ) ); + } + + if ( IsLeader() && FClassnameIs ( pev, "monster_human_grunt" ) ) + { + SetBodygroup( 1, 1 ); // UNDONE: truly ugly hack + pev->skin = 0; + } + + } +} + +//========================================================= +// NoFriendlyFire - checks for possibility of friendly fire +// +// Builds a large box in front of the grunt and checks to see +// if any squad members are in that box. +//========================================================= +BOOL CSquadMonster :: NoFriendlyFire( void ) +{ + if ( !InSquad() ) + { + return TRUE; + } + + CPlane backPlane; + CPlane leftPlane; + CPlane rightPlane; + + Vector vecLeftSide; + Vector vecRightSide; + Vector v_left; + + //!!!BUGBUG - to fix this, the planes must be aligned to where the monster will be firing its gun, not the direction it is facing!!! + + if ( m_hEnemy != NULL ) + { + UTIL_MakeVectors ( UTIL_VecToAngles( m_hEnemy->Center() - pev->origin ) ); + } + else + { + // if there's no enemy, pretend there's a friendly in the way, so the grunt won't shoot. + return FALSE; + } + + //UTIL_MakeVectors ( pev->angles ); + + vecLeftSide = pev->origin - ( gpGlobals->v_right * ( pev->size.x * 1.5 ) ); + vecRightSide = pev->origin + ( gpGlobals->v_right * ( pev->size.x * 1.5 ) ); + v_left = gpGlobals->v_right * -1; + + leftPlane.InitializePlane ( gpGlobals->v_right, vecLeftSide ); + rightPlane.InitializePlane ( v_left, vecRightSide ); + backPlane.InitializePlane ( gpGlobals->v_forward, pev->origin ); + +/* + ALERT ( at_console, "LeftPlane: %f %f %f : %f\n", leftPlane.m_vecNormal.x, leftPlane.m_vecNormal.y, leftPlane.m_vecNormal.z, leftPlane.m_flDist ); + ALERT ( at_console, "RightPlane: %f %f %f : %f\n", rightPlane.m_vecNormal.x, rightPlane.m_vecNormal.y, rightPlane.m_vecNormal.z, rightPlane.m_flDist ); + ALERT ( at_console, "BackPlane: %f %f %f : %f\n", backPlane.m_vecNormal.x, backPlane.m_vecNormal.y, backPlane.m_vecNormal.z, backPlane.m_flDist ); +*/ + + CSquadMonster *pSquadLeader = MySquadLeader(); + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + if (pMember && pMember != this) + { + + if ( backPlane.PointInFront ( pMember->pev->origin ) && + leftPlane.PointInFront ( pMember->pev->origin ) && + rightPlane.PointInFront ( pMember->pev->origin) ) + { + // this guy is in the check volume! Don't shoot! + return FALSE; + } + } + } + + return TRUE; +} + +//========================================================= +// GetIdealState - surveys the Conditions information available +// and finds the best new state for a monster. +//========================================================= +MONSTERSTATE CSquadMonster :: GetIdealState ( void ) +{ + int iConditions; + + iConditions = IScheduleFlags(); + + // If no schedule conditions, the new ideal state is probably the reason we're in here. + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + case MONSTERSTATE_ALERT: + if ( HasConditions ( bits_COND_NEW_ENEMY ) && InSquad() ) + { + SquadMakeEnemy ( m_hEnemy ); + } + break; + } + + return CBaseMonster :: GetIdealState(); +} + +//========================================================= +// FValidateCover - determines whether or not the chosen +// cover location is a good one to move to. (currently based +// on proximity to others in the squad) +//========================================================= +BOOL CSquadMonster :: FValidateCover ( const Vector &vecCoverLocation ) +{ + if ( !InSquad() ) + { + return TRUE; + } + + if (SquadMemberInRange( vecCoverLocation, 128 )) + { + // another squad member is too close to this piece of cover. + return FALSE; + } + + return TRUE; +} + +//========================================================= +// SquadEnemySplit- returns TRUE if not all squad members +// are fighting the same enemy. +//========================================================= +BOOL CSquadMonster :: SquadEnemySplit ( void ) +{ + if (!InSquad()) + return FALSE; + + CSquadMonster *pSquadLeader = MySquadLeader(); + CBaseEntity *pEnemy = pSquadLeader->m_hEnemy; + + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pMember = pSquadLeader->MySquadMember(i); + if (pMember != NULL && pMember->m_hEnemy != NULL && pMember->m_hEnemy != pEnemy) + { + return TRUE; + } + } + return FALSE; +} + +//========================================================= +// FValidateCover - determines whether or not the chosen +// cover location is a good one to move to. (currently based +// on proximity to others in the squad) +//========================================================= +BOOL CSquadMonster :: SquadMemberInRange ( const Vector &vecLocation, float flDist ) +{ + if (!InSquad()) + return FALSE; + + CSquadMonster *pSquadLeader = MySquadLeader(); + + for (int i = 0; i < MAX_SQUAD_MEMBERS; i++) + { + CSquadMonster *pSquadMember = pSquadLeader->MySquadMember(i); + if (pSquadMember && (vecLocation - pSquadMember->pev->origin ).Length2D() <= flDist) + return TRUE; + } + return FALSE; +} + + +extern Schedule_t slChaseEnemyFailed[]; + +Schedule_t *CSquadMonster::GetScheduleOfType( int iType ) +{ + switch ( iType ) + { + + case SCHED_CHASE_ENEMY_FAILED: + { + return &slChaseEnemyFailed[ 0 ]; + } + + default: + return CBaseMonster::GetScheduleOfType( iType ); + } +} + diff --git a/dlls/squadmonster.h b/dlls/squadmonster.h new file mode 100644 index 0000000..f529787 --- /dev/null +++ b/dlls/squadmonster.h @@ -0,0 +1,120 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// CSquadMonster - all the extra data for monsters that +// form squads. +//========================================================= + +#define SF_SQUADMONSTER_LEADER 32 + + +#define bits_NO_SLOT 0 + +// HUMAN GRUNT SLOTS +#define bits_SLOT_HGRUNT_ENGAGE1 ( 1 << 0 ) +#define bits_SLOT_HGRUNT_ENGAGE2 ( 1 << 1 ) +#define bits_SLOTS_HGRUNT_ENGAGE ( bits_SLOT_HGRUNT_ENGAGE1 | bits_SLOT_HGRUNT_ENGAGE2 ) + +#define bits_SLOT_HGRUNT_GRENADE1 ( 1 << 2 ) +#define bits_SLOT_HGRUNT_GRENADE2 ( 1 << 3 ) +#define bits_SLOTS_HGRUNT_GRENADE ( bits_SLOT_HGRUNT_GRENADE1 | bits_SLOT_HGRUNT_GRENADE2 ) + +// ALIEN GRUNT SLOTS +#define bits_SLOT_AGRUNT_HORNET1 ( 1 << 4 ) +#define bits_SLOT_AGRUNT_HORNET2 ( 1 << 5 ) +#define bits_SLOT_AGRUNT_CHASE ( 1 << 6 ) +#define bits_SLOTS_AGRUNT_HORNET ( bits_SLOT_AGRUNT_HORNET1 | bits_SLOT_AGRUNT_HORNET2 ) + +// HOUNDEYE SLOTS +#define bits_SLOT_HOUND_ATTACK1 ( 1 << 7 ) +#define bits_SLOT_HOUND_ATTACK2 ( 1 << 8 ) +#define bits_SLOT_HOUND_ATTACK3 ( 1 << 9 ) +#define bits_SLOTS_HOUND_ATTACK ( bits_SLOT_HOUND_ATTACK1 | bits_SLOT_HOUND_ATTACK2 | bits_SLOT_HOUND_ATTACK3 ) + +// global slots +#define bits_SLOT_SQUAD_SPLIT ( 1 << 10 )// squad members don't all have the same enemy + +#define NUM_SLOTS 11// update this every time you add/remove a slot. + +#define MAX_SQUAD_MEMBERS 5 + +//========================================================= +// CSquadMonster - for any monster that forms squads. +//========================================================= +class CSquadMonster : public CBaseMonster +{ +public: + // squad leader info + EHANDLE m_hSquadLeader; // who is my leader + EHANDLE m_hSquadMember[MAX_SQUAD_MEMBERS-1]; // valid only for leader + int m_afSquadSlots; + float m_flLastEnemySightTime; // last time anyone in the squad saw the enemy + BOOL m_fEnemyEluded; + + // squad member info + int m_iMySlot;// this is the behaviour slot that the monster currently holds in the squad. + + int CheckEnemy ( CBaseEntity *pEnemy ); + void StartMonster ( void ); + void VacateSlot( void ); + void ScheduleChange( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + BOOL OccupySlot( int iDesiredSlot ); + BOOL NoFriendlyFire( void ); + + // squad functions still left in base class + CSquadMonster *MySquadLeader( ) + { + CSquadMonster *pSquadLeader = (CSquadMonster *)((CBaseEntity *)m_hSquadLeader); + if (pSquadLeader != NULL) + return pSquadLeader; + return this; + } + CSquadMonster *MySquadMember( int i ) + { + if (i >= MAX_SQUAD_MEMBERS-1) + return this; + else + return (CSquadMonster *)((CBaseEntity *)m_hSquadMember[i]); + } + int InSquad ( void ) { return m_hSquadLeader != NULL; } + int IsLeader ( void ) { return m_hSquadLeader == this; } + int SquadJoin ( int searchRadius ); + int SquadRecruit ( int searchRadius, int maxMembers ); + int SquadCount( void ); + void SquadRemove( CSquadMonster *pRemove ); + void SquadUnlink( void ); + BOOL SquadAdd( CSquadMonster *pAdd ); + void SquadDisband( void ); + void SquadAddConditions ( int iConditions ); + void SquadMakeEnemy ( CBaseEntity *pEnemy ); + void SquadPasteEnemyInfo ( void ); + void SquadCopyEnemyInfo ( void ); + BOOL SquadEnemySplit ( void ); + BOOL SquadMemberInRange( const Vector &vecLocation, float flDist ); + + virtual CSquadMonster *MySquadMonsterPointer( void ) { return this; } + + static TYPEDESCRIPTION m_SaveData[]; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + BOOL FValidateCover ( const Vector &vecCoverLocation ); + + MONSTERSTATE GetIdealState ( void ); + Schedule_t *GetScheduleOfType ( int iType ); +}; + diff --git a/dlls/squeakgrenade.cpp b/dlls/squeakgrenade.cpp index aad82e8..5b7ce72 100644 --- a/dlls/squeakgrenade.cpp +++ b/dlls/squeakgrenade.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/stats.cpp b/dlls/stats.cpp new file mode 100644 index 0000000..3323c1c --- /dev/null +++ b/dlls/stats.cpp @@ -0,0 +1,156 @@ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ +// +// Purpose: New version of the slider bar +// +// $NoKeywords: $ +//============================================================================= + +#include "extdll.h" +#include "util.h" + +#include "cbase.h" +#include "player.h" +#include "trains.h" +#include "nodes.h" +#include "weapons.h" +#include "soundent.h" +#include "monsters.h" +#include "..\engine\shake.h" +#include "decals.h" +#include "gamerules.h" + + +float AmmoDamage( const char *pName ) +{ + if ( !pName ) + return 0; + + if ( !strcmp( pName, "9mm" ) ) + return gSkillData.plrDmg9MM; + if ( !strcmp( pName, "357" ) ) + return gSkillData.plrDmg357; + if ( !strcmp( pName, "ARgrenades" ) ) + return gSkillData.plrDmgM203Grenade; + if ( !strcmp( pName, "buckshot" ) ) + return gSkillData.plrDmgBuckshot; + if ( !strcmp( pName, "bolts") ) + return gSkillData.plrDmgCrossbowMonster; + if ( !strcmp( pName, "rockets") ) + return gSkillData.plrDmgRPG; + if ( !strcmp( pName, "uranium") ) + return gSkillData.plrDmgGauss; + if ( !strcmp( pName, "Hand Grenade") ) + return gSkillData.plrDmgHandGrenade; + if ( !strcmp( pName, "Satchel Charge") ) + return gSkillData.plrDmgSatchel; + if ( !strcmp( pName, "Trip Mine") ) + return gSkillData.plrDmgTripmine; + + return 0; +} + + +void UpdateStatsFile( float dataTime, char *pMapname, float health, float ammo, int skillLevel ) +{ + FILE *fp; + + fp = fopen( "stats.txt", "a" ); + if ( !fp ) + return; + fprintf( fp, "%6.2f, %6.2f, %6.2f, %s, %2d\n", dataTime, health, ammo, pMapname, skillLevel ); + fclose( fp ); +} + + +#define AMMO_THRESHOLD 10 // This much ammo goes by before it is "interesting" +#define HEALTH_THRESHOLD 10 // Same for health +#define OUTPUT_LATENCY 3 // This many seconds for ammo/health to settle + +typedef struct +{ + int lastAmmo; + float lastHealth; + float lastOutputTime; // NOTE: These times are in "game" time -- a running total of elapsed time since the game started + float nextOutputTime; + float dataTime; + float gameTime; + float lastGameTime; +} TESTSTATS; + +TESTSTATS gStats = {0,0,0,0,0,0,0}; + +void UpdateStats( CBasePlayer *pPlayer ) +{ + int i; + + int ammoCount[ MAX_AMMO_SLOTS ]; + memcpy( ammoCount, pPlayer->m_rgAmmo, MAX_AMMO_SLOTS * sizeof(int) ); + + // Keep a running time, so the graph doesn't overlap + + if ( gpGlobals->time < gStats.lastGameTime ) // Changed level or died, don't b0rk + { + gStats.lastGameTime = gpGlobals->time; + gStats.dataTime = gStats.gameTime; + } + + gStats.gameTime += gpGlobals->time - gStats.lastGameTime; + gStats.lastGameTime = gpGlobals->time; + + for (i = 0; i < MAX_ITEM_TYPES; i++) + { + CBasePlayerItem *p = pPlayer->m_rgpPlayerItems[i]; + while (p) + { + ItemInfo II; + + memset(&II, 0, sizeof(II)); + p->GetItemInfo(&II); + + int index = pPlayer->GetAmmoIndex(II.pszAmmo1); + if ( index >= 0 ) + ammoCount[ index ] += ((CBasePlayerWeapon *)p)->m_iClip; + + p = p->m_pNext; + } + } + + float ammo = 0; + for (i = 1; i < MAX_AMMO_SLOTS; i++) + { + ammo += ammoCount[i] * AmmoDamage( CBasePlayerItem::AmmoInfoArray[i].pszName ); + } + + float health = pPlayer->pev->health + pPlayer->pev->armorvalue * 2; // Armor is 2X health + float ammoDelta = fabs( ammo - gStats.lastAmmo ); + float healthDelta = fabs( health - gStats.lastHealth ); + int forceWrite = 0; + if ( health <= 0 && gStats.lastHealth > 0 ) + forceWrite = 1; + + if ( (ammoDelta > AMMO_THRESHOLD || healthDelta > HEALTH_THRESHOLD) && !forceWrite ) + { + if ( gStats.nextOutputTime == 0 ) + gStats.dataTime = gStats.gameTime; + + gStats.lastAmmo = ammo; + gStats.lastHealth = health; + + gStats.nextOutputTime = gStats.gameTime + OUTPUT_LATENCY; + } + else if ( (gStats.nextOutputTime != 0 && gStats.nextOutputTime < gStats.gameTime) || forceWrite ) + { + UpdateStatsFile( gStats.dataTime, (char *)STRING(gpGlobals->mapname), health, ammo, (int)CVAR_GET_FLOAT("skill") ); + + gStats.lastAmmo = ammo; + gStats.lastHealth = health; + gStats.lastOutputTime = gStats.gameTime; + gStats.nextOutputTime = 0; + } +} + +void InitStats( CBasePlayer *pPlayer ) +{ + gStats.lastGameTime = gpGlobals->time; // Fixup stats time +} + diff --git a/dlls/subs.cpp b/dlls/subs.cpp index 4d98947..a109366 100644 --- a/dlls/subs.cpp +++ b/dlls/subs.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/talkmonster.cpp b/dlls/talkmonster.cpp new file mode 100644 index 0000000..76ac05b --- /dev/null +++ b/dlls/talkmonster.cpp @@ -0,0 +1,1472 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "talkmonster.h" +#include "defaultai.h" +#include "scripted.h" +#include "soundent.h" +#include "animation.h" + +//========================================================= +// Talking monster base class +// Used for scientists and barneys +//========================================================= +float CTalkMonster::g_talkWaitTime = 0; // time delay until it's ok to speak: used so that two NPCs don't talk at once + +// NOTE: m_voicePitch & m_szGrp should be fixed up by precache each save/restore + +TYPEDESCRIPTION CTalkMonster::m_SaveData[] = +{ + DEFINE_FIELD( CTalkMonster, m_bitsSaid, FIELD_INTEGER ), + DEFINE_FIELD( CTalkMonster, m_nSpeak, FIELD_INTEGER ), + + // Recalc'ed in Precache() + // DEFINE_FIELD( CTalkMonster, m_voicePitch, FIELD_INTEGER ), + // DEFINE_FIELD( CTalkMonster, m_szGrp, FIELD_??? ), + DEFINE_FIELD( CTalkMonster, m_useTime, FIELD_TIME ), + DEFINE_FIELD( CTalkMonster, m_iszUse, FIELD_STRING ), + DEFINE_FIELD( CTalkMonster, m_iszUnUse, FIELD_STRING ), + DEFINE_FIELD( CTalkMonster, m_flLastSaidSmelled, FIELD_TIME ), + DEFINE_FIELD( CTalkMonster, m_flStopTalkTime, FIELD_TIME ), + DEFINE_FIELD( CTalkMonster, m_hTalkTarget, FIELD_EHANDLE ), +}; + +IMPLEMENT_SAVERESTORE( CTalkMonster, CBaseMonster ); + +// array of friend names +char *CTalkMonster::m_szFriends[TLK_CFRIENDS] = +{ + "monster_barney", + "monster_scientist", + "monster_sitting_scientist", +}; + + +//========================================================= +// AI Schedules Specific to talking monsters +//========================================================= + +Task_t tlIdleResponse[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_IDLE },// Stop and listen + { TASK_WAIT, (float)0.5 },// Wait until sure it's me they are talking to + { TASK_TLK_EYECONTACT, (float)0 },// Wait until speaker is done + { TASK_TLK_RESPOND, (float)0 },// Wait and then say my response + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 },// Wait until speaker is done +}; + +Schedule_t slIdleResponse[] = +{ + { + tlIdleResponse, + ARRAYSIZE ( tlIdleResponse ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Response" + + }, +}; + +Task_t tlIdleSpeak[] = +{ + { TASK_TLK_SPEAK, (float)0 },// question or remark + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT_RANDOM, (float)0.5 }, +}; + +Schedule_t slIdleSpeak[] = +{ + { + tlIdleSpeak, + ARRAYSIZE ( tlIdleSpeak ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Speak" + }, +}; + +Task_t tlIdleSpeakWait[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 },// Stop and talk + { TASK_TLK_SPEAK, (float)0 },// question or remark + { TASK_TLK_EYECONTACT, (float)0 },// + { TASK_WAIT, (float)2 },// wait - used when sci is in 'use' mode to keep head turned +}; + +Schedule_t slIdleSpeakWait[] = +{ + { + tlIdleSpeakWait, + ARRAYSIZE ( tlIdleSpeakWait ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "Idle Speak Wait" + }, +}; + +Task_t tlIdleHello[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 },// Stop and talk + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + { TASK_TLK_HELLO, (float)0 },// Try to say hello to player + { TASK_TLK_EYECONTACT, (float)0 }, + { TASK_WAIT, (float)0.5 },// wait a bit + +}; + +Schedule_t slIdleHello[] = +{ + { + tlIdleHello, + ARRAYSIZE ( tlIdleHello ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT, + "Idle Hello" + }, +}; + +Task_t tlIdleStopShooting[] = +{ + { TASK_TLK_STOPSHOOTING, (float)0 },// tell player to stop shooting friend + // { TASK_TLK_EYECONTACT, (float)0 },// look at the player +}; + +Schedule_t slIdleStopShooting[] = +{ + { + tlIdleStopShooting, + ARRAYSIZE ( tlIdleStopShooting ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + 0, + "Idle Stop Shooting" + }, +}; + +Task_t tlMoveAway[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_MOVE_AWAY_FAIL }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_MOVE_AWAY_PATH, (float)100 }, + { TASK_WALK_PATH_FOR_UNITS, (float)100 }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_PLAYER, (float)0.5 }, +}; + +Schedule_t slMoveAway[] = +{ + { + tlMoveAway, + ARRAYSIZE ( tlMoveAway ), + 0, + 0, + "MoveAway" + }, +}; + + +Task_t tlMoveAwayFail[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_PLAYER, (float)0.5 }, +}; + +Schedule_t slMoveAwayFail[] = +{ + { + tlMoveAwayFail, + ARRAYSIZE ( tlMoveAwayFail ), + 0, + 0, + "MoveAwayFail" + }, +}; + + + +Task_t tlMoveAwayFollow[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_TARGET_FACE }, + { TASK_STORE_LASTPOSITION, (float)0 }, + { TASK_MOVE_AWAY_PATH, (float)100 }, + { TASK_WALK_PATH_FOR_UNITS, (float)100 }, + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slMoveAwayFollow[] = +{ + { + tlMoveAwayFollow, + ARRAYSIZE ( tlMoveAwayFollow ), + 0, + 0, + "MoveAwayFollow" + }, +}; + +Task_t tlTlkIdleWatchClient[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_TLK_LOOK_AT_CLIENT, (float)6 }, +}; + +Task_t tlTlkIdleWatchClientStare[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_TLK_CLIENT_STARE, (float)6 }, + { TASK_TLK_STARE, (float)0 }, + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 }, +}; + +Schedule_t slTlkIdleWatchClient[] = +{ + { + tlTlkIdleWatchClient, + ARRAYSIZE ( tlTlkIdleWatchClient ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_CLIENT_PUSH | + bits_COND_CLIENT_UNSEEN | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "TlkIdleWatchClient" + }, + + { + tlTlkIdleWatchClientStare, + ARRAYSIZE ( tlTlkIdleWatchClientStare ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_SMELL | + bits_COND_CLIENT_PUSH | + bits_COND_CLIENT_UNSEEN | + bits_COND_PROVOKED, + + bits_SOUND_COMBAT |// sound flags - change these, and you'll break the talking code. + //bits_SOUND_PLAYER | + //bits_SOUND_WORLD | + + bits_SOUND_DANGER | + bits_SOUND_MEAT |// scents + bits_SOUND_CARCASS | + bits_SOUND_GARBAGE, + "TlkIdleWatchClientStare" + }, +}; + + +Task_t tlTlkIdleEyecontact[] = +{ + { TASK_TLK_IDEALYAW, (float)0 },// look at who I'm talking to + { TASK_FACE_IDEAL, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_SIGNAL3 }, + { TASK_TLK_EYECONTACT, (float)0 },// Wait until speaker is done +}; + +Schedule_t slTlkIdleEyecontact[] = +{ + { + tlTlkIdleEyecontact, + ARRAYSIZE ( tlTlkIdleEyecontact ), + bits_COND_NEW_ENEMY | + bits_COND_CLIENT_PUSH | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "TlkIdleEyecontact" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CTalkMonster ) +{ + slIdleResponse, + slIdleSpeak, + slIdleHello, + slIdleSpeakWait, + slIdleStopShooting, + slMoveAway, + slMoveAwayFollow, + slMoveAwayFail, + slTlkIdleWatchClient, + &slTlkIdleWatchClient[ 1 ], + slTlkIdleEyecontact, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CTalkMonster, CBaseMonster ); + + +void CTalkMonster :: SetActivity ( Activity newActivity ) +{ + if (newActivity == ACT_IDLE && IsTalking() ) + newActivity = ACT_SIGNAL3; + + if ( newActivity == ACT_SIGNAL3 && (LookupActivity ( ACT_SIGNAL3 ) == ACTIVITY_NOT_AVAILABLE)) + newActivity = ACT_IDLE; + + CBaseMonster::SetActivity( newActivity ); +} + + +void CTalkMonster :: StartTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_TLK_SPEAK: + // ask question or make statement + FIdleSpeak(); + TaskComplete(); + break; + + case TASK_TLK_RESPOND: + // respond to question + IdleRespond(); + TaskComplete(); + break; + + case TASK_TLK_HELLO: + // greet player + FIdleHello(); + TaskComplete(); + break; + + + case TASK_TLK_STARE: + // let the player know I know he's staring at me. + FIdleStare(); + TaskComplete(); + break; + + case TASK_FACE_PLAYER: + case TASK_TLK_LOOK_AT_CLIENT: + case TASK_TLK_CLIENT_STARE: + // track head to the client for a while. + m_flWaitFinished = gpGlobals->time + pTask->flData; + break; + + case TASK_TLK_EYECONTACT: + break; + + case TASK_TLK_IDEALYAW: + if (m_hTalkTarget != NULL) + { + pev->yaw_speed = 60; + float yaw = VecToYaw(m_hTalkTarget->pev->origin - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + if (yaw < 0) + { + pev->ideal_yaw = min( yaw + 45, 0 ) + pev->angles.y; + } + else + { + pev->ideal_yaw = max( yaw - 45, 0 ) + pev->angles.y; + } + } + TaskComplete(); + break; + + case TASK_TLK_HEADRESET: + // reset head position after looking at something + m_hTalkTarget = NULL; + TaskComplete(); + break; + + case TASK_TLK_STOPSHOOTING: + // tell player to stop shooting + PlaySentence( m_szGrp[TLK_NOSHOOT], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_NORM ); + TaskComplete(); + break; + + case TASK_CANT_FOLLOW: + StopFollowing( FALSE ); + PlaySentence( m_szGrp[TLK_STOP], RANDOM_FLOAT(2, 2.5), VOL_NORM, ATTN_NORM ); + TaskComplete(); + break; + + case TASK_WALK_PATH_FOR_UNITS: + m_movementActivity = ACT_WALK; + break; + + case TASK_MOVE_AWAY_PATH: + { + Vector dir = pev->angles; + dir.y = pev->ideal_yaw + 180; + Vector move; + + UTIL_MakeVectorsPrivate( dir, move, NULL, NULL ); + dir = pev->origin + move * pTask->flData; + if ( MoveToLocation( ACT_WALK, 2, dir ) ) + { + TaskComplete(); + } + else if ( FindCover( pev->origin, pev->view_ofs, 0, CoverRadius() ) ) + { + // then try for plain ole cover + m_flMoveWaitFinished = gpGlobals->time + 2; + TaskComplete(); + } + else + { + // nowhere to go? + TaskFail(); + } + } + break; + + case TASK_PLAY_SCRIPT: + m_hTalkTarget = NULL; + CBaseMonster::StartTask( pTask ); + break; + + default: + CBaseMonster::StartTask( pTask ); + } +} + + +void CTalkMonster :: RunTask( Task_t *pTask ) +{ + switch( pTask->iTask ) + { + case TASK_TLK_CLIENT_STARE: + case TASK_TLK_LOOK_AT_CLIENT: + + edict_t *pPlayer; + + // track head to the client for a while. + if ( m_MonsterState == MONSTERSTATE_IDLE && + !IsMoving() && + !IsTalking() ) + { + // Get edict for one player + pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + if ( pPlayer ) + { + IdleHeadTurn( pPlayer->v.origin ); + } + } + else + { + // started moving or talking + TaskFail(); + return; + } + + if ( pTask->iTask == TASK_TLK_CLIENT_STARE ) + { + // fail out if the player looks away or moves away. + if ( ( pPlayer->v.origin - pev->origin ).Length2D() > TLK_STARE_DIST ) + { + // player moved away. + TaskFail(); + } + + UTIL_MakeVectors( pPlayer->v.angles ); + if ( UTIL_DotPoints( pPlayer->v.origin, pev->origin, gpGlobals->v_forward ) < m_flFieldOfView ) + { + // player looked away + TaskFail(); + } + } + + if ( gpGlobals->time > m_flWaitFinished ) + { + TaskComplete(); + } + break; + + case TASK_FACE_PLAYER: + { + // Get edict for one player + edict_t *pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + if ( pPlayer ) + { + MakeIdealYaw ( pPlayer->v.origin ); + ChangeYaw ( pev->yaw_speed ); + IdleHeadTurn( pPlayer->v.origin ); + if ( gpGlobals->time > m_flWaitFinished && FlYawDiff() < 10 ) + { + TaskComplete(); + } + } + else + { + TaskFail(); + } + } + break; + + case TASK_TLK_EYECONTACT: + if (!IsMoving() && IsTalking() && m_hTalkTarget != NULL) + { + // ALERT( at_console, "waiting %f\n", m_flStopTalkTime - gpGlobals->time ); + IdleHeadTurn( m_hTalkTarget->pev->origin ); + } + else + { + TaskComplete(); + } + break; + + case TASK_WALK_PATH_FOR_UNITS: + { + float distance; + + distance = (m_vecLastPosition - pev->origin).Length2D(); + + // Walk path until far enough away + if ( distance > pTask->flData || MovementIsComplete() ) + { + TaskComplete(); + RouteClear(); // Stop moving + } + } + break; + case TASK_WAIT_FOR_MOVEMENT: + if (IsTalking() && m_hTalkTarget != NULL) + { + // ALERT(at_console, "walking, talking\n"); + IdleHeadTurn( m_hTalkTarget->pev->origin ); + } + else + { + IdleHeadTurn( pev->origin ); + // override so that during walk, a scientist may talk and greet player + FIdleHello(); + if (RANDOM_LONG(0,m_nSpeak * 20) == 0) + { + FIdleSpeak(); + } + } + + CBaseMonster::RunTask( pTask ); + if (TaskIsComplete()) + IdleHeadTurn( pev->origin ); + break; + + default: + if (IsTalking() && m_hTalkTarget != NULL) + { + IdleHeadTurn( m_hTalkTarget->pev->origin ); + } + else + { + SetBoneController( 0, 0 ); + } + CBaseMonster::RunTask( pTask ); + } +} + + +void CTalkMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + // If a client killed me (unless I was already Barnacle'd), make everyone else mad/afraid of him + if ( (pevAttacker->flags & FL_CLIENT) && m_MonsterState != MONSTERSTATE_PRONE ) + { + AlertFriends(); + LimitFollowers( CBaseEntity::Instance(pevAttacker), 0 ); + } + + m_hTargetEnt = NULL; + // Don't finish that sentence + StopTalking(); + SetUse( NULL ); + CBaseMonster::Killed( pevAttacker, iGib ); +} + + + +CBaseEntity *CTalkMonster::EnumFriends( CBaseEntity *pPrevious, int listNumber, BOOL bTrace ) +{ + CBaseEntity *pFriend = pPrevious; + char *pszFriend; + TraceResult tr; + Vector vecCheck; + + pszFriend = m_szFriends[ FriendNumber(listNumber) ]; + while (pFriend = UTIL_FindEntityByClassname( pFriend, pszFriend )) + { + if (pFriend == this || !pFriend->IsAlive()) + // don't talk to self or dead people + continue; + if ( bTrace ) + { + vecCheck = pFriend->pev->origin; + vecCheck.z = pFriend->pev->absmax.z; + + UTIL_TraceLine( pev->origin, vecCheck, ignore_monsters, ENT(pev), &tr); + } + else + tr.flFraction = 1.0; + + if (tr.flFraction == 1.0) + { + return pFriend; + } + } + + return NULL; +} + + +void CTalkMonster::AlertFriends( void ) +{ + CBaseEntity *pFriend = NULL; + int i; + + // for each friend in this bsp... + for ( i = 0; i < TLK_CFRIENDS; i++ ) + { + while (pFriend = EnumFriends( pFriend, i, TRUE )) + { + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + if ( pMonster->IsAlive() ) + { + // don't provoke a friend that's playing a death animation. They're a goner + pMonster->m_afMemory |= bits_MEMORY_PROVOKED; + } + } + } +} + + + +void CTalkMonster::ShutUpFriends( void ) +{ + CBaseEntity *pFriend = NULL; + int i; + + // for each friend in this bsp... + for ( i = 0; i < TLK_CFRIENDS; i++ ) + { + while (pFriend = EnumFriends( pFriend, i, TRUE )) + { + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + if ( pMonster ) + { + pMonster->SentenceStop(); + } + } + } +} + + +// UNDONE: Keep a follow time in each follower, make a list of followers in this function and do LRU +// UNDONE: Check this in Restore to keep restored monsters from joining a full list of followers +void CTalkMonster::LimitFollowers( CBaseEntity *pPlayer, int maxFollowers ) +{ + CBaseEntity *pFriend = NULL; + int i, count; + + count = 0; + // for each friend in this bsp... + for ( i = 0; i < TLK_CFRIENDS; i++ ) + { + while (pFriend = EnumFriends( pFriend, i, FALSE )) + { + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + if ( pMonster ) + { + if ( pMonster->m_hTargetEnt == pPlayer ) + { + count++; + if ( count > maxFollowers ) + pMonster->StopFollowing( TRUE ); + } + } + } + } +} + + +float CTalkMonster::TargetDistance( void ) +{ + // If we lose the player, or he dies, return a really large distance + if ( m_hTargetEnt == NULL || !m_hTargetEnt->IsAlive() ) + return 1e6; + + return (m_hTargetEnt->pev->origin - pev->origin).Length(); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CTalkMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SCRIPT_EVENT_SENTENCE_RND1: // Play a named sentence group 25% of the time + if (RANDOM_LONG(0,99) < 75) + break; + // fall through... + case SCRIPT_EVENT_SENTENCE: // Play a named sentence group + ShutUpFriends(); + PlaySentence( pEvent->options, RANDOM_FLOAT(2.8, 3.4), VOL_NORM, ATTN_IDLE ); + //ALERT(at_console, "script event speak\n"); + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +// monsters derived from ctalkmonster should call this in precache() + +void CTalkMonster :: TalkInit( void ) +{ + // every new talking monster must reset this global, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + + CTalkMonster::g_talkWaitTime = 0; + + m_voicePitch = 100; +} +//========================================================= +// FindNearestFriend +// Scan for nearest, visible friend. If fPlayer is true, look for +// nearest player +//========================================================= +CBaseEntity *CTalkMonster :: FindNearestFriend(BOOL fPlayer) +{ + CBaseEntity *pFriend = NULL; + CBaseEntity *pNearest = NULL; + float range = 10000000.0; + TraceResult tr; + Vector vecStart = pev->origin; + Vector vecCheck; + int i; + char *pszFriend; + int cfriends; + + vecStart.z = pev->absmax.z; + + if (fPlayer) + cfriends = 1; + else + cfriends = TLK_CFRIENDS; + + // for each type of friend... + + for (i = cfriends-1; i > -1; i--) + { + if (fPlayer) + pszFriend = "player"; + else + pszFriend = m_szFriends[FriendNumber(i)]; + + if (!pszFriend) + continue; + + // for each friend in this bsp... + while (pFriend = UTIL_FindEntityByClassname( pFriend, pszFriend )) + { + if (pFriend == this || !pFriend->IsAlive()) + // don't talk to self or dead people + continue; + + CBaseMonster *pMonster = pFriend->MyMonsterPointer(); + + // If not a monster for some reason, or in a script, or prone + if ( !pMonster || pMonster->m_MonsterState == MONSTERSTATE_SCRIPT || pMonster->m_MonsterState == MONSTERSTATE_PRONE ) + continue; + + vecCheck = pFriend->pev->origin; + vecCheck.z = pFriend->pev->absmax.z; + + // if closer than previous friend, and in range, see if he's visible + + if (range > (vecStart - vecCheck).Length()) + { + UTIL_TraceLine(vecStart, vecCheck, ignore_monsters, ENT(pev), &tr); + + if (tr.flFraction == 1.0) + { + // visible and in range, this is the new nearest scientist + if ((vecStart - vecCheck).Length() < TALKRANGE_MIN) + { + pNearest = pFriend; + range = (vecStart - vecCheck).Length(); + } + } + } + } + } + return pNearest; +} + +int CTalkMonster :: GetVoicePitch( void ) +{ + return m_voicePitch + RANDOM_LONG(0,3); +} + + +void CTalkMonster :: Touch( CBaseEntity *pOther ) +{ + // Did the player touch me? + if ( pOther->IsPlayer() ) + { + // Ignore if pissed at player + if ( m_afMemory & bits_MEMORY_PROVOKED ) + return; + + // Stay put during speech + if ( IsTalking() ) + return; + + // Heuristic for determining if the player is pushing me away + float speed = fabs(pOther->pev->velocity.x) + fabs(pOther->pev->velocity.y); + if ( speed > 50 ) + { + SetConditions( bits_COND_CLIENT_PUSH ); + MakeIdealYaw( pOther->pev->origin ); + } + } +} + + + +//========================================================= +// IdleRespond +// Respond to a previous question +//========================================================= +void CTalkMonster :: IdleRespond( void ) +{ + int pitch = GetVoicePitch(); + + // play response + PlaySentence( m_szGrp[TLK_ANSWER], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); +} + +int CTalkMonster :: FOkToSpeak( void ) +{ + // if in the grip of a barnacle, don't speak + if ( m_MonsterState == MONSTERSTATE_PRONE || m_IdealMonsterState == MONSTERSTATE_PRONE ) + { + return FALSE; + } + + // if not alive, certainly don't speak + if ( pev->deadflag != DEAD_NO ) + { + return FALSE; + } + + // if someone else is talking, don't speak + if (gpGlobals->time <= CTalkMonster::g_talkWaitTime) + return FALSE; + + if ( pev->spawnflags & SF_MONSTER_GAG ) + return FALSE; + + if ( m_MonsterState == MONSTERSTATE_PRONE ) + return FALSE; + + // if player is not in pvs, don't speak + if (!IsAlive() || FNullEnt(FIND_CLIENT_IN_PVS(edict()))) + return FALSE; + + // don't talk if you're in combat + if (m_hEnemy != NULL && FVisible( m_hEnemy )) + return FALSE; + + return TRUE; +} + + +int CTalkMonster::CanPlaySentence( BOOL fDisregardState ) +{ + if ( fDisregardState ) + return CBaseMonster::CanPlaySentence( fDisregardState ); + return FOkToSpeak(); +} + +//========================================================= +// FIdleStare +//========================================================= +int CTalkMonster :: FIdleStare( void ) +{ + if (!FOkToSpeak()) + return FALSE; + + PlaySentence( m_szGrp[TLK_STARE], RANDOM_FLOAT(5, 7.5), VOL_NORM, ATTN_IDLE ); + + m_hTalkTarget = FindNearestFriend( TRUE ); + return TRUE; +} + +//========================================================= +// IdleHello +// Try to greet player first time he's seen +//========================================================= +int CTalkMonster :: FIdleHello( void ) +{ + if (!FOkToSpeak()) + return FALSE; + + // if this is first time scientist has seen player, greet him + if (!FBitSet(m_bitsSaid, bit_saidHelloPlayer)) + { + // get a player + CBaseEntity *pPlayer = FindNearestFriend(TRUE); + + if (pPlayer) + { + if (FInViewCone(pPlayer) && FVisible(pPlayer)) + { + m_hTalkTarget = pPlayer; + + if (FBitSet(pev->spawnflags, SF_MONSTER_PREDISASTER)) + PlaySentence( m_szGrp[TLK_PHELLO], RANDOM_FLOAT(3, 3.5), VOL_NORM, ATTN_IDLE ); + else + PlaySentence( m_szGrp[TLK_HELLO], RANDOM_FLOAT(3, 3.5), VOL_NORM, ATTN_IDLE ); + + SetBits(m_bitsSaid, bit_saidHelloPlayer); + + return TRUE; + } + } + } + return FALSE; +} + + +// turn head towards supplied origin +void CTalkMonster :: IdleHeadTurn( Vector &vecFriend ) +{ + // turn head in desired direction only if ent has a turnable head + if (m_afCapability & bits_CAP_TURN_HEAD) + { + float yaw = VecToYaw(vecFriend - pev->origin) - pev->angles.y; + + if (yaw > 180) yaw -= 360; + if (yaw < -180) yaw += 360; + + // turn towards vector + SetBoneController( 0, yaw ); + } +} + +//========================================================= +// FIdleSpeak +// ask question of nearby friend, or make statement +//========================================================= +int CTalkMonster :: FIdleSpeak ( void ) +{ + // try to start a conversation, or make statement + int pitch; + const char *szIdleGroup; + const char *szQuestionGroup; + float duration; + + if (!FOkToSpeak()) + return FALSE; + + // set idle groups based on pre/post disaster + if (FBitSet(pev->spawnflags, SF_MONSTER_PREDISASTER)) + { + szIdleGroup = m_szGrp[TLK_PIDLE]; + szQuestionGroup = m_szGrp[TLK_PQUESTION]; + // set global min delay for next conversation + duration = RANDOM_FLOAT(4.8, 5.2); + } + else + { + szIdleGroup = m_szGrp[TLK_IDLE]; + szQuestionGroup = m_szGrp[TLK_QUESTION]; + // set global min delay for next conversation + duration = RANDOM_FLOAT(2.8, 3.2); + + } + + pitch = GetVoicePitch(); + + // player using this entity is alive and wounded? + CBaseEntity *pTarget = m_hTargetEnt; + + if ( pTarget != NULL ) + { + if ( pTarget->IsPlayer() ) + { + if ( pTarget->IsAlive() ) + { + m_hTalkTarget = m_hTargetEnt; + if (!FBitSet(m_bitsSaid, bit_saidDamageHeavy) && + (m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 8)) + { + //EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, m_szGrp[TLK_PLHURT3], 1.0, ATTN_IDLE, 0, pitch); + PlaySentence( m_szGrp[TLK_PLHURT3], duration, VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidDamageHeavy); + return TRUE; + } + else if (!FBitSet(m_bitsSaid, bit_saidDamageMedium) && + (m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 4)) + { + //EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, m_szGrp[TLK_PLHURT2], 1.0, ATTN_IDLE, 0, pitch); + PlaySentence( m_szGrp[TLK_PLHURT2], duration, VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidDamageMedium); + return TRUE; + } + else if (!FBitSet(m_bitsSaid, bit_saidDamageLight) && + (m_hTargetEnt->pev->health <= m_hTargetEnt->pev->max_health / 2)) + { + //EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, m_szGrp[TLK_PLHURT1], 1.0, ATTN_IDLE, 0, pitch); + PlaySentence( m_szGrp[TLK_PLHURT1], duration, VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidDamageLight); + return TRUE; + } + } + else + { + //!!!KELLY - here's a cool spot to have the talkmonster talk about the dead player if we want. + // "Oh dear, Gordon Freeman is dead!" -Scientist + // "Damn, I can't do this without you." -Barney + } + } + } + + // if there is a friend nearby to speak to, play sentence, set friend's response time, return + CBaseEntity *pFriend = FindNearestFriend(FALSE); + + if (pFriend && !(pFriend->IsMoving()) && (RANDOM_LONG(0,99) < 75)) + { + PlaySentence( szQuestionGroup, duration, VOL_NORM, ATTN_IDLE ); + //SENTENCEG_PlayRndSz( ENT(pev), szQuestionGroup, 1.0, ATTN_IDLE, 0, pitch ); + + // force friend to answer + CTalkMonster *pTalkMonster = (CTalkMonster *)pFriend; + m_hTalkTarget = pFriend; + pTalkMonster->SetAnswerQuestion( this ); // UNDONE: This is EVIL!!! + pTalkMonster->m_flStopTalkTime = m_flStopTalkTime; + + m_nSpeak++; + return TRUE; + } + + // otherwise, play an idle statement, try to face client when making a statement. + if ( RANDOM_LONG(0,1) ) + { + //SENTENCEG_PlayRndSz( ENT(pev), szIdleGroup, 1.0, ATTN_IDLE, 0, pitch ); + CBaseEntity *pFriend = FindNearestFriend(TRUE); + + if ( pFriend ) + { + m_hTalkTarget = pFriend; + PlaySentence( szIdleGroup, duration, VOL_NORM, ATTN_IDLE ); + m_nSpeak++; + return TRUE; + } + } + + // didn't speak + Talk( 0 ); + CTalkMonster::g_talkWaitTime = 0; + return FALSE; +} + +void CTalkMonster::PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ) +{ + if ( !bConcurrent ) + ShutUpFriends(); + + ClearConditions( bits_COND_CLIENT_PUSH ); // Forget about moving! I've got something to say! + m_useTime = gpGlobals->time + duration; + PlaySentence( pszSentence, duration, volume, attenuation ); + + m_hTalkTarget = pListener; +} + +void CTalkMonster::PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ) +{ + if ( !pszSentence ) + return; + + Talk ( duration ); + + CTalkMonster::g_talkWaitTime = gpGlobals->time + duration + 2.0; + if ( pszSentence[0] == '!' ) + EMIT_SOUND_DYN( edict(), CHAN_VOICE, pszSentence, volume, attenuation, 0, GetVoicePitch()); + else + SENTENCEG_PlayRndSz( edict(), pszSentence, volume, attenuation, 0, GetVoicePitch() ); + + // If you say anything, don't greet the player - you may have already spoken to them + SetBits(m_bitsSaid, bit_saidHelloPlayer); +} + +//========================================================= +// Talk - set a timer that tells us when the monster is done +// talking. +//========================================================= +void CTalkMonster :: Talk( float flDuration ) +{ + if ( flDuration <= 0 ) + { + // no duration :( + m_flStopTalkTime = gpGlobals->time + 3; + } + else + { + m_flStopTalkTime = gpGlobals->time + flDuration; + } +} + +// Prepare this talking monster to answer question +void CTalkMonster :: SetAnswerQuestion( CTalkMonster *pSpeaker ) +{ + if ( !m_pCine ) + ChangeSchedule( slIdleResponse ); + m_hTalkTarget = (CBaseMonster *)pSpeaker; +} + +int CTalkMonster :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + if ( IsAlive() ) + { + // if player damaged this entity, have other friends talk about it + if (pevAttacker && m_MonsterState != MONSTERSTATE_PRONE && FBitSet(pevAttacker->flags, FL_CLIENT)) + { + CBaseEntity *pFriend = FindNearestFriend(FALSE); + + if (pFriend && pFriend->IsAlive()) + { + // only if not dead or dying! + CTalkMonster *pTalkMonster = (CTalkMonster *)pFriend; + pTalkMonster->ChangeSchedule( slIdleStopShooting ); + } + } + } + return CBaseMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + + +Schedule_t* CTalkMonster :: GetScheduleOfType ( int Type ) +{ + switch( Type ) + { + case SCHED_MOVE_AWAY: + return slMoveAway; + + case SCHED_MOVE_AWAY_FOLLOW: + return slMoveAwayFollow; + + case SCHED_MOVE_AWAY_FAIL: + return slMoveAwayFail; + + case SCHED_TARGET_FACE: + // speak during 'use' + if (RANDOM_LONG(0,99) < 2) + //ALERT ( at_console, "target chase speak\n" ); + return slIdleSpeakWait; + else + return slIdleStand; + + case SCHED_IDLE_STAND: + { + // if never seen player, try to greet him + if (!FBitSet(m_bitsSaid, bit_saidHelloPlayer)) + { + return slIdleHello; + } + + // sustained light wounds? + if (!FBitSet(m_bitsSaid, bit_saidWoundLight) && (pev->health <= (pev->max_health * 0.75))) + { + //SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_WOUND], 1.0, ATTN_IDLE, 0, GetVoicePitch() ); + //CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(2.8, 3.2); + PlaySentence( m_szGrp[TLK_WOUND], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidWoundLight); + return slIdleStand; + } + // sustained heavy wounds? + else if (!FBitSet(m_bitsSaid, bit_saidWoundHeavy) && (pev->health <= (pev->max_health * 0.5))) + { + //SENTENCEG_PlayRndSz( ENT(pev), m_szGrp[TLK_MORTAL], 1.0, ATTN_IDLE, 0, GetVoicePitch() ); + //CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(2.8, 3.2); + PlaySentence( m_szGrp[TLK_MORTAL], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + SetBits(m_bitsSaid, bit_saidWoundHeavy); + return slIdleStand; + } + + // talk about world + if (FOkToSpeak() && RANDOM_LONG(0,m_nSpeak * 2) == 0) + { + //ALERT ( at_console, "standing idle speak\n" ); + return slIdleSpeak; + } + + if ( !IsTalking() && HasConditions ( bits_COND_SEE_CLIENT ) && RANDOM_LONG( 0, 6 ) == 0 ) + { + edict_t *pPlayer = g_engfuncs.pfnPEntityOfEntIndex( 1 ); + + if ( pPlayer ) + { + // watch the client. + UTIL_MakeVectors ( pPlayer->v.angles ); + if ( ( pPlayer->v.origin - pev->origin ).Length2D() < TLK_STARE_DIST && + UTIL_DotPoints( pPlayer->v.origin, pev->origin, gpGlobals->v_forward ) >= m_flFieldOfView ) + { + // go into the special STARE schedule if the player is close, and looking at me too. + return &slTlkIdleWatchClient[ 1 ]; + } + + return slTlkIdleWatchClient; + } + } + else + { + if (IsTalking()) + // look at who we're talking to + return slTlkIdleEyecontact; + else + // regular standing idle + return slIdleStand; + } + + + // NOTE - caller must first CTalkMonster::GetScheduleOfType, + // then check result and decide what to return ie: if sci gets back + // slIdleStand, return slIdleSciStand + } + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + +//========================================================= +// IsTalking - am I saying a sentence right now? +//========================================================= +BOOL CTalkMonster :: IsTalking( void ) +{ + if ( m_flStopTalkTime > gpGlobals->time ) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// If there's a player around, watch him. +//========================================================= +void CTalkMonster :: PrescheduleThink ( void ) +{ + if ( !HasConditions ( bits_COND_SEE_CLIENT ) ) + { + SetConditions ( bits_COND_CLIENT_UNSEEN ); + } +} + +// try to smell something +void CTalkMonster :: TrySmellTalk( void ) +{ + if ( !FOkToSpeak() ) + return; + + // clear smell bits periodically + if ( gpGlobals->time > m_flLastSaidSmelled ) + { +// ALERT ( at_aiconsole, "Clear smell bits\n" ); + ClearBits(m_bitsSaid, bit_saidSmelled); + } + // smelled something? + if (!FBitSet(m_bitsSaid, bit_saidSmelled) && HasConditions ( bits_COND_SMELL )) + { + PlaySentence( m_szGrp[TLK_SMELL], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + m_flLastSaidSmelled = gpGlobals->time + 60;// don't talk about the stinky for a while. + SetBits(m_bitsSaid, bit_saidSmelled); + } +} + + + +int CTalkMonster::IRelationship( CBaseEntity *pTarget ) +{ + if ( pTarget->IsPlayer() ) + if ( m_afMemory & bits_MEMORY_PROVOKED ) + return R_HT; + return CBaseMonster::IRelationship( pTarget ); +} + + +void CTalkMonster::StopFollowing( BOOL clearSchedule ) +{ + if ( IsFollowing() ) + { + if ( !(m_afMemory & bits_MEMORY_PROVOKED) ) + { + PlaySentence( m_szGrp[TLK_UNUSE], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + m_hTalkTarget = m_hTargetEnt; + } + + if ( m_movementGoal == MOVEGOAL_TARGETENT ) + RouteClear(); // Stop him from walking toward the player + m_hTargetEnt = NULL; + if ( clearSchedule ) + ClearSchedule(); + if ( m_hEnemy != NULL ) + m_IdealMonsterState = MONSTERSTATE_COMBAT; + } +} + + +void CTalkMonster::StartFollowing( CBaseEntity *pLeader ) +{ + if ( m_pCine ) + m_pCine->CancelScript(); + + if ( m_hEnemy != NULL ) + m_IdealMonsterState = MONSTERSTATE_ALERT; + + m_hTargetEnt = pLeader; + PlaySentence( m_szGrp[TLK_USE], RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + m_hTalkTarget = m_hTargetEnt; + ClearConditions( bits_COND_CLIENT_PUSH ); + ClearSchedule(); +} + + +BOOL CTalkMonster::CanFollow( void ) +{ + if ( m_MonsterState == MONSTERSTATE_SCRIPT ) + { + if ( !m_pCine->CanInterrupt() ) + return FALSE; + } + + if ( !IsAlive() ) + return FALSE; + + return !IsFollowing(); +} + + +void CTalkMonster :: FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Don't allow use during a scripted_sentence + if ( m_useTime > gpGlobals->time ) + return; + + if ( pCaller != NULL && pCaller->IsPlayer() ) + { + // Pre-disaster followers can't be used + if ( pev->spawnflags & SF_MONSTER_PREDISASTER ) + { + DeclineFollowing(); + } + else if ( CanFollow() ) + { + LimitFollowers( pCaller , 1 ); + + if ( m_afMemory & bits_MEMORY_PROVOKED ) + ALERT( at_console, "I'm not following you, you evil person!\n" ); + else + { + StartFollowing( pCaller ); + SetBits(m_bitsSaid, bit_saidHelloPlayer); // Don't say hi after you've started following + } + } + else + { + StopFollowing( TRUE ); + } + } +} + +void CTalkMonster::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "UseSentence")) + { + m_iszUse = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "UnUseSentence")) + { + m_iszUnUse = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + + +void CTalkMonster::Precache( void ) +{ + if ( m_iszUse ) + m_szGrp[TLK_USE] = STRING( m_iszUse ); + if ( m_iszUnUse ) + m_szGrp[TLK_UNUSE] = STRING( m_iszUnUse ); +} + diff --git a/dlls/talkmonster.h b/dlls/talkmonster.h index e97ba4b..d59c55f 100644 --- a/dlls/talkmonster.h +++ b/dlls/talkmonster.h @@ -1,26 +1,183 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. * All Rights Reserved. * -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. * ****/ - #ifndef TALKMONSTER_H #define TALKMONSTER_H +#ifndef MONSTERS_H +#include "monsters.h" +#endif + +//========================================================= +// Talking monster base class +// Used for scientists and barneys +//========================================================= + +#define TALKRANGE_MIN 500.0 // don't talk to anyone farther away than this + +#define TLK_STARE_DIST 128 // anyone closer than this and looking at me is probably staring at me. + +#define bit_saidDamageLight (1<<0) // bits so we don't repeat key sentences +#define bit_saidDamageMedium (1<<1) +#define bit_saidDamageHeavy (1<<2) +#define bit_saidHelloPlayer (1<<3) +#define bit_saidWoundLight (1<<4) +#define bit_saidWoundHeavy (1<<5) +#define bit_saidHeard (1<<6) +#define bit_saidSmelled (1<<7) + +#define TLK_CFRIENDS 3 + +typedef enum +{ + TLK_ANSWER = 0, + TLK_QUESTION, + TLK_IDLE, + TLK_STARE, + TLK_USE, + TLK_UNUSE, + TLK_STOP, + TLK_NOSHOOT, + TLK_HELLO, + TLK_PHELLO, + TLK_PIDLE, + TLK_PQUESTION, + TLK_PLHURT1, + TLK_PLHURT2, + TLK_PLHURT3, + TLK_SMELL, + TLK_WOUND, + TLK_MORTAL, + + TLK_CGROUPS, // MUST be last entry +} TALKGROUPNAMES; + + +enum +{ + SCHED_CANT_FOLLOW = LAST_COMMON_SCHEDULE + 1, + SCHED_MOVE_AWAY, // Try to get out of the player's way + SCHED_MOVE_AWAY_FOLLOW, // same, but follow afterward + SCHED_MOVE_AWAY_FAIL, // Turn back toward player + + LAST_TALKMONSTER_SCHEDULE, // MUST be last +}; + +enum +{ + TASK_CANT_FOLLOW = LAST_COMMON_TASK + 1, + TASK_MOVE_AWAY_PATH, + TASK_WALK_PATH_FOR_UNITS, + + TASK_TLK_RESPOND, // say my response + TASK_TLK_SPEAK, // question or remark + TASK_TLK_HELLO, // Try to say hello to player + TASK_TLK_HEADRESET, // reset head position + TASK_TLK_STOPSHOOTING, // tell player to stop shooting friend + TASK_TLK_STARE, // let the player know I know he's staring at me. + TASK_TLK_LOOK_AT_CLIENT,// faces player if not moving and not talking and in idle. + TASK_TLK_CLIENT_STARE, // same as look at client, but says something if the player stares. + TASK_TLK_EYECONTACT, // maintain eyecontact with person who I'm talking to + TASK_TLK_IDEALYAW, // set ideal yaw to face who I'm talking to + TASK_FACE_PLAYER, // Face the player + + LAST_TALKMONSTER_TASK, // MUST be last +}; + class CTalkMonster : public CBaseMonster { public: - static float g_talkWaitTime; + void TalkInit( void ); + CBaseEntity *FindNearestFriend(BOOL fPlayer); + float TargetDistance( void ); + void StopTalking( void ) { SentenceStop(); } + + // Base Monster functions + void Precache( void ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType); + void Touch( CBaseEntity *pOther ); + void Killed( entvars_t *pevAttacker, int iGib ); + int IRelationship ( CBaseEntity *pTarget ); + virtual int CanPlaySentence( BOOL fDisregardState ); + virtual void PlaySentence( const char *pszSentence, float duration, float volume, float attenuation ); + void PlayScriptedSentence( const char *pszSentence, float duration, float volume, float attenuation, BOOL bConcurrent, CBaseEntity *pListener ); + void KeyValue( KeyValueData *pkvd ); + // AI functions + void SetActivity ( Activity newActivity ); + Schedule_t *GetScheduleOfType ( int Type ); + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void PrescheduleThink( void ); + + + // Conversations / communication + int GetVoicePitch( void ); + void IdleRespond( void ); + int FIdleSpeak( void ); + int FIdleStare( void ); + int FIdleHello( void ); + void IdleHeadTurn( Vector &vecFriend ); + int FOkToSpeak( void ); + void TrySmellTalk( void ); + CBaseEntity *EnumFriends( CBaseEntity *pentPrevious, int listNumber, BOOL bTrace ); + void AlertFriends( void ); + void ShutUpFriends( void ); + BOOL IsTalking( void ); + void Talk( float flDuration ); + // For following + BOOL CanFollow( void ); + BOOL IsFollowing( void ) { return m_hTargetEnt != NULL && m_hTargetEnt->IsPlayer(); } + void StopFollowing( BOOL clearSchedule ); + void StartFollowing( CBaseEntity *pLeader ); + virtual void DeclineFollowing( void ) {} + void LimitFollowers( CBaseEntity *pPlayer, int maxFollowers ); + + void EXPORT FollowerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual void SetAnswerQuestion( CTalkMonster *pSpeaker ); + virtual int FriendNumber( int arrayNumber ) { return arrayNumber; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + + static char *m_szFriends[TLK_CFRIENDS]; // array of friend names + static float g_talkWaitTime; + + int m_bitsSaid; // set bits for sentences we don't want repeated + int m_nSpeak; // number of times initiated talking + int m_voicePitch; // pitch of voice for this head + const char *m_szGrp[TLK_CGROUPS]; // sentence group names + float m_useTime; // Don't allow +USE until this time + int m_iszUse; // Custom +USE sentence group (follow) + int m_iszUnUse; // Custom +USE sentence group (stop following) + + float m_flLastSaidSmelled;// last time we talked about something that stinks + float m_flStopTalkTime;// when in the future that I'll be done saying this sentence. + + EHANDLE m_hTalkTarget; // who to look at while talking + CUSTOM_SCHEDULES; }; + +// Clients can push talkmonsters out of their way +#define bits_COND_CLIENT_PUSH ( bits_COND_SPECIAL1 ) +// Don't see a client right now. +#define bits_COND_CLIENT_UNSEEN ( bits_COND_SPECIAL2 ) + + #endif //TALKMONSTER_H diff --git a/dlls/teamplay_gamerules.cpp b/dlls/teamplay_gamerules.cpp index d77aca1..db99778 100644 --- a/dlls/teamplay_gamerules.cpp +++ b/dlls/teamplay_gamerules.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -346,10 +346,10 @@ void CHalfLifeTeamplay::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infob sprintf( text, "* %s has changed to team \'%s\'\n", STRING(pPlayer->pev->netname), mdls ); UTIL_SayTextAll( text, pPlayer ); - UTIL_LogPrintf( "\"%s<%i><%u><%s>\" joined team \"%s\"\n", + UTIL_LogPrintf( "\"%s<%i><%s><%s>\" joined team \"%s\"\n", STRING(pPlayer->pev->netname), GETPLAYERUSERID( pPlayer->edict() ), - GETPLAYERWONID( pPlayer->edict() ), + GETPLAYERAUTHID( pPlayer->edict() ), pPlayer->m_szTeamName, mdls ); diff --git a/dlls/teamplay_gamerules.h b/dlls/teamplay_gamerules.h index ac11dc2..c7c351e 100644 --- a/dlls/teamplay_gamerules.h +++ b/dlls/teamplay_gamerules.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/tempmonster.cpp b/dlls/tempmonster.cpp new file mode 100644 index 0000000..2ad893f --- /dev/null +++ b/dlls/tempmonster.cpp @@ -0,0 +1,117 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// monster template +//========================================================= +#if 0 + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CMyMonster : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); +}; +LINK_ENTITY_TO_CLASS( my_monster, CMyMonster ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CMyMonster :: Classify ( void ) +{ + return CLASS_MY_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CMyMonster :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + default: + ys = 90; + } + + pev->yaw_speed = ys; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CMyMonster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case 0: + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CMyMonster :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/mymodel.mdl"); + UTIL_SetSize( pev, Vector( -12, -12, 0 ), Vector( 12, 12, 24 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = 8; + pev->view_ofs = Vector ( 0, 0, 0 );// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CMyMonster :: Precache() +{ + PRECACHE_SOUND("mysound.wav"); + + PRECACHE_MODEL("models/mymodel.mdl"); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +#endif 0 diff --git a/dlls/tentacle.cpp b/dlls/tentacle.cpp new file mode 100644 index 0000000..ef3ab57 --- /dev/null +++ b/dlls/tentacle.cpp @@ -0,0 +1,1044 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +/* + + h_tentacle.cpp - silo of death tentacle monster (half life) + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "soundent.h" + + +#define ACT_T_IDLE 1010 +#define ACT_T_TAP 1020 +#define ACT_T_STRIKE 1030 +#define ACT_T_REARIDLE 1040 + +class CTentacle : public CBaseMonster +{ +public: + CTentacle( void ); + + void Spawn( ); + void Precache( ); + void KeyValue( KeyValueData *pkvd ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + // Don't allow the tentacle to go across transitions!!! + virtual int ObjectCaps( void ) { return CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector(-400, -400, 0); + pev->absmax = pev->origin + Vector(400, 400, 850); + } + + void EXPORT Cycle( void ); + void EXPORT CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Start( void ); + void EXPORT DieThink( void ); + + void EXPORT Test( void ); + + void EXPORT HitTouch( CBaseEntity *pOther ); + + float HearingSensitivity( void ) { return 2.0; }; + + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + void Killed( entvars_t *pevAttacker, int iGib ); + + MONSTERSTATE GetIdealState ( void ) { return MONSTERSTATE_IDLE; }; + int CanPlaySequence( BOOL fDisregardState ) { return TRUE; }; + + int Classify( void ); + + int Level( float dz ); + int MyLevel( void ); + float MyHeight( void ); + + float m_flInitialYaw; + int m_iGoalAnim; + int m_iLevel; + int m_iDir; + float m_flFramerateAdj; + float m_flSoundYaw; + int m_iSoundLevel; + float m_flSoundTime; + float m_flSoundRadius; + int m_iHitDmg; + float m_flHitTime; + + float m_flTapRadius; + + float m_flNextSong; + static int g_fFlySound; + static int g_fSquirmSound; + + float m_flMaxYaw; + int m_iTapSound; + + Vector m_vecPrevSound; + float m_flPrevSoundTime; + + static const char *pHitSilo[]; + static const char *pHitDirt[]; + static const char *pHitWater[]; +}; + + + +int CTentacle :: g_fFlySound; +int CTentacle :: g_fSquirmSound; + +LINK_ENTITY_TO_CLASS( monster_tentacle, CTentacle ); + +// stike sounds +#define TE_NONE -1 +#define TE_SILO 0 +#define TE_DIRT 1 +#define TE_WATER 2 + +const char *CTentacle::pHitSilo[] = +{ + "tentacle/te_strike1.wav", + "tentacle/te_strike2.wav", +}; + +const char *CTentacle::pHitDirt[] = +{ + "player/pl_dirt1.wav", + "player/pl_dirt2.wav", + "player/pl_dirt3.wav", + "player/pl_dirt4.wav", +}; + +const char *CTentacle::pHitWater[] = +{ + "player/pl_slosh1.wav", + "player/pl_slosh2.wav", + "player/pl_slosh3.wav", + "player/pl_slosh4.wav", +}; + + +TYPEDESCRIPTION CTentacle::m_SaveData[] = +{ + DEFINE_FIELD( CTentacle, m_flInitialYaw, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_iGoalAnim, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_iLevel, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_iDir, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_flFramerateAdj, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_flSoundYaw, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_iSoundLevel, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_flSoundTime, FIELD_TIME ), + DEFINE_FIELD( CTentacle, m_flSoundRadius, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_iHitDmg, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_flHitTime, FIELD_TIME ), + DEFINE_FIELD( CTentacle, m_flTapRadius, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_flNextSong, FIELD_TIME ), + DEFINE_FIELD( CTentacle, m_iTapSound, FIELD_INTEGER ), + DEFINE_FIELD( CTentacle, m_flMaxYaw, FIELD_FLOAT ), + DEFINE_FIELD( CTentacle, m_vecPrevSound, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CTentacle, m_flPrevSoundTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CTentacle, CBaseMonster ); + + +// animation sequence aliases +typedef enum +{ + TENTACLE_ANIM_Pit_Idle, + + TENTACLE_ANIM_rise_to_Temp1, + TENTACLE_ANIM_Temp1_to_Floor, + TENTACLE_ANIM_Floor_Idle, + TENTACLE_ANIM_Floor_Fidget_Pissed, + TENTACLE_ANIM_Floor_Fidget_SmallRise, + TENTACLE_ANIM_Floor_Fidget_Wave, + TENTACLE_ANIM_Floor_Strike, + TENTACLE_ANIM_Floor_Tap, + TENTACLE_ANIM_Floor_Rotate, + TENTACLE_ANIM_Floor_Rear, + TENTACLE_ANIM_Floor_Rear_Idle, + TENTACLE_ANIM_Floor_to_Lev1, + + TENTACLE_ANIM_Lev1_Idle, + TENTACLE_ANIM_Lev1_Fidget_Claw, + TENTACLE_ANIM_Lev1_Fidget_Shake, + TENTACLE_ANIM_Lev1_Fidget_Snap, + TENTACLE_ANIM_Lev1_Strike, + TENTACLE_ANIM_Lev1_Tap, + TENTACLE_ANIM_Lev1_Rotate, + TENTACLE_ANIM_Lev1_Rear, + TENTACLE_ANIM_Lev1_Rear_Idle, + TENTACLE_ANIM_Lev1_to_Lev2, + + TENTACLE_ANIM_Lev2_Idle, + TENTACLE_ANIM_Lev2_Fidget_Shake, + TENTACLE_ANIM_Lev2_Fidget_Swing, + TENTACLE_ANIM_Lev2_Fidget_Tut, + TENTACLE_ANIM_Lev2_Strike, + TENTACLE_ANIM_Lev2_Tap, + TENTACLE_ANIM_Lev2_Rotate, + TENTACLE_ANIM_Lev2_Rear, + TENTACLE_ANIM_Lev2_Rear_Idle, + TENTACLE_ANIM_Lev2_to_Lev3, + + TENTACLE_ANIM_Lev3_Idle, + TENTACLE_ANIM_Lev3_Fidget_Shake, + TENTACLE_ANIM_Lev3_Fidget_Side, + TENTACLE_ANIM_Lev3_Fidget_Swipe, + TENTACLE_ANIM_Lev3_Strike, + TENTACLE_ANIM_Lev3_Tap, + TENTACLE_ANIM_Lev3_Rotate, + TENTACLE_ANIM_Lev3_Rear, + TENTACLE_ANIM_Lev3_Rear_Idle, + + TENTACLE_ANIM_Lev1_Door_reach, + + TENTACLE_ANIM_Lev3_to_Engine, + TENTACLE_ANIM_Engine_Idle, + TENTACLE_ANIM_Engine_Sway, + TENTACLE_ANIM_Engine_Swat, + TENTACLE_ANIM_Engine_Bob, + TENTACLE_ANIM_Engine_Death1, + TENTACLE_ANIM_Engine_Death2, + TENTACLE_ANIM_Engine_Death3, + + TENTACLE_ANIM_none +} TENTACLE_ANIM; + + + + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CTentacle :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +// +// Tentacle Spawn +// +void CTentacle :: Spawn( ) +{ + Precache( ); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_FLY; + pev->effects = 0; + pev->health = 75; + pev->sequence = 0; + + SET_MODEL(ENT(pev), "models/tentacle2.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 ) ); + + pev->takedamage = DAMAGE_AIM; + pev->flags |= FL_MONSTER; + + m_bloodColor = BLOOD_COLOR_GREEN; + + SetThink( Start ); + SetTouch( HitTouch ); + SetUse( CommandUse ); + + pev->nextthink = gpGlobals->time + 0.2; + + ResetSequenceInfo( ); + m_iDir = 1; + + pev->yaw_speed = 18; + m_flInitialYaw = pev->angles.y; + pev->ideal_yaw = m_flInitialYaw; + + g_fFlySound = FALSE; + g_fSquirmSound = FALSE; + + m_iHitDmg = 20; + + if (m_flMaxYaw <= 0) + m_flMaxYaw = 65; + + m_MonsterState = MONSTERSTATE_IDLE; + + // SetThink( Test ); + UTIL_SetOrigin( pev, pev->origin ); +} + +void CTentacle :: Precache( ) +{ + PRECACHE_MODEL("models/tentacle2.mdl"); + + PRECACHE_SOUND("ambience/flies.wav"); + PRECACHE_SOUND("ambience/squirm2.wav"); + + PRECACHE_SOUND("tentacle/te_alert1.wav"); + PRECACHE_SOUND("tentacle/te_alert2.wav"); + PRECACHE_SOUND("tentacle/te_flies1.wav"); + PRECACHE_SOUND("tentacle/te_move1.wav"); + PRECACHE_SOUND("tentacle/te_move2.wav"); + PRECACHE_SOUND("tentacle/te_roar1.wav"); + PRECACHE_SOUND("tentacle/te_roar2.wav"); + PRECACHE_SOUND("tentacle/te_search1.wav"); + PRECACHE_SOUND("tentacle/te_search2.wav"); + PRECACHE_SOUND("tentacle/te_sing1.wav"); + PRECACHE_SOUND("tentacle/te_sing2.wav"); + PRECACHE_SOUND("tentacle/te_squirm2.wav"); + PRECACHE_SOUND("tentacle/te_strike1.wav"); + PRECACHE_SOUND("tentacle/te_strike2.wav"); + PRECACHE_SOUND("tentacle/te_swing1.wav"); + PRECACHE_SOUND("tentacle/te_swing2.wav"); + + PRECACHE_SOUND_ARRAY( pHitSilo ); + PRECACHE_SOUND_ARRAY( pHitDirt ); + PRECACHE_SOUND_ARRAY( pHitWater ); +} + + +CTentacle::CTentacle( ) +{ + m_flMaxYaw = 65; + m_iTapSound = 0; +} + +void CTentacle::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "sweeparc")) + { + m_flMaxYaw = atof(pkvd->szValue) / 2.0; + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "sound")) + { + m_iTapSound = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else + CBaseMonster::KeyValue( pkvd ); +} + + + +int CTentacle :: Level( float dz ) +{ + if (dz < 216) + return 0; + if (dz < 408) + return 1; + if (dz < 600) + return 2; + return 3; +} + + +float CTentacle :: MyHeight( ) +{ + switch ( MyLevel( ) ) + { + case 1: + return 256; + case 2: + return 448; + case 3: + return 640; + } + return 0; +} + + +int CTentacle :: MyLevel( ) +{ + switch( pev->sequence ) + { + case TENTACLE_ANIM_Pit_Idle: + return -1; + + case TENTACLE_ANIM_rise_to_Temp1: + case TENTACLE_ANIM_Temp1_to_Floor: + case TENTACLE_ANIM_Floor_to_Lev1: + return 0; + + case TENTACLE_ANIM_Floor_Idle: + case TENTACLE_ANIM_Floor_Fidget_Pissed: + case TENTACLE_ANIM_Floor_Fidget_SmallRise: + case TENTACLE_ANIM_Floor_Fidget_Wave: + case TENTACLE_ANIM_Floor_Strike: + case TENTACLE_ANIM_Floor_Tap: + case TENTACLE_ANIM_Floor_Rotate: + case TENTACLE_ANIM_Floor_Rear: + case TENTACLE_ANIM_Floor_Rear_Idle: + return 0; + + case TENTACLE_ANIM_Lev1_Idle: + case TENTACLE_ANIM_Lev1_Fidget_Claw: + case TENTACLE_ANIM_Lev1_Fidget_Shake: + case TENTACLE_ANIM_Lev1_Fidget_Snap: + case TENTACLE_ANIM_Lev1_Strike: + case TENTACLE_ANIM_Lev1_Tap: + case TENTACLE_ANIM_Lev1_Rotate: + case TENTACLE_ANIM_Lev1_Rear: + case TENTACLE_ANIM_Lev1_Rear_Idle: + return 1; + + case TENTACLE_ANIM_Lev1_to_Lev2: + return 1; + + case TENTACLE_ANIM_Lev2_Idle: + case TENTACLE_ANIM_Lev2_Fidget_Shake: + case TENTACLE_ANIM_Lev2_Fidget_Swing: + case TENTACLE_ANIM_Lev2_Fidget_Tut: + case TENTACLE_ANIM_Lev2_Strike: + case TENTACLE_ANIM_Lev2_Tap: + case TENTACLE_ANIM_Lev2_Rotate: + case TENTACLE_ANIM_Lev2_Rear: + case TENTACLE_ANIM_Lev2_Rear_Idle: + return 2; + + case TENTACLE_ANIM_Lev2_to_Lev3: + return 2; + + case TENTACLE_ANIM_Lev3_Idle: + case TENTACLE_ANIM_Lev3_Fidget_Shake: + case TENTACLE_ANIM_Lev3_Fidget_Side: + case TENTACLE_ANIM_Lev3_Fidget_Swipe: + case TENTACLE_ANIM_Lev3_Strike: + case TENTACLE_ANIM_Lev3_Tap: + case TENTACLE_ANIM_Lev3_Rotate: + case TENTACLE_ANIM_Lev3_Rear: + case TENTACLE_ANIM_Lev3_Rear_Idle: + return 3; + + case TENTACLE_ANIM_Lev1_Door_reach: + return -1; + } + return -1; +} + + +void CTentacle :: Test( void ) +{ + pev->sequence = TENTACLE_ANIM_Floor_Strike; + pev->framerate = 0; + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; +} + + + +// +// TentacleThink +// +void CTentacle :: Cycle( void ) +{ + // ALERT( at_console, "%s %.2f %d %d\n", STRING( pev->targetname ), pev->origin.z, m_MonsterState, m_IdealMonsterState ); + pev->nextthink = gpGlobals-> time + 0.1; + + // ALERT( at_console, "%s %d %d %d %f %f\n", STRING( pev->targetname ), pev->sequence, m_iGoalAnim, m_iDir, pev->framerate, pev->health ); + + if (m_MonsterState == MONSTERSTATE_SCRIPT || m_IdealMonsterState == MONSTERSTATE_SCRIPT) + { + pev->angles.y = m_flInitialYaw; + pev->ideal_yaw = m_flInitialYaw; + ClearConditions( IgnoreConditions() ); + MonsterThink( ); + m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; + return; + } + + DispatchAnimEvents( ); + StudioFrameAdvance( ); + + ChangeYaw( pev->yaw_speed ); + + CSound *pSound; + + Listen( ); + + // Listen will set this if there's something in my sound list + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + pSound = PBestSound(); + else + pSound = NULL; + + if ( pSound ) + { + Vector vecDir; + if (gpGlobals->time - m_flPrevSoundTime < 0.5) + { + float dt = gpGlobals->time - m_flPrevSoundTime; + vecDir = pSound->m_vecOrigin + (pSound->m_vecOrigin - m_vecPrevSound) / dt - pev->origin; + } + else + { + vecDir = pSound->m_vecOrigin - pev->origin; + } + m_flPrevSoundTime = gpGlobals->time; + m_vecPrevSound = pSound->m_vecOrigin; + + m_flSoundYaw = UTIL_VecToYaw ( vecDir ) - m_flInitialYaw; + m_iSoundLevel = Level( vecDir.z ); + + if (m_flSoundYaw < -180) + m_flSoundYaw += 360; + if (m_flSoundYaw > 180) + m_flSoundYaw -= 360; + + // ALERT( at_console, "sound %d %.0f\n", m_iSoundLevel, m_flSoundYaw ); + if (m_flSoundTime < gpGlobals->time) + { + // play "I hear new something" sound + char *sound; + + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_alert1.wav"; break; + case 1: sound = "tentacle/te_alert2.wav"; break; + } + + // UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), sound, 1.0, ATTN_NORM, 0, 100); + } + m_flSoundTime = gpGlobals->time + RANDOM_FLOAT( 5.0, 10.0 ); + } + + // clip ideal_yaw + float dy = m_flSoundYaw; + switch( pev->sequence ) + { + case TENTACLE_ANIM_Floor_Rear: + case TENTACLE_ANIM_Floor_Rear_Idle: + case TENTACLE_ANIM_Lev1_Rear: + case TENTACLE_ANIM_Lev1_Rear_Idle: + case TENTACLE_ANIM_Lev2_Rear: + case TENTACLE_ANIM_Lev2_Rear_Idle: + case TENTACLE_ANIM_Lev3_Rear: + case TENTACLE_ANIM_Lev3_Rear_Idle: + if (dy < 0 && dy > -m_flMaxYaw) + dy = -m_flMaxYaw; + if (dy > 0 && dy < m_flMaxYaw) + dy = m_flMaxYaw; + break; + default: + if (dy < -m_flMaxYaw) + dy = -m_flMaxYaw; + if (dy > m_flMaxYaw) + dy = m_flMaxYaw; + } + pev->ideal_yaw = m_flInitialYaw + dy; + + if (m_fSequenceFinished) + { + // ALERT( at_console, "%s done %d %d\n", STRING( pev->targetname ), pev->sequence, m_iGoalAnim ); + if (pev->health <= 1) + { + m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; + if (pev->sequence == TENTACLE_ANIM_Pit_Idle) + { + pev->health = 75; + } + } + else if ( m_flSoundTime > gpGlobals->time ) + { + if (m_flSoundYaw >= -(m_flMaxYaw + 30) && m_flSoundYaw <= (m_flMaxYaw + 30)) + { + // strike + m_iGoalAnim = LookupActivity( ACT_T_STRIKE + m_iSoundLevel ); + } + else if (m_flSoundYaw >= -m_flMaxYaw * 2 && m_flSoundYaw <= m_flMaxYaw * 2) + { + // tap + m_iGoalAnim = LookupActivity( ACT_T_TAP + m_iSoundLevel ); + } + else + { + // go into rear idle + m_iGoalAnim = LookupActivity( ACT_T_REARIDLE + m_iSoundLevel ); + } + } + else if (pev->sequence == TENTACLE_ANIM_Pit_Idle) + { + // stay in pit until hear noise + m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; + } + else if (pev->sequence == m_iGoalAnim) + { + if (MyLevel() >= 0 && gpGlobals->time < m_flSoundTime) + { + if (RANDOM_LONG(0,9) < m_flSoundTime - gpGlobals->time) + { + // continue stike + m_iGoalAnim = LookupActivity( ACT_T_STRIKE + m_iSoundLevel ); + } + else + { + // tap + m_iGoalAnim = LookupActivity( ACT_T_TAP + m_iSoundLevel ); + } + } + else if (MyLevel( ) < 0) + { + m_iGoalAnim = LookupActivity( ACT_T_IDLE + 0 ); + } + else + { + if (m_flNextSong < gpGlobals->time) + { + // play "I hear new something" sound + char *sound; + + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_sing1.wav"; break; + case 1: sound = "tentacle/te_sing2.wav"; break; + } + + EMIT_SOUND(ENT(pev), CHAN_VOICE, sound, 1.0, ATTN_NORM); + + m_flNextSong = gpGlobals->time + RANDOM_FLOAT( 10, 20 ); + } + + if (RANDOM_LONG(0,15) == 0) + { + // idle on new level + m_iGoalAnim = LookupActivity( ACT_T_IDLE + RANDOM_LONG(0,3) ); + } + else if (RANDOM_LONG(0,3) == 0) + { + // tap + m_iGoalAnim = LookupActivity( ACT_T_TAP + MyLevel( ) ); + } + else + { + // idle + m_iGoalAnim = LookupActivity( ACT_T_IDLE + MyLevel( ) ); + } + } + if (m_flSoundYaw < 0) + m_flSoundYaw += RANDOM_FLOAT( 2, 8 ); + else + m_flSoundYaw -= RANDOM_FLOAT( 2, 8 ); + } + + pev->sequence = FindTransition( pev->sequence, m_iGoalAnim, &m_iDir ); + + if (m_iDir > 0) + { + pev->frame = 0; + } + else + { + m_iDir = -1; // just to safe + pev->frame = 255; + } + ResetSequenceInfo( ); + + m_flFramerateAdj = RANDOM_FLOAT( -0.2, 0.2 ); + pev->framerate = m_iDir * 1.0 + m_flFramerateAdj; + + switch( pev->sequence) + { + case TENTACLE_ANIM_Floor_Tap: + case TENTACLE_ANIM_Lev1_Tap: + case TENTACLE_ANIM_Lev2_Tap: + case TENTACLE_ANIM_Lev3_Tap: + { + Vector vecSrc; + UTIL_MakeVectors( pev->angles ); + + TraceResult tr1, tr2; + + vecSrc = pev->origin + Vector( 0, 0, MyHeight() - 4); + UTIL_TraceLine( vecSrc, vecSrc + gpGlobals->v_forward * 512, ignore_monsters, ENT( pev ), &tr1 ); + + vecSrc = pev->origin + Vector( 0, 0, MyHeight() + 8); + UTIL_TraceLine( vecSrc, vecSrc + gpGlobals->v_forward * 512, ignore_monsters, ENT( pev ), &tr2 ); + + // ALERT( at_console, "%f %f\n", tr1.flFraction * 512, tr2.flFraction * 512 ); + + m_flTapRadius = SetBlending( 0, RANDOM_FLOAT( tr1.flFraction * 512, tr2.flFraction * 512 ) ); + } + break; + default: + m_flTapRadius = 336; // 400 - 64 + break; + } + pev->view_ofs.z = MyHeight( ); + // ALERT( at_console, "seq %d\n", pev->sequence ); + } + + if (m_flPrevSoundTime + 2.0 > gpGlobals->time) + { + // 1.5 normal speed if hears sounds + pev->framerate = m_iDir * 1.5 + m_flFramerateAdj; + } + else if (m_flPrevSoundTime + 5.0 > gpGlobals->time) + { + // slowdown to normal + pev->framerate = m_iDir + m_iDir * (5 - (gpGlobals->time - m_flPrevSoundTime)) / 2 + m_flFramerateAdj; + } +} + + + +void CTentacle::CommandUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // ALERT( at_console, "%s triggered %d\n", STRING( pev->targetname ), useType ); + switch( useType ) + { + case USE_OFF: + pev->takedamage = DAMAGE_NO; + SetThink( DieThink ); + m_iGoalAnim = TENTACLE_ANIM_Engine_Death1; + break; + case USE_ON: + if (pActivator) + { + // ALERT( at_console, "insert sound\n"); + CSoundEnt::InsertSound ( bits_SOUND_WORLD, pActivator->pev->origin, 1024, 1.0 ); + } + break; + case USE_SET: + break; + case USE_TOGGLE: + pev->takedamage = DAMAGE_NO; + SetThink( DieThink ); + m_iGoalAnim = TENTACLE_ANIM_Engine_Idle; + break; + } + +} + + + +void CTentacle :: DieThink( void ) +{ + pev->nextthink = gpGlobals-> time + 0.1; + + DispatchAnimEvents( ); + StudioFrameAdvance( ); + + ChangeYaw( 24 ); + + if (m_fSequenceFinished) + { + if (pev->sequence == m_iGoalAnim) + { + switch( m_iGoalAnim ) + { + case TENTACLE_ANIM_Engine_Idle: + case TENTACLE_ANIM_Engine_Sway: + case TENTACLE_ANIM_Engine_Swat: + case TENTACLE_ANIM_Engine_Bob: + m_iGoalAnim = TENTACLE_ANIM_Engine_Sway + RANDOM_LONG( 0, 2 ); + break; + case TENTACLE_ANIM_Engine_Death1: + case TENTACLE_ANIM_Engine_Death2: + case TENTACLE_ANIM_Engine_Death3: + UTIL_Remove( this ); + return; + } + } + + // ALERT( at_console, "%d : %d => ", pev->sequence, m_iGoalAnim ); + pev->sequence = FindTransition( pev->sequence, m_iGoalAnim, &m_iDir ); + // ALERT( at_console, "%d\n", pev->sequence ); + + if (m_iDir > 0) + { + pev->frame = 0; + } + else + { + pev->frame = 255; + } + ResetSequenceInfo( ); + + float dy; + switch( pev->sequence ) + { + case TENTACLE_ANIM_Floor_Rear: + case TENTACLE_ANIM_Floor_Rear_Idle: + case TENTACLE_ANIM_Lev1_Rear: + case TENTACLE_ANIM_Lev1_Rear_Idle: + case TENTACLE_ANIM_Lev2_Rear: + case TENTACLE_ANIM_Lev2_Rear_Idle: + case TENTACLE_ANIM_Lev3_Rear: + case TENTACLE_ANIM_Lev3_Rear_Idle: + case TENTACLE_ANIM_Engine_Idle: + case TENTACLE_ANIM_Engine_Sway: + case TENTACLE_ANIM_Engine_Swat: + case TENTACLE_ANIM_Engine_Bob: + case TENTACLE_ANIM_Engine_Death1: + case TENTACLE_ANIM_Engine_Death2: + case TENTACLE_ANIM_Engine_Death3: + pev->framerate = RANDOM_FLOAT( m_iDir - 0.2, m_iDir + 0.2 ); + dy = 180; + break; + default: + pev->framerate = 1.5; + dy = 0; + break; + } + pev->ideal_yaw = m_flInitialYaw + dy; + } +} + + +void CTentacle :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + char *sound; + + switch( pEvent->event ) + { + case 1: // bang + { + Vector vecSrc, vecAngles; + GetAttachment( 0, vecSrc, vecAngles ); + + // Vector vecSrc = pev->origin + m_flTapRadius * Vector( cos( pev->angles.y * (3.14192653 / 180.0) ), sin( pev->angles.y * (M_PI / 180.0) ), 0.0 ); + + // vecSrc.z += MyHeight( ); + + switch( m_iTapSound ) + { + case TE_SILO: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitSilo ), 1.0, ATTN_NORM, 0, 100); + break; + case TE_NONE: + break; + case TE_DIRT: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitDirt ), 1.0, ATTN_NORM, 0, 100); + break; + case TE_WATER: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitWater ), 1.0, ATTN_NORM, 0, 100); + break; + } + gpGlobals->force_retouch++; + } + break; + + case 3: // start killing swing + m_iHitDmg = 200; + // UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), "tentacle/te_swing1.wav", 1.0, ATTN_NORM, 0, 100); + break; + + case 4: // end killing swing + m_iHitDmg = 25; + break; + + case 5: // just "whoosh" sound + // UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), "tentacle/te_swing2.wav", 1.0, ATTN_NORM, 0, 100); + break; + + case 2: // tap scrape + case 6: // light tap + { + Vector vecSrc = pev->origin + m_flTapRadius * Vector( cos( pev->angles.y * (M_PI / 180.0) ), sin( pev->angles.y * (M_PI / 180.0) ), 0.0 ); + + vecSrc.z += MyHeight( ); + + float flVol = RANDOM_FLOAT( 0.3, 0.5 ); + + switch( m_iTapSound ) + { + case TE_SILO: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitSilo ), flVol, ATTN_NORM, 0, 100); + break; + case TE_NONE: + break; + case TE_DIRT: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitDirt ), flVol, ATTN_NORM, 0, 100); + break; + case TE_WATER: + UTIL_EmitAmbientSound(ENT(pev), vecSrc, RANDOM_SOUND_ARRAY( pHitWater ), flVol, ATTN_NORM, 0, 100); + break; + } + } + break; + + + case 7: // roar + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_roar1.wav"; break; + case 1: sound = "tentacle/te_roar2.wav"; break; + } + + UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), sound, 1.0, ATTN_NORM, 0, 100); + break; + + case 8: // search + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_search1.wav"; break; + case 1: sound = "tentacle/te_search2.wav"; break; + } + + UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), sound, 1.0, ATTN_NORM, 0, 100); + break; + + case 9: // swing + switch( RANDOM_LONG(0,1) ) + { + case 0: sound = "tentacle/te_move1.wav"; break; + case 1: sound = "tentacle/te_move2.wav"; break; + } + + UTIL_EmitAmbientSound(ENT(pev), pev->origin + Vector( 0, 0, MyHeight()), sound, 1.0, ATTN_NORM, 0, 100); + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + } +} + + +// +// TentacleStart +// +// void CTentacle :: Start( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +void CTentacle :: Start( void ) +{ + SetThink( Cycle ); + + if ( !g_fFlySound ) + { + EMIT_SOUND (ENT(pev), CHAN_BODY, "ambience/flies.wav", 1, ATTN_NORM ); + g_fFlySound = TRUE; +// pev->nextthink = gpGlobals-> time + 0.1; + } + else if ( !g_fSquirmSound ) + { + EMIT_SOUND (ENT(pev), CHAN_BODY, "ambience/squirm2.wav", 1, ATTN_NORM ); + g_fSquirmSound = TRUE; + } + + pev->nextthink = gpGlobals->time + 0.1; +} + + + + +void CTentacle :: HitTouch( CBaseEntity *pOther ) +{ + TraceResult tr = UTIL_GetGlobalTrace( ); + + if (pOther->pev->modelindex == pev->modelindex) + return; + + if (m_flHitTime > gpGlobals->time) + return; + + // only look at the ones where the player hit me + if (tr.pHit == NULL || tr.pHit->v.modelindex != pev->modelindex) + return; + + if (tr.iHitgroup >= 3) + { + pOther->TakeDamage( pev, pev, m_iHitDmg, DMG_CRUSH ); + // ALERT( at_console, "wack %3d : ", m_iHitDmg ); + } + else if (tr.iHitgroup != 0) + { + pOther->TakeDamage( pev, pev, 20, DMG_CRUSH ); + // ALERT( at_console, "tap %3d : ", 20 ); + } + else + { + return; // Huh? + } + + m_flHitTime = gpGlobals->time + 0.5; + + // ALERT( at_console, "%s : ", STRING( tr.pHit->v.classname ) ); + + // ALERT( at_console, "%.0f : %s : %d\n", pev->angles.y, STRING( pOther->pev->classname ), tr.iHitgroup ); +} + + +int CTentacle::TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (flDamage > pev->health) + { + pev->health = 1; + } + else + { + pev->health -= flDamage; + } + return 1; +} + + + + +void CTentacle :: Killed( entvars_t *pevAttacker, int iGib ) +{ + m_iGoalAnim = TENTACLE_ANIM_Pit_Idle; + return; +} + + + +class CTentacleMaw : public CBaseMonster +{ +public: + void Spawn( ); + void Precache( ); +}; + +LINK_ENTITY_TO_CLASS( monster_tentaclemaw, CTentacleMaw ); + +// +// Tentacle Spawn +// +void CTentacleMaw :: Spawn( ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/maw.mdl"); + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 64)); + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_STEP; + pev->effects = 0; + pev->health = 75; + pev->yaw_speed = 8; + pev->sequence = 0; + + pev->angles.x = 90; + // ResetSequenceInfo( ); +} + +void CTentacleMaw :: Precache( ) +{ + PRECACHE_MODEL("models/maw.mdl"); +} + +#endif \ No newline at end of file diff --git a/dlls/trains.h b/dlls/trains.h index 90d06e7..4ee4411 100644 --- a/dlls/trains.h +++ b/dlls/trains.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/triggers.cpp b/dlls/triggers.cpp index 61becd7..bd36a08 100644 --- a/dlls/triggers.cpp +++ b/dlls/triggers.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -1218,8 +1218,9 @@ void CBaseTrigger::CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, US return; BOOL fTellActivator = - (FClassnameIs(m_hActivator->pev, "player") && - !FBitSet(pev->spawnflags, SPAWNFLAG_NOMESSAGE)); + (m_hActivator != 0) && + FClassnameIs(m_hActivator->pev, "player") && + !FBitSet(pev->spawnflags, SPAWNFLAG_NOMESSAGE); if (m_cTriggersLeft != 0) { if (fTellActivator) diff --git a/dlls/tripmine.cpp b/dlls/tripmine.cpp index 8dbdaa3..81ff104 100644 --- a/dlls/tripmine.cpp +++ b/dlls/tripmine.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/turret.cpp b/dlls/turret.cpp new file mode 100644 index 0000000..c0ebac9 --- /dev/null +++ b/dlls/turret.cpp @@ -0,0 +1,1305 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +/* + +===== turret.cpp ======================================================== + +*/ + +// +// TODO: +// Take advantage of new monster fields like m_hEnemy and get rid of that OFFSET() stuff +// Revisit enemy validation stuff, maybe it's not necessary with the newest monster code +// + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "effects.h" + +extern Vector VecBModelOrigin( entvars_t* pevBModel ); + +#define TURRET_SHOTS 2 +#define TURRET_RANGE (100 * 12) +#define TURRET_SPREAD Vector( 0, 0, 0 ) +#define TURRET_TURNRATE 30 //angles per 0.1 second +#define TURRET_MAXWAIT 15 // seconds turret will stay active w/o a target +#define TURRET_MAXSPIN 5 // seconds turret barrel will spin w/o a target +#define TURRET_MACHINE_VOLUME 0.5 + +typedef enum +{ + TURRET_ANIM_NONE = 0, + TURRET_ANIM_FIRE, + TURRET_ANIM_SPIN, + TURRET_ANIM_DEPLOY, + TURRET_ANIM_RETIRE, + TURRET_ANIM_DIE, +} TURRET_ANIM; + +class CBaseTurret : public CBaseMonster +{ +public: + void Spawn(void); + virtual void Precache(void); + void KeyValue( KeyValueData *pkvd ); + void EXPORT TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); + virtual int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + virtual int Classify(void); + + int BloodColor( void ) { return DONT_BLEED; } + void GibMonster( void ) {} // UNDONE: Throw turret gibs? + + // Think functions + + void EXPORT ActiveThink(void); + void EXPORT SearchThink(void); + void EXPORT AutoSearchThink(void); + void EXPORT TurretDeath(void); + + virtual void EXPORT SpinDownCall(void) { m_iSpin = 0; } + virtual void EXPORT SpinUpCall(void) { m_iSpin = 1; } + + // void SpinDown(void); + // float EXPORT SpinDownCall( void ) { return SpinDown(); } + + // virtual float SpinDown(void) { return 0;} + // virtual float Retire(void) { return 0;} + + void EXPORT Deploy(void); + void EXPORT Retire(void); + + void EXPORT Initialize(void); + + virtual void Ping(void); + virtual void EyeOn(void); + virtual void EyeOff(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // other functions + void SetTurretAnim(TURRET_ANIM anim); + int MoveTurret(void); + virtual void Shoot(Vector &vecSrc, Vector &vecDirToEnemy) { }; + + float m_flMaxSpin; // Max time to spin the barrel w/o a target + int m_iSpin; + + CSprite *m_pEyeGlow; + int m_eyeBrightness; + + int m_iDeployHeight; + int m_iRetractHeight; + int m_iMinPitch; + + int m_iBaseTurnRate; // angles per second + float m_fTurnRate; // actual turn rate + int m_iOrientation; // 0 = floor, 1 = Ceiling + int m_iOn; + int m_fBeserk; // Sometimes this bitch will just freak out + int m_iAutoStart; // true if the turret auto deploys when a target + // enters its range + + Vector m_vecLastSight; + float m_flLastSight; // Last time we saw a target + float m_flMaxWait; // Max time to seach w/o a target + int m_iSearchSpeed; // Not Used! + + // movement + float m_flStartYaw; + Vector m_vecCurAngles; + Vector m_vecGoalAngles; + + + float m_flPingTime; // Time until the next ping, used when searching + float m_flSpinUpTime; // Amount of time until the barrel should spin down when searching +}; + + +TYPEDESCRIPTION CBaseTurret::m_SaveData[] = +{ + DEFINE_FIELD( CBaseTurret, m_flMaxSpin, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iSpin, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_pEyeGlow, FIELD_CLASSPTR ), + DEFINE_FIELD( CBaseTurret, m_eyeBrightness, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iDeployHeight, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iRetractHeight, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iMinPitch, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_iBaseTurnRate, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_fTurnRate, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iOrientation, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iOn, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_fBeserk, FIELD_INTEGER ), + DEFINE_FIELD( CBaseTurret, m_iAutoStart, FIELD_INTEGER ), + + + DEFINE_FIELD( CBaseTurret, m_vecLastSight, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CBaseTurret, m_flLastSight, FIELD_TIME ), + DEFINE_FIELD( CBaseTurret, m_flMaxWait, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_iSearchSpeed, FIELD_INTEGER ), + + DEFINE_FIELD( CBaseTurret, m_flStartYaw, FIELD_FLOAT ), + DEFINE_FIELD( CBaseTurret, m_vecCurAngles, FIELD_VECTOR ), + DEFINE_FIELD( CBaseTurret, m_vecGoalAngles, FIELD_VECTOR ), + + DEFINE_FIELD( CBaseTurret, m_flPingTime, FIELD_TIME ), + DEFINE_FIELD( CBaseTurret, m_flSpinUpTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CBaseTurret, CBaseMonster ); + +class CTurret : public CBaseTurret +{ +public: + void Spawn(void); + void Precache(void); + // Think functions + void SpinUpCall(void); + void SpinDownCall(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); + +private: + int m_iStartSpin; + +}; +TYPEDESCRIPTION CTurret::m_SaveData[] = +{ + DEFINE_FIELD( CTurret, m_iStartSpin, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CTurret, CBaseTurret ); + + +class CMiniTurret : public CBaseTurret +{ +public: + void Spawn( ); + void Precache(void); + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); +}; + + +LINK_ENTITY_TO_CLASS( monster_turret, CTurret ); +LINK_ENTITY_TO_CLASS( monster_miniturret, CMiniTurret ); + +void CBaseTurret::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "maxsleep")) + { + m_flMaxWait = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "orientation")) + { + m_iOrientation = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else if (FStrEq(pkvd->szKeyName, "searchspeed")) + { + m_iSearchSpeed = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else if (FStrEq(pkvd->szKeyName, "turnrate")) + { + m_iBaseTurnRate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "style") || + FStrEq(pkvd->szKeyName, "height") || + FStrEq(pkvd->szKeyName, "value1") || + FStrEq(pkvd->szKeyName, "value2") || + FStrEq(pkvd->szKeyName, "value3")) + pkvd->fHandled = TRUE; + else + CBaseMonster::KeyValue( pkvd ); +} + + +void CBaseTurret::Spawn() +{ + Precache( ); + pev->nextthink = gpGlobals->time + 1; + pev->movetype = MOVETYPE_FLY; + pev->sequence = 0; + pev->frame = 0; + pev->solid = SOLID_SLIDEBOX; + pev->takedamage = DAMAGE_AIM; + + SetBits (pev->flags, FL_MONSTER); + SetUse( TurretUse ); + + if (( pev->spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) + && !( pev->spawnflags & SF_MONSTER_TURRET_STARTINACTIVE )) + { + m_iAutoStart = TRUE; + } + + ResetSequenceInfo( ); + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + m_flFieldOfView = VIEW_FIELD_FULL; + // m_flSightRange = TURRET_RANGE; +} + + +void CBaseTurret::Precache( ) +{ + PRECACHE_SOUND ("turret/tu_fire1.wav"); + PRECACHE_SOUND ("turret/tu_ping.wav"); + PRECACHE_SOUND ("turret/tu_active2.wav"); + PRECACHE_SOUND ("turret/tu_die.wav"); + PRECACHE_SOUND ("turret/tu_die2.wav"); + PRECACHE_SOUND ("turret/tu_die3.wav"); + // PRECACHE_SOUND ("turret/tu_retract.wav"); // just use deploy sound to save memory + PRECACHE_SOUND ("turret/tu_deploy.wav"); + PRECACHE_SOUND ("turret/tu_spinup.wav"); + PRECACHE_SOUND ("turret/tu_spindown.wav"); + PRECACHE_SOUND ("turret/tu_search.wav"); + PRECACHE_SOUND ("turret/tu_alert.wav"); +} + +#define TURRET_GLOW_SPRITE "sprites/flare3.spr" + +void CTurret::Spawn() +{ + Precache( ); + SET_MODEL(ENT(pev), "models/turret.mdl"); + pev->health = gSkillData.turretHealth; + m_HackedGunPos = Vector( 0, 0, 12.75 ); + m_flMaxSpin = TURRET_MAXSPIN; + pev->view_ofs.z = 12.75; + + CBaseTurret::Spawn( ); + + m_iRetractHeight = 16; + m_iDeployHeight = 32; + m_iMinPitch = -15; + UTIL_SetSize(pev, Vector(-32, -32, -m_iRetractHeight), Vector(32, 32, m_iRetractHeight)); + + SetThink(Initialize); + + m_pEyeGlow = CSprite::SpriteCreate( TURRET_GLOW_SPRITE, pev->origin, FALSE ); + m_pEyeGlow->SetTransparency( kRenderGlow, 255, 0, 0, 0, kRenderFxNoDissipation ); + m_pEyeGlow->SetAttachment( edict(), 2 ); + m_eyeBrightness = 0; + + pev->nextthink = gpGlobals->time + 0.3; +} + +void CTurret::Precache() +{ + CBaseTurret::Precache( ); + PRECACHE_MODEL ("models/turret.mdl"); + PRECACHE_MODEL (TURRET_GLOW_SPRITE); +} + +void CMiniTurret::Spawn() +{ + Precache( ); + SET_MODEL(ENT(pev), "models/miniturret.mdl"); + pev->health = gSkillData.miniturretHealth; + m_HackedGunPos = Vector( 0, 0, 12.75 ); + m_flMaxSpin = 0; + pev->view_ofs.z = 12.75; + + CBaseTurret::Spawn( ); + m_iRetractHeight = 16; + m_iDeployHeight = 32; + m_iMinPitch = -15; + UTIL_SetSize(pev, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight)); + + SetThink(Initialize); + pev->nextthink = gpGlobals->time + 0.3; +} + + +void CMiniTurret::Precache() +{ + CBaseTurret::Precache( ); + PRECACHE_MODEL ("models/miniturret.mdl"); + PRECACHE_SOUND("weapons/hks1.wav"); + PRECACHE_SOUND("weapons/hks2.wav"); + PRECACHE_SOUND("weapons/hks3.wav"); +} + +void CBaseTurret::Initialize(void) +{ + m_iOn = 0; + m_fBeserk = 0; + m_iSpin = 0; + + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + + if (m_iBaseTurnRate == 0) m_iBaseTurnRate = TURRET_TURNRATE; + if (m_flMaxWait == 0) m_flMaxWait = TURRET_MAXWAIT; + m_flStartYaw = pev->angles.y; + if (m_iOrientation == 1) + { + pev->idealpitch = 180; + pev->angles.x = 180; + pev->view_ofs.z = -pev->view_ofs.z; + pev->effects |= EF_INVLIGHT; + pev->angles.y = pev->angles.y + 180; + if (pev->angles.y > 360) + pev->angles.y = pev->angles.y - 360; + } + + m_vecGoalAngles.x = 0; + + if (m_iAutoStart) + { + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(AutoSearchThink); + pev->nextthink = gpGlobals->time + .1; + } + else + SetThink(SUB_DoNothing); +} + +void CBaseTurret::TurretUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( !ShouldToggle( useType, m_iOn ) ) + return; + + if (m_iOn) + { + m_hEnemy = NULL; + pev->nextthink = gpGlobals->time + 0.1; + m_iAutoStart = FALSE;// switching off a turret disables autostart + //!!!! this should spin down first!!BUGBUG + SetThink(Retire); + } + else + { + pev->nextthink = gpGlobals->time + 0.1; // turn on delay + + // if the turret is flagged as an autoactivate turret, re-enable it's ability open self. + if ( pev->spawnflags & SF_MONSTER_TURRET_AUTOACTIVATE ) + { + m_iAutoStart = TRUE; + } + + SetThink(Deploy); + } +} + + +void CBaseTurret::Ping( void ) +{ + // make the pinging noise every second while searching + if (m_flPingTime == 0) + m_flPingTime = gpGlobals->time + 1; + else if (m_flPingTime <= gpGlobals->time) + { + m_flPingTime = gpGlobals->time + 1; + EMIT_SOUND(ENT(pev), CHAN_ITEM, "turret/tu_ping.wav", 1, ATTN_NORM); + EyeOn( ); + } + else if (m_eyeBrightness > 0) + { + EyeOff( ); + } +} + + +void CBaseTurret::EyeOn( ) +{ + if (m_pEyeGlow) + { + if (m_eyeBrightness != 255) + { + m_eyeBrightness = 255; + } + m_pEyeGlow->SetBrightness( m_eyeBrightness ); + } +} + + +void CBaseTurret::EyeOff( ) +{ + if (m_pEyeGlow) + { + if (m_eyeBrightness > 0) + { + m_eyeBrightness = max( 0, m_eyeBrightness - 30 ); + m_pEyeGlow->SetBrightness( m_eyeBrightness ); + } + } +} + + +void CBaseTurret::ActiveThink(void) +{ + int fAttack = 0; + Vector vecDirToEnemy; + + pev->nextthink = gpGlobals->time + 0.1; + StudioFrameAdvance( ); + + if ((!m_iOn) || (m_hEnemy == NULL)) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(SearchThink); + return; + } + + // if it's dead, look for something new + if ( !m_hEnemy->IsAlive() ) + { + if (!m_flLastSight) + { + m_flLastSight = gpGlobals->time + 0.5; // continue-shooting timeout + } + else + { + if (gpGlobals->time > m_flLastSight) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(SearchThink); + return; + } + } + } + + Vector vecMid = pev->origin + pev->view_ofs; + Vector vecMidEnemy = m_hEnemy->BodyTarget( vecMid ); + + // Look for our current enemy + int fEnemyVisible = FBoxVisible(pev, m_hEnemy->pev, vecMidEnemy ); + + vecDirToEnemy = vecMidEnemy - vecMid; // calculate dir and dist to enemy + float flDistToEnemy = vecDirToEnemy.Length(); + + Vector vec = UTIL_VecToAngles(vecMidEnemy - vecMid); + + // Current enmey is not visible. + if (!fEnemyVisible || (flDistToEnemy > TURRET_RANGE)) + { + if (!m_flLastSight) + m_flLastSight = gpGlobals->time + 0.5; + else + { + // Should we look for a new target? + if (gpGlobals->time > m_flLastSight) + { + m_hEnemy = NULL; + m_flLastSight = gpGlobals->time + m_flMaxWait; + SetThink(SearchThink); + return; + } + } + fEnemyVisible = 0; + } + else + { + m_vecLastSight = vecMidEnemy; + } + + UTIL_MakeAimVectors(m_vecCurAngles); + + /* + ALERT( at_console, "%.0f %.0f : %.2f %.2f %.2f\n", + m_vecCurAngles.x, m_vecCurAngles.y, + gpGlobals->v_forward.x, gpGlobals->v_forward.y, gpGlobals->v_forward.z ); + */ + + Vector vecLOS = vecDirToEnemy; //vecMid - m_vecLastSight; + vecLOS = vecLOS.Normalize(); + + // Is the Gun looking at the target + if (DotProduct(vecLOS, gpGlobals->v_forward) <= 0.866) // 30 degree slop + fAttack = FALSE; + else + fAttack = TRUE; + + // fire the gun + if (m_iSpin && ((fAttack) || (m_fBeserk))) + { + Vector vecSrc, vecAng; + GetAttachment( 0, vecSrc, vecAng ); + SetTurretAnim(TURRET_ANIM_FIRE); + Shoot(vecSrc, gpGlobals->v_forward ); + } + else + { + SetTurretAnim(TURRET_ANIM_SPIN); + } + + //move the gun + if (m_fBeserk) + { + if (RANDOM_LONG(0,9) == 0) + { + m_vecGoalAngles.y = RANDOM_FLOAT(0,360); + m_vecGoalAngles.x = RANDOM_FLOAT(0,90) - 90 * m_iOrientation; + TakeDamage(pev,pev,1, DMG_GENERIC); // don't beserk forever + return; + } + } + else if (fEnemyVisible) + { + if (vec.y > 360) + vec.y -= 360; + + if (vec.y < 0) + vec.y += 360; + + //ALERT(at_console, "[%.2f]", vec.x); + + if (vec.x < -180) + vec.x += 360; + + if (vec.x > 180) + vec.x -= 360; + + // now all numbers should be in [1...360] + // pin to turret limitations to [-90...15] + + if (m_iOrientation == 0) + { + if (vec.x > 90) + vec.x = 90; + else if (vec.x < m_iMinPitch) + vec.x = m_iMinPitch; + } + else + { + if (vec.x < -90) + vec.x = -90; + else if (vec.x > -m_iMinPitch) + vec.x = -m_iMinPitch; + } + + // ALERT(at_console, "->[%.2f]\n", vec.x); + + m_vecGoalAngles.y = vec.y; + m_vecGoalAngles.x = vec.x; + + } + + SpinUpCall(); + MoveTurret(); +} + + +void CTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_MONSTER_12MM, 1 ); + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "turret/tu_fire1.wav", 1, 0.6); + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + + +void CMiniTurret::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_MONSTER_9MM, 1 ); + + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks3.wav", 1, ATTN_NORM); break; + } + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + + +void CBaseTurret::Deploy(void) +{ + pev->nextthink = gpGlobals->time + 0.1; + StudioFrameAdvance( ); + + if (pev->sequence != TURRET_ANIM_DEPLOY) + { + m_iOn = 1; + SetTurretAnim(TURRET_ANIM_DEPLOY); + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_deploy.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + SUB_UseTargets( this, USE_ON, 0 ); + } + + if (m_fSequenceFinished) + { + pev->maxs.z = m_iDeployHeight; + pev->mins.z = -m_iDeployHeight; + UTIL_SetSize(pev, pev->mins, pev->maxs); + + m_vecCurAngles.x = 0; + + if (m_iOrientation == 1) + { + m_vecCurAngles.y = UTIL_AngleMod( pev->angles.y + 180 ); + } + else + { + m_vecCurAngles.y = UTIL_AngleMod( pev->angles.y ); + } + + SetTurretAnim(TURRET_ANIM_SPIN); + pev->framerate = 0; + SetThink(SearchThink); + } + + m_flLastSight = gpGlobals->time + m_flMaxWait; +} + +void CBaseTurret::Retire(void) +{ + // make the turret level + m_vecGoalAngles.x = 0; + m_vecGoalAngles.y = m_flStartYaw; + + pev->nextthink = gpGlobals->time + 0.1; + + StudioFrameAdvance( ); + + EyeOff( ); + + if (!MoveTurret()) + { + if (m_iSpin) + { + SpinDownCall(); + } + else if (pev->sequence != TURRET_ANIM_RETIRE) + { + SetTurretAnim(TURRET_ANIM_RETIRE); + EMIT_SOUND_DYN(ENT(pev), CHAN_BODY, "turret/tu_deploy.wav", TURRET_MACHINE_VOLUME, ATTN_NORM, 0, 120); + SUB_UseTargets( this, USE_OFF, 0 ); + } + else if (m_fSequenceFinished) + { + m_iOn = 0; + m_flLastSight = 0; + SetTurretAnim(TURRET_ANIM_NONE); + pev->maxs.z = m_iRetractHeight; + pev->mins.z = -m_iRetractHeight; + UTIL_SetSize(pev, pev->mins, pev->maxs); + if (m_iAutoStart) + { + SetThink(AutoSearchThink); + pev->nextthink = gpGlobals->time + .1; + } + else + SetThink(SUB_DoNothing); + } + } + else + { + SetTurretAnim(TURRET_ANIM_SPIN); + } +} + + +void CTurret::SpinUpCall(void) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + // Are we already spun up? If not start the two stage process. + if (!m_iSpin) + { + SetTurretAnim( TURRET_ANIM_SPIN ); + // for the first pass, spin up the the barrel + if (!m_iStartSpin) + { + pev->nextthink = gpGlobals->time + 1.0; // spinup delay + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_spinup.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + m_iStartSpin = 1; + pev->framerate = 0.1; + } + // after the barrel is spun up, turn on the hum + else if (pev->framerate >= 1.0) + { + pev->nextthink = gpGlobals->time + 0.1; // retarget delay + EMIT_SOUND(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + SetThink(ActiveThink); + m_iStartSpin = 0; + m_iSpin = 1; + } + else + { + pev->framerate += 0.075; + } + } + + if (m_iSpin) + { + SetThink(ActiveThink); + } +} + + +void CTurret::SpinDownCall(void) +{ + if (m_iSpin) + { + SetTurretAnim( TURRET_ANIM_SPIN ); + if (pev->framerate == 1.0) + { + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + EMIT_SOUND(ENT(pev), CHAN_ITEM, "turret/tu_spindown.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + } + pev->framerate -= 0.02; + if (pev->framerate <= 0) + { + pev->framerate = 0; + m_iSpin = 0; + } + } +} + + +void CBaseTurret::SetTurretAnim(TURRET_ANIM anim) +{ + if (pev->sequence != anim) + { + switch(anim) + { + case TURRET_ANIM_FIRE: + case TURRET_ANIM_SPIN: + if (pev->sequence != TURRET_ANIM_FIRE && pev->sequence != TURRET_ANIM_SPIN) + { + pev->frame = 0; + } + break; + default: + pev->frame = 0; + break; + } + + pev->sequence = anim; + ResetSequenceInfo( ); + + switch(anim) + { + case TURRET_ANIM_RETIRE: + pev->frame = 255; + pev->framerate = -1.0; + break; + case TURRET_ANIM_DIE: + pev->framerate = 1.0; + break; + } + //ALERT(at_console, "Turret anim #%d\n", anim); + } +} + + +// +// This search function will sit with the turret deployed and look for a new target. +// After a set amount of time, the barrel will spin down. After m_flMaxWait, the turret will +// retact. +// +void CBaseTurret::SearchThink(void) +{ + // ensure rethink + SetTurretAnim(TURRET_ANIM_SPIN); + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (m_flSpinUpTime == 0 && m_flMaxSpin) + m_flSpinUpTime = gpGlobals->time + m_flMaxSpin; + + Ping( ); + + // If we have a target and we're still healthy + if (m_hEnemy != NULL) + { + if (!m_hEnemy->IsAlive() ) + m_hEnemy = NULL;// Dead enemy forces a search for new one + } + + + // Acquire Target + if (m_hEnemy == NULL) + { + Look(TURRET_RANGE); + m_hEnemy = BestVisibleEnemy(); + } + + // If we've found a target, spin up the barrel and start to attack + if (m_hEnemy != NULL) + { + m_flLastSight = 0; + m_flSpinUpTime = 0; + SetThink(ActiveThink); + } + else + { + // Are we out of time, do we need to retract? + if (gpGlobals->time > m_flLastSight) + { + //Before we retrace, make sure that we are spun down. + m_flLastSight = 0; + m_flSpinUpTime = 0; + SetThink(Retire); + } + // should we stop the spin? + else if ((m_flSpinUpTime) && (gpGlobals->time > m_flSpinUpTime)) + { + SpinDownCall(); + } + + // generic hunt for new victims + m_vecGoalAngles.y = (m_vecGoalAngles.y + 0.1 * m_fTurnRate); + if (m_vecGoalAngles.y >= 360) + m_vecGoalAngles.y -= 360; + MoveTurret(); + } +} + + +// +// This think function will deploy the turret when something comes into range. This is for +// automatically activated turrets. +// +void CBaseTurret::AutoSearchThink(void) +{ + // ensure rethink + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.3; + + // If we have a target and we're still healthy + + if (m_hEnemy != NULL) + { + if (!m_hEnemy->IsAlive() ) + m_hEnemy = NULL;// Dead enemy forces a search for new one + } + + // Acquire Target + + if (m_hEnemy == NULL) + { + Look( TURRET_RANGE ); + m_hEnemy = BestVisibleEnemy(); + } + + if (m_hEnemy != NULL) + { + SetThink(Deploy); + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_alert.wav", TURRET_MACHINE_VOLUME, ATTN_NORM); + } +} + + +void CBaseTurret :: TurretDeath( void ) +{ + BOOL iActive = FALSE; + + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->deadflag != DEAD_DEAD) + { + pev->deadflag = DEAD_DEAD; + + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die.wav", 1.0, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die2.wav", 1.0, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die3.wav", 1.0, ATTN_NORM); + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + + if (m_iOrientation == 0) + m_vecGoalAngles.x = -15; + else + m_vecGoalAngles.x = -90; + + SetTurretAnim(TURRET_ANIM_DIE); + + EyeOn( ); + } + + EyeOff( ); + + if (pev->dmgtime + RANDOM_FLOAT( 0, 2 ) > gpGlobals->time) + { + // lots of smoke + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ) ); + WRITE_COORD( RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ) ); + WRITE_COORD( pev->origin.z - m_iOrientation * 64 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 25 ); // scale * 10 + WRITE_BYTE( 10 - m_iOrientation * 5); // framerate + MESSAGE_END(); + } + + if (pev->dmgtime + RANDOM_FLOAT( 0, 5 ) > gpGlobals->time) + { + Vector vecSrc = Vector( RANDOM_FLOAT( pev->absmin.x, pev->absmax.x ), RANDOM_FLOAT( pev->absmin.y, pev->absmax.y ), 0 ); + if (m_iOrientation == 0) + vecSrc = vecSrc + Vector( 0, 0, RANDOM_FLOAT( pev->origin.z, pev->absmax.z ) ); + else + vecSrc = vecSrc + Vector( 0, 0, RANDOM_FLOAT( pev->absmin.z, pev->origin.z ) ); + + UTIL_Sparks( vecSrc ); + } + + if (m_fSequenceFinished && !MoveTurret( ) && pev->dmgtime + 5 < gpGlobals->time) + { + pev->framerate = 0; + SetThink( NULL ); + } +} + + + +void CBaseTurret :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ( ptr->iHitgroup == 10 ) + { + // hit armor + if ( pev->dmgtime != gpGlobals->time || (RANDOM_LONG(0,10) < 1) ) + { + UTIL_Ricochet( ptr->vecEndPos, RANDOM_FLOAT( 1, 2) ); + pev->dmgtime = gpGlobals->time; + } + + flDamage = 0.1;// don't hurt the monster much, but allow bits_COND_LIGHT_DAMAGE to be generated + } + + if ( !pev->takedamage ) + return; + + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); +} + +// take damage. bitsDamageType indicates type of damage sustained, ie: DMG_BULLET + +int CBaseTurret::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + if ( !pev->takedamage ) + return 0; + + if (!m_iOn) + flDamage /= 10.0; + + pev->health -= flDamage; + if (pev->health <= 0) + { + pev->health = 0; + pev->takedamage = DAMAGE_NO; + pev->dmgtime = gpGlobals->time; + + ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place??? + + SetUse(NULL); + SetThink(TurretDeath); + SUB_UseTargets( this, USE_ON, 0 ); // wake up others + pev->nextthink = gpGlobals->time + 0.1; + + return 0; + } + + if (pev->health <= 10) + { + if (m_iOn && (1 || RANDOM_LONG(0, 0x7FFF) > 800)) + { + m_fBeserk = 1; + SetThink(SearchThink); + } + } + + return 1; +} + +int CBaseTurret::MoveTurret(void) +{ + int state = 0; + // any x movement? + + if (m_vecCurAngles.x != m_vecGoalAngles.x) + { + float flDir = m_vecGoalAngles.x > m_vecCurAngles.x ? 1 : -1 ; + + m_vecCurAngles.x += 0.1 * m_fTurnRate * flDir; + + // if we started below the goal, and now we're past, peg to goal + if (flDir == 1) + { + if (m_vecCurAngles.x > m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + else + { + if (m_vecCurAngles.x < m_vecGoalAngles.x) + m_vecCurAngles.x = m_vecGoalAngles.x; + } + + if (m_iOrientation == 0) + SetBoneController(1, -m_vecCurAngles.x); + else + SetBoneController(1, m_vecCurAngles.x); + state = 1; + } + + if (m_vecCurAngles.y != m_vecGoalAngles.y) + { + float flDir = m_vecGoalAngles.y > m_vecCurAngles.y ? 1 : -1 ; + float flDist = fabs(m_vecGoalAngles.y - m_vecCurAngles.y); + + if (flDist > 180) + { + flDist = 360 - flDist; + flDir = -flDir; + } + if (flDist > 30) + { + if (m_fTurnRate < m_iBaseTurnRate * 10) + { + m_fTurnRate += m_iBaseTurnRate; + } + } + else if (m_fTurnRate > 45) + { + m_fTurnRate -= m_iBaseTurnRate; + } + else + { + m_fTurnRate += m_iBaseTurnRate; + } + + m_vecCurAngles.y += 0.1 * m_fTurnRate * flDir; + + if (m_vecCurAngles.y < 0) + m_vecCurAngles.y += 360; + else if (m_vecCurAngles.y >= 360) + m_vecCurAngles.y -= 360; + + if (flDist < (0.05 * m_iBaseTurnRate)) + m_vecCurAngles.y = m_vecGoalAngles.y; + + //ALERT(at_console, "%.2f -> %.2f\n", m_vecCurAngles.y, y); + if (m_iOrientation == 0) + SetBoneController(0, m_vecCurAngles.y - pev->angles.y ); + else + SetBoneController(0, pev->angles.y - 180 - m_vecCurAngles.y ); + state = 1; + } + + if (!state) + m_fTurnRate = m_iBaseTurnRate; + + //ALERT(at_console, "(%.2f, %.2f)->(%.2f, %.2f)\n", m_vecCurAngles.x, + // m_vecCurAngles.y, m_vecGoalAngles.x, m_vecGoalAngles.y); + return state; +} + +// +// ID as a machine +// +int CBaseTurret::Classify ( void ) +{ + if (m_iOn || m_iAutoStart) + return CLASS_MACHINE; + return CLASS_NONE; +} + + + + +//========================================================= +// Sentry gun - smallest turret, placed near grunt entrenchments +//========================================================= +class CSentry : public CBaseTurret +{ +public: + void Spawn( ); + void Precache(void); + // other functions + void Shoot(Vector &vecSrc, Vector &vecDirToEnemy); + int TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType); + void EXPORT SentryTouch( CBaseEntity *pOther ); + void EXPORT SentryDeath( void ); + +}; + +LINK_ENTITY_TO_CLASS( monster_sentry, CSentry ); + +void CSentry::Precache() +{ + CBaseTurret::Precache( ); + PRECACHE_MODEL ("models/sentry.mdl"); +} + +void CSentry::Spawn() +{ + Precache( ); + SET_MODEL(ENT(pev), "models/sentry.mdl"); + pev->health = gSkillData.sentryHealth; + m_HackedGunPos = Vector( 0, 0, 48 ); + pev->view_ofs.z = 48; + m_flMaxWait = 1E6; + m_flMaxSpin = 1E6; + + CBaseTurret::Spawn(); + m_iRetractHeight = 64; + m_iDeployHeight = 64; + m_iMinPitch = -60; + UTIL_SetSize(pev, Vector(-16, -16, -m_iRetractHeight), Vector(16, 16, m_iRetractHeight)); + + SetTouch(SentryTouch); + SetThink(Initialize); + pev->nextthink = gpGlobals->time + 0.3; +} + +void CSentry::Shoot(Vector &vecSrc, Vector &vecDirToEnemy) +{ + FireBullets( 1, vecSrc, vecDirToEnemy, TURRET_SPREAD, TURRET_RANGE, BULLET_MONSTER_MP5, 1 ); + + switch(RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/hks3.wav", 1, ATTN_NORM); break; + } + pev->effects = pev->effects | EF_MUZZLEFLASH; +} + +int CSentry::TakeDamage(entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType) +{ + if ( !pev->takedamage ) + return 0; + + if (!m_iOn) + { + SetThink( Deploy ); + SetUse( NULL ); + pev->nextthink = gpGlobals->time + 0.1; + } + + pev->health -= flDamage; + if (pev->health <= 0) + { + pev->health = 0; + pev->takedamage = DAMAGE_NO; + pev->dmgtime = gpGlobals->time; + + ClearBits (pev->flags, FL_MONSTER); // why are they set in the first place??? + + SetUse(NULL); + SetThink(SentryDeath); + SUB_UseTargets( this, USE_ON, 0 ); // wake up others + pev->nextthink = gpGlobals->time + 0.1; + + return 0; + } + + return 1; +} + + +void CSentry::SentryTouch( CBaseEntity *pOther ) +{ + if ( pOther && (pOther->IsPlayer() || (pOther->pev->flags & FL_MONSTER)) ) + { + TakeDamage(pOther->pev, pOther->pev, 0, 0 ); + } +} + + +void CSentry :: SentryDeath( void ) +{ + BOOL iActive = FALSE; + + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + if (pev->deadflag != DEAD_DEAD) + { + pev->deadflag = DEAD_DEAD; + + float flRndSound = RANDOM_FLOAT ( 0 , 1 ); + + if ( flRndSound <= 0.33 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die.wav", 1.0, ATTN_NORM); + else if ( flRndSound <= 0.66 ) + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die2.wav", 1.0, ATTN_NORM); + else + EMIT_SOUND(ENT(pev), CHAN_BODY, "turret/tu_die3.wav", 1.0, ATTN_NORM); + + EMIT_SOUND_DYN(ENT(pev), CHAN_STATIC, "turret/tu_active2.wav", 0, 0, SND_STOP, 100); + + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + + SetTurretAnim(TURRET_ANIM_DIE); + + pev->solid = SOLID_NOT; + pev->angles.y = UTIL_AngleMod( pev->angles.y + RANDOM_LONG( 0, 2 ) * 120 ); + + EyeOn( ); + } + + EyeOff( ); + + Vector vecSrc, vecAng; + GetAttachment( 1, vecSrc, vecAng ); + + if (pev->dmgtime + RANDOM_FLOAT( 0, 2 ) > gpGlobals->time) + { + // lots of smoke + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( vecSrc.x + RANDOM_FLOAT( -16, 16 ) ); + WRITE_COORD( vecSrc.y + RANDOM_FLOAT( -16, 16 ) ); + WRITE_COORD( vecSrc.z - 32 ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 15 ); // scale * 10 + WRITE_BYTE( 8 ); // framerate + MESSAGE_END(); + } + + if (pev->dmgtime + RANDOM_FLOAT( 0, 8 ) > gpGlobals->time) + { + UTIL_Sparks( vecSrc ); + } + + if (m_fSequenceFinished && pev->dmgtime + 5 < gpGlobals->time) + { + pev->framerate = 0; + SetThink( NULL ); + } +} + diff --git a/dlls/util.cpp b/dlls/util.cpp index 328d846..ab27c18 100644 --- a/dlls/util.cpp +++ b/dlls/util.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/util.h b/dlls/util.h index e6541fb..4865515 100644 --- a/dlls/util.h +++ b/dlls/util.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -422,13 +422,9 @@ extern DLL_GLOBAL int g_Language; #define SVC_CDTRACK 32 #define SVC_WEAPONANIM 35 #define SVC_ROOMTYPE 37 -#define SVC_HLTV 50 +#define SVC_DIRECTOR 51 -// prxoy director stuff -#define DRC_EVENT 3 // informs the dircetor about ann important game event -#define DRC_FLAG_PRIO_MASK 0x0F // priorities between 0 and 15 (15 most important) -#define DRC_FLAG_DRAMATIC (1<<5) // triggers #define SF_TRIGGER_ALLOWMONSTERS 1// monsters allowed to fire this trigger diff --git a/dlls/vector.h b/dlls/vector.h index 282e3b9..c498c83 100644 --- a/dlls/vector.h +++ b/dlls/vector.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1999, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/weapons.cpp b/dlls/weapons.cpp index 9576e18..f818779 100644 --- a/dlls/weapons.cpp +++ b/dlls/weapons.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/weapons.h b/dlls/weapons.h index f682767..aaa9824 100644 --- a/dlls/weapons.h +++ b/dlls/weapons.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/world.cpp b/dlls/world.cpp index 610b33b..6287fca 100644 --- a/dlls/world.cpp +++ b/dlls/world.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/wpn_shared/hl_wpn_glock.cpp b/dlls/wpn_shared/hl_wpn_glock.cpp index 3a3102a..769ab4a 100644 --- a/dlls/wpn_shared/hl_wpn_glock.cpp +++ b/dlls/wpn_shared/hl_wpn_glock.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/wxdebug.h b/dlls/wxdebug.h new file mode 100644 index 0000000..321b1bc --- /dev/null +++ b/dlls/wxdebug.h @@ -0,0 +1,137 @@ +#ifndef __WXDEBUG__ +#define __WXDEBUG__ + +// This library provides fairly straight forward debugging functionality, this +// is split into two main sections. The first is assertion handling, there are +// three types of assertions provided here. The most commonly used one is the +// ASSERT(condition) macro which will pop up a message box including the file +// and line number if the condition evaluates to FALSE. Then there is the +// EXECUTE_ASSERT macro which is the same as ASSERT except the condition will +// still be executed in NON debug builds. The final type of assertion is the +// KASSERT macro which is more suitable for pure (perhaps kernel) filters as +// the condition is printed onto the debugger rather than in a message box. +// +// The other part of the debug module facilties is general purpose logging. +// This is accessed by calling DbgLog(). The function takes a type and level +// field which define the type of informational string you are presenting and +// it's relative importance. The type field can be a combination (one or more) +// of LOG_TIMING, LOG_TRACE, LOG_MEMORY, LOG_LOCKING and LOG_ERROR. The level +// is a DWORD value where zero defines highest important. Use of zero as the +// debug logging level is to be encouraged ONLY for major errors or events as +// they will ALWAYS be displayed on the debugger. Other debug output has it's +// level matched against the current debug output level stored in the registry +// for this module and if less than the current setting it will be displayed. +// +// Each module or executable has it's own debug output level for each of the +// five types. These are read in when the DbgInitialise function is called +// for DLLs linking to STRMBASE.LIB this is done automatically when the DLL +// is loaded, executables must call it explicitely with the module instance +// handle given to them through the WINMAIN entry point. An executable must +// also call DbgTerminate when they have finished to clean up the resources +// the debug library uses, once again this is done automatically for DLLs + +// These are the five different categories of logging information + +#ifdef _DEBUG + + +enum +{ + LOG_TRACE = 0x00000001, // General tracing + LOG_ENTRY = 0x00000002, // Function entry logging + LOG_EXIT = 0x00000004, // Function exit logging + LOG_MEMORY = 0x00000008, // Memory alloc/free debugging + LOG_ERROR = 0x00000010, // Error notification + LOG_UNUSED0 = 0x00000020, // reserved + LOG_UNUSED1 = 0x00000040, // reserved + LOG_UNUSED2 = 0x00000080, // reserved + LOG_CHUM = 0x00000100, // Chumtoad debugging + LOG_LEECH = 0x00000200, // Leech debugging + LOG_ICHTHYOSAUR = 0x00000400, // Ichthyosaur debugging +}; + + +// These are public but should be called only by the DLLMain function +void WINAPI DbgInitialise(HINSTANCE hInst); +void WINAPI DbgTerminate(); +// These are public but should be called by macro only +void WINAPI DbgKernelAssert(const TCHAR *pCondition,const TCHAR *pFileName,INT iLine); +void WINAPI DbgLogInfo(DWORD Type,DWORD Level,const TCHAR *pFormat,...); +void WINAPI DbgOutString(LPCTSTR psz); + + +// These are the macros that should be used in code. + +#define DBGASSERT(_x_) \ + if (!(_x_)) \ + DbgKernelAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + +#define DBGBREAK(_x_) \ + DbgKernelAssert(TEXT(#_x_),TEXT(__FILE__),__LINE__) + +#define DBGASSERTEXECUTE(_x_) DBGASSERT(_x_) + +#define DBGLOG(_x_) DbgLogInfo _x_ + +#define DBGOUT(_x_) DbgOutString(_x_) + +#define ValidateReadPtr(p,cb) \ + {if(IsBadReadPtr((PVOID)p,cb) == TRUE) \ + DBGBREAK("Invalid read pointer");} + +#define ValidateWritePtr(p,cb) \ + {if(IsBadWritePtr((PVOID)p,cb) == TRUE) \ + DBGBREAK("Invalid write pointer");} + +#define ValidateReadWritePtr(p,cb) \ + {ValidateReadPtr(p,cb) ValidateWritePtr(p,cb)} + +#define ValidateStringPtr(p) \ + {if(IsBadStringPtr((LPCTSTR)p,INFINITE) == TRUE) \ + DBGBREAK("Invalid string pointer");} + +#define ValidateStringPtrA(p) \ + {if(IsBadStringPtrA((LPCSTR)p,INFINITE) == TRUE) \ + DBGBREAK("Invalid ANSII string pointer");} + +#define ValidateStringPtrW(p) \ + {if(IsBadStringPtrW((LPCWSTR)p,INFINITE) == TRUE) \ + DBGBREAK("Invalid UNICODE string pointer");} + +#else // !_DEBUG + +// Retail builds make public debug functions inert - WARNING the source +// files do not define or build any of the entry points in debug builds +// (public entry points compile to nothing) so if you go trying to call +// any of the private entry points in your source they won't compile + +#define DBGASSERT(_x_) +#define DBGBREAK(_x_) +#define DBGASSERTEXECUTE(_x_) _x_ +#define DBGLOG(_x_) +#define DBGOUT(_x_) +#define ValidateReadPtr(p,cb) +#define ValidateWritePtr(p,cb) +#define ValidateReadWritePtr(p,cb) +#define ValidateStringPtr(p) +#define ValidateStringPtrA(p) +#define ValidateStringPtrW(p) + +#endif // !_DEBUG + + +#ifndef REMIND + // REMIND macro - generates warning as reminder to complete coding + // (eg) usage: + // + // #pragma message (REMIND("Add automation support")) + + + #define REMINDQUOTE(x) #x + #define REMINDQQUOTE(y) REMINDQUOTE(y) + #define REMIND(str) __FILE__ "(" REMINDQQUOTE(__LINE__) ") : " str +#endif + +#endif // __WXDEBUG__ + + diff --git a/dlls/xen.cpp b/dlls/xen.cpp index 6f0f8e3..a52ea6b 100644 --- a/dlls/xen.cpp +++ b/dlls/xen.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/dlls/zombie.cpp b/dlls/zombie.cpp new file mode 100644 index 0000000..234eafc --- /dev/null +++ b/dlls/zombie.cpp @@ -0,0 +1,346 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +//========================================================= +// Zombie +//========================================================= + +// UNDONE: Don't flinch every time you get hit + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define ZOMBIE_AE_ATTACK_RIGHT 0x01 +#define ZOMBIE_AE_ATTACK_LEFT 0x02 +#define ZOMBIE_AE_ATTACK_BOTH 0x03 + +#define ZOMBIE_FLINCH_DELAY 2 // at most one flinch every n secs + +class CZombie : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int IgnoreConditions ( void ); + + float m_flNextFlinch; + + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + void AttackSound( void ); + + static const char *pAttackSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + + // No range attacks + BOOL CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } + BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); +}; + +LINK_ENTITY_TO_CLASS( monster_zombie, CZombie ); + +const char *CZombie::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CZombie::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CZombie::pAttackSounds[] = +{ + "zombie/zo_attack1.wav", + "zombie/zo_attack2.wav", +}; + +const char *CZombie::pIdleSounds[] = +{ + "zombie/zo_idle1.wav", + "zombie/zo_idle2.wav", + "zombie/zo_idle3.wav", + "zombie/zo_idle4.wav", +}; + +const char *CZombie::pAlertSounds[] = +{ + "zombie/zo_alert10.wav", + "zombie/zo_alert20.wav", + "zombie/zo_alert30.wav", +}; + +const char *CZombie::pPainSounds[] = +{ + "zombie/zo_pain1.wav", + "zombie/zo_pain2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CZombie :: Classify ( void ) +{ + return CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CZombie :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + pev->yaw_speed = ys; +} + +int CZombie :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Take 30% damage from bullets + if ( bitsDamageType == DMG_BULLET ) + { + Vector vecDir = pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5; + vecDir = vecDir.Normalize(); + float flForce = DamageForce( flDamage ); + pev->velocity = pev->velocity + vecDir * flForce; + flDamage *= 0.3; + } + + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CZombie :: PainSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + if (RANDOM_LONG(0,5) < 2) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); +} + +void CZombie :: AlertSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAlertSounds[ RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); +} + +void CZombie :: IdleSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + // Play a random idle sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pIdleSounds[ RANDOM_LONG(0,ARRAYSIZE(pIdleSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); +} + +void CZombie :: AttackSound( void ) +{ + // Play a random attack sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CZombie :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case ZOMBIE_AE_ATTACK_RIGHT: + { + // do stuff for this event. + // ALERT( at_console, "Slash right!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.zombieDmgOneSlash, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = -18; + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity - gpGlobals->v_right * 100; + } + // Play a random attack hit sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + else // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + if (RANDOM_LONG(0,1)) + AttackSound(); + } + break; + + case ZOMBIE_AE_ATTACK_LEFT: + { + // do stuff for this event. + // ALERT( at_console, "Slash left!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.zombieDmgOneSlash, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = 18; + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_right * 100; + } + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + else + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + if (RANDOM_LONG(0,1)) + AttackSound(); + } + break; + + case ZOMBIE_AE_ATTACK_BOTH: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.zombieDmgBothSlash, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * -100; + } + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + } + else + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + + if (RANDOM_LONG(0,1)) + AttackSound(); + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CZombie :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/zombie.mdl"); + UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = gSkillData.zombieHealth; + pev->view_ofs = VEC_VIEW;// position of the eyes relative to monster's origin. + m_flFieldOfView = 0.5;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = bits_CAP_DOORS_GROUP; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CZombie :: Precache() +{ + int i; + + PRECACHE_MODEL("models/zombie.mdl"); + + for ( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackHitSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackMissSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pIdleSounds ); i++ ) + PRECACHE_SOUND((char *)pIdleSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAlertSounds ); i++ ) + PRECACHE_SOUND((char *)pAlertSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + + +int CZombie::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK1)) + { +#if 0 + if (pev->health < 20) + iIgnore |= (bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE); + else +#endif + if (m_flNextFlinch >= gpGlobals->time) + iIgnore |= (bits_COND_LIGHT_DAMAGE|bits_COND_HEAVY_DAMAGE); + } + + if ((m_Activity == ACT_SMALL_FLINCH) || (m_Activity == ACT_BIG_FLINCH)) + { + if (m_flNextFlinch < gpGlobals->time) + m_flNextFlinch = gpGlobals->time + ZOMBIE_FLINCH_DELAY; + } + + return iIgnore; + +} \ No newline at end of file diff --git a/engine/anorms.h b/engine/anorms.h index 4f719a7..c90f8d6 100644 --- a/engine/anorms.h +++ b/engine/anorms.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/engine/cdll_int.h b/engine/cdll_int.h index 6eb44d1..2c70fa5 100644 --- a/engine/cdll_int.h +++ b/engine/cdll_int.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -28,6 +28,7 @@ extern "C" { #include "const.h" + // this file is included by both the engine and the client-dll, // so make sure engine declarations aren't done twice @@ -36,7 +37,7 @@ typedef int HSPRITE; // handle to a graphic #define SCRINFO_SCREENFLASH 1 #define SCRINFO_STRETCHED 2 -typedef struct +typedef struct SCREENINFO_s { int iSize; int iWidth; @@ -67,7 +68,7 @@ typedef struct client_sprite_s wrect_t rc; } client_sprite_t; -typedef struct +typedef struct client_textmessage_s { int effect; byte r1, g1, b1, a1; // 2 colors for effects @@ -82,7 +83,7 @@ typedef struct const char *pMessage; } client_textmessage_t; -typedef struct +typedef struct hud_player_info_s { char *name; short ping; @@ -99,7 +100,6 @@ typedef struct } hud_player_info_t; -// this is by no means complete, or even accurate typedef struct cl_enginefuncs_s { // sprite handlers @@ -246,6 +246,10 @@ typedef struct cl_enginefuncs_s // Same as pfnServerCmd, but the message goes in the unreliable stream so it can't clog the net stream // (but it might not get there). int ( *pfnServerCmdUnreliable )( char *szCmdString ); + + void ( *pfnGetMousePos )( struct tagPOINT *ppt ); + void ( *pfnSetMousePos )( int x, int y ); + void ( *pfnSetMouseEnable )( qboolean fEnable ); } cl_enginefunc_t; #ifndef IN_BUTTONS_H @@ -297,7 +301,7 @@ extern int ClientDLL_Key_Event( int down, int keynum, const char *pszCurrentBind extern void ClientDLL_TempEntUpdate( double ft, double ct, double grav, struct tempent_s **ppFreeTE, struct tempent_s **ppActiveTE, int ( *addTEntity )( struct cl_entity_s *pEntity ), void ( *playTESound )( struct tempent_s *pTemp, float damp ) ); extern struct cl_entity_s *ClientDLL_GetUserEntity( int index ); extern void ClientDLL_VoiceStatus(int entindex, qboolean bTalking); -extern void ClientDLL_DirectorEvent(unsigned char command, unsigned int firstObject, unsigned int secondObject, unsigned int flags); +extern void ClientDLL_DirectorMessage( int iSize, void *pbuf ); #ifdef __cplusplus diff --git a/engine/custom.h b/engine/custom.h index 3d7e189..a5a8da9 100644 --- a/engine/custom.h +++ b/engine/custom.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -35,11 +35,10 @@ typedef enum t_model, t_decal, t_generic, - t_eventscript + t_eventscript, + t_world, // Fake type for world, is really t_model } resourcetype_t; -// Fake type for world -#define t_world 6 typedef struct { @@ -48,7 +47,7 @@ typedef struct typedef struct resourceinfo_s { - _resourceinfo_t info[ 7 ]; + _resourceinfo_t info[ 8 ]; } resourceinfo_t; #define RES_FATALIFMISSING (1<<0) // Disconnect if we can't get this file. diff --git a/engine/customentity.h b/engine/customentity.h index 4ba5a92..2c4eb5e 100644 --- a/engine/customentity.h +++ b/engine/customentity.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/engine/edict.h b/engine/edict.h index 07e975a..6bb109d 100644 --- a/engine/edict.h +++ b/engine/edict.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/engine/eiface.h b/engine/eiface.h index dc5ff1f..157173e 100644 --- a/engine/eiface.h +++ b/engine/eiface.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -258,6 +258,8 @@ typedef struct enginefuncs_s // NOTE: these functions take player entity indices (starting at 1). qboolean (*pfnVoice_GetClientListening)(int iReceiver, int iSender); qboolean (*pfnVoice_SetClientListening)(int iReceiver, int iSender, qboolean bListen); + + const char *(*pfnGetPlayerAuthId) ( edict_t *e ); } enginefuncs_t; // ONLY ADD NEW FUNCTIONS TO THE END OF THIS STRUCT. INTERFACE VERSION IS FROZEN AT 138 diff --git a/engine/keydefs.h b/engine/keydefs.h index 48b8ca8..aca2c09 100644 --- a/engine/keydefs.h +++ b/engine/keydefs.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/engine/progdefs.h b/engine/progdefs.h index 506825a..b8c58ad 100644 --- a/engine/progdefs.h +++ b/engine/progdefs.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/engine/progs.h b/engine/progs.h index a5c9ba3..6115fcb 100644 --- a/engine/progs.h +++ b/engine/progs.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/engine/shake.h b/engine/shake.h index 568f3f4..8e405db 100644 --- a/engine/shake.h +++ b/engine/shake.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/engine/studio.h b/engine/studio.h index a13a981..62d4a98 100644 --- a/engine/studio.h +++ b/engine/studio.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/game_shared/bitvec.h b/game_shared/bitvec.h index a7f2b15..0271a11 100644 --- a/game_shared/bitvec.h +++ b/game_shared/bitvec.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/vgui_checkbutton2.cpp b/game_shared/vgui_checkbutton2.cpp index 109964f..511853f 100644 --- a/game_shared/vgui_checkbutton2.cpp +++ b/game_shared/vgui_checkbutton2.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/vgui_checkbutton2.h b/game_shared/vgui_checkbutton2.h index ca0b12f..8f985d9 100644 --- a/game_shared/vgui_checkbutton2.h +++ b/game_shared/vgui_checkbutton2.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/vgui_defaultinputsignal.h b/game_shared/vgui_defaultinputsignal.h index 7d9ceca..94dae8d 100644 --- a/game_shared/vgui_defaultinputsignal.h +++ b/game_shared/vgui_defaultinputsignal.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/vgui_grid.cpp b/game_shared/vgui_grid.cpp index 2fc8cba..00e9d69 100644 --- a/game_shared/vgui_grid.cpp +++ b/game_shared/vgui_grid.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/vgui_grid.h b/game_shared/vgui_grid.h index cfd9376..472f509 100644 --- a/game_shared/vgui_grid.h +++ b/game_shared/vgui_grid.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/vgui_helpers.cpp b/game_shared/vgui_helpers.cpp index c84ea10..fbcfeb5 100644 --- a/game_shared/vgui_helpers.cpp +++ b/game_shared/vgui_helpers.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/vgui_helpers.h b/game_shared/vgui_helpers.h index e79a06d..f9ee45e 100644 --- a/game_shared/vgui_helpers.h +++ b/game_shared/vgui_helpers.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/vgui_listbox.cpp b/game_shared/vgui_listbox.cpp index c482e1e..972091e 100644 --- a/game_shared/vgui_listbox.cpp +++ b/game_shared/vgui_listbox.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/vgui_listbox.h b/game_shared/vgui_listbox.h index f24a947..a48acef 100644 --- a/game_shared/vgui_listbox.h +++ b/game_shared/vgui_listbox.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/vgui_loadtga.cpp b/game_shared/vgui_loadtga.cpp index fd90571..d7e1f5e 100644 --- a/game_shared/vgui_loadtga.cpp +++ b/game_shared/vgui_loadtga.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/vgui_loadtga.h b/game_shared/vgui_loadtga.h index 2a9c0b2..8930e4e 100644 --- a/game_shared/vgui_loadtga.h +++ b/game_shared/vgui_loadtga.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/vgui_scrollbar2.cpp b/game_shared/vgui_scrollbar2.cpp index acfbb0d..7b240e6 100644 --- a/game_shared/vgui_scrollbar2.cpp +++ b/game_shared/vgui_scrollbar2.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/vgui_scrollbar2.h b/game_shared/vgui_scrollbar2.h index ba85bb6..b9dfea6 100644 --- a/game_shared/vgui_scrollbar2.h +++ b/game_shared/vgui_scrollbar2.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/vgui_slider2.cpp b/game_shared/vgui_slider2.cpp index c8af14b..7a220b5 100644 --- a/game_shared/vgui_slider2.cpp +++ b/game_shared/vgui_slider2.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: New version of the slider bar // diff --git a/game_shared/vgui_slider2.h b/game_shared/vgui_slider2.h index 4f1d3f8..ec38421 100644 --- a/game_shared/vgui_slider2.h +++ b/game_shared/vgui_slider2.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/voice_banmgr.cpp b/game_shared/voice_banmgr.cpp index af29510..470e80e 100644 --- a/game_shared/voice_banmgr.cpp +++ b/game_shared/voice_banmgr.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/voice_banmgr.h b/game_shared/voice_banmgr.h index 3db37b9..2f14d8b 100644 --- a/game_shared/voice_banmgr.h +++ b/game_shared/voice_banmgr.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/voice_common.h b/game_shared/voice_common.h index abd49ef..93e9353 100644 --- a/game_shared/voice_common.h +++ b/game_shared/voice_common.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/voice_gamemgr.cpp b/game_shared/voice_gamemgr.cpp index 28c35f7..5ba2edf 100644 --- a/game_shared/voice_gamemgr.cpp +++ b/game_shared/voice_gamemgr.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -146,6 +146,23 @@ void CVoiceGameMgr::ClientConnected(edict_t *pEdict) g_SentBanMasks[index].Init(0); } +// Called to determine if the Receiver has muted (blocked) the Sender +// Returns true if the receiver has blocked the sender +bool CVoiceGameMgr::PlayerHasBlockedPlayer(CBasePlayer *pReceiver, CBasePlayer *pSender) +{ + int iReceiverIndex, iSenderIndex; + + if ( !pReceiver || !pSender ) + return false; + + iReceiverIndex = pReceiver->entindex() - 1; + iSenderIndex = pSender->entindex() - 1; + + if ( iReceiverIndex < 0 || iReceiverIndex >= m_nMaxPlayers || iSenderIndex < 0 || iSenderIndex >= m_nMaxPlayers ) + return false; + + return ( g_BanMasks[iReceiverIndex][iSenderIndex] ? true : false ); +} bool CVoiceGameMgr::ClientCommand(CBasePlayer *pPlayer, const char *cmd) { @@ -176,7 +193,7 @@ bool CVoiceGameMgr::ClientCommand(CBasePlayer *pPlayer, const char *cmd) } // Force it to update the masks now. - UpdateMasks(); + //UpdateMasks(); return true; } else if(stricmp(cmd, "VModEnable") == 0 && CMD_ARGC() >= 2) @@ -184,7 +201,7 @@ bool CVoiceGameMgr::ClientCommand(CBasePlayer *pPlayer, const char *cmd) VoiceServerDebug( "CVoiceGameMgr::ClientCommand: VModEnable (%d)\n", !!atoi(CMD_ARGV(1)) ); g_PlayerModEnable[playerClientIndex] = !!atoi(CMD_ARGV(1)); g_bWantModEnable[playerClientIndex] = false; - UpdateMasks(); + //UpdateMasks(); return true; } else diff --git a/game_shared/voice_gamemgr.h b/game_shared/voice_gamemgr.h index 21e8f51..9605c5c 100644 --- a/game_shared/voice_gamemgr.h +++ b/game_shared/voice_gamemgr.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -55,6 +55,10 @@ public: // Returns true if it handled the command. bool ClientCommand(CBasePlayer *pPlayer, const char *cmd); + // Called to determine if the Receiver has muted (blocked) the Sender + // Returns true if the receiver has blocked the sender + bool PlayerHasBlockedPlayer(CBasePlayer *pReceiver, CBasePlayer *pSender); + private: diff --git a/game_shared/voice_status.cpp b/game_shared/voice_status.cpp index 2452ccc..e5378d0 100644 --- a/game_shared/voice_status.cpp +++ b/game_shared/voice_status.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -9,6 +9,9 @@ #if defined( DMC_BUILD ) #include "../dmc/cl_dll/hud.h" #include "../dmc/cl_dll/cl_util.h" +#elif defined( RICOCHET_BUILD ) + #include "../ricochet/cl_dll/hud.h" + #include "../ricochet/cl_dll/cl_util.h" #else #include "../cl_dll/hud.h" #include "../cl_dll/cl_util.h" @@ -22,6 +25,10 @@ #include "../dmc/cl_dll/parsemsg.h" #include "../dmc/cl_dll/hud_servers.h" #include "../dmc/cl_dll/demo.h" +#elif defined( RICOCHET_BUILD ) + #include "../ricochet/cl_dll/parsemsg.h" + #include "../ricochet/cl_dll/hud_servers.h" + #include "../ricochet/cl_dll/demo.h" #else #include "../cl_dll/parsemsg.h" #include "../cl_dll/hud_servers.h" @@ -39,10 +46,14 @@ #include "vgui_helpers.h" #include "vgui_mousecode.h" + + using namespace vgui; + extern int cam_thirdperson; + #define VOICE_MODEL_INTERVAL 0.3 #define SCOREBOARD_BLINK_FREQUENCY 0.3 // How often to blink the scoreboard icons. #define SQUELCHOSCILLATE_PER_SECOND 2.0f @@ -140,6 +151,8 @@ CVoiceStatus::CVoiceStatus() memset(m_pBanButtons, 0, sizeof(m_pBanButtons)); m_bServerModEnable = -1; + + m_pchGameDir = NULL; } @@ -164,12 +177,14 @@ CVoiceStatus::~CVoiceStatus() FreeBitmaps(); - if(gEngfuncs.pfnGetGameDirectory()) + if(m_pchGameDir) { if(m_bBanMgrInitialized) { - m_BanMgr.SaveState(gEngfuncs.pfnGetGameDirectory()); + m_BanMgr.SaveState(m_pchGameDir); } + + free(m_pchGameDir); } } @@ -206,6 +221,7 @@ int CVoiceStatus::Init( if(pLabel->m_pLabel = new Label("")) { + pLabel->m_pLabel->setVisible( true ); pLabel->m_pLabel->setFont( Scheme::sf_primary2 ); pLabel->m_pLabel->setTextAlignment( Label::a_east ); pLabel->m_pLabel->setContentAlignment( Label::a_east ); @@ -214,6 +230,7 @@ int CVoiceStatus::Init( if( pLabel->m_pIcon = new ImagePanel( NULL ) ) { + pLabel->m_pIcon->setVisible( true ); pLabel->m_pIcon->setParent( pLabel->m_pBackground ); } @@ -230,6 +247,12 @@ int CVoiceStatus::Init( m_iFlags = HUD_ACTIVE; HOOK_MESSAGE(VoiceMask); HOOK_MESSAGE(ReqState); + + // Cache the game directory for use when we shut down + const char *pchGameDirT = gEngfuncs.pfnGetGameDirectory(); + m_pchGameDir = (char *)malloc(strlen(pchGameDirT) + 1); + strcpy(m_pchGameDir, pchGameDirT); + return 1; } @@ -241,12 +264,12 @@ int CVoiceStatus::VidInit() if( m_pLocalBitmap = vgui_LoadTGA("gfx/vgui/icntlk_pl.tga") ) { - m_pLocalBitmap->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + m_pLocalBitmap->setColor(Color(255,255,255,135)); } if( m_pAckBitmap = vgui_LoadTGA("gfx/vgui/icntlk_sv.tga") ) { - m_pAckBitmap->setColor(Color(255,255,255,1)); // Give just a tiny bit of translucency so software draws correctly. + m_pAckBitmap->setColor(Color(255,255,255,135)); // Give just a tiny bit of translucency so software draws correctly. } m_pLocalLabel->setImage( m_pLocalBitmap ); @@ -308,12 +331,12 @@ void CVoiceStatus::Frame(double frametime) if( m_pHelper->CanShowSpeakerLabels() ) { for( int i=0; i < MAX_VOICE_SPEAKERS; i++ ) - m_Labels[i].m_pLabel->setVisible( m_Labels[i].m_clientindex != -1 ); + m_Labels[i].m_pBackground->setVisible( m_Labels[i].m_clientindex != -1 ); } else { for( int i=0; i < MAX_VOICE_SPEAKERS; i++ ) - m_Labels[i].m_pLabel->setVisible( false ); + m_Labels[i].m_pBackground->setVisible( false ); } for(int i=0; i < VOICE_MAX_PLAYERS; i++) @@ -400,7 +423,7 @@ void CVoiceStatus::UpdateSpeakerStatus(int entindex, qboolean bTalking) { m_bServerAcked = !!bTalking; } - else if(entindex >= 0 && entindex < VOICE_MAX_PLAYERS) + else if(entindex >= 0 && entindex <= VOICE_MAX_PLAYERS) { int iClient = entindex - 1; if(iClient < 0) @@ -430,7 +453,7 @@ void CVoiceStatus::UpdateSpeakerStatus(int entindex, qboolean bTalking) if( pLabel->m_pBackground ) { - pLabel->m_pBackground->setBgColor( color[0], color[1], color[2], 100 ); + pLabel->m_pBackground->setBgColor( color[0], color[1], color[2], 135 ); pLabel->m_pBackground->setParent( *m_pParentPanel ); pLabel->m_pBackground->setVisible( m_pHelper->CanShowSpeakerLabels() ); } @@ -453,7 +476,7 @@ void CVoiceStatus::UpdateSpeakerStatus(int entindex, qboolean bTalking) // If we have a label for this guy, kill it. if(pLabel) { - pLabel->m_pLabel->setVisible(false); + pLabel->m_pBackground->setVisible(false); pLabel->m_clientindex = -1; } } @@ -560,22 +583,18 @@ void CVoiceStatus::UpdateBanButton(int iClient) return; char playerID[16]; - if(!gEngfuncs.GetPlayerUniqueID(iClient+1, playerID)) + extern bool HACK_GetPlayerUniqueID( int iPlayer, char playerID[16] ); + if(!HACK_GetPlayerUniqueID(iClient+1, playerID)) return; // Figure out if it's blinking or not. bool bBlink = fmod(m_BlinkTimer, SCOREBOARD_BLINK_FREQUENCY*2) < SCOREBOARD_BLINK_FREQUENCY; bool bTalking = !!m_VoicePlayers[iClient]; - bool bSquelch = !IsPlayerAudible(iClient+1); bool bBanned = m_BanMgr.GetPlayerBan(playerID); bool bNeverSpoken = !m_VoiceEnabledPlayers[iClient]; // Get the appropriate image to display on the panel. - if (bSquelch) - { - pPanel->setImage(NULL); - } - else if (bBanned) + if (bBanned) { pPanel->setImage(m_pScoreboardBanned); } @@ -695,14 +714,8 @@ void CVoiceStatus::RepositionLabels() if( pLabel->m_clientindex == -1 || !pLabel->m_pLabel ) { - if( pLabel->m_pIcon ) - pLabel->m_pIcon->setVisible( false ); - if( pLabel->m_pBackground ) pLabel->m_pBackground->setVisible( false ); - - if( pLabel->m_pLabel ) - pLabel->m_pLabel->setVisible( false ); continue; } @@ -729,14 +742,8 @@ void CVoiceStatus::RepositionLabels() if( pLabel->m_pIcon ) { pLabel->m_pIcon->setImage( m_pSpeakerLabelIcon ); - pLabel->m_pIcon->setVisible( true ); - pLabel->m_pIcon->setBounds( iconLeft, iconTop, iconWide, iconTall ); } - else - { - pLabel->m_pIcon->setVisible( false ); - } y += bgTall + 2; } @@ -855,16 +862,13 @@ void CVoiceStatus::SetPlayerBlockedState(int iPlayer, bool blocked) } // Squelch or (try to) unsquelch this player. - if (m_AudiblePlayers[iPlayer-1]) + if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) { - if (gEngfuncs.pfnGetCvarFloat("voice_clientdebug")) - { - char str[256]; - sprintf(str, "CVoiceStatus::SetPlayerBlockedState: setting player %d ban to %d\n", iPlayer, !m_BanMgr.GetPlayerBan(playerID)); - gEngfuncs.pfnConsolePrint(str); - } - - m_BanMgr.SetPlayerBan(playerID, !m_BanMgr.GetPlayerBan(playerID)); - UpdateServerState(false); + char str[256]; + sprintf(str, "CVoiceStatus::SetPlayerBlockedState: setting player %d ban to %d\n", iPlayer, !m_BanMgr.GetPlayerBan(playerID)); + gEngfuncs.pfnConsolePrint(str); } + + m_BanMgr.SetPlayerBan( playerID, blocked ); + UpdateServerState(false); } diff --git a/game_shared/voice_status.h b/game_shared/voice_status.h index 31f97c5..7825083 100644 --- a/game_shared/voice_status.h +++ b/game_shared/voice_status.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // @@ -134,7 +134,7 @@ public: // blocks the target client from being heard void SetPlayerBlockedState(int iPlayerIndex, bool blocked); -private: +public: CVoiceLabel* FindVoiceLabel(int clientindex); // Find a CVoiceLabel representing the specified speaker. // Returns NULL if none. @@ -151,7 +151,7 @@ private: void UpdateBanButton(int iClient); -private: +public: enum {MAX_VOICE_SPEAKERS=7}; @@ -209,12 +209,15 @@ public: CVoiceBanMgr m_BanMgr; // Tracks which users we have squelched and don't want to hear. -private: +public: bool m_bBanMgrInitialized; // Labels telling who is speaking. CVoiceLabel m_Labels[MAX_VOICE_SPEAKERS]; + + // Cache the game directory for use when we shut down + char * m_pchGameDir; }; diff --git a/game_shared/voice_vgui_tweakdlg.cpp b/game_shared/voice_vgui_tweakdlg.cpp index 2812b76..b5cde70 100644 --- a/game_shared/voice_vgui_tweakdlg.cpp +++ b/game_shared/voice_vgui_tweakdlg.cpp @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/game_shared/voice_vgui_tweakdlg.h b/game_shared/voice_vgui_tweakdlg.h index 94e5590..857ffd2 100644 --- a/game_shared/voice_vgui_tweakdlg.h +++ b/game_shared/voice_vgui_tweakdlg.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/pm_shared/pm_debug.c b/pm_shared/pm_debug.c index 766ea73..d9d097d 100644 --- a/pm_shared/pm_debug.c +++ b/pm_shared/pm_debug.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -44,21 +44,37 @@ static int PM_boxpnt[6][4] = void PM_ShowClipBox( void ) { #if defined( _DEBUG ) + vec3_t org; + vec3_t offset = { 0, 0, 0 }; + if ( !pmove->runfuncs ) return; // More debugging, draw the particle bbox for player and for the entity we are looking directly at. // aslo prints entity info to the console overlay. - if ( !pmove->server ) - return; + //if ( !pmove->server ) + // return; // Draw entity in center of view // Also draws the normal to the clip plane that intersects our movement ray. Leaves a particle // trail at the intersection point. PM_ViewEntity(); + VectorCopy( pmove->origin, org ); + + if ( pmove->server ) + { + VectorAdd( org, offset, org ); + } + else + { + VectorSubtract( org, offset, org ); + } + // Show our BBOX in particles. - //PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], pmove->origin, 132, 0.1 ); + PM_DrawBBox( pmove->player_mins[pmove->usehull], pmove->player_maxs[pmove->usehull], org, pmove->server ? 132 : 0, 0.1 ); + + PM_ParticleLine( org, org, pmove->server ? 132 : 0, 0.1, 5.0 ); /* { int i; diff --git a/pm_shared/pm_debug.h b/pm_shared/pm_debug.h index 946ece1..c856ea1 100644 --- a/pm_shared/pm_debug.h +++ b/pm_shared/pm_debug.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/pm_shared/pm_defs.h b/pm_shared/pm_defs.h index e9ae314..ccaffb0 100644 --- a/pm_shared/pm_defs.h +++ b/pm_shared/pm_defs.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -28,8 +28,9 @@ #define PM_WORLD_ONLY 0x00000008 // Only trace against the world // Values for flags parameter of PM_TraceLine -#define PM_TRACELINE_ANYVISIBLE 0 -#define PM_TRACELINE_PHYSENTSONLY 1 +#define PM_TRACELINE_PHYSENTSONLY 0 +#define PM_TRACELINE_ANYVISIBLE 1 + #include "pm_info.h" @@ -83,9 +84,8 @@ typedef struct physent_s vec3_t vuser4; } physent_t; -typedef struct playermove_s playermove_t; -struct playermove_s +typedef struct playermove_s { int player_index; // So we don't try to run the PM_CheckStuck nudging too quickly. qboolean server; // For debugging, are we running physics code on server side? @@ -217,6 +217,6 @@ struct playermove_s pmtrace_t (*PM_PlayerTraceEx) (float *start, float *end, int traceFlags, int (*pfnIgnore)( physent_t *pe ) ); int (*PM_TestPlayerPositionEx) (float *pos, pmtrace_t *ptrace, int (*pfnIgnore)( physent_t *pe ) ); struct pmtrace_s *(*PM_TraceLineEx)( float *start, float *end, int flags, int usehulll, int (*pfnIgnore)( physent_t *pe ) ); -}; +} playermove_t; #endif diff --git a/pm_shared/pm_info.h b/pm_shared/pm_info.h index c3a9f36..ddb9528 100644 --- a/pm_shared/pm_info.h +++ b/pm_shared/pm_info.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -15,7 +15,9 @@ // Physics info string definition #if !defined( PM_INFOH ) #define PM_INFOH +#ifdef _WIN32 #pragma once +#endif #define MAX_PHYSINFO_STRING 256 diff --git a/pm_shared/pm_materials.h b/pm_shared/pm_materials.h index 88bba3b..5dba498 100644 --- a/pm_shared/pm_materials.h +++ b/pm_shared/pm_materials.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/pm_shared/pm_math.c b/pm_shared/pm_math.c index b1b7625..a5d78dc 100644 --- a/pm_shared/pm_math.c +++ b/pm_shared/pm_math.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -221,7 +221,8 @@ void InterpolateAngles( float *start, float *end, float *output, float frac ) } NormalizeAngles( output ); -} +} + /* =================== @@ -229,7 +230,7 @@ AngleBetweenVectors =================== */ -float AngleBetweenVectors( vec3_t v1, vec3_t v2 ) +float AngleBetweenVectors( const vec3_t v1, const vec3_t v2 ) { float angle; float l1 = Length( v1 ); @@ -244,6 +245,7 @@ float AngleBetweenVectors( vec3_t v1, vec3_t v2 ) return angle; } + void VectorTransform (const vec3_t in1, float in2[3][4], vec3_t out) { out[0] = DotProduct(in1, in2[0]) + in2[0][3]; @@ -309,9 +311,8 @@ double sqrt(double x); float Length(const vec3_t v) { int i; - float length; - - length = 0; + float length = 0.0f; + for (i=0 ; i< 3 ; i++) length += v[i]*v[i]; length = sqrt (length); // FIXME @@ -319,6 +320,13 @@ float Length(const vec3_t v) return length; } +float Distance(const vec3_t v1, const vec3_t v2) +{ + vec3_t d; + VectorSubtract(v2,v1,d); + return Length(d); +} + float VectorNormalize (vec3_t v) { float length, ilength; diff --git a/pm_shared/pm_movevars.h b/pm_shared/pm_movevars.h index 0c5d323..e625b82 100644 --- a/pm_shared/pm_movevars.h +++ b/pm_shared/pm_movevars.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/pm_shared/pm_shared.c b/pm_shared/pm_shared.c index d31a942..dfd0a0d 100644 --- a/pm_shared/pm_shared.c +++ b/pm_shared/pm_shared.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -29,21 +29,12 @@ #ifdef CLIENT_DLL // Spectator Mode - #include "..\common\hltv.h" - float vecNewViewAngles[3]; - float vecNewViewOrigin[3]; - int iHasNewViewAngles; - int iHasNewViewOrigin; - int iIsSpectator; - unsigned int uiDirectorFlags; - int iJumpSpectator; float vJumpOrigin[3]; float vJumpAngles[3]; #endif static int pm_shared_initialized = 0; -void InterpolateAngles( float *start, float *end, float *output, float frac ); #pragma warning( disable : 4305 ) @@ -1749,42 +1740,6 @@ int PM_CheckStuck (void) return 1; } -#define CHASE_DISTANCE 112 // Desired distance from target -#define CHASE_PADDING 4 // Minimum allowable distance between the view and a solid face - -// Get the origin of the Observer based around the target's position and angles -void GetChaseOrigin( vec3_t targetangles, int iTargetIndex, vec3_t offset, vec3_t *returnvec ) -{ - vec3_t forward; - vec3_t vecEnd; - vec3_t vecStart; - struct pmtrace_s *trace; - physent_t *target; - - target = &(pmove->physents[ iTargetIndex ]); - - // Trace back from the target using the player's view angles - AngleVectors(targetangles, forward, NULL, NULL); - - // Without view_ofs, just guess at adding 28 (standing player) to the origin to get the eye-height - VectorCopy( target->origin, vecStart ); - vecStart[2] += 28; - VectorMA(offset, CHASE_DISTANCE, forward, vecEnd); - VectorSubtract( vecStart, vecEnd, vecEnd ); - - trace = pmove->PM_TraceLine( vecStart, vecEnd, 0, 2, iTargetIndex ); - - // Return the position - VectorMA( trace->endpos, CHASE_PADDING, trace->plane.normal, *returnvec ); - -#ifdef CLIENT_DLL - // pmove->Con_NPrintf( 9, "vecStart %f %f %f.\n", vecStart[0], vecStart[1], vecStart[2] ); - // pmove->Con_NPrintf( 10, " vecEnd %f %f %f.\n", vecEnd[0], vecEnd[1], vecEnd[2] ); - // pmove->Con_NPrintf( 11, " EndPos %f %f %f.\n", trace->endpos[0], trace->endpos[1], trace->endpos[2] ); -#endif -} - - /* =============== PM_SpectatorMove @@ -1800,175 +1755,16 @@ void PM_SpectatorMove (void) float fmove, smove; vec3_t wishdir; float wishspeed; - static vec3_t lastAngle; - -#ifdef CLIENT_DLL - vec3_t tempVec; - pmtrace_t tr; - - if ( pmove->runfuncs ) - { - // Set spectator flag - iIsSpectator = SPEC_IS_SPECTATOR; - } -#endif - - // Are we not roaming and locked onto a target? - if ( (pmove->iuser1 != OBS_ROAMING) && pmove->iuser2 ) - { - vec3_t vecViewAngle; - vec3_t vecNewOrg; - vec3_t vecOffset; - - int target1,target2 = 0; - - // Find the client this player's targeting - for (target1 = 0; target1 < pmove->numphysent; target1++) - { - if ( pmove->physents[target1].info == pmove->iuser2 ) - break; - } - - if (target1 == pmove->numphysent) - { -#ifdef CLIENT_DLL - if ( pmove->runfuncs ) - { - // Force the client to keep last know view - VectorCopy( vecNewViewOrigin, pmove->origin ); - VectorCopy( vecNewViewAngles, pmove->angles ); - VectorCopy( vec3_origin, pmove->velocity ); - iHasNewViewAngles = true; - iHasNewViewOrigin = true; - } -#endif - return; // if we don't find the observers primary target, stop whole function - } - - VectorCopy( vec3_origin, vecOffset ); - + // this routine keeps track of the spectators psoition + // there a two different main move types : track player or moce freely (OBS_ROAMING) + // doesn't need excate track position, only to generate PVS, so just copy + // targets position and real view position is calculated on client (saves server CPU) - if ( (pmove->iuser1 == OBS_DIRECTED) && pmove->iuser3 ) - { - // find cameras seconds target - // Find the client this player's targeting - for (target2 = 0; target2 < pmove->numphysent; target2++) - { - if ( pmove->physents[target2].info == pmove->iuser3 ) - break; - } - - if (target2 == pmove->numphysent) - { - target2 = -1; // if we didn't found the second target - } - } - - // Calculate a camera position based upon the target's origin and angles - if ( (pmove->iuser1 == OBS_CHASE_LOCKED) ) - { - // use the targets directon as viewangles - VectorCopy( pmove->physents[target1].angles, vecViewAngle ); - vecViewAngle[0] = 0; - -#ifdef CLIENT_DLL - if ( pmove->runfuncs ) - { - // Force the client to start smoothing both the spectator's origin and angles - iIsSpectator |= (SPEC_SMOOTH_ANGLES | SPEC_SMOOTH_ORIGIN); - } -#endif - } - else if ( pmove->iuser1 == OBS_DIRECTED ) - { - // use angles between primary and second target as viewangle - // tracking also seconds target -#ifdef CLIENT_DLL - if ( target2 > 0 ) - { - VectorSubtract( pmove->physents[target2].origin, pmove->physents[target1].origin, vecViewAngle ); - VectorAngles(vecViewAngle, vecViewAngle ); - vecViewAngle[0] = -vecViewAngle[0]; - - if ( uiDirectorFlags & DRC_FLAG_DRAMATIC ) - vecViewAngle[0]-=12.5f; - else - vecViewAngle[0]+=12.5f; - - if ( uiDirectorFlags & DRC_FLAG_SIDE ) - vecViewAngle[1]+=22.5f; - else - vecViewAngle[1]-=22.5f; - - GetChaseOrigin( vecViewAngle, target1, vecOffset, &tempVec); - - tr = *(pmove->PM_TraceLine( (float *)&tempVec, (float *)&pmove->physents[target2].origin, PM_TRACELINE_PHYSENTSONLY, 2 /*point sized hull*/, -1 )); - - // if second target isn't viewable from this side, choose the other side - if ( tr.fraction != 1.0 ) - { - if ( uiDirectorFlags & DRC_FLAG_SIDE ) - vecViewAngle[1]-=2.0f * 22.5f; - else - vecViewAngle[1]+=2.0f * 22.5f; - } - } - else - { - - if ( target2 == -1 ) - { - // we had a second target but it disappeard, keep last known view angles - VectorCopy( vecNewViewAngles, vecViewAngle ); - - } - else - { // we have no second target, choose view direction based on - // entity/player direction - VectorCopy( pmove->physents[target1].angles, vecViewAngle ); - vecViewAngle[1] *= -0.5f; - } - } - - - if ( pmove->runfuncs ) - { - // Force the client to start smoothing both the spectator's origin and angles - iIsSpectator |= (SPEC_SMOOTH_ANGLES | SPEC_SMOOTH_ORIGIN); - InterpolateAngles( lastAngle, vecViewAngle, vecViewAngle, 0.1f ); - VectorCopy( vecViewAngle, lastAngle ); - } -#endif - } - else - { - // Freelooking around the target - VectorCopy( pmove->angles, vecViewAngle ); - } - - GetChaseOrigin( vecViewAngle, target1, vecOffset, &vecNewOrg); - VectorCopy( vecNewOrg, pmove->origin ); - VectorCopy( vecViewAngle, pmove->angles ); - VectorCopy( vec3_origin, pmove->velocity ); - -#ifdef CLIENT_DLL - if ( pmove->runfuncs ) - { - // Copy the desired angles into the client global var so we can force them to the player's view - VectorCopy( pmove->angles, vecNewViewAngles ); - iHasNewViewAngles = true; - VectorCopy( pmove->origin, vecNewViewOrigin ); - iHasNewViewOrigin = true; - } -#endif - } - else + if ( pmove->iuser1 == OBS_ROAMING) { - // Move around in normal spectator method - // friction - #ifdef CLIENT_DLL - // in free roam mode, spectator can jump within level +#ifdef CLIENT_DLL + // jump only in roaming mode if ( iJumpSpectator ) { VectorCopy( vJumpOrigin, pmove->origin ); @@ -1978,6 +1774,7 @@ void PM_SpectatorMove (void) return; } #endif + // Move around in normal spectator method speed = Length (pmove->velocity); if (speed < 1) @@ -2040,7 +1837,33 @@ void PM_SpectatorMove (void) // move VectorMA (pmove->origin, pmove->frametime, pmove->velocity, pmove->origin); + } + else + { + // all other modes just track some kind of target, so spectator PVS = target PVS + int target; + + // no valid target ? + if ( pmove->iuser2 <= 0) + return; + + // Find the client this player's targeting + for (target = 0; target < pmove->numphysent; target++) + { + if ( pmove->physents[target].info == pmove->iuser2 ) + break; + } + + if (target == pmove->numphysent) + return; + + // use targets position as own origin for PVS + VectorCopy( pmove->physents[target].angles, pmove->angles ); + VectorCopy( pmove->physents[target].origin, pmove->origin ); + + // no velocity + VectorCopy( vec3_origin, pmove->velocity ); } } @@ -3104,15 +2927,6 @@ void PM_PlayerMove ( qboolean server ) // PM_ShowClipBox(); -#ifdef CLIENT_DLL - if ( pmove->runfuncs ) - { - iIsSpectator = false; - iHasNewViewAngles = false; - iHasNewViewOrigin = false; - } -#endif - // Special handling for spectator and observers. (iuser1 is set if the player's in observer mode) if ( pmove->spectator || pmove->iuser1 > 0 ) { @@ -3486,7 +3300,7 @@ void PM_Move ( struct playermove_s *ppmove, int server ) } } -int PM_GetInfo( int ent ) +int PM_GetVisEntInfo( int ent ) { if ( ent >= 0 && ent <= pmove->numvisent ) { @@ -3495,6 +3309,15 @@ int PM_GetInfo( int ent ) return -1; } +int PM_GetPhysEntInfo( int ent ) +{ + if ( ent >= 0 && ent <= pmove->numphysent) + { + return pmove->physents[ ent ].info; + } + return -1; +} + void PM_Init( struct playermove_s *ppmove ) { assert( !pm_shared_initialized ); diff --git a/pm_shared/pm_shared.h b/pm_shared/pm_shared.h index 74e2a19..c2d5285 100644 --- a/pm_shared/pm_shared.h +++ b/pm_shared/pm_shared.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -24,16 +24,13 @@ void PM_Init( struct playermove_s *ppmove ); void PM_Move ( struct playermove_s *ppmove, int server ); char PM_FindTextureType( char *name ); -// Spectator flags -#define SPEC_IS_SPECTATOR (1<<0) -#define SPEC_SMOOTH_ANGLES (1<<1) -#define SPEC_SMOOTH_ORIGIN (1<<2) - // Spectator Movement modes (stored in pev->iuser1, so the physics code can get at them) #define OBS_NONE 0 #define OBS_CHASE_LOCKED 1 #define OBS_CHASE_FREE 2 #define OBS_ROAMING 3 -#define OBS_DIRECTED 4 +#define OBS_IN_EYE 4 +#define OBS_MAP_FREE 5 +#define OBS_MAP_CHASE 6 #endif \ No newline at end of file diff --git a/utils/bspinfo/bspinfo.c b/utils/bspinfo/bspinfo.c index 6707d9a..7a7d97a 100644 --- a/utils/bspinfo/bspinfo.c +++ b/utils/bspinfo/bspinfo.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/bspfile.c b/utils/common/bspfile.c index d9c3ae6..c118701 100644 --- a/utils/common/bspfile.c +++ b/utils/common/bspfile.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/bspfile.h b/utils/common/bspfile.h index 5f7ec3e..96dd94b 100644 --- a/utils/common/bspfile.h +++ b/utils/common/bspfile.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/cmdlib.c b/utils/common/cmdlib.c index 2675ac0..535aced 100644 --- a/utils/common/cmdlib.c +++ b/utils/common/cmdlib.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/cmdlib.h b/utils/common/cmdlib.h index b04833a..2a5eb30 100644 --- a/utils/common/cmdlib.h +++ b/utils/common/cmdlib.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/lbmlib.c b/utils/common/lbmlib.c index c03c1c0..578707a 100644 --- a/utils/common/lbmlib.c +++ b/utils/common/lbmlib.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/lbmlib.h b/utils/common/lbmlib.h index 104f600..8aaf177 100644 --- a/utils/common/lbmlib.h +++ b/utils/common/lbmlib.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/mathlib.c b/utils/common/mathlib.c index f2dfc1e..4f5e6bf 100644 --- a/utils/common/mathlib.c +++ b/utils/common/mathlib.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/mathlib.h b/utils/common/mathlib.h index 4d13cf0..6c29e56 100644 --- a/utils/common/mathlib.h +++ b/utils/common/mathlib.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/movie.h b/utils/common/movie.h index 058bc9d..a68b807 100644 --- a/utils/common/movie.h +++ b/utils/common/movie.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/polylib.c b/utils/common/polylib.c index 980488d..844bfea 100644 --- a/utils/common/polylib.c +++ b/utils/common/polylib.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/polylib.h b/utils/common/polylib.h index bbcb2fd..ed69f0b 100644 --- a/utils/common/polylib.h +++ b/utils/common/polylib.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/scriplib.c b/utils/common/scriplib.c index d15e09f..c22476c 100644 --- a/utils/common/scriplib.c +++ b/utils/common/scriplib.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/scriplib.h b/utils/common/scriplib.h index 08708be..0a6f56d 100644 --- a/utils/common/scriplib.h +++ b/utils/common/scriplib.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/threads.c b/utils/common/threads.c index a3d11d3..acda957 100644 --- a/utils/common/threads.c +++ b/utils/common/threads.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/threads.h b/utils/common/threads.h index 9333f32..2c91511 100644 --- a/utils/common/threads.h +++ b/utils/common/threads.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/trilib.c b/utils/common/trilib.c index 85ea5b0..bdb75fb 100644 --- a/utils/common/trilib.c +++ b/utils/common/trilib.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/trilib.h b/utils/common/trilib.h index dc5f6bf..0927166 100644 --- a/utils/common/trilib.h +++ b/utils/common/trilib.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/wadlib.c b/utils/common/wadlib.c index da41f31..19faf60 100644 --- a/utils/common/wadlib.c +++ b/utils/common/wadlib.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/common/wadlib.h b/utils/common/wadlib.h index 9a43fd6..5b83618 100644 --- a/utils/common/wadlib.h +++ b/utils/common/wadlib.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/light/light.c b/utils/light/light.c index 99b9da2..cb35f56 100644 --- a/utils/light/light.c +++ b/utils/light/light.c @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/light/light.dsp b/utils/light/light.dsp index 33b3df3..15fcff9 100644 --- a/utils/light/light.dsp +++ b/utils/light/light.dsp @@ -39,7 +39,6 @@ RSC=rc.exe # PROP Use_Debug_Libraries 0 # PROP Output_Dir ".\Release" # PROP Intermediate_Dir ".\Release" -# PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /c # ADD CPP /nologo /GX /O2 /I "..\common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /YX /FD /c diff --git a/utils/light/light.h b/utils/light/light.h index 0db1e6a..0d645a0 100644 --- a/utils/light/light.h +++ b/utils/light/light.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/light/ltface.c b/utils/light/ltface.c index 09ddc34..7eff249 100644 --- a/utils/light/ltface.c +++ b/utils/light/ltface.c @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/light/trace.c b/utils/light/trace.c index e8f6404..9889ce9 100644 --- a/utils/light/trace.c +++ b/utils/light/trace.c @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/makefont/makefont.cpp b/utils/makefont/makefont.cpp index 3cad5b5..3af09e6 100644 --- a/utils/makefont/makefont.cpp +++ b/utils/makefont/makefont.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/makels/makels.cpp b/utils/makels/makels.cpp index 2a28e2b..514f15b 100644 --- a/utils/makels/makels.cpp +++ b/utils/makels/makels.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/mdlviewer/mdlviewer.cpp b/utils/mdlviewer/mdlviewer.cpp index 739bf19..d7e4081 100644 --- a/utils/mdlviewer/mdlviewer.cpp +++ b/utils/mdlviewer/mdlviewer.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * ****/ // updates: diff --git a/utils/mdlviewer/mdlviewer.dsp b/utils/mdlviewer/mdlviewer.dsp index 9214974..3d53807 100644 --- a/utils/mdlviewer/mdlviewer.dsp +++ b/utils/mdlviewer/mdlviewer.dsp @@ -42,7 +42,7 @@ RSC=rc.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c -# ADD CPP /nologo /W3 /GX /O2 /I "..\common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c +# ADD CPP /nologo /W3 /GX /O2 /I "..\common" /I "..\..\common" /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /c # ADD BASE RSC /l 0x409 /d "NDEBUG" # ADD RSC /l 0x409 /d "NDEBUG" BSC32=bscmake.exe @@ -66,7 +66,7 @@ LINK32=link.exe # PROP Ignore_Export_Lib 0 # PROP Target_Dir "" # ADD BASE CPP /nologo /W3 /Gm /GX /ZI /Od /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /YX /FD /GZ /c -# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /GZ /c +# ADD CPP /nologo /W3 /Gm /GX /ZI /Od /I "..\common" /I "..\..\common" /D "WIN32" /D "_DEBUG" /D "_CONSOLE" /D "_MBCS" /FR /YX /FD /GZ /c # ADD BASE RSC /l 0x409 /d "_DEBUG" # ADD RSC /l 0x409 /d "_DEBUG" BSC32=bscmake.exe diff --git a/utils/mdlviewer/mdlviewer.dsw b/utils/mdlviewer/mdlviewer.dsw index e0d6570..6cbc6bb 100644 --- a/utils/mdlviewer/mdlviewer.dsw +++ b/utils/mdlviewer/mdlviewer.dsw @@ -3,14 +3,10 @@ Microsoft Developer Studio Workspace File, Format Version 6.00 ############################################################################### -Project: "mdlviewer"=.\mdlviewer.dsp - Package Owner=<4> +Project: "mdlviewer"=".\mdlviewer.dsp" - Package Owner=<4> Package=<5> {{{ - begin source code control - "$/SDKSrc/Tools/utils/mdlviewer", AIHBAAAA - . - end source code control }}} Package=<4> @@ -23,10 +19,6 @@ Global: Package=<5> {{{ - begin source code control - "$/SDKSrc/Tools/utils/mdlviewer", AIHBAAAA - . - end source code control }}} Package=<3> diff --git a/utils/mdlviewer/mdlviewer.h b/utils/mdlviewer/mdlviewer.h index 9434c11..57f77b1 100644 --- a/utils/mdlviewer/mdlviewer.h +++ b/utils/mdlviewer/mdlviewer.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/mdlviewer/studio_render.cpp b/utils/mdlviewer/studio_render.cpp index 18689b6..6bbac2e 100644 --- a/utils/mdlviewer/studio_render.cpp +++ b/utils/mdlviewer/studio_render.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/mdlviewer/studio_utils.cpp b/utils/mdlviewer/studio_utils.cpp index 2def395..bee352a 100644 --- a/utils/mdlviewer/studio_utils.cpp +++ b/utils/mdlviewer/studio_utils.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/mkmovie/mkmovie.c b/utils/mkmovie/mkmovie.c index 989e431..303315a 100644 --- a/utils/mkmovie/mkmovie.c +++ b/utils/mkmovie/mkmovie.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/procinfo/lib/win32_vc6/procinfo.lib b/utils/procinfo/lib/win32_vc6/procinfo.lib index 536951d9e420bac9a83513bf6867609fd764086f..d5d54ab32f343c981ac42a6528dea6e529390c40 100644 GIT binary patch delta 419 zcmZ1?+#x(clEciv($LJ<#A2f23IzM&G6Y*ueDX9#Qxljv1yo>YU|?ourl4SGX>1OW zN#OEfF5bl*RbKYrAgO zC#nn#4GgUZO0qQLrNzv$mICA=xKXuk*4Gx9q9w3Juh}jrad`t{2O^g}3(L@Xx+E7J|Ef{>!L@XJ6&_v7` N+&3@gU}R+E0suzqc3}Vj delta 430 zcmeAWULrg}lEc){#K_Xj%xt3K3IzM&G6Y*ueDX9#Qxljv1yo>YU;xytpkN3z10s{a z<-^FqptNeMMcL;6jNFWZ2N)O_444@hJ^(QYuuqm|mS#0$VPNpvY|YHc#Q8=bI4ry~ z^~KJ~{;Z<)ueo|+0|6FA#s@I;O@IPcFg^J$tA=O+&{PE=W@DHNXRX-G z#`cqu=KxTE9f(`zOuaIhn?oVG5h}pOpyFd diff --git a/utils/qcsg/gldraw.c b/utils/qcsg/gldraw.c index dbbfb4a..0b61f01 100644 --- a/utils/qcsg/gldraw.c +++ b/utils/qcsg/gldraw.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/qcsg/hullfile.c b/utils/qcsg/hullfile.c index 848e215..892ec3c 100644 --- a/utils/qcsg/hullfile.c +++ b/utils/qcsg/hullfile.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/qcsg/map.c b/utils/qcsg/map.c index c00231d..f31d1d5 100644 --- a/utils/qcsg/map.c +++ b/utils/qcsg/map.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/qcsg/qcsg.c b/utils/qcsg/qcsg.c index 8f4621c..f596978 100644 --- a/utils/qcsg/qcsg.c +++ b/utils/qcsg/qcsg.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/qcsg/textures.c b/utils/qcsg/textures.c index 6b64083..7c03896 100644 --- a/utils/qcsg/textures.c +++ b/utils/qcsg/textures.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/qlumpy/qlumpy.c b/utils/qlumpy/qlumpy.c index d1a4315..9a6d645 100644 --- a/utils/qlumpy/qlumpy.c +++ b/utils/qlumpy/qlumpy.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/qlumpy/qlumpy.h b/utils/qlumpy/qlumpy.h index ae371a1..ce695d6 100644 --- a/utils/qlumpy/qlumpy.h +++ b/utils/qlumpy/qlumpy.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/qlumpy/quakegrb.c b/utils/qlumpy/quakegrb.c index 46251bd..82fd130 100644 --- a/utils/qlumpy/quakegrb.c +++ b/utils/qlumpy/quakegrb.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/qrad/lightmap.c b/utils/qrad/lightmap.c index 1676e3b..174ab96 100644 --- a/utils/qrad/lightmap.c +++ b/utils/qrad/lightmap.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -963,6 +963,9 @@ void CreateDirectLights (void) } else { // point down angle + vec3_t vAngles; + GetVectorForKey( e, "angles", vAngles ); + angle = (float)FloatForKey (e, "angle"); if (angle == ANGLE_UP) { @@ -976,12 +979,23 @@ void CreateDirectLights (void) } else { + // if we don't have a specific "angle" use the "angles" YAW + if ( !angle ) + { + angle = vAngles[1]; + } + dl->normal[2] = 0; dl->normal[0] = (float)cos (angle/180*Q_PI); dl->normal[1] = (float)sin (angle/180*Q_PI); } angle = FloatForKey (e, "pitch"); + if ( !angle ) + { + // if we don't have a specific "pitch" use the "angles" PITCH + angle = vAngles[0]; + } dl->normal[2] = (float)sin(angle/180*Q_PI); dl->normal[0] *= (float)cos(angle/180*Q_PI); diff --git a/utils/qrad/qrad.c b/utils/qrad/qrad.c index efcd104..cc6ff0a 100644 --- a/utils/qrad/qrad.c +++ b/utils/qrad/qrad.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/qrad/qrad.h b/utils/qrad/qrad.h index 99c8663..b64aec1 100644 --- a/utils/qrad/qrad.h +++ b/utils/qrad/qrad.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/qrad/trace.c b/utils/qrad/trace.c index 7c1a482..f92633d 100644 --- a/utils/qrad/trace.c +++ b/utils/qrad/trace.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/qrad/vismat.c b/utils/qrad/vismat.c index b8fa98c..7877068 100644 --- a/utils/qrad/vismat.c +++ b/utils/qrad/vismat.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/smdlexp/smdlexp.cpp b/utils/smdlexp/smdlexp.cpp index 2108e2e..623f6ee 100644 --- a/utils/smdlexp/smdlexp.cpp +++ b/utils/smdlexp/smdlexp.cpp @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * ****/ diff --git a/utils/smdlexp/smedefs.h b/utils/smdlexp/smedefs.h index a551eb9..f52ff49 100644 --- a/utils/smdlexp/smedefs.h +++ b/utils/smdlexp/smedefs.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * ****/ diff --git a/utils/sprgen/sprgen.c b/utils/sprgen/sprgen.c index d5db968..1ef2b60 100644 --- a/utils/sprgen/sprgen.c +++ b/utils/sprgen/sprgen.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/sprgen/spritegn.h b/utils/sprgen/spritegn.h index b4c666a..215788d 100644 --- a/utils/sprgen/spritegn.h +++ b/utils/sprgen/spritegn.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/studiomdl/bmpread.c b/utils/studiomdl/bmpread.c index 8b96f8b..813207f 100644 --- a/utils/studiomdl/bmpread.c +++ b/utils/studiomdl/bmpread.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/studiomdl/studiomdl.c b/utils/studiomdl/studiomdl.c index 0f5496b..350470a 100644 --- a/utils/studiomdl/studiomdl.c +++ b/utils/studiomdl/studiomdl.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. @@ -35,6 +35,8 @@ #include "..\..\dlls\activitymap.h" +static int force_powerof2_textures = 0; + void Sys_Error (char *error, ...) {}; void clip_rotations( vec3_t rot ); @@ -1464,6 +1466,52 @@ void Grab_BMP ( char *filename, s_texture_t *ptexture ) } +#define MIN_DIMENSION 8 +#define MAX_DIMENSION 512 + +int GetBestPowerOf2( int value ) +{ + int i; + int power = MIN_DIMENSION; + + for(i=0;i<32;i++) + { + if ( (1< MAX_DIMENSION ) + continue; + + power=(1<=value) + { + break; + } + } + + return power; +} + +int GetSkinWidth( int rawsize ) +{ + if ( !force_powerof2_textures ) + { + return (int)( rawsize + 3) & ~3; + } + + return GetBestPowerOf2( rawsize ); +} + +int GetSkinHeight( int rawsize ) +{ + if ( !force_powerof2_textures ) + { + return ( rawsize ); + } + + return GetBestPowerOf2( rawsize ); +} + void ResizeTexture( s_texture_t *ptexture ) { int i, j, s, t; @@ -1475,8 +1523,9 @@ void ResizeTexture( s_texture_t *ptexture ) ptexture->skintop = ptexture->min_t; ptexture->skinleft = ptexture->min_s; - ptexture->skinwidth = (int)((ptexture->max_s - ptexture->min_s) + 1 + 3) & ~3; - ptexture->skinheight = (int)(ptexture->max_t - ptexture->min_t) + 1; + + ptexture->skinwidth = GetSkinWidth( ptexture->max_s - ptexture->min_s + 1); + ptexture->skinheight = GetSkinHeight(ptexture->max_t - ptexture->min_t + 1); ptexture->size = ptexture->skinwidth * ptexture->skinheight + 256 * 3; @@ -3252,7 +3301,7 @@ int main (int argc, char **argv) gamma = 1.8; if (argc == 1) - Error ("usage: studiomdl [-t texture] -r(tag reversed) -n(tag bad normals) -f(flip all triangles) [-a normal_blend_angle] -h(dump hboxes) -i(ignore warnings) [-g max_sequencegroup_size(K)] file.qc"); + Error ("usage: studiomdl [-t texture] -r(tag reversed) -n(tag bad normals) -f(flip all triangles) [-a normal_blend_angle] -h(dump hboxes) -i(ignore warnings) -p(force power of 2 textures) [-g max_sequencegroup_size(K)] file.qc"); for (i = 1; i < argc - 1; i++) { if (argv[i][0] == '-') { @@ -3288,6 +3337,10 @@ int main (int argc, char **argv) i++; maxseqgroupsize = 1024 * atoi( argv[i] ); break; + case 'p': + case '2': + force_powerof2_textures = 1; + break; case 'i': ignore_warnings = 1; break; diff --git a/utils/studiomdl/studiomdl.h b/utils/studiomdl/studiomdl.h index 0ebe9f1..cfc3f7f 100644 --- a/utils/studiomdl/studiomdl.h +++ b/utils/studiomdl/studiomdl.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/studiomdl/tristrip.c b/utils/studiomdl/tristrip.c index 2a7fbba..2205f1e 100644 --- a/utils/studiomdl/tristrip.c +++ b/utils/studiomdl/tristrip.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/studiomdl/write.c b/utils/studiomdl/write.c index 453bc82..30986d7 100644 --- a/utils/studiomdl/write.c +++ b/utils/studiomdl/write.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/vgui/include/VGUI.h b/utils/vgui/include/VGUI.h index c337f1f..acbd542 100644 --- a/utils/vgui/include/VGUI.h +++ b/utils/vgui/include/VGUI.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_ActionSignal.h b/utils/vgui/include/VGUI_ActionSignal.h index 0b9a047..530f468 100644 --- a/utils/vgui/include/VGUI_ActionSignal.h +++ b/utils/vgui/include/VGUI_ActionSignal.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_App.h b/utils/vgui/include/VGUI_App.h index 46f9e0f..a13cdb5 100644 --- a/utils/vgui/include/VGUI_App.h +++ b/utils/vgui/include/VGUI_App.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Bitmap.h b/utils/vgui/include/VGUI_Bitmap.h index 1ad5a93..04de306 100644 --- a/utils/vgui/include/VGUI_Bitmap.h +++ b/utils/vgui/include/VGUI_Bitmap.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_BitmapTGA.h b/utils/vgui/include/VGUI_BitmapTGA.h index 8e83ff8..f0ecedb 100644 --- a/utils/vgui/include/VGUI_BitmapTGA.h +++ b/utils/vgui/include/VGUI_BitmapTGA.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Border.h b/utils/vgui/include/VGUI_Border.h index 194f59b..416b878 100644 --- a/utils/vgui/include/VGUI_Border.h +++ b/utils/vgui/include/VGUI_Border.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_BorderLayout.h b/utils/vgui/include/VGUI_BorderLayout.h index 56a5621..2d7678e 100644 --- a/utils/vgui/include/VGUI_BorderLayout.h +++ b/utils/vgui/include/VGUI_BorderLayout.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_BorderPair.h b/utils/vgui/include/VGUI_BorderPair.h index d7014d9..82318d4 100644 --- a/utils/vgui/include/VGUI_BorderPair.h +++ b/utils/vgui/include/VGUI_BorderPair.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_BuildGroup.h b/utils/vgui/include/VGUI_BuildGroup.h index f6f0e00..694ac5c 100644 --- a/utils/vgui/include/VGUI_BuildGroup.h +++ b/utils/vgui/include/VGUI_BuildGroup.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Button.h b/utils/vgui/include/VGUI_Button.h index c96b7c7..7885192 100644 --- a/utils/vgui/include/VGUI_Button.h +++ b/utils/vgui/include/VGUI_Button.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_ButtonController.h b/utils/vgui/include/VGUI_ButtonController.h index 63285e2..f4f6d10 100644 --- a/utils/vgui/include/VGUI_ButtonController.h +++ b/utils/vgui/include/VGUI_ButtonController.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_ButtonGroup.h b/utils/vgui/include/VGUI_ButtonGroup.h index 445ccd8..5f14bb4 100644 --- a/utils/vgui/include/VGUI_ButtonGroup.h +++ b/utils/vgui/include/VGUI_ButtonGroup.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_ChangeSignal.h b/utils/vgui/include/VGUI_ChangeSignal.h index bcf0388..85d6d27 100644 --- a/utils/vgui/include/VGUI_ChangeSignal.h +++ b/utils/vgui/include/VGUI_ChangeSignal.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_CheckButton.h b/utils/vgui/include/VGUI_CheckButton.h index cb8b8c8..a3d7169 100644 --- a/utils/vgui/include/VGUI_CheckButton.h +++ b/utils/vgui/include/VGUI_CheckButton.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Color.h b/utils/vgui/include/VGUI_Color.h index 4add478..2ed676a 100644 --- a/utils/vgui/include/VGUI_Color.h +++ b/utils/vgui/include/VGUI_Color.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_ComboKey.h b/utils/vgui/include/VGUI_ComboKey.h index 6f049b6..adc8abf 100644 --- a/utils/vgui/include/VGUI_ComboKey.h +++ b/utils/vgui/include/VGUI_ComboKey.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_ConfigWizard.h b/utils/vgui/include/VGUI_ConfigWizard.h index e2a24de..713ecb4 100644 --- a/utils/vgui/include/VGUI_ConfigWizard.h +++ b/utils/vgui/include/VGUI_ConfigWizard.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Cursor.h b/utils/vgui/include/VGUI_Cursor.h index 0fb31e6..44ae111 100644 --- a/utils/vgui/include/VGUI_Cursor.h +++ b/utils/vgui/include/VGUI_Cursor.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Dar.h b/utils/vgui/include/VGUI_Dar.h index 9ddd016..c0f1990 100644 --- a/utils/vgui/include/VGUI_Dar.h +++ b/utils/vgui/include/VGUI_Dar.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_DataInputStream.h b/utils/vgui/include/VGUI_DataInputStream.h index 3b06c33..dba1acf 100644 --- a/utils/vgui/include/VGUI_DataInputStream.h +++ b/utils/vgui/include/VGUI_DataInputStream.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Desktop.h b/utils/vgui/include/VGUI_Desktop.h index fbad47c..da8e0d0 100644 --- a/utils/vgui/include/VGUI_Desktop.h +++ b/utils/vgui/include/VGUI_Desktop.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_DesktopIcon.h b/utils/vgui/include/VGUI_DesktopIcon.h index 8da21ce..192fef4 100644 --- a/utils/vgui/include/VGUI_DesktopIcon.h +++ b/utils/vgui/include/VGUI_DesktopIcon.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_EditPanel.h b/utils/vgui/include/VGUI_EditPanel.h index 226b302..23e2926 100644 --- a/utils/vgui/include/VGUI_EditPanel.h +++ b/utils/vgui/include/VGUI_EditPanel.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_EtchedBorder.h b/utils/vgui/include/VGUI_EtchedBorder.h index c1a632e..7047c08 100644 --- a/utils/vgui/include/VGUI_EtchedBorder.h +++ b/utils/vgui/include/VGUI_EtchedBorder.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_FileInputStream.h b/utils/vgui/include/VGUI_FileInputStream.h index c5e6f4c..41b6086 100644 --- a/utils/vgui/include/VGUI_FileInputStream.h +++ b/utils/vgui/include/VGUI_FileInputStream.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_FlowLayout.h b/utils/vgui/include/VGUI_FlowLayout.h index 492cf4d..c958565 100644 --- a/utils/vgui/include/VGUI_FlowLayout.h +++ b/utils/vgui/include/VGUI_FlowLayout.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_FocusChangeSignal.h b/utils/vgui/include/VGUI_FocusChangeSignal.h index 16e2e9d..0681f8a 100644 --- a/utils/vgui/include/VGUI_FocusChangeSignal.h +++ b/utils/vgui/include/VGUI_FocusChangeSignal.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_FocusNavGroup.h b/utils/vgui/include/VGUI_FocusNavGroup.h index 5fc8889..b7e8307 100644 --- a/utils/vgui/include/VGUI_FocusNavGroup.h +++ b/utils/vgui/include/VGUI_FocusNavGroup.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Font.h b/utils/vgui/include/VGUI_Font.h index cc63222..822722c 100644 --- a/utils/vgui/include/VGUI_Font.h +++ b/utils/vgui/include/VGUI_Font.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Frame.h b/utils/vgui/include/VGUI_Frame.h index 287dd91..ffe41f8 100644 --- a/utils/vgui/include/VGUI_Frame.h +++ b/utils/vgui/include/VGUI_Frame.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_FrameSignal.h b/utils/vgui/include/VGUI_FrameSignal.h index 28aea25..7deab55 100644 --- a/utils/vgui/include/VGUI_FrameSignal.h +++ b/utils/vgui/include/VGUI_FrameSignal.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_GridLayout.h b/utils/vgui/include/VGUI_GridLayout.h index 6d7c1ca..946159d 100644 --- a/utils/vgui/include/VGUI_GridLayout.h +++ b/utils/vgui/include/VGUI_GridLayout.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_HeaderPanel.h b/utils/vgui/include/VGUI_HeaderPanel.h index 2e65a12..163d88f 100644 --- a/utils/vgui/include/VGUI_HeaderPanel.h +++ b/utils/vgui/include/VGUI_HeaderPanel.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Image.h b/utils/vgui/include/VGUI_Image.h index 6bcca1f..8250a87 100644 --- a/utils/vgui/include/VGUI_Image.h +++ b/utils/vgui/include/VGUI_Image.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_ImagePanel.h b/utils/vgui/include/VGUI_ImagePanel.h index 7d868d0..cb492cd 100644 --- a/utils/vgui/include/VGUI_ImagePanel.h +++ b/utils/vgui/include/VGUI_ImagePanel.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_InputSignal.h b/utils/vgui/include/VGUI_InputSignal.h index cc9d34d..d5fc02a 100644 --- a/utils/vgui/include/VGUI_InputSignal.h +++ b/utils/vgui/include/VGUI_InputSignal.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_InputStream.h b/utils/vgui/include/VGUI_InputStream.h index 34f79e5..141e9e2 100644 --- a/utils/vgui/include/VGUI_InputStream.h +++ b/utils/vgui/include/VGUI_InputStream.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_IntChangeSignal.h b/utils/vgui/include/VGUI_IntChangeSignal.h index 415e03b..9aa4d49 100644 --- a/utils/vgui/include/VGUI_IntChangeSignal.h +++ b/utils/vgui/include/VGUI_IntChangeSignal.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_IntLabel.h b/utils/vgui/include/VGUI_IntLabel.h index cd2c31d..275b3ce 100644 --- a/utils/vgui/include/VGUI_IntLabel.h +++ b/utils/vgui/include/VGUI_IntLabel.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_KeyCode.h b/utils/vgui/include/VGUI_KeyCode.h index b925c6f..f100cd4 100644 --- a/utils/vgui/include/VGUI_KeyCode.h +++ b/utils/vgui/include/VGUI_KeyCode.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Label.h b/utils/vgui/include/VGUI_Label.h index f259e0e..af90e9f 100644 --- a/utils/vgui/include/VGUI_Label.h +++ b/utils/vgui/include/VGUI_Label.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Layout.h b/utils/vgui/include/VGUI_Layout.h index 1bbadbe..081c2ec 100644 --- a/utils/vgui/include/VGUI_Layout.h +++ b/utils/vgui/include/VGUI_Layout.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_LayoutInfo.h b/utils/vgui/include/VGUI_LayoutInfo.h index 2892168..569be76 100644 --- a/utils/vgui/include/VGUI_LayoutInfo.h +++ b/utils/vgui/include/VGUI_LayoutInfo.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_LineBorder.h b/utils/vgui/include/VGUI_LineBorder.h index 439b3e4..1263a46 100644 --- a/utils/vgui/include/VGUI_LineBorder.h +++ b/utils/vgui/include/VGUI_LineBorder.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_ListPanel.h b/utils/vgui/include/VGUI_ListPanel.h index 9c01316..542f5bd 100644 --- a/utils/vgui/include/VGUI_ListPanel.h +++ b/utils/vgui/include/VGUI_ListPanel.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_LoweredBorder.h b/utils/vgui/include/VGUI_LoweredBorder.h index 0a253a2..85ed51c 100644 --- a/utils/vgui/include/VGUI_LoweredBorder.h +++ b/utils/vgui/include/VGUI_LoweredBorder.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Menu.h b/utils/vgui/include/VGUI_Menu.h index b8c6a6a..035c148 100644 --- a/utils/vgui/include/VGUI_Menu.h +++ b/utils/vgui/include/VGUI_Menu.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_MenuItem.h b/utils/vgui/include/VGUI_MenuItem.h index 451c12d..33cfa27 100644 --- a/utils/vgui/include/VGUI_MenuItem.h +++ b/utils/vgui/include/VGUI_MenuItem.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_MenuSeparator.h b/utils/vgui/include/VGUI_MenuSeparator.h index ffc265f..f183a97 100644 --- a/utils/vgui/include/VGUI_MenuSeparator.h +++ b/utils/vgui/include/VGUI_MenuSeparator.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_MessageBox.h b/utils/vgui/include/VGUI_MessageBox.h index bb1a6ad..38a6e3c 100644 --- a/utils/vgui/include/VGUI_MessageBox.h +++ b/utils/vgui/include/VGUI_MessageBox.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_MiniApp.h b/utils/vgui/include/VGUI_MiniApp.h index 1ac9898..b7ce668 100644 --- a/utils/vgui/include/VGUI_MiniApp.h +++ b/utils/vgui/include/VGUI_MiniApp.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_MouseCode.h b/utils/vgui/include/VGUI_MouseCode.h index 7d3628b..8adf864 100644 --- a/utils/vgui/include/VGUI_MouseCode.h +++ b/utils/vgui/include/VGUI_MouseCode.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Panel.h b/utils/vgui/include/VGUI_Panel.h index 5c2bdb3..d5cbd7b 100644 --- a/utils/vgui/include/VGUI_Panel.h +++ b/utils/vgui/include/VGUI_Panel.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_PopupMenu.h b/utils/vgui/include/VGUI_PopupMenu.h index 2f11a81..26f6bbc 100644 --- a/utils/vgui/include/VGUI_PopupMenu.h +++ b/utils/vgui/include/VGUI_PopupMenu.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_ProgressBar.h b/utils/vgui/include/VGUI_ProgressBar.h index a1367be..9c3ca11 100644 --- a/utils/vgui/include/VGUI_ProgressBar.h +++ b/utils/vgui/include/VGUI_ProgressBar.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_RadioButton.h b/utils/vgui/include/VGUI_RadioButton.h index 857751d..89add27 100644 --- a/utils/vgui/include/VGUI_RadioButton.h +++ b/utils/vgui/include/VGUI_RadioButton.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_RaisedBorder.h b/utils/vgui/include/VGUI_RaisedBorder.h index eff248c..b5a3588 100644 --- a/utils/vgui/include/VGUI_RaisedBorder.h +++ b/utils/vgui/include/VGUI_RaisedBorder.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_RepaintSignal.h b/utils/vgui/include/VGUI_RepaintSignal.h index 9e6e3cc..e728a4b 100644 --- a/utils/vgui/include/VGUI_RepaintSignal.h +++ b/utils/vgui/include/VGUI_RepaintSignal.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Scheme.h b/utils/vgui/include/VGUI_Scheme.h index 8c55431..82f8500 100644 --- a/utils/vgui/include/VGUI_Scheme.h +++ b/utils/vgui/include/VGUI_Scheme.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_ScrollBar.h b/utils/vgui/include/VGUI_ScrollBar.h index 1da5f32..ea280ea 100644 --- a/utils/vgui/include/VGUI_ScrollBar.h +++ b/utils/vgui/include/VGUI_ScrollBar.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_ScrollPanel.h b/utils/vgui/include/VGUI_ScrollPanel.h index 0202435..88bcf2f 100644 --- a/utils/vgui/include/VGUI_ScrollPanel.h +++ b/utils/vgui/include/VGUI_ScrollPanel.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Slider.h b/utils/vgui/include/VGUI_Slider.h index faf59a8..c4c6924 100644 --- a/utils/vgui/include/VGUI_Slider.h +++ b/utils/vgui/include/VGUI_Slider.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_StackLayout.h b/utils/vgui/include/VGUI_StackLayout.h index ce9385b..9da6652 100644 --- a/utils/vgui/include/VGUI_StackLayout.h +++ b/utils/vgui/include/VGUI_StackLayout.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_String.h b/utils/vgui/include/VGUI_String.h index cc30d94..bfe9fde 100644 --- a/utils/vgui/include/VGUI_String.h +++ b/utils/vgui/include/VGUI_String.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_Surface.h b/utils/vgui/include/VGUI_Surface.h index df33b0c..7b5476a 100644 --- a/utils/vgui/include/VGUI_Surface.h +++ b/utils/vgui/include/VGUI_Surface.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_SurfaceBase.h b/utils/vgui/include/VGUI_SurfaceBase.h index 0f4d3b0..f654f9a 100644 --- a/utils/vgui/include/VGUI_SurfaceBase.h +++ b/utils/vgui/include/VGUI_SurfaceBase.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_SurfaceGL.h b/utils/vgui/include/VGUI_SurfaceGL.h index 4d147aa..b509a12 100644 --- a/utils/vgui/include/VGUI_SurfaceGL.h +++ b/utils/vgui/include/VGUI_SurfaceGL.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_TabPanel.h b/utils/vgui/include/VGUI_TabPanel.h index 8fa3875..f80bca5 100644 --- a/utils/vgui/include/VGUI_TabPanel.h +++ b/utils/vgui/include/VGUI_TabPanel.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_TablePanel.h b/utils/vgui/include/VGUI_TablePanel.h index 7ab32fc..3a76f7a 100644 --- a/utils/vgui/include/VGUI_TablePanel.h +++ b/utils/vgui/include/VGUI_TablePanel.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_TaskBar.h b/utils/vgui/include/VGUI_TaskBar.h index 2c5d758..e9ec49d 100644 --- a/utils/vgui/include/VGUI_TaskBar.h +++ b/utils/vgui/include/VGUI_TaskBar.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_TextEntry.h b/utils/vgui/include/VGUI_TextEntry.h index eed8cb6..682f000 100644 --- a/utils/vgui/include/VGUI_TextEntry.h +++ b/utils/vgui/include/VGUI_TextEntry.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_TextGrid.h b/utils/vgui/include/VGUI_TextGrid.h index d371b0f..beb7e52 100644 --- a/utils/vgui/include/VGUI_TextGrid.h +++ b/utils/vgui/include/VGUI_TextGrid.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_TextImage.h b/utils/vgui/include/VGUI_TextImage.h index 63d272d..b11feaf 100644 --- a/utils/vgui/include/VGUI_TextImage.h +++ b/utils/vgui/include/VGUI_TextImage.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_TextPanel.h b/utils/vgui/include/VGUI_TextPanel.h index efbcd3c..b000f7b 100644 --- a/utils/vgui/include/VGUI_TextPanel.h +++ b/utils/vgui/include/VGUI_TextPanel.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_TickSignal.h b/utils/vgui/include/VGUI_TickSignal.h index 5718b50..bb0bcd5 100644 --- a/utils/vgui/include/VGUI_TickSignal.h +++ b/utils/vgui/include/VGUI_TickSignal.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_ToggleButton.h b/utils/vgui/include/VGUI_ToggleButton.h index 91c39eb..0e6d8bc 100644 --- a/utils/vgui/include/VGUI_ToggleButton.h +++ b/utils/vgui/include/VGUI_ToggleButton.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_TreeFolder.h b/utils/vgui/include/VGUI_TreeFolder.h index 2a7bd0e..ab86cda 100644 --- a/utils/vgui/include/VGUI_TreeFolder.h +++ b/utils/vgui/include/VGUI_TreeFolder.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/include/VGUI_WizardPanel.h b/utils/vgui/include/VGUI_WizardPanel.h index f2de2c0..d2f7a2b 100644 --- a/utils/vgui/include/VGUI_WizardPanel.h +++ b/utils/vgui/include/VGUI_WizardPanel.h @@ -1,4 +1,4 @@ -//========= Copyright © 1996-2001, Valve LLC, All rights reserved. ============ +//========= Copyright © 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // diff --git a/utils/vgui/lib/win32_vc6/vgui.lib b/utils/vgui/lib/win32_vc6/vgui.lib index 66f7e909a7f18fec6b0f7ca5b4bb8e070b5f0735..aff77160c68e81162d591b53579221dd23619ab5 100644 GIT binary patch delta 59933 zcmeFad7MvW`2T;OdpQP!u``w#V;|d$ExWPrYqBpxk|c&C32!9H&d7~~Bq4jEBuhz> z7(#YKNV11y{l2dI%$)N+MxWpJ_xtnr`1E)@=RMB3&V66^wcYo*&v{R`j^s!fz*zc8Ozeqf;uo;5JCjq}GFg7JLxuHB8Y<%xRBGu^ zeJGPem6Z;aA2LZ)$?s5c3zJ0E8V)sVCW(qMhJbu-CNos-j|)Mi;||q2Gf7lUa;SD5 z|31&etz?6Iy&;qJLk!~FMMkI|Nd~*+1ZHS)kJ&^K?$CNFvw^BB9XiwvFx8<8^${XxLof)Ar?{av&ktyQv7Kc1Vm?Cn|H^`HV z8KH^G=U}dI&UFf_+RBvV*F5&YMlgrx~?DiNT(V?J0^{v4O zyB*|+&f^T~m1IWP9VAAewdpW2RFTN(V;MW zNxYYh$xcuPh#MIUa&T`Z>xDYxyul zwstZ??Fyj7UEmvNOspMjkUf_$LLA@@Nz*aX>F6xVpl&X~2mwRH1DSZ#pejKDVs4y6 zqum%F24`WAK>aY)Ve?815CqmB(17e&-XIR$U=9QDIe^Jzj0UK`*E{f|OaVDNvq3!v z3lSe8kcpz?9Q-{S!TcS-7gPoxdBh57;(=&!%V4K#%n0>!X@~8rnIgV~-+}Cdst3f;Tn@iYWCF;uj}7Wh zd#3IL@IwwaNPUqJsw$2VAGLI74#j}2kg7oal;E%xz9qg`VUWG=GeR6M;c#p*6F~ls z>;md8VvA5S4RS7y2k=8xo9(b*DpN#@<_6nd$_TY*j>Gm)j1X+tWsrT)01&?-D-7}Y zjA!!927~$=t|2&F2IL3Gd7vtvbeMOD1r&a$CIbvMM`Z%)$4G}Q4G}b8-7tgfwTuzs zWPOJdo8e2i?>r_Wp#FjKgbj-TIj@gFRX+k3f`$7WT4F%JPIoy#Kj6RZ4m(;iMSOYA zAYX3F2yr&SA^9?b^)i!xls2gQ=#)qwZ;&4iXN0PSHi^Y(hiJ3MU}p?rgxa^jVP{|f zQ#-*|iw&}0MMj8&$Oc1Ke4=`tDPmB!=AqC4>pO#-h0Fk|a(#z+sQW}?)CFL3Hx?Y-#m9ehb_d}xq;5p+PDLWjgjbO_}6iw1Qs z5*>nc7$}e*W;UoADBA)0u?RjQT46N64#C_H)ZUH`JK$qt;}C;<`3ABA^Te6<4yVx} zkQcc@{TqYQ4MNZ!kn^!#1ghpzhmR5cMC;+mfBdl1!-qiqf)0r9i#co>#}tsSNC&Gs zlf-ZYZ8IDL;(Qf@U8Wr))bI%g`#G$HfP8HP7B84NfjGa~;X*+sfEscf{vUE2LdfY_ zh;BkfI=nrANr_k>nj=7gxZTkphm>XVbyT@22&DSs4C2BZGzu=FaiDTx5WvpA#UOV> z5n>OlM;w70js);y7aAdU_IDW26^cwWh(AJ@;o>YbI*Z8+@E!p(ef_pKYoZmZaZA+&jgS=t{BwYA&ig%YoU<| z25|{XZQ{?q1{FGs8D2+v5^r2`7zS4mZ=(JK`32GqsEKeXkbku_1mucjW>^gi0F~ji zLC)O52!(<{*rA9KAP25xG7d@saScTa$U!5RxVqFJ>t9p?WjmzYd9+My+bSr|rk&fzj59H{eq4e}Bq8K_rU8tlRY7@^MBcj$@Z#D(4l zITf7{oBA2lYk8RA#BQd5T{(~qKS0It1~vK?Bjh@mj7Y%f;$V6pu2eI~1Vr**nE8OL zBOPQ}1S%MQpGmRaq1j_($YWR~#vlj5Vpld8WSxn)4hxdNxdhxg!Swy-V}?T<09j+U zLm8Nhn2fCG-q@h(4q=4&tFFW4y|B<;W>><^2awapI+TVbiAk78y6rNEYse0u#=y-$ z4!VK$f8ELO*JLKHPfZ~QK679e-0kqF8k2^N_(XtR!NrEl_ylzka6sHd1te~aP630t z0d8W-1}fW7gZvi?0sFbx1pGhvAns7cJ7kGxk_h16t%n%Itv1J zaY5v)Y!H7VR)AU>?yzJcQ^co8M`A^5hova>#B#*c@&JB(I@@8z5eR_KP&kMsw!VGLjB(SsZGN{KDnV|v%iM(44@*9kl_zI&Y zHeYo3Djxp^U!QZ>(w|AgEqsD+t~hMQwZzxG9lnLtzYPpWiJj~)b{JDYzJUmBC=KGD zLm1s5Y%0LlK-_C?P$Lk4Kn~3SIiwsA_jWt{3ppTHAzp};P!_0VsHVj7x(0c3Hv%0$ z)X}95$2K64!10|1ITW*ABXk19zgr#d*I)vuNec{e=v|zjU=a5=;v9GwZBX}Na^hE5 zlsE)y0{Llkhd0l`|Ba_ILOfh%P&uIhQ6|bE@-9R)n*!0H#sg@+94wV5crpx=FiU9&v{SpOG6=>@9X5`$f@FC*mrNP~L$J4V|#~rF*QwfMCw+!;F^7tHTkWFDdAf9l8WM~(t*=U3KAW#rLz)YA2$jeO~W~^Wm zsA9~a=m@5OT?-pcKrTf?Kut#81NqicbTrO^!QaGpuoh4wCK}|4aAr7)m?4ghhyUrv zZY&&#pAdY+&$m-R13!Z!a1n6;4G>8PP~umZ)&PJ03U=W-V&^r7eM6Zfez|9`tJ;i^ zj~g1))nQow;fE@*%%KoQ3G7mE0gx6tAOxZr*!eaX)H}EaNUZfFukX+d!AkJ04t6dk ziMNrKYPLi3$5_Szd%D4H&>#M9&>xM#7ergkNwphh7>fXs5*A*?-< z#3xG~0uM2bqg^6jE{AvSVe_&#f%!jcHHWZ0xDaH8C5X{|nQW8bkgW)lhI#k|*~1NL z+(V2A9gK#_+dxSo`y7WH*_b3QZFHE{nMt6gptu1!rX^;`06J(3a!fYZMX`(mDu0qe z=7e>C>b~3Ig=|b2X5$ldIPK60YXzeHMu%?Ov4IV`&Uffs85`H23xb_^9~;`VLbOM8K|f(x4{d9PvJ?9k82W;R4i4 zkq&uQV+(#YvkRh(1695}0sr4X3ITO8mGAKTcTC>KXbq$BIhv`k?kOMz!@$`E4#|s{ zBF^SF*jaGnLEu?{AxmBS8-1*R|AC4`Nd|I#4G^fo)PcW$jad(qhfcpOs7yY)_;f=nyc?1bp90npViTzL#$WMk-GkY25z%m`LC%cB%_qzrjfn`z2e1rreY-&& zs0){Yr1lO2M?g^mBLj9LxCE#OC{L7%a;OM-An!~tsB?k!@C7Id0bp0XZ%~D9V&l>F{1)Gb^(*mUv$V*jY%Sq z8|9YBv)-VJA$@>d`jA15XN-{VgfZC;C6mZoHiaelM6it?XOQoV!};?L;q#a@MB+0N zwdB4*=9|d~m1&tFpdRI6hU_pMuybNv2IS-226Ywg0Wv=-A#tUj!ES^79iT44r$BDJ zWKbpN!;ON2qYj2qV^N-A~rh2I~3^4q~RPs&oQ;?mcec_2TIRjDi%r-IRYpmIl$*#9f}}W zfL#&g7RU$D4)+&hF}j%9f6TWQT&?p%A!&$_kW5N5lt6XJY1V zht)%o|MX*3KZgs@l8A$^h=Mo=?3mUL3MjZt*28SJvnVKsETIGaHYKnOU5wklR>=tkV!Ri~-pRI8x3zx*AtO9j?vcX2W5vq7*gI#GVBh+<7 zKafw5Rlv@h$00lyvjg~{t|5PkOS>EbqY)UO{CbB$gP8*Iy}P*2Z|hLBAMXDF9<39K zHW-MNy$yC{m>9@qNd~+6dH5c+;l1&AMtsPj2=;#^e#nKb4QkpJM#y4_0iaGaHOLL{ zJy6AGIg~&_0rF2&FrY5u9I)#mMTwjaa@sX4xxiMKo%kNXM|^k7VN){vzX@scJ%osF zF+yS^Mn-IhAh9)tZ9|wOwx_V|9{!C2bp%05l+I(Y<9ahfVYG(9un_Tjl*15Y5ivLb z{|p9iL68`B&0#2hC*Fibfc&Dl!}{;AXaE~}ITXin!!CU8Vrs@_gWawlGc<-JfEpfV zu=An-0W|?B31r~?gSbi9>rkQ=ZYpq_l?z41fJSnGxN{D5;+YLpcDR8kyEp~7qbz8E zs3!uj2(W8(H^`DP%wZWmL3BZbDm@$dj~{j(R68Q~D2F}{>4Js>4Oca1AfxOk)pe`Z6fQ=gvL$Rf3U@22)ke)S;R*W8ba)k`AbKGUi2}(6c?)?A?BX>HDjA&+4dV>* zIxIn;rWhuVViGqnhM4|L0+q3!A)rE$7C`P8?C>SrKrC75Fe4N@Az(osgL;4qfxLR# z;ZI}-@fR!z)O6GXASXXW?MKBU%3VnT*Odb?mket97WjYq7N!cLQDDa$F{r{Q$H0#5 zY*0O5As`=CHmJWK59GtI460l-Gh}IrJ!0^rmP77Hyzdjq#NXJ)11fs8LH<*OIRuvC z7x=JVXM=b&iy0ng#ujO2rdB^T$h9yhu^giWYDSF1oGN&er3z-VJO=d$9RPW%ph3k$ zaUf6P-^4M@1H|!}4uM~WVs8e}n+e20_=z|S-w{6|oroV$9f>5kfmpN1pb`?WR0nYg zF5*;ugWb3f3J=IL)1lruW&@RPufc9RnGxyiwmI-i4m~ZIm;d5^v5;lDVMVK;)kYs! zDst$zDq8u{wW-sfW8=n+^}tG2em%N|l}%r-WaUhZ+doAo{mnD$QS<}9WmG+$Yu>Y$ zKAT_U3auB{BPOO!Tcr`F*<=A7^Ec0%czu6*ouLvhq!TiTj9=7Bnww5{isbq9 znVUR9Ck(VQ=@;^fTwnA(72Q~G{fCF@4{!3ER!7}tFE6LB7qwRE*KhKgI;;TCsGC&e zMRa@#D}(;2m^DmqjO2N#iH!Q~TWFCQ+w@a$E0d*rl(Zss*e@bX_bq9?s$0Lqr9RzV zMJHzYDV@$x(kes0Y&u@j8fNQju~tLQ&>@uTnRo*(L?Kv3y{0dft-u{lrNx!Yww5X!hV&JAb%dGei9WhK)Pdt7yRF@ql zqV(gby!y=D8Fa*3;yJx?8m}^Qci5)zPpuO|+p$)z#L%GjCe7&RSU| z_+7!+s#TnK1+PlRqW1_E{fl7T_X*Z^s$`qXOSZm(WR*e7N|Ft&F4@W|l0B>}S$=$O zsVG^EsuGo3vKah6^Et`-?QK@DgUxF6 zRV;9+oz2E|wpsEkidF1rvtFHSw)GE;2#U>WZnLo0B+FA1n&-#J#zFfRaIUaq+1`_^ zT_?#h+z_nyV97%3Np>SAtS}aG&r3GBlw`#wK@Oh{yGa)LNU)B>BnxW-%lt0bZcs2u z1lY>`f-OEJS@cbMtxd76iIVNDsMyXqiZyJc%v)@ke}FYMD>iD4V%y^-yL3RXAz<-o z$qL?5tg)q7yIGR$tOSe9Rjg@aTyNPd^Df1rzfo-PTE&+1#K>{Y^d!NeaNQF0mxQ^R z9Yu$YabX-T+>H(!;=-xua3wmthYri*!W)MK8vq(7BOd<7HC(aGvn3n#oMJ;iR4i9h z#l{JnZTwNO%Ucw)*Q31`CF`9m*y%HZ&ABMq_J0*S6r$Mi`I0rQ1y?VI>jG^Q>yyD| z8TP@)-z%2mE5#PPB3Z=~f~`I-*yi66mH#MK&{k~MTm)iuSYn}Kw_0G->1!@H8e-rHSS;^YpQEa$W?4ri+RTXRbkz%a5V!f2j5`My9x5ED`zEJFb zZ^=rav&F{*D~1jS-&Jg;Qmhy{oLx<^sS6Zqjt-aFHk-3Yu~2llYXi=s!%M#k77sR@ zk*o+T(-fBJ|AAyDD`T|t6dTY)v1D$uNLZ#JEHiAKV#|6-R{tahcLM%zd0w)q7~u+x zu+Kcnj$nlQFv2-4VM>f}J4V=ghhpb2!nQ9J@^AI!NA!ZKE3P2NRS_7IUA!r&x)9DDB zl?a+^2%2zc8U?E?hFRyFLnkn4C@eE`4uT1mxet@Bhe^X=nGCSZ&~1vIH9U|EVl-7hPl8vh`*&JL`@FD^^7cwG0+S`rv-6L7%BaqpJ;_8)L z!{^u_n*h0sIM(-1v>A%>{JLV7-au*&L!Q5>*pV-w;6$Y1UziV8KpFILI*>lvf4IP#2Z0AgWoYWQ(E1&{mS|Nssaig)d>$qtl^AK&f-+ zxcz0p_MV3uF2SuR=>rPm790O=*%PsK0Rx&US-(vv>8~OGPr(1#D%)&)MVs9k4K3H9 z;{=QZ3RJs-Qi_TIlBgw zECZKKMrG|T*^s)Zc4%iWl;f3bHg-A`E)7>rg8z4Q!!H@mcVR^d8b$vObu8 z_hdov-9gYM!a6w>YyY=k;Y(5fCoVz$eu^Tq0_n08skB_N+4%Pn{JYp^sO306`V$nV zm69Drz|}gAG&-hO)KS#yBW#azMeq@kkWMdM$z_>Y%JwvDpR$ zYDfdD2a4j{1j!C|Mr;KzWo{pX2yH7_>$|81LX8fmdbvRmK^PWHu#-Z(b=TPT9)9yS){g<|vs{}4K?g~dnn zdx(n>C@e!I8;j$+@$Xvy3f5;88i%E}z$$fN$=$HfQdnvOEVdJt8wGiK_`VggHSVMS zFF=~z#lZ2KV|2$G zV$O$;ZaopK{9EuDKItJh$7f%L$e)ek^Z}Hbi5U(Zu9$%cE~eOsqNwAwY}R)v7BG`B zL*m+Txb8fz8T=jG2MdLtz$)n|g7FyEjVMOLeuDdc#(6L`fT|RJ0Dek>X@8ZhIk=4~ zbq!T&=swH^zaU7eqSimgg;z0kmO!V4Fp5$bAdNl+CuRW@c&8t7~2Aoi6U}oB_bLATX6$ggN}7=cJv8~ z4$QqLhs~OT%Xs^4FsO#o8Zr{8l-XvRGof&;!<=6aRj-6(bthtl(-je33>kw+@Ay87 zQ5RHB^wAE5D>(#Z9ITwI0&Hyt9ITH~p|Sg!5C@@>jlkz)d=AKr(PL$NG1_L!7Ndcs z2+DDA!xRJ(tdaxyeHYbmXM|*B$3uY*uv~s*%s8Z8dkhHIvr&qjMlEO`hCoC{Jfv$- z6LRH)b>G21dcS}olNBWu0xR*^cQlGk8?0usq3(n5?6?S>9>wu%I6epe&XxngdI>Ap zX)r&OX^9k0KnhRBab6Swm>&w}L{@ZHEZYmHC*X7k)RIoHPfZ zj{-JZbPu;&|H2Zr72A9evZ#nBk?+Map{_qf^n=B}Av4}k>`DTxIS_5`M;S-`D38y} zizJ(cJD=6K^V!oE7JmWCbwU0QMuAAeozFeo`K*TaI~zjtnJ^`SZsP-#=j*TvM%F$F zL5fBtXxGSQcOx(|l-KqZ(fJ+Bj2J{zNdz$h^EOiSG*Wc+1Jn-`uhRGoX^h1!0{wU{ zOh}+DuNMN(eDcGmfB#z7h(R5SaU$xz(JHo z`!4D={53od3(Ns9Z8`Y2ETS08vSA;i9>m}#0)=BOf@p9x_^lwU@-C*>4zN-I6d7=9 z73K&uK0g+*k^{GKpTh-35Jy;BM8k@U@1soP#&lQ(n_WX2&2fEld(8jc(bx@WHebW% zD6zX|B05(q)(;Kdx&U)Rsi6p()-g6~48^V?keWleY^Vnlp==R!*bC)58*AX2Tg5|)K^q4_$tXC||p8>AwkAc04{BIf$zx0r-%B#3zMUc&Eg~p>K zTd)=vqo^E3BL{KOL>!A=0uyIMwqU8Y71`1-AC$?j*xDn=fWwLM51Pg>rQ0yQkj=fzl%awp4xop-CoI^+Pb+9>s z2wz+lBLU4D;8q=0>R!WUF(u(5P%s)Z9~9$xY&I%4?y_*r@X^SsFsu{6ama3%7(k82 zOf?6>_evul!TkOZDsHp3C6EPZcmfo;Q3jD-7E|!^s9=DDbNF{bWJY%^IHF+sA)jH| zt$>?X)P^~KpyO55(ijcgMoFF72e)3YVgZ8l$!O?28ma+BLy{5g4PkZ^s&Usa|4*ig z2X2gk+cH9tP{hs-1mu^P;1Ix}GZ147aP9%}|Mpb`2m;~sUnoG+aZ`wk+xI}al!E}c zghsaDc)!9ZrAWaW`0R}6ZQKL1*F(tsg|xzFbUDm|S#T%x1TM)93u1Y_8g;+OKbUqw z=XwZI>;YxQ!uIiMD2wbmyf~RoP5RcE3nEQ`m4R;(f+Am1I z-!Q6!7~x^K>PN)G4;V#~WQ*1yXcJ&D)QS41uuMk`&4ka-VV$=Z={dPA)(K!zdZyQ; z<4wdqYp;Hgjz}M&SiAVALyaX@F8}LTF8E?)z@Q%DIpDw0~ZoD_|!1l`|8No;MQG5g+!>96&)@OVr zU(J{CUDi&kuhqv|$Jg*L_-6jO^=p71;fMJlzRVh79p&S#an`5SNq(B2;wShSex2X2 zZt`3FKL3|L;Ey@IZ7GEk8AV>6S+r)eM56dW%oB6PhvH+gSbQdyio1NdXvEfvD}0yu zQ5+H9i_79qens3A*Tpq~DM&VD8DtSzT87HPvQjQyPVVRJ$&ekgaqxFilo`20(TBEG@tk0}V z{FZfxKjK1U5czmpHb*QHpNf0D3A@G*iyy>a{Hk~?nzK?eO6K7QdAICxAAdu(XUDCB z)(ZKx{72jsAFSp@a-OyBi96!9*l48_Rpf8hIys(?_}~v-Jg{DN5oP2+o|O+0Tg7>C zj>qu7xh2=io_wH~$baYW$?>v;tjd0sjl>))jA!JMx8yDO7CzS+ZJn0~oG2@P zm-F~sUQ(10XXJeTAulDOMY4Run!}5W({d&+Bu>ag9wko789Y)Pmoe<9?9PU>{_Gyx z$86lOU@A(l-@Ol^6WJVhpEImx0r{f!s+eiDlC!MVGSO-&TgXnbn>ER*!yEJI z{6~JA-{1wrGEq>zEED7w>!ekj*X3{V75qzno?D`zSRg(TmqcUPMUIsDMMY6T%(li@ zo%kgF8^6FqM6B2${t{*7Ci$&AB-_hz{J6MlWs;fYJ3Rd{kwuQ>N5x^iEi*4#MsUvW zTMw)!*1y&x%i^It18>D!^T*ahi}Agps4OPG;U~pzaY7u>S+nq>qL~=3TPWTnkRS$# zF`@z+Et-l-theaFyNY<;O@zoYtbt4?OR|0@Ldl@A-Fpm3U6Z%N}xod`-L0!ucK$a1m|x_R#CPIX@uw&$`^wkl zV7^hj&&!J+_)gwP_LRNkK)H=qmT&OO@?H5F-@vzvKg4i8PP{KK$&Ru#FU4nz2vJEy z^ZM+$dMr#9VENe*d0!;+2yFKKB(BOUa;zLJ$H)${CVN9vV}nI?_PU5;!$ckS7G7Z= zsy|Q1Tm0|5`v2c~HIO>5{$e#?J*?i=Fl($e*;-(2wRTuPSsz=ctd{JWm524WUbS-Y z0=yn?!e8K>`83gscVv-#B%jPXS)cPCte35|d=vkQFSkZo?^;W(Kln}SHh;*uNH4;9 z8>YoVu|)jC8?&qYkk~0M^GBjtSr#n|$=p1NAK+c(FMNn>$BtPCtmX16`I@!D!e-3> zPWb;j;s5W1|GyLd|4#V-FDCq|{~srOArfmh$hB!rR{lvf-EWyyQYSa(eK)<`f)9^# zUdm}U74I2Ek5*;r^_)@qeo^cvPEAwR;4;^t5SALD4u1br_YHd^CqnC@WsmNz1Gv^sZ52 z*il=*z1%9Q2Q0VJZF;?&7}J<^sc6(lc|mBr#*0B^6T@nx)8WI#PSPeuhrKOA6Bqqd zHT9Pe(l6!k$m8h~Pw#F>N;*e2#f=nu>ZH8OwJBnoc#<>aZ6Y1BS%&Lchec))ziRfT zR)@uY?!IW0N7p!tTL^RFs1R1riG*V!OyNZJUv-P*nPwQr9r>1qI6QXA78M-um>ZM%Ao)md>a#p;hwf(e6Kl_BpmMZ9P zqSQ&RiKKqAv}yOFlcIKzxN|V;DeoYL?&w(_TH;Nn#E&ONr|D>VqZFZ72&FSZ#dMEz z-e3~GOzH3a)8hHm{^BmC9L#z~v`IZkO_{+_Q+V|&SrktUYiq`qKSgGC3FYXSH(4}G zz0U1nZt^pFD0J5A!R_|5po5Y7QgkVE&Xc@xN9NUMva0N%WbGPID2z##Sf@{gG!{r+ z4$;7e%JEk1-$jR~mHqj5PuaM>DZMp2A9OI)96z7;RB-ibbHO|4j^MYTgRW$^i$Rhv z*rko~)u24ygJjyez+<1S{hG@u+Q0UPcp*r86Qwb@J{*pX{k%k)t7$Yws2qgy8KEM< z-Y0*#jpWDQ$GMr4|39=+z>)Gbqmv4!`2C+hJr}!v4_x*Rx&})3muCytKvSMNXu_=$ zbdS;tuZU7lhnqIO-BvoqI&-gxR;fW3RxHJj1+I!lsRvu%5l(o{y(;RY9z-fS2eVvr zb8ALq9^L<%$YE?#0R0z-?atKCsTW-nsj;q24o`U~oWQzyO;k^9((R|`bzeVu^{IT) z5&!MFC%(2qimr`rhz6-`xQ5zs!!r~$Y;?lcxmg)_qIDi#P$%3F>GkyMUUhM6$hSA$ zAvx;0{m0(&`gd*h@h#6*H?pM~@NZEsRjJmGQTYOYi|j^;Xed!0N?3XELTuoN^l25L zl3|KjmfNC5nnui-8MnPLyLxB5RopT6!9kQC~c}z&1mym@wAG&12wWVP`n9FvITX^TcTiU4+$Ak#&_x;&vK-j z8Cs`%p2Dt;_uum*-Jv!6*OPSH-0^RaWUAp!!!_@FHgg-^bl(&AnWwH^^FZX%tsaP? zsd~FHzx;t`XgA^tJoF;Y4RVu;5imppr@3tpy*Y$*FcDE2=Nlp-dgj9$&1u5YHL4lJ zv`5|`TtlRP>=^>$jvWJ!MH5%^*ww`h-f5>&KNV5y@IESXv}b!tR>FiE;G z?Xy+O4`#?b`lG8nonBi~x~cB^^j2v@qcT-5WsJRw-SxR>zb`AQ6 zl&(Qtr?@HI0p0B=z@ySS(2eXf%Ik`9+>DTx1)XRjYcmjUk;h#X+4Mr`Wh2?p$d!Uz z=M=dp9hcEFR^0HE>8K1uxGu`B&~MGWrFS7 z5b0J{GJ}ccvgoCUZWSVP>ek6tc73gtO0Nf`m+mUf7aH+8HiOKipA_WA39kR#Tq>2p z>)j2Qg&Dknam|-6qh~%>az;i^(wz}9g?f6^<;F==^ZLL#A4l*4zEEp1It>-WKKswrj51S-pX+Et9e)`8ul?DsKG5 zX7diZep#N)>&TUi$nHt{*7tAdEIF{W99Tex&aUJIjT?>Wb9nLKCh5o=UKg&r+}b)! zugQiO%aYSG4P9x@)0F>~K9f`C)}wREBB^n18gZxfV>$oJ=S^~XrXdwgo8-$8x>YWj zPj9*EO>1uMxHVxUzQ(@)f2s)Ua(m;V1T$Asg5isidV3|SnEv#R$d{_LFVRO++j;f& zDBQh19dV`^(sGO=6g>Ho*Ufn}$9Yz1%^L3ckoq=Z^?tZl1j~Ghfqb54UHcnVB60b9P-RSEo=3l%U%Dgq-BeC+By#G}`DJ9P zO(Fc#De>HPmZ%nkKpcgd$Gu0!WamHAX7E9A~oe0EK&AMp)41P-;qnlLaB0Yx~3%a+{ zpG8XdcG?|l-a_73A@2BZTA`=BKx=WM0xeT!>4-u|CDbRk%J}Bu4~z{4;R{v45##oF zHOlLev^LhI;Lxyc>%mqy`7X0>x# z*iDLh=BBlQ8D?pyGheqVDiQVTEQPO?d6ssUg|&+XwV8VV^Gz{tbZ%*iE$$t3quPYQ zXT`m$;?~;SB|MefgEN8-y2Hv?(o=yXjq;SVOLSH&vUE&=r;R|5*;*NphCD5 zUI;7BbL#0+5Mm!idjofSy&4_VYwBF^N+~by++NO>@_M1@GWr!pFDvwc(kQo&r&_j- ztLm*&{MPxov}YYRz^j+>I`TPem9A3;OU!bKR%U&s%ro>ZSJu_r-A?(uY|vzvI+jx9 zye?=gMr|_AIvraM6H}^L)6b5u-6f4bi}S|#edv1T3mp-I1V$55e-+~uDR+FCV!h5O zX^dxRVe%zBlU#!5(t(FhdDRV)2eDqSu0Ez~svVoORbGbU8-y8r0qsub*UNi7(?L_! zsAhgm8NFkjl}nGW;LVS2uV*TFy}HBbRMBh9jfVpjy?AghZ&b;<+*R5slK*+8dS&k# z-<-Q$*QpHiVhQA5Jh@q0@;NsM@D0-x^U}ikJD7J+G|!iqa29*F&v`NFPHTm#c+;9+ z(S=n!bzBSNt?F689qf#%o}{~tld+m~HwbC((RhsZ9(U`k)nswKy_y>*6u9Oz-DUse zoPOp?(KwIBwa2U|pE!3N?%LW_C~FN*A%FHws^MjyIpfYG4{AJPCV8!<=UcM2QGg2R zekhRGt$1O5qmlH_xUN%r)bgAH%Q~~riCUhy-Cao&7oBR$)U8IH5MnziQU_iAihISL z(N(D@cclJZ$~WWwrKZ3>htEBD4$hNAVqUKBW zWsg)f-Twcr@AXdyjdN&!^b|!$4FxUFDSC=bcwXKByF3_F*VI)sweR<5H04$v<3pD(~1++ahJ!d%d}X z`mDXL36t7X$C-sKu2X4!?F$GCLh)PD3O%(|U%X?GHB)cMKJVyx#2vc{jFO!^Jt;6u zkkYiF@EQsQX`QCtEZ`0#qO&^??H*hhbkL23h%R2wB$yKK4ctw`EIoZHIo(X^nYNqsy}TyftKaYCUhVd+Ui1qZtC3Ul z!fIKnaDSzAZDaLL+tkS3UQ@1D9`$yW^W6XTOP-|LIe*W`<-vw)ey z={c|I>XiZBvdDkUodMo8wCXY9QwGYOsSQm(pq#4F7D|*iRijSgppvEZjRYxmAe+do zYYy}@a+f4OBzQ{_x8YWUyoTwZu^+9mD$(eAzEsoKmeKUNW+`KMd$4>ZRVlZZysvw` z_!d4@sKGTWV)d!lJ>^V;uO3fnu*?woQfh;)hCdGRH1sdLy1n5oy!sRv!HjKK>*fqIt0Q@L;OrU%}$P8D;nefKTzTI$_gM!Sg(aGAc5 zQ{>Yhz2j+3A#FtH#gB$0k~J5ecTO4Q7csgX8}6CJ?XTI}UVrY&@$0v}l_Lh=gl6mr z*96+9Su^T(O)h9QY8>uEB8Ph$%kIT_N4giMKB#u$eWcgDYwQP}uyz$4ILeds&$OpT zc@A?Ik{trxipKcBed>KG;ENe&a4kl=`Z-UX-CMF9qdlwnhW!E!dn%rB`K^tOH{%f6 zcxz80V=`C;^}aEluiY{fG1hC^4Zj&w_-id}DM5zTZ2T+JaG^-XQ1>^%F;bl|#qB`wZ>Zz3XXDeVEH=3o4#Q-6pRHr+9E9 z+$YkLLNCP(RnS2IX-z4uB z$`9}FpNWFeYP|Qv)ou3A>Aq$iRXfb^6!6>P=NYao$kL8@^_fqJyU(1~%=E-vOGhO7 z^hFg-eNkpcq9^XIXRKM?GR4i_F|)kvH5$5IKA7dTVa~WA_38&t_3NHF6?Dc`v(;=* z+;z>4+1^lH$x7OjbXReoX>S!rmU7c}0(m9rC>|4}@rt|iHg`^Nx>>l1DJ`?-qucMi zNc@Rtp|oxPR zA^o@($8E|ynsZipbI#~9DXW`$=w&JvBf36GoJ1~@-M51jAq&d^%jShqAz%_K!y_OlA>YkTm> ze)S#i@pDYyl;ufl$R+AAA9;%-x*0LO(>=y4U1)(Z=}7#Yy|8QEKq*+w z_4JT!wvJtdM*}enW!Y4Zm?54Stg;)kHqvDl$^41&(@N^=b)n?6WnN;A?xezX(&s9h zk!+*)ljQ!P6?}G0YmvFsXP(&;MRA{L-PVk9ZJ^vdBbaz*VTIYBxcbk2ImI%qAn~Ci z5jQ_pXI`yRy)+{d@3k&Y@j(}e&3DAsyizl$Ax%MH`l6ut$PtNoUCk~mB6Z6(Zfm{{ z7o>{C>ZBFV)@h*;(a8r?HJ`QCu2rRtp*raOpTbaKfyl(sqjTx0jTN5H2AdUnnF*`s zQarwmpDc>%u%&Kaezg}noh>4vSC+88r=smx{u_&f&qNs3yNMI}9Ttt;sp-4aJ8 zp+4T=T+{a{Z=1Ln_#nlsEzsnrDV-lyReS?TE4fskTIMB5*o2fv#7)r1XSm6{(aow8 z>=|rXIor$?Ag~MqeH-vn!9LN5Do27|T3QZ?ZP~0!!7efHa+*x50txz>Ph!?PDEJUx5#u+5n`+LYpc9s zOLK%dMUN5J=$os&itV$(S~PwWUx@Yj*!S>aoxZ->yZiT*kM(-c8n1kq?Lew?reCUa zU+AoBy=^So;y0&gi+_XuaIJT*N_W7fI?^5Rmr!Y4KD>U9u=AUgrWMly?ICQ`->h@( zL5W#leaiju+4U&br*~Kl^z`*^bf-#~Zo1)m`h%YzH|8${3wdTW8)#(Kui@a@J$vgJ zA6vO~SQC6HGwDN>es<9NEbh47`o0%s*(^*loL#%ZF-OhK8 zR8jrp7bx^(r&sDt6{igh(YIPO^i^N zKWpn-FGk$Gl1y8@l}y^$`~}1L7T~V2?QmG|Btrcd73jIs ze*N=yFT*KuOAlkX!0?$2z z#e6~fDZU7(5C(o2*j?k zN}zFnCc$Qo6ip?+LF(g{D5FouH??JqN^Kpb7_lt8c@)%}v58yNj_F^1aI2d3m4xHE z*)GrBzFK%fAK2wxMfqtOq#Qq~Tm9&bos>4GNa<7hz>nUHL8~{TDXrd4lcsB9g>jBQ zVSP?MgK;Of;$_l|TT{t0JyFR@)?I$`?x%gV>MZq?RU*HhvWfE^y=j8N;GFKU$D6}^ zIs7}67}c6br*RP(!VHNFah^KbQC^TIQ`d7;?>&25(5HU(>_J+Z-lh zbBapjANtf@&-P?R6FX$ZOHeGX4KJ>T@AA4#8{vOKtj8hCAK_$XqdVo&W$2!``G?ZM zN$ZQ!ztBW%0josX;^SKgT+v)PE%D@e@d;08Q8KvJl>`k}i1!J1B`s^XRu{W|ZzN~-- z((9>5ys3l2)vWD%9LDOWY&#y7rV-N#x$=STaRi;zYKL9vG!2n~jMHi056N9uE8~f5 z@VlDi@C>Ps!5>+=`%%1M_vkn2etm)lZ3HL_9vgxBsSfa6{KV4n$8hnJqyCGHs&p~l zYB=U?a?rKr6eYB!&m8kAo39>n-SfDYlfH$gpi_lrpOBSP74lt)mxoUL$CZjs71CiR z-AgHGjXG4^Z9V3sr>QT@(rN3Ir>R*E1cLA6L+E_bV4di41AFQz9pDFW6{Q=K6Aa;CYn5Jm>MI zp6}Y=#a|wM{erg|@U08;(y81!qN$ZvM_lyQR`exA(?J#e*9FAOfb)2q5G>$J{d_di zdo=`=^59DCFT?p^wQoB;vkd3eR2EH#6?NbbY=T|?-OBrPYreI1L9~{&GahW?z9o2$ zcA7FCr3xDXv76_t(t3GMZ_!TXbIV4gUUx<2O}x?If7qcao%iK%G__Gkue~l)|F0GP&7)F|ezk9S`nj8bMv+EPq_iV+y(?ay z3f@M8&n$i**`#JEL7awb0VTA+uRPk9*4_XKKGROxNzOawaC%PM7xP)EA`{ z^upWTl;K;8R@4!9ylPE}X`EYLe|Q^8jb4Gxh~T{gvWZC#vPos6$K2nrYqfQoSEAhs z!OZQHMbA-!U(aiqn-=#5%vW2hprt}RuwIG($IC|we$xmAe^qLvM?Fzl-v|;hmh*l4 zyc)^XIcilPrToiB-wj1|^tiUivu-HJ2JY&wh9j0+ubE_p>Ko&+_i$t3Q|;0P#&fig zsi{x=>rKJF{H>+C-S_U8({3@=)>rR)Ta4~`V*=W&jH98gJ7CrK*_+CUQJ0n~b)at8 ztN4o)*YiWkZBgDHg703aF1571E$)Sm74r(4uPWCgkx31(APW|u%5Lto!d@Zwl^MM7`_0%w(5=jzF@zQF--e(qu!p{-#!k17bRH4 z)DGH~G6tvZp$=%N&^Tmsu$Zs5b<`0d-q3ya>ZA*$S2=XjNqobjz)SeH=$;U7EI!H3 zB$;Yi|BX@?9g#srB*t7w=ZhU*W_KkS_q)x$mb>XS8N8N#lW%t&n^C!wukWfJdRs>K zDxat0b)8UexW0FQdg>ja%6&HNJJpL$;eQ0xOk~j!S-lKEt~|B<@uFUr$qN^s!+YyG zSyZmXz@$7gN6Fb?C@?p2c@zkQh(OC#-D$o6vt~kl$m|`j;LDi&j zwDukYv2y)+T$%}ux-)@7v-4Qpv!FLyQVnn`$UFK>L9aOamV)DG93QvEQgHk-Z?)+2 z!n^uWgy#jH3CHU|q-Vmkn!g9l17oc?-K3CbN8cPjK`$&6q&41EG+NWUitpxVFlSEdD(f`~C`dWAJ zvy-WMN)hFLKZ72tm}}{=$}|i%p^a5uH!B)6*x=j$>G&%I%J+vWOgxeIW{_?XFW|X7 z)(HN|z}Lx4DA?lP^1%y9-;|c9%N6&gG+$w!rPma9d#8(x=Clj;0W|-3ApS;DtDqIE zDMhpyV8rO&W40p}uHPSsKcjGCApRMAfp5*DX*{*t!`5lC-?wU=gGM5ES8><%w5pd~Tno&qRA}_vPV-xOi;>@-R4=$ug#SdL}m?5^ZKX>nDHl^Y!tO z?orz7!?lkIShCLoeWJ8-_mF*$ffni>Wn9~9T22_x)9vRXvee@a_;YQ+`uSEIAM0*q zz2Z&DX;#{Fi?kS5V@+=D#9JzhDyXQ`DniaMLbL$=#1UGx6AP3lgYX4b^psxjFXY|P z_#Rv=A&nMx6s2|jzrFY6eRq7H!eX(5amP0y#?2&O94^&oV*VozKhr&8JzKzxjw7fb zE<>~H2IGlb@Ism5-&l*1Y&q$+KOB!jKPvBKnXi!*Xe9J?G!j|C)lK{S{W&Dod^#e6R!ucr=AoW{;!aKh4CrKS=3lEfEi zBrpVjdc|k@;O)5$+N$EY)HnBhsmE0D>anY#$qdqPqkdAwv!w5qW0Rg#)oYdJ24g0A zNcR8|j_#_FCYF}kPL z@>_JK8lF2%^3%vg9dJA$JhR|~|ZKel>Gu`nT3963UA@O94RV#RwrDQaQr24*t#(1+E-mnWcgD;`K zLmNp$kjRb5p$wfwnD5hK-DuYy!! zmMLZEtwndsg1$Xt)N2d%_BcAU8XlP#6Hq!(Pn8L3%#kBw|7_&&9bz6%f{8WSh0=SA z{&$|JWpf8syPhhDHx}^+jpFe%=o4%o2fv5vdoSx3r{S=-c%?M&rO|s(pEO9Zb604w z&k-KoP~l~uctI~R`kOX6=-bHN?_T0Q)${eAnj2xWXSYbs^fmf$iJk_@EvIEX# zk-db{k@bvG(&^)kygeWDW>1mE%6a#&Ba}brUh;pV_pgrhntJ$id}Q$WRT*{Fyq*`+ zj`Wao#q=i19jQ@b)V>rY+ChoKM(F=EcHMzh6-)oiz1(XjL8Jspdn!mX1O%ifwx_ft zf*>sBRDcX#dy z`2D@VGdsIGJ3BKwJ13I-dYVv8|7UH^gVd9#2KtHjLC%0SoD6A%NFw0_8m( zaOz{pLb>bX37k`=DJ$P>FP-Rak9T#5?4uDqtkzLs-u>9I9mXTfd;AWow|L8lUSKcg z01z9Vf)@kCS(Gr=#urrRT{qvj_ahv;Ch zka^R@3FgwohI~2YW?Ms^RkzUQ80%rMSW=D{E2@+kACYYzL9lsr1c)Cp;!kd^c(CdC}ZO*P}n&E z#)ccVSMJl-)S|a#PjUVYW%S|50rcMf-gf*7^DSdcG)*bv?Vh1y+i zR_^x&001@u=5n)fbyJISjdgKoGxs(XU3$^@lB}S>~I`SJeAG{ z(~AC~v`Bto2Ny!>g6dYU@3be+`tB^$BJXU5 zBh_Sxb!1qYWyeTv3FJL{5*}5opADVjCaH=Z=E#m<`Qoht*cFoyCz?75S+lOvJ+uRF}>wu=+tvEA~4j7lr+*$^eZo2wfK)g#ie^4EP_aEU^3^0m?7Pa?e1vV!+ayv=1% zhJKx3jY!2V=pyGl_se=+O25O7jAN3rL>!Z>hki<*40Kc!7$H~PjHr)MbRBUQO+OxL zMGNi>)l{j`>ex!817qyD`2HQ#^+k2L~iW3%FO@I$8y(G?BZg2;|L9u*^M$dK$@ z+>5yob^&Eb>;fiXPJY;mq6E&IsC7wZ8ijWudIE?xa%AuF%O#Nt_D)jhk2|e*V%mjg zZck-mD5vA7S5#nPrr&SY=(rT}`<=*o+l~ zwhei|2#%jzT+Fz4?heYnGdo7b*)IW0gPsnwS3RoBrbcw&UPrFoP()*sOSM}Unv8)IsM@@DcNX&9#&6A z9*WDkAucn>#@l5)!mP>Ol4%K1hGsg*wHyUQ6XZ0^PqQV{jJq!=VxYl_`wSW~-VTg~ zUnHAja;?x^{uE?Qog8m9AuAMNob!iUb0_~9ij1kYPBOlV-e1m;u_btR<3dE*I5;7s zI5^RUamn`wTe-Q&dMVq|g@tx0WxuOHZ9DK=F%{_rkZUckMt#dZyc;iSDADJDfc#`!)l+Nx*DUVv6EgM(4T%D-o(U2*xA??Zt?#%7=ynl4K z`_fYZxZ9GM;=3*W3p(XL6zu9Br_mQipLC_APgs4zr@JgKCv0Ux3vAzBwsc6S*1rY7(!elBew?s^jt+N||gA z6?H0Ii>?Pn0x4|p8U3RUzQ5CjQ_R7qA4Xh93#Zt)pz;^5rNt&|Fkb_XJUCJZOvYcthH51Kd4_Mw>WNwub1rj;V-1>(i?p$L2?t~wyIDZbb_ zAl-+{IaFM19eSt_PssV6Czoo?L>fiX4C}M0mAJucPni`+WK%=G;!w!mRmh0Fl2(pm zAFq8-A&S7bYp1T?Ze);2u~FmeOSNXf&RRb!Am1xvUxoVnG0^18C?0O2;#qc%)B^PP z*y~b+q9DiuGoq!~8{mP)76ogoF{p_c=z*G%od|9hceH2*ZtaiXjhtHFWU6Hu?9n(d zJ8*$NCru40Pmr?-vl2_viAtU~GgKd#8q}Nu=PR30&2hfhZQ&#MqmC~XPHo--N}FRR zSxwT-v~Z4HLq23gLGd9ol$-EqI0vbAv5%bi(FJ1MI3@(n$HXl(cdqq)SUHQPk|Y%9 zmq^~m%hjGV6wE<$E32WYlVT1UHG#t^Yo3(}w!h_TzE!!6zn;<>7anTo;}Sx213xVo zfz6q41v0rEkygg`?EnnP!lN3jj!~7$>>SCx6g`ToNr#5i(lWh_0+IA*Ikx6Vrkr!? z(c@^qR!_mBN1qx}hRPkx(jqRc|GKO*&+Bs2IH)EOG-2J#Ka0etaugVjQLnp_c z#Y=5>>Wt+gOU%~LomBR`wMdkmcNdi}!XbH6BC_s%4+vGn@NPz$WGgl`WeNEL{T{Fz zDfm8}N?x!LHFYxH3q}vk3FxCyIkorEv=>7oqwc;6!ro8gUJli#!nuJ~r#PThwCVv5 z9{(g>G&<+LdgcBgV}?b010!A$1DUa0jOZb%wKz09+Ac-_2Lz;@0|Mi@J(0F9wh;;= zDFWgPunCm?irraik3LMbN}*?uzY=O5?L@Ut^x98fxS^0irzqSYOt5=X#Gv|rQM|#7 z`wz~NDk;%N#vJmw+(s({1e3`>kGAJNGz6QG3o35G;`c6;m)l|8gF~i_YP|oQMcJvBi|M(wdEI zp-B^`RLr1-E3JLS)J3_nUlW08eBy@|v9!H!+5VeJOW&|$l_xukGTyYFtl>!14|6n| z)ld1+X7u%&R{bcugQ)Xcnlbs98GD9@oi(kxVY8|g99KwqwhD)z0j;umTWXARpyV!G zgzHq_1gcsKbE*H^HnQg*&U4&Tt?7so#E!?z<~+=1_EFuDT8a5|bhRBYpSSWYpSLC8 z@{dI*N)T%ss1tPoPzx5|MD^LjVAxvi3jxb{0XHG;sj94<=c#h7J>^v7Z4ni{qt?~Z zeF1dKm~JeS`nB?lG-aKk!2HW{@$L6ZJc*UXxcx>k0Ams;KmBFeunAUq=NU-p&E5$2 z-z|2akc$NwsmQ4YsMPi=46I8f8$!*ZYF*Y>t^cbGsz(jq4Vfo7>Y!aB?UC;HCnJ=G zx-f3MUG0m1c)&w_k>Rn?4pI?Y>UFkeW2jS& zQFq@XE8rRrIDNKPF-pIDQyY6NBh4M9m`Gg6Qx>rs7V#a5+m7k4sv2dspx9dKQhuEW ztg<}TrcyuN^>9^IhXV3F;NZGd_n&#_bEAj# zYDXz3fkE#Kz3ehH8xd& z`Ru-{YRU(o|M$KAovKpi_kPT$bbT$Y+lsr-sBNMdRHNA<)M!CfT`7%}Wn9xB>|Hzd zrHXAJdbr9~(NHz*sDuKkFjk8Z3uW+ct?K#XP`9dW!Ma^UfOY#eN;|-L`JWgqtD3ia z&FgHlJcO0ex1y9X+%x5mJcn=4&!wJwLgt@3vNieoOOTng0Z2++hDSKQC!ikGy%{Ov zPLt^5!zfUa@RRv_1S$QoOF)sseV{8HjGx*^9yMI4Yifd22|l|$WWs){-(}D6R+Mpc zqw?J{;@19Q@JS({E>!e!sLqU8jr|zYr2wy{v`>)Sw0tC>GJMC) zK>k$YJoSuL*yJY$g1nB>_JmR+3yS2et5!SeK-=%lVdgT_c#0*dfCs98-g{F`QOWtI z0>r(0il3qxy$Bz_xI0K`<@Oll6H@rUijM8WE&tdjWll_B%UfFaO0^uideE@AD}1}z zyH4Y&6C;NEI?P7Qk&<9XKUihi9ih=joQxM#%@GofK;koyh&+bHVpHZW z4b*<%xm(z-zIx-%XQ1@Qi4jLZsQC52s%P^fFJtEO!At%9aVb&hkN@*^jQh)KTUYhR zj0=JTVaJ(L_E5xXd>Na{EXV&dBP&icQ&qkgAnVQE)=#wdTf1_88}(<(`Yy&tQ8w(q z=|iVo`H{cyC{j-}SDh=w|H&xM#vNB|H8U9~UjhHhkmOfzlNcLsCAQ2ZON5iEe+$y# zA3|MR9}SsO94t%nIm^-?{Q2}3@HoSFgU{k9 zGQdvnpOAqgD{(F1gL#`Dr-by!_u~~o%0Ctw5M`IOhxEtS^e4H$mM`LQQLmNuQI4S@ zIHpm_xFef!$L_Zv`@`-cqYlm?i0tSvtE$jY!vz+A4t3Ip=^x*;_E> zdlfk2cSm(DGn26xqXNWtUg7;DR#*Dtu-@kIhd3g+YS1t2<;Md4~OuEm_{aLQ{vpHBR4e0J|XLaCP8+@4O0 z|F9W=99S2YSv6e1{TzQ4s-fwhW?L%*unradX|GLGAgC_IRj9KgCgHyx#hrv1_h(Aj zJgUK@dU}&R)B4vsA@D7{ zI`|sSN(!5YWJ>yyKp=HZpsjz2Jdtq!eU(fZapCAwYc+2vv>`5R?(|dyB$cuPVeWGt9h2f06)UXyK(OeJ$BfQjMdfM{@nT0LpPW1*kFv8YO zXNpUNJabn18Ry%s6sC)Z8Ip4z${^#Kdr%*o2X2)vHR`U6#ua>@P{WwQB#EQ~MVSn2 z5KUG$_hJ4(QI-JN$CgBMeXbjKQijBlqANYRRaxsA?I(VfM?9LvuHh<#akYm;DtkxQ zvhYQXuJLfue0EHlV}>XX^jZ%YO>93+-+vcmq$<1pG{EZ^*wCc7E2J-ObUlN|M8#UM z*3Ij%D#hK1pu%*gtOj;hn8=VUL|-gK51|1EyQ`W4C&ratC!8Fq%4}Dxl8OuU5<;Ba z-d<5+J4AZp$D-M^uHh+8kQ{ERLwQMI#4o6?F=}1r?Zc3rd)hMl(zZ%&huHCF`^&7#{^1dk37d44bZL9b7 zpr^6b`$dVx+Ujfv$oM$Eb`y^;XBF;FVqF8}u=KBjrBW<4t&|7g_urWH9jmG}vt5^E zpoc7dk6rVA_zkq{32{DwEHY=>4PvBG^y*4rFm=B;Y)VV0Ga!!+UK}=WwUzZYgbFUP zwvS)K=F^c&tcLX;N(D5!kv%k2uQyXgBipNL8cIctZIe2MZs8{U(fgn9hFsxL-w&h2 zCIC&}5(v?cTLNdQ+KYnsUd7^E&?c4czg)O^j=z48z+fy{VlXbta6U(xzb( zr;&M6C(rGWxb+|6hZsvgKlTG7ld8e1i@DcMIG1Fc8^9E z8!kcq5D+nuXLzzAGVICTLAfpLDa_WXbio-@W6;I6ckm02pN&Hyzbck*88DWzFS9$D zmq5PdNXj_$es~D-7Js|U@+b44PUt%+JKf5UTEx5PKzjIZak&(?yGCvG8+K*Y-c3VV zT2sM4)AxXp1=}#wKBdHDWOK8{aWAiS(Yj!|AIA$T<6?v0K9E~hfZ&PG@=)Fya&uZ) zZL2)q2S6cvJGANBm)dxSpYI1f7~V~GtXWM?m78Di5LLFeHWn)*VV9#P}z^yUi z8O3W6M!aGNo@}`4%~L6$Btv3Kj{ugs870l!)5tS(pfsgJcHt*+hdp<~h0i}>$Ifdh z5MIki8Irmd$!I>=P!8r}G@`woUzt9&*pJh(_I9zA5_y8VC>;bP;&Q80?2}a4!D}a)!%98tEs_1C9t$OJBG)?Rj?ie*&xJgFLVeiSz&bec7Y&zo0I!$Jm54kCr z-F0PnpA!QR5)7EiB=YaVQB+#e-VuXYu~tQcxcI z9E!WzdhE*Fn@iKKwlXRGssrdbUWnZ>u8&&9c{J}DyNZT$SpepIJ{BOd{75VHry|pl zz*x7G@H%BZhm*jOC|!bg0hH8<-A$|6g)m@crr3a82#$L#MBcwI!Bi#xKhJEHuS)Tk zH&%Ll5&A6rMyqj--$-{Xhc5t7`b(N$3i(BVjIJ?IFY@Td*TpXjebTC)wZBA3-NL4X z9&1=3!H4L}Ok%}m|oJ?(cPq%SC`_#|Q95mgHDfr=onQ z1Cwi~L1j9x1gE9Takwn`6Y+rfy*6T|U~d4Hyb`d~8?2$Ebmp5LBAJ>W55>=R{@cSS z8CG))>n$Eu;fF{f!ttqo?}I7uR24_qR?(Q=R%Iwf^EOrVwiBm@xtb>SvBOlWy$0MJ z+>6zAPii7t>Z!FHNpEeUr1O9(0Z!q zYXyjxRk-0R-3{Po{yyX&`6!s~jF7)eC;Hi8n|7ha>}&+BL;FxN!Dl~s_K15|vWaJ5 zAMQ#T_qVd741xD(LVs%rC|Ud$RrGiEzI#3LK2J%?c3h7v9bgHwlOwanS5cc8GGIIE z+5w`ERCQ&y1uSNc^IjZik3qd8`hXG#g-vw}HSZtNltFghCFH?B!4pn6e|#%*Qfo)> zI#o@}TrtbU@)h$oa8hyr&wf-H^`pq6;~zxI`lJH3os#mxrU)f#0inQG&^tgO>sj0# zxVPQP_1j7DL#*qkn#!oeX;LHHMd|r=gk0J~y5!OxAEC>I#i$4Te!g7@6-nMrNd?wd z;V$K4UZan3++3E?&aadFQuhh^Y55?6pYG7q(Y=RCZ+4h;pJVN%W<#wcDdTV-FV3j4 zU=sBh3NbnPyseRy71X~fTrDI_lFJ_BOsp+GzL@q~EXBHeuD{W(uyw~vA?{O_1WAE@UTUr_fE;ex1@?q~Q* zZf`)#uSI$A!@Kdr6kn|Y`G%b=83Z3~UxCGC<$=zMMS-b1g|8`oq;0NpW2uSZDBJu=8M5F`)3?f}bnlup* z=^_FmgorfhO+dPcbk6slO?ICp_ec zI(|%NR_(GHd0`gQgk)oqxWqJ8+12O(lq;RgdVE4F-yYOhJuyP!oogC7tSuv~9waXHyEcB&I!J zqI*RRe{z!%;=>pw8sD}FO=5zW9K=Lt)F&zqU_SaGW~F7K0vaHCh1;YxnIJx@!bD35 z0r>4|$*jO9_=DM;n!-2`*P&Km84olvU>GCByrUW!4bu_Jr`WXY2K%?{#;h#UHS+fp zOmpB7lf=$?8qs$#Blzh9$+X62YbI`>LtvRuJCGT>Ys7q*1<0D`ZC1b!h*mu{R?rki z$YVZqcntjVz-Cu9CV}WTUc>*mj1dw;G~z}LbXWtO4baE{csvmEn`vZC2tcezZ`1NJ z26!2r1#0B6w&)N1GTg?ub2mnaaO#&$!_Or#n`Zb#qc^*2EHR1^G7|&^Vj%_%^_%6FLP}o(PRRdz5L8B9e%GXEkD293%ME95z?t8NU+mW8%&+jg{sABV~5V(@%M@QaU<>4(pLOxzCESXL;r zEMF*-fshi2#ho>>?hvL~or_7LZC;I)1EC7!DF{d$o^G>e7?VH@JgwnZ(qm-c@;HsS zQwsyEg~3(R$SkWFA-;rwKKzg|>uuJw#Rx&WUK%UsAV$bPy4fTiW0LqOULyu0Yym%u zsL<5HCp!BpQDgDjjF6dNE+7_M*2r3qm}cdC=9Bm#--4Zim34(i9>2jfzXrl-!0uWa z(SIT%_@6~=uFi!m=Q44(kH$)ij0a@UUX56C7A8Ck^WC>uJDW+OJsc5Oxi2H_tS`Xb1;IRK{RM;;IjtQiRlttkaeIo;+qjRZ7>R8WryDYd2*@Ep}R~HKSJd|42;q6^QV{%OnDx;2N1|YNmzaC+8 zZ7t(K+>6&(>4q>uW<3pjrtG+E1L7Q5xiR+xd8VLE5^PNT zTtg#dq6COMu4op+h#pHpXfLX@sA%?Fl-3q zS#&`BcGqTK6XfnD7#-|L1Rx5CanG1|3%>(?Csv5+>Tg?ttHKuMyv44gm6tQyTI9DzpI&5IIKKd>VvL4321b0{I_5`2Axxe>Y(q zh(l>L@-u`R5FF%PL1$ic`|z{&?70b=BI2y#QiA0uf2F{(4;k0xkDqnk{V z<1R)CK1DKXkM@b6ZZ;DtGST)T;ln8Q*!%-;2J)X-8u0+O1@hfK8moL|M#z6M+4RSC z;vq%|#OJkaeuS-od>_ISH>Yc?nA1#Cw;8j5oVFGCwleXLtl7>(V;Q!{;h!2J^ z{&22FG`xy?V8?x%ZCDu-+u^7{jz>Nq#@)4jQ`Hkh(>Tyz;7X?0`VU5`c3!}5O0pKsSLFfvu-od zySPR+iO2Q}VfZi%>p%S9{~%ogt1fmhK+J*tiHfihF>56D1j{u1F+vQ;=~XmhR1!8A z12p`}AZ$Fq|O9rEP3Z7x! z2QmceBqZtpt6r=|e2CeyNiUmV_yI8yzjtV@;m_+~(^`+oJXdXuP$r2bwEkNH3bfGh z7jQx#H(s{+t`(D-Q}_fw1lw%t$0V@@&Pr^IwfSKo?t@M60b)a9G7a$AfQgM~HT>lk zB(g2A|6ggDB(6WOxptFDAf9*B$jz99fY^xNf!sV?BUl|q$k4JH@e$^B-mDsEYg+{R^V8h5)YXL#I7kCIej~PXFJ+ljrG5=nhCR-#_G70Y1(gOmL?pZ z;Y>a)Y4c-w{OmJv0qhxN z^V1950Q)A`?9apmu`jL7UQ{N2inG}dbpko5wM_yh9U$TmG0m>pm>V&;jX0QSuMxa7 zBjlt*us?o?IM};+cN>n7Aw;OnCOC`+_TL2ie~-*YYzfea=a=CpaMtUCY;KHZlK5+i zMvRF?yAXsB3vFZw6F|=Gh4~*p#F!IIwCH0aXX7SF6QHp&EWr{RD!&AE5?4%(ShI&| z#^ST(Ae%H3QD-8P1>p6>8#Qc71u+T4<0&=|Z!iJmGm8IbuvV#NobD}DCNk``IRupfIT>CI#Ge&yE*D{fxU$&h_gqY9X5bT? zz&t`6M}6Yr3Y#;iPn_PM@riSHnGO5^TO#orDiKH0+nk!tgeDt4vq4LV8krsw7xAK& zO?ha7D1}i1s~SuIL;$>>$OvJ9RdTdO&Op5xwGjUw_rVMYFC{XcT&FcYkrV1Ca&@-J3Dpw0(I~KDVQ-@9T8;Q76e$<1 z8=#Rh>oCncct5Zj)YFJhA7af1p^4&4ZKee>(G4aciY5}U|J1Eabgg5P`v5vU010hG z@J`I-AWvnR)gep}6VGB^N5e$nM>bPV;Y1W8Bl708$pftrc@Y7`XAPL>*uo~C#Y8?I z{TP+ZGkiY7X!dHvXD~%aNJ)f@Oy&VT!Gk25&+!T5{LvaQDFQPlIw0~xL%^zlRTYro znA(8|jn&9Lmy!SJN7vHHz%#pox1ZSbD99wy4IL1@Puuj`%p}pXh0Qw?m?XN)N+t-O zAa;e#=SP_&rXoWUvl?lv4hNZ!euzIe+gx6PqynzOQ;B0i$v}0-z)Ad0oQSu%gfhh0 zCN^i#7I7iK=KMG&h~K8$9GQrm0{%eK(uBeOe}pmFC)Q>@oCsJ=mulo3j9SwQpRJhH zF4?)70( za}}RBa0o^C5c%V6LIdzSM!6wLBTFJh1F;n$4CKXeHv7>gF%b8OzwvKig{0R=-;jz- z5*2IMl!hIFRXs!_OJri2f;+LB-N~#fm>G#T7TZ+8p(K#^NqBG@qa^xY)QIJ!86hu2 zAmR$z2Ua0y8Te$s8;r0@kJd=Bm1$&UEU`huB{n#el0*?S46I0)2*}*EG*-YNrpefX zSwLRvXY&zENPL*qW>gT9#CZsLeiH1z7S03Yxn~-&vKu3;cRJhj8i7Y8BbXcpQvj=H z9h<&L)j$r1T7lIYYXzduZjC&EIz$W1ut3a(lLM=2S&d8sq0;zZzo(})@?rpN1}=o! zd@!3y4SxS1lv!F`y~p?@=}ta1U4w_Go13K$tcVv*I$1un?v#EzqP_U%N>JY;SF*%Nj&iiZ%xYVZ54E#O= ziw~FySk>oim?{oe(R5MhDIjK}AACKXHq#FU=6zt^VlD5P4#)X1rOkWlt8 zQDm4#=Em>9$_W1eGCwp2tRglt{bi@s6^$?@qLQfrzW`$8U5%`^ml0NL-&*`w%j5#& z0AMx5JPt%LBhaXmLEF_3JwQL$k z@-`14B+)pBO>vY1R?T3I&x(d*K$c0bv7&N7Z8?}&HBlo+E`>@#bupr z0oxP9C)xCc=M#OP8De;_Mv7WUSkPQ-XN@R{>6!QegCM4%%wx7mv8`!qjX6I-ofV`PaBX*_5Ap@MN+*+IR$R$Ah(^?~+ zZp2x_MrO4{=(nW!FJdF+^kkX?uqSbNLNZ9rhry4}Y!cJsoERJkve}K%5x=4g@k7ZBs53n~hLr6-7?bppBx`=PHfQYEhgKGCvw5svs+AptdTYc`uvz z-Eg4Y%xWC25#=#@4e~^JP=RS=WtbCK;mCFx_(?cu0jCCXS{;p50FzOH`FQ?QAjGEe z5;VL7+oD<65v{PPFcOV|N(e^{oUc-6CP!@6Se4NT@kU;aY<>~Kg3nuML}h4$c%y;F z>RyZ4AZ>RCZ~GwS+Xr!Tk49Fjf{CFDv);lefmlDsX5CHfc5dP@2`UFx+u0iV2xBVS_z}B#J?sK)mRuk-kT81Yi|HoD-D>Y2;m~o)`p`5kq5aMj(L@ z1D@FYdjK;fR=@?z+Ek2z04RGW$YxL=>VqK&W8%Gdn*lvhzbDpz#hyVTc=k)!4_K8g zjl2tIB3g9Rh^L5O;>kiA6^p}Un2D&`L?d&KXPR6gcqkEqXqaNNI{*;^)`Z!7F^&mh z89o1B7LSD^JQ9c}fj0jjDG~oZ(a1%D5kj3s?yqc9qM0$VIRzvE0;HT`n?BrV%*J9D!GB zs%T_C2o1zF{7ziKR7+e%%o8W4+gw_T?HM?G+2#UFM;t{WB9hXx4)jBuhU$qexioUr zB&^Xvqr5h^GhtJKiK<xjX31-aBurXFx3h%0aFEygc59LQU#ly5 z&79M>2dUB#M)v93vm~@RJW74|uNkDC-7o{`Us)2K91i+6Hu0x4Io^JE$k2g98V?-a zqkDAy0fXKh*0T3I1G=iv4|pLps0g=I6n>=l-pQ+0tuWGN88EEJ5O1w}TX_MyR0VnuH|oa>eEZ#@t$M%R*CT==^ZwtrgTD?+v#FY;javz; z4}~P0JfB@<+`#iBe7`+FtslxOsqcsKf}1`mV>}q3S`Lws39&z=QP~FaNcGPrW>qp> zdIc3$+O+d)qmWQ{R+)v=)Y?XxO<&D7DyCK4OG4JrRYv-Rh|}pd1*|d-RrY@iwZa?8 zZ=wDx7z_BFvw#zvMW5vCz-+#(tud~GtmbX~j zt`-YzB3b9RC7aM)vWnd#YtUP=T)iY))kCstJ@M~Y$)5cwSy)+%T@02iF0;kPUzIF6 zOtQ^|B-?&Xu;~$!m91^DSr;VB79ttDB-v_|FI~=J>%qZq1v}W&V%Oe5pTi{ct?yy6 zCVegT>|M!T^s-pVJ{CLmw_u5#EY=y^$42DIn~=N+Mm81F$6~al1bZ+|u!`LUyZMB( zm=6WJ-B_@sP-tN?%5@fOKt;jQ&O*5gf`#`I%uFj-m5&6w-x8X+&DjdT{@^|~g|+labLj>oaU@G*nR_LO^bEU zDA~dJf}O7^+4vQb^=yUuL8y03vIoCNR&5uq_Y>^$ADj&VHBfH=>fH?RN!AP%yQ5-y zRJ@E115vRpDh@}*-RLkIDkh!dtQqKjQ?OCOVh=K*!G(g&sSWk7g34P<)+C$7;!a4m z;aABLcS}}qfM79yakldYXB+MdHZi@$?q-ACzZR@#1IYruMO|Mf$s+Pt?C4p^W*wF6 z>^|6am|!`sau$DuvtxHK+B6oc5+qrTC4#kh6Pj2fS*G@q1?95X!#^Y&aRjG(dnKDc zSg=PIIqL_4ZwnS`!4CrAQ;P)~QWyTSTC%)tB`cW&?VW;V55xY$_DFVmpkV3I*|^J` z)k25W(BbMVXz)vTaExSa(P6K4k`2mfv2N(_SfXU{KS`Dr9UeG~Iw0{c_yD(9PiW@y z0tiwI^;b$Z@h!<#WVP6hoi zvt5O=1)^Rf)N6}+tKn=B*Ew5z4SsVE_D*N9(^;XprGk~NkEmLQ!E``01sk6kW^03l@{+Sam~a+SYYU|7 zo=pWCi8|~aoU$MWQOaVoj|vuWT(II71)G#4*n~vEYP>I4zxo*LJs)SoQ8;gQ!In%A ztYbUD+TZ7F*!M8Q7jP&PyoQ3Kav-yP3IXtW0%eY&Oq0JkOM3|Rgy5kg1?$rQ*%8+o zJb+emNj7N=VtpLK^%KcTk4IA4fy{_D`(UDJ`yJZd46XTcz^jq*HV=lX@>y&;8oGgo z#AD7nkAk$1Fj+Jb>@GSU2Lal)7pz+lQZIzP*jBJT=&bTToXz?NojrgM@51XbpJc-C zefp#0hX}dPp|KwYEB`)X{wBgb#$wItT5Rhy*njR1=nU`w^l2zq-hYuE@$b10IlGGA z??d7$+o1C8n8h%HyBN*%3GjhW(FrnV$XNJB96m=Qa$u6vA7i%6gf!e5+JfLW(AF%p z*$)F~fmv@f=8|Iw-w~Kk+E#{1XTko(d&AaEFsq@{)fmY^NcLn7MpqHGoF&+@UigGI zs!WH|K_de&(G0?*^AOioAg)`d!*x*QIr0S%O(pC2jI(Y}kv+jd{ChfP!VOZg*m@S* zfXS*?6TxZ)<8vaMr*#Lx>S2Dqng@0?z!Ipk07m$Nv&$PLYxN!C8wCG=WU>ify#)>7 z`a=AE7r!TNz*G&+ZWL?_95U@+2(#clmy%B;nNI%Jf>9k)f`_+@S zMI;*=i20y|#ir~MY&*2I8s)2*xZlxY8`~psfs#)pTk-^hDvZx>1oLgEgj^nGu@{*v z)+!1k#f@=4V=43#yd9sT_F$!he^1yaSf>4$)A2cOuV8VwHVePEo&*O=kZdFby9&WJ zqQmyVa6B&9A>6x(>)o3RwgSJ03ap$qA)*PgN9IH zAqs~`*l4a~CvdG#3&CdN_kc7ARpk8_mn1WK~9&FwOAyL6%JF8+YZ)LG+7ECe}9hA1% z9*k~D8>|WHBgdx~EZaDEFFvnA^_O9W1{om0*GOJpVFk4qagGiP&P6np#Y7ef$8Kt| z^J}rd!9XUUZqK)1V$_*}+|kN68Ed^iF&$opov&a<`yC+!0#Bg8aj5MgV*Lz8a2h$_ z9JB!ff5UWr1b%Z0!hl2w)-65E@B-Q>k4`c2vejU7q}d`F1sh$`Vzp)Syk zfeEN5q(R)Cpv;yH6G&Dh9k2@j9zG9ACp+xh4E8Kzv6v~yV7cHAIR)#C&-3_fmK$1# z#mrU}t7v%jfOU97Y$nWsy7d|$Dn3QpZjXp54((0Dpt_-aQAEa6gj}7jSjC`$188_B zl0xM?aJ<#<)QPBs-0-L{;&ckuaNzQ}Ef~Gi0@n2+4ei67US{MWb3r9z7pRJ%n4k*U6VCGv5=R$!GFTCdIsJ1iUZ-LWFbgwe2a9d~6OjQw3crfj#3)On(dwANO28Zip^+*UTl@wx7d{`M@kzHOyVxJvT8@S;qdaoJ zi$Yjn_CpRqxNa3l-4Y?&5Qz*q;vzERQpA0&+*mmUV4acCV*QYsld!Hx`yt{#XcQVf zkJSlc^w~MgXe;4Z*dC4OWwEQnkh6Qj+p$BMGF-Aby|HJ*4r#;z$8UHEu`h-B26$!ta~WNNF^%8zCBv#sVHA zeJ+E|mm(*83Fq8_)e{=r`VeWnF|HNIG+h%ahhXW;;2MORxC&Z=u+iwSB+_^iv{JE= z#UhXko-FVo)JkAM_%@uWBvc2|=761Xtxh$;#^T@fC~neln4vE;(MPag{JR#MtaB`~ zAIj(W0d53Hut7c82$3*BkrR$9xO{_92|c zFN9}CBEyYFe8wU4#-c%JsM$oQ?o)K~G5q5bc=~qOdK)s@PE0^I5k|WNTMOZ;m6xpb zKUf1`k+BwY!&>-N&pFWE0jwVm!@0pT_*42sBqm%NzZ>~K)33Pk3$he(5DkJyP*M5H zkhnRf<=OZw3k`vBi*daN8g2)*La1&SeG=|(n1K=X#H>~hCMs&N@?aI}7H@&F`QcMl zAROq`98L}`g*S$el*eRO9{HbDz|4lu4u(TDU@0oqnTD_`fCU3sg2J;fYpuYnH4c^md5b6iw{%T~>`1cU|xc?08u$P#mAZQZmcSO>vfkd_N3DUiS{a5rxqNoa6 zpCJ+NEg!tcxW%hxJ@Olp0fZJZ8Fu7vM`r z;cQ75z-g?k(9Wb$*hwRKWx^a10UzpE4Ez06Snpj!e71(I&UUldLbdIY(U2cAj;aig zjr_h9#!BOiaoX5nY%`LKL}RhB(70q=GyXIhv%AJUFJBu!&m-x*bDu#)7MVxqxwPfdw zv0{ukVN4L8ipk<9u}6F-_KIgboye{_JuwRVjvFy7*63lpZ+v7-HWnCLjh)7C#+Sxb zqbd8>$jt^A`OG3_BeR9s*?imlg7-Dsv0~;Y=1jA_vB5lS^ftDbyUgXrXyX%Osd3+Y zV!Sld@QgegFJ!i0i}?z^hMT-Tdt{#DhxtF|zdWsIgm^9~Ld}!rJ6Xl=rcbnH7mZWK z225lITvug#YJ_FKZkXaFf5E>saJVhb89#_A=6KcksZrFoRrKJM#Cv9L^8>z*-{v>X z%I0%3gV-W^oA2}K<}GuYh!;iMv~c* z?>1_SAB|dKrm@SYBVt5p^Lx=sZ08${=AxzzOTmqKRlN z+K6_dt$0gp784C+HWN)n3-O+@)U3pBis8mrW<~y&7;4Nl=b5E=X+F$YV9qxqcp3hu zSZ020zQHT=TVk=f$SlXp^6O%W`K4Kbm*+RcyT(E@l3x>Z%o6;H_`)p7uZqvj;{39R zU>8MaHj=42mRYdFH%1_@FLsE@=3rA9JH-$D0v}{<7wg43@hPvvGl?u_JM#pMw_SjNj`!9Dh`Vq#$Uz?b2Bd__NiT#S=jf4Kj(9d zjbf9CHwNj8yk6ld{^vu{?h1g4mLXT-$WT!n$_f$%<5(d zb0E(w{t_}<2>G+YA)rW;!ROctTJct z9lSmNy@-hCZURrj2GhJtS}Ged3Yff!V9pxydcZRi?9N` zC@aVdvrrz!^7AS@H$N!yuuGyhUuSgZE5(G>-Z6uCRsIYAhgTN6#Lpu4SrIEH znV0zsBd5qEJ~J=z++w16k)Kmf)0%}#WiZp5rpe7TreR7mote!HGTWGKP0JK!TJsn$ zBO=6)=2f1=ukb%q<8)?W{uUpn=DjrP`$qB+{4-vTP2{b31vY@cWA@~|%y)Q@D8<@~ zETR}2#skgvW@fX4naS*E))gC#x;%#86L0gcg%S(I8qtV1<_&o_p1|Ai-F&UF%2;pw z$+z((VuqM2ii*6VqFKRgAP$Kk=1t?4vC{mW7ZC+Th}dhIKdGqnX5s8T&23@`AIiVu z7erOFBuf${%{(&~!T13pqnKy3O zTa3VZ@^29)x{JahzbIv{GY^?Vj5xCouf;E!L(SplFmn(u&MLA>tTJ0;gs>0H9DJ#m zCFYCOVx0I`#PY7Z3;%@wWv(_A@5sOQ@s7sN{1)HK|KdOK8+^a`MdTA@@p$DlKfyN} zXN>}4lMyL88~+%2%#Y0F=3R3pKgiGUzj-+^M0_YlnmhS4vl{=^JZN?leMLX-DJZt!~SU-K+K z#POtAG%U%=ixMKgdE7i<_7q3W(V`W*V4O6*6}v@$ED8R%2Ke6^;D2j?|E&T3w+8tC zvIcnL|Lq!pClsg~tU?3KbO|Nu1|;OK8?Y%bz#LuWm6y}>8*6D*@JgfD>)u_{w+H&y z!CP~A)yS1bALWT}Urm><6WPM-H}w)S?~inUOI^Ce3ZuGxKV-R)P6bXhqf-^4x8_u{iDnJ|LD36t zo8l&#E2_|oQ=2OOWaezX>FpdmDzkqn&s%}T^_x`mX5s5!`b&O)@cIg)m{U@}gQzkV z;g$WHbzV{2gjWZ^7x8^Yj~D`yD)W;A^9T zYA}fJ@f4&N71jCWMgzR9Sod{rEb7-7)uVJSAHl26Ta;f&e@<98> zubdlxH>Mm)zOm>K&zEvToyQ(aEl4Y}W%4;ZJDa+5xmRy#}S@Cr4Thm@~Qeqc=eQvf#Z@d{&0jxrCij_ zbtxdS>f&XR+&j>KElyd8gU5skjVBgY{_@6;m3AkEf8&i)+KcUyeDUkwc+2FAvyO7R zV40)5c}hX5tw$S)=J9^7iq2>jp59ha7wOcUqwa`9CnT4QK*;{ec-*O`aJ+4g;>dB{CZ$uH@Y@$V zop3Lbz*?QxPw<8*B^O2|YrpqN=c1#S>nHvCN$GsxDd!>;3?O@x%sH4~s>^A!l*by) zP)!%XmmFBmVgSXb-RECrn$L$~2CZ=biId*|BJ- zT6g~cu;|M3j#3q=Oxr2)f_pJ=pRn~<9zqm?9ut~>R3f#v#jQ*>XVr`R%@iAl<`uSU z6+zv?s^&$WPvv`t0QUD&Z^V_l$dq_w8!@@SF*2N~a^I#{A&tD(5>UOFr*!UO&Yv;(kcJnCAg+nsU)e zPzxToR&8+-jB9CfU(7^V`^>M?iZRwSbi9mzoMj~>vTX770@4nsgP>+6M?BA> zXe_GEk1#xoA~(&K$?RH+(uvdKIJjlk%Z2@yQo5EdP!uA4tPA8-XYRRv>*#7;pd*aC zZpxCywUFZ#TJ|wngtMu098>#CIC}b24Gx08bgqm;iByxIqvF0nu8N%~xDez-ftxM6 zW)<#qnXL z*KC75Ndey_RWs#j~i8M0YLb ztOPFv`_)X@m~_hHUUVY-WFB`A&c&vA-HXn${inRC^->|9qZdb4Yuszf8A`Dbr*A5# zM?zbUC8|saMshw(1bP$UdK@? z`<~WuyB?VN)i>de4V|Gp4|h9v0`k3LZi9}#?i6$MMYXlM$Q`#q%7`%U4p=W)wd&6m zciX4I>5J`3xECpZ>$Yf_xD#y+4#U)xYQs73FXTnlvW^xc+iWacDb*VfQ z^3E>XS-wVeYptv;00lW}AQ1$o*jU^xevxy!n9os)_`E9=-( zInid6a|6q{_@UoLX9M`SoU1@O)zfOPhJhtP?So5{SBVvbXKOO+t>k@KO%(bKh7Kue zzCop&og1A4>hV`aiK7_*^zgF&1*iq>KLsj^PAMs6?N!Xv^C~7E3rC~oF|@t2s98Cc z1ZrnDls(*AWX(*+fH5e09KA$El~1VIuSCMy;+a!dadxaHD~XijKIQbOrKLVklC>h9 zn3Ynu;1t%KQsGm{okqqMetoBF56F_bbRCrbBf0dLF~weA+7U5l6<0*(;v&C`T9huG zlFuniRDF#WwSKo%6&+IayYN}^`Cj8{?nURs=u9=&y`9vf+vrl=2^)7xJ6_#&FVDvJ zG6tCt{X^l@>T`1bks5AYr~k%LZvV~->~NGj5jYo{M!OfCHV;R;Z8`^;4QmSLfZaKZ zI#ScE?)2WgmfJg()Hb8wzGd4?UATiy@7)sSYaEM`p^h1va&E(AUzEUd{?(vSPG7&) z_UkKU{%%^wm7mUBwdQ}RBb-whCx)BWbxS&f`lYTrC?~KR#<+p)^u9mF?cM39;hSzp zj-C&{>9pybgEp<_YSi;YmfGWyVd5uN&6AN98G@TK}VyhWNJ&JamwNguWXru%kR}?3i zp+Tf;HRd-XB()Nqv6X7&O6OQ_Wh+-T&czaMxfdO0U-_1!1vhn-YVFG8bhNy+wdN+ITURzmiTT^P7oG8b z(as&OW6V75-BCDB^Lcx>q|;{Z4sM%{5@vSrtM0#_4}}t%tTuE~cLwKFo7T~-=3LC& z$-U@^GP#p0iesdVon0e2C8u_FOVT;8&H*glcvSG z>m=_HLK?j4nbNTL&ij6MoCWk>UHuw*k({i%c5{Wp^2C<-PB-y(O2eMDRT*eBu7X)! zReIZ5%G0XGo=NJ#T_Oy;0=*LI#zZUT~itl&0qzq^E+jbzEiV@=$~?j z)(_g7tDwt-Y-=W9!K0d0GG#)Q9)bQ6&KjP)#`ydl z(c51F9R(yoCA5&xb#0`o|Bfqyv&Pxg%U$^B-p~AyyvAwRTl7e2(2+|I_DXM8Vp?zM ziy!n6T~dlWOX|#hU$dn4^>uBJ6yrXG<7unw$XD8O#6LXJ5&snzsrkWw_41wH z9Y;gOM!6btRzZtKxl@bRH4A;{6!%E_#fPs*>bOd}(XZU0F=)S~#}e^~Y zH_ce54UbOV8|#kT5&6nkPYZtM%56USFT?%fcgOMfsBy0p_oTyjDIY9|{=d>;fsg-7 zl7x?6)3hFcn(^+C92eHcr1Q@A$(Z@G;jayLc@J5r%vkq52S-`g$2-b;#c|tx;`ZuH zJ->ePN^!>_s!#BUZ<}Jx1h=@eCd&V*v(j-weZi-0s5_nIp6J$g4EE_nw;$)?Q@@K| zKkfgSTimhLxzF68ImMkA7*A^3p8d5kfX7x^@jp#+mxZYnKY?24{F@OxYq*bOjcFnodurrnwS>h4cwlh4#zutXj%p7rkmi-R5YC>(TMRhy)&b@n|A(G%rUQ zN~fZxyU|8!(n6D(W>YJ<)XtfBD>lQcC7nif@#ZN0xwuvY z-CRU4{vP9w?XPsawuuZ@ar2}tV+S==(G#Z~GR7kwJk3@`ZB=Hr$fsiG%jo~^aGvfk zVc@{X*9?@-Gf6xhB>g`ZAu2Wx1RJo(RlFYJ!urWFwNaVppj6_QIH~ma6hE2P%n^AL zV%HR&wa9fOx~)}2KE23RL3CbTScNWmrKiM2GDw$fiIR)$l5u%?1(i70E$OIL+p?+h z;hsv&3&{t~d!^LMdC4Bp80{^w+dDVU$!PYGE)~*|LxsMAGcQUs>ZVp#{G#NIQhgL# ziejVN8Z|v?pf{OxVk(CcU(?R}LOxzxO|J5ap=ocaM@jD(EmH*+Iw$|mJDB=5y@R=2 zty$=Q_t$+-X(Re+{-(V!Au1r#{R9zOsdlPSptm^I}QQqSxbds$#7^8N<;wo-D z4jfX2u;&Qs8yeH0NE|`Ia-LA9xk2lYp6TFkX{n4g@l0n>OVop}3mJJ$_l%NtD_alqlDe+@sSO5&j-W z&jWOK^hDv?lRjHM( zKhhMTD^t7Mu+Lw9CrtT9x)UB9xahj{?$~w}xyn5Oq5~xDf2CBZRU%xC8Yu(R9k^p9%U-z5W)unR}-7h^;+N~m2yGO{LTR*C8ez$bC^W>mCs{R`HP=P#Dw@M!R zle*=;*`-g@^evj|_Nswv9Sy@awvOn<@t>jK9d*spsU658);=|Oohvat+0vS-s`{@( zq&6vGihMMhv;|b3)2)z8u^9X-Tn*Obzl3}u<<3M zS0Y?F=OyXBb4$ls1pxqL-=G1a*QvP%43zjc=X8!pEYhJ%lWc*2klF10LlCVWDb+U&Q0M_$&q zC@-H>-)wf*%AQ)MROI(=ElPLJxc^Y=zIVs%IlVZo%4~6`TThZa13A0CD53^!!&Gwj zJLlX=KeW;&r<`#XrD{GaMx~sDKMHRK}KeFB>)HPm)r5*;^UyDHYzYNbc8sHI~e{%Gg_X>?;S238^ zzlbLXUR6;$+#E<+a18gS+O@+qoF^?`L)SIGvfaz^dh$SBnLO~i+O^ZQAFZ|ZEmGYL z6}8K?y2qveQag6JF72`UP4&iZ*XkZOx}|pPb{eErgYJq}4YyU)k8XQ3aK{PmU$fws zXcUzXAX?W(W^qqtGL25y<b#%-Vg(nc<&F=&VKrzFp*Bh_=LfJntvr9n!OT@PO7|SU*S3@*5m^&FkvxPH0Pw z@9-qK?}B*TH>2HuXe5qv{QGxal3H^Vj~ALn;yYc@+1v%HXMdSl6*%ggnJ7;d52Rb! z)a^I1eEQG&S)4+Fq|dY}uq@6+{D$i+T=a-&;S!{d{O+zAJoz)L>Tt}>9G(!$26H4f zH;bsrl`JQOJSDQDM70>WpMME5srHNrb>sy0b*s-Ac{~OD*40f z(BXp?vc4L4Nfbzk>siEe-*fO$Lbsoge}GJAHZ6yzoF|}5+Hy?2^jbN}T!9?Nj2KTd@NS(}e=VrLoZZ@*HfAWb<~5Y_TuDa>y{B8=6bVhDLOqK|@=jfV zCbJ3%fm>~GjEUu?cMV0Wl`OP(q8&g*ID1sj-a~q*)l=a|rLMcvw9-iUgGA}X>9{uel@7XO}zs-p7z)t1{ekMvyu~$EeBooDA*U)=82Q_?u+Xkm|gQ z3S3G_^1i#D^JJBJYTkW!de(!bgGenPy;56WMgHxs4ruz;uW!-OQv-6%cX}k;`zNb9 z|F@gRY0lL3X%F5|HGSYND?NLpMs%xyy8S?er%WTB`;Aref80G3&5~NXv_onF?T+ba z7EY~Ql28{T37e7;h8ty6)youPet$qA^+21cKOVXR^~?dy)!=^}OL+eXKnwJ6@+1yw z^F4AWTkN8=9cUNTlI##T$p}@m9=WdKS(&#|w;s7Z;CZ9$EvkJX3U8F*{|1oaXsM0e z8pRU&T`EI|Q0dg#b*`;F<7lJ$KXJ$5S&Fq)cb+)jWq+B{mdDQ_i&*jO6;c+5zpNcp3QS7l(^T~45O}67B`BbTicux zQD?hEzJ&hgy-RqHEL~9QP6+-S&-NE?PoAUYSd?fs)TpQ$zjS+|HZoCarV#I{{a;O+ zP2cG#Cqq-5*t5v*hGq_T!6HAl5L|Fjes`_n%o%z@3s=(&nKNN)-3+R#A)PInQ%DCC zO)TA2fpjvfiZEpHlny<|06pkF_NDh_26eX<$KNwBoC(449NlGQRe<9OSH{{{diWL6 zU%A%Z(c_SJsJR>}dNJ1c=eavbkN5P_g%TRK%;_oPnSOfP4Xv$>@6n^TbgB!N&e5{x zu(gj}vUFF>wEl9Xo|F69CC><%@-1JC(9S({^xDt9Jy6ODDXQ{x)Ze);6Lt>NvS*x` z=vv5Acz|8_P#yjnspuOET4mAbcAzeq(DcJFfAL8&t@es)YFem$UK&R#zgzjz$>4;{ zON0GuD$jy(h%LwVZaBR1*N#Wnp;X9u?VZkSwRCi5JIt;h62tRTeXkwJlXN7}8QHsV z-F2VBxjUtIJjnYE&f#{6TB zYdPr&(ED`T`J*zaZ9h_7@V}3_Uk4`5{U6wYS+f(4+~YI4aYCy@Jv!Ra$GJ*#prnoz-b#8BKY)bUpkp2^VC93coq!KKb^fD<3+^d;({EsiL2j z)LIsL1{;UP;@~S))sj>0i7CyZdPFpfj!7O-bDSr{Ho>=8{nH;!nz|&d7svXQEYlR< zHTO?&9{qo$zR4n;FWyjhdU({`I5d(F{{|iizdD~#NNcevH+&4S_g$=@o&>p%6+J7h z@v3=N>3+|~e|`3ex{y^m-&Q2$>Sjr~6QHyMJxx8ErObKywOB15xz4Ag{(%j6lq!|o zbxqF-VIpK(ezhmTSYq=y|ML~GIB`gMNDcy_d!dHNz}*l23$`ysSoCK6SRVx ztnTG-(vNo!J4N-+>D+R1uO25Y(WYXYXWlU*RGwV!AUuyh;?>|>()soW-O`om$q4?K zfO^r;D67ikb}CaI(ehLBn@%E@$+9I8Q-t%-c10C(`WtLUj zf}M`MYoM7Zaj(~FrZ|s0vq+z7reb}6rQm-wq^O}wSWLYtkmu}Rwp}V%^_~Lvp7>aJ za<=F7mCseae2xJWt$X$0X*WHG#JRiEsHzr5AcD{5bJKq6wc!`4REX@5lHfcMJ{QtN z_Q8TARfKz%!Shs9ewlK*N_x@$Lw1_4w&j;8e`rBJ7}he9Yb_ud7d655)&Iev=Y(#d z+7T+9@1oOwO4p_bWQ(Zw(t`T2L&;2txZ}u1z%pr>qv1%6_S87L?9g z@}7O!5;W7fFZN}rn(?e?zapu2evDbgKQcXnFQp=fnnLiae!)vcw9GWNuhH4M-{DLB z!rbuoth<(>gwYR9c`MBEHjh6pr(U`=Gi@QVgNcOE7SlpG2 z-V)Ru(|g<-sN*p$@V>~a0ZnhGYNslUV&AEOC1ll<=<*zrY(y5n@VKbTRMHume#EQ0 zpqXkDb&+K$zLhe$q*p*a1Ui1$j3K1|7JK#FrQ8ypV~y|Cz*5d4ME{isEgQ`OThyIW zPTcCvitd`0|36UIk?WAI{MAZ-JI-^Gx>emN?W`g^cG*S^+&vDv%qimz&$F1{uI`m_ z4CCFF?ZB`W48*f{yEFo zO4@r9?p5g#QCtEf*yscVq)7r|10)F`77)oQD~ganMAt=3u$QHTyD*Cig1a^r2#Bt0 zfrz~!uof(|*gK%#HXGmb%$s}8JNFH%|J~p3Ja^8VIdkUBnKNfz9VP<+Pm{}S*PyUx zc(3JJQxd$$ct$FEdiimoY7-agvvQ!Pw{B()KL_@fmm#+$v^IhSFy;;F^f7PndBD~_ z8OxUuz5F<-KZ>CKyZ}VQ=?K4u3DHQayWK^a^ob)wu~lXx>DN1+o6<7^lucUNmn5qX z4o%2&Y?SIJ#r5BdOyWC3p9WXD7>+_lhQoF(pg;EkeIrv|mAv zV>U>}lNC#{nCs}sYMSFM{pwN*$II^i$mKjR1D?4o_RYaDVy!%D?8bRA9(AiJe_h&$~1M0sszs{J0> zG$ihx4>>PgM?){Y7M#qz5;bC`4Do7f!a45)(e+(~a~}S$KS4S7MR}$B@&S1*8IP0i z@S&EH@KhO@|EV^qo&1G1W(K#lYZqo$bG}9p1y&6 zLdZ|!5d_(OsE;cdkKL*u&k=RV5+J`(WU~2XgA7Stp6M{o1m@d-TG9Qm%aw~7 z$)L0RP?Ssdt&%xgYDdOVE9-tdjC`uJqYOAC(ZqzF)8X?A0bl!jusLs}ACSpD{g?K> zxFoax!8!bQz~S1$_>h$NLf3y&i_yt~S-9QCr%0W(|Y`w2U0n7VHJRi^A16F(z0ES|NrjrY#6OqqDL zZBz1W%Rr#DY*4HV8ML=jPSMp$P|-6pG3c_3Gml8_?b*%b$kVY_82wg!i%MZ9O6POE zy-{+TrEcAf04^@`&xyM?z$WIJU{fpEIWg8)7UaiTkdl2|K^4(KP{|L$AU0i_G1%~A zPCSPK5vzczhFOa0#$mGaJg*zdvbj`h$o6(pv>m1Bz*j=H7Q~v#vT;nK4DIy#M?Q#9 z4hz9yBlcg3HMp}^p5am_+|-R}96W=Yiqza#MMqFTah5l_Wv{n2J_nbWUMX@JwE`ml(sJ3k1VN8I&&J|W=fea} zkA~>7UqTQk?8OkF>@c+YYw3JZ+!YKkCZn~~T;xrLNgc?R!590051Vulj2pM12)=p- z&&0xgny^z_nL5dbofuVAc6gp_C!bC7=gH_CNo?w~u|Z*0&B6Z|X*SuPFHLaw*vtmW%Ln#G?9cBPCJuKTWWV$Vyn6`Q0u#YM zUhcfqMexl^Isvq|p4=}?8&QX}gL4O|xy&4#)e6wN?TDV|Hcj(x&8&BlO;bX%9*(tl z#-BIcm|<0hj<1T1hruos+r&pv8#potbmf!{dV5_ligj%4S|M`qE3Lm56@yBy4ibl~!LVoZ&-IT&?=_fUC7%9o?cC zlgi5t&C7+K@sP@~i6bKK`JDCWFYnFpdgL}TePO}i$bdTXqb6NGspMev>py1>l+tof zI=X|L3~_e91r7@qpxqX!>A4gf?c1O_4#r$v_DhCmmSH}+sJTC8)}bx9T>!-LTkxc9 z!c1=k%>FYN{Y&==NQw+c!NPA}^$z;hjF!m*FZ75GCc4L;fM=M=B41! z;D4uvt%{A%oGR0=^7fLh&v6tZU7yn^#;K(<%O};RDf$tXNbj`8&eonzIZRs#TN}oi zGsGFP;&*XZkVFT-$|kx7hJlAO9S`-YQ;G?z3?LI&B!-}dK?DW2WfVlkOwUramY(Hs z*U>qXCqZ+~Qp2ZUjx}DGaR5i4hw29q@(xEtoGnt*NLzFkU~6x~okIBSgwdOk;2~M? z*zv}ZFg|}F%xl$i_DQ0Nf)t^XcDFMM5_;BX&a=f*e2ovG@Ipp8WZ-xd{(N^UsBPq3 zRrE#rXwGd_IjlvvCo-$kPY-PjA?wQV>Lp~Q%zeYzMAVk!zTx0KDtC;Cj0Jajry!9y z+@bNr&LO$j^fXV*&4k+(&Xvs-epfIzHs_&}=$aAECln)AMe1NIkbu=;2ghxF)pD4vQBSiY+KkET8*r7S^n{P&#W6J>~ zFo*cz#;PQ`O7Sf?@=TbE1m$`Y36;i{TabZKmW%uV5V7Yq6Yechu4UW}@gAui%>-S3 z6SQ}`5()ZYc8nHY6o4+c(cUzxXy_AO9JJ57GP8vtV&pd|01cama;nIDbVqmt`mJoc z$)6L<99@BPfU&3$mG(Xeedenk!HG$Z+%%^cUPg*ZMS)6!5o)M<$~Meqf>)6|0>t_zT~79Ic(U^>A}XqX{$ z<|1XQ@!WX-6mg0`+bM!_w!2FOBkU_AHZRl^uHP_I=FIc1m?3zj{22O|xvslPX3zHn zF>?ML2J+QXT&0Ee%MA5uAn*-zJ5+P!_j{V9-EY8hg)D*k_WYodj+ierX36X-pV&ZO zwF!4(dezr}LcXkc6G!qjtMJ^T^Fkj3f; zR1sbkw@L1un8chr{AG#`o^F?#`qQOmIWD1^9*sAeCqD^shO34+0jSPV0DmAoUke1M z1a0CeVJ@3~c~y~l0eoTw%9Vu6Zn(Ko!i-nV50F1oUqs>K0AElCudl|4g6Q#A1)vL; zdErJoUZ|j-sWal(Oa>$mUTs8?4CntbK;o*cpBcJYnTvwX|5w!!RD+?!e?ip|36KY` zI^srw+AyT~3o4H+36SB+BWB0GLpJ}}`;Clx==xxU>Q1hY*!6fSar!sz{>C_ZcL9;y z{Ylp_K)mGZ~$FzKz2=s2V5VPzk8(Of%plM^FTaRX+3Je|4fWaO*7K_i_CxU z=l*^#G4Ah1!uOHE?l)QqTL$l!r7N8PDSVY5^Hm;zS*p4d^&CR8qmK?1!Oi$VB4#69 zCj5fk?71sZ7Q^qk#=-iltXt*fYP{)Hpu6=rc+(M&(gb5yb97s;$BhRrGox;>&L9V?uJc7gO-s>~+)~s` zzOMGFVJ!E0S@B5x$mHq~1`}0}hlx8XxnO>_EMM+MIb?wc+4LciU)8G8QZ<=bfbA&Pv z5|4XIwmoX&ajJfd$8D05zdN3^R*5!o=YKjF(gnA+iwfF!q0h*y$D9Ic_JPu%$NQ{& zA8IlO^XFgz2ah;1S#*ZhB|A*ocJdc32Kjt|bk)h=t%PcDn8o>mLOV&zPR?cri8l#Zh1=bng~rVM`r0Syq~tp1u>59CqAp1-H}Zw3a=U@Tw!pJIZqQp>>ixA8c$> z{HXuIvD&TEFevyZvPlWC83*<{H*ZgV=My%nIh&!l3D-$jgMKbK}({Slo>o9ah1Y4Z#E zK?RE-ZR3X_-%1HH-|9;_dYjJ+j)peBlKIn5L zjIPlt&f7cAt#t0EGR^qXk6=1^*Oz&+vL2Mq9Z^3?>|K8dF{h*h9QE=);gl5WM-L%7 z9X!STssZjhS0R5nj4%PU7!Y5rLf|qz;!T%Z>F_o?1E{RkFedrC;NWh#(5Ku{T$+7E zmkHxuu*0XuF)pWV3_*fI^PTb2QY+qgNpUn*tVZG&$frC9YM=g@8l1qNcg2UL{!HVo zyfH9x2>!fnHBx~iywO-Fj4tz`faTR74I^Bpkc6}o5;_czh<3-%cb%}*skuLE*nsDv zJ2D5!%O`}ARB$h^z3^VX5qjCQCgWJ?)&_|};V&?oAZ6nKhL7{sAie0-p-xTgJK-iN zSYGplyRJ4`*i>e{@3qhzgPO^K_kH+<7FxMvK5TQ)omY+Yxln$mDyVrCkR^wGco47s zaH$G4aYIqs+)!G8cFtPd?~VJ&&82-EdW5X|$ouVPy|tA5kNtWxyULML^RfSzZYUbedZVx~Say+?C;n=ixkMl^}{i@I?6tnJst?fp}%M_668)S<3g zLR!nx&)lkYl2sWoq?_FU{^x(X%Gl52sn4=H``}i78>#->(a;wk$^ma<+LD8=cW2^7 z*B4%O=#gm`OmS+5F}K@}hacgfu`wSxB+{)hZs#JyHG52Mg9eM&!$H}4cjh5cwdN^N zJLQS2y+V73DVI&KuFc1SZ_0jQOBW!T(ZwQ4o1Qq!C~RU zc)b-iS}>YAfi#+(WdHtn3p|%M0c6@$2kA^m>QR$@n28cL-(qLR8+n(0&&Y$hqUcJ@ zk@Dj=q0DrQQhnY*vj0DD$?+&&onqr#I*n9DsFA2+14?)u-|AS9U9bp@X$~Yn2&YS zQL3<}CH~lK27X=p1ALNjY=CA?Z4zCe!C*3XCu{*=PS(bd43RlMg$+rNRh3hoYNlkHHh)*AIM!KljT!TIGh74NisS-|~MaF6!_72NM)OZBbRiJ(Z=8 zq-(jG>`f$Er`}Ci@*z;wA&J&Jga6v}Z^nA2WMw4WlXjJ`Il2y$ZJ7yFcfgy8@SALn zFqrp5!xiY>Of-=_83|XzpALbV7CHnYh;(oG9VH#u7^!ZUXe+6w67ZNpR9sj?p`)a@ z5uiAO_muPEKH1y_n6yI%mxF=LSd|)!j34`c&F69j5c7qH&_7Oqhop zp2!L^e&G2;rXtbyz1TRc!F?O?UaVe+M*HHSdA!VRl5m@@Nvpkpman2yEL(Q1!MBqm zPCPA`8V0T6QwQ#2td@DRd7)%APqdS~JL_|ucp^}ycs!aIAnQF3D2P3l5aeoAm|>qU~Am2m0hCf)C1DLfov-+g^Xhvc5`5>dgaMj1bQ693|sv!V9efZDas z#_FeL_S8#cPK$)Aox)w?aHdxkw;VV8-xX7Qjw4bLF-^wa*3UdJ14WX2kl+&X(T9n8W?$K;%A+!+p3Ks6k8xV$T|U zF0!mTjU%d$cE!n@7Qjw29GTs-R0>-qawNNBJ!G6tlo8}~!d!Arm)))WdNa=J40K*C zI37e78#WzPafWlssp7d0;j4N9+Dgw1=Y!J%ybF&m4>-y1&R|=f?TR@>r zvwCM0QB{=|H;7xM@HoFgn45yzP-(;Ox5mugE*Bo}%`E?=f@5Uq4;0+~r|}*qqE1l* zJ6CQ!!5di4l`_WVh@_qliFzxv%$K`6xCIfNxdmk4y|MV1NrVAoF{-4xqa&l6tB!$3 z_k|=;F&5d#5fUb#n1qafx)>RHJ34tCF~P4t0+I6)k`cpN#8=EZPmHQB0wOmD7BpQa zg)kKdVT+}6m&AZnyv}TgA}hLhJv2>7!RhLo%yzv*R&-6I%Cy-JpxhyCy16D38@&_E z*4~ILJ}~PRg)vEYQFMDBL&5Lxh|MZo3L+C;#wtwzPJ^3E9W__sGFh4jKY!xm2#cpr z1<>9|-c6u$x6Br`9Nq1C8Jk#956`m+sjnc_>+55pj z)s7>iVrpi?%}IrswP1(iJkjDI=O+#@nZ?kqHYuF6{>S?boQw6LS9`uP$b< zc~Uy&`@P1=KnF(|Y>=A#M5@r02`g@73u1pAQHAgP0>A2LsP*p*BG<^e0xx3I^ptcT z?3;{<+{C6F**rL5s?@l(_B3P`+lmX2$Y-ofr1%-Y#-D)j>y9CwJ!3SV<^04p!Dyyi z7h`>&;~Z_p%~UwHZ?x=r=|0qJnOTr8$X|#01EvOyeSpGIBQP}oJC-W72IKWLN6+X~ZcJwMGQJlp*aeqCg5U1yxWyR@U z&*4)9)6DS8Yts4*?@pMb(-yE6`v=a;k=s?rpR-L{$wcSX2RojtR2W#_1_KrQPakvb zV2(Zi0AT*VPm;;gT{w=rmJ*;d_&S(c_mBF%cI6&D>9CIh_6;cObhgVAgk45sRJ6r4-HTetUo??NVx^G-6=0*uh!1D$=Z;|q%6uN88< z&YbN4FN=3V8`JM`{Ree*NUT+}Q)&zSkZ7rtVV=Bpp`(&FkhdAuR>R6}Am+c3#)?@O zdw|$@1y~7-#cas6Kn%WyWqZ<64(#Vr-!?J9V-yT`aQE<+mbgMgm` zk$+W5x%9X=vsH5W0My9x57X6=;FE_Qr!&nnt*HO!Qad)$LI%7A zk2(ECM$|j}f@tSsaOQMu3^;r#Q_gXb0^=!uCEuUpJw-i|n6fut%bar)u6#$7#l39D zM^?5Y{vEB5fs>H(z?0BEsXou|Y39anKg6+mJ4*0JDe$V+6zBLxww>?EnEQ}#+0;_< J-VS(a`2Ub$uiXFu diff --git a/utils/visx2/flow.c b/utils/visx2/flow.c index d2df8d5..cc3dd28 100644 --- a/utils/visx2/flow.c +++ b/utils/visx2/flow.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/visx2/soundpvs.c b/utils/visx2/soundpvs.c index 8ca1fb7..2fb9579 100644 --- a/utils/visx2/soundpvs.c +++ b/utils/visx2/soundpvs.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/visx2/vis.c b/utils/visx2/vis.c index ba973b5..892e9ff 100644 --- a/utils/visx2/vis.c +++ b/utils/visx2/vis.c @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. diff --git a/utils/visx2/vis.h b/utils/visx2/vis.h index 2fb8912..a912ae6 100644 --- a/utils/visx2/vis.h +++ b/utils/visx2/vis.h @@ -1,6 +1,6 @@ /*** * -* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. * * This product contains software technology licensed from Id * Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc.