-
Notifications
You must be signed in to change notification settings - Fork 1
/
MovementRecorder.lua
433 lines (364 loc) · 16.4 KB
/
MovementRecorder.lua
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
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
-- Movement Recorder V5 by Scape#4313
-- Mostly writted from scratch i stole the base 64 functions from stackoverflow tho https://stackoverflow.com/questions/34618946/lua-base64-encode
-- Dm me with questions but please try to figure them out yourself first
-- Important globals that run the script smoothly
local stringBase64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -- Dont touch this youll corrupt your reorded files and not be able to load other ones
local stringFolder = "MovementRecs" -- You can change this folder name to your liking
local stringPattern = stringFolder.."/.+%.dat" -- NEVER CHANGE THIS you could accidentally delete configs or scripts
local isRecording = false
local isPlayback = false
local isPaused = false
local inReturn = false
local cache = nil
-- gui vars
local guiTab = gui.Tab(gui.Reference("Misc"), "moverec", "Movement Recorder")
local guiRef = gui.Reference("Misc", "Movement Recorder")
local groupboxSettings = gui.Groupbox(guiRef, "Recording and Playback Settings", 16, 16, 296, 0)
-- Big multi setup right here
local drawingSettingsMulti = gui.Multibox(groupboxSettings, "Path Drawing")
local multiDrawEnds = gui.Checkbox(drawingSettingsMulti, "settings.drawends", "loaded Start and Stop Points", true)
local colorEnd = gui.ColorPicker(multiDrawEnds, "color.end", "", 255, 0, 0, 255)
local colorStart= gui.ColorPicker(multiDrawEnds, "color.Start", "", 0, 255, 0, 255)
local multiDrawOnPlay = gui.Checkbox(drawingSettingsMulti, "settings.drawpathPlay", "As Recording Plays", true)
local colorPlay = gui.ColorPicker(multiDrawOnPlay, "color.play", "", 255, 255, 0, 255)
local multiDrawOnRec = gui.Checkbox(drawingSettingsMulti, "settings.drawpathlive", "As Recording Saves", true)
local colorRec = gui.ColorPicker(multiDrawOnRec, "color.rec", "", 255, 0, 0, 255)
local multiDrawLoaded = gui.Checkbox(drawingSettingsMulti, "settings.drawpathloaded", "Loaded Path", true)
local colorLoaded = gui.ColorPicker(multiDrawLoaded, "color.loaded", "", 255, 255, 0, 255)
drawingSettingsMulti:SetDescription("Control draw conditions and color of the path.")
-- Pathfinding options, not really but it does 'find' the path so it counts as 'pathfinding'
local pathReturnMulti = gui.Multibox(groupboxSettings, "Return To Path")
local multiReturnUnpause = gui.Checkbox(pathReturnMulti, "settings.return.unpause", "On Unpause", true);
local multiReturnPlay = gui.Checkbox(pathReturnMulti, "settings.return.play", "On start", true);
local multiReturnKeypress = gui.Checkbox(pathReturnMulti, "settings.return.play", "On Keypress", false)
pathReturnMulti:SetDescription("Moves player to start position of path before it plays.")
-- Rest of options and shit
local recordingText = gui.Checkbox(groupboxSettings, "settings.indicator", "Recording and Playback Indicator", true)
recordingText:SetDescription("Enable to see recording and playback status.")
local recordingStartKey = gui.Keybox(groupboxSettings, "recoder.key.start", "Start Recording", 0)
local playControlKey = gui.Keybox(groupboxSettings, "playback.key.start", "Play/Pause Playback", 0)
local recordingEndKey = gui.Keybox(groupboxSettings, "recoder.key.end", "End Recording", 0)
local returnKey = gui.Keybox(groupboxSettings, "playback.key.pause", "Return Key", 0)
local recordingSaveName = gui.Editbox(groupboxSettings, "", "Recording File Name")
recordingSaveName:SetDescription("The name of next recording saved. (alphanumeric only)")
-- List box soo cool
local groupboxRecordings = gui.Groupbox(guiRef, "Saved Recordings", 328, 16, 296, 0)
local selectedRecording = gui.Listbox(groupboxRecordings, "", 244)
local splashText = gui.Text(groupboxRecordings, "Movement Recorder - by scape#4313")
local function recordingEncode(tableData)
local stringData = ""
for i, data in ipairs(tableData) do
stringData = stringData..string.format( "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s", unpack(data))
if i ~= #tableData then stringData = stringData..":" end
end
return ((stringData:gsub('.', function(x)
local r,stringBase64='',x:byte()
for i=8,1,-1 do r=r..(stringBase64%2^i-stringBase64%2^(i-1)>0 and '1' or '0') end
return r;
end)..'0000'):gsub('%d%d%d?%d?%d?%d?', function(x)
if (#x < 6) then return '' end
local c=0
for i=1,6 do c=c+(x:sub(i,i)=='1' and 2^(6-i) or 0) end
return stringBase64:sub(c+1,c+1)
end)..({ '', '==', '=' })[#stringData%3+1])
end
local function recordingDecode(stringData)
stringDecoded = string.gsub(stringData, '[^'..stringBase64..'=]', '')
local stringDecoded = (stringData:gsub('.', function(x)
if (x == '=') then return '' end
local r,f='',(stringBase64:find(x)-1)
for i=6,1,-1 do r=r..(f%2^i-f%2^(i-1)>0 and '1' or '0') end
return r;
end):gsub('%d%d%d?%d?%d?%d?%d?%d?', function(x)
if (#x ~= 8) then return '' end
local c=0
for i=1,8 do c=c+(x:sub(i,i)=='1' and 2^(8-i) or 0) end
return string.char(c)
end))
local ticks = {}
for tick in string.gmatch(stringDecoded, "([^:]+)") do
if tick ~= nil then
local tickData = {}
for data in string.gmatch(tick, "([^,]+)") do
table.insert(tickData, tonumber(data))
end
if #tickData ~= 10 then
error("Corrupt .dat File!")
end
table.insert(ticks, {tickData[1], tickData[2], tickData[3], tickData[4], tickData[5], tickData[6], tickData[7], tickData[8], tickData[9], tickData[10]})
end
end
return ticks
end
local function refreshListBox(text)
local saves = {}
file.Enumerate(function(filename)
if string.match(filename, stringPattern) then
local f = file.Open(filename, "r")
if not pcall(recordingDecode, f:Read()) then
filename = filename.." - WARNING CORRUPT FILE!"
end
filename = filename:gsub(stringFolder.."/", "")
table.insert(saves, filename)
f:Close()
end
end)
splashText:SetText(text or "Movement Recorder - by scape#4313")
recordingSaveName:SetValue("Enter a Recording Name")
selectedRecording:SetOptions(unpack(saves))
end
local buttonLoadRecording = gui.Button(groupboxRecordings, "Load Recording", function()
local index = 0
local error
file.Enumerate(function(filename)
if string.match(filename, stringPattern) then
if index == selectedRecording:GetValue() then
local f = file.Open(filename, "r")
error, cache = pcall(recordingDecode, f:Read())
f:Close()
if error then
splashText:SetText(string.format("Loaded Playback, %s!", filename))
else
splashText:SetText(string.format("Failed to load Playback, %s!", cache))
end
end
index = index + 1
end
end)
end)
local buttonUnloadRecording = gui.Button(groupboxRecordings, "Unload Recording", function()
if isRecording then return end
isPlayback = false
isPaused = false
inReturn = false
cache = nil
splashText:SetText("Unloaded Playback.")
end)
local buttonDeleteRecording = gui.Button(groupboxRecordings, "Delete Recording", function()
local index = 0
file.Enumerate(function(filename)
if string.match(filename, stringPattern) then
if index == selectedRecording:GetValue() then
file.Delete(filename)
refreshListBox("Deleted "..filename)
end
index = index + 1
end
end)
end)
local buttonRenameRecording = gui.Button(groupboxRecordings, "Rename Recording", function()
local index = 0
file.Enumerate(function(filename)
if string.match(filename, stringPattern) then
if index == selectedRecording:GetValue() then
-- Sanitize filenames for renaming procedure
if not (recordingSaveName:GetValue():match("%W")) then
-- Save and delete old file
local f = file.Open(filename, "r")
local backup = f:Read()
f:Close()
file.Delete(filename)
print("deleted "..filename)
-- Create new file with new name same data
local r = file.Open(string.format( "%s/%s",stringFolder, recordingSaveName:GetValue())..".dat", "w")
r:Write(backup)
r:Close()
refreshListBox(string.format("Renamed %s to %s.", filename, recordingSaveName:GetValue()))
else
recordingSaveName:SetValue("Invalid filename, Alphanum chars only!")
end
return
end
index = index + 1
end
end)
end)
local buttonRefreshRecording = gui.Button(groupboxRecordings, "Refresh List", refreshListBox)
-- Finishing touches cause front end is as important as back end and my ocd makes me do this
-- I always scale dpi wayyyy up to align everything pixel perfect
buttonDeleteRecording:SetPosX(136)
buttonRenameRecording:SetPosX(136)
buttonDeleteRecording:SetPosY(260)
buttonRenameRecording:SetPosY(308)
buttonLoadRecording:SetPosY(260)
buttonLoadRecording:SetPosX(0)
recordingStartKey:SetPosY(264)
recordingEndKey:SetPosX(136)
recordingEndKey:SetPosY(264)
playControlKey:SetPosY(320)
returnKey:SetPosX(136)
returnKey:SetPosY(320)
splashText:SetPosY(404)
local function Init()
print("Movement Recorder - Initializing...")
-- Loops through folders to find all recordings
file.Enumerate(function(filename)
if string.match(filename, stringPattern) then
print(string.format(" Validating %s", filename))
-- Load each file to cache to make sure they are valid
local f = file.Open(filename, "r")
local error, data = pcall(recordingDecode, f:Read())
f:Close()
if error then
print(string.format(" Validated %s!\n", filename))
else
print(string.format(" Failed to Validate %s!\n", data))
end
end
end)
recordingSaveName:SetValue("Enter a name and start recording!")
print("Movement Recorder - Initializing done!")
refreshListBox("Movement Recorder - by scape#4313")
end
if not pcall(Init) then
print("Movement Recorder - Error Loading Recordings, Corrupt .dat file!")
print("Movement Recorder - UnLoading Script")
UnloadScript(GetScriptName())
end
-- Callbacks
callbacks.Register("CreateMove", function(cmd)
local Mf = cmd:GetForwardMove()
local Ms = cmd:GetSideMove()
local Mu = cmd:GetUpMove()
local B = cmd:GetButtons()
local V = cmd:GetViewAngles()
local P = entities.GetLocalPlayer():GetAbsOrigin()
if cache and isRecording then
table.insert(cache, {Mf, Ms, Mu, B, V.x, V.y, V.z, P.x, P.y, P.z})
end
if cache and inReturn and isPaused then
if cache[1] then
local target = Vector3(cache[1][8], cache[1][9], cache[1][10])
local forward = target - entities.GetLocalPlayer():GetAbsOrigin()
local angles = engine.GetViewAngles()
if forward:Length() < 10 then
inReturn = false
isPaused = false
end
cmd.forwardmove = (((math.sin(math.rad(angles.y)) * forward.y) + (math.cos(math.rad(angles.y)) * forward.x) ) * 200)
cmd.sidemove = (((math.cos(math.rad(angles.y)) * -forward.y) + (math.sin(math.rad(angles.y)) * forward.x) ) * 200)
end
elseif cache and isPlayback and not isPaused then
if cache[1] then
local actions = cache[1]
cmd.forwardmove = actions[1]
cmd.sidemove = actions[2]
cmd.upmove = actions[3]
cmd.buttons = actions[4]
engine.SetViewAngles(EulerAngles(actions[5], actions[6], actions[7]))
table.remove(cache, 1)
if #cache == 0 then
isPlayback = false
isPaused = false
cache = nil
end
end
end
end)
callbacks.Register("Draw", function()
-- Key Press control
-- Start recording
if recordingStartKey:GetValue() ~= 0 and input.IsButtonPressed(recordingStartKey:GetValue()) then
if not isPlayback and not isRecording then
local filename = recordingSaveName:GetValue()
-- Sanitize filenames
if not (filename:match("%W")) then
isRecording = true
cache = {}
cache.filename = string.format( "%s/%s",stringFolder, recordingSaveName:GetValue())..".dat"
splashText:SetText(string.format("Recording to %s.dat ", filename))
else
recordingSaveName:SetValue("Invalid filename, Alphanum chars only!")
end
end
end
-- Save recording
if recordingEndKey:GetValue() ~= 0 and input.IsButtonPressed(recordingEndKey:GetValue()) then
if isRecording and not isPlayback then
isRecording = false
local f = file.Open(cache.filename, "w")
local data = recordingEncode(cache)
f:Write(data)
f:Close()
refreshListBox("Recording Ended "..cache.filename)
end
end
-- Start playback
if cache and playControlKey:GetValue() ~= 0 and input.IsButtonPressed(playControlKey:GetValue()) then
if not isRecording then
-- Start
if not isPlayback then
if multiReturnPlay:GetValue() then
isPaused = true inReturn = true
else
isPaused = false inReturn = false
end
isPlayback = true
-- Unpause
elseif isPaused then
if multiReturnUnpause:GetValue() and not inReturn then
isPaused = true inReturn = true
else
isPaused = false inReturn = false
end
-- Pause
else
isPaused = true
inReturn = false
end
end
end
-- Recording indicator
if recordingText:GetValue() then
-- Draw Control
draw.Color(255, 0, 0, 255)
if isRecording then
draw.Color(0, 255, 0, 255)
end
draw.Text(100, 100, "Recording")
draw.Color(255, 0, 0, 255)
if isPlayback then
draw.Color(0, 255, 0, 255)
if isPaused then
draw.Color(255, 255, 0, 255)
end
end
draw.Text(100, 115, "Playback")
draw.Color(255, 0, 0, 255)
if inReturn then
draw.Color(0, 255, 0, 255)
end
draw.Text(100, 130, "Return")
end
-- Path drawing stuff
if cache and ((isRecording and multiDrawOnRec:GetValue()) or (isPlayback and multiDrawOnPlay:GetValue()) or (multiDrawLoaded:GetValue()and not isPlayback and not isRecording)) then
for i = 1, #cache - 1 do
local x1, y1 = client.WorldToScreen(Vector3(cache[i][8], cache[i][9], cache[i][10]))
local x2, y2 = client.WorldToScreen(Vector3(cache[i+1][8], cache[i+1][9], cache[i+1][10]))
if x1 and x2 then
-- Draw Line
local r, g, b, a = colorLoaded:GetValue()
if isRecording then
r, g, b, a = colorRec:GetValue()
elseif isPlayback then
r, g, b, a = colorPlay:GetValue()
end
draw.Color(r, g, b, a)
draw.Line(x1, y1, x2, y2)
end
end
end
-- Start and end points, how path drawing and play back is handled makes this real ez
if cache and multiDrawEnds:GetValue() then
if cache[1] and cache[#cache] then
local Bx, By = client.WorldToScreen(Vector3(cache[1][8], cache[1][9], cache[1][10]))
local Ex, Ey = client.WorldToScreen(Vector3(cache[#cache][8], cache[#cache][9], cache[#cache][10]))
local r, g, b, a = colorStart:GetValue()
draw.Color(r, g, b, a)
draw.FilledCircle(Bx, By, 4)
r, g, b, a = colorEnd:GetValue()
draw.Color(r, g, b, a)
draw.FilledCircle(Ex, Ey, 4)
end
end
end)