201 lines
7.8 KiB
JavaScript
201 lines
7.8 KiB
JavaScript
// ==================== СИСТЕМА СОХРАНЕНИЯ ИГРЫ ====================
|
||
import { state } from '../core/state.js';
|
||
import { getBlock, setBlock, removeBlock } from '../world/world-storage3d.js';
|
||
import { resetGenerationCache, ensureGenAroundCamera } from '../world/generation3d.js';
|
||
import { rebuildHotbar } from '../ui/hotbar.js';
|
||
import { renderInventory } from '../ui/inventory.js';
|
||
|
||
const SAVE_STORAGE_KEY = state.SAVE_KEY;
|
||
let db = null; // Оставляем для совместимости, но не используем
|
||
|
||
// Инициализация (localStorage + in-memory fallback)
|
||
export function initDB() {
|
||
return new Promise((resolve) => {
|
||
console.log('Используем localStorage для сохранений (sandbox режим)');
|
||
resolve(null);
|
||
});
|
||
}
|
||
|
||
export function saveGame() {
|
||
const saveData = {
|
||
version: 2,
|
||
worldSeed: state.worldSeed,
|
||
player: {
|
||
x: state.player.x,
|
||
y: state.player.y,
|
||
hp: state.player.hp,
|
||
hunger: state.player.hunger,
|
||
o2: state.player.o2
|
||
},
|
||
inventory: state.inv,
|
||
time: state.worldTime,
|
||
isNight: state.isNightTime,
|
||
dayNightCycle: state.dayNightCycle,
|
||
// Сохраняем только изменения
|
||
placedBlocks: state.placedBlocks.slice(),
|
||
removedBlocks: state.removedBlocks.slice()
|
||
};
|
||
|
||
const saveSize = JSON.stringify(saveData).length;
|
||
console.log('Сохранение: player HP:', state.player.hp, 'hunger:', state.player.hunger, 'o2:', state.player.o2);
|
||
|
||
// Пробуем сохранить в localStorage (основной метод для персистентности)
|
||
try {
|
||
localStorage.setItem(SAVE_STORAGE_KEY, JSON.stringify(saveData));
|
||
console.log(`Игра сохранена в localStorage (размер: ${saveSize} байт)`);
|
||
} catch (e) {
|
||
console.warn('Ошибка сохранения в localStorage, используем только in-memory:', e);
|
||
|
||
// Если localStorage недоступен, используем in-memory fallback
|
||
state.inMemorySave = saveData;
|
||
console.log(`Игра сохранена в памяти (размер: ${saveSize} байт)`);
|
||
}
|
||
}
|
||
|
||
export function loadGame() {
|
||
return new Promise((resolve, reject) => {
|
||
// Пробуем localStorage
|
||
try {
|
||
const localSave = localStorage.getItem(SAVE_STORAGE_KEY);
|
||
if (localSave) {
|
||
const parsed = JSON.parse(localSave);
|
||
console.log('Загружено из localStorage, player HP:', parsed.player?.hp);
|
||
resolve(parsed);
|
||
return;
|
||
}
|
||
} catch (e) {
|
||
console.warn('Ошибка доступа к localStorage:', e);
|
||
}
|
||
|
||
// Если localStorage недоступен, используем in-memory сохранение
|
||
if (state.inMemorySave) {
|
||
console.log('Загружено из in-memory сохранения, player HP:', state.inMemorySave.player?.hp);
|
||
resolve(state.inMemorySave);
|
||
return;
|
||
}
|
||
|
||
console.log('Сохранение не найдено');
|
||
resolve(null);
|
||
});
|
||
}
|
||
|
||
// Миграция с версии 1 на версию 2
|
||
export function migrateV1toV2(saveData) {
|
||
console.log('Миграция сохранения с версии 1 на версию 2...');
|
||
|
||
// Сохраняем seed из текущей игры (так как v1 его не хранил)
|
||
saveData.worldSeed = state.worldSeed;
|
||
|
||
// Инициализируем массивы изменений
|
||
saveData.placedBlocks = [];
|
||
saveData.removedBlocks = [];
|
||
|
||
// Для v1 нам нужно сравнить сгенерированные блоки с тем, что должно быть по seed
|
||
// Это сложно сделать без полной перегенерации, поэтому для v1 сохраняем только seed
|
||
// и при загрузке просто перегенерируем мир
|
||
|
||
// Удаляем старые данные
|
||
delete saveData.generatedBlocks;
|
||
|
||
saveData.version = 2;
|
||
console.log('Миграция завершена');
|
||
}
|
||
|
||
export async function applySave(saveData) {
|
||
if (!saveData) return;
|
||
|
||
console.log('=== applySave START ===');
|
||
console.log('player HP before applySave:', state.player.hp);
|
||
console.log('saveData.player.hp:', saveData.player?.hp);
|
||
|
||
// Миграция версий
|
||
if (saveData.version === 1) {
|
||
migrateV1toV2(saveData);
|
||
}
|
||
|
||
// Восстанавливаем seed
|
||
if (saveData.worldSeed !== undefined) {
|
||
state.worldSeed = saveData.worldSeed;
|
||
}
|
||
|
||
// Восстанавливаем игрока
|
||
if (saveData.player) {
|
||
// ВАЖНО: в 3D координаты из сейва НЕ применяем. Сейв 2D-версии содержит
|
||
// y в другой системе (там игрок мог стоять на 2D-поверхности), из-за чего
|
||
// после загрузки player.y улетал на +119 тайлов (5286 вместо 526) — игрок
|
||
// оказывался под миром и падал на дно. Позицию ставит ensurePlayerOnGround
|
||
// после генерации, а из сейва берём только состояние.
|
||
state.player.hunger = saveData.player.hunger;
|
||
state.player.o2 = saveData.player.o2;
|
||
// Опыт и уровень восстанавливаем с проверкой: старые сейвы полей не
|
||
// содержат, и undefined в player.xp ломает арифметику уровня.
|
||
state.player.xp = Number(saveData.player.xp) || 0;
|
||
state.player.level = Number(saveData.player.level) || 1;
|
||
|
||
// Проверяем HP из сохранения - если <= 0, устанавливаем 100
|
||
const savedHP = saveData.player.hp;
|
||
console.log('Saved HP from file:', savedHP);
|
||
if (savedHP <= 0) {
|
||
console.log('WARNING: Saved HP is <= 0, setting to 100!');
|
||
state.player.hp = 100;
|
||
} else {
|
||
state.player.hp = savedHP;
|
||
}
|
||
console.log('player HP after restore:', state.player.hp);
|
||
console.log('Сохранение: координаты игрока НЕ применяются в 3D (позицию ставит ensurePlayerOnGround)');
|
||
} else {
|
||
console.log('No player data in save, setting default HP: 100');
|
||
state.player.hp = 100;
|
||
}
|
||
|
||
console.log('=== applySave END ===');
|
||
|
||
// Восстанавливаем инвентарь
|
||
if (saveData.inventory) {
|
||
for (const key in saveData.inventory) {
|
||
state.inv[key] = saveData.inventory[key];
|
||
}
|
||
}
|
||
|
||
// Восстанавливаем время
|
||
if (saveData.time !== undefined) {
|
||
state.worldTime = saveData.time;
|
||
}
|
||
|
||
// Восстанавливаем день/ночь
|
||
if (saveData.isNight !== undefined) {
|
||
state.isNightTime = saveData.isNight;
|
||
}
|
||
if (saveData.dayNightCycle !== undefined) {
|
||
state.dayNightCycle = saveData.dayNightCycle;
|
||
}
|
||
|
||
// Перегенерируем мир по seed
|
||
resetGenerationCache();
|
||
ensureGenAroundCamera();
|
||
|
||
// Применяем изменения (только для v2)
|
||
if (saveData.version === 2) {
|
||
// Применяем блоки, установленные игроком
|
||
for (const block of saveData.placedBlocks) {
|
||
setBlock(block.gx, block.gy, block.gz || 0, block.t, true);
|
||
}
|
||
|
||
// Применяем удалённые блоки
|
||
for (const block of saveData.removedBlocks) {
|
||
removeBlock(block.gx, block.gy, block.gz || 0);
|
||
}
|
||
|
||
// Восстанавливаем массивы изменений
|
||
state.placedBlocks = saveData.placedBlocks || [];
|
||
state.removedBlocks = saveData.removedBlocks || [];
|
||
}
|
||
|
||
// Fill recentItems from inventory if empty (hotbar won.t render otherwise)
|
||
if (state.recentItems.length === 0) {
|
||
state.recentItems = Object.keys(state.inv).filter(id => state.inv[id] > 0).slice(0, 5);
|
||
}
|
||
rebuildHotbar();
|
||
renderInventory();
|
||
console.log('Игра загружена');
|
||
} |