Skip to content

Commit 9195bec

Browse files
authored
[visualise_impacts] Improved code for sending temporary entities. (#914)
* [visualise_impacts] Improved code for sending temporary entities. 1) Change "BSP Decal" to "World Decal" allows you to use command `r_cleardecal` for clearing. 2) Now when sending temporary entities, cvar `sv_multiplayer_maxtempentities` is taken into account; previously, the code ignored this and created a bunch of timers. 3) Attempting to add code to automatically clean up temporary entities. * Remove non-working code:(
1 parent d658f09 commit 9195bec

File tree

1 file changed

+186
-54
lines changed

1 file changed

+186
-54
lines changed
Lines changed: 186 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,233 @@
1-
/* @A1m`:
2-
* We cannot send to the client temporary objects larger than specified in cvar 'sv_multiplayer_maxtempentities'.
3-
* A large number of decals will not be displayed if you do not set a delay in sending,
4-
* or we need to increase the cvar 'sv_multiplayer_maxtempentities' value, by default it is 32 (we can set 255).
1+
/** @A1m`:
2+
* The engine does not allow sending temporary entities larger than the value set in the cvar 'sv_multiplayer_maxtempentities'.
3+
* If too many decals are sent in a single tick, some will not be displayed unless we add a delay,
4+
* or increase the cvar value (default is 32, can be raised up to 255).
55
*
6-
* TE_SendToClient with the set delay does not fix this issue.
7-
* Now the plugin shows all impacts correctly.
8-
* The plugin also correctly resets this delay with some time, so we don't get high delay.
9-
* Fix plugin not working after loading the map, it was necessary to constantly reload it.
10-
*/
6+
* Note: Using TE_SendToClient with a delay alone does not fix this issue.
7+
*
8+
* This plugin solves the problem by properly queuing decals, so all bullet impacts are displayed.
9+
* The delay is cleared automatically after a short period, so it won’t accumulate.
10+
* Additionally, the plugin fixes an issue where it would stop working after map load (previously it required a manual reload)
11+
* (Add PrecacheDecal in `OnMapStart`).
12+
*
13+
* The plugin now supports automatic decal removal after the configured time period.
14+
*
15+
* Original code & Notes (Author Jahze): https://github.com/Jahze/l4d2_plugins/tree/master/spread_patch
16+
*
17+
* Note: For some reason, calling function `CBaseEntity::RemoveAllDecals` for the client doesn't work to clear decals.
18+
* Note: Use command `r_removedecals` for client to clean old decals.
19+
*
20+
**/
1121

1222
#pragma semicolon 1
1323
#pragma newdecls required
1424

1525
#include <sourcemod>
1626
#include <sdktools>
1727

18-
#define DECAL_NAME "materials/decals/metal/metal01b.vtf"
28+
#define DECAL_NAME "materials/decals/metal/metal01b.vtf"
1929

2030
int
21-
decalThisTick = 0,
22-
iLastTick = 0,
2331
g_iPrecacheDecal = 0;
24-
25-
public Plugin myinfo =
32+
33+
float
34+
g_hRemoveDecalsTime = 0.0;
35+
36+
ConVar
37+
g_hCvarMultiplayerMaxTempEnts = null,
38+
g_hCvarRemoveDecalsTime = null;
39+
40+
ArrayList
41+
g_hDecalQueue = null;
42+
43+
public Plugin myinfo =
2644
{
2745
name = "Visualise impacts",
28-
author = "Jahze?, A1m`",
29-
version = "1.3",
30-
description = "See name",
46+
author = "A1m`",
47+
version = "1.7",
48+
description = "Shows bullet impacts (based on the original by Jahze, fully rewritten and improved)",
3149
url = "https://github.com/SirPlease/L4D2-Competitive-Rework"
3250
};
3351

3452
public void OnPluginStart()
3553
{
54+
g_hCvarRemoveDecalsTime = CreateConVar("l4d_remove_decals_time", "20.0", "After what time will the decals be removed? (0 for disable)", _, true, 0.0, true, 320.0);
55+
56+
InitPlugin();
57+
}
58+
59+
void InitPlugin()
60+
{
61+
g_hCvarMultiplayerMaxTempEnts = FindConVar("sv_multiplayer_maxtempentities");
62+
63+
g_hDecalQueue = new ArrayList();
64+
3665
g_iPrecacheDecal = PrecacheDecal(DECAL_NAME, true);
37-
38-
HookEvent("bullet_impact", BulletImpactEvent, EventHookMode_Post);
39-
HookEvent("round_start", EventRoundReset, EventHookMode_PostNoCopy);
40-
HookEvent("round_end", EventRoundReset, EventHookMode_PostNoCopy);
66+
67+
HookEvent("bullet_impact", Event_BulletImpact, EventHookMode_Post);
68+
69+
HookEvent("round_start", Event_RoundChangeState, EventHookMode_PostNoCopy);
70+
HookEvent("round_end", Event_RoundChangeState, EventHookMode_PostNoCopy);
71+
}
72+
73+
public void OnPluginEnd()
74+
{
75+
ClearAllData();
4176
}
4277

4378
public void OnMapStart()
4479
{
80+
ClearAllData();
81+
4582
if (!IsDecalPrecached(DECAL_NAME)) {
4683
g_iPrecacheDecal = PrecacheDecal(DECAL_NAME, true); //true or false?
4784
}
4885
}
4986

50-
void EventRoundReset(Event hEvent, const char[] name, bool dontBroadcast)
87+
public void OnMapEnd()
5188
{
52-
decalThisTick = 0;
53-
iLastTick = 0;
89+
ClearAllData();
5490
}
5591

56-
void BulletImpactEvent(Event hEvent, const char[] name, bool dontBroadcast)
92+
public void OnGameFrame()
5793
{
58-
float pos[3];
59-
int userid = hEvent.GetInt("userid");
60-
//int client = GetClientOfUserId(userid);
61-
62-
pos[0] = hEvent.GetFloat("x");
63-
pos[1] = hEvent.GetFloat("y");
64-
pos[2] = hEvent.GetFloat("z");
94+
/** @A1m`:
95+
* We only use half the possible value for reliability if any other decals were sent.
96+
* We use a function `OnGameFrame` instead of creating a bunch of timers,
97+
* and no longer ignore cvar `sv_multiplayer_maxtempentities`.
98+
**/
6599

66-
int iTick = GetGameTickCount();
100+
SendSendQueueDecals();
101+
ShouldRemoveAllDecals();
102+
}
67103

68-
if (iTick != iLastTick) {
69-
decalThisTick = 0;
70-
iLastTick = iTick;
104+
void ShouldRemoveAllDecals()
105+
{
106+
if (g_hRemoveDecalsTime <= 0.5 || GetGameTime() < g_hRemoveDecalsTime) {
107+
return;
71108
}
72109

73-
ArrayStack hStack = new ArrayStack(sizeof(pos));
74-
hStack.PushArray(pos[0], sizeof(pos));
75-
hStack.Push(userid);
76-
77-
CreateTimer(++decalThisTick * GetTickInterval(), TimerDelayShowDecal, hStack, TIMER_FLAG_NO_MAPCHANGE | TIMER_HNDL_CLOSE);
110+
RemoveAllDecalsForAll();
111+
g_hRemoveDecalsTime = 0.0;
78112
}
79113

80-
Action TimerDelayShowDecal(Handle hTimer, ArrayStack hStack)
114+
void SendSendQueueDecals()
81115
{
82-
if (!hStack.Empty) {
83-
int client = GetClientOfUserId(hStack.Pop());
84-
if (client > 0) {
85-
float pos[3];
86-
hStack.PopArray(pos[0], sizeof(pos));
87-
SendDecal(client, pos);
116+
if (g_hDecalQueue.Length <= 0) {
117+
return;
118+
}
119+
120+
int iMaxPerTick = 32 / 2; // 32 - default value
121+
122+
if (g_hCvarMultiplayerMaxTempEnts != null) {
123+
int iCvarValue = g_hCvarMultiplayerMaxTempEnts.IntValue;
124+
125+
// Disabled?
126+
// We protect against division by zero and guarantee that at least one decal will be send.
127+
if (iCvarValue < 1) {
128+
return;
88129
}
130+
131+
if (iCvarValue < 2) {
132+
iCvarValue = 2;
133+
}
134+
135+
iMaxPerTick = iCvarValue / 2;
89136
}
90137

91-
return Plugin_Stop;
138+
int iProcessed = 0;
139+
140+
while (g_hDecalQueue.Length > 0 && iProcessed < iMaxPerTick) {
141+
DataPack hDp = g_hDecalQueue.Get(0);
142+
143+
if (hDp != null) {
144+
hDp.Reset();
145+
146+
int iClient = GetClientOfUserId(hDp.ReadCell());
147+
if (iClient > 0) {
148+
float fPos[3];
149+
hDp.ReadFloatArray(fPos, sizeof(fPos));
150+
151+
SendDecal(iClient, fPos);
152+
}
153+
}
154+
155+
CloseHandle(hDp);
156+
g_hDecalQueue.Erase(0);
157+
iProcessed++;
158+
}
159+
}
160+
161+
void Event_RoundChangeState(Event hEvent, const char[] sEventName, bool bDontBroadcast)
162+
{
163+
ClearAllData();
92164
}
93165

94-
void SendDecal(int client, float pos[3])
166+
void Event_BulletImpact(Event hEvent, const char[] sEventName, bool bDontBroadcast)
95167
{
96-
TE_Start("BSP Decal");
97-
TE_WriteVector("m_vecOrigin", pos);
98-
TE_WriteNum("m_nEntity", 0);
168+
int iUserId = hEvent.GetInt("userid");
169+
170+
float fPos[3];
171+
fPos[0] = hEvent.GetFloat("x");
172+
fPos[1] = hEvent.GetFloat("y");
173+
fPos[2] = hEvent.GetFloat("z");
174+
175+
DataPack hDp = new DataPack();
176+
hDp.WriteCell(iUserId);
177+
hDp.WriteFloatArray(fPos, sizeof(fPos), false);
178+
179+
g_hDecalQueue.Push(hDp);
180+
181+
g_hRemoveDecalsTime = GetGameTime() + g_hCvarRemoveDecalsTime.FloatValue;
182+
}
183+
184+
void SendDecal(int iClient, float fPos[3])
185+
{
186+
/** @A1m`:
187+
* "World Decal" instead of "BSP Decal" allows you to use command `r_cleardecal` for clearing.
188+
* Command `r_cleardecal` cannot be executed by the server only by the client. =(
189+
* But it seems like it's impossible to clean "BSP Decal" at all.
190+
**/
191+
192+
TE_Start("World Decal");
193+
194+
TE_WriteVector("m_vecOrigin", fPos);
99195
TE_WriteNum("m_nIndex", g_iPrecacheDecal);
100-
TE_SendToClient(client, 0.0);
196+
197+
TE_SendToClient(iClient, 0.0);
198+
199+
g_hRemoveDecalsTime = GetGameTime() + g_hCvarRemoveDecalsTime.FloatValue;
200+
}
201+
202+
void RemoveAllDecalsForAll()
203+
{
204+
for (int iIter = 1; iIter <= MaxClients; iIter++) {
205+
if (!IsClientInGame(iIter) || IsFakeClient(iIter)) {
206+
continue;
207+
}
208+
209+
RemoveAllDecals(iIter);
210+
}
211+
}
212+
213+
void RemoveAllDecals(int iClient)
214+
{
215+
PrintToChat(iClient, "[Note] Use command `r_removedecals` for client to clean old decals.");
216+
}
217+
218+
void ClearAllData()
219+
{
220+
g_hRemoveDecalsTime = 0.0;
221+
222+
for (int iIter = 0; iIter < g_hDecalQueue.Length; iIter++) {
223+
DataPack hDp = g_hDecalQueue.Get(0);
224+
225+
if (hDp != null) {
226+
CloseHandle(hDp);
227+
}
228+
229+
g_hDecalQueue.Erase(0);
230+
}
231+
232+
g_hDecalQueue.Clear();
101233
}

0 commit comments

Comments
 (0)