From 8720c4518c81624c3e61ebb466fb70cc8cb68837 Mon Sep 17 00:00:00 2001 From: Andrey Akhmichin Date: Mon, 4 Nov 2019 23:40:16 +0500 Subject: [PATCH] Merge original "Cthulhu" source code. --- cl_dll/cthulhu/ImageLabel.h | 32 + cl_dll/cthulhu/readbook.cpp | 90 ++ cl_dll/cthulhu/sanity.cpp | 146 ++ cl_dll/ev_hldm.cpp | 1114 ++------------ cl_dll/ev_hldm.h | 59 +- cl_dll/hl/hl_baseentity.cpp | 1 + cl_dll/hl/hl_events.cpp | 41 +- cl_dll/hl/hl_objects.cpp | 69 - cl_dll/hl/hl_weapons.cpp | 112 +- cl_dll/hud.cpp | 8 +- cl_dll/hud.h | 32 +- cl_dll/hud_msg.cpp | 8 - dlls/aflock.cpp | 5 +- dlls/barney.cpp | 9 +- dlls/basemonster.h | 8 +- dlls/bigmomma.cpp | 208 +-- dlls/bmodels.cpp | 44 +- dlls/cbase.cpp | 10 +- dlls/cbase.h | 15 +- dlls/client.cpp | 20 +- dlls/combat.cpp | 38 +- dlls/controller.cpp | 2 +- dlls/crossbow.cpp | 4 +- dlls/crowbar.cpp | 1 + dlls/cthulhu/Chicken.cpp | 423 ++++++ dlls/cthulhu/Chicken.h | 48 + dlls/cthulhu/ClimbingMonster.cpp | 216 +++ dlls/cthulhu/ClimbingMonster.h | 33 + dlls/cthulhu/Cow.cpp | 133 ++ dlls/cthulhu/Cow.h | 26 + dlls/cthulhu/Cthonian.cpp | 1154 +++++++++++++++ dlls/cthulhu/Cthonian.h | 62 + dlls/cthulhu/Dagger.cpp | 328 +++++ dlls/cthulhu/Dagger.h | 36 + dlls/cthulhu/DeepOne.cpp | 442 ++++++ dlls/cthulhu/DeepOne.h | 52 + dlls/cthulhu/DimensionalShambler.cpp | 418 ++++++ dlls/cthulhu/DreadName.cpp | 183 +++ dlls/cthulhu/DreadName.h | 32 + dlls/cthulhu/DunwichHorror.cpp | 990 +++++++++++++ dlls/cthulhu/FormlessSpawn.cpp | 493 +++++++ dlls/cthulhu/FormlessSpawn.h | 45 + dlls/cthulhu/Gangster.cpp | 1963 +++++++++++++++++++++++++ dlls/cthulhu/Gangster.h | 88 ++ dlls/cthulhu/Ghoul.cpp | 330 +++++ dlls/cthulhu/Ghoul.h | 39 + dlls/cthulhu/GreatRace.cpp | 780 ++++++++++ dlls/cthulhu/GreatRace.h | 59 + dlls/cthulhu/HuntingHorror.cpp | 986 +++++++++++++ dlls/cthulhu/HuntingHorror.h | 80 ++ dlls/cthulhu/NightGaunt.cpp | 980 +++++++++++++ dlls/cthulhu/NightGaunt.h | 79 + dlls/cthulhu/PowderOfIbn.cpp | 196 +++ dlls/cthulhu/PowderOfIbn.h | 59 + dlls/cthulhu/Priest.cpp | 1131 +++++++++++++++ dlls/cthulhu/Priest.h | 72 + dlls/cthulhu/Rifle.cpp | 342 +++++ dlls/cthulhu/Rifle.h | 53 + dlls/cthulhu/SerpentMan.cpp | 739 ++++++++++ dlls/cthulhu/SerpentMan.h | 62 + dlls/cthulhu/SerpentStaff.cpp | 247 ++++ dlls/cthulhu/SerpentStaff.h | 35 + dlls/cthulhu/Shrivelling.h | 63 + dlls/cthulhu/SirHenry.cpp | 1764 +++++++++++++++++++++++ dlls/cthulhu/Snake.cpp | 249 ++++ dlls/cthulhu/Snake.h | 49 + dlls/cthulhu/Spiral.cpp | 105 ++ dlls/cthulhu/Spiral.h | 16 + dlls/cthulhu/Stukabat.cpp | 1952 +++++++++++++++++++++++++ dlls/cthulhu/Stukabat.h | 133 ++ dlls/cthulhu/SwordCane.cpp | 336 +++++ dlls/cthulhu/SwordCane.h | 37 + dlls/cthulhu/TRex.cpp | 1115 ++++++++++++++ dlls/cthulhu/TRex.h | 86 ++ dlls/cthulhu/TommyGun.cpp | 219 +++ dlls/cthulhu/TommyGun.h | 46 + dlls/cthulhu/Yodan.cpp | 882 ++++++++++++ dlls/cthulhu/Yodan.h | 63 + dlls/cthulhu/alien_egg.cpp | 313 ++++ dlls/cthulhu/alien_egg.h | 34 + dlls/cthulhu/bm.cpp | 188 +++ dlls/cthulhu/bm.h | 55 + dlls/cthulhu/book.cpp | 173 +++ dlls/cthulhu/butler.cpp | 1280 +++++++++++++++++ dlls/cthulhu/butler.h | 88 ++ dlls/cthulhu/charm.cpp | 422 ++++++ dlls/cthulhu/civilian.cpp | 1468 +++++++++++++++++++ dlls/cthulhu/crowbar.h | 26 + dlls/cthulhu/cthulhu.cpp | 1142 +++++++++++++++ dlls/cthulhu/cultist.cpp | 1996 ++++++++++++++++++++++++++ dlls/cthulhu/cultist.h | 79 + dlls/cthulhu/defaultai.h | 99 ++ dlls/cthulhu/drainlife.cpp | 411 ++++++ dlls/cthulhu/drainlife.h | 60 + dlls/cthulhu/dynamite.cpp | 225 +++ dlls/cthulhu/dynamite.h | 61 + dlls/cthulhu/eihortvictim.cpp | 295 ++++ dlls/cthulhu/eihortvictim.h | 32 + dlls/cthulhu/elder_sign.cpp | 377 +++++ dlls/cthulhu/elder_sign.h | 106 ++ dlls/cthulhu/furniture.cpp | 246 ++++ dlls/cthulhu/golem.cpp | 571 ++++++++ dlls/cthulhu/hellhound.cpp | 498 +++++++ dlls/cthulhu/hellhound.h | 69 + dlls/cthulhu/hologram.cpp | 612 ++++++++ dlls/cthulhu/kingpin.cpp | 366 +++++ dlls/cthulhu/laserspot.cpp | 84 ++ dlls/cthulhu/lightning_gun.cpp | 319 ++++ dlls/cthulhu/lightning_gun.h | 60 + dlls/cthulhu/molotov.cpp | 224 +++ dlls/cthulhu/molotov.h | 61 + dlls/cthulhu/monster_PowderOfIbn.cpp | 311 ++++ dlls/cthulhu/monster_PowderOfIbn.h | 32 + dlls/cthulhu/monster_dynamite.cpp | 336 +++++ dlls/cthulhu/monster_dynamite.h | 30 + dlls/cthulhu/monster_molotov.cpp | 527 +++++++ dlls/cthulhu/monster_molotov.h | 49 + dlls/cthulhu/monsterstate.cpp | 235 +++ dlls/cthulhu/orderly.cpp | 1462 +++++++++++++++++++ dlls/cthulhu/orderly.h | 70 + dlls/cthulhu/policeman.cpp | 901 ++++++++++++ dlls/cthulhu/ranulf.cpp | 817 +++++++++++ dlls/cthulhu/ranulf.h | 60 + dlls/cthulhu/revolver.cpp | 265 ++++ dlls/cthulhu/revolver.h | 60 + dlls/cthulhu/rlyeh_seal.cpp | 402 ++++++ dlls/cthulhu/rlyeh_seal.h | 119 ++ dlls/cthulhu/servitor.cpp | 625 ++++++++ dlls/cthulhu/shotgun.cpp | 347 +++++ dlls/cthulhu/shrivelling.cpp | 500 +++++++ dlls/cthulhu/spider.cpp | 575 ++++++++ dlls/cthulhu/stukagrenade.cpp | 278 ++++ dlls/cthulhu/stukagrenade.h | 29 + dlls/cthulhu/szlachta.cpp | 317 ++++ dlls/cthulhu/teleport.cpp | 622 ++++++++ dlls/cthulhu/tindalos.cpp | 336 +++++ dlls/cthulhu/tindalos.h | 50 + dlls/cthulhu/triggers.h | 671 +++++++++ dlls/cthulhu/werewolf.cpp | 494 +++++++ dlls/cthulhu/werewolf.h | 42 + dlls/cthulhu/wolf.cpp | 299 ++++ dlls/cthulhu/wolf.h | 41 + dlls/cthulhu/wraith.cpp | 277 ++++ dlls/cthulhu/wraith.h | 39 + dlls/defaultai.cpp | 64 +- dlls/effects.cpp | 67 +- dlls/effects.h | 39 + dlls/func_break.cpp | 44 +- dlls/game.cpp | 572 +++++++- dlls/gamerules.cpp | 129 +- dlls/gargantua.cpp | 77 +- dlls/genericmonster.cpp | 5 +- dlls/headcrab.cpp | 7 +- dlls/items.cpp | 27 +- dlls/locus.cpp | 2 +- dlls/monstermaker.cpp | 28 +- dlls/monsters.cpp | 124 +- dlls/monsters.h | 1 + dlls/movewith.cpp | 14 +- dlls/mp5.cpp | 8 +- dlls/multiplay_gamerules.cpp | 8 +- dlls/plats.cpp | 10 +- dlls/player.cpp | 220 ++- dlls/player.h | 6 +- dlls/python.cpp | 4 +- dlls/rat.cpp | 366 ++++- dlls/roach.cpp | 3 +- dlls/schedule.h | 4 +- dlls/scientist.cpp | 9 +- dlls/scripted.cpp | 105 +- dlls/skill.h | 85 +- dlls/squadmonster.cpp | 14 +- dlls/squadmonster.h | 45 +- dlls/talkmonster.cpp | 4 +- dlls/talkmonster.h | 2 +- dlls/tentacle.cpp | 7 +- dlls/triggers.cpp | 876 ++++------- dlls/util.cpp | 11 +- dlls/util.h | 1 + dlls/weapons.cpp | 151 +- dlls/weapons.h | 746 +++------- dlls/world.cpp | 2 +- dlls/zombie.cpp | 98 +- engine/progdefs.h | 3 +- pm_shared/pm_shared.c | 6 +- 185 files changed, 46704 insertions(+), 3173 deletions(-) create mode 100755 cl_dll/cthulhu/ImageLabel.h create mode 100755 cl_dll/cthulhu/readbook.cpp create mode 100755 cl_dll/cthulhu/sanity.cpp create mode 100755 dlls/cthulhu/Chicken.cpp create mode 100755 dlls/cthulhu/Chicken.h create mode 100755 dlls/cthulhu/ClimbingMonster.cpp create mode 100755 dlls/cthulhu/ClimbingMonster.h create mode 100755 dlls/cthulhu/Cow.cpp create mode 100755 dlls/cthulhu/Cow.h create mode 100755 dlls/cthulhu/Cthonian.cpp create mode 100755 dlls/cthulhu/Cthonian.h create mode 100755 dlls/cthulhu/Dagger.cpp create mode 100755 dlls/cthulhu/Dagger.h create mode 100755 dlls/cthulhu/DeepOne.cpp create mode 100755 dlls/cthulhu/DeepOne.h create mode 100755 dlls/cthulhu/DimensionalShambler.cpp create mode 100755 dlls/cthulhu/DreadName.cpp create mode 100755 dlls/cthulhu/DreadName.h create mode 100755 dlls/cthulhu/DunwichHorror.cpp create mode 100755 dlls/cthulhu/FormlessSpawn.cpp create mode 100755 dlls/cthulhu/FormlessSpawn.h create mode 100755 dlls/cthulhu/Gangster.cpp create mode 100755 dlls/cthulhu/Gangster.h create mode 100755 dlls/cthulhu/Ghoul.cpp create mode 100755 dlls/cthulhu/Ghoul.h create mode 100755 dlls/cthulhu/GreatRace.cpp create mode 100755 dlls/cthulhu/GreatRace.h create mode 100755 dlls/cthulhu/HuntingHorror.cpp create mode 100755 dlls/cthulhu/HuntingHorror.h create mode 100755 dlls/cthulhu/NightGaunt.cpp create mode 100755 dlls/cthulhu/NightGaunt.h create mode 100755 dlls/cthulhu/PowderOfIbn.cpp create mode 100755 dlls/cthulhu/PowderOfIbn.h create mode 100755 dlls/cthulhu/Priest.cpp create mode 100755 dlls/cthulhu/Priest.h create mode 100755 dlls/cthulhu/Rifle.cpp create mode 100755 dlls/cthulhu/Rifle.h create mode 100755 dlls/cthulhu/SerpentMan.cpp create mode 100755 dlls/cthulhu/SerpentMan.h create mode 100755 dlls/cthulhu/SerpentStaff.cpp create mode 100755 dlls/cthulhu/SerpentStaff.h create mode 100755 dlls/cthulhu/Shrivelling.h create mode 100755 dlls/cthulhu/SirHenry.cpp create mode 100755 dlls/cthulhu/Snake.cpp create mode 100755 dlls/cthulhu/Snake.h create mode 100755 dlls/cthulhu/Spiral.cpp create mode 100755 dlls/cthulhu/Spiral.h create mode 100755 dlls/cthulhu/Stukabat.cpp create mode 100755 dlls/cthulhu/Stukabat.h create mode 100755 dlls/cthulhu/SwordCane.cpp create mode 100755 dlls/cthulhu/SwordCane.h create mode 100755 dlls/cthulhu/TRex.cpp create mode 100755 dlls/cthulhu/TRex.h create mode 100755 dlls/cthulhu/TommyGun.cpp create mode 100755 dlls/cthulhu/TommyGun.h create mode 100755 dlls/cthulhu/Yodan.cpp create mode 100755 dlls/cthulhu/Yodan.h create mode 100755 dlls/cthulhu/alien_egg.cpp create mode 100755 dlls/cthulhu/alien_egg.h create mode 100755 dlls/cthulhu/bm.cpp create mode 100755 dlls/cthulhu/bm.h create mode 100755 dlls/cthulhu/book.cpp create mode 100755 dlls/cthulhu/butler.cpp create mode 100755 dlls/cthulhu/butler.h create mode 100755 dlls/cthulhu/charm.cpp create mode 100755 dlls/cthulhu/civilian.cpp create mode 100755 dlls/cthulhu/crowbar.h create mode 100755 dlls/cthulhu/cthulhu.cpp create mode 100755 dlls/cthulhu/cultist.cpp create mode 100755 dlls/cthulhu/cultist.h create mode 100755 dlls/cthulhu/defaultai.h create mode 100755 dlls/cthulhu/drainlife.cpp create mode 100755 dlls/cthulhu/drainlife.h create mode 100755 dlls/cthulhu/dynamite.cpp create mode 100755 dlls/cthulhu/dynamite.h create mode 100755 dlls/cthulhu/eihortvictim.cpp create mode 100755 dlls/cthulhu/eihortvictim.h create mode 100755 dlls/cthulhu/elder_sign.cpp create mode 100755 dlls/cthulhu/elder_sign.h create mode 100755 dlls/cthulhu/furniture.cpp create mode 100755 dlls/cthulhu/golem.cpp create mode 100755 dlls/cthulhu/hellhound.cpp create mode 100755 dlls/cthulhu/hellhound.h create mode 100755 dlls/cthulhu/hologram.cpp create mode 100755 dlls/cthulhu/kingpin.cpp create mode 100755 dlls/cthulhu/laserspot.cpp create mode 100755 dlls/cthulhu/lightning_gun.cpp create mode 100755 dlls/cthulhu/lightning_gun.h create mode 100755 dlls/cthulhu/molotov.cpp create mode 100755 dlls/cthulhu/molotov.h create mode 100755 dlls/cthulhu/monster_PowderOfIbn.cpp create mode 100755 dlls/cthulhu/monster_PowderOfIbn.h create mode 100755 dlls/cthulhu/monster_dynamite.cpp create mode 100755 dlls/cthulhu/monster_dynamite.h create mode 100755 dlls/cthulhu/monster_molotov.cpp create mode 100755 dlls/cthulhu/monster_molotov.h create mode 100755 dlls/cthulhu/monsterstate.cpp create mode 100755 dlls/cthulhu/orderly.cpp create mode 100755 dlls/cthulhu/orderly.h create mode 100755 dlls/cthulhu/policeman.cpp create mode 100755 dlls/cthulhu/ranulf.cpp create mode 100755 dlls/cthulhu/ranulf.h create mode 100755 dlls/cthulhu/revolver.cpp create mode 100755 dlls/cthulhu/revolver.h create mode 100755 dlls/cthulhu/rlyeh_seal.cpp create mode 100755 dlls/cthulhu/rlyeh_seal.h create mode 100755 dlls/cthulhu/servitor.cpp create mode 100755 dlls/cthulhu/shotgun.cpp create mode 100755 dlls/cthulhu/shrivelling.cpp create mode 100755 dlls/cthulhu/spider.cpp create mode 100755 dlls/cthulhu/stukagrenade.cpp create mode 100755 dlls/cthulhu/stukagrenade.h create mode 100755 dlls/cthulhu/szlachta.cpp create mode 100755 dlls/cthulhu/teleport.cpp create mode 100755 dlls/cthulhu/tindalos.cpp create mode 100755 dlls/cthulhu/tindalos.h create mode 100755 dlls/cthulhu/triggers.h create mode 100755 dlls/cthulhu/werewolf.cpp create mode 100755 dlls/cthulhu/werewolf.h create mode 100755 dlls/cthulhu/wolf.cpp create mode 100755 dlls/cthulhu/wolf.h create mode 100755 dlls/cthulhu/wraith.cpp create mode 100755 dlls/cthulhu/wraith.h diff --git a/cl_dll/cthulhu/ImageLabel.h b/cl_dll/cthulhu/ImageLabel.h new file mode 100755 index 00000000..8dfee6a0 --- /dev/null +++ b/cl_dll/cthulhu/ImageLabel.h @@ -0,0 +1,32 @@ + +#ifndef IMAGELABEL_H +#define IMAGELABEL_H + +#include +#include +#include + +using namespace vgui; + +// Wrapper for an Image Label without a background +class CImageLabel : public Label +{ +public: + BitmapTGA *m_pTGA; + +public: + CImageLabel( const char* pImageName,int x,int y ); + CImageLabel( const char* pImageName,int x,int y,int wide,int tall ); + ~CImageLabel(); + + virtual int getImageTall(); + virtual int getImageWide(); + + virtual void paintBackground() + { + // Do nothing, so the background's left transparent. + } +}; + +#endif + diff --git a/cl_dll/cthulhu/readbook.cpp b/cl_dll/cthulhu/readbook.cpp new file mode 100755 index 00000000..7f29e1ea --- /dev/null +++ b/cl_dll/cthulhu/readbook.cpp @@ -0,0 +1,90 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// battery.cpp +// +// implementation of CHudReadBook class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" +#include "vgui_TeamFortressViewport.h" + +#include +#include + +DECLARE_MESSAGE(m_ReadBook, ReadBook) + +int CHudReadBook::Init(void) +{ + ConsolePrint ("Initialising ReadBook\n"); + + pImage = NULL; + + HOOK_MESSAGE(ReadBook); + + gHUD.AddHudElem(this); + + InitHUDData(); + + return 1; +}; + + +int CHudReadBook::VidInit(void) +{ + return 1; +}; + +int CHudReadBook:: MsgFunc_ReadBook(const char *pszName, int iSize, void *pbuf ) +{ + ConsolePrint ("Beginning ReadBook\n"); + m_iFlags |= HUD_ACTIVE; + + BEGIN_READ( pbuf, iSize ); + char* szName = READ_STRING(); + + if (strcmp(szName,"") == 0) + { + ConsolePrint ("Beginning remove ReadBook Image\n"); + if (pImage) + { + ConsolePrint ("Removing ReadBook Image\n"); + gViewPort->removeChild(pImage); + // I think the deletion is managed by the framework...anyway, I do not get a memory leak + // but if I keep the delete, then I get an exception ONLY in 640x480. Go figure... + //pImage->setParent(NULL); + //delete pImage; + pImage = NULL; + } + } + else + { + ConsolePrint ("Beginning create ReadBook Image\n"); + if (pImage == NULL) + { + ConsolePrint ("Creating ReadBook Image\n"); + ConsolePrint ("Image name : "); + ConsolePrint (szName); + ConsolePrint ("\n"); + pImage = new CImageLabel(szName,0,YRES(30)); + pImage->setParent(gViewPort); + } + } + + return 1; +} + diff --git a/cl_dll/cthulhu/sanity.cpp b/cl_dll/cthulhu/sanity.cpp new file mode 100755 index 00000000..17a78cbe --- /dev/null +++ b/cl_dll/cthulhu/sanity.cpp @@ -0,0 +1,146 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// battery.cpp +// +// implementation of CHudSanity class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE(m_Sanity, Sanity) + +int CHudSanity::Init(void) +{ + m_iSan = 100; + m_fFade = 0; + m_iFlags = 0; + + HOOK_MESSAGE(Sanity); + + gHUD.AddHudElem(this); + + return 1; +}; + + +int CHudSanity::VidInit(void) +{ + int HUD_san_zero = gHUD.GetSpriteIndex( "san_zero" ); + int HUD_san_full = gHUD.GetSpriteIndex( "san_full" ); + + m_hSprite1 = m_hSprite2 = 0; // delaying get sprite handles until we know the sprites are loaded + m_prc1 = &gHUD.GetSpriteRect( HUD_san_zero ); + m_prc2 = &gHUD.GetSpriteRect( HUD_san_full ); + m_iHeight = m_prc2->bottom - m_prc1->top; + m_fFade = 0; + return 1; +}; + +int CHudSanity:: MsgFunc_Sanity(const char *pszName, int iSize, void *pbuf ) +{ + m_iFlags |= HUD_ACTIVE; + + BEGIN_READ( pbuf, iSize ); + int x = READ_SHORT(); + + if (x != m_iSan) + { + m_fFade = FADE_TIME; + m_iSan = x; + } + + return 1; +} + + +int CHudSanity::Draw(float flTime) +{ + if ( gHUD.m_iHideHUDDisplay & HIDEHUD_HEALTH ) + return 1; + + int r, g, b, x, y, a; + wrect_t rc; +// wrect_t rc_zero; + + rc = *m_prc2; + rc.top += m_iHeight * ((float)(100-(min(100,m_iSan))) * 0.01); // sanity can go from 0 to 100 so * 0.01 goes from 0 to 1 + +// rc_zero = *m_prc1; +// rc_zero.top += m_iHeight * ((float)(min(100,m_iSan)) * 0.01); // sanity can go from 0 to 100 so * 0.01 goes from 0 to 1 + + UnpackRGB(r,g,b, RGB_YELLOWISH); + + if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) + return 1; + + // Has health changed? Flash the health # + if (m_fFade) + { + if (m_fFade > FADE_TIME) + m_fFade = FADE_TIME; + + m_fFade -= (gHUD.m_flTimeDelta * 20); + if (m_fFade <= 0) + { + a = 128; + m_fFade = 0; + } + + // Fade the health number back to dim + + a = MIN_ALPHA + (m_fFade/FADE_TIME) * 128; + + } + else + a = MIN_ALPHA; + + ScaleColors(r, g, b, a ); + + int iOffset = (m_prc1->bottom - m_prc1->top)/6; + + y = ScreenHeight - gHUD.m_iFontHeight - gHUD.m_iFontHeight / 2; + x = ScreenWidth/5; + + // make sure we have the right sprite handles + if ( !m_hSprite1 ) + m_hSprite1 = gHUD.GetSprite( gHUD.GetSpriteIndex( "san_zero" ) ); + if ( !m_hSprite2 ) + m_hSprite2 = gHUD.GetSprite( gHUD.GetSpriteIndex( "san_full" ) ); + + SPR_Set(m_hSprite1, r, g, b ); + SPR_DrawAdditive( 0, x, y - iOffset, m_prc1); +// if (rc_zero.bottom > rc_zero.top) +// { +// SPR_Set(m_hSprite1, r, g, b ); +// SPR_DrawAdditive( 0, x, y - iOffset + (rc_zero.top - m_prc1->top), &rc_zero); +// } + + if (rc.bottom > rc.top) + { + SPR_Set(m_hSprite2, r, g, b ); + SPR_DrawAdditive( 0, x, y - iOffset + (rc.top - m_prc2->top), &rc); + } + + x += (m_prc1->right - m_prc1->left); + x = gHUD.DrawHudNumber(x, y, DHN_3DIGITS | DHN_DRAWZERO, m_iSan, r, g, b); + + return 1; +} \ No newline at end of file diff --git a/cl_dll/ev_hldm.cpp b/cl_dll/ev_hldm.cpp index f8fefad5..3ef24096 100644 --- a/cl_dll/ev_hldm.cpp +++ b/cl_dll/ev_hldm.cpp @@ -50,24 +50,10 @@ extern cvar_t *cl_lw; extern "C" { // HLDM -void EV_FireGlock1( struct event_args_s *args ); -void EV_FireGlock2( struct event_args_s *args ); void EV_FireShotGunSingle( struct event_args_s *args ); void EV_FireShotGunDouble( struct event_args_s *args ); -void EV_FireMP5( struct event_args_s *args ); -void EV_FireMP52( struct event_args_s *args ); -void EV_FirePython( struct event_args_s *args ); -void EV_FireGauss( struct event_args_s *args ); -void EV_SpinGauss( struct event_args_s *args ); -void EV_Crowbar( struct event_args_s *args ); -void EV_FireCrossbow( struct event_args_s *args ); -void EV_FireCrossbow2( struct event_args_s *args ); -void EV_FireRpg( struct event_args_s *args ); -void EV_EgonFire( struct event_args_s *args ); -void EV_EgonStop( struct event_args_s *args ); -void EV_HornetGunFire( struct event_args_s *args ); -void EV_TripmineFire( struct event_args_s *args ); -void EV_SnarkFire( struct event_args_s *args ); +void EV_FireTommy( struct event_args_s *args ); +void EV_FireRifle( struct event_args_s *args ); void EV_TrainPitchAdjust( struct event_args_s *args ); } @@ -85,6 +71,8 @@ void EV_TrainPitchAdjust( struct event_args_s *args ); #define VECTOR_CONE_15DEGREES Vector( 0.13053, 0.13053, 0.13053 ) #define VECTOR_CONE_20DEGREES Vector( 0.17365, 0.17365, 0.17365 ) +#define SND_CHANGE_PITCH (1 << 7) // duplicated in protocol.h change sound pitch + // play a strike sound based on the texture that was hit by the attack traceline. VecSrc/VecEnd are the // original traceline endpoints used by the attacker, iBulletType is the type of bullet that hit the texture. // returns volume of strike instrument (crowbar) to play @@ -313,10 +301,10 @@ void EV_HLDM_DecalGunshot( pmtrace_t *pTrace, int iBulletType ) { case BULLET_PLAYER_9MM: case BULLET_MONSTER_9MM: - case BULLET_PLAYER_MP5: - case BULLET_MONSTER_MP5: - case BULLET_PLAYER_BUCKSHOT: - case BULLET_PLAYER_357: + // case BULLET_PLAYER_MP5: + // case BULLET_MONSTER_MP5: + case BULLET_PLAYER_SHOTGUN: + // case BULLET_PLAYER_357: default: // smoke and decal EV_HLDM_GunshotDecalTrace( pTrace, EV_HLDM_DamageDecal( pe ) ); @@ -387,28 +375,16 @@ void EV_HLDM_FireBullets( int idx, float *forward, float *right, float *up, int vec3_t vecDir, vecEnd; float x, y, z; - //We randomize for the Shotgun. - if( iBulletType == BULLET_PLAYER_BUCKSHOT ) - { - do{ - x = gEngfuncs.pfnRandomFloat( -0.5, 0.5 ) + gEngfuncs.pfnRandomFloat( -0.5, 0.5 ); - y = gEngfuncs.pfnRandomFloat( -0.5, 0.5 ) + gEngfuncs.pfnRandomFloat( -0.5, 0.5 ); - z = x * x + y * y; - }while( z > 1 ); + do{ + x = gEngfuncs.pfnRandomFloat( -0.5, 0.5 ) + gEngfuncs.pfnRandomFloat( -0.5, 0.5 ); + y = gEngfuncs.pfnRandomFloat( -0.5, 0.5 ) + gEngfuncs.pfnRandomFloat( -0.5, 0.5 ); + z = x * x + y * y; + }while( z > 1 ); - for( i = 0 ; i < 3; i++ ) - { - vecDir[i] = vecDirShooting[i] + x * flSpreadX * right[i] + y * flSpreadY * up [i]; - vecEnd[i] = vecSrc[i] + flDistance * vecDir[i]; - } - }//But other guns already have their spread randomized in the synched spread. - else + for( i = 0 ; i < 3; i++ ) { - for( i = 0 ; i < 3; i++ ) - { - vecDir[i] = vecDirShooting[i] + flSpreadX * right[i] + flSpreadY * up [i]; - vecEnd[i] = vecSrc[i] + flDistance * vecDir[i]; - } + vecDir[i] = vecDirShooting[i] + x * flSpreadX * right[i] + y * flSpreadY * up [i]; + vecEnd[i] = vecSrc[i] + flDistance * vecDir[i]; } gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); @@ -431,8 +407,11 @@ void EV_HLDM_FireBullets( int idx, float *forward, float *right, float *up, int { default: case BULLET_PLAYER_9MM: - EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); - EV_HLDM_DecalGunshot( &tr, iBulletType ); + if( !tracer ) + { + EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); + EV_HLDM_DecalGunshot( &tr, iBulletType ); + } break; case BULLET_PLAYER_MP5: if( !tracer ) @@ -441,12 +420,18 @@ void EV_HLDM_FireBullets( int idx, float *forward, float *right, float *up, int EV_HLDM_DecalGunshot( &tr, iBulletType ); } break; - case BULLET_PLAYER_BUCKSHOT: - EV_HLDM_DecalGunshot( &tr, iBulletType ); + case BULLET_PLAYER_SHOTGUN: + if( !tracer ) + { + EV_HLDM_DecalGunshot( &tr, iBulletType ); + } break; case BULLET_PLAYER_357: - EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); - EV_HLDM_DecalGunshot( &tr, iBulletType ); + if( !tracer ) + { + EV_HLDM_PlayTextureSound( idx, &tr, vecSrc, vecEnd, iBulletType ); + EV_HLDM_DecalGunshot( &tr, iBulletType ); + } break; } } @@ -456,9 +441,9 @@ void EV_HLDM_FireBullets( int idx, float *forward, float *right, float *up, int } //====================== -// GLOCK START +// REVOLVER START //====================== -void EV_FireGlock1( event_args_t *args ) +void EV_FireRevolver1( event_args_t *args ) { int idx; vec3_t origin; @@ -485,8 +470,7 @@ void EV_FireGlock1( event_args_t *args ) if( EV_IsLocal( idx ) ) { EV_MuzzleFlash(); - gEngfuncs.pEventAPI->EV_WeaponAnimation( empty ? GLOCK_SHOOT_EMPTY : GLOCK_SHOOT, 2 ); - + gEngfuncs.pEventAPI->EV_WeaponAnimation( REVOLVER_FIRE, 2 ); V_PunchAxis( 0, -2.0 ); } @@ -494,7 +478,15 @@ void EV_FireGlock1( event_args_t *args ) EV_EjectBrass( ShellOrigin, ShellVelocity, angles[YAW], shell, TE_BOUNCE_SHELL ); - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/pl_gun3.wav", gEngfuncs.pfnRandomFloat( 0.92, 1.0 ), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + if( gEngfuncs.pfnRandomLong( 0, 1 ) == 0 ) + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/revolver_shot1.wav", gEngfuncs.pfnRandomFloat( 0.92, 1.0 ), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + } + else + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/revolver_shot2.wav", gEngfuncs.pfnRandomFloat( 0.92, 1.0 ), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + } + // gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/pl_gun3.wav", gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); EV_GetGunPosition( args, vecSrc, origin ); @@ -503,7 +495,7 @@ void EV_FireGlock1( event_args_t *args ) EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_PLAYER_9MM, 0, 0, args->fparam1, args->fparam2 ); } -void EV_FireGlock2( event_args_t *args ) +void EV_FireRevolver2( event_args_t *args ) { int idx; vec3_t origin; @@ -531,7 +523,7 @@ void EV_FireGlock2( event_args_t *args ) { // Add muzzle flash to current weapon model EV_MuzzleFlash(); - gEngfuncs.pEventAPI->EV_WeaponAnimation( empty ? GLOCK_SHOOT_EMPTY : GLOCK_SHOOT, 2 ); + gEngfuncs.pEventAPI->EV_WeaponAnimation( REVOLVER_FIRE, 2 ); V_PunchAxis( 0, -2.0 ); } @@ -540,7 +532,15 @@ void EV_FireGlock2( event_args_t *args ) EV_EjectBrass ( ShellOrigin, ShellVelocity, angles[YAW], shell, TE_BOUNCE_SHELL ); - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/pl_gun3.wav", gEngfuncs.pfnRandomFloat( 0.92, 1.0 ), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + if( gEngfuncs.pfnRandomLong( 0, 1 ) == 0 ) + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/revolver_shot1.wav", gEngfuncs.pfnRandomFloat( 0.92, 1.0 ), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + } + else + { + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/revolver_shot2.wav", gEngfuncs.pfnRandomFloat( 0.92, 1.0 ), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); + } + // gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/pl_gun3.wav", gEngfuncs.pfnRandomFloat( 0.92, 1.0 ), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); EV_GetGunPosition( args, vecSrc, origin ); @@ -549,7 +549,7 @@ void EV_FireGlock2( event_args_t *args ) EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_PLAYER_9MM, 0, &g_tracerCount[idx - 1], args->fparam1, args->fparam2 ); } //====================== -// GLOCK END +// REVOLVER END //====================== //====================== @@ -584,7 +584,7 @@ void EV_FireShotGunDouble( event_args_t *args ) { // Add muzzle flash to current weapon model EV_MuzzleFlash(); - gEngfuncs.pEventAPI->EV_WeaponAnimation( SHOTGUN_FIRE2, 2 ); + gEngfuncs.pEventAPI->EV_WeaponAnimation( SHOTGUN_FIRE, 2 ); V_PunchAxis( 0, -10.0 ); } @@ -592,7 +592,7 @@ void EV_FireShotGunDouble( event_args_t *args ) { EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 32, -12, 6 ); - EV_EjectBrass( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHOTSHELL ); + // EV_EjectBrass( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHOTSHELL ); } gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/dbarrel1.wav", gEngfuncs.pfnRandomFloat( 0.98, 1.0 ), ATTN_NORM, 0, 85 + gEngfuncs.pfnRandomLong( 0, 0x1f ) ); @@ -638,14 +638,14 @@ void EV_FireShotGunSingle( event_args_t *args ) { // Add muzzle flash to current weapon model EV_MuzzleFlash(); - gEngfuncs.pEventAPI->EV_WeaponAnimation( SHOTGUN_FIRE, 2 ); + gEngfuncs.pEventAPI->EV_WeaponAnimation( SHOTGUN_FIRE2, 2 ); V_PunchAxis( 0, -5.0 ); } EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 32, -12, 6 ); - EV_EjectBrass ( ShellOrigin, ShellVelocity, angles[YAW], shell, TE_BOUNCE_SHOTSHELL ); + // EV_EjectBrass ( ShellOrigin, ShellVelocity, angles[YAW], shell, TE_BOUNCE_SHOTSHELL ); gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/sbarrel1.wav", gEngfuncs.pfnRandomFloat( 0.95, 1.0 ), ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong( 0, 0x1f ) ); @@ -654,11 +654,11 @@ void EV_FireShotGunSingle( event_args_t *args ) if( gEngfuncs.GetMaxClients() > 1 ) { - EV_HLDM_FireBullets( idx, forward, right, up, 4, vecSrc, vecAiming, 2048, BULLET_PLAYER_BUCKSHOT, 0, &g_tracerCount[idx - 1], 0.08716, 0.04362 ); + EV_HLDM_FireBullets( idx, forward, right, up, 4, vecSrc, vecAiming, 2048, BULLET_PLAYER_SHOTGUN, 0, &g_tracerCount[idx - 1], 0.08716, 0.04362 ); } else { - EV_HLDM_FireBullets( idx, forward, right, up, 6, vecSrc, vecAiming, 2048, BULLET_PLAYER_BUCKSHOT, 0, &g_tracerCount[idx - 1], 0.08716, 0.08716 ); + EV_HLDM_FireBullets( idx, forward, right, up, 6, vecSrc, vecAiming, 2048, BULLET_PLAYER_SHOTGUN, 0, &g_tracerCount[idx - 1], 0.08716, 0.08716 ); } } //====================== @@ -666,7 +666,7 @@ void EV_FireShotGunSingle( event_args_t *args ) //====================== //====================== -// MP5 START +// TOMMYGUN START //====================== void EV_FireMP5( event_args_t *args ) { @@ -695,7 +695,7 @@ void EV_FireMP5( event_args_t *args ) { // Add muzzle flash to current weapon model EV_MuzzleFlash(); - gEngfuncs.pEventAPI->EV_WeaponAnimation( MP5_FIRE1 + gEngfuncs.pfnRandomLong( 0, 2 ), 2 ); + gEngfuncs.pEventAPI->EV_WeaponAnimation( TOMMYGUN_FIRE1 + gEngfuncs.pfnRandomLong( 0, 1 ), 2 ); V_PunchAxis( 0, gEngfuncs.pfnRandomFloat( -2, 2 ) ); } @@ -707,10 +707,10 @@ void EV_FireMP5( event_args_t *args ) switch( gEngfuncs.pfnRandomLong( 0, 1 ) ) { case 0: - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/hks1.wav", 1, ATTN_NORM, 0, 94 + gEngfuncs.pfnRandomLong( 0, 0xf ) ); + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/tommy_shoot1.wav", 1, ATTN_NORM, 0, 94 + gEngfuncs.pfnRandomLong( 0, 0xf ) ); break; case 1: - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/hks2.wav", 1, ATTN_NORM, 0, 94 + gEngfuncs.pfnRandomLong( 0, 0xf ) ); + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/tommy_shoot2.wav", 1, ATTN_NORM, 0, 94 + gEngfuncs.pfnRandomLong( 0, 0xf ) ); break; } @@ -726,48 +726,23 @@ void EV_FireMP5( event_args_t *args ) EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_PLAYER_MP5, 2, &g_tracerCount[idx - 1], args->fparam1, args->fparam2 ); } } - -// We only predict the animation and sound -// The grenade is still launched from the server. -void EV_FireMP52( event_args_t *args ) -{ - int idx; - vec3_t origin; - - idx = args->entindex; - VectorCopy( args->origin, origin ); - - if( EV_IsLocal( idx ) ) - { - gEngfuncs.pEventAPI->EV_WeaponAnimation( MP5_LAUNCH, 2 ); - V_PunchAxis( 0, -10 ); - } - - switch( gEngfuncs.pfnRandomLong( 0, 1 ) ) - { - case 0: - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/glauncher.wav", 1, ATTN_NORM, 0, 94 + gEngfuncs.pfnRandomLong( 0, 0xf ) ); - break; - case 1: - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/glauncher2.wav", 1, ATTN_NORM, 0, 94 + gEngfuncs.pfnRandomLong( 0, 0xf ) ); - break; - } -} //====================== -// MP5 END +// TOMMYGUN END //====================== //====================== -// PHYTON START -// ( .357 ) +// RIFLE START //====================== -void EV_FirePython( event_args_t *args ) +void EV_FireRifle( event_args_t *args ) { int idx; vec3_t origin; vec3_t angles; vec3_t velocity; + vec3_t ShellVelocity; + vec3_t ShellOrigin; + int shell; vec3_t vecSrc, vecAiming; vec3_t up, right, forward; //float flSpread = 0.01; @@ -779,956 +754,31 @@ void EV_FirePython( event_args_t *args ) AngleVectors( angles, forward, right, up ); + shell = gEngfuncs.pEventAPI->EV_FindModelIndex ("models/rifleshell.mdl");// brass shell + if( EV_IsLocal( idx ) ) { - // Python uses different body in multiplayer versus single player - int multiplayer = gEngfuncs.GetMaxClients() == 1 ? 0 : 1; - // Add muzzle flash to current weapon model EV_MuzzleFlash(); - gEngfuncs.pEventAPI->EV_WeaponAnimation( PYTHON_FIRE1, multiplayer ? 1 : 0 ); + gEngfuncs.pEventAPI->EV_WeaponAnimation( RIFLE_FIRE1, 2 ); - V_PunchAxis( 0, -10.0 ); + V_PunchAxis( 0, -2.0 ); } - switch( gEngfuncs.pfnRandomLong( 0, 1 ) ) - { - case 0: - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/357_shot1.wav", gEngfuncs.pfnRandomFloat( 0.8, 0.9 ), ATTN_NORM, 0, PITCH_NORM ); - break; - case 1: - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/357_shot2.wav", gEngfuncs.pfnRandomFloat( 0.8, 0.9 ), ATTN_NORM, 0, PITCH_NORM ); - break; - } + EV_GetDefaultShellInfo( args, origin, velocity, ShellVelocity, ShellOrigin, forward, right, up, 20, -12, 4 ); + + EV_EjectBrass ( ShellOrigin, ShellVelocity, angles[ YAW ], shell, TE_BOUNCE_SHELL ); + + gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/rifle_fire.wav", gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong( 0, 3 ) ); EV_GetGunPosition( args, vecSrc, origin ); VectorCopy( forward, vecAiming ); - EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_PLAYER_357, 0, 0, args->fparam1, args->fparam2 ); + EV_HLDM_FireBullets( idx, forward, right, up, 1, vecSrc, vecAiming, 8192, BULLET_PLAYER_RIFLE, 0, 0, args->fparam1, args->fparam2 ); } //====================== -// PHYTON END -// ( .357 ) -//====================== - -//====================== -// GAUSS START -//====================== -#define SND_CHANGE_PITCH (1 << 7) // duplicated in protocol.h change sound pitch - -void EV_SpinGauss( event_args_t *args ) -{ - int idx; - vec3_t origin; - vec3_t angles; - vec3_t velocity; - int iSoundState = 0; - - int pitch; - - idx = args->entindex; - VectorCopy( args->origin, origin ); - VectorCopy( args->angles, angles ); - VectorCopy( args->velocity, velocity ); - - pitch = args->iparam1; - - iSoundState = args->bparam1 ? SND_CHANGE_PITCH : 0; - - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "ambience/pulsemachine.wav", 1.0, ATTN_NORM, iSoundState, pitch ); -} - -/* -============================== -EV_StopPreviousGauss - -============================== -*/ -void EV_StopPreviousGauss( int idx ) -{ - // Make sure we don't have a gauss spin event in the queue for this guy - gEngfuncs.pEventAPI->EV_KillEvents( idx, "events/gaussspin.sc" ); - gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_WEAPON, "ambience/pulsemachine.wav" ); -} - -extern float g_flApplyVel; - -void EV_FireGauss( event_args_t *args ) -{ - int idx; - vec3_t origin; - vec3_t angles; - vec3_t velocity; - float flDamage = args->fparam1; - //int primaryfire = args->bparam1; - - int m_fPrimaryFire = args->bparam1; - //int m_iWeaponVolume = GAUSS_PRIMARY_FIRE_VOLUME; - vec3_t vecSrc; - vec3_t vecDest; - //edict_t *pentIgnore; - pmtrace_t tr, beam_tr; - float flMaxFrac = 1.0; - //int nTotal = 0; - int fHasPunched = 0; - int fFirstBeam = 1; - int nMaxHits = 10; - physent_t *pEntity; - int m_iBeam, m_iGlow, m_iBalls; - vec3_t up, right, forward; - - idx = args->entindex; - VectorCopy( args->origin, origin ); - VectorCopy( args->angles, angles ); - VectorCopy( args->velocity, velocity ); - - if( args->bparam2 ) - { - EV_StopPreviousGauss( idx ); - return; - } - - //Con_Printf( "Firing gauss with %f\n", flDamage ); - EV_GetGunPosition( args, vecSrc, origin ); - - m_iBeam = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/smoke.spr" ); - m_iBalls = m_iGlow = gEngfuncs.pEventAPI->EV_FindModelIndex( "sprites/hotglow.spr" ); - - AngleVectors( angles, forward, right, up ); - - VectorMA( vecSrc, 8192, forward, vecDest ); - - if( EV_IsLocal( idx ) ) - { - V_PunchAxis( 0, -2.0 ); - gEngfuncs.pEventAPI->EV_WeaponAnimation( GAUSS_FIRE2, 2 ); - - if( m_fPrimaryFire == false ) - g_flApplyVel = flDamage; - } - - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/gauss2.wav", 0.5 + flDamage * ( 1.0 / 400.0 ), ATTN_NORM, 0, 85 + gEngfuncs.pfnRandomLong( 0, 0x1f ) ); - - while( flDamage > 10 && nMaxHits > 0 ) - { - nMaxHits--; - - gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); - - // Store off the old count - gEngfuncs.pEventAPI->EV_PushPMStates(); - - // Now add in all of the players. - gEngfuncs.pEventAPI->EV_SetSolidPlayers( idx - 1 ); - - gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); - gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecDest, PM_STUDIO_BOX, -1, &tr ); - - gEngfuncs.pEventAPI->EV_PopPMStates(); - - if( tr.allsolid ) - break; - - if( fFirstBeam ) - { - if( EV_IsLocal( idx ) ) - { - // Add muzzle flash to current weapon model - EV_MuzzleFlash(); - } - fFirstBeam = 0; - - gEngfuncs.pEfxAPI->R_BeamEntPoint( - idx | 0x1000, - tr.endpos, - m_iBeam, - 0.1, - m_fPrimaryFire ? 1.0 : 2.5, - 0.0, - m_fPrimaryFire ? 128.0 : flDamage, - 0, - 0, - 0, - m_fPrimaryFire ? 255 : 255, - m_fPrimaryFire ? 128 : 255, - m_fPrimaryFire ? 0 : 255 - ); - } - else - { - gEngfuncs.pEfxAPI->R_BeamPoints( vecSrc, - tr.endpos, - m_iBeam, - 0.1, - m_fPrimaryFire ? 1.0 : 2.5, - 0.0, - m_fPrimaryFire ? 128.0 : flDamage, - 0, - 0, - 0, - m_fPrimaryFire ? 255 : 255, - m_fPrimaryFire ? 128 : 255, - m_fPrimaryFire ? 0 : 255 - ); - } - - pEntity = gEngfuncs.pEventAPI->EV_GetPhysent( tr.ent ); - if( pEntity == NULL ) - break; - - if( pEntity->solid == SOLID_BSP ) - { - float n; - - //pentIgnore = NULL; - - n = -DotProduct( tr.plane.normal, forward ); - - if( n < 0.5 ) // 60 degrees - { - // ALERT( at_console, "reflect %f\n", n ); - // reflect - vec3_t r; - - VectorMA( forward, 2.0 * n, tr.plane.normal, r ); - - flMaxFrac = flMaxFrac - tr.fraction; - - VectorCopy( r, forward ); - - VectorMA( tr.endpos, 8.0, forward, vecSrc ); - VectorMA( vecSrc, 8192.0, forward, vecDest ); - - gEngfuncs.pEfxAPI->R_TempSprite( tr.endpos, vec3_origin, 0.2, m_iGlow, kRenderGlow, kRenderFxNoDissipation, flDamage * n / 255.0, flDamage * n * 0.5 * 0.1, FTENT_FADEOUT ); - - vec3_t fwd; - VectorAdd( tr.endpos, tr.plane.normal, fwd ); - - gEngfuncs.pEfxAPI->R_Sprite_Trail( TE_SPRITETRAIL, tr.endpos, fwd, m_iBalls, 3, 0.1, gEngfuncs.pfnRandomFloat( 10, 20 ) / 100.0, 100, - 255, 100 ); - - // lose energy - if( n == 0 ) - { - n = 0.1; - } - - flDamage = flDamage * ( 1 - n ); - } - else - { - // tunnel - EV_HLDM_DecalGunshot( &tr, BULLET_MONSTER_12MM ); - - gEngfuncs.pEfxAPI->R_TempSprite( tr.endpos, vec3_origin, 1.0, m_iGlow, kRenderGlow, kRenderFxNoDissipation, flDamage / 255.0, 6.0, FTENT_FADEOUT ); - - // limit it to one hole punch - if( fHasPunched ) - { - break; - } - fHasPunched = 1; - - // try punching through wall if secondary attack (primary is incapable of breaking through) - if( !m_fPrimaryFire ) - { - vec3_t start; - - VectorMA( tr.endpos, 8.0, forward, start ); - - // Store off the old count - gEngfuncs.pEventAPI->EV_PushPMStates(); - - // Now add in all of the players. - gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); - - gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); - gEngfuncs.pEventAPI->EV_PlayerTrace( start, vecDest, PM_STUDIO_BOX, -1, &beam_tr ); - - if( !beam_tr.allsolid ) - { - vec3_t delta; - - // trace backwards to find exit point - gEngfuncs.pEventAPI->EV_PlayerTrace( beam_tr.endpos, tr.endpos, PM_STUDIO_BOX, -1, &beam_tr ); - - VectorSubtract( beam_tr.endpos, tr.endpos, delta ); - - n = Length( delta ); - - if( n < flDamage ) - { - if( n == 0 ) - n = 1; - flDamage -= n; - - // absorption balls - { - vec3_t fwd; - VectorSubtract( tr.endpos, forward, fwd ); - gEngfuncs.pEfxAPI->R_Sprite_Trail( TE_SPRITETRAIL, tr.endpos, fwd, m_iBalls, 3, 0.1, gEngfuncs.pfnRandomFloat( 10, 20 ) / 100.0, 100, - 255, 100 ); - } - - //////////////////////////////////// WHAT TO DO HERE - // CSoundEnt::InsertSound( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); - - EV_HLDM_DecalGunshot( &beam_tr, BULLET_MONSTER_12MM ); - - gEngfuncs.pEfxAPI->R_TempSprite( beam_tr.endpos, vec3_origin, 0.1, m_iGlow, kRenderGlow, kRenderFxNoDissipation, flDamage / 255.0, 6.0, FTENT_FADEOUT ); - - // balls - { - vec3_t fwd; - VectorSubtract( beam_tr.endpos, forward, fwd ); - gEngfuncs.pEfxAPI->R_Sprite_Trail( TE_SPRITETRAIL, beam_tr.endpos, fwd, m_iBalls, (int)( flDamage * 0.3 ), 0.1, gEngfuncs.pfnRandomFloat( 10, 20 ) / 100.0, 200, - 255, 40 ); - } - - VectorAdd( beam_tr.endpos, forward, vecSrc ); - } - } - else - { - flDamage = 0; - } - - gEngfuncs.pEventAPI->EV_PopPMStates(); - } - else - { - if( m_fPrimaryFire ) - { - // slug doesn't punch through ever with primary - // fire, so leave a little glowy bit and make some balls - gEngfuncs.pEfxAPI->R_TempSprite( tr.endpos, vec3_origin, 0.2, m_iGlow, kRenderGlow, kRenderFxNoDissipation, 200.0 / 255.0, 0.3, FTENT_FADEOUT ); - { - vec3_t fwd; - VectorAdd( tr.endpos, tr.plane.normal, fwd ); - gEngfuncs.pEfxAPI->R_Sprite_Trail( TE_SPRITETRAIL, tr.endpos, fwd, m_iBalls, 8, 0.6, gEngfuncs.pfnRandomFloat( 10, 20 ) / 100.0, 100, - 255, 200 ); - } - } - - flDamage = 0; - } - } - } - else - { - VectorAdd( tr.endpos, forward, vecSrc ); - } - } -} -//====================== -// GAUSS END -//====================== - -//====================== -// CROWBAR START -//====================== -enum crowbar_e -{ - CROWBAR_IDLE = 0, - CROWBAR_DRAW, - CROWBAR_HOLSTER, - CROWBAR_ATTACK1HIT, - CROWBAR_ATTACK1MISS, - CROWBAR_ATTACK2MISS, - CROWBAR_ATTACK2HIT, - CROWBAR_ATTACK3MISS, -#ifndef CROWBAR_IDLE_ANIM - CROWBAR_ATTACK3HIT -#else - CROWBAR_ATTACK3HIT, - CROWBAR_IDLE2, - CROWBAR_IDLE3 -#endif -}; - -int g_iSwing; - -//Only predict the miss sounds, hit sounds are still played -//server side, so players don't get the wrong idea. -void EV_Crowbar( event_args_t *args ) -{ - int idx; - vec3_t origin; - vec3_t angles; - vec3_t velocity; - - idx = args->entindex; - VectorCopy( args->origin, origin ); - - //Play Swing sound - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/cbar_miss1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); - - if( EV_IsLocal( idx ) ) - { - switch( (g_iSwing++) % 3 ) - { - case 0: - gEngfuncs.pEventAPI->EV_WeaponAnimation( CROWBAR_ATTACK1MISS, 1 ); - break; - case 1: - gEngfuncs.pEventAPI->EV_WeaponAnimation( CROWBAR_ATTACK2MISS, 1 ); - break; - case 2: - gEngfuncs.pEventAPI->EV_WeaponAnimation( CROWBAR_ATTACK3MISS, 1 ); - break; - } - } -} -//====================== -// CROWBAR END -//====================== - -//====================== -// CROSSBOW START -//====================== -enum crossbow_e -{ - CROSSBOW_IDLE1 = 0, // full - CROSSBOW_IDLE2, // empty - CROSSBOW_FIDGET1, // full - CROSSBOW_FIDGET2, // empty - CROSSBOW_FIRE1, // full - CROSSBOW_FIRE2, // reload - CROSSBOW_FIRE3, // empty - CROSSBOW_RELOAD, // from empty - CROSSBOW_DRAW1, // full - CROSSBOW_DRAW2, // empty - CROSSBOW_HOLSTER1, // full - CROSSBOW_HOLSTER2 // empty -}; - -//===================== -// EV_BoltCallback -// This function is used to correct the origin and angles -// of the bolt, so it looks like it's stuck on the wall. -//===================== -void EV_BoltCallback( struct tempent_s *ent, float frametime, float currenttime ) -{ - ent->entity.origin = ent->entity.baseline.vuser1; - ent->entity.angles = ent->entity.baseline.vuser2; -} - -void EV_FireCrossbow2( event_args_t *args ) -{ - vec3_t vecSrc, vecEnd; - vec3_t up, right, forward; - pmtrace_t tr; - - int idx; - vec3_t origin; - vec3_t angles; - vec3_t velocity; - - idx = args->entindex; - VectorCopy( args->origin, origin ); - VectorCopy( args->angles, angles ); - - VectorCopy( args->velocity, velocity ); - - AngleVectors( angles, forward, right, up ); - - EV_GetGunPosition( args, vecSrc, origin ); - - VectorMA( vecSrc, 8192, forward, vecEnd ); - - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/xbow_fire1.wav", 1, ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong( 0, 0xF ) ); - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_ITEM, "weapons/xbow_reload1.wav", gEngfuncs.pfnRandomFloat( 0.95, 1.0 ), ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong( 0, 0xF ) ); - - if( EV_IsLocal( idx ) ) - { - if( args->iparam1 ) - gEngfuncs.pEventAPI->EV_WeaponAnimation( CROSSBOW_FIRE1, 1 ); - else if( args->iparam2 ) - gEngfuncs.pEventAPI->EV_WeaponAnimation( CROSSBOW_FIRE3, 1 ); - } - - // Store off the old count - gEngfuncs.pEventAPI->EV_PushPMStates(); - - // Now add in all of the players. - gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); - gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); - gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecEnd, PM_STUDIO_BOX, -1, &tr ); - - //We hit something - if( tr.fraction < 1.0 ) - { - physent_t *pe = gEngfuncs.pEventAPI->EV_GetPhysent( tr.ent ); - - //Not the world, let's assume we hit something organic ( dog, cat, uncle joe, etc ). - if( pe->solid != SOLID_BSP ) - { - switch( gEngfuncs.pfnRandomLong( 0, 1 ) ) - { - case 0: - gEngfuncs.pEventAPI->EV_PlaySound( idx, tr.endpos, CHAN_BODY, "weapons/xbow_hitbod1.wav", 1, ATTN_NORM, 0, PITCH_NORM ); - break; - case 1: - gEngfuncs.pEventAPI->EV_PlaySound( idx, tr.endpos, CHAN_BODY, "weapons/xbow_hitbod2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); - break; - } - } - //Stick to world but don't stick to glass, it might break and leave the bolt floating. It can still stick to other non-transparent breakables though. - else if( pe->rendermode == kRenderNormal ) - { - gEngfuncs.pEventAPI->EV_PlaySound( 0, tr.endpos, CHAN_BODY, "weapons/xbow_hit1.wav", gEngfuncs.pfnRandomFloat( 0.95, 1.0 ), ATTN_NORM, 0, PITCH_NORM ); - - //Not underwater, do some sparks... - if( gEngfuncs.PM_PointContents( tr.endpos, NULL ) != CONTENTS_WATER ) - gEngfuncs.pEfxAPI->R_SparkShower( tr.endpos ); - - vec3_t vBoltAngles; - int iModelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( "models/crossbow_bolt.mdl" ); - - VectorAngles( forward, vBoltAngles ); - - TEMPENTITY *bolt = gEngfuncs.pEfxAPI->R_TempModel( tr.endpos - forward * 10, Vector( 0, 0, 0 ), vBoltAngles , 5, iModelIndex, TE_BOUNCE_NULL ); - - if( bolt ) - { - bolt->flags |= ( FTENT_CLIENTCUSTOM ); //So it calls the callback function. - bolt->entity.baseline.vuser1 = tr.endpos - forward * 10; // Pull out a little bit - bolt->entity.baseline.vuser2 = vBoltAngles; //Look forward! - bolt->callback = EV_BoltCallback; //So we can set the angles and origin back. (Stick the bolt to the wall) - } - } - } - - gEngfuncs.pEventAPI->EV_PopPMStates(); -} - -//TODO: Fully predict the fliying bolt. -void EV_FireCrossbow( event_args_t *args ) -{ - int idx; - vec3_t origin; - - idx = args->entindex; - VectorCopy( args->origin, origin ); - - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/xbow_fire1.wav", 1, ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong( 0, 0xF ) ); - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_ITEM, "weapons/xbow_reload1.wav", gEngfuncs.pfnRandomFloat( 0.95, 1.0 ), ATTN_NORM, 0, 93 + gEngfuncs.pfnRandomLong( 0, 0xF ) ); - - //Only play the weapon anims if I shot it. - if( EV_IsLocal( idx ) ) - { - if( args->iparam1 ) - gEngfuncs.pEventAPI->EV_WeaponAnimation( CROSSBOW_FIRE1, 1 ); - else if ( args->iparam2 ) - gEngfuncs.pEventAPI->EV_WeaponAnimation( CROSSBOW_FIRE3, 1 ); - - V_PunchAxis( 0, -2.0 ); - } -} -//====================== -// CROSSBOW END -//====================== - -//====================== -// RPG START -//====================== -enum rpg_e -{ - RPG_IDLE = 0, - RPG_FIDGET, - RPG_RELOAD, // to reload - RPG_FIRE2, // to empty - RPG_HOLSTER1, // loaded - RPG_DRAW1, // loaded - RPG_HOLSTER2, // unloaded - RPG_DRAW_UL, // unloaded - RPG_IDLE_UL, // unloaded idle - RPG_FIDGET_UL // unloaded fidget -}; - -void EV_FireRpg( event_args_t *args ) -{ - int idx; - vec3_t origin; - - idx = args->entindex; - VectorCopy( args->origin, origin ); - - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "weapons/rocketfire1.wav", 0.9, ATTN_NORM, 0, PITCH_NORM ); - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_ITEM, "weapons/glauncher.wav", 0.7, ATTN_NORM, 0, PITCH_NORM ); - - //Only play the weapon anims if I shot it. - if( EV_IsLocal( idx ) ) - { - gEngfuncs.pEventAPI->EV_WeaponAnimation( RPG_FIRE2, 1 ); - - V_PunchAxis( 0, -5.0 ); - } -} -//====================== -// RPG END -//====================== - -//====================== -// EGON END -//====================== -enum egon_e -{ - EGON_IDLE1 = 0, - EGON_FIDGET1, - EGON_ALTFIREON, - EGON_ALTFIRECYCLE, - EGON_ALTFIREOFF, - EGON_FIRE1, - EGON_FIRE2, - EGON_FIRE3, - EGON_FIRE4, - EGON_DRAW, - EGON_HOLSTER -}; - -int g_fireAnims1[] = { EGON_FIRE1, EGON_FIRE2, EGON_FIRE3, EGON_FIRE4 }; -int g_fireAnims2[] = { EGON_ALTFIRECYCLE }; - -enum EGON_FIRESTATE -{ - FIRE_OFF, - FIRE_CHARGE -}; - -enum EGON_FIREMODE -{ - FIRE_NARROW, - FIRE_WIDE -}; - -#define EGON_PRIMARY_VOLUME 450 -#define EGON_BEAM_SPRITE "sprites/xbeam1.spr" -#define EGON_FLARE_SPRITE "sprites/XSpark1.spr" -#define EGON_SOUND_OFF "weapons/egon_off1.wav" -#define EGON_SOUND_RUN "weapons/egon_run3.wav" -#define EGON_SOUND_STARTUP "weapons/egon_windup2.wav" - -#if !defined(ARRAYSIZE) -#define ARRAYSIZE(p) ( sizeof(p) /sizeof(p[0]) ) -#endif - -BEAM *pBeam; -BEAM *pBeam2; -TEMPENTITY *pFlare; // Vit_amiN: egon's beam flare - -void EV_EgonFlareCallback( struct tempent_s *ent, float frametime, float currenttime ) -{ - float delta = currenttime - ent->tentOffset.z; // time past since the last scale - if( delta >= ent->tentOffset.y ) - { - ent->entity.curstate.scale += ent->tentOffset.x * delta; - ent->tentOffset.z = currenttime; - } -} - -void EV_EgonFire( event_args_t *args ) -{ - int idx, /*iFireState,*/ iFireMode; - vec3_t origin; - - idx = args->entindex; - VectorCopy( args->origin, origin ); - //iFireState = args->iparam1; - iFireMode = args->iparam2; - int iStartup = args->bparam1; - - if( iStartup ) - { - if( iFireMode == FIRE_WIDE ) - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, EGON_SOUND_STARTUP, 0.98, ATTN_NORM, 0, 125 ); - else - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, EGON_SOUND_STARTUP, 0.9, ATTN_NORM, 0, 100 ); - } - else - { - // If there is any sound playing already, kill it. - Solokiller - // This is necessary because multiple sounds can play on the same channel at the same time. - // In some cases, more than 1 run sound plays when the egon stops firing, in which case only the earliest entry in the list is stopped. - // This ensures no more than 1 of those is ever active at the same time. - gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, EGON_SOUND_RUN ); - - if( iFireMode == FIRE_WIDE ) - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_STATIC, EGON_SOUND_RUN, 0.98, ATTN_NORM, 0, 125 ); - else - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_STATIC, EGON_SOUND_RUN, 0.9, ATTN_NORM, 0, 100 ); - } - - //Only play the weapon anims if I shot it. - if( EV_IsLocal( idx ) ) - gEngfuncs.pEventAPI->EV_WeaponAnimation( g_fireAnims1[gEngfuncs.pfnRandomLong( 0, 3 )], 1 ); - - if( iStartup == 1 && EV_IsLocal( idx ) && !( pBeam || pBeam2 || pFlare ) && cl_lw->value ) //Adrian: Added the cl_lw check for those lital people that hate weapon prediction. - { - vec3_t vecSrc, vecEnd, angles, forward, right, up; - pmtrace_t tr; - - cl_entity_t *pl = gEngfuncs.GetEntityByIndex( idx ); - - if( pl ) - { - VectorCopy( gHUD.m_vecAngles, angles ); - - AngleVectors( angles, forward, right, up ); - - EV_GetGunPosition( args, vecSrc, pl->origin ); - - VectorMA( vecSrc, 2048, forward, vecEnd ); - - gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); - - // Store off the old count - gEngfuncs.pEventAPI->EV_PushPMStates(); - - // Now add in all of the players. - gEngfuncs.pEventAPI->EV_SetSolidPlayers( idx - 1 ); - - gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); - gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecEnd, PM_STUDIO_BOX, -1, &tr ); - - gEngfuncs.pEventAPI->EV_PopPMStates(); - - int iBeamModelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex( EGON_BEAM_SPRITE ); - - float r = 50.0f; - float g = 50.0f; - float b = 125.0f; - - if( IEngineStudio.IsHardware() ) - { - r /= 100.0f; - g /= 100.0f; - } - - pBeam = gEngfuncs.pEfxAPI->R_BeamEntPoint( idx | 0x1000, tr.endpos, iBeamModelIndex, 99999, 3.5, 0.2, 0.7, 55, 0, 0, r, g, b ); - - if( pBeam ) - pBeam->flags |= ( FBEAM_SINENOISE ); - - pBeam2 = gEngfuncs.pEfxAPI->R_BeamEntPoint( idx | 0x1000, tr.endpos, iBeamModelIndex, 99999, 5.0, 0.08, 0.7, 25, 0, 0, r, g, b ); - - // Vit_amiN: egon beam flare - pFlare = gEngfuncs.pEfxAPI->R_TempSprite( tr.endpos, vec3_origin, 1.0, gEngfuncs.pEventAPI->EV_FindModelIndex( EGON_FLARE_SPRITE ), kRenderGlow, kRenderFxNoDissipation, 1.0, 99999, FTENT_SPRCYCLE | FTENT_PERSIST ); - } - } - - if( pFlare ) // Vit_amiN: store the last mode for EV_EgonStop() - { - pFlare->tentOffset.x = ( iFireMode == FIRE_WIDE ) ? 1.0f : 0.0f; - } -} - -void EV_EgonStop( event_args_t *args ) -{ - int idx; - vec3_t origin; - - idx = args->entindex; - VectorCopy( args->origin, origin ); - - gEngfuncs.pEventAPI->EV_StopSound( idx, CHAN_STATIC, EGON_SOUND_RUN ); - - if( args->iparam1 ) - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, EGON_SOUND_OFF, 0.98, ATTN_NORM, 0, 100 ); - - if( EV_IsLocal( idx ) ) - { - if( pBeam ) - { - pBeam->die = 0.0; - pBeam = NULL; - } - - if( pBeam2 ) - { - pBeam2->die = 0.0; - pBeam2 = NULL; - } - - if( pFlare ) // Vit_amiN: egon beam flare - { - pFlare->die = gEngfuncs.GetClientTime(); - - if( gEngfuncs.GetMaxClients() == 1 || !(pFlare->flags & FTENT_NOMODEL) ) - { - if( pFlare->tentOffset.x != 0.0f ) // true for iFireMode == FIRE_WIDE - { - pFlare->callback = &EV_EgonFlareCallback; - pFlare->fadeSpeed = 2.0; // fade out will take 0.5 sec - pFlare->tentOffset.x = 10.0; // scaling speed per second - pFlare->tentOffset.y = 0.1; // min time between two scales - pFlare->tentOffset.z = pFlare->die; // the last callback run time - pFlare->flags = FTENT_FADEOUT | FTENT_CLIENTCUSTOM; - } - } - - pFlare = NULL; - } - } -} -//====================== -// EGON END -//====================== - -//====================== -// HORNET START -//====================== -enum hgun_e -{ - HGUN_IDLE1 = 0, - HGUN_FIDGETSWAY, - HGUN_FIDGETSHAKE, - HGUN_DOWN, - HGUN_UP, - HGUN_SHOOT -}; - -void EV_HornetGunFire( event_args_t *args ) -{ - int idx; //, iFireMode; - vec3_t origin, angles, vecSrc, forward, right, up; - - idx = args->entindex; - VectorCopy( args->origin, origin ); - VectorCopy( args->angles, angles ); - //iFireMode = args->iparam1; - - //Only play the weapon anims if I shot it. - if( EV_IsLocal( idx ) ) - { - V_PunchAxis( 0, gEngfuncs.pfnRandomLong( 0, 2 ) ); - gEngfuncs.pEventAPI->EV_WeaponAnimation( HGUN_SHOOT, 1 ); - } - - switch( gEngfuncs.pfnRandomLong( 0, 2 ) ) - { - case 0: - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "agrunt/ag_fire1.wav", 1, ATTN_NORM, 0, 100 ); - break; - case 1: - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "agrunt/ag_fire2.wav", 1, ATTN_NORM, 0, 100 ); - break; - case 2: - gEngfuncs.pEventAPI->EV_PlaySound( idx, origin, CHAN_WEAPON, "agrunt/ag_fire3.wav", 1, ATTN_NORM, 0, 100 ); - break; - } -} -//====================== -// HORNET END -//====================== - -//====================== -// TRIPMINE START -//====================== -enum tripmine_e -{ - TRIPMINE_IDLE1 = 0, - TRIPMINE_IDLE2, - TRIPMINE_ARM1, - TRIPMINE_ARM2, - TRIPMINE_FIDGET, - TRIPMINE_HOLSTER, - TRIPMINE_DRAW, - TRIPMINE_WORLD, - TRIPMINE_GROUND -}; - -//We only check if it's possible to put a trip mine -//and if it is, then we play the animation. Server still places it. -void EV_TripmineFire( event_args_t *args ) -{ - int idx; - vec3_t vecSrc, angles, view_ofs, forward; - pmtrace_t tr; - - idx = args->entindex; - VectorCopy( args->origin, vecSrc ); - VectorCopy( args->angles, angles ); - - AngleVectors( angles, forward, NULL, NULL ); - - if( !EV_IsLocal ( idx ) ) - return; - - // Grab predicted result for local player - gEngfuncs.pEventAPI->EV_LocalPlayerViewheight( view_ofs ); - - vecSrc = vecSrc + view_ofs; - - // Store off the old count - gEngfuncs.pEventAPI->EV_PushPMStates(); - - // Now add in all of the players. - gEngfuncs.pEventAPI->EV_SetSolidPlayers ( idx - 1 ); - gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); - gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecSrc + forward * 128, PM_NORMAL, -1, &tr ); - - //Hit something solid - if( tr.fraction < 1.0 ) - gEngfuncs.pEventAPI->EV_WeaponAnimation ( TRIPMINE_DRAW, 0 ); - - gEngfuncs.pEventAPI->EV_PopPMStates(); -} -//====================== -// TRIPMINE END -//====================== - -//====================== -// SQUEAK START -//====================== -enum squeak_e -{ - SQUEAK_IDLE1 = 0, - SQUEAK_FIDGETFIT, - SQUEAK_FIDGETNIP, - SQUEAK_DOWN, - SQUEAK_UP, - SQUEAK_THROW -}; - -#define VEC_HULL_MIN Vector( -16, -16, -36 ) -#define VEC_DUCK_HULL_MIN Vector( -16, -16, -18 ) - -void EV_SnarkFire( event_args_t *args ) -{ - int idx; - vec3_t vecSrc, angles, view_ofs, forward; - pmtrace_t tr; - - idx = args->entindex; - VectorCopy( args->origin, vecSrc ); - VectorCopy( args->angles, angles ); - - AngleVectors( angles, forward, NULL, NULL ); - - if( !EV_IsLocal ( idx ) ) - return; - - if( args->ducking ) - vecSrc = vecSrc - ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); - - // Store off the old count - gEngfuncs.pEventAPI->EV_PushPMStates(); - - // Now add in all of the players. - gEngfuncs.pEventAPI->EV_SetSolidPlayers( idx - 1 ); - gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); - gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc + forward * 20, vecSrc + forward * 64, PM_NORMAL, -1, &tr ); - - //Find space to drop the thing. - if( tr.allsolid == 0 && tr.startsolid == 0 && tr.fraction > 0.25 ) - gEngfuncs.pEventAPI->EV_WeaponAnimation( SQUEAK_THROW, 0 ); - - gEngfuncs.pEventAPI->EV_PopPMStates(); -} -//====================== -// SQUEAK END +// RIFLE END //====================== void EV_TrainPitchAdjust( event_args_t *args ) diff --git a/cl_dll/ev_hldm.h b/cl_dll/ev_hldm.h index 8a836762..7cecfe3e 100644 --- a/cl_dll/ev_hldm.h +++ b/cl_dll/ev_hldm.h @@ -16,14 +16,21 @@ typedef enum BULLET_PLAYER_9MM, // glock BULLET_PLAYER_MP5, // mp5 BULLET_PLAYER_357, // python - BULLET_PLAYER_BUCKSHOT, // shotgun + BULLET_PLAYER_SHOTGUN, // shotgun BULLET_PLAYER_CROWBAR, // crowbar swipe + BULLET_PLAYER_KNIFE, // knife swipe + BULLET_PLAYER_SWORDCANE, // swordcane swipe + BULLET_PLAYER_REVOLVER, // revolver shot + BULLET_PLAYER_TOMMYGUN, // tommy gun burst + BULLET_PLAYER_RIFLE, // rifle shot + BULLET_MONSTER_9MM, BULLET_MONSTER_MP5, BULLET_MONSTER_12MM }Bullet; +/* enum glock_e { GLOCK_IDLE1 = 0, @@ -37,6 +44,22 @@ enum glock_e GLOCK_HOLSTER, GLOCK_ADD_SILENCER }; +*/ + +enum revolver_e +{ + REVOLVER_IDLE1 = 0, + REVOLVER_FIDGET1, + REVOLVER_FIRE, + REVOLVER_RELOAD, + REVOLVER_HOLSTER, + REVOLVER_DRAW, + REVOLVER_IDLE2, + REVOLVER_IDLE3, + REVOLVER_QUICKFIRE_READY, + REVOLVER_QUICKFIRE_SHOOT, + REVOLVER_QUICKFIRE_RELAX +}; enum shotgun_e { @@ -52,6 +75,39 @@ enum shotgun_e SHOTGUN_IDLE_DEEP }; +enum rifle_e +{ + RIFLE_IDLE1 = 0, + RIFLE_FIRE1, + RIFLE_RELOAD, + RIFLE_CLOSEBREAK, + RIFLE_BREAK, + RIFLE_DRAW, + RIFLE_HOLSTER +}; + +enum tommygun_e +{ + TOMMYGUN_IDLE = 0, + TOMMYGUN_RELOAD, + TOMMYGUN_DRAW, + TOMMYGUN_FIRE1, + TOMMYGUN_FIRE2, + TOMMYGUN_EMPTY_IDLE +}; + +enum LightningGun_e +{ + LIGHTNING_GUN_IDLE = 0, + LIGHTNING_GUN_DRAW, + LIGHTNING_GUN_HOLSTER, + LIGHTNING_GUN_ZAP +}; + +#define LIGHTNING_PRIMARY_CHARGE_VOLUME 256// how loud lightning is while charging +#define LIGHTNING_PRIMARY_FIRE_VOLUME 450// how loud lightning is when discharged + +/* enum mp5_e { MP5_LONGIDLE = 0, @@ -91,6 +147,7 @@ enum gauss_e GAUSS_HOLSTER, GAUSS_DRAW }; +*/ void EV_HLDM_GunshotDecalTrace( pmtrace_t *pTrace, char *decalName ); void EV_HLDM_DecalGunshot( pmtrace_t *pTrace, int iBulletType ); diff --git a/cl_dll/hl/hl_baseentity.cpp b/cl_dll/hl/hl_baseentity.cpp index 6e132dd8..70eae26c 100644 --- a/cl_dll/hl/hl_baseentity.cpp +++ b/cl_dll/hl/hl_baseentity.cpp @@ -260,6 +260,7 @@ void CBasePlayer::StartObserver( Vector vecPosition, Vector vecViewAngle ) { } void CBasePlayer::PlayerUse( void ) { } void CBasePlayer::Jump() { } void CBasePlayer::Duck() { } +int CBasePlayer::LoseSanity( float flSanLoss ) { return 0; } int CBasePlayer::Classify( void ) { return 0; } void CBasePlayer::PreThink(void) { } void CBasePlayer::CheckTimeBasedDamage() { } diff --git a/cl_dll/hl/hl_events.cpp b/cl_dll/hl/hl_events.cpp index c79279dd..97cbd518 100644 --- a/cl_dll/hl/hl_events.cpp +++ b/cl_dll/hl/hl_events.cpp @@ -20,24 +20,12 @@ extern "C" { // HLDM -void EV_FireGlock1( struct event_args_s *args ); -void EV_FireGlock2( struct event_args_s *args ); +void EV_FireRevolver1( struct event_args_s *args ); +void EV_FireRevolver2( struct event_args_s *args ); void EV_FireShotGunSingle( struct event_args_s *args ); void EV_FireShotGunDouble( struct event_args_s *args ); -void EV_FireMP5( struct event_args_s *args ); -void EV_FireMP52( struct event_args_s *args ); -void EV_FirePython( struct event_args_s *args ); -void EV_FireGauss( struct event_args_s *args ); -void EV_SpinGauss( struct event_args_s *args ); -void EV_Crowbar( struct event_args_s *args ); -void EV_FireCrossbow( struct event_args_s *args ); -void EV_FireCrossbow2( struct event_args_s *args ); -void EV_FireRpg( struct event_args_s *args ); -void EV_EgonFire( struct event_args_s *args ); -void EV_EgonStop( struct event_args_s *args ); -void EV_HornetGunFire( struct event_args_s *args ); -void EV_TripmineFire( struct event_args_s *args ); -void EV_SnarkFire( struct event_args_s *args ); +void EV_FireTommy( struct event_args_s *args ); +void EV_FireRifle( struct event_args_s *args ); void EV_TrainPitchAdjust( struct event_args_s *args ); } @@ -57,23 +45,12 @@ That was what we were going to do, but we ran out of time...oh well. */ void Game_HookEvents( void ) { - gEngfuncs.pfnHookEvent( "events/glock1.sc", EV_FireGlock1 ); - gEngfuncs.pfnHookEvent( "events/glock2.sc", EV_FireGlock2 ); + gEngfuncs.pfnHookEvent( "events/revolver1.sc", EV_FireRevolver1 ); + gEngfuncs.pfnHookEvent( "events/revolver2.sc", EV_FireRevolver2 ); gEngfuncs.pfnHookEvent( "events/shotgun1.sc", EV_FireShotGunSingle ); gEngfuncs.pfnHookEvent( "events/shotgun2.sc", EV_FireShotGunDouble ); - gEngfuncs.pfnHookEvent( "events/mp5.sc", EV_FireMP5 ); - gEngfuncs.pfnHookEvent( "events/mp52.sc", EV_FireMP52 ); - gEngfuncs.pfnHookEvent( "events/python.sc", EV_FirePython ); - gEngfuncs.pfnHookEvent( "events/gauss.sc", EV_FireGauss ); - gEngfuncs.pfnHookEvent( "events/gaussspin.sc", EV_SpinGauss ); + gEngfuncs.pfnHookEvent( "events/tommygun.sc", EV_FireTommy ); + gEngfuncs.pfnHookEvent( "events/rifle.sc", EV_FireRifle ); + gEngfuncs.pfnHookEvent( "events/train.sc", EV_TrainPitchAdjust ); - gEngfuncs.pfnHookEvent( "events/crowbar.sc", EV_Crowbar ); - gEngfuncs.pfnHookEvent( "events/crossbow1.sc", EV_FireCrossbow ); - gEngfuncs.pfnHookEvent( "events/crossbow2.sc", EV_FireCrossbow2 ); - gEngfuncs.pfnHookEvent( "events/rpg.sc", EV_FireRpg ); - gEngfuncs.pfnHookEvent( "events/egon_fire.sc", EV_EgonFire ); - gEngfuncs.pfnHookEvent( "events/egon_stop.sc", EV_EgonStop ); - gEngfuncs.pfnHookEvent( "events/firehornet.sc", EV_HornetGunFire ); - gEngfuncs.pfnHookEvent( "events/tripfire.sc", EV_TripmineFire ); - gEngfuncs.pfnHookEvent( "events/snarkfire.sc", EV_SnarkFire ); } diff --git a/cl_dll/hl/hl_objects.cpp b/cl_dll/hl/hl_objects.cpp index 55822dcb..dfeb4e1f 100644 --- a/cl_dll/hl/hl_objects.cpp +++ b/cl_dll/hl/hl_objects.cpp @@ -27,77 +27,10 @@ #include "entity_types.h" #include "r_efx.h" -extern BEAM *pBeam; -extern BEAM *pBeam2; -extern TEMPENTITY *pFlare; // Vit_amiN: egon's energy flare void HUD_GetLastOrg( float *org ); void UpdateBeams( void ) { - vec3_t forward, vecSrc, vecEnd, origin, angles, right, up; - vec3_t view_ofs; - pmtrace_t tr; - cl_entity_t *pthisplayer = gEngfuncs.GetLocalPlayer(); - int idx = pthisplayer->index; - - // Get our exact viewangles from engine - gEngfuncs.GetViewAngles( (float *)angles ); - - // Determine our last predicted origin - HUD_GetLastOrg( (float *)&origin ); - - AngleVectors( angles, forward, right, up ); - - VectorCopy( origin, vecSrc ); - - VectorMA( vecSrc, 2048, forward, vecEnd ); - - gEngfuncs.pEventAPI->EV_SetUpPlayerPrediction( false, true ); - - // Store off the old count - gEngfuncs.pEventAPI->EV_PushPMStates(); - - // Now add in all of the players. - gEngfuncs.pEventAPI->EV_SetSolidPlayers( idx - 1 ); - - gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); - gEngfuncs.pEventAPI->EV_PlayerTrace( vecSrc, vecEnd, PM_STUDIO_BOX, -1, &tr ); - - gEngfuncs.pEventAPI->EV_PopPMStates(); - - if( pBeam ) - { - pBeam->target = tr.endpos; - pBeam->die = gEngfuncs.GetClientTime() + 0.1; // We keep it alive just a little bit forward in the future, just in case. - } - - if( pBeam2 ) - { - pBeam2->target = tr.endpos; - pBeam2->die = gEngfuncs.GetClientTime() + 0.1; // We keep it alive just a little bit forward in the future, just in case. - } - - if( pFlare ) // Vit_amiN: beam flare - { - pFlare->entity.origin = tr.endpos; - pFlare->die = gEngfuncs.GetClientTime() + 0.1; // We keep it alive just a little bit forward in the future, just in case. - - if( gEngfuncs.GetMaxClients() != 1 ) // Singleplayer always draws the egon's energy beam flare - { - pFlare->flags |= FTENT_NOMODEL; - - if( !( tr.allsolid || tr.ent <= 0 || tr.fraction == 1.0 ) ) // Beam hit some non-world entity - { - physent_t *pEntity = gEngfuncs.pEventAPI->EV_GetPhysent( tr.ent ); - - // Not the world, let's assume that we hit something organic ( dog, cat, uncle joe, etc ) - if( pEntity && !( pEntity->solid == SOLID_BSP || pEntity->movetype == MOVETYPE_PUSHSTEP ) ) - { - pFlare->flags &= ~FTENT_NOMODEL; - } - } - } - } } /* @@ -109,6 +42,4 @@ Add game specific, client-side objects here */ void Game_AddObjects( void ) { - if( pBeam || pBeam2 || pFlare ) // Vit_amiN: egon flare added - UpdateBeams(); } diff --git a/cl_dll/hl/hl_weapons.cpp b/cl_dll/hl/hl_weapons.cpp index 4b3af7ea..6660d3e1 100644 --- a/cl_dll/hl/hl_weapons.cpp +++ b/cl_dll/hl/hl_weapons.cpp @@ -53,20 +53,7 @@ int g_irunninggausspred = 0; vec3_t previousorigin; // HLDM Weapon placeholder entities. -CGlock g_Glock; -CCrowbar g_Crowbar; -CPython g_Python; -CMP5 g_Mp5; -CCrossbow g_Crossbow; -CShotgun g_Shotgun; -CRpg g_Rpg; -CGauss g_Gauss; -CEgon g_Egon; -CHgun g_HGun; -CHandGrenade g_HandGren; -CSatchel g_Satchel; -CTripmine g_Tripmine; -CSqueak g_Snark; +CRevolver g_Revolver; /* ====================== @@ -625,20 +612,7 @@ void HUD_InitClientWeapons( void ) HUD_PrepEntity( &player, NULL ); // Allocate slot(s) for each weapon that we are going to be predicting - HUD_PrepEntity( &g_Glock, &player ); - HUD_PrepEntity( &g_Crowbar, &player ); - HUD_PrepEntity( &g_Python, &player ); - HUD_PrepEntity( &g_Mp5, &player ); - HUD_PrepEntity( &g_Crossbow, &player ); - HUD_PrepEntity( &g_Shotgun, &player ); - HUD_PrepEntity( &g_Rpg, &player ); - HUD_PrepEntity( &g_Gauss, &player ); - HUD_PrepEntity( &g_Egon, &player ); - HUD_PrepEntity( &g_HGun, &player ); - HUD_PrepEntity( &g_HandGren, &player ); - HUD_PrepEntity( &g_Satchel, &player ); - HUD_PrepEntity( &g_Tripmine, &player ); - HUD_PrepEntity( &g_Snark, &player ); + HUD_PrepEntity( &g_Revolver, &player ); } /* @@ -702,47 +676,8 @@ void HUD_WeaponsPostThink( local_state_s *from, local_state_s *to, usercmd_t *cm // FIXME, make this a method in each weapon? where you pass in an entity_state_t *? switch( from->client.m_iId ) { - case WEAPON_CROWBAR: - pWeapon = &g_Crowbar; - break; - case WEAPON_GLOCK: - pWeapon = &g_Glock; - break; - case WEAPON_PYTHON: - pWeapon = &g_Python; - break; - case WEAPON_MP5: - pWeapon = &g_Mp5; - break; - case WEAPON_CROSSBOW: - pWeapon = &g_Crossbow; - break; - case WEAPON_SHOTGUN: - pWeapon = &g_Shotgun; - break; - case WEAPON_RPG: - pWeapon = &g_Rpg; - break; - case WEAPON_GAUSS: - pWeapon = &g_Gauss; - break; - case WEAPON_EGON: - pWeapon = &g_Egon; - break; - case WEAPON_HORNETGUN: - pWeapon = &g_HGun; - break; - case WEAPON_HANDGRENADE: - pWeapon = &g_HandGren; - break; - case WEAPON_SATCHEL: - pWeapon = &g_Satchel; - break; - case WEAPON_TRIPMINE: - pWeapon = &g_Tripmine; - break; - case WEAPON_SNARK: - pWeapon = &g_Snark; + case WEAPON_REVOLVER: + pWeapon = &g_Revolver; break; } @@ -833,14 +768,7 @@ void HUD_WeaponsPostThink( local_state_s *from, local_state_s *to, usercmd_t *cm player.m_flAmmoStartCharge = from->client.fuser3; //Stores all our ammo info, so the client side weapons can use them. - player.ammo_9mm = (int)from->client.vuser1[0]; - player.ammo_357 = (int)from->client.vuser1[1]; - player.ammo_argrens = (int)from->client.vuser1[2]; - player.ammo_bolts = (int)from->client.ammo_nails; //is an int anyways... - player.ammo_buckshot = (int)from->client.ammo_shells; - player.ammo_uranium = (int)from->client.ammo_cells; - player.ammo_hornets = (int)from->client.vuser2[0]; - player.ammo_rockets = (int)from->client.ammo_rockets; + player.ammo_revolver = (int)from->client.vuser1[0]; // Point to current weapon object if( from->client.m_iId ) @@ -848,12 +776,6 @@ void HUD_WeaponsPostThink( local_state_s *from, local_state_s *to, usercmd_t *cm player.m_pActiveItem = g_pWpns[from->client.m_iId]; } - if( player.m_pActiveItem->m_iId == WEAPON_RPG ) - { - ( (CRpg *)player.m_pActiveItem )->m_fSpotActive = (int)from->client.vuser2[1]; - ( (CRpg *)player.m_pActiveItem )->m_cActiveRockets = (int)from->client.vuser2[2]; - } - // Don't go firing anything if we have died. // Or if we don't have a weapon model deployed if( ( player.pev->deadflag != ( DEAD_DISCARDBODY + 1 ) ) && @@ -906,21 +828,7 @@ void HUD_WeaponsPostThink( local_state_s *from, local_state_s *to, usercmd_t *cm to->client.maxspeed = player.pev->maxspeed; //HL Weapons - to->client.vuser1[0] = player.ammo_9mm; - to->client.vuser1[1] = player.ammo_357; - to->client.vuser1[2] = player.ammo_argrens; - - to->client.ammo_nails = player.ammo_bolts; - to->client.ammo_shells = player.ammo_buckshot; - to->client.ammo_cells = player.ammo_uranium; - to->client.vuser2[0] = player.ammo_hornets; - to->client.ammo_rockets = player.ammo_rockets; - - if( player.m_pActiveItem->m_iId == WEAPON_RPG ) - { - from->client.vuser2[1] = ( (CRpg *)player.m_pActiveItem)->m_fSpotActive; - from->client.vuser2[2] = ( (CRpg *)player.m_pActiveItem)->m_cActiveRockets; - } + to->client.vuser1[0] = player.ammo_revolver; // Make sure that weapon animation matches what the game .dll is telling us // over the wire ( fixes some animation glitches ) @@ -928,14 +836,6 @@ void HUD_WeaponsPostThink( local_state_s *from, local_state_s *to, usercmd_t *cm { int body = 2; - //Pop the model to body 0. - if( pWeapon == &g_Tripmine ) - body = 0; - - //Show laser sight/scope combo - if( pWeapon == &g_Python && bIsMultiplayer() ) - body = 1; - // Force a fixed anim down to viewmodel HUD_SendWeaponAnim( to->client.weaponanim, body, 1 ); } diff --git a/cl_dll/hud.cpp b/cl_dll/hud.cpp index 20936f10..393b3053 100644 --- a/cl_dll/hud.cpp +++ b/cl_dll/hud.cpp @@ -299,7 +299,9 @@ void CHud::Init( void ) m_Spectator.Init(); m_Geiger.Init(); m_Train.Init(); - m_Battery.Init(); + // m_Battery.Init(); + m_Sanity.Init(); + m_ReadBook.Init(); m_Flash.Init(); m_Message.Init(); m_StatusBar.Init(); @@ -485,7 +487,9 @@ void CHud::VidInit( void ) m_Spectator.VidInit(); m_Geiger.VidInit(); m_Train.VidInit(); - m_Battery.VidInit(); + // m_Battery.VidInit(); + m_Sanity.VidInit(); + m_ReadBook.VidInit(); m_Flash.VidInit(); m_Message.VidInit(); m_StatusBar.VidInit(); diff --git a/cl_dll/hud.h b/cl_dll/hud.h index ba43138c..cee0f1cd 100644 --- a/cl_dll/hud.h +++ b/cl_dll/hud.h @@ -27,6 +27,8 @@ #define RGB_REDISH 0x00FF1010 //255,160,0 #define RGB_GREENISH 0x0000A000 //0,160,0 +#include "util_vector.h" +#include "const.h" #include "wrect.h" #include "cl_dll.h" #include "ammo.h" @@ -408,24 +410,42 @@ private: // //----------------------------------------------------- // -class CHudBattery : public CHudBase +class CHudSanity : public CHudBase { public: int Init( void ); int VidInit( void ); int Draw( float flTime ); - int MsgFunc_Battery( const char *pszName, int iSize, void *pbuf ); + int MsgFunc_Sanity( const char *pszName, int iSize, void *pbuf ); private: HSPRITE m_hSprite1; HSPRITE m_hSprite2; wrect_t *m_prc1; wrect_t *m_prc2; - int m_iBat; + int m_iSan; float m_fFade; - int m_iHeight; // width of the battery innards + int m_iHeight; // width of the sanity innards }; +// +//----------------------------------------------------- +// +#include "ImageLabel.h" + +class CHudReadBook : public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + // int Draw(float flTime); + int MsgFunc_ReadBook(const char *pszName, int iSize, void *pbuf ); + +private: + CImageLabel *pImage; +}; + + // //----------------------------------------------------- // @@ -679,7 +699,9 @@ public: CHudHealth m_Health; CHudSpectator m_Spectator; CHudGeiger m_Geiger; - CHudBattery m_Battery; + CHudSanity m_Sanity; + CHudReadBook m_ReadBook; + // CHudBattery m_Battery; CHudTrain m_Train; CHudFlashlight m_Flash; CHudMessage m_Message; diff --git a/cl_dll/hud_msg.cpp b/cl_dll/hud_msg.cpp index 272e270e..40d77ffb 100644 --- a/cl_dll/hud_msg.cpp +++ b/cl_dll/hud_msg.cpp @@ -31,10 +31,6 @@ float g_fFadeDuration; //negative = fading out #define MAX_CLIENTS 32 -extern BEAM *pBeam; -extern BEAM *pBeam2; -extern TEMPENTITY *pFlare; // Vit_amiN - extern float g_lastFOV; // Vit_amiN /// USER-DEFINED SERVER MESSAGE HANDLERS @@ -102,10 +98,6 @@ void CHud::MsgFunc_InitHUD( const char *pszName, int iSize, void *pbuf ) pList->p->InitHUDData(); pList = pList->pNext; } - - //Probably not a good place to put this. - pBeam = pBeam2 = NULL; - pFlare = NULL; // Vit_amiN: clear egon's beam flare } //LRC diff --git a/dlls/aflock.cpp b/dlls/aflock.cpp index 68578e7d..60827231 100644 --- a/dlls/aflock.cpp +++ b/dlls/aflock.cpp @@ -347,8 +347,9 @@ void CFlockingFlyer::SpawnCommonCode() //========================================================= void CFlockingFlyer::BoidAdvanceFrame() { - float flapspeed = ( pev->speed - pev->armorvalue ) / AFLOCK_ACCELERATE; - pev->armorvalue = pev->armorvalue * .8 + pev->speed * .2; + // float flapspeed = ( pev->speed - pev->armorvalue ) / AFLOCK_ACCELERATE; + float flapspeed = pev->speed / AFLOCK_ACCELERATE; + // pev->armorvalue = pev->armorvalue * .8 + pev->speed * .2; if( flapspeed < 0 ) flapspeed = -flapspeed; diff --git a/dlls/barney.cpp b/dlls/barney.cpp index eba8545b..34baa845 100644 --- a/dlls/barney.cpp +++ b/dlls/barney.cpp @@ -897,8 +897,13 @@ LINK_ENTITY_TO_CLASS( monster_barney_dead, CDeadBarney ) //========================================================= void CDeadBarney::Spawn() { - PRECACHE_MODEL( "models/barney.mdl" ); - SET_MODEL( ENT( pev ), "models/barney.mdl" ); + const char *szModel = "models/barney.mdl"; + + if( pev->model ) + szModel = STRING( pev->model ); //LRC + + PRECACHE_MODEL( szModel ); + SET_MODEL( ENT( pev ), szModel ); pev->effects = 0; pev->yaw_speed = 8; diff --git a/dlls/basemonster.h b/dlls/basemonster.h index cef4b6ae..05b83896 100644 --- a/dlls/basemonster.h +++ b/dlls/basemonster.h @@ -134,6 +134,7 @@ public: virtual BOOL ShouldFadeOnDeath( void ); // Basic Monster AI functions + virtual float GetFleeDistance( void ) { return 500.0f; }; virtual float ChangeYaw( int speed ); float VecToYaw( Vector vecDir ); float FlYawDiff( void ); @@ -211,7 +212,7 @@ public: void PushEnemy( CBaseEntity *pEnemy, Vector &vecLastKnownPos ); BOOL PopEnemy( void ); - BOOL FGetNodeRoute( Vector vecDest ); + virtual BOOL FGetNodeRoute( Vector vecDest ); inline void TaskComplete( void ) { if ( !HasConditions( bits_COND_TASK_FAILED ) ) m_iTaskStatus = TASKSTATUS_COMPLETE; } void MovementComplete( void ); @@ -229,11 +230,12 @@ public: 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 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 RunAwayFromEnemy( void ); 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; }; @@ -273,6 +275,8 @@ public: void SetEyePosition( void ); + void Panic( entvars_t *pevPanic );// make the monster panic for a while. + BOOL FShouldEat( void );// see if a monster is 'hungry' void Eat( float flFullDuration );// make the monster 'full' for a while. diff --git a/dlls/bigmomma.cpp b/dlls/bigmomma.cpp index c7cccbcf..9fdfea2d 100644 --- a/dlls/bigmomma.cpp +++ b/dlls/bigmomma.cpp @@ -26,34 +26,9 @@ #include "decals.h" #include "weapons.h" #include "game.h" +#include "bm.h" -//LRC brought in from animation.h -#define ACTIVITY_NOT_AVAILABLE -1 - -#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[]; - - string_t m_preSequence; -}; +int gSpitSprite, gSpitDebrisSprite; LINK_ENTITY_TO_CLASS( info_bigmomma, CInfoBM ) @@ -102,21 +77,6 @@ void CInfoBM::KeyValue( KeyValueData* 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 ) @@ -162,10 +122,6 @@ IMPLEMENT_SAVERESTORE( CBMortar, CBaseEntity ) #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" @@ -1094,164 +1050,4 @@ void CBigMomma::RunTask( Task_t *pTask ) } } -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; - - // 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 ) -{ - SetNextThink( 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, vecStart ); - pSpit->pev->velocity = vecVelocity; - pSpit->pev->owner = pOwner; - pSpit->pev->scale = 2.5; - pSpit->SetThink( &CBMortar::Animate ); - pSpit->SetNextThink( 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/bmodels.cpp b/dlls/bmodels.cpp index 2d7d625d..801311fb 100644 --- a/dlls/bmodels.cpp +++ b/dlls/bmodels.cpp @@ -24,6 +24,7 @@ #include "util.h" #include "cbase.h" #include "doors.h" +#include "bmodels.h" #include "movewith.h" extern DLL_GLOBAL Vector g_vecAttackDir; @@ -52,19 +53,6 @@ Vector VecBModelOrigin( entvars_t* pevBModel ) /*QUAKED func_wall (0 .5 .8) ? This is just a solid wall if not inhibited */ -class CFuncWall : public CBaseEntity -{ -public: - void Spawn( void ); - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - - virtual STATE GetState( void ) { return pev->frame?STATE_ON:STATE_OFF; }; - - // Bmodels don't go across transitions - virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } - - int m_iStyle; -}; LINK_ENTITY_TO_CLASS( func_wall, CFuncWall ) @@ -260,6 +248,30 @@ void CFuncIllusionary::Spawn( void ) // MAKE_STATIC(ENT(pev)); } +// ------------------------------------------------------------------------------- +// +// Burning clip brush +// +// This brush will be solid for any entity who has the FL_BURNING_CLIP flag set +// in pev->flags +// +// otherwise it will be invisible and not solid. This can be used to keep +// specific monsters out of certain areas +// +// ------------------------------------------------------------------------------- + +LINK_ENTITY_TO_CLASS( func_burning_clip, CFuncBurningClip ); + +void CFuncBurningClip::Spawn( void ) +{ + CFuncWall::Spawn(); + + if( CVAR_GET_FLOAT( "showtriggers" ) == 0 ) + pev->effects = EF_NODRAW; + + pev->flags |= FL_BURNING_CLIP; +} + // =================== FUNC_SHINE ============================================== //LRC - shiny surfaces @@ -331,12 +343,6 @@ void CFuncShine :: Think( void ) // specific monsters out of certain areas // // ------------------------------------------------------------------------------- -class CFuncMonsterClip : public CFuncWall -{ -public: - void Spawn( void ); - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) {} // Clear out func_wall's use function -}; LINK_ENTITY_TO_CLASS( func_monsterclip, CFuncMonsterClip ) diff --git a/dlls/cbase.cpp b/dlls/cbase.cpp index b7ab31c2..19cdec27 100644 --- a/dlls/cbase.cpp +++ b/dlls/cbase.cpp @@ -175,6 +175,12 @@ int DispatchSpawn( edict_t *pent ) else if( !FStrEq( STRING( gpGlobals->mapname ), pGlobal->levelName ) ) pEntity->MakeDormant(); // Hasn't been moved to this level yet, wait but stay alive // In this level & not dead, continue on as normal + //LRCT + else + { + ALERT(at_console, "Deleting %s \"%s\" (\"%s\")\n", STRING(pEntity->pev->classname), STRING(pEntity->pev->targetname), STRING(pEntity->pev->globalname)); + return -1; + } } else { @@ -614,7 +620,8 @@ void CBaseEntity :: SetEternalThink( void ) void CBaseEntity :: SetNextThink( float delay, BOOL correctSpeed ) { // now monsters use this method, too. - if (m_pMoveWith || m_pChildMoveWith || pev->flags & FL_MONSTER) + if( m_pMoveWith || m_pChildMoveWith ) + //if (m_pMoveWith || m_pChildMoveWith || pev->flags & FL_MONSTER) // Cthulhu - this causes a level transition bug { // use the Assist system, so that thinking doesn't mess up movement. if (pev->movetype == MOVETYPE_PUSH) @@ -994,6 +1001,7 @@ CBaseEntity *CBaseEntity::Create( const char *szName, const Vector &vecOrigin, c return NULL; } pEntity = Instance( pent ); + pEntity->m_pAssistLink = 0; pEntity->pev->owner = pentOwner; pEntity->pev->origin = vecOrigin; pEntity->pev->angles = vecAngles; diff --git a/dlls/cbase.h b/dlls/cbase.h index 76659c9e..f648fed8 100644 --- a/dlls/cbase.h +++ b/dlls/cbase.h @@ -123,9 +123,12 @@ typedef void(CBaseEntity::*USEPTR)( CBaseEntity *pActivator, CBaseEntity *pCalle #define CLASS_PLAYER_ALLY 11 #define CLASS_PLAYER_BIOWEAPON 12 // hornets and snarks.launched by players #define CLASS_ALIEN_BIOWEAPON 13 // hornets and snarks.launched by the alien menace -#define CLASS_FACTION_A 14 //LRC - very simple new classes, for use with Behaves As -#define CLASS_FACTION_B 15 -#define CLASS_FACTION_C 16 +#define CLASS_HUMAN_CULTIST 14 +#define CLASS_MUNDANE_PREY 15 // like chickens +#define CLASS_MUNDANE_PREDATOR 16 // like wolves +#define CLASS_FACTION_A 17 //LRC - very simple new classes, for use with Behaves As +#define CLASS_FACTION_B 18 +#define CLASS_FACTION_C 19 #define CLASS_BARNACLE 99 // special because no one pays attention to it, and it eats a wide cross-section of creatures. class CBaseEntity; @@ -448,6 +451,8 @@ public: virtual BOOL FVisible( const Vector &vecOrigin ); //We use this variables to store each ammo count. + int ammo_revolver; +/* int ammo_9mm; int ammo_357; int ammo_bolts; @@ -456,6 +461,7 @@ public: int ammo_uranium; int ammo_hornets; int ammo_argrens; +*/ //Special stuff for grenades and satchels. float m_flStartThrow; float m_flReleaseThrow; @@ -737,9 +743,10 @@ public: #define DMG_SLOWBURN (1 << 21) // in an oven #define DMG_SLOWFREEZE (1 << 22) // in a subzero freezer #define DMG_MORTAR (1 << 23) // Hit by air raid (done to distinguish grenade from mortar) +#define DMG_POWDER_IBN (1 << 24) // hit by powder of ibn...does zero damage but makes Dunwich Horror visible // these are the damage types that are allowed to gib corpses -#define DMG_GIB_CORPSE ( DMG_CRUSH | DMG_FALL | DMG_BLAST | DMG_SONIC | DMG_CLUB ) +#define DMG_GIB_CORPSE ( DMG_CRUSH | DMG_SLASH | DMG_FALL | DMG_BLAST | DMG_SONIC | DMG_CLUB ) // these are the damage types that have client hud art #define DMG_SHOWNHUD (DMG_POISON | DMG_ACID | DMG_FREEZE | DMG_SLOWFREEZE | DMG_DROWN | DMG_BURN | DMG_SLOWBURN | DMG_NERVEGAS | DMG_RADIATION | DMG_SHOCK) diff --git a/dlls/client.cpp b/dlls/client.cpp index 92f1418b..3cce44f2 100644 --- a/dlls/client.cpp +++ b/dlls/client.cpp @@ -864,8 +864,8 @@ void ClientPrecache( void ) // PRECACHE_SOUND( "player/pl_jumpland2.wav" ); // UNDONE: play 2x step sound - PRECACHE_SOUND( "player/pl_fallpain2.wav" ); - PRECACHE_SOUND( "player/pl_fallpain3.wav" ); + // PRECACHE_SOUND( "player/pl_fallpain2.wav" ); + // PRECACHE_SOUND( "player/pl_fallpain3.wav" ); PRECACHE_SOUND( "player/pl_step1.wav" ); // walk on concrete PRECACHE_SOUND( "player/pl_step2.wav" ); @@ -942,11 +942,14 @@ void ClientPrecache( void ) PRECACHE_SOUND( "common/bodysplat.wav" ); // player pain sounds - PRECACHE_SOUND( "player/pl_pain2.wav" ); - PRECACHE_SOUND( "player/pl_pain4.wav" ); - PRECACHE_SOUND( "player/pl_pain5.wav" ); - PRECACHE_SOUND( "player/pl_pain6.wav" ); - PRECACHE_SOUND( "player/pl_pain7.wav" ); + // PRECACHE_SOUND( "player/pl_pain2.wav" ); + // PRECACHE_SOUND( "player/pl_pain4.wav" ); + // PRECACHE_SOUND( "player/pl_pain5.wav" ); + // PRECACHE_SOUND( "player/pl_pain6.wav" ); + // PRECACHE_SOUND( "player/pl_pain7.wav" ); + PRECACHE_SOUND( "player/rs_pain1.wav" ); + PRECACHE_SOUND( "player/rs_pain2.wav" ); + PRECACHE_SOUND( "player/rs_pain3.wav" ); PRECACHE_MODEL( "models/player.mdl" ); @@ -1800,6 +1803,7 @@ void UpdateClientData( const struct edict_s *ent, int sendweapons, struct client cd->m_flNextAttack = pl->m_flNextAttack; cd->fuser2 = pl->m_flNextAmmoBurn; cd->fuser3 = pl->m_flAmmoStartCharge; + /* cd->vuser1.x = pl->ammo_9mm; cd->vuser1.y = pl->ammo_357; cd->vuser1.z = pl->ammo_argrens; @@ -1808,7 +1812,7 @@ void UpdateClientData( const struct edict_s *ent, int sendweapons, struct client cd->ammo_rockets = pl->ammo_rockets; cd->ammo_cells = pl->ammo_uranium; cd->vuser2.x = pl->ammo_hornets; - + */ if( pl->m_pActiveItem ) { CBasePlayerWeapon *gun; diff --git a/dlls/combat.cpp b/dlls/combat.cpp index 887811df..b845f8d7 100644 --- a/dlls/combat.cpp +++ b/dlls/combat.cpp @@ -551,6 +551,12 @@ Activity CBaseMonster::GetSmallFlinchActivity( void ) flinchActivity = ACT_SMALL_FLINCH; } + // do we have even a basic flinch??? + if( LookupActivity( flinchActivity ) == ACTIVITY_NOT_AVAILABLE ) + { + flinchActivity = ACT_IDLE; + } + return flinchActivity; } @@ -1503,6 +1509,22 @@ void CBaseEntity::FireBullets( ULONG cShots, Vector vecSrc, Vector vecDirShootin else switch( iBulletType ) { default: + case BULLET_PLAYER_REVOLVER: + // make distance based! + pEntity->TraceAttack( pevAttacker, gSkillData.plrDmgRevolver, vecDir, &tr, DMG_BULLET ); + break; + case BULLET_PLAYER_SHOTGUN: + // make distance based! + pEntity->TraceAttack(pevAttacker, gSkillData.plrDmgShotgun, vecDir, &tr, DMG_BULLET); + break; + case BULLET_PLAYER_TOMMYGUN: + // make distance based! + pEntity->TraceAttack(pevAttacker, gSkillData.plrDmgTommyGun, vecDir, &tr, DMG_BULLET); + break; + case BULLET_PLAYER_RIFLE: + // make distance based! + pEntity->TraceAttack(pevAttacker, gSkillData.plrDmgRifle, vecDir, &tr, DMG_BULLET); + break; case BULLET_MONSTER_9MM: pEntity->TraceAttack( pevAttacker, gSkillData.monDmg9MM, vecDir, &tr, DMG_BULLET ); @@ -1523,15 +1545,6 @@ void CBaseEntity::FireBullets( ULONG cShots, Vector vecSrc, Vector vecDirShootin DecalGunshot( &tr, iBulletType ); } break; - - case BULLET_PLAYER_357: - pEntity->TraceAttack(pevAttacker, gSkillData.plrDmg357, vecDir, &tr, DMG_BULLET); - if ( !tracer ) - { - TEXTURETYPE_PlaySound(&tr, vecSrc, vecEnd, iBulletType); - DecalGunshot( &tr, iBulletType ); - } - break; case BULLET_NONE: // FIX pEntity->TraceAttack( pevAttacker, 50, vecDir, &tr, DMG_CLUB ); @@ -1612,13 +1625,6 @@ Vector CBaseEntity::FireBulletsPlayer( ULONG cShots, Vector vecSrc, Vector vecDi case BULLET_PLAYER_MP5: pEntity->TraceAttack( pevAttacker, gSkillData.plrDmgMP5, vecDir, &tr, DMG_BULLET ); break; - case BULLET_PLAYER_BUCKSHOT: - // make distance based! - pEntity->TraceAttack( pevAttacker, gSkillData.plrDmgBuckshot, vecDir, &tr, DMG_BULLET ); - break; - case BULLET_PLAYER_357: - pEntity->TraceAttack( pevAttacker, gSkillData.plrDmg357, vecDir, &tr, DMG_BULLET ); - break; case BULLET_NONE: // FIX pEntity->TraceAttack( pevAttacker, 50, vecDir, &tr, DMG_CLUB ); TEXTURETYPE_PlaySound( &tr, vecSrc, vecEnd, iBulletType ); diff --git a/dlls/controller.cpp b/dlls/controller.cpp index 4fed2a3d..1f0721f7 100644 --- a/dlls/controller.cpp +++ b/dlls/controller.cpp @@ -65,7 +65,7 @@ public: void Stop( void ); void Move( float flInterval ); - int CheckLocalMove( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ); + virtual 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 ); diff --git a/dlls/crossbow.cpp b/dlls/crossbow.cpp index 25feb3ff..2db48df1 100644 --- a/dlls/crossbow.cpp +++ b/dlls/crossbow.cpp @@ -448,9 +448,9 @@ void CCrossbow::FireBolt() pBolt->pev->avelocity.z = 10; #endif - if( !m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) + //if( !m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) // HEV suit - indicate out of ammo condition - m_pPlayer->SetSuitUpdate( "!HEV_AMO0", FALSE, 0 ); + // m_pPlayer->SetSuitUpdate( "!HEV_AMO0", FALSE, 0 ); m_flNextPrimaryAttack = GetNextAttackDelay( 0.75 ); diff --git a/dlls/crowbar.cpp b/dlls/crowbar.cpp index e1652239..253e9cd0 100644 --- a/dlls/crowbar.cpp +++ b/dlls/crowbar.cpp @@ -21,6 +21,7 @@ #include "nodes.h" #include "player.h" #include "gamerules.h" +#include "crowbar.h" #define CROWBAR_BODYHIT_VOLUME 128 #define CROWBAR_WALLHIT_VOLUME 512 diff --git a/dlls/cthulhu/Chicken.cpp b/dlls/cthulhu/Chicken.cpp new file mode 100755 index 00000000..b7e16f39 --- /dev/null +++ b/dlls/cthulhu/Chicken.cpp @@ -0,0 +1,423 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Chicken +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "soundent.h" +#include "decals.h" + +#define CHICKEN_IDLE 0 +#define CHICKEN_BORED 1 +#define CHICKEN_SCARED_BY_ENT 2 +#define CHICKEN_EAT 3 +#define CHICKEN_CROW 4 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +#include "Chicken.h" + +//========================================================= +// Furniture - this is the cool comment I cut-and-pasted +//========================================================= + +LINK_ENTITY_TO_CLASS( monster_chicken_feathers, CChickenFeathers ); + + +//========================================================= +// Furniture is killed +//========================================================= +void CChickenFeathers :: Die ( void ) +{ + SetThink ( SUB_Remove ); + SetNextThink(0); +} + +//========================================================= +// This used to have something to do with bees flying, but +// now it only initializes moving furniture in scripted sequences +//========================================================= +void CChickenFeathers :: Spawn( ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/feathers.mdl"); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/feathers.mdl"); + + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT; + 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(); + // so it doesn't get affected by things... + pev->deadflag = DEAD_DEAD; +} + +//========================================================= +// ID's Furniture as neutral (noone will attack it) +//========================================================= +int CChickenFeathers::Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_NONE; +} + + +////////////////////////////////////////////////////////////////////////////////////////////////////// + + +LINK_ENTITY_TO_CLASS( monster_chicken, CChicken ); + +TYPEDESCRIPTION CChicken::m_SaveData[] = +{ + DEFINE_FIELD( CChicken, m_iszGibModel, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CChicken, CBaseMonster ); + +//========================================================= +// 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 CChicken :: ISoundMask ( void ) +{ + return bits_SOUND_COMBAT | bits_SOUND_DANGER; +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CChicken :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_MUNDANE_PREY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CChicken :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + + pev->yaw_speed = ys; +} + +//========================================================= +// Spawn +//========================================================= +void CChicken :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/chicken.mdl"); + UTIL_SetSize( pev, Vector( -8, -8, 0 ), Vector( 8, 8, 8 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + pev->health = 4; + 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_iMode = CHICKEN_IDLE; + m_flNextSmellTime = gpGlobals->time; +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CChicken :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/chicken.mdl"); + + PRECACHE_SOUND("chicken/chick_cluck1.wav"); + PRECACHE_SOUND("chicken/chick_cluck2.wav"); + PRECACHE_SOUND("chicken/chick_cluck3.wav"); + PRECACHE_SOUND("chicken/chick_scream.wav"); + + m_iszGibModel = ALLOC_STRING("models/chickengibs.mdl"); + PRECACHE_MODEL( "models/chickengibs.mdl" ); //LRC + PRECACHE_MODEL( "models/feathers.mdl" ); //LRC +} + + +//========================================================= +// Killed. +//========================================================= +void CChicken :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->solid = SOLID_NOT; + + CSoundEnt::InsertSound ( bits_SOUND_WORLD, pev->origin, 128, 1 ); + + CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); + if ( pOwner ) + { + pOwner->DeathNotice( pev ); + } + CBaseMonster::Killed( pevAttacker, GIB_ALWAYS ); + + // send some faethers flying... + CBaseEntity *pNew = Create( "monster_chicken_feathers", pev->origin, pev->angles ); + CBaseMonster *pNewMonster = pNew->MyMonsterPointer( ); + pNew->pev->spawnflags |= 1; + + UTIL_Remove( this ); +} + +//========================================================= +// MonsterThink, overridden for chickens. +//========================================================= +void CChicken :: MonsterThink( void ) +{ + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + SetNextThink(RANDOM_FLOAT(1,1.5)); + else + { + SetNextThink(0.l);// keep monster thinking + } + + float flInterval = StudioFrameAdvance( ); // animate + + switch ( m_iMode ) + { + case CHICKEN_CROW: + if (m_fSequenceFinished) + { + SetActivity ( ACT_IDLE ); + m_iMode = CHICKEN_IDLE; + } + break; + case CHICKEN_BORED: + Look( 100 ); + if (HasConditions(bits_COND_SEE_FEAR | bits_COND_SEE_CLIENT)) + { + // if see something scary + //ALERT ( at_aiconsole, "Scared\n" ); + Eat( 30 + ( RANDOM_LONG(0,14) ) );// chicken will ignore food for 30 to 45 seconds + PickNewDest( CHICKEN_SCARED_BY_ENT ); + SetActivity ( ACT_RUN ); + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "chicken/chick_scream.wav", 0.8, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + } + break; + case CHICKEN_IDLE: + case CHICKEN_EAT: + { + // 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_COMBAT ) + { + PickNewDest( CHICKEN_SCARED_BY_ENT ); + SetActivity ( ACT_RUN ); + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "chicken/chick_scream.wav", 0.8, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + break; + } + } + } + // if not moving, sample environment to see if anything scary is around. Do a radius search 'look' at random. + if ( RANDOM_LONG(0,3) < 5 ) + { + Look( 150 ); + if (HasConditions(bits_COND_SEE_FEAR | bits_COND_SEE_CLIENT)) + { + // if see something scary + //ALERT ( at_aiconsole, "Scared\n" ); + Eat( 30 + ( RANDOM_LONG(0,14) ) );// chicken will ignore food for 30 to 45 seconds + PickNewDest( CHICKEN_SCARED_BY_ENT ); + SetActivity ( ACT_RUN ); + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "chicken/chick_scream.wav", 0.8, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + } + else if ( RANDOM_LONG(0,100) < 5 ) + { + // if chicken doesn't see anything, there's still a good chance that it will move. (boredom) + //ALERT ( at_aiconsole, "Bored\n" ); + PickNewDest( CHICKEN_BORED ); + SetActivity ( ACT_WALK ); + + if ( m_iMode == CHICKEN_EAT ) + { + // chicken 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! + else if ( m_iMode == CHICKEN_IDLE ) + { + float f = RANDOM_FLOAT(0,1.0); + if ( f < 0.1 ) + { + // crow + SetActivity ( ACT_EXCITED ); + m_iMode = CHICKEN_CROW; + } + else if ( f < 0.4 ) + { + // peck at ground... + SetActivity ( ACT_EAT ); + m_iMode = CHICKEN_EAT; + } + else + { + SetActivity ( ACT_IDLE ); + } + } + + break; + } + } + + if (( m_iMode != CHICKEN_SCARED_BY_ENT ) && ( m_iMode != CHICKEN_CROW )) + { + if ( RANDOM_FLOAT(0,1.0) < 0.05 ) + { + switch (RANDOM_LONG(0,2)) + { + case 0: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "chicken/chick_cluck1.wav", 0.4, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + break; + case 1: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "chicken/chick_cluck2.wav", 0.4, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + break; + default: + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "chicken/chick_cluck3.wav", 0.4, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + break; + } + } + } + + if ( m_flGroundSpeed != 0 ) + { + Move( flInterval ); + } +} + +//========================================================= +// Picks a new spot for chicken to run to.( +//========================================================= +void CChicken :: PickNewDest ( int iCondition ) +{ + Vector vecNewDir; + Vector vecDest; + float flDist; + + m_iMode = iCondition; + + do + { + // picks a random spot, requiring that it be at least 128 units away + // else, the chicken 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() < 64 ); + + 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 ); +} + +//========================================================= +// chickens's move function +//========================================================= +void CChicken :: 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 chicken to overshoot) + if ( flWaypointDist <= m_flGroundSpeed * flInterval ) + { + // take truncated step and stop + + SetActivity ( ACT_IDLE ); + + m_iMode = CHICKEN_IDLE; + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + diff --git a/dlls/cthulhu/Chicken.h b/dlls/cthulhu/Chicken.h new file mode 100755 index 00000000..f4e11c66 --- /dev/null +++ b/dlls/cthulhu/Chicken.h @@ -0,0 +1,48 @@ + +#ifndef CHICKEN_H +#define CHICKEN_H + + +class CChickenFeathers : public CBaseMonster +{ +public: + void Spawn ( void ); + void Die( void ); + int Classify ( void ); + virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } +}; + + + + +class CChicken : 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 Killed( entvars_t *pevAttacker, int iGib ); + + float m_flNextSmellTime; + int Classify ( void ); + int ISoundMask ( void ); + + virtual int HasCustomGibs( void ) { return m_iszGibModel; } + + int m_iszGibModel; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + // UNDONE: These don't necessarily need to be save/restored, but if we add more data, it may + int m_iMode; + // ----------------------------- +}; + + +#endif + diff --git a/dlls/cthulhu/ClimbingMonster.cpp b/dlls/cthulhu/ClimbingMonster.cpp new file mode 100755 index 00000000..04f3b7fa --- /dev/null +++ b/dlls/cthulhu/ClimbingMonster.cpp @@ -0,0 +1,216 @@ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "climbingmonster.h" + + +float CClimbingMonster :: ChangeYaw( int speed ) +{ + if (mbIsClimbing) + { + 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 ); +} + +BOOL CClimbingMonster::FGetNodeRoute ( Vector vecDest ) +{ + // if we are using the nodes, then we may be climbing + mbIsClimbing = TRUE; + + // while calculating the node route, assume the monster can fly. + // this will allow it to use the info_node_air nodes. + SetBits( pev->flags, FL_FLY ); + mOldType = pev->movetype; + pev->movetype = MOVETYPE_FLY; + m_flGroundSpeed = 100; + m_afCapability |= bits_CAP_FLY; + + BOOL b = CBaseMonster::FGetNodeRoute( vecDest ); + + return b; +} + +// +// We may need to override Move, in order to reset the FL_FLY flag again. +// + +BOOL CClimbingMonster::BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ) +{ + // by default, we are not climbing + mbIsClimbing = FALSE; + pev->movetype = mOldType; + ClearBits( pev->flags, FL_FLY ); + m_velocity = Vector(0,0,0); + m_afCapability &= ~bits_CAP_FLY; + + return CBaseMonster::BuildRoute ( vecGoal, iMoveFlag, pTarget ); +} + +BOOL CClimbingMonster::FRefreshRoute( void ) +{ + // by default, we are not climbing + mbIsClimbing = FALSE; + pev->movetype = mOldType; + ClearBits( pev->flags, FL_FLY ); + m_afCapability &= ~bits_CAP_FLY; + + return CBaseMonster::FRefreshRoute(); +} + +int CClimbingMonster :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + // if we are climbing, then we are effectively flying (like the controller) + if (mbIsClimbing) + { + TraceResult tr; + + UTIL_TraceHull( vecStart + Vector( 0, 0, 32), vecEnd + Vector( 0, 0, 32), dont_ignore_monsters, human_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 ); + // (tr.fStartSolid || tr.flFraction < 1.0) + if (tr.flFraction < 1.0) + { + if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) + return LOCALMOVE_VALID; + return LOCALMOVE_INVALID; + } + + return LOCALMOVE_VALID; + } + else // otherwise we walk, just like anyone else + { + return CBaseMonster::CheckLocalMove( vecStart, vecEnd, pTarget, pflDist ); + } +} + +BOOL CClimbingMonster:: ShouldAdvanceRoute( float flWaypointDist ) +{ + if (mbIsClimbing) + { + // 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; + } + else + { + return CBaseMonster::ShouldAdvanceRoute(flWaypointDist); + } +} + +void CClimbingMonster :: Move( float flInterval ) +{ + if (mbIsClimbing) + { + if ( pev->movetype == MOVETYPE_FLY ) + m_flGroundSpeed = m_climbSpeed; + } + CBaseMonster::Move( flInterval ); +} + +void CClimbingMonster::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + if (mbIsClimbing) + { + if ( pev->movetype == MOVETYPE_FLY ) + { + if ( gpGlobals->time - m_stopTime > 1.0 ) + { + if ( m_IdealActivity != m_movementActivity ) + { + m_IdealActivity = m_movementActivity; + m_flGroundSpeed = m_climbSpeed = 200; + } + } + // hardcoded momentum + Vector vecMove = pev->origin + (( vecDir + (m_vecTravel * 2.5) ).Normalize() * (m_flGroundSpeed * flInterval)); + + if ( m_IdealActivity != m_movementActivity ) + { + m_climbSpeed = UTIL_Approach( 100, m_climbSpeed, 75 * gpGlobals->frametime ); + if ( m_climbSpeed < 100 ) + m_stopTime = gpGlobals->time; + } + else + m_climbSpeed = UTIL_Approach( 20, m_climbSpeed, 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 ); + } +} + +void CClimbingMonster :: Stop( void ) +{ + if (mbIsClimbing) + { + Activity stopped = GetStoppedActivity(); + if ( m_IdealActivity != stopped ) + { + m_climbSpeed = 0; + m_IdealActivity = stopped; + } + pev->angles.z = 0; + pev->angles.x = 0; + m_vecTravel = g_vecZero; + } + else + { + CBaseMonster::Stop(); + } +} + +void CClimbingMonster :: Killed( entvars_t *pevAttacker, int iGib ) +{ + if (mbIsClimbing) + { + pev->movetype = MOVETYPE_STEP; + ClearBits( pev->flags, FL_ONGROUND ); + pev->angles.z = 0; + pev->angles.x = 0; + } + CBaseMonster::Killed( pevAttacker, iGib ); +} diff --git a/dlls/cthulhu/ClimbingMonster.h b/dlls/cthulhu/ClimbingMonster.h new file mode 100755 index 00000000..27c262b1 --- /dev/null +++ b/dlls/cthulhu/ClimbingMonster.h @@ -0,0 +1,33 @@ + +#ifndef CLIMBINGMONSTER_H +#define CLIMBINGMONSTER_H + +class CClimbingMonster : public CBaseMonster +{ +public: + float ChangeYaw( int speed ); + virtual BOOL FGetNodeRoute ( Vector vecDest ); + virtual BOOL BuildRoute ( const Vector &vecGoal, int iMoveFlag, CBaseEntity *pTarget ); + BOOL FRefreshRoute( void ); + BOOL ShouldAdvanceRoute( float flWaypointDist ); + virtual int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist );// check validity of a straight move through space + void Move( float flInterval = 0.1 ); + virtual void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + void Stop( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + +protected: + BOOL mbIsClimbing; + + int mOldType; + + Vector m_vecTravel; // Current direction + Vector m_velocity; + float m_climbSpeed; // Current climb speed + float m_stopTime; // Last time we stopped (to avoid switching states too soon) +}; + + +#endif + + diff --git a/dlls/cthulhu/Cow.cpp b/dlls/cthulhu/Cow.cpp new file mode 100755 index 00000000..03fcd0a9 --- /dev/null +++ b/dlls/cthulhu/Cow.cpp @@ -0,0 +1,133 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Chicken +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "soundent.h" +#include "decals.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +#include "Cow.h" + + +LINK_ENTITY_TO_CLASS( monster_cow, CCow ); + +TYPEDESCRIPTION CCow::m_SaveData[] = +{ + DEFINE_FIELD( CCow, m_iszGibModel, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CCow, CBaseMonster ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CCow :: Classify ( void ) +{ + return CLASS_NONE; +} + +//========================================================= +// Spawn +//========================================================= +void CCow :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/cow.mdl"); + UTIL_SetSize( pev, Vector( -48, -16, 0 ), Vector( 48, 16, 72 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + if (pev->health == 0) + pev->health = 20; + 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; +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CCow :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/cow.mdl"); + + m_iszGibModel = ALLOC_STRING("models/horsegibs.mdl"); + PRECACHE_MODEL( "models/horsegibs.mdl" ); + + PRECACHE_SOUND("cow/cowmoo.wav"); + PRECACHE_SOUND("cow/cowdie.wav"); +} + +//========================================================= +// Killed. +//========================================================= +void CCow :: IdleSound( void ) +{ + if (RANDOM_LONG(0,5) < 2) + { + // Play a random idle sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, "cow/cowmoo.wav", 1.0, ATTN_NORM, 0, 100 ); + } +} + +//========================================================= +// Killed. +//========================================================= +void CCow :: Killed( entvars_t *pevAttacker, int iGib ) +{ +// pev->solid = SOLID_NOT; + +// CSoundEnt::InsertSound ( bits_SOUND_WORLD, pev->origin, 128, 1 ); + + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "cow/cowdie.wav", 0.8, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + +// CBaseEntity *pOwner = CBaseEntity::Instance(pev->owner); +// if ( pOwner ) +// { +// pOwner->DeathNotice( pev ); +// } +// UTIL_Remove( this ); + CBaseMonster::Killed(pevAttacker, GIB_ALWAYS); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + diff --git a/dlls/cthulhu/Cow.h b/dlls/cthulhu/Cow.h new file mode 100755 index 00000000..30294e1c --- /dev/null +++ b/dlls/cthulhu/Cow.h @@ -0,0 +1,26 @@ + +#ifndef COW_H +#define COW_H + +class CCow : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + void IdleSound( void ); + + int Classify ( void ); + + virtual int HasCustomGibs( void ) { return m_iszGibModel; } + + int m_iszGibModel; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; +}; + + +#endif + diff --git a/dlls/cthulhu/Cthonian.cpp b/dlls/cthulhu/Cthonian.cpp new file mode 100755 index 00000000..2a14f16a --- /dev/null +++ b/dlls/cthulhu/Cthonian.cpp @@ -0,0 +1,1154 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Cthonian - 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" + +#define CTHONIAN_SPRINT_DIST 256 // how close the Cthonian has to get before starting to sprint and refusing to swerve + +int iCthonianSpitSprite; + + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_CTHONIAN_HURTREAR = LAST_COMMON_SCHEDULE + 1, + SCHED_CTHONIAN_SMELLFOOD, + SCHED_CTHONIAN_EAT, + SCHED_CTHONIAN_SNIFF_AND_EAT, + SCHED_CTHONIAN_WALLOW, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_CTHONIAN_REARTURN = LAST_COMMON_TASK + 1, +}; + +#include "Cthonian.h" + +//========================================================= +// Cthonian's spit projectile +//========================================================= + +LINK_ENTITY_TO_CLASS( cthonianspit, CCthonianSpit ); + +TYPEDESCRIPTION CCthonianSpit::m_SaveData[] = +{ + DEFINE_FIELD( CCthonianSpit, m_maxFrame, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CCthonianSpit, CBaseEntity ); + +void CCthonianSpit:: Spawn( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->classname = MAKE_STRING( "cthonianspit" ); + + 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 CCthonianSpit::Animate( void ) +{ + SetNextThink( 0.1 ); + + if ( pev->frame++ ) + { + if ( pev->frame > m_maxFrame ) + { + pev->frame = 0; + } + } +} + +void CCthonianSpit::Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CCthonianSpit *pSpit = GetClassPtr( (CCthonianSpit *)NULL ); + pSpit->Spawn(); + + UTIL_SetOrigin( pSpit, vecStart ); + pSpit->pev->velocity = vecVelocity; + pSpit->pev->owner = ENT(pevOwner); + + pSpit->SetThink ( Animate ); + pSpit->SetNextThink( 0.1 ); +} + +void CCthonianSpit :: Touch ( CBaseEntity *pOther ) +{ + TraceResult tr; + int iPitch; + + // splat sound + iPitch = RANDOM_FLOAT( 90, 110 ); + + // use bullsquid sounds + 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( iCthonianSpitSprite ); // 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.cthonianDmgSpit, DMG_ACID ); + } + + SetThink ( SUB_Remove ); + SetNextThink(0); +} + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define CTHONIAN_AE_SPIT ( 1 ) +#define CTHONIAN_AE_BITE ( 2 ) +#define CTHONIAN_AE_BLINK ( 3 ) +#define CTHONIAN_AE_TAILWHIP ( 4 ) +#define CTHONIAN_AE_REAR ( 5 ) +#define CTHONIAN_AE_THROW ( 6 ) + +LINK_ENTITY_TO_CLASS( monster_cthonian, CCthonian ); + +TYPEDESCRIPTION CCthonian::m_SaveData[] = +{ + DEFINE_FIELD( CCthonian, m_flLastHurtTime, FIELD_TIME ), + DEFINE_FIELD( CCthonian, m_flNextSpitTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CCthonian, CBaseMonster ); + +//========================================================= +// IgnoreConditions +//========================================================= +int CCthonian::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ( gpGlobals->time - m_flLastHurtTime <= 20 ) + { + // haven't been hurt in 20 seconds, so let the Cthonian care about stink. + iIgnore = bits_COND_SMELL | bits_COND_SMELL_FOOD; + } + + return iIgnore; +} + +//========================================================= +// IRelationship - overridden for Cthonian. +//========================================================= +int CCthonian::IRelationship ( CBaseEntity *pTarget ) +{ + return CBaseMonster :: IRelationship ( pTarget ); +} + +//========================================================= +// TakeDamage - overridden for Cthonian so we can keep track +// of how much time has passed since it was last injured +//========================================================= +int CCthonian :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + float flDist; + Vector vecApex; + + // if the Cthonian 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 > CTHONIAN_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 ); + } + } + } + + return CBaseMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CCthonian :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( IsMoving() && flDist >= 512 ) + { + // Cthonian 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 + 2.5; + } + else + { + // not moving, so spit again pretty soon. + m_flNextSpitTime = gpGlobals->time + 0.5; + } + + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CheckMeleeAttack1 - Cthonian is a big guy, so has a longer +// melee range than most monsters. This is the tailwhip attack +//========================================================= +BOOL CCthonian :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( m_hEnemy->pev->health <= gSkillData.cthonianDmgWhip && flDist <= 85 && flDot >= 0.7 ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack2 - Cthonian 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 CCthonian :: CheckMeleeAttack2 ( float flDot, float flDist ) +{ + if ( flDist <= 85 && flDot >= 0.7 && !HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) // The player & Cthonian 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 CCthonian :: FValidateHintType ( short sHint ) +{ + int i; + + static short sCthonianHints[] = + { + HINT_WORLD_HUMAN_BLOOD, + }; + + for ( i = 0 ; i < ARRAYSIZE ( sCthonianHints ) ; i++ ) + { + if ( sCthonianHints[ 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 CCthonian :: 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 CCthonian :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_PREDATOR; +} + +//========================================================= +// IdleSound +//========================================================= +#define CTHONIAN_ATTN_IDLE (float)1.5 +void CCthonian :: IdleSound ( void ) +{ + switch ( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "cthonian/cth_idle1.wav", 1, CTHONIAN_ATTN_IDLE ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "cthonian/cth_idle2.wav", 1, CTHONIAN_ATTN_IDLE ); + break; + case 2: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "cthonian/cth_idle3.wav", 1, CTHONIAN_ATTN_IDLE ); + break; + } +} + +//========================================================= +// PainSound +//========================================================= +void CCthonian :: PainSound ( void ) +{ + int iPitch = RANDOM_LONG( 85, 120 ); + + switch ( RANDOM_LONG(0,3) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "cthonian/cth_pain1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "cthonian/cth_pain2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 2: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "cthonian/cth_pain3.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 3: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "cthonian/cth_pain4.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } +} + +//========================================================= +// AlertSound +//========================================================= +void CCthonian :: AlertSound ( void ) +{ + int iPitch = RANDOM_LONG( 140, 160 ); + + switch ( RANDOM_LONG ( 0, 2 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "cthonian/cth_idle1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "cthonian/cth_idle2.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 2: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "cthonian/cth_idle3.wav", 1, ATTN_NORM, 0, iPitch ); + break; + } +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CCthonian :: 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 CCthonian :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case CTHONIAN_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 * 96 ); + 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( iCthonianSpitSprite ); // model + WRITE_BYTE ( 15 ); // count + WRITE_BYTE ( 210 ); // speed + WRITE_BYTE ( 25 ); // noise ( client will divide by 100 ) + MESSAGE_END(); + + CCthonianSpit::Shoot( pev, vecSpitOffset, vecSpitDir * 900 ); + } + break; + + case CTHONIAN_AE_BITE: + { + // SOUND HERE! + pev->origin.z -= 32; + CBaseEntity *pHurt = CheckTraceHullAttack( 90, gSkillData.cthonianDmgBite, DMG_SLASH ); + pev->origin.z += 32; + + 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 CTHONIAN_AE_TAILWHIP: + { + CBaseEntity *pHurt = CheckTraceHullAttack( 90, gSkillData.cthonianDmgWhip, 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 CTHONIAN_AE_BLINK: + { + // close eye. + pev->skin = 1; + } + break; + + case CTHONIAN_AE_REAR: + { + // the Cthonian does not jump, but rears...hence, no need to throw into the air + + //float flGravity = CVAR_GET_FLOAT( "sv_gravity" ); + + // throw the cthonian 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 CTHONIAN_AE_THROW: + { + int iPitch; + + // cthonian throws its prey IF the prey is a client. + CBaseEntity *pHurt = CheckTraceHullAttack( 90, 0, 0 ); + + + if ( pHurt ) + { + // croonchy bite sound : use bullsquid sounds + iPitch = RANDOM_FLOAT( 90, 110 ); + switch ( RANDOM_LONG( 0, 1 ) ) + { + case 0: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "cthonian/cth_bite1.wav", 1, ATTN_NORM, 0, iPitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "cthonian/cth_bite2.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 CCthonian :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/cthonian.mdl"); + UTIL_SetSize( pev, Vector( -36, -36, 0 ), Vector( 36, 36, 100 ) ); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + if (pev->health == 0) + pev->health = gSkillData.cthonianHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + m_flNextSpitTime = gpGlobals->time; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CCthonian :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/cthonian.mdl"); + + PRECACHE_MODEL("sprites/bigspit.spr");// spit projectile. + + iCthonianSpitSprite = 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("cthonian/cth_die1.wav"); + PRECACHE_SOUND("cthonian/cth_die2.wav"); + + PRECACHE_SOUND("cthonian/cth_idle1.wav"); + PRECACHE_SOUND("cthonian/cth_idle2.wav"); + PRECACHE_SOUND("cthonian/cth_idle3.wav"); + + PRECACHE_SOUND("cthonian/cth_pain1.wav"); + PRECACHE_SOUND("cthonian/cth_pain2.wav"); + PRECACHE_SOUND("cthonian/cth_pain3.wav"); + PRECACHE_SOUND("cthonian/cth_pain4.wav"); + + PRECACHE_SOUND("cthonian/cth_attackgrowl.wav"); + //PRECACHE_SOUND("cthonian/cthonian_attackgrowl2.wav"); + //PRECACHE_SOUND("cthonian/cthonian_attackgrowl3.wav"); + + PRECACHE_SOUND("bullchicken/bc_acid1.wav"); + + PRECACHE_SOUND("cthonian/cth_bite1.wav"); + PRECACHE_SOUND("cthonian/cth_bite2.wav"); + + PRECACHE_SOUND("bullchicken/bc_spithit1.wav"); + PRECACHE_SOUND("bullchicken/bc_spithit2.wav"); + +} + +//========================================================= +// DeathSound +//========================================================= +void CCthonian :: DeathSound ( void ) +{ + switch ( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "cthonian/cth_die1.wav", 1, ATTN_NORM ); + break; + case 1: + EMIT_SOUND( ENT(pev), CHAN_VOICE, "cthonian/cth_die2.wav", 1, ATTN_NORM ); + break; + } +} + +//========================================================= +// AttackSound +//========================================================= +void CCthonian :: AttackSound ( void ) +{ + // use bullsquid sounds for spitting + 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 cthonian because there are things +// that need to be checked every think. +//======================================================== +void CCthonian :: 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() < CTHONIAN_SPRINT_DIST ) + { + pev->framerate = 1.25; + } + } + +} + +//======================================================== +// AI Schedules Specific to this monster +//========================================================= + +// primary range attack +Task_t tlCthonianRangeAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slCthonianRangeAttack1[] = +{ + { + tlCthonianRangeAttack1, + ARRAYSIZE ( tlCthonianRangeAttack1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "Cthonian Range Attack1" + }, +}; + +// Chase enemy schedule +Task_t tlCthonianChaseEnemy1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_RANGE_ATTACK1 },// !!!OEM - this will stop nasty Cthonian oscillation. + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slCthonianChaseEnemy[] = +{ + { + tlCthonianChaseEnemy1, + ARRAYSIZE ( tlCthonianChaseEnemy1 ), + 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, + "Cthonian Chase Enemy" + }, +}; + +Task_t tlCthonianHurtRear[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SOUND_WAKE, (float)0 }, + { TASK_CTHONIAN_REARTURN, (float)0 }, + { TASK_FACE_ENEMY, (float)0 },// in case Cthonian didn't turn all the way in the air. +}; + +Schedule_t slCthonianHurtRear[] = +{ + { + tlCthonianHurtRear, + ARRAYSIZE ( tlCthonianHurtRear ), + 0, + 0, + "CthonianHurtRear" + } +}; + +// Cthonian walks to something tasty and eats it. +Task_t tlCthonianEat[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the Cthonian 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 slCthonianEat[] = +{ + { + tlCthonianEat, + ARRAYSIZE( tlCthonianEat ), + 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, + "CthonianEat" + } +}; + +// this is a bit different than just Eat. We use this schedule when the food is far away, occluded, or behind +// the Cthonian. This schedule plays a sniff animation before going to the source of food. +Task_t tlCthonianSniffAndEat[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the Cthonian 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 slCthonianSniffAndEat[] = +{ + { + tlCthonianSniffAndEat, + ARRAYSIZE( tlCthonianSniffAndEat ), + 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, + "CthonianSniffAndEat" + } +}; + +// Cthonian does this to stinky things. +Task_t tlCthonianWallow[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_EAT, (float)10 },// this is in case the Cthonian 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 Cthonian 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 slCthonianWallow[] = +{ + { + tlCthonianWallow, + ARRAYSIZE( tlCthonianWallow ), + 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, + + "CthonianWallow" + } +}; + +DEFINE_CUSTOM_SCHEDULES( CCthonian ) +{ + slCthonianRangeAttack1, + slCthonianChaseEnemy, + slCthonianHurtRear, + slCthonianEat, + slCthonianSniffAndEat, + slCthonianWallow +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CCthonian, CBaseMonster ); + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t *CCthonian :: GetSchedule( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_ALERT: + { + if ( HasConditions(bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE) ) + { + return GetScheduleOfType ( SCHED_CTHONIAN_HURTREAR ); + } + + 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_CTHONIAN_SNIFF_AND_EAT ); + } + + // food is right out in the open. Just go get it. + return GetScheduleOfType( SCHED_CTHONIAN_EAT ); + } + + if ( HasConditions(bits_COND_SMELL) ) + { + // there's something stinky. + CSound *pSound; + + pSound = PBestScent(); + if ( pSound ) + return GetScheduleOfType( SCHED_CTHONIAN_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) ) + { + 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_CTHONIAN_SNIFF_AND_EAT ); + } + + // food is right out in the open. Just go get it. + return GetScheduleOfType( SCHED_CTHONIAN_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* CCthonian :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_RANGE_ATTACK1: + return &slCthonianRangeAttack1[ 0 ]; + break; + case SCHED_CTHONIAN_HURTREAR: + return &slCthonianHurtRear[ 0 ]; + break; + case SCHED_CTHONIAN_EAT: + return &slCthonianEat[ 0 ]; + break; + case SCHED_CTHONIAN_SNIFF_AND_EAT: + return &slCthonianSniffAndEat[ 0 ]; + break; + case SCHED_CTHONIAN_WALLOW: + return &slCthonianWallow[ 0 ]; + break; + case SCHED_CHASE_ENEMY: + return &slCthonianChaseEnemy[ 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 Cthonian because it needs to +// know explicitly when the last attempt to chase the enemy +// failed, since that impacts its attack choices. +//========================================================= +void CCthonian :: 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, "cthonian/cth_attackgrowl.wav", 1, ATTN_NORM ); + // break; + //} + + CBaseMonster :: StartTask ( pTask ); + break; + } + case TASK_CTHONIAN_REARTURN: + { + SetActivity ( ACT_HOP ); // this is not actually a hop, but a rear up... + 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 CCthonian :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_CTHONIAN_REARTURN: + { + MakeIdealYaw( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if ( m_fSequenceFinished ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + default: + { + CBaseMonster :: RunTask( pTask ); + break; + } + } +} + + +//========================================================= +// GetIdealState - Overridden for Cthonian. +//========================================================= +MONSTERSTATE CCthonian :: 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 + */ + { + m_hEnemy = NULL; + m_IdealMonsterState = MONSTERSTATE_ALERT; + break; + } + } + + m_IdealMonsterState = CBaseMonster :: GetIdealState(); + + return m_IdealMonsterState; +} + diff --git a/dlls/cthulhu/Cthonian.h b/dlls/cthulhu/Cthonian.h new file mode 100755 index 00000000..dc2f202e --- /dev/null +++ b/dlls/cthulhu/Cthonian.h @@ -0,0 +1,62 @@ + +#ifndef CTHONIAN_H +#define CTHONIAN_H + +class CCthonianSpit : 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; +}; + +///////////////////////////////////////////////////////////////////////////// + +class CCthonian : 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[]; + + 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. +}; + + +#endif diff --git a/dlls/cthulhu/Dagger.cpp b/dlls/cthulhu/Dagger.cpp new file mode 100755 index 00000000..820b83f6 --- /dev/null +++ b/dlls/cthulhu/Dagger.cpp @@ -0,0 +1,328 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + + +#define KNIFE_BODYHIT_VOLUME 128 +#define KNIFE_WALLHIT_VOLUME 512 + +#include "Dagger.h" + +LINK_ENTITY_TO_CLASS( weapon_knife, CKnife ); + + + +enum knife_e { + KNIFE_IDLE = 0, + KNIFE_DRAW, + KNIFE_HOLSTER, + KNIFE_ATTACK1, + KNIFE_ATTACK2 +}; + + +void CKnife::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_KNIFE; + SET_MODEL(ENT(pev), "models/w_knife.mdl"); + m_iClip = -1; + + FallInit();// get ready to fall down. +} + + +void CKnife::Precache( void ) +{ + PRECACHE_MODEL("models/v_knife.mdl"); + PRECACHE_MODEL("models/w_knife.mdl"); +// PRECACHE_MODEL("models/p_knife.mdl"); + PRECACHE_SOUND("weapons/cbar_hit1.wav"); + PRECACHE_SOUND("weapons/cbar_hit2.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod1.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod2.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod3.wav"); + PRECACHE_SOUND("weapons/cbar_miss1.wav"); +} + +int CKnife::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = NULL; + p->iMaxAmmo1 = -1; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 0; + p->iPosition = 0; + p->iId = WEAPON_KNIFE; + p->iWeight = KNIFE_WEIGHT; + return 1; +} + + +BOOL CKnife::Deploy( ) +{ + return DefaultDeploy( "models/v_knife.mdl", "", KNIFE_DRAW, "knife" ); +} + +void CKnife::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + SendWeaponAnim( KNIFE_HOLSTER ); +} + +int CKnife::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + + +void CKnife::FindHullIntersection( const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity ) +{ + int i, j, k; + float distance; + float *minmaxs[2] = {mins, maxs}; + TraceResult tmpTrace; + Vector vecHullEnd = tr.vecEndPos; + Vector vecEnd; + + distance = 1e6f; + + vecHullEnd = vecSrc + ((vecHullEnd - vecSrc)*2); + UTIL_TraceLine( vecSrc, vecHullEnd, dont_ignore_monsters, pEntity, &tmpTrace ); + if ( tmpTrace.flFraction < 1.0 ) + { + tr = tmpTrace; + return; + } + + for ( i = 0; i < 2; i++ ) + { + for ( j = 0; j < 2; j++ ) + { + for ( k = 0; k < 2; k++ ) + { + vecEnd.x = vecHullEnd.x + minmaxs[i][0]; + vecEnd.y = vecHullEnd.y + minmaxs[j][1]; + vecEnd.z = vecHullEnd.z + minmaxs[k][2]; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, pEntity, &tmpTrace ); + if ( tmpTrace.flFraction < 1.0 ) + { + float thisDistance = (tmpTrace.vecEndPos - vecSrc).Length(); + if ( thisDistance < distance ) + { + tr = tmpTrace; + distance = thisDistance; + } + } + } + } + } +} + + +void CKnife::PrimaryAttack() +{ + if (! Swing( 1 )) + { + SetThink( SwingAgain ); + SetNextThink( 0.5 ); + } +} + + +void CKnife::SwingAgain( void ) +{ + Swing( 0 ); + DontThink(); +} + + +int CKnife::Swing( int fFirst ) +{ + int fDidHit = FALSE; + + TraceResult tr; + + UTIL_MakeVectors (m_pPlayer->pev->v_angle); + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecEnd = vecSrc + gpGlobals->v_forward * 32; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); + + if ( tr.flFraction >= 1.0 ) + { + UTIL_TraceHull( vecSrc, vecEnd, dont_ignore_monsters, head_hull, ENT( m_pPlayer->pev ), &tr ); + if ( tr.flFraction < 1.0 ) + { + // Calculate the point of intersection of the line (or hull) and the object we hit + // This is and approximation of the "best" intersection + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + if ( !pHit || pHit->IsBSPModel() ) + FindHullIntersection( vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, m_pPlayer->edict() ); + vecEnd = tr.vecEndPos; // This is the point on the actual surface (the hull could have hit space) + } + } + + if ( tr.flFraction >= 1.0 ) + { + if (fFirst) + { + // miss + switch( (m_iSwing++) % 3 ) + { + case 0: + SendWeaponAnim( KNIFE_ATTACK1 ); break; + case 1: + SendWeaponAnim( KNIFE_ATTACK2 ); break; + default: + SendWeaponAnim( KNIFE_ATTACK1 ); break; + } + m_flNextPrimaryAttack = gpGlobals->time + 0.5; + // play wiff or swish sound + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/cbar_miss1.wav", 1, ATTN_NORM, 0, 94 + RANDOM_LONG(0,0xF)); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + } + } + else + { + // hit + fDidHit = TRUE; + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + switch( ((m_iSwing++) % 2) + 1 ) + { + case 0: + SendWeaponAnim( KNIFE_ATTACK1); break; + case 1: + SendWeaponAnim( KNIFE_ATTACK2 ); break; + default: + SendWeaponAnim( KNIFE_ATTACK1 ); break; + } + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + ClearMultiDamage( ); + if ( (m_flNextPrimaryAttack + 1 < gpGlobals->time) || g_pGameRules->IsMultiplayer() ) + { + // first swing does full damage + pEntity->TraceAttack(m_pPlayer->pev, gSkillData.plrDmgKnife, gpGlobals->v_forward, &tr, DMG_SLASH ); + } + else + { + // subsequent swings do half + pEntity->TraceAttack(m_pPlayer->pev, gSkillData.plrDmgKnife / 2, gpGlobals->v_forward, &tr, DMG_SLASH ); + } + ApplyMultiDamage( m_pPlayer->pev, m_pPlayer->pev ); + + m_flNextPrimaryAttack = gpGlobals->time + 0.5; + + // play thwack, or dong sound + float flVol = 1.0; + int fHitWorld = TRUE; + + if (pEntity) + { + if (pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE) + { + // play thwack sound + switch( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/cbar_hitbod1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/cbar_hitbod2.wav", 1, ATTN_NORM); break; + case 2: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/cbar_hitbod3.wav", 1, ATTN_NORM); break; + } + m_pPlayer->m_iWeaponVolume = KNIFE_BODYHIT_VOLUME; + if (!pEntity->IsAlive() ) + return TRUE; + else + flVol = 0.1; + + fHitWorld = FALSE; + } + } + + // play texture hit sound + // UNDONE: Calculate the correct point of intersection when we hit with the hull instead of the line + + if (fHitWorld) + { + float fvolbar = TEXTURETYPE_PlaySound(&tr, vecSrc, vecSrc + (vecEnd-vecSrc)*2, BULLET_PLAYER_KNIFE); + + if ( g_pGameRules->IsMultiplayer() ) + { + // override the volume here, cause we don't play texture sounds in multiplayer, + // and fvolbar is going to be 0 from the above call. + + fvolbar = 1; + } + + // also play Knife strike + switch( RANDOM_LONG(0,1) ) + { + case 0: + //UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "weapons/dagger_hit1.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hit1.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + case 1: + //UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "weapons/dagger_hit2.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hit2.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + } + } + + // delay the decal a bit + m_trHit = tr; + SetNextThink( 0.2 ); + + m_pPlayer->m_iWeaponVolume = flVol * KNIFE_WALLHIT_VOLUME; + } + return fDidHit; +} + +void CKnife::WeaponIdle( void ) +{ + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + (30.0 / 15.0) + RANDOM_FLOAT ( 1, 3 ); + SendWeaponAnim( KNIFE_IDLE, 1 ); +} + + diff --git a/dlls/cthulhu/Dagger.h b/dlls/cthulhu/Dagger.h new file mode 100755 index 00000000..11b66730 --- /dev/null +++ b/dlls/cthulhu/Dagger.h @@ -0,0 +1,36 @@ + +#ifndef DAGGER_H +#define DAGGER_H + + +class CKnife : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 0; } + void EXPORT SwingAgain( void ); + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + void FindHullIntersection( const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity ); + + void PrimaryAttack( void ); + int Swing( int fFirst ); + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + int m_iSwing; + TraceResult m_trHit; + void WeaponIdle( void ); + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } +}; + + +#endif \ No newline at end of file diff --git a/dlls/cthulhu/DeepOne.cpp b/dlls/cthulhu/DeepOne.cpp new file mode 100755 index 00000000..544ca4d8 --- /dev/null +++ b/dlls/cthulhu/DeepOne.cpp @@ -0,0 +1,442 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// DeepOne +//========================================================= + +// UNDONE: Don't flinch every time you get hit + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "flyingmonster.h" +#include "schedule.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define DEEP_ONE_AE_ATTACK_RIGHT 0x01 +#define DEEP_ONE_AE_ATTACK_LEFT 0x02 +#define DEEP_ONE_AE_ATTACK_BOTH 0x03 + +#define DEEP_ONE_FLINCH_DELAY 2 // at most one flinch every n secs + + +#include "DeepOne.h" + + +LINK_ENTITY_TO_CLASS( monster_deepone, CDeepOne ); + +const char *CDeepOne::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CDeepOne::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CDeepOne::pAttackSounds[] = +{ + "deepone/do_attack1.wav", + "deepone/do_attack2.wav", +}; + +const char *CDeepOne::pIdleSounds[] = +{ + "deepone/do_idle1.wav", + "deepone/do_idle2.wav", +}; + +const char *CDeepOne::pAlertSounds[] = +{ + "deepone/do_alert1.wav", + "deepone/do_alert2.wav", +}; + +const char *CDeepOne::pPainSounds[] = +{ + "deepone/do_pain1.wav", + "deepone/do_pain2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CDeepOne :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CDeepOne :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + + pev->yaw_speed = ys; +} + +int CDeepOne :: 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 CDeepOne :: PainSound( void ) +{ + int pitch = GetVoicePitch() - 5 + RANDOM_LONG(0,10); + + if (RANDOM_LONG(0,5) < 2) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], GetVolume(), ATTN_NORM, 0, pitch ); +} + +void CDeepOne :: AlertSound( void ) +{ + int pitch = GetVoicePitch() - 5 + RANDOM_LONG(0,10); + + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAlertSounds[ RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1) ], GetVolume(), ATTN_NORM, 0, pitch ); +} + +void CDeepOne :: IdleSound( void ) +{ + int pitch = GetVoicePitch() - 5 + RANDOM_LONG(0,10); + + // Play a random idle sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pIdleSounds[ RANDOM_LONG(0,ARRAYSIZE(pIdleSounds)-1) ], GetVolume(), ATTN_NORM, 0, pitch ); +} + +void CDeepOne :: AttackSound( void ) +{ + int pitch = GetVoicePitch() - 5 + RANDOM_LONG(0,10); + + // Play a random attack sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], GetVolume(), ATTN_NORM, 0, pitch ); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= + +float CDeepOne :: GetAttackDist( void ) +{ + return 70.0; +} + +void CDeepOne :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + double fldist = GetAttackDist(); + + switch( pEvent->event ) + { + case DEEP_ONE_AE_ATTACK_RIGHT: + { + // do stuff for this event. + // ALERT( at_console, "Slash right!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( fldist, gSkillData.deeponeDmgOneSlash, 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 DEEP_ONE_AE_ATTACK_LEFT: + { + // do stuff for this event. + // ALERT( at_console, "Slash left!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( fldist, gSkillData.deeponeDmgOneSlash, 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 DEEP_ONE_AE_ATTACK_BOTH: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( fldist, gSkillData.deeponeDmgBothSlash, 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 CDeepOne :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/deepone.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; + if (pev->health == 0) + pev->health = gSkillData.deeponeHealth; + 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 | bits_CAP_SWIM | bits_CAP_CLIMB | bits_CAP_JUMP; + + MonsterInit(); + +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CDeepOne :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/deepone.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 CDeepOne::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { + 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 + DEEP_ONE_FLINCH_DELAY; + } + + return iIgnore; + +} + +////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +LINK_ENTITY_TO_CLASS( monster_dagon, CDagon ); + +void CDagon :: Spawn( void ) +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/dagon.mdl"); + + UTIL_SetSize(pev, Vector(-32, -32, 0), Vector(32, 32, 216)); + + if (pev->health == 0) + pev->health = gSkillData.deeponeHealth * 9; + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + 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 | bits_CAP_SWIM | bits_CAP_CLIMB | bits_CAP_JUMP; + + MonsterInit(); +} + +void CDagon :: Precache( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL( "models/dagon.mdl" ); + CDeepOne::Precache(); +} + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CDagon :: 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 <= 128 && flDot >= 0.7 && m_hEnemy != NULL && FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) ) + if ( flDist <= 160 && flDot >= 0.7 && m_hEnemy != NULL && FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ) ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +float CDagon :: GetAttackDist( void ) +{ + return 160.0; +} + +void CDagon :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case DEEP_ONE_AE_ATTACK_RIGHT: + case DEEP_ONE_AE_ATTACK_LEFT: + case DEEP_ONE_AE_ATTACK_BOTH: + { + pev->size.z *= 0.25; + } + break; + + default: + break; + } + + CDeepOne::HandleAnimEvent( pEvent ); + + pev->origin.z += 64; + + CDeepOne::HandleAnimEvent( pEvent ); + + pev->origin.z += 64; + + CDeepOne::HandleAnimEvent( pEvent ); + + pev->origin.z -= 128; + + switch( pEvent->event ) + { + case DEEP_ONE_AE_ATTACK_RIGHT: + case DEEP_ONE_AE_ATTACK_LEFT: + case DEEP_ONE_AE_ATTACK_BOTH: + { + pev->size.z *= 4.0; + } + break; + + default: + break; + } +} + +int CDagon :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( bitsDamageType & DMG_CRUSH ) return 0; + if ( bitsDamageType & DMG_SLASH ) return 0; + if ( bitsDamageType & DMG_BULLET ) return 0; + if ( bitsDamageType & DMG_CLUB ) return 0; + + return CDeepOne::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + + diff --git a/dlls/cthulhu/DeepOne.h b/dlls/cthulhu/DeepOne.h new file mode 100755 index 00000000..adde5000 --- /dev/null +++ b/dlls/cthulhu/DeepOne.h @@ -0,0 +1,52 @@ + +#ifndef DEEP_ONE_H +#define DEEP_ONE_H + +class CDeepOne : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + virtual float GetAttackDist( 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[]; + + virtual int GetVoicePitch( void ) { return PITCH_NORM; }; + virtual float GetVolume (void) { return 0.5; }; + + // 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 ); +}; + +class CDagon : public CDeepOne +{ +public: + void Spawn( void ); + void Precache( void ); + virtual float GetAttackDist( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + virtual BOOL CheckMeleeAttack1( float flDot, float flDist ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + virtual int GetVoicePitch( void ) { return RANDOM_LONG(40,50); }; + virtual float GetVolume (void) { return 1.0; }; +}; + +#endif diff --git a/dlls/cthulhu/DimensionalShambler.cpp b/dlls/cthulhu/DimensionalShambler.cpp new file mode 100755 index 00000000..ce542c81 --- /dev/null +++ b/dlls/cthulhu/DimensionalShambler.cpp @@ -0,0 +1,418 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// SHAMBLER +//========================================================= + +// UNDONE: Don't flinch every time you get hit + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "effects.h" + +const float PI = 3.14159; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define SHAMBLER_AE_ATTACK_RIGHT 0x01 +#define SHAMBLER_AE_ATTACK_LEFT 0x02 +#define SHAMBLER_AE_ATTACK_BOTH 0x03 + +#define SHAMBLER_FLINCH_DELAY 2 // at most one flinch every n secs + + +#include "DimensionalShambler.h" + + +LINK_ENTITY_TO_CLASS( monster_dimensionalshambler, CDimensionalShambler ); + +const char *CDimensionalShambler::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CDimensionalShambler::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CDimensionalShambler::pAttackSounds[] = +{ + "shambler/ds_attack1.wav", + "shambler/ds_attack2.wav", +}; + +const char *CDimensionalShambler::pIdleSounds[] = +{ + "shambler/ds_idle1.wav", + "shambler/ds_idle2.wav", +}; + +const char *CDimensionalShambler::pAlertSounds[] = +{ + "shambler/ds_alert1.wav", + "shambler/ds_alert2.wav", +}; + +const char *CDimensionalShambler::pPainSounds[] = +{ + "shambler/ds_pain1.wav", + "shambler/ds_pain2.wav", + "shambler/ds_pain3.wav", + "shambler/ds_pain4.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CDimensionalShambler :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CDimensionalShambler :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + + pev->yaw_speed = ys; +} + +int CDimensionalShambler :: 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 CDimensionalShambler :: 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 CDimensionalShambler :: 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 CDimensionalShambler :: 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 CDimensionalShambler :: 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 CDimensionalShambler :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SHAMBLER_AE_ATTACK_RIGHT: + { + // do stuff for this event. + // ALERT( at_console, "Slash right!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.shamblerDmgOneSlash, 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 SHAMBLER_AE_ATTACK_LEFT: + { + // do stuff for this event. + // ALERT( at_console, "Slash left!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.shamblerDmgOneSlash, 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 SHAMBLER_AE_ATTACK_BOTH: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.shamblerDmgBothSlash, 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 CDimensionalShambler :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/shambler.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; + if (pev->health == 0) + pev->health = gSkillData.shamblerHealth; + 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; + m_flNextTeleport = gpGlobals->time + 5.0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CDimensionalShambler :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/shambler.mdl"); + + PRECACHE_SOUND("debris/beamstart6.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( 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]); + + // sprites + PRECACHE_MODEL("sprites/c-tele1.spr"); + PRECACHE_MODEL("sprites/d-tele1.spr"); + // for the warpball + PRECACHE_MODEL( "sprites/Fexplo1.spr" ); + PRECACHE_MODEL( "sprites/XFlare1.spr" ); + PRECACHE_SOUND( "debris/beamstart2.wav" ); + PRECACHE_SOUND( "debris/beamstart7.wav" ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + + +int CDimensionalShambler::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { + 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 + SHAMBLER_FLINCH_DELAY; + } + + return iIgnore; + +} + +//========================================================= +// Look +//========================================================= + +void CDimensionalShambler :: Look ( int iDistance ) +{ + CBaseMonster::Look ( iDistance ); + + float yaw, y; + BOOL bDown, bUp; + Vector vOffset; + + // if we are within teleport time + if (m_flNextTeleport < gpGlobals->time) + { + // if we can see the enemy + if (HasConditions(bits_COND_SEE_ENEMY)) + { + CBaseEntity* pEnemy = m_hEnemy; + + // and we can find a place to teleport to + yaw = RANDOM_FLOAT(-PI,PI); + + Vector vecTele; + + // Store original origin + Vector myOrig = pev->origin; + + for (y =-PI; y < PI; y += PI/6) + { + // if vecGoal+yaw*36 (close enough) is ok. + vOffset = Vector(36*cos(y+yaw),36*sin(y+yaw),pev->mins.z - pEnemy->pev->mins.z); + + // check that we are over some solid floor - i.e. if we move down 8 bits, we are in a brush! + // now check we are not stuck in a brush anyway + pev->origin = m_hEnemy->pev->origin + vOffset; + + pev->origin.z += 1; + DROP_TO_FLOOR ( ENT(pev) ); + + bDown = (fabs((pev->origin.z + pev->mins.z) - (pEnemy->pev->origin.z + pEnemy->pev->mins.z)) < 16.0); + bUp = WALK_MOVE ( ENT(pev), 0,1, WALKMOVE_NORMAL ); + + // this (hopefully) ensures that the dimensional shambler will not teleport to empty air (e.g. if the player is on a ledge) + // and will account for slight slopes or steps. + //if ( bDown && WALK_MOVE ( ENT(pev), 0,1, WALKMOVE_NORMAL ) ) + if ( bUp && bDown ) + { + // yes, we can teleport there + CEnvWarpBall* pWarpOut = (CEnvWarpBall*)CBaseEntity::Create("env_warpball",myOrig+Vector(0,0,36),Vector(0,0,0),edict()); + pWarpOut->pev->frags = 8; // num of beams + pWarpOut->pev->health = 128; // max length of beam + pWarpOut->Use(NULL,NULL,USE_TOGGLE,0); + // make a flash here + //CSprite *pHereSprite = CSprite::SpriteCreate( "sprites/d-tele1.spr", myOrig+Vector(0,0,36), TRUE ); + //pHereSprite->pev->scale = 1.0; + //pHereSprite->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + //pHereSprite->AnimateAndDie( 20 ); + CEnvWarpBall* pWarpIn = (CEnvWarpBall*)CBaseEntity::Create("env_warpball",pev->origin+Vector(0,0,36),Vector(0,0,0),edict()); + pWarpIn->pev->frags = 8; // num of beams + pWarpIn->pev->health = 128; // max length of beam + pWarpIn->Use(NULL,NULL,USE_TOGGLE,0); + // make a flash there + //CSprite *pThereSprite = CSprite::SpriteCreate( "sprites/c-tele1.spr", pev->origin+Vector(0,0,36), TRUE ); + //pThereSprite->pev->scale = 1.0; + //pThereSprite->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + //pThereSprite->AnimateAndDie( 20 ); + // now the sound + //EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, "debris/beamstart6.wav", 1.0, ATTN_NORM, 0, 100 + RANDOM_LONG(-5,5) ); + // set ideal facing + MakeIdealYaw(m_hEnemy->pev->origin); + pev->angles.y = pev->ideal_yaw; + // clear the route + RouteClear(); + // set the next teleport time (10 secs) + m_flNextTeleport = gpGlobals->time + RANDOM_LONG(8.0,12.0); + + return; + } + } + + // we failed to find a teleport spot + // restore the old origin + pev->origin = myOrig; + } + } +} + diff --git a/dlls/cthulhu/DreadName.cpp b/dlls/cthulhu/DreadName.cpp new file mode 100755 index 00000000..5dd190cb --- /dev/null +++ b/dlls/cthulhu/DreadName.cpp @@ -0,0 +1,183 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + + +#define DREAD_NAME_SPEAK_VOLUME 512 + +#include "DreadName.h" + +LINK_ENTITY_TO_CLASS( weapon_dread_name, CDreadName ); + + + +enum dread_name_e { + DREAD_NAME_OPEN = 0, + DREAD_NAME_IDLE1, + DREAD_NAME_IDLE2, + DREAD_NAME_IDLE3, + DREAD_NAME_CAST, + DREAD_NAME_CLOSE +}; + + +void CDreadName::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_DREAD_NAME; + SET_MODEL(ENT(pev), "models/w_dread_name.mdl"); + m_iClip = -1; + m_iIsCasting = 0; + + FallInit();// get ready to fall down. +} + + +void CDreadName::Precache( void ) +{ + PRECACHE_MODEL("models/v_dread_name.mdl"); + PRECACHE_MODEL("models/w_dread_name.mdl"); + PRECACHE_MODEL("models/p_dread_name.mdl"); + PRECACHE_SOUND("weapons/dreadname.wav"); +} + +int CDreadName::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = NULL; + p->iMaxAmmo1 = -1; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 0; + p->iId = WEAPON_DREAD_NAME; + p->iWeight = DREAD_NAME_WEIGHT; + return 1; +} + + + +BOOL CDreadName::Deploy( ) +{ + return DefaultDeploy( "models/v_dread_name.mdl", "models/p_dread_name.mdl", DREAD_NAME_OPEN, "dread name" ); +} + +void CDreadName::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + SendWeaponAnim( DREAD_NAME_CLOSE ); +} + +int CDreadName::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +void CDreadName::PrimaryAttack() +{ + // send the weapon animation + SendWeaponAnim( DREAD_NAME_CAST ); + m_iIsCasting = 1; + m_flNextPrimaryAttack = gpGlobals->time + (42.0/30.0); + m_flTimeWeaponIdle = gpGlobals->time + 1.0; +} + +void CDreadName::WeaponIdle( void ) +{ + ResetEmptySound( ); + + if ( m_flTimeWeaponIdle > gpGlobals->time ) + return; + + if ( m_iIsCasting ) + { + // find all monster in a sphere + const int MAX_MONSTER = 1000; + CBaseEntity* pEntInSphere[MAX_MONSTER]; + int iMonsters = UTIL_MonstersInSphere(pEntInSphere, MAX_MONSTER, pev->origin, 384); + + // send them the panic message + int iClass; + for (int i = 0; i < iMonsters; i++) + { + CBaseEntity* pEnt = pEntInSphere[i]; + + // is this a monster? + if (!(pEnt->pev->flags & FL_MONSTER)) continue; + + CBaseMonster* pMon = (CBaseMonster*)pEnt; + + // is it an alien monster (as opposed to a cultist, chicken, etc)? + iClass = pMon->Classify(); + + if (iClass != CLASS_ALIEN_MILITARY + && iClass != CLASS_ALIEN_MONSTER + && iClass != CLASS_ALIEN_PREY + && iClass != CLASS_ALIEN_PREDATOR) + continue; + + pMon->Panic(m_pPlayer->pev); + } + + m_iIsCasting = 0; + m_pPlayer->LoseSanity(5); + m_flTimeWeaponIdle = gpGlobals->time + 0.5; + return; + } + + int iAnim; + + float flRand = RANDOM_FLOAT(0,1); + + if ( flRand <= 0.4 ) + { + iAnim = DREAD_NAME_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT(2,3); + } + else if ( flRand <= 0.75 ) + { + iAnim = DREAD_NAME_IDLE2; + m_flTimeWeaponIdle = gpGlobals->time + 3; + } + else + { + iAnim = DREAD_NAME_IDLE3; + m_flTimeWeaponIdle = gpGlobals->time + 3; + } + + SendWeaponAnim( iAnim ); +} + + + + + + diff --git a/dlls/cthulhu/DreadName.h b/dlls/cthulhu/DreadName.h new file mode 100755 index 00000000..0952258f --- /dev/null +++ b/dlls/cthulhu/DreadName.h @@ -0,0 +1,32 @@ + +#ifndef CROWBAR_H +#define CROWBAR_H + + +class CDreadName : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 4; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + void PrimaryAttack( void ); + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + void WeaponIdle( void ); + int m_iIsCasting; + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } +}; + + +#endif \ No newline at end of file diff --git a/dlls/cthulhu/DunwichHorror.cpp b/dlls/cthulhu/DunwichHorror.cpp new file mode 100755 index 00000000..09f9847f --- /dev/null +++ b/dlls/cthulhu/DunwichHorror.cpp @@ -0,0 +1,990 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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" + +#include "bm.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define DH_AE_STEP1 1 // Footstep left +#define DH_AE_STEP2 2 // Footstep right +#define DH_AE_STEP3 3 // Footstep back left +#define DH_AE_STEP4 4 // Footstep back right +#define DH_AE_SACK 5 // Sack slosh +#define DH_AE_DEATHSOUND 6 // Death sound + +#define DH_AE_MELEE_ATTACKBR 8 // Leg attack +#define DH_AE_MELEE_ATTACKBL 9 // Leg attack +#define DH_AE_MELEE_ATTACK1 10 // Leg attack +//#define DH_AE_MORTAR_ATTACK1 11 // Launch a mortar +//#define DH_AE_LAY_CRAB 12 // Lay a headcrab +#define DH_AE_JUMP_FORWARD 13 // Jump up and forward +#define DH_AE_SCREAM 14 // alert sound +#define DH_AE_PAIN_SOUND 15 // pain sound +#define DH_AE_ATTACK_SOUND 16 // attack sound +//#define DH_AE_BIRTH_SOUND 17 // birth sound +#define DH_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 DH_ATTACKDIST 170 +//#define DH_MORTARDIST 800 +//#define DH_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 gDHSpitSprite, gDHSpitDebrisSprite; + + +class CDunwichHorror : 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 SetActivity ( Activity NewActivity ); + virtual void MonsterThink( void ); + + void NodeStart( int iszNextNode ); + void NodeReach( void ); + BOOL ShouldGoToNode( void ); + + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + 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; + } + + //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 *pSackSounds[]; + static const char *pDeathSounds[]; + static const char *pAttackSounds[]; + static const char *pAttackHitSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pFootSounds[]; + + CUSTOM_SCHEDULES; + +private: + float m_nodeTime; + float m_mortarTime; + float m_painSoundTime; + + BOOL m_bVisible; + BOOL m_bBecomingVisible; +}; +LINK_ENTITY_TO_CLASS( monster_dunwichhorror, CDunwichHorror ); + +TYPEDESCRIPTION CDunwichHorror::m_SaveData[] = +{ + DEFINE_FIELD( CDunwichHorror, m_nodeTime, FIELD_TIME ), + DEFINE_FIELD( CDunwichHorror, m_mortarTime, FIELD_TIME ), + DEFINE_FIELD( CDunwichHorror, m_painSoundTime, FIELD_TIME ), + DEFINE_FIELD( CDunwichHorror, m_bVisible, FIELD_BOOLEAN ), + DEFINE_FIELD( CDunwichHorror, m_bBecomingVisible, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CDunwichHorror, CBaseMonster ); + +const char *CDunwichHorror::pSackSounds[] = +{ + "gonarch/gon_sack1.wav", + "gonarch/gon_sack2.wav", + "gonarch/gon_sack3.wav", +}; + +const char *CDunwichHorror::pDeathSounds[] = +{ + "gonarch/gon_die1.wav", +}; + +const char *CDunwichHorror::pAttackSounds[] = +{ + "gonarch/gon_attack1.wav", + "gonarch/gon_attack2.wav", + "gonarch/gon_attack3.wav", +}; +const char *CDunwichHorror::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CDunwichHorror::pAlertSounds[] = +{ + "gonarch/gon_alert1.wav", + "gonarch/gon_alert2.wav", + "gonarch/gon_alert3.wav", +}; + +const char *CDunwichHorror::pPainSounds[] = +{ + "gonarch/gon_pain2.wav", + "gonarch/gon_pain4.wav", + "gonarch/gon_pain5.wav", +}; + +const char *CDunwichHorror::pFootSounds[] = +{ + "gonarch/gon_step1.wav", + "gonarch/gon_step2.wav", + "gonarch/gon_step3.wav", +}; + + + +void CDunwichHorror :: KeyValue( KeyValueData *pkvd ) +{ +#if 0 + if (FStrEq(pkvd->szKeyName, "volume")) + { + m_volume = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else +#endif + if (FStrEq(pkvd->szKeyName, "visible")) + { + m_bVisible = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CDunwichHorror :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CDunwichHorror :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 100; + break; + default: + ys = 90; + } + pev->yaw_speed = ys; +} + +void CDunwichHorror :: MonsterThink ( void ) +{ + // if we are becoming visible, then update the fx amount... + if (m_bBecomingVisible) + { + // update the fx amount + pev->renderamt += 5; + // don't go above 255 + if (pev->renderamt > 255) pev->renderamt = 255; + // if we are at max fx amount, then + if (pev->renderamt == 255) + { + // stop updating the fx amount + m_bBecomingVisible = FALSE; + } + } + + CBaseMonster :: MonsterThink (); +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CDunwichHorror :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case DH_AE_MELEE_ATTACKBR: + case DH_AE_MELEE_ATTACKBL: + case DH_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 DH_AE_MELEE_ATTACKBR: + pHurt->pev->velocity = pHurt->pev->velocity + (forward * 150) + Vector(0,0,250) - (right * 200); + break; + + case DH_AE_MELEE_ATTACKBL: + pHurt->pev->velocity = pHurt->pev->velocity + (forward * 150) + Vector(0,0,250) + (right * 200); + break; + + case DH_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 DH_AE_SCREAM: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAlertSounds ); + break; + + case DH_AE_PAIN_SOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); + break; + + case DH_AE_ATTACK_SOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_WEAPON, pAttackSounds ); + break; + + case DH_AE_SACK: + if ( RANDOM_LONG(0,100) < 30 ) + EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pSackSounds ); + break; + + case DH_AE_DEATHSOUND: + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pDeathSounds ); + break; + + case DH_AE_STEP1: // Footstep left + case DH_AE_STEP3: // Footstep back left + EMIT_SOUND_ARRAY_DYN( CHAN_ITEM, pFootSounds ); + break; + + case DH_AE_STEP4: // Footstep back right + case DH_AE_STEP2: // Footstep right + EMIT_SOUND_ARRAY_DYN( CHAN_BODY, pFootSounds ); + break; + +// case DH_AE_MORTAR_ATTACK1: +// LaunchMortar(); +// break; + + case DH_AE_JUMP_FORWARD: + ClearBits( pev->flags, FL_ONGROUND ); + + UTIL_SetOrigin (this, pev->origin + Vector ( 0 , 0 , 1) );// take her 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 DH_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 CDunwichHorror :: 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 CDunwichHorror :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( bitsDamageType & DMG_POWDER_IBN && !m_bVisible) + { + m_bVisible = TRUE; + m_bBecomingVisible = TRUE; + return 0; + } + + // if we are invisible, we are invulnerable + if (!m_bVisible) return 0; + + // 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 CDunwichHorror::LaunchMortar( void ) +{ + m_mortarTime = gpGlobals->time + RANDOM_FLOAT( 2, 15 ); + + Vector startPos = pev->origin; + startPos.z += 180; + Vector vecLaunch = g_vecZero; + + if (m_pCine) // is a scripted_action making me shoot? + { + if (m_hTargetEnt != NULL) // don't check m_fTurnType- bigmomma can fire in any direction. + { + vecLaunch = VecCheckSplatToss( pev, startPos, m_hTargetEnt->pev->origin, RANDOM_FLOAT( 150, 500 ) ); + } + if (vecLaunch == g_vecZero) + { + vecLaunch = pev->movedir; + } + } + else + { + vecLaunch = pev->movedir; + } + + 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, vecLaunch ); + pBomb->pev->gravity = 1.0; + MortarSpray( startPos, Vector(0,0,1), gDHSpitSprite, 24 ); +} +*/ + +//========================================================= +// Spawn +//========================================================= +void CDunwichHorror :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + 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; + if (pev->health == 0) + 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; + m_bBecomingVisible = FALSE; + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CDunwichHorror :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/big_mom.mdl"); + + PRECACHE_SOUND_ARRAY( pSackSounds ); + PRECACHE_SOUND_ARRAY( pDeathSounds ); + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pAttackHitSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); + PRECACHE_SOUND_ARRAY( pFootSounds ); + + // TEMP: Squid + PRECACHE_MODEL("sprites/mommaspit.spr");// spit projectile. + gDHSpitSprite = PRECACHE_MODEL("sprites/mommaspout.spr");// client side spittle. + gDHSpitDebrisSprite = PRECACHE_MODEL("sprites/mommablob.spr" ); + + PRECACHE_SOUND( "bullchicken/bc_acid1.wav" ); + PRECACHE_SOUND( "bullchicken/bc_spithit1.wav" ); + PRECACHE_SOUND( "bullchicken/bc_spithit2.wav" ); +} + + +void CDunwichHorror::Activate( void ) +{ + if ( m_hTargetEnt == NULL ) + Remember( bits_MEMORY_ADVANCE_NODE ); // Start 'er up + + CBaseMonster::Activate(); +} + + +void CDunwichHorror::NodeStart( int iszNextNode ) +{ + pev->netname = iszNextNode; + + CBaseEntity *pTarget = NULL; + + if ( pev->netname ) + { + pTarget = UTIL_FindEntityByTargetname( NULL, STRING(pev->netname) ); + } + + + 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 CDunwichHorror::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 CDunwichHorror::CheckMeleeAttack1( float flDot, float flDist ) +{ + if (flDot >= 0.7) + { + if ( flDist <= DH_ATTACKDIST ) + return TRUE; + } + return FALSE; +} + + +// Lay a crab +BOOL CDunwichHorror::CheckMeleeAttack2( float flDot, float flDist ) +{ + return FALSE; +} + + +// Mortar launch +BOOL CDunwichHorror::CheckRangeAttack1( float flDot, float flDist ) +{ + /* + if ( flDist <= DH_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_DH_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 tlDHBigNode[] = +{ + { 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 slDHBigNode[] = +{ + { + tlDHBigNode, + ARRAYSIZE ( tlDHBigNode ), + 0, + 0, + "DH Big Node" + }, +}; + + +Task_t tlDHNodeFail[] = +{ + { TASK_NODE_DELAY, (float)10 }, // Try to do something else for 10 seconds + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +Schedule_t slDHNodeFail[] = +{ + { + tlDHNodeFail, + ARRAYSIZE ( tlDHNodeFail ), + 0, + 0, + "DH NodeFail" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CDunwichHorror ) +{ + slDHBigNode, + slDHNodeFail, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CDunwichHorror, CBaseMonster ); + + + + +Schedule_t *CDunwichHorror::GetScheduleOfType( int Type ) +{ + switch( Type ) + { + case SCHED_DH_NODE: + return slDHBigNode; + break; + + case SCHED_NODE_FAIL: + return slDHNodeFail; + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + + +BOOL CDunwichHorror::ShouldGoToNode( void ) +{ + if ( HasMemory( bits_MEMORY_ADVANCE_NODE ) ) + { + if ( m_nodeTime < gpGlobals->time ) + return TRUE; + } + return FALSE; +} + +// Overridden to make Dunwich Horror jump on command; the model doesn't support it otherwise. +void CDunwichHorror :: SetActivity ( Activity NewActivity ) +{ + int iSequence; + + if (NewActivity == ACT_HOP) + { + iSequence = LookupSequence( "jump" ); + } + else + { + 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; +} + +Schedule_t *CDunwichHorror::GetSchedule( void ) +{ + if ( ShouldGoToNode() ) + { + return GetScheduleOfType( SCHED_DH_NODE ); + } + + return CBaseMonster::GetSchedule(); +} + + +void CDunwichHorror::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 CDunwichHorror::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; + } +} + + +#endif diff --git a/dlls/cthulhu/FormlessSpawn.cpp b/dlls/cthulhu/FormlessSpawn.cpp new file mode 100755 index 00000000..e3b7b43c --- /dev/null +++ b/dlls/cthulhu/FormlessSpawn.cpp @@ -0,0 +1,493 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Formless Spawn +//========================================================= + +// UNDONE: Don't flinch every time you get hit + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "game.h" +#include "soundent.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define FS_AE_ATTACK ( 1 ) +#define FS_AE_JUMPATTACK ( 2 ) + +#define FORMLESS_SPAWN_FLINCH_DELAY 3 // at most one flinch every n secs + + +Task_t tlFSRangeAttack1[] = +{ + { 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 slFSRangeAttack1[] = +{ + { + tlFSRangeAttack1, + ARRAYSIZE ( tlFSRangeAttack1 ), + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED, + 0, + "FSRangeAttack1" + }, +}; + + +#include "FormlessSpawn.h" + + +LINK_ENTITY_TO_CLASS( monster_formless_spawn, CFormlessSpawn ); + +DEFINE_CUSTOM_SCHEDULES( CFormlessSpawn ) +{ + slFSRangeAttack1, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CFormlessSpawn, CBaseMonster ); + +const char *CFormlessSpawn::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CFormlessSpawn::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CFormlessSpawn::pAttackSounds[] = +{ + "formless_spawn/fs_attack1.wav", + "formless_spawn/fs_attack2.wav", +}; + +const char *CFormlessSpawn::pIdleSounds[] = +{ + "formless_spawn/fs_idle1.wav", + "formless_spawn/fs_idle2.wav", +}; + +const char *CFormlessSpawn::pAlertSounds[] = +{ + "formless_spawn/fs_alert1.wav", + "formless_spawn/fs_alert2.wav", +}; + +const char *CFormlessSpawn::pPainSounds[] = +{ + "formless_spawn/fs_pain1.wav", + "formless_spawn/fs_pain2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CFormlessSpawn :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CFormlessSpawn :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_IDLE: + ys = 120; + break; + case ACT_RUN: + case ACT_WALK: + ys = 120; + break; + case ACT_TURN_LEFT: + case ACT_TURN_RIGHT: + ys = 120; + break; + case ACT_RANGE_ATTACK1: + ys = 120; + break; + default: + ys = 120; + break; + } + + pev->yaw_speed = ys; +} + +int CFormlessSpawn :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Take 20% damage from bullets + if ( bitsDamageType & DMG_CRUSH ) return 0; + if ( bitsDamageType & DMG_SLASH ) return 0; + if ( bitsDamageType & DMG_FALL ) return 0; + if ( bitsDamageType & DMG_CLUB ) return 0; + if ( bitsDamageType & DMG_DROWN ) return 0; + + 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.1; + } + + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CFormlessSpawn :: 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 CFormlessSpawn :: 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 CFormlessSpawn :: 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 CFormlessSpawn :: 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 CFormlessSpawn :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case FS_AE_ATTACK: + { + // do stuff for this event. + // ALERT( at_console, "Slash right!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.formless_spawnDmgAttack, 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 FS_AE_JUMPATTACK: + { + ClearBits( pev->flags, FL_ONGROUND ); + + UTIL_SetOrigin (this, pev->origin + Vector ( 0 , 0 , 1) );// take him off ground so engine doesn't instantly reset onground + UTIL_MakeVectors ( pev->angles ); + + Vector vdir = (pev->angles).Normalize(); + UTIL_SetOrigin (this, pev->origin - (8*vdir) );// move him back a bit + BOOL bOK = WALK_MOVE ( ENT(pev), 0,1, WALKMOVE_NORMAL ); + if (!bOK) + UTIL_SetOrigin (this, pev->origin + (8*vdir) );// move him forward a bit + 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], 1.0, ATTN_IDLE, 0, 100 ); + + pev->velocity = vecJumpDir; + m_flNextAttack = gpGlobals->time + 2; + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CFormlessSpawn :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/formless_spawn.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; + if (pev->health == 0) + pev->health = gSkillData.formless_spawnHealth; + pev->view_ofs = VEC_VIEW;// 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; + m_afCapability = bits_CAP_DOORS_GROUP; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CFormlessSpawn :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/formless_spawn.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]); +} + +//========================================================= +// 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 CFormlessSpawn :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER; +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + +int CFormlessSpawn::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { + 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 + FORMLESS_SPAWN_FLINCH_DELAY; + } + + return iIgnore; + +} + +//========================================================= +// RunTask +//========================================================= +void CFormlessSpawn :: 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); + } + } +} + +void CFormlessSpawn :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + { + EMIT_SOUND_DYN( edict(), CHAN_WEAPON, pAttackSounds[0], 1.0, ATTN_IDLE, 0, 100 ); + SetTouch ( LeapTouch ); + m_IdealActivity = ACT_RANGE_ATTACK1; + break; + } + default: + { + CBaseMonster :: StartTask( pTask ); + } + } +} + +//========================================================= +// LeapTouch - this is the headcrab's touch function when it +// is in the air +//========================================================= +void CFormlessSpawn :: 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(pAttackSounds), 1.0, ATTN_IDLE, 0, 100 ); + + pOther->TakeDamage( pev, pev, gSkillData.formless_spawnDmgAttack, DMG_SLASH ); + } + + SetTouch( NULL ); +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CFormlessSpawn :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( FBitSet( pev->flags, FL_ONGROUND ) && flDist > 64 && flDist <= 512 && flDot >= 0.65 ) + { + return TRUE; + } + return FALSE; +} + +Schedule_t* CFormlessSpawn :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_RANGE_ATTACK1: + { + return &slFSRangeAttack1[ 0 ]; + } + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} diff --git a/dlls/cthulhu/FormlessSpawn.h b/dlls/cthulhu/FormlessSpawn.h new file mode 100755 index 00000000..bae04c67 --- /dev/null +++ b/dlls/cthulhu/FormlessSpawn.h @@ -0,0 +1,45 @@ + +#ifndef FORMLESSSPAWN_H +#define FORMLESSSPAWN_H + +class CFormlessSpawn : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void RunTask ( Task_t *pTask ); + void StartTask ( Task_t *pTask ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int IgnoreConditions ( void ); + void EXPORT LeapTouch ( CBaseEntity *pOther ); + virtual int ISoundMask( 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 ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; }; + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + Schedule_t* GetScheduleOfType ( int Type ); + + CUSTOM_SCHEDULES; +}; + + +#endif + diff --git a/dlls/cthulhu/Gangster.cpp b/dlls/cthulhu/Gangster.cpp new file mode 100755 index 00000000..cd874d35 --- /dev/null +++ b/dlls/cthulhu/Gangster.cpp @@ -0,0 +1,1963 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// gangster +//========================================================= + +//========================================================= +// Hit groups! +//========================================================= +/* + + 1 - Head + 2 - Stomach + 3 - Gun + +*/ + + +#include "extdll.h" +#include "util.h" +#include "plane.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_fGangsterQuestion; // true if an idle gangster asked a question. Cleared when someone answers. + +extern DLL_GLOBAL int g_iSkillLevel; + +//========================================================= +// monster-specific DEFINE's +//========================================================= +#define GANGSTER_CLIP_SIZE 36 // how many bullets in a clip? - NOTE: 3 round burst sound, so keep as 3 * x! +#define GANGSTER_VOL 0.70 // volume of gangster sounds +#define GANGSTER_ATTN ATTN_NORM // attenutation of gangster sentences +#define GANGSTER_LIMP_HEALTH 20 +#define GANGSTER_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a gangster with a single headshot. +#define GANGSTER_NUM_HEADS 3 // how many gangster heads are there? +#define GANGSTER_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill +#define GANGSTER_SENTENCE_VOLUME (float)0.85 // volume of gangster sentences + +#define GANGSTER_REVOLVER ( 1 << 0) +#define GANGSTER_SHOTGUN ( 1 << 1) +#define GANGSTER_TOMMYGUN ( 1 << 2) + +#define GUN_GROUP 1 +#define GUN_REVOLVER 0 +#define GUN_SHOTGUN 1 +#define GUN_TOMMYGUN 2 +#define GUN_NONE 3 + +#define HEAD_GROUP 2 +#define HEAD_SLIME 0 +#define HEAD_PSYCHO 1 +#define HEAD_PURPLE 2 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define GANGSTER_AE_RELOAD ( 2 ) +#define GANGSTER_AE_KICK ( 3 ) +#define GANGSTER_AE_BURST1 ( 4 ) +#define GANGSTER_AE_BURST2 ( 5 ) +#define GANGSTER_AE_BURST3 ( 6 ) +#define GANGSTER_AE_CAUGHT_ENEMY ( 7 ) // gangster established sight with an enemy (player only) that had previously eluded the squad. +#define GANGSTER_AE_DROP_GUN ( 8 ) // gangster (probably dead) is dropping his mp5. + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_GANGSTER_SUPPRESS = LAST_COMMON_SCHEDULE + 1, + SCHED_GANGSTER_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_GANGSTER_COVER_AND_RELOAD, + SCHED_GANGSTER_SWEEP, + SCHED_GANGSTER_FOUND_ENEMY, + SCHED_GANGSTER_WAIT_FACE_ENEMY, + SCHED_GANGSTER_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. + SCHED_GANGSTER_ELOF_FAIL, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_GANGSTER_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, + TASK_GANGSTER_SPEAK_SENTENCE, + TASK_GANGSTER_CHECK_FIRE, +}; + +//========================================================= +// monster-specific conditions +//========================================================= +#define bits_COND_GANGSTER_NOFIRE ( bits_COND_SPECIAL1 ) + + +#include "Gangster.h" + + +LINK_ENTITY_TO_CLASS( monster_gangster, CGangster ); + +TYPEDESCRIPTION CGangster::m_SaveData[] = +{ + DEFINE_FIELD( CGangster, m_flNextPainTime, FIELD_TIME ), +// DEFINE_FIELD( CGangster, m_flLastEnemySightTime, FIELD_TIME ), // don't save, go to zero + DEFINE_FIELD( CGangster, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CGangster, m_fFirstEncounter, FIELD_BOOLEAN ), + DEFINE_FIELD( CGangster, m_cClipSize, FIELD_INTEGER ), + DEFINE_FIELD( CGangster, m_voicePitch, FIELD_INTEGER ), +// DEFINE_FIELD( CShotgun, m_iBrassShell, FIELD_INTEGER ), +// DEFINE_FIELD( CShotgun, m_iShotgunShell, FIELD_INTEGER ), + DEFINE_FIELD( CGangster, m_iSentence, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CGangster, CSquadMonster ); + +const char *CGangster::pGangsterSentences[] = +{ + "GA_GREN", // grenade scared gangster + "GA_ALERT", // sees player + "GA_MONSTER", // sees monster + "GA_COVER", // running to cover + "GA_CHARGE", // running out to get the enemy + "GA_TAUNT", // say rude things +}; + +enum +{ + GANGSTER_SENT_NONE = -1, + GANGSTER_SENT_GREN = 0, + GANGSTER_SENT_ALERT, + GANGSTER_SENT_MONSTER, + GANGSTER_SENT_COVER, + GANGSTER_SENT_CHARGE, + GANGSTER_SENT_TAUNT, +} GANGSTER_SENTENCE_TYPES; + +//========================================================= +// Speak Sentence - say your cued up sentence. +// +// Some gangster sentences (take cover and charge) rely on actually +// being able to execute the intended action. It's really lame +// when a gangster 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 gangster has +// started moving. +//========================================================= +void CGangster :: SpeakSentence( void ) +{ + if ( m_iSentence == GANGSTER_SENT_NONE ) + { + // no sentence cued up. + return; + } + + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), pGangsterSentences[ m_iSentence ], GANGSTER_SENTENCE_VOLUME, GANGSTER_ATTN, 0, m_voicePitch); + JustSpoke(); + } +} + +//========================================================= +// IRelationship. +//========================================================= +int CGangster::IRelationship ( CBaseEntity *pTarget ) +{ + return CSquadMonster::IRelationship( pTarget ); +} + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CGangster :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + //if ( GetBodygroup( 1 ) != 3 ) + if (( GetBodygroup( GUN_GROUP ) != GUN_NONE ) && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) + {// throw a gun if the gangster has one + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun; + if (FBitSet( pev->weapons, GANGSTER_SHOTGUN )) + { + pGun = DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else if (FBitSet( pev->weapons, GANGSTER_TOMMYGUN )) + { + pGun = DropItem( "weapon_tommygun", vecGunPos, vecGunAngles ); + } + else + { + pGun = DropItem( "weapon_revolver", 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 gangsters because they +// hear the DANGER sound that is made by hand grenades and +// other dangerous items. +//========================================================= +int CGangster :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_DANGER; +} + +//========================================================= +// someone else is talking - don't speak +//========================================================= +BOOL CGangster :: 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 CGangster :: JustSpoke( void ) +{ + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(1.5, 2.0); + m_iSentence = GANGSTER_SENT_NONE; +} + +//========================================================= +// PrescheduleThink - this function runs after conditions +// are collected and before scheduling code is run. +//========================================================= +void CGangster :: 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 gangsters. +//========================================================= +BOOL CGangster :: FCanCheckAttacks ( void ) +{ + if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CGangster :: 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 Gangster. We must +// disqualify the machine gun attack if the enemy is occluded. +//========================================================= +BOOL CGangster :: 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 gangsters second range attack. +//========================================================= +BOOL CGangster :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; +} + + +//========================================================= +// TraceAttack - make sure we're not taking it in the helmet +//========================================================= +void CGangster :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +//========================================================= +// TakeDamage - overridden for the gangster because the gangster +// needs to forget that he is in cover if he's hurt. (Obviously +// not in a safe place anymore). +//========================================================= +int CGangster :: 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 CGangster :: 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_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 CGangster :: IdleSound( void ) +{ + if (FOkToSpeak() && (g_fGangsterQuestion || RANDOM_LONG(0,1))) + { + if (!g_fGangsterQuestion) + { + // ask question or make statement + switch (RANDOM_LONG(0,2)) + { + case 0: // check in + SENTENCEG_PlayRndSz(ENT(pev), "GA_CHECK", GANGSTER_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fGangsterQuestion = 1; + break; + case 1: // question + SENTENCEG_PlayRndSz(ENT(pev), "GA_QUEST", GANGSTER_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fGangsterQuestion = 2; + break; + case 2: // statement + SENTENCEG_PlayRndSz(ENT(pev), "GA_IDLE", GANGSTER_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + } + else + { + switch (g_fGangsterQuestion) + { + case 1: // check in + SENTENCEG_PlayRndSz(ENT(pev), "GA_CLEAR", GANGSTER_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + case 2: // question + SENTENCEG_PlayRndSz(ENT(pev), "GA_ANSWER", GANGSTER_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + g_fGangsterQuestion = 0; + } + JustSpoke(); + } +} + +//========================================================= +// CheckAmmo - overridden for the gangster because he actually +// uses ammo! (base class doesn't) +//========================================================= +void CGangster :: CheckAmmo ( void ) +{ + if ( m_cAmmoLoaded <= 0 ) + { + SetConditions(bits_COND_NO_AMMO_LOADED); + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGangster :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_MILITARY; +} + +//========================================================= +//========================================================= +CBaseEntity *CGangster :: 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 CGangster :: GetGunPosition( ) +{ + // because origin is at the centre of the box, not at the bottom + if (m_fStanding ) + { + return pev->origin + Vector( 0, 0, 24 ); +// return pev->origin + Vector( 0, 0, 60 ); + } + else + { + return pev->origin + Vector( 0, 0, 12 ); +// return pev->origin + Vector( 0, 0, 48 ); + } +} + +//========================================================= +// Shoot +//========================================================= +void CGangster :: 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 CGangster :: 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.gangsterShotgunPellets, vecShootOrigin, vecShootDir, VECTOR_CONE_15DEGREES, 2048, BULLET_PLAYER_SHOTGUN, 0 ); // shoot +-7.5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + +void CGangster :: Revolver ( 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(1, vecShootOrigin, vecShootDir, VECTOR_CONE_15DEGREES, 2048, BULLET_PLAYER_REVOLVER, 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 CGangster :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case GANGSTER_AE_DROP_GUN: + { + if (pev->spawnflags & SF_MONSTER_NO_WPN_DROP) break; + + 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, GANGSTER_SHOTGUN )) + { + DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else if (FBitSet( pev->weapons, GANGSTER_TOMMYGUN )) + { + DropItem( "weapon_tommygun", vecGunPos, vecGunAngles ); + } + else + { + DropItem( "weapon_revolver", vecGunPos, vecGunAngles ); + } + + } + break; + + case GANGSTER_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 GANGSTER_AE_BURST1: + { + if ( FBitSet( pev->weapons, GANGSTER_TOMMYGUN )) + { + 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 if ( FBitSet( pev->weapons, GANGSTER_SHOTGUN )) + { + Shotgun( ); + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/sbarrel1.wav", 1, ATTN_NORM ); + + + } + else + { + Revolver( ); + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/revolver_shot1.wav", 1, ATTN_NORM ); + } + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 384, 0.3 ); + } + break; + + case GANGSTER_AE_BURST2: + case GANGSTER_AE_BURST3: + Shoot(); + break; + + case GANGSTER_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.gangsterDmgKick, DMG_CLUB ); + } + } + break; + + case GANGSTER_AE_CAUGHT_ENEMY: + { + if ( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz(ENT(pev), "GA_ALERT", GANGSTER_SENTENCE_VOLUME, GANGSTER_ATTN, 0, m_voicePitch); + JustSpoke(); + } + + } + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CGangster :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/gangster.mdl"); + //UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + UTIL_SetSize(pev, Vector(-16,-16,-36), Vector(16,16,36)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + if (pev->health == 0) + pev->health = gSkillData.gangsterHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextPainTime = gpGlobals->time; + m_iSentence = GANGSTER_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 gangster 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 = GANGSTER_TOMMYGUN; + // pev->weapons = GANGSTER_SHOTGUN; + } + + if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, 2);// pick a head, any head + } + + if (FBitSet( pev->weapons, GANGSTER_SHOTGUN )) + { + m_cClipSize = 2; + SetBodygroup( GUN_GROUP, GUN_SHOTGUN ); + } + else if (FBitSet( pev->weapons, GANGSTER_REVOLVER )) + { + m_cClipSize = 6; + SetBodygroup( GUN_GROUP, GUN_REVOLVER ); + } + else + { + m_cClipSize = GANGSTER_CLIP_SIZE; + SetBodygroup( GUN_GROUP, GUN_TOMMYGUN ); + } + m_cAmmoLoaded = m_cClipSize; + + CTalkMonster::g_talkWaitTime = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CGangster :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/gangster.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_pain3.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain4.wav" ); + + PRECACHE_SOUND( "hgrunt/gr_reload1.wav" ); + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + + PRECACHE_SOUND( "weapons/sbarrel1.wav" ); + PRECACHE_SOUND( "weapons/revolver_shot1.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 CGangster :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_GANGSTER_CHECK_FIRE: + if ( !NoFriendlyFire() ) + { + SetConditions( bits_COND_GANGSTER_NOFIRE ); + } + TaskComplete(); + break; + + case TASK_GANGSTER_SPEAK_SENTENCE: + SpeakSentence(); + TaskComplete(); + break; + + case TASK_WALK_PATH: + case TASK_RUN_PATH: + // gangster no longer assumes he is covered if he moves5 + Forget( bits_MEMORY_INCOVER ); + CSquadMonster ::StartTask( pTask ); + break; + + case TASK_RELOAD: + m_IdealActivity = ACT_RELOAD; + break; + + case TASK_GANGSTER_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 CGangster :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_GANGSTER_FACE_TOSS_DIR: + { + // project a point along the toss vector and turn to face that point. + MakeIdealYaw( pev->origin ); + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + default: + { + CSquadMonster :: RunTask( pTask ); + break; + } + } +} + +//========================================================= +// PainSound +//========================================================= +void CGangster :: PainSound ( void ) +{ + if ( gpGlobals->time > m_flNextPainTime ) + { + switch ( RANDOM_LONG(0,1) ) + { + 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; + } + + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CGangster :: 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 +//========================================================= + +//========================================================= +// GangsterFail +//========================================================= +Task_t tlGangsterFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGangsterFail[] = +{ + { + tlGangsterFail, + ARRAYSIZE ( tlGangsterFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + 0, + "Gangster Fail" + }, +}; + +//========================================================= +// Gangster Combat Fail +//========================================================= +Task_t tlGangsterCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slGangsterCombatFail[] = +{ + { + tlGangsterCombatFail, + ARRAYSIZE ( tlGangsterCombatFail ), + bits_COND_CAN_RANGE_ATTACK1, + 0, + "Gangster Combat Fail" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlGangsterVictoryDance[] = +{ + { 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 slGangsterVictoryDance[] = +{ + { + tlGangsterVictoryDance, + ARRAYSIZE ( tlGangsterVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "GangsterVictoryDance" + }, +}; + +//========================================================= +// Establish line of fire - move to a position that allows +// the gangster to attack. +//========================================================= +Task_t tlGangsterEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GANGSTER_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_GANGSTER_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slGangsterEstablishLineOfFire[] = +{ + { + tlGangsterEstablishLineOfFire, + ARRAYSIZE ( tlGangsterEstablishLineOfFire ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2 | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GangsterEstablishLineOfFire" + }, +}; + +//========================================================= +// GangsterFoundEnemy - gangster established sight with an enemy +// that was hiding from the squad. +//========================================================= +Task_t tlGangsterFoundEnemy[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_SIGNAL1 }, +}; + +Schedule_t slGangsterFoundEnemy[] = +{ + { + tlGangsterFoundEnemy, + ARRAYSIZE ( tlGangsterFoundEnemy ), + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GangsterFoundEnemy" + }, +}; + +//========================================================= +// GangsterCombatFace Schedule +//========================================================= +Task_t tlGangsterCombatFace1[] = +{ + { 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_GANGSTER_SWEEP }, +}; + +Schedule_t slGangsterCombatFace[] = +{ + { + tlGangsterCombatFace1, + ARRAYSIZE ( tlGangsterCombatFace1 ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_CAN_RANGE_ATTACK1, + 0, + "Combat Face" + }, +}; + +//========================================================= +// Suppressing fire - don't stop shooting until the clip is +// empty or gangster gets hurt. +//========================================================= +Task_t tlGangsterSignalSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGangsterSignalSuppress[] = +{ + { + tlGangsterSignalSuppress, + ARRAYSIZE ( tlGangsterSignalSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GANGSTER_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "SignalSuppress" + }, +}; + +Task_t tlGangsterSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGangsterSuppress[] = +{ + { + tlGangsterSuppress, + ARRAYSIZE ( tlGangsterSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_GANGSTER_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Suppress" + }, +}; + + +//========================================================= +// gangster wait in cover - we don't allow danger or the ability +// to attack to break a gangster's run to cover schedule, but +// when a gangster is in cover, we do want them to attack if they can. +//========================================================= +Task_t tlGangsterWaitInCover[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slGangsterWaitInCover[] = +{ + { + tlGangsterWaitInCover, + ARRAYSIZE ( tlGangsterWaitInCover ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + + bits_SOUND_DANGER, + "GangsterWaitInCover" + }, +}; + +//========================================================= +// run to cover. +// !!!BUGBUG - set a decent fail schedule here. +//========================================================= +Task_t tlGangsterTakeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_GANGSTER_TAKECOVER_FAILED }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_GANGSTER_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_GANGSTER_WAIT_FACE_ENEMY }, +}; + +Schedule_t slGangsterTakeCover[] = +{ + { + tlGangsterTakeCover1, + ARRAYSIZE ( tlGangsterTakeCover1 ), + 0, + 0, + "TakeCover" + }, +}; + + + +//========================================================= +// hide from the loudest sound source (to run from grenade) +//========================================================= +Task_t tlGangsterTakeCoverFromBestSound[] = +{ + { 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 slGangsterTakeCoverFromBestSound[] = +{ + { + tlGangsterTakeCoverFromBestSound, + ARRAYSIZE ( tlGangsterTakeCoverFromBestSound ), + 0, + 0, + "GangsterTakeCoverFromBestSound" + }, +}; + +//========================================================= +// Gangster reload schedule +//========================================================= +Task_t tlGangsterHideReload[] = +{ + { 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 slGangsterHideReload[] = +{ + { + tlGangsterHideReload, + ARRAYSIZE ( tlGangsterHideReload ), + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "GangsterHideReload" + } +}; + +//========================================================= +// Do a turning sweep of the area +//========================================================= +Task_t tlGangsterSweep[] = +{ + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slGangsterSweep[] = +{ + { + tlGangsterSweep, + ARRAYSIZE ( tlGangsterSweep ), + + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_HEAR_SOUND, + + bits_SOUND_WORLD |// sound flags + bits_SOUND_DANGER | + bits_SOUND_PLAYER, + + "Gangster Sweep" + }, +}; + +//========================================================= +// primary range attack. +//========================================================= +Task_t tlGangsterRangeAttack1A[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGangsterRangeAttack1A[] = +{ + { + tlGangsterRangeAttack1A, + ARRAYSIZE ( tlGangsterRangeAttack1A ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND | + bits_COND_GANGSTER_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Range Attack1A" + }, +}; + + +//========================================================= +// primary range attack. +//========================================================= +Task_t tlGangsterRangeAttack1B[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_IDLE }, +// { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_IDLE_ANGRY }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_GANGSTER_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGangsterRangeAttack1B[] = +{ + { + tlGangsterRangeAttack1B, + ARRAYSIZE ( tlGangsterRangeAttack1B ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_GANGSTER_NOFIRE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack1B" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CGangster ) +{ + slGangsterFail, + slGangsterCombatFail, + slGangsterVictoryDance, + slGangsterEstablishLineOfFire, + slGangsterFoundEnemy, + slGangsterCombatFace, + slGangsterSignalSuppress, + slGangsterSuppress, + slGangsterWaitInCover, + slGangsterTakeCover, + slGangsterTakeCoverFromBestSound, + slGangsterHideReload, + slGangsterSweep, + slGangsterRangeAttack1A, + slGangsterRangeAttack1B, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CGangster, CSquadMonster ); + +//========================================================= +// SetActivity +//========================================================= +void CGangster :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_RANGE_ATTACK1: + // gangster is either shooting standing or shooting crouched + if (FBitSet( pev->weapons, GANGSTER_TOMMYGUN)) + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "ref_shoot_tommy" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouch_shoot_tommy" ); + } + } + else if (FBitSet( pev->weapons, GANGSTER_SHOTGUN)) + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "ref_shoot_shotgun" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouch_shoot_shotgun" ); + } + } + else + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "ref_shoot_onehanded" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouch_shoot_onehanded" ); + } + } + break; + case ACT_RUN: + if ( pev->health <= GANGSTER_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_RUN_HURT ); + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + { + iSequence = LookupActivity ( ACT_RUN ); + } + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_WALK: + if ( pev->health <= GANGSTER_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_WALK_HURT ); + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + { + iSequence = LookupActivity ( ACT_WALK ); + } + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_IDLE: + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { + NewActivity = ACT_IDLE; +// NewActivity = ACT_IDLE_ANGRY; + } + iSequence = LookupActivity ( NewActivity ); + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + { + iSequence = LookupActivity ( ACT_IDLE ); + } + 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 *CGangster :: GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = GANGSTER_SENT_NONE; + + // gangsters 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 gangster's signal that a grenade has landed nearby, + // and the gangster 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), "GA_GREN", GANGSTER_SENTENCE_VOLUME, GANGSTER_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 gangsters 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), "GA_ALERT", GANGSTER_SENTENCE_VOLUME, GANGSTER_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), "GA_MONST", GANGSTER_SENTENCE_VOLUME, GANGSTER_ATTN, 0, m_voicePitch); + + JustSpoke(); + } + + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_GANGSTER_SUPPRESS ); + } + else + { + return GetScheduleOfType ( SCHED_GANGSTER_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_GANGSTER_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 gangster was hit and is going to run to cover. + if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "HG_COVER", GANGSTER_SENTENCE_VOLUME, GANGSTER_ATTN, 0, m_voicePitch); + m_iSentence = GANGSTER_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 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_GANGSTER_FOUND_ENEMY ); + } + } + + if ( OccupySlot ( bits_SLOTS_GANGSTER_ENGAGE ) ) + { + // try to take an available ENGAGE slot + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + else + { + // hide! + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } +// can't see enemy + else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if ( OccupySlot( bits_SLOTS_GANGSTER_ENGAGE ) ) + { + //!!!KELLY - gangster 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", GANGSTER_SENTENCE_VOLUME, GANGSTER_ATTN, 0, m_voicePitch); + m_iSentence = GANGSTER_SENT_CHARGE; + //JustSpoke(); + } + + return GetScheduleOfType( SCHED_GANGSTER_ESTABLISH_LINE_OF_FIRE ); + } + else + { + //!!!KELLY - gangster is going to stay put for a couple seconds to see if + // the enemy wanders back out into the open, or approaches the + // gangster's covered position. Good place for a taunt, I guess? + if (FOkToSpeak() && RANDOM_LONG(0,1)) + { + SENTENCEG_PlayRndSz( ENT(pev), "GA_TAUNT", GANGSTER_SENTENCE_VOLUME, GANGSTER_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_GANGSTER_ESTABLISH_LINE_OF_FIRE ); + } + } + } + + // no special cases here, call the base class + return CSquadMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CGangster :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + { + return &slGangsterTakeCover[ 0 ]; + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slGangsterTakeCoverFromBestSound[ 0 ]; + } + case SCHED_GANGSTER_TAKECOVER_FAILED: + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot( bits_SLOTS_GANGSTER_ENGAGE ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType ( SCHED_FAIL ); + } + break; + case SCHED_GANGSTER_ELOF_FAIL: + { + // human gangster is unable to move to a position that allows him to attack the enemy. + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + case SCHED_GANGSTER_ESTABLISH_LINE_OF_FIRE: + { + return &slGangsterEstablishLineOfFire[ 0 ]; + } + break; + case SCHED_RANGE_ATTACK1: + { + // randomly stand or crouch + if (RANDOM_LONG(0,99) == 0) + m_fStanding = 0; + else + m_fStanding = 1; + + if (m_fStanding) + return &slGangsterRangeAttack1B[ 0 ]; + else + return &slGangsterRangeAttack1A[ 0 ]; + } + case SCHED_COMBAT_FACE: + { + return &slGangsterCombatFace[ 0 ]; + } + case SCHED_GANGSTER_WAIT_FACE_ENEMY: + { + return &slGangsterWaitInCover[ 0 ]; + } + case SCHED_GANGSTER_SWEEP: + { + return &slGangsterSweep[ 0 ]; + } + case SCHED_GANGSTER_COVER_AND_RELOAD: + { + return &slGangsterHideReload[ 0 ]; + } + case SCHED_GANGSTER_FOUND_ENEMY: + { + return &slGangsterFoundEnemy[ 0 ]; + } + case SCHED_VICTORY_DANCE: + { + if ( InSquad() ) + { + if ( !IsLeader() ) + { + return &slGangsterFail[ 0 ]; + } + } + + return &slGangsterVictoryDance[ 0 ]; + } + case SCHED_GANGSTER_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 &slGangsterSignalSuppress[ 0 ]; + } + else + { + return &slGangsterSuppress[ 0 ]; + } + } + case SCHED_FAIL: + { + if ( m_hEnemy != NULL ) + { + // gangster has an enemy, so pick a different default fail schedule most likely to help recover. + return &slGangsterCombatFail[ 0 ]; + } + + return &slGangsterFail[ 0 ]; + } + default: + { + return CSquadMonster :: GetScheduleOfType ( Type ); + } + } +} + + +//========================================================= +// DEAD GANGSTER PROP +//========================================================= + +char *CDeadGangster::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" }; + +void CDeadGangster::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_gangster_dead, CDeadGangster ); + +//========================================================= +// ********** DeadGangster SPAWN ********** +//========================================================= +void CDeadGangster :: Spawn( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/gangster.mdl"); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/gangster.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 gangster with bad pose\n" ); + } + + // Corpses have less health + pev->health = 8; + + // map old bodies onto new bodies + switch( pev->body ) + { + case 0: // Gangster with Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_SLIME ); + SetBodygroup( GUN_GROUP, GUN_TOMMYGUN ); + break; + case 1: // Commander with Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_PSYCHO ); + SetBodygroup( GUN_GROUP, GUN_TOMMYGUN ); + break; + case 2: // Gangster no Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_SLIME ); + SetBodygroup( GUN_GROUP, GUN_NONE ); + break; + case 3: // Commander no Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_PSYCHO ); + SetBodygroup( GUN_GROUP, GUN_NONE ); + break; + } + + MonsterInitDead(); +} diff --git a/dlls/cthulhu/Gangster.h b/dlls/cthulhu/Gangster.h new file mode 100755 index 00000000..20b2c3b9 --- /dev/null +++ b/dlls/cthulhu/Gangster.h @@ -0,0 +1,88 @@ + +#ifndef GANGSTER_H +#define GANGSTER_H + +class CGangster : 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 ); + virtual 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 Revolver ( 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[]; + + float m_flNextPainTime; + float m_flLastEnemySightTime; + + 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 *pGangsterSentences[]; +}; + + +///////////////////////////////////////////////////////////////////////////////// + + +class CDeadGangster : 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]; +}; + + + +#endif + + + diff --git a/dlls/cthulhu/Ghoul.cpp b/dlls/cthulhu/Ghoul.cpp new file mode 100755 index 00000000..caa79228 --- /dev/null +++ b/dlls/cthulhu/Ghoul.cpp @@ -0,0 +1,330 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Ghoul +//========================================================= + +// 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 GHOUL_AE_ATTACK_RIGHT 0x01 +#define GHOUL_AE_ATTACK_LEFT 0x02 +#define GHOUL_AE_ATTACK_BOTH 0x03 + +#define GHOUL_FLINCH_DELAY 2 // at most one flinch every n secs + + +#include "Ghoul.h" + + +LINK_ENTITY_TO_CLASS( monster_ghoul, CGhoul ); + +const char *CGhoul::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CGhoul::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CGhoul::pAttackSounds[] = +{ + "ghoul/gh_attack1.wav", +// "ghoul/gh_attack2.wav", +}; + +const char *CGhoul::pIdleSounds[] = +{ + "ghoul/gh_idle1.wav", + "ghoul/gh_idle2.wav", + "ghoul/gh_idle3.wav", +// "ghoul/gh_idle4.wav", +}; + +const char *CGhoul::pAlertSounds[] = +{ + "ghoul/gh_alert1.wav", +// "ghoul/gh_alert20.wav", +// "ghoul/gh_alert30.wav", +}; + +const char *CGhoul::pPainSounds[] = +{ + "ghoul/gh_pain1.wav", +// "ghoul/gh_pain2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGhoul :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CGhoul :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + pev->yaw_speed = ys; +} + +int CGhoul :: 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 CGhoul :: PainSound( void ) +{ + //int pitch = 95 + RANDOM_LONG(0,9); + int pitch = 125 + 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 CGhoul :: AlertSound( void ) +{ + //int pitch = 95 + RANDOM_LONG(0,9); + int pitch = 125 + 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 CGhoul :: IdleSound( void ) +{ + //int pitch = 95 + RANDOM_LONG(0,9); + int pitch = 125 + RANDOM_LONG(0,9); + + // Play a random idle sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pIdleSounds[ RANDOM_LONG(0,ARRAYSIZE(pIdleSounds)-1) ], 0.2, ATTN_NORM, 0, pitch ); +} + +void CGhoul :: AttackSound( void ) +{ + int pitch = 125 + RANDOM_LONG(0,9); + + // Play a random attack sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 0.5, ATTN_NORM, 0, pitch ); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CGhoul :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case GHOUL_AE_ATTACK_RIGHT: + { + // do stuff for this event. + // ALERT( at_console, "Slash right!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.ghoulDmgOneSlash, 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 GHOUL_AE_ATTACK_LEFT: + { + // do stuff for this event. + // ALERT( at_console, "Slash left!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.ghoulDmgOneSlash, 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 GHOUL_AE_ATTACK_BOTH: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.ghoulDmgBothSlash, 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 CGhoul :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/ghoul.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; + if (pev->health == 0) + pev->health = gSkillData.ghoulHealth; + 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 CGhoul :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/ghoul.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 CGhoul::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { +#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 + GHOUL_FLINCH_DELAY; + } + + return iIgnore; + +} \ No newline at end of file diff --git a/dlls/cthulhu/Ghoul.h b/dlls/cthulhu/Ghoul.h new file mode 100755 index 00000000..d311cbc2 --- /dev/null +++ b/dlls/cthulhu/Ghoul.h @@ -0,0 +1,39 @@ + +#ifndef GHOUL_H +#define GHOUL_H + +class CGhoul : 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 ); +}; + + + +#endif + + diff --git a/dlls/cthulhu/GreatRace.cpp b/dlls/cthulhu/GreatRace.cpp new file mode 100755 index 00000000..fc1d7a1f --- /dev/null +++ b/dlls/cthulhu/GreatRace.cpp @@ -0,0 +1,780 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Great Race of Yith 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 GREAT_RACE_AE_CLAW ( 1 ) +#define GREAT_RACE_AE_CLAWRAKE ( 2 ) +#define GREAT_RACE_AE_ZAP_POWERUP ( 3 ) +#define GREAT_RACE_AE_ZAP_SHOOT ( 4 ) +#define GREAT_RACE_AE_ZAP_DONE ( 5 ) +#define GREAT_RACE_DROP_GUN ( 6 ) + +#define GREAT_RACE_MAX_BEAMS 8 + +#define GREAT_RACE_LIGHTNING_GUN ( 1 << 0) +#define GREAT_RACE_NONE ( 1 << 1) + +#define GUN_GROUP 1 +#define GUN_LIGHTNING 0 +#define GUN_NONE 1 + +#include "GreatRace.h" + + +LINK_ENTITY_TO_CLASS( monster_greatrace, CGreatRace ); + + +TYPEDESCRIPTION CGreatRace::m_SaveData[] = +{ + DEFINE_ARRAY( CGreatRace, m_pBeam, FIELD_CLASSPTR, GREAT_RACE_MAX_BEAMS ), + DEFINE_FIELD( CGreatRace, m_iBeams, FIELD_INTEGER ), + DEFINE_FIELD( CGreatRace, m_flNextAttack, FIELD_TIME ), + + DEFINE_FIELD( CGreatRace, m_voicePitch, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CGreatRace, CSquadMonster ); + + + + +const char *CGreatRace::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CGreatRace::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CGreatRace::pPainSounds[] = +{ + "greatrace/gr_pain1.wav", + "greatrace/gr_pain2.wav", +}; + +const char *CGreatRace::pDeathSounds[] = +{ + "greatrace/gr_die1.wav", + "greatrace/gr_die2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGreatRace :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MILITARY; +} + + +int CGreatRace::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 CGreatRace :: 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 CGreatRace :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + SENTENCEG_PlayRndSz(ENT(pev), "GRACE_ALERT", 0.85, ATTN_NORM, 0, m_voicePitch); + + CallForHelp( "monster_greatrace", 512, m_hEnemy, m_vecEnemyLKP ); + CallForHelp( "monster_yodan", 512, m_hEnemy, m_vecEnemyLKP ); + } +} + +//========================================================= +// IdleSound +//========================================================= +void CGreatRace :: IdleSound( void ) +{ + //if (RANDOM_LONG( 0, 1 ) == 0) + //{ + SENTENCEG_PlayRndSz(ENT(pev), "GRACE_IDLE", 0.85, ATTN_NORM, 0, m_voicePitch); + //} + +} + +//========================================================= +// PainSound +//========================================================= +void CGreatRace :: PainSound( void ) +{ + if (RANDOM_LONG( 0, 1 ) == 0) + { + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } +} + +//========================================================= +// DieSound +//========================================================= + +void CGreatRace :: 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 CGreatRace :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + + +void CGreatRace::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 CGreatRace :: 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 CGreatRace :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + // ALERT( at_console, "event %d : %f\n", pEvent->event, pev->frame ); + switch( pEvent->event ) + { + case GREAT_RACE_AE_CLAW: + { + // SOUND HERE! + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.greatraceDmgClaw, 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 GREAT_RACE_AE_CLAWRAKE: + { + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.greatraceDmgClawrake, 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 GREAT_RACE_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( ); + + } + //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 GREAT_RACE_AE_ZAP_SHOOT: + { + ClearBeams( ); + + 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( 2.0, 5.0 ); + } + break; + + case GREAT_RACE_AE_ZAP_DONE: + { + ClearBeams( ); + } + break; + + case GREAT_RACE_DROP_GUN: + { + if (GetBodygroup(GUN_GROUP) != GUN_LIGHTNING) break; + if (pev->spawnflags & SF_MONSTER_NO_WPN_DROP) break; + + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( GUN_GROUP, GUN_NONE ); + + // now spawn a gun. + DropItem( "weapon_lightninggun", vecGunPos, vecGunAngles ); + + } + break; + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// CheckRangeAttack1 - normal beam attack +//========================================================= +BOOL CGreatRace :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if (GetBodygroup(GUN_GROUP) == GUN_NONE) + return FALSE; + + if (m_flNextAttack > gpGlobals->time) + { + return FALSE; + } + + return CSquadMonster::CheckRangeAttack1( flDot, flDist ); +} + +//========================================================= +// CheckRangeAttack2 - check bravery and try to resurect dead comrades +//========================================================= +BOOL CGreatRace :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; +} + + +//========================================================= +// StartTask +//========================================================= +void CGreatRace :: StartTask ( Task_t *pTask ) +{ + ClearBeams( ); + + CSquadMonster :: StartTask ( pTask ); +} + + +//========================================================= +// Spawn +//========================================================= +void CGreatRace :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/greatrace.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; + if (pev->health == 0) + pev->health = gSkillData.greatraceHealth; + 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 ); + + if (FBitSet( pev->weapons, GREAT_RACE_LIGHTNING_GUN )) + { + SetBodygroup( GUN_GROUP, GUN_LIGHTNING ); + } + else // none + { + SetBodygroup( GUN_GROUP, GUN_NONE ); + } + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CGreatRace :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/greatrace.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 CGreatRace :: 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); +} + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CGreatRace :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if (( GetBodygroup( GUN_GROUP ) != GUN_NONE ) && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) + {// throw a gun if the yodan has one + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun; + pGun = DropItem( "weapon_lightninggun", 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 ); + } + } + + CSquadMonster :: GibMonster(); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + + +// primary range attack +Task_t tlGreatRaceAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slGreatRaceAttack1[] = +{ + { + tlGreatRaceAttack1, + ARRAYSIZE ( tlGreatRaceAttack1 ), + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND | + bits_COND_HEAVY_DAMAGE, + + bits_SOUND_DANGER, + "GreatRace Range Attack1" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CGreatRace ) +{ + slGreatRaceAttack1, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CGreatRace, CSquadMonster ); + + +//========================================================= +//========================================================= +Schedule_t *CGreatRace :: 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) + { + 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 *CGreatRace :: 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 slGreatRaceAttack1; + case SCHED_RANGE_ATTACK2: + return slGreatRaceAttack1; + } + return CSquadMonster :: GetScheduleOfType( Type ); +} + + +//========================================================= +// ArmBeam - small beam from arm to nearby geometry +//========================================================= + +void CGreatRace :: ArmBeam( int side ) +{ + TraceResult tr; + float flDist = 1.0; + + if (m_iBeams >= GREAT_RACE_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 CGreatRace :: 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 CGreatRace :: WackBeam( int side, CBaseEntity *pEntity ) +{ + Vector vecDest; + float flDist = 1.0; + + if (m_iBeams >= GREAT_RACE_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 CGreatRace :: ZapBeam( int side ) +{ + Vector vecSrc, vecAim; + TraceResult tr; + CBaseEntity *pEntity; + + if (m_iBeams >= GREAT_RACE_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.greatraceDmgZap, 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 CGreatRace :: ClearBeams( ) +{ + for (int i = 0; i < GREAT_RACE_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/cthulhu/GreatRace.h b/dlls/cthulhu/GreatRace.h new file mode 100755 index 00000000..9476f613 --- /dev/null +++ b/dlls/cthulhu/GreatRace.h @@ -0,0 +1,59 @@ + +#ifndef GREAT_RACE_H +#define GREAT_RACE_H + +class CGreatRace : 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 ); + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + void GibMonster( void ); + + 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 ); + + CBeam *m_pBeam[GREAT_RACE_MAX_BEAMS]; + + int m_iBeams; + float m_flNextAttack; + + int m_voicePitch; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + static const char *pPainSounds[]; + static const char *pDeathSounds[]; +}; + + + + +#endif diff --git a/dlls/cthulhu/HuntingHorror.cpp b/dlls/cthulhu/HuntingHorror.cpp new file mode 100755 index 00000000..694b308d --- /dev/null +++ b/dlls/cthulhu/HuntingHorror.cpp @@ -0,0 +1,986 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 ) + +//========================================================= +// hunting_horror +//========================================================= + +#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 HUNTING_HORROR_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 +//========================================================= + + +#include "HuntingHorror.h" + + +LINK_ENTITY_TO_CLASS( monster_huntinghorror, CHuntingHorror ); + +TYPEDESCRIPTION CHuntingHorror::m_SaveData[] = +{ + DEFINE_FIELD( CHuntingHorror, m_SaveVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CHuntingHorror, m_idealDist, FIELD_FLOAT ), + DEFINE_FIELD( CHuntingHorror, m_flEnemyTouched, FIELD_FLOAT ), + DEFINE_FIELD( CHuntingHorror, m_bOnAttack, FIELD_BOOLEAN ), + DEFINE_FIELD( CHuntingHorror, m_flMaxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CHuntingHorror, m_flMinSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CHuntingHorror, m_flMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( CHuntingHorror, m_flNextAlert, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CHuntingHorror, CFlyingMonster ); + + +const char *CHuntingHorror::pIdleSounds[] = +{ + "hhorror/hh_idle1.wav", + "hhorror/hh_idle2.wav", + "hhorror/hh_idle3.wav", +}; + +const char *CHuntingHorror::pAlertSounds[] = +{ + "hhorror/hh_alert1.wav", + "hhorror/hh_alert2.wav", + "hhorror/hh_alert3.wav", +}; + +const char *CHuntingHorror::pAttackSounds[] = +{ + "hhorror/hh_attack1.wav", + "hhorror/hh_attack2.wav", + "hhorror/hh_attack3.wav", +}; + +const char *CHuntingHorror::pBiteSounds[] = +{ + "ichy/ichy_bite1.wav", + "ichy/ichy_bite2.wav", +}; + +const char *CHuntingHorror::pPainSounds[] = +{ + "hhorror/hh_pain1.wav", + "hhorror/hh_pain2.wav", + "hhorror/hh_pain3.wav", +}; + +const char *CHuntingHorror::pDieSounds[] = +{ + "hhorror/hh_die1.wav", + "hhorror/hh_die2.wav", + "hhorror/hh_die3.wav", +}; + +#define EMIT_HUNT_HORR_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 CHuntingHorror :: IdleSound( void ) +{ + EMIT_HUNT_HORR_SOUND( CHAN_VOICE, pIdleSounds ); +} + +void CHuntingHorror :: AlertSound( void ) +{ + EMIT_HUNT_HORR_SOUND( CHAN_VOICE, pAlertSounds ); +} + +void CHuntingHorror :: AttackSound( void ) +{ + EMIT_HUNT_HORR_SOUND( CHAN_VOICE, pAttackSounds ); +} + +void CHuntingHorror :: BiteSound( void ) +{ + EMIT_HUNT_HORR_SOUND( CHAN_WEAPON, pBiteSounds ); +} + +void CHuntingHorror :: DeathSound( void ) +{ + EMIT_HUNT_HORR_SOUND( CHAN_VOICE, pDieSounds ); +} + +void CHuntingHorror :: PainSound( void ) +{ + EMIT_HUNT_HORR_SOUND( CHAN_VOICE, pPainSounds ); +} + +//========================================================= +// monster-specific tasks and states +//========================================================= +enum +{ + TASK_HUNTING_HORROR_CIRCLE_ENEMY = LAST_COMMON_TASK + 1, + TASK_HUNTING_HORROR_FLY, +}; + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +static Task_t tlFlyAround[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_WALK }, + { TASK_HUNTING_HORROR_FLY, 0.0 }, +}; + +static Schedule_t slFlyAround[] = +{ + { + tlFlyAround, + ARRAYSIZE(tlFlyAround), + 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, + "FlyAround" + }, +}; + +static Task_t tlFlyAgitated[] = +{ + { TASK_STOP_MOVING, (float) 0 }, + { TASK_SET_ACTIVITY, (float)ACT_RUN }, + { TASK_WAIT, (float)2.0 }, +}; + +static Schedule_t slFlyAgitated[] = +{ + { + tlFlyAgitated, + ARRAYSIZE(tlFlyAgitated), + 0, + 0, + "FlyAgitated" + }, +}; + + +static Task_t tlCircleEnemy[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_WALK }, + { TASK_HUNTING_HORROR_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 tlHuntHorrTwitchDie[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SOUND_DIE, (float)0 }, + { TASK_DIE, (float)0 }, +}; + +Schedule_t slHuntHorrTwitchDie[] = +{ + { + tlHuntHorrTwitchDie, + ARRAYSIZE( tlHuntHorrTwitchDie ), + 0, + 0, + "Die" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES(CHuntingHorror) +{ + slFlyAround, + slFlyAgitated, + slCircleEnemy, + slHuntHorrTwitchDie, +}; +IMPLEMENT_CUSTOM_SCHEDULES(CHuntingHorror, CFlyingMonster); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHuntingHorror :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CHuntingHorror :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( flDot >= 0.7 && m_flEnemyTouched > gpGlobals->time - 0.2 && flDist <= 64.0 ) + { + return TRUE; + } + return FALSE; +} + +void CHuntingHorror::BiteTouch( CBaseEntity *pOther ) +{ + // bite if we hit who we want to eat + if ( pOther == m_hEnemy ) + { + m_flEnemyTouched = gpGlobals->time; + m_bOnAttack = TRUE; + } +} + +void CHuntingHorror::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 - Fly in for a chomp +// +//========================================================= +BOOL CHuntingHorror :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDot > -0.7 && (m_bOnAttack || ( flDist <= 384 && m_idealDist <= 384))) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CHuntingHorror :: SetYawSpeed ( void ) +{ + pev->yaw_speed = 100; +} + + + +//========================================================= +// Killed - overrides CFlyingMonster. +// +void CHuntingHorror :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->velocity = Vector(0,0,-100); + pev->gravity = 1.0; + pev->angles.x = 0; + + CFlyingMonster::Killed( pevAttacker, iGib ); + //CBaseMonster::Killed( pevAttacker, iGib ); +} + +void CHuntingHorror::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 HUNTING_HORROR_AE_SHAKE_RIGHT 1 +#define HUNTING_HORROR_AE_SHAKE_LEFT 2 + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CHuntingHorror :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case HUNTING_HORROR_AE_SHAKE_RIGHT: + case HUNTING_HORROR_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.huntinghorrorDmgBite, DMG_SLASH ); + } + } + BiteSound(); + } + break; + default: + CFlyingMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHuntingHorror :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/huntinghorror.mdl"); + UTIL_SetSize( pev, Vector( -15, -15, 5 ), Vector( 15, 15, 17 ) ); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_FLY; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = gSkillData.huntinghorrorHealth; + pev->view_ofs = Vector ( 0, 0, 16 ); + m_flFieldOfView = VIEW_FIELD_WIDE; + m_MonsterState = MONSTERSTATE_NONE; + SetBits(pev->flags, FL_FLY); + SetFlyingSpeed( HUNTING_HORROR_SPEED ); + SetFlyingMomentum( 2.5 ); // Set momentum constant + + m_afCapability = bits_CAP_RANGE_ATTACK1 | bits_CAP_FLY; + + 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 CHuntingHorror :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/huntinghorror.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* CHuntingHorror::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* CHuntingHorror :: GetScheduleOfType ( int Type ) +{ + // ALERT( at_console, "GetScheduleOfType( %d ) %d\n", Type, m_bOnAttack ); + switch ( Type ) + { + case SCHED_IDLE_WALK: + return slFlyAround; + case SCHED_STANDOFF: + return slCircleEnemy; + case SCHED_FAIL: + return slFlyAgitated; + case SCHED_DIE: + return slHuntHorrTwitchDie; + 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 CHuntingHorror::StartTask(Task_t *pTask) +{ + switch (pTask->iTask) + { + case TASK_HUNTING_HORROR_CIRCLE_ENEMY: + break; + case TASK_HUNTING_HORROR_FLY: + break; + case TASK_SMALL_FLINCH: + if (m_idealDist > 128) + { + m_flMaxDist = 512; + m_idealDist = 512; + } + else + { + m_bOnAttack = TRUE; + } + CFlyingMonster::StartTask(pTask); + break; + + default: + CFlyingMonster::StartTask(pTask); + break; + } +} + +void CHuntingHorror :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_HUNTING_HORROR_CIRCLE_ENEMY: + if (m_hEnemy == NULL) + { + TaskComplete( ); + } + else if (FVisible( m_hEnemy )) + { + Vector vecFrom = m_hEnemy->EyePosition( ); + + Vector vecDelta = (pev->origin - vecFrom).Normalize( ); + Vector vecFly = CrossProduct( vecDelta, Vector( 0, 0, 1 ) ).Normalize( ); + + if (DotProduct( vecFly, m_SaveVelocity ) < 0) + vecFly = vecFly * -1.0; + + Vector vecPos = vecFrom + vecDelta * m_idealDist + vecFly * 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_HUNTING_HORROR_FLY: + if (m_fSequenceFinished) + { + TaskComplete( ); + } + break; + case TASK_DIE: + if ( m_fSequenceFinished ) + { + CFlyingMonster :: RunTask ( pTask ); + + pev->deadflag = DEAD_DEAD; + + TaskComplete( ); + } + break; + + default: + CFlyingMonster :: RunTask ( pTask ); + break; + } +} + + + +float CHuntingHorror::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 CHuntingHorror::Move(float flInterval) +{ + CFlyingMonster::Move( flInterval ); +} + +float CHuntingHorror::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 CHuntingHorror :: 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 CHuntingHorror::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 CHuntingHorror:: GetStoppedActivity( void ) +{ + if ( pev->movetype != MOVETYPE_FLY ) // UNDONE: Ground idle here, IDLE may be something else + return ACT_IDLE; + return ACT_WALK; +} + +void CHuntingHorror::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + m_SaveVelocity = vecDir * m_flightSpeed; +} + + +void CHuntingHorror::MonsterThink ( void ) +{ + CFlyingMonster::MonsterThink( ); + + if (pev->deadflag == DEAD_NO) + { + if (m_MonsterState != MONSTERSTATE_SCRIPT) + { + Fly( ); + } + } +} + +void CHuntingHorror :: Stop( void ) +{ + if (!m_bOnAttack) + m_flightSpeed = 80.0; +} + +void CHuntingHorror::Fly( ) +{ + 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 ); + } + +#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; + + + 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); + +} + + +Vector CHuntingHorror::DoProbe(const Vector &Probe) +{ + Vector WallNormal = Vector(0,0,-1); // AIR normal is Straight Down for flying thing. + 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/cthulhu/HuntingHorror.h b/dlls/cthulhu/HuntingHorror.h new file mode 100755 index 00000000..f46df561 --- /dev/null +++ b/dlls/cthulhu/HuntingHorror.h @@ -0,0 +1,80 @@ + +#ifndef HUNTING_HORROR_H +#define HUNTING_HORROR_H + +class CHuntingHorror : 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 Fly( 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_flEnemyTouched; + BOOL m_bOnAttack; + + float m_flMaxSpeed; + float m_flMinSpeed; + float m_flMaxDist; + + 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 ); +}; + + + + +#endif + + diff --git a/dlls/cthulhu/NightGaunt.cpp b/dlls/cthulhu/NightGaunt.cpp new file mode 100755 index 00000000..0a5b33a0 --- /dev/null +++ b/dlls/cthulhu/NightGaunt.cpp @@ -0,0 +1,980 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 ) + +//========================================================= +// night_gaunt +//========================================================= + +#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 NIGHT_GAUNT_SPEED 200 + +extern CGraph WorldGraph; + + +#define NGAUNT_AE_SLASH 1 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + + +#include "NightGaunt.h" + + +LINK_ENTITY_TO_CLASS( monster_nightgaunt, CNightGaunt ); + +TYPEDESCRIPTION CNightGaunt::m_SaveData[] = +{ + DEFINE_FIELD( CNightGaunt, m_SaveVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CNightGaunt, m_idealDist, FIELD_FLOAT ), + DEFINE_FIELD( CNightGaunt, m_flEnemyTouched, FIELD_FLOAT ), + DEFINE_FIELD( CNightGaunt, m_bOnAttack, FIELD_BOOLEAN ), + DEFINE_FIELD( CNightGaunt, m_flMaxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CNightGaunt, m_flMinSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CNightGaunt, m_flMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( CNightGaunt, m_flNextAlert, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CNightGaunt, CFlyingMonster ); + + +/* nightgaunt does not make idle sounds +const char *CNightGaunt::pIdleSounds[] = +{ + "nightgaunt/nightgaunt_idle1.wav", + "nightgaunt/nightgaunt_idle2.wav", + "nightgaunt/nightgaunt_idle3.wav", + "nightgaunt/nightgaunt_idle4.wav", +}; +*/ + +const char *CNightGaunt::pAlertSounds[] = +{ + "nightgaunt/ng_alert1.wav", +}; + +const char *CNightGaunt::pAttackSounds[] = +{ + "nightgaunt/ng_attack1.wav", +}; + +const char *CNightGaunt::pSlashSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CNightGaunt::pPainSounds[] = +{ + "nightgaunt/ng_pain1.wav", + "nightgaunt/ng_pain2.wav", + "nightgaunt/ng_pain3.wav", +}; + +const char *CNightGaunt::pDieSounds[] = +{ + "nightgaunt/ng_die1.wav", + "nightgaunt/ng_die2.wav", +}; + +#define EMIT_NIGHT_GAUNT_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 CNightGaunt :: IdleSound( void ) +{ +// EMIT_NIGHT_GAUNT_SOUND( CHAN_VOICE, pIdleSounds ); +} + +void CNightGaunt :: AlertSound( void ) +{ + EMIT_NIGHT_GAUNT_SOUND( CHAN_VOICE, pAlertSounds ); +} + +void CNightGaunt :: AttackSound( void ) +{ + EMIT_NIGHT_GAUNT_SOUND( CHAN_VOICE, pAttackSounds ); +} + +void CNightGaunt :: SlashSound( void ) +{ + EMIT_NIGHT_GAUNT_SOUND( CHAN_WEAPON, pSlashSounds ); +} + +void CNightGaunt :: DeathSound( void ) +{ + EMIT_NIGHT_GAUNT_SOUND( CHAN_VOICE, pDieSounds ); +} + +void CNightGaunt :: PainSound( void ) +{ + EMIT_NIGHT_GAUNT_SOUND( CHAN_VOICE, pPainSounds ); +} + +//========================================================= +// monster-specific tasks and states +//========================================================= +enum +{ + TASK_NIGHT_GAUNT_CIRCLE_ENEMY = LAST_COMMON_TASK + 1, + TASK_NIGHT_GAUNT_FLY, +}; + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +static Task_t tlFlyAround[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_WALK }, + { TASK_NIGHT_GAUNT_FLY, 0.0 }, +}; + +static Schedule_t slFlyAround[] = +{ + { + tlFlyAround, + ARRAYSIZE(tlFlyAround), + 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, + "FlyAround" + }, +}; + +static Task_t tlFlyAgitated[] = +{ + { TASK_STOP_MOVING, (float) 0 }, + { TASK_SET_ACTIVITY, (float)ACT_RUN }, + { TASK_WAIT, (float)2.0 }, +}; + +static Schedule_t slFlyAgitated[] = +{ + { + tlFlyAgitated, + ARRAYSIZE(tlFlyAgitated), + 0, + 0, + "FlyAgitated" + }, +}; + + +static Task_t tlCircleEnemy[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_WALK }, + { TASK_NIGHT_GAUNT_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 tlNightGauntTwitchDie[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SOUND_DIE, (float)0 }, + { TASK_DIE, (float)0 }, +}; + +Schedule_t slNightGauntTwitchDie[] = +{ + { + tlNightGauntTwitchDie, + ARRAYSIZE( tlNightGauntTwitchDie ), + 0, + 0, + "Die" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES(CNightGaunt) +{ + slFlyAround, + slFlyAgitated, + slCircleEnemy, + slNightGauntTwitchDie, +}; +IMPLEMENT_CUSTOM_SCHEDULES(CNightGaunt, CFlyingMonster); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CNightGaunt :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + + +Vector CNightGaunt :: Center ( void ) +{ + return Vector( pev->origin.x, pev->origin.y, pev->origin.z + 64 ); +} + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CNightGaunt :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( flDot >= 0.7 && m_flEnemyTouched > gpGlobals->time - 0.2 ) + { + return TRUE; + } + return FALSE; +} + +void CNightGaunt::SlashTouch( CBaseEntity *pOther ) +{ + // slash if we hit who we want to eat + if ( pOther == m_hEnemy ) + { + m_flEnemyTouched = gpGlobals->time; + m_bOnAttack = TRUE; + } +} + +void CNightGaunt::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 - Fly in for a chomp +// +//========================================================= +BOOL CNightGaunt :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDot > -0.7 && (m_bOnAttack || ( flDist <= 384 && m_idealDist <= 384))) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CNightGaunt :: SetYawSpeed ( void ) +{ + pev->yaw_speed = 100; +} + + + +//========================================================= +// Killed - overrides CFlyingMonster. +// +void CNightGaunt :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->velocity = Vector(0,0,-100); + pev->gravity = 1.0; + pev->angles.x = 0; + + //CBaseMonster::Killed( pevAttacker, iGib ); + CFlyingMonster::Killed( pevAttacker, iGib ); +} + +void CNightGaunt::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. +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CNightGaunt :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case NGAUNT_AE_SLASH: + { + 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 * 100; +// 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.nightgauntDmgSlash, DMG_SLASH ); + } + } + SlashSound(); + } + break; + default: + CFlyingMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CNightGaunt :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/monsters/ngaunt.mdl"); + UTIL_SetSize( pev, Vector( -16, -16, 0 ), Vector( 16, 16, 72 ) ); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_FLY; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = gSkillData.nightgauntHealth; + pev->view_ofs = Vector ( 0, 0, 64 ); + m_flFieldOfView = VIEW_FIELD_WIDE; + m_MonsterState = MONSTERSTATE_NONE; + SetBits(pev->flags, FL_FLY); + SetFlyingSpeed( NIGHT_GAUNT_SPEED ); + SetFlyingMomentum( 2.5 ); // Set momentum constant + + m_afCapability = bits_CAP_RANGE_ATTACK1 | bits_CAP_FLY; + + MonsterInit(); + + SetTouch( SlashTouch ); + 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 CNightGaunt :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/monsters/ngaunt.mdl"); + +// PRECACHE_SOUND_ARRAY( pIdleSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pSlashSounds ); + PRECACHE_SOUND_ARRAY( pDieSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); +} + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t* CNightGaunt::GetSchedule() +{ + // ALERT( at_console, "GetSchedule( )\n" ); + switch(m_MonsterState) + { + case MONSTERSTATE_IDLE: + m_flightSpeed = NIGHT_GAUNT_SPEED / 2; + return GetScheduleOfType( SCHED_IDLE_WALK ); + + case MONSTERSTATE_ALERT: + m_flightSpeed = NIGHT_GAUNT_SPEED - 50; + return GetScheduleOfType( SCHED_IDLE_WALK ); + + case MONSTERSTATE_COMBAT: + m_flMaxSpeed = NIGHT_GAUNT_SPEED + 50; + // 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* CNightGaunt :: GetScheduleOfType ( int Type ) +{ + // ALERT( at_console, "GetScheduleOfType( %d ) %d\n", Type, m_bOnAttack ); + switch ( Type ) + { + case SCHED_IDLE_WALK: + return slFlyAround; + case SCHED_STANDOFF: + return slCircleEnemy; + case SCHED_FAIL: + return slFlyAgitated; + case SCHED_DIE: + return slNightGauntTwitchDie; + 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 CNightGaunt::StartTask(Task_t *pTask) +{ + switch (pTask->iTask) + { + case TASK_NIGHT_GAUNT_CIRCLE_ENEMY: + break; + case TASK_NIGHT_GAUNT_FLY: + break; + case TASK_SMALL_FLINCH: + if (m_idealDist > 128) + { + m_flMaxDist = 512; + m_idealDist = 512; + } + else + { + m_bOnAttack = TRUE; + } + CFlyingMonster::StartTask(pTask); + break; + + default: + CFlyingMonster::StartTask(pTask); + break; + } +} + +void CNightGaunt :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_NIGHT_GAUNT_CIRCLE_ENEMY: + if (m_hEnemy == NULL) + { + TaskComplete( ); + } + else if (FVisible( m_hEnemy )) + { + Vector vecFrom = m_hEnemy->EyePosition( ); + + Vector vecDelta = (pev->origin - vecFrom).Normalize( ); + Vector vecFly = CrossProduct( vecDelta, Vector( 0, 0, 1 ) ).Normalize( ); + + if (DotProduct( vecFly, m_SaveVelocity ) < 0) + vecFly = vecFly * -1.0; + + Vector vecPos = vecFrom + vecDelta * m_idealDist + vecFly * 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_NIGHT_GAUNT_FLY: + if (m_fSequenceFinished) + { + TaskComplete( ); + } + break; + case TASK_DIE: + if ( m_fSequenceFinished ) + { + CFlyingMonster :: RunTask ( pTask ); + + pev->deadflag = DEAD_DEAD; + + TaskComplete( ); + } + break; + + default: + CFlyingMonster :: RunTask ( pTask ); + break; + } +} + + + +float CNightGaunt::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 CNightGaunt::Move(float flInterval) +{ + CFlyingMonster::Move( flInterval ); +} + +float CNightGaunt::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 CNightGaunt :: 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 CNightGaunt::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 CNightGaunt:: GetStoppedActivity( void ) +{ + if ( pev->movetype != MOVETYPE_FLY ) // UNDONE: Ground idle here, IDLE may be something else + return ACT_IDLE; + return ACT_WALK; +} + +void CNightGaunt::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + m_SaveVelocity = vecDir * m_flightSpeed; +} + + +void CNightGaunt::MonsterThink ( void ) +{ + CFlyingMonster::MonsterThink( ); + + if (pev->deadflag == DEAD_NO) + { + if (m_MonsterState != MONSTERSTATE_SCRIPT) + { + Fly( ); + } + } +} + +void CNightGaunt :: Stop( void ) +{ + if (!m_bOnAttack) + m_flightSpeed = 80.0; +} + +void CNightGaunt::Fly( ) +{ + 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 ); + } + +#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; + + + 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); + +} + + +Vector CNightGaunt::DoProbe(const Vector &Probe) +{ + Vector WallNormal = Vector(0,0,-1); // AIR normal is Straight Down for flying thing. + 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/cthulhu/NightGaunt.h b/dlls/cthulhu/NightGaunt.h new file mode 100755 index 00000000..2d9aa0b4 --- /dev/null +++ b/dlls/cthulhu/NightGaunt.h @@ -0,0 +1,79 @@ + +#ifndef NIGHT_GAUNT_H +#define NIGHT_GAUNT_H + +class CNightGaunt : public CFlyingMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify( void ); + Vector Center( 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 SlashTouch( 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 Fly( 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_flEnemyTouched; + BOOL m_bOnAttack; + + float m_flMaxSpeed; + float m_flMinSpeed; + float m_flMaxDist; + + float m_flNextAlert; + + //static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pAttackSounds[]; + static const char *pSlashSounds[]; + static const char *pDieSounds[]; + static const char *pPainSounds[]; + + void IdleSound( void ); + void AlertSound( void ); + void AttackSound( void ); + void SlashSound( void ); + void DeathSound( void ); + void PainSound( void ); +}; + + +#endif + + diff --git a/dlls/cthulhu/PowderOfIbn.cpp b/dlls/cthulhu/PowderOfIbn.cpp new file mode 100755 index 00000000..bc2b06df --- /dev/null +++ b/dlls/cthulhu/PowderOfIbn.cpp @@ -0,0 +1,196 @@ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + +#include "PowderOfIbn.h" +#include "monster_PowderOfIbn.h" + +LINK_ENTITY_TO_CLASS( weapon_powderofibn, CPowderOfIbn ); + + +void CPowderOfIbn::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_POWDER_IBN; + SET_MODEL(ENT(pev), "models/w_powderofibn.mdl"); + + pev->dmg = 0; + + m_iDefaultAmmo = POWDER_IBN_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + +void CPowderOfIbn::Precache( void ) +{ + PRECACHE_MODEL("models/w_powderofibn.mdl"); + PRECACHE_MODEL("models/v_powderofibn.mdl"); +// PRECACHE_MODEL("models/p_powderofibn.mdl"); +} + +int CPowderOfIbn::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Powder Of Ibn"; + p->iMaxAmmo1 = POWDER_IBN_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 3; + p->iId = m_iId = WEAPON_POWDER_IBN; + p->iWeight = POWDER_IBN_WEIGHT; + p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + + return 1; +} + + +BOOL CPowderOfIbn::Deploy( ) +{ + m_flReleaseThrow = -1; + return DefaultDeploy( "models/v_powderofibn.mdl", "", IBN_DRAW, "powderofibn" ); +} + +BOOL CPowderOfIbn::CanHolster( void ) +{ + // can only holster powder when not lit! + return ( m_flStartThrow == 0 ); +} + +void CPowderOfIbn::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + SendWeaponAnim( IBN_HOLSTER ); + } + else + { + // no more powders! + m_pPlayer->pev->weapons &= ~(1<pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + +int CPowderOfIbn::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +void CPowderOfIbn::PrimaryAttack() +{ + if (!m_flStartThrow && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0) + { + m_flStartThrow = gpGlobals->time; + m_flReleaseThrow = gpGlobals->time; + + SendWeaponAnim( IBN_THROW ); + m_flTimeWeaponIdle = gpGlobals->time + 0.5; + } +} + +void CPowderOfIbn::WeaponIdle( void ) +{ + //if (m_flReleaseThrow == 0) + // m_flReleaseThrow = gpGlobals->time; + + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + if (m_flStartThrow > 0) + { + Vector angThrow = m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle; + + if (angThrow.x < 0) + angThrow.x = -10 + angThrow.x * ((90 - 10) / 90.0); + else + angThrow.x = -10 + angThrow.x * ((90 + 10) / 90.0); + + float flVel = (90 - angThrow.x) * 4; + if (flVel > 500) + flVel = 500; + + UTIL_MakeVectors( angThrow ); + + Vector vecSrc = m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_forward * 16; + + Vector vecThrow = gpGlobals->v_forward * flVel + m_pPlayer->pev->velocity; + + CMonsterPowderOfIbn::ShootContact( m_pPlayer->pev, vecSrc, vecThrow ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_flStartThrow = 0; + m_flNextPrimaryAttack = gpGlobals->time + 0.5; + m_flTimeWeaponIdle = gpGlobals->time + 0.5; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + if ( !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + // just threw last bottle of powder + // set attack times in the future, and weapon idle in the future so we can see the whole throw + // animation, weapon idle will automatically retire the weapon for us. + m_flTimeWeaponIdle = m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->time + 0.5;// ensure that the animation can finish playing + } + return; + } + else if (m_flReleaseThrow > 0) + { + // we've finished the throw, restart. + m_flStartThrow = 0; + m_flReleaseThrow = -1; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + SendWeaponAnim( IBN_DRAW ); + } + else + { + RetireWeapon(); + return; + } + + return; + } + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.75) + { + iAnim = IBN_IDLE; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 );// how long till we do this again. + } + else + { + iAnim = IBN_FIDGET; + m_flTimeWeaponIdle = gpGlobals->time + 75.0 / 30.0; + } + + SendWeaponAnim( iAnim ); + } +} + + + + diff --git a/dlls/cthulhu/PowderOfIbn.h b/dlls/cthulhu/PowderOfIbn.h new file mode 100755 index 00000000..0d86cc06 --- /dev/null +++ b/dlls/cthulhu/PowderOfIbn.h @@ -0,0 +1,59 @@ + +#ifndef POWDER_IBN_H +#define POWDER_IBN_H + +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define IBN_PRIMARY_VOLUME 450 + +enum ibn_e { + IBN_IDLE = 0, + IBN_FIDGET, + IBN_THROW, // toss + IBN_HOLSTER, + IBN_DRAW +}; + + + +class CPowderOfIbn : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 4; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + void PrimaryAttack( void ); + BOOL Deploy( void ); + BOOL CanHolster( void ); + void Holster( int skiplocal = 0 ); + void WeaponIdle( void ); + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } +}; + +#endif + + diff --git a/dlls/cthulhu/Priest.cpp b/dlls/cthulhu/Priest.cpp new file mode 100755 index 00000000..46fdc31f --- /dev/null +++ b/dlls/cthulhu/Priest.cpp @@ -0,0 +1,1131 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Priest +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "squadmonster.h" +#include "talkmonster.h" +#include "schedule.h" +#include "animation.h" +#include "effects.h" +#include "weapons.h" +#include "soundent.h" + +extern DLL_GLOBAL int g_iSkillLevel; + +#define PRIEST_SENTENCE_VOLUME (float)0.70 // volume of priest sentences +#define PRIEST_VOL 0.35 // volume of priest sounds +#define PRIEST_ATTN ATTN_NORM // attenutation of priest sentences + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define PRIEST_AE_KNIFE ( 1 ) +#define PRIEST_AE_SUMMON_POWERUP ( 2 ) +#define PRIEST_AE_SUMMON_DO ( 3 ) +#define PRIEST_AE_SUMMON_DONE ( 4 ) +#define PRIEST_AE_DROP_KNIFE ( 5 ) + +#define PRIEST_MAX_BEAMS 8 +#define PRIEST_LIMP_HEALTH 20 + +#define GUN_GROUP 1 +#define GUN_KNIFE 0 +#define GUN_NONE 1 + +#include "Priest.h" + + +LINK_ENTITY_TO_CLASS( monster_priest, CPriest ); + + +TYPEDESCRIPTION CPriest::m_SaveData[] = +{ + DEFINE_ARRAY( CPriest, m_pBeam, FIELD_CLASSPTR, PRIEST_MAX_BEAMS ), + DEFINE_FIELD( CPriest, m_iBeams, FIELD_INTEGER ), + DEFINE_FIELD( CPriest, m_flNextAttack, FIELD_TIME ), + + DEFINE_FIELD( CPriest, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CPriest, m_voicePitch, FIELD_INTEGER ), + DEFINE_FIELD( CPriest, m_iSentence, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CPriest, CSquadMonster ); + + + + +const char *CPriest::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CPriest::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CPriest::pPainSounds[] = +{ + "hgrunt/gr_pain3.wav", + "hgrunt/gr_pain4.wav", +}; + +const char *CPriest::pDeathSounds[] = +{ + "hgrunt/gr_die1.wav", + "hgrunt/gr_die2.wav", + "hgrunt/gr_die3.wav", +}; + +const char *CPriest::pPriestSentences[] = +{ + "PR_GREN", // grenade scared PRIEST + "PR_ALERT", // sees player + "PR_CAST", // casts a spell + "PR_COVER", // running to cover + "PR_CHARGE", // running out to get the enemy + "PR_TAUNT", // say rude things +}; + +enum +{ + PRIEST_SENT_NONE = -1, + PRIEST_SENT_GREN = 0, + PRIEST_SENT_ALERT, + PRIEST_SENT_CAST, + PRIEST_SENT_COVER, + PRIEST_SENT_CHARGE, + PRIEST_SENT_TAUNT, +} PRIEST_SENTENCE_TYPES; + +//========================================================= +// Speak Sentence - say your cued up sentence. +// +// Some priest sentences (take cover and charge) rely on actually +// being able to execute the intended action. It's really lame +// when a priest 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 PRIEST has +// started moving. +//========================================================= +void CPriest :: SpeakSentence( void ) +{ + if ( m_iSentence == PRIEST_SENT_NONE ) + { + // no sentence cued up. + return; + } + + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), pPriestSentences[ m_iSentence ], PRIEST_SENTENCE_VOLUME, PRIEST_ATTN, 0, m_voicePitch); + JustSpoke(); + } +} + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_PRIEST_SPEAK_SENTENCE = LAST_COMMON_TASK + 1, +}; + +//========================================================= +// SetActivity +//========================================================= +void CPriest :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_MELEE_ATTACK1: + + // a short enemy...probably a snake... + if ((m_hEnemy != NULL) && (m_hEnemy->pev->maxs.z < 36)) + { + m_fStanding = 0; + } + else + { + m_fStanding = 1; + } + + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "ref_shoot_crowbar" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouch_shoot_crowbar" ); + } + break; + case ACT_RUN: + if ( pev->health <= PRIEST_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_RUN_HURT ); + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + { + iSequence = LookupActivity ( ACT_RUN ); + } + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_WALK: + if ( pev->health <= PRIEST_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_WALK_HURT ); + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + { + iSequence = LookupActivity ( ACT_WALK ); + } + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_IDLE: + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { +// NewActivity = ACT_IDLE_ANGRY; + NewActivity = ACT_IDLE; + } + iSequence = LookupActivity ( NewActivity ); + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + { + iSequence = LookupActivity ( ACT_IDLE ); + } + 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) + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CPriest :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_CULTIST; +} + + +int CPriest::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 CPriest :: 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 CPriest :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + SENTENCEG_PlayRndSz(ENT(pev), "PR_ALERT", PRIEST_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + JustSpoke(); + + CallForHelp( "monster_cultist", 512, m_hEnemy, m_vecEnemyLKP ); + } +} + +//========================================================= +// IdleSound +//========================================================= +void CPriest :: IdleSound( void ) +{ + //if (RANDOM_LONG( 0, 2 ) == 0) + //{ + // SENTENCEG_PlayRndSz(ENT(pev), "PRIEST_IDLE", 0.85, ATTN_NORM, 0, m_voicePitch); + //} +} + +//========================================================= +// PainSound +//========================================================= +void CPriest :: 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 CPriest :: 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 CPriest :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + +//========================================================= +// someone else is talking - don't speak +//========================================================= +BOOL CPriest :: 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 CPriest :: JustSpoke( void ) +{ + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(1.5, 2.0); + m_iSentence = PRIEST_SENT_NONE; +} + +//======================================================== +// Killed +//======================================================== +void CPriest::Killed( entvars_t *pevAttacker, int iGib ) +{ + ClearBeams( ); + CSquadMonster::Killed( pevAttacker, iGib ); +} + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CPriest :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if ( GetBodygroup( GUN_GROUP ) != GUN_NONE ) + {// throw a knife if the priest has one + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun; + + pGun = DropItem( "weapon_knife", 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(); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CPriest :: 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 CPriest :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + // ALERT( at_console, "event %d : %f\n", pEvent->event, pev->frame ); + switch( pEvent->event ) + { + case PRIEST_AE_DROP_KNIFE: + { + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( GUN_GROUP, GUN_NONE ); + + DropItem( "weapon_knife", vecGunPos, vecGunAngles ); + + } + break; + + case PRIEST_AE_KNIFE: + { + Vector oldorig = pev->origin; + CBaseEntity *pHurt = NULL; + // check down below in stages for snakes... + for (int dz = 0; dz >= -3; dz--) + { + pev->origin = oldorig; + pev->origin.z += dz * 12; + pHurt = CheckTraceHullAttack( 70, gSkillData.priestDmgKnife, DMG_SLASH ); + if (pHurt) + { + break; + } + } + pev->origin = oldorig; + if ( pHurt ) + { + // 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 PRIEST_AE_SUMMON_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( ); + + } + + 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 PRIEST_AE_SUMMON_DO: + { + ClearBeams( ); + + // I WAS GOING TO GET THE PRIEST TO SUMMON MORE MONSTERS, BUT DECIDED AGAINST IT... + // find a clear spot nearby + // create a monster + //CBaseEntity *pNew = Create( "monster_ghoul", pev->origin + (pev->angles*64), pev->angles ); + //CBaseMonster *pNewMonster = pNew->MyMonsterPointer( ); + //pNew->pev->spawnflags |= 1; + //EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "hassault/hw_shoot1.wav", 1, ATTN_NORM, 0, RANDOM_LONG( 130, 160 ) ); + + 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 PRIEST_AE_SUMMON_DONE: + { + ClearBeams( ); + } + break; + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +void CPriest :: ZapBeam( int side ) +{ + Vector vecSrc, vecAim; + TraceResult tr; + CBaseEntity *pEntity; + + if (m_iBeams >= PRIEST_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 ) ); +} + +//========================================================= +// GetGunPosition return the end of the barrel +//========================================================= + +Vector CPriest :: GetGunPosition( ) +{ + // because origin is at the centre of the box, not at the bottom + if (m_fStanding ) + { + return pev->origin + Vector( 0, 0, 24 ); +// return pev->origin + Vector( 0, 0, 60 ); + } + else + { + return pev->origin + Vector( 0, 0, 12 ); +// return pev->origin + Vector( 0, 0, 48 ); + } +} + +//========================================================= +// CheckRangeAttack1 - normal beam attack +//========================================================= +BOOL CPriest :: 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 - normal beam attack +//========================================================= +BOOL CPriest :: 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 CPriest :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; +} + + +//========================================================= +// StartTask +//========================================================= +void CPriest :: StartTask ( Task_t *pTask ) +{ + ClearBeams( ); + + switch ( pTask->iTask ) + { + case TASK_PRIEST_SPEAK_SENTENCE: + SpeakSentence(); + TaskComplete(); + break; + + default: + CSquadMonster :: StartTask ( pTask ); + break; + } +} + + +//========================================================= +// Spawn +//========================================================= +void CPriest :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/priest.mdl"); + //UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + UTIL_SetSize(pev, Vector(-16,-16,-36), Vector(16,16,36)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + if (pev->health == 0) + pev->health = gSkillData.priestHealth; + 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_SQUAD | bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_RANGE_ATTACK2 | bits_CAP_DOORS_GROUP; + + m_voicePitch = RANDOM_LONG( 85, 110 ); + m_iSentence = PRIEST_SENT_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CPriest :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/priest.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 CPriest :: 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 CPriest::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +enum +{ + SCHED_PRIEST_ESTABLISH_LINE_OF_FIRE = LAST_COMMON_SCHEDULE + 1,// move to a location to set up an attack against the enemy. (usually when a friendly is in the way). + SCHED_PRIEST_ATTACK1, + //SCHED_PRIEST_SUPPRESS, + //SCHED_PRIEST_COVER_AND_RELOAD, + //SCHED_PRIEST_SWEEP, + //SCHED_PRIEST_FOUND_ENEMY, + //SCHED_PRIEST_WAIT_FACE_ENEMY, + //SCHED_PRIEST_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. + SCHED_PRIEST_ELOF_FAIL, +}; + +// primary range attack +Task_t tlPriestAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PRIEST_SPEAK_SENTENCE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slPriestAttack1[] = +{ + { + tlPriestAttack1, + ARRAYSIZE ( tlPriestAttack1 ), + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND | + bits_COND_HEAVY_DAMAGE, + + bits_SOUND_DANGER, + "Priest Range Attack1" + }, +}; + + +//========================================================= +// Establish line of fire - move to a position that allows +// the priest to attack. +//========================================================= +Task_t tlPriestEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_PRIEST_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_PRIEST_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slPriestEstablishLineOfFire[] = +{ + { + tlPriestEstablishLineOfFire, + ARRAYSIZE ( tlPriestEstablishLineOfFire ), + 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, + "PriestEstablishLineOfFire" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CPriest ) +{ + slPriestAttack1, + slPriestEstablishLineOfFire, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CPriest, CSquadMonster ); + + +//========================================================= +//========================================================= +Schedule_t *CPriest :: GetSchedule( void ) +{ + m_iSentence = PRIEST_SENT_NONE; + + ClearBeams( ); + +/* + if (pev->spawnflags) + { + pev->spawnflags = 0; + return GetScheduleOfType( SCHED_RELOAD ); + } +*/ + + // priests 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), "PR_GREN", PRIEST_SENTENCE_VOLUME, PRIEST_ATTN, 0, m_voicePitch); + JustSpoke(); + } + 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) + { + if (!HasConditions( bits_COND_CAN_MELEE_ATTACK1 )) + { + m_failSchedule = SCHED_CHASE_ENEMY; + if (HasConditions( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE)) + { + if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + { + SENTENCEG_PlayRndSz( ENT(pev), "PR_COVER", PRIEST_SENTENCE_VOLUME, PRIEST_ATTN, 0, m_voicePitch); + JustSpoke(); + } + 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 ); + } + } + } +// can't see enemy + else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if ( OccupySlot( bits_SLOTS_PRIEST_ENGAGE ) ) + { + //!!!KELLY - priest cannot see the enemy and has just decided to + // charge the enemy's position. + if (FOkToSpeak() && RANDOM_LONG(0,1)) + { + m_iSentence = PRIEST_SENT_CHARGE; + } + + return GetScheduleOfType( SCHED_PRIEST_ESTABLISH_LINE_OF_FIRE ); + } + else + { + //!!!KELLY - priest is going to stay put for a couple seconds to see if + // the enemy wanders back out into the open, or approaches the + // priest's covered position. Good place for a taunt, I guess? + if (FOkToSpeak() && RANDOM_LONG(0,1)) + { + //m_iSentence = PRIEST_SENT_TAUNT; + SENTENCEG_PlayRndSz( ENT(pev), "PR_TAUNT", PRIEST_SENTENCE_VOLUME, PRIEST_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + break; + } + return CSquadMonster::GetSchedule( ); +} + + +Schedule_t *CPriest :: 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: + m_iSentence = PRIEST_SENT_CAST; + return slPriestAttack1; + case SCHED_RANGE_ATTACK2: + m_iSentence = PRIEST_SENT_CAST; + return slPriestAttack1; + case SCHED_PRIEST_ESTABLISH_LINE_OF_FIRE: + { + return &slPriestEstablishLineOfFire[ 0 ]; + } + break; + case SCHED_PRIEST_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; + } + return CSquadMonster :: GetScheduleOfType( Type ); +} + + +//========================================================= +// ArmBeam - small beam from arm to nearby geometry +//========================================================= + +void CPriest :: ArmBeam( int side ) +{ + TraceResult tr; + float flDist = 1.0; + + if (m_iBeams >= PRIEST_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 CPriest :: 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 CPriest :: WackBeam( int side, CBaseEntity *pEntity ) +{ + Vector vecDest; + float flDist = 1.0; + + if (m_iBeams >= PRIEST_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++; +} + + +//========================================================= +// ClearBeams - remove all beams +//========================================================= +void CPriest :: ClearBeams( ) +{ + for (int i = 0; i < PRIEST_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/cthulhu/Priest.h b/dlls/cthulhu/Priest.h new file mode 100755 index 00000000..b8d7dbf0 --- /dev/null +++ b/dlls/cthulhu/Priest.h @@ -0,0 +1,72 @@ + +#ifndef PRIEST_H +#define PRIEST_H + +class CPriest : 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 CheckMeleeAttack1 ( float flDot, float flDist ); + 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); + Vector GetGunPosition( void ); + + void DeathSound( void ); + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster ( void ); + + void SetActivity ( Activity NewActivity ); + 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 BeamGlow( void ); + void ZapBeam( int side ); + + CBeam *m_pBeam[PRIEST_MAX_BEAMS]; + + int m_iBeams; + float m_flNextAttack; + + BOOL m_fStanding; + int m_voicePitch; + + BOOL FOkToSpeak( void ); + void JustSpoke( void ); + void SpeakSentence( void ); + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + static const char *pPainSounds[]; + static const char *pDeathSounds[]; + + int m_iSentence; + static const char *pPriestSentences[]; +}; + + + + +#endif + diff --git a/dlls/cthulhu/Rifle.cpp b/dlls/cthulhu/Rifle.cpp new file mode 100755 index 00000000..ef8a959e --- /dev/null +++ b/dlls/cthulhu/Rifle.cpp @@ -0,0 +1,342 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "weapons.h" +#include "monsters.h" +#include "player.h" +#include "gamerules.h" + + +enum rifle_e { + RIFLE_IDLE1 = 0, + RIFLE_FIRE1, + RIFLE_RELOAD, + RIFLE_CLOSEBREAK, + RIFLE_BREAK, + RIFLE_DRAW, + RIFLE_HOLSTER +}; + + +#include "Rifle.h" + + +LINK_ENTITY_TO_CLASS( weapon_rifle, CRifle ); + +int CRifle::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Rifle"; + p->iMaxAmmo1 = RIFLE_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = RIFLE_MAX_CLIP; + p->iFlags = 0; + p->iSlot = 1; + p->iPosition = 3; + p->iId = m_iId = WEAPON_RIFLE; + p->iWeight = RIFLE_WEIGHT; + + return 1; +} + +int CRifle::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +void CRifle::Spawn( ) +{ + pev->classname = MAKE_STRING("weapon_rifle"); // hack to allow for old names + Precache( ); + m_iId = WEAPON_RIFLE; + SET_MODEL(ENT(pev), "models/w_rifle.mdl"); + + m_iDefaultAmmo = RIFLE_DEFAULT_GIVE; + + m_fInZoom = FALSE; + + FallInit();// get ready to fall down. +} + + +void CRifle::Precache( void ) +{ + PRECACHE_MODEL("models/v_rifle.mdl"); + PRECACHE_MODEL("models/w_rifle.mdl"); +// PRECACHE_MODEL("models/p_rifle.mdl"); + + PRECACHE_MODEL("models/w_rifleammo.mdl"); + + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND ("weapons/rifle_reload.wav"); + PRECACHE_SOUND ("weapons/rifle_break.wav"); + //PRECACHE_SOUND ("weapons/rifle_closebreak.wav"); + PRECACHE_SOUND ("weapons/rifle_fire.wav"); + PRECACHE_SOUND ("weapons/rifle_dryfire.wav"); + + m_usFireRifle = PRECACHE_EVENT( 1, "events/rifle.sc" ); +} + +BOOL CRifle::Deploy( ) +{ + if ( g_pGameRules->IsMultiplayer() ) + { + // enable laser sight geometry. + pev->body = 1; + } + else + { + pev->body = 0; + } + + return DefaultDeploy( "models/v_rifle.mdl", "", RIFLE_DRAW, "rifle" ); +} + + +void CRifle::Holster( int skiplocal /* = 0 */ ) +{ + m_fInReload = FALSE;// cancel any reload in progress. + + if ( m_fInZoom ) + { + m_fInZoom = FALSE; + m_pPlayer->m_iFOV = 0; // 0 means reset to default fov + } + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + m_flTimeWeaponIdle = gpGlobals->time + 10 + RANDOM_FLOAT ( 0, 5 ); + SendWeaponAnim( RIFLE_HOLSTER ); +} + +void CRifle::SecondaryAttack( void ) +{ + if (m_iClip <= 0) return; + + if ( m_fInZoom ) + { + m_fInZoom = FALSE; + m_pPlayer->m_iFOV = 0; // 0 means reset to default fov + } + else + { + m_fInZoom = TRUE; + m_pPlayer->m_iFOV = 40; + } + + m_flNextSecondaryAttack = gpGlobals->time + 0.5; +} + +void CRifle::PrimaryAttack() +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = gpGlobals->time + 0.15; + return; + } + + if (m_iClip <= 0) + { + if (m_fInZoom) + SecondaryAttack(); + + if (!m_fFireOnEmpty) + Reload( ); + else + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/rifle_dryfire.wav", 0.8, ATTN_NORM); + m_flNextPrimaryAttack = gpGlobals->time + 0.15; + } + + return; + } + + PLAYBACK_EVENT( 0, m_pPlayer->edict(), m_usFireRifle ); + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = BRIGHT_GUN_FLASH; + + m_iClip--; + + if (m_iClip <= 0) + { + if (m_fInZoom) + SecondaryAttack(); + } + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_2DEGREES ); + m_pPlayer->FireBullets( 1, vecSrc, vecAiming, VECTOR_CONE_1DEGREES, 8192, BULLET_PLAYER_RIFLE, 0 ); + + //if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + // m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + m_flNextPrimaryAttack = gpGlobals->time + 1.0; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); +} + + +void CRifle::Reload( void ) +{ + if ( m_fInZoom ) + { + m_fInZoom = FALSE; + m_pPlayer->m_iFOV = 0; // 0 means reset to default fov + } + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 || m_iClip == RIFLE_MAX_CLIP) + return; + + if (m_flNextReload > gpGlobals->time) + return; + + // don't reload until recoil is done + if (m_flNextPrimaryAttack > gpGlobals->time) + return; + + // check to see if we're ready to reload + if (m_fInReload == 0) + { + SendWeaponAnim( RIFLE_BREAK ); + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/rifle_break.wav", 0.8, ATTN_NORM); + m_fInReload = 1; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.75; + m_flTimeWeaponIdle = gpGlobals->time + 0.75; + m_flNextPrimaryAttack = gpGlobals->time + 1.0; + m_flNextSecondaryAttack = gpGlobals->time + 1.0; + return; + } + else if (m_fInReload == 1) + { + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + // was waiting for gun to move to side + m_fInReload = 2; + + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/rifle_reload.wav", 1, ATTN_NORM, 0, 85 + RANDOM_LONG(0,0x1f)); + //if (RANDOM_LONG(0,1)) + // EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/rifle_reload1.wav", 1, ATTN_NORM, 0, 85 + RANDOM_LONG(0,0x1f)); + //else + // EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/rifle_reload2.wav", 1, ATTN_NORM, 0, 85 + RANDOM_LONG(0,0x1f)); + + SendWeaponAnim( RIFLE_RELOAD ); + + m_flNextReload = gpGlobals->time + 0.5; + m_flTimeWeaponIdle = gpGlobals->time + 0.5; + } + else + { + // Add them to the clip + m_iClip += 1; + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= 1; + m_fInReload = 1; + } + + // like the HL shotgun reload... + //if (DefaultReload( 6, RIFLE_RELOAD, 2.0 )) + //{ + // m_flSoundDelay = gpGlobals->time + 1.5; + //} +} + + +void CRifle::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + if (m_iClip == 0 && m_fInReload == 0 && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + Reload( ); + } + else if (m_fInReload != 0) + { + if (m_iClip != RIFLE_MAX_CLIP && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + Reload( ); + } + else + { + // reload debounce has timed out + SendWeaponAnim( RIFLE_CLOSEBREAK ); + + // play cocking sound + //EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/rifle_closebreak.wav", 1, ATTN_NORM, 0, 95 + RANDOM_LONG(0,0x1f)); + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/rifle_break.wav", 1, ATTN_NORM, 0, 95 + RANDOM_LONG(0,0x1f)); + m_fInReload = 0; + m_flTimeWeaponIdle = gpGlobals->time + 0.75; + } + } + else + { + SendWeaponAnim( RIFLE_IDLE1 ); + m_flTimeWeaponIdle = gpGlobals->time + (70.0/30.0);// * RANDOM_LONG(2, 5); + } +} + +////////////////////////////////////////////////////////////////////////////// + +void CRifleAmmo::Spawn( void ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/w_rifleammo.mdl"); + CBasePlayerAmmo::Spawn( ); +} + +void CRifleAmmo::Precache( void ) +{ + PRECACHE_MODEL ("models/w_rifleammo.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); +} + +BOOL CRifleAmmo::AddAmmo( CBaseEntity *pOther ) +{ + if (pOther->GiveAmmo( AMMO_RIFLE_GIVE, "Rifle", RIFLE_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; +} + +LINK_ENTITY_TO_CLASS( ammo_rifle, CRifleAmmo ); + + +#endif \ No newline at end of file diff --git a/dlls/cthulhu/Rifle.h b/dlls/cthulhu/Rifle.h new file mode 100755 index 00000000..e6f6401a --- /dev/null +++ b/dlls/cthulhu/Rifle.h @@ -0,0 +1,53 @@ + +#ifndef RIFLE_H +#define RIFLE_H + +class CRifle : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 1; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + void PrimaryAttack( void ); + void SecondaryAttack( void ); + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + void Reload( void ); + void WeaponIdle( void ); + int m_fInReload; + float m_flNextReload; + + BOOL m_fInZoom;// don't save this. + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } + +private: + unsigned short m_usFireRifle; + +}; + + +////////////////////////////////////////////////////////////////////////////// + +class CRifleAmmo : public CBasePlayerAmmo +{ + void Spawn( void ); + void Precache( void ); + BOOL AddAmmo( CBaseEntity *pOther ); +}; + + + + + +#endif + diff --git a/dlls/cthulhu/SerpentMan.cpp b/dlls/cthulhu/SerpentMan.cpp new file mode 100755 index 00000000..62329712 --- /dev/null +++ b/dlls/cthulhu/SerpentMan.cpp @@ -0,0 +1,739 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Serpent Man 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" +#include "animation.h" +#include "snake.h" + +extern DLL_GLOBAL int g_iSkillLevel; + +// TO DO: +// cast serpent staff +// melee attack + +#define GUN_GROUP 2 +#define GUN_STAFF 0 +#define GUN_NONE 1 + +#define MAX_SERPENTS 12 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define SERPENT_MAN_AE_CAST ( 1 ) +#define SERPENT_MAN_AE_CLAW ( 2 ) +#define SERPENT_MAN_AE_DROP_STAFF ( 3 ) + +#include "SerpentMan.h" + + +LINK_ENTITY_TO_CLASS( monster_serpentman, CSerpentMan ); + + +TYPEDESCRIPTION CSerpentMan::m_SaveData[] = +{ + DEFINE_FIELD( CSerpentMan, m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( CSerpentMan, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CSerpentMan, m_voicePitch, FIELD_INTEGER ), + DEFINE_FIELD( CSerpentMan, m_iNumSerpents, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CSerpentMan, CSquadMonster ); + + + + +const char *CSerpentMan::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CSerpentMan::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CSerpentMan::pAttackSounds[] = +{ + "serpentman/sm_attack1.wav", + "serpentman/sm_attack2.wav", + "serpentman/sm_attack3.wav", +}; + +const char *CSerpentMan::pPainSounds[] = +{ + "serpentman/sm_pain1.wav", + "serpentman/sm_pain2.wav", + "serpentman/sm_pain3.wav", +}; + +const char *CSerpentMan::pDeathSounds[] = +{ + "serpentman/sm_die1.wav", + "serpentman/sm_die2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CSerpentMan :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MILITARY; +} + + +int CSerpentMan::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 CSerpentMan :: 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 CSerpentMan :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + SENTENCEG_PlayRndSz(ENT(pev), "SM_ALERT", 0.85, ATTN_NORM, 0, m_voicePitch); + + CallForHelp( "monster_serpent_man", 512, m_hEnemy, m_vecEnemyLKP ); + CallForHelp( "monster_human_cultist", 512, m_hEnemy, m_vecEnemyLKP ); + } +} + +//========================================================= +// IdleSound +//========================================================= +void CSerpentMan :: IdleSound( void ) +{ + if (RANDOM_LONG( 0, 2 ) == 0) + { + SENTENCEG_PlayRndSz(ENT(pev), "SM_IDLE", 0.85, ATTN_NORM, 0, m_voicePitch); + } +} + +//========================================================= +// AttackSound +//========================================================= +void CSerpentMan :: AttackSound( void ) +{ + if (RANDOM_LONG( 0, 2 ) == 0) + { + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 1.0, ATTN_NORM, 0, m_voicePitch ); + } +} + +//========================================================= +// PainSound +//========================================================= +void CSerpentMan :: 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 CSerpentMan :: 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 CSerpentMan :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + + +void CSerpentMan::Killed( entvars_t *pevAttacker, int iGib ) +{ + CSquadMonster::Killed( pevAttacker, iGib ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CSerpentMan :: 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; +} + +//========================================================= +// GetGunPosition return the end of the barrel +//========================================================= + +Vector CSerpentMan :: GetGunPosition( ) +{ + // because origin is at the centre of the box, not at the bottom + if (m_fStanding ) + { + return pev->origin + Vector( 0, 0, 24 ); +// return pev->origin + Vector( 0, 0, 60 ); + } + else + { + return pev->origin + Vector( 0, 0, 12 ); +// return pev->origin + Vector( 0, 0, 48 ); + } +} +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CSerpentMan :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if ( GetBodygroup( GUN_GROUP ) != GUN_NONE ) + {// throw a serpent staff if the serpent man has one + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity* pGun = DropItem( "weapon_serpentstaff", 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(); +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CSerpentMan :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + int box = 24; + int spawndist = 64; + TraceResult tr; + Vector trace_origin; + Vector vecShootOrigin; + Vector vecShootDir; + + // ALERT( at_console, "event %d : %f\n", pEvent->event, pev->frame ); + switch( pEvent->event ) + { + case SERPENT_MAN_AE_CAST: + UTIL_MakeVectors( pev->v_angle ); + + // HACK HACK: Ugly hacks to handle change in origin based on new physics code for players + // Move origin up if crouched and start trace a bit outside of body ( 20 units instead of 16 ) + trace_origin = pev->origin; + if ( !m_fStanding ) + { + trace_origin = trace_origin - ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + } + + // find place to toss monster - this is some horrible code I have written here... + // UTIL_TraceLine( trace_origin + angle * 20, trace_origin + angle * 64, dont_ignore_monsters, NULL, &tr ); + // firstly check all areas within the box to be created + BOOL b1, b2, b3, b4; + vecShootOrigin = pev->origin + Vector( 0, 0, 55 ); + vecShootDir = ShootAtEnemy( vecShootOrigin ); + + UTIL_TraceLine( trace_origin + vecShootDir * 20, trace_origin + vecShootDir * spawndist + Vector(box,box,0), dont_ignore_monsters, NULL, &tr ); + b1 = (tr.fAllSolid == 0 && tr.fStartSolid == 0 && tr.flFraction > 0.99); + UTIL_TraceLine( trace_origin + vecShootDir * 20, trace_origin + vecShootDir * spawndist + Vector(-box,-box,0), dont_ignore_monsters, NULL, &tr ); + b2 = (tr.fAllSolid == 0 && tr.fStartSolid == 0 && tr.flFraction > 0.99); + UTIL_TraceLine( trace_origin + vecShootDir * 20, trace_origin + vecShootDir * spawndist + Vector(-box,box,0), dont_ignore_monsters, NULL, &tr ); + b3 = (tr.fAllSolid == 0 && tr.fStartSolid == 0 && tr.flFraction > 0.99); + UTIL_TraceLine( trace_origin + vecShootDir * 20, trace_origin + vecShootDir * spawndist + Vector(box,-box,0), dont_ignore_monsters, NULL, &tr ); + b4 = (tr.fAllSolid == 0 && tr.fStartSolid == 0 && tr.flFraction > 0.99); + UTIL_TraceLine( trace_origin + vecShootDir * 20, trace_origin + vecShootDir * spawndist, dont_ignore_monsters, NULL, &tr ); + + // if we are clear at all corners of the box the snake is going to be created in + if (b1 && b2 && b3 && b4) + { + Vector temp; + temp.x = temp.y = temp.z = 0.0; + temp.y = pev->v_angle.y; + + //CBaseEntity *pSnake = CBaseEntity::Create( "monster_snake", tr.vecEndPos, temp, m_pPlayer->edict() ); + CBaseEntity *pSnake = CBaseEntity::Create( "monster_snake", tr.vecEndPos, pev->angles ); + ((CSnake*)pSnake)->SetOwner(this); + pSnake->pev->spawnflags |= 1; + SetBits(pSnake->pev->spawnflags,SF_MONSTER_NO_YELLOW_BLOBS); + // drop to floor, to check if we have landed on top of an entity... + DROP_TO_FLOOR( ENT( pSnake->pev ) ); + + CBaseEntity *pList[2]; + int buf = 24; + // if there are any no other entities nearby (particularly under it!), then... + int count = UTIL_EntitiesInBox( pList, 2, pSnake->pev->origin - Vector(buf,buf,buf), pSnake->pev->origin + Vector(buf,buf,buf), FL_CLIENT|FL_MONSTER ); + //...put the snake there and decrement ammo + if ( count <= 1 ) + { + pSnake->pev->velocity = vecShootDir * 100 + pev->velocity; + + m_iNumSerpents--; + } + // otherwise we do not want this snake...as it will cause yellow blobs! + else + { + UTIL_Remove(pSnake); + } + + m_flNextAttack = gpGlobals->time + 2.0; + } + break; + case SERPENT_MAN_AE_DROP_STAFF: + { + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( GUN_GROUP, GUN_NONE ); + + // now spawn a serpent staff. + DropItem( "weapon_serpentstaff", vecGunPos, vecGunAngles ); + + } + break; + + case SERPENT_MAN_AE_CLAW: + { + Vector oldorig = pev->origin; + CBaseEntity *pHurt = NULL; + // check down below in stages for snakes... + for (int dz = 0; dz >=-3; dz--) + { + pev->origin = oldorig; + pev->origin.z += dz * 12; + pHurt = CheckTraceHullAttack( 70, gSkillData.serpentmanDmgStaff, DMG_POISON ); + if (pHurt) + { + break; + } + } + pev->origin = oldorig; + + if ( pHurt ) + { + // 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 ); + } + + // also play an attack sound + AttackSound(); + } + break; + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CSerpentMan :: 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 - normal beam attack +//========================================================= +BOOL CSerpentMan :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if (m_flNextAttack > gpGlobals->time || m_iNumSerpents <= 0) + { + return FALSE; + } + + return CSquadMonster::CheckRangeAttack1( flDot, flDist ); +} + +//========================================================= +// CheckRangeAttack2 - check bravery and try to resurect dead comrades +//========================================================= +BOOL CSerpentMan :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; +} + + +//========================================================= +// StartTask +//========================================================= +void CSerpentMan :: StartTask ( Task_t *pTask ) +{ + CSquadMonster :: StartTask ( pTask ); +} + + +//========================================================= +// Spawn +//========================================================= +void CSerpentMan :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/serpentman.mdl"); + //UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + UTIL_SetSize(pev, Vector(-16,-16,-36), Vector(16,16,36)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + if (pev->health == 0) + pev->health = gSkillData.serpentmanHealth; + 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 ); + + m_iNumSerpents = MAX_SERPENTS; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CSerpentMan :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/serpentman.mdl"); + PRECACHE_SOUND("zombie/zo_pain2.wav"); + PRECACHE_SOUND("weapons/cbar_miss1.wav"); + PRECACHE_SOUND("serpentman/sm_summon.wav"); + + for ( i = 0; i < ARRAYSIZE( pAttackSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackSounds[i]); + + 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 CSerpentMan :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + // don't slash one of your own + if ((bitsDamageType & DMG_POISON) || (bitsDamageType & DMG_NERVEGAS) || (bitsDamageType & DMG_PARALYZE)) + return 0; + + m_afMemory |= bits_MEMORY_PROVOKED; + return CSquadMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + + +void CSerpentMan::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if ((bitsDamageType & DMG_PARALYZE) || (bitsDamageType & DMG_NERVEGAS) || (bitsDamageType & DMG_POISON)) + return; + + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + + +// primary range attack +Task_t tlSerpentManAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slSerpentManAttack1[] = +{ + { + tlSerpentManAttack1, + ARRAYSIZE ( tlSerpentManAttack1 ), + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND | + bits_COND_HEAVY_DAMAGE, + + bits_SOUND_DANGER, + "SerpentMan Range Attack1" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CSerpentMan ) +{ + slSerpentManAttack1, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CSerpentMan, CSquadMonster ); + + +//========================================================= +//========================================================= +// SetActivity +//========================================================= +void CSerpentMan :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_MELEE_ATTACK1: + + // randomly stand or crouch + if (RANDOM_LONG(0,99) == 0) + m_fStanding = 0; + else + m_fStanding = 1; + + // a short enemy...probably a snake... + if ((m_hEnemy != NULL) && (m_hEnemy->pev->maxs.z < 36)) + { + m_fStanding = 0; + } + + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "ref_shoot_serpentstaff" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouch_shoot_serpentstaff" ); + } + 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) + } +} +//========================================================= +Schedule_t *CSerpentMan :: 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 ( 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) + { + 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 *CSerpentMan :: 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 slSerpentManAttack1; + case SCHED_RANGE_ATTACK2: + return slSerpentManAttack1; + } + return CSquadMonster :: GetScheduleOfType( Type ); +} + + diff --git a/dlls/cthulhu/SerpentMan.h b/dlls/cthulhu/SerpentMan.h new file mode 100755 index 00000000..d051bd47 --- /dev/null +++ b/dlls/cthulhu/SerpentMan.h @@ -0,0 +1,62 @@ + +#ifndef SERPENT_MAN_H +#define SERPENT_MAN_H + +class CSerpentMan : 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 ); + Vector GetGunPosition( void ); + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + 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 AttackSound( void ); + + void GibMonster( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + + void SetActivity ( Activity NewActivity ); + 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[]; + + BOOL m_fStanding; + float m_flNextAttack; + + int m_voicePitch; + + int m_iNumSerpents; + + static const char *pAttackSounds[]; + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + static const char *pPainSounds[]; + static const char *pDeathSounds[]; +}; + + + + + + + +#endif diff --git a/dlls/cthulhu/SerpentStaff.cpp b/dlls/cthulhu/SerpentStaff.cpp new file mode 100755 index 00000000..2e596023 --- /dev/null +++ b/dlls/cthulhu/SerpentStaff.cpp @@ -0,0 +1,247 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "soundent.h" +#include "gamerules.h" + +#include "snake.h" + +enum squeak_e { + SERPENT_STAFF_DRAW = 0, + SERPENT_STAFF_IDLE1, + SERPENT_STAFF_IDLE2, + SERPENT_STAFF_CAST, + SERPENT_STAFF_HOLSTER +}; + + +#include "SerpentStaff.h" + + +LINK_ENTITY_TO_CLASS( weapon_serpentstaff, CSerpentStaff ); + + +void CSerpentStaff::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_SERPENT_STAFF; + SET_MODEL(ENT(pev), "models/w_serpentstaff.mdl"); + + FallInit();//get ready to fall down. + + m_iDefaultAmmo = SERPENT_STAFF_DEFAULT_GIVE; + + pev->sequence = 1; + pev->animtime = gpGlobals->time; + pev->framerate = 1.0; +} + + +void CSerpentStaff::Precache( void ) +{ + PRECACHE_MODEL("models/w_serpentstaff.mdl"); + PRECACHE_MODEL("models/v_serpentstaff.mdl"); +// PRECACHE_MODEL("models/p_serpentstaff.mdl"); + UTIL_PrecacheOther("monster_snake"); +} + + +int CSerpentStaff::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Serpent Staff"; + p->iMaxAmmo1 = SERPENT_STAFF_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 3; + p->iPosition = 2; + p->iId = m_iId = WEAPON_SERPENT_STAFF; + p->iWeight = SERPENT_STAFF_WEIGHT; + p->iFlags = 0; + + return 1; +} + + + +BOOL CSerpentStaff::Deploy( ) +{ + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + + return DefaultDeploy( "models/v_serpentstaff.mdl", "", SERPENT_STAFF_DRAW, "serpent_staff" ); +} + + +void CSerpentStaff::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = gpGlobals->time + 0.5; + + if (!m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + m_pPlayer->pev->weapons &= ~(1<pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + +int CSerpentStaff::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +void CSerpentStaff::PrimaryAttack() +{ + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + UTIL_MakeVectors( m_pPlayer->pev->v_angle ); + TraceResult tr; + Vector trace_origin; + + // HACK HACK: Ugly hacks to handle change in origin based on new physics code for players + // Move origin up if crouched and start trace a bit outside of body ( 20 units instead of 16 ) + trace_origin = m_pPlayer->pev->origin; + if ( m_pPlayer->pev->flags & FL_DUCKING ) + { + trace_origin = trace_origin - ( VEC_HULL_MIN - VEC_DUCK_HULL_MIN ); + } + + // find place to toss monster - this is some horrible code I have written here... +// UTIL_TraceLine( trace_origin + gpGlobals->v_forward * 20, trace_origin + gpGlobas->v_forward * 64, dont_ignore_monsters, NULL, &tr ); + // firstly check all areas within the box to be created + int box = 24; + int spawndist = 64; + BOOL b1, b2, b3, b4; + UTIL_TraceLine( trace_origin + gpGlobals->v_forward * 20, trace_origin + gpGlobals->v_forward * spawndist + Vector(box,box,0), dont_ignore_monsters, NULL, &tr ); + b1 = (tr.fAllSolid == 0 && tr.fStartSolid == 0 && tr.flFraction > 0.99); + UTIL_TraceLine( trace_origin + gpGlobals->v_forward * 20, trace_origin + gpGlobals->v_forward * spawndist + Vector(-box,-box,0), dont_ignore_monsters, NULL, &tr ); + b2 = (tr.fAllSolid == 0 && tr.fStartSolid == 0 && tr.flFraction > 0.99); + UTIL_TraceLine( trace_origin + gpGlobals->v_forward * 20, trace_origin + gpGlobals->v_forward * spawndist + Vector(-box,box,0), dont_ignore_monsters, NULL, &tr ); + b3 = (tr.fAllSolid == 0 && tr.fStartSolid == 0 && tr.flFraction > 0.99); + UTIL_TraceLine( trace_origin + gpGlobals->v_forward * 20, trace_origin + gpGlobals->v_forward * spawndist + Vector(box,-box,0), dont_ignore_monsters, NULL, &tr ); + b4 = (tr.fAllSolid == 0 && tr.fStartSolid == 0 && tr.flFraction > 0.99); + UTIL_TraceLine( trace_origin + gpGlobals->v_forward * 20, trace_origin + gpGlobals->v_forward * spawndist, dont_ignore_monsters, NULL, &tr ); + + // if we are clear at all corners of the box the snake is going to be created in + if (b1 && b2 && b3 && b4) + { + SendWeaponAnim( SERPENT_STAFF_CAST ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector temp; + temp.x = temp.y = temp.z = 0.0; + temp.y = m_pPlayer->pev->v_angle.y; + + //CBaseEntity *pSnake = CBaseEntity::Create( "monster_snake", tr.vecEndPos, temp, m_pPlayer->edict() ); + CBaseEntity *pSnake = CBaseEntity::Create( "monster_snake", tr.vecEndPos, temp ); + ((CSnake*)pSnake)->SetOwner(m_pPlayer); + pSnake->pev->spawnflags |= 1; + pSnake->pev->spawnflags |= SF_MONSTER_NO_YELLOW_BLOBS; + // drop to floor, to check if we have landed on top of an entity... + DROP_TO_FLOOR( ENT( pSnake->pev ) ); + + CBaseEntity *pList[2]; + int buf = 24; + // if there are any no other entities nearby (particularly under it!), then... + int count = UTIL_EntitiesInBox( pList, 2, pSnake->pev->origin - Vector(buf,buf,buf), pSnake->pev->origin + Vector(buf,buf,buf), FL_CLIENT|FL_MONSTER ); + //...put the snake there and decrement ammo + if ( count <= 1 ) + { + pSnake->pev->velocity = gpGlobals->v_forward * 100 + m_pPlayer->pev->velocity; + + m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + } + // otherwise we do not want this snake...as it will cause yellow blobs! + else + { + UTIL_Remove(pSnake); + } + + m_fJustThrown = 1; + + m_flNextPrimaryAttack = gpGlobals->time + 0.5; + m_flTimeWeaponIdle = gpGlobals->time + 0.5; + } + } +} + + +void CSerpentStaff::SecondaryAttack( void ) +{ + +} + + +void CSerpentStaff::WeaponIdle( void ) +{ + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + if (m_fJustThrown) + { + m_fJustThrown = 0; + + if ( !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] ) + { + RetireWeapon(); + return; + } + + //SendWeaponAnim( SERPENT_STAFF_DRAW ); + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 3,5 ); + return; + } + + int iAnim; + + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.75) + { + iAnim = SERPENT_STAFF_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + 30.0 / 16 * (2); + } + else + { + iAnim = SERPENT_STAFF_IDLE2; + m_flTimeWeaponIdle = gpGlobals->time + 70.0 / 16.0; + } + + SendWeaponAnim( iAnim ); +} + +#endif \ No newline at end of file diff --git a/dlls/cthulhu/SerpentStaff.h b/dlls/cthulhu/SerpentStaff.h new file mode 100755 index 00000000..255fcb42 --- /dev/null +++ b/dlls/cthulhu/SerpentStaff.h @@ -0,0 +1,35 @@ + +#ifndef SERPENT_STAFF_H +#define SERPENT_STAFF_H + +class CSerpentStaff : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 3; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + void WeaponIdle( void ); + int m_fJustThrown; + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } +}; + + + + +#endif + diff --git a/dlls/cthulhu/Shrivelling.h b/dlls/cthulhu/Shrivelling.h new file mode 100755 index 00000000..0583b537 --- /dev/null +++ b/dlls/cthulhu/Shrivelling.h @@ -0,0 +1,63 @@ + +#ifndef SHRIVELLING_H +#define SHRIVELLING_H + +class CShrivelling : public CBasePlayerWeapon +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 4; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + + void CreateEffect( void ); + void UpdateEffect( const Vector &startPoint, const Vector &endPoint, float timeBlend ); + void DestroyEffect( void ); + + void EndAttack( void ); + void Attack( void ); + void PrimaryAttack( void ); + void WeaponIdle( void ); + static int g_fireAnims1[]; + static int g_fireAnims2[]; + + float m_flAmmoUseTime;// since we use < 1 point of ammo per update, we subtract ammo on a timer. + + float GetPulseInterval( void ); + float GetDischargeInterval( void ); + + void Fire( const Vector &vecOrigSrc, const Vector &vecDir ); + + enum SHRIVELLING_FIRESTATE { FIRE_OFF, FIRE_CHARGE }; + enum SHRIVELLING_FIREMODE { FIRE_NARROW, FIRE_WIDE}; + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } + +private: + float m_shootTime; + CBeam *m_pBeam; + CBeam *m_pNoise; + CSprite *m_pSprite; + SHRIVELLING_FIRESTATE m_fireState; + SHRIVELLING_FIREMODE m_fireMode; + float m_shakeTime; + BOOL m_deployed; +}; + +#endif + diff --git a/dlls/cthulhu/SirHenry.cpp b/dlls/cthulhu/SirHenry.cpp new file mode 100755 index 00000000..7b2f8861 --- /dev/null +++ b/dlls/cthulhu/SirHenry.cpp @@ -0,0 +1,1764 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 SirHenry +//========================================================= + +#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 "effects.h" +#include "weapons.h" +#include "soundent.h" + +#define BODY_GROUP 1 +#define BODY_CIVILIAN 0 +#define BODY_CULTIST 1 +#define NUM_BODY_GROUPS 2 + +#define GUN_GROUP 2 +#define GUN_NONE 0 +#define GUN_BOOK1 1 +#define GUN_BOOK2 2 +#define GUN_KNIFE 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_FEAR = LAST_TALKMONSTER_TASK + 1, + TASK_RUN_PATH_SCARED, + TASK_SCREAM, + TASK_RANDOM_SCREAM, + TASK_MOVE_TO_TARGET_RANGE_SCARED, +}; + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +#define SIRHENRY_AE_KNIFE ( 1 ) +#define SIRHENRY_AE_SUMMON_POWERUP ( 2 ) +#define SIRHENRY_AE_SUMMON_DO ( 3 ) +#define SIRHENRY_AE_SUMMON_DONE ( 4 ) +#define SIRHENRY_AE_DROP_KNIFE ( 5 ) + +#define SIRHENRY_MAX_BEAMS 8 + +//======================================================= +// Sir Henry +//======================================================= + +#include "SirHenry.h" + + +LINK_ENTITY_TO_CLASS( monster_sirhenry, CSirHenry ); + +TYPEDESCRIPTION CSirHenry::m_SaveData[] = +{ + DEFINE_FIELD( CSirHenry, m_iHolding, FIELD_INTEGER ), + DEFINE_ARRAY( CSirHenry, m_pBeam, FIELD_CLASSPTR, SIRHENRY_MAX_BEAMS ), + DEFINE_FIELD( CSirHenry, m_iBeams, FIELD_INTEGER ), + DEFINE_FIELD( CSirHenry, m_painTime, FIELD_TIME ), + DEFINE_FIELD( CSirHenry, m_flNextAttack, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CSirHenry, CTalkMonster ); + +//////////////////////////////////////////////////////////////////////////////////////////// +// Sounds +//////////////////////////////////////////////////////////////////////////////////////////// + +const char *CSirHenry::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CSirHenry::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlSirHenryFollow[] = +{ + { 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 slSirHenryFollow[] = +{ + { + tlSirHenryFollow, + ARRAYSIZE ( tlSirHenryFollow ), + 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 tlSirHenryFollowScared[] = +{ + { 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 slSirHenryFollowScared[] = +{ + { + tlSirHenryFollowScared, + ARRAYSIZE ( tlSirHenryFollowScared ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + bits_SOUND_DANGER, + "FollowScared" + }, +}; + +Task_t tlSirHenryFaceTargetScared[] = +{ + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE_SCARED }, +}; + +Schedule_t slSirHenryFaceTargetScared[] = +{ + { + tlSirHenryFaceTargetScared, + ARRAYSIZE ( tlSirHenryFaceTargetScared ), + bits_COND_HEAR_SOUND | + bits_COND_NEW_ENEMY, + bits_SOUND_DANGER, + "FaceTargetScared" + }, +}; + +Task_t tlSirHenryStopFollowing[] = +{ + { TASK_CANT_FOLLOW, (float)0 }, +}; + +Schedule_t slSirHenryStopFollowing[] = +{ + { + tlSirHenryStopFollowing, + ARRAYSIZE ( tlSirHenryStopFollowing ), + 0, + 0, + "StopFollowing" + }, +}; + + +Task_t tlSirHenryFaceTarget[] = +{ + { 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 slSirHenryFaceTarget[] = +{ + { + tlSirHenryFaceTarget, + ARRAYSIZE ( tlSirHenryFaceTarget ), + bits_COND_CLIENT_PUSH | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + bits_SOUND_COMBAT | + bits_SOUND_DANGER, + "FaceTarget" + }, +}; + + +Task_t tlSirHenryPanic[] = +{ + { 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 slSirHenryPanic[] = +{ + { + tlSirHenryPanic, + ARRAYSIZE ( tlSirHenryPanic ), + 0, + 0, + "SciPanic" + }, +}; + + +Task_t tlIdleSirHenryStand[] = +{ + { 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 slIdleSirHenryStand[] = +{ + { + tlIdleSirHenryStand, + ARRAYSIZE ( tlIdleSirHenryStand ), + 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 tlSirHenryCover[] = +{ + { 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 slSirHenryCover[] = +{ + { + tlSirHenryCover, + ARRAYSIZE ( tlSirHenryCover ), + bits_COND_NEW_ENEMY, + 0, + "SirHenryCover" + }, +}; + + + +Task_t tlSirHenryHide[] = +{ + { 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 slSirHenryHide[] = +{ + { + tlSirHenryHide, + ARRAYSIZE ( tlSirHenryHide ), + 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, + "SirHenryHide" + }, +}; + + +Task_t tlSirHenryStartle[] = +{ + { 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 slSirHenryStartle[] = +{ + { + tlSirHenryStartle, + ARRAYSIZE ( tlSirHenryStartle ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_HATE | + bits_COND_SEE_FEAR | + bits_COND_SEE_DISLIKE, + 0, + "SirHenryStartle" + }, +}; + + + +Task_t tlSirHenryFear[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SAY_FEAR, (float)0 }, +// { TASK_PLAY_SEQUENCE, (float)ACT_FEAR_DISPLAY }, +}; + +Schedule_t slSirHenryFear[] = +{ + { + tlSirHenryFear, + ARRAYSIZE ( tlSirHenryFear ), + bits_COND_NEW_ENEMY, + 0, + "Fear" + }, +}; + +// primary range attack +Task_t tlSirHenryAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slSirHenryAttack1[] = +{ + { + tlSirHenryAttack1, + ARRAYSIZE ( tlSirHenryAttack1 ), + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND | + bits_COND_HEAVY_DAMAGE, + + bits_SOUND_DANGER, + "SirHenry Range Attack1" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CSirHenry ) +{ + slSirHenryFollow, + slSirHenryFaceTarget, + slIdleSirHenryStand, + slSirHenryFear, + slSirHenryAttack1, + slSirHenryCover, + slSirHenryHide, + slSirHenryStartle, + slSirHenryStopFollowing, + slSirHenryPanic, + slSirHenryFollowScared, + slSirHenryFaceTargetScared, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CSirHenry, CTalkMonster ); + + +void CSirHenry::DeclineFollowing( void ) +{ + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( m_szGrp[TLK_DECLINE], 2, VOL_NORM, ATTN_NORM ); //LRC +} + + +void CSirHenry :: Scream( void ) +{ + if ( FOkToSpeak() ) + { + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( "SC_SCREAM", RANDOM_FLOAT(3, 6), VOL_NORM, ATTN_NORM ); + } +} + + +Activity CSirHenry::GetStoppedActivity( void ) +{ + if ( m_hEnemy != NULL ) + return ACT_EXCITED; + return CTalkMonster::GetStoppedActivity(); +} + + +void CSirHenry :: StartTask( Task_t *pTask ) +{ + ClearBeams( ); + + switch( pTask->iTask ) + { + 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_RUN_PATH_SCARED: + m_movementActivity = ACT_RUN_SCARED; + break; + + case TASK_MOVE_TO_TARGET_RANGE_SCARED: + { + if ( m_hTargetEnt== NULL) + { + TaskFail(); + } + else + { + 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 CSirHenry :: 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; + + default: + CTalkMonster::RunTask( pTask ); + break; + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CSirHenry :: Classify ( void ) +{ + switch( GetBodygroup( BODY_GROUP ) ) + { + case BODY_CULTIST: + return m_iClass?m_iClass:CLASS_HUMAN_CULTIST; + break; + default: + return m_iClass?m_iClass:CLASS_HUMAN_PASSIVE; + break; + } +} + + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CSirHenry :: 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 CSirHenry :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SIRHENRY_AE_DROP_KNIFE: + { + if (m_iHolding == GUN_KNIFE) + { + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + int bg = GetBodygroup(BODY_GROUP); + m_iHolding = GUN_NONE; + // set the correct body group for what Sir Henry is holding + pev->body = NUM_BODY_GROUPS * m_iHolding + bg; + + DropItem( "weapon_knife", vecGunPos, vecGunAngles ); + } + } + break; + + case SIRHENRY_AE_KNIFE: + { + Vector oldorig = pev->origin; + CBaseEntity *pHurt = NULL; + // check down below in stages for snakes... + for (int dz = 0; dz >= -3; dz--) + { + pev->origin = oldorig; + pev->origin.z += dz * 12; + pHurt = CheckTraceHullAttack( 70, gSkillData.sirhenryDmgKnife, DMG_SLASH ); + if (pHurt) + { + break; + } + } + pev->origin = oldorig; + if ( pHurt ) + { + // 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 SIRHENRY_AE_SUMMON_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( ); + + } + + 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 SIRHENRY_AE_SUMMON_DO: + { + ClearBeams( ); + + // I WAS GOING TO GET THE SIRHENRY TO SUMMON MORE MONSTERS, BUT DECIDED AGAINST IT... + // find a clear spot nearby + // create a monster + //CBaseEntity *pNew = Create( "monster_ghoul", pev->origin + (pev->angles*64), pev->angles ); + //CBaseMonster *pNewMonster = pNew->MyMonsterPointer( ); + //pNew->pev->spawnflags |= 1; + //EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "hassault/hw_shoot1.wav", 1, ATTN_NORM, 0, RANDOM_LONG( 130, 160 ) ); + + 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 SIRHENRY_AE_SUMMON_DONE: + { + ClearBeams( ); + } + break; + + default: + CTalkMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CSirHenry :: Spawn( void ) +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/SirHenry.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; + if (pev->health == 0) + pev->health = gSkillData.sirhenryHealth; + 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 Sir Henry 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; + + MonsterInit(); + + int bg = GetBodygroup(BODY_GROUP); + if (bg == BODY_CIVILIAN) + { + SetUse( FollowerUse ); + } + + // set the correct body group for what Sir Henry is holding + pev->body = NUM_BODY_GROUPS * m_iHolding + bg; +} + +void CSirHenry::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "holding")) + { + m_iHolding = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + { + CTalkMonster::KeyValue( pkvd ); + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CSirHenry :: Precache( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/SirHenry.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"); + + int i; + for ( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackHitSounds[i]); + + for ( i = 0; i < ARRAYSIZE( pAttackMissSounds ); i++ ) + PRECACHE_SOUND((char *)pAttackMissSounds[i]); + + // every new SirHenry must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + + CTalkMonster::Precache(); +} + +// Init talk data +void CSirHenry :: TalkInit() +{ + + CTalkMonster::TalkInit(); + + // Sir Henry will try to talk to friends in this order: + + m_szFriends[0] = "monster_scientist"; + m_szFriends[1] = "monster_sitting_scientist"; + m_szFriends[2] = "monster_policeman"; + m_szFriends[3] = ""; + m_szFriends[4] = ""; + + // Sir Henry speech group names (group names are in sentences.txt) + + if (!m_iszSpeakAs) + { + m_szGrp[TLK_ANSWER] = "CIV_ANSWER"; + m_szGrp[TLK_QUESTION] = "CIV_QUESTION"; + m_szGrp[TLK_IDLE] = "SH_IDLE"; + m_szGrp[TLK_STARE] = "SC_STARE"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_USE] = NULL; + else + m_szGrp[TLK_USE] = NULL; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_UNUSE] = NULL; + else + m_szGrp[TLK_UNUSE] = NULL; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_DECLINE] = NULL; + else + m_szGrp[TLK_DECLINE] = NULL; + m_szGrp[TLK_STOP] = "SC_STOP"; + m_szGrp[TLK_NOSHOOT] = NULL; + m_szGrp[TLK_HELLO] = "SH_HELLO"; + + m_szGrp[TLK_PLHURT1] = "!SC_CUREA"; + m_szGrp[TLK_PLHURT2] = "!SC_CUREB"; + m_szGrp[TLK_PLHURT3] = "!SC_CUREC"; + + m_szGrp[TLK_PHELLO] = "SH_PHELLO"; + m_szGrp[TLK_PIDLE] = "SH_IDLE"; + m_szGrp[TLK_PQUESTION] = "CIV_PQUEST"; + m_szGrp[TLK_SMELL] = "SC_SMELL"; + + m_szGrp[TLK_WOUND] = "SC_WOUND"; + m_szGrp[TLK_MORTAL] = "SC_MORTAL"; + } + + // always this pitch... + m_voicePitch = 95; +} + +int CSirHenry :: 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 Sir Henry... + return CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); +} + +void CSirHenry::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + CTalkMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, 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 CSirHenry :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + +//========================================================= +// PainSound +//========================================================= +void CSirHenry :: 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 CSirHenry :: DeathSound ( void ) +{ + PainSound(); +} + + +void CSirHenry::Killed( entvars_t *pevAttacker, int iGib ) +{ + ClearBeams( ); + SetUse( NULL ); + CTalkMonster::Killed( pevAttacker, iGib ); +} + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CSirHenry :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if ( m_iHolding == GUN_KNIFE ) + {// throw a gun if the cultist has one + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity* pGun = DropItem( "weapon_knife", 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 ); + } + + int bg = GetBodygroup(BODY_GROUP); + m_iHolding = GUN_NONE; + // set the correct body group for what Sir Henry is holding + pev->body = NUM_BODY_GROUPS * m_iHolding + bg; + } + + CTalkMonster :: GibMonster(); +} + +void CSirHenry :: 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* CSirHenry :: 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 Sir Henry will talk + // when 'used' + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slSirHenryFaceTarget; // override this for different target face behavior + else + return psched; + + case SCHED_TARGET_CHASE: + return slSirHenryFollow; + + case SCHED_CANT_FOLLOW: + return slSirHenryStopFollowing; + + case SCHED_PANIC: + return slSirHenryPanic; + + case SCHED_TARGET_CHASE_SCARED: + //return slSirHenryFollowScared; + return slSirHenryFollow; + + case SCHED_TARGET_FACE_SCARED: + return slSirHenryFaceTargetScared; + + case SCHED_IDLE_STAND: + // call base class default so that Sir Henry will talk + // when standing during idle + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slIdleSirHenryStand; + else + return psched; + + case SCHED_HIDE: + return slSirHenryHide; + + case SCHED_STARTLE: + return slSirHenryStartle; + + case SCHED_FEAR: + return slSirHenryFear; + + case SCHED_FAIL: + if (GetBodygroup(BODY_GROUP) == BODY_CULTIST) + { + if (HasConditions( bits_COND_CAN_MELEE_ATTACK1 )) + { + return CTalkMonster :: GetScheduleOfType( SCHED_MELEE_ATTACK1 ); ; + } + } + else + { + return CTalkMonster::GetScheduleOfType( Type ); + } + break; + case SCHED_RANGE_ATTACK1: + if (GetBodygroup(BODY_GROUP) == BODY_CULTIST) + { + return slSirHenryAttack1; + } + else + { + return CTalkMonster::GetScheduleOfType( Type ); + } + case SCHED_RANGE_ATTACK2: + if (GetBodygroup(BODY_GROUP) == BODY_CULTIST) + { + return slSirHenryAttack1; + } + else + { + return CTalkMonster::GetScheduleOfType( Type ); + } + } + + return CTalkMonster::GetScheduleOfType( Type ); +} + +Schedule_t *CSirHenry :: GetSchedule ( void ) +{ + ClearBeams( ); + + // 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 ( 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) ) + { + 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 ( 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, Sir Henry 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_TAKE_COVER_FROM_ENEMY ); // React to something scary + } + + //return GetScheduleOfType( SCHED_TARGET_FACE_SCARED ); // face and follow, but I'm scared! + return GetScheduleOfType( SCHED_TARGET_FACE ); // 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 (GetBodygroup(BODY_GROUP) == BODY_CULTIST) + { + if ( HasConditions( bits_COND_ENEMY_DEAD ) ) + { + // call base class, all code to handle dead enemies is centralized there. + return CBaseMonster :: GetSchedule(); + } + } + else + { + if ( HasConditions( bits_COND_NEW_ENEMY ) ) + //return slSirHenryFear; // Point and scream! + return slSirHenryCover; // Point and scream! + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + return slSirHenryCover; // Take Cover + + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + return slTakeCoverFromBestSound; // Cower and panic from the scary sound! + + return slSirHenryCover; // Run & Cower + } + break; + } + + return CTalkMonster::GetSchedule(); +} + +MONSTERSTATE CSirHenry :: 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_IdealMonsterState = MONSTERSTATE_COMBAT; + return m_IdealMonsterState; + } + + } + } + break; + } + + return CTalkMonster::GetIdealState(); +} + + +int CSirHenry::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 1, 2, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + + +//========================================================= +// ArmBeam - small beam from arm to nearby geometry +//========================================================= + +void CSirHenry :: ArmBeam( int side ) +{ + TraceResult tr; + float flDist = 1.0; + + if (m_iBeams >= SIRHENRY_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 CSirHenry :: 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 CSirHenry :: WackBeam( int side, CBaseEntity *pEntity ) +{ + Vector vecDest; + float flDist = 1.0; + + if (m_iBeams >= SIRHENRY_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++; +} + +void CSirHenry :: ZapBeam( int side ) +{ + Vector vecSrc, vecAim; + TraceResult tr; + CBaseEntity *pEntity; + + if (m_iBeams >= SIRHENRY_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 ) ); +} + +//========================================================= +// GetGunPosition return the end of the barrel +//========================================================= + +Vector CSirHenry :: GetGunPosition( ) +{ + return pev->origin + Vector( 0, 0, 24 ); +} + +//========================================================= +// CheckRangeAttack1 - normal beam attack +//========================================================= +BOOL CSirHenry :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if (m_iHolding != GUN_KNIFE) + { + return FALSE; + } + + 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 - normal beam attack +//========================================================= +BOOL CSirHenry :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if (m_flNextAttack > gpGlobals->time) + { + return FALSE; + } + + return CTalkMonster::CheckRangeAttack1( flDot, flDist ); +} + +//========================================================= +// CheckRangeAttack2 - check bravery and try to resurect dead comrades +//========================================================= +BOOL CSirHenry :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; +} + +//========================================================= +// ClearBeams - remove all beams +//========================================================= +void CSirHenry :: ClearBeams( ) +{ + for (int i = 0; i < SIRHENRY_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" ); +} + +//========================================================= +// Dead SirHenry PROP +//========================================================= + +char *CDeadSirHenry::m_szPoses[] = { "lying_on_back", "lying_on_stomach", "dead_sitting", "dead_hang", "dead_table1", "dead_table2", "dead_table3" }; + +void CDeadSirHenry::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_sirhenry_dead, CDeadSirHenry ); + +// +// ********** DeadSirHenry SPAWN ********** +// +void CDeadSirHenry :: Spawn( ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/SirHenry.mdl"); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/SirHenry.mdl"); + + pev->effects = 0; + pev->sequence = 0; + // Corpses have less health + pev->health = 8;//gSkillData.SirHenryHealth; + + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead Sir Henry with bad pose\n" ); + } + + // pev->skin += 2; // use bloody skin -- UNDONE: Turn this back on when we have a bloody skin again! + MonsterInitDead(); +} + + +//========================================================= +// Sitting SirHenry PROP +//========================================================= + + +LINK_ENTITY_TO_CLASS( monster_sitting_sirhenry, CSittingSirHenry ); +TYPEDESCRIPTION CSittingSirHenry::m_SaveData[] = +{ + // Don't need to save/restore m_baseSequence (recalced) + DEFINE_FIELD( CSittingSirHenry, m_headTurn, FIELD_INTEGER ), + DEFINE_FIELD( CSittingSirHenry, m_flResponseDelay, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CSittingSirHenry, CSirHenry ); + +// animation sequence aliases +typedef enum +{ +SITTING_ANIM_sitlookleft, +SITTING_ANIM_sitlookright, +SITTING_ANIM_sitscared, +SITTING_ANIM_sitting2, +SITTING_ANIM_sitting3 +} SITTING_ANIM; + + +#define SF_SITTINGSCI_POSTDISASTER 1024 + +// +// ********** SirHenry SPAWN ********** +// +void CSittingSirHenry :: Spawn( ) +{ + PRECACHE_MODEL("models/SirHenry.mdl"); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/SirHenry.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; + + if (!FBitSet(pev->spawnflags, SF_SITTINGSCI_POSTDISASTER)) //LRC- allow a sitter to be postdisaster. + SetBits(pev->spawnflags, SF_MONSTER_PREDISASTER); // predisaster only! + + m_baseSequence = LookupSequence( "sitlookleft" ); + pev->sequence = m_baseSequence + RANDOM_LONG(0,4); + ResetSequenceInfo( ); + + SetThink (SittingThink); + SetNextThink( 0.1 ); + + DROP_TO_FLOOR ( ENT(pev) ); +} + +void CSittingSirHenry :: Precache( void ) +{ + m_baseSequence = LookupSequence( "sitlookleft" ); + TalkInit(); +} + +//========================================================= +// ID as a passive human +//========================================================= +int CSittingSirHenry :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_PASSIVE; +} + + +int CSittingSirHenry::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 2, 1, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + + + +//========================================================= +// sit, do stuff +//========================================================= +void CSittingSirHenry :: 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 ); + } + SetNextThink( 0.1 ); +} + +// prepare sitting Sir Henry to answer a question +void CSittingSirHenry :: 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 CSittingSirHenry :: 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/cthulhu/Snake.cpp b/dlls/cthulhu/Snake.cpp new file mode 100755 index 00000000..02bb9521 --- /dev/null +++ b/dlls/cthulhu/Snake.cpp @@ -0,0 +1,249 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Snake +//========================================================= + +// 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 SNAKE_AE_ATTACK 0x01 + +#define SNAKE_FLINCH_DELAY 2 // at most one flinch every n secs + + +#include "Snake.h" + + +LINK_ENTITY_TO_CLASS( monster_snake, CSnake ); + +TYPEDESCRIPTION CSnake::m_SaveData[] = +{ + DEFINE_FIELD( CSnake, m_iszGibModel, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CSnake, CBaseMonster ); + +const char *CSnake::pAttackSounds[] = +{ + "snake/snake_attack1.wav", + "snake/snake_attack2.wav", +}; + +const char *CSnake::pIdleSounds[] = +{ + "snake/snake_idle1.wav", + "snake/snake_idle2.wav", +}; + +const char *CSnake::pPainSounds[] = +{ + "snake/snake_pain1.wav", + "snake/snake_pain2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CSnake :: Classify ( void ) +{ + // snakes are servants of great old ones...so treat them like aliens....if they are not "owned" by anyone else... + if (mpOwner == NULL) + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; + + return mpOwner->Classify(); +} + +int CSnake::IRelationship( CBaseEntity *pTarget ) +{ + return CBaseMonster::IRelationship( pTarget ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CSnake :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + + pev->yaw_speed = ys; +} + +int CSnake :: 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 CSnake :: 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 CSnake :: 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 CSnake :: 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 CSnake :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SNAKE_AE_ATTACK: + { + pev->size.z += 48; + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.snakeDmgBite, DMG_POISON ); + pev->size.z -= 48; + + if (RANDOM_LONG(0,1)) + AttackSound(); + + // can only bite once per second + m_fNextBiteTime = gpGlobals->time + 1.0; + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CSnake :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/snake.mdl"); + UTIL_SetSize( pev, Vector(-16,-16,0), Vector(16,16,20) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = gSkillData.snakeHealth; + 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_MELEE_ATTACK1 | bits_CAP_SWIM; + m_fNextBiteTime = 0.0; + mpOwner = NULL; + + MonsterInit(); +} + +void CSnake :: SetOwner ( CBaseEntity* pOwner ) +{ + mpOwner = pOwner; +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CSnake :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/snake.mdl"); + m_iszGibModel = ALLOC_STRING("models/snakegibs.mdl"); + PRECACHE_MODEL( "models/snakegibs.mdl" ); //LRC + + 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( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +BOOL CSnake::CheckMeleeAttack1 ( float flDot, float flDist ) +{ + // are we in range and other conditions, plus have we waited long enough + // since the last bite + return (CBaseMonster::CheckMeleeAttack1(flDot,flDist) && (m_fNextBiteTime <= gpGlobals->time)); +} + +BOOL CSnake::CheckMeleeAttack2 ( float flDot, float flDist ) +{ + // are we in range and other conditions, plus have we waited long enough + // since the last bite + return (CBaseMonster::CheckMeleeAttack2(flDot,flDist) && (m_fNextBiteTime <= gpGlobals->time)); +} + +int CSnake::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { + 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 + SNAKE_FLINCH_DELAY; + } + + return iIgnore; + +} \ No newline at end of file diff --git a/dlls/cthulhu/Snake.h b/dlls/cthulhu/Snake.h new file mode 100755 index 00000000..d80aab61 --- /dev/null +++ b/dlls/cthulhu/Snake.h @@ -0,0 +1,49 @@ + +#ifndef SNAKE_H +#define SNAKE_H + +class CSnake : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int IgnoreConditions ( void ); + int IRelationship( CBaseEntity *pTarget ); + void SetOwner( CBaseEntity* pOwner ); + + float m_flNextFlinch; + + void AttackSound( void ); + void IdleSound( void ); + void PainSound( void ); + + static const char *pAttackSounds[]; + static const char *pIdleSounds[]; + static const char *pPainSounds[]; + + // No range attacks + BOOL CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } + BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckMeleeAttack2 ( float flDot, float flDist ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + virtual int HasCustomGibs( void ) { return m_iszGibModel; } + + int m_iszGibModel; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +protected: + + float m_fNextBiteTime; + CBaseEntity* mpOwner; +}; + + +#endif diff --git a/dlls/cthulhu/Spiral.cpp b/dlls/cthulhu/Spiral.cpp new file mode 100755 index 00000000..92f9f837 --- /dev/null +++ b/dlls/cthulhu/Spiral.cpp @@ -0,0 +1,105 @@ + +#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" +#include "scripted.h" + +#include "spiral.h" + +void SpiralStreakSplash( 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(); +} + + + +LINK_ENTITY_TO_CLASS( streak_spiral, CSpiral ); + +void CSpiral::Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + SetNextThink( 0 ); + 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->m_fNextThink; + 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(); + + SpiralStreakSplash( 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; + } + + SetNextThink( 0 ); + + if ( pev->health >= pev->speed ) + UTIL_Remove( this ); +} + + + diff --git a/dlls/cthulhu/Spiral.h b/dlls/cthulhu/Spiral.h new file mode 100755 index 00000000..54d97ed2 --- /dev/null +++ b/dlls/cthulhu/Spiral.h @@ -0,0 +1,16 @@ + +#ifndef SPIRAL_H +#define SPIRAL_H + +// 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 ); +}; + +#endif + diff --git a/dlls/cthulhu/Stukabat.cpp b/dlls/cthulhu/Stukabat.cpp new file mode 100755 index 00000000..b7e62955 --- /dev/null +++ b/dlls/cthulhu/Stukabat.cpp @@ -0,0 +1,1952 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 "schedule.h" +#include "flyingmonster.h" +#include "nodes.h" +#include "soundent.h" +#include "animation.h" +#include "effects.h" +#include "weapons.h" +#include "stukagrenade.h" + +#include "Stukabat.h" + + +LINK_ENTITY_TO_CLASS( monster_stukabat, CStukabat ); +LINK_ENTITY_TO_CLASS( info_node_stukabat, CStukabatNode ); + + +//========================================================= +// stukabat nodes +//========================================================= + + +TYPEDESCRIPTION CStukabatNode::m_SaveData[] = +{ + DEFINE_FIELD( CStukabatNode, mbInUse, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CStukabatNode, CBaseEntity ); + +//========================================================= +// nodes start out as ents in the world. As they are spawned, +// the node info is recorded then the ents are discarded. +//========================================================= +void CStukabatNode :: KeyValue( KeyValueData *pkvd ) +{ + CBaseEntity::KeyValue( pkvd ); +} + +//========================================================= +//========================================================= +void CStukabatNode :: Spawn( void ) +{ + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT;// always solid_not + mbInUse = false; + pev->classname = MAKE_STRING("info_node_stukabat"); +} + + +//========================================================= +// stukabat +//========================================================= + +#define SEARCH_RETRY 16 + +#define STUKABAT_SPEED 200 + +extern CGraph WorldGraph; + + +#define STUKABAT_AE_BOMB 1 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + + +TYPEDESCRIPTION CStukabat::m_SaveData[] = +{ + DEFINE_FIELD( CStukabat, m_SaveVelocity, FIELD_VECTOR ), + DEFINE_FIELD( CStukabat, m_idealDist, FIELD_FLOAT ), + DEFINE_FIELD( CStukabat, m_flNextMeleeAttack, FIELD_FLOAT ), + DEFINE_FIELD( CStukabat, m_flNextRangedAttack, FIELD_FLOAT ), + //DEFINE_FIELD( CStukabat, m_bOnAttack, FIELD_BOOLEAN ), + DEFINE_FIELD( CStukabat, m_flMaxSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CStukabat, m_flMinSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CStukabat, m_flMaxDist, FIELD_FLOAT ), + DEFINE_FIELD( CStukabat, m_flNextAlert, FIELD_TIME ), + DEFINE_FIELD( CStukabat, m_flLandTime, FIELD_TIME ), + DEFINE_FIELD( CStukabat, m_iMode, FIELD_INTEGER ), + DEFINE_FIELD( CStukabat, meMedium, FIELD_INTEGER ), + DEFINE_FIELD( CStukabat, mpCeilingNode, FIELD_CLASSPTR ), +}; + +IMPLEMENT_SAVERESTORE( CStukabat, CFlyingMonster ); + + +const char *CStukabat::pIdleSounds[] = +{ + "stukabat/stukabat_idle1.wav", + "stukabat/stukabat_idle2.wav", + "stukabat/stukabat_idle3.wav", + "stukabat/stukabat_idle4.wav", +}; + +const char *CStukabat::pAlertSounds[] = +{ + "stukabat/ng_alert1.wav", +}; + +const char *CStukabat::pAttackSounds[] = +{ + "stukabat/ng_attack1.wav", +}; + +const char *CStukabat::pSlashSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CStukabat::pPainSounds[] = +{ + "stukabat/ng_pain1.wav", + "stukabat/ng_pain2.wav", + "stukabat/ng_pain3.wav", +}; + +const char *CStukabat::pDieSounds[] = +{ + "stukabat/ng_die1.wav", + "stukabat/ng_die2.wav", +}; + +#define EMIT_STUKABAT_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 CStukabat :: IdleSound( void ) +{ + EMIT_STUKABAT_SOUND( CHAN_VOICE, pIdleSounds ); +} + +void CStukabat :: AlertSound( void ) +{ + EMIT_STUKABAT_SOUND( CHAN_VOICE, pAlertSounds ); +} + +void CStukabat :: AttackSound( void ) +{ + EMIT_STUKABAT_SOUND( CHAN_VOICE, pAttackSounds ); +} + +void CStukabat :: SlashSound( void ) +{ + EMIT_STUKABAT_SOUND( CHAN_WEAPON, pSlashSounds ); +} + +void CStukabat :: DeathSound( void ) +{ + EMIT_STUKABAT_SOUND( CHAN_VOICE, pDieSounds ); +} + +void CStukabat :: PainSound( void ) +{ + EMIT_STUKABAT_SOUND( CHAN_VOICE, pPainSounds ); +} + +//========================================================= +// monster-specific tasks and states +//========================================================= +enum +{ + TASK_STUKABAT_CIRCLE_ENEMY = LAST_COMMON_TASK + 1, + TASK_STUKABAT_FLY, + TASK_STUKABAT_MOVETOGROUND, + TASK_STUKABAT_LANDGROUND, + TASK_STUKABAT_TAKEOFFGROUND, + TASK_STUKABAT_FIND_NODE, + TASK_STUKABAT_LANDCEILING, + TASK_STUKABAT_TAKEOFFCEILING, + TASK_STUKABAT_BOMB_OK, + TASK_STUKABAT_FALL +}; + +// Note: tried hovering, didn't like it + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +static Task_t tlFlyAround[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_FLY }, + { TASK_STUKABAT_FLY, 0.0 }, +}; + +static Schedule_t slFlyAround[] = +{ + { + tlFlyAround, + ARRAYSIZE(tlFlyAround), + 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, + "FlyAround" + }, +}; + +static Task_t tlFlyAgitated[] = +{ + { TASK_STOP_MOVING, (float) 0 }, + { TASK_SET_ACTIVITY, (float)ACT_RUN }, + { TASK_WAIT, (float)2.0 }, +}; + +static Schedule_t slFlyAgitated[] = +{ + { + tlFlyAgitated, + ARRAYSIZE(tlFlyAgitated), + 0, + 0, + "FlyAgitated" + }, +}; + + +static Task_t tlCircleEnemy[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_FLY }, + { TASK_STUKABAT_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 tlStukabatTwitchDie[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SOUND_DIE, (float)0 }, + { TASK_STUKABAT_FALL, (float)0 }, + { TASK_DIE, (float)0 }, +}; + +Schedule_t slStukabatTwitchDie[] = +{ + { + tlStukabatTwitchDie, + ARRAYSIZE( tlStukabatTwitchDie ), + 0, + 0, + "Die" + }, +}; + +static Task_t tlLandGround[] = +{ + { TASK_STUKABAT_MOVETOGROUND, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_LAND }, + { TASK_STUKABAT_LANDGROUND, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, +}; + +static Schedule_t slLandGround[] = +{ + { + tlLandGround, + ARRAYSIZE(tlLandGround), + 0, + 0, + "LandGround" + }, +}; + +static Task_t tlTakeOffGround[] = +{ + { TASK_SET_ACTIVITY, (float)ACT_LEAP }, + { TASK_STUKABAT_TAKEOFFGROUND, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_FLY }, +}; + +static Schedule_t slTakeOffGround[] = +{ + { + tlTakeOffGround, + ARRAYSIZE(tlTakeOffGround), + 0, + 0, + "TakeOffGround" + }, +}; + +static Task_t tlLandCeiling[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_FAIL }, + { TASK_STUKABAT_FIND_NODE, 0 }, + { TASK_GET_PATH_TO_TARGET, (float)0 }, + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_STAND }, + { TASK_STUKABAT_LANDCEILING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, +}; + +static Schedule_t slLandCeiling[] = +{ + { + tlLandCeiling, + ARRAYSIZE(tlLandCeiling), + 0, + 0, + "LandCeiling" + }, +}; + +static Task_t tlTakeOffCeiling[] = +{ + { TASK_STUKABAT_TAKEOFFCEILING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_FLY }, +}; + +static Schedule_t slTakeOffCeiling[] = +{ + { + tlTakeOffCeiling, + ARRAYSIZE(tlTakeOffCeiling), + 0, + 0, + "TakeOffCeiling" + }, +}; + +static Task_t tlBombAttack[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_FAIL }, + { TASK_GET_PATH_TO_SPOT, (float)0 }, // todo: overwrite this... + { TASK_WALK_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_STUKABAT_BOMB_OK, (float)0 }, // checks 2D length <= 128, z length <= 1024 && > 128, path is unobstructed + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +static Schedule_t slBombAttack[] = +{ + { + tlBombAttack, + ARRAYSIZE(tlBombAttack), + 0, + 0, + "BombAttack" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES(CStukabat) +{ + slFlyAround, + slFlyAgitated, + slCircleEnemy, + slStukabatTwitchDie, + slLandGround, + slTakeOffGround, + slLandCeiling, + slTakeOffCeiling, + slBombAttack, +}; + +IMPLEMENT_CUSTOM_SCHEDULES(CStukabat, CFlyingMonster); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CStukabat :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CStukabat :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + if ( flDist <= 64 && flDot >= 0.7 && m_flNextMeleeAttack <= gpGlobals->time ) + { + return TRUE; + } + return FALSE; +} + +/* +void CStukabat::SlashTouch( CBaseEntity *pOther ) +{ + // slash if we hit who we want to eat + if ( pOther == m_hEnemy ) + { + m_flNextMeleeAttack = gpGlobals->time; + m_bOnAttack = TRUE; + } +} +*/ + +void CStukabat::CombatUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + //if ( !ShouldToggle( useType, m_bOnAttack ) ) + if ( !ShouldToggle( useType ) ) + return; + + /* + if (m_bOnAttack) + { + m_bOnAttack = 0; + } + else + { + m_bOnAttack = 1; + } + */ +} + +//========================================================= +// CheckRangeAttack1 - Fly in for a chomp +// +//========================================================= +BOOL CStukabat :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + //if ( flDot > -0.7 && (m_bOnAttack || ( flDist <= 384 && m_idealDist <= 384))) + if ( flDot > -0.7 && flDist <= 2048 && m_flNextRangedAttack <= gpGlobals->time) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 - Fly in for a chomp +// +//========================================================= +BOOL CStukabat :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + //if ( flDot > -0.7 && (m_bOnAttack || ( flDist <= 384 && m_idealDist <= 384))) + if ( flDot > -0.7 && ( flDist <= 384 && m_idealDist <= 384) && m_flNextMeleeAttack <= gpGlobals->time) + { + return TRUE; + } + + return FALSE; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CStukabat :: SetYawSpeed ( void ) +{ + pev->yaw_speed = 100; +} + + + +//========================================================= +// Killed - overrides CFlyingMonster. +// +void CStukabat :: Killed( entvars_t *pevAttacker, int iGib ) +{ + pev->velocity = Vector(0,0,0); + pev->gravity = 1.0; + pev->angles.x = 0; + pev->deadflag = DEAD_DYING; + + //CBaseMonster::Killed( pevAttacker, iGib ); + CFlyingMonster::Killed( pevAttacker, iGib ); + pev->movetype = MOVETYPE_TOSS; +} + +void CStukabat::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. +} + +void CStukabat :: Slash( void ) +{ + if (m_hEnemy != NULL && FVisible( m_hEnemy )) + { + CBaseEntity *pHurt = m_hEnemy; + + 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 * 100; + //pHurt->TakeDamage( pev, pev, gSkillData.stukabatDmgSlash, DMG_SLASH ); + pHurt->TakeDamage( pev, pev, gSkillData.nightgauntDmgSlash, DMG_SLASH ); + } + } + SlashSound(); +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CStukabat :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case STUKABAT_AE_BOMB: + { + Vector vecAngle; + UTIL_MakeVectors(vecAngle); + vecAngle.Normalize(); + vecAngle = 20.0 * vecAngle; + + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "weapons/glauncher.wav", 0.8, ATTN_NORM); + + // todo: need to set hackedgunposition + CStukaGrenade::ShootContact( pev, GetGunPosition(), vecAngle ); + + m_flNextRangedAttack = gpGlobals->time + RANDOM_FLOAT(7.0, 10.0); + } + break; + default: + CFlyingMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CStukabat :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/stukabat.mdl"); + UTIL_SetSize( pev, Vector( -16, -16, -2 ), Vector( 16, 16, 16 ) ); + + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_FLY; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) +// pev->health = gSkillData.stukabatHealth; +// pev->health = gSkillData.zombieHealth; + pev->health = 5; + pev->view_ofs = Vector ( 0, 0, 16 ); + m_flFieldOfView = VIEW_FIELD_WIDE; + m_MonsterState = MONSTERSTATE_NONE; + SetBits(pev->flags, FL_FLY); + SetFlyingSpeed( STUKABAT_SPEED ); + SetFlyingMomentum( 2.5 ); // Set momentum constant + + m_afCapability = bits_CAP_RANGE_ATTACK1 | bits_CAP_RANGE_ATTACK2 | bits_CAP_FLY; + + meMedium = SB_INAIR; + m_iMode = STUKABAT_IDLE; + + MonsterInit(); + + // PhilG: hack to allow CheckMeleeAttack1 to be used + m_afCapability |= bits_CAP_MELEE_ATTACK1; + + //SetTouch( SlashTouch ); + m_flNextRangedAttack = gpGlobals->time; + m_flNextMeleeAttack = gpGlobals->time; + SetTouch( NULL ); + SetUse( CombatUse ); + + m_idealDist = 384; + m_flMinSpeed = 80; + m_flMaxSpeed = 300; + m_flMaxDist = 384; + + m_flLandTime = gpGlobals->time; + + Vector Forward; + UTIL_MakeVectorsPrivate(pev->angles, Forward, 0, 0); + pev->velocity = m_flightSpeed * Forward.Normalize(); + m_SaveVelocity = pev->velocity; + + mpCeilingNode = NULL; +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CStukabat :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/stukabat.mdl"); + + PRECACHE_SOUND_ARRAY( pIdleSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pSlashSounds ); + PRECACHE_SOUND_ARRAY( pDieSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); + + PRECACHE_SOUND("weapons/glauncher.wav"); +} + +void CStukabat :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_RANGE_ATTACK2: + // get aimable sequence + iSequence = LookupSequence( "Attack_claw" ); + default: + iSequence = LookupActivity ( NewActivity ); + break; + } + + // 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) + } + + 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; +} + +//========================================================= +// GetSchedule +//========================================================= +Schedule_t* CStukabat::GetSchedule() +{ + // ALERT( at_console, "GetSchedule( )\n" ); + switch(m_MonsterState) + { + case MONSTERSTATE_IDLE: + m_flightSpeed = STUKABAT_SPEED / 4; + switch (meMedium) + { + case SB_ONGROUND: + return GetScheduleOfType( SCHED_IDLE_STAND ); + break; + case SB_ONCEILING: + return GetScheduleOfType( SCHED_IDLE_STAND ); + break; + case SB_INAIR: + default: + return GetScheduleOfType( SCHED_IDLE_WALK ); + break; + } + + case MONSTERSTATE_ALERT: + m_flightSpeed = STUKABAT_SPEED - 50; + return GetScheduleOfType( SCHED_IDLE_WALK ); + + case MONSTERSTATE_COMBAT: + switch (meMedium) + { + case SB_INAIR: + { + m_flMaxSpeed = STUKABAT_SPEED + 100; + + // chase them down and eat them + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + // can we claw? (which is defined as a range attack for some unknown reason) + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK2 ) ) + { + if ( HasConditions( bits_COND_CAN_MELEE_ATTACK1 ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK2 ); + } + else + { + 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 ); + } + break; + case SB_ONGROUND: + case SB_ONCEILING: + default: + return GetScheduleOfType( SCHED_COMBAT_STAND ); + break; + } + } + + return CFlyingMonster :: GetSchedule(); +} + + +//========================================================= +//========================================================= +Schedule_t* CStukabat :: GetScheduleOfType ( int Type ) +{ + int iRand; + + // ALERT( at_console, "GetScheduleOfType( %d ) %d\n", Type, m_bOnAttack ); + switch ( Type ) + { + case SCHED_IDLE_STAND: + case SCHED_IDLE_WALK: + switch (meMedium) + { + case SB_ONGROUND: + // randomly want to take off, if we have been on ground for at least 10 secs + if (RANDOM_LONG(0,30) == 0 && gpGlobals->time > m_flLandTime + 10.0) + { + return slTakeOffGround; + } + return CBaseMonster :: GetScheduleOfType( Type ); + break; + case SB_ONCEILING: + // randomly want to take off, if we have been on ground for at least 10 secs + if (RANDOM_LONG(0,10) == 0 && gpGlobals->time > m_flLandTime + 10.0) + { + return slTakeOffCeiling; + } + return CBaseMonster :: GetScheduleOfType( Type ); + break; + case SB_INAIR: + default: + // randomly want to land if we have been in the air at least 10 secs + if (gpGlobals->time > m_flLandTime + 10.0) + { + iRand = RANDOM_LONG(0,10); + if (iRand == 1) + { + return slLandGround; + } + else if (iRand < 4) + { + return slLandCeiling; + } + } + return slFlyAround; + break; + } + case SCHED_STANDOFF: + return slCircleEnemy; + case SCHED_COMBAT_STAND: + switch (meMedium) + { + case SB_ONGROUND: + return slTakeOffGround; + break; + case SB_ONCEILING: + return slTakeOffCeiling; + break; + default: + case SB_INAIR: + // this should never happen + return slFlyAround; + break; + } + break; + case SCHED_RANGE_ATTACK1: + return slBombAttack; + case SCHED_FAIL: + return slFlyAgitated; + case SCHED_DIE: + return slStukabatTwitchDie; + 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 CStukabat::StartTask(Task_t *pTask) +{ + switch (pTask->iTask) + { + case TASK_STUKABAT_CIRCLE_ENEMY: + break; + case TASK_STUKABAT_FLY: + break; + case TASK_STUKABAT_MOVETOGROUND: + // angle downwards + //if (pev->angles.x > -60) pev->angles.x = -60; + break; + case TASK_STUKABAT_LANDGROUND: + SetActivity(ACT_LAND); + LandGround(); + break; + case TASK_STUKABAT_TAKEOFFGROUND: + SetActivity(ACT_LEAP); + TakeOffGround(); + break; + case TASK_STUKABAT_FIND_NODE: + FindNode(); + m_hTargetEnt = mpCeilingNode; + m_movementActivity = ACT_FLY; + break; + case TASK_STUKABAT_LANDCEILING: + // stop moving + m_flightSpeed = 0.0; + m_flGroundSpeed = 0.0; + pev->velocity = Vector(0,0,0); + + SetActivity(ACT_STAND); + LandCeiling(); + break; + case TASK_STUKABAT_TAKEOFFCEILING: + TakeOffGround(); + break; + case TASK_STUKABAT_BOMB_OK: + { + bool bOK = false; + // do we have an enemy + if (m_hEnemy != NULL) + { + //CBaseEntity* pEnemy = CBaseEntity::Instance(m_hEnemy); + CBaseEntity* pEnemy = (m_hEnemy); + if (pEnemy && pEnemy->IsAlive()) + { + // are we at least 128 (2D) away from enemy + if ((pev->origin - pEnemy->pev->origin).Length2D() <= 128) + { + float dz = pev->origin.z - pEnemy->pev->origin.z; + // are we a good height above + if (dz > 128 && dz <= 1024) + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, pEnemy->pev->origin, ignore_monsters, NULL, &tr ); + // are we obstructed (at least before 95% of the distance + if (tr.flFraction > 0.95) + { + bOK = true; + } + } + } + } + } + if (bOK) + { + TaskComplete(); + } + else + { + TaskFail(); + } + } + 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_STUKABAT_FALL: + m_movementActivity = ACT_FALL; + //m_IdealActivity = ACT_FALL; + SetActivity ( ACT_FALL ); + //SetSequenceByName( "fall_cycler" ); + break; + + // overridden task + case TASK_WALK_PATH: + { + switch (meMedium) + { + case SB_ONGROUND: + m_movementActivity = ACT_WALK; + break; + case SB_ONCEILING: + case SB_INAIR: + default: + m_movementActivity = ACT_FLY; + break; + } + TaskComplete(); + break; + } + + case TASK_GET_PATH_TO_SPOT: + { + // get the target location + if (m_hEnemy == NULL) + { + TaskFail(); + break; + } + + //CBaseEntity* pEnemy = CBaseEntity::Instance(m_hEnemy); + CBaseEntity* pEnemy = (m_hEnemy); + + TraceResult tr; + + // trace directly up for 1024 + Vector up = pEnemy->pev->origin; + up.z += 1024; + + UTIL_TraceLine( pEnemy->pev->origin, up, ignore_monsters, NULL, &tr ); + + // choose highest point (minus a bit) + m_vecMoveGoal = tr.vecEndPos; + m_vecMoveGoal.z -= 64; + + // will we be too close to his head? + if ((pEnemy->pev->origin - m_vecMoveGoal).Length() < 128) + { + TaskFail(); + break; + } + + if ( BuildRoute ( m_vecMoveGoal, bits_MF_TO_LOCATION, NULL ) ) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToSpot failed!!\n" ); + TaskFail(); + } + } + break; + + default: + CFlyingMonster::StartTask(pTask); + break; + } +} + +void CStukabat :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_STUKABAT_CIRCLE_ENEMY: + if (m_hEnemy == NULL) + { + TaskComplete( ); + } + else if (FVisible( m_hEnemy )) + { + Vector vecFrom = m_hEnemy->EyePosition( ); + + Vector vecDelta = (pev->origin - vecFrom).Normalize( ); + Vector vecFly = CrossProduct( vecDelta, Vector( 0, 0, 1 ) ).Normalize( ); + + if (DotProduct( vecFly, m_SaveVelocity ) < 0) + vecFly = vecFly * -1.0; + + Vector vecPos = vecFrom + vecDelta * m_idealDist + vecFly * 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 ); + } + + // PhilG: they will circle me forever unless I break it... + if (RANDOM_LONG(0,50) == 1) + { + TaskComplete(); + } + + break; + case TASK_STUKABAT_FLY: + //if (m_fSequenceFinished) + // the sequence loops, so it is very rare that the 'finished' flag is set + // set we randomly finish the task, which will recall the result in the + // getting of a new schedule (possibly the same one again) + if (RANDOM_LONG(0,20) == 1) + { + TaskComplete( ); + } + break; + case TASK_STUKABAT_MOVETOGROUND: + { + // how far above the floor are we? + float fz = FloorZ(pev->origin); + if (pev->origin.z - fz < 6) + { + // we are very close to the floor, so drop the last little bit + DROP_TO_FLOOR(ENT(pev)); + TaskComplete( ); + } + else + { + int iContents = UTIL_PointContents(pev->origin + Vector(0,0,-6)); + if ((iContents == CONTENTS_WATER) || + (iContents == CONTENTS_LAVA) || + (iContents == CONTENTS_SLIME)) + { + TaskFail(); + break; + } + // note: the stukabat origin is 2 units above the bottom of its hull + // hence if we are checking for 6 units (above), we can only safely push down 4 units + if ( CheckLocalMove ( pev->origin, pev->origin + Vector(0,0,-4), NULL, NULL ) == LOCALMOVE_VALID) + { + pev->origin.z -= 4; + } + } + } + break; + case TASK_STUKABAT_LANDGROUND: + if (m_fSequenceFinished) + { + TaskComplete( ); + } + break; + case TASK_STUKABAT_TAKEOFFGROUND: + if (m_fSequenceFinished) + { + TaskComplete( ); + } + break; + case TASK_STUKABAT_FIND_NODE: + if (mpCeilingNode) + { + TaskComplete( ); + } + else + { + TaskFail(); + } + break; + case TASK_STUKABAT_LANDCEILING: + if (m_fSequenceFinished) + { + mpCeilingNode->mbInUse = true; + TaskComplete( ); + } + break; + case TASK_STUKABAT_TAKEOFFCEILING: + { + mpCeilingNode->mbInUse = false; + mpCeilingNode = NULL; + TaskComplete( ); + } + break; + case TASK_RANGE_ATTACK2: + { + MakeIdealYaw ( m_vecEnemyLKP ); + ChangeYaw ( pev->yaw_speed ); + + if ( m_fSequenceFinished ) + { + m_Activity = ACT_RESET; + TaskComplete(); + } + if (gpGlobals->time > m_flNextMeleeAttack) + { + // do the attack + Slash(); + // set the next melee attack time + m_flNextMeleeAttack = gpGlobals->time + RANDOM_FLOAT(3.0,6.0); + } + } + break; + case TASK_STUKABAT_FALL: + if (FBitSet(pev->flags, FL_ONGROUND)) + { + if ( RANDOM_LONG(0,4) == 1 ) + { + m_IdealActivity = ACT_DIEVIOLENT; + } + else + { + m_IdealActivity = ACT_DIESIMPLE; + } + TaskComplete( ); + } + else + { + if (m_fSequenceFinished) + { + SetActivity ( ACT_FALL ); + } + MoveExecute(NULL, Vector(0,0,-1), 0.1); + } + break; + case TASK_DIE: + if ( m_fSequenceFinished ) + { + CFlyingMonster :: RunTask ( pTask ); + + pev->deadflag = DEAD_DEAD; + + TaskComplete( ); + } + break; + + default: + CFlyingMonster :: RunTask ( pTask ); + break; + } +} + + + +float CStukabat::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 CStukabat::FindNode() +{ + mpCeilingNode = NULL; + + Vector vDistance; + const int MAX_NODE = 1000; + int iNode = 0; + CStukabatNode* pNodesInSphere[MAX_NODE]; + CBaseEntity* pNode = UTIL_FindEntityByClassname(NULL, "info_node_stukabat"); + while (pNode) + { + vDistance = pev->origin - pNode->pev->origin; + if (vDistance.Length() <= 1024.0 && !((CStukabatNode*)pNode)->mbInUse) + { + pNodesInSphere[iNode] = (CStukabatNode*)pNode; + iNode++; + } + + pNode = UTIL_FindEntityByClassname(pNode, "info_node_stukabat"); + } + + // no viable nodes found + if (iNode == 0) return; + + // choose a viable node at random + int iRand = RANDOM_LONG(0,iNode-1); + + mpCeilingNode = pNodesInSphere[iRand]; +} + +int CStukabat :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + int iContents = UTIL_PointContents(vecEnd); + if ((iContents == CONTENTS_WATER) || + (iContents == CONTENTS_LAVA) || + (iContents == CONTENTS_SLIME)) + { + return FALSE; + } + + TraceResult tr; + + UTIL_TraceHull( vecStart, vecEnd, dont_ignore_monsters, head_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 - 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) + { + // PhilG + if (gpGlobals->trace_ent) + { + CBaseEntity* pBlocker = CBaseEntity::Instance(gpGlobals->trace_ent); + } + + if ( tr.flFraction < 1.0 && pTarget && pTarget->edict() == gpGlobals->trace_ent ) + return LOCALMOVE_VALID; + return LOCALMOVE_INVALID; + } + + return LOCALMOVE_VALID; +} + +void CStukabat::Move(float flInterval) +{ + if (meMedium == SB_INAIR) + { + CFlyingMonster::Move( flInterval ); + } + else + { + 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 rat to overshoot) + if ( flWaypointDist <= m_flGroundSpeed * flInterval ) + { + // take truncated step and stop + + SetActivity ( ACT_CROUCHIDLE ); + + if ( m_iMode == STUKABAT_SMELL_FOOD ) + { + m_iMode = STUKABAT_EAT; + } + else + { + m_iMode = STUKABAT_IDLE; + } + } + } +} + +float CStukabat::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 CStukabat :: 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 CStukabat::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 CStukabat:: GetStoppedActivity( void ) +{ + //if ( pev->movetype != MOVETYPE_FLY ) // UNDONE: Ground idle here, IDLE may be something else + // return ACT_IDLE; + //return ACT_WALK; + switch (meMedium) + { + case SB_ONGROUND: + return ACT_CROUCHIDLE; + break; + case SB_ONCEILING: + return ACT_IDLE; + break; + case SB_INAIR: + default: + return ACT_FLY; + break; + } +} + +void CStukabat::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + m_SaveVelocity = vecDir * m_flightSpeed; + CFlyingMonster::MoveExecute(pTargetEnt, vecDir, flInterval); +} + + +void CStukabat::MonsterThink ( void ) +{ + float flInterval = 0.1; + + if (!IsAlive() || pev->deadflag == DEAD_DYING) m_iMode = STUKABAT_DEAD; + + if (pev->deadflag == DEAD_NO) + { + if (m_MonsterState != MONSTERSTATE_SCRIPT) + { + switch (meMedium) + { + case SB_ONCEILING: + MonsterThinkOnCeiling(); + break; + case SB_ONGROUND: + MonsterThinkOnGround(); + break; + case SB_INAIR: + default: + MonsterThinkInAir(); + break; + } + } + } + else if (pev->deadflag == DEAD_DYING) + { + if (m_MonsterState != MONSTERSTATE_SCRIPT) + { + SetNextThink( 0.1 );// keep monster thinking. + RunAI(); + DispatchAnimEvents( ); + StudioFrameAdvance( ); + } + } + +} + +void CStukabat::MonsterThinkInAir ( void ) +{ + // we just want to run the activity if we are doing any of the following + // taking off from the ground + // landing (ground or ceiling) + // attacking + if ((m_IdealActivity == ACT_LEAP) || + (m_IdealActivity == ACT_LAND) || + (m_IdealActivity == ACT_STAND)) + { + SetNextThink( 0.1 );// keep monster thinking + RunAI(); + StudioFrameAdvance( ); // animate + return; + } + + CFlyingMonster::MonsterThink( ); + + // if we are not dead, in the air and not attacking + if (m_MonsterState != MONSTERSTATE_DEAD && + meMedium == SB_INAIR && + m_IdealActivity != ACT_RANGE_ATTACK1 && + m_IdealActivity != ACT_RANGE_ATTACK2) + { + Fly(); + } +} + +void CStukabat::MonsterThinkOnCeiling ( void ) +{ + CBaseMonster::MonsterThink( ); +} + +void CStukabat::MonsterThinkOnGround ( void ) +{ + if ( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + SetNextThink( RANDOM_FLOAT(1,1.5) ); + else + SetNextThink( 0.1 );// keep monster thinking + + RunAI(); + + if (m_IdealActivity == ACT_IDLE) + { + pev->sequence = LookupActivity ( ACT_CROUCHIDLE ); + m_Activity = ACT_CROUCHIDLE; + m_IdealActivity = ACT_CROUCHIDLE; + } + if (m_IdealActivity == ACT_CROUCHIDLE && m_fSequenceFinished) + { + SetActivity(ACT_CROUCHIDLE); + } + + float flInterval = StudioFrameAdvance( ); // animate + + // if we are landing, do nothing + if (m_IdealActivity == ACT_LAND) return; + + switch (m_iMode) + { + case STUKABAT_IDLE: + case STUKABAT_EAT: + { + // if not moving, sample environment to see if anything scary is around. Do a radius search 'look' at random. + if ( RANDOM_LONG(0,1) == 1 ) + { + Look( 150 ); + if (HasConditions(bits_COND_SEE_FEAR)) + { + // if see something scary + //ALERT ( at_aiconsole, "Scared\n" ); + Eat( 30 + ( RANDOM_LONG(0,14) ) );// rat will ignore food for 30 to 45 seconds + PickNewDest( STUKABAT_SCARED_BY_ENT ); + SetActivity ( ACT_RUN ); + } + else if ( RANDOM_LONG(0,1) == 1 ) + { + // if rat doesn't see anything, there's still a chance that it will move. (boredom) + //ALERT ( at_aiconsole, "Bored\n" ); + PickNewDest( STUKABAT_BORED ); + SetActivity ( ACT_WALK ); + + if ( m_iMode == STUKABAT_EAT ) + { + // rat 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 == STUKABAT_IDLE ) + { + if ( FShouldEat() ) + { + Listen(); + } + + if ( HasConditions(bits_COND_SMELL_FOOD) ) + { + CSound *pSound; + + pSound = CSoundEnt::SoundPointerForIndex( m_iAudibleList ); + + // rat 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( STUKABAT_SMELL_FOOD ); + SetActivity ( ACT_WALK ); + } + } + else + { + //SetActivity ( ACT_CROUCHIDLE ); + } + } + + break; + } + case STUKABAT_DEAD: + return; + break; + } + if ( m_flGroundSpeed != 0 ) + { + Move( 0.1 ); + } +} + +void CStukabat :: Stop( void ) +{ + //if (!m_bOnAttack) + m_flightSpeed = 80.0; +} + +void CStukabat::Fly( ) +{ + 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 (meMedium == SB_ONGROUND) + //{ + // pev->angles.x = 0; + // SetActivity( ACT_WALK ); + // return; + //} + + //if (m_bOnAttack && m_flightSpeed < m_flMaxSpeed) + if (m_flightSpeed < m_flMaxSpeed) + { + m_flightSpeed += 40; + } + + SetActivity( ACT_FLY ); + + /* + if (m_flightSpeed < 180) + { + SetActivity( ACT_FLY ); + //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 + { + switch (meMedium) + { + case SB_ONGROUND: + //SetActivity( ACT_WALK ); + break; + case SB_ONCEILING: + SetActivity( ACT_WALK ); + break; + default: + SetActivity( ACT_FLY ); + break; + } + //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 ); + } + */ + +#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; + + + 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); + +} + + +void CStukabat::LandGround(void) +{ + ClearBits( pev->flags, FL_FLY ); + meMedium = SB_ONGROUND; + m_afCapability = bits_CAP_RANGE_ATTACK2; + pev->movetype = MOVETYPE_STEP; + pev->angles.x = 0; + //pev->angles.z = 0; + m_flLandTime = gpGlobals->time; +} + +void CStukabat::LandCeiling(void) +{ + meMedium = SB_ONCEILING; + m_afCapability = bits_CAP_FLY; + pev->movetype = MOVETYPE_FLY; + pev->angles.x = 0; + //pev->angles.z = 0; + m_flLandTime = gpGlobals->time; +} + +void CStukabat::TakeOffGround(void) +{ + SetBits( pev->flags, FL_FLY ); + meMedium = SB_INAIR; + m_afCapability = bits_CAP_RANGE_ATTACK2 | bits_CAP_FLY; + pev->movetype = MOVETYPE_FLY; + m_flLandTime = gpGlobals->time; +} + +Vector CStukabat::DoProbe(const Vector &Probe) +{ + Vector WallNormal = Vector(0,0,1); // AIR normal is Straight Up for flying thing. + 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); +} + +//========================================================= +// Picks a new spot for rat to run to.( +//========================================================= +void CStukabat :: PickNewDest ( int iCondition ) +{ + Vector vecNewDir; + Vector vecDest; + float flDist; + + m_iMode = iCondition; + + if ( m_iMode == STUKABAT_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 rat 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 rat 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) ); + } +} + + + +#endif \ No newline at end of file diff --git a/dlls/cthulhu/Stukabat.h b/dlls/cthulhu/Stukabat.h new file mode 100755 index 00000000..0b22b3af --- /dev/null +++ b/dlls/cthulhu/Stukabat.h @@ -0,0 +1,133 @@ + +#ifndef STUKABAT_H +#define STUKABAT_H + +#define STUKABAT_IDLE 0 +#define STUKABAT_BORED 1 +#define STUKABAT_SCARED_BY_ENT 2 +#define STUKABAT_SMELL_FOOD 3 +#define STUKABAT_EAT 4 +#define STUKABAT_DEAD 5 + +//========================================================= +// Nodes at which Stukabats may land. +//========================================================= +class CStukabatNode : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + + bool mbInUse; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; +}; + +//========================================================= +// The Stukabat monster. +//========================================================= +class CStukabat : public CFlyingMonster +{ +public: + enum SB_Medium + { + SB_ONGROUND, + SB_INAIR, + SB_ONCEILING + }; + + 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[]; + + virtual void SetActivity ( Activity NewActivity ); + + 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 SlashTouch( CBaseEntity *pOther ); + void Slash(void); + + void StartTask( Task_t *pTask ); + void RunTask( Task_t *pTask ); + + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ); + + float ChangeYaw( int speed ); + Activity GetStoppedActivity( void ); + + 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 ); + virtual void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + void MonsterThink( void ); + void MonsterThinkInAir( void ); + void MonsterThinkOnGround( void ); + void MonsterThinkOnCeiling( void ); + void Stop( void ); + void Fly( void ); + Vector DoProbe(const Vector &Probe); + + void LandGround (void); + void LandCeiling (void); + void TakeOffGround (void); + void FindNode (void); + + float VectorToPitch( const Vector &vec); + float FlPitchDiff( void ); + float ChangePitch( int speed ); + + Vector m_SaveVelocity; + float m_idealDist; + + float m_flNextRangedAttack; + float m_flNextMeleeAttack; + //BOOL m_bOnAttack; + + float m_flMaxSpeed; + float m_flMinSpeed; + float m_flMaxDist; + + float m_flNextAlert; + float m_flLandTime; + + void PickNewDest ( int iCondition ); + + SB_Medium meMedium; + int m_iMode; + + CStukabatNode* mpCeilingNode; + + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pAttackSounds[]; + static const char *pSlashSounds[]; + static const char *pDieSounds[]; + static const char *pPainSounds[]; + + void IdleSound( void ); + void AlertSound( void ); + void AttackSound( void ); + void SlashSound( void ); + void DeathSound( void ); + void PainSound( void ); +}; + + +#endif + + diff --git a/dlls/cthulhu/SwordCane.cpp b/dlls/cthulhu/SwordCane.cpp new file mode 100755 index 00000000..8c49cdad --- /dev/null +++ b/dlls/cthulhu/SwordCane.cpp @@ -0,0 +1,336 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + + +#define SWORDCANE_BODYHIT_VOLUME 128 +#define SWORDCANE_WALLHIT_VOLUME 512 + +#include "swordcane.h" + +LINK_ENTITY_TO_CLASS( weapon_swordcane, CSwordcane ); + + + +enum swordcane_e { + SWORDCANE_IDLE = 0, + SWORDCANE_DRAW, + SWORDCANE_HOLSTER, + SWORDCANE_ATTACK1, + SWORDCANE_ATTACK2, +}; + + +void CSwordcane::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_SWORDCANE; + SET_MODEL(ENT(pev), "models/w_swordcane.mdl"); + m_iClip = -1; + + FallInit();// get ready to fall down. +} + + +void CSwordcane::Precache( void ) +{ + PRECACHE_MODEL("models/v_swordcane.mdl"); + PRECACHE_MODEL("models/w_swordcane.mdl"); +// PRECACHE_MODEL("models/p_swordcane.mdl"); + + PRECACHE_SOUND("weapons/cbar_hit1.wav"); + PRECACHE_SOUND("weapons/cbar_hit2.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod1.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod2.wav"); + PRECACHE_SOUND("weapons/cbar_hitbod3.wav"); + PRECACHE_SOUND("weapons/cbar_miss1.wav"); +} + +int CSwordcane::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = NULL; + p->iMaxAmmo1 = -1; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 0; + p->iPosition = 1; + p->iId = WEAPON_SWORDCANE; + p->iWeight = SWORDCANE_WEIGHT; + return 1; +} + + + +BOOL CSwordcane::Deploy( ) +{ + return DefaultDeploy( "models/v_swordcane.mdl", "", SWORDCANE_DRAW, "swordcane" ); +} + +void CSwordcane::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + SendWeaponAnim( SWORDCANE_HOLSTER ); +} + +int CSwordcane::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +void CSwordcane::FindHullIntersection( const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity ) +{ + int i, j, k; + float distance; + float *minmaxs[2] = {mins, maxs}; + TraceResult tmpTrace; + Vector vecHullEnd = tr.vecEndPos; + Vector vecEnd; + + distance = 1e6f; + + vecHullEnd = vecSrc + ((vecHullEnd - vecSrc)*2); + UTIL_TraceLine( vecSrc, vecHullEnd, dont_ignore_monsters, pEntity, &tmpTrace ); + if ( tmpTrace.flFraction < 1.0 ) + { + tr = tmpTrace; + return; + } + + for ( i = 0; i < 2; i++ ) + { + for ( j = 0; j < 2; j++ ) + { + for ( k = 0; k < 2; k++ ) + { + vecEnd.x = vecHullEnd.x + minmaxs[i][0]; + vecEnd.y = vecHullEnd.y + minmaxs[j][1]; + vecEnd.z = vecHullEnd.z + minmaxs[k][2]; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, pEntity, &tmpTrace ); + if ( tmpTrace.flFraction < 1.0 ) + { + float thisDistance = (tmpTrace.vecEndPos - vecSrc).Length(); + if ( thisDistance < distance ) + { + tr = tmpTrace; + distance = thisDistance; + } + } + } + } + } +} + + +void CSwordcane::PrimaryAttack() +{ + if (! Swing( 1 )) + { + SetThink( SwingAgain ); + SetNextThink( 0.5 ); + } +} + + +void CSwordcane::Smack( ) +{ + DecalGunshot( &m_trHit, BULLET_PLAYER_SWORDCANE ); +} + + +void CSwordcane::SwingAgain( void ) +{ + Swing( 0 ); + DontThink(); +} + + +int CSwordcane::Swing( int fFirst ) +{ + int fDidHit = FALSE; + + TraceResult tr; + + UTIL_MakeVectors (m_pPlayer->pev->v_angle); + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecEnd = vecSrc + gpGlobals->v_forward * 32; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); + + if ( tr.flFraction >= 1.0 ) + { + UTIL_TraceHull( vecSrc, vecEnd, dont_ignore_monsters, head_hull, ENT( m_pPlayer->pev ), &tr ); + if ( tr.flFraction < 1.0 ) + { + // Calculate the point of intersection of the line (or hull) and the object we hit + // This is and approximation of the "best" intersection + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + if ( !pHit || pHit->IsBSPModel() ) + FindHullIntersection( vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, m_pPlayer->edict() ); + vecEnd = tr.vecEndPos; // This is the point on the actual surface (the hull could have hit space) + } + } + + if ( tr.flFraction >= 1.0 ) + { + if (fFirst) + { + // miss + switch( (m_iSwing++) % 3 ) + { + case 0: + SendWeaponAnim( SWORDCANE_ATTACK1 ); break; + case 1: + SendWeaponAnim( SWORDCANE_ATTACK2 ); break; + default: + SendWeaponAnim( SWORDCANE_ATTACK1 ); break; + } + m_flNextPrimaryAttack = gpGlobals->time + 0.5; + // play wiff or swish sound + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/cbar_miss1.wav", 1, ATTN_NORM, 0, 94 + RANDOM_LONG(0,0xF)); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + } + } + else + { + // hit + fDidHit = TRUE; + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + switch( ((m_iSwing++) % 2) + 1 ) + { + case 0: + SendWeaponAnim( SWORDCANE_ATTACK1 ); break; + case 1: + SendWeaponAnim( SWORDCANE_ATTACK2 ); break; + default: + SendWeaponAnim( SWORDCANE_ATTACK1 ); break; + } + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + ClearMultiDamage( ); + if ( (m_flNextPrimaryAttack + 1 < gpGlobals->time) || g_pGameRules->IsMultiplayer() ) + { + // first swing does full damage + pEntity->TraceAttack(m_pPlayer->pev, gSkillData.plrDmgSwordCane, gpGlobals->v_forward, &tr, DMG_ENERGYBEAM ); + } + else + { + // subsequent swings do half + pEntity->TraceAttack(m_pPlayer->pev, gSkillData.plrDmgSwordCane / 2, gpGlobals->v_forward, &tr, DMG_ENERGYBEAM ); + } + ApplyMultiDamage( m_pPlayer->pev, m_pPlayer->pev ); + + m_flNextPrimaryAttack = gpGlobals->time + 0.5; + + // play thwack, smack, or dong sound + float flVol = 1.0; + int fHitWorld = TRUE; + + if (pEntity) + { + if (pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE) + { + // play thwack or smack sound + switch( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/cbar_hitbod1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/cbar_hitbod2.wav", 1, ATTN_NORM); break; + case 2: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/cbar_hitbod3.wav", 1, ATTN_NORM); break; + } + m_pPlayer->m_iWeaponVolume = SWORDCANE_BODYHIT_VOLUME; + if (!pEntity->IsAlive() ) + return TRUE; + else + flVol = 0.1; + + fHitWorld = FALSE; + } + } + + // play texture hit sound + // UNDONE: Calculate the correct point of intersection when we hit with the hull instead of the line + + if (fHitWorld) + { + float fvolbar = TEXTURETYPE_PlaySound(&tr, vecSrc, vecSrc + (vecEnd-vecSrc)*2, BULLET_PLAYER_SWORDCANE); + + if ( g_pGameRules->IsMultiplayer() ) + { + // override the volume here, cause we don't play texture sounds in multiplayer, + // and fvolbar is going to be 0 from the above call. + + fvolbar = 1; + } + + // also play swordcane strike + switch( RANDOM_LONG(0,1) ) + { + case 0: + //UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "weapons/swordcane_hit1.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hit1.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + case 1: + //UTIL_EmitAmbientSound(ENT(0), ptr->vecEndPos, "weapons/swordcane_hit2.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/cbar_hit2.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + } + } + + // delay the decal a bit + m_trHit = tr; + SetThink( Smack ); + SetNextThink( 0.2 ); + + m_pPlayer->m_iWeaponVolume = flVol * SWORDCANE_WALLHIT_VOLUME; + } + return fDidHit; +} + +void CSwordcane::WeaponIdle( void ) +{ + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + (30.0 / 15.0) + RANDOM_FLOAT ( 1, 3 ); + SendWeaponAnim( SWORDCANE_IDLE, 1 ); +} + + diff --git a/dlls/cthulhu/SwordCane.h b/dlls/cthulhu/SwordCane.h new file mode 100755 index 00000000..1a3ff54d --- /dev/null +++ b/dlls/cthulhu/SwordCane.h @@ -0,0 +1,37 @@ + +#ifndef SWORDCANE_H +#define SWORDCANE_H + + +class CSwordcane : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 0; } + void EXPORT SwingAgain( void ); + void EXPORT Smack( void ); + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + void FindHullIntersection( const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity ); + + void PrimaryAttack( void ); + int Swing( int fFirst ); + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + int m_iSwing; + TraceResult m_trHit; + void WeaponIdle( void ); + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } +}; + + +#endif \ No newline at end of file diff --git a/dlls/cthulhu/TRex.cpp b/dlls/cthulhu/TRex.cpp new file mode 100755 index 00000000..1dead56a --- /dev/null +++ b/dlls/cthulhu/TRex.cpp @@ -0,0 +1,1115 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 + +//========================================================= +// TRex +//========================================================= +#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" +#include "scripted.h" + +#include "spiral.h" + + +#include "TRex.h" + +//========================================================= +// TRex Monster +//========================================================= +const float TREX_ATTACKDIST = 320.0; + +// TRex animation events +#define TREX_AE_SLASH_LEFT 1 +//#define TREX_AE_BEAM_ATTACK_RIGHT 2 // No longer used +#define TREX_AE_LEFT_FOOT 3 +#define TREX_AE_RIGHT_FOOT 4 +#define TREX_AE_STOMP 5 +#define TREX_AE_BREATHE 6 +#define TREX_AE_ROAR 7 + + +// TRex is immune to any damage but this +#define TREX_BEAM_SPRITE_NAME "sprites/xbeam3.spr" +#define TREX_BEAM_SPRITE2 "sprites/xbeam3.spr" +#define TREX_STOMP_SPRITE_NAME "sprites/gargeye1.spr" +#define TREX_STOMP_BUZZ_SOUND "weapons/mine_charge.wav" +#define TREX_FLAME_LENGTH 330 +//#define TREX_GIB_MODEL "models/metalplategibs.mdl" + +#define ATTN_TREX (ATTN_NORM) + +#define STOMP_SPRITE_COUNT 10 + +int tStompSprite = 0; +//int gTRexGibModel = 0; + +LINK_ENTITY_TO_CLASS( trex_stomp, CTRexStomp ); +CTRexStomp *CTRexStomp::StompCreate( const Vector &origin, const Vector &end, float speed ) +{ + CTRexStomp *pStomp = GetClassPtr( (CTRexStomp *)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 CTRexStomp::Spawn( void ) +{ + SetNextThink( 0 ); + pev->classname = MAKE_STRING("trex_stomp"); + pev->dmgtime = gpGlobals->time; + + pev->framerate = 30; + pev->model = MAKE_STRING(TREX_STOMP_SPRITE_NAME); + pev->rendermode = kRenderTransTexture; + pev->renderamt = 0; + EMIT_SOUND_DYN( edict(), CHAN_BODY, TREX_STOMP_BUZZ_SOUND, 1, ATTN_NORM, 0, PITCH_NORM * 0.55); +} + + +#define STOMP_INTERVAL 0.025 + +void CTRexStomp::Think( void ) +{ + TraceResult tr; + + SetNextThink( 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( TREX_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->SetNextThink( 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, TREX_STOMP_BUZZ_SOUND ); + } + + } +} + + +void TRexStreakSplash( 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(); +} + + + +LINK_ENTITY_TO_CLASS( monster_trex, CTRex ); + +TYPEDESCRIPTION CTRex::m_SaveData[] = +{ + DEFINE_FIELD( CTRex, m_seeTime, FIELD_TIME ), + DEFINE_FIELD( CTRex, m_flameTime, FIELD_TIME ), + DEFINE_FIELD( CTRex, m_streakTime, FIELD_TIME ), + DEFINE_FIELD( CTRex, m_painSoundTime, FIELD_TIME ), + DEFINE_ARRAY( CTRex, m_pFlame, FIELD_CLASSPTR, 2 ), + DEFINE_FIELD( CTRex, m_flameX, FIELD_FLOAT ), + DEFINE_FIELD( CTRex, m_flameY, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CTRex, CBaseMonster ); + +const char *CTRex::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CTRex::pBeamAttackSounds[] = +{ + "garg/gar_flameoff1.wav", + "garg/gar_flameon1.wav", + "garg/gar_flamerun1.wav", +}; + + +const char *CTRex::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CTRex::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 *CTRex::pFootSounds[] = +{ + "garg/gar_step1.wav", + "garg/gar_step2.wav", +}; + + +const char *CTRex::pIdleSounds[] = +{ + "garg/gar_idle1.wav", + "garg/gar_idle2.wav", + "garg/gar_idle3.wav", + "garg/gar_idle4.wav", + "garg/gar_idle5.wav", +}; + + +const char *CTRex::pAttackSounds[] = +{ + "garg/gar_attack1.wav", + "garg/gar_attack2.wav", + "garg/gar_attack3.wav", +}; + +const char *CTRex::pAlertSounds[] = +{ + "garg/gar_alert1.wav", + "garg/gar_alert2.wav", + "garg/gar_alert3.wav", +}; + +const char *CTRex::pPainSounds[] = +{ + "garg/gar_pain1.wav", + "garg/gar_pain2.wav", + "garg/gar_pain3.wav", +}; + +const char *CTRex::pStompSounds[] = +{ + "garg/gar_stomp1.wav", +}; + +const char *CTRex::pBreatheSounds[] = +{ + "garg/gar_breathe1.wav", + "garg/gar_breathe2.wav", + "garg/gar_breathe3.wav", +}; + +const char *CTRex::pRoarSounds[] = +{ + "trex/trex_roar1.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 tlTRexFlame[] = +{ + { 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 slTRexFlame[] = +{ + { + tlTRexFlame, + ARRAYSIZE ( tlTRexFlame ), + 0, + 0, + "TRexFlame" + }, +}; + + +// primary melee attack +Task_t tlTRexSwipe[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MELEE_ATTACK1, (float)0 }, +}; + +Schedule_t slTRexSwipe[] = +{ + { + tlTRexSwipe, + ARRAYSIZE ( tlTRexSwipe ), + bits_COND_CAN_MELEE_ATTACK2, + 0, + "TRexSwipe" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CTRex ) +{ + slTRexFlame, + slTRexSwipe, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CTRex, CBaseMonster ); + + +void CTRex::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 ); + CTRexStomp::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_TREX, 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 CTRex :: FlameCreate( void ) +{ + int i; + Vector posGun, angleGun; + TraceResult trace; + + UTIL_MakeVectors( pev->angles ); + + for ( i = 0; i < 2; i++ ) + { + if ( i < 2 ) + m_pFlame[i] = CBeam::BeamCreate( TREX_BEAM_SPRITE_NAME, 240 ); + else + m_pFlame[i] = CBeam::BeamCreate( TREX_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 * TREX_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 CTRex :: 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 CTRex :: FlameUpdate( void ) +{ + int i; + static float offset[2] = { 60, -60 }; + TraceResult trace; + Vector vecStart, angleGun; + BOOL streaks = FALSE; + + for ( i = 0; i < 1; 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 * TREX_FLAME_LENGTH); // - offset[i] * gpGlobals->v_right; + + UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, edict(), &trace ); + + m_pFlame[i]->SetStartPos( trace.vecEndPos ); + + if ( trace.flFraction != 1.0 && gpGlobals->time > m_streakTime ) + { + TRexStreakSplash( 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 CTRex :: 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 CTRex :: FlameDestroy( void ) +{ + int i; + + EMIT_SOUND_DYN ( edict(), CHAN_WEAPON, pBeamAttackSounds[ 0 ], 1.0, ATTN_NORM, 0, PITCH_NORM ); + for ( i = 0; i < 2; i++ ) + { + if ( m_pFlame[i] ) + { + UTIL_Remove( m_pFlame[i] ); + m_pFlame[i] = NULL; + } + } +} + + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CTRex :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_PREDATOR; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CTRex :: 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 CTRex :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/trex.mdl"); + UTIL_SetSize( pev, Vector( -64, -64, 0 ), Vector( 64, 64, 128 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + 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_seeTime = gpGlobals->time + 5; + m_flameTime = gpGlobals->time + 2; +} + + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CTRex :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/trex.mdl"); + PRECACHE_MODEL( TREX_BEAM_SPRITE_NAME ); + PRECACHE_MODEL( TREX_BEAM_SPRITE2 ); + tStompSprite = PRECACHE_MODEL( TREX_STOMP_SPRITE_NAME ); + //gTRexGibModel = PRECACHE_MODEL( TREX_GIB_MODEL ); + PRECACHE_SOUND( TREX_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]); + + for ( i = 0; i < ARRAYSIZE( pRoarSounds ); i++ ) + PRECACHE_SOUND((char *)pRoarSounds[i]); +} + + +void CTRex::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + ALERT( at_aiconsole, "CTRex::TraceAttack\n"); + + if ( !IsAlive() ) + { + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + return; + } + + if ( m_painSoundTime < gpGlobals->time ) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_TREX, 0, PITCH_NORM ); + m_painSoundTime = gpGlobals->time + RANDOM_FLOAT( 2.5, 4 ); + } + + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + +} + + + +int CTRex::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + ALERT( at_aiconsole, "CTRex::TakeDamage\n"); + + if (bitsDamageType & DMG_SONIC) + { + return 0; + } + + if ( IsAlive() ) + { + // only take a bit of damage from melee weapons + if ( !(bitsDamageType & DMG_BULLET|DMG_SLASH|DMG_CLUB) ) + flDamage *= 0.1; + if ( bitsDamageType & DMG_BLAST ) + SetConditions( bits_COND_LIGHT_DAMAGE ); + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CTRex::Killed( entvars_t *pevAttacker, int iGib ) +{ + CBaseMonster::Killed( pevAttacker, GIB_NEVER ); +} + +//========================================================= +// CheckMeleeAttack1 +// TRex swipe attack +// +//========================================================= +BOOL CTRex::CheckMeleeAttack1( float flDot, float flDist ) +{ +// ALERT(at_aiconsole, "CheckMelee(%f, %f)\n", flDot, flDist); + + if (flDot >= 0.7) + { + if (flDist <= TREX_ATTACKDIST) + return TRUE; + } + return FALSE; +} + + +// Flame thrower madness! +BOOL CTRex::CheckMeleeAttack2( float flDot, float flDist ) +{ +// ALERT(at_aiconsole, "CheckMelee(%f, %f)\n", flDot, flDist); + + //if ( gpGlobals->time > m_flameTime ) + //{ + // if (flDot >= 0.8 && flDist > TREX_ATTACKDIST) + // { + // if ( flDist <= TREX_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 CTRex::CheckRangeAttack1( float flDot, float flDist ) +{ + if ( gpGlobals->time > m_seeTime ) + { + if (flDot >= 0.7 && flDist > TREX_ATTACKDIST) + { + return TRUE; + } + } + return FALSE; +} + + + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CTRex::HandleAnimEvent(MonsterEvent_t *pEvent) +{ + switch( pEvent->event ) + { + case TREX_AE_SLASH_LEFT: + { + // HACKHACK!!! + CBaseEntity *pHurt = TRexCheckTraceHullAttack( TREX_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 TREX_AE_RIGHT_FOOT: + case TREX_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_TREX, 0, PITCH_NORM + RANDOM_LONG(-10,10) ); + break; + + case TREX_AE_STOMP: + StompAttack(); + m_seeTime = gpGlobals->time + 12; + break; + + case TREX_AE_BREATHE: + if ( !(pev->spawnflags & SF_MONSTER_GAG) || m_MonsterState != MONSTERSTATE_IDLE) + EMIT_SOUND_DYN ( edict(), CHAN_VOICE, pBreatheSounds[ RANDOM_LONG(0,ARRAYSIZE(pBreatheSounds)-1) ], 1.0, ATTN_TREX, 0, PITCH_NORM + RANDOM_LONG(-10,10) ); + break; + + case TREX_AE_ROAR: + EMIT_SOUND_DYN ( edict(), CHAN_VOICE, pRoarSounds[ RANDOM_LONG(0,ARRAYSIZE(pRoarSounds)-1) ], 1.0, ATTN_TREX, 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 TRex because his swing starts lower as +// a percentage of his height (otherwise he swings over the +// players head) +//========================================================= +CBaseEntity* CTRex::TRexCheckTraceHullAttack(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); + Vector vecEnd = vecStart + (gpGlobals->v_forward * flDist) - (gpGlobals->v_up * 32); + + 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 *CTRex::GetScheduleOfType( int Type ) +{ + // HACKHACK - turn off the flames if they are on and TRex goes scripted / dead + if ( FlameIsOn() ) + FlameDestroy(); + + switch( Type ) + { + case SCHED_MELEE_ATTACK2: + return slTRexFlame; + case SCHED_MELEE_ATTACK1: + return slTRexSwipe; + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + + +void CTRex::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_TREX, 0, PITCH_NORM ); + TaskComplete(); + break; + + // allow a scripted_action to make TRex shoot flames. + case TASK_PLAY_SCRIPT: + if ( m_pCine->IsAction() && m_pCine->m_fAction == 3) + { + FlameCreate(); + m_flWaitFinished = gpGlobals->time + 4.5; + m_flameTime = gpGlobals->time + 6; + m_flameX = 0; + m_flameY = 0; + } + CBaseMonster::RunTask( pTask ); + break; + + case TASK_DIE: + m_flWaitFinished = gpGlobals->time + 1.6; + // FALL THROUGH + default: + CBaseMonster::StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CTRex::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(); + SetNextThink( 0.15 ); + SetThink( SUB_Remove ); + /* + int i; + int parts = MODEL_FRAMES( gTRexGibModel ); + for ( i = 0; i < 10; i++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( TREX_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->SetNextThink( 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( gTRexGibModel ); //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_PLAY_SCRIPT: + if (!m_pCine->IsAction() || m_pCine->m_fAction != 3) + { + CBaseMonster::RunTask( pTask ); + break; + } + else + { + if (m_fSequenceFinished) + { + FlameDestroy(); + FlameControls( 0, 0 ); + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + m_pCine->SequenceDone( this ); + break; + } + //if not finished, carry on into task_flame_sweep! + } + 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; + if (m_pCine) // LRC- are we obeying a scripted_action? + pEnemy = m_hTargetEnt; + else + 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; + } +} + + +#endif diff --git a/dlls/cthulhu/TRex.h b/dlls/cthulhu/TRex.h new file mode 100755 index 00000000..8f672a48 --- /dev/null +++ b/dlls/cthulhu/TRex.h @@ -0,0 +1,86 @@ + +#ifndef TREX_H +#define TREX_H + +class CTRexStomp : public CBaseEntity +{ +public: + void Spawn( void ); + void Think( void ); + static CTRexStomp *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 ]; +}; + +class CTRex : 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 Killed( entvars_t *pevAttacker, int iGib ); + 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 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[]; + static const char *pRoarSounds[]; + + CBaseEntity* TRexCheckTraceHullAttack(float flDist, int iDamage, int iDmgType); + + CBeam *m_pFlame[2]; // Flame beams + + 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; +}; + + +#endif + diff --git a/dlls/cthulhu/TommyGun.cpp b/dlls/cthulhu/TommyGun.cpp new file mode 100755 index 00000000..28c0b7e4 --- /dev/null +++ b/dlls/cthulhu/TommyGun.cpp @@ -0,0 +1,219 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "soundent.h" +#include "gamerules.h" + +enum tommygun_e +{ + TOMMYGUN_IDLE = 0, + TOMMYGUN_RELOAD, + TOMMYGUN_DRAW, + TOMMYGUN_FIRE1, + TOMMYGUN_FIRE2, + TOMMYGUN_EMPTY_IDLE, +}; + +#include "TommyGun.h" + +LINK_ENTITY_TO_CLASS( weapon_tommygun, CTommyGun ); + + +//========================================================= +//========================================================= + +void CTommyGun::Spawn( ) +{ + pev->classname = MAKE_STRING("weapon_tommygun"); // hack to allow for old names + Precache( ); + SET_MODEL(ENT(pev), "models/w_tommygun.mdl"); + m_iId = WEAPON_TOMMYGUN; + + m_iDefaultAmmo = TOMMYGUN_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + + +void CTommyGun::Precache( void ) +{ + PRECACHE_MODEL("models/v_tommygun.mdl"); + PRECACHE_MODEL("models/w_tommygun.mdl"); + PRECACHE_MODEL("models/p_tommygun.mdl"); + + m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shellTE_MODEL + + PRECACHE_MODEL("models/tommy_ammo.mdl"); + + PRECACHE_SOUND("weapons/tommy_draw_slideback.wav"); + PRECACHE_SOUND("weapons/tommy_reload_clipin.wav"); + PRECACHE_SOUND("weapons/tommy_reload_clipout.wav"); + PRECACHE_SOUND("weapons/tommy_shoot1.wav"); + PRECACHE_SOUND("weapons/tommy_shoot2.wav"); + +// PRECACHE_SOUND ("weapons/357_cock1.wav"); + + m_usTommyGun = PRECACHE_EVENT( 1, "events/tommygun.sc" ); +} + +int CTommyGun::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Tommy Gun"; + p->iMaxAmmo1 = TOMMYGUN_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = TOMMYGUN_MAX_CLIP; + p->iSlot = 1; + p->iPosition = 2; + p->iFlags = 0; + p->iId = m_iId = WEAPON_TOMMYGUN; + p->iWeight = TOMMYGUN_WEIGHT; + + return 1; +} + +int CTommyGun::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +BOOL CTommyGun::Deploy( ) +{ + return DefaultDeploy( "models/v_tommygun.mdl", "models/p_tommygun.mdl", TOMMYGUN_DRAW, "tommygun" ); +} + +void CTommyGun::PrimaryAttack() +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = gpGlobals->time + 0.15; + return; + } + + if (m_iClip <= 0) + { + PlayEmptySound(); + m_flNextPrimaryAttack = gpGlobals->time + 0.15; + return; + } + + PLAYBACK_EVENT( 0, m_pPlayer->edict(), m_usTommyGun ); + + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + m_iClip--; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + if ( g_pGameRules->IsDeathmatch() ) + { + // optimized multiplayer. Widened to make it easier to hit a moving player + m_pPlayer->FireBullets( 1, vecSrc, vecAiming, VECTOR_CONE_6DEGREES, 8192, BULLET_PLAYER_TOMMYGUN, 2 ); + } + else + { + // single player spread + m_pPlayer->FireBullets( 1, vecSrc, vecAiming, VECTOR_CONE_3DEGREES, 8192, BULLET_PLAYER_TOMMYGUN, 2 ); + } + + //if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + // m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + m_flNextPrimaryAttack = m_flNextPrimaryAttack + 0.1; + if (m_flNextPrimaryAttack < gpGlobals->time) + m_flNextPrimaryAttack = gpGlobals->time + 0.1; + + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); +} + +void CTommyGun::SecondaryAttack( void ) +{ + PrimaryAttack(); +} + +void CTommyGun::Reload( void ) +{ + DefaultReload( TOMMYGUN_MAX_CLIP, TOMMYGUN_RELOAD, 177.0/38.0 ); +} + +void CTommyGun::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + SendWeaponAnim( TOMMYGUN_IDLE ); + + //m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 );// how long till we do this again. + m_flTimeWeaponIdle = gpGlobals->time + 40.0/12.0 + RANDOM_FLOAT ( 1, 4 ); +} + +//////////////////////////////////////////////////////////////////////////// + +void CTommyGunAmmoClip::Spawn( void ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/tommy_ammo.mdl"); + CBasePlayerAmmo::Spawn( ); +} + +void CTommyGunAmmoClip::Precache( void ) +{ + PRECACHE_MODEL ("models/tommy_ammo.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); +} + +BOOL CTommyGunAmmoClip::AddAmmo( CBaseEntity *pOther ) +{ + int bResult = (pOther->GiveAmmo( AMMO_TOMMYGUNCLIP_GIVE, "Tommy Gun", TOMMYGUN_MAX_CARRY) != -1); + if (bResult) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + } + return bResult; +} + + +LINK_ENTITY_TO_CLASS( ammo_tommygun, CTommyGunAmmoClip ); + + + + diff --git a/dlls/cthulhu/TommyGun.h b/dlls/cthulhu/TommyGun.h new file mode 100755 index 00000000..54f27ac5 --- /dev/null +++ b/dlls/cthulhu/TommyGun.h @@ -0,0 +1,46 @@ + +#ifndef TOMMYGUN_H +#define TOMMYGUN_H + +class CTommyGun : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 1; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + BOOL Deploy( void ); + void Reload( void ); + void WeaponIdle( void ); + float m_flNextAnimTime; + int m_iShell; + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } + +private: + unsigned short m_usTommyGun; +}; + +class CTommyGunAmmoClip : public CBasePlayerAmmo +{ +public: + void Spawn( void ); + void Precache( void ); + BOOL AddAmmo( CBaseEntity *pOther ); +}; + + + +#endif + diff --git a/dlls/cthulhu/Yodan.cpp b/dlls/cthulhu/Yodan.cpp new file mode 100755 index 00000000..05c7e5d9 --- /dev/null +++ b/dlls/cthulhu/Yodan.cpp @@ -0,0 +1,882 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Great Race of Yith servant - the Yodan 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 "animation.h" +#include "soundent.h" + +extern DLL_GLOBAL int g_iSkillLevel; + +#define YODAN_LIGHTNING_GUN ( 1 << 0) +#define YODAN_NONE ( 1 << 1) + +#define GUN_GROUP 1 +#define GUN_LIGHTNING 0 +#define GUN_NONE 1 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define YODAN_AE_CLAW ( 1 ) +#define YODAN_AE_CLAWRAKE ( 2 ) +#define YODAN_AE_ZAP_POWERUP ( 3 ) +#define YODAN_AE_ZAP_SHOOT ( 4 ) +#define YODAN_AE_ZAP_DONE ( 5 ) +#define YODAN_AE_DROP_GUN ( 6 ) + +#define YODAN_MAX_BEAMS 8 + + +#include "Yodan.h" + + +LINK_ENTITY_TO_CLASS( monster_yodan, CYodan ); + + +TYPEDESCRIPTION CYodan::m_SaveData[] = +{ + DEFINE_ARRAY( CYodan, m_pBeam, FIELD_CLASSPTR, YODAN_MAX_BEAMS ), + DEFINE_FIELD( CYodan, m_iBeams, FIELD_INTEGER ), + DEFINE_FIELD( CYodan, m_flNextAttack, FIELD_TIME ), + DEFINE_FIELD( CYodan, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CYodan, m_voicePitch, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CYodan, CSquadMonster ); + + + + +const char *CYodan::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CYodan::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CYodan::pPainSounds[] = +{ + "yodan/yo_pain1.wav", + "yodan/yo_pain2.wav", + "yodan/yo_pain3.wav", +}; + +const char *CYodan::pDeathSounds[] = +{ + "yodan/yo_die1.wav", + "yodan/yo_die2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CYodan :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MILITARY; +} + + +int CYodan::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 CYodan :: 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 CYodan :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + SENTENCEG_PlayRndSz(ENT(pev), "YODAN_ALERT", 0.85, ATTN_NORM, 0, m_voicePitch); + + CallForHelp( "monster_greatrace", 512, m_hEnemy, m_vecEnemyLKP ); + CallForHelp( "monster_yodan", 512, m_hEnemy, m_vecEnemyLKP ); + } +} + +//========================================================= +// IdleSound +//========================================================= +void CYodan :: IdleSound( void ) +{ + //if (RANDOM_LONG( 0, 2 ) == 0) + //{ + SENTENCEG_PlayRndSz(ENT(pev), "YODAN_IDLE", 0.85, ATTN_NORM, 0, m_voicePitch); + //} + +} + +//========================================================= +// PainSound +//========================================================= +void CYodan :: 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 CYodan :: 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 CYodan :: ISoundMask ( void) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + + +void CYodan::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 CYodan :: SetYawSpeed ( void ) +{ + int ys; + + switch ( m_Activity ) + { + case ACT_WALK: + ys = 90; + break; + case ACT_RUN: + ys = 120; + break; + case ACT_IDLE: + 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. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CYodan :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + // ALERT( at_console, "event %d : %f\n", pEvent->event, pev->frame ); + switch( pEvent->event ) + { + case YODAN_AE_DROP_GUN: + { + if (GetBodygroup(GUN_GROUP) != GUN_LIGHTNING) break; + if (pev->spawnflags & SF_MONSTER_NO_WPN_DROP) break; + + Vector vecGunPos; + Vector vecGunAngles; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + // switch to body group with no gun. + SetBodygroup( GUN_GROUP, GUN_NONE ); + + // now spawn a gun. + DropItem( "weapon_lightninggun", vecGunPos, vecGunAngles ); + + } + break; + + case YODAN_AE_CLAW: + { + // SOUND HERE! + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.yodanDmgClaw, 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 YODAN_AE_CLAWRAKE: + { + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.yodanDmgClawrake, 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 YODAN_AE_ZAP_POWERUP: + { + if (m_flNextAttack > gpGlobals->time) break; + + // 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( ); + + } + //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 YODAN_AE_ZAP_SHOOT: + { + ClearBeams( ); + + 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( 2.0, 5.0 ); + } + break; + + case YODAN_AE_ZAP_DONE: + { + ClearBeams( ); + } + break; + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// CheckRangeAttack1 - normal beam attack +//========================================================= +BOOL CYodan :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if (GetBodygroup(GUN_GROUP) == GUN_NONE) + return FALSE; + + if (m_flNextAttack > gpGlobals->time) + { + return FALSE; + } + + return CSquadMonster::CheckRangeAttack1( flDot, flDist ); +} + +//========================================================= +// CheckRangeAttack2 - check bravery and try to resurect dead comrades +//========================================================= +BOOL CYodan :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; +} + + +//========================================================= +// StartTask +//========================================================= +void CYodan :: StartTask ( Task_t *pTask ) +{ + ClearBeams( ); + + CSquadMonster :: StartTask ( pTask ); +} + + +//========================================================= +// Spawn +//========================================================= +void CYodan :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/yodan.mdl"); +// UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + if (pev->health == 0) + pev->health = gSkillData.yodanHealth; + 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_DOORS_GROUP; + + m_voicePitch = RANDOM_LONG( 85, 110 ); + m_fStanding = 1; + + if (FBitSet( pev->weapons, YODAN_LIGHTNING_GUN )) + { + SetBodygroup( GUN_GROUP, GUN_LIGHTNING ); + } + else // none + { + SetBodygroup( GUN_GROUP, GUN_NONE ); + } + + MonsterInit(); + + m_iBeams = 0; +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CYodan :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/yodan.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 CYodan :: 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 CYodan::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + if (bitsDamageType & DMG_SHOCK) + if (FClassnameIs(pev, STRING(pevAttacker->classname))) + return; + + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CYodan :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if (( GetBodygroup( GUN_GROUP ) != GUN_NONE ) && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) + {// throw a gun if the yodan has one + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun; + pGun = DropItem( "weapon_lightninggun", 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 ); + } + } + + CSquadMonster :: GibMonster(); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + + +// primary range attack +Task_t tlYodanAttack1[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slYodanAttack1[] = +{ + { + tlYodanAttack1, + ARRAYSIZE ( tlYodanAttack1 ), + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_HEAR_SOUND | + bits_COND_HEAVY_DAMAGE, + + bits_SOUND_DANGER, + "Yodan Range Attack1" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CYodan ) +{ + slYodanAttack1, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CYodan, CSquadMonster ); + + +//========================================================= +//========================================================= +Schedule_t *CYodan :: 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) + { + 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 *CYodan :: 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 slYodanAttack1; + case SCHED_RANGE_ATTACK2: + return slYodanAttack1; + } + return CSquadMonster :: GetScheduleOfType( Type ); +} + +//========================================================= +// SetActivity +//========================================================= +void CYodan :: SetActivity ( Activity NewActivity ) +{ + // if we were crouching, move up again + if (m_fStanding == 0) + { + m_fStanding = 1; + pev->origin.z += 18; + UTIL_SetSize(pev, VEC_HULL_MIN, VEC_HULL_MAX); + } + + + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_MELEE_ATTACK1: + + // randomly stand or crouch + if (RANDOM_LONG(0,99) == 0) + m_fStanding = 0; + else + m_fStanding = 1; + + // a short enemy...probably a snake... + if ((m_hEnemy != NULL) && (m_hEnemy->pev->maxs.z < 36)) + { + m_fStanding = 0; + } + + if (m_fStanding == 0) + { + UTIL_SetSize(pev, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX); + pev->origin.z -= 18; + } + + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "ref_shoot_crowbar" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouch_shoot_crowbar" ); + } + 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) + } +} + +//========================================================= +// ArmBeam - small beam from arm to nearby geometry +//========================================================= + +/* +void CYodan :: ArmBeam( int side ) +{ + TraceResult tr; + float flDist = 1.0; + + if (m_iBeams >= YODAN_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 ); + + // Cthulhu: check m_pBeam + 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 CYodan :: 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 CYodan :: WackBeam( int side, CBaseEntity *pEntity ) +{ + Vector vecDest; + float flDist = 1.0; + + if (m_iBeams >= YODAN_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 CYodan :: ZapBeam( int side ) +{ + Vector vecSrc, vecAim; + TraceResult tr; + CBaseEntity *pEntity; + + if (m_iBeams >= YODAN_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.yodanDmgZap, 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 CYodan :: ClearBeams( ) +{ + for (int i = 0; i < YODAN_MAX_BEAMS; i++) + { + if (m_pBeam[i]) + { + UTIL_Remove( m_pBeam[i] ); + m_pBeam[i] = NULL; + m_iBeams--; + } + } + m_iBeams = 0; + pev->skin = 0; + + STOP_SOUND( ENT(pev), CHAN_WEAPON, "debris/zap4.wav" ); +} diff --git a/dlls/cthulhu/Yodan.h b/dlls/cthulhu/Yodan.h new file mode 100755 index 00000000..92f0ab09 --- /dev/null +++ b/dlls/cthulhu/Yodan.h @@ -0,0 +1,63 @@ + +#ifndef YODAN_H +#define YODAN_H + +class CYodan : 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 GibMonster( void ); + + void DeathSound( void ); + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + + void Killed( entvars_t *pevAttacker, int iGib ); + + BOOL m_fStanding; + + void SetActivity ( Activity NewActivity ); + 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 ); + + CBeam *m_pBeam[YODAN_MAX_BEAMS]; + + int m_iBeams; + float m_flNextAttack; + + int m_voicePitch; + + static const char *pAttackHitSounds[]; + static const char *pAttackMissSounds[]; + static const char *pPainSounds[]; + static const char *pDeathSounds[]; +}; + + + + +#endif diff --git a/dlls/cthulhu/alien_egg.cpp b/dlls/cthulhu/alien_egg.cpp new file mode 100755 index 00000000..ccd38f0c --- /dev/null +++ b/dlls/cthulhu/alien_egg.cpp @@ -0,0 +1,313 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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_egg.cpp - spawns headcrabs +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "game.h" +#include "defaultai.h" +#include "scripted.h" +#include "weapons.h" +#include "soundent.h" + +#include "alien_egg.h" + +const int DEAD_EGG_Z = 8; + +LINK_ENTITY_TO_CLASS( monster_alienegg, CAlienEgg ); + +TYPEDESCRIPTION CAlienEgg::m_SaveData[] = +{ + DEFINE_FIELD( CAlienEgg, m_iOrientation, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CAlienEgg, CBaseMonster ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CAlienEgg :: Classify ( void ) +{ + return m_iClass?m_iClass: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 CAlienEgg :: Center ( void ) +{ + if (m_iOrientation == 0) + { + return Vector( pev->origin.x, pev->origin.y, pev->origin.z + 8 ); + } + else + { + return Vector( pev->origin.x, pev->origin.y, pev->origin.z - 8 ); + } +} + + +Vector CAlienEgg :: BodyTarget( const Vector &posSrc ) +{ + return Center( ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CAlienEgg :: SetYawSpeed ( void ) +{ +} + +void CAlienEgg::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "orientation")) + { + m_iOrientation = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + + } + else + CBaseMonster::KeyValue( pkvd ); +} + +//========================================================= +// Spawn +//========================================================= +void CAlienEgg :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/alien_egg.mdl"); + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 48)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_NONE; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->effects = 0; + if (pev->health == 0) + pev->health = gSkillData.headcrabHealth; + pev->yaw_speed = 0;//!!! should we put this in the monster's changeanim function since turn rates may vary with state/anim? + m_flFieldOfView = VIEW_FIELD_FULL;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + if (m_iOrientation == 1) + { + UTIL_SetSize(pev, Vector(-16, -16, -48), Vector(16, 16, 0)); + + pev->idealpitch = 180; + pev->flags |= FL_FLY; + SetBits( pev->spawnflags, SF_MONSTER_FALL_TO_GROUND); + pev->effects |= EF_INVLIGHT; + + pev->angles.x = 180; + pev->angles.y = pev->angles.y + 180; + if (pev->angles.y > 360) + pev->angles.y = pev->angles.y - 360; + } + + MonsterInit(); + + SetUse(EggUse); + + if (m_iOrientation == 0) + { + pev->view_ofs = Vector ( 0, 0, 32 );// position of the eyes relative to monster's origin. + } + else + { + pev->view_ofs = Vector ( 0, 0, -32 );// position of the eyes relative to monster's origin. + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CAlienEgg :: Precache() +{ + PRECACHE_SOUND("gonarch/gon_birth3.wav"); + PRECACHE_SOUND("common/bodysplat.wav"); + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/alien_egg.mdl"); + + UTIL_PrecacheOther( "monster_headcrab" ); +} + + +//========================================================= +// RunTask +//========================================================= +void CAlienEgg :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_WAIT_FOR_MOVEMENT: + { + TaskComplete(); + break; + } + case TASK_RANGE_ATTACK1: + case TASK_RANGE_ATTACK2: + { + if ( m_fSequenceFinished ) + { + TaskComplete(); + + Burst(); + + m_IdealActivity = ACT_IDLE; + } + break; + } + default: + { + CBaseMonster :: RunTask(pTask); + } + } +} + + +void CAlienEgg :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_FACE_ENEMY: + { + TaskComplete(); + break; + } + case TASK_RANGE_ATTACK1: + { + m_IdealActivity = ACT_RANGE_ATTACK1; + CBaseMonster :: StartTask( pTask ); + break; + } + default: + { + CBaseMonster :: StartTask( pTask ); + } + } +} + + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CAlienEgg :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist <= 128 + (128 * m_iOrientation) ) + { + return TRUE; + } + return FALSE; +} + +int CAlienEgg :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CAlienEgg :: Killed( entvars_t *pevAttacker, int iGib ) +{ + // change body + pev->body = 1; + // spray gibs + CGib::SpawnRandomGibs( pev, 6, 0 ); // throw some human gibs. + // make sound + EMIT_SOUND_DYN( edict(), CHAN_BODY, "common/bodysplat.wav", 1.0, ATTN_IDLE, 0, 100 ); + // cannot use now + SetUse(NULL); + + pev->deadflag = DEAD_DEAD; + SetThink(NULL); + StopAnimation(); + if (m_iOrientation == 0) + { + UTIL_SetSize ( pev, Vector ( pev->mins.x, pev->mins.y, pev->mins.z ), Vector ( pev->maxs.x, pev->maxs.y, pev->mins.z + DEAD_EGG_Z ) ); + } + else + { + UTIL_SetSize ( pev, Vector ( pev->mins.x, pev->mins.y, pev->maxs.z - DEAD_EGG_Z), Vector ( pev->maxs.x, pev->maxs.y, pev->maxs.z ) ); + } + pev->solid = SOLID_NOT; +} + +void CAlienEgg::Burst() +{ + pev->health = 0; + Killed(NULL, GIB_NEVER); + + // spawn a headcrab + edict_t* pent = CREATE_NAMED_ENTITY( ALLOC_STRING("monster_headcrab") ); + + if ( FNullEnt( pent ) ) + { + ALERT ( at_debug, "NULL Ent in Alien Egg!\n" ); + return; + } + + entvars_t* pevCreate = VARS( pent ); + pevCreate->origin = Center(); + if (m_iOrientation == 0) + { + } + else + { + pevCreate->origin.z -= 32; + } + pevCreate->angles.y = pev->angles.y; + pevCreate->angles.z = pev->angles.z; + SetBits( pevCreate->spawnflags, SF_MONSTER_FALL_TO_GROUND ); + + DispatchSpawn( ENT( pevCreate ) ); +} + +void CAlienEgg::EggUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (IsAlive()) + { + Burst(); + } +} + +Schedule_t *CAlienEgg :: GetSchedule ( void ) +{ + // we can see the enemy + if ( HasConditions(bits_COND_CAN_RANGE_ATTACK1) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType ( SCHED_IDLE_STAND ); +} + + diff --git a/dlls/cthulhu/alien_egg.h b/dlls/cthulhu/alien_egg.h new file mode 100755 index 00000000..60788c4f --- /dev/null +++ b/dlls/cthulhu/alien_egg.h @@ -0,0 +1,34 @@ + +#ifndef ALIEN_EGG +#define ALIEN_EGG + + +class CAlienEgg : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + void RunTask ( Task_t *pTask ); + void StartTask ( Task_t *pTask ); + void SetYawSpeed ( void ); + Vector Center( void ); + Vector BodyTarget( const Vector &posSrc ); + int Classify ( void ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + virtual void Killed( entvars_t *pevAttacker, int iGib ); + void Burst(); + void EXPORT EggUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual Schedule_t *GetSchedule( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_iOrientation; // 0 = floor, 1 = ceiling +}; + + +#endif diff --git a/dlls/cthulhu/bm.cpp b/dlls/cthulhu/bm.cpp new file mode 100755 index 00000000..39b285a0 --- /dev/null +++ b/dlls/cthulhu/bm.cpp @@ -0,0 +1,188 @@ + +#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" + + +#include "bm.h" + +extern int gSpitSprite; + +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 ) +{ + SetNextThink( 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, vecStart ); + pSpit->pev->velocity = vecVelocity; + pSpit->pev->owner = pOwner; + pSpit->pev->scale = 2.5; + pSpit->SetThink ( Animate ); + pSpit->SetNextThink( 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/cthulhu/bm.h b/dlls/cthulhu/bm.h new file mode 100755 index 00000000..c4a55afd --- /dev/null +++ b/dlls/cthulhu/bm.h @@ -0,0 +1,55 @@ + +#ifndef BM_H +#define BM_H + +//LRC brought in from animation.h +#define ACTIVITY_NOT_AVAILABLE -1 + +#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; +}; + + +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; +}; + +Vector VecCheckSplatToss( entvars_t *pev, const Vector &vecSpot1, Vector vecSpot2, float maxHeight ); +void MortarSpray( const Vector &position, const Vector &direction, int spriteModel, int count ); + + + +#endif diff --git a/dlls/cthulhu/book.cpp b/dlls/cthulhu/book.cpp new file mode 100755 index 00000000..1ed920af --- /dev/null +++ b/dlls/cthulhu/book.cpp @@ -0,0 +1,173 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "items.h" +#include "gamerules.h" + +extern int gmsgItemPickup; + +#define SF_BOOK_FIREONCE 0x00000001 + +#define NUM_BOOK_PAGES 3 + +class CBook : public CBaseToggle +{ +public: + void Spawn( void ); + void Precache( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() | FCAP_ONOFF_USE) & ~FCAP_ACROSS_TRANSITION; } + void EXPORT ReadThink( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + void KeyValue( KeyValueData *pkvd ); + + int m_iszImage[NUM_BOOK_PAGES]; + float m_fImageDuration; + + int m_iImage; + + BOOL m_bHasFired; +}; + + +LINK_ENTITY_TO_CLASS( item_book, CBook ); + +TYPEDESCRIPTION CBook::m_SaveData[] = +{ + DEFINE_FIELD( CBook, m_iImage, FIELD_INTEGER ), + DEFINE_FIELD( CBook, m_iszImage[0], FIELD_STRING ), + DEFINE_FIELD( CBook, m_iszImage[1], FIELD_STRING ), + DEFINE_FIELD( CBook, m_iszImage[2], FIELD_STRING ), + DEFINE_FIELD( CBook, m_fImageDuration, FIELD_FLOAT ), + DEFINE_FIELD( CBook, m_bHasFired, FIELD_BOOLEAN ), +}; + + +IMPLEMENT_SAVERESTORE( CBook, CBaseToggle); + +void CBook :: Spawn( void ) +{ + Precache( ); + pev->solid = SOLID_BBOX; + pev->movetype = MOVETYPE_NONE; + pev->takedamage = DAMAGE_NO; + pev->sequence = 0; + pev->frame = 0; + + // initialise the book pages + m_iImage = -1; + + // we haven't fired yet + m_bHasFired = FALSE; + + UTIL_SetOrigin(this, pev->origin); // set size and link into world +// UTIL_SetSize(pev, Vector(-16,-16,0), Vector(16,16,16)); + SET_MODEL(ENT(pev), STRING(pev->model)); + + ResetSequenceInfo( ); + pev->frame = 0; + // NB: FallInit is a weapons function, so make sure the book is on a surface in Worldcraft, + // since it will not fall (i.e. it is not affected by gravity!). + if (DROP_TO_FLOOR(ENT(pev)) == 0) + { + ALERT(at_error, "Book %s fell out of level at %f,%f,%f", STRING( pev->classname ), pev->origin.x, pev->origin.y, pev->origin.z); + UTIL_Remove( this ); + return; + } +} + +void CBook::Precache( void ) +{ + char* sz = (char*)STRING(pev->model); + PRECACHE_MODEL(sz); +} + +void CBook::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // we are already reading this book + if (m_iImage >= 0) return; + + // write a message to the client to display the picture + m_iImage = 0; + UTIL_ReadBook(STRING(m_iszImage[m_iImage])); + SetThink(ReadThink); + SetNextThink(m_fImageDuration); + + // if we haven't fired or we are allowed to fire more than once, fire target + if (!m_bHasFired || !(pev->spawnflags & SF_BOOK_FIREONCE)) + { + SUB_UseTargets( NULL, USE_TOGGLE, 0 ); + m_bHasFired = TRUE; + } +} + +void CBook::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "image1")) + { + m_iszImage[0] = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "image2")) + { + m_iszImage[1] = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "image3")) + { + m_iszImage[2] = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "imageduration")) + { + m_fImageDuration = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); +} + +void CBook::ReadThink( ) +{ + // turn the page + m_iImage++; + + if (m_iImage == NUM_BOOK_PAGES || FStringNull(m_iszImage[m_iImage])) + { + // we have finished the book + UTIL_ReadBook(""); + DontThink(); + m_iImage = -1; + } + else + { + // clear the page + UTIL_ReadBook(""); + // show the next page + UTIL_ReadBook(STRING(m_iszImage[m_iImage])); + SetNextThink(m_fImageDuration); + } +} diff --git a/dlls/cthulhu/butler.cpp b/dlls/cthulhu/butler.cpp new file mode 100755 index 00000000..ad815eed --- /dev/null +++ b/dlls/cthulhu/butler.cpp @@ -0,0 +1,1280 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 butler (passive servant) +//========================================================= + +#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" + + +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 +//========================================================= + +// none yet... + +//======================================================= +// Butler +//======================================================= + +#include "butler.h" + + +LINK_ENTITY_TO_CLASS( monster_butler, CButler ); + +TYPEDESCRIPTION CButler::m_SaveData[] = +{ + DEFINE_FIELD( CButler, m_painTime, FIELD_TIME ), + DEFINE_FIELD( CButler, m_fearTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CButler, CTalkMonster ); + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlButlerFollow[] = +{ + { 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 slButlerFollow[] = +{ + { + tlButlerFollow, + ARRAYSIZE ( tlButlerFollow ), + 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 tlButlerFollowScared[] = +{ + { 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 slButlerFollowScared[] = +{ + { + tlButlerFollowScared, + ARRAYSIZE ( tlButlerFollowScared ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + bits_SOUND_DANGER, + "FollowScared" + }, +}; + +Task_t tlButlerFaceTargetScared[] = +{ + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE_SCARED }, +}; + +Schedule_t slButlerFaceTargetScared[] = +{ + { + tlButlerFaceTargetScared, + ARRAYSIZE ( tlButlerFaceTargetScared ), + bits_COND_HEAR_SOUND | + bits_COND_NEW_ENEMY, + bits_SOUND_DANGER, + "FaceTargetScared" + }, +}; + +Task_t tlButlerStopFollowing[] = +{ + { TASK_CANT_FOLLOW, (float)0 }, +}; + +Schedule_t slButlerStopFollowing[] = +{ + { + tlButlerStopFollowing, + ARRAYSIZE ( tlButlerStopFollowing ), + 0, + 0, + "StopFollowing" + }, +}; + + +Task_t tlButlerFaceTarget[] = +{ + { 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 slButlerFaceTarget[] = +{ + { + tlButlerFaceTarget, + ARRAYSIZE ( tlButlerFaceTarget ), + bits_COND_CLIENT_PUSH | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + bits_SOUND_COMBAT | + bits_SOUND_DANGER, + "FaceTarget" + }, +}; + + +Task_t tlButlerPanic[] = +{ + { 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 slButlerPanic[] = +{ + { + tlButlerPanic, + ARRAYSIZE ( tlButlerPanic ), + 0, + 0, + "ButlerPanic" + }, +}; + + +Task_t tlIdleButlerStand[] = +{ + { 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 slIdleButlerStand[] = +{ + { + tlIdleButlerStand, + ARRAYSIZE ( tlIdleButlerStand ), + 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, + "IdleButlerStand" + + }, +}; + + +Task_t tlButlerCover[] = +{ + { 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 slButlerCover[] = +{ + { + tlButlerCover, + ARRAYSIZE ( tlButlerCover ), + bits_COND_NEW_ENEMY, + 0, + "ButlerCover" + }, +}; + + + +Task_t tlButlerHide[] = +{ + { 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 slButlerHide[] = +{ + { + tlButlerHide, + ARRAYSIZE ( tlButlerHide ), + 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, + "ButlerHide" + }, +}; + + +Task_t tlButlerStartle[] = +{ + { 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 slButlerStartle[] = +{ + { + tlButlerStartle, + ARRAYSIZE ( tlButlerStartle ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_HATE | + bits_COND_SEE_FEAR | + bits_COND_SEE_DISLIKE, + 0, + "ButlerStartle" + }, +}; + + + +Task_t tlButlerFear[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SAY_FEAR, (float)0 }, +// { TASK_PLAY_SEQUENCE, (float)ACT_FEAR_DISPLAY }, +}; + +Schedule_t slButlerFear[] = +{ + { + tlButlerFear, + ARRAYSIZE ( tlButlerFear ), + bits_COND_NEW_ENEMY, + 0, + "ButlerFear" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CButler ) +{ + slButlerFollow, + slButlerFaceTarget, + slIdleButlerStand, + slButlerFear, + slButlerCover, + slButlerHide, + slButlerStartle, + slButlerStopFollowing, + slButlerPanic, + slButlerFollowScared, + slButlerFaceTargetScared, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CButler, CTalkMonster ); + + +void CButler::DeclineFollowing( void ) +{ + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( m_szGrp[TLK_DECLINE], 2, VOL_NORM, ATTN_NORM ); //LRC +} + + +void CButler :: Scream( void ) +{ + if ( FOkToSpeak() ) + { + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( "SC_SCREAM", RANDOM_FLOAT(3, 6), VOL_NORM, ATTN_NORM ); + } +} + + +Activity CButler::GetStoppedActivity( void ) +{ + if ( m_hEnemy != NULL ) + return ACT_EXCITED; + return CTalkMonster::GetStoppedActivity(); +} + + +void CButler :: 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 CButler :: 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 CButler :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_PASSIVE; +} + + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CButler :: 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 CButler :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + CTalkMonster::HandleAnimEvent( pEvent ); +} + +//========================================================= +// Spawn +//========================================================= +void CButler :: Spawn( void ) +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/butler.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; + if (pev->health == 0) + pev->health = gSkillData.butlerHealth; + 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 butler 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; + + MonsterInit(); + SetUse( FollowerUse ); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CButler :: Precache( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/butler.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 butler must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + + CTalkMonster::Precache(); +} + +// Init talk data +void CButler :: TalkInit() +{ + + CTalkMonster::TalkInit(); + + // butler will try to talk to friends in this order: + + m_szFriends[0] = "monster_sirhenry"; + m_szFriends[1] = "monster_butler"; + m_szFriends[2] = "monster_sitting_butler"; + m_szFriends[3] = ""; + m_szFriends[4] = ""; + + // butler speech group names (group names are in sentences.txt) + + if (!m_iszSpeakAs) + { + m_szGrp[TLK_ANSWER] = "CIV_ANSWER"; + m_szGrp[TLK_QUESTION] = "CIV_QUESTION"; + m_szGrp[TLK_IDLE] = "BUT_IDLE"; + m_szGrp[TLK_STARE] = NULL; + + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_USE] = NULL; + else + m_szGrp[TLK_USE] = NULL; + + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_UNUSE] = NULL; + else + m_szGrp[TLK_UNUSE] = NULL; + + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_DECLINE] = NULL; + else + m_szGrp[TLK_DECLINE] = NULL; + + m_szGrp[TLK_STOP] = "SC_STOP"; + m_szGrp[TLK_NOSHOOT] = "SC_SCARED"; + m_szGrp[TLK_HELLO] = "BUT_HELLO"; + + m_szGrp[TLK_PLHURT1] = NULL; + m_szGrp[TLK_PLHURT2] = NULL; + m_szGrp[TLK_PLHURT3] = NULL; + + m_szGrp[TLK_PHELLO] = "BUT_GREET"; + m_szGrp[TLK_PIDLE] = "BUT_IDLE"; + m_szGrp[TLK_PQUESTION] = NULL; + m_szGrp[TLK_SMELL] = "SC_SMELL"; + + m_szGrp[TLK_WOUND] = "SC_WOUND"; + m_szGrp[TLK_MORTAL] = "SC_MORTAL"; + } + + m_voicePitch = 95; +} + +int CButler :: 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 butler... + 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 CButler :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + +//========================================================= +// PainSound +//========================================================= +void CButler :: 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 CButler :: DeathSound ( void ) +{ + PainSound(); +} + + +void CButler::Killed( entvars_t *pevAttacker, int iGib ) +{ + SetUse( NULL ); + CTalkMonster::Killed( pevAttacker, iGib ); +} + + +void CButler :: 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* CButler :: 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 butler will talk + // when 'used' + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slButlerFaceTarget; // override this for different target face behavior + else + return psched; + + case SCHED_TARGET_CHASE: + return slButlerFollow; + + case SCHED_CANT_FOLLOW: + return slButlerStopFollowing; + + case SCHED_PANIC: + return slButlerPanic; + + case SCHED_TARGET_CHASE_SCARED: + return slButlerFollowScared; + + case SCHED_TARGET_FACE_SCARED: + return slButlerFaceTargetScared; + + case SCHED_IDLE_STAND: + // call base class default so that butler will talk + // when standing during idle + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slIdleButlerStand; + else + return psched; + + case SCHED_HIDE: + return slButlerHide; + + case SCHED_STARTLE: + return slButlerStartle; + + case SCHED_FEAR: + return slButlerFear; + } + + return CTalkMonster::GetScheduleOfType( Type ); +} + +Schedule_t *CButler :: 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 ( 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, butler 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 slButlerFear; // Point and scream! + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + return slButlerCover; // Take Cover + + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + return slTakeCoverFromBestSound; // Cower and panic from the scary sound! + + return slButlerCover; // Run & Cower + break; + } + + return CTalkMonster::GetSchedule(); +} + +MONSTERSTATE CButler :: 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(); +} + + +int CButler::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 1, 2, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + + +//========================================================= +// Dead Butler PROP +//========================================================= + +char *CDeadButler::m_szPoses[] = { "lying_on_back", "lying_on_stomach", "dead_sitting", "dead_hang", "dead_table1", "dead_table2", "dead_table3" }; + +void CDeadButler::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_butler_dead, CDeadButler ); + +// +// ********** CDeadButler SPAWN ********** +// +void CDeadButler :: Spawn( ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/butler.mdl"); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/butler.mdl"); + + pev->effects = 0; + pev->sequence = 0; + // Corpses have less health + pev->health = 8;//gSkillData.butlerHealth; + + m_bloodColor = BLOOD_COLOR_RED; + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + if (pev->sequence == -1) + { + ALERT ( at_console, "Dead Butler with bad pose\n" ); + } + + // pev->skin += 2; // use bloody skin -- UNDONE: Turn this back on when we have a bloody skin again! + MonsterInitDead(); +} + + +//========================================================= +// Sitting Butler PROP +//========================================================= + + +LINK_ENTITY_TO_CLASS( monster_sitting_butler, CSittingButler ); +TYPEDESCRIPTION CSittingButler::m_SaveData[] = +{ + // Don't need to save/restore m_baseSequence (recalced) + DEFINE_FIELD( CSittingButler, m_headTurn, FIELD_INTEGER ), + DEFINE_FIELD( CSittingButler, m_flResponseDelay, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CSittingButler, CButler ); + +// animation sequence aliases +typedef enum +{ +SITTING_ANIM_sitlookleft, +SITTING_ANIM_sitlookright, +SITTING_ANIM_sitscared, +SITTING_ANIM_sitting2, +SITTING_ANIM_sitting3 +} SITTING_ANIM; + + +#define SF_SITTINGBUTLER_POSTDISASTER 1024 + +// +// ********** Butler SPAWN ********** +// +void CSittingButler :: Spawn( ) +{ + PRECACHE_MODEL("models/butler.mdl"); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/butler.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; + + if (!FBitSet(pev->spawnflags, SF_SITTINGBUTLER_POSTDISASTER)) //LRC- allow a sitter to be postdisaster. + SetBits(pev->spawnflags, SF_MONSTER_PREDISASTER); // predisaster only! + + m_baseSequence = LookupSequence( "sitlookleft" ); + pev->sequence = m_baseSequence + RANDOM_LONG(0,4); + ResetSequenceInfo( ); + + SetThink (SittingThink); + SetNextThink( 0.1 ); + + DROP_TO_FLOOR ( ENT(pev) ); +} + +void CSittingButler :: Precache( void ) +{ + m_baseSequence = LookupSequence( "sitlookleft" ); + TalkInit(); +} + +//========================================================= +// ID as a passive human +//========================================================= +int CSittingButler :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_PASSIVE; +} + + +int CSittingButler::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 2, 1, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + + + +//========================================================= +// sit, do stuff +//========================================================= +void CSittingButler :: 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 butler to answer a question +void CSittingButler :: 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 CSittingButler :: 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 butlers 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/cthulhu/butler.h b/dlls/cthulhu/butler.h new file mode 100755 index 00000000..0bd96b4e --- /dev/null +++ b/dlls/cthulhu/butler.h @@ -0,0 +1,88 @@ + +#ifndef BUTLER_H +#define BUTLER_H + +class CButler : 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 Butlers want to get far away! + BOOL DisregardEnemy( CBaseEntity *pEnemy ) { return !pEnemy->IsAlive() || (gpGlobals->time - m_fearTime) > 15; } + + 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_fearTime; +}; + + +class CDeadButler : 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]; +}; + + +class CSittingButler : public CButler // 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; +}; + + + +#endif diff --git a/dlls/cthulhu/charm.cpp b/dlls/cthulhu/charm.cpp new file mode 100755 index 00000000..1fd46352 --- /dev/null +++ b/dlls/cthulhu/charm.cpp @@ -0,0 +1,422 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "soundent.h" +#include "shake.h" +#include "gamerules.h" + + +// set the weapon sounds +// set the weapon slot +// set the weapon crosshairs + +#define CHARM_PRIMARY_FIRE_VOLUME 450// how loud charm is when cast + +class CCharmedMonster : public CBaseEntity +{ +public: + void Spawn( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Initialise(CBaseMonster* pCharmedMonster); + void EXPORT CharmThink(void); + + int m_iOriginalClass; + CBaseMonster* m_pCharmedMonster; // this is the thing being charmed +}; + +TYPEDESCRIPTION CCharmedMonster::m_SaveData[] = +{ + DEFINE_FIELD( CCharmedMonster, m_iOriginalClass, FIELD_INTEGER), + DEFINE_FIELD( CCharmedMonster, m_pCharmedMonster, FIELD_CLASSPTR), +}; + +IMPLEMENT_SAVERESTORE( CCharmedMonster, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(charmedmonster, CCharmedMonster); + +void CCharmedMonster::Spawn() +{ + pev->movetype = MOVETYPE_NONE; + pev->classname = MAKE_STRING( "charmedmonster" ); + + pev->solid = SOLID_NOT; + + UTIL_SetSize( pev, Vector( 0, 0, 0), Vector(0, 0, 0) ); + + DontThink(); +} + +void CCharmedMonster::Initialise(CBaseMonster* pCharmedMonster) +{ + m_pCharmedMonster = pCharmedMonster; + + m_iOriginalClass = m_pCharmedMonster->Classify(); + m_pCharmedMonster->m_iClass = CLASS_PLAYER_ALLY; + + m_pCharmedMonster->m_hEnemy = NULL; + + SetThink(CharmThink); + + SetNextThink(3000.0 / (float)m_pCharmedMonster->pev->max_health); +} + +void CCharmedMonster::CharmThink() +{ + m_pCharmedMonster->m_iClass = m_iOriginalClass; + + UTIL_Remove(this); +} + + +///////////////////////////////////////////////////////////////////////////////////// + +enum charm_e { + CHARM_OPEN = 0, + CHARM_IDLE1, + CHARM_IDLE2, + CHARM_IDLE3, + CHARM_CAST, + CHARM_CLOSE +}; + +class CCharm : public CBasePlayerWeapon +{ +public: + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 4; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + void WeaponIdle( void ); + void ZapPowerUp( void ); + void ZapShoot( void ); + void ZapDone( void ); + + int m_fInAttack; + + void ClearBeam( void ); + void ZapBeam(); + + BOOL IsMonsterCharmed (CBaseEntity* pEntity); + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } + +private: + + // the beam effect + CBeam* m_pBeam; +}; + +LINK_ENTITY_TO_CLASS( weapon_charm, CCharm ); + +TYPEDESCRIPTION CCharm::m_SaveData[] = +{ + DEFINE_FIELD( CCharm, m_fInAttack, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CCharm, CBasePlayerWeapon ); + +void CCharm::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_CHARM; + SET_MODEL(ENT(pev), "models/w_charm.mdl"); + m_iClip = -1; + + FallInit();// get ready to fall down. +} + + +void CCharm::Precache( void ) +{ + PRECACHE_MODEL("models/w_charm.mdl"); + PRECACHE_MODEL("models/v_charm.mdl"); + //PRECACHE_MODEL("models/p_charm.mdl"); + + PRECACHE_MODEL( "sprites/xbeam3.spr" ); + + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND("weapons/gauss2.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); + PRECACHE_SOUND("weapons/electro5.wav"); + PRECACHE_SOUND("weapons/electro6.wav"); + PRECACHE_SOUND("ambience/pulsemachine.wav"); +} + +int CCharm::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +int CCharm::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = NULL; + p->iMaxAmmo1 = -1; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 5; + p->iId = m_iId = WEAPON_CHARM; + p->iFlags = 0; + p->iWeight = CHARM_WEIGHT; + + return 1; +} + +BOOL CCharm::Deploy( ) +{ + return DefaultDeploy( "models/v_charm.mdl", "", CHARM_OPEN, "charm" ); +} + +void CCharm::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + SendWeaponAnim( CHARM_CLOSE ); + m_fInAttack = 0; +} + + +void CCharm::PrimaryAttack() +{ + if (m_fInAttack != 0) + { + if ( m_fInAttack == 1 ) + { + ZapShoot(); + } + else // == 2 + { + ZapDone(); + } + return; + } + + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->time + 0.15; + return; + } + + /* + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] < 1) + { + PlayEmptySound( ); + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + return; + } + */ + + ZapPowerUp(); +} + +void CCharm::SecondaryAttack() +{ +} + +void CCharm::ZapPowerUp() +{ + m_fInAttack = 1; + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "debris/zap4.wav", 1, ATTN_NORM, 0, 150 ); + + m_flTimeWeaponIdle = gpGlobals->time + 0.2; +} + + +void CCharm::ZapShoot() +{ + ClearBeam( ); + + UTIL_MakeAimVectors( pev->angles ); + + ZapBeam(); + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "hassault/hw_shoot1.wav", 1, ATTN_NORM, 0, RANDOM_LONG( 130, 160 ) ); + + m_flTimeWeaponIdle = gpGlobals->time + 0.2; + m_flNextPrimaryAttack = gpGlobals->time + 0.2; + + m_fInAttack = 2; +} + +void CCharm::ZapDone() +{ + ClearBeam( ); + + m_fInAttack = 0; + m_flTimeWeaponIdle = gpGlobals->time + 0.4; + m_flNextPrimaryAttack = gpGlobals->time + 0.4; +} + +void CCharm::WeaponIdle( void ) +{ + ResetEmptySound( ); + + if ( m_flTimeWeaponIdle > gpGlobals->time ) + return; + + if (m_fInAttack != 0) + { + if ( m_fInAttack == 1 ) + { + ZapShoot(); + } + else // == 2 + { + ZapDone(); + } + } + else + { + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.4) + { + iAnim = CHARM_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT(2,3); + } + else if (flRand <= 0.75) + { + iAnim = CHARM_IDLE2; + m_flTimeWeaponIdle = gpGlobals->time + 3; + } + else + { + iAnim = CHARM_IDLE3; + m_flTimeWeaponIdle = gpGlobals->time + 3; + } + + return; + SendWeaponAnim( iAnim ); + } +} + +//========================================================= +// ZapBeam - heavy damage directly forward +//========================================================= +void CCharm :: ZapBeam() +{ + TraceResult tr; + CBaseEntity *pEntity; + + // this should be attachment 0 (???) + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecDest = vecSrc + vecAiming * 2048; + UTIL_TraceLine( vecSrc, vecDest, dont_ignore_monsters, m_pPlayer->edict(), &tr ); + + m_pBeam = CBeam::BeamCreate( "sprites/xbeam3.spr", 20 ); + if (!m_pBeam) + return; + + m_pBeam->PointEntInit( tr.vecEndPos, m_pPlayer->entindex( ) ); + m_pBeam->SetEndAttachment( 1 ); + m_pBeam->SetColor( 255, 180, 96 ); + m_pBeam->SetBrightness( 255 ); + m_pBeam->SetNoise( 0 ); + m_pBeam->SetScrollRate( 20 ); + m_pBeam->pev->spawnflags |= SF_BEAM_TEMPORARY; // Flag these to be destroyed on save/restore or level transition + + pEntity = CBaseEntity::Instance(tr.pHit); + if (pEntity != NULL) + { + // is it a monster + if (pEntity->pev->flags & FL_MONSTER) + { + // we cannot charm a monster who is already charmed + if (!IsMonsterCharmed(pEntity)) + { + // create a charmed monster entity + CCharmedMonster* pCharm = (CCharmedMonster*)CBaseEntity::Create( "charmedmonster", pEntity->pev->origin, pEntity->pev->angles, pEntity->edict() ); + + // set the monster + pCharm->Initialise((CBaseMonster*)pEntity); + } + } + } + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); +} + +BOOL CCharm :: IsMonsterCharmed(CBaseEntity* pEntity) +{ + CCharmedMonster* pCharm = NULL; + pCharm = (CCharmedMonster*)UTIL_FindEntityByClassname(pCharm, "charmedmonster"); + while (pCharm != NULL) + { + if (pCharm->m_pCharmedMonster == pEntity) return TRUE; + + pCharm = (CCharmedMonster*)UTIL_FindEntityByClassname(pCharm, "charmedmonster"); + } + + return FALSE; +} + +void CCharm :: ClearBeam( ) +{ + if (m_pBeam) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } + + STOP_SOUND( ENT(pev), CHAN_WEAPON, "debris/zap4.wav" ); +} + + +#endif diff --git a/dlls/cthulhu/civilian.cpp b/dlls/cthulhu/civilian.cpp new file mode 100755 index 00000000..41dcedcb --- /dev/null +++ b/dlls/cthulhu/civilian.cpp @@ -0,0 +1,1468 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 civilian (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_CIVILIAN_BODIES 6 // six bodies available for civilian model +enum +{ + BODY_DOCTOR = 0, + BODY_GANGSTER = 1, + BODY_JOURNALIST = 2, + BODY_MI5 = 3, + BODY_PATIENT = 4, + BODY_WORKER = 5 +}; + +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 DOCTOR_AE_HEAL ( 1 ) +#define DOCTOR_AE_NEEDLEON ( 2 ) +#define DOCTOR_AE_NEEDLEOFF ( 3 ) + +//======================================================= +// civilian +//======================================================= + +class CCivilian : 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 civilians 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_civilian, CCivilian ); + +TYPEDESCRIPTION CCivilian::m_SaveData[] = +{ + DEFINE_FIELD( CCivilian, m_painTime, FIELD_TIME ), + DEFINE_FIELD( CCivilian, m_healTime, FIELD_TIME ), + DEFINE_FIELD( CCivilian, m_fearTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CCivilian, CTalkMonster ); + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlCivFollow[] = +{ + { 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 slCivFollow[] = +{ + { + tlCivFollow, + ARRAYSIZE ( tlCivFollow ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + bits_SOUND_COMBAT | + bits_SOUND_DANGER, + "CivFollow" + }, +}; + +Task_t tlCivFollowScared[] = +{ + { 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 slCivFollowScared[] = +{ + { + tlCivFollowScared, + ARRAYSIZE ( tlCivFollowScared ), + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + bits_SOUND_DANGER, + "CivFollowScared" + }, +}; + +Task_t tlCivFaceTargetScared[] = +{ + { TASK_FACE_TARGET, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_CROUCHIDLE }, + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_CHASE_SCARED }, +}; + +Schedule_t slCivFaceTargetScared[] = +{ + { + tlCivFaceTargetScared, + ARRAYSIZE ( tlCivFaceTargetScared ), + bits_COND_HEAR_SOUND | + bits_COND_NEW_ENEMY, + bits_SOUND_DANGER, + "CivFaceTargetScared" + }, +}; + +Task_t tlCivStopFollowing[] = +{ + { TASK_CANT_FOLLOW, (float)0 }, +}; + +Schedule_t slCivStopFollowing[] = +{ + { + tlCivStopFollowing, + ARRAYSIZE ( tlCivStopFollowing ), + 0, + 0, + "CivStopFollowing" + }, +}; + + +Task_t tlDocHeal[] = +{ + { 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 slDocHeal[] = +{ + { + tlDocHeal, + ARRAYSIZE ( tlDocHeal ), + 0, // Don't interrupt or he'll end up running around with a needle all the time + 0, + "DocHeal" + }, +}; + + +Task_t tlCivFaceTarget[] = +{ + { 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 slCivFaceTarget[] = +{ + { + tlCivFaceTarget, + ARRAYSIZE ( tlCivFaceTarget ), + bits_COND_CLIENT_PUSH | + bits_COND_NEW_ENEMY | + bits_COND_HEAR_SOUND, + bits_SOUND_COMBAT | + bits_SOUND_DANGER, + "CivFaceTarget" + }, +}; + + +Task_t tlCivPanic[] = +{ + { 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 slCivPanic[] = +{ + { + tlCivPanic, + ARRAYSIZE ( tlCivPanic ), + 0, + 0, + "CivPanic" + }, +}; + + +Task_t tlIdleCivStand[] = +{ + { 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 slIdleCivStand[] = +{ + { + tlIdleCivStand, + ARRAYSIZE ( tlIdleCivStand ), + 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, + "IdleCivStand" + + }, +}; + + +Task_t tlCivilianCover[] = +{ + { 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 slCivilianCover[] = +{ + { + tlCivilianCover, + ARRAYSIZE ( tlCivilianCover ), + bits_COND_NEW_ENEMY, + 0, + "CivilianCover" + }, +}; + + + +Task_t tlCivilianHide[] = +{ + { 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 slCivilianHide[] = +{ + { + tlCivilianHide, + ARRAYSIZE ( tlCivilianHide ), + 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, + "CivilianHide" + }, +}; + + +Task_t tlCivilianStartle[] = +{ + { 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 slCivilianStartle[] = +{ + { + tlCivilianStartle, + ARRAYSIZE ( tlCivilianStartle ), + bits_COND_NEW_ENEMY | + bits_COND_SEE_ENEMY | + bits_COND_SEE_HATE | + bits_COND_SEE_FEAR | + bits_COND_SEE_DISLIKE, + 0, + "CivilianStartle" + }, +}; + + + +Task_t tlCivFear[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_SAY_FEAR, (float)0 }, +// { TASK_PLAY_SEQUENCE, (float)ACT_FEAR_DISPLAY }, +}; + +Schedule_t slCivFear[] = +{ + { + tlCivFear, + ARRAYSIZE ( tlCivFear ), + bits_COND_NEW_ENEMY, + 0, + "CivFear" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CCivilian ) +{ + slCivFollow, + slCivFaceTarget, + slIdleCivStand, + slCivFear, + slCivilianCover, + slCivilianHide, + slCivilianStartle, + slDocHeal, + slCivStopFollowing, + slCivPanic, + slCivFollowScared, + slCivFaceTargetScared, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CCivilian, CTalkMonster ); + + +void CCivilian::DeclineFollowing( void ) +{ + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( m_szGrp[TLK_DECLINE], 2, VOL_NORM, ATTN_NORM ); //LRC +} + + +void CCivilian :: Scream( void ) +{ + if ( FOkToSpeak() ) + { + Talk( 10 ); + m_hTalkTarget = m_hEnemy; + PlaySentence( "SC_SCREAM", RANDOM_FLOAT(3, 6), VOL_NORM, ATTN_NORM ); + } +} + + +Activity CCivilian::GetStoppedActivity( void ) +{ + if ( m_hEnemy != NULL ) + return ACT_EXCITED; + return CTalkMonster::GetStoppedActivity(); +} + + +void CCivilian :: 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 CCivilian :: 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 CCivilian :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_PASSIVE; +} + + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CCivilian :: 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 CCivilian :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case DOCTOR_AE_HEAL: // Heal my target (if within range) + Heal(); + break; + case DOCTOR_AE_NEEDLEON: + { + int oldBody = pev->body; + pev->body = (oldBody % NUM_CIVILIAN_BODIES) + NUM_CIVILIAN_BODIES * 1; + } + break; + case DOCTOR_AE_NEEDLEOFF: + { + int oldBody = pev->body; + pev->body = (oldBody % NUM_CIVILIAN_BODIES) + NUM_CIVILIAN_BODIES * 0; + } + break; + + default: + CTalkMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CCivilian :: Spawn( void ) +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/civilian.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; + if (pev->health == 0) + 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 civilians 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_CIVILIAN_BODIES-1);// pick a head, any head + } + + MonsterInit(); + SetUse( FollowerUse ); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CCivilian :: Precache( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/civilian.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 civilian must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + + CTalkMonster::Precache(); +} + +// Init talk data +void CCivilian :: TalkInit() +{ + + CTalkMonster::TalkInit(); + + // civilian will try to talk to friends in this order: + + m_szFriends[0] = "monster_civilian"; + m_szFriends[1] = "monster_sitting_civilian"; + m_szFriends[2] = "monster_policeman"; + m_szFriends[3] = "monster_scientist"; + m_szFriends[4] = "monster_sitting_scientist"; + + // civilians speach group names (group names are in sentences.txt) + + if (!m_iszSpeakAs) + { + m_szGrp[TLK_ANSWER] = "CIV_ANSWER"; + m_szGrp[TLK_QUESTION] = "CIV_QUESTION"; + m_szGrp[TLK_IDLE] = "CIV_IDLE"; + m_szGrp[TLK_STARE] = "SC_STARE"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_USE] = "SC_PFOLLOW"; + else + m_szGrp[TLK_USE] = "CIV_OK"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_UNUSE] = "SC_PWAIT"; + else + m_szGrp[TLK_UNUSE] = "SC_WAIT"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_DECLINE] = "CIV_POK"; + else + m_szGrp[TLK_DECLINE] = "SC_NOTOK"; + m_szGrp[TLK_STOP] = "SC_STOP"; + m_szGrp[TLK_NOSHOOT] = "SC_SCARED"; + m_szGrp[TLK_HELLO] = "CIV_HELLO"; + + m_szGrp[TLK_PLHURT1] = "!SC_CUREA"; + m_szGrp[TLK_PLHURT2] = "!SC_CUREB"; + m_szGrp[TLK_PLHURT3] = "!SC_CUREC"; + + m_szGrp[TLK_PHELLO] = "CIV_PHELLO"; + m_szGrp[TLK_PIDLE] = "CIV_PIDLE"; + m_szGrp[TLK_PQUESTION] = "CIV_PQUEST"; + m_szGrp[TLK_SMELL] = "CIV_SMELL"; + + m_szGrp[TLK_WOUND] = "SC_WOUND"; + m_szGrp[TLK_MORTAL] = "SC_MORTAL"; + } + + // get voice for head + switch (pev->body % 3) + { + default: + case BODY_DOCTOR: m_voicePitch = 105; break; + case BODY_GANGSTER: m_voicePitch = 95; break; + case BODY_JOURNALIST: m_voicePitch = 100; break; + case BODY_MI5: m_voicePitch = 105; break; + case BODY_PATIENT: m_voicePitch = 100; break; + case BODY_WORKER: m_voicePitch = 95; break; + } +} + +int CCivilian :: 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 civilian... + 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 CCivilian :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_DANGER | + bits_SOUND_PLAYER; +} + +//========================================================= +// PainSound +//========================================================= +void CCivilian :: 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 CCivilian :: DeathSound ( void ) +{ + PainSound(); +} + + +void CCivilian::Killed( entvars_t *pevAttacker, int iGib ) +{ + SetUse( NULL ); + CTalkMonster::Killed( pevAttacker, iGib ); +} + + +void CCivilian :: 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* CCivilian :: 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 civilian will talk + // when 'used' + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slCivFaceTarget; // override this for different target face behavior + else + return psched; + + case SCHED_TARGET_CHASE: + return slCivFollow; + + case SCHED_CANT_FOLLOW: + return slCivStopFollowing; + + case SCHED_PANIC: + return slCivPanic; + + case SCHED_TARGET_CHASE_SCARED: + return slCivFollowScared; + + case SCHED_TARGET_FACE_SCARED: + return slCivFaceTargetScared; + + case SCHED_IDLE_STAND: + // call base class default so that civilian will talk + // when standing during idle + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slIdleCivStand; + else + return psched; + + case SCHED_HIDE: + return slCivilianHide; + + case SCHED_STARTLE: + return slCivilianStartle; + + case SCHED_FEAR: + return slCivFear; + } + + return CTalkMonster::GetScheduleOfType( Type ); +} + +Schedule_t *CCivilian :: 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 slDocHeal; + 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, civilian 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 slCivFear; // Point and scream! + if ( HasConditions( bits_COND_SEE_ENEMY ) ) + return slCivilianCover; // Take Cover + + if ( HasConditions( bits_COND_HEAR_SOUND ) ) + return slTakeCoverFromBestSound; // Cower and panic from the scary sound! + + return slCivilianCover; // Run & Cower + break; + } + + return CTalkMonster::GetSchedule(); +} + +MONSTERSTATE CCivilian :: 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 CCivilian::CanHeal( void ) +{ + if (!(pev->spawnflags & SF_DOCTOR_CAN_HEAL)) + return FALSE; + + // are we a doctor or a doctor holding a needle + if ( (pev->body != BODY_DOCTOR) && (pev->body != BODY_DOCTOR + NUM_CIVILIAN_BODIES) ) + return FALSE; + + if ( (m_healTime > gpGlobals->time) || (m_hTargetEnt == NULL) || (m_hTargetEnt->pev->health > (m_hTargetEnt->pev->max_health * 0.5)) ) + return FALSE; + + return TRUE; +} + +void CCivilian::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 CCivilian::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 1, 2, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + + +//========================================================= +// Dead Civilian PROP +//========================================================= +class CDeadCivilian : 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 *CDeadCivilian::m_szPoses[] = { "lying_on_back", "lying_on_stomach", "dead_sitting", "dead_hang", "dead_table1", "dead_table2", "dead_table3" }; + +void CDeadCivilian::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_civilian_dead, CDeadCivilian ); + +// +// ********** CDeadCivilian SPAWN ********** +// +void CDeadCivilian :: Spawn( ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/civilian.mdl"); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/civilian.mdl"); + + pev->effects = 0; + pev->sequence = 0; + // Corpses have less health + pev->health = 8;//gSkillData.civilianHealth; + + m_bloodColor = BLOOD_COLOR_RED; + + if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, NUM_CIVILIAN_BODIES-1);// pick a head, any head + } + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + if (pev->sequence == -1) + { + ALERT ( at_debug, "Dead civilian with bad pose\n" ); + } + + // pev->skin += 2; // use bloody skin -- UNDONE: Turn this back on when we have a bloody skin again! + MonsterInitDead(); +} + + +//========================================================= +// Sitting Civilian PROP +//========================================================= + +class CSittingCivilian : public CCivilian // 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_civilian, CSittingCivilian ); +TYPEDESCRIPTION CSittingCivilian::m_SaveData[] = +{ + // Don't need to save/restore m_baseSequence (recalced) + DEFINE_FIELD( CSittingCivilian, m_headTurn, FIELD_INTEGER ), + DEFINE_FIELD( CSittingCivilian, m_flResponseDelay, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CSittingCivilian, CCivilian ); + +// animation sequence aliases +typedef enum +{ +SITTING_ANIM_sitlookleft, +SITTING_ANIM_sitlookright, +SITTING_ANIM_sitscared, +SITTING_ANIM_sitting2, +SITTING_ANIM_sitting3 +} SITTING_ANIM; + + +#define SF_SITTINGCIV_POSTDISASTER 1024 + +// +// ********** Civilian SPAWN ********** +// +void CSittingCivilian :: Spawn( ) +{ + PRECACHE_MODEL("models/civilian.mdl"); + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/civilian.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; + + if (!FBitSet(pev->spawnflags, SF_SITTINGCIV_POSTDISASTER)) //LRC- allow a sitter to be postdisaster. + SetBits(pev->spawnflags, SF_MONSTER_PREDISASTER); // predisaster only! + + if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, NUM_CIVILIAN_BODIES-1);// pick a head, any head + } + + m_baseSequence = LookupSequence( "sitlookleft" ); + pev->sequence = m_baseSequence + RANDOM_LONG(0,4); + ResetSequenceInfo( ); + + SetThink (SittingThink); + SetNextThink( 0.1 ); + + DROP_TO_FLOOR ( ENT(pev) ); +} + +void CSittingCivilian :: Precache( void ) +{ + m_baseSequence = LookupSequence( "sitlookleft" ); + TalkInit(); +} + +//========================================================= +// ID as a passive human +//========================================================= +int CSittingCivilian :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_PASSIVE; +} + + +int CSittingCivilian::FriendNumber( int arrayNumber ) +{ + static int array[3] = { 2, 1, 0 }; + if ( arrayNumber < 3 ) + return array[ arrayNumber ]; + return arrayNumber; +} + + + +//========================================================= +// sit, do stuff +//========================================================= +void CSittingCivilian :: 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 ); + } + SetNextThink( 0.1 ); +} + +// prepare sitting civilian to answer a question +void CSittingCivilian :: 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 CSittingCivilian :: 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 civilians 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/cthulhu/crowbar.h b/dlls/cthulhu/crowbar.h new file mode 100755 index 00000000..f298b8de --- /dev/null +++ b/dlls/cthulhu/crowbar.h @@ -0,0 +1,26 @@ + +#ifndef CROWBAR_H +#define CROWBAR_H + + +class CCrowbar : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 1; } + void EXPORT SwingAgain( void ); + void EXPORT Smack( void ); + int GetItemInfo(ItemInfo *p); + void FindHullIntersection( const Vector &vecSrc, TraceResult &tr, float *mins, float *maxs, edict_t *pEntity ); + + void PrimaryAttack( void ); + int Swing( int fFirst ); + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + int m_iSwing; + TraceResult m_trHit; +}; + + +#endif \ No newline at end of file diff --git a/dlls/cthulhu/cthulhu.cpp b/dlls/cthulhu/cthulhu.cpp new file mode 100755 index 00000000..11343444 --- /dev/null +++ b/dlls/cthulhu/cthulhu.cpp @@ -0,0 +1,1142 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 + +//========================================================= +// Cthulhu +//========================================================= +#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" +#include "scripted.h" + +#include "spiral.h" + +//========================================================= +// Cthulhu Monster +//========================================================= +const float CTH_ATTACKDIST = 100.0; + +// Cthulhu animation events +#define CTH_AE_SLASH_LEFT 1 +//#define CTH_AE_BEAM_ATTACK_RIGHT 2 // No longer used +#define CTH_AE_LEFT_FOOT 3 +#define CTH_AE_RIGHT_FOOT 4 +//#define CTH_AE_STOMP 5 // Cthulhu does not stomp +#define CTH_AE_BREATHE 6 + + +// Cthulhu is immune to any damage but this +//#define CTH_DAMAGE (DMG_ENERGYBEAM|DMG_CRUSH|DMG_MORTAR|DMG_BLAST) +#define CTH_EYE_SPRITE_NAME "sprites/gargeye1.spr" +#define CTH_BEAM_SPRITE_NAME "sprites/xbeam3.spr" +#define CTH_BEAM_SPRITE2 "sprites/xbeam3.spr" +//#define CTH_STOMP_SPRITE_NAME "sprites/gargeye1.spr" +//#define CTH_STOMP_BUZZ_SOUND "weapons/mine_charge.wav" +#define CTH_FLAME_LENGTH 440 +#define CTH_GIB_MODEL "models/metalplategibs.mdl" + +#define ATTN_CTH (ATTN_NORM) + +int gCthulhuGibModel = 0; + +void CthSpawnExplosion( Vector center, float randomRange, float time, int magnitude ); + + +void CthStreakSplash( 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 CCthulhu : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData *pkvd ); + 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 ); + pev->absmin = pev->origin + Vector( -160, -160, 0 ); + pev->absmax = pev->origin + Vector( 160, 160, 418 ); + } + + 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 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 *pFootSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pAttackSounds[]; + static const char *pBreatheSounds[]; + + CBaseEntity* CthulhuCheckTraceHullAttack(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; + + BOOL m_bExplodeOnDeath; +}; + +LINK_ENTITY_TO_CLASS( monster_cthulhu, CCthulhu ); + +TYPEDESCRIPTION CCthulhu::m_SaveData[] = +{ + DEFINE_FIELD( CCthulhu, m_pEyeGlow, FIELD_CLASSPTR ), + DEFINE_FIELD( CCthulhu, m_eyeBrightness, FIELD_INTEGER ), + DEFINE_FIELD( CCthulhu, m_seeTime, FIELD_TIME ), + DEFINE_FIELD( CCthulhu, m_flameTime, FIELD_TIME ), + DEFINE_FIELD( CCthulhu, m_streakTime, FIELD_TIME ), + DEFINE_FIELD( CCthulhu, m_painSoundTime, FIELD_TIME ), + DEFINE_ARRAY( CCthulhu, m_pFlame, FIELD_CLASSPTR, 4 ), + DEFINE_FIELD( CCthulhu, m_flameX, FIELD_FLOAT ), + DEFINE_FIELD( CCthulhu, m_flameY, FIELD_FLOAT ), + DEFINE_FIELD( CCthulhu, m_bExplodeOnDeath, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CCthulhu, CBaseMonster ); + +const char *CCthulhu::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CCthulhu::pBeamAttackSounds[] = +{ + "garg/gar_flameoff1.wav", + "garg/gar_flameon1.wav", + "garg/gar_flamerun1.wav", +}; + + +const char *CCthulhu::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CCthulhu::pFootSounds[] = +{ + "garg/gar_step1.wav", + "garg/gar_step2.wav", +}; + + +const char *CCthulhu::pIdleSounds[] = +{ + "garg/gar_idle1.wav", + "garg/gar_idle2.wav", + "garg/gar_idle3.wav", + "garg/gar_idle4.wav", + "garg/gar_idle5.wav", +}; + + +const char *CCthulhu::pAttackSounds[] = +{ + "garg/gar_attack1.wav", + "garg/gar_attack2.wav", + "garg/gar_attack3.wav", +}; + +const char *CCthulhu::pAlertSounds[] = +{ + "garg/gar_alert1.wav", + "garg/gar_alert2.wav", + "garg/gar_alert3.wav", +}; + +const char *CCthulhu::pPainSounds[] = +{ + "garg/gar_pain1.wav", + "garg/gar_pain2.wav", + "garg/gar_pain3.wav", +}; + +const char *CCthulhu::pBreatheSounds[] = +{ + "garg/gar_breathe1.wav", + "garg/gar_breathe2.wav", + "garg/gar_breathe3.wav", +}; +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +enum +{ + TASK_SOUND_ATTACK = LAST_COMMON_TASK + 1, + TASK_FLAME_SWEEP, +}; + +Task_t tlCthulhuFlame[] = +{ + { 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 slCthulhuFlame[] = +{ + { + tlCthulhuFlame, + ARRAYSIZE ( tlCthulhuFlame ), + 0, + 0, + "CthulhuFlame" + }, +}; + + +// primary melee attack +Task_t tlCthulhuSwipe[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_MELEE_ATTACK1, (float)0 }, +}; + +Schedule_t slCthulhuSwipe[] = +{ + { + tlCthulhuSwipe, + ARRAYSIZE ( tlCthulhuSwipe ), + bits_COND_CAN_MELEE_ATTACK2, + 0, + "CthulhuSwipe" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( CCthulhu ) +{ + slCthulhuFlame, + slCthulhuSwipe, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CCthulhu, CBaseMonster ); + + +void CCthulhu::EyeOn( int level ) +{ + m_eyeBrightness = level; +} + + +void CCthulhu::EyeOff( void ) +{ + m_eyeBrightness = 0; +} + + +void CCthulhu::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->origin ); + } +} + + +void CCthulhu :: FlameCreate( void ) +{ + int i; + Vector posGun, angleGun; + TraceResult trace; + + UTIL_MakeVectors( pev->angles ); + + // we only have one "flame", coming from the mouth + for ( i = 0; i < 4; i += 2 ) + { + if ( i < 2 ) + m_pFlame[i] = CBeam::BeamCreate( CTH_BEAM_SPRITE_NAME, 240 ); + else + m_pFlame[i] = CBeam::BeamCreate( CTH_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 * CTH_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 CCthulhu :: 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 CCthulhu :: FlameUpdate( void ) +{ + int i; + static float offset[2] = { 60, -60 }; + TraceResult trace; + Vector vecStart, angleGun; + BOOL streaks = FALSE; + + //for ( i = 0; i < 2; i++ ) + for ( i = 0; i < 1; 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 * CTH_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 ) + { + CthStreakSplash( 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_POISON ); + + 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 CCthulhu :: 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) + if (dist > 128) + { + flAdjustedDamage = flDamage - (dist - 128) * 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 CCthulhu :: 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++ ) + for ( i = 0; i < 4; i += 2 ) + { + if ( m_pFlame[i] ) + { + UTIL_Remove( m_pFlame[i] ); + m_pFlame[i] = NULL; + } + } +} + + +void CCthulhu :: 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 CCthulhu :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CCthulhu :: 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 CCthulhu :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/cthulhu.mdl"); + //UTIL_SetSize( pev, Vector( -64, -64, 0 ), Vector( 64, 64, 512 ) ); + UTIL_SetSize( pev, Vector( -64, -64, 0 ), Vector( 64, 64, 512 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + 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_flFieldOfView = 0.3;// width of forward view cone ( as a dotproduct result ) + // a more narrow field of view... + m_MonsterState = MONSTERSTATE_NONE; + m_bExplodeOnDeath = FALSE; + + MonsterInit(); + + // override this, Cthulhu has long look range... + m_flDistLook = 4096.0; + + m_pEyeGlow = CSprite::SpriteCreate( CTH_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 CCthulhu :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/cthulhu.mdl"); + + PRECACHE_MODEL( CTH_EYE_SPRITE_NAME ); + PRECACHE_MODEL( CTH_BEAM_SPRITE_NAME ); + PRECACHE_MODEL( CTH_BEAM_SPRITE2 ); + gCthulhuGibModel = PRECACHE_MODEL( CTH_GIB_MODEL ); + + 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( 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( pBreatheSounds ); i++ ) + PRECACHE_SOUND((char *)pBreatheSounds[i]); +} + +void CCthulhu :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "blowup")) + { + int flag = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + if ( flag ) m_bExplodeOnDeath = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +void CCthulhu::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + ALERT( at_aiconsole, "CCthulhu::TraceAttack\n"); + + if ( !IsAlive() ) + { + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + return; + } + + // UNDONE: Hit group specific damage? + if ( bitsDamageType && m_painSoundTime < gpGlobals->time ) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_CTH, 0, PITCH_NORM ); + m_painSoundTime = gpGlobals->time + RANDOM_FLOAT( 2.5, 4 ); + } + + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + +} + + + +int CCthulhu::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + ALERT( at_aiconsole, "CCthulhu::TakeDamage\n"); + + if ( IsAlive() ) + { + if ( bitsDamageType & DMG_BLAST ) + SetConditions( bits_COND_LIGHT_DAMAGE ); + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +void CCthulhu::DeathEffect( void ) +{ + 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; + if (m_bExplodeOnDeath) + { + int i; + for ( i = 0; i < 7; i+=2 ) + { + CthSpawnExplosion( 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->SetNextThink( 2.5 ); // Start in 2.5 seconds +} + + +void CCthulhu::Killed( entvars_t *pevAttacker, int iGib ) +{ + EyeOff(); + UTIL_Remove( m_pEyeGlow ); + m_pEyeGlow = NULL; + CBaseMonster::Killed( pevAttacker, GIB_NEVER ); +} + +//========================================================= +// CheckMeleeAttack1 +// Cthulhu swipe attack +// +//========================================================= +BOOL CCthulhu::CheckMeleeAttack1( float flDot, float flDist ) +{ +// ALERT(at_aiconsole, "CheckMelee(%f, %f)\n", flDot, flDist); + + if (flDot >= 0.7) + { + if (flDist <= CTH_ATTACKDIST) + return TRUE; + } + return FALSE; +} + + +// Flame thrower madness! +BOOL CCthulhu::CheckMeleeAttack2( float flDot, float flDist ) +{ +// ALERT(at_aiconsole, "CheckMelee(%f, %f)\n", flDot, flDist); + + if ( gpGlobals->time > m_flameTime ) + { + if (flDot >= 0.8 && flDist > CTH_ATTACKDIST) + { + if ( flDist <= CTH_FLAME_LENGTH ) + return TRUE; + } + } + return FALSE; +} + + +//========================================================= +// CheckRangeAttack1 +// flDot is the cos of the angle of the cone within which +// the attack can occur. +//========================================================= +BOOL CCthulhu::CheckRangeAttack1( float flDot, float flDist ) +{ + //if ( gpGlobals->time > m_seeTime ) + //{ + // if (flDot >= 0.7 && flDist > CTH_ATTACKDIST) + // { + // return TRUE; + // } + //} + return FALSE; +} + + + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CCthulhu::HandleAnimEvent(MonsterEvent_t *pEvent) +{ + switch( pEvent->event ) + { + case CTH_AE_SLASH_LEFT: + { + // HACKHACK!!! + CBaseEntity *pHurt = CthulhuCheckTraceHullAttack( CTH_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 CTH_AE_RIGHT_FOOT: + case CTH_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_CTH, 0, PITCH_NORM + RANDOM_LONG(-10,10) ); + break; + + case CTH_AE_BREATHE: + if ( !(pev->spawnflags & SF_MONSTER_GAG) || m_MonsterState != MONSTERSTATE_IDLE) + EMIT_SOUND_DYN ( edict(), CHAN_VOICE, pBreatheSounds[ RANDOM_LONG(0,ARRAYSIZE(pBreatheSounds)-1) ], 1.0, ATTN_CTH, 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 Cthulhu because his swing starts lower as +// a percentage of his height (otherwise he swings over the +// players head) +//========================================================= +CBaseEntity* CCthulhu::CthulhuCheckTraceHullAttack(float flDist, int iDamage, int iDmgType) +{ + TraceResult tr; + + UTIL_MakeVectors( pev->angles ); + Vector vecStart = pev->origin; + vecStart.z += 64; + //vecStart.z += 128; + 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 *CCthulhu::GetScheduleOfType( int Type ) +{ + // HACKHACK - turn off the flames if they are on and cthulhu goes scripted / dead + if ( FlameIsOn() ) + FlameDestroy(); + + switch( Type ) + { + case SCHED_MELEE_ATTACK2: + return slCthulhuFlame; + case SCHED_MELEE_ATTACK1: + return slCthulhuSwipe; + break; + } + + return CBaseMonster::GetScheduleOfType( Type ); +} + + +void CCthulhu::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_CTH, 0, PITCH_NORM ); + TaskComplete(); + break; + + // allow a scripted_action to make cthulhu shoot flames. + case TASK_PLAY_SCRIPT: + if ( m_pCine->IsAction() && m_pCine->m_fAction == 3) + { + FlameCreate(); + m_flWaitFinished = gpGlobals->time + 4.5; + m_flameTime = gpGlobals->time + 6; + m_flameX = 0; + m_flameY = 0; + } + CBaseMonster::RunTask( pTask ); + break; + + case TASK_DIE: + m_flWaitFinished = gpGlobals->time + 1.6; + DeathEffect(); + // FALL THROUGH + default: + CBaseMonster::StartTask( pTask ); + break; + } +} + +//========================================================= +// RunTask +//========================================================= +void CCthulhu::RunTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_DIE: + if ( m_bExplodeOnDeath && gpGlobals->time > m_flWaitFinished ) + { + pev->renderfx = kRenderFxExplode; + pev->rendercolor.x = 255; + pev->rendercolor.y = 0; + pev->rendercolor.z = 0; + StopAnimation(); + SetNextThink( 0.15 ); + SetThink( SUB_Remove ); + int i; + int parts = MODEL_FRAMES( gCthulhuGibModel ); + for ( i = 0; i < 10; i++ ) + { + CGib *pGib = GetClassPtr( (CGib *)NULL ); + + pGib->Spawn( CTH_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->SetNextThink( 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( gCthulhuGibModel ); //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_PLAY_SCRIPT: + if (!m_pCine->IsAction() || m_pCine->m_fAction != 3) + { + CBaseMonster::RunTask( pTask ); + break; + } + else + { + if (m_fSequenceFinished) + { + FlameDestroy(); + FlameControls( 0, 0 ); + SetBoneController( 0, 0 ); + SetBoneController( 1, 0 ); + m_pCine->SequenceDone( this ); + break; + } + //if not finished, carry on into task_flame_sweep! + } + 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; + if (m_pCine) // LRC- are we obeying a scripted_action? + pEnemy = m_hTargetEnt; + else + pEnemy = m_hEnemy; + if ( pEnemy ) + { + Vector org = pev->origin; + //org.z += 64; + org.z += 128; + Vector dir = pEnemy->BodyTarget(org) - org; + angles = UTIL_VecToAngles( dir ); + angles.x = -angles.x; + angles.y -= pev->angles.y; + //if ( dir.Length() > 400 ) + if ( dir.Length() > 600 ) + 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; + } +} + + + +// HACKHACK Cut and pasted from explode.cpp +void CthSpawnExplosion( 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->SetNextThink( time ); +} + + + +#endif \ No newline at end of file diff --git a/dlls/cthulhu/cultist.cpp b/dlls/cthulhu/cultist.cpp new file mode 100755 index 00000000..172a500c --- /dev/null +++ b/dlls/cthulhu/cultist.cpp @@ -0,0 +1,1996 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// cultist +//========================================================= + +//========================================================= +// Hit groups! +//========================================================= +/* + + 1 - Head + 2 - Stomach + 3 - Gun + +*/ + + +#include "extdll.h" +#include "util.h" +#include "plane.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_fCultistQuestion; // true if an idle cultist asked a question. Cleared when someone answers. + +extern DLL_GLOBAL int g_iSkillLevel; + +//========================================================= +// monster-specific DEFINE's +//========================================================= +#define CULTIST_CLIP_SIZE 6 // how many bullets in a clip? +#define CULTIST_VOL 0.70 // volume of cultist sounds +#define CULTIST_ATTN ATTN_NORM // attenutation of cultist sentences +#define CULTIST_LIMP_HEALTH 20 +#define CULTIST_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a cultist with a single headshot. +#define CULTIST_NUM_HEADS 3 // how many cultist heads are there? +#define CULTIST_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill +#define CULTIST_SENTENCE_VOLUME (float)0.70 // volume of cultist sentences + +#define CULTIST_KNIFE ( 1 << 0) +#define CULTIST_REVOLVER ( 1 << 1) +#define CULTIST_SHOTGUN ( 1 << 2) + +#define HEAD_GROUP 1 +#define HEAD_GRIM 0 +#define HEAD_BEARDY 1 +#define HEAD_UGLY 2 + +#define GUN_GROUP 2 +#define GUN_KNIFE 0 +#define GUN_REVOLVER 1 +#define GUN_SHOTGUN 2 +#define GUN_NONE 3 + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define CULTIST_AE_RELOAD ( 2 ) +#define CULTIST_AE_KICK ( 3 ) +#define CULTIST_AE_BURST1 ( 4 ) +#define CULTIST_AE_CAUGHT_ENEMY ( 5 ) // cultist established sight with an enemy (player only) that had previously eluded the squad. +#define CULTIST_AE_DROP_GUN ( 6 ) // cultist (probably dead) is dropping his mp5. +#define CULTIST_AE_KNIFE ( 7 ) + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_CULTIST_SUPPRESS = LAST_COMMON_SCHEDULE + 1, + SCHED_CULTIST_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_CULTIST_COVER_AND_RELOAD, + SCHED_CULTIST_SWEEP, + SCHED_CULTIST_FOUND_ENEMY, + SCHED_CULTIST_WAIT_FACE_ENEMY, + SCHED_CULTIST_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. + SCHED_CULTIST_ELOF_FAIL, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_CULTIST_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, + TASK_CULTIST_SPEAK_SENTENCE, + TASK_CULTIST_CHECK_FIRE, +}; + +//========================================================= +// monster-specific conditions +//========================================================= +#define bits_COND_CULTIST_NOFIRE ( bits_COND_SPECIAL1 ) + + +#include "cultist.h" + + +LINK_ENTITY_TO_CLASS( monster_cultist, CCultist ); + +TYPEDESCRIPTION CCultist::m_SaveData[] = +{ + DEFINE_FIELD( CCultist, m_flNextPainTime, FIELD_TIME ), +// DEFINE_FIELD( CCultist, m_flLastEnemySightTime, FIELD_TIME ), // don't save, go to zero + DEFINE_FIELD( CCultist, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( CCultist, m_fFirstEncounter, FIELD_BOOLEAN ), + DEFINE_FIELD( CCultist, m_cClipSize, FIELD_INTEGER ), + DEFINE_FIELD( CCultist, m_voicePitch, FIELD_INTEGER ), +// DEFINE_FIELD( CShotgun, m_iBrassShell, FIELD_INTEGER ), +// DEFINE_FIELD( CShotgun, m_iShotgunShell, FIELD_INTEGER ), + DEFINE_FIELD( CCultist, m_iSentence, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CCultist, CSquadMonster ); + +const char *CCultist::pCultistSentences[] = +{ + "CULT_GREN", // grenade scared cultist + "CULT_ALERT", // sees player + "CULT_COVER", // running to cover + "CULT_CHARGE", // running out to get the enemy + "CULT_TAUNT", // say rude things +}; + +enum +{ + CULTIST_SENT_NONE = -1, + CULTIST_SENT_GREN = 0, + CULTIST_SENT_ALERT, + CULTIST_SENT_COVER, + CULTIST_SENT_CHARGE, + CULTIST_SENT_TAUNT, +} CULTIST_SENTENCE_TYPES; + +//========================================================= +// Speak Sentence - say your cued up sentence. +// +// Some cultist sentences (take cover and charge) rely on actually +// being able to execute the intended action. It's really lame +// when a cultist 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 cultist has +// started moving. +//========================================================= +void CCultist :: SpeakSentence( void ) +{ + if ( m_iSentence == CULTIST_SENT_NONE ) + { + // no sentence cued up. + return; + } + + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), pCultistSentences[ m_iSentence ], CULTIST_SENTENCE_VOLUME, CULTIST_ATTN, 0, m_voicePitch); + JustSpoke(); + } +} + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void CCultist :: GibMonster ( void ) +{ + Vector vecGunPos; + Vector vecGunAngles; + + if (( GetBodygroup( GUN_GROUP ) != GUN_NONE ) && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) + {// throw a gun if the cultist has one + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun; + if (FBitSet( pev->weapons, CULTIST_SHOTGUN )) + { + pGun = DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else if (FBitSet( pev->weapons, CULTIST_REVOLVER )) + { + pGun = DropItem( "weapon_revolver", vecGunPos, vecGunAngles ); + } + else + { + pGun = DropItem( "weapon_knife", 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 cultists because they +// hear the DANGER sound that is made by hand grenades and +// other dangerous items. +//========================================================= +int CCultist :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_DANGER; +} + +//========================================================= +// someone else is talking - don't speak +//========================================================= +BOOL CCultist :: 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 CCultist :: JustSpoke( void ) +{ + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(1.5, 2.0); + m_iSentence = CULTIST_SENT_NONE; +} + +//========================================================= +// PrescheduleThink - this function runs after conditions +// are collected and before scheduling code is run. +//========================================================= +void CCultist :: 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. +//========================================================= +BOOL CCultist :: FCanCheckAttacks ( void ) +{ + if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CCultist :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + CBaseMonster *pEnemy; + + if ( m_hEnemy != NULL ) + { + pEnemy = m_hEnemy->MyMonsterPointer(); + + if ( !pEnemy ) + { + return FALSE; + } + } + + if (!FBitSet( pev->weapons, CULTIST_KNIFE )) + { + 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 cultists. +//========================================================= +BOOL CCultist :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if (FBitSet( pev->weapons, CULTIST_KNIFE )) + { + return FALSE; + } + + 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 cultists's (non-existant) second range +// attack. +//========================================================= +BOOL CCultist :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; +} + + +//========================================================= +// TraceAttack - make sure we're not taking it in the helmet +//========================================================= +void CCultist :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // check for helmet shot + if (ptr->iHitgroup == 11) + { + // it's head shot anyways + ptr->iHitgroup = HITGROUP_HEAD; + } + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +//========================================================= +// TakeDamage - overridden for the cultist because the cultist +// needs to forget that he is in cover if he's hurt. (Obviously +// not in a safe place anymore). +//========================================================= +int CCultist :: 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 CCultist :: 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 CCultist :: IdleSound( void ) +{ + if (FOkToSpeak() && (g_fCultistQuestion || RANDOM_LONG(0,1))) + { + if (!g_fCultistQuestion) + { + // ask question or make statement + switch (RANDOM_LONG(0,2)) + { + case 0: // check in + SENTENCEG_PlayRndSz(ENT(pev), "CULT_CHECK", CULTIST_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fCultistQuestion = 1; + break; + case 1: // question + SENTENCEG_PlayRndSz(ENT(pev), "CULT_QUEST", CULTIST_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fCultistQuestion = 2; + break; + case 2: // statement + SENTENCEG_PlayRndSz(ENT(pev), "CULT_IDLE", CULTIST_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + } + else + { + switch (g_fCultistQuestion) + { + case 1: // check in + SENTENCEG_PlayRndSz(ENT(pev), "CULT_CLEAR", CULTIST_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + case 2: // question + SENTENCEG_PlayRndSz(ENT(pev), "CULT_ANSWER", CULTIST_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + g_fCultistQuestion = 0; + } + JustSpoke(); + } +} + +//========================================================= +// CheckAmmo - overridden for the cultist because he actually +// uses ammo! (base class doesn't) +//========================================================= +void CCultist :: CheckAmmo ( void ) +{ + if ( m_cAmmoLoaded <= 0 ) + { + SetConditions(bits_COND_NO_AMMO_LOADED); + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CCultist :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_CULTIST; +} + +//========================================================= +//========================================================= +CBaseEntity *CCultist :: 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 CCultist :: GetGunPosition( ) +{ + // because origin is at the centre of the box, not at the bottom + if (m_fStanding ) + { + return pev->origin + Vector( 0, 0, 24 ); +// return pev->origin + Vector( 0, 0, 60 ); + } + else + { + return pev->origin + Vector( 0, 0, 12 ); +// return pev->origin + Vector( 0, 0, 48 ); + } +} + +//========================================================= +// Shoot +//========================================================= +void CCultist :: 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_9MM ); // shoot +-5 degrees + + pev->effects |= EF_MUZZLEFLASH; + + m_cAmmoLoaded--;// take away a bullet! + + Vector angDir = UTIL_VecToAngles( vecShootDir ); + SetBlending( 0, angDir.x ); +} + +//========================================================= +// Shoot +//========================================================= +void CCultist :: 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.cultistShotgunPellets, vecShootOrigin, vecShootDir, VECTOR_CONE_15DEGREES, 2048, BULLET_PLAYER_SHOTGUN, 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 CCultist :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case CULTIST_AE_DROP_GUN: + { + if (pev->spawnflags & SF_MONSTER_NO_WPN_DROP) break; + + 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, CULTIST_SHOTGUN )) + { + DropItem( "weapon_shotgun", vecGunPos, vecGunAngles ); + } + else if (FBitSet( pev->weapons, CULTIST_REVOLVER )) + { + DropItem( "weapon_revolver", vecGunPos, vecGunAngles ); + } + else + { + DropItem( "weapon_knife", vecGunPos, vecGunAngles ); + } + + } + break; + + case CULTIST_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 CULTIST_AE_BURST1: + { + if ( FBitSet( pev->weapons, CULTIST_KNIFE )) + { + // TO DO: need to enter knife logic + } + else if ( FBitSet( pev->weapons, CULTIST_REVOLVER )) + { + 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, "weapons/revolver_shot1.wav", 1, ATTN_NORM ); + } + else + { + EMIT_SOUND( ENT(pev), CHAN_WEAPON, "weapons/revolver_shot2.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 CULTIST_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.cultistDmgKick, DMG_CLUB ); + } + } + break; + + case CULTIST_AE_KNIFE: + { + Vector oldorig = pev->origin; + CBaseEntity *pHurt = NULL; + // check down below in stages for snakes... + for (int dz = 0; dz >=-3; dz--) + { + pev->origin = oldorig; + pev->origin.z += dz * 12; + pHurt = CheckTraceHullAttack( 70, gSkillData.priestDmgKnife, DMG_SLASH ); + if (pHurt) + { + break; + } + } + pev->origin = oldorig; + if ( pHurt ) + { + // Play a random attack hit sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, "zombie/claw_strike1.wav", 1.0, ATTN_NORM, 0, m_voicePitch ); + } + else + { + // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, "zombie/claw_miss1.wav", 1.0, ATTN_NORM, 0, m_voicePitch ); + } + } + break; + + case CULTIST_AE_CAUGHT_ENEMY: + { + if ( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz(ENT(pev), "CULT_ALERT", CULTIST_SENTENCE_VOLUME, CULTIST_ATTN, 0, m_voicePitch); + JustSpoke(); + } + + } + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CCultist :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/cultist.mdl"); + //UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + UTIL_SetSize(pev, Vector(-16,-16,-36), Vector(16,16,36)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + if (pev->health == 0) + pev->health = gSkillData.cultistHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextPainTime = gpGlobals->time; + m_iSentence = CULTIST_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 cultist spawns, because he hasn't encountered an enemy yet. + + m_HackedGunPos = Vector ( 0, 0, 19 ); + //m_HackedGunPos = Vector ( 0, 0, 55 ); + + if (pev->weapons == 0) + { + // initialize to original values + pev->weapons = CULTIST_KNIFE; + // pev->weapons = CULTIST_REVOLVER; + // pev->weapons = CULTIST_SHOTGUN; + } + + if ( pev->body == -1 ) + {// -1 chooses a random head + pev->body = RANDOM_LONG(0, 2);// pick a head, any head + } + + if (FBitSet( pev->weapons, CULTIST_SHOTGUN )) + { + SetBodygroup( GUN_GROUP, GUN_SHOTGUN ); + m_cClipSize = 2; + } + else if (FBitSet( pev->weapons, CULTIST_REVOLVER )) + { + SetBodygroup( GUN_GROUP, GUN_REVOLVER ); + m_cClipSize = CULTIST_CLIP_SIZE; + } + else // knife + { + SetBodygroup( GUN_GROUP, GUN_KNIFE ); + m_cClipSize = 1; + } + m_cAmmoLoaded = m_cClipSize; + + CTalkMonster::g_talkWaitTime = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CCultist :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/cultist.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_pain3.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain4.wav" ); + + PRECACHE_SOUND( "hgrunt/gr_reload1.wav" ); + + PRECACHE_SOUND( "weapons/glauncher.wav" ); + + PRECACHE_SOUND( "weapons/sbarrel1.wav" ); + PRECACHE_SOUND( "weapons/revolver_shot1.wav" ); + + PRECACHE_SOUND("zombie/claw_strike1.wav");// because we use the basemonster SWIPE animation event + PRECACHE_SOUND("zombie/claw_miss1.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 CCultist :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_CULTIST_CHECK_FIRE: + if ( !NoFriendlyFire() ) + { + SetConditions( bits_COND_CULTIST_NOFIRE ); + } + TaskComplete(); + break; + + case TASK_CULTIST_SPEAK_SENTENCE: + SpeakSentence(); + TaskComplete(); + break; + + case TASK_WALK_PATH: + case TASK_RUN_PATH: + // cultist 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_CULTIST_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 CCultist :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_CULTIST_FACE_TOSS_DIR: + { + // project a point along the toss vector and turn to face that point. + MakeIdealYaw( pev->origin ); + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + default: + { + CSquadMonster :: RunTask( pTask ); + break; + } + } +} + +//========================================================= +// PainSound +//========================================================= +void CCultist :: PainSound ( void ) +{ + if ( gpGlobals->time > m_flNextPainTime ) + { + switch ( RANDOM_LONG(0,1) ) + { + 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; + } + + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CCultist :: 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 +//========================================================= + +//========================================================= +// CultistFail +//========================================================= +Task_t tlCultistFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slCultistFail[] = +{ + { + tlCultistFail, + ARRAYSIZE ( tlCultistFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + 0, + "Cultist Fail" + }, +}; + +//========================================================= +// Cultist Combat Fail +//========================================================= +Task_t tlCultistCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slCultistCombatFail[] = +{ + { + tlCultistCombatFail, + ARRAYSIZE ( tlCultistCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Cultist Combat Fail" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlCultistVictoryDance[] = +{ + { 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 slCultistVictoryDance[] = +{ + { + tlCultistVictoryDance, + ARRAYSIZE ( tlCultistVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "CultistVictoryDance" + }, +}; + +//========================================================= +// Establish line of fire - move to a position that allows +// the cultist to attack. +//========================================================= +Task_t tlCultistEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_CULTIST_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_CULTIST_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slCultistEstablishLineOfFire[] = +{ + { + tlCultistEstablishLineOfFire, + ARRAYSIZE ( tlCultistEstablishLineOfFire ), + 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, + "CultistEstablishLineOfFire" + }, +}; + +//========================================================= +// CultistFoundEnemy - cultist established sight with an enemy +// that was hiding from the squad. +//========================================================= +Task_t tlCultistFoundEnemy[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_SIGNAL1 }, +}; + +Schedule_t slCultistFoundEnemy[] = +{ + { + tlCultistFoundEnemy, + ARRAYSIZE ( tlCultistFoundEnemy ), + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "CultistFoundEnemy" + }, +}; + +//========================================================= +// CultistCombatFace Schedule +//========================================================= +Task_t tlCultistCombatFace1[] = +{ + { 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_CULTIST_SWEEP }, +}; + +Schedule_t slCultistCombatFace[] = +{ + { + tlCultistCombatFace1, + ARRAYSIZE ( tlCultistCombatFace1 ), + 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 cultist gets hurt. +//========================================================= +Task_t tlCultistSignalSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slCultistSignalSuppress[] = +{ + { + tlCultistSignalSuppress, + ARRAYSIZE ( tlCultistSignalSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_CULTIST_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "SignalSuppress" + }, +}; + +Task_t tlCultistSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slCultistSuppress[] = +{ + { + tlCultistSuppress, + ARRAYSIZE ( tlCultistSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_CULTIST_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Suppress" + }, +}; + + +//========================================================= +// cultist wait in cover - we don't allow danger or the ability +// to attack to break a cultist's run to cover schedule, but +// when a cultist is in cover, we do want them to attack if they can. +//========================================================= +Task_t tlCultistWaitInCover[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slCultistWaitInCover[] = +{ + { + tlCultistWaitInCover, + ARRAYSIZE ( tlCultistWaitInCover ), + 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, + "CultistWaitInCover" + }, +}; + +//========================================================= +// run to cover. +// !!!BUGBUG - set a decent fail schedule here. +//========================================================= +Task_t tlCultistTakeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_CULTIST_TAKECOVER_FAILED }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_CULTIST_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_CULTIST_WAIT_FACE_ENEMY }, +}; + +Schedule_t slCultistTakeCover[] = +{ + { + tlCultistTakeCover1, + ARRAYSIZE ( tlCultistTakeCover1 ), + 0, + 0, + "TakeCover" + }, +}; + + +//========================================================= +// hide from the loudest sound source (to run from grenade) +//========================================================= +Task_t tlCultistTakeCoverFromBestSound[] = +{ + { 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 slCultistTakeCoverFromBestSound[] = +{ + { + tlCultistTakeCoverFromBestSound, + ARRAYSIZE ( tlCultistTakeCoverFromBestSound ), + 0, + 0, + "CultistTakeCoverFromBestSound" + }, +}; + +//========================================================= +// Cultist reload schedule +//========================================================= +Task_t tlCultistHideReload[] = +{ + { 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 slCultistHideReload[] = +{ + { + tlCultistHideReload, + ARRAYSIZE ( tlCultistHideReload ), + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "CultistHideReload" + } +}; + +//========================================================= +// Do a turning sweep of the area +//========================================================= +Task_t tlCultistSweep[] = +{ + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slCultistSweep[] = +{ + { + tlCultistSweep, + ARRAYSIZE ( tlCultistSweep ), + + 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, + + "Cultist Sweep" + }, +}; + +//========================================================= +// primary range attack. Overriden. +//========================================================= +Task_t tlCultistRangeAttack1A[] = +{ + { TASK_STOP_MOVING, (float)0 }, +// { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_CROUCH }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slCultistRangeAttack1A[] = +{ + { + tlCultistRangeAttack1A, + ARRAYSIZE ( tlCultistRangeAttack1A ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_HEAR_SOUND | + bits_COND_CULTIST_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Range Attack1A" + }, +}; + + +//========================================================= +// primary range attack. Overriden. +//========================================================= +Task_t tlCultistRangeAttack1B[] = +{ + { TASK_STOP_MOVING, (float)0 }, +// { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_IDLE_ANGRY }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_IDLE }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_CULTIST_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slCultistRangeAttack1B[] = +{ + { + tlCultistRangeAttack1B, + ARRAYSIZE ( tlCultistRangeAttack1B ), + bits_COND_NEW_ENEMY | + bits_COND_ENEMY_DEAD | + bits_COND_HEAVY_DAMAGE | + bits_COND_ENEMY_OCCLUDED | + bits_COND_NO_AMMO_LOADED | + bits_COND_CULTIST_NOFIRE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "Range Attack1B" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CCultist ) +{ + slCultistFail, + slCultistCombatFail, + slCultistVictoryDance, + slCultistEstablishLineOfFire, + slCultistFoundEnemy, + slCultistCombatFace, + slCultistSignalSuppress, + slCultistSuppress, + slCultistWaitInCover, + slCultistTakeCover, + slCultistTakeCoverFromBestSound, + slCultistHideReload, + slCultistSweep, + slCultistRangeAttack1A, + slCultistRangeAttack1B, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CCultist, CSquadMonster ); + +//========================================================= +// SetActivity +//========================================================= +void CCultist :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_MELEE_ATTACK1: + + // randomly stand or crouch + if (RANDOM_LONG(0,99) == 0) + m_fStanding = 0; + else + m_fStanding = 1; + + // a short enemy...probably a snake... + if ((m_hEnemy != NULL) && (m_hEnemy->pev->maxs.z < 36) && FBitSet( pev->weapons, CULTIST_KNIFE)) + { + m_fStanding = 0; + } + + if (FBitSet( pev->weapons, CULTIST_KNIFE)) + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "ref_shoot_knife" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouch_shoot_knife" ); + } + } + break; + case ACT_RANGE_ATTACK1: + + // randomly stand or crouch + if (RANDOM_LONG(0,99) == 0) + m_fStanding = 0; + else + m_fStanding = 1; + + // cultist is either shooting standing or shooting crouched + if (FBitSet( pev->weapons, CULTIST_REVOLVER)) + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "ref_shoot_onehanded" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouch_shoot_onehanded" ); + } + } + else if (FBitSet( pev->weapons, CULTIST_SHOTGUN)) + { + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "ref_shoot_shotgun" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouch_shoot_shotgun" ); + } + } + break; + case ACT_RUN: + if ( pev->health <= CULTIST_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_RUN_HURT ); + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + { + iSequence = LookupActivity ( ACT_RUN ); + } + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_WALK: + if ( pev->health <= CULTIST_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_WALK_HURT ); + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + { + iSequence = LookupActivity ( ACT_WALK ); + } + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_IDLE: + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { +// NewActivity = ACT_IDLE_ANGRY; + NewActivity = ACT_IDLE; + } + iSequence = LookupActivity ( NewActivity ); + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + { + iSequence = LookupActivity ( ACT_IDLE ); + } + 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 *CCultist :: GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = CULTIST_SENT_NONE; + + // cultists 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 cultist's signal that a grenade has landed nearby, + // and the cultist 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), "CULT_GREN", CULTIST_SENTENCE_VOLUME, CULTIST_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 cultists 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), "CULT_ALERT", CULTIST_SENTENCE_VOLUME, CULTIST_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), "CULT_MONST", CULTIST_SENTENCE_VOLUME, CULTIST_ATTN, 0, m_voicePitch); + */ + + JustSpoke(); + } + + if ( HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_CULTIST_SUPPRESS ); + } + else + { + return GetScheduleOfType ( SCHED_CULTIST_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_CULTIST_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 cultist was hit and is going to run to cover. + if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "CULT_COVER", CULTIST_SENTENCE_VOLUME, CULTIST_ATTN, 0, m_voicePitch); + m_iSentence = CULTIST_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 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_CULTIST_FOUND_ENEMY ); + } + } + + if ( OccupySlot ( bits_SLOTS_CULTIST_ENGAGE ) ) + { + // try to take an available ENGAGE slot + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + else + { + // hide! + return GetScheduleOfType( SCHED_TAKE_COVER_FROM_ENEMY ); + } + } +// can't see enemy + else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if ( OccupySlot( bits_SLOTS_CULTIST_ENGAGE ) ) + { + //!!!KELLY - cultist cannot see the enemy and has just decided to + // charge the enemy's position. + if (FOkToSpeak())// && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "CULT_CHARGE", CULTIST_SENTENCE_VOLUME, CULTIST_ATTN, 0, m_voicePitch); + m_iSentence = CULTIST_SENT_CHARGE; + //JustSpoke(); + } + + return GetScheduleOfType( SCHED_CULTIST_ESTABLISH_LINE_OF_FIRE ); + } + else + { + //!!!KELLY - cultist is going to stay put for a couple seconds to see if + // the enemy wanders back out into the open, or approaches the + // cultist's covered position. Good place for a taunt, I guess? + if (FOkToSpeak() && RANDOM_LONG(0,1)) + { + SENTENCEG_PlayRndSz( ENT(pev), "CULT_TAUNT", CULTIST_SENTENCE_VOLUME, CULTIST_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_CULTIST_ESTABLISH_LINE_OF_FIRE ); + } + } + } + + // no special cases here, call the base class + return CSquadMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* CCultist :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + { + if ( InSquad() ) + { + return &slCultistTakeCover[ 0 ]; + } + else + { + return &slCultistTakeCover[ 0 ]; + } + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slCultistTakeCoverFromBestSound[ 0 ]; + } + case SCHED_CULTIST_TAKECOVER_FAILED: + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot( bits_SLOTS_CULTIST_ENGAGE ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType ( SCHED_FAIL ); + } + break; + case SCHED_CULTIST_ELOF_FAIL: + { + // human cultist is unable to move to a position that allows him to attack the enemy. + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + case SCHED_CULTIST_ESTABLISH_LINE_OF_FIRE: + { + return &slCultistEstablishLineOfFire[ 0 ]; + } + break; + case SCHED_RANGE_ATTACK1: + { + if (m_fStanding) + return &slCultistRangeAttack1B[ 0 ]; + else + return &slCultistRangeAttack1A[ 0 ]; + } + case SCHED_COMBAT_FACE: + { + return &slCultistCombatFace[ 0 ]; + } + case SCHED_CULTIST_WAIT_FACE_ENEMY: + { + return &slCultistWaitInCover[ 0 ]; + } + case SCHED_CULTIST_SWEEP: + { + return &slCultistSweep[ 0 ]; + } + case SCHED_CULTIST_COVER_AND_RELOAD: + { + return &slCultistHideReload[ 0 ]; + } + case SCHED_CULTIST_FOUND_ENEMY: + { + return &slCultistFoundEnemy[ 0 ]; + } + case SCHED_VICTORY_DANCE: + { + if ( InSquad() ) + { + if ( !IsLeader() ) + { + return &slCultistFail[ 0 ]; + } + } + + return &slCultistVictoryDance[ 0 ]; + } + case SCHED_CULTIST_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 &slCultistSignalSuppress[ 0 ]; + } + else + { + return &slCultistSuppress[ 0 ]; + } + } + case SCHED_FAIL: + { + if ( m_hEnemy != NULL ) + { + // cultist has an enemy, so pick a different default fail schedule most likely to help recover. + return &slCultistCombatFail[ 0 ]; + } + + return &slCultistFail[ 0 ]; + } + default: + { + return CSquadMonster :: GetScheduleOfType ( Type ); + } + } +} + + +//========================================================= +// DEAD CULTIST PROP +//========================================================= + +char *CDeadCultist::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" }; + +void CDeadCultist::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_cultist_dead, CDeadCultist ); + +//========================================================= +// ********** DeadCultist SPAWN ********** +//========================================================= +void CDeadCultist :: Spawn( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/cultist.mdl"); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/cultist.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 cultist with bad pose\n" ); + } + + // Corpses have less health + pev->health = 8; + + // map old bodies onto new bodies + switch( pev->body ) + { + case 0: // Cultist with Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_GRIM ); + SetBodygroup( GUN_GROUP, GUN_REVOLVER ); + break; + case 1: // Commander with Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_BEARDY ); + SetBodygroup( GUN_GROUP, GUN_REVOLVER ); + break; + case 2: // Cultist no Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_UGLY ); + SetBodygroup( GUN_GROUP, GUN_NONE ); + break; + case 3: // Commander no Gun + pev->body = 0; + pev->skin = 0; + SetBodygroup( HEAD_GROUP, HEAD_GRIM ); + SetBodygroup( GUN_GROUP, GUN_NONE ); + break; + } + + MonsterInitDead(); +} diff --git a/dlls/cthulhu/cultist.h b/dlls/cthulhu/cultist.h new file mode 100755 index 00000000..326de46f --- /dev/null +++ b/dlls/cthulhu/cultist.h @@ -0,0 +1,79 @@ + +#ifndef CULTIST_H +#define CULTIST_H + +class CCultist : 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 ); + + BOOL FOkToSpeak( void ); + void JustSpoke( void ); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + float m_flNextPainTime; + float m_flLastEnemySightTime; + + 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 *pCultistSentences[]; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class CDeadCultist : 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]; +}; + + +#endif diff --git a/dlls/cthulhu/defaultai.h b/dlls/cthulhu/defaultai.h new file mode 100755 index 00000000..b3904301 --- /dev/null +++ b/dlls/cthulhu/defaultai.h @@ -0,0 +1,99 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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[]; +extern Schedule_t slChaseEnemyLKP[]; + +//========================================================= +// 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/cthulhu/drainlife.cpp b/dlls/cthulhu/drainlife.cpp new file mode 100755 index 00000000..582ed469 --- /dev/null +++ b/dlls/cthulhu/drainlife.cpp @@ -0,0 +1,411 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "effects.h" +#include "customentity.h" +#include "gamerules.h" + +#include "drainlife.h" + +#define DRAINLIFE_PRIMARY_VOLUME 250 +#define DRAINLIFE_BEAM_SPRITE "sprites/xbeam1.spr" +#define DRAINLIFE_FLARE_SPRITE "sprites/XSpark1.spr" +#define DRAINLIFE_SOUND_OFF "weapons/egon_off1.wav" +#define DRAINLIFE_SOUND_RUN "weapons/egon_run3.wav" +#define DRAINLIFE_SOUND_STARTUP "weapons/egon_windup2.wav" + + +enum DrainLife_e { + DRAINLIFE_OPEN = 0, + DRAINLIFE_IDLE1, + DRAINLIFE_IDLE2, + DRAINLIFE_IDLE3, + DRAINLIFE_CAST, + DRAINLIFE_CLOSE +}; + + +LINK_ENTITY_TO_CLASS( weapon_drainlife, CDrainLife ); + +TYPEDESCRIPTION CDrainLife::m_SaveData[] = +{ + DEFINE_FIELD( CDrainLife, m_pBeam, FIELD_CLASSPTR ), + DEFINE_FIELD( CDrainLife, m_pNoise, FIELD_CLASSPTR ), + DEFINE_FIELD( CDrainLife, m_pSprite, FIELD_CLASSPTR ), + DEFINE_FIELD( CDrainLife, m_shootTime, FIELD_TIME ), + DEFINE_FIELD( CDrainLife, m_fireState, FIELD_INTEGER ), + DEFINE_FIELD( CDrainLife, m_flAmmoUseTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CDrainLife, CBasePlayerWeapon ); + + +void CDrainLife::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_DRAINLIFE; + SET_MODEL(ENT(pev), "models/w_drainlife.mdl"); + m_iClip = -1; + + FallInit();// get ready to fall down. +} + + +void CDrainLife::Precache( void ) +{ + PRECACHE_MODEL("models/w_drainlife.mdl"); + PRECACHE_MODEL("models/v_drainlife.mdl"); +// PRECACHE_MODEL("models/p_drainlife.mdl"); + + PRECACHE_MODEL("models/w_9mmclip.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND( DRAINLIFE_SOUND_OFF ); + PRECACHE_SOUND( DRAINLIFE_SOUND_RUN ); + PRECACHE_SOUND( DRAINLIFE_SOUND_STARTUP ); + + PRECACHE_MODEL( DRAINLIFE_BEAM_SPRITE ); + PRECACHE_MODEL( DRAINLIFE_FLARE_SPRITE ); + + PRECACHE_SOUND ("weapons/357_cock1.wav"); +} + + +BOOL CDrainLife::Deploy( void ) +{ + m_deployed = FALSE; + return DefaultDeploy( "models/v_drainlife.mdl", "", DRAINLIFE_OPEN, "shrivelling" ); +} + +int CDrainLife::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + + + +void CDrainLife::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + // m_flTimeWeaponIdle = gpGlobals->time + UTIL_RandomFloat ( 10, 15 ); + SendWeaponAnim( DRAINLIFE_CLOSE ); + + if ( m_fireState != FIRE_OFF ) + EndAttack(); +} + +int CDrainLife::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = NULL; + p->iMaxAmmo1 = -1; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 2; + p->iId = m_iId = WEAPON_DRAINLIFE; + p->iFlags = 0; + p->iWeight = DRAINLIFE_WEIGHT; + + return 1; +} + + +//#define DRAINLIFE_PULSE_INTERVAL 0.25 +//#define DRAINLIFE_DISCHARGE_INTERVAL 0.5 + +#define DRAINLIFE_PULSE_INTERVAL 0.1 +#define DRAINLIFE_DISCHARGE_INTERVAL 0.1 + +float CDrainLife::GetPulseInterval( void ) +{ + if ( g_pGameRules->IsMultiplayer() ) + { + return 0.1; + } + + return DRAINLIFE_PULSE_INTERVAL; +} + +float CDrainLife::GetDischargeInterval( void ) +{ + if ( g_pGameRules->IsMultiplayer() ) + { + return 0.1; + } + + return DRAINLIFE_DISCHARGE_INTERVAL; +} + +void CDrainLife::Attack( void ) +{ + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecSrc = m_pPlayer->GetGunPosition( ); + + switch( m_fireState ) + { + case FIRE_OFF: + { + m_flAmmoUseTime = gpGlobals->time;// start using ammo ASAP. + + SendWeaponAnim( DRAINLIFE_CAST ); + + m_pPlayer->m_iWeaponVolume = DRAINLIFE_PRIMARY_VOLUME; + m_flTimeWeaponIdle = gpGlobals->time + 0.1; + m_shootTime = gpGlobals->time + 2; + + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, DRAINLIFE_SOUND_STARTUP, 0.2, ATTN_NORM, 0, 100 ); + + pev->dmgtime = gpGlobals->time + GetPulseInterval(); + m_fireState = FIRE_CHARGE; + } + break; + + case FIRE_CHARGE: + { + Fire( vecSrc, vecAiming ); + m_pPlayer->m_iWeaponVolume = DRAINLIFE_PRIMARY_VOLUME; + + if ( m_shootTime != 0 && gpGlobals->time > m_shootTime ) + { + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_STATIC, DRAINLIFE_SOUND_RUN, 0.2, ATTN_NORM, 0, 100 ); + + m_shootTime = 0; + } + } + break; + } +} + +void CDrainLife::PrimaryAttack( void ) +{ + Attack(); +} + +void CDrainLife::Fire( const Vector &vecOrigSrc, const Vector &vecDir ) +{ + Vector vecDest = vecOrigSrc + vecDir * 2048; + edict_t *pentIgnore; + TraceResult tr; + + pentIgnore = m_pPlayer->edict(); + Vector tmpSrc = vecOrigSrc + gpGlobals->v_up * -8 + gpGlobals->v_right * 3; + + // ALERT( at_console, "." ); + + UTIL_TraceLine( vecOrigSrc, vecDest, dont_ignore_monsters, pentIgnore, &tr ); + + if (tr.fAllSolid) + return; + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + if (pEntity == NULL) + return; + + if ( g_pGameRules->IsMultiplayer() ) + { + if ( m_pSprite && pEntity->pev->takedamage ) + { + m_pSprite->pev->effects &= ~EF_NODRAW; + } + else if ( m_pSprite ) + { + m_pSprite->pev->effects |= EF_NODRAW; + } + } + + float timedist; + + if ( pev->dmgtime < gpGlobals->time ) + { + bool bHuman = false; + + if (FClassnameIs(pEntity->pev, "player")) bHuman = true; + else if (FClassnameIs(pEntity->pev, "monster_cultist")) bHuman = true; + else if (FClassnameIs(pEntity->pev, "monster_priest")) bHuman = true; + else if (FClassnameIs(pEntity->pev, "monster_policeman")) bHuman = true; + else if (FClassnameIs(pEntity->pev, "monster_civilian")) bHuman = true; + else if (FClassnameIs(pEntity->pev, "monster_butler")) bHuman = true; + else if (FClassnameIs(pEntity->pev, "monster_sirhenry")) bHuman = true; + else if (FClassnameIs(pEntity->pev, "monster_ranulf")) bHuman = true; + else if (FClassnameIs(pEntity->pev, "monster_gangster")) bHuman = true; + else if (FClassnameIs(pEntity->pev, "monster_sitting_scientist")) bHuman = true; + else if (FClassnameIs(pEntity->pev, "monster_scientist")) bHuman = true; + else if (FClassnameIs(pEntity->pev, "monster_sitting_butler")) bHuman = true; + else if (FClassnameIs(pEntity->pev, "monster_sitting_civilian")) bHuman = true; + else if (FClassnameIs(pEntity->pev, "monster_sitting_sirhenry")) bHuman = true; + + if (bHuman) + { + float flOldTgtHealth = pEntity->pev->health; + pEntity->TakeDamage(m_pPlayer->pev,m_pPlayer->pev, gSkillData.plrDmgDrainLife, DMG_ENERGYBEAM); + float flTgtDamage = flOldTgtHealth - max(0,pEntity->pev->health); + + // lose one point of san for every 5 health taken + m_pPlayer->LoseSanity(flTgtDamage/5.0); + + float flOldHealth = m_pPlayer->pev->health; + m_pPlayer->TakeHealth(flTgtDamage, DMG_ENERGYBEAM); + } + + pev->dmgtime = gpGlobals->time + GetPulseInterval(); + } + timedist = ( pev->dmgtime - gpGlobals->time ) / GetPulseInterval(); + + if ( timedist < 0 ) + timedist = 0; + else if ( timedist > 1 ) + timedist = 1; + timedist = 1-timedist; + + UpdateEffect( tmpSrc, tr.vecEndPos, timedist ); +} + + +void CDrainLife::UpdateEffect( const Vector &startPoint, const Vector &endPoint, float timeBlend ) +{ + if ( !m_pBeam ) + { + CreateEffect(); + } + + m_pBeam->SetStartPos( endPoint ); + m_pBeam->SetBrightness( 255 - (timeBlend*180) ); + m_pBeam->SetWidth( 40 - (timeBlend*20) ); + + m_pBeam->SetColor( 120 + (25*timeBlend), 60 + (30*timeBlend), 64 + 80*fabs(sin(gpGlobals->time*10)) ); + + UTIL_SetOrigin( m_pSprite, endPoint ); + m_pSprite->pev->frame += 8 * gpGlobals->frametime; + if ( m_pSprite->pev->frame > m_pSprite->Frames() ) + m_pSprite->pev->frame = 0; + + m_pNoise->SetStartPos( endPoint ); +} + + +void CDrainLife::CreateEffect( void ) +{ + DestroyEffect(); + + m_pBeam = CBeam::BeamCreate( DRAINLIFE_BEAM_SPRITE, 40 ); + m_pBeam->PointEntInit( pev->origin, m_pPlayer->entindex() ); + m_pBeam->SetFlags( BEAM_FSINE ); + m_pBeam->SetEndAttachment( 1 ); + m_pBeam->pev->spawnflags |= SF_BEAM_TEMPORARY; // Flag these to be destroyed on save/restore or level transition + + m_pNoise = CBeam::BeamCreate( DRAINLIFE_BEAM_SPRITE, 55 ); + m_pNoise->PointEntInit( pev->origin, m_pPlayer->entindex() ); + m_pNoise->SetScrollRate( 25 ); + m_pNoise->SetBrightness( 100 ); + m_pNoise->SetEndAttachment( 1 ); + m_pNoise->pev->spawnflags |= SF_BEAM_TEMPORARY; + + m_pSprite = CSprite::SpriteCreate( DRAINLIFE_FLARE_SPRITE, pev->origin, FALSE ); + m_pSprite->pev->scale = 1.0; + m_pSprite->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_pSprite->pev->spawnflags |= SF_SPRITE_TEMPORARY; + + m_pBeam->SetScrollRate( 110 ); + m_pBeam->SetNoise( 5 ); + m_pNoise->SetColor( 255, 80, 120 ); + m_pNoise->SetNoise( 2 ); +} + + +void CDrainLife::DestroyEffect( void ) +{ + if ( m_pBeam ) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } + if ( m_pNoise ) + { + UTIL_Remove( m_pNoise ); + m_pNoise = NULL; + } + if ( m_pSprite ) + { + UTIL_Remove( m_pSprite ); + m_pSprite = NULL; + } +} + +void CDrainLife::WeaponIdle( void ) +{ + ResetEmptySound( ); + + if ( m_flTimeWeaponIdle > gpGlobals->time ) + return; + + if ( m_fireState != FIRE_OFF ) + EndAttack(); + + + int iAnim; + + float flRand = RANDOM_FLOAT(0,1); + + if ( flRand <= 0.4 ) + { + iAnim = DRAINLIFE_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT(2,3); + } + else if ( flRand <= 0.75 ) + { + iAnim = DRAINLIFE_IDLE2; + m_flTimeWeaponIdle = gpGlobals->time + 3; + } + else + { + iAnim = DRAINLIFE_IDLE3; + m_flTimeWeaponIdle = gpGlobals->time + 3; + } + + SendWeaponAnim( iAnim ); + m_deployed = TRUE; +} + +void CDrainLife::EndAttack( void ) +{ + STOP_SOUND( ENT(m_pPlayer->pev), CHAN_STATIC, DRAINLIFE_SOUND_RUN ); + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, DRAINLIFE_SOUND_OFF, 0.2, ATTN_NORM, 0, 100); + m_fireState = FIRE_OFF; + m_flTimeWeaponIdle = gpGlobals->time + 2.0; + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->time + 0.5; + DestroyEffect(); +} + + + +#endif \ No newline at end of file diff --git a/dlls/cthulhu/drainlife.h b/dlls/cthulhu/drainlife.h new file mode 100755 index 00000000..f3db3f1f --- /dev/null +++ b/dlls/cthulhu/drainlife.h @@ -0,0 +1,60 @@ + +#ifndef DRAINLIFE_H +#define DRAINLIFE_H + +class CDrainLife : public CBasePlayerWeapon +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 4; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + + void CreateEffect( void ); + void UpdateEffect( const Vector &startPoint, const Vector &endPoint, float timeBlend ); + void DestroyEffect( void ); + + void EndAttack( void ); + void Attack( void ); + void PrimaryAttack( void ); + void WeaponIdle( void ); + static int g_fireAnims1[]; + static int g_fireAnims2[]; + + float m_flAmmoUseTime;// since we use < 1 point of ammo per update, we subtract ammo on a timer. + + float GetPulseInterval( void ); + float GetDischargeInterval( void ); + + void Fire( const Vector &vecOrigSrc, const Vector &vecDir ); + + enum DRAINLIFE_FIRESTATE { FIRE_OFF, FIRE_CHARGE }; + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } + +private: + float m_shootTime; + CBeam *m_pBeam; + CBeam *m_pNoise; + CSprite *m_pSprite; + DRAINLIFE_FIRESTATE m_fireState; + BOOL m_deployed; +}; + +#endif + diff --git a/dlls/cthulhu/dynamite.cpp b/dlls/cthulhu/dynamite.cpp new file mode 100755 index 00000000..2bccf13f --- /dev/null +++ b/dlls/cthulhu/dynamite.cpp @@ -0,0 +1,225 @@ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "effects.h" + + +#include "dynamite.h" +#include "monster_dynamite.h" + +//#define DYNAMITE_FUSE_SPRITE "sprites/fire.spr" + + +LINK_ENTITY_TO_CLASS( weapon_dynamite, CDynamite ); + + +void CDynamite::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_DYNAMITE; + SET_MODEL(ENT(pev), "models/w_dynamite.mdl"); + + pev->dmg = gSkillData.plrDmgDynamite; + + m_iDefaultAmmo = DYNAMITE_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + +void CDynamite::Precache( void ) +{ + PRECACHE_MODEL("models/w_dynamite.mdl"); + PRECACHE_MODEL("models/v_dynamite.mdl"); +// PRECACHE_MODEL("models/p_dynamite.mdl"); + +// PRECACHE_MODEL( DYNAMITE_FUSE_SPRITE ); +} + +int CDynamite::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Dynamite"; + p->iMaxAmmo1 = DYNAMITE_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 2; + p->iPosition = 1; + p->iId = m_iId = WEAPON_DYNAMITE; + p->iWeight = DYNAMITE_WEIGHT; + p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + + return 1; +} + + +BOOL CDynamite::Deploy( ) +{ + m_flReleaseThrow = -1; + m_flFinishThrow = -1; + return DefaultDeploy( "models/v_dynamite.mdl", "", DYNAMITE_DRAW, "dynamite" ); +} + +BOOL CDynamite::CanHolster( void ) +{ + // can only holster dynamite when not lit! + return ( m_flStartThrow == 0 ); +} + +void CDynamite::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + SendWeaponAnim( DYNAMITE_HOLSTER ); + } + else + { + // no more dynamites! + m_pPlayer->pev->weapons &= ~(1<pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + +int CDynamite::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +void CDynamite::PrimaryAttack() +{ + if (!m_flStartThrow && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0) + { + m_flStartThrow = gpGlobals->time; + m_flReleaseThrow = gpGlobals->time; + m_flFinishThrow = gpGlobals->time; + + SendWeaponAnim( DYNAMITE_LIGHT ); + + m_flTimeWeaponIdle = gpGlobals->time + 1.5; + } +} + + +void CDynamite::WeaponIdle( void ) +{ + //if (m_flReleaseThrow == 0) + // m_flReleaseThrow = gpGlobals->time; + //else if (m_flFinishThrow == 0) + // m_flFinishThrow = gpGlobals->time; + + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + if (m_flStartThrow > 0) + { + // alway explode 5 seconds after the fuse was lit + SendWeaponAnim( DYNAMITE_THROW ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_flStartThrow = 0; + m_flNextPrimaryAttack = gpGlobals->time + 1.0; + m_flTimeWeaponIdle = gpGlobals->time + 0.5; + + return; + } + else if (m_flReleaseThrow > 0) + { + Vector angThrow = m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle; + + if (angThrow.x < 0) + angThrow.x = -10 + angThrow.x * ((90 - 10) / 90.0); + else + angThrow.x = -10 + angThrow.x * ((90 + 10) / 90.0); + + float flVel = (90 - angThrow.x) * 4; + if (flVel > 500) + flVel = 500; + + UTIL_MakeVectors( angThrow ); + + Vector vecSrc = m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_forward * 16; + + Vector vecThrow = gpGlobals->v_forward * flVel + m_pPlayer->pev->velocity; + + float time = m_flReleaseThrow - gpGlobals->time + 5.0 - 0.5; // because it has been 0.5 seconds since we lit the fuse... + if (time < 0) + time = 0; + + CMonsterDynamite::Shoot( m_pPlayer->pev, vecSrc, vecThrow, time ); + + m_flTimeWeaponIdle = gpGlobals->time + 0.5; + + m_flReleaseThrow = -1; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + if ( !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + // just threw last stick of dynamite + // set attack times in the future, and weapon idle in the future so we can see the whole throw + // animation, weapon idle will automatically retire the weapon for us. + m_flTimeWeaponIdle = m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->time + 1.0;// ensure that the animation can finish playing + } + return; + } + else if (m_flFinishThrow > 0) + { + // we've finished the throw, restart. + m_flStartThrow = 0; + m_flReleaseThrow = -1; + m_flFinishThrow = -1; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + SendWeaponAnim( DYNAMITE_DRAW ); + } + else + { + RetireWeapon(); + return; + } + + return; + } + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.75) + { + iAnim = DYNAMITE_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 );// how long till we do this again. + } + else + { + iAnim = DYNAMITE_IDLE2; + m_flTimeWeaponIdle = gpGlobals->time + 75.0 / 30.0; + } + + SendWeaponAnim( iAnim ); + } +} + + + + diff --git a/dlls/cthulhu/dynamite.h b/dlls/cthulhu/dynamite.h new file mode 100755 index 00000000..30fe8cca --- /dev/null +++ b/dlls/cthulhu/dynamite.h @@ -0,0 +1,61 @@ + +#ifndef DYNAMITE_H +#define DYNAMITE_H + +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define DYNAMITE_PRIMARY_VOLUME 450 + +enum dynamite_e { + DYNAMITE_DRAW = 0, + DYNAMITE_IDLE1, + DYNAMITE_IDLE2, + DYNAMITE_LIGHT, + DYNAMITE_THROW, // toss + DYNAMITE_HOLSTER +}; + + + +class CDynamite : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 2; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + void PrimaryAttack( void ); + BOOL Deploy( void ); + BOOL CanHolster( void ); + void Holster( int skiplocal = 0 ); + void WeaponIdle( void ); + float m_flFinishThrow; + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } +}; + +#endif + + diff --git a/dlls/cthulhu/eihortvictim.cpp b/dlls/cthulhu/eihortvictim.cpp new file mode 100755 index 00000000..b6062dd2 --- /dev/null +++ b/dlls/cthulhu/eihortvictim.cpp @@ -0,0 +1,295 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 EihortVictim dying for scripted sequences? +#define EIHORTVICTIM_AE_BURST ( 2 ) +#define EIHORTVICTIM_AE_PAIN ( 3 ) + +#include "EihortVictim.h" + +LINK_ENTITY_TO_CLASS( monster_eihortvictim, CEihortVictim ); + +TYPEDESCRIPTION CEihortVictim::m_SaveData[] = +{ + DEFINE_FIELD( CEihortVictim, m_painTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CEihortVictim, CTalkMonster ); + + +//========================================================= +// ISoundMask - returns a bit mask indicating which types +// of sounds this monster regards. +//========================================================= +int CEihortVictim :: 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 CEihortVictim :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_NONE; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CEihortVictim :: SetYawSpeed ( void ) +{ + pev->yaw_speed = 0; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +// +// Returns number of events handled, 0 if none. +//========================================================= +void CEihortVictim :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case EIHORTVICTIM_AE_BURST: + Burst(); + break; + + case EIHORTVICTIM_AE_PAIN: + if (IsTalking()) return; + if (RANDOM_LONG(0,2) == 0) + { + //play scientist pain sound 1, 4, 5, 7 or 10 + PainSound(); + } + break; + + default: + CTalkMonster::HandleAnimEvent( pEvent ); + } +} + +void CEihortVictim :: StartTask( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + // victims do not turn + case TASK_TLK_IDEALYAW: + TaskComplete(); + break; + default: + CTalkMonster::StartTask( pTask ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CEihortVictim :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/eihort_victim.mdl"); + //UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + UTIL_SetSize(pev, Vector(-16,-16,0), Vector(16,16,8)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = 100; + 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; + + m_afCapability = bits_CAP_HEAR | bits_CAP_TURN_HEAD | bits_CAP_DOORS_GROUP; + + MonsterInit(); + SetUse( NULL ); + // this monster cannot turn + SetYawSpeed(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CEihortVictim :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/eihort_victim.mdl"); + + PRECACHE_SOUND("scientist/sci_pain1.wav"); + PRECACHE_SOUND("scientist/sci_pain4.wav"); + PRECACHE_SOUND("scientist/sci_pain5.wav"); + PRECACHE_SOUND("scientist/sci_pain7.wav"); + PRECACHE_SOUND("scientist/sci_pain10.wav"); + + UTIL_PrecacheOther( "monster_babycrab" ); + + // every new EihortVictim must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + CTalkMonster::Precache(); +} + +// Init talk data +void CEihortVictim :: TalkInit() +{ + CTalkMonster::TalkInit(); + + // EihortVictim speech group names (group names are in sentences.txt) + + if (!m_iszSpeakAs) + { + //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_STOP] = "BA_STOP"; + + m_szGrp[TLK_NOSHOOT] = "BA_SCARED"; + //m_szGrp[TLK_HELLO] = "BA_HELLO"; + + //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 EihortVictim voice for now + m_voicePitch = 100; +} + +int CEihortVictim :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType) +{ + return 0; + + // make sure friends talk about it if player hurts talkmonsters... + int ret = CTalkMonster::TakeDamage(pevInflictor, pevAttacker, flDamage, bitsDamageType); + + return ret; +} + + +//========================================================= +// PainSound +//========================================================= +void CEihortVictim :: 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, m_voicePitch ); + break; + case 1: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain4.wav", 1, ATTN_NORM, 0, m_voicePitch ); + break; + case 2: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain5.wav", 1, ATTN_NORM, 0, m_voicePitch ); + break; + case 3: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain7.wav", 1, ATTN_NORM, 0, m_voicePitch ); + break; + case 4: + EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "scientist/sci_pain10.wav", 1, ATTN_NORM, 0, m_voicePitch ); + break; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CEihortVictim :: DeathSound ( void ) +{ + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "EihortVictim/ba_die1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "EihortVictim/ba_die2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "EihortVictim/ba_die3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + + +void CEihortVictim::Killed( entvars_t *pevAttacker, int iGib ) +{ + SetUse( NULL ); + Burst(); + CTalkMonster::Killed( pevAttacker, iGib ); +} + +void CEihortVictim::Burst() +{ + pev->body = 1; + EMIT_SOUND(ENT(pev), CHAN_WEAPON, "common/bodysplat.wav", 1, ATTN_NORM); + CGib::SpawnRandomGibs( pev, 4, 1 ); // throw some human gibs. + + // now make some baby headcrabs + for (int x = 0; x <= 1; x++) + { + for (int y = 0; y <= 1; y++) + { + //CBaseEntity *pChild = CBaseEntity::Create( "monster_babycrab", pev->origin + 24*Vector(x-0.5,y-0.5,0) + Vector(0,0,1), pev->angles, edict() ); + CBaseEntity *pChild = CBaseEntity::Create( "monster_babycrab", pev->origin + 24*Vector(x-0.5,y-0.5,0) + Vector(0,0,8), pev->angles, edict() ); + pChild->pev->spawnflags |= SF_MONSTER_FALL_TO_GROUND; + pChild->pev->spawnflags |= SF_MONSTER_NO_YELLOW_BLOBS; + } + } +} + + + + + diff --git a/dlls/cthulhu/eihortvictim.h b/dlls/cthulhu/eihortvictim.h new file mode 100755 index 00000000..cf217b71 --- /dev/null +++ b/dlls/cthulhu/eihortvictim.h @@ -0,0 +1,32 @@ + +class CEihortVictim : public CTalkMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int ISoundMask( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int Classify ( void ); + + void StartTask( Task_t *pTask ); + + int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + + void DeathSound( void ); + void PainSound( void ); + + void TalkInit( void ); + + void Killed( entvars_t *pevAttacker, int iGib ); + + void Burst ( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +// BOOL m_fGunDrawn; + float m_painTime; +}; + diff --git a/dlls/cthulhu/elder_sign.cpp b/dlls/cthulhu/elder_sign.cpp new file mode 100755 index 00000000..b5707065 --- /dev/null +++ b/dlls/cthulhu/elder_sign.cpp @@ -0,0 +1,377 @@ + +#include "elder_sign.h" + +enum elder_sign_e { + ELDER_SIGN_DRAW = 0, + ELDER_SIGN_IDLE1, + ELDER_SIGN_IDLE2, + ELDER_SIGN_IDLE3, + ELDER_SIGN_CAST, + ELDER_SIGN_HOLSTER, + ELDER_SIGN_WORLD, + ELDER_SIGN_GROUND +}; + + +LINK_ENTITY_TO_CLASS( monster_elder_sign, CElderSignArea ); + +//TYPEDESCRIPTION CElderSignArea::m_SaveData[] = +//{ +//}; + +//IMPLEMENT_SAVERESTORE( CElderSignArea, CBaseMonster ); + + +void CElderSignArea :: Spawn( void ) +{ + Precache( ); + + m_afCapability = bits_CAP_RANGE_ATTACK1; + + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_NOT; + + SET_MODEL(ENT(pev), "models/v_elder_sign.mdl"); + pev->frame = 0; + pev->body = 3; + //pev->sequence = ELDER_SIGN_IDLE1; + pev->sequence = ELDER_SIGN_WORLD; + ResetSequenceInfo( ); + pev->framerate = 0; + + UTIL_SetSize(pev, Vector( -8, 0, -8), Vector(8, 4, 8)); + UTIL_SetOrigin( this, pev->origin ); + + //pev->solid = SOLID_BBOX; + SetThink ( ElderSignThink ); + SetNextThink( 0.2 ); + + pev->takedamage = DAMAGE_YES; + pev->health = 5; // don't let die normally + + //MonsterInit(); + SetBits (pev->flags, FL_MONSTER); + + // make solid + //pev->solid = SOLID_BBOX; + UTIL_SetOrigin( this, pev->origin ); + m_bloodColor = DONT_BLEED; +} + +void CElderSignArea :: Precache( void ) +{ + PRECACHE_MODEL("models/v_elder_sign.mdl"); +} + +int CElderSignArea :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if (flDamage < pev->health) + { + SetThink( SUB_Remove ); + SetNextThink( 0.1 ); + return FALSE; + } + return CBaseMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +BOOL CElderSignArea :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + return FALSE; +} + +void CElderSignArea :: ElderSignThink ( void ) +{ + int iMonsters; + + // wait until we are free of monsters (incl. players) in our box and then become solid + if (pev->solid == SOLID_NOT) + { + iMonsters = UTIL_EntitiesInBox(mpEntInSphere, MAX_MONSTER, pev->origin - Vector(8,2,8), pev->origin + Vector(8,2,8), (FL_CLIENT|FL_MONSTER)); + + // are we (the elder sign) the only monster in the area + if (iMonsters <= 1) + { + pev->solid = SOLID_BBOX; + } + } + + iMonsters = UTIL_MonstersInSphere(mpEntInSphere, MAX_MONSTER, pev->origin, 96); + + for (int i = 0; i < iMonsters; i++) + { + if (!(mpEntInSphere[i]->pev->flags & FL_MONSTER)) continue; + + Repel((CBaseMonster*)mpEntInSphere[i]); + } + + SetNextThink( 0.1 ); +} + +void CElderSignArea :: Killed( entvars_t *pevAttacker, int iGib ) +{ + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + WRITE_COORD( pev->origin.x ); // Send to PAS because of the sound + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 5 ); + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + UTIL_Remove(this); +} + +int CElderSignArea :: Classify ( void ) +{ + return CLASS_ALIEN_PASSIVE; +} + +void CElderSignArea::Repel( CBaseMonster* pEnt ) +{ + int iClass = pEnt->Classify(); + + if (iClass != CLASS_ALIEN_MILITARY + && iClass != CLASS_ALIEN_MONSTER + && iClass != CLASS_ALIEN_PREY + && iClass != CLASS_ALIEN_PREDATOR) + return; + + entvars_t* pevEnt = pEnt->pev; + + if ( !FBitSet ( pevEnt->flags , FL_MONSTER ) ) + {// touched by a non-monster. + return; + } + + pevEnt->origin.z += 1; + + if ( FBitSet ( pevEnt->flags, FL_ONGROUND ) ) + {// clear the onground so physics don't bitch + pevEnt->flags &= ~FL_ONGROUND; + } + + // flying monsters do not work the same way, so we panic them instead. + if (pEnt->pev->movetype == MOVETYPE_FLY) + { + CBaseEntity* pPlayer = UTIL_FindEntityByClassname( NULL, "player" ); + pEnt->Panic(pPlayer->pev); + return; + } + + // calculate the opposite direction, and push in that direction + Vector vecDirToEnemy = ( pevEnt->origin - pev->origin ); + vecDirToEnemy.z = 0; + //Vector angDir = UTIL_VecToAngles( vecDirToEnemy ); + vecDirToEnemy = vecDirToEnemy.Normalize(); + + pevEnt->velocity = vecDirToEnemy * 256; + pevEnt->velocity.z += 128; +} + +////////////////////////////////////////////////////////////////////////////////// + +LINK_ENTITY_TO_CLASS( weapon_eldersign, CElderSign ); + + +void CElderSign::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_ELDER_SIGN; + SET_MODEL(ENT(pev), "models/v_elder_sign.mdl"); + pev->frame = 0; + pev->body = 3; + pev->sequence = ELDER_SIGN_GROUND; + //ResetSequenceInfo( ); + pev->framerate = 0; + + FallInit();// get ready to fall down + + m_iDefaultAmmo = ELDER_SIGN_DEFAULT_GIVE; + + if ( !g_pGameRules->IsDeathmatch() ) + { + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 28) ); + } +} + +void CElderSign::Precache( void ) +{ + PRECACHE_MODEL ("models/v_elder_sign.mdl"); +// PRECACHE_MODEL ("models/p_elder_sign.mdl"); + UTIL_PrecacheOther( "monster_elder_sign" ); +} + +int CElderSign::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Elder Sign"; + p->iMaxAmmo1 = ELDER_SIGN_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 3; + p->iPosition = 0; + p->iId = m_iId = WEAPON_ELDER_SIGN; + p->iWeight = ELDER_SIGN_WEIGHT; + p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + + return 1; +} + +BOOL CElderSign::Deploy( ) +{ + pev->body = 0; + return DefaultDeploy( "models/v_elder_sign.mdl", "", ELDER_SIGN_DRAW, "eldersign" ); +} + + +void CElderSign::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + if (!m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + // out of mines + m_pPlayer->pev->weapons &= ~(1<pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + +int CElderSign::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + + +void CElderSign::PrimaryAttack( void ) +{ + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + return; + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + + TraceResult tr; + + UTIL_TraceLine( vecSrc, vecSrc + vecAiming * 128, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); + + if (tr.flFraction < 1.0) + { + // ALERT( at_console, "hit %f\n", tr.flFraction ); + + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + if (pEntity && !(pEntity->pev->flags & FL_CONVEYOR)) + { + Vector angles = UTIL_VecToAngles( tr.vecPlaneNormal ); + + edict_t *pentity; + entvars_t *pevCreate; + + // ALERT(at_console,"Making Monster NOW\n"); + + pentity = CREATE_NAMED_ENTITY( MAKE_STRING("monster_elder_sign") ); + + pevCreate = VARS( pentity ); + //pevCreate->origin = tr.vecEndPos + tr.vecPlaneNormal * 8; + pevCreate->origin = tr.vecEndPos + tr.vecPlaneNormal * 0.1; + pevCreate->angles = angles; + + DispatchSpawn( ENT( pevCreate ) ); + pevCreate->owner = edict(); + + //LRC - custom monster behaviour + CBaseEntity *pEnt = CBaseEntity::Instance( pevCreate ); + + + + + //CBaseEntity *pEnt = CBaseEntity::Create( "monster_elder_sign", tr.vecEndPos + tr.vecPlaneNormal * 8, angles, m_pPlayer->edict() ); + //CBaseMonster *pNewMonster = pEnt->MyMonsterPointer( ); + //pEnt->pev->spawnflags |= 1; + + //CElderSignArea *pElderSignArea = (CElderSignArea *)pEnt; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0) + { + SendWeaponAnim( ELDER_SIGN_DRAW ); + } + else + { + // no more mines! + RetireWeapon(); + return; + } + } + else + { + // ALERT( at_console, "no deploy\n" ); + } + } + else + { + + } + + m_flNextPrimaryAttack = gpGlobals->time + 0.3; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); +} + +void CElderSign::WeaponIdle( void ) +{ + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0) + { + SendWeaponAnim( ELDER_SIGN_DRAW ); + } + else + { + RetireWeapon(); + return; + } + + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.25) + { + iAnim = ELDER_SIGN_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + 90.0 / 30.0; + } + else if (flRand <= 0.75) + { + iAnim = ELDER_SIGN_IDLE2; + m_flTimeWeaponIdle = gpGlobals->time + 60.0 / 30.0; + } + else + { + iAnim = ELDER_SIGN_IDLE3; + m_flTimeWeaponIdle = gpGlobals->time + 100.0 / 30.0; + } + + SendWeaponAnim( iAnim ); + +} + + + diff --git a/dlls/cthulhu/elder_sign.h b/dlls/cthulhu/elder_sign.h new file mode 100755 index 00000000..36d295b2 --- /dev/null +++ b/dlls/cthulhu/elder_sign.h @@ -0,0 +1,106 @@ + +#ifndef ELDERSIGN_H +#define ELDERSIGN_H + +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "effects.h" +#include "gamerules.h" + +#include "triggers.h" + +#define ELDER_SIGN_PRIMARY_VOLUME 450 + + +const int MAX_MONSTER = 1000; + + +// The elder sign is supposed to push all monsters (not humans, like cultists) +// away from it, to prevent them passing... + + +class CElderSignArea : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + int Classify ( void ); + + // no melee attacks + BOOL CheckMeleeAttack1 ( float flDot, float flDist ) { return FALSE; }; + BOOL CheckMeleeAttack2 ( float flDot, float flDist ) { return FALSE; }; + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + + void EXPORT ElderSignThink ( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + void Repel ( CBaseMonster* pEnt ); + + //int Save( CSave &save ); + //int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +protected: + CBaseEntity* mpEntInSphere[MAX_MONSTER]; + +}; + + +//////////////////////////////////////////////////////////////////////////////// + +class CElderSign : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 3; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + void SetObjectCollisionBox( void ) + { + //!!!BUGBUG - fix the model! + pev->absmin = pev->origin + Vector(-16, -16, -5); + pev->absmax = pev->origin + Vector(16, 16, 28); + } + + void PrimaryAttack( void ); + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + void WeaponIdle( void ); + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } +}; + +#endif + + + diff --git a/dlls/cthulhu/furniture.cpp b/dlls/cthulhu/furniture.cpp new file mode 100755 index 00000000..c2a3f40e --- /dev/null +++ b/dlls/cthulhu/furniture.cpp @@ -0,0 +1,246 @@ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "effects.h" + +#define SF_DROP_TO_FLOOR 32 + +//========================================================= +// Furniture - this is the cool comment I cut-and-pasted +//========================================================= +class CFurniture : public CBaseMonster +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + void Die( void ); + int Classify ( void ); + + virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + Vector m_vecSize; + +}; + + +LINK_ENTITY_TO_CLASS( monster_furniture, CFurniture ); + +TYPEDESCRIPTION CFurniture::m_SaveData[] = +{ + DEFINE_FIELD( CFurniture, m_vecSize, FIELD_VECTOR), +}; + +IMPLEMENT_SAVERESTORE( CFurniture, CBaseMonster ); + + +//========================================================= +// Furniture is killed +//========================================================= +void CFurniture :: Die ( void ) +{ + SetThink ( SUB_Remove ); + SetNextThink( 0 ); +} + +void CFurniture :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "m_vecSize")) + { + UTIL_StringToVector((float*)m_vecSize, pkvd->szValue); + m_vecSize = m_vecSize/2; + pkvd->fHandled = TRUE; + } + else + CBaseMonster::KeyValue( pkvd ); +} + +//========================================================= +// 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; + + m_bloodColor = DONT_BLEED; + + if (pev->spawnflags & SF_DROP_TO_FLOOR) + { + pev->movetype = MOVETYPE_NONE; + } + else + { + pev->movetype = MOVETYPE_FLY; + ClearBits( pev->spawnflags, SF_MONSTER_FALL_TO_GROUND ); + } + +// pev->nextthink += 1.0; +// SetThink (WalkMonsterDelay); + + if (m_vecSize != Vector(0,0,0)) + { + Vector vTemp; + vTemp.z = m_vecSize.z; + + UTIL_SetSize( pev, -m_vecSize + vTemp, m_vecSize + vTemp ); + } + + // we probably do not need this... + //pev->solid = SOLID_SLIDEBOX; + + ResetSequenceInfo( ); + pev->frame = 0; + MonsterInit(); +} + +//========================================================= +// ID's Furniture as neutral (noone will attack it) +//========================================================= +int CFurniture::Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_NONE; +} + +///////////////////////////////////////////////////////////////////////////////////////////////// + +#define MAX_CANDLE_FLAMES 8 +#define SF_CANDLE_STARTON 8 +#define CANDLE_SPRITE_NAME "sprites/flames.spr" + + + +class CCandle : public CFurniture +{ +public: + void Spawn ( void ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void FlamesOn(); + void FlamesOff(); + + void EXPORT CandleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +private: + int m_nNumFlames; + float m_fFlameSize; + CSprite* m_pFlame[MAX_CANDLE_FLAMES]; +}; + + +LINK_ENTITY_TO_CLASS( monster_candle, CCandle ); + +TYPEDESCRIPTION CCandle::m_SaveData[] = +{ + DEFINE_FIELD( CCandle, m_nNumFlames, FIELD_INTEGER ), + DEFINE_FIELD( CCandle, m_fFlameSize, FIELD_FLOAT ), + DEFINE_ARRAY( CCandle, m_pFlame, FIELD_CLASSPTR, MAX_CANDLE_FLAMES ), +}; + +IMPLEMENT_SAVERESTORE( CCandle, CFurniture ); + +////////////////////////////////////////////////// + +void CCandle :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "numflames")) + { + m_nNumFlames = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "flamesize")) + { + m_fFlameSize = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CFurniture::KeyValue( pkvd ); +} + +void CCandle :: Spawn( ) +{ + for (int f = 0; f < MAX_CANDLE_FLAMES; f++) + { + m_pFlame[f] = NULL; + } + + CFurniture::Spawn(); + + SetUse(CandleUse); + + if ( pev->spawnflags & SF_CANDLE_STARTON ) + { + FlamesOn(); + } +} + +void CCandle::FlamesOff() +{ + for (int f = 0; f < MAX_CANDLE_FLAMES; f++) + { + if (m_pFlame) + { + UTIL_Remove(m_pFlame[f]); + m_pFlame[f] = NULL; + } + } +} + +void CCandle::FlamesOn() +{ + Vector vecPosition; + Vector vecJunk; + + for (int f = 0; f < m_nNumFlames; f++) + { + GetAttachment( f, vecPosition, vecJunk ); + + m_pFlame[f] = CSprite::SpriteCreate( CANDLE_SPRITE_NAME, vecPosition, TRUE ); + m_pFlame[f]->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone ); + m_pFlame[f]->SetAttachment( edict(), f+1 ); + m_pFlame[f]->pev->scale = m_fFlameSize; + m_pFlame[f]->pev->framerate = 10.0; + m_pFlame[f]->TurnOn(); + } +} + +void CCandle::CandleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // players cannot turn these on or off + if ( pCaller && pCaller->IsPlayer() ) return; + + // do we have flames to turn on and off + if (m_nNumFlames == 0) return; + + // are they already on + if (m_pFlame[0]) + { + FlamesOff(); + } + else + { + FlamesOn(); + } +} + diff --git a/dlls/cthulhu/golem.cpp b/dlls/cthulhu/golem.cpp new file mode 100755 index 00000000..13e3db16 --- /dev/null +++ b/dlls/cthulhu/golem.cpp @@ -0,0 +1,571 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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" + +#define GOLEM_DAMAGE (DMG_ENERGYBEAM|DMG_CRUSH|DMG_MORTAR|DMG_BLAST|DMG_SONIC|DMG_ACID|DMG_FALL) + +#define GOLEM_SHOCK_RADIUS 512 + +// todo: +// clean up other animations +// gibs + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define GOLEM_AE_ATTACK_SMASH 0x01 +#define GOLEM_AE_ATTACK_UPPERCUT 0x02 +#define GOLEM_AE_LEFT_FOOT 0x03 +#define GOLEM_AE_RIGHT_FOOT 0x04 +#define GOLEM_AE_ATTACK_SHOCK 0x05 + +#define GOLEM_FLINCH_DELAY 4 // at most one flinch every n secs + +class CGolem : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int IgnoreConditions ( void ); + void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ); + + void Shockwave ( void ); + + float m_flNextFlinch; + int m_iSpriteTexture; + + 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[]; + static const char *pFootSounds[]; + + // No range attacks + virtual BOOL CheckMeleeAttack1( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; }; + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + virtual int HasCustomGibs( void ) { return m_iszGibModel; } + + int m_iszGibModel; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; +}; + +LINK_ENTITY_TO_CLASS( monster_golem, CGolem ); + +TYPEDESCRIPTION CGolem::m_SaveData[] = +{ + DEFINE_FIELD( CGolem, m_iSpriteTexture, FIELD_INTEGER ), + DEFINE_FIELD( CGolem, m_iszGibModel, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CGolem, CBaseMonster ); + +const char *CGolem::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CGolem::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CGolem::pAttackSounds[] = +{ + "zombie/zo_attack1.wav", + "zombie/zo_attack2.wav", +}; + +const char *CGolem::pIdleSounds[] = +{ + "zombie/zo_idle1.wav", + "zombie/zo_idle2.wav", + "zombie/zo_idle3.wav", + "zombie/zo_idle4.wav", +}; + +const char *CGolem::pAlertSounds[] = +{ + "zombie/zo_alert10.wav", + "zombie/zo_alert20.wav", + "zombie/zo_alert30.wav", +}; + +const char *CGolem::pPainSounds[] = +{ + "zombie/zo_pain1.wav", + "zombie/zo_pain2.wav", +}; + +const char *CGolem::pFootSounds[] = +{ + "garg/gar_step1.wav", + "garg/gar_step2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CGolem :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CGolem :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + pev->yaw_speed = ys; +} + +BOOL CGolem :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb) + //int iGround = FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ); + + // Cthulhu: but this stops the monster from hitting flying monsters that are + // melee attacking it (e.g. nightgaunt) + // Solution: explicitly check for monster types that we cannot hit + + BOOL bHit = TRUE; // we can hit by default + if (m_hEnemy) + { + if (FClassnameIs( m_hEnemy->pev, "hornet")) bHit = FALSE; + if (FClassnameIs( m_hEnemy->pev, "monster_snark")) bHit = FALSE; + } + + //if ( flDist <= 64 && flDot >= 0.7 && m_hEnemy != NULL && iGround ) + if ( flDist <= 96 && flDot >= 0.7 && m_hEnemy != NULL && bHit ) + { + return TRUE; + } + return FALSE; +} + +BOOL CGolem :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDist <= GOLEM_SHOCK_RADIUS && flDot >= 0.5 ) + { + if (gpGlobals->time >= m_flNextAttack) + { + return TRUE; + } + } + + // we always return false, otherwise it will try and do an animation that does not exist + return FALSE; +} + +int CGolem :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Take 300% damage from SONIC + if ( bitsDamageType & DMG_SONIC ) + { + flDamage *= 3.0; + } + + if (!(bitsDamageType & GOLEM_DAMAGE)) + { + flDamage = 0.0; + } + + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CGolem :: PainSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + if (RANDOM_LONG(0,5) < 3) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); +} + +void CGolem :: 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 CGolem :: 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 CGolem :: 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 CGolem :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case GOLEM_AE_RIGHT_FOOT: + case GOLEM_AE_LEFT_FOOT: + UTIL_ScreenShake( pev->origin, 2.0, 3.0, 1.0, 500 ); + EMIT_SOUND_DYN ( edict(), CHAN_BODY, pFootSounds[ RANDOM_LONG(0,ARRAYSIZE(pFootSounds)-1) ], 1.0, ATTN_NORM, 0, PITCH_NORM + RANDOM_LONG(-10,10) ); + break; + + case GOLEM_AE_ATTACK_UPPERCUT: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( 96, gSkillData.zombieDmgOneSlash * 2, DMG_CLUB ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = -20; + pHurt->pev->punchangle.x = 20; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 200; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 500; + } + // 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 GOLEM_AE_ATTACK_SMASH: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( 96, gSkillData.zombieDmgOneSlash * 3, DMG_CLUB ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = -20; + pHurt->pev->punchangle.x = 20; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 500; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 200; + } + 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 GOLEM_AE_ATTACK_SHOCK: + { + // we do the shockwave here + Shockwave(); + + m_flNextAttack = gpGlobals->time + 20.0; + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CGolem :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/golem.mdl"); + UTIL_SetSize( pev, Vector( -24, -24, 0 ), Vector( 24, 24, 144 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = gSkillData.zombieHealth * 3; + pev->view_ofs = Vector( 0, 0, 48 );// 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(); + + m_bloodColor = DONT_BLEED; +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CGolem :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/golem.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]); + + for ( i = 0; i < ARRAYSIZE( pFootSounds ); i++ ) + PRECACHE_SOUND((char *)pFootSounds[i]); + + PRECACHE_SOUND("houndeye/he_blast1.wav"); + PRECACHE_SOUND("houndeye/he_blast2.wav"); + PRECACHE_SOUND("houndeye/he_blast3.wav"); + + m_iszGibModel = ALLOC_STRING("models/golem_gibs.mdl"); + m_iSpriteTexture = PRECACHE_MODEL( "sprites/shockwave.spr" ); + PRECACHE_MODEL("models/golem_gibs.mdl"); +} + +void CGolem::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + ALERT( at_aiconsole, "CGolem::TraceAttack\n"); + + if ( !IsAlive() ) + { + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + return; + } + + bitsDamageType &= GOLEM_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 ); + +} + +void CGolem::Shockwave() +{ + 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 + GOLEM_SHOCK_RADIUS / .2); // reach damage radius over .3 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 16 ); // life + WRITE_BYTE( 96 ); // width + WRITE_BYTE( 0 ); // noise + + WRITE_BYTE( 64 ); // RED + WRITE_BYTE( 64 ); // GREEN + WRITE_BYTE( 64 ); // BLUE + + 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 + (GOLEM_SHOCK_RADIUS / 2 ) / .2); // reach damage radius over .3 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 12 ); // life + WRITE_BYTE( 96 ); // width + WRITE_BYTE( 0 ); // noise + + WRITE_BYTE( 64 ); // RED + WRITE_BYTE( 64 ); // GREEN + WRITE_BYTE( 64 ); // BLUE + + 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, GOLEM_SHOCK_RADIUS )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + if ( !FClassnameIs(pEntity->pev, "monster_golem") ) + { + + // golems do FULL damage if the ent in question is visible. Half damage otherwise. + // This means that you must get out of the kingpin's attack range entirely to avoid damage. + // Calculate full damage first + + flAdjustedDamage = gSkillData.zombieDmgBothSlash * 2; + + flDist = (pEntity->Center() - pev->origin).Length(); + + flAdjustedDamage *= (1.0 - ( flDist / GOLEM_SHOCK_RADIUS )); + + 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 ); + } + } + } + } +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + + +int CGolem::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { +#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 + GOLEM_FLINCH_DELAY; + } + + return iIgnore; + +} \ No newline at end of file diff --git a/dlls/cthulhu/hellhound.cpp b/dlls/cthulhu/hellhound.cpp new file mode 100755 index 00000000..8ef0e7ef --- /dev/null +++ b/dlls/cthulhu/hellhound.cpp @@ -0,0 +1,498 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Hellhound +//========================================================= + +// UNDONE: Don't flinch every time you get hit + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "soundent.h" +#include "scripted.h" +#include "decals.h" + + +#include "hellhound.h" + +int iHHFireballSprite; + +///////////////////////////////////////////////////////////////////////////////////////////// + +LINK_ENTITY_TO_CLASS( hhfireball, CHHFireball ); + +TYPEDESCRIPTION CHHFireball::m_SaveData[] = +{ + DEFINE_FIELD( CHHFireball, m_maxFrame, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CHHFireball, CBaseEntity ); + +void CHHFireball:: Spawn( void ) +{ + pev->movetype = MOVETYPE_FLY; + pev->classname = MAKE_STRING( "hhfireball" ); + + pev->solid = SOLID_BBOX; + pev->rendermode = kRenderTransAdd; + //pev->rendermode = kRenderTransAlpha; + pev->renderamt = 255; + pev->framerate = 10.0; // cthulhu + + SET_MODEL(ENT(pev), "sprites/rjet1.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 CHHFireball::Animate( void ) +{ + SetNextThink( 0.1 ); + + if ( pev->frame++ ) + { + if ( pev->frame > m_maxFrame ) + { + pev->frame = 0; + } + } +} + +void CHHFireball::Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CHHFireball *pFireball = GetClassPtr( (CHHFireball *)NULL ); + pFireball->Spawn(); + + UTIL_SetOrigin( pFireball, vecStart ); + pFireball->pev->velocity = vecVelocity; + pFireball->pev->owner = ENT(pevOwner); + + pFireball->SetThink ( Animate ); + pFireball->SetNextThink( 0.1 ); +} + +void CHHFireball :: 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_SMALLSCORCH1 + RANDOM_LONG(0,2)); + + // 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( iHHFireballSprite ); // model + WRITE_BYTE ( 1 ); // count + WRITE_BYTE ( 30 ); // speed + WRITE_BYTE ( 8 ); // noise ( client will divide by 100 ) + MESSAGE_END(); + */ + } + else + { + pOther->TakeDamage ( pev, pev, gSkillData.bullsquidDmgSpit * 2, DMG_BURN ); + } + + SetThink ( SUB_Remove ); + SetNextThink( 0 ); +} + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define HELLHOUND_AE_ATTACK 0x01 +#define HELLHOUND_AE_FIREBALL 0x02 + +#define HELLHOUND_FLINCH_DELAY 8 // at most one flinch every n secs +#define HELLHOUND_FIREBALL_DELAY 5 // at most one fireball every n secs + + +LINK_ENTITY_TO_CLASS( monster_hellhound, CHellhound ); + +TYPEDESCRIPTION CHellhound::m_SaveData[] = +{ + DEFINE_FIELD( CHellhound, m_flNextFireballTime, FIELD_TIME ), +}; + +IMPLEMENT_SAVERESTORE( CHellhound, CBaseMonster ); + +const char *CHellhound::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CHellhound::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CHellhound::pAttackSounds[] = +{ + "ghoul/gh_attack1.wav", +// "ghoul/gh_attack2.wav", +}; + +const char *CHellhound::pIdleSounds[] = +{ + "ghoul/gh_idle1.wav", + "ghoul/gh_idle2.wav", + "ghoul/gh_idle3.wav", +// "ghoul/gh_idle4.wav", +}; + +const char *CHellhound::pAlertSounds[] = +{ + "ghoul/gh_alert1.wav", +// "ghoul/gh_alert20.wav", +// "ghoul/gh_alert30.wav", +}; + +const char *CHellhound::pPainSounds[] = +{ + "ghoul/gh_pain1.wav", +// "ghoul/gh_pain2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CHellhound :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_PREDATOR; +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CHellhound :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( IsMoving() && flDist >= 512 ) + { + // hound 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_flNextFireballTime ) + { + 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_flNextFireballTime = gpGlobals->time + (HELLHOUND_FIREBALL_DELAY*2); + } + else + { + // not moving, so spit again pretty soon. + m_flNextFireballTime = gpGlobals->time + HELLHOUND_FIREBALL_DELAY; + } + + return TRUE; + } + + 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 CHellhound :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_CARCASS | + bits_SOUND_MEAT | + bits_SOUND_GARBAGE | + bits_SOUND_PLAYER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CHellhound :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + + pev->yaw_speed = ys; +} + +int CHellhound :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if (bitsDamageType & DMG_BURN) + { + return 0; + } + + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CHellhound :: 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 CHellhound :: 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 CHellhound :: 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) ], 0.2, ATTN_NORM, 0, pitch ); +} + +void CHellhound :: AttackSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + // Play a random attack sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 0.5, ATTN_NORM, 0, pitch ); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CHellhound :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case HELLHOUND_AE_ATTACK: + { + // do stuff for this event. + // ALERT( at_console, "Slash!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.bullsquidDmgBite * 1.5, 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 * -150; + } + // 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 HELLHOUND_AE_FIREBALL: + { + // do stuff for this event. + Vector vecFireballOffset; + Vector vecFireballDir; + + 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. + vecFireballOffset = ( gpGlobals->v_right * 8 + gpGlobals->v_forward * 37 + gpGlobals->v_up * 23 ); + vecFireballOffset = ( pev->origin + vecFireballOffset ); + if (m_pCine) // LRC- are we being told to do this by a scripted_action? + { + if (m_hTargetEnt != NULL && m_pCine->PreciseAttack()) + vecFireballDir = ( ( m_hTargetEnt->pev->origin ) - vecFireballOffset ).Normalize(); + else + vecFireballDir = gpGlobals->v_forward; + } + else + vecFireballDir = ( ( m_hEnemy->pev->origin + m_hEnemy->pev->view_ofs ) - vecFireballOffset ).Normalize(); + + vecFireballDir.x += RANDOM_FLOAT( -0.02, 0.02 ); + vecFireballDir.y += RANDOM_FLOAT( -0.02, 0.02 ); + vecFireballDir.z += RANDOM_FLOAT( -0.02, 0 ); + + + // do stuff for this event. + AttackSound(); + + // spew the spittle temporary ents. + /* + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecFireballOffset ); + WRITE_BYTE( TE_SPRITE_SPRAY ); + WRITE_COORD( vecFireballOffset.x); // pos + WRITE_COORD( vecFireballOffset.y); + WRITE_COORD( vecFireballOffset.z); + WRITE_COORD( vecFireballDir.x); // dir + WRITE_COORD( vecFireballDir.y); + WRITE_COORD( vecFireballDir.z); + WRITE_SHORT( iHHFireballSprite ); // model + WRITE_BYTE ( 1 ); // count + WRITE_BYTE ( 210 ); // speed + WRITE_BYTE ( 8 ); // noise ( client will divide by 100 ) + MESSAGE_END(); + */ + + CHHFireball::Shoot( pev, vecFireballOffset, vecFireballDir * 900 ); + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CHellhound :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/monsters/hellhound.mdl"); + UTIL_SetSize( pev, Vector( -24, -24, 0 ), Vector( 24, 24, 36 ) ); + + // Hellhounds are immune to fire + SetBits( pev->flags, FL_IMMUNE_LAVA ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = gSkillData.houndeyeHealth * 4; // use this one + 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 = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CHellhound :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/monsters/hellhound.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]); + + iHHFireballSprite = PRECACHE_MODEL("sprites/rjet1.spr");// client side spittle. +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +int CHellhound::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { + 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 + HELLHOUND_FLINCH_DELAY; + } + + return iIgnore; + +} + diff --git a/dlls/cthulhu/hellhound.h b/dlls/cthulhu/hellhound.h new file mode 100755 index 00000000..07d75e82 --- /dev/null +++ b/dlls/cthulhu/hellhound.h @@ -0,0 +1,69 @@ + +#ifndef HELLHOUND_H +#define HELLHOUND_H + +//========================================================= +// Hellhound's fireball +//========================================================= +class CHHFireball : 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; +}; + +//========================================================= +// Hellhound +//========================================================= +class CHellhound : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int IgnoreConditions ( void ); + virtual int ISoundMask( void ); + + float m_flNextFlinch; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + 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 + virtual BOOL CheckRangeAttack1 ( float flDot, float flDist ); + virtual BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; }; + + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + float m_flNextFireballTime;// next time the hellhound may use the fireball attack. +}; + + + +#endif + + diff --git a/dlls/cthulhu/hologram.cpp b/dlls/cthulhu/hologram.cpp new file mode 100755 index 00000000..6df32155 --- /dev/null +++ b/dlls/cthulhu/hologram.cpp @@ -0,0 +1,612 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" +#include "soundent.h" + +enum hologram_e { + HOLOGRAM_IDLE1 = 0, + HOLOGRAM_FIDGET1, + HOLOGRAM_DRAW, + HOLOGRAM_DROP +}; + +enum hologram_radio_e { + HOLOGRAM_RADIO_IDLE1 = 0, + HOLOGRAM_RADIO_FIDGET1, + HOLOGRAM_RADIO_DRAW, + HOLOGRAM_RADIO_FIRE, + HOLOGRAM_RADIO_HOLSTER +}; + + + +class CHologramCharge : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + void BounceSound( void ); + + void EXPORT HologramSlide( CBaseEntity *pOther ); + void EXPORT HologramThink( void ); + + CBaseEntity* m_pHologram; + float m_fDeactivateTime; + +public: + +#ifndef CLIENT_DLL + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; +#endif + + void Deactivate( void ); + void MakeHologram( void ); +}; + +TYPEDESCRIPTION CHologramCharge::m_SaveData[] = +{ + DEFINE_FIELD( CHologramCharge, m_pHologram, FIELD_CLASSPTR ), + DEFINE_FIELD( CHologramCharge, m_fDeactivateTime, FIELD_FLOAT ), +}; +IMPLEMENT_SAVERESTORE( CHologramCharge, CGrenade ); + +LINK_ENTITY_TO_CLASS( monster_hologram, CHologramCharge ); + +void CHologramCharge::MakeHologram( void ) +{ + // create a hologram (monster_furniture) + edict_t* pent = CREATE_NAMED_ENTITY( ALLOC_STRING("monster_furniture") ); + + if ( FNullEnt( pent ) ) + { + ALERT ( at_debug, "NULL Ent in Hologram!\n" ); + return; + } + + entvars_t* pevCreate = VARS( pent ); + pevCreate->origin = Center() + Vector(0,0,40); + pevCreate->angles = pev->angles; + pevCreate->model = ALLOC_STRING("models/player.mdl"); + + pevCreate->renderfx = 16; // hologram + + //pevCreate->rendermode = 4; // solid + //pevCreate->renderamt = 255; + + SetBits( pevCreate->spawnflags, SF_MONSTER_FALL_TO_GROUND ); + + DispatchSpawn( ENT( pevCreate ) ); + + m_pHologram = CBaseEntity::Instance( pevCreate ); + + SetBits(m_pHologram->pev->flags, FL_ONGROUND); + m_pHologram->DontThink(); + + ((CBaseMonster*)m_pHologram)->m_iClass = CLASS_PLAYER; + + // set the destruct time + m_fDeactivateTime = UTIL_WeaponTimeBase() + 60.0; +} + +//========================================================= +// Deactivate - do whatever it is we do to an orphaned +// Hologram when we don't want it in the world anymore. +//========================================================= +void CHologramCharge::Deactivate( void ) +{ + pev->solid = SOLID_NOT; + if (m_pHologram) UTIL_Remove(m_pHologram); + UTIL_Remove( this ); +} + +void CHologramCharge :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_BOUNCE; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_satchel.mdl"); + UTIL_SetSize(pev, Vector( -16, -16, -4), Vector(16, 16, 4)); + UTIL_SetOrigin( this, pev->origin ); + + SetTouch(&CHologramCharge :: HologramSlide ); + SetUse(&CHologramCharge :: DetonateUse ); + SetThink(&CHologramCharge :: HologramThink ); + SetNextThink( 0.1 ); + + pev->gravity = 0.5; + pev->friction = 0.8; + + // hardcoded small damage + pev->dmg = 10; + + // ResetSequenceInfo( ); + pev->sequence = 1; + + m_pHologram = NULL; + m_fDeactivateTime = 0.0; +} + + +void CHologramCharge::HologramSlide( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // don't hit the guy that launched this grenade + if ( pOther->edict() == pev->owner ) + return; + + // pev->avelocity = Vector (300, 300, 300); + pev->gravity = 1;// normal gravity now + + // HACKHACK - On ground isn't always set, so look for ground underneath + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,10), ignore_monsters, edict(), &tr ); + + if ( tr.flFraction < 1.0 ) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.95; + pev->avelocity = pev->avelocity * 0.9; + // play sliding sound, volume based on velocity + } + if ( !(pev->flags & FL_ONGROUND) && pev->velocity.Length2D() > 10 ) + { + BounceSound(); + } + StudioFrameAdvance( ); +} + + +void CHologramCharge :: HologramThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if (m_pHologram && gpGlobals->time >= m_fDeactivateTime) + { + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + WRITE_COORD( pev->origin.x ); // Send to PAS because of the sound + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 2 ); + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, SMALL_EXPLOSION_VOLUME, 3.0 ); + + Deactivate(); + } + + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + if (pev->waterlevel == 3 && pev->watertype != CONTENT_FOG) + { + pev->movetype = MOVETYPE_FLY; + pev->velocity = pev->velocity * 0.8; + pev->avelocity = pev->avelocity * 0.9; + pev->velocity.z += 8; + } + else if (pev->waterlevel == 0 || pev->watertype == CONTENT_FOG) + { + pev->movetype = MOVETYPE_BOUNCE; + } + else + { + pev->velocity.z -= 8; + } +} + +void CHologramCharge :: Precache( void ) +{ + PRECACHE_MODEL("models/grenade.mdl"); + PRECACHE_SOUND("weapons/g_bounce1.wav"); + PRECACHE_SOUND("weapons/g_bounce2.wav"); + PRECACHE_SOUND("weapons/g_bounce3.wav"); +} + +void CHologramCharge :: BounceSound( void ) +{ + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/g_bounce1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/g_bounce2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/g_bounce3.wav", 1, ATTN_NORM); break; + } +} + + +class CHologram : public CBasePlayerWeapon +{ +public: + +#ifndef CLIENT_DLL + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; +#endif + + + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 3; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + void PrimaryAttack( void ); + void SecondaryAttack( void ); + int AddDuplicate( CBasePlayerItem *pOriginal ); + BOOL CanDeploy( void ); + BOOL Deploy( void ); + BOOL IsUseable( void ); + + void Holster( int skiplocal = 0 ); + void WeaponIdle( void ); + void Throw( void ); + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } +}; + +TYPEDESCRIPTION CHologram::m_SaveData[] = +{ + DEFINE_FIELD( CHologram, m_chargeReady, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CHologram, CBasePlayerWeapon ); + +LINK_ENTITY_TO_CLASS( weapon_hologram, CHologram ); + + +//========================================================= +// CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal +//========================================================= +int CHologram::AddDuplicate( CBasePlayerItem *pOriginal ) +{ + CHologram *pHologram; + +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + pHologram = (CHologram *)pOriginal; + + if ( pHologram->m_chargeReady != 0 ) + { + // player has some Holograms deployed. Refuse to add more. + return FALSE; + } + } + + return CBasePlayerWeapon::AddDuplicate ( pOriginal ); +} + +//========================================================= +//========================================================= +int CHologram::AddToPlayer( CBasePlayer *pPlayer ) +{ + int bResult = CBasePlayerItem::AddToPlayer( pPlayer ); + + pPlayer->pev->weapons |= (1<pszName = STRING(pev->classname); + p->pszAmmo1 = "Hologram Charge"; + p->iMaxAmmo1 = HOLOGRAM_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 3; + p->iPosition = 4; + p->iFlags = ITEM_FLAG_SELECTONEMPTY | ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + p->iId = m_iId = WEAPON_HOLOGRAM; + p->iWeight = HOLOGRAM_WEIGHT; + + return 1; +} + +//========================================================= +//========================================================= +BOOL CHologram::IsUseable( void ) +{ + if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] > 0 ) + { + // player is carrying some Holograms + return TRUE; + } + + if ( m_chargeReady != 0 ) + { + // player isn't carrying any Holograms, but has some out + return TRUE; + } + + return FALSE; +} + +BOOL CHologram::CanDeploy( void ) +{ + if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] > 0 ) + { + // player is carrying some Holograms + return TRUE; + } + + if ( m_chargeReady != 0 ) + { + // player isn't carrying any Holograms, but has some out + return TRUE; + } + + return FALSE; +} + +BOOL CHologram::Deploy( ) +{ + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + + if ( m_chargeReady ) + return DefaultDeploy( "models/v_satchel_radio.mdl", "models/p_satchel_radio.mdl", HOLOGRAM_RADIO_DRAW, "hive" ); + else + return DefaultDeploy( "models/v_satchel.mdl", "models/p_satchel.mdl", HOLOGRAM_DRAW, "trip" ); + + + return TRUE; +} + + +void CHologram::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + if ( m_chargeReady ) + { + SendWeaponAnim( HOLOGRAM_RADIO_HOLSTER ); + } + else + { + SendWeaponAnim( HOLOGRAM_DROP ); + } + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); + + if ( !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] && !m_chargeReady ) + { + m_pPlayer->pev->weapons &= ~(1<edict( ); + + CBaseEntity *pHologram = NULL; + + while ((pHologram = UTIL_FindEntityInSphere( pHologram, m_pPlayer->pev->origin, 4096 )) != NULL) + { + if (FClassnameIs( pHologram->pev, "monster_hologram")) + { + if (pHologram->pev->owner == pPlayer) + { + ((CHologramCharge*)pHologram)->MakeHologram(); + m_chargeReady = 2; + } + } + } + + m_chargeReady = 2; + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5; + } + break; + case 2: + // we're reloading, don't allow fire + { + } + break; + } +} + + +void CHologram::SecondaryAttack( void ) +{ + if ( m_chargeReady != 2 ) + { + Throw( ); + } +} + + +void CHologram::Throw( void ) +{ + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + Vector vecSrc = m_pPlayer->pev->origin; + + Vector vecThrow = gpGlobals->v_forward * 274 + m_pPlayer->pev->velocity; + +#ifndef CLIENT_DLL + CBaseEntity *pHologram = Create( "monster_hologram", vecSrc, Vector( 0, 0, 0), m_pPlayer->edict() ); + pHologram->pev->velocity = vecThrow; + pHologram->pev->avelocity.y = 400; + + m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_satchel_radio.mdl"); + m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_satchel_radio.mdl"); +#else + LoadVModel ( "models/v_satchel_radio.mdl", m_pPlayer ); +#endif + + SendWeaponAnim( HOLOGRAM_RADIO_DRAW ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_chargeReady = 1; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 1.0; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + } +} + + +void CHologram::WeaponIdle( void ) +{ + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + switch( m_chargeReady ) + { + case 0: + SendWeaponAnim( HOLOGRAM_FIDGET1 ); + // use tripmine animations + strcpy( m_pPlayer->m_szAnimExtention, "trip" ); + break; + case 1: + SendWeaponAnim( HOLOGRAM_RADIO_FIDGET1 ); + // use hivehand animations + strcpy( m_pPlayer->m_szAnimExtention, "hive" ); + break; + case 2: + if ( !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + m_chargeReady = 0; + RetireWeapon(); + return; + } + +#ifndef CLIENT_DLL + m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_satchel.mdl"); + m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_satchel.mdl"); +#else + LoadVModel ( "models/v_satchel.mdl", m_pPlayer ); +#endif + + SendWeaponAnim( HOLOGRAM_DRAW ); + + // use tripmine animations + strcpy( m_pPlayer->m_szAnimExtention, "trip" ); + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_chargeReady = 0; + break; + } + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );// how long till we do this again. +} + +//========================================================= +// DeactivateHolograms - removes all Holograms owned by +// the provided player. Should only be used upon death. +// +// Made this global on purpose. +//========================================================= +void DeactivateHolograms( CBasePlayer *pOwner ) +{ + edict_t *pFind; + + pFind = FIND_ENTITY_BY_CLASSNAME( NULL, "monster_hologram" ); + + while ( !FNullEnt( pFind ) ) + { + CBaseEntity *pEnt = CBaseEntity::Instance( pFind ); + CHologramCharge *pHologram = (CHologramCharge *)pEnt; + + if ( pHologram ) + { + if ( pHologram->pev->owner == pOwner->edict() ) + { + pHologram->Deactivate(); + } + } + + pFind = FIND_ENTITY_BY_CLASSNAME( pFind, "monster_hologram" ); + } +} + +#endif diff --git a/dlls/cthulhu/kingpin.cpp b/dlls/cthulhu/kingpin.cpp new file mode 100755 index 00000000..0c611305 --- /dev/null +++ b/dlls/cthulhu/kingpin.cpp @@ -0,0 +1,366 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Kingpin +//========================================================= + +#define KINGPIN_MAX_ATTACK_RADIUS 1024 + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= + +class CKingpin : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + int IgnoreConditions ( void ); + + float m_flNextFlinch; + + void Shockwave ( void ); + + void PainSound( void ); + void IdleSound( void ); + void AttackSound( void ); + + static const char *pAttackSounds[]; + static const char *pIdleSounds[]; + static const char *pPainSounds[]; + + // No range attacks + BOOL CheckRangeAttack1 ( float flDot, float flDist ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + int m_iSpriteTexture; + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; +}; + +LINK_ENTITY_TO_CLASS( monster_kingpin, CKingpin ); + +TYPEDESCRIPTION CKingpin::m_SaveData[] = +{ + DEFINE_FIELD( CKingpin, m_iSpriteTexture, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CKingpin, CBaseMonster ); + +const char *CKingpin::pAttackSounds[] = +{ + "x/x_attack1.wav", + "x/x_attack2.wav", + "x/x_attack3.wav", +}; + +const char *CKingpin::pIdleSounds[] = +{ + "x/x_attack1.wav", + "x/x_attack2.wav", + "x/x_attack3.wav", + "x/x_pain1.wav", + "x/x_pain2.wav", + "x/x_pain3.wav", +}; + +const char *CKingpin::pPainSounds[] = +{ + "x/x_pain1.wav", + "x/x_pain2.wav", + "x/x_pain3.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CKingpin :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CKingpin :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + pev->yaw_speed = ys; +} + +int CKingpin :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( IsAlive() ) + PainSound(); + + // kingpin does not take damage, but we want to use this logic, so pass a small number + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, 0.001, bitsDamageType ); +} + +void CKingpin :: PainSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + if (RANDOM_LONG(0,5) < 3) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); +} + +void CKingpin :: 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 CKingpin :: 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) ); +} + + +//========================================================= +// Spawn +//========================================================= +void CKingpin :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/kingpin.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; + if (pev->health == 0) + pev->health = 1000; // always 1000 + 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(); + + // PhilG: hack to allow CheckRangeAttack1 to be used + m_afCapability |= bits_CAP_RANGE_ATTACK1; +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CKingpin :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/kingpin.mdl"); + + 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( pPainSounds ); i++ ) + PRECACHE_SOUND((char *)pPainSounds[i]); + + 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" ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + + +int CKingpin::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { + 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 + 2.0; + } + + return iIgnore; + +} + +BOOL CKingpin :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + //if ( !HasConditions( bits_COND_ENEMY_OCCLUDED ) && flDist <= 1024 && flDot >= 0.5 ) + if ( flDist <= 1024 && flDot >= 0.5 ) + { + if (gpGlobals->time >= m_flNextAttack) + { + // we do the shockwave here + Shockwave(); + + m_flNextAttack = gpGlobals->time + 2.0; + } + } + + // we always return false, otherwise it will try and do an animation that does not exist + return FALSE; +} + +void CKingpin::Shockwave() +{ + 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 + KINGPIN_MAX_ATTACK_RADIUS / .2); // reach damage radius over .3 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 16 ); // life + WRITE_BYTE( 96 ); // width + WRITE_BYTE( 0 ); // noise + + WRITE_BYTE( 64 ); // RED + WRITE_BYTE( 255 ); // GREEN + WRITE_BYTE( 64 ); // BLUE + + 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 + (KINGPIN_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( 12 ); // life + WRITE_BYTE( 96 ); // width + WRITE_BYTE( 0 ); // noise + + WRITE_BYTE( 64 ); // RED + WRITE_BYTE( 255 ); // GREEN + WRITE_BYTE( 64 ); // BLUE + + 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, KINGPIN_MAX_ATTACK_RADIUS )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + if ( !FClassnameIs(pEntity->pev, "monster_kingpin") ) + { + + // kingpins do FULL damage if the ent in question is visible. Half damage otherwise. + // This means that you must get out of the kingpin's attack range entirely to avoid damage. + // Calculate full damage first + + flAdjustedDamage = 200.0; + + flDist = (pEntity->Center() - pev->origin).Length(); + + flAdjustedDamage -= ( flDist / KINGPIN_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 ); + } + } + } + } +} + + diff --git a/dlls/cthulhu/laserspot.cpp b/dlls/cthulhu/laserspot.cpp new file mode 100755 index 00000000..b117715b --- /dev/null +++ b/dlls/cthulhu/laserspot.cpp @@ -0,0 +1,84 @@ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + + +LINK_ENTITY_TO_CLASS( laser_spot, CLaserSpot ); + +//========================================================= +CLaserSpot *CLaserSpot::CreateSpot( void ) +{ + CLaserSpot *pSpot = GetClassPtr( (CLaserSpot *)NULL ); + pSpot->Spawn(); + + pSpot->pev->classname = MAKE_STRING("laser_spot"); + + return pSpot; +} + +//========================================================= +//========================================================= +CLaserSpot *CLaserSpot::CreateSpot( const char* spritename ) +{ + CLaserSpot *pSpot = CreateSpot(); + SET_MODEL(ENT(pSpot->pev), spritename); + return pSpot; +} + +//========================================================= +//========================================================= +void CLaserSpot::Spawn( void ) +{ + Precache( ); + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_NOT; + + pev->rendermode = kRenderGlow; + pev->renderfx = kRenderFxNoDissipation; + pev->renderamt = 255; + + SET_MODEL(ENT(pev), "sprites/laserdot.spr"); + UTIL_SetOrigin( this, pev->origin ); +}; + +//========================================================= +// Suspend- make the laser sight invisible. +//========================================================= +void CLaserSpot::Suspend( float flSuspendTime ) +{ + pev->effects |= EF_NODRAW; + + //LRC: -1 means suspend indefinitely + if (flSuspendTime == -1) + { + SetThink( NULL ); + } + else + { + SetThink( Revive ); + SetNextThink( flSuspendTime ); + } +} + +//========================================================= +// Revive - bring a suspended laser sight back. +//========================================================= +void CLaserSpot::Revive( void ) +{ + pev->effects &= ~EF_NODRAW; + + SetThink( NULL ); +} + +void CLaserSpot::Precache( void ) +{ + PRECACHE_MODEL("sprites/laserdot.spr"); +}; + + diff --git a/dlls/cthulhu/lightning_gun.cpp b/dlls/cthulhu/lightning_gun.cpp new file mode 100755 index 00000000..69520bf8 --- /dev/null +++ b/dlls/cthulhu/lightning_gun.cpp @@ -0,0 +1,319 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "soundent.h" +#include "shake.h" +#include "gamerules.h" +#include "effects.h" + +#include "lightning_gun.h" + + +enum LightningGun_e { + LIGHTNING_GUN_IDLE = 0, + LIGHTNING_GUN_DRAW, + LIGHTNING_GUN_HOLSTER, + LIGHTNING_GUN_ZAP +}; + +LINK_ENTITY_TO_CLASS( weapon_lightninggun, CLightningGun ); + + +TYPEDESCRIPTION CLightningGun::m_SaveData[] = +{ + DEFINE_FIELD( CLightningGun, m_fInAttack, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CLightningGun, CBasePlayerWeapon ); + + +void CLightningGun::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_LIGHTNING_GUN; + SET_MODEL(ENT(pev), "models/w_lightning_gun.mdl"); + + m_iDefaultAmmo = LIGHTNING_GUN_DEFAULT_GIVE; + + FallInit();// get ready to fall down. + + m_iBeams = 0; +} + +void CLightningGun::Precache( void ) +{ + PRECACHE_MODEL("models/w_lightning_gun.mdl"); + PRECACHE_MODEL("models/v_lightning_gun.mdl"); +// PRECACHE_MODEL("models/p_lightning_gun.mdl"); + + PRECACHE_SOUND("items/9mmclip1.wav"); + +// PRECACHE_SOUND("weapons/lightning_gun2.wav"); + PRECACHE_SOUND("weapons/electro4.wav"); +// PRECACHE_SOUND("weapons/electro5.wav"); +// PRECACHE_SOUND("weapons/electro6.wav"); + PRECACHE_SOUND("debris/zap4.wav"); + PRECACHE_SOUND("hassault/hw_shoot1.wav"); + + PRECACHE_MODEL( "sprites/lgtning.spr" ); +} + +int CLightningGun::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +int CLightningGun::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "lightning"; + p->iMaxAmmo1 = LIGHTNING_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 3; + p->iPosition = 1; + p->iId = m_iId = WEAPON_LIGHTNING_GUN; + p->iFlags = 0; + p->iWeight = LIGHTNING_GUN_WEIGHT; + + return 1; +} + + +BOOL CLightningGun::Deploy( ) +{ + return DefaultDeploy( "models/v_lightning_gun.mdl", "", LIGHTNING_GUN_DRAW, "lightning gun" ); +} + + +void CLightningGun::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + // m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + SendWeaponAnim( LIGHTNING_GUN_HOLSTER ); + m_fInAttack = 0; + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + + +void CLightningGun::PrimaryAttack() +{ + if (m_fInAttack != 0) + { + if ( m_fInAttack == 1 ) + { + ZapShoot(); + } + else // == 2 + { + ZapDone(); + } + return; + } + + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3) + { + PlayEmptySound( ); + m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->time + 0.15; + return; + } + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] < 1) + { + PlayEmptySound( ); + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + return; + } + + ZapPowerUp(); +} + +void CLightningGun::ZapPowerUp() +{ + m_fInAttack = 1; + + EMIT_SOUND_DYN( ENT(pev), CHAN_WEAPON, "debris/zap4.wav", 1, ATTN_NORM, 0, 150 ); + + m_flTimeWeaponIdle = gpGlobals->time + 0.5; +} + + +void CLightningGun::ZapShoot() +{ + ClearBeams( ); + + 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_flTimeWeaponIdle = gpGlobals->time + 0.2; + m_flNextPrimaryAttack = gpGlobals->time + 0.2; + + m_fInAttack = 2; +} + +void CLightningGun::ZapDone() +{ + ClearBeams( ); + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + m_fInAttack = 0; + m_flTimeWeaponIdle = gpGlobals->time + 0.8; + m_flNextPrimaryAttack = gpGlobals->time + 0.8; +} + +//========================================================= +// ZapBeam - heavy damage directly forward +//========================================================= +void CLightningGun :: ZapBeam( int side ) +{ + TraceResult tr; + CBaseEntity *pEntity; + + if (m_iBeams >= LIGHTNING_GUN_MAX_BEAMS) + return; + + // this should be attachment 0 (???) + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecDest = vecSrc + vecAiming * 2048; + UTIL_TraceLine( vecSrc, vecDest, dont_ignore_monsters, m_pPlayer->edict(), &tr ); + + m_pBeam[m_iBeams] = CBeam::BeamCreate( "sprites/lgtning.spr", 50 ); + if (!m_pBeam[m_iBeams]) + return; + + m_pBeam[m_iBeams]->PointEntInit( tr.vecEndPos, m_pPlayer->entindex( ) ); + m_pBeam[m_iBeams]->SetEndAttachment( 1 ); + m_pBeam[m_iBeams]->SetColor( 180, 255, 96 ); + m_pBeam[m_iBeams]->SetBrightness( 255 ); + m_pBeam[m_iBeams]->SetNoise( 20 ); + m_pBeam[m_iBeams]->pev->spawnflags |= SF_BEAM_TEMPORARY; // Flag these to be destroyed on save/restore or level transition + m_iBeams++; + + pEntity = CBaseEntity::Instance(tr.pHit); + if (pEntity != NULL && pEntity->pev->takedamage) + { + pEntity->TraceAttack( pev, gSkillData.plrDmgLightningGun, vecAiming, &tr, DMG_SHOCK ); + } + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "weapons/electro4.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); +} + +void CLightningGun :: ClearBeams( ) +{ + for (int i = 0; i < LIGHTNING_GUN_MAX_BEAMS; i++) + { + if (m_pBeam[i]) + { + UTIL_Remove( m_pBeam[i] ); + m_pBeam[i] = NULL; + } + } + m_iBeams = 0; + + STOP_SOUND( ENT(pev), CHAN_WEAPON, "debris/zap4.wav" ); +} + +void CLightningGun::SecondaryAttack() +{ +} + +//========================================================= +// StartFire- since all of this code has to run and then +// call Fire(), it was easier at this point to rip it out +// of weaponidle() and make its own function then to try to +// merge this into Fire(), which has some identical variable names +//========================================================= + +void CLightningGun::WeaponIdle( void ) +{ + ResetEmptySound( ); + + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + if (m_fInAttack != 0) + { + if ( m_fInAttack == 1 ) + { + ZapShoot(); + } + else // == 2 + { + ZapDone(); + } + } + else + { + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 2, 3 ); + + SendWeaponAnim( LIGHTNING_GUN_IDLE ); + } +} + +/////////////////////////////////////////////////////////////////////////////////////// + +void CLightningGunAmmo::Spawn( void ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/w_lightning_gun_ammo.mdl"); + CBasePlayerAmmo::Spawn( ); +} + +void CLightningGunAmmo::Precache( void ) +{ + PRECACHE_MODEL ("models/w_lightning_gun_ammo.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); +} + +BOOL CLightningGunAmmo::AddAmmo( CBaseEntity *pOther ) +{ + if (pOther->GiveAmmo( AMMO_LIGHTNING_GIVE, "lightning", LIGHTNING_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; +} + + +LINK_ENTITY_TO_CLASS( ammo_lightning, CLightningGunAmmo ); + +#endif \ No newline at end of file diff --git a/dlls/cthulhu/lightning_gun.h b/dlls/cthulhu/lightning_gun.h new file mode 100755 index 00000000..c04c27df --- /dev/null +++ b/dlls/cthulhu/lightning_gun.h @@ -0,0 +1,60 @@ + +#ifndef LIGHTNING_GUN_H +#define LIGHTNING_GUN_H + +class CLightningGun : public CBasePlayerWeapon +{ +public: + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 3; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + void WeaponIdle( void ); + void ZapPowerUp( void ); + void ZapShoot( void ); + void ZapDone( void ); + + int m_fInAttack; + + int m_iBeams; + + void ClearBeams( void ); + void ZapBeam( int side ); + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } + +private: + + // the beam effect + CBeam* m_pBeam[LIGHTNING_GUN_MAX_BEAMS]; +}; + +//////////////////////////////////////////////////////////////////////////// + +class CLightningGunAmmo : public CBasePlayerAmmo +{ + void Spawn( void ); + void Precache( void ); + BOOL AddAmmo( CBaseEntity *pOther ); +}; + +#endif + diff --git a/dlls/cthulhu/molotov.cpp b/dlls/cthulhu/molotov.cpp new file mode 100755 index 00000000..db5eb252 --- /dev/null +++ b/dlls/cthulhu/molotov.cpp @@ -0,0 +1,224 @@ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + +#include "effects.h" + +#include "molotov.h" +#include "monster_molotov.h" + +LINK_ENTITY_TO_CLASS( weapon_molotov, CMolotov ); + + +void CMolotov::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_MOLOTOV; + SET_MODEL(ENT(pev), "models/w_molotov.mdl"); + + pev->dmg = gSkillData.plrDmgMolotov; + + m_iDefaultAmmo = MOLOTOV_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + +void CMolotov::Precache( void ) +{ + PRECACHE_MODEL("models/w_molotov.mdl"); + PRECACHE_MODEL("models/v_molotov.mdl"); +// PRECACHE_MODEL("models/p_molotov.mdl"); +} + +int CMolotov::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Molotov"; + p->iMaxAmmo1 = MOLOTOV_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 2; + p->iPosition = 0; + p->iId = m_iId = WEAPON_MOLOTOV; + p->iWeight = MOLOTOV_WEIGHT; + p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + + return 1; +} + + +BOOL CMolotov::Deploy( ) +{ + m_flReleaseThrow = -1; + m_flFinishThrow = -1; + return DefaultDeploy( "models/v_molotov.mdl", "", MOLOTOV_DRAW, "molotov" ); +} + +BOOL CMolotov::CanHolster( void ) +{ + // can only holster molotov when not lit! + return ( m_flStartThrow == 0 ); +} + +void CMolotov::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + SendWeaponAnim( MOLOTOV_HOLSTER ); + } + else + { + // no more molotovs! + m_pPlayer->pev->weapons &= ~(1<pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + +int CMolotov::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +void CMolotov::PrimaryAttack() +{ + if (!m_flStartThrow && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0) + { + m_flStartThrow = gpGlobals->time; + //m_flReleaseThrow = 0; + //m_flFinishThrow = 0; + m_flReleaseThrow = gpGlobals->time; + m_flFinishThrow = gpGlobals->time; + + SendWeaponAnim( MOLOTOV_LIGHT ); + m_flTimeWeaponIdle = gpGlobals->time + 1.5; + } +} + +void CMolotov::WeaponIdle( void ) +{ + //if (m_flReleaseThrow == 0) + // m_flReleaseThrow = gpGlobals->time; + //else if (m_flFinishThrow == 0) + // m_flFinishThrow = gpGlobals->time; + + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + if (m_flStartThrow > 0) + { + SendWeaponAnim( MOLOTOV_THROW ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_flStartThrow = 0; + m_flNextPrimaryAttack = gpGlobals->time + 1.0; + m_flTimeWeaponIdle = gpGlobals->time + 0.5; + + return; + } + else if (m_flReleaseThrow > 0) + { + Vector angThrow = m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle; + + if (angThrow.x < 0) + angThrow.x = -10 + angThrow.x * ((90 - 10) / 90.0); + else + angThrow.x = -10 + angThrow.x * ((90 + 10) / 90.0); + + float flVel = (90 - angThrow.x) * 4; + if (flVel > 500) + flVel = 500; + + UTIL_MakeVectors( angThrow ); + + Vector vecSrc = m_pPlayer->pev->origin + m_pPlayer->pev->view_ofs + gpGlobals->v_forward * 16; + + Vector vecThrow = gpGlobals->v_forward * flVel + m_pPlayer->pev->velocity; + + float time = m_flReleaseThrow - gpGlobals->time + 5.0 - 0.5; // because it has been 0.5 seconds since we lit the fuse... + if (time < 0) + time = 0; + + CMonsterMolotov::ShootContact( m_pPlayer->pev, vecSrc, vecThrow ); + + m_flTimeWeaponIdle = gpGlobals->time + 0.5; + + m_flReleaseThrow = -1; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + if ( !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + // just threw last bottle of molotov + // set attack times in the future, and weapon idle in the future so we can see the whole throw + // animation, weapon idle will automatically retire the weapon for us. + m_flTimeWeaponIdle = m_flNextSecondaryAttack = m_flNextPrimaryAttack = gpGlobals->time + 0.5;// ensure that the animation can finish playing + } + + return; + } + else if (m_flFinishThrow > 0) + { + // we've finished the throw, restart. + m_flStartThrow = 0; + + m_flReleaseThrow = -1; + m_flFinishThrow = -1; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + SendWeaponAnim( MOLOTOV_DRAW ); + } + else + { + RetireWeapon(); + return; + } + + //m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); + //m_flReleaseThrow = -1; + //m_flFinishThrow = -1; + return; + } + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.75) + { + iAnim = MOLOTOV_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 );// how long till we do this again. + } + else + { + iAnim = MOLOTOV_IDLE2; + m_flTimeWeaponIdle = gpGlobals->time + 75.0 / 30.0; + } + + SendWeaponAnim( iAnim ); + } +} + + + + diff --git a/dlls/cthulhu/molotov.h b/dlls/cthulhu/molotov.h new file mode 100755 index 00000000..f39f6a30 --- /dev/null +++ b/dlls/cthulhu/molotov.h @@ -0,0 +1,61 @@ + +#ifndef MOLOTOV_H +#define MOLOTOV_H + +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#define MOLOTOV_PRIMARY_VOLUME 450 + +enum molotov_e { + MOLOTOV_DRAW = 0, + MOLOTOV_IDLE1, + MOLOTOV_IDLE2, + MOLOTOV_LIGHT, + MOLOTOV_THROW, // toss + MOLOTOV_HOLSTER +}; + + + +class CMolotov : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 2; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + void PrimaryAttack( void ); + BOOL Deploy( void ); + BOOL CanHolster( void ); + void Holster( int skiplocal = 0 ); + void WeaponIdle( void ); + float m_flFinishThrow; + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } +}; + +#endif + + diff --git a/dlls/cthulhu/monster_PowderOfIbn.cpp b/dlls/cthulhu/monster_PowderOfIbn.cpp new file mode 100755 index 00000000..686e6658 --- /dev/null +++ b/dlls/cthulhu/monster_PowderOfIbn.cpp @@ -0,0 +1,311 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== powder of ibn.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" + +#include "monster_PowderOfIbn.h" + +//===================powder of ibn + + +LINK_ENTITY_TO_CLASS( powderofibn, CMonsterPowderOfIbn ); + +// +// Powder Explode +// +void CMonsterPowderOfIbn::Explode( Vector vecSrc, Vector vecAim ) +{ + TraceResult tr; + UTIL_TraceLine ( pev->origin, pev->origin + Vector ( 0, 0, -32 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_POWDER_IBN ); +} + +// UNDONE: temporary scorching for PreAlpha - find a less sleazy permenant solution. +void CMonsterPowderOfIbn::Explode( TraceResult *pTrace, int bitsDamageType ) +{ + float flRndSound;// sound randomizer + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + pev->takedamage = DAMAGE_NO; + + // Pull out of the wall a bit + if ( pTrace->flFraction != 1.0 ) + { + pev->origin = pTrace->vecEndPos + (pTrace->vecPlaneNormal * (pev->dmg - 24) * 0.6); + } + + int iContents = UTIL_PointContents ( pev->origin ); + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + WRITE_COORD( pev->origin.x ); // Send to PAS because of the sound + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + //WRITE_SHORT( g_sModelIndexFireball ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 5 ); // scale * 10 + //WRITE_BYTE( (pev->dmg - 50) * .60 ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NOSOUND ); + MESSAGE_END(); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, 64, 3.0 ); + entvars_t *pevOwner; + if ( pev->owner ) + pevOwner = VARS( pev->owner ); + else + pevOwner = NULL; + + pev->owner = NULL; // can't traceline attack owner if this is set + + RadiusDamage ( pev, pevOwner, pev->dmg, CLASS_NONE, bitsDamageType ); + + flRndSound = RANDOM_FLOAT( 0 , 1 ); + + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris1.wav", 0.55, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris2.wav", 0.55, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris3.wav", 0.55, ATTN_NORM); break; + } + + pev->effects |= EF_NODRAW; + SetThink( Smoke ); + pev->velocity = g_vecZero; + SetNextThink( 0.3 ); +} + +void CMonsterPowderOfIbn::ExplodeTouch( CBaseEntity *pOther ) +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + pev->enemy = pOther->edict(); + + vecSpot = pev->origin - pev->velocity.Normalize() * 32; + UTIL_TraceLine( vecSpot, vecSpot + pev->velocity.Normalize() * 64, ignore_monsters, ENT(pev), &tr ); + + Explode( &tr, DMG_POWDER_IBN ); +} + +void CMonsterPowderOfIbn::Smoke( void ) +{ + if (UTIL_PointContents ( pev->origin ) == CONTENTS_WATER) + { + UTIL_Bubbles( pev->origin - Vector( 64, 64, 64 ), pev->origin + Vector( 64, 64, 64 ), 100 ); + } + else + { + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 8 ); // scale * 10 + //WRITE_BYTE( (pev->dmg - 50) * 0.80 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + UTIL_Remove( this ); +} + +void CMonsterPowderOfIbn::Killed( entvars_t *pevAttacker, int iGib ) +{ + Detonate( ); +} + + +void CMonsterPowderOfIbn::PreDetonate( void ) +{ + CSoundEnt::InsertSound ( bits_SOUND_PLAYER, pev->origin, 400, 0.3 ); + + SetThink( Detonate ); + SetNextThink( 1 ); +} + + +void CMonsterPowderOfIbn::Detonate( void ) +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 ); + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -40 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_POWDER_IBN ); +} + +void CMonsterPowderOfIbn::BounceTouch( CBaseEntity *pOther ) +{ + // don't hit the guy that launched this powder + if ( pOther->edict() == pev->owner ) + return; + + Vector vecTestVelocity; + // pev->avelocity = Vector (300, 300, 300); + + // this is my heuristic for modulating the powder velocity because powder dropped purely vertical + // or thrown very far tend to slow down too quickly for me to always catch just by testing velocity. + // trimming the Z velocity a bit seems to help quite a bit. + vecTestVelocity = pev->velocity; + vecTestVelocity.z *= 0.45; + + if ( !m_fRegisteredSound && vecTestVelocity.Length() <= 60 ) + { + //ALERT( at_console, "Powder of Ibn Registered!: %f\n", vecTestVelocity.Length() ); + + // powder is moving really slow. It's probably very close to where it will ultimately stop moving. + // go ahead and emit the player sound. + + // register a radius louder than the explosion, so we make sure everyone gets out of the way + CSoundEnt::InsertSound ( bits_SOUND_PLAYER, pev->origin, pev->dmg / 0.4, 0.3 ); + m_fRegisteredSound = TRUE; + } + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.8; + + pev->sequence = RANDOM_LONG( 1, 1 ); + } + else + { + // play bounce sound + BounceSound(); + } + pev->framerate = pev->velocity.Length() / 200.0; + if (pev->framerate > 1.0) + pev->framerate = 1; + else if (pev->framerate < 0.5) + pev->framerate = 0; + +} + +void CMonsterPowderOfIbn :: BounceSound( void ) +{ + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/powder_hit1.wav", 0.25, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/powder_hit2.wav", 0.25, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/powder_hit3.wav", 0.25, ATTN_NORM); break; + } +} + +void CMonsterPowderOfIbn :: TumbleThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if (pev->dmgtime - 1 < gpGlobals->time) + { + CSoundEnt::InsertSound ( bits_SOUND_PLAYER, pev->origin + pev->velocity * (pev->dmgtime - gpGlobals->time), 400, 0.1 ); + } + + if (pev->dmgtime <= gpGlobals->time) + { + SetThink( Detonate ); + } + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + pev->framerate = 0.2; + } +} + + +void CMonsterPowderOfIbn:: Spawn( void ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->classname = MAKE_STRING( "powder of ibn" ); + + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_powderofibn.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->dmg = 10; + m_fRegisteredSound = FALSE; +} + +void CMonsterPowderOfIbn::DangerSoundThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + CSoundEnt::InsertSound ( bits_SOUND_PLAYER, pev->origin + pev->velocity * 0.5, pev->velocity.Length( ), 0.2 ); + SetNextThink( 0.2 ); + + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + } +} + +CMonsterPowderOfIbn * CMonsterPowderOfIbn::ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CMonsterPowderOfIbn *pPowder = GetClassPtr( (CMonsterPowderOfIbn *)NULL ); + pPowder->Spawn(); + // powders arc lower + pPowder->pev->gravity = 0.5;// lower gravity since powder is aerodynamic and engine doesn't know it. + UTIL_SetOrigin( pPowder, vecStart ); + pPowder->pev->velocity = vecVelocity; + pPowder->pev->angles = UTIL_VecToAngles (pPowder->pev->velocity); + pPowder->pev->owner = ENT(pevOwner); + + // make monsters afaid of it while in the air + pPowder->SetThink( DangerSoundThink ); + pPowder->SetNextThink(0); + + // Tumble in air + pPowder->pev->avelocity.x = RANDOM_FLOAT ( -100, -500 ); + + // Explode on contact + pPowder->SetTouch( ExplodeTouch ); + + // powder does a little damage, but makes Dunwich Horror visible + pPowder->pev->dmg = 10; + + return pPowder; +} + + +//======================end powder of ibn + diff --git a/dlls/cthulhu/monster_PowderOfIbn.h b/dlls/cthulhu/monster_PowderOfIbn.h new file mode 100755 index 00000000..f284d575 --- /dev/null +++ b/dlls/cthulhu/monster_PowderOfIbn.h @@ -0,0 +1,32 @@ + +#ifndef MONSTER_POWDER_IBN_H +#define MONSTER_POWDER_IBN_H + + +// Dynamite +class CMonsterPowderOfIbn : public CBaseMonster +{ +public: + void Spawn( void ); + + static CMonsterPowderOfIbn *ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + + void Explode( Vector vecSrc, Vector vecAim ); + void Explode( TraceResult *pTrace, int bitsDamageType ); + void EXPORT Smoke( void ); + + void EXPORT BounceTouch( CBaseEntity *pOther ); + void EXPORT ExplodeTouch( CBaseEntity *pOther ); + void EXPORT DangerSoundThink( void ); + void EXPORT PreDetonate( void ); + void EXPORT Detonate( void ); + void EXPORT TumbleThink( void ); + + virtual void BounceSound( void ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void Killed( entvars_t *pevAttacker, int iGib ); + + BOOL m_fRegisteredSound;// whether or not this grenade has issued its DANGER sound to the world sound list yet. +}; + +#endif diff --git a/dlls/cthulhu/monster_dynamite.cpp b/dlls/cthulhu/monster_dynamite.cpp new file mode 100755 index 00000000..8ed66452 --- /dev/null +++ b/dlls/cthulhu/monster_dynamite.cpp @@ -0,0 +1,336 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== dynamite.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" + + +#include "monster_dynamite.h" + +//===================dynamite + + +LINK_ENTITY_TO_CLASS( dynamite, CMonsterDynamite ); + +// +// Dynamite Explode +// +void CMonsterDynamite::Explode( Vector vecSrc, Vector vecAim ) +{ + TraceResult tr; + UTIL_TraceLine ( pev->origin, pev->origin + Vector ( 0, 0, -32 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + +// UNDONE: temporary scorching for PreAlpha - find a less sleazy permenant solution. +void CMonsterDynamite::Explode( TraceResult *pTrace, int bitsDamageType ) +{ + float flRndSound;// sound randomizer + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + pev->takedamage = DAMAGE_NO; + + // Pull out of the wall a bit + if ( pTrace->flFraction != 1.0 ) + { + pev->origin = pTrace->vecEndPos + (pTrace->vecPlaneNormal * (pev->dmg - 24) * 0.6); + } + + int iContents = UTIL_PointContents ( pev->origin ); + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + WRITE_COORD( pev->origin.x ); // Send to PAS because of the sound + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + if (iContents != CONTENTS_WATER) + { + WRITE_SHORT( g_sModelIndexFireball ); + } + else + { + WRITE_SHORT( g_sModelIndexWExplosion ); + } + WRITE_BYTE( (pev->dmg - 50) * .60 ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); + entvars_t *pevOwner; + if ( pev->owner ) + pevOwner = VARS( pev->owner ); + else + pevOwner = NULL; + + pev->owner = NULL; // can't traceline attack owner if this is set + + RadiusDamage ( pev, pevOwner, pev->dmg, CLASS_NONE, bitsDamageType ); + + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH2 ); + } + + flRndSound = RANDOM_FLOAT( 0 , 1 ); + + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris1.wav", 0.55, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris2.wav", 0.55, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris3.wav", 0.55, ATTN_NORM); break; + } + + pev->effects |= EF_NODRAW; + SetThink( Smoke ); + pev->velocity = g_vecZero; + SetNextThink( 0.3 ); + + if (iContents != CONTENTS_WATER) + { + int sparkCount = RANDOM_LONG(0,3); + for ( int i = 0; i < sparkCount; i++ ) + Create( "spark_shower", pev->origin, pTrace->vecPlaneNormal, NULL ); + } +} + + +void CMonsterDynamite::Smoke( void ) +{ + if (UTIL_PointContents ( pev->origin ) == CONTENTS_WATER) + { + UTIL_Bubbles( pev->origin - Vector( 64, 64, 64 ), pev->origin + Vector( 64, 64, 64 ), 100 ); + } + else + { + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( (pev->dmg - 50) * 0.80 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + UTIL_Remove( this ); +} + +void CMonsterDynamite::Killed( entvars_t *pevAttacker, int iGib ) +{ + Detonate( ); +} + + +// Timed dynamite, this think is called when time runs out. +void CMonsterDynamite::DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( Detonate ); + SetNextThink(0); +} + +void CMonsterDynamite::PreDetonate( void ) +{ + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, 400, 0.3 ); + + SetThink( Detonate ); + SetNextThink( 1 ); +} + + +void CMonsterDynamite::Detonate( void ) +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 ); + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -40 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + +void CMonsterDynamite::BounceTouch( CBaseEntity *pOther ) +{ + // don't hit the guy that launched this dynamite + if ( pOther->edict() == pev->owner ) + return; + + // only do damage if we're moving fairly fast + if (m_flNextAttack < gpGlobals->time && pev->velocity.Length() > 100) + { + entvars_t *pevOwner = VARS( pev->owner ); + if (pevOwner) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + ClearMultiDamage( ); + pOther->TraceAttack(pevOwner, 1, gpGlobals->v_forward, &tr, DMG_CLUB ); + ApplyMultiDamage( pev, pevOwner); + } + m_flNextAttack = gpGlobals->time + 1.0; // debounce + } + + Vector vecTestVelocity; + // pev->avelocity = Vector (300, 300, 300); + + // this is my heuristic for modulating the dynamite velocity because dynamite dropped purely vertical + // or thrown very far tend to slow down too quickly for me to always catch just by testing velocity. + // trimming the Z velocity a bit seems to help quite a bit. + vecTestVelocity = pev->velocity; + vecTestVelocity.z *= 0.45; + + if ( !m_fRegisteredSound && vecTestVelocity.Length() <= 60 ) + { + //ALERT( at_console, "Dynamite Registered!: %f\n", vecTestVelocity.Length() ); + + // dynamite is moving really slow. It's probably very close to where it will ultimately stop moving. + // go ahead and emit the danger sound. + + // register a radius louder than the explosion, so we make sure everyone gets out of the way + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, pev->dmg / 0.4, 0.3 ); + m_fRegisteredSound = TRUE; + } + + if (pev->flags & FL_ONGROUND) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.8; + + pev->sequence = RANDOM_LONG( 1, 1 ); + } + else + { + // play bounce sound + BounceSound(); + } + pev->framerate = pev->velocity.Length() / 200.0; + if (pev->framerate > 1.0) + pev->framerate = 1; + else if (pev->framerate < 0.5) + pev->framerate = 0; + +} + +void CMonsterDynamite :: BounceSound( void ) +{ + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit1.wav", 0.25, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit2.wav", 0.25, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/grenade_hit3.wav", 0.25, ATTN_NORM); break; + } +} + +void CMonsterDynamite :: TumbleThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if (pev->dmgtime - 1 < gpGlobals->time) + { + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * (pev->dmgtime - gpGlobals->time), 400, 0.1 ); + } + + if (pev->dmgtime <= gpGlobals->time) + { + SetThink( Detonate ); + } + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + pev->framerate = 0.2; + } +} + + +void CMonsterDynamite:: Spawn( void ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->classname = MAKE_STRING( "dynamite" ); + + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_dynamite.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->dmg = 100; + m_fRegisteredSound = FALSE; +} + +CMonsterDynamite * CMonsterDynamite:: Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ) +{ + CMonsterDynamite *pDynamite = GetClassPtr( (CMonsterDynamite *)NULL ); + pDynamite->Spawn(); + UTIL_SetOrigin( pDynamite, vecStart ); + pDynamite->pev->velocity = vecVelocity; + pDynamite->pev->angles = UTIL_VecToAngles(pDynamite->pev->velocity); + pDynamite->pev->owner = ENT(pevOwner); + + pDynamite->SetTouch( BounceTouch ); // Bounce if touched + + // Take one second off of the desired detonation time and set the think to PreDetonate. PreDetonate + // will insert a DANGER sound into the world sound list and delay detonation for one second so that + // the dynamite explodes after the exact amount of time specified in the call to ShootTimed(). + + pDynamite->pev->dmgtime = gpGlobals->time + time; + pDynamite->SetThink( TumbleThink ); + pDynamite->SetNextThink( 0.1 ); + if (time < 0.1) + { + pDynamite->SetNextThink(0); + pDynamite->pev->velocity = Vector( 0, 0, 0 ); + } + + pDynamite->pev->sequence = RANDOM_LONG( 3, 6 ); + pDynamite->pev->framerate = 1.0; + + // Tumble through the air + // pDynamite->pev->avelocity.x = -400; + + pDynamite->pev->gravity = 0.5; + pDynamite->pev->friction = 0.8; + + SET_MODEL(ENT(pDynamite->pev), "models/w_dynamite.mdl"); + pDynamite->pev->dmg = 100; + + return pDynamite; +} + + +//======================end dynamite + diff --git a/dlls/cthulhu/monster_dynamite.h b/dlls/cthulhu/monster_dynamite.h new file mode 100755 index 00000000..692fd8ce --- /dev/null +++ b/dlls/cthulhu/monster_dynamite.h @@ -0,0 +1,30 @@ + +#ifndef MONSTER_DYNAMITE_H +#define MONSTER_DYNAMITE_H + +// Dynamite +class CMonsterDynamite : public CBaseMonster +{ +public: + void Spawn( void ); + + static CMonsterDynamite *Shoot( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity, float time ); + + void Explode( Vector vecSrc, Vector vecAim ); + void Explode( TraceResult *pTrace, int bitsDamageType ); + void EXPORT Smoke( void ); + + void EXPORT BounceTouch( CBaseEntity *pOther ); + void EXPORT PreDetonate( void ); + void EXPORT Detonate( void ); + void EXPORT DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TumbleThink( void ); + + virtual void BounceSound( void ); + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void Killed( entvars_t *pevAttacker, int iGib ); + + BOOL m_fRegisteredSound;// whether or not this grenade has issued its DANGER sound to the world sound list yet. +}; + +#endif diff --git a/dlls/cthulhu/monster_molotov.cpp b/dlls/cthulhu/monster_molotov.cpp new file mode 100755 index 00000000..cb9f616a --- /dev/null +++ b/dlls/cthulhu/monster_molotov.cpp @@ -0,0 +1,527 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== molotov.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" +#include "effects.h" +#include "triggers.h" + +#include "monster_molotov.h" + +//===================molotov + + +LINK_ENTITY_TO_CLASS( molotov, CMonsterMolotov ); + +//TYPEDESCRIPTION CMonsterMolotov::m_SaveData[] = +//{ + //DEFINE_FIELD( CMonsterMolotov, m_pFireSprite[0][0], FIELD_CLASSPTR ), + //DEFINE_FIELD( CMonsterMolotov, m_pFireSprite[1][0], FIELD_CLASSPTR ), + //DEFINE_FIELD( CMonsterMolotov, m_pFireSprite[2][0], FIELD_CLASSPTR ), + //DEFINE_FIELD( CMonsterMolotov, m_pFireSprite[0][1], FIELD_CLASSPTR ), + //DEFINE_FIELD( CMonsterMolotov, m_pFireSprite[1][1], FIELD_CLASSPTR ), + //DEFINE_FIELD( CMonsterMolotov, m_pFireSprite[2][1], FIELD_CLASSPTR ), + //DEFINE_FIELD( CMonsterMolotov, m_pFireSprite[0][2], FIELD_CLASSPTR ), + //DEFINE_FIELD( CMonsterMolotov, m_pFireSprite[1][2], FIELD_CLASSPTR ), + //DEFINE_FIELD( CMonsterMolotov, m_pFireSprite[2][2], FIELD_CLASSPTR ), + //DEFINE_ARRAY( CMonsterMolotov, m_pFireSprite, FIELD_CLASSPTR, 9 ), + //DEFINE_ARRAY( CMonsterMolotov, m_pFireSprite[1], FIELD_CLASSPTR, 3 ), + //DEFINE_ARRAY( CMonsterMolotov, m_pFireSprite[2], FIELD_CLASSPTR, 3 ), + //DEFINE_FIELD( CMonsterMolotov, m_pFireHurt, FIELD_CLASSPTR ), + //DEFINE_FIELD( CMonsterMolotov, m_pBurningArea, FIELD_CLASSPTR ), +//}; +//IMPLEMENT_SAVERESTORE( CMonsterMolotov, CBaseMonster ); +int CMonsterMolotov::Save( CSave &save ) +{ + // get rid of the stuff that doesn't get saved... + int spr; + for (int x = -1; x <= 1; x++) + { + for (int y = -1; y <= 1; y++) + { + spr = 3*(x+1)+(y+1); + UTIL_Remove( m_pFireSprite[spr] ); + m_pFireSprite[spr] = NULL; + } + } + UTIL_Remove( m_pFireHurt ); + m_pFireHurt = NULL; + + // now save as normal + int status = CBaseMonster::Save( save ); + return status; +} + +int CMonsterMolotov::Restore( CRestore &restore ) +{ + int status = CBaseMonster::Restore ( restore ); + return status; +} + +void CMonsterMolotov::Precache( void ) +{ + PRECACHE_MODEL( "models/w_molotov.mdl" ); +} + +// +// Molotov Explode +// +void CMonsterMolotov::Explode( Vector vecSrc, Vector vecAim ) +{ + TraceResult tr; + UTIL_TraceLine ( pev->origin, pev->origin + Vector ( 0, 0, -32 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + +// UNDONE: temporary scorching for PreAlpha - find a less sleazy permenant solution. +void CMonsterMolotov::Explode( TraceResult *pTrace, int bitsDamageType ) +{ + float flRndSound;// sound randomizer + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + pev->takedamage = DAMAGE_NO; + + // Pull out of the wall a bit + if ( pTrace->flFraction != 1.0 ) + { + pev->origin = pTrace->vecEndPos + (pTrace->vecPlaneNormal * max(1,(pev->dmg - 24)) * 0.6); + } + + DROP_TO_FLOOR ( ENT(pev) ); + + int iContents = UTIL_PointContents ( pev->origin ); + + if (iContents == CONTENTS_WATER) + { + UTIL_Remove( this ); + return; + } + + //MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + // WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + // WRITE_COORD( pev->origin.x ); // Send to PAS because of the sound + // WRITE_COORD( pev->origin.y ); + // WRITE_COORD( pev->origin.z ); + // WRITE_SHORT( g_sModelIndexFireball ); + // WRITE_BYTE( (pev->dmg - 50) * .30 ); // scale * 10 + // WRITE_BYTE( 15 ); // framerate + // WRITE_BYTE( TE_EXPLFLAG_NOSOUND ); + //MESSAGE_END(); + + /* + // create a fire sprite + float dx,dy,dz, SCALE; + Vector burn; + burn = pev->origin; + int x, y, spr; + for (x = -1; x <= 1; x++) + { + for (y = -1; y <= 1; y++) + { + spr = 3*(x+1)+(y+1); + m_pFireSprite[spr] = CSprite::SpriteCreate("sprites/fire.spr",burn,TRUE); + SCALE = 1.0; + if (x == 0 && y == 0) SCALE = 2.0; + m_pFireSprite[spr]->pev->scale = SCALE; + dx = burn.x + x*m_pFireSprite[spr]->pev->size.x; + dy = burn.y + y*m_pFireSprite[spr]->pev->size.y; + dz = burn.z + (0.8 * m_pFireSprite[spr]->pev->size.z * SCALE / 2); + m_pFireSprite[spr]->pev->origin = Vector(dx,dy,dz); + m_pFireSprite[spr]->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + //m_pFireSprite[spr]->pev->spawnflags |= SF_SPRITE_TEMPORARY; + m_pFireSprite[spr]->pev->framerate = 10.0; + m_pFireSprite[spr]->TurnOn(); + } + } + + // create a trigger_hurt entity + m_pFireHurt = (CTriggerHurt*) CTriggerHurt::Create("trigger_hurt",burn,g_vecZero,edict()); + //m_pFireHurt = (CTriggerHurt*) CTriggerHurt::Create("trigger_hurt",burn,pev->angles,edict()); + Vector v_size = m_pFireSprite[4]->pev->size * 4.0; + v_size.z = m_pFireSprite[4]->pev->size.z; + m_pFireHurt->pev->size = v_size; + m_pFireHurt->pev->absmin = burn - Vector(v_size.x/2,v_size.y/2,0); + m_pFireHurt->pev->absmax = burn + Vector(v_size.x/2,v_size.y/2,v_size.z); + m_pFireHurt->pev->dmg = pev->dmg; + FBitSet(m_pFireHurt->pev->spawnflags,SF_TRIGGER_ALLOWMONSTERS); + m_pFireHurt->m_bitsDamageInflict |= DMG_BURN; + */ + MakeEffects(); + + // create a burning clip area + // THIS DOES NOT WORK DYNAMICALLY, SINCE THE NODE GRAPH ALREADY EXISTS... + + /* + m_pBurningArea = (CFuncBurningClip*)CFuncBurningClip::Create("func_burning_clip",burn,pev->angles,edict()); + m_pBurningArea->pev->size = v_size; + m_pBurningArea->pev->absmin = burn - Vector(v_size.x/2,v_size.y/2,0); + m_pBurningArea->pev->absmax = burn + Vector(v_size.x/2,v_size.y/2,v_size.z); + */ + // the hurt doesn't seem to work with snakes very well. I don't know why... + + float duration = RANDOM_LONG(90,150); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, SMALL_EXPLOSION_VOLUME, 3.0 ); + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, 128, duration ); + entvars_t *pevOwner; + if ( pev->owner ) + pevOwner = VARS( pev->owner ); + else + pevOwner = NULL; + + pev->owner = NULL; // can't traceline attack owner if this is set + + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH2 ); + } + + flRndSound = RANDOM_FLOAT( 0 , 1 ); + + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris1.wav", 0.55, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris2.wav", 0.55, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris3.wav", 0.55, ATTN_NORM); break; + } + + pev->effects |= EF_NODRAW; + + SetThink( Burning ); + pev->velocity = g_vecZero; + pev->dmgtime = gpGlobals->time + duration; + SetNextThink( 0.5 ); +} + +void CMonsterMolotov::ExplodeTouch( CBaseEntity *pOther ) +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + pev->enemy = pOther->edict(); + + vecSpot = pev->origin - pev->velocity.Normalize() * 32; + UTIL_TraceLine( vecSpot, vecSpot + pev->velocity.Normalize() * 64, ignore_monsters, ENT(pev), &tr ); + + Explode( &tr, DMG_BLAST ); +} + +void CMonsterMolotov::MakeEffects( void ) +{ + // create a fire sprite + float dx,dy,dz, SCALE; + Vector burn; + burn = pev->origin; + int x, y, spr; + if (!m_pFireSprite[4]) + { + for (x = -1; x <= 1; x++) + { + for (y = -1; y <= 1; y++) + { + spr = 3*(x+1)+(y+1); + m_pFireSprite[spr] = CSprite::SpriteCreate("sprites/fire.spr",burn,TRUE); + SCALE = 1.0; + if (x == 0 && y == 0) SCALE = 2.0; + m_pFireSprite[spr]->pev->scale = SCALE; + dx = burn.x + x*m_pFireSprite[spr]->pev->size.x; + dy = burn.y + y*m_pFireSprite[spr]->pev->size.y; + dz = burn.z + (0.8 * m_pFireSprite[spr]->pev->size.z * SCALE / 2); + m_pFireSprite[spr]->pev->origin = Vector(dx,dy,dz); + m_pFireSprite[spr]->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + //m_pFireSprite[spr]->pev->spawnflags |= SF_SPRITE_TEMPORARY; + m_pFireSprite[spr]->pev->framerate = 10.0; + m_pFireSprite[spr]->TurnOn(); + } + } + } + + // if we have restored, then the trigger hurt area is wrong... + if (!m_pFireHurt) + { + m_pFireHurt = (CTriggerHurt*) CTriggerHurt::Create("trigger_hurt",burn,g_vecZero,edict()); + Vector v_size = m_pFireSprite[4]->pev->size * 4.0; + v_size.z = m_pFireSprite[4]->pev->size.z; + m_pFireHurt->pev->size = v_size; + m_pFireHurt->pev->absmin = burn - Vector(v_size.x/2,v_size.y/2,0); + m_pFireHurt->pev->absmax = burn + Vector(v_size.x/2,v_size.y/2,v_size.z); + m_pFireHurt->pev->dmg = pev->dmg; + FBitSet(m_pFireHurt->pev->spawnflags,SF_TRIGGER_ALLOWMONSTERS); + m_pFireHurt->m_bitsDamageInflict |= DMG_BURN; + + // we also won't have the sound... + EMIT_SOUND( ENT(pev), CHAN_BODY, "ambience/burning1.wav", 0.5, ATTN_NORM ); + } +} + +void CMonsterMolotov::Burning( void ) +{ + MakeEffects(); + + // have we burnt out... + if (gpGlobals->time >= pev->dmgtime) + { + BurnOut(); + return; + } + else + { + //BOOL bRepel = FALSE; + // for each monster inside the list + int iMonsters = UTIL_MonstersInSphere(pEntInSphere, MAX_MONSTER, pev->origin, 128); + for (int i = 0; i < iMonsters; i++) + { + if (!(pEntInSphere[i]->pev->flags & FL_MONSTER)) continue; + if (pEntInSphere[i]->pev->flags & FL_IMMUNE_LAVA) continue; + if (!(pEntInSphere[i]->IsAlive())) continue; + + // this causes it to listen and evaluate... + //MonsterThink(); + ((CBaseMonster*)pEntInSphere[i])->Listen(); + if (((CBaseMonster*)pEntInSphere[i])->m_iAudibleList != SOUNDLIST_EMPTY) + { + ((CBaseMonster*)pEntInSphere[i])->ChangeSchedule( GetScheduleOfType( SCHED_TAKE_COVER_FROM_BEST_SOUND ) ); + } + + + //bRepel |= Repel((CBaseMonster*)pEntInSphere[i]); + } + + //if (bRepel) + //{ + SetNextThink( 0.5 ); + //} + //else + //{ + // SetNextThink( 0.5 ); + //} + } +} + +BOOL CMonsterMolotov::Repel( CBaseMonster* pEnt ) +{ + entvars_t* pevEnt = pEnt->pev; + + if ( !FBitSet ( pevEnt->flags , FL_MONSTER ) ) + {// touched by a non-monster. + return FALSE; + } + if ( FBitSet ( pevEnt->flags , FL_IMMUNE_LAVA ) ) + {// immune to fire. + return FALSE; + } + if ( !pEnt->IsAlive() ) + {// don't repel dead things. + return FALSE; + } + + pevEnt->origin.z += 1; + + if ( FBitSet ( pevEnt->flags, FL_ONGROUND ) ) + {// clear the onground so physics don't bitch + pevEnt->flags &= ~FL_ONGROUND; + } + + // calculate the opposite direction, and push in that direction + Vector vecDirToEnemy = ( pevEnt->origin - pev->origin ); + float distance = vecDirToEnemy.Length2D(); + vecDirToEnemy.z = 0; + vecDirToEnemy = vecDirToEnemy.Normalize(); + + // This is a crap way of doing it, but it is the only way that sort of works for now... + float speed; + if (distance < 32) speed = 412; + else if (distance < 64) speed = 312; + else if (distance < 92) speed = 256; + else if (distance < 128) speed = 214; + else if (distance < 192) speed = 172; + else if (distance < 256) speed = 92; + else speed = 64; + + pevEnt->velocity = vecDirToEnemy * speed; + pevEnt->velocity.z += 16; + + return TRUE; +} + +void CMonsterMolotov::BurnOut( void ) +{ + int spr; + STOP_SOUND( ENT(pev), CHAN_BODY, "ambience/burning1.wav" ); + for (int x = -1; x <= 1; x++) + { + for (int y = -1; y <= 1; y++) + { + spr = 3*(x+1)+(y+1); + UTIL_Remove( m_pFireSprite[spr] ); + m_pFireSprite[spr] = NULL; + } + } + UTIL_Remove( m_pFireHurt ); + m_pFireHurt = NULL; + UTIL_Remove( this ); +} + +void CMonsterMolotov::Killed( entvars_t *pevAttacker, int iGib ) +{ + Detonate( ); +} + + +// Timed molotov, this think is called when time runs out. +void CMonsterMolotov::DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + SetThink( Detonate ); + SetNextThink(0); +} + +void CMonsterMolotov::PreDetonate( void ) +{ + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, 400, 0.3 ); + + SetThink( Detonate ); + SetNextThink( 1 ); +} + + +void CMonsterMolotov::Detonate( void ) +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 ); + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -40 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + +void CMonsterMolotov :: TumbleThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if (pev->dmgtime - 1 < gpGlobals->time) + { + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * (pev->dmgtime - gpGlobals->time), 400, 0.1 ); + } + + if (pev->dmgtime <= gpGlobals->time) + { + SetThink( Detonate ); + } + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + pev->framerate = 0.2; + } +} + + +void CMonsterMolotov:: Spawn( void ) +{ + Precache( ); + + pev->movetype = MOVETYPE_BOUNCE; + pev->classname = MAKE_STRING( "molotov" ); + + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_molotov.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + int spr; + for (int x = -1; x <= 1; x++) + { + for (int y = -1; y <= 1; y++) + { + spr = 3*(x+1)+(y+1); + m_pFireSprite[spr] = NULL; + } + } + m_pFireHurt = NULL; +} + +void CMonsterMolotov::DangerSoundThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * 0.5, pev->velocity.Length( ), 0.2 ); + SetNextThink( 0.2 ); + + if (pev->waterlevel != 0) + { + pev->velocity = pev->velocity * 0.5; + } +} + +CMonsterMolotov * CMonsterMolotov::ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CMonsterMolotov *pMolotov = GetClassPtr( (CMonsterMolotov *)NULL ); + pMolotov->Spawn(); + // molotovs arc lower + pMolotov->pev->gravity = 0.5;// lower gravity since molotov is aerodynamic and engine doesn't know it. + UTIL_SetOrigin( pMolotov, vecStart ); + pMolotov->pev->velocity = vecVelocity; + pMolotov->pev->angles = UTIL_VecToAngles (pMolotov->pev->velocity); + pMolotov->pev->owner = ENT(pevOwner); + + // make monsters afaid of it while in the air + pMolotov->SetThink( DangerSoundThink ); + pMolotov->SetNextThink(0); + + // Tumble in air + pMolotov->pev->avelocity.x = RANDOM_FLOAT ( -100, -500 ); + + // Explode on contact + pMolotov->SetTouch( ExplodeTouch ); + + pMolotov->pev->dmg = gSkillData.plrDmgMolotov; + + pMolotov->pev->spawnflags |= 1; + + return pMolotov; +} + + +//======================end molotov + diff --git a/dlls/cthulhu/monster_molotov.h b/dlls/cthulhu/monster_molotov.h new file mode 100755 index 00000000..b68f3d63 --- /dev/null +++ b/dlls/cthulhu/monster_molotov.h @@ -0,0 +1,49 @@ + +#ifndef MONSTER_MOLOTOV_H +#define MONSTER_MOLOTOV_H + +#include "effects.h" +#include "triggers.h" +#include "bmodels.h" + +const int MAX_MONSTER = 1000; + +// Molotov burning area +class CMonsterMolotov : public CBaseMonster +{ +public: + void Spawn( void ); + + static CMonsterMolotov *ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + + void Explode( Vector vecSrc, Vector vecAim ); + void Explode( TraceResult *pTrace, int bitsDamageType ); + void MakeEffects( void ); + void EXPORT BurnOut( void ); + void EXPORT Burning( void ); + + void EXPORT ExplodeTouch( CBaseEntity *pOther ); + void EXPORT DangerSoundThink( void ); + void EXPORT PreDetonate( void ); + void EXPORT Detonate( void ); + void EXPORT DetonateUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TumbleThink( void ); + + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void Killed( entvars_t *pevAttacker, int iGib ); + void Precache( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + //static TYPEDESCRIPTION m_SaveData[]; + + CSprite* m_pFireSprite[9]; + CTriggerHurt* m_pFireHurt; + //CFuncBurningClip* m_pBurningArea; + BOOL Repel ( CBaseMonster* pEnt ); + +protected: + CBaseEntity* pEntInSphere[MAX_MONSTER]; +}; + +#endif diff --git a/dlls/cthulhu/monsterstate.cpp b/dlls/cthulhu/monsterstate.cpp new file mode 100755 index 00000000..702f38eb --- /dev/null +++ b/dlls/cthulhu/monsterstate.cpp @@ -0,0 +1,235 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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) ) + if ( ( m_MonsterState == MONSTERSTATE_IDLE || m_MonsterState == MONSTERSTATE_ALERT ) && RANDOM_LONG(0,99) <= 2 && !(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/cthulhu/orderly.cpp b/dlls/cthulhu/orderly.cpp new file mode 100755 index 00000000..f034505d --- /dev/null +++ b/dlls/cthulhu/orderly.cpp @@ -0,0 +1,1462 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Orderly +//========================================================= + +//========================================================= +// Hit groups! +//========================================================= +/* + + 1 - Head + 2 - Stomach + 3 - Gun + +*/ + + +#include "extdll.h" +#include "util.h" +#include "plane.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_fOrderlyQuestion; // true if an idle orderly asked a question. Cleared when someone answers. + +extern DLL_GLOBAL int g_iSkillLevel; + +//========================================================= +// monster-specific DEFINE's +//========================================================= +#define ORDERLY_VOL 0.70 // volume of orderly sounds +#define ORDERLY_ATTN ATTN_NORM // attenutation of orderlyist sentences +#define ORDERLY_LIMP_HEALTH 20 +#define ORDERLY_DMG_HEADSHOT ( DMG_BULLET | DMG_CLUB ) // damage types that can kill a orderly with a single headshot. +#define ORDERLY_MINIMUM_HEADSHOT_DAMAGE 15 // must do at least this much damage in one shot to head to score a headshot kill +#define ORDERLY_SENTENCE_VOLUME (float)0.70 // volume of orderly sentences + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define ORDERLY_AE_PUNCH ( 1 ) +#define ORDERLY_AE_CAUGHT_ENEMY ( 2 ) // orderly established sight with an enemy (player only) that had previously eluded the squad. + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_ORDERLY_SWEEP = LAST_COMMON_SCHEDULE + 1, + SCHED_ORDERLY_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_ORDERLY_FOUND_ENEMY, + SCHED_ORDERLY_WAIT_FACE_ENEMY, + SCHED_ORDERLY_TAKECOVER_FAILED,// special schedule type that forces analysis of conditions and picks the best possible schedule to recover from this type of failure. + SCHED_ORDERLY_ELOF_FAIL, +}; + +//========================================================= +// monster-specific tasks +//========================================================= +enum +{ + TASK_ORDERLY_FACE_TOSS_DIR = LAST_COMMON_TASK + 1, + TASK_ORDERLY_SPEAK_SENTENCE, + TASK_ORDERLY_CHECK_FIRE, +}; + +//========================================================= +// monster-specific conditions +//========================================================= +#define bits_COND_ORDERLY_NOFIRE ( bits_COND_SPECIAL1 ) + + +#include "orderly.h" + + +LINK_ENTITY_TO_CLASS( monster_orderly, COrderly ); + +TYPEDESCRIPTION COrderly::m_SaveData[] = +{ + DEFINE_FIELD( COrderly, m_flNextPainTime, FIELD_TIME ), + DEFINE_FIELD( COrderly, m_fStanding, FIELD_BOOLEAN ), + DEFINE_FIELD( COrderly, m_fFirstEncounter, FIELD_BOOLEAN ), + DEFINE_FIELD( COrderly, m_voicePitch, FIELD_INTEGER ), + DEFINE_FIELD( COrderly, m_iSentence, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( COrderly, CSquadMonster ); + +const char *COrderly::pOrderlySentences[] = +{ + "ORDERLY_GREN", // grenade scared orderly + "ORDERLY_ALERT", // sees player + "ORDERLY_COVER", // running to cover + "ORDERLY_CHARGE", // running out to get the enemy + "ORDERLY_TAUNT", // say rude things +}; + +enum +{ + ORDERLY_SENT_NONE = -1, + ORDERLY_SENT_GREN = 0, + ORDERLY_SENT_ALERT, + ORDERLY_SENT_COVER, + ORDERLY_SENT_CHARGE, + ORDERLY_SENT_TAUNT, +} ORDERLY_SENTENCE_TYPES; + +//========================================================= +// Speak Sentence - say your cued up sentence. +// +// Some orderly sentences (take cover and charge) rely on actually +// being able to execute the intended action. It's really lame +// when a orderly 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 orderly has +// started moving. +//========================================================= +void COrderly :: SpeakSentence( void ) +{ + if ( m_iSentence == ORDERLY_SENT_NONE ) + { + // no sentence cued up. + return; + } + + if (FOkToSpeak()) + { + SENTENCEG_PlayRndSz( ENT(pev), pOrderlySentences[ m_iSentence ], ORDERLY_SENTENCE_VOLUME, ORDERLY_ATTN, 0, m_voicePitch); + JustSpoke(); + } +} + +//========================================================= +// GibMonster - make gun fly through the air. +//========================================================= +void COrderly :: GibMonster ( void ) +{ + CBaseMonster :: GibMonster(); +} + +//========================================================= +// ISoundMask - Overidden for human orderlies because they +// hear the DANGER sound that is made by hand grenades and +// other dangerous items. +//========================================================= +int COrderly :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_PLAYER | + bits_SOUND_DANGER; +} + +//========================================================= +// someone else is talking - don't speak +//========================================================= +BOOL COrderly :: 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 COrderly :: JustSpoke( void ) +{ + CTalkMonster::g_talkWaitTime = gpGlobals->time + RANDOM_FLOAT(1.5, 2.0); + m_iSentence = ORDERLY_SENT_NONE; +} + +//========================================================= +// PrescheduleThink - this function runs after conditions +// are collected and before scheduling code is run. +//========================================================= +void COrderly :: 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. +//========================================================= +BOOL COrderly :: FCanCheckAttacks ( void ) +{ + if ( !HasConditions( bits_COND_ENEMY_TOOFAR ) ) + { + return TRUE; + } + else + { + return FALSE; + } +} + + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL COrderly :: 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 orderlies. +//========================================================= +BOOL COrderly :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + return FALSE; +} + +//========================================================= +// CheckRangeAttack2 - this checks the orderlies (non-existant) second range +// attack. +//========================================================= +BOOL COrderly :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + return FALSE; +} + + +//========================================================= +// TraceAttack - make sure we're not taking it in the helmet +//========================================================= +void COrderly :: TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // check for helmet shot + if (ptr->iHitgroup == 11) + { + // it's head shot anyways + ptr->iHitgroup = HITGROUP_HEAD; + } + CSquadMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + + +//========================================================= +// TakeDamage - overridden for the orderly because the orderly +// needs to forget that he is in cover if he's hurt. (Obviously +// not in a safe place anymore). +//========================================================= +int COrderly :: 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 COrderly :: 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 COrderly :: IdleSound( void ) +{ + if (FOkToSpeak() && (g_fOrderlyQuestion || RANDOM_LONG(0,1))) + { + if (!g_fOrderlyQuestion) + { + // ask question or make statement + switch (RANDOM_LONG(0,2)) + { + case 0: // check in + SENTENCEG_PlayRndSz(ENT(pev), "ORDERLY_CHECK", ORDERLY_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fOrderlyQuestion = 1; + break; + case 1: // question + SENTENCEG_PlayRndSz(ENT(pev), "ORDERLY_QUEST", ORDERLY_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + g_fOrderlyQuestion = 2; + break; + case 2: // statement + SENTENCEG_PlayRndSz(ENT(pev), "ORDERLY_IDLE", ORDERLY_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + } + else + { + switch (g_fOrderlyQuestion) + { + case 1: // check in + SENTENCEG_PlayRndSz(ENT(pev), "ORDERLY_CLEAR", ORDERLY_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + case 2: // question + SENTENCEG_PlayRndSz(ENT(pev), "ORDERLY_ANSWER", ORDERLY_SENTENCE_VOLUME, ATTN_NORM, 0, m_voicePitch); + break; + } + g_fOrderlyQuestion = 0; + } + JustSpoke(); + } +} + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int COrderly :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_HUMAN_MILITARY; +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void COrderly :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + Vector vecShootDir; + Vector vecShootOrigin; + + switch( pEvent->event ) + { + case ORDERLY_AE_PUNCH: + { + Vector oldorig = pev->origin; + CBaseEntity *pHurt = NULL; + // check down below in stages for snakes... + for (int dz = 0; dz >=-3; dz--) + { + pev->origin = oldorig; + pev->origin.z += dz * 12; + pHurt = CheckTraceHullAttack( 70, gSkillData.priestDmgKnife, DMG_SLASH ); + if (pHurt) + { + break; + } + } + pev->origin = oldorig; + if ( pHurt ) + { + // Play a random attack hit sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, "zombie/claw_strike1.wav", 1.0, ATTN_NORM, 0, m_voicePitch ); + } + else + { + // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, "zombie/claw_miss1.wav", 1.0, ATTN_NORM, 0, m_voicePitch ); + } + } + break; + + case ORDERLY_AE_CAUGHT_ENEMY: + { + if ( FOkToSpeak() ) + { + SENTENCEG_PlayRndSz(ENT(pev), "ORDERLY_ALERT", ORDERLY_SENTENCE_VOLUME, ORDERLY_ATTN, 0, m_voicePitch); + JustSpoke(); + } + + } + + default: + CSquadMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void COrderly :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/monsters/orderly.mdl"); + //UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + UTIL_SetSize(pev, Vector(-16,-16,-36), Vector(16,16,36)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + pev->effects = 0; + if (pev->health == 0) + pev->health = gSkillData.cultistHealth; + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_flNextPainTime = gpGlobals->time; + m_iSentence = ORDERLY_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 orderly spawns, because he hasn't encountered an enemy yet. + + m_HackedGunPos = Vector ( 0, 0, 19 ); + + CTalkMonster::g_talkWaitTime = 0; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void COrderly :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/monsters/orderly.mdl"); + + PRECACHE_SOUND( "hgrunt/gr_die1.wav" ); + PRECACHE_SOUND( "hgrunt/gr_die2.wav" ); + PRECACHE_SOUND( "hgrunt/gr_die3.wav" ); + + PRECACHE_SOUND( "hgrunt/gr_pain3.wav" ); + PRECACHE_SOUND( "hgrunt/gr_pain4.wav" ); + + PRECACHE_SOUND("zombie/claw_strike1.wav");// because we use the basemonster SWIPE animation event + PRECACHE_SOUND("zombie/claw_miss1.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; +} + +//========================================================= +// start task +//========================================================= +void COrderly :: StartTask ( Task_t *pTask ) +{ + m_iTaskStatus = TASKSTATUS_RUNNING; + + switch ( pTask->iTask ) + { + case TASK_ORDERLY_CHECK_FIRE: + if ( !NoFriendlyFire() ) + { + SetConditions( bits_COND_ORDERLY_NOFIRE ); + } + TaskComplete(); + break; + + case TASK_ORDERLY_SPEAK_SENTENCE: + SpeakSentence(); + TaskComplete(); + break; + + case TASK_WALK_PATH: + case TASK_RUN_PATH: + // orderly no longer assumes he is covered if he moves + Forget( bits_MEMORY_INCOVER ); + CSquadMonster ::StartTask( pTask ); + break; + + case TASK_ORDERLY_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 COrderly :: RunTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_ORDERLY_FACE_TOSS_DIR: + { + // project a point along the toss vector and turn to face that point. + MakeIdealYaw( pev->origin ); + ChangeYaw( pev->yaw_speed ); + + if ( FacingIdeal() ) + { + m_iTaskStatus = TASKSTATUS_COMPLETE; + } + break; + } + default: + { + CSquadMonster :: RunTask( pTask ); + break; + } + } +} + +//========================================================= +// PainSound +//========================================================= +void COrderly :: PainSound ( void ) +{ + if ( gpGlobals->time > m_flNextPainTime ) + { + switch ( RANDOM_LONG(0,1) ) + { + 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; + } + + m_flNextPainTime = gpGlobals->time + 1; + } +} + +//========================================================= +// DeathSound +//========================================================= +void COrderly :: 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 +//========================================================= + +//========================================================= +// OrderlyFail +//========================================================= +Task_t tlOrderlyFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slOrderlyFail[] = +{ + { + tlOrderlyFail, + ARRAYSIZE ( tlOrderlyFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2 | + bits_COND_CAN_MELEE_ATTACK1 | + bits_COND_CAN_MELEE_ATTACK2, + 0, + "Orderly Fail" + }, +}; + +//========================================================= +// Orderly Combat Fail +//========================================================= +Task_t tlOrderlyCombatFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slOrderlyCombatFail[] = +{ + { + tlOrderlyCombatFail, + ARRAYSIZE ( tlOrderlyCombatFail ), + bits_COND_CAN_RANGE_ATTACK1 | + bits_COND_CAN_RANGE_ATTACK2, + 0, + "Orderly Combat Fail" + }, +}; + +//========================================================= +// Victory dance! +//========================================================= +Task_t tlOrderlyVictoryDance[] = +{ + { 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 slOrderlyVictoryDance[] = +{ + { + tlOrderlyVictoryDance, + ARRAYSIZE ( tlOrderlyVictoryDance ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE, + 0, + "OrderlyVictoryDance" + }, +}; + +//========================================================= +// Establish line of fire - move to a position that allows +// the orderly to attack. +//========================================================= +Task_t tlOrderlyEstablishLineOfFire[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ORDERLY_ELOF_FAIL }, + { TASK_GET_PATH_TO_ENEMY, (float)0 }, + { TASK_ORDERLY_SPEAK_SENTENCE,(float)0 }, + { TASK_RUN_PATH, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, +}; + +Schedule_t slOrderlyEstablishLineOfFire[] = +{ + { + tlOrderlyEstablishLineOfFire, + ARRAYSIZE ( tlOrderlyEstablishLineOfFire ), + 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, + "OrderlyEstablishLineOfFire" + }, +}; + +//========================================================= +// OrderlyFoundEnemy - orderly established sight with an enemy +// that was hiding from the squad. +//========================================================= +Task_t tlOrderlyFoundEnemy[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY,(float)ACT_SIGNAL1 }, +}; + +Schedule_t slOrderlyFoundEnemy[] = +{ + { + tlOrderlyFoundEnemy, + ARRAYSIZE ( tlOrderlyFoundEnemy ), + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "OrderlyFoundEnemy" + }, +}; + +//========================================================= +// OrderlyCombatFace Schedule +//========================================================= +Task_t tlOrderlyCombatFace1[] = +{ + { 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_ORDERLY_SWEEP }, +}; + +Schedule_t slOrderlyCombatFace[] = +{ + { + tlOrderlyCombatFace1, + ARRAYSIZE ( tlOrderlyCombatFace1 ), + 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 Orderly gets hurt. +//========================================================= +Task_t tlOrderlySignalSuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float)ACT_SIGNAL2 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_ORDERLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_ORDERLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_ORDERLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_ORDERLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_ORDERLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slOrderlySignalSuppress[] = +{ + { + tlOrderlySignalSuppress, + ARRAYSIZE ( tlOrderlySignalSuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_ORDERLY_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "SignalSuppress" + }, +}; + +Task_t tlOrderlySuppress[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_ORDERLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_ORDERLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_ORDERLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_ORDERLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, + { TASK_FACE_ENEMY, (float)0 }, + { TASK_ORDERLY_CHECK_FIRE, (float)0 }, + { TASK_RANGE_ATTACK1, (float)0 }, +}; + +Schedule_t slOrderlySuppress[] = +{ + { + tlOrderlySuppress, + ARRAYSIZE ( tlOrderlySuppress ), + bits_COND_ENEMY_DEAD | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_ORDERLY_NOFIRE | + bits_COND_NO_AMMO_LOADED, + + bits_SOUND_DANGER, + "Suppress" + }, +}; + + +//========================================================= +// Orderly wait in cover - we don't allow danger or the ability +// to attack to break a Orderly's run to cover schedule, but +// when a Orderly is in cover, we do want them to attack if they can. +//========================================================= +Task_t tlOrderlyWaitInCover[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT_FACE_ENEMY, (float)1 }, +}; + +Schedule_t slOrderlyWaitInCover[] = +{ + { + tlOrderlyWaitInCover, + ARRAYSIZE ( tlOrderlyWaitInCover ), + 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, + "OrderlyWaitInCover" + }, +}; + +//========================================================= +// run to cover. +// !!!BUGBUG - set a decent fail schedule here. +//========================================================= +Task_t tlOrderlyTakeCover1[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_ORDERLY_TAKECOVER_FAILED }, + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_ORDERLY_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_ORDERLY_WAIT_FACE_ENEMY }, +}; + +Schedule_t slOrderlyTakeCover[] = +{ + { + tlOrderlyTakeCover1, + ARRAYSIZE ( tlOrderlyTakeCover1 ), + 0, + 0, + "TakeCover" + }, +}; + + +//========================================================= +// hide from the loudest sound source (to run from grenade) +//========================================================= +Task_t tlOrderlyTakeCoverFromBestSound[] = +{ + { 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 slOrderlyTakeCoverFromBestSound[] = +{ + { + tlOrderlyTakeCoverFromBestSound, + ARRAYSIZE ( tlOrderlyTakeCoverFromBestSound ), + 0, + 0, + "OrderlyTakeCoverFromBestSound" + }, +}; + +//========================================================= +// Orderly reload schedule +//========================================================= +Task_t tlOrderlyHideReload[] = +{ + { 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 slOrderlyHideReload[] = +{ + { + tlOrderlyHideReload, + ARRAYSIZE ( tlOrderlyHideReload ), + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND, + + bits_SOUND_DANGER, + "OrderlyHideReload" + } +}; + +//========================================================= +// Do a turning sweep of the area +//========================================================= +Task_t tlOrderlySweep[] = +{ + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, + { TASK_TURN_LEFT, (float)179 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slOrderlySweep[] = +{ + { + tlOrderlySweep, + ARRAYSIZE ( tlOrderlySweep ), + + 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, + + "Orderly Sweep" + }, +}; + + +DEFINE_CUSTOM_SCHEDULES( COrderly ) +{ + slOrderlyFail, + slOrderlyCombatFail, + slOrderlyVictoryDance, + slOrderlyEstablishLineOfFire, + slOrderlyFoundEnemy, + slOrderlyCombatFace, + slOrderlySignalSuppress, + slOrderlySuppress, + slOrderlyWaitInCover, + slOrderlyTakeCover, + slOrderlyTakeCoverFromBestSound, + slOrderlyHideReload, + slOrderlySweep, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( COrderly, CSquadMonster ); + +//========================================================= +// SetActivity +//========================================================= +void COrderly :: SetActivity ( Activity NewActivity ) +{ + int iSequence = ACTIVITY_NOT_AVAILABLE; + void *pmodel = GET_MODEL_PTR( ENT(pev) ); + + switch ( NewActivity) + { + case ACT_MELEE_ATTACK1: + + // randomly stand or crouch + if (RANDOM_LONG(0,99) == 0) + m_fStanding = 0; + else + m_fStanding = 1; + + // a short enemy...probably a snake... + if ((m_hEnemy != NULL) && (m_hEnemy->pev->maxs.z < 36)) + { + m_fStanding = 0; + } + + if ( m_fStanding ) + { + // get aimable sequence + iSequence = LookupSequence( "ref_shoot_kosh" ); + } + else + { + // get crouching shoot + iSequence = LookupSequence( "crouch_shoot_kosh" ); + } + break; + case ACT_RUN: + if ( pev->health <= ORDERLY_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_RUN_HURT ); + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + { + iSequence = LookupActivity ( ACT_RUN ); + } + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_WALK: + if ( pev->health <= ORDERLY_LIMP_HEALTH ) + { + // limp! + iSequence = LookupActivity ( ACT_WALK_HURT ); + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + { + iSequence = LookupActivity ( ACT_WALK ); + } + } + else + { + iSequence = LookupActivity ( NewActivity ); + } + break; + case ACT_IDLE: + if ( m_MonsterState == MONSTERSTATE_COMBAT ) + { +// NewActivity = ACT_IDLE_ANGRY; + NewActivity = ACT_IDLE; + } + iSequence = LookupActivity ( NewActivity ); + if ( iSequence == ACTIVITY_NOT_AVAILABLE ) + { + iSequence = LookupActivity ( ACT_IDLE ); + } + 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 *COrderly :: GetSchedule( void ) +{ + + // clear old sentence + m_iSentence = ORDERLY_SENT_NONE; + + // orderlies 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 orderly's signal that a grenade has landed nearby, + // and the orderly 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), "ORDERLY_GREN", ORDERLY_SENTENCE_VOLUME, ORDERLY_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 orderlies 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), "ORDERLY_ALERT", ORDERLY_SENTENCE_VOLUME, ORDERLY_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), "ORDERLY_MONST", ORDERLY_SENTENCE_VOLUME, ORDERLY_ATTN, 0, m_voicePitch); + */ + + JustSpoke(); + } + } + } + } + +// 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 orderly was hit and is going to run to cover. + if (FOkToSpeak()) // && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "ORDERLY_COVER", ORDERLY_SENTENCE_VOLUME, ORDERLY_ATTN, 0, m_voicePitch); + m_iSentence = ORDERLY_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't see enemy + else if ( HasConditions( bits_COND_ENEMY_OCCLUDED ) ) + { + if ( OccupySlot( bits_SLOTS_ORDERLY_ENGAGE ) ) + { + //!!!KELLY - orderly cannot see the enemy and has just decided to + // charge the enemy's position. + if (FOkToSpeak())// && RANDOM_LONG(0,1)) + { + //SENTENCEG_PlayRndSz( ENT(pev), "ORDERLY_CHARGE", ORDERLY_SENTENCE_VOLUME, ORDERLY_ATTN, 0, m_voicePitch); + m_iSentence = ORDERLY_SENT_CHARGE; + //JustSpoke(); + } + + return GetScheduleOfType( SCHED_ORDERLY_ESTABLISH_LINE_OF_FIRE ); + } + else + { + //!!!KELLY - orderly is going to stay put for a couple seconds to see if + // the enemy wanders back out into the open, or approaches the + // orderly's covered position. Good place for a taunt, I guess? + if (FOkToSpeak() && RANDOM_LONG(0,1)) + { + SENTENCEG_PlayRndSz( ENT(pev), "ORDERLY_TAUNT", ORDERLY_SENTENCE_VOLUME, ORDERLY_ATTN, 0, m_voicePitch); + JustSpoke(); + } + return GetScheduleOfType( SCHED_STANDOFF ); + } + } + + if ( HasConditions( bits_COND_SEE_ENEMY ) && !HasConditions ( bits_COND_CAN_RANGE_ATTACK1 ) ) + { + return GetScheduleOfType ( SCHED_ORDERLY_ESTABLISH_LINE_OF_FIRE ); + } + } + } + + // no special cases here, call the base class + return CSquadMonster :: GetSchedule(); +} + +//========================================================= +//========================================================= +Schedule_t* COrderly :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_TAKE_COVER_FROM_ENEMY: + { + if ( InSquad() ) + { + return &slOrderlyTakeCover[ 0 ]; + } + else + { + return &slOrderlyTakeCover[ 0 ]; + } + } + case SCHED_TAKE_COVER_FROM_BEST_SOUND: + { + return &slOrderlyTakeCoverFromBestSound[ 0 ]; + } + case SCHED_ORDERLY_TAKECOVER_FAILED: + { + if ( HasConditions( bits_COND_CAN_RANGE_ATTACK1 ) && OccupySlot( bits_SLOTS_ORDERLY_ENGAGE ) ) + { + return GetScheduleOfType( SCHED_RANGE_ATTACK1 ); + } + + return GetScheduleOfType ( SCHED_FAIL ); + } + break; + case SCHED_ORDERLY_ELOF_FAIL: + { + // human orderly is unable to move to a position that allows him to attack the enemy. + return GetScheduleOfType ( SCHED_TAKE_COVER_FROM_ENEMY ); + } + break; + case SCHED_ORDERLY_ESTABLISH_LINE_OF_FIRE: + { + return &slOrderlyEstablishLineOfFire[ 0 ]; + } + break; + case SCHED_COMBAT_FACE: + { + return &slOrderlyCombatFace[ 0 ]; + } + case SCHED_ORDERLY_WAIT_FACE_ENEMY: + { + return &slOrderlyWaitInCover[ 0 ]; + } + case SCHED_ORDERLY_SWEEP: + { + return &slOrderlySweep[ 0 ]; + } + case SCHED_ORDERLY_FOUND_ENEMY: + { + return &slOrderlyFoundEnemy[ 0 ]; + } + case SCHED_VICTORY_DANCE: + { + if ( InSquad() ) + { + if ( !IsLeader() ) + { + return &slOrderlyFail[ 0 ]; + } + } + + return &slOrderlyVictoryDance[ 0 ]; + } + case SCHED_FAIL: + { + if ( m_hEnemy != NULL ) + { + // orderly has an enemy, so pick a different default fail schedule most likely to help recover. + return &slOrderlyCombatFail[ 0 ]; + } + + return &slOrderlyFail[ 0 ]; + } + default: + { + return CSquadMonster :: GetScheduleOfType ( Type ); + } + } +} + + +//========================================================= +// DEAD ORDERLY PROP +//========================================================= + +char *CDeadOrderly::m_szPoses[] = { "deadstomach", "deadside", "deadsitting" }; + +void CDeadOrderly::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_orderly_dead, CDeadOrderly ); + +//========================================================= +// ********** DeadOrderly SPAWN ********** +//========================================================= +void CDeadOrderly :: Spawn( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/monsters/orderly.mdl"); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/monsters/orderly.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 orderly with bad pose\n" ); + } + + // Corpses have less health + pev->health = 8; + + MonsterInitDead(); +} diff --git a/dlls/cthulhu/orderly.h b/dlls/cthulhu/orderly.h new file mode 100755 index 00000000..5ea3d79f --- /dev/null +++ b/dlls/cthulhu/orderly.h @@ -0,0 +1,70 @@ + +#ifndef ORDERLY_H +#define ORDERLY_H + +class COrderly : 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 SetActivity ( Activity NewActivity ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + void DeathSound( void ); + void PainSound( void ); + void IdleSound ( void ); + void PrescheduleThink ( void ); + void GibMonster( void ); + void SpeakSentence( void ); + + int Save( CSave &save ); + int Restore( CRestore &restore ); + + 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 ); + + BOOL FOkToSpeak( void ); + void JustSpoke( void ); + + CUSTOM_SCHEDULES; + static TYPEDESCRIPTION m_SaveData[]; + + float m_flNextPainTime; + float m_flLastEnemySightTime; + + BOOL m_fStanding; + BOOL m_fFirstEncounter;// only put on the handsign show in the squad's first encounter. + + int m_voicePitch; + + int m_iSentence; + + static const char *pOrderlySentences[]; +}; + +//////////////////////////////////////////////////////////////////////////////// + +class CDeadOrderly : 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]; +}; + + +#endif diff --git a/dlls/cthulhu/policeman.cpp b/dlls/cthulhu/policeman.cpp new file mode 100755 index 00000000..a4994740 --- /dev/null +++ b/dlls/cthulhu/policeman.cpp @@ -0,0 +1,901 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 policeman dying for scripted sequences? +#define POLICEMAN_AE_DRAW ( 2 ) +#define POLICEMAN_AE_SHOOT ( 3 ) +#define POLICEMAN_AE_HOLSTER ( 4 ) + +#define POLICEMAN_BODY_GUNHOLSTERED 0 +#define POLICEMAN_BODY_GUNDRAWN 1 +#define POLICEMAN_BODY_GUNGONE 2 + +class CPoliceman : public CTalkMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int ISoundMask( void ); + void PolicemanFirePistol( 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_policeman, CPoliceman ); + +TYPEDESCRIPTION CPoliceman::m_SaveData[] = +{ + DEFINE_FIELD( CPoliceman, m_fGunDrawn, FIELD_BOOLEAN ), + DEFINE_FIELD( CPoliceman, m_painTime, FIELD_TIME ), + DEFINE_FIELD( CPoliceman, m_checkAttackTime, FIELD_TIME ), + DEFINE_FIELD( CPoliceman, m_lastAttackCheck, FIELD_BOOLEAN ), + DEFINE_FIELD( CPoliceman, m_flPlayerDamage, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CPoliceman, CTalkMonster ); + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlPolFollow[] = +{ + { TASK_MOVE_TO_TARGET_RANGE,(float)128 }, // Move within 128 of target ent (client) + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slPolFollow[] = +{ + { + tlPolFollow, + ARRAYSIZE ( tlPolFollow ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "Follow" + }, +}; + +//========================================================= +// PolicemanDraw- much better looking draw schedule for when +// policeman knows who he's gonna attack. +//========================================================= +Task_t tlPolicemanEnemyDraw[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_FACE_ENEMY, 0 }, + { TASK_PLAY_SEQUENCE_FACE_ENEMY, (float) ACT_ARM }, +}; + +Schedule_t slPolicemanEnemyDraw[] = +{ + { + tlPolicemanEnemyDraw, + ARRAYSIZE ( tlPolicemanEnemyDraw ), + 0, + 0, + "Policeman Enemy Draw" + } +}; + +Task_t tlPolFaceTarget[] = +{ + { 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 slPolFaceTarget[] = +{ + { + tlPolFaceTarget, + ARRAYSIZE ( tlPolFaceTarget ), + 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 tlIdlePolStand[] = +{ + { 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 slIdlePolStand[] = +{ + { + tlIdlePolStand, + ARRAYSIZE ( tlIdlePolStand ), + 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( CPoliceman ) +{ + slPolFollow, + slPolicemanEnemyDraw, + slPolFaceTarget, + slIdlePolStand, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CPoliceman, CTalkMonster ); + +void CPoliceman :: StartTask( Task_t *pTask ) +{ + CTalkMonster::StartTask( pTask ); +} + +void CPoliceman :: 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 CPoliceman :: 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 CPoliceman :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_PLAYER_ALLY; +} + +//========================================================= +// ALertSound - Policeman says "Freeze!" +//========================================================= +void CPoliceman :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + if ( FOkToSpeak() ) + { + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_ATTACK"); + PlaySentence( szBuf, RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + } + else + { + PlaySentence( "PM_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 CPoliceman :: 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 CPoliceman :: 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; +} + + +//========================================================= +// PolicemanFirePistol - shoots one round from the pistol at +// the enemy Policeman is facing. +//========================================================= +void CPoliceman :: PolicemanFirePistol ( 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 CPoliceman :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case POLICEMAN_AE_SHOOT: + PolicemanFirePistol(); + break; + + case POLICEMAN_AE_DRAW: + // Policeman's bodygroup switches here so he can pull gun from holster + pev->body = POLICEMAN_BODY_GUNDRAWN; + m_fGunDrawn = TRUE; + break; + + case POLICEMAN_AE_HOLSTER: + // change bodygroup to replace gun in holster + pev->body = POLICEMAN_BODY_GUNHOLSTERED; + m_fGunDrawn = FALSE; + break; + + default: + CTalkMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CPoliceman :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/policeman.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; + if (pev->health == 0) + 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 CPoliceman :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/policeman.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 policeman must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + CTalkMonster::Precache(); +} + +// Init talk data +void CPoliceman :: TalkInit() +{ + + CTalkMonster::TalkInit(); + + // policeman speech group names (group names are in sentences.txt) + + if (!m_iszSpeakAs) + { + m_szGrp[TLK_ANSWER] = "PM_ANSWER"; + m_szGrp[TLK_QUESTION] = "PM_QUESTION"; + m_szGrp[TLK_IDLE] = "PM_IDLE"; + m_szGrp[TLK_STARE] = "PM_STARE"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) //LRC + m_szGrp[TLK_USE] = "PM_PFOLLOW"; + else + m_szGrp[TLK_USE] = "PM_OK"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_UNUSE] = "PM_PWAIT"; + else + m_szGrp[TLK_UNUSE] = "PM_WAIT"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_DECLINE] = "PM_POK"; + else + m_szGrp[TLK_DECLINE] = "PM_NOTOK"; + m_szGrp[TLK_STOP] = "PM_STOP"; + + m_szGrp[TLK_NOSHOOT] = "PM_SCARED"; + m_szGrp[TLK_HELLO] = "PM_HELLO"; + + m_szGrp[TLK_PLHURT1] = "!PM_CUREA"; + m_szGrp[TLK_PLHURT2] = "!PM_CUREB"; + m_szGrp[TLK_PLHURT3] = "!PM_CUREC"; + + m_szGrp[TLK_PHELLO] = "PM_PHELLO"; + m_szGrp[TLK_PIDLE] = "PM_PIDLE"; + m_szGrp[TLK_PQUESTION] = "PM_PQUEST"; + + m_szGrp[TLK_SMELL] = "PM_SMELL"; + + m_szGrp[TLK_WOUND] = "PM_WOUND"; + m_szGrp[TLK_MORTAL] = "PM_MORTAL"; + } + + // get voice for head - just one policeman voice for now + m_voicePitch = 100; +} + +int CPoliceman :: 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) || UTIL_IsFacing( pevAttacker, pev->origin ) ) + { + // Alright, now I'm pissed! + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_MAD"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "PM_MAD", 4, VOL_NORM, ATTN_NORM ); + } + + Remember( bits_MEMORY_PROVOKED ); + StopFollowing( TRUE ); + } + else + { + // Hey, be careful with that + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_SHOT"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "PM_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + Remember( bits_MEMORY_SUSPICIOUS ); + } + } + else if ( !(m_hEnemy->IsPlayer()) && pev->deadflag == DEAD_NO ) + { + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_SHOT"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "PM_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + } + } + + return ret; +} + + +//========================================================= +// PainSound +//========================================================= +void CPoliceman :: 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 CPoliceman :: 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 CPoliceman::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 CPoliceman::Killed( entvars_t *pevAttacker, int iGib ) +{ + if ( pev->body < POLICEMAN_BODY_GUNGONE && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) + {// drop the gun! + Vector vecGunPos; + Vector vecGunAngles; + + pev->body = POLICEMAN_BODY_GUNGONE; + + GetAttachment( 0, vecGunPos, vecGunAngles ); + + CBaseEntity *pGun = DropItem( "weapon_revolver", vecGunPos, vecGunAngles ); + } + + SetUse( NULL ); + CTalkMonster::Killed( pevAttacker, iGib ); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + +Schedule_t* CPoliceman :: GetScheduleOfType ( int Type ) +{ + Schedule_t *psched; + + switch( Type ) + { + case SCHED_ARM_WEAPON: + if ( m_hEnemy != NULL ) + { + // face enemy, then draw. + return slPolicemanEnemyDraw; + } + break; + + // Hook these to make a looping schedule + case SCHED_TARGET_FACE: + // call base class default so that Policeman will talk + // when 'used' + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slPolFaceTarget; // override this for different target face behavior + else + return psched; + + case SCHED_TARGET_CHASE: + return slPolFollow; + + 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 slIdlePolStand; + } + 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 *CPoliceman :: 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() ) + { + // Hey, be careful with that + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_KILL"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "PM_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 CPoliceman :: GetIdealState ( void ) +{ + return CTalkMonster::GetIdealState(); +} + + + +void CPoliceman::DeclineFollowing( void ) +{ + PlaySentence( m_szGrp[TLK_DECLINE], 2, VOL_NORM, ATTN_NORM ); //LRC +} + + + + + +//========================================================= +// DEAD POLICEMAN 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 CDeadPoliceman : 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 *CDeadPoliceman::m_szPoses[] = { "lying_on_back", "lying_on_side", "lying_on_stomach" }; + +void CDeadPoliceman::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_policeman_dead, CDeadPoliceman ); + +//========================================================= +// ********** DeadPoliceman SPAWN ********** +//========================================================= +void CDeadPoliceman :: Spawn( ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/policeman.mdl"); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/policeman.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_debug, "Dead Policeman with bad pose\n" ); + } + // Corpses have less health + pev->health = 8;//gSkillData.barneyHealth; + + MonsterInitDead(); +} + + diff --git a/dlls/cthulhu/ranulf.cpp b/dlls/cthulhu/ranulf.cpp new file mode 100755 index 00000000..6ba7e012 --- /dev/null +++ b/dlls/cthulhu/ranulf.cpp @@ -0,0 +1,817 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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 ranulf dying for scripted sequences? +//#define RANULF_AE_DRAW ( 2 ) +#define RANULF_AE_SHOOT ( 3 ) +//#define RANULF_AE_HOLSTER ( 4 ) + +//#define RANULF_BODY_GUNHOLSTERED 0 +//#define RANULF_BODY_GUNDRAWN 1 +//#define RANULF_BODY_GUNGONE 2 + +#include "ranulf.h" + +LINK_ENTITY_TO_CLASS( monster_ranulf, CRanulf ); + +TYPEDESCRIPTION CRanulf::m_SaveData[] = +{ +// DEFINE_FIELD( CRanulf, m_fGunDrawn, FIELD_BOOLEAN ), + DEFINE_FIELD( CRanulf, m_painTime, FIELD_TIME ), + DEFINE_FIELD( CRanulf, m_checkAttackTime, FIELD_TIME ), + DEFINE_FIELD( CRanulf, m_lastAttackCheck, FIELD_BOOLEAN ), +}; + +IMPLEMENT_SAVERESTORE( CRanulf, CTalkMonster ); + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= +Task_t tlRaFollow[] = +{ + { TASK_MOVE_TO_TARGET_RANGE,(float)128 }, // Move within 128 of target ent (client) + { TASK_SET_SCHEDULE, (float)SCHED_TARGET_FACE }, +}; + +Schedule_t slRaFollow[] = +{ + { + tlRaFollow, + ARRAYSIZE ( tlRaFollow ), + bits_COND_NEW_ENEMY | + bits_COND_LIGHT_DAMAGE | + bits_COND_HEAVY_DAMAGE | + bits_COND_HEAR_SOUND | + bits_COND_PROVOKED, + bits_SOUND_DANGER, + "Follow" + }, +}; + +Task_t tlRaFaceTarget[] = +{ + { 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 slRaFaceTarget[] = +{ + { + tlRaFaceTarget, + ARRAYSIZE ( tlRaFaceTarget ), + 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 tlIdleSaStand[] = +{ + { 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 slIdleSaStand[] = +{ + { + tlIdleSaStand, + ARRAYSIZE ( tlIdleSaStand ), + 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( CRanulf ) +{ + slRaFollow, + slRaFaceTarget, + slIdleSaStand, +}; + + +IMPLEMENT_CUSTOM_SCHEDULES( CRanulf, CTalkMonster ); + +void CRanulf :: StartTask( Task_t *pTask ) +{ + CTalkMonster::StartTask( pTask ); +} + +void CRanulf :: 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 CRanulf :: 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 CRanulf :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_PLAYER_ALLY; +} + +//========================================================= +// ALertSound - Ranulf says "Freeze!" +//========================================================= +void CRanulf :: AlertSound( void ) +{ + if ( m_hEnemy != NULL ) + { + if ( FOkToSpeak() ) + { + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_ATTACK"); + PlaySentence( szBuf, RANDOM_FLOAT(2.8, 3.2), VOL_NORM, ATTN_IDLE ); + } + else + { + 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 CRanulf :: 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 CRanulf :: 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; +} + + +//========================================================= +// RanulfFirePistol - shoots one round from the pistol at +// the enemy Ranulf is facing. +//========================================================= +void CRanulf :: RanulfFirePistol ( 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, "Ranulf/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 CRanulf :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case RANULF_AE_SHOOT: + RanulfFirePistol(); + break; + +// case RANULF_AE_DRAW: + // Ranulf's bodygroup switches here so he can pull gun from holster +// pev->body = RANULF_BODY_GUNDRAWN; +// m_fGunDrawn = TRUE; +// break; + +// case RANULF_AE_HOLSTER: + // change bodygroup to replace gun in holster +// pev->body = RANULF_BODY_GUNHOLSTERED; +// m_fGunDrawn = FALSE; +// break; + + default: + CTalkMonster::HandleAnimEvent( pEvent ); + } +} + +//========================================================= +// Spawn +//========================================================= +void CRanulf :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/Ranulf.mdl"); + //UTIL_SetSize(pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX); + UTIL_SetSize(pev, Vector(-16,-16,-36), Vector(16,16,36)); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = 100; + 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; + + // why is the weapons characteristic not working??? +// pev->body = 0; // normal, nothing +// pev->body = 1; // jacket, nothing +// pev->body = 2; // normal, shovel +// pev->body = 3; // jacket, shovel + if (pev->weapons == 1) pev->body += 2; + +// 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 CRanulf :: Precache() +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/Ranulf.mdl"); + + PRECACHE_SOUND("Ranulf/ba_attack1.wav" ); + PRECACHE_SOUND("Ranulf/ba_attack2.wav" ); + + PRECACHE_SOUND("Ranulf/ba_pain1.wav"); + PRECACHE_SOUND("Ranulf/ba_pain2.wav"); + PRECACHE_SOUND("Ranulf/ba_pain3.wav"); + + PRECACHE_SOUND("Ranulf/ba_die1.wav"); + PRECACHE_SOUND("Ranulf/ba_die2.wav"); + PRECACHE_SOUND("Ranulf/ba_die3.wav"); + + // every new Ranulf must call this, otherwise + // when a level is loaded, nobody will talk (time is reset to 0) + TalkInit(); + CTalkMonster::Precache(); +} + +// Init talk data +void CRanulf :: TalkInit() +{ + CTalkMonster::TalkInit(); + + // Ranulf speech group names (group names are in sentences.txt) + + if (!m_iszSpeakAs) + { + m_szGrp[TLK_ANSWER] = "BA_ANSWER"; + m_szGrp[TLK_QUESTION] = "BA_QUESTION"; + m_szGrp[TLK_IDLE] = "BA_IDLE"; + m_szGrp[TLK_STARE] = "BA_STARE"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) //LRC + m_szGrp[TLK_USE] = "BA_PFOLLOW"; + else + m_szGrp[TLK_USE] = "BA_OK"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_UNUSE] = "BA_PWAIT"; + else + m_szGrp[TLK_UNUSE] = "BA_WAIT"; + if (pev->spawnflags & SF_MONSTER_PREDISASTER) + m_szGrp[TLK_DECLINE] = "BA_POK"; + else + m_szGrp[TLK_DECLINE] = "BA_NOTOK"; + 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 Ranulf voice for now + m_voicePitch = 100; +} + +int CRanulf :: 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) ) + { + // 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) || UTIL_IsFacing( pevAttacker, pev->origin ) ) + { + // Alright, now I'm pissed! + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_MAD"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "BA_MAD", 4, VOL_NORM, ATTN_NORM ); + } + + Remember( bits_MEMORY_PROVOKED ); + StopFollowing( TRUE ); + } + else + { + // Hey, be careful with that + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_SHOT"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "BA_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + Remember( bits_MEMORY_SUSPICIOUS ); + } + } + else if ( !(m_hEnemy->IsPlayer()) && pev->deadflag == DEAD_NO ) + { + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_SHOT"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "BA_SHOT", 4, VOL_NORM, ATTN_NORM ); + } + } + } + + return ret; +} + + +//========================================================= +// PainSound +//========================================================= +void CRanulf :: 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, "Ranulf/ba_pain1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "Ranulf/ba_pain2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "Ranulf/ba_pain3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + +//========================================================= +// DeathSound +//========================================================= +void CRanulf :: DeathSound ( void ) +{ + switch (RANDOM_LONG(0,2)) + { + case 0: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "Ranulf/ba_die1.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 1: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "Ranulf/ba_die2.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + case 2: EMIT_SOUND_DYN( ENT(pev), CHAN_VOICE, "Ranulf/ba_die3.wav", 1, ATTN_NORM, 0, GetVoicePitch()); break; + } +} + + +void CRanulf::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 CRanulf::Killed( entvars_t *pevAttacker, int iGib ) +{ +// if ( pev->body < RANULF_BODY_GUNGONE && !(pev->spawnflags & SF_MONSTER_NO_WPN_DROP)) +// {// drop the gun! +// Vector vecGunPos; +// Vector vecGunAngles; + +// pev->body = RANULF_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* CRanulf :: 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 Ranulf will talk + // when 'used' + psched = CTalkMonster::GetScheduleOfType(Type); + + if (psched == slIdleStand) + return slRaFaceTarget; // override this for different target face behavior + else + return psched; + + case SCHED_TARGET_CHASE: + return slRaFollow; + + 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 slIdleSaStand; + } + 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 *CRanulf :: GetSchedule ( void ) +{ + // NB: Ranulf is a replacement model for the player when we are getting him to do something strange... + // It should not ever actually have to use its AI. + 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() ) + { + // Hey, be careful with that + if (m_iszSpeakAs) + { + char szBuf[32]; + strcpy(szBuf,STRING(m_iszSpeakAs)); + strcat(szBuf,"_KILL"); + PlaySentence( szBuf, 4, VOL_NORM, ATTN_NORM ); + } + else + { + PlaySentence( "RA_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 CRanulf :: GetIdealState ( void ) +{ + return CTalkMonster::GetIdealState(); +} + + + +void CRanulf::DeclineFollowing( void ) +{ + PlaySentence( m_szGrp[TLK_DECLINE], 2, VOL_NORM, ATTN_NORM ); //LRC +} + + + + + +//========================================================= +// DEAD RANULF 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! +// +//========================================================= + +char *CDeadRanulf::m_szPoses[] = { "lying_on_back", "lying_on_side", "lying_on_stomach" }; + +void CDeadRanulf::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_ranulf_dead, CDeadRanulf ); + +//========================================================= +// ********** DeadRanulf SPAWN ********** +//========================================================= +void CDeadRanulf :: Spawn( ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/Ranulf.mdl"); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/Ranulf.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 Ranulf with bad pose\n" ); + } + // Corpses have less health + pev->health = 8;//gSkillData.ranulfHealth; + + MonsterInitDead(); +} + + diff --git a/dlls/cthulhu/ranulf.h b/dlls/cthulhu/ranulf.h new file mode 100755 index 00000000..547cb42b --- /dev/null +++ b/dlls/cthulhu/ranulf.h @@ -0,0 +1,60 @@ + +class CRanulf : public CTalkMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int ISoundMask( void ); + void RanulfFirePistol( 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; + + CUSTOM_SCHEDULES; +}; + +/////////////////////////////////////////////////////////////////////////////////////////////////////// + +class CDeadRanulf : 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]; +}; + diff --git a/dlls/cthulhu/revolver.cpp b/dlls/cthulhu/revolver.cpp new file mode 100755 index 00000000..239c1264 --- /dev/null +++ b/dlls/cthulhu/revolver.cpp @@ -0,0 +1,265 @@ +/*** +* +* Copyright (c) 1999, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "revolver.h" + + +enum revolver_e { + REVOLVER_IDLE1 = 0, + REVOLVER_FIDGET1, + REVOLVER_FIRE, + REVOLVER_RELOAD, + REVOLVER_HOLSTER, + REVOLVER_DRAW, + REVOLVER_IDLE2, + REVOLVER_IDLE3, + REVOLVER_QUICKFIRE_READY, + REVOLVER_QUICKFIRE_SHOOT, + REVOLVER_QUICKFIRE_RELAX +}; + +LINK_ENTITY_TO_CLASS( weapon_revolver, CRevolver ); + + +void CRevolver::Spawn( ) +{ + pev->classname = MAKE_STRING("weapon_revolver"); // hack to allow for old names + Precache( ); + m_iId = WEAPON_REVOLVER; + SET_MODEL(ENT(pev), "models/w_revolver.mdl"); + + m_iDefaultAmmo = REVOLVER_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + +void CRevolver::Precache( void ) +{ + PRECACHE_MODEL("models/v_revolver.mdl"); + PRECACHE_MODEL("models/w_revolver.mdl"); +// PRECACHE_MODEL("models/p_revolver.mdl"); + + m_iShell = PRECACHE_MODEL ("models/revolver_rounds.mdl");// brass shell + + PRECACHE_SOUND ("weapons/revolver_cock1.wav"); + PRECACHE_SOUND ("weapons/revolver_reload1.wav"); + PRECACHE_SOUND ("weapons/revolver_shot1.wav"); + PRECACHE_SOUND ("weapons/revolver_shot2.wav"); + + m_usFireRevolver1 = PRECACHE_EVENT( 1, "events/revolver1.sc" ); + m_usFireRevolver2 = PRECACHE_EVENT( 1, "events/revolver2.sc" ); +} + +int CRevolver::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "Revolver"; + p->iMaxAmmo1 = REVOLVER_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = REVOLVER_MAX_CLIP; + p->iSlot = 1; + p->iPosition = 0; + p->iFlags = 0; + p->iId = m_iId = WEAPON_REVOLVER; + p->iWeight = REVOLVER_WEIGHT; + + return 1; +} + +BOOL CRevolver::Deploy( ) +{ + // pev->body = 1; + return DefaultDeploy( "models/v_revolver.mdl", "", REVOLVER_DRAW, "revolver", /*UseDecrement() ? 1 : 0*/ 0 ); +} + +int CRevolver::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + +void CRevolver::SecondaryAttack( void ) +{ + // no difference + PrimaryAttack(); +} + +void CRevolver::PrimaryAttack( void ) +{ + RevolverFire( 0.01, 1.0, TRUE ); +} + +void CRevolver::RevolverFire( float flSpread , float flCycleTime, BOOL fUseAutoAim ) +{ + if (m_iClip <= 0) + { + if (m_fFireOnEmpty) + { + PlayEmptySound(); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.2; + } + + return; + } + + m_iClip--; + + m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; + + int flags = 0; + +//#if defined( CLIENT_WEAPONS ) +// flags = FEV_NOTHOST; +//#else +// flags = 0; +//#endif + + if (fUseAutoAim) + { + PLAYBACK_EVENT( flags, m_pPlayer->edict(), m_usFireRevolver1); + } + else + { + PLAYBACK_EVENT( flags, m_pPlayer->edict(), m_usFireRevolver2); + } + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming; + + if ( fUseAutoAim ) + { + vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + } + else + { + vecAiming = gpGlobals->v_forward; + } + + m_pPlayer->FireBullets( 1, vecSrc, vecAiming, Vector( flSpread, flSpread, flSpread ), 8192, BULLET_PLAYER_REVOLVER, 0 ); + + m_flNextPrimaryAttack = m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + flCycleTime; + + //if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + // m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); +} + + +void CRevolver::Reload( void ) +{ + int iResult; + + iResult = DefaultReload( REVOLVER_MAX_CLIP, REVOLVER_RELOAD, 111.0/36.0 ); + + if (iResult) + { + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } +} + +void CRevolver::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + // only idle if the slid isn't back + if (m_iClip != 0) + { + int iAnim; + float flRand = UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0.0, 1.0 ); + + if (flRand <= 0.3 + 0 * 0.75) + { + iAnim = REVOLVER_IDLE3; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 49.0 / 16; + } + else if (flRand <= 0.6 + 0 * 0.875) + { + iAnim = REVOLVER_IDLE1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 60.0 / 16.0; + } + else + { + iAnim = REVOLVER_IDLE2; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 40.0 / 16.0; + } + SendWeaponAnim( iAnim, 1 ); + } +} + + + +///////////////////////////////////////////////////////////////////////////// + + +void CRevolverAmmo::Spawn( void ) +{ + Precache( ); + SET_MODEL(ENT(pev), "models/w_revolverammo.mdl"); + CBasePlayerAmmo::Spawn( ); +} + +void CRevolverAmmo::Precache( void ) +{ + PRECACHE_MODEL ("models/w_revolverammo.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); +} + +BOOL CRevolverAmmo::AddAmmo( CBaseEntity *pOther ) +{ + if (pOther->GiveAmmo( AMMO_REVOLVER_GIVE, "Revolver", REVOLVER_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + //EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/revolver_rounds1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; +} + +LINK_ENTITY_TO_CLASS( ammo_revolver, CRevolverAmmo ); + + + + + + + + + + + + + + + diff --git a/dlls/cthulhu/revolver.h b/dlls/cthulhu/revolver.h new file mode 100755 index 00000000..9cf36ba8 --- /dev/null +++ b/dlls/cthulhu/revolver.h @@ -0,0 +1,60 @@ + +#ifndef REVOLVER_H +#define REVOLVER_H + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" + + +class CRevolver : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 1; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + void RevolverFire( float flSpread, float flCycleTime, BOOL fUseAutoAim ); + BOOL Deploy( void ); + void Reload( void ); + void WeaponIdle( void ); + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } + +private: + int m_iShell; + + + unsigned short m_usFireRevolver1; + unsigned short m_usFireRevolver2; +}; + +////////////////////////////////////////////////////////////////////////////////////////////// + +class CRevolverAmmo : public CBasePlayerAmmo +{ + void Spawn( void ); + void Precache( void ); + BOOL AddAmmo( CBaseEntity *pOther ); +}; + + + +#endif + + diff --git a/dlls/cthulhu/rlyeh_seal.cpp b/dlls/cthulhu/rlyeh_seal.cpp new file mode 100755 index 00000000..24b8e8fc --- /dev/null +++ b/dlls/cthulhu/rlyeh_seal.cpp @@ -0,0 +1,402 @@ + +#include "rlyeh_seal.h" + + +TYPEDESCRIPTION CFuncRlyehLock::m_SaveData[] = +{ + DEFINE_FIELD( CFuncRlyehLock, m_sMaster, FIELD_STRING ), + DEFINE_FIELD( CFuncRlyehLock, m_sTarget, FIELD_STRING ), +}; + +IMPLEMENT_SAVERESTORE( CFuncRlyehLock, CBaseEntity ); + +LINK_ENTITY_TO_CLASS( func_rlyehlock, CFuncRlyehLock ); + +void CFuncRlyehLock :: Precache( void ) +{ + char* szSoundFile = (char*) STRING(pev->message); + + if ( !FStringNull( pev->message ) && strlen( szSoundFile ) > 1 ) + { + if (*szSoundFile != '!') + PRECACHE_SOUND(szSoundFile); + } + CBaseEntity::Precache(); +} + +void CFuncRlyehLock :: Spawn( void ) +{ + pev->angles = g_vecZero; + pev->movetype = MOVETYPE_PUSH; // so it doesn't get pushed by anything + pev->solid = SOLID_BSP; + SET_MODEL( ENT(pev), STRING(pev->model) ); + + // If it can't move/go away, it's really part of the world + if (!m_pMoveWith) //LRC + pev->flags |= FL_WORLDBRUSH; + + Precache( ); +} + +BOOL CFuncRlyehLock :: IsLockedByMaster( void ) +{ + return !UTIL_IsMasterTriggered(m_sMaster, NULL); +} + +void CFuncRlyehLock :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( ShouldToggle( useType, (int)(pev->frame)) ) + pev->frame = 1 - pev->frame; +} + +void CFuncRlyehLock::KeyValue( KeyValueData *pkvd ) +{ + pkvd->fHandled = TRUE; + + if ( FStrEq(pkvd->szKeyName, "master") ) + m_sMaster = ALLOC_STRING( pkvd->szValue ); + if ( FStrEq(pkvd->szKeyName, "targetonlock") ) + m_sTarget = ALLOC_STRING( pkvd->szValue ); + else + CBaseEntity::KeyValue( pkvd ); +} + +void CFuncRlyehLock::FireTarget( ) +{ + CBaseEntity* pTarget = NULL; + pTarget = UTIL_FindEntityByTargetname(pTarget, STRING(m_sTarget)); + + if (FNullEnt(pTarget)) + return; + + pTarget->Use( NULL, this, USE_TOGGLE, 0 ); +} + +///////////////////////////////////////////////////////////////////////////////////////////////// + +enum rlyeh_seal_e { + RLYEH_SEAL_DRAW = 0, + RLYEH_SEAL_IDLE1, + RLYEH_SEAL_IDLE2, + RLYEH_SEAL_IDLE3, + RLYEH_SEAL_CAST, + RLYEH_SEAL_HOLSTER, + RLYEH_SEAL_WORLD, + RLYEH_SEAL_GROUND +}; + + +LINK_ENTITY_TO_CLASS( monster_rlyeh_seal, CRlyehSealed ); + +//TYPEDESCRIPTION CRlyehSealed::m_SaveData[] = +//{ +//}; + +//IMPLEMENT_SAVERESTORE( CRlyehSealed, CBaseMonster ); + + +void CRlyehSealed :: Spawn( void ) +{ + Precache( ); + + m_afCapability = 0; + + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_NOT; + + SET_MODEL(ENT(pev), "models/v_rlyeh_seal.mdl"); + pev->frame = 0; + pev->body = 3; + //pev->sequence = RLYEH_SEAL_IDLE1; + pev->sequence = RLYEH_SEAL_WORLD; + ResetSequenceInfo( ); + //pev->framerate = 6; + + UTIL_SetSize(pev, Vector( -4, -4, -4), Vector(4, 4, 4)); + UTIL_SetOrigin( this, pev->origin ); + + pev->solid = SOLID_BBOX; + DontThink (); + + pev->takedamage = DAMAGE_YES; + pev->health = 5; // don't let die normally + + //MonsterInit(); + SetBits (pev->flags, FL_MONSTER); + + pev->solid = SOLID_BBOX; + m_bloodColor = DONT_BLEED; +} + +void CRlyehSealed :: Precache( void ) +{ + PRECACHE_MODEL("models/v_rlyeh_seal.mdl"); +} + +int CRlyehSealed :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if (flDamage < pev->health) + { + SetThink( SUB_Remove ); + SetNextThink( 0.1 ); + return FALSE; + } + return CBaseMonster :: TakeDamage ( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CRlyehSealed :: RlyehThink ( void ) +{ + SetNextThink(1); +} + +void CRlyehSealed :: Killed( entvars_t *pevAttacker, int iGib ) +{ + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + WRITE_COORD( pev->origin.x ); // Send to PAS because of the sound + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 10 ); + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + UTIL_Remove(this); +} + +int CRlyehSealed :: Classify ( void ) +{ + return CLASS_ALIEN_PASSIVE; +} + +////////////////////////////////////////////////////////////////////////////////// + +LINK_ENTITY_TO_CLASS( weapon_rlyeh_seal, CRlyehSeal ); + + +void CRlyehSeal::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_RLYEH_SEAL; + SET_MODEL(ENT(pev), "models/v_rlyeh_seal.mdl"); + pev->frame = 0; + pev->body = 3; + pev->sequence = RLYEH_SEAL_GROUND; + //ResetSequenceInfo( ); + pev->framerate = 0; + + FallInit();// get ready to fall down + + m_iDefaultAmmo = 1; + + if ( !g_pGameRules->IsDeathmatch() ) + { + UTIL_SetSize(pev, Vector(-16, -16, 0), Vector(16, 16, 28) ); + } +} + +void CRlyehSeal::Precache( void ) +{ + PRECACHE_MODEL ("models/v_rlyeh_seal.mdl"); + UTIL_PrecacheOther( "monster_rlyeh_seal" ); + // sound for trying to put it somewhere other than a R'Lyeh Lock + PRECACHE_SOUND("voiceover/rs_notthere.wav"); +} + +int CRlyehSeal::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "R'lyeh Seal"; + p->iMaxAmmo1 = 1; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 4; + p->iId = m_iId = WEAPON_RLYEH_SEAL; + p->iWeight = 1; + p->iFlags = ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + + return 1; +} + +BOOL CRlyehSeal::Deploy( ) +{ + pev->body = 0; + return DefaultDeploy( "models/v_rlyeh_seal.mdl", "", RLYEH_SEAL_DRAW, "rlyehseal" ); +} + + +void CRlyehSeal::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + if (!m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + // out of mines + m_pPlayer->pev->weapons &= ~(1<pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); +} + +int CRlyehSeal::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + + +void CRlyehSeal::PrimaryAttack( void ) +{ + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + return; + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = gpGlobals->v_forward; + + TraceResult tr; + + UTIL_TraceLine( vecSrc, vecSrc + vecAiming * 128, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); + + if (tr.flFraction < 1.0) + { + // ALERT( at_console, "hit %f\n", tr.flFraction ); + + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + if (pEntity && !(pEntity->pev->flags & FL_CONVEYOR)) + { + // is this a R'lyeh Lock? + if (strcmp(STRING(pEntity->pev->classname),"func_rlyehlock")) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_STATIC, "voiceover/rs_notthere.wav", 1, ATTN_NORM, 0, 100); + } + else + { + CFuncRlyehLock* pLock = (CFuncRlyehLock*)pEntity; + if (pLock->IsLockedByMaster()) + { + // play locked sound if there is one... + char* szSoundFile = (char*) STRING(pLock->pev->message); + + if ( !FStringNull( pLock->pev->message ) && strlen( szSoundFile ) > 1 ) + { + EMIT_SOUND_DYN( ENT(pev), CHAN_STATIC, szSoundFile, 1, ATTN_NORM, 0, 100); + } + } + else + { + Vector angles = UTIL_VecToAngles( tr.vecPlaneNormal ); + + edict_t *pentity; + entvars_t *pevCreate; + + // ALERT(at_console,"Making Monster NOW\n"); + + pentity = CREATE_NAMED_ENTITY( MAKE_STRING("monster_rlyeh_seal") ); + + pevCreate = VARS( pentity ); + pevCreate->origin = tr.vecEndPos + tr.vecPlaneNormal * 8; + pevCreate->angles = angles; + + DispatchSpawn( ENT( pevCreate ) ); + pevCreate->owner = edict(); + + //LRC - custom monster behaviour + CBaseEntity *pEnt = CBaseEntity::Instance( pevCreate ); + + + + //CBaseEntity *pEnt = CBaseEntity::Create( "monster_rlyeh_seal", tr.vecEndPos + tr.vecPlaneNormal * 8, angles, m_pPlayer->edict() ); + //CBaseMonster *pNewMonster = pEnt->MyMonsterPointer( ); + //pEnt->pev->spawnflags |= 1; + + CRlyehSealed *pRlyehSealed = (CRlyehSealed *)pEnt; + pRlyehSealed->pev->origin = (pLock->pev->absmin + pLock->pev->absmax)/2; + pRlyehSealed->pev->origin.z = pLock->pev->absmax.z + 8; + + pLock->FireTarget(); + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0) + { + SendWeaponAnim( RLYEH_SEAL_DRAW ); + } + else + { + // no more mines! + RetireWeapon(); + return; + } + } + } + } + else + { + // ALERT( at_console, "no deploy\n" ); + } + } + else + { + + } + + m_flNextPrimaryAttack = gpGlobals->time + 0.3; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); +} + +void CRlyehSeal::WeaponIdle( void ) +{ + if (m_flTimeWeaponIdle > gpGlobals->time) + return; + + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] > 0) + { + SendWeaponAnim( RLYEH_SEAL_DRAW ); + } + else + { + RetireWeapon(); + return; + } + + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.25) + { + iAnim = RLYEH_SEAL_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + 90.0 / 30.0; + } + else if (flRand <= 0.75) + { + iAnim = RLYEH_SEAL_IDLE2; + m_flTimeWeaponIdle = gpGlobals->time + 60.0 / 30.0; + } + else + { + iAnim = RLYEH_SEAL_IDLE3; + m_flTimeWeaponIdle = gpGlobals->time + 100.0 / 30.0; + } + + SendWeaponAnim( iAnim ); + +} + + + diff --git a/dlls/cthulhu/rlyeh_seal.h b/dlls/cthulhu/rlyeh_seal.h new file mode 100755 index 00000000..2ddba602 --- /dev/null +++ b/dlls/cthulhu/rlyeh_seal.h @@ -0,0 +1,119 @@ + +#ifndef RLYEH_SEAL_H +#define RLYEH_SEAL_H + +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "effects.h" +#include "gamerules.h" + +#include "triggers.h" + +////////////////////////////////////////////////////////////////////////////////////////////////// + +class CFuncRlyehLock : public CBaseEntity +{ +public: + void Precache( void ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + virtual STATE GetState( void ) { return pev->frame?STATE_ON:STATE_OFF; }; + + // Bmodels don't go across transitions + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + BOOL IsLockedByMaster( void ); + void FireTarget(); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_sMaster; + int m_sTarget; +}; + +////////////////////////////////////////////////////////////////////////////////////////////////// + +#define RLYEH_SEAL_PRIMARY_VOLUME 450 + +// The R'lyeh Seal locks Cthulhu into his temple. +// It only attaches itself to a R'lyeh Lock, and only then when the Lock is active (Cthulhu is dead). +// It automatically positions itself above the lock... + +class CRlyehSealed : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + int Classify ( void ); + + // no melee attacks + BOOL CheckMeleeAttack1 ( float flDot, float flDist ) { return FALSE; }; + BOOL CheckMeleeAttack2 ( float flDot, float flDist ) { return FALSE; }; + BOOL CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; }; + + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + void EXPORT RlyehThink ( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + + //int Save( CSave &save ); + //int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +protected: + +}; + + +//////////////////////////////////////////////////////////////////////////////// + +class CRlyehSeal : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 4; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + void SetObjectCollisionBox( void ) + { + //!!!BUGBUG - fix the model! + pev->absmin = pev->origin + Vector(-16, -16, -5); + pev->absmax = pev->origin + Vector(16, 16, 28); + } + + void PrimaryAttack( void ); + BOOL Deploy( void ); + void Holster( int skiplocal = 0 ); + void WeaponIdle( void ); +}; + +#endif + + + diff --git a/dlls/cthulhu/servitor.cpp b/dlls/cthulhu/servitor.cpp new file mode 100755 index 00000000..5752a50c --- /dev/null +++ b/dlls/cthulhu/servitor.cpp @@ -0,0 +1,625 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Servitor +//========================================================= + +// UNDONE: Don't flinch every time you get hit + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "player.h" +#include "schedule.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define SERVITOR_AE_ATTACK_RIGHT 0x01 +#define SERVITOR_AE_ATTACK_LEFT 0x02 +#define SERVITOR_AE_GRAB 0x03 +#define SERVITOR_AE_BITE 0x04 +#define SERVITOR_AE_THROW 0x05 + +#define SERVITOR_FLINCH_DELAY 8 // at most one flinch every n secs + +#define SERVITOR_REACH 256 + +class CServitor : 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 ); + + void EXPORT ServitorThink( 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 + virtual BOOL CheckMeleeAttack1( float flDot, float flDist ); + virtual BOOL CheckMeleeAttack2( float flDot, float flDist ); + 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 ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + CBaseEntity* ServitorCheckTraceHullAttack(float flDist, int iDamage, int iDmgType); + + void DropVictim(void); + + BOOL mbGrabbedVictim; + CBaseEntity* mpVictim; + int miVictimMoveType; +}; + +LINK_ENTITY_TO_CLASS( monster_servitor, CServitor ); + +const char *CServitor::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CServitor::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CServitor::pAttackSounds[] = +{ + "zombie/zo_attack1.wav", + "zombie/zo_attack2.wav", +}; + +const char *CServitor::pIdleSounds[] = +{ + "zombie/zo_idle1.wav", + "zombie/zo_idle2.wav", + "zombie/zo_idle3.wav", + "zombie/zo_idle4.wav", +}; + +const char *CServitor::pAlertSounds[] = +{ + "zombie/zo_alert10.wav", + "zombie/zo_alert20.wav", + "zombie/zo_alert30.wav", +}; + +const char *CServitor::pPainSounds[] = +{ + "zombie/zo_pain1.wav", + "zombie/zo_pain2.wav", +}; + +TYPEDESCRIPTION CServitor::m_SaveData[] = +{ + DEFINE_FIELD( CServitor, mbGrabbedVictim, FIELD_BOOLEAN ), + DEFINE_FIELD( CServitor, mpVictim, FIELD_CLASSPTR ), + DEFINE_FIELD( CServitor, miVictimMoveType, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CServitor, CBaseMonster ); + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CServitor :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CServitor :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + pev->yaw_speed = ys; +} + +//========================================================= +// CheckMeleeAttack1 +//========================================================= +BOOL CServitor :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb) + //int iGround = FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ); + + // Cthulhu: but this stops the monster from hitting flying monsters that are + // melee attacking it (e.g. nightgaunt) + // Solution: explicitly check for monster types that we cannot hit + + BOOL bHit = TRUE; // we can hit by default + /* + if (m_hEnemy) + { + if (FClassnameIs( m_hEnemy->pev, "hornet")) bHit = FALSE; + if (FClassnameIs( m_hEnemy->pev, "monster_snark")) bHit = FALSE; + } + */ + + //if ( flDist <= 64 && flDot >= 0.7 && m_hEnemy != NULL && iGround ) + if ( flDist <= SERVITOR_REACH && flDot >= 0.7 && m_hEnemy != NULL && bHit ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// CheckMeleeAttack2 +//========================================================= +BOOL CServitor :: CheckMeleeAttack2 ( float flDot, float flDist ) +{ + // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb) + //int iGround = FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ); + + // Cthulhu: but this stops the monster from hitting flying monsters that are + // melee attacking it (e.g. nightgaunt) + // Solution: explicitly check for monster types that we cannot hit + + BOOL bHit = TRUE; // we can hit by default + /* + if (m_hEnemy) + { + if (FClassnameIs( m_hEnemy->pev, "hornet")) bHit = FALSE; + if (FClassnameIs( m_hEnemy->pev, "monster_snark")) bHit = FALSE; + } + */ + + //if ( flDist <= 64 && flDot >= 0.7 && m_hEnemy != NULL && iGround ) + if ( flDist <= SERVITOR_REACH && flDot >= 0.7 && m_hEnemy != NULL && bHit ) + { + return TRUE; + } + return FALSE; +} + +int CServitor :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Take 20% 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.2; + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CServitor :: PainSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + if (RANDOM_LONG(0,5) < 3) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); +} + +void CServitor :: 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 CServitor :: 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 CServitor :: 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 CServitor :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SERVITOR_AE_ATTACK_RIGHT: + { + // do stuff for this event. + CBaseEntity *pHurt = ServitorCheckTraceHullAttack( SERVITOR_REACH + 96, gSkillData.zombieDmgOneSlash * 2, 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 * 150; + } + // 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 SERVITOR_AE_ATTACK_LEFT: + { + // do stuff for this event. + CBaseEntity *pHurt = ServitorCheckTraceHullAttack( SERVITOR_REACH + 96, gSkillData.zombieDmgOneSlash * 2, 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 * 150; + } + 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 SERVITOR_AE_GRAB: + { + // get the position of attachment 1 (index 0) (right hand) + Vector vecPosition; + Vector vecJunk; + CBaseEntity* pEntity = NULL; + GetAttachment( 0, vecPosition, vecJunk ); + // look in the region of his hand + bool bFound = false; + while ((pEntity = UTIL_FindEntityInSphere( pEntity, vecPosition, 64 )) != NULL) + { + // is it a monster or player + if (pEntity->pev->flags & (FL_MONSTER|FL_CLIENT)) + { + // is it small enough (near man sized) + if (pEntity->pev->absmax.z - pEntity->pev->absmin.z <= 96) + { + // set the grabbed flag to true + mbGrabbedVictim = TRUE; + // set the grabbed monster pointer + mpVictim = pEntity; + // make monster prone (cf barnacle) + mpVictim->FBecomeProne(); + // save the grabbed monsters original movetype + miVictimMoveType = mpVictim->pev->movetype; + // set the grabbed monsters movetype to fly + mpVictim->pev->movetype = MOVETYPE_FLY; + // set it's origin to the servitors hand + mpVictim->pev->origin = vecPosition - Vector(0,0,48); + bFound = true; + break; + } + } + } + // if we didn't find something + if (!bFound) + { + // stop the sequence + ResetSequenceInfo(); + } + } + break; + + case SERVITOR_AE_BITE: + { + // we have gotten the monster as far as our mouth + if (mbGrabbedVictim) + { + // if the victim is dead + if (!mpVictim->IsAlive()) + { + // just drop it + DropVictim(); // reset movetype, unset flag and pointer (cf barnacle release) + ResetSequenceInfo(); + } + else + { + // apply damage + mpVictim->TakeDamage ( pev, pev, mpVictim->pev->health, DMG_SLASH | DMG_ALWAYSGIB ); + if (!mpVictim->IsAlive()) + { + mbGrabbedVictim = FALSE; + mpVictim = NULL; + } + } + } + } + break; + + case SERVITOR_AE_THROW: + { + if (mbGrabbedVictim) + { + // we have bitten this monster, but it is still alive! + // so just throw it away + mpVictim->pev->movetype = miVictimMoveType; + if (mpVictim->pev->flags & FL_MONSTER) + { + ((CBaseMonster*)mpVictim)->m_IdealMonsterState = MONSTERSTATE_IDLE; + } + else // player + { + ((CBasePlayer*)mpVictim)->BarnacleVictimReleased(); + } + // face the victim the same way as the monster and then turn 180 + mpVictim->pev->angles = pev->angles; + mpVictim->pev->angles.y = UTIL_AngleMod(mpVictim->pev->angles.y + 180.0); + + mpVictim->pev->punchangle.z = -20; + mpVictim->pev->punchangle.x = 20; + mpVictim->pev->velocity = mpVictim->pev->velocity + gpGlobals->v_forward * -500; + mpVictim->pev->velocity = mpVictim->pev->velocity + gpGlobals->v_up * 100; + mpVictim = NULL; + mbGrabbedVictim = FALSE; + } + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +void CServitor :: ServitorThink() +{ + // do we need to start the monster off... + if (m_afCapability == bits_CAP_DOORS_GROUP) + { + StartMonster(); + SetThink(ServitorThink); + } + + //if we have grabbed someone + if (mbGrabbedVictim) + { + SetNextThink(0.05); + float flInterval = StudioFrameAdvance( ); // animate + + // are we dead + if (!IsAlive()) + { + DropVictim(); + } + // else are they dead + else if (!mpVictim->IsAlive()) + { + DropVictim(); + } + else + { + // set their origin to the correct position + Vector vecPosition; + Vector vecJunk; + GetAttachment( 0, vecPosition, vecJunk ); + mpVictim->pev->origin = vecPosition - Vector(0,0,48); + } + + DispatchAnimEvents( flInterval ); + } + else + { + CBaseMonster::MonsterThink(); + } +} + +void CServitor :: DropVictim() +{ + mpVictim->pev->movetype = miVictimMoveType; + if (mpVictim->pev->flags & FL_MONSTER) + { + ((CBaseMonster*)mpVictim)->m_IdealMonsterState = MONSTERSTATE_IDLE; + } + mpVictim->pev->velocity = g_vecZero; + mpVictim = NULL; + mbGrabbedVictim = FALSE; +} + +//========================================================= +// 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 Servitor because his swing starts lower as +// a percentage of his height (otherwise he swings over the +// players head) +//========================================================= +CBaseEntity* CServitor::ServitorCheckTraceHullAttack(float flDist, int iDamage, int iDmgType) +{ + // the enemy may be standing on something, still within reach of a big monster like this, + // but above the 'normal' swipe height + + TraceResult tr; + + UTIL_MakeVectors( pev->angles ); + Vector vecStart = pev->origin; + vecStart.z += 32; + //vecStart.z += 128; + //Vector vecEnd = vecStart + (gpGlobals->v_forward * flDist) - (gpGlobals->v_up * flDist * 0.3); + Vector vecEnd = vecStart + (gpGlobals->v_forward * flDist); + + for (double dUp = 0.0; dUp <= 192.0; dUp += 64.0) + { + UTIL_TraceHull( vecStart + Vector(0,0,dUp), vecEnd + Vector(0,0,dUp), dont_ignore_monsters, head_hull, ENT(pev), &tr ); + + if ( tr.pHit ) + { + CBaseEntity *pEntity = CBaseEntity::Instance( tr.pHit ); + + // don't hit the world + if (FClassnameIs( pEntity->pev, "worldspawn")) continue; + + if ( iDamage > 0 ) + { + pEntity->TakeDamage( pev, pev, iDamage, iDmgType ); + } + + return pEntity; + } + } + + return NULL; +} + +//========================================================= +// Spawn +//========================================================= +void CServitor :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/monsters/servitor.mdl"); + UTIL_SetSize( pev, Vector( -48, -48, 0 ), Vector( 48, 48, 256 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_YELLOW; + + if (pev->health == 0) + pev->health = gSkillData.gargantuaHealth; + //pev->view_ofs = VEC_VIEW;// position of the eyes relative to monster's origin (taken from the model file). + m_flFieldOfView = 0.2;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + m_afCapability = bits_CAP_DOORS_GROUP; + + // initialise the victim variables + mbGrabbedVictim = FALSE; + mpVictim = NULL; + miVictimMoveType = 0; + + MonsterInit(); + + SetThink(ServitorThink); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CServitor :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/monsters/servitor.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 CServitor::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { +#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 + SERVITOR_FLINCH_DELAY; + } + + return iIgnore; +} + + + + diff --git a/dlls/cthulhu/shotgun.cpp b/dlls/cthulhu/shotgun.cpp new file mode 100755 index 00000000..2883e919 --- /dev/null +++ b/dlls/cthulhu/shotgun.cpp @@ -0,0 +1,347 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" + +// special deathmatch shotgun spreads +#define VECTOR_CONE_DM_SHOTGUN Vector( 0.08716, 0.04362, 0.00 )// 10 degrees by 5 degrees +#define VECTOR_CONE_DM_DOUBLESHOTGUN Vector( 0.17365, 0.04362, 0.00 ) // 20 degrees by 5 degrees + +enum shotgun_e { + SHOTGUN_IDLE1 = 0, + SHOTGUN_IDLE2, + SHOTGUN_IDLE3, + SHOTGUN_HOLSTER, + SHOTGUN_DRAW, + SHOTGUN_FIRE, + SHOTGUN_FIRE2, + SHOTGUN_RELOAD +}; + +class CShotgun : public CBasePlayerWeapon +{ +public: + void Spawn( void ); + void Precache( void ); + int iItemSlot( ) { return 1; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + + void PrimaryAttack( void ); + void SecondaryAttack( void ); + BOOL Deploy( ); + void Holster( int skiplocal = 0 ); + void Reload( void ); + void WeaponIdle( void ); + int m_iShell; + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } + +private: + unsigned short m_usDoubleFire; + unsigned short m_usSingleFire; +}; +LINK_ENTITY_TO_CLASS( weapon_shotgun, CShotgun ); + + + +void CShotgun::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_SHOTGUN; + SET_MODEL(ENT(pev), "models/w_shotgun.mdl"); + + m_iDefaultAmmo = SHOTGUN_DEFAULT_GIVE; + + FallInit();// get ready to fall +} + + +void CShotgun::Precache( void ) +{ + PRECACHE_MODEL("models/v_shotgun.mdl"); + PRECACHE_MODEL("models/w_shotgun.mdl"); + PRECACHE_MODEL("models/p_shotgun.mdl"); + + m_iShell = PRECACHE_MODEL ("models/shotgunshell.mdl");// shotgun shell + + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND ("weapons/dbarrel1.wav");//shotgun + PRECACHE_SOUND ("weapons/sbarrel1.wav");//shotgun + + PRECACHE_SOUND ("weapons/reload1.wav"); // shotgun reload + PRECACHE_SOUND ("weapons/reload3.wav"); // shotgun reload + +// PRECACHE_SOUND ("weapons/sshell1.wav"); // shotgun reload - played on client +// PRECACHE_SOUND ("weapons/sshell3.wav"); // shotgun reload - played on client + + PRECACHE_SOUND ("weapons/357_cock1.wav"); // gun empty sound +// PRECACHE_SOUND ("weapons/scock1.wav"); // cock gun + + m_usSingleFire = PRECACHE_EVENT( 1, "events/shotgun1.sc" ); + m_usDoubleFire = PRECACHE_EVENT( 1, "events/shotgun2.sc" ); +} + +int CShotgun::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + + +int CShotgun::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "shotgun"; + p->iMaxAmmo1 = SHOTGUN_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = SHOTGUN_MAX_CLIP; + p->iSlot = 1; + p->iPosition = 1; + p->iFlags = 0; + p->iId = m_iId = WEAPON_SHOTGUN; + p->iWeight = SHOTGUN_WEIGHT; + + return 1; +} + + + +BOOL CShotgun::Deploy( ) +{ + return DefaultDeploy( "models/v_shotgun.mdl", "models/p_shotgun.mdl", SHOTGUN_DRAW, "shotgun" ); +} + +void CShotgun::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + SendWeaponAnim( SHOTGUN_HOLSTER ); +} + + +void CShotgun::PrimaryAttack() +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3 && m_pPlayer->pev->watertype > CONTENT_FLYFIELD) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if (m_iClip <= 0) + { + Reload( ); + if (m_iClip == 0) + PlayEmptySound( ); + return; + } + + PLAYBACK_EVENT( 0, m_pPlayer->edict(), m_usSingleFire ); + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + m_iClip--; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + if ( g_pGameRules->IsDeathmatch() ) + { + // altered deathmatch spread + m_pPlayer->FireBullets( 4, vecSrc, vecAiming, VECTOR_CONE_DM_SHOTGUN, 2048, BULLET_PLAYER_SHOTGUN, 0 ); + } + else + { + // regular old, untouched spread. + m_pPlayer->FireBullets( 6, vecSrc, vecAiming, VECTOR_CONE_10DEGREES, 2048, BULLET_PLAYER_SHOTGUN, 0 ); + } + + //if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + // m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + double dSingleShotAnim = 0.95; + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + dSingleShotAnim; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + dSingleShotAnim; + + if (m_iClip != 0) + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + dSingleShotAnim + 3.6; + else + m_flTimeWeaponIdle = dSingleShotAnim; +} + + +void CShotgun::SecondaryAttack( void ) +{ + // don't fire underwater + if (m_pPlayer->pev->waterlevel == 3 && m_pPlayer->pev->watertype > CONTENT_FLYFIELD) + { + PlayEmptySound( ); + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + if (m_iClip <= 1) + { + PlayEmptySound( ); + Reload( ); + return; + } + + PLAYBACK_EVENT( 0, m_pPlayer->edict(), m_usDoubleFire ); + + m_pPlayer->m_iWeaponVolume = LOUD_GUN_VOLUME; + m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; + + m_iClip -= 2; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + if ( g_pGameRules->IsDeathmatch() ) + { + // tuned for deathmatch + m_pPlayer->FireBullets( 8, vecSrc, vecAiming, VECTOR_CONE_DM_DOUBLESHOTGUN, 2048, BULLET_PLAYER_SHOTGUN, 0 ); + } + else + { + // untouched default single player + m_pPlayer->FireBullets( 12, vecSrc, vecAiming, VECTOR_CONE_10DEGREES, 2048, BULLET_PLAYER_SHOTGUN, 0 ); + } + + //if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) + // HEV suit - indicate out of ammo condition + // m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); + + double dDoubleShotAnim = 1.1; + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + dDoubleShotAnim; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + dDoubleShotAnim; + + if (m_iClip != 0) + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + dDoubleShotAnim + 3.6; + else + m_flTimeWeaponIdle = dDoubleShotAnim; +} + + +void CShotgun::Reload( void ) +{ + int iResult; + + iResult = DefaultReload( SHOTGUN_MAX_CLIP, SHOTGUN_RELOAD, 111.0/36.0 ); + + if (iResult) + { + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } +} + + +void CShotgun::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pPlayer->GetAutoaimVector( AUTOAIM_5DEGREES ); + + if (m_flTimeWeaponIdle < gpGlobals->time) + { + if (m_iClip == 0 && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]) + { + Reload( ); + } + else + { + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.8) + { + iAnim = SHOTGUN_IDLE1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + (89.0/30.0);// * RANDOM_LONG(2, 5); + } + else if (flRand <= 0.95) + { + iAnim = SHOTGUN_IDLE2; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + (149.0/40.0); + } + else + { + iAnim = SHOTGUN_IDLE3; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + (149.0/40.0); + } + SendWeaponAnim( iAnim ); + } + } +} + + + +class CShotgunAmmo : public CBasePlayerAmmo +{ + void Spawn( void ) + { + Precache( ); + //SET_MODEL(ENT(pev), "models/w_shotbox.mdl"); + SET_MODEL(ENT(pev), "models/w_shotgunammo.mdl"); + CBasePlayerAmmo::Spawn( ); + } + void Precache( void ) + { + //PRECACHE_MODEL ("models/w_shotbox.mdl"); + PRECACHE_MODEL ("models/w_shotgunammo.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + } + BOOL AddAmmo( CBaseEntity *pOther ) + { + if (pOther->GiveAmmo( AMMO_SHOTGUN_GIVE, "shotgun", SHOTGUN_MAX_CARRY ) != -1) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); + return TRUE; + } + return FALSE; + } +}; +LINK_ENTITY_TO_CLASS( ammo_shotgun, CShotgunAmmo ); + + diff --git a/dlls/cthulhu/shrivelling.cpp b/dlls/cthulhu/shrivelling.cpp new file mode 100755 index 00000000..f953b002 --- /dev/null +++ b/dlls/cthulhu/shrivelling.cpp @@ -0,0 +1,500 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "effects.h" +#include "customentity.h" +#include "gamerules.h" + +#include "Shrivelling.h" + +#define SHRIVELLING_PRIMARY_VOLUME 450 +#define SHRIVELLING_BEAM_SPRITE "sprites/xbeam1.spr" +#define SHRIVELLING_FLARE_SPRITE "sprites/XSpark1.spr" +#define SHRIVELLING_SOUND_OFF "weapons/egon_off1.wav" +#define SHRIVELLING_SOUND_RUN "weapons/egon_run3.wav" +#define SHRIVELLING_SOUND_STARTUP "weapons/egon_windup2.wav" + +#define SHRIVELLING_SWITCH_NARROW_TIME 0.75 // Time it takes to switch fire modes +#define SHRIVELLING_SWITCH_WIDE_TIME 1.5 + + +enum Shrivelling_e { + SHRIVELLING_OPEN = 0, + SHRIVELLING_IDLE1, + SHRIVELLING_IDLE2, + SHRIVELLING_IDLE3, + SHRIVELLING_CAST, + SHRIVELLING_CLOSE +}; + + +LINK_ENTITY_TO_CLASS( weapon_shrivelling, CShrivelling ); + +TYPEDESCRIPTION CShrivelling::m_SaveData[] = +{ + DEFINE_FIELD( CShrivelling, m_pBeam, FIELD_CLASSPTR ), + DEFINE_FIELD( CShrivelling, m_pNoise, FIELD_CLASSPTR ), + DEFINE_FIELD( CShrivelling, m_pSprite, FIELD_CLASSPTR ), + DEFINE_FIELD( CShrivelling, m_shootTime, FIELD_TIME ), + DEFINE_FIELD( CShrivelling, m_fireState, FIELD_INTEGER ), + DEFINE_FIELD( CShrivelling, m_fireMode, FIELD_INTEGER ), + DEFINE_FIELD( CShrivelling, m_shakeTime, FIELD_TIME ), + DEFINE_FIELD( CShrivelling, m_flAmmoUseTime, FIELD_TIME ), +}; +IMPLEMENT_SAVERESTORE( CShrivelling, CBasePlayerWeapon ); + + +void CShrivelling::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_SHRIVELLING; + SET_MODEL(ENT(pev), "models/w_shrivelling.mdl"); + m_iClip = -1; + + FallInit();// get ready to fall down. +} + + +void CShrivelling::Precache( void ) +{ + PRECACHE_MODEL("models/w_shrivelling.mdl"); + PRECACHE_MODEL("models/v_shrivelling.mdl"); +// PRECACHE_MODEL("models/p_shrivelling.mdl"); + + PRECACHE_MODEL("models/w_9mmclip.mdl"); + PRECACHE_SOUND("items/9mmclip1.wav"); + + PRECACHE_SOUND( SHRIVELLING_SOUND_OFF ); + PRECACHE_SOUND( SHRIVELLING_SOUND_RUN ); + PRECACHE_SOUND( SHRIVELLING_SOUND_STARTUP ); + + PRECACHE_MODEL( SHRIVELLING_BEAM_SPRITE ); + PRECACHE_MODEL( SHRIVELLING_FLARE_SPRITE ); + + PRECACHE_SOUND ("weapons/357_cock1.wav"); +} + + +BOOL CShrivelling::Deploy( void ) +{ + m_deployed = FALSE; + return DefaultDeploy( "models/v_shrivelling.mdl", "", SHRIVELLING_OPEN, "shrivelling" ); +} + +int CShrivelling::AddToPlayer( CBasePlayer *pPlayer ) +{ + if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) + { + MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); + WRITE_BYTE( m_iId ); + MESSAGE_END(); + return TRUE; + } + return FALSE; +} + + + +void CShrivelling::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + // m_flTimeWeaponIdle = gpGlobals->time + UTIL_RandomFloat ( 10, 15 ); + SendWeaponAnim( SHRIVELLING_CLOSE ); + + if ( m_fireState != FIRE_OFF ) + EndAttack(); +} + +int CShrivelling::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = NULL; + p->iMaxAmmo1 = -1; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 4; + p->iPosition = 1; + p->iId = m_iId = WEAPON_SHRIVELLING; + p->iFlags = 0; + p->iWeight = SHRIVELLING_WEIGHT; + + return 1; +} + + +//#define SHRIVELLING_PULSE_INTERVAL 0.25 +//#define SHRIVELLING_DISCHARGE_INTERVAL 0.5 + +#define SHRIVELLING_PULSE_INTERVAL 0.1 +#define SHRIVELLING_DISCHARGE_INTERVAL 0.1 + +float CShrivelling::GetPulseInterval( void ) +{ + if ( g_pGameRules->IsMultiplayer() ) + { + return 0.1; + } + + return SHRIVELLING_PULSE_INTERVAL; +} + +float CShrivelling::GetDischargeInterval( void ) +{ + if ( g_pGameRules->IsMultiplayer() ) + { + return 0.1; + } + + return SHRIVELLING_DISCHARGE_INTERVAL; +} + +void CShrivelling::Attack( void ) +{ + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecSrc = m_pPlayer->GetGunPosition( ); + + switch( m_fireState ) + { + case FIRE_OFF: + { + m_flAmmoUseTime = gpGlobals->time;// start using ammo ASAP. + + SendWeaponAnim( SHRIVELLING_CAST ); + m_shakeTime = 0; + + m_pPlayer->m_iWeaponVolume = SHRIVELLING_PRIMARY_VOLUME; + m_flTimeWeaponIdle = gpGlobals->time + 0.1; + m_shootTime = gpGlobals->time + 2; + + if ( m_fireMode == FIRE_WIDE ) + { + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, SHRIVELLING_SOUND_STARTUP, 0.98, ATTN_NORM, 0, 125 ); + } + else + { + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, SHRIVELLING_SOUND_STARTUP, 0.9, ATTN_NORM, 0, 100 ); + } + + pev->dmgtime = gpGlobals->time + GetPulseInterval(); + m_fireState = FIRE_CHARGE; + } + break; + + case FIRE_CHARGE: + { + Fire( vecSrc, vecAiming ); + m_pPlayer->m_iWeaponVolume = SHRIVELLING_PRIMARY_VOLUME; + + if ( m_shootTime != 0 && gpGlobals->time > m_shootTime ) + { + if ( m_fireMode == FIRE_WIDE ) + { + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_STATIC, SHRIVELLING_SOUND_RUN, 0.98, ATTN_NORM, 0, 125 ); + } + else + { + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_STATIC, SHRIVELLING_SOUND_RUN, 0.9, ATTN_NORM, 0, 100 ); + } + + m_shootTime = 0; + } + } + break; + } +} + +void CShrivelling::PrimaryAttack( void ) +{ + m_fireMode = FIRE_WIDE; + Attack(); +} + +void CShrivelling::Fire( const Vector &vecOrigSrc, const Vector &vecDir ) +{ + Vector vecDest = vecOrigSrc + vecDir * 2048; + edict_t *pentIgnore; + TraceResult tr; + + pentIgnore = m_pPlayer->edict(); + Vector tmpSrc = vecOrigSrc + gpGlobals->v_up * -8 + gpGlobals->v_right * 3; + + // ALERT( at_console, "." ); + + UTIL_TraceLine( vecOrigSrc, vecDest, dont_ignore_monsters, pentIgnore, &tr ); + + if (tr.fAllSolid) + return; + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + if (pEntity == NULL) + return; + + if ( g_pGameRules->IsMultiplayer() ) + { + if ( m_pSprite && pEntity->pev->takedamage ) + { + m_pSprite->pev->effects &= ~EF_NODRAW; + } + else if ( m_pSprite ) + { + m_pSprite->pev->effects |= EF_NODRAW; + } + } + + float timedist; + + switch ( m_fireMode ) + { + case FIRE_NARROW: + if ( pev->dmgtime < gpGlobals->time ) + { + // Narrow mode only does damage to the entity it hits + ClearMultiDamage(); + if (pEntity->pev->takedamage) + { + pEntity->TraceAttack( m_pPlayer->pev, gSkillData.plrDmgShrivellingNarrow, vecDir, &tr, DMG_ENERGYBEAM ); + } + ApplyMultiDamage(m_pPlayer->pev, m_pPlayer->pev); + + if ( g_pGameRules->IsMultiplayer() ) + { + // multiplayer uses 1 ammo every 1/10th second + if ( gpGlobals->time >= m_flAmmoUseTime ) + { + m_pPlayer->LoseSanity(1); + m_flAmmoUseTime = gpGlobals->time + 0.1; + } + } + else + { + // single player, use 3 ammo/second + if ( gpGlobals->time >= m_flAmmoUseTime ) + { + m_pPlayer->LoseSanity(1); + m_flAmmoUseTime = gpGlobals->time + 0.166; + } + } + + pev->dmgtime = gpGlobals->time + GetPulseInterval(); + } + timedist = ( pev->dmgtime - gpGlobals->time ) / GetPulseInterval(); + break; + + case FIRE_WIDE: + if ( pev->dmgtime < gpGlobals->time ) + { + // wide mode does damage to the ent, and radius damage + ClearMultiDamage(); + if (pEntity->pev->takedamage) + { + pEntity->TraceAttack( m_pPlayer->pev, gSkillData.plrDmgShrivellingWide, vecDir, &tr, DMG_ENERGYBEAM | DMG_ALWAYSGIB); + } + ApplyMultiDamage(m_pPlayer->pev, m_pPlayer->pev); + + if ( g_pGameRules->IsMultiplayer() ) + { + // radius damage a little more potent in multiplayer. + ::RadiusDamage( tr.vecEndPos, pev, m_pPlayer->pev, gSkillData.plrDmgShrivellingWide/4, 128, CLASS_NONE, DMG_ENERGYBEAM | DMG_BLAST | DMG_ALWAYSGIB ); + } + + if ( !m_pPlayer->IsAlive() ) + return; + + if ( g_pGameRules->IsMultiplayer() ) + { + //multiplayer uses 5 ammo/second + if ( gpGlobals->time >= m_flAmmoUseTime ) + { + m_pPlayer->LoseSanity(1); + m_flAmmoUseTime = gpGlobals->time + 0.2; + } + } + else + { + // Wide mode uses 10 charges per second in single player + if ( gpGlobals->time >= m_flAmmoUseTime ) + { + m_pPlayer->LoseSanity(1); + m_flAmmoUseTime = gpGlobals->time + 0.1; + } + } + + pev->dmgtime = gpGlobals->time + GetDischargeInterval(); + if ( m_shakeTime < gpGlobals->time ) + { + UTIL_ScreenShake( tr.vecEndPos, 5.0, 150.0, 0.75, 250.0 ); + m_shakeTime = gpGlobals->time + 1.5; + } + } + timedist = ( pev->dmgtime - gpGlobals->time ) / GetDischargeInterval(); + break; + } + + if ( timedist < 0 ) + timedist = 0; + else if ( timedist > 1 ) + timedist = 1; + timedist = 1-timedist; + + UpdateEffect( tmpSrc, tr.vecEndPos, timedist ); +} + + +void CShrivelling::UpdateEffect( const Vector &startPoint, const Vector &endPoint, float timeBlend ) +{ + if ( !m_pBeam ) + { + CreateEffect(); + } + + m_pBeam->SetStartPos( endPoint ); + m_pBeam->SetBrightness( 255 - (timeBlend*180) ); + m_pBeam->SetWidth( 40 - (timeBlend*20) ); + + if ( m_fireMode == FIRE_WIDE ) + m_pBeam->SetColor( 30 + (25*timeBlend), 30 + (30*timeBlend), 64 + 80*fabs(sin(gpGlobals->time*10)) ); + else + m_pBeam->SetColor( 60 + (25*timeBlend), 120 + (30*timeBlend), 64 + 80*fabs(sin(gpGlobals->time*10)) ); + + + UTIL_SetOrigin( m_pSprite, endPoint ); + m_pSprite->pev->frame += 8 * gpGlobals->frametime; + if ( m_pSprite->pev->frame > m_pSprite->Frames() ) + m_pSprite->pev->frame = 0; + + m_pNoise->SetStartPos( endPoint ); +} + + +void CShrivelling::CreateEffect( void ) +{ + DestroyEffect(); + + m_pBeam = CBeam::BeamCreate( SHRIVELLING_BEAM_SPRITE, 40 ); + m_pBeam->PointEntInit( pev->origin, m_pPlayer->entindex() ); + m_pBeam->SetFlags( BEAM_FSINE ); + m_pBeam->SetEndAttachment( 1 ); + m_pBeam->pev->spawnflags |= SF_BEAM_TEMPORARY; // Flag these to be destroyed on save/restore or level transition + + m_pNoise = CBeam::BeamCreate( SHRIVELLING_BEAM_SPRITE, 55 ); + m_pNoise->PointEntInit( pev->origin, m_pPlayer->entindex() ); + m_pNoise->SetScrollRate( 25 ); + m_pNoise->SetBrightness( 100 ); + m_pNoise->SetEndAttachment( 1 ); + m_pNoise->pev->spawnflags |= SF_BEAM_TEMPORARY; + + m_pSprite = CSprite::SpriteCreate( SHRIVELLING_FLARE_SPRITE, pev->origin, FALSE ); + m_pSprite->pev->scale = 1.0; + m_pSprite->SetTransparency( kRenderGlow, 255, 255, 255, 255, kRenderFxNoDissipation ); + m_pSprite->pev->spawnflags |= SF_SPRITE_TEMPORARY; + + if ( m_fireMode == FIRE_WIDE ) + { + m_pBeam->SetScrollRate( 50 ); + m_pBeam->SetNoise( 20 ); + m_pNoise->SetColor( 50, 255, 50 ); + m_pNoise->SetNoise( 8 ); + } + else + { + m_pBeam->SetScrollRate( 110 ); + m_pBeam->SetNoise( 5 ); + m_pNoise->SetColor( 80, 255, 120 ); + m_pNoise->SetNoise( 2 ); + } +} + + +void CShrivelling::DestroyEffect( void ) +{ + if ( m_pBeam ) + { + UTIL_Remove( m_pBeam ); + m_pBeam = NULL; + } + if ( m_pNoise ) + { + UTIL_Remove( m_pNoise ); + m_pNoise = NULL; + } + if ( m_pSprite ) + { + if ( m_fireMode == FIRE_WIDE ) + m_pSprite->Expand( 10, 500 ); + else + UTIL_Remove( m_pSprite ); + m_pSprite = NULL; + } +} + +void CShrivelling::WeaponIdle( void ) +{ + ResetEmptySound( ); + + if ( m_flTimeWeaponIdle > gpGlobals->time ) + return; + + if ( m_fireState != FIRE_OFF ) + EndAttack(); + + + int iAnim; + + float flRand = RANDOM_FLOAT(0,1); + + if ( flRand <= 0.4 ) + { + iAnim = SHRIVELLING_IDLE1; + m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT(2,3); + } + else if ( flRand <= 0.75 ) + { + iAnim = SHRIVELLING_IDLE2; + m_flTimeWeaponIdle = gpGlobals->time + 3; + } + else + { + iAnim = SHRIVELLING_IDLE3; + m_flTimeWeaponIdle = gpGlobals->time + 3; + } + + SendWeaponAnim( iAnim ); + m_deployed = TRUE; +} + +void CShrivelling::EndAttack( void ) +{ + STOP_SOUND( ENT(m_pPlayer->pev), CHAN_STATIC, SHRIVELLING_SOUND_RUN ); + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, SHRIVELLING_SOUND_OFF, 0.98, ATTN_NORM, 0, 100); + m_fireState = FIRE_OFF; + m_flTimeWeaponIdle = gpGlobals->time + 2.0; + m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->time + 0.5; + DestroyEffect(); +} + + + +#endif \ No newline at end of file diff --git a/dlls/cthulhu/spider.cpp b/dlls/cthulhu/spider.cpp new file mode 100755 index 00000000..33b75f32 --- /dev/null +++ b/dlls/cthulhu/spider.cpp @@ -0,0 +1,575 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// spider +//========================================================= + +// UNDONE: Don't flinch every time you get hit + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "effects.h" +#include "customentity.h" +#include "soundent.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define SPIDER_AE_ATTACK 0x01 + +#define SPIDER_FLINCH_DELAY 3 // at most one flinch every n secs + +//========================================================= +// monster-specific schedule types +//========================================================= +enum +{ + SCHED_SPIDER_REPEL = LAST_COMMON_SCHEDULE + 1, + SCHED_SPIDER_REPEL_LAND, +}; + +//========================================================= +// repel +//========================================================= +Task_t tlSpiderRepel[] = +{ + { TASK_STOP_MOVING, (float)0 }, + { TASK_FACE_IDEAL, (float)0 }, + { TASK_PLAY_SEQUENCE, (float)ACT_GLIDE }, +}; + +Schedule_t slSpiderRepel[] = +{ + { + tlSpiderRepel, + ARRAYSIZE ( tlSpiderRepel ), + 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 land +//========================================================= +Task_t tlSpiderRepelLand[] = +{ + { 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 slSpiderRepelLand[] = +{ + { + tlSpiderRepelLand, + ARRAYSIZE ( tlSpiderRepelLand ), + 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" + }, +}; + + +class CSpider : 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[]; + + virtual int GetVoicePitch( void ) { return 95 + RANDOM_LONG(0,9); } + virtual float GetSoundVolue( void ) { return 1.0; } + + // No range attacks + virtual BOOL CheckMeleeAttack1( float flDot, float flDist ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } + BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } + virtual float GetDamageAmount( void ) { return gSkillData.zombieDmgOneSlash; } + virtual float GetKnockback( void ) { return 200; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + + virtual int GetHeightFix(void) { return 0; }; + + Schedule_t *GetSchedule( void ); + Schedule_t *GetScheduleOfType ( int Type ); + CUSTOM_SCHEDULES; + +}; + +LINK_ENTITY_TO_CLASS( monster_giantspider, CSpider ); + +DEFINE_CUSTOM_SCHEDULES( CSpider ) +{ + slSpiderRepel, + slSpiderRepelLand, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CSpider, CBaseMonster ); + + +const char *CSpider::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CSpider::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CSpider::pAttackSounds[] = +{ + "zombie/zo_attack1.wav", + "zombie/zo_attack2.wav", +}; + +const char *CSpider::pIdleSounds[] = +{ + "zombie/zo_idle1.wav", + "zombie/zo_idle2.wav", + "zombie/zo_idle3.wav", + "zombie/zo_idle4.wav", +}; + +const char *CSpider::pAlertSounds[] = +{ + "zombie/zo_alert10.wav", + "zombie/zo_alert20.wav", + "zombie/zo_alert30.wav", +}; + +const char *CSpider::pPainSounds[] = +{ + "zombie/zo_pain1.wav", + "zombie/zo_pain2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CSpider :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CSpider :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + + pev->yaw_speed = ys; +} + +int CSpider :: 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 CSpider :: PainSound( void ) +{ + int pitch = GetVoicePitch(); + + if (RANDOM_LONG(0,5) < 3) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], GetSoundVolue(), ATTN_NORM, 0, pitch ); +} + +void CSpider :: AlertSound( void ) +{ + int pitch = GetVoicePitch(); + + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAlertSounds[ RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1) ], GetSoundVolue(), ATTN_NORM, 0, pitch ); +} + +void CSpider :: IdleSound( void ) +{ + int pitch = GetVoicePitch(); + + // Play a random idle sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pIdleSounds[ RANDOM_LONG(0,ARRAYSIZE(pIdleSounds)-1) ], GetSoundVolue(), ATTN_NORM, 0, pitch ); +} + +void CSpider :: AttackSound( void ) +{ + int pitch = GetVoicePitch(); + + // Play a random attack sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], GetSoundVolue(), ATTN_NORM, 0, pitch ); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CSpider :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SPIDER_AE_ATTACK: + { + int pitch = GetVoicePitch(); + + // do stuff for this event. + // Cthulhu: bug fix (hack) for origin being below ground! + pev->size.z += GetHeightFix(); + CBaseEntity *pHurt = CheckTraceHullAttack( 120, GetDamageAmount(), DMG_SLASH ); + pev->size.z -= GetHeightFix(); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.x = 5; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * -GetKnockback(); + } + // Play a random attack hit sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackHitSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackHitSounds)-1) ], GetSoundVolue(), ATTN_NORM, 0, pitch ); + } + else // Play a random attack miss sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_WEAPON, pAttackMissSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackMissSounds)-1) ], GetSoundVolue(), ATTN_NORM, 0, pitch ); + + if (RANDOM_LONG(0,1)) + AttackSound(); + } + break; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CSpider :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/monsters/spider.mdl"); + UTIL_SetSize( pev, Vector( -36, -36, 0 ), Vector( 36, 36, 76 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_GREEN; + if (pev->health == 0) + pev->health = gSkillData.zombieHealth * 2; + 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_JUMP | bits_CAP_CLIMB | bits_CAP_HEAR | bits_CAP_AUTO_DOORS | bits_CAP_MELEE_ATTACK1; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CSpider :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/monsters/spider.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 CSpider::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { + 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 + SPIDER_FLINCH_DELAY; + } + + return iIgnore; +} + +BOOL CSpider :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb) + //int iGround = FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ); + + // Cthulhu: but this stops the monster from hitting flying monsters that are + // melee attacking it (e.g. nightgaunt) + // Solution: explicitly check for monster types that we cannot hit + + BOOL bHit = TRUE; // we can hit by default + if (m_hEnemy) + { + if (FClassnameIs( m_hEnemy->pev, "hornet")) bHit = FALSE; + if (FClassnameIs( m_hEnemy->pev, "monster_snark")) bHit = FALSE; + } + + if ( flDist <= 80 && flDot >= 0.7 && m_hEnemy != NULL && bHit ) + { + return TRUE; + } + return FALSE; +} + +//========================================================= +// Get Schedule! +//========================================================= +Schedule_t *CSpider :: GetSchedule( void ) +{ + // 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_SPIDER_REPEL_LAND ); + } + else + { + // repel down a rope, + return GetScheduleOfType ( SCHED_SPIDER_REPEL ); + } + } + + return CBaseMonster::GetSchedule(); +} + +//========================================================= +Schedule_t* CSpider :: GetScheduleOfType ( int Type ) +{ + switch ( Type ) + { + case SCHED_SPIDER_REPEL: + { + if (pev->velocity.z > -128) + pev->velocity.z -= 32; + return &slSpiderRepel[ 0 ]; + } + case SCHED_SPIDER_REPEL_LAND: + { + return &slSpiderRepelLand[ 0 ]; + } + default: + { + return CBaseMonster :: GetScheduleOfType ( Type ); + } + } +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +//========================================================= +// CSpiderRepel - when triggered, spawns a monster_giantspider +// repelling down a web. +//========================================================= + +class CSpiderRepel : 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_spider_repel, CSpiderRepel ); + +void CSpiderRepel::Spawn( void ) +{ + Precache( ); + pev->solid = SOLID_NOT; + + SetUse( RepelUse ); +} + +void CSpiderRepel::Precache( void ) +{ + UTIL_PrecacheOther( "monster_giantspider" ); + m_iSpriteTexture = PRECACHE_MODEL( "sprites/rope.spr" ); +} + +void CSpiderRepel::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_giantspider", pev->origin, pev->angles ); + CBaseMonster *pSpider = pEntity->MyMonsterPointer( ); + pSpider->pev->movetype = MOVETYPE_FLY; + pSpider->pev->velocity = Vector( 0, 0, RANDOM_FLOAT( -196, -128 ) ); + pSpider->SetActivity( ACT_GLIDE ); + // UNDONE: position? + pSpider->m_vecLastPosition = tr.vecEndPos; + + CBeam *pBeam = CBeam::BeamCreate( "sprites/rope.spr", 10 ); + pBeam->PointEntInit( pev->origin + Vector(0,0,112), pSpider->entindex() ); + pBeam->SetEndAttachment( 1 ); + pBeam->SetFlags( BEAM_FSOLID ); + pBeam->SetColor( 255, 255, 255 ); + pBeam->SetThink( SUB_Remove ); + pBeam->SetNextThink( -4096.0 * tr.flFraction / pSpider->pev->velocity.z + 0.5 ); + + UTIL_Remove( this ); +} + +////////////////////////////////////////////////////////////////////////////////////////////////////// + +class CBabySpider : public CSpider +{ +public: + void Spawn( void ); + void Precache( void ); + + virtual int GetVoicePitch( void ) { return PITCH_NORM + RANDOM_LONG(40,50); } + virtual float GetSoundVolue( void ) { return 0.6; } + + virtual BOOL CheckMeleeAttack1( float flDot, float flDist ); + virtual float GetDamageAmount( void ) { return gSkillData.headcrabDmgBite * 0.5; } + virtual float GetKnockback( void ) { return 50; } + + virtual int GetHeightFix(void) { return 32; }; +}; + +LINK_ENTITY_TO_CLASS( monster_babygiantspider, CBabySpider ); + +void CBabySpider :: Spawn( void ) +{ + CSpider::Spawn(); + + SET_MODEL(ENT(pev), "models/monsters/babyspider.mdl"); + UTIL_SetSize(pev, Vector(-4, -4, 0), Vector(4, 4, 16)); + + // this is always true + pev->health = gSkillData.headcrabHealth; +} + +void CBabySpider :: Precache( void ) +{ + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL( "models/monsters/babyspider.mdl" ); + CSpider::Precache(); +} + +BOOL CBabySpider :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb) + //int iGround = FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ); + + // Cthulhu: but this stops the monster from hitting flying monsters that are + // melee attacking it (e.g. nightgaunt) + // Solution: explicitly check for monster types that we cannot hit + + BOOL bHit = TRUE; // we can hit by default + if (m_hEnemy) + { + if (FClassnameIs( m_hEnemy->pev, "hornet")) bHit = FALSE; + if (FClassnameIs( m_hEnemy->pev, "monster_snark")) bHit = FALSE; + } + + if ( flDist <= 48 && flDot >= 0.7 && m_hEnemy != NULL && bHit ) + { + return TRUE; + } + return FALSE; +} + diff --git a/dlls/cthulhu/stukagrenade.cpp b/dlls/cthulhu/stukagrenade.cpp new file mode 100755 index 00000000..643ba3a3 --- /dev/null +++ b/dlls/cthulhu/stukagrenade.cpp @@ -0,0 +1,278 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +/* + +===== generic grenade.cpp ======================================================== + +*/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" + +#include "stukagrenade.h" + + +//===================grenade + + +LINK_ENTITY_TO_CLASS( stuka_grenade, CStukaGrenade ); + +// +// Grenade Explode +// +void CStukaGrenade::Explode( Vector vecSrc, Vector vecAim ) +{ + TraceResult tr; + UTIL_TraceLine ( pev->origin, pev->origin + Vector ( 0, 0, -32 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + +// UNDONE: temporary scorching for PreAlpha - find a less sleazy permenant solution. +void CStukaGrenade::Explode( TraceResult *pTrace, int bitsDamageType ) +{ + float flRndSound;// sound randomizer + + pev->model = iStringNull;//invisible + pev->solid = SOLID_NOT;// intangible + + pev->takedamage = DAMAGE_NO; + + // Pull out of the wall a bit + if ( pTrace->flFraction != 1.0 ) + { + pev->origin = pTrace->vecEndPos + (pTrace->vecPlaneNormal * (pev->dmg - 24) * 0.6); + } + + int iContents = UTIL_PointContents ( pev->origin ); + + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + WRITE_COORD( pev->origin.x ); // Send to PAS because of the sound + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + if (iContents != CONTENTS_WATER) + { + WRITE_SHORT( g_sModelIndexFireball ); + } + else + { + WRITE_SHORT( g_sModelIndexWExplosion ); + } + WRITE_BYTE( 20 ); // scale * 10 + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); + entvars_t *pevOwner; + if ( pev->owner ) + pevOwner = VARS( pev->owner ); + else + pevOwner = NULL; + + pev->owner = NULL; // can't traceline attack owner if this is set + + RadiusDamage ( pev, pevOwner, pev->dmg, CLASS_NONE, bitsDamageType ); + + if ( RANDOM_FLOAT( 0 , 1 ) < 0.5 ) + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH1 ); + } + else + { + UTIL_DecalTrace( pTrace, DECAL_SCORCH2 ); + } + + flRndSound = RANDOM_FLOAT( 0 , 1 ); + + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris1.wav", 0.35, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris2.wav", 0.35, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/debris3.wav", 0.35, ATTN_NORM); break; + } + + pev->effects |= EF_NODRAW; + SetThink( Smoke ); + pev->velocity = g_vecZero; + SetNextThink( 0.3 ); + + if (iContents != CONTENTS_WATER) + { + int sparkCount = RANDOM_LONG(0,3); + for ( int i = 0; i < sparkCount; i++ ) + Create( "spark_shower", pev->origin, pTrace->vecPlaneNormal, NULL ); + } +} + + +void CStukaGrenade::Smoke( void ) +{ + if (UTIL_PointContents ( pev->origin ) == CONTENTS_WATER) + { + UTIL_Bubbles( pev->origin - Vector( 64, 64, 64 ), pev->origin + Vector( 64, 64, 64 ), 100 ); + } + else + { + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_SMOKE ); + WRITE_COORD( pev->origin.x ); + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexSmoke ); + WRITE_BYTE( 25 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + MESSAGE_END(); + } + UTIL_Remove( this ); +} + +void CStukaGrenade::Killed( entvars_t *pevAttacker, int iGib ) +{ + Detonate( ); +} + +void CStukaGrenade::PreDetonate( void ) +{ + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin, 400, 0.3 ); + + SetThink( Detonate ); + SetNextThink( 1 ); +} + + +void CStukaGrenade::Detonate( void ) +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + vecSpot = pev->origin + Vector ( 0 , 0 , 8 ); + UTIL_TraceLine ( vecSpot, vecSpot + Vector ( 0, 0, -40 ), ignore_monsters, ENT(pev), & tr); + + Explode( &tr, DMG_BLAST ); +} + + +// +// Contact grenade, explode when it touches something +// +void CStukaGrenade::ExplodeTouch( CBaseEntity *pOther ) +{ + TraceResult tr; + Vector vecSpot;// trace starts here! + + pev->enemy = pOther->edict(); + + vecSpot = pev->origin - pev->velocity.Normalize() * 32; + UTIL_TraceLine( vecSpot, vecSpot + pev->velocity.Normalize() * 64, ignore_monsters, ENT(pev), &tr ); + + Explode( &tr, DMG_BLAST ); +} + +void CStukaGrenade :: TumbleThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if (pev->dmgtime - 1 < gpGlobals->time) + { + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * (pev->dmgtime - gpGlobals->time), 400, 0.1 ); + } + + if (pev->dmgtime <= gpGlobals->time) + { + SetThink( Detonate ); + } + if (pev->waterlevel != 0 && pev->watertype > CONTENT_FLYFIELD) + { + pev->velocity = pev->velocity * 0.5; + pev->framerate = 0.2; + } +} + + +void CStukaGrenade:: Spawn( void ) +{ + pev->movetype = MOVETYPE_BOUNCE; + pev->classname = MAKE_STRING( "stuka_grenade" ); + + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/stickygib.mdl"); + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + + pev->dmg = 25; +} + + +CStukaGrenade *CStukaGrenade::ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ) +{ + CStukaGrenade *pGrenade = GetClassPtr( (CStukaGrenade *)NULL ); + pGrenade->Spawn(); + // contact grenades arc lower + pGrenade->pev->gravity = 0.5;// lower gravity since grenade is aerodynamic and engine doesn't know it. + UTIL_SetOrigin( pGrenade, vecStart ); + pGrenade->pev->velocity = vecVelocity; + pGrenade->pev->angles = UTIL_VecToAngles (pGrenade->pev->velocity); + pGrenade->pev->owner = ENT(pevOwner); + + // make monsters afaid of it while in the air + pGrenade->SetThink( DangerSoundThink ); + pGrenade->SetNextThink( 0 ); + + // Tumble in air + pGrenade->pev->avelocity.x = RANDOM_FLOAT ( -100, -500 ); + + // Explode on contact + pGrenade->SetTouch( ExplodeTouch ); + + pGrenade->pev->dmg = 25; + + return pGrenade; +} + +void CStukaGrenade::DangerSoundThink( void ) +{ + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + CSoundEnt::InsertSound ( bits_SOUND_DANGER, pev->origin + pev->velocity * 0.5, pev->velocity.Length( ), 0.2 ); + SetNextThink( 0.2 ); + + if (pev->waterlevel != 0 && pev->watertype > CONTENT_FLYFIELD) + { + pev->velocity = pev->velocity * 0.5; + } +} + + +//======================end grenade + diff --git a/dlls/cthulhu/stukagrenade.h b/dlls/cthulhu/stukagrenade.h new file mode 100755 index 00000000..d6a699da --- /dev/null +++ b/dlls/cthulhu/stukagrenade.h @@ -0,0 +1,29 @@ + +#ifndef STUKAGRENADE_H +#define STUKAGRENADE_H + +#include "effects.h" + +// Contact Grenade +class CStukaGrenade : public CBaseMonster +{ +public: + void Spawn( void ); + + static CStukaGrenade *ShootContact( entvars_t *pevOwner, Vector vecStart, Vector vecVelocity ); + + void Explode( Vector vecSrc, Vector vecAim ); + void Explode( TraceResult *pTrace, int bitsDamageType ); + void EXPORT Smoke( void ); + + void EXPORT ExplodeTouch( CBaseEntity *pOther ); + void EXPORT PreDetonate( void ); + void EXPORT Detonate( void ); + void EXPORT TumbleThink( void ); + void EXPORT DangerSoundThink( void ); + + virtual int BloodColor( void ) { return DONT_BLEED; } + virtual void Killed( entvars_t *pevAttacker, int iGib ); +}; + +#endif diff --git a/dlls/cthulhu/szlachta.cpp b/dlls/cthulhu/szlachta.cpp new file mode 100755 index 00000000..889e41fc --- /dev/null +++ b/dlls/cthulhu/szlachta.cpp @@ -0,0 +1,317 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Szlachta +//========================================================= + +// 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 SZLACHTA_AE_ATTACK_BITE 0x01 +#define SZLACHTA_AE_ATTACK_SPEAR 0x02 + +#define SZLACHTA_FLINCH_DELAY 5 // at most one flinch every n secs + +class CSzlachta : 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_szlachta, CSzlachta ); + +const char *CSzlachta::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CSzlachta::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CSzlachta::pAttackSounds[] = +{ + "zombie/zo_attack1.wav", + "zombie/zo_attack2.wav", +}; + +const char *CSzlachta::pIdleSounds[] = +{ + "zombie/zo_idle1.wav", + "zombie/zo_idle2.wav", + "zombie/zo_idle3.wav", + "zombie/zo_idle4.wav", +}; + +const char *CSzlachta::pAlertSounds[] = +{ + "zombie/zo_alert10.wav", + "zombie/zo_alert20.wav", + "zombie/zo_alert30.wav", +}; + +const char *CSzlachta::pPainSounds[] = +{ + "zombie/zo_pain1.wav", + "zombie/zo_pain2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CSzlachta :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CSzlachta :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + + pev->yaw_speed = ys; +} + +int CSzlachta :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Take 50% 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.5; + } + + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CSzlachta :: PainSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + if (RANDOM_LONG(0,5) < 3) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], 1.0, ATTN_NORM, 0, pitch ); +} + +void CSzlachta :: 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 CSzlachta :: 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 CSzlachta :: 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 CSzlachta :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case SZLACHTA_AE_ATTACK_BITE: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.zombieDmgOneSlash, 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 * -50; + } + 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 SZLACHTA_AE_ATTACK_SPEAR: + { + // 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 CSzlachta :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/monsters/szlachta.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; + + if (pev->health == 0) + pev->health = gSkillData.zombieHealth * 1.5; + 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 CSzlachta :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/monsters/szlachta.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 CSzlachta::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { + 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 + SZLACHTA_FLINCH_DELAY; + } + + return iIgnore; +} + + + + diff --git a/dlls/cthulhu/teleport.cpp b/dlls/cthulhu/teleport.cpp new file mode 100755 index 00000000..f7fc2bc5 --- /dev/null +++ b/dlls/cthulhu/teleport.cpp @@ -0,0 +1,622 @@ +/*** +* +* Copyright (c) 1996-2001, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#include "gamerules.h" +#include "soundent.h" + +enum teleport_e { + TELEPORT_IDLE1 = 0, + TELEPORT_FIDGET1, + TELEPORT_DRAW, + TELEPORT_DROP +}; + +enum teleport_radio_e { + TELEPORT_RADIO_IDLE1 = 0, + TELEPORT_RADIO_FIDGET1, + TELEPORT_RADIO_DRAW, + TELEPORT_RADIO_FIRE, + TELEPORT_RADIO_HOLSTER +}; + + + +class CTeleportCharge : public CGrenade +{ + void Spawn( void ); + void Precache( void ); + void BounceSound( void ); + + void EXPORT TeleportSlide( CBaseEntity *pOther ); + void EXPORT TeleportThink( void ); + +public: + void Destroy(); + void Deactivate( void ); +}; +LINK_ENTITY_TO_CLASS( monster_teleport, CTeleportCharge ); + +class CTeleport : public CBasePlayerWeapon +{ +public: + +#ifndef CLIENT_DLL + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; +#endif + + + void Spawn( void ); + void Precache( void ); + int iItemSlot( void ) { return 3; } + int GetItemInfo(ItemInfo *p); + int AddToPlayer( CBasePlayer *pPlayer ); + void PrimaryAttack( void ); + void SecondaryAttack( void ); + int AddDuplicate( CBasePlayerItem *pOriginal ); + BOOL CanDeploy( void ); + BOOL Deploy( void ); + BOOL IsUseable( void ); + + void Holster( int skiplocal = 0 ); + void WeaponIdle( void ); + void Throw( void ); + + virtual BOOL UseDecrement( void ) + { +#if defined( CLIENT_WEAPONS ) + return TRUE; +#else + return FALSE; +#endif + } +}; + + +void CTeleportCharge::Destroy( void ) +{ + MESSAGE_BEGIN( MSG_PAS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION ); // This makes a dynamic light and the explosion sprites/sound + WRITE_COORD( pev->origin.x ); // Send to PAS because of the sound + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( 5 ); + WRITE_BYTE( 15 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, SMALL_EXPLOSION_VOLUME, 3.0 ); + + Deactivate(); +} + +//========================================================= +// Deactivate - do whatever it is we do to an orphaned +// Teleport when we don't want it in the world anymore. +//========================================================= +void CTeleportCharge::Deactivate( void ) +{ + pev->solid = SOLID_NOT; + UTIL_Remove( this ); +} + + +void CTeleportCharge :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_BOUNCE; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/w_satchel.mdl"); + UTIL_SetSize(pev, Vector( -16, -16, -4), Vector(16, 16, 4)); // Old box -- size of headcrab monsters/players get blocked by this + UTIL_SetOrigin( this, pev->origin ); + + SetTouch( TeleportSlide ); + SetUse( DetonateUse ); + SetThink( TeleportThink ); + SetNextThink( 0.1 ); + + pev->gravity = 0.5; + pev->friction = 0.8; + + // hardcoded small value + pev->dmg = 10; + + // ResetSequenceInfo( ); + pev->sequence = 1; +} + + +void CTeleportCharge::TeleportSlide( CBaseEntity *pOther ) +{ + entvars_t *pevOther = pOther->pev; + + // don't hit the guy that launched this grenade + if ( pOther->edict() == pev->owner ) + return; + + // pev->avelocity = Vector (300, 300, 300); + pev->gravity = 1;// normal gravity now + + // HACKHACK - On ground isn't always set, so look for ground underneath + TraceResult tr; + UTIL_TraceLine( pev->origin, pev->origin - Vector(0,0,10), ignore_monsters, edict(), &tr ); + + if ( tr.flFraction < 1.0 ) + { + // add a bit of static friction + pev->velocity = pev->velocity * 0.95; + pev->avelocity = pev->avelocity * 0.9; + // play sliding sound, volume based on velocity + } + if ( !(pev->flags & FL_ONGROUND) && pev->velocity.Length2D() > 10 ) + { + BounceSound(); + } + StudioFrameAdvance( ); +} + + +void CTeleportCharge :: TeleportThink( void ) +{ + StudioFrameAdvance( ); + SetNextThink( 0.1 ); + + if (!IsInWorld()) + { + UTIL_Remove( this ); + return; + } + + if (pev->waterlevel == 3 && pev->watertype != CONTENT_FOG) + { + pev->movetype = MOVETYPE_FLY; + pev->velocity = pev->velocity * 0.8; + pev->avelocity = pev->avelocity * 0.9; + pev->velocity.z += 8; + } + else if (pev->waterlevel == 0 || pev->watertype == CONTENT_FOG) + { + pev->movetype = MOVETYPE_BOUNCE; + } + else + { + pev->velocity.z -= 8; + } +} + +void CTeleportCharge :: Precache( void ) +{ + PRECACHE_MODEL("models/grenade.mdl"); + PRECACHE_SOUND("weapons/g_bounce1.wav"); + PRECACHE_SOUND("weapons/g_bounce2.wav"); + PRECACHE_SOUND("weapons/g_bounce3.wav"); +} + +void CTeleportCharge :: BounceSound( void ) +{ + switch ( RANDOM_LONG( 0, 2 ) ) + { + case 0: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/g_bounce1.wav", 1, ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/g_bounce2.wav", 1, ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(pev), CHAN_VOICE, "weapons/g_bounce3.wav", 1, ATTN_NORM); break; + } +} + + +LINK_ENTITY_TO_CLASS( weapon_teleport, CTeleport ); + + +TYPEDESCRIPTION CTeleport::m_SaveData[] = +{ + DEFINE_FIELD( CTeleport, m_chargeReady, FIELD_INTEGER ), +}; +IMPLEMENT_SAVERESTORE( CTeleport, CBasePlayerWeapon ); + +//========================================================= +// CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal +//========================================================= +int CTeleport::AddDuplicate( CBasePlayerItem *pOriginal ) +{ + CTeleport *pTeleport; + +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + pTeleport = (CTeleport *)pOriginal; + + if ( pTeleport->m_chargeReady != 0 ) + { + // player has some teleports deployed. Refuse to add more. + return FALSE; + } + } + + return CBasePlayerWeapon::AddDuplicate ( pOriginal ); +} + +//========================================================= +//========================================================= +int CTeleport::AddToPlayer( CBasePlayer *pPlayer ) +{ + int bResult = CBasePlayerItem::AddToPlayer( pPlayer ); + + // cthulhu + m_iPrimaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo1() ); + + pPlayer->pev->weapons |= (1<pszName = STRING(pev->classname); + p->pszAmmo1 = "Teleport Pad"; + p->iMaxAmmo1 = TELEPORT_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = WEAPON_NOCLIP; + p->iSlot = 3; + p->iPosition = 3; + p->iFlags = ITEM_FLAG_SELECTONEMPTY | ITEM_FLAG_LIMITINWORLD | ITEM_FLAG_EXHAUSTIBLE; + p->iId = m_iId = WEAPON_TELEPORT; + p->iWeight = TELEPORT_WEIGHT; + + return 1; +} + +//========================================================= +//========================================================= +BOOL CTeleport::IsUseable( void ) +{ + if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] > 0 ) + { + // player is carrying some teleports + return TRUE; + } + + if ( m_chargeReady != 0 ) + { + // player isn't carrying any teleports, but has some out + return TRUE; + } + + return FALSE; +} + +BOOL CTeleport::CanDeploy( void ) +{ + if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] > 0 ) + { + // player is carrying some teleports + return TRUE; + } + + if ( m_chargeReady != 0 ) + { + // player isn't carrying any teleports, but has some out + return TRUE; + } + + return FALSE; +} + +BOOL CTeleport::Deploy( ) +{ + + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + + if ( m_chargeReady ) + return DefaultDeploy( "models/v_satchel_radio.mdl", "models/p_satchel_radio.mdl", TELEPORT_RADIO_DRAW, "hive" ); + else + return DefaultDeploy( "models/v_satchel.mdl", "models/p_satchel.mdl", TELEPORT_DRAW, "trip" ); + + + return TRUE; +} + + +void CTeleport::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + + if ( m_chargeReady ) + { + SendWeaponAnim( TELEPORT_RADIO_HOLSTER ); + } + else + { + SendWeaponAnim( TELEPORT_DROP ); + } + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "common/null.wav", 1.0, ATTN_NORM); + + if ( !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] && !m_chargeReady ) + { + m_pPlayer->pev->weapons &= ~(1<edict( ); + + // get the teleport pad + CBaseEntity *pTeleport = NULL; + + while ((pTeleport = UTIL_FindEntityInSphere( pTeleport, m_pPlayer->pev->origin, 4096 )) != NULL) + { + if (FClassnameIs( pTeleport->pev, "monster_teleport")) + { + if (pTeleport->pev->owner == pPlayer) + { + // get its position + vOldOrigin = m_pPlayer->pev->origin; + + // move our position (if possible) + m_pPlayer->pev->origin = pTeleport->pev->origin; + m_pPlayer->pev->origin.z += 40; + if (!WALK_MOVE ( ENT(m_pPlayer->pev), 0,1, WALKMOVE_NORMAL )) + { + m_pPlayer->pev->origin = vOldOrigin; + } + else + { + CEnvWarpBall* pWarpIn = (CEnvWarpBall*)CBaseEntity::Create("env_warpball",m_pPlayer->pev->origin,Vector(0,0,0),pPlayer); + pWarpIn->pev->frags = 8; // num of beams + pWarpIn->pev->health = 128; // max length of beam + pWarpIn->Use(NULL,NULL,USE_TOGGLE,0); + + CEnvWarpBall* pWarpOut = (CEnvWarpBall*)CBaseEntity::Create("env_warpball",vOldOrigin,Vector(0,0,0),pPlayer); + pWarpOut->pev->frags = 8; // num of beams + pWarpOut->pev->health = 128; // max length of beam + pWarpOut->Use(NULL,NULL,USE_TOGGLE,0); + } + + break; + } + } + } + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5; + } + break; + + case 2: + // we're reloading, don't allow fire + { + } + break; + } +} + + +void CTeleport::SecondaryAttack( void ) +{ + switch (m_chargeReady) + { + case 0: + { + // we don't do anything if we are ready to throw + } + break; + case 1: + { + SendWeaponAnim( TELEPORT_RADIO_FIRE ); + + edict_t *pPlayer = m_pPlayer->edict( ); + + CBaseEntity *pTeleport = NULL; + + while ((pTeleport = UTIL_FindEntityInSphere( pTeleport, m_pPlayer->pev->origin, 4096 )) != NULL) + { + if (FClassnameIs( pTeleport->pev, "monster_teleport")) + { + if (pTeleport->pev->owner == pPlayer) + { + //pTeleport->Use( m_pPlayer, m_pPlayer, USE_ON, 0 ); + ((CTeleportCharge*)pTeleport)->Destroy(); + m_chargeReady = 2; + } + } + } + + m_chargeReady = 2; + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.5; + break; + } + + case 2: + // we're reloading, don't allow fire + { + } + break; + } +} + + +void CTeleport::Throw( void ) +{ + if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + Vector vecSrc = m_pPlayer->pev->origin; + + Vector vecThrow = gpGlobals->v_forward * 274 + m_pPlayer->pev->velocity; + +#ifndef CLIENT_DLL + CBaseEntity *pTeleport = Create( "monster_teleport", vecSrc, Vector( 0, 0, 0), m_pPlayer->edict() ); + pTeleport->pev->velocity = vecThrow; + pTeleport->pev->avelocity.y = 400; + + m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_satchel_radio.mdl"); + m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_satchel_radio.mdl"); +#else + LoadVModel ( "models/v_satchel_radio.mdl", m_pPlayer ); +#endif + + SendWeaponAnim( TELEPORT_RADIO_DRAW ); + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + + m_chargeReady = 1; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 1.0; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + } +} + + +void CTeleport::WeaponIdle( void ) +{ + if ( m_flTimeWeaponIdle > UTIL_WeaponTimeBase() ) + return; + + switch( m_chargeReady ) + { + case 0: + SendWeaponAnim( TELEPORT_FIDGET1 ); + // use tripmine animations + strcpy( m_pPlayer->m_szAnimExtention, "trip" ); + break; + case 1: + SendWeaponAnim( TELEPORT_RADIO_FIDGET1 ); + // use hivehand animations + strcpy( m_pPlayer->m_szAnimExtention, "hive" ); + break; + case 2: + if ( !m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] ) + { + m_chargeReady = 0; + RetireWeapon(); + return; + } + +#ifndef CLIENT_DLL + m_pPlayer->pev->viewmodel = MAKE_STRING("models/v_satchel.mdl"); + m_pPlayer->pev->weaponmodel = MAKE_STRING("models/p_satchel.mdl"); +#else + LoadVModel ( "models/v_satchel.mdl", m_pPlayer ); +#endif + + SendWeaponAnim( TELEPORT_DRAW ); + + // use tripmine animations + strcpy( m_pPlayer->m_szAnimExtention, "trip" ); + + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 0.5; + m_chargeReady = 0; + break; + } + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 );// how long till we do this again. +} + +//========================================================= +// DeactivateTeleports - removes all Teleports owned by +// the provided player. Should only be used upon death. +// +// Made this global on purpose. +//========================================================= +void DeactivateTeleports( CBasePlayer *pOwner ) +{ + edict_t *pFind; + + pFind = FIND_ENTITY_BY_CLASSNAME( NULL, "monster_teleport" ); + + while ( !FNullEnt( pFind ) ) + { + CBaseEntity *pEnt = CBaseEntity::Instance( pFind ); + CTeleportCharge *pTeleport = (CTeleportCharge *)pEnt; + + if ( pTeleport ) + { + if ( pTeleport->pev->owner == pOwner->edict() ) + { + pTeleport->Deactivate(); + } + } + + pFind = FIND_ENTITY_BY_CLASSNAME( pFind, "monster_teleport" ); + } +} + +#endif \ No newline at end of file diff --git a/dlls/cthulhu/tindalos.cpp b/dlls/cthulhu/tindalos.cpp new file mode 100755 index 00000000..603fdacb --- /dev/null +++ b/dlls/cthulhu/tindalos.cpp @@ -0,0 +1,336 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Tindalos +//========================================================= + +// UNDONE: Don't flinch every time you get hit + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "soundent.h" +#include "effects.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define TINDALOS_AE_ATTACK 0x01 + +#define TINDALOS_FLINCH_DELAY 4 // at most one flinch every n secs + +#define TINDALOS_SPRITE_NAME "sprites/ballsmoke.spr" + + +#include "tindalos.h" + + +LINK_ENTITY_TO_CLASS( monster_tindalos, CTindalos ); + +TYPEDESCRIPTION CTindalos::m_SaveData[] = +{ + DEFINE_ARRAY( CTindalos, m_pSmoke, FIELD_CLASSPTR, 4 ), +}; + +IMPLEMENT_SAVERESTORE( CTindalos, CBaseMonster ); + +const char *CTindalos::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CTindalos::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CTindalos::pAttackSounds[] = +{ + "ghoul/gh_attack1.wav", +// "ghoul/gh_attack2.wav", +}; + +const char *CTindalos::pIdleSounds[] = +{ + "ghoul/gh_idle1.wav", + "ghoul/gh_idle2.wav", + "ghoul/gh_idle3.wav", +// "ghoul/gh_idle4.wav", +}; + +const char *CTindalos::pAlertSounds[] = +{ + "ghoul/gh_alert1.wav", +// "ghoul/gh_alert20.wav", +// "ghoul/gh_alert30.wav", +}; + +const char *CTindalos::pPainSounds[] = +{ + "ghoul/gh_pain1.wav", +// "ghoul/gh_pain2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CTindalos :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// 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 CTindalos :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_CARCASS | + bits_SOUND_MEAT | + bits_SOUND_GARBAGE | + bits_SOUND_PLAYER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CTindalos :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + + pev->yaw_speed = ys; +} + +void CTindalos::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + if (bitsDamageType & DMG_BULLET) return; + if (bitsDamageType & DMG_CRUSH) return; + if (bitsDamageType & DMG_SLASH) return; + if (bitsDamageType & DMG_FALL) return; + if (bitsDamageType & DMG_CLUB) return; + + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); +} + +int CTindalos :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if (bitsDamageType & DMG_BULLET) return 0; + if (bitsDamageType & DMG_CRUSH) return 0; + if (bitsDamageType & DMG_SLASH) return 0; + if (bitsDamageType & DMG_FALL) return 0; + if (bitsDamageType & DMG_CLUB) return 0; + + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CTindalos :: 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 CTindalos :: 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 CTindalos :: 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) ], 0.2, ATTN_NORM, 0, pitch ); +} + +void CTindalos :: AttackSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + // Play a random attack sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 0.5, ATTN_NORM, 0, pitch ); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CTindalos :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case TINDALOS_AE_ATTACK: + { + // do stuff for this event. + // ALERT( at_console, "Slash right!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.bullsquidDmgBite, DMG_POISON ); + 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; + } + // 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; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CTindalos :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/monsters/tindalos.mdl"); + UTIL_SetSize( pev, Vector( -24, -24, 0 ), Vector( 24, 24, 36 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = DONT_BLEED; + if (pev->health == 0) + pev->health = gSkillData.houndeyeHealth * 2.5; // use this one + 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 = 0; + + pev->rendermode = kRenderTransTexture; + pev->renderamt = 48; + + SetBits(pev->spawnflags, SF_MONSTER_FADECORPSE); + + MonsterInit(); + + Vector vecPosition; + Vector vecJunk; + + for (int i = 0; i < 4; i++) + { + GetAttachment( i, vecPosition, vecJunk ); + + m_pSmoke[i] = CSprite::SpriteCreate( TINDALOS_SPRITE_NAME, vecPosition, TRUE ); + m_pSmoke[i]->SetTransparency( kRenderTransAdd, 255, 255, 255, 24, kRenderFxNone ); + m_pSmoke[i]->SetAttachment( edict(), i+1 ); + m_pSmoke[i]->pev->scale = 1.0; + m_pSmoke[i]->pev->frame = RANDOM_FLOAT(0,10); + m_pSmoke[i]->pev->framerate = RANDOM_FLOAT(5,15); + m_pSmoke[i]->TurnOn(); + } +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CTindalos :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/monsters/tindalos.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 CTindalos::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { + 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 + TINDALOS_FLINCH_DELAY; + } + + return iIgnore; + +} + +void CTindalos :: Killed( entvars_t *pevAttacker, int iGib ) +{ + for (int i = 0; i < 4; i++) + { + UTIL_Remove( m_pSmoke[i] ); + m_pSmoke[i] = NULL; + } + + CBaseMonster::Killed(pevAttacker, iGib); +} + diff --git a/dlls/cthulhu/tindalos.h b/dlls/cthulhu/tindalos.h new file mode 100755 index 00000000..2e2792fa --- /dev/null +++ b/dlls/cthulhu/tindalos.h @@ -0,0 +1,50 @@ + +#ifndef TINDALOS_H +#define TINDALOS_H + +class CTindalos : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int IgnoreConditions ( void ); + virtual int ISoundMask( void ); + void Killed( entvars_t *pevAttacker, int iGib ); + + 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[]; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + // No range attacks + BOOL CheckRangeAttack1 ( float flDot, float flDist ) { return FALSE; } + BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } + 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 ); + +private: + CSprite* m_pSmoke[4]; + +}; + + + +#endif + + diff --git a/dlls/cthulhu/triggers.h b/dlls/cthulhu/triggers.h new file mode 100755 index 00000000..4a4cc2a4 --- /dev/null +++ b/dlls/cthulhu/triggers.h @@ -0,0 +1,671 @@ + +#ifndef TRIGGERS_H +#define TRIGGERS_H + +class CFrictionModifier : public CBaseEntity +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT ChangeFriction( CBaseEntity *pOther ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + static TYPEDESCRIPTION m_SaveData[]; + + float m_frictionFraction; // Sorry, couldn't resist this name :) +}; + +/////////////////////////////////////////////////////////////// + +class CAutoTrigger : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); + void Think( void ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_globalstate; + USE_TYPE triggerType; +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerRelay : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + USE_TYPE m_triggerType; + int m_sMaster; + string_t m_iszAltTarget; +}; + +//********************************************************** +// The Multimanager Entity - when fired, will fire up to 16 targets +// at specified times. +// FLAG: THREAD (create clones when triggered) +// FLAG: CLONE (this is a clone for a threaded execution) + +#define SF_MULTIMAN_CLONE 0x80000000 +#define SF_MULTIMAN_SAMETRIG 0x40000000 +#define SF_MULTIMAN_TRIGCHOSEN 0x20000000 + +#define SF_MULTIMAN_THREAD 0x00000001 +#define SF_MULTIMAN_LOOP 0x00000004 +#define SF_MULTIMAN_ONLYONCE 0x00000008 +#define SF_MULTIMAN_SPAWNFIRE 0x00000010 +#define SF_MULTIMAN_DEBUG 0x00000020 + +#define MM_MODE_CHOOSE 1 +#define MM_MODE_PERCENT 2 +#define MM_MODE_SIMULTANEOUS 3 + +class CMultiManager : public CBaseEntity//Toggle +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn ( void ); + void EXPORT UseThink ( void ); + void EXPORT ManagerThink ( void ); + void EXPORT ManagerUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + +#if _DEBUG + void EXPORT ManagerReport( void ); +#endif + + BOOL HasTarget( string_t targetname ); + + int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + STATE m_iState; + virtual STATE GetState( void ) { return m_iState; }; + + int m_cTargets; // the total number of targets in this manager's fire list. + int m_index; // Current target + float m_startTime;// Time we started firing + int m_iTargetName [ MAX_MULTI_TARGETS ];// list if indexes into global string array + float m_flTargetDelay [ MAX_MULTI_TARGETS ];// delay (in seconds) from time of manager fire to target fire + + float m_flWait; //LRC- minimum length of time to wait + float m_flMaxWait; //LRC- random, maximum length of time to wait + string_t m_sMaster; //LRC- master + int m_iMode; //LRC- 0 = timed, 1 = pick random, 2 = each random + int m_iszThreadName; //LRC + int m_iszLocusThread; //LRC + + EHANDLE m_hActivator; +private: + USE_TYPE m_triggerType; //LRC + inline BOOL IsClone( void ) { return (pev->spawnflags & SF_MULTIMAN_CLONE) ? TRUE : FALSE; } + inline BOOL ShouldClone( void ) + { + if ( IsClone() ) + return FALSE; + + return (pev->spawnflags & SF_MULTIMAN_THREAD) ? TRUE : FALSE; + } + + CMultiManager *Clone( void ); +}; + +/////////////////////////////////////////////////////////////////////////////// + +#define SF_SWATCHER_SENDTOGGLE 0x1 +#define SF_SWATCHER_DONTSEND_ON 0x2 +#define SF_SWATCHER_DONTSEND_OFF 0x4 +#define SF_SWATCHER_NOTON 0x8 +#define SF_SWATCHER_OFF 0x10 +#define SF_SWATCHER_TURN_ON 0x20 +#define SF_SWATCHER_TURN_OFF 0x40 +#define SF_SWATCHER_IN_USE 0x80 +#define SF_SWATCHER_VALID 0x200 + +#define SWATCHER_LOGIC_AND 0 +#define SWATCHER_LOGIC_OR 1 +#define SWATCHER_LOGIC_NAND 2 +#define SWATCHER_LOGIC_NOR 3 +#define SWATCHER_LOGIC_XOR 4 +#define SWATCHER_LOGIC_XNOR 5 + +class CStateWatcher : public CBaseToggle +{ +public: + void Spawn ( void ); + void EXPORT Think ( void ); + void KeyValue( KeyValueData *pkvd ); + virtual STATE GetState( void ); + virtual STATE GetState( CBaseEntity *pActivator ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + int m_fLogic; // Logic by which to combine the targets + int m_cTargets; // the total number of targets in this manager's fire list. + int m_iTargetName [ MAX_MULTI_TARGETS ];// list of indexes into global string array +// CBaseEntity* m_pTargetEnt [ MAX_MULTI_TARGETS ]; + + BOOL EvalLogic ( CBaseEntity *pEntity ); +}; + +#define SF_WRCOUNT_FIRESTART 0x0001 +#define SF_WRCOUNT_STARTED 0x8000 +class CWatcherCount : public CBaseToggle +{ +public: + void Spawn ( void ); + void EXPORT Think ( void ); + virtual STATE GetState( void ) { return (pev->spawnflags & SF_SWATCHER_VALID)?STATE_ON:STATE_OFF; }; + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +/////////////////////////////////////////////////////////////////////////////// + +// Flags to indicate masking off various render parameters that are normally copied to the targets +#define SF_RENDER_MASKFX (1<<0) +#define SF_RENDER_MASKAMT (1<<1) +#define SF_RENDER_MASKMODE (1<<2) +#define SF_RENDER_MASKCOLOR (1<<3) +//LRC +#define SF_RENDER_KILLTARGET (1<<5) +#define SF_RENDER_ONLYONCE (1<<6) + + +//LRC- RenderFxFader, a subsidiary entity for RenderFxManager +class CRenderFxFader : public CBaseEntity +{ +public: + void Spawn ( void ); + void EXPORT FadeThink ( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flStartTime; + float m_flDuration; + float m_flCoarseness; + int m_iStartAmt; + int m_iOffsetAmt; + Vector m_vecStartColor; + Vector m_vecOffsetColor; + float m_fStartScale; + float m_fOffsetScale; + EHANDLE m_hTarget; + + int m_iszAmtFactor; +}; + +/////////////////////////////////////////////////////////////////////////////// + +// RenderFxManager itself +class CRenderFxManager : public CPointEntity +{ +public: + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void Affect( CBaseEntity *pEntity, BOOL bIsLocus, CBaseEntity *pActivator ); + + void KeyValue( KeyValueData *pkvd ); +}; + +/////////////////////////////////////////////////////////////////////////////// + +class CEnvCustomize : public CBaseEntity +{ +public: + void Spawn( void ); + void EXPORT Think( void ); + void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + void Affect (CBaseEntity *pTarget, USE_TYPE useType); + int GetActionFor( int iField, int iActive, USE_TYPE useType, char *szDebug ); + void SetBoneController (float fController, int cnum, CBaseEntity *pTarget); + + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flRadius; + int m_iszModel; + int m_iClass; + int m_iPlayerReact; + int m_iPrisoner; + int m_iMonsterClip; + int m_iVisible; + int m_iSolid; + int m_iProvoked; + int m_voicePitch; + int m_iBloodColor; + float m_fFramerate; + float m_fController0; + float m_fController1; + float m_fController2; + float m_fController3; +}; + +/////////////////////////////////////////////////////////////////////////////// + +class CBaseTrigger : public CBaseToggle +{ +public: + //LRC - this was very bloated. I moved lots of methods into the + // subclasses where they belonged. + void InitTrigger( void ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + BOOL CanTouch( entvars_t *pevToucher ); + + virtual int ObjectCaps( void ) { return CBaseToggle :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerMonsterJump : public CBaseTrigger +{ +public: + void Spawn( void ); + void Touch( CBaseEntity *pOther ); + void Think( void ); +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerCDAudio : public CBaseTrigger +{ +public: + void Spawn( void ); + + virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void PlayTrack( void ); + void Touch ( CBaseEntity *pOther ); +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerPush : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Touch( CBaseEntity *pOther ); +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerMultiple : public CBaseTrigger +{ +public: + void Spawn( void ); + void Precache( void ) + { + if (!FStringNull(pev->noise)) + PRECACHE_SOUND((char*)STRING(pev->noise)); + } + void EXPORT MultiTouch( CBaseEntity *pOther ); + void EXPORT MultiWaitOver( void ); + void ActivateMultiTrigger( CBaseEntity *pActivator ); +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerInOut; + +class CInOutRegister : public CPointEntity +{ +public: + // returns true if found in the list + BOOL IsRegistered ( CBaseEntity *pValue ); + // remove all invalid entries from the list, trigger their targets as appropriate + // returns the new list + CInOutRegister *Prune( void ); + BOOL IsEmpty( void ) { return m_pNext?FALSE:TRUE; }; + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + CTriggerInOut *m_pField; + CInOutRegister *m_pNext; + EHANDLE m_hValue; +}; + +class CTriggerInOut : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT Touch( CBaseEntity *pOther ); + void EXPORT Think( void ); + void FireOnLeaving( CBaseEntity *pOther ); + + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + STATE GetState() { return m_pRegister->IsEmpty()?STATE_OFF:STATE_ON; } + + string_t m_iszAltTarget; + string_t m_iszBothTarget; + CInOutRegister *m_pRegister; + + // Cthulhu + //BOOL mbRestored; +}; + + +/////////////////////////////////////////////////////////////// + +class CTriggerHurt : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT HurtThink( void ); + void EXPORT RadiationThink( void ); + void EXPORT HurtTouch ( CBaseEntity *pOther ); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual void KeyValue( KeyValueData *pkvd ); +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerOnce : public CTriggerMultiple +{ +public: + void Spawn( void ); +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerCounter : public CTriggerMultiple +{ +public: + void Spawn( void ); + void EXPORT CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerVolume : public CPointEntity // Derive from point entity so this doesn't move across levels +{ +public: + void Spawn( void ); +}; + +/////////////////////////////////////////////////////////////// + +class CFireAndDie : public CBaseDelay +{ +public: + void Spawn( void ); + void Precache( void ); + void Think( void ); + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() | FCAP_FORCE_TRANSITION; } // Always go across transitions +}; + +/////////////////////////////////////////////////////////////// + +class CChangeLevel : public CBaseTrigger +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT UseChangeLevel ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT TriggerChangeLevel( void ); + void EXPORT ExecuteChangeLevel( void ); + void EXPORT TouchChangeLevel( CBaseEntity *pOther ); + void ChangeLevelNow( CBaseEntity *pActivator ); + + static edict_t *FindLandmark( const char *pLandmarkName ); + static int ChangeList( LEVELLIST *pLevelList, int maxList ); + static int AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ); + static int InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + char m_szMapName[cchMapNameMost]; // trigger_changelevel only: next map + char m_szLandmarkName[cchMapNameMost]; // trigger_changelevel only: landmark on next map + int m_changeTarget; + float m_changeTargetDelay; +}; + +/////////////////////////////////////////////////////////////// + +class CLadder : public CBaseTrigger +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Precache( void ); +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerTeleport : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT TeleportTouch ( CBaseEntity *pOther ); +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerSave : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT SaveTouch( CBaseEntity *pOther ); +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerEndSection : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT EndSectionTouch( CBaseEntity *pOther ); + void KeyValue( KeyValueData *pkvd ); + void EXPORT EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerGravity : public CBaseTrigger +{ +public: + void Spawn( void ); + void EXPORT GravityTouch( CBaseEntity *pOther ); +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerSetPatrol : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iszPath; +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerMotion : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void KeyValue( KeyValueData *pkvd ); + + int m_iszPosition; + int m_iPosMode; + int m_iszAngles; + int m_iAngMode; + int m_iszVelocity; + int m_iVelMode; + int m_iszAVelocity; + int m_iAVelMode; +}; + +class CMotionThread : public CPointEntity +{ +public: + void Think( void ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iszPosition; + int m_iPosMode; + int m_iszFacing; + int m_iFaceMode; + EHANDLE m_hLocus; + EHANDLE m_hTarget; +}; + +class CMotionManager : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + void Affect( CBaseEntity *pTarget, CBaseEntity *pActivator ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + int m_iszPosition; + int m_iPosMode; + int m_iszFacing; + int m_iFaceMode; +}; + +/////////////////////////////////////////////////////////////////// + +class CTriggerChangeTarget : public CBaseDelay +{ +public: + void KeyValue( KeyValueData *pkvd ); + void Spawn( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + +private: + int m_iszNewTarget; +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerCommand : public CBaseEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerChangeCVar : public CBaseEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT Think( void ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + static TYPEDESCRIPTION m_SaveData[]; + + char m_szStoredString[256]; +}; + +/////////////////////////////////////////////////////////////// + +class CTriggerCamera : public CBaseDelay +{ +public: + void Spawn( void ); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT FollowTarget( void ); + void Move(void); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + static TYPEDESCRIPTION m_SaveData[]; + + EHANDLE m_hPlayer; + EHANDLE m_hTarget; + CBaseEntity *m_pentPath; + int m_sPath; + float m_flWait; + float m_flReturnTime; + float m_flStopTime; + float m_moveDistance; + float m_targetSpeed; + float m_initialSpeed; + float m_acceleration; + float m_deceleration; + int m_state; + +}; + + +/////////////////////////////////////////////////////////////// + + +#endif + diff --git a/dlls/cthulhu/werewolf.cpp b/dlls/cthulhu/werewolf.cpp new file mode 100755 index 00000000..7fc89094 --- /dev/null +++ b/dlls/cthulhu/werewolf.cpp @@ -0,0 +1,494 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Wolf +//========================================================= + +// UNDONE: Don't flinch every time you get hit + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "soundent.h" +#include "game.h" + +#define WEREWOLF_IMMUNE (DMG_BULLET|DMG_SLASH|DMG_CLUB) + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define WEREWOLF_AE_ATTACK1 0x01 +#define WEREWOLF_AE_ATTACK2 0x02 +#define WEREWOLF_AE_ATTACK_RAGE1 0x03 +#define WEREWOLF_AE_ATTACK_RAGE2 0x04 +#define WEREWOLF_AE_LEAP 0x05 +#define WEREWOLF_AE_LEAP_ATTACK 0x06 + +#define WEREWOLF_FLINCH_DELAY 6 // at most one flinch every n secs + +#define WEREWOLF_GREY 0 +#define WEREWOLF_BLACK 1 +#define WEREWOLF_SILVERMANE 2 +#define WEREWOLF_BROWN 3 +#define WEREWOLF_WHITE 4 +#define WEREWOLF_WINTER 5 +#define NUM_WEREWOLF_BODIES 6 + + +#include "werewolf.h" + + +LINK_ENTITY_TO_CLASS( monster_werewolf, CWerewolf ); + +const char *CWerewolf::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CWerewolf::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CWerewolf::pAttackSounds[] = +{ + "ghoul/gh_attack1.wav", +// "ghoul/gh_attack2.wav", +}; + +const char *CWerewolf::pIdleSounds[] = +{ + "ghoul/gh_idle1.wav", + "ghoul/gh_idle2.wav", + "ghoul/gh_idle3.wav", +// "ghoul/gh_idle4.wav", +}; + +const char *CWerewolf::pAlertSounds[] = +{ + "ghoul/gh_alert1.wav", +// "ghoul/gh_alert20.wav", +// "ghoul/gh_alert30.wav", +}; + +const char *CWerewolf::pPainSounds[] = +{ + "ghoul/gh_pain1.wav", +// "ghoul/gh_pain2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CWerewolf :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_PREDATOR; +} + +//========================================================= +// 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 CWerewolf :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_CARCASS | + bits_SOUND_MEAT | + bits_SOUND_GARBAGE | + bits_SOUND_PLAYER; +} + +//========================================================= +// IRelationship - overridden. +//========================================================= +int CWerewolf::IRelationship ( CBaseEntity *pTarget ) +{ + if ( FClassnameIs( pTarget->pev, "monster_wolf" ) ) + { + return R_AL; + } + + return CBaseMonster :: IRelationship( pTarget ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CWerewolf :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + + pev->yaw_speed = ys; +} + +//========================================================= +// CheckRangeAttack1 +//========================================================= +BOOL CWerewolf :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + // Decent fix to keep folks from kicking/punching hornets and snarks is to check the onground flag(sjb) + //int iGround = FBitSet ( m_hEnemy->pev->flags, FL_ONGROUND ); + + // Cthulhu: but this stops the monster from hitting flying monsters that are + // melee attacking it (e.g. nightgaunt) + // Solution: explicitly check for monster types that we cannot hit + + BOOL bHit = TRUE; // we can hit by default + if (m_hEnemy) + { + if (FClassnameIs( m_hEnemy->pev, "hornet")) bHit = FALSE; + if (FClassnameIs( m_hEnemy->pev, "monster_snark")) bHit = FALSE; + } + + if ( flDist >= 96 && flDist <= 312 && flDot >= 0.7 && m_hEnemy != NULL && bHit ) + { + return TRUE; + } + return FALSE; +} + +int CWerewolf :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + if ( (bitsDamageType & WEREWOLF_IMMUNE) != 0 ) return 0; + + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CWerewolf :: 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 CWerewolf :: 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 CWerewolf :: 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) ], 0.2, ATTN_NORM, 0, pitch ); +} + +void CWerewolf :: AttackSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + // Play a random attack sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 0.5, ATTN_NORM, 0, pitch ); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CWerewolf :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case WEREWOLF_AE_ATTACK1: + { + // 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.z = -20; + pHurt->pev->punchangle.x = 20; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 100; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 200; + } + // 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 WEREWOLF_AE_ATTACK2: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.bullsquidDmgBite, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = -20; + pHurt->pev->punchangle.x = 20; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 50; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 10; + } + // 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 WEREWOLF_AE_ATTACK_RAGE1: + { + // do stuff for this event. + 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 WEREWOLF_AE_ATTACK_RAGE2: + { + // do stuff for this event. + 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 WEREWOLF_AE_LEAP: + { + ClearBits( pev->flags, FL_ONGROUND ); + + UTIL_SetOrigin (this, 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; + } + + pev->velocity = vecJumpDir; + } + break; + + case WEREWOLF_AE_LEAP_ATTACK: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.bullsquidDmgBite, DMG_SLASH ); + if ( pHurt ) + { + if ( pHurt->pev->flags & (FL_MONSTER|FL_CLIENT) ) + { + pHurt->pev->punchangle.z = -20; + pHurt->pev->punchangle.x = 20; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_forward * 50; + pHurt->pev->velocity = pHurt->pev->velocity + gpGlobals->v_up * 10; + } + // 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; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CWerewolf :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/monsters/werewolf.mdl"); + UTIL_SetSize( pev, Vector( -16, -16, 0 ), Vector( 16, 16, 72 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = gSkillData.agruntHealth; // use this one + 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 | bits_CAP_HEAR | bits_CAP_JUMP; + + if ( pev->body == -1 ) + {// -1 chooses a random body + pev->body = RANDOM_LONG(0, NUM_WEREWOLF_BODIES-1);// pick a body, any body + } + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CWerewolf :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/monsters/werewolf.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 CWerewolf::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { + 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 + WEREWOLF_FLINCH_DELAY; + } + + return iIgnore; + +} + +void CWerewolf::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType ) +{ + //ALERT( at_aiconsole, "CWerewolf::TraceAttack\n"); + + if ( !IsAlive() ) + { + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + return; + } + + if ((bitsDamageType & WEREWOLF_IMMUNE) != 0) return; + + CBaseMonster::TraceAttack( pevAttacker, flDamage, vecDir, ptr, bitsDamageType ); + +} + diff --git a/dlls/cthulhu/werewolf.h b/dlls/cthulhu/werewolf.h new file mode 100755 index 00000000..6d67e911 --- /dev/null +++ b/dlls/cthulhu/werewolf.h @@ -0,0 +1,42 @@ + +#ifndef WEREWOLF_H +#define WEREWOLF_H + +class CWerewolf : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int IgnoreConditions ( void ); + virtual int ISoundMask( void ); + virtual int IRelationship( CBaseEntity *pTarget ); + + 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 ); + BOOL CheckRangeAttack2 ( float flDot, float flDist ) { return FALSE; } + 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 ); +}; + + + +#endif + + diff --git a/dlls/cthulhu/wolf.cpp b/dlls/cthulhu/wolf.cpp new file mode 100755 index 00000000..738362e1 --- /dev/null +++ b/dlls/cthulhu/wolf.cpp @@ -0,0 +1,299 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// Wolf +//========================================================= + +// UNDONE: Don't flinch every time you get hit + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "soundent.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define WOLF_AE_ATTACK 0x01 + +#define WOLF_FLINCH_DELAY 4 // at most one flinch every n secs + +#define WOLF_GREY 0 +#define WOLF_BLACK 1 +#define WOLF_RED 2 +#define WOLF_WHITE 3 +#define NUM_WOLF_BODIES 4 + + +#include "Wolf.h" + + +LINK_ENTITY_TO_CLASS( monster_wolf, CWolf ); + +const char *CWolf::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CWolf::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CWolf::pAttackSounds[] = +{ + "ghoul/gh_attack1.wav", +// "ghoul/gh_attack2.wav", +}; + +const char *CWolf::pIdleSounds[] = +{ + "ghoul/gh_idle1.wav", + "ghoul/gh_idle2.wav", + "ghoul/gh_idle3.wav", +// "ghoul/gh_idle4.wav", +}; + +const char *CWolf::pAlertSounds[] = +{ + "ghoul/gh_alert1.wav", +// "ghoul/gh_alert20.wav", +// "ghoul/gh_alert30.wav", +}; + +const char *CWolf::pPainSounds[] = +{ + "ghoul/gh_pain1.wav", +// "ghoul/gh_pain2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CWolf :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_MUNDANE_PREDATOR; +} + +//========================================================= +// 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 CWolf :: ISoundMask ( void ) +{ + return bits_SOUND_WORLD | + bits_SOUND_COMBAT | + bits_SOUND_CARCASS | + bits_SOUND_MEAT | + bits_SOUND_GARBAGE | + bits_SOUND_PLAYER; +} + +//========================================================= +// IRelationship - overridden. +//========================================================= +int CWolf::IRelationship ( CBaseEntity *pTarget ) +{ + if ( FClassnameIs( pTarget->pev, "monster_werewolf" ) ) + { + return R_AL; + } + + return CBaseMonster :: IRelationship( pTarget ); +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CWolf :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + + pev->yaw_speed = ys; +} + +int CWolf :: 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 CWolf :: 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 CWolf :: 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 CWolf :: 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) ], 0.2, ATTN_NORM, 0, pitch ); +} + +void CWolf :: AttackSound( void ) +{ + int pitch = 95 + RANDOM_LONG(0,9); + + // Play a random attack sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], 0.5, ATTN_NORM, 0, pitch ); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CWolf :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case WOLF_AE_ATTACK: + { + // do stuff for this event. + // ALERT( at_console, "Slash right!\n" ); + CBaseEntity *pHurt = CheckTraceHullAttack( 70, gSkillData.bullsquidDmgBite, 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; + } + // 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; + + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CWolf :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/monsters/wolf.mdl"); + UTIL_SetSize( pev, Vector( -24, -24, 0 ), Vector( 24, 24, 36 ) ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = BLOOD_COLOR_RED; + if (pev->health == 0) + pev->health = gSkillData.houndeyeHealth * 2.5; // use this one + 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 = 0; + + if ( pev->body == -1 ) + {// -1 chooses a random body + pev->body = RANDOM_LONG(0, NUM_WOLF_BODIES-1);// pick a body, any body + } + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CWolf :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/monsters/wolf.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 CWolf::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { + 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 + WOLF_FLINCH_DELAY; + } + + return iIgnore; + +} \ No newline at end of file diff --git a/dlls/cthulhu/wolf.h b/dlls/cthulhu/wolf.h new file mode 100755 index 00000000..4ce1488b --- /dev/null +++ b/dlls/cthulhu/wolf.h @@ -0,0 +1,41 @@ + +#ifndef WOLF_H +#define WOLF_H + +class CWolf : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + int IgnoreConditions ( void ); + virtual int ISoundMask( void ); + virtual int IRelationship( CBaseEntity *pTarget ); + + 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 ); +}; + + + +#endif + + diff --git a/dlls/cthulhu/wraith.cpp b/dlls/cthulhu/wraith.cpp new file mode 100755 index 00000000..d2b71428 --- /dev/null +++ b/dlls/cthulhu/wraith.cpp @@ -0,0 +1,277 @@ +/*** +* +* Copyright (c) 1999, 2000 Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* 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. +* +****/ +//========================================================= +// DeepOne +//========================================================= + +// UNDONE: Don't flinch every time you get hit + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "flyingmonster.h" +#include "schedule.h" + + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define WRAITH_AE_ATTACK 0x01 + +#define WRAITH_FLINCH_DELAY 4 // at most one flinch every n secs + + +#include "wraith.h" + + +LINK_ENTITY_TO_CLASS( monster_wraith, CWraith ); + +const char *CWraith::pAttackHitSounds[] = +{ + "zombie/claw_strike1.wav", + "zombie/claw_strike2.wav", + "zombie/claw_strike3.wav", +}; + +const char *CWraith::pAttackMissSounds[] = +{ + "zombie/claw_miss1.wav", + "zombie/claw_miss2.wav", +}; + +const char *CWraith::pAttackSounds[] = +{ + "deepone/do_attack1.wav", + "deepone/do_attack2.wav", +}; + +const char *CWraith::pIdleSounds[] = +{ + "deepone/do_idle1.wav", + "deepone/do_idle2.wav", +}; + +const char *CWraith::pAlertSounds[] = +{ + "deepone/do_alert1.wav", + "deepone/do_alert2.wav", +}; + +const char *CWraith::pPainSounds[] = +{ + "deepone/do_pain1.wav", + "deepone/do_pain2.wav", +}; + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CWraith :: Classify ( void ) +{ + return m_iClass?m_iClass:CLASS_ALIEN_MONSTER; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CWraith :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + + pev->yaw_speed = ys; +} + +int CWraith :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // Take no damage from bullets + if ( bitsDamageType & DMG_BULLET ) return 0; + if ( bitsDamageType & DMG_FALL ) return 0; + if ( bitsDamageType & DMG_SLASH ) return 0; + if ( bitsDamageType & DMG_CLUB ) return 0; + if ( bitsDamageType & DMG_CRUSH ) return 0; + if ( bitsDamageType & DMG_BLAST ) return 0; + + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CWraith :: PainSound( void ) +{ + int pitch = GetVoicePitch() - 5 + RANDOM_LONG(0,10); + + if (RANDOM_LONG(0,5) < 2) + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pPainSounds[ RANDOM_LONG(0,ARRAYSIZE(pPainSounds)-1) ], GetVolume(), ATTN_NORM, 0, pitch ); +} + +void CWraith :: AlertSound( void ) +{ + int pitch = GetVoicePitch() - 5 + RANDOM_LONG(0,10); + + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAlertSounds[ RANDOM_LONG(0,ARRAYSIZE(pAlertSounds)-1) ], GetVolume(), ATTN_NORM, 0, pitch ); +} + +void CWraith :: IdleSound( void ) +{ + int pitch = GetVoicePitch() - 5 + RANDOM_LONG(0,10); + + // Play a random idle sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pIdleSounds[ RANDOM_LONG(0,ARRAYSIZE(pIdleSounds)-1) ], GetVolume(), ATTN_NORM, 0, pitch ); +} + +void CWraith :: AttackSound( void ) +{ + int pitch = GetVoicePitch() - 5 + RANDOM_LONG(0,10); + + // Play a random attack sound + EMIT_SOUND_DYN ( ENT(pev), CHAN_VOICE, pAttackSounds[ RANDOM_LONG(0,ARRAYSIZE(pAttackSounds)-1) ], GetVolume(), ATTN_NORM, 0, pitch ); +} + + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= + +float CWraith :: GetAttackDist( void ) +{ + return 70.0; +} + +void CWraith :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + double fldist = GetAttackDist(); + + switch( pEvent->event ) + { + case WRAITH_AE_ATTACK: + { + // do stuff for this event. + CBaseEntity *pHurt = CheckTraceHullAttack( fldist, gSkillData.deeponeDmgBothSlash * 0.5, DMG_FREEZE ); + 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 CWraith :: Spawn() +{ + Precache( ); + + if (pev->model) + SET_MODEL(ENT(pev), STRING(pev->model)); //LRC + else + SET_MODEL(ENT(pev), "models/monsters/wraith.mdl"); + UTIL_SetSize( pev, VEC_HUMAN_HULL_MIN, VEC_HUMAN_HULL_MAX ); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_STEP; + m_bloodColor = DONT_BLEED; + if (pev->health == 0) + pev->health = gSkillData.deeponeHealth; + 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 | bits_CAP_SWIM | bits_CAP_CLIMB | bits_CAP_JUMP; + + MonsterInit(); + +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CWraith :: Precache() +{ + int i; + + if (pev->model) + PRECACHE_MODEL((char*)STRING(pev->model)); //LRC + else + PRECACHE_MODEL("models/monsters/wraith.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 CWraith::IgnoreConditions ( void ) +{ + int iIgnore = CBaseMonster::IgnoreConditions(); + + if ((m_Activity == ACT_MELEE_ATTACK1) || (m_Activity == ACT_MELEE_ATTACK2)) + { + 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 + WRAITH_FLINCH_DELAY; + } + + return iIgnore; + +} + + + diff --git a/dlls/cthulhu/wraith.h b/dlls/cthulhu/wraith.h new file mode 100755 index 00000000..fe6b6446 --- /dev/null +++ b/dlls/cthulhu/wraith.h @@ -0,0 +1,39 @@ + +#ifndef WRAITH_H +#define WRAITH_H + +class CWraith : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + virtual float GetAttackDist( 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[]; + + virtual int GetVoicePitch( void ) { return PITCH_NORM; }; + virtual float GetVolume (void) { return 0.5; }; + + // 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 ); +}; + +#endif diff --git a/dlls/defaultai.cpp b/dlls/defaultai.cpp index 854c9134..3cbdaf12 100644 --- a/dlls/defaultai.cpp +++ b/dlls/defaultai.cpp @@ -621,6 +621,30 @@ Schedule_t slChaseEnemy[] = }, }; +Task_t tlChaseEnemyLKP1[] = +{ + { TASK_SET_FAIL_SCHEDULE, (float)SCHED_CHASE_ENEMY_FAILED }, + { TASK_GET_PATH_TO_ENEMY_LKP, 0.0f }, + { TASK_RUN_PATH, 0.0f }, + { TASK_WAIT_FOR_MOVEMENT, 0.0f }, +}; + +Schedule_t slChaseEnemyLKP[] = +{ + { + tlChaseEnemyLKP1, + ARRAYSIZE( tlChaseEnemyLKP1 ), + 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 LKP" + }, +}; // Chase enemy failure schedule Task_t tlChaseEnemyFailed[] = @@ -912,6 +936,31 @@ Schedule_t slCower[] = }, }; +//========================================================= +// Panic - used when dread name is spoken. +//========================================================= +Task_t tlPanic[] = +{ + { TASK_STOP_MOVING, 0.0f }, + { TASK_FIND_COVER_FROM_ENEMY, 0.0f }, + // { TASK_FIND_COVER_FROM_ORIGIN, 0.0f }, + { TASK_RUN_PATH, 0.0f }, + { TASK_WAIT_FOR_MOVEMENT, 0.0f }, + { TASK_PLAY_SEQUENCE, (float)ACT_COWER }, + { TASK_WAIT, 15.0f }, // default 5 seconds +}; + +Schedule_t slPanic[] = +{ + { + tlPanic, + ARRAYSIZE( tlPanic ), + 0, + 0, + "Panic" + }, +}; + //========================================================= // move away from where you're currently standing. //========================================================= @@ -955,7 +1004,8 @@ Schedule_t slTakeCoverFromBestSound[] = tlTakeCoverFromBestSound, ARRAYSIZE( tlTakeCoverFromBestSound ), bits_COND_NEW_ENEMY, - 0, + // 0, + bits_SOUND_DANGER, "TakeCoverFromBestSound" }, }; @@ -1012,6 +1062,7 @@ Schedule_t *CBaseMonster::m_scheduleList[] = slSpecialAttack1, slSpecialAttack2, slChaseEnemy, + slChaseEnemyLKP, slChaseEnemyFailed, slSmallFlinch, slDie, @@ -1027,7 +1078,8 @@ Schedule_t *CBaseMonster::m_scheduleList[] = slTakeCoverFromOrigin, slTakeCoverFromBestSound, slTakeCoverFromEnemy, - slFail + slFail, + slPanic }; Schedule_t *CBaseMonster::ScheduleFromName( const char *pName ) @@ -1137,6 +1189,10 @@ Schedule_t* CBaseMonster::GetScheduleOfType( int Type ) { return &slChaseEnemy[0]; } + case SCHED_CHASE_ENEMY_LKP: + { + return &slChaseEnemyLKP[0]; + } case SCHED_CHASE_ENEMY_FAILED: { return &slFail[0]; @@ -1197,6 +1253,10 @@ Schedule_t* CBaseMonster::GetScheduleOfType( int Type ) { return &slCower[0]; } + case SCHED_DREAD_NAME_PANIC: + { + return &slPanic[0]; + } case SCHED_AMBUSH: { return &slAmbush[0]; diff --git a/dlls/effects.cpp b/dlls/effects.cpp index 321d8de1..d08247f9 100644 --- a/dlls/effects.cpp +++ b/dlls/effects.cpp @@ -656,6 +656,9 @@ void CLightning::StrikeUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_T int IsPointEntity( CBaseEntity *pEnt ) { + if( pEnt == NULL ) // Cthulhu + return 0; + // ALERT(at_console, "IsPE: %s, %d\n", STRING(pEnt->pev->classname), pEnt->pev->modelindex); if (pEnt->pev->modelindex && !(pEnt->pev->flags & FL_CUSTOMENTITY)) //LRC- follow (almost) any entity that has a model return 0; @@ -910,6 +913,12 @@ void CLightning::Zap( const Vector &vecSrc, const Vector &vecDest ) MESSAGE_END(); #endif DoSparks( vecSrc, vecDest ); + + // Cthulhu + TraceResult tr; + UTIL_TraceLine( vecSrc, vecDest, dont_ignore_monsters, NULL, &tr ); + if( pev->dmg ) + BeamDamageInstant( &tr, pev->dmg ); } void CLightning::RandomArea( void ) @@ -1016,12 +1025,25 @@ void CLightning::BeamUpdatePoints( void ) if( beamType == BEAM_POINTS || beamType == BEAM_HOSE ) SetEndPos( pEnd->pev->origin ); else - SetEndEntity( ENTINDEX(ENT(pEnd->pev)) ); + { + // Cthulhu: fix for bug..though it should not occur + if( pEnd ) + SetEndEntity( ENTINDEX(ENT(pEnd->pev)) ); + else + SetEndEntity( ENTINDEX(0) ); + } } else { - SetStartEntity( ENTINDEX(ENT(pStart->pev)) ); - SetEndEntity( ENTINDEX(ENT(pEnd->pev)) ); + if( pStart ) + SetStartEntity( ENTINDEX(ENT(pStart->pev)) ); + else + SetStartEntity( ENTINDEX(0) ); + + if( pEnd ) + SetEndEntity( ENTINDEX(ENT(pEnd->pev)) ); + else + SetEndEntity( ENTINDEX(0) ); } RelinkBeam(); @@ -3087,7 +3109,7 @@ void CEnvBeamTrail::Affect( CBaseEntity *pTarget, USE_TYPE useType ) WRITE_SHORT(pTarget->entindex()); // entity WRITE_SHORT( m_iSprite ); // model WRITE_BYTE( pev->health*10 ); // life - WRITE_BYTE( pev->armorvalue ); // width + WRITE_BYTE( pev->sanity ); // width WRITE_BYTE( pev->rendercolor.x ); // r, g, b WRITE_BYTE( pev->rendercolor.y ); // r, g, b WRITE_BYTE( pev->rendercolor.z ); // r, g, b @@ -3588,15 +3610,6 @@ void CEnvRain::Think( void ) //================================================================== //LRC- Xen monsters' warp-in effect, for those too lazy to build it. :) //================================================================== -class CEnvWarpBall : public CBaseEntity -{ -public: - void Precache( void ); - void Spawn( void ) { Precache(); } - void Think( void ); - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } -}; LINK_ENTITY_TO_CLASS( env_warpball, CEnvWarpBall ); @@ -4236,34 +4249,6 @@ void CItemSoda::CanTouch( CBaseEntity *pOther ) #define SF_FOG_ACTIVE 1 #define SF_FOG_FADING 0x8000 -class CEnvFog : public CBaseEntity -{ -public: - void Spawn( void ); - void Precache( void ); - void EXPORT ResumeThink( void ); - void EXPORT Resume2Think( void ); - void EXPORT TurnOn( void ); - void EXPORT TurnOff( void ); - void EXPORT FadeInDone( void ); - void EXPORT FadeOutDone( void ); - void SendData( Vector col, int fFadeTime, int StartDist, int iEndDist); - void KeyValue( KeyValueData *pkvd ); - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - static TYPEDESCRIPTION m_SaveData[]; - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - - STATE GetState( void ); - - int m_iStartDist; - int m_iEndDist; - float m_iFadeIn; - float m_iFadeOut; - float m_fHoldTime; - float m_fFadeStart; // if we're fading in/out, then when did the fade start? -}; - TYPEDESCRIPTION CEnvFog::m_SaveData[] = { DEFINE_FIELD( CEnvFog, m_iStartDist, FIELD_INTEGER ), diff --git a/dlls/effects.h b/dlls/effects.h index 4b179463..8913c4ef 100644 --- a/dlls/effects.h +++ b/dlls/effects.h @@ -337,4 +337,43 @@ public: int m_iszStartPosition; int m_iTowardsMode; }; + +class CEnvWarpBall : public CBaseEntity +{ +public: + void Precache( void ); + void Spawn( void ) { Precache(); } + void Think( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +}; + +class CEnvFog : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + void EXPORT ResumeThink( void ); + void EXPORT TurnOn( void ); + void EXPORT TurnOff( void ); + void EXPORT FadeInDone( void ); + void EXPORT FadeOutDone( void ); + void SendData( Vector col, int fFadeTime, int StartDist, int iEndDist); + void KeyValue( KeyValueData *pkvd ); + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + //virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + STATE GetState( void ); + + int m_iStartDist; + int m_iEndDist; + float m_iFadeIn; + float m_iFadeOut; + float m_fHoldTime; + float m_fFadeStart; // if we're fading in/out, then when did the fade start? +}; + #endif //EFFECTS_H diff --git a/dlls/func_break.cpp b/dlls/func_break.cpp index 709a6c2b..abbdbafc 100644 --- a/dlls/func_break.cpp +++ b/dlls/func_break.cpp @@ -39,25 +39,24 @@ const char *CBreakable::pSpawnObjects[] = NULL, // 0 "item_battery", // 1 "item_healthkit", // 2 - "weapon_9mmhandgun", // 3 - "ammo_9mmclip", // 4 - "weapon_9mmAR", // 5 - "ammo_9mmAR", // 6 - "ammo_ARgrenades", // 7 + "weapon_revolver", // 3 + "weapon_rifle", // 4 + "weapon_tommygun", // 5 + "ammo_revolver", // 6 + "ammo_rifle", // 7 "weapon_shotgun", // 8 - "ammo_buckshot", // 9 - "weapon_crossbow", // 10 - "ammo_crossbow", // 11 - "weapon_357", // 12 - "ammo_357", // 13 - "weapon_rpg", // 14 - "ammo_rpgclip", // 15 - "ammo_gaussclip", // 16 - "weapon_handgrenade", // 17 - "weapon_tripmine", // 18 - "weapon_satchel", // 19 - "weapon_snark", // 20 - "weapon_hornetgun", // 21 + "ammo_shotgun", // 9 + "ammo_tommygun", // 10 + "ammo_lightninggun", // 11 + "weapon_dynamite", // 12 + "weapon_molotov", // 13 + "weapon_elder_sign", // 14 + "weapon_serpent_staff", // 15 + "weapon_dreadname", // 16 + "weapon_shrivelling", // 17 + "weapon_rlyeh_seal", // 18 + "weapon_drainlife", // 19 + "weapon_charm" // 20 }; void CBreakable::KeyValue( KeyValueData* pkvd ) @@ -913,16 +912,17 @@ void CBreakable::Die( void ) { // ALERT(at_debug,"No respawn\n"); - //tidy up + // Cthulhu : remove sooner than 0.1 seconds, otherwise the MoveWith physics causes a crash + // tidy up if (m_pHitProxy) { m_pHitProxy->SetThink(&CBreakable::SUB_Remove ); - m_pHitProxy->SetNextThink( 0.1 ); + m_pHitProxy->SetNextThink( 0.01f ); m_pHitProxy = NULL; } - SetThink(&CBreakable::SUB_Remove ); - SetNextThink( 0.1 ); + SetThink(&CBreakable::SUB_Remove ); + SetNextThink( 0.01f ); // ALERT(at_console, "Set SUB_Remove\n"); } diff --git a/dlls/game.cpp b/dlls/game.cpp index dad521e4..7bfe51b5 100644 --- a/dlls/game.cpp +++ b/dlls/game.cpp @@ -56,6 +56,12 @@ cvar_t *g_psv_aim = NULL; cvar_t *g_footsteps = NULL; //CVARS FOR SKILL LEVEL SETTINGS + +// Duration of the panic from the Dread Name +cvar_t sk_panic_duration1 = {"sk_panic_duration1","0"}; +cvar_t sk_panic_duration2 = {"sk_panic_duration2","0"}; +cvar_t sk_panic_duration3 = {"sk_panic_duration3","0"}; + // Agrunt cvar_t sk_agrunt_health1 = {"sk_agrunt_health1","0"}; cvar_t sk_agrunt_health2 = {"sk_agrunt_health2","0"}; @@ -75,6 +81,23 @@ cvar_t sk_barney_health1 = {"sk_barney_health1","0"}; cvar_t sk_barney_health2 = {"sk_barney_health2","0"}; cvar_t sk_barney_health3 = {"sk_barney_health3","0"}; +// Cthonian +cvar_t sk_cthonian_health1 = {"sk_cthonian_health1","0"}; +cvar_t sk_cthonian_health2 = {"sk_cthonian_health2","0"}; +cvar_t sk_cthonian_health3 = {"sk_cthonian_health3","0"}; + +cvar_t sk_cthonian_dmg_bite1 = {"sk_cthonian_dmg_bite1","0"}; +cvar_t sk_cthonian_dmg_bite2 = {"sk_cthonian_dmg_bite2","0"}; +cvar_t sk_cthonian_dmg_bite3 = {"sk_cthonian_dmg_bite3","0"}; + +cvar_t sk_cthonian_dmg_whip1 = {"sk_cthonian_dmg_whip1","0"}; +cvar_t sk_cthonian_dmg_whip2 = {"sk_cthonian_dmg_whip2","0"}; +cvar_t sk_cthonian_dmg_whip3 = {"sk_cthonian_dmg_whip3","0"}; + +cvar_t sk_cthonian_dmg_spit1 = {"sk_cthonian_dmg_spit1","0"}; +cvar_t sk_cthonian_dmg_spit2 = {"sk_cthonian_dmg_spit2","0"}; +cvar_t sk_cthonian_dmg_spit3 = {"sk_cthonian_dmg_spit3","0"}; + // Bullsquid cvar_t sk_bullsquid_health1 = {"sk_bullsquid_health1","0"}; cvar_t sk_bullsquid_health2 = {"sk_bullsquid_health2","0"}; @@ -143,6 +166,31 @@ cvar_t sk_headcrab_dmg_bite1 = {"sk_headcrab_dmg_bite1","0"}; cvar_t sk_headcrab_dmg_bite2 = {"sk_headcrab_dmg_bite2","0"}; cvar_t sk_headcrab_dmg_bite3 = {"sk_headcrab_dmg_bite3","0"}; +// Gangster +cvar_t sk_gangster_health1 = {"sk_gangster_health1","0"}; +cvar_t sk_gangster_health2 = {"sk_gangster_health2","0"}; +cvar_t sk_gangster_health3 = {"sk_gangster_health3","0"}; + +cvar_t sk_gangster_kick1 = {"sk_gangster_kick1","0"}; +cvar_t sk_gangster_kick2 = {"sk_gangster_kick2","0"}; +cvar_t sk_gangster_kick3 = {"sk_gangster_kick3","0"}; + +cvar_t sk_gangster_pellets1 = {"sk_gangster_pellets1","0"}; +cvar_t sk_gangster_pellets2 = {"sk_gangster_pellets2","0"}; +cvar_t sk_gangster_pellets3 = {"sk_gangster_pellets3","0"}; + +// Cultist +cvar_t sk_cultist_health1 = {"sk_cultist_health1","0"}; +cvar_t sk_cultist_health2 = {"sk_cultist_health2","0"}; +cvar_t sk_cultist_health3 = {"sk_cultist_health3","0"}; + +cvar_t sk_cultist_kick1 = {"sk_cultist_kick1","0"}; +cvar_t sk_cultist_kick2 = {"sk_cultist_kick2","0"}; +cvar_t sk_cultist_kick3 = {"sk_cultist_kick3","0"}; + +cvar_t sk_cultist_pellets1 = {"sk_cultist_pellets1","0"}; +cvar_t sk_cultist_pellets2 = {"sk_cultist_pellets2","0"}; +cvar_t sk_cultist_pellets3 = {"sk_cultist_pellets3","0"}; // Hgrunt cvar_t sk_hgrunt_health1 = {"sk_hgrunt_health1","0"}; @@ -171,6 +219,69 @@ cvar_t sk_houndeye_dmg_blast2 = {"sk_houndeye_dmg_blast2","0"}; cvar_t sk_houndeye_dmg_blast3 = {"sk_houndeye_dmg_blast3","0"}; +// Great Race +cvar_t sk_great_race_health1 = {"sk_great_race_health1","0"}; +cvar_t sk_great_race_health2 = {"sk_great_race_health2","0"}; +cvar_t sk_great_race_health3 = {"sk_great_race_health3","0"}; + +cvar_t sk_great_race_dmg_claw1 = {"sk_great_race_dmg_claw1","0"}; +cvar_t sk_great_race_dmg_claw2 = {"sk_great_race_dmg_claw2","0"}; +cvar_t sk_great_race_dmg_claw3 = {"sk_great_race_dmg_claw3","0"}; + +cvar_t sk_great_race_dmg_clawrake1 = {"sk_great_race_dmg_clawrake1","0"}; +cvar_t sk_great_race_dmg_clawrake2 = {"sk_great_race_dmg_clawrake2","0"}; +cvar_t sk_great_race_dmg_clawrake3 = {"sk_great_race_dmg_clawrake3","0"}; + +cvar_t sk_great_race_dmg_zap1 = {"sk_great_race_dmg_zap1","0"}; +cvar_t sk_great_race_dmg_zap2 = {"sk_great_race_dmg_zap2","0"}; +cvar_t sk_great_race_dmg_zap3 = {"sk_great_race_dmg_zap3","0"}; + +// Yodan +cvar_t sk_yodan_health1 = {"sk_yodan_health1","0"}; +cvar_t sk_yodan_health2 = {"sk_yodan_health2","0"}; +cvar_t sk_yodan_health3 = {"sk_yodan_health3","0"}; + +cvar_t sk_yodan_dmg_claw1 = {"sk_yodan_dmg_claw1","0"}; +cvar_t sk_yodan_dmg_claw2 = {"sk_yodan_dmg_claw2","0"}; +cvar_t sk_yodan_dmg_claw3 = {"sk_yodan_dmg_claw3","0"}; + +cvar_t sk_yodan_dmg_clawrake1 = {"sk_yodan_dmg_clawrake1","0"}; +cvar_t sk_yodan_dmg_clawrake2 = {"sk_yodan_dmg_clawrake2","0"}; +cvar_t sk_yodan_dmg_clawrake3 = {"sk_yodan_dmg_clawrake3","0"}; + +cvar_t sk_yodan_dmg_zap1 = {"sk_yodan_dmg_zap1","0"}; +cvar_t sk_yodan_dmg_zap2 = {"sk_yodan_dmg_zap2","0"}; +cvar_t sk_yodan_dmg_zap3 = {"sk_yodan_dmg_zap3","0"}; + + +// Serpent Man +cvar_t sk_serpent_man_health1 = {"sk_serpent_man_health1","0"}; +cvar_t sk_serpent_man_health2 = {"sk_serpent_man_health2","0"}; +cvar_t sk_serpent_man_health3 = {"sk_serpent_man_health3","0"}; + +cvar_t sk_serpent_man_dmg_staff1 = {"sk_serpent_man_dmg_staff1","0"}; +cvar_t sk_serpent_man_dmg_staff2 = {"sk_serpent_man_dmg_staff2","0"}; +cvar_t sk_serpent_man_dmg_staff3 = {"sk_serpent_man_dmg_staff3","0"}; + +cvar_t sk_serpent_man_dmg_bite1 = {"sk_serpent_man_dmg_bite1","0"}; +cvar_t sk_serpent_man_dmg_bite2 = {"sk_serpent_man_dmg_bite2","0"}; +cvar_t sk_serpent_man_dmg_bite3 = {"sk_serpent_man_dmg_bite3","0"}; + +cvar_t sk_serpent_man_dmg_zap1 = {"sk_serpent_man_dmg_zap1","0"}; +cvar_t sk_serpent_man_dmg_zap2 = {"sk_serpent_man_dmg_zap2","0"}; +cvar_t sk_serpent_man_dmg_zap3 = {"sk_serpent_man_dmg_zap3","0"}; + + +// Priest +cvar_t sk_priest_health1 = {"sk_priest_health1","0"}; +cvar_t sk_priest_health2 = {"sk_priest_health2","0"}; +cvar_t sk_priest_health3 = {"sk_priest_health3","0"}; + +cvar_t sk_priest_dmg_knife1 = {"sk_priest_dmg_knife1","0"}; +cvar_t sk_priest_dmg_knife2 = {"sk_priest_dmg_knife2","0"}; +cvar_t sk_priest_dmg_knife3 = {"sk_priest_dmg_knife3","0"}; + + // ISlave cvar_t sk_islave_health1 = {"sk_islave_health1","0"}; cvar_t sk_islave_health2 = {"sk_islave_health2","0"}; @@ -199,6 +310,15 @@ cvar_t sk_ichthyosaur_shake2 = {"sk_ichthyosaur_shake2","0"}; cvar_t sk_ichthyosaur_shake3 = {"sk_ichthyosaur_shake3","0"}; +// Hunting Horror +cvar_t sk_huntinghorror_health1 = {"sk_huntinghorror_health1","0"}; +cvar_t sk_huntinghorror_health2 = {"sk_huntinghorror_health2","0"}; +cvar_t sk_huntinghorror_health3 = {"sk_huntinghorror_health3","0"}; + +cvar_t sk_huntinghorror_bite1 = {"sk_huntinghorror_bite1","0"}; +cvar_t sk_huntinghorror_bite2 = {"sk_huntinghorror_bite2","0"}; +cvar_t sk_huntinghorror_bite3 = {"sk_huntinghorror_bite3","0"}; + // Leech cvar_t sk_leech_health1 = {"sk_leech_health1","0"}; cvar_t sk_leech_health2 = {"sk_leech_health2","0"}; @@ -239,6 +359,24 @@ cvar_t sk_scientist_health1 = {"sk_scientist_health1","0"}; cvar_t sk_scientist_health2 = {"sk_scientist_health2","0"}; cvar_t sk_scientist_health3 = {"sk_scientist_health3","0"}; +// Butler +cvar_t sk_butler_health1 = {"sk_butler_health1","0"}; +cvar_t sk_butler_health2 = {"sk_butler_health2","0"}; +cvar_t sk_butler_health3 = {"sk_butler_health3","0"}; + +// Sir Henry +cvar_t sk_sirhenry_health1 = {"sk_sirhenry_health1","0"}; +cvar_t sk_sirhenry_health2 = {"sk_sirhenry_health2","0"}; +cvar_t sk_sirhenry_health3 = {"sk_sirhenry_health3","0"}; + +cvar_t sk_sirhenry_dmg_zap1 = {"sk_sirhenry_dmg_zap1","0"}; +cvar_t sk_sirhenry_dmg_zap2 = {"sk_sirhenry_dmg_zap2","0"}; +cvar_t sk_sirhenry_dmg_zap3 = {"sk_sirhenry_dmg_zap3","0"}; + +cvar_t sk_sirhenry_dmg_knife1 = {"sk_sirhenry_dmg_knife1","0"}; +cvar_t sk_sirhenry_dmg_knife2 = {"sk_sirhenry_dmg_knife2","0"}; +cvar_t sk_sirhenry_dmg_knife3 = {"sk_sirhenry_dmg_knife3","0"}; + // Snark cvar_t sk_snark_health1 = {"sk_snark_health1","0"}; cvar_t sk_snark_health2 = {"sk_snark_health2","0"}; @@ -252,6 +390,77 @@ cvar_t sk_snark_dmg_pop1 = {"sk_snark_dmg_pop1","0"}; cvar_t sk_snark_dmg_pop2 = {"sk_snark_dmg_pop2","0"}; cvar_t sk_snark_dmg_pop3 = {"sk_snark_dmg_pop3","0"}; +// Formless Spawn +cvar_t sk_formless_spawn_health1 = {"sk_formless_spawn_health1","0"}; +cvar_t sk_formless_spawn_health2 = {"sk_formless_spawn_health2","0"}; +cvar_t sk_formless_spawn_health3 = {"sk_formless_spawn_health3","0"}; + +cvar_t sk_formless_spawn_dmg_attack1 = {"sk_formless_spawn_dmg_attack1","0"}; +cvar_t sk_formless_spawn_dmg_attack2 = {"sk_formless_spawn_dmg_attack2","0"}; +cvar_t sk_formless_spawn_dmg_attack3 = {"sk_formless_spawn_dmg_attack3","0"}; + + +// Ghoul +cvar_t sk_ghoul_health1 = {"sk_ghoul_health1","0"}; +cvar_t sk_ghoul_health2 = {"sk_ghoul_health2","0"}; +cvar_t sk_ghoul_health3 = {"sk_ghoul_health3","0"}; + +cvar_t sk_ghoul_dmg_one_slash1 = {"sk_ghoul_dmg_one_slash1","0"}; +cvar_t sk_ghoul_dmg_one_slash2 = {"sk_ghoul_dmg_one_slash2","0"}; +cvar_t sk_ghoul_dmg_one_slash3 = {"sk_ghoul_dmg_one_slash3","0"}; + +cvar_t sk_ghoul_dmg_both_slash1 = {"sk_ghoul_dmg_both_slash1","0"}; +cvar_t sk_ghoul_dmg_both_slash2 = {"sk_ghoul_dmg_both_slash2","0"}; +cvar_t sk_ghoul_dmg_both_slash3 = {"sk_ghoul_dmg_both_slash3","0"}; + + +// Night Gaunt +cvar_t sk_nightgaunt_health1 = {"sk_nightgaunt_health1","0"}; +cvar_t sk_nightgaunt_health2 = {"sk_nightgaunt_health2","0"}; +cvar_t sk_nightgaunt_health3 = {"sk_nightgaunt_health3","0"}; + +cvar_t sk_nightgaunt_dmg_slash1 = {"sk_nightgaunt_dmg_slash1","0"}; +cvar_t sk_nightgaunt_dmg_slash2 = {"sk_nightgaunt_dmg_slash2","0"}; +cvar_t sk_nightgaunt_dmg_slash3 = {"sk_nightgaunt_dmg_slash3","0"}; + + +// Snake +cvar_t sk_snake_health1 = {"sk_snake_health1","0"}; +cvar_t sk_snake_health2 = {"sk_snake_health2","0"}; +cvar_t sk_snake_health3 = {"sk_snake_health3","0"}; + +cvar_t sk_snake_dmg_bite1 = {"sk_snake_dmg_bite1","0"}; +cvar_t sk_snake_dmg_bite2 = {"sk_snake_dmg_bite2","0"}; +cvar_t sk_snake_dmg_bite3 = {"sk_snake_dmg_bite3","0"}; + + +// Deep One +cvar_t sk_deep_one_health1 = {"sk_deep_one_health1","0"}; +cvar_t sk_deep_one_health2 = {"sk_deep_one_health2","0"}; +cvar_t sk_deep_one_health3 = {"sk_deep_one_health3","0"}; + +cvar_t sk_deep_one_dmg_one_slash1 = {"sk_deep_one_dmg_one_slash1","0"}; +cvar_t sk_deep_one_dmg_one_slash2 = {"sk_deep_one_dmg_one_slash2","0"}; +cvar_t sk_deep_one_dmg_one_slash3 = {"sk_deep_one_dmg_one_slash3","0"}; + +cvar_t sk_deep_one_dmg_both_slash1 = {"sk_deep_one_dmg_both_slash1","0"}; +cvar_t sk_deep_one_dmg_both_slash2 = {"sk_deep_one_dmg_both_slash2","0"}; +cvar_t sk_deep_one_dmg_both_slash3 = {"sk_deep_one_dmg_both_slash3","0"}; + + +// Dimensional Shambler +cvar_t sk_shambler_health1 = {"sk_shambler_health1","0"}; +cvar_t sk_shambler_health2 = {"sk_shambler_health2","0"}; +cvar_t sk_shambler_health3 = {"sk_shambler_health3","0"}; + +cvar_t sk_shambler_dmg_one_slash1 = {"sk_shambler_dmg_one_slash1","0"}; +cvar_t sk_shambler_dmg_one_slash2 = {"sk_shambler_dmg_one_slash2","0"}; +cvar_t sk_shambler_dmg_one_slash3 = {"sk_shambler_dmg_one_slash3","0"}; + +cvar_t sk_shambler_dmg_both_slash1 = {"sk_shambler_dmg_both_slash1","0"}; +cvar_t sk_shambler_dmg_both_slash2 = {"sk_shambler_dmg_both_slash2","0"}; +cvar_t sk_shambler_dmg_both_slash3 = {"sk_shambler_dmg_both_slash3","0"}; + // Zombie cvar_t sk_zombie_health1 = {"sk_zombie_health1","0"}; cvar_t sk_zombie_health2 = {"sk_zombie_health2","0"}; @@ -282,6 +491,61 @@ cvar_t sk_sentry_health3 = {"sk_sentry_health3","0"}; // PLAYER WEAPONS +// Swordcane +cvar_t sk_plr_swordcane1 = {"sk_plr_swordcane1","0"}; +cvar_t sk_plr_swordcane2 = {"sk_plr_swordcane2","0"}; +cvar_t sk_plr_swordcane3 = {"sk_plr_swordcane3","0"}; + +// Knife +cvar_t sk_plr_knife1 = {"sk_plr_knife1","0"}; +cvar_t sk_plr_knife2 = {"sk_plr_knife2","0"}; +cvar_t sk_plr_knife3 = {"sk_plr_knife3","0"}; + +// Revolver +cvar_t sk_plr_revolver1 = {"sk_plr_revolver1","0"}; +cvar_t sk_plr_revolver2 = {"sk_plr_revolver2","0"}; +cvar_t sk_plr_revolver3 = {"sk_plr_revolver3","0"}; + +// Shotgun buckshot +cvar_t sk_plr_shotgun1 = {"sk_plr_shotgun1","0"}; +cvar_t sk_plr_shotgun2 = {"sk_plr_shotgun2","0"}; +cvar_t sk_plr_shotgun3 = {"sk_plr_shotgun3","0"}; + +// Tommy Gun +cvar_t sk_plr_tommygun1 = {"sk_plr_tommygun1","0"}; +cvar_t sk_plr_tommygun2 = {"sk_plr_tommygun2","0"}; +cvar_t sk_plr_tommygun3 = {"sk_plr_tommygun3","0"}; + +// Rifle +cvar_t sk_plr_rifle1 = {"sk_plr_rifle1","0"}; +cvar_t sk_plr_rifle2 = {"sk_plr_rifle2","0"}; +cvar_t sk_plr_rifle3 = {"sk_plr_rifle3","0"}; + +// Molotov +cvar_t sk_plr_molotov1 = {"sk_plr_molotov1","0"}; +cvar_t sk_plr_molotov2 = {"sk_plr_molotov2","0"}; +cvar_t sk_plr_molotov3 = {"sk_plr_molotov3","0"}; + +// Dynamite +cvar_t sk_plr_dynamite1 = {"sk_plr_dynamite1","0"}; +cvar_t sk_plr_dynamite2 = {"sk_plr_dynamite2","0"}; +cvar_t sk_plr_dynamite3 = {"sk_plr_dynamite3","0"}; + +// Lightning +cvar_t sk_plr_lightning1 = {"sk_plr_lightning1","0"}; +cvar_t sk_plr_lightning2 = {"sk_plr_lightning2","0"}; +cvar_t sk_plr_lightning3 = {"sk_plr_lightning3","0"}; + +// Shrivelling narrow +cvar_t sk_plr_shrivelling_wide1 = {"sk_plr_shrivelling_wide1","0"}; +cvar_t sk_plr_shrivelling_wide2 = {"sk_plr_shrivelling_wide2","0"}; +cvar_t sk_plr_shrivelling_wide3 = {"sk_plr_shrivelling_wide3","0"}; + +// Drain Life +cvar_t sk_plr_drainlife1 = {"sk_plr_drainlife1","0"}; +cvar_t sk_plr_drainlife2 = {"sk_plr_drainlife2","0"}; +cvar_t sk_plr_drainlife3 = {"sk_plr_drainlife3","0"}; + // Crowbar whack cvar_t sk_plr_crowbar1 = {"sk_plr_crowbar1","0"}; cvar_t sk_plr_crowbar2 = {"sk_plr_crowbar2","0"}; @@ -308,13 +572,6 @@ cvar_t sk_plr_9mmAR_grenade1 = {"sk_plr_9mmAR_grenade1","0"}; cvar_t sk_plr_9mmAR_grenade2 = {"sk_plr_9mmAR_grenade2","0"}; cvar_t sk_plr_9mmAR_grenade3 = {"sk_plr_9mmAR_grenade3","0"}; - -// Shotgun buckshot -cvar_t sk_plr_buckshot1 = {"sk_plr_buckshot1","0"}; -cvar_t sk_plr_buckshot2 = {"sk_plr_buckshot2","0"}; -cvar_t sk_plr_buckshot3 = {"sk_plr_buckshot3","0"}; - - // Crossbow cvar_t sk_plr_xbow_bolt_client1 = {"sk_plr_xbow_bolt_client1","0"}; cvar_t sk_plr_xbow_bolt_client2 = {"sk_plr_xbow_bolt_client2","0"}; @@ -385,13 +642,13 @@ cvar_t sk_hornet_dmg2 = {"sk_hornet_dmg2","0"}; cvar_t sk_hornet_dmg3 = {"sk_hornet_dmg3","0"}; // HEALTH/CHARGE -cvar_t sk_suitcharger1 = { "sk_suitcharger1","0" }; -cvar_t sk_suitcharger2 = { "sk_suitcharger2","0" }; -cvar_t sk_suitcharger3 = { "sk_suitcharger3","0" }; +// cvar_t sk_suitcharger1 = { "sk_suitcharger1","0" }; +// cvar_t sk_suitcharger2 = { "sk_suitcharger2","0" }; +// cvar_t sk_suitcharger3 = { "sk_suitcharger3","0" }; -cvar_t sk_battery1 = { "sk_battery1","0" }; -cvar_t sk_battery2 = { "sk_battery2","0" }; -cvar_t sk_battery3 = { "sk_battery3","0" }; +// cvar_t sk_battery1 = { "sk_battery1","0" }; +// cvar_t sk_battery2 = { "sk_battery2","0" }; +// cvar_t sk_battery3 = { "sk_battery3","0" }; cvar_t sk_healthcharger1 = { "sk_healthcharger1","0" }; cvar_t sk_healthcharger2 = { "sk_healthcharger2","0" }; @@ -492,6 +749,11 @@ void GameDLLInit( void ) CVAR_REGISTER( &mp_chattime ); // REGISTER CVARS FOR SKILL LEVEL STUFF + // Panic duration from the Dread Name + CVAR_REGISTER( &sk_panic_duration1 );// {"sk_panic_duration1","0"}; + CVAR_REGISTER( &sk_panic_duration2 );// {"sk_panic_duration2","0"}; + CVAR_REGISTER( &sk_panic_duration3 );// {"sk_panic_duration3","0"}; + // Agrunt CVAR_REGISTER( &sk_agrunt_health1 );// {"sk_agrunt_health1","0"}; CVAR_REGISTER( &sk_agrunt_health2 );// {"sk_agrunt_health2","0"}; @@ -511,6 +773,23 @@ void GameDLLInit( void ) CVAR_REGISTER( &sk_barney_health2 );// {"sk_barney_health2","0"}; CVAR_REGISTER( &sk_barney_health3 );// {"sk_barney_health3","0"}; + // Cthonian + CVAR_REGISTER( &sk_cthonian_health1 );// {"sk_cthonian_health1","0"}; + CVAR_REGISTER( &sk_cthonian_health2 );// {"sk_cthonian_health2","0"}; + CVAR_REGISTER( &sk_cthonian_health3 );// {"sk_cthonian_health3","0"}; + + CVAR_REGISTER( &sk_cthonian_dmg_bite1 );// {"sk_cthonian_dmg_bite1","0"}; + CVAR_REGISTER( &sk_cthonian_dmg_bite2 );// {"sk_cthonian_dmg_bite2","0"}; + CVAR_REGISTER( &sk_cthonian_dmg_bite3 );// {"sk_cthonian_dmg_bite3","0"}; + + CVAR_REGISTER( &sk_cthonian_dmg_whip1 );// {"sk_cthonian_dmg_whip1","0"}; + CVAR_REGISTER( &sk_cthonian_dmg_whip2 );// {"sk_cthonian_dmg_whip2","0"}; + CVAR_REGISTER( &sk_cthonian_dmg_whip3 );// {"sk_cthonian_dmg_whip3","0"}; + + CVAR_REGISTER( &sk_cthonian_dmg_spit1 );// {"sk_cthonian_dmg_spit1","0"}; + CVAR_REGISTER( &sk_cthonian_dmg_spit2 );// {"sk_cthonian_dmg_spit2","0"}; + CVAR_REGISTER( &sk_cthonian_dmg_spit3 );// {"sk_cthonian_dmg_spit3","0"}; + // Bullsquid CVAR_REGISTER( &sk_bullsquid_health1 );// {"sk_bullsquid_health1","0"}; CVAR_REGISTER( &sk_bullsquid_health2 );// {"sk_bullsquid_health2","0"}; @@ -575,6 +854,32 @@ void GameDLLInit( void ) CVAR_REGISTER( &sk_headcrab_dmg_bite2 );// {"sk_headcrab_dmg_bite2","0"}; CVAR_REGISTER( &sk_headcrab_dmg_bite3 );// {"sk_headcrab_dmg_bite3","0"}; + // Gangster + CVAR_REGISTER( &sk_gangster_health1 );// {"sk_gangster_health1","0"}; + CVAR_REGISTER( &sk_gangster_health2 );// {"sk_gangster_health2","0"}; + CVAR_REGISTER( &sk_gangster_health3 );// {"sk_gangster_health3","0"}; + + CVAR_REGISTER( &sk_gangster_kick1 );// {"sk_gangster_kick1","0"}; + CVAR_REGISTER( &sk_gangster_kick2 );// {"sk_gangster_kick2","0"}; + CVAR_REGISTER( &sk_gangster_kick3 );// {"sk_gangster_kick3","0"}; + + CVAR_REGISTER( &sk_gangster_pellets1 ); + CVAR_REGISTER( &sk_gangster_pellets2 ); + CVAR_REGISTER( &sk_gangster_pellets3 ); + + // Cultist + CVAR_REGISTER( &sk_cultist_health1 );// {"sk_cultist_health1","0"}; + CVAR_REGISTER( &sk_cultist_health2 );// {"sk_cultist_health2","0"}; + CVAR_REGISTER( &sk_cultist_health3 );// {"sk_cultist_health3","0"}; + + CVAR_REGISTER( &sk_cultist_kick1 );// {"sk_cultist_kick1","0"}; + CVAR_REGISTER( &sk_cultist_kick2 );// {"sk_cultist_kick2","0"}; + CVAR_REGISTER( &sk_cultist_kick3 );// {"sk_cultist_kick3","0"}; + + CVAR_REGISTER( &sk_cultist_pellets1 ); + CVAR_REGISTER( &sk_cultist_pellets2 ); + CVAR_REGISTER( &sk_cultist_pellets3 ); + // Hgrunt CVAR_REGISTER( &sk_hgrunt_health1 );// {"sk_hgrunt_health1","0"}; CVAR_REGISTER( &sk_hgrunt_health2 );// {"sk_hgrunt_health2","0"}; @@ -601,6 +906,69 @@ void GameDLLInit( void ) CVAR_REGISTER( &sk_houndeye_dmg_blast2 );// {"sk_houndeye_dmg_blast2","0"}; CVAR_REGISTER( &sk_houndeye_dmg_blast3 );// {"sk_houndeye_dmg_blast3","0"}; + // Great Race + CVAR_REGISTER( &sk_great_race_health1 );// {"sk_great_race_health1","0"}; + CVAR_REGISTER( &sk_great_race_health2 );// {"sk_great_race_health2","0"}; + CVAR_REGISTER( &sk_great_race_health3 );// {"sk_great_race_health3","0"}; + + CVAR_REGISTER( &sk_great_race_dmg_claw1 );// {"sk_great_race_dmg_claw1","0"}; + CVAR_REGISTER( &sk_great_race_dmg_claw2 );// {"sk_great_race_dmg_claw2","0"}; + CVAR_REGISTER( &sk_great_race_dmg_claw3 );// {"sk_great_race_dmg_claw3","0"}; + + CVAR_REGISTER( &sk_great_race_dmg_clawrake1 );// {"sk_great_race_dmg_clawrake1","0"}; + CVAR_REGISTER( &sk_great_race_dmg_clawrake2 );// {"sk_great_race_dmg_clawrake2","0"}; + CVAR_REGISTER( &sk_great_race_dmg_clawrake3 );// {"sk_great_race_dmg_clawrake3","0"}; + + CVAR_REGISTER( &sk_great_race_dmg_zap1 );// {"sk_great_race_dmg_zap1","0"}; + CVAR_REGISTER( &sk_great_race_dmg_zap2 );// {"sk_great_race_dmg_zap2","0"}; + CVAR_REGISTER( &sk_great_race_dmg_zap3 );// {"sk_great_race_dmg_zap3","0"}; + + + // Yodan + CVAR_REGISTER( &sk_yodan_health1 );// {"sk_yodan_health1","0"}; + CVAR_REGISTER( &sk_yodan_health2 );// {"sk_yodan_health2","0"}; + CVAR_REGISTER( &sk_yodan_health3 );// {"sk_yodan_health3","0"}; + + CVAR_REGISTER( &sk_yodan_dmg_claw1 );// {"sk_yodan_dmg_claw1","0"}; + CVAR_REGISTER( &sk_yodan_dmg_claw2 );// {"sk_yodan_dmg_claw2","0"}; + CVAR_REGISTER( &sk_yodan_dmg_claw3 );// {"sk_yodan_dmg_claw3","0"}; + + CVAR_REGISTER( &sk_yodan_dmg_clawrake1 );// {"sk_yodan_dmg_clawrake1","0"}; + CVAR_REGISTER( &sk_yodan_dmg_clawrake2 );// {"sk_yodan_dmg_clawrake2","0"}; + CVAR_REGISTER( &sk_yodan_dmg_clawrake3 );// {"sk_yodan_dmg_clawrake3","0"}; + + CVAR_REGISTER( &sk_yodan_dmg_zap1 );// {"sk_yodan_dmg_zap1","0"}; + CVAR_REGISTER( &sk_yodan_dmg_zap2 );// {"sk_yodan_dmg_zap2","0"}; + CVAR_REGISTER( &sk_yodan_dmg_zap3 );// {"sk_yodan_dmg_zap3","0"}; + + + // Serpent Man + CVAR_REGISTER( &sk_serpent_man_health1 );// {"sk_serpent_man_health1","0"}; + CVAR_REGISTER( &sk_serpent_man_health2 );// {"sk_serpent_man_health2","0"}; + CVAR_REGISTER( &sk_serpent_man_health3 );// {"sk_serpent_man_health3","0"}; + + CVAR_REGISTER( &sk_serpent_man_dmg_staff1 );// {"sk_serpent_man_dmg_staff1","0"}; + CVAR_REGISTER( &sk_serpent_man_dmg_staff2 );// {"sk_serpent_man_dmg_staff2","0"}; + CVAR_REGISTER( &sk_serpent_man_dmg_staff3 );// {"sk_serpent_man_dmg_staff3","0"}; + + CVAR_REGISTER( &sk_serpent_man_dmg_bite1 );// {"sk_serpent_man_dmg_bite1","0"}; + CVAR_REGISTER( &sk_serpent_man_dmg_bite2 );// {"sk_serpent_man_dmg_bite2","0"}; + CVAR_REGISTER( &sk_serpent_man_dmg_bite3 );// {"sk_serpent_man_dmg_bite3","0"}; + + CVAR_REGISTER( &sk_serpent_man_dmg_zap1 );// {"sk_serpent_man_dmg_zap1","0"}; + CVAR_REGISTER( &sk_serpent_man_dmg_zap2 );// {"sk_serpent_man_dmg_zap2","0"}; + CVAR_REGISTER( &sk_serpent_man_dmg_zap3 );// {"sk_serpent_man_dmg_zap3","0"}; + + + // Priest + CVAR_REGISTER( &sk_priest_health1 );// {"sk_priest_health1","0"}; + CVAR_REGISTER( &sk_priest_health2 );// {"sk_priest_health2","0"}; + CVAR_REGISTER( &sk_priest_health3 );// {"sk_priest_health3","0"}; + + CVAR_REGISTER( &sk_priest_dmg_knife1 );// {"sk_priest_dmg_knife1","0"}; + CVAR_REGISTER( &sk_priest_dmg_knife2 );// {"sk_priest_dmg_knife2","0"}; + CVAR_REGISTER( &sk_priest_dmg_knife3 );// {"sk_priest_dmg_knife3","0"}; + // ISlave CVAR_REGISTER( &sk_islave_health1 );// {"sk_islave_health1","0"}; CVAR_REGISTER( &sk_islave_health2 );// {"sk_islave_health2","0"}; @@ -627,6 +995,15 @@ void GameDLLInit( void ) CVAR_REGISTER( &sk_ichthyosaur_shake2 );// {"sk_ichthyosaur_health3","0"}; CVAR_REGISTER( &sk_ichthyosaur_shake3 );// {"sk_ichthyosaur_health3","0"}; + // Hunting Horror + CVAR_REGISTER( &sk_huntinghorror_health1 );// {"sk_huntinghorror_health1","0"}; + CVAR_REGISTER( &sk_huntinghorror_health2 );// {"sk_huntinghorror_health2","0"}; + CVAR_REGISTER( &sk_huntinghorror_health3 );// {"sk_huntinghorror_health3","0"}; + + CVAR_REGISTER( &sk_huntinghorror_bite1 );// {"sk_huntinghorror_bite1","0"}; + CVAR_REGISTER( &sk_huntinghorror_bite2 );// {"sk_huntinghorror_bite2","0"}; + CVAR_REGISTER( &sk_huntinghorror_bite3 );// {"sk_huntinghorror_bite3","0"}; + // Leech CVAR_REGISTER( &sk_leech_health1 );// {"sk_leech_health1","0"}; CVAR_REGISTER( &sk_leech_health2 );// {"sk_leech_health2","0"}; @@ -667,6 +1044,24 @@ void GameDLLInit( void ) CVAR_REGISTER( &sk_scientist_health2 );// {"sk_scientist_health2","0"}; CVAR_REGISTER( &sk_scientist_health3 );// {"sk_scientist_health3","0"}; + // Butler + CVAR_REGISTER( &sk_butler_health1 );// {"sk_butler_health1","0"}; + CVAR_REGISTER( &sk_butler_health2 );// {"sk_butler_health2","0"}; + CVAR_REGISTER( &sk_butler_health3 );// {"sk_butler_health3","0"}; + + // SirHenry + CVAR_REGISTER( &sk_sirhenry_health1 );// {"sk_sirhenry_health1","0"}; + CVAR_REGISTER( &sk_sirhenry_health2 );// {"sk_sirhenry_health2","0"}; + CVAR_REGISTER( &sk_sirhenry_health3 );// {"sk_sirhenry_health3","0"}; + + CVAR_REGISTER( &sk_sirhenry_dmg_zap1 );// {"sk_sirhenry_dmg_zap1","0"}; + CVAR_REGISTER( &sk_sirhenry_dmg_zap2 );// {"sk_sirhenry_dmg_zap2","0"}; + CVAR_REGISTER( &sk_sirhenry_dmg_zap3 );// {"sk_sirhenry_dmg_zap3","0"}; + + CVAR_REGISTER( &sk_sirhenry_dmg_knife1 );// {"sk_sirhenry_dmg_knife1","0"}; + CVAR_REGISTER( &sk_sirhenry_dmg_knife2 );// {"sk_sirhenry_dmg_knife2","0"}; + CVAR_REGISTER( &sk_sirhenry_dmg_knife3 );// {"sk_sirhenry_dmg_knife3","0"}; + // Snark CVAR_REGISTER( &sk_snark_health1 );// {"sk_snark_health1","0"}; CVAR_REGISTER( &sk_snark_health2 );// {"sk_snark_health2","0"}; @@ -680,6 +1075,77 @@ void GameDLLInit( void ) CVAR_REGISTER( &sk_snark_dmg_pop2 );// {"sk_snark_dmg_pop2","0"}; CVAR_REGISTER( &sk_snark_dmg_pop3 );// {"sk_snark_dmg_pop3","0"}; + // Formless Spawn + CVAR_REGISTER( &sk_formless_spawn_health1 );// {"sk_formless_spawn_health1","0"}; + CVAR_REGISTER( &sk_formless_spawn_health2 );// {"sk_formless_spawn_health3","0"}; + CVAR_REGISTER( &sk_formless_spawn_health3 );// {"sk_formless_spawn_health3","0"}; + + CVAR_REGISTER( &sk_formless_spawn_dmg_attack1 );// {"sk_formless_spawn_dmg_attack1","0"}; + CVAR_REGISTER( &sk_formless_spawn_dmg_attack2 );// {"sk_formless_spawn_dmg_attack2","0"}; + CVAR_REGISTER( &sk_formless_spawn_dmg_attack3 );// {"sk_formless_spawn_dmg_attack3","0"}; + + + // Ghoul + CVAR_REGISTER( &sk_ghoul_health1 );// {"sk_ghoul_health1","0"}; + CVAR_REGISTER( &sk_ghoul_health2 );// {"sk_ghoul_health3","0"}; + CVAR_REGISTER( &sk_ghoul_health3 );// {"sk_ghoul_health3","0"}; + + CVAR_REGISTER( &sk_ghoul_dmg_one_slash1 );// {"sk_ghoul_dmg_one_slash1","0"}; + CVAR_REGISTER( &sk_ghoul_dmg_one_slash2 );// {"sk_ghoul_dmg_one_slash2","0"}; + CVAR_REGISTER( &sk_ghoul_dmg_one_slash3 );// {"sk_ghoul_dmg_one_slash3","0"}; + + CVAR_REGISTER( &sk_ghoul_dmg_both_slash1 );// {"sk_ghoul_dmg_both_slash1","0"}; + CVAR_REGISTER( &sk_ghoul_dmg_both_slash2 );// {"sk_ghoul_dmg_both_slash2","0"}; + CVAR_REGISTER( &sk_ghoul_dmg_both_slash3 );// {"sk_ghoul_dmg_both_slash3","0"}; + + + // Night Gaunt + CVAR_REGISTER( &sk_nightgaunt_health1 );// {"sk_nightgaunt_health1","0"}; + CVAR_REGISTER( &sk_nightgaunt_health2 );// {"sk_nightgaunt_health3","0"}; + CVAR_REGISTER( &sk_nightgaunt_health3 );// {"sk_nightgaunt_health3","0"}; + + CVAR_REGISTER( &sk_nightgaunt_dmg_slash1 );// {"sk_nightgaunt_dmg_slash1","0"}; + CVAR_REGISTER( &sk_nightgaunt_dmg_slash2 );// {"sk_nightgaunt_dmg_slash2","0"}; + CVAR_REGISTER( &sk_nightgaunt_dmg_slash3 );// {"sk_nightgaunt_dmg_slash3","0"}; + + + // Snake + CVAR_REGISTER( &sk_snake_health1 );// {"sk_snake_health1","0"}; + CVAR_REGISTER( &sk_snake_health2 );// {"sk_snake_health3","0"}; + CVAR_REGISTER( &sk_snake_health3 );// {"sk_snake_health3","0"}; + + CVAR_REGISTER( &sk_snake_dmg_bite1 );// {"sk_snake_dmg_bite1","0"}; + CVAR_REGISTER( &sk_snake_dmg_bite2 );// {"sk_snake_dmg_bite2","0"}; + CVAR_REGISTER( &sk_snake_dmg_bite3 );// {"sk_snake_dmg_bite3","0"}; + + + // Deep One + CVAR_REGISTER( &sk_deep_one_health1 );// {"sk_deep_one_health1","0"}; + CVAR_REGISTER( &sk_deep_one_health2 );// {"sk_deep_one_health3","0"}; + CVAR_REGISTER( &sk_deep_one_health3 );// {"sk_deep_one_health3","0"}; + + CVAR_REGISTER( &sk_deep_one_dmg_one_slash1 );// {"sk_deep_one_dmg_one_slash1","0"}; + CVAR_REGISTER( &sk_deep_one_dmg_one_slash2 );// {"sk_deep_one_dmg_one_slash2","0"}; + CVAR_REGISTER( &sk_deep_one_dmg_one_slash3 );// {"sk_deep_one_dmg_one_slash3","0"}; + + CVAR_REGISTER( &sk_deep_one_dmg_both_slash1 );// {"sk_deep_one_dmg_both_slash1","0"}; + CVAR_REGISTER( &sk_deep_one_dmg_both_slash2 );// {"sk_deep_one_dmg_both_slash2","0"}; + CVAR_REGISTER( &sk_deep_one_dmg_both_slash3 );// {"sk_deep_one_dmg_both_slash3","0"}; + + + // Dimensional Shambler + CVAR_REGISTER( &sk_shambler_health1 );// {"sk_shambler_health1","0"}; + CVAR_REGISTER( &sk_shambler_health2 );// {"sk_shambler_health3","0"}; + CVAR_REGISTER( &sk_shambler_health3 );// {"sk_shambler_health3","0"}; + + CVAR_REGISTER( &sk_shambler_dmg_one_slash1 );// {"sk_shambler_dmg_one_slash1","0"}; + CVAR_REGISTER( &sk_shambler_dmg_one_slash2 );// {"sk_shambler_dmg_one_slash2","0"}; + CVAR_REGISTER( &sk_shambler_dmg_one_slash3 );// {"sk_shambler_dmg_one_slash3","0"}; + + CVAR_REGISTER( &sk_shambler_dmg_both_slash1 );// {"sk_shambler_dmg_both_slash1","0"}; + CVAR_REGISTER( &sk_shambler_dmg_both_slash2 );// {"sk_shambler_dmg_both_slash2","0"}; + CVAR_REGISTER( &sk_shambler_dmg_both_slash3 );// {"sk_shambler_dmg_both_slash3","0"}; + // Zombie CVAR_REGISTER( &sk_zombie_health1 );// {"sk_zombie_health1","0"}; CVAR_REGISTER( &sk_zombie_health2 );// {"sk_zombie_health3","0"}; @@ -711,6 +1177,69 @@ void GameDLLInit( void ) // PLAYER WEAPONS + // Swordcane + CVAR_REGISTER( &sk_plr_swordcane1 );// {"sk_plr_swordcane1","0"}; + CVAR_REGISTER( &sk_plr_swordcane2 );// {"sk_plr_swordcane2","0"}; + CVAR_REGISTER( &sk_plr_swordcane3 );// {"sk_plr_swordcane3","0"}; + + // Knife + CVAR_REGISTER( &sk_plr_knife1 );// {"sk_plr_knife1","0"}; + CVAR_REGISTER( &sk_plr_knife2 );// {"sk_plr_knife2","0"}; + CVAR_REGISTER( &sk_plr_knife3 );// {"sk_plr_knife3","0"}; + + // Revolver + CVAR_REGISTER( &sk_plr_revolver1 );// {"sk_plr_revolver1","0"}; + CVAR_REGISTER( &sk_plr_revolver2 );// {"sk_plr_revolver2","0"}; + CVAR_REGISTER( &sk_plr_revolver3 );// {"sk_plr_revolver3","0"}; + + // Shotgun buckshot + CVAR_REGISTER( &sk_plr_shotgun1 );// {"sk_plr_shot1gun","0"}; + CVAR_REGISTER( &sk_plr_shotgun2 );// {"sk_plr_shot2gun","0"}; + CVAR_REGISTER( &sk_plr_shotgun3 );// {"sk_plr_shotgun3","0"}; + + // Tommy Gun + CVAR_REGISTER( &sk_plr_tommygun1 );// {"sk_plr_tommygun1","0"}; + CVAR_REGISTER( &sk_plr_tommygun2 );// {"sk_plr_tommygun2","0"}; + CVAR_REGISTER( &sk_plr_tommygun3 );// {"sk_plr_tommygun3","0"}; + + // Rifle + CVAR_REGISTER( &sk_plr_rifle1 );// {"sk_plr_rifle1","0"}; + CVAR_REGISTER( &sk_plr_rifle2 );// {"sk_plr_rifle2","0"}; + CVAR_REGISTER( &sk_plr_rifle3 );// {"sk_plr_rifle3","0"}; + + // Dynamite + CVAR_REGISTER( &sk_plr_dynamite1 );// {"sk_plr_dynamite1","0"}; + CVAR_REGISTER( &sk_plr_dynamite2 );// {"sk_plr_dynamite2","0"}; + CVAR_REGISTER( &sk_plr_dynamite3 );// {"sk_plr_dynamite3","0"}; + + // Molotov + CVAR_REGISTER( &sk_plr_molotov1 );// {"sk_plr_molotov1","0"}; + CVAR_REGISTER( &sk_plr_molotov2 );// {"sk_plr_molotov2","0"}; + CVAR_REGISTER( &sk_plr_molotov3 );// {"sk_plr_molotov3","0"}; + + // Lightning + CVAR_REGISTER( &sk_plr_lightning1 );// {"sk_plr_lightning1","0"}; + CVAR_REGISTER( &sk_plr_lightning2 );// {"sk_plr_lightning2","0"}; + CVAR_REGISTER( &sk_plr_lightning3 );// {"sk_plr_lightning3","0"}; + + // Shrivelling Narrow + CVAR_REGISTER( &sk_plr_shrivelling_narrow1 );// {"sk_plr_shrivelling_narrow1","0"}; + CVAR_REGISTER( &sk_plr_shrivelling_narrow2 );// {"sk_plr_shrivelling_narrow2","0"}; + CVAR_REGISTER( &sk_plr_shrivelling_narrow3 );// {"sk_plr_shrivelling_narrow3","0"}; + + // Shrivelling Wide + CVAR_REGISTER( &sk_plr_shrivelling_wide1 );// {"sk_plr_shrivelling_wide1","0"}; + CVAR_REGISTER( &sk_plr_shrivelling_wide2 );// {"sk_plr_shrivelling_wide2","0"}; + CVAR_REGISTER( &sk_plr_shrivelling_wide3 );// {"sk_plr_shrivelling_wide3","0"}; + + // Drain Life + CVAR_REGISTER( &sk_plr_drainlife1 );// {"sk_plr_drainlife1","0"}; + CVAR_REGISTER( &sk_plr_drainlife2 );// {"sk_plr_drainlife2","0"}; + CVAR_REGISTER( &sk_plr_drainlife3 );// {"sk_plr_drainlife3","0"}; + + + // OLD WEAPONS + // Crowbar whack CVAR_REGISTER( &sk_plr_crowbar1 );// {"sk_plr_crowbar1","0"}; CVAR_REGISTER( &sk_plr_crowbar2 );// {"sk_plr_crowbar2","0"}; @@ -736,11 +1265,6 @@ void GameDLLInit( void ) CVAR_REGISTER( &sk_plr_9mmAR_grenade2 );// {"sk_plr_9mmAR_grenade2","0"}; CVAR_REGISTER( &sk_plr_9mmAR_grenade3 );// {"sk_plr_9mmAR_grenade3","0"}; - // Shotgun buckshot - CVAR_REGISTER( &sk_plr_buckshot1 );// {"sk_plr_buckshot1","0"}; - CVAR_REGISTER( &sk_plr_buckshot2 );// {"sk_plr_buckshot2","0"}; - CVAR_REGISTER( &sk_plr_buckshot3 );// {"sk_plr_buckshot3","0"}; - // Crossbow CVAR_REGISTER( &sk_plr_xbow_bolt_monster1 );// {"sk_plr_xbow_bolt1","0"}; CVAR_REGISTER( &sk_plr_xbow_bolt_monster2 );// {"sk_plr_xbow_bolt2","0"}; @@ -803,13 +1327,13 @@ void GameDLLInit( void ) CVAR_REGISTER( &sk_hornet_dmg3 );// {"sk_hornet_dmg3","0"}; // HEALTH/SUIT CHARGE DISTRIBUTION - CVAR_REGISTER( &sk_suitcharger1 ); - CVAR_REGISTER( &sk_suitcharger2 ); - CVAR_REGISTER( &sk_suitcharger3 ); + // CVAR_REGISTER( &sk_suitcharger1 ); + // CVAR_REGISTER( &sk_suitcharger2 ); + // CVAR_REGISTER( &sk_suitcharger3 ); - CVAR_REGISTER( &sk_battery1 ); - CVAR_REGISTER( &sk_battery2 ); - CVAR_REGISTER( &sk_battery3 ); + // CVAR_REGISTER( &sk_battery1 ); + // CVAR_REGISTER( &sk_battery2 ); + // CVAR_REGISTER( &sk_battery3 ); CVAR_REGISTER( &sk_healthcharger1 ); CVAR_REGISTER( &sk_healthcharger2 ); diff --git a/dlls/gamerules.cpp b/dlls/gamerules.cpp index 9d7084c5..354d1f1d 100644 --- a/dlls/gamerules.cpp +++ b/dlls/gamerules.cpp @@ -136,6 +136,9 @@ void CGameRules::RefreshSkillData ( void ) ALERT( at_console, "\nGAME SKILL LEVEL:%d\n",iSkill ); + // how long monsters flee from the Dread Name + gSkillData.panicDuration = GetSkillCvar( "sk_panic_duration" ); + //Agrunt gSkillData.agruntHealth = GetSkillCvar( "sk_agrunt_health" ); gSkillData.agruntDmgPunch = GetSkillCvar( "sk_agrunt_dmg_punch" ); @@ -152,6 +155,12 @@ void CGameRules::RefreshSkillData ( void ) gSkillData.bigmommaDmgBlast = GetSkillCvar( "sk_bigmomma_dmg_blast" ); gSkillData.bigmommaRadiusBlast = GetSkillCvar( "sk_bigmomma_radius_blast" ); + // Cthonian + gSkillData.cthonianHealth = GetSkillCvar( "sk_cthonian_health" ); + gSkillData.cthonianDmgBite = GetSkillCvar( "sk_cthonian_dmg_bite" ); + gSkillData.cthonianDmgWhip = GetSkillCvar( "sk_cthonian_dmg_whip" ); + gSkillData.cthonianDmgSpit = GetSkillCvar( "sk_cthonian_dmg_spit" ); + // Bullsquid gSkillData.bullsquidHealth = GetSkillCvar( "sk_bullsquid_health" ); gSkillData.bullsquidDmgBite = GetSkillCvar( "sk_bullsquid_dmg_bite" ); @@ -171,6 +180,16 @@ void CGameRules::RefreshSkillData ( void ) gSkillData.headcrabHealth = GetSkillCvar( "sk_headcrab_health" ); gSkillData.headcrabDmgBite = GetSkillCvar( "sk_headcrab_dmg_bite" ); + // Gangster + gSkillData.gangsterHealth = GetSkillCvar( "sk_gangster_health" ); + gSkillData.gangsterDmgKick = GetSkillCvar( "sk_gangster_kick" ); + gSkillData.gangsterShotgunPellets = GetSkillCvar( "sk_gangster_pellets" ); + + // Cultist + gSkillData.cultistHealth = GetSkillCvar( "sk_cultist_health" ); + gSkillData.cultistDmgKick = GetSkillCvar( "sk_cultist_kick" ); + gSkillData.cultistShotgunPellets = GetSkillCvar( "sk_cultist_pellets" ); + // Hgrunt gSkillData.hgruntHealth = GetSkillCvar( "sk_hgrunt_health" ); gSkillData.hgruntDmgKick = GetSkillCvar( "sk_hgrunt_kick" ); @@ -181,6 +200,26 @@ void CGameRules::RefreshSkillData ( void ) gSkillData.houndeyeHealth = GetSkillCvar( "sk_houndeye_health" ); gSkillData.houndeyeDmgBlast = GetSkillCvar( "sk_houndeye_dmg_blast" ); + // Great Race + gSkillData.greatraceHealth = GetSkillCvar( "sk_great_race_health" ); + gSkillData.greatraceDmgClaw = GetSkillCvar( "sk_great_race_dmg_claw" ); + gSkillData.greatraceDmgClawrake = GetSkillCvar( "sk_great_race_dmg_clawrake" ); + gSkillData.greatraceDmgZap = GetSkillCvar( "sk_great_race_dmg_zap" ); + + // Yodan + gSkillData.yodanHealth = GetSkillCvar( "sk_yodan_health" ); + gSkillData.yodanDmgClaw = GetSkillCvar( "sk_yodan_dmg_claw" ); + gSkillData.yodanDmgClawrake = GetSkillCvar( "sk_yodan_dmg_clawrake" ); + gSkillData.yodanDmgZap = GetSkillCvar( "sk_yodan_dmg_zap" ); + + // Serpent Men + gSkillData.serpentmanHealth = GetSkillCvar( "sk_serpent_man_health" ); + gSkillData.serpentmanDmgStaff = GetSkillCvar( "sk_serpent_man_dmg_staff" ); + + // Priest + gSkillData.priestHealth = GetSkillCvar( "sk_priest_health" ); + gSkillData.priestDmgKnife = GetSkillCvar( "sk_priest_dmg_knife" ); + // ISlave gSkillData.slaveHealth = GetSkillCvar( "sk_islave_health" ); gSkillData.slaveDmgClaw = GetSkillCvar( "sk_islave_dmg_claw" ); @@ -191,6 +230,10 @@ void CGameRules::RefreshSkillData ( void ) gSkillData.ichthyosaurHealth = GetSkillCvar( "sk_ichthyosaur_health" ); gSkillData.ichthyosaurDmgShake = GetSkillCvar( "sk_ichthyosaur_shake" ); + // Hunting Horror + gSkillData.huntinghorrorHealth = GetSkillCvar( "sk_huntinghorror_health" ); + gSkillData.huntinghorrorDmgBite = GetSkillCvar( "sk_huntinghorror_bite" ); + // Leech gSkillData.leechHealth = GetSkillCvar( "sk_leech_health" ); @@ -209,11 +252,46 @@ void CGameRules::RefreshSkillData ( void ) // Scientist gSkillData.scientistHealth = GetSkillCvar( "sk_scientist_health" ); + // Scientist + gSkillData.butlerHealth = GetSkillCvar( "sk_butler_health" ); + + // Sir Henry + gSkillData.sirhenryHealth = GetSkillCvar( "sk_sirhenry_health" ); + gSkillData.sirhenryDmgZap = GetSkillCvar( "sk_sirhenry_dmg_zap" ); + gSkillData.sirhenryDmgKnife = GetSkillCvar( "sk_sirhenry_dmg_knife" ); + // Snark gSkillData.snarkHealth = GetSkillCvar( "sk_snark_health" ); gSkillData.snarkDmgBite = GetSkillCvar( "sk_snark_dmg_bite" ); gSkillData.snarkDmgPop = GetSkillCvar( "sk_snark_dmg_pop" ); + // Formless Spawn + gSkillData.formless_spawnHealth = GetSkillCvar( "sk_formless_spawn_health" ); + gSkillData.formless_spawnDmgAttack = GetSkillCvar( "sk_formless_spawn_dmg_attack" ); + + // Ghoul + gSkillData.ghoulHealth = GetSkillCvar( "sk_ghoul_health" ); + gSkillData.ghoulDmgOneSlash = GetSkillCvar( "sk_ghoul_dmg_one_slash" ); + gSkillData.ghoulDmgBothSlash = GetSkillCvar( "sk_ghoul_dmg_both_slash" ); + + // Night Gaunt + gSkillData.nightgauntHealth = GetSkillCvar( "sk_nightgaunt_health" ); + gSkillData.nightgauntDmgSlash = GetSkillCvar( "sk_nightgaunt_dmg_slash" ); + + // Snake + gSkillData.snakeHealth = GetSkillCvar( "sk_snake_health" ); + gSkillData.snakeDmgBite = GetSkillCvar( "sk_snake_dmg_bite" ); + + // DeepOne + gSkillData.deeponeHealth = GetSkillCvar( "sk_deep_one_health" ); + gSkillData.deeponeDmgOneSlash = GetSkillCvar( "sk_deep_one_dmg_one_slash" ); + gSkillData.deeponeDmgBothSlash = GetSkillCvar( "sk_deep_one_dmg_both_slash" ); + + // Dimensional Shambler + gSkillData.shamblerHealth = GetSkillCvar( "sk_shambler_health" ); + gSkillData.shamblerDmgOneSlash = GetSkillCvar( "sk_shambler_dmg_one_slash" ); + gSkillData.shamblerDmgBothSlash = GetSkillCvar( "sk_shambler_dmg_both_slash" ); + // Zombie gSkillData.zombieHealth = GetSkillCvar( "sk_zombie_health" ); gSkillData.zombieDmgOneSlash = GetSkillCvar( "sk_zombie_dmg_one_slash" ); @@ -230,46 +308,61 @@ void CGameRules::RefreshSkillData ( void ) // PLAYER WEAPONS + gSkillData.plrDmgSwordCane = GetSkillCvar( "sk_plr_swordcane" ); + gSkillData.plrDmgKnife = GetSkillCvar( "sk_plr_knife" ); + gSkillData.plrDmgRevolver = GetSkillCvar( "sk_plr_revolver" ); + gSkillData.plrDmgShotgun = GetSkillCvar( "sk_plr_shotgun" ); + gSkillData.plrDmgTommyGun = GetSkillCvar( "sk_plr_tommygun" ); + gSkillData.plrDmgRifle = GetSkillCvar( "sk_plr_rifle" ); + gSkillData.plrDmgDynamite = GetSkillCvar( "sk_plr_dynamite" ); + gSkillData.plrDmgMolotov = GetSkillCvar( "sk_plr_molotov" ); + gSkillData.plrDmgLightningGun = GetSkillCvar( "sk_plr_lightning" ); + gSkillData.plrDmgShrivellingNarrow = GetSkillCvar( "sk_plr_shrivelling_narrow" ); + gSkillData.plrDmgShrivellingWide = GetSkillCvar( "sk_plr_shrivelling_wide" ); + gSkillData.plrDmgDrainLife = GetSkillCvar( "sk_plr_drainlife" ); + + // OLD WEAPONS + // Crowbar whack - gSkillData.plrDmgCrowbar = GetSkillCvar( "sk_plr_crowbar" ); + // gSkillData.plrDmgCrowbar = GetSkillCvar( "sk_plr_crowbar" ); // Glock Round - gSkillData.plrDmg9MM = GetSkillCvar( "sk_plr_9mm_bullet" ); + // gSkillData.plrDmg9MM = GetSkillCvar( "sk_plr_9mm_bullet" ); // 357 Round - gSkillData.plrDmg357 = GetSkillCvar( "sk_plr_357_bullet" ); + // gSkillData.plrDmg357 = GetSkillCvar( "sk_plr_357_bullet" ); // MP5 Round - gSkillData.plrDmgMP5 = GetSkillCvar( "sk_plr_9mmAR_bullet" ); + // gSkillData.plrDmgMP5 = GetSkillCvar( "sk_plr_9mmAR_bullet" ); // M203 grenade - gSkillData.plrDmgM203Grenade = GetSkillCvar( "sk_plr_9mmAR_grenade" ); + // gSkillData.plrDmgM203Grenade = GetSkillCvar( "sk_plr_9mmAR_grenade" ); // Shotgun buckshot - gSkillData.plrDmgBuckshot = GetSkillCvar( "sk_plr_buckshot" ); + // gSkillData.plrDmgBuckshot = GetSkillCvar( "sk_plr_buckshot" ); // Crossbow - gSkillData.plrDmgCrossbowClient = GetSkillCvar( "sk_plr_xbow_bolt_client" ); - gSkillData.plrDmgCrossbowMonster = GetSkillCvar( "sk_plr_xbow_bolt_monster" ); + // gSkillData.plrDmgCrossbowClient = GetSkillCvar( "sk_plr_xbow_bolt_client" ); + // gSkillData.plrDmgCrossbowMonster = GetSkillCvar( "sk_plr_xbow_bolt_monster" ); // RPG - gSkillData.plrDmgRPG = GetSkillCvar( "sk_plr_rpg" ); + // gSkillData.plrDmgRPG = GetSkillCvar( "sk_plr_rpg" ); // Gauss gun - gSkillData.plrDmgGauss = GetSkillCvar( "sk_plr_gauss" ); + // gSkillData.plrDmgGauss = GetSkillCvar( "sk_plr_gauss" ); // Egon Gun - gSkillData.plrDmgEgonNarrow = GetSkillCvar( "sk_plr_egon_narrow" ); - gSkillData.plrDmgEgonWide = GetSkillCvar( "sk_plr_egon_wide" ); + // gSkillData.plrDmgEgonNarrow = GetSkillCvar( "sk_plr_egon_narrow" ); + // gSkillData.plrDmgEgonWide = GetSkillCvar( "sk_plr_egon_wide" ); // Hand Grendade - gSkillData.plrDmgHandGrenade = GetSkillCvar( "sk_plr_hand_grenade" ); + // gSkillData.plrDmgHandGrenade = GetSkillCvar( "sk_plr_hand_grenade" ); // Satchel Charge - gSkillData.plrDmgSatchel = GetSkillCvar( "sk_plr_satchel" ); + // gSkillData.plrDmgSatchel = GetSkillCvar( "sk_plr_satchel" ); // Tripmine - gSkillData.plrDmgTripmine = GetSkillCvar( "sk_plr_tripmine" ); + // gSkillData.plrDmgTripmine = GetSkillCvar( "sk_plr_tripmine" ); // MONSTER WEAPONS gSkillData.monDmg12MM = GetSkillCvar( "sk_12mm_bullet" ); @@ -287,9 +380,11 @@ void CGameRules::RefreshSkillData ( void ) // via SKILLS.CFG. Any player hivehand tuning must take place in the code. (sjb) gSkillData.plrDmgHornet = 7; + gSkillData.plrDmgMolotov = GetSkillCvar( "sk_plr_molotov" ); + // HEALTH/CHARGE - gSkillData.suitchargerCapacity = GetSkillCvar( "sk_suitcharger" ); - gSkillData.batteryCapacity = GetSkillCvar( "sk_battery" ); + // gSkillData.suitchargerCapacity = GetSkillCvar( "sk_suitcharger" ); + // gSkillData.batteryCapacity = GetSkillCvar( "sk_battery" ); gSkillData.healthchargerCapacity = GetSkillCvar ( "sk_healthcharger" ); gSkillData.healthkitCapacity = GetSkillCvar ( "sk_healthkit" ); gSkillData.scientistHeal = GetSkillCvar ( "sk_scientist_heal" ); diff --git a/dlls/gargantua.cpp b/dlls/gargantua.cpp index e61a2ca9..0e1f42f0 100644 --- a/dlls/gargantua.cpp +++ b/dlls/gargantua.cpp @@ -31,7 +31,7 @@ #include "explode.h" #include "func_break.h" #include "scripted.h" - +#include "spiral.h" //========================================================= // Gargantua Monster //========================================================= @@ -65,18 +65,6 @@ 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: @@ -1299,69 +1287,6 @@ void CSmoker::Think( void ) UTIL_Remove( this ); } -void CSpiral::Spawn( void ) -{ - pev->movetype = MOVETYPE_NONE; - SetNextThink( 0 ); - 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->m_fNextThink; - 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; - } - - SetNextThink( 0 ); - - 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 ) { diff --git a/dlls/genericmonster.cpp b/dlls/genericmonster.cpp index f7ad7b41..04f7008c 100644 --- a/dlls/genericmonster.cpp +++ b/dlls/genericmonster.cpp @@ -45,6 +45,8 @@ public: int ISoundMask( void ); void KeyValue( KeyValueData *pkvd ); + // virtual int ObjectCaps( void ) { return (CBaseMonster :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION); } + virtual int Save( CSave &save ); virtual int Restore( CRestore &restore ); static TYPEDESCRIPTION m_SaveData[]; @@ -84,7 +86,8 @@ void CGenericMonster::KeyValue( KeyValueData *pkvd ) //========================================================= int CGenericMonster::Classify( void ) { - return m_iClass?m_iClass:CLASS_PLAYER_ALLY; + // return m_iClass?m_iClass:CLASS_PLAYER_ALLY; + return m_iClass ? m_iClass : CLASS_NONE; } //========================================================= diff --git a/dlls/headcrab.cpp b/dlls/headcrab.cpp index 01b01fa3..5d1002c1 100644 --- a/dlls/headcrab.cpp +++ b/dlls/headcrab.cpp @@ -512,9 +512,10 @@ void CBabyCrab::Spawn( void ) 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 + UTIL_SetSize( pev, Vector( -10, -10, 0 ), Vector( 10, 10, 20 ) ); + + if (pev->health == 0) + pev->health = gSkillData.headcrabHealth * 0.25; // less health than full grown } void CBabyCrab::Precache( void ) diff --git a/dlls/items.cpp b/dlls/items.cpp index 76de7b6a..64f2685f 100644 --- a/dlls/items.cpp +++ b/dlls/items.cpp @@ -58,14 +58,15 @@ void CWorldItem::Spawn( void ) switch( m_iType ) { - case 44: // ITEM_BATTERY: - pEntity = CBaseEntity::Create( "item_battery", pev->origin, pev->angles ); - break; + // case 44: // ITEM_BATTERY: + // pEntity = CBaseEntity::Create( "item_battery", pev->origin, pev->angles ); + // break; case 42: // ITEM_ANTIDOTE: pEntity = CBaseEntity::Create( "item_antidote", pev->origin, pev->angles ); break; - case 43: // ITEM_SECURITY: - pEntity = CBaseEntity::Create( "item_security", pev->origin, pev->angles ); + case 43: // ITEM_KEY: + // pEntity = CBaseEntity::Create( "item_security", pev->origin, pev->angles ); + pEntity = CBaseEntity::Create( "item_key", pev->origin, pev->angles ); break; case 45: // ITEM_SUIT: pEntity = CBaseEntity::Create( "item_suit", pev->origin, pev->angles ); @@ -193,10 +194,12 @@ class CItemSuit : public CItem if( pPlayer->pev->weapons & ( 1<spawnflags & SF_SUIT_SHORTLOGON ) EMIT_SOUND_SUIT( pPlayer->edict(), "!HEV_A0" ); // short version of suit logon, else EMIT_SOUND_SUIT( pPlayer->edict(), "!HEV_AAx" ); // long version of suit logon + */ pPlayer->pev->weapons |= ( 1 << WEAPON_SUIT ); return TRUE; @@ -204,7 +207,7 @@ class CItemSuit : public CItem }; LINK_ENTITY_TO_CLASS( item_suit, CItemSuit ) - +/* class CItemBattery : public CItem { void Spawn( void ) @@ -274,6 +277,7 @@ class CItemBattery : public CItem }; LINK_ENTITY_TO_CLASS( item_battery, CItemBattery ) +*/ class CItemAntidote : public CItem { @@ -303,21 +307,24 @@ class CItemSecurity : public CItem void Spawn( void ) { Precache(); - SET_MODEL( ENT( pev ), "models/w_security.mdl" ); + SET_MODEL( ENT( pev ), "models/key.mdl" ); CItem::Spawn(); } void Precache( void ) { - PRECACHE_MODEL( "models/w_security.mdl" ); + PRECACHE_MODEL( "models/key.mdl" ); + PRECACHE_SOUND( "items/ammopickup1.wav" ); } BOOL MyTouch( CBasePlayer *pPlayer ) { - pPlayer->m_rgItems[ITEM_SECURITY] += 1; + pPlayer->m_rgItems[ITEM_SECURITY] += 2; + EMIT_SOUND( ENT( pPlayer->pev ), CHAN_ITEM, "items/ammopickup1.wav", 1, ATTN_NORM ); return TRUE; } }; -LINK_ENTITY_TO_CLASS( item_security, CItemSecurity ) +// LINK_ENTITY_TO_CLASS( item_security, CItemSecurity ) +LINK_ENTITY_TO_CLASS( item_key, CItemSecurity ) class CItemLongJump : public CItem { diff --git a/dlls/locus.cpp b/dlls/locus.cpp index 2e90620b..76e070cf 100644 --- a/dlls/locus.cpp +++ b/dlls/locus.cpp @@ -570,7 +570,7 @@ Vector CCalcVelocityPath::CalcVelocity( CBaseEntity *pLocus ) Vector vecOffs; float fFactor = CalcLocus_Ratio( pLocus, STRING(pev->noise) ); - switch ((int)pev->armorvalue) + switch ((int)pev->sanity) { case 0: vecOffs = CalcLocus_Position( this, pLocus, STRING(pev->netname) ) - vecStart; diff --git a/dlls/monstermaker.cpp b/dlls/monstermaker.cpp index 855f647b..fc178fcd 100644 --- a/dlls/monstermaker.cpp +++ b/dlls/monstermaker.cpp @@ -53,9 +53,11 @@ public: static TYPEDESCRIPTION m_SaveData[]; string_t m_iszMonsterClassname;// classname of the monster(s) that will be created. + string_t m_iszTargetOnChildDeath;// target to be fired whenever a child dies... int m_cNumMonsters;// max number of monsters this ent can create - + int m_iMonsterBody;// the monsters body number + 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. @@ -71,7 +73,9 @@ LINK_ENTITY_TO_CLASS( monstermaker, CMonsterMaker ) TYPEDESCRIPTION CMonsterMaker::m_SaveData[] = { DEFINE_FIELD( CMonsterMaker, m_iszMonsterClassname, FIELD_STRING ), + DEFINE_FIELD( CMonsterMaker, m_iszTargetOnChildDeath, FIELD_STRING ), DEFINE_FIELD( CMonsterMaker, m_cNumMonsters, FIELD_INTEGER ), + DEFINE_FIELD( CMonsterMaker, m_iMonsterBody, FIELD_INTEGER ), DEFINE_FIELD( CMonsterMaker, m_cLiveChildren, FIELD_INTEGER ), DEFINE_FIELD( CMonsterMaker, m_flGround, FIELD_FLOAT ), DEFINE_FIELD( CMonsterMaker, m_iMaxLiveChildren, FIELD_INTEGER ), @@ -94,6 +98,11 @@ void CMonsterMaker::KeyValue( KeyValueData *pkvd ) m_iMaxLiveChildren = atoi( pkvd->szValue ); pkvd->fHandled = TRUE; } + else if( FStrEq(pkvd->szKeyName, "monsterbody" ) ) + { + m_iMonsterBody = atoi( pkvd->szValue ); + pkvd->fHandled = TRUE; + } else if( FStrEq( pkvd->szKeyName, "monstertype" ) ) { m_iszMonsterClassname = ALLOC_STRING( pkvd->szValue ); @@ -104,6 +113,11 @@ void CMonsterMaker::KeyValue( KeyValueData *pkvd ) m_fSpawnDelay = atof( pkvd->szValue ); pkvd->fHandled = TRUE; } + else if( FStrEq(pkvd->szKeyName, "targetondeath" ) ) + { + m_iszTargetOnChildDeath = ALLOC_STRING( pkvd->szValue ); + pkvd->fHandled = TRUE; + } else CBaseMonster::KeyValue( pkvd ); } @@ -274,6 +288,9 @@ CBaseMonster* CMonsterMaker::MakeMonster( void ) pMonst->m_iPlayerReact = this->m_iPlayerReact; } + // Cthulhu: allow monster maker to set the body... + pEntity->pev->body = m_iMonsterBody; + if( !FStringNull( pev->netname ) ) { // if I have a netname (overloaded), give the child monster that name as a targetname @@ -351,4 +368,13 @@ void CMonsterMaker::DeathNotice( entvars_t *pevChild ) { pevChild->owner = NULL; } + + // do we need to fire a target? + CBaseEntity *pTarget = NULL; + pTarget = UTIL_FindEntityByTargetname( pTarget, STRING( m_iszTargetOnChildDeath ) ); + + if( !FNullEnt( pTarget ) ) + { + pTarget->Use( NULL, this, USE_TOGGLE, 0 ); + } } diff --git a/dlls/monsters.cpp b/dlls/monsters.cpp index 83338922..0173e0b7 100644 --- a/dlls/monsters.cpp +++ b/dlls/monsters.cpp @@ -146,6 +146,27 @@ int CBaseMonster::Restore( CRestore &restore ) return status; } +//========================================================= +// Panic - makes a monster panic for a little while. +//========================================================= +void CBaseMonster::Panic( entvars_t *pevPanic ) +{ + float flPanicDuration = gSkillData.panicDuration; + + Schedule_t *pNewSchedule; + + pNewSchedule = GetScheduleOfType( SCHED_DREAD_NAME_PANIC ); + + if( pNewSchedule ) + { + // Get the last task and set the data depending on the monsters + // own panic time + pNewSchedule->pTasklist[5].flData = flPanicDuration; + + ChangeSchedule( pNewSchedule ); + } +} + //========================================================= // Eat - makes a monster full for a little while. //========================================================= @@ -409,6 +430,7 @@ int CBaseMonster::ISoundMask( void ) { return bits_SOUND_WORLD | bits_SOUND_COMBAT | + bits_SOUND_DANGER | bits_SOUND_PLAYER; } @@ -595,7 +617,16 @@ int CBaseMonster::IgnoreConditions( void ) } if( m_MonsterState == MONSTERSTATE_SCRIPT && m_pCine ) - iIgnoreConditions |= m_pCine->IgnoreConditions(); + { + int tempIgnore = m_pCine->IgnoreConditions(); + + // if our trigger condition is "take damage", we do not want to forget the taken damage + if( m_iTriggerCondition == AITRIGGER_TAKEDAMAGE ) + { + tempIgnore &= ~( bits_COND_LIGHT_DAMAGE | bits_COND_HEAVY_DAMAGE ); + } + iIgnoreConditions |= tempIgnore; + } return iIgnoreConditions; } @@ -960,6 +991,21 @@ BOOL CBaseMonster::CheckRangeAttack2( float flDot, float flDist ) 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) + //int iGround = FBitSet( m_hEnemy->pev->flags, FL_ONGROUND ); + + // Cthulhu: but this stops the monster from hitting flying monsters that are + // melee attacking it (e.g. nightgaunt) + // Solution: explicitly check for monster types that we cannot hit + + BOOL bHit = TRUE; // we can hit by default + if( m_hEnemy ) + { + if( FClassnameIs( m_hEnemy->pev, "hornet" ) + || FClassnameIs( m_hEnemy->pev, "monster_snark" ) ) + bHit = FALSE; + } + + // if( flDist <= 64 && flDot >= 0.7 && m_hEnemy != NULL && iGround ) if( flDist <= 64 && flDot >= 0.7 && m_hEnemy != 0 && FBitSet( m_hEnemy->pev->flags, FL_ONGROUND ) ) { return TRUE; @@ -2003,6 +2049,10 @@ void CBaseMonster::MonsterInit( void ) if( pev->spawnflags & SF_MONSTER_HITMONSTERCLIP ) pev->flags |= FL_MONSTERCLIP; + // all monster avoid burning molotov unless they are immune to fire + if( !FBitSet( pev->flags,FL_IMMUNE_LAVA ) ) + pev->flags |= FL_BURNING_CLIP; + ClearSchedule(); RouteClear(); InitBoneControllers( ); // FIX: should be done in Spawn @@ -2167,26 +2217,29 @@ int CBaseMonster::TaskIsRunning( void ) //========================================================= int CBaseMonster::IRelationship( CBaseEntity *pTarget ) { - static int iEnemy[17][17] = - { // NONE MACH PLYR HPASS HMIL AMIL APASS AMONST APREY APRED INSECT PLRALY PBWPN ABWPN FACT_A FACT_B FACT_C - /*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, 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, 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, R_DL, 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, R_DL, R_DL, R_DL }, - /*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, R_DL, R_DL, R_DL }, - /*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, R_DL, R_DL, R_DL }, - /*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, R_DL, R_DL, R_DL }, - /*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, R_DL, R_DL, R_DL }, - /*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, R_DL, R_DL, R_DL }, - /*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, R_DL, R_DL, R_DL }, - /*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, R_FR, R_FR, R_FR }, - /*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, R_DL, R_DL, R_DL }, - /*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, R_DL, R_DL, 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, R_DL, R_DL, R_DL }, - /*FACTION_A*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_AL, R_DL, R_DL }, - /*FACTION_B*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_AL, R_DL }, - /*FACTION_C*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_AL } - }; + static int iEnemy[20][20] = + { // NONE MACH PLYR HPASS HMIL AMIL APASS AMONST APREY APRED INSECT PLRALY PBWPN ABWPN CULTIST PREY PRED FACT_A FACT_B FACT_C + /*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, 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, R_NO, R_NO, R_DL, 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, R_HT, R_NO, R_DL, R_DL, R_DL, R_DL }, + /*HUMANPASSIVE*/{ R_NO, R_NO, R_AL, R_AL, R_HT, R_FR, R_NO, R_HT, R_DL, R_DL, R_NO, R_AL, R_NO, R_NO, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL }, + /*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, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL }, + /*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, R_AL, R_DL, R_DL, R_DL, R_DL, R_DL }, + /*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, R_AL, R_NO, R_DL, R_DL, R_DL, R_DL }, + /*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, R_AL, R_DL, R_DL, R_DL, R_DL, R_DL }, + /*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, R_AL, R_NO, R_FR, R_DL, R_DL, R_DL }, + /*ALIENPREDATO*/{ R_NO, R_NO, R_DL, R_DL, R_DL, R_NO, R_NO, R_NO, R_HT, R_NO, R_NO, R_DL, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL }, + /*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, R_NO, R_NO, R_NO, R_FR, R_FR, R_FR }, + /*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, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL }, + /*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, R_DL, R_NO, R_DL, R_DL, R_DL, 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, R_NO, R_NO, R_DL, R_DL, R_DL, R_DL }, + /*CULTIST*/ { R_NO, R_NO, R_HT, R_DL, R_DL, R_AL, R_AL, R_AL, R_NO, R_FR, R_NO, R_DL, R_DL, R_NO, R_AL, R_NO, R_DL, R_DL, R_DL, R_DL }, + /*MUND_PREY*/ { R_NO, R_FR, R_FR, R_FR, R_FR, 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_FR, R_FR, R_FR }, + /*MUND_PRED*/ { R_NO, R_FR, R_DL, R_DL, R_DL, R_HT, R_HT, R_HT, R_HT, R_HT, R_NO, R_DL, R_NO, R_NO, R_DL, R_DL, R_AL, R_DL, R_DL, R_DL }, + /*FACTION_A*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_AL, R_DL, R_DL }, + /*FACTION_B*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_AL, R_DL }, + /*FACTION_C*/ { R_NO, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_DL, R_NO, R_DL, R_DL, R_DL, R_AL } + }; int iTargClass = pTarget->Classify(); @@ -3119,6 +3172,30 @@ int CBaseMonster :: CanPlaySequence( int interruptFlags ) return FALSE; } +//========================================================= +// RunAwayFromEnemy +// simply runs directly away +//========================================================= +BOOL CBaseMonster::RunAwayFromEnemy( void ) +{ + TraceResult tr; + Vector run_away; + Vector enemy = m_hEnemy->pev->origin; + run_away = ( pev->origin - enemy ); + run_away.z = 0.0f; + run_away.Normalize(); + run_away = run_away * GetFleeDistance(); + UTIL_TraceLine( pev->origin, pev->origin + run_away, ignore_monsters, ENT( pev )/*pentIgnore*/, &tr ); + + // get the furthest direction + if( MoveToLocation( ACT_RUN, 0, pev->origin + ( run_away * tr.flFraction * 0.95f ) ) ) + { + return TRUE; + } + + return FALSE; +} + //========================================================= // FindLateralCover - attempts to locate a spot in the world // directly to the left or right of the caller that will @@ -3189,7 +3266,10 @@ Vector CBaseMonster::ShootAtEnemy( const Vector &shootOrigin ) } else if ( m_hEnemy ) { - return ( (m_hEnemy->BodyTarget( shootOrigin ) - m_hEnemy->pev->origin) + m_vecEnemyLKP - shootOrigin ).Normalize(); + Vector bt = m_hEnemy->BodyTarget( shootOrigin ); + Vector eo = m_hEnemy->pev->origin; + return ( ( bt - eo ) + m_vecEnemyLKP - shootOrigin ).Normalize(); + // return ( (m_hEnemy->BodyTarget( shootOrigin ) - m_hEnemy->pev->origin) + m_vecEnemyLKP - shootOrigin ).Normalize(); } else return gpGlobals->v_forward; diff --git a/dlls/monsters.h b/dlls/monsters.h index cabdce6d..fcc9a486 100644 --- a/dlls/monsters.h +++ b/dlls/monsters.h @@ -58,6 +58,7 @@ #define SF_MONSTER_FALL_TO_GROUND 0x80000000 // specialty spawnflags +#define SF_DOCTOR_CAN_HEAL 64 #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 diff --git a/dlls/movewith.cpp b/dlls/movewith.cpp index 12ebca03..a454d6b5 100644 --- a/dlls/movewith.cpp +++ b/dlls/movewith.cpp @@ -321,7 +321,7 @@ void CheckDesiredList( void ) return; } pListMember = g_pWorld; - CBaseEntity *pNext; + CBaseEntity *pNext = pListMember->m_pAssistLink; // int count = 0; // int all = 0; @@ -334,14 +334,18 @@ void CheckDesiredList( void ) // if (count) // ALERT(at_console, "CheckDesiredList begins, length is %d, total %d\n", count, all); // count = 0; - pListMember = g_pWorld->m_pAssistLink; +// pListMember = g_pWorld->m_pAssistLink; + - while (pListMember) + while (pNext) { // cache this, in case ApplyDesiredSettings does a SUB_Remove. + ApplyDesiredSettings( pNext ); + pListMember = pListMember->m_pAssistLink; + if( !pListMember ) + break; + pNext = pListMember->m_pAssistLink; - ApplyDesiredSettings( pListMember ); - pListMember = pNext; loopbreaker--; if (loopbreaker <= 0) { diff --git a/dlls/mp5.cpp b/dlls/mp5.cpp index 744a5629..e5af7bb3 100644 --- a/dlls/mp5.cpp +++ b/dlls/mp5.cpp @@ -173,9 +173,9 @@ void CMP5::PrimaryAttack() #endif PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usMP5, 0.0, g_vecZero, g_vecZero, vecDir.x, vecDir.y, 0, 0, 0, 0 ); - if( !m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) + // if( !m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) // HEV suit - indicate out of ammo condition - m_pPlayer->SetSuitUpdate( "!HEV_AMO0", FALSE, 0 ); + // m_pPlayer->SetSuitUpdate( "!HEV_AMO0", FALSE, 0 ); m_flNextPrimaryAttack = GetNextAttackDelay( 0.1 ); @@ -231,9 +231,9 @@ void CMP5::SecondaryAttack( void ) m_flNextSecondaryAttack = UTIL_WeaponTimeBase() + 1; m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 5;// idle pretty soon after shooting. - if( !m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] ) + // if( !m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] ) // HEV suit - indicate out of ammo condition - m_pPlayer->SetSuitUpdate( "!HEV_AMO0", FALSE, 0 ); + // m_pPlayer->SetSuitUpdate( "!HEV_AMO0", FALSE, 0 ); } void CMP5::Reload( void ) diff --git a/dlls/multiplay_gamerules.cpp b/dlls/multiplay_gamerules.cpp index 5724a844..832798c0 100644 --- a/dlls/multiplay_gamerules.cpp +++ b/dlls/multiplay_gamerules.cpp @@ -141,7 +141,7 @@ void CHalfLifeMultiplay::RefreshSkillData( void ) // override some values for multiplay. // suitcharger - gSkillData.suitchargerCapacity = 30; + // gSkillData.suitchargerCapacity = 30; // Crowbar whack gSkillData.plrDmgCrowbar = 25; @@ -159,7 +159,7 @@ void CHalfLifeMultiplay::RefreshSkillData( void ) gSkillData.plrDmgM203Grenade = 100; // Shotgun buckshot - gSkillData.plrDmgBuckshot = 20;// fewer pellets in deathmatch + // gSkillData.plrDmgBuckshot = 20;// fewer pellets in deathmatch // Crossbow gSkillData.plrDmgCrossbowClient = 20; @@ -583,8 +583,8 @@ void CHalfLifeMultiplay::PlayerSpawn( CBasePlayer *pPlayer ) if( addDefault ) { pPlayer->GiveNamedItem( "weapon_crowbar" ); - pPlayer->GiveNamedItem( "weapon_9mmhandgun" ); - pPlayer->GiveAmmo( 68, "9mm", _9MM_MAX_CARRY );// 4 full reloads + // pPlayer->GiveNamedItem( "weapon_9mmhandgun" ); + // pPlayer->GiveAmmo( 68, "9mm", _9MM_MAX_CARRY );// 4 full reloads } } diff --git a/dlls/plats.cpp b/dlls/plats.cpp index 8dd5162c..4a1e4db7 100644 --- a/dlls/plats.cpp +++ b/dlls/plats.cpp @@ -997,7 +997,7 @@ void CFuncTrain::Next( void ) //pev->avelocity = pTarg->pev->avelocity; //LRC } - if (m_pevCurrentTarget->armorvalue) + if (m_pevCurrentTarget->sanity) { UTIL_SetAngles(this, m_pevCurrentTarget->angles); //pev->angles = m_pevCurrentTarget->angles; //LRC - if we just passed a "turn to face" corner, set angle exactly. @@ -1026,7 +1026,7 @@ void CFuncTrain::Next( void ) UTIL_AssignOrigin(this, pTarg->pev->origin - (pev->mins + pev->maxs) * 0.5 ); } - if (pTarg->pev->armorvalue) //LRC - "teleport and turn to face" means you set an angle as you teleport. + if (pTarg->pev->sanity) //LRC - "teleport and turn to face" means you set an angle as you teleport. { UTIL_SetAngles(this, pTarg->pev->angles); //pev->angles = pTarg->pev->angles; @@ -1051,7 +1051,7 @@ void CFuncTrain::Next( void ) ClearBits(pev->effects, EF_NOINTERP); SetMoveDone(&CFuncTrain :: Wait ); - if (pTarg->pev->armorvalue) //LRC - "turn to face" the next corner + if (pTarg->pev->sanity) //LRC - "turn to face" the next corner { Vector vTemp = pev->angles; FixupAngles( vTemp ); @@ -1767,7 +1767,7 @@ void CFuncTrackTrain :: DesiredAction( void ) // Next( void ) pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL; //LRC is "match angle" set to "Yes"? If so, set the angle exactly, because we've reached the corner. - if ( pFire->pev->armorvalue == PATHMATCH_YES && pev->spawnflags & SF_TRACKTRAIN_AVELOCITY ) + if ( pFire->pev->sanity == PATHMATCH_YES && pev->spawnflags & SF_TRACKTRAIN_AVELOCITY ) { Vector vTemp = pFire->pev->angles; vTemp.y -= 180; //the train is actually built facing west. @@ -1846,7 +1846,7 @@ void CFuncTrackTrain :: DesiredAction( void ) // Next( void ) } //LRC - if (pDest->pev->armorvalue == PATHMATCH_YES) + if (pDest->pev->sanity == PATHMATCH_YES) { pev->spawnflags |= SF_TRACKTRAIN_AVELOCITY | SF_TRACKTRAIN_AVEL_GEARS; Vector vTemp = pev->angles; diff --git a/dlls/player.cpp b/dlls/player.cpp index 938d50d0..af4dd48c 100644 --- a/dlls/player.cpp +++ b/dlls/player.cpp @@ -38,6 +38,7 @@ #include "hltv.h" #include "effects.h" //LRC #include "movewith.h" //LRC +#include "triggers.h" //Cthulhu // #define DUCKFIX @@ -48,6 +49,8 @@ int gEvilImpulse101; BOOL g_markFrameBounds = 0; //LRC extern DLL_GLOBAL int g_iSkillLevel, gDisplayTitle; +char st_szPreInsaneMap[cchMapNameMost]; + extern "C" int g_bhopcap; BOOL gInitHUD = TRUE; @@ -151,7 +154,7 @@ TYPEDESCRIPTION CBasePlayer::m_playerSaveData[] = //DEFINE_FIELD( CBasePlayer, m_fNoPlayerSound, FIELD_BOOLEAN ), // Don't need to restore, debug //DEFINE_FIELD( CBasePlayer, m_iUpdateTime, FIELD_INTEGER ), // Don't need to restore //DEFINE_FIELD( CBasePlayer, m_iClientHealth, FIELD_INTEGER ), // Don't restore, client needs reset - //DEFINE_FIELD( CBasePlayer, m_iClientBattery, FIELD_INTEGER ), // Don't restore, client needs reset + //DEFINE_FIELD( CBasePlayer, m_iClientSanity, FIELD_INTEGER ), // Don't restore, client needs reset //DEFINE_FIELD( CBasePlayer, m_iClientHideHUD, FIELD_INTEGER ), // Don't restore, client needs reset //DEFINE_FIELD( CBasePlayer, m_fWeapon, FIELD_BOOLEAN ), // Don't restore, client needs reset //DEFINE_FIELD( CBasePlayer, m_nCustomSprayFrames, FIELD_INTEGER ), // Don't restore, depends on server message after spawning and only matters in multiplayer @@ -179,7 +182,7 @@ int gmsgShowGameTitle = 0; int gmsgCurWeapon = 0; int gmsgHealth = 0; int gmsgDamage = 0; -int gmsgBattery = 0; +int gmsgSanity = 0; int gmsgTrain = 0; int gmsgLogo = 0; int gmsgWeaponList = 0; @@ -207,6 +210,7 @@ int gmsgStatusIcon = 0; //LRC int gmsgBhopcap = 0; int gmsgStatusText = 0; int gmsgStatusValue = 0; +int gmsgReadBook = 0; // Cthulhu void LinkUserMessages( void ) { @@ -223,7 +227,7 @@ void LinkUserMessages( void ) gmsgFlashBattery = REG_USER_MSG( "FlashBat", 1 ); gmsgHealth = REG_USER_MSG( "Health", 1 ); gmsgDamage = REG_USER_MSG( "Damage", 12 ); - gmsgBattery = REG_USER_MSG( "Battery", 2); + gmsgSanity = REG_USER_MSG( "Sanity", 2); gmsgTrain = REG_USER_MSG( "Train", 1 ); gmsgHudText = REG_USER_MSG( "HudText", -1 ); gmsgSayText = REG_USER_MSG( "SayText", -1 ); @@ -257,6 +261,7 @@ void LinkUserMessages( void ) gmsgFade = REG_USER_MSG( "ScreenFade", sizeof(ScreenFade) ); gmsgAmmoX = REG_USER_MSG( "AmmoX", 2 ); gmsgTeamNames = REG_USER_MSG( "TeamNames", -1 ); + gmsgReadBook = REG_USER_MSG( "ReadBook", -1 ); gmsgStatusIcon = REG_USER_MSG( "StatusIcon", -1 ); gmsgStatusText = REG_USER_MSG( "StatusText", -1 ); @@ -272,12 +277,20 @@ void CBasePlayer::Pain( void ) flRndSound = RANDOM_FLOAT( 0, 1 ); - if( flRndSound <= 0.33 ) + /*if( flRndSound <= 0.33 ) EMIT_SOUND( ENT( pev ), CHAN_VOICE, "player/pl_pain5.wav", 1, ATTN_NORM ); else if( flRndSound <= 0.66 ) EMIT_SOUND( ENT( pev ), CHAN_VOICE, "player/pl_pain6.wav", 1, ATTN_NORM ); else EMIT_SOUND( ENT( pev ), CHAN_VOICE, "player/pl_pain7.wav", 1, ATTN_NORM ); + */ + + if( flRndSound <= 0.66f ) + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "player/rs_pain1.wav", 1, ATTN_NORM ); + else if( flRndSound <= 0.95f ) + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "player/rs_pain2.wav", 1, ATTN_NORM ); + else + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "player/rs_pain3.wav", 1, ATTN_NORM ); } Vector VecVelocityForDamage( float flDamage ) @@ -370,6 +383,7 @@ void CBasePlayer::DeathSound( void ) */ // temporarily using pain sounds for death sounds + /* switch( RANDOM_LONG( 1, 5 ) ) { case 1: @@ -382,12 +396,15 @@ void CBasePlayer::DeathSound( void ) EMIT_SOUND( ENT( pev ), CHAN_VOICE, "player/pl_pain7.wav", 1, ATTN_NORM ); break; } + */ + + EMIT_SOUND( ENT( pev ), CHAN_VOICE, "player/rs_pain3.wav", 1, ATTN_NORM ); // play one of the suit death alarms //LRC- if no suit, then no flatline sound. (unless it's a deathmatch.) if ( !(pev->weapons & (1<IsDeathmatch() ) return; - EMIT_GROUPNAME_SUIT( ENT( pev ), "HEV_DEAD" ); + // EMIT_GROUPNAME_SUIT( ENT( pev ), "HEV_DEAD" ); } // override takehealth @@ -455,8 +472,8 @@ void CBasePlayer::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector ve etc are implemented with subsequent calls to TakeDamage using DMG_GENERIC. */ -#define ARMOR_RATIO 0.2 // Armor Takes 80% of the damage -#define ARMOR_BONUS 0.5 // Each Point of Armor is work 1/x points of health +// #define ARMOR_RATIO 0.2 // Armor Takes 80% of the damage +// #define ARMOR_BONUS 0.5 // Each Point of Armor is work 1/x points of health int CBasePlayer::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { @@ -467,19 +484,8 @@ int CBasePlayer::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, fl int fcritical; int fTookDamage; int ftrivial; - float flRatio; - float flBonus; float flHealthPrev = pev->health; - flBonus = ARMOR_BONUS; - flRatio = ARMOR_RATIO; - - if( ( bitsDamageType & DMG_BLAST ) && g_pGameRules->IsMultiplayer() ) - { - // blasts damage armor more. - flBonus *= 2; - } - // Already dead if( !IsAlive() ) return 0; @@ -496,29 +502,6 @@ int CBasePlayer::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, fl // keep track of amount of damage last sustained m_lastDamageAmount = (int)flDamage; - // Armor. - if( !( pev->flags & FL_GODMODE ) && pev->armorvalue && !( bitsDamageType & ( DMG_FALL | DMG_DROWN ) ) )// armor doesn't protect against fall or drown damage! - { - float flNew = flDamage * flRatio; - - float flArmor; - - flArmor = ( flDamage - flNew ) * flBonus; - - // Does this use more armor than we have? - if( flArmor > pev->armorvalue ) - { - flArmor = pev->armorvalue; - flArmor *= ( 1 / flBonus ); - flNew = flDamage - flArmor; - pev->armorvalue = 0; - } - else - pev->armorvalue -= flArmor; - - flDamage = flNew; - } - // this cast to INT is critical!!! If a player ends up with 0.5 health, the engine will get that // as an int (zero) and think the player is dead! (this will incite a clientside screentilt, etc) fTookDamage = CBaseMonster::TakeDamage( pevInflictor, pevAttacker, (int)flDamage, bitsDamageType ); @@ -660,7 +643,7 @@ int CBasePlayer::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, fl if( fTookDamage && !ftrivial && fcritical && flHealthPrev < 75 ) { // already took major damage, now it's critical... - if( pev->health < 6 ) + /*if( pev->health < 6 ) SetSuitUpdate( "!HEV_HLTH3", FALSE, SUIT_NEXT_IN_10MIN ); // near death else if( pev->health < 20 ) SetSuitUpdate( "!HEV_HLTH2", FALSE, SUIT_NEXT_IN_10MIN ); // health critical @@ -668,23 +651,88 @@ int CBasePlayer::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, fl // give critical health warnings if( !RANDOM_LONG( 0, 3 ) && flHealthPrev < 50 ) SetSuitUpdate( "!HEV_DMG7", FALSE, SUIT_NEXT_IN_5MIN ); //seek medical attention + */ } // if we're taking time based damage, warn about its continuing effects if( fTookDamage && ( bitsDamageType & DMG_TIMEBASED ) && flHealthPrev < 75 ) { - if( flHealthPrev < 50 ) + /*if( flHealthPrev < 50 ) { if( !RANDOM_LONG( 0, 3 ) ) SetSuitUpdate( "!HEV_DMG7", FALSE, SUIT_NEXT_IN_5MIN ); //seek medical attention } else SetSuitUpdate( "!HEV_HLTH1", FALSE, SUIT_NEXT_IN_10MIN ); // health dropping + */ } return fTookDamage; } +int CBasePlayer::LoseSanity( float flSanLoss ) +{ + // Already dead + if( !IsAlive() ) + return 0; + + pev->sanity -= flSanLoss; + pev->dmg_inflictor = ENT( pev ); + + if( pev->sanity <= 0.0f ) + { + pev->sanity = 0.0f; + // go to insane map... + GoInsane(); + } + else if( pev->sanity > 100.0f ) + { + pev->sanity = 100.0f; + } + + m_bitsHUDDamage = -1; // make sure the damage bits get resent + + return 1; +} + +FILE_GLOBAL char st_szNextMap[cchMapNameMost]; +FILE_GLOBAL char st_szNextSpot[cchMapNameMost]; + +void CBasePlayer::GoInsane ( void ) +{ + // SERVER_COMMAND( "map insane\n" ); + + // Cthulhu: shut down any active cameras. + CTriggerCamera *pCamera = 0; + + do + { + // find all camera entities + pCamera = (CTriggerCamera*)UTIL_FindEntityByClassname( pCamera, "trigger_camera" ); + + if( !pCamera ) + break; + + // is it active + if( pCamera->m_state ) + { + // tell the camera that it's time is up, and to hand control back to the player + pCamera->m_flReturnTime = gpGlobals->time - 1.0f; + pCamera->FollowTarget(); + } + } while( true ); + + strcpy( st_szNextSpot, "lm_insane" ); + strcpy( st_szNextMap, "insane" ); + + pev->origin = Vector( 0.0f , 0.0f, 1.0f ); + gpGlobals->vecLandmarkOffset = pev->origin; + strcpy( st_szPreInsaneMap, STRING( gpGlobals->mapname ) ); + + ALERT( at_debug, "CHANGE LEVEL: %s %s\n", st_szNextMap, st_szNextSpot ); + CHANGE_LEVEL( st_szNextMap, st_szNextSpot ); +} + //========================================================= // PackDeadPlayerItems - call this when a player dies to // pack up the appropriate weapons and ammo items, and to @@ -906,7 +954,7 @@ void CBasePlayer::RemoveItems( int iWeaponMask, int i9mm, int i357, int iBuck, i CBasePlayerItem *pCurrentItem; // hornetgun is outside the spawnflags Worldcraft can set - handle it seperately. - if (iHornet) + /*if (iHornet) iWeaponMask |= 1<classname = MAKE_STRING( "player" ); pev->health = 100; - pev->armorvalue = 0; + pev->sanity = 0; pev->takedamage = DAMAGE_AIM; pev->solid = SOLID_SLIDEBOX; pev->movetype = MOVETYPE_WALK; @@ -3057,7 +3108,7 @@ void CBasePlayer::Spawn( void ) m_iClientHideHUD = -1; // force this to be recalculated m_fWeapon = FALSE; m_pClientActiveItem = NULL; - m_iClientBattery = -1; + m_iClientSanity = -1; // reset all ammo values to 0 for( int i = 0; i < MAX_AMMO_SLOTS; i++ ) @@ -3103,7 +3154,7 @@ void CBasePlayer::Precache( void ) m_bitsDamageType = 0; m_bitsHUDDamage = -1; - m_iClientBattery = -1; + m_iClientSanity = -1; m_flFlashLightTime = 1; @@ -3202,6 +3253,18 @@ int CBasePlayer::Restore( CRestore &restore ) // Barring that, we clear it out here instead of using the incorrect restored time value. m_flNextAttack = UTIL_WeaponTimeBase(); #endif + // Cthulhu: are we entering the insane map? + if( FStrEq( STRING( gpGlobals->mapname ), "insane" ) ) + { + // find the landmark + edict_t *lmpev = CChangeLevel::FindLandmark( "lm_insane" ); + if( lmpev ) + { + // put us there (moved up a bit, incase the landmark is on the ground + pev->origin = VARS( lmpev )->origin + Vector( 0, 0, 36 ); + } + } + return status; } @@ -3555,7 +3618,7 @@ Reset stuff so that the state is transmitted. void CBasePlayer::ForceClientDllUpdate( void ) { m_iClientHealth = -1; - m_iClientBattery = -1; + m_iClientSanity = -1; m_iClientHideHUD = -1; // Vit_amiN: forcing to update m_iClientFOV = -1; // Vit_amiN: force client weapons to be sent m_iTrain |= TRAIN_NEW; // Force new train message. @@ -4207,15 +4270,15 @@ void CBasePlayer::UpdateClientData( void ) m_iClientHealth = (int)pev->health; } - if( (int)pev->armorvalue != m_iClientBattery ) + if( (int)pev->sanity != m_iClientSanity ) { - m_iClientBattery = (int)pev->armorvalue; + m_iClientSanity = (int)pev->sanity; - ASSERT( gmsgBattery > 0 ); + ASSERT( gmsgSanity > 0 ); // send "health" update message - MESSAGE_BEGIN( MSG_ONE, gmsgBattery, NULL, pev ); - WRITE_SHORT( (int)pev->armorvalue ); + MESSAGE_BEGIN( MSG_ONE, gmsgSanity, NULL, pev ); + WRITE_SHORT( (int)pev->sanity ); MESSAGE_END(); } @@ -4914,6 +4977,57 @@ void CDeadHEV::Spawn( void ) MonsterInitDead(); } +class CPlayerGainSanity : public CPointEntity +{ +public: + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + float m_flSanGain; +}; + +LINK_ENTITY_TO_CLASS( player_gainsan, CPlayerGainSanity ); + +TYPEDESCRIPTION CPlayerGainSanity::m_SaveData[] = +{ + DEFINE_FIELD( CPlayerGainSanity, m_flSanGain, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CPlayerGainSanity, CPointEntity ); + +void CPlayerGainSanity::KeyValue( KeyValueData *pkvd ) +{ + if( FStrEq( pkvd->szKeyName, "m_flSanGain" ) ) + { + m_flSanGain = atof( pkvd->szValue ); + pkvd->fHandled = TRUE; + } + else + CPointEntity::KeyValue( pkvd ); +} + +void CPlayerGainSanity::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + CBasePlayer *pPlayer = NULL; + + if( pActivator && pActivator->IsPlayer() ) + { + pPlayer = (CBasePlayer *)pActivator; + } + else if( !g_pGameRules->IsDeathmatch() ) + { + pPlayer = (CBasePlayer *)CBaseEntity::Instance( g_engfuncs.pfnPEntityOfEntIndex( 1 ) ); + } + + if( pPlayer ) + pPlayer->LoseSanity( -m_flSanGain ); +} + class CStripWeapons : public CPointEntity { public: diff --git a/dlls/player.h b/dlls/player.h index 09830863..c25ffdb5 100644 --- a/dlls/player.h +++ b/dlls/player.h @@ -186,7 +186,7 @@ public: float m_tSneaking; int m_iUpdateTime; // stores the number of frame ticks before sending HUD update messages int m_iClientHealth; // the health currently known by the client. If this changes, send a new - int m_iClientBattery; // the Battery currently known by the client. If this changes, send a new + int m_iClientSanity; // the Battery currently known by the client. If this changes, send a new int m_iHideHUD; // the players hud weapon info is to be hidden int m_iClientHideHUD; int m_iFOV; // field of view @@ -226,6 +226,7 @@ public: virtual int TakeHealth( float flHealth, int bitsDamageType ); virtual void TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType); virtual int TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType); + virtual int LoseSanity( float flSanLoss ); virtual void Killed( entvars_t *pevAttacker, int iGib ); virtual Vector BodyTarget( const Vector &posSrc ) { return Center( ) + pev->view_ofs * RANDOM_FLOAT( 0.5, 1.1 ); }; // position to shoot at virtual void StartSneaking( void ) { m_tSneaking = gpGlobals->time - 1; } @@ -324,6 +325,8 @@ public: void SetCustomDecalFrames( int nFrames ); int GetCustomDecalFrames( void ); + void GoInsane( void ); // loads the "insane" map + void TabulateAmmo( void ); float m_flStartCharge; @@ -355,5 +358,6 @@ public: extern int gmsgHudText; extern int gmsgParticle; // LRC extern BOOL gInitHUD; +extern char st_szPreInsaneMap[cchMapNameMost]; #endif // PLAYER_H diff --git a/dlls/python.cpp b/dlls/python.cpp index da9e53f3..7fc38fb0 100644 --- a/dlls/python.cpp +++ b/dlls/python.cpp @@ -202,9 +202,9 @@ void CPython::PrimaryAttack() #endif PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), m_usFirePython, 0.0, g_vecZero, g_vecZero, vecDir.x, vecDir.y, 0, 0, 0, 0 ); - if( !m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) + // if( !m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) // HEV suit - indicate out of ammo condition - m_pPlayer->SetSuitUpdate( "!HEV_AMO0", FALSE, 0 ); + // m_pPlayer->SetSuitUpdate( "!HEV_AMO0", FALSE, 0 ); m_flNextPrimaryAttack = 0.75; m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); diff --git a/dlls/rat.cpp b/dlls/rat.cpp index 3650fe05..47b29294 100644 --- a/dlls/rat.cpp +++ b/dlls/rat.cpp @@ -21,10 +21,20 @@ #include "cbase.h" #include "monsters.h" #include "schedule.h" +#include "soundent.h" +#include "decals.h" + //========================================================= // Monster's Anim Events Go Here //========================================================= +#define RAT_IDLE 0 +#define RAT_BORED 1 +#define RAT_SCARED_BY_ENT 2 +#define RAT_SCARED_BY_LIGHT 3 +#define RAT_SMELL_FOOD 4 +#define RAT_EAT 5 +#define RAT_DEAD 6 class CRat : public CBaseMonster { @@ -33,10 +43,33 @@ public: void Precache( void ); void SetYawSpeed( void ); int Classify( void ); + void Look ( int iDistance ); + int ISoundMask( 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; + BOOL m_fLightHacked; + int m_iMode; + float m_flNextTouchTime; }; LINK_ENTITY_TO_CLASS( monster_rat, CRat ) +//========================================================= +// 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 CRat::ISoundMask( void ) +{ + return bits_SOUND_CARCASS | bits_SOUND_MEAT; +} + //========================================================= // Classify - indicates this monster's place in the // relationship table. @@ -46,6 +79,30 @@ int CRat::Classify( void ) return m_iClass?m_iClass:CLASS_INSECT; //LRC- maybe someone needs to give them a basic biology lesson... } +//========================================================= +// Touch +//========================================================= +void CRat::Touch( CBaseEntity *pOther ) +{ + Vector vecSpot; + TraceResult tr; + + if( m_flNextTouchTime > gpGlobals->time ) + return; + + if( pOther->pev->velocity == g_vecZero || !pOther->IsPlayer() ) + { + return; + } + + vecSpot = pev->origin + Vector( 0, 0, 8 ); // move up a bit, and trace down. + + TakeDamage( pOther->pev, pOther->pev, 1, DMG_CRUSH ); + EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, "rat/rat_squeak.wav", 0.3, ATTN_NORM, 0, 80 + RANDOM_LONG( 0, 39 ) ); + + m_flNextTouchTime = gpGlobals->time + 1.0f; +} + //========================================================= // SetYawSpeed - allows each sequence to have a different // turn rate associated with it. @@ -76,17 +133,27 @@ void CRat::Spawn() SET_MODEL(ENT(pev), STRING(pev->model)); //LRC else SET_MODEL( ENT( pev ), "models/bigrat.mdl" ); - UTIL_SetSize( pev, Vector( 0, 0, 0 ), Vector( 0, 0, 0 ) ); + UTIL_SetSize( pev, Vector( -6, -6, 0 ), Vector( 6, 6, 6 ) ); pev->solid = SOLID_SLIDEBOX; pev->movetype = MOVETYPE_STEP; m_bloodColor = BLOOD_COLOR_RED; - pev->health = 8; + if (pev->health == 0) + 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(); + 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 = RAT_IDLE; + m_flNextSmellTime = gpGlobals->time; + m_flNextTouchTime = gpGlobals->time; } //========================================================= @@ -98,6 +165,301 @@ void CRat::Precache() PRECACHE_MODEL(STRING(pev->model)); //LRC else PRECACHE_MODEL( "models/bigrat.mdl" ); + + PRECACHE_SOUND( "roach/rch_walk.wav" ); + PRECACHE_SOUND( "rat/rat_squeak.wav" ); + PRECACHE_SOUND( "rat/rat_die.wav" ); +} + +//========================================================= +// Killed. +//========================================================= +void CRat::Killed( entvars_t *pevAttacker, int iGib ) +{ + // random sound + EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "rat/rat_die.wav", 0.3, ATTN_NORM, 0, 80 + RANDOM_LONG(0,39) ); + + CSoundEnt::InsertSound ( bits_SOUND_WORLD, pev->origin, 128, 1 ); + + // no gibs + CBaseMonster::Killed( pevAttacker, GIB_NEVER ); +} + +//========================================================= +// MonsterThink, overridden for rats. +//========================================================= +void CRat::MonsterThink( void ) +{ + if( FNullEnt( FIND_CLIENT_IN_PVS( edict() ) ) ) + SetNextThink( RANDOM_FLOAT( 1.0f, 1.5f ) ); + else + SetNextThink( 0.1f );// keep monster thinking + + RunAI(); + + if( !IsAlive() ) + m_iMode = RAT_DEAD; + + 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. + SetNextThink( 1.0f ); + m_fLightHacked = TRUE; + return; + } + else if( m_flLastLightLevel < 0 ) + { + // collect light level for the first time, now that all of the lightmaps in the rat's area have been calculated. + m_flLastLightLevel = GETENTITYILLUM( ENT( pev ) ); + } + + switch( m_iMode ) + { + case RAT_IDLE: + case RAT_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 ) ) );// rat will ignore food for 30 to 45 seconds + PickNewDest( RAT_SCARED_BY_ENT ); + SetActivity( ACT_RUN ); + } + else if( RANDOM_LONG( 0, 149 ) == 1 ) + { + // if rat doesn't see anything, there's still a chance that it will move. (boredom) + // ALERT( at_aiconsole, "Bored\n" ); + PickNewDest( RAT_BORED ); + SetActivity( ACT_WALK ); + + if( m_iMode == RAT_EAT ) + { + // rat 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 == RAT_IDLE ) + { + if( FShouldEat() ) + { + Listen(); + } + + if( GETENTITYILLUM( ENT( pev ) ) > m_flLastLightLevel ) + { + // someone turned on lights! + // ALERT( at_console, "Lights!\n" ); + PickNewDest( RAT_SCARED_BY_LIGHT ); + SetActivity( ACT_RUN ); + } + else if( HasConditions(bits_COND_SMELL_FOOD) ) + { + CSound *pSound; + + pSound = CSoundEnt::SoundPointerForIndex( m_iAudibleList ); + + // rat 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( RAT_SMELL_FOOD ); + SetActivity( ACT_WALK ); + } + } + } + + break; + } + case RAT_SCARED_BY_LIGHT: + { + // if rat 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; + } + case RAT_DEAD: + return; + break; + } + + if( m_flGroundSpeed != 0 ) + { + Move( flInterval ); + } +} + +//========================================================= +// Picks a new spot for rat to run to.( +//========================================================= +void CRat::PickNewDest( int iCondition ) +{ + Vector vecNewDir; + Vector vecDest; + float flDist; + + m_iMode = iCondition; + + if( m_iMode == RAT_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 rat 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 rat 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 ) ); + } +} + +//========================================================= +// rat's move function +//========================================================= +void CRat::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 rat to overshoot) + if( flWaypointDist <= m_flGroundSpeed * flInterval ) + { + // take truncated step and stop + SetActivity( ACT_IDLE ); + m_flLastLightLevel = GETENTITYILLUM( ENT( pev ) );// this is rat's new comfortable light level + + if( m_iMode == RAT_SMELL_FOOD ) + { + m_iMode = RAT_EAT; + } + else + { + m_iMode = RAT_IDLE; + } + } + + // if( RANDOM_LONG( 0, 149 ) == 1 && m_iMode != RAT_SCARED_BY_LIGHT && m_iMode != RAT_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 rat, which can virtually see +// 360 degrees. +//========================================================= +void CRat::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_debug, "%s can't assess %s\n", STRING( pev->classname ), STRING( pSightEnt->pev->classname ) ); + break; + } + } + } + } + SetConditions( iSighted ); } //========================================================= diff --git a/dlls/roach.cpp b/dlls/roach.cpp index 0964958a..749f33d5 100644 --- a/dlls/roach.cpp +++ b/dlls/roach.cpp @@ -131,7 +131,8 @@ void CRoach::Spawn() pev->movetype = MOVETYPE_STEP; m_bloodColor = BLOOD_COLOR_YELLOW; pev->effects = 0; - pev->health = 1; + if( pev->health == 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; diff --git a/dlls/schedule.h b/dlls/schedule.h index a1b8f3f9..c5a459da 100644 --- a/dlls/schedule.h +++ b/dlls/schedule.h @@ -43,6 +43,7 @@ typedef enum SCHED_COMBAT_FACE, SCHED_COMBAT_STAND, SCHED_CHASE_ENEMY, + SCHED_CHASE_ENEMY_LKP, SCHED_CHASE_ENEMY_FAILED, SCHED_VICTORY_DANCE, SCHED_TARGET_FACE, @@ -72,8 +73,9 @@ typedef enum SCHED_BARNACLE_VICTIM_CHOMP, SCHED_AISCRIPT, SCHED_FAIL, + SCHED_DREAD_NAME_PANIC, // used only by the dread name - LAST_COMMON_SCHEDULE // Leave this at the bottom + LAST_COMMON_SCHEDULE // Leave this at the bottom (24) } SCHEDULE_TYPE; //========================================================= diff --git a/dlls/scientist.cpp b/dlls/scientist.cpp index 96bc5a97..198bfa2d 100644 --- a/dlls/scientist.cpp +++ b/dlls/scientist.cpp @@ -1141,8 +1141,13 @@ LINK_ENTITY_TO_CLASS( monster_scientist_dead, CDeadScientist ) // void CDeadScientist::Spawn() { - PRECACHE_MODEL( "models/scientist.mdl" ); - SET_MODEL( ENT( pev ), "models/scientist.mdl" ); + const char *szModel = "models/scientist.mdl"; + + if( pev->model ) + szModel = STRING( pev->model ); + + PRECACHE_MODEL( szModel ); + SET_MODEL( ENT( pev ), szModel ); pev->effects = 0; pev->sequence = 0; diff --git a/dlls/scripted.cpp b/dlls/scripted.cpp index 8ebe5c87..d2b989b6 100644 --- a/dlls/scripted.cpp +++ b/dlls/scripted.cpp @@ -102,16 +102,16 @@ void CCineMonster::KeyValue( KeyValueData *pkvd ) m_fAction = atoi( pkvd->szValue ); pkvd->fHandled = TRUE; } -// LRC else if (FStrEq(pkvd->szKeyName, "m_flRepeat")) -// { -// m_flRepeat = atof( pkvd->szValue ); -// pkvd->fHandled = TRUE; -// } - else if( FStrEq( pkvd->szKeyName, "m_flRadius" ) ) + else if( FStrEq( pkvd->szKeyName, "m_flRepeat" ) ) { - m_flRadius = atof( pkvd->szValue ); + m_flRepeat = atof( pkvd->szValue ); pkvd->fHandled = TRUE; } +// LRC else if( FStrEq( pkvd->szKeyName, "m_flRadius" ) ) +// { +// m_flRadius = atof( pkvd->szValue ); +// pkvd->fHandled = TRUE; +// } else if (FStrEq(pkvd->szKeyName, "m_iRepeats")) { m_iRepeats = atoi( pkvd->szValue ); @@ -503,6 +503,7 @@ BOOL CCineMonster::StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL comple ALERT( at_console, "%s (%s): started \"%s\":INT:%s\n", STRING( pTarget->pev->targetname ), STRING( pTarget->pev->classname ), STRING( iszSeq ), s ); #endif + m_interruptable = 0; // cthulhu bug fix pTarget->pev->frame = 0; pTarget->ResetSequenceInfo(); return TRUE; @@ -517,6 +518,39 @@ BOOL CCineMonster::StartSequence( CBaseMonster *pTarget, int iszSeq, BOOL comple //========================================================= void CCineMonster::SequenceDone( CBaseMonster *pMonster ) { + // CTHULHU: horrible hack... + if( FStrEq( STRING( m_iszPlay ), "busting_through_wall" ) ) + { + // copied from CineCleanup + ////////////////////////// + Vector new_origin, new_angle; + pMonster->GetBonePosition( 0, new_origin, new_angle ); + + // UNDONE: THIS SHOULD ONLY HAPPEN IF WE ACTUALLY PLAYED THE SEQUENCE. + //Vector oldOrigin = pMonster->pev->origin; + + //UTIL_SetOrigin( pMonster, new_origin ); + pMonster->pev->origin = new_origin; + DROP_TO_FLOOR( ENT( pMonster->pev ) ); + ////////////////////////// + // Need to also set "move to position" to false... + m_fMoveTo = 0; + m_iszPlay = MAKE_STRING( "getup" ); + StartSequence( pMonster, m_iszPlay, FALSE ); + return; + } + else if( FStrEq( STRING( m_iszPlay ), "insane" ) ) + { + ////////////////////////// + // Need to also set "move to position" to false... + m_fMoveTo = 0; + m_iszPlay = MAKE_STRING( "insane2" ); + m_iRepeats = 100; + m_iRepeatsLeft = m_iRepeats; + StartSequence( pMonster, m_iszPlay, FALSE ); + return; + } + m_iRepeatsLeft = m_iRepeats; //LRC - reset the repeater count m_iState = STATE_OFF; // we've finished. // ALERT( at_console, "Sequence %s finished\n", STRING(pev->targetname));//STRING( m_pCine->m_iszPlay ) ); @@ -1173,60 +1207,3 @@ BOOL CScriptedSentence::StartSentence( CBaseMonster *pTarget ) 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( &CBaseEntity::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( 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 m_iClass?m_iClass:CLASS_NONE; -} diff --git a/dlls/skill.h b/dlls/skill.h index 5244c923..e0281fae 100644 --- a/dlls/skill.h +++ b/dlls/skill.h @@ -24,6 +24,8 @@ struct skilldata_t int iSkillLevel; // game skill level // Monster Health & Damage + float panicDuration; // replace this for either small/med/large or individual monsters + float agruntHealth; float agruntDmgPunch; @@ -41,6 +43,11 @@ struct skilldata_t float bullsquidDmgWhip; float bullsquidDmgSpit; + float cthonianHealth; + float cthonianDmgBite; + float cthonianDmgWhip; + float cthonianDmgSpit; + float gargantuaHealth; float gargantuaDmgSlash; float gargantuaDmgFire; @@ -51,14 +58,41 @@ struct skilldata_t float headcrabHealth; float headcrabDmgBite; + float gangsterHealth; + float gangsterDmgKick; + float gangsterShotgunPellets; + float hgruntHealth; float hgruntDmgKick; float hgruntShotgunPellets; float hgruntGrenadeSpeed; + float cultistHealth; + float cultistDmgKick; + float cultistShotgunPellets; + float houndeyeHealth; float houndeyeDmgBlast; + float greatraceHealth; + float greatraceDmgClaw; + float greatraceDmgClawrake; + float greatraceDmgZap; + + float yodanHealth; + float yodanDmgClaw; + float yodanDmgClawrake; + float yodanDmgZap; + + float serpentmanHealth; + float serpentmanDmgStaff; + + float nightgauntHealth; + float nightgauntDmgSlash; + + float priestHealth; + float priestDmgKnife; + float slaveHealth; float slaveDmgClaw; float slaveDmgClawrake; @@ -80,25 +114,65 @@ struct skilldata_t float scientistHealth; + float butlerHealth; + + float sirhenryHealth; + float sirhenryDmgZap; + float sirhenryDmgKnife; + float snarkHealth; float snarkDmgBite; float snarkDmgPop; + float formless_spawnHealth; + float formless_spawnDmgAttack; + + float snakeHealth; + float snakeDmgBite; + + float deeponeHealth; + float deeponeDmgOneSlash; + float deeponeDmgBothSlash; + + float shamblerHealth; + float shamblerDmgOneSlash; + float shamblerDmgBothSlash; + + float huntinghorrorHealth; + float huntinghorrorDmgBite; + float zombieHealth; float zombieDmgOneSlash; float zombieDmgBothSlash; + float ghoulHealth; + float ghoulDmgOneSlash; + float ghoulDmgBothSlash; + float turretHealth; float miniturretHealth; float sentryHealth; // Player Weapons + float plrDmgSwordCane; + float plrDmgKnife; + float plrDmgRevolver; + float plrDmgShotgun; + float plrDmgTommyGun; + float plrDmgRifle; + float plrDmgDynamite; + float plrDmgMolotov; + float plrDmgLightningGun; + float plrDmgShrivellingNarrow; + float plrDmgShrivellingWide; + float plrDmgDrainLife; + + // OLD WEAPONS float plrDmgCrowbar; float plrDmg9MM; float plrDmg357; float plrDmgMP5; float plrDmgM203Grenade; - float plrDmgBuckshot; float plrDmgCrossbowClient; float plrDmgCrossbowMonster; float plrDmgRPG; @@ -107,18 +181,21 @@ struct skilldata_t float plrDmgEgonWide; float plrDmgHornet; float plrDmgHandGrenade; - float plrDmgSatchel; float plrDmgTripmine; + float plrDmgSatchel; // weapons shared by monsters float monDmg9MM; float monDmgMP5; float monDmg12MM; float monDmgHornet; + float monDmgLightningGun; + float monDmgShrivelling; + float monDmgDagger; // health/suit charge - float suitchargerCapacity; - float batteryCapacity; + // float suitchargerCapacity; + // float batteryCapacity; float healthchargerCapacity; float healthkitCapacity; float scientistHeal; diff --git a/dlls/squadmonster.cpp b/dlls/squadmonster.cpp index b4a44ceb..ee52f05e 100644 --- a/dlls/squadmonster.cpp +++ b/dlls/squadmonster.cpp @@ -497,12 +497,16 @@ BOOL CSquadMonster :: NoFriendlyFire( BOOL playerAlly ) 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 ) ) + // are we of the same class (because if we are not, then we have probably been charmed, so it is ok to shoot!) + if (pMember->Classify() == Classify()) { - // this guy is in the check volume! Don't shoot! - return FALSE; + 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; + } } } } diff --git a/dlls/squadmonster.h b/dlls/squadmonster.h index e2b9587d..9aa54ac0 100644 --- a/dlls/squadmonster.h +++ b/dlls/squadmonster.h @@ -24,31 +24,48 @@ #define bits_NO_SLOT 0 +// HUMAN CULTISTS SLOTS +#define bits_SLOT_CULTIST_ENGAGE1 ( 1 << 0 ) +#define bits_SLOT_CULTIST_ENGAGE2 ( 1 << 1 ) +#define bits_SLOTS_CULTIST_ENGAGE ( bits_SLOT_CULTIST_ENGAGE1 | bits_SLOT_CULTIST_ENGAGE2 ) +// Priests and Cultists use the same engagement slots +#define bits_SLOTS_PRIEST_ENGAGE ( bits_SLOT_CULTIST_ENGAGE1 | bits_SLOT_CULTIST_ENGAGE2 ) + +// GANGSTER SLOTS +#define bits_SLOT_GANGSTER_ENGAGE1 ( 1 << 2 ) +#define bits_SLOT_GANGSTER_ENGAGE2 ( 1 << 3 ) +#define bits_SLOTS_GANGSTER_ENGAGE ( bits_SLOT_GANGSTER_ENGAGE1 | bits_SLOT_GANGSTER_ENGAGE2 ) + // HUMAN GRUNT SLOTS -#define bits_SLOT_HGRUNT_ENGAGE1 ( 1 << 0 ) -#define bits_SLOT_HGRUNT_ENGAGE2 ( 1 << 1 ) +#define bits_SLOT_HGRUNT_ENGAGE1 ( 1 << 4 ) +#define bits_SLOT_HGRUNT_ENGAGE2 ( 1 << 5 ) #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_SLOT_HGRUNT_GRENADE1 ( 1 << 6 ) +#define bits_SLOT_HGRUNT_GRENADE2 ( 1 << 7 ) #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_SLOT_AGRUNT_HORNET1 ( 1 << 8 ) +#define bits_SLOT_AGRUNT_HORNET2 ( 1 << 9 ) +#define bits_SLOT_AGRUNT_CHASE ( 1 << 10 ) #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_SLOT_HOUND_ATTACK1 ( 1 << 11 ) +#define bits_SLOT_HOUND_ATTACK2 ( 1 << 12 ) +#define bits_SLOT_HOUND_ATTACK3 ( 1 << 13 ) #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 +// ORDERLY SLOTS +#define bits_SLOT_ORDERLY_ENGAGE1 ( 1 << 14 ) +#define bits_SLOT_ORDERLY_ENGAGE2 ( 1 << 15 ) +#define bits_SLOTS_ORDERLY_ENGAGE ( bits_SLOT_ORDERLY_ENGAGE1 | bits_SLOT_ORDERLY_ENGAGE2 ) -#define NUM_SLOTS 11// update this every time you add/remove a slot. +// global slots +#define bits_SLOT_SQUAD_SPLIT ( 1 << 16 )// squad members don't all have the same enemy + +#define NUM_SLOTS 17// update this every time you add/remove a slot. #define MAX_SQUAD_MEMBERS 5 @@ -69,7 +86,7 @@ public: int m_iMySlot;// this is the behaviour slot that the monster currently holds in the squad. int CheckEnemy( CBaseEntity *pEnemy ); - void StartMonster( void ); + virtual void StartMonster( void ); void VacateSlot( void ); void ScheduleChange( void ); void Killed( entvars_t *pevAttacker, int iGib ); diff --git a/dlls/talkmonster.cpp b/dlls/talkmonster.cpp index dfc0c0ec..04d2a67b 100644 --- a/dlls/talkmonster.cpp +++ b/dlls/talkmonster.cpp @@ -53,7 +53,9 @@ IMPLEMENT_SAVERESTORE( CTalkMonster, CBaseMonster ) // array of friend names const char *CTalkMonster::m_szFriends[TLK_CFRIENDS] = { - "monster_barney", + "monster_civilian", + "monster_sitting_civilian", + "monster_policeman", "monster_scientist", "monster_sitting_scientist", }; diff --git a/dlls/talkmonster.h b/dlls/talkmonster.h index 6d6d0f03..14fe4043 100644 --- a/dlls/talkmonster.h +++ b/dlls/talkmonster.h @@ -37,7 +37,7 @@ #define bit_saidHeard (1<<6) #define bit_saidSmelled (1<<7) -#define TLK_CFRIENDS 3 +#define TLK_CFRIENDS 5 typedef enum { diff --git a/dlls/tentacle.cpp b/dlls/tentacle.cpp index e6f14b5a..b1137bd9 100644 --- a/dlls/tentacle.cpp +++ b/dlls/tentacle.cpp @@ -287,7 +287,12 @@ void CTentacle::Spawn() void CTentacle::Precache() { - PRECACHE_MODEL( "models/tentacle2.mdl" ); + const char *szModel = "models/tentacle2.mdl" + + if( pev->model ) + szModel = STRING( pev->model ); + + PRECACHE_MODEL( szModel ); PRECACHE_SOUND( "ambience/flies.wav" ); PRECACHE_SOUND( "ambience/squirm2.wav" ); diff --git a/dlls/triggers.cpp b/dlls/triggers.cpp index 607b7af1..6482cc31 100644 --- a/dlls/triggers.cpp +++ b/dlls/triggers.cpp @@ -33,6 +33,7 @@ #include "weapons.h" //LRC, for trigger_hevcharge #include "movewith.h" //LRC #include "locus.h" //LRC +#include "triggers.h" //#include "hgrunt.h" //#include "islave.h" @@ -48,22 +49,6 @@ extern DLL_GLOBAL BOOL g_fGameOver; extern void SetMovedir(entvars_t* pev); extern Vector VecBModelOrigin( entvars_t* pevBModel ); -class CFrictionModifier : public CBaseEntity -{ -public: - void Spawn( void ); - void KeyValue( KeyValueData *pkvd ); - void EXPORT ChangeFriction( CBaseEntity *pOther ); - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - - virtual int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } - - static TYPEDESCRIPTION m_SaveData[]; - - float m_frictionFraction; // Sorry, couldn't resist this name :) -}; - LINK_ENTITY_TO_CLASS( func_friction, CFrictionModifier ) // Global Savedata for changelevel friction modifier @@ -108,24 +93,6 @@ void CFrictionModifier::KeyValue( KeyValueData *pkvd ) #define SF_AUTO_FIREONCE 0x0001 #define SF_AUTO_FROMPLAYER 0x0002 -class CAutoTrigger : public CBaseDelay -{ -public: - void KeyValue( KeyValueData *pkvd ); - void Activate( void ); - void DesiredAction( void ); - - int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - - static TYPEDESCRIPTION m_SaveData[]; - -private: - string_t m_globalstate; - USE_TYPE triggerType; -}; - LINK_ENTITY_TO_CLASS( trigger_auto, CAutoTrigger ) TYPEDESCRIPTION CAutoTrigger::m_SaveData[] = @@ -198,25 +165,6 @@ void CAutoTrigger::DesiredAction( void ) #define SF_RELAY_DEBUG 0x00000002 #define SF_RELAY_USESAME 0x80000000 -class CTriggerRelay : public CBaseDelay -{ -public: - void KeyValue( KeyValueData *pkvd ); - void Spawn( void ); - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - - int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - - static TYPEDESCRIPTION m_SaveData[]; - -private: - USE_TYPE m_triggerType; - int m_sMaster; - string_t m_iszAltTarget; -}; - LINK_ENTITY_TO_CLASS( trigger_relay, CTriggerRelay ) TYPEDESCRIPTION CTriggerRelay::m_SaveData[] = @@ -400,7 +348,7 @@ void CTriggerRotTest::PostSpawn( void ) m_pReference = UTIL_FindEntityByTargetname(NULL, STRING(pev->netname)); m_pBridge = UTIL_FindEntityByTargetname(NULL, STRING(pev->noise1)); m_pHinge = UTIL_FindEntityByTargetname(NULL, STRING(pev->message)); - pev->armorvalue = 0; // initial angle + pev->sanity = 0; // initial angle if (pev->armortype == 0) //angle offset pev->armortype = 30; SetNextThink( 1 ); @@ -417,7 +365,7 @@ void CTriggerRotTest::Think( void ) } if (m_pMarker) { - Vector vecTemp = UTIL_AxisRotationToVec( (m_pHinge->pev->origin - pev->origin).Normalize(), pev->armorvalue ); + Vector vecTemp = UTIL_AxisRotationToVec( (m_pHinge->pev->origin - pev->origin).Normalize(), pev->sanity ); m_pMarker->pev->origin = pev->origin + pev->health * vecTemp; // ALERT(at_console, "vecTemp = %.2f %.2f %.2f\n", vecTemp.x, vecTemp.y, vecTemp.z); @@ -425,14 +373,14 @@ void CTriggerRotTest::Think( void ) } if (m_pBridge) { - Vector vecTemp = UTIL_AxisRotationToAngles( (m_pHinge->pev->origin - pev->origin).Normalize(), pev->armorvalue ); + Vector vecTemp = UTIL_AxisRotationToAngles( (m_pHinge->pev->origin - pev->origin).Normalize(), pev->sanity ); m_pBridge->pev->origin = pev->origin; m_pBridge->pev->angles = vecTemp; // ALERT(at_console, "vecTemp = %.2f %.2f %.2f\n", vecTemp.x, vecTemp.y, vecTemp.z); // ALERT(at_console, "Set Marker = %.2f %.2f %.2f\n", m_pMarker->pev->origin.x, m_pMarker->pev->origin.y, m_pMarker->pev->origin.z); } - pev->armorvalue += pev->armortype * 0.1; + pev->sanity += pev->armortype * 0.1; SetNextThink( 0.1 ); } @@ -442,72 +390,6 @@ void CTriggerRotTest::Think( void ) // FLAG: THREAD (create clones when triggered) // FLAG: CLONE (this is a clone for a threaded execution) -#define SF_MULTIMAN_CLONE 0x80000000 -#define SF_MULTIMAN_SAMETRIG 0x40000000 -#define SF_MULTIMAN_TRIGCHOSEN 0x20000000 - -#define SF_MULTIMAN_THREAD 0x00000001 -#define SF_MULTIMAN_LOOP 0x00000004 -#define SF_MULTIMAN_ONLYONCE 0x00000008 -#define SF_MULTIMAN_SPAWNFIRE 0x00000010 -#define SF_MULTIMAN_DEBUG 0x00000020 - -#define MM_MODE_CHOOSE 1 -#define MM_MODE_PERCENT 2 -#define MM_MODE_SIMULTANEOUS 3 - -class CMultiManager : public CBaseEntity//Toggle -{ -public: - void KeyValue( KeyValueData *pkvd ); - void Spawn( void ); - void EXPORT UseThink ( void ); - void EXPORT ManagerThink( void ); - void EXPORT ManagerUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - -#if _DEBUG - void EXPORT ManagerReport( void ); -#endif - BOOL HasTarget( string_t targetname ); - - int ObjectCaps( void ) { return CBaseEntity::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } - - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - - static TYPEDESCRIPTION m_SaveData[]; - - STATE m_iState; - virtual STATE GetState( void ) { return m_iState; }; - - int m_cTargets; // the total number of targets in this manager's fire list. - int m_index; // Current target - float m_startTime;// Time we started firing - string_t m_iTargetName[MAX_MULTI_TARGETS];// list if indexes into global string array - float m_flTargetDelay[MAX_MULTI_TARGETS];// delay (in seconds) from time of manager fire to target fire - - float m_flWait; //LRC- minimum length of time to wait - float m_flMaxWait; //LRC- random, maximum length of time to wait - string_t m_sMaster; //LRC- master - int m_iMode; //LRC- 0 = timed, 1 = pick random, 2 = each random - int m_iszThreadName; //LRC - int m_iszLocusThread; //LRC - - EHANDLE m_hActivator; -private: - USE_TYPE m_triggerType; //LRC - inline BOOL IsClone( void ) { return ( pev->spawnflags & SF_MULTIMAN_CLONE ) ? TRUE : FALSE; } - inline BOOL ShouldClone( void ) - { - if( IsClone() ) - return FALSE; - - return ( pev->spawnflags & SF_MULTIMAN_THREAD ) ? TRUE : FALSE; - } - - CMultiManager *Clone( void ); -}; - LINK_ENTITY_TO_CLASS( multi_manager, CMultiManager ) // Global Savedata for multi_manager @@ -701,6 +583,16 @@ void CMultiManager::ManagerThink( void ) SetThink(&CMultiManager :: SUB_Remove ); SetNextThink( 0.1 ); SetUse( NULL ); + + // Cthulhu: but we still need to do our last triggers... + while( index < m_cTargets && m_flTargetDelay[index] <= time ) + { + // ALERT( at_debug,"Manager sends %d to %s\n", m_triggerType, STRING( m_iTargetName[m_index] ) ); + FireTargets( STRING( m_iTargetName[index] ), m_hActivator, this, m_triggerType, 0 ); + index++; + } + + return; } else { @@ -836,6 +728,10 @@ void CMultiManager::ManagerThink( void ) AbsoluteNextThink( m_startTime + m_flTargetDelay[ m_index ] ); } + // we don't do stuff while master is not triggered + if( !UTIL_IsMasterTriggered( m_sMaster, m_hActivator ) ) + return; + while ( index < m_cTargets && m_flTargetDelay[ index ] <= time ) { // ALERT(at_console,"Manager sends %d to %s\n",m_triggerType,STRING(m_iTargetName[m_index])); @@ -859,6 +755,7 @@ CMultiManager *CMultiManager::Clone( void ) if (m_iszThreadName) pMulti->pev->targetname = m_iszThreadName; //LRC pMulti->m_triggerType = m_triggerType; //LRC pMulti->m_iMode = m_iMode; //LRC + pMulti->m_sMaster = m_sMaster; //LRC pMulti->m_flWait = m_flWait; //LRC pMulti->m_flMaxWait = m_flMaxWait; //LRC memcpy( pMulti->m_iTargetName, m_iTargetName, sizeof( m_iTargetName ) ); @@ -1002,46 +899,6 @@ void CMultiManager::ManagerReport( void ) //LRC- multi_watcher entity: useful? Well, I think it is. And I'm worth it. :) //*********************************************************** -#define SF_SWATCHER_SENDTOGGLE 0x1 -#define SF_SWATCHER_DONTSEND_ON 0x2 -#define SF_SWATCHER_DONTSEND_OFF 0x4 -#define SF_SWATCHER_NOTON 0x8 -#define SF_SWATCHER_OFF 0x10 -#define SF_SWATCHER_TURN_ON 0x20 -#define SF_SWATCHER_TURN_OFF 0x40 -#define SF_SWATCHER_IN_USE 0x80 -#define SF_SWATCHER_VALID 0x200 - -#define SWATCHER_LOGIC_AND 0 -#define SWATCHER_LOGIC_OR 1 -#define SWATCHER_LOGIC_NAND 2 -#define SWATCHER_LOGIC_NOR 3 -#define SWATCHER_LOGIC_XOR 4 -#define SWATCHER_LOGIC_XNOR 5 - -class CStateWatcher : public CBaseToggle -{ -public: - void Spawn ( void ); - void EXPORT Think ( void ); - void KeyValue( KeyValueData *pkvd ); - virtual STATE GetState( void ); - virtual STATE GetState( CBaseEntity *pActivator ); - virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } - - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - - static TYPEDESCRIPTION m_SaveData[]; - - int m_fLogic; // Logic by which to combine the targets - int m_cTargets; // the total number of targets in this manager's fire list. - int m_iTargetName [ MAX_MULTI_TARGETS ];// list of indexes into global string array -// CBaseEntity* m_pTargetEnt [ MAX_MULTI_TARGETS ]; - - BOOL EvalLogic ( CBaseEntity *pEntity ); -}; - LINK_ENTITY_TO_CLASS( multi_watcher, CStateWatcher ); LINK_ENTITY_TO_CLASS( watcher, CStateWatcher ); @@ -1259,18 +1116,6 @@ BOOL CStateWatcher :: EvalLogic ( CBaseEntity *pActivator ) } } -//*********************************************************** -#define SF_WRCOUNT_FIRESTART 0x0001 -#define SF_WRCOUNT_STARTED 0x8000 -class CWatcherCount : public CBaseToggle -{ -public: - void Spawn ( void ); - void EXPORT Think ( void ); - virtual STATE GetState( void ) { return (pev->spawnflags & SF_SWATCHER_VALID)?STATE_ON:STATE_OFF; }; - virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } -}; - LINK_ENTITY_TO_CLASS( watcher_count, CWatcherCount ); void CWatcherCount :: Spawn ( void ) @@ -1323,49 +1168,8 @@ void CWatcherCount :: Think ( void ) //*********************************************************** -// -// Render parameters trigger -// -// This entity will copy its render parameters (renderfx, rendermode, rendercolor, renderamt) -// to its targets when triggered. -// - -// Flags to indicate masking off various render parameters that are normally copied to the targets -#define SF_RENDER_MASKFX ( 1 << 0 ) -#define SF_RENDER_MASKAMT ( 1 << 1 ) -#define SF_RENDER_MASKMODE ( 1 << 2 ) -#define SF_RENDER_MASKCOLOR ( 1 << 3 ) -//LRC -#define SF_RENDER_KILLTARGET (1<<5) -#define SF_RENDER_ONLYONCE (1<<6) - //LRC- RenderFxFader, a subsidiary entity for RenderFxManager -class CRenderFxFader : public CBaseEntity -{ -public: - void Spawn( void ); - void EXPORT FadeThink ( void ); - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } - - static TYPEDESCRIPTION m_SaveData[]; - - float m_flStartTime; - float m_flDuration; - float m_flCoarseness; - int m_iStartAmt; - int m_iOffsetAmt; - Vector m_vecStartColor; - Vector m_vecOffsetColor; - float m_fStartScale; - float m_fOffsetScale; - EHANDLE m_hTarget; - - int m_iszAmtFactor; -}; - TYPEDESCRIPTION CRenderFxFader::m_SaveData[] = { DEFINE_FIELD( CRenderFxFader, m_flStartTime, FIELD_FLOAT), @@ -1433,17 +1237,6 @@ void CRenderFxFader :: FadeThink( void ) } } - -// RenderFxManager itself -class CRenderFxManager : public CPointEntity -{ -public: - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void Affect( CBaseEntity *pEntity, BOOL bIsLocus, CBaseEntity *pActivator ); - - void KeyValue( KeyValueData *pkvd ); -}; - LINK_ENTITY_TO_CLASS( env_render, CRenderFxManager ) void CRenderFxManager :: KeyValue( KeyValueData *pkvd ) @@ -1570,45 +1363,6 @@ void CRenderFxManager::Affect( CBaseEntity *pTarget, BOOL bIsFirst, CBaseEntity #define CUSTOM_FLAG_USETYPE 4 #define CUSTOM_FLAG_INVUSETYPE 5 -class CEnvCustomize : public CBaseEntity -{ -public: - void Spawn( void ); - void Precache( void ); - void PostSpawn( void ); - void DesiredAction( void ); - void Use ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - - void Affect (CBaseEntity *pTarget, USE_TYPE useType); - int GetActionFor( int iField, int iActive, USE_TYPE useType, const char *szDebug ); - void SetBoneController (float fController, int cnum, CBaseEntity *pTarget); - - virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } - - void KeyValue( KeyValueData *pkvd ); - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - - static TYPEDESCRIPTION m_SaveData[]; - - float m_flRadius; - int m_iszModel; - int m_iClass; - int m_iPlayerReact; - int m_iPrisoner; - int m_iMonsterClip; - int m_iVisible; - int m_iSolid; - int m_iProvoked; - int m_voicePitch; - int m_iBloodColor; - float m_fFramerate; - float m_fController0; - float m_fController1; - float m_fController2; - float m_fController3; -}; - LINK_ENTITY_TO_CLASS( env_customize, CEnvCustomize ); TYPEDESCRIPTION CEnvCustomize::m_SaveData[] = @@ -2114,23 +1868,14 @@ void CEnvCustomize::SetBoneController( float fController, int cnum, CBaseEntity } } -//===================================== -// trigger_x entities - -class CBaseTrigger : public CBaseToggle -{ -public: - //LRC - this was very bloated. I moved lots of methods into the - // subclasses where they belonged. - void InitTrigger( void ); - void EXPORT ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - BOOL CanTouch( entvars_t *pevToucher ); - - virtual int ObjectCaps( void ) { return CBaseToggle::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } -}; - LINK_ENTITY_TO_CLASS( trigger, CBaseTrigger ); + +/* +================ +InitTrigger +================ +*/ BOOL CBaseTrigger :: CanTouch( entvars_t *pevToucher ) { if ( !pev->netname ) @@ -2196,14 +1941,6 @@ void CBaseTrigger::InitTrigger() // // trigger_hurt - hurts anything that touches it. if the trigger has a targetname, firing it will toggle state // -class CTriggerHurt : public CBaseTrigger -{ -public: - void Spawn( void ); - void EXPORT RadiationThink( void ); - void EXPORT HurtTouch ( CBaseEntity *pOther ); - virtual void KeyValue( KeyValueData *pkvd ); -}; LINK_ENTITY_TO_CLASS( trigger_hurt, CTriggerHurt ); @@ -2260,11 +1997,31 @@ void CTriggerHurt :: Spawn( void ) UTIL_SetOrigin( this, pev->origin ); // Link into the list } +void CTriggerHurt::ToggleUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // if we are turning on + if( pev->solid == SOLID_NOT ) + { + // set the think to thinkhurt + SetThink( HurtThink ); + SetNextThink( 0.0f ); + } + else + { + DontThink(); + } + + CBaseTrigger::ToggleUse( pActivator, pCaller, useType, value ); +} + // When touched, a hurt trigger does DMG points of damage each half-second void CTriggerHurt :: HurtTouch ( CBaseEntity *pOther ) { float fldmg; + if( !UTIL_IsMasterTriggered( m_sMaster, pOther ) ) + return; + if ( !pOther->pev->takedamage ) return; @@ -2318,6 +2075,36 @@ void CTriggerHurt :: HurtTouch ( CBaseEntity *pOther ) } } } + else if( !( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH ) ) // can other things be hurt... + { + if( pev->dmgtime > gpGlobals->time ) + { + if( gpGlobals->time != pev->pain_finished ) + { + // too early to hurt again, and not same frame with a different entity + int otherMask = 1 << ( pOther->entindex() - 1 ); + + // If I've already touched this entity (this time), then bail out + if( pev->impulse & otherMask ) + return; + + // Mark this entity as touched + // BUGBUG - There can be only 32 other entities! + pev->impulse |= otherMask; + } + } + else + { + // New clock, "un-touch" all players + pev->impulse = 0; + + int otherMask = 1 << ( pOther->entindex() - 1 ); + + // Mark this entity as touched + // BUGBUG - There can be only 32 other entities! + pev->impulse |= otherMask; + } + } else // Original code -- single player { if ( pev->dmgtime > gpGlobals->time && gpGlobals->time != pev->pain_finished ) @@ -2382,6 +2169,157 @@ void CTriggerHurt :: HurtTouch ( CBaseEntity *pOther ) } } +void CTriggerHurt::HurtThink( void ) +{ + // get everything in the area + const int MAX_ENTITIES = 1000; + CBaseEntity *pList[MAX_ENTITIES]; + int iEntities = UTIL_EntitiesInBox( pList, 10, pev->mins, pev->maxs, ( FL_CLIENT | FL_MONSTER ) ); + + float fldmg; + + for( int i = 0; i < iEntities; i++ ) + { + CBaseEntity *pOther = pList[i]; + + if( !pOther->pev->takedamage ) + break; + + if( ( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH) && !pOther->IsPlayer() ) + { + // this trigger is only allowed to touch clients, and this ain't a client. + break; + } + + if( ( pev->spawnflags & SF_TRIGGER_HURT_NO_CLIENTS) && pOther->IsPlayer() ) + break; + + // HACKHACK -- In multiplayer, players touch this based on packet receipt. + // So the players who send packets later aren't always hurt. Keep track of + // how much time has passed and whether or not you've touched that player + if( g_pGameRules->IsMultiplayer() ) + { + if( pev->dmgtime > gpGlobals->time ) + { + if( gpGlobals->time != pev->pain_finished ) + { + // too early to hurt again, and not same frame with a different entity + if( pOther->IsPlayer() ) + { + int playerMask = 1 << ( pOther->entindex() - 1 ); + + // If I've already touched this player (this time), then bail out + if( pev->impulse & playerMask ) + break; + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + else + { + break; + } + } + } + else + { + // New clock, "un-touch" all players + pev->impulse = 0; + if( pOther->IsPlayer() ) + { + int playerMask = 1 << ( pOther->entindex() - 1 ); + + // Mark this player as touched + // BUGBUG - There can be only 32 players! + pev->impulse |= playerMask; + } + } + } + else if( !( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYTOUCH ) ) // can other things be hurt... + { + if( pev->dmgtime > gpGlobals->time ) + { + if( gpGlobals->time != pev->pain_finished ) + { + // too early to hurt again, and not same frame with a different entity + int otherMask = 1 << ( pOther->entindex() - 1 ); + + // If I've already touched this entity (this time), then bail out + if( pev->impulse & otherMask ) + break; + + // Mark this entity as touched + // BUGBUG - There can be only 32 other entities! + pev->impulse |= otherMask; + } + } + else + { + // New clock, "un-touch" all players + pev->impulse = 0; + + int otherMask = 1 << (pOther->entindex() - 1); + + // Mark this entity as touched + // BUGBUG - There can be only 32 other entities! + pev->impulse |= otherMask; + } + } + else // Original code -- single player + { + if( pev->dmgtime > gpGlobals->time && gpGlobals->time != pev->pain_finished ) + { + // too early to hurt again, and not same frame with a different entity + break; + } + } + + // If this is time_based damage (poison, radiation), override the pev->dmg with a + // default for the given damage type. Monsters only take time-based damage + // while touching the trigger. Player continues taking damage for a while after + // leaving the trigger + + fldmg = pev->dmg * 0.5f; // 0.5 seconds worth of damage, pev->dmg is damage/second + + // hurt it!!! + if( fldmg < 0 ) + pOther->TakeHealth( -fldmg, m_bitsDamageInflict ); + else + pOther->TakeDamage( pev, pev, fldmg, m_bitsDamageInflict ); + + // Store pain time so we can get all of the other entities on this frame + pev->pain_finished = gpGlobals->time; + + // Apply damage every half second + pev->dmgtime = gpGlobals->time + 0.5f;// half second delay until this trigger can hurt toucher again + + if( pev->target ) + { + // trigger has a target it wants to fire. + if( pev->spawnflags & SF_TRIGGER_HURT_CLIENTONLYFIRE ) + { + // if the toucher isn't a client, don't fire the target! + if( !pOther->IsPlayer() ) + { + break; + } + } + + SUB_UseTargets( pOther, USE_TOGGLE, 0 ); + if( pev->spawnflags & SF_TRIGGER_HURT_TARGETONCE ) + pev->target = 0; + } + } + + // do we need to set the geiger counter off + if( m_bitsDamageInflict & DMG_RADIATION ) + { + RadiationThink(); + } + SetNextThink( 0.25f ); +} + // trigger hurt that causes radiation will do a radius // check and set the player's geiger counter level // according to distance from center of trigger @@ -2551,13 +2489,6 @@ void CTriggerHevCharge :: AnnounceThink ( ) // // trigger_monsterjump // -class CTriggerMonsterJump : public CBaseTrigger -{ -public: - void Spawn( void ); - void Touch( CBaseEntity *pOther ); - void Think( void ); -}; LINK_ENTITY_TO_CLASS( trigger_monsterjump, CTriggerMonsterJump ) @@ -2615,15 +2546,6 @@ void CTriggerMonsterJump::Touch( CBaseEntity *pOther ) // // trigger_cdaudio - starts/stops cd audio tracks // -class CTriggerCDAudio : public CBaseTrigger -{ -public: - void Spawn( void ); - - virtual void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void PlayTrack( void ); - void Touch( CBaseEntity *pOther ); -}; LINK_ENTITY_TO_CLASS( trigger_cdaudio, CTriggerCDAudio ) @@ -2757,20 +2679,6 @@ void CTargetCDAudio::Play( void ) //===================================== //trigger_multiple -class CTriggerMultiple : public CBaseTrigger -{ -public: - void Spawn( void ); - void Precache( void ) - { - if (!FStringNull(pev->noise)) - PRECACHE_SOUND(STRING(pev->noise)); - } - void EXPORT MultiTouch( CBaseEntity *pOther ); - void EXPORT MultiWaitOver( void ); - void ActivateMultiTrigger( CBaseEntity *pActivator ); -}; - LINK_ENTITY_TO_CLASS( trigger_multiple, CTriggerMultiple ) @@ -2873,12 +2781,6 @@ void CTriggerMultiple :: MultiWaitOver( void ) //===================================== //trigger_once -class CTriggerOnce : public CTriggerMultiple -{ -public: - void Spawn( void ); -}; - LINK_ENTITY_TO_CLASS( trigger_once, CTriggerOnce ); void CTriggerOnce::Spawn( void ) { @@ -2894,50 +2796,6 @@ void CTriggerOnce::Spawn( void ) // If there's more than one entity it can trigger off, then // it will trigger for each one that enters and leaves. //=========================================================== -class CTriggerInOut; - -class CInOutRegister : public CPointEntity - { -public: - // returns true if found in the list - BOOL IsRegistered ( CBaseEntity *pValue ); - // remove all invalid entries from the list, trigger their targets as appropriate - // returns the new list - CInOutRegister *Prune( void ); - // adds a new entry to the list - CInOutRegister *Add( CBaseEntity *pValue ); - BOOL IsEmpty( void ) { return m_pNext?FALSE:TRUE; }; - - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - static TYPEDESCRIPTION m_SaveData[]; - - CTriggerInOut *m_pField; - CInOutRegister *m_pNext; - EHANDLE m_hValue; -}; - -class CTriggerInOut : public CBaseTrigger - { -public: - void Spawn( void ); - void EXPORT Touch( CBaseEntity *pOther ); - void EXPORT Think( void ); - void FireOnEntry( CBaseEntity *pOther ); - void FireOnLeaving( CBaseEntity *pOther ); - - void KeyValue( KeyValueData *pkvd ); - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - static TYPEDESCRIPTION m_SaveData[]; - - STATE GetState() { return m_pRegister->IsEmpty()?STATE_OFF:STATE_ON; } - - string_t m_iszAltTarget; - string_t m_iszBothTarget; - CInOutRegister *m_pRegister; -}; - // CInOutRegister method bodies: @@ -3037,12 +2895,49 @@ LINK_ENTITY_TO_CLASS( trigger_inout, CTriggerInOut ); TYPEDESCRIPTION CTriggerInOut::m_SaveData[] = { - DEFINE_FIELD( CTriggerInOut, m_iszAltTarget, FIELD_STRING ), + // DEFINE_FIELD( CTriggerInOut, m_iszAltTarget, FIELD_STRING ), DEFINE_FIELD( CTriggerInOut, m_iszBothTarget, FIELD_STRING ), DEFINE_FIELD( CTriggerInOut, m_pRegister, FIELD_CLASSPTR ), + DEFINE_FIELD( CTriggerInOut, m_iszAltTarget, FIELD_STRING ), }; -IMPLEMENT_SAVERESTORE(CTriggerInOut,CBaseTrigger); +// Cthulhu : a hack to get the registers to work properly +// IMPLEMENT_SAVERESTORE( CTriggerInOut, CBaseTrigger ); +int CTriggerInOut::Save( CSave &save ) +{ + if( !CBaseTrigger::Save( save ) ) + return 0; + + if( pev->targetname ) + return save.WriteFields( STRING(pev->targetname), "CTriggerInOut", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + else + return save.WriteFields( STRING(pev->classname), "CTriggerInOut", this, m_SaveData, ARRAYSIZE(m_SaveData) ); +} + +int CTriggerInOut::Restore( CRestore &restore ) +{ + if( !CBaseTrigger::Restore(restore) ) + return 0; + + int status = restore.ReadFields( "CTriggerInOut", this, m_SaveData, ARRAYSIZE(m_SaveData) ); + + /* + // Cthulhu + // create a null-terminator for the registry + m_pRegister = GetClassPtr( (CInOutRegister*)NULL ); + m_pRegister->m_hValue = NULL; + m_pRegister->m_pNext = NULL; + m_pRegister->m_pField = this; + m_pRegister->pev->classname = MAKE_STRING( "inout_register" ); + + // and set a flag + mbRestored = TRUE; + */ + // and schedule a think + SetNextThink( 0.1f ); + + return status; +} void CTriggerInOut::KeyValue( KeyValueData *pkvd ) { @@ -3111,11 +3006,8 @@ void CTriggerInOut :: FireOnLeaving( CBaseEntity *pEnt ) } -// ============================== -// trigger_counter +// ========================= COUNTING TRIGGER ===================================== -//After the counter has been triggered "cTriggersLeft" -//times (default 2), it will fire all of it's targets and remove itself. class CTriggerCounter : public CTriggerMultiple { public: @@ -3191,12 +3083,6 @@ void CTriggerCounter::CounterUse( CBaseEntity *pActivator, CBaseEntity *pCaller, // ====================== TRIGGER_CHANGELEVEL ================================ -class CTriggerVolume : public CPointEntity // Derive from point entity so this doesn't move across levels -{ -public: - void Spawn( void ); -}; - LINK_ENTITY_TO_CLASS( trigger_transition, CTriggerVolume ) // Define space that travels across a level transition @@ -3210,14 +3096,6 @@ void CTriggerVolume::Spawn( void ) } // Fires a target after level transition and then dies -class CFireAndDie : public CBaseDelay -{ -public: - void Spawn( void ); - void Precache( void ); - void Think( void ); - int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() | FCAP_FORCE_TRANSITION; } // Always go across transitions -}; LINK_ENTITY_TO_CLASS( fireanddie, CFireAndDie ) @@ -3240,32 +3118,6 @@ void CFireAndDie::Think( void ) } #define SF_CHANGELEVEL_USEONLY 0x0002 -class CChangeLevel : public CBaseTrigger -{ -public: - void Spawn( void ); - void KeyValue( KeyValueData *pkvd ); - void EXPORT UseChangeLevel( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void EXPORT TriggerChangeLevel( void ); - void EXPORT ExecuteChangeLevel( void ); - void EXPORT TouchChangeLevel( CBaseEntity *pOther ); - void ChangeLevelNow( CBaseEntity *pActivator ); - - static edict_t *FindLandmark( const char *pLandmarkName ); - static int ChangeList( LEVELLIST *pLevelList, int maxList ); - static int AddTransitionToList( LEVELLIST *pLevelList, int listCount, const char *pMapName, const char *pLandmarkName, edict_t *pentLandmark ); - static int InTransitionVolume( CBaseEntity *pEntity, char *pVolumeName ); - - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - - static TYPEDESCRIPTION m_SaveData[]; - - char m_szMapName[cchMapNameMost]; // trigger_changelevel only: next map - char m_szLandmarkName[cchMapNameMost]; // trigger_changelevel only: landmark on next map - string_t m_changeTarget; - float m_changeTargetDelay; -}; LINK_ENTITY_TO_CLASS( trigger_changelevel, CChangeLevel ) @@ -3293,7 +3145,8 @@ void CChangeLevel::KeyValue( KeyValueData *pkvd ) //LRC -- don't allow changelevels to contain capital letters; it causes problems // ALERT(at_console, "MapName %s ", m_szMapName); - for (int i = 0; m_szMapName[i]; i++) { m_szMapName[i] = tolower(m_szMapName[i]); } + // Cthulhu - this line causes a bug!!! + // for (int i = 0; m_szMapName[i]; i++) { m_szMapName[i] = tolower(m_szMapName[i]); } // ALERT(at_console, "changed to %s\n", m_szMapName); pkvd->fHandled = TRUE; @@ -3303,6 +3156,13 @@ void CChangeLevel::KeyValue( KeyValueData *pkvd ) if( strlen( pkvd->szValue ) >= cchMapNameMost ) ALERT( at_error, "Landmark name '%s' too long (32 chars)\n", pkvd->szValue ); strcpy( m_szLandmarkName, pkvd->szValue ); + + // Cthulhu: is this the insane map landmark + if( FStrEq( m_szLandmarkName, "lm_insane" ) ) + { + strcpy( m_szMapName, st_szPreInsaneMap ); + } + pkvd->fHandled = TRUE; } else if( FStrEq( pkvd->szKeyName, "changetarget" ) ) @@ -3658,14 +3518,6 @@ void NextLevel( void ) #define SF_LADDER_VISIBLE 1 -class CLadder : public CBaseTrigger -{ -public: - void KeyValue( KeyValueData *pkvd ); - void Spawn( void ); - void Precache( void ); -}; - LINK_ENTITY_TO_CLASS( func_ladder, CLadder ) void CLadder::KeyValue( KeyValueData *pkvd ) @@ -3703,20 +3555,6 @@ void CLadder::Spawn( void ) // ========================== A TRIGGER THAT PUSHES YOU =============================== -class CTriggerPush : public CBaseTrigger -{ -public: - void Spawn( void ); - void KeyValue( KeyValueData *pkvd ); - void Touch( CBaseEntity *pOther ); - - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - static TYPEDESCRIPTION m_SaveData[]; - - int m_iszPushVel; - int m_iszPushSpeed; -}; LINK_ENTITY_TO_CLASS( trigger_push, CTriggerPush ) TYPEDESCRIPTION CTriggerPush::m_SaveData[] = @@ -3796,6 +3634,9 @@ void CTriggerPush::Touch( CBaseEntity *pOther ) else vecPush = vecPush * 100; + if( pev->solid == SOLID_NOT ) + return; + if( pevToucher->solid != SOLID_NOT && pevToucher->solid != SOLID_BSP ) { // Instant trigger, just transfer velocity and remove @@ -3849,10 +3690,10 @@ void CTriggerBounce :: Touch( CBaseEntity *pOther ) return; float dot = DotProduct(pev->movedir, pOther->pev->velocity); - if (dot < -pev->armorvalue) + if (dot < -pev->sanity) { if (pev->spawnflags & SF_BOUNCE_CUTOFF) - pOther->pev->velocity = pOther->pev->velocity - (dot + pev->frags*(dot+pev->armorvalue))*pev->movedir; + pOther->pev->velocity = pOther->pev->velocity - (dot + pev->frags*(dot+pev->sanity))*pev->movedir; else pOther->pev->velocity = pOther->pev->velocity - (dot + pev->frags*dot)*pev->movedir; SUB_UseTargets( pOther, USE_TOGGLE, 0 ); @@ -4040,12 +3881,6 @@ BOOL CTriggerOnSight :: CanSee(CBaseEntity *pLooker, CBaseEntity *pSeen) // teleport trigger // // -class CTriggerTeleport : public CBaseTrigger -{ -public: - void Spawn( void ); - void EXPORT TeleportTouch ( CBaseEntity *pOther ); -}; LINK_ENTITY_TO_CLASS( trigger_teleport, CTriggerTeleport ) @@ -4153,13 +3988,6 @@ void CTriggerTeleport :: TeleportTouch( CBaseEntity *pOther ) LINK_ENTITY_TO_CLASS( info_teleport_destination, CPointEntity ) -class CTriggerSave : public CBaseTrigger -{ -public: - void Spawn( void ); - void EXPORT SaveTouch( CBaseEntity *pOther ); -}; - LINK_ENTITY_TO_CLASS( trigger_autosave, CTriggerSave ) void CTriggerSave::Spawn( void ) @@ -4190,15 +4018,6 @@ void CTriggerSave::SaveTouch( CBaseEntity *pOther ) #define SF_ENDSECTION_USEONLY 0x0001 -class CTriggerEndSection : public CBaseTrigger -{ -public: - void Spawn( void ); - void EXPORT EndSectionTouch( CBaseEntity *pOther ); - void KeyValue( KeyValueData *pkvd ); - void EXPORT EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); -}; - LINK_ENTITY_TO_CLASS( trigger_endsection, CTriggerEndSection ) void CTriggerEndSection::EndSectionUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) @@ -4261,13 +4080,6 @@ void CTriggerEndSection::KeyValue( KeyValueData *pkvd ) CBaseTrigger::KeyValue( pkvd ); } -class CTriggerGravity : public CBaseTrigger -{ -public: - void Spawn( void ); - void EXPORT GravityTouch( CBaseEntity *pOther ); -}; - LINK_ENTITY_TO_CLASS( trigger_gravity, CTriggerGravity ) void CTriggerGravity::Spawn( void ) @@ -4291,22 +4103,7 @@ void CTriggerGravity::GravityTouch( CBaseEntity *pOther ) //=========================================================== //LRC- trigger_startpatrol //=========================================================== -class CTriggerSetPatrol : public CBaseDelay -{ -public: - void KeyValue( KeyValueData *pkvd ); - void Spawn( void ); - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - - int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - - static TYPEDESCRIPTION m_SaveData[]; - -private: - int m_iszPath; -}; +// new class for Spirit LINK_ENTITY_TO_CLASS( trigger_startpatrol, CTriggerSetPatrol ); TYPEDESCRIPTION CTriggerSetPatrol::m_SaveData[] = @@ -4349,26 +4146,6 @@ void CTriggerSetPatrol::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_ //LRC- trigger_motion //=========================================================== #define SF_MOTION_DEBUG 1 -class CTriggerMotion : public CPointEntity -{ -public: - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - static TYPEDESCRIPTION m_SaveData[]; - - void KeyValue( KeyValueData *pkvd ); - - int m_iszPosition; - int m_iPosMode; - int m_iszAngles; - int m_iAngMode; - int m_iszVelocity; - int m_iVelMode; - int m_iszAVelocity; - int m_iAVelMode; -}; LINK_ENTITY_TO_CLASS( trigger_motion, CTriggerMotion ); TYPEDESCRIPTION CTriggerMotion::m_SaveData[] = @@ -4563,22 +4340,6 @@ void CTriggerMotion::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYP //=========================================================== //LRC- motion_manager //=========================================================== -class CMotionThread : public CPointEntity -{ -public: - void Think( void ); - - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - static TYPEDESCRIPTION m_SaveData[]; - - int m_iszPosition; - int m_iPosMode; - int m_iszFacing; - int m_iFaceMode; - EHANDLE m_hLocus; - EHANDLE m_hTarget; -}; LINK_ENTITY_TO_CLASS( motion_thread, CPointEntity ); TYPEDESCRIPTION CMotionThread::m_SaveData[] = @@ -4714,23 +4475,6 @@ void CMotionThread::Think( void ) } -class CMotionManager : public CPointEntity -{ -public: - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void KeyValue( KeyValueData *pkvd ); - void Affect( CBaseEntity *pTarget, CBaseEntity *pActivator ); - void PostSpawn( void ); - - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - static TYPEDESCRIPTION m_SaveData[]; - - int m_iszPosition; - int m_iPosMode; - int m_iszFacing; - int m_iFaceMode; -}; LINK_ENTITY_TO_CLASS( motion_manager, CMotionManager ); TYPEDESCRIPTION CMotionManager::m_SaveData[] = @@ -4813,23 +4557,6 @@ void CMotionManager::Affect( CBaseEntity *pTarget, CBaseEntity *pActivator ) // this is a really bad idea. -class CTriggerChangeTarget : public CBaseDelay -{ -public: - void KeyValue( KeyValueData *pkvd ); - void Spawn( void ); - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - - int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - - static TYPEDESCRIPTION m_SaveData[]; - -private: - string_t m_iszNewTarget; -}; - LINK_ENTITY_TO_CLASS( trigger_changetarget, CTriggerChangeTarget ) TYPEDESCRIPTION CTriggerChangeTarget::m_SaveData[] = @@ -4932,12 +4659,6 @@ void CTriggerChangeValue::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, US //===================================================== // trigger_command: activate a console command //===================================================== -class CTriggerCommand : public CBaseEntity -{ -public: - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } -}; LINK_ENTITY_TO_CLASS( trigger_command, CTriggerCommand ); void CTriggerCommand::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) @@ -4956,19 +4677,6 @@ void CTriggerCommand::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TY //========================================================= #define SF_CVAR_ACTIVE 0x80000 -class CTriggerChangeCVar : public CBaseEntity -{ -public: - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void EXPORT Think( void ); - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - virtual int ObjectCaps( void ) { return CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } - - static TYPEDESCRIPTION m_SaveData[]; - - char m_szStoredString[256]; -}; LINK_ENTITY_TO_CLASS( trigger_changecvar, CTriggerChangeCVar ); TYPEDESCRIPTION CTriggerChangeCVar::m_SaveData[] = @@ -4997,9 +4705,9 @@ void CTriggerChangeCVar::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE sprintf( szCommand, "%s \"%s\"\n", STRING(pev->netname), STRING(pev->message) ); pev->spawnflags |= SF_CVAR_ACTIVE; - if (pev->armorvalue >= 0) + if (pev->sanity >= 0) { - SetNextThink( pev->armorvalue ); + SetNextThink( pev->sanity ); } } SERVER_COMMAND( szCommand ); @@ -5123,6 +4831,8 @@ void CTriggerCamera::KeyValue( KeyValueData *pkvd ) CBaseDelay::KeyValue( pkvd ); } +extern int gmsgHideWeapon; + void CTriggerCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) { if( !ShouldToggle( useType, m_state ) ) @@ -5212,6 +4922,10 @@ void CTriggerCamera::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYP SET_VIEW( pActivator->edict(), edict() ); } + MESSAGE_BEGIN( MSG_ONE, gmsgHideWeapon, NULL, ((CBasePlayer *)pActivator)->pev ); + WRITE_BYTE( HIDEHUD_WEAPONS ); + MESSAGE_END(); + SET_MODEL( ENT( pev ), STRING( pActivator->pev->model ) ); // follow the player down @@ -5236,6 +4950,10 @@ void CTriggerCamera::FollowTarget() } SUB_UseTargets( this, USE_TOGGLE, 0 ); pev->avelocity = Vector( 0, 0, 0 ); + + MESSAGE_BEGIN( MSG_ONE, gmsgHideWeapon, NULL, ((CBasePlayer *)((CBaseEntity *)m_hPlayer))->pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); m_state = 0; return; } @@ -5313,7 +5031,11 @@ void CTriggerCamera::Move() Vector delta = m_pentPath->pev->origin - pev->origin; m_moveDistance = delta.Length(); pev->movedir = delta.Normalize(); - m_flStopTime = gpGlobals->time + m_pentPath->GetDelay(); + // I think there is a bug here. + float fStop = gpGlobals->time + m_pentPath->GetDelay(); + if( gpGlobals->time >= m_flStopTime ) + m_flStopTime = fStop; + // m_flStopTime = gpGlobals->time + m_pentPath->GetDelay(); } } diff --git a/dlls/util.cpp b/dlls/util.cpp index bebe3d9c..75061cfb 100644 --- a/dlls/util.cpp +++ b/dlls/util.cpp @@ -284,7 +284,7 @@ TYPEDESCRIPTION gEntvarsDescription[] = DEFINE_ENTITY_FIELD( max_health, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( teleport_time, FIELD_TIME ), DEFINE_ENTITY_FIELD( armortype, FIELD_FLOAT ), - DEFINE_ENTITY_FIELD( armorvalue, FIELD_FLOAT ), + DEFINE_ENTITY_FIELD( sanity, FIELD_FLOAT ), DEFINE_ENTITY_FIELD( waterlevel, FIELD_INTEGER ), DEFINE_ENTITY_FIELD( watertype, FIELD_INTEGER ), @@ -1149,6 +1149,15 @@ void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ) MESSAGE_END(); } +extern int gmsgReadBook; + +void UTIL_ReadBook( const char *szImage ) +{ + MESSAGE_BEGIN( MSG_ALL, gmsgReadBook, NULL ); + WRITE_STRING( szImage ); + MESSAGE_END(); +} + char *UTIL_dtos1( int d ) { static char buf[8]; diff --git a/dlls/util.h b/dlls/util.h index a05c338b..a7071ae6 100644 --- a/dlls/util.h +++ b/dlls/util.h @@ -380,6 +380,7 @@ extern void ClientPrint( entvars_t *client, int msg_dest, const char *msg_name, // prints a message to the HUD say (chat) extern void UTIL_SayText( const char *pText, CBaseEntity *pEntity ); extern void UTIL_SayTextAll( const char *pText, CBaseEntity *pEntity ); +extern void UTIL_ReadBook( const char *szImage ); typedef struct hudtextparms_s { diff --git a/dlls/weapons.cpp b/dlls/weapons.cpp index b859b46c..a11baad9 100644 --- a/dlls/weapons.cpp +++ b/dlls/weapons.cpp @@ -167,8 +167,6 @@ void DecalGunshot( TraceResult *pTrace, int iBulletType ) case BULLET_MONSTER_9MM: case BULLET_PLAYER_MP5: case BULLET_MONSTER_MP5: - case BULLET_PLAYER_BUCKSHOT: - case BULLET_PLAYER_357: default: // smoke and decal UTIL_GunshotDecalTrace( pTrace, DamageDecal( pEntity, DMG_BULLET ) ); @@ -296,62 +294,86 @@ void W_Precache( void ) // common world objects UTIL_PrecacheOther( "item_suit" ); UTIL_PrecacheOther( "item_healthkit" ); - UTIL_PrecacheOther( "item_battery" ); + // UTIL_PrecacheOther( "item_battery" ); UTIL_PrecacheOther( "item_antidote" ); - UTIL_PrecacheOther( "item_security" ); + // UTIL_PrecacheOther( "item_security" ); + UTIL_PrecacheOther( "item_key" ); UTIL_PrecacheOther( "item_longjump" ); // shotgun UTIL_PrecacheOtherWeapon( "weapon_shotgun" ); - UTIL_PrecacheOther( "ammo_buckshot" ); + UTIL_PrecacheOther( "ammo_shotgun" ); - // crowbar - UTIL_PrecacheOtherWeapon( "weapon_crowbar" ); + // swordcane & knife + UTIL_PrecacheOtherWeapon( "weapon_knife" ); + UTIL_PrecacheOtherWeapon( "weapon_swordcane" ); - // glock - UTIL_PrecacheOtherWeapon( "weapon_9mmhandgun" ); - UTIL_PrecacheOther( "ammo_9mmclip" ); - UTIL_PrecacheOther( "ammo_9mmbox" ); //LRC + // revolver + UTIL_PrecacheOtherWeapon( "weapon_revolver" ); + UTIL_PrecacheOther( "ammo_revolver" ); - // mp5 - UTIL_PrecacheOtherWeapon( "weapon_9mmAR" ); - UTIL_PrecacheOther( "ammo_9mmAR" ); - UTIL_PrecacheOther( "ammo_ARgrenades" ); + // rifle + UTIL_PrecacheOtherWeapon( "weapon_rifle" ); + UTIL_PrecacheOther( "ammo_rifle" ); + + // tommy gun + UTIL_PrecacheOtherWeapon( "weapon_tommygun" ); + UTIL_PrecacheOther( "ammo_tommygun" ); + // UTIL_PrecacheOther( "ammo_ARgrenades" ); + + // explosives + UTIL_PrecacheOtherWeapon( "weapon_molotov" ); + UTIL_PrecacheOtherWeapon( "weapon_dynamite" ); + UTIL_PrecacheOtherWeapon( "weapon_powderofibn" ); + + // magic + UTIL_PrecacheOtherWeapon( "weapon_rlyeh_seal" ); + UTIL_PrecacheOtherWeapon( "weapon_eldersign" ); + UTIL_PrecacheOtherWeapon( "weapon_shrivelling" ); + UTIL_PrecacheOtherWeapon( "weapon_dread_name" ); + UTIL_PrecacheOtherWeapon( "weapon_drainlife" ); + UTIL_PrecacheOtherWeapon( "weapon_charm" ); + + // artefacts + UTIL_PrecacheOtherWeapon( "weapon_lightninggun" ); + UTIL_PrecacheOtherWeapon( "weapon_serpentstaff" ); + UTIL_PrecacheOtherWeapon( "weapon_teleport" ); + UTIL_PrecacheOtherWeapon( "weapon_hologram" ); #if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) // python - UTIL_PrecacheOtherWeapon( "weapon_357" ); - UTIL_PrecacheOther( "ammo_357" ); + // UTIL_PrecacheOtherWeapon( "weapon_357" ); + // UTIL_PrecacheOther( "ammo_357" ); // gauss - UTIL_PrecacheOtherWeapon( "weapon_gauss" ); - UTIL_PrecacheOther( "ammo_gaussclip" ); + // UTIL_PrecacheOtherWeapon( "weapon_gauss" ); + // UTIL_PrecacheOther( "ammo_gaussclip" ); // rpg - UTIL_PrecacheOtherWeapon( "weapon_rpg" ); - UTIL_PrecacheOther( "ammo_rpgclip" ); + // UTIL_PrecacheOtherWeapon( "weapon_rpg" ); + // UTIL_PrecacheOther( "ammo_rpgclip" ); // crossbow - UTIL_PrecacheOtherWeapon( "weapon_crossbow" ); - UTIL_PrecacheOther( "ammo_crossbow" ); + // UTIL_PrecacheOtherWeapon( "weapon_crossbow" ); + // UTIL_PrecacheOther( "ammo_crossbow" ); // egon - UTIL_PrecacheOtherWeapon( "weapon_egon" ); + // UTIL_PrecacheOtherWeapon( "weapon_egon" ); #endif // tripmine - UTIL_PrecacheOtherWeapon( "weapon_tripmine" ); + // UTIL_PrecacheOtherWeapon( "weapon_tripmine" ); #if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) // satchel charge - UTIL_PrecacheOtherWeapon( "weapon_satchel" ); + // UTIL_PrecacheOtherWeapon( "weapon_satchel" ); #endif // hand grenade - UTIL_PrecacheOtherWeapon("weapon_handgrenade"); + // UTIL_PrecacheOtherWeapon("weapon_handgrenade"); #if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) // squeak grenade - UTIL_PrecacheOtherWeapon( "weapon_snark" ); + // UTIL_PrecacheOtherWeapon( "weapon_snark" ); // hornetgun - UTIL_PrecacheOtherWeapon( "weapon_hornetgun" ); + // UTIL_PrecacheOtherWeapon( "weapon_hornetgun" ); if( g_pGameRules->IsDeathmatch() ) { @@ -370,7 +392,11 @@ void W_Precache( void ) // used by explosions PRECACHE_MODEL( "models/grenade.mdl" ); + PRECACHE_MODEL( "models/stickygib.mdl" ); PRECACHE_MODEL( "sprites/explode1.spr" ); + PRECACHE_MODEL( "sprites/xffloor.spr" ); + PRECACHE_MODEL( "sprites/fire.spr" ); + PRECACHE_SOUND( "ambience/burning1.wav" ); PRECACHE_SOUND( "weapons/debris1.wav" );// explosion aftermaths PRECACHE_SOUND( "weapons/debris2.wav" );// explosion aftermaths @@ -1198,6 +1224,7 @@ void CBasePlayerWeapon::DrainClip(CBasePlayer* pPlayer, BOOL keep, int i9mm, int int iPAI = PrimaryAmmoIndex(); int iAmt; if (iPAI == -1) return; + /* else if (iPAI == pPlayer->GetAmmoIndex("9mm")) iAmt = i9mm; else if (iPAI == pPlayer->GetAmmoIndex("357")) iAmt = i357; else if (iPAI == pPlayer->GetAmmoIndex("buckshot")) iAmt = iBuck; @@ -1209,6 +1236,7 @@ void CBasePlayerWeapon::DrainClip(CBasePlayer* pPlayer, BOOL keep, int i9mm, int else if (iPAI == pPlayer->GetAmmoIndex("Snarks")) iAmt = iSnark; else if (iPAI == pPlayer->GetAmmoIndex("Trip Mine")) iAmt = iTrip; else if (iPAI == pPlayer->GetAmmoIndex("Hand Grenade")) iAmt = iGren; + */ else return; if (iAmt > 0) @@ -1607,68 +1635,3 @@ void CBasePlayerWeapon::PrintState( void ) ALERT( at_console, "m_iclip: %i\n", m_iClip ); } -TYPEDESCRIPTION CRpg::m_SaveData[] = -{ - DEFINE_FIELD( CRpg, m_fSpotActive, FIELD_INTEGER ), - DEFINE_FIELD( CRpg, m_cActiveRockets, FIELD_INTEGER ), -}; - -IMPLEMENT_SAVERESTORE( CRpg, CBasePlayerWeapon ) - -TYPEDESCRIPTION CRpgRocket::m_SaveData[] = -{ - DEFINE_FIELD( CRpgRocket, m_flIgniteTime, FIELD_TIME ), - DEFINE_FIELD( CRpgRocket, m_hLauncher, FIELD_EHANDLE ), -}; - -IMPLEMENT_SAVERESTORE( CRpgRocket, CGrenade ) - -TYPEDESCRIPTION CShotgun::m_SaveData[] = -{ - DEFINE_FIELD( CShotgun, m_flNextReload, FIELD_TIME ), - DEFINE_FIELD( CShotgun, m_fInSpecialReload, FIELD_INTEGER ), - DEFINE_FIELD( CShotgun, m_flNextReload, FIELD_TIME ), - // DEFINE_FIELD( CShotgun, m_iShell, FIELD_INTEGER ), - DEFINE_FIELD( CShotgun, m_flPumpTime, FIELD_TIME ), -}; - -IMPLEMENT_SAVERESTORE( CShotgun, CBasePlayerWeapon ) - -TYPEDESCRIPTION CGauss::m_SaveData[] = -{ - DEFINE_FIELD( CGauss, m_fInAttack, FIELD_INTEGER ), - //DEFINE_FIELD( CGauss, m_flStartCharge, FIELD_TIME ), - //DEFINE_FIELD( CGauss, m_flPlayAftershock, FIELD_TIME ), - //DEFINE_FIELD( CGauss, m_flNextAmmoBurn, FIELD_TIME ), - DEFINE_FIELD( CGauss, m_fPrimaryFire, FIELD_BOOLEAN ), -}; - -IMPLEMENT_SAVERESTORE( CGauss, CBasePlayerWeapon ) - -TYPEDESCRIPTION CEgon::m_SaveData[] = -{ - //DEFINE_FIELD( CEgon, m_pBeam, FIELD_CLASSPTR ), - //DEFINE_FIELD( CEgon, m_pNoise, FIELD_CLASSPTR ), - //DEFINE_FIELD( CEgon, m_pSprite, FIELD_CLASSPTR ), - DEFINE_FIELD( CEgon, m_shootTime, FIELD_TIME ), - DEFINE_FIELD( CEgon, m_fireState, FIELD_INTEGER ), - DEFINE_FIELD( CEgon, m_fireMode, FIELD_INTEGER ), - DEFINE_FIELD( CEgon, m_shakeTime, FIELD_TIME ), - DEFINE_FIELD( CEgon, m_flAmmoUseTime, FIELD_TIME ), -}; - -IMPLEMENT_SAVERESTORE( CEgon, CBasePlayerWeapon ) - -TYPEDESCRIPTION CHgun::m_SaveData[] = -{ - DEFINE_FIELD( CHgun, m_flRechargeTime, FIELD_TIME ), -}; - -IMPLEMENT_SAVERESTORE( CHgun, CBasePlayerWeapon ) - -TYPEDESCRIPTION CSatchel::m_SaveData[] = -{ - DEFINE_FIELD( CSatchel, m_chargeReady, FIELD_INTEGER ), -}; - -IMPLEMENT_SAVERESTORE( CSatchel, CBasePlayerWeapon ) diff --git a/dlls/weapons.h b/dlls/weapons.h index 2c96551f..27eab9d9 100644 --- a/dlls/weapons.h +++ b/dlls/weapons.h @@ -59,25 +59,44 @@ public: // constant items #define ITEM_HEALTHKIT 1 #define ITEM_ANTIDOTE 2 -#define ITEM_SECURITY 3 -#define ITEM_BATTERY 4 +#define ITEM_SECURITY 3 // aka KEY +// #define ITEM_BATTERY 4 -#define WEAPON_NONE 0 -#define WEAPON_CROWBAR 1 -#define WEAPON_GLOCK 2 -#define WEAPON_PYTHON 3 -#define WEAPON_MP5 4 -#define WEAPON_CHAINGUN 5 -#define WEAPON_CROSSBOW 6 -#define WEAPON_SHOTGUN 7 -#define WEAPON_RPG 8 -#define WEAPON_GAUSS 9 -#define WEAPON_EGON 10 -#define WEAPON_HORNETGUN 11 -#define WEAPON_HANDGRENADE 12 -#define WEAPON_TRIPMINE 13 -#define WEAPON_SATCHEL 14 -#define WEAPON_SNARK 15 +// #define WEAPON_NONE 0 +// #define WEAPON_CROWBAR 1 +// #define WEAPON_GLOCK 2 +// #define WEAPON_PYTHON 3 +// #define WEAPON_MP5 4 +// #define WEAPON_CHAINGUN 5 +// #define WEAPON_CROSSBOW 6 +// #define WEAPON_SHOTGUN 7 +// #define WEAPON_RPG 8 +// #define WEAPON_GAUSS 9 +// #define WEAPON_EGON 10 +// #define WEAPON_HORNETGUN 11 +// #define WEAPON_HANDGRENADE 12 +// #define WEAPON_TRIPMINE 13 +// #define WEAPON_SATCHEL 14 +// #define WEAPON_SNARK 15 +#define WEAPON_KNIFE 1 +#define WEAPON_SWORDCANE 2 +#define WEAPON_REVOLVER 3 +#define WEAPON_SHOTGUN 4 +#define WEAPON_RIFLE 5 +#define WEAPON_TOMMYGUN 6 +#define WEAPON_DYNAMITE 7 +#define WEAPON_MOLOTOV 8 +#define WEAPON_ELDER_SIGN 9 +#define WEAPON_LIGHTNING_GUN 10 +#define WEAPON_SHRIVELLING 11 +#define WEAPON_DREAD_NAME 12 +#define WEAPON_SERPENT_STAFF 13 +#define WEAPON_POWDER_IBN 14 +#define WEAPON_DRAINLIFE 15 +#define WEAPON_RLYEH_SEAL 16 +#define WEAPON_CHARM 17 +#define WEAPON_TELEPORT 18 +#define WEAPON_HOLOGRAM 19 #define WEAPON_ALLWEAPONS (~(1<absmin = pev->origin + Vector(-16, -16, -5); - pev->absmax = pev->origin + Vector(16, 16, 28); - } - - void PrimaryAttack( void ); - BOOL Deploy( void ); - void Holster( int skiplocal = 0 ); - void WeaponIdle( void ); - - virtual BOOL UseDecrement( void ) - { -#if defined( CLIENT_WEAPONS ) - return TRUE; -#else - return FALSE; -#endif - } - -private: - unsigned short m_usTripFire; -}; - -class CSqueak : public CBasePlayerWeapon -{ -public: - void Spawn( void ); - void Precache( void ); - int iItemSlot( void ) { return 5; } - int GetItemInfo(ItemInfo *p); - - void PrimaryAttack( void ); - void SecondaryAttack( void ); - BOOL Deploy( void ); - void Holster( int skiplocal = 0 ); - void WeaponIdle( void ); - int m_fJustThrown; - - virtual BOOL UseDecrement( void ) - { -#if defined( CLIENT_WEAPONS ) - return TRUE; -#else - return FALSE; -#endif - } - -private: - unsigned short m_usSnarkFire; -}; #endif // WEAPONS_H diff --git a/dlls/world.cpp b/dlls/world.cpp index 16c303b0..0f3c530e 100644 --- a/dlls/world.cpp +++ b/dlls/world.cpp @@ -605,7 +605,7 @@ void CWorld::Precache( void ) pEntity->SetThink(&CWorld::SUB_CallUseToggle ); pEntity->pev->message = pev->netname; pev->netname = 0; - pEntity->SetNextThink( 0.3 ); + pEntity->SetNextThink( 1.0f ); pEntity->pev->spawnflags = SF_MESSAGE_ONCE; } } diff --git a/dlls/zombie.cpp b/dlls/zombie.cpp index 3dd90e5e..033329a8 100644 --- a/dlls/zombie.cpp +++ b/dlls/zombie.cpp @@ -134,7 +134,7 @@ void CZombie::SetYawSpeed( void ) int CZombie::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) { // Take 30% damage from bullets - if( bitsDamageType == DMG_BULLET ) + if( bitsDamageType & DMG_BULLET ) { Vector vecDir = pev->origin - (pevInflictor->absmin + pevInflictor->absmax) * 0.5; vecDir = vecDir.Normalize(); @@ -153,7 +153,7 @@ void CZombie::PainSound( void ) { int pitch = 95 + RANDOM_LONG( 0, 9 ); - if( RANDOM_LONG( 0, 5 ) < 2 ) + if( RANDOM_LONG( 0, 5 ) < 3 ) EMIT_SOUND_DYN( ENT( pev ), CHAN_VOICE, pPainSounds[RANDOM_LONG( 0, ARRAYSIZE( pPainSounds ) - 1 )], 1.0, ATTN_NORM, 0, pitch ); } @@ -269,12 +269,25 @@ void CZombie::Spawn() if (pev->model) SET_MODEL(ENT(pev), STRING(pev->model)); //LRC else - SET_MODEL( ENT( pev ), "models/zombie.mdl" ); + SET_MODEL( ENT( pev ), "models/monsters/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; + + if( pev->body == 1 ) // skeleton + { + m_bloodColor = DONT_BLEED; + } + else if( pev->body == 0 ) // corpse + { + m_bloodColor = BLOOD_COLOR_RED; + } + else + { + m_bloodColor = BLOOD_COLOR_GREEN; + } + if (pev->health == 0) pev->health = gSkillData.zombieHealth; pev->view_ofs = VEC_VIEW;// position of the eyes relative to monster's origin. @@ -295,7 +308,7 @@ void CZombie::Precache() if (pev->model) PRECACHE_MODEL(STRING(pev->model)); //LRC else - PRECACHE_MODEL( "models/zombie.mdl" ); + PRECACHE_MODEL( "models/monsters/zombie.mdl" ); for( i = 0; i < ARRAYSIZE( pAttackHitSounds ); i++ ) PRECACHE_SOUND( pAttackHitSounds[i] ); @@ -324,7 +337,7 @@ int CZombie::IgnoreConditions( void ) { int iIgnore = CBaseMonster::IgnoreConditions(); - if( ( m_Activity == ACT_MELEE_ATTACK1 ) || ( m_Activity == ACT_MELEE_ATTACK1 ) ) + if( ( m_Activity == ACT_MELEE_ATTACK1 ) || ( m_Activity == ACT_MELEE_ATTACK2 ) ) { #if 0 if( pev->health < 20 ) @@ -343,3 +356,76 @@ int CZombie::IgnoreConditions( void ) return iIgnore; } + +/////////////////////////////////////////////////////////////////////////////////////////////////// +class CDeadZombie : public CBaseMonster +{ +public: + void Spawn( void ); + int Classify( void ) { return CLASS_ALIEN_MONSTER; } + + void KeyValue( KeyValueData *pkvd ); + + int m_iPose;// which sequence to display -- temporary, don't need to save + static const char *m_szPoses[8]; +}; + +const char *CDeadZombie::m_szPoses[] = { "hanging1", "hanging2", "hanging3", "hanging4", "hanging5", "dead1", "dead2", "dead3" }; + +void CDeadZombie::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_zombie_dead, CDeadZombie ) + +//========================================================= +// ********** DeadZombie SPAWN ********** +//========================================================= +void CDeadZombie::Spawn( void ) +{ + if( pev->model ) + PRECACHE_MODEL( STRING( pev->model ) ); //LRC + else + PRECACHE_MODEL( "models/monsters/zombiedead.mdl" ); + + if( pev->model ) + SET_MODEL( ENT( pev ), STRING( pev->model ) ); //LRC + else + SET_MODEL( ENT( pev ), "models/monsters/zombiedead.mdl" ); + + pev->effects = 0; + pev->yaw_speed = 8; + pev->sequence = 0; + + if( pev->body == 1 ) // skeleton + { + m_bloodColor = DONT_BLEED; + } + else if( pev->body == 0 ) // corpse + { + m_bloodColor = BLOOD_COLOR_RED; + } + else + { + m_bloodColor = BLOOD_COLOR_GREEN; + } + + pev->sequence = LookupSequence( m_szPoses[m_iPose] ); + + if( pev->sequence == -1 ) + { + ALERT( at_console, "Dead zombie with bad pose\n" ); + } + + // Corpses have less health + pev->health = 8; + + MonsterInitDead(); +} diff --git a/engine/progdefs.h b/engine/progdefs.h index 31cfb3cb..b4ad4228 100644 --- a/engine/progdefs.h +++ b/engine/progdefs.h @@ -149,7 +149,8 @@ typedef struct entvars_s float max_health; float teleport_time; float armortype; - float armorvalue; + //float armorvalue; + float sanity; int waterlevel; int watertype; diff --git a/pm_shared/pm_shared.c b/pm_shared/pm_shared.c index d7b9f09c..00996f24 100644 --- a/pm_shared/pm_shared.c +++ b/pm_shared/pm_shared.c @@ -2775,7 +2775,8 @@ void PM_CheckFalling( void ) //pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); //break; //case 1: - pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + // pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + pmove->PM_PlaySound( CHAN_VOICE, "player/rs_pain2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); // break; //} fvol = 1.0; @@ -2787,7 +2788,8 @@ void PM_CheckFalling( void ) if( tfc ) { - pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + // pmove->PM_PlaySound( CHAN_VOICE, "player/pl_fallpain3.wav", 1, ATTN_NORM, 0, PITCH_NORM ); + pmove->PM_PlaySound( CHAN_VOICE, "player/rs_pain2.wav", 1, ATTN_NORM, 0, PITCH_NORM ); } fvol = 0.85;