idle/src/Modules/InventoryPanel.php
2025-12-12 18:23:42 +08:00

710 lines
26 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Game\Modules;
use Game\Core\Game;
use Game\Core\Screen;
use Game\Core\ItemDisplay;
use Game\Core\SpellDisplay;
use Game\Core\Colors;
use Game\Modules\Bag\Equipment;
use Game\Modules\Bag\Item;
class InventoryPanel
{
private array $rarityColors = [
'common' => Colors::WHITE,
'uncommon' => Colors::GREEN,
'rare' => Colors::BLUE,
'epic' => Colors::MAGENTA,
'legendary' => Colors::YELLOW,
];
private string $reset = Colors::RESET;
private int $perPage = 8;
private array $categories = [
'all' => '全部',
'weapon' => '武器',
'armor' => '护甲',
'boots' => '靴子',
'ring' => '戒指',
'necklace' => '项链',
'equipment' => '装备',
'consume' => '消耗品',
'quest_item' => '任务物品',
];
public function __construct(public Game $game) {}
public function show(int $page = 1, string $category = 'equipment')
{
$out = $this->game->output;
Screen::clear($out);
$player = $this->game->player;
// Display compact stats panel
$this->renderCompactStats();
$allItems = array_values($player->inventory);
// Filter items by category
if ($category == 'equipment'){
$items = array_values(array_filter($allItems, fn($item) =>
$item['type'] !== 'consume' && $item['type'] !== 'quest_item'));
}else{
$items = $category === 'all'
? $allItems
: array_values(array_filter($allItems, fn($item) => $item['type'] === $category));
}
$total = count($items);
$totalPages = max(1, ceil($total / $this->perPage));
$page = max(1, min($page, $totalPages));
$start = ($page - 1) * $this->perPage;
$pageItems = array_slice($items, $start, $this->perPage);
$categoryName = $this->categories[$category] ?? '全部';
$out->writeln("");
$out->writeln("╔════════════════════════════╗");
$out->writeln("║ 背 包 [{$categoryName}] ║");
$out->writeln("║ 第 {$page} / {$totalPages} 页 ║");
$out->writeln("╠════════════════════════════╣");
$out->writeln("║ 🟢 小绿瓶: {$player->potionPool}");
$out->writeln("╚════════════════════════════╝");
if (empty($items)) {
$out->writeln("该分类下没有物品...");
$out->writeln("");
$out->writeln("[h] 使用小绿瓶回血");
$out->writeln("[c] 切换分类 | [0] 返回");
$choice = Screen::input($out, "选择操作:");
if ($choice === 'h'){
$this->autoHeal();
return $this->show($page, $category);
}
if ($choice === "c" || $choice === "C") {
return $this->showCategoryMenu($page, $category);
}
$this->game->state = Game::MENU;
return;
}
foreach ($pageItems as $i => $item) {
$index = $start + $i + 1;
// 装备使用 ItemDisplay 显示
$displayStr = Item::show($item);
$out->writeln("[{$index}] {$displayStr}");
}
$out->writeln("");
$out->writeln("[n] 下一页 | [p] 上一页 | [c] 切换分类 | [s] 批量出售");
$out->writeln("[h] 一键回血 | [0] 返回");
$choice = Screen::input($out, "选择编号或操作:");
if ($choice === "n") return $this->show($page + 1, $category);
if ($choice === "p") return $this->show($page - 1, $category);
if ($choice === "c" || $choice === "C") return $this->showCategoryMenu($page, $category);
if ($choice === "s" || $choice === "S") {
$this->batchSell();
return $this->show($page, $category);
}
if ($choice === "h" || $choice === "H") {
$this->autoHeal();
return $this->show($page, $category);
}
if ($choice == 0) {
$this->game->state = Game::MENU;
return;
}
$choice = intval($choice);
if (!isset($items[$choice - 1])) {
$out->writeln("无效编号");
Screen::sleep(1);
return $this->show($page, $category);
}
// Find the actual inventory index
$selectedItem = $items[$choice - 1];
$inventoryIndex = array_search($selectedItem, $allItems, true);
$this->showItemDetail($selectedItem, $inventoryIndex, $page, $category);
}
private function showItemDetail(array $item, int $inventoryIndex, int $page, string $category = 'all')
{
$out = $this->game->output;
Screen::clear($out);
$quantity = $item['quantity'] ?? 1;
$qtyText = $quantity > 1 ? " (拥有: {$quantity})" : "";
$out->writeln("╔════════════════════════════════════════╗");
// 使用统一的详细显示
$detailLines = Item::show($item);
$out->writeln($detailLines);
if ($quantity > 1) {
$out->writeln("");
$out->writeln("║ 拥有数量: {$quantity}");
}
// 任务物品不显示售价
if ($item['type'] !== 'quest_item') {
// 计算售价
$sellPrice = Equipment::calculateSellPrice($item);
$out->writeln("");
$out->writeln("║ 💰 售价: {$sellPrice} 灵石");
}
$out->writeln("╚════════════════════════════════════════╝");
$out->writeln("");
$isEquip = in_array($item['type'], ['weapon', 'armor', 'boots', 'ring', 'necklace']);
$isQuestItem = $item['type'] === 'quest_item';
if ($isQuestItem) {
// 任务物品无法操作
$out->writeln("这是一个关键任务物品,无法进行任何操作。");
$out->writeln("");
$out->writeln("[0] 返回");
} else {
if ($isEquip) {
$out->writeln("[1] 装备");
} elseif ($item['type'] === 'consume') {
$out->writeln("[1] 使用");
}
$out->writeln("[2] 出售");
$out->writeln("[3] 丢弃");
$out->writeln("[0] 返回");
}
$choice = Screen::input($out, "选择操作:");
if (!$isQuestItem) {
if ($choice == 1) $this->useItem($item, $inventoryIndex);
if ($choice == 2) $this->sellItem($item, $inventoryIndex);
if ($choice == 3) $this->dropItem($item, $inventoryIndex);
}
return $this->show($page, $category);
}
private function useItem(array $item, int $index)
{
$out = $this->game->output;
$player = $this->game->player;
if ($item['type'] === 'consume') {
$stats = $player->getStats();
$maxHp = $stats['maxHp'];
// 检查玩家血量
$playerNeedsHeal = $player->hp < $maxHp;
// 检查队友血量
$partnersNeedHeal = [];
foreach ($player->partners as $partner) {
$partnerStats = $partner->getStats();
$partnerMaxHp = $partnerStats['maxHp'];
if ($partner->hp < $partnerMaxHp) {
$partnersNeedHeal[] = $partner;
}
}
// 如果都不需要恢复
if (!$playerNeedsHeal && empty($partnersNeedHeal)) {
$out->writeln("你和队友的生命值都已满,无需使用!");
Screen::sleep(1);
return;
}
// 选择恢复目标
$target = null;
if ($playerNeedsHeal && !empty($partnersNeedHeal)) {
// 需要询问玩家选择
$out->writeln("");
$out->writeln("选择恢复目标:");
$out->writeln("[1] 恢复自己");
foreach ($partnersNeedHeal as $i => $partner) {
$out->writeln("[" . ($i + 2) . "] 恢复 {$partner->name}");
}
$choice = Screen::input($out, "选择:");
$choiceInt = (int)$choice;
if ($choiceInt === 1) {
$target = 'player';
} elseif ($choiceInt >= 2 && $choiceInt - 2 < count($partnersNeedHeal)) {
$target = $partnersNeedHeal[$choiceInt - 2];
} else {
$out->writeln("无效选择");
Screen::sleep(1);
return;
}
} elseif ($playerNeedsHeal) {
$target = 'player';
} elseif (!empty($partnersNeedHeal)) {
$target = $partnersNeedHeal[0];
}
// 使用消耗品进行恢复
if ($target === 'player') {
// 从小绿瓶池获取实际恢复量
$healAmount = $item['heal'];
$actualPotionHeal = $player->consumePotionPool($healAmount);
if ($actualPotionHeal == 0){
$out->writeln("小绿瓶已用尽");
Screen::sleep(1);
return;
}
$actualHeal = $player->heal($actualPotionHeal);
$out->writeln("你使用了 {$item['name']},从小绿瓶中恢复了 {$actualHeal} HP(当前: {$player->hp}/{$maxHp})");
$out->writeln("小绿瓶剩余: {$player->potionPool}");
} else {
// 恢复队友
$partnerStats = $target->getStats();
$partnerMaxHp = $partnerStats['maxHp'];
// 从小绿瓶池获取实际恢复量
$healAmount = $item['heal'];
$actualPotionHeal = $player->consumePotionPool($healAmount);
if ($actualPotionHeal == 0){
$out->writeln("小绿瓶已用尽");
Screen::sleep(1);
return;
}
$actualHeal = $target->heal($actualPotionHeal);
$out->writeln("你使用了 {$item['name']} 来恢复 {$target->name},从小绿瓶中恢复了 {$actualHeal} HP(当前: {$target->hp}/{$partnerMaxHp})");
$out->writeln("小绿瓶剩余: {$player->potionPool}");
}
// Decrease quantity or remove
if (($player->inventory[$index]['quantity'] ?? 1) > 1) {
$player->inventory[$index]['quantity']--;
$out->writeln("剩余数量: {$player->inventory[$index]['quantity']}");
} else {
unset($player->inventory[$index]);
$player->inventory = array_values($player->inventory);
}
Screen::sleep(1);
return;
}
$slot = $item['type'];
$player->equip[$slot] = $item;
// Remove from inventory
unset($player->inventory[$index]);
$player->inventory = array_values($player->inventory);
$out->writeln("你装备了:{$item['name']}");
Screen::sleep(1);
}
private function dropItem(array $item, int $index)
{
$out = $this->game->output;
// Decrease quantity or remove
if (($this->game->player->inventory[$index]['quantity'] ?? 1) > 1) {
$this->game->player->inventory[$index]['quantity']--;
$out->writeln("你丢弃了 1 个 {$item['name']},剩余: {$this->game->player->inventory[$index]['quantity']}");
} else {
unset($this->game->player->inventory[$index]);
$this->game->player->inventory = array_values($this->game->player->inventory);
$out->writeln("你丢弃了 {$item['name']}");
}
Screen::sleep(1);
}
private function sellItem(array $item, int $index)
{
$out = $this->game->output;
$player = $this->game->player;
// 计算售价
$sellPrice = Item::calcPrice($item);
// 消耗品可能有多个,卖一个
$quantity = $item['quantity'] ?? 1;
if ($quantity > 1) {
// 卖一个
$player->inventory[$index]['quantity']--;
$player->addSpiritStones($sellPrice);
$out->writeln("你出售了 1 个 {$item['name']},获得 {$sellPrice} 灵石");
$out->writeln("剩余数量: {$player->inventory[$index]['quantity']}");
} else {
// 卖掉整个物品
unset($player->inventory[$index]);
$player->inventory = array_values($player->inventory);
$player->addSpiritStones($sellPrice);
$out->writeln("你出售了 {$item['name']},获得 {$sellPrice} 灵石");
}
$out->writeln("当前灵石: {$player->spiritStones}");
Screen::sleep(1);
}
/**
* 一键使用消耗品回复生命值至满(包括队友)
*/
private function autoHeal()
{
$out = $this->game->output;
$player = $this->game->player;
// 检查玩家和队友谁需要治疗
$playerStats = $player->getStats();
$playerMaxHp = $playerStats['maxHp'];
$playerNeedsHeal = $player->hp < $playerMaxHp;
// 收集需要治疗的队友
$partnersNeedHeal = [];
foreach ($player->partners as $partner) {
$partnerStats = $partner->getStats();
$partnerMaxHp = $partnerStats['maxHp'];
if ($partner->hp < $partnerMaxHp) {
$partnersNeedHeal[] = [
'partner' => $partner,
'maxHp' => $partnerMaxHp,
'needHeal' => $partnerMaxHp - $partner->hp,
];
}
}
// 如果都不需要治疗
if (!$playerNeedsHeal && empty($partnersNeedHeal)) {
$out->writeln("你和队友的生命值都已满!");
Screen::sleep(1);
return;
}
$totalHealed = 0;
$healLog = [];
// 回复玩家
while ($playerNeedsHeal && $player->hp < $playerMaxHp) {
// 计算需要恢复的量
$needHeal = $playerMaxHp - $player->hp;
// 使用药品,从小绿瓶池里扣除
$actualPotionHeal = $player->consumePotionPool($needHeal);
if ($actualPotionHeal == 0){
$out->writeln("小绿瓶已用尽");
Screen::sleep(1);
return;
}
$actualHeal = $player->heal($actualPotionHeal);
$totalHealed += $actualHeal;
$healLog[] = "玩家: +{$actualHeal} HP";
}
// 回复队友(按需要程度优先恢复)
if (!empty($partnersNeedHeal)) {
// 按需要治疗量从大到小排序(优先治疗伤势最重的)
usort($partnersNeedHeal, fn($a, $b) => $b['needHeal'] <=> $a['needHeal']);
foreach ($partnersNeedHeal as $healData) {
$partner = $healData['partner'];
$partnerMaxHp = $healData['maxHp'];
while ($partner->hp < $partnerMaxHp) {
// 计算需要恢复的量
$needHeal = $partnerMaxHp - $partner->hp;
// 使用药品回复队友,从小绿瓶池里扣除
$actualPotionHeal = $player->consumePotionPool($needHeal);
if ($actualPotionHeal == 0){
$out->writeln("小绿瓶已用尽");
Screen::sleep(1);
return;
}
$actualHeal = $partner->heal($actualPotionHeal);
$totalHealed += $actualHeal;
$healLog[] = "{$partner->name}: +{$actualHeal} HP";
}
}
}
$this->game->saveState();
if ($totalHealed > 0) {
$out->writeln("");
$out->writeln("╔════════════════════════════════════╗");
$out->writeln("║ 一键回血结果 ║");
$out->writeln("╚════════════════════════════════════╝");
$out->writeln("共恢复 {$totalHealed} HP");
$out->writeln("");
$out->writeln("治疗详情:");
foreach ($healLog as $log) {
$out->writeln(" " . $log);
}
$out->writeln("");
$out->writeln("当前状态:");
$out->writeln(" 玩家: {$player->hp}/{$playerMaxHp}");
foreach ($player->partners as $partner) {
$partnerStats = $partner->getStats();
$out->writeln(" {$partner->name}: {$partner->hp}/{$partnerStats['maxHp']}");
}
} else {
$out->writeln("没有使用任何药品。");
}
Screen::pause($out);
}
private function batchSell()
{
$out = $this->game->output;
$player = $this->game->player;
Screen::clear($out);
$out->writeln("╔════════════════════════════════════╗");
$out->writeln("║ 批量出售装备 ║");
$out->writeln("╚════════════════════════════════════╝");
$out->writeln("");
$out->writeln("选择要出售的品质:");
$out->writeln("[1] 仅 Common (普通)");
$out->writeln("[2] Common + Rare (普通+稀有)");
$out->writeln("[3] Common + Rare + Epic (普通+稀有+史诗)");
$out->writeln("[0] 取消");
$out->writeln("");
$choice = Screen::input($out, "请选择:");
if ($choice == 0) {
return;
}
// 确定要出售的品质范围
$qualitiesToSell = [];
switch ($choice) {
case 1:
$qualitiesToSell = ['common'];
break;
case 2:
$qualitiesToSell = ['common', 'rare'];
break;
case 3:
$qualitiesToSell = ['common', 'rare', 'epic'];
break;
default:
$out->writeln("无效选择");
Screen::sleep(1);
return;
}
// 统计符合条件的装备(排除消耗品)
$itemsToSell = [];
$totalValue = 0;
foreach ($player->inventory as $index => $item) {
$quality = $item['quality'] ?? $item['rarity'] ?? 'common';
$isEquipment = in_array($item['type'], ['weapon', 'armor', 'ring', 'necklace', 'boots','spell']);
if ($isEquipment && in_array($quality, $qualitiesToSell)) {
$price = Item::calcPrice($item);
$itemsToSell[] = [
'index' => $index,
'item' => $item,
'price' => $price
];
$totalValue += $price;
}
}
if (empty($itemsToSell)) {
$out->writeln("\n没有符合条件的装备可以出售。");
Screen::sleep(2);
return;
}
// 显示将要出售的物品
Screen::clear($out);
$out->writeln("╔════════════════════════════════════╗");
$out->writeln("║ 确认批量出售 ║");
$out->writeln("╚════════════════════════════════════╝");
$out->writeln("");
$out->writeln("将要出售以下装备:");
$out->writeln("");
$maxDisplay = 10;
$displayCount = min(count($itemsToSell), $maxDisplay);
for ($i = 0; $i < $displayCount; $i++) {
$data = $itemsToSell[$i];
$item = $data['item'];
$price = $data['price'];
$quality = $item['quality'] ?? 'common';
$color = $this->rarityColors[$quality] ?? '';
$reset = $this->reset;
$out->writeln(" {$color}[{$quality}]{$reset} {$item['name']} - {$price} 灵石");
}
if (count($itemsToSell) > $maxDisplay) {
$remaining = count($itemsToSell) - $maxDisplay;
$out->writeln(" ... 还有 {$remaining} 件装备");
}
$out->writeln("");
$out->writeln("总计: {$totalValue} 灵石 (共 " . count($itemsToSell) . " 件装备)");
$out->writeln("当前灵石: {$player->spiritStones}");
$out->writeln("");
$confirm = Screen::input($out, "确认出售? [y/n]");
if (strtolower($confirm) !== 'y') {
$out->writeln("已取消");
Screen::sleep(1);
return;
}
// 执行批量出售(从后往前删除,避免索引问题)
$soldCount = 0;
usort($itemsToSell, function($a, $b) {
return $b['index'] - $a['index'];
});
foreach ($itemsToSell as $data) {
$index = $data['index'];
$price = $data['price'];
unset($player->inventory[$index]);
$player->addSpiritStones($price);
$soldCount++;
}
// 重建索引
$player->inventory = array_values($player->inventory);
$out->writeln("");
$out->writeln("✅ 成功出售 {$soldCount} 件装备!");
$out->writeln("💰 获得灵石: {$totalValue}");
$out->writeln("💰 当前灵石: {$player->spiritStones}");
Screen::sleep(2);
}
private function showCategoryMenu(int $page, string $currentCategory)
{
$out = $this->game->output;
Screen::clear($out);
$out->writeln("╔════════════════════════════════════╗");
$out->writeln("║ 选择物品分类 ║");
$out->writeln("╚════════════════════════════════════╝");
$out->writeln("");
$index = 1;
$categoryKeys = [];
foreach ($this->categories as $key => $name) {
$marker = ($key === $currentCategory) ? "" : "";
$out->writeln("[{$index}] {$name}{$marker}");
$categoryKeys[$index] = $key;
$index++;
}
$out->writeln("");
$out->writeln("[0] 返回");
$out->writeln("");
$choice = Screen::input($out, "请选择分类:");
if ($choice == 0) {
return $this->show($page, $currentCategory);
}
$choice = intval($choice);
if (!isset($categoryKeys[$choice])) {
$out->writeln("无效选择");
Screen::sleep(1);
return $this->showCategoryMenu($page, $currentCategory);
}
$selectedCategory = $categoryKeys[$choice];
return $this->show(1, $selectedCategory);
}
private function renderCompactStats(): void
{
$out = $this->game->output;
$player = $this->game->player;
// Calculate total stats
$stats = $player->getStats();
// Colors
$cyan = Colors::CYAN;
$yellow = Colors::YELLOW;
$green = Colors::GREEN;
$red = Colors::RED;
$reset = Colors::RESET;
$out->writeln("╔════════════════════════════════════════════════════════╗");
$out->writeln("{$cyan}角色属性{$reset} Lv.{$player->level} | {$green}💰 {$player->spiritStones}{$reset} 灵石 | 🟢 {$player->potionPool}");
$out->writeln("╠════════════════════════════════════════════════════════╣");
// First row: HP (current/max), patk, matk
$hpDisplay = "{$player->hp}/{$stats['maxHp']}";
$out->writeln(sprintf(
"{$red}HP{$reset}: %-10s {$yellow}物攻{$reset}: %-5s {$cyan}魔攻{$reset}: %-5s ║",
$hpDisplay,
$stats['patk'],
$stats['matk']
));
// Second row: pdef, mdef
$out->writeln(sprintf(
"{$yellow}物防{$reset}: %-5s {$cyan}魔防{$reset}: %-5s ║",
$stats['pdef'],
$stats['mdef']
));
// Third row: Crit, CritDmg
$out->writeln(sprintf(
"{$cyan}暴击{$reset}: %-4s%% {$cyan}暴伤{$reset}: %-4s%% ║",
$stats['crit'],
$stats['critdmg']
));
$out->writeln("╠════════════════════════════════════════════════════════╣");
$out->writeln("{$yellow}装备{$reset}");
$out->writeln("╠════════════════════════════════════════════════════════╣");
// Equipment slots
$slots = [
'weapon' => '武器',
'armor' => '护甲',
'boots' => '鞋子',
'ring' => '戒指',
'necklace' => '项链',
];
foreach ($slots as $slot => $slotName) {
$item = $player->equip[$slot] ?? null;
if ($item){
$slotLines = Item::show($item);
$out->writeln($slotLines);
}
}
$out->writeln("╚════════════════════════════════════════════════════════╝");
}
}