同伴当前HP */ private array $partnerHp = []; // 法术数据 private array $spellsData = []; private array $qualityColors = [ 'common' => "\033[37m", // 白色 'rare' => "\033[34m", // 蓝色 'epic' => "\033[35m", // 紫色 'legendary' => "\033[33m", // 黄色 ]; // 颜色定义 private string $red = "\033[31m"; private string $green = "\033[32m"; private string $yellow = "\033[33m"; private string $cyan = "\033[36m"; private string $white = "\033[37m"; private string $magenta = "\033[35m"; private string $bold = "\033[1m"; private string $reset = "\033[0m"; private int $round = 0; private int $totalMaxHp = 0; public function __construct(public Game $game) { $this->player = $game->player; $this->spellsData = require __DIR__ . '/../../src/Data/spells.php'; } /** * 初始化同伴HP */ private function initPartnerHp(): void { $this->partnerHp = []; foreach ($this->player->partners as $partner) { // 从Partner对象的hp属性读取,允许队友在战斗外也能恢复 $this->partnerHp[$partner->id] = $partner->hp; } } /** * 获取存活的同伴 */ private function getAlivePartners(): array { $alive = []; foreach ($this->player->partners as $partner) { if (($this->partnerHp[$partner->id] ?? 0) > 0) { $alive[] = $partner; } } return $alive; } /** * 将战斗中的队友HP同步回Partner对象 */ private function syncPartnerHp(): void { foreach ($this->player->partners as $partner) { $partner->hp = $this->partnerHp[$partner->id] ?? 0; } } /** * 获取存活的敌人 * @return Monster[] */ private function getAliveEnemies(): array { $alive = []; foreach ($this->enemies as $enemy) { if ($enemy->hp > 0) { $alive[] = $enemy; } } return $alive; } public function start() { $out = $this->game->output; // 初始化同伴HP $this->initPartnerHp(); while ($this->player->hp > 0) { Screen::delay(500000); // 创建敌人群组 $this->enemies = Monster::createGroup($this->game->dungeonId); $this->totalMaxHp = 0; foreach ($this->enemies as $enemy) { $this->totalMaxHp += $enemy->hp; } $this->round = 0; // 显示遭遇界面 $this->showEncounter($out); $playerFirst = $this->determineFirstStrike(); // 战斗循环 while (true) { if ($this->checkExit($out)) { $this->syncPartnerHp(); return; } $this->round++; $this->renderBattleScreen($out, $playerFirst); Screen::delay(800000); // 回合开始停顿 if ($playerFirst) { // 玩家攻击 $result = $this->playerAttack($out); Screen::delay(800000); if ($result) break; // 同伴攻击 $result = $this->partnersAttack($out); Screen::delay(800000); if ($result) break; if ($this->checkExit($out)) { $this->syncPartnerHp(); return; } // 怪物攻击 if ($this->enemiesAttack($out)) { Screen::delay(1000000); $this->syncPartnerHp(); return; } Screen::delay(800000); } else { // 怪物先攻 if ($this->enemiesAttack($out)) { Screen::delay(1000000); $this->syncPartnerHp(); return; } Screen::delay(800000); if ($this->checkExit($out)) { $this->syncPartnerHp(); return; } // 玩家攻击 $result = $this->playerAttack($out); Screen::delay(800000); if ($result) break; // 同伴攻击 $result = $this->partnersAttack($out); Screen::delay(800000); if ($result) break; } Screen::delay(500000); // 同步队友HP到Partner对象,然后保存状态 $this->syncPartnerHp(); $this->game->saveState(); } } } private function showEncounter($out) { Screen::clear($out); $out->writeln(""); $out->writeln("{$this->yellow}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{$this->reset}"); $out->writeln(""); $out->writeln(" {$this->red}⚔️ 遭遇敌人!{$this->reset}"); $out->writeln(""); foreach ($this->enemies as $enemy) { $out->writeln(" {$this->bold}{$this->white}{$enemy->name}{$this->reset} {$this->cyan}Lv.{$enemy->level}{$this->reset}"); } $out->writeln(""); $out->writeln("{$this->yellow}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{$this->reset}"); Screen::delay(1000000); // 1秒 } private function renderBattleScreen($out, bool $playerFirst) { Screen::clear($out); $stats = $this->player->getStats(); $out->writeln("{$this->cyan}╔══════════════════════════════════════════╗{$this->reset}"); $out->writeln("{$this->cyan}║{$this->reset} {$this->bold}第 {$this->round} 回合{$this->reset} {$this->white}[q] 逃跑{$this->reset} {$this->cyan}║{$this->reset}"); $out->writeln("{$this->cyan}╠══════════════════════════════════════════╣{$this->reset}"); // 敌人信息 foreach ($this->enemies as $enemy) { if ($enemy->hp <= 0) { $out->writeln("{$this->cyan}║{$this->reset} {$this->red}💀{$this->reset} {$this->white}{$enemy->name}{$this->reset} {$this->red}[已击败]{$this->reset}"); continue; } $enemyHpPercent = max(0, $enemy->hp) / $enemy->baseHp; // 使用baseHp作为最大值近似,或者应该在hydrate时保存maxHp // 实际上Monster没有maxHp属性,hp初始值就是最大值。但在战斗中hp会减少。 // 我们需要知道最大HP。Monster::create时hp=baseHp+equipHp。 // 简单起见,假设当前hp <= 初始hp。如果需要精确显示条,应该在Monster类加maxHp。 // 这里暂时用 $enemy->baseHp + equipHp 估算,或者直接存一个 maxHp。 // 为了简单,我们假设满血是初始状态。 // 更好的做法是Monster类加一个maxHp属性。 // 暂时用 $enemy->hp / $enemy->hp (如果满血) ... 不行。 // 让我们修改Monster类加maxHp? 或者这里不显示条,只显示数值? // 或者我们假定 create 出来的 hp 就是 maxHp。 // $enemy->maxHp = $enemy->hp; // 在create里做最好。 // 这里先只显示数值吧,或者大概估算。 $hpText = max(0, $enemy->hp); $out->writeln("{$this->cyan}║{$this->reset} {$this->red}👹{$this->reset} {$this->bold}{$enemy->name}{$this->reset} Lv.{$enemy->level} HP: {$this->red}{$hpText}{$this->reset}"); } $out->writeln("{$this->cyan}║{$this->reset}"); // VS 分隔 $out->writeln("{$this->cyan}║{$this->reset} {$this->yellow}⚔️ VS ⚔️{$this->reset}"); $out->writeln("{$this->cyan}║{$this->reset}"); // 玩家信息 $playerHpPercent = $this->player->hp / $stats['maxHp']; $playerHpBar = $this->renderHpBar($playerHpPercent, 20); $playerHpText = $this->player->hp . "/" . $stats['maxHp']; $out->writeln("{$this->cyan}║{$this->reset} {$this->green}🧙{$this->reset} {$this->bold}玩家{$this->reset} Lv.{$this->player->level}"); $out->writeln("{$this->cyan}║{$this->reset} {$playerHpBar} {$this->white}{$playerHpText}{$this->reset}"); $out->writeln("{$this->cyan}║{$this->reset} {$this->yellow}⚔️{$this->reset} {$stats['patk']}/{$stats['matk']} {$this->green}🛡️{$this->reset} {$stats['pdef']}/{$stats['mdef']} {$this->red}💥{$this->reset} {$stats['crit']}%"); // 显示同伴信息 foreach ($this->player->partners as $partner) { $partnerStats = $partner->getStats(); $partnerHp = $this->partnerHp[$partner->id] ?? 0; $partnerMaxHp = $partnerStats['maxHp']; $partnerHpPercent = $partnerMaxHp > 0 ? $partnerHp / $partnerMaxHp : 0; $partnerHpBar = $this->renderHpBar($partnerHpPercent, 15); $partnerHpText = $partnerHp . "/" . $partnerMaxHp; $status = $partnerHp > 0 ? "" : " {$this->red}[倒下]{$this->reset}"; $out->writeln("{$this->cyan}║{$this->reset} {$this->magenta}👤{$this->reset} {$partner->name} Lv.{$partner->level}{$status}"); $out->writeln("{$this->cyan}║{$this->reset} {$partnerHpBar} {$this->white}{$partnerHpText}{$this->reset}"); } $out->writeln("{$this->cyan}╠══════════════════════════════════════════╣{$this->reset}"); $out->writeln("{$this->cyan}║{$this->reset} {$this->white}战斗日志:{$this->reset}"); } private function renderHpBar(float $percent, int $width): string { $filled = (int)($percent * $width); $empty = max($width - $filled,0); // 根据血量百分比选择颜色 if ($percent > 0.6) { $color = $this->green; } elseif ($percent > 0.3) { $color = $this->yellow; } else { $color = $this->red; } $bar = $color . str_repeat("█", $filled) . $this->white . str_repeat("░", $empty) . $this->reset; return "[" . $bar . "]"; } private function determineFirstStrike(): bool { // Use the leader's level for comparison $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)); $playerChance += $levelBonus; $roll = rand(1, 100); 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) { if ($enemy->hp > 0) { $target = $enemy; break; } } if (!$target) return true; // All dead // 计算物理伤害和魔法伤害 $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}"); // Check if all enemies are dead if (empty($this->getAliveEnemies())) { Screen::delay(500000); $this->showVictory($out, $stats); return true; } } return false; } /** * 同伴攻击 */ private function partnersAttack($out): bool { $alivePartners = $this->getAlivePartners(); foreach ($alivePartners as $partner) { // Target first alive enemy $target = null; foreach ($this->enemies as $enemy) { if ($enemy->hp > 0) { $target = $enemy; break; } } if (!$target) return true; // All dead $stats = $partner->getStats(); // 计算物理伤害和魔法伤害 $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->magenta}➤{$this->reset} {$partner->name} 攻击 {$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->magenta}➤{$this->reset} {$partner->name} 攻击 {$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, $this->player->getStats()); return true; } } Screen::delay(400000); // 每个同伴攻击间隔 } return false; } private function enemiesAttack($out): bool { $aliveEnemies = $this->getAliveEnemies(); foreach ($aliveEnemies as $enemy) { // 选择攻击目标:玩家或存活的同伴 $alivePartners = $this->getAlivePartners(); $targets = ['player']; foreach ($alivePartners as $partner) { $targets[] = $partner->id; } $targetIdx = array_rand($targets); $target = $targets[$targetIdx]; if ($target === 'player') { // 攻击玩家 $playerStats = $this->player->getStats(); $physicalDamage = max(1, $enemy->patk - $playerStats['pdef']); $magicDamage = max(0, $enemy->matk - $playerStats['mdef']); $damage = $physicalDamage + $magicDamage; $out->writeln("{$this->cyan}║{$this->reset} {$this->red}➤{$this->reset} {$enemy->name} 向你发起攻击..."); $out->writeln("{$this->cyan}║{$this->reset} {$this->red}💢 你受到 {$damage} 点伤害{$this->reset}"); $this->player->hp -= $damage; if ($this->player->hp <= 0) { $this->player->hp = 0; Screen::delay(500000); $this->showDefeat($out, $enemy); return true; } } else { // 攻击同伴 $partner = $this->player->partners[$target]; $partnerStats = $partner->getStats(); $physicalDamage = max(1, $enemy->patk - $partnerStats['pdef']); $magicDamage = max(0, $enemy->matk - $partnerStats['mdef']); $damage = $physicalDamage + $magicDamage; $out->writeln("{$this->cyan}║{$this->reset} {$this->red}➤{$this->reset} {$enemy->name} 向 {$partner->name} 发起攻击..."); $out->writeln("{$this->cyan}║{$this->reset} {$this->red}💢 {$partner->name} 受到 {$damage} 点伤害{$this->reset}"); $this->partnerHp[$target] -= $damage; if ($this->partnerHp[$target] <= 0) { $this->partnerHp[$target] = 0; $out->writeln("{$this->cyan}║{$this->reset} {$this->red}💀 {$partner->name} 倒下了!{$this->reset}"); } } Screen::delay(400000); } return false; } private function showVictory($out, $stats) { Screen::clear($out); $out->writeln(""); $out->writeln("{$this->yellow}╔══════════════════════════════════════════╗{$this->reset}"); $out->writeln("{$this->yellow}║{$this->reset} {$this->yellow}║{$this->reset}"); $out->writeln("{$this->yellow}║{$this->reset} {$this->green}{$this->bold}🎉 胜 利 ! 🎉{$this->reset} {$this->yellow}║{$this->reset}"); $out->writeln("{$this->yellow}║{$this->reset} {$this->yellow}║{$this->reset}"); $out->writeln("{$this->yellow}╠══════════════════════════════════════════╣{$this->reset}"); $enemyNames = []; foreach ($this->enemies as $e) $enemyNames[] = $e->name; $out->writeln("{$this->yellow}║{$this->reset} 击败: {$this->white}" . implode(', ', array_unique($enemyNames)) . "{$this->reset}"); $out->writeln("{$this->yellow}║{$this->reset} 血量: {$this->green}{$this->player->hp}{$this->reset}/{$stats['maxHp']}"); // 汇总经验和灵石 $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); $allDrops[] = $item; } foreach ($enemy->dropTable as $drop) { if (rand(1, 100) <= $drop['rate']) { $this->player->addItem($drop['item']); $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; } } } } // 经验 $levelUpMsg = ""; if ($this->player->gainExp($totalExp)) { $levelUpMsg = " {$this->yellow}🎊 升级! Lv.{$this->player->level}{$this->reset}"; } $out->writeln("{$this->yellow}║{$this->reset} 经验: {$this->cyan}+{$totalExp}{$this->reset}{$levelUpMsg}"); // 同伴经验 $alivePartners = $this->getAlivePartners(); if (!empty($alivePartners)) { $partnerExp = (int)($totalExp * 0.8); foreach ($alivePartners as $partner) { $partnerLevelUp = ""; if ($partner->gainExp($partnerExp)) { $partnerLevelUp = " {$this->yellow}🎊 升级! Lv.{$partner->level}{$this->reset}"; } $out->writeln("{$this->yellow}║{$this->reset} {$this->magenta}{$partner->name}{$this->reset}: {$this->cyan}+{$partnerExp}{$this->reset}{$partnerLevelUp}"); } } // 灵石 if ($totalStones > 0) { $this->player->addSpiritStones($totalStones); $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) { $out->writeln("{$this->yellow}║{$this->reset} " . ItemDisplay::renderDrop($item, "")); } } $out->writeln("{$this->yellow}╚══════════════════════════════════════════╝{$this->reset}"); $out->writeln(""); $this->game->saveState(); Screen::delay(1500000); } private function showDefeat($out, ?Monster $killer = null) { Screen::clear($out); $killerName = $killer ? $killer->name : "敌人"; $out->writeln(""); $out->writeln("{$this->red}╔══════════════════════════════════════════╗{$this->reset}"); $out->writeln("{$this->red}║{$this->reset} {$this->red}║{$this->reset}"); $out->writeln("{$this->red}║{$this->reset} {$this->red}{$this->bold}💀 战 败 ! 💀{$this->reset} {$this->red}║{$this->reset}"); $out->writeln("{$this->red}║{$this->reset} {$this->red}║{$this->reset}"); $out->writeln("{$this->red}╠══════════════════════════════════════════╣{$this->reset}"); $out->writeln("{$this->red}║{$this->reset} 你被 {$this->white}{$killerName}{$this->reset} 击败了..."); $out->writeln("{$this->red}║{$this->reset}"); $out->writeln("{$this->red}║{$this->reset} {$this->white}不要气馁,休整后再战!{$this->reset}"); $out->writeln("{$this->red}╚══════════════════════════════════════════╝{$this->reset}"); $out->writeln(""); $this->game->state = Game::MENU; Screen::pause($out); } private function checkExit($out): bool { // Web 模式下跳过实时输入检测 $webInput = \Game\Core\WebInput::getInstance(); if ($webInput->isWebMode()) { return false; } stream_set_blocking(\STDIN, 0); $input = ''; while (($char = fgetc(\STDIN)) !== false) { $input .= $char; } stream_set_blocking(\STDIN, 1); if (str_contains($input, 'q') || str_contains($input, 'Q')) { $this->game->saveState(); Screen::clear($out); $out->writeln(""); $out->writeln("{$this->yellow}🏃 你逃离了战斗...{$this->reset}"); $out->writeln(""); Screen::delay(800000); $this->game->state = Game::MENU; return true; } return false; } }