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,228 @@
|
|
|
1
|
+
-- ── Pulse.Log + Pulse.Monitor ─────────────────────────────────────────────────
|
|
2
|
+
-- Structured dev logging with levels, tag filtering, throttling, and assertions.
|
|
3
|
+
-- Active only when _PULSE_DEV = true (injected by compiler in --dev builds).
|
|
4
|
+
-- In prod every call is a single boolean check — zero overhead.
|
|
5
|
+
--
|
|
6
|
+
-- Usage:
|
|
7
|
+
-- Pulse.Log.info("Aimbot", "locked", { name = t.Name, dist = d })
|
|
8
|
+
-- Pulse.Log.warn("globals", "folder missing")
|
|
9
|
+
-- Pulse.Log.error("Cutter", "pcall failed", { err = tostring(e) })
|
|
10
|
+
-- Pulse.Log.assert(x ~= nil, "Cutter", "x must not be nil here")
|
|
11
|
+
-- Pulse.Log.throttle("NapeSize", 5, "debug", "scanning " .. n .. " titans")
|
|
12
|
+
-- Pulse.Log.configure({ level="debug", file="dev.log", tags={"Aimbot"} })
|
|
13
|
+
--
|
|
14
|
+
-- Monitors (live key-value dashboard):
|
|
15
|
+
-- Pulse.Monitor.set("titans", 3)
|
|
16
|
+
-- Pulse.Monitor.getAll() → { titans=3, shifters=1, ... }
|
|
17
|
+
|
|
18
|
+
do
|
|
19
|
+
-- ── Shared state ─────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
local _active = (_PULSE_DEV == true)
|
|
22
|
+
local _minN = 2 -- 0=trace 1=debug 2=info 3=warn 4=error
|
|
23
|
+
local _tagWL = nil -- nil=all; table=whitelist
|
|
24
|
+
local _tagBL = nil -- blacklist (only when _tagWL is nil)
|
|
25
|
+
local _disabledTags = {} -- per-tag disable (runtime toggle)
|
|
26
|
+
local _console = true
|
|
27
|
+
local _file = nil
|
|
28
|
+
local _buf = {} -- ring buffer of structured entries
|
|
29
|
+
local _bufMax = 600
|
|
30
|
+
local _seq = 0 -- monotonic entry ID for GUI polling
|
|
31
|
+
local _timers = {} -- throttle: key → last tick
|
|
32
|
+
local _knownTags = {} -- set of all tags ever seen
|
|
33
|
+
local _subs = {} -- subscribers: fn(entry) — used by Pulse.Narrate etc.
|
|
34
|
+
|
|
35
|
+
local _N = { trace=0, debug=1, info=2, warn=3, error=4 }
|
|
36
|
+
local _LBL = { [0]="TRC", [1]="DBG", [2]="INF", [3]="WRN", [4]="ERR" }
|
|
37
|
+
local _HS = game:GetService("HttpService")
|
|
38
|
+
|
|
39
|
+
-- ── Helpers ───────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
local function _ts()
|
|
42
|
+
local ok, s = pcall(os.date, "%H:%M:%S")
|
|
43
|
+
return (ok and type(s) == "string") and s
|
|
44
|
+
or string.format("%d", math.floor(tick() % 86400))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
local function _fmtData(data)
|
|
48
|
+
if data == nil then return "" end
|
|
49
|
+
if type(data) ~= "table" then return " " .. tostring(data) end
|
|
50
|
+
local ok, s = pcall(function() return _HS:JSONEncode(data) end)
|
|
51
|
+
return ok and (" " .. s) or ""
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
local function _tagAllowed(tag)
|
|
55
|
+
if _disabledTags[tag] then return false end
|
|
56
|
+
if _tagWL then return _tagWL[tag] == true end
|
|
57
|
+
if _tagBL then return _tagBL[tag] ~= true end
|
|
58
|
+
return true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
local function _emit(tag, n, msg, data)
|
|
62
|
+
if not _active then return end
|
|
63
|
+
if n < _minN then return end
|
|
64
|
+
if not _tagAllowed(tag) then return end
|
|
65
|
+
|
|
66
|
+
_knownTags[tag] = true
|
|
67
|
+
local ts = _ts()
|
|
68
|
+
local line = string.format("[%s] %s [%s] %s%s", ts, _LBL[n], tag, tostring(msg), _fmtData(data))
|
|
69
|
+
|
|
70
|
+
_seq = _seq + 1
|
|
71
|
+
local entry = { id=_seq, ts=ts, n=n, lbl=_LBL[n], tag=tag, msg=tostring(msg), data=data, line=line }
|
|
72
|
+
_buf[#_buf + 1] = entry
|
|
73
|
+
if #_buf > _bufMax then table.remove(_buf, 1) end
|
|
74
|
+
|
|
75
|
+
if _console then
|
|
76
|
+
if n >= 3 then warn(line) else print(line) end
|
|
77
|
+
end
|
|
78
|
+
for _, fn in ipairs(_subs) do pcall(fn, entry) end
|
|
79
|
+
if _file then
|
|
80
|
+
pcall(function()
|
|
81
|
+
if type(rawget(_G, "appendfile")) == "function" then
|
|
82
|
+
appendfile(_file, line .. "\n")
|
|
83
|
+
end
|
|
84
|
+
end)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
-- ── Public API ────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
Pulse.Log = {}
|
|
91
|
+
|
|
92
|
+
-- Configure (call from rb/pulse/dev/devconfig.lua).
|
|
93
|
+
-- opts: level, console, file, tags (whitelist array), except (blacklist array)
|
|
94
|
+
function Pulse.Log.configure(opts)
|
|
95
|
+
if not _active then return end
|
|
96
|
+
if opts.level ~= nil then _minN = _N[opts.level] or _minN end
|
|
97
|
+
if opts.console ~= nil then _console = opts.console end
|
|
98
|
+
if opts.file ~= nil then _file = opts.file
|
|
99
|
+
-- Start fresh log file for this session.
|
|
100
|
+
if _file then
|
|
101
|
+
pcall(function()
|
|
102
|
+
if type(rawget(_G, "writefile")) == "function" then
|
|
103
|
+
writefile(_file, string.format("=== Pulse Dev %s ===\n", _ts()))
|
|
104
|
+
end
|
|
105
|
+
end)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
if opts.tags ~= nil then
|
|
109
|
+
_tagWL = {}; _tagBL = nil
|
|
110
|
+
for _, t in ipairs(opts.tags) do _tagWL[t] = true end
|
|
111
|
+
elseif opts.except ~= nil then
|
|
112
|
+
_tagBL = {}; _tagWL = nil
|
|
113
|
+
for _, t in ipairs(opts.except) do _tagBL[t] = true end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
-- Per-tag enable/disable at runtime (used by debug UI toggles).
|
|
118
|
+
function Pulse.Log.setTagEnabled(tag, enabled)
|
|
119
|
+
_disabledTags[tag] = not enabled
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
-- Returns a copy of all tags ever seen (for the debug UI tag list).
|
|
123
|
+
function Pulse.Log.getKnownTags()
|
|
124
|
+
local t = {}
|
|
125
|
+
for tag in pairs(_knownTags) do t[#t+1] = tag end
|
|
126
|
+
table.sort(t)
|
|
127
|
+
return t
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
-- Poll for entries newer than `afterId` (0 = all). Used by the debug GUI.
|
|
131
|
+
function Pulse.Log.getNewEntries(afterId)
|
|
132
|
+
local result = {}
|
|
133
|
+
for _, e in ipairs(_buf) do
|
|
134
|
+
if e.id > afterId then result[#result+1] = e end
|
|
135
|
+
end
|
|
136
|
+
return result
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
-- Log levels.
|
|
140
|
+
function Pulse.Log.trace(tag, msg, data) _emit(tag, 0, msg, data) end
|
|
141
|
+
function Pulse.Log.debug(tag, msg, data) _emit(tag, 1, msg, data) end
|
|
142
|
+
function Pulse.Log.info (tag, msg, data) _emit(tag, 2, msg, data) end
|
|
143
|
+
function Pulse.Log.warn (tag, msg, data) _emit(tag, 3, msg, data) end
|
|
144
|
+
function Pulse.Log.error(tag, msg, data) _emit(tag, 4, msg, data) end
|
|
145
|
+
|
|
146
|
+
-- Assertion — logs ERROR if condition is false; never throws.
|
|
147
|
+
function Pulse.Log.assert(cond, tag, msg, data)
|
|
148
|
+
if not cond then _emit(tag, 4, "ASSERT: " .. tostring(msg), data) end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
-- Throttled log — safe for Heartbeat loops; at most once per `interval` s.
|
|
152
|
+
function Pulse.Log.throttle(tag, interval, level, msg, data)
|
|
153
|
+
if not _active then return end
|
|
154
|
+
local key = tag .. "|" .. tostring(msg)
|
|
155
|
+
local now = tick()
|
|
156
|
+
if _timers[key] and (now - _timers[key]) < interval then return end
|
|
157
|
+
_timers[key] = now
|
|
158
|
+
_emit(tag, _N[level] or 2, msg, data)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
-- Dump a table as a debug entry.
|
|
162
|
+
function Pulse.Log.snapshot(tag, label, tbl)
|
|
163
|
+
_emit(tag, 1, "snapshot: " .. tostring(label), tbl)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
-- Subscribe to all emitted entries (used by Pulse.Narrate for auto-narration).
|
|
167
|
+
function Pulse.Log.subscribe(fn)
|
|
168
|
+
_subs[#_subs + 1] = fn
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
-- Watch a Signal for value changes — logs every time it fires.
|
|
172
|
+
-- Essential for "feature randomly stops working": you'll see exactly when
|
|
173
|
+
-- and to what value the signal was set.
|
|
174
|
+
-- Usage: Pulse.Log.watchSignal(Components.Aimbot.enabled, "aimbot", "enabled")
|
|
175
|
+
function Pulse.Log.watchSignal(signal, tag, name)
|
|
176
|
+
if not _active then return end
|
|
177
|
+
signal:onChange(function(v)
|
|
178
|
+
_emit(tag, 2, "signal change: " .. tostring(name or "?") .. " → " .. tostring(v))
|
|
179
|
+
end)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
-- Compat / utility.
|
|
183
|
+
function Pulse.Log.enable() _active = true end
|
|
184
|
+
function Pulse.Log.disable() _active = false end
|
|
185
|
+
function Pulse.Log.clear() _buf = {}; _timers = {}; _seq = 0 end
|
|
186
|
+
function Pulse.Log.dump() local t={}; for _,e in ipairs(_buf) do t[#t+1]=e.line end; return table.concat(t,"\n") end
|
|
187
|
+
function Pulse.Log.save(path)
|
|
188
|
+
path = path or "pulse_log.txt"
|
|
189
|
+
if type(rawget(_G, "writefile")) == "function" then
|
|
190
|
+
pcall(writefile, path, Pulse.Log.dump())
|
|
191
|
+
return true
|
|
192
|
+
end
|
|
193
|
+
return false
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
if _active then
|
|
197
|
+
print("[Pulse.Log] dev mode — configure via Pulse.Log.configure({...})")
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
-- ── Pulse.Monitor ─────────────────────────────────────────────────────────────
|
|
202
|
+
-- Lightweight live key-value store for the debug UI monitor bar.
|
|
203
|
+
-- Pulse.Monitor.set("titans", 3) anywhere; debug GUI reads it every frame.
|
|
204
|
+
|
|
205
|
+
do
|
|
206
|
+
local _vals = {}
|
|
207
|
+
local _subs = {}
|
|
208
|
+
|
|
209
|
+
Pulse.Monitor = {}
|
|
210
|
+
|
|
211
|
+
function Pulse.Monitor.set(key, value)
|
|
212
|
+
_vals[key] = value
|
|
213
|
+
for _, fn in ipairs(_subs) do pcall(fn, key, value) end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
-- Increment a named counter — use in loop bodies to prove execution.
|
|
217
|
+
-- Put Pulse.Monitor.tick("aimbot") inside a Heartbeat loop and watch
|
|
218
|
+
-- the monitor bar: if the count stops increasing, the loop stopped.
|
|
219
|
+
function Pulse.Monitor.tick(key)
|
|
220
|
+
local v = (_vals[key] or 0) + 1
|
|
221
|
+
_vals[key] = v
|
|
222
|
+
for _, fn in ipairs(_subs) do pcall(fn, key, v) end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
function Pulse.Monitor.get(key) return _vals[key] end
|
|
226
|
+
function Pulse.Monitor.getAll() local t={}; for k,v in pairs(_vals) do t[k]=v end; return t end
|
|
227
|
+
function Pulse.Monitor.subscribe(fn) _subs[#_subs+1] = fn end
|
|
228
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
-- ── Pulse.Loop ───────────────────────────────────────────────────────────────
|
|
2
|
+
-- A managed task loop with lifecycle logging and spam-safe error handling.
|
|
3
|
+
-- Usage:
|
|
4
|
+
-- local loop = Pulse.Loop.new(1.0, function() ... end, "Aimbot")
|
|
5
|
+
--
|
|
6
|
+
-- loop:start() -- begin (restarts if already running)
|
|
7
|
+
-- loop:stop() -- cancel (safe even if not running)
|
|
8
|
+
-- loop:running() → bool
|
|
9
|
+
-- loop:setInterval(n) -- change interval while running
|
|
10
|
+
|
|
11
|
+
Pulse.Loop = (function()
|
|
12
|
+
local L = {}
|
|
13
|
+
|
|
14
|
+
-- interval: seconds between ticks
|
|
15
|
+
-- fn: body; return false to self-stop
|
|
16
|
+
-- tag: log tag for this loop (optional, defaults to "loop")
|
|
17
|
+
function L.new(interval, fn, tag)
|
|
18
|
+
local _tag = tag or "loop"
|
|
19
|
+
local self = { _id = nil, _interval = interval, _fn = fn }
|
|
20
|
+
|
|
21
|
+
function self:start()
|
|
22
|
+
self:stop() -- stop previous run if any (silent — _id already nil after stop)
|
|
23
|
+
local id = {}
|
|
24
|
+
self._id = id
|
|
25
|
+
Pulse.Log.trace(_tag, "loop started", { interval = self._interval })
|
|
26
|
+
task.spawn(function()
|
|
27
|
+
while self._id == id do
|
|
28
|
+
local ok, result = pcall(self._fn)
|
|
29
|
+
if not ok then
|
|
30
|
+
-- Throttle to once every 5 s per unique error so loops can't spam.
|
|
31
|
+
Pulse.Log.throttle(_tag, 5, "warn", "loop error", { err = tostring(result) })
|
|
32
|
+
elseif result == false then
|
|
33
|
+
Pulse.Log.trace(_tag, "loop self-stopped")
|
|
34
|
+
break
|
|
35
|
+
end
|
|
36
|
+
if self._id == id then
|
|
37
|
+
task.wait(self._interval)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
if self._id == id then self._id = nil end
|
|
41
|
+
end)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
function self:stop()
|
|
45
|
+
if self._id ~= nil then
|
|
46
|
+
Pulse.Log.trace(_tag, "loop stopped")
|
|
47
|
+
end
|
|
48
|
+
self._id = nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
function self:running() return self._id ~= nil end
|
|
52
|
+
function self:setInterval(n) self._interval = n end
|
|
53
|
+
|
|
54
|
+
return self
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
return L
|
|
58
|
+
end)()
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
-- ── Pulse.Memory ─────────────────────────────────────────────────────────────
|
|
2
|
+
-- Executor capability checks and safe module resolution.
|
|
3
|
+
-- Usage:
|
|
4
|
+
-- if Pulse.Memory.supports("writefile") then Pulse.Log.save() end
|
|
5
|
+
-- local mem = Pulse.Memory.requireFrom("ODMG", "Client", "Memory")
|
|
6
|
+
|
|
7
|
+
Pulse.Memory = (function()
|
|
8
|
+
local M = {}
|
|
9
|
+
|
|
10
|
+
-- True if the named global function exists (e.g. "writefile", "readfile").
|
|
11
|
+
function M.supports(fnName)
|
|
12
|
+
return type(rawget(_G, fnName)) == "function"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
-- Test whether a table field is actually writable on this executor.
|
|
16
|
+
-- Returns true / false.
|
|
17
|
+
function M.testWritable(tbl, key)
|
|
18
|
+
if not tbl then return false end
|
|
19
|
+
local orig
|
|
20
|
+
if not pcall(function() orig = tbl[key] end) then return false end
|
|
21
|
+
local test = type(orig) == "number" and (orig + 9999.1) or 9999.1
|
|
22
|
+
if not pcall(function() tbl[key] = test end) then return false end
|
|
23
|
+
local readback
|
|
24
|
+
if not pcall(function() readback = tbl[key] end) then
|
|
25
|
+
pcall(function() tbl[key] = orig end); return false
|
|
26
|
+
end
|
|
27
|
+
pcall(function() tbl[key] = orig end)
|
|
28
|
+
return readback == test
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
-- Traverse child names from Player.Character and require() the final Instance.
|
|
32
|
+
-- Returns the module value or nil if any step fails.
|
|
33
|
+
function M.requireFrom(...)
|
|
34
|
+
local node = _LocalPlayer.Character
|
|
35
|
+
if not node then return nil end
|
|
36
|
+
for _, name in ipairs({...}) do
|
|
37
|
+
if not node then return nil end
|
|
38
|
+
local ok, child = pcall(function() return node:FindFirstChild(name) end)
|
|
39
|
+
if not ok or not child then return nil end
|
|
40
|
+
node = child
|
|
41
|
+
end
|
|
42
|
+
if typeof(node) ~= "Instance" then return nil end
|
|
43
|
+
local ok, result = pcall(require, node)
|
|
44
|
+
return ok and result or nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
return M
|
|
48
|
+
end)()
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
-- Pulse.Narrate — on-screen toast + sound narration for dev builds.
|
|
2
|
+
-- Active only when _PULSE_DEV = true. In prod every call is a no-op.
|
|
3
|
+
--
|
|
4
|
+
-- Usage:
|
|
5
|
+
-- Pulse.Narrate.say("locking titan", "warn") -- explicit toast
|
|
6
|
+
-- Pulse.Narrate.configure({ autoLevel = "warn", sound = true, volume = 0.5 })
|
|
7
|
+
-- -- After configure(), Pulse.Log entries at or above autoLevel are auto-narrated.
|
|
8
|
+
|
|
9
|
+
do
|
|
10
|
+
local _active = (_PULSE_DEV == true)
|
|
11
|
+
local _sound = true
|
|
12
|
+
local _volume = 0.5
|
|
13
|
+
local _autoN = 3 -- auto-narrate warn (3) and error (4) by default
|
|
14
|
+
local _N = { trace=0, debug=1, info=2, warn=3, error=4 }
|
|
15
|
+
|
|
16
|
+
-- Level colors matching the log palette
|
|
17
|
+
local _colors = {
|
|
18
|
+
[0] = Color3.fromRGB(72, 72, 90),
|
|
19
|
+
[1] = Color3.fromRGB(70, 175, 155),
|
|
20
|
+
[2] = Color3.fromRGB(170, 195, 220),
|
|
21
|
+
[3] = Color3.fromRGB(215, 165, 45),
|
|
22
|
+
[4] = Color3.fromRGB(215, 65, 65),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
-- ── Lazy GUI (created on first call) ─────────────────────────────────────
|
|
26
|
+
local _gui = nil
|
|
27
|
+
local _frame = nil
|
|
28
|
+
local _label = nil
|
|
29
|
+
local _TS = nil
|
|
30
|
+
pcall(function() _TS = game:GetService("TweenService") end)
|
|
31
|
+
|
|
32
|
+
local function _ensureGui()
|
|
33
|
+
if _gui then return end
|
|
34
|
+
local parent
|
|
35
|
+
pcall(function() parent = game:GetService("CoreGui") end)
|
|
36
|
+
if not parent then
|
|
37
|
+
pcall(function() parent = game:GetService("Players").LocalPlayer.PlayerGui end)
|
|
38
|
+
end
|
|
39
|
+
if not parent then return end
|
|
40
|
+
|
|
41
|
+
_gui = Instance.new("ScreenGui")
|
|
42
|
+
_gui.Name = "PulseNarrate"
|
|
43
|
+
_gui.ResetOnSpawn = false
|
|
44
|
+
_gui.IgnoreGuiInset = true
|
|
45
|
+
_gui.DisplayOrder = 1001
|
|
46
|
+
pcall(function() _gui.Parent = parent end)
|
|
47
|
+
|
|
48
|
+
_frame = Instance.new("Frame")
|
|
49
|
+
_frame.Name = "Toast"
|
|
50
|
+
_frame.Size = UDim2.new(0, 520, 0, 52)
|
|
51
|
+
_frame.Position = UDim2.new(0.5, -260, 0, 110)
|
|
52
|
+
_frame.BackgroundColor3 = Color3.fromRGB(10, 10, 18)
|
|
53
|
+
_frame.BackgroundTransparency = 0.15
|
|
54
|
+
_frame.BorderSizePixel = 0
|
|
55
|
+
_frame.Visible = false
|
|
56
|
+
_frame.Parent = _gui
|
|
57
|
+
Instance.new("UICorner", _frame).CornerRadius = UDim.new(0, 8)
|
|
58
|
+
local stroke = Instance.new("UIStroke", _frame)
|
|
59
|
+
stroke.Color = Color3.fromRGB(60, 60, 90)
|
|
60
|
+
stroke.Thickness = 1
|
|
61
|
+
|
|
62
|
+
_label = Instance.new("TextLabel")
|
|
63
|
+
_label.Size = UDim2.new(1, -20, 1, 0)
|
|
64
|
+
_label.Position = UDim2.new(0, 10, 0, 0)
|
|
65
|
+
_label.BackgroundTransparency = 1
|
|
66
|
+
_label.Font = Enum.Font.GothamBold
|
|
67
|
+
_label.TextSize = 15
|
|
68
|
+
_label.TextColor3 = Color3.fromRGB(200, 200, 220)
|
|
69
|
+
_label.TextXAlignment = Enum.TextXAlignment.Center
|
|
70
|
+
_label.TextYAlignment = Enum.TextYAlignment.Center
|
|
71
|
+
_label.TextWrapped = true
|
|
72
|
+
_label.RichText = true
|
|
73
|
+
_label.Text = ""
|
|
74
|
+
_label.Parent = _frame
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
-- ── Sound ─────────────────────────────────────────────────────────────────
|
|
78
|
+
local _snd = nil
|
|
79
|
+
local _pitches = { [0]=0.5, [1]=0.7, [2]=1.0, [3]=1.35, [4]=1.7 }
|
|
80
|
+
|
|
81
|
+
local function _playSound(n)
|
|
82
|
+
if not _sound then return end
|
|
83
|
+
pcall(function()
|
|
84
|
+
if not _snd then
|
|
85
|
+
_snd = Instance.new("Sound")
|
|
86
|
+
_snd.SoundId = "rbxasset://sounds/electronicpingshort.wav"
|
|
87
|
+
_snd.Parent = game:GetService("SoundService")
|
|
88
|
+
end
|
|
89
|
+
_snd.Volume = _volume
|
|
90
|
+
_snd.PlaybackSpeed = _pitches[n] or 1.0
|
|
91
|
+
_snd:Play()
|
|
92
|
+
end)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
-- ── Toast display ─────────────────────────────────────────────────────────
|
|
96
|
+
local _toastActive = false
|
|
97
|
+
|
|
98
|
+
local function _showToast(msg, n)
|
|
99
|
+
_ensureGui()
|
|
100
|
+
if not (_frame and _frame.Parent) then return end
|
|
101
|
+
local color = _colors[n] or _colors[2]
|
|
102
|
+
|
|
103
|
+
_label.Text = tostring(msg)
|
|
104
|
+
_label.TextColor3 = color
|
|
105
|
+
_label.TextTransparency = 0
|
|
106
|
+
_frame.BackgroundTransparency = 0.15
|
|
107
|
+
_frame.Visible = true
|
|
108
|
+
_toastActive = true
|
|
109
|
+
|
|
110
|
+
-- Fade out after 2.8 s
|
|
111
|
+
task.delay(2.3, function()
|
|
112
|
+
if not (_frame and _frame.Parent) then return end
|
|
113
|
+
if _TS then
|
|
114
|
+
local ti = TweenInfo.new(0.5, Enum.EasingStyle.Quad, Enum.EasingDirection.In)
|
|
115
|
+
_TS:Create(_frame, ti, { BackgroundTransparency = 1 }):Play()
|
|
116
|
+
_TS:Create(_label, ti, { TextTransparency = 1 }):Play()
|
|
117
|
+
task.wait(0.55)
|
|
118
|
+
end
|
|
119
|
+
if _frame and _frame.Parent then
|
|
120
|
+
_frame.Visible = false
|
|
121
|
+
_toastActive = false
|
|
122
|
+
if _label then _label.TextTransparency = 0 end
|
|
123
|
+
end
|
|
124
|
+
end)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
-- ── Public API ────────────────────────────────────────────────────────────
|
|
128
|
+
|
|
129
|
+
Pulse.Narrate = {}
|
|
130
|
+
|
|
131
|
+
function Pulse.Narrate.configure(opts)
|
|
132
|
+
if not _active then return end
|
|
133
|
+
if opts.sound ~= nil then _sound = opts.sound end
|
|
134
|
+
if opts.volume ~= nil then _volume = opts.volume end
|
|
135
|
+
if opts.autoLevel ~= nil then
|
|
136
|
+
_autoN = _N[opts.autoLevel] or _autoN
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
-- Explicitly announce a message.
|
|
141
|
+
-- levelName: "trace" | "debug" | "info" | "warn" | "error" (default "info")
|
|
142
|
+
function Pulse.Narrate.say(msg, levelName)
|
|
143
|
+
if not _active then return end
|
|
144
|
+
local n = _N[levelName] or 2
|
|
145
|
+
local prefix = levelName and ("[" .. levelName:upper():sub(1,3) .. "] ") or ""
|
|
146
|
+
_showToast(prefix .. tostring(msg), n)
|
|
147
|
+
_playSound(n)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
-- Subscribe to Pulse.Log so entries at/above autoLevel get auto-narrated.
|
|
151
|
+
-- This runs immediately after narrate.lua is loaded (Pulse.Log is already defined).
|
|
152
|
+
if _active and Pulse.Log and Pulse.Log.subscribe then
|
|
153
|
+
Pulse.Log.subscribe(function(e)
|
|
154
|
+
if e.n >= _autoN then
|
|
155
|
+
_showToast(e.tag .. ": " .. e.msg, e.n)
|
|
156
|
+
_playSound(e.n)
|
|
157
|
+
end
|
|
158
|
+
end)
|
|
159
|
+
end
|
|
160
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
-- ── Pulse.Notify ─────────────────────────────────────────────────────────────
|
|
2
|
+
-- In-game notification helpers.
|
|
3
|
+
--
|
|
4
|
+
-- Instant toast:
|
|
5
|
+
-- Pulse.Notify("Aimbot locked on target")
|
|
6
|
+
-- Pulse.Notify("Speed boost active", 5) -- 5-second toast
|
|
7
|
+
--
|
|
8
|
+
-- Auto-notify when a boolean signal changes:
|
|
9
|
+
-- Pulse.Notify.onToggle(self.enabled, "Aimbot")
|
|
10
|
+
-- -- fires "Aimbot on" when signal → true, "Aimbot off" when → false
|
|
11
|
+
--
|
|
12
|
+
-- Pulse.Notify.onToggle(self.enabled, "AutoFarm", {
|
|
13
|
+
-- on = "AutoFarm started", -- custom on message
|
|
14
|
+
-- off = "AutoFarm stopped", -- custom off message
|
|
15
|
+
-- duration = 5, -- toast duration in seconds (default 3)
|
|
16
|
+
-- notify_on = true, -- set false to skip the ON notification
|
|
17
|
+
-- notify_off = true, -- set false to skip the OFF notification
|
|
18
|
+
-- })
|
|
19
|
+
--
|
|
20
|
+
-- Returns the unsubscribe function so you can stop watching:
|
|
21
|
+
-- local stop = Pulse.Notify.onToggle(sig, "Name")
|
|
22
|
+
-- stop() -- disconnect
|
|
23
|
+
|
|
24
|
+
do
|
|
25
|
+
Pulse.Notify = {}
|
|
26
|
+
|
|
27
|
+
-- Make Pulse.Notify(...) callable as a direct shorthand for _PulseNotify.
|
|
28
|
+
setmetatable(Pulse.Notify, {
|
|
29
|
+
__call = function(_, msg, duration)
|
|
30
|
+
pcall(_PulseNotify, tostring(msg), duration or 3)
|
|
31
|
+
end,
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
-- Watch a boolean signal and fire a toast each time it changes.
|
|
35
|
+
-- Returns the unsubscribe function (useful inside component:watch).
|
|
36
|
+
function Pulse.Notify.onToggle(signal, name, opts)
|
|
37
|
+
opts = opts or {}
|
|
38
|
+
local dur = opts.duration or 3
|
|
39
|
+
return signal:onChange(function(v)
|
|
40
|
+
if v then
|
|
41
|
+
if opts.notify_on ~= false then
|
|
42
|
+
pcall(_PulseNotify, opts.on or (name .. " on"), dur)
|
|
43
|
+
end
|
|
44
|
+
else
|
|
45
|
+
if opts.notify_off ~= false then
|
|
46
|
+
pcall(_PulseNotify, opts.off or (name .. " off"), dur)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
-- ── Pulse.Perf ───────────────────────────────────────────────────────────────
|
|
2
|
+
-- Lightweight frame-time sampler with rolling average and optional warn threshold.
|
|
3
|
+
-- Usage:
|
|
4
|
+
-- Pulse.Perf.tick() -- call once per frame to sample
|
|
5
|
+
-- Pulse.Perf.avg() -- average frame time in seconds
|
|
6
|
+
-- Pulse.Perf.fps() -- estimated FPS (1 / avg)
|
|
7
|
+
-- Pulse.Perf.enabled -- false when auto-disabled on error
|
|
8
|
+
-- Pulse.Perf.setThreshold(ms) -- warn when avg > ms (default 50 ms)
|
|
9
|
+
|
|
10
|
+
Pulse.Perf = (function()
|
|
11
|
+
local P = {}
|
|
12
|
+
P.enabled = true
|
|
13
|
+
P._buf = {}
|
|
14
|
+
P._max = 60
|
|
15
|
+
P._last = 0
|
|
16
|
+
P._avg = 0
|
|
17
|
+
P._thresh = 0.05 -- 50 ms
|
|
18
|
+
P._lastWarn = 0
|
|
19
|
+
P._warnCD = 5
|
|
20
|
+
|
|
21
|
+
function P.tick()
|
|
22
|
+
if not P.enabled then return end
|
|
23
|
+
local ok = pcall(function()
|
|
24
|
+
local now = tick()
|
|
25
|
+
if P._last > 0 then
|
|
26
|
+
local ft = now - P._last
|
|
27
|
+
P._buf[#P._buf + 1] = ft
|
|
28
|
+
if #P._buf > P._max then table.remove(P._buf, 1) end
|
|
29
|
+
local total = 0
|
|
30
|
+
for i = 1, #P._buf do total = total + P._buf[i] end
|
|
31
|
+
P._avg = total / #P._buf
|
|
32
|
+
if P._thresh > 0 and P._avg > P._thresh
|
|
33
|
+
and now - P._lastWarn > P._warnCD then
|
|
34
|
+
warn("[Pulse.Perf] avg frame time: " .. math.floor(P._avg * 1000) .. " ms")
|
|
35
|
+
P._lastWarn = now
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
P._last = now
|
|
39
|
+
end)
|
|
40
|
+
if not ok then P.enabled = false end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
function P.avg() return P._avg end
|
|
44
|
+
function P.fps() return P._avg > 0 and (1 / P._avg) or 0 end
|
|
45
|
+
function P.setThreshold(ms) P._thresh = ms / 1000 end
|
|
46
|
+
|
|
47
|
+
return P
|
|
48
|
+
end)()
|