VoltMod
C++23 framework for CS2 server plugins
Loading...
Searching...
No Matches
PlayerCommands.cpp
Go to the documentation of this file.
2
8#include <algorithm>
9#include <cs_usercmd.pb.h>
10#include <entityhandle.h>
11
12namespace VoltMod
13{
14
15/** The slot of the player whose pawn owns @p movementServices, or -1. */
16static int OwnerSlot(void* movementServices)
17{
19 if (!pawn)
20 {
21 return -1;
22 }
23 // Controllers sit at entity index slot + 1.
25 const int slot = CEntityHandle(controller.Handle).GetEntryIndex() - 1;
26 return IsValidSlot(slot) ? slot : -1;
27}
28
30 : _registry(registry), _bindings(bindings)
31{
32 if (Status installed = Install(); !installed)
33 {
34 Log::Warn("Player commands: {}; Movement is off.", installed.error().Detail);
35 return;
36 }
37 if (!_bindings.UserCmdNumber)
38 {
39 Log::Warn("Player commands: no 'CUserCmdBase::cmdNum' offset; command numbers read 0.");
40 }
41}
42
43Status PlayerCommands::Install()
44{
45 if (!_bindings.RunCommand || !_bindings.UserCmdProto)
46 {
47 return std::unexpected(Error::Unsupported("the RunCommand slot or the usercmd offset did not bind"));
48 }
49
50 const auto beforeCommand = [this](EngineMovementServices& services, void* userCmd) { Before(&services, userCmd); };
51 const auto afterCommand = [this](EngineMovementServices&, void*) { After(); };
52 auto hook = HookVirtual("Player commands", _bindings.RunCommand, beforeCommand, afterCommand);
53 if (!hook)
54 {
55 return std::unexpected(hook.error());
56 }
57 _hook = std::move(*hook);
58 return {};
59}
60
61void PlayerCommands::Before(void* movementServices, const void* userCmd)
62{
63 _slot = _registry.HasCommandListeners() ? OwnerSlot(movementServices) : -1;
64 if (_slot < 0)
65 {
66 return;
67 }
68 Decode(userCmd);
69 _registry.RaisePlayerCommand(_slot, _input);
70}
71
72void PlayerCommands::After()
73{
74 if (_slot >= 0)
75 {
76 _registry.RaisePlayerCommandDone(_slot, _input);
77 }
78}
79
80void PlayerCommands::Decode(const void* userCmd)
81{
82 _input = {};
83 if (!userCmd)
84 {
85 return;
86 }
87
88 const auto* pb = static_cast<const CSGOUserCmdPB*>(_bindings.UserCmdProto.Ptr(userCmd));
89 const auto& base = pb->base();
90
91 _input.Valid = true;
92 _input.ClientTick = base.client_tick();
93 // Live clients store the command number in the wrapper, not the protobuf payload.
94 _input.CommandNumber =
95 _bindings.UserCmdNumber ? _bindings.UserCmdNumber.Read(userCmd) : base.legacy_command_number();
96 _input.HasViewAngles = base.has_viewangles();
97 if (_input.HasViewAngles)
98 {
99 _input.ViewPitch = base.viewangles().x();
100 _input.ViewYaw = base.viewangles().y();
101 _input.ViewRoll = base.viewangles().z();
102 }
103 _input.ForwardMove = base.forwardmove();
104 _input.LeftMove = base.leftmove();
105 if (base.has_buttons_pb())
106 {
107 _input.ButtonsHeld = base.buttons_pb().buttonstate1();
108 _input.ButtonsChanged = base.buttons_pb().buttonstate2();
109 }
110 _input.MouseDx = base.mousedx();
111 _input.MouseDy = base.mousedy();
112 _input.Attack1StartHistoryIndex = pb->attack1_start_history_index();
113 _input.Attack2StartHistoryIndex = pb->attack2_start_history_index();
114
115 _input.SubtickMoveCount = std::min(base.subtick_moves_size(), PlayerInput::MaxSubtickMoves);
116 for (int i = 0; i < _input.SubtickMoveCount; ++i)
117 {
118 const auto& move = base.subtick_moves(i);
119 _input.SubtickMoves[i] = {
120 .Button = move.button(),
121 .Pressed = move.pressed(),
122 .When = move.when(),
123 .PitchDelta = move.pitch_delta(),
124 .YawDelta = move.yaw_delta(),
125 };
126 }
127
128 _input.InputHistorySent = pb->input_history_size();
130 for (int i = 0; i < _input.InputHistoryCount; ++i)
131 {
132 const auto& entry = pb->input_history(i);
133 auto& sample = _input.InputHistorySamples[i];
134 sample.TargetEntIndex = entry.target_ent_index();
135 if (entry.has_view_angles())
136 {
137 sample.HasViewAngles = true;
138 sample.ViewPitch = entry.view_angles().x();
139 sample.ViewYaw = entry.view_angles().y();
140 }
141 }
142}
143
144} // namespace VoltMod
const void * Ptr(const void *base) const
T Read(const void *base) const
PlayerCommands(const Bindings &bindings, PluginRegistry &registry)
The loaded plugins and the engine events raised on them, one per process.
void RaisePlayerCommand(int slot, const PlayerInput &input)
void RaisePlayerCommandDone(int slot, const PlayerInput &input)
void Warn(std::format_string< Args... > fmt, Args &&... args)
Definition Log.hpp:88
Result< Subscription > HookVirtual(std::string_view name, const VirtualFn< Ret(Object *, Args...)> &function, Before &&before, After &&after=nullptr)
Hook a gamedata-bound virtual function on objects sharing its class vtable.
Definition Hook.hpp:168
static std::string ReadFile(const std::filesystem::path &path)
Definition Loader.cpp:56
constexpr bool IsValidSlot(int slot)
Definition Slot.hpp:13
std::expected< void, Error > Status
Definition Result.hpp:67
static int OwnerSlot(void *movementServices)
Offset< int32_t > UserCmdNumber
Definition Bindings.hpp:107
Offset< void > UserCmdProto
Definition Bindings.hpp:105
VirtualFn< void *(EngineMovementServices *, void *)> RunCommand
Definition Bindings.hpp:84
static Error Unsupported(std::string detail)
Definition Result.hpp:57
static constexpr int MaxInputHistory
int32_t Attack1StartHistoryIndex
static constexpr int MaxSubtickMoves
std::array< SubtickMove, MaxSubtickMoves > SubtickMoves
std::array< InputHistorySample, MaxInputHistory > InputHistorySamples
int32_t Attack2StartHistoryIndex