diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index aa8fcafa6..be261fc05 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -57,6 +57,7 @@ list(APPEND CLDLL_SOURCES "${CMAKE_SOURCE_DIR}/game_shared/stringlib.cpp" "${CMAKE_SOURCE_DIR}/game_shared/virtualfs.cpp" "${CMAKE_SOURCE_DIR}/game_shared/trace.cpp" + "${CMAKE_SOURCE_DIR}/game_shared/weapon_context.cpp" "${CMAKE_SOURCE_DIR}/game_shared/filesystem_utils.cpp" "${CMAKE_SOURCE_DIR}/game_shared/filesystem_manager.cpp" "${CMAKE_SOURCE_DIR}/public/crclib.cpp" @@ -68,6 +69,9 @@ file(GLOB RENDER_SOURCES "render/*.cpp") # entity wrappers source files file(GLOB ENTITIES_SOURCES "entities/*.cpp") +# weapon shared code source files +file(GLOB WEAPONS_SHARED_SOURCES "${CMAKE_SOURCE_DIR}/game_shared/weapons/*.cpp") + # ImGui source files if(NOT ENABLE_VGUI_COMPATIBILITY) list(APPEND IMGUI_SOURCES @@ -84,6 +88,7 @@ endif() list(APPEND CLDLL_SOURCES ${RENDER_SOURCES}) list(APPEND CLDLL_SOURCES ${ENTITIES_SOURCES}) +list(APPEND CLDLL_SOURCES ${WEAPONS_SHARED_SOURCES}) add_library(${PROJECT_NAME} SHARED ${CLDLL_SOURCES}) target_include_directories(${PROJECT_NAME} PRIVATE diff --git a/game_shared/item_info.h b/game_shared/item_info.h new file mode 100644 index 000000000..344028716 --- /dev/null +++ b/game_shared/item_info.h @@ -0,0 +1,46 @@ +#pragma once +#include "cdll_dll.h" + +#define ITEM_FLAG_SELECTONEMPTY 1 +#define ITEM_FLAG_NOAUTORELOAD 2 +#define ITEM_FLAG_NOAUTOSWITCHEMPTY 4 +#define ITEM_FLAG_LIMITINWORLD 8 +#define ITEM_FLAG_EXHAUSTIBLE 16 // A player can totally exhaust their ammo supply and lose this weapon + +#define WEAPON_NOCLIP -1 +#define WEAPON_ALLWEAPONS (~(1<GetPlayerNextAttackTime() <= m_pFuncs->GetWeaponTimeBase()) + { + // complete the reload. + int j = Q_min( iMaxClip() - m_iClip, m_pFuncs->GetPlayerAmmo(m_iPrimaryAmmoType) ); + + // Add them to the clip + m_iClip += j; + m_pFuncs->SetPlayerAmmo( m_iPrimaryAmmoType, m_pFuncs->GetPlayerAmmo(m_iPrimaryAmmoType) - j ); + + m_fInReload = FALSE; + } + + if (m_pFuncs->CheckPlayerButtonFlag(IN_ATTACK2) && m_flNextSecondaryAttack <= m_pFuncs->GetWeaponTimeBase() ) + { + if ( pszAmmo2() && !m_pFuncs->GetPlayerAmmo(SecondaryAmmoIndex()) ) + { + m_fFireOnEmpty = TRUE; + } + + SecondaryAttack(); + m_pFuncs->ClearPlayerButtonFlag(IN_ATTACK2); + } + else if (m_pFuncs->CheckPlayerButtonFlag(IN_ATTACK) && m_flNextPrimaryAttack <= m_pFuncs->GetWeaponTimeBase() ) + { + if ( (m_iClip == 0 && pszAmmo1()) || (iMaxClip() == -1 && !m_pFuncs->GetPlayerAmmo(PrimaryAmmoIndex())) ) + { + m_fFireOnEmpty = TRUE; + } + + PrimaryAttack(); + } + else if ( m_pFuncs->CheckPlayerButtonFlag(IN_RELOAD) && iMaxClip() != WEAPON_NOCLIP && !m_fInReload ) + { + // reload when reload is pressed, or if no buttons are down and weapon is empty. + Reload(); + } + else if ( !(m_pFuncs->CheckPlayerButtonFlag(IN_ATTACK|IN_ATTACK2) ) ) + { + // no fire buttons down + + m_fFireOnEmpty = FALSE; +#ifndef CLIENT_DLL // we don't need this branch on client side, because client is not responsible for changing weapons + if ( !IsUseable() && m_flNextPrimaryAttack < m_pFuncs->GetWeaponTimeBase() ) + { + // weapon isn't useable, switch. GetNextBestWeapon exactly does weapon switching + if ( !(iFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) && m_pFuncs->GetNextBestWeapon() ) + { + m_flNextPrimaryAttack = m_pFuncs->GetWeaponTimeBase() + 0.3; + return; + } + } + else +#endif + { + // weapon is useable. Reload if empty and weapon has waited as long as it has to after firing + if ( m_iClip == 0 && !(iFlags() & ITEM_FLAG_NOAUTORELOAD) && m_flNextPrimaryAttack < m_pFuncs->GetWeaponTimeBase() ) + { + Reload(); + return; + } + } + + WeaponIdle( ); + return; + } + + // catch all + if ( ShouldWeaponIdle() ) + { + WeaponIdle(); + } +} + +void CBaseWeaponContext::Holster() +{ + m_fInReload = FALSE; // cancel any reload in progress. + m_pFuncs->SetPlayerViewmodel(0); +#ifndef CLIENT_DLL + m_pWeaponEntity->m_pPlayer->pev->weaponmodel = 0; +#endif +} + +//========================================================= +// IsUseable - this function determines whether or not a +// weapon is useable by the player in its current state. +// (does it have ammo loaded? do I have any ammo for the +// weapon?, etc) +//========================================================= +bool CBaseWeaponContext :: IsUseable() +{ + if ( m_iClip <= 0 ) + { + if ( m_pFuncs->GetPlayerAmmo( PrimaryAmmoIndex() ) <= 0 && iMaxAmmo1() != -1 ) + { + // clip is empty (or nonexistant) and the player has no more ammo of this type. + return FALSE; + } + } + + return TRUE; +} + +bool CBaseWeaponContext :: CanDeploy() +{ + BOOL bHasAmmo = 0; + + if ( !pszAmmo1() ) + { + // this weapon doesn't use ammo, can always deploy. + return TRUE; + } + + if ( pszAmmo1() ) + { + bHasAmmo |= (m_pFuncs->GetPlayerAmmo(m_iPrimaryAmmoType) != 0); + } + if ( pszAmmo2() ) + { + bHasAmmo |= (m_pFuncs->GetPlayerAmmo(m_iSecondaryAmmoType) != 0); + } + if (m_iClip > 0) + { + bHasAmmo |= 1; + } + if (!bHasAmmo) + { + return FALSE; + } + + return TRUE; +} + +bool CBaseWeaponContext :: DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal /* = 0 */, int body ) +{ + if (!CanDeploy( )) + return FALSE; + +#ifndef CLIENT_DLL + //m_pWeaponEntity->m_pPlayer->TabulateAmmo(); + m_pWeaponEntity->m_pPlayer->pev->viewmodel = MAKE_STRING(szViewModel); + m_pWeaponEntity->m_pPlayer->pev->weaponmodel = MAKE_STRING(szWeaponModel); + strcpy( m_pWeaponEntity->m_pPlayer->m_szAnimExtention, szAnimExt ); +#else + // gEngfuncs.CL_LoadModel( szViewModel, &m_pPlayer->pev->viewmodel ); +#endif + SendWeaponAnim( iAnim, skiplocal, body ); + + m_pFuncs->SetPlayerNextAttackTime(m_pFuncs->GetWeaponTimeBase() + 0.5); + m_flTimeWeaponIdle = m_pFuncs->GetWeaponTimeBase() + 1.0; + return TRUE; +} + +BOOL CBaseWeaponContext :: DefaultReload( int iClipSize, int iAnim, float fDelay, int body ) +{ + if (m_pFuncs->GetPlayerAmmo(m_iPrimaryAmmoType) <= 0) + return FALSE; + + int j = Q_min(iClipSize - m_iClip, m_pFuncs->GetPlayerAmmo(m_iPrimaryAmmoType)); + + if (j == 0) + return FALSE; + + m_pFuncs->SetPlayerNextAttackTime(m_pFuncs->GetWeaponTimeBase() + fDelay); + + //!!UNDONE -- reload sound goes here !!! + SendWeaponAnim( iAnim, UseDecrement() ? 1 : 0, body ); + + m_fInReload = TRUE; + + m_flTimeWeaponIdle = m_pFuncs->GetWeaponTimeBase() + 3; + return TRUE; +} + +void CBaseWeaponContext::SendWeaponAnim( int iAnim, int skiplocal, int body ) +{ + m_pFuncs->SetPlayerWeaponAnim(iAnim); + +#ifndef CLIENT_DLL + if ( UseDecrement() ) + skiplocal = 1; + else + skiplocal = 0; + + if ( skiplocal && ENGINE_CANSKIP( m_pWeaponEntity->m_pPlayer->edict() ) ) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_WEAPONANIM, NULL, m_pWeaponEntity->m_pPlayer->pev ); + WRITE_BYTE( iAnim ); // sequence number + WRITE_BYTE( m_pFuncs->GetWeaponBodygroup() ); // weaponmodel bodygroup. + MESSAGE_END(); +#else + // HUD_SendWeaponAnim( iAnim, body, 0 ); +#endif +} + +bool CBaseWeaponContext :: PlayEmptySound() +{ + if (m_iPlayEmptySound) + { +#ifdef CLIENT_DLL + // HUD_PlaySound( "weapons/357_cock1.wav", 0.8 ); +#else + EMIT_SOUND(ENT(m_pWeaponEntity->m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); +#endif + m_iPlayEmptySound = 0; + return 0; + } + return 0; +} + +void CBaseWeaponContext :: ResetEmptySound() +{ + m_iPlayEmptySound = 1; +} + +int CBaseWeaponContext::PrimaryAmmoIndex() +{ + return m_iPrimaryAmmoType; +} + +int CBaseWeaponContext::SecondaryAmmoIndex() +{ + return -1; +} + +int CBaseWeaponContext::iItemSlot() { return 0; } // return 0 to MAX_ITEMS_SLOTS, used in hud +int CBaseWeaponContext::iItemPosition() { return CBaseWeaponContext::ItemInfoArray[ m_iId ].iPosition; } +const char *CBaseWeaponContext::pszAmmo1() { return CBaseWeaponContext::ItemInfoArray[ m_iId ].pszAmmo1; } +int CBaseWeaponContext::iMaxAmmo1() { return CBaseWeaponContext::ItemInfoArray[ m_iId ].iMaxAmmo1; } +const char *CBaseWeaponContext::pszAmmo2() { return CBaseWeaponContext::ItemInfoArray[ m_iId ].pszAmmo2; } +int CBaseWeaponContext::iMaxAmmo2() { return CBaseWeaponContext::ItemInfoArray[ m_iId ].iMaxAmmo2; } +const char *CBaseWeaponContext::pszName() { return CBaseWeaponContext::ItemInfoArray[ m_iId ].pszName; } +int CBaseWeaponContext::iMaxClip() { return CBaseWeaponContext::ItemInfoArray[ m_iId ].iMaxClip; } +int CBaseWeaponContext::iWeight() { return CBaseWeaponContext::ItemInfoArray[ m_iId ].iWeight; } +int CBaseWeaponContext::iFlags() { return CBaseWeaponContext::ItemInfoArray[ m_iId ].iFlags; } diff --git a/game_shared/weapon_context.h b/game_shared/weapon_context.h new file mode 100644 index 000000000..838f2589d --- /dev/null +++ b/game_shared/weapon_context.h @@ -0,0 +1,88 @@ +/* +weapon_context.h - part of weapons implementation common for client & server +Copyright (C) 2024 SNMetamorph + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#pragma once +#include "vector.h" +#include "item_info.h" +#include "cdll_dll.h" +#include "weapon_layer.h" + +// forward declaration, client should not know anything about server entities +class CBasePlayerWeapon; + +class CBaseWeaponContext +{ +public: + CBaseWeaponContext(IWeaponLayer *funcs); + virtual ~CBaseWeaponContext(); + void ItemPostFrame(); + + // called by CBasePlayerWeapons ItemPostFrame() + virtual void PrimaryAttack() { return; } // do "+ATTACK" + virtual void SecondaryAttack() { return; } // do "+ATTACK2" + virtual void Reload() { return; } // do "+RELOAD" + virtual void WeaponIdle() { return; } // called when no buttons pressed + + virtual bool ShouldWeaponIdle() { return FALSE; }; + virtual bool CanDeploy(); + virtual bool Deploy() { return TRUE; }; // returns is deploy was successful + virtual bool CanHolster() { return TRUE; }; // can this weapon be put away right nxow? + virtual void Holster(); + virtual bool IsUseable(); + virtual bool UseDecrement() { return TRUE; }; // always true because weapon prediction enabled regardless of anything + + virtual int GetItemInfo(ItemInfo *p) { return 0; }; // returns 0 if struct not filled out + virtual int PrimaryAmmoIndex(); + virtual int SecondaryAmmoIndex(); + + virtual int iItemSlot(); + virtual int iItemPosition(); + virtual const char *pszAmmo1(); + virtual int iMaxAmmo1(); + virtual const char *pszAmmo2(); + virtual int iMaxAmmo2(); + virtual const char *pszName(); + virtual int iMaxClip(); + virtual int iWeight(); + virtual int iFlags(); + + static ItemInfo ItemInfoArray[ MAX_WEAPONS ]; + static AmmoInfo AmmoInfoArray[ MAX_AMMO_SLOTS ]; + + bool DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal = 0, int body = 0 ); + int DefaultReload( int iClipSize, int iAnim, float fDelay, int body = 0 ); + void SendWeaponAnim( int iAnim, int skiplocal = 1, int body = 0 ); // skiplocal is 1 if client is predicting weapon animations + bool PlayEmptySound(); + void ResetEmptySound(); + + int m_iId; // WEAPON_??? + int m_iPlayEmptySound; + int m_fFireOnEmpty; // True when the gun is empty and the player is still holding down the attack key(s) + float m_flPumpTime; + int m_fInSpecialReload; // Are we in the middle of a reload for the shotguns + float m_flNextPrimaryAttack; // soonest time ItemPostFrame will call PrimaryAttack + float m_flNextSecondaryAttack; // soonest time ItemPostFrame will call SecondaryAttack + float m_flTimeWeaponIdle; // soonest time ItemPostFrame will call WeaponIdle + int m_iPrimaryAmmoType; // "primary" ammo index into players m_rgAmmo[] + int m_iSecondaryAmmoType; // "secondary" ammo index into players m_rgAmmo[] + int m_iClip; // number of shots left in the primary weapon clip, -1 it not used + int m_iClientClip; // the last version of m_iClip sent to hud dll + int m_iClientWeaponState; // the last version of the weapon state sent to hud dll (is current weapon, is on target) + int m_fInReload; // Are we in the middle of a reload; + int m_iDefaultAmmo; // how much ammo you get when you pick up this weapon as placed by a level designer. + CBasePlayerWeapon *m_pWeaponEntity; // not used on client side + IWeaponLayer *m_pFuncs; +}; + diff --git a/game_shared/weapon_layer.h b/game_shared/weapon_layer.h new file mode 100644 index 000000000..2498182dd --- /dev/null +++ b/game_shared/weapon_layer.h @@ -0,0 +1,40 @@ +/* +weapon_layer.h - interface for abstracting client & server weapons implementation differences +Copyright (C) 2024 SNMetamorph + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. +*/ + +#pragma once + +class IWeaponLayer +{ +public: + virtual ~IWeaponLayer() {}; + + // accessing weapon entity state + virtual const char *GetWeaponClassname() = 0; + virtual int GetWeaponBodygroup() = 0; + virtual bool GetNextBestWeapon() = 0; + + // modifying/accessing player state + virtual int GetPlayerAmmo(int ammoType) = 0; + virtual void SetPlayerAmmo(int ammoType, int count) = 0; + virtual void SetPlayerWeaponAnim(int anim) = 0; + virtual void SetPlayerViewmodel(int model) = 0; + virtual bool CheckPlayerButtonFlag(int buttonMask) = 0; + virtual void ClearPlayerButtonFlag(int buttonMask) = 0; + virtual float GetPlayerNextAttackTime() = 0; + virtual void SetPlayerNextAttackTime(float value) = 0; + + // miscellaneous things + virtual float GetWeaponTimeBase() = 0; +}; diff --git a/server/weapons/glock.cpp b/game_shared/weapons/glock.cpp similarity index 77% rename from server/weapons/glock.cpp rename to game_shared/weapons/glock.cpp index bb20271fc..841438cf2 100644 --- a/server/weapons/glock.cpp +++ b/game_shared/weapons/glock.cpp @@ -14,6 +14,17 @@ ****/ #include "glock.h" +//#include "weapon_logic_funcs_impl.h" + +#ifndef CLIENT_DLL +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "player.h" +#endif enum glock_e { @@ -29,40 +40,16 @@ enum glock_e GLOCK_ADD_SILENCER }; -LINK_ENTITY_TO_CLASS( weapon_glock, CGlock ); -LINK_ENTITY_TO_CLASS( weapon_9mmhandgun, CGlock ); - -void CGlock::Spawn( ) +CGlockWeaponLogic::CGlockWeaponLogic(IWeaponLayer *funcs) : + CBaseWeaponContext(funcs) { - pev->classname = MAKE_STRING( "weapon_9mmhandgun" ); // hack to allow for old names - Precache( ); - m_iId = WEAPON_GLOCK; - SET_MODEL( edict(), "models/w_9mmhandgun.mdl" ); - m_iDefaultAmmo = GLOCK_DEFAULT_GIVE; - - FallInit();// get ready to fall down. -} - -void CGlock::Precache( void ) -{ - PRECACHE_MODEL("models/v_9mmhandgun.mdl"); - PRECACHE_MODEL("models/w_9mmhandgun.mdl"); - PRECACHE_MODEL("models/p_9mmhandgun.mdl"); - - m_iShell = PRECACHE_MODEL ("models/shell.mdl");// brass shell - - PRECACHE_SOUND("items/9mmclip1.wav"); - PRECACHE_SOUND("items/9mmclip2.wav"); - - PRECACHE_SOUND ("weapons/pl_gun1.wav");//silenced handgun - PRECACHE_SOUND ("weapons/pl_gun2.wav");//silenced handgun - PRECACHE_SOUND ("weapons/pl_gun3.wav");//handgun + m_iId = WEAPON_GLOCK; } -int CGlock::GetItemInfo(ItemInfo *p) +int CGlockWeaponLogic::GetItemInfo(ItemInfo *p) { - p->pszName = STRING(pev->classname); + p->pszName = m_pFuncs->GetWeaponClassname();//STRING(pev->classname); p->pszAmmo1 = "9mm"; p->iMaxAmmo1 = _9MM_MAX_CARRY; p->pszAmmo2 = NULL; @@ -77,23 +64,23 @@ int CGlock::GetItemInfo(ItemInfo *p) return 1; } -BOOL CGlock::Deploy( ) +bool CGlockWeaponLogic::Deploy( ) { // pev->body = 1; return DefaultDeploy( "models/v_9mmhandgun.mdl", "models/p_9mmhandgun.mdl", GLOCK_DRAW, "onehanded" ); } -void CGlock::SecondaryAttack( void ) +void CGlockWeaponLogic::SecondaryAttack( void ) { GlockFire( 0.1, 0.2, FALSE ); } -void CGlock::PrimaryAttack( void ) +void CGlockWeaponLogic::PrimaryAttack( void ) { GlockFire( 0.01, 0.3, TRUE ); } -void CGlock::GlockFire( float flSpread , float flCycleTime, BOOL fUseAutoAim ) +void CGlockWeaponLogic::GlockFire( float flSpread , float flCycleTime, BOOL fUseAutoAim ) { if (m_iClip <= 0) { @@ -125,10 +112,10 @@ void CGlock::GlockFire( float flSpread , float flCycleTime, BOOL fUseAutoAim ) + gpGlobals->v_up * RANDOM_FLOAT(100,150) + gpGlobals->v_forward * 25; EjectBrass ( m_pPlayer->EyePosition() + gpGlobals->v_up * -12 + gpGlobals->v_forward * 32 + gpGlobals->v_right * 6, - vecShellVelocity, m_pPlayer->GetAbsAngles().y, m_iShell, TE_BOUNCE_SHELL ); + vecShellVelocity, m_pPlayer->GetAbsAngles().y, MODEL_INDEX("models/shell.mdl"), TE_BOUNCE_SHELL); // silenced - if (pev->body == 1) + if (m_pFuncs->GetWeaponBodygroup() == 1) { m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; m_pPlayer->m_iWeaponFlash = DIM_GUN_FLASH; @@ -175,7 +162,7 @@ void CGlock::GlockFire( float flSpread , float flCycleTime, BOOL fUseAutoAim ) m_pPlayer->pev->punchangle.x -= 2; } -void CGlock::Reload( void ) +void CGlockWeaponLogic::Reload( void ) { int iResult; @@ -190,7 +177,7 @@ void CGlock::Reload( void ) } } -void CGlock::WeaponIdle( void ) +void CGlockWeaponLogic::WeaponIdle( void ) { ResetEmptySound( ); diff --git a/game_shared/weapons/glock.h b/game_shared/weapons/glock.h new file mode 100644 index 000000000..67f3a6243 --- /dev/null +++ b/game_shared/weapons/glock.h @@ -0,0 +1,39 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is restricted to non-commercial enhancements to products from +* Valve LLC. All other use, distribution, or modification is prohibited +* without written permission from Valve LLC. +* +****/ + +#pragma once +#include "weapon_context.h" +#include "weapon_layer.h" + +#define WEAPON_GLOCK 2 +#define GLOCK_WEIGHT 10 +#define GLOCK_MAX_CLIP 17 +#define GLOCK_DEFAULT_GIVE 17 + +class CGlockWeaponLogic : public CBaseWeaponContext +{ +public: + CGlockWeaponLogic() = delete; + CGlockWeaponLogic(IWeaponLayer *funcs); + + int iItemSlot() override { return 2; } + int GetItemInfo(ItemInfo *p) override; + void PrimaryAttack() override; + void SecondaryAttack() override; + bool Deploy() override; + void Reload() override; + void WeaponIdle() override; + void GlockFire( float flSpread, float flCycleTime, BOOL fUseAutoAim ); +}; diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index ba5e4e162..ff249789d 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -48,6 +48,7 @@ list(APPEND SVDLL_SOURCES "user_messages.cpp" "util.cpp" "weapons.cpp" + "server_weapon_layer_impl.cpp" "world.cpp" ) @@ -65,6 +66,7 @@ list(APPEND SVDLL_SOURCES "${CMAKE_SOURCE_DIR}/game_shared/vector.cpp" "${CMAKE_SOURCE_DIR}/game_shared/virtualfs.cpp" "${CMAKE_SOURCE_DIR}/game_shared/trace.cpp" + "${CMAKE_SOURCE_DIR}/game_shared/weapon_context.cpp" "${CMAKE_SOURCE_DIR}/game_shared/meshdesc.cpp" "${CMAKE_SOURCE_DIR}/game_shared/meshdesc_factory.cpp" "${CMAKE_SOURCE_DIR}/game_shared/filesystem_utils.cpp" @@ -91,6 +93,7 @@ file(GLOB ENTITIES_SOURCES "entities/*.cpp") file(GLOB MONSTERS_SOURCES "monsters/*.cpp") file(GLOB WEAPONS_SOURCES "weapons/*.cpp") file(GLOB GAMERULES_SOURCES "gamerules/*.cpp") +file(GLOB WEAPONS_SHARED_SOURCES "${CMAKE_SOURCE_DIR}/game_shared/weapons/*.cpp") # add .def file to sources if(MSVC) @@ -101,6 +104,7 @@ list(APPEND SVDLL_SOURCES ${ENTITIES_SOURCES}) list(APPEND SVDLL_SOURCES ${MONSTERS_SOURCES}) list(APPEND SVDLL_SOURCES ${WEAPONS_SOURCES}) list(APPEND SVDLL_SOURCES ${GAMERULES_SOURCES}) +list(APPEND SVDLL_SOURCES ${WEAPONS_SHARED_SOURCES}) add_library(${PROJECT_NAME} SHARED ${SVDLL_SOURCES}) target_include_directories(${PROJECT_NAME} PRIVATE diff --git a/server/gamerules/multiplay_gamerules.cpp b/server/gamerules/multiplay_gamerules.cpp index ac28c586d..680668478 100644 --- a/server/gamerules/multiplay_gamerules.cpp +++ b/server/gamerules/multiplay_gamerules.cpp @@ -874,7 +874,7 @@ float CHalfLifeMultiplay :: FlWeaponRespawnTime( CBasePlayerItem *pWeapon ) //========================================================= float CHalfLifeMultiplay :: FlWeaponTryRespawn( CBasePlayerItem *pWeapon ) { - if ( pWeapon && pWeapon->m_iId && (pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD) ) + if ( pWeapon && pWeapon->iWeaponID() && (pWeapon->iFlags() & ITEM_FLAG_LIMITINWORLD)) { if ( NUMBER_OF_ENTITIES() < (gpGlobals->maxEntities - ENTITY_INTOLERANCE) ) return 0; @@ -927,7 +927,7 @@ BOOL CHalfLifeMultiplay::CanHavePlayerItem( CBasePlayer *pPlayer, CBasePlayerIte while ( it != NULL ) { - if ( it->m_iId == pItem->m_iId ) + if ( it->iWeaponID() == pItem->iWeaponID()) { return FALSE; } diff --git a/server/player.cpp b/server/player.cpp index 579f48702..5a1db0222 100644 --- a/server/player.cpp +++ b/server/player.cpp @@ -668,7 +668,7 @@ void CBasePlayer::PackDeadPlayerItems( void ) // pack the ammo while ( iPackAmmo[iPA] != -1 ) { - pWeaponBox->PackAmmo( MAKE_STRING( CBasePlayerItem::AmmoInfoArray[iPackAmmo[iPA]].pszName ), m_rgAmmo[iPackAmmo[iPA]] ); + pWeaponBox->PackAmmo( MAKE_STRING( CBaseWeaponContext::AmmoInfoArray[iPackAmmo[iPA]].pszName ), m_rgAmmo[iPackAmmo[iPA]] ); iPA++; } @@ -4090,10 +4090,10 @@ int CBasePlayer::GetAmmoIndex(const char *psz) for (i = 1; i < MAX_AMMO_SLOTS; i++) { - if ( !CBasePlayerItem::AmmoInfoArray[i].pszName ) + if ( !CBaseWeaponContext::AmmoInfoArray[i].pszName ) continue; - if (stricmp( psz, CBasePlayerItem::AmmoInfoArray[i].pszName ) == 0) + if (stricmp( psz, CBaseWeaponContext::AmmoInfoArray[i].pszName ) == 0) return i; } @@ -4449,7 +4449,7 @@ void CBasePlayer :: UpdateClientData( void ) for (i = 0; i < MAX_WEAPONS; i++) { - ItemInfo& II = CBasePlayerItem::ItemInfoArray[i]; + ItemInfo& II = CBaseWeaponContext::ItemInfoArray[i]; if ( !II.iId ) continue; @@ -4915,7 +4915,7 @@ void CBasePlayer::DropPlayerItem ( char *pszItemName ) UTIL_MakeVectors ( GetAbsAngles() ); - RemoveWeapon( pWeapon->m_iId ); // take item off hud + RemoveWeapon( pWeapon->iWeaponID() ); // take item off hud CWeaponBox *pWeaponBox = (CWeaponBox *)CBaseEntity::Create( "weaponbox", GetAbsOrigin() + gpGlobals->v_forward * 10, GetAbsAngles(), edict() ); Vector vecAngles = pWeaponBox->GetAbsAngles(); diff --git a/server/server_weapon_layer_impl.cpp b/server/server_weapon_layer_impl.cpp new file mode 100644 index 000000000..581d99208 --- /dev/null +++ b/server/server_weapon_layer_impl.cpp @@ -0,0 +1,67 @@ +#include "server_weapon_layer_impl.h" +#include "gamerules.h" + +CServerWeaponLayerImpl::CServerWeaponLayerImpl(CBasePlayerWeapon *weaponEntity) : + m_pWeapon(weaponEntity) +{ +} + +const char *CServerWeaponLayerImpl::GetWeaponClassname() +{ + return STRING(m_pWeapon->pev->classname); +} + +int CServerWeaponLayerImpl::GetWeaponBodygroup() +{ + return m_pWeapon->pev->body; +} + +bool CServerWeaponLayerImpl::GetNextBestWeapon() +{ + return g_pGameRules->GetNextBestWeapon( m_pWeapon->m_pPlayer, m_pWeapon ); +} + +int CServerWeaponLayerImpl::GetPlayerAmmo(int ammoType) +{ + return m_pWeapon->m_pPlayer->m_rgAmmo[ammoType]; +} + +void CServerWeaponLayerImpl::SetPlayerAmmo(int ammoType, int count) +{ + m_pWeapon->m_pPlayer->m_rgAmmo[ammoType] = count; +} + +void CServerWeaponLayerImpl::SetPlayerWeaponAnim(int anim) +{ + m_pWeapon->m_pPlayer->pev->weaponanim = anim; +} + +void CServerWeaponLayerImpl::SetPlayerViewmodel(int model) +{ + m_pWeapon->m_pPlayer->pev->viewmodel = model; +} + +bool CServerWeaponLayerImpl::CheckPlayerButtonFlag(int buttonMask) +{ + return FBitSet(m_pWeapon->m_pPlayer->pev->button, buttonMask); +} + +void CServerWeaponLayerImpl::ClearPlayerButtonFlag(int buttonMask) +{ + ClearBits(m_pWeapon->m_pPlayer->pev->button, buttonMask); +} + +float CServerWeaponLayerImpl::GetPlayerNextAttackTime() +{ + return 0.0f; // zero because we're using predicting +} + +void CServerWeaponLayerImpl::SetPlayerNextAttackTime(float value) +{ + +} + +float CServerWeaponLayerImpl::GetWeaponTimeBase() +{ + return 0.0f; +} diff --git a/server/server_weapon_layer_impl.h b/server/server_weapon_layer_impl.h new file mode 100644 index 000000000..df79f1271 --- /dev/null +++ b/server/server_weapon_layer_impl.h @@ -0,0 +1,32 @@ +#pragma once +#include "weapon_layer.h" +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" + +class CServerWeaponLayerImpl : public IWeaponLayer +{ +public: + CServerWeaponLayerImpl(CBasePlayerWeapon *weaponEntity); + + const char *GetWeaponClassname() override; + int GetWeaponBodygroup() override; + bool GetNextBestWeapon() override; + + int GetPlayerAmmo(int ammoType) override; + void SetPlayerAmmo(int ammoType, int count) override; + void SetPlayerWeaponAnim(int anim) override; + void SetPlayerViewmodel(int model) override; + bool CheckPlayerButtonFlag(int buttonMask) override; + void ClearPlayerButtonFlag(int buttonMask) override; + float GetPlayerNextAttackTime() override; + void SetPlayerNextAttackTime(float value) override; + + float GetWeaponTimeBase() override; + +private: + CBasePlayerWeapon *m_pWeapon; +}; diff --git a/server/stats.cpp b/server/stats.cpp index 5650336d3..c8bf46e75 100644 --- a/server/stats.cpp +++ b/server/stats.cpp @@ -109,7 +109,7 @@ void UpdateStats( CBasePlayer *pPlayer ) int index = pPlayer->GetAmmoIndex(II.pszAmmo1); if ( index >= 0 ) - ammoCount[ index ] += ((CBasePlayerWeapon *)p)->m_iClip; + ammoCount[ index ] += ((CBasePlayerWeapon *)p)->m_pWeaponLogic->m_iClip; p = p->m_pNext; } @@ -118,7 +118,7 @@ void UpdateStats( CBasePlayer *pPlayer ) float ammo = 0; for (i = 1; i < MAX_AMMO_SLOTS; i++) { - ammo += ammoCount[i] * AmmoDamage( CBasePlayerItem::AmmoInfoArray[i].pszName ); + ammo += ammoCount[i] * AmmoDamage( CBaseWeaponContext::AmmoInfoArray[i].pszName ); } float health = pPlayer->pev->health + pPlayer->pev->armorvalue * 2; // Armor is 2X health diff --git a/server/weapons.cpp b/server/weapons.cpp index 54ec7477d..a33215837 100644 --- a/server/weapons.cpp +++ b/server/weapons.cpp @@ -47,9 +47,6 @@ DLL_GLOBAL short g_sModelIndexBubbles;// holds the index for the bubbles model DLL_GLOBAL short g_sModelIndexBloodDrop;// holds the sprite index for the initial blood DLL_GLOBAL short g_sModelIndexBloodSpray;// holds the sprite index for splattered blood -ItemInfo CBasePlayerItem::ItemInfoArray[MAX_WEAPONS]; -AmmoInfo CBasePlayerItem::AmmoInfoArray[MAX_AMMO_SLOTS]; - MULTIDAMAGE gMultiDamage; #define TRACER_FREQ 4 // Tracers fire every fourth bullet @@ -64,10 +61,10 @@ int MaxAmmoCarry( int iszName ) { for ( int i = 0; i < MAX_WEAPONS; i++ ) { - if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo1 && !strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo1 ) ) - return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo1; - if ( CBasePlayerItem::ItemInfoArray[i].pszAmmo2 && !strcmp( STRING(iszName), CBasePlayerItem::ItemInfoArray[i].pszAmmo2 ) ) - return CBasePlayerItem::ItemInfoArray[i].iMaxAmmo2; + if ( CBaseWeaponContext::ItemInfoArray[i].pszAmmo1 && !strcmp( STRING(iszName), CBaseWeaponContext::ItemInfoArray[i].pszAmmo1 ) ) + return CBaseWeaponContext::ItemInfoArray[i].iMaxAmmo1; + if ( CBaseWeaponContext::ItemInfoArray[i].pszAmmo2 && !strcmp( STRING(iszName), CBaseWeaponContext::ItemInfoArray[i].pszAmmo2 ) ) + return CBaseWeaponContext::ItemInfoArray[i].iMaxAmmo2; } ALERT( at_console, "MaxAmmoCarry() doesn't recognize '%s'!\n", STRING( iszName ) ); @@ -241,10 +238,10 @@ void AddAmmoNameToAmmoRegistry( const char *szAmmoname ) // make sure it's not already in the registry for ( int i = 0; i < MAX_AMMO_SLOTS; i++ ) { - if ( !CBasePlayerItem::AmmoInfoArray[i].pszName) + if ( !CBaseWeaponContext::AmmoInfoArray[i].pszName) continue; - if ( stricmp( CBasePlayerItem::AmmoInfoArray[i].pszName, szAmmoname ) == 0 ) + if ( stricmp( CBaseWeaponContext::AmmoInfoArray[i].pszName, szAmmoname ) == 0 ) return; // ammo already in registry, just quite } @@ -254,8 +251,8 @@ void AddAmmoNameToAmmoRegistry( const char *szAmmoname ) if ( giAmmoIndex >= MAX_AMMO_SLOTS ) giAmmoIndex = 0; - CBasePlayerItem::AmmoInfoArray[giAmmoIndex].pszName = szAmmoname; - CBasePlayerItem::AmmoInfoArray[giAmmoIndex].iId = giAmmoIndex; // yes, this info is redundant + CBaseWeaponContext::AmmoInfoArray[giAmmoIndex].pszName = szAmmoname; + CBaseWeaponContext::AmmoInfoArray[giAmmoIndex].iId = giAmmoIndex; // yes, this info is redundant } @@ -276,7 +273,7 @@ void UTIL_PrecacheOtherWeapon( const char *szClassname ) if ( ((CBasePlayerItem*)pEntity)->GetItemInfo( &II ) ) { - CBasePlayerItem::ItemInfoArray[II.iId] = II; + CBaseWeaponContext::ItemInfoArray[II.iId] = II; if ( II.pszAmmo1 && *II.pszAmmo1 ) { @@ -297,8 +294,8 @@ void UTIL_PrecacheOtherWeapon( const char *szClassname ) // called by worldspawn void W_Precache(void) { - memset( CBasePlayerItem::ItemInfoArray, 0, sizeof(CBasePlayerItem::ItemInfoArray) ); - memset( CBasePlayerItem::AmmoInfoArray, 0, sizeof(CBasePlayerItem::AmmoInfoArray) ); + memset( CBaseWeaponContext::ItemInfoArray, 0, sizeof(CBaseWeaponContext::ItemInfoArray) ); + memset( CBaseWeaponContext::AmmoInfoArray, 0, sizeof(CBaseWeaponContext::AmmoInfoArray) ); giAmmoIndex = 0; // custom items... @@ -398,7 +395,6 @@ void W_Precache(void) BEGIN_DATADESC( CBasePlayerItem ) DEFINE_FIELD( m_pPlayer, FIELD_CLASSPTR ), DEFINE_FIELD( m_pNext, FIELD_CLASSPTR ), - DEFINE_FIELD( m_iId, FIELD_INTEGER ), DEFINE_FUNCTION( DestroyItem ), DEFINE_FUNCTION( DefaultTouch ), DEFINE_FUNCTION( FallThink ), @@ -407,13 +403,14 @@ BEGIN_DATADESC( CBasePlayerItem ) END_DATADESC() BEGIN_DATADESC( CBasePlayerWeapon ) - DEFINE_FIELD( m_flNextPrimaryAttack, FIELD_TIME ), - DEFINE_FIELD( m_flNextSecondaryAttack, FIELD_TIME ), - DEFINE_FIELD( m_flTimeWeaponIdle, FIELD_TIME ), - DEFINE_FIELD( m_iPrimaryAmmoType, FIELD_INTEGER ), - DEFINE_FIELD( m_iSecondaryAmmoType, FIELD_INTEGER ), - DEFINE_FIELD( m_iClip, FIELD_INTEGER ), - DEFINE_FIELD( m_iDefaultAmmo, FIELD_INTEGER ), + //DEFINE_FIELD( m_flNextPrimaryAttack, FIELD_TIME ), + //DEFINE_FIELD( m_flNextSecondaryAttack, FIELD_TIME ), + //DEFINE_FIELD( m_flTimeWeaponIdle, FIELD_TIME ), + //DEFINE_FIELD( m_iPrimaryAmmoType, FIELD_INTEGER ), + //DEFINE_FIELD( m_iSecondaryAmmoType, FIELD_INTEGER ), + //DEFINE_FIELD( m_iClip, FIELD_INTEGER ), + //DEFINE_FIELD( m_iDefaultAmmo, FIELD_INTEGER ), + //DEFINE_FIELD( m_iId, FIELD_INTEGER ), END_DATADESC() void CBasePlayerItem :: SetObjectCollisionBox( void ) @@ -584,84 +581,7 @@ void CBasePlayerItem::DefaultTouch( CBaseEntity *pOther ) SUB_UseTargets( pOther, USE_TOGGLE, 0 ); // UNDONE: when should this happen? } -BOOL CanAttack( float attack_time, float curtime, BOOL isPredicted ) -{ - return ( attack_time <= curtime ) ? TRUE : FALSE; -} - -void CBasePlayerWeapon::ItemPostFrame( void ) -{ - if ((m_fInReload) && ( m_pPlayer->m_flNextAttack <= gpGlobals->time )) - { - // complete the reload. - int j = Q_min( iMaxClip() - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); - - // Add them to the clip - m_iClip += j; - m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] -= j; - - m_fInReload = FALSE; - } - - if ((m_pPlayer->pev->button & IN_ATTACK2) && CanAttack( m_flNextSecondaryAttack, gpGlobals->time, UseDecrement() ) ) - { - if ( pszAmmo2() && !m_pPlayer->m_rgAmmo[SecondaryAmmoIndex()] ) - { - m_fFireOnEmpty = TRUE; - } - - SecondaryAttack(); - m_pPlayer->pev->button &= ~IN_ATTACK2; - } - else if ((m_pPlayer->pev->button & IN_ATTACK) && CanAttack( m_flNextPrimaryAttack, gpGlobals->time, UseDecrement() ) ) - { - if ( (m_iClip == 0 && pszAmmo1()) || (iMaxClip() == -1 && !m_pPlayer->m_rgAmmo[PrimaryAmmoIndex()] ) ) - { - m_fFireOnEmpty = TRUE; - } - - PrimaryAttack(); - } - else if ( m_pPlayer->pev->button & IN_RELOAD && iMaxClip() != WEAPON_NOCLIP && !m_fInReload ) - { - // reload when reload is pressed, or if no buttons are down and weapon is empty. - Reload(); - } - else if ( !(m_pPlayer->pev->button & (IN_ATTACK|IN_ATTACK2) ) ) - { - // no fire buttons down - - m_fFireOnEmpty = FALSE; - - if ( !IsUseable() && m_flNextPrimaryAttack < ( UseDecrement() ? 0.0 : gpGlobals->time ) ) - { - // weapon isn't useable, switch. - if ( !(iFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) && g_pGameRules->GetNextBestWeapon( m_pPlayer, this ) ) - { - m_flNextPrimaryAttack = ( UseDecrement() ? 0.0 : gpGlobals->time ) + 0.3; - return; - } - } - else - { - // weapon is useable. Reload if empty and weapon has waited as long as it has to after firing - if ( m_iClip == 0 && !(iFlags() & ITEM_FLAG_NOAUTORELOAD) && m_flNextPrimaryAttack < ( UseDecrement() ? 0.0 : gpGlobals->time ) ) - { - Reload(); - return; - } - } - WeaponIdle( ); - return; - } - - // catch all - if ( ShouldWeaponIdle() ) - { - WeaponIdle(); - } -} void CBasePlayerItem::DestroyItem( void ) { @@ -720,7 +640,7 @@ void CBasePlayerItem::AttachToPlayer ( CBasePlayer *pPlayer ) // CALLED THROUGH the newly-touched weapon's instance. The existing player weapon is pOriginal int CBasePlayerWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) { - if ( m_iDefaultAmmo ) + if ( m_pWeaponLogic->m_iDefaultAmmo ) { return ExtractAmmo( (CBasePlayerWeapon *)pOriginal ); } @@ -731,22 +651,33 @@ int CBasePlayerWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) } } +CBasePlayerWeapon::CBasePlayerWeapon() : + m_pWeaponLogic(nullptr) +{ +} + +CBasePlayerWeapon::~CBasePlayerWeapon() +{ + if (m_pWeaponLogic) { + delete m_pWeaponLogic; + } +} int CBasePlayerWeapon::AddToPlayer( CBasePlayer *pPlayer ) { int bResult = CBasePlayerItem::AddToPlayer( pPlayer ); - pPlayer->AddWeapon( m_iId ); + pPlayer->AddWeapon( m_pWeaponLogic->m_iId ); - if ( !m_iPrimaryAmmoType ) + if ( !m_pWeaponLogic->m_iPrimaryAmmoType ) { - m_iPrimaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo1() ); - m_iSecondaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo2() ); + m_pWeaponLogic->m_iPrimaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo1() ); + m_pWeaponLogic->m_iSecondaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo2() ); } - if (bResult) return AddWeapon( ); + return FALSE; } @@ -779,8 +710,8 @@ int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) } // If the ammo, state, or fov has changed, update the weapon - if ( m_iClip != m_iClientClip || - state != m_iClientWeaponState || + if ( m_pWeaponLogic->m_iClip != m_pWeaponLogic->m_iClientClip || + state != m_pWeaponLogic->m_iClientWeaponState || pPlayer->m_iFOV != pPlayer->m_iClientFOV ) { bSend = TRUE; @@ -790,12 +721,12 @@ int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) { MESSAGE_BEGIN( MSG_ONE, gmsgCurWeapon, NULL, pPlayer->pev ); WRITE_BYTE( state ); - WRITE_BYTE( m_iId ); - WRITE_BYTE( m_iClip ); + WRITE_BYTE( m_pWeaponLogic->m_iId ); + WRITE_BYTE( m_pWeaponLogic->m_iClip ); MESSAGE_END(); - m_iClientClip = m_iClip; - m_iClientWeaponState = state; + m_pWeaponLogic->m_iClientClip = m_pWeaponLogic->m_iClip; + m_pWeaponLogic->m_iClientWeaponState = state; pPlayer->m_fWeapon = TRUE; } @@ -805,36 +736,20 @@ int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) return 1; } - -void CBasePlayerWeapon::SendWeaponAnim( int iAnim, int skiplocal, int body ) -{ - if ( UseDecrement() ) - skiplocal = 1; - else - skiplocal = 0; - - m_pPlayer->pev->weaponanim = iAnim; - - MESSAGE_BEGIN( MSG_ONE, SVC_WEAPONANIM, NULL, m_pPlayer->pev ); - WRITE_BYTE( iAnim ); // sequence number - WRITE_BYTE( pev->body ); // weaponmodel bodygroup. - MESSAGE_END(); -} - BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip, int iMaxCarry ) { int iIdAmmo; if (iMaxClip < 1) { - m_iClip = -1; + m_pWeaponLogic->m_iClip = -1; iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); } - else if (m_iClip == 0) + else if (m_pWeaponLogic->m_iClip == 0) { int i; - i = Q_min( m_iClip + iCount, iMaxClip ) - m_iClip; - m_iClip += i; + i = Q_min( m_pWeaponLogic->m_iClip + iCount, iMaxClip ) - m_pWeaponLogic->m_iClip; + m_pWeaponLogic->m_iClip += i; iIdAmmo = m_pPlayer->GiveAmmo( iCount - i, szName, iMaxCarry ); } else @@ -846,7 +761,7 @@ BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip if (iIdAmmo > 0) { - m_iPrimaryAmmoType = iIdAmmo; + m_pWeaponLogic->m_iPrimaryAmmoType = iIdAmmo; if (m_pPlayer->HasPlayerItem( this ) ) { // play the "got ammo" sound only if we gave some ammo to a player that already had this gun. @@ -869,137 +784,12 @@ BOOL CBasePlayerWeapon :: AddSecondaryAmmo( int iCount, char *szName, int iMax ) if (iIdAmmo > 0) { - m_iSecondaryAmmoType = iIdAmmo; + m_pWeaponLogic->m_iSecondaryAmmoType = iIdAmmo; EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); } return iIdAmmo > 0 ? TRUE : FALSE; } -//========================================================= -// IsUseable - this function determines whether or not a -// weapon is useable by the player in its current state. -// (does it have ammo loaded? do I have any ammo for the -// weapon?, etc) -//========================================================= -BOOL CBasePlayerWeapon :: IsUseable( void ) -{ - if ( m_iClip <= 0 ) - { - if ( m_pPlayer->m_rgAmmo[ PrimaryAmmoIndex() ] <= 0 && iMaxAmmo1() != -1 ) - { - // clip is empty (or nonexistant) and the player has no more ammo of this type. - return FALSE; - } - } - - return TRUE; -} - -BOOL CBasePlayerWeapon :: CanDeploy( void ) -{ - BOOL bHasAmmo = 0; - - if ( !pszAmmo1() ) - { - // this weapon doesn't use ammo, can always deploy. - return TRUE; - } - - if ( pszAmmo1() ) - { - bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] != 0); - } - if ( pszAmmo2() ) - { - bHasAmmo |= (m_pPlayer->m_rgAmmo[m_iSecondaryAmmoType] != 0); - } - if (m_iClip > 0) - { - bHasAmmo |= 1; - } - if (!bHasAmmo) - { - return FALSE; - } - - return TRUE; -} - -BOOL CBasePlayerWeapon :: DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal /* = 0 */, int body ) -{ - if (!CanDeploy( )) - return FALSE; - - m_pPlayer->pev->viewmodel = MAKE_STRING(szViewModel); - m_pPlayer->pev->weaponmodel = MAKE_STRING(szWeaponModel); - strcpy( m_pPlayer->m_szAnimExtention, szAnimExt ); - SendWeaponAnim( iAnim, skiplocal, body ); - - m_pPlayer->m_flNextAttack = gpGlobals->time + 0.5; - m_flTimeWeaponIdle = gpGlobals->time + 1.0; - - return TRUE; -} - - -BOOL CBasePlayerWeapon :: DefaultReload( int iClipSize, int iAnim, float fDelay, int body ) -{ - if (m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) - return FALSE; - - int j = Q_min(iClipSize - m_iClip, m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType]); - - if (j == 0) - return FALSE; - - m_pPlayer->m_flNextAttack = gpGlobals->time + fDelay; - - //!!UNDONE -- reload sound goes here !!! - SendWeaponAnim( iAnim, UseDecrement() ? 1 : 0 ); - - m_fInReload = TRUE; - - m_flTimeWeaponIdle = gpGlobals->time + 3; - return TRUE; -} - -BOOL CBasePlayerWeapon :: PlayEmptySound( void ) -{ - if (m_iPlayEmptySound) - { - EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/357_cock1.wav", 0.8, ATTN_NORM); - m_iPlayEmptySound = 0; - return 0; - } - return 0; -} - -void CBasePlayerWeapon :: ResetEmptySound( void ) -{ - m_iPlayEmptySound = 1; -} - -//========================================================= -//========================================================= -int CBasePlayerWeapon::PrimaryAmmoIndex( void ) -{ - return m_iPrimaryAmmoType; -} - -//========================================================= -//========================================================= -int CBasePlayerWeapon::SecondaryAmmoIndex( void ) -{ - return -1; -} - -void CBasePlayerWeapon::Holster( void ) -{ - m_fInReload = FALSE; // cancel any reload in progress. - m_pPlayer->pev->viewmodel = 0; - m_pPlayer->pev->weaponmodel = 0; -} - BEGIN_DATADESC( CBasePlayerAmmo ) DEFINE_FUNCTION( DefaultTouch ), DEFINE_FUNCTION( Materialize ), @@ -1083,8 +873,8 @@ int CBasePlayerWeapon::ExtractAmmo( CBasePlayerWeapon *pWeapon ) { // blindly call with m_iDefaultAmmo. It's either going to be a value or zero. If it is zero, // we only get the ammo in the weapon's clip, which is what we want. - iReturn = pWeapon->AddPrimaryAmmo( m_iDefaultAmmo, (char *)pszAmmo1(), iMaxClip(), iMaxAmmo1() ); - m_iDefaultAmmo = 0; + iReturn = pWeapon->AddPrimaryAmmo( m_pWeaponLogic->m_iDefaultAmmo, (char *)pszAmmo1(), iMaxClip(), iMaxAmmo1() ); + m_pWeaponLogic->m_iDefaultAmmo = 0; } if ( pszAmmo2() != NULL ) @@ -1102,13 +892,13 @@ int CBasePlayerWeapon::ExtractClipAmmo( CBasePlayerWeapon *pWeapon ) { int iAmmo; - if ( m_iClip == WEAPON_NOCLIP ) + if ( m_pWeaponLogic->m_iClip == WEAPON_NOCLIP ) { iAmmo = 0;// guns with no clips always come empty if they are second-hand } else { - iAmmo = m_iClip; + iAmmo = m_pWeaponLogic->m_iClip; } return pWeapon->m_pPlayer->GiveAmmo( iAmmo, (char *)pszAmmo1(), iMaxAmmo1() ); // , &m_iPrimaryAmmoType @@ -1465,17 +1255,17 @@ void CWeaponBox::SetObjectCollisionBox( void ) void CBasePlayerWeapon::PrintState( void ) { - ALERT( at_console, "primary: %f\n", m_flNextPrimaryAttack ); - ALERT( at_console, "idle : %f\n", m_flTimeWeaponIdle ); +// ALERT( at_console, "primary: %f\n", m_flNextPrimaryAttack ); +// ALERT( at_console, "idle : %f\n", m_flTimeWeaponIdle ); // ALERT( at_console, "nextrl : %f\n", m_flNextReload ); // ALERT( at_console, "nextpum: %f\n", m_flPumpTime ); // ALERT( at_console, "m_frt : %f\n", m_fReloadTime ); - ALERT( at_console, "m_finre: %i\n", m_fInReload ); +// ALERT( at_console, "m_finre: %i\n", m_fInReload ); // ALERT( at_console, "m_finsr: %i\n", m_fInSpecialReload ); - ALERT( at_console, "m_iclip: %i\n", m_iClip ); +// ALERT( at_console, "m_iclip: %i\n", m_iClip ); } //========================================================= diff --git a/server/weapons.h b/server/weapons.h index 70379ad89..6ab6d4bf3 100644 --- a/server/weapons.h +++ b/server/weapons.h @@ -15,7 +15,10 @@ #ifndef WEAPONS_H #define WEAPONS_H +#include "cbase.h" #include "effects.h" +#include "item_info.h" +#include "weapon_context.h" class CBasePlayer; @@ -29,7 +32,6 @@ void DeactivateSatchels( CBasePlayer *pOwner ); #define WEAPON_NONE 0 #define WEAPON_CROWBAR 1 -#define WEAPON_GLOCK 2 #define WEAPON_PYTHON 3 #define WEAPON_MP5 4 #define WEAPON_CYCLER 5 @@ -44,14 +46,10 @@ void DeactivateSatchels( CBasePlayer *pOwner ); #define WEAPON_SATCHEL 14 #define WEAPON_SNARK 15 -#define WEAPON_ALLWEAPONS (~(1<ItemPostFrame(); }; // called each frame by the player PostThink + + int PrimaryAmmoIndex() override { return m_pWeaponLogic->PrimaryAmmoIndex(); }; // forward to weapon logic + int SecondaryAmmoIndex() override { return m_pWeaponLogic->SecondaryAmmoIndex(); }; // forward to weapon logic + + // forward all this to weapon logic + virtual int GetItemInfo(ItemInfo *p) override { return m_pWeaponLogic->GetItemInfo(p); }; // returns 0 if struct not filled out + virtual BOOL CanDeploy( void ) override { return m_pWeaponLogic->CanDeploy(); }; + virtual BOOL Deploy() override { return m_pWeaponLogic->Deploy(); }; // returns is deploy was successful + virtual BOOL CanHolster( void ) override { return m_pWeaponLogic->CanHolster(); }; // can this weapon be put away right nxow? + virtual void Holster(void) override { m_pWeaponLogic->Holster(); }; + + void UpdateItemInfo( void ) override {}; // updates HUD state + CBasePlayerItem *GetWeaponPtr( void ) override { return (CBasePlayerItem *)this; }; + + // forward all of them to weapon logic + int iItemSlot() override { return m_pWeaponLogic->iItemSlot(); } + int iItemPosition() override { return m_pWeaponLogic->iItemPosition(); } + const char *pszAmmo1() override { return m_pWeaponLogic->pszAmmo1(); } + int iMaxAmmo1() override { return m_pWeaponLogic->iMaxAmmo1(); } + const char *pszAmmo2() override { return m_pWeaponLogic->pszAmmo2(); } + int iMaxAmmo2() override { return m_pWeaponLogic->iMaxAmmo2(); } + const char *pszName() override { return m_pWeaponLogic->pszName(); } + int iMaxClip() override { return m_pWeaponLogic->iMaxClip(); } + int iWeight() override { return m_pWeaponLogic->iWeight(); } + int iFlags() override { return m_pWeaponLogic->iFlags(); } + int iWeaponID() override { return m_pWeaponLogic->m_iId; } + +protected: + int ExtractAmmo( CBasePlayerWeapon *pWeapon ); // Return TRUE if you can add ammo to yourself when picked up + int ExtractClipAmmo( CBasePlayerWeapon *pWeapon ); // Return TRUE if you can add ammo to yourself when picked up + + int AddWeapon( void ) { ExtractAmmo( this ); return TRUE; }; // Return TRUE if you want to add yourself to the player + void RetireWeapon( void ); // generic "shared" ammo handlers BOOL AddPrimaryAmmo( int iCount, char *szName, int iMaxClip, int iMaxCarry ); BOOL AddSecondaryAmmo( int iCount, char *szName, int iMaxCarry ); - - virtual void UpdateItemInfo( void ) {}; // updates HUD state - - int m_iPlayEmptySound; - int m_fFireOnEmpty; // True when the gun is empty and the player is still holding down the - // attack key(s) - virtual BOOL PlayEmptySound( void ); - virtual void ResetEmptySound( void ); - - virtual void SendWeaponAnim( int iAnim, int skiplocal = 1, int body = 0 ); // skiplocal is 1 if client is predicting weapon animations - - virtual BOOL CanDeploy( void ); - virtual BOOL IsUseable( void ); - BOOL DefaultDeploy( char *szViewModel, char *szWeaponModel, int iAnim, char *szAnimExt, int skiplocal = 0, int body = 0 ); - int DefaultReload( int iClipSize, int iAnim, float fDelay, int body = 0 ); - - virtual void ItemPostFrame( void ); // called each frame by the player PostThink - // called by CBasePlayerWeapons ItemPostFrame() - virtual void PrimaryAttack( void ) { return; } // do "+ATTACK" - virtual void SecondaryAttack( void ) { return; } // do "+ATTACK2" - virtual void Reload( void ) { return; } // do "+RELOAD" - virtual void WeaponIdle( void ) { return; } // called when no buttons pressed - virtual int UpdateClientData( CBasePlayer *pPlayer ); // sends hud info to client dll, if things have changed - virtual void RetireWeapon( void ); - virtual BOOL ShouldWeaponIdle( void ) {return FALSE; }; - virtual void Holster( void ); - virtual BOOL UseDecrement( void ) { return FALSE; }; - - int PrimaryAmmoIndex(); - int SecondaryAmmoIndex(); - void PrintState( void ); - - virtual CBasePlayerItem *GetWeaponPtr( void ) { return (CBasePlayerItem *)this; }; - - float m_flPumpTime; - int m_fInSpecialReload; // Are we in the middle of a reload for the shotguns - float m_flNextPrimaryAttack; // soonest time ItemPostFrame will call PrimaryAttack - float m_flNextSecondaryAttack; // soonest time ItemPostFrame will call SecondaryAttack - float m_flTimeWeaponIdle; // soonest time ItemPostFrame will call WeaponIdle - int m_iPrimaryAmmoType; // "primary" ammo index into players m_rgAmmo[] - int m_iSecondaryAmmoType; // "secondary" ammo index into players m_rgAmmo[] - int m_iClip; // number of shots left in the primary weapon clip, -1 it not used - int m_iClientClip; // the last version of m_iClip sent to hud dll - int m_iClientWeaponState; // the last version of the weapon state sent to hud dll (is current weapon, is on target) - int m_fInReload; // Are we in the middle of a reload; - - int m_iDefaultAmmo;// how much ammo you get when you pick up this weapon as placed by a level designer. - }; class CBasePlayerAmmo : public CBaseEntity diff --git a/server/weapons/crossbow.cpp b/server/weapons/crossbow.cpp index 0f37a018a..b0b48e739 100644 --- a/server/weapons/crossbow.cpp +++ b/server/weapons/crossbow.cpp @@ -15,6 +15,7 @@ #include "crossbow.h" #include "crossbow_bolt.h" +#include "weapon_logic_funcs_impl.h" #define BOLT_AIR_VELOCITY 2000 #define BOLT_WATER_VELOCITY 1000 @@ -38,19 +39,17 @@ enum crossbow_e LINK_ENTITY_TO_CLASS( weapon_crossbow, CCrossbow ); BEGIN_DATADESC( CCrossbow ) - DEFINE_FIELD( m_fInZoom, FIELD_BOOLEAN ), - DEFINE_FIELD( m_fZoomInUse, FIELD_BOOLEAN ), + //DEFINE_FIELD( m_fInZoom, FIELD_BOOLEAN ), + //DEFINE_FIELD( m_fZoomInUse, FIELD_BOOLEAN ), END_DATADESC() void CCrossbow::Spawn( void ) { + pev->classname = MAKE_STRING( "weapon_crossbow" ); Precache( ); - m_iId = WEAPON_CROSSBOW; SET_MODEL(ENT(pev), "models/w_crossbow.mdl"); - - m_iDefaultAmmo = CROSSBOW_DEFAULT_GIVE; - FallInit();// get ready to fall down. + m_pWeaponLogic = new CCrossbowWeaponLogic(new CServerWeaponLayerImpl(this)); } int CCrossbow::AddToPlayer( CBasePlayer *pPlayer ) @@ -58,7 +57,7 @@ int CCrossbow::AddToPlayer( CBasePlayer *pPlayer ) if ( CBasePlayerWeapon::AddToPlayer( pPlayer ) ) { MESSAGE_BEGIN( MSG_ONE, gmsgWeapPickup, NULL, pPlayer->pev ); - WRITE_BYTE( m_iId ); + WRITE_BYTE( m_pWeaponLogic->m_iId ); MESSAGE_END(); return TRUE; } @@ -77,9 +76,16 @@ void CCrossbow::Precache( void ) UTIL_PrecacheOther( "crossbow_bolt" ); } -int CCrossbow::GetItemInfo(ItemInfo *p) +CCrossbowWeaponLogic::CCrossbowWeaponLogic(IWeaponLayer *funcs) : + CBaseWeaponContext(funcs) +{ + m_iId = WEAPON_CROSSBOW; + m_iDefaultAmmo = CROSSBOW_DEFAULT_GIVE; +} + +int CCrossbowWeaponLogic::GetItemInfo(ItemInfo *p) { - p->pszName = STRING(pev->classname); + p->pszName = m_pFuncs->GetWeaponClassname(); //STRING(pev->classname); p->pszAmmo1 = "bolts"; p->iMaxAmmo1 = BOLT_MAX_CARRY; p->pszAmmo2 = NULL; @@ -93,14 +99,14 @@ int CCrossbow::GetItemInfo(ItemInfo *p) return 1; } -BOOL CCrossbow::Deploy( ) +bool CCrossbowWeaponLogic::Deploy( ) { if (m_iClip) return DefaultDeploy( "models/v_crossbow.mdl", "models/p_crossbow.mdl", CROSSBOW_DRAW1, "bow" ); return DefaultDeploy( "models/v_crossbow.mdl", "models/p_crossbow.mdl", CROSSBOW_DRAW2, "bow" ); } -void CCrossbow::Holster( void ) +void CCrossbowWeaponLogic::Holster( void ) { m_fInReload = FALSE;// cancel any reload in progress. @@ -117,7 +123,7 @@ void CCrossbow::Holster( void ) SendWeaponAnim( CROSSBOW_HOLSTER2 ); } -void CCrossbow::PrimaryAttack( void ) +void CCrossbowWeaponLogic::PrimaryAttack( void ) { if ( m_fInZoom && g_pGameRules->IsMultiplayer() ) { @@ -129,7 +135,7 @@ void CCrossbow::PrimaryAttack( void ) } // this function only gets called in multiplayer -void CCrossbow::FireSniperBolt() +void CCrossbowWeaponLogic::FireSniperBolt() { m_flNextPrimaryAttack = gpGlobals->time + 0.75; @@ -210,7 +216,7 @@ void CCrossbow::FireSniperBolt() } } -void CCrossbow::FireBolt( void ) +void CCrossbowWeaponLogic::FireBolt( void ) { TraceResult tr; @@ -292,7 +298,7 @@ void CCrossbow::FireBolt( void ) m_pPlayer->pev->punchangle.x -= 2; } -void CCrossbow::SecondaryAttack( void ) +void CCrossbowWeaponLogic::SecondaryAttack( void ) { // do not switch zoom when player stay button is pressed if (m_fZoomInUse) @@ -315,7 +321,7 @@ void CCrossbow::SecondaryAttack( void ) m_flTimeWeaponIdle = gpGlobals->time + 5.0; } -void CCrossbow::Reload( void ) +void CCrossbowWeaponLogic::Reload( void ) { if ( m_fInZoom ) { @@ -329,7 +335,7 @@ void CCrossbow::Reload( void ) } } -void CCrossbow::WeaponIdle( void ) +void CCrossbowWeaponLogic::WeaponIdle( void ) { m_pPlayer->GetAutoaimVector( AUTOAIM_2DEGREES ); // get the autoaim vector but ignore it; used for autoaim crosshair in DM diff --git a/server/weapons/crossbow.h b/server/weapons/crossbow.h index bb9dc2a3c..0923c3077 100644 --- a/server/weapons/crossbow.h +++ b/server/weapons/crossbow.h @@ -23,13 +23,14 @@ #include "player.h" #include "gamerules.h" #include "user_messages.h" +#include "weapon_layer.h" -class CCrossbow : public CBasePlayerWeapon +class CCrossbowWeaponLogic : public CBaseWeaponContext { - DECLARE_CLASS( CCrossbow, CBasePlayerWeapon ); public: - void Spawn( void ); - void Precache( void ); + CCrossbowWeaponLogic() = delete; + CCrossbowWeaponLogic(IWeaponLayer *funcs); + int iItemSlot( ) { return 3; } int GetItemInfo(ItemInfo *p); @@ -37,11 +38,7 @@ class CCrossbow : public CBasePlayerWeapon void FireSniperBolt( void ); void PrimaryAttack( void ); void SecondaryAttack( void ); - int AddToPlayer( CBasePlayer *pPlayer ); - - DECLARE_DATADESC(); - - BOOL Deploy( void ); + bool Deploy( void ); void Holster( void ); void Reload( void ); void WeaponIdle( void ); @@ -49,3 +46,14 @@ class CCrossbow : public CBasePlayerWeapon int m_fInZoom; // don't save this int m_fZoomInUse; }; + +class CCrossbow : public CBasePlayerWeapon +{ + DECLARE_CLASS( CCrossbow, CBasePlayerWeapon ); +public: + void Spawn( void ); + void Precache( void ); + int AddToPlayer( CBasePlayer *pPlayer ); + + DECLARE_DATADESC(); +}; diff --git a/server/weapons/weapon_glock.cpp b/server/weapons/weapon_glock.cpp new file mode 100644 index 000000000..35e8efbfc --- /dev/null +++ b/server/weapons/weapon_glock.cpp @@ -0,0 +1,45 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All rights reserved. +* +* This product contains software technology licensed from Id +* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. +* All Rights Reserved. +* +* Use, distribution, and modification of this source code and/or resulting +* object code is 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 "weapon_glock.h" +#include "weapon_layer.h" +#include "weapon_logic_funcs_impl.h" + +LINK_ENTITY_TO_CLASS( weapon_glock, CGlock ); +LINK_ENTITY_TO_CLASS( weapon_9mmhandgun, CGlock ); + +void CGlock::Spawn( ) +{ + pev->classname = MAKE_STRING( "weapon_9mmhandgun" ); // hack to allow for old names + Precache( ); + SET_MODEL( edict(), "models/w_9mmhandgun.mdl" ); + FallInit();// get ready to fall down. + m_pWeaponLogic = new CGlockWeaponLogic(new CServerWeaponLayerImpl(this)); +} + +void CGlock::Precache( void ) +{ + PRECACHE_MODEL("models/v_9mmhandgun.mdl"); + PRECACHE_MODEL("models/w_9mmhandgun.mdl"); + PRECACHE_MODEL("models/p_9mmhandgun.mdl"); + PRECACHE_MODEL("models/shell.mdl"); // brass shell + + PRECACHE_SOUND("items/9mmclip1.wav"); + PRECACHE_SOUND("items/9mmclip2.wav"); + + PRECACHE_SOUND("weapons/pl_gun1.wav"); //silenced handgun + PRECACHE_SOUND("weapons/pl_gun2.wav"); //silenced handgun + PRECACHE_SOUND("weapons/pl_gun3.wav"); //handgun +} diff --git a/server/weapons/glock.h b/server/weapons/weapon_glock.h similarity index 73% rename from server/weapons/glock.h rename to server/weapons/weapon_glock.h index 4448beac3..864380a67 100644 --- a/server/weapons/glock.h +++ b/server/weapons/weapon_glock.h @@ -28,15 +28,4 @@ class CGlock : public CBasePlayerWeapon public: void Spawn( void ); void Precache( void ); - int iItemSlot( void ) { return 2; } - int GetItemInfo(ItemInfo *p); - - void PrimaryAttack( void ); - void SecondaryAttack( void ); - void GlockFire( float flSpread, float flCycleTime, BOOL fUseAutoAim ); - BOOL Deploy( void ); - void Reload( void ); - void WeaponIdle( void ); -private: - int m_iShell; };