pulse-rb 1.4.3 → 1.4.4
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/adapters/windui.lua +218 -4
- package/dist/index.js +64 -4
- package/package.json +1 -1
- package/pulse/runtime.lua +1 -0
- package/templates/component_aimbot.ts +18 -0
- package/templates/layout.ts +35 -2
- package/templates/page_combat.ts +9 -0
package/adapters/windui.lua
CHANGED
|
@@ -59,6 +59,7 @@ local _toggleKeyName = "RightControl"
|
|
|
59
59
|
local _windowVisible = true
|
|
60
60
|
|
|
61
61
|
local _isMobile = _UIS.TouchEnabled and not _UIS.KeyboardEnabled
|
|
62
|
+
local _PULSE_PREMIUM = false -- set true during key validation if key grants premium
|
|
62
63
|
|
|
63
64
|
-- ── Adapter ───────────────────────────────────────────────────────────────────
|
|
64
65
|
|
|
@@ -140,7 +141,7 @@ function _UIAdapter:CreateWindow(title, w, h, opts)
|
|
|
140
141
|
local openBtnMobileOnly = opts.open_btn_mobile_only ~= false
|
|
141
142
|
local openBtnIcon = (opts.open_btn_icon and opts.open_btn_icon ~= "") and opts.open_btn_icon or nil
|
|
142
143
|
|
|
143
|
-
|
|
144
|
+
local _winCfg = {
|
|
144
145
|
Title = title,
|
|
145
146
|
Icon = icon,
|
|
146
147
|
Author = author,
|
|
@@ -151,7 +152,6 @@ function _UIAdapter:CreateWindow(title, w, h, opts)
|
|
|
151
152
|
Transparent = acrylic or (transparency > 0),
|
|
152
153
|
BackgroundImageTransparency = acrylic and 0 or transparency,
|
|
153
154
|
Acrylic = acrylic,
|
|
154
|
-
|
|
155
155
|
OpenButton = {
|
|
156
156
|
Enabled = true,
|
|
157
157
|
Draggable = true,
|
|
@@ -161,7 +161,62 @@ function _UIAdapter:CreateWindow(title, w, h, opts)
|
|
|
161
161
|
CornerRadius = UDim.new(1, 0),
|
|
162
162
|
StrokeThickness = 3,
|
|
163
163
|
},
|
|
164
|
-
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
-- Key system — configured via layout.keySystem
|
|
167
|
+
local _ks = _PULSE_LAYOUT.keySystem
|
|
168
|
+
if _ks then
|
|
169
|
+
local function _checkPremium(key)
|
|
170
|
+
local _pr = _PULSE_LAYOUT.premium
|
|
171
|
+
if not _pr then return end
|
|
172
|
+
if _pr.keys then
|
|
173
|
+
for _, k in ipairs(_pr.keys) do
|
|
174
|
+
if k == key then _PULSE_PREMIUM = true; return end
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
if _pr.validatorUrl and _pr.validatorUrl ~= "" then
|
|
178
|
+
local ok, resp = pcall(function() return game:HttpGet(_pr.validatorUrl .. key) end)
|
|
179
|
+
if ok and resp and resp:lower():match("^true") then _PULSE_PREMIUM = true end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
local _ksCfg = {
|
|
183
|
+
Title = _ks.title or "Key Required",
|
|
184
|
+
Note = (_ks.note and _ks.note ~= "") and _ks.note or nil,
|
|
185
|
+
SaveKey = _ks.saveKey == true,
|
|
186
|
+
URL = (_ks.getKeyUrl and _ks.getKeyUrl ~= "") and _ks.getKeyUrl or nil,
|
|
187
|
+
KeyValidator = function(key)
|
|
188
|
+
local passed = false
|
|
189
|
+
-- Check against static keys list
|
|
190
|
+
if _ks.keys then
|
|
191
|
+
for _, k in ipairs(_ks.keys) do
|
|
192
|
+
if k == key then passed = true; break end
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
-- HTTP validation (server-side) when no static list or key not found
|
|
196
|
+
if not passed and _ks.validatorUrl and _ks.validatorUrl ~= "" then
|
|
197
|
+
local ok, resp = pcall(function() return game:HttpGet(_ks.validatorUrl .. key) end)
|
|
198
|
+
if ok and resp and resp:lower():match("^true") then passed = true end
|
|
199
|
+
end
|
|
200
|
+
-- No validation configured → any non-empty key passes (dev mode)
|
|
201
|
+
if not passed and (not _ks.keys or #_ks.keys == 0)
|
|
202
|
+
and (not _ks.validatorUrl or _ks.validatorUrl == "") then
|
|
203
|
+
passed = key ~= nil and key ~= ""
|
|
204
|
+
end
|
|
205
|
+
_checkPremium(key)
|
|
206
|
+
return passed or _PULSE_PREMIUM -- premium key also grants basic access
|
|
207
|
+
end,
|
|
208
|
+
}
|
|
209
|
+
if _ks.thumbnail then
|
|
210
|
+
_ksCfg.Thumbnail = {
|
|
211
|
+
Image = _ks.thumbnail.image or "",
|
|
212
|
+
Title = _ks.thumbnail.title or "",
|
|
213
|
+
Width = _ks.thumbnail.width or 100,
|
|
214
|
+
}
|
|
215
|
+
end
|
|
216
|
+
_winCfg.KeySystem = _ksCfg
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
_windWindow = WindUI:CreateWindow(_winCfg)
|
|
165
220
|
|
|
166
221
|
pcall(function()
|
|
167
222
|
_windConfig = _windWindow.ConfigManager:Config(_folder)
|
|
@@ -552,6 +607,28 @@ task.spawn(function()
|
|
|
552
607
|
|
|
553
608
|
local L = _PULSE_LAYOUT
|
|
554
609
|
|
|
610
|
+
-- Premium dynamic unlock system
|
|
611
|
+
-- Each entry is a function that destroys the locked UI and mounts the real component.
|
|
612
|
+
local _premiumPendingUnlocks = {}
|
|
613
|
+
local _premiumStatusPara = nil
|
|
614
|
+
|
|
615
|
+
local function _activatePremium()
|
|
616
|
+
_PULSE_PREMIUM = true
|
|
617
|
+
-- Update settings status paragraph if it exists
|
|
618
|
+
pcall(function()
|
|
619
|
+
if _premiumStatusPara then
|
|
620
|
+
_premiumStatusPara:SetTitle("Premium Active")
|
|
621
|
+
_premiumStatusPara:SetDesc("All premium features are now unlocked.")
|
|
622
|
+
end
|
|
623
|
+
end)
|
|
624
|
+
-- Swap locked UI → real components for every registered premium groupbox
|
|
625
|
+
for _, fn in ipairs(_premiumPendingUnlocks) do
|
|
626
|
+
task.spawn(function() pcall(fn) end)
|
|
627
|
+
end
|
|
628
|
+
_premiumPendingUnlocks = {}
|
|
629
|
+
pcall(function() _PulseNotify("Premium unlocked!", 3) end)
|
|
630
|
+
end
|
|
631
|
+
|
|
555
632
|
-- Resolve toggle key
|
|
556
633
|
local _tkName = (L.toggleKey and L.toggleKey ~= "") and L.toggleKey or "RightControl"
|
|
557
634
|
local _tk = pcall(function() return Enum.KeyCode[_tkName] end) and Enum.KeyCode[_tkName]
|
|
@@ -778,7 +855,107 @@ task.spawn(function()
|
|
|
778
855
|
end
|
|
779
856
|
if gb.mount then
|
|
780
857
|
local comp = _BundleComponents[gb.mount]
|
|
781
|
-
|
|
858
|
+
-- Premium gate: show inline key-entry UI; dynamically replaced on unlock
|
|
859
|
+
if gb.premium and not _PULSE_PREMIUM then
|
|
860
|
+
local _mountComp = comp
|
|
861
|
+
local _mountCont = container
|
|
862
|
+
local _destroyFns = {}
|
|
863
|
+
|
|
864
|
+
pcall(function()
|
|
865
|
+
local _keyValue = ""
|
|
866
|
+
local _statusPara
|
|
867
|
+
|
|
868
|
+
local function _setStatus(msg)
|
|
869
|
+
pcall(function() if _statusPara then _statusPara:SetDesc(msg) end end)
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
-- Header
|
|
873
|
+
local _header = _mountCont._container:Paragraph({
|
|
874
|
+
Title = "Premium Feature",
|
|
875
|
+
Desc = "Enter your key below to unlock this feature.",
|
|
876
|
+
})
|
|
877
|
+
table.insert(_destroyFns, function() pcall(function() _header:Destroy() end) end)
|
|
878
|
+
|
|
879
|
+
-- Key input (wrapped in pcall — Input may not exist on all Section types)
|
|
880
|
+
local _inputElem
|
|
881
|
+
pcall(function()
|
|
882
|
+
_inputElem = _mountCont._container:Input({
|
|
883
|
+
Title = "Premium Key",
|
|
884
|
+
Placeholder = "Paste your key here…",
|
|
885
|
+
Flag = (gb.mount or "prem") .. "_premKey",
|
|
886
|
+
Callback = function(v) _keyValue = v end,
|
|
887
|
+
})
|
|
888
|
+
table.insert(_destroyFns, function() pcall(function() _inputElem:Destroy() end) end)
|
|
889
|
+
end)
|
|
890
|
+
|
|
891
|
+
-- Validate + unlock
|
|
892
|
+
local function _doCheck()
|
|
893
|
+
local key = (_keyValue or ""):match("^%s*(.-)%s*$")
|
|
894
|
+
if key == "" then _setStatus("Enter a key first."); return end
|
|
895
|
+
local valid = false
|
|
896
|
+
local _pr = _PULSE_LAYOUT.premium
|
|
897
|
+
if _pr then
|
|
898
|
+
if _pr.keys then
|
|
899
|
+
for _, k in ipairs(_pr.keys) do
|
|
900
|
+
if k == key then valid = true; break end
|
|
901
|
+
end
|
|
902
|
+
end
|
|
903
|
+
if not valid and _pr.validatorUrl and _pr.validatorUrl ~= "" then
|
|
904
|
+
_setStatus("Checking…")
|
|
905
|
+
local ok, resp = pcall(function()
|
|
906
|
+
return game:HttpGet(_pr.validatorUrl .. key)
|
|
907
|
+
end)
|
|
908
|
+
if ok and resp and resp:lower():match("^true") then valid = true end
|
|
909
|
+
end
|
|
910
|
+
end
|
|
911
|
+
if valid then
|
|
912
|
+
_setStatus("Valid! Unlocking…")
|
|
913
|
+
task.delay(0.4, _activatePremium)
|
|
914
|
+
else
|
|
915
|
+
_setStatus("Invalid key.")
|
|
916
|
+
end
|
|
917
|
+
end
|
|
918
|
+
|
|
919
|
+
-- "Copy Link" button
|
|
920
|
+
local _pKeyUrl = (_PULSE_LAYOUT.premium and _PULSE_LAYOUT.premium.getKeyUrl)
|
|
921
|
+
or (_PULSE_LAYOUT.keySystem and _PULSE_LAYOUT.keySystem.getKeyUrl) or ""
|
|
922
|
+
local _copyBtn = _mountCont._container:Button({
|
|
923
|
+
Title = "Copy Link",
|
|
924
|
+
Desc = "Get your premium key",
|
|
925
|
+
Callback = function()
|
|
926
|
+
if _pKeyUrl == "" then _setStatus("No link configured."); return end
|
|
927
|
+
local ok = false
|
|
928
|
+
pcall(function() setclipboard(_pKeyUrl); ok = true end)
|
|
929
|
+
pcall(function() if not ok then toclipboard(_pKeyUrl); ok = true end end)
|
|
930
|
+
_setStatus(ok and "Link copied!" or "No clipboard API.")
|
|
931
|
+
end,
|
|
932
|
+
})
|
|
933
|
+
table.insert(_destroyFns, function() pcall(function() _copyBtn:Destroy() end) end)
|
|
934
|
+
|
|
935
|
+
-- "Check Key" button
|
|
936
|
+
local _checkBtn = _mountCont._container:Button({
|
|
937
|
+
Title = "Check Key",
|
|
938
|
+
Callback = _doCheck,
|
|
939
|
+
})
|
|
940
|
+
table.insert(_destroyFns, function() pcall(function() _checkBtn:Destroy() end) end)
|
|
941
|
+
|
|
942
|
+
-- Status line
|
|
943
|
+
_statusPara = _mountCont._container:Paragraph({
|
|
944
|
+
Title = "Status",
|
|
945
|
+
Desc = "—",
|
|
946
|
+
})
|
|
947
|
+
table.insert(_destroyFns, function() pcall(function() _statusPara:Destroy() end) end)
|
|
948
|
+
end)
|
|
949
|
+
|
|
950
|
+
-- Register: when premium activates, destroy locked UI and mount real component
|
|
951
|
+
table.insert(_premiumPendingUnlocks, function()
|
|
952
|
+
for _, fn in ipairs(_destroyFns) do fn() end
|
|
953
|
+
task.wait(0.05)
|
|
954
|
+
if _mountComp then
|
|
955
|
+
pcall(function() _UIAdapter:mount(_mountComp, _mountCont) end)
|
|
956
|
+
end
|
|
957
|
+
end)
|
|
958
|
+
elseif comp then
|
|
782
959
|
local ok, err = pcall(function() _UIAdapter:mount(comp, container) end)
|
|
783
960
|
if not ok then
|
|
784
961
|
warn("[Pulse] mount '" .. gb.mount .. "' error: " .. tostring(err))
|
|
@@ -852,6 +1029,43 @@ task.spawn(function()
|
|
|
852
1029
|
end,
|
|
853
1030
|
})
|
|
854
1031
|
|
|
1032
|
+
-- Premium status groupbox — shown only when premium tier is configured
|
|
1033
|
+
if _PULSE_LAYOUT.premium then
|
|
1034
|
+
local gb_prem = _settingsTab:AddRightGroupbox("Premium")
|
|
1035
|
+
if _PULSE_PREMIUM then
|
|
1036
|
+
pcall(function()
|
|
1037
|
+
gb_prem._container:Paragraph({
|
|
1038
|
+
Title = "Premium Active",
|
|
1039
|
+
Desc = "All premium features are unlocked.",
|
|
1040
|
+
})
|
|
1041
|
+
end)
|
|
1042
|
+
else
|
|
1043
|
+
pcall(function()
|
|
1044
|
+
-- Store reference so _activatePremium can update it live
|
|
1045
|
+
_premiumStatusPara = gb_prem._container:Paragraph({
|
|
1046
|
+
Title = "Premium Locked",
|
|
1047
|
+
Desc = "Enter a key in any locked feature to unlock.",
|
|
1048
|
+
})
|
|
1049
|
+
end)
|
|
1050
|
+
local _pKeyUrl = (_PULSE_LAYOUT.premium and _PULSE_LAYOUT.premium.getKeyUrl)
|
|
1051
|
+
or (_PULSE_LAYOUT.keySystem and _PULSE_LAYOUT.keySystem.getKeyUrl)
|
|
1052
|
+
if _pKeyUrl and _pKeyUrl ~= "" then
|
|
1053
|
+
pcall(function()
|
|
1054
|
+
gb_prem._container:Button({
|
|
1055
|
+
Title = "Get Premium Key",
|
|
1056
|
+
Desc = "Copy the link to your key page",
|
|
1057
|
+
Callback = function()
|
|
1058
|
+
local ok = false
|
|
1059
|
+
pcall(function() setclipboard(_pKeyUrl); ok = true end)
|
|
1060
|
+
pcall(function() if not ok then toclipboard(_pKeyUrl); ok = true end end)
|
|
1061
|
+
_PulseNotify(ok and "Link copied!" or _pKeyUrl, 4)
|
|
1062
|
+
end,
|
|
1063
|
+
})
|
|
1064
|
+
end)
|
|
1065
|
+
end
|
|
1066
|
+
end
|
|
1067
|
+
end
|
|
1068
|
+
|
|
855
1069
|
-- Menu settings groupbox
|
|
856
1070
|
local gb_menu = _settingsTab:AddRightGroupbox("Menu Settings")
|
|
857
1071
|
_UIAdapter:addButton(gb_menu, {
|
package/dist/index.js
CHANGED
|
@@ -56,7 +56,7 @@ var RB_VERSION, RB_HOME, IRONBREW2_DIR, IRONBREW2_REPO, ALWAYS_EXCLUDE, VALID_UI
|
|
|
56
56
|
var init_constants = __esm({
|
|
57
57
|
"src/constants.ts"() {
|
|
58
58
|
init_cjs_shims();
|
|
59
|
-
RB_VERSION = "1.4.
|
|
59
|
+
RB_VERSION = "1.4.4";
|
|
60
60
|
RB_HOME = path.join(os.homedir(), ".rb");
|
|
61
61
|
path.join(RB_HOME, "bin");
|
|
62
62
|
IRONBREW2_DIR = path.join(RB_HOME, "ironbrew2");
|
|
@@ -1156,6 +1156,52 @@ _G.__AOT_R_DESTROY = _PulseDestroy
|
|
|
1156
1156
|
}
|
|
1157
1157
|
return "windui";
|
|
1158
1158
|
}
|
|
1159
|
+
// Extract content between matching braces for a named TS field.
|
|
1160
|
+
// Returns null if field is absent or commented out.
|
|
1161
|
+
extractLayoutBlock(src, field) {
|
|
1162
|
+
const rx = new RegExp(`\\b${field}\\s*:\\s*\\{`);
|
|
1163
|
+
const m = src.match(rx);
|
|
1164
|
+
if (!m || m.index === void 0) return null;
|
|
1165
|
+
const before = src.slice(0, m.index);
|
|
1166
|
+
const lineStart = before.lastIndexOf("\n") + 1;
|
|
1167
|
+
if (before.slice(lineStart).trimStart().startsWith("//")) return null;
|
|
1168
|
+
let depth = 0, i = m.index + m[0].length - 1;
|
|
1169
|
+
const start = i + 1;
|
|
1170
|
+
for (; i < src.length; i++) {
|
|
1171
|
+
if (src[i] === "{") depth++;
|
|
1172
|
+
else if (src[i] === "}") {
|
|
1173
|
+
depth--;
|
|
1174
|
+
if (depth === 0) break;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
return src.slice(start, i);
|
|
1178
|
+
}
|
|
1179
|
+
// Convert an extracted TS block body to a Lua table literal string.
|
|
1180
|
+
blockToLuaTable(block) {
|
|
1181
|
+
const str = (k) => block.match(new RegExp(`\\b${k}\\s*:\\s*['"]([^'"]+)['"]`))?.at(1) ?? null;
|
|
1182
|
+
const bool = (k) => {
|
|
1183
|
+
const m = block.match(new RegExp(`\\b${k}\\s*:\\s*(true|false)\\b`));
|
|
1184
|
+
return m ? m[1] === "true" : null;
|
|
1185
|
+
};
|
|
1186
|
+
const arr = (k) => {
|
|
1187
|
+
const m = block.match(new RegExp(`\\b${k}\\s*:\\s*\\[([^\\]]*)\\]`));
|
|
1188
|
+
return m ? [...m[1].matchAll(/['"]([^'"]+)['"]/g)].map((x) => x[1]) : null;
|
|
1189
|
+
};
|
|
1190
|
+
const parts = [];
|
|
1191
|
+
const title = str("title");
|
|
1192
|
+
if (title !== null) parts.push(`title=${JSON.stringify(title)}`);
|
|
1193
|
+
const note = str("note");
|
|
1194
|
+
if (note !== null) parts.push(`note=${JSON.stringify(note)}`);
|
|
1195
|
+
const save = bool("saveKey");
|
|
1196
|
+
if (save !== null) parts.push(`saveKey=${save}`);
|
|
1197
|
+
const gkUrl = str("getKeyUrl");
|
|
1198
|
+
if (gkUrl !== null) parts.push(`getKeyUrl=${JSON.stringify(gkUrl)}`);
|
|
1199
|
+
const valUrl = str("validatorUrl");
|
|
1200
|
+
if (valUrl !== null) parts.push(`validatorUrl=${JSON.stringify(valUrl)}`);
|
|
1201
|
+
const keys = arr("keys");
|
|
1202
|
+
if (keys && keys.length) parts.push(`keys={${keys.map((k) => JSON.stringify(k)).join(",")}}`);
|
|
1203
|
+
return parts.length ? `{${parts.join(",")}}` : null;
|
|
1204
|
+
}
|
|
1159
1205
|
readLayoutConfig() {
|
|
1160
1206
|
const layoutTs = path.join(this.srcDir, "layout.ts");
|
|
1161
1207
|
const cfg = {
|
|
@@ -1324,6 +1370,17 @@ _G.__AOT_R_DESTROY = _PulseDestroy
|
|
|
1324
1370
|
if (lc["discord"]) parts.push(`,discord=${JSON.stringify(lc["discord"])}`);
|
|
1325
1371
|
if (lc["version"]) parts.push(`,version=${JSON.stringify(lc["version"])}`);
|
|
1326
1372
|
if (opts.dev) parts.push(`,dev=true`);
|
|
1373
|
+
const layoutSrc = fs.existsSync(path.join(this.srcDir, "layout.ts")) ? fs.readFileSync(path.join(this.srcDir, "layout.ts"), "utf8") : "";
|
|
1374
|
+
const ksBlock = this.extractLayoutBlock(layoutSrc, "keySystem");
|
|
1375
|
+
const prBlock = this.extractLayoutBlock(layoutSrc, "premium");
|
|
1376
|
+
if (ksBlock) {
|
|
1377
|
+
const lua = this.blockToLuaTable(ksBlock);
|
|
1378
|
+
if (lua) parts.push(`,keySystem=${lua}`);
|
|
1379
|
+
}
|
|
1380
|
+
if (prBlock) {
|
|
1381
|
+
const lua = this.blockToLuaTable(prBlock);
|
|
1382
|
+
if (lua) parts.push(`,premium=${lua}`);
|
|
1383
|
+
}
|
|
1327
1384
|
parts.push(`}
|
|
1328
1385
|
`);
|
|
1329
1386
|
parts.push(`local _P=loadstring(game:HttpGet("${base}/pulse.lua"))(_PULSE_LAYOUT)
|
|
@@ -1835,7 +1892,7 @@ function makeClaudeMd(name) {
|
|
|
1835
1892
|
function makeAgentsMd(name) {
|
|
1836
1893
|
return read("AGENTS.md").replace(/{NAME}/g, name);
|
|
1837
1894
|
}
|
|
1838
|
-
var DIR, TEMPLATE_GLOBALS, TEMPLATE_REMOTES, MODULE_BOILERPLATE, REMOTE_BOILERPLATE, TEMPLATE_GITIGNORE, TEMPLATE_DEPLOY_EXAMPLE, TEMPLATE_LAYOUT_TS,
|
|
1895
|
+
var DIR, TEMPLATE_GLOBALS, TEMPLATE_REMOTES, MODULE_BOILERPLATE, REMOTE_BOILERPLATE, TEMPLATE_GITIGNORE, TEMPLATE_DEPLOY_EXAMPLE, TEMPLATE_LAYOUT_TS, TEMPLATE_PAGE_COMBAT_TS, TEMPLATE_COMPONENT_TS, TEMPLATE_EX_SPEED_TS, TEMPLATE_EX_FOV_TS, TEMPLATE_EX_ESP_TS, TEMPLATE_EX_AIMBOT_TS;
|
|
1839
1896
|
var init_templates = __esm({
|
|
1840
1897
|
"src/templates.ts"() {
|
|
1841
1898
|
init_cjs_shims();
|
|
@@ -1847,11 +1904,13 @@ var init_templates = __esm({
|
|
|
1847
1904
|
TEMPLATE_GITIGNORE = read("gitignore");
|
|
1848
1905
|
TEMPLATE_DEPLOY_EXAMPLE = read("deploy_config.example");
|
|
1849
1906
|
TEMPLATE_LAYOUT_TS = read("layout.ts");
|
|
1850
|
-
|
|
1907
|
+
read("page_home.ts");
|
|
1908
|
+
TEMPLATE_PAGE_COMBAT_TS = read("page_combat.ts");
|
|
1851
1909
|
TEMPLATE_COMPONENT_TS = read("component.ts");
|
|
1852
1910
|
TEMPLATE_EX_SPEED_TS = read("component_speed.ts");
|
|
1853
1911
|
TEMPLATE_EX_FOV_TS = read("component_fov.ts");
|
|
1854
1912
|
TEMPLATE_EX_ESP_TS = read("component_esp.ts");
|
|
1913
|
+
TEMPLATE_EX_AIMBOT_TS = read("component_aimbot.ts");
|
|
1855
1914
|
}
|
|
1856
1915
|
});
|
|
1857
1916
|
|
|
@@ -1940,9 +1999,10 @@ async function cmdInit(args) {
|
|
|
1940
1999
|
["src/misc/globals.lua", TEMPLATE_GLOBALS.replace(/{NAME}/g, name)],
|
|
1941
2000
|
["src/misc/remotes.lua", TEMPLATE_REMOTES],
|
|
1942
2001
|
["src/layout.ts", TEMPLATE_LAYOUT_TS.replace(/{NAME}/g, name)],
|
|
1943
|
-
["src/pages/
|
|
2002
|
+
["src/pages/1_Combat.ts", TEMPLATE_PAGE_COMBAT_TS],
|
|
1944
2003
|
["src/combat/SpeedHack.ts", TEMPLATE_EX_SPEED_TS],
|
|
1945
2004
|
["src/combat/FOVChanger.ts", TEMPLATE_EX_FOV_TS],
|
|
2005
|
+
["src/combat/Aimbot.ts", TEMPLATE_EX_AIMBOT_TS],
|
|
1946
2006
|
["src/visuals/PlayerESP.ts", TEMPLATE_EX_ESP_TS],
|
|
1947
2007
|
["tsconfig.json", TSCONFIG],
|
|
1948
2008
|
[".rb-deploy.example", TEMPLATE_DEPLOY_EXAMPLE],
|
package/package.json
CHANGED
package/pulse/runtime.lua
CHANGED
|
@@ -544,6 +544,7 @@ local function _groupbox(side, title, opts)
|
|
|
544
544
|
icon=(opts and opts.icon) or "",
|
|
545
545
|
mount=(opts and opts.mount) or nil,
|
|
546
546
|
plain=(opts and opts.plain) or false,
|
|
547
|
+
premium=(opts and opts.premium) or false,
|
|
547
548
|
widgets=(opts and opts.widgets) or {} }
|
|
548
549
|
end
|
|
549
550
|
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Example premium component — mark the groupbox as { premium: true } in your page.
|
|
2
|
+
// Users see an inline key-entry form until they unlock with a valid premium key.
|
|
3
|
+
defineComponent('Aimbot', (): WidgetDef[] => {
|
|
4
|
+
const enabled = signal<boolean>(false)
|
|
5
|
+
const fovRadius = signal<number>(90)
|
|
6
|
+
const smoothing = signal<number>(0.15)
|
|
7
|
+
|
|
8
|
+
on.heartbeat({ when: enabled, every: 0.016 }, (): void => {
|
|
9
|
+
// Aimbot logic — find nearest enemy and aim toward them.
|
|
10
|
+
// Use Pulse.World helpers and _PulseGetHRP() for the local root part.
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
return [
|
|
14
|
+
toggle('Aimbot').bind(enabled),
|
|
15
|
+
slider('FOV Radius', { min: 10, max: 360, suffix: '°' }).bind(fovRadius),
|
|
16
|
+
slider('Smoothing', { min: 0, max: 1 }).bind(smoothing),
|
|
17
|
+
]
|
|
18
|
+
})
|
package/templates/layout.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
// Pulse layout — configures the script window.
|
|
2
|
-
//
|
|
1
|
+
// Pulse layout — configures the script window and optional key/premium systems.
|
|
2
|
+
// This file is never compiled into the output; the rb CLI reads it at build time.
|
|
3
3
|
|
|
4
4
|
export default {
|
|
5
5
|
title: '{NAME}',
|
|
6
6
|
author: '',
|
|
7
|
+
version: '1.0.0',
|
|
8
|
+
description: 'A powerful script for {NAME}. Enable features from the tabs above.',
|
|
9
|
+
discord: '', // shown as "Join Discord" button in Home tab
|
|
7
10
|
toggleKey: 'RightControl',
|
|
8
11
|
size: [850, 560] as [number, number],
|
|
9
12
|
uiLibrary: 'windui' as const,
|
|
@@ -16,4 +19,34 @@ export default {
|
|
|
16
19
|
openButtonIcon: 'code-2',
|
|
17
20
|
themes: [] as LayoutConfig['themes'],
|
|
18
21
|
compatExclude: [] as string[],
|
|
22
|
+
|
|
23
|
+
// ── Key system (optional) ────────────────────────────────────────────────────
|
|
24
|
+
// Blocks the UI until the user enters a valid key.
|
|
25
|
+
// Remove this block entirely to let anyone use the script without a key.
|
|
26
|
+
//
|
|
27
|
+
// keySystem: {
|
|
28
|
+
// title: 'Key Required',
|
|
29
|
+
// note: 'Get your free key from our Discord',
|
|
30
|
+
// saveKey: true, // persist key to executor filesystem
|
|
31
|
+
// getKeyUrl: 'https://discord.gg/example',
|
|
32
|
+
//
|
|
33
|
+
// // Option A — static list (client-side):
|
|
34
|
+
// keys: ['FREE_KEY_1', 'FREE_KEY_2'],
|
|
35
|
+
//
|
|
36
|
+
// // Option B — server-side validator (more secure, any string "true" = valid):
|
|
37
|
+
// // validatorUrl: 'https://yoursite.com/validate?key=',
|
|
38
|
+
// },
|
|
39
|
+
|
|
40
|
+
// ── Premium tier (optional) ──────────────────────────────────────────────────
|
|
41
|
+
// A second key layer on top of the free tier.
|
|
42
|
+
// Mark groupboxes with { premium: true } in your pages to lock them.
|
|
43
|
+
// Users see an inline key-entry UI in each locked groupbox; entering a valid
|
|
44
|
+
// premium key unlocks all of them live — no re-injection needed.
|
|
45
|
+
// Premium keys also grant basic (keySystem) access automatically.
|
|
46
|
+
//
|
|
47
|
+
// premium: {
|
|
48
|
+
// keys: ['PREMIUM_KEY_1'],
|
|
49
|
+
// // OR: validatorUrl: 'https://yoursite.com/premium?key=',
|
|
50
|
+
// getKeyUrl: 'https://yoursite.com/premium', // shown in "Copy Link" / "Get Premium Key" buttons
|
|
51
|
+
// },
|
|
19
52
|
} satisfies LayoutConfig
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// Page names must not clash with the built-in "Home" and "Settings" tabs.
|
|
2
|
+
// Rename this page and add more pages as needed.
|
|
3
|
+
definePage('Combat', { icon: 'swords' }, () => [
|
|
4
|
+
groupbox('left', 'Movement', { icon: 'person', mount: 'SpeedHack' }),
|
|
5
|
+
groupbox('left', 'Camera', { icon: 'eye', mount: 'FOVChanger' }),
|
|
6
|
+
groupbox('right', 'Visuals', { icon: 'scan-eye', mount: 'PlayerESP' }),
|
|
7
|
+
// premium: true — shows inline key-entry form until the user unlocks with a premium key.
|
|
8
|
+
groupbox('right', 'Aimbot', { icon: 'crosshair', mount: 'Aimbot', premium: true }),
|
|
9
|
+
])
|