feat(rbxl): RegenerationScript no-op + реактивный Humanoid

8. RegenerationScript: эвристика по имени скрипта (regenerate*/
   regenerationscript) → пропускается. У нас Anchored=True для импорта,
   постройки не разрушаются, регенерация не нужна. Их работа дала бы
   визуальные глитчи (model:remove + Clone каждые 2 мин).

9. BattleArmor: Humanoid.MaxHealth/Health/WalkSpeed/JumpPower теперь
   реактивные (Object.defineProperty). При смене .MaxHealth=N шлёт
   playerSet → player.maxHp обновляется → HUD HP-бар. BattleArmor
   touch'нул → Humanoid.MaxHealth=20, Health=20 → игрок видит броню.

10. WinGui/FireButton: GUI-элементы из StarterGui приходят через
    converter scene.gui[] и рендерятся стандартно. Если визуально не
    идеально — это про GuiManager позиционирование, не специфично
    для импорта.

11. AdminConsole: no-op, скрипт-заглушка, ничего не делает.

13. NotLinkedBlocker: слишком специфично (отмена урона через флаг
    блока), пропускаю.

ROBLOX Battle итог: 9 механик реализованы (1-7, 12, 14), 2 решены
no-op (8, 11), 3 не критичны (10, 13). Карта должна играться.
This commit is contained in:
min 2026-06-08 19:46:39 +03:00
parent 913283ffa6
commit 932ef2bc20
2 changed files with 38 additions and 4 deletions

View File

@ -134,6 +134,13 @@ export class GameRuntime {
// запуск немедленно крашит coroutine (WASM access out of bounds).
const meta = parseRobloxLuaMeta(s.code);
if (meta && meta.enabled === false) { rbxlSkipped++; continue; }
// Пропускаем Regeneration-скрипты: у нас Anchored=True для
// импорта, постройки не разрушаются, регенерация не нужна.
// Их работа (model:remove + Clone) даст визуальные глитчи.
const sname = String(s.name || '').toLowerCase();
if (sname.startsWith('regenerate') || sname === 'regenerationscript') {
rbxlSkipped++; continue;
}
const luaSource = unpackRobloxLuaCode(s.code);
if (luaSource && luaSource.trim()) {
// Эвристика Tool: если скрипт ссылается на Equipped/Activated
@ -4258,6 +4265,12 @@ export class GameRuntime {
try { player.walkSpeed = Number(payload.value) || player.walkSpeed; } catch (_) {}
} else if (payload.prop === 'jumpPower') {
try { player.jumpPower = Number(payload.value) || player.jumpPower; } catch (_) {}
} else if (payload.prop === 'maxHealth') {
try {
const max = Math.max(1, Number(payload.value) || 100);
player.maxHp = max;
if (player.hp > max) player.hp = max;
} catch (_) {}
}
return;
}

View File

@ -831,10 +831,31 @@ export function registerRobloxShim(lua, opts) {
const humanoid = newInstance('Humanoid', 'Humanoid');
humanoid.Parent = character;
humanoid.Health = 100;
humanoid.MaxHealth = 100;
humanoid.WalkSpeed = 16;
humanoid.JumpPower = 50;
let _hp = 100, _maxHp = 100, _ws = 16, _jp = 50;
Object.defineProperty(humanoid, 'Health', {
get() { return _hp; },
set(v) {
_hp = Math.max(0, Math.min(_maxHp, Number(v) || 0));
try { humanoid.HealthChanged.Fire(_hp); } catch (_) {}
send('playerSet', { prop: 'health', value: _hp });
},
});
Object.defineProperty(humanoid, 'MaxHealth', {
get() { return _maxHp; },
set(v) {
_maxHp = Math.max(1, Number(v) || 100);
if (_hp > _maxHp) humanoid.Health = _maxHp;
send('playerSet', { prop: 'maxHealth', value: _maxHp });
},
});
Object.defineProperty(humanoid, 'WalkSpeed', {
get() { return _ws; },
set(v) { _ws = Number(v) || 16; send('playerSet', { prop: 'walkSpeed', value: _ws }); },
});
Object.defineProperty(humanoid, 'JumpPower', {
get() { return _jp; },
set(v) { _jp = Number(v) || 50; send('playerSet', { prop: 'jumpPower', value: _jp }); },
});
humanoid.Died = makeSignal();
humanoid.HealthChanged = makeSignal();
humanoid.Touched = makeSignal();