Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial implementation of weapons predicting #217

Draft
wants to merge 18 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
a352efb
server: fixed header hell in several files
SNMetamorph Sep 7, 2024
f0430a7
server: temporary removed cycler_weapon entity
SNMetamorph Nov 13, 2024
5620c14
game_shared: implemented seeded RNG class
SNMetamorph Dec 29, 2024
35ba355
server: function DeactivateSatchels moved to combat.cpp
SNMetamorph Dec 29, 2024
47453ae
client: initially implemented weapon events
SNMetamorph Jan 5, 2025
8c7b5d9
game_shared: major refactoring of weapon gamelogic code, now it is sh…
SNMetamorph Jan 7, 2025
1167497
server: major overhauling of weapon gamelogic to make it shared acros…
SNMetamorph Jan 7, 2025
1aea2dd
client: enabled compiling weapons shared code
SNMetamorph Jan 7, 2025
2ced21a
common: updated local_state_t definition to match it with engine
SNMetamorph Jan 18, 2025
bac5a05
game_shared: changed weapon timers logic to support both weapons with…
SNMetamorph Jan 18, 2025
d45ba49
server: fixup server-side weapon layer implementation
SNMetamorph Jan 18, 2025
591b90a
server: initially implemented server-side logic for weapon predicting
SNMetamorph Jan 19, 2025
1fb886a
game_shared: expanded weapon layer interface, implemented some of cli…
SNMetamorph Feb 9, 2025
3541de6
server: enabled sending current weapon ammo info within clientdata
SNMetamorph Feb 9, 2025
4e6e37c
server: added few things to weapon layer implementation
SNMetamorph Feb 9, 2025
9be46a6
client: initially implemented client-side weapon predicting backend
SNMetamorph Feb 9, 2025
6c4289f
client: fixed weapon animation desync glitches
SNMetamorph Feb 9, 2025
59595d2
game_shared: glock: removed redundant runfuncs checks
SNMetamorph Feb 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions client/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand All @@ -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
Expand All @@ -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
Expand Down
16 changes: 14 additions & 2 deletions client/cdll_int.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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;

/*
Expand Down Expand Up @@ -149,6 +152,8 @@ int Initialize( cl_enginefunc_t *pEnginefuncs, int iVersion )
BuildInfo::GetArchitecture(),
BuildInfo::GetPlatform()
);

g_pEventManager = new CGameEventManager();
return 1;
}

Expand Down Expand Up @@ -193,6 +198,10 @@ void DLLEXPORT HUD_Shutdown( void )

if (g_fRenderInitialized)
GL_Shutdown();

if (g_pEventManager) {
delete g_pEventManager;
}
}

/*
Expand Down Expand Up @@ -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);
}
}

/*
Expand Down
160 changes: 160 additions & 0 deletions client/client_weapon_layer_impl.cpp
Original file line number Diff line number Diff line change
@@ -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 &params)
{
gEngfuncs.pfnPlaybackEvent(static_cast<int>(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;
}
55 changes: 55 additions & 0 deletions client/client_weapon_layer_impl.h
Original file line number Diff line number Diff line change
@@ -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 &params) override;
bool ShouldRunFuncs() override;

private:
CSeededRandomGenerator m_randomGenerator;
CWeaponPredictingContext::PlayerState &m_playerState;
};
2 changes: 1 addition & 1 deletion client/entity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 ) );
}

/*
Expand Down
35 changes: 35 additions & 0 deletions client/events/base_game_event.cpp
Original file line number Diff line number Diff line change
@@ -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());
}
36 changes: 36 additions & 0 deletions client/events/base_game_event.h
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>

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;
};
Loading