同伴当前HP */ private array $partnerHp = []; // 法术数据 private array $spellsData = []; private array $qualityColors = []; // 颜色定义 (use Colors constants) private string $red; private string $green; private string $yellow; private string $cyan; private string $white; private string $blue; private string $magenta; private string $bold; private string $reset; 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'; // Initialize color palette from centralized Colors $this->red = Colors::RED; $this->green = Colors::GREEN; $this->yellow = Colors::YELLOW; $this->cyan = Colors::CYAN; $this->white = Colors::WHITE; $this->magenta = Colors::MAGENTA; $this->bold = Colors::BOLD; $this->reset = Colors::RESET; $this->blue = Colors::BLUE; $this->qualityColors = [ 'common' => Colors::WHITE, 'rare' => Colors::BLUE, 'epic' => Colors::MAGENTA, 'legendary' => Colors::YELLOW, ]; } /** * 初始化同伴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 Actor[] */ 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(); if ($this->player->hp <= 0) { $out->writeln("{$this->red}你已经重伤倒地,无法继续战斗!请先去恢复生命值。{$this->reset}"); Screen::pause($out); $this->game->state = Game::MENU; return; } 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; } $enemyStats = $enemy->getStats(); $enemyMaxHp = $enemyStats['maxHp']; $enemyHpPercent = $enemyMaxHp > 0 ? $enemy->hp / $enemyMaxHp : 0; $enemyHpBar = $this->renderHpBar($enemyHpPercent, 15); $enemyHpText = $enemy->hp . "/" . $enemyMaxHp; $out->writeln("{$this->cyan}║{$this->reset} {$this->red}👹{$this->reset} {$this->bold}{$enemy->name}{$this->reset} Lv.{$enemy->level}"); $out->writeln("{$this->cyan}║{$this->reset} {$enemyHpBar} {$this->white}{$enemyHpText}{$this->reset}"); $out->writeln("{$this->cyan}║{$this->reset} {$this->yellow}⚔️{$this->reset} {$enemyStats['patk']}/{$enemyStats['matk']} {$this->green}🛡️{$this->reset} {$enemyStats['pdef']}/{$enemyStats['mdef']} {$this->red}💥{$this->reset} {$enemyStats['crit']}/{$enemyStats['critdmg']}%"); } $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->blue}🔮 {$stats['mana']}/{$stats['maxMana']}{$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']}/{$stats['critdmg']}%"); // 显示同伴信息 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} {$this->blue}🔮 {$partnerStats['mana']}/{$partnerStats['maxMana']}{$this->reset}"); $out->writeln("{$this->cyan}║{$this->reset} {$this->yellow}⚔️{$this->reset} {$partnerStats['patk']}/{$partnerStats['matk']} {$this->green}🛡️{$this->reset} {$partnerStats['pdef']}/{$partnerStats['mdef']} {$this->red}💥{$this->reset} {$partnerStats['crit']}/{$partnerStats['critdmg']}%"); } $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 { // 检查是否有装备的法术 $hasSpells = false; foreach ($this->player->skillSlots as $slot => $spell) { if ($spell !== null) { $hasSpells = true; break; } } if (!$hasSpells || $this->player->mana < 15) { return 'attack'; } // 如果玩家有法术且魔法值充足,40% 概率选择法术 $spellChance = rand(1, 100); if ($spellChance <= 40) { return 'spell'; } return 'attack'; } /** * 获取敌对阵营 * @return Actor[] */ private function getOpponents(Actor $actor): array { if ($actor instanceof Player || $actor instanceof Partner) { return $this->getAliveEnemies(); } else { // 怪物视角的敌人是玩家和同伴 $opponents = []; if ($this->player->hp > 0) $opponents[] = $this->player; foreach ($this->getAlivePartners() as $partner) { $opponents[] = $partner; } return $opponents; } } /** * 获取友方阵营 * @return Actor[] */ private function getAllies(Actor $actor): array { if ($actor instanceof Player || $actor instanceof Partner) { $allies = []; if ($this->player->hp > 0) $allies[] = $this->player; foreach ($this->getAlivePartners() as $partner) { $allies[] = $partner; } return $allies; } else { return $this->getAliveEnemies(); // 怪物的友方是其他怪物 } } /** * 计算目标的威胁值(用于敌人选择目标) * 威胁值 = 输出能力 + 防御能力 + 防护状态加成 */ private function calculateThreat(Actor $target): int { $stats = $target->getStats(); // 基础威胁值 = (物攻 + 魔攻) / 2 $threat = (int)(($stats['patk'] + $stats['matk']) / 2); // 防御角色威胁值大幅上升 $threat += $target->getThreatBonus(); // 血量比例也会影响威胁值(血量越多越有威胁) $threat += (int)($stats['hp'] / $stats['maxHp'] * 10); return $threat; } /** * 智能选择单体目标(考虑防护角色机制) */ private function selectTargetWithThreat(Actor $actor): ?Actor { $opponents = $this->getOpponents($actor); $aliveOpponents = array_filter($opponents, fn($e) => $e->hp > 0); if (empty($aliveOpponents)) { return null; } // 优先级1:选择处于防护状态的目标(防御者) $protectingTargets = array_filter($aliveOpponents, fn($e) => $e->isProtecting); if (!empty($protectingTargets)) { return current($protectingTargets); } // 优先级2:选择威胁值最高的目标 $maxThreat = -1; $bestTarget = null; foreach ($aliveOpponents as $target) { $threat = $this->calculateThreat($target); if ($threat > $maxThreat) { $maxThreat = $threat; $bestTarget = $target; } } return $bestTarget ?? current($aliveOpponents); } /** * 智能选择法术 (通用) */ private function smartSelectSpell(Actor $actor): ?array { // 1. 分析战场状态 $opponents = $this->getOpponents($actor); $allies = $this->getAllies($actor); $enemyCount = count($opponents); $lowHpAllies = []; foreach ($allies as $ally) { $status = $ally->getStats(); if ($status['hp'] < $status['maxHp'] * 0.5) { $lowHpAllies[] = $ally; } } $lowHpCount = count($lowHpAllies); // 2. 筛选可用法术并分类 $availableSpells = [ 'heal_aoe' => [], 'heal_single' => [], 'damage_aoe' => [], 'damage_single' => [], 'support' => [], 'all' => [] ]; foreach ($actor->skillSlots as $slot => $spellItem) { if ($spellItem === null) continue; $actualCost = SpellCalculator::calculateCost($spellItem); if ($actor->mana >= $actualCost) { $spellData = [ 'item' => $spellItem, 'cost' => $actualCost, 'level' => $spellItem['enhanceLevel'] ?? 0 ]; $type = $spellItem['spellType'] ?? 'damage_single'; if (isset($availableSpells[$type])) { $availableSpells[$type][] = $spellData; } $availableSpells['all'][] = $spellData; } } if (empty($availableSpells['all'])) { return null; } // 3. 智能选择策略 $selected = null; // 策略A: 优先治疗 if ($lowHpCount > 0) { if ($lowHpCount > 1 && !empty($availableSpells['heal_aoe'])) { $selected = $availableSpells['heal_aoe'][array_rand($availableSpells['heal_aoe'])]; } elseif (!empty($availableSpells['heal_single'])) { $selected = $availableSpells['heal_single'][array_rand($availableSpells['heal_single'])]; } elseif (!empty($availableSpells['heal_aoe'])) { $selected = $availableSpells['heal_aoe'][array_rand($availableSpells['heal_aoe'])]; } } // 策略B: 伤害输出 if ($selected === null) { if ($enemyCount > 1 && !empty($availableSpells['damage_aoe'])) { $selected = $availableSpells['damage_aoe'][array_rand($availableSpells['damage_aoe'])]; } elseif (!empty($availableSpells['damage_single'])) { $selected = $availableSpells['damage_single'][array_rand($availableSpells['damage_single'])]; } elseif (!empty($availableSpells['damage_aoe'])) { $selected = $availableSpells['damage_aoe'][array_rand($availableSpells['damage_aoe'])]; } } // 策略C: 兜底 if ($selected === null) { $selected = $availableSpells['all'][array_rand($availableSpells['all'])]; } if ($selected && in_array($selected['item']['spellType'],['heal_aoe', 'heal_single']) && $lowHpCount == 0){ return []; } return $selected; } /** * 施放单体伤害法术 (通用) */ private function castDamageSingleSpell($out, Actor $caster, ?Actor $target, array $spellInfo, array $stats, int $damageBonus, string $name): bool { // 自动选择目标(使用威胁值系统) if (!$target) { $target = $this->selectTargetWithThreat($caster); } if (!$target) return true; // 显示法术基础信息 $quality = $spellInfo['quality'] ?? 'common'; $qualityColor = SpellDisplay::getQualityColor($quality); // 计算法术伤害 $damageResult = SpellCalculator::calculateDamage($spellInfo, $stats, $target->getStats(), $damageBonus); $damage = $damageResult['damage']; $isCrit = $damageResult['isCrit']; // 显示法术施放信息 $casterName = ($caster instanceof Player) ? "你" : $caster->name; $actionVerb = ($caster instanceof Player) ? "施放" : "施放了"; $out->writeln("{$this->cyan}║{$this->reset} {$this->magenta}✦{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}"); if ($isCrit) { $out->writeln("{$this->cyan}║{$this->reset} {$this->magenta}✨ {$this->red}{$this->bold}暴击!{$this->reset} 对 {$target->name} 造成 {$this->red}{$damage}{$this->reset} 点魔法伤害!"); } else { $out->writeln("{$this->cyan}║{$this->reset} {$this->magenta}✨ 对 {$target->name} 造成 {$this->green}{$damage}{$this->reset} 点魔法伤害"); } // 应用防护机制:防护角色承受更多伤害 $actualDamage = (int)($damage * (1 + $target->getProtectDamageTakenBonus())); $target->hp -= $actualDamage; // 如果防护角色正在保护队友,需要显示保护效果 if ($target->isProtecting && $actualDamage > $damage) { $extraDamage = $actualDamage - $damage; $out->writeln("{$this->cyan}║{$this->reset} {$this->yellow}🛡️ 防护状态:额外承受 {$extraDamage} 伤害!{$this->reset}"); } if ($target->hp <= 0) { $target->hp = 0; $out->writeln("{$this->cyan}║{$this->reset} {$this->red}💀 {$target->name} 被击败了!{$this->reset}"); // 如果是玩家击败了所有敌人 if (($caster instanceof Player || $caster instanceof Partner) && empty($this->getAliveEnemies())) { Screen::delay(500000); $this->showVictory($out, $stats); return true; } // 如果是敌人击败了玩家 if ($target instanceof Player) { Screen::delay(500000); $this->showDefeat($out, $caster); return true; } } return false; } /** * 施放AOE伤害法术 (通用) */ private function castDamageAoeSpell($out, Actor $caster, ?Actor $target, array $spellInfo, array $stats, int $damageBonus, string $name): bool { // 显示法术基础信息 $quality = $spellInfo['quality'] ?? 'common'; $qualityColor = SpellDisplay::getQualityColor($quality); $casterName = ($caster instanceof Player) ? "你" : $caster->name; $actionVerb = ($caster instanceof Player) ? "施放" : "施放了"; $out->writeln("{$this->cyan}║{$this->reset} {$this->magenta}✦{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}"); $out->writeln("{$this->cyan}║{$this->reset} {$this->magenta}✨ 魔法在整个战场爆炸!{$this->reset}"); $opponents = $this->getOpponents($caster); $allOpponentsDefeated = true; foreach ($opponents as $enemy) { if ($enemy->hp <= 0) continue; $damageResult = SpellCalculator::calculateDamage($spellInfo, $stats, $enemy->getStats(), $damageBonus, true); $damage = $damageResult['damage']; $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}"); if ($enemy instanceof Player) { Screen::delay(500000); $this->showDefeat($out, $caster); return true; } } else { $allOpponentsDefeated = false; } } // 更新同伴血量显示 (如果敌人是同伴) if (!($caster instanceof Player) && !($caster instanceof Partner)) { foreach ($this->player->partners as $partner) { if (isset($this->partnerHp[$partner->id])) { $this->partnerHp[$partner->id] = $partner->hp; } } } if ($allOpponentsDefeated && ($caster instanceof Player || $caster instanceof Partner) && empty($this->getAliveEnemies())) { Screen::delay(500000); $this->showVictory($out, $stats); return true; } return false; } /** * 施放单体治疗法术 (通用) */ private function castHealSingleSpell($out, Actor $caster, ?Actor $target, array $spellInfo, array $stats, int $healBonus, string $name): bool { // 自动选择目标 (优先治疗血量最低的友方) if (!$target) { $allies = $this->getAllies($caster); $lowestHpRatio = 1.0; foreach ($allies as $ally) { $ratio = $ally->hp / $ally->maxHp; if ($ratio < $lowestHpRatio) { $lowestHpRatio = $ratio; $target = $ally; } } // 如果都满血,治疗自己 if (!$target) $target = $caster; } // 显示法术基础信息 $quality = $spellInfo['quality'] ?? 'common'; $qualityColor = SpellDisplay::getQualityColor($quality); $casterName = ($caster instanceof Player) ? "你" : $caster->name; $actionVerb = ($caster instanceof Player) ? "施放" : "施放了"; $out->writeln("{$this->cyan}║{$this->reset} {$this->green}✦{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}"); $healAmount = SpellCalculator::calculateHeal($spellInfo, $stats, $healBonus); $actualHeal = $target->heal($healAmount); // 更新同伴血量显示 if ($target instanceof Partner && isset($this->partnerHp[$target->id])) { $this->partnerHp[$target->id] = $target->hp; } $targetName = ($target === $this->player) ? "你" : $target->name; $out->writeln("{$this->cyan}║{$this->reset} {$this->green}💚 {$targetName} 恢复了 {$actualHeal} 点生命值{$this->reset}"); return false; } /** * 施放群体治疗法术 (通用) */ private function castHealAoeSpell($out, Actor $caster, ?Actor $target, array $spellInfo, array $stats, int $healBonus, string $name): bool { // 显示法术基础信息 $quality = $spellInfo['quality'] ?? 'common'; $qualityColor = SpellDisplay::getQualityColor($quality); $casterName = ($caster instanceof Player) ? "你" : $caster->name; $actionVerb = ($caster instanceof Player) ? "施放" : "施放了"; $out->writeln("{$this->cyan}║{$this->reset} {$this->green}✦{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}"); $healAmount = SpellCalculator::calculateHeal($spellInfo, $stats, $healBonus); $allies = $this->getAllies($caster); foreach ($allies as $ally) { if ($ally->hp <= 0) continue; // 如果是施法者本人,全额治疗;如果是队友,80%效果 $finalHeal = ($ally === $caster) ? $healAmount : (int)($healAmount * 0.8); $actualHeal = $ally->heal($finalHeal); // 更新同伴血量显示 if ($ally instanceof Partner && isset($this->partnerHp[$ally->id])) { $this->partnerHp[$ally->id] = $ally->hp; } $allyName = ($ally === $this->player) ? "你" : $ally->name; $out->writeln("{$this->cyan}║{$this->reset} {$this->green}💚 {$allyName} 恢复了 {$actualHeal} 点生命值{$this->reset}"); } return false; } /** * 施放辅助法术 (通用) */ /** * 施放辅助法术 (通用) */ private function castSupportSpell($out, Actor $caster, ?Actor $target, array $spellInfo, array $stats, string $name): bool { // 显示法术基础信息 $quality = $spellInfo['quality'] ?? 'common'; $qualityColor = SpellDisplay::getQualityColor($quality); $casterName = ($caster instanceof Player) ? "你" : $caster->name; $actionVerb = ($caster instanceof Player) ? "施放" : "施放了"; $out->writeln("{$this->cyan}║{$this->reset} {$this->cyan}✦{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}"); $subtype = $spellInfo['subtype'] ?? ''; if ($subtype === 'heal' || $subtype === 'heal_all') { // 委托给治疗逻辑 $healBonus = 0; // 辅助类治疗通常没有额外加成 if ($subtype === 'heal') { return $this->castHealSingleSpell($out, $caster, $target, $spellInfo, $stats, $healBonus, $name); } else { return $this->castHealAoeSpell($out, $caster, $target, $spellInfo, $stats, $healBonus, $name); } } elseif ($subtype === 'defend') { // 简单的防御提升逻辑 (目前只是显示,实际效果需在伤害计算中支持buff) $out->writeln("{$this->cyan}║{$this->reset} {$this->cyan}🛡️ 防御力提升!{$this->reset}"); } return false; } /** * 检查是否应该自动进入防护状态来保护低血量队友 * 条件:自身血量健康 (>50%) 且有队友血量低 (<30%) */ private function checkAutoEnterProtectMode(Actor $actor, $out): void { // 仅玩家和队友可以进入防护状态,敌人不需要 if (!($actor instanceof Player) && !($actor instanceof Partner)) { return; } // 已经在防护状态,不需要再进入 if ($actor->isProtecting) { return; } $stats = $actor->getStats(); $healthRatio = $stats['hp'] / $stats['maxHp']; // 自身血量需要健康(>50%) if ($healthRatio <= 0.5) { return; } // 检查是否有队友血量低 $allies = $this->getAllies($actor); $hasLowHpAlly = false; foreach ($allies as $ally) { // 跳过自己和倒下的队友 if ($ally === $actor || $ally->hp <= 0) { continue; } $allyStats = $ally->getStats(); $allyHealthRatio = $allyStats['hp'] / $allyStats['maxHp']; // 如果有队友血量低于30%,进入防护状态 if ($allyHealthRatio < 0.3) { $hasLowHpAlly = true; break; } } // 自动进入防护状态 if ($hasLowHpAlly) { $actor->enterProtectMode(); $actorName = ($actor instanceof Player) ? "你" : $actor->name; $out->writeln("{$this->cyan}║{$this->reset} {$this->yellow}🛡️{$this->reset} {$actorName} 主动进入防护状态,为队友抗伤!{$this->reset}"); } } /** * 执行角色的回合行动 (通用) */ private function executeActorTurn(Actor $actor, $out): bool { if ($actor->hp <= 0){ return false; } // 检查是否应该自动进入防护状态 $this->checkAutoEnterProtectMode($actor, $out); // 1. 尝试使用法术 $selectedSpell = $this->smartSelectSpell($actor); if ($selectedSpell) { $spellItem = $selectedSpell['item']; $actualCost = $selectedSpell['cost']; $enhanceLevel = $selectedSpell['level']; // 消耗魔法值 $actor->spendMana($actualCost); // 计算强化加成 $damageBonus = $enhanceLevel * 5; $type = $spellItem['spellType'] ?? 'damage_single'; $name = $spellItem['name'] ?? '未知法术'; $spellInfo = $spellItem; $spellInfo['type'] = $type; $stats = $actor->getStats(); if ($type === 'damage_single') { return $this->castDamageSingleSpell($out, $actor, null, $spellInfo, $stats, $damageBonus, $name); } elseif ($type === 'damage_aoe') { return $this->castDamageAoeSpell($out, $actor, null, $spellInfo, $stats, $damageBonus, $name); } elseif ($type === 'heal_single') { return $this->castHealSingleSpell($out, $actor, null, $spellInfo, $stats, $damageBonus, $name); } elseif ($type === 'heal_aoe') { return $this->castHealAoeSpell($out, $actor, null, $spellInfo, $stats, $damageBonus, $name); } elseif ($type === 'support') { return $this->castSupportSpell($out, $actor, null, $spellInfo, $stats, $name); } } // 2. 如果没有使用法术,执行普通攻击 // 寻找目标(使用威胁值系统) $target = $this->selectTargetWithThreat($actor); if (!$target) return true; // 无目标,回合结束 $stats = $actor->getStats(); $targetStats = $target->getStats(); // 计算伤害 $physicalDamage = max(1, $stats['patk'] - $targetStats['pdef']); $magicDamage = max(0, $stats['matk'] - $targetStats['mdef']); $baseDamage = $physicalDamage + $magicDamage; $critRate = $stats['crit']; $critDmg = $stats['critdmg']; $isCrit = rand(1, 100) <= $critRate; $actorName = ($actor instanceof Player) ? "你" : $actor->name; $targetName = ($target === $this->player) ? "你" : $target->name; $actionVerb = ($actor instanceof Player) ? "攻击" : "向 {$targetName} 发起攻击"; if ($actor instanceof Player) { $out->writeln("{$this->cyan}║{$this->reset} {$this->green}➤{$this->reset} 你攻击 {$targetName}..."); } else { $out->writeln("{$this->cyan}║{$this->reset} {$this->red}➤{$this->reset} {$actorName} {$actionVerb}..."); } if ($isCrit) { $damage = (int)($baseDamage * ($critDmg / 100)); $out->writeln("{$this->cyan}║{$this->reset} {$this->red}💥 造成 {$damage} 点暴击伤害!{$this->reset}"); } else { $damage = $baseDamage; $out->writeln("{$this->cyan}║{$this->reset} {$this->white}⚔️ 造成 {$damage} 点伤害{$this->reset}"); } // 应用防护机制:防护角色承受更多伤害 $actualDamage = (int)($damage * (1 + $target->getProtectDamageTakenBonus())); $target->hp -= $actualDamage; // 如果防护角色正在保护队友,需要显示保护效果 if ($target->isProtecting && $actualDamage > $damage) { $extraDamage = $actualDamage - $damage; $out->writeln("{$this->cyan}║{$this->reset} {$this->yellow}🛡️ 防护状态:额外承受 {$extraDamage} 伤害!{$this->reset}"); } // 蓝量恢复机制 // 攻击者恢复 15 点 $actorRecovered = $actor->recoverMana(15); // 受击者恢复 10 点 $targetRecovered = $target->recoverMana(10); // 更新同伴血量显示 if ($target instanceof Partner && isset($this->partnerHp[$target->id])) { $this->partnerHp[$target->id] = $target->hp; } if ($target->hp <= 0) { $target->hp = 0; $out->writeln("{$this->cyan}║{$this->reset} {$this->red}💀 {$targetName} 倒下了!{$this->reset}"); if (($actor instanceof Player || $actor instanceof Partner) && empty($this->getAliveEnemies())) { Screen::delay(500000); $this->showVictory($out, $stats); return true; } if ($target instanceof Player) { Screen::delay(500000); $this->showDefeat($out, $actor); return true; } } return false; } private function playerAttack($out): bool { if ($this->executeActorTurn($this->player, $out)) { return true; // 战斗结束 } Screen::delay(300000); return false; } /** * 同伴攻击 */ private function partnersAttack($out): bool { $alivePartners = $this->getAlivePartners(); foreach ($alivePartners as $partner) { if ($this->executeActorTurn($partner, $out)) { return true; // 战斗结束 } Screen::delay(300000); } return false; } /** * 怪物攻击 */ private function enemiesAttack($out): bool { foreach ($this->enemies as $enemy) { if ($enemy->hp <= 0) continue; if ($this->executeActorTurn($enemy, $out)) { return true; // 战斗结束 } Screen::delay(300000); } 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; // 掉落 - 从怪物穿着的装备随机掉落(50%概率) foreach ($enemy->getRandomEquipmentDrops(50) as $item) { $this->player->addItem($item); $allDrops[] = $item; } // 掉落 - 从怪物穿着的法术随机掉落(50%概率) foreach ($enemy->getRandomSpellDrops(50) as $spell) { $this->player->addItem($spell); $allDrops[] = $spell; } // 掉落 - 从掉落表中随机掉落物品 foreach ($enemy->dropTable as $drop) { if (rand(1, 100) <= $drop['rate']) { $this->player->addItem($drop['item']); $allDrops[] = $drop['item']; } } } // 经验 $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, ?Actor $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; } }