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