restore: Mixamo + dev-skin через URL #skin= #28
@ -311,36 +311,26 @@ const KubikonPlayer = () => {
|
||||
return () => { active = false; };
|
||||
}, [projectId]);
|
||||
|
||||
// Перехват Ctrl-комбинаций которые ломают игру (Ctrl+W = закрыть вкладку,
|
||||
// Ctrl+R = reload, Ctrl+T/N — мешают). Большинство браузеров блокирует
|
||||
// отмену системных шорткатов, но beforeunload даёт пользователю шанс
|
||||
// подтвердить выход. Также превентим preventDefault на keydown для
|
||||
// случаев когда фокус НЕ на window-уровне (Chrome иногда позволяет).
|
||||
// Перехват Ctrl-комбинаций которые ломают игру (Ctrl+W, Ctrl+R, Ctrl+T,
|
||||
// Ctrl+N). Большинство браузеров блокирует отмену системных шорткатов,
|
||||
// но мы пробуем preventDefault — иногда срабатывает.
|
||||
//
|
||||
// 2026-06-14: beforeunload-подтверждение убрано по решению UX — системное
|
||||
// окно браузера невозможно стилизовать (с 2017 Chrome игнорирует кастомный
|
||||
// текст), а уродливое модальное мешает. Случайное закрытие вкладки теперь
|
||||
// просто закрывает игру без вопроса.
|
||||
useEffect(() => {
|
||||
const onKey = (e) => {
|
||||
if (!e.ctrlKey && !e.metaKey) return;
|
||||
// Список «опасных» в игре сочетаний — превентим
|
||||
const dangerousCodes = ['KeyW', 'KeyR', 'KeyT', 'KeyN'];
|
||||
if (dangerousCodes.includes(e.code)) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
};
|
||||
const onBeforeUnload = (e) => {
|
||||
// Если юзер сам нажал «Покинуть» в меню — пропускаем без
|
||||
// подтверждения. Флаг ставит exitPlayer().
|
||||
if (window.__rubloxExplicitExit) return undefined;
|
||||
// Случайное закрытие вкладки (Ctrl+W, X-кнопка) — показываем
|
||||
// подтверждение чтобы не потерять прогресс игры.
|
||||
e.preventDefault();
|
||||
e.returnValue = '';
|
||||
return '';
|
||||
};
|
||||
window.addEventListener('keydown', onKey, { capture: true });
|
||||
window.addEventListener('beforeunload', onBeforeUnload);
|
||||
return () => {
|
||||
window.removeEventListener('keydown', onKey, { capture: true });
|
||||
window.removeEventListener('beforeunload', onBeforeUnload);
|
||||
};
|
||||
}, []);
|
||||
|
||||
@ -592,29 +582,39 @@ const KubikonPlayer = () => {
|
||||
// Этот же skinFolder уйдёт в мультиплеер как modelType,
|
||||
// чтобы соперники видели наш реальный скин.
|
||||
//
|
||||
// LOCAL DEV (localhost): сначала пробуем localStorage
|
||||
// ('rublox_selected_skin' — тот же ключ что в rublox-site),
|
||||
// чтобы юзер мог тестить выбор скинов без записи в прод-БД.
|
||||
// LOCAL DEV (localhost): берём скин из URL #skin=<id>
|
||||
// (передаётся сайтом 3000 при нажатии «Играть»), потом из
|
||||
// localStorage самого плеера, потом дефолт. В БД НЕ лезем —
|
||||
// прод-БД хранит легаси скины (skin_sigma-labubu и др.),
|
||||
// которые мы не хотим грузить локально.
|
||||
//
|
||||
// PROD: только БД (rublox_equipped_skin).
|
||||
let mySkin = 'skin_y-bot';
|
||||
const isLocalDev = (typeof window !== 'undefined'
|
||||
&& (window.location.hostname === 'localhost'
|
||||
|| window.location.hostname === '127.0.0.1'));
|
||||
if (isLocalDev) {
|
||||
try {
|
||||
const localPick = localStorage.getItem('rublox_selected_skin');
|
||||
if (localPick && typeof localPick === 'string') {
|
||||
mySkin = localPick;
|
||||
console.log('[KubikonPlayer] local-dev skin:', mySkin);
|
||||
// 1) hash-параметр #skin=<id> (от сайта при play-ticket)
|
||||
const m = window.location.hash.match(/[#&]skin=([\w-]+)/);
|
||||
if (m && m[1]) {
|
||||
mySkin = m[1];
|
||||
console.log('[KubikonPlayer] local-dev skin (URL):', mySkin);
|
||||
} else {
|
||||
// 2) localStorage самого плеера
|
||||
const localPick = localStorage.getItem('rublox_selected_skin');
|
||||
if (localPick && typeof localPick === 'string') {
|
||||
mySkin = localPick;
|
||||
console.log('[KubikonPlayer] local-dev skin (LS):', mySkin);
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
if (mySkin === 'skin_y-bot' && userId) {
|
||||
} else if (userId) {
|
||||
try {
|
||||
const skinRes = await Kubikon3DApi.getEquippedSkin(userId);
|
||||
const sf = skinRes?.data?.skin_folder;
|
||||
if (sf && typeof sf === 'string') mySkin = sf;
|
||||
} catch (e) {
|
||||
// Сеть/ошибка — играем с дефолтным скином, не блокируем.
|
||||
console.warn('[KubikonPlayer] equipped-skin load failed', e);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user