|
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). |
5 | 5 | * |
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 | +**/ |
11 | 21 |
|
12 | 22 | #pragma semicolon 1 |
13 | 23 | #pragma newdecls required |
14 | 24 |
|
15 | 25 | #include <sourcemod> |
16 | 26 | #include <sdktools> |
17 | 27 |
|
18 | | -#define DECAL_NAME "materials/decals/metal/metal01b.vtf" |
| 28 | +#define DECAL_NAME "materials/decals/metal/metal01b.vtf" |
19 | 29 |
|
20 | 30 | int |
21 | | - decalThisTick = 0, |
22 | | - iLastTick = 0, |
23 | 31 | 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 = |
26 | 44 | { |
27 | 45 | 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)", |
31 | 49 | url = "https://github.com/SirPlease/L4D2-Competitive-Rework" |
32 | 50 | }; |
33 | 51 |
|
34 | 52 | public void OnPluginStart() |
35 | 53 | { |
| 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 | + |
36 | 65 | 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(); |
41 | 76 | } |
42 | 77 |
|
43 | 78 | public void OnMapStart() |
44 | 79 | { |
| 80 | + ClearAllData(); |
| 81 | + |
45 | 82 | if (!IsDecalPrecached(DECAL_NAME)) { |
46 | 83 | g_iPrecacheDecal = PrecacheDecal(DECAL_NAME, true); //true or false? |
47 | 84 | } |
48 | 85 | } |
49 | 86 |
|
50 | | -void EventRoundReset(Event hEvent, const char[] name, bool dontBroadcast) |
| 87 | +public void OnMapEnd() |
51 | 88 | { |
52 | | - decalThisTick = 0; |
53 | | - iLastTick = 0; |
| 89 | + ClearAllData(); |
54 | 90 | } |
55 | 91 |
|
56 | | -void BulletImpactEvent(Event hEvent, const char[] name, bool dontBroadcast) |
| 92 | +public void OnGameFrame() |
57 | 93 | { |
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 | + **/ |
65 | 99 |
|
66 | | - int iTick = GetGameTickCount(); |
| 100 | + SendSendQueueDecals(); |
| 101 | + ShouldRemoveAllDecals(); |
| 102 | +} |
67 | 103 |
|
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; |
71 | 108 | } |
72 | 109 |
|
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; |
78 | 112 | } |
79 | 113 |
|
80 | | -Action TimerDelayShowDecal(Handle hTimer, ArrayStack hStack) |
| 114 | +void SendSendQueueDecals() |
81 | 115 | { |
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; |
88 | 129 | } |
| 130 | + |
| 131 | + if (iCvarValue < 2) { |
| 132 | + iCvarValue = 2; |
| 133 | + } |
| 134 | + |
| 135 | + iMaxPerTick = iCvarValue / 2; |
89 | 136 | } |
90 | 137 |
|
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(); |
92 | 164 | } |
93 | 165 |
|
94 | | -void SendDecal(int client, float pos[3]) |
| 166 | +void Event_BulletImpact(Event hEvent, const char[] sEventName, bool bDontBroadcast) |
95 | 167 | { |
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); |
99 | 195 | 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(); |
101 | 233 | } |
0 commit comments