pulse-rb 1.2.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +225 -0
- package/adapters/linoria.lua +233 -0
- package/adapters/windui.llms.txt +366 -0
- package/adapters/windui.lua +505 -0
- package/bin/rb.js +2 -0
- package/dist/index.js +3285 -0
- package/package.json +59 -0
- package/pulse/dev/debuggui.lua +1206 -0
- package/pulse/dev/devconfig.lua +81 -0
- package/pulse/dev/ui/9_DevPanel.lua +384 -0
- package/pulse/helpers/aim.lua +193 -0
- package/pulse/helpers/cache.lua +68 -0
- package/pulse/helpers/cleaner.lua +110 -0
- package/pulse/helpers/conn.lua +33 -0
- package/pulse/helpers/cooldown.lua +47 -0
- package/pulse/helpers/draw.lua +122 -0
- package/pulse/helpers/hitbox.lua +63 -0
- package/pulse/helpers/input.lua +24 -0
- package/pulse/helpers/log.lua +228 -0
- package/pulse/helpers/loop.lua +58 -0
- package/pulse/helpers/memory.lua +48 -0
- package/pulse/helpers/narrate.lua +160 -0
- package/pulse/helpers/notify.lua +51 -0
- package/pulse/helpers/perf.lua +48 -0
- package/pulse/helpers/remote.lua +128 -0
- package/pulse/helpers/restore.lua +59 -0
- package/pulse/helpers/store.lua +39 -0
- package/pulse/helpers/team.lua +83 -0
- package/pulse/helpers/testmode.lua +80 -0
- package/pulse/helpers/trace.lua +111 -0
- package/pulse/helpers/track.lua +85 -0
- package/pulse/helpers/vec.lua +52 -0
- package/pulse/helpers/world.lua +51 -0
- package/pulse/runtime.lua +343 -0
- package/pulse/ui/linoria_settings.lua +55 -0
- package/pulse/ui/windui_settings.lua +87 -0
- package/templates/AGENTS.md +177 -0
- package/templates/CLAUDE.md +424 -0
- package/templates/deploy_config.example +17 -0
- package/templates/example_esp.rblua +69 -0
- package/templates/example_fov.rblua +20 -0
- package/templates/example_speed.rblua +25 -0
- package/templates/gitignore +4 -0
- package/templates/globals.lua +66 -0
- package/templates/layout.rblua +28 -0
- package/templates/module.lua +7 -0
- package/templates/module.rblua +32 -0
- package/templates/page_home.rblua +9 -0
- package/templates/remote.lua +6 -0
- package/templates/remotes.lua +14 -0
- package/vscode/language-configuration.json +35 -0
- package/vscode/package.json +35 -0
- package/vscode/src/extension.js +397 -0
- package/vscode/syntaxes/rblua.tmLanguage.json +126 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
-- ── Pulse.Remote ─────────────────────────────────────────────────────────────
|
|
2
|
+
-- Lazy-resolved, path-cached RemoteEvent / RemoteFunction wrapper.
|
|
3
|
+
-- Paths are slash-separated from ReplicatedStorage: "Remotes/Building".
|
|
4
|
+
--
|
|
5
|
+
-- Usage:
|
|
6
|
+
-- -- Direct fire / invoke
|
|
7
|
+
-- Pulse.Remote.fire("Remotes/Building", "Place", cf)
|
|
8
|
+
-- local result = Pulse.Remote.invoke("Remotes/GetStats")
|
|
9
|
+
--
|
|
10
|
+
-- -- Pre-bound function (preferred in a game's remotes.lua)
|
|
11
|
+
-- func.Remote_Build = Pulse.Remote.bind("Remotes/Building")
|
|
12
|
+
-- func.Remote_Build("Place", cf)
|
|
13
|
+
--
|
|
14
|
+
-- -- Client-event listener
|
|
15
|
+
-- Pulse.Remote.connect("Remotes/Notify", function(msg) ... end)
|
|
16
|
+
--
|
|
17
|
+
-- -- Warm the cache before first use (avoids WaitForChild latency in hot loops)
|
|
18
|
+
-- Pulse.Remote.prefetch("Remotes/Building", "Remotes/Storage")
|
|
19
|
+
|
|
20
|
+
Pulse.Remote = (function()
|
|
21
|
+
local R = {}
|
|
22
|
+
local _RS = game:GetService("ReplicatedStorage")
|
|
23
|
+
local _PENDING = {} -- sentinel: resolution in progress
|
|
24
|
+
local _cache = {} -- path → Instance | false | _PENDING
|
|
25
|
+
|
|
26
|
+
local function _split(path)
|
|
27
|
+
local parts = {}
|
|
28
|
+
for part in path:gmatch("[^/]+") do parts[#parts + 1] = part end
|
|
29
|
+
return parts
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
-- Walk a slash-separated path under ReplicatedStorage via WaitForChild.
|
|
33
|
+
-- Yields on the first call; O(1) cache hit on every subsequent call.
|
|
34
|
+
-- Returns the resolved Instance, or nil if any segment is missing.
|
|
35
|
+
local function _resolve(path)
|
|
36
|
+
-- Another coroutine may be resolving the same path; wait for it.
|
|
37
|
+
while _cache[path] == _PENDING do task.wait(0.05) end
|
|
38
|
+
|
|
39
|
+
local cached = _cache[path]
|
|
40
|
+
if cached ~= nil then return cached ~= false and cached or nil end
|
|
41
|
+
|
|
42
|
+
_cache[path] = _PENDING
|
|
43
|
+
local node = _RS
|
|
44
|
+
for _, part in ipairs(_split(path)) do
|
|
45
|
+
local ok, child = pcall(function()
|
|
46
|
+
return node:WaitForChild(part, 10)
|
|
47
|
+
end)
|
|
48
|
+
if not ok or not child then
|
|
49
|
+
Pulse.Log.warn("Remote", "path not found",
|
|
50
|
+
{ path = path, segment = part })
|
|
51
|
+
_cache[path] = false
|
|
52
|
+
return nil
|
|
53
|
+
end
|
|
54
|
+
node = child
|
|
55
|
+
end
|
|
56
|
+
_cache[path] = node
|
|
57
|
+
return node
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
-- FireServer a RemoteEvent at `path`.
|
|
61
|
+
function R.fire(path, ...)
|
|
62
|
+
local remote = _resolve(path)
|
|
63
|
+
if not remote then return end
|
|
64
|
+
local args = { ... }
|
|
65
|
+
local ok, err = pcall(function()
|
|
66
|
+
remote:FireServer(table.unpack(args))
|
|
67
|
+
end)
|
|
68
|
+
if not ok then
|
|
69
|
+
Pulse.Log.warn("Remote", "fire failed",
|
|
70
|
+
{ path = path, err = tostring(err) })
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
-- InvokeServer a RemoteFunction at `path`. Returns the result or nil.
|
|
75
|
+
function R.invoke(path, ...)
|
|
76
|
+
local remote = _resolve(path)
|
|
77
|
+
if not remote then return nil end
|
|
78
|
+
local args = { ... }
|
|
79
|
+
local ok, result = pcall(function()
|
|
80
|
+
return remote:InvokeServer(table.unpack(args))
|
|
81
|
+
end)
|
|
82
|
+
if not ok then
|
|
83
|
+
Pulse.Log.warn("Remote", "invoke failed",
|
|
84
|
+
{ path = path, err = tostring(result) })
|
|
85
|
+
return nil
|
|
86
|
+
end
|
|
87
|
+
return result
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
-- Connect `fn` to a RemoteEvent's OnClientEvent. Returns the connection.
|
|
91
|
+
function R.connect(path, fn)
|
|
92
|
+
local remote = _resolve(path)
|
|
93
|
+
if not remote then return nil end
|
|
94
|
+
return remote.OnClientEvent:Connect(fn)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
-- Returns a pre-bound fire function: fn(...) → fires path with those args.
|
|
98
|
+
-- Use this in a game's remotes.lua to define named wrappers without boilerplate.
|
|
99
|
+
function R.bind(path)
|
|
100
|
+
return function(...) R.fire(path, ...) end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
-- Returns a pre-bound invoke function: fn(...) → returns invoke result.
|
|
104
|
+
function R.bindi(path)
|
|
105
|
+
return function(...) return R.invoke(path, ...) end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
-- Returns a wrapper object with :fire, :invoke, :connect, :get.
|
|
109
|
+
-- Useful when you need multiple operations on the same remote in one place.
|
|
110
|
+
function R.wrap(path)
|
|
111
|
+
local self = {}
|
|
112
|
+
function self:fire(...) R.fire(path, ...) end
|
|
113
|
+
function self:invoke(...) return R.invoke(path, ...) end
|
|
114
|
+
function self:connect(fn) return R.connect(path, fn) end
|
|
115
|
+
function self:get() return _resolve(path) end
|
|
116
|
+
return self
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
-- Resolve paths eagerly in background threads so first-use has zero latency.
|
|
120
|
+
-- Call Pulse.Remote.prefetch(...) inside a task.spawn in init {}.
|
|
121
|
+
function R.prefetch(...)
|
|
122
|
+
for _, path in ipairs({...}) do
|
|
123
|
+
task.spawn(function() _resolve(path) end)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
return R
|
|
128
|
+
end)()
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
-- ── Pulse.Restore ────────────────────────────────────────────────────────────
|
|
2
|
+
-- Save and restore Instance property values.
|
|
3
|
+
-- Replaces _originalSizes tables, OriginalProtectionValues, etc.
|
|
4
|
+
-- Usage:
|
|
5
|
+
-- local r = Pulse.Restore.new()
|
|
6
|
+
-- r:save(part, "Size") -- remember current part.Size
|
|
7
|
+
-- r:save(boolValue, "Value") -- remember current .Value
|
|
8
|
+
-- r:all() -- restore everything to saved state
|
|
9
|
+
-- r:one(part, "Size") -- restore just one entry
|
|
10
|
+
-- r:has(part, "Size") → bool
|
|
11
|
+
|
|
12
|
+
Pulse.Restore = (function()
|
|
13
|
+
local R = {}
|
|
14
|
+
|
|
15
|
+
function R.new()
|
|
16
|
+
local self = { _data = {} }
|
|
17
|
+
|
|
18
|
+
local function key(obj, prop)
|
|
19
|
+
return tostring(obj) .. "\0" .. prop
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
-- Save obj[prop] if not already saved (first-save wins).
|
|
23
|
+
function self:save(obj, prop)
|
|
24
|
+
local k = key(obj, prop)
|
|
25
|
+
if self._data[k] then return end
|
|
26
|
+
local ok, val = pcall(function() return obj[prop] end)
|
|
27
|
+
if ok then self._data[k] = { obj = obj, prop = prop, val = val } end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
-- Restore one saved entry.
|
|
31
|
+
function self:one(obj, prop)
|
|
32
|
+
local k = key(obj, prop)
|
|
33
|
+
local rec = self._data[k]
|
|
34
|
+
if rec then
|
|
35
|
+
pcall(function() rec.obj[rec.prop] = rec.val end)
|
|
36
|
+
self._data[k] = nil
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
-- Restore all saved entries and clear the table.
|
|
41
|
+
function self:all()
|
|
42
|
+
for _, rec in pairs(self._data) do
|
|
43
|
+
pcall(function() rec.obj[rec.prop] = rec.val end)
|
|
44
|
+
end
|
|
45
|
+
self._data = {}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
-- Forget saved values without restoring.
|
|
49
|
+
function self:clear() self._data = {} end
|
|
50
|
+
|
|
51
|
+
function self:has(obj, prop)
|
|
52
|
+
return self._data[key(obj, prop)] ~= nil
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
return self
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
return R
|
|
59
|
+
end)()
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
-- ── Pulse.Store ───────────────────────────────────────────────────────────────
|
|
2
|
+
-- Key-value reactive store — cross-component shared state via named signals.
|
|
3
|
+
-- Usage:
|
|
4
|
+
-- Pulse.Store.define("key", defaultValue)
|
|
5
|
+
-- Pulse.Store.get("key") → current value
|
|
6
|
+
-- Pulse.Store.set("key", value) → writes + fires watchers
|
|
7
|
+
-- Pulse.Store.watch("key", fn) → fn(newValue) on change
|
|
8
|
+
-- Pulse.Store.signal("key") → raw Signal object
|
|
9
|
+
|
|
10
|
+
Pulse.Store = (function()
|
|
11
|
+
local S = {}
|
|
12
|
+
local _entries = {}
|
|
13
|
+
|
|
14
|
+
function S.define(key, default)
|
|
15
|
+
if _entries[key] then return end
|
|
16
|
+
_entries[key] = Signal(default)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
function S.get(key)
|
|
20
|
+
local e = _entries[key]
|
|
21
|
+
return e and e() or nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
function S.set(key, value)
|
|
25
|
+
local e = _entries[key]
|
|
26
|
+
if e then e(value) end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
function S.watch(key, fn)
|
|
30
|
+
local e = _entries[key]
|
|
31
|
+
if e then e:onChange(fn) end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
function S.signal(key)
|
|
35
|
+
return _entries[key]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
return S
|
|
39
|
+
end)()
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
-- ── Pulse.Team ────────────────────────────────────────────────────────────────
|
|
2
|
+
-- Configurable faction/enemy resolver factory.
|
|
3
|
+
-- Decouples "who is an enemy" logic from features that act on enemies.
|
|
4
|
+
-- Each resolver is an independent object — create one per use-case.
|
|
5
|
+
--
|
|
6
|
+
-- Usage:
|
|
7
|
+
-- local r = Pulse.Team.resolver({
|
|
8
|
+
-- isSelf = function(e) return e == _LocalPlayer end,
|
|
9
|
+
-- isValid = function(e) return e.Character ~= nil end,
|
|
10
|
+
-- isHostile = function(e) return func.IsOnWarriorsTeam() ~= func.IsWarrior(e) end,
|
|
11
|
+
-- exclude = { func.IsParamountShifterFriendly },
|
|
12
|
+
-- })
|
|
13
|
+
--
|
|
14
|
+
-- r:isEnemy(player) → bool
|
|
15
|
+
-- r:isAlly(player) → bool (valid, not self, not enemy)
|
|
16
|
+
-- r:filter(playerList) → list of enemies only
|
|
17
|
+
-- r:partition(playerList) → allies, enemies (two return values)
|
|
18
|
+
--
|
|
19
|
+
-- opts fields:
|
|
20
|
+
-- isSelf(entity) → bool — entity is the local player / our own unit; always skipped
|
|
21
|
+
-- isValid(entity) → bool — entity is in a valid state (alive, has character, etc.)
|
|
22
|
+
-- Returning false means "skip" but NOT enemy — used for
|
|
23
|
+
-- pre-filtering dead/respawning players.
|
|
24
|
+
-- isHostile(entity) → bool — REQUIRED core check: is this entity on the opposing side?
|
|
25
|
+
-- exclude = { fn, fn, ... }
|
|
26
|
+
-- Additional veto functions. Any fn(entity) returning true
|
|
27
|
+
-- means "treat as not-enemy" (friendly exceptions, etc.).
|
|
28
|
+
|
|
29
|
+
Pulse.Team = {}
|
|
30
|
+
|
|
31
|
+
function Pulse.Team.resolver(opts)
|
|
32
|
+
opts = opts or {}
|
|
33
|
+
local R = {}
|
|
34
|
+
|
|
35
|
+
-- Internal: run all checks in order. Returns "enemy", "skip", or "ally".
|
|
36
|
+
local function _classify(entity)
|
|
37
|
+
if not entity then return "skip" end
|
|
38
|
+
if opts.isSelf and opts.isSelf(entity) then return "skip" end
|
|
39
|
+
if opts.isValid and not opts.isValid(entity) then return "skip" end
|
|
40
|
+
if opts.exclude then
|
|
41
|
+
for _, fn in ipairs(opts.exclude) do
|
|
42
|
+
if fn(entity) then return "ally" end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
if not opts.isHostile then return "ally" end
|
|
46
|
+
return opts.isHostile(entity) and "enemy" or "ally"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
-- True if entity should be targeted as an enemy.
|
|
50
|
+
function R:isEnemy(entity)
|
|
51
|
+
return _classify(entity) == "enemy"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
-- True if entity is valid, not self, and not an enemy.
|
|
55
|
+
function R:isAlly(entity)
|
|
56
|
+
return _classify(entity) == "ally"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
-- Returns a new list containing only enemies from `list`.
|
|
60
|
+
function R:filter(list)
|
|
61
|
+
local out = {}
|
|
62
|
+
for _, e in ipairs(list) do
|
|
63
|
+
if _classify(e) == "enemy" then out[#out + 1] = e end
|
|
64
|
+
end
|
|
65
|
+
return out
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
-- Splits `list` into two lists: allies, enemies.
|
|
69
|
+
function R:partition(list)
|
|
70
|
+
local allies, enemies = {}, {}
|
|
71
|
+
for _, e in ipairs(list) do
|
|
72
|
+
local c = _classify(e)
|
|
73
|
+
if c == "enemy" then
|
|
74
|
+
enemies[#enemies + 1] = e
|
|
75
|
+
elseif c == "ally" then
|
|
76
|
+
allies[#allies + 1] = e
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
return allies, enemies
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
return R
|
|
83
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
-- Pulse.TestMode — dev-build test harness.
|
|
2
|
+
-- Active only when _PULSE_DEV = true. In prod every call is a no-op.
|
|
3
|
+
--
|
|
4
|
+
-- Usage (in rb/pulse/dev/devconfig.lua):
|
|
5
|
+
-- Pulse.TestMode.configure({ target = "ShifterPartsCutter" })
|
|
6
|
+
-- Pulse.TestMode.configure({ target = { "Aimbot", "ShifterCutter" } })
|
|
7
|
+
-- Pulse.TestMode.configure({ all = true }) -- all features on (normal dev mode)
|
|
8
|
+
--
|
|
9
|
+
-- The defaults runner in compiler.py reads isActive()/isTarget(id) to force
|
|
10
|
+
-- non-target toggles to false before the UI is shown.
|
|
11
|
+
|
|
12
|
+
do
|
|
13
|
+
local _active = (_PULSE_DEV == true)
|
|
14
|
+
local _enabled = false
|
|
15
|
+
local _targets = {} -- set: lowercased target names
|
|
16
|
+
|
|
17
|
+
Pulse.TestMode = {}
|
|
18
|
+
|
|
19
|
+
-- Configure test mode. Call from devlog.lua before the defaults runner fires.
|
|
20
|
+
-- opts.target = "Name" | { "Name1", "Name2" } — only these features enabled
|
|
21
|
+
-- opts.all = true — all features on (disable test filter)
|
|
22
|
+
function Pulse.TestMode.configure(opts)
|
|
23
|
+
if not _active then return end
|
|
24
|
+
_targets = {}
|
|
25
|
+
_enabled = false
|
|
26
|
+
|
|
27
|
+
if opts.all then
|
|
28
|
+
-- explicit "all on" — not in filtered test mode
|
|
29
|
+
Pulse.Monitor.set("test", "all-on")
|
|
30
|
+
return
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
local raw = opts.target
|
|
34
|
+
if raw == nil then return end
|
|
35
|
+
|
|
36
|
+
local list = type(raw) == "table" and raw or { raw }
|
|
37
|
+
for _, name in ipairs(list) do
|
|
38
|
+
if type(name) == "string" and name ~= "" then
|
|
39
|
+
_targets[name:lower()] = true
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if next(_targets) then
|
|
44
|
+
_enabled = true
|
|
45
|
+
local names = {}
|
|
46
|
+
for n in pairs(_targets) do names[#names + 1] = n end
|
|
47
|
+
table.sort(names)
|
|
48
|
+
Pulse.Monitor.set("test", "only:" .. table.concat(names, ","))
|
|
49
|
+
Pulse.Log.info("testmode", "test mode active", { targets = names })
|
|
50
|
+
else
|
|
51
|
+
Pulse.Monitor.set("test", "all-on")
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
-- Returns true when test mode is filtering features (some are forced off).
|
|
56
|
+
function Pulse.TestMode.isActive()
|
|
57
|
+
return _active and _enabled
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
-- Returns true if widgetId belongs to a test target.
|
|
61
|
+
-- Uses case-insensitive substring match:
|
|
62
|
+
-- target "ShifterCutter" matches "ShifterCutterEnable", "ShifterCutterKey", etc.
|
|
63
|
+
function Pulse.TestMode.isTarget(widgetId)
|
|
64
|
+
if not _active or not _enabled then return true end
|
|
65
|
+
if next(_targets) == nil then return true end
|
|
66
|
+
local lower = widgetId:lower()
|
|
67
|
+
for name in pairs(_targets) do
|
|
68
|
+
if lower:find(name, 1, true) then return true end
|
|
69
|
+
end
|
|
70
|
+
return false
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
-- Returns a sorted list of target names (used by the defaults runner notify).
|
|
74
|
+
function Pulse.TestMode.getTargets()
|
|
75
|
+
local t = {}
|
|
76
|
+
for name in pairs(_targets) do t[#t + 1] = name end
|
|
77
|
+
table.sort(t)
|
|
78
|
+
return t
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
-- Pulse.Trace — inter-function call tracing for dev builds.
|
|
2
|
+
-- Active only when _PULSE_DEV = true. In prod every call is a no-op.
|
|
3
|
+
--
|
|
4
|
+
-- Usage (in rb/pulse/dev/devconfig.lua):
|
|
5
|
+
-- Pulse.Trace.instrument(func, "globals") -- wrap all func.* functions
|
|
6
|
+
-- Pulse.Trace.wrap(someFunc, "tag", "name") -- wrap a single function
|
|
7
|
+
-- Pulse.Trace.disable() -- stop tracing without unwrapping
|
|
8
|
+
-- Pulse.Trace.enable() -- re-enable
|
|
9
|
+
--
|
|
10
|
+
-- Output example:
|
|
11
|
+
-- → IsWarrior Player1
|
|
12
|
+
-- ← IsWarrior true
|
|
13
|
+
-- → GetCachedTitans (none)
|
|
14
|
+
-- ← GetCachedTitans {n=3}
|
|
15
|
+
|
|
16
|
+
do
|
|
17
|
+
local _active = (_PULSE_DEV == true)
|
|
18
|
+
local _on = true
|
|
19
|
+
|
|
20
|
+
-- ── Value serializer ─────────────────────────────────────────────────────
|
|
21
|
+
-- Converts a single Lua/Roblox value to a short readable string.
|
|
22
|
+
local function _val(v)
|
|
23
|
+
local t = type(v)
|
|
24
|
+
if t == "nil" then return "nil" end
|
|
25
|
+
if t == "boolean" then return tostring(v) end
|
|
26
|
+
if t == "number" then
|
|
27
|
+
-- trim unnecessary decimals
|
|
28
|
+
local s = string.format("%.4g", v)
|
|
29
|
+
return s
|
|
30
|
+
end
|
|
31
|
+
if t == "string" then
|
|
32
|
+
local s = v:sub(1, 28)
|
|
33
|
+
return '"' .. s .. (v:len() > 28 and '…"' or '"')
|
|
34
|
+
end
|
|
35
|
+
if t == "table" then
|
|
36
|
+
local n = 0; for _ in pairs(v) do n = n + 1 end
|
|
37
|
+
return "{n=" .. n .. "}"
|
|
38
|
+
end
|
|
39
|
+
-- Roblox Instance, Vector3, CFrame, etc.
|
|
40
|
+
local ok, s = pcall(tostring, v)
|
|
41
|
+
if ok then return s:sub(1, 36) end
|
|
42
|
+
return "<?>"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
-- Serializes variadic args to a single display string.
|
|
46
|
+
local function _args(...)
|
|
47
|
+
local n = select("#", ...)
|
|
48
|
+
if n == 0 then return "(none)" end
|
|
49
|
+
local parts = {}
|
|
50
|
+
for i = 1, math.min(n, 4) do
|
|
51
|
+
parts[i] = _val(select(i, ...))
|
|
52
|
+
end
|
|
53
|
+
if n > 4 then parts[#parts + 1] = "+" .. (n - 4) .. " more" end
|
|
54
|
+
return table.concat(parts, " ")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
-- Serializes return values (same logic).
|
|
58
|
+
local function _rets(pack)
|
|
59
|
+
local n = pack.n
|
|
60
|
+
if n == 0 then return "(none)" end
|
|
61
|
+
local parts = {}
|
|
62
|
+
for i = 1, math.min(n, 4) do
|
|
63
|
+
parts[i] = _val(pack[i])
|
|
64
|
+
end
|
|
65
|
+
if n > 4 then parts[#parts + 1] = "+" .. (n - 4) .. " more" end
|
|
66
|
+
return table.concat(parts, " ")
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
-- ── Public API ────────────────────────────────────────────────────────────
|
|
70
|
+
|
|
71
|
+
Pulse.Trace = {}
|
|
72
|
+
|
|
73
|
+
function Pulse.Trace.enable() _on = true end
|
|
74
|
+
function Pulse.Trace.disable() _on = false end
|
|
75
|
+
|
|
76
|
+
-- Wrap a single function. The returned function is safe — it never swallows
|
|
77
|
+
-- errors; it re-throws after logging so callers behave identically.
|
|
78
|
+
function Pulse.Trace.wrap(fn, tag, name)
|
|
79
|
+
if not _active then return fn end
|
|
80
|
+
local _name = tostring(name or "?")
|
|
81
|
+
return function(...)
|
|
82
|
+
if not _on then return fn(...) end
|
|
83
|
+
Pulse.Log.trace(tag, "→ " .. _name, { in_ = _args(...) })
|
|
84
|
+
local results = table.pack(pcall(fn, ...))
|
|
85
|
+
local ok = table.remove(results, 1)
|
|
86
|
+
results.n = results.n - 1
|
|
87
|
+
if ok then
|
|
88
|
+
Pulse.Log.trace(tag, "← " .. _name, { out = _rets(results) })
|
|
89
|
+
return table.unpack(results, 1, results.n)
|
|
90
|
+
else
|
|
91
|
+
local err = tostring(results[1])
|
|
92
|
+
Pulse.Log.error(tag, "✕ " .. _name .. " threw", { err = err })
|
|
93
|
+
error(results[1], 2)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
-- Wrap every function-valued field in tbl in-place.
|
|
99
|
+
-- Useful for tracing all func.* helpers at once from devlog.lua.
|
|
100
|
+
function Pulse.Trace.instrument(tbl, tag)
|
|
101
|
+
if not _active then return end
|
|
102
|
+
local count = 0
|
|
103
|
+
for k, v in pairs(tbl) do
|
|
104
|
+
if type(v) == "function" then
|
|
105
|
+
tbl[k] = Pulse.Trace.wrap(v, tag, k)
|
|
106
|
+
count = count + 1
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
Pulse.Log.debug(tag, "instrumented", { functions = count })
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
-- ── Pulse.Track ──────────────────────────────────────────────────────────────
|
|
2
|
+
-- Entity lifecycle tracking — apply effects and auto-cleanup when they leave.
|
|
3
|
+
-- Usage:
|
|
4
|
+
-- local tracker = Pulse.Track.new("Aimbot") -- name is optional but helps logs
|
|
5
|
+
--
|
|
6
|
+
-- tracker:apply(titan, function(t)
|
|
7
|
+
-- Pulse.Draw.removeHighlight(t:FindFirstChild("Hitboxes"), "ESP")
|
|
8
|
+
-- end)
|
|
9
|
+
--
|
|
10
|
+
-- on Heartbeat every 1.0 when titanEnabled {
|
|
11
|
+
-- tracker:cleanup(func.IsTitanDead)
|
|
12
|
+
-- for _, t in ipairs(func.GetCachedTitans()) do
|
|
13
|
+
-- if not tracker:has(t) then applyESP(t) end
|
|
14
|
+
-- end
|
|
15
|
+
-- }
|
|
16
|
+
--
|
|
17
|
+
-- on titanEnabled { if not v then tracker:clear() end }
|
|
18
|
+
|
|
19
|
+
Pulse.Track = (function()
|
|
20
|
+
local T = {}
|
|
21
|
+
|
|
22
|
+
function T.new(name)
|
|
23
|
+
local _tag = "track" .. (name and (":" .. name) or "")
|
|
24
|
+
local self = { _e = {}, _c = {} }
|
|
25
|
+
|
|
26
|
+
-- Mark entity as tracked; cleanupFn(entity) is called when removed.
|
|
27
|
+
function self:apply(entity, cleanupFn)
|
|
28
|
+
self._e[entity] = true
|
|
29
|
+
if cleanupFn then self._c[entity] = cleanupFn end
|
|
30
|
+
Pulse.Log.trace(_tag, "apply", { name = pcall(function() return entity.Name end) and entity.Name or "?" })
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
-- Remove one entity and call its cleanup function.
|
|
34
|
+
function self:remove(entity)
|
|
35
|
+
if not self._e[entity] then return end
|
|
36
|
+
local fn = self._c[entity]
|
|
37
|
+
if fn then pcall(fn, entity) end
|
|
38
|
+
local eName = "?"
|
|
39
|
+
pcall(function() eName = entity.Name end)
|
|
40
|
+
Pulse.Log.trace(_tag, "remove", { name = eName })
|
|
41
|
+
self._e[entity] = nil; self._c[entity] = nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
-- Remove all entities where predicate(entity) returns true.
|
|
45
|
+
function self:cleanup(predicate)
|
|
46
|
+
local toRemove = {}
|
|
47
|
+
for entity in pairs(self._e) do
|
|
48
|
+
local ok, result = pcall(predicate, entity)
|
|
49
|
+
if ok and result then toRemove[#toRemove + 1] = entity end
|
|
50
|
+
end
|
|
51
|
+
if #toRemove > 0 then
|
|
52
|
+
Pulse.Log.debug(_tag, "cleanup", { removed = #toRemove, remaining = self:count() - #toRemove })
|
|
53
|
+
end
|
|
54
|
+
for _, entity in ipairs(toRemove) do self:remove(entity) end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
-- Remove every tracked entity and call all cleanup functions.
|
|
58
|
+
function self:clear()
|
|
59
|
+
local n = self:count()
|
|
60
|
+
if n > 0 then
|
|
61
|
+
Pulse.Log.debug(_tag, "clear", { count = n })
|
|
62
|
+
end
|
|
63
|
+
for entity in pairs(self._e) do
|
|
64
|
+
local fn = self._c[entity]
|
|
65
|
+
if fn then pcall(fn, entity) end
|
|
66
|
+
end
|
|
67
|
+
self._e = {}; self._c = {}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
function self:has(entity) return self._e[entity] == true end
|
|
71
|
+
|
|
72
|
+
function self:count()
|
|
73
|
+
local n = 0; for _ in pairs(self._e) do n = n + 1 end; return n
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
-- Call fn(entity) for every tracked entity (errors are swallowed).
|
|
77
|
+
function self:each(fn)
|
|
78
|
+
for entity in pairs(self._e) do pcall(fn, entity) end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
return self
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
return T
|
|
85
|
+
end)()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
-- ── Pulse.Vec ────────────────────────────────────────────────────────────────
|
|
2
|
+
-- NaN-safe Vector3 utilities.
|
|
3
|
+
-- Replaces `tostring(dir.X) == "nan"` guards and zero-distance crashes.
|
|
4
|
+
-- Usage:
|
|
5
|
+
-- local dir = Pulse.Vec.dir(fromPos, toPos) -- safe unit direction
|
|
6
|
+
-- local dir = Pulse.Vec.flatDir(fromPos, toPos) -- XZ-plane only
|
|
7
|
+
-- local ok = Pulse.Vec.isValid(someVec) -- no NaN/inf
|
|
8
|
+
|
|
9
|
+
Pulse.Vec = (function()
|
|
10
|
+
local V = {}
|
|
11
|
+
local _n = math.huge -- used for inf check
|
|
12
|
+
|
|
13
|
+
local function isNaN(n) return n ~= n end
|
|
14
|
+
|
|
15
|
+
-- Unit direction from `from` to `to`. Returns fallback if positions overlap.
|
|
16
|
+
function V.dir(from, to, fallback)
|
|
17
|
+
local d = to - from
|
|
18
|
+
if d.Magnitude < 1e-6 then
|
|
19
|
+
return fallback or Vector3.new(0, 0, 1)
|
|
20
|
+
end
|
|
21
|
+
return d.Unit
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
-- XZ-plane unit direction (Y ignored). Falls back to camera look if coincident.
|
|
25
|
+
function V.flatDir(from, to, fallback)
|
|
26
|
+
local dx = to.X - from.X
|
|
27
|
+
local dz = to.Z - from.Z
|
|
28
|
+
local mag = math.sqrt(dx * dx + dz * dz)
|
|
29
|
+
if mag < 1e-6 then
|
|
30
|
+
if fallback then return fallback end
|
|
31
|
+
local cam = workspace.CurrentCamera
|
|
32
|
+
if cam then
|
|
33
|
+
local lv = cam.CFrame.LookVector
|
|
34
|
+
if math.sqrt(lv.X * lv.X + lv.Z * lv.Z) > 1e-6 then
|
|
35
|
+
return Vector3.new(lv.X, 0, lv.Z).Unit
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
return Vector3.new(0, 0, 1)
|
|
39
|
+
end
|
|
40
|
+
return Vector3.new(dx / mag, 0, dz / mag)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
-- True if vec has no NaN or infinite components.
|
|
44
|
+
function V.isValid(vec)
|
|
45
|
+
if typeof(vec) ~= "Vector3" then return false end
|
|
46
|
+
return not (isNaN(vec.X) or isNaN(vec.Y) or isNaN(vec.Z)
|
|
47
|
+
or vec.X == _n or vec.Y == _n or vec.Z == _n
|
|
48
|
+
or vec.X == -_n or vec.Y == -_n or vec.Z == -_n)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
return V
|
|
52
|
+
end)()
|