feat: 50 игр на Lua + импорт Roblox для всех + поддержка Lua в плеере #39
@ -121,12 +121,11 @@ export class GameRuntime {
|
||||
// скрипты (с маркером // @roblox-lua) теперь идут через ОДИН LuaSharedSandbox.
|
||||
// .rbxl-скрипты распаковываем из JS-комментария-обёртки в чистый Lua.
|
||||
const luaUserBatch = [];
|
||||
// По умолчанию импортированные .rbxl-скрипты НЕ выполняются:
|
||||
// - типичная Roblox-карта = 500-2000 скриптов, многие используют
|
||||
// DataStore/Tool/PlayerGui/UserInputService, которых у нас нет;
|
||||
// - даже с stub'ами сотни падений → tab подвисает;
|
||||
// Юзер может включить через window.__RBXL_RUN_IMPORTED = true в консоли.
|
||||
const runImportedRbxl = typeof window !== 'undefined' && window.__RBXL_RUN_IMPORTED === true;
|
||||
// Импортированные .rbxl-скрипты выполняются по умолчанию (батчами по 20
|
||||
// через setTimeout, чтобы не подвешивать UI). Падения скриптов изолированы
|
||||
// pcall'ом в shim — один битый скрипт не валит остальных.
|
||||
// Выключить можно через window.__RBXL_SKIP_IMPORTED = true.
|
||||
const runImportedRbxl = !(typeof window !== 'undefined' && window.__RBXL_SKIP_IMPORTED === true);
|
||||
let rbxlSkipped = 0;
|
||||
for (const s of scripts) {
|
||||
if (s && typeof s.code === 'string' && s.code.startsWith('// @roblox-lua')) {
|
||||
@ -221,7 +220,7 @@ export class GameRuntime {
|
||||
this._log('info', `Запущено Roblox-Lua скриптов (импортированных): ${rbxlImported}`);
|
||||
}
|
||||
if (rbxlSkipped > 0) {
|
||||
this._log('info', `Импортированных .rbxl-скриптов пропущено: ${rbxlSkipped}. Включить: window.__RBXL_RUN_IMPORTED = true`);
|
||||
this._log('info', `Импортированных .rbxl-скриптов пропущено: ${rbxlSkipped}. Вернуть: убрать window.__RBXL_SKIP_IMPORTED`);
|
||||
}
|
||||
if (luaWritten > 0) {
|
||||
this._log('info', `Запущено Lua-скриптов юзера: ${luaWritten}`);
|
||||
|
||||
@ -109,10 +109,33 @@ export class LuaSharedSandbox {
|
||||
this._isKickedOff = true;
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[LuaSharedSandbox] kickoff: starting ${this._pendingScripts.length} scripts`);
|
||||
for (const entry of this._pendingScripts) this._startSingleScript(entry);
|
||||
const pending = this._pendingScripts;
|
||||
this._pendingScripts = [];
|
||||
// Запускаем main-loop сразу — он начнёт tick'ать как только будут coroutines.
|
||||
this._lastTickAt = performance.now();
|
||||
this._startMainLoop();
|
||||
// Init батчами по 20 с yield между ними, чтобы UI не подвисал на 700+ скриптах.
|
||||
const BATCH_SIZE = 20;
|
||||
let idx = 0;
|
||||
const initBatch = () => {
|
||||
if (this._isStopped) return;
|
||||
const end = Math.min(idx + BATCH_SIZE, pending.length);
|
||||
for (let i = idx; i < end; i++) {
|
||||
try { this._startSingleScript(pending[i]); }
|
||||
catch (e) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('[LuaSharedSandbox] init batch err:', e);
|
||||
}
|
||||
}
|
||||
idx = end;
|
||||
if (idx < pending.length) {
|
||||
setTimeout(initBatch, 0);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[LuaSharedSandbox] all ${pending.length} scripts kicked off`);
|
||||
}
|
||||
};
|
||||
setTimeout(initBatch, 0);
|
||||
}
|
||||
|
||||
_startSingleScript(entry) {
|
||||
|
||||
@ -541,6 +541,53 @@ export function registerRobloxShim(lua, opts) {
|
||||
HumanoidStateType: mkE(['Running','Jumping','Freefall','Landed','Dead','Climbing','Swimming','Seated']),
|
||||
EasingStyle: mkE(['Linear','Sine','Quad','Cubic','Quart','Quint','Bounce','Elastic']),
|
||||
EasingDirection: mkE(['In','Out','InOut']),
|
||||
// Часто используемые в туториалах
|
||||
InfoType: mkE(['Asset','BundleDetails','Subscription','GamePass','UserProductsInExperience']),
|
||||
SortOrder: mkE(['Name','Custom','LayoutOrder']),
|
||||
FillDirection: mkE(['Horizontal','Vertical']),
|
||||
HorizontalAlignment: mkE(['Left','Center','Right']),
|
||||
VerticalAlignment: mkE(['Top','Center','Bottom']),
|
||||
Font: mkE(['Legacy','Arial','SourceSans','Code','Highway','SciFi','Cartoon','Gotham','GothamBold']),
|
||||
TextXAlignment: mkE(['Left','Center','Right']),
|
||||
TextYAlignment: mkE(['Top','Center','Bottom']),
|
||||
ScaleType: mkE(['Stretch','Slice','Tile','Fit','Crop']),
|
||||
AspectType: mkE(['FitWithinMaxSize','ScaleWithParentSize']),
|
||||
DominantAxis: mkE(['Width','Height']),
|
||||
BorderMode: mkE(['Outline','Middle','Inset']),
|
||||
FormFactor: mkE(['Symmetric','Brick','Plate','Custom']),
|
||||
PartType: mkE(['Ball','Block','Cylinder','Wedge','CornerWedge']),
|
||||
SurfaceType: mkE(['Smooth','Glue','Weld','Studs','Inlet','Universal']),
|
||||
ContextActionResult: mkE(['Pass','Sink']),
|
||||
UserInputState: mkE(['Begin','Change','End','Cancel','None']),
|
||||
});
|
||||
|
||||
// TweenInfo — конструктор объекта с параметрами анимации
|
||||
// Сигнатура: TweenInfo.new(time, easingStyle, easingDirection, repeatCount, reverses, delayTime)
|
||||
global.set('TweenInfo', {
|
||||
new(time, easingStyle, easingDirection, repeatCount, reverses, delayTime) {
|
||||
return {
|
||||
Time: time || 1,
|
||||
EasingStyle: easingStyle,
|
||||
EasingDirection: easingDirection,
|
||||
RepeatCount: repeatCount || 0,
|
||||
Reverses: !!reverses,
|
||||
DelayTime: delayTime || 0,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
// NumberSequence, ColorSequence — упрощённые конструкторы для GUI-эффектов
|
||||
global.set('NumberSequence', {
|
||||
new(...args) { return { Keypoints: [], __ns: true }; },
|
||||
});
|
||||
global.set('ColorSequence', {
|
||||
new(...args) { return { Keypoints: [], __cs: true }; },
|
||||
});
|
||||
global.set('NumberRange', {
|
||||
new(min, max) { return { Min: min, Max: max == null ? min : max }; },
|
||||
});
|
||||
global.set('Rect', {
|
||||
new(minX, minY, maxX, maxY) { return { Min: { X: minX, Y: minY }, Max: { X: maxX, Y: maxY } }; },
|
||||
});
|
||||
|
||||
// === print / warn ===
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user