feat(11): placement mode � ����������� ��������� (tycoon) #15

Merged
min merged 2 commits from feat/placement-task11 into main 2026-06-02 17:24:23 +00:00
Showing only changes of commit 91af8514c5 - Show all commits

View File

@ -3156,6 +3156,70 @@ const game = {
return a + Math.random() * (b - a); 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). * Аргументы могут быть {x,y,z} или ref-строкой (для ref берём позицию из sceneIndex).
@ -3690,7 +3754,8 @@ self.onmessage = (e) => {
} }
} else if (t === 'skinUnlocked') { } else if (t === 'skinUnlocked') {
const slug = payload && payload.slug; 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 }; const ev = { itemKey: payload.itemKey, position: payload.position, rotationY: payload.rotationY };
for (const fn of _placeOnPlaceHandlers) _safeCall(fn, ev, 'placement.onPlace'); for (const fn of _placeOnPlaceHandlers) _safeCall(fn, ev, 'placement.onPlace');
} else if (t === 'placeCancel') { } else if (t === 'placeCancel') {