diff --git a/src/engine/ScriptSandboxWorker.js b/src/engine/ScriptSandboxWorker.js index a12a742..529161f 100644 --- a/src/engine/ScriptSandboxWorker.js +++ b/src/engine/ScriptSandboxWorker.js @@ -3156,6 +3156,70 @@ const game = { return a + Math.random() * (b - a); }, + // Форматирование чисел/времени/денег для UI. Портировано из студии + // (задача 11 — игра «Мой завод» использует game.format.money). + format: { + time(seconds, fmt) { + let sec = Math.max(0, Math.floor(Number(seconds) || 0)); + const h = Math.floor(sec / 3600); + const m = Math.floor((sec % 3600) / 60); + const s = sec % 60; + const p2 = (n) => String(n).padStart(2, '0'); + if (fmt === 'hh:mm:ss') return p2(h) + ':' + p2(m) + ':' + p2(s); + if (fmt === 'mm:ss') { + const tm = Math.floor(sec / 60); + return p2(tm) + ':' + p2(s); + } + // auto + if (h > 0) return h + 'ч ' + m + 'м'; + if (m > 0) return m + 'м ' + s + 'с'; + return s + 'с'; + }, + number(n, fmt) { + n = Number(n) || 0; + if (fmt === 'percent') return Math.round(n * 100) + '%'; + if (fmt === 'short') { + const abs = Math.abs(n); + if (abs >= 1e9) return (n / 1e9).toFixed(1).replace('.0', '') + 'B'; + if (abs >= 1e6) return (n / 1e6).toFixed(1).replace('.0', '') + 'M'; + if (abs >= 1e3) return (n / 1e3).toFixed(1).replace('.0', '') + 'K'; + return String(Math.round(n)); + } + // comma — пробелы-разделители тысяч (русский стиль), без regex. + const str = String(Math.abs(Math.round(n))); + let out = ''; + for (let i = 0; i < str.length; i++) { + if (i > 0 && (str.length - i) % 3 === 0) out += ' '; + out += str[i]; + } + return (n < 0 ? '-' : '') + out; + }, + money(amount, unit) { + const num = this.number(amount, 'comma'); + const u = (unit === 'rubles' || unit === undefined) + ? this._plural(Math.round(Number(amount) || 0), 'рублик', 'рублика', 'рубликов') + : unit; + return num + ' ' + u; + }, + duration(seconds) { + let sec = Math.max(0, Math.floor(Number(seconds) || 0)); + const h = Math.floor(sec / 3600); + const m = Math.floor((sec % 3600) / 60); + if (h > 0) return h + ' ' + this._plural(h, 'час', 'часа', 'часов'); + if (m > 0) return m + ' ' + this._plural(m, 'минута', 'минуты', 'минут'); + return sec + ' ' + this._plural(sec, 'секунда', 'секунды', 'секунд'); + }, + // Русское склонение числительных (1 рублик / 2 рублика / 5 рубликов). + _plural(n, one, few, many) { + n = Math.abs(n) % 100; + const n1 = n % 10; + if (n > 10 && n < 20) return many; + if (n1 > 1 && n1 < 5) return few; + if (n1 === 1) return one; + return many; + }, + }, + /** * Расстояние между двумя точками или объектами. * Аргументы могут быть {x,y,z} или ref-строкой (для ref берём позицию из sceneIndex). @@ -3690,7 +3754,8 @@ self.onmessage = (e) => { } } else if (t === 'skinUnlocked') { const slug = payload && payload.slug; - if (slug && _unlockedSkins.indexOf(slug) === -1) _unlockedSkins.push(slug); } else if (t === 'placeConfirm') { + if (slug && _unlockedSkins.indexOf(slug) === -1) _unlockedSkins.push(slug); + } else if (t === 'placeConfirm') { const ev = { itemKey: payload.itemKey, position: payload.position, rotationY: payload.rotationY }; for (const fn of _placeOnPlaceHandlers) _safeCall(fn, ev, 'placement.onPlace'); } else if (t === 'placeCancel') {