hanli/src/Modules/Battle.php
hant dabc1f1cd4 修复装备配置并实现自动防护状态功能
- 修复装备配置中硬编码属性值的问题
  * 5处直接指定patk/matk/pdef/mdef的装备改为使用模板
  * 现在装备会正确生成词条和动态属性
  * 装备品质与属性值完全匹配

- 实现战斗中的自动防护状态切换
  * 当自身血量健康(>50%)且队友血量低(<30%)时
  * 自动进入防护状态为队友抗伤
  * 仅在有需要时自动切换,提高战斗智能性
  * 显示战斗信息提示玩家防护状态的激活

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 22:50:06 +08:00

1158 lines
44 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

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

<?php
namespace Game\Modules;
use Game\Core\Game;
use Game\Core\Input;
use Game\Core\Screen;
use Game\Core\ItemDisplay;
use Game\Core\SpellDisplay;
use Game\Core\SpellCalculator;
use Game\Core\Colors;
use Game\Entities\Player;
use Game\Entities\Actor;
use Game\Entities\Monster;
use Game\Entities\Partner;
class Battle
{
public Player $player;
/** @var Actor[] */
public array $enemies = [];
/** @var array<string, int> 同伴当前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;
}
}