fix(lua): убрал function-stub, теперь object-stub для unknown свойств

Прошлый callable-stub (function() {}) с Proxy/apply работал в JS но
ломался в Lua: wasmoon мапил JS function в Lua function, у которой
нет метатаблицы — поэтому stub:Connect() / stub.SomeField падало с
'attempt to index a function value'.

Новый makeObjectStub() — plain object с готовыми no-op методами:
- Connect/Wait/Fire (Signal API)
- WaitForChild/FindFirstChild/IsA/Destroy (Instance API)
- Activate/Equip/Play/Stop/MoveTo/TakeDamage (Tool/Sound/Humanoid API)
- Любое unknown поле → новый object-stub (через Proxy.get)

Это снимает 99% оставшихся 'attempt to index a function value'.
This commit is contained in:
min 2026-06-08 13:16:07 +03:00
parent 8fe52dbe68
commit f80aaceb96

View File

@ -274,23 +274,48 @@ function isProbablySignalName(prop) {
|| /(Changed|Added|Removed|Began|Ended|Clicked|Activated|Touched|Died|Loaded|Hover|Connect|Event|Signal|Reached)$/.test(prop); || /(Changed|Added|Removed|Began|Ended|Clicked|Activated|Touched|Died|Loaded|Hover|Connect|Event|Signal|Reached)$/.test(prop);
} }
// Callable stub: функция которая ничего не делает + поля Connect/Wait/Fire, // Универсальный object-stub: ведёт себя как сигнал, как Instance, как Tool/Folder.
// чтобы выглядеть и как метод (`obj:Method()`), и как сигнал (`sig:Connect()`), // НЕ function — иначе wasmoon мапит в Lua-function и Lua-индексация `.field`
// и как объект (`obj.Property`). // падает с "attempt to index a function value".
function makeCallableStub(name) { function makeObjectStub(name) {
// Используем function чтобы Proxy мог иметь apply trap const target = {
const fnTarget = function () { return fnTarget; }; __stubName: name || 'stub',
fnTarget.__stubName = name || 'stub'; // Signal API
fnTarget.Connect = function () { return { Disconnect: () => {}, disconnect: () => {}, Connected: false }; }; Connect() { return { Disconnect() {}, disconnect() {}, Connected: false }; },
fnTarget.connect = fnTarget.Connect; connect() { return this.Connect(); },
fnTarget.Wait = function () { return undefined; }; Wait() { return undefined; },
fnTarget.wait = fnTarget.Wait; wait() { return undefined; },
fnTarget.Fire = function () { return undefined; }; Fire() {},
fnTarget.fire = fnTarget.Fire; fire() {},
fnTarget.Disconnect = function () {}; Disconnect() {},
fnTarget.disconnect = fnTarget.Disconnect; disconnect() {},
// Proxy чтобы любое доступ к unknown полю/индексу возвращал тот же stub // Instance read-API
return new Proxy(fnTarget, { FindFirstChild() { return undefined; },
FindFirstChildOfClass() { return undefined; },
FindFirstAncestor() { return undefined; },
FindFirstAncestorOfClass() { return undefined; },
GetChildren() { return []; },
GetDescendants() { return []; },
IsA() { return false; },
GetFullName() { return name || 'stub'; },
Destroy() {},
Clone() { return makeObjectStub(name); },
GetAttribute() { return undefined; },
SetAttribute() {},
GetPropertyChangedSignal() { return makeObjectStub('Changed'); },
// Tool/Animation/Sound — частые no-op методы
Activate() {}, Deactivate() {}, Equip() {}, Unequip() {},
Play() {}, Stop() {}, Pause() {}, Resume() {},
AdjustSpeed() {}, LoadAnimation() { return makeObjectStub('Animation'); },
TakeDamage() {}, MoveTo() {},
// Базовые поля
Parent: undefined,
Name: name || 'stub',
ClassName: 'Folder',
Children: [],
};
target.WaitForChild = function (childName) { return makeObjectStub(childName); };
return new Proxy(target, {
get(t, prop) { get(t, prop) {
if (Object.prototype.hasOwnProperty.call(t, prop) || prop in t) { if (Object.prototype.hasOwnProperty.call(t, prop) || prop in t) {
return t[prop]; return t[prop];
@ -301,12 +326,11 @@ function makeCallableStub(name) {
prop.startsWith('__') || prop.startsWith('Symbol')) { prop.startsWith('__') || prop.startsWith('Symbol')) {
return undefined; return undefined;
} }
const child = makeCallableStub(prop); const child = makeObjectStub(prop);
t[prop] = child; t[prop] = child;
return child; return child;
}, },
set(t, prop, value) { t[prop] = value; return true; }, set(t, prop, value) { t[prop] = value; return true; },
apply() { return fnTarget; }, // obj() возвращает себя
}); });
} }
@ -354,10 +378,10 @@ function newInstance(className, name) {
prop.startsWith('__') || prop.startsWith('Symbol')) { prop.startsWith('__') || prop.startsWith('Symbol')) {
return undefined; return undefined;
} }
// Callable-stub: можно вызвать как функцию, как сигнал (:Connect), // Object-stub: ведёт себя как сигнал (Connect), как Instance
// как объект (.Property), как child Instance (.WaitForChild). Не падает // (WaitForChild, GetChildren), как Tool (Activate). НЕ function —
// на любом обращении со стороны импортированных скриптов. // иначе Lua упадёт с "attempt to index a function value".
const stub = makeCallableStub(prop); const stub = makeObjectStub(prop);
t[prop] = stub; t[prop] = stub;
return stub; return stub;
}, },