diff --git a/cl_dll/aliencrosshair.cpp b/cl_dll/aliencrosshair.cpp index f2796644..69ab54e9 100644 --- a/cl_dll/aliencrosshair.cpp +++ b/cl_dll/aliencrosshair.cpp @@ -1,141 +1,141 @@ -/*** -* -* (C) 2008 Vyacheslav Dzhura -* -****/ -// -// mode.cpp -// -// implementation of CHudModeIcon class -// - -#include "hud.h" -#include "cl_util.h" -#include "parsemsg.h" - -#include -#include - -DECLARE_MESSAGE(m_AlienCrosshair, AlienState) - -// gHUD.m_AlienCrosshair.m_iState - -/* -class CHudAlienCrosshair: public CHudBase -{ -public: - int Init( void ); - int VidInit( void ); - int Draw(float flTime); - void Reset( void ); - int MsgFunc_AlienState(const char *pszName, int iSize, void *pbuf ); - -private: - HSPRITE m_hCrosshair[4]; - wrect_t *m_prcCrosshair[4]; - int m_iState; -}; -*/ - -int CHudAlienCrosshair::Init(void) -{ - m_iState = 0; - - HOOK_MESSAGE(AlienState); - - m_iFlags |= HUD_ACTIVE; - m_iFlags |= HUD_ALIEN; - - gHUD.AddHudElem(this); - - return 1; -}; - -void CHudAlienCrosshair::Reset(void) -{ - m_iState = 0; -} - -int CHudAlienCrosshair::VidInit(void) -{ - int sCenter = gHUD.GetSpriteIndex( "islave_center" ); - int sCharged = gHUD.GetSpriteIndex( "islave_charged" ); - int sInner = gHUD.GetSpriteIndex( "islave_inner" ); - int sOuter = gHUD.GetSpriteIndex( "islave_outer" ); - - if ( (sCenter == -1) || (sCharged == -1) || (sInner == -1) || (sOuter == -1) ) - return 0; - - m_hCrosshair[0] = gHUD.GetSprite(sOuter); - m_hCrosshair[1] = gHUD.GetSprite(sInner); - m_hCrosshair[2] = gHUD.GetSprite(sCenter); - m_hCrosshair[3] = gHUD.GetSprite(sCharged); - - m_prcCrosshair[0] = &gHUD.GetSpriteRect( sOuter ); - m_prcCrosshair[1] = &gHUD.GetSpriteRect( sInner ); - m_prcCrosshair[2] = &gHUD.GetSpriteRect( sCenter ); - m_prcCrosshair[3] = &gHUD.GetSpriteRect( sCharged ); - - return 1; -}; - -int CHudAlienCrosshair:: MsgFunc_AlienState(const char *pszName, int iSize, void *pbuf ) -{ - BEGIN_READ( pbuf, iSize ); - m_iState = READ_BYTE(); - - return 1; -} - -int CHudAlienCrosshair::Draw(float flTime) -{ - if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_ALL ) ) - return 1; - - if ( !gHUD.m_bAlienMode ) - return 1; - - if (m_iState > 3) - m_iState = 3; - - int chR, chG, chB; - chR = chG = chB = 0; - - for (int i = 0; i < 4; i++ ) - { - if ( i > m_iState ) - break; - - m_hActiveSprite = m_hCrosshair[i]; - m_prcActiveRect = m_prcCrosshair[i]; - - if ( i < 3 ) - { // 180, 255, 96 - chR = 180; - chG = 255; - chB = 96; - } else - { - chR = 255; - chG = 0; - chB = 0; - } - - if (( i == 2 ) && ( m_iState == 3 )) - continue; - - int x,y, SWidth, SHeight; - SWidth = m_prcActiveRect->right - m_prcActiveRect->left; - SHeight = m_prcActiveRect->bottom - m_prcActiveRect->top; // SPR_Height(m_hActiveSprite,0); - x = ScreenWidth / 2 - ( SWidth / 2 ); - y = ScreenHeight / 2 - ( SHeight / 2 ); - - SPR_Set(m_hActiveSprite, chR, chG, chB ); - SPR_DrawAdditive(0, x, y, m_prcActiveRect); - } - - //char szMes[20]; - //sprintf(szMes,"%d %d/%d", m_fMode, SWidth, SHeight); - //gHUD.DrawHudString( 5, 5, ScreenWidth, szMes, r, g, b); - return 1; +/*** +* +* (C) 2008 Vyacheslav Dzhura +* +****/ +// +// mode.cpp +// +// implementation of CHudModeIcon class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE(m_AlienCrosshair, AlienState) + +// gHUD.m_AlienCrosshair.m_iState + +/* +class CHudAlienCrosshair: public CHudBase +{ +public: + int Init( void ); + int VidInit( void ); + int Draw(float flTime); + void Reset( void ); + int MsgFunc_AlienState(const char *pszName, int iSize, void *pbuf ); + +private: + HSPRITE m_hCrosshair[4]; + wrect_t *m_prcCrosshair[4]; + int m_iState; +}; +*/ + +int CHudAlienCrosshair::Init(void) +{ + m_iState = 0; + + HOOK_MESSAGE(AlienState); + + m_iFlags |= HUD_ACTIVE; + m_iFlags |= HUD_ALIEN; + + gHUD.AddHudElem(this); + + return 1; +}; + +void CHudAlienCrosshair::Reset(void) +{ + m_iState = 0; +} + +int CHudAlienCrosshair::VidInit(void) +{ + int sCenter = gHUD.GetSpriteIndex( "islave_center" ); + int sCharged = gHUD.GetSpriteIndex( "islave_charged" ); + int sInner = gHUD.GetSpriteIndex( "islave_inner" ); + int sOuter = gHUD.GetSpriteIndex( "islave_outer" ); + + if ( (sCenter == -1) || (sCharged == -1) || (sInner == -1) || (sOuter == -1) ) + return 0; + + m_hCrosshair[0] = gHUD.GetSprite(sOuter); + m_hCrosshair[1] = gHUD.GetSprite(sInner); + m_hCrosshair[2] = gHUD.GetSprite(sCenter); + m_hCrosshair[3] = gHUD.GetSprite(sCharged); + + m_prcCrosshair[0] = &gHUD.GetSpriteRect( sOuter ); + m_prcCrosshair[1] = &gHUD.GetSpriteRect( sInner ); + m_prcCrosshair[2] = &gHUD.GetSpriteRect( sCenter ); + m_prcCrosshair[3] = &gHUD.GetSpriteRect( sCharged ); + + return 1; +}; + +int CHudAlienCrosshair:: MsgFunc_AlienState(const char *pszName, int iSize, void *pbuf ) +{ + BEGIN_READ( pbuf, iSize ); + m_iState = READ_BYTE(); + + return 1; +} + +int CHudAlienCrosshair::Draw(float flTime) +{ + if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_ALL ) ) + return 1; + + if ( !gHUD.m_bAlienMode ) + return 1; + + if (m_iState > 3) + m_iState = 3; + + int chR, chG, chB; + chR = chG = chB = 0; + + for (int i = 0; i < 4; i++ ) + { + if ( i > m_iState ) + break; + + m_hActiveSprite = m_hCrosshair[i]; + m_prcActiveRect = m_prcCrosshair[i]; + + if ( i < 3 ) + { // 180, 255, 96 + chR = 180; + chG = 255; + chB = 96; + } else + { + chR = 255; + chG = 0; + chB = 0; + } + + if (( i == 2 ) && ( m_iState == 3 )) + continue; + + int x,y, SWidth, SHeight; + SWidth = m_prcActiveRect->right - m_prcActiveRect->left; + SHeight = m_prcActiveRect->bottom - m_prcActiveRect->top; // SPR_Height(m_hActiveSprite,0); + x = ScreenWidth / 2 - ( SWidth / 2 ); + y = ScreenHeight / 2 - ( SHeight / 2 ); + + SPR_Set(m_hActiveSprite, chR, chG, chB ); + SPR_DrawAdditive(0, x, y, m_prcActiveRect); + } + + //char szMes[20]; + //sprintf(szMes,"%d %d/%d", m_fMode, SWidth, SHeight); + //gHUD.DrawHudString( 5, 5, ScreenWidth, szMes, r, g, b); + return 1; } \ No newline at end of file diff --git a/cl_dll/mode.cpp b/cl_dll/mode.cpp index 8d273576..f7753f48 100644 --- a/cl_dll/mode.cpp +++ b/cl_dll/mode.cpp @@ -1,117 +1,117 @@ -/*** -* -* Copyright (c) 1996-2002, Valve LLC. All rights reserved. -* -* This product contains software technology licensed from Id -* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. -* All Rights Reserved. -* -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. -* -****/ -// -// mode.cpp -// -// implementation of CHudModeIcon class -// - -#include "hud.h" -#include "cl_util.h" -#include "parsemsg.h" - -#include -#include - -DECLARE_MESSAGE(m_ModeIcon, ChangeMode) - -int CHudModeIcon::Init(void) -{ - m_fMode = 0; - - HOOK_MESSAGE(ChangeMode); - - m_iFlags |= HUD_ACTIVE; - - gHUD.AddHudElem(this); - - return 1; -}; - -void CHudModeIcon::Reset(void) -{ - m_fMode = 0; -} - -int CHudModeIcon::VidInit(void) -{ - int HUD_mode_stand = gHUD.GetSpriteIndex( "mode_stand" ); - int HUD_mode_run = gHUD.GetSpriteIndex( "mode_run" ); - int HUD_mode_crouch = gHUD.GetSpriteIndex( "mode_crouch" ); - int HUD_mode_jump = gHUD.GetSpriteIndex( "mode_jump" ); - - m_hSpriteStand = m_hSpriteRun = m_hSpriteCrouch = m_hSpriteJump = 0; - - m_hSpriteStand = gHUD.GetSprite(HUD_mode_stand); - m_hSpriteRun = gHUD.GetSprite(HUD_mode_run); - m_hSpriteCrouch = gHUD.GetSprite(HUD_mode_crouch); - m_hSpriteJump = gHUD.GetSprite(HUD_mode_jump); - - m_prcStand = &gHUD.GetSpriteRect( HUD_mode_stand ); - m_prcRun = &gHUD.GetSpriteRect( HUD_mode_run ); - m_prcCrouch = &gHUD.GetSpriteRect( HUD_mode_crouch ); - m_prcJump = &gHUD.GetSpriteRect( HUD_mode_jump ); - - return 1; -}; - -int CHudModeIcon:: MsgFunc_ChangeMode(const char *pszName, int iSize, void *pbuf ) -{ - - BEGIN_READ( pbuf, iSize ); - m_fMode = READ_BYTE(); - - return 1; -} - -int CHudModeIcon::Draw(float flTime) -{ - if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_ALL ) ) - return 1; - - if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) - return 1; - - switch(m_fMode){ - case 0: m_hActiveSprite = m_hSpriteStand; - m_prcActiveRect = m_prcStand; - break; - case 1: m_hActiveSprite = m_hSpriteRun; - m_prcActiveRect = m_prcRun; - break; - case 2: m_hActiveSprite = m_hSpriteCrouch; - m_prcActiveRect = m_prcCrouch; - break; - case 3: m_hActiveSprite = m_hSpriteJump; - m_prcActiveRect = m_prcJump; - break; - } - - int r,g,b, x,y, SWidth, SHeight; - - SWidth = m_prcActiveRect->right - m_prcActiveRect->left;// SPR_Width(m_hActiveSprite,0); - SHeight = SPR_Height(m_hActiveSprite,0); - x = ScreenWidth - SWidth - SWidth/2; - y = 64; - - UnpackRGB(r,g,b, gHUD.uColor); - SPR_Set(m_hActiveSprite, r, g, b ); - SPR_DrawAdditive(0, x, y, m_prcActiveRect); - - //char szMes[20]; - //sprintf(szMes,"%d %d/%d", m_fMode, SWidth, SHeight); - //gHUD.DrawHudString( 5, 5, ScreenWidth, szMes, r, g, b); - return 1; +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// mode.cpp +// +// implementation of CHudModeIcon class +// + +#include "hud.h" +#include "cl_util.h" +#include "parsemsg.h" + +#include +#include + +DECLARE_MESSAGE(m_ModeIcon, ChangeMode) + +int CHudModeIcon::Init(void) +{ + m_fMode = 0; + + HOOK_MESSAGE(ChangeMode); + + m_iFlags |= HUD_ACTIVE; + + gHUD.AddHudElem(this); + + return 1; +}; + +void CHudModeIcon::Reset(void) +{ + m_fMode = 0; +} + +int CHudModeIcon::VidInit(void) +{ + int HUD_mode_stand = gHUD.GetSpriteIndex( "mode_stand" ); + int HUD_mode_run = gHUD.GetSpriteIndex( "mode_run" ); + int HUD_mode_crouch = gHUD.GetSpriteIndex( "mode_crouch" ); + int HUD_mode_jump = gHUD.GetSpriteIndex( "mode_jump" ); + + m_hSpriteStand = m_hSpriteRun = m_hSpriteCrouch = m_hSpriteJump = 0; + + m_hSpriteStand = gHUD.GetSprite(HUD_mode_stand); + m_hSpriteRun = gHUD.GetSprite(HUD_mode_run); + m_hSpriteCrouch = gHUD.GetSprite(HUD_mode_crouch); + m_hSpriteJump = gHUD.GetSprite(HUD_mode_jump); + + m_prcStand = &gHUD.GetSpriteRect( HUD_mode_stand ); + m_prcRun = &gHUD.GetSpriteRect( HUD_mode_run ); + m_prcCrouch = &gHUD.GetSpriteRect( HUD_mode_crouch ); + m_prcJump = &gHUD.GetSpriteRect( HUD_mode_jump ); + + return 1; +}; + +int CHudModeIcon:: MsgFunc_ChangeMode(const char *pszName, int iSize, void *pbuf ) +{ + + BEGIN_READ( pbuf, iSize ); + m_fMode = READ_BYTE(); + + return 1; +} + +int CHudModeIcon::Draw(float flTime) +{ + if ( gHUD.m_iHideHUDDisplay & ( HIDEHUD_ALL ) ) + return 1; + + if (!(gHUD.m_iWeaponBits & (1<<(WEAPON_SUIT)) )) + return 1; + + switch(m_fMode){ + case 0: m_hActiveSprite = m_hSpriteStand; + m_prcActiveRect = m_prcStand; + break; + case 1: m_hActiveSprite = m_hSpriteRun; + m_prcActiveRect = m_prcRun; + break; + case 2: m_hActiveSprite = m_hSpriteCrouch; + m_prcActiveRect = m_prcCrouch; + break; + case 3: m_hActiveSprite = m_hSpriteJump; + m_prcActiveRect = m_prcJump; + break; + } + + int r,g,b, x,y, SWidth, SHeight; + + SWidth = m_prcActiveRect->right - m_prcActiveRect->left;// SPR_Width(m_hActiveSprite,0); + SHeight = SPR_Height(m_hActiveSprite,0); + x = ScreenWidth - SWidth - SWidth/2; + y = 64; + + UnpackRGB(r,g,b, gHUD.uColor); + SPR_Set(m_hActiveSprite, r, g, b ); + SPR_DrawAdditive(0, x, y, m_prcActiveRect); + + //char szMes[20]; + //sprintf(szMes,"%d %d/%d", m_fMode, SWidth, SHeight); + //gHUD.DrawHudString( 5, 5, ScreenWidth, szMes, r, g, b); + return 1; } \ No newline at end of file diff --git a/cl_dll/vgui_DecaySparePlayer.cpp b/cl_dll/vgui_DecaySparePlayer.cpp index 7f8e5958..d964e832 100644 --- a/cl_dll/vgui_DecaySparePlayer.cpp +++ b/cl_dll/vgui_DecaySparePlayer.cpp @@ -1,144 +1,144 @@ -//=========== (C) Copyright 2008 Vyacheslav Dzhura. All rights reserved. =========== -// -// Purpose: Notification which is displayed for players which have connected -// to Decay game server, after there are two human players, also -// displays count down before disconnecting player -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//============================================================================= - -#include "vgui_int.h" -#include "VGUI_Font.h" -#include "VGUI_ScrollPanel.h" -#include "VGUI_TextImage.h" -#include "VGUI_loadtga.h" - -#include "hud.h" -#include "cl_util.h" -#include "vgui_TeamFortressViewport.h" - -// Windows' Dimensions -#define SNW_TITLE_X XRES(40) -#define SNW_TITLE_Y YRES(32) -#define SNW_TOPLEFT_BUTTON_X XRES(251)//was 245 -#define SNW_TOPLEFT_BUTTON_Y YRES(400) -#define SNW_BUTTON_SIZE_X XRES(100) -#define SNW_BUTTON_SIZE_Y YRES(24) -#define SNW_BUTTON_SPACER_Y YRES(8) -#define SNW_WINDOW_X XRES(150) -#define SNW_WINDOW_Y YRES(150) -#define SNW_WINDOW_SIZE_X XRES(350) -#define SNW_WINDOW_SIZE_Y YRES(220) -#define SNW_WINDOW_TITLE_X XRES(16) -#define SNW_WINDOW_TITLE_Y YRES(16) -#define SNW_WINDOW_TEXT_X XRES(80) // was 16 -#define SNW_WINDOW_TEXT_Y YRES(32) -#define SNW_WINDOW_TEXT_SIZE_Y YRES(168) -#define SNW_WINDOW_INFO_X XRES(16) -#define SNW_WINDOW_INFO_Y YRES(234) - -// Creation -CSparePlayerWindow::CSparePlayerWindow(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall) -{ - // Get the scheme used for the Titles - CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); - - // schemes - SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); - SchemeHandle_t hDecayFont = pSchemes->getSchemeHandle( "Briefing Text" ); //Decay - - // get the Font used for the Titles - Font *pTitleFont = pSchemes->getFont( hTitleScheme ); - int r, g, b, a; - - // Create the Info Window itself - m_pWindow = new CTransparentPanel( 255, SNW_WINDOW_X, SNW_WINDOW_Y, SNW_WINDOW_SIZE_X, SNW_WINDOW_SIZE_Y ); - m_pWindow->setParent( this ); - m_pWindow->setBorder( new LineBorder( Color(255*0.7,170*0.7,0,0 )) ); - - // Create the Title label - m_pTitle = new Label( "", SNW_WINDOW_TEXT_X, YRES(5) ); - m_pTitle->setParent( m_pWindow ); - m_pTitle->setFont( pTitleFont ); - pSchemes->getFgColor( hTitleScheme, r, g, b, a ); - m_pTitle->setFgColor( r, g, b, a ); - pSchemes->getBgColor( hTitleScheme, r, g, b, a ); - m_pTitle->setBgColor( r, g, b, a ); - m_pTitle->setContentAlignment( vgui::Label::a_west ); - m_pTitle->setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Decay_SparePlayerTitle")); - - - // Create the Briefing panel - m_pMemo = new TextPanel("", SNW_WINDOW_TEXT_X, SNW_WINDOW_TEXT_Y, YRES(230), SNW_WINDOW_TEXT_SIZE_Y ); - m_pMemo->setParent( m_pWindow ); - m_pMemo->setFont( pSchemes->getFont(hDecayFont) ); - pSchemes->getFgColor( hDecayFont, r, g, b, a ); - m_pMemo->setFgColor( r, g, b, a ); - pSchemes->getBgColor( hDecayFont, r, g, b, a ); - m_pMemo->setBgColor( r, g, b, a ); - m_pMemo->setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Decay_SparePlayerMessage")); - - // Create the Cancel button - //m_pCancelButton = new CommandButton( "", SNW_TOPLEFT_BUTTON_X, SNW_TOPLEFT_BUTTON_Y, SNW_BUTTON_SIZE_X, SNW_BUTTON_SIZE_Y, true); - //m_pCancelButton->setParent( this ); - //m_pCancelButton->setText( gHUD.m_TextMessage.BufferedLocaliseTextString(" CLOSE") ); - //m_pCancelButton->setVisible( true ); - //m_pCancelButton->addActionSignal(new CMenuHandler_TextWindow(HIDE_TEXTWINDOW)); - - m_pImage = new CImageLabel( "gina", 0, 0, 128, 256 ); // gfx/vgui/640_gina.tga - //m_pImage->setText(25, "this is a test!!!"); - m_pImage->setParent( m_pWindow ); - m_pImage->setVisible( true ); - - Initialize(); -} - -//----------------------------------------------------------------------------- -// Purpose: Called each time a new level is started. -//----------------------------------------------------------------------------- -void CSparePlayerWindow::Initialize( void ) -{ - //m_pScrollPanel->setScrollValue( 0, 0 ); -} - -//----------------------------------------------------------------------------- -// Purpose: Called everytime the Team Menu is displayed -//----------------------------------------------------------------------------- -void CSparePlayerWindow::Update( void ) -{ - // TODO: maybe update "Disconnect in ..." label here? - - //m_pMemo->setText(gHUD.m_TextMessage.BufferedLocaliseTextString(szText)); - //m_pTitle->setText(gHUD.m_TextMessage.BufferedLocaliseTextString(szTitle)); - - int iYPos = SNW_TOPLEFT_BUTTON_Y; - - // Move the AutoAssign button into place - //m_pCancelButton->setPos( SNW_TOPLEFT_BUTTON_X, iYPos ); - - //m_pScrollPanel->validate(); - m_pImage->setBounds( XRES(8), YRES(16), 128, 256 ); -} - -//===================================== -// Key inputs -bool CSparePlayerWindow::SlotInput( int iSlot ) -{ - if ( iSlot == 1) - { - //m_pCancelButton->fireActionSignal(); - return true; - } - return false; -} - -//====================================== -// Update the Team menu before opening it -void CSparePlayerWindow::Open( void ) -{ - Update(); - CMenuPanel::Open(); -} +//=========== (C) Copyright 2008 Vyacheslav Dzhura. All rights reserved. =========== +// +// Purpose: Notification which is displayed for players which have connected +// to Decay game server, after there are two human players, also +// displays count down before disconnecting player +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "vgui_int.h" +#include "VGUI_Font.h" +#include "VGUI_ScrollPanel.h" +#include "VGUI_TextImage.h" +#include "VGUI_loadtga.h" + +#include "hud.h" +#include "cl_util.h" +#include "vgui_TeamFortressViewport.h" + +// Windows' Dimensions +#define SNW_TITLE_X XRES(40) +#define SNW_TITLE_Y YRES(32) +#define SNW_TOPLEFT_BUTTON_X XRES(251)//was 245 +#define SNW_TOPLEFT_BUTTON_Y YRES(400) +#define SNW_BUTTON_SIZE_X XRES(100) +#define SNW_BUTTON_SIZE_Y YRES(24) +#define SNW_BUTTON_SPACER_Y YRES(8) +#define SNW_WINDOW_X XRES(150) +#define SNW_WINDOW_Y YRES(150) +#define SNW_WINDOW_SIZE_X XRES(350) +#define SNW_WINDOW_SIZE_Y YRES(220) +#define SNW_WINDOW_TITLE_X XRES(16) +#define SNW_WINDOW_TITLE_Y YRES(16) +#define SNW_WINDOW_TEXT_X XRES(80) // was 16 +#define SNW_WINDOW_TEXT_Y YRES(32) +#define SNW_WINDOW_TEXT_SIZE_Y YRES(168) +#define SNW_WINDOW_INFO_X XRES(16) +#define SNW_WINDOW_INFO_Y YRES(234) + +// Creation +CSparePlayerWindow::CSparePlayerWindow(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall) +{ + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hDecayFont = pSchemes->getSchemeHandle( "Briefing Text" ); //Decay + + // get the Font used for the Titles + Font *pTitleFont = pSchemes->getFont( hTitleScheme ); + int r, g, b, a; + + // Create the Info Window itself + m_pWindow = new CTransparentPanel( 255, SNW_WINDOW_X, SNW_WINDOW_Y, SNW_WINDOW_SIZE_X, SNW_WINDOW_SIZE_Y ); + m_pWindow->setParent( this ); + m_pWindow->setBorder( new LineBorder( Color(255*0.7,170*0.7,0,0 )) ); + + // Create the Title label + m_pTitle = new Label( "", SNW_WINDOW_TEXT_X, YRES(5) ); + m_pTitle->setParent( m_pWindow ); + m_pTitle->setFont( pTitleFont ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + m_pTitle->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + m_pTitle->setBgColor( r, g, b, a ); + m_pTitle->setContentAlignment( vgui::Label::a_west ); + m_pTitle->setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Decay_SparePlayerTitle")); + + + // Create the Briefing panel + m_pMemo = new TextPanel("", SNW_WINDOW_TEXT_X, SNW_WINDOW_TEXT_Y, YRES(230), SNW_WINDOW_TEXT_SIZE_Y ); + m_pMemo->setParent( m_pWindow ); + m_pMemo->setFont( pSchemes->getFont(hDecayFont) ); + pSchemes->getFgColor( hDecayFont, r, g, b, a ); + m_pMemo->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hDecayFont, r, g, b, a ); + m_pMemo->setBgColor( r, g, b, a ); + m_pMemo->setText(gHUD.m_TextMessage.BufferedLocaliseTextString("#Decay_SparePlayerMessage")); + + // Create the Cancel button + //m_pCancelButton = new CommandButton( "", SNW_TOPLEFT_BUTTON_X, SNW_TOPLEFT_BUTTON_Y, SNW_BUTTON_SIZE_X, SNW_BUTTON_SIZE_Y, true); + //m_pCancelButton->setParent( this ); + //m_pCancelButton->setText( gHUD.m_TextMessage.BufferedLocaliseTextString(" CLOSE") ); + //m_pCancelButton->setVisible( true ); + //m_pCancelButton->addActionSignal(new CMenuHandler_TextWindow(HIDE_TEXTWINDOW)); + + m_pImage = new CImageLabel( "gina", 0, 0, 128, 256 ); // gfx/vgui/640_gina.tga + //m_pImage->setText(25, "this is a test!!!"); + m_pImage->setParent( m_pWindow ); + m_pImage->setVisible( true ); + + Initialize(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void CSparePlayerWindow::Initialize( void ) +{ + //m_pScrollPanel->setScrollValue( 0, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime the Team Menu is displayed +//----------------------------------------------------------------------------- +void CSparePlayerWindow::Update( void ) +{ + // TODO: maybe update "Disconnect in ..." label here? + + //m_pMemo->setText(gHUD.m_TextMessage.BufferedLocaliseTextString(szText)); + //m_pTitle->setText(gHUD.m_TextMessage.BufferedLocaliseTextString(szTitle)); + + int iYPos = SNW_TOPLEFT_BUTTON_Y; + + // Move the AutoAssign button into place + //m_pCancelButton->setPos( SNW_TOPLEFT_BUTTON_X, iYPos ); + + //m_pScrollPanel->validate(); + m_pImage->setBounds( XRES(8), YRES(16), 128, 256 ); +} + +//===================================== +// Key inputs +bool CSparePlayerWindow::SlotInput( int iSlot ) +{ + if ( iSlot == 1) + { + //m_pCancelButton->fireActionSignal(); + return true; + } + return false; +} + +//====================================== +// Update the Team menu before opening it +void CSparePlayerWindow::Open( void ) +{ + Update(); + CMenuPanel::Open(); +} diff --git a/cl_dll/vgui_notepad.cpp b/cl_dll/vgui_notepad.cpp index 629d5a9e..a522e7ee 100644 --- a/cl_dll/vgui_notepad.cpp +++ b/cl_dll/vgui_notepad.cpp @@ -1,154 +1,154 @@ -//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== -// -// The copyright to the contents herein is the property of Valve, L.L.C. -// The contents may be used and/or copied only with the written permission of -// Valve, L.L.C., or in accordance with the terms and conditions stipulated in -// the agreement/contract under which the contents have been supplied. -// -// Purpose: Notepad -// -// $Workfile: $ -// $Date: $ -// -//----------------------------------------------------------------------------- -// $Log: $ -// -// $NoKeywords: $ -//============================================================================= - -#include "vgui_int.h" -#include "VGUI_Font.h" -#include "VGUI_ScrollPanel.h" -#include "VGUI_TextImage.h" -#include "VGUI_loadtga.h" - -#include "hud.h" -#include "cl_util.h" -#include "vgui_TeamFortressViewport.h" - -// Notepad Dimensions -#define NOTEPAD_TITLE_X XRES(40) -#define NOTEPAD_TITLE_Y YRES(32) -#define NOTEPAD_TOPLEFT_BUTTON_X XRES(251)//was 245 -#define NOTEPAD_TOPLEFT_BUTTON_Y YRES(400) -#define NOTEPAD_BUTTON_SIZE_X XRES(100) -#define NOTEPAD_BUTTON_SIZE_Y YRES(24) -#define NOTEPAD_BUTTON_SPACER_Y YRES(8) -#define NOTEPAD_WINDOW_X XRES(150) -#define NOTEPAD_WINDOW_Y YRES(150) -#define NOTEPAD_WINDOW_SIZE_X XRES(300) -#define NOTEPAD_WINDOW_SIZE_Y YRES(220) -#define NOTEPAD_WINDOW_TITLE_X XRES(16) -#define NOTEPAD_WINDOW_TITLE_Y YRES(16) -#define NOTEPAD_WINDOW_TEXT_X XRES(16) -#define NOTEPAD_WINDOW_TEXT_Y YRES(32) -#define NOTEPAD_WINDOW_TEXT_SIZE_Y YRES(168) -#define NOTEPAD_WINDOW_INFO_X XRES(16) -#define NOTEPAD_WINDOW_INFO_Y YRES(234) - -// Creation -CNotepad::CNotepad(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall) -{ - // Get the scheme used for the Titles - CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); - - // schemes - SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); - SchemeHandle_t hDecayFont = pSchemes->getSchemeHandle( "Briefing Text" ); //Decay - - // get the Font used for the Titles - Font *pTitleFont = pSchemes->getFont( hTitleScheme ); - int r, g, b, a; - - // Create the title - m_pTitle = new Label( "", NOTEPAD_WINDOW_X+10, NOTEPAD_WINDOW_Y+5 ); - m_pTitle->setParent( this ); - m_pTitle->setFont( pTitleFont ); - pSchemes->getFgColor( hTitleScheme, r, g, b, a ); - m_pTitle->setFgColor( r, g, b, a ); - pSchemes->getBgColor( hTitleScheme, r, g, b, a ); - m_pTitle->setBgColor( r, g, b, a ); - m_pTitle->setContentAlignment( vgui::Label::a_west ); - m_pTitle->setText(gHUD.m_TextMessage.BufferedLocaliseTextString(szTitle)); - - // Create the Info Window - m_pNotepadWindow = new CTransparentPanel( 255, NOTEPAD_WINDOW_X, NOTEPAD_WINDOW_Y, NOTEPAD_WINDOW_SIZE_X, NOTEPAD_WINDOW_SIZE_Y ); - m_pNotepadWindow->setParent( this ); - m_pNotepadWindow->setBorder( new LineBorder( Color(255*0.7,170*0.7,0,0 )) ); - - // Create the Scroll panel - m_pScrollPanel = new CTFScrollPanel( NOTEPAD_WINDOW_TEXT_X, NOTEPAD_WINDOW_TEXT_Y, NOTEPAD_WINDOW_SIZE_X - (NOTEPAD_WINDOW_TEXT_X * 2), NOTEPAD_WINDOW_TEXT_SIZE_Y ); - m_pScrollPanel->setParent(m_pNotepadWindow); - m_pScrollPanel->setScrollBarVisible(false, false); - - // Create the Map Briefing panel - m_pBriefing = new TextPanel("", 0,0, NOTEPAD_WINDOW_SIZE_X - NOTEPAD_WINDOW_TEXT_X, NOTEPAD_WINDOW_TEXT_SIZE_Y ); - m_pBriefing->setParent( m_pScrollPanel->getClient() ); - m_pBriefing->setFont( pSchemes->getFont(hDecayFont) ); - pSchemes->getFgColor( hDecayFont, r, g, b, a ); - m_pBriefing->setFgColor( r, g, b, a ); - pSchemes->getBgColor( hDecayFont, r, g, b, a ); - m_pBriefing->setBgColor( r, g, b, a ); - m_pBriefing->setText(gHUD.m_TextMessage.BufferedLocaliseTextString(szText)); - - // Create the Cancel button - m_pCancelButton = new CommandButton( "", NOTEPAD_TOPLEFT_BUTTON_X, NOTEPAD_TOPLEFT_BUTTON_Y, NOTEPAD_BUTTON_SIZE_X, NOTEPAD_BUTTON_SIZE_Y, true); - m_pCancelButton->setParent( this ); - m_pCancelButton->setText( gHUD.m_TextMessage.BufferedLocaliseTextString(" CLOSE") ); - m_pCancelButton->setVisible( true ); - m_pCancelButton->addActionSignal(new CMenuHandler_TextWindow(HIDE_TEXTWINDOW)); - - /* - m_pImage = new CImageLabel( "gina", 0, 0, 128, 256 ); // gfx/vgui/640_gina.tga - //m_pImage->setText(25, "this is a test!!!"); - m_pImage->setParent( this ); - m_pImage->setVisible( true ); - */ - - Initialize(); -} - -//----------------------------------------------------------------------------- -// Purpose: Called each time a new level is started. -//----------------------------------------------------------------------------- -void CNotepad::Initialize( void ) -{ - m_pScrollPanel->setScrollValue( 0, 0 ); -} - -//----------------------------------------------------------------------------- -// Purpose: Called everytime the Team Menu is displayed -//----------------------------------------------------------------------------- -void CNotepad::Update( void ) -{ - m_pBriefing->setText(gHUD.m_TextMessage.BufferedLocaliseTextString(szText)); - m_pTitle->setText(gHUD.m_TextMessage.BufferedLocaliseTextString(szTitle)); - - int iYPos = NOTEPAD_TOPLEFT_BUTTON_Y; - - // Move the AutoAssign button into place - m_pCancelButton->setPos( NOTEPAD_TOPLEFT_BUTTON_X, iYPos ); - - m_pScrollPanel->validate(); - //m_pImage->setBounds( NOTEPAD_TOPLEFT_BUTTON_X, NOTEPAD_TOPLEFT_BUTTON_Y-256-32, 128, 256 ); -} - -//===================================== -// Key inputs -bool CNotepad::SlotInput( int iSlot ) -{ - if ( iSlot == 1) - { - m_pCancelButton->fireActionSignal(); - return true; - } - return false; -} - -//====================================== -// Update the Team menu before opening it -void CNotepad::Open( void ) -{ - Update(); - CMenuPanel::Open(); -} +//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. =========== +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: Notepad +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//============================================================================= + +#include "vgui_int.h" +#include "VGUI_Font.h" +#include "VGUI_ScrollPanel.h" +#include "VGUI_TextImage.h" +#include "VGUI_loadtga.h" + +#include "hud.h" +#include "cl_util.h" +#include "vgui_TeamFortressViewport.h" + +// Notepad Dimensions +#define NOTEPAD_TITLE_X XRES(40) +#define NOTEPAD_TITLE_Y YRES(32) +#define NOTEPAD_TOPLEFT_BUTTON_X XRES(251)//was 245 +#define NOTEPAD_TOPLEFT_BUTTON_Y YRES(400) +#define NOTEPAD_BUTTON_SIZE_X XRES(100) +#define NOTEPAD_BUTTON_SIZE_Y YRES(24) +#define NOTEPAD_BUTTON_SPACER_Y YRES(8) +#define NOTEPAD_WINDOW_X XRES(150) +#define NOTEPAD_WINDOW_Y YRES(150) +#define NOTEPAD_WINDOW_SIZE_X XRES(300) +#define NOTEPAD_WINDOW_SIZE_Y YRES(220) +#define NOTEPAD_WINDOW_TITLE_X XRES(16) +#define NOTEPAD_WINDOW_TITLE_Y YRES(16) +#define NOTEPAD_WINDOW_TEXT_X XRES(16) +#define NOTEPAD_WINDOW_TEXT_Y YRES(32) +#define NOTEPAD_WINDOW_TEXT_SIZE_Y YRES(168) +#define NOTEPAD_WINDOW_INFO_X XRES(16) +#define NOTEPAD_WINDOW_INFO_Y YRES(234) + +// Creation +CNotepad::CNotepad(int iTrans, int iRemoveMe, int x,int y,int wide,int tall) : CMenuPanel(iTrans, iRemoveMe, x,y,wide,tall) +{ + // Get the scheme used for the Titles + CSchemeManager *pSchemes = gViewPort->GetSchemeManager(); + + // schemes + SchemeHandle_t hTitleScheme = pSchemes->getSchemeHandle( "Title Font" ); + SchemeHandle_t hDecayFont = pSchemes->getSchemeHandle( "Briefing Text" ); //Decay + + // get the Font used for the Titles + Font *pTitleFont = pSchemes->getFont( hTitleScheme ); + int r, g, b, a; + + // Create the title + m_pTitle = new Label( "", NOTEPAD_WINDOW_X+10, NOTEPAD_WINDOW_Y+5 ); + m_pTitle->setParent( this ); + m_pTitle->setFont( pTitleFont ); + pSchemes->getFgColor( hTitleScheme, r, g, b, a ); + m_pTitle->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hTitleScheme, r, g, b, a ); + m_pTitle->setBgColor( r, g, b, a ); + m_pTitle->setContentAlignment( vgui::Label::a_west ); + m_pTitle->setText(gHUD.m_TextMessage.BufferedLocaliseTextString(szTitle)); + + // Create the Info Window + m_pNotepadWindow = new CTransparentPanel( 255, NOTEPAD_WINDOW_X, NOTEPAD_WINDOW_Y, NOTEPAD_WINDOW_SIZE_X, NOTEPAD_WINDOW_SIZE_Y ); + m_pNotepadWindow->setParent( this ); + m_pNotepadWindow->setBorder( new LineBorder( Color(255*0.7,170*0.7,0,0 )) ); + + // Create the Scroll panel + m_pScrollPanel = new CTFScrollPanel( NOTEPAD_WINDOW_TEXT_X, NOTEPAD_WINDOW_TEXT_Y, NOTEPAD_WINDOW_SIZE_X - (NOTEPAD_WINDOW_TEXT_X * 2), NOTEPAD_WINDOW_TEXT_SIZE_Y ); + m_pScrollPanel->setParent(m_pNotepadWindow); + m_pScrollPanel->setScrollBarVisible(false, false); + + // Create the Map Briefing panel + m_pBriefing = new TextPanel("", 0,0, NOTEPAD_WINDOW_SIZE_X - NOTEPAD_WINDOW_TEXT_X, NOTEPAD_WINDOW_TEXT_SIZE_Y ); + m_pBriefing->setParent( m_pScrollPanel->getClient() ); + m_pBriefing->setFont( pSchemes->getFont(hDecayFont) ); + pSchemes->getFgColor( hDecayFont, r, g, b, a ); + m_pBriefing->setFgColor( r, g, b, a ); + pSchemes->getBgColor( hDecayFont, r, g, b, a ); + m_pBriefing->setBgColor( r, g, b, a ); + m_pBriefing->setText(gHUD.m_TextMessage.BufferedLocaliseTextString(szText)); + + // Create the Cancel button + m_pCancelButton = new CommandButton( "", NOTEPAD_TOPLEFT_BUTTON_X, NOTEPAD_TOPLEFT_BUTTON_Y, NOTEPAD_BUTTON_SIZE_X, NOTEPAD_BUTTON_SIZE_Y, true); + m_pCancelButton->setParent( this ); + m_pCancelButton->setText( gHUD.m_TextMessage.BufferedLocaliseTextString(" CLOSE") ); + m_pCancelButton->setVisible( true ); + m_pCancelButton->addActionSignal(new CMenuHandler_TextWindow(HIDE_TEXTWINDOW)); + + /* + m_pImage = new CImageLabel( "gina", 0, 0, 128, 256 ); // gfx/vgui/640_gina.tga + //m_pImage->setText(25, "this is a test!!!"); + m_pImage->setParent( this ); + m_pImage->setVisible( true ); + */ + + Initialize(); +} + +//----------------------------------------------------------------------------- +// Purpose: Called each time a new level is started. +//----------------------------------------------------------------------------- +void CNotepad::Initialize( void ) +{ + m_pScrollPanel->setScrollValue( 0, 0 ); +} + +//----------------------------------------------------------------------------- +// Purpose: Called everytime the Team Menu is displayed +//----------------------------------------------------------------------------- +void CNotepad::Update( void ) +{ + m_pBriefing->setText(gHUD.m_TextMessage.BufferedLocaliseTextString(szText)); + m_pTitle->setText(gHUD.m_TextMessage.BufferedLocaliseTextString(szTitle)); + + int iYPos = NOTEPAD_TOPLEFT_BUTTON_Y; + + // Move the AutoAssign button into place + m_pCancelButton->setPos( NOTEPAD_TOPLEFT_BUTTON_X, iYPos ); + + m_pScrollPanel->validate(); + //m_pImage->setBounds( NOTEPAD_TOPLEFT_BUTTON_X, NOTEPAD_TOPLEFT_BUTTON_Y-256-32, 128, 256 ); +} + +//===================================== +// Key inputs +bool CNotepad::SlotInput( int iSlot ) +{ + if ( iSlot == 1) + { + m_pCancelButton->fireActionSignal(); + return true; + } + return false; +} + +//====================================== +// Update the Team menu before opening it +void CNotepad::Open( void ) +{ + Update(); + CMenuPanel::Open(); +} diff --git a/dlls/actanimating.cpp b/dlls/actanimating.cpp index fac50005..59307242 100644 --- a/dlls/actanimating.cpp +++ b/dlls/actanimating.cpp @@ -1,36 +1,36 @@ -#include "extdll.h" -#include "util.h" -#include "cbase.h" -#include "animation.h" -#include "effects.h" -#include "actanimating.h" - -TYPEDESCRIPTION CActAnimating::m_SaveData[] = -{ - DEFINE_FIELD( CActAnimating, m_Activity, FIELD_INTEGER ), - DEFINE_FIELD( CActAnimating, m_iSequence, FIELD_INTEGER ), -}; - -IMPLEMENT_SAVERESTORE( CActAnimating, CBaseAnimating ); - -void CActAnimating :: SetActivity( Activity act ) -{ - int sequence = LookupActivity( act ); - if ( sequence != ACTIVITY_NOT_AVAILABLE ) - { - m_iSequence = sequence; - pev->sequence = sequence; - m_Activity = act; - pev->frame = 0; - ResetSequenceInfo( ); - } -} - -void CActAnimating :: SetSequence( int sequence ) -{ - //m_Activity = ACTIVITY_NOT_AVAILABLE; - pev->sequence = sequence; - m_iSequence = sequence; - pev->frame = 0; - ResetSequenceInfo( ); +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "animation.h" +#include "effects.h" +#include "actanimating.h" + +TYPEDESCRIPTION CActAnimating::m_SaveData[] = +{ + DEFINE_FIELD( CActAnimating, m_Activity, FIELD_INTEGER ), + DEFINE_FIELD( CActAnimating, m_iSequence, FIELD_INTEGER ), +}; + +IMPLEMENT_SAVERESTORE( CActAnimating, CBaseAnimating ); + +void CActAnimating :: SetActivity( Activity act ) +{ + int sequence = LookupActivity( act ); + if ( sequence != ACTIVITY_NOT_AVAILABLE ) + { + m_iSequence = sequence; + pev->sequence = sequence; + m_Activity = act; + pev->frame = 0; + ResetSequenceInfo( ); + } +} + +void CActAnimating :: SetSequence( int sequence ) +{ + //m_Activity = ACTIVITY_NOT_AVAILABLE; + pev->sequence = sequence; + m_iSequence = sequence; + pev->frame = 0; + ResetSequenceInfo( ); } \ No newline at end of file diff --git a/dlls/actanimating.h b/dlls/actanimating.h index b795970c..9a02da28 100644 --- a/dlls/actanimating.h +++ b/dlls/actanimating.h @@ -1,19 +1,19 @@ - -class CActAnimating : public CBaseAnimating -{ -public: - void SetActivity( Activity act ); - inline Activity GetActivity( void ) { return m_Activity; } - void SetSequence( int sequence ); - inline int GetSequence( void ) { return m_iSequence; } - - virtual int ObjectCaps( void ) { return CBaseAnimating :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } - - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - static TYPEDESCRIPTION m_SaveData[]; - -private: - Activity m_Activity; - int m_iSequence; -}; + +class CActAnimating : public CBaseAnimating +{ +public: + void SetActivity( Activity act ); + inline Activity GetActivity( void ) { return m_Activity; } + void SetSequence( int sequence ); + inline int GetSequence( void ) { return m_iSequence; } + + virtual int ObjectCaps( void ) { return CBaseAnimating :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + +private: + Activity m_Activity; + int m_iSequence; +}; diff --git a/dlls/alienflyer.cpp b/dlls/alienflyer.cpp index fa7a99c6..9531ff7d 100644 --- a/dlls/alienflyer.cpp +++ b/dlls/alienflyer.cpp @@ -1,809 +1,809 @@ -/*** -* -* Copyright (c) 2007, Vyacheslav Dzhura -* -****/ - -#include "extdll.h" -#include "util.h" -#include "cbase.h" -#include "monsters.h" -#include "weapons.h" -#include "nodes.h" -#include "effects.h" -#include "trains.h" - -extern DLL_GLOBAL int g_iSkillLevel; - -#define SF_WAITFORTRIGGER (0x04 | 0x40) // UNDONE: Fix -#define SF_NOWRECKAGE 0x08 -#define SF_PATHCORNER_USELASER 8 -#define SF_PATHCORNER_ATTACK 16 - -class CAlienFlyer : public CBaseMonster -{ - int Save( CSave &save ); - int Restore( CRestore &restore ); - static TYPEDESCRIPTION m_SaveData[]; - - void Spawn( void ); - void Precache( void ); - int Classify( void ) { return CLASS_ALIEN_MONSTER; }; - int BloodColor( void ) { return DONT_BLEED; } - void Killed( entvars_t *pevAttacker, int iGib ); - void GibMonster( void ); - //void (CAlienFlyer::*m_pfnCallWhenMoveDone)(void); - void KeyValue(KeyValueData *pkvd); - - void SetObjectCollisionBox( void ) - { - pev->absmin = pev->origin + Vector( -300, -300, -172); - pev->absmax = pev->origin + Vector(300, 300, 8); - } - - void EXPORT HuntThink( void ); - void EXPORT FlyTouch( CBaseEntity *pOther ); - - void EXPORT CrashTouch( CBaseEntity *pOther ); - void EXPORT DyingThink( void ); - void EXPORT StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void EXPORT NullThink( void ); - - void ShowDamage( void ); - void Flight( void ); - void UseDamagingBeam( bool bTurnOn ); - void CheckAttack( 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 EXPORT Wait( void ); - //void EXPORT Next( void ); - - void Activate( void ); - - float m_flForce; - int m_iszDeathTarget; - - // from func_train - entvars_t *m_pevCurrentTarget; - BOOL m_activated; - // end of - - Vector m_vecTarget; - Vector m_posTarget; - - Vector m_vecDesired; - Vector m_posDesired; - - Vector m_vecGoal; - - float m_flLastSeen; - float m_flPrevSeen; - float m_flDieCounter; - float m_flLaserCounter; - float m_flAttackCounter; - - //int m_iSoundState; // don't save this - - int m_iSpriteTexture; - int m_iExplode; - int m_iBodyGibs; - - float m_flGoalSpeed; - - CBeam *m_pBeam[2]; -}; -//#define SetMoveDone( a ) m_pfnCallWhenMoveDone = static_cast (a) - -LINK_ENTITY_TO_CLASS( monster_alienflyer, CAlienFlyer ); - -TYPEDESCRIPTION CAlienFlyer::m_SaveData[] = -{ - DEFINE_FIELD( CAlienFlyer, m_pevCurrentTarget, FIELD_EVARS ), - DEFINE_FIELD( CAlienFlyer, m_activated, FIELD_BOOLEAN ), - - DEFINE_FIELD( CAlienFlyer, m_flForce, FIELD_FLOAT ), - DEFINE_FIELD( CAlienFlyer, m_vecTarget, FIELD_VECTOR ), - DEFINE_FIELD( CAlienFlyer, m_posTarget, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( CAlienFlyer, m_vecDesired, FIELD_VECTOR ), - DEFINE_FIELD( CAlienFlyer, m_posDesired, FIELD_POSITION_VECTOR ), - DEFINE_FIELD( CAlienFlyer, m_vecGoal, FIELD_VECTOR ), - DEFINE_FIELD( CAlienFlyer, m_flLastSeen, FIELD_TIME ), - DEFINE_FIELD( CAlienFlyer, m_flPrevSeen, FIELD_TIME ), - DEFINE_FIELD( CAlienFlyer, m_flGoalSpeed, FIELD_FLOAT ), - DEFINE_FIELD( CAlienFlyer, m_iszDeathTarget, FIELD_STRING ), - DEFINE_FIELD( CAlienFlyer, m_flLaserCounter, FIELD_FLOAT ), - DEFINE_FIELD( CAlienFlyer, m_flDieCounter, FIELD_FLOAT ), - DEFINE_FIELD( CAlienFlyer, m_flAttackCounter, FIELD_FLOAT ), - DEFINE_ARRAY( CAlienFlyer, m_pBeam, FIELD_CLASSPTR , 2 ), -}; -IMPLEMENT_SAVERESTORE( CAlienFlyer, CBaseMonster ); - -void CAlienFlyer::KeyValue(KeyValueData *pkvd) -{ - if (FStrEq(pkvd->szKeyName, "death_target")) - { - m_iszDeathTarget = ALLOC_STRING(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else - CBaseEntity::KeyValue( pkvd ); -} - -void CAlienFlyer :: Spawn( void ) -{ - Precache( ); - // motor - pev->movetype = MOVETYPE_FLY; - pev->solid = SOLID_BBOX; - - SET_MODEL(ENT(pev), "models/flyer.mdl"); - UTIL_SetSize( pev, Vector( -32, -32, -64 ), Vector( 32, 32, 0 ) ); - UTIL_SetOrigin( pev, pev->origin ); - - pev->flags |= FL_MONSTER; - pev->takedamage = DAMAGE_AIM; - pev->health = 150*10; // gSkillData.apacheHealth; - - m_flFieldOfView = -0.707; // 270 degrees - - pev->sequence = 0; - ResetSequenceInfo( ); - pev->frame = RANDOM_LONG(0, 0xFF); - - InitBoneControllers(); - - m_pBeam[0] = CBeam::BeamCreate( "sprites/xenobeam.spr", 200 ); - m_pBeam[0]->SetFlags( SF_BEAM_SHADEIN ); - m_pBeam[0]->EntsInit( entindex( ), entindex( ) ); - m_pBeam[0]->SetStartAttachment( 0 ); - m_pBeam[0]->SetEndAttachment( 1 ); - m_pBeam[0]->SetColor( 254, 244, 233 ); - m_pBeam[0]->SetNoise( 3 ); - m_pBeam[0]->SetBrightness( 150 ); - m_pBeam[0]->SetWidth( 255 ); - m_pBeam[0]->SetScrollRate( 40 ); - - m_pBeam[1] = CBeam::BeamCreate( "sprites/laserbeam.spr", 200 ); - m_pBeam[1]->SetFlags( SF_BEAM_SHADEIN ); - m_pBeam[1]->EntsInit( entindex( ), entindex( ) ); - m_pBeam[1]->SetStartAttachment( 0 ); - m_pBeam[1]->SetEndAttachment( 1 ); - m_pBeam[1]->SetColor( 255, 255, 255 ); - m_pBeam[1]->SetNoise( 100 ); - m_pBeam[1]->SetBrightness( 125 ); - m_pBeam[1]->SetWidth( 30 ); - m_pBeam[1]->SetScrollRate( 35 ); - - UseDamagingBeam( false ); - - if (pev->spawnflags & SF_WAITFORTRIGGER) - { - SetUse( &CAlienFlyer::StartupUse ); - m_activated = false; - pev->effects |= EF_NODRAW; - } - else - { - SetThink( &CAlienFlyer::HuntThink ); - SetTouch( &CAlienFlyer::FlyTouch ); - pev->nextthink = gpGlobals->time + 1.0; - m_activated = true; - } -} - -void CAlienFlyer::UseDamagingBeam( bool bTurnOn ) -{ - if ( bTurnOn ) - { - m_pBeam[0]->pev->effects &= ~EF_NODRAW; - m_pBeam[1]->pev->effects &= ~EF_NODRAW; - } else - { - m_pBeam[0]->pev->effects |= EF_NODRAW; - m_pBeam[1]->pev->effects |= EF_NODRAW; - } -} - -void CAlienFlyer::Activate( void ) -{ - // Not yet active, so teleport to first target - if ( !m_activated ) - { - m_activated = TRUE; - entvars_t *pevTarg = VARS( FIND_ENTITY_BY_TARGETNAME (NULL, STRING(pev->target) ) ); - - pev->target = pevTarg->target; - m_pevCurrentTarget = pevTarg;// keep track of this since path corners change our target for us. - - UTIL_SetOrigin (pev, pevTarg->origin - (pev->mins + pev->maxs) * 0.5 ); - - if ( FStringNull(pev->targetname) ) - { // not triggered, so start immediately - pev->nextthink = pev->ltime + 0.1; - SetThink( &CAlienFlyer::HuntThink ); - } - else - pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; - } -} - -void CAlienFlyer::Precache( void ) -{ - PRECACHE_MODEL("models/flyer.mdl"); - - m_iSpriteTexture = PRECACHE_MODEL( "sprites/white.spr" ); - PRECACHE_MODEL("sprites/lgtning.spr"); - PRECACHE_MODEL("sprites/xenobeam.spr"); - - m_iExplode = PRECACHE_MODEL( "sprites/fexplo.spr" ); - m_iBodyGibs = PRECACHE_MODEL( "models/flyer_gibs.mdl" ); -} - - - -void CAlienFlyer::NullThink( void ) -{ - StudioFrameAdvance( ); - pev->nextthink = gpGlobals->time + 0.5; -} - - -void CAlienFlyer::StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) -{ - pev->effects &= ~EF_NODRAW; - SetThink( &CAlienFlyer::HuntThink ); - SetTouch( &CAlienFlyer::FlyTouch ); - pev->nextthink = gpGlobals->time + 0.1; - SetUse( NULL ); -} - -void CAlienFlyer :: Killed( entvars_t *pevAttacker, int iGib ) -{ - for ( int i = 0; i<2; i++) - { - if (m_pBeam[i]) - { - m_pBeam[i]->SUB_StartFadeOut(); - m_pBeam[i] = NULL; - } - } - - pev->movetype = MOVETYPE_TOSS; - pev->gravity = 0.3; - - UTIL_SetSize( pev, Vector( -32, -32, -64), Vector( 32, 32, 0) ); - SetThink( &CAlienFlyer::DyingThink ); - SetTouch( &CAlienFlyer::CrashTouch ); - pev->nextthink = gpGlobals->time + 0.1; - pev->health = 0; - pev->takedamage = DAMAGE_NO; - - m_flDieCounter = gpGlobals->time + 2.5; - - FireTargets( STRING(m_iszDeathTarget), this, this, USE_TOGGLE, 1.0 ); -} - -void CAlienFlyer :: DyingThink( void ) -{ - StudioFrameAdvance( ); - pev->nextthink = gpGlobals->time + 0.1; - - pev->avelocity = pev->avelocity * 1.02; - - // still falling? - if (m_flDieCounter > gpGlobals->time ) - { - // random explosions - MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); - WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now - WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -150, 150 )); - WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -150, 150 )); - WRITE_COORD( pev->origin.z + RANDOM_FLOAT( -150, -50 )); - WRITE_SHORT( g_sModelIndexFireball ); - WRITE_BYTE( RANDOM_LONG(0,29) + 30 ); // scale * 10 - WRITE_BYTE( 12 ); // framerate - WRITE_BYTE( TE_EXPLFLAG_NONE ); - MESSAGE_END(); - - Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; - MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); - WRITE_BYTE( TE_BREAKMODEL); - - // position - WRITE_COORD( vecSpot.x ); - WRITE_COORD( vecSpot.y ); - WRITE_COORD( vecSpot.z ); - - // size - WRITE_COORD( 400 ); - WRITE_COORD( 400 ); - WRITE_COORD( 132 ); - - // velocity - WRITE_COORD( pev->velocity.x ); - WRITE_COORD( pev->velocity.y ); - WRITE_COORD( pev->velocity.z ); - - // randomization - WRITE_BYTE( 25 ); - - // Model - WRITE_SHORT( m_iBodyGibs ); //model id# - - // # of shards - WRITE_BYTE( 1 ); // let client decide - - // duration - WRITE_BYTE( 30 );// 3.0 seconds - - // flags - - WRITE_BYTE( BREAK_METAL ); - MESSAGE_END(); - - // don't stop it we touch a entity - pev->flags &= ~FL_ONGROUND; - pev->nextthink = gpGlobals->time + 0.2; - return; - } - else - // fell - { - Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; - - // blast circle - MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); - WRITE_BYTE( TE_BEAMCYLINDER ); - WRITE_COORD( pev->origin.x); - WRITE_COORD( pev->origin.y); - WRITE_COORD( pev->origin.z); - WRITE_COORD( pev->origin.x); - WRITE_COORD( pev->origin.y); - WRITE_COORD( pev->origin.z + 2000 ); // reach damage radius over .2 seconds - WRITE_SHORT( m_iSpriteTexture ); - WRITE_BYTE( 0 ); // startframe - WRITE_BYTE( 0 ); // framerate - WRITE_BYTE( 4 ); // life - WRITE_BYTE( 32 ); // width - WRITE_BYTE( 0 ); // noise - WRITE_BYTE( 255 ); // r, g, b - WRITE_BYTE( 255 ); // r, g, b - WRITE_BYTE( 192 ); // r, g, b - WRITE_BYTE( 128 ); // brightness - WRITE_BYTE( 0 ); // speed - MESSAGE_END(); - - EMIT_SOUND(ENT(pev), CHAN_STATIC, "weapons/mortarhit.wav", 1.0, 0.3); - - // Vyacheslav Dzhura: fix pointed by Barnz - damage was 300 - // so when players killed the flyer, flyer killed focusemitter, fun isn't it? :) - RadiusDamage( pev->origin, pev, pev, 0, CLASS_NONE, DMG_BLAST ); - - if (/*!(pev->spawnflags & SF_NOWRECKAGE) && */(pev->flags & FL_ONGROUND)) - { - CBaseEntity *pWreckage = Create( "cycler_wreckage", pev->origin, pev->angles ); - // SET_MODEL( ENT(pWreckage->pev), STRING(pev->model) ); - UTIL_SetSize( pWreckage->pev, Vector( -200, -200, -128 ), Vector( 200, 200, -32 ) ); - pWreckage->pev->frame = pev->frame; - pWreckage->pev->sequence = pev->sequence; - pWreckage->pev->framerate = 0; - pWreckage->pev->dmgtime = gpGlobals->time + 5; - } - - // gibs - vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; - MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); - WRITE_BYTE( TE_BREAKMODEL); - - // position - WRITE_COORD( vecSpot.x ); - WRITE_COORD( vecSpot.y ); - WRITE_COORD( vecSpot.z + 64); - - // size - WRITE_COORD( 400 ); - WRITE_COORD( 400 ); - WRITE_COORD( 128 ); - - // velocity - WRITE_COORD( 0 ); - WRITE_COORD( 0 ); - WRITE_COORD( 200 ); - - // randomization - WRITE_BYTE( 30 ); - - // Model - WRITE_SHORT( m_iBodyGibs ); //model id# - - // # of shards - WRITE_BYTE( 100 ); - - // duration - WRITE_BYTE( 200 );// 10.0 seconds - - // flags - WRITE_BYTE( BREAK_FLESH ); - MESSAGE_END(); - - SetThink( &CAlienFlyer::SUB_Remove ); - pev->nextthink = gpGlobals->time + 0.1; - } -} - - -void CAlienFlyer::FlyTouch( CBaseEntity *pOther ) -{ - // bounce if we hit something solid - if ( pOther->pev->solid == SOLID_BSP) - { - TraceResult tr = UTIL_GetGlobalTrace( ); - - // UNDONE, do a real bounce - pev->velocity = pev->velocity + tr.vecPlaneNormal * (pev->velocity.Length() + 200); - } -} - - -void CAlienFlyer::CrashTouch( CBaseEntity *pOther ) -{ - // only crash if we hit something solid - if ( pOther->pev->solid == SOLID_BSP) - { - SetTouch( NULL ); - pev->nextthink = gpGlobals->time; - } -} - -void CAlienFlyer :: CheckAttack( void ) -{ - if (m_flAttackCounter < gpGlobals->time) - return; - - CBaseEntity *pList[8]; - - Vector delta = Vector( 32, 32, 0 ); - Vector mins = pev->origin - delta; - Vector maxs = pev->origin + delta; - maxs.z = pev->origin.z; - mins.z -= 320; - - int count = UTIL_EntitiesInBox( pList, 8, mins, maxs, FL_MONSTER|FL_CLIENT ); - Vector forward; - - if (count > 1) - ALERT( at_console, "(CAlienFlyer) Found %d victims:\n", count-1 ); - - UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); - - for ( int i = 0; i < count; i++ ) - { - if ( pList[i] != this ) - { - if ( pList[i]->pev->owner != edict() ) - { - pList[i]->TakeDamage( pev, pev, 150, DMG_CRUSH | DMG_SLASH ); - ALERT( at_console, "(%s) %s\n", STRING(pList[i]->pev->classname), STRING(pList[i]->pev->targetname) ); - if ( FClassnameIs( pList[i]->pev, "item_focusemitter" )) - m_flAttackCounter = gpGlobals->time - 1.0; // prevent multi-damage 2.5 second attack on focusemitter - - //pList[i]->pev->punchangle.x = 15; - //pList[i]->pev->velocity = pList[i]->pev->velocity + forward * 100; - } - } - } -} - -void CAlienFlyer :: GibMonster( void ) -{ - // EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200); -} - -void CAlienFlyer :: HuntThink( void ) -{ - StudioFrameAdvance( ); - pev->nextthink = gpGlobals->time + 0.1; - - ShowDamage( ); - - if ( m_pGoalEnt == NULL && !FStringNull(pev->target) )// this monster has a target - { - m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ) ); - if (m_pGoalEnt) - { - m_posDesired = m_pGoalEnt->pev->origin; - UTIL_MakeAimVectors( m_pGoalEnt->pev->angles ); - m_vecGoal = gpGlobals->v_forward; - } - } - - Look( 4092 ); - m_hEnemy = BestVisibleEnemy( ); - - // generic speed up - if (m_flGoalSpeed < 800) - m_flGoalSpeed += 10; // was 5 - - if (m_hEnemy != NULL) - { - // ALERT( at_console, "%s\n", STRING( m_hEnemy->pev->classname ) ); - if (FVisible( m_hEnemy )) - { - if (m_flLastSeen < gpGlobals->time - 5) - m_flPrevSeen = gpGlobals->time; - m_flLastSeen = gpGlobals->time; - m_posTarget = m_hEnemy->Center( ); - } - else - { - m_hEnemy = NULL; - } - } - - m_vecTarget = (m_posTarget - pev->origin).Normalize(); - - float flLength = (pev->origin - m_posDesired).Length(); - - if ( m_flLaserCounter < gpGlobals->time ) - UseDamagingBeam( false ); - - if (m_pGoalEnt) - { - // ALERT( at_console, "%.0f\n", flLength ); - CheckAttack(); - - // we are close to target entity, check next target - // and if targeting entity has target, slowdown - if (flLength < 128) - { - // NEW CODE - // Save last target in case we need to find it again - pev->message = m_pGoalEnt->pev->targetname; - - if ( m_pGoalEnt->pev->spawnflags & SF_PATHCORNER_USELASER ) - { - UseDamagingBeam( true ); - m_flLaserCounter = gpGlobals->time + 2.5; - } - - if ( m_pGoalEnt->pev->spawnflags & SF_PATHCORNER_ATTACK ) - { - m_flAttackCounter = gpGlobals->time + 2.5; - CheckAttack(); - } - - if (m_pGoalEnt->pev->message) - { - m_flGoalSpeed -= 32; - //if (flLength < 32) - { - // fire target's "message" - if ( m_activated == false ) - { - FireTargets( STRING( m_pGoalEnt->pev->message ), this, this, USE_TOGGLE, 1.0 ); - ALERT( at_console, "AlienFlyer used %s\n", STRING( m_pGoalEnt->pev->message )); - m_activated = true; - } - } - } - // END OF NEW CODE - - m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( m_pGoalEnt->pev->target ) ); - if (m_pGoalEnt) - { - m_posDesired = m_pGoalEnt->pev->origin; - UTIL_MakeAimVectors( m_pGoalEnt->pev->angles ); - m_vecGoal = gpGlobals->v_forward; - flLength = (pev->origin - m_posDesired).Length(); - } - } else - m_activated = false; - } - else - { - m_posDesired = pev->origin; - } - - if (flLength > 250) // 500 - { - if (m_flLastSeen + 90 > gpGlobals->time && DotProduct( (m_posTarget - pev->origin).Normalize(), (m_posDesired - pev->origin).Normalize( )) > 0.25) - { - m_vecDesired = (m_posTarget - pev->origin).Normalize( ); - } - else - { - m_vecDesired = (m_posDesired - pev->origin).Normalize( ); - } - } - else - { - m_vecDesired = m_vecGoal; - } - - Flight( ); - - // ALERT( at_console, "%.0f %.0f %.0f\n", gpGlobals->time, m_flLastSeen, m_flPrevSeen ); - if ((m_flLastSeen + 1 > gpGlobals->time) && (m_flPrevSeen + 2 < gpGlobals->time)) - { - // FireGun was here - - // don't fire rockets and gun on easy mode - //if (g_iSkillLevel == SKILL_EASY) - // m_flNextRocket = gpGlobals->time + 10.0; - } - - UTIL_MakeAimVectors( pev->angles ); - Vector vecEst = (gpGlobals->v_forward * 800 + pev->velocity).Normalize( ); - // ALERT( at_console, "%d %d %d %4.2f\n", pev->angles.x < 0, DotProduct( pev->velocity, gpGlobals->v_forward ) > -100, m_flNextRocket < gpGlobals->time, DotProduct( m_vecTarget, vecEst ) ); - - // rocket firing stuff was here -} - - -void CAlienFlyer :: Flight( void ) -{ - // tilt model 5 degrees - Vector vecAdj = Vector( 5.0, 0, 0 ); - - // estimate where I'll be facing in one seconds - UTIL_MakeAimVectors( pev->angles + pev->avelocity * 2 + vecAdj); - // Vector vecEst1 = pev->origin + pev->velocity + gpGlobals->v_up * m_flForce - Vector( 0, 0, 384 ); - // float flSide = DotProduct( m_posDesired - vecEst1, gpGlobals->v_right ); - - float flSide = DotProduct( m_vecDesired, gpGlobals->v_right ); - - if (flSide < 0) - { - if (pev->avelocity.y < 60) - { - pev->avelocity.y += 8; // 9 * (3.0/2.0); - } - } - else - { - if (pev->avelocity.y > -60) - { - pev->avelocity.y -= 8; // 9 * (3.0/2.0); - } - } - pev->avelocity.y *= 0.98; - - // estimate where I'll be in two seconds - UTIL_MakeAimVectors( pev->angles + pev->avelocity * 1 + vecAdj); - Vector vecEst = pev->origin + pev->velocity * 2.0 + gpGlobals->v_up * m_flForce * 20 - Vector( 0, 0, 384 * 2 ); - - // add immediate force - UTIL_MakeAimVectors( pev->angles + vecAdj); - pev->velocity.x += gpGlobals->v_up.x * m_flForce; - pev->velocity.y += gpGlobals->v_up.y * m_flForce; - pev->velocity.z += gpGlobals->v_up.z * m_flForce; - // add gravity - pev->velocity.z -= 38.4; // 32ft/sec - - - float flSpeed = pev->velocity.Length(); - float flDir = DotProduct( Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, 0 ), Vector( pev->velocity.x, pev->velocity.y, 0 ) ); - if (flDir < 0) - flSpeed = -flSpeed; - - float flDist = DotProduct( m_posDesired - vecEst, gpGlobals->v_forward ); - - // float flSlip = DotProduct( pev->velocity, gpGlobals->v_right ); - float flSlip = -DotProduct( m_posDesired - vecEst, gpGlobals->v_right ); - - // fly sideways - if (flSlip > 0) - { - if (pev->angles.z > -30 && pev->avelocity.z > -15) - pev->avelocity.z -= 4; - else - pev->avelocity.z += 2; - } - else - { - - if (pev->angles.z < 30 && pev->avelocity.z < 15) - pev->avelocity.z += 4; - else - pev->avelocity.z -= 2; - } - - // sideways drag - pev->velocity.x = pev->velocity.x * (1.0 - fabs( gpGlobals->v_right.x ) * 0.05); - pev->velocity.y = pev->velocity.y * (1.0 - fabs( gpGlobals->v_right.y ) * 0.05); - pev->velocity.z = pev->velocity.z * (1.0 - fabs( gpGlobals->v_right.z ) * 0.05); - - // general drag - pev->velocity = pev->velocity * 0.995; - - // apply power to stay correct height - if (m_flForce < 80 && vecEst.z < m_posDesired.z) - { - m_flForce += 12; - } - else if (m_flForce > 30) - { - if (vecEst.z > m_posDesired.z) - m_flForce -= 8; - } - - // pitch forward or back to get to target - if (flDist > 0 && flSpeed < m_flGoalSpeed && pev->angles.x + pev->avelocity.x > -40) - { - // ALERT( at_console, "F " ); - // lean forward - pev->avelocity.x -= 24; //12.0; - } - else if (flDist < 0 && flSpeed > -50 && pev->angles.x + pev->avelocity.x < 20) - { - // ALERT( at_console, "B " ); - // lean backward - pev->avelocity.x += 12.0; - } - else if (pev->angles.x + pev->avelocity.x > 0) - { - // ALERT( at_console, "f " ); - pev->avelocity.x -= 4.0; - } - else if (pev->angles.x + pev->avelocity.x < 0) - { - // ALERT( at_console, "b " ); - pev->avelocity.x += 4.0; - } - - // ALERT( at_console, "%.0f %.0f : %.0f %.0f : %.0f %.0f : %.0f\n", pev->origin.x, pev->velocity.x, flDist, flSpeed, pev->angles.x, pev->avelocity.x, m_flForce ); - // ALERT( at_console, "%.0f %.0f : %.0f %0.f : %.0f\n", pev->origin.z, pev->velocity.z, vecEst.z, m_posDesired.z, m_flForce ); - - // rotor sounds were here -} - -void CAlienFlyer :: ShowDamage( void ) -{ - // smoke was here -} - -int CAlienFlyer :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) -{ - if (pevInflictor->owner == edict()) - return 0; - - if (bitsDamageType & DMG_BLAST) - { - flDamage *= 2; - } - - /* - if ( (bitsDamageType & DMG_BULLET) && flDamage > 50) - { - // clip bullet damage at 50 - flDamage = 50; - } - */ - - // ALERT( at_console, "%.0f\n", flDamage ); - return CBaseEntity::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); -} - -void CAlienFlyer::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) -{ - // ALERT( at_console, "%d %.0f\n", ptr->iHitgroup, flDamage ); - - // ignore blades - if (ptr->iHitgroup == 6 && (bitsDamageType & (DMG_ENERGYBEAM|DMG_BULLET|DMG_CLUB))) - return; - - // hit hard, hits cockpit, hits engines - if (flDamage > 50 || ptr->iHitgroup == 1 || ptr->iHitgroup == 2) - { - // ALERT( at_console, "%.0f\n", flDamage ); - AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); - } - else - { - // do half damage in the body - // AddMultiDamage( pevAttacker, this, flDamage / 2.0, bitsDamageType ); - UTIL_Ricochet( ptr->vecEndPos, 2.0 ); - } -} +/*** +* +* Copyright (c) 2007, Vyacheslav Dzhura +* +****/ + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "effects.h" +#include "trains.h" + +extern DLL_GLOBAL int g_iSkillLevel; + +#define SF_WAITFORTRIGGER (0x04 | 0x40) // UNDONE: Fix +#define SF_NOWRECKAGE 0x08 +#define SF_PATHCORNER_USELASER 8 +#define SF_PATHCORNER_ATTACK 16 + +class CAlienFlyer : public CBaseMonster +{ + int Save( CSave &save ); + int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + int Classify( void ) { return CLASS_ALIEN_MONSTER; }; + int BloodColor( void ) { return DONT_BLEED; } + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + //void (CAlienFlyer::*m_pfnCallWhenMoveDone)(void); + void KeyValue(KeyValueData *pkvd); + + void SetObjectCollisionBox( void ) + { + pev->absmin = pev->origin + Vector( -300, -300, -172); + pev->absmax = pev->origin + Vector(300, 300, 8); + } + + void EXPORT HuntThink( void ); + void EXPORT FlyTouch( CBaseEntity *pOther ); + + void EXPORT CrashTouch( CBaseEntity *pOther ); + void EXPORT DyingThink( void ); + void EXPORT StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT NullThink( void ); + + void ShowDamage( void ); + void Flight( void ); + void UseDamagingBeam( bool bTurnOn ); + void CheckAttack( 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 EXPORT Wait( void ); + //void EXPORT Next( void ); + + void Activate( void ); + + float m_flForce; + int m_iszDeathTarget; + + // from func_train + entvars_t *m_pevCurrentTarget; + BOOL m_activated; + // end of + + Vector m_vecTarget; + Vector m_posTarget; + + Vector m_vecDesired; + Vector m_posDesired; + + Vector m_vecGoal; + + float m_flLastSeen; + float m_flPrevSeen; + float m_flDieCounter; + float m_flLaserCounter; + float m_flAttackCounter; + + //int m_iSoundState; // don't save this + + int m_iSpriteTexture; + int m_iExplode; + int m_iBodyGibs; + + float m_flGoalSpeed; + + CBeam *m_pBeam[2]; +}; +//#define SetMoveDone( a ) m_pfnCallWhenMoveDone = static_cast (a) + +LINK_ENTITY_TO_CLASS( monster_alienflyer, CAlienFlyer ); + +TYPEDESCRIPTION CAlienFlyer::m_SaveData[] = +{ + DEFINE_FIELD( CAlienFlyer, m_pevCurrentTarget, FIELD_EVARS ), + DEFINE_FIELD( CAlienFlyer, m_activated, FIELD_BOOLEAN ), + + DEFINE_FIELD( CAlienFlyer, m_flForce, FIELD_FLOAT ), + DEFINE_FIELD( CAlienFlyer, m_vecTarget, FIELD_VECTOR ), + DEFINE_FIELD( CAlienFlyer, m_posTarget, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CAlienFlyer, m_vecDesired, FIELD_VECTOR ), + DEFINE_FIELD( CAlienFlyer, m_posDesired, FIELD_POSITION_VECTOR ), + DEFINE_FIELD( CAlienFlyer, m_vecGoal, FIELD_VECTOR ), + DEFINE_FIELD( CAlienFlyer, m_flLastSeen, FIELD_TIME ), + DEFINE_FIELD( CAlienFlyer, m_flPrevSeen, FIELD_TIME ), + DEFINE_FIELD( CAlienFlyer, m_flGoalSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CAlienFlyer, m_iszDeathTarget, FIELD_STRING ), + DEFINE_FIELD( CAlienFlyer, m_flLaserCounter, FIELD_FLOAT ), + DEFINE_FIELD( CAlienFlyer, m_flDieCounter, FIELD_FLOAT ), + DEFINE_FIELD( CAlienFlyer, m_flAttackCounter, FIELD_FLOAT ), + DEFINE_ARRAY( CAlienFlyer, m_pBeam, FIELD_CLASSPTR , 2 ), +}; +IMPLEMENT_SAVERESTORE( CAlienFlyer, CBaseMonster ); + +void CAlienFlyer::KeyValue(KeyValueData *pkvd) +{ + if (FStrEq(pkvd->szKeyName, "death_target")) + { + m_iszDeathTarget = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + +void CAlienFlyer :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "models/flyer.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, -64 ), Vector( 32, 32, 0 ) ); + UTIL_SetOrigin( pev, pev->origin ); + + pev->flags |= FL_MONSTER; + pev->takedamage = DAMAGE_AIM; + pev->health = 150*10; // gSkillData.apacheHealth; + + m_flFieldOfView = -0.707; // 270 degrees + + pev->sequence = 0; + ResetSequenceInfo( ); + pev->frame = RANDOM_LONG(0, 0xFF); + + InitBoneControllers(); + + m_pBeam[0] = CBeam::BeamCreate( "sprites/xenobeam.spr", 200 ); + m_pBeam[0]->SetFlags( SF_BEAM_SHADEIN ); + m_pBeam[0]->EntsInit( entindex( ), entindex( ) ); + m_pBeam[0]->SetStartAttachment( 0 ); + m_pBeam[0]->SetEndAttachment( 1 ); + m_pBeam[0]->SetColor( 254, 244, 233 ); + m_pBeam[0]->SetNoise( 3 ); + m_pBeam[0]->SetBrightness( 150 ); + m_pBeam[0]->SetWidth( 255 ); + m_pBeam[0]->SetScrollRate( 40 ); + + m_pBeam[1] = CBeam::BeamCreate( "sprites/laserbeam.spr", 200 ); + m_pBeam[1]->SetFlags( SF_BEAM_SHADEIN ); + m_pBeam[1]->EntsInit( entindex( ), entindex( ) ); + m_pBeam[1]->SetStartAttachment( 0 ); + m_pBeam[1]->SetEndAttachment( 1 ); + m_pBeam[1]->SetColor( 255, 255, 255 ); + m_pBeam[1]->SetNoise( 100 ); + m_pBeam[1]->SetBrightness( 125 ); + m_pBeam[1]->SetWidth( 30 ); + m_pBeam[1]->SetScrollRate( 35 ); + + UseDamagingBeam( false ); + + if (pev->spawnflags & SF_WAITFORTRIGGER) + { + SetUse( &CAlienFlyer::StartupUse ); + m_activated = false; + pev->effects |= EF_NODRAW; + } + else + { + SetThink( &CAlienFlyer::HuntThink ); + SetTouch( &CAlienFlyer::FlyTouch ); + pev->nextthink = gpGlobals->time + 1.0; + m_activated = true; + } +} + +void CAlienFlyer::UseDamagingBeam( bool bTurnOn ) +{ + if ( bTurnOn ) + { + m_pBeam[0]->pev->effects &= ~EF_NODRAW; + m_pBeam[1]->pev->effects &= ~EF_NODRAW; + } else + { + m_pBeam[0]->pev->effects |= EF_NODRAW; + m_pBeam[1]->pev->effects |= EF_NODRAW; + } +} + +void CAlienFlyer::Activate( void ) +{ + // Not yet active, so teleport to first target + if ( !m_activated ) + { + m_activated = TRUE; + entvars_t *pevTarg = VARS( FIND_ENTITY_BY_TARGETNAME (NULL, STRING(pev->target) ) ); + + pev->target = pevTarg->target; + m_pevCurrentTarget = pevTarg;// keep track of this since path corners change our target for us. + + UTIL_SetOrigin (pev, pevTarg->origin - (pev->mins + pev->maxs) * 0.5 ); + + if ( FStringNull(pev->targetname) ) + { // not triggered, so start immediately + pev->nextthink = pev->ltime + 0.1; + SetThink( &CAlienFlyer::HuntThink ); + } + else + pev->spawnflags |= SF_TRAIN_WAIT_RETRIGGER; + } +} + +void CAlienFlyer::Precache( void ) +{ + PRECACHE_MODEL("models/flyer.mdl"); + + m_iSpriteTexture = PRECACHE_MODEL( "sprites/white.spr" ); + PRECACHE_MODEL("sprites/lgtning.spr"); + PRECACHE_MODEL("sprites/xenobeam.spr"); + + m_iExplode = PRECACHE_MODEL( "sprites/fexplo.spr" ); + m_iBodyGibs = PRECACHE_MODEL( "models/flyer_gibs.mdl" ); +} + + + +void CAlienFlyer::NullThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.5; +} + + +void CAlienFlyer::StartupUse( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + pev->effects &= ~EF_NODRAW; + SetThink( &CAlienFlyer::HuntThink ); + SetTouch( &CAlienFlyer::FlyTouch ); + pev->nextthink = gpGlobals->time + 0.1; + SetUse( NULL ); +} + +void CAlienFlyer :: Killed( entvars_t *pevAttacker, int iGib ) +{ + for ( int i = 0; i<2; i++) + { + if (m_pBeam[i]) + { + m_pBeam[i]->SUB_StartFadeOut(); + m_pBeam[i] = NULL; + } + } + + pev->movetype = MOVETYPE_TOSS; + pev->gravity = 0.3; + + UTIL_SetSize( pev, Vector( -32, -32, -64), Vector( 32, 32, 0) ); + SetThink( &CAlienFlyer::DyingThink ); + SetTouch( &CAlienFlyer::CrashTouch ); + pev->nextthink = gpGlobals->time + 0.1; + pev->health = 0; + pev->takedamage = DAMAGE_NO; + + m_flDieCounter = gpGlobals->time + 2.5; + + FireTargets( STRING(m_iszDeathTarget), this, this, USE_TOGGLE, 1.0 ); +} + +void CAlienFlyer :: DyingThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + pev->avelocity = pev->avelocity * 1.02; + + // still falling? + if (m_flDieCounter > gpGlobals->time ) + { + // random explosions + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_EXPLOSION); // This just makes a dynamic light now + WRITE_COORD( pev->origin.x + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.y + RANDOM_FLOAT( -150, 150 )); + WRITE_COORD( pev->origin.z + RANDOM_FLOAT( -150, -50 )); + WRITE_SHORT( g_sModelIndexFireball ); + WRITE_BYTE( RANDOM_LONG(0,29) + 30 ); // scale * 10 + WRITE_BYTE( 12 ); // framerate + WRITE_BYTE( TE_EXPLFLAG_NONE ); + MESSAGE_END(); + + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z ); + + // size + WRITE_COORD( 400 ); + WRITE_COORD( 400 ); + WRITE_COORD( 132 ); + + // velocity + WRITE_COORD( pev->velocity.x ); + WRITE_COORD( pev->velocity.y ); + WRITE_COORD( pev->velocity.z ); + + // randomization + WRITE_BYTE( 25 ); + + // Model + WRITE_SHORT( m_iBodyGibs ); //model id# + + // # of shards + WRITE_BYTE( 1 ); // let client decide + + // duration + WRITE_BYTE( 30 );// 3.0 seconds + + // flags + + WRITE_BYTE( BREAK_METAL ); + MESSAGE_END(); + + // don't stop it we touch a entity + pev->flags &= ~FL_ONGROUND; + pev->nextthink = gpGlobals->time + 0.2; + return; + } + else + // fell + { + Vector vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + + // blast circle + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z); + WRITE_COORD( pev->origin.x); + WRITE_COORD( pev->origin.y); + WRITE_COORD( pev->origin.z + 2000 ); // reach damage radius over .2 seconds + WRITE_SHORT( m_iSpriteTexture ); + WRITE_BYTE( 0 ); // startframe + WRITE_BYTE( 0 ); // framerate + WRITE_BYTE( 4 ); // life + WRITE_BYTE( 32 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 192 ); // r, g, b + WRITE_BYTE( 128 ); // brightness + WRITE_BYTE( 0 ); // speed + MESSAGE_END(); + + EMIT_SOUND(ENT(pev), CHAN_STATIC, "weapons/mortarhit.wav", 1.0, 0.3); + + // Vyacheslav Dzhura: fix pointed by Barnz - damage was 300 + // so when players killed the flyer, flyer killed focusemitter, fun isn't it? :) + RadiusDamage( pev->origin, pev, pev, 0, CLASS_NONE, DMG_BLAST ); + + if (/*!(pev->spawnflags & SF_NOWRECKAGE) && */(pev->flags & FL_ONGROUND)) + { + CBaseEntity *pWreckage = Create( "cycler_wreckage", pev->origin, pev->angles ); + // SET_MODEL( ENT(pWreckage->pev), STRING(pev->model) ); + UTIL_SetSize( pWreckage->pev, Vector( -200, -200, -128 ), Vector( 200, 200, -32 ) ); + pWreckage->pev->frame = pev->frame; + pWreckage->pev->sequence = pev->sequence; + pWreckage->pev->framerate = 0; + pWreckage->pev->dmgtime = gpGlobals->time + 5; + } + + // gibs + vecSpot = pev->origin + (pev->mins + pev->maxs) * 0.5; + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecSpot ); + WRITE_BYTE( TE_BREAKMODEL); + + // position + WRITE_COORD( vecSpot.x ); + WRITE_COORD( vecSpot.y ); + WRITE_COORD( vecSpot.z + 64); + + // size + WRITE_COORD( 400 ); + WRITE_COORD( 400 ); + WRITE_COORD( 128 ); + + // velocity + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + WRITE_COORD( 200 ); + + // randomization + WRITE_BYTE( 30 ); + + // Model + WRITE_SHORT( m_iBodyGibs ); //model id# + + // # of shards + WRITE_BYTE( 100 ); + + // duration + WRITE_BYTE( 200 );// 10.0 seconds + + // flags + WRITE_BYTE( BREAK_FLESH ); + MESSAGE_END(); + + SetThink( &CAlienFlyer::SUB_Remove ); + pev->nextthink = gpGlobals->time + 0.1; + } +} + + +void CAlienFlyer::FlyTouch( CBaseEntity *pOther ) +{ + // bounce if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + + // UNDONE, do a real bounce + pev->velocity = pev->velocity + tr.vecPlaneNormal * (pev->velocity.Length() + 200); + } +} + + +void CAlienFlyer::CrashTouch( CBaseEntity *pOther ) +{ + // only crash if we hit something solid + if ( pOther->pev->solid == SOLID_BSP) + { + SetTouch( NULL ); + pev->nextthink = gpGlobals->time; + } +} + +void CAlienFlyer :: CheckAttack( void ) +{ + if (m_flAttackCounter < gpGlobals->time) + return; + + CBaseEntity *pList[8]; + + Vector delta = Vector( 32, 32, 0 ); + Vector mins = pev->origin - delta; + Vector maxs = pev->origin + delta; + maxs.z = pev->origin.z; + mins.z -= 320; + + int count = UTIL_EntitiesInBox( pList, 8, mins, maxs, FL_MONSTER|FL_CLIENT ); + Vector forward; + + if (count > 1) + ALERT( at_console, "(CAlienFlyer) Found %d victims:\n", count-1 ); + + UTIL_MakeVectorsPrivate( pev->angles, forward, NULL, NULL ); + + for ( int i = 0; i < count; i++ ) + { + if ( pList[i] != this ) + { + if ( pList[i]->pev->owner != edict() ) + { + pList[i]->TakeDamage( pev, pev, 150, DMG_CRUSH | DMG_SLASH ); + ALERT( at_console, "(%s) %s\n", STRING(pList[i]->pev->classname), STRING(pList[i]->pev->targetname) ); + if ( FClassnameIs( pList[i]->pev, "item_focusemitter" )) + m_flAttackCounter = gpGlobals->time - 1.0; // prevent multi-damage 2.5 second attack on focusemitter + + //pList[i]->pev->punchangle.x = 15; + //pList[i]->pev->velocity = pList[i]->pev->velocity + forward * 100; + } + } + } +} + +void CAlienFlyer :: GibMonster( void ) +{ + // EMIT_SOUND_DYN(ENT(pev), CHAN_VOICE, "common/bodysplat.wav", 0.75, ATTN_NORM, 0, 200); +} + +void CAlienFlyer :: HuntThink( void ) +{ + StudioFrameAdvance( ); + pev->nextthink = gpGlobals->time + 0.1; + + ShowDamage( ); + + if ( m_pGoalEnt == NULL && !FStringNull(pev->target) )// this monster has a target + { + m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( pev->target ) ); + if (m_pGoalEnt) + { + m_posDesired = m_pGoalEnt->pev->origin; + UTIL_MakeAimVectors( m_pGoalEnt->pev->angles ); + m_vecGoal = gpGlobals->v_forward; + } + } + + Look( 4092 ); + m_hEnemy = BestVisibleEnemy( ); + + // generic speed up + if (m_flGoalSpeed < 800) + m_flGoalSpeed += 10; // was 5 + + if (m_hEnemy != NULL) + { + // ALERT( at_console, "%s\n", STRING( m_hEnemy->pev->classname ) ); + if (FVisible( m_hEnemy )) + { + if (m_flLastSeen < gpGlobals->time - 5) + m_flPrevSeen = gpGlobals->time; + m_flLastSeen = gpGlobals->time; + m_posTarget = m_hEnemy->Center( ); + } + else + { + m_hEnemy = NULL; + } + } + + m_vecTarget = (m_posTarget - pev->origin).Normalize(); + + float flLength = (pev->origin - m_posDesired).Length(); + + if ( m_flLaserCounter < gpGlobals->time ) + UseDamagingBeam( false ); + + if (m_pGoalEnt) + { + // ALERT( at_console, "%.0f\n", flLength ); + CheckAttack(); + + // we are close to target entity, check next target + // and if targeting entity has target, slowdown + if (flLength < 128) + { + // NEW CODE + // Save last target in case we need to find it again + pev->message = m_pGoalEnt->pev->targetname; + + if ( m_pGoalEnt->pev->spawnflags & SF_PATHCORNER_USELASER ) + { + UseDamagingBeam( true ); + m_flLaserCounter = gpGlobals->time + 2.5; + } + + if ( m_pGoalEnt->pev->spawnflags & SF_PATHCORNER_ATTACK ) + { + m_flAttackCounter = gpGlobals->time + 2.5; + CheckAttack(); + } + + if (m_pGoalEnt->pev->message) + { + m_flGoalSpeed -= 32; + //if (flLength < 32) + { + // fire target's "message" + if ( m_activated == false ) + { + FireTargets( STRING( m_pGoalEnt->pev->message ), this, this, USE_TOGGLE, 1.0 ); + ALERT( at_console, "AlienFlyer used %s\n", STRING( m_pGoalEnt->pev->message )); + m_activated = true; + } + } + } + // END OF NEW CODE + + m_pGoalEnt = UTIL_FindEntityByTargetname( NULL, STRING( m_pGoalEnt->pev->target ) ); + if (m_pGoalEnt) + { + m_posDesired = m_pGoalEnt->pev->origin; + UTIL_MakeAimVectors( m_pGoalEnt->pev->angles ); + m_vecGoal = gpGlobals->v_forward; + flLength = (pev->origin - m_posDesired).Length(); + } + } else + m_activated = false; + } + else + { + m_posDesired = pev->origin; + } + + if (flLength > 250) // 500 + { + if (m_flLastSeen + 90 > gpGlobals->time && DotProduct( (m_posTarget - pev->origin).Normalize(), (m_posDesired - pev->origin).Normalize( )) > 0.25) + { + m_vecDesired = (m_posTarget - pev->origin).Normalize( ); + } + else + { + m_vecDesired = (m_posDesired - pev->origin).Normalize( ); + } + } + else + { + m_vecDesired = m_vecGoal; + } + + Flight( ); + + // ALERT( at_console, "%.0f %.0f %.0f\n", gpGlobals->time, m_flLastSeen, m_flPrevSeen ); + if ((m_flLastSeen + 1 > gpGlobals->time) && (m_flPrevSeen + 2 < gpGlobals->time)) + { + // FireGun was here + + // don't fire rockets and gun on easy mode + //if (g_iSkillLevel == SKILL_EASY) + // m_flNextRocket = gpGlobals->time + 10.0; + } + + UTIL_MakeAimVectors( pev->angles ); + Vector vecEst = (gpGlobals->v_forward * 800 + pev->velocity).Normalize( ); + // ALERT( at_console, "%d %d %d %4.2f\n", pev->angles.x < 0, DotProduct( pev->velocity, gpGlobals->v_forward ) > -100, m_flNextRocket < gpGlobals->time, DotProduct( m_vecTarget, vecEst ) ); + + // rocket firing stuff was here +} + + +void CAlienFlyer :: Flight( void ) +{ + // tilt model 5 degrees + Vector vecAdj = Vector( 5.0, 0, 0 ); + + // estimate where I'll be facing in one seconds + UTIL_MakeAimVectors( pev->angles + pev->avelocity * 2 + vecAdj); + // Vector vecEst1 = pev->origin + pev->velocity + gpGlobals->v_up * m_flForce - Vector( 0, 0, 384 ); + // float flSide = DotProduct( m_posDesired - vecEst1, gpGlobals->v_right ); + + float flSide = DotProduct( m_vecDesired, gpGlobals->v_right ); + + if (flSide < 0) + { + if (pev->avelocity.y < 60) + { + pev->avelocity.y += 8; // 9 * (3.0/2.0); + } + } + else + { + if (pev->avelocity.y > -60) + { + pev->avelocity.y -= 8; // 9 * (3.0/2.0); + } + } + pev->avelocity.y *= 0.98; + + // estimate where I'll be in two seconds + UTIL_MakeAimVectors( pev->angles + pev->avelocity * 1 + vecAdj); + Vector vecEst = pev->origin + pev->velocity * 2.0 + gpGlobals->v_up * m_flForce * 20 - Vector( 0, 0, 384 * 2 ); + + // add immediate force + UTIL_MakeAimVectors( pev->angles + vecAdj); + pev->velocity.x += gpGlobals->v_up.x * m_flForce; + pev->velocity.y += gpGlobals->v_up.y * m_flForce; + pev->velocity.z += gpGlobals->v_up.z * m_flForce; + // add gravity + pev->velocity.z -= 38.4; // 32ft/sec + + + float flSpeed = pev->velocity.Length(); + float flDir = DotProduct( Vector( gpGlobals->v_forward.x, gpGlobals->v_forward.y, 0 ), Vector( pev->velocity.x, pev->velocity.y, 0 ) ); + if (flDir < 0) + flSpeed = -flSpeed; + + float flDist = DotProduct( m_posDesired - vecEst, gpGlobals->v_forward ); + + // float flSlip = DotProduct( pev->velocity, gpGlobals->v_right ); + float flSlip = -DotProduct( m_posDesired - vecEst, gpGlobals->v_right ); + + // fly sideways + if (flSlip > 0) + { + if (pev->angles.z > -30 && pev->avelocity.z > -15) + pev->avelocity.z -= 4; + else + pev->avelocity.z += 2; + } + else + { + + if (pev->angles.z < 30 && pev->avelocity.z < 15) + pev->avelocity.z += 4; + else + pev->avelocity.z -= 2; + } + + // sideways drag + pev->velocity.x = pev->velocity.x * (1.0 - fabs( gpGlobals->v_right.x ) * 0.05); + pev->velocity.y = pev->velocity.y * (1.0 - fabs( gpGlobals->v_right.y ) * 0.05); + pev->velocity.z = pev->velocity.z * (1.0 - fabs( gpGlobals->v_right.z ) * 0.05); + + // general drag + pev->velocity = pev->velocity * 0.995; + + // apply power to stay correct height + if (m_flForce < 80 && vecEst.z < m_posDesired.z) + { + m_flForce += 12; + } + else if (m_flForce > 30) + { + if (vecEst.z > m_posDesired.z) + m_flForce -= 8; + } + + // pitch forward or back to get to target + if (flDist > 0 && flSpeed < m_flGoalSpeed && pev->angles.x + pev->avelocity.x > -40) + { + // ALERT( at_console, "F " ); + // lean forward + pev->avelocity.x -= 24; //12.0; + } + else if (flDist < 0 && flSpeed > -50 && pev->angles.x + pev->avelocity.x < 20) + { + // ALERT( at_console, "B " ); + // lean backward + pev->avelocity.x += 12.0; + } + else if (pev->angles.x + pev->avelocity.x > 0) + { + // ALERT( at_console, "f " ); + pev->avelocity.x -= 4.0; + } + else if (pev->angles.x + pev->avelocity.x < 0) + { + // ALERT( at_console, "b " ); + pev->avelocity.x += 4.0; + } + + // ALERT( at_console, "%.0f %.0f : %.0f %.0f : %.0f %.0f : %.0f\n", pev->origin.x, pev->velocity.x, flDist, flSpeed, pev->angles.x, pev->avelocity.x, m_flForce ); + // ALERT( at_console, "%.0f %.0f : %.0f %0.f : %.0f\n", pev->origin.z, pev->velocity.z, vecEst.z, m_posDesired.z, m_flForce ); + + // rotor sounds were here +} + +void CAlienFlyer :: ShowDamage( void ) +{ + // smoke was here +} + +int CAlienFlyer :: TakeDamage( entvars_t* pevInflictor, entvars_t* pevAttacker, float flDamage, int bitsDamageType ) +{ + if (pevInflictor->owner == edict()) + return 0; + + if (bitsDamageType & DMG_BLAST) + { + flDamage *= 2; + } + + /* + if ( (bitsDamageType & DMG_BULLET) && flDamage > 50) + { + // clip bullet damage at 50 + flDamage = 50; + } + */ + + // ALERT( at_console, "%.0f\n", flDamage ); + return CBaseEntity::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + +void CAlienFlyer::TraceAttack( entvars_t *pevAttacker, float flDamage, Vector vecDir, TraceResult *ptr, int bitsDamageType) +{ + // ALERT( at_console, "%d %.0f\n", ptr->iHitgroup, flDamage ); + + // ignore blades + if (ptr->iHitgroup == 6 && (bitsDamageType & (DMG_ENERGYBEAM|DMG_BULLET|DMG_CLUB))) + return; + + // hit hard, hits cockpit, hits engines + if (flDamage > 50 || ptr->iHitgroup == 1 || ptr->iHitgroup == 2) + { + // ALERT( at_console, "%.0f\n", flDamage ); + AddMultiDamage( pevAttacker, this, flDamage, bitsDamageType ); + } + else + { + // do half damage in the body + // AddMultiDamage( pevAttacker, this, flDamage / 2.0, bitsDamageType ); + UTIL_Ricochet( ptr->vecEndPos, 2.0 ); + } +} diff --git a/dlls/bot.cpp b/dlls/bot.cpp index e970ec9f..b0632890 100644 --- a/dlls/bot.cpp +++ b/dlls/bot.cpp @@ -1,2148 +1,2148 @@ -// botman's Half-Life bot example -// -// http://planethalflife.com/botman/ -// -// bot.cpp -// - -#include "extdll.h" -#include "util.h" -#include "client.h" -#include "cbase.h" -#include "player.h" -#include "items.h" -#include "effects.h" -#include "weapons.h" -#include "soundent.h" -#include "gamerules.h" -#include "animation.h" - -#include "bot.h" - -#include -#include - -extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; - -// Set in combat.cpp. Used to pass the damage inflictor for death messages. -extern entvars_t *g_pevLastInflictor; - -extern DLL_GLOBAL BOOL g_fGameOver; -extern int gmsgHealth; -extern int gmsgCurWeapon; -extern int gmsgSetFOV; - -#define PLAYER_SEARCH_RADIUS (float)40 - -int f_Observer = 0; // flag to indicate if player is in observer mode -int f_botskill = 3; // default bot skill level -int f_botdontshoot = 0; - -// array of bot respawn information -respawn_t bot_respawn[32] = { - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, - {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}}; - -#define MAX_SKINS 2 - -// indicate which models are currently used for random model allocation -BOOL skin_used[MAX_SKINS] = { - FALSE }; - -// store the names of the models... -const char *bot_skins[MAX_SKINS] = { - "ginacol", "player/dm_slave/dm_slave" }; - -// store the player names for each of the models... -const char *bot_names[MAX_SKINS] = { - "Colette", "R-4913" }; - -// sounds for TakeDamage speaking effects... -char gina_sounds[][30] = { GI_SND1, GI_SND2, GI_SND3, GI_SND4, GI_SND5 }; -char colette_sounds[][30] = { CO_SND1, CO_SND2, CO_SND3, CO_SND4, CO_SND5 }; - -// how often (out of 1000 times) the bot will pause, based on bot skill -float pause_frequency[5] = {4, 7, 10, 15, 20}; - -// how long the bot will delay when paused, based on bot skill -float pause_time[5][2] = { - {0.2, 0.5}, {0.5, 1.0}, {0.7, 1.3}, {1.0, 1.7}, {1.2, 2.0}}; - -extern ammo_check_t ammo_check[]; - -LINK_ENTITY_TO_CLASS( bot, CBot ); - - -inline edict_t *CREATE_FAKE_CLIENT( const char *netname ) -{ - return (*g_engfuncs.pfnCreateFakeClient)( netname ); -} - -inline char *GET_INFOBUFFER( edict_t *e ) -{ - return (*g_engfuncs.pfnGetInfoKeyBuffer)( e ); -} - -inline char *GET_INFO_KEY_VALUE( char *infobuffer, const char *key ) -{ - return (g_engfuncs.pfnInfoKeyValue( infobuffer, key )); -} - -inline void SET_CLIENT_KEY_VALUE( int clientIndex, char *infobuffer, - const char *key, const char *value ) -{ - (*g_engfuncs.pfnSetClientKeyValue)( clientIndex, infobuffer, key, value ); -} - - -void BotDebug( char *buffer ) -{ - // print out debug messages to the HUD of all players - // this allows you to print messages from bots to your display - // as you are playing the game. Use STRING(pev->netname) in - // buffer to see the name of the bot. - - UTIL_ClientPrintAll( HUD_PRINTNOTIFY, buffer ); -} - - -void BotCreate(const char *skin, const char *name, const char *skill) -{ - edict_t *BotEnt; - CBot *BotClass; - int skill_level; - char c_skill[2]; - char c_skin[BOT_SKIN_LEN+1]; - char c_name[BOT_NAME_LEN+1]; - char c_index[3]; - int i, j, length; - int index; // index into array of predefined names - BOOL found = FALSE; - - if ((skin == NULL) || (*skin == 0)) - { - // pick a random skin - index = RANDOM_LONG(0, 9); // there are ten possible skins - - // check if this skin has already been used... - while (skin_used[index] == TRUE) - { - index++; - - if (index == MAX_SKINS) - index = 0; - } - - skin_used[index] = TRUE; - - // check if all skins are used... - for (i = 0; i < MAX_SKINS; i++) - { - if (skin_used[i] == FALSE) - break; - } - - // if all skins are used, reset used to FALSE for next selection - if (i == MAX_SKINS) - { - for (i = 0; i < MAX_SKINS; i++) - skin_used[i] = FALSE; - } - - strcpy( c_skin, bot_skins[index] ); - } - else - { - strncpy( c_skin, skin, BOT_SKIN_LEN); - c_skin[BOT_SKIN_LEN] = 0; // make sure c_skin is null terminated - } - - for (i = 0; c_skin[i] != 0; i++) - c_skin[i] = tolower( c_skin[i] ); // convert to all lowercase - - index = 0; - - while ((!found) && (index < MAX_SKINS)) - { - if (strcmp(c_skin, bot_skins[index]) == 0) - found = TRUE; - else - index++; - } - - if (found == TRUE) - { - if ((name != NULL) && (*name != 0)) - { - strncpy( c_name, name, 31 ); - c_name[31] = 0; // make sure c_name is null terminated - } - else - { - strcpy( c_name, bot_names[index] ); - } - } - else - { - char dir_name[32]; - char filename[128]; - - struct stat stat_str; - - GET_GAME_DIR(dir_name); - - sprintf(filename, "%s\\models\\player\\%s", dir_name, c_skin); - - if (stat(filename, &stat_str) != 0) - { - sprintf(filename, "valve\\models\\player\\%s", c_skin); - if (stat(filename, &stat_str) != 0) - { - char err_msg[80]; - - sprintf( err_msg, "model \"%s\" is unknown.\n", c_skin ); - UTIL_ClientPrintAll( HUD_PRINTNOTIFY, err_msg ); - if (IS_DEDICATED_SERVER()) - printf(err_msg); - - UTIL_ClientPrintAll( HUD_PRINTNOTIFY, - "use barney, gina, gman, gordon, helmet, hgrunt,\n"); - if (IS_DEDICATED_SERVER()) - printf("use barney, gina, gman, gordon, helmet, hgrunt,\n"); - UTIL_ClientPrintAll( HUD_PRINTNOTIFY, - " recon, robo, scientist, or zombie\n"); - if (IS_DEDICATED_SERVER()) - printf(" recon, robo, scientist, or zombie\n"); - return; - } - } - - // copy the name of the model to the bot's name... - strncpy( c_name, skin, BOT_SKIN_LEN); - c_name[BOT_SKIN_LEN] = 0; // make sure c_skin is null terminated - } - - if ((name != NULL) && (*name != 0)) - { - strncpy( c_name, name, 31 ); - c_name[31] = 0; // make sure c_name is null terminated - } - - /*length = strlen(c_name); - - // remove any illegal characters from name... - for (i = 0; i < length; i++) - { - if ((c_name[i] <= ' ') || (c_name[i] > '~') || - (c_name[i] == '"')) - { - for (j = i; j < length; j++) // shuffle chars left (and null) - c_name[j] = c_name[j+1]; - length--; - } - }*/ - - skill_level = 0; - - if ((skill != NULL) && (*skill != 0)) - sscanf( skill, "%d", &skill_level ); - else - skill_level = f_botskill; - - if ((skill_level < 1) || (skill_level > 5)) - skill_level = f_botskill; - - sprintf( c_skill, "%d", skill_level ); - - BotEnt = CREATE_FAKE_CLIENT( c_name ); - - if (FNullEnt( BotEnt )) - { - UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "Max. Players reached. Can't create bot!\n"); - - if (IS_DEDICATED_SERVER()) - printf("Max. Players reached. Can't create bot!\n"); - } - else - { - char ptr[128]; // allocate space for message from ClientConnect - char *infobuffer; - int clientIndex; - - index = 0; - while ((bot_respawn[index].is_used) && (index < 32)) - index++; - - if (index >= 32) - { - UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "Can't create bot!\n"); - return; - } - - sprintf(c_index, "%d", index); - - bot_respawn[index].is_used = TRUE; // this slot is used - - // don't store the name here, it might change if same as another - strcpy(bot_respawn[index].skin, c_skin); - strcpy(bot_respawn[index].skill, c_skill); - - sprintf(ptr, "Creating bot \"%s\" using model %s with skill=%d\n", c_name, c_skin, skill_level); - UTIL_ClientPrintAll( HUD_PRINTNOTIFY, ptr); - - if (IS_DEDICATED_SERVER()) - printf("%s", ptr); - - BotClass = GetClassPtr( (CBot *) VARS(BotEnt) ); - infobuffer = GET_INFOBUFFER( BotClass->edict( ) ); - clientIndex = BotClass->entindex( ); - - SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "model", c_skin ); - SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "skill", c_skill ); - SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "index", c_index ); - - ClientConnect( BotClass->edict( ), c_name, "127.0.0.1", ptr ); - DispatchSpawn( BotClass->edict( ) ); - } -} - - -void CBot::Spawn( ) -{ - char c_skill[2]; - char c_index[3]; - - CBasePlayer::Spawn(); - - pev->flags = FL_CLIENT | FL_FAKECLIENT; - - // set the respawn index value based on key from BotCreate - strcpy(c_index, GET_INFO_KEY_VALUE(GET_INFOBUFFER(edict( )), "index") ); - sscanf(c_index, "%d", &respawn_index); - - bot_respawn[respawn_index].pBot = (CBasePlayer *)this; - - // get the bot's name and save it in respawn array... - strcpy(bot_respawn[respawn_index].name, STRING(pev->netname)); - - bot_respawn[respawn_index].state = BOT_IDLE; - - pev->ideal_yaw = pev->v_angle.y; - pev->yaw_speed = BOT_YAW_SPEED; - - // bot starts out in "paused" state since it hasn't moved yet... - bot_was_paused = TRUE; - v_prev_origin = pev->origin; - - f_shoot_time = 0; - - // get bot's skill level (0=good, 4=bad) - strcpy(c_skill, GET_INFO_KEY_VALUE(GET_INFOBUFFER(edict( )), "skill") ); - sscanf(c_skill, "%d", &bot_skill); - bot_skill--; // make 0 based for array index (now 0-4) - - f_max_speed = CVAR_GET_FLOAT("sv_maxspeed"); - - f_speed_check_time = gpGlobals->time + 2.0; - - ladder_dir = 0; - - // pick a wander direction (50% of the time to the left, 50% to the right) - if (RANDOM_LONG(1, 100) <= 50) - wander_dir = WANDER_LEFT; - else - wander_dir = WANDER_RIGHT; - - f_pause_time = 0; - f_find_item = 0; - - if (g_pGameRules->IsTeamplay()) // is team play enabled? - { - strcpy(model_name, g_pGameRules->GetTeamID(this)); - } - else - { - strcpy(model_name, GET_INFO_KEY_VALUE(GET_INFOBUFFER(edict( )), "model") ); - } - - bot_model = 0; - if ((strcmp( model_name, "ginacol" ) == 0) || - (strcmp( model_name, "player" ) == 0)) - { - // Vyacheslav Dzhura: TODO somehow need to know DecayId in here - if ( m_iDecayId == 1) - bot_model = MODEL_GINA; - else - bot_model = MODEL_COLETTE; - } - else if (strcmp( model_name, "dm_gina") == 0) - { - bot_model = MODEL_GINA; - } - else if (strcmp( model_name, "dm_colette") == 0) - { - bot_model = MODEL_COLETTE; - } - - f_pain_time = gpGlobals->time + 5.0; - - b_use_health_station = FALSE; - b_use_HEV_station = FALSE; - b_use_button = FALSE; - f_use_button_time = 0; - b_lift_moving = FALSE; - f_use_ladder_time = 0; - f_fire_gauss = -1; // -1 means not charging gauss gun - - b_see_tripmine = FALSE; - b_shoot_tripmine = FALSE; - - f_weapon_inventory_time = 0; - f_dont_avoid_wall_time = 0; - f_bot_use_time = 0; - f_wall_on_right = 0; - f_wall_on_left = 0; - - pBotEnemy = NULL; - pBotEnemyOffset = 0; - pBotUser = NULL; - pBotPickupItem = NULL; -} - - -int CBot::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, - float flDamage, int bitsDamageType ) -{ - CBaseEntity *pAttacker = CBaseEntity::Instance(pevAttacker); - char sound[40]; - int ret_damage; - - // do the damage first... - ret_damage = CBasePlayer::TakeDamage( pevInflictor, pevAttacker, flDamage, - bitsDamageType ); - - // if the bot doesn't have an enemy and someone is shooting at it then - // turn in the attacker's direction... - if (pBotEnemy == NULL) - { - // face the attacker... - Vector v_enemy = pAttacker->pev->origin - pev->origin; - Vector bot_angles = UTIL_VecToAngles( v_enemy ); - - pev->ideal_yaw = bot_angles.y; - - // check for wrap around of angle... - if (pev->ideal_yaw > 180) - pev->ideal_yaw -= 360; - if (pev->ideal_yaw < -180) - pev->ideal_yaw += 360; - - // stop using health or HEV stations... - b_use_health_station = FALSE; - b_use_HEV_station = FALSE; - } - - // check if bot model is known, attacker is not a bot, - // time for pain sound, and bot has some of health left... - - if ( - // Vyacheslav Dzhura: commented out for Decay - // (bot_model != 0) && (pAttacker->IsNetClient()) && - (f_pain_time <= gpGlobals->time) && (pev->health > 0) && - ( !IS_DEDICATED_SERVER() ) - ) - { - float distance = (pAttacker->pev->origin - pev->origin).Length( ); - - // check if the distance to attacker is close enough (otherwise - // the attacker is too far away to hear the pain sounds) - - if (distance <= 400) - { - // speak pain sounds about 50% of the time - if (RANDOM_LONG(1, 100) <= 50) - { - f_pain_time = gpGlobals->time + 5.0; - - // Vyacheslav Dzhura TODO: put Gina\Colette sounds here - if (bot_model == MODEL_GINA) - strcpy( sound, gina_sounds[RANDOM_LONG(0,4)] ); - else if (bot_model == MODEL_COLETTE) - strcpy( sound, colette_sounds[RANDOM_LONG(0,4)] ); - - EMIT_SOUND(ENT(pevAttacker), CHAN_VOICE, sound, - RANDOM_FLOAT(0.9, 1.0), ATTN_NORM); - } - } - } - - return ret_damage; -} - - -void CBot::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, - USE_TYPE useType, float value ) -{ - if (g_pGameRules->IsTeamplay()) // is team play enabled? - { - // check the bot and player are on the same team... - - if (UTIL_TeamsMatch(g_pGameRules->GetTeamID(this), - g_pGameRules->GetTeamID(pActivator))) - { - if (pBotEnemy == NULL) // is bot NOT currently engaged in combat? - { - if (pBotUser == NULL) // does bot NOT have a "user" - { - // tell teammate that bot will cover them... - - EMIT_SOUND(ENT(pActivator->pev), CHAN_VOICE, USE_TEAMPLAY_SND, - 1.0, ATTN_NORM); - - pBotUser = pActivator; - f_bot_use_time = gpGlobals->time; - } - else - { - // tell teammate that you'll see them later.. - - EMIT_SOUND(ENT(pActivator->pev), CHAN_VOICE, USE_TEAMPLAY_LATER_SND, - 1.0, ATTN_NORM); - - pBotUser = NULL; - f_bot_use_time = 0; - } - } - else - { - EMIT_SOUND(ENT(pActivator->pev), CHAN_VOICE, USE_TEAMPLAY_ENEMY_SND, 1.0, - ATTN_NORM); - } - } - } -} - - -int CBot::BotInFieldOfView(Vector dest) -{ - // find angles from source to destination... - Vector entity_angles = UTIL_VecToAngles( dest ); - - // make yaw angle 0 to 360 degrees if negative... - if (entity_angles.y < 0) - entity_angles.y += 360; - - // get bot's current view angle... - float view_angle = pev->v_angle.y; - - // make view angle 0 to 360 degrees if negative... - if (view_angle < 0) - view_angle += 360; - - // return the absolute value of angle to destination entity - // zero degrees means straight ahead, 45 degrees to the left or - // 45 degrees to the right is the limit of the normal view angle - - return abs((int)view_angle - (int)entity_angles.y); -} - - -BOOL CBot::BotEntityIsVisible( Vector dest ) -{ - TraceResult tr; - - // trace a line from bot's eyes to destination... - UTIL_TraceLine( pev->origin + pev->view_ofs, dest, ignore_monsters, - ENT(pev), &tr ); - - // check if line of sight to object is not blocked (i.e. visible) - if (tr.flFraction >= 1.0) - return TRUE; - else - return FALSE; -} - - -float CBot::BotChangeYaw( float speed ) -{ - float ideal; - float current; - float current_180; // current +/- 180 degrees - float diff; - - // turn from the current v_angle yaw to the ideal_yaw by selecting - // the quickest way to turn to face that direction - - current = pev->v_angle.y; - ideal = pev->ideal_yaw; - - // find the difference in the current and ideal angle - diff = abs(current - ideal); - - // check if the bot is already facing the ideal_yaw direction... - if (diff <= 1) - return diff; // return number of degrees turned - - // check if difference is less than the max degrees per turn - if (diff < speed) - speed = diff; // just need to turn a little bit (less than max) - - // here we have four cases, both angle positive, one positive and - // the other negative, one negative and the other positive, or - // both negative. handle each case separately... - - if ((current >= 0) && (ideal >= 0)) // both positive - { - if (current > ideal) - current -= speed; - else - current += speed; - } - else if ((current >= 0) && (ideal < 0)) - { - current_180 = current - 180; - - if (current_180 > ideal) - current += speed; - else - current -= speed; - } - else if ((current < 0) && (ideal >= 0)) - { - current_180 = current + 180; - if (current_180 > ideal) - current += speed; - else - current -= speed; - } - else // (current < 0) && (ideal < 0) both negative - { - if (current > ideal) - current -= speed; - else - current += speed; - } - - // check for wrap around of angle... - if (current > 180) - current -= 360; - if (current < -180) - current += 360; - - pev->v_angle.y = current; - - pev->angles.x = 0; - pev->angles.y = pev->v_angle.y; - pev->angles.z = 0; - - return speed; // return number of degrees turned -} - - -void CBot::BotOnLadder( float moved_distance ) -{ - // moves the bot up or down a ladder. if the bot can't move - // (i.e. get's stuck with someone else on ladder), the bot will - // change directions and go the other way on the ladder. - - if (ladder_dir == LADDER_UP) // is the bot currently going up? - { - pev->v_angle.x = -60; // look upwards - - // check if the bot hasn't moved much since the last location... - if (moved_distance <= 1) - { - // the bot must be stuck, change directions... - - pev->v_angle.x = 60; // look downwards - ladder_dir = LADDER_DOWN; - } - } - else if (ladder_dir == LADDER_DOWN) // is the bot currently going down? - { - pev->v_angle.x = 60; // look downwards - - // check if the bot hasn't moved much since the last location... - if (moved_distance <= 1) - { - // the bot must be stuck, change directions... - - pev->v_angle.x = -60; // look upwards - ladder_dir = LADDER_UP; - } - } - else // the bot hasn't picked a direction yet, try going up... - { - pev->v_angle.x = -60; // look upwards - ladder_dir = LADDER_UP; - } - - // move forward (i.e. in the direction the bot is looking, up or down) - pev->button |= IN_FORWARD; -} - - -void CBot::BotUnderWater( void ) -{ - // handle movements under water. right now, just try to keep from - // drowning by swimming up towards the surface and look to see if - // there is a surface the bot can jump up onto to get out of the - // water. bots DON'T like water! - - Vector v_src, v_forward; - TraceResult tr; - int contents; - - // swim up towards the surface - pev->v_angle.x = -60; // look upwards - - // move forward (i.e. in the direction the bot is looking, up or down) - pev->button |= IN_FORWARD; - - // set gpGlobals angles based on current view angle (for TraceLine) - UTIL_MakeVectors( pev->v_angle ); - - // look from eye position straight forward (remember: the bot is looking - // upwards at a 60 degree angle so TraceLine will go out and up... - - v_src = pev->origin + pev->view_ofs; // EyePosition() - v_forward = v_src + gpGlobals->v_forward * 90; - - // trace from the bot's eyes straight forward... - UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), &tr); - - // check if the trace didn't hit anything (i.e. nothing in the way)... - if (tr.flFraction >= 1.0) - { - // find out what the contents is of the end of the trace... - contents = UTIL_PointContents( tr.vecEndPos ); - - // check if the trace endpoint is in open space... - if (contents == CONTENTS_EMPTY) - { - // ok so far, we are at the surface of the water, continue... - - v_src = tr.vecEndPos; - v_forward = v_src; - v_forward.z -= 90; - - // trace from the previous end point straight down... - UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, - ENT(pev), &tr); - - // check if the trace hit something... - if (tr.flFraction < 1.0) - { - contents = UTIL_PointContents( tr.vecEndPos ); - - // if contents isn't water then assume it's land, jump! - if (contents != CONTENTS_WATER) - { - pev->button |= IN_JUMP; - } - } - } - } -} - - -void CBot::BotFindItem( void ) -{ - CBaseEntity *pEntity = NULL; - CBaseEntity *pPickupEntity = NULL; - Vector pickup_origin; - Vector entity_origin; - float radius = 500; - BOOL can_pickup; - float min_distance; - char item_name[40]; - TraceResult tr; - Vector vecStart; - Vector vecEnd; - int angle_to_entity; - - pBotPickupItem = NULL; - - min_distance = radius + 1.0; - - while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, radius )) != NULL) - { - can_pickup = FALSE; // assume can't use it until known otherwise - - strcpy(item_name, STRING(pEntity->pev->classname)); - - // see if this is a "func_" type of entity (func_button, etc.)... - if (strncmp("func_", item_name, 5) == 0) - { - // BModels have 0,0,0 for origin so must use VecBModelOrigin... - entity_origin = VecBModelOrigin(pEntity->pev); - - vecStart = pev->origin + pev->view_ofs; - vecEnd = entity_origin; - - angle_to_entity = BotInFieldOfView( vecEnd - vecStart ); - - // check if entity is outside field of view (+/- 45 degrees) - if (angle_to_entity > 45) - continue; // skip this item if bot can't "see" it - - // check if entity is a ladder (ladders are a special case)... - if (strcmp("func_ladder", item_name) == 0) - { - // force ladder origin to same z coordinate as bot since - // the VecBModelOrigin is the center of the ladder. For - // LONG ladders, the center MAY be hundreds of units above - // the bot. Fake an origin at the same level as the bot... - - entity_origin.z = pev->origin.z; - vecEnd = entity_origin; - - // trace a line from bot's eyes to func_ladder entity... - UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, ENT(pev), &tr); - - // check if traced all the way up to the entity (didn't hit wall) - if (tr.flFraction >= 1.0) - { - // find distance to item for later use... - float distance = (vecEnd - vecStart).Length( ); - - // use the ladder about 100% of the time, if haven't - // used a ladder in at least 5 seconds... - if ((RANDOM_LONG(1, 100) <= 100) && - ((f_use_ladder_time + 5) < gpGlobals->time)) - { - // if close to ladder... - if (distance < 100) - { - // don't avoid walls for a while - f_dont_avoid_wall_time = gpGlobals->time + 5.0; - } - - can_pickup = TRUE; - } - } - } - else - { - // trace a line from bot's eyes to func_ entity... - UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, ENT(pev), &tr); - - // check if traced all the way up to the entity (didn't hit wall) - if (strcmp(item_name, STRING(tr.pHit->v.classname)) == 0) - { - // find distance to item for later use... - float distance = (vecEnd - vecStart).Length( ); - - // check if entity is wall mounted health charger... - if ( strcmp("func_healthcharger", item_name) == 0 || - strcmp("item_healthcharger", item_name) == 0 ) - { - // check if the bot can use this item and - // check if the recharger is ready to use (has power left)... - if ((pev->health < 100) && (pEntity->pev->frame == 0)) - { - // check if flag not set... - if (!b_use_health_station) - { - // check if close enough and facing it directly... - if ((distance < PLAYER_SEARCH_RADIUS) && - (angle_to_entity <= 10)) - { - b_use_health_station = TRUE; - f_use_health_time = gpGlobals->time; - } - - // if close to health station... - if (distance < 100) - { - // don't avoid walls for a while - f_dont_avoid_wall_time = gpGlobals->time + 5.0; - } - - can_pickup = TRUE; - } - } - else - { - // don't need or can't use this item... - b_use_health_station = FALSE; - } - } - - // check if entity is wall mounted HEV charger... - else if ( strcmp("func_recharge", item_name) == 0 || - strcmp("item_recharge", item_name) == 0 ) - { - // check if the bot can use this item and - // check if the recharger is ready to use (has power left)... - if ((pev->armorvalue < MAX_NORMAL_BATTERY) && - (pev->weapons & (1<pev->frame == 0)) - { - // check if flag not set and facing it... - if (!b_use_HEV_station) - { - // check if close enough and facing it directly... - if ((distance < PLAYER_SEARCH_RADIUS) && - (angle_to_entity <= 10)) - { - b_use_HEV_station = TRUE; - f_use_HEV_time = gpGlobals->time; - } - - // if close to HEV recharger... - if (distance < 100) - { - // don't avoid walls for a while - f_dont_avoid_wall_time = gpGlobals->time + 5.0; - } - - can_pickup = TRUE; - } - } - else - { - // don't need or can't use this item... - b_use_HEV_station = FALSE; - } - } - - // check if entity is a button... - else if (strcmp("func_button", item_name) == 0) - { - // use the button about 100% of the time, if haven't - // used a button in at least 5 seconds... - if ((RANDOM_LONG(1, 100) <= 100) && - ((f_use_button_time + 5) < gpGlobals->time)) - { - // check if flag not set and facing it... - if (!b_use_button) - { - // check if close enough and facing it directly... - if ((distance < PLAYER_SEARCH_RADIUS) && - (angle_to_entity <= 10)) - { - b_use_button = TRUE; - b_lift_moving = FALSE; - f_use_button_time = gpGlobals->time; - } - - // if close to button... - if (distance < 100) - { - // don't avoid walls for a while - f_dont_avoid_wall_time = gpGlobals->time + 5.0; - } - - can_pickup = TRUE; - } - } - else - { - // don't need or can't use this item... - b_use_button = FALSE; - } - } - } - } - } - - // check if entity is an armed tripmine beam - else if (strcmp("beam", item_name) == 0) - { - CBeam *pBeam = (CBeam *)pEntity; - -// Vector v_beam_start = pBeam->GetStartPos(); -// Vector v_beam_end = pBeam->GetEndPos(); -// -// if (FInViewCone( &v_beam_start ) && FVisible( v_beam_start )) -// { -// BotDebug("I see a beam start!\n"); -// } -// -// if (FInViewCone( &v_beam_end ) && FVisible( v_beam_end )) -// { -// BotDebug("I see a beam end!\n"); -// } - } - - else // everything else... - { - entity_origin = pEntity->pev->origin; - - vecStart = pev->origin + pev->view_ofs; - vecEnd = entity_origin; - - // find angles from bot origin to entity... - angle_to_entity = BotInFieldOfView( vecEnd - vecStart ); - - // check if entity is outside field of view (+/- 45 degrees) - if (angle_to_entity > 45) - continue; // skip this item if bot can't "see" it - - // check if line of sight to object is not blocked (i.e. visible) - if (BotEntityIsVisible( vecEnd )) - { - - // check if entity is a weapon... - if (strncmp("weapon_", item_name, 7) == 0) - { - CBasePlayerWeapon *pWeapon = (CBasePlayerWeapon *)pEntity; - - if ((pWeapon->m_pPlayer) || (pWeapon->pev->effects & EF_NODRAW)) - { - // someone owns this weapon or it hasn't respawned yet - continue; - } - - if (g_pGameRules->CanHavePlayerItem( this, pWeapon )) - { - can_pickup = TRUE; - } - } - - // check if entity is ammo... - else if (strncmp("ammo_", item_name, 5) == 0) - { - CBasePlayerAmmo *pAmmo = (CBasePlayerAmmo *)pEntity; - BOOL ammo_found = FALSE; - int i; - - // check if the item is not visible (i.e. has not respawned) - if (pAmmo->pev->effects & EF_NODRAW) - continue; - - i = 0; - while (ammo_check[i].ammo_name[0]) - { - if (strcmp(ammo_check[i].ammo_name, item_name) == 0) - { - ammo_found = TRUE; - - // see if the bot can pick up this item... - if (g_pGameRules->CanHaveAmmo( this, - ammo_check[i].weapon_name, ammo_check[i].max_carry)) - { - can_pickup = TRUE; - break; - } - } - - i++; - } - if (ammo_found == FALSE) - { - sprintf(message, "unknown ammo: %s\n", item_name); - BotDebug(message); - } - } - - // check if entity is a battery... - else if (strcmp("item_battery", item_name) == 0) - { - CItem *pBattery = (CItem *)pEntity; - - // check if the item is not visible (i.e. has not respawned) - if (pBattery->pev->effects & EF_NODRAW) - continue; - - // check if the bot can use this item... - if ((pev->armorvalue < MAX_NORMAL_BATTERY) && - (pev->weapons & (1<pev->effects & EF_NODRAW) - continue; - - // check if the bot can use this item... - if (pev->health < 100) - { - can_pickup = TRUE; - } - } - - // check if entity is a packed up weapons box... - else if (strcmp("weaponbox", item_name) == 0) - { - can_pickup = TRUE; - } - - // check if entity is the spot from RPG laser - else if (strcmp("laser_spot", item_name) == 0) - { - } - - // check if entity is an armed tripmine - else if (strcmp("monster_tripmine", item_name) == 0) - { - float distance = (pEntity->pev->origin - pev->origin).Length( ); - - if (b_see_tripmine) - { - // see if this tripmine is closer to bot... - if (distance < (v_tripmine_origin - pev->origin).Length()) - { - v_tripmine_origin = pEntity->pev->origin; - b_shoot_tripmine = FALSE; - - // see if bot is far enough to shoot the tripmine... - if (distance >= 375) - { - b_shoot_tripmine = TRUE; - } - } - } - else - { - b_see_tripmine = TRUE; - v_tripmine_origin = pEntity->pev->origin; - b_shoot_tripmine = FALSE; - - // see if bot is far enough to shoot the tripmine... - if (distance >= 375) // 375 is damage radius - { - b_shoot_tripmine = TRUE; - } - } - } - - // check if entity is an armed satchel charge - else if (strcmp("monster_satchel", item_name) == 0) - { - } - - // check if entity is a snark (squeak grenade) - else if (strcmp("monster_snark", item_name) == 0) - { - } - - } // end if object is visible - } // end else not "func_" entity - - if (can_pickup) // if the bot found something it can pickup... - { - float distance = (entity_origin - pev->origin).Length( ); - - // see if it's the closest item so far... - if (distance < min_distance) - { - min_distance = distance; // update the minimum distance - pPickupEntity = pEntity; // remember this entity - pickup_origin = entity_origin; // remember location of entity - } - } - } // end while loop - - if (pPickupEntity != NULL) - { - // let's head off toward that item... - Vector v_item = pickup_origin - pev->origin; - - Vector bot_angles = UTIL_VecToAngles( v_item ); - - pev->ideal_yaw = bot_angles.y; - - // check for wrap around of angle... - if (pev->ideal_yaw > 180) - pev->ideal_yaw -= 360; - if (pev->ideal_yaw < -180) - pev->ideal_yaw += 360; - - pBotPickupItem = pPickupEntity; // save the item bot is trying to get - } -} - - -void CBot::BotUseLift( float moved_distance ) -{ - // just need to press the button once, when the flag gets set... - if (f_use_button_time == gpGlobals->time) - { - pev->button = IN_USE; - - // face opposite from the button - pev->ideal_yaw = -pev->ideal_yaw; // rotate 180 degrees - - // check for wrap around of angle... - if (pev->ideal_yaw > 180) - pev->ideal_yaw -= 360; - if (pev->ideal_yaw < -180) - pev->ideal_yaw += 360; - } - - // check if the bot has waited too long for the lift to move... - if (((f_use_button_time + 2.0) < gpGlobals->time) && - (!b_lift_moving)) - { - // clear use button flag - b_use_button = FALSE; - - // bot doesn't have to set f_find_item since the bot - // should already be facing away from the button - - f_move_speed = f_max_speed; - } - - // check if lift has started moving... - if ((moved_distance > 1) && (!b_lift_moving)) - { - b_lift_moving = TRUE; - } - - // check if lift has stopped moving... - if ((moved_distance <= 1) && (b_lift_moving)) - { - TraceResult tr1, tr2; - Vector v_src, v_forward, v_right, v_left; - Vector v_down, v_forward_down, v_right_down, v_left_down; - - b_use_button = FALSE; - - // TraceLines in 4 directions to find which way to go... - - UTIL_MakeVectors( pev->v_angle ); - - v_src = pev->origin + pev->view_ofs; - v_forward = v_src + gpGlobals->v_forward * 90; - v_right = v_src + gpGlobals->v_right * 90; - v_left = v_src + gpGlobals->v_right * -90; - - v_down = pev->v_angle; - v_down.x = v_down.x + 45; // look down at 45 degree angle - - UTIL_MakeVectors( v_down ); - - v_forward_down = v_src + gpGlobals->v_forward * 100; - v_right_down = v_src + gpGlobals->v_right * 100; - v_left_down = v_src + gpGlobals->v_right * -100; - - // try tracing forward first... - UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), &tr1); - UTIL_TraceLine( v_src, v_forward_down, dont_ignore_monsters, ENT(pev), &tr2); - - // check if we hit a wall or didn't find a floor... - if ((tr1.flFraction < 1.0) || (tr2.flFraction >= 1.0)) - { - // try tracing to the RIGHT side next... - UTIL_TraceLine( v_src, v_right, dont_ignore_monsters, ENT(pev), &tr1); - UTIL_TraceLine( v_src, v_right_down, dont_ignore_monsters, ENT(pev), &tr2); - - // check if we hit a wall or didn't find a floor... - if ((tr1.flFraction < 1.0) || (tr2.flFraction >= 1.0)) - { - // try tracing to the LEFT side next... - UTIL_TraceLine( v_src, v_left, dont_ignore_monsters, ENT(pev), &tr1); - UTIL_TraceLine( v_src, v_left_down, dont_ignore_monsters, ENT(pev), &tr2); - - // check if we hit a wall or didn't find a floor... - if ((tr1.flFraction < 1.0) || (tr2.flFraction >= 1.0)) - { - // only thing to do is turn around... - pev->ideal_yaw += 180; // turn all the way around - } - else - { - pev->ideal_yaw += 90; // turn to the LEFT - } - } - else - { - pev->ideal_yaw -= 90; // turn to the RIGHT - } - - // check for wrap around of angle... - if (pev->ideal_yaw > 180) - pev->ideal_yaw -= 360; - if (pev->ideal_yaw < -180) - pev->ideal_yaw += 360; - } - - BotChangeYaw( pev->yaw_speed ); - - f_move_speed = f_max_speed; - } -} - - -void CBot::BotTurnAtWall( TraceResult *tr ) -{ - Vector Normal; - float Y, Y1, Y2, D1, D2, Z; - - // Find the normal vector from the trace result. The normal vector will - // be a vector that is perpendicular to the surface from the TraceResult. - - Normal = UTIL_VecToAngles(tr->vecPlaneNormal); - - // Since the bot keeps it's view angle in -180 < x < 180 degrees format, - // and since TraceResults are 0 < x < 360, we convert the bot's view - // angle (yaw) to the same format at TraceResult. - - Y = pev->v_angle.y; - Y = Y + 180; - if (Y > 359) Y -= 360; - - // Turn the normal vector around 180 degrees (i.e. make it point towards - // the wall not away from it. That makes finding the angles that the - // bot needs to turn a little easier. - - Normal.y = Normal.y - 180; - if (Normal.y < 0) - Normal.y += 360; - - // Here we compare the bots view angle (Y) to the Normal - 90 degrees (Y1) - // and the Normal + 90 degrees (Y2). These two angles (Y1 & Y2) represent - // angles that are parallel to the wall surface, but heading in opposite - // directions. We want the bot to choose the one that will require the - // least amount of turning (saves time) and have the bot head off in that - // direction. - - Y1 = Normal.y - 90; - if (RANDOM_LONG(1, 100) <= 50) - { - Y1 = Y1 - RANDOM_FLOAT(5.0, 20.0); - } - if (Y1 < 0) Y1 += 360; - - Y2 = Normal.y + 90; - if (RANDOM_LONG(1, 100) <= 50) - { - Y2 = Y2 + RANDOM_FLOAT(5.0, 20.0); - } - if (Y2 > 359) Y2 -= 360; - - // D1 and D2 are the difference (in degrees) between the bot's current - // angle and Y1 or Y2 (respectively). - - D1 = abs(Y - Y1); - if (D1 > 179) D1 = abs(D1 - 360); - D2 = abs(Y - Y2); - if (D2 > 179) D2 = abs(D2 - 360); - - // If difference 1 (D1) is more than difference 2 (D2) then the bot will - // have to turn LESS if it heads in direction Y1 otherwise, head in - // direction Y2. I know this seems backwards, but try some sample angles - // out on some graph paper and go through these equations using a - // calculator, you'll see what I mean. - - if (D1 > D2) - Z = Y1; - else - Z = Y2; - - // convert from TraceResult 0 to 360 degree format back to bot's - // -180 to 180 degree format. - - if (Z > 180) - Z -= 360; - - // set the direction to head off into... - pev->ideal_yaw = Z; - - // check for wrap around of angle... - if (pev->ideal_yaw > 180) - pev->ideal_yaw -= 360; - if (pev->ideal_yaw < -180) - pev->ideal_yaw += 360; - - f_move_speed = 0; // don't move while turning -} - - -BOOL CBot::BotCantMoveForward( TraceResult *tr ) -{ - // use some TraceLines to determine if anything is blocking the current - // path of the bot. - - Vector v_src, v_forward; - - UTIL_MakeVectors( pev->v_angle ); - - // first do a trace from the bot's eyes forward... - - v_src = pev->origin + pev->view_ofs; // EyePosition() - v_forward = v_src + gpGlobals->v_forward * 40; - - // trace from the bot's eyes straight forward... - UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), tr); - - // check if the trace hit something... - if (tr->flFraction < 1.0) - { - return TRUE; // bot's head will hit something - } - - // bot's head is clear, check at waist level... - - v_src = pev->origin; - v_forward = v_src + gpGlobals->v_forward * 40; - - // trace from the bot's waist straight forward... - UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), tr); - - // check if the trace hit something... - if (tr->flFraction < 1.0) - { - return TRUE; // bot's body will hit something - } - - return FALSE; // bot can move forward, return false -} - - -BOOL CBot::BotCanJumpUp( void ) -{ - // What I do here is trace 3 lines straight out, one unit higher than - // the highest normal jumping distance. I trace once at the center of - // the body, once at the right side, and once at the left side. If all - // three of these TraceLines don't hit an obstruction then I know the - // area to jump to is clear. I then need to trace from head level, - // above where the bot will jump to, downward to see if there is anything - // blocking the jump. There could be a narrow opening that the body - // will not fit into. These horizontal and vertical TraceLines seem - // to catch most of the problems with falsely trying to jump on something - // that the bot can not get onto. - - TraceResult tr; - Vector v_jump, v_source, v_dest; - - // convert current view angle to vectors for TraceLine math... - - v_jump = pev->v_angle; - v_jump.x = 0; // reset pitch to 0 (level horizontally) - v_jump.z = 0; // reset roll to 0 (straight up and down) - - UTIL_MakeVectors( v_jump ); - - // use center of the body first... - - // maximum jump height is 45, so check one unit above that (46) - v_source = pev->origin + Vector(0, 0, -36 + 46); - v_dest = v_source + gpGlobals->v_forward * 24; - - // trace a line forward at maximum jump height... - UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); - - // if trace hit something, return FALSE - if (tr.flFraction < 1.0) - return FALSE; - - // now check same height to one side of the bot... - v_source = pev->origin + gpGlobals->v_right * 16 + Vector(0, 0, -36 + 46); - v_dest = v_source + gpGlobals->v_forward * 24; - - // trace a line forward at maximum jump height... - UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); - - // if trace hit something, return FALSE - if (tr.flFraction < 1.0) - return FALSE; - - // now check same height on the other side of the bot... - v_source = pev->origin + gpGlobals->v_right * -16 + Vector(0, 0, -36 + 46); - v_dest = v_source + gpGlobals->v_forward * 24; - - // trace a line forward at maximum jump height... - UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); - - // if trace hit something, return FALSE - if (tr.flFraction < 1.0) - return FALSE; - - // now trace from head level downward to check for obstructions... - - // start of trace is 24 units in front of bot, 72 units above head... - v_source = pev->origin + gpGlobals->v_forward * 24; - - // offset 72 units from top of head (72 + 36) - v_source.z = v_source.z + 108; - - // end point of trace is 99 units straight down from start... - // (99 is 108 minus the jump limit height which is 45 - 36 = 9) - v_dest = v_source + Vector(0, 0, -99); - - // trace a line straight down toward the ground... - UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); - - // if trace hit something, return FALSE - if (tr.flFraction < 1.0) - return FALSE; - - // now check same height to one side of the bot... - v_source = pev->origin + gpGlobals->v_right * 16 + gpGlobals->v_forward * 24; - v_source.z = v_source.z + 108; - v_dest = v_source + Vector(0, 0, -99); - - // trace a line straight down toward the ground... - UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); - - // if trace hit something, return FALSE - if (tr.flFraction < 1.0) - return FALSE; - - // now check same height on the other side of the bot... - v_source = pev->origin + gpGlobals->v_right * -16 + gpGlobals->v_forward * 24; - v_source.z = v_source.z + 108; - v_dest = v_source + Vector(0, 0, -99); - - // trace a line straight down toward the ground... - UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); - - // if trace hit something, return FALSE - if (tr.flFraction < 1.0) - return FALSE; - - return TRUE; -} - - -BOOL CBot::BotCanDuckUnder( void ) -{ - // What I do here is trace 3 lines straight out, one unit higher than - // the ducking height. I trace once at the center of the body, once - // at the right side, and once at the left side. If all three of these - // TraceLines don't hit an obstruction then I know the area to duck to - // is clear. I then need to trace from the ground up, 72 units, to make - // sure that there is something blocking the TraceLine. Then we know - // we can duck under it. - - TraceResult tr; - Vector v_duck, v_source, v_dest; - - // convert current view angle to vectors for TraceLine math... - - v_duck = pev->v_angle; - v_duck.x = 0; // reset pitch to 0 (level horizontally) - v_duck.z = 0; // reset roll to 0 (straight up and down) - - UTIL_MakeVectors( v_duck ); - - // use center of the body first... - - // duck height is 36, so check one unit above that (37) - v_source = pev->origin + Vector(0, 0, -36 + 37); - v_dest = v_source + gpGlobals->v_forward * 24; - - // trace a line forward at duck height... - UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); - - // if trace hit something, return FALSE - if (tr.flFraction < 1.0) - return FALSE; - - // now check same height to one side of the bot... - v_source = pev->origin + gpGlobals->v_right * 16 + Vector(0, 0, -36 + 37); - v_dest = v_source + gpGlobals->v_forward * 24; - - // trace a line forward at duck height... - UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); - - // if trace hit something, return FALSE - if (tr.flFraction < 1.0) - return FALSE; - - // now check same height on the other side of the bot... - v_source = pev->origin + gpGlobals->v_right * -16 + Vector(0, 0, -36 + 37); - v_dest = v_source + gpGlobals->v_forward * 24; - - // trace a line forward at duck height... - UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); - - // if trace hit something, return FALSE - if (tr.flFraction < 1.0) - return FALSE; - - // now trace from the ground up to check for object to duck under... - - // start of trace is 24 units in front of bot near ground... - v_source = pev->origin + gpGlobals->v_forward * 24; - v_source.z = v_source.z - 35; // offset to feet + 1 unit up - - // end point of trace is 72 units straight up from start... - v_dest = v_source + Vector(0, 0, 72); - - // trace a line straight up in the air... - UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); - - // if trace didn't hit something, return FALSE - if (tr.flFraction >= 1.0) - return FALSE; - - // now check same height to one side of the bot... - v_source = pev->origin + gpGlobals->v_right * 16 + gpGlobals->v_forward * 24; - v_source.z = v_source.z - 35; // offset to feet + 1 unit up - v_dest = v_source + Vector(0, 0, 72); - - // trace a line straight up in the air... - UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); - - // if trace didn't hit something, return FALSE - if (tr.flFraction >= 1.0) - return FALSE; - - // now check same height on the other side of the bot... - v_source = pev->origin + gpGlobals->v_right * -16 + gpGlobals->v_forward * 24; - v_source.z = v_source.z - 35; // offset to feet + 1 unit up - v_dest = v_source + Vector(0, 0, 72); - - // trace a line straight up in the air... - UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); - - // if trace didn't hit something, return FALSE - if (tr.flFraction >= 1.0) - return FALSE; - - return TRUE; -} - - -BOOL CBot::BotShootTripmine( void ) -{ - if (b_shoot_tripmine != TRUE) - return FALSE; - - // aim at the tripmine and fire the glock... - - Vector v_enemy = v_tripmine_origin - GetGunPosition( ); - - pev->v_angle = UTIL_VecToAngles( v_enemy ); - - pev->angles.x = 0; - pev->angles.y = pev->v_angle.y; - pev->angles.z = 0; - - pev->ideal_yaw = pev->v_angle.y; - - // check for wrap around of angle... - if (pev->ideal_yaw > 180) - pev->ideal_yaw -= 360; - if (pev->ideal_yaw < -180) - pev->ideal_yaw += 360; - - pev->v_angle.x = -pev->v_angle.x; //adjust pitch to point gun - - return (BotFireWeapon( v_tripmine_origin, WEAPON_GLOCK, TRUE )); -} - - -BOOL CBot::BotFollowUser( void ) -{ - BOOL user_visible; - float f_distance; - - Vector vecEnd = pBotUser->pev->origin + pBotUser->pev->view_ofs; - - pev->v_angle.x = 0; // reset pitch to 0 (level horizontally) - pev->v_angle.z = 0; // reset roll to 0 (straight up and down) - - pev->angles.x = 0; - pev->angles.y = pev->v_angle.y; - pev->angles.z = 0; - - if (!pBotUser->IsAlive( )) - { - // the bot's user is dead! - pBotUser = NULL; - return FALSE; - } - - user_visible = FInViewCone( &vecEnd ) && FVisible( vecEnd ); - - // check if the "user" is still visible or if the user has been visible - // in the last 5 seconds (or the player just starting "using" the bot) - - if (user_visible || (f_bot_use_time + 5 > gpGlobals->time)) - { - if (user_visible) - f_bot_use_time = gpGlobals->time; // reset "last visible time" - - // face the user - Vector v_user = pBotUser->pev->origin - pev->origin; - Vector bot_angles = UTIL_VecToAngles( v_user ); - - pev->ideal_yaw = bot_angles.y; - - // check for wrap around of angle... - if (pev->ideal_yaw > 180) - pev->ideal_yaw -= 360; - if (pev->ideal_yaw < -180) - pev->ideal_yaw += 360; - - f_distance = v_user.Length( ); // how far away is the "user"? - - if (f_distance > 200) // run if distance to enemy is far - f_move_speed = f_max_speed; - else if (f_distance > 50) // walk if distance is closer - f_move_speed = f_max_speed / 2; - else // don't move if close enough - f_move_speed = 0.0; - - return TRUE; - } - else - { - // person to follow has gone out of sight... - pBotUser = NULL; - - return FALSE; - } -} - - -BOOL CBot::BotCheckWallOnLeft( void ) -{ - Vector v_src, v_left; - TraceResult tr; - - UTIL_MakeVectors( pev->v_angle ); - - // do a trace to the left... - - v_src = pev->origin; - v_left = v_src + gpGlobals->v_right * -40; // 40 units to the left - - UTIL_TraceLine( v_src, v_left, dont_ignore_monsters, ENT(pev), &tr); - - // check if the trace hit something... - if (tr.flFraction < 1.0) - { - if (f_wall_on_left == 0) - f_wall_on_left = gpGlobals->time; - - return TRUE; - } - - return FALSE; -} - - -BOOL CBot::BotCheckWallOnRight( void ) -{ - Vector v_src, v_right; - TraceResult tr; - - UTIL_MakeVectors( pev->v_angle ); - - // do a trace to the right... - - v_src = pev->origin; - v_right = v_src + gpGlobals->v_right * 40; // 40 units to the right - - UTIL_TraceLine( v_src, v_right, dont_ignore_monsters, ENT(pev), &tr); - - // check if the trace hit something... - if (tr.flFraction < 1.0) - { - if (f_wall_on_right == 0) - f_wall_on_right = gpGlobals->time; - - return TRUE; - } - - return FALSE; -} - - -void CBot::BotThink( void ) -{ - Vector v_diff; // vector from previous to current location - float moved_distance; // length of v_diff vector (distance bot moved) - float degrees_turned; - - /* - if (pBotEnemy != NULL) // does an enemy exist? - { - ALERT( at_console, "Bot has enemy\n" ); - //ALERT( at_console, "Bot has enemy %s\n", STRING(pBotEnemy->pev->classname) ); - } else - ALERT( at_console, "Bot has NO ENEMY\n" ); - */ - - // check if someone kicked the bot off of the server (DON'T RESPAWN!)... - if ((pev->takedamage == DAMAGE_NO) && (respawn_index >= 0)) - { - pev->health = 0; - pev->deadflag = DEAD_DEAD; // make the kicked bot be dead - - bot_respawn[respawn_index].is_used = FALSE; // this slot is now free - bot_respawn[respawn_index].state = BOT_IDLE; - respawn_index = -1; // indicate no slot used - - // fall through to next if statement (respawn_index will be -1) - } - - // is the round over (time/frag limit) or has the bot been removed? - if ((g_fGameOver) || (respawn_index == -1)) - { - CSound *pSound; - - // keep resetting the sound entity until the bot is respawned... - pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); - if ( pSound ) - { - pSound->Reset(); - } - - return; - } - - pev->button = 0; // make sure no buttons are pressed - - // if the bot is dead, randomly press fire to respawn... - /* - if ((pev->health < 1) || (pev->deadflag != DEAD_NO)) - { - if (RANDOM_LONG(1, 100) > 50) - pev->button = IN_ATTACK; - - g_engfuncs.pfnRunPlayerMove( edict( ), pev->v_angle, f_move_speed, - 0, 0, pev->button, 0, - gpGlobals->frametime * 1000 ); - return; - } - */ - - // see if it's time to check for sv_maxspeed change... - if (f_speed_check_time <= gpGlobals->time) - { - // store current sv_maxspeed setting for quick access - f_max_speed = CVAR_GET_FLOAT("sv_maxspeed"); - - // set next check time to 2 seconds from now - f_speed_check_time = gpGlobals->time + 2.0; - } - - // see how far bot has moved since the previous position... - v_diff = v_prev_origin - pev->origin; - moved_distance = v_diff.Length(); - - v_prev_origin = pev->origin; // save current position as previous - - f_move_speed = f_max_speed; // set to max speed unless known otherwise - - // turn towards ideal_yaw by yaw_speed degrees - // - degrees_turned = BotChangeYaw( pev->yaw_speed ); - // - - if (degrees_turned >= pev->yaw_speed) - { - // if bot is still turning, turn in place by setting speed to 0 - f_move_speed = 0; - } - - else if (IsOnLadder( )) // check if the bot is on a ladder... - { - f_use_ladder_time = gpGlobals->time; - - BotOnLadder( moved_distance ); // go handle the ladder movement - } - - else // else handle movement related actions... - { - // bot is not on a ladder so clear ladder direction flag... - ladder_dir = 0; - - // Vyacheslav Dzhura: IMPORTNANT - DECAY'S BOT - FIND ENEMY - if (f_botdontshoot == 0) // is bot targeting turned on? - pBotEnemy = BotFindEnemy( ); // leave ";" to disable bot AI - else - pBotEnemy = NULL; // clear enemy pointer (no ememy for you!) - - if (pBotEnemy != NULL) // does an enemy exist? - { - BotShootAtEnemy( ); // shoot at the enemy - } - - else if (f_pause_time > gpGlobals->time) // is bot "paused"? - { - // you could make the bot look left then right, or look up - // and down, to make it appear that the bot is hunting for - // something (don't do anything right now) - - f_move_speed = 0; - } - - // is bot being "used" and can still follow "user"? - else if ((pBotUser != NULL) && BotFollowUser( )) - { - // do nothing here! - ; - } - - else - { - // no enemy, let's just wander around... - // - pev->v_angle.x = 0; // reset pitch to 0 (level horizontally) - pev->v_angle.z = 0; // reset roll to 0 (straight up and down) - - pev->angles.x = 0; - pev->angles.y = pev->v_angle.y; - pev->angles.z = 0; - // - - // check if bot should look for items now or not... - /*/ - if (f_find_item < gpGlobals->time) - { - BotFindItem( ); // see if there are any visible items - } - // */ - - // check if bot sees a tripmine... - - /* - if (b_see_tripmine) - { - // check if bot can shoot the tripmine... - if ((b_shoot_tripmine) && BotShootTripmine( )) - { - // shot at tripmine, may or may not have hit it, clear - // flags anyway, next BotFindItem will see it again if - // it is still there... - - b_shoot_tripmine = FALSE; - b_see_tripmine = FALSE; - - // pause for a while to allow tripmine to explode... - f_pause_time = gpGlobals->time + 0.5; - } - else // run away!!! - { - Vector tripmine_angles; - - tripmine_angles = UTIL_VecToAngles( v_tripmine_origin - pev->origin ); - - // face away from the tripmine - pev->ideal_yaw = -tripmine_angles.y; - - // check for wrap around of angle... - if (pev->ideal_yaw > 180) - pev->ideal_yaw -= 360; - if (pev->ideal_yaw < -180) - pev->ideal_yaw += 360; - - b_see_tripmine = FALSE; - - f_move_speed = 0; // don't run while turning - } - } - - // check if should use wall mounted health station... - else if (b_use_health_station) - { - if ((f_use_health_time + 10.0) > gpGlobals->time) - { - f_move_speed = 0; // don't move while using health station - - pev->button = IN_USE; - } - else - { - // bot is stuck trying to "use" a health station... - - b_use_health_station = FALSE; - - // don't look for items for a while since the bot - // could be stuck trying to get to an item - f_find_item = gpGlobals->time + 0.5; - } - } - - // check if should use wall mounted HEV station... - else if (b_use_HEV_station) - { - if ((f_use_HEV_time + 10.0) > gpGlobals->time) - { - f_move_speed = 0; // don't move while using HEV station - - pev->button = IN_USE; - } - else - { - // bot is stuck trying to "use" a HEV station... - - b_use_HEV_station = FALSE; - - // don't look for items for a while since the bot - // could be stuck trying to get to an item - f_find_item = gpGlobals->time + 0.5; - } - } - - else if (b_use_button) - { - f_move_speed = 0; // don't move while using elevator - - BotUseLift( moved_distance ); - } - - else */ - { - - TraceResult tr; - - if (pev->waterlevel == 3) // check if the bot is underwater... - { - BotUnderWater( ); - } - - // check if there is a wall on the left... - /*/ start comment - if (!BotCheckWallOnLeft()) - { - // if there was a wall on the left over 1/2 a second ago then - // 20% of the time randomly turn between 45 and 60 degrees - - if ((f_wall_on_left != 0) && - (f_wall_on_left <= gpGlobals->time - 0.5) && - (RANDOM_LONG(1, 100) <= 20)) - { - pev->ideal_yaw += RANDOM_LONG(45, 60); - - // check for wrap around of angle... - if (pev->ideal_yaw > 180) - pev->ideal_yaw -= 360; - if (pev->ideal_yaw < -180) - pev->ideal_yaw += 360; - - f_move_speed = 0; // don't move while turning - f_dont_avoid_wall_time = gpGlobals->time + 1.0; - } - - f_wall_on_left = 0; // reset wall detect time - } - - // check if there is a wall on the right... - if (!BotCheckWallOnRight()) - { - // if there was a wall on the right over 1/2 a second ago then - // 20% of the time randomly turn between 45 and 60 degrees - - if ((f_wall_on_right != 0) && - (f_wall_on_right <= gpGlobals->time - 0.5) && - (RANDOM_LONG(1, 100) <= 20)) - { - pev->ideal_yaw -= RANDOM_LONG(45, 60); - - // check for wrap around of angle... - if (pev->ideal_yaw > 180) - pev->ideal_yaw -= 360; - if (pev->ideal_yaw < -180) - pev->ideal_yaw += 360; - - f_move_speed = 0; // don't move while turning - f_dont_avoid_wall_time = gpGlobals->time + 1.0; - } - - f_wall_on_right = 0; // reset wall detect time - } - // end comment */ - - // check if bot is about to hit a wall. TraceResult gets returned - if ((f_dont_avoid_wall_time <= gpGlobals->time) && - BotCantMoveForward( &tr )) - { - // ADD LATER - // need to check if bot can jump up or duck under here... - // ADD LATER - - BotTurnAtWall( &tr ); - } - - // check if the bot hasn't moved much since the last location - // commented for Decay - /* - - else if ((moved_distance <= 1) && (!bot_was_paused)) - { - // the bot must be stuck! - - if (BotCanJumpUp( )) // can the bot jump onto something? - { - pev->button |= IN_JUMP; // jump up and move forward - } - else if (BotCanDuckUnder( )) // can the bot duck under something? - { - pev->button |= IN_DUCK; // duck down and move forward - } - else - { - f_move_speed = 0; // don't move while turning - - // turn randomly between 30 and 60 degress - // - if (wander_dir == WANDER_LEFT) - pev->ideal_yaw += RANDOM_LONG(30, 60); - else - pev->ideal_yaw -= RANDOM_LONG(30, 60); - // - // check for wrap around of angle... - if (pev->ideal_yaw > 180) - pev->ideal_yaw -= 360; - if (pev->ideal_yaw < -180) - pev->ideal_yaw += 360; - - - // is the bot trying to get to an item?... - if (pBotPickupItem != NULL) - { - // don't look for items for a while since the bot - // could be stuck trying to get to an item - f_find_item = gpGlobals->time + 0.5; - } - } - }// */ - - // should the bot pause for a while here? - if ((RANDOM_LONG(1, 1000) <= pause_frequency[bot_skill]) && - (pBotUser == NULL)) // don't pause if being "used" - { - // set the time that the bot will stop "pausing" - f_pause_time = gpGlobals->time + - RANDOM_FLOAT(pause_time[bot_skill][0], - pause_time[bot_skill][1]); - f_move_speed = 0; // don't move while turning - } - } - } - } - - if (f_move_speed < 1) - bot_was_paused = TRUE; - else - bot_was_paused = FALSE; - - // TheFatal START - from www.telefragged.com/thefatal/jumblow.shtml - - // disable movement for Decay - f_move_speed = 0; - g_engfuncs.pfnRunPlayerMove( edict( ), pev->v_angle, f_move_speed, - 0, 0, pev->button, 0, - gpGlobals->frametime * 1000 ); - // TheFatal - END -} - +// botman's Half-Life bot example +// +// http://planethalflife.com/botman/ +// +// bot.cpp +// + +#include "extdll.h" +#include "util.h" +#include "client.h" +#include "cbase.h" +#include "player.h" +#include "items.h" +#include "effects.h" +#include "weapons.h" +#include "soundent.h" +#include "gamerules.h" +#include "animation.h" + +#include "bot.h" + +#include +#include + +extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; + +// Set in combat.cpp. Used to pass the damage inflictor for death messages. +extern entvars_t *g_pevLastInflictor; + +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgHealth; +extern int gmsgCurWeapon; +extern int gmsgSetFOV; + +#define PLAYER_SEARCH_RADIUS (float)40 + +int f_Observer = 0; // flag to indicate if player is in observer mode +int f_botskill = 3; // default bot skill level +int f_botdontshoot = 0; + +// array of bot respawn information +respawn_t bot_respawn[32] = { + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}, + {FALSE, BOT_IDLE, "", "", "", NULL}, {FALSE, BOT_IDLE, "", "", "", NULL}}; + +#define MAX_SKINS 2 + +// indicate which models are currently used for random model allocation +BOOL skin_used[MAX_SKINS] = { + FALSE }; + +// store the names of the models... +const char *bot_skins[MAX_SKINS] = { + "ginacol", "player/dm_slave/dm_slave" }; + +// store the player names for each of the models... +const char *bot_names[MAX_SKINS] = { + "Colette", "R-4913" }; + +// sounds for TakeDamage speaking effects... +char gina_sounds[][30] = { GI_SND1, GI_SND2, GI_SND3, GI_SND4, GI_SND5 }; +char colette_sounds[][30] = { CO_SND1, CO_SND2, CO_SND3, CO_SND4, CO_SND5 }; + +// how often (out of 1000 times) the bot will pause, based on bot skill +float pause_frequency[5] = {4, 7, 10, 15, 20}; + +// how long the bot will delay when paused, based on bot skill +float pause_time[5][2] = { + {0.2, 0.5}, {0.5, 1.0}, {0.7, 1.3}, {1.0, 1.7}, {1.2, 2.0}}; + +extern ammo_check_t ammo_check[]; + +LINK_ENTITY_TO_CLASS( bot, CBot ); + + +inline edict_t *CREATE_FAKE_CLIENT( const char *netname ) +{ + return (*g_engfuncs.pfnCreateFakeClient)( netname ); +} + +inline char *GET_INFOBUFFER( edict_t *e ) +{ + return (*g_engfuncs.pfnGetInfoKeyBuffer)( e ); +} + +inline char *GET_INFO_KEY_VALUE( char *infobuffer, const char *key ) +{ + return (g_engfuncs.pfnInfoKeyValue( infobuffer, key )); +} + +inline void SET_CLIENT_KEY_VALUE( int clientIndex, char *infobuffer, + const char *key, const char *value ) +{ + (*g_engfuncs.pfnSetClientKeyValue)( clientIndex, infobuffer, key, value ); +} + + +void BotDebug( char *buffer ) +{ + // print out debug messages to the HUD of all players + // this allows you to print messages from bots to your display + // as you are playing the game. Use STRING(pev->netname) in + // buffer to see the name of the bot. + + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, buffer ); +} + + +void BotCreate(const char *skin, const char *name, const char *skill) +{ + edict_t *BotEnt; + CBot *BotClass; + int skill_level; + char c_skill[2]; + char c_skin[BOT_SKIN_LEN+1]; + char c_name[BOT_NAME_LEN+1]; + char c_index[3]; + int i, j, length; + int index; // index into array of predefined names + BOOL found = FALSE; + + if ((skin == NULL) || (*skin == 0)) + { + // pick a random skin + index = RANDOM_LONG(0, 9); // there are ten possible skins + + // check if this skin has already been used... + while (skin_used[index] == TRUE) + { + index++; + + if (index == MAX_SKINS) + index = 0; + } + + skin_used[index] = TRUE; + + // check if all skins are used... + for (i = 0; i < MAX_SKINS; i++) + { + if (skin_used[i] == FALSE) + break; + } + + // if all skins are used, reset used to FALSE for next selection + if (i == MAX_SKINS) + { + for (i = 0; i < MAX_SKINS; i++) + skin_used[i] = FALSE; + } + + strcpy( c_skin, bot_skins[index] ); + } + else + { + strncpy( c_skin, skin, BOT_SKIN_LEN); + c_skin[BOT_SKIN_LEN] = 0; // make sure c_skin is null terminated + } + + for (i = 0; c_skin[i] != 0; i++) + c_skin[i] = tolower( c_skin[i] ); // convert to all lowercase + + index = 0; + + while ((!found) && (index < MAX_SKINS)) + { + if (strcmp(c_skin, bot_skins[index]) == 0) + found = TRUE; + else + index++; + } + + if (found == TRUE) + { + if ((name != NULL) && (*name != 0)) + { + strncpy( c_name, name, 31 ); + c_name[31] = 0; // make sure c_name is null terminated + } + else + { + strcpy( c_name, bot_names[index] ); + } + } + else + { + char dir_name[32]; + char filename[128]; + + struct stat stat_str; + + GET_GAME_DIR(dir_name); + + sprintf(filename, "%s\\models\\player\\%s", dir_name, c_skin); + + if (stat(filename, &stat_str) != 0) + { + sprintf(filename, "valve\\models\\player\\%s", c_skin); + if (stat(filename, &stat_str) != 0) + { + char err_msg[80]; + + sprintf( err_msg, "model \"%s\" is unknown.\n", c_skin ); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, err_msg ); + if (IS_DEDICATED_SERVER()) + printf(err_msg); + + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, + "use barney, gina, gman, gordon, helmet, hgrunt,\n"); + if (IS_DEDICATED_SERVER()) + printf("use barney, gina, gman, gordon, helmet, hgrunt,\n"); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, + " recon, robo, scientist, or zombie\n"); + if (IS_DEDICATED_SERVER()) + printf(" recon, robo, scientist, or zombie\n"); + return; + } + } + + // copy the name of the model to the bot's name... + strncpy( c_name, skin, BOT_SKIN_LEN); + c_name[BOT_SKIN_LEN] = 0; // make sure c_skin is null terminated + } + + if ((name != NULL) && (*name != 0)) + { + strncpy( c_name, name, 31 ); + c_name[31] = 0; // make sure c_name is null terminated + } + + /*length = strlen(c_name); + + // remove any illegal characters from name... + for (i = 0; i < length; i++) + { + if ((c_name[i] <= ' ') || (c_name[i] > '~') || + (c_name[i] == '"')) + { + for (j = i; j < length; j++) // shuffle chars left (and null) + c_name[j] = c_name[j+1]; + length--; + } + }*/ + + skill_level = 0; + + if ((skill != NULL) && (*skill != 0)) + sscanf( skill, "%d", &skill_level ); + else + skill_level = f_botskill; + + if ((skill_level < 1) || (skill_level > 5)) + skill_level = f_botskill; + + sprintf( c_skill, "%d", skill_level ); + + BotEnt = CREATE_FAKE_CLIENT( c_name ); + + if (FNullEnt( BotEnt )) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "Max. Players reached. Can't create bot!\n"); + + if (IS_DEDICATED_SERVER()) + printf("Max. Players reached. Can't create bot!\n"); + } + else + { + char ptr[128]; // allocate space for message from ClientConnect + char *infobuffer; + int clientIndex; + + index = 0; + while ((bot_respawn[index].is_used) && (index < 32)) + index++; + + if (index >= 32) + { + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, "Can't create bot!\n"); + return; + } + + sprintf(c_index, "%d", index); + + bot_respawn[index].is_used = TRUE; // this slot is used + + // don't store the name here, it might change if same as another + strcpy(bot_respawn[index].skin, c_skin); + strcpy(bot_respawn[index].skill, c_skill); + + sprintf(ptr, "Creating bot \"%s\" using model %s with skill=%d\n", c_name, c_skin, skill_level); + UTIL_ClientPrintAll( HUD_PRINTNOTIFY, ptr); + + if (IS_DEDICATED_SERVER()) + printf("%s", ptr); + + BotClass = GetClassPtr( (CBot *) VARS(BotEnt) ); + infobuffer = GET_INFOBUFFER( BotClass->edict( ) ); + clientIndex = BotClass->entindex( ); + + SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "model", c_skin ); + SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "skill", c_skill ); + SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "index", c_index ); + + ClientConnect( BotClass->edict( ), c_name, "127.0.0.1", ptr ); + DispatchSpawn( BotClass->edict( ) ); + } +} + + +void CBot::Spawn( ) +{ + char c_skill[2]; + char c_index[3]; + + CBasePlayer::Spawn(); + + pev->flags = FL_CLIENT | FL_FAKECLIENT; + + // set the respawn index value based on key from BotCreate + strcpy(c_index, GET_INFO_KEY_VALUE(GET_INFOBUFFER(edict( )), "index") ); + sscanf(c_index, "%d", &respawn_index); + + bot_respawn[respawn_index].pBot = (CBasePlayer *)this; + + // get the bot's name and save it in respawn array... + strcpy(bot_respawn[respawn_index].name, STRING(pev->netname)); + + bot_respawn[respawn_index].state = BOT_IDLE; + + pev->ideal_yaw = pev->v_angle.y; + pev->yaw_speed = BOT_YAW_SPEED; + + // bot starts out in "paused" state since it hasn't moved yet... + bot_was_paused = TRUE; + v_prev_origin = pev->origin; + + f_shoot_time = 0; + + // get bot's skill level (0=good, 4=bad) + strcpy(c_skill, GET_INFO_KEY_VALUE(GET_INFOBUFFER(edict( )), "skill") ); + sscanf(c_skill, "%d", &bot_skill); + bot_skill--; // make 0 based for array index (now 0-4) + + f_max_speed = CVAR_GET_FLOAT("sv_maxspeed"); + + f_speed_check_time = gpGlobals->time + 2.0; + + ladder_dir = 0; + + // pick a wander direction (50% of the time to the left, 50% to the right) + if (RANDOM_LONG(1, 100) <= 50) + wander_dir = WANDER_LEFT; + else + wander_dir = WANDER_RIGHT; + + f_pause_time = 0; + f_find_item = 0; + + if (g_pGameRules->IsTeamplay()) // is team play enabled? + { + strcpy(model_name, g_pGameRules->GetTeamID(this)); + } + else + { + strcpy(model_name, GET_INFO_KEY_VALUE(GET_INFOBUFFER(edict( )), "model") ); + } + + bot_model = 0; + if ((strcmp( model_name, "ginacol" ) == 0) || + (strcmp( model_name, "player" ) == 0)) + { + // Vyacheslav Dzhura: TODO somehow need to know DecayId in here + if ( m_iDecayId == 1) + bot_model = MODEL_GINA; + else + bot_model = MODEL_COLETTE; + } + else if (strcmp( model_name, "dm_gina") == 0) + { + bot_model = MODEL_GINA; + } + else if (strcmp( model_name, "dm_colette") == 0) + { + bot_model = MODEL_COLETTE; + } + + f_pain_time = gpGlobals->time + 5.0; + + b_use_health_station = FALSE; + b_use_HEV_station = FALSE; + b_use_button = FALSE; + f_use_button_time = 0; + b_lift_moving = FALSE; + f_use_ladder_time = 0; + f_fire_gauss = -1; // -1 means not charging gauss gun + + b_see_tripmine = FALSE; + b_shoot_tripmine = FALSE; + + f_weapon_inventory_time = 0; + f_dont_avoid_wall_time = 0; + f_bot_use_time = 0; + f_wall_on_right = 0; + f_wall_on_left = 0; + + pBotEnemy = NULL; + pBotEnemyOffset = 0; + pBotUser = NULL; + pBotPickupItem = NULL; +} + + +int CBot::TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, + float flDamage, int bitsDamageType ) +{ + CBaseEntity *pAttacker = CBaseEntity::Instance(pevAttacker); + char sound[40]; + int ret_damage; + + // do the damage first... + ret_damage = CBasePlayer::TakeDamage( pevInflictor, pevAttacker, flDamage, + bitsDamageType ); + + // if the bot doesn't have an enemy and someone is shooting at it then + // turn in the attacker's direction... + if (pBotEnemy == NULL) + { + // face the attacker... + Vector v_enemy = pAttacker->pev->origin - pev->origin; + Vector bot_angles = UTIL_VecToAngles( v_enemy ); + + pev->ideal_yaw = bot_angles.y; + + // check for wrap around of angle... + if (pev->ideal_yaw > 180) + pev->ideal_yaw -= 360; + if (pev->ideal_yaw < -180) + pev->ideal_yaw += 360; + + // stop using health or HEV stations... + b_use_health_station = FALSE; + b_use_HEV_station = FALSE; + } + + // check if bot model is known, attacker is not a bot, + // time for pain sound, and bot has some of health left... + + if ( + // Vyacheslav Dzhura: commented out for Decay + // (bot_model != 0) && (pAttacker->IsNetClient()) && + (f_pain_time <= gpGlobals->time) && (pev->health > 0) && + ( !IS_DEDICATED_SERVER() ) + ) + { + float distance = (pAttacker->pev->origin - pev->origin).Length( ); + + // check if the distance to attacker is close enough (otherwise + // the attacker is too far away to hear the pain sounds) + + if (distance <= 400) + { + // speak pain sounds about 50% of the time + if (RANDOM_LONG(1, 100) <= 50) + { + f_pain_time = gpGlobals->time + 5.0; + + // Vyacheslav Dzhura TODO: put Gina\Colette sounds here + if (bot_model == MODEL_GINA) + strcpy( sound, gina_sounds[RANDOM_LONG(0,4)] ); + else if (bot_model == MODEL_COLETTE) + strcpy( sound, colette_sounds[RANDOM_LONG(0,4)] ); + + EMIT_SOUND(ENT(pevAttacker), CHAN_VOICE, sound, + RANDOM_FLOAT(0.9, 1.0), ATTN_NORM); + } + } + } + + return ret_damage; +} + + +void CBot::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, + USE_TYPE useType, float value ) +{ + if (g_pGameRules->IsTeamplay()) // is team play enabled? + { + // check the bot and player are on the same team... + + if (UTIL_TeamsMatch(g_pGameRules->GetTeamID(this), + g_pGameRules->GetTeamID(pActivator))) + { + if (pBotEnemy == NULL) // is bot NOT currently engaged in combat? + { + if (pBotUser == NULL) // does bot NOT have a "user" + { + // tell teammate that bot will cover them... + + EMIT_SOUND(ENT(pActivator->pev), CHAN_VOICE, USE_TEAMPLAY_SND, + 1.0, ATTN_NORM); + + pBotUser = pActivator; + f_bot_use_time = gpGlobals->time; + } + else + { + // tell teammate that you'll see them later.. + + EMIT_SOUND(ENT(pActivator->pev), CHAN_VOICE, USE_TEAMPLAY_LATER_SND, + 1.0, ATTN_NORM); + + pBotUser = NULL; + f_bot_use_time = 0; + } + } + else + { + EMIT_SOUND(ENT(pActivator->pev), CHAN_VOICE, USE_TEAMPLAY_ENEMY_SND, 1.0, + ATTN_NORM); + } + } + } +} + + +int CBot::BotInFieldOfView(Vector dest) +{ + // find angles from source to destination... + Vector entity_angles = UTIL_VecToAngles( dest ); + + // make yaw angle 0 to 360 degrees if negative... + if (entity_angles.y < 0) + entity_angles.y += 360; + + // get bot's current view angle... + float view_angle = pev->v_angle.y; + + // make view angle 0 to 360 degrees if negative... + if (view_angle < 0) + view_angle += 360; + + // return the absolute value of angle to destination entity + // zero degrees means straight ahead, 45 degrees to the left or + // 45 degrees to the right is the limit of the normal view angle + + return abs((int)view_angle - (int)entity_angles.y); +} + + +BOOL CBot::BotEntityIsVisible( Vector dest ) +{ + TraceResult tr; + + // trace a line from bot's eyes to destination... + UTIL_TraceLine( pev->origin + pev->view_ofs, dest, ignore_monsters, + ENT(pev), &tr ); + + // check if line of sight to object is not blocked (i.e. visible) + if (tr.flFraction >= 1.0) + return TRUE; + else + return FALSE; +} + + +float CBot::BotChangeYaw( float speed ) +{ + float ideal; + float current; + float current_180; // current +/- 180 degrees + float diff; + + // turn from the current v_angle yaw to the ideal_yaw by selecting + // the quickest way to turn to face that direction + + current = pev->v_angle.y; + ideal = pev->ideal_yaw; + + // find the difference in the current and ideal angle + diff = abs(current - ideal); + + // check if the bot is already facing the ideal_yaw direction... + if (diff <= 1) + return diff; // return number of degrees turned + + // check if difference is less than the max degrees per turn + if (diff < speed) + speed = diff; // just need to turn a little bit (less than max) + + // here we have four cases, both angle positive, one positive and + // the other negative, one negative and the other positive, or + // both negative. handle each case separately... + + if ((current >= 0) && (ideal >= 0)) // both positive + { + if (current > ideal) + current -= speed; + else + current += speed; + } + else if ((current >= 0) && (ideal < 0)) + { + current_180 = current - 180; + + if (current_180 > ideal) + current += speed; + else + current -= speed; + } + else if ((current < 0) && (ideal >= 0)) + { + current_180 = current + 180; + if (current_180 > ideal) + current += speed; + else + current -= speed; + } + else // (current < 0) && (ideal < 0) both negative + { + if (current > ideal) + current -= speed; + else + current += speed; + } + + // check for wrap around of angle... + if (current > 180) + current -= 360; + if (current < -180) + current += 360; + + pev->v_angle.y = current; + + pev->angles.x = 0; + pev->angles.y = pev->v_angle.y; + pev->angles.z = 0; + + return speed; // return number of degrees turned +} + + +void CBot::BotOnLadder( float moved_distance ) +{ + // moves the bot up or down a ladder. if the bot can't move + // (i.e. get's stuck with someone else on ladder), the bot will + // change directions and go the other way on the ladder. + + if (ladder_dir == LADDER_UP) // is the bot currently going up? + { + pev->v_angle.x = -60; // look upwards + + // check if the bot hasn't moved much since the last location... + if (moved_distance <= 1) + { + // the bot must be stuck, change directions... + + pev->v_angle.x = 60; // look downwards + ladder_dir = LADDER_DOWN; + } + } + else if (ladder_dir == LADDER_DOWN) // is the bot currently going down? + { + pev->v_angle.x = 60; // look downwards + + // check if the bot hasn't moved much since the last location... + if (moved_distance <= 1) + { + // the bot must be stuck, change directions... + + pev->v_angle.x = -60; // look upwards + ladder_dir = LADDER_UP; + } + } + else // the bot hasn't picked a direction yet, try going up... + { + pev->v_angle.x = -60; // look upwards + ladder_dir = LADDER_UP; + } + + // move forward (i.e. in the direction the bot is looking, up or down) + pev->button |= IN_FORWARD; +} + + +void CBot::BotUnderWater( void ) +{ + // handle movements under water. right now, just try to keep from + // drowning by swimming up towards the surface and look to see if + // there is a surface the bot can jump up onto to get out of the + // water. bots DON'T like water! + + Vector v_src, v_forward; + TraceResult tr; + int contents; + + // swim up towards the surface + pev->v_angle.x = -60; // look upwards + + // move forward (i.e. in the direction the bot is looking, up or down) + pev->button |= IN_FORWARD; + + // set gpGlobals angles based on current view angle (for TraceLine) + UTIL_MakeVectors( pev->v_angle ); + + // look from eye position straight forward (remember: the bot is looking + // upwards at a 60 degree angle so TraceLine will go out and up... + + v_src = pev->origin + pev->view_ofs; // EyePosition() + v_forward = v_src + gpGlobals->v_forward * 90; + + // trace from the bot's eyes straight forward... + UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), &tr); + + // check if the trace didn't hit anything (i.e. nothing in the way)... + if (tr.flFraction >= 1.0) + { + // find out what the contents is of the end of the trace... + contents = UTIL_PointContents( tr.vecEndPos ); + + // check if the trace endpoint is in open space... + if (contents == CONTENTS_EMPTY) + { + // ok so far, we are at the surface of the water, continue... + + v_src = tr.vecEndPos; + v_forward = v_src; + v_forward.z -= 90; + + // trace from the previous end point straight down... + UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, + ENT(pev), &tr); + + // check if the trace hit something... + if (tr.flFraction < 1.0) + { + contents = UTIL_PointContents( tr.vecEndPos ); + + // if contents isn't water then assume it's land, jump! + if (contents != CONTENTS_WATER) + { + pev->button |= IN_JUMP; + } + } + } + } +} + + +void CBot::BotFindItem( void ) +{ + CBaseEntity *pEntity = NULL; + CBaseEntity *pPickupEntity = NULL; + Vector pickup_origin; + Vector entity_origin; + float radius = 500; + BOOL can_pickup; + float min_distance; + char item_name[40]; + TraceResult tr; + Vector vecStart; + Vector vecEnd; + int angle_to_entity; + + pBotPickupItem = NULL; + + min_distance = radius + 1.0; + + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, radius )) != NULL) + { + can_pickup = FALSE; // assume can't use it until known otherwise + + strcpy(item_name, STRING(pEntity->pev->classname)); + + // see if this is a "func_" type of entity (func_button, etc.)... + if (strncmp("func_", item_name, 5) == 0) + { + // BModels have 0,0,0 for origin so must use VecBModelOrigin... + entity_origin = VecBModelOrigin(pEntity->pev); + + vecStart = pev->origin + pev->view_ofs; + vecEnd = entity_origin; + + angle_to_entity = BotInFieldOfView( vecEnd - vecStart ); + + // check if entity is outside field of view (+/- 45 degrees) + if (angle_to_entity > 45) + continue; // skip this item if bot can't "see" it + + // check if entity is a ladder (ladders are a special case)... + if (strcmp("func_ladder", item_name) == 0) + { + // force ladder origin to same z coordinate as bot since + // the VecBModelOrigin is the center of the ladder. For + // LONG ladders, the center MAY be hundreds of units above + // the bot. Fake an origin at the same level as the bot... + + entity_origin.z = pev->origin.z; + vecEnd = entity_origin; + + // trace a line from bot's eyes to func_ladder entity... + UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, ENT(pev), &tr); + + // check if traced all the way up to the entity (didn't hit wall) + if (tr.flFraction >= 1.0) + { + // find distance to item for later use... + float distance = (vecEnd - vecStart).Length( ); + + // use the ladder about 100% of the time, if haven't + // used a ladder in at least 5 seconds... + if ((RANDOM_LONG(1, 100) <= 100) && + ((f_use_ladder_time + 5) < gpGlobals->time)) + { + // if close to ladder... + if (distance < 100) + { + // don't avoid walls for a while + f_dont_avoid_wall_time = gpGlobals->time + 5.0; + } + + can_pickup = TRUE; + } + } + } + else + { + // trace a line from bot's eyes to func_ entity... + UTIL_TraceLine( vecStart, vecEnd, dont_ignore_monsters, ENT(pev), &tr); + + // check if traced all the way up to the entity (didn't hit wall) + if (strcmp(item_name, STRING(tr.pHit->v.classname)) == 0) + { + // find distance to item for later use... + float distance = (vecEnd - vecStart).Length( ); + + // check if entity is wall mounted health charger... + if ( strcmp("func_healthcharger", item_name) == 0 || + strcmp("item_healthcharger", item_name) == 0 ) + { + // check if the bot can use this item and + // check if the recharger is ready to use (has power left)... + if ((pev->health < 100) && (pEntity->pev->frame == 0)) + { + // check if flag not set... + if (!b_use_health_station) + { + // check if close enough and facing it directly... + if ((distance < PLAYER_SEARCH_RADIUS) && + (angle_to_entity <= 10)) + { + b_use_health_station = TRUE; + f_use_health_time = gpGlobals->time; + } + + // if close to health station... + if (distance < 100) + { + // don't avoid walls for a while + f_dont_avoid_wall_time = gpGlobals->time + 5.0; + } + + can_pickup = TRUE; + } + } + else + { + // don't need or can't use this item... + b_use_health_station = FALSE; + } + } + + // check if entity is wall mounted HEV charger... + else if ( strcmp("func_recharge", item_name) == 0 || + strcmp("item_recharge", item_name) == 0 ) + { + // check if the bot can use this item and + // check if the recharger is ready to use (has power left)... + if ((pev->armorvalue < MAX_NORMAL_BATTERY) && + (pev->weapons & (1<pev->frame == 0)) + { + // check if flag not set and facing it... + if (!b_use_HEV_station) + { + // check if close enough and facing it directly... + if ((distance < PLAYER_SEARCH_RADIUS) && + (angle_to_entity <= 10)) + { + b_use_HEV_station = TRUE; + f_use_HEV_time = gpGlobals->time; + } + + // if close to HEV recharger... + if (distance < 100) + { + // don't avoid walls for a while + f_dont_avoid_wall_time = gpGlobals->time + 5.0; + } + + can_pickup = TRUE; + } + } + else + { + // don't need or can't use this item... + b_use_HEV_station = FALSE; + } + } + + // check if entity is a button... + else if (strcmp("func_button", item_name) == 0) + { + // use the button about 100% of the time, if haven't + // used a button in at least 5 seconds... + if ((RANDOM_LONG(1, 100) <= 100) && + ((f_use_button_time + 5) < gpGlobals->time)) + { + // check if flag not set and facing it... + if (!b_use_button) + { + // check if close enough and facing it directly... + if ((distance < PLAYER_SEARCH_RADIUS) && + (angle_to_entity <= 10)) + { + b_use_button = TRUE; + b_lift_moving = FALSE; + f_use_button_time = gpGlobals->time; + } + + // if close to button... + if (distance < 100) + { + // don't avoid walls for a while + f_dont_avoid_wall_time = gpGlobals->time + 5.0; + } + + can_pickup = TRUE; + } + } + else + { + // don't need or can't use this item... + b_use_button = FALSE; + } + } + } + } + } + + // check if entity is an armed tripmine beam + else if (strcmp("beam", item_name) == 0) + { + CBeam *pBeam = (CBeam *)pEntity; + +// Vector v_beam_start = pBeam->GetStartPos(); +// Vector v_beam_end = pBeam->GetEndPos(); +// +// if (FInViewCone( &v_beam_start ) && FVisible( v_beam_start )) +// { +// BotDebug("I see a beam start!\n"); +// } +// +// if (FInViewCone( &v_beam_end ) && FVisible( v_beam_end )) +// { +// BotDebug("I see a beam end!\n"); +// } + } + + else // everything else... + { + entity_origin = pEntity->pev->origin; + + vecStart = pev->origin + pev->view_ofs; + vecEnd = entity_origin; + + // find angles from bot origin to entity... + angle_to_entity = BotInFieldOfView( vecEnd - vecStart ); + + // check if entity is outside field of view (+/- 45 degrees) + if (angle_to_entity > 45) + continue; // skip this item if bot can't "see" it + + // check if line of sight to object is not blocked (i.e. visible) + if (BotEntityIsVisible( vecEnd )) + { + + // check if entity is a weapon... + if (strncmp("weapon_", item_name, 7) == 0) + { + CBasePlayerWeapon *pWeapon = (CBasePlayerWeapon *)pEntity; + + if ((pWeapon->m_pPlayer) || (pWeapon->pev->effects & EF_NODRAW)) + { + // someone owns this weapon or it hasn't respawned yet + continue; + } + + if (g_pGameRules->CanHavePlayerItem( this, pWeapon )) + { + can_pickup = TRUE; + } + } + + // check if entity is ammo... + else if (strncmp("ammo_", item_name, 5) == 0) + { + CBasePlayerAmmo *pAmmo = (CBasePlayerAmmo *)pEntity; + BOOL ammo_found = FALSE; + int i; + + // check if the item is not visible (i.e. has not respawned) + if (pAmmo->pev->effects & EF_NODRAW) + continue; + + i = 0; + while (ammo_check[i].ammo_name[0]) + { + if (strcmp(ammo_check[i].ammo_name, item_name) == 0) + { + ammo_found = TRUE; + + // see if the bot can pick up this item... + if (g_pGameRules->CanHaveAmmo( this, + ammo_check[i].weapon_name, ammo_check[i].max_carry)) + { + can_pickup = TRUE; + break; + } + } + + i++; + } + if (ammo_found == FALSE) + { + sprintf(message, "unknown ammo: %s\n", item_name); + BotDebug(message); + } + } + + // check if entity is a battery... + else if (strcmp("item_battery", item_name) == 0) + { + CItem *pBattery = (CItem *)pEntity; + + // check if the item is not visible (i.e. has not respawned) + if (pBattery->pev->effects & EF_NODRAW) + continue; + + // check if the bot can use this item... + if ((pev->armorvalue < MAX_NORMAL_BATTERY) && + (pev->weapons & (1<pev->effects & EF_NODRAW) + continue; + + // check if the bot can use this item... + if (pev->health < 100) + { + can_pickup = TRUE; + } + } + + // check if entity is a packed up weapons box... + else if (strcmp("weaponbox", item_name) == 0) + { + can_pickup = TRUE; + } + + // check if entity is the spot from RPG laser + else if (strcmp("laser_spot", item_name) == 0) + { + } + + // check if entity is an armed tripmine + else if (strcmp("monster_tripmine", item_name) == 0) + { + float distance = (pEntity->pev->origin - pev->origin).Length( ); + + if (b_see_tripmine) + { + // see if this tripmine is closer to bot... + if (distance < (v_tripmine_origin - pev->origin).Length()) + { + v_tripmine_origin = pEntity->pev->origin; + b_shoot_tripmine = FALSE; + + // see if bot is far enough to shoot the tripmine... + if (distance >= 375) + { + b_shoot_tripmine = TRUE; + } + } + } + else + { + b_see_tripmine = TRUE; + v_tripmine_origin = pEntity->pev->origin; + b_shoot_tripmine = FALSE; + + // see if bot is far enough to shoot the tripmine... + if (distance >= 375) // 375 is damage radius + { + b_shoot_tripmine = TRUE; + } + } + } + + // check if entity is an armed satchel charge + else if (strcmp("monster_satchel", item_name) == 0) + { + } + + // check if entity is a snark (squeak grenade) + else if (strcmp("monster_snark", item_name) == 0) + { + } + + } // end if object is visible + } // end else not "func_" entity + + if (can_pickup) // if the bot found something it can pickup... + { + float distance = (entity_origin - pev->origin).Length( ); + + // see if it's the closest item so far... + if (distance < min_distance) + { + min_distance = distance; // update the minimum distance + pPickupEntity = pEntity; // remember this entity + pickup_origin = entity_origin; // remember location of entity + } + } + } // end while loop + + if (pPickupEntity != NULL) + { + // let's head off toward that item... + Vector v_item = pickup_origin - pev->origin; + + Vector bot_angles = UTIL_VecToAngles( v_item ); + + pev->ideal_yaw = bot_angles.y; + + // check for wrap around of angle... + if (pev->ideal_yaw > 180) + pev->ideal_yaw -= 360; + if (pev->ideal_yaw < -180) + pev->ideal_yaw += 360; + + pBotPickupItem = pPickupEntity; // save the item bot is trying to get + } +} + + +void CBot::BotUseLift( float moved_distance ) +{ + // just need to press the button once, when the flag gets set... + if (f_use_button_time == gpGlobals->time) + { + pev->button = IN_USE; + + // face opposite from the button + pev->ideal_yaw = -pev->ideal_yaw; // rotate 180 degrees + + // check for wrap around of angle... + if (pev->ideal_yaw > 180) + pev->ideal_yaw -= 360; + if (pev->ideal_yaw < -180) + pev->ideal_yaw += 360; + } + + // check if the bot has waited too long for the lift to move... + if (((f_use_button_time + 2.0) < gpGlobals->time) && + (!b_lift_moving)) + { + // clear use button flag + b_use_button = FALSE; + + // bot doesn't have to set f_find_item since the bot + // should already be facing away from the button + + f_move_speed = f_max_speed; + } + + // check if lift has started moving... + if ((moved_distance > 1) && (!b_lift_moving)) + { + b_lift_moving = TRUE; + } + + // check if lift has stopped moving... + if ((moved_distance <= 1) && (b_lift_moving)) + { + TraceResult tr1, tr2; + Vector v_src, v_forward, v_right, v_left; + Vector v_down, v_forward_down, v_right_down, v_left_down; + + b_use_button = FALSE; + + // TraceLines in 4 directions to find which way to go... + + UTIL_MakeVectors( pev->v_angle ); + + v_src = pev->origin + pev->view_ofs; + v_forward = v_src + gpGlobals->v_forward * 90; + v_right = v_src + gpGlobals->v_right * 90; + v_left = v_src + gpGlobals->v_right * -90; + + v_down = pev->v_angle; + v_down.x = v_down.x + 45; // look down at 45 degree angle + + UTIL_MakeVectors( v_down ); + + v_forward_down = v_src + gpGlobals->v_forward * 100; + v_right_down = v_src + gpGlobals->v_right * 100; + v_left_down = v_src + gpGlobals->v_right * -100; + + // try tracing forward first... + UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), &tr1); + UTIL_TraceLine( v_src, v_forward_down, dont_ignore_monsters, ENT(pev), &tr2); + + // check if we hit a wall or didn't find a floor... + if ((tr1.flFraction < 1.0) || (tr2.flFraction >= 1.0)) + { + // try tracing to the RIGHT side next... + UTIL_TraceLine( v_src, v_right, dont_ignore_monsters, ENT(pev), &tr1); + UTIL_TraceLine( v_src, v_right_down, dont_ignore_monsters, ENT(pev), &tr2); + + // check if we hit a wall or didn't find a floor... + if ((tr1.flFraction < 1.0) || (tr2.flFraction >= 1.0)) + { + // try tracing to the LEFT side next... + UTIL_TraceLine( v_src, v_left, dont_ignore_monsters, ENT(pev), &tr1); + UTIL_TraceLine( v_src, v_left_down, dont_ignore_monsters, ENT(pev), &tr2); + + // check if we hit a wall or didn't find a floor... + if ((tr1.flFraction < 1.0) || (tr2.flFraction >= 1.0)) + { + // only thing to do is turn around... + pev->ideal_yaw += 180; // turn all the way around + } + else + { + pev->ideal_yaw += 90; // turn to the LEFT + } + } + else + { + pev->ideal_yaw -= 90; // turn to the RIGHT + } + + // check for wrap around of angle... + if (pev->ideal_yaw > 180) + pev->ideal_yaw -= 360; + if (pev->ideal_yaw < -180) + pev->ideal_yaw += 360; + } + + BotChangeYaw( pev->yaw_speed ); + + f_move_speed = f_max_speed; + } +} + + +void CBot::BotTurnAtWall( TraceResult *tr ) +{ + Vector Normal; + float Y, Y1, Y2, D1, D2, Z; + + // Find the normal vector from the trace result. The normal vector will + // be a vector that is perpendicular to the surface from the TraceResult. + + Normal = UTIL_VecToAngles(tr->vecPlaneNormal); + + // Since the bot keeps it's view angle in -180 < x < 180 degrees format, + // and since TraceResults are 0 < x < 360, we convert the bot's view + // angle (yaw) to the same format at TraceResult. + + Y = pev->v_angle.y; + Y = Y + 180; + if (Y > 359) Y -= 360; + + // Turn the normal vector around 180 degrees (i.e. make it point towards + // the wall not away from it. That makes finding the angles that the + // bot needs to turn a little easier. + + Normal.y = Normal.y - 180; + if (Normal.y < 0) + Normal.y += 360; + + // Here we compare the bots view angle (Y) to the Normal - 90 degrees (Y1) + // and the Normal + 90 degrees (Y2). These two angles (Y1 & Y2) represent + // angles that are parallel to the wall surface, but heading in opposite + // directions. We want the bot to choose the one that will require the + // least amount of turning (saves time) and have the bot head off in that + // direction. + + Y1 = Normal.y - 90; + if (RANDOM_LONG(1, 100) <= 50) + { + Y1 = Y1 - RANDOM_FLOAT(5.0, 20.0); + } + if (Y1 < 0) Y1 += 360; + + Y2 = Normal.y + 90; + if (RANDOM_LONG(1, 100) <= 50) + { + Y2 = Y2 + RANDOM_FLOAT(5.0, 20.0); + } + if (Y2 > 359) Y2 -= 360; + + // D1 and D2 are the difference (in degrees) between the bot's current + // angle and Y1 or Y2 (respectively). + + D1 = abs(Y - Y1); + if (D1 > 179) D1 = abs(D1 - 360); + D2 = abs(Y - Y2); + if (D2 > 179) D2 = abs(D2 - 360); + + // If difference 1 (D1) is more than difference 2 (D2) then the bot will + // have to turn LESS if it heads in direction Y1 otherwise, head in + // direction Y2. I know this seems backwards, but try some sample angles + // out on some graph paper and go through these equations using a + // calculator, you'll see what I mean. + + if (D1 > D2) + Z = Y1; + else + Z = Y2; + + // convert from TraceResult 0 to 360 degree format back to bot's + // -180 to 180 degree format. + + if (Z > 180) + Z -= 360; + + // set the direction to head off into... + pev->ideal_yaw = Z; + + // check for wrap around of angle... + if (pev->ideal_yaw > 180) + pev->ideal_yaw -= 360; + if (pev->ideal_yaw < -180) + pev->ideal_yaw += 360; + + f_move_speed = 0; // don't move while turning +} + + +BOOL CBot::BotCantMoveForward( TraceResult *tr ) +{ + // use some TraceLines to determine if anything is blocking the current + // path of the bot. + + Vector v_src, v_forward; + + UTIL_MakeVectors( pev->v_angle ); + + // first do a trace from the bot's eyes forward... + + v_src = pev->origin + pev->view_ofs; // EyePosition() + v_forward = v_src + gpGlobals->v_forward * 40; + + // trace from the bot's eyes straight forward... + UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0) + { + return TRUE; // bot's head will hit something + } + + // bot's head is clear, check at waist level... + + v_src = pev->origin; + v_forward = v_src + gpGlobals->v_forward * 40; + + // trace from the bot's waist straight forward... + UTIL_TraceLine( v_src, v_forward, dont_ignore_monsters, ENT(pev), tr); + + // check if the trace hit something... + if (tr->flFraction < 1.0) + { + return TRUE; // bot's body will hit something + } + + return FALSE; // bot can move forward, return false +} + + +BOOL CBot::BotCanJumpUp( void ) +{ + // What I do here is trace 3 lines straight out, one unit higher than + // the highest normal jumping distance. I trace once at the center of + // the body, once at the right side, and once at the left side. If all + // three of these TraceLines don't hit an obstruction then I know the + // area to jump to is clear. I then need to trace from head level, + // above where the bot will jump to, downward to see if there is anything + // blocking the jump. There could be a narrow opening that the body + // will not fit into. These horizontal and vertical TraceLines seem + // to catch most of the problems with falsely trying to jump on something + // that the bot can not get onto. + + TraceResult tr; + Vector v_jump, v_source, v_dest; + + // convert current view angle to vectors for TraceLine math... + + v_jump = pev->v_angle; + v_jump.x = 0; // reset pitch to 0 (level horizontally) + v_jump.z = 0; // reset roll to 0 (straight up and down) + + UTIL_MakeVectors( v_jump ); + + // use center of the body first... + + // maximum jump height is 45, so check one unit above that (46) + v_source = pev->origin + Vector(0, 0, -36 + 46); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at maximum jump height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now check same height to one side of the bot... + v_source = pev->origin + gpGlobals->v_right * 16 + Vector(0, 0, -36 + 46); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at maximum jump height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now check same height on the other side of the bot... + v_source = pev->origin + gpGlobals->v_right * -16 + Vector(0, 0, -36 + 46); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at maximum jump height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now trace from head level downward to check for obstructions... + + // start of trace is 24 units in front of bot, 72 units above head... + v_source = pev->origin + gpGlobals->v_forward * 24; + + // offset 72 units from top of head (72 + 36) + v_source.z = v_source.z + 108; + + // end point of trace is 99 units straight down from start... + // (99 is 108 minus the jump limit height which is 45 - 36 = 9) + v_dest = v_source + Vector(0, 0, -99); + + // trace a line straight down toward the ground... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now check same height to one side of the bot... + v_source = pev->origin + gpGlobals->v_right * 16 + gpGlobals->v_forward * 24; + v_source.z = v_source.z + 108; + v_dest = v_source + Vector(0, 0, -99); + + // trace a line straight down toward the ground... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now check same height on the other side of the bot... + v_source = pev->origin + gpGlobals->v_right * -16 + gpGlobals->v_forward * 24; + v_source.z = v_source.z + 108; + v_dest = v_source + Vector(0, 0, -99); + + // trace a line straight down toward the ground... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + return TRUE; +} + + +BOOL CBot::BotCanDuckUnder( void ) +{ + // What I do here is trace 3 lines straight out, one unit higher than + // the ducking height. I trace once at the center of the body, once + // at the right side, and once at the left side. If all three of these + // TraceLines don't hit an obstruction then I know the area to duck to + // is clear. I then need to trace from the ground up, 72 units, to make + // sure that there is something blocking the TraceLine. Then we know + // we can duck under it. + + TraceResult tr; + Vector v_duck, v_source, v_dest; + + // convert current view angle to vectors for TraceLine math... + + v_duck = pev->v_angle; + v_duck.x = 0; // reset pitch to 0 (level horizontally) + v_duck.z = 0; // reset roll to 0 (straight up and down) + + UTIL_MakeVectors( v_duck ); + + // use center of the body first... + + // duck height is 36, so check one unit above that (37) + v_source = pev->origin + Vector(0, 0, -36 + 37); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at duck height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now check same height to one side of the bot... + v_source = pev->origin + gpGlobals->v_right * 16 + Vector(0, 0, -36 + 37); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at duck height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now check same height on the other side of the bot... + v_source = pev->origin + gpGlobals->v_right * -16 + Vector(0, 0, -36 + 37); + v_dest = v_source + gpGlobals->v_forward * 24; + + // trace a line forward at duck height... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); + + // if trace hit something, return FALSE + if (tr.flFraction < 1.0) + return FALSE; + + // now trace from the ground up to check for object to duck under... + + // start of trace is 24 units in front of bot near ground... + v_source = pev->origin + gpGlobals->v_forward * 24; + v_source.z = v_source.z - 35; // offset to feet + 1 unit up + + // end point of trace is 72 units straight up from start... + v_dest = v_source + Vector(0, 0, 72); + + // trace a line straight up in the air... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); + + // if trace didn't hit something, return FALSE + if (tr.flFraction >= 1.0) + return FALSE; + + // now check same height to one side of the bot... + v_source = pev->origin + gpGlobals->v_right * 16 + gpGlobals->v_forward * 24; + v_source.z = v_source.z - 35; // offset to feet + 1 unit up + v_dest = v_source + Vector(0, 0, 72); + + // trace a line straight up in the air... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); + + // if trace didn't hit something, return FALSE + if (tr.flFraction >= 1.0) + return FALSE; + + // now check same height on the other side of the bot... + v_source = pev->origin + gpGlobals->v_right * -16 + gpGlobals->v_forward * 24; + v_source.z = v_source.z - 35; // offset to feet + 1 unit up + v_dest = v_source + Vector(0, 0, 72); + + // trace a line straight up in the air... + UTIL_TraceLine( v_source, v_dest, dont_ignore_monsters, ENT(pev), &tr); + + // if trace didn't hit something, return FALSE + if (tr.flFraction >= 1.0) + return FALSE; + + return TRUE; +} + + +BOOL CBot::BotShootTripmine( void ) +{ + if (b_shoot_tripmine != TRUE) + return FALSE; + + // aim at the tripmine and fire the glock... + + Vector v_enemy = v_tripmine_origin - GetGunPosition( ); + + pev->v_angle = UTIL_VecToAngles( v_enemy ); + + pev->angles.x = 0; + pev->angles.y = pev->v_angle.y; + pev->angles.z = 0; + + pev->ideal_yaw = pev->v_angle.y; + + // check for wrap around of angle... + if (pev->ideal_yaw > 180) + pev->ideal_yaw -= 360; + if (pev->ideal_yaw < -180) + pev->ideal_yaw += 360; + + pev->v_angle.x = -pev->v_angle.x; //adjust pitch to point gun + + return (BotFireWeapon( v_tripmine_origin, WEAPON_GLOCK, TRUE )); +} + + +BOOL CBot::BotFollowUser( void ) +{ + BOOL user_visible; + float f_distance; + + Vector vecEnd = pBotUser->pev->origin + pBotUser->pev->view_ofs; + + pev->v_angle.x = 0; // reset pitch to 0 (level horizontally) + pev->v_angle.z = 0; // reset roll to 0 (straight up and down) + + pev->angles.x = 0; + pev->angles.y = pev->v_angle.y; + pev->angles.z = 0; + + if (!pBotUser->IsAlive( )) + { + // the bot's user is dead! + pBotUser = NULL; + return FALSE; + } + + user_visible = FInViewCone( &vecEnd ) && FVisible( vecEnd ); + + // check if the "user" is still visible or if the user has been visible + // in the last 5 seconds (or the player just starting "using" the bot) + + if (user_visible || (f_bot_use_time + 5 > gpGlobals->time)) + { + if (user_visible) + f_bot_use_time = gpGlobals->time; // reset "last visible time" + + // face the user + Vector v_user = pBotUser->pev->origin - pev->origin; + Vector bot_angles = UTIL_VecToAngles( v_user ); + + pev->ideal_yaw = bot_angles.y; + + // check for wrap around of angle... + if (pev->ideal_yaw > 180) + pev->ideal_yaw -= 360; + if (pev->ideal_yaw < -180) + pev->ideal_yaw += 360; + + f_distance = v_user.Length( ); // how far away is the "user"? + + if (f_distance > 200) // run if distance to enemy is far + f_move_speed = f_max_speed; + else if (f_distance > 50) // walk if distance is closer + f_move_speed = f_max_speed / 2; + else // don't move if close enough + f_move_speed = 0.0; + + return TRUE; + } + else + { + // person to follow has gone out of sight... + pBotUser = NULL; + + return FALSE; + } +} + + +BOOL CBot::BotCheckWallOnLeft( void ) +{ + Vector v_src, v_left; + TraceResult tr; + + UTIL_MakeVectors( pev->v_angle ); + + // do a trace to the left... + + v_src = pev->origin; + v_left = v_src + gpGlobals->v_right * -40; // 40 units to the left + + UTIL_TraceLine( v_src, v_left, dont_ignore_monsters, ENT(pev), &tr); + + // check if the trace hit something... + if (tr.flFraction < 1.0) + { + if (f_wall_on_left == 0) + f_wall_on_left = gpGlobals->time; + + return TRUE; + } + + return FALSE; +} + + +BOOL CBot::BotCheckWallOnRight( void ) +{ + Vector v_src, v_right; + TraceResult tr; + + UTIL_MakeVectors( pev->v_angle ); + + // do a trace to the right... + + v_src = pev->origin; + v_right = v_src + gpGlobals->v_right * 40; // 40 units to the right + + UTIL_TraceLine( v_src, v_right, dont_ignore_monsters, ENT(pev), &tr); + + // check if the trace hit something... + if (tr.flFraction < 1.0) + { + if (f_wall_on_right == 0) + f_wall_on_right = gpGlobals->time; + + return TRUE; + } + + return FALSE; +} + + +void CBot::BotThink( void ) +{ + Vector v_diff; // vector from previous to current location + float moved_distance; // length of v_diff vector (distance bot moved) + float degrees_turned; + + /* + if (pBotEnemy != NULL) // does an enemy exist? + { + ALERT( at_console, "Bot has enemy\n" ); + //ALERT( at_console, "Bot has enemy %s\n", STRING(pBotEnemy->pev->classname) ); + } else + ALERT( at_console, "Bot has NO ENEMY\n" ); + */ + + // check if someone kicked the bot off of the server (DON'T RESPAWN!)... + if ((pev->takedamage == DAMAGE_NO) && (respawn_index >= 0)) + { + pev->health = 0; + pev->deadflag = DEAD_DEAD; // make the kicked bot be dead + + bot_respawn[respawn_index].is_used = FALSE; // this slot is now free + bot_respawn[respawn_index].state = BOT_IDLE; + respawn_index = -1; // indicate no slot used + + // fall through to next if statement (respawn_index will be -1) + } + + // is the round over (time/frag limit) or has the bot been removed? + if ((g_fGameOver) || (respawn_index == -1)) + { + CSound *pSound; + + // keep resetting the sound entity until the bot is respawned... + pSound = CSoundEnt::SoundPointerForIndex( CSoundEnt::ClientSoundIndex( edict() ) ); + if ( pSound ) + { + pSound->Reset(); + } + + return; + } + + pev->button = 0; // make sure no buttons are pressed + + // if the bot is dead, randomly press fire to respawn... + /* + if ((pev->health < 1) || (pev->deadflag != DEAD_NO)) + { + if (RANDOM_LONG(1, 100) > 50) + pev->button = IN_ATTACK; + + g_engfuncs.pfnRunPlayerMove( edict( ), pev->v_angle, f_move_speed, + 0, 0, pev->button, 0, + gpGlobals->frametime * 1000 ); + return; + } + */ + + // see if it's time to check for sv_maxspeed change... + if (f_speed_check_time <= gpGlobals->time) + { + // store current sv_maxspeed setting for quick access + f_max_speed = CVAR_GET_FLOAT("sv_maxspeed"); + + // set next check time to 2 seconds from now + f_speed_check_time = gpGlobals->time + 2.0; + } + + // see how far bot has moved since the previous position... + v_diff = v_prev_origin - pev->origin; + moved_distance = v_diff.Length(); + + v_prev_origin = pev->origin; // save current position as previous + + f_move_speed = f_max_speed; // set to max speed unless known otherwise + + // turn towards ideal_yaw by yaw_speed degrees + // + degrees_turned = BotChangeYaw( pev->yaw_speed ); + // + + if (degrees_turned >= pev->yaw_speed) + { + // if bot is still turning, turn in place by setting speed to 0 + f_move_speed = 0; + } + + else if (IsOnLadder( )) // check if the bot is on a ladder... + { + f_use_ladder_time = gpGlobals->time; + + BotOnLadder( moved_distance ); // go handle the ladder movement + } + + else // else handle movement related actions... + { + // bot is not on a ladder so clear ladder direction flag... + ladder_dir = 0; + + // Vyacheslav Dzhura: IMPORTNANT - DECAY'S BOT - FIND ENEMY + if (f_botdontshoot == 0) // is bot targeting turned on? + pBotEnemy = BotFindEnemy( ); // leave ";" to disable bot AI + else + pBotEnemy = NULL; // clear enemy pointer (no ememy for you!) + + if (pBotEnemy != NULL) // does an enemy exist? + { + BotShootAtEnemy( ); // shoot at the enemy + } + + else if (f_pause_time > gpGlobals->time) // is bot "paused"? + { + // you could make the bot look left then right, or look up + // and down, to make it appear that the bot is hunting for + // something (don't do anything right now) + + f_move_speed = 0; + } + + // is bot being "used" and can still follow "user"? + else if ((pBotUser != NULL) && BotFollowUser( )) + { + // do nothing here! + ; + } + + else + { + // no enemy, let's just wander around... + // + pev->v_angle.x = 0; // reset pitch to 0 (level horizontally) + pev->v_angle.z = 0; // reset roll to 0 (straight up and down) + + pev->angles.x = 0; + pev->angles.y = pev->v_angle.y; + pev->angles.z = 0; + // + + // check if bot should look for items now or not... + /*/ + if (f_find_item < gpGlobals->time) + { + BotFindItem( ); // see if there are any visible items + } + // */ + + // check if bot sees a tripmine... + + /* + if (b_see_tripmine) + { + // check if bot can shoot the tripmine... + if ((b_shoot_tripmine) && BotShootTripmine( )) + { + // shot at tripmine, may or may not have hit it, clear + // flags anyway, next BotFindItem will see it again if + // it is still there... + + b_shoot_tripmine = FALSE; + b_see_tripmine = FALSE; + + // pause for a while to allow tripmine to explode... + f_pause_time = gpGlobals->time + 0.5; + } + else // run away!!! + { + Vector tripmine_angles; + + tripmine_angles = UTIL_VecToAngles( v_tripmine_origin - pev->origin ); + + // face away from the tripmine + pev->ideal_yaw = -tripmine_angles.y; + + // check for wrap around of angle... + if (pev->ideal_yaw > 180) + pev->ideal_yaw -= 360; + if (pev->ideal_yaw < -180) + pev->ideal_yaw += 360; + + b_see_tripmine = FALSE; + + f_move_speed = 0; // don't run while turning + } + } + + // check if should use wall mounted health station... + else if (b_use_health_station) + { + if ((f_use_health_time + 10.0) > gpGlobals->time) + { + f_move_speed = 0; // don't move while using health station + + pev->button = IN_USE; + } + else + { + // bot is stuck trying to "use" a health station... + + b_use_health_station = FALSE; + + // don't look for items for a while since the bot + // could be stuck trying to get to an item + f_find_item = gpGlobals->time + 0.5; + } + } + + // check if should use wall mounted HEV station... + else if (b_use_HEV_station) + { + if ((f_use_HEV_time + 10.0) > gpGlobals->time) + { + f_move_speed = 0; // don't move while using HEV station + + pev->button = IN_USE; + } + else + { + // bot is stuck trying to "use" a HEV station... + + b_use_HEV_station = FALSE; + + // don't look for items for a while since the bot + // could be stuck trying to get to an item + f_find_item = gpGlobals->time + 0.5; + } + } + + else if (b_use_button) + { + f_move_speed = 0; // don't move while using elevator + + BotUseLift( moved_distance ); + } + + else */ + { + + TraceResult tr; + + if (pev->waterlevel == 3) // check if the bot is underwater... + { + BotUnderWater( ); + } + + // check if there is a wall on the left... + /*/ start comment + if (!BotCheckWallOnLeft()) + { + // if there was a wall on the left over 1/2 a second ago then + // 20% of the time randomly turn between 45 and 60 degrees + + if ((f_wall_on_left != 0) && + (f_wall_on_left <= gpGlobals->time - 0.5) && + (RANDOM_LONG(1, 100) <= 20)) + { + pev->ideal_yaw += RANDOM_LONG(45, 60); + + // check for wrap around of angle... + if (pev->ideal_yaw > 180) + pev->ideal_yaw -= 360; + if (pev->ideal_yaw < -180) + pev->ideal_yaw += 360; + + f_move_speed = 0; // don't move while turning + f_dont_avoid_wall_time = gpGlobals->time + 1.0; + } + + f_wall_on_left = 0; // reset wall detect time + } + + // check if there is a wall on the right... + if (!BotCheckWallOnRight()) + { + // if there was a wall on the right over 1/2 a second ago then + // 20% of the time randomly turn between 45 and 60 degrees + + if ((f_wall_on_right != 0) && + (f_wall_on_right <= gpGlobals->time - 0.5) && + (RANDOM_LONG(1, 100) <= 20)) + { + pev->ideal_yaw -= RANDOM_LONG(45, 60); + + // check for wrap around of angle... + if (pev->ideal_yaw > 180) + pev->ideal_yaw -= 360; + if (pev->ideal_yaw < -180) + pev->ideal_yaw += 360; + + f_move_speed = 0; // don't move while turning + f_dont_avoid_wall_time = gpGlobals->time + 1.0; + } + + f_wall_on_right = 0; // reset wall detect time + } + // end comment */ + + // check if bot is about to hit a wall. TraceResult gets returned + if ((f_dont_avoid_wall_time <= gpGlobals->time) && + BotCantMoveForward( &tr )) + { + // ADD LATER + // need to check if bot can jump up or duck under here... + // ADD LATER + + BotTurnAtWall( &tr ); + } + + // check if the bot hasn't moved much since the last location + // commented for Decay + /* + + else if ((moved_distance <= 1) && (!bot_was_paused)) + { + // the bot must be stuck! + + if (BotCanJumpUp( )) // can the bot jump onto something? + { + pev->button |= IN_JUMP; // jump up and move forward + } + else if (BotCanDuckUnder( )) // can the bot duck under something? + { + pev->button |= IN_DUCK; // duck down and move forward + } + else + { + f_move_speed = 0; // don't move while turning + + // turn randomly between 30 and 60 degress + // + if (wander_dir == WANDER_LEFT) + pev->ideal_yaw += RANDOM_LONG(30, 60); + else + pev->ideal_yaw -= RANDOM_LONG(30, 60); + // + // check for wrap around of angle... + if (pev->ideal_yaw > 180) + pev->ideal_yaw -= 360; + if (pev->ideal_yaw < -180) + pev->ideal_yaw += 360; + + + // is the bot trying to get to an item?... + if (pBotPickupItem != NULL) + { + // don't look for items for a while since the bot + // could be stuck trying to get to an item + f_find_item = gpGlobals->time + 0.5; + } + } + }// */ + + // should the bot pause for a while here? + if ((RANDOM_LONG(1, 1000) <= pause_frequency[bot_skill]) && + (pBotUser == NULL)) // don't pause if being "used" + { + // set the time that the bot will stop "pausing" + f_pause_time = gpGlobals->time + + RANDOM_FLOAT(pause_time[bot_skill][0], + pause_time[bot_skill][1]); + f_move_speed = 0; // don't move while turning + } + } + } + } + + if (f_move_speed < 1) + bot_was_paused = TRUE; + else + bot_was_paused = FALSE; + + // TheFatal START - from www.telefragged.com/thefatal/jumblow.shtml + + // disable movement for Decay + f_move_speed = 0; + g_engfuncs.pfnRunPlayerMove( edict( ), pev->v_angle, f_move_speed, + 0, 0, pev->button, 0, + gpGlobals->frametime * 1000 ); + // TheFatal - END +} + diff --git a/dlls/bot.h b/dlls/bot.h index f27b5b34..aa01c958 100644 --- a/dlls/bot.h +++ b/dlls/bot.h @@ -1,177 +1,177 @@ -// botman's Half-Life bot example -// -// http://planethalflife.com/botman/ -// -// bot.h -// - -#ifndef BOT_H -#define BOT_H - -#define LADDER_UP 1 -#define LADDER_DOWN 2 - -#define WANDER_LEFT 1 -#define WANDER_RIGHT 2 - -#define MODEL_GINA 1 -#define MODEL_COLETTE 2 - -#define BOT_YAW_SPEED 20 // degrees per 10th of second turning speed - -#define BOT_SKIN_LEN 128 -#define BOT_NAME_LEN 31 - -#define ENEMY_HEADCRAB 0 -#define ENEMY_ZOMBIE 1 -#define ENEMY_BULLSQUID 2 -#define ENEMY_ASLAVE 3 -#define ENEMY_HOUNDEYE 4 -#define ENEMY_AGRUNT 5 -#define ENEMY_HGRUNT 6 -#define ENEMY_CONTROLLER 7 -#define ENEMY_FLYER 8 -#define ENEMY_UNKNOWN 254 -#define ENEMY_NONE 255 - -typedef struct // used in checking if bot can pick up ammo -{ - const char *ammo_name; - const char *weapon_name; - int max_carry; -} ammo_check_t; - -#define BOT_IDLE 0 -#define BOT_NEED_TO_KICK 1 -#define BOT_NEED_TO_RESPAWN 2 -#define BOT_IS_RESPAWNING 3 - -typedef struct // used to respawn bot at end of round (time/frag limit) -{ - BOOL is_used; // is this slot in use? - int state; // current state of the bot - char skin[BOT_SKIN_LEN+1]; - char name[BOT_NAME_LEN+1]; - char skill[2]; - CBasePlayer *pBot; -} respawn_t; - -#define GI_SND1 "gina/gina_attacked0.wav" -#define GI_SND2 "gina/gina_attacked1.wav " -#define GI_SND3 "gina/gina_pain1.wav " -#define GI_SND4 "gina/gina_pain2.wav " -#define GI_SND5 "gina/gina_pain3.wav " - -#define CO_SND1 "colette/colette_attacked0.wav" -#define CO_SND2 "colette/colette_attacked1.wav" -#define CO_SND3 "colette/colette_pain0.wav" -#define CO_SND4 "colette/colette_pain2.wav" -#define CO_SND5 "colette/colette_pain3.wav" - -// -// joy after successful enemy kill -// - -#define CO_TNT1 "colette/colette_kill0.wav" -#define CO_TNT2 "colette/colette_kill1.wav" -#define CO_TNT3 "colette/colette_kill2.wav" -#define CO_TNT4 "colette/colette_kill3.wav" -#define CO_TNT5 "colette/colette_kill4.wav" - -#define GI_TNT1 "gina/gina_kill0.wav" -#define GI_TNT2 "gina/gina_kill1.wav" -#define GI_TNT3 "gina/gina_kill2.wav" -#define GI_TNT4 "gina/gina_kill3.wav" -#define GI_TNT5 "gina/gina_kill4.wav" - -#define USE_TEAMPLAY_SND "barney/teamup2.wav" -#define USE_TEAMPLAY_LATER_SND "barney/seeya.wav" -#define USE_TEAMPLAY_ENEMY_SND "barney/ba_raincheck.wav" - -void BotDebug( char *buffer ); // print out message to HUD for debugging - - -class CBot : public CBasePlayer //Derive a bot class from CBasePlayer -{ - public: - Vector v_prev_origin; // previous origin (i.e. location) - float f_shoot_time; // next time to shoot weapon at - float f_max_speed; // last sv_maxspeed setting - float f_speed_check_time; // check sv_maxspeed every so often - float f_move_speed; // speed at which the bot will move - int ladder_dir; // direction traveling on ladder (UP or DOWN) - int wander_dir; // randomly wander left or right - float f_pause_time; // timeout for periods when the bot pauses - float f_find_item; // timeout for not looking for items - char model_name[64]; - int bot_model; - int bot_skill; // bot skill level (0=very good, 4=very bad) - float f_pain_time; // time when pain sound can be spoken - BOOL b_use_health_station; // set if bot should "use" health station - float f_use_health_time; // time when b_use_health_station is set - BOOL b_use_HEV_station; // set if bot should "use" HEV station - float f_use_HEV_time; // time when b_use_HEV_station is set - BOOL b_use_button; // set if bot should "use" button - float f_use_button_time; // time when b_use_button is set - BOOL b_lift_moving; // flag set when lift (elevator) is moving - float f_use_ladder_time; // time when bot sees a ladder - BOOL b_see_tripmine; // set if bot "sees" a tripmine - BOOL b_shoot_tripmine; // set if bot should shoot a tripmine - Vector v_tripmine_origin; // origin of tripmine - float f_fire_gauss; // time to release secondary fire on gauss gun - BOOL bot_was_paused; // TRUE if bot was previously "paused" - float f_weapon_inventory_time; // time to check weapon inventory - int respawn_index; // index in respawn structure for this bot - float f_dont_avoid_wall_time; // time when avoiding walls is OK - float f_bot_use_time; // time the bot was "used" by player - float f_wall_on_left; // time since bot has had a wall on the left - float f_wall_on_right; // time since bot has had a wall on the right - - CBaseEntity *pBotEnemy; // pointer to bot's enemy - EOFFSET pBotEnemyOffset; - byte pBotEnemyClass; - CBaseEntity *pBotUser; // pointer to player using bot - CBaseEntity *pBotPickupItem; // pointer to item we are trying to get - CBasePlayerItem *weapon_ptr[MAX_WEAPONS]; // pointer array to weapons - int primary_ammo[MAX_WEAPONS]; // amount of primary ammo available - int secondary_ammo[MAX_WEAPONS]; // amount of secondary ammo available - - char message[256]; // buffer for debug messages - - void Spawn( void ); - void BotThink( void ); // think function for the bot - - // Bots should return FALSE for this, they can't receive NET messages - virtual BOOL IsNetClient( void ) { return FALSE; } - - int BloodColor() { return BLOOD_COLOR_RED; } - int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, - float flDamage, int bitsDamageType ); - int ObjectCaps() { return FCAP_IMPULSE_USE; }; - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, - USE_TYPE useType, float value ); - - int BotInFieldOfView( Vector dest ); - BOOL BotEntityIsVisible( Vector dest ); - float BotChangeYaw( float speed ); - void BotOnLadder( float moved_distance ); - void BotUnderWater( void ); - CBaseEntity * BotFindEnemy( void ); - Vector BotBodyTarget( CBaseEntity *pBotEnemy ); - void BotWeaponInventory( void ); - BOOL BotFireWeapon( Vector enemy, int weapon_choice = 0, BOOL primary = TRUE ); - void BotShootAtEnemy( void ); - void BotFindItem( void ); - void BotUseLift( float moved_distance ); - void BotTurnAtWall( TraceResult *tr ); - BOOL BotCantMoveForward( TraceResult *tr ); - BOOL BotCanJumpUp( void ); - BOOL BotCanDuckUnder( void ); - BOOL BotShootTripmine( void ); - BOOL BotFollowUser( void ); // returns FALSE if can find "user" - BOOL BotCheckWallOnLeft( void ); - BOOL BotCheckWallOnRight( void ); -}; - -#endif // BOT_H - +// botman's Half-Life bot example +// +// http://planethalflife.com/botman/ +// +// bot.h +// + +#ifndef BOT_H +#define BOT_H + +#define LADDER_UP 1 +#define LADDER_DOWN 2 + +#define WANDER_LEFT 1 +#define WANDER_RIGHT 2 + +#define MODEL_GINA 1 +#define MODEL_COLETTE 2 + +#define BOT_YAW_SPEED 20 // degrees per 10th of second turning speed + +#define BOT_SKIN_LEN 128 +#define BOT_NAME_LEN 31 + +#define ENEMY_HEADCRAB 0 +#define ENEMY_ZOMBIE 1 +#define ENEMY_BULLSQUID 2 +#define ENEMY_ASLAVE 3 +#define ENEMY_HOUNDEYE 4 +#define ENEMY_AGRUNT 5 +#define ENEMY_HGRUNT 6 +#define ENEMY_CONTROLLER 7 +#define ENEMY_FLYER 8 +#define ENEMY_UNKNOWN 254 +#define ENEMY_NONE 255 + +typedef struct // used in checking if bot can pick up ammo +{ + const char *ammo_name; + const char *weapon_name; + int max_carry; +} ammo_check_t; + +#define BOT_IDLE 0 +#define BOT_NEED_TO_KICK 1 +#define BOT_NEED_TO_RESPAWN 2 +#define BOT_IS_RESPAWNING 3 + +typedef struct // used to respawn bot at end of round (time/frag limit) +{ + BOOL is_used; // is this slot in use? + int state; // current state of the bot + char skin[BOT_SKIN_LEN+1]; + char name[BOT_NAME_LEN+1]; + char skill[2]; + CBasePlayer *pBot; +} respawn_t; + +#define GI_SND1 "gina/gina_attacked0.wav" +#define GI_SND2 "gina/gina_attacked1.wav " +#define GI_SND3 "gina/gina_pain1.wav " +#define GI_SND4 "gina/gina_pain2.wav " +#define GI_SND5 "gina/gina_pain3.wav " + +#define CO_SND1 "colette/colette_attacked0.wav" +#define CO_SND2 "colette/colette_attacked1.wav" +#define CO_SND3 "colette/colette_pain0.wav" +#define CO_SND4 "colette/colette_pain2.wav" +#define CO_SND5 "colette/colette_pain3.wav" + +// +// joy after successful enemy kill +// + +#define CO_TNT1 "colette/colette_kill0.wav" +#define CO_TNT2 "colette/colette_kill1.wav" +#define CO_TNT3 "colette/colette_kill2.wav" +#define CO_TNT4 "colette/colette_kill3.wav" +#define CO_TNT5 "colette/colette_kill4.wav" + +#define GI_TNT1 "gina/gina_kill0.wav" +#define GI_TNT2 "gina/gina_kill1.wav" +#define GI_TNT3 "gina/gina_kill2.wav" +#define GI_TNT4 "gina/gina_kill3.wav" +#define GI_TNT5 "gina/gina_kill4.wav" + +#define USE_TEAMPLAY_SND "barney/teamup2.wav" +#define USE_TEAMPLAY_LATER_SND "barney/seeya.wav" +#define USE_TEAMPLAY_ENEMY_SND "barney/ba_raincheck.wav" + +void BotDebug( char *buffer ); // print out message to HUD for debugging + + +class CBot : public CBasePlayer //Derive a bot class from CBasePlayer +{ + public: + Vector v_prev_origin; // previous origin (i.e. location) + float f_shoot_time; // next time to shoot weapon at + float f_max_speed; // last sv_maxspeed setting + float f_speed_check_time; // check sv_maxspeed every so often + float f_move_speed; // speed at which the bot will move + int ladder_dir; // direction traveling on ladder (UP or DOWN) + int wander_dir; // randomly wander left or right + float f_pause_time; // timeout for periods when the bot pauses + float f_find_item; // timeout for not looking for items + char model_name[64]; + int bot_model; + int bot_skill; // bot skill level (0=very good, 4=very bad) + float f_pain_time; // time when pain sound can be spoken + BOOL b_use_health_station; // set if bot should "use" health station + float f_use_health_time; // time when b_use_health_station is set + BOOL b_use_HEV_station; // set if bot should "use" HEV station + float f_use_HEV_time; // time when b_use_HEV_station is set + BOOL b_use_button; // set if bot should "use" button + float f_use_button_time; // time when b_use_button is set + BOOL b_lift_moving; // flag set when lift (elevator) is moving + float f_use_ladder_time; // time when bot sees a ladder + BOOL b_see_tripmine; // set if bot "sees" a tripmine + BOOL b_shoot_tripmine; // set if bot should shoot a tripmine + Vector v_tripmine_origin; // origin of tripmine + float f_fire_gauss; // time to release secondary fire on gauss gun + BOOL bot_was_paused; // TRUE if bot was previously "paused" + float f_weapon_inventory_time; // time to check weapon inventory + int respawn_index; // index in respawn structure for this bot + float f_dont_avoid_wall_time; // time when avoiding walls is OK + float f_bot_use_time; // time the bot was "used" by player + float f_wall_on_left; // time since bot has had a wall on the left + float f_wall_on_right; // time since bot has had a wall on the right + + CBaseEntity *pBotEnemy; // pointer to bot's enemy + EOFFSET pBotEnemyOffset; + byte pBotEnemyClass; + CBaseEntity *pBotUser; // pointer to player using bot + CBaseEntity *pBotPickupItem; // pointer to item we are trying to get + CBasePlayerItem *weapon_ptr[MAX_WEAPONS]; // pointer array to weapons + int primary_ammo[MAX_WEAPONS]; // amount of primary ammo available + int secondary_ammo[MAX_WEAPONS]; // amount of secondary ammo available + + char message[256]; // buffer for debug messages + + void Spawn( void ); + void BotThink( void ); // think function for the bot + + // Bots should return FALSE for this, they can't receive NET messages + virtual BOOL IsNetClient( void ) { return FALSE; } + + int BloodColor() { return BLOOD_COLOR_RED; } + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, + float flDamage, int bitsDamageType ); + int ObjectCaps() { return FCAP_IMPULSE_USE; }; + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, + USE_TYPE useType, float value ); + + int BotInFieldOfView( Vector dest ); + BOOL BotEntityIsVisible( Vector dest ); + float BotChangeYaw( float speed ); + void BotOnLadder( float moved_distance ); + void BotUnderWater( void ); + CBaseEntity * BotFindEnemy( void ); + Vector BotBodyTarget( CBaseEntity *pBotEnemy ); + void BotWeaponInventory( void ); + BOOL BotFireWeapon( Vector enemy, int weapon_choice = 0, BOOL primary = TRUE ); + void BotShootAtEnemy( void ); + void BotFindItem( void ); + void BotUseLift( float moved_distance ); + void BotTurnAtWall( TraceResult *tr ); + BOOL BotCantMoveForward( TraceResult *tr ); + BOOL BotCanJumpUp( void ); + BOOL BotCanDuckUnder( void ); + BOOL BotShootTripmine( void ); + BOOL BotFollowUser( void ); // returns FALSE if can find "user" + BOOL BotCheckWallOnLeft( void ); + BOOL BotCheckWallOnRight( void ); +}; + +#endif // BOT_H + diff --git a/dlls/bot_combat.cpp b/dlls/bot_combat.cpp index 6f9f892d..b6e75108 100644 --- a/dlls/bot_combat.cpp +++ b/dlls/bot_combat.cpp @@ -1,1091 +1,1091 @@ -// botman's Half-Life bot example -// -// http://planethalflife.com/botman/ -// -// bot_combat.cpp -// - -#include "extdll.h" -#include "util.h" -#include "client.h" -#include "cbase.h" -#include "player.h" -#include "items.h" -#include "effects.h" -#include "weapons.h" -#include "soundent.h" -#include "gamerules.h" -#include "animation.h" -#include "monsters.h" - -#include "bot.h" - - -extern int f_Observer; // flag to indicate if player is in observer mode - -// weapon firing delay based on skill (min and max delay for each weapon) -float primary_fire_delay[WEAPON_SNARK+1][5][2] = { - // WEAPON_NONE - NOT USED - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_CROWBAR - {{0.0, 0.1}, {0.2, 0.3}, {0.3, 0.5}, {0.4, 0.6}, {0.6, 1.0}}, - // WEAPON_GLOCK (9mm) - {{0.0, 0.1}, {0.1, 0.2}, {0.2, 0.3}, {0.3, 0.4}, {0.4, 0.5}}, - // WEAPON_PYTHON (357) - {{0.0, 0.25}, {0.2, 0.5}, {0.4, 0.8}, {1.0, 1.3}, {1.5, 2.0}}, - // WEAPON_MP5 (9mmAR) - {{0.0, 0.1}, {0.1, 0.3}, {0.3, 0.5}, {0.4, 0.6}, {0.5, 0.8}}, - // WEAPON_CHAINGUN - NOT USED - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_CROSSBOW - {{0.0, 0.25}, {0.2, 0.4}, {0.5, 0.7}, {0.8, 1.0}, {1.0, 1.3}}, - // WEAPON_SHOTGUN - {{0.0, 0.25}, {0.2, 0.5}, {0.4, 0.8}, {0.6, 1.2}, {0.8, 2.0}}, - // WEAPON_RPG - {{1.0, 3.0}, {2.0, 4.0}, {3.0, 5.0}, {4.0, 6.0}, {5.0, 7.0}}, - // WEAPON_GAUSS - {{0.0, 0.1}, {0.2, 0.3}, {0.3, 0.5}, {0.5, 0.8}, {1.0, 1.2}}, - // WEAPON_EGON - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_HORNETGUN - {{0.0, 0.1}, {0.25, 0.4}, {0.4, 0.7}, {0.6, 1.0}, {1.0, 1.5}}, - // WEAPON_HANDGRENADE - {{1.0, 1.4}, {1.4, 2.0}, {1.8, 2.6}, {2.0, 3.0}, {2.5, 3.8}}, - // WEAPON_TRIPMINE - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_SATCHEL - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_SNARK - {{0.0, 0.1}, {0.1, 0.2}, {0.2, 0.5}, {0.5, 0.7}, {0.6, 1.0}}, - }; - -float secondary_fire_delay[WEAPON_SNARK+1][5][2] = { - // WEAPON_NONE - NOT USED - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_CROWBAR - Not applicable - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_GLOCK (9mm) - {{0.0, 0.1}, {0.0, 0.1}, {0.1, 0.2}, {0.1, 0.2}, {0.2, 0.4}}, - // WEAPON_PYTHON (357) - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_MP5 (9mmAR) - {{0.0, 0.3}, {0.5, 0.8}, {0.7, 1.0}, {1.0, 1.6}, {1.4, 2.0}}, - // WEAPON_CHAINGUN - NOT USED - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_CROSSBOW - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_SHOTGUN - {{0.0, 0.25}, {0.2, 0.5}, {0.4, 0.8}, {0.6, 1.2}, {0.8, 2.0}}, - // WEAPON_RPG - Not applicable - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_GAUSS - {{0.2, 0.5}, {0.3, 0.7}, {0.5, 1.0}, {0.8, 1.5}, {1.0, 2.0}}, - // WEAPON_EGON - Not applicable - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_HORNETGUN - {{0.0, 0.1}, {0.2, 0.3}, {0.3, 0.5}, {0.5, 0.8}, {0.7, 1.2}}, - // WEAPON_HANDGRENADE - Not applicable - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_TRIPMINE - Not applicable - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_SATCHEL - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, - // WEAPON_SNARK - Not applicable - {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}} - }; - -ammo_check_t ammo_check[] = { - {"ammo_glockclip", "9mm", _9MM_MAX_CARRY}, - {"ammo_9mmclip", "9mm", _9MM_MAX_CARRY}, - {"ammo_9mmAR", "9mm", _9MM_MAX_CARRY}, - {"ammo_9mmbox", "9mm", _9MM_MAX_CARRY}, - {"ammo_mp5clip", "9mm", _9MM_MAX_CARRY}, - {"ammo_chainboxclip", "9mm", _9MM_MAX_CARRY}, - {"ammo_mp5grenades", "ARgrenades", M203_GRENADE_MAX_CARRY}, - {"ammo_ARgrenades", "ARgrenades", M203_GRENADE_MAX_CARRY}, - {"ammo_buckshot", "buckshot", BUCKSHOT_MAX_CARRY}, - {"ammo_crossbow", "bolts", BOLT_MAX_CARRY}, - {"ammo_357", "357", _357_MAX_CARRY}, - {"ammo_rpgclip", "rockets", ROCKET_MAX_CARRY}, - {"ammo_egonclip", "uranium", URANIUM_MAX_CARRY}, - {"ammo_gaussclip", "uranium", URANIUM_MAX_CARRY}, - {"", 0, 0}}; - -typedef struct -{ - const char *enemy_name; - byte enemy_id; - byte preferred_weapons_id[3]; -} enemy_weapon_check_t; - -enemy_weapon_check_t enemy_weapon_check[] = { - { "monster_heacrab", ENEMY_HEADCRAB, {WEAPON_GLOCK, WEAPON_CROWBAR, WEAPON_NONE} }, - { "monster_zombie", ENEMY_ZOMBIE, {WEAPON_MP5, WEAPON_SHOTGUN, WEAPON_GLOCK} }, - { "monster_bullsquid", ENEMY_BULLSQUID, {WEAPON_PYTHON, WEAPON_SHOTGUN, WEAPON_MP5} }, - { "monster_aslave", ENEMY_ASLAVE, {WEAPON_MP5, WEAPON_SHOTGUN, WEAPON_GLOCK} }, - { "monster_houndeye", ENEMY_HOUNDEYE, {WEAPON_GLOCK, WEAPON_CROWBAR, WEAPON_NONE} }, - { "monster_alien_grunt", ENEMY_AGRUNT, {WEAPON_PYTHON, WEAPON_SHOTGUN, WEAPON_MP5} }, - { "monster_human_grunt", ENEMY_HGRUNT, {WEAPON_MP5, WEAPON_SHOTGUN, WEAPON_PYTHON} }, - { "monster_alien_controller", ENEMY_CONTROLLER, {WEAPON_PYTHON, WEAPON_MP5, WEAPON_SHOTGUN} }, - { "monster_alien_flyer", ENEMY_FLYER, {WEAPON_RPG, WEAPON_NONE, WEAPON_NONE} }, - { "unknown", ENEMY_UNKNOWN, {WEAPON_MP5, WEAPON_SHOTGUN, WEAPON_GLOCK} }, -}; - -#define WEAPON_CHECK_COUNT 10 - -/* - -#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_DISPLACER 16 -*/ - -// sounds for Bot taunting after a kill... -char gina_taunt[][30] = { GI_TNT1, GI_TNT2, GI_TNT3, GI_TNT4, GI_TNT5 }; -char colette_taunt[][30] = { CO_TNT1, CO_TNT2, CO_TNT3, CO_TNT4, CO_TNT5 }; - - -bool BOT_VICTIM_EXISTS( CBaseEntity *victim, EOFFSET victimOffset) -{ - CBaseEntity *pTest = CBaseEntity::Instance( victimOffset ); - - if ( pTest ) - { - if ( !FNullEnt( pTest->pev )) - { - if ( pTest->eoffset() == victim->eoffset() ) - return true; - else - return false; - } else - return false; - } else - return false; -} - -byte GetBotEnemyClassId( int classname ) -{ - char szEnemyClassName[64]; - strcpy( szEnemyClassName, STRING( classname ) ); - - for ( int i = 0; i < WEAPON_CHECK_COUNT; i++ ) - { - if ( stricmp( enemy_weapon_check[i].enemy_name, szEnemyClassName ) == 0 ) - return enemy_weapon_check[i].enemy_id; - } - return ENEMY_UNKNOWN; -} - -CBaseEntity * CBot::BotFindEnemy( void ) -{ - Vector vecEnd; - static BOOL flag=TRUE; - char sound[128]; // for taunting sounds - - // does the bot already have an enemy? - //if (pBotEnemy != NULL) //&& (pBotEnemy)) - - if ( pBotEnemy ) - if ( BOT_VICTIM_EXISTS( pBotEnemy, pBotEnemyOffset ) ) - // if (pBotEnemy->pev->deadflag == DEAD_NO) - { - vecEnd = pBotEnemy->EyePosition(); - - // if the enemy is dead or has switched to botcam mode... - if (!pBotEnemy->IsAlive() || (pBotEnemy->pev->effects & EF_NODRAW)) - { - if (!pBotEnemy->IsAlive()) // is the enemy dead?, assume bot killed it - { - // the enemy is dead - // speak taunt sounds about 10% of the time - if (RANDOM_LONG(1, 100) <= 10) - { - if (bot_model == MODEL_GINA) - strcpy( sound, gina_taunt[RANDOM_LONG(0,4)] ); - else if (bot_model == MODEL_COLETTE) - strcpy( sound, colette_taunt[RANDOM_LONG(0,4)] ); - - EMIT_SOUND(ENT(pBotEnemy->pev), CHAN_VOICE, sound, - RANDOM_FLOAT(0.9, 1.0), ATTN_NORM); - } - } - - // don't have an enemy anymore so null out the pointer... - pBotEnemy = NULL; - pBotEnemyOffset = 0; - } // <-- if dead or in botcam - else if (FInViewCone( &vecEnd ) && FVisible( vecEnd )) - { - // if enemy is still visible and in field of view, keep it - - // face the enemy - Vector v_enemy = pBotEnemy->pev->origin - pev->origin; - Vector bot_angles = UTIL_VecToAngles( v_enemy ); - - pev->ideal_yaw = bot_angles.y; - - // check for wrap around of angle... - if (pev->ideal_yaw > 180) - pev->ideal_yaw -= 360; - if (pev->ideal_yaw < -180) - pev->ideal_yaw += 360; - - return (pBotEnemy); - } // <-- if alive and visible - } - - int i; - float nearestdistance = 1000; - CBaseEntity *pNewEnemy = NULL; - -//=================================================================== -// BOTMAN'S CODE FOR FINDING PLAYER ENEMY -//=================================================================== -/* - // search the world for players... - for (i = 1; i <= gpGlobals->maxClients; i++) - { - CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); - - // skip invalid players and skip self (i.e. this bot) - if ((!pPlayer) || (pPlayer == this)) - continue; - - // skip this player if not alive (i.e. dead or dying) - if (pPlayer->pev->deadflag != DEAD_NO) - continue; - - vecEnd = pPlayer->EyePosition(); - - // see if bot can see the player... - if (FInViewCone( &vecEnd ) && FVisible( vecEnd )) - { - float distance = (pPlayer->pev->origin - pev->origin).Length(); - if (distance < nearestdistance) - { - nearestdistance = distance; - pNewEnemy = pPlayer; - - pBotUser = NULL; // don't follow user when enemy found - } - } - } -*/ -//=================================================================== -// END OF BOTMANS ENEMY SEARCHO CODE -//=================================================================== - - int iDistance = 2048; - - CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with - CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( 1 ); - if (!pPlayer) - { - ALERT( at_console, "Can not find player!\n" ); - return NULL; - } - /* - for ( int i = 1; i <= gpGlobals->maxClients; i++ ) - { - pPlayer = UTIL_PlayerByIndex( i ); - if ( pPlayer ) - }*/ - - Vector delta = Vector( iDistance, iDistance, iDistance ); - - // Find only monsters/clients in box, NOT limited to PVS - CBaseEntity *pList[100]; - int count = UTIL_EntitiesInBox( pList, 100, pev->origin - delta, pev->origin + delta, /*FL_CLIENT|*/ FL_MONSTER ); - for ( int i = 0; i < count; i++ ) - { - pSightEnt = pList[i]; - - // skip invalid players and skip self (i.e. this bot) - if ((!pSightEnt) || (pSightEnt == this)) - continue; - - // skip this player if not alive (i.e. dead or dying) - if (pSightEnt->pev->deadflag != DEAD_NO) - continue; - - if ( !FBitSet(pSightEnt->pev->flags, FL_MONSTER) ) - continue; - - // !!!temporarily only considering other monsters and clients, don't see prisoners - if ( //pSightEnt != this && - !FBitSet( pSightEnt->pev->spawnflags, SF_MONSTER_PRISONER ) && - pSightEnt->pev->health > 0 ) - { - vecEnd = pSightEnt->EyePosition(); - - // the looker will want to consider this entity - // don't check anything else about an entity that can't be seen, or an entity that you don't care about. - - // OLD STYLE: - if ( pPlayer->IRelationship( pSightEnt ) != R_NO && // if relationship not neutral - - //CBaseMonster *gNPC = (CBaseMonster*)pSightEnt; - - // NEW STYLE: - //if ( gNPC->IRelationship( pPlayer ) > 0 && // if relationship is agressive - - FInViewCone( &vecEnd ) && // if we see it - !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && // if it can be targeted - FVisible( pSightEnt->pev->origin ) // if it is visible - ) - { - /* - if ( pSightEnt->IsPlayer() ) - { - // do nothing (yet) - } - */ - - // see if bot can see the enemy... - float distance = (pSightEnt->pev->origin - pev->origin).Length(); - if (distance < nearestdistance) - { - nearestdistance = distance; - pNewEnemy = pSightEnt; - - pBotUser = NULL; // don't follow user when enemy found - } - - } - } - } - -// -// END OF DECAY'S ENEMY SEARCH CODE -// ****************************************************************** - - if (pNewEnemy) - { - pBotEnemyOffset = pNewEnemy->eoffset(); - pBotEnemyClass = GetBotEnemyClassId( pNewEnemy->pev->classname ); - - // face the enemy - Vector v_enemy = pNewEnemy->pev->origin - pev->origin; - Vector bot_angles = UTIL_VecToAngles( v_enemy ); - - pev->ideal_yaw = bot_angles.y; - - // check for wrap around of angle... - if (pev->ideal_yaw > 180) - pev->ideal_yaw -= 360; - if (pev->ideal_yaw < -180) - pev->ideal_yaw += 360; - } else - { - pBotEnemyOffset = 0; - pBotEnemyClass = ENEMY_NONE; - } - - return (pNewEnemy); -} - - -Vector CBot::BotBodyTarget( CBaseEntity *pBotEnemy ) -{ - Vector target; - float f_distance; - float f_scale; - int d_x, d_y, d_z; - - f_distance = (pBotEnemy->pev->origin - pev->origin).Length(); - - if (f_distance > 1000) - f_scale = 1.0; - else if (f_distance > 100) - f_scale = f_distance / 1000.0; - else - f_scale = 0.1; - - switch (bot_skill) - { - case 0: - // VERY GOOD, same as from CBasePlayer::BodyTarget (in player.h) - target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs * RANDOM_FLOAT( 0.5, 1.1 ); - d_x = 0; // no offset - d_y = 0; - d_z = 0; - break; - case 1: - // GOOD, offset a little for x, y, and z - target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs; - d_x = RANDOM_FLOAT(-5, 5) * f_scale; - d_y = RANDOM_FLOAT(-5, 5) * f_scale; - d_z = RANDOM_FLOAT(-9, 9) * f_scale; - break; - case 2: - // FAIR, offset somewhat for x, y, and z - target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs; - d_x = RANDOM_FLOAT(-9, 9) * f_scale; - d_y = RANDOM_FLOAT(-9, 9) * f_scale; - d_z = RANDOM_FLOAT(-15, 15) * f_scale; - break; - case 3: - // POOR, offset for x, y, and z - target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs; - d_x = RANDOM_FLOAT(-16, 16) * f_scale; - d_y = RANDOM_FLOAT(-16, 16) * f_scale; - d_z = RANDOM_FLOAT(-20, 20) * f_scale; - break; - case 4: - // BAD, offset lots for x, y, and z - target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs; - d_x = RANDOM_FLOAT(-20, 20) * f_scale; - d_y = RANDOM_FLOAT(-20, 20) * f_scale; - d_z = RANDOM_FLOAT(-27, 27) * f_scale; - break; - } - - target = target + Vector(d_x, d_y, d_z); - - return target; -} - - -void CBot::BotWeaponInventory( void ) -{ - int i; - - // initialize the elements of the weapons arrays... - for (i = 0; i < MAX_WEAPONS; i++) - { - weapon_ptr[i] = NULL; - primary_ammo[i] = 0; - secondary_ammo[i] = 0; - } - - // find out which weapons the bot is carrying... - for (i = 0; i < MAX_ITEM_TYPES; i++) - { - CBasePlayerItem *pItem = NULL; - - if (m_rgpPlayerItems[i]) - { - pItem = m_rgpPlayerItems[i]; - while (pItem) - { - weapon_ptr[pItem->m_iId] = pItem; // store pointer to item - - pItem = pItem->m_pNext; - } - } - } - - // find out how much ammo of each type the bot is carrying... - for (i = 0; i < MAX_AMMO_SLOTS; i++) - { - if (!CBasePlayerItem::AmmoInfoArray[i].pszName) - continue; - - if (strcmp("9mm", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) - { - primary_ammo[WEAPON_GLOCK] = m_rgAmmo[i]; - primary_ammo[WEAPON_MP5] = m_rgAmmo[i]; - } - else if (strcmp("357", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) - { - primary_ammo[WEAPON_PYTHON] = m_rgAmmo[i]; - } - else if (strcmp("ARgrenades", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) - secondary_ammo[WEAPON_MP5] = m_rgAmmo[i]; - else if (strcmp("bolts", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) - { - primary_ammo[WEAPON_CROSSBOW] = m_rgAmmo[i]; - } - else if (stricmp("buckshot", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) - { - primary_ammo[WEAPON_SHOTGUN] = m_rgAmmo[i]; - } - else if (stricmp("rockets", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) - primary_ammo[WEAPON_RPG] = m_rgAmmo[i]; - else if (strcmp("uranium", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) - { - primary_ammo[WEAPON_GAUSS] = m_rgAmmo[i]; - primary_ammo[WEAPON_EGON] = m_rgAmmo[i]; - } - else if (stricmp("Hornets", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) - primary_ammo[WEAPON_HORNETGUN] = m_rgAmmo[i]; - else if (stricmp("Hand Grenade", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) - primary_ammo[WEAPON_HANDGRENADE] = m_rgAmmo[i]; - else if (stricmp("Trip Mine", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) - primary_ammo[WEAPON_TRIPMINE] = m_rgAmmo[i]; - else if (stricmp("Satchel Charge", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) - primary_ammo[WEAPON_SATCHEL] = m_rgAmmo[i]; - else if (stricmp("Snarks", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) - primary_ammo[WEAPON_SNARK] = m_rgAmmo[i]; - } - -} - -// specifing a weapon_choice allows you to choose the weapon the bot will -// use (assuming enough ammo exists for that weapon) -// BotFireWeapon will return TRUE if weapon was fired, FALSE otherwise -// primary is used to indicate whether you want primary or secondary fire -// if you have specified a weapon using weapon_choice - -BOOL CBot::BotFireWeapon( Vector v_enemy_origin, int weapon_choice, BOOL primary ) -{ - CBasePlayerItem *new_weapon; - BOOL enemy_below; - BOOL IsAboveWater = pev->waterlevel != 3; - - // is it time to check weapons inventory yet? - if (f_weapon_inventory_time <= gpGlobals->time) - { - // check weapon and ammo inventory then update check time... - BotWeaponInventory(); - f_weapon_inventory_time = gpGlobals->time + 1.0; - } - - Vector v_enemy = v_enemy_origin - GetGunPosition( ); - - float distance = v_enemy.Length(); // how far away is the enemy? - - // is enemy at least 45 units below bot? (for handgrenades and snarks) - if (v_enemy_origin.z < (pev->origin.z - 45)) - enemy_below = TRUE; - else - enemy_below = FALSE; - - // if bot is carrying the crowbar... - if (pev->weapons & (1<button |= IN_ATTACK; // use primary attack (whack! whack!) - - // set next time to "shoot" - f_shoot_time = gpGlobals->time + 0.3 + - RANDOM_FLOAT(primary_fire_delay[WEAPON_CROWBAR][bot_skill][0], - primary_fire_delay[WEAPON_CROWBAR][bot_skill][1]); - return TRUE; - } - } - - // if bot is carrying any hand grenades and enemy is below bot... - if ((pev->weapons & (1< 250) && (distance < 750) && - (weapon_choice == 0) && (use_grenade <= 30)) || - (weapon_choice == WEAPON_HANDGRENADE)) - { -// BigGuy - START - new_weapon = weapon_ptr[WEAPON_HANDGRENADE]; - - // check if the bot isn't already using this item... - if (m_pActiveItem != new_weapon) - SelectItem("weapon_handgrenade"); // select the hand grenades - - pev->button |= IN_ATTACK; // use primary attack (boom!) - - // set next time to "shoot" - f_shoot_time = gpGlobals->time + 0.1 + - RANDOM_FLOAT(primary_fire_delay[WEAPON_HANDGRENADE][bot_skill][0], - primary_fire_delay[WEAPON_HANDGRENADE][bot_skill][1]); - return TRUE; -// BigGuy - END - } - } - - // if bot is carrying any snarks (can't use underwater) and enemy is below bot... - if ((pev->weapons & (1< 150) && (distance < 500) && - (weapon_choice == 0) && (use_snark <= 50)) || - (weapon_choice == WEAPON_SNARK)) - { -// BigGuy - START - new_weapon = weapon_ptr[WEAPON_SNARK]; - - // check if the bot isn't already using this item... - if (m_pActiveItem != new_weapon) - SelectItem("weapon_snark"); // select the "squeak grenades" - - pev->button |= IN_ATTACK; // use primary attack (eek! eek!) - - // set next time to "shoot" - f_shoot_time = gpGlobals->time + 0.1 + - RANDOM_FLOAT(primary_fire_delay[WEAPON_SNARK][bot_skill][0], - primary_fire_delay[WEAPON_SNARK][bot_skill][1]); - return TRUE; -// BigGuy - END - } - } - - /* - // if the bot is carrying the egon gun (can't use underwater)... - if ((pev->weapons & (1< 0) - { - // check if the bot isn't already using this item... - if (m_pActiveItem != new_weapon) - SelectItem("weapon_egon"); // select the egon gun - - pev->button |= IN_ATTACK; // use primary attack (bang! bang!) - - // set next time to shoot - f_shoot_time = gpGlobals->time; - - return TRUE; - } - } - } - - - // if the bot is carrying the gauss gun (can't use underwater)... - if ((pev->weapons & (1< 1) - { - // check if the bot isn't already using this item... - if (m_pActiveItem != new_weapon) - SelectItem("weapon_gauss"); // select the gauss gun - - long use_secondary = RANDOM_LONG(1,100); - - // are we charging the gauss gun? - if (f_fire_gauss > 0) - { - // is it time to fire the charged gauss gun? - if (f_fire_gauss >= gpGlobals->time) - { - // we DON'T set pev->button here to release the secondary - // fire button which will fire the charged gauss gun - - f_fire_gauss = -1; // -1 means not charging gauss gun - - // set next time to shoot - f_shoot_time = gpGlobals->time + 1.0 + - RANDOM_FLOAT(secondary_fire_delay[WEAPON_GAUSS][bot_skill][0], - secondary_fire_delay[WEAPON_GAUSS][bot_skill][1]); - } - else - { - pev->button |= IN_ATTACK2; // charge the gauss gun - f_shoot_time = gpGlobals->time; // keep charging - } - } - else if ((use_secondary <= 20) && - (primary_ammo[WEAPON_GAUSS] >= 10)) - { - // release secondary fire in 0.5 seconds... - f_fire_gauss = gpGlobals->time + 0.5; - - pev->button |= IN_ATTACK2; // charge the gauss gun - f_shoot_time = gpGlobals->time; // keep charging - } - else - { - pev->button |= IN_ATTACK; // use primary attack (bang! bang!) - - // set next time to shoot - f_shoot_time = gpGlobals->time + 0.2 + - RANDOM_FLOAT(primary_fire_delay[WEAPON_GAUSS][bot_skill][0], - primary_fire_delay[WEAPON_GAUSS][bot_skill][1]); - } - - return TRUE; - } - } - } -*/ - // if the bot is carrying the shotgun (can't use underwater)... - if ((pev->weapons & (1< 30) && (distance < 150) && (weapon_choice == 0)) || - (weapon_choice == WEAPON_SHOTGUN)) - { - new_weapon = weapon_ptr[WEAPON_SHOTGUN]; - - // check if the bot has any ammo left for this weapon... - if (primary_ammo[WEAPON_SHOTGUN] > 0) - { - // check if the bot isn't already using this item... - if (m_pActiveItem != new_weapon) - SelectItem("weapon_shotgun"); // select the shotgun - - long use_secondary = RANDOM_LONG(1,100); - - // use secondary attack about 30% of the time - if ((use_secondary <= 30) && (primary_ammo[WEAPON_SHOTGUN] >= 2)) - { -// BigGuy - START - pev->button |= IN_ATTACK2; // use secondary attack (bang! bang!) - - // set next time to shoot - f_shoot_time = gpGlobals->time + 1.5 + - RANDOM_FLOAT(secondary_fire_delay[WEAPON_SHOTGUN][bot_skill][0], - secondary_fire_delay[WEAPON_SHOTGUN][bot_skill][1]); - } -// BigGuy - END - else - { - pev->button |= IN_ATTACK; // use primary attack (bang! bang!) - - // set next time to shoot - f_shoot_time = gpGlobals->time + 0.75 + - RANDOM_FLOAT(primary_fire_delay[WEAPON_SHOTGUN][bot_skill][0], - primary_fire_delay[WEAPON_SHOTGUN][bot_skill][1]); - } - - return TRUE; - } - } - } - - // if the bot is carrying the 357/PYTHON, (can't use underwater)... - if ((pev->weapons & (1< 30) && (distance < 700) && (weapon_choice == 0)) || - (weapon_choice == WEAPON_PYTHON)) - { - new_weapon = weapon_ptr[WEAPON_PYTHON]; - - // check if the bot has any ammo left for this weapon... - if (primary_ammo[WEAPON_PYTHON] > 0) - { - // check if the bot isn't already using this item... - if (m_pActiveItem != new_weapon) - SelectItem("weapon_357"); // select the 357 python - - pev->button |= IN_ATTACK; // use primary attack (bang! bang!) - - // set next time to shoot - f_shoot_time = gpGlobals->time + 0.75 + - RANDOM_FLOAT(primary_fire_delay[WEAPON_PYTHON][bot_skill][0], - primary_fire_delay[WEAPON_PYTHON][bot_skill][1]); - - return TRUE; - } - } - } -/* - // if the bot is carrying the hornet gun... - if (pev->weapons & (1< 30) && (distance < 1000) && (weapon_choice == 0)) || - (weapon_choice == WEAPON_HORNETGUN)) - { - new_weapon = weapon_ptr[WEAPON_HORNETGUN]; - - // check if the bot has any ammo left for this weapon... - if (primary_ammo[WEAPON_HORNETGUN] > 0) - { - // check if the bot isn't already using this item... - if (m_pActiveItem != new_weapon) - SelectItem("weapon_hornetgun"); // select the hornet gun - - long use_secondary = RANDOM_LONG(1,100); - - // use secondary attack about 50% of the time (if fully reloaded) - if ((use_secondary <= 50) && - (primary_ammo[WEAPON_HORNETGUN] >= HORNET_MAX_CARRY)) - { -// BigGuy - START - pev->button |= IN_ATTACK2; // use secondary attack (buzz! buzz!) - - // set next time to shoot - f_shoot_time = gpGlobals->time + 0.1 + - RANDOM_FLOAT(secondary_fire_delay[WEAPON_HORNETGUN][bot_skill][0], - secondary_fire_delay[WEAPON_HORNETGUN][bot_skill][1]); -// BigGuy - END - } - else - { - pev->button |= IN_ATTACK; // use primary attack (buzz! buzz!) - - // set next time to shoot - f_shoot_time = gpGlobals->time + 0.25 + - RANDOM_FLOAT(primary_fire_delay[WEAPON_HORNETGUN][bot_skill][0], - primary_fire_delay[WEAPON_HORNETGUN][bot_skill][1]); - } - - return TRUE; - } - } - } -*/ - // if the bot is carrying the MP5 (can't use underwater)... - if ((pev->weapons & (1< 300) && (distance < 600) && - (weapon_choice == 0) && (use_secondary <= 10)) || - ((weapon_choice == WEAPON_MP5) && (primary == FALSE))) - { - // at some point we need to fire upwards in the air slightly - // for long distance kills. for right now, just fire the - // grenade at the poor sucker. - -// BigGuy - START - new_weapon = weapon_ptr[WEAPON_MP5]; - - // check if the bot has any ammo left for this weapon... - if (secondary_ammo[WEAPON_MP5] > 0) - { - // check if the bot isn't already using this item... - if (m_pActiveItem != new_weapon) - SelectItem("weapon_9mmAR"); // select the 9mmAR (MP5) - - pev->button |= IN_ATTACK2; // use secodnary attack (boom!) - - // set next time to shoot - f_shoot_time = gpGlobals->time + 1.0 + - RANDOM_FLOAT(secondary_fire_delay[WEAPON_MP5][bot_skill][0], - secondary_fire_delay[WEAPON_MP5][bot_skill][1]); - - return TRUE; - } -// BigGuy - END - } - - // if close enough for good MP5 shot... - if (((distance < 250) && (weapon_choice == 0)) || - (weapon_choice == WEAPON_MP5)) - { - new_weapon = weapon_ptr[WEAPON_MP5]; - - // check if the bot has any ammo left for this weapon... - if (primary_ammo[WEAPON_MP5] > 0) - { - // check if the bot isn't already using this item... - if (m_pActiveItem != new_weapon) - SelectItem("weapon_9mmAR"); // select the 9mmAR (MP5) - - pev->button |= IN_ATTACK; // use primary attack (bang! bang!) - - // set next time to shoot - f_shoot_time = gpGlobals->time + 0.1 + - RANDOM_FLOAT(primary_fire_delay[WEAPON_MP5][bot_skill][0], - primary_fire_delay[WEAPON_MP5][bot_skill][1]); - - return TRUE; - } - } - } - - // if the bot is carrying the crossbow... - if (pev->weapons & (1< 100) && (distance < 1000) && (weapon_choice == 0)) || - (weapon_choice == WEAPON_CROSSBOW)) - { - new_weapon = weapon_ptr[WEAPON_CROSSBOW]; - - // check if the bot has any ammo left for this weapon... - if (primary_ammo[WEAPON_CROSSBOW] > 0) - { - // check if the bot isn't already using this item... - if (m_pActiveItem != new_weapon) - SelectItem("weapon_crossbow"); // select the crossbow - - pev->button |= IN_ATTACK; // use primary attack (bang! bang!) - - // set next time to shoot - f_shoot_time = gpGlobals->time + 0.75 + - RANDOM_FLOAT(primary_fire_delay[WEAPON_CROSSBOW][bot_skill][0], - primary_fire_delay[WEAPON_CROSSBOW][bot_skill][1]); - - return TRUE; - } - } - } - - // if the bot is carrying the RPG... - if (pev->weapons & (1< 300) && (weapon_choice == 0)) || - (weapon_choice == WEAPON_RPG)) - { - new_weapon = weapon_ptr[WEAPON_RPG]; - - // check if the bot has any ammo left for this weapon... - if (primary_ammo[WEAPON_RPG] > 0) - { - // check if the bot isn't already using this item... - if (m_pActiveItem != new_weapon) - SelectItem("weapon_rpg"); // select the RPG rocket launcher - - pev->button |= IN_ATTACK; // use primary attack (bang! bang!) - - // set next time to shoot - f_shoot_time = gpGlobals->time + 1.5 + - RANDOM_FLOAT(primary_fire_delay[WEAPON_RPG][bot_skill][0], - primary_fire_delay[WEAPON_RPG][bot_skill][1]); - - return TRUE; - } - } - } - - // if the bot is carrying the 9mm glock... - if (pev->weapons & (1< 0) - { - // check if the bot isn't already using this item... - if (m_pActiveItem != new_weapon) - SelectItem("weapon_9mmhandgun"); // select the trusty 9mm glock - - long use_secondary = RANDOM_LONG(1,100); - - // use secondary attack about 30% of the time - if (use_secondary <= 30) - { -// BigGuy - START - pev->button |= IN_ATTACK2; // use secondary attack (bang! bang!) - - // set next time to shoot - f_shoot_time = gpGlobals->time + 0.2 + - RANDOM_FLOAT(secondary_fire_delay[WEAPON_GLOCK][bot_skill][0], - secondary_fire_delay[WEAPON_GLOCK][bot_skill][1]); -// BigGuy - END - } - else - { - pev->button |= IN_ATTACK; // use primary attack (bang! bang!) - - // set next time to shoot - f_shoot_time = gpGlobals->time + 0.3 + - RANDOM_FLOAT(primary_fire_delay[WEAPON_GLOCK][bot_skill][0], - primary_fire_delay[WEAPON_GLOCK][bot_skill][1]); - } - - return TRUE; - } - } - } - - // didn't have any available weapons or ammo, return FALSE - return FALSE; -} - - -void CBot::BotShootAtEnemy( void ) -{ - float f_distance; - - if (!pBotEnemy) - return; - - // aim for the head and/or body - Vector v_enemy = BotBodyTarget( pBotEnemy ) - GetGunPosition(); - - pev->v_angle = UTIL_VecToAngles( v_enemy ); - - pev->angles.x = 0; - pev->angles.y = pev->v_angle.y; - //pev->angles.z = pev->v_angle.z; // HOAXER'S TEST - - pev->ideal_yaw = pev->v_angle.y; - - // check for wrap around of angle... - if (pev->ideal_yaw > 180) - pev->ideal_yaw -= 360; - if (pev->ideal_yaw < -180) - pev->ideal_yaw += 360; - - pev->v_angle.x = -pev->v_angle.x; //adjust pitch to point gun - - // is it time to shoot yet? - if (f_shoot_time <= gpGlobals->time) - { - int iPreferredWeapon = 0; - bool bPrimaryAttack = true; - int iCurEnemyId = -1; - if ( ( pBotEnemyClass != ENEMY_NONE ) && ( pBotEnemyClass != ENEMY_UNKNOWN ) ) - { - for (int i=0; i 200 ) ) - bPrimaryAttack = false; - } - }; - - // select the best weapon to use at this distance and fire... - BotFireWeapon( pBotEnemy->pev->origin, iPreferredWeapon, bPrimaryAttack ); - } - - // HOAXER'S TEST (was uncommented) - v_enemy.z = 0; // ignore z component (up & down) - - f_distance = v_enemy.Length(); // how far away is the enemy scum? - - if (f_distance > 200) // run if distance to enemy is far - f_move_speed = f_max_speed; - else if (f_distance > 20) // walk if distance is closer - f_move_speed = f_max_speed / 2; - else // don't move if close enough - f_move_speed = 0.0; -} - - - +// botman's Half-Life bot example +// +// http://planethalflife.com/botman/ +// +// bot_combat.cpp +// + +#include "extdll.h" +#include "util.h" +#include "client.h" +#include "cbase.h" +#include "player.h" +#include "items.h" +#include "effects.h" +#include "weapons.h" +#include "soundent.h" +#include "gamerules.h" +#include "animation.h" +#include "monsters.h" + +#include "bot.h" + + +extern int f_Observer; // flag to indicate if player is in observer mode + +// weapon firing delay based on skill (min and max delay for each weapon) +float primary_fire_delay[WEAPON_SNARK+1][5][2] = { + // WEAPON_NONE - NOT USED + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_CROWBAR + {{0.0, 0.1}, {0.2, 0.3}, {0.3, 0.5}, {0.4, 0.6}, {0.6, 1.0}}, + // WEAPON_GLOCK (9mm) + {{0.0, 0.1}, {0.1, 0.2}, {0.2, 0.3}, {0.3, 0.4}, {0.4, 0.5}}, + // WEAPON_PYTHON (357) + {{0.0, 0.25}, {0.2, 0.5}, {0.4, 0.8}, {1.0, 1.3}, {1.5, 2.0}}, + // WEAPON_MP5 (9mmAR) + {{0.0, 0.1}, {0.1, 0.3}, {0.3, 0.5}, {0.4, 0.6}, {0.5, 0.8}}, + // WEAPON_CHAINGUN - NOT USED + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_CROSSBOW + {{0.0, 0.25}, {0.2, 0.4}, {0.5, 0.7}, {0.8, 1.0}, {1.0, 1.3}}, + // WEAPON_SHOTGUN + {{0.0, 0.25}, {0.2, 0.5}, {0.4, 0.8}, {0.6, 1.2}, {0.8, 2.0}}, + // WEAPON_RPG + {{1.0, 3.0}, {2.0, 4.0}, {3.0, 5.0}, {4.0, 6.0}, {5.0, 7.0}}, + // WEAPON_GAUSS + {{0.0, 0.1}, {0.2, 0.3}, {0.3, 0.5}, {0.5, 0.8}, {1.0, 1.2}}, + // WEAPON_EGON + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_HORNETGUN + {{0.0, 0.1}, {0.25, 0.4}, {0.4, 0.7}, {0.6, 1.0}, {1.0, 1.5}}, + // WEAPON_HANDGRENADE + {{1.0, 1.4}, {1.4, 2.0}, {1.8, 2.6}, {2.0, 3.0}, {2.5, 3.8}}, + // WEAPON_TRIPMINE + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_SATCHEL + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_SNARK + {{0.0, 0.1}, {0.1, 0.2}, {0.2, 0.5}, {0.5, 0.7}, {0.6, 1.0}}, + }; + +float secondary_fire_delay[WEAPON_SNARK+1][5][2] = { + // WEAPON_NONE - NOT USED + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_CROWBAR - Not applicable + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_GLOCK (9mm) + {{0.0, 0.1}, {0.0, 0.1}, {0.1, 0.2}, {0.1, 0.2}, {0.2, 0.4}}, + // WEAPON_PYTHON (357) + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_MP5 (9mmAR) + {{0.0, 0.3}, {0.5, 0.8}, {0.7, 1.0}, {1.0, 1.6}, {1.4, 2.0}}, + // WEAPON_CHAINGUN - NOT USED + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_CROSSBOW + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_SHOTGUN + {{0.0, 0.25}, {0.2, 0.5}, {0.4, 0.8}, {0.6, 1.2}, {0.8, 2.0}}, + // WEAPON_RPG - Not applicable + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_GAUSS + {{0.2, 0.5}, {0.3, 0.7}, {0.5, 1.0}, {0.8, 1.5}, {1.0, 2.0}}, + // WEAPON_EGON - Not applicable + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_HORNETGUN + {{0.0, 0.1}, {0.2, 0.3}, {0.3, 0.5}, {0.5, 0.8}, {0.7, 1.2}}, + // WEAPON_HANDGRENADE - Not applicable + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_TRIPMINE - Not applicable + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_SATCHEL + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}}, + // WEAPON_SNARK - Not applicable + {{0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}, {0.0, 0.0}} + }; + +ammo_check_t ammo_check[] = { + {"ammo_glockclip", "9mm", _9MM_MAX_CARRY}, + {"ammo_9mmclip", "9mm", _9MM_MAX_CARRY}, + {"ammo_9mmAR", "9mm", _9MM_MAX_CARRY}, + {"ammo_9mmbox", "9mm", _9MM_MAX_CARRY}, + {"ammo_mp5clip", "9mm", _9MM_MAX_CARRY}, + {"ammo_chainboxclip", "9mm", _9MM_MAX_CARRY}, + {"ammo_mp5grenades", "ARgrenades", M203_GRENADE_MAX_CARRY}, + {"ammo_ARgrenades", "ARgrenades", M203_GRENADE_MAX_CARRY}, + {"ammo_buckshot", "buckshot", BUCKSHOT_MAX_CARRY}, + {"ammo_crossbow", "bolts", BOLT_MAX_CARRY}, + {"ammo_357", "357", _357_MAX_CARRY}, + {"ammo_rpgclip", "rockets", ROCKET_MAX_CARRY}, + {"ammo_egonclip", "uranium", URANIUM_MAX_CARRY}, + {"ammo_gaussclip", "uranium", URANIUM_MAX_CARRY}, + {"", 0, 0}}; + +typedef struct +{ + const char *enemy_name; + byte enemy_id; + byte preferred_weapons_id[3]; +} enemy_weapon_check_t; + +enemy_weapon_check_t enemy_weapon_check[] = { + { "monster_heacrab", ENEMY_HEADCRAB, {WEAPON_GLOCK, WEAPON_CROWBAR, WEAPON_NONE} }, + { "monster_zombie", ENEMY_ZOMBIE, {WEAPON_MP5, WEAPON_SHOTGUN, WEAPON_GLOCK} }, + { "monster_bullsquid", ENEMY_BULLSQUID, {WEAPON_PYTHON, WEAPON_SHOTGUN, WEAPON_MP5} }, + { "monster_aslave", ENEMY_ASLAVE, {WEAPON_MP5, WEAPON_SHOTGUN, WEAPON_GLOCK} }, + { "monster_houndeye", ENEMY_HOUNDEYE, {WEAPON_GLOCK, WEAPON_CROWBAR, WEAPON_NONE} }, + { "monster_alien_grunt", ENEMY_AGRUNT, {WEAPON_PYTHON, WEAPON_SHOTGUN, WEAPON_MP5} }, + { "monster_human_grunt", ENEMY_HGRUNT, {WEAPON_MP5, WEAPON_SHOTGUN, WEAPON_PYTHON} }, + { "monster_alien_controller", ENEMY_CONTROLLER, {WEAPON_PYTHON, WEAPON_MP5, WEAPON_SHOTGUN} }, + { "monster_alien_flyer", ENEMY_FLYER, {WEAPON_RPG, WEAPON_NONE, WEAPON_NONE} }, + { "unknown", ENEMY_UNKNOWN, {WEAPON_MP5, WEAPON_SHOTGUN, WEAPON_GLOCK} }, +}; + +#define WEAPON_CHECK_COUNT 10 + +/* + +#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_DISPLACER 16 +*/ + +// sounds for Bot taunting after a kill... +char gina_taunt[][30] = { GI_TNT1, GI_TNT2, GI_TNT3, GI_TNT4, GI_TNT5 }; +char colette_taunt[][30] = { CO_TNT1, CO_TNT2, CO_TNT3, CO_TNT4, CO_TNT5 }; + + +bool BOT_VICTIM_EXISTS( CBaseEntity *victim, EOFFSET victimOffset) +{ + CBaseEntity *pTest = CBaseEntity::Instance( victimOffset ); + + if ( pTest ) + { + if ( !FNullEnt( pTest->pev )) + { + if ( pTest->eoffset() == victim->eoffset() ) + return true; + else + return false; + } else + return false; + } else + return false; +} + +byte GetBotEnemyClassId( int classname ) +{ + char szEnemyClassName[64]; + strcpy( szEnemyClassName, STRING( classname ) ); + + for ( int i = 0; i < WEAPON_CHECK_COUNT; i++ ) + { + if ( stricmp( enemy_weapon_check[i].enemy_name, szEnemyClassName ) == 0 ) + return enemy_weapon_check[i].enemy_id; + } + return ENEMY_UNKNOWN; +} + +CBaseEntity * CBot::BotFindEnemy( void ) +{ + Vector vecEnd; + static BOOL flag=TRUE; + char sound[128]; // for taunting sounds + + // does the bot already have an enemy? + //if (pBotEnemy != NULL) //&& (pBotEnemy)) + + if ( pBotEnemy ) + if ( BOT_VICTIM_EXISTS( pBotEnemy, pBotEnemyOffset ) ) + // if (pBotEnemy->pev->deadflag == DEAD_NO) + { + vecEnd = pBotEnemy->EyePosition(); + + // if the enemy is dead or has switched to botcam mode... + if (!pBotEnemy->IsAlive() || (pBotEnemy->pev->effects & EF_NODRAW)) + { + if (!pBotEnemy->IsAlive()) // is the enemy dead?, assume bot killed it + { + // the enemy is dead + // speak taunt sounds about 10% of the time + if (RANDOM_LONG(1, 100) <= 10) + { + if (bot_model == MODEL_GINA) + strcpy( sound, gina_taunt[RANDOM_LONG(0,4)] ); + else if (bot_model == MODEL_COLETTE) + strcpy( sound, colette_taunt[RANDOM_LONG(0,4)] ); + + EMIT_SOUND(ENT(pBotEnemy->pev), CHAN_VOICE, sound, + RANDOM_FLOAT(0.9, 1.0), ATTN_NORM); + } + } + + // don't have an enemy anymore so null out the pointer... + pBotEnemy = NULL; + pBotEnemyOffset = 0; + } // <-- if dead or in botcam + else if (FInViewCone( &vecEnd ) && FVisible( vecEnd )) + { + // if enemy is still visible and in field of view, keep it + + // face the enemy + Vector v_enemy = pBotEnemy->pev->origin - pev->origin; + Vector bot_angles = UTIL_VecToAngles( v_enemy ); + + pev->ideal_yaw = bot_angles.y; + + // check for wrap around of angle... + if (pev->ideal_yaw > 180) + pev->ideal_yaw -= 360; + if (pev->ideal_yaw < -180) + pev->ideal_yaw += 360; + + return (pBotEnemy); + } // <-- if alive and visible + } + + int i; + float nearestdistance = 1000; + CBaseEntity *pNewEnemy = NULL; + +//=================================================================== +// BOTMAN'S CODE FOR FINDING PLAYER ENEMY +//=================================================================== +/* + // search the world for players... + for (i = 1; i <= gpGlobals->maxClients; i++) + { + CBaseEntity *pPlayer = UTIL_PlayerByIndex( i ); + + // skip invalid players and skip self (i.e. this bot) + if ((!pPlayer) || (pPlayer == this)) + continue; + + // skip this player if not alive (i.e. dead or dying) + if (pPlayer->pev->deadflag != DEAD_NO) + continue; + + vecEnd = pPlayer->EyePosition(); + + // see if bot can see the player... + if (FInViewCone( &vecEnd ) && FVisible( vecEnd )) + { + float distance = (pPlayer->pev->origin - pev->origin).Length(); + if (distance < nearestdistance) + { + nearestdistance = distance; + pNewEnemy = pPlayer; + + pBotUser = NULL; // don't follow user when enemy found + } + } + } +*/ +//=================================================================== +// END OF BOTMANS ENEMY SEARCHO CODE +//=================================================================== + + int iDistance = 2048; + + CBaseEntity *pSightEnt = NULL;// the current visible entity that we're dealing with + CBasePlayer *pPlayer = (CBasePlayer *)UTIL_PlayerByIndex( 1 ); + if (!pPlayer) + { + ALERT( at_console, "Can not find player!\n" ); + return NULL; + } + /* + for ( int i = 1; i <= gpGlobals->maxClients; i++ ) + { + pPlayer = UTIL_PlayerByIndex( i ); + if ( pPlayer ) + }*/ + + Vector delta = Vector( iDistance, iDistance, iDistance ); + + // Find only monsters/clients in box, NOT limited to PVS + CBaseEntity *pList[100]; + int count = UTIL_EntitiesInBox( pList, 100, pev->origin - delta, pev->origin + delta, /*FL_CLIENT|*/ FL_MONSTER ); + for ( int i = 0; i < count; i++ ) + { + pSightEnt = pList[i]; + + // skip invalid players and skip self (i.e. this bot) + if ((!pSightEnt) || (pSightEnt == this)) + continue; + + // skip this player if not alive (i.e. dead or dying) + if (pSightEnt->pev->deadflag != DEAD_NO) + continue; + + if ( !FBitSet(pSightEnt->pev->flags, FL_MONSTER) ) + continue; + + // !!!temporarily only considering other monsters and clients, don't see prisoners + if ( //pSightEnt != this && + !FBitSet( pSightEnt->pev->spawnflags, SF_MONSTER_PRISONER ) && + pSightEnt->pev->health > 0 ) + { + vecEnd = pSightEnt->EyePosition(); + + // the looker will want to consider this entity + // don't check anything else about an entity that can't be seen, or an entity that you don't care about. + + // OLD STYLE: + if ( pPlayer->IRelationship( pSightEnt ) != R_NO && // if relationship not neutral + + //CBaseMonster *gNPC = (CBaseMonster*)pSightEnt; + + // NEW STYLE: + //if ( gNPC->IRelationship( pPlayer ) > 0 && // if relationship is agressive + + FInViewCone( &vecEnd ) && // if we see it + !FBitSet( pSightEnt->pev->flags, FL_NOTARGET ) && // if it can be targeted + FVisible( pSightEnt->pev->origin ) // if it is visible + ) + { + /* + if ( pSightEnt->IsPlayer() ) + { + // do nothing (yet) + } + */ + + // see if bot can see the enemy... + float distance = (pSightEnt->pev->origin - pev->origin).Length(); + if (distance < nearestdistance) + { + nearestdistance = distance; + pNewEnemy = pSightEnt; + + pBotUser = NULL; // don't follow user when enemy found + } + + } + } + } + +// +// END OF DECAY'S ENEMY SEARCH CODE +// ****************************************************************** + + if (pNewEnemy) + { + pBotEnemyOffset = pNewEnemy->eoffset(); + pBotEnemyClass = GetBotEnemyClassId( pNewEnemy->pev->classname ); + + // face the enemy + Vector v_enemy = pNewEnemy->pev->origin - pev->origin; + Vector bot_angles = UTIL_VecToAngles( v_enemy ); + + pev->ideal_yaw = bot_angles.y; + + // check for wrap around of angle... + if (pev->ideal_yaw > 180) + pev->ideal_yaw -= 360; + if (pev->ideal_yaw < -180) + pev->ideal_yaw += 360; + } else + { + pBotEnemyOffset = 0; + pBotEnemyClass = ENEMY_NONE; + } + + return (pNewEnemy); +} + + +Vector CBot::BotBodyTarget( CBaseEntity *pBotEnemy ) +{ + Vector target; + float f_distance; + float f_scale; + int d_x, d_y, d_z; + + f_distance = (pBotEnemy->pev->origin - pev->origin).Length(); + + if (f_distance > 1000) + f_scale = 1.0; + else if (f_distance > 100) + f_scale = f_distance / 1000.0; + else + f_scale = 0.1; + + switch (bot_skill) + { + case 0: + // VERY GOOD, same as from CBasePlayer::BodyTarget (in player.h) + target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs * RANDOM_FLOAT( 0.5, 1.1 ); + d_x = 0; // no offset + d_y = 0; + d_z = 0; + break; + case 1: + // GOOD, offset a little for x, y, and z + target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs; + d_x = RANDOM_FLOAT(-5, 5) * f_scale; + d_y = RANDOM_FLOAT(-5, 5) * f_scale; + d_z = RANDOM_FLOAT(-9, 9) * f_scale; + break; + case 2: + // FAIR, offset somewhat for x, y, and z + target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs; + d_x = RANDOM_FLOAT(-9, 9) * f_scale; + d_y = RANDOM_FLOAT(-9, 9) * f_scale; + d_z = RANDOM_FLOAT(-15, 15) * f_scale; + break; + case 3: + // POOR, offset for x, y, and z + target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs; + d_x = RANDOM_FLOAT(-16, 16) * f_scale; + d_y = RANDOM_FLOAT(-16, 16) * f_scale; + d_z = RANDOM_FLOAT(-20, 20) * f_scale; + break; + case 4: + // BAD, offset lots for x, y, and z + target = pBotEnemy->Center() + pBotEnemy->pev->view_ofs; + d_x = RANDOM_FLOAT(-20, 20) * f_scale; + d_y = RANDOM_FLOAT(-20, 20) * f_scale; + d_z = RANDOM_FLOAT(-27, 27) * f_scale; + break; + } + + target = target + Vector(d_x, d_y, d_z); + + return target; +} + + +void CBot::BotWeaponInventory( void ) +{ + int i; + + // initialize the elements of the weapons arrays... + for (i = 0; i < MAX_WEAPONS; i++) + { + weapon_ptr[i] = NULL; + primary_ammo[i] = 0; + secondary_ammo[i] = 0; + } + + // find out which weapons the bot is carrying... + for (i = 0; i < MAX_ITEM_TYPES; i++) + { + CBasePlayerItem *pItem = NULL; + + if (m_rgpPlayerItems[i]) + { + pItem = m_rgpPlayerItems[i]; + while (pItem) + { + weapon_ptr[pItem->m_iId] = pItem; // store pointer to item + + pItem = pItem->m_pNext; + } + } + } + + // find out how much ammo of each type the bot is carrying... + for (i = 0; i < MAX_AMMO_SLOTS; i++) + { + if (!CBasePlayerItem::AmmoInfoArray[i].pszName) + continue; + + if (strcmp("9mm", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) + { + primary_ammo[WEAPON_GLOCK] = m_rgAmmo[i]; + primary_ammo[WEAPON_MP5] = m_rgAmmo[i]; + } + else if (strcmp("357", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) + { + primary_ammo[WEAPON_PYTHON] = m_rgAmmo[i]; + } + else if (strcmp("ARgrenades", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) + secondary_ammo[WEAPON_MP5] = m_rgAmmo[i]; + else if (strcmp("bolts", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) + { + primary_ammo[WEAPON_CROSSBOW] = m_rgAmmo[i]; + } + else if (stricmp("buckshot", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) + { + primary_ammo[WEAPON_SHOTGUN] = m_rgAmmo[i]; + } + else if (stricmp("rockets", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) + primary_ammo[WEAPON_RPG] = m_rgAmmo[i]; + else if (strcmp("uranium", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) + { + primary_ammo[WEAPON_GAUSS] = m_rgAmmo[i]; + primary_ammo[WEAPON_EGON] = m_rgAmmo[i]; + } + else if (stricmp("Hornets", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) + primary_ammo[WEAPON_HORNETGUN] = m_rgAmmo[i]; + else if (stricmp("Hand Grenade", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) + primary_ammo[WEAPON_HANDGRENADE] = m_rgAmmo[i]; + else if (stricmp("Trip Mine", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) + primary_ammo[WEAPON_TRIPMINE] = m_rgAmmo[i]; + else if (stricmp("Satchel Charge", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) + primary_ammo[WEAPON_SATCHEL] = m_rgAmmo[i]; + else if (stricmp("Snarks", CBasePlayerItem::AmmoInfoArray[i].pszName) == 0) + primary_ammo[WEAPON_SNARK] = m_rgAmmo[i]; + } + +} + +// specifing a weapon_choice allows you to choose the weapon the bot will +// use (assuming enough ammo exists for that weapon) +// BotFireWeapon will return TRUE if weapon was fired, FALSE otherwise +// primary is used to indicate whether you want primary or secondary fire +// if you have specified a weapon using weapon_choice + +BOOL CBot::BotFireWeapon( Vector v_enemy_origin, int weapon_choice, BOOL primary ) +{ + CBasePlayerItem *new_weapon; + BOOL enemy_below; + BOOL IsAboveWater = pev->waterlevel != 3; + + // is it time to check weapons inventory yet? + if (f_weapon_inventory_time <= gpGlobals->time) + { + // check weapon and ammo inventory then update check time... + BotWeaponInventory(); + f_weapon_inventory_time = gpGlobals->time + 1.0; + } + + Vector v_enemy = v_enemy_origin - GetGunPosition( ); + + float distance = v_enemy.Length(); // how far away is the enemy? + + // is enemy at least 45 units below bot? (for handgrenades and snarks) + if (v_enemy_origin.z < (pev->origin.z - 45)) + enemy_below = TRUE; + else + enemy_below = FALSE; + + // if bot is carrying the crowbar... + if (pev->weapons & (1<button |= IN_ATTACK; // use primary attack (whack! whack!) + + // set next time to "shoot" + f_shoot_time = gpGlobals->time + 0.3 + + RANDOM_FLOAT(primary_fire_delay[WEAPON_CROWBAR][bot_skill][0], + primary_fire_delay[WEAPON_CROWBAR][bot_skill][1]); + return TRUE; + } + } + + // if bot is carrying any hand grenades and enemy is below bot... + if ((pev->weapons & (1< 250) && (distance < 750) && + (weapon_choice == 0) && (use_grenade <= 30)) || + (weapon_choice == WEAPON_HANDGRENADE)) + { +// BigGuy - START + new_weapon = weapon_ptr[WEAPON_HANDGRENADE]; + + // check if the bot isn't already using this item... + if (m_pActiveItem != new_weapon) + SelectItem("weapon_handgrenade"); // select the hand grenades + + pev->button |= IN_ATTACK; // use primary attack (boom!) + + // set next time to "shoot" + f_shoot_time = gpGlobals->time + 0.1 + + RANDOM_FLOAT(primary_fire_delay[WEAPON_HANDGRENADE][bot_skill][0], + primary_fire_delay[WEAPON_HANDGRENADE][bot_skill][1]); + return TRUE; +// BigGuy - END + } + } + + // if bot is carrying any snarks (can't use underwater) and enemy is below bot... + if ((pev->weapons & (1< 150) && (distance < 500) && + (weapon_choice == 0) && (use_snark <= 50)) || + (weapon_choice == WEAPON_SNARK)) + { +// BigGuy - START + new_weapon = weapon_ptr[WEAPON_SNARK]; + + // check if the bot isn't already using this item... + if (m_pActiveItem != new_weapon) + SelectItem("weapon_snark"); // select the "squeak grenades" + + pev->button |= IN_ATTACK; // use primary attack (eek! eek!) + + // set next time to "shoot" + f_shoot_time = gpGlobals->time + 0.1 + + RANDOM_FLOAT(primary_fire_delay[WEAPON_SNARK][bot_skill][0], + primary_fire_delay[WEAPON_SNARK][bot_skill][1]); + return TRUE; +// BigGuy - END + } + } + + /* + // if the bot is carrying the egon gun (can't use underwater)... + if ((pev->weapons & (1< 0) + { + // check if the bot isn't already using this item... + if (m_pActiveItem != new_weapon) + SelectItem("weapon_egon"); // select the egon gun + + pev->button |= IN_ATTACK; // use primary attack (bang! bang!) + + // set next time to shoot + f_shoot_time = gpGlobals->time; + + return TRUE; + } + } + } + + + // if the bot is carrying the gauss gun (can't use underwater)... + if ((pev->weapons & (1< 1) + { + // check if the bot isn't already using this item... + if (m_pActiveItem != new_weapon) + SelectItem("weapon_gauss"); // select the gauss gun + + long use_secondary = RANDOM_LONG(1,100); + + // are we charging the gauss gun? + if (f_fire_gauss > 0) + { + // is it time to fire the charged gauss gun? + if (f_fire_gauss >= gpGlobals->time) + { + // we DON'T set pev->button here to release the secondary + // fire button which will fire the charged gauss gun + + f_fire_gauss = -1; // -1 means not charging gauss gun + + // set next time to shoot + f_shoot_time = gpGlobals->time + 1.0 + + RANDOM_FLOAT(secondary_fire_delay[WEAPON_GAUSS][bot_skill][0], + secondary_fire_delay[WEAPON_GAUSS][bot_skill][1]); + } + else + { + pev->button |= IN_ATTACK2; // charge the gauss gun + f_shoot_time = gpGlobals->time; // keep charging + } + } + else if ((use_secondary <= 20) && + (primary_ammo[WEAPON_GAUSS] >= 10)) + { + // release secondary fire in 0.5 seconds... + f_fire_gauss = gpGlobals->time + 0.5; + + pev->button |= IN_ATTACK2; // charge the gauss gun + f_shoot_time = gpGlobals->time; // keep charging + } + else + { + pev->button |= IN_ATTACK; // use primary attack (bang! bang!) + + // set next time to shoot + f_shoot_time = gpGlobals->time + 0.2 + + RANDOM_FLOAT(primary_fire_delay[WEAPON_GAUSS][bot_skill][0], + primary_fire_delay[WEAPON_GAUSS][bot_skill][1]); + } + + return TRUE; + } + } + } +*/ + // if the bot is carrying the shotgun (can't use underwater)... + if ((pev->weapons & (1< 30) && (distance < 150) && (weapon_choice == 0)) || + (weapon_choice == WEAPON_SHOTGUN)) + { + new_weapon = weapon_ptr[WEAPON_SHOTGUN]; + + // check if the bot has any ammo left for this weapon... + if (primary_ammo[WEAPON_SHOTGUN] > 0) + { + // check if the bot isn't already using this item... + if (m_pActiveItem != new_weapon) + SelectItem("weapon_shotgun"); // select the shotgun + + long use_secondary = RANDOM_LONG(1,100); + + // use secondary attack about 30% of the time + if ((use_secondary <= 30) && (primary_ammo[WEAPON_SHOTGUN] >= 2)) + { +// BigGuy - START + pev->button |= IN_ATTACK2; // use secondary attack (bang! bang!) + + // set next time to shoot + f_shoot_time = gpGlobals->time + 1.5 + + RANDOM_FLOAT(secondary_fire_delay[WEAPON_SHOTGUN][bot_skill][0], + secondary_fire_delay[WEAPON_SHOTGUN][bot_skill][1]); + } +// BigGuy - END + else + { + pev->button |= IN_ATTACK; // use primary attack (bang! bang!) + + // set next time to shoot + f_shoot_time = gpGlobals->time + 0.75 + + RANDOM_FLOAT(primary_fire_delay[WEAPON_SHOTGUN][bot_skill][0], + primary_fire_delay[WEAPON_SHOTGUN][bot_skill][1]); + } + + return TRUE; + } + } + } + + // if the bot is carrying the 357/PYTHON, (can't use underwater)... + if ((pev->weapons & (1< 30) && (distance < 700) && (weapon_choice == 0)) || + (weapon_choice == WEAPON_PYTHON)) + { + new_weapon = weapon_ptr[WEAPON_PYTHON]; + + // check if the bot has any ammo left for this weapon... + if (primary_ammo[WEAPON_PYTHON] > 0) + { + // check if the bot isn't already using this item... + if (m_pActiveItem != new_weapon) + SelectItem("weapon_357"); // select the 357 python + + pev->button |= IN_ATTACK; // use primary attack (bang! bang!) + + // set next time to shoot + f_shoot_time = gpGlobals->time + 0.75 + + RANDOM_FLOAT(primary_fire_delay[WEAPON_PYTHON][bot_skill][0], + primary_fire_delay[WEAPON_PYTHON][bot_skill][1]); + + return TRUE; + } + } + } +/* + // if the bot is carrying the hornet gun... + if (pev->weapons & (1< 30) && (distance < 1000) && (weapon_choice == 0)) || + (weapon_choice == WEAPON_HORNETGUN)) + { + new_weapon = weapon_ptr[WEAPON_HORNETGUN]; + + // check if the bot has any ammo left for this weapon... + if (primary_ammo[WEAPON_HORNETGUN] > 0) + { + // check if the bot isn't already using this item... + if (m_pActiveItem != new_weapon) + SelectItem("weapon_hornetgun"); // select the hornet gun + + long use_secondary = RANDOM_LONG(1,100); + + // use secondary attack about 50% of the time (if fully reloaded) + if ((use_secondary <= 50) && + (primary_ammo[WEAPON_HORNETGUN] >= HORNET_MAX_CARRY)) + { +// BigGuy - START + pev->button |= IN_ATTACK2; // use secondary attack (buzz! buzz!) + + // set next time to shoot + f_shoot_time = gpGlobals->time + 0.1 + + RANDOM_FLOAT(secondary_fire_delay[WEAPON_HORNETGUN][bot_skill][0], + secondary_fire_delay[WEAPON_HORNETGUN][bot_skill][1]); +// BigGuy - END + } + else + { + pev->button |= IN_ATTACK; // use primary attack (buzz! buzz!) + + // set next time to shoot + f_shoot_time = gpGlobals->time + 0.25 + + RANDOM_FLOAT(primary_fire_delay[WEAPON_HORNETGUN][bot_skill][0], + primary_fire_delay[WEAPON_HORNETGUN][bot_skill][1]); + } + + return TRUE; + } + } + } +*/ + // if the bot is carrying the MP5 (can't use underwater)... + if ((pev->weapons & (1< 300) && (distance < 600) && + (weapon_choice == 0) && (use_secondary <= 10)) || + ((weapon_choice == WEAPON_MP5) && (primary == FALSE))) + { + // at some point we need to fire upwards in the air slightly + // for long distance kills. for right now, just fire the + // grenade at the poor sucker. + +// BigGuy - START + new_weapon = weapon_ptr[WEAPON_MP5]; + + // check if the bot has any ammo left for this weapon... + if (secondary_ammo[WEAPON_MP5] > 0) + { + // check if the bot isn't already using this item... + if (m_pActiveItem != new_weapon) + SelectItem("weapon_9mmAR"); // select the 9mmAR (MP5) + + pev->button |= IN_ATTACK2; // use secodnary attack (boom!) + + // set next time to shoot + f_shoot_time = gpGlobals->time + 1.0 + + RANDOM_FLOAT(secondary_fire_delay[WEAPON_MP5][bot_skill][0], + secondary_fire_delay[WEAPON_MP5][bot_skill][1]); + + return TRUE; + } +// BigGuy - END + } + + // if close enough for good MP5 shot... + if (((distance < 250) && (weapon_choice == 0)) || + (weapon_choice == WEAPON_MP5)) + { + new_weapon = weapon_ptr[WEAPON_MP5]; + + // check if the bot has any ammo left for this weapon... + if (primary_ammo[WEAPON_MP5] > 0) + { + // check if the bot isn't already using this item... + if (m_pActiveItem != new_weapon) + SelectItem("weapon_9mmAR"); // select the 9mmAR (MP5) + + pev->button |= IN_ATTACK; // use primary attack (bang! bang!) + + // set next time to shoot + f_shoot_time = gpGlobals->time + 0.1 + + RANDOM_FLOAT(primary_fire_delay[WEAPON_MP5][bot_skill][0], + primary_fire_delay[WEAPON_MP5][bot_skill][1]); + + return TRUE; + } + } + } + + // if the bot is carrying the crossbow... + if (pev->weapons & (1< 100) && (distance < 1000) && (weapon_choice == 0)) || + (weapon_choice == WEAPON_CROSSBOW)) + { + new_weapon = weapon_ptr[WEAPON_CROSSBOW]; + + // check if the bot has any ammo left for this weapon... + if (primary_ammo[WEAPON_CROSSBOW] > 0) + { + // check if the bot isn't already using this item... + if (m_pActiveItem != new_weapon) + SelectItem("weapon_crossbow"); // select the crossbow + + pev->button |= IN_ATTACK; // use primary attack (bang! bang!) + + // set next time to shoot + f_shoot_time = gpGlobals->time + 0.75 + + RANDOM_FLOAT(primary_fire_delay[WEAPON_CROSSBOW][bot_skill][0], + primary_fire_delay[WEAPON_CROSSBOW][bot_skill][1]); + + return TRUE; + } + } + } + + // if the bot is carrying the RPG... + if (pev->weapons & (1< 300) && (weapon_choice == 0)) || + (weapon_choice == WEAPON_RPG)) + { + new_weapon = weapon_ptr[WEAPON_RPG]; + + // check if the bot has any ammo left for this weapon... + if (primary_ammo[WEAPON_RPG] > 0) + { + // check if the bot isn't already using this item... + if (m_pActiveItem != new_weapon) + SelectItem("weapon_rpg"); // select the RPG rocket launcher + + pev->button |= IN_ATTACK; // use primary attack (bang! bang!) + + // set next time to shoot + f_shoot_time = gpGlobals->time + 1.5 + + RANDOM_FLOAT(primary_fire_delay[WEAPON_RPG][bot_skill][0], + primary_fire_delay[WEAPON_RPG][bot_skill][1]); + + return TRUE; + } + } + } + + // if the bot is carrying the 9mm glock... + if (pev->weapons & (1< 0) + { + // check if the bot isn't already using this item... + if (m_pActiveItem != new_weapon) + SelectItem("weapon_9mmhandgun"); // select the trusty 9mm glock + + long use_secondary = RANDOM_LONG(1,100); + + // use secondary attack about 30% of the time + if (use_secondary <= 30) + { +// BigGuy - START + pev->button |= IN_ATTACK2; // use secondary attack (bang! bang!) + + // set next time to shoot + f_shoot_time = gpGlobals->time + 0.2 + + RANDOM_FLOAT(secondary_fire_delay[WEAPON_GLOCK][bot_skill][0], + secondary_fire_delay[WEAPON_GLOCK][bot_skill][1]); +// BigGuy - END + } + else + { + pev->button |= IN_ATTACK; // use primary attack (bang! bang!) + + // set next time to shoot + f_shoot_time = gpGlobals->time + 0.3 + + RANDOM_FLOAT(primary_fire_delay[WEAPON_GLOCK][bot_skill][0], + primary_fire_delay[WEAPON_GLOCK][bot_skill][1]); + } + + return TRUE; + } + } + } + + // didn't have any available weapons or ammo, return FALSE + return FALSE; +} + + +void CBot::BotShootAtEnemy( void ) +{ + float f_distance; + + if (!pBotEnemy) + return; + + // aim for the head and/or body + Vector v_enemy = BotBodyTarget( pBotEnemy ) - GetGunPosition(); + + pev->v_angle = UTIL_VecToAngles( v_enemy ); + + pev->angles.x = 0; + pev->angles.y = pev->v_angle.y; + //pev->angles.z = pev->v_angle.z; // HOAXER'S TEST + + pev->ideal_yaw = pev->v_angle.y; + + // check for wrap around of angle... + if (pev->ideal_yaw > 180) + pev->ideal_yaw -= 360; + if (pev->ideal_yaw < -180) + pev->ideal_yaw += 360; + + pev->v_angle.x = -pev->v_angle.x; //adjust pitch to point gun + + // is it time to shoot yet? + if (f_shoot_time <= gpGlobals->time) + { + int iPreferredWeapon = 0; + bool bPrimaryAttack = true; + int iCurEnemyId = -1; + if ( ( pBotEnemyClass != ENEMY_NONE ) && ( pBotEnemyClass != ENEMY_UNKNOWN ) ) + { + for (int i=0; i 200 ) ) + bPrimaryAttack = false; + } + }; + + // select the best weapon to use at this distance and fire... + BotFireWeapon( pBotEnemy->pev->origin, iPreferredWeapon, bPrimaryAttack ); + } + + // HOAXER'S TEST (was uncommented) + v_enemy.z = 0; // ignore z component (up & down) + + f_distance = v_enemy.Length(); // how far away is the enemy scum? + + if (f_distance > 200) // run if distance to enemy is far + f_move_speed = f_max_speed; + else if (f_distance > 20) // walk if distance is closer + f_move_speed = f_max_speed / 2; + else // don't move if close enough + f_move_speed = 0.0; +} + + + diff --git a/dlls/chargers.cpp b/dlls/chargers.cpp index c4570883..093da5b4 100644 --- a/dlls/chargers.cpp +++ b/dlls/chargers.cpp @@ -1,236 +1,236 @@ -// -// Decay entities (chargers: health and hev; retinal scanner) -// - -#include "extdll.h" -#include "util.h" -#include "cbase.h" -#include "monsters.h" -#include "schedule.h" -#include "defaultai.h" -#include "scripted.h" -#include "animation.h" -#include "soundent.h" -#include "actanimating.h" - -// -// EYE RETINAL SCANNER -// - -#define EYESCANNER_HIDE_TIME 3 -#define EYESCANNER_SCAN_LOOPS 3 - -class CEyeScanner : public CActAnimating -{ -public: - void Precache( void ); - void Spawn( void ); - void Touch( CBaseEntity *pOther ); - void EXPORT ScannerThink( void ); - void EXPORT UseThink( void ); - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void KeyValue( KeyValueData *pkvd ); - - virtual int ObjectCaps( void ) { return CActAnimating :: ObjectCaps() | /*FCAP_CONTINUOUS_USE | */FCAP_IMPULSE_USE; } - - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - static TYPEDESCRIPTION m_SaveData[]; - - BOOL m_bState; - BOOL m_bIsKeymaker; - int m_iCheckFrame; - int m_iCheckLoop; - int m_iszTargetUnlocked; - int m_iszTargetLocked; - int m_iszKeymaker; // The One's who unlocks name :) -}; - -LINK_ENTITY_TO_CLASS(item_eyescanner, CEyeScanner); - -// -// Implementing save/restore -// - -TYPEDESCRIPTION CEyeScanner::m_SaveData[] = -{ - DEFINE_FIELD( CEyeScanner, m_bState, FIELD_BOOLEAN ), - DEFINE_FIELD( CEyeScanner, m_bIsKeymaker, FIELD_BOOLEAN ), - DEFINE_FIELD( CEyeScanner, m_iCheckLoop, FIELD_INTEGER ), - DEFINE_FIELD( CEyeScanner, m_iCheckFrame, FIELD_INTEGER ), - DEFINE_FIELD( CEyeScanner, m_iszTargetUnlocked, FIELD_STRING ), - DEFINE_FIELD( CEyeScanner, m_iszTargetLocked, FIELD_STRING ), - DEFINE_FIELD( CEyeScanner, m_iszKeymaker, FIELD_STRING ), -}; -IMPLEMENT_SAVERESTORE( CEyeScanner, CActAnimating ); - -// -// common functions -// - -#define mdlScanner "models/eye_scanner.mdl" -#define sndScannerBeep "buttons/blip1.wav" -#define sndScannerOpen "buttons/blip2.wav" -#define sndScannerDeny "buttons/button11.wav" - -void CEyeScanner::Precache( void ) -{ - PRECACHE_MODEL( mdlScanner ); - PRECACHE_SOUND( sndScannerBeep ); - PRECACHE_SOUND( sndScannerOpen ); - PRECACHE_SOUND( sndScannerDeny ); -} - -void CEyeScanner::Spawn( void ) -{ - Precache( ); - - SET_MODEL(ENT(pev), mdlScanner ); - - pev->movetype = MOVETYPE_NONE; - pev->solid = SOLID_TRIGGER; - - UTIL_SetSize( pev, Vector(-8,-8,0), Vector(8,8,32)); - SetActivity( ACT_CROUCHIDLE ); - - SetThink ( &CEyeScanner::ScannerThink ); - pev->nextthink = gpGlobals->time + 0.1; - //pev->frame = RANDOM_FLOAT(0,255); -} - -void CEyeScanner::KeyValue( KeyValueData *pkvd ) -{ - if (FStrEq(pkvd->szKeyName, "unlocked_target")) - { - m_iszTargetUnlocked = ALLOC_STRING(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "locked_target")) - { - m_iszTargetLocked = ALLOC_STRING(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "unlockersname")) - { - m_iszKeymaker = ALLOC_STRING(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else - CActAnimating::KeyValue( pkvd ); - //m_iReactivate = atoi(pkvd->szValue); -} - -void CEyeScanner::ScannerThink( void ) -{ - StudioFrameAdvance(); - pev->nextthink = gpGlobals->time + 0.1; - - switch( GetActivity() ) - { - case ACT_CROUCH: // deactivate in progress - if ( m_fSequenceFinished ) - SetActivity( ACT_CROUCHIDLE ); - break; - - case ACT_CROUCHIDLE: // deactivated - pev->skin = 0; - m_bState = FALSE; - break; - - case ACT_STAND: // activate in progress - m_bState = TRUE; - if ( m_fSequenceFinished ) - SetActivity( ACT_IDLE ); - break; - - case ACT_IDLE: // activated - if ( gpGlobals->time > pev->dmgtime ) - SetActivity( ACT_CROUCH ); - break; - default: - break; - } -} - -void CEyeScanner :: Touch( CBaseEntity *pOther ) -{/* - int iClass = pOther->Classify(); - //if ( pOther->IsPlayer() ) - if (iClass == CLASS_PLAYER || iClass == CLASS_PLAYER_ALLY) - { - pev->dmgtime = gpGlobals->time + EYESCANNER_HIDE_TIME; - if ( GetActivity() == ACT_CROUCH || GetActivity() == ACT_CROUCHIDLE ) - { - SetActivity( ACT_STAND ); - } - }*/ -} - -void CEyeScanner :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) -{ - if (GetActivity() == ACT_CROUCHIDLE) - { - pev->dmgtime = gpGlobals->time + EYESCANNER_HIDE_TIME; - //if ( GetActivity() == ACT_CROUCH || GetActivity() == ACT_CROUCHIDLE ) - //{ - SetActivity( ACT_STAND ); - //} - } - - //if (m_bState == FALSE) - // return; - - int m_iszCallerName; - if (pActivator) - m_iszCallerName = pActivator->pev->targetname; - else - m_iszCallerName = pCaller->pev->targetname; - - //ALERT( at_console, "caller name is %s\n", STRING(m_iszCallerName) ); - - if (m_iszKeymaker) // if unlocker's name is specified then do check activator's name - m_bIsKeymaker = !strcmp(STRING(m_iszCallerName), STRING(m_iszKeymaker)); - else // otherwise open for everyone - m_bIsKeymaker = true; - - m_iCheckLoop = 0; - m_iCheckFrame = 1; - m_bState = FALSE; - SetThink( &CEyeScanner::UseThink ); - pev->nextthink = gpGlobals->time + 1.5; -} - -void CEyeScanner :: UseThink( void ) -{ - pev->nextthink = gpGlobals->time + 0.15; - // buttons/blip1.wav - EMIT_SOUND( ENT(pev), CHAN_ITEM, sndScannerBeep, 0.85, ATTN_NORM ); - - if (m_iCheckLoop == EYESCANNER_SCAN_LOOPS) - { - // scan process finished - do something! - if (m_bIsKeymaker) - { - FireTargets( STRING(m_iszTargetUnlocked), this, this, USE_TOGGLE, 0 ); - EMIT_SOUND( ENT(pev), CHAN_ITEM, sndScannerOpen, 0.85, ATTN_NORM ); - } else - { - FireTargets( STRING(m_iszTargetLocked), this, this, USE_TOGGLE, 0 ); - EMIT_SOUND( ENT(pev), CHAN_ITEM, sndScannerDeny, 0.85, ATTN_NORM ); - } - - SetThink( &CEyeScanner::ScannerThink ); - pev->skin = 0; - return; - } - - // if we are in last animation frame, skip to first one - if (m_iCheckFrame == 4) - { - m_iCheckFrame = 1; - m_iCheckLoop++; - } - - pev->skin = m_iCheckFrame; - m_iCheckFrame++; -} +// +// Decay entities (chargers: health and hev; retinal scanner) +// + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "schedule.h" +#include "defaultai.h" +#include "scripted.h" +#include "animation.h" +#include "soundent.h" +#include "actanimating.h" + +// +// EYE RETINAL SCANNER +// + +#define EYESCANNER_HIDE_TIME 3 +#define EYESCANNER_SCAN_LOOPS 3 + +class CEyeScanner : public CActAnimating +{ +public: + void Precache( void ); + void Spawn( void ); + void Touch( CBaseEntity *pOther ); + void EXPORT ScannerThink( void ); + void EXPORT UseThink( void ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData *pkvd ); + + virtual int ObjectCaps( void ) { return CActAnimating :: ObjectCaps() | /*FCAP_CONTINUOUS_USE | */FCAP_IMPULSE_USE; } + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + BOOL m_bState; + BOOL m_bIsKeymaker; + int m_iCheckFrame; + int m_iCheckLoop; + int m_iszTargetUnlocked; + int m_iszTargetLocked; + int m_iszKeymaker; // The One's who unlocks name :) +}; + +LINK_ENTITY_TO_CLASS(item_eyescanner, CEyeScanner); + +// +// Implementing save/restore +// + +TYPEDESCRIPTION CEyeScanner::m_SaveData[] = +{ + DEFINE_FIELD( CEyeScanner, m_bState, FIELD_BOOLEAN ), + DEFINE_FIELD( CEyeScanner, m_bIsKeymaker, FIELD_BOOLEAN ), + DEFINE_FIELD( CEyeScanner, m_iCheckLoop, FIELD_INTEGER ), + DEFINE_FIELD( CEyeScanner, m_iCheckFrame, FIELD_INTEGER ), + DEFINE_FIELD( CEyeScanner, m_iszTargetUnlocked, FIELD_STRING ), + DEFINE_FIELD( CEyeScanner, m_iszTargetLocked, FIELD_STRING ), + DEFINE_FIELD( CEyeScanner, m_iszKeymaker, FIELD_STRING ), +}; +IMPLEMENT_SAVERESTORE( CEyeScanner, CActAnimating ); + +// +// common functions +// + +#define mdlScanner "models/eye_scanner.mdl" +#define sndScannerBeep "buttons/blip1.wav" +#define sndScannerOpen "buttons/blip2.wav" +#define sndScannerDeny "buttons/button11.wav" + +void CEyeScanner::Precache( void ) +{ + PRECACHE_MODEL( mdlScanner ); + PRECACHE_SOUND( sndScannerBeep ); + PRECACHE_SOUND( sndScannerOpen ); + PRECACHE_SOUND( sndScannerDeny ); +} + +void CEyeScanner::Spawn( void ) +{ + Precache( ); + + SET_MODEL(ENT(pev), mdlScanner ); + + pev->movetype = MOVETYPE_NONE; + pev->solid = SOLID_TRIGGER; + + UTIL_SetSize( pev, Vector(-8,-8,0), Vector(8,8,32)); + SetActivity( ACT_CROUCHIDLE ); + + SetThink ( &CEyeScanner::ScannerThink ); + pev->nextthink = gpGlobals->time + 0.1; + //pev->frame = RANDOM_FLOAT(0,255); +} + +void CEyeScanner::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "unlocked_target")) + { + m_iszTargetUnlocked = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "locked_target")) + { + m_iszTargetLocked = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "unlockersname")) + { + m_iszKeymaker = ALLOC_STRING(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CActAnimating::KeyValue( pkvd ); + //m_iReactivate = atoi(pkvd->szValue); +} + +void CEyeScanner::ScannerThink( void ) +{ + StudioFrameAdvance(); + pev->nextthink = gpGlobals->time + 0.1; + + switch( GetActivity() ) + { + case ACT_CROUCH: // deactivate in progress + if ( m_fSequenceFinished ) + SetActivity( ACT_CROUCHIDLE ); + break; + + case ACT_CROUCHIDLE: // deactivated + pev->skin = 0; + m_bState = FALSE; + break; + + case ACT_STAND: // activate in progress + m_bState = TRUE; + if ( m_fSequenceFinished ) + SetActivity( ACT_IDLE ); + break; + + case ACT_IDLE: // activated + if ( gpGlobals->time > pev->dmgtime ) + SetActivity( ACT_CROUCH ); + break; + default: + break; + } +} + +void CEyeScanner :: Touch( CBaseEntity *pOther ) +{/* + int iClass = pOther->Classify(); + //if ( pOther->IsPlayer() ) + if (iClass == CLASS_PLAYER || iClass == CLASS_PLAYER_ALLY) + { + pev->dmgtime = gpGlobals->time + EYESCANNER_HIDE_TIME; + if ( GetActivity() == ACT_CROUCH || GetActivity() == ACT_CROUCHIDLE ) + { + SetActivity( ACT_STAND ); + } + }*/ +} + +void CEyeScanner :: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if (GetActivity() == ACT_CROUCHIDLE) + { + pev->dmgtime = gpGlobals->time + EYESCANNER_HIDE_TIME; + //if ( GetActivity() == ACT_CROUCH || GetActivity() == ACT_CROUCHIDLE ) + //{ + SetActivity( ACT_STAND ); + //} + } + + //if (m_bState == FALSE) + // return; + + int m_iszCallerName; + if (pActivator) + m_iszCallerName = pActivator->pev->targetname; + else + m_iszCallerName = pCaller->pev->targetname; + + //ALERT( at_console, "caller name is %s\n", STRING(m_iszCallerName) ); + + if (m_iszKeymaker) // if unlocker's name is specified then do check activator's name + m_bIsKeymaker = !strcmp(STRING(m_iszCallerName), STRING(m_iszKeymaker)); + else // otherwise open for everyone + m_bIsKeymaker = true; + + m_iCheckLoop = 0; + m_iCheckFrame = 1; + m_bState = FALSE; + SetThink( &CEyeScanner::UseThink ); + pev->nextthink = gpGlobals->time + 1.5; +} + +void CEyeScanner :: UseThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.15; + // buttons/blip1.wav + EMIT_SOUND( ENT(pev), CHAN_ITEM, sndScannerBeep, 0.85, ATTN_NORM ); + + if (m_iCheckLoop == EYESCANNER_SCAN_LOOPS) + { + // scan process finished - do something! + if (m_bIsKeymaker) + { + FireTargets( STRING(m_iszTargetUnlocked), this, this, USE_TOGGLE, 0 ); + EMIT_SOUND( ENT(pev), CHAN_ITEM, sndScannerOpen, 0.85, ATTN_NORM ); + } else + { + FireTargets( STRING(m_iszTargetLocked), this, this, USE_TOGGLE, 0 ); + EMIT_SOUND( ENT(pev), CHAN_ITEM, sndScannerDeny, 0.85, ATTN_NORM ); + } + + SetThink( &CEyeScanner::ScannerThink ); + pev->skin = 0; + return; + } + + // if we are in last animation frame, skip to first one + if (m_iCheckFrame == 4) + { + m_iCheckFrame = 1; + m_iCheckLoop++; + } + + pev->skin = m_iCheckFrame; + m_iCheckFrame++; +} diff --git a/dlls/decay_gamerules.cpp b/dlls/decay_gamerules.cpp index 799c9837..1ce20bca 100644 --- a/dlls/decay_gamerules.cpp +++ b/dlls/decay_gamerules.cpp @@ -1,1583 +1,1583 @@ -/*** -* -* Copyright (c) 1996-2002, Valve LLC. All rights reserved. -* -* This product contains software technology licensed from Id -* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. -* All Rights Reserved. -* -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. -* -****/ -// -// decay_gamerules.cpp -// Author: Vyacheslav Dzhura ( slava.dzhura@gmail.com ; slava.dzhura@protonmail.com ) -// (C) 2008 -// -#ifdef XASH_WIN32 -#include -#else -#include -#include -#define MAX_COMPUTERNAME_LENGTH 64 // random number -#endif -#include -#include "extdll.h" -#include "util.h" -#include "cbase.h" -#include "player.h" -#include "weapons.h" -#include "gamerules.h" -#include "skill.h" -#include "items.h" -#include "client.h" -#include "bot.h" -#include "triggers.h" - -extern DLL_GLOBAL CGameRules *g_pGameRules; -extern DLL_GLOBAL BOOL g_fGameOver; -extern int gmsgDeathMsg; // client dll messages -extern int gmsgScoreInfo; -extern int gmsgMOTD; -extern int gmsgChangePlayer; -extern int gmsgGameMode; -extern int gmsgSparePlayer; -extern int gmsgUpdateDecayPlayerName; -extern respawn_t bot_respawn[32]; - -bool bStatsLoaded = false; -int m_iMagicWord1 = 159123512; -int m_iMagicWord2 = 241245; -byte m_bMagicShift = 23; - -struct mapEntry //structure definition -{ - char szName[128]; - int m_iId; - int m_iNextId; - bool m_bLocked; - t_playerStats statsBest1; - t_playerStats statsLast1; - t_playerStats statsBest2; - t_playerStats statsLast2; -} ; - -static t_playerStats blankStats = -{ - 0, // kills; - 0, // int damage; - 0, // int shots; - 0, // int hits; - 0, // int lastShotId; - m_iMagicWord1, - 0, // lastShotCounted; - m_iMagicWord2, - 0, // float accuracy; - 3, // int gradeKills; - 3, // int gradeDamage; - 3, // int gradeAccuracy; - 3 // int gradeFinal; -}; - -mapEntry decayMaps[] = { - { "dy_accident1", 1, 2, false, blankStats, blankStats, blankStats, blankStats }, - { "dy_accident2", 2, 3, false, blankStats, blankStats, blankStats, blankStats }, - { "dy_hazard", 3, 4, true, blankStats, blankStats, blankStats, blankStats }, - { "dy_uplink", 4, 5, true, blankStats, blankStats, blankStats, blankStats }, - { "dy_dampen", 5, 6, true, blankStats, blankStats, blankStats, blankStats }, - { "dy_dorms", 6, 7, true, blankStats, blankStats, blankStats, blankStats }, - { "dy_signal", 7, 8, true, blankStats, blankStats, blankStats, blankStats }, - { "dy_focus", 8, 9, true, blankStats, blankStats, blankStats, blankStats }, - { "dy_lasers", 9, 10, true, blankStats, blankStats, blankStats, blankStats }, - { "dy_fubar", 10, 11, true, blankStats, blankStats, blankStats, blankStats }, - { "dy_outro", 11, -1, true, blankStats, blankStats, blankStats, blankStats }, - { "dy_alien", 12, -1, true, blankStats, blankStats, blankStats, blankStats } -}; - -inline char *GET_INFOBUFFER( edict_t *e ) -{ - return (*g_engfuncs.pfnGetInfoKeyBuffer)( e ); -} - -inline void SET_CLIENT_KEY_VALUE( int clientIndex, char *infobuffer, - const char *key, const char *value ) -{ - (*g_engfuncs.pfnSetClientKeyValue)( clientIndex, infobuffer, key, value ); -} - -int getMapEntryId( int desiredId ) -{ - int mapCount = sizeof(decayMaps)/sizeof(decayMaps[0]); - for ( int i = 0; i < mapCount; i++ ) - { - if ( decayMaps[i].m_iId == desiredId ) - return i; - } - return -1; -} - -int findMapId( char* szMapName ) -{ - int mapCount = sizeof(decayMaps)/sizeof(decayMaps[0]); - - int mapId = -1; - - //char szMapName[ 128 ]; - //strcpy( szMapName, szMapName ); - - for ( int i = 0; i < mapCount; i++ ) - { - if ( strcmp( szMapName, decayMaps[i].szName ) == 0 ) - { - mapId = decayMaps[i].m_iId; - break; - } - } - - return mapId; -} - -bool canUnlockBonusMission() -{ - int mapCount = sizeof(decayMaps)/sizeof(decayMaps[0]); - for ( int i = 0; i < mapCount; i++ ) - { - if (( decayMaps[i].statsBest1.gradeFinal != 0 ) || ( decayMaps[i].statsBest2.gradeFinal != 0 )) - return false; - } - return true; -} - -char *CDecayRules::getDecayMapName( int mapId ) -{ - static char szMap[128]; - sprintf( szMap, "null" ); - - int entryId = getMapEntryId( mapId ); - if ( entryId == -1 ) - return szMap; - - sprintf( szMap, decayMaps[entryId].szName ); - return szMap; -} - -void CDecayRules::SetAlienMode( bool bMode ) -{ - this->m_bAlienMode = bMode; -} - -static void Platform_GetComputerName( char *buffer, size_t buffersize ) -{ -#if XASH_WIN32 - DWORD size = buffersize; - GetComputerName( buffer, &size ); -#else - gethostname( buffer, buffersize); - buffer[buffersize - 1] = 0; // unspecified by POSIX if truncated name will be null-terminated -#endif -} - - -//========================================================= -//========================================================= -CDecayRules::CDecayRules( void ) -{ - char buffer[MAX_COMPUTERNAME_LENGTH+1]; - Platform_GetComputerName( buffer, sizeof( buffer )); - - m_iMagicWord1 = 0; - - int cnl = strlen(buffer); - for (int i=1; im_bAlienMode = false; - PlayersCount = 0; - RefreshSkillData(); - - statsLoad(); - - memset( &this->pStats[0], 0, sizeof( t_playerStats )); - memset( &this->pStats[1], 0, sizeof( t_playerStats )); - memset( &this->pStats[2], 0, sizeof( t_playerStats )); - memset( &this->pStats[3], 0, sizeof( t_playerStats )); - - int curMapId = this->getDecayMapId(); - if ( curMapId != -1 ) - { - int mapEntryId = getMapEntryId( curMapId ); - if ( decayMaps[mapEntryId].m_bLocked == true ) - { - ALERT( at_console, "Loading LOCKED MISSION!!!\n" ); - } else - ALERT( at_console, "Current mission is UNLOCKED!!!\n" ); - } - - //pLoopBack = NULL; -} - -//========================================================= -//========================================================= -void CDecayRules::statsLoad() -{ - byte *aMemFile; - byte *pMemFile; - int length; - - char szDirName[MAX_PATH]; - GET_GAME_DIR( szDirName ); - strcat( szDirName, "/save" ); - - char szFilename[MAX_PATH]; - strcpy ( szFilename, "save/save0.sv2" ); - - pMemFile = aMemFile = LOAD_FILE_FOR_ME(szFilename, &length); - - if ( ( !pMemFile ) || ( length == 0 ) ) - { - ALERT( at_console, "(load game) failed, file is null or does not exists!!!\n" ); - statsSave(); // create blank stats file - return; - } - - int iVersion; - byte bTrash[50]; - bool bHasNext = true; - bool bInvalid = false; - - // read version - memcpy(&iVersion, pMemFile, sizeof(int)); - pMemFile += sizeof(int); - - // read map entry - - mapEntry m_mapEntry; - byte bEntryCount = 0; - - while ( bHasNext == true ) - { - if ( bEntryCount > 12 ) - { - ALERT( at_console, "(load game) probably infinite loop - stopped reading game save!!!\n" ); - bInvalid = true; - break; - } - - memcpy(&bTrash[0], pMemFile, m_bMagicShift ); - pMemFile += m_bMagicShift; - - memcpy(&m_mapEntry, pMemFile, sizeof(mapEntry)); - pMemFile += sizeof(mapEntry); - - if (( m_mapEntry.statsLast1.magicWord1 != m_iMagicWord1 ) || ( m_mapEntry.statsLast1.magicWord2 != m_iMagicWord2 )) - { - bInvalid = true; // INVALID FILE!!! - break; - } - - int m_iReadMapId = findMapId( m_mapEntry.szName ); - int m_iReadMapEntryId = getMapEntryId( m_iReadMapId ); - if (( m_iReadMapId == -1 ) || ( m_iReadMapEntryId == -1 )) - { - bInvalid = true; // INVALID FILE!!! - break; - } - - decayMaps[m_iReadMapEntryId].m_bLocked = m_mapEntry.m_bLocked; - decayMaps[m_iReadMapEntryId].statsLast1 = m_mapEntry.statsLast1; - decayMaps[m_iReadMapEntryId].statsBest1 = m_mapEntry.statsBest1; - decayMaps[m_iReadMapEntryId].statsLast2 = m_mapEntry.statsLast2; - decayMaps[m_iReadMapEntryId].statsBest2 = m_mapEntry.statsBest2; - - // read next entry - memcpy(&bHasNext, pMemFile, sizeof(bHasNext)); - pMemFile += sizeof(bHasNext); - - bEntryCount++; - } - - FREE_FILE(aMemFile); - - if ( bInvalid == true ) - ALERT( at_console, "(load game) failed, file is invalid!!!\n" ); -} - -static void Platform_CreateDirectory( const char *szFilename ) -{ -#if XASH_WIN32 - CreateDirectory( szFilename, NULL ); -#else - mkdir( szFilename, 0777 ); -#endif -} - -void CDecayRules::statsSave() -{ - FILE *file; - int length; - - char szFilename[MAX_PATH]; - GET_GAME_DIR( szFilename ); - strcat( szFilename, "/save" ); - Platform_CreateDirectory( szFilename ); - strcat( szFilename, "/save0.sv2" ); - - // TODO: replace with fs_stdio interface - file = fopen ( szFilename, "wb" ); - - if ( !file ) - { - ALERT( at_console, "(save game) failed to create %s: %s!!!\n", szFilename, strerror( errno )); - return; - } - - int iVersion = 692008; - byte bTrash[50]; - bool bHasNext = true; - bool bInvalid = false; - - // write version - fwrite ( &iVersion, sizeof ( int ), 1, file ); - - // read map entry - mapEntry m_mapEntry; - byte bEntryCount = 0; - -/*********/ - char buffer[MAX_COMPUTERNAME_LENGTH+1]; - Platform_GetComputerName( buffer, sizeof( buffer )); - m_iMagicWord1 = 0; - - int cnl = strlen(buffer); - for (int i=1; i\n" ); - else - fprintf( fp, "\n" ); - - fprintf( fp, "%d\n", playerId ); - - fprintf( fp, "%f\n", playerStats.accuracy ); - fprintf( fp, "%d\n", playerStats.damage ); - fprintf( fp, "%d\n", playerStats.hits ); - fprintf( fp, "%d\n", playerStats.kills ); - - fprintf( fp, "\n" ); - fprintf( fp, "%c\n", getGradeChar( playerStats.gradeFinal ) ); - fprintf( fp, "%c\n", getGradeChar( playerStats.gradeAccuracy ) ); - fprintf( fp, "%c\n", getGradeChar( playerStats.gradeDamage ) ); - fprintf( fp, "%c\n", getGradeChar( playerStats.gradeKills ) ); - fprintf( fp, "\n" ); - - if ( bBest ) - fprintf( fp, "\n" ); - else - fprintf( fp, "\n" ); -} - -void CDecayRules::statsExportXml( void ) -{ - char szFilename[MAX_PATH]; - GET_GAME_DIR( szFilename ); - strcat( szFilename, "/manual" ); - Platform_CreateDirectory( szFilename ); - strcat( szFilename, "/stats.xml" ); - - FILE *fp; - - // TODO: replace with fs_stdio interface - fp = fopen( szFilename, "w" ); - if ( !fp ) - { - ALERT( at_console, "failed to export stats %s: %s", szFilename, strerror( errno )); - return; - } - - int mapCount = sizeof(decayMaps)/sizeof(decayMaps[0]); - - fprintf( fp, "\n" ); - fprintf( fp, "\n" ); - fprintf( fp, "\n" ); - for ( int i = 0; i < mapCount; i++ ) - { - fprintf( fp, "\n" ); - - fprintf( fp, "%d\n", decayMaps[i].m_iId ); - fprintf( fp, "%s\n", decayMaps[i].szName ); - fprintf( fp, "%d\n", decayMaps[i].m_bLocked ); - if ( decayMaps[i].m_iId == 12 ) - fprintf( fp, "1\n" ); - - printXmlPlayerStats( fp, decayMaps[i].statsBest1, true, 1 ); - printXmlPlayerStats( fp, decayMaps[i].statsLast1, false, 1 ); - printXmlPlayerStats( fp, decayMaps[i].statsBest2, true, 2 ); - printXmlPlayerStats( fp, decayMaps[i].statsLast2, false, 2 ); - - fprintf( fp, "\n" ); - } - fprintf( fp, "\n" ); - - //fprintf( fp, "%6.2f, %6.2f, %6.2f, %s, %2d\n", dataTime, health, ammo, pMapname, skillLevel ); - fclose( fp ); - -} - -//========================================================= -//========================================================= -void CDecayRules::Think ( void ) -{ -} - -//========================================================= -//========================================================= -BOOL CDecayRules::IsMultiplayer( void ) -{ - return FALSE; -} - -//========================================================= -//========================================================= -BOOL CDecayRules::IsDeathmatch ( void ) -{ - return FALSE; -} - -//========================================================= -//========================================================= -BOOL CDecayRules::IsCoOp( void ) -{ - int mapId = getDecayMapId(); - ALERT( at_console, "Decay IsCoOp = True, %d\n", mapId); - - return TRUE; -} - - -//========================================================= -//========================================================= -BOOL CDecayRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) -{ - if ( !pPlayer->m_pActiveItem ) - { - // player doesn't have an active item! - return TRUE; - } - - if ( !pPlayer->m_pActiveItem->CanHolster() ) - { - return FALSE; - } - - return TRUE; -} - -//========================================================= -//========================================================= -BOOL CDecayRules :: GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) -{ - return FALSE; -} - -void CDecayRules ::UpdateGameMode( CBasePlayer *pPlayer ) -{ - MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); - if ( m_bAlienMode ) - WRITE_BYTE( 2 ); // alien mode - else - WRITE_BYTE( 3 ); // turn off alien mode - MESSAGE_END(); -} - -//========================================================= -//========================================================= -BOOL CDecayRules :: ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) -{ - // pPlayers array of 2 (now 8) stores Decay players starting from 0 - // so first player is in pPlayer[0] (PlayersCount = 1) Gina - // second is in pPlayer[1] (PlayersCount = 2) Colette - // only second player could be a bot - - if (PlayersCount >= 2) - { - if (FBitSet(pPlayers[1]->pev->flags, FL_FAKECLIENT) && strcmp(pszAddress, "127.0.0.1") != 0) - { - bot_respawn[0].state = 0; - //SERVER_COMMAND("kick Colette\n"); - - char cmd[128]; - sprintf( cmd, "kick \"%s\"\n", STRING( pPlayers[1]->pev->netname ) ); - SERVER_COMMAND( cmd ); - return TRUE; - } else - return FALSE; // even if we return FALSE clien still gets connected - } - - // Players DecayId is assigned in GameRules->OnPlayerSpawn code which is called via ClientPutInServer - // PlayersCount variable is also incremented there - // thus code below is obsolete - - /* - pPlayers[PlayersCount] = GetClassPtr( (CBasePlayer *)VARS(pEntity)); - pPlayers[PlayersCount]->SetDecayPlayerIndex( PlayersCount+1 ); // Gina 1, Collete 2 - PlayersCount++; - - UTIL_LogPrintf( "\"%s<%i>\" has entered the game\n", STRING( pPlayers[PlayersCount]->pev->netname ), GETPLAYERUSERID( pPlayers[PlayersCount]->edict() ) ); - ALERT( at_console, "\"%s<%i>\" has entered the game\n", STRING( pPlayers[PlayersCount]->pev->netname ), GETPLAYERUSERID( pPlayers[PlayersCount]->edict() ) ); - */ - return TRUE; -} - -void CDecayRules :: InitHUD( CBasePlayer *pl ) -{ - // - // pop-up spare player message - // - - if ( pl->m_iDecayId >= 3) - { - MESSAGE_BEGIN( MSG_ONE, gmsgSparePlayer, NULL, pl->edict() ); - WRITE_BYTE( 0 ); - MESSAGE_END(); - } - - // - // update alien\human game mode - // - - MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pl->edict() ); - if ( m_bAlienMode ) - WRITE_BYTE( 2 ); // alien mode - else - WRITE_BYTE( 3 ); // turn off alien mode - MESSAGE_END(); - - // - // update HUD color - // - - MESSAGE_BEGIN( MSG_ONE, gmsgChangePlayer, NULL, pl->edict() ); - WRITE_BYTE( pl->m_iDecayId ); - MESSAGE_END(); - - // - // update pPlayer name - // - - MESSAGE_BEGIN( MSG_ONE, gmsgUpdateDecayPlayerName, NULL, pl->edict() ); - WRITE_BYTE( pl->m_iDecayId ); - MESSAGE_END(); - - UTIL_LogPrintf( "\"%s<%i>\" has entered the game\n", STRING( pl->pev->netname ), GETPLAYERUSERID( pl->edict() ) ); - - // - // check if mission is locked!!! - // - - int curMapId = this->getDecayMapId(); - if ( curMapId != -1 ) - { - if ( decayMaps[ getMapEntryId(curMapId) ].m_bLocked == true ) - { - // fade screen - // show message - // call end section - - CTriggerLockedMission *pKicker = (CTriggerLockedMission *)CBaseEntity::Create( "trigger_lockedmission", g_vecZero, g_vecZero ); - if ( !pKicker ) - ALERT( at_aiconsole, "Locked mission entity was not created!\n"); - pKicker->Lock(); - } - } - - // - // fire all trigger_clientspawn entities - // - - CBaseEntity *pEnt = NULL; - while ((pEnt = UTIL_FindEntityByClassname( pEnt, "trigger_clientspawn" )) != NULL) - pEnt->Use( pl, pl, USE_ON, 0.0 ); -} - -//========================================================= -//========================================================= -void CDecayRules :: ClientDisconnected( edict_t *pClient ) -{ - PlayersCount--; -} - -//========================================================= -//========================================================= -float CDecayRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) -{ - // subtract off the speed at which a player is allowed to fall without being hurt, - // so damage will be based on speed beyond that, not the entire fall - pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; - return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; -} - -void CDecayRules ::RemoveNewPlayer( int PlayerIdInArray ) -{ - CTriggerKicker *pKicker = (CTriggerKicker *)CBaseEntity::Create( "trigger_kicker", g_vecZero, g_vecZero ); - if ( !pKicker ) - ALERT( at_aiconsole, "Kicker entity was not created!\n"); - - pKicker->m_flDelay = 3; - pKicker->KickPlayer( pPlayers[PlayerIdInArray] ); -} - -//========================================================= -//========================================================= -void CDecayRules :: PlayerSpawn( CBasePlayer *pPlayer ) -{ - // UpdateGameMode( pPlayer ); - if ( PlayersCount >= 2) - { - pPlayers[PlayersCount] = pPlayer; // also store extra player in our array - PlayersCount++; - pPlayer->m_iDecayId = PlayersCount; - ALERT( at_console, "Spare player joined the game!\n"); - - // Vyacheslav Dzhura TODO - RemoveNewPlayer( PlayersCount-1 ); // in 5 seconds - - // all these won't normally remove the player: - // ClientDisconnect( pPlayer->edict( ) ); - // ClientKill( pPlayer->edict( ) ); - // UTIL_Remove( pPlayer ); - return; - } - - - pPlayers[PlayersCount] = pPlayer; - PlayersCount++; - pPlayer->m_iDecayId = PlayersCount; - - ALERT( at_console, "Player spawned with DecayID = %i \n", pPlayer->m_iDecayId ); - - if ( m_bAlienMode ) - pPlayer->GiveNamedItem( "weapon_vorti" ); -/* - // - // update pPlayerayer's models - // - - if ( !m_bAlienMode ) - SET_MODEL(ENT(pPlayer->pev), "models/player.mdl"); - else - SET_MODEL(ENT(pPlayer->pev), "models/player/dm_slave/dm_slave.mdl"); - - char *infobuffer = GET_INFOBUFFER( pPlayer->edict( ) ); - int clientIndex = pPlayer->entindex( ); - - if ( !m_bAlienMode ) - SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "model", "ginacol" ); - else - SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "model", "player/dm_slave/dm_slave" ); - - // - // update heads (for human mode) - // - - pPlayer->SetBodygroup( 0, 0 ); - if ( !m_bAlienMode ) - { - if ( pPlayer->m_iDecayId == 1 ) - { - pPlayer->SetBodygroup( 1, 1 ); - pPlayer->pev->skin = 1; - } else - if ( pPlayer->m_iDecayId == 2 ) - { - pPlayer->SetBodygroup( 1, 0 ); - pPlayer->pev->skin = 0; - } - } - - // - // update various pPlayerayer's properties - // - - if ( !m_bAlienMode ) - pPlayer->m_bloodColor = BLOOD_COLOR_RED; - else - pPlayer->m_bloodColor = BLOOD_COLOR_YELLOW; - - pPlayer->UpdateClientData(); -*/ -} - -//========================================================= -//========================================================= -BOOL CDecayRules :: AllowAutoTargetCrosshair( void ) -{ - return ( g_iSkillLevel == SKILL_EASY ); -} - -//========================================================= -//========================================================= -void CDecayRules :: PlayerThink( CBasePlayer *pPlayer ) -{ -} - - -//========================================================= -//========================================================= -BOOL CDecayRules :: FPlayerCanRespawn( CBasePlayer *pPlayer ) -{ - return TRUE; -} - -//========================================================= -//========================================================= -float CDecayRules :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) -{ - return gpGlobals->time;//now! -} - -//========================================================= -// IPointsForKill - how many points awarded to anyone -// that kills this player? -//========================================================= -int CDecayRules :: IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) -{ - return 1; -} - -void CDecayRules :: MonsterKilled( entvars_t *pKiller, entvars_t *pVictim ) -{ -// CBaseEntity *pAttacker = CBaseEntity::Instance(pevAttacker); - CBaseEntity *pKillerEnt = CBaseEntity::Instance( pKiller ); - if ( pKillerEnt->IsPlayer() ) - { - CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pKillerEnt->pev ); - CBaseEntity *pVictimEnt = CBaseEntity::Instance( pVictim ); - //ALERT( at_console, "Player %d killed %s entity!\n", pl->m_iDecayId, STRING( pVictimEnt->pev->classname ) ); - pStats[pl->m_iDecayId].kills++; - } -} - -void CDecayRules :: PlayerDamaged( CBasePlayer *pPlayer, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) -{ - //ALERT( at_console, "Player %d damaged for %f hp (dt: %d)\n", pPlayer->m_iDecayId, flDamage, bitsDamageType ); - pStats[pPlayer->m_iDecayId].damage += flDamage; -} - -void CDecayRules :: BulletsFired( entvars_t *pevAttacker, ULONG cShots, int iBulletType, int iShotId ) -{ - //lastShotAction - CBaseEntity *pShooterEnt = CBaseEntity::Instance( pevAttacker ); - if ( pShooterEnt->IsPlayer() ) - { - CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pShooterEnt->pev ); - pStats[pl->m_iDecayId].lastShotId = iShotId; - pStats[pl->m_iDecayId].lastShotCounted = false; - pStats[pl->m_iDecayId].shots++; - } -} - -void CDecayRules :: BulletHit( CBaseEntity *pEntity, entvars_t *pevAttacker, int iShotId ) -{ - CBaseEntity *pShooterEnt = CBaseEntity::Instance( pevAttacker ); - if ( pShooterEnt->IsPlayer() ) - { - CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pShooterEnt->pev ); - if ( ( iShotId == pStats[pl->m_iDecayId].lastShotId ) & (!pStats[pl->m_iDecayId].lastShotCounted) ) - { - // yes! we hit something - pStats[pl->m_iDecayId].lastShotCounted = true; - - if ( !FClassnameIs( pEntity->pev, "worldspawn" ) ) - pStats[pl->m_iDecayId].hits++; - - //ALERT( at_console, "Bullet hit stats - player hit %s \n", STRING(pEntity->pev->classname) ); - } - } -} - -char* CDecayRules :: getDecayNextMap() -{ - int mapCount = sizeof(decayMaps)/sizeof(decayMaps[0]); - - static char szNextMap[128]; - sprintf( szNextMap, "null" ); - - char szMapName[ 128 ]; - strcpy( szMapName, STRING(gpGlobals->mapname) ); - - for ( int i = 0; i < mapCount; i++ ) - { - if ( strcmp( szMapName, decayMaps[i].szName ) == 0 ) - { - if ( decayMaps[i].m_iNextId != -1 ) - { - int nextMapEntryId = getMapEntryId( decayMaps[i].m_iNextId ); - - if ( nextMapEntryId != -1 ) - { - sprintf( szNextMap, decayMaps[nextMapEntryId].szName ); - break; - } - } - } - } - - return szNextMap; -} - -int CDecayRules :: getDecayMapId() -{ - int mapCount = sizeof(decayMaps)/sizeof(decayMaps[0]); - - int mapId = -1; - - char szMapName[ 128 ]; - strcpy( szMapName, STRING(gpGlobals->mapname) ); - - for ( int i = 0; i < mapCount; i++ ) - { - if ( strcmp( szMapName, decayMaps[i].szName ) == 0 ) - { - mapId = decayMaps[i].m_iId; - break; - } - } - - return mapId; -/* - if ( strcmp( szMapName, "dy_accident1" ) == 0 ) - mapId = 0; -*/ -} - -void CDecayRules :: savePlayerStats( int playerId, int finalGrade, int damageGrade, int killsGrade, int accuracyGrade ) -{ - char buffer[MAX_COMPUTERNAME_LENGTH+1]; - Platform_GetComputerName( buffer, sizeof( buffer )); - - m_iMagicWord1 = 0; - - int cnl = strlen(buffer); - for (int i=1; igetDecayMapId(); - int mapEntryId = getMapEntryId( mapId ); - - pStats[playerId].magicWord1 = m_iMagicWord1; - pStats[playerId].magicWord2 = m_iMagicWord2; - - if ( mapEntryId != -1 ) - { - if ( playerId == 1 ) - { - //if ( !decayMaps[mapEntryId].statsBest1.gradeFinal || decayMaps[mapEntryId].statsBest1.gradeFinal <= finalGrade ) - if ( decayMaps[mapEntryId].statsBest1.gradeFinal >= finalGrade ) - { - decayMaps[mapEntryId].statsBest1 = pStats[playerId]; - decayMaps[mapEntryId].statsLast1 = pStats[playerId]; - } else - decayMaps[mapEntryId].statsLast1 = pStats[playerId]; - } else - if ( playerId == 2 ) - { - //if ( !decayMaps[mapEntryId].statsBest2.gradeFinal || decayMaps[mapEntryId].statsBest2.gradeFinal <= finalGrade ) - if ( decayMaps[mapEntryId].statsBest2.gradeFinal >= finalGrade ) - { - decayMaps[mapEntryId].statsBest2 = pStats[playerId]; - decayMaps[mapEntryId].statsLast2 = pStats[playerId]; - } else - decayMaps[mapEntryId].statsLast2 = pStats[playerId]; - } - - int nextMapId = decayMaps[mapEntryId].m_iNextId; - if ( nextMapId != -1 ) - { - decayMaps[getMapEntryId(nextMapId)].m_bLocked = false; // TODO: what if -1 here? - if ( mapId == 10 ) - if ( canUnlockBonusMission() == true ) - { - int bonusEntryId = getMapEntryId( 12 ); // dy_alien - if ( bonusEntryId != -1 ) - { - decayMaps[bonusEntryId].m_bLocked = false; - UTIL_ShowMessageAll( "CONGRATULATIONS!!!!! :D Alien mission unlocked!!!" ); - } - } - } - } - - statsSave(); -} - -//========================================================= -// PlayerKilled - someone/something killed this player -//========================================================= -void CDecayRules :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) -{ - if (PlayersCount > 2) // player count will be decreased afterwards in ClientDisconnected function - { - ALERT( at_console, "Removed player with id > 2 !!!\n" ); - return; - } - - char enddecayname[32]; - sprintf( enddecayname, "decay_player%d_dead", pVictim->m_iDecayId ); // decay_player2_dead - - //edict_t *pentTarget = NULL; - //pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, enddecayname); - FireTargets( enddecayname, pVictim, NULL, USE_ON, 1); - - // Could not find trigger_enddecay for player death! -} - -//========================================================= -// Deathnotice -//========================================================= -void CDecayRules::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) -{ -} - -//========================================================= -// PlayerGotWeapon - player has grabbed a weapon that was -// sitting in the world -//========================================================= -void CDecayRules :: PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) -{ -} - -//========================================================= -// CanHavePlayerItem - the player is touching an CBasePlayerItem, -// do I give it to him? -//========================================================= - -BOOL CDecayRules :: CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) -{ - // only living players can have items - if ( pPlayer->pev->deadflag != DEAD_NO ) - return FALSE; - - if (this->m_bAlienMode == true) - { - return (pWeapon->m_iId == WEAPON_VORTI); - } - - if (pWeapon->DecayPlayerIndex !=0) - { - return (pWeapon->DecayPlayerIndex == pPlayer->m_iDecayId); - } - - return true; -} - -//========================================================= -// FlWeaponRespawnTime - what is the time in the future -// at which this weapon may spawn? -//========================================================= -float CDecayRules :: FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) -{ - return -1; -} - -//========================================================= -// FlWeaponRespawnTime - Returns 0 if the weapon can respawn -// now, otherwise it returns the time at which it can try -// to spawn again. -//========================================================= -float CDecayRules :: FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) -{ - return 0; -} - -//========================================================= -// VecWeaponRespawnSpot - where should this weapon spawn? -// Some game variations may choose to randomize spawn locations -//========================================================= -Vector CDecayRules :: VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) -{ - return pWeapon->pev->origin; -} - -//========================================================= -// WeaponShouldRespawn - any conditions inhibiting the -// respawning of this weapon? -//========================================================= -int CDecayRules :: WeaponShouldRespawn( CBasePlayerItem *pWeapon ) -{ - return GR_WEAPON_RESPAWN_NO; -} - -//========================================================= -//========================================================= -BOOL CDecayRules::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) -{ - if ( this->m_bAlienMode ) - return FALSE; - else - return TRUE; -} - -//========================================================= -//========================================================= -void CDecayRules::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) -{ -} - -//========================================================= -//========================================================= -int CDecayRules::ItemShouldRespawn( CItem *pItem ) -{ - return GR_ITEM_RESPAWN_NO; -} - - -//========================================================= -// At what time in the future may this Item respawn? -//========================================================= -float CDecayRules::FlItemRespawnTime( CItem *pItem ) -{ - return -1; -} - -//========================================================= -// Where should this item respawn? -// Some game variations may choose to randomize spawn locations -//========================================================= -Vector CDecayRules::VecItemRespawnSpot( CItem *pItem ) -{ - return pItem->pev->origin; -} - -//========================================================= -//========================================================= -BOOL CDecayRules::IsAllowedToSpawn( CBaseEntity *pEntity ) -{ - return TRUE; -} - -//========================================================= -//========================================================= -void CDecayRules::PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) -{ -} - -//========================================================= -//========================================================= -int CDecayRules::AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) -{ - return GR_AMMO_RESPAWN_NO; -} - -//========================================================= -//========================================================= -float CDecayRules::FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) -{ - return -1; -} - -//========================================================= -//========================================================= -Vector CDecayRules::VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) -{ - return pAmmo->pev->origin; -} - -//========================================================= -//========================================================= -float CDecayRules::FlHealthChargerRechargeTime( void ) -{ - return 0;// don't recharge -} - -//========================================================= -//========================================================= -int CDecayRules::DeadPlayerWeapons( CBasePlayer *pPlayer ) -{ - return GR_PLR_DROP_GUN_NO; -} - -//========================================================= -//========================================================= -int CDecayRules::DeadPlayerAmmo( CBasePlayer *pPlayer ) -{ - return GR_PLR_DROP_AMMO_NO; -} - -//========================================================= -//========================================================= -int CDecayRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) -{ - // why would a single player in half life need this? - return GR_TEAMMATE; -} - -//========================================================= -//========================================================= -BOOL CDecayRules :: FAllowMonsters( void ) -{ - return TRUE; -} - -void CDecayRules::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) -{ -// ALERT( at_console, "client info (player %d) changed!\n", pPlayer->m_iDecayId ); -} - -void CopyWeaponsAndAmmo(CBasePlayer *PlayerFrom, CBasePlayer *PlayerTo, bool bDummyDest = false) -{ - int i; - CBasePlayerItem *pWeapon; - CBasePlayerItem *pNextWeapon; - CBasePlayerItem *pActiveWeapon = NULL; - int viewmodel, weaponmodel, sequence = 0; - - // - // AMMO - // - - for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) - { - PlayerTo->m_rgAmmo[i] = PlayerFrom->m_rgAmmo[ i ]; - // String below prevents sending to HUD (gmsgAmmoX) new ammo values (applied after weapon change) - //PlayerTo->m_rgAmmoLast[i] = PlayerFrom->m_rgAmmoLast[i]; - PlayerTo->m_rgAmmoLast[i] = 0; - } - if ( !bDummyDest ) - PlayerTo->SendAmmoUpdate(); - - // - // SELECTED (ACTIVE) WEAPON - // - - pActiveWeapon = NULL; - for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) - { - if ( PlayerFrom->m_rgpPlayerItems[ i ] ) - { - pWeapon = PlayerFrom->m_rgpPlayerItems[i]; - while ( pWeapon ) //rgpPackWeapons[ iPW ] - { - if ( pWeapon == PlayerFrom->m_pActiveItem ) - { - pActiveWeapon = pWeapon; - break; - } - pWeapon = pWeapon->m_pNext; - } // while - } - } - - sequence = PlayerFrom->pev->sequence; - if (pActiveWeapon) - { - viewmodel = PlayerFrom->pev->viewmodel; - weaponmodel = PlayerFrom->pev->weaponmodel; - } else - { - viewmodel = 0; - weaponmodel = 0; - } - - // - // WEAPONS - // - - for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) - { - if ( PlayerFrom->m_rgpPlayerItems[ i ] ) - { - pWeapon = PlayerFrom->m_rgpPlayerItems[i]; //(CBasePlayerWeapon *) - - while ( pWeapon ) //rgpPackWeapons[ iPW ] - { - if ( pWeapon->m_pPlayer ) - { - //if ( pWeapon == PlayerFrom->m_pActiveItem ) - //{ - // PlayerTo->m_pActiveItem = pWeapon; - //} - - pNextWeapon = pWeapon->m_pNext; - - // problem here moving from tmpPlayer to player 2!!! - if ( !pWeapon->m_pPlayer->RemovePlayerItem( pWeapon, false ) ) - { - ALERT( at_console, "problems removing weapon\n" ); - } - - int iWeaponSlot = pWeapon->iItemSlot(); - - if ( PlayerTo->m_rgpPlayerItems[ iWeaponSlot ] ) - { - // there's already one weapon in this slot, so link this into the slot's column - pWeapon->m_pNext = PlayerTo->m_rgpPlayerItems[ iWeaponSlot ]; - PlayerTo->m_rgpPlayerItems[ iWeaponSlot ] = pWeapon; - } - else - { - // first weapon we have for this slot - PlayerTo->m_rgpPlayerItems[ iWeaponSlot ] = pWeapon; - pWeapon->m_pNext = NULL; - } - - if ( bDummyDest == true ) - { - pWeapon->m_pPlayer = PlayerTo; //NULL; - pWeapon->pev->owner = PlayerTo->edict(); - } - else - { - pWeapon->AddToPlayer( PlayerTo ); - pWeapon->AttachToPlayer( PlayerTo ); - } - } - - pWeapon = pNextWeapon; - } // while - } - } - - if ( pActiveWeapon != NULL ) - { - PlayerTo->m_pActiveItem = pActiveWeapon; - PlayerTo->TabulateAmmo(); - if ( !bDummyDest ) - { - PlayerTo->m_pActiveItem->UpdateClientData( PlayerTo ); // should send gmsgCurWeapon, then should call SendAmmoUpdate which sends gmsgAmmox - PlayerTo->SwitchWeapon(pActiveWeapon); // calls m_pActiveWeapon->Deploy() - //PlayerTo->SendAmmoUpdate(); - PlayerTo->m_pActiveItem->UpdateItemInfo(); // updates HUD state - } - } else - PlayerTo->m_pActiveItem = NULL; - - //PlayerTo->m_pActiveItem = pActiveWeapon; - - PlayerTo->pev->viewmodel = viewmodel; - PlayerTo->pev->weaponmodel = weaponmodel; - PlayerTo->pev->weapons = PlayerFrom->pev->weapons; - PlayerTo->pev->sequence = sequence; - - if ( !bDummyDest ) - PlayerTo->UpdateClientData(); -} - -void CDecayRules :: ChangePlayer( void ) -{ - if (PlayersCount < 2) // was != - { - ALERT( at_console, "There is no second player!\n" ); - return; - } - - // CREATE TEMP PLAYER TO STORE SETTINGS TO COPY - edict_t *pent; - pent = CREATE_NAMED_ENTITY( MAKE_STRING( "player" ) ); - - if ( FNullEnt( pent ) ) - { - ALERT ( at_console, "Can't create tmpPlayer for ChangePlayer function!\n" ); - return; - } - CBasePlayer *tmpPlayer = (CBasePlayer*)CBasePlayer::Instance( VARS( pent )); - //ClearBits(tmpPlayer->pev->flags, FL_FAKECLIENT); - tmpPlayer->pev->flags |= FL_IMMUNE_WATER; // Hoaxer: HACK-HACK-HACK - - // ************** INFOBUFFERS ********************** - - char *infobuffer1; - char *infobuffer2; - char *pName; - char sName1[256]; - char sName2[256]; - - infobuffer1 = GET_INFOBUFFER( pPlayers[0]->edict( ) ); - pName = g_engfuncs.pfnInfoKeyValue( infobuffer1, "model" ); - strncpy( sName1, pName, sizeof(sName1) - 1 ); - sName1[ sizeof(sName1) - 1 ] = '\0'; - - infobuffer2 = GET_INFOBUFFER( pPlayers[1]->edict( ) ); - pName = g_engfuncs.pfnInfoKeyValue( infobuffer2, "model" ); - strncpy( sName2, pName, sizeof(sName2) - 1 ); - sName2[ sizeof(sName2) - 1 ] = '\0'; - - //sNameX contains model name without .mdl extensions - - // Vyacheslav Dzhura TODO: set "name" property as Gina and Colette manually here - //char sName3[256] = "Gina"; - //sName3[ sizeof(sName3) - 1 ] = '\0'; - //char sName4[256] = "Colette"; - //sName4[ sizeof(sName4) - 1 ] = '\0'; - - //SET_CLIENT_KEY_VALUE( pPlayers[0]->entindex(), infobuffer1, "name", sName3 ); - //SET_CLIENT_KEY_VALUE( pPlayers[1]->entindex(), infobuffer2, "name", sName4 ); - - SET_CLIENT_KEY_VALUE( pPlayers[0]->entindex(), infobuffer1, "model", sName2 ); - SET_CLIENT_KEY_VALUE( pPlayers[1]->entindex(), infobuffer2, "model", sName1 ); - // ************************************************* - - bool bP1Ducked = FBitSet(pPlayers[0]->pev->flags, FL_DUCKING); - bool bP2Ducked = FBitSet(pPlayers[1]->pev->flags, FL_DUCKING); - - // ************** FLASHLIGHT ********************** - bool tmpPlayer1UseFlashligh = pPlayers[0]->FlashlightIsOn(); // to avoid NOT CLIENT bug - bool tmpPlayer2UseFlashligh = pPlayers[1]->FlashlightIsOn(); // to avoid NOT CLIENT bug - - if (tmpPlayer2UseFlashligh) - pPlayers[0]->FlashlightTurnOn(); - else - pPlayers[0]->FlashlightTurnOff(); - - if (tmpPlayer1UseFlashligh) - pPlayers[1]->FlashlightTurnOn(); - else - pPlayers[1]->FlashlightTurnOff(); - - tmpPlayer->m_flFlashLightTime = pPlayers[0]->m_flFlashLightTime; - tmpPlayer->m_iFlashBattery = pPlayers[0]->m_iFlashBattery; - - pPlayers[0]->m_flFlashLightTime = pPlayers[1]->m_flFlashLightTime; - pPlayers[0]->m_iFlashBattery = pPlayers[1]->m_iFlashBattery; - - pPlayers[1]->m_flFlashLightTime = tmpPlayer->m_flFlashLightTime; - pPlayers[1]->m_iFlashBattery = tmpPlayer->m_iFlashBattery; - // ************************************************* - - //for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) - // ALERT( at_console, "BE4 p1 ammo %d is %d | p2 ammo %d is %d \n", i, pPlayers[0]->m_rgAmmo[ i ], i, pPlayers[1]->m_rgAmmo[ i ]); - - // - // weapons switching stuff - // - - // store items from PLAYER 1 to TEMP PLAYER - //ALERT( at_console, "(CopyWeaponsAndAmmo) from pPlayers[0] to tmpPlayer\n" ); - CopyWeaponsAndAmmo( pPlayers[0], tmpPlayer, true ); - pPlayers[0]->RemoveAllItems( FALSE ); - - // now from PLAYER 2 to PLAYER 1 - //ALERT( at_console, "(CopyWeaponsAndAmmo) from pPlayers[1] to pPlayers[0]\n" ); - CopyWeaponsAndAmmo( pPlayers[1], pPlayers[0] ); - pPlayers[1]->RemoveAllItems( FALSE ); - - // and finally from TEMP PLAYER to PLAYER 2 - //ALERT( at_console, "(CopyWeaponsAndAmmo) from tmpPlayer to pPlayers[1]\n" ); - CopyWeaponsAndAmmo( tmpPlayer, pPlayers[1] ); - - //ALERT( at_console, "DONE WITH WEAPOSN COPYING!!!\n" ); - - //for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) - // ALERT( at_console, "AFTER p1 ammo %d is %d | p2 ammo %d is %d \n", i, pPlayers[0]->m_rgAmmo[ i ], i, pPlayers[1]->m_rgAmmo[ i ]); - - // ******* BASIC PARAMS FROM PLAYER 1 TO TEMP PLAYER ******* - tmpPlayer->pev->origin = pPlayers[0]->pev->origin; - tmpPlayer->pev->v_angle = pPlayers[0]->pev->v_angle; - tmpPlayer->pev->angles = pPlayers[0]->pev->angles; - - tmpPlayer->m_iDecayId = pPlayers[0]->m_iDecayId; - tmpPlayer->pev->skin = pPlayers[0]->pev->skin; - tmpPlayer->pev->body = pPlayers[0]->pev->body; - tmpPlayer->pev->view_ofs = pPlayers[0]->pev->view_ofs; - tmpPlayer->pev->health = pPlayers[0]->pev->health; - tmpPlayer->pev->armorvalue = pPlayers[0]->pev->armorvalue; - - // ******* BASIC PARAMS FROM PLAYER 1 TO PLAYER 2 ******* - pPlayers[0]->pev->origin = pPlayers[1]->pev->origin; - pPlayers[0]->pev->v_angle = pPlayers[1]->pev->v_angle; - pPlayers[0]->pev->angles = pPlayers[1]->pev->angles; - pPlayers[0]->pev->fixangle = TRUE; - - pPlayers[0]->pev->view_ofs = pPlayers[1]->pev->view_ofs; - pPlayers[0]->m_iDecayId = pPlayers[1]->m_iDecayId; - pPlayers[0]->pev->skin = pPlayers[1]->pev->skin; - pPlayers[0]->pev->body= pPlayers[1]->pev->body; - pPlayers[0]->pev->health = pPlayers[1]->pev->health; - pPlayers[0]->pev->armorvalue = pPlayers[1]->pev->armorvalue; - - //pPlayers[0]->m_hEnemy = NULL; - //SetBits(pPlayers[0]->pev->flags, FL_FAKECLIENT); - - // ******* BASIC PARAMS FROM PLAYER 2 TO TEMP PLAYER ******* - pPlayers[1]->pev->origin = tmpPlayer->pev->origin; - pPlayers[1]->pev->v_angle = tmpPlayer->pev->v_angle; - pPlayers[1]->pev->angles = tmpPlayer->pev->angles; - pPlayers[1]->pev->fixangle = TRUE; - - pPlayers[1]->pev->view_ofs = tmpPlayer->pev->view_ofs; - pPlayers[1]->m_iDecayId = tmpPlayer->m_iDecayId; - pPlayers[1]->pev->skin = tmpPlayer->pev->skin; - pPlayers[1]->pev->body = tmpPlayer->pev->body; - pPlayers[1]->pev->health = tmpPlayer->pev->health; - pPlayers[1]->pev->armorvalue = tmpPlayer->pev->armorvalue; - - // AI - pPlayers[0]->m_hEnemy = NULL; - pPlayers[1]->m_hEnemy = NULL; - - //ClearBits(pPlayers[1]->pev->flags, FL_FAKECLIENT); - - // ******* FINAL PREPARATIONS ******* - // remove temp player - //ALERT( at_console, "REMOVING TMPPLAYER!!!\n" ); - UTIL_Remove( tmpPlayer ); - //ALERT( at_console, "TMPPLAYER SUCCESFULLY REMOVED!!!\n" ); - - // ******* DUCK FIX ************ - //if ((pev->button & IN_DUCK) || FBitSet(pev->flags,FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) ) - // Duck(); - - if (bP1Ducked == true) //= FBitSet(pPlayers[0]->pev->flags, FL_DUCKING); - { - SetBits(pPlayers[1]->pev->flags, FL_DUCKING); - pPlayers[1]->Duck(); - } - if (bP2Ducked == true) - { - SetBits(pPlayers[0]->pev->flags, FL_DUCKING); - pPlayers[0]->Duck(); - } - - // ******** END OF DUCK FIX **** - - // ****** HEADS CHANGE ********* - for ( int i = 0; i < 2; i++ ) - { - pPlayers[i]->SetBodygroup( 0, 0 ); - if ( pPlayers[i]->m_iDecayId == 1 ) - { - pPlayers[i]->SetBodygroup( 1, 1 ); - pPlayers[i]->pev->skin = 1; - } else - if ( pPlayers[i]->m_iDecayId == 2 ) - { - pPlayers[i]->SetBodygroup( 1, 0 ); - pPlayers[i]->pev->skin = 0; - } - } - - // ****** END OF HEADS CHANGE ****** - - if ( gmsgChangePlayer != 0) - { - //MESSAGE_BEGIN( MSG_ALL, gmsgChangePlayer); - MESSAGE_BEGIN( MSG_ONE, gmsgChangePlayer, NULL, pPlayers[0]->edict() ); - WRITE_BYTE( pPlayers[0]->m_iDecayId ); - MESSAGE_END(); - MESSAGE_BEGIN( MSG_ONE, gmsgChangePlayer, NULL, pPlayers[1]->edict() ); - WRITE_BYTE( pPlayers[1]->m_iDecayId ); - MESSAGE_END(); - } else - ALERT( at_console, "Message gmsgChangePlayer not found in client.dll! Invalid CLIENT.DLL!!!\n" ); - - // update client data... again :) - //pPlayers[0]->UpdateClientData(); - //pPlayers[1]->UpdateClientData(); - - //ALERT( at_console, "Checking if second player is bot...\n" ); - if (!pPlayers[1]->IsNetClient()) // if second player is bot - { - CBot *pBot; - pBot = (CBot*)pPlayers[1]; - pBot->pBotEnemy = NULL; - pBot->BotWeaponInventory(); - } - // BotWeaponInventory -} +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +// +// decay_gamerules.cpp +// Author: Vyacheslav Dzhura ( slava.dzhura@gmail.com ; slava.dzhura@protonmail.com ) +// (C) 2008 +// +#ifdef XASH_WIN32 +#include +#else +#include +#include +#define MAX_COMPUTERNAME_LENGTH 64 // random number +#endif +#include +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "weapons.h" +#include "gamerules.h" +#include "skill.h" +#include "items.h" +#include "client.h" +#include "bot.h" +#include "triggers.h" + +extern DLL_GLOBAL CGameRules *g_pGameRules; +extern DLL_GLOBAL BOOL g_fGameOver; +extern int gmsgDeathMsg; // client dll messages +extern int gmsgScoreInfo; +extern int gmsgMOTD; +extern int gmsgChangePlayer; +extern int gmsgGameMode; +extern int gmsgSparePlayer; +extern int gmsgUpdateDecayPlayerName; +extern respawn_t bot_respawn[32]; + +bool bStatsLoaded = false; +int m_iMagicWord1 = 159123512; +int m_iMagicWord2 = 241245; +byte m_bMagicShift = 23; + +struct mapEntry //structure definition +{ + char szName[128]; + int m_iId; + int m_iNextId; + bool m_bLocked; + t_playerStats statsBest1; + t_playerStats statsLast1; + t_playerStats statsBest2; + t_playerStats statsLast2; +} ; + +static t_playerStats blankStats = +{ + 0, // kills; + 0, // int damage; + 0, // int shots; + 0, // int hits; + 0, // int lastShotId; + m_iMagicWord1, + 0, // lastShotCounted; + m_iMagicWord2, + 0, // float accuracy; + 3, // int gradeKills; + 3, // int gradeDamage; + 3, // int gradeAccuracy; + 3 // int gradeFinal; +}; + +mapEntry decayMaps[] = { + { "dy_accident1", 1, 2, false, blankStats, blankStats, blankStats, blankStats }, + { "dy_accident2", 2, 3, false, blankStats, blankStats, blankStats, blankStats }, + { "dy_hazard", 3, 4, true, blankStats, blankStats, blankStats, blankStats }, + { "dy_uplink", 4, 5, true, blankStats, blankStats, blankStats, blankStats }, + { "dy_dampen", 5, 6, true, blankStats, blankStats, blankStats, blankStats }, + { "dy_dorms", 6, 7, true, blankStats, blankStats, blankStats, blankStats }, + { "dy_signal", 7, 8, true, blankStats, blankStats, blankStats, blankStats }, + { "dy_focus", 8, 9, true, blankStats, blankStats, blankStats, blankStats }, + { "dy_lasers", 9, 10, true, blankStats, blankStats, blankStats, blankStats }, + { "dy_fubar", 10, 11, true, blankStats, blankStats, blankStats, blankStats }, + { "dy_outro", 11, -1, true, blankStats, blankStats, blankStats, blankStats }, + { "dy_alien", 12, -1, true, blankStats, blankStats, blankStats, blankStats } +}; + +inline char *GET_INFOBUFFER( edict_t *e ) +{ + return (*g_engfuncs.pfnGetInfoKeyBuffer)( e ); +} + +inline void SET_CLIENT_KEY_VALUE( int clientIndex, char *infobuffer, + const char *key, const char *value ) +{ + (*g_engfuncs.pfnSetClientKeyValue)( clientIndex, infobuffer, key, value ); +} + +int getMapEntryId( int desiredId ) +{ + int mapCount = sizeof(decayMaps)/sizeof(decayMaps[0]); + for ( int i = 0; i < mapCount; i++ ) + { + if ( decayMaps[i].m_iId == desiredId ) + return i; + } + return -1; +} + +int findMapId( char* szMapName ) +{ + int mapCount = sizeof(decayMaps)/sizeof(decayMaps[0]); + + int mapId = -1; + + //char szMapName[ 128 ]; + //strcpy( szMapName, szMapName ); + + for ( int i = 0; i < mapCount; i++ ) + { + if ( strcmp( szMapName, decayMaps[i].szName ) == 0 ) + { + mapId = decayMaps[i].m_iId; + break; + } + } + + return mapId; +} + +bool canUnlockBonusMission() +{ + int mapCount = sizeof(decayMaps)/sizeof(decayMaps[0]); + for ( int i = 0; i < mapCount; i++ ) + { + if (( decayMaps[i].statsBest1.gradeFinal != 0 ) || ( decayMaps[i].statsBest2.gradeFinal != 0 )) + return false; + } + return true; +} + +char *CDecayRules::getDecayMapName( int mapId ) +{ + static char szMap[128]; + sprintf( szMap, "null" ); + + int entryId = getMapEntryId( mapId ); + if ( entryId == -1 ) + return szMap; + + sprintf( szMap, decayMaps[entryId].szName ); + return szMap; +} + +void CDecayRules::SetAlienMode( bool bMode ) +{ + this->m_bAlienMode = bMode; +} + +static void Platform_GetComputerName( char *buffer, size_t buffersize ) +{ +#if XASH_WIN32 + DWORD size = buffersize; + GetComputerName( buffer, &size ); +#else + gethostname( buffer, buffersize); + buffer[buffersize - 1] = 0; // unspecified by POSIX if truncated name will be null-terminated +#endif +} + + +//========================================================= +//========================================================= +CDecayRules::CDecayRules( void ) +{ + char buffer[MAX_COMPUTERNAME_LENGTH+1]; + Platform_GetComputerName( buffer, sizeof( buffer )); + + m_iMagicWord1 = 0; + + int cnl = strlen(buffer); + for (int i=1; im_bAlienMode = false; + PlayersCount = 0; + RefreshSkillData(); + + statsLoad(); + + memset( &this->pStats[0], 0, sizeof( t_playerStats )); + memset( &this->pStats[1], 0, sizeof( t_playerStats )); + memset( &this->pStats[2], 0, sizeof( t_playerStats )); + memset( &this->pStats[3], 0, sizeof( t_playerStats )); + + int curMapId = this->getDecayMapId(); + if ( curMapId != -1 ) + { + int mapEntryId = getMapEntryId( curMapId ); + if ( decayMaps[mapEntryId].m_bLocked == true ) + { + ALERT( at_console, "Loading LOCKED MISSION!!!\n" ); + } else + ALERT( at_console, "Current mission is UNLOCKED!!!\n" ); + } + + //pLoopBack = NULL; +} + +//========================================================= +//========================================================= +void CDecayRules::statsLoad() +{ + byte *aMemFile; + byte *pMemFile; + int length; + + char szDirName[MAX_PATH]; + GET_GAME_DIR( szDirName ); + strcat( szDirName, "/save" ); + + char szFilename[MAX_PATH]; + strcpy ( szFilename, "save/save0.sv2" ); + + pMemFile = aMemFile = LOAD_FILE_FOR_ME(szFilename, &length); + + if ( ( !pMemFile ) || ( length == 0 ) ) + { + ALERT( at_console, "(load game) failed, file is null or does not exists!!!\n" ); + statsSave(); // create blank stats file + return; + } + + int iVersion; + byte bTrash[50]; + bool bHasNext = true; + bool bInvalid = false; + + // read version + memcpy(&iVersion, pMemFile, sizeof(int)); + pMemFile += sizeof(int); + + // read map entry + + mapEntry m_mapEntry; + byte bEntryCount = 0; + + while ( bHasNext == true ) + { + if ( bEntryCount > 12 ) + { + ALERT( at_console, "(load game) probably infinite loop - stopped reading game save!!!\n" ); + bInvalid = true; + break; + } + + memcpy(&bTrash[0], pMemFile, m_bMagicShift ); + pMemFile += m_bMagicShift; + + memcpy(&m_mapEntry, pMemFile, sizeof(mapEntry)); + pMemFile += sizeof(mapEntry); + + if (( m_mapEntry.statsLast1.magicWord1 != m_iMagicWord1 ) || ( m_mapEntry.statsLast1.magicWord2 != m_iMagicWord2 )) + { + bInvalid = true; // INVALID FILE!!! + break; + } + + int m_iReadMapId = findMapId( m_mapEntry.szName ); + int m_iReadMapEntryId = getMapEntryId( m_iReadMapId ); + if (( m_iReadMapId == -1 ) || ( m_iReadMapEntryId == -1 )) + { + bInvalid = true; // INVALID FILE!!! + break; + } + + decayMaps[m_iReadMapEntryId].m_bLocked = m_mapEntry.m_bLocked; + decayMaps[m_iReadMapEntryId].statsLast1 = m_mapEntry.statsLast1; + decayMaps[m_iReadMapEntryId].statsBest1 = m_mapEntry.statsBest1; + decayMaps[m_iReadMapEntryId].statsLast2 = m_mapEntry.statsLast2; + decayMaps[m_iReadMapEntryId].statsBest2 = m_mapEntry.statsBest2; + + // read next entry + memcpy(&bHasNext, pMemFile, sizeof(bHasNext)); + pMemFile += sizeof(bHasNext); + + bEntryCount++; + } + + FREE_FILE(aMemFile); + + if ( bInvalid == true ) + ALERT( at_console, "(load game) failed, file is invalid!!!\n" ); +} + +static void Platform_CreateDirectory( const char *szFilename ) +{ +#if XASH_WIN32 + CreateDirectory( szFilename, NULL ); +#else + mkdir( szFilename, 0777 ); +#endif +} + +void CDecayRules::statsSave() +{ + FILE *file; + int length; + + char szFilename[MAX_PATH]; + GET_GAME_DIR( szFilename ); + strcat( szFilename, "/save" ); + Platform_CreateDirectory( szFilename ); + strcat( szFilename, "/save0.sv2" ); + + // TODO: replace with fs_stdio interface + file = fopen ( szFilename, "wb" ); + + if ( !file ) + { + ALERT( at_console, "(save game) failed to create %s: %s!!!\n", szFilename, strerror( errno )); + return; + } + + int iVersion = 692008; + byte bTrash[50]; + bool bHasNext = true; + bool bInvalid = false; + + // write version + fwrite ( &iVersion, sizeof ( int ), 1, file ); + + // read map entry + mapEntry m_mapEntry; + byte bEntryCount = 0; + +/*********/ + char buffer[MAX_COMPUTERNAME_LENGTH+1]; + Platform_GetComputerName( buffer, sizeof( buffer )); + m_iMagicWord1 = 0; + + int cnl = strlen(buffer); + for (int i=1; i\n" ); + else + fprintf( fp, "\n" ); + + fprintf( fp, "%d\n", playerId ); + + fprintf( fp, "%f\n", playerStats.accuracy ); + fprintf( fp, "%d\n", playerStats.damage ); + fprintf( fp, "%d\n", playerStats.hits ); + fprintf( fp, "%d\n", playerStats.kills ); + + fprintf( fp, "\n" ); + fprintf( fp, "%c\n", getGradeChar( playerStats.gradeFinal ) ); + fprintf( fp, "%c\n", getGradeChar( playerStats.gradeAccuracy ) ); + fprintf( fp, "%c\n", getGradeChar( playerStats.gradeDamage ) ); + fprintf( fp, "%c\n", getGradeChar( playerStats.gradeKills ) ); + fprintf( fp, "\n" ); + + if ( bBest ) + fprintf( fp, "\n" ); + else + fprintf( fp, "\n" ); +} + +void CDecayRules::statsExportXml( void ) +{ + char szFilename[MAX_PATH]; + GET_GAME_DIR( szFilename ); + strcat( szFilename, "/manual" ); + Platform_CreateDirectory( szFilename ); + strcat( szFilename, "/stats.xml" ); + + FILE *fp; + + // TODO: replace with fs_stdio interface + fp = fopen( szFilename, "w" ); + if ( !fp ) + { + ALERT( at_console, "failed to export stats %s: %s", szFilename, strerror( errno )); + return; + } + + int mapCount = sizeof(decayMaps)/sizeof(decayMaps[0]); + + fprintf( fp, "\n" ); + fprintf( fp, "\n" ); + fprintf( fp, "\n" ); + for ( int i = 0; i < mapCount; i++ ) + { + fprintf( fp, "\n" ); + + fprintf( fp, "%d\n", decayMaps[i].m_iId ); + fprintf( fp, "%s\n", decayMaps[i].szName ); + fprintf( fp, "%d\n", decayMaps[i].m_bLocked ); + if ( decayMaps[i].m_iId == 12 ) + fprintf( fp, "1\n" ); + + printXmlPlayerStats( fp, decayMaps[i].statsBest1, true, 1 ); + printXmlPlayerStats( fp, decayMaps[i].statsLast1, false, 1 ); + printXmlPlayerStats( fp, decayMaps[i].statsBest2, true, 2 ); + printXmlPlayerStats( fp, decayMaps[i].statsLast2, false, 2 ); + + fprintf( fp, "\n" ); + } + fprintf( fp, "\n" ); + + //fprintf( fp, "%6.2f, %6.2f, %6.2f, %s, %2d\n", dataTime, health, ammo, pMapname, skillLevel ); + fclose( fp ); + +} + +//========================================================= +//========================================================= +void CDecayRules::Think ( void ) +{ +} + +//========================================================= +//========================================================= +BOOL CDecayRules::IsMultiplayer( void ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CDecayRules::IsDeathmatch ( void ) +{ + return FALSE; +} + +//========================================================= +//========================================================= +BOOL CDecayRules::IsCoOp( void ) +{ + int mapId = getDecayMapId(); + ALERT( at_console, "Decay IsCoOp = True, %d\n", mapId); + + return TRUE; +} + + +//========================================================= +//========================================================= +BOOL CDecayRules::FShouldSwitchWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + if ( !pPlayer->m_pActiveItem ) + { + // player doesn't have an active item! + return TRUE; + } + + if ( !pPlayer->m_pActiveItem->CanHolster() ) + { + return FALSE; + } + + return TRUE; +} + +//========================================================= +//========================================================= +BOOL CDecayRules :: GetNextBestWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pCurrentWeapon ) +{ + return FALSE; +} + +void CDecayRules ::UpdateGameMode( CBasePlayer *pPlayer ) +{ + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pPlayer->edict() ); + if ( m_bAlienMode ) + WRITE_BYTE( 2 ); // alien mode + else + WRITE_BYTE( 3 ); // turn off alien mode + MESSAGE_END(); +} + +//========================================================= +//========================================================= +BOOL CDecayRules :: ClientConnected( edict_t *pEntity, const char *pszName, const char *pszAddress, char szRejectReason[ 128 ] ) +{ + // pPlayers array of 2 (now 8) stores Decay players starting from 0 + // so first player is in pPlayer[0] (PlayersCount = 1) Gina + // second is in pPlayer[1] (PlayersCount = 2) Colette + // only second player could be a bot + + if (PlayersCount >= 2) + { + if (FBitSet(pPlayers[1]->pev->flags, FL_FAKECLIENT) && strcmp(pszAddress, "127.0.0.1") != 0) + { + bot_respawn[0].state = 0; + //SERVER_COMMAND("kick Colette\n"); + + char cmd[128]; + sprintf( cmd, "kick \"%s\"\n", STRING( pPlayers[1]->pev->netname ) ); + SERVER_COMMAND( cmd ); + return TRUE; + } else + return FALSE; // even if we return FALSE clien still gets connected + } + + // Players DecayId is assigned in GameRules->OnPlayerSpawn code which is called via ClientPutInServer + // PlayersCount variable is also incremented there + // thus code below is obsolete + + /* + pPlayers[PlayersCount] = GetClassPtr( (CBasePlayer *)VARS(pEntity)); + pPlayers[PlayersCount]->SetDecayPlayerIndex( PlayersCount+1 ); // Gina 1, Collete 2 + PlayersCount++; + + UTIL_LogPrintf( "\"%s<%i>\" has entered the game\n", STRING( pPlayers[PlayersCount]->pev->netname ), GETPLAYERUSERID( pPlayers[PlayersCount]->edict() ) ); + ALERT( at_console, "\"%s<%i>\" has entered the game\n", STRING( pPlayers[PlayersCount]->pev->netname ), GETPLAYERUSERID( pPlayers[PlayersCount]->edict() ) ); + */ + return TRUE; +} + +void CDecayRules :: InitHUD( CBasePlayer *pl ) +{ + // + // pop-up spare player message + // + + if ( pl->m_iDecayId >= 3) + { + MESSAGE_BEGIN( MSG_ONE, gmsgSparePlayer, NULL, pl->edict() ); + WRITE_BYTE( 0 ); + MESSAGE_END(); + } + + // + // update alien\human game mode + // + + MESSAGE_BEGIN( MSG_ONE, gmsgGameMode, NULL, pl->edict() ); + if ( m_bAlienMode ) + WRITE_BYTE( 2 ); // alien mode + else + WRITE_BYTE( 3 ); // turn off alien mode + MESSAGE_END(); + + // + // update HUD color + // + + MESSAGE_BEGIN( MSG_ONE, gmsgChangePlayer, NULL, pl->edict() ); + WRITE_BYTE( pl->m_iDecayId ); + MESSAGE_END(); + + // + // update pPlayer name + // + + MESSAGE_BEGIN( MSG_ONE, gmsgUpdateDecayPlayerName, NULL, pl->edict() ); + WRITE_BYTE( pl->m_iDecayId ); + MESSAGE_END(); + + UTIL_LogPrintf( "\"%s<%i>\" has entered the game\n", STRING( pl->pev->netname ), GETPLAYERUSERID( pl->edict() ) ); + + // + // check if mission is locked!!! + // + + int curMapId = this->getDecayMapId(); + if ( curMapId != -1 ) + { + if ( decayMaps[ getMapEntryId(curMapId) ].m_bLocked == true ) + { + // fade screen + // show message + // call end section + + CTriggerLockedMission *pKicker = (CTriggerLockedMission *)CBaseEntity::Create( "trigger_lockedmission", g_vecZero, g_vecZero ); + if ( !pKicker ) + ALERT( at_aiconsole, "Locked mission entity was not created!\n"); + pKicker->Lock(); + } + } + + // + // fire all trigger_clientspawn entities + // + + CBaseEntity *pEnt = NULL; + while ((pEnt = UTIL_FindEntityByClassname( pEnt, "trigger_clientspawn" )) != NULL) + pEnt->Use( pl, pl, USE_ON, 0.0 ); +} + +//========================================================= +//========================================================= +void CDecayRules :: ClientDisconnected( edict_t *pClient ) +{ + PlayersCount--; +} + +//========================================================= +//========================================================= +float CDecayRules::FlPlayerFallDamage( CBasePlayer *pPlayer ) +{ + // subtract off the speed at which a player is allowed to fall without being hurt, + // so damage will be based on speed beyond that, not the entire fall + pPlayer->m_flFallVelocity -= PLAYER_MAX_SAFE_FALL_SPEED; + return pPlayer->m_flFallVelocity * DAMAGE_FOR_FALL_SPEED; +} + +void CDecayRules ::RemoveNewPlayer( int PlayerIdInArray ) +{ + CTriggerKicker *pKicker = (CTriggerKicker *)CBaseEntity::Create( "trigger_kicker", g_vecZero, g_vecZero ); + if ( !pKicker ) + ALERT( at_aiconsole, "Kicker entity was not created!\n"); + + pKicker->m_flDelay = 3; + pKicker->KickPlayer( pPlayers[PlayerIdInArray] ); +} + +//========================================================= +//========================================================= +void CDecayRules :: PlayerSpawn( CBasePlayer *pPlayer ) +{ + // UpdateGameMode( pPlayer ); + if ( PlayersCount >= 2) + { + pPlayers[PlayersCount] = pPlayer; // also store extra player in our array + PlayersCount++; + pPlayer->m_iDecayId = PlayersCount; + ALERT( at_console, "Spare player joined the game!\n"); + + // Vyacheslav Dzhura TODO + RemoveNewPlayer( PlayersCount-1 ); // in 5 seconds + + // all these won't normally remove the player: + // ClientDisconnect( pPlayer->edict( ) ); + // ClientKill( pPlayer->edict( ) ); + // UTIL_Remove( pPlayer ); + return; + } + + + pPlayers[PlayersCount] = pPlayer; + PlayersCount++; + pPlayer->m_iDecayId = PlayersCount; + + ALERT( at_console, "Player spawned with DecayID = %i \n", pPlayer->m_iDecayId ); + + if ( m_bAlienMode ) + pPlayer->GiveNamedItem( "weapon_vorti" ); +/* + // + // update pPlayerayer's models + // + + if ( !m_bAlienMode ) + SET_MODEL(ENT(pPlayer->pev), "models/player.mdl"); + else + SET_MODEL(ENT(pPlayer->pev), "models/player/dm_slave/dm_slave.mdl"); + + char *infobuffer = GET_INFOBUFFER( pPlayer->edict( ) ); + int clientIndex = pPlayer->entindex( ); + + if ( !m_bAlienMode ) + SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "model", "ginacol" ); + else + SET_CLIENT_KEY_VALUE( clientIndex, infobuffer, "model", "player/dm_slave/dm_slave" ); + + // + // update heads (for human mode) + // + + pPlayer->SetBodygroup( 0, 0 ); + if ( !m_bAlienMode ) + { + if ( pPlayer->m_iDecayId == 1 ) + { + pPlayer->SetBodygroup( 1, 1 ); + pPlayer->pev->skin = 1; + } else + if ( pPlayer->m_iDecayId == 2 ) + { + pPlayer->SetBodygroup( 1, 0 ); + pPlayer->pev->skin = 0; + } + } + + // + // update various pPlayerayer's properties + // + + if ( !m_bAlienMode ) + pPlayer->m_bloodColor = BLOOD_COLOR_RED; + else + pPlayer->m_bloodColor = BLOOD_COLOR_YELLOW; + + pPlayer->UpdateClientData(); +*/ +} + +//========================================================= +//========================================================= +BOOL CDecayRules :: AllowAutoTargetCrosshair( void ) +{ + return ( g_iSkillLevel == SKILL_EASY ); +} + +//========================================================= +//========================================================= +void CDecayRules :: PlayerThink( CBasePlayer *pPlayer ) +{ +} + + +//========================================================= +//========================================================= +BOOL CDecayRules :: FPlayerCanRespawn( CBasePlayer *pPlayer ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +float CDecayRules :: FlPlayerSpawnTime( CBasePlayer *pPlayer ) +{ + return gpGlobals->time;//now! +} + +//========================================================= +// IPointsForKill - how many points awarded to anyone +// that kills this player? +//========================================================= +int CDecayRules :: IPointsForKill( CBasePlayer *pAttacker, CBasePlayer *pKilled ) +{ + return 1; +} + +void CDecayRules :: MonsterKilled( entvars_t *pKiller, entvars_t *pVictim ) +{ +// CBaseEntity *pAttacker = CBaseEntity::Instance(pevAttacker); + CBaseEntity *pKillerEnt = CBaseEntity::Instance( pKiller ); + if ( pKillerEnt->IsPlayer() ) + { + CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pKillerEnt->pev ); + CBaseEntity *pVictimEnt = CBaseEntity::Instance( pVictim ); + //ALERT( at_console, "Player %d killed %s entity!\n", pl->m_iDecayId, STRING( pVictimEnt->pev->classname ) ); + pStats[pl->m_iDecayId].kills++; + } +} + +void CDecayRules :: PlayerDamaged( CBasePlayer *pPlayer, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + //ALERT( at_console, "Player %d damaged for %f hp (dt: %d)\n", pPlayer->m_iDecayId, flDamage, bitsDamageType ); + pStats[pPlayer->m_iDecayId].damage += flDamage; +} + +void CDecayRules :: BulletsFired( entvars_t *pevAttacker, ULONG cShots, int iBulletType, int iShotId ) +{ + //lastShotAction + CBaseEntity *pShooterEnt = CBaseEntity::Instance( pevAttacker ); + if ( pShooterEnt->IsPlayer() ) + { + CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pShooterEnt->pev ); + pStats[pl->m_iDecayId].lastShotId = iShotId; + pStats[pl->m_iDecayId].lastShotCounted = false; + pStats[pl->m_iDecayId].shots++; + } +} + +void CDecayRules :: BulletHit( CBaseEntity *pEntity, entvars_t *pevAttacker, int iShotId ) +{ + CBaseEntity *pShooterEnt = CBaseEntity::Instance( pevAttacker ); + if ( pShooterEnt->IsPlayer() ) + { + CBasePlayer *pl = (CBasePlayer*) CBasePlayer::Instance( pShooterEnt->pev ); + if ( ( iShotId == pStats[pl->m_iDecayId].lastShotId ) & (!pStats[pl->m_iDecayId].lastShotCounted) ) + { + // yes! we hit something + pStats[pl->m_iDecayId].lastShotCounted = true; + + if ( !FClassnameIs( pEntity->pev, "worldspawn" ) ) + pStats[pl->m_iDecayId].hits++; + + //ALERT( at_console, "Bullet hit stats - player hit %s \n", STRING(pEntity->pev->classname) ); + } + } +} + +char* CDecayRules :: getDecayNextMap() +{ + int mapCount = sizeof(decayMaps)/sizeof(decayMaps[0]); + + static char szNextMap[128]; + sprintf( szNextMap, "null" ); + + char szMapName[ 128 ]; + strcpy( szMapName, STRING(gpGlobals->mapname) ); + + for ( int i = 0; i < mapCount; i++ ) + { + if ( strcmp( szMapName, decayMaps[i].szName ) == 0 ) + { + if ( decayMaps[i].m_iNextId != -1 ) + { + int nextMapEntryId = getMapEntryId( decayMaps[i].m_iNextId ); + + if ( nextMapEntryId != -1 ) + { + sprintf( szNextMap, decayMaps[nextMapEntryId].szName ); + break; + } + } + } + } + + return szNextMap; +} + +int CDecayRules :: getDecayMapId() +{ + int mapCount = sizeof(decayMaps)/sizeof(decayMaps[0]); + + int mapId = -1; + + char szMapName[ 128 ]; + strcpy( szMapName, STRING(gpGlobals->mapname) ); + + for ( int i = 0; i < mapCount; i++ ) + { + if ( strcmp( szMapName, decayMaps[i].szName ) == 0 ) + { + mapId = decayMaps[i].m_iId; + break; + } + } + + return mapId; +/* + if ( strcmp( szMapName, "dy_accident1" ) == 0 ) + mapId = 0; +*/ +} + +void CDecayRules :: savePlayerStats( int playerId, int finalGrade, int damageGrade, int killsGrade, int accuracyGrade ) +{ + char buffer[MAX_COMPUTERNAME_LENGTH+1]; + Platform_GetComputerName( buffer, sizeof( buffer )); + + m_iMagicWord1 = 0; + + int cnl = strlen(buffer); + for (int i=1; igetDecayMapId(); + int mapEntryId = getMapEntryId( mapId ); + + pStats[playerId].magicWord1 = m_iMagicWord1; + pStats[playerId].magicWord2 = m_iMagicWord2; + + if ( mapEntryId != -1 ) + { + if ( playerId == 1 ) + { + //if ( !decayMaps[mapEntryId].statsBest1.gradeFinal || decayMaps[mapEntryId].statsBest1.gradeFinal <= finalGrade ) + if ( decayMaps[mapEntryId].statsBest1.gradeFinal >= finalGrade ) + { + decayMaps[mapEntryId].statsBest1 = pStats[playerId]; + decayMaps[mapEntryId].statsLast1 = pStats[playerId]; + } else + decayMaps[mapEntryId].statsLast1 = pStats[playerId]; + } else + if ( playerId == 2 ) + { + //if ( !decayMaps[mapEntryId].statsBest2.gradeFinal || decayMaps[mapEntryId].statsBest2.gradeFinal <= finalGrade ) + if ( decayMaps[mapEntryId].statsBest2.gradeFinal >= finalGrade ) + { + decayMaps[mapEntryId].statsBest2 = pStats[playerId]; + decayMaps[mapEntryId].statsLast2 = pStats[playerId]; + } else + decayMaps[mapEntryId].statsLast2 = pStats[playerId]; + } + + int nextMapId = decayMaps[mapEntryId].m_iNextId; + if ( nextMapId != -1 ) + { + decayMaps[getMapEntryId(nextMapId)].m_bLocked = false; // TODO: what if -1 here? + if ( mapId == 10 ) + if ( canUnlockBonusMission() == true ) + { + int bonusEntryId = getMapEntryId( 12 ); // dy_alien + if ( bonusEntryId != -1 ) + { + decayMaps[bonusEntryId].m_bLocked = false; + UTIL_ShowMessageAll( "CONGRATULATIONS!!!!! :D Alien mission unlocked!!!" ); + } + } + } + } + + statsSave(); +} + +//========================================================= +// PlayerKilled - someone/something killed this player +//========================================================= +void CDecayRules :: PlayerKilled( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ + if (PlayersCount > 2) // player count will be decreased afterwards in ClientDisconnected function + { + ALERT( at_console, "Removed player with id > 2 !!!\n" ); + return; + } + + char enddecayname[32]; + sprintf( enddecayname, "decay_player%d_dead", pVictim->m_iDecayId ); // decay_player2_dead + + //edict_t *pentTarget = NULL; + //pentTarget = FIND_ENTITY_BY_TARGETNAME(pentTarget, enddecayname); + FireTargets( enddecayname, pVictim, NULL, USE_ON, 1); + + // Could not find trigger_enddecay for player death! +} + +//========================================================= +// Deathnotice +//========================================================= +void CDecayRules::DeathNotice( CBasePlayer *pVictim, entvars_t *pKiller, entvars_t *pInflictor ) +{ +} + +//========================================================= +// PlayerGotWeapon - player has grabbed a weapon that was +// sitting in the world +//========================================================= +void CDecayRules :: PlayerGotWeapon( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ +} + +//========================================================= +// CanHavePlayerItem - the player is touching an CBasePlayerItem, +// do I give it to him? +//========================================================= + +BOOL CDecayRules :: CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerItem *pWeapon ) +{ + // only living players can have items + if ( pPlayer->pev->deadflag != DEAD_NO ) + return FALSE; + + if (this->m_bAlienMode == true) + { + return (pWeapon->m_iId == WEAPON_VORTI); + } + + if (pWeapon->DecayPlayerIndex !=0) + { + return (pWeapon->DecayPlayerIndex == pPlayer->m_iDecayId); + } + + return true; +} + +//========================================================= +// FlWeaponRespawnTime - what is the time in the future +// at which this weapon may spawn? +//========================================================= +float CDecayRules :: FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) +{ + return -1; +} + +//========================================================= +// FlWeaponRespawnTime - Returns 0 if the weapon can respawn +// now, otherwise it returns the time at which it can try +// to spawn again. +//========================================================= +float CDecayRules :: FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) +{ + return 0; +} + +//========================================================= +// VecWeaponRespawnSpot - where should this weapon spawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CDecayRules :: VecWeaponRespawnSpot( CBasePlayerItem *pWeapon ) +{ + return pWeapon->pev->origin; +} + +//========================================================= +// WeaponShouldRespawn - any conditions inhibiting the +// respawning of this weapon? +//========================================================= +int CDecayRules :: WeaponShouldRespawn( CBasePlayerItem *pWeapon ) +{ + return GR_WEAPON_RESPAWN_NO; +} + +//========================================================= +//========================================================= +BOOL CDecayRules::CanHaveItem( CBasePlayer *pPlayer, CItem *pItem ) +{ + if ( this->m_bAlienMode ) + return FALSE; + else + return TRUE; +} + +//========================================================= +//========================================================= +void CDecayRules::PlayerGotItem( CBasePlayer *pPlayer, CItem *pItem ) +{ +} + +//========================================================= +//========================================================= +int CDecayRules::ItemShouldRespawn( CItem *pItem ) +{ + return GR_ITEM_RESPAWN_NO; +} + + +//========================================================= +// At what time in the future may this Item respawn? +//========================================================= +float CDecayRules::FlItemRespawnTime( CItem *pItem ) +{ + return -1; +} + +//========================================================= +// Where should this item respawn? +// Some game variations may choose to randomize spawn locations +//========================================================= +Vector CDecayRules::VecItemRespawnSpot( CItem *pItem ) +{ + return pItem->pev->origin; +} + +//========================================================= +//========================================================= +BOOL CDecayRules::IsAllowedToSpawn( CBaseEntity *pEntity ) +{ + return TRUE; +} + +//========================================================= +//========================================================= +void CDecayRules::PlayerGotAmmo( CBasePlayer *pPlayer, char *szName, int iCount ) +{ +} + +//========================================================= +//========================================================= +int CDecayRules::AmmoShouldRespawn( CBasePlayerAmmo *pAmmo ) +{ + return GR_AMMO_RESPAWN_NO; +} + +//========================================================= +//========================================================= +float CDecayRules::FlAmmoRespawnTime( CBasePlayerAmmo *pAmmo ) +{ + return -1; +} + +//========================================================= +//========================================================= +Vector CDecayRules::VecAmmoRespawnSpot( CBasePlayerAmmo *pAmmo ) +{ + return pAmmo->pev->origin; +} + +//========================================================= +//========================================================= +float CDecayRules::FlHealthChargerRechargeTime( void ) +{ + return 0;// don't recharge +} + +//========================================================= +//========================================================= +int CDecayRules::DeadPlayerWeapons( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_GUN_NO; +} + +//========================================================= +//========================================================= +int CDecayRules::DeadPlayerAmmo( CBasePlayer *pPlayer ) +{ + return GR_PLR_DROP_AMMO_NO; +} + +//========================================================= +//========================================================= +int CDecayRules::PlayerRelationship( CBaseEntity *pPlayer, CBaseEntity *pTarget ) +{ + // why would a single player in half life need this? + return GR_TEAMMATE; +} + +//========================================================= +//========================================================= +BOOL CDecayRules :: FAllowMonsters( void ) +{ + return TRUE; +} + +void CDecayRules::ClientUserInfoChanged( CBasePlayer *pPlayer, char *infobuffer ) +{ +// ALERT( at_console, "client info (player %d) changed!\n", pPlayer->m_iDecayId ); +} + +void CopyWeaponsAndAmmo(CBasePlayer *PlayerFrom, CBasePlayer *PlayerTo, bool bDummyDest = false) +{ + int i; + CBasePlayerItem *pWeapon; + CBasePlayerItem *pNextWeapon; + CBasePlayerItem *pActiveWeapon = NULL; + int viewmodel, weaponmodel, sequence = 0; + + // + // AMMO + // + + for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + { + PlayerTo->m_rgAmmo[i] = PlayerFrom->m_rgAmmo[ i ]; + // String below prevents sending to HUD (gmsgAmmoX) new ammo values (applied after weapon change) + //PlayerTo->m_rgAmmoLast[i] = PlayerFrom->m_rgAmmoLast[i]; + PlayerTo->m_rgAmmoLast[i] = 0; + } + if ( !bDummyDest ) + PlayerTo->SendAmmoUpdate(); + + // + // SELECTED (ACTIVE) WEAPON + // + + pActiveWeapon = NULL; + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( PlayerFrom->m_rgpPlayerItems[ i ] ) + { + pWeapon = PlayerFrom->m_rgpPlayerItems[i]; + while ( pWeapon ) //rgpPackWeapons[ iPW ] + { + if ( pWeapon == PlayerFrom->m_pActiveItem ) + { + pActiveWeapon = pWeapon; + break; + } + pWeapon = pWeapon->m_pNext; + } // while + } + } + + sequence = PlayerFrom->pev->sequence; + if (pActiveWeapon) + { + viewmodel = PlayerFrom->pev->viewmodel; + weaponmodel = PlayerFrom->pev->weaponmodel; + } else + { + viewmodel = 0; + weaponmodel = 0; + } + + // + // WEAPONS + // + + for ( i = 0 ; i < MAX_ITEM_TYPES ; i++ ) + { + if ( PlayerFrom->m_rgpPlayerItems[ i ] ) + { + pWeapon = PlayerFrom->m_rgpPlayerItems[i]; //(CBasePlayerWeapon *) + + while ( pWeapon ) //rgpPackWeapons[ iPW ] + { + if ( pWeapon->m_pPlayer ) + { + //if ( pWeapon == PlayerFrom->m_pActiveItem ) + //{ + // PlayerTo->m_pActiveItem = pWeapon; + //} + + pNextWeapon = pWeapon->m_pNext; + + // problem here moving from tmpPlayer to player 2!!! + if ( !pWeapon->m_pPlayer->RemovePlayerItem( pWeapon, false ) ) + { + ALERT( at_console, "problems removing weapon\n" ); + } + + int iWeaponSlot = pWeapon->iItemSlot(); + + if ( PlayerTo->m_rgpPlayerItems[ iWeaponSlot ] ) + { + // there's already one weapon in this slot, so link this into the slot's column + pWeapon->m_pNext = PlayerTo->m_rgpPlayerItems[ iWeaponSlot ]; + PlayerTo->m_rgpPlayerItems[ iWeaponSlot ] = pWeapon; + } + else + { + // first weapon we have for this slot + PlayerTo->m_rgpPlayerItems[ iWeaponSlot ] = pWeapon; + pWeapon->m_pNext = NULL; + } + + if ( bDummyDest == true ) + { + pWeapon->m_pPlayer = PlayerTo; //NULL; + pWeapon->pev->owner = PlayerTo->edict(); + } + else + { + pWeapon->AddToPlayer( PlayerTo ); + pWeapon->AttachToPlayer( PlayerTo ); + } + } + + pWeapon = pNextWeapon; + } // while + } + } + + if ( pActiveWeapon != NULL ) + { + PlayerTo->m_pActiveItem = pActiveWeapon; + PlayerTo->TabulateAmmo(); + if ( !bDummyDest ) + { + PlayerTo->m_pActiveItem->UpdateClientData( PlayerTo ); // should send gmsgCurWeapon, then should call SendAmmoUpdate which sends gmsgAmmox + PlayerTo->SwitchWeapon(pActiveWeapon); // calls m_pActiveWeapon->Deploy() + //PlayerTo->SendAmmoUpdate(); + PlayerTo->m_pActiveItem->UpdateItemInfo(); // updates HUD state + } + } else + PlayerTo->m_pActiveItem = NULL; + + //PlayerTo->m_pActiveItem = pActiveWeapon; + + PlayerTo->pev->viewmodel = viewmodel; + PlayerTo->pev->weaponmodel = weaponmodel; + PlayerTo->pev->weapons = PlayerFrom->pev->weapons; + PlayerTo->pev->sequence = sequence; + + if ( !bDummyDest ) + PlayerTo->UpdateClientData(); +} + +void CDecayRules :: ChangePlayer( void ) +{ + if (PlayersCount < 2) // was != + { + ALERT( at_console, "There is no second player!\n" ); + return; + } + + // CREATE TEMP PLAYER TO STORE SETTINGS TO COPY + edict_t *pent; + pent = CREATE_NAMED_ENTITY( MAKE_STRING( "player" ) ); + + if ( FNullEnt( pent ) ) + { + ALERT ( at_console, "Can't create tmpPlayer for ChangePlayer function!\n" ); + return; + } + CBasePlayer *tmpPlayer = (CBasePlayer*)CBasePlayer::Instance( VARS( pent )); + //ClearBits(tmpPlayer->pev->flags, FL_FAKECLIENT); + tmpPlayer->pev->flags |= FL_IMMUNE_WATER; // Hoaxer: HACK-HACK-HACK + + // ************** INFOBUFFERS ********************** + + char *infobuffer1; + char *infobuffer2; + char *pName; + char sName1[256]; + char sName2[256]; + + infobuffer1 = GET_INFOBUFFER( pPlayers[0]->edict( ) ); + pName = g_engfuncs.pfnInfoKeyValue( infobuffer1, "model" ); + strncpy( sName1, pName, sizeof(sName1) - 1 ); + sName1[ sizeof(sName1) - 1 ] = '\0'; + + infobuffer2 = GET_INFOBUFFER( pPlayers[1]->edict( ) ); + pName = g_engfuncs.pfnInfoKeyValue( infobuffer2, "model" ); + strncpy( sName2, pName, sizeof(sName2) - 1 ); + sName2[ sizeof(sName2) - 1 ] = '\0'; + + //sNameX contains model name without .mdl extensions + + // Vyacheslav Dzhura TODO: set "name" property as Gina and Colette manually here + //char sName3[256] = "Gina"; + //sName3[ sizeof(sName3) - 1 ] = '\0'; + //char sName4[256] = "Colette"; + //sName4[ sizeof(sName4) - 1 ] = '\0'; + + //SET_CLIENT_KEY_VALUE( pPlayers[0]->entindex(), infobuffer1, "name", sName3 ); + //SET_CLIENT_KEY_VALUE( pPlayers[1]->entindex(), infobuffer2, "name", sName4 ); + + SET_CLIENT_KEY_VALUE( pPlayers[0]->entindex(), infobuffer1, "model", sName2 ); + SET_CLIENT_KEY_VALUE( pPlayers[1]->entindex(), infobuffer2, "model", sName1 ); + // ************************************************* + + bool bP1Ducked = FBitSet(pPlayers[0]->pev->flags, FL_DUCKING); + bool bP2Ducked = FBitSet(pPlayers[1]->pev->flags, FL_DUCKING); + + // ************** FLASHLIGHT ********************** + bool tmpPlayer1UseFlashligh = pPlayers[0]->FlashlightIsOn(); // to avoid NOT CLIENT bug + bool tmpPlayer2UseFlashligh = pPlayers[1]->FlashlightIsOn(); // to avoid NOT CLIENT bug + + if (tmpPlayer2UseFlashligh) + pPlayers[0]->FlashlightTurnOn(); + else + pPlayers[0]->FlashlightTurnOff(); + + if (tmpPlayer1UseFlashligh) + pPlayers[1]->FlashlightTurnOn(); + else + pPlayers[1]->FlashlightTurnOff(); + + tmpPlayer->m_flFlashLightTime = pPlayers[0]->m_flFlashLightTime; + tmpPlayer->m_iFlashBattery = pPlayers[0]->m_iFlashBattery; + + pPlayers[0]->m_flFlashLightTime = pPlayers[1]->m_flFlashLightTime; + pPlayers[0]->m_iFlashBattery = pPlayers[1]->m_iFlashBattery; + + pPlayers[1]->m_flFlashLightTime = tmpPlayer->m_flFlashLightTime; + pPlayers[1]->m_iFlashBattery = tmpPlayer->m_iFlashBattery; + // ************************************************* + + //for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + // ALERT( at_console, "BE4 p1 ammo %d is %d | p2 ammo %d is %d \n", i, pPlayers[0]->m_rgAmmo[ i ], i, pPlayers[1]->m_rgAmmo[ i ]); + + // + // weapons switching stuff + // + + // store items from PLAYER 1 to TEMP PLAYER + //ALERT( at_console, "(CopyWeaponsAndAmmo) from pPlayers[0] to tmpPlayer\n" ); + CopyWeaponsAndAmmo( pPlayers[0], tmpPlayer, true ); + pPlayers[0]->RemoveAllItems( FALSE ); + + // now from PLAYER 2 to PLAYER 1 + //ALERT( at_console, "(CopyWeaponsAndAmmo) from pPlayers[1] to pPlayers[0]\n" ); + CopyWeaponsAndAmmo( pPlayers[1], pPlayers[0] ); + pPlayers[1]->RemoveAllItems( FALSE ); + + // and finally from TEMP PLAYER to PLAYER 2 + //ALERT( at_console, "(CopyWeaponsAndAmmo) from tmpPlayer to pPlayers[1]\n" ); + CopyWeaponsAndAmmo( tmpPlayer, pPlayers[1] ); + + //ALERT( at_console, "DONE WITH WEAPOSN COPYING!!!\n" ); + + //for ( i = 0 ; i < MAX_AMMO_SLOTS ; i++ ) + // ALERT( at_console, "AFTER p1 ammo %d is %d | p2 ammo %d is %d \n", i, pPlayers[0]->m_rgAmmo[ i ], i, pPlayers[1]->m_rgAmmo[ i ]); + + // ******* BASIC PARAMS FROM PLAYER 1 TO TEMP PLAYER ******* + tmpPlayer->pev->origin = pPlayers[0]->pev->origin; + tmpPlayer->pev->v_angle = pPlayers[0]->pev->v_angle; + tmpPlayer->pev->angles = pPlayers[0]->pev->angles; + + tmpPlayer->m_iDecayId = pPlayers[0]->m_iDecayId; + tmpPlayer->pev->skin = pPlayers[0]->pev->skin; + tmpPlayer->pev->body = pPlayers[0]->pev->body; + tmpPlayer->pev->view_ofs = pPlayers[0]->pev->view_ofs; + tmpPlayer->pev->health = pPlayers[0]->pev->health; + tmpPlayer->pev->armorvalue = pPlayers[0]->pev->armorvalue; + + // ******* BASIC PARAMS FROM PLAYER 1 TO PLAYER 2 ******* + pPlayers[0]->pev->origin = pPlayers[1]->pev->origin; + pPlayers[0]->pev->v_angle = pPlayers[1]->pev->v_angle; + pPlayers[0]->pev->angles = pPlayers[1]->pev->angles; + pPlayers[0]->pev->fixangle = TRUE; + + pPlayers[0]->pev->view_ofs = pPlayers[1]->pev->view_ofs; + pPlayers[0]->m_iDecayId = pPlayers[1]->m_iDecayId; + pPlayers[0]->pev->skin = pPlayers[1]->pev->skin; + pPlayers[0]->pev->body= pPlayers[1]->pev->body; + pPlayers[0]->pev->health = pPlayers[1]->pev->health; + pPlayers[0]->pev->armorvalue = pPlayers[1]->pev->armorvalue; + + //pPlayers[0]->m_hEnemy = NULL; + //SetBits(pPlayers[0]->pev->flags, FL_FAKECLIENT); + + // ******* BASIC PARAMS FROM PLAYER 2 TO TEMP PLAYER ******* + pPlayers[1]->pev->origin = tmpPlayer->pev->origin; + pPlayers[1]->pev->v_angle = tmpPlayer->pev->v_angle; + pPlayers[1]->pev->angles = tmpPlayer->pev->angles; + pPlayers[1]->pev->fixangle = TRUE; + + pPlayers[1]->pev->view_ofs = tmpPlayer->pev->view_ofs; + pPlayers[1]->m_iDecayId = tmpPlayer->m_iDecayId; + pPlayers[1]->pev->skin = tmpPlayer->pev->skin; + pPlayers[1]->pev->body = tmpPlayer->pev->body; + pPlayers[1]->pev->health = tmpPlayer->pev->health; + pPlayers[1]->pev->armorvalue = tmpPlayer->pev->armorvalue; + + // AI + pPlayers[0]->m_hEnemy = NULL; + pPlayers[1]->m_hEnemy = NULL; + + //ClearBits(pPlayers[1]->pev->flags, FL_FAKECLIENT); + + // ******* FINAL PREPARATIONS ******* + // remove temp player + //ALERT( at_console, "REMOVING TMPPLAYER!!!\n" ); + UTIL_Remove( tmpPlayer ); + //ALERT( at_console, "TMPPLAYER SUCCESFULLY REMOVED!!!\n" ); + + // ******* DUCK FIX ************ + //if ((pev->button & IN_DUCK) || FBitSet(pev->flags,FL_DUCKING) || (m_afPhysicsFlags & PFLAG_DUCKING) ) + // Duck(); + + if (bP1Ducked == true) //= FBitSet(pPlayers[0]->pev->flags, FL_DUCKING); + { + SetBits(pPlayers[1]->pev->flags, FL_DUCKING); + pPlayers[1]->Duck(); + } + if (bP2Ducked == true) + { + SetBits(pPlayers[0]->pev->flags, FL_DUCKING); + pPlayers[0]->Duck(); + } + + // ******** END OF DUCK FIX **** + + // ****** HEADS CHANGE ********* + for ( int i = 0; i < 2; i++ ) + { + pPlayers[i]->SetBodygroup( 0, 0 ); + if ( pPlayers[i]->m_iDecayId == 1 ) + { + pPlayers[i]->SetBodygroup( 1, 1 ); + pPlayers[i]->pev->skin = 1; + } else + if ( pPlayers[i]->m_iDecayId == 2 ) + { + pPlayers[i]->SetBodygroup( 1, 0 ); + pPlayers[i]->pev->skin = 0; + } + } + + // ****** END OF HEADS CHANGE ****** + + if ( gmsgChangePlayer != 0) + { + //MESSAGE_BEGIN( MSG_ALL, gmsgChangePlayer); + MESSAGE_BEGIN( MSG_ONE, gmsgChangePlayer, NULL, pPlayers[0]->edict() ); + WRITE_BYTE( pPlayers[0]->m_iDecayId ); + MESSAGE_END(); + MESSAGE_BEGIN( MSG_ONE, gmsgChangePlayer, NULL, pPlayers[1]->edict() ); + WRITE_BYTE( pPlayers[1]->m_iDecayId ); + MESSAGE_END(); + } else + ALERT( at_console, "Message gmsgChangePlayer not found in client.dll! Invalid CLIENT.DLL!!!\n" ); + + // update client data... again :) + //pPlayers[0]->UpdateClientData(); + //pPlayers[1]->UpdateClientData(); + + //ALERT( at_console, "Checking if second player is bot...\n" ); + if (!pPlayers[1]->IsNetClient()) // if second player is bot + { + CBot *pBot; + pBot = (CBot*)pPlayers[1]; + pBot->pBotEnemy = NULL; + pBot->BotWeaponInventory(); + } + // BotWeaponInventory +} diff --git a/dlls/displacer.cpp b/dlls/displacer.cpp index ca8e338e..00eed490 100644 --- a/dlls/displacer.cpp +++ b/dlls/displacer.cpp @@ -1,578 +1,578 @@ -#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 "shake.h" - -/*void GetNearestEarthTarget( void ) -{ - CBaseEntity *pEarth = NULL; - CBaseEntity *pNearest = NULL; - float dist, closest; - - closest = 1024; - - while ((pEarth = UTIL_FindEntityInSphere( pEarth, pev->origin, 1024 )) != NULL) - { - if ( FClassnameIs( pEarth->pev, "info_displacer_earth_target" ) ) - { - dist = (pev->origin - pEarth->pev->origin).Length(); - if ( dist < closest ) - { - closest = dist; - pNearest = pEarth; - } - } - } - - if ( !pNearest ) - { - ALERT( at_console, "Can't find a nearby info_displacer_earth_target !!!\n" ); - return; - } else - pEarthTarget = pNearest; -}*/ - -enum displacer_e { - DISPLACER_IDLE1 = 0, - DISPLACER_IDLE2, - DISPLACER_SPINUP, - DISPLACER_SPIN, - DISPLACER_FIRE, - DISPLACER_DRAW, - DISPLACER_HOLSTER1 -}; - -LINK_ENTITY_TO_CLASS( weapon_displacer, CDisplacer ); - -LINK_ENTITY_TO_CLASS( displacer_teleporter, CTeleBall); - - -TYPEDESCRIPTION CTeleBall::m_SaveData[] = -{ - DEFINE_FIELD( CTeleBall, m_vecIdeal, FIELD_VECTOR ), - DEFINE_FIELD( CTeleBall, m_hTouch, FIELD_EHANDLE ), -}; - - -IMPLEMENT_SAVERESTORE( CTeleBall, CBaseMonster ); - -void CTeleBall::Spawn( void ) -{ - Precache( ); - - pev->movetype = MOVETYPE_FLY; - pev->solid = SOLID_BBOX; - - SET_MODEL(edict(), "sprites/exit1.spr"); - - pev->rendercolor.x = 255; - pev->rendercolor.y = 255; - pev->rendercolor.z = 255; - pev->scale = 1.0; - - pev->rendermode = kRenderTransAdd; - pev->renderamt = 255; - - UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); - UTIL_SetOrigin( pev, pev->origin ); - - SetThink( &CTeleBall::TeleportThink ); - SetTouch( &CTeleBall::TeleportTouch ); - EMIT_SOUND(ENT(pev), CHAN_ITEM, "weapons/displacer_fire.wav", 1, ATTN_NORM); - - int i; - for (i=1;i<5;i++) - { - pBeam[i] = CBeam::BeamCreate("sprites/lgtning.spr",200); - pBeam[i]->pev->origin = pev->origin; - - pBeam[i]->SetColor( 211,255,81 ); - pBeam[i]->SetNoise( 70 ); - pBeam[i]->SetBrightness( 120 );//was 150 - pBeam[i]->SetWidth( 45 ); - pBeam[i]->SetScrollRate( 35 ); - pBeam[i]->SetThink( &CBeam::SUB_Remove ); - pBeam[i]->pev->nextthink = 0; //was gpGlobals->time + 1 - } - - pev->nextthink = 0.1; -} - -void CTeleBall:: Precache( void ) -{ - ring_sprite = PRECACHE_MODEL("sprites/disp_ring.spr"); - PRECACHE_MODEL("sprites/exit1.spr"); - PRECACHE_MODEL( "sprites/lgtning.spr" ); - - PRECACHE_SOUND("weapons/displacer_teleport.wav"); - PRECACHE_SOUND("weapons/displacer_teleport_player.wav"); - PRECACHE_SOUND("weapons/displacer_fire.wav"); -} - -void CTeleBall:: PlayEffect( Vector Origin, CBaseEntity *pEntity ) -{ - CBeam *pBeam[15]; - TraceResult tr; - Vector vecDest; - - EMIT_SOUND( edict(), CHAN_BODY, "debris/beamstart7.wav", 1, ATTN_NORM ); - UTIL_ScreenFade( pEntity, Vector(0,255,0), 0.5, 0.25, 255, FFADE_IN ); - - int i; - for (i=1;i<15;i++) - { - vecDest = 500 * (Vector(RANDOM_FLOAT(-1000,1000), RANDOM_FLOAT(-1000,1000), RANDOM_FLOAT(-1000,1000)).Normalize()); - UTIL_TraceLine( Origin, Origin + vecDest, ignore_monsters, NULL, &tr); - if (tr.flFraction != 1.0) - { - // we hit something. - pBeam[i] = CBeam::BeamCreate("sprites/lgtning.spr",200); - pBeam[i]->pev->origin = Origin; - pBeam[i]->PointsInit( Origin, tr.vecEndPos ); - pBeam[i]->SetColor( 0, 255, 0 ); //Blue-Shift style - pBeam[i]->SetNoise( 65 ); - pBeam[i]->SetBrightness( 150 ); - pBeam[i]->SetWidth( 18 ); - pBeam[i]->SetScrollRate( 35 ); - pBeam[i]->SetThink( &CBeam::SUB_Remove ); - pBeam[i]->pev->nextthink = gpGlobals->time + 1; //was 0.1 - } - } - EMIT_SOUND( edict(), CHAN_BODY, "debris/beamstart2.wav", 1, ATTN_NORM ); - - CSprite *pSpr = CSprite::SpriteCreate( "sprites/Fexplo1.spr", Origin, TRUE ); - pSpr->AnimateAndDie( 10 ); - pSpr->SetTransparency(kRenderGlow, 77, 210, 130, 255, kRenderFxNoDissipation); - - pSpr = CSprite::SpriteCreate( "sprites/XFlare1.spr", Origin, TRUE ); - pSpr->AnimateAndDie( 10 ); - pSpr->SetTransparency(kRenderGlow, 184, 250, 214, 255, kRenderFxNoDissipation); -} - -void CTeleBall:: TeleportThink( void ) -{ - TraceResult tr; - Vector vecDest; - - int i; - for (i=1;i<5;i++) - { - vecDest = 500 * (Vector(RANDOM_FLOAT(-1000,1000), RANDOM_FLOAT(-1000,1000), RANDOM_FLOAT(-1000,1000)).Normalize()); - UTIL_TraceLine( pev->origin, pev->origin + vecDest, ignore_monsters, NULL, &tr); - if (tr.flFraction != 1.0) - { - // we hit something. - pBeam[i]->pev->origin = pev->origin; - pBeam[i]->PointsInit( pev->origin, tr.vecEndPos ); - } - } - - pev->nextthink = gpGlobals->time + 0.1; - pev->frame = (int)(pev->frame + 1) % 20; //comment this to disable sprite animation -} - -void CTeleBall::TeleportTouch( CBaseEntity *pOther ) -{ - if (pOther->Classify() != CLASS_PLAYER) - { - EMIT_SOUND(ENT(pev), CHAN_ITEM, "weapons/displacer_teleport.wav", 1, ATTN_NORM); - } - else EMIT_SOUND(ENT(pev), CHAN_ITEM, "weapons/displacer_teleport_player.wav", 1, ATTN_NORM); - - //if we shoot player - teleport him before energy ball killed him - if (pOther->IsPlayer()) //if we hit player - { - #ifdef CLIENT_DLL - if (bIsMultiplayer()) - #else - if (g_pGameRules->IsMultiplayer() ) //and if we are in MP game - #endif - { - CBaseEntity *pSpot = NULL; - CBaseEntity *pEntity = NULL; - int count = 0; - while ((pEntity = UTIL_FindEntityByClassname(pEntity, "info_player_deathmatch")) != NULL) - { - count++; - } - if (count>0) - { - for ( int i = RANDOM_LONG(1,count); i > 0; i-- ) //Randomize teleport spot - pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); - Vector tmp = pSpot->pev->origin; - //tmp.z -= pOther->pev->mins.z; - //tmp.z++; - UTIL_SetOrigin( pOther->pev, tmp ); //teleport player - PlayEffect( tmp, pOther); - } - } - } - SetTouch( NULL ); - pev->velocity = Vector( 0, 0, 0 ); //stop energy ball, otherwise it will "slide" - - SetThink(&CTeleBall::TeleportKill); - pev->nextthink = gpGlobals->time+0.2; //allows energy ball to stay alive after it touched something -} - -void CTeleBall::TeleportKill( void ) -{ - int flAdjustedDamage; - float flDist; - - MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); - WRITE_BYTE( TE_BEAMCYLINDER ); - WRITE_COORD( pev->origin.x );// coord coord coord (center position) - WRITE_COORD( pev->origin.y ); - WRITE_COORD( pev->origin.z ); - WRITE_COORD( pev->origin.x );// coord coord coord (axis and radius) - WRITE_COORD( pev->origin.y ); - WRITE_COORD( pev->origin.z + 16 + (300 / 2)/ .2); - WRITE_SHORT( ring_sprite ); // short (sprite index) - WRITE_BYTE( 0 ); // byte (starting frame) - WRITE_BYTE( 0 ); // byte (frame rate in 0.1's) - WRITE_BYTE( 4 ); // byte (life in 0.1's) - WRITE_BYTE( 16 ); // byte (line width in 0.1's) - WRITE_BYTE( 0 ); // byte (noise amplitude in 0.01's) - WRITE_BYTE( 188 );// byte,byte,byte (color) - WRITE_BYTE( 220 ); - WRITE_BYTE( 255 ); - WRITE_BYTE( pev->renderamt );// byte (brightness) - WRITE_BYTE( 0 ); // byte (scroll speed in 0.1's) - MESSAGE_END(); - - MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); - WRITE_BYTE(TE_DLIGHT); - WRITE_COORD(pev->origin.x); // X - WRITE_COORD(pev->origin.y); // Y - WRITE_COORD(pev->origin.z); // Z - WRITE_BYTE( 10 ); // radius * 0.1 - WRITE_BYTE( 216 ); // r - WRITE_BYTE( 239 ); // g - WRITE_BYTE( 80 ); // b - WRITE_BYTE( 5 ); // time * 10 - WRITE_BYTE( 0 ); // decay * 0.1 - MESSAGE_END( ); - //there was SetTouch( NULL ); - CBaseEntity *pEntity = NULL; - // iterate on all entities in the vicinity. - while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, 300 )) != NULL) - { - if ( pEntity->pev->takedamage != DAMAGE_NO ) - { - // houndeyes do FULL damage if the ent in question is visible. Half damage otherwise. - // This means that you must get out of the houndeye's attack range entirely to avoid damage. - // Calculate full damage first - flAdjustedDamage = 250; - - flDist = (pEntity->Center() - pev->origin).Length(); - - flAdjustedDamage -= ( flDist / 300 ) * flAdjustedDamage; - - if ( !FVisible( pEntity ) ) - { - 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; - } - } - if (flAdjustedDamage > 0 ) - { - //pev, pev - entvars_t *pevOwner; - pevOwner = VARS( pev->owner ); - - pEntity->TakeDamage ( pev, pevOwner, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB ); - } - } - } - - for (int i=1;i<5;i++) UTIL_Remove( pBeam[i] ); //remove beams - SetThink ( &CTeleBall::SUB_Remove ); - pev->nextthink = 0.2; -} - -void CDisplacer::Spawn( ) -{ - Precache( ); - m_iId = WEAPON_DISPLACER; - SET_MODEL(ENT(pev), "models/w_displacer.mdl"); - - m_iDefaultAmmo = DISPLACER_DEFAULT_GIVE; - - FallInit();// get ready to fall down. -} - -void CDisplacer::Precache( void ) -{ - PRECACHE_MODEL("models/w_displacer.mdl"); - PRECACHE_MODEL("models/v_displacer.mdl"); - PRECACHE_MODEL("models/p_displacer.mdl"); - - PRECACHE_SOUND("weapons/displacer_fire.wav"); - PRECACHE_SOUND("weapons/displacer_impact.wav"); - PRECACHE_SOUND("weapons/displacer_self.wav"); - PRECACHE_SOUND("weapons/displacer_spin.wav"); - PRECACHE_SOUND("weapons/displacer_spin2.wav"); - PRECACHE_SOUND("weapons/displacer_start.wav"); - PRECACHE_SOUND("weapons/displacer_teleport.wav"); - PRECACHE_SOUND("weapons/displacer_teleport_player.wav"); - - PRECACHE_MODEL("sprites/Fexplo1.spr"); - PRECACHE_MODEL("sprites/XFlare1.spr"); - PRECACHE_SOUND ("debris/beamstart7.wav"); - UTIL_PrecacheOther( "displacer_teleporter" ); -} - -int CDisplacer::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 CDisplacer::Holster( int skiplocal) -{ - SendWeaponAnim( DISPLACER_HOLSTER1 ); -} - -BOOL CDisplacer::Deploy( ) -{ - return DefaultDeploy( "models/v_displacer.mdl", "models/p_displacer.mdl", DISPLACER_DRAW, "displacer" ); -} - -int CDisplacer::GetItemInfo(ItemInfo *p) -{ - p->pszName = STRING(pev->classname); - p->pszAmmo1 = "uranium"; - p->iMaxAmmo1 = URANIUM_MAX_CARRY; - p->pszAmmo2 = NULL; - p->iMaxAmmo2 = -1; - p->iMaxClip = DISPLACER_MAX_CLIP; - p->iSlot = 4; // slot 5, position 5 - p->iPosition = 4; - p->iId = m_iId = WEAPON_DISPLACER; - p->iFlags = 0; - p->iWeight = DISPLACER_WEIGHT; - - return 1; -} - -BOOL CDisplacer::PlayEmptySound( void ) -{ - //button11.wav - if (m_iPlayEmptySound) - { - EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); - m_iPlayEmptySound = 0; - return 0; - } - return 0; -} - -void CDisplacer::SpinupThink( void ) -{ - SendWeaponAnim( DISPLACER_SPINUP ); - EMIT_SOUND(ENT(pev), CHAN_ITEM, "weapons/displacer_spin.wav", 1, ATTN_NORM); - - // don't fire underwater - if ( m_pPlayer->pev->waterlevel == 3 ) - { - ResetEmptySound( ); - //m_flNextSecondaryAttack = m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; - return; - } - - SetThink(&CDisplacer::FireThink); - pev->nextthink=gpGlobals->time + 1.0; - //m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 3.0; -} - -void CDisplacer::FireThink( void ) -{ - if ( m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] < 20 ) - { - PlayEmptySound( ); - SendWeaponAnim( DISPLACER_IDLE1 ); - //m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; - return; - } - - SendWeaponAnim( DISPLACER_FIRE ); - UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); - Vector vecSrc = m_pPlayer->GetGunPosition( ); - - CTeleBall *pEntity = (CTeleBall *)Create( "displacer_teleporter", vecSrc, m_pPlayer->pev->angles, m_pPlayer->edict() ); - pEntity->pev->owner = m_pPlayer->edict(); - pEntity->pev->velocity = gpGlobals->v_forward * 500; - - m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= 20; - - m_fInAttack = 0; - m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; -} - -void CDisplacer::PrimaryAttack( void ) -{ - SetThink(&CDisplacer::SpinupThink); - pev->nextthink=gpGlobals->time + 0.1; - m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.5; -} - -void CDisplacer::SecondaryAttack( void ) -{ - if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] >= 60) - { - //if is multiplayer game - #ifdef CLIENT_DLL - if (bIsMultiplayer()) - #else - if (g_pGameRules->IsMultiplayer() ) - #endif - { - CBaseEntity *pSpot = NULL; - CBaseEntity *pEntity = NULL; - int count = 0; - while ((pEntity = UTIL_FindEntityByClassname(pEntity, "info_player_deathmatch")) != NULL) - { - count++; - } - if (count>0) - { - for ( int i = RANDOM_LONG(1,count); i > 0; i-- ) //Randomize teleport spot - pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); - - //spawn CTeleBall here - CTeleBall *pEntity = (CTeleBall *)Create( "displacer_teleporter", pev->origin, m_pPlayer->pev->angles, m_pPlayer->edict() ); - pEntity->pev->owner = m_pPlayer->edict(); - pEntity->pev->velocity = -gpGlobals->v_up * 150; - // - UTIL_SetOrigin( m_pPlayer->pev, pSpot->pev->origin ); - m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= 60; - - PlayEffect( pSpot->pev->origin ); - } - } - else - //if is singleplayer game - { - CDisplacerTarget *pSpot = NULL; - - while ( (pSpot = (CDisplacerTarget*)UTIL_FindEntityByClassname( pSpot, "info_displacer_xen_target" )) && (!FNullEnt(pSpot->edict())) ) - { - if ( pSpot->m_iPlayerIndex == m_pPlayer->m_iDecayId ) - break; - } - - if ( (pSpot != NULL) && (pSpot->m_iPlayerIndex == m_pPlayer->m_iDecayId) ) - { - Vector tmp = pSpot->pev->origin; - tmp.z -= m_pPlayer->pev->mins.z; - tmp.z++; - - //spawn CTeleBall here - CTeleBall *pEntity = (CTeleBall *)Create( "displacer_teleporter", pev->origin, m_pPlayer->pev->angles, m_pPlayer->edict() ); - pEntity->pev->owner = m_pPlayer->edict(); - pEntity->pev->velocity = -gpGlobals->v_up * 150; - // - UTIL_SetOrigin( m_pPlayer->pev, tmp); - m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= 60; - PlayEffect( tmp ); - - if ( !FStringNull( pSpot->pev->target ) ) - FireTargets( STRING(pSpot->pev->target), this, this, USE_TOGGLE, 0.0 ); - - } else - PlayEmptySound( ); - } - m_flNextSecondaryAttack = gpGlobals->time + 4.0; - } - else - { - PlayEmptySound( ); - m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; - return; - } -} - -void CDisplacer::PlayEffect( Vector Origin ) -{ - CBeam *pBeam[15]; - TraceResult tr; - Vector vecDest; - - EMIT_SOUND( edict(), CHAN_BODY, "debris/beamstart7.wav", 1, ATTN_NORM ); - UTIL_ScreenFade( m_pPlayer, Vector(0,255,0), 0.5, 0.25, 255, FFADE_IN ); - - int i; - for (i=1;i<15;i++) - { - vecDest = 500 * (Vector(RANDOM_FLOAT(-1000,1000), RANDOM_FLOAT(-1000,1000), RANDOM_FLOAT(-1000,1000)).Normalize()); - UTIL_TraceLine( Origin, Origin + vecDest, ignore_monsters, NULL, &tr); - if (tr.flFraction != 1.0) - { - // we hit something. - pBeam[i] = CBeam::BeamCreate("sprites/lgtning.spr",200); - pBeam[i]->pev->origin = Origin; - pBeam[i]->PointsInit( Origin, tr.vecEndPos ); - pBeam[i]->SetColor( 0, 255, 0 ); //Blue-Shift style - //197 243 169 //c1a1b - pBeam[i]->SetNoise( 65 ); - pBeam[i]->SetBrightness( 150 ); - pBeam[i]->SetWidth( 18 ); - pBeam[i]->SetScrollRate( 35 ); - pBeam[i]->SetThink( &CBeam::SUB_Remove ); - pBeam[i]->pev->nextthink = gpGlobals->time + 1; //was 0.1 - } - } - EMIT_SOUND( edict(), CHAN_BODY, "debris/beamstart2.wav", 1, ATTN_NORM ); - - CSprite *pSpr = CSprite::SpriteCreate( "sprites/Fexplo1.spr", Origin, TRUE ); - pSpr->AnimateAndDie( 10 ); - pSpr->SetTransparency(kRenderGlow, 77, 210, 130, 255, kRenderFxNoDissipation); - - pSpr = CSprite::SpriteCreate( "sprites/XFlare1.spr", Origin, TRUE ); - pSpr->AnimateAndDie( 10 ); - pSpr->SetTransparency(kRenderGlow, 184, 250, 214, 255, kRenderFxNoDissipation); -// pev->nextthink = 0; //was 0 -} - -void CDisplacer::Reload( void ) -{ - -} - -void CDisplacer::WeaponIdle( void ) -{ - ResetEmptySound( ); - - if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) - return; - - int iAnim; - float flRand = RANDOM_FLOAT(0, 1); - if (flRand <= 0.5) - { - iAnim = DISPLACER_IDLE1; - m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 2;//UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); - } - else - { - iAnim = DISPLACER_IDLE2; - m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; - } - - SendWeaponAnim( iAnim ); -} +#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 "shake.h" + +/*void GetNearestEarthTarget( void ) +{ + CBaseEntity *pEarth = NULL; + CBaseEntity *pNearest = NULL; + float dist, closest; + + closest = 1024; + + while ((pEarth = UTIL_FindEntityInSphere( pEarth, pev->origin, 1024 )) != NULL) + { + if ( FClassnameIs( pEarth->pev, "info_displacer_earth_target" ) ) + { + dist = (pev->origin - pEarth->pev->origin).Length(); + if ( dist < closest ) + { + closest = dist; + pNearest = pEarth; + } + } + } + + if ( !pNearest ) + { + ALERT( at_console, "Can't find a nearby info_displacer_earth_target !!!\n" ); + return; + } else + pEarthTarget = pNearest; +}*/ + +enum displacer_e { + DISPLACER_IDLE1 = 0, + DISPLACER_IDLE2, + DISPLACER_SPINUP, + DISPLACER_SPIN, + DISPLACER_FIRE, + DISPLACER_DRAW, + DISPLACER_HOLSTER1 +}; + +LINK_ENTITY_TO_CLASS( weapon_displacer, CDisplacer ); + +LINK_ENTITY_TO_CLASS( displacer_teleporter, CTeleBall); + + +TYPEDESCRIPTION CTeleBall::m_SaveData[] = +{ + DEFINE_FIELD( CTeleBall, m_vecIdeal, FIELD_VECTOR ), + DEFINE_FIELD( CTeleBall, m_hTouch, FIELD_EHANDLE ), +}; + + +IMPLEMENT_SAVERESTORE( CTeleBall, CBaseMonster ); + +void CTeleBall::Spawn( void ) +{ + Precache( ); + + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(edict(), "sprites/exit1.spr"); + + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->scale = 1.0; + + pev->rendermode = kRenderTransAdd; + pev->renderamt = 255; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( &CTeleBall::TeleportThink ); + SetTouch( &CTeleBall::TeleportTouch ); + EMIT_SOUND(ENT(pev), CHAN_ITEM, "weapons/displacer_fire.wav", 1, ATTN_NORM); + + int i; + for (i=1;i<5;i++) + { + pBeam[i] = CBeam::BeamCreate("sprites/lgtning.spr",200); + pBeam[i]->pev->origin = pev->origin; + + pBeam[i]->SetColor( 211,255,81 ); + pBeam[i]->SetNoise( 70 ); + pBeam[i]->SetBrightness( 120 );//was 150 + pBeam[i]->SetWidth( 45 ); + pBeam[i]->SetScrollRate( 35 ); + pBeam[i]->SetThink( &CBeam::SUB_Remove ); + pBeam[i]->pev->nextthink = 0; //was gpGlobals->time + 1 + } + + pev->nextthink = 0.1; +} + +void CTeleBall:: Precache( void ) +{ + ring_sprite = PRECACHE_MODEL("sprites/disp_ring.spr"); + PRECACHE_MODEL("sprites/exit1.spr"); + PRECACHE_MODEL( "sprites/lgtning.spr" ); + + PRECACHE_SOUND("weapons/displacer_teleport.wav"); + PRECACHE_SOUND("weapons/displacer_teleport_player.wav"); + PRECACHE_SOUND("weapons/displacer_fire.wav"); +} + +void CTeleBall:: PlayEffect( Vector Origin, CBaseEntity *pEntity ) +{ + CBeam *pBeam[15]; + TraceResult tr; + Vector vecDest; + + EMIT_SOUND( edict(), CHAN_BODY, "debris/beamstart7.wav", 1, ATTN_NORM ); + UTIL_ScreenFade( pEntity, Vector(0,255,0), 0.5, 0.25, 255, FFADE_IN ); + + int i; + for (i=1;i<15;i++) + { + vecDest = 500 * (Vector(RANDOM_FLOAT(-1000,1000), RANDOM_FLOAT(-1000,1000), RANDOM_FLOAT(-1000,1000)).Normalize()); + UTIL_TraceLine( Origin, Origin + vecDest, ignore_monsters, NULL, &tr); + if (tr.flFraction != 1.0) + { + // we hit something. + pBeam[i] = CBeam::BeamCreate("sprites/lgtning.spr",200); + pBeam[i]->pev->origin = Origin; + pBeam[i]->PointsInit( Origin, tr.vecEndPos ); + pBeam[i]->SetColor( 0, 255, 0 ); //Blue-Shift style + pBeam[i]->SetNoise( 65 ); + pBeam[i]->SetBrightness( 150 ); + pBeam[i]->SetWidth( 18 ); + pBeam[i]->SetScrollRate( 35 ); + pBeam[i]->SetThink( &CBeam::SUB_Remove ); + pBeam[i]->pev->nextthink = gpGlobals->time + 1; //was 0.1 + } + } + EMIT_SOUND( edict(), CHAN_BODY, "debris/beamstart2.wav", 1, ATTN_NORM ); + + CSprite *pSpr = CSprite::SpriteCreate( "sprites/Fexplo1.spr", Origin, TRUE ); + pSpr->AnimateAndDie( 10 ); + pSpr->SetTransparency(kRenderGlow, 77, 210, 130, 255, kRenderFxNoDissipation); + + pSpr = CSprite::SpriteCreate( "sprites/XFlare1.spr", Origin, TRUE ); + pSpr->AnimateAndDie( 10 ); + pSpr->SetTransparency(kRenderGlow, 184, 250, 214, 255, kRenderFxNoDissipation); +} + +void CTeleBall:: TeleportThink( void ) +{ + TraceResult tr; + Vector vecDest; + + int i; + for (i=1;i<5;i++) + { + vecDest = 500 * (Vector(RANDOM_FLOAT(-1000,1000), RANDOM_FLOAT(-1000,1000), RANDOM_FLOAT(-1000,1000)).Normalize()); + UTIL_TraceLine( pev->origin, pev->origin + vecDest, ignore_monsters, NULL, &tr); + if (tr.flFraction != 1.0) + { + // we hit something. + pBeam[i]->pev->origin = pev->origin; + pBeam[i]->PointsInit( pev->origin, tr.vecEndPos ); + } + } + + pev->nextthink = gpGlobals->time + 0.1; + pev->frame = (int)(pev->frame + 1) % 20; //comment this to disable sprite animation +} + +void CTeleBall::TeleportTouch( CBaseEntity *pOther ) +{ + if (pOther->Classify() != CLASS_PLAYER) + { + EMIT_SOUND(ENT(pev), CHAN_ITEM, "weapons/displacer_teleport.wav", 1, ATTN_NORM); + } + else EMIT_SOUND(ENT(pev), CHAN_ITEM, "weapons/displacer_teleport_player.wav", 1, ATTN_NORM); + + //if we shoot player - teleport him before energy ball killed him + if (pOther->IsPlayer()) //if we hit player + { + #ifdef CLIENT_DLL + if (bIsMultiplayer()) + #else + if (g_pGameRules->IsMultiplayer() ) //and if we are in MP game + #endif + { + CBaseEntity *pSpot = NULL; + CBaseEntity *pEntity = NULL; + int count = 0; + while ((pEntity = UTIL_FindEntityByClassname(pEntity, "info_player_deathmatch")) != NULL) + { + count++; + } + if (count>0) + { + for ( int i = RANDOM_LONG(1,count); i > 0; i-- ) //Randomize teleport spot + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + Vector tmp = pSpot->pev->origin; + //tmp.z -= pOther->pev->mins.z; + //tmp.z++; + UTIL_SetOrigin( pOther->pev, tmp ); //teleport player + PlayEffect( tmp, pOther); + } + } + } + SetTouch( NULL ); + pev->velocity = Vector( 0, 0, 0 ); //stop energy ball, otherwise it will "slide" + + SetThink(&CTeleBall::TeleportKill); + pev->nextthink = gpGlobals->time+0.2; //allows energy ball to stay alive after it touched something +} + +void CTeleBall::TeleportKill( void ) +{ + int flAdjustedDamage; + float flDist; + + MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, pev->origin ); + WRITE_BYTE( TE_BEAMCYLINDER ); + WRITE_COORD( pev->origin.x );// coord coord coord (center position) + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( pev->origin.x );// coord coord coord (axis and radius) + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z + 16 + (300 / 2)/ .2); + WRITE_SHORT( ring_sprite ); // short (sprite index) + WRITE_BYTE( 0 ); // byte (starting frame) + WRITE_BYTE( 0 ); // byte (frame rate in 0.1's) + WRITE_BYTE( 4 ); // byte (life in 0.1's) + WRITE_BYTE( 16 ); // byte (line width in 0.1's) + WRITE_BYTE( 0 ); // byte (noise amplitude in 0.01's) + WRITE_BYTE( 188 );// byte,byte,byte (color) + WRITE_BYTE( 220 ); + WRITE_BYTE( 255 ); + WRITE_BYTE( pev->renderamt );// byte (brightness) + WRITE_BYTE( 0 ); // byte (scroll speed in 0.1's) + MESSAGE_END(); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE(TE_DLIGHT); + WRITE_COORD(pev->origin.x); // X + WRITE_COORD(pev->origin.y); // Y + WRITE_COORD(pev->origin.z); // Z + WRITE_BYTE( 10 ); // radius * 0.1 + WRITE_BYTE( 216 ); // r + WRITE_BYTE( 239 ); // g + WRITE_BYTE( 80 ); // b + WRITE_BYTE( 5 ); // time * 10 + WRITE_BYTE( 0 ); // decay * 0.1 + MESSAGE_END( ); + //there was SetTouch( NULL ); + CBaseEntity *pEntity = NULL; + // iterate on all entities in the vicinity. + while ((pEntity = UTIL_FindEntityInSphere( pEntity, pev->origin, 300 )) != NULL) + { + if ( pEntity->pev->takedamage != DAMAGE_NO ) + { + // houndeyes do FULL damage if the ent in question is visible. Half damage otherwise. + // This means that you must get out of the houndeye's attack range entirely to avoid damage. + // Calculate full damage first + flAdjustedDamage = 250; + + flDist = (pEntity->Center() - pev->origin).Length(); + + flAdjustedDamage -= ( flDist / 300 ) * flAdjustedDamage; + + if ( !FVisible( pEntity ) ) + { + 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; + } + } + if (flAdjustedDamage > 0 ) + { + //pev, pev + entvars_t *pevOwner; + pevOwner = VARS( pev->owner ); + + pEntity->TakeDamage ( pev, pevOwner, flAdjustedDamage, DMG_SONIC | DMG_ALWAYSGIB ); + } + } + } + + for (int i=1;i<5;i++) UTIL_Remove( pBeam[i] ); //remove beams + SetThink ( &CTeleBall::SUB_Remove ); + pev->nextthink = 0.2; +} + +void CDisplacer::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_DISPLACER; + SET_MODEL(ENT(pev), "models/w_displacer.mdl"); + + m_iDefaultAmmo = DISPLACER_DEFAULT_GIVE; + + FallInit();// get ready to fall down. +} + +void CDisplacer::Precache( void ) +{ + PRECACHE_MODEL("models/w_displacer.mdl"); + PRECACHE_MODEL("models/v_displacer.mdl"); + PRECACHE_MODEL("models/p_displacer.mdl"); + + PRECACHE_SOUND("weapons/displacer_fire.wav"); + PRECACHE_SOUND("weapons/displacer_impact.wav"); + PRECACHE_SOUND("weapons/displacer_self.wav"); + PRECACHE_SOUND("weapons/displacer_spin.wav"); + PRECACHE_SOUND("weapons/displacer_spin2.wav"); + PRECACHE_SOUND("weapons/displacer_start.wav"); + PRECACHE_SOUND("weapons/displacer_teleport.wav"); + PRECACHE_SOUND("weapons/displacer_teleport_player.wav"); + + PRECACHE_MODEL("sprites/Fexplo1.spr"); + PRECACHE_MODEL("sprites/XFlare1.spr"); + PRECACHE_SOUND ("debris/beamstart7.wav"); + UTIL_PrecacheOther( "displacer_teleporter" ); +} + +int CDisplacer::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 CDisplacer::Holster( int skiplocal) +{ + SendWeaponAnim( DISPLACER_HOLSTER1 ); +} + +BOOL CDisplacer::Deploy( ) +{ + return DefaultDeploy( "models/v_displacer.mdl", "models/p_displacer.mdl", DISPLACER_DRAW, "displacer" ); +} + +int CDisplacer::GetItemInfo(ItemInfo *p) +{ + p->pszName = STRING(pev->classname); + p->pszAmmo1 = "uranium"; + p->iMaxAmmo1 = URANIUM_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = DISPLACER_MAX_CLIP; + p->iSlot = 4; // slot 5, position 5 + p->iPosition = 4; + p->iId = m_iId = WEAPON_DISPLACER; + p->iFlags = 0; + p->iWeight = DISPLACER_WEIGHT; + + return 1; +} + +BOOL CDisplacer::PlayEmptySound( void ) +{ + //button11.wav + if (m_iPlayEmptySound) + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +void CDisplacer::SpinupThink( void ) +{ + SendWeaponAnim( DISPLACER_SPINUP ); + EMIT_SOUND(ENT(pev), CHAN_ITEM, "weapons/displacer_spin.wav", 1, ATTN_NORM); + + // don't fire underwater + if ( m_pPlayer->pev->waterlevel == 3 ) + { + ResetEmptySound( ); + //m_flNextSecondaryAttack = m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.15; + return; + } + + SetThink(&CDisplacer::FireThink); + pev->nextthink=gpGlobals->time + 1.0; + //m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 3.0; +} + +void CDisplacer::FireThink( void ) +{ + if ( m_pPlayer->m_rgAmmo[ m_iPrimaryAmmoType ] < 20 ) + { + PlayEmptySound( ); + SendWeaponAnim( DISPLACER_IDLE1 ); + //m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + return; + } + + SendWeaponAnim( DISPLACER_FIRE ); + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecSrc = m_pPlayer->GetGunPosition( ); + + CTeleBall *pEntity = (CTeleBall *)Create( "displacer_teleporter", vecSrc, m_pPlayer->pev->angles, m_pPlayer->edict() ); + pEntity->pev->owner = m_pPlayer->edict(); + pEntity->pev->velocity = gpGlobals->v_forward * 500; + + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= 20; + + m_fInAttack = 0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; +} + +void CDisplacer::PrimaryAttack( void ) +{ + SetThink(&CDisplacer::SpinupThink); + pev->nextthink=gpGlobals->time + 0.1; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.5; +} + +void CDisplacer::SecondaryAttack( void ) +{ + if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] >= 60) + { + //if is multiplayer game + #ifdef CLIENT_DLL + if (bIsMultiplayer()) + #else + if (g_pGameRules->IsMultiplayer() ) + #endif + { + CBaseEntity *pSpot = NULL; + CBaseEntity *pEntity = NULL; + int count = 0; + while ((pEntity = UTIL_FindEntityByClassname(pEntity, "info_player_deathmatch")) != NULL) + { + count++; + } + if (count>0) + { + for ( int i = RANDOM_LONG(1,count); i > 0; i-- ) //Randomize teleport spot + pSpot = UTIL_FindEntityByClassname( pSpot, "info_player_deathmatch" ); + + //spawn CTeleBall here + CTeleBall *pEntity = (CTeleBall *)Create( "displacer_teleporter", pev->origin, m_pPlayer->pev->angles, m_pPlayer->edict() ); + pEntity->pev->owner = m_pPlayer->edict(); + pEntity->pev->velocity = -gpGlobals->v_up * 150; + // + UTIL_SetOrigin( m_pPlayer->pev, pSpot->pev->origin ); + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= 60; + + PlayEffect( pSpot->pev->origin ); + } + } + else + //if is singleplayer game + { + CDisplacerTarget *pSpot = NULL; + + while ( (pSpot = (CDisplacerTarget*)UTIL_FindEntityByClassname( pSpot, "info_displacer_xen_target" )) && (!FNullEnt(pSpot->edict())) ) + { + if ( pSpot->m_iPlayerIndex == m_pPlayer->m_iDecayId ) + break; + } + + if ( (pSpot != NULL) && (pSpot->m_iPlayerIndex == m_pPlayer->m_iDecayId) ) + { + Vector tmp = pSpot->pev->origin; + tmp.z -= m_pPlayer->pev->mins.z; + tmp.z++; + + //spawn CTeleBall here + CTeleBall *pEntity = (CTeleBall *)Create( "displacer_teleporter", pev->origin, m_pPlayer->pev->angles, m_pPlayer->edict() ); + pEntity->pev->owner = m_pPlayer->edict(); + pEntity->pev->velocity = -gpGlobals->v_up * 150; + // + UTIL_SetOrigin( m_pPlayer->pev, tmp); + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= 60; + PlayEffect( tmp ); + + if ( !FStringNull( pSpot->pev->target ) ) + FireTargets( STRING(pSpot->pev->target), this, this, USE_TOGGLE, 0.0 ); + + } else + PlayEmptySound( ); + } + m_flNextSecondaryAttack = gpGlobals->time + 4.0; + } + else + { + PlayEmptySound( ); + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + return; + } +} + +void CDisplacer::PlayEffect( Vector Origin ) +{ + CBeam *pBeam[15]; + TraceResult tr; + Vector vecDest; + + EMIT_SOUND( edict(), CHAN_BODY, "debris/beamstart7.wav", 1, ATTN_NORM ); + UTIL_ScreenFade( m_pPlayer, Vector(0,255,0), 0.5, 0.25, 255, FFADE_IN ); + + int i; + for (i=1;i<15;i++) + { + vecDest = 500 * (Vector(RANDOM_FLOAT(-1000,1000), RANDOM_FLOAT(-1000,1000), RANDOM_FLOAT(-1000,1000)).Normalize()); + UTIL_TraceLine( Origin, Origin + vecDest, ignore_monsters, NULL, &tr); + if (tr.flFraction != 1.0) + { + // we hit something. + pBeam[i] = CBeam::BeamCreate("sprites/lgtning.spr",200); + pBeam[i]->pev->origin = Origin; + pBeam[i]->PointsInit( Origin, tr.vecEndPos ); + pBeam[i]->SetColor( 0, 255, 0 ); //Blue-Shift style + //197 243 169 //c1a1b + pBeam[i]->SetNoise( 65 ); + pBeam[i]->SetBrightness( 150 ); + pBeam[i]->SetWidth( 18 ); + pBeam[i]->SetScrollRate( 35 ); + pBeam[i]->SetThink( &CBeam::SUB_Remove ); + pBeam[i]->pev->nextthink = gpGlobals->time + 1; //was 0.1 + } + } + EMIT_SOUND( edict(), CHAN_BODY, "debris/beamstart2.wav", 1, ATTN_NORM ); + + CSprite *pSpr = CSprite::SpriteCreate( "sprites/Fexplo1.spr", Origin, TRUE ); + pSpr->AnimateAndDie( 10 ); + pSpr->SetTransparency(kRenderGlow, 77, 210, 130, 255, kRenderFxNoDissipation); + + pSpr = CSprite::SpriteCreate( "sprites/XFlare1.spr", Origin, TRUE ); + pSpr->AnimateAndDie( 10 ); + pSpr->SetTransparency(kRenderGlow, 184, 250, 214, 255, kRenderFxNoDissipation); +// pev->nextthink = 0; //was 0 +} + +void CDisplacer::Reload( void ) +{ + +} + +void CDisplacer::WeaponIdle( void ) +{ + ResetEmptySound( ); + + if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.5) + { + iAnim = DISPLACER_IDLE1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 2;//UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } + else + { + iAnim = DISPLACER_IDLE2; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; + } + + SendWeaponAnim( iAnim ); +} diff --git a/dlls/modeltrain.cpp b/dlls/modeltrain.cpp index f9073fc4..ef1f1926 100644 --- a/dlls/modeltrain.cpp +++ b/dlls/modeltrain.cpp @@ -1,528 +1,528 @@ -#include "extdll.h" -#include "util.h" -#include "cbase.h" -#include "trains.h" -#include "saverestore.h" - -// --------------------------------------------------------------------- -// -// Sprite Train -// -// --------------------------------------------------------------------- - -class CFuncModelTrain : public CBaseEntity -{ -public: - void Spawn( void ); - void Precache( void ); - - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void KeyValue( KeyValueData* pkvd ); - - void EXPORT Next( void ); - void EXPORT Find( void ); - void EXPORT NearestPath( void ); - void EXPORT DeadEnd( void ); - - void NextThink( float thinkTime, BOOL alwaysThink ); - - void SetTrack( CPathTrack *track ) { m_ppath = track->Nearest(pev->origin); } - void SetControls( entvars_t *pevControls ); - BOOL OnControls( entvars_t *pev ); - void Animate( float frames ); - - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - - static TYPEDESCRIPTION m_SaveData[]; - virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DIRECTIONAL_USE; } - - virtual void OverrideReset( void ); - - CPathTrack *m_ppath; - float m_length; - float m_height; - float m_speed; - float m_dir; - float m_startSpeed; - Vector m_controlMins; - Vector m_controlMaxs; - float m_flBank; - float m_oldSpeed; - int m_scale; - - float m_lastTime; - float m_maxFrame; -}; - -TYPEDESCRIPTION CFuncModelTrain::m_SaveData[] = -{ - DEFINE_FIELD( CFuncModelTrain, m_ppath, FIELD_CLASSPTR ), - DEFINE_FIELD( CFuncModelTrain, m_length, FIELD_FLOAT ), - DEFINE_FIELD( CFuncModelTrain, m_height, FIELD_FLOAT ), - DEFINE_FIELD( CFuncModelTrain, m_speed, FIELD_FLOAT ), - DEFINE_FIELD( CFuncModelTrain, m_dir, FIELD_FLOAT ), - DEFINE_FIELD( CFuncModelTrain, m_startSpeed, FIELD_FLOAT ), - DEFINE_FIELD( CFuncModelTrain, m_controlMins, FIELD_VECTOR ), - DEFINE_FIELD( CFuncModelTrain, m_controlMaxs, FIELD_VECTOR ), - DEFINE_FIELD( CFuncModelTrain, m_flBank, FIELD_FLOAT ), - DEFINE_FIELD( CFuncModelTrain, m_oldSpeed, FIELD_FLOAT ), - DEFINE_FIELD( CFuncModelTrain, m_maxFrame, FIELD_FLOAT ), - DEFINE_FIELD( CFuncModelTrain, m_lastTime, FIELD_FLOAT ), -}; - -IMPLEMENT_SAVERESTORE( CFuncModelTrain, CBaseEntity ); -LINK_ENTITY_TO_CLASS( monster_modeltrain, CFuncModelTrain); - -void CFuncModelTrain :: KeyValue( KeyValueData *pkvd ) -{ - if (FStrEq(pkvd->szKeyName, "wheels"))//! - { - m_length = atof(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "height"))//! - { - m_height = atof(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "speed"))//op4 - { - m_startSpeed = atof(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "bank"))//! - { - m_flBank = atof(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "scale"))//op4 - { - m_scale = atof(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else - CBaseEntity::KeyValue( pkvd ); -} - - -void CFuncModelTrain:: NextThink( float thinkTime, BOOL alwaysThink ) -{ - if ( alwaysThink ) - pev->flags |= FL_ALWAYSTHINK; - else - pev->flags &= ~FL_ALWAYSTHINK; - - pev->nextthink = thinkTime; -} - -void CFuncModelTrain:: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) -{ - if ( useType != USE_SET ) - { - if ( !ShouldToggle( useType, (pev->speed != 0) ) ) - return; - - if ( pev->speed == 0 ) - { - pev->speed = m_speed * m_dir; - - Next(); - } - else - { - pev->speed = 0; - pev->velocity = g_vecZero; - pev->avelocity = g_vecZero; - SetThink( NULL ); - } - } - else - { - float delta = value; - - delta = ((int)(pev->speed * 4) / (int)m_speed)*0.25 + 0.25 * delta; - if ( delta > 1 ) - delta = 1; - else if ( delta < -1 ) - delta = -1; - if ( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY ) - { - if ( delta < 0 ) - delta = 0; - } - pev->speed = m_speed * delta; - Next(); - ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING(pev->targetname), pev->speed ); - } -} - - -static float Fix( float angle ) -{ - while ( angle < 0 ) - angle += 360; - while ( angle > 360 ) - angle -= 360; - - return angle; -} - - -static void FixupAngles( Vector &v ) -{ - v.x = Fix( v.x ); - v.y = Fix( v.y ); - v.z = Fix( v.z ); -} - -#define TRAIN_STARTPITCH 60 -#define TRAIN_MAXPITCH 200 -#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation - -void CFuncModelTrain:: Next( void ) -{ - float time = 0.5; - - if ( !pev->speed ) - { - ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING(pev->targetname) ); - return; - } - - if ( !m_ppath ) - { - ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING(pev->targetname) ); - return; - } - - Vector nextPos = pev->origin; - - nextPos.z -= m_height; - CPathTrack *pnext = m_ppath->LookAhead( &nextPos, pev->speed * 0.1, 1 ); - nextPos.z += m_height; - - pev->velocity = (nextPos - pev->origin) * 10; - pev->angles = pnext->pev->angles; // set modeltrain's angles to angles of path_track - Vector nextFront = pev->origin; - - nextFront.z -= m_height; - if ( m_length > 0 ) - m_ppath->LookAhead( &nextFront, m_length, 0 ); - else - m_ppath->LookAhead( &nextFront, 100, 0 ); - nextFront.z += m_height; - - Vector delta = nextFront - pev->origin; - Vector angles = UTIL_VecToAngles( delta ); - // The train actually points west - angles.y += 180; - - // !!! All of this crap has to be done to make the angles not wrap around, revisit this. - FixupAngles( angles ); - FixupAngles( pev->angles ); - - if ( !pnext || (delta.x == 0 && delta.y == 0) ) - angles = pev->angles; - - float vy, vx; - if ( !(pev->spawnflags & SF_TRACKTRAIN_NOPITCH) ) - vx = UTIL_AngleDistance( angles.x, pev->angles.x ); - else - vx = 0; - vy = UTIL_AngleDistance( angles.y, pev->angles.y ); - - pev->avelocity.y = vy * 10; - pev->avelocity.x = vx * 10; - - if ( m_flBank != 0 ) - { - if ( pev->avelocity.y < -5 ) - pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); - else if ( pev->avelocity.y > 5 ) - pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); - else - pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( 0, pev->angles.z, m_flBank*4 ), pev->angles.z) * 4; - } - - if ( pnext ) - { - if ( pnext != m_ppath ) - { - CPathTrack *pFire; - if ( pev->speed >= 0 ) - pFire = pnext; - else - pFire = m_ppath; - - m_ppath = pnext; - // Fire the pass target if there is one - if ( pFire->pev->message ) - { - FireTargets( STRING(pFire->pev->message), this, this, USE_TOGGLE, 0 ); - if ( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) ) - pFire->pev->message = 0; - } - - if ( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN ) - pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL; - - // Don't override speed if under user control - if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) - { - if ( pFire->pev->speed != 0 ) - {// don't copy speed from target if it is 0 (uninitialized) - pev->speed = pFire->pev->speed; - ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); - } - } - } - SetThink( &CFuncModelTrain::Next ); - NextThink( pev->ltime + time, TRUE ); - } - else // end of path, stop - { - pev->velocity = (nextPos - pev->origin); - pev->avelocity = g_vecZero; - float distance = pev->velocity.Length(); - m_oldSpeed = pev->speed; - - - pev->speed = 0; - - // Move to the dead end - - // Are we there yet? - if ( distance > 0 ) - { - // no, how long to get there? - time = distance / m_oldSpeed; - pev->velocity = pev->velocity * (m_oldSpeed / distance); - SetThink( &CFuncModelTrain::DeadEnd ); - NextThink( pev->ltime + time, FALSE ); - } - else - { - DeadEnd(); - } - } -//Animate( 1 );//was 1 - too fast -Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); -m_lastTime = gpGlobals->time; -} - - -void CFuncModelTrain::DeadEnd( void ) -{ - // Fire the dead-end target if there is one - CPathTrack *pTrack, *pNext; - - pTrack = m_ppath; - - ALERT( at_aiconsole, "TRAIN(%s): Dead end ", STRING(pev->targetname) ); - // Find the dead end path node - // HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed - // so we have to traverse the list to it's end. - if ( pTrack ) - { - if ( m_oldSpeed < 0 ) - { - do - { - pNext = pTrack->ValidPath( pTrack->GetPrevious(), TRUE ); - if ( pNext ) - pTrack = pNext; - } while ( pNext ); - } - else - { - do - { - pNext = pTrack->ValidPath( pTrack->GetNext(), TRUE ); - if ( pNext ) - pTrack = pNext; - } while ( pNext ); - } - } - - pev->velocity = g_vecZero; - pev->avelocity = g_vecZero; - if ( pTrack ) - { - ALERT( at_aiconsole, "at %s\n", STRING(pTrack->pev->targetname) ); - if ( pTrack->pev->netname ) - FireTargets( STRING(pTrack->pev->netname), this, this, USE_TOGGLE, 0 ); - } - else - ALERT( at_aiconsole, "\n" ); -} - - -void CFuncModelTrain :: SetControls( entvars_t *pevControls ) -{ - Vector offset = pevControls->origin - pev->oldorigin; - - m_controlMins = pevControls->mins + offset; - m_controlMaxs = pevControls->maxs + offset; -} - - -BOOL CFuncModelTrain :: OnControls( entvars_t *pevTest ) -{ - Vector offset = pevTest->origin - pev->origin; - - if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) - return FALSE; - - // Transform offset into local coordinates - UTIL_MakeVectors( pev->angles ); - Vector local; - local.x = DotProduct( offset, gpGlobals->v_forward ); - local.y = -DotProduct( offset, gpGlobals->v_right ); - local.z = DotProduct( offset, gpGlobals->v_up ); - - if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z && - local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z ) - return TRUE; - - return FALSE; -} - - -void CFuncModelTrain :: Find( void ) -{ - m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); - if ( !m_ppath ) - return; - - entvars_t *pevTarget = m_ppath->pev; - if ( !FClassnameIs( pevTarget, "path_track" ) ) - { - ALERT( at_error, "func_track_train must be on a path of path_track\n" ); - m_ppath = NULL; - return; - } - - Vector nextPos = pevTarget->origin; - nextPos.z += m_height; - - Vector look = nextPos; - look.z -= m_height; - m_ppath->LookAhead( &look, m_length, 0 ); - look.z += m_height; - - pev->angles = UTIL_VecToAngles( look - nextPos ); - // The train actually points west - pev->angles.y += 180; - - if ( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) - pev->angles.x = 0; - UTIL_SetOrigin( pev, nextPos ); - NextThink( pev->ltime + 0.1, FALSE ); - SetThink( &CFuncModelTrain::Next ); - pev->speed = m_startSpeed; -} - - -void CFuncModelTrain :: NearestPath( void ) -{ - CBaseEntity *pTrack = NULL; - CBaseEntity *pNearest = NULL; - float dist, closest; - - closest = 1024; - - while ((pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 )) != NULL) - { - // filter out non-tracks - if ( !(pTrack->pev->flags & (FL_CLIENT|FL_MONSTER)) && FClassnameIs( pTrack->pev, "path_track" ) ) - { - dist = (pev->origin - pTrack->pev->origin).Length(); - if ( dist < closest ) - { - closest = dist; - pNearest = pTrack; - } - } - } - - if ( !pNearest ) - { - ALERT( at_console, "Can't find a nearby track !!!\n" ); - SetThink(NULL); - return; - } - - ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING(pev->targetname), STRING(pNearest->pev->targetname) ); - // If I'm closer to the next path_track on this path, then it's my real path - pTrack = ((CPathTrack *)pNearest)->GetNext(); - if ( pTrack ) - { - if ( (pev->origin - pTrack->pev->origin).Length() < (pev->origin - pNearest->pev->origin).Length() ) - pNearest = pTrack; - } - - m_ppath = (CPathTrack *)pNearest; - - if ( pev->speed != 0 ) - { - NextThink( pev->ltime + 0.1, FALSE ); - SetThink( &CFuncModelTrain::Next ); - } -} - -void CFuncModelTrain ::OverrideReset( void ) -{ - NextThink( pev->ltime + 0.1, FALSE ); - SetThink( &CFuncModelTrain::NearestPath ); -} - -void CFuncModelTrain ::Animate( float frames ) -{ -if ( m_maxFrame > 0 ) - pev->frame = fmod( pev->frame + frames, m_maxFrame ); -} - -void CFuncModelTrain :: Spawn( void ) -{ - Precache(); - - if ( pev->speed == 0 ) - m_speed = 100; - else - m_speed = pev->speed; - - pev->speed = 0; - pev->velocity = g_vecZero; - pev->avelocity = g_vecZero; - pev->impulse = m_speed; - m_dir = 1; - - pev->solid = SOLID_NOT; - pev->movetype = MOVETYPE_PUSH; - //pev->rendercolor.x = 255; - //pev->rendercolor.y = 255; - //pev->rendercolor.z = 255; - pev->scale = m_scale; - //pev->rendermode = kRenderTransAdd; - //pev->renderamt = 255; - - SET_MODEL( ENT(pev), STRING(pev->model) ); - UTIL_SetOrigin( pev, pev->origin ); - pev->oldorigin = pev->origin; - - m_controlMins = pev->mins; - m_controlMaxs = pev->maxs; - m_controlMaxs.z += 72; - - m_lastTime = gpGlobals->time; - m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; - - NextThink( pev->ltime + 0.1, FALSE ); - SetThink( &CFuncModelTrain::Find ); -} - -void CFuncModelTrain :: Precache( void ) -{ - PRECACHE_MODEL( (char *)STRING(pev->model) ); -} +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +// --------------------------------------------------------------------- +// +// Sprite Train +// +// --------------------------------------------------------------------- + +class CFuncModelTrain : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData* pkvd ); + + void EXPORT Next( void ); + void EXPORT Find( void ); + void EXPORT NearestPath( void ); + void EXPORT DeadEnd( void ); + + void NextThink( float thinkTime, BOOL alwaysThink ); + + void SetTrack( CPathTrack *track ) { m_ppath = track->Nearest(pev->origin); } + void SetControls( entvars_t *pevControls ); + BOOL OnControls( entvars_t *pev ); + void Animate( float frames ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DIRECTIONAL_USE; } + + virtual void OverrideReset( void ); + + CPathTrack *m_ppath; + float m_length; + float m_height; + float m_speed; + float m_dir; + float m_startSpeed; + Vector m_controlMins; + Vector m_controlMaxs; + float m_flBank; + float m_oldSpeed; + int m_scale; + + float m_lastTime; + float m_maxFrame; +}; + +TYPEDESCRIPTION CFuncModelTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncModelTrain, m_ppath, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncModelTrain, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CFuncModelTrain, m_height, FIELD_FLOAT ), + DEFINE_FIELD( CFuncModelTrain, m_speed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncModelTrain, m_dir, FIELD_FLOAT ), + DEFINE_FIELD( CFuncModelTrain, m_startSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncModelTrain, m_controlMins, FIELD_VECTOR ), + DEFINE_FIELD( CFuncModelTrain, m_controlMaxs, FIELD_VECTOR ), + DEFINE_FIELD( CFuncModelTrain, m_flBank, FIELD_FLOAT ), + DEFINE_FIELD( CFuncModelTrain, m_oldSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncModelTrain, m_maxFrame, FIELD_FLOAT ), + DEFINE_FIELD( CFuncModelTrain, m_lastTime, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CFuncModelTrain, CBaseEntity ); +LINK_ENTITY_TO_CLASS( monster_modeltrain, CFuncModelTrain); + +void CFuncModelTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wheels"))//! + { + m_length = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height"))//! + { + m_height = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "speed"))//op4 + { + m_startSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bank"))//! + { + m_flBank = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "scale"))//op4 + { + m_scale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CFuncModelTrain:: NextThink( float thinkTime, BOOL alwaysThink ) +{ + if ( alwaysThink ) + pev->flags |= FL_ALWAYSTHINK; + else + pev->flags &= ~FL_ALWAYSTHINK; + + pev->nextthink = thinkTime; +} + +void CFuncModelTrain:: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) + { + if ( !ShouldToggle( useType, (pev->speed != 0) ) ) + return; + + if ( pev->speed == 0 ) + { + pev->speed = m_speed * m_dir; + + Next(); + } + else + { + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + SetThink( NULL ); + } + } + else + { + float delta = value; + + delta = ((int)(pev->speed * 4) / (int)m_speed)*0.25 + 0.25 * delta; + if ( delta > 1 ) + delta = 1; + else if ( delta < -1 ) + delta = -1; + if ( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY ) + { + if ( delta < 0 ) + delta = 0; + } + pev->speed = m_speed * delta; + Next(); + ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING(pev->targetname), pev->speed ); + } +} + + +static float Fix( float angle ) +{ + while ( angle < 0 ) + angle += 360; + while ( angle > 360 ) + angle -= 360; + + return angle; +} + + +static void FixupAngles( Vector &v ) +{ + v.x = Fix( v.x ); + v.y = Fix( v.y ); + v.z = Fix( v.z ); +} + +#define TRAIN_STARTPITCH 60 +#define TRAIN_MAXPITCH 200 +#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation + +void CFuncModelTrain:: Next( void ) +{ + float time = 0.5; + + if ( !pev->speed ) + { + ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING(pev->targetname) ); + return; + } + + if ( !m_ppath ) + { + ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING(pev->targetname) ); + return; + } + + Vector nextPos = pev->origin; + + nextPos.z -= m_height; + CPathTrack *pnext = m_ppath->LookAhead( &nextPos, pev->speed * 0.1, 1 ); + nextPos.z += m_height; + + pev->velocity = (nextPos - pev->origin) * 10; + pev->angles = pnext->pev->angles; // set modeltrain's angles to angles of path_track + Vector nextFront = pev->origin; + + nextFront.z -= m_height; + if ( m_length > 0 ) + m_ppath->LookAhead( &nextFront, m_length, 0 ); + else + m_ppath->LookAhead( &nextFront, 100, 0 ); + nextFront.z += m_height; + + Vector delta = nextFront - pev->origin; + Vector angles = UTIL_VecToAngles( delta ); + // The train actually points west + angles.y += 180; + + // !!! All of this crap has to be done to make the angles not wrap around, revisit this. + FixupAngles( angles ); + FixupAngles( pev->angles ); + + if ( !pnext || (delta.x == 0 && delta.y == 0) ) + angles = pev->angles; + + float vy, vx; + if ( !(pev->spawnflags & SF_TRACKTRAIN_NOPITCH) ) + vx = UTIL_AngleDistance( angles.x, pev->angles.x ); + else + vx = 0; + vy = UTIL_AngleDistance( angles.y, pev->angles.y ); + + pev->avelocity.y = vy * 10; + pev->avelocity.x = vx * 10; + + if ( m_flBank != 0 ) + { + if ( pev->avelocity.y < -5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else if ( pev->avelocity.y > 5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( 0, pev->angles.z, m_flBank*4 ), pev->angles.z) * 4; + } + + if ( pnext ) + { + if ( pnext != m_ppath ) + { + CPathTrack *pFire; + if ( pev->speed >= 0 ) + pFire = pnext; + else + pFire = m_ppath; + + m_ppath = pnext; + // Fire the pass target if there is one + if ( pFire->pev->message ) + { + FireTargets( STRING(pFire->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) ) + pFire->pev->message = 0; + } + + if ( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN ) + pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL; + + // Don't override speed if under user control + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + { + if ( pFire->pev->speed != 0 ) + {// don't copy speed from target if it is 0 (uninitialized) + pev->speed = pFire->pev->speed; + ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); + } + } + } + SetThink( &CFuncModelTrain::Next ); + NextThink( pev->ltime + time, TRUE ); + } + else // end of path, stop + { + pev->velocity = (nextPos - pev->origin); + pev->avelocity = g_vecZero; + float distance = pev->velocity.Length(); + m_oldSpeed = pev->speed; + + + pev->speed = 0; + + // Move to the dead end + + // Are we there yet? + if ( distance > 0 ) + { + // no, how long to get there? + time = distance / m_oldSpeed; + pev->velocity = pev->velocity * (m_oldSpeed / distance); + SetThink( &CFuncModelTrain::DeadEnd ); + NextThink( pev->ltime + time, FALSE ); + } + else + { + DeadEnd(); + } + } +//Animate( 1 );//was 1 - too fast +Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); +m_lastTime = gpGlobals->time; +} + + +void CFuncModelTrain::DeadEnd( void ) +{ + // Fire the dead-end target if there is one + CPathTrack *pTrack, *pNext; + + pTrack = m_ppath; + + ALERT( at_aiconsole, "TRAIN(%s): Dead end ", STRING(pev->targetname) ); + // Find the dead end path node + // HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed + // so we have to traverse the list to it's end. + if ( pTrack ) + { + if ( m_oldSpeed < 0 ) + { + do + { + pNext = pTrack->ValidPath( pTrack->GetPrevious(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + else + { + do + { + pNext = pTrack->ValidPath( pTrack->GetNext(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + } + + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + if ( pTrack ) + { + ALERT( at_aiconsole, "at %s\n", STRING(pTrack->pev->targetname) ); + if ( pTrack->pev->netname ) + FireTargets( STRING(pTrack->pev->netname), this, this, USE_TOGGLE, 0 ); + } + else + ALERT( at_aiconsole, "\n" ); +} + + +void CFuncModelTrain :: SetControls( entvars_t *pevControls ) +{ + Vector offset = pevControls->origin - pev->oldorigin; + + m_controlMins = pevControls->mins + offset; + m_controlMaxs = pevControls->maxs + offset; +} + + +BOOL CFuncModelTrain :: OnControls( entvars_t *pevTest ) +{ + Vector offset = pevTest->origin - pev->origin; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + return FALSE; + + // Transform offset into local coordinates + UTIL_MakeVectors( pev->angles ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = -DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z && + local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z ) + return TRUE; + + return FALSE; +} + + +void CFuncModelTrain :: Find( void ) +{ + m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); + if ( !m_ppath ) + return; + + entvars_t *pevTarget = m_ppath->pev; + if ( !FClassnameIs( pevTarget, "path_track" ) ) + { + ALERT( at_error, "func_track_train must be on a path of path_track\n" ); + m_ppath = NULL; + return; + } + + Vector nextPos = pevTarget->origin; + nextPos.z += m_height; + + Vector look = nextPos; + look.z -= m_height; + m_ppath->LookAhead( &look, m_length, 0 ); + look.z += m_height; + + pev->angles = UTIL_VecToAngles( look - nextPos ); + // The train actually points west + pev->angles.y += 180; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) + pev->angles.x = 0; + UTIL_SetOrigin( pev, nextPos ); + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncModelTrain::Next ); + pev->speed = m_startSpeed; +} + + +void CFuncModelTrain :: NearestPath( void ) +{ + CBaseEntity *pTrack = NULL; + CBaseEntity *pNearest = NULL; + float dist, closest; + + closest = 1024; + + while ((pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 )) != NULL) + { + // filter out non-tracks + if ( !(pTrack->pev->flags & (FL_CLIENT|FL_MONSTER)) && FClassnameIs( pTrack->pev, "path_track" ) ) + { + dist = (pev->origin - pTrack->pev->origin).Length(); + if ( dist < closest ) + { + closest = dist; + pNearest = pTrack; + } + } + } + + if ( !pNearest ) + { + ALERT( at_console, "Can't find a nearby track !!!\n" ); + SetThink(NULL); + return; + } + + ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING(pev->targetname), STRING(pNearest->pev->targetname) ); + // If I'm closer to the next path_track on this path, then it's my real path + pTrack = ((CPathTrack *)pNearest)->GetNext(); + if ( pTrack ) + { + if ( (pev->origin - pTrack->pev->origin).Length() < (pev->origin - pNearest->pev->origin).Length() ) + pNearest = pTrack; + } + + m_ppath = (CPathTrack *)pNearest; + + if ( pev->speed != 0 ) + { + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncModelTrain::Next ); + } +} + +void CFuncModelTrain ::OverrideReset( void ) +{ + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncModelTrain::NearestPath ); +} + +void CFuncModelTrain ::Animate( float frames ) +{ +if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame + frames, m_maxFrame ); +} + +void CFuncModelTrain :: Spawn( void ) +{ + Precache(); + + if ( pev->speed == 0 ) + m_speed = 100; + else + m_speed = pev->speed; + + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->impulse = m_speed; + m_dir = 1; + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_PUSH; + //pev->rendercolor.x = 255; + //pev->rendercolor.y = 255; + //pev->rendercolor.z = 255; + pev->scale = m_scale; + //pev->rendermode = kRenderTransAdd; + //pev->renderamt = 255; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + UTIL_SetOrigin( pev, pev->origin ); + pev->oldorigin = pev->origin; + + m_controlMins = pev->mins; + m_controlMaxs = pev->maxs; + m_controlMaxs.z += 72; + + m_lastTime = gpGlobals->time; + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncModelTrain::Find ); +} + +void CFuncModelTrain :: Precache( void ) +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); +} diff --git a/dlls/monstermaker.h b/dlls/monstermaker.h index f339bb68..f1f375f5 100644 --- a/dlls/monstermaker.h +++ b/dlls/monstermaker.h @@ -1,66 +1,66 @@ -/*** -* -* Copyright (c) 1996-2002, Valve LLC. All rights reserved. -* -* This product contains software technology licensed from Id -* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. -* All Rights Reserved. -* -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. -* -****/ -//========================================================= -// Monster Maker - this is an entity that creates monsters -// in the game. -//========================================================= - -// Monstermaker spawnflags -#define SF_MONSTERMAKER_START_ON 1 // start active ( if has targetname ) -#define SF_MONSTERMAKER_CYCLIC 4 // drop one monster every time fired. -#define SF_MONSTERMAKER_MONSTERCLIP 8 // Children are blocked by monsterclip -#define SF_MONSTERMAKER_FIREONCE 16 // kill after all children spawned if not cyclic - -#define SF_WARPBALL_ONCE 1 // spawn monster only once, ignore "maxlivechildren" - -//========================================================= -// MonsterMaker - this ent creates monsters during the game. -//========================================================= -class CMonsterMaker : public CBaseMonster -{ -public: - void Spawn( void ); - void Precache( void ); - void KeyValue( KeyValueData* pkvd); - void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void EXPORT CyclicUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void EXPORT MakerThink ( void ); - void DeathNotice ( entvars_t *pevChild );// monster maker children use this to tell the monster maker that they have died. - void MakeMonster( void ); - void MonsterMakerInit( const char* ChildName, int MaxLiveChildren, int NumMonsters ); - static CMonsterMaker *MonsterMakerCreate( const char* ChildName, int MaxLiveChildren, int NumMonsters ); - - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - - static TYPEDESCRIPTION m_SaveData[]; - - string_t m_iszMonsterClassname; // classname of the monster(s) that will be created. - string_t m_iszWarpTarget; // name of the entity which's origin will be used to spawn monsters at. - - int m_cNumMonsters;// counter number of monsters this ent should create - int m_cTotalMonstersCount; // number of monsters to create - - int m_iChildrenSpawnflags; - int m_cLiveChildren;// how many monsters made by this monster maker that are currently alive - int m_iMaxLiveChildren;// max number of monsters that this maker may have out at one time. - - float m_flGround; // z coord of the ground under me, used to make sure no monsters are under the maker when it drops a new child - - BOOL m_fActive; - BOOL m_fFadeChildren;// should we make the children fadeout? - BOOL m_fIsWarpBall; - +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ +//========================================================= +// Monster Maker - this is an entity that creates monsters +// in the game. +//========================================================= + +// Monstermaker spawnflags +#define SF_MONSTERMAKER_START_ON 1 // start active ( if has targetname ) +#define SF_MONSTERMAKER_CYCLIC 4 // drop one monster every time fired. +#define SF_MONSTERMAKER_MONSTERCLIP 8 // Children are blocked by monsterclip +#define SF_MONSTERMAKER_FIREONCE 16 // kill after all children spawned if not cyclic + +#define SF_WARPBALL_ONCE 1 // spawn monster only once, ignore "maxlivechildren" + +//========================================================= +// MonsterMaker - this ent creates monsters during the game. +//========================================================= +class CMonsterMaker : public CBaseMonster +{ +public: + void Spawn( void ); + void Precache( void ); + void KeyValue( KeyValueData* pkvd); + void EXPORT ToggleUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT CyclicUse ( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void EXPORT MakerThink ( void ); + void DeathNotice ( entvars_t *pevChild );// monster maker children use this to tell the monster maker that they have died. + void MakeMonster( void ); + void MonsterMakerInit( const char* ChildName, int MaxLiveChildren, int NumMonsters ); + static CMonsterMaker *MonsterMakerCreate( const char* ChildName, int MaxLiveChildren, int NumMonsters ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + + string_t m_iszMonsterClassname; // classname of the monster(s) that will be created. + string_t m_iszWarpTarget; // name of the entity which's origin will be used to spawn monsters at. + + int m_cNumMonsters;// counter number of monsters this ent should create + int m_cTotalMonstersCount; // number of monsters to create + + int m_iChildrenSpawnflags; + int m_cLiveChildren;// how many monsters made by this monster maker that are currently alive + int m_iMaxLiveChildren;// max number of monsters that this maker may have out at one time. + + float m_flGround; // z coord of the ground under me, used to make sure no monsters are under the maker when it drops a new child + + BOOL m_fActive; + BOOL m_fFadeChildren;// should we make the children fadeout? + BOOL m_fIsWarpBall; + }; \ No newline at end of file diff --git a/dlls/notepad.cpp b/dlls/notepad.cpp index c0670bc6..4a789a1d 100644 --- a/dlls/notepad.cpp +++ b/dlls/notepad.cpp @@ -1,136 +1,136 @@ -#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" - - -class CNotepad : public CBaseToggle -{ -public: - void Spawn( ); - void Precache( void ); - void EXPORT Off(void); - void EXPORT Recharge(void); - void KeyValue( KeyValueData *pkvd ); - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - virtual int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() | FCAP_CONTINUOUS_USE) & ~FCAP_ACROSS_TRANSITION; } - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - - char m_iszText[256]; - int m_iTitle; - - static TYPEDESCRIPTION m_SaveData[]; - - float m_flNextCharge; - int m_iReactivate ; // DeathMatch Delay until reactvated - int m_iJuice; - int m_iOn; // 0 = off, 1 = startup, 2 = going - float m_flSoundTime; -}; - -TYPEDESCRIPTION CNotepad::m_SaveData[] = -{ - DEFINE_FIELD( CNotepad, m_flNextCharge, FIELD_TIME), - DEFINE_FIELD( CNotepad, m_iReactivate, FIELD_INTEGER), - DEFINE_FIELD( CNotepad, m_iJuice, FIELD_INTEGER), - DEFINE_FIELD( CNotepad, m_iOn, FIELD_INTEGER), - DEFINE_FIELD( CNotepad, m_flSoundTime, FIELD_TIME), -}; - -IMPLEMENT_SAVERESTORE( CNotepad, CBaseEntity ); - -LINK_ENTITY_TO_CLASS(func_notepad, CNotepad); - -extern int gmsgNotepad; -extern int gmsgSparePlayer; - -void CNotepad::KeyValue( KeyValueData *pkvd ) -{ - if (FStrEq(pkvd->szKeyName, "dmdelay")) - { - m_iReactivate = atoi(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "title")) - { - m_iTitle = atoi(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else - CBaseToggle::KeyValue( pkvd ); - - FStrEq(pkvd->szKeyName, "text"); - sprintf(m_iszText, "%s", pkvd->szValue); -} - -void CNotepad::Spawn() -{ - Precache( ); - - pev->solid = SOLID_BSP; - pev->movetype = MOVETYPE_PUSH; - - UTIL_SetOrigin(pev, pev->origin); // set size and link into world - UTIL_SetSize(pev, pev->mins, pev->maxs); - SET_MODEL(ENT(pev), STRING(pev->model) ); - m_iJuice = gSkillData.healthchargerCapacity; - pev->frame = 0; - -} - -void CNotepad::Precache() -{ -} - - -void CNotepad::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) -{ - // Make sure that we have a caller - if (!pActivator) - return; - // if it's not a player, ignore - if ( !pActivator->IsPlayer() ) - return; - - - pev->nextthink = pev->ltime + 0.25; - SetThink(&CNotepad::Off); - - // Time to recharge yet? - - if (m_flNextCharge >= gpGlobals->time) - return; - - MESSAGE_BEGIN( MSG_ONE, gmsgNotepad, NULL, GetClassPtr((CBasePlayer *)pActivator->pev)->pev ); - WRITE_STRING( m_iszText ); - WRITE_BYTE( m_iTitle ); - MESSAGE_END(); - - // govern the rate of charge - m_flNextCharge = gpGlobals->time + 0.1; -} - -void CNotepad::Recharge(void) -{ - pev->frame = 0; - SetThink( &CNotepad::SUB_DoNothing ); -} - -void CNotepad::Off(void) -{ - m_iOn = 0; - - if ((!m_iJuice) && ( ( m_iReactivate = g_pGameRules->FlHealthChargerRechargeTime() ) > 0) ) - { - pev->nextthink = pev->ltime + m_iReactivate; - SetThink(&CNotepad::Recharge); - } - else - SetThink( &CNotepad::SUB_DoNothing ); -} +#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" + + +class CNotepad : public CBaseToggle +{ +public: + void Spawn( ); + void Precache( void ); + void EXPORT Off(void); + void EXPORT Recharge(void); + void KeyValue( KeyValueData *pkvd ); + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + virtual int ObjectCaps( void ) { return (CBaseToggle :: ObjectCaps() | FCAP_CONTINUOUS_USE) & ~FCAP_ACROSS_TRANSITION; } + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + char m_iszText[256]; + int m_iTitle; + + static TYPEDESCRIPTION m_SaveData[]; + + float m_flNextCharge; + int m_iReactivate ; // DeathMatch Delay until reactvated + int m_iJuice; + int m_iOn; // 0 = off, 1 = startup, 2 = going + float m_flSoundTime; +}; + +TYPEDESCRIPTION CNotepad::m_SaveData[] = +{ + DEFINE_FIELD( CNotepad, m_flNextCharge, FIELD_TIME), + DEFINE_FIELD( CNotepad, m_iReactivate, FIELD_INTEGER), + DEFINE_FIELD( CNotepad, m_iJuice, FIELD_INTEGER), + DEFINE_FIELD( CNotepad, m_iOn, FIELD_INTEGER), + DEFINE_FIELD( CNotepad, m_flSoundTime, FIELD_TIME), +}; + +IMPLEMENT_SAVERESTORE( CNotepad, CBaseEntity ); + +LINK_ENTITY_TO_CLASS(func_notepad, CNotepad); + +extern int gmsgNotepad; +extern int gmsgSparePlayer; + +void CNotepad::KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "dmdelay")) + { + m_iReactivate = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "title")) + { + m_iTitle = atoi(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseToggle::KeyValue( pkvd ); + + FStrEq(pkvd->szKeyName, "text"); + sprintf(m_iszText, "%s", pkvd->szValue); +} + +void CNotepad::Spawn() +{ + Precache( ); + + pev->solid = SOLID_BSP; + pev->movetype = MOVETYPE_PUSH; + + UTIL_SetOrigin(pev, pev->origin); // set size and link into world + UTIL_SetSize(pev, pev->mins, pev->maxs); + SET_MODEL(ENT(pev), STRING(pev->model) ); + m_iJuice = gSkillData.healthchargerCapacity; + pev->frame = 0; + +} + +void CNotepad::Precache() +{ +} + + +void CNotepad::Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + // Make sure that we have a caller + if (!pActivator) + return; + // if it's not a player, ignore + if ( !pActivator->IsPlayer() ) + return; + + + pev->nextthink = pev->ltime + 0.25; + SetThink(&CNotepad::Off); + + // Time to recharge yet? + + if (m_flNextCharge >= gpGlobals->time) + return; + + MESSAGE_BEGIN( MSG_ONE, gmsgNotepad, NULL, GetClassPtr((CBasePlayer *)pActivator->pev)->pev ); + WRITE_STRING( m_iszText ); + WRITE_BYTE( m_iTitle ); + MESSAGE_END(); + + // govern the rate of charge + m_flNextCharge = gpGlobals->time + 0.1; +} + +void CNotepad::Recharge(void) +{ + pev->frame = 0; + SetThink( &CNotepad::SUB_DoNothing ); +} + +void CNotepad::Off(void) +{ + m_iOn = 0; + + if ((!m_iJuice) && ( ( m_iReactivate = g_pGameRules->FlHealthChargerRechargeTime() ) > 0) ) + { + pev->nextthink = pev->ltime + m_iReactivate; + SetThink(&CNotepad::Recharge); + } + else + SetThink( &CNotepad::SUB_DoNothing ); +} diff --git a/dlls/priest.cpp b/dlls/priest.cpp index ba14fd59..80522726 100644 --- a/dlls/priest.cpp +++ b/dlls/priest.cpp @@ -1,1408 +1,1408 @@ -/*** -* -* Copyright (c) 1996-2002, Valve LLC. All rights reserved. -* -* This product contains software technology licensed from Id -* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. -* All Rights Reserved. -* -* This source code contains proprietary and confidential information of -* Valve LLC and its suppliers. Access to this code is restricted to -* persons who have executed a written SDK license with Valve. Any access, -* use or distribution of this code by or to any unlicensed person is illegal. -* -****/ -#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) - -//========================================================= -// CONTROLLER -//========================================================= - -#include "extdll.h" -#include "util.h" -#include "cbase.h" -#include "monsters.h" -#include "effects.h" -#include "schedule.h" -#include "weapons.h" -#include "squadmonster.h" - -//========================================================= -// Monster's Anim Events Go Here -//========================================================= -#define CONTROLLER_AE_HEAD_OPEN 1 -#define CONTROLLER_AE_BALL_SHOOT 2 -#define CONTROLLER_AE_SMALL_SHOOT 3 -#define CONTROLLER_AE_POWERUP_FULL 4 -#define CONTROLLER_AE_POWERUP_HALF 5 - -#define CONTROLLER_FLINCH_DELAY 2 // at most one flinch every n secs - -#define SF_AUTO_FIREONCE 0x0001 // env_warpball's spawnflag - -class CPriest : public CSquadMonster -{ -public: - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - static TYPEDESCRIPTION m_SaveData[]; - - void Spawn( void ); - void Precache( void ); - void SetYawSpeed( void ); - int Classify ( void ); - void HandleAnimEvent( MonsterEvent_t *pEvent ); - - void RunAI( void ); - BOOL CheckRangeAttack1 ( float flDot, float flDist ); // balls - BOOL CheckRangeAttack2 ( float flDot, float flDist ); // head - BOOL CheckMeleeAttack1 ( float flDot, float flDist ); // block, throw - Schedule_t* GetSchedule ( void ); - Schedule_t* GetScheduleOfType ( int Type ); - void StartTask ( Task_t *pTask ); - void RunTask ( Task_t *pTask ); - CUSTOM_SCHEDULES; - - void Stop( void ); - void Move ( float flInterval ); - int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ); - void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); - void SetActivity ( Activity NewActivity ); - BOOL ShouldAdvanceRoute( float flWaypointDist ); - int LookupFloat( ); - - float m_flNextFlinch; - - float m_flShootTime; - float m_flShootEnd; - - void PainSound( void ); - void AlertSound( void ); - void IdleSound( void ); - void AttackSound( void ); - void DeathSound( void ); - - static const char *pAttackSounds[]; - static const char *pIdleSounds[]; - static const char *pAlertSounds[]; - static const char *pPainSounds[]; - static const char *pDeathSounds[]; - - int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); - void Killed( entvars_t *pevAttacker, int iGib ); - void GibMonster( void ); - - CSprite *m_pBall[2]; // hand balls - int m_iBall[2]; // how bright it should be - float m_iBallTime[2]; // when it should be that color - int m_iBallCurrent[2]; // current brightness - - Vector m_vecEstVelocity; - - Vector m_velocity; - int m_fInCombat; -}; - -LINK_ENTITY_TO_CLASS( monster_alien_priest, CPriest ); - -TYPEDESCRIPTION CPriest::m_SaveData[] = -{ - DEFINE_ARRAY( CPriest, m_pBall, FIELD_CLASSPTR, 2 ), - DEFINE_ARRAY( CPriest, m_iBall, FIELD_INTEGER, 2 ), - DEFINE_ARRAY( CPriest, m_iBallTime, FIELD_TIME, 2 ), - DEFINE_ARRAY( CPriest, m_iBallCurrent, FIELD_INTEGER, 2 ), - DEFINE_FIELD( CPriest, m_vecEstVelocity, FIELD_VECTOR ), -}; -IMPLEMENT_SAVERESTORE( CPriest, CSquadMonster ); - - -const char *CPriest::pAttackSounds[] = -{ - "controller/con_attack1.wav", - "controller/con_attack2.wav", - "controller/con_attack3.wav", -}; - -const char *CPriest::pIdleSounds[] = -{ - "controller/con_idle1.wav", - "controller/con_idle2.wav", - "controller/con_idle3.wav", - "controller/con_idle4.wav", - "controller/con_idle5.wav", -}; - -const char *CPriest::pAlertSounds[] = -{ - "controller/con_alert1.wav", - "controller/con_alert2.wav", - "controller/con_alert3.wav", -}; - -const char *CPriest::pPainSounds[] = -{ - "controller/con_pain1.wav", - "controller/con_pain2.wav", - "controller/con_pain3.wav", -}; - -const char *CPriest::pDeathSounds[] = -{ - "controller/con_die1.wav", - "controller/con_die2.wav", -}; - - -//========================================================= -// Classify - indicates this monster's place in the -// relationship table. -//========================================================= -int CPriest :: Classify ( void ) -{ - return CLASS_ALIEN_MILITARY; -} - -//========================================================= -// SetYawSpeed - allows each sequence to have a different -// turn rate associated with it. -//========================================================= -void CPriest :: SetYawSpeed ( void ) -{ - int ys; - - ys = 120; - -#if 0 - switch ( m_Activity ) - { - } -#endif - - pev->yaw_speed = ys; -} - -int CPriest :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) -{ - // HACK HACK -- until we fix this. - if ( IsAlive() ) - PainSound(); - - ALERT( at_console, "Priest damaged - took %f hit points!\n", flDamage); - - if (flDamage > 20.0) - { - - } - - return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); -} - - -void CPriest::Killed( entvars_t *pevAttacker, int iGib ) -{ - // shut off balls - /* - m_iBall[0] = 0; - m_iBallTime[0] = gpGlobals->time + 4.0; - m_iBall[1] = 0; - m_iBallTime[1] = gpGlobals->time + 4.0; - */ - - // fade balls - if (m_pBall[0]) - { - m_pBall[0]->SUB_StartFadeOut(); - m_pBall[0] = NULL; - } - if (m_pBall[1]) - { - m_pBall[1]->SUB_StartFadeOut(); - m_pBall[1] = NULL; - } - - CSquadMonster::Killed( pevAttacker, iGib ); -} - - -void CPriest::GibMonster( void ) -{ - // delete balls - if (m_pBall[0]) - { - UTIL_Remove( m_pBall[0] ); - m_pBall[0] = NULL; - } - if (m_pBall[1]) - { - UTIL_Remove( m_pBall[1] ); - m_pBall[1] = NULL; - } - CSquadMonster::GibMonster( ); -} - - - - -void CPriest :: PainSound( void ) -{ - if (RANDOM_LONG(0,5) < 2) - EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); -} - -void CPriest :: AlertSound( void ) -{ - EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAlertSounds ); -} - -void CPriest :: IdleSound( void ) -{ - EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pIdleSounds ); -} - -void CPriest :: AttackSound( void ) -{ - EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAttackSounds ); -} - -void CPriest :: DeathSound( void ) -{ - EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pDeathSounds ); -} - -//========================================================= -// HandleAnimEvent - catches the monster-specific messages -// that occur when tagged animation frames are played. -//========================================================= -void CPriest :: HandleAnimEvent( MonsterEvent_t *pEvent ) -{ - switch( pEvent->event ) - { - case CONTROLLER_AE_HEAD_OPEN: - { - Vector vecStart, angleGun; - - GetAttachment( 0, vecStart, angleGun ); - - MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); - WRITE_BYTE( TE_ELIGHT ); - WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment - WRITE_COORD( vecStart.x ); // origin - WRITE_COORD( vecStart.y ); - WRITE_COORD( vecStart.z ); - WRITE_COORD( 1 ); // radius - WRITE_BYTE( 255 ); // R - WRITE_BYTE( 192 ); // G - WRITE_BYTE( 64 ); // B - WRITE_BYTE( 20 ); // life * 10 - WRITE_COORD( -32 ); // decay - MESSAGE_END(); - - m_iBall[0] = 192; - m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; - m_iBall[1] = 255; - m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; - - } - break; - - case CONTROLLER_AE_BALL_SHOOT: - { - Vector vecStart, angleGun; - - GetAttachment( 0, vecStart, angleGun ); - - MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); - WRITE_BYTE( TE_ELIGHT ); - WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment - WRITE_COORD( 0 ); // origin - WRITE_COORD( 0 ); - WRITE_COORD( 0 ); - WRITE_COORD( 32 ); // radius - WRITE_BYTE( 255 ); // R - WRITE_BYTE( 192 ); // G - WRITE_BYTE( 64 ); // B - WRITE_BYTE( 10 ); // life * 10 - WRITE_COORD( 32 ); // decay - MESSAGE_END(); - - CBaseMonster *pBall = (CBaseMonster*)Create( "priest_head_ball", vecStart, pev->angles, edict() ); - - pBall->pev->velocity = Vector( 0, 0, 32 ); - pBall->m_hEnemy = m_hEnemy; - - m_iBall[0] = 0; - m_iBall[1] = 0; - } - break; - - case CONTROLLER_AE_SMALL_SHOOT: - { - AttackSound( ); - m_flShootTime = gpGlobals->time; - m_flShootEnd = m_flShootTime + atoi( pEvent->options ) / 15.0; - } - break; - case CONTROLLER_AE_POWERUP_FULL: - { - m_iBall[0] = 255; - m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; - m_iBall[1] = 255; - m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; - } - break; - case CONTROLLER_AE_POWERUP_HALF: - { - m_iBall[0] = 192; - m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; - m_iBall[1] = 192; - m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; - } - break; - default: - CBaseMonster::HandleAnimEvent( pEvent ); - break; - } -} - -//========================================================= -// Spawn -//========================================================= -void CPriest :: Spawn() -{ - Precache( ); - - SET_MODEL(ENT(pev), "models/controller.mdl"); - UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 )); - - pev->solid = SOLID_SLIDEBOX; - pev->movetype = MOVETYPE_FLY; - pev->flags |= FL_FLY; - m_bloodColor = BLOOD_COLOR_GREEN; - pev->health = gSkillData.controllerHealth; - pev->view_ofs = Vector( 0, 0, -2 );// position of the eyes relative to monster's origin. - m_flFieldOfView = VIEW_FIELD_FULL;// indicates the width of this monster's forward view cone ( as a dotproduct result ) - m_MonsterState = MONSTERSTATE_NONE; - - MonsterInit(); -} - -//========================================================= -// Precache - precaches all resources this monster needs -//========================================================= -void CPriest :: Precache() -{ - PRECACHE_MODEL("models/controller.mdl"); - - PRECACHE_SOUND_ARRAY( pAttackSounds ); - PRECACHE_SOUND_ARRAY( pIdleSounds ); - PRECACHE_SOUND_ARRAY( pAlertSounds ); - PRECACHE_SOUND_ARRAY( pPainSounds ); - PRECACHE_SOUND_ARRAY( pDeathSounds ); - - PRECACHE_MODEL( "sprites/flare6.spr"); - - UTIL_PrecacheOther( "priest_energy_ball" ); - UTIL_PrecacheOther( "priest_head_ball" ); - UTIL_PrecacheOther( "env_warpball"); -} - -//========================================================= -// AI Schedules Specific to this monster -//========================================================= - - -// Chase enemy schedule -Task_t tlPriestChaseEnemy[] = -{ - { TASK_GET_PATH_TO_ENEMY, (float)128 }, - { TASK_WAIT_FOR_MOVEMENT, (float)0 }, - -}; - -Schedule_t slPriestChaseEnemy[] = -{ - { - tlPriestChaseEnemy, - ARRAYSIZE ( tlPriestChaseEnemy ), - bits_COND_NEW_ENEMY | - bits_COND_TASK_FAILED, - 0, - "ControllerChaseEnemy" - }, -}; - - - -Task_t tlPriestStrafe[] = -{ - { TASK_WAIT, (float)0.2 }, - { TASK_GET_PATH_TO_ENEMY, (float)128 }, - { TASK_WAIT_FOR_MOVEMENT, (float)0 }, - { TASK_WAIT, (float)1 }, -}; - -Schedule_t slPriestStrafe[] = -{ - { - tlPriestStrafe, - ARRAYSIZE ( tlPriestStrafe ), - bits_COND_NEW_ENEMY, - 0, - "ControllerStrafe" - }, -}; - - -Task_t tlPriestTakeCover[] = -{ - { TASK_WAIT, (float)0.2 }, - { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, - { TASK_WAIT_FOR_MOVEMENT, (float)0 }, - { TASK_WAIT, (float)1 }, -}; - -Schedule_t slPriestTakeCover[] = -{ - { - tlPriestTakeCover, - ARRAYSIZE ( tlPriestTakeCover ), - bits_COND_NEW_ENEMY, - 0, - "ControllerTakeCover" - }, -}; - - -Task_t tlPriestFail[] = -{ - { TASK_STOP_MOVING, 0 }, - { TASK_SET_ACTIVITY, (float)ACT_IDLE }, - { TASK_WAIT, (float)2 }, - { TASK_WAIT_PVS, (float)0 }, -}; - -Schedule_t slPriestFail[] = -{ - { - tlPriestFail, - ARRAYSIZE ( tlPriestFail ), - 0, - 0, - "ControllerFail" - }, -}; - -DEFINE_CUSTOM_SCHEDULES( CPriest ) -{ - slPriestChaseEnemy, - slPriestStrafe, - slPriestTakeCover, - slPriestFail, -}; - -IMPLEMENT_CUSTOM_SCHEDULES( CPriest, CSquadMonster ); - - - -//========================================================= -// StartTask -//========================================================= -void CPriest :: StartTask ( Task_t *pTask ) -{ - switch ( pTask->iTask ) - { - case TASK_RANGE_ATTACK1: - CSquadMonster :: StartTask ( pTask ); - break; - case TASK_GET_PATH_TO_ENEMY_LKP: - { - if (BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, pTask->flData, (m_vecEnemyLKP - pev->origin).Length() + 1024 )) - { - TaskComplete(); - } - else - { - // no way to get there =( - ALERT ( at_aiconsole, "GetPathToEnemyLKP failed!!\n" ); - TaskFail(); - } - break; - } - case TASK_GET_PATH_TO_ENEMY: - { - CBaseEntity *pEnemy = m_hEnemy; - - if ( pEnemy == NULL ) - { - TaskFail(); - return; - } - - if (BuildNearestRoute( pEnemy->pev->origin, pEnemy->pev->view_ofs, pTask->flData, (pEnemy->pev->origin - pev->origin).Length() + 1024 )) - { - TaskComplete(); - } - else - { - // no way to get there =( - ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); - TaskFail(); - } - break; - } - default: - CSquadMonster :: StartTask ( pTask ); - break; - } -} - -int CPriest::LookupFloat( ) -{ - if (m_velocity.Length( ) < 32.0) - { - return LookupSequence( "up" ); - } - - UTIL_MakeAimVectors( pev->angles ); - float x = DotProduct( gpGlobals->v_forward, m_velocity ); - float y = DotProduct( gpGlobals->v_right, m_velocity ); - float z = DotProduct( gpGlobals->v_up, m_velocity ); - - if (fabs(x) > fabs(y) && fabs(x) > fabs(z)) - { - if (x > 0) - return LookupSequence( "forward"); - else - return LookupSequence( "backward"); - } - else if (fabs(y) > fabs(z)) - { - if (y > 0) - return LookupSequence( "right"); - else - return LookupSequence( "left"); - } - else - { - if (z > 0) - return LookupSequence( "up"); - else - return LookupSequence( "down"); - } -} - - -//========================================================= -// RunTask -//========================================================= -void CPriest :: RunTask ( Task_t *pTask ) -{ - - if (m_flShootEnd > gpGlobals->time) - { - Vector vecHand, vecAngle; - - GetAttachment( 2, vecHand, vecAngle ); - - while (m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->time) - { - Vector vecSrc = vecHand + pev->velocity * (m_flShootTime - gpGlobals->time); - Vector vecDir; - - if (m_hEnemy != NULL) - { - if (HasConditions( bits_COND_SEE_ENEMY )) - { - m_vecEstVelocity = m_vecEstVelocity * 0.5 + m_hEnemy->pev->velocity * 0.5; - } - else - { - m_vecEstVelocity = m_vecEstVelocity * 0.8; - } - vecDir = UTIL_Intersect( vecSrc, m_hEnemy->BodyTarget( pev->origin ), m_vecEstVelocity, gSkillData.controllerSpeedBall ); - float delta = 0.03490; // +-2 degree - vecDir = vecDir + Vector( RANDOM_FLOAT( -delta, delta ), RANDOM_FLOAT( -delta, delta ), RANDOM_FLOAT( -delta, delta ) ) * gSkillData.controllerSpeedBall; - - vecSrc = vecSrc + vecDir * (gpGlobals->time - m_flShootTime); - CBaseMonster *pBall = (CBaseMonster*)Create( "priest_energy_ball", vecSrc, pev->angles, edict() ); - pBall->pev->velocity = vecDir; - } - m_flShootTime += 0.2; - } - - if (m_flShootTime > m_flShootEnd) - { - m_iBall[0] = 64; - m_iBallTime[0] = m_flShootEnd; - m_iBall[1] = 64; - m_iBallTime[1] = m_flShootEnd; - m_fInCombat = FALSE; - } - } - - switch ( pTask->iTask ) - { - case TASK_WAIT_FOR_MOVEMENT: - case TASK_WAIT: - case TASK_WAIT_FACE_ENEMY: - case TASK_WAIT_PVS: - MakeIdealYaw( m_vecEnemyLKP ); - ChangeYaw( pev->yaw_speed ); - - if (m_fSequenceFinished) - { - m_fInCombat = FALSE; - } - - CSquadMonster :: RunTask ( pTask ); - - if (!m_fInCombat) - { - if (HasConditions ( bits_COND_CAN_RANGE_ATTACK1 )) - { - pev->sequence = LookupActivity( ACT_RANGE_ATTACK1 ); - pev->frame = 0; - ResetSequenceInfo( ); - m_fInCombat = TRUE; - } - else if (HasConditions ( bits_COND_CAN_RANGE_ATTACK2 )) - { - pev->sequence = LookupActivity( ACT_RANGE_ATTACK2 ); - pev->frame = 0; - ResetSequenceInfo( ); - m_fInCombat = TRUE; - } - else - { - int iFloat = LookupFloat( ); - if (m_fSequenceFinished || iFloat != pev->sequence) - { - pev->sequence = iFloat; - pev->frame = 0; - ResetSequenceInfo( ); - } - } - } - break; - default: - CSquadMonster :: RunTask ( pTask ); - break; - } -} - - -//========================================================= -// GetSchedule - Decides which type of schedule best suits -// the monster's current state and conditions. Then calls -// monster's member function to get a pointer to a schedule -// of the proper type. -//========================================================= -Schedule_t *CPriest :: GetSchedule ( void ) -{ - switch ( m_MonsterState ) - { - case MONSTERSTATE_IDLE: - break; - - case MONSTERSTATE_ALERT: - break; - - case MONSTERSTATE_COMBAT: - { - Vector vecTmp = UTIL_Intersect( Vector( 0, 0, 0 ), Vector( 100, 4, 7 ), Vector( 2, 10, -3 ), 20.0 ); - - // dead enemy - if ( HasConditions ( bits_COND_LIGHT_DAMAGE ) ) - { - // m_iFrustration++; - } - if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) - { - // m_iFrustration++; - } - } - break; - } - - return CSquadMonster :: GetSchedule(); -} - - - -//========================================================= -//========================================================= -Schedule_t* CPriest :: GetScheduleOfType ( int Type ) -{ - // ALERT( at_console, "%d\n", m_iFrustration ); - switch ( Type ) - { - case SCHED_CHASE_ENEMY: - return slPriestChaseEnemy; - case SCHED_RANGE_ATTACK1: - return slPriestStrafe; - case SCHED_RANGE_ATTACK2: - case SCHED_MELEE_ATTACK1: - case SCHED_MELEE_ATTACK2: - case SCHED_TAKE_COVER_FROM_ENEMY: - return slPriestTakeCover; - case SCHED_FAIL: - return slPriestFail; - } - - return CBaseMonster :: GetScheduleOfType( Type ); -} - - - - - -//========================================================= -// CheckRangeAttack1 - shoot a bigass energy ball out of their head -// -//========================================================= -BOOL CPriest :: CheckRangeAttack1 ( float flDot, float flDist ) -{ - if ( flDot > 0.5 && flDist > 256 && flDist <= 2048 ) - { - return TRUE; - } - return FALSE; -} - - -BOOL CPriest :: CheckRangeAttack2 ( float flDot, float flDist ) -{ - if ( flDot > 0.5 && flDist > 64 && flDist <= 2048 ) - { - return TRUE; - } - return FALSE; -} - - -BOOL CPriest :: CheckMeleeAttack1 ( float flDot, float flDist ) -{ - return FALSE; -} - - -void CPriest :: SetActivity ( Activity NewActivity ) -{ - CBaseMonster::SetActivity( NewActivity ); - - switch ( m_Activity) - { - case ACT_WALK: - m_flGroundSpeed = 100; - break; - default: - m_flGroundSpeed = 100; - break; - } -} - - - -//========================================================= -// RunAI -//========================================================= -void CPriest :: RunAI( void ) -{ - CBaseMonster :: RunAI(); - Vector vecStart, angleGun; - - if ( HasMemory( bits_MEMORY_KILLED ) ) - return; - - for (int i = 0; i < 2; i++) - { - if (m_pBall[i] == NULL) - { - m_pBall[i] = CSprite::SpriteCreate( "sprites/flare6.spr", pev->origin, TRUE ); - m_pBall[i]->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone ); - m_pBall[i]->SetAttachment( edict(), (i + 3) ); - m_pBall[i]->SetScale( 0.4 ); - } - - float t = m_iBallTime[i] - gpGlobals->time; - if (t > 0.1) - t = 0.1 / t; - else - t = 1.0; - - m_iBallCurrent[i] += (m_iBall[i] - m_iBallCurrent[i]) * t; - - m_pBall[i]->SetBrightness( m_iBallCurrent[i] ); - - GetAttachment( i + 2, vecStart, angleGun ); - UTIL_SetOrigin( m_pBall[i]->pev, vecStart ); - - MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); - WRITE_BYTE( TE_ELIGHT ); - WRITE_SHORT( entindex( ) + 0x1000 * (i + 3) ); // entity, attachment - WRITE_COORD( vecStart.x ); // origin - WRITE_COORD( vecStart.y ); - WRITE_COORD( vecStart.z ); - WRITE_COORD( m_iBallCurrent[i] / 8 ); // radius - WRITE_BYTE( 255 ); // R - WRITE_BYTE( 192 ); // G - WRITE_BYTE( 64 ); // B - WRITE_BYTE( 5 ); // life * 10 - WRITE_COORD( 0 ); // decay - MESSAGE_END(); - } -} - - -extern void DrawRoute( entvars_t *pev, WayPoint_t *m_Route, int m_iRouteIndex, int r, int g, int b ); - -void CPriest::Stop( void ) -{ - m_IdealActivity = GetStoppedActivity(); -} - - -#define DIST_TO_CHECK 200 -void CPriest :: Move ( float flInterval ) -{ - float flWaypointDist; - float flCheckDist; - float flDist;// how far the lookahead check got before hitting an object. - float flMoveDist; - Vector vecDir; - Vector vecApex; - CBaseEntity *pTargetEnt; - - // Don't move if no valid route - if ( FRouteClear() ) - { - ALERT( at_aiconsole, "Tried to move with no route!\n" ); - TaskFail(); - return; - } - - if ( m_flMoveWaitFinished > gpGlobals->time ) - return; - -// Debug, test movement code -#if 0 -// if ( CVAR_GET_FLOAT("stopmove" ) != 0 ) - { - if ( m_movementGoal == MOVEGOAL_ENEMY ) - RouteSimplify( m_hEnemy ); - else - RouteSimplify( m_hTargetEnt ); - FRefreshRoute(); - return; - } -#else -// Debug, draw the route -// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 0, 255 ); -#endif - - // if the monster is moving directly towards an entity (enemy for instance), we'll set this pointer - // to that entity for the CheckLocalMove and Triangulate functions. - pTargetEnt = NULL; - - if (m_flGroundSpeed == 0) - { - m_flGroundSpeed = 100; - // TaskFail( ); - // return; - } - - flMoveDist = m_flGroundSpeed * flInterval; - - do - { - // local move to waypoint. - vecDir = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Normalize(); - flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length(); - - // MakeIdealYaw ( m_Route[ m_iRouteIndex ].vecLocation ); - // ChangeYaw ( pev->yaw_speed ); - - // if the waypoint is closer than CheckDist, CheckDist is the dist to waypoint - if ( flWaypointDist < DIST_TO_CHECK ) - { - flCheckDist = flWaypointDist; - } - else - { - flCheckDist = DIST_TO_CHECK; - } - - if ( (m_Route[ m_iRouteIndex ].iType & (~bits_MF_NOT_TO_MASK)) == bits_MF_TO_ENEMY ) - { - // only on a PURE move to enemy ( i.e., ONLY MF_TO_ENEMY set, not MF_TO_ENEMY and DETOUR ) - pTargetEnt = m_hEnemy; - } - else if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_TARGETENT ) - { - pTargetEnt = m_hTargetEnt; - } - - // !!!BUGBUG - CheckDist should be derived from ground speed. - // If this fails, it should be because of some dynamic entity blocking this guy. - // We've already checked this path, so we should wait and time out if the entity doesn't move - flDist = 0; - if ( CheckLocalMove ( pev->origin, pev->origin + vecDir * flCheckDist, pTargetEnt, &flDist ) != LOCALMOVE_VALID ) - { - CBaseEntity *pBlocker; - - // Can't move, stop - Stop(); - // Blocking entity is in global trace_ent - pBlocker = CBaseEntity::Instance( gpGlobals->trace_ent ); - if (pBlocker) - { - DispatchBlocked( edict(), pBlocker->edict() ); - } - if ( pBlocker && m_moveWaitTime > 0 && pBlocker->IsMoving() && !pBlocker->IsPlayer() && (gpGlobals->time-m_flMoveWaitFinished) > 3.0 ) - { - // Can we still move toward our target? - if ( flDist < m_flGroundSpeed ) - { - // Wait for a second - m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime; - // ALERT( at_aiconsole, "Move %s!!!\n", STRING( pBlocker->pev->classname ) ); - return; - } - } - else - { - // try to triangulate around whatever is in the way. - if ( FTriangulate( pev->origin, m_Route[ m_iRouteIndex ].vecLocation, flDist, pTargetEnt, &vecApex ) ) - { - InsertWaypoint( vecApex, bits_MF_TO_DETOUR ); - RouteSimplify( pTargetEnt ); - } - else - { - ALERT ( at_aiconsole, "Couldn't Triangulate\n" ); - Stop(); - if ( m_moveWaitTime > 0 ) - { - FRefreshRoute(); - m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime * 0.5; - } - else - { - TaskFail(); - ALERT( at_aiconsole, "Failed to move!\n" ); - //ALERT( at_aiconsole, "%f, %f, %f\n", pev->origin.z, (pev->origin + (vecDir * flCheckDist)).z, m_Route[m_iRouteIndex].vecLocation.z ); - } - return; - } - } - } - - // UNDONE: this is a hack to quit moving farther than it has looked ahead. - if (flCheckDist < flMoveDist) - { - MoveExecute( pTargetEnt, vecDir, flCheckDist / m_flGroundSpeed ); - - // ALERT( at_console, "%.02f\n", flInterval ); - AdvanceRoute( flWaypointDist ); - flMoveDist -= flCheckDist; - } - else - { - MoveExecute( pTargetEnt, vecDir, flMoveDist / m_flGroundSpeed ); - - if ( ShouldAdvanceRoute( flWaypointDist - flMoveDist ) ) - { - AdvanceRoute( flWaypointDist ); - } - flMoveDist = 0; - } - - if ( MovementIsComplete() ) - { - Stop(); - RouteClear(); - } - } while (flMoveDist > 0 && flCheckDist > 0); - - // cut corner? - if (flWaypointDist < 128) - { - if ( m_movementGoal == MOVEGOAL_ENEMY ) - RouteSimplify( m_hEnemy ); - else - RouteSimplify( m_hTargetEnt ); - FRefreshRoute(); - - if (m_flGroundSpeed > 100) - m_flGroundSpeed -= 40; - } - else - { - if (m_flGroundSpeed < 400) - m_flGroundSpeed += 10; - } -} - - - -BOOL CPriest:: ShouldAdvanceRoute( float flWaypointDist ) -{ - if ( flWaypointDist <= 32 ) - { - return TRUE; - } - - return FALSE; -} - - -int CPriest :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) -{ - TraceResult tr; - - UTIL_TraceHull( vecStart + Vector( 0, 0, 32), vecEnd + Vector( 0, 0, 32), dont_ignore_monsters, large_hull, edict(), &tr ); - - // ALERT( at_console, "%.0f %.0f %.0f : ", vecStart.x, vecStart.y, vecStart.z ); - // ALERT( at_console, "%.0f %.0f %.0f\n", vecEnd.x, vecEnd.y, vecEnd.z ); - - if (pflDist) - { - *pflDist = ( (tr.vecEndPos - Vector( 0, 0, 32 )) - vecStart ).Length();// get the distance. - } - - // ALERT( at_console, "check %d %d %f\n", tr.fStartSolid, tr.fAllSolid, tr.flFraction ); - if (tr.fStartSolid || tr.flFraction < 1.0) - { - if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) - return LOCALMOVE_VALID; - return LOCALMOVE_INVALID; - } - - return LOCALMOVE_VALID; -} - - -void CPriest::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) -{ - if ( m_IdealActivity != m_movementActivity ) - m_IdealActivity = m_movementActivity; - - // ALERT( at_console, "move %.4f %.4f %.4f : %f\n", vecDir.x, vecDir.y, vecDir.z, flInterval ); - - // float flTotal = m_flGroundSpeed * pev->framerate * flInterval; - // UTIL_MoveToOrigin ( ENT(pev), m_Route[ m_iRouteIndex ].vecLocation, flTotal, MOVE_STRAFE ); - - m_velocity = m_velocity * 0.8 + m_flGroundSpeed * vecDir * 0.2; - - UTIL_MoveToOrigin ( ENT(pev), pev->origin + m_velocity, m_velocity.Length() * flInterval, MOVE_STRAFE ); - -} - - - - -//========================================================= -// Controller bouncy ball attack -//========================================================= -class CPriestHeadBall : public CBaseMonster -{ - void Spawn( void ); - void Precache( void ); - void EXPORT HuntThink( void ); - void EXPORT DieThink( void ); - void EXPORT BounceTouch( CBaseEntity *pOther ); - void MovetoTarget( Vector vecTarget ); - void Crawl( void ); - int m_iTrail; - int m_flNextAttack; - Vector m_vecIdeal; - EHANDLE m_hOwner; -}; -LINK_ENTITY_TO_CLASS( priest_head_ball, CPriestHeadBall ); - - - -void CPriestHeadBall :: Spawn( void ) -{ - Precache( ); - // motor - pev->movetype = MOVETYPE_FLY; - pev->solid = SOLID_BBOX; - - SET_MODEL(ENT(pev), "sprites/flare6.spr"); - pev->rendermode = kRenderTransAdd; - pev->rendercolor.x = 128; - pev->rendercolor.y = 255; - pev->rendercolor.z = 128; - pev->renderamt = 255; - pev->scale = 0.5; - - UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); - UTIL_SetOrigin( pev, pev->origin ); - - SetThink( &CPriestHeadBall::HuntThink ); - SetTouch( &CPriestHeadBall::BounceTouch ); - - m_vecIdeal = Vector( 0, 0, 0 ); - - pev->nextthink = gpGlobals->time + 0.1; - - m_hOwner = Instance( pev->owner ); - pev->dmgtime = gpGlobals->time; -} - - -void CPriestHeadBall :: Precache( void ) -{ - PRECACHE_MODEL("sprites/flare6.spr"); - PRECACHE_SOUND("debris/zap4.wav"); - PRECACHE_SOUND("debris/beamstart9.wav"); -} - -void CPriestHeadBall :: HuntThink( void ) -{ - pev->frame = ((int)pev->frame + 1) % 11; - - pev->nextthink = gpGlobals->time + 0.1; - - pev->renderamt -= 5; - - MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); - WRITE_BYTE( TE_ELIGHT ); - WRITE_SHORT( entindex( ) ); // entity, attachment - WRITE_COORD( pev->origin.x ); // origin - WRITE_COORD( pev->origin.y ); - WRITE_COORD( pev->origin.z ); - WRITE_COORD( pev->renderamt / 16 ); // radius - WRITE_BYTE( 255 ); // R - WRITE_BYTE( 255 ); // G - WRITE_BYTE( 255 ); // B - WRITE_BYTE( 2 ); // life * 10 - WRITE_COORD( 0 ); // decay - MESSAGE_END(); - - // check world boundaries - if (gpGlobals->time - pev->dmgtime > 5 || pev->renderamt < 64 || m_hEnemy == NULL || m_hOwner == NULL || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) - { - SetTouch( NULL ); - UTIL_Remove( this ); - return; - } - - MovetoTarget( m_hEnemy->Center( ) ); - - if ((m_hEnemy->Center() - pev->origin).Length() < 64) - { - TraceResult tr; - - UTIL_TraceLine( pev->origin, m_hEnemy->Center(), dont_ignore_monsters, ENT(pev), &tr ); - - CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); - if (pEntity != NULL && pEntity->pev->takedamage) - { - ClearMultiDamage( ); - pEntity->TraceAttack( m_hOwner->pev, gSkillData.controllerDmgZap, pev->velocity, &tr, DMG_SHOCK ); - ApplyMultiDamage( pev, m_hOwner->pev ); - } - - CBaseEntity *pBall = (CBaseEntity*)Create( "env_warpball", tr.vecEndPos, pev->angles, edict() ); - - pBall->pev->velocity = Vector( 0, 0, 32 ); - pBall->pev->spawnflags = SF_AUTO_FIREONCE; - pBall->Use( this, this, USE_TOGGLE, 0); - /* - MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); - WRITE_BYTE( TE_BEAMENTPOINT ); - WRITE_SHORT( entindex() ); - WRITE_COORD( tr.vecEndPos.x ); - WRITE_COORD( tr.vecEndPos.y ); - WRITE_COORD( tr.vecEndPos.z ); - WRITE_SHORT( g_sModelIndexLaser ); - WRITE_BYTE( 0 ); // frame start - WRITE_BYTE( 10 ); // framerate - WRITE_BYTE( 3 ); // life - WRITE_BYTE( 20 ); // width - WRITE_BYTE( 0 ); // noise - WRITE_BYTE( 255 ); // r, g, b - WRITE_BYTE( 255 ); // r, g, b - WRITE_BYTE( 255 ); // r, g, b - WRITE_BYTE( 255 ); // brightness - WRITE_BYTE( 10 ); // speed - MESSAGE_END(); - */ - UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "debris/beamstart9.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); - - m_flNextAttack = gpGlobals->time + 3.0; - - SetThink( &CPriestHeadBall::DieThink ); - pev->nextthink = gpGlobals->time + 0.3; - } - // Crawl( ); -} - -void CPriestHeadBall :: DieThink( void ) -{ - UTIL_Remove( this ); -} - - -void CPriestHeadBall :: MovetoTarget( Vector vecTarget ) -{ - // accelerate - float flSpeed = m_vecIdeal.Length(); - if (flSpeed == 0) - { - m_vecIdeal = pev->velocity; - flSpeed = m_vecIdeal.Length(); - } - - if (flSpeed > 400) - { - m_vecIdeal = m_vecIdeal.Normalize( ) * 400; - } - m_vecIdeal = m_vecIdeal + (vecTarget - pev->origin).Normalize() * 100; - pev->velocity = m_vecIdeal; -} - - - -void CPriestHeadBall :: Crawl( void ) -{ - - Vector vecAim = Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ) ).Normalize( ); - Vector vecPnt = pev->origin + pev->velocity * 0.3 + vecAim * 64; - - CBaseEntity *pBall = (CBaseEntity*)Create( "env_warpball", vecPnt, pev->angles, edict() ); - - pBall->pev->velocity = Vector( 0, 0, 32 ); - pBall->pev->spawnflags = SF_AUTO_FIREONCE; - pBall->Use( this, this, USE_TOGGLE, 0); -/* - MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); - WRITE_BYTE( TE_BEAMENTPOINT ); - WRITE_SHORT( entindex() ); - WRITE_COORD( vecPnt.x); - WRITE_COORD( vecPnt.y); - WRITE_COORD( vecPnt.z); - WRITE_SHORT( g_sModelIndexLaser ); - WRITE_BYTE( 0 ); // frame start - WRITE_BYTE( 10 ); // framerate - WRITE_BYTE( 3 ); // life - WRITE_BYTE( 20 ); // width - WRITE_BYTE( 0 ); // noise - WRITE_BYTE( 255 ); // r, g, b - WRITE_BYTE( 255 ); // r, g, b - WRITE_BYTE( 255 ); // r, g, b - WRITE_BYTE( 255 ); // brightness - WRITE_BYTE( 10 ); // speed - MESSAGE_END(); */ -} - - -void CPriestHeadBall::BounceTouch( CBaseEntity *pOther ) -{ - Vector vecDir = m_vecIdeal.Normalize( ); - - TraceResult tr = UTIL_GetGlobalTrace( ); - - float n = -DotProduct(tr.vecPlaneNormal, vecDir); - - vecDir = 2.0 * tr.vecPlaneNormal * n + vecDir; - - m_vecIdeal = vecDir * m_vecIdeal.Length(); -} - - - - -class CPriestZapBall : public CBaseMonster -{ - void Spawn( void ); - void Precache( void ); - void EXPORT AnimateThink( void ); - void EXPORT ExplodeTouch( CBaseEntity *pOther ); - - EHANDLE m_hOwner; -}; -LINK_ENTITY_TO_CLASS( priest_energy_ball, CPriestZapBall ); - - -void CPriestZapBall :: Spawn( void ) -{ - Precache( ); - // motor - pev->movetype = MOVETYPE_FLY; - pev->solid = SOLID_BBOX; - - SET_MODEL(ENT(pev), "sprites/flare6.spr"); - pev->rendermode = kRenderTransAdd; - pev->rendercolor.x = 255; - pev->rendercolor.y = 255; - pev->rendercolor.z = 255; - pev->renderamt = 255; - pev->scale = 0.5; - - UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); - UTIL_SetOrigin( pev, pev->origin ); - - SetThink( &CPriestZapBall::AnimateThink ); - SetTouch( &CPriestZapBall::ExplodeTouch ); - - m_hOwner = Instance( pev->owner ); - pev->dmgtime = gpGlobals->time; // keep track of when ball spawned - pev->nextthink = gpGlobals->time + 0.1; -} - - -void CPriestZapBall :: Precache( void ) -{ - PRECACHE_MODEL("sprites/flare6.spr"); - // PRECACHE_SOUND("debris/zap4.wav"); - // PRECACHE_SOUND("weapons/electro4.wav"); -} - - -void CPriestZapBall :: AnimateThink( void ) -{ - pev->nextthink = gpGlobals->time + 0.1; - - pev->frame = ((int)pev->frame + 1) % 11; - - if (gpGlobals->time - pev->dmgtime > 5 || pev->velocity.Length() < 10) - { - SetTouch( NULL ); - UTIL_Remove( this ); - } -} - - -void CPriestZapBall::ExplodeTouch( CBaseEntity *pOther ) -{ - if (pOther->pev->takedamage) - { - TraceResult tr = UTIL_GetGlobalTrace( ); - - entvars_t *pevOwner; - if (m_hOwner != NULL) - { - pevOwner = m_hOwner->pev; - } - else - { - pevOwner = pev; - } - - ClearMultiDamage( ); - pOther->TraceAttack(pevOwner, gSkillData.controllerDmgBall, pev->velocity.Normalize(), &tr, DMG_ENERGYBEAM ); - ApplyMultiDamage( pevOwner, pevOwner ); - - UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "debris/beamstart9.wav", 0.3, ATTN_NORM, 0, RANDOM_LONG( 90, 99 ) ); - - } - - UTIL_Remove( this ); -} - - - -#endif // !OEM && !HLDEMO +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* This source code contains proprietary and confidential information of +* Valve LLC and its suppliers. Access to this code is restricted to +* persons who have executed a written SDK license with Valve. Any access, +* use or distribution of this code by or to any unlicensed person is illegal. +* +****/ +#if !defined( OEM_BUILD ) && !defined( HLDEMO_BUILD ) + +//========================================================= +// CONTROLLER +//========================================================= + +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "effects.h" +#include "schedule.h" +#include "weapons.h" +#include "squadmonster.h" + +//========================================================= +// Monster's Anim Events Go Here +//========================================================= +#define CONTROLLER_AE_HEAD_OPEN 1 +#define CONTROLLER_AE_BALL_SHOOT 2 +#define CONTROLLER_AE_SMALL_SHOOT 3 +#define CONTROLLER_AE_POWERUP_FULL 4 +#define CONTROLLER_AE_POWERUP_HALF 5 + +#define CONTROLLER_FLINCH_DELAY 2 // at most one flinch every n secs + +#define SF_AUTO_FIREONCE 0x0001 // env_warpball's spawnflag + +class CPriest : public CSquadMonster +{ +public: + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + static TYPEDESCRIPTION m_SaveData[]; + + void Spawn( void ); + void Precache( void ); + void SetYawSpeed( void ); + int Classify ( void ); + void HandleAnimEvent( MonsterEvent_t *pEvent ); + + void RunAI( void ); + BOOL CheckRangeAttack1 ( float flDot, float flDist ); // balls + BOOL CheckRangeAttack2 ( float flDot, float flDist ); // head + BOOL CheckMeleeAttack1 ( float flDot, float flDist ); // block, throw + Schedule_t* GetSchedule ( void ); + Schedule_t* GetScheduleOfType ( int Type ); + void StartTask ( Task_t *pTask ); + void RunTask ( Task_t *pTask ); + CUSTOM_SCHEDULES; + + void Stop( void ); + void Move ( float flInterval ); + int CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ); + void MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ); + void SetActivity ( Activity NewActivity ); + BOOL ShouldAdvanceRoute( float flWaypointDist ); + int LookupFloat( ); + + float m_flNextFlinch; + + float m_flShootTime; + float m_flShootEnd; + + void PainSound( void ); + void AlertSound( void ); + void IdleSound( void ); + void AttackSound( void ); + void DeathSound( void ); + + static const char *pAttackSounds[]; + static const char *pIdleSounds[]; + static const char *pAlertSounds[]; + static const char *pPainSounds[]; + static const char *pDeathSounds[]; + + int TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ); + void Killed( entvars_t *pevAttacker, int iGib ); + void GibMonster( void ); + + CSprite *m_pBall[2]; // hand balls + int m_iBall[2]; // how bright it should be + float m_iBallTime[2]; // when it should be that color + int m_iBallCurrent[2]; // current brightness + + Vector m_vecEstVelocity; + + Vector m_velocity; + int m_fInCombat; +}; + +LINK_ENTITY_TO_CLASS( monster_alien_priest, CPriest ); + +TYPEDESCRIPTION CPriest::m_SaveData[] = +{ + DEFINE_ARRAY( CPriest, m_pBall, FIELD_CLASSPTR, 2 ), + DEFINE_ARRAY( CPriest, m_iBall, FIELD_INTEGER, 2 ), + DEFINE_ARRAY( CPriest, m_iBallTime, FIELD_TIME, 2 ), + DEFINE_ARRAY( CPriest, m_iBallCurrent, FIELD_INTEGER, 2 ), + DEFINE_FIELD( CPriest, m_vecEstVelocity, FIELD_VECTOR ), +}; +IMPLEMENT_SAVERESTORE( CPriest, CSquadMonster ); + + +const char *CPriest::pAttackSounds[] = +{ + "controller/con_attack1.wav", + "controller/con_attack2.wav", + "controller/con_attack3.wav", +}; + +const char *CPriest::pIdleSounds[] = +{ + "controller/con_idle1.wav", + "controller/con_idle2.wav", + "controller/con_idle3.wav", + "controller/con_idle4.wav", + "controller/con_idle5.wav", +}; + +const char *CPriest::pAlertSounds[] = +{ + "controller/con_alert1.wav", + "controller/con_alert2.wav", + "controller/con_alert3.wav", +}; + +const char *CPriest::pPainSounds[] = +{ + "controller/con_pain1.wav", + "controller/con_pain2.wav", + "controller/con_pain3.wav", +}; + +const char *CPriest::pDeathSounds[] = +{ + "controller/con_die1.wav", + "controller/con_die2.wav", +}; + + +//========================================================= +// Classify - indicates this monster's place in the +// relationship table. +//========================================================= +int CPriest :: Classify ( void ) +{ + return CLASS_ALIEN_MILITARY; +} + +//========================================================= +// SetYawSpeed - allows each sequence to have a different +// turn rate associated with it. +//========================================================= +void CPriest :: SetYawSpeed ( void ) +{ + int ys; + + ys = 120; + +#if 0 + switch ( m_Activity ) + { + } +#endif + + pev->yaw_speed = ys; +} + +int CPriest :: TakeDamage( entvars_t *pevInflictor, entvars_t *pevAttacker, float flDamage, int bitsDamageType ) +{ + // HACK HACK -- until we fix this. + if ( IsAlive() ) + PainSound(); + + ALERT( at_console, "Priest damaged - took %f hit points!\n", flDamage); + + if (flDamage > 20.0) + { + + } + + return CBaseMonster::TakeDamage( pevInflictor, pevAttacker, flDamage, bitsDamageType ); +} + + +void CPriest::Killed( entvars_t *pevAttacker, int iGib ) +{ + // shut off balls + /* + m_iBall[0] = 0; + m_iBallTime[0] = gpGlobals->time + 4.0; + m_iBall[1] = 0; + m_iBallTime[1] = gpGlobals->time + 4.0; + */ + + // fade balls + if (m_pBall[0]) + { + m_pBall[0]->SUB_StartFadeOut(); + m_pBall[0] = NULL; + } + if (m_pBall[1]) + { + m_pBall[1]->SUB_StartFadeOut(); + m_pBall[1] = NULL; + } + + CSquadMonster::Killed( pevAttacker, iGib ); +} + + +void CPriest::GibMonster( void ) +{ + // delete balls + if (m_pBall[0]) + { + UTIL_Remove( m_pBall[0] ); + m_pBall[0] = NULL; + } + if (m_pBall[1]) + { + UTIL_Remove( m_pBall[1] ); + m_pBall[1] = NULL; + } + CSquadMonster::GibMonster( ); +} + + + + +void CPriest :: PainSound( void ) +{ + if (RANDOM_LONG(0,5) < 2) + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pPainSounds ); +} + +void CPriest :: AlertSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAlertSounds ); +} + +void CPriest :: IdleSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pIdleSounds ); +} + +void CPriest :: AttackSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pAttackSounds ); +} + +void CPriest :: DeathSound( void ) +{ + EMIT_SOUND_ARRAY_DYN( CHAN_VOICE, pDeathSounds ); +} + +//========================================================= +// HandleAnimEvent - catches the monster-specific messages +// that occur when tagged animation frames are played. +//========================================================= +void CPriest :: HandleAnimEvent( MonsterEvent_t *pEvent ) +{ + switch( pEvent->event ) + { + case CONTROLLER_AE_HEAD_OPEN: + { + Vector vecStart, angleGun; + + GetAttachment( 0, vecStart, angleGun ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment + WRITE_COORD( vecStart.x ); // origin + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( 1 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 20 ); // life * 10 + WRITE_COORD( -32 ); // decay + MESSAGE_END(); + + m_iBall[0] = 192; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 255; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + + } + break; + + case CONTROLLER_AE_BALL_SHOOT: + { + Vector vecStart, angleGun; + + GetAttachment( 0, vecStart, angleGun ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 ); // entity, attachment + WRITE_COORD( 0 ); // origin + WRITE_COORD( 0 ); + WRITE_COORD( 0 ); + WRITE_COORD( 32 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 10 ); // life * 10 + WRITE_COORD( 32 ); // decay + MESSAGE_END(); + + CBaseMonster *pBall = (CBaseMonster*)Create( "priest_head_ball", vecStart, pev->angles, edict() ); + + pBall->pev->velocity = Vector( 0, 0, 32 ); + pBall->m_hEnemy = m_hEnemy; + + m_iBall[0] = 0; + m_iBall[1] = 0; + } + break; + + case CONTROLLER_AE_SMALL_SHOOT: + { + AttackSound( ); + m_flShootTime = gpGlobals->time; + m_flShootEnd = m_flShootTime + atoi( pEvent->options ) / 15.0; + } + break; + case CONTROLLER_AE_POWERUP_FULL: + { + m_iBall[0] = 255; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 255; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + } + break; + case CONTROLLER_AE_POWERUP_HALF: + { + m_iBall[0] = 192; + m_iBallTime[0] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + m_iBall[1] = 192; + m_iBallTime[1] = gpGlobals->time + atoi( pEvent->options ) / 15.0; + } + break; + default: + CBaseMonster::HandleAnimEvent( pEvent ); + break; + } +} + +//========================================================= +// Spawn +//========================================================= +void CPriest :: Spawn() +{ + Precache( ); + + SET_MODEL(ENT(pev), "models/controller.mdl"); + UTIL_SetSize( pev, Vector( -32, -32, 0 ), Vector( 32, 32, 64 )); + + pev->solid = SOLID_SLIDEBOX; + pev->movetype = MOVETYPE_FLY; + pev->flags |= FL_FLY; + m_bloodColor = BLOOD_COLOR_GREEN; + pev->health = gSkillData.controllerHealth; + pev->view_ofs = Vector( 0, 0, -2 );// position of the eyes relative to monster's origin. + m_flFieldOfView = VIEW_FIELD_FULL;// indicates the width of this monster's forward view cone ( as a dotproduct result ) + m_MonsterState = MONSTERSTATE_NONE; + + MonsterInit(); +} + +//========================================================= +// Precache - precaches all resources this monster needs +//========================================================= +void CPriest :: Precache() +{ + PRECACHE_MODEL("models/controller.mdl"); + + PRECACHE_SOUND_ARRAY( pAttackSounds ); + PRECACHE_SOUND_ARRAY( pIdleSounds ); + PRECACHE_SOUND_ARRAY( pAlertSounds ); + PRECACHE_SOUND_ARRAY( pPainSounds ); + PRECACHE_SOUND_ARRAY( pDeathSounds ); + + PRECACHE_MODEL( "sprites/flare6.spr"); + + UTIL_PrecacheOther( "priest_energy_ball" ); + UTIL_PrecacheOther( "priest_head_ball" ); + UTIL_PrecacheOther( "env_warpball"); +} + +//========================================================= +// AI Schedules Specific to this monster +//========================================================= + + +// Chase enemy schedule +Task_t tlPriestChaseEnemy[] = +{ + { TASK_GET_PATH_TO_ENEMY, (float)128 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + +}; + +Schedule_t slPriestChaseEnemy[] = +{ + { + tlPriestChaseEnemy, + ARRAYSIZE ( tlPriestChaseEnemy ), + bits_COND_NEW_ENEMY | + bits_COND_TASK_FAILED, + 0, + "ControllerChaseEnemy" + }, +}; + + + +Task_t tlPriestStrafe[] = +{ + { TASK_WAIT, (float)0.2 }, + { TASK_GET_PATH_TO_ENEMY, (float)128 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slPriestStrafe[] = +{ + { + tlPriestStrafe, + ARRAYSIZE ( tlPriestStrafe ), + bits_COND_NEW_ENEMY, + 0, + "ControllerStrafe" + }, +}; + + +Task_t tlPriestTakeCover[] = +{ + { TASK_WAIT, (float)0.2 }, + { TASK_FIND_COVER_FROM_ENEMY, (float)0 }, + { TASK_WAIT_FOR_MOVEMENT, (float)0 }, + { TASK_WAIT, (float)1 }, +}; + +Schedule_t slPriestTakeCover[] = +{ + { + tlPriestTakeCover, + ARRAYSIZE ( tlPriestTakeCover ), + bits_COND_NEW_ENEMY, + 0, + "ControllerTakeCover" + }, +}; + + +Task_t tlPriestFail[] = +{ + { TASK_STOP_MOVING, 0 }, + { TASK_SET_ACTIVITY, (float)ACT_IDLE }, + { TASK_WAIT, (float)2 }, + { TASK_WAIT_PVS, (float)0 }, +}; + +Schedule_t slPriestFail[] = +{ + { + tlPriestFail, + ARRAYSIZE ( tlPriestFail ), + 0, + 0, + "ControllerFail" + }, +}; + +DEFINE_CUSTOM_SCHEDULES( CPriest ) +{ + slPriestChaseEnemy, + slPriestStrafe, + slPriestTakeCover, + slPriestFail, +}; + +IMPLEMENT_CUSTOM_SCHEDULES( CPriest, CSquadMonster ); + + + +//========================================================= +// StartTask +//========================================================= +void CPriest :: StartTask ( Task_t *pTask ) +{ + switch ( pTask->iTask ) + { + case TASK_RANGE_ATTACK1: + CSquadMonster :: StartTask ( pTask ); + break; + case TASK_GET_PATH_TO_ENEMY_LKP: + { + if (BuildNearestRoute( m_vecEnemyLKP, pev->view_ofs, pTask->flData, (m_vecEnemyLKP - pev->origin).Length() + 1024 )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemyLKP failed!!\n" ); + TaskFail(); + } + break; + } + case TASK_GET_PATH_TO_ENEMY: + { + CBaseEntity *pEnemy = m_hEnemy; + + if ( pEnemy == NULL ) + { + TaskFail(); + return; + } + + if (BuildNearestRoute( pEnemy->pev->origin, pEnemy->pev->view_ofs, pTask->flData, (pEnemy->pev->origin - pev->origin).Length() + 1024 )) + { + TaskComplete(); + } + else + { + // no way to get there =( + ALERT ( at_aiconsole, "GetPathToEnemy failed!!\n" ); + TaskFail(); + } + break; + } + default: + CSquadMonster :: StartTask ( pTask ); + break; + } +} + +int CPriest::LookupFloat( ) +{ + if (m_velocity.Length( ) < 32.0) + { + return LookupSequence( "up" ); + } + + UTIL_MakeAimVectors( pev->angles ); + float x = DotProduct( gpGlobals->v_forward, m_velocity ); + float y = DotProduct( gpGlobals->v_right, m_velocity ); + float z = DotProduct( gpGlobals->v_up, m_velocity ); + + if (fabs(x) > fabs(y) && fabs(x) > fabs(z)) + { + if (x > 0) + return LookupSequence( "forward"); + else + return LookupSequence( "backward"); + } + else if (fabs(y) > fabs(z)) + { + if (y > 0) + return LookupSequence( "right"); + else + return LookupSequence( "left"); + } + else + { + if (z > 0) + return LookupSequence( "up"); + else + return LookupSequence( "down"); + } +} + + +//========================================================= +// RunTask +//========================================================= +void CPriest :: RunTask ( Task_t *pTask ) +{ + + if (m_flShootEnd > gpGlobals->time) + { + Vector vecHand, vecAngle; + + GetAttachment( 2, vecHand, vecAngle ); + + while (m_flShootTime < m_flShootEnd && m_flShootTime < gpGlobals->time) + { + Vector vecSrc = vecHand + pev->velocity * (m_flShootTime - gpGlobals->time); + Vector vecDir; + + if (m_hEnemy != NULL) + { + if (HasConditions( bits_COND_SEE_ENEMY )) + { + m_vecEstVelocity = m_vecEstVelocity * 0.5 + m_hEnemy->pev->velocity * 0.5; + } + else + { + m_vecEstVelocity = m_vecEstVelocity * 0.8; + } + vecDir = UTIL_Intersect( vecSrc, m_hEnemy->BodyTarget( pev->origin ), m_vecEstVelocity, gSkillData.controllerSpeedBall ); + float delta = 0.03490; // +-2 degree + vecDir = vecDir + Vector( RANDOM_FLOAT( -delta, delta ), RANDOM_FLOAT( -delta, delta ), RANDOM_FLOAT( -delta, delta ) ) * gSkillData.controllerSpeedBall; + + vecSrc = vecSrc + vecDir * (gpGlobals->time - m_flShootTime); + CBaseMonster *pBall = (CBaseMonster*)Create( "priest_energy_ball", vecSrc, pev->angles, edict() ); + pBall->pev->velocity = vecDir; + } + m_flShootTime += 0.2; + } + + if (m_flShootTime > m_flShootEnd) + { + m_iBall[0] = 64; + m_iBallTime[0] = m_flShootEnd; + m_iBall[1] = 64; + m_iBallTime[1] = m_flShootEnd; + m_fInCombat = FALSE; + } + } + + switch ( pTask->iTask ) + { + case TASK_WAIT_FOR_MOVEMENT: + case TASK_WAIT: + case TASK_WAIT_FACE_ENEMY: + case TASK_WAIT_PVS: + MakeIdealYaw( m_vecEnemyLKP ); + ChangeYaw( pev->yaw_speed ); + + if (m_fSequenceFinished) + { + m_fInCombat = FALSE; + } + + CSquadMonster :: RunTask ( pTask ); + + if (!m_fInCombat) + { + if (HasConditions ( bits_COND_CAN_RANGE_ATTACK1 )) + { + pev->sequence = LookupActivity( ACT_RANGE_ATTACK1 ); + pev->frame = 0; + ResetSequenceInfo( ); + m_fInCombat = TRUE; + } + else if (HasConditions ( bits_COND_CAN_RANGE_ATTACK2 )) + { + pev->sequence = LookupActivity( ACT_RANGE_ATTACK2 ); + pev->frame = 0; + ResetSequenceInfo( ); + m_fInCombat = TRUE; + } + else + { + int iFloat = LookupFloat( ); + if (m_fSequenceFinished || iFloat != pev->sequence) + { + pev->sequence = iFloat; + pev->frame = 0; + ResetSequenceInfo( ); + } + } + } + break; + default: + CSquadMonster :: RunTask ( pTask ); + break; + } +} + + +//========================================================= +// GetSchedule - Decides which type of schedule best suits +// the monster's current state and conditions. Then calls +// monster's member function to get a pointer to a schedule +// of the proper type. +//========================================================= +Schedule_t *CPriest :: GetSchedule ( void ) +{ + switch ( m_MonsterState ) + { + case MONSTERSTATE_IDLE: + break; + + case MONSTERSTATE_ALERT: + break; + + case MONSTERSTATE_COMBAT: + { + Vector vecTmp = UTIL_Intersect( Vector( 0, 0, 0 ), Vector( 100, 4, 7 ), Vector( 2, 10, -3 ), 20.0 ); + + // dead enemy + if ( HasConditions ( bits_COND_LIGHT_DAMAGE ) ) + { + // m_iFrustration++; + } + if ( HasConditions ( bits_COND_HEAVY_DAMAGE ) ) + { + // m_iFrustration++; + } + } + break; + } + + return CSquadMonster :: GetSchedule(); +} + + + +//========================================================= +//========================================================= +Schedule_t* CPriest :: GetScheduleOfType ( int Type ) +{ + // ALERT( at_console, "%d\n", m_iFrustration ); + switch ( Type ) + { + case SCHED_CHASE_ENEMY: + return slPriestChaseEnemy; + case SCHED_RANGE_ATTACK1: + return slPriestStrafe; + case SCHED_RANGE_ATTACK2: + case SCHED_MELEE_ATTACK1: + case SCHED_MELEE_ATTACK2: + case SCHED_TAKE_COVER_FROM_ENEMY: + return slPriestTakeCover; + case SCHED_FAIL: + return slPriestFail; + } + + return CBaseMonster :: GetScheduleOfType( Type ); +} + + + + + +//========================================================= +// CheckRangeAttack1 - shoot a bigass energy ball out of their head +// +//========================================================= +BOOL CPriest :: CheckRangeAttack1 ( float flDot, float flDist ) +{ + if ( flDot > 0.5 && flDist > 256 && flDist <= 2048 ) + { + return TRUE; + } + return FALSE; +} + + +BOOL CPriest :: CheckRangeAttack2 ( float flDot, float flDist ) +{ + if ( flDot > 0.5 && flDist > 64 && flDist <= 2048 ) + { + return TRUE; + } + return FALSE; +} + + +BOOL CPriest :: CheckMeleeAttack1 ( float flDot, float flDist ) +{ + return FALSE; +} + + +void CPriest :: SetActivity ( Activity NewActivity ) +{ + CBaseMonster::SetActivity( NewActivity ); + + switch ( m_Activity) + { + case ACT_WALK: + m_flGroundSpeed = 100; + break; + default: + m_flGroundSpeed = 100; + break; + } +} + + + +//========================================================= +// RunAI +//========================================================= +void CPriest :: RunAI( void ) +{ + CBaseMonster :: RunAI(); + Vector vecStart, angleGun; + + if ( HasMemory( bits_MEMORY_KILLED ) ) + return; + + for (int i = 0; i < 2; i++) + { + if (m_pBall[i] == NULL) + { + m_pBall[i] = CSprite::SpriteCreate( "sprites/flare6.spr", pev->origin, TRUE ); + m_pBall[i]->SetTransparency( kRenderTransAdd, 255, 255, 255, 255, kRenderFxNone ); + m_pBall[i]->SetAttachment( edict(), (i + 3) ); + m_pBall[i]->SetScale( 0.4 ); + } + + float t = m_iBallTime[i] - gpGlobals->time; + if (t > 0.1) + t = 0.1 / t; + else + t = 1.0; + + m_iBallCurrent[i] += (m_iBall[i] - m_iBallCurrent[i]) * t; + + m_pBall[i]->SetBrightness( m_iBallCurrent[i] ); + + GetAttachment( i + 2, vecStart, angleGun ); + UTIL_SetOrigin( m_pBall[i]->pev, vecStart ); + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) + 0x1000 * (i + 3) ); // entity, attachment + WRITE_COORD( vecStart.x ); // origin + WRITE_COORD( vecStart.y ); + WRITE_COORD( vecStart.z ); + WRITE_COORD( m_iBallCurrent[i] / 8 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 192 ); // G + WRITE_BYTE( 64 ); // B + WRITE_BYTE( 5 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + } +} + + +extern void DrawRoute( entvars_t *pev, WayPoint_t *m_Route, int m_iRouteIndex, int r, int g, int b ); + +void CPriest::Stop( void ) +{ + m_IdealActivity = GetStoppedActivity(); +} + + +#define DIST_TO_CHECK 200 +void CPriest :: Move ( float flInterval ) +{ + float flWaypointDist; + float flCheckDist; + float flDist;// how far the lookahead check got before hitting an object. + float flMoveDist; + Vector vecDir; + Vector vecApex; + CBaseEntity *pTargetEnt; + + // Don't move if no valid route + if ( FRouteClear() ) + { + ALERT( at_aiconsole, "Tried to move with no route!\n" ); + TaskFail(); + return; + } + + if ( m_flMoveWaitFinished > gpGlobals->time ) + return; + +// Debug, test movement code +#if 0 +// if ( CVAR_GET_FLOAT("stopmove" ) != 0 ) + { + if ( m_movementGoal == MOVEGOAL_ENEMY ) + RouteSimplify( m_hEnemy ); + else + RouteSimplify( m_hTargetEnt ); + FRefreshRoute(); + return; + } +#else +// Debug, draw the route +// DrawRoute( pev, m_Route, m_iRouteIndex, 0, 0, 255 ); +#endif + + // if the monster is moving directly towards an entity (enemy for instance), we'll set this pointer + // to that entity for the CheckLocalMove and Triangulate functions. + pTargetEnt = NULL; + + if (m_flGroundSpeed == 0) + { + m_flGroundSpeed = 100; + // TaskFail( ); + // return; + } + + flMoveDist = m_flGroundSpeed * flInterval; + + do + { + // local move to waypoint. + vecDir = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Normalize(); + flWaypointDist = ( m_Route[ m_iRouteIndex ].vecLocation - pev->origin ).Length(); + + // MakeIdealYaw ( m_Route[ m_iRouteIndex ].vecLocation ); + // ChangeYaw ( pev->yaw_speed ); + + // if the waypoint is closer than CheckDist, CheckDist is the dist to waypoint + if ( flWaypointDist < DIST_TO_CHECK ) + { + flCheckDist = flWaypointDist; + } + else + { + flCheckDist = DIST_TO_CHECK; + } + + if ( (m_Route[ m_iRouteIndex ].iType & (~bits_MF_NOT_TO_MASK)) == bits_MF_TO_ENEMY ) + { + // only on a PURE move to enemy ( i.e., ONLY MF_TO_ENEMY set, not MF_TO_ENEMY and DETOUR ) + pTargetEnt = m_hEnemy; + } + else if ( (m_Route[ m_iRouteIndex ].iType & ~bits_MF_NOT_TO_MASK) == bits_MF_TO_TARGETENT ) + { + pTargetEnt = m_hTargetEnt; + } + + // !!!BUGBUG - CheckDist should be derived from ground speed. + // If this fails, it should be because of some dynamic entity blocking this guy. + // We've already checked this path, so we should wait and time out if the entity doesn't move + flDist = 0; + if ( CheckLocalMove ( pev->origin, pev->origin + vecDir * flCheckDist, pTargetEnt, &flDist ) != LOCALMOVE_VALID ) + { + CBaseEntity *pBlocker; + + // Can't move, stop + Stop(); + // Blocking entity is in global trace_ent + pBlocker = CBaseEntity::Instance( gpGlobals->trace_ent ); + if (pBlocker) + { + DispatchBlocked( edict(), pBlocker->edict() ); + } + if ( pBlocker && m_moveWaitTime > 0 && pBlocker->IsMoving() && !pBlocker->IsPlayer() && (gpGlobals->time-m_flMoveWaitFinished) > 3.0 ) + { + // Can we still move toward our target? + if ( flDist < m_flGroundSpeed ) + { + // Wait for a second + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime; + // ALERT( at_aiconsole, "Move %s!!!\n", STRING( pBlocker->pev->classname ) ); + return; + } + } + else + { + // try to triangulate around whatever is in the way. + if ( FTriangulate( pev->origin, m_Route[ m_iRouteIndex ].vecLocation, flDist, pTargetEnt, &vecApex ) ) + { + InsertWaypoint( vecApex, bits_MF_TO_DETOUR ); + RouteSimplify( pTargetEnt ); + } + else + { + ALERT ( at_aiconsole, "Couldn't Triangulate\n" ); + Stop(); + if ( m_moveWaitTime > 0 ) + { + FRefreshRoute(); + m_flMoveWaitFinished = gpGlobals->time + m_moveWaitTime * 0.5; + } + else + { + TaskFail(); + ALERT( at_aiconsole, "Failed to move!\n" ); + //ALERT( at_aiconsole, "%f, %f, %f\n", pev->origin.z, (pev->origin + (vecDir * flCheckDist)).z, m_Route[m_iRouteIndex].vecLocation.z ); + } + return; + } + } + } + + // UNDONE: this is a hack to quit moving farther than it has looked ahead. + if (flCheckDist < flMoveDist) + { + MoveExecute( pTargetEnt, vecDir, flCheckDist / m_flGroundSpeed ); + + // ALERT( at_console, "%.02f\n", flInterval ); + AdvanceRoute( flWaypointDist ); + flMoveDist -= flCheckDist; + } + else + { + MoveExecute( pTargetEnt, vecDir, flMoveDist / m_flGroundSpeed ); + + if ( ShouldAdvanceRoute( flWaypointDist - flMoveDist ) ) + { + AdvanceRoute( flWaypointDist ); + } + flMoveDist = 0; + } + + if ( MovementIsComplete() ) + { + Stop(); + RouteClear(); + } + } while (flMoveDist > 0 && flCheckDist > 0); + + // cut corner? + if (flWaypointDist < 128) + { + if ( m_movementGoal == MOVEGOAL_ENEMY ) + RouteSimplify( m_hEnemy ); + else + RouteSimplify( m_hTargetEnt ); + FRefreshRoute(); + + if (m_flGroundSpeed > 100) + m_flGroundSpeed -= 40; + } + else + { + if (m_flGroundSpeed < 400) + m_flGroundSpeed += 10; + } +} + + + +BOOL CPriest:: ShouldAdvanceRoute( float flWaypointDist ) +{ + if ( flWaypointDist <= 32 ) + { + return TRUE; + } + + return FALSE; +} + + +int CPriest :: CheckLocalMove ( const Vector &vecStart, const Vector &vecEnd, CBaseEntity *pTarget, float *pflDist ) +{ + TraceResult tr; + + UTIL_TraceHull( vecStart + Vector( 0, 0, 32), vecEnd + Vector( 0, 0, 32), dont_ignore_monsters, large_hull, edict(), &tr ); + + // ALERT( at_console, "%.0f %.0f %.0f : ", vecStart.x, vecStart.y, vecStart.z ); + // ALERT( at_console, "%.0f %.0f %.0f\n", vecEnd.x, vecEnd.y, vecEnd.z ); + + if (pflDist) + { + *pflDist = ( (tr.vecEndPos - Vector( 0, 0, 32 )) - vecStart ).Length();// get the distance. + } + + // ALERT( at_console, "check %d %d %f\n", tr.fStartSolid, tr.fAllSolid, tr.flFraction ); + if (tr.fStartSolid || tr.flFraction < 1.0) + { + if ( pTarget && pTarget->edict() == gpGlobals->trace_ent ) + return LOCALMOVE_VALID; + return LOCALMOVE_INVALID; + } + + return LOCALMOVE_VALID; +} + + +void CPriest::MoveExecute( CBaseEntity *pTargetEnt, const Vector &vecDir, float flInterval ) +{ + if ( m_IdealActivity != m_movementActivity ) + m_IdealActivity = m_movementActivity; + + // ALERT( at_console, "move %.4f %.4f %.4f : %f\n", vecDir.x, vecDir.y, vecDir.z, flInterval ); + + // float flTotal = m_flGroundSpeed * pev->framerate * flInterval; + // UTIL_MoveToOrigin ( ENT(pev), m_Route[ m_iRouteIndex ].vecLocation, flTotal, MOVE_STRAFE ); + + m_velocity = m_velocity * 0.8 + m_flGroundSpeed * vecDir * 0.2; + + UTIL_MoveToOrigin ( ENT(pev), pev->origin + m_velocity, m_velocity.Length() * flInterval, MOVE_STRAFE ); + +} + + + + +//========================================================= +// Controller bouncy ball attack +//========================================================= +class CPriestHeadBall : public CBaseMonster +{ + void Spawn( void ); + void Precache( void ); + void EXPORT HuntThink( void ); + void EXPORT DieThink( void ); + void EXPORT BounceTouch( CBaseEntity *pOther ); + void MovetoTarget( Vector vecTarget ); + void Crawl( void ); + int m_iTrail; + int m_flNextAttack; + Vector m_vecIdeal; + EHANDLE m_hOwner; +}; +LINK_ENTITY_TO_CLASS( priest_head_ball, CPriestHeadBall ); + + + +void CPriestHeadBall :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "sprites/flare6.spr"); + pev->rendermode = kRenderTransAdd; + pev->rendercolor.x = 128; + pev->rendercolor.y = 255; + pev->rendercolor.z = 128; + pev->renderamt = 255; + pev->scale = 0.5; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( &CPriestHeadBall::HuntThink ); + SetTouch( &CPriestHeadBall::BounceTouch ); + + m_vecIdeal = Vector( 0, 0, 0 ); + + pev->nextthink = gpGlobals->time + 0.1; + + m_hOwner = Instance( pev->owner ); + pev->dmgtime = gpGlobals->time; +} + + +void CPriestHeadBall :: Precache( void ) +{ + PRECACHE_MODEL("sprites/flare6.spr"); + PRECACHE_SOUND("debris/zap4.wav"); + PRECACHE_SOUND("debris/beamstart9.wav"); +} + +void CPriestHeadBall :: HuntThink( void ) +{ + pev->frame = ((int)pev->frame + 1) % 11; + + pev->nextthink = gpGlobals->time + 0.1; + + pev->renderamt -= 5; + + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_ELIGHT ); + WRITE_SHORT( entindex( ) ); // entity, attachment + WRITE_COORD( pev->origin.x ); // origin + WRITE_COORD( pev->origin.y ); + WRITE_COORD( pev->origin.z ); + WRITE_COORD( pev->renderamt / 16 ); // radius + WRITE_BYTE( 255 ); // R + WRITE_BYTE( 255 ); // G + WRITE_BYTE( 255 ); // B + WRITE_BYTE( 2 ); // life * 10 + WRITE_COORD( 0 ); // decay + MESSAGE_END(); + + // check world boundaries + if (gpGlobals->time - pev->dmgtime > 5 || pev->renderamt < 64 || m_hEnemy == NULL || m_hOwner == NULL || pev->origin.x < -4096 || pev->origin.x > 4096 || pev->origin.y < -4096 || pev->origin.y > 4096 || pev->origin.z < -4096 || pev->origin.z > 4096) + { + SetTouch( NULL ); + UTIL_Remove( this ); + return; + } + + MovetoTarget( m_hEnemy->Center( ) ); + + if ((m_hEnemy->Center() - pev->origin).Length() < 64) + { + TraceResult tr; + + UTIL_TraceLine( pev->origin, m_hEnemy->Center(), dont_ignore_monsters, ENT(pev), &tr ); + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + if (pEntity != NULL && pEntity->pev->takedamage) + { + ClearMultiDamage( ); + pEntity->TraceAttack( m_hOwner->pev, gSkillData.controllerDmgZap, pev->velocity, &tr, DMG_SHOCK ); + ApplyMultiDamage( pev, m_hOwner->pev ); + } + + CBaseEntity *pBall = (CBaseEntity*)Create( "env_warpball", tr.vecEndPos, pev->angles, edict() ); + + pBall->pev->velocity = Vector( 0, 0, 32 ); + pBall->pev->spawnflags = SF_AUTO_FIREONCE; + pBall->Use( this, this, USE_TOGGLE, 0); + /* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( tr.vecEndPos.x ); + WRITE_COORD( tr.vecEndPos.y ); + WRITE_COORD( tr.vecEndPos.z ); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); + */ + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "debris/beamstart9.wav", 0.5, ATTN_NORM, 0, RANDOM_LONG( 140, 160 ) ); + + m_flNextAttack = gpGlobals->time + 3.0; + + SetThink( &CPriestHeadBall::DieThink ); + pev->nextthink = gpGlobals->time + 0.3; + } + // Crawl( ); +} + +void CPriestHeadBall :: DieThink( void ) +{ + UTIL_Remove( this ); +} + + +void CPriestHeadBall :: MovetoTarget( Vector vecTarget ) +{ + // accelerate + float flSpeed = m_vecIdeal.Length(); + if (flSpeed == 0) + { + m_vecIdeal = pev->velocity; + flSpeed = m_vecIdeal.Length(); + } + + if (flSpeed > 400) + { + m_vecIdeal = m_vecIdeal.Normalize( ) * 400; + } + m_vecIdeal = m_vecIdeal + (vecTarget - pev->origin).Normalize() * 100; + pev->velocity = m_vecIdeal; +} + + + +void CPriestHeadBall :: Crawl( void ) +{ + + Vector vecAim = Vector( RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ), RANDOM_FLOAT( -1, 1 ) ).Normalize( ); + Vector vecPnt = pev->origin + pev->velocity * 0.3 + vecAim * 64; + + CBaseEntity *pBall = (CBaseEntity*)Create( "env_warpball", vecPnt, pev->angles, edict() ); + + pBall->pev->velocity = Vector( 0, 0, 32 ); + pBall->pev->spawnflags = SF_AUTO_FIREONCE; + pBall->Use( this, this, USE_TOGGLE, 0); +/* + MESSAGE_BEGIN( MSG_BROADCAST, SVC_TEMPENTITY ); + WRITE_BYTE( TE_BEAMENTPOINT ); + WRITE_SHORT( entindex() ); + WRITE_COORD( vecPnt.x); + WRITE_COORD( vecPnt.y); + WRITE_COORD( vecPnt.z); + WRITE_SHORT( g_sModelIndexLaser ); + WRITE_BYTE( 0 ); // frame start + WRITE_BYTE( 10 ); // framerate + WRITE_BYTE( 3 ); // life + WRITE_BYTE( 20 ); // width + WRITE_BYTE( 0 ); // noise + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // r, g, b + WRITE_BYTE( 255 ); // brightness + WRITE_BYTE( 10 ); // speed + MESSAGE_END(); */ +} + + +void CPriestHeadBall::BounceTouch( CBaseEntity *pOther ) +{ + Vector vecDir = m_vecIdeal.Normalize( ); + + TraceResult tr = UTIL_GetGlobalTrace( ); + + float n = -DotProduct(tr.vecPlaneNormal, vecDir); + + vecDir = 2.0 * tr.vecPlaneNormal * n + vecDir; + + m_vecIdeal = vecDir * m_vecIdeal.Length(); +} + + + + +class CPriestZapBall : public CBaseMonster +{ + void Spawn( void ); + void Precache( void ); + void EXPORT AnimateThink( void ); + void EXPORT ExplodeTouch( CBaseEntity *pOther ); + + EHANDLE m_hOwner; +}; +LINK_ENTITY_TO_CLASS( priest_energy_ball, CPriestZapBall ); + + +void CPriestZapBall :: Spawn( void ) +{ + Precache( ); + // motor + pev->movetype = MOVETYPE_FLY; + pev->solid = SOLID_BBOX; + + SET_MODEL(ENT(pev), "sprites/flare6.spr"); + pev->rendermode = kRenderTransAdd; + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->renderamt = 255; + pev->scale = 0.5; + + UTIL_SetSize(pev, Vector( 0, 0, 0), Vector(0, 0, 0)); + UTIL_SetOrigin( pev, pev->origin ); + + SetThink( &CPriestZapBall::AnimateThink ); + SetTouch( &CPriestZapBall::ExplodeTouch ); + + m_hOwner = Instance( pev->owner ); + pev->dmgtime = gpGlobals->time; // keep track of when ball spawned + pev->nextthink = gpGlobals->time + 0.1; +} + + +void CPriestZapBall :: Precache( void ) +{ + PRECACHE_MODEL("sprites/flare6.spr"); + // PRECACHE_SOUND("debris/zap4.wav"); + // PRECACHE_SOUND("weapons/electro4.wav"); +} + + +void CPriestZapBall :: AnimateThink( void ) +{ + pev->nextthink = gpGlobals->time + 0.1; + + pev->frame = ((int)pev->frame + 1) % 11; + + if (gpGlobals->time - pev->dmgtime > 5 || pev->velocity.Length() < 10) + { + SetTouch( NULL ); + UTIL_Remove( this ); + } +} + + +void CPriestZapBall::ExplodeTouch( CBaseEntity *pOther ) +{ + if (pOther->pev->takedamage) + { + TraceResult tr = UTIL_GetGlobalTrace( ); + + entvars_t *pevOwner; + if (m_hOwner != NULL) + { + pevOwner = m_hOwner->pev; + } + else + { + pevOwner = pev; + } + + ClearMultiDamage( ); + pOther->TraceAttack(pevOwner, gSkillData.controllerDmgBall, pev->velocity.Normalize(), &tr, DMG_ENERGYBEAM ); + ApplyMultiDamage( pevOwner, pevOwner ); + + UTIL_EmitAmbientSound( ENT(pev), tr.vecEndPos, "debris/beamstart9.wav", 0.3, ATTN_NORM, 0, RANDOM_LONG( 90, 99 ) ); + + } + + UTIL_Remove( this ); +} + + + +#endif // !OEM && !HLDEMO diff --git a/dlls/spritetrain.cpp b/dlls/spritetrain.cpp index f951bd14..8b296d0f 100644 --- a/dlls/spritetrain.cpp +++ b/dlls/spritetrain.cpp @@ -1,527 +1,527 @@ -#include "extdll.h" -#include "util.h" -#include "cbase.h" -#include "trains.h" -#include "saverestore.h" - -// --------------------------------------------------------------------- -// -// Sprite Train -// -// --------------------------------------------------------------------- - -class CFuncSpriteTrain : public CBaseEntity -{ -public: - void Spawn( void ); - void Precache( void ); - - void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); - void KeyValue( KeyValueData* pkvd ); - - void EXPORT Next( void ); - void EXPORT Find( void ); - void EXPORT NearestPath( void ); - void EXPORT DeadEnd( void ); - - void NextThink( float thinkTime, BOOL alwaysThink ); - - void SetTrack( CPathTrack *track ) { m_ppath = track->Nearest(pev->origin); } - void SetControls( entvars_t *pevControls ); - BOOL OnControls( entvars_t *pev ); - void Animate( float frames ); - - virtual int Save( CSave &save ); - virtual int Restore( CRestore &restore ); - - static TYPEDESCRIPTION m_SaveData[]; - virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DIRECTIONAL_USE; } - - virtual void OverrideReset( void ); - - CPathTrack *m_ppath; - float m_length; - float m_height; - float m_speed; - float m_dir; - float m_startSpeed; - Vector m_controlMins; - Vector m_controlMaxs; - float m_flBank; - float m_oldSpeed; - int m_scale; - - float m_lastTime; - float m_maxFrame; -}; - -TYPEDESCRIPTION CFuncSpriteTrain::m_SaveData[] = -{ - DEFINE_FIELD( CFuncSpriteTrain, m_ppath, FIELD_CLASSPTR ), - DEFINE_FIELD( CFuncSpriteTrain, m_length, FIELD_FLOAT ), - DEFINE_FIELD( CFuncSpriteTrain, m_height, FIELD_FLOAT ), - DEFINE_FIELD( CFuncSpriteTrain, m_speed, FIELD_FLOAT ), - DEFINE_FIELD( CFuncSpriteTrain, m_dir, FIELD_FLOAT ), - DEFINE_FIELD( CFuncSpriteTrain, m_startSpeed, FIELD_FLOAT ), - DEFINE_FIELD( CFuncSpriteTrain, m_controlMins, FIELD_VECTOR ), - DEFINE_FIELD( CFuncSpriteTrain, m_controlMaxs, FIELD_VECTOR ), - DEFINE_FIELD( CFuncSpriteTrain, m_flBank, FIELD_FLOAT ), - DEFINE_FIELD( CFuncSpriteTrain, m_oldSpeed, FIELD_FLOAT ), - DEFINE_FIELD( CFuncSpriteTrain, m_maxFrame, FIELD_FLOAT ), - DEFINE_FIELD( CFuncSpriteTrain, m_lastTime, FIELD_FLOAT ), -}; - -IMPLEMENT_SAVERESTORE( CFuncSpriteTrain, CBaseEntity ); -LINK_ENTITY_TO_CLASS( env_spritetrain , CFuncSpriteTrain); - -void CFuncSpriteTrain :: KeyValue( KeyValueData *pkvd ) -{ - if (FStrEq(pkvd->szKeyName, "wheels"))//! - { - m_length = atof(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "height"))//! - { - m_height = atof(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "speed"))//op4 - { - m_startSpeed = atof(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "bank"))//! - { - m_flBank = atof(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "scale"))//op4 - { - m_scale = atof(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else - CBaseEntity::KeyValue( pkvd ); -} - - -void CFuncSpriteTrain:: NextThink( float thinkTime, BOOL alwaysThink ) -{ - if ( alwaysThink ) - pev->flags |= FL_ALWAYSTHINK; - else - pev->flags &= ~FL_ALWAYSTHINK; - - pev->nextthink = thinkTime; -} - -void CFuncSpriteTrain:: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) -{ - if ( useType != USE_SET ) - { - if ( !ShouldToggle( useType, (pev->speed != 0) ) ) - return; - - if ( pev->speed == 0 ) - { - pev->speed = m_speed * m_dir; - - Next(); - } - else - { - pev->speed = 0; - pev->velocity = g_vecZero; - pev->avelocity = g_vecZero; - SetThink( NULL ); - } - } - else - { - float delta = value; - - delta = ((int)(pev->speed * 4) / (int)m_speed)*0.25 + 0.25 * delta; - if ( delta > 1 ) - delta = 1; - else if ( delta < -1 ) - delta = -1; - if ( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY ) - { - if ( delta < 0 ) - delta = 0; - } - pev->speed = m_speed * delta; - Next(); - ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING(pev->targetname), pev->speed ); - } -} - - -static float Fix( float angle ) -{ - while ( angle < 0 ) - angle += 360; - while ( angle > 360 ) - angle -= 360; - - return angle; -} - - -static void FixupAngles( Vector &v ) -{ - v.x = Fix( v.x ); - v.y = Fix( v.y ); - v.z = Fix( v.z ); -} - -#define TRAIN_STARTPITCH 60 -#define TRAIN_MAXPITCH 200 -#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation - -void CFuncSpriteTrain:: Next( void ) -{ - float time = 0.5; - - if ( !pev->speed ) - { - ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING(pev->targetname) ); - return; - } - - if ( !m_ppath ) - { - ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING(pev->targetname) ); - return; - } - - Vector nextPos = pev->origin; - - nextPos.z -= m_height; - CPathTrack *pnext = m_ppath->LookAhead( &nextPos, pev->speed * 0.1, 1 ); - nextPos.z += m_height; - - pev->velocity = (nextPos - pev->origin) * 10; - Vector nextFront = pev->origin; - - nextFront.z -= m_height; - if ( m_length > 0 ) - m_ppath->LookAhead( &nextFront, m_length, 0 ); - else - m_ppath->LookAhead( &nextFront, 100, 0 ); - nextFront.z += m_height; - - Vector delta = nextFront - pev->origin; - Vector angles = UTIL_VecToAngles( delta ); - // The train actually points west - angles.y += 180; - - // !!! All of this crap has to be done to make the angles not wrap around, revisit this. - FixupAngles( angles ); - FixupAngles( pev->angles ); - - if ( !pnext || (delta.x == 0 && delta.y == 0) ) - angles = pev->angles; - - float vy, vx; - if ( !(pev->spawnflags & SF_TRACKTRAIN_NOPITCH) ) - vx = UTIL_AngleDistance( angles.x, pev->angles.x ); - else - vx = 0; - vy = UTIL_AngleDistance( angles.y, pev->angles.y ); - - pev->avelocity.y = vy * 10; - pev->avelocity.x = vx * 10; - - if ( m_flBank != 0 ) - { - if ( pev->avelocity.y < -5 ) - pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); - else if ( pev->avelocity.y > 5 ) - pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); - else - pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( 0, pev->angles.z, m_flBank*4 ), pev->angles.z) * 4; - } - - if ( pnext ) - { - if ( pnext != m_ppath ) - { - CPathTrack *pFire; - if ( pev->speed >= 0 ) - pFire = pnext; - else - pFire = m_ppath; - - m_ppath = pnext; - // Fire the pass target if there is one - if ( pFire->pev->message ) - { - FireTargets( STRING(pFire->pev->message), this, this, USE_TOGGLE, 0 ); - if ( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) ) - pFire->pev->message = 0; - } - - if ( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN ) - pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL; - - // Don't override speed if under user control - if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) - { - if ( pFire->pev->speed != 0 ) - {// don't copy speed from target if it is 0 (uninitialized) - pev->speed = pFire->pev->speed; - ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); - } - } - } - SetThink( &CFuncSpriteTrain::Next ); - NextThink( pev->ltime + time, TRUE ); - } - else // end of path, stop - { - pev->velocity = (nextPos - pev->origin); - pev->avelocity = g_vecZero; - float distance = pev->velocity.Length(); - m_oldSpeed = pev->speed; - - - pev->speed = 0; - - // Move to the dead end - - // Are we there yet? - if ( distance > 0 ) - { - // no, how long to get there? - time = distance / m_oldSpeed; - pev->velocity = pev->velocity * (m_oldSpeed / distance); - SetThink( &CFuncSpriteTrain::DeadEnd ); - NextThink( pev->ltime + time, FALSE ); - } - else - { - DeadEnd(); - } - } -//Animate( 1 );//was 1 - too fast -Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); -m_lastTime = gpGlobals->time; -} - - -void CFuncSpriteTrain::DeadEnd( void ) -{ - // Fire the dead-end target if there is one - CPathTrack *pTrack, *pNext; - - pTrack = m_ppath; - - ALERT( at_aiconsole, "TRAIN(%s): Dead end ", STRING(pev->targetname) ); - // Find the dead end path node - // HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed - // so we have to traverse the list to it's end. - if ( pTrack ) - { - if ( m_oldSpeed < 0 ) - { - do - { - pNext = pTrack->ValidPath( pTrack->GetPrevious(), TRUE ); - if ( pNext ) - pTrack = pNext; - } while ( pNext ); - } - else - { - do - { - pNext = pTrack->ValidPath( pTrack->GetNext(), TRUE ); - if ( pNext ) - pTrack = pNext; - } while ( pNext ); - } - } - - pev->velocity = g_vecZero; - pev->avelocity = g_vecZero; - if ( pTrack ) - { - ALERT( at_aiconsole, "at %s\n", STRING(pTrack->pev->targetname) ); - if ( pTrack->pev->netname ) - FireTargets( STRING(pTrack->pev->netname), this, this, USE_TOGGLE, 0 ); - } - else - ALERT( at_aiconsole, "\n" ); -} - - -void CFuncSpriteTrain :: SetControls( entvars_t *pevControls ) -{ - Vector offset = pevControls->origin - pev->oldorigin; - - m_controlMins = pevControls->mins + offset; - m_controlMaxs = pevControls->maxs + offset; -} - - -BOOL CFuncSpriteTrain :: OnControls( entvars_t *pevTest ) -{ - Vector offset = pevTest->origin - pev->origin; - - if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) - return FALSE; - - // Transform offset into local coordinates - UTIL_MakeVectors( pev->angles ); - Vector local; - local.x = DotProduct( offset, gpGlobals->v_forward ); - local.y = -DotProduct( offset, gpGlobals->v_right ); - local.z = DotProduct( offset, gpGlobals->v_up ); - - if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z && - local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z ) - return TRUE; - - return FALSE; -} - - -void CFuncSpriteTrain :: Find( void ) -{ - m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); - if ( !m_ppath ) - return; - - entvars_t *pevTarget = m_ppath->pev; - if ( !FClassnameIs( pevTarget, "path_track" ) ) - { - ALERT( at_error, "func_track_train must be on a path of path_track\n" ); - m_ppath = NULL; - return; - } - - Vector nextPos = pevTarget->origin; - nextPos.z += m_height; - - Vector look = nextPos; - look.z -= m_height; - m_ppath->LookAhead( &look, m_length, 0 ); - look.z += m_height; - - pev->angles = UTIL_VecToAngles( look - nextPos ); - // The train actually points west - pev->angles.y += 180; - - if ( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) - pev->angles.x = 0; - UTIL_SetOrigin( pev, nextPos ); - NextThink( pev->ltime + 0.1, FALSE ); - SetThink( &CFuncSpriteTrain::Next ); - pev->speed = m_startSpeed; -} - - -void CFuncSpriteTrain :: NearestPath( void ) -{ - CBaseEntity *pTrack = NULL; - CBaseEntity *pNearest = NULL; - float dist, closest; - - closest = 1024; - - while ((pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 )) != NULL) - { - // filter out non-tracks - if ( !(pTrack->pev->flags & (FL_CLIENT|FL_MONSTER)) && FClassnameIs( pTrack->pev, "path_track" ) ) - { - dist = (pev->origin - pTrack->pev->origin).Length(); - if ( dist < closest ) - { - closest = dist; - pNearest = pTrack; - } - } - } - - if ( !pNearest ) - { - ALERT( at_console, "Can't find a nearby track !!!\n" ); - SetThink(NULL); - return; - } - - ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING(pev->targetname), STRING(pNearest->pev->targetname) ); - // If I'm closer to the next path_track on this path, then it's my real path - pTrack = ((CPathTrack *)pNearest)->GetNext(); - if ( pTrack ) - { - if ( (pev->origin - pTrack->pev->origin).Length() < (pev->origin - pNearest->pev->origin).Length() ) - pNearest = pTrack; - } - - m_ppath = (CPathTrack *)pNearest; - - if ( pev->speed != 0 ) - { - NextThink( pev->ltime + 0.1, FALSE ); - SetThink( &CFuncSpriteTrain::Next ); - } -} - -void CFuncSpriteTrain ::OverrideReset( void ) -{ - NextThink( pev->ltime + 0.1, FALSE ); - SetThink( &CFuncSpriteTrain::NearestPath ); -} - -void CFuncSpriteTrain ::Animate( float frames ) -{ -if ( m_maxFrame > 0 ) - pev->frame = fmod( pev->frame + frames, m_maxFrame ); -} - -void CFuncSpriteTrain :: Spawn( void ) -{ - Precache(); - - if ( pev->speed == 0 ) - m_speed = 100; - else - m_speed = pev->speed; - - pev->speed = 0; - pev->velocity = g_vecZero; - pev->avelocity = g_vecZero; - pev->impulse = m_speed; - m_dir = 1; - - pev->solid = SOLID_NOT; - pev->movetype = MOVETYPE_PUSH; - pev->rendercolor.x = 255; - pev->rendercolor.y = 255; - pev->rendercolor.z = 255; - pev->scale = m_scale; - pev->rendermode = kRenderTransAdd; - pev->renderamt = 255; - - SET_MODEL( ENT(pev), STRING(pev->model) ); - UTIL_SetOrigin( pev, pev->origin ); - pev->oldorigin = pev->origin; - - m_controlMins = pev->mins; - m_controlMaxs = pev->maxs; - m_controlMaxs.z += 72; - - m_lastTime = gpGlobals->time; - m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; - - NextThink( pev->ltime + 0.1, FALSE ); - SetThink( &CFuncSpriteTrain::Find ); -} - -void CFuncSpriteTrain :: Precache( void ) -{ - PRECACHE_MODEL( (char *)STRING(pev->model) ); -} +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "trains.h" +#include "saverestore.h" + +// --------------------------------------------------------------------- +// +// Sprite Train +// +// --------------------------------------------------------------------- + +class CFuncSpriteTrain : public CBaseEntity +{ +public: + void Spawn( void ); + void Precache( void ); + + void Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ); + void KeyValue( KeyValueData* pkvd ); + + void EXPORT Next( void ); + void EXPORT Find( void ); + void EXPORT NearestPath( void ); + void EXPORT DeadEnd( void ); + + void NextThink( float thinkTime, BOOL alwaysThink ); + + void SetTrack( CPathTrack *track ) { m_ppath = track->Nearest(pev->origin); } + void SetControls( entvars_t *pevControls ); + BOOL OnControls( entvars_t *pev ); + void Animate( float frames ); + + virtual int Save( CSave &save ); + virtual int Restore( CRestore &restore ); + + static TYPEDESCRIPTION m_SaveData[]; + virtual int ObjectCaps( void ) { return (CBaseEntity :: ObjectCaps() & ~FCAP_ACROSS_TRANSITION) | FCAP_DIRECTIONAL_USE; } + + virtual void OverrideReset( void ); + + CPathTrack *m_ppath; + float m_length; + float m_height; + float m_speed; + float m_dir; + float m_startSpeed; + Vector m_controlMins; + Vector m_controlMaxs; + float m_flBank; + float m_oldSpeed; + int m_scale; + + float m_lastTime; + float m_maxFrame; +}; + +TYPEDESCRIPTION CFuncSpriteTrain::m_SaveData[] = +{ + DEFINE_FIELD( CFuncSpriteTrain, m_ppath, FIELD_CLASSPTR ), + DEFINE_FIELD( CFuncSpriteTrain, m_length, FIELD_FLOAT ), + DEFINE_FIELD( CFuncSpriteTrain, m_height, FIELD_FLOAT ), + DEFINE_FIELD( CFuncSpriteTrain, m_speed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncSpriteTrain, m_dir, FIELD_FLOAT ), + DEFINE_FIELD( CFuncSpriteTrain, m_startSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncSpriteTrain, m_controlMins, FIELD_VECTOR ), + DEFINE_FIELD( CFuncSpriteTrain, m_controlMaxs, FIELD_VECTOR ), + DEFINE_FIELD( CFuncSpriteTrain, m_flBank, FIELD_FLOAT ), + DEFINE_FIELD( CFuncSpriteTrain, m_oldSpeed, FIELD_FLOAT ), + DEFINE_FIELD( CFuncSpriteTrain, m_maxFrame, FIELD_FLOAT ), + DEFINE_FIELD( CFuncSpriteTrain, m_lastTime, FIELD_FLOAT ), +}; + +IMPLEMENT_SAVERESTORE( CFuncSpriteTrain, CBaseEntity ); +LINK_ENTITY_TO_CLASS( env_spritetrain , CFuncSpriteTrain); + +void CFuncSpriteTrain :: KeyValue( KeyValueData *pkvd ) +{ + if (FStrEq(pkvd->szKeyName, "wheels"))//! + { + m_length = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "height"))//! + { + m_height = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "speed"))//op4 + { + m_startSpeed = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "bank"))//! + { + m_flBank = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else if (FStrEq(pkvd->szKeyName, "scale"))//op4 + { + m_scale = atof(pkvd->szValue); + pkvd->fHandled = TRUE; + } + else + CBaseEntity::KeyValue( pkvd ); +} + + +void CFuncSpriteTrain:: NextThink( float thinkTime, BOOL alwaysThink ) +{ + if ( alwaysThink ) + pev->flags |= FL_ALWAYSTHINK; + else + pev->flags &= ~FL_ALWAYSTHINK; + + pev->nextthink = thinkTime; +} + +void CFuncSpriteTrain:: Use( CBaseEntity *pActivator, CBaseEntity *pCaller, USE_TYPE useType, float value ) +{ + if ( useType != USE_SET ) + { + if ( !ShouldToggle( useType, (pev->speed != 0) ) ) + return; + + if ( pev->speed == 0 ) + { + pev->speed = m_speed * m_dir; + + Next(); + } + else + { + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + SetThink( NULL ); + } + } + else + { + float delta = value; + + delta = ((int)(pev->speed * 4) / (int)m_speed)*0.25 + 0.25 * delta; + if ( delta > 1 ) + delta = 1; + else if ( delta < -1 ) + delta = -1; + if ( pev->spawnflags & SF_TRACKTRAIN_FORWARDONLY ) + { + if ( delta < 0 ) + delta = 0; + } + pev->speed = m_speed * delta; + Next(); + ALERT( at_aiconsole, "TRAIN(%s), speed to %.2f\n", STRING(pev->targetname), pev->speed ); + } +} + + +static float Fix( float angle ) +{ + while ( angle < 0 ) + angle += 360; + while ( angle > 360 ) + angle -= 360; + + return angle; +} + + +static void FixupAngles( Vector &v ) +{ + v.x = Fix( v.x ); + v.y = Fix( v.y ); + v.z = Fix( v.z ); +} + +#define TRAIN_STARTPITCH 60 +#define TRAIN_MAXPITCH 200 +#define TRAIN_MAXSPEED 1000 // approx max speed for sound pitch calculation + +void CFuncSpriteTrain:: Next( void ) +{ + float time = 0.5; + + if ( !pev->speed ) + { + ALERT( at_aiconsole, "TRAIN(%s): Speed is 0\n", STRING(pev->targetname) ); + return; + } + + if ( !m_ppath ) + { + ALERT( at_aiconsole, "TRAIN(%s): Lost path\n", STRING(pev->targetname) ); + return; + } + + Vector nextPos = pev->origin; + + nextPos.z -= m_height; + CPathTrack *pnext = m_ppath->LookAhead( &nextPos, pev->speed * 0.1, 1 ); + nextPos.z += m_height; + + pev->velocity = (nextPos - pev->origin) * 10; + Vector nextFront = pev->origin; + + nextFront.z -= m_height; + if ( m_length > 0 ) + m_ppath->LookAhead( &nextFront, m_length, 0 ); + else + m_ppath->LookAhead( &nextFront, 100, 0 ); + nextFront.z += m_height; + + Vector delta = nextFront - pev->origin; + Vector angles = UTIL_VecToAngles( delta ); + // The train actually points west + angles.y += 180; + + // !!! All of this crap has to be done to make the angles not wrap around, revisit this. + FixupAngles( angles ); + FixupAngles( pev->angles ); + + if ( !pnext || (delta.x == 0 && delta.y == 0) ) + angles = pev->angles; + + float vy, vx; + if ( !(pev->spawnflags & SF_TRACKTRAIN_NOPITCH) ) + vx = UTIL_AngleDistance( angles.x, pev->angles.x ); + else + vx = 0; + vy = UTIL_AngleDistance( angles.y, pev->angles.y ); + + pev->avelocity.y = vy * 10; + pev->avelocity.x = vx * 10; + + if ( m_flBank != 0 ) + { + if ( pev->avelocity.y < -5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( -m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else if ( pev->avelocity.y > 5 ) + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( m_flBank, pev->angles.z, m_flBank*2 ), pev->angles.z); + else + pev->avelocity.z = UTIL_AngleDistance( UTIL_ApproachAngle( 0, pev->angles.z, m_flBank*4 ), pev->angles.z) * 4; + } + + if ( pnext ) + { + if ( pnext != m_ppath ) + { + CPathTrack *pFire; + if ( pev->speed >= 0 ) + pFire = pnext; + else + pFire = m_ppath; + + m_ppath = pnext; + // Fire the pass target if there is one + if ( pFire->pev->message ) + { + FireTargets( STRING(pFire->pev->message), this, this, USE_TOGGLE, 0 ); + if ( FBitSet( pFire->pev->spawnflags, SF_PATH_FIREONCE ) ) + pFire->pev->message = 0; + } + + if ( pFire->pev->spawnflags & SF_PATH_DISABLE_TRAIN ) + pev->spawnflags |= SF_TRACKTRAIN_NOCONTROL; + + // Don't override speed if under user control + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + { + if ( pFire->pev->speed != 0 ) + {// don't copy speed from target if it is 0 (uninitialized) + pev->speed = pFire->pev->speed; + ALERT( at_aiconsole, "TrackTrain %s speed to %4.2f\n", STRING(pev->targetname), pev->speed ); + } + } + } + SetThink( &CFuncSpriteTrain::Next ); + NextThink( pev->ltime + time, TRUE ); + } + else // end of path, stop + { + pev->velocity = (nextPos - pev->origin); + pev->avelocity = g_vecZero; + float distance = pev->velocity.Length(); + m_oldSpeed = pev->speed; + + + pev->speed = 0; + + // Move to the dead end + + // Are we there yet? + if ( distance > 0 ) + { + // no, how long to get there? + time = distance / m_oldSpeed; + pev->velocity = pev->velocity * (m_oldSpeed / distance); + SetThink( &CFuncSpriteTrain::DeadEnd ); + NextThink( pev->ltime + time, FALSE ); + } + else + { + DeadEnd(); + } + } +//Animate( 1 );//was 1 - too fast +Animate( pev->framerate * (gpGlobals->time - m_lastTime) ); +m_lastTime = gpGlobals->time; +} + + +void CFuncSpriteTrain::DeadEnd( void ) +{ + // Fire the dead-end target if there is one + CPathTrack *pTrack, *pNext; + + pTrack = m_ppath; + + ALERT( at_aiconsole, "TRAIN(%s): Dead end ", STRING(pev->targetname) ); + // Find the dead end path node + // HACKHACK -- This is bugly, but the train can actually stop moving at a different node depending on it's speed + // so we have to traverse the list to it's end. + if ( pTrack ) + { + if ( m_oldSpeed < 0 ) + { + do + { + pNext = pTrack->ValidPath( pTrack->GetPrevious(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + else + { + do + { + pNext = pTrack->ValidPath( pTrack->GetNext(), TRUE ); + if ( pNext ) + pTrack = pNext; + } while ( pNext ); + } + } + + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + if ( pTrack ) + { + ALERT( at_aiconsole, "at %s\n", STRING(pTrack->pev->targetname) ); + if ( pTrack->pev->netname ) + FireTargets( STRING(pTrack->pev->netname), this, this, USE_TOGGLE, 0 ); + } + else + ALERT( at_aiconsole, "\n" ); +} + + +void CFuncSpriteTrain :: SetControls( entvars_t *pevControls ) +{ + Vector offset = pevControls->origin - pev->oldorigin; + + m_controlMins = pevControls->mins + offset; + m_controlMaxs = pevControls->maxs + offset; +} + + +BOOL CFuncSpriteTrain :: OnControls( entvars_t *pevTest ) +{ + Vector offset = pevTest->origin - pev->origin; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOCONTROL ) + return FALSE; + + // Transform offset into local coordinates + UTIL_MakeVectors( pev->angles ); + Vector local; + local.x = DotProduct( offset, gpGlobals->v_forward ); + local.y = -DotProduct( offset, gpGlobals->v_right ); + local.z = DotProduct( offset, gpGlobals->v_up ); + + if ( local.x >= m_controlMins.x && local.y >= m_controlMins.y && local.z >= m_controlMins.z && + local.x <= m_controlMaxs.x && local.y <= m_controlMaxs.y && local.z <= m_controlMaxs.z ) + return TRUE; + + return FALSE; +} + + +void CFuncSpriteTrain :: Find( void ) +{ + m_ppath = CPathTrack::Instance(FIND_ENTITY_BY_TARGETNAME( NULL, STRING(pev->target) )); + if ( !m_ppath ) + return; + + entvars_t *pevTarget = m_ppath->pev; + if ( !FClassnameIs( pevTarget, "path_track" ) ) + { + ALERT( at_error, "func_track_train must be on a path of path_track\n" ); + m_ppath = NULL; + return; + } + + Vector nextPos = pevTarget->origin; + nextPos.z += m_height; + + Vector look = nextPos; + look.z -= m_height; + m_ppath->LookAhead( &look, m_length, 0 ); + look.z += m_height; + + pev->angles = UTIL_VecToAngles( look - nextPos ); + // The train actually points west + pev->angles.y += 180; + + if ( pev->spawnflags & SF_TRACKTRAIN_NOPITCH ) + pev->angles.x = 0; + UTIL_SetOrigin( pev, nextPos ); + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncSpriteTrain::Next ); + pev->speed = m_startSpeed; +} + + +void CFuncSpriteTrain :: NearestPath( void ) +{ + CBaseEntity *pTrack = NULL; + CBaseEntity *pNearest = NULL; + float dist, closest; + + closest = 1024; + + while ((pTrack = UTIL_FindEntityInSphere( pTrack, pev->origin, 1024 )) != NULL) + { + // filter out non-tracks + if ( !(pTrack->pev->flags & (FL_CLIENT|FL_MONSTER)) && FClassnameIs( pTrack->pev, "path_track" ) ) + { + dist = (pev->origin - pTrack->pev->origin).Length(); + if ( dist < closest ) + { + closest = dist; + pNearest = pTrack; + } + } + } + + if ( !pNearest ) + { + ALERT( at_console, "Can't find a nearby track !!!\n" ); + SetThink(NULL); + return; + } + + ALERT( at_aiconsole, "TRAIN: %s, Nearest track is %s\n", STRING(pev->targetname), STRING(pNearest->pev->targetname) ); + // If I'm closer to the next path_track on this path, then it's my real path + pTrack = ((CPathTrack *)pNearest)->GetNext(); + if ( pTrack ) + { + if ( (pev->origin - pTrack->pev->origin).Length() < (pev->origin - pNearest->pev->origin).Length() ) + pNearest = pTrack; + } + + m_ppath = (CPathTrack *)pNearest; + + if ( pev->speed != 0 ) + { + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncSpriteTrain::Next ); + } +} + +void CFuncSpriteTrain ::OverrideReset( void ) +{ + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncSpriteTrain::NearestPath ); +} + +void CFuncSpriteTrain ::Animate( float frames ) +{ +if ( m_maxFrame > 0 ) + pev->frame = fmod( pev->frame + frames, m_maxFrame ); +} + +void CFuncSpriteTrain :: Spawn( void ) +{ + Precache(); + + if ( pev->speed == 0 ) + m_speed = 100; + else + m_speed = pev->speed; + + pev->speed = 0; + pev->velocity = g_vecZero; + pev->avelocity = g_vecZero; + pev->impulse = m_speed; + m_dir = 1; + + pev->solid = SOLID_NOT; + pev->movetype = MOVETYPE_PUSH; + pev->rendercolor.x = 255; + pev->rendercolor.y = 255; + pev->rendercolor.z = 255; + pev->scale = m_scale; + pev->rendermode = kRenderTransAdd; + pev->renderamt = 255; + + SET_MODEL( ENT(pev), STRING(pev->model) ); + UTIL_SetOrigin( pev, pev->origin ); + pev->oldorigin = pev->origin; + + m_controlMins = pev->mins; + m_controlMaxs = pev->maxs; + m_controlMaxs.z += 72; + + m_lastTime = gpGlobals->time; + m_maxFrame = (float) MODEL_FRAMES( pev->modelindex ) - 1; + + NextThink( pev->ltime + 0.1, FALSE ); + SetThink( &CFuncSpriteTrain::Find ); +} + +void CFuncSpriteTrain :: Precache( void ) +{ + PRECACHE_MODEL( (char *)STRING(pev->model) ); +} diff --git a/dlls/triggers.h b/dlls/triggers.h index 54113763..aeea4902 100644 --- a/dlls/triggers.h +++ b/dlls/triggers.h @@ -1,21 +1,21 @@ -class CTriggerKicker : public CBaseDelay -{ -public: - void Spawn( void ); - void Think( void ); - void KickPlayer( CBasePlayer *pKickMe ); - - int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } -private: - CBasePlayer *pPlayerToKick; -}; - -class CTriggerLockedMission : public CBaseDelay -{ -public: - void Spawn( void ); - void Think( void ); - void Lock( void ); - - int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +class CTriggerKicker : public CBaseDelay +{ +public: + void Spawn( void ); + void Think( void ); + void KickPlayer( CBasePlayer *pKickMe ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } +private: + CBasePlayer *pPlayerToKick; +}; + +class CTriggerLockedMission : public CBaseDelay +{ +public: + void Spawn( void ); + void Think( void ); + void Lock( void ); + + int ObjectCaps( void ) { return CBaseDelay::ObjectCaps() & ~FCAP_ACROSS_TRANSITION; } }; \ No newline at end of file diff --git a/dlls/vorti.cpp b/dlls/vorti.cpp index 5b01881b..c8c27e5c 100644 --- a/dlls/vorti.cpp +++ b/dlls/vorti.cpp @@ -1,769 +1,769 @@ -/*** -* -* Copyright (c) 1996-2002, Valve LLC. All rights reserved. -* -* This product contains software technology licensed from Id -* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. -* All Rights Reserved. -* -* Use, distribution, and modification of this source code and/or resulting -* object code is 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" - -#ifndef CLIENT_DLL - extern int gmsgAlienState; -#endif - -#define CROWBAR_BODYHIT_VOLUME 128 -#define CROWBAR_WALLHIT_VOLUME 512 - -#define SLAVE_PRIMARY_CHARGE_VOLUME 256// how loud gauss is while charging -#define SLAVE_PRIMARY_FIRE_VOLUME 450// how loud gauss is when discharged - -LINK_ENTITY_TO_CLASS( weapon_vorti, CVortiHands ); - -enum vorti_e { - SLAVE_IDLE1 = 0, - SLAVE_IDLE2, - SLAVE_ATTACK1HIT, - SLAVE_ATTACK1MISS, - SLAVE_ATTACK2MISS, - SLAVE_ATTACK2HIT, - SLAVE_ATTACK3MISS, - SLAVE_ATTACK3HIT, - - SLAVE_CHARGE, - SLAVE_CHARGE_LOOP, - SLAVE_ZAP, - SLAVE_RETURN -}; - - -void CVortiHands::Spawn( ) -{ - Precache( ); - m_iId = WEAPON_VORTI; - SET_MODEL(ENT(pev), "models/w_slave.mdl"); - m_iClip = -1; - - FallInit();// get ready to fall down. -} - - -void CVortiHands::Precache( void ) -{ - PRECACHE_MODEL("models/v_slave.mdl"); //v_slave.mdl - PRECACHE_MODEL("models/w_slave.mdl"); - PRECACHE_MODEL("models/p_slave.mdl"); - - PRECACHE_SOUND("debris/zap1.wav"); - PRECACHE_SOUND("debris/zap4.wav"); - PRECACHE_SOUND("zombie/claw_strike1.wav"); - PRECACHE_SOUND("zombie/claw_strike2.wav"); - PRECACHE_SOUND("zombie/claw_strike3.wav"); - PRECACHE_SOUND("zombie/claw_miss1.wav"); - PRECACHE_SOUND("zombie/claw_miss2.wav"); - - PRECACHE_MODEL("sprites/lgtning.spr"); - - PRECACHE_SOUND("zombie/claw_miss2.wav"); - PRECACHE_SOUND("aslave/slv_word5.wav"); - PRECACHE_SOUND("aslave/slv_word7.wav"); - PRECACHE_SOUND("aslave/slv_word3.wav"); - PRECACHE_SOUND("aslave/slv_word4.wav"); - - m_usVorti = PRECACHE_EVENT ( 1, "events/vorti.sc" ); - m_usVortiFire = PRECACHE_EVENT( 1, "events/vortifire.sc" ); - m_usVortiSpin = PRECACHE_EVENT( 1, "events/vortispin.sc" ); -} - -int CVortiHands::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_VORTI; - p->iWeight = 0; - return 1; -} - - - -BOOL CVortiHands::Deploy( ) -{ - return DefaultDeploy( "models/v_slave.mdl", "models/p_slave.mdl", SLAVE_IDLE1, "crowbar" ); -} - -void CVortiHands::Holster( int skiplocal /* = 0 */ ) -{ - m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; - SendWeaponAnim( SLAVE_IDLE1 ); -} - -void CVortiHands::PrimaryAttack() -{ - m_pPlayer->m_iWeaponVolume = SLAVE_PRIMARY_FIRE_VOLUME; - m_fPrimaryFire = TRUE; - m_fInAttack = 0; - -#ifndef CLIENT_DLL - MESSAGE_BEGIN( MSG_ONE, gmsgAlienState, NULL, m_pPlayer->pev ); - WRITE_BYTE( 0 ); - MESSAGE_END(); -#endif - - if (! Swing( 1 )) - { - SetThink( &CVortiHands::SwingAgain ); - pev->nextthink = gpGlobals->time + 0.1; - } -} - - -void CVortiHands::Smack( ) -{ - DecalGunshot( &m_trHit, BULLET_PLAYER_CROWBAR ); -} - - -void CVortiHands::SwingAgain( void ) -{ - Swing( 0 ); -} - - -int CVortiHands::Swing( int fFirst ) -{ - int fDidHit = FALSE; - - TraceResult tr; - - UTIL_MakeVectors (m_pPlayer->pev->v_angle); - Vector vecSrc = m_pPlayer->GetGunPosition( ); - Vector vecEnd = vecSrc + gpGlobals->v_forward * 32; - - UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); - -#ifndef CLIENT_DLL - if ( tr.flFraction >= 1.0 ) - { - UTIL_TraceHull( vecSrc, vecEnd, dont_ignore_monsters, head_hull, ENT( m_pPlayer->pev ), &tr ); - if ( tr.flFraction < 1.0 ) - { - // Calculate the point of intersection of the line (or hull) and the object we hit - // This is and approximation of the "best" intersection - CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); - if ( !pHit || pHit->IsBSPModel() ) - FindHullIntersection( vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, m_pPlayer->edict() ); - vecEnd = tr.vecEndPos; // This is the point on the actual surface (the hull could have hit space) - } - } -#endif - - PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usVorti, - 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0, 0, 0, - 0.0, 0, 0.0 ); - - - if ( tr.flFraction >= 1.0 ) - { - if (fFirst) - { - // miss - m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; - - // player "shoot" animation - m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); - } - } - else - { - switch( ((m_iSwing++) % 2) + 1 ) - { - case 0: - SendWeaponAnim( SLAVE_ATTACK1HIT ); break; - case 1: - SendWeaponAnim( SLAVE_ATTACK2HIT ); break; - case 2: - SendWeaponAnim( SLAVE_ATTACK3HIT ); break; - } - - // player "shoot" animation - m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); - -#ifndef CLIENT_DLL - - // hit - fDidHit = TRUE; - CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); - - ClearMultiDamage( ); - - if ( (m_flNextPrimaryAttack + 1 < UTIL_WeaponTimeBase() ) || g_pGameRules->IsMultiplayer() ) - { - // first swing does full damage - pEntity->TraceAttack(m_pPlayer->pev, gSkillData.plrDmgCrowbar, gpGlobals->v_forward, &tr, DMG_CLUB ); - if ( pEntity->IsAlive() == true ) - m_pPlayer->TakeHealth( gSkillData.plrDmgCrowbar, 0 ); - } - else - { - // subsequent swings do half - pEntity->TraceAttack(m_pPlayer->pev, gSkillData.plrDmgCrowbar / 2, gpGlobals->v_forward, &tr, DMG_CLUB ); - if ( pEntity->IsAlive() == true ) - m_pPlayer->TakeHealth( gSkillData.plrDmgCrowbar / 2, 0 ); - } - ApplyMultiDamage( m_pPlayer->pev, m_pPlayer->pev ); - - // play thwack, smack, or dong sound - float flVol = 1.0; - int fHitWorld = TRUE; - - if (pEntity) - { - if ( pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE ) - { - // play thwack or smack sound - switch( RANDOM_LONG(0,2) ) - { - case 0: - EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "zombie/claw_strike1.wav", 1, ATTN_NORM); break; - case 1: - EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "zombie/claw_strike2.wav", 1, ATTN_NORM); break; - case 2: - EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "zombie/claw_strike3.wav", 1, ATTN_NORM); break; - } - m_pPlayer->m_iWeaponVolume = CROWBAR_BODYHIT_VOLUME; - if ( !pEntity->IsAlive() ) - return TRUE; - else - flVol = 0.1; - - fHitWorld = FALSE; - } - } - - // play texture hit sound - // UNDONE: Calculate the correct point of intersection when we hit with the hull instead of the line - - if (fHitWorld) - { - float fvolbar = TEXTURETYPE_PlaySound(&tr, vecSrc, vecSrc + (vecEnd-vecSrc)*2, BULLET_PLAYER_CROWBAR); - - if ( g_pGameRules->IsMultiplayer() ) - { - // override the volume here, cause we don't play texture sounds in multiplayer, - // and fvolbar is going to be 0 from the above call. - - fvolbar = 1; - } - - // also play crowbar strike - switch( RANDOM_LONG(0,1) ) - { - case 0: - EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "zombie/claw_miss1.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); - break; - case 1: - EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "zombie/claw_miss2.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); - break; - } - - // delay the decal a bit - m_trHit = tr; - } - - m_pPlayer->m_iWeaponVolume = flVol * CROWBAR_WALLHIT_VOLUME; -#endif - m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.25; - - SetThink( &CVortiHands::Smack ); - pev->nextthink = UTIL_WeaponTimeBase() + 0.2; - - - } - return fDidHit; -} - -float CVortiHands::GetFullChargeTime( void ) -{ -/* -#ifdef CLIENT_DLL - if ( bIsMultiplayer() ) -#else - if ( g_pGameRules->IsMultiplayer() ) -#endif -*/ - { - return 3; - } - - return 3; -} - -void CVortiHands::SecondaryAttack( void ) -{ - // don't fire underwater - if ( m_pPlayer->pev->waterlevel == 3 ) - { - if ( m_fInAttack != 0 ) - { - //EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro4.wav", 1.0, ATTN_NORM, 0, 80 + RANDOM_LONG(0,0x3f)); - SendWeaponAnim( SLAVE_IDLE1 ); - m_fInAttack = 0; - } - else - { - PlayEmptySound( ); - } - - m_flNextSecondaryAttack = m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; - return; - } - - if ( m_fInAttack == 0 ) - { - /*if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) - { - EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); - m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; - return; - }*/ - - m_fPrimaryFire = FALSE; - - //m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--;// take one ammo just to start the spin - //m_pPlayer->m_flNextAmmoBurn = UTIL_WeaponTimeBase(); - - // spin up - m_pPlayer->m_iWeaponVolume = SLAVE_PRIMARY_CHARGE_VOLUME; -#ifndef CLIENT_DLL - MESSAGE_BEGIN( MSG_ONE, gmsgAlienState, NULL, m_pPlayer->pev); - WRITE_BYTE( 1 ); - MESSAGE_END(); -#endif - SendWeaponAnim( SLAVE_CHARGE ); - m_fInAttack = 1; - m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1; //0.5; - m_pPlayer->m_flStartCharge = gpGlobals->time; - m_pPlayer->m_flAmmoStartCharge = UTIL_WeaponTimeBase() + GetFullChargeTime(); - - PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usVortiSpin, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 110, 0, 0, 0 ); - - m_iSoundState = SND_CHANGE_PITCH; - } - else if (m_fInAttack == 1) - { - if (m_flTimeWeaponIdle < UTIL_WeaponTimeBase()) - { - SendWeaponAnim( SLAVE_CHARGE_LOOP ); - m_fInAttack = 2; -#ifndef CLIENT_DLL - MESSAGE_BEGIN( MSG_ONE, gmsgAlienState, NULL, m_pPlayer->pev); - WRITE_BYTE( 2 ); - MESSAGE_END(); -#endif - } - } - else - { - // during the charging process, eat one bit of ammo every once in a while -/* if ( UTIL_WeaponTimeBase() >= m_pPlayer->m_flNextAmmoBurn && m_pPlayer->m_flNextAmmoBurn != 1000 ) - { -#ifdef CLIENT_DLL - if ( bIsMultiplayer() ) -#else - if ( g_pGameRules->IsMultiplayer() ) -#endif - { - m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; - m_pPlayer->m_flNextAmmoBurn = UTIL_WeaponTimeBase() + 0.1; - } - else - { - m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; - m_pPlayer->m_flNextAmmoBurn = UTIL_WeaponTimeBase() + 0.3; - } - }*/ - - /*if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) - { - // out of ammo! force the gun to fire - StartFire(); - m_fInAttack = 0; - m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; - m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1; - return; - }*/ - - //if ( UTIL_WeaponTimeBase() >= m_pPlayer->m_flAmmoStartCharge ) - //{ - // don't eat any more ammo after gun is fully charged. - // m_pPlayer->m_flNextAmmoBurn = 1000; - //} - - int pitch = ( gpGlobals->time - m_pPlayer->m_flStartCharge ) * ( 150 / GetFullChargeTime() ) + 100; - if ( pitch > 250 ) - pitch = 250; - - // ALERT( at_console, "%d %d %d\n", m_fInAttack, m_iSoundState, pitch ); - - if ( m_iSoundState == 0 ) - ALERT( at_console, "sound state %d\n", m_iSoundState ); -#ifndef CLIENT_DLL - MESSAGE_BEGIN( MSG_ONE, gmsgAlienState, NULL, m_pPlayer->pev); - WRITE_BYTE( 3 ); - MESSAGE_END(); -#endif - PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usVortiSpin, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, pitch, 0, ( m_iSoundState == SND_CHANGE_PITCH ) ? 1 : 0, 0 ); - - m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions - - m_pPlayer->m_iWeaponVolume = SLAVE_PRIMARY_CHARGE_VOLUME; - - // m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.1; - if ( m_pPlayer->m_flStartCharge < gpGlobals->time - 10 ) - { - // Player charged up too long. Zap him. - EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro4.wav", 1.0, ATTN_NORM, 0, 80 + RANDOM_LONG(0,0x3f)); - EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/electro6.wav", 1.0, ATTN_NORM, 0, 75 + RANDOM_LONG(0,0x3f)); - - m_fInAttack = 0; - m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; - m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; - /* -#ifndef CLIENT_DLL - m_pPlayer->TakeDamage( VARS(eoNullEntity), VARS(eoNullEntity), 50, DMG_SHOCK ); - UTIL_ScreenFade( m_pPlayer, Vector(255,128,0), 2, 0.5, 128, FFADE_IN ); -#endif - */ -#ifndef CLIENT_DLL - MESSAGE_BEGIN( MSG_ONE, gmsgAlienState, NULL, m_pPlayer->pev); - WRITE_BYTE( 0 ); - MESSAGE_END(); -#endif - SendWeaponAnim( SLAVE_IDLE1 ); - - // Player may have been killed and this weapon dropped, don't execute any more code after this! - return; - } - } -} - -void CVortiHands::WeaponIdle( void ) -{ - ResetEmptySound( ); - - // play aftershock static discharge - /*if ( m_pPlayer->m_flPlayAftershock && m_pPlayer->m_flPlayAftershock < gpGlobals->time ) - { - switch (RANDOM_LONG(0,3)) - { - case 0: EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro4.wav", RANDOM_FLOAT(0.7, 0.8), ATTN_NORM); break; - case 1: EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro5.wav", RANDOM_FLOAT(0.7, 0.8), ATTN_NORM); break; - case 2: EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro6.wav", RANDOM_FLOAT(0.7, 0.8), ATTN_NORM); break; - case 3: break; // no sound - } - m_pPlayer->m_flPlayAftershock = 0.0; - }*/ - - if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) - return; - - if (m_fInAttack != 0) - { -#ifndef CLIENT_DLL - MESSAGE_BEGIN( MSG_ONE, gmsgAlienState, NULL, m_pPlayer->pev); - WRITE_BYTE( 0 ); - MESSAGE_END(); -#endif - ALERT( at_console, "released right button!\n" ); - StartFire(); - m_fInAttack = 0; - m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 2.0; - }/* - else - { - int iAnim; - float flRand = RANDOM_FLOAT(0, 1); - if (flRand <= 0.5) - { - iAnim = GAUSS_IDLE; - m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); - } - else if (flRand <= 0.75) - { - iAnim = GAUSS_IDLE2; - m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); - } - else - { - iAnim = GAUSS_FIDGET; - m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; - } - - return; - SendWeaponAnim( iAnim ); - - }*/ -} - - -//========================================================= -// 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 CVortiHands::StartFire( void ) -{ - float flDamage; - - UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); - Vector vecAiming = gpGlobals->v_forward; - Vector vecSrc = m_pPlayer->GetGunPosition( ); // + gpGlobals->v_up * -8 + gpGlobals->v_right * 8; - - if ( gpGlobals->time - m_pPlayer->m_flStartCharge > GetFullChargeTime() ) - { - flDamage = 60; - } - else - { - flDamage = 60 * (( gpGlobals->time - m_pPlayer->m_flStartCharge) / GetFullChargeTime() ); - } - - if ( m_fPrimaryFire ) - { - // fixed damage on primary attack -#ifdef CLIENT_DLL - flDamage = 20; -#else - flDamage = gSkillData.plrDmgGauss; -#endif - } - - if (m_fInAttack != 3) - { - //ALERT ( at_console, "Time:%f Damage:%f\n", gpGlobals->time - m_pPlayer->m_flStartCharge, flDamage ); -/* -#ifndef CLIENT_DLL - float flZVel = m_pPlayer->pev->velocity.z; - - if ( !m_fPrimaryFire ) - { - m_pPlayer->pev->velocity = m_pPlayer->pev->velocity - gpGlobals->v_forward * flDamage * 5; - } - - if ( !g_pGameRules->IsMultiplayer() ) - - { - // in deathmatch, gauss can pop you up into the air. Not in single play. - m_pPlayer->pev->velocity.z = flZVel; - } -#endif -*/ - // player "shoot" animation - m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); - } - - // time until aftershock 'static discharge' sound - m_pPlayer->m_flPlayAftershock = gpGlobals->time + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0.3, 0.8 ); - - Fire( vecSrc, vecAiming, flDamage ); -} - -void CVortiHands::Fire( Vector vecOrigSrc, Vector vecDir, float flDamage ) -{ - m_pPlayer->m_iWeaponVolume = SLAVE_PRIMARY_FIRE_VOLUME; - - Vector vecSrc = vecOrigSrc; - Vector vecDest = vecSrc + vecDir * 8192; - edict_t *pentIgnore; - TraceResult tr, beam_tr; - float flMaxFrac = 1.0; - int nTotal = 0; - int fHasPunched = 0; - int fFirstBeam = 1; - int nMaxHits = 10; - - pentIgnore = ENT( m_pPlayer->pev ); - -//#ifdef CLIENT_DLL -// if ( m_fPrimaryFire == false ) -// g_irunninggausspred = true; -//#endif - - // The main firing event is sent unreliably so it won't be delayed. - PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usVortiFire, 0.0, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, flDamage, 0.0, 0, 0, m_fPrimaryFire ? 1 : 0, 0 ); - - // This reliable event is used to stop the spinning sound - // It's delayed by a fraction of second to make sure it is delayed by 1 frame on the client - // It's sent reliably anyway, which could lead to other delays - - PLAYBACK_EVENT_FULL( FEV_NOTHOST | FEV_RELIABLE, m_pPlayer->edict(), m_usVortiFire, 0.01, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, 0.0, 0.0, 0, 0, 0, 1 ); - - - /*ALERT( at_console, "%f %f %f\n%f %f %f\n", - vecSrc.x, vecSrc.y, vecSrc.z, - vecDest.x, vecDest.y, vecDest.z );*/ - - -// ALERT( at_console, "%f %f\n", tr.flFraction, flMaxFrac ); - -#ifndef CLIENT_DLL - while (flDamage > 10 && nMaxHits > 0) - { - nMaxHits--; - - // ALERT( at_console, "." ); - UTIL_TraceLine(vecSrc, vecDest, dont_ignore_monsters, pentIgnore, &tr); - - if (tr.fAllSolid) - break; - - CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); - - if (pEntity == NULL) - break; - - if ( fFirstBeam ) - { - m_pPlayer->pev->effects |= EF_MUZZLEFLASH; - fFirstBeam = 0; - - nTotal += 26; - } - - if (pEntity->pev->takedamage) - { - ClearMultiDamage(); - pEntity->TraceAttack( m_pPlayer->pev, flDamage, vecDir, &tr, DMG_BULLET ); - ApplyMultiDamage(m_pPlayer->pev, m_pPlayer->pev); - - if ( pEntity->IsAlive() == true ) - m_pPlayer->TakeHealth( 10, 0 ); - } - - /* - if ( pEntity->ReflectGauss() ) - { - float n; - - pentIgnore = NULL; - - n = -DotProduct(tr.vecPlaneNormal, vecDir); - - if (n < 0.5) // 60 degrees - { - // ALERT( at_console, "reflect %f\n", n ); - // reflect - Vector r; - - r = 2.0 * tr.vecPlaneNormal * n + vecDir; - flMaxFrac = flMaxFrac - tr.flFraction; - vecDir = r; - vecSrc = tr.vecEndPos + vecDir * 8; - vecDest = vecSrc + vecDir * 8192; - - // explode a bit - m_pPlayer->RadiusDamage( tr.vecEndPos, pev, m_pPlayer->pev, flDamage * n, CLASS_NONE, DMG_BLAST ); - - nTotal += 34; - - // lose energy - if (n == 0) n = 0.1; - flDamage = flDamage * (1 - n); - } - else - { - nTotal += 13; - - // limit it to one hole punch - if (fHasPunched) - break; - fHasPunched = 1; - - // try punching through wall if secondary attack (primary is incapable of breaking through) - if ( !m_fPrimaryFire ) - { - UTIL_TraceLine( tr.vecEndPos + vecDir * 8, vecDest, dont_ignore_monsters, pentIgnore, &beam_tr); - if (!beam_tr.fAllSolid) - { - // trace backwards to find exit point - UTIL_TraceLine( beam_tr.vecEndPos, tr.vecEndPos, dont_ignore_monsters, pentIgnore, &beam_tr); - - float n = (beam_tr.vecEndPos - tr.vecEndPos).Length( ); - - if (n < flDamage) - { - if (n == 0) n = 1; - flDamage -= n; - - // ALERT( at_console, "punch %f\n", n ); - nTotal += 21; - - // exit blast damage - //m_pPlayer->RadiusDamage( beam_tr.vecEndPos + vecDir * 8, pev, m_pPlayer->pev, flDamage, CLASS_NONE, DMG_BLAST ); - float damage_radius; - - - if ( g_pGameRules->IsMultiplayer() ) - { - damage_radius = flDamage * 1.75; // Old code == 2.5 - } - else - { - damage_radius = flDamage * 2.5; - } - - ::RadiusDamage( beam_tr.vecEndPos + vecDir * 8, pev, m_pPlayer->pev, flDamage, damage_radius, CLASS_NONE, DMG_BLAST ); - - CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); - - nTotal += 53; - - vecSrc = beam_tr.vecEndPos + vecDir; - } - } - else - { - //ALERT( at_console, "blocked %f\n", n ); - flDamage = 0; - } - } - else - { - //ALERT( at_console, "blocked solid\n" ); - - flDamage = 0; - } - - } - } - else - */ - { - vecSrc = tr.vecEndPos + vecDir; - pentIgnore = ENT( pEntity->pev ); - } - } -#endif - // ALERT( at_console, "%d bytes\n", nTotal ); -} +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is 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" + +#ifndef CLIENT_DLL + extern int gmsgAlienState; +#endif + +#define CROWBAR_BODYHIT_VOLUME 128 +#define CROWBAR_WALLHIT_VOLUME 512 + +#define SLAVE_PRIMARY_CHARGE_VOLUME 256// how loud gauss is while charging +#define SLAVE_PRIMARY_FIRE_VOLUME 450// how loud gauss is when discharged + +LINK_ENTITY_TO_CLASS( weapon_vorti, CVortiHands ); + +enum vorti_e { + SLAVE_IDLE1 = 0, + SLAVE_IDLE2, + SLAVE_ATTACK1HIT, + SLAVE_ATTACK1MISS, + SLAVE_ATTACK2MISS, + SLAVE_ATTACK2HIT, + SLAVE_ATTACK3MISS, + SLAVE_ATTACK3HIT, + + SLAVE_CHARGE, + SLAVE_CHARGE_LOOP, + SLAVE_ZAP, + SLAVE_RETURN +}; + + +void CVortiHands::Spawn( ) +{ + Precache( ); + m_iId = WEAPON_VORTI; + SET_MODEL(ENT(pev), "models/w_slave.mdl"); + m_iClip = -1; + + FallInit();// get ready to fall down. +} + + +void CVortiHands::Precache( void ) +{ + PRECACHE_MODEL("models/v_slave.mdl"); //v_slave.mdl + PRECACHE_MODEL("models/w_slave.mdl"); + PRECACHE_MODEL("models/p_slave.mdl"); + + PRECACHE_SOUND("debris/zap1.wav"); + PRECACHE_SOUND("debris/zap4.wav"); + PRECACHE_SOUND("zombie/claw_strike1.wav"); + PRECACHE_SOUND("zombie/claw_strike2.wav"); + PRECACHE_SOUND("zombie/claw_strike3.wav"); + PRECACHE_SOUND("zombie/claw_miss1.wav"); + PRECACHE_SOUND("zombie/claw_miss2.wav"); + + PRECACHE_MODEL("sprites/lgtning.spr"); + + PRECACHE_SOUND("zombie/claw_miss2.wav"); + PRECACHE_SOUND("aslave/slv_word5.wav"); + PRECACHE_SOUND("aslave/slv_word7.wav"); + PRECACHE_SOUND("aslave/slv_word3.wav"); + PRECACHE_SOUND("aslave/slv_word4.wav"); + + m_usVorti = PRECACHE_EVENT ( 1, "events/vorti.sc" ); + m_usVortiFire = PRECACHE_EVENT( 1, "events/vortifire.sc" ); + m_usVortiSpin = PRECACHE_EVENT( 1, "events/vortispin.sc" ); +} + +int CVortiHands::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_VORTI; + p->iWeight = 0; + return 1; +} + + + +BOOL CVortiHands::Deploy( ) +{ + return DefaultDeploy( "models/v_slave.mdl", "models/p_slave.mdl", SLAVE_IDLE1, "crowbar" ); +} + +void CVortiHands::Holster( int skiplocal /* = 0 */ ) +{ + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + SendWeaponAnim( SLAVE_IDLE1 ); +} + +void CVortiHands::PrimaryAttack() +{ + m_pPlayer->m_iWeaponVolume = SLAVE_PRIMARY_FIRE_VOLUME; + m_fPrimaryFire = TRUE; + m_fInAttack = 0; + +#ifndef CLIENT_DLL + MESSAGE_BEGIN( MSG_ONE, gmsgAlienState, NULL, m_pPlayer->pev ); + WRITE_BYTE( 0 ); + MESSAGE_END(); +#endif + + if (! Swing( 1 )) + { + SetThink( &CVortiHands::SwingAgain ); + pev->nextthink = gpGlobals->time + 0.1; + } +} + + +void CVortiHands::Smack( ) +{ + DecalGunshot( &m_trHit, BULLET_PLAYER_CROWBAR ); +} + + +void CVortiHands::SwingAgain( void ) +{ + Swing( 0 ); +} + + +int CVortiHands::Swing( int fFirst ) +{ + int fDidHit = FALSE; + + TraceResult tr; + + UTIL_MakeVectors (m_pPlayer->pev->v_angle); + Vector vecSrc = m_pPlayer->GetGunPosition( ); + Vector vecEnd = vecSrc + gpGlobals->v_forward * 32; + + UTIL_TraceLine( vecSrc, vecEnd, dont_ignore_monsters, ENT( m_pPlayer->pev ), &tr ); + +#ifndef CLIENT_DLL + if ( tr.flFraction >= 1.0 ) + { + UTIL_TraceHull( vecSrc, vecEnd, dont_ignore_monsters, head_hull, ENT( m_pPlayer->pev ), &tr ); + if ( tr.flFraction < 1.0 ) + { + // Calculate the point of intersection of the line (or hull) and the object we hit + // This is and approximation of the "best" intersection + CBaseEntity *pHit = CBaseEntity::Instance( tr.pHit ); + if ( !pHit || pHit->IsBSPModel() ) + FindHullIntersection( vecSrc, tr, VEC_DUCK_HULL_MIN, VEC_DUCK_HULL_MAX, m_pPlayer->edict() ); + vecEnd = tr.vecEndPos; // This is the point on the actual surface (the hull could have hit space) + } + } +#endif + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usVorti, + 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0, 0, 0, + 0.0, 0, 0.0 ); + + + if ( tr.flFraction >= 1.0 ) + { + if (fFirst) + { + // miss + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + } + } + else + { + switch( ((m_iSwing++) % 2) + 1 ) + { + case 0: + SendWeaponAnim( SLAVE_ATTACK1HIT ); break; + case 1: + SendWeaponAnim( SLAVE_ATTACK2HIT ); break; + case 2: + SendWeaponAnim( SLAVE_ATTACK3HIT ); break; + } + + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + +#ifndef CLIENT_DLL + + // hit + fDidHit = TRUE; + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + ClearMultiDamage( ); + + if ( (m_flNextPrimaryAttack + 1 < UTIL_WeaponTimeBase() ) || g_pGameRules->IsMultiplayer() ) + { + // first swing does full damage + pEntity->TraceAttack(m_pPlayer->pev, gSkillData.plrDmgCrowbar, gpGlobals->v_forward, &tr, DMG_CLUB ); + if ( pEntity->IsAlive() == true ) + m_pPlayer->TakeHealth( gSkillData.plrDmgCrowbar, 0 ); + } + else + { + // subsequent swings do half + pEntity->TraceAttack(m_pPlayer->pev, gSkillData.plrDmgCrowbar / 2, gpGlobals->v_forward, &tr, DMG_CLUB ); + if ( pEntity->IsAlive() == true ) + m_pPlayer->TakeHealth( gSkillData.plrDmgCrowbar / 2, 0 ); + } + ApplyMultiDamage( m_pPlayer->pev, m_pPlayer->pev ); + + // play thwack, smack, or dong sound + float flVol = 1.0; + int fHitWorld = TRUE; + + if (pEntity) + { + if ( pEntity->Classify() != CLASS_NONE && pEntity->Classify() != CLASS_MACHINE ) + { + // play thwack or smack sound + switch( RANDOM_LONG(0,2) ) + { + case 0: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "zombie/claw_strike1.wav", 1, ATTN_NORM); break; + case 1: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "zombie/claw_strike2.wav", 1, ATTN_NORM); break; + case 2: + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_ITEM, "zombie/claw_strike3.wav", 1, ATTN_NORM); break; + } + m_pPlayer->m_iWeaponVolume = CROWBAR_BODYHIT_VOLUME; + if ( !pEntity->IsAlive() ) + return TRUE; + else + flVol = 0.1; + + fHitWorld = FALSE; + } + } + + // play texture hit sound + // UNDONE: Calculate the correct point of intersection when we hit with the hull instead of the line + + if (fHitWorld) + { + float fvolbar = TEXTURETYPE_PlaySound(&tr, vecSrc, vecSrc + (vecEnd-vecSrc)*2, BULLET_PLAYER_CROWBAR); + + if ( g_pGameRules->IsMultiplayer() ) + { + // override the volume here, cause we don't play texture sounds in multiplayer, + // and fvolbar is going to be 0 from the above call. + + fvolbar = 1; + } + + // also play crowbar strike + switch( RANDOM_LONG(0,1) ) + { + case 0: + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "zombie/claw_miss1.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + case 1: + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "zombie/claw_miss2.wav", fvolbar, ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); + break; + } + + // delay the decal a bit + m_trHit = tr; + } + + m_pPlayer->m_iWeaponVolume = flVol * CROWBAR_WALLHIT_VOLUME; +#endif + m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.25; + + SetThink( &CVortiHands::Smack ); + pev->nextthink = UTIL_WeaponTimeBase() + 0.2; + + + } + return fDidHit; +} + +float CVortiHands::GetFullChargeTime( void ) +{ +/* +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif +*/ + { + return 3; + } + + return 3; +} + +void CVortiHands::SecondaryAttack( void ) +{ + // don't fire underwater + if ( m_pPlayer->pev->waterlevel == 3 ) + { + if ( m_fInAttack != 0 ) + { + //EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro4.wav", 1.0, ATTN_NORM, 0, 80 + RANDOM_LONG(0,0x3f)); + SendWeaponAnim( SLAVE_IDLE1 ); + m_fInAttack = 0; + } + else + { + PlayEmptySound( ); + } + + m_flNextSecondaryAttack = m_flNextPrimaryAttack = UTIL_WeaponTimeBase() + 0.5; + return; + } + + if ( m_fInAttack == 0 ) + { + /*if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) + { + EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 0.5; + return; + }*/ + + m_fPrimaryFire = FALSE; + + //m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--;// take one ammo just to start the spin + //m_pPlayer->m_flNextAmmoBurn = UTIL_WeaponTimeBase(); + + // spin up + m_pPlayer->m_iWeaponVolume = SLAVE_PRIMARY_CHARGE_VOLUME; +#ifndef CLIENT_DLL + MESSAGE_BEGIN( MSG_ONE, gmsgAlienState, NULL, m_pPlayer->pev); + WRITE_BYTE( 1 ); + MESSAGE_END(); +#endif + SendWeaponAnim( SLAVE_CHARGE ); + m_fInAttack = 1; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1; //0.5; + m_pPlayer->m_flStartCharge = gpGlobals->time; + m_pPlayer->m_flAmmoStartCharge = UTIL_WeaponTimeBase() + GetFullChargeTime(); + + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usVortiSpin, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, 110, 0, 0, 0 ); + + m_iSoundState = SND_CHANGE_PITCH; + } + else if (m_fInAttack == 1) + { + if (m_flTimeWeaponIdle < UTIL_WeaponTimeBase()) + { + SendWeaponAnim( SLAVE_CHARGE_LOOP ); + m_fInAttack = 2; +#ifndef CLIENT_DLL + MESSAGE_BEGIN( MSG_ONE, gmsgAlienState, NULL, m_pPlayer->pev); + WRITE_BYTE( 2 ); + MESSAGE_END(); +#endif + } + } + else + { + // during the charging process, eat one bit of ammo every once in a while +/* if ( UTIL_WeaponTimeBase() >= m_pPlayer->m_flNextAmmoBurn && m_pPlayer->m_flNextAmmoBurn != 1000 ) + { +#ifdef CLIENT_DLL + if ( bIsMultiplayer() ) +#else + if ( g_pGameRules->IsMultiplayer() ) +#endif + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_pPlayer->m_flNextAmmoBurn = UTIL_WeaponTimeBase() + 0.1; + } + else + { + m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]--; + m_pPlayer->m_flNextAmmoBurn = UTIL_WeaponTimeBase() + 0.3; + } + }*/ + + /*if ( m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0 ) + { + // out of ammo! force the gun to fire + StartFire(); + m_fInAttack = 0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1; + return; + }*/ + + //if ( UTIL_WeaponTimeBase() >= m_pPlayer->m_flAmmoStartCharge ) + //{ + // don't eat any more ammo after gun is fully charged. + // m_pPlayer->m_flNextAmmoBurn = 1000; + //} + + int pitch = ( gpGlobals->time - m_pPlayer->m_flStartCharge ) * ( 150 / GetFullChargeTime() ) + 100; + if ( pitch > 250 ) + pitch = 250; + + // ALERT( at_console, "%d %d %d\n", m_fInAttack, m_iSoundState, pitch ); + + if ( m_iSoundState == 0 ) + ALERT( at_console, "sound state %d\n", m_iSoundState ); +#ifndef CLIENT_DLL + MESSAGE_BEGIN( MSG_ONE, gmsgAlienState, NULL, m_pPlayer->pev); + WRITE_BYTE( 3 ); + MESSAGE_END(); +#endif + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usVortiSpin, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, 0.0, 0.0, pitch, 0, ( m_iSoundState == SND_CHANGE_PITCH ) ? 1 : 0, 0 ); + + m_iSoundState = SND_CHANGE_PITCH; // hack for going through level transitions + + m_pPlayer->m_iWeaponVolume = SLAVE_PRIMARY_CHARGE_VOLUME; + + // m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 0.1; + if ( m_pPlayer->m_flStartCharge < gpGlobals->time - 10 ) + { + // Player charged up too long. Zap him. + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro4.wav", 1.0, ATTN_NORM, 0, 80 + RANDOM_LONG(0,0x3f)); + EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_ITEM, "weapons/electro6.wav", 1.0, ATTN_NORM, 0, 75 + RANDOM_LONG(0,0x3f)); + + m_fInAttack = 0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 1.0; + m_pPlayer->m_flNextAttack = UTIL_WeaponTimeBase() + 1.0; + /* +#ifndef CLIENT_DLL + m_pPlayer->TakeDamage( VARS(eoNullEntity), VARS(eoNullEntity), 50, DMG_SHOCK ); + UTIL_ScreenFade( m_pPlayer, Vector(255,128,0), 2, 0.5, 128, FFADE_IN ); +#endif + */ +#ifndef CLIENT_DLL + MESSAGE_BEGIN( MSG_ONE, gmsgAlienState, NULL, m_pPlayer->pev); + WRITE_BYTE( 0 ); + MESSAGE_END(); +#endif + SendWeaponAnim( SLAVE_IDLE1 ); + + // Player may have been killed and this weapon dropped, don't execute any more code after this! + return; + } + } +} + +void CVortiHands::WeaponIdle( void ) +{ + ResetEmptySound( ); + + // play aftershock static discharge + /*if ( m_pPlayer->m_flPlayAftershock && m_pPlayer->m_flPlayAftershock < gpGlobals->time ) + { + switch (RANDOM_LONG(0,3)) + { + case 0: EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro4.wav", RANDOM_FLOAT(0.7, 0.8), ATTN_NORM); break; + case 1: EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro5.wav", RANDOM_FLOAT(0.7, 0.8), ATTN_NORM); break; + case 2: EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/electro6.wav", RANDOM_FLOAT(0.7, 0.8), ATTN_NORM); break; + case 3: break; // no sound + } + m_pPlayer->m_flPlayAftershock = 0.0; + }*/ + + if (m_flTimeWeaponIdle > UTIL_WeaponTimeBase()) + return; + + if (m_fInAttack != 0) + { +#ifndef CLIENT_DLL + MESSAGE_BEGIN( MSG_ONE, gmsgAlienState, NULL, m_pPlayer->pev); + WRITE_BYTE( 0 ); + MESSAGE_END(); +#endif + ALERT( at_console, "released right button!\n" ); + StartFire(); + m_fInAttack = 0; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 2.0; + }/* + else + { + int iAnim; + float flRand = RANDOM_FLOAT(0, 1); + if (flRand <= 0.5) + { + iAnim = GAUSS_IDLE; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } + else if (flRand <= 0.75) + { + iAnim = GAUSS_IDLE2; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 10, 15 ); + } + else + { + iAnim = GAUSS_FIDGET; + m_flTimeWeaponIdle = UTIL_WeaponTimeBase() + 3; + } + + return; + SendWeaponAnim( iAnim ); + + }*/ +} + + +//========================================================= +// 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 CVortiHands::StartFire( void ) +{ + float flDamage; + + UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); + Vector vecAiming = gpGlobals->v_forward; + Vector vecSrc = m_pPlayer->GetGunPosition( ); // + gpGlobals->v_up * -8 + gpGlobals->v_right * 8; + + if ( gpGlobals->time - m_pPlayer->m_flStartCharge > GetFullChargeTime() ) + { + flDamage = 60; + } + else + { + flDamage = 60 * (( gpGlobals->time - m_pPlayer->m_flStartCharge) / GetFullChargeTime() ); + } + + if ( m_fPrimaryFire ) + { + // fixed damage on primary attack +#ifdef CLIENT_DLL + flDamage = 20; +#else + flDamage = gSkillData.plrDmgGauss; +#endif + } + + if (m_fInAttack != 3) + { + //ALERT ( at_console, "Time:%f Damage:%f\n", gpGlobals->time - m_pPlayer->m_flStartCharge, flDamage ); +/* +#ifndef CLIENT_DLL + float flZVel = m_pPlayer->pev->velocity.z; + + if ( !m_fPrimaryFire ) + { + m_pPlayer->pev->velocity = m_pPlayer->pev->velocity - gpGlobals->v_forward * flDamage * 5; + } + + if ( !g_pGameRules->IsMultiplayer() ) + + { + // in deathmatch, gauss can pop you up into the air. Not in single play. + m_pPlayer->pev->velocity.z = flZVel; + } +#endif +*/ + // player "shoot" animation + m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); + } + + // time until aftershock 'static discharge' sound + m_pPlayer->m_flPlayAftershock = gpGlobals->time + UTIL_SharedRandomFloat( m_pPlayer->random_seed, 0.3, 0.8 ); + + Fire( vecSrc, vecAiming, flDamage ); +} + +void CVortiHands::Fire( Vector vecOrigSrc, Vector vecDir, float flDamage ) +{ + m_pPlayer->m_iWeaponVolume = SLAVE_PRIMARY_FIRE_VOLUME; + + Vector vecSrc = vecOrigSrc; + Vector vecDest = vecSrc + vecDir * 8192; + edict_t *pentIgnore; + TraceResult tr, beam_tr; + float flMaxFrac = 1.0; + int nTotal = 0; + int fHasPunched = 0; + int fFirstBeam = 1; + int nMaxHits = 10; + + pentIgnore = ENT( m_pPlayer->pev ); + +//#ifdef CLIENT_DLL +// if ( m_fPrimaryFire == false ) +// g_irunninggausspred = true; +//#endif + + // The main firing event is sent unreliably so it won't be delayed. + PLAYBACK_EVENT_FULL( FEV_NOTHOST, m_pPlayer->edict(), m_usVortiFire, 0.0, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, flDamage, 0.0, 0, 0, m_fPrimaryFire ? 1 : 0, 0 ); + + // This reliable event is used to stop the spinning sound + // It's delayed by a fraction of second to make sure it is delayed by 1 frame on the client + // It's sent reliably anyway, which could lead to other delays + + PLAYBACK_EVENT_FULL( FEV_NOTHOST | FEV_RELIABLE, m_pPlayer->edict(), m_usVortiFire, 0.01, (float *)&m_pPlayer->pev->origin, (float *)&m_pPlayer->pev->angles, 0.0, 0.0, 0, 0, 0, 1 ); + + + /*ALERT( at_console, "%f %f %f\n%f %f %f\n", + vecSrc.x, vecSrc.y, vecSrc.z, + vecDest.x, vecDest.y, vecDest.z );*/ + + +// ALERT( at_console, "%f %f\n", tr.flFraction, flMaxFrac ); + +#ifndef CLIENT_DLL + while (flDamage > 10 && nMaxHits > 0) + { + nMaxHits--; + + // ALERT( at_console, "." ); + UTIL_TraceLine(vecSrc, vecDest, dont_ignore_monsters, pentIgnore, &tr); + + if (tr.fAllSolid) + break; + + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + if (pEntity == NULL) + break; + + if ( fFirstBeam ) + { + m_pPlayer->pev->effects |= EF_MUZZLEFLASH; + fFirstBeam = 0; + + nTotal += 26; + } + + if (pEntity->pev->takedamage) + { + ClearMultiDamage(); + pEntity->TraceAttack( m_pPlayer->pev, flDamage, vecDir, &tr, DMG_BULLET ); + ApplyMultiDamage(m_pPlayer->pev, m_pPlayer->pev); + + if ( pEntity->IsAlive() == true ) + m_pPlayer->TakeHealth( 10, 0 ); + } + + /* + if ( pEntity->ReflectGauss() ) + { + float n; + + pentIgnore = NULL; + + n = -DotProduct(tr.vecPlaneNormal, vecDir); + + if (n < 0.5) // 60 degrees + { + // ALERT( at_console, "reflect %f\n", n ); + // reflect + Vector r; + + r = 2.0 * tr.vecPlaneNormal * n + vecDir; + flMaxFrac = flMaxFrac - tr.flFraction; + vecDir = r; + vecSrc = tr.vecEndPos + vecDir * 8; + vecDest = vecSrc + vecDir * 8192; + + // explode a bit + m_pPlayer->RadiusDamage( tr.vecEndPos, pev, m_pPlayer->pev, flDamage * n, CLASS_NONE, DMG_BLAST ); + + nTotal += 34; + + // lose energy + if (n == 0) n = 0.1; + flDamage = flDamage * (1 - n); + } + else + { + nTotal += 13; + + // limit it to one hole punch + if (fHasPunched) + break; + fHasPunched = 1; + + // try punching through wall if secondary attack (primary is incapable of breaking through) + if ( !m_fPrimaryFire ) + { + UTIL_TraceLine( tr.vecEndPos + vecDir * 8, vecDest, dont_ignore_monsters, pentIgnore, &beam_tr); + if (!beam_tr.fAllSolid) + { + // trace backwards to find exit point + UTIL_TraceLine( beam_tr.vecEndPos, tr.vecEndPos, dont_ignore_monsters, pentIgnore, &beam_tr); + + float n = (beam_tr.vecEndPos - tr.vecEndPos).Length( ); + + if (n < flDamage) + { + if (n == 0) n = 1; + flDamage -= n; + + // ALERT( at_console, "punch %f\n", n ); + nTotal += 21; + + // exit blast damage + //m_pPlayer->RadiusDamage( beam_tr.vecEndPos + vecDir * 8, pev, m_pPlayer->pev, flDamage, CLASS_NONE, DMG_BLAST ); + float damage_radius; + + + if ( g_pGameRules->IsMultiplayer() ) + { + damage_radius = flDamage * 1.75; // Old code == 2.5 + } + else + { + damage_radius = flDamage * 2.5; + } + + ::RadiusDamage( beam_tr.vecEndPos + vecDir * 8, pev, m_pPlayer->pev, flDamage, damage_radius, CLASS_NONE, DMG_BLAST ); + + CSoundEnt::InsertSound ( bits_SOUND_COMBAT, pev->origin, NORMAL_EXPLOSION_VOLUME, 3.0 ); + + nTotal += 53; + + vecSrc = beam_tr.vecEndPos + vecDir; + } + } + else + { + //ALERT( at_console, "blocked %f\n", n ); + flDamage = 0; + } + } + else + { + //ALERT( at_console, "blocked solid\n" ); + + flDamage = 0; + } + + } + } + else + */ + { + vecSrc = tr.vecEndPos + vecDir; + pentIgnore = ENT( pEntity->pev ); + } + } +#endif + // ALERT( at_console, "%d bytes\n", nTotal ); +}