From 826f0e38a4989a9105b51a85c0c152e817b28641 Mon Sep 17 00:00:00 2001 From: hant Date: Tue, 2 Dec 2025 23:13:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B3=95=E6=9C=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bin/game | 3 +- save.json | 2 +- src/Core/Game.php | 19 ++ src/Data/items.php | 12 + src/Data/spells.php | 171 ++++++++++++++ src/Entities/Item.php | 3 + src/Entities/Partner.php | 62 +++++ src/Entities/Player.php | 109 +++++++++ src/Modules/Battle.php | 394 ++++++++++++++++++++++++++++++- src/Modules/Menu.php | 4 + src/Modules/NpcPanel.php | 8 +- src/Modules/SpellPanel.php | 461 +++++++++++++++++++++++++++++++++++++ 12 files changed, 1235 insertions(+), 13 deletions(-) create mode 100644 src/Data/spells.php create mode 100644 src/Modules/SpellPanel.php diff --git a/bin/game b/bin/game index a7ae088..98ae70c 100755 --- a/bin/game +++ b/bin/game @@ -12,6 +12,7 @@ if (file_exists(__DIR__ . '/../vendor/autoload.php')) { require 'phar://hanli-idle.phar/vendor/autoload.php'; } +use Game\Core\Input; use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Output\ConsoleOutput; use Game\Core\Game; @@ -28,7 +29,7 @@ if (php_sapi_name() !== 'cli') { } // 创建输入输出 -$input = new ArgvInput(); +$input = new Input(); $output = new ConsoleOutput(); // 启动游戏 diff --git a/save.json b/save.json index e12277d..7793c49 100644 --- a/save.json +++ b/save.json @@ -1 +1 @@ -{"player":{"hp":0,"maxHp":50,"patk":10,"matk":5,"pdef":5,"mdef":3,"crit":5,"critdmg":130,"level":3,"exp":50,"maxExp":225,"inventory":[],"equip":{"weapon":{"name":"烈焰刀","type":"weapon","quality":"common","level":1,"patk":5,"matk":5,"pdef":0,"mdef":0,"hp":0,"crit":0,"critdmg":0,"heal":0,"affixes":[],"desc":"Lv.1 common品质的武器","id":"692eb3035a60f","quantity":1}},"spiritStones":67,"npcFlags":[],"talentPoints":6,"talents":{"hp":0,"patk":0,"matk":0,"pdef":0,"mdef":0,"crit":0,"critdmg":0},"partners":[{"id":"li_feiyu","name":"厉飞雨","level":2,"exp":140,"maxExp":150,"baseStats":{"hp":100,"patk":15,"matk":5,"pdef":5,"mdef":3,"crit":10,"critdmg":130,"growth":1.2},"equip":{"weapon":{"name":"铁剑","type":"weapon","quality":"common","level":1,"patk":8,"matk":1,"pdef":0,"mdef":0,"hp":0,"crit":0,"critdmg":0,"heal":0,"affixes":[],"desc":"Lv.1 common品质的武器","id":"692eb39459686","quantity":1}},"talents":{"hp":1,"patk":1,"matk":0,"pdef":0,"mdef":0,"crit":1,"critdmg":0},"talentWeights":{"hp":1,"patk":3,"matk":1,"pdef":1,"mdef":1,"crit":3,"critdmg":2}}]},"dungeonId":1,"state":0} \ No newline at end of file +{"player":{"hp":31,"maxHp":50,"patk":10,"matk":5,"pdef":5,"mdef":3,"crit":5,"critdmg":130,"level":3,"exp":70,"maxExp":225,"inventory":[],"equip":{"weapon":{"name":"青钢剑","type":"weapon","quality":"rare","level":1,"patk":14,"matk":3,"pdef":0,"mdef":0,"hp":0,"crit":0,"critdmg":0,"heal":0,"affixes":["暴击伤害 +6"],"desc":"Lv.1 rare品质的武器","id":"692ee8e0df082","quantity":1},"armor":{"name":"皮甲","type":"armor","quality":"common","level":3,"patk":0,"matk":0,"pdef":6,"mdef":1,"hp":0,"crit":0,"critdmg":0,"heal":0,"affixes":[],"desc":"Lv.3 common品质的防具","id":"692efecedbc7a","quantity":1}},"spiritStones":72,"npcFlags":[],"talentPoints":0,"talents":{"hp":6,"patk":0,"matk":0,"pdef":0,"mdef":0,"crit":0,"critdmg":0},"mana":100,"maxMana":100,"spells":[],"spellBooks":{"3":1},"partners":[{"id":"li_feiyu","name":"厉飞雨","level":2,"exp":60,"maxExp":150,"baseStats":{"hp":100,"patk":15,"matk":5,"pdef":5,"mdef":3,"crit":10,"critdmg":130,"growth":1.2},"equip":{"weapon":{"name":"青钢剑","type":"weapon","quality":"common","level":1,"patk":6,"matk":2,"pdef":0,"mdef":0,"hp":0,"crit":0,"critdmg":0,"heal":0,"affixes":[],"desc":"Lv.1 common品质的武器","id":"692ee91453dd8","quantity":1}},"talents":{"hp":1,"patk":1,"matk":0,"pdef":0,"mdef":0,"crit":1,"critdmg":0},"talentWeights":{"hp":1,"patk":3,"matk":1,"pdef":1,"mdef":1,"crit":3,"critdmg":2},"mana":80,"maxMana":80,"spells":[],"spellBooks":[]}]},"dungeonId":1,"state":2} \ No newline at end of file diff --git a/src/Core/Game.php b/src/Core/Game.php index 9eada75..666e200 100644 --- a/src/Core/Game.php +++ b/src/Core/Game.php @@ -12,6 +12,7 @@ use Game\Modules\PartnerPanel; use Game\Modules\TalentPanel; use Game\Modules\DungeonSelectPanel; use Game\Modules\EquipmentEnhancePanel; +use Game\Modules\SpellPanel; class Game { @@ -24,6 +25,7 @@ class Game const TALENT = 7; const DUNGEON_SELECT = 8; const EQUIPMENT_ENHANCE = 9; + const SPELL = 10; const EXIT = 0; public Player $player; @@ -98,6 +100,12 @@ class Game } } + // 加载法术数据 + $this->player->mana = $p['mana'] ?? $this->player->mana; + $this->player->maxMana = $p['maxMana'] ?? $this->player->maxMana; + $this->player->spells = $p['spells'] ?? $this->player->spells; + $this->player->spellBooks = $p['spellBooks'] ?? $this->player->spellBooks; + $this->dungeonId = $data['dungeonId'] ?? $this->dungeonId; $this->state = self::MENU; } @@ -119,6 +127,10 @@ class Game 'equip' => $partner->equip, 'talents' => $partner->talents, 'talentWeights' => $partner->talentWeights, + 'mana' => $partner->mana, + 'maxMana' => $partner->maxMana, + 'spells' => $partner->spells, + 'spellBooks' => $partner->spellBooks, ]; } @@ -141,6 +153,10 @@ class Game 'npcFlags' => $this->player->npcFlags, 'talentPoints' => $this->player->talentPoints, 'talents' => $this->player->talents, + 'mana' => $this->player->mana, + 'maxMana' => $this->player->maxMana, + 'spells' => $this->player->spells, + 'spellBooks' => $this->player->spellBooks, 'partners' => $partnersData, ], 'dungeonId' => $this->dungeonId, @@ -180,6 +196,9 @@ class Game case self::EQUIPMENT_ENHANCE: (new EquipmentEnhancePanel($this))->show(); break; + case self::SPELL: + (new SpellPanel($this))->show(); + break; case self::EXIT: $this->output->writeln("再见!"); $this->saveState(); diff --git a/src/Data/items.php b/src/Data/items.php index 8636791..d3e8147 100644 --- a/src/Data/items.php +++ b/src/Data/items.php @@ -230,5 +230,17 @@ return [ 'base_stats' => [30, 50, 80, 120], // 不同品质的基础治疗量 'growth' => 5, // 每级增加的治疗量 ], + + // 法术资源书 - 用于学习和升级法术 + 'spell_tome' => [ + 'names' => [ + 'common' => ['火球术卷', '冰雹术卷', '治愈术卷', '防护术卷'], + 'rare' => ['冰锥术册', '雷击术册', '神圣庇护册', '炎爆术册'], + 'epic' => ['烈焰焚天记', '流星雨诀', '恢复光环经'], + 'legendary' => ['诛仙剑气秘籍', '灭世风暴秘典'], + ], + 'base_stats' => [0, 0, 0, 0], // 资源书本身无属性 + 'growth' => 0, // 资源书无等级 + ], ], ]; diff --git a/src/Data/spells.php b/src/Data/spells.php new file mode 100644 index 0000000..13c6713 --- /dev/null +++ b/src/Data/spells.php @@ -0,0 +1,171 @@ + [ + 1 => [ + 'name' => '火球术', + 'type' => 'damage_single', + 'quality' => 'common', + 'cost' => 20, + 'damage' => 1.2, // 伤害倍数 = 魔攻 * damage + 'level_req' => 1, + 'desc' => '发出一团火球,攻击单个敌人', + ], + 2 => [ + 'name' => '冰锥术', + 'type' => 'damage_single', + 'quality' => 'rare', + 'cost' => 25, + 'damage' => 1.3, + 'level_req' => 5, + 'desc' => '凝聚寒冰之力,发出锐利冰锥', + ], + 3 => [ + 'name' => '雷击术', + 'type' => 'damage_single', + 'quality' => 'rare', + 'cost' => 30, + 'damage' => 1.5, + 'level_req' => 10, + 'desc' => '召唤雷电直击单个敌人', + ], + 4 => [ + 'name' => '烈焰焚天', + 'type' => 'damage_single', + 'quality' => 'epic', + 'cost' => 45, + 'damage' => 1.8, + 'level_req' => 20, + 'desc' => '释放强大的火焰,对单个敌人造成巨大伤害', + ], + 5 => [ + 'name' => '诛仙剑气', + 'type' => 'damage_single', + 'quality' => 'legendary', + 'cost' => 60, + 'damage' => 2.2, + 'level_req' => 35, + 'desc' => '凝聚剑意,发出致命一击', + ], + ], + + // AOE伤害法术 + 'damage_aoe' => [ + 10 => [ + 'name' => '冰雹术', + 'type' => 'damage_aoe', + 'quality' => 'common', + 'cost' => 35, + 'damage' => 0.8, // 对每个敌人的伤害倍数较低,但打全体 + 'level_req' => 8, + 'desc' => '召唤冰雹,攻击所有敌人', + ], + 11 => [ + 'name' => '炎爆术', + 'type' => 'damage_aoe', + 'quality' => 'rare', + 'cost' => 45, + 'damage' => 0.95, + 'level_req' => 15, + 'desc' => '引发连锁爆炸,对所有敌人造成伤害', + ], + 12 => [ + 'name' => '流星雨', + 'type' => 'damage_aoe', + 'quality' => 'epic', + 'cost' => 60, + 'damage' => 1.1, + 'level_req' => 25, + 'desc' => '召唤流星坠落,轰击全体敌人', + ], + 13 => [ + 'name' => '灭世风暴', + 'type' => 'damage_aoe', + 'quality' => 'legendary', + 'cost' => 80, + 'damage' => 1.3, + 'level_req' => 40, + 'desc' => '引发天地异变,对所有敌人造成毁灭性伤害', + ], + ], + + // 辅助法术(恢复、增益) + 'support' => [ + 20 => [ + 'name' => '治愈术', + 'type' => 'support', + 'subtype' => 'heal', + 'quality' => 'common', + 'cost' => 15, + 'heal' => 0.5, // 恢复量倍数 = 魔攻 * heal + 基础值 + 'heal_base' => 20, + 'level_req' => 3, + 'desc' => '恢复自己或队友的生命值', + ], + 21 => [ + 'name' => '神圣庇护', + 'type' => 'support', + 'subtype' => 'defend', + 'quality' => 'rare', + 'cost' => 25, + 'defense_boost' => 30, // 增加固定防御值 + 'duration' => 3, // 持续回合数(如果支持的话) + 'level_req' => 12, + 'desc' => '增加自己或队友的防御力', + ], + 22 => [ + 'name' => '恢复光环', + 'type' => 'support', + 'subtype' => 'heal_all', + 'quality' => 'epic', + 'cost' => 50, + 'heal' => 0.6, + 'heal_base' => 40, + 'level_req' => 30, + 'desc' => '为所有队员恢复生命值', + ], + ], + + // 法术品质对应的学习资源书数量 + 'quality_levels' => [ + 'common' => 1, // 普通资源书可学习普通法术 + 'rare' => 2, // 稀有资源书可学习稀有法术 + 'epic' => 3, // 史诗资源书可学习史诗法术 + 'legendary' => 4, // 传奇资源书可学习传奇法术 + ], + + // 法术升级系统 + 'upgrades' => [ + // 每个等级需要的资源书数量和属性提升 + // level => ['cost' => 资源书数量, 'damage_bonus' => 伤害加成%, 'cost_reduction' => 消耗减少] + 1 => ['cost' => 0, 'damage_bonus' => 0, 'cost_reduction' => 0], + 2 => ['cost' => 2, 'damage_bonus' => 10, 'cost_reduction' => 2], + 3 => ['cost' => 3, 'damage_bonus' => 20, 'cost_reduction' => 4], + 4 => ['cost' => 4, 'damage_bonus' => 30, 'cost_reduction' => 6], + 5 => ['cost' => 5, 'damage_bonus' => 40, 'cost_reduction' => 8], + 6 => ['cost' => 6, 'damage_bonus' => 50, 'cost_reduction' => 10], + 7 => ['cost' => 8, 'damage_bonus' => 60, 'cost_reduction' => 12], + 8 => ['cost' => 10, 'damage_bonus' => 70, 'cost_reduction' => 14], + 9 => ['cost' => 12, 'damage_bonus' => 80, 'cost_reduction' => 16], + 10 => ['cost' => 15, 'damage_bonus' => 100, 'cost_reduction' => 20], + ], + + // 地牢法术掉落映射 - 定义各地牢的法术资源书掉落池 + 'dungeon_spell_drops' => [ + 1 => [1, 10], // 七玄门 (Lv.1-5): 火球术、冰雹术 + 2 => [2, 3, 11], // 太南谷 (Lv.5-10): 冰锥术、雷击术、炎爆术 + 3 => [20, 21], // 血色禁地 (Lv.10-15): 治愈术、神圣庇护 + 4 => [12], // 黄枫谷 (Lv.15-20): 流星雨 + 5 => [4], // 燕翎堡 (Lv.20-30): 烈焰焚天 + 6 => [5, 22], // 越京皇宫 (Lv.30-40): 诛仙剑气、恢复光环 + 7 => [13], // 乱星海-魁星岛 (Lv.40-50): 灭世风暴 + 8 => [13, 22], // 虚天殿 (Lv.50-60): 灭世风暴、恢复光环 + 9 => [5, 13], // 外星海 (Lv.60+): 诛仙剑气、灭世风暴 + ], +]; diff --git a/src/Entities/Item.php b/src/Entities/Item.php index 764ae9a..3603e2e 100644 --- a/src/Entities/Item.php +++ b/src/Entities/Item.php @@ -87,6 +87,9 @@ class Item $growth = $typeConfig['growth'] ?? 0; $item->heal = $baseStats[$qualityIndex] + ($level * $growth) + rand(0, 10); $item->desc = "Lv.{$level} {$quality}品质的药剂"; + } elseif ($type === 'spell_tome') { + // 法术资源书特殊处理 + $item->desc = "Lv.{$level} {$quality}品质的法术资源书"; } else { // 检查是否有特定物品配置 $specificConfig = $typeConfig['specific_config'][$item->name] ?? []; diff --git a/src/Entities/Partner.php b/src/Entities/Partner.php index f2b94ce..84637b2 100644 --- a/src/Entities/Partner.php +++ b/src/Entities/Partner.php @@ -13,6 +13,12 @@ class Partner public array $baseStats = []; public array $equip = []; // weapon, armor, ring, boots, necklace + // 法术系统 + public int $mana = 80; // 当前魔法值(队友初始值低于玩家) + public int $maxMana = 80; // 最大魔法值 + public array $spells = []; // 已学习的法术ID列表 + public array $spellBooks = []; // 拥有的法术资源书 + // 天赋系统 public array $talents = [ 'hp' => 0, @@ -58,6 +64,12 @@ class Partner $this->talents = $data['talents'] ?? $this->talents; $this->talentWeights = $data['talentWeights'] ?? $this->talentWeights; + // 加载法术系统数据 + $this->mana = $data['mana'] ?? $this->mana; + $this->maxMana = $data['maxMana'] ?? $this->maxMana; + $this->spells = $data['spells'] ?? $this->spells; + $this->spellBooks = $data['spellBooks'] ?? $this->spellBooks; + // 设置当前血量为最大血量 $stats = $this->getStats(); $this->hp = $data['hp'] ?? $stats['maxHp']; @@ -249,4 +261,54 @@ class Partner return $this->hp - $oldHp; // 返回实际恢复的血量 } + + /** + * 恢复魔法值,不超过上限 + */ + public function recoverMana(int $amount): int + { + $oldMana = $this->mana; + $this->mana = min($this->mana + $amount, $this->maxMana); + return $this->mana - $oldMana; + } + + /** + * 消耗魔法值(返回是否成功) + */ + public function spendMana(int $amount): bool + { + if ($this->mana >= $amount) { + $this->mana -= $amount; + return true; + } + return false; + } + + /** + * 学习法术 + */ + public function learnSpell(int $spellId): bool + { + if (!isset($this->spells[$spellId])) { + $this->spells[$spellId] = ['level' => 1]; + return true; + } + return false; + } + + /** + * 检查是否已学习法术 + */ + public function hasSpell(int $spellId): bool + { + return isset($this->spells[$spellId]); + } + + /** + * 获取法术等级 + */ + public function getSpellLevel(int $spellId): int + { + return $this->spells[$spellId]['level'] ?? 0; + } } diff --git a/src/Entities/Player.php b/src/Entities/Player.php index 3a7fbc9..5c996eb 100644 --- a/src/Entities/Player.php +++ b/src/Entities/Player.php @@ -21,6 +21,12 @@ class Player public int $spiritStones = 0; // 灵石 + // 法术系统 + public int $mana = 100; // 当前魔法值 + public int $maxMana = 100; // 最大魔法值 + public array $spells = []; // 已学习的法术ID列表 [1 => ['level' => 1], ...] + public array $spellBooks = []; // 拥有的法术资源书 [spell_id => 数量, ...] + // 天赋系统 public int $talentPoints = 0; // 可用天赋点 public array $talents = [ // 已分配的天赋点 @@ -100,6 +106,109 @@ class Player $this->hp = $stats['maxHp']; } + /** + * 恢复魔法值,不超过上限 + * @param int $amount 恢复量 + * @return int 实际恢复量 + */ + public function recoverMana(int $amount): int + { + $oldMana = $this->mana; + $this->mana = min($this->mana + $amount, $this->maxMana); + return $this->mana - $oldMana; + } + + /** + * 完全恢复魔法值 + */ + public function fullRecoverMana(): void + { + $this->mana = $this->maxMana; + } + + /** + * 消耗魔法值(返回是否成功) + */ + public function spendMana(int $amount): bool + { + if ($this->mana >= $amount) { + $this->mana -= $amount; + return true; + } + return false; + } + + /** + * 学习法术 + */ + public function learnSpell(int $spellId): bool + { + if (!isset($this->spells[$spellId])) { + $this->spells[$spellId] = ['level' => 1]; + return true; + } + return false; + } + + /** + * 升级法术等级 + */ + public function upgradeSpell(int $spellId): bool + { + if (!isset($this->spells[$spellId])) { + return false; + } + $this->spells[$spellId]['level'] = min($this->spells[$spellId]['level'] + 1, 10); + return true; + } + + /** + * 检查是否已学习法术 + */ + public function hasSpell(int $spellId): bool + { + return isset($this->spells[$spellId]); + } + + /** + * 获取法术等级 + */ + public function getSpellLevel(int $spellId): int + { + return $this->spells[$spellId]['level'] ?? 0; + } + + /** + * 添加法术资源书 + */ + public function addSpellBook(int $spellId, int $quantity = 1): void + { + if (!isset($this->spellBooks[$spellId])) { + $this->spellBooks[$spellId] = 0; + } + $this->spellBooks[$spellId] += $quantity; + } + + /** + * 消耗法术资源书 + */ + public function spendSpellBooks(int $spellId, int $quantity = 1): bool + { + if (($this->spellBooks[$spellId] ?? 0) >= $quantity) { + $this->spellBooks[$spellId] -= $quantity; + return true; + } + return false; + } + + /** + * 获取法术资源书数量 + */ + public function getSpellBookCount(int $spellId): int + { + return $this->spellBooks[$spellId] ?? 0; + } + public function gainExp(int $amount): bool { $this->exp += $amount; diff --git a/src/Modules/Battle.php b/src/Modules/Battle.php index d4c2941..7a1de54 100644 --- a/src/Modules/Battle.php +++ b/src/Modules/Battle.php @@ -18,6 +18,9 @@ class Battle /** @var array 同伴当前HP */ private array $partnerHp = []; + // 法术数据 + private array $spellsData = []; + private array $qualityColors = [ 'common' => "\033[37m", // 白色 'rare' => "\033[34m", // 蓝色 @@ -41,6 +44,7 @@ class Battle public function __construct(public Game $game) { $this->player = $game->player; + $this->spellsData = require __DIR__ . '/../../src/Data/spells.php'; } /** @@ -289,14 +293,14 @@ class Battle private function determineFirstStrike(): bool { // Use the leader's level for comparison - $leader = $this->enemies[count($this->enemies) - 1]; // Assume leader is last or first? + $leader = $this->enemies[count($this->enemies) - 1]; // Assume leader is last or first? // In createGroup we put minions first, so leader is last. // Let's just use the highest level enemy. $maxLevel = 0; foreach ($this->enemies as $e) { if ($e->level > $maxLevel) $maxLevel = $e->level; } - + $levelDiff = $this->player->level - $maxLevel; $playerChance = 50; $levelBonus = max(-30, min(30, $levelDiff * 5)); @@ -305,10 +309,359 @@ class Battle return $roll <= $playerChance; } + /** + * 玩家选择行动类型 + */ + private function playerChooseAction(): string + { + // 简化:默认使用普通攻击,除非有法术且魔法值充足 + // 为了简化战斗流程,我们先自动选择攻击 + // 在实战中可以添加交互菜单 + + // 检查是否有可以施放的法术 + if (empty($this->player->spells) || $this->player->mana < 15) { + return 'attack'; + } + + // 暂时选择法术的概率(可根据需要调整) + // 如果玩家有法术且魔法值充足,50% 概率选择法术 + $spellChance = rand(1, 100); + if ($spellChance <= 40) { // 40% 概率使用法术 + return 'spell'; + } + + return 'attack'; + } + + /** + * 玩家施放法术 + */ + private function playerCastSpell($out): bool + { + $stats = $this->player->getStats(); + + // 随机选择一个已学的法术 + $availableSpells = []; + foreach ($this->player->spells as $spellId => $spellData) { + $spellInfo = $this->getSpellInfo($spellId); + if (!$spellInfo) continue; + + $cost = $spellInfo['cost'] ?? 0; + $upgrades = $this->spellsData['upgrades'] ?? []; + $level = $spellData['level'] ?? 1; + $upgradeInfo = $upgrades[$level] ?? []; + $costReduction = $upgradeInfo['cost_reduction'] ?? 0; + $actualCost = max(1, $cost - $costReduction); + + if ($this->player->mana >= $actualCost) { + $availableSpells[$spellId] = ['info' => $spellInfo, 'cost' => $actualCost, 'level' => $level]; + } + } + + if (empty($availableSpells)) { + // 如果没有可用的法术,改用普通攻击 + $out->writeln("{$this->cyan}║{$this->reset} {$this->yellow}✦ 魔法值不足,改为普通攻击{$this->reset}"); + // 递归调用普通攻击 + return $this->executePhysicalAttack($out); + } + + // 随机选择一个可用的法术 + $selectedSpellId = array_rand($availableSpells); + $spellData = $availableSpells[$selectedSpellId]; + $spellInfo = $spellData['info']; + $actualCost = $spellData['cost']; + $spellLevel = $spellData['level']; + + // 消耗魔法值 + $this->player->spendMana($actualCost); + + // 获取法术升级信息 + $upgrades = $this->spellsData['upgrades'] ?? []; + $upgradeInfo = $upgrades[$spellLevel] ?? []; + $damageBonus = $upgradeInfo['damage_bonus'] ?? 0; + + $type = $spellInfo['type'] ?? ''; + $name = $spellInfo['name'] ?? '未知法术'; + + if ($type === 'damage_single') { + return $this->castDamageSingleSpell($out, $selectedSpellId, $spellInfo, $stats, $damageBonus, $name); + } elseif ($type === 'damage_aoe') { + return $this->castDamageAoeSpell($out, $selectedSpellId, $spellInfo, $stats, $damageBonus, $name); + } elseif ($type === 'support') { + return $this->castSupportSpell($out, $spellInfo, $stats, $name); + } + + return false; + } + + /** + * 施放单体伤害法术 + */ + private function castDamageSingleSpell($out, int $spellId, array $spellInfo, array $stats, int $damageBonus, string $name): bool + { + // 选择目标 + $target = null; + foreach ($this->enemies as $enemy) { + if ($enemy->hp > 0) { + $target = $enemy; + break; + } + } + + if (!$target) return true; + + // 计算法术伤害 + $baseDamageMultiplier = $spellInfo['damage'] ?? 1.0; + $actualDamageMultiplier = $baseDamageMultiplier * (1 + $damageBonus / 100); + $baseDamage = (int)($stats['matk'] * $actualDamageMultiplier); + + // 计算抵抗 + $resistance = $target->mdef; + $damage = max(5, $baseDamage - $resistance); + + // 暴击机制同样适用 + $critRate = $stats['crit']; + $isCrit = rand(1, 100) <= $critRate; + + if ($isCrit) { + $critDmg = $stats['critdmg']; + $damage = (int)($damage * ($critDmg / 100)); + $out->writeln("{$this->cyan}║{$this->reset} {$this->magenta}✦{$this->reset} 你施放 {$name}... {$this->red}{$this->bold}暴击!{$this->reset}"); + $out->writeln("{$this->cyan}║{$this->reset} {$this->magenta}✨ 造成 {$damage} 点魔法伤害!{$this->reset}"); + } else { + $out->writeln("{$this->cyan}║{$this->reset} {$this->magenta}✦{$this->reset} 你施放 {$name}..."); + $out->writeln("{$this->cyan}║{$this->reset} {$this->magenta}✨ 造成 {$damage} 点魔法伤害{$this->reset}"); + } + + $target->hp -= $damage; + + if ($target->hp <= 0) { + $target->hp = 0; + $out->writeln("{$this->cyan}║{$this->reset} {$this->red}💀 {$target->name} 被击败了!{$this->reset}"); + + if (empty($this->getAliveEnemies())) { + Screen::delay(500000); + $this->showVictory($out, $stats); + return true; + } + } + + return false; + } + + /** + * 施放AOE伤害法术 + */ + private function castDamageAoeSpell($out, int $spellId, array $spellInfo, array $stats, int $damageBonus, string $name): bool + { + $out->writeln("{$this->cyan}║{$this->reset} {$this->magenta}✦{$this->reset} 你施放 {$name}..."); + $out->writeln("{$this->cyan}║{$this->reset} {$this->magenta}✨ 魔法在整个战场爆炸!{$this->reset}"); + + // 计算法术伤害 + $baseDamageMultiplier = $spellInfo['damage'] ?? 0.8; + $actualDamageMultiplier = $baseDamageMultiplier * (1 + $damageBonus / 100); + + $allEnemiesDefeated = true; + foreach ($this->enemies as $enemy) { + if ($enemy->hp <= 0) continue; + + $baseDamage = (int)($stats['matk'] * $actualDamageMultiplier); + $resistance = $enemy->mdef; + $damage = max(5, $baseDamage - $resistance); + + // AOE 法术也可以暴击 + $critRate = $stats['crit']; + $isCrit = rand(1, 100) <= ($critRate / 2); // AOE 暴击率减半 + if ($isCrit) { + $critDmg = $stats['critdmg']; + $damage = (int)($damage * ($critDmg / 100)); + } + + $out->writeln("{$this->cyan}║{$this->reset} {$enemy->name} 受到 {$damage} 点伤害"); + $enemy->hp -= $damage; + + if ($enemy->hp <= 0) { + $enemy->hp = 0; + $out->writeln("{$this->cyan}║{$this->reset} {$this->red}💀 {$enemy->name} 被击败了!{$this->reset}"); + } else { + $allEnemiesDefeated = false; + } + } + + if ($allEnemiesDefeated && empty($this->getAliveEnemies())) { + Screen::delay(500000); + $this->showVictory($out, $stats); + return true; + } + + return false; + } + + /** + * 施放辅助法术 + */ + private function castSupportSpell($out, array $spellInfo, array $stats, string $name): bool + { + $subtype = $spellInfo['subtype'] ?? ''; + + if ($subtype === 'heal' || $subtype === 'heal_all') { + if ($subtype === 'heal') { + // 恢复自己 + $heal = $spellInfo['heal'] ?? 0.5; + $healBase = $spellInfo['heal_base'] ?? 20; + $healAmount = (int)($stats['matk'] * $heal + $healBase); + + $actualHeal = $this->player->heal($healAmount); + $out->writeln("{$this->cyan}║{$this->reset} {$this->green}✦{$this->reset} 你施放 {$name}..."); + $out->writeln("{$this->cyan}║{$this->reset} {$this->green}💚 恢复了 {$actualHeal} 点生命值{$this->reset}"); + } else { + // 恢复全体 + $heal = $spellInfo['heal'] ?? 0.6; + $healBase = $spellInfo['heal_base'] ?? 40; + $healAmount = (int)($stats['matk'] * $heal + $healBase); + + $out->writeln("{$this->cyan}║{$this->reset} {$this->green}✦{$this->reset} 你施放 {$name}..."); + $actualHeal = $this->player->heal($healAmount); + $out->writeln("{$this->cyan}║{$this->reset} {$this->green}💚 你恢复了 {$actualHeal} 点生命值{$this->reset}"); + + // 同伴也恢复 + $alivePartners = $this->getAlivePartners(); + foreach ($alivePartners as $partner) { + $partnerHeal = (int)($healAmount * 0.8); // 同伴恢复量为玩家的80% + $actualPartnerHeal = $partner->heal($partnerHeal); + $this->partnerHp[$partner->id] = $partner->hp; + $out->writeln("{$this->cyan}║{$this->reset} {$this->green}💚 {$partner->name} 恢复了 {$actualPartnerHeal} 点生命值{$this->reset}"); + } + } + } elseif ($subtype === 'defend') { + $defenseBoost = $spellInfo['defense_boost'] ?? 30; + $out->writeln("{$this->cyan}║{$this->reset} {$this->cyan}✦{$this->reset} 你施放 {$name}..."); + $out->writeln("{$this->cyan}║{$this->reset} {$this->cyan}🛡️ 防御力提升!{$this->reset}"); + } + + return false; + } + + /** + * 执行普通物理攻击 + */ + private function executePhysicalAttack($out): bool + { + $stats = $this->player->getStats(); + + $target = null; + foreach ($this->enemies as $enemy) { + if ($enemy->hp > 0) { + $target = $enemy; + break; + } + } + + if (!$target) return true; + + $physicalDamage = max(1, $stats['patk'] - $target->pdef); + $magicDamage = max(0, $stats['matk'] - $target->mdef); + $baseDamage = $physicalDamage + $magicDamage; + + $critRate = $stats['crit']; + $critDmg = $stats['critdmg']; + + $isCrit = rand(1, 100) <= $critRate; + + if ($isCrit) { + $damage = (int)($baseDamage * ($critDmg / 100)); + $out->writeln("{$this->cyan}║{$this->reset} {$this->green}➤{$this->reset} 你攻击 {$target->name}... {$this->red}{$this->bold}暴击!{$this->reset}"); + $out->writeln("{$this->cyan}║{$this->reset} {$this->red}💥 造成 {$damage} 点伤害!{$this->reset}"); + } else { + $damage = $baseDamage; + $out->writeln("{$this->cyan}║{$this->reset} {$this->green}➤{$this->reset} 你攻击 {$target->name}..."); + $out->writeln("{$this->cyan}║{$this->reset} {$this->white}⚔️ 造成 {$damage} 点伤害{$this->reset}"); + } + + $target->hp -= $damage; + + if ($target->hp <= 0) { + $target->hp = 0; + $out->writeln("{$this->cyan}║{$this->reset} {$this->red}💀 {$target->name} 被击败了!{$this->reset}"); + + if (empty($this->getAliveEnemies())) { + Screen::delay(500000); + $this->showVictory($out, $stats); + return true; + } + } + + return false; + } + + /** + * 获取法术信息 + */ + private function getSpellInfo(int $spellId): ?array + { + foreach ($this->spellsData as $category => $spells) { + if (is_array($spells) && $category !== 'quality_levels' && $category !== 'upgrades') { + if (isset($spells[$spellId])) { + return $spells[$spellId]; + } + } + } + return null; + } + + /** + * 生成法术资源书掉落 + */ + private function generateSpellTomeDrop(Monster $enemy): ?array + { + // 获取当前地牢的法术掉落池 + $dungeonId = $this->game->dungeonId; + $dungeonSpellDrops = $this->spellsData['dungeon_spell_drops'] ?? []; + + if (!isset($dungeonSpellDrops[$dungeonId])) { + return null; // 该地牢没有配置法术掉落 + } + + $spellIds = $dungeonSpellDrops[$dungeonId]; + + if (empty($spellIds)) { + return null; + } + + // 从该地牢的掉落池中随机选择一个法术 + $spellId = $spellIds[array_rand($spellIds)]; + $spellInfo = $this->getSpellInfo($spellId); + + if (!$spellInfo) { + return null; + } + + // 创建法术资源书物品 + $tome = [ + 'name' => $spellInfo['name'] . '的法术书', + 'type' => 'spell_tome', + 'quality' => $spellInfo['quality'] ?? 'common', + 'level' => $enemy->level, + 'spell_id' => $spellId, + 'spell_name' => $spellInfo['name'], + 'desc' => "能够学习或升级 {$spellInfo['name']} 的法术资源书", + ]; + + return $tome; + } + private function playerAttack($out): bool { $stats = $this->player->getStats(); - + + // 显示玩家选择菜单 + $choice = $this->playerChooseAction(); + + if ($choice === 'spell') { + return $this->playerCastSpell($out); + } + + // 普通攻击逻辑 // Target first alive enemy $target = null; foreach ($this->enemies as $enemy) { @@ -317,7 +670,7 @@ class Battle break; } } - + if (!$target) return true; // All dead // 计算物理伤害和魔法伤害 @@ -345,7 +698,7 @@ class Battle if ($target->hp <= 0) { $target->hp = 0; $out->writeln("{$this->cyan}║{$this->reset} {$this->red}💀 {$target->name} 被击败了!{$this->reset}"); - + // Check if all enemies are dead if (empty($this->getAliveEnemies())) { Screen::delay(500000); @@ -493,11 +846,11 @@ class Battle $totalExp = 0; $totalStones = 0; $allDrops = []; - + foreach ($this->enemies as $enemy) { $totalExp += $enemy->expReward; $totalStones += $enemy->spiritStoneReward; - + // 掉落 foreach ($enemy->getEquippedItems() as $item) { $this->player->addItem($item); @@ -509,6 +862,20 @@ class Battle $allDrops[] = $drop['item']; } } + + // 掉落法术资源书 + $spellTomeDropChance = 15; // 15% 概率掉落法术资源书 + if (rand(1, 100) <= $spellTomeDropChance && !empty($this->spellsData)) { + $spellTome = $this->generateSpellTomeDrop($enemy); + if ($spellTome) { + // 添加到玩家的法术资源书库存 + $spellId = $spellTome['spell_id'] ?? 0; + if ($spellId > 0) { + $this->player->addSpellBook($spellId, 1); + $allDrops[] = $spellTome; + } + } + } } // 经验 @@ -537,6 +904,19 @@ class Battle $out->writeln("{$this->yellow}║{$this->reset} 灵石: {$this->yellow}+{$totalStones}{$this->reset}"); } + // 恢复魔法值 + $manaRecover = (int)($this->player->maxMana * 0.3); // 恢复30%的最大魔法值 + $actualManaRecover = $this->player->recoverMana($manaRecover); + $out->writeln("{$this->yellow}║{$this->reset} 魔法: {$this->magenta}+{$actualManaRecover}{$this->reset}"); + + // 恢复队友魔法值 + $alivePartners = $this->getAlivePartners(); + foreach ($alivePartners as $partner) { + $partnerManaRecover = (int)($partner->maxMana * 0.3); + $actualPartnerManaRecover = $partner->recoverMana($partnerManaRecover); + $this->partnerHp[$partner->id] = $partner->hp; // 同步HP + } + if (!empty($allDrops)) { $out->writeln("{$this->yellow}║{$this->reset} {$this->white}掉落:{$this->reset}"); foreach ($allDrops as $item) { diff --git a/src/Modules/Menu.php b/src/Modules/Menu.php index adf2e39..548a46d 100644 --- a/src/Modules/Menu.php +++ b/src/Modules/Menu.php @@ -22,6 +22,7 @@ class Menu $out->writeln("[5] 同伴管理"); $out->writeln("[6] 天赋系统"); $out->writeln("[7] 装备强化"); + $out->writeln("[8] 法术系统"); $out->writeln("[0] 退出"); $out->writeln("========================="); @@ -48,6 +49,9 @@ class Menu } elseif ($choice == 7) { $this->game->state = Game::EQUIPMENT_ENHANCE; + } elseif ($choice == 8) { + $this->game->state = Game::SPELL; + } elseif ($choice == 0) { $this->game->state = Game::EXIT; diff --git a/src/Modules/NpcPanel.php b/src/Modules/NpcPanel.php index 788b23d..c6e3eff 100644 --- a/src/Modules/NpcPanel.php +++ b/src/Modules/NpcPanel.php @@ -84,8 +84,8 @@ class NpcPanel if ($key === 'recruit' && isset($this->game->player->partners[$npc['id']])) { $label = '已入队 ✓'; } - - $this->game->output->writeln("[{$idx}] {$label}"); + $idx_no = $idx + 1; + $this->game->output->writeln("[{$idx_no}] {$label}"); } $this->game->output->writeln("[0] 离开"); @@ -94,8 +94,8 @@ class NpcPanel if ($choice == 0) return; - if (isset($actionKeys[$choice])) { - $actionType = $actionKeys[$choice]; + if (isset($actionKeys[$choice-1])) { + $actionType = $actionKeys[$choice-1]; $actionData = $actions[$actionType]; $this->handleAction($actionType, $actionData, $npc); Screen::pause($this->game->output); diff --git a/src/Modules/SpellPanel.php b/src/Modules/SpellPanel.php new file mode 100644 index 0000000..f510824 --- /dev/null +++ b/src/Modules/SpellPanel.php @@ -0,0 +1,461 @@ +game->output; + $in = $this->game->input; + $player = $this->game->player; + + while (true) { + // 清屏显示菜单 + Screen::clear($out); + $out->writeln("{$this->bold}{$this->cyan}=== 法术系统 ==={$this->reset}"); + $out->writeln(""); + + // 显示魔法值状态 + $manaPercent = ($player->mana / $player->maxMana) * 100; + $manaBar = $this->getManaBar($player->mana, $player->maxMana); + $out->writeln("魔法值: {$manaBar} {$player->mana}/{$player->maxMana}"); + $out->writeln(""); + + // 显示已学习的法术 + $out->writeln("{$this->bold}{$this->yellow}已学习的法术:{$this->reset}"); + if (empty($player->spells)) { + $out->writeln(" {$this->white}暂无法术{$this->reset}"); + } else { + $spellIndex = 0; + foreach ($player->spells as $spellId => $spellData) { + $spellIndex++; + // 获取法术信息 + $spellInfo = $this->getSpellInfo($spellId, $spellsData); + $level = $spellData['level'] ?? 1; + $name = $spellInfo['name'] ?? '未知法术'; + $quality = $spellInfo['quality'] ?? 'common'; + $qualityColor = $this->getQualityColor($quality); + + $out->writeln(" [{$spellIndex}] {$qualityColor}{$name}{$this->reset} (Lv.{$level})"); + } + } + $out->writeln(""); + + // 显示拥有的资源书 + $out->writeln("{$this->bold}{$this->magenta}拥有的法术资源书:{$this->reset}"); + if (empty($player->spellBooks)) { + $out->writeln(" {$this->white}暂无资源书{$this->reset}"); + } else { + $bookIndex = 0; + foreach ($player->spellBooks as $spellId => $count) { + $bookIndex++; + $spellInfo = $this->getSpellInfo($spellId, $spellsData); + $name = $spellInfo['name'] ?? '未知法术'; + $quality = $spellInfo['quality'] ?? 'common'; + $qualityColor = $this->getQualityColor($quality); + $out->writeln(" [{$bookIndex}] {$qualityColor}{$name}的法术书{$this->reset} x{$count}"); + } + } + $out->writeln(""); + + // 菜单选项 + $out->writeln("{$this->bold}选择操作:{$this->reset}"); + $out->writeln("[1] 查看法术详情"); + $out->writeln("[2] 学习法术"); + $out->writeln("[3] 升级法术"); + $out->writeln("[0] 返回"); + + $choice = Input::ask($this->game->output, "请选择: ");; + + switch ($choice) { + case '1': + $this->viewSpellDetails($spellsData); + break; + case '2': + $this->learnSpell($spellsData); + break; + case '3': + $this->upgradeSpell($spellsData); + break; + case '0': + $this->game->state = Game::MENU; + return; + default: + $out->writeln("{$this->red}无效选择{$this->reset}"); + Screen::delay(500000); + } + } + } + + /** + * 查看法术详情 + */ + private function viewSpellDetails(array $spellsData) + { + $out = $this->game->output; + $player = $this->game->player; + + while (true) { + Screen::clear($out); + $out->writeln("{$this->bold}{$this->cyan}=== 法术详情 ==={$this->reset}"); + $out->writeln(""); + + if (empty($player->spells)) { + $out->writeln("{$this->red}还未学习任何法术{$this->reset}"); + Screen::delay(1000000); + return; + } + + // 列出已学习的法术 + $spellArray = []; + $spellIndex = 0; + foreach ($player->spells as $spellId => $spellData) { + $spellIndex++; + $spellInfo = $this->getSpellInfo($spellId, $spellsData); + $name = $spellInfo['name'] ?? '未知法术'; + $quality = $spellInfo['quality'] ?? 'common'; + $qualityColor = $this->getQualityColor($quality); + $level = $spellData['level'] ?? 1; + + $out->writeln("[{$spellIndex}] {$qualityColor}{$name}{$this->reset} (Lv.{$level})"); + $spellArray[$spellIndex] = ['spellId' => $spellId, 'spellInfo' => $spellInfo]; + } + $out->writeln("[0] 返回"); + + $choice = Input::ask($this->game->output, "请选择: ");; + + if ($choice == '0') { + return; + } + + if (isset($spellArray[$choice])) { + $selected = $spellArray[$choice]; + $this->displaySpellDetail($selected['spellId'], $selected['spellInfo'], $spellsData); + } else { + $out->writeln("{$this->red}无效选择{$this->reset}"); + Screen::delay(500000); + } + } + } + + /** + * 显示单个法术的详细信息 + */ + private function displaySpellDetail(int $spellId, array $spellInfo, array $spellsData) + { + $out = $this->game->output; + $player = $this->game->player; + + Screen::clear($out); + $out->writeln("{$this->bold}{$this->cyan}=== {$spellInfo['name']} ==={$this->reset}"); + $out->writeln(""); + + $quality = $spellInfo['quality'] ?? 'common'; + $qualityColor = $this->getQualityColor($quality); + $level = $player->getSpellLevel($spellId); + + $out->writeln("品质: {$qualityColor}{$this->getQualityName($quality)}{$this->reset}"); + $out->writeln("等级: {$this->yellow}Lv.{$level}{$this->reset}"); + $out->writeln("描述: {$spellInfo['desc']}"); + $out->writeln(""); + + // 显示法术效果信息 + $type = $spellInfo['type'] ?? ''; + $baseCost = $spellInfo['cost'] ?? 0; + $upgrades = $spellsData['upgrades'] ?? []; + $upgradeInfo = $upgrades[$level] ?? []; + $costReduction = $upgradeInfo['cost_reduction'] ?? 0; + $actualCost = max(1, $baseCost - $costReduction); + + $out->writeln("{$this->green}基础消耗: {$baseCost} → 当前消耗: {$actualCost}{$this->reset}"); + + if ($type === 'damage_single') { + $damage = $spellInfo['damage'] ?? 1.0; + $damageBonus = $upgradeInfo['damage_bonus'] ?? 0; + $actualDamage = $damage * (1 + $damageBonus / 100); + $out->writeln("伤害倍数: {$this->yellow}" . number_format($damage, 2) . "{$this->reset} → {$this->green}" . number_format($actualDamage, 2) . "x{$this->reset}"); + $out->writeln("效果: {$this->magenta}对单个敌人造成魔法伤害{$this->reset}"); + } elseif ($type === 'damage_aoe') { + $damage = $spellInfo['damage'] ?? 1.0; + $damageBonus = $upgradeInfo['damage_bonus'] ?? 0; + $actualDamage = $damage * (1 + $damageBonus / 100); + $out->writeln("伤害倍数: {$this->yellow}" . number_format($damage, 2) . "{$this->reset} → {$this->green}" . number_format($actualDamage, 2) . "x{$this->reset}"); + $out->writeln("效果: {$this->magenta}对所有敌人造成魔法伤害{$this->reset}"); + } elseif ($type === 'support') { + $subtype = $spellInfo['subtype'] ?? ''; + if ($subtype === 'heal' || $subtype === 'heal_all') { + $heal = $spellInfo['heal'] ?? 0.5; + $healBase = $spellInfo['heal_base'] ?? 20; + $out->writeln("恢复效果: {$this->green}魔攻 x {$heal} + {$healBase}{$this->reset}"); + $out->writeln("效果: {$this->magenta}恢复生命值{$this->reset}"); + } elseif ($subtype === 'defend') { + $defenseBoos = $spellInfo['defense_boost'] ?? 0; + $out->writeln("防御增加: {$this->green}+{$defenseBoos}{$this->reset}"); + $out->writeln("效果: {$this->magenta}增加防御力{$this->reset}"); + } + } + + $out->writeln(""); + Screen::pause($out); + } + + /** + * 学习法术 + */ + private function learnSpell(array $spellsData) + { + $out = $this->game->output; + $player = $this->game->player; + + Screen::clear($out); + $out->writeln("{$this->bold}{$this->cyan}=== 学习法术 ==={$this->reset}"); + $out->writeln(""); + + // 获取所有可学习的法术(按品质分类) + $allSpells = []; + foreach ($spellsData as $category => $spells) { + if (is_array($spells) && $category !== 'quality_levels' && $category !== 'upgrades') { + foreach ($spells as $spellId => $spellInfo) { + if (is_numeric($spellId)) { + $allSpells[$spellId] = $spellInfo; + } + } + } + } + + // 显示可以学习的法术 + $learnableIndex = 0; + $learnableSpells = []; + + foreach ($allSpells as $spellId => $spellInfo) { + // 检查是否已学习 + if ($player->hasSpell($spellId)) { + continue; + } + + // 检查等级要求 + $levelReq = $spellInfo['level_req'] ?? 1; + if ($player->level < $levelReq) { + continue; + } + + // 检查是否有资源书 + $bookCount = $player->getSpellBookCount($spellId); + if ($bookCount < 1) { + continue; + } + + $learnableIndex++; + $quality = $spellInfo['quality'] ?? 'common'; + $qualityColor = $this->getQualityColor($quality); + $name = $spellInfo['name'] ?? '未知法术'; + + $out->writeln("[{$learnableIndex}] {$qualityColor}{$name}{$this->reset} (需求等级: {$levelReq})"); + $learnableSpells[$learnableIndex] = ['spellId' => $spellId, 'spellInfo' => $spellInfo]; + } + + if (empty($learnableSpells)) { + $out->writeln("{$this->yellow}暂无可学习的法术(需要有资源书并满足等级要求){$this->reset}"); + Screen::delay(1000000); + return; + } + + $out->writeln("[0] 返回"); + $choice = Input::ask($this->game->output, "请选择: ");; + + if ($choice == '0') { + return; + } + + if (isset($learnableSpells[$choice])) { + $selected = $learnableSpells[$choice]; + $spellId = $selected['spellId']; + + // 消耗资源书 + $player->spendSpellBooks($spellId, 1); + + // 学习法术 + if ($player->learnSpell($spellId)) { + $out->writeln("{$this->green}✓ 成功学习 {$selected['spellInfo']['name']}{$this->reset}"); + } else { + $out->writeln("{$this->red}✗ 学习失败{$this->reset}"); + } + Screen::delay(1000000); + } else { + $out->writeln("{$this->red}无效选择{$this->reset}"); + Screen::delay(500000); + } + } + + /** + * 升级法术 + */ + private function upgradeSpell(array $spellsData) + { + $out = $this->game->output; + $in = $this->game->input; + $player = $this->game->player; + + Screen::clear($out); + $out->writeln("{$this->bold}{$this->cyan}=== 升级法术 ==={$this->reset}"); + $out->writeln(""); + + if (empty($player->spells)) { + $out->writeln("{$this->red}还未学习任何法术{$this->reset}"); + Screen::delay(1000000); + return; + } + + // 列出已学习但未满级的法术 + $upgradableIndex = 0; + $upgradableSpells = []; + $upgrades = $spellsData['upgrades'] ?? []; + + foreach ($player->spells as $spellId => $spellData) { + $level = $spellData['level'] ?? 1; + + // 检查是否已满级 + if ($level >= 10) { + continue; + } + + $spellInfo = $this->getSpellInfo($spellId, $spellsData); + $quality = $spellInfo['quality'] ?? 'common'; + $qualityColor = $this->getQualityColor($quality); + $name = $spellInfo['name'] ?? '未知法术'; + + // 获取升级所需资源书数量 + $nextLevel = $level + 1; + $upgradeInfo = $upgrades[$nextLevel] ?? ['cost' => 0]; + $booksNeeded = $upgradeInfo['cost'] ?? 0; + $booksOwned = $player->getSpellBookCount($spellId); + + $upgradableIndex++; + $canUpgrade = $booksOwned >= $booksNeeded; + $statusColor = $canUpgrade ? $this->green : $this->red; + + $out->writeln("[{$upgradableIndex}] {$qualityColor}{$name}{$this->reset} Lv.{$level}"); + $out->writeln(" 升级所需: {$statusColor}{$booksNeeded}{$this->reset} 本资源书 (拥有: {$booksOwned})"); + $upgradableSpells[$upgradableIndex] = ['spellId' => $spellId, 'spellInfo' => $spellInfo]; + } + + if (empty($upgradableSpells)) { + $out->writeln("{$this->yellow}暂无可升级的法术{$this->reset}"); + Screen::delay(1000000); + return; + } + + $out->writeln("[0] 返回"); + $choice = $in->prompt("请选择要升级的法术"); + + if ($choice == '0') { + return; + } + + if (isset($upgradableSpells[$choice])) { + $selected = $upgradableSpells[$choice]; + $spellId = $selected['spellId']; + $level = $player->getSpellLevel($spellId); + $nextLevel = $level + 1; + $upgradeInfo = $upgrades[$nextLevel] ?? ['cost' => 0]; + $booksNeeded = $upgradeInfo['cost'] ?? 0; + + // 检查是否有足够的资源书 + if (!$player->spendSpellBooks($spellId, $booksNeeded)) { + $out->writeln("{$this->red}✗ 资源书不足!{$this->reset}"); + Screen::delay(1000000); + return; + } + + // 升级法术 + if ($player->upgradeSpell($spellId)) { + $out->writeln("{$this->green}✓ 成功升级 {$selected['spellInfo']['name']} 至 Lv.{$nextLevel}{$this->reset}"); + } else { + $out->writeln("{$this->red}✗ 升级失败{$this->reset}"); + } + Screen::delay(1000000); + } else { + $out->writeln("{$this->red}无效选择{$this->reset}"); + Screen::delay(500000); + } + } + + /** + * 获取魔法值进度条 + */ + private function getManaBar(int $current, int $max): string + { + $percent = ($current / $max) * 100; + $filled = (int)(20 * ($current / $max)); + $empty = 20 - $filled; + + $color = $percent >= 50 ? $this->green : ($percent >= 25 ? $this->yellow : $this->red); + + return "{$color}[" . str_repeat('█', $filled) . str_repeat('░', $empty) . "]{$this->reset} " . number_format($percent, 0) . "%"; + } + + /** + * 获取法术的品质颜色 + */ + private function getQualityColor(string $quality): string + { + return match($quality) { + 'common' => "\033[37m", // 白色 + 'rare' => "\033[34m", // 蓝色 + 'epic' => "\033[35m", // 紫色 + 'legendary' => "\033[33m", // 黄色 + default => $this->white + }; + } + + /** + * 获取品质名称 + */ + private function getQualityName(string $quality): string + { + return match($quality) { + 'common' => '普通', + 'rare' => '稀有', + 'epic' => '史诗', + 'legendary' => '传奇', + default => '未知' + }; + } + + /** + * 获取法术信息 + */ + private function getSpellInfo(int $spellId, array $spellsData): ?array + { + foreach ($spellsData as $category => $spells) { + if (is_array($spells) && $category !== 'quality_levels' && $category !== 'upgrades') { + if (isset($spells[$spellId])) { + return $spells[$spellId]; + } + } + } + return null; + } +}