diff --git a/saves/game_time.json b/saves/game_time.json index afdd7c8..d86a075 100644 --- a/saves/game_time.json +++ b/saves/game_time.json @@ -1 +1 @@ -{"time":"2025-02-14T12:10:49.296Z","lastReal":1755514421720} \ No newline at end of file +{"time":"2025-04-20T19:17:09.736Z","lastReal":1756219619275} \ No newline at end of file diff --git a/server.js b/server.js index e4aebd3..c76c7fd 100644 --- a/server.js +++ b/server.js @@ -85,6 +85,16 @@ let onlineUsers = {}; const organizationsRouter = require('./server/organizations')(io, onlineUsers); app.use('/api/organizations', organizationsRouter); +// Инициализация игрового времени (ускорение 8x) и вещание клиентам +try { + if (GameTime && typeof GameTime === 'function') { + global.__gameTimeInstance = global.__gameTimeInstance || new GameTime(io, 8); + console.log('GameTime таймер запущен'); + } +} catch (e) { + console.error('GameTime не запущен:', e); +} + io.use((socket, next) => { const token = socket.handshake.auth.token; if (!token) return next(new Error('No token')); diff --git a/src/Game.js b/src/Game.js index 42d98bb..9525663 100644 --- a/src/Game.js +++ b/src/Game.js @@ -73,7 +73,7 @@ function Game({ avatarUrl, gender }) { }); const [inventory, setInventory] = useState([]); const [showInventory, setShowInventory] = useState(false); - const [gameTime, setGameTime] = useState(''); + const [gameTime, setGameTime] = useState(null); const [balance, setBalance] = useState(() => { const p = JSON.parse(sessionStorage.getItem('user_profile') || '{}'); return p.balance ?? 0; @@ -736,6 +736,13 @@ function Game({ avatarUrl, gender }) { objGltf.scene.scale.set(o.scale, o.scale, o.scale); intGroup.add(objGltf.scene); + // Добавляем меши объекта как коллайдеры интерьера + objGltf.scene.traverse((child) => { + if (child.isMesh && child.geometry) { + colliders.push(child); + } + }); + // Если это NPC внутри интерьера — добавим кликабельную хит‑зону const isNpc = (o.type === 'npc') || (typeof o.model_url === 'string' && o.model_url.includes('/models/npc/')); if (isNpc) { @@ -791,6 +798,10 @@ function Game({ avatarUrl, gender }) { } } intGroup.add(mesh); + // Плейсхолдер не рендерим, но используем как коллайдер + try { mesh.visible = false; } catch (_) {} + // Плейсхолдер без GLTF тоже участвует в коллизиях + colliders.push(mesh); } // Если сервер пометил объект как «интерактивный/маркер» — кликабельная зона @@ -802,7 +813,8 @@ function Game({ avatarUrl, gender }) { hit.position.set(o.x, o.y + 1.0, o.z); hit.userData.interactable = true; hit.userData.payload = { type: o.type || 'marker', id: o.id || null, label: o.label || 'Интерактив' }; - hit.visible = true; // невидим визуально (opacity≈0), но кликабелен + hit.visible = true; // кликабелен + try { if (hit.material) hit.material.visible = false; } catch (_) {} intGroup.add(hit); interiorInteractablesRef.current.push(hit); } @@ -3325,9 +3337,10 @@ useEffect(() => { } function loadInteriorPlaceholder(int) { + // Упрощённый невидимый placeholder с кликабельной зоной const mesh = new THREE.Mesh( new THREE.BoxGeometry(2, 2, 2), - new THREE.MeshStandardMaterial({ color: 0x00ffcc }) + new THREE.MeshBasicMaterial({ visible: false }) ); mesh.position.set(int.pos_x, int.pos_y, int.pos_z); mesh.userData.interiorId = int.id; @@ -3793,16 +3806,28 @@ useEffect(() => { const tryMove = (dirVec) => { const candidate = player.position.clone().addScaledVector(dirVec, speed * delta); // Обновляем AABB игрока (простая капсула не используется, только коробка) - const half = 0.3; // половина ширины - const height = 1.8; + const half = 0.25; // чуточку уже, чтобы не цепляться за стены + const height = 1.7; // немного ниже, чтобы не пересекать потолок const playerBox = new THREE.Box3( new THREE.Vector3(candidate.x - half, candidate.y, candidate.z - half), new THREE.Vector3(candidate.x + half, candidate.y + height, candidate.z + half) ); - const hits = (interiorCollidersRef.current || []).some((mesh) => { + // Обновляем мировые матрицы статических коллайдеров для корректных AABB + try { interiorGroupRef.current && interiorGroupRef.current.updateMatrixWorld(true); } catch (_) {} + + // В интерьере учитываем только внутренние коллайдеры, без городских объектов + const blockingMeshes = Array.isArray(interiorCollidersRef.current) + ? interiorCollidersRef.current + : []; + + let hits = false; + for (const mesh of blockingMeshes) { + if (!mesh) continue; const box = new THREE.Box3().setFromObject(mesh); - return box.intersectsBox(playerBox); - }); + // небольшой зазор, чтобы скользить вдоль стен + const expanded = box.clone().expandByScalar(0.01); + if (expanded.intersectsBox(playerBox)) { hits = true; break; } + } if (!hits) { player.position.copy(candidate); } @@ -4088,7 +4113,12 @@ useEffect(() => { X: {playerCoords.x} Y: {playerCoords.y} Z: {playerCoords.z}