283 lines
11 KiB
JavaScript
283 lines
11 KiB
JavaScript
// ==================== 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-storage.js';
|
|
import { genColumn, surfaceGyAt } from './world/generation.js';
|
|
import { initDB, loadGame, applySave, saveGame } from './game/save.js';
|
|
import { loop } from './game/loop.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 { initMouseHandlers } from './input/mouse-handler.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();
|
|
|
|
// ==================== 8) MOUSE — initMouseHandlers ====================
|
|
initMouseHandlers();
|
|
|
|
// ==================== 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;
|
|
for (let dx = -3; dx <= 3; dx++) genColumn(startGX + dx);
|
|
const surfaceY = surfaceGyAt(startGX);
|
|
let safeGY = surfaceY - 1;
|
|
const aboveBlock = getBlock(startGX, surfaceY - 1);
|
|
if (aboveBlock && aboveBlock.t === 'water') {
|
|
for (let gy = state.SEA_GY - 1; gy >= 0; gy--) {
|
|
const b = getBlock(startGX, gy);
|
|
if (!b || b.dead || b.t === 'air' || b.t === 'water') continue;
|
|
safeGY = gy - 1;
|
|
break;
|
|
}
|
|
}
|
|
state.player.y = safeGY * state.TILE;
|
|
state.player.x = startGX * state.TILE;
|
|
state.player.fallStartY = state.player.y;
|
|
|
|
state.spawnPoint.x = state.player.x;
|
|
state.spawnPoint.y = state.player.y;
|
|
|
|
console.log('Новая игра: startGX=', startGX, 'surfaceY=', surfaceY, 'player.y=', state.player.y);
|
|
|
|
// Генерируем карту вокруг стартовой позиции
|
|
for (let gx = startGX - 50; gx <= startGX + 50; gx++) {
|
|
genColumn(gx);
|
|
}
|
|
}
|
|
|
|
// Автосейв при скрытии страницы
|
|
document.addEventListener('visibilitychange', () => {
|
|
if (document.hidden) {
|
|
saveGame();
|
|
}
|
|
});
|
|
|
|
// Автосейв перед закрытием страницы
|
|
window.addEventListener('beforeunload', () => {
|
|
saveGame();
|
|
});
|
|
}).catch(err => {
|
|
console.error('Ошибка инициализации:', err);
|
|
const startGX = 6;
|
|
genColumn(startGX);
|
|
state.player.y = (surfaceGyAt(startGX) - 1) * state.TILE;
|
|
state.player.fallStartY = state.player.y;
|
|
|
|
for (let gx = startGX - 50; gx <= startGX + 50; gx++) {
|
|
genColumn(gx);
|
|
}
|
|
});
|
|
|
|
// ==================== 13) LOOP — startLoop ====================
|
|
requestAnimationFrame(loop);
|
|
}
|
|
|
|
// Start the game
|
|
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]); };
|