-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathpylon.lua
More file actions
262 lines (218 loc) · 8.95 KB
/
pylon.lua
File metadata and controls
262 lines (218 loc) · 8.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
-- Configuration variables
local updateInterval = 0.5 -- Update interval in seconds (0 = every frame)
local pylonWidth = 6 -- Width of pylon in pixels
local pylonHeight = 350 -- Height in hammer units
local pylonOffset = 35 -- Offset above player head in hammer units
local pylonColor = {r = 255, g = 0, b = 0} -- Default red color
local pylonStartAlpha = 200 -- Starting alpha at bottom
local pylonEndAlpha = 25 -- Ending alpha at top
local segments = 10 -- Number of segments for fade effect
local minDistance = 800 -- Minimum distance in hammer units to draw pylon
local visibilityPersistence = 0.2 -- How long to keep using previous visibility results (seconds)
-- Store medic positions with timestamps
local medicPositions = {}
local lastCleanup = 0
local cleanupInterval = 1.0 -- Cleanup stale entries every second
local minDistanceSqr = minDistance * minDistance -- Pre-calculate squared min distance
-- Visibility cache to prevent flashing
local visibilityCache = {}
local lastVisibilityCheckTime = {}
-- Reuse vectors to prevent allocation during rendering
local reuseWorldPos = Vector3(0, 0, 0)
local teamCache = {}
local classCache = {}
local validCache = {}
local aliveCache = {}
local posCache = {}
local heightCache = {}
-- Cache frequently used functions and values
local Vector3 = Vector3
local TraceLine = engine.TraceLine
local WorldToScreen = client.WorldToScreen
local TF2_Medic = 5
local MASK_VISIBLE = MASK_VISIBLE
local floor = math.floor
local RealTime = globals.RealTime
local FrameCount = globals.FrameCount
local Color = draw.Color
local Line = draw.Line
local IsGameUIVisible = engine.IsGameUIVisible
local Con_IsVisible = engine.Con_IsVisible
-- Pre-calculate segment data
local segmentHeight = pylonHeight / segments
local alphaSteps = {}
for i = 0, segments do
local progress = i / segments
alphaSteps[i] = floor(pylonStartAlpha - (progress * (pylonStartAlpha - pylonEndAlpha)))
end
-- Check if we can directly see the position with caching for stability
local function isVisibleCached(fromPos, targetPos, identifier)
local currentTime = RealTime()
-- Check if we need to update the cache
if not lastVisibilityCheckTime[identifier] or
(currentTime - lastVisibilityCheckTime[identifier] > visibilityPersistence) then
-- Update the cache
local trace = TraceLine(fromPos, targetPos, MASK_VISIBLE)
visibilityCache[identifier] = trace.fraction >= 0.99
lastVisibilityCheckTime[identifier] = currentTime
end
-- Return cached value
return visibilityCache[identifier]
end
-- Get distance between two points without vector allocation
local function getDistanceSqr(pos1, pos2)
local dx = pos2.x - pos1.x
local dy = pos2.y - pos1.y
local dz = pos2.z - pos1.z
return dx * dx + dy * dy + dz * dz
end
local function shouldUpdatePosition(medicIndex)
if updateInterval == 0 then return true end
local lastUpdate = medicPositions[medicIndex] and medicPositions[medicIndex].lastUpdate or 0
return (RealTime() - lastUpdate) >= updateInterval
end
local function cleanupStaleEntries()
local currentTime = RealTime()
-- Only clean up periodically
if currentTime - lastCleanup < cleanupInterval then
return
end
lastCleanup = currentTime
-- Remove stale entries
for medicIdx, data in pairs(medicPositions) do
if currentTime - data.lastUpdate > updateInterval * 3 then
medicPositions[medicIdx] = nil
-- Clean up associated visibility cache
for k in pairs(visibilityCache) do
if string.match(k, "^" .. medicIdx .. "_") then
visibilityCache[k] = nil
lastVisibilityCheckTime[k] = nil
end
end
end
end
end
-- Update entity caches less frequently
local function updateEntityCaches()
local frameCount = FrameCount()
if frameCount % 10 == 0 then -- Update every 10 frames
local players = entities.FindByClass("CTFPlayer")
for _, player in pairs(players) do
local index = player:GetIndex()
if player:IsValid() then
validCache[index] = true
teamCache[index] = player:GetTeamNumber()
classCache[index] = player:GetPropInt("m_iClass")
aliveCache[index] = player:IsAlive()
posCache[index] = player:GetAbsOrigin()
heightCache[index] = player:GetMaxs().z
else
validCache[index] = false
end
end
end
end
callbacks.Register("Draw", function()
-- Skip if game UI is visible
if IsGameUIVisible() or Con_IsVisible() then return end
local localPlayer = entities.GetLocalPlayer()
if not localPlayer then return end
-- Cache these values outside the loop
local localPos = localPlayer:GetAbsOrigin()
local eyePos = localPos + localPlayer:GetPropVector("localdata", "m_vecViewOffset[0]")
local localTeam = localPlayer:GetTeamNumber()
-- Update entity caches
updateEntityCaches()
-- Clean up stale entries
cleanupStaleEntries()
-- Process all players (using cached data when possible)
local players = entities.FindByClass("CTFPlayer")
for _, player in pairs(players) do
local index = player:GetIndex()
-- Use cached values when possible
if not validCache[index] or (teamCache[index] == localTeam) or
(classCache[index] ~= TF2_Medic) or not aliveCache[index] then
goto continue
end
local playerPos = posCache[index] or player:GetAbsOrigin()
-- Skip if we can see the medic directly (with caching)
if isVisibleCached(eyePos, playerPos, index .. "_base") then
goto continue
end
-- Skip if we're too close to the medic (using squared distance for performance)
local distanceSqr = getDistanceSqr(localPos, playerPos)
if distanceSqr < minDistanceSqr then
goto continue
end
-- Update position with Z offset
local currentPos = Vector3(playerPos.x, playerPos.y, playerPos.z + (heightCache[index] or 0) + pylonOffset)
if shouldUpdatePosition(index) then
-- Store a deep copy to avoid reference issues
if not medicPositions[index] then
medicPositions[index] = {}
end
medicPositions[index].position = Vector3(currentPos.x, currentPos.y, currentPos.z)
medicPositions[index].lastUpdate = RealTime()
end
-- If we don't have a position yet, create one
if not medicPositions[index] or not medicPositions[index].position then
medicPositions[index] = {
position = Vector3(currentPos.x, currentPos.y, currentPos.z),
lastUpdate = RealTime()
}
end
local basePosition = medicPositions[index].position
local lastScreenPos = nil
local anySegmentVisible = false
-- First pass: verify that at least one segment is visible
for i = 0, segments do
reuseWorldPos.x = basePosition.x
reuseWorldPos.y = basePosition.y
reuseWorldPos.z = basePosition.z + (i * segmentHeight)
local segmentKey = index .. "_segment_" .. i
if isVisibleCached(eyePos, reuseWorldPos, segmentKey) then
anySegmentVisible = true
break
end
end
-- Skip drawing if no segments are visible
if not anySegmentVisible then
goto continue
end
-- Second pass: draw visible segments
for i = 0, segments do
reuseWorldPos.x = basePosition.x
reuseWorldPos.y = basePosition.y
reuseWorldPos.z = basePosition.z + (i * segmentHeight)
local segmentKey = index .. "_segment_" .. i
local visible = isVisibleCached(eyePos, reuseWorldPos, segmentKey)
if not visible then
lastScreenPos = nil
goto continueSegment
end
local screenPos = WorldToScreen(reuseWorldPos)
if screenPos and lastScreenPos then
Color(pylonColor.r, pylonColor.g, pylonColor.b, alphaSteps[i])
for w = 0, pylonWidth - 1 do
Line(lastScreenPos[1] + w, lastScreenPos[2], screenPos[1] + w, screenPos[2])
end
end
lastScreenPos = screenPos
::continueSegment::
end
::continue::
end
end)
-- On script unload, clean up memory
callbacks.Register("Unload", function()
medicPositions = nil
teamCache = nil
classCache = nil
validCache = nil
aliveCache = nil
posCache = nil
heightCache = nil
alphaSteps = nil
visibilityCache = nil
lastVisibilityCheckTime = nil
end)