hanli/src/Modules/Battle.php
hant c065e19113 优化技能显示系统:增强战斗中的法术信息显示
新增功能:
- 创建 SpellDisplay 工具类,统一管理法术显示逻辑
- 支持 14 种计算方式的中文说明显示
- 显示品质特定的基础伤害/治疗倍数
- 增强 Battle.php 中所有法术施放方法的信息显示

改进内容:
- castDamageSingleSpell: 显示计算方式、消耗、伤害倍数
- castDamageAoeSpell: 显示计算方式、消耗
- castHealSingleSpell: 显示计算方式、消耗
- castHealAoeSpell: 显示计算方式、消耗
- castSupportSpell: 显示品质颜色和消耗

技术细节:
- ItemDisplay.php 添加 getQualityIndex() 辅助方法
- SpellDisplay.php 修复 match 表达式语法(改用 if-elseif)
- 使用质量索引访问品质相关数组
- 显示强化后的实际消耗

🧙 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-04 22:52:48 +08:00

1067 lines
42 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\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 $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->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();
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->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->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';
}
/**
* 玩家施放法术
*/
private function playerCastSpell($out): bool
{
$stats = $this->player->getStats();
// 筛选可用法术
$availableSpells = [];
foreach ($this->player->skillSlots as $slot => $spellItem) {
if ($spellItem === null) continue;
$baseCost = $spellItem['cost'] ?? 20;
$enhanceLevel = $spellItem['enhanceLevel'] ?? 0;
// 强化减少消耗: 每级 -2
$costReduction = $enhanceLevel * 2;
$actualCost = max(1, $baseCost - $costReduction);
if ($this->player->mana >= $actualCost) {
$availableSpells[] = [
'item' => $spellItem,
'cost' => $actualCost,
'level' => $enhanceLevel
];
}
}
if (empty($availableSpells)) {
// 如果没有可用的法术,改用普通攻击
$out->writeln("{$this->cyan}{$this->reset} {$this->yellow}✦ 魔法值不足,改为普通攻击{$this->reset}");
return $this->executePhysicalAttack($out);
}
// 随机选择一个可用的法术
$selected = $availableSpells[array_rand($availableSpells)];
$spellItem = $selected['item'];
$actualCost = $selected['cost'];
$enhanceLevel = $selected['level'];
// 消耗魔法值
$this->player->spendMana($actualCost);
// 计算强化加成: 每级 +5% 伤害或治疗
$damageBonus = $enhanceLevel * 5;
$type = $spellItem['spellType'] ?? 'damage_single';
$name = $spellItem['name'] ?? '未知法术';
// 构造兼容旧方法的 spellInfo
$spellInfo = $spellItem;
$spellInfo['type'] = $type; // 映射 spellType 到 type 以兼容
if ($type === 'damage_single') {
return $this->castDamageSingleSpell($out, 0, $spellInfo, $stats, $damageBonus, $name);
} elseif ($type === 'damage_aoe') {
return $this->castDamageAoeSpell($out, 0, $spellInfo, $stats, $damageBonus, $name);
} elseif ($type === 'heal_single') {
return $this->castHealSingleSpell($out, $spellInfo, $stats, $damageBonus, $name);
} elseif ($type === 'heal_aoe') {
return $this->castHealAoeSpell($out, $spellInfo, $stats, $damageBonus, $name);
} elseif ($type === 'support') {
// 兼容旧版本的 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;
// 显示法术基础信息
$calcType = $spellInfo['calc_type'] ?? 'matk';
$calcDesc = SpellDisplay::getCalcTypeDescription($calcType);
$cost = $spellInfo['cost'] ?? 20;
$actualCost = max(1, $cost - (($spellInfo['enhanceLevel'] ?? 0) * 2));
$quality = $spellInfo['quality'] ?? 'common';
$qualityColor = SpellDisplay::getQualityColor($quality);
// 计算法术伤害
$baseDamageMultiplier = $spellInfo['damage_ratio'][$this->getQualityIndex($quality)] ?? ($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;
// 显示法术施放信息
$out->writeln("{$this->cyan}{$this->reset} {$this->magenta}{$this->reset} 你施放 {$qualityColor}{$name}{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset} {$this->white}计算方式: {$calcDesc} | 消耗: {$actualCost} | 倍数: {$baseDamageMultiplier}x{$this->reset}");
if ($isCrit) {
$critDmg = $stats['critdmg'];
$damage = (int)($damage * ($critDmg / 100));
$out->writeln("{$this->cyan}{$this->reset} {$this->magenta}{$this->red}{$this->bold}暴击!{$this->reset} 造成 {$this->red}{$damage}{$this->reset} 点魔法伤害!");
} else {
$out->writeln("{$this->cyan}{$this->reset} {$this->magenta}✨ 造成 {$this->green}{$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
{
// 显示法术基础信息
$calcType = $spellInfo['calc_type'] ?? 'matk';
$calcDesc = SpellDisplay::getCalcTypeDescription($calcType);
$cost = $spellInfo['cost'] ?? 20;
$actualCost = max(1, $cost - (($spellInfo['enhanceLevel'] ?? 0) * 2));
$quality = $spellInfo['quality'] ?? 'common';
$qualityColor = SpellDisplay::getQualityColor($quality);
$out->writeln("{$this->cyan}{$this->reset} {$this->magenta}{$this->reset} 你施放 {$qualityColor}{$name}{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset} {$this->white}计算方式: {$calcDesc} | 消耗: {$actualCost}{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset} {$this->magenta}✨ 魔法在整个战场爆炸!{$this->reset}");
// 计算法术伤害
$baseDamageMultiplier = $spellInfo['damage_ratio'][$this->getQualityIndex($quality)] ?? ($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 castHealSingleSpell($out, array $spellInfo, array $stats, int $healBonus, string $name): bool
{
// 显示法术基础信息
$calcType = $spellInfo['calc_type'] ?? 'matk';
$calcDesc = SpellDisplay::getCalcTypeDescription($calcType);
$cost = $spellInfo['cost'] ?? 20;
$quality = $spellInfo['quality'] ?? 'common';
$qualityColor = SpellDisplay::getQualityColor($quality);
$actualCost = max(1, $cost - (($spellInfo['enhanceLevel'] ?? 0) * 2));
$out->writeln("{$this->cyan}{$this->reset} {$this->green}{$this->reset} 你施放 {$qualityColor}{$name}{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset} {$this->white}计算方式: {$calcDesc} | 消耗: {$actualCost}{$this->reset}");
$heal = $spellInfo['heal'] ?? 0.5;
$healBase = $spellInfo['heal_base'] ?? 20;
$healAmount = (int)($stats['matk'] * $heal + $healBase);
// 应用治疗加成
$healAmount = (int)($healAmount * (1 + $healBonus / 100));
$actualHeal = $this->player->heal($healAmount);
$out->writeln("{$this->cyan}{$this->reset} {$this->green}💚 恢复了 {$actualHeal} 点生命值{$this->reset}");
return false;
}
/**
* 施放群体治疗法术
*/
private function castHealAoeSpell($out, array $spellInfo, array $stats, int $healBonus, string $name): bool
{
// 显示法术基础信息
$calcType = $spellInfo['calc_type'] ?? 'matk';
$calcDesc = SpellDisplay::getCalcTypeDescription($calcType);
$cost = $spellInfo['cost'] ?? 20;
$quality = $spellInfo['quality'] ?? 'common';
$qualityColor = SpellDisplay::getQualityColor($quality);
$actualCost = max(1, $cost - (($spellInfo['enhanceLevel'] ?? 0) * 2));
$out->writeln("{$this->cyan}{$this->reset} {$this->green}{$this->reset} 你施放 {$qualityColor}{$name}{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset} {$this->white}计算方式: {$calcDesc} | 消耗: {$actualCost}{$this->reset}");
$heal = $spellInfo['heal'] ?? 0.5;
$healBase = $spellInfo['heal_base'] ?? 20;
$healAmount = (int)($stats['matk'] * $heal + $healBase);
// 应用治疗加成
$healAmount = (int)($healAmount * (1 + $healBonus / 100));
$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}");
}
return false;
}
/**
* 施放辅助法术
*/
private function castSupportSpell($out, array $spellInfo, array $stats, string $name): bool
{
// 显示法术基础信息
$quality = $spellInfo['quality'] ?? 'common';
$qualityColor = SpellDisplay::getQualityColor($quality);
$cost = $spellInfo['cost'] ?? 20;
$actualCost = max(1, $cost - (($spellInfo['enhanceLevel'] ?? 0) * 2));
$out->writeln("{$this->cyan}{$this->reset} {$this->cyan}{$this->reset} 你施放 {$qualityColor}{$name}{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset} {$this->white}消耗: {$actualCost}{$this->reset}");
$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 getQualityIndex(string $quality): int
{
return match($quality) {
'common' => 0,
'rare' => 1,
'epic' => 2,
'legendary' => 3,
default => 0,
};
}
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;
// 掉落 - 从怪物穿着的装备随机掉落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;
}
}