Compare commits
No commits in common. "830f4b8f4a2055e5e596a95b3268e30619c34724" and "7d6e14a08fe2fcf0709a846f9a5efba536dd7184" have entirely different histories.
830f4b8f4a
...
7d6e14a08f
@ -209,11 +209,9 @@ const KubikonPlayer = () => {
|
|||||||
const roomRef = useRef(null);
|
const roomRef = useRef(null);
|
||||||
/** MultiplayerSync (мост между room и Babylon-сценой). */
|
/** MultiplayerSync (мост между room и Babylon-сценой). */
|
||||||
const mpSyncRef = useRef(null);
|
const mpSyncRef = useRef(null);
|
||||||
/** Выбранный Mixamo-скин текущего игрока (из rublox_equipped_skin).
|
/** Выбранный R15-скин текущего игрока (из rublox_equipped_skin).
|
||||||
* Грузится при старте, уходит в мультиплеер как modelType.
|
* Грузится при старте, уходит в мультиплеер как modelType. */
|
||||||
* 2026-06-13: дефолт сменён с skin_bacon-hair на skin_y-bot
|
const skinFolderRef = useRef('skin_bacon-hair');
|
||||||
* (Игрек-Бот, новый Mixamo-каталог). */
|
|
||||||
const skinFolderRef = useRef('skin_y-bot');
|
|
||||||
|
|
||||||
const [meta, setMeta] = useState(null); // { title, description, user_id, ... }
|
const [meta, setMeta] = useState(null); // { title, description, user_id, ... }
|
||||||
const [forbidden, setForbidden] = useState(false);
|
const [forbidden, setForbidden] = useState(false);
|
||||||
@ -591,24 +589,8 @@ const KubikonPlayer = () => {
|
|||||||
// тогда player.setModelType подхватит правильный скин.
|
// тогда player.setModelType подхватит правильный скин.
|
||||||
// Этот же skinFolder уйдёт в мультиплеер как modelType,
|
// Этот же skinFolder уйдёт в мультиплеер как modelType,
|
||||||
// чтобы соперники видели наш реальный скин.
|
// чтобы соперники видели наш реальный скин.
|
||||||
//
|
let mySkin = 'skin_bacon-hair';
|
||||||
// LOCAL DEV (localhost): сначала пробуем localStorage
|
if (userId) {
|
||||||
// ('rublox_selected_skin' — тот же ключ что в rublox-site),
|
|
||||||
// чтобы юзер мог тестить выбор скинов без записи в прод-БД.
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
if (mySkin === 'skin_y-bot' && userId) {
|
|
||||||
try {
|
try {
|
||||||
const skinRes = await Kubikon3DApi.getEquippedSkin(userId);
|
const skinRes = await Kubikon3DApi.getEquippedSkin(userId);
|
||||||
const sf = skinRes?.data?.skin_folder;
|
const sf = skinRes?.data?.skin_folder;
|
||||||
@ -849,7 +831,7 @@ const KubikonPlayer = () => {
|
|||||||
// загружен при старте в skinFolderRef). Сервер всё равно перепроверит
|
// загружен при старте в skinFolderRef). Сервер всё равно перепроверит
|
||||||
// скин по userId из JWT и при расхождении возьмёт значение из БД —
|
// скин по userId из JWT и при расхождении возьмёт значение из БД —
|
||||||
// так каждый игрок виден соперникам в своём реальном скине.
|
// так каждый игрок виден соперникам в своём реальном скине.
|
||||||
const modelType = skinFolderRef.current || 'skin_y-bot';
|
const modelType = skinFolderRef.current || 'skin_bacon-hair';
|
||||||
// Если у нас есть валидный reconnectionToken от прошлой сессии —
|
// Если у нас есть валидный reconnectionToken от прошлой сессии —
|
||||||
// используем Colyseus reconnect (это та же сессия для сервера,
|
// используем Colyseus reconnect (это та же сессия для сервера,
|
||||||
// allowReconnection(5) её подхватит, не будет +join/-leave цикла).
|
// allowReconnection(5) её подхватит, не будет +join/-leave цикла).
|
||||||
|
|||||||
@ -199,9 +199,10 @@ export class BabylonScene {
|
|||||||
// Точка спавна игрока в режиме Play (обновляется setSpawnPoint)
|
// Точка спавна игрока в режиме Play (обновляется setSpawnPoint)
|
||||||
this._spawnPoint = { x: 0, y: 5, z: 0 };
|
this._spawnPoint = { x: 0, y: 5, z: 0 };
|
||||||
// Модель персонажа для режима Play.
|
// Модель персонажа для режима Play.
|
||||||
// 2026-06-13: дефолт сменён на skin_y-bot (Mixamo Y-Bot,
|
// Дефолт — R15-скин bacon-hair (классический Roblox-вид).
|
||||||
// нейтральный по полу). Старые скины (skin_bacon-hair и др.) удалены.
|
// 'skin_*' грузится из characters/<id>/body.glb (R15-скелет),
|
||||||
this._playerModelType = 'skin_y-bot';
|
// 'character-*' — старые Kenney-модели.
|
||||||
|
this._playerModelType = 'skin_bacon-hair';
|
||||||
// Размер пола: пол идёт от -worldHalf до +worldHalf по X и Z.
|
// Размер пола: пол идёт от -worldHalf до +worldHalf по X и Z.
|
||||||
// По умолчанию 80×80 (worldHalf = 40). Можно менять через setWorldSize().
|
// По умолчанию 80×80 (worldHalf = 40). Можно менять через setWorldSize().
|
||||||
this._worldHalf = 40;
|
this._worldHalf = 40;
|
||||||
@ -7522,9 +7523,7 @@ export class BabylonScene {
|
|||||||
// форсим R15 bacon-hair. Явно выбранные 'skin_*' не трогаем.
|
// форсим R15 bacon-hair. Явно выбранные 'skin_*' не трогаем.
|
||||||
if (state.scene.playerModelType) {
|
if (state.scene.playerModelType) {
|
||||||
const pmt = state.scene.playerModelType;
|
const pmt = state.scene.playerModelType;
|
||||||
// character-a..g (Kenney) и legacy R15 (skin_bacon-hair и др.)
|
this._playerModelType = pmt.startsWith('character-') ? 'skin_bacon-hair' : pmt;
|
||||||
// мигрируем на новый дефолт skin_y-bot.
|
|
||||||
this._playerModelType = pmt.startsWith('character-') ? 'skin_y-bot' : pmt;
|
|
||||||
}
|
}
|
||||||
// Задача 07: конфиг скинов { default, unlocked, shopVisible, coins, customGlbs }.
|
// Задача 07: конфиг скинов { default, unlocked, shopVisible, coins, customGlbs }.
|
||||||
if (state.scene.skins && typeof state.scene.skins === 'object') {
|
if (state.scene.skins && typeof state.scene.skins === 'object') {
|
||||||
|
|||||||
@ -64,7 +64,7 @@ function loadSkinManifest() {
|
|||||||
* @returns {Promise<{file:string, isR15:boolean, overrides:object}|null>}
|
* @returns {Promise<{file:string, isR15:boolean, overrides:object}|null>}
|
||||||
*/
|
*/
|
||||||
async function resolveRemoteModelSource(modelType) {
|
async function resolveRemoteModelSource(modelType) {
|
||||||
const typeId = modelType || 'skin_y-bot';
|
const typeId = modelType || 'skin_bacon-hair';
|
||||||
if (typeId.startsWith('skin_')) {
|
if (typeId.startsWith('skin_')) {
|
||||||
const manifest = await loadSkinManifest();
|
const manifest = await loadSkinManifest();
|
||||||
const entry = manifest.find((s) => s.id === typeId);
|
const entry = manifest.find((s) => s.id === typeId);
|
||||||
@ -713,7 +713,7 @@ export class MultiplayerSync {
|
|||||||
maxHp: player.maxHp ?? 100,
|
maxHp: player.maxHp ?? 100,
|
||||||
isDead: !!player.isDead,
|
isDead: !!player.isDead,
|
||||||
username: player.username || sessionId,
|
username: player.username || sessionId,
|
||||||
modelType: player.modelType || 'skin_y-bot',
|
modelType: player.modelType || 'skin_bacon-hair',
|
||||||
animState: player.animState || 'idle',
|
animState: player.animState || 'idle',
|
||||||
// Если модель не успеет загрузиться, висит fallback-капсула.
|
// Если модель не успеет загрузиться, висит fallback-капсула.
|
||||||
fallbackMesh: null,
|
fallbackMesh: null,
|
||||||
|
|||||||
@ -33,29 +33,6 @@ import { AccessoryManager } from './AccessoryManager';
|
|||||||
|
|
||||||
// Цикл режимов: первое лицо → за спиной → спереди (показ персонажа лицом).
|
// Цикл режимов: первое лицо → за спиной → спереди (показ персонажа лицом).
|
||||||
// 'front' удобен чтобы оценить скин: камера спереди, смотрит на лицо персонажа.
|
// 'front' удобен чтобы оценить скин: камера спереди, смотрит на лицо персонажа.
|
||||||
/* Mixamo-скины (новые персонажи rublox-site /character-assets/skins/).
|
|
||||||
* 2026-06-11: эти 80 ID перенесены сюда из data/skinsCatalog.js фронта
|
|
||||||
* чтобы плеер их распознавал и грузил по правильному пути.
|
|
||||||
* Дефолтные: skin_x-bot (male), skin_y-bot (female/null). */
|
|
||||||
const MIXAMO_SKINS = new Set([
|
|
||||||
'skin_aj', 'skin_akai', 'skin_arissa', 'skin_big-vegas',
|
|
||||||
'skin_castle-guard-1', 'skin_castle-guard-2',
|
|
||||||
'skin_ch01', 'skin_ch02', 'skin_ch03', 'skin_ch04', 'skin_ch07', 'skin_ch08',
|
|
||||||
'skin_ch09', 'skin_ch10', 'skin_ch11', 'skin_ch13', 'skin_ch14', 'skin_ch15',
|
|
||||||
'skin_ch16', 'skin_ch17', 'skin_ch18', 'skin_ch19', 'skin_ch20', 'skin_ch21',
|
|
||||||
'skin_ch22', 'skin_ch23', 'skin_ch24', 'skin_ch29', 'skin_ch31', 'skin_ch32',
|
|
||||||
'skin_ch33', 'skin_ch34', 'skin_ch35', 'skin_ch39', 'skin_ch40', 'skin_ch42',
|
|
||||||
'skin_ch43', 'skin_ch44', 'skin_ch45', 'skin_ch46', 'skin_ch47', 'skin_ch48',
|
|
||||||
'skin_claire', 'skin_demon', 'skin_ely', 'skin_erika-archer', 'skin_erika-archer-bow',
|
|
||||||
'skin_eve', 'skin_exo-gray', 'skin_exo-red', 'skin_ganfaul', 'skin_heraklios',
|
|
||||||
'skin_kachujin', 'skin_kaya', 'skin_knight', 'skin_lola', 'skin_maria',
|
|
||||||
'skin_maria-wprop', 'skin_maw', 'skin_medea', 'skin_mutant', 'skin_nightshade',
|
|
||||||
'skin_paladin', 'skin_passive-marker-man', 'skin_peasant-girl', 'skin_peasant-man',
|
|
||||||
'skin_prisoner', 'skin_pumpkinhulk', 'skin_skeleton-zombie', 'skin_sporty-granny',
|
|
||||||
'skin_survivor', 'skin_swat', 'skin_ty', 'skin_uriel', 'skin_vampire',
|
|
||||||
'skin_war-zombie', 'skin_warrok', 'skin_white-clown', 'skin_x-bot', 'skin_y-bot',
|
|
||||||
]);
|
|
||||||
|
|
||||||
const CAMERA_MODES = ['third', 'first', 'front'];
|
const CAMERA_MODES = ['third', 'first', 'front'];
|
||||||
// Для режима 'sideview' (Кубикон Dash):
|
// Для режима 'sideview' (Кубикон Dash):
|
||||||
// - камера фиксирована сбоку (смотрит на +Z с расстояния SIDEVIEW_DIST по -Z)
|
// - камера фиксирована сбоку (смотрит на +Z с расстояния SIDEVIEW_DIST по -Z)
|
||||||
@ -191,8 +168,8 @@ export class PlayerController {
|
|||||||
this._stepUpDecay = 4.5;
|
this._stepUpDecay = 4.5;
|
||||||
|
|
||||||
// Модель игрока (грузится в start)
|
// Модель игрока (грузится в start)
|
||||||
// 2026-06-13: дефолт сменён на skin_y-bot (Mixamo Y-Bot).
|
// Дефолт — R15-скин bacon-hair (классический Roblox-вид).
|
||||||
this._modelTypeId = 'skin_y-bot';
|
this._modelTypeId = 'skin_bacon-hair';
|
||||||
this._modelRoot = null;
|
this._modelRoot = null;
|
||||||
this._modelMeshes = [];
|
this._modelMeshes = [];
|
||||||
// Кубикон Dash: скрипт через game.player.setSkinVisible(false) выставляет
|
// Кубикон Dash: скрипт через game.player.setSkinVisible(false) выставляет
|
||||||
@ -809,32 +786,19 @@ export class PlayerController {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (typeId.startsWith('skin_')) {
|
if (typeId.startsWith('skin_')) {
|
||||||
// 2026-06-11: палитра скинов Рублокса заменена на 80 Mixamo.
|
|
||||||
// Mixamo-скины: /character-assets/skins/<id>.glb (на rublox-site).
|
|
||||||
// Legacy-скины (skin_bacon-hair / skin_sigma-labubu / skin_cop / ...)
|
|
||||||
// ещё могут приходить из БД пока feature-flag в storys выключен —
|
|
||||||
// их грузим из старого /kubikon-assets/characters/<id>/body.glb
|
|
||||||
// (R15-скелет). После заливки 80 GLB на rublox.pro и включения
|
|
||||||
// RUBLOX_NEW_SKINS_AVAILABLE=true legacy-ветка перестанет
|
|
||||||
// срабатывать (бэк начнёт отдавать только новые типы).
|
|
||||||
if (MIXAMO_SKINS.has(typeId)) {
|
|
||||||
const base = (typeof window !== 'undefined'
|
|
||||||
&& window.location.hostname === 'localhost')
|
|
||||||
? 'http://localhost:3000'
|
|
||||||
: 'https://rublox.pro';
|
|
||||||
return {
|
|
||||||
file: `${base}/character-assets/skins/${typeId}.glb`,
|
|
||||||
isR15: false,
|
|
||||||
kind: 'non-humanoid-rigged', // Mixamo-rig, не R15
|
|
||||||
overrides: {},
|
|
||||||
isMixamo: true,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// Legacy R15-скин — через старый manifest.
|
|
||||||
const manifest = await this._loadSkinManifest();
|
const manifest = await this._loadSkinManifest();
|
||||||
const entry = manifest.find((s) => s.id === typeId);
|
const entry = manifest.find((s) => s.id === typeId);
|
||||||
if (entry) {
|
if (entry) {
|
||||||
|
// kind определяет систему анимации:
|
||||||
|
// 'r15' → R15-скелет (как раньше)
|
||||||
|
// 'non-humanoid-mesh' → single-mesh, процедурное покачивание
|
||||||
|
// 'non-humanoid-rigged' → свой скелет + встроенные AnimationGroup
|
||||||
|
// Отсутствие kind = 'r15' (обратная совместимость со старым манифестом).
|
||||||
const kind = entry.kind || 'r15';
|
const kind = entry.kind || 'r15';
|
||||||
|
// absolute_file=true (источник /rublox/avatars) — file уже
|
||||||
|
// полный URL (legacy /kubikon-assets/... или дизайнерский
|
||||||
|
// /api-storys/...). Без флага — это легаси-формат
|
||||||
|
// skins_manifest.json без префикса.
|
||||||
const file = entry.absolute_file
|
const file = entry.absolute_file
|
||||||
? entry.file
|
? entry.file
|
||||||
: '/kubikon-assets/' + entry.file;
|
: '/kubikon-assets/' + entry.file;
|
||||||
@ -848,7 +812,7 @@ export class PlayerController {
|
|||||||
rotationYOffset: Number.isFinite(entry.rotationYOffset) ? entry.rotationYOffset : 0,
|
rotationYOffset: Number.isFinite(entry.rotationYOffset) ? entry.rotationYOffset : 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
// нет ни в Mixamo, ни в manifest — пробуем прямой legacy-путь
|
// нет в манифесте — пробуем прямой путь
|
||||||
return {
|
return {
|
||||||
file: `/kubikon-assets/characters/${typeId}/body.glb`,
|
file: `/kubikon-assets/characters/${typeId}/body.glb`,
|
||||||
isR15: true,
|
isR15: true,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user