168 lines
6.2 KiB
PHP
168 lines
6.2 KiB
PHP
<?php
|
||
namespace Game\System;
|
||
|
||
use Game\Model\Item;
|
||
use Game\Model\Player;
|
||
use Game\Event\Event;
|
||
use Game\Event\EventDispatcher;
|
||
use Game\Model\Quest;
|
||
|
||
/**
|
||
* SaveLoadService: 负责将玩家状态持久化,并支持装备与地图位置的还原。
|
||
*/
|
||
class SaveLoadService {
|
||
|
||
private EventDispatcher $dispatcher;
|
||
private StateManager $stateManager;
|
||
private string $savePath;
|
||
|
||
public function __construct(EventDispatcher $dispatcher, StateManager $stateManager, string $savePath = 'save/player.json') {
|
||
$this->dispatcher = $dispatcher;
|
||
$this->stateManager = $stateManager;
|
||
$this->savePath = $savePath;
|
||
|
||
if (!is_dir(dirname($this->savePath))) {
|
||
mkdir(dirname($this->savePath), 0777, true);
|
||
}
|
||
}
|
||
|
||
public function hasSaveFile(): bool {
|
||
return file_exists($this->savePath);
|
||
}
|
||
|
||
/**
|
||
* 将完整游戏状态保存
|
||
*/
|
||
public function saveGame(): void {
|
||
$player = $this->stateManager->getPlayer();
|
||
$currentTile = $this->stateManager->getCurrentTile();
|
||
|
||
if (!$player) return;
|
||
|
||
try {
|
||
// 1. 序列化背包
|
||
$serializedInventory = array_map(fn(Item $item) => $this->itemToData($item), $player->getInventory());
|
||
|
||
// 2. ⭐ 新增:序列化当前装备槽位
|
||
$serializedEquipment = [];
|
||
foreach ($player->getEquipment() as $slot => $item) {
|
||
$serializedEquipment[$slot] = $item ? $this->itemToData($item) : null;
|
||
}
|
||
|
||
// 3. 序列化任务
|
||
$activeQuests = [];
|
||
foreach ($player->getActiveQuests() as $quest) {
|
||
$activeQuests[] = [
|
||
'config' => $quest->toArray(),
|
||
'currentCount' => $quest->getCurrentCount()
|
||
];
|
||
}
|
||
|
||
$data = [
|
||
'player' => [
|
||
'name' => $player->getName(),
|
||
'health' => $player->getHealth(),
|
||
'maxHealth' => $player->getMaxHealth(),
|
||
'base_attack' => $player->attack, // 保存基础值
|
||
'base_defense' => $player->defense,
|
||
'level' => $player->getLevel(),
|
||
'currentXp' => $player->getCurrentXp(),
|
||
'gold' => $player->getGold(),
|
||
'inventory' => $serializedInventory,
|
||
'equipment' => $serializedEquipment, // ⭐ 装备持久化
|
||
'activeQuests' => $activeQuests,
|
||
'completedQuests' => $player->getCompletedQuests(),
|
||
],
|
||
'world' => [
|
||
'currentTileId' => $currentTile->id // ⭐ 位置持久化
|
||
]
|
||
];
|
||
|
||
file_put_contents($this->savePath, json_encode($data, JSON_PRETTY_PRINT));
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '💾 进度已安全存入魔法卷轴!']));
|
||
|
||
} catch (\Exception $e) {
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '❌ 存档失败: ' . $e->getMessage()]));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 加载存档并还原所有复杂对象
|
||
*/
|
||
public function loadGame(): ?Player {
|
||
if (!$this->hasSaveFile()) return null;
|
||
|
||
try {
|
||
$data = json_decode(file_get_contents($this->savePath), true);
|
||
$pData = $data['player'];
|
||
|
||
// 1. 创建基础玩家对象
|
||
$player = new Player($pData['name'], $pData['maxHealth'], $pData['base_attack'], $pData['base_defense']);
|
||
$player->setHealth($pData['health']);
|
||
$player->setLevel($pData['level']);
|
||
$player->setCurrentXp($pData['currentXp']);
|
||
$player->setGold($pData['gold']);
|
||
$player->setCompletedQuests($pData['completedQuests'] ?? []);
|
||
|
||
// 2. 还原背包物品 (包含 Stats 属性)
|
||
$inventory = array_map(fn($itemData) => $this->dataToItem($itemData), $pData['inventory']);
|
||
$player->setInventory($inventory);
|
||
|
||
// 3. ⭐ 还原装备 (确保 Stats 效果重新生效)
|
||
foreach ($pData['equipment'] as $slot => $itemData) {
|
||
if ($itemData) {
|
||
$item = $this->dataToItem($itemData);
|
||
$player->equipItem($item); // 使用装备方法,确保属性刷新
|
||
}
|
||
}
|
||
|
||
// 4. 还原任务进度
|
||
$activeQuests = [];
|
||
foreach ($pData['activeQuests'] as $qData) {
|
||
$config = $qData['config'];
|
||
$quest = new Quest($config['id'], $config['name'], $config['description'], $config['type'], $config['target'], $config['rewards']);
|
||
$quest->setCurrentCount($qData['currentCount']);
|
||
$activeQuests[$config['id']] = $quest;
|
||
}
|
||
$player->setActiveQuests($activeQuests);
|
||
|
||
// 5. ⭐ 还原地图位置
|
||
if (isset($data['world']['currentTileId'])) {
|
||
$this->stateManager->setCurrentTileId($data['world']['currentTileId']);
|
||
}
|
||
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '📂 欢迎回来,勇者!进度已加载。']));
|
||
return $player;
|
||
|
||
} catch (\Exception $e) {
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '❌ 加载失败: ' . $e->getMessage()]));
|
||
return null;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 辅助:Item 转 数组 (包含新增的 stats 字段)
|
||
*/
|
||
private function itemToData(Item $item): array {
|
||
return [
|
||
'id' => $item->id,
|
||
'name' => $item->name,
|
||
'type' => $item->type,
|
||
'description' => $item->description,
|
||
'value' => $item->value,
|
||
'effects' => $item->effects ?? [],
|
||
'stats' => $item->stats ?? [], // ⭐ 对应 D 阶段的属性加成
|
||
'slot' => $item->slot ?? null
|
||
];
|
||
}
|
||
|
||
/**
|
||
* 辅助:数组 转 Item
|
||
*/
|
||
private function dataToItem(array $d): Item {
|
||
$item = new Item($d['id'], $d['name'], $d['type'], $d['description'], $d['value'], $d['effects']);
|
||
$item->stats = $d['stats'] ?? [];
|
||
$item->slot = $d['slot'] ?? null;
|
||
return $item;
|
||
}
|
||
} |