233 lines
9.0 KiB
PHP
233 lines
9.0 KiB
PHP
<?php
|
||
namespace Game\System;
|
||
|
||
use Game\Event\Event;
|
||
use Game\Event\EventListenerInterface;
|
||
use Game\Event\EventDispatcher;
|
||
use Game\Model\Item;
|
||
use Game\Model\Player;
|
||
use Game\Model\NPC; // ⭐ 新增
|
||
use Game\Database\ItemRepository; // ⭐ 新增
|
||
use Symfony\Component\Console\Input\InputInterface;
|
||
use Symfony\Component\Console\Output\OutputInterface;
|
||
use Symfony\Component\Console\Helper\QuestionHelper;
|
||
use Symfony\Component\Console\Question\Question;
|
||
|
||
/**
|
||
* ShopService: 负责处理商店的购买和出售交易。
|
||
*/
|
||
class ShopService implements EventListenerInterface {
|
||
|
||
private EventDispatcher $dispatcher;
|
||
private StateManager $stateManager;
|
||
private InputInterface $input;
|
||
private OutputInterface $output;
|
||
private QuestionHelper $helper;
|
||
private ItemRepository $itemRepository; // ⭐ 新增
|
||
|
||
// 商店的固定库存(通常从DPC配置加载)
|
||
private array $shopInventory;
|
||
private ?NPC $currentShopNpc = null; // ⭐ 当前商店NPC
|
||
|
||
public function __construct(
|
||
EventDispatcher $dispatcher,
|
||
StateManager $stateManager,
|
||
InputInterface $input,
|
||
OutputInterface $output,
|
||
QuestionHelper $helper,
|
||
ItemRepository $itemRepository // ⭐ 新增
|
||
) {
|
||
$this->dispatcher = $dispatcher;
|
||
$this->stateManager = $stateManager;
|
||
$this->input = $input;
|
||
$this->output = $output;
|
||
$this->helper = $helper;
|
||
$this->itemRepository = $itemRepository; // ⭐ 赋值
|
||
$this->loadShopInventory();
|
||
}
|
||
|
||
private function loadShopInventory(): void {
|
||
// 模拟商店出售的物品配置 (ID 对应 LootService::loadItemData)
|
||
$this->shopInventory = [
|
||
// 商店物品 ID => 价格倍数(如果价格不是 Item::value)
|
||
1 => ['stock' => 10, 'price' => 10], // 小药水
|
||
3 => ['stock' => 5, 'price' => 100], // 新物品:高级药水
|
||
];
|
||
}
|
||
|
||
public function handleEvent(Event $event): void {
|
||
switch ($event->getType()) {
|
||
case 'OpenShopEvent': // 响应 InteractionSystem 的请求
|
||
$payload = $event->getPayload();
|
||
$npc = $payload['npc'] ?? null;
|
||
if ($npc && $npc->hasShop) {
|
||
$this->currentShopNpc = $npc;
|
||
$this->shopInventory = $npc->shopInventory;
|
||
$this->startShopping();
|
||
} else {
|
||
$this->startShopping(); // 使用默认库存
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 启动商店界面和循环
|
||
*/
|
||
private function startShopping(): void {
|
||
$npcName = $this->currentShopNpc ? $this->currentShopNpc->getName() : '商人';
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "\n🛍️ 欢迎光临 <fg=yellow>{$npcName}</> 的商店!"]));
|
||
$running = true;
|
||
|
||
while ($running) {
|
||
$player = $this->stateManager->getPlayer();
|
||
$this->displayShopMenu($player);
|
||
|
||
$question = new Question("> 请选择操作 (B:购买 | S:出售 | X:退出):");
|
||
$choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
|
||
|
||
switch ($choice) {
|
||
case 'B':
|
||
$this->handlePurchase();
|
||
break;
|
||
case 'S':
|
||
$this->handleSelling();
|
||
break;
|
||
case 'X':
|
||
$running = false;
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '下次再来!']));
|
||
// ⭐ 返回地图模式
|
||
$this->stateManager->setMode(StateManager::MODE_MAP);
|
||
$this->dispatcher->dispatch(new Event('ShowMenuEvent'));
|
||
break;
|
||
default:
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的指令。']));
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- 购买逻辑 ---
|
||
|
||
private function handlePurchase(): void {
|
||
$this->displaySaleItems();
|
||
$player = $this->stateManager->getPlayer();
|
||
|
||
$question = new Question("> 输入要购买的物品编号 (或 X 退出):");
|
||
$choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
|
||
|
||
if ($choice === 'X') return;
|
||
|
||
if (is_numeric($choice)) {
|
||
$itemId = (int)$choice;
|
||
if (isset($this->shopInventory[$itemId])) {
|
||
$price = $this->shopInventory[$itemId]['price'];
|
||
|
||
if ($player->spendGold($price)) {
|
||
// ⭐ 触发事件请求 LootService 给予物品(重用 LootService 的逻辑)
|
||
$this->dispatcher->dispatch(new Event('ShopPurchaseEvent', ['itemId' => $itemId]));
|
||
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||
'message' => "交易成功!花费 {$price} 💰,剩余 {$player->getGold()} 💰。"
|
||
]));
|
||
} else {
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => "金币不足!你需要 {$price} 💰。"]));
|
||
}
|
||
} else {
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '商店没有这个编号的物品。']));
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- 出售逻辑 ---
|
||
|
||
private function handleSelling(): void {
|
||
$player = $this->stateManager->getPlayer();
|
||
if (empty($player->getInventory())) {
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '你的背包是空的,没有什么可卖的。']));
|
||
return;
|
||
}
|
||
|
||
$this->displaySellableItems($player);
|
||
|
||
$question = new Question("> 输入要出售的物品编号 (背包索引 | 或 X 退出):");
|
||
$choice = strtoupper($this->helper->ask($this->input, $this->output, $question) ?? '');
|
||
|
||
if ($choice === 'X') return;
|
||
|
||
if (is_numeric($choice)) {
|
||
$index = (int)$choice;
|
||
$inventory = $player->getInventory();
|
||
|
||
if (isset($inventory[$index])) {
|
||
$item = $inventory[$index];
|
||
// 简化:出售价格为 Item::value 的一半
|
||
$sellPrice = floor($item->value / 2);
|
||
|
||
// 移除物品并获得金币
|
||
$player->removeItemByIndex($index);
|
||
$player->gainGold($sellPrice);
|
||
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', [
|
||
'message' => "💰 出售了 <fg=white>{$item->name}</>,获得 {$sellPrice} 💰。剩余 {$player->getGold()} 💰。"
|
||
]));
|
||
|
||
// 触发 UI 更新
|
||
$this->dispatcher->dispatch(new Event('InventoryUpdateEvent'));
|
||
|
||
} else {
|
||
$this->dispatcher->dispatch(new Event('SystemMessage', ['message' => '无效的背包编号。']));
|
||
}
|
||
}
|
||
}
|
||
|
||
// --- UI 辅助方法 ---
|
||
|
||
private function displayShopMenu(Player $player): void {
|
||
$this->output->writeln("\n--- 商店主页 ---");
|
||
$this->output->writeln("你的金币: <fg=yellow>{$player->getGold()}</> 💰");
|
||
$this->output->writeln("--------------------------");
|
||
}
|
||
|
||
private function displaySaleItems(): void {
|
||
$this->output->writeln("\n--- 🛍️ 商店出售 ---");
|
||
|
||
if (empty($this->shopInventory)) {
|
||
$this->output->writeln(" <fg=gray>商店没有货物。</>");
|
||
$this->output->writeln("--------------------------");
|
||
return;
|
||
}
|
||
|
||
foreach ($this->shopInventory as $itemId => $data) {
|
||
// ⭐ 从 ItemRepository 获取真实数据
|
||
$itemData = $this->itemRepository->find($itemId);
|
||
if ($itemData) {
|
||
$name = $itemData['name'] ?? "未知物品";
|
||
$price = $data['price'] ?? $itemData['value'] ?? 0;
|
||
$type = $itemData['type'] ?? '';
|
||
|
||
$typeLabel = match($type) {
|
||
'potion' => '<fg=green>药水</>',
|
||
'weapon' => '<fg=red>武器</>',
|
||
'armor' => '<fg=blue>护甲</>',
|
||
default => $type
|
||
};
|
||
|
||
$this->output->writeln("[<fg=green>{$itemId}</>] <fg=white;options=bold>{$name}</> {$typeLabel} | 价格: <fg=yellow>{$price}</> 💰");
|
||
}
|
||
}
|
||
$this->output->writeln("--------------------------");
|
||
}
|
||
|
||
private function displaySellableItems(Player $player): void {
|
||
$this->output->writeln("\n--- 📦 出售你的物品 ---");
|
||
$inventory = $player->getInventory();
|
||
|
||
if (empty($inventory)) return;
|
||
|
||
foreach ($inventory as $index => $item) {
|
||
$sellPrice = floor($item->value / 2);
|
||
$this->output->writeln("[<fg=green>{$index}</>] <fg=white>{$item->name}</> | 售价: <fg=yellow>{$sellPrice}</> 💰");
|
||
}
|
||
$this->output->writeln("--------------------------");
|
||
}
|
||
} |