hanli/src/Modules/InventoryPanel.php
2025-12-06 22:59:52 +08:00

792 lines
29 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;
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' => '消耗品',
];
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'));
}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("╚════════════════════════════╝");
if (empty($items)) {
$out->writeln("该分类下没有物品...");
$out->writeln("");
$out->writeln("[c] 切换分类 | [0] 返回");
$choice = Screen::input($out, "选择操作:");
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 = ItemDisplay::renderListItem($item, true, true);
$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 = ItemDisplay::renderDetail($item, "");
foreach ($detailLines as $line) {
$out->writeln($line);
}
if ($quantity > 1) {
$out->writeln("");
$out->writeln("║ 拥有数量: {$quantity}");
}
// 计算售价
$sellPrice = \Game\Entities\Item::calculateSellPrice($item);
$out->writeln("");
$out->writeln("║ 💰 售价: {$sellPrice} 灵石");
$out->writeln("╚════════════════════════════════════════╝");
$out->writeln("");
$isEquip = in_array($item['type'], ['weapon', 'armor', 'boots', 'ring', 'necklace']);
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 ($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') {
$actualHeal = $player->heal($item['heal']);
$out->writeln("你使用了 {$item['name']},恢复了 {$actualHeal} HP(当前: {$player->hp}/{$maxHp})");
} else {
// 恢复队友
$partnerStats = $target->getStats();
$partnerMaxHp = $partnerStats['maxHp'];
$actualHeal = $target->heal($item['heal']);
$out->writeln("你使用了 {$item['name']} 来恢复 {$target->name},恢复了 {$actualHeal} HP(当前: {$target->hp}/{$partnerMaxHp})");
}
// 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'];
// 获取新装备原有的强化等级
$newItemEnhanceLevel = $item['enhanceLevel'] ?? 0;
// $newItemEnhanceLevel = 0;
// If there's already an item in the slot, swap enhance levels
// 调整为不能继承强化等级
// if (isset($player->equip[$slot]) && !empty($player->equip[$slot])) {
// $oldItem = $player->equip[$slot];
// $oldEnhanceLevel = $oldItem['enhanceLevel'] ?? 0;
//
// // 旧装备继承新装备的强化等级
// $oldItem['enhanceLevel'] = $newItemEnhanceLevel;
// $player->addItem($oldItem);
//
// $oldEnhanceStr = $oldEnhanceLevel > 0 ? "+{$oldEnhanceLevel}" : "";
// $newEnhanceStr = $newItemEnhanceLevel > 0 ? "+{$newItemEnhanceLevel}" : "";
// $out->writeln("已取下 {$oldItem['name']}{$newEnhanceStr} 并放入背包");
//
// // 新装备继承旧装备的强化等级
// $item['enhanceLevel'] = $oldEnhanceLevel;
// }
$player->equip[$slot] = $item;
// Remove from inventory
unset($player->inventory[$index]);
$player->inventory = array_values($player->inventory);
$enhanceStr = ($item['enhanceLevel'] ?? 0) > 0 ? "+{$item['enhanceLevel']}" : "";
$out->writeln("你装备了:{$item['name']}{$enhanceStr}");
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 = \Game\Entities\Item::calculateSellPrice($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;
}
// 获取所有消耗品并按治疗量排序(优先使用小瓶)
$consumables = [];
foreach ($player->inventory as $index => $item) {
if (($item['type'] ?? '') === 'consume' && ($item['heal'] ?? 0) > 0) {
$consumables[] = ['index' => $index, 'item' => $item];
}
}
if (empty($consumables)) {
$out->writeln("没有可用的回复药品!");
Screen::sleep(1);
return;
}
// 按治疗量从小到大排序(优先使用小瓶,避免浪费)
usort($consumables, fn($a, $b) => ($a['item']['heal'] ?? 0) <=> ($b['item']['heal'] ?? 0));
$totalHealed = 0;
$itemsUsed = 0;
$healLog = [];
// 回复玩家
while ($playerNeedsHeal && $player->hp < $playerMaxHp && !empty($consumables)) {
// 计算需要恢复的量
$needHeal = $playerMaxHp - $player->hp;
// 找到最合适的药品(不浪费或浪费最少的)
$bestIndex = null;
$bestWaste = PHP_INT_MAX;
foreach ($consumables as $i => $c) {
$healAmount = $c['item']['heal'] ?? 0;
$waste = max(0, $healAmount - $needHeal);
if ($waste < $bestWaste) {
$bestWaste = $waste;
$bestIndex = $i;
}
// 如果找到完美匹配或不浪费的,直接使用
if ($waste === 0) break;
}
if ($bestIndex === null) break;
$selected = $consumables[$bestIndex];
$inventoryIndex = $selected['index'];
$item = $selected['item'];
// 使用药品
$actualHeal = $player->heal($item['heal']);
$totalHealed += $actualHeal;
$itemsUsed++;
$healLog[] = "玩家: +{$actualHeal} HP";
// 减少数量或移除物品
if (($player->inventory[$inventoryIndex]['quantity'] ?? 1) > 1) {
$player->inventory[$inventoryIndex]['quantity']--;
$consumables[$bestIndex]['item']['quantity']--;
} else {
unset($player->inventory[$inventoryIndex]);
unset($consumables[$bestIndex]);
$consumables = array_values($consumables);
}
// 重新整理背包索引
$player->inventory = array_values($player->inventory);
// 更新 consumables 的索引引用
$consumables = [];
foreach ($player->inventory as $index => $invItem) {
if (($invItem['type'] ?? '') === 'consume' && ($invItem['heal'] ?? 0) > 0) {
$consumables[] = ['index' => $index, 'item' => $invItem];
}
}
usort($consumables, fn($a, $b) => ($a['item']['heal'] ?? 0) <=> ($b['item']['heal'] ?? 0));
}
// 回复队友(按需要程度优先恢复)
if (!empty($partnersNeedHeal) && !empty($consumables)) {
// 按需要治疗量从大到小排序(优先治疗伤势最重的)
usort($partnersNeedHeal, fn($a, $b) => $b['needHeal'] <=> $a['needHeal']);
foreach ($partnersNeedHeal as $healData) {
$partner = $healData['partner'];
$partnerMaxHp = $healData['maxHp'];
while ($partner->hp < $partnerMaxHp && !empty($consumables)) {
// 计算需要恢复的量
$needHeal = $partnerMaxHp - $partner->hp;
// 找到最合适的药品
$bestIndex = null;
$bestWaste = PHP_INT_MAX;
foreach ($consumables as $i => $c) {
$healAmount = $c['item']['heal'] ?? 0;
$waste = max(0, $healAmount - $needHeal);
if ($waste < $bestWaste) {
$bestWaste = $waste;
$bestIndex = $i;
}
if ($waste === 0) break;
}
if ($bestIndex === null) break;
$selected = $consumables[$bestIndex];
$inventoryIndex = $selected['index'];
$item = $selected['item'];
// 使用药品回复队友
$actualHeal = $partner->heal($item['heal']);
$totalHealed += $actualHeal;
$itemsUsed++;
$healLog[] = "{$partner->name}: +{$actualHeal} HP";
// 减少数量或移除物品
if (($player->inventory[$inventoryIndex]['quantity'] ?? 1) > 1) {
$player->inventory[$inventoryIndex]['quantity']--;
$consumables[$bestIndex]['item']['quantity']--;
} else {
unset($player->inventory[$inventoryIndex]);
unset($consumables[$bestIndex]);
$consumables = array_values($consumables);
}
// 重新整理背包索引
$player->inventory = array_values($player->inventory);
// 更新 consumables 的索引引用
$consumables = [];
foreach ($player->inventory as $index => $invItem) {
if (($invItem['type'] ?? '') === 'consume' && ($invItem['heal'] ?? 0) > 0) {
$consumables[] = ['index' => $index, 'item' => $invItem];
}
}
usort($consumables, fn($a, $b) => ($a['item']['heal'] ?? 0) <=> ($b['item']['heal'] ?? 0));
}
}
}
$this->game->saveState();
if ($totalHealed > 0) {
$out->writeln("");
$out->writeln("╔════════════════════════════════════╗");
$out->writeln("║ 一键回血结果 ║");
$out->writeln("╚════════════════════════════════════╝");
$out->writeln("使用了 {$itemsUsed} 个药品,共恢复 {$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 = \Game\Entities\Item::calculateSellPrice($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} 灵石 ║");
$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;
$slotLines = ItemDisplay::renderSlot($slotName, $item, "");
foreach ($slotLines as $line) {
$out->writeln($line);
}
}
$out->writeln("╚════════════════════════════════════════════════════════╝");
}
}