diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index aa8fcafa6..1b0754831 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -38,6 +38,8 @@ list(APPEND CLDLL_SOURCES "r_view.cpp" "postfx_controller.cpp" "postfx_parameters.cpp" + "client_weapon_layer_impl.cpp" + "weapon_predicting_context.cpp" ) # shared source files @@ -57,6 +59,8 @@ 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/seeded_random_generator.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 +72,12 @@ file(GLOB RENDER_SOURCES "render/*.cpp") # entity wrappers source files file(GLOB ENTITIES_SOURCES "entities/*.cpp") +# game events source files +file(GLOB GAME_EVENTS_SOURCES "events/*.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 +94,8 @@ endif() list(APPEND CLDLL_SOURCES ${RENDER_SOURCES}) list(APPEND CLDLL_SOURCES ${ENTITIES_SOURCES}) +list(APPEND CLDLL_SOURCES ${GAME_EVENTS_SOURCES}) +list(APPEND CLDLL_SOURCES ${WEAPONS_SHARED_SOURCES}) add_library(${PROJECT_NAME} SHARED ${CLDLL_SOURCES}) target_include_directories(${PROJECT_NAME} PRIVATE diff --git a/client/cdll_int.cpp b/client/cdll_int.cpp index c2ab4953c..108e40652 100644 --- a/client/cdll_int.cpp +++ b/client/cdll_int.cpp @@ -28,7 +28,8 @@ #include "tri.h" #include "mobility_int.h" #include "imgui_manager.h" -#include "utils.h" +#include "events/game_event_manager.h" +#include "weapon_predicting_context.h" #include "pm_shared.h" #include "filesystem_utils.h" #include "build_info.h" @@ -38,6 +39,8 @@ int g_iXashEngineBuildNumber; cl_enginefunc_t gEngfuncs; mobile_engfuncs_t gMobileAPI; render_api_t gRenderfuncs; +static CGameEventManager *g_pEventManager; +static CWeaponPredictingContext g_WeaponPredicting; CHud gHUD; /* @@ -149,6 +152,8 @@ int Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion ) BuildInfo::GetArchitecture(), BuildInfo::GetPlatform() ); + + g_pEventManager = new CGameEventManager(); return 1; } @@ -193,6 +198,10 @@ void DLLEXPORT HUD_Shutdown( void ) if (g_fRenderInitialized) GL_Shutdown(); + + if (g_pEventManager) { + delete g_pEventManager; + } } /* @@ -323,8 +332,11 @@ extern "C" int DLLEXPORT HUD_Key_Event( int down, int keynum, const char *pszCur return g_ImGuiManager.KeyInput(down != 0, keynum, pszCurrentBinding) ? 1 : 0; } -extern "C" void DLLEXPORT HUD_PostRunCmd( struct local_state_s*, local_state_s *, struct usercmd_s*, int, double, unsigned int ) +extern "C" void DLLEXPORT HUD_PostRunCmd(local_state_t *from, local_state_t *to, usercmd_t *cmd, int runfuncs, double time, unsigned int random_seed) { + if (CVAR_GET_FLOAT("cl_lw") > 0.0f) { + g_WeaponPredicting.PostThink(from, to, cmd, runfuncs != 0, time, random_seed); + } } /* diff --git a/client/client_weapon_layer_impl.cpp b/client/client_weapon_layer_impl.cpp new file mode 100644 index 000000000..47e8b74fe --- /dev/null +++ b/client/client_weapon_layer_impl.cpp @@ -0,0 +1,160 @@ +/* +client_weapon_layer_impl.cpp - part of client-side weapons predicting implementation +Copyright (C) 2025 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. +*/ + +#include "client_weapon_layer_impl.h" +#include "hud.h" +#include "utils.h" +#include "event_api.h" +#include "event_args.h" + +CClientWeaponLayerImpl::CClientWeaponLayerImpl(CWeaponPredictingContext::PlayerState &state) : + m_playerState(state) +{ +} + +int CClientWeaponLayerImpl::GetWeaponBodygroup() +{ + // stub for now, later this better to be sent thru network within weapon_data_t + // but anyway it isn't much important, because it is not even used by default + return 0; +} + +Vector CClientWeaponLayerImpl::GetGunPosition() +{ + return m_playerState.origin + m_playerState.viewOffset; +} + +matrix3x3 CClientWeaponLayerImpl::GetCameraOrientation() +{ + return matrix3x3(m_playerState.viewAngles); +} + +Vector CClientWeaponLayerImpl::GetAutoaimVector(float delta) +{ + matrix3x3 camera(m_playerState.viewAngles); + return camera.GetForward(); // stub +} + +Vector CClientWeaponLayerImpl::FireBullets(int bullets, Vector origin, matrix3x3 orientation, float distance, float spread, int bulletType, uint32_t seed, int damage) +{ + float x, y, z; + + for (uint32_t i = 1; i <= bullets; i++) + { + // use player's random seed, get circular gaussian spread + // TODO: reimplement it for proper uniform-distributed generating of points within circle + x = m_randomGenerator.GetFloat(seed + i, -0.5f, 0.5f) + m_randomGenerator.GetFloat(seed + 1 + i, -0.5f, 0.5f); + y = m_randomGenerator.GetFloat(seed + 2 + i, -0.5f, 0.5f) + m_randomGenerator.GetFloat(seed + 3 + i, -0.5f, 0.5f); + z = x * x + y * y; + } + + return Vector( x * spread, y * spread, 0.0 ); +} + +int CClientWeaponLayerImpl::GetPlayerAmmo(int ammoType) +{ + return m_playerState.ammo[ammoType]; +} + +void CClientWeaponLayerImpl::SetPlayerAmmo(int ammoType, int count) +{ + m_playerState.ammo[ammoType] = count; +} + +void CClientWeaponLayerImpl::SetPlayerWeaponAnim(int anim) +{ + m_playerState.weaponanim = anim; + if (m_playerState.runfuncs) + { + // to avoid animation desync, this should changed only when runfuncs is true. + // i don't know why it works like this, but this is the only way. + m_playerState.activeWeaponanim = anim; + } +} + +void CClientWeaponLayerImpl::SetPlayerViewmodel(int model) +{ + m_playerState.viewmodel = model; +} + +int CClientWeaponLayerImpl::GetPlayerViewmodel() +{ + return m_playerState.viewmodel; +} + +bool CClientWeaponLayerImpl::CheckPlayerButtonFlag(int buttonMask) +{ + return FBitSet(m_playerState.buttons, buttonMask); +} + +void CClientWeaponLayerImpl::ClearPlayerButtonFlag(int buttonMask) +{ + ClearBits(m_playerState.buttons, buttonMask); +} + +float CClientWeaponLayerImpl::GetPlayerNextAttackTime() +{ + return m_playerState.nextAttack; +} + +void CClientWeaponLayerImpl::SetPlayerNextAttackTime(float value) +{ + m_playerState.nextAttack = value; +} + +float CClientWeaponLayerImpl::GetWeaponTimeBase(bool usePredicting) +{ + return usePredicting ? 0.0f : m_playerState.time; +} + +uint32_t CClientWeaponLayerImpl::GetRandomSeed() +{ + return m_playerState.randomSeed; +} + +uint32_t CClientWeaponLayerImpl::GetRandomInt(uint32_t seed, int32_t min, int32_t max) +{ + return m_randomGenerator.GetInteger(seed, min, max); +} + +float CClientWeaponLayerImpl::GetRandomFloat(uint32_t seed, float min, float max) +{ + return m_randomGenerator.GetFloat(seed, min, max); +} + +uint16_t CClientWeaponLayerImpl::PrecacheEvent(const char *eventName) +{ + return gEngfuncs.pfnPrecacheEvent(1, eventName); +} + +void CClientWeaponLayerImpl::PlaybackWeaponEvent(const WeaponEventParams ¶ms) +{ + gEngfuncs.pfnPlaybackEvent(static_cast(params.flags), nullptr, + params.eventindex, params.delay, + params.origin, params.angles, + params.fparam1, params.fparam2, + params.iparam1, params.iparam2, + params.bparam1, params.bparam2); +} + +// if you need to do something that has visible effects for user or that should happen only +// once per game frame, then you should use this function for doing check before running such code. +// that's because CBaseWeaponContext::ItemPostFrame could be run multiple times within one game frame. +// if it returns true, you should run desired code. otherwise, you can just update some internal state, +// but do not run code that meant to be invoked once a frame - it will be called multiple times. +bool CClientWeaponLayerImpl::ShouldRunFuncs() +{ + return m_playerState.runfuncs; +} diff --git a/client/client_weapon_layer_impl.h b/client/client_weapon_layer_impl.h new file mode 100644 index 000000000..bac9109ad --- /dev/null +++ b/client/client_weapon_layer_impl.h @@ -0,0 +1,55 @@ +/* +client_weapon_layer_impl.h - part of client-side weapons predicting implementation +Copyright (C) 2025 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 "weapon_layer.h" +#include "weapon_predicting_context.h" +#include "seeded_random_generator.h" + +class CClientWeaponLayerImpl : public IWeaponLayer +{ +public: + CClientWeaponLayerImpl(CWeaponPredictingContext::PlayerState &state); + ~CClientWeaponLayerImpl() = default; + + int GetWeaponBodygroup() override; + Vector GetGunPosition() override; + matrix3x3 GetCameraOrientation() override; + Vector GetAutoaimVector(float delta) override; + Vector FireBullets(int bullets, Vector origin, matrix3x3 orientation, float distance, float spread, int bulletType, uint32_t seed, int damage = 0) override; + CBasePlayerWeapon* GetWeaponEntity() override { return nullptr; }; + + int GetPlayerAmmo(int ammoType) override; + void SetPlayerAmmo(int ammoType, int count) override; + void SetPlayerWeaponAnim(int anim) override; + void SetPlayerViewmodel(int model) override; + int GetPlayerViewmodel() override; + bool CheckPlayerButtonFlag(int buttonMask) override; + void ClearPlayerButtonFlag(int buttonMask) override; + float GetPlayerNextAttackTime() override; + void SetPlayerNextAttackTime(float value) override; + + float GetWeaponTimeBase(bool usePredicting) override; + uint32_t GetRandomSeed() override; + uint32_t GetRandomInt(uint32_t seed, int32_t min, int32_t max) override; + float GetRandomFloat(uint32_t seed, float min, float max) override; + uint16_t PrecacheEvent(const char *eventName) override; + void PlaybackWeaponEvent(const WeaponEventParams ¶ms) override; + bool ShouldRunFuncs() override; + +private: + CSeededRandomGenerator m_randomGenerator; + CWeaponPredictingContext::PlayerState &m_playerState; +}; diff --git a/client/entity.cpp b/client/entity.cpp index 70d317255..322920b06 100644 --- a/client/entity.cpp +++ b/client/entity.cpp @@ -251,7 +251,7 @@ void DLLEXPORT HUD_TxferPredictionData ( struct entity_state_s *ps, const struct pcd->vuser3 = ppcd->vuser3; pcd->vuser4 = ppcd->vuser4; - memcpy( wd, pwd, 32 * sizeof( weapon_data_t ) ); + memcpy( wd, pwd, MAX_LOCAL_WEAPONS * sizeof( weapon_data_t ) ); } /* diff --git a/client/events/base_game_event.cpp b/client/events/base_game_event.cpp new file mode 100644 index 000000000..bb2cf7c43 --- /dev/null +++ b/client/events/base_game_event.cpp @@ -0,0 +1,35 @@ +/* +base_game_event.cpp - +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. +*/ + +#include "base_game_event.h" +#include "hud.h" +#include "utils.h" +#include "event_api.h" +#include "event_args.h" + +CBaseGameEvent::CBaseGameEvent(event_args_s *args) : + m_arguments(args) +{ +} + +bool CBaseGameEvent::IsEventLocal() const +{ + return gEngfuncs.pEventAPI->EV_IsLocal(m_arguments->entindex - 1) != 0; +} + +bool CBaseGameEvent::IsEntityPlayer() const +{ + return (m_arguments->entindex >= 1) && (m_arguments->entindex <= gEngfuncs.GetMaxClients()); +} diff --git a/client/events/base_game_event.h b/client/events/base_game_event.h new file mode 100644 index 000000000..7e9455651 --- /dev/null +++ b/client/events/base_game_event.h @@ -0,0 +1,36 @@ +/* +base_game_event.h - +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 "event_args.h" +#include "vector.h" +#include + +class CBaseGameEvent +{ +public: + CBaseGameEvent(event_args_s *args); + ~CBaseGameEvent() = default; + +protected: + Vector GetOrigin() const { return Vector(m_arguments->origin); }; + Vector GetAngles() const { return Vector(m_arguments->angles); }; + Vector GetVelocity() const { return Vector(m_arguments->velocity); }; + int32_t GetEntityIndex() const { return m_arguments->entindex; }; + bool IsEventLocal() const; + bool IsEntityPlayer() const; + + event_args_t *m_arguments; +}; diff --git a/client/events/game_event_manager.cpp b/client/events/game_event_manager.cpp new file mode 100644 index 000000000..24c5ce89d --- /dev/null +++ b/client/events/game_event_manager.cpp @@ -0,0 +1,34 @@ +/* +game_event_manager.cpp - class that responsible for registering game events handlers +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. +*/ + +#include "game_event_manager.h" +#include "hud.h" +#include "utils.h" +#include "event_api.h" +#include "event_args.h" +#include "glock_fire_event.h" + +CGameEventManager::CGameEventManager() +{ + RegisterGlockEvents(); +} + +void CGameEventManager::RegisterGlockEvents() +{ + gEngfuncs.pfnHookEvent("events/glock1.sc", [](event_args_s *args) { + CGlockFireEvent event(args); + event.Execute(); + }); +} diff --git a/client/events/game_event_manager.h b/client/events/game_event_manager.h new file mode 100644 index 000000000..6fbe7613e --- /dev/null +++ b/client/events/game_event_manager.h @@ -0,0 +1,31 @@ +/* +game_event_manager.h - class that responsible for registering game events handlers +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 CGameEventManager +{ +public: + CGameEventManager(); + ~CGameEventManager() = default; + +private: + CGameEventManager(const CGameEventManager&) = delete; + CGameEventManager(CGameEventManager&&) = delete; + CGameEventManager& operator=(const CGameEventManager&) = delete; + CGameEventManager& operator=(CGameEventManager&&) = delete; + + void RegisterGlockEvents(); +}; diff --git a/client/events/game_event_utils.cpp b/client/events/game_event_utils.cpp new file mode 100644 index 000000000..4e089770b --- /dev/null +++ b/client/events/game_event_utils.cpp @@ -0,0 +1,38 @@ +/* +game_event_utils.cpp - events-related code that used among several events +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. +*/ + +#include "game_event_utils.h" +#include "hud.h" +#include "utils.h" +#include "r_efx.h" +#include "event_api.h" +#include "event_args.h" + +void GameEventUtils::EjectBrass(const Vector &origin, const Vector &angles, const Vector &velocity, int modelIndex, int soundType) +{ + gEngfuncs.pEfxAPI->R_TempModel( + const_cast(&origin[0]), + const_cast(&velocity[0]), + const_cast(&angles[0]), + 2.5, modelIndex, soundType); +} + +void GameEventUtils::SpawnMuzzleflash() +{ + cl_entity_t *ent = gEngfuncs.GetViewModel(); + if (ent) { + ent->curstate.effects |= EF_MUZZLEFLASH; + } +} diff --git a/client/events/game_event_utils.h b/client/events/game_event_utils.h new file mode 100644 index 000000000..5ee015052 --- /dev/null +++ b/client/events/game_event_utils.h @@ -0,0 +1,23 @@ +/* +game_event_utils.h - events-related code that used among several events +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" + +namespace GameEventUtils +{ + void EjectBrass(const Vector &origin, const Vector &angles, const Vector &velocity, int modelIndex, int soundType); + void SpawnMuzzleflash(); +} diff --git a/client/events/glock_fire_event.cpp b/client/events/glock_fire_event.cpp new file mode 100644 index 000000000..c7fdfe576 --- /dev/null +++ b/client/events/glock_fire_event.cpp @@ -0,0 +1,66 @@ +/* +glock_fire_event.cpp +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. +*/ + +#include "glock_fire_event.h" +#include "game_event_utils.h" +#include "hud.h" +#include "const.h" +#include "utils.h" +#include "event_api.h" +#include "event_args.h" + +enum glock_e { + GLOCK_IDLE1 = 0, + GLOCK_IDLE2, + GLOCK_IDLE3, + GLOCK_SHOOT, + GLOCK_SHOOT_EMPTY, + GLOCK_RELOAD, + GLOCK_RELOAD_NOT_EMPTY, + GLOCK_DRAW, + GLOCK_HOLSTER, + GLOCK_ADD_SILENCER +}; + +CGlockFireEvent::CGlockFireEvent(event_args_t *args) : + CBaseGameEvent(args) +{ +} + +void CGlockFireEvent::Execute() +{ + if (IsEventLocal()) + { + GameEventUtils::SpawnMuzzleflash(); + gEngfuncs.pEventAPI->EV_WeaponAnimation( ClipEmpty() ? GLOCK_SHOOT_EMPTY : GLOCK_SHOOT, 2 ); + // V_PunchAxis( 0, -2.0 ); + } + + matrix3x3 cameraMatrix(GetAngles()); + Vector up = cameraMatrix.GetUp(); + Vector right = cameraMatrix.GetRight(); + Vector forward = cameraMatrix.GetForward(); + int brassModelIndex = gEngfuncs.pEventAPI->EV_FindModelIndex("models/shell.mdl"); + Vector shellVelocity = GetVelocity() + right * gEngfuncs.pfnRandomFloat(50, 70) + up * gEngfuncs.pfnRandomFloat(100, 150) + forward * 25.0f; + Vector shellOrigin = GetOrigin() + up * -12.0f + forward * 20.0f + right * 4.0f; + + GameEventUtils::EjectBrass(shellOrigin, GetAngles(), shellVelocity, brassModelIndex, TE_BOUNCE_SHELL); + gEngfuncs.pEventAPI->EV_PlaySound( GetEntityIndex(), GetOrigin(), CHAN_WEAPON, "weapons/pl_gun3.wav", gEngfuncs.pfnRandomFloat(0.92, 1.0), ATTN_NORM, 0, 98 + gEngfuncs.pfnRandomLong(0, 3)); +} + +bool CGlockFireEvent::ClipEmpty() const +{ + return m_arguments->bparam1 != 0; +} diff --git a/client/events/glock_fire_event.h b/client/events/glock_fire_event.h new file mode 100644 index 000000000..39f1161bf --- /dev/null +++ b/client/events/glock_fire_event.h @@ -0,0 +1,29 @@ +/* +glock_fire_event.h +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 "base_game_event.h" + +class CGlockFireEvent : public CBaseGameEvent +{ +public: + CGlockFireEvent(event_args_t *args); + ~CGlockFireEvent() = default; + + void Execute(); + +private: + bool ClipEmpty() const; +}; diff --git a/client/weapon_predicting_context.cpp b/client/weapon_predicting_context.cpp new file mode 100644 index 000000000..f07c04448 --- /dev/null +++ b/client/weapon_predicting_context.cpp @@ -0,0 +1,268 @@ +/* +weapon_predicting_context.cpp - part of client-side weapons predicting implementation +Copyright (C) 2025 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. +*/ + +#include "weapon_predicting_context.h" +#include "weapons/glock.h" +#include "client_weapon_layer_impl.h" +#include "hud.h" +#include "utils.h" +#include + +CWeaponPredictingContext::CWeaponPredictingContext() : + m_pWeaponLayer(std::make_unique(m_playerState)) +{ +} + +void CWeaponPredictingContext::PostThink(local_state_t *from, local_state_t *to, usercmd_t *cmd, bool runfuncs, double time, uint32_t randomSeed) +{ + m_playerState.time = time; + m_playerState.randomSeed = randomSeed; + m_playerState.runfuncs = runfuncs; + ReadPlayerState(from, cmd); + ReadWeaponsState(from); + + const bool playerAlive = m_playerState.deadflag != (DEAD_DISCARDBODY + 1); + CBaseWeaponContext *currWeapon = GetWeaponContext(from->client.m_iId); + if (currWeapon) + { + if (runfuncs) { + HandlePlayerSpawnDeath(to, currWeapon); + } + + if (playerAlive && m_playerState.viewmodel) + { + if (m_playerState.nextAttack <= 0.0f) { + currWeapon->ItemPostFrame(); + } + } + + if (playerAlive && cmd->weaponselect) + { + // handle weapon switching on weapons code side + HandleWeaponSwitch(from, to, cmd, currWeapon); + } + else + { + // does not changing weapons now, keep weapon ID same + to->client.m_iId = from->client.m_iId; + } + + // check for desync between local & server-side weapon animation + if (runfuncs && (m_playerState.activeWeaponanim != m_playerState.weaponanim)) + { + gEngfuncs.pfnWeaponAnim( m_playerState.weaponanim, 0 ); + m_playerState.activeWeaponanim = m_playerState.weaponanim; + } + } + + UpdatePlayerTimers(cmd); + WritePlayerState(to); + WriteWeaponsState(to, cmd); +} + +void CWeaponPredictingContext::ReadPlayerState(const local_state_t *from, usercmd_t *cmd) +{ + m_playerState.cached.buttons = from->playerstate.oldbuttons; + int32_t buttonsChanged = (m_playerState.cached.buttons ^ cmd->buttons); + m_playerState.buttonsPressed = buttonsChanged & cmd->buttons; + m_playerState.buttonsReleased = buttonsChanged & (~cmd->buttons); + + m_playerState.viewAngles = cmd->viewangles; + m_playerState.viewOffset = from->client.view_ofs; + m_playerState.origin = from->playerstate.origin; + + m_playerState.buttons = cmd->buttons; + m_playerState.velocity = from->client.velocity; + m_playerState.flags = from->client.flags; + + m_playerState.deadflag = from->client.deadflag; + m_playerState.waterlevel = from->client.waterlevel; + m_playerState.maxSpeed = from->client.maxspeed; + + m_playerState.fov = from->client.fov; + m_playerState.weaponanim = from->client.weaponanim; + m_playerState.viewmodel = from->client.viewmodel; + m_playerState.nextAttack = from->client.m_flNextAttack; + //m_playerState.m_flNextAmmoBurn = from->client.fuser2; + //m_playerState.m_flAmmoStartCharge = from->client.fuser3; +} + +void CWeaponPredictingContext::WritePlayerState(local_state_t *to) +{ + // here we need to write back values that potentially could be modified from weapons code + to->client.viewmodel = m_playerState.viewmodel; + to->client.fov = m_playerState.fov; + to->client.weaponanim = m_playerState.weaponanim; + to->client.m_flNextAttack = m_playerState.nextAttack; + //to->client.fuser2 = m_playerState.m_flNextAmmoBurn; + //to->client.fuser3 = m_playerState.m_flAmmoStartCharge; + to->client.maxspeed = m_playerState.maxSpeed; +} + +void CWeaponPredictingContext::UpdatePlayerTimers(const usercmd_t *cmd) +{ + m_playerState.nextAttack -= cmd->msec / 1000.0; + if (m_playerState.nextAttack < -0.001) + m_playerState.nextAttack = -0.001; + + //m_playerState.fuser2 -= cmd->msec / 1000.0; + //if (m_playerState.fuser2 < -0.001) + //{ + // m_playerState.fuser2 = -0.001; + //} + + //m_playerState.fuser3 -= cmd->msec / 1000.0; + //if (m_playerState.fuser3 < -0.001) + //{ + // m_playerState.fuser3 = -0.001; + //} +} + +void CWeaponPredictingContext::UpdateWeaponTimers(CBaseWeaponContext *weapon, const usercmd_t *cmd) +{ + weapon->m_flNextPrimaryAttack -= cmd->msec / 1000.0; + weapon->m_flNextSecondaryAttack -= cmd->msec / 1000.0; + weapon->m_flTimeWeaponIdle -= cmd->msec / 1000.0; + //weapon->m_flNextReload -= cmd->msec / 1000.0; + //weapon->m_fNextAimBonus -= cmd->msec / 1000.0; + //weapon->fuser1 -= cmd->msec / 1000.0; + + if (weapon->m_flNextPrimaryAttack < -1.0) + weapon->m_flNextPrimaryAttack = -1.0; + + if (weapon->m_flNextSecondaryAttack < -0.001) + weapon->m_flNextSecondaryAttack = -0.001; + + if (weapon->m_flTimeWeaponIdle < -0.001) + weapon->m_flTimeWeaponIdle = -0.001; + + //if (weapon->m_fNextAimBonus < -1.0) + // weapon->m_fNextAimBonus = -1.0; + + //if (weapon->m_flNextReload < -0.001) + // weapon->m_flNextReload = -0.001; + + //if (weapon->fuser1 < -0.001) + // weapon->fuser1 = -0.001; +} + +void CWeaponPredictingContext::ReadWeaponsState(const local_state_t *from) +{ + for (size_t i = 0; i < MAX_LOCAL_WEAPONS; i++) + { + CBaseWeaponContext *weapon = GetWeaponContext(i); + const weapon_data_t &data = from->weapondata[i]; + if (weapon) + { + weapon->m_fInReload = data.m_fInReload; + weapon->m_fInSpecialReload = data.m_fInSpecialReload; + //weapon->m_flPumpTime = data.m_flPumpTime; + weapon->m_iClip = data.m_iClip; + weapon->m_flNextPrimaryAttack = data.m_flNextPrimaryAttack; + weapon->m_flNextSecondaryAttack = data.m_flNextSecondaryAttack; + weapon->m_flTimeWeaponIdle = data.m_flTimeWeaponIdle; + //weapon->pev->fuser1 = data.fuser1; + //weapon->m_flStartThrow = data.fuser2; + //weapon->m_flReleaseThrow = data.fuser3; + //weapon->m_chargeReady = data.iuser1; + //weapon->m_fInAttack = data.iuser2; + //weapon->m_fireState = data.iuser3; + + weapon->m_iSecondaryAmmoType = static_cast(from->client.vuser3.z); + weapon->m_iPrimaryAmmoType = static_cast(from->client.vuser4.x); + m_playerState.ammo[weapon->m_iPrimaryAmmoType] = static_cast(from->client.vuser4.y); + m_playerState.ammo[weapon->m_iSecondaryAmmoType] = static_cast(from->client.vuser4.z); + } + } +} + +void CWeaponPredictingContext::WriteWeaponsState(local_state_t *to, const usercmd_t *cmd) +{ + for (size_t i = 0; i < MAX_LOCAL_WEAPONS; i++) + { + weapon_data_t &data = to->weapondata[i]; + CBaseWeaponContext *weapon = GetWeaponContext(i); + if (!weapon) + { + std::memset(&data, 0x0, sizeof(weapon_data_t)); + continue; + } + else + { + UpdateWeaponTimers(weapon, cmd); + data.m_fInReload = weapon->m_fInReload; + data.m_fInSpecialReload = weapon->m_fInSpecialReload; + //data.m_flPumpTime = pCurrent->m_flPumpTime; + data.m_iClip = weapon->m_iClip; + data.m_flNextPrimaryAttack = weapon->m_flNextPrimaryAttack; + data.m_flNextSecondaryAttack = weapon->m_flNextSecondaryAttack; + data.m_flTimeWeaponIdle = weapon->m_flTimeWeaponIdle; + //data.fuser1 = pCurrent->pev->fuser1; + //data.fuser2 = weapon->m_flStartThrow; + //data.fuser3 = weapon->m_flReleaseThrow; + //data.iuser1 = weapon->m_chargeReady; + //data.iuser2 = weapon->m_fInAttack; + //data.iuser3 = weapon->m_fireState; + } + } +} + +void CWeaponPredictingContext::HandlePlayerSpawnDeath(local_state_t *to, CBaseWeaponContext *weapon) +{ + if (to->client.health <= 0 && m_playerState.cached.health > 0) { + weapon->Holster(); + } + else if (to->client.health > 0 && m_playerState.cached.health <= 0) { + weapon->Deploy(); + } + m_playerState.cached.health = to->client.health; +} + +void CWeaponPredictingContext::HandleWeaponSwitch(const local_state_t *from, local_state_t *to, const usercmd_t *cmd, CBaseWeaponContext *weapon) +{ + if (from->weapondata[cmd->weaponselect].m_iId == cmd->weaponselect) + { + CBaseWeaponContext *selectedWeapon = GetWeaponContext(cmd->weaponselect); + if (selectedWeapon && selectedWeapon->m_iId != weapon->m_iId) + { + weapon->Holster(); + selectedWeapon->Deploy(); + to->client.m_iId = cmd->weaponselect; + } + } +} + +CBaseWeaponContext* CWeaponPredictingContext::GetWeaponContext(uint32_t weaponID) +{ + if (m_weaponsState.count(weaponID)) { + return m_weaponsState[weaponID].get(); + } + else + { + switch (weaponID) + { + case WEAPON_GLOCK: + m_weaponsState[weaponID] = std::make_unique(m_pWeaponLayer.get()); + break; + default: + return nullptr; + } + + ItemInfo itemInfo; + m_weaponsState[weaponID]->GetItemInfo(&itemInfo); + CBaseWeaponContext::ItemInfoArray[weaponID] = itemInfo; + return m_weaponsState[weaponID].get(); + } +} diff --git a/client/weapon_predicting_context.h b/client/weapon_predicting_context.h new file mode 100644 index 000000000..5201b5597 --- /dev/null +++ b/client/weapon_predicting_context.h @@ -0,0 +1,79 @@ +/* +weapon_predicting_context.h - part of client-side weapons predicting implementation +Copyright (C) 2025 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 "mathlib.h" +#include "usercmd.h" +#include "entity_state.h" +#include "weapon_layer.h" +#include "weapon_context.h" +#include +#include +#include + +class CWeaponPredictingContext +{ +public: + struct PlayerState + { + int32_t buttons; + int32_t buttonsPressed; + int32_t buttonsReleased; + int32_t flags; + int32_t deadflag; + int32_t waterlevel; + int32_t weaponanim; + int32_t activeWeaponanim; + int32_t viewmodel; + Vector viewAngles; + Vector viewOffset; + Vector origin; + Vector velocity; + float maxSpeed; + float fov; + float nextAttack; + std::unordered_map ammo; + + // state unrelated to player, but required anyway + double time; + bool runfuncs; + uint32_t randomSeed; + + // variables that are used for tracking of changes between frames + struct { + float health; + int32_t buttons; + } cached; + }; + + CWeaponPredictingContext(); + void PostThink(local_state_t *from, local_state_t *to, usercmd_t *cmd, bool runfuncs, double time, uint32_t randomSeed); + +private: + void ReadPlayerState(const local_state_t *from, usercmd_t *cmd); + void WritePlayerState(local_state_t *to); + void UpdatePlayerTimers(const usercmd_t *cmd); + void UpdateWeaponTimers(CBaseWeaponContext *weapon, const usercmd_t *cmd); + void ReadWeaponsState(const local_state_t *from); + void WriteWeaponsState(local_state_t *to, const usercmd_t *cmd); + void HandlePlayerSpawnDeath(local_state_t *to, CBaseWeaponContext *weapon); + void HandleWeaponSwitch(const local_state_t *from, local_state_t *to, const usercmd_t *cmd, CBaseWeaponContext *weapon); + CBaseWeaponContext* GetWeaponContext(uint32_t weaponID); + + PlayerState m_playerState; + std::unique_ptr m_pWeaponLayer; + std::unordered_map> m_weaponsState; +}; diff --git a/common/entity_state.h b/common/entity_state.h index 7cd60881d..b44a2aa1f 100644 --- a/common/entity_state.h +++ b/common/entity_state.h @@ -180,11 +180,13 @@ typedef struct clientdata_s #include "weaponinfo.h" +#define MAX_LOCAL_WEAPONS 64 // max weapons that can be predicted on the client + typedef struct local_state_s { entity_state_t playerstate; clientdata_t client; - weapon_data_t weapondata[32]; + weapon_data_t weapondata[MAX_LOCAL_WEAPONS]; } local_state_t; #endif//ENTITY_STATE_H diff --git a/engine/eiface.h b/engine/eiface.h index 0fa6c4452..907595829 100644 --- a/engine/eiface.h +++ b/engine/eiface.h @@ -354,8 +354,8 @@ typedef enum _fieldtypes FIELD_TIME, // a floating point time (these are fixed up automatically too!) FIELD_MODELNAME, // Engine string that is a model name (needs precache) FIELD_SOUNDNAME, // Engine string that is a sound name (needs precache) + // do not modify order of lines above, it is shared with engine FIELD_VOID, // No type or value - FIELD_TYPECOUNT, // MUST BE LAST } FIELDTYPE; @@ -366,6 +366,7 @@ typedef struct datamap_s DATAMAP; #define FTYPEDESC_SAVE 0x0002 // This field is saved to disk #define FTYPEDESC_KEY 0x0004 // This field can be requested and written to by string name at load time #define FTYPEDESC_FUNCTIONTABLE 0x0008 // This is a table entry for a member function pointer +#define FTYPEDESC_CUSTOMCALLBACK 0x0010 // This field uses custom getter/setter instead of obtaining value by offset from entity pdata // NOTE: engine typedescription is shorter than game typedescription typedef struct diff --git a/engine/progdefs.h b/engine/progdefs.h index 5e5404398..db247a58f 100644 --- a/engine/progdefs.h +++ b/engine/progdefs.h @@ -16,6 +16,8 @@ #ifndef PROGDEFS_H #define PROGDEFS_H +#include "const.h" + typedef struct { float time; diff --git a/game_shared/item_info.h b/game_shared/item_info.h new file mode 100644 index 000000000..7f5e5d44e --- /dev/null +++ b/game_shared/item_info.h @@ -0,0 +1,75 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All 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 "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< + +const uint32_t CSeededRandomGenerator::m_seedTable[256] = +{ + 28985, 27138, 26457, 9451, 17764, 10909, 28790, 8716, 6361, 4853, 17798, 21977, 19643, 20662, 10834, 20103, + 27067, 28634, 18623, 25849, 8576, 26234, 23887, 18228, 32587, 4836, 3306, 1811, 3035, 24559, 18399, 315, + 26766, 907, 24102, 12370, 9674, 2972, 10472, 16492, 22683, 11529, 27968, 30406, 13213, 2319, 23620, 16823, + 10013, 23772, 21567, 1251, 19579, 20313, 18241, 30130, 8402, 20807, 27354, 7169, 21211, 17293, 5410, 19223, + 10255, 22480, 27388, 9946, 15628, 24389, 17308, 2370, 9530, 31683, 25927, 23567, 11694, 26397, 32602, 15031, + 18255, 17582, 1422, 28835, 23607, 12597, 20602, 10138, 5212, 1252, 10074, 23166, 19823, 31667, 5902, 24630, + 18948, 14330, 14950, 8939, 23540, 21311, 22428, 22391, 3583, 29004, 30498, 18714, 4278, 2437, 22430, 3439, + 28313, 23161, 25396, 13471, 19324, 15287, 2563, 18901, 13103, 16867, 9714, 14322, 15197, 26889, 19372, 26241, + 31925, 14640, 11497, 8941, 10056, 6451, 28656, 10737, 13874, 17356, 8281, 25937, 1661, 4850, 7448, 12744, + 21826, 5477, 10167, 16705, 26897, 8839, 30947, 27978, 27283, 24685, 32298, 3525, 12398, 28726, 9475, 10208, + 617, 13467, 22287, 2376, 6097, 26312, 2974, 9114, 21787, 28010, 4725, 15387, 3274, 10762, 31695, 17320, + 18324, 12441, 16801, 27376, 22464, 7500, 5666, 18144, 15314, 31914, 31627, 6495, 5226, 31203, 2331, 4668, + 12650, 18275, 351, 7268, 31319, 30119, 7600, 2905, 13826, 11343, 13053, 15583, 30055, 31093, 5067, 761, + 9685, 11070, 21369, 27155, 3663, 26542, 20169, 12161, 15411, 30401, 7580, 31784, 8985, 29367, 20989, 14203, + 29694, 21167, 10337, 1706, 28578, 887, 3373, 19477, 14382, 675, 7033, 15111, 26138, 12252, 30996, 21409, + 25678, 18555, 13256, 23316, 22407, 16727, 991, 9236, 5373, 29402, 6117, 15241, 27715, 19291, 19888, 19847 +}; + +int32_t CSeededRandomGenerator::GetInteger(uint32_t seed, int32_t min, int32_t max) const +{ + int32_t range = max - min; + if (range <= 0) { + return min; + } + else + { + uint32_t initialSeed = m_seedTable[static_cast(seed + min + max) & 0xFF]; + uint32_t value = ExecuteRound(initialSeed); + return min + (value % range); + } +} + +float CSeededRandomGenerator::GetFloat(uint32_t seed, float min, float max) const +{ + int32_t a, b; + std::memcpy(&a, &min, sizeof(a)); + std::memcpy(&b, &max, sizeof(b)); + + float range = max - min; + if (range <= 0.0f) { + return min; + } + else + { + uint32_t initialSeed = m_seedTable[static_cast(seed + a + b) & 0xFF]; + for (int32_t i = 0; i < 2; i++) { + ExecuteRound(initialSeed); + } + uint32_t value = ExecuteRound(initialSeed) & 0xFFFF; + float fraction = value / 65536.0f; + return min + (range * fraction); + } +} + +uint32_t CSeededRandomGenerator::ExecuteRound(uint32_t &seed) const +{ + seed *= 69069; + seed += m_seedTable[seed & 0xFF]; + seed += 1; + return seed & 0x0FFFFFFF; +} diff --git a/game_shared/seeded_random_generator.h b/game_shared/seeded_random_generator.h new file mode 100644 index 000000000..d08a60be9 --- /dev/null +++ b/game_shared/seeded_random_generator.h @@ -0,0 +1,29 @@ +/* +seeded_random_generator.h - stateless random numbers generator, used within weapon predicting +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 + +class CSeededRandomGenerator +{ +public: + int32_t GetInteger(uint32_t seed, int32_t min, int32_t max) const; + float GetFloat(uint32_t seed, float min, float max) const; + +private: + uint32_t ExecuteRound(uint32_t &seed) const; + + static const uint32_t m_seedTable[256]; +}; diff --git a/game_shared/weapon_context.cpp b/game_shared/weapon_context.cpp new file mode 100644 index 000000000..9a6b6d7bf --- /dev/null +++ b/game_shared/weapon_context.cpp @@ -0,0 +1,307 @@ +/* +weapon_context.cpp - 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. +*/ + +#include "weapon_context.h" +#ifdef CLIENT_DLL +#include "const.h" +#include "hud.h" +#include "utils.h" +#include "event_api.h" +#include "event_args.h" +#else +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" +#include "nodes.h" +#include "soundent.h" +#include "decals.h" +#include "gamerules.h" +#endif + +ItemInfo CBaseWeaponContext::ItemInfoArray[ MAX_WEAPONS ]; +AmmoInfo CBaseWeaponContext::AmmoInfoArray[ MAX_AMMO_SLOTS ]; + +CBaseWeaponContext::CBaseWeaponContext(IWeaponLayer *layer) : + m_pLayer(layer), + m_fFireOnEmpty(false), + m_fInReload(false), + m_fInSpecialReload(false), + m_flNextPrimaryAttack(0.0f), + m_flNextSecondaryAttack(0.0f), + m_flPumpTime(0.0f), + m_flTimeWeaponIdle(0.0f), + m_iClientClip(0), + m_iClientWeaponState(0), + m_iClip(0), + m_iDefaultAmmo(0), + m_iPlayEmptySound(false), + m_iPrimaryAmmoType(0), + m_iSecondaryAmmoType(0) +{ +} + +CBaseWeaponContext::~CBaseWeaponContext() +{ + if (m_pLayer) { + delete m_pLayer; + } +} + +void CBaseWeaponContext::ItemPostFrame() +{ + if ((m_fInReload) && m_pLayer->GetPlayerNextAttackTime() <= m_pLayer->GetWeaponTimeBase(false)) + { + // complete the reload. + int j = Q_min( iMaxClip() - m_iClip, m_pLayer->GetPlayerAmmo(m_iPrimaryAmmoType) ); + + // Add them to the clip + m_iClip += j; + m_pLayer->SetPlayerAmmo( m_iPrimaryAmmoType, m_pLayer->GetPlayerAmmo(m_iPrimaryAmmoType) - j ); + + m_fInReload = FALSE; + } + + if (m_pLayer->CheckPlayerButtonFlag(IN_ATTACK2) && m_flNextSecondaryAttack <= m_pLayer->GetWeaponTimeBase(UseDecrement()) ) + { + if ( pszAmmo2() && !m_pLayer->GetPlayerAmmo(SecondaryAmmoIndex()) ) + { + m_fFireOnEmpty = TRUE; + } + + SecondaryAttack(); + m_pLayer->ClearPlayerButtonFlag(IN_ATTACK2); + } + else if (m_pLayer->CheckPlayerButtonFlag(IN_ATTACK) && m_flNextPrimaryAttack <= m_pLayer->GetWeaponTimeBase(UseDecrement()) ) + { + if ( (m_iClip == 0 && pszAmmo1()) || (iMaxClip() == -1 && !m_pLayer->GetPlayerAmmo(PrimaryAmmoIndex())) ) + { + m_fFireOnEmpty = TRUE; + } + + PrimaryAttack(); + } + else if ( m_pLayer->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_pLayer->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_pLayer->GetWeaponTimeBase(UseDecrement()) ) + { + // weapon isn't useable, switch. GetNextBestWeapon does weapon switching + if ( !(iFlags() & ITEM_FLAG_NOAUTOSWITCHEMPTY) && g_pGameRules->GetNextBestWeapon( m_pLayer->GetWeaponEntity()->m_pPlayer, m_pLayer->GetWeaponEntity() )) + { + m_flNextPrimaryAttack = m_pLayer->GetWeaponTimeBase(UseDecrement()) + 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_pLayer->GetWeaponTimeBase(UseDecrement()) ) + { + Reload(); + return; + } + } + + WeaponIdle( ); + return; + } + + // catch all + if ( ShouldWeaponIdle() ) + { + WeaponIdle(); + } +} + +void CBaseWeaponContext::Holster() +{ + m_fInReload = FALSE; // cancel any reload in progress. + m_pLayer->SetPlayerViewmodel(0); +#ifndef CLIENT_DLL + m_pLayer->GetWeaponEntity()->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_pLayer->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_pLayer->GetPlayerAmmo(m_iPrimaryAmmoType) != 0); + } + if ( pszAmmo2() ) + { + bHasAmmo |= (m_pLayer->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 + CBasePlayer *player = m_pLayer->GetWeaponEntity()->m_pPlayer; + player->pev->viewmodel = MAKE_STRING(szViewModel); + player->pev->weaponmodel = MAKE_STRING(szWeaponModel); + strcpy( player->m_szAnimExtention, szAnimExt ); + //player->TabulateAmmo(); +#else + int modelIndex = m_pLayer->GetPlayerViewmodel(); + gEngfuncs.CL_LoadModel( szViewModel, &modelIndex ); +#endif + SendWeaponAnim( iAnim, skiplocal, body ); + + m_pLayer->SetPlayerNextAttackTime(m_pLayer->GetWeaponTimeBase(UseDecrement()) + 0.5); + m_flTimeWeaponIdle = m_pLayer->GetWeaponTimeBase(UseDecrement()) + 1.0; + return TRUE; +} + +BOOL CBaseWeaponContext :: DefaultReload( int iClipSize, int iAnim, float fDelay, int body ) +{ + if (m_pLayer->GetPlayerAmmo(m_iPrimaryAmmoType) <= 0) + return FALSE; + + int j = Q_min(iClipSize - m_iClip, m_pLayer->GetPlayerAmmo(m_iPrimaryAmmoType)); + + if (j == 0) + return FALSE; + + m_pLayer->SetPlayerNextAttackTime(m_pLayer->GetWeaponTimeBase(UseDecrement()) + fDelay); + + //!!UNDONE -- reload sound goes here !!! + SendWeaponAnim( iAnim, UseDecrement() ? 1 : 0, body ); + + m_fInReload = TRUE; + + m_flTimeWeaponIdle = m_pLayer->GetWeaponTimeBase(UseDecrement()) + 3; + return TRUE; +} + +void CBaseWeaponContext::SendWeaponAnim( int iAnim, int skiplocal, int body ) +{ + m_pLayer->SetPlayerWeaponAnim(iAnim); + +#ifdef CLIENT_DLL + if (m_pLayer->ShouldRunFuncs()) { + gEngfuncs.pfnWeaponAnim(iAnim, body); + } +#else + CBasePlayer *player = m_pLayer->GetWeaponEntity()->m_pPlayer; + + if ( UseDecrement() ) + skiplocal = 1; + else + skiplocal = 0; + + if ( skiplocal && ENGINE_CANSKIP( player->edict() ) ) + return; + + MESSAGE_BEGIN( MSG_ONE, SVC_WEAPONANIM, NULL, player->pev ); + WRITE_BYTE( iAnim ); // sequence number + WRITE_BYTE( m_pLayer->GetWeaponBodygroup() ); // weaponmodel bodygroup. + MESSAGE_END(); +#endif +} + +bool CBaseWeaponContext :: PlayEmptySound() +{ + if (m_iPlayEmptySound) + { +#ifdef CLIENT_DLL + // HUD_PlaySound( "weapons/357_cock1.wav", 0.8 ); +#else + EMIT_SOUND(ENT(m_pLayer->GetWeaponEntity()->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..f5119f6be --- /dev/null +++ b/game_shared/weapon_context.h @@ -0,0 +1,85 @@ +/* +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" +#include + +class CBaseWeaponContext +{ +public: + CBaseWeaponContext(IWeaponLayer *layer); + virtual ~CBaseWeaponContext(); + + // called by CBasePlayerWeapons ItemPostFrame() + virtual void PrimaryAttack() {} // do "+ATTACK" + virtual void SecondaryAttack() {} // do "+ATTACK2" + virtual void Reload() {} // do "+RELOAD" + virtual void WeaponIdle() {} // called when no buttons pressed + + void ItemPostFrame(); + + 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(); + + 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(); + + static ItemInfo ItemInfoArray[ MAX_WEAPONS ]; + static AmmoInfo AmmoInfoArray[ MAX_AMMO_SLOTS ]; + + 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. + IWeaponLayer *m_pLayer; +}; diff --git a/game_shared/weapon_layer.h b/game_shared/weapon_layer.h new file mode 100644 index 000000000..1764f3826 --- /dev/null +++ b/game_shared/weapon_layer.h @@ -0,0 +1,111 @@ +/* +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 +#include "vector.h" +#include "matrix.h" +#include + +#define _CLASSNAME_STR(s) (#s) +#define CLASSNAME_STR(s) _CLASSNAME_STR(s) + +#define AUTOAIM_2DEGREES 0.0348994967025 +#define AUTOAIM_5DEGREES 0.08715574274766 +#define AUTOAIM_8DEGREES 0.1391731009601 +#define AUTOAIM_10DEGREES 0.1736481776669 + +// do not change, these flags are shared with engine +enum class WeaponEventFlags : int +{ + // Empty flag + None = 0, + + // Skip local host for event send. + NotHost = (1<<0), + + // Send the event reliably. You must specify the origin and angles and use + // PLAYBACK_EVENT_FULL for this to work correctly on the server for anything + // that depends on the event origin/angles. I.e., the origin/angles are not + // taken from the invoking edict for reliable events. + Reliable = (1<<1), + + // Don't restrict to PAS/PVS, send this event to _everybody_ on the server ( useful for stopping CHAN_STATIC + // sounds started by client event when client is not in PVS anymore ( hwguy in TFC e.g. ). + Global = (1<<2), + + // If this client already has one of these events in its queue, just update the event instead of sending it as a duplicate + Update = (1<<3), + + // Only send to entity specified as the invoker + HostOnly = (1<<4), + + // Only send if the event was created on the server. + Server = (1<<5), + + // Only issue event client side ( from shared code ) + Client = (1<<6) +}; + +struct WeaponEventParams +{ + WeaponEventFlags flags; + float *origin; + float *angles; + float delay; + float fparam1; + float fparam2; + int iparam1; + int iparam2; + int bparam1; + int bparam2; + uint16_t eventindex; +}; + +// forward declaration, client should not know anything about server entities +class CBasePlayerWeapon; + +class IWeaponLayer +{ +public: + virtual ~IWeaponLayer() {}; + + // accessing weapon entity state + virtual int GetWeaponBodygroup() = 0; + virtual Vector GetGunPosition() = 0; + virtual matrix3x3 GetCameraOrientation() = 0; + virtual Vector GetAutoaimVector(float delta) = 0; + virtual Vector FireBullets(int bullets, Vector origin, matrix3x3 orientation, float distance, float spread, int bulletType, uint32_t seed, int damage = 0) = 0; + virtual CBasePlayerWeapon* GetWeaponEntity() = 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 int GetPlayerViewmodel() = 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(bool usePredicting) = 0; + virtual uint32_t GetRandomSeed() = 0; + virtual uint32_t GetRandomInt(uint32_t seed, int32_t min, int32_t max) = 0; + virtual float GetRandomFloat(uint32_t seed, float min, float max) = 0; + virtual uint16_t PrecacheEvent(const char *eventName) = 0; + virtual void PlaybackWeaponEvent(const WeaponEventParams ¶ms) = 0; + virtual bool ShouldRunFuncs() = 0; +}; diff --git a/game_shared/weapons/glock.cpp b/game_shared/weapons/glock.cpp new file mode 100644 index 000000000..f4361076d --- /dev/null +++ b/game_shared/weapons/glock.cpp @@ -0,0 +1,208 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All 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 "glock.h" + +#ifdef CLIENT_DLL + +#else +#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 +{ + GLOCK_IDLE1 = 0, + GLOCK_IDLE2, + GLOCK_IDLE3, + GLOCK_SHOOT, + GLOCK_SHOOT_EMPTY, + GLOCK_RELOAD, + GLOCK_RELOAD_NOT_EMPTY, + GLOCK_DRAW, + GLOCK_HOLSTER, + GLOCK_ADD_SILENCER +}; + +CGlockWeaponLogic::CGlockWeaponLogic(IWeaponLayer *layer) : + CBaseWeaponContext(layer) +{ + m_iDefaultAmmo = GLOCK_DEFAULT_GIVE; + m_iId = WEAPON_GLOCK; + m_usFireGlock1 = m_pLayer->PrecacheEvent("events/glock1.sc"); + m_usFireGlock2 = m_pLayer->PrecacheEvent("events/glock2.sc"); +} + +int CGlockWeaponLogic::GetItemInfo(ItemInfo *p) +{ + p->pszName = CLASSNAME_STR(GLOCK_CLASSNAME); + p->pszAmmo1 = "9mm"; + p->iMaxAmmo1 = _9MM_MAX_CARRY; + p->pszAmmo2 = NULL; + p->iMaxAmmo2 = -1; + p->iMaxClip = GLOCK_MAX_CLIP; + p->iSlot = 1; + p->iPosition = 0; + p->iFlags = 0; + p->iId = m_iId; + p->iWeight = GLOCK_WEIGHT; + + return 1; +} + +bool CGlockWeaponLogic::Deploy( ) +{ + // pev->body = 1; + return DefaultDeploy( "models/v_9mmhandgun.mdl", "models/p_9mmhandgun.mdl", GLOCK_DRAW, "onehanded" ); +} + +void CGlockWeaponLogic::SecondaryAttack( void ) +{ + GlockFire( 0.1, 0.2, FALSE ); +} + +void CGlockWeaponLogic::PrimaryAttack( void ) +{ + GlockFire( 0.01, 0.3, TRUE ); +} + +void CGlockWeaponLogic::GlockFire( float flSpread , float flCycleTime, bool fUseAutoAim ) +{ + if (m_iClip <= 0) + { + if (m_fFireOnEmpty) + { + PlayEmptySound(); + m_flNextPrimaryAttack = m_pLayer->GetWeaponTimeBase(UseDecrement()) + 0.2; + } + + return; + } + + m_iClip--; + + SendWeaponAnim(m_iClip != 0 ? GLOCK_SHOOT : GLOCK_SHOOT_EMPTY); + +#ifndef CLIENT_DLL + // player "shoot" animation + CBasePlayer *player = m_pLayer->GetWeaponEntity()->m_pPlayer; + + player->SetAnimation( PLAYER_ATTACK1 ); + player->pev->effects = (int)(player->pev->effects) | EF_MUZZLEFLASH; + + // silenced + if (m_pLayer->GetWeaponBodygroup() == 1) + { + player->m_iWeaponVolume = QUIET_GUN_VOLUME; + player->m_iWeaponFlash = DIM_GUN_FLASH; + } + else + { + // non-silenced + player->m_iWeaponVolume = NORMAL_GUN_VOLUME; + player->m_iWeaponFlash = NORMAL_GUN_FLASH; + } +#endif + + Vector vecSrc = m_pLayer->GetGunPosition(); + matrix3x3 aimMatrix = m_pLayer->GetCameraOrientation(); + + if (fUseAutoAim) { + aimMatrix.SetForward(m_pLayer->GetAutoaimVector(AUTOAIM_10DEGREES)); + } + + Vector vecDir = m_pLayer->FireBullets(1, vecSrc, aimMatrix, 8192, flSpread, BULLET_PLAYER_9MM, m_pLayer->GetRandomSeed()); + m_flNextPrimaryAttack = m_flNextSecondaryAttack = m_pLayer->GetWeaponTimeBase(UseDecrement()) + flCycleTime; + + WeaponEventParams params; + params.flags = WeaponEventFlags::NotHost; + params.eventindex = fUseAutoAim ? m_usFireGlock1 : m_usFireGlock2; + params.delay = 0.0f; + params.origin = vecSrc; + params.angles = aimMatrix.GetAngles(); + params.fparam1 = vecDir.x; + params.fparam2 = vecDir.y; + params.iparam1 = 0; + params.iparam2 = 0; + params.bparam1 = (m_iClip == 0) ? 1 : 0; + params.bparam2 = 0; + + if (m_pLayer->ShouldRunFuncs()) { + m_pLayer->PlaybackWeaponEvent(params); + } + + // PLAYBACK_EVENT_FULL( flags, m_pPlayer->edict(), fUseAutoAim ? m_usFireGlock1 : m_usFireGlock2, 0.0, (float *)&g_vecZero, (float *)&g_vecZero, vecDir.x, vecDir.y, 0, 0, ( m_iClip == 0 ) ? 1 : 0, 0 ); + +#ifndef CLIENT_DLL + if (!m_iClip && m_pLayer->GetPlayerAmmo(m_iPrimaryAmmoType) <= 0) + // HEV suit - indicate out of ammo condition + m_pLayer->GetWeaponEntity()->m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); +#endif + m_flTimeWeaponIdle = m_pLayer->GetWeaponTimeBase(UseDecrement()) + m_pLayer->GetRandomFloat(m_pLayer->GetRandomSeed(), 10.f, 15.f); + //m_pPlayer->pev->punchangle.x -= 2; +} + +void CGlockWeaponLogic::Reload( void ) +{ + int iResult; + + if (m_iClip == 0) + iResult = DefaultReload( 17, GLOCK_RELOAD, 1.5 ); + else + iResult = DefaultReload( 17, GLOCK_RELOAD_NOT_EMPTY, 1.5 ); + + if (iResult) + { + m_flTimeWeaponIdle = m_pLayer->GetWeaponTimeBase(UseDecrement()) + m_pLayer->GetRandomFloat(m_pLayer->GetRandomSeed(), 10.0f, 15.0f); + } +} + +void CGlockWeaponLogic::WeaponIdle( void ) +{ + ResetEmptySound( ); + + m_pLayer->GetAutoaimVector( AUTOAIM_10DEGREES ); + + if (m_flTimeWeaponIdle > m_pLayer->GetWeaponTimeBase(UseDecrement())) + return; + + // only idle if the slid isn't back + if (m_iClip != 0) + { + int iAnim; + float flRand = m_pLayer->GetRandomFloat(m_pLayer->GetRandomSeed(), 0.0f, 1.0f); + if (flRand <= 0.3 + 0 * 0.75) + { + iAnim = GLOCK_IDLE3; + m_flTimeWeaponIdle = m_pLayer->GetWeaponTimeBase(UseDecrement()) + 49.0 / 16; + } + else if (flRand <= 0.6 + 0 * 0.875) + { + iAnim = GLOCK_IDLE1; + m_flTimeWeaponIdle = m_pLayer->GetWeaponTimeBase(UseDecrement()) + 60.0 / 16.0; + } + else + { + iAnim = GLOCK_IDLE2; + m_flTimeWeaponIdle = m_pLayer->GetWeaponTimeBase(UseDecrement()) + 40.0 / 16.0; + } + SendWeaponAnim( iAnim ); + } +} diff --git a/game_shared/weapons/glock.h b/game_shared/weapons/glock.h new file mode 100644 index 000000000..b966f3a0b --- /dev/null +++ b/game_shared/weapons/glock.h @@ -0,0 +1,43 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All 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 +#define GLOCK_CLASSNAME weapon_9mmhandgun + +class CGlockWeaponLogic : public CBaseWeaponContext +{ +public: + CGlockWeaponLogic() = delete; + CGlockWeaponLogic(IWeaponLayer *layer); + + 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 ); + + uint16_t m_usFireGlock1; + uint16_t m_usFireGlock2; +}; diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index e4fda15ae..ba3cb7178 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,8 @@ 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/seeded_random_generator.cpp" "${CMAKE_SOURCE_DIR}/game_shared/meshdesc.cpp" "${CMAKE_SOURCE_DIR}/game_shared/meshdesc_factory.cpp" "${CMAKE_SOURCE_DIR}/game_shared/filesystem_utils.cpp" @@ -89,8 +92,9 @@ endif() file(GLOB ENTITIES_SOURCES "entities/*.cpp") file(GLOB MONSTERS_SOURCES "monsters/*.cpp") -file(GLOB WEAPONS_SOURCES "weapons/*.cpp") +file(GLOB WEAPONS_SOURCES "weapons/weapon_*.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(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") @@ -101,6 +105,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/cbase.h b/server/cbase.h index 22cf549de..9f1ef325f 100644 --- a/server/cbase.h +++ b/server/cbase.h @@ -26,6 +26,8 @@ CBaseEntity CBaseGroup */ #pragma once +#include "util.h" + #define MAX_PATH_SIZE 10 // max number of nodes available for a path. // These are caps bits to indicate what an object's capabilities (currently used for save/restore and level transitions) diff --git a/server/client.cpp b/server/client.cpp index 7a6ca3926..5cc54713c 100644 --- a/server/client.cpp +++ b/server/client.cpp @@ -40,6 +40,7 @@ #include "netadr.h" #include "user_messages.h" #include "beam.h" +#include #include extern DLL_GLOBAL ULONG g_ulModelIndexPlayer; @@ -1600,8 +1601,47 @@ void RegisterEncoders( void ) int GetWeaponData( struct edict_s *player, struct weapon_data_s *info ) { - memset( info, 0, 32 * sizeof( weapon_data_t ) ); + entvars_t *pev = &player->v; + CBasePlayer *playerEntity = dynamic_cast(CBasePlayer::Instance(&player->v)); + + memset(info, 0, MAX_LOCAL_WEAPONS * sizeof(weapon_data_t)); + + if (!player) + return 1; + + for (int i = 0; i < MAX_ITEM_TYPES; i++) + { + CBasePlayerItem *item = playerEntity->m_rgpPlayerItems[i]; + if (item) + { + while (item) + { + CBasePlayerWeapon *weapon = dynamic_cast(item->GetWeaponPtr()); + CBaseWeaponContext *ctx = weapon->m_pWeaponContext; + if (weapon && ctx->UseDecrement()) + { + ItemInfo itemInfo; + memset(&itemInfo, 0, sizeof(itemInfo)); + weapon->GetItemInfo(&itemInfo); + if (itemInfo.iId >= 0 && itemInfo.iId < MAX_LOCAL_WEAPONS) + { + weapon_data_t *data = &info[itemInfo.iId]; + + data->m_iId = itemInfo.iId; + data->m_iClip = ctx->m_iClip; + data->m_flTimeWeaponIdle = std::max(ctx->m_flTimeWeaponIdle, -0.001f); + data->m_flNextPrimaryAttack = std::max(ctx->m_flNextPrimaryAttack, -0.001f); + data->m_flNextSecondaryAttack = std::max(ctx->m_flNextSecondaryAttack, -0.001f); + data->m_fInReload = ctx->m_fInReload; + data->m_fInSpecialReload = ctx->m_fInSpecialReload; + data->fuser1 = std::max(weapon->pev->fuser1, -0.001f); + } + } + item = item->m_pNext; + } + } + } return 1; } @@ -1615,34 +1655,76 @@ engine sets cd to 0 before calling. */ void UpdateClientData ( const struct edict_s *ent, int sendweapons, struct clientdata_s *cd ) { - cd->flags = ent->v.flags; - cd->health = ent->v.health; + if (!ent || !ent->pvPrivateData) + return; - cd->viewmodel = MODEL_INDEX( STRING( ent->v.viewmodel ) ); + entvars_t *pev = const_cast(&ent->v); + CBasePlayer *player = dynamic_cast(CBasePlayer::Instance(pev)); - cd->waterlevel = ent->v.waterlevel; - cd->watertype = ent->v.watertype; - cd->weapons = 0; // not used + cd->flags = pev->flags; + cd->health = pev->health; + cd->viewmodel = MODEL_INDEX(STRING(pev->viewmodel)); + cd->waterlevel = pev->waterlevel; + cd->watertype = pev->watertype; + cd->weapons = 0; // not used // Vectors - cd->origin = ent->v.origin; - cd->velocity = ent->v.velocity; - cd->view_ofs = ent->v.view_ofs; - cd->punchangle = ent->v.punchangle; - - cd->bInDuck = ent->v.bInDuck; - cd->flTimeStepSound = ent->v.flTimeStepSound; - cd->flDuckTime = ent->v.flDuckTime; - cd->flSwimTime = ent->v.flSwimTime; - cd->waterjumptime = ent->v.teleport_time; - - strcpy( cd->physinfo, ENGINE_GETPHYSINFO( ent ) ); - - cd->maxspeed = ent->v.maxspeed; - cd->fov = ent->v.fov; - cd->weaponanim = ent->v.weaponanim; - - cd->pushmsec = ent->v.pushmsec; + cd->origin = pev->origin; + cd->velocity = pev->velocity; + cd->view_ofs = pev->view_ofs; + cd->punchangle = pev->punchangle; + + cd->bInDuck = pev->bInDuck; + cd->flTimeStepSound = pev->flTimeStepSound; + cd->flDuckTime = pev->flDuckTime; + cd->flSwimTime = pev->flSwimTime; + cd->waterjumptime = pev->teleport_time; + + strncpy(cd->physinfo, ENGINE_GETPHYSINFO(ent), sizeof(cd->physinfo)); + + cd->maxspeed = pev->maxspeed; + cd->fov = pev->fov; + cd->weaponanim = pev->weaponanim; + cd->pushmsec = pev->pushmsec; + + if (sendweapons && player) + { + cd->m_flNextAttack = player->m_flNextAttack; + //cd->fuser2 = player->m_flNextAmmoBurn; + //cd->fuser3 = player->m_flAmmoStartCharge; + //cd->vuser1.x = player->ammo_9mm; + //cd->vuser1.y = player->ammo_357; + //cd->vuser1.z = player->ammo_argrens; + //cd->ammo_nails = player->ammo_bolts; + //cd->ammo_shells = player->ammo_buckshot; + //cd->ammo_rockets = player->ammo_rockets; + //cd->ammo_cells = player->ammo_uranium; + //cd->vuser2.x = player->ammo_hornets; + + CBasePlayerItem *item = player->m_pActiveItem; + if (item) + { + CBasePlayerWeapon *weapon = static_cast(item->GetWeaponPtr()); + if (weapon && weapon->m_pWeaponContext->UseDecrement()) + { + ItemInfo itemInfo; + memset(&itemInfo, 0, sizeof(itemInfo)); + weapon->GetItemInfo(&itemInfo); + + cd->m_iId = itemInfo.iId; + cd->vuser3.z = weapon->m_pWeaponContext->m_iSecondaryAmmoType; + cd->vuser4.x = weapon->m_pWeaponContext->m_iPrimaryAmmoType; + cd->vuser4.y = player->m_rgAmmo[weapon->m_pWeaponContext->m_iPrimaryAmmoType]; + cd->vuser4.z = player->m_rgAmmo[weapon->m_pWeaponContext->m_iSecondaryAmmoType]; + + //if (weapon->iWeaponID() == WEAPON_RPG) + //{ + // cd->vuser2.y = ((CRpg *)item)->m_fSpotActive; + // cd->vuser2.z = ((CRpg *)item)->m_cActiveRockets; + //} + } + } + } } /* diff --git a/server/combat.cpp b/server/combat.cpp index dcbdef07c..b5a18620c 100644 --- a/server/combat.cpp +++ b/server/combat.cpp @@ -29,6 +29,7 @@ #include "animation.h" #include "weapons.h" #include "func_break.h" +#include "monster_satchel.h" extern DLL_GLOBAL Vector g_vecAttackDir; extern DLL_GLOBAL int g_iSkillLevel; @@ -1661,3 +1662,31 @@ void CBaseMonster :: MakeDamageBloodDecal ( int cCount, float flNoise, TraceResu } } +//========================================================= +// DeactivateSatchels - removes all satchels owned by +// the provided player. Should only be used upon death. +// +// Made this global on purpose. +//========================================================= +void DeactivateSatchels( CBasePlayer *pOwner ) +{ + edict_t *pFind; + + pFind = FIND_ENTITY_BY_CLASSNAME( NULL, "monster_satchel" ); + + while ( !FNullEnt( pFind ) ) + { + CBaseEntity *pEnt = CBaseEntity::Instance( pFind ); + CSatchelCharge *pSatchel = (CSatchelCharge *)pEnt; + + if ( pSatchel ) + { + if ( pSatchel->pev->owner == pOwner->edict() ) + { + pSatchel->Deactivate(); + } + } + + pFind = FIND_ENTITY_BY_CLASSNAME( pFind, "monster_satchel" ); + } +} diff --git a/server/datamap.h b/server/datamap.h index 241817671..c3ebc7b5a 100644 --- a/server/datamap.h +++ b/server/datamap.h @@ -14,6 +14,7 @@ #include "utlarray.h" #include "ehandle.h" #include +#include // SINGLE_INHERITANCE restricts the size of CBaseEntity pointers-to-member-functions to 4 bytes class __single_inheritance CBaseEntity; @@ -72,8 +73,10 @@ DECLARE_FIELD_SIZE( FIELD_TIME, sizeof(float), sizeof(float) ) DECLARE_FIELD_SIZE( FIELD_MODELNAME, sizeof(int), sizeof(int) ) DECLARE_FIELD_SIZE( FIELD_SOUNDNAME, sizeof(int), sizeof(int) ) -#define _FIELD(name, fieldtype, count, flags, mapname ) { fieldtype, #name, offsetof(classNameTypedef, name), count, flags, mapname, NULL } -#define _EFIELD(name, fieldtype, count, flags, mapname ) { fieldtype, #name, offsetof(entvars_t, name), count, flags, mapname, NULL } +#define _FIELD( name, fieldtype, count, flags, mapname ) { fieldtype, #name, offsetof(classNameTypedef, name), count, flags, mapname, NULL, NULL, NULL } +#define _EFIELD( name, fieldtype, count, flags, mapname ) { fieldtype, #name, offsetof(entvars_t, name), count, flags, mapname, NULL, NULL, NULL } +#define _CFIELD( name, fieldtype, count, flags, mapname, getter, setter ) { fieldtype, #name, 0, count, flags, mapname, NULL, getter, setter } + #define DEFINE_FIELD_NULL { FIELD_VOID, 0, 0, 0, 0, 0, 0 } #define DEFINE_FIELD(name, fieldtype) _FIELD(name, fieldtype, 1, FTYPEDESC_SAVE, NULL ) #define DEFINE_KEYFIELD(name, fieldtype, mapname) _FIELD(name, fieldtype, 1, FTYPEDESC_KEY|FTYPEDESC_SAVE, mapname ) @@ -83,6 +86,7 @@ DECLARE_FIELD_SIZE( FIELD_SOUNDNAME, sizeof(int), sizeof(int) ) #define DEFINE_ENTITY_GLOBAL_FIELD(name, fieldtype) _EFIELD(name, fieldtype, 1, FTYPEDESC_KEY|FTYPEDESC_SAVE|FTYPEDESC_GLOBAL, #name ) #define DEFINE_GLOBAL_FIELD(name, fieldtype) _FIELD(name, fieldtype, 1, FTYPEDESC_GLOBAL|FTYPEDESC_SAVE, NULL ) #define DEFINE_GLOBAL_KEYFIELD(name, fieldtype, mapname) _FIELD(name, fieldtype, 1, FTYPEDESC_GLOBAL|FTYPEDESC_KEY|FTYPEDESC_SAVE, mapname ) +#define DEFINE_CUSTOM_FIELD(name, fieldtype, getter, setter) _CFIELD(name, fieldtype, 1, FTYPEDESC_SAVE|FTYPEDESC_CUSTOMCALLBACK, NULL, getter, setter ) // replaces EXPORT table for portability and non-DLL based systems (xbox) #define DEFINE_FUNCTION_RAW( function, func_type ) { FIELD_VOID, nameHolder.GenerateName(#function), 0, 1, FTYPEDESC_FUNCTIONTABLE, NULL, (func_t)((func_type)(&classNameTypedef::function)) } @@ -91,7 +95,9 @@ DECLARE_FIELD_SIZE( FIELD_SOUNDNAME, sizeof(int), sizeof(int) ) // // Generic function prototype. // -typedef void (CBaseEntity::*func_t)(void); +using func_t = void (CBaseEntity::*)(void); +using field_getter_t = std::function; +using field_setter_t = std::function; typedef struct typedescription_s { @@ -100,8 +106,10 @@ typedef struct typedescription_s int fieldOffset; unsigned short fieldSize; short flags; - const char *mapName; // a name of keyfield on a map (e.g. "origin", "spawnflags" etc) - func_t func; // pointer to a function + const char *mapName; // a name of keyfield on a map (e.g. "origin", "spawnflags" etc) + func_t func; // pointer to a function + field_getter_t storeCallback; + field_setter_t loadCallback; } TYPEDESCRIPTION; diff --git a/server/enginecallback.h b/server/enginecallback.h index 5875d33a4..4a45ddd11 100644 --- a/server/enginecallback.h +++ b/server/enginecallback.h @@ -16,6 +16,8 @@ #define ENGINECALLBACK_H #pragma once +#include "edict.h" +#include "eiface.h" #include "event_flags.h" // Must be provided by user of this code diff --git a/server/entities/ammo_9mmclip.cpp b/server/entities/ammo_9mmclip.cpp index 8d93d0116..ca74d6626 100644 --- a/server/entities/ammo_9mmclip.cpp +++ b/server/entities/ammo_9mmclip.cpp @@ -14,6 +14,7 @@ ****/ #include "ammo_9mmclip.h" +#include "weapons/glock.h" LINK_ENTITY_TO_CLASS( ammo_glockclip, CGlockAmmo ); LINK_ENTITY_TO_CLASS( ammo_9mmclip, CGlockAmmo ); @@ -33,7 +34,7 @@ void CGlockAmmo::Precache() BOOL CGlockAmmo::AddAmmo( CBaseEntity *pOther ) { - if (pOther->GiveAmmo( AMMO_GLOCKCLIP_GIVE, "9mm", _9MM_MAX_CARRY ) != -1) + if (pOther->GiveAmmo( GLOCK_MAX_CLIP, "9mm", _9MM_MAX_CARRY ) != -1) { EMIT_SOUND(ENT(pev), CHAN_ITEM, "items/9mmclip1.wav", 1, ATTN_NORM); return TRUE; diff --git a/server/entities/cycler_weapon.cpp b/server/entities/cycler_weapon.cpp deleted file mode 100644 index bd0c5027d..000000000 --- a/server/entities/cycler_weapon.cpp +++ /dev/null @@ -1,162 +0,0 @@ -/*** -* -* Copyright (c) 1996-2002, Valve LLC. All rights reserved. -* -* This product contains software technology licensed from Id -* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. -* All Rights Reserved. -* -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. -* -****/ -/* - -===== h_cycler.cpp ======================================================== - - The Halflife Cycler Monsters - -*/ - -#include "cycler_weapon.h" - -LINK_ENTITY_TO_CLASS( cycler_weapon, CWeaponCycler ); -LINK_ENTITY_TO_CLASS( weapon_question, CWeaponCycler ); // saverestore issues - -BEGIN_DATADESC( CWeaponCycler ) - DEFINE_FIELD( m_iPlayerModel, FIELD_MODELNAME ), - DEFINE_FIELD( m_iViewModel, FIELD_MODELNAME ), - DEFINE_FIELD( m_iWorldModel, FIELD_MODELNAME ), -END_DATADESC() - -void CWeaponCycler::Spawn( ) -{ - // g-cont. this alias need for right slot switching because all selectable items must be preceed with "weapon_" or "item_" - pev->classname = MAKE_STRING( "weapon_question" ); - m_iId = WEAPON_CYCLER; - pev->solid = SOLID_SLIDEBOX; - pev->movetype = MOVETYPE_NONE; - - char basemodel[80], v_path[80], p_path[80], w_path[80]; - - strncpy( basemodel, (char *)STRING(pev->model), sizeof( basemodel ) - 1 ); - - for( int i = 0; i < (int)strlen( basemodel ); i++ ) - { - int c = basemodel[i]; - - if(( c == 'v' || c == 'p' || c == 'w' ) && basemodel[i+1] == '_' ) - { - basemodel[i] = 'v'; - strcpy( v_path, basemodel ); - basemodel[i] = 'p'; - strcpy( p_path, basemodel ); - basemodel[i] = 'w'; - strcpy( w_path, basemodel ); - - // create wepon model pathes - m_iPlayerModel = ALLOC_STRING( p_path ); - m_iWorldModel = ALLOC_STRING( w_path ); - m_iViewModel = ALLOC_STRING( v_path ); - break; - } - } - - if( m_iPlayerModel && m_iWorldModel && m_iViewModel ) - { - PRECACHE_MODEL( (char *)STRING(m_iPlayerModel) ); - PRECACHE_MODEL( (char *)STRING(m_iWorldModel) ); - PRECACHE_MODEL( (char *)STRING(m_iViewModel) ); - - // set right world model - pev->model = m_iWorldModel; - - SET_MODEL( ENT(pev), STRING(pev->model) ); - } - else - { - // fallback to default relationship - PRECACHE_MODEL( (char *)STRING(pev->model) ); - SET_MODEL( ENT(pev), STRING(pev->model) ); - - // setup viewmodel - m_iViewModel = pev->model; - } - m_iClip = -1; - FallInit(); -} - -void CWeaponCycler::KeyValue( KeyValueData *pkvd ) -{ - if (FStrEq(pkvd->szKeyName, "deploy")) - { - pev->impulse = atoi(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "holster")) - { - pev->button = atoi(pkvd->szValue); - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "primary")) - { - pev->sequence = atoi(pkvd->szValue); - m_bActiveAnims.primary = strlen(pkvd->szKeyName) > 0; - pkvd->fHandled = TRUE; - } - else if (FStrEq(pkvd->szKeyName, "secondary")) - { - pev->team = atoi(pkvd->szValue); - m_bActiveAnims.secondary = strlen(pkvd->szKeyName) > 0; - pkvd->fHandled = TRUE; - } - else CBasePlayerWeapon::KeyValue( pkvd ); -} - -int CWeaponCycler::GetItemInfo(ItemInfo *p) -{ - p->pszName = "weapon_question"; // need for right HUD displaying - p->pszAmmo1 = NULL; - p->iMaxAmmo1 = -1; - p->pszAmmo2 = NULL; - p->iMaxAmmo2 = -1; - p->iMaxClip = WEAPON_NOCLIP; - p->iSlot = 0; - p->iPosition = 1; - p->iFlags = 0; - p->iId = m_iId = WEAPON_CYCLER; - p->iWeight = -1; - - return 1; -} - -BOOL CWeaponCycler::Deploy( ) -{ - return DefaultDeploy( (char *)STRING( m_iViewModel ), (char *)STRING( m_iPlayerModel ), pev->impulse, "onehanded" ); -} - -void CWeaponCycler::Holster( ) -{ - m_pPlayer->m_flNextAttack = gpGlobals->time + 1.0; - SendWeaponAnim( pev->button ); -} - -void CWeaponCycler::PrimaryAttack() -{ - if (m_bActiveAnims.primary) - { - SendWeaponAnim(pev->sequence); - m_flNextPrimaryAttack = gpGlobals->time + 3.0; - } -} - -void CWeaponCycler::SecondaryAttack( void ) -{ - if (m_bActiveAnims.secondary) - { - SendWeaponAnim(pev->team); - m_flNextSecondaryAttack = gpGlobals->time + 3.0; - } -} \ No newline at end of file diff --git a/server/entities/cycler_weapon.h b/server/entities/cycler_weapon.h deleted file mode 100644 index 7dd3bb359..000000000 --- a/server/entities/cycler_weapon.h +++ /dev/null @@ -1,57 +0,0 @@ -/*** -* -* Copyright (c) 1996-2002, Valve LLC. All rights reserved. -* -* This product contains software technology licensed from Id -* Software, Inc. ("Id Technology"). Id Technology (c) 1996 Id Software, Inc. -* All Rights Reserved. -* -* Use, distribution, and modification of this source code and/or resulting -* object code is restricted to non-commercial enhancements to products from -* Valve LLC. All other use, distribution, or modification is prohibited -* without written permission from Valve LLC. -* -****/ -/* - -===== h_cycler.cpp ======================================================== - - The Halflife Cycler Monsters - -*/ - -#pragma once - -#include "extdll.h" -#include "util.h" -#include "cbase.h" -#include "monsters.h" -#include "animation.h" -#include "weapons.h" -#include "player.h" - -//Weapon Cycler -class CWeaponCycler : public CBasePlayerWeapon -{ - DECLARE_CLASS( CWeaponCycler, CBasePlayerWeapon ); -public: - void Spawn( void ); - int GetItemInfo(ItemInfo *p); - - void PrimaryAttack( void ); - void SecondaryAttack( void ); - BOOL Deploy( void ); - void Holster( void ); - void KeyValue( KeyValueData *pkvd ); - - DECLARE_DATADESC(); -private: - string_t m_iPlayerModel; - string_t m_iWorldModel; - string_t m_iViewModel; - - struct { - bool primary = false; - bool secondary = false; - } m_bActiveAnims; -}; \ No newline at end of file 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..b2c990ea7 100644 --- a/server/player.cpp +++ b/server/player.cpp @@ -37,6 +37,7 @@ #include "hltv.h" #include "user_messages.h" #include "ropes/CRope.h" +#include // #define DUCKFIX @@ -668,7 +669,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++; } @@ -2584,6 +2585,69 @@ void CBasePlayer :: UpdatePlayerSound ( void ) m_iWeaponFlash = 0; } + +void CBasePlayer::UpdatePlayerTimers() +{ + m_flNextAttack -= gpGlobals->frametime; + if ( m_flNextAttack < -0.001 ) + m_flNextAttack = -0.001; + + //if ( m_flNextAmmoBurn != 1000 ) + //{ + // m_flNextAmmoBurn -= gpGlobals->frametime; + // + // if ( m_flNextAmmoBurn < -0.001 ) + // m_flNextAmmoBurn = -0.001; + //} + + //if ( m_flAmmoStartCharge != 1000 ) + //{ + // m_flAmmoStartCharge -= gpGlobals->frametime; + // + // if ( m_flAmmoStartCharge < -0.001 ) + // m_flAmmoStartCharge = -0.001; + //} +} + +void CBasePlayer::UpdateWeaponTimers() +{ + for (int i = 0; i < MAX_ITEM_TYPES; i++) + { + CBasePlayerItem *pPlayerItem = m_rgpPlayerItems[i]; + if (pPlayerItem) + { + while (pPlayerItem) + { + CBasePlayerWeapon *gun = (CBasePlayerWeapon *)pPlayerItem->GetWeaponPtr(); + CBaseWeaponContext *ctx = gun->m_pWeaponContext; + + if (gun && ctx->UseDecrement()) + { + ctx->m_flNextPrimaryAttack = std::max(ctx->m_flNextPrimaryAttack - gpGlobals->frametime, -1.0f); + ctx->m_flNextSecondaryAttack = std::max(ctx->m_flNextSecondaryAttack - gpGlobals->frametime, -0.001f); + + if (ctx->m_flTimeWeaponIdle != 1000) + { + ctx->m_flTimeWeaponIdle = std::max(ctx->m_flTimeWeaponIdle - gpGlobals->frametime, -0.001f); + } + + if (gun->pev->fuser1 != 1000) + { + gun->pev->fuser1 = std::max(gun->pev->fuser1 - gpGlobals->frametime, -0.001f); + } + + // Only decrement if not flagged as NO_DECREMENT + // if ( gun->m_flPumpTime != 1000 ) + // { + // gun->m_flPumpTime = max( gun->m_flPumpTime - gpGlobals->frametime, -0.001f ); + // } + } + pPlayerItem = pPlayerItem->m_pNext; + } + } + } +} + void CBasePlayer::PostThink() { if ( g_fGameOver ) @@ -2623,7 +2687,7 @@ void CBasePlayer::PostThink() } // do weapon stuff - ItemPostFrame( ); + ItemPostFrame(); // check to see if player landed hard enough to make a sound // falling farther than half of the maximum safe distance, but not as far a max safe distance will @@ -2697,6 +2761,8 @@ void CBasePlayer::PostThink() CheckPowerups(pev); UpdatePlayerSound(); + UpdatePlayerTimers(); + UpdateWeaponTimers(); // Track button info so we can detect 'pressed' and 'released' buttons next frame m_afButtonLast = pev->button; @@ -3249,6 +3315,11 @@ int CBasePlayer::Restore( CRestore &restore ) RenewItems(); + // HACK: This variable is saved/restored in CBaseMonster as a time variable, but we're using it + // as just a counter. Ideally, this needs its own variable that's saved as a plain float. + // Barring that, we clear it out here instead of using the incorrect restored time value. + m_flNextAttack = 0.0f; + return status; } @@ -4030,7 +4101,7 @@ Called every frame by the player PreThink */ void CBasePlayer::ItemPreFrame() { - if ( gpGlobals->time < m_flNextAttack ) + if ( m_flNextAttack > 0.0f ) { return; } @@ -4059,7 +4130,7 @@ void CBasePlayer::ItemPostFrame() ImpulseCommands(); - if ( gpGlobals->time < m_flNextAttack ) + if ( m_flNextAttack > 0.0f ) return; if( FBitSet( m_iHideHUD, HIDEHUD_WEAPONS )) @@ -4068,7 +4139,7 @@ void CBasePlayer::ItemPostFrame() if (!m_pActiveItem) return; - m_pActiveItem->ItemPostFrame( ); + m_pActiveItem->ItemPostFrame(); } int CBasePlayer::AmmoInventory( int iAmmoIndex ) @@ -4090,10 +4161,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 +4520,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; @@ -4605,91 +4676,6 @@ void CBasePlayer :: HideWeapons( BOOL fHideWeapons ) #define DOT_20DEGREE 0.9396926207859 #define DOT_25DEGREE 0.9063077870367 -//========================================================= -// Autoaim -// set crosshair position to point to enemey -//========================================================= -Vector CBasePlayer :: GetAutoaimVector( float flDelta ) -{ - if (g_iSkillLevel == SKILL_HARD) - { - UTIL_MakeVectors( pev->v_angle + pev->punchangle ); - return gpGlobals->v_forward; - } - - Vector vecSrc = GetGunPosition( ); - float flDist = 8192; - - // always use non-sticky autoaim - // UNDONE: use server variable to chose! - if (1 || g_iSkillLevel == SKILL_MEDIUM) - { - m_vecAutoAim = Vector( 0, 0, 0 ); - // flDelta *= 0.5; - } - - BOOL m_fOldTargeting = m_fOnTarget; - Vector angles = AutoaimDeflection(vecSrc, flDist, flDelta ); - - // update ontarget if changed - if ( !g_pGameRules->AllowAutoTargetCrosshair() ) - m_fOnTarget = 0; - else if (m_fOldTargeting != m_fOnTarget) - { - m_pActiveItem->UpdateItemInfo( ); - } - - if (angles.x > 180) - angles.x -= 360; - if (angles.x < -180) - angles.x += 360; - if (angles.y > 180) - angles.y -= 360; - if (angles.y < -180) - angles.y += 360; - - if (angles.x > 25) - angles.x = 25; - if (angles.x < -25) - angles.x = -25; - if (angles.y > 12) - angles.y = 12; - if (angles.y < -12) - angles.y = -12; - - - // always use non-sticky autoaim - // UNDONE: use sever variable to chose! - if (0 || g_iSkillLevel == SKILL_EASY) - { - m_vecAutoAim = m_vecAutoAim * 0.67 + angles * 0.33; - } - else - { - m_vecAutoAim = angles * 0.9; - } - - // m_vecAutoAim = m_vecAutoAim * 0.99; - - // Don't send across network if sv_aim is 0 - if ( g_psv_aim->value != 0 ) - { - if ( m_vecAutoAim.x != m_lastx || m_vecAutoAim.y != m_lasty ) - { - SET_CROSSHAIRANGLE( edict(), -m_vecAutoAim.x, m_vecAutoAim.y ); - - m_lastx = m_vecAutoAim.x; - m_lasty = m_vecAutoAim.y; - } - } - - // ALERT( at_console, "%f %f\n", angles.x, angles.y ); - - UTIL_MakeVectors( pev->v_angle + pev->punchangle + m_vecAutoAim ); - return gpGlobals->v_forward; -} - - Vector CBasePlayer :: AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ) { edict_t *pEdict = INDEXENT( 1 ); @@ -4915,7 +4901,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/player.h b/server/player.h index 2839364ac..92f8b514f 100644 --- a/server/player.h +++ b/server/player.h @@ -272,6 +272,8 @@ class CBasePlayer : public CBaseMonster void FlashlightTurnOff( void ); void UpdatePlayerSound ( void ); + void UpdatePlayerTimers(); + void UpdateWeaponTimers(); void DeathSound ( void ); void TransferReset( void ); @@ -328,7 +330,6 @@ class CBasePlayer : public CBaseMonster int Illumination( void ); void ResetAutoaim( void ); - Vector GetAutoaimVector( float flDelta ); Vector AutoaimDeflection( Vector &vecSrc, float flDist, float flDelta ); void ForceClientDllUpdate( void ); // Forces all client .dll specific data to be resent to client. @@ -409,11 +410,6 @@ class CBasePlayer : public CBaseMonster bool m_bRainNeedsUpdate; // don't save\restore this }; -#define AUTOAIM_2DEGREES 0.0348994967025 -#define AUTOAIM_5DEGREES 0.08715574274766 -#define AUTOAIM_8DEGREES 0.1391731009601 -#define AUTOAIM_10DEGREES 0.1736481776669 - extern BOOL gInitHUD; diff --git a/server/saverestore.cpp b/server/saverestore.cpp index ae760acbf..4b3e366e1 100644 --- a/server/saverestore.cpp +++ b/server/saverestore.cpp @@ -23,6 +23,7 @@ #include "extdll.h" #include "util.h" #include "cbase.h" +#include #define ENTVARS_COUNT ARRAYSIZE( gEntvarsDescription ) @@ -367,6 +368,17 @@ unsigned short CSaveRestoreBuffer :: TokenHash( const char *pszToken ) return 0; } +int CSaveRestoreBuffer::GetFieldSize(TYPEDESCRIPTION *type) +{ + // TODO add out-of-bounds checks? + return gSizes[type->fieldType]; +} + +int CSaveRestoreBuffer::GetFieldInputSize(TYPEDESCRIPTION *type) +{ + return gInputSizes[type->fieldType]; +} + void CSave :: Log( DATAMAP *pMap, const char *pName, const char *pFieldName, FIELDTYPE fieldType, void *value, int count ) { // Check to see if we are logging. @@ -687,15 +699,28 @@ int CSave :: WriteFields( const char *pname, const void *pBaseData, DATAMAP *pMa { int i, j, actualCount, emptyCount; int entityArray[MAX_ENTITYARRAY]; + std::vector dataBuffer; TYPEDESCRIPTION *pTest; + void *pOutputData; // Precalculate the number of empty fields emptyCount = 0; for ( i = 0; i < fieldCount; i++ ) { - void *pOutputData; - pOutputData = ((char *)pBaseData + pFields[i].fieldOffset ); - if ( DataEmpty( (const char *)pOutputData, pFields[i].fieldSize * gSizes[pFields[i].fieldType] ) ) + pTest = &pFields[ i ]; + + if (!FBitSet(pTest->flags, FTYPEDESC_CUSTOMCALLBACK)) { + pOutputData = ((char *)pBaseData + pTest->fieldOffset); + } + else + { + size_t dataSize = GetFieldSize(pTest); + dataBuffer.resize(dataSize); + pTest->storeCallback((CBaseEntity*)pBaseData, dataBuffer.data(), dataSize); + pOutputData = dataBuffer.data(); + } + + if ( DataEmpty( (const char *)pOutputData, pFields[i].fieldSize * GetFieldSize(pTest) ) ) emptyCount++; } @@ -705,12 +730,21 @@ int CSave :: WriteFields( const char *pname, const void *pBaseData, DATAMAP *pMa for ( i = 0; i < fieldCount; i++ ) { - void *pOutputData; pTest = &pFields[ i ]; - pOutputData = ((char *)pBaseData + pTest->fieldOffset ); + + if (!FBitSet(pTest->flags, FTYPEDESC_CUSTOMCALLBACK)) { + pOutputData = ((char *)pBaseData + pTest->fieldOffset); + } + else + { + size_t dataSize = GetFieldSize(pTest); + dataBuffer.resize(dataSize); + pTest->storeCallback((CBaseEntity*)pBaseData, dataBuffer.data(), dataSize); + pOutputData = dataBuffer.data(); + } // UNDONE: Must we do this twice? - if ( DataEmpty( (const char *)pOutputData, pTest->fieldSize * gSizes[pTest->fieldType] ) ) + if ( DataEmpty( (const char *)pOutputData, pTest->fieldSize * GetFieldSize(pTest) ) ) continue; #ifdef _DEBUG @@ -856,6 +890,7 @@ int CRestore::ReadField( const void *pBaseData, DATAMAP *pMap, TYPEDESCRIPTION * Vector position; edict_t *pent; char *pString; + std::vector dataBuffer; time = 0; position = Vector(0,0,0); @@ -877,8 +912,18 @@ int CRestore::ReadField( const void *pBaseData, DATAMAP *pMap, TYPEDESCRIPTION * { for ( j = 0; j < pTest->fieldSize; j++ ) { - void *pOutputData = ((char *)pBaseData + pTest->fieldOffset + (j*gSizes[pTest->fieldType]) ); - void *pInputData = (char *)pData + j * gInputSizes[pTest->fieldType]; + void *pOutputData; + void *pInputData = (char *)pData + j * GetFieldInputSize(pTest); + size_t dataSize = GetFieldSize(pTest); + + if (!FBitSet(pTest->flags, FTYPEDESC_CUSTOMCALLBACK)) { + pOutputData = ((char *)pBaseData + pTest->fieldOffset + (j * GetFieldSize(pTest))); + } + else + { + dataBuffer.resize(dataSize); + pOutputData = dataBuffer.data(); + } switch( pTest->fieldType ) { @@ -988,6 +1033,12 @@ int CRestore::ReadField( const void *pBaseData, DATAMAP *pMap, TYPEDESCRIPTION * default: ALERT( at_error, "Bad field type\n" ); } + + // value was written to buffer, then make external code aware of this + // and pass these data to that external code + if (FBitSet(pTest->flags, FTYPEDESC_CUSTOMCALLBACK)) { + pTest->loadCallback((CBaseEntity*)pBaseData, dataBuffer.data(), dataSize); + } } } return fieldNumber; @@ -1027,9 +1078,12 @@ int CRestore::ReadFields( const char *pname, const void *pBaseData, DATAMAP *pMa // Clear out base data for (int i = 0; i < fieldCount; i++) { - // Don't clear global fields - if (!m_global || !(pFields[i].flags & FTYPEDESC_GLOBAL)) - memset(((char *)pBaseData + pFields[i].fieldOffset), 0, pFields[i].fieldSize * gSizes[pFields[i].fieldType]); + if (!FBitSet(pFields[i].flags, FTYPEDESC_CUSTOMCALLBACK)) + { + // Don't clear global fields + if (!m_global || !(pFields[i].flags & FTYPEDESC_GLOBAL)) + memset(((char *)pBaseData + pFields[i].fieldOffset), 0, pFields[i].fieldSize * GetFieldSize(&pFields[i])); + } } for (int i = 0; i < fileCount; i++) diff --git a/server/saverestore.h b/server/saverestore.h index 84da6e47e..3c80e6c7d 100644 --- a/server/saverestore.h +++ b/server/saverestore.h @@ -16,6 +16,8 @@ #ifndef SAVERESTORE_H #define SAVERESTORE_H +#include "eiface.h" + class CBaseEntity; class CSaveRestoreBuffer @@ -37,6 +39,10 @@ class CSaveRestoreBuffer unsigned short TokenHash( const char *pszToken ); Vector GetLandmark() const { return ( m_pdata->fUseLandmark ) ? m_pdata->vecLandmarkOffset : g_vecZero; } + + static int GetFieldSize(TYPEDESCRIPTION *type); + static int GetFieldInputSize(TYPEDESCRIPTION *type); + Vector modelSpaceOffset; // used only for globaly entity brushes modelled in different coordinate systems. Vector modelOriginOffset; protected: diff --git a/server/server_weapon_layer_impl.cpp b/server/server_weapon_layer_impl.cpp new file mode 100644 index 000000000..91a0a304c --- /dev/null +++ b/server/server_weapon_layer_impl.cpp @@ -0,0 +1,283 @@ +/* +server_weapon_layer_impl.cpp - part of server-side weapons predicting implementation +Copyright (C) 2025 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. +*/ + +#include "server_weapon_layer_impl.h" +#include "gamerules.h" +#include "game.h" + +CServerWeaponLayerImpl::CServerWeaponLayerImpl(CBasePlayerWeapon *weaponEntity) : + m_pWeapon(weaponEntity) +{ +} + +int CServerWeaponLayerImpl::GetWeaponBodygroup() +{ + return m_pWeapon->pev->body; +} + +Vector CServerWeaponLayerImpl::GetGunPosition() +{ + return m_pWeapon->m_pPlayer->pev->origin + m_pWeapon->m_pPlayer->pev->view_ofs; +} + +matrix3x3 CServerWeaponLayerImpl::GetCameraOrientation() +{ + CBasePlayer *player = m_pWeapon->m_pPlayer; + return matrix3x3(player->pev->v_angle + player->pev->punchangle); +} + +Vector CServerWeaponLayerImpl::GetAutoaimVector(float delta) +{ + CBasePlayer *player = m_pWeapon->m_pPlayer; + + if (g_iSkillLevel == SKILL_HARD) + { + UTIL_MakeVectors( player->pev->v_angle + player->pev->punchangle ); + return gpGlobals->v_forward; + } + + Vector vecSrc = GetGunPosition( ); + float flDist = 8192; + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (1 || g_iSkillLevel == SKILL_MEDIUM) + { + player->m_vecAutoAim = Vector( 0, 0, 0 ); + // flDelta *= 0.5; + } + + BOOL m_fOldTargeting = player->m_fOnTarget; + Vector angles = player->AutoaimDeflection(vecSrc, flDist, delta ); + + // update ontarget if changed + if ( !g_pGameRules->AllowAutoTargetCrosshair() ) + player->m_fOnTarget = 0; + else if (m_fOldTargeting != player->m_fOnTarget) + { + player->m_pActiveItem->UpdateItemInfo( ); + } + + if (angles.x > 180) + angles.x -= 360; + if (angles.x < -180) + angles.x += 360; + if (angles.y > 180) + angles.y -= 360; + if (angles.y < -180) + angles.y += 360; + + if (angles.x > 25) + angles.x = 25; + if (angles.x < -25) + angles.x = -25; + if (angles.y > 12) + angles.y = 12; + if (angles.y < -12) + angles.y = -12; + + + // always use non-sticky autoaim + // UNDONE: use sever variable to chose! + if (0 || g_iSkillLevel == SKILL_EASY) + { + player->m_vecAutoAim = player->m_vecAutoAim * 0.67 + angles * 0.33; + } + else + { + player->m_vecAutoAim = angles * 0.9; + } + + // m_vecAutoAim = m_vecAutoAim * 0.99; + + // Don't send across network if sv_aim is 0 + if ( g_psv_aim->value != 0 ) + { + if ( player->m_vecAutoAim.x != player->m_lastx || + player->m_vecAutoAim.y != player->m_lasty ) + { + SET_CROSSHAIRANGLE( player->edict(), -player->m_vecAutoAim.x, player->m_vecAutoAim.y ); + + player->m_lastx = player->m_vecAutoAim.x; + player->m_lasty = player->m_vecAutoAim.y; + } + } + + // ALERT( at_console, "%f %f\n", angles.x, angles.y ); + + UTIL_MakeVectors( player->pev->v_angle + player->pev->punchangle + player->m_vecAutoAim ); + return gpGlobals->v_forward; +} + +Vector CServerWeaponLayerImpl::FireBullets(int bullets, Vector origin, matrix3x3 orientation, float distance, float spread, int bulletType, uint32_t seed, int damage) +{ + float x, y, z; + TraceResult tr; + CBasePlayer *player = m_pWeapon->m_pPlayer; + + ClearMultiDamage(); + gMultiDamage.type = DMG_BULLET | DMG_NEVERGIB; + + for (uint32_t i = 1; i <= bullets; i++) + { + // use player's random seed, get circular gaussian spread + x = m_randomGenerator.GetFloat(seed + i, -0.5f, 0.5f) + m_randomGenerator.GetFloat(seed + 1 + i, -0.5f, 0.5f); + y = m_randomGenerator.GetFloat(seed + 2 + i, -0.5f, 0.5f) + m_randomGenerator.GetFloat(seed + 3 + i, -0.5f, 0.5f); + z = x * x + y * y; + + Vector vecDir = orientation.GetForward() + + x * spread * orientation.GetRight() + + y * spread * orientation.GetUp(); + Vector vecEnd = origin + vecDir * distance; + + SetBits(gpGlobals->trace_flags, FTRACE_MATERIAL_TRACE); + UTIL_TraceLine(origin, vecEnd, dont_ignore_monsters, ENT(player->pev), &tr); + ClearBits(gpGlobals->trace_flags, FTRACE_MATERIAL_TRACE); + + // do damage, paint decals + if (tr.flFraction != 1.0) + { + CBaseEntity *pEntity = CBaseEntity::Instance(tr.pHit); + + if ( damage ) + { + pEntity->TraceAttack(player->pev, damage, vecDir, &tr, DMG_BULLET | ((damage > 16) ? DMG_ALWAYSGIB : DMG_NEVERGIB) ); + + TEXTURETYPE_PlaySound(&tr, origin, vecEnd, bulletType); + DecalGunshot( &tr, bulletType, origin, vecEnd ); + } + else switch(bulletType) + { + default: + case BULLET_PLAYER_9MM: + pEntity->TraceAttack(player->pev, gSkillData.plrDmg9MM, vecDir, &tr, DMG_BULLET); + break; + + case BULLET_PLAYER_MP5: + pEntity->TraceAttack(player->pev, gSkillData.plrDmgMP5, vecDir, &tr, DMG_BULLET); + break; + + case BULLET_PLAYER_BUCKSHOT: + // make distance based! + pEntity->TraceAttack(player->pev, gSkillData.plrDmgBuckshot, vecDir, &tr, DMG_BULLET); + break; + + case BULLET_PLAYER_357: + pEntity->TraceAttack(player->pev, gSkillData.plrDmg357, vecDir, &tr, DMG_BULLET); + break; + + case BULLET_NONE: // FIX + pEntity->TraceAttack(player->pev, 50, vecDir, &tr, DMG_CLUB); + TEXTURETYPE_PlaySound(&tr, origin, vecEnd, bulletType); + if ( !FNullEnt(tr.pHit) && VARS(tr.pHit)->rendermode != 0) // only decal glass + { + UTIL_DecalTrace( &tr, "{break1" ); + } + + break; + } + } + // make bullet trails + UTIL_BubbleTrail( origin, tr.vecEndPos, (distance * tr.flFraction) / 64.0 ); + } + + ApplyMultiDamage(player->pev, player->pev); + return Vector( x * spread, y * spread, 0.0 ); +} + +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; +} + +int CServerWeaponLayerImpl::GetPlayerViewmodel() +{ + return m_pWeapon->m_pPlayer->pev->viewmodel; +} + +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 m_pWeapon->m_pPlayer->m_flNextAttack; +} + +void CServerWeaponLayerImpl::SetPlayerNextAttackTime(float value) +{ + m_pWeapon->m_pPlayer->m_flNextAttack = value; +} + +float CServerWeaponLayerImpl::GetWeaponTimeBase(bool usePredicting) +{ + return usePredicting ? 0.0f : gpGlobals->time; +} + +uint32_t CServerWeaponLayerImpl::GetRandomSeed() +{ + return m_pWeapon->m_pPlayer->random_seed; +} + +uint32_t CServerWeaponLayerImpl::GetRandomInt(uint32_t seed, int32_t min, int32_t max) +{ + return m_randomGenerator.GetInteger(seed, min, max); +} + +float CServerWeaponLayerImpl::GetRandomFloat(uint32_t seed, float min, float max) +{ + return m_randomGenerator.GetFloat(seed, min, max); +} + +uint16_t CServerWeaponLayerImpl::PrecacheEvent(const char *eventName) +{ + return g_engfuncs.pfnPrecacheEvent(1, eventName); +} + +void CServerWeaponLayerImpl::PlaybackWeaponEvent(const WeaponEventParams ¶ms) +{ + g_engfuncs.pfnPlaybackEvent(static_cast(params.flags), m_pWeapon->m_pPlayer->edict(), + params.eventindex, params.delay, + params.origin, params.angles, + params.fparam1, params.fparam2, + params.iparam1, params.iparam2, + params.bparam1, params.bparam2); +} + +bool CServerWeaponLayerImpl::ShouldRunFuncs() +{ + return true; // always true, because server do not any kind of subticking for weapons +} diff --git a/server/server_weapon_layer_impl.h b/server/server_weapon_layer_impl.h new file mode 100644 index 000000000..ff7b21603 --- /dev/null +++ b/server/server_weapon_layer_impl.h @@ -0,0 +1,60 @@ +/* +server_weapon_layer_impl.h - part of server-side weapons predicting implementation +Copyright (C) 2025 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 "weapon_layer.h" +#include "extdll.h" +#include "util.h" +#include "cbase.h" +#include "player.h" +#include "monsters.h" +#include "weapons.h" +#include "seeded_random_generator.h" + +class CServerWeaponLayerImpl : public IWeaponLayer +{ +public: + CServerWeaponLayerImpl(CBasePlayerWeapon *weaponEntity); + ~CServerWeaponLayerImpl() = default; + + int GetWeaponBodygroup() override; + Vector GetGunPosition() override; + matrix3x3 GetCameraOrientation() override; + Vector GetAutoaimVector(float delta) override; + Vector FireBullets(int bullets, Vector origin, matrix3x3 orientation, float distance, float spread, int bulletType, uint32_t seed, int damage = 0) override; + CBasePlayerWeapon* GetWeaponEntity() override { return m_pWeapon; }; + + int GetPlayerAmmo(int ammoType) override; + void SetPlayerAmmo(int ammoType, int count) override; + void SetPlayerWeaponAnim(int anim) override; + void SetPlayerViewmodel(int model) override; + int GetPlayerViewmodel() override; + bool CheckPlayerButtonFlag(int buttonMask) override; + void ClearPlayerButtonFlag(int buttonMask) override; + float GetPlayerNextAttackTime() override; + void SetPlayerNextAttackTime(float value) override; + + float GetWeaponTimeBase(bool usePredicting) override; + uint32_t GetRandomSeed() override; + uint32_t GetRandomInt(uint32_t seed, int32_t min, int32_t max) override; + float GetRandomFloat(uint32_t seed, float min, float max) override; + uint16_t PrecacheEvent(const char *eventName) override; + void PlaybackWeaponEvent(const WeaponEventParams ¶ms) override; + bool ShouldRunFuncs() override; + +private: + CBasePlayerWeapon *m_pWeapon; + CSeededRandomGenerator m_randomGenerator; +}; diff --git a/server/stats.cpp b/server/stats.cpp index 5650336d3..2cc0ef87f 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_pWeaponContext->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/util.h b/server/util.h index 353833936..e1a764f20 100644 --- a/server/util.h +++ b/server/util.h @@ -29,7 +29,7 @@ #include "datamap.h" #include "xashxt_strings.h" - +#include "extdll.h" #include "com_model.h" #include "sprite.h" diff --git a/server/weapons.cpp b/server/weapons.cpp index 54ec7477d..5ec1b3602 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 ), @@ -406,14 +402,25 @@ BEGIN_DATADESC( CBasePlayerItem ) DEFINE_FUNCTION( AttemptToMaterialize ), END_DATADESC() +#define DEFINE_BASEPLAYERWEAPON_FIELD( x, ft ) \ + DEFINE_CUSTOM_FIELD( x, ft, [](CBaseEntity *pEntity, void *pData, size_t dataSize) { \ + CBasePlayerWeapon *p = reinterpret_cast(pEntity); \ + std::memcpy(pData, &p->m_pWeaponContext->x, dataSize); \ + }, \ + [](CBaseEntity *pEntity, const void *pData, size_t dataSize) { \ + CBasePlayerWeapon *p = reinterpret_cast(pEntity); \ + std::memcpy(&p->m_pWeaponContext->x, pData, dataSize); \ + }) + 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_BASEPLAYERWEAPON_FIELD( m_flNextPrimaryAttack, FIELD_TIME ), + DEFINE_BASEPLAYERWEAPON_FIELD( m_flNextSecondaryAttack, FIELD_TIME ), + DEFINE_BASEPLAYERWEAPON_FIELD( m_flTimeWeaponIdle, FIELD_TIME ), + DEFINE_BASEPLAYERWEAPON_FIELD( m_iPrimaryAmmoType, FIELD_INTEGER ), + DEFINE_BASEPLAYERWEAPON_FIELD( m_iSecondaryAmmoType, FIELD_INTEGER ), + DEFINE_BASEPLAYERWEAPON_FIELD( m_iClip, FIELD_INTEGER ), + DEFINE_BASEPLAYERWEAPON_FIELD( m_iDefaultAmmo, FIELD_INTEGER ), + DEFINE_BASEPLAYERWEAPON_FIELD( m_iId, FIELD_INTEGER ), END_DATADESC() void CBasePlayerItem :: SetObjectCollisionBox( void ) @@ -584,84 +591,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 +650,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_pWeaponContext->m_iDefaultAmmo ) { return ExtractAmmo( (CBasePlayerWeapon *)pOriginal ); } @@ -731,22 +661,33 @@ int CBasePlayerWeapon::AddDuplicate( CBasePlayerItem *pOriginal ) } } +CBasePlayerWeapon::CBasePlayerWeapon() : + m_pWeaponContext(nullptr) +{ +} + +CBasePlayerWeapon::~CBasePlayerWeapon() +{ + if (m_pWeaponContext) { + delete m_pWeaponContext; + } +} int CBasePlayerWeapon::AddToPlayer( CBasePlayer *pPlayer ) { int bResult = CBasePlayerItem::AddToPlayer( pPlayer ); - pPlayer->AddWeapon( m_iId ); + pPlayer->AddWeapon( m_pWeaponContext->m_iId ); - if ( !m_iPrimaryAmmoType ) + if ( !m_pWeaponContext->m_iPrimaryAmmoType ) { - m_iPrimaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo1() ); - m_iSecondaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo2() ); + m_pWeaponContext->m_iPrimaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo1() ); + m_pWeaponContext->m_iSecondaryAmmoType = pPlayer->GetAmmoIndex( pszAmmo2() ); } - if (bResult) return AddWeapon( ); + return FALSE; } @@ -779,8 +720,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_pWeaponContext->m_iClip != m_pWeaponContext->m_iClientClip || + state != m_pWeaponContext->m_iClientWeaponState || pPlayer->m_iFOV != pPlayer->m_iClientFOV ) { bSend = TRUE; @@ -790,12 +731,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_pWeaponContext->m_iId ); + WRITE_BYTE( m_pWeaponContext->m_iClip ); MESSAGE_END(); - m_iClientClip = m_iClip; - m_iClientWeaponState = state; + m_pWeaponContext->m_iClientClip = m_pWeaponContext->m_iClip; + m_pWeaponContext->m_iClientWeaponState = state; pPlayer->m_fWeapon = TRUE; } @@ -805,20 +746,9 @@ int CBasePlayerWeapon::UpdateClientData( CBasePlayer *pPlayer ) return 1; } - -void CBasePlayerWeapon::SendWeaponAnim( int iAnim, int skiplocal, int body ) +void CBasePlayerWeapon::ItemPostFrame() { - 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(); + m_pWeaponContext->ItemPostFrame(); } BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip, int iMaxCarry ) @@ -827,14 +757,14 @@ BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip if (iMaxClip < 1) { - m_iClip = -1; + m_pWeaponContext->m_iClip = -1; iIdAmmo = m_pPlayer->GiveAmmo( iCount, szName, iMaxCarry ); } - else if (m_iClip == 0) + else if (m_pWeaponContext->m_iClip == 0) { int i; - i = Q_min( m_iClip + iCount, iMaxClip ) - m_iClip; - m_iClip += i; + i = Q_min( m_pWeaponContext->m_iClip + iCount, iMaxClip ) - m_pWeaponContext->m_iClip; + m_pWeaponContext->m_iClip += i; iIdAmmo = m_pPlayer->GiveAmmo( iCount - i, szName, iMaxCarry ); } else @@ -846,7 +776,7 @@ BOOL CBasePlayerWeapon :: AddPrimaryAmmo( int iCount, char *szName, int iMaxClip if (iIdAmmo > 0) { - m_iPrimaryAmmoType = iIdAmmo; + m_pWeaponContext->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 +799,12 @@ BOOL CBasePlayerWeapon :: AddSecondaryAmmo( int iCount, char *szName, int iMax ) if (iIdAmmo > 0) { - m_iSecondaryAmmoType = iIdAmmo; + m_pWeaponContext->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 +888,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_pWeaponContext->m_iDefaultAmmo, (char *)pszAmmo1(), iMaxClip(), iMaxAmmo1() ); + m_pWeaponContext->m_iDefaultAmmo = 0; } if ( pszAmmo2() != NULL ) @@ -1102,13 +907,13 @@ int CBasePlayerWeapon::ExtractClipAmmo( CBasePlayerWeapon *pWeapon ) { int iAmmo; - if ( m_iClip == WEAPON_NOCLIP ) + if ( m_pWeaponContext->m_iClip == WEAPON_NOCLIP ) { iAmmo = 0;// guns with no clips always come empty if they are second-hand } else { - iAmmo = m_iClip; + iAmmo = m_pWeaponContext->m_iClip; } return pWeapon->m_pPlayer->GiveAmmo( iAmmo, (char *)pszAmmo1(), iMaxAmmo1() ); // , &m_iPrimaryAmmoType @@ -1465,17 +1270,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_pWeaponContext->m_flNextPrimaryAttack ); + ALERT( at_console, "idle : %f\n", m_pWeaponContext->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_pWeaponContext->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_pWeaponContext->m_iClip ); } //========================================================= diff --git a/server/weapons.h b/server/weapons.h index 70379ad89..eba4c8221 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<PrimaryAmmoIndex(); }; // forward to weapon logic + int SecondaryAmmoIndex() override { return m_pWeaponContext->SecondaryAmmoIndex(); }; // forward to weapon logic - 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(); + virtual int GetItemInfo(ItemInfo *p) override { return m_pWeaponContext->GetItemInfo(p); }; // returns 0 if struct not filled out + virtual BOOL CanDeploy( void ) override { return m_pWeaponContext->CanDeploy(); }; + virtual BOOL Deploy() override { return m_pWeaponContext->Deploy(); }; // returns is deploy was successful + virtual BOOL CanHolster( void ) override { return m_pWeaponContext->CanHolster(); }; // can this weapon be put away right nxow? + virtual void Holster(void) override { m_pWeaponContext->Holster(); }; - void PrintState( void ); + void UpdateItemInfo( void ) override {}; // updates HUD state + CBasePlayerItem *GetWeaponPtr( void ) override { return (CBasePlayerItem *)this; }; - virtual CBasePlayerItem *GetWeaponPtr( void ) { return (CBasePlayerItem *)this; }; + int iItemSlot() override { return m_pWeaponContext->iItemSlot(); } + int iItemPosition() override { return m_pWeaponContext->iItemPosition(); } + const char *pszAmmo1() override { return m_pWeaponContext->pszAmmo1(); } + int iMaxAmmo1() override { return m_pWeaponContext->iMaxAmmo1(); } + const char *pszAmmo2() override { return m_pWeaponContext->pszAmmo2(); } + int iMaxAmmo2() override { return m_pWeaponContext->iMaxAmmo2(); } + const char *pszName() override { return m_pWeaponContext->pszName(); } + int iMaxClip() override { return m_pWeaponContext->iMaxClip(); } + int iWeight() override { return m_pWeaponContext->iWeight(); } + int iFlags() override { return m_pWeaponContext->iFlags(); } + int iWeaponID() override { return m_pWeaponContext->m_iId; } - 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; +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 m_iDefaultAmmo;// how much ammo you get when you pick up this weapon as placed by a level designer. + 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 ); + void PrintState( void ); }; class CBasePlayerAmmo : public CBaseEntity diff --git a/server/weapons/glock.cpp b/server/weapons/glock.cpp deleted file mode 100644 index bb20271fc..000000000 --- a/server/weapons/glock.cpp +++ /dev/null @@ -1,224 +0,0 @@ -/*** -* -* Copyright (c) 1996-2002, Valve LLC. All 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 "glock.h" - -enum glock_e -{ - GLOCK_IDLE1 = 0, - GLOCK_IDLE2, - GLOCK_IDLE3, - GLOCK_SHOOT, - GLOCK_SHOOT_EMPTY, - GLOCK_RELOAD, - GLOCK_RELOAD_NOT_EMPTY, - GLOCK_DRAW, - GLOCK_HOLSTER, - GLOCK_ADD_SILENCER -}; - -LINK_ENTITY_TO_CLASS( weapon_glock, CGlock ); -LINK_ENTITY_TO_CLASS( weapon_9mmhandgun, CGlock ); - -void CGlock::Spawn( ) -{ - pev->classname = MAKE_STRING( "weapon_9mmhandgun" ); // hack to allow for old names - Precache( ); - m_iId = WEAPON_GLOCK; - SET_MODEL( 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 -} - -int CGlock::GetItemInfo(ItemInfo *p) -{ - p->pszName = STRING(pev->classname); - p->pszAmmo1 = "9mm"; - p->iMaxAmmo1 = _9MM_MAX_CARRY; - p->pszAmmo2 = NULL; - p->iMaxAmmo2 = -1; - p->iMaxClip = GLOCK_MAX_CLIP; - p->iSlot = 1; - p->iPosition = 0; - p->iFlags = 0; - p->iId = m_iId = WEAPON_GLOCK; - p->iWeight = GLOCK_WEIGHT; - - return 1; -} - -BOOL CGlock::Deploy( ) -{ - // pev->body = 1; - return DefaultDeploy( "models/v_9mmhandgun.mdl", "models/p_9mmhandgun.mdl", GLOCK_DRAW, "onehanded" ); -} - -void CGlock::SecondaryAttack( void ) -{ - GlockFire( 0.1, 0.2, FALSE ); -} - -void CGlock::PrimaryAttack( void ) -{ - GlockFire( 0.01, 0.3, TRUE ); -} - -void CGlock::GlockFire( float flSpread , float flCycleTime, BOOL fUseAutoAim ) -{ - if (m_iClip <= 0) - { - if (m_fFireOnEmpty) - { - PlayEmptySound(); - m_flNextPrimaryAttack = gpGlobals->time + 0.2; - } - - return; - } - - m_iClip--; - - m_pPlayer->pev->effects = (int)(m_pPlayer->pev->effects) | EF_MUZZLEFLASH; - - if (m_iClip != 0) - SendWeaponAnim( GLOCK_SHOOT ); - else - SendWeaponAnim( GLOCK_SHOOT_EMPTY ); - - // player "shoot" animation - m_pPlayer->SetAnimation( PLAYER_ATTACK1 ); - - UTIL_MakeVectors( m_pPlayer->pev->v_angle + m_pPlayer->pev->punchangle ); - - Vector vecShellVelocity = m_pPlayer->GetAbsVelocity() - + gpGlobals->v_right * RANDOM_FLOAT(50,70) - + 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 ); - - // silenced - if (pev->body == 1) - { - m_pPlayer->m_iWeaponVolume = QUIET_GUN_VOLUME; - m_pPlayer->m_iWeaponFlash = DIM_GUN_FLASH; - - switch(RANDOM_LONG(0,1)) - { - case 0: - EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/pl_gun1.wav", RANDOM_FLOAT(0.9, 1.0), ATTN_NORM); - break; - case 1: - EMIT_SOUND(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/pl_gun2.wav", RANDOM_FLOAT(0.9, 1.0), ATTN_NORM); - break; - } - } - else - { - // non-silenced - m_pPlayer->m_iWeaponVolume = NORMAL_GUN_VOLUME; - m_pPlayer->m_iWeaponFlash = NORMAL_GUN_FLASH; - EMIT_SOUND_DYN(ENT(m_pPlayer->pev), CHAN_WEAPON, "weapons/pl_gun3.wav", RANDOM_FLOAT(0.92, 1.0), ATTN_NORM, 0, 98 + RANDOM_LONG(0,3)); - } - - Vector vecSrc = m_pPlayer->GetGunPosition( ); - Vector vecAiming; - - if ( fUseAutoAim ) - { - vecAiming = m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); - } - else - { - vecAiming = gpGlobals->v_forward; - } - - m_pPlayer->FireBullets( 1, vecSrc, vecAiming, Vector( flSpread, flSpread, flSpread ), 8192, BULLET_PLAYER_9MM, 0 ); - m_flNextPrimaryAttack = m_flNextSecondaryAttack = gpGlobals->time + flCycleTime; - - if (!m_iClip && m_pPlayer->m_rgAmmo[m_iPrimaryAmmoType] <= 0) - // HEV suit - indicate out of ammo condition - m_pPlayer->SetSuitUpdate("!HEV_AMO0", FALSE, 0); - - m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); - - m_pPlayer->pev->punchangle.x -= 2; -} - -void CGlock::Reload( void ) -{ - int iResult; - - if (m_iClip == 0) - iResult = DefaultReload( 17, GLOCK_RELOAD, 1.5 ); - else - iResult = DefaultReload( 17, GLOCK_RELOAD_NOT_EMPTY, 1.5 ); - - if (iResult) - { - m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 ); - } -} - -void CGlock::WeaponIdle( void ) -{ - ResetEmptySound( ); - - m_pPlayer->GetAutoaimVector( AUTOAIM_10DEGREES ); - - if (m_flTimeWeaponIdle > gpGlobals->time) - return; - - // only idle if the slid isn't back - if (m_iClip != 0) - { - int iAnim; - float flRand = RANDOM_FLOAT(0, 1); - if (flRand <= 0.3 + 0 * 0.75) - { - iAnim = GLOCK_IDLE3; - m_flTimeWeaponIdle = gpGlobals->time + 49.0 / 16; - } - else if (flRand <= 0.6 + 0 * 0.875) - { - iAnim = GLOCK_IDLE1; - m_flTimeWeaponIdle = gpGlobals->time + 60.0 / 16.0; - } - else - { - iAnim = GLOCK_IDLE2; - m_flTimeWeaponIdle = gpGlobals->time + 40.0 / 16.0; - } - SendWeaponAnim( iAnim ); - } -} diff --git a/server/weapons/satchel.cpp b/server/weapons/satchel.cpp index 66718542a..419917768 100644 --- a/server/weapons/satchel.cpp +++ b/server/weapons/satchel.cpp @@ -317,32 +317,3 @@ void CSatchel::WeaponIdle( void ) } m_flTimeWeaponIdle = gpGlobals->time + RANDOM_FLOAT ( 10, 15 );// how long till we do this again. } - -//========================================================= -// DeactivateSatchels - removes all satchels owned by -// the provided player. Should only be used upon death. -// -// Made this global on purpose. -//========================================================= -void DeactivateSatchels( CBasePlayer *pOwner ) -{ - edict_t *pFind; - - pFind = FIND_ENTITY_BY_CLASSNAME( NULL, "monster_satchel" ); - - while ( !FNullEnt( pFind ) ) - { - CBaseEntity *pEnt = CBaseEntity::Instance( pFind ); - CSatchelCharge *pSatchel = (CSatchelCharge *)pEnt; - - if ( pSatchel ) - { - if ( pSatchel->pev->owner == pOwner->edict() ) - { - pSatchel->Deactivate(); - } - } - - pFind = FIND_ENTITY_BY_CLASSNAME( pFind, "monster_satchel" ); - } -} diff --git a/server/weapons/weapon_glock.cpp b/server/weapons/weapon_glock.cpp new file mode 100644 index 000000000..ba7e109d7 --- /dev/null +++ b/server/weapons/weapon_glock.cpp @@ -0,0 +1,50 @@ +/*** +* +* Copyright (c) 1996-2002, Valve LLC. All 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 "weapons/glock.h" +#include "server_weapon_layer_impl.h" + +LINK_ENTITY_TO_CLASS( weapon_9mmhandgun, CGlock ); +LINK_ENTITY_TO_CLASS( weapon_glock, CGlock ); + +CGlock::CGlock() +{ + m_pWeaponContext = new CGlockWeaponLogic(new CServerWeaponLayerImpl(this)); +} + +void CGlock::Spawn( ) +{ + pev->classname = MAKE_STRING(CLASSNAME_STR(GLOCK_CLASSNAME)); // hack to allow for old names + Precache( ); + SET_MODEL( edict(), "models/w_9mmhandgun.mdl" ); + 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"); + 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..8c1dc78f2 100644 --- a/server/weapons/glock.h +++ b/server/weapons/weapon_glock.h @@ -25,18 +25,10 @@ class CGlock : public CBasePlayerWeapon { DECLARE_CLASS( CGlock, CBasePlayerWeapon ); + public: + CGlock(); + 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; };