Skip to content

Commit 05b7909

Browse files
authored
Add player beam and measure (#374)
* Add player beam * Add eyeorigin * Add measure commands * Fix submodule * Fix linux compilation * Update comments
1 parent c63d177 commit 05b7909

21 files changed

+845
-7
lines changed

AMBuilder

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ for sdk_target in MMSPlugin.sdk_targets:
113113
os.path.join(builder.sourcePath, 'src', 'kz', 'kz_player_print.cpp'),
114114

115115
os.path.join(builder.sourcePath, 'src', 'kz', 'anticheat', 'kz_anticheat.cpp'),
116+
os.path.join(builder.sourcePath, 'src', 'kz', 'beam', 'kz_beam.cpp'),
116117
os.path.join(builder.sourcePath, 'src', 'kz', 'checkpoint', 'kz_checkpoint.cpp'),
117118
os.path.join(builder.sourcePath, 'src', 'kz', 'checkpoint', 'commands.cpp'),
118119

gamedata/cs2kz-core.games.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,24 @@
280280
"windows" "\x48\x8D\x0D\x2A\x2A\x2A\x2A\x48\x0F\x45\xD0\x48\x8B\x05"
281281
"linux" "\x48\x8D\x3D\x2A\x2A\x2A\x2A\x4C\x89\xE0"
282282
}
283+
"CreateEntityByName"
284+
{
285+
"library" "server"
286+
"windows" "\x48\x83\xEC\x48\xC6\x44\x24\x30\x00"
287+
"linux" "\x48\x8D\x05\x2A\x2A\x2A\x2A\x55\x48\x89\xFA"
288+
}
289+
"DispatchSpawn"
290+
{
291+
"library" "server"
292+
"windows" "\x48\x89\x5C\x24\x10\x57\x48\x83\xEC\x30\x48\x8B\xDA\x48\x8B\xF9\x48\x85\xC9"
293+
"linux" "\x48\x85\xFF\x74\x2A\x55\x48\x89\xE5\x41\x56"
294+
}
295+
"RemoveEntity"
296+
{
297+
"library" "server"
298+
"windows" "\x48\x85\xC9\x74\x2A\x48\x8B\xD1\x48\x8B\x0D\x2A\x2A\x2A\x2A"
299+
"linux" "\x48\x89\xFE\x48\x85\xFF\x74\x2A\x48\x8D\x05\x2A\x2A\x2A\x2A\x48"
300+
}
283301
}
284302
"Offsets"
285303
{

src/kz/beam/kz_beam.cpp

Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
#include "kz_beam.h"
2+
#include "kz/spec/kz_spec.h"
3+
#include "kz/noclip/kz_noclip.h"
4+
#include "kz/language/kz_language.h"
5+
#include "kz/option/kz_option.h"
6+
7+
#include "utils/simplecmds.h"
8+
#include "utils/ctimer.h"
9+
10+
using namespace KZ::beam;
11+
12+
static_global class KZOptionServiceEventListener_Beam : public KZOptionServiceEventListener
13+
{
14+
virtual void OnPlayerPreferencesLoaded(KZPlayer *player)
15+
{
16+
player->beamService->OnPlayerPreferencesLoaded();
17+
}
18+
} optionEventListener;
19+
20+
void KZBeamService::OnPlayerPreferencesLoaded()
21+
{
22+
this->SetBeamType(this->player->optionService->GetPreferenceInt("desiredBeamType"));
23+
this->playerBeamOffset = this->player->optionService->GetPreferenceVector("beamOffset", KZBeamService::defaultOffset);
24+
}
25+
26+
SCMD(kz_beam, SCFL_MISC | SCFL_PREFERENCE)
27+
{
28+
KZPlayer *player = g_pKZPlayerManager->ToPlayer(controller);
29+
u8 newDesiredBeamType = (player->beamService->desiredBeamType + 1) % KZBeamService::BEAM_COUNT;
30+
if (args->ArgC() >= 2)
31+
{
32+
if (KZ_STREQI(args->Arg(1), "off"))
33+
{
34+
newDesiredBeamType = KZBeamService::BEAM_NONE;
35+
}
36+
else if (KZ_STREQI(args->Arg(1), "feet"))
37+
{
38+
newDesiredBeamType = KZBeamService::BEAM_FEET;
39+
}
40+
else if (KZ_STREQI(args->Arg(1), "ground"))
41+
{
42+
newDesiredBeamType = KZBeamService::BEAM_GROUND;
43+
}
44+
else
45+
{
46+
player->languageService->PrintChat(true, false, "Beam Command Usage");
47+
}
48+
}
49+
else
50+
{
51+
player->beamService->SetBeamType(newDesiredBeamType);
52+
}
53+
player->beamService->buffer.Clear();
54+
switch (player->beamService->desiredBeamType)
55+
{
56+
case KZBeamService::BEAM_NONE:
57+
{
58+
player->languageService->PrintChat(true, false, "Beam Changed (None)");
59+
break;
60+
}
61+
case KZBeamService::BEAM_FEET:
62+
{
63+
player->languageService->PrintChat(true, false, "Beam Changed (Feet)");
64+
break;
65+
}
66+
case KZBeamService::BEAM_GROUND:
67+
{
68+
player->languageService->PrintChat(true, false, "Beam Changed (Ground)");
69+
break;
70+
}
71+
}
72+
player->optionService->SetPreferenceInt("desiredBeamType", player->beamService->desiredBeamType);
73+
return MRES_HANDLED;
74+
}
75+
76+
SCMD(kz_beamoffset, SCFL_MISC | SCFL_PREFERENCE)
77+
{
78+
KZPlayer *player = g_pKZPlayerManager->ToPlayer(controller);
79+
if (args->ArgC() < 4 || !utils::IsNumeric(args->Arg(1)) || !utils::IsNumeric(args->Arg(2)) || !utils::IsNumeric(args->Arg(3)))
80+
{
81+
player->languageService->PrintChat(true, false, "Beam Offset Command Usage");
82+
player->languageService->PrintChat(true, false, "Current Beam Offset", player->beamService->playerBeamOffset.x,
83+
player->beamService->playerBeamOffset.y, player->beamService->playerBeamOffset.z);
84+
}
85+
player->beamService->playerBeamOffset = Vector(atof(args->Arg(1)), atof(args->Arg(2)), atof(args->Arg(3)));
86+
87+
player->optionService->SetPreferenceVector("beamOffset", player->beamService->playerBeamOffset);
88+
return MRES_HANDLED;
89+
}
90+
91+
static_function CBaseModelEntity *CreateGrenadeEnt(i32 team)
92+
{
93+
CBaseModelEntity *beamEnt = utils::CreateEntityByName<CBaseModelEntity>("smokegrenade_projectile");
94+
beamEnt->m_iTeamNum(team);
95+
beamEnt->DispatchSpawn();
96+
beamEnt->m_clrRender({0, 0, 0, 0});
97+
beamEnt->m_nRenderMode(10);
98+
beamEnt->m_nActualMoveType = MOVETYPE_NONE;
99+
beamEnt->m_MoveType = MOVETYPE_NONE;
100+
beamEnt->m_flGravityScale = 0.0f;
101+
102+
return beamEnt;
103+
}
104+
105+
void KZBeamService::Update()
106+
{
107+
KZPlayer *newTarget = this->player->IsAlive() ? this->player : this->player->specService->GetSpectatedPlayer();
108+
109+
if (this->target != newTarget)
110+
{
111+
this->buffer.Clear();
112+
this->target = newTarget;
113+
}
114+
115+
if (!this->target || !this->target->GetPlayerPawn())
116+
{
117+
this->playerBeam.moving = false;
118+
return;
119+
}
120+
121+
/*
122+
For the player beam, create a grenade that is X ticks behind the player's origin.
123+
124+
This is done by storing a history of player's position.
125+
126+
The grenade position is updated as long as the player is in the air,
127+
except if they enter the air via noclip, in which case the player needs to land before the beam is updated again.
128+
*/
129+
if (this->target->noclipService->JustNoclipped() && !this->noclipTick)
130+
{
131+
this->noclipTick = g_pKZUtils->GetServerGlobals()->tickcount + originHistorySize;
132+
}
133+
134+
if ((this->target->GetPlayerPawn()->m_fFlags & FL_ONGROUND && this->target->GetMoveType() == MOVETYPE_WALK)
135+
|| this->target->GetMoveType() == MOVETYPE_LADDER)
136+
{
137+
this->noclipTick = 0;
138+
this->canResumeBeam = true;
139+
}
140+
141+
bool shouldStopBeam =
142+
this->target->GetPlayerPawn()->m_fFlags & FL_ONGROUND || this->target->GetMoveType() != MOVETYPE_WALK || !this->target->IsAlive();
143+
if (shouldStopBeam && this->beamStopTick <= 0)
144+
{
145+
// Add a few trailing ticks just so the beam doesn't end abruptly right before landing.
146+
this->beamStopTick = g_pKZUtils->GetServerGlobals()->tickcount + originHistorySize + 4;
147+
}
148+
149+
if (!shouldStopBeam && this->canResumeBeam)
150+
{
151+
this->beamStopTick = 0;
152+
}
153+
154+
if ((this->beamStopTick && g_pKZUtils->GetServerGlobals()->tickcount > this->beamStopTick)
155+
|| this->noclipTick && g_pKZUtils->GetServerGlobals()->tickcount > this->noclipTick || !this->desiredBeamType)
156+
{
157+
this->playerBeam.moving = false;
158+
}
159+
else
160+
{
161+
this->UpdatePlayerBeam();
162+
}
163+
BeamOrigin origin;
164+
this->target->GetOrigin(&origin);
165+
if (this->desiredBeamType == BEAM_GROUND)
166+
{
167+
origin.z = this->target->takeoffGroundOrigin.z;
168+
}
169+
origin.forceRecreate = this->teleportedThisTick;
170+
this->buffer.Write(origin);
171+
this->teleportedThisTick = false;
172+
}
173+
174+
void KZBeamService::UpdatePlayerBeam()
175+
{
176+
if (this->buffer.GetReadAvailable() < originHistorySize)
177+
{
178+
return;
179+
}
180+
if (!this->playerBeam.handle.Get())
181+
{
182+
this->playerBeam.handle = CreateGrenadeEnt(CS_TEAM_CT);
183+
}
184+
CBaseModelEntity *ent = static_cast<CBaseModelEntity *>(playerBeam.handle.Get());
185+
BeamOrigin origin;
186+
this->buffer.Peek(&origin, originHistorySize - 1);
187+
origin += this->playerBeamOffset;
188+
189+
if (origin == playerBeam.lastOrigin)
190+
{
191+
playerBeam.moving = false;
192+
}
193+
else
194+
{
195+
if (!playerBeam.moving || origin.forceRecreate)
196+
{
197+
g_pKZUtils->RemoveEntity(playerBeam.handle.Get());
198+
playerBeam.handle = CreateGrenadeEnt(ent->m_iTeamNum())->GetRefEHandle();
199+
playerBeam.moving = true;
200+
}
201+
playerBeam.lastOrigin = origin;
202+
ent->Teleport(&origin, nullptr, &vec3_origin);
203+
}
204+
}
205+
206+
void KZBeamService::Reset()
207+
{
208+
this->playerBeam = {};
209+
this->playerBeamOffset = KZBeamService::defaultOffset;
210+
this->target = {};
211+
this->buffer.Clear();
212+
this->desiredBeamType = {};
213+
this->beamStopTick = 0;
214+
this->canResumeBeam = {};
215+
this->noclipTick = 0;
216+
this->teleportedThisTick = false;
217+
FOR_EACH_VEC(this->instantBeams, i)
218+
{
219+
KZ::beam::InstantBeam *beam = &this->instantBeams[i];
220+
if (beam->handle.Get())
221+
{
222+
g_pKZUtils->RemoveEntity(beam->handle.Get());
223+
}
224+
}
225+
this->instantBeams.RemoveAll();
226+
}
227+
228+
void KZBeamService::UpdateBeams()
229+
{
230+
static CConVarRef<float> sv_grenade_trajectory_prac_trailtime("sv_grenade_trajectory_prac_trailtime");
231+
if (sv_grenade_trajectory_prac_trailtime.Get() != 4.0f)
232+
{
233+
sv_grenade_trajectory_prac_trailtime.Set(4.0f);
234+
}
235+
for (i32 i = 0; i < MAXPLAYERS + 1; i++)
236+
{
237+
KZPlayer *player = g_pKZPlayerManager->ToPlayer(i);
238+
if (!player || !player->IsInGame())
239+
{
240+
continue;
241+
}
242+
player->beamService->Update();
243+
player->beamService->UpdateInstantBeams();
244+
}
245+
}
246+
247+
void KZBeamService::AddInstantBeam(const Vector &start, const Vector &end, u32 lifetime)
248+
{
249+
KZ::beam::InstantBeam *beam = this->instantBeams.AddToTailGetPtr();
250+
CBaseModelEntity *ent = CreateGrenadeEnt(CS_TEAM_T);
251+
beam->handle = ent->GetRefEHandle();
252+
beam->start = start;
253+
beam->end = end;
254+
beam->tickRemaining = lifetime;
255+
beam->totalTicks = lifetime;
256+
ent->Teleport(&start, nullptr, &vec3_origin);
257+
}
258+
259+
void KZBeamService::UpdateInstantBeams()
260+
{
261+
FOR_EACH_VEC(this->instantBeams, i)
262+
{
263+
KZ::beam::InstantBeam *beam = &this->instantBeams[i];
264+
if (!beam->handle.Get())
265+
{
266+
this->instantBeams.Remove(i);
267+
i--;
268+
continue;
269+
}
270+
271+
if (beam->tickLingered > beam->maxLingerTicks)
272+
{
273+
g_pKZUtils->RemoveEntity(beam->handle.Get());
274+
this->instantBeams.Remove(i);
275+
i--;
276+
continue;
277+
}
278+
else if (beam->tickRemaining >= 0)
279+
{
280+
Vector current = Lerp((f32)beam->tickRemaining / (f32)beam->totalTicks, beam->end, beam->start);
281+
static_cast<CBaseEntity *>(beam->handle.Get())->Teleport(&current, nullptr, &vec3_origin);
282+
beam->tickRemaining--;
283+
}
284+
else
285+
{
286+
beam->tickLingered++;
287+
}
288+
}
289+
}

0 commit comments

Comments
 (0)