// ==================== ENTRY POINT ==================== import { state } from './core/state.js'; import { SERVER_URL } from './config.js'; import { showStartMenu, isTgWebApp, tgUser } from './ui/start-menu.js'; import { initCanvas } from './core/canvas.js'; import { initSocket } from './multiplayer/socket.js'; import { loadSound, playSound } from './audio/sound-engine.js'; import { initTextures, tex } from './render/textures.js'; import { getBlock, setBlock, removeBlock } from './world/world-storage3d.js'; import { genColumn, surfaceGyAt, resetGenerationCache } from './world/generation3d.js'; import { initDB, loadGame, applySave, saveGame } from './game/save.js'; import { loop } from './game/loop3d.js'; import { initChat } from './ui/chat.js'; import { initFurnace } from './ui/furnace.js'; import { initMinimap } from './ui/minimap.js'; import { initSaveControls, updateSaveButtonVisibility } from './ui/save-controls.js'; import { initMarket } from './ui/market.js'; import { initSettings } from './ui/settings.js'; import { initRespawn } from './ui/respawn.js'; import { initShare } from './ui/share.js'; import { initControls } from './input/controls.js'; import { initModes } from './game/modes.js'; import { initVoice } from './multiplayer/voice-chat.js'; import { rebuildHotbar } from './ui/hotbar.js'; import { toggleCraft, closeCraft, toggleInventory, closeInventory } from './ui/craft.js'; // ==================== 1) CONFIG — parse URL ==================== // urlParams defined in config.js state.SERVER_URL = SERVER_URL; state.TELEGRAM_BOT_USERNAME = 'Grechkacraft_bot'; state.TELEGRAM_APP_SHORT_NAME = 'minegrechka'; // Set TG-related state if (isTgWebApp) { state.myTgId = tgUser?.id || null; } state.heroImg = new Image(); state.heroImg.src = 'https://supamg.mkn8n.ru/storage/v1/object/public/img//grechka.png'; // ==================== 2) START MENU ==================== // Show the start menu before initializing the game. // The menu returns a Promise that resolves with the worldId. async function startGame() { const worldId = await showStartMenu(); // Set worldId and playerName state.worldId = worldId; if (!state.playerName) { state.playerName = localStorage.getItem('minegrechka_playerName') || 'Игрок'; localStorage.setItem('minegrechka_playerName', state.playerName); } console.log('[Launch] worldId:', state.worldId, 'player:', state.playerName, 'tg:', isTgWebApp); // Validate TG initData if in TG context if (isTgWebApp && window.Telegram && Telegram.WebApp.initData) { try { const resp = await fetch(state.SERVER_URL + '/api/tg/auth', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ initData: Telegram.WebApp.initData }) }); const data = await resp.json(); if (data.ok && data.tg_id) { console.log('[TG] Authenticated, tg_id:', data.tg_id); state.myTgId = data.tg_id; } } catch (e) { console.warn('[TG] Auth failed:', e); } } // Update URL with world code try { const newUrl = new URL(window.location.href); newUrl.searchParams.set('world', state.worldId); if (typeof history !== 'undefined' && history.replaceState) { history.replaceState(null, '', newUrl.toString()); } } catch (e) {} // ==================== 3) CANVAS — init from canvas module ==================== initCanvas(); // ==================== 4) STATE — set DOM refs ==================== state.hpEl = document.getElementById('hp'); state.foodEl = document.getElementById('food'); state.sxEl = document.getElementById('sx'); state.syEl = document.getElementById('sy'); state.todEl = document.getElementById('tod'); state.worldIdEl = document.getElementById('worldId'); state.playerCountEl = document.getElementById('playerCount'); state.deathEl = document.getElementById('death'); state.hotbarEl = document.getElementById('hotbar'); state.craftPanel = document.getElementById('craftPanel'); state.recipesEl = document.getElementById('recipes'); state.inventoryPanel = document.getElementById('inventoryPanel'); state.inventoryGrid = document.getElementById('inventoryGrid'); state.marketPanel = document.getElementById('marketPanel'); state.marketContent = document.getElementById('marketContent'); state.tradePanel = document.getElementById('tradePanel'); state.tradeContent = document.getElementById('tradeContent'); state.multiplayerStatus = document.getElementById('multiplayerStatus'); // ==================== 5) TEXTURES — initTextures() ==================== initTextures(); state.tex = tex; // ==================== 6) CRAFT/INVENTORY BUTTONS ==================== const craftBtn = document.getElementById('craftBtn'); const invToggle = document.getElementById('invToggle'); const craftClose = document.getElementById('craftClose'); const inventoryClose = document.getElementById('inventoryClose'); if (craftBtn) craftBtn.onclick = () => toggleCraft(); if (craftClose) craftClose.onclick = () => closeCraft(); if (invToggle) invToggle.onclick = () => toggleInventory(); if (inventoryClose) inventoryClose.onclick = () => closeInventory(); // ==================== 7) CONTROLS — initControls ==================== initControls(); // ==================== 9) UI MODULES — init all UI ==================== initChat(); initFurnace(); initMinimap(); initSaveControls(); initMarket(); initRespawn(); initShare(); initSettings(); initModes(); // ==================== 10) SOCKET — initSocket ==================== initSocket(); // ==================== 11) VOICE — initVoice ==================== initVoice(); // ==================== 12) SAVE — loadGame, applySave ==================== // Ensure hotbar has items on first load if (state.recentItems.length === 0) { state.recentItems = Object.keys(state.inv).filter(k => state.inv[k] > 0).slice(0, 5); } // Make sure selected item is valid and in recentItems if (!state.selected || !state.inv[state.selected] || state.inv[state.selected] <= 0) { state.selected = state.recentItems[0] || 'dirt'; } if (!state.recentItems.includes(state.selected)) { state.recentItems.unshift(state.selected); state.recentItems = state.recentItems.slice(0, 5); } rebuildHotbar(); // Keyboard hotbar switching (1/2/3) document.addEventListener('keydown', (e) => { if (state.showFullInventory || state.craftOpen || state.inventoryOpen || state.chatOpen) return; if (e.key >= '1' && e.key <= '3') { const items = state.recentItems.filter(id => state.inv[id] > 0).slice(0, 3); const idx = parseInt(e.key) - 1; if (items[idx]) { state.selected = items[idx]; rebuildHotbar(); } } }); // Scroll wheel hotbar cycling document.addEventListener('wheel', (e) => { if (state.showFullInventory || state.craftOpen || state.inventoryOpen || state.chatOpen) return; const items = state.recentItems.filter(id => state.inv[id] > 0).slice(0, 3); if (items.length === 0) return; let idx = items.indexOf(state.selected); if (idx < 0) idx = 0; if (e.deltaY > 0) idx = (idx + 1) % items.length; else idx = (idx - 1 + items.length) % items.length; state.selected = items[idx]; rebuildHotbar(); e.preventDefault(); }, { passive: false }); initDB().then(async () => { const loadedSave = await loadGame(); if (loadedSave) { await applySave(loadedSave); console.log('Загружено сохранение, HP:', state.player.hp); if (state.player.hp <= 0) { console.log('WARNING: HP <= 0 после загрузки, возрождаемся'); state.player.hp = 100; state.player.hunger = 100; state.player.o2 = 100; state.player.x = state.spawnPoint.x; state.player.y = state.spawnPoint.y; state.player.vx = state.player.vy = 0; state.player.invuln = 0; state.player.fallStartY = state.player.y; } } else { console.log('Сохранение не найдено, начинаем новую игру'); // Fill recentItems from inventory state.recentItems = Object.keys(state.inv).filter(k => state.inv[k] > 0).slice(0, 5); rebuildHotbar(); state.player.hp = 100; state.player.hunger = 100; state.player.o2 = 100; state.player.vx = state.player.vy = 0; state.player.invuln = 0; // старт — на поверхности const startGX = 6, startGZ = 0; state.player.x = startGX * state.TILE; state.player.z = startGZ * state.TILE; state.player.vx = state.player.vy = state.player.vz = 0; for (let dx = -4; dx <= 4; dx++) { for (let dz = -20; dz <= 20; dz++) genColumn(startGX + dx, startGZ + dz); } const surfaceY = surfaceGyAt(startGX, startGZ); let safeGY = surfaceY - 1; const aboveBlock = getBlock(startGX, surfaceY - 1, startGZ); if (aboveBlock && (aboveBlock.t === 'water' || aboveBlock.t === 'swamp_water')) { for (let gy = state.SEA_GY - 1; gy >= 0; gy--) { const b = getBlock(startGX, gy, startGZ); if (!b || b.dead || b.t === 'air' || b.t === 'water') continue; safeGY = gy - 1; break; } } state.player.y = safeGY * state.TILE; state.player.fallStartY = state.player.y; state.spawnPoint.x = state.player.x; state.spawnPoint.y = state.player.y; state.spawnPoint.z = state.player.z; console.log('Новая 3D игра: gx=', startGX, 'gz=', startGZ, 'surfaceY=', surfaceY, 'y=', state.player.y); } // Автосейв при скрытии страницы document.addEventListener('visibilitychange', () => { if (document.hidden) { saveGame(); } }); // Автосейв перед закрытием страницы window.addEventListener('beforeunload', () => { saveGame(); }); }).catch(err => { console.error('Ошибка инициализации:', err); const startGX = 6, startGZ = 0; genColumn(startGX, startGZ); state.player.y = (surfaceGyAt(startGX, startGZ) - 1) * state.TILE; state.player.fallStartY = state.player.y; for (let gx = startGX - 4; gx <= startGX + 4; gx++) { for (let gz = -20; gz <= 20; gz++) genColumn(gx, startGZ + gz); } }); // ==================== 13) LOOP — startLoop ==================== requestAnimationFrame(loop); } // Start the game (v3 — 3D) startGame().catch(err => { console.error('Fatal error starting game:', err); }); // Debug: give items window.debugGive = function(id, count) { state.inv[id] = (state.inv[id] || 0) + (count || 1); if (!state.recentItems.includes(id)) { state.recentItems.unshift(id); state.recentItems = state.recentItems.slice(0, 5); } rebuildHotbar(); // Can't call renderInventory from here, but user can click inv console.log('Gave', count || 1, id, '=>', state.inv[id]); console.log('inv keys>0:', Object.keys(state.inv).filter(k => state.inv[k] > 0).join(', ')); }; window.debugInv = function() { return Object.keys(state.inv).filter(k => state.inv[k] > 0).map(k => k + ':' + state.inv[k]); };