hanli/src/Modules/NpcPanel.php
hant e593d81942 重构:将天赋系统统一移到 Actor 基类
- 将天赋系统 (talents, talentWeights, talentBonus, getTalentStats) 集中在 Actor 基类
- 添加 allocateTalent(), resetTalents(), autoAllocateTalents(), gainExp() 到 Actor
- Monster 保留特有的基础属性、奖励和掉落表
- NPC 保留特有的标识和配置相关属性
- Player 保留特有的名称映射、NPC 标记、同伴系统和升级治疗逻辑
- 删除所有重复代码,提高代码复用性和可维护性

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 21:57:59 +08:00

292 lines
10 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\Screen;
use Game\Core\Input;
use Game\Entities\Item;
use Game\Entities\Partner;
use Game\Entities\NPC;
class NpcPanel
{
private array $npcs;
public function __construct(public Game $game)
{
$this->npcs = require __DIR__ . '/../../src/Data/npcs.php';
}
public function show()
{
while (true) {
Screen::clear($this->game->output);
$this->game->output->writeln("========== 拜访故人 ==========");
// Filter NPCs based on level
$availableNpcs = [];
foreach ($this->npcs as $npc) {
if ($this->game->player->level >= $npc['min_level']) {
$availableNpcs[] = $npc;
}
}
if (empty($availableNpcs)) {
$this->game->output->writeln("目前没有故人可以拜访。");
} else {
foreach ($availableNpcs as $index => $npc) {
$idx = $index+1;
$this->game->output->writeln("[{$idx}] {$npc['name']} <{$npc['title']}>");
}
}
$this->game->output->writeln("==============================");
$this->game->output->writeln("[0] 返回");
$choice = Input::ask($this->game->output, "请选择故人: ");
if ($choice == 0) {
$this->game->state = Game::MENU;
return;
}
if (isset($availableNpcs[$choice-1])) {
$this->interact($availableNpcs[$choice-1]);
} else {
$this->game->output->writeln("无效选择");
Screen::sleep(1);
}
}
}
/**
* 从配置创建NPC实例
*/
public function createNPCInstance(array $npcConfig): NPC
{
return NPC::createFromConfig($npcConfig);
}
/**
* 获取NPC实例从配置ID
*/
public function getNPCById(string $id): ?NPC
{
foreach ($this->npcs as $npcConfig) {
if ($npcConfig['id'] === $id) {
return $this->createNPCInstance($npcConfig);
}
}
return null;
}
private function interact(array $npc)
{
while (true) {
Screen::clear($this->game->output);
$this->game->output->writeln("========== {$npc['name']} ==========");
$this->game->output->writeln($npc['desc']);
$this->game->output->writeln("------------------------------");
$actions = $npc['actions'] ?? [];
$actionKeys = array_keys($actions);
foreach ($actionKeys as $idx => $key) {
$label = match($key) {
'talk' => '交谈',
'gift' => '领取赠礼',
'trade' => '交易',
'heal' => '疗伤',
'buff' => '强化',
'recruit' => '邀请入队',
default => ucfirst($key)
};
// 如果已招募,显示不同文本
if ($key === 'recruit' && isset($this->game->player->partners[$npc['id']])) {
$label = '已入队 ✓';
}
$idx_no = $idx + 1;
$this->game->output->writeln("[{$idx_no}] {$label}");
}
$this->game->output->writeln("[0] 离开");
$choice = Input::ask($this->game->output, "请选择: ");
if ($choice == 0) return;
if (isset($actionKeys[$choice-1])) {
$actionType = $actionKeys[$choice-1];
$actionData = $actions[$actionType];
$this->handleAction($actionType, $actionData, $npc);
Screen::pause($this->game->output);
}
}
}
private function handleAction(string $type, array $data, array $npc)
{
switch ($type) {
case 'talk':
$this->game->output->writeln("{$npc['name']}: \"{$data['text']}\"");
break;
case 'recruit':
$this->handleRecruit($data, $npc);
break;
case 'heal':
$this->game->output->writeln("{$npc['name']}: \"{$data['text']}\"");
$healAmount = $this->game->player->maxHp - $this->game->player->hp;
if ($healAmount > 0) {
$this->game->player->hp = $this->game->player->maxHp;
$this->game->output->writeln("你的生命值已恢复满!");
} else {
$this->game->output->writeln("你状态很好,无需治疗。");
}
break;
case 'gift':
// Check if already received (need storage for this state, maybe in player save data?)
// For simplicity, let's just give it for now, or check inventory?
// Real implementation should save received gifts in player data.
// Let's assume we can give it repeatedly for now or add a simple check if I can modify Player class.
// I'll modify Player class to store 'npc_flags'.
$flag = "gift_{$npc['id']}";
if (isset($this->game->player->npcFlags[$flag])) {
$this->game->output->writeln("{$npc['name']}: \"东西已经给你了,别太贪心。\"");
return;
}
$this->game->output->writeln("{$npc['name']}: \"{$data['text']}\"");
if (isset($data['item_spec'])) {
$item = Item::createFromSpec($data['item_spec'], $this->game->player->level);
$this->game->player->addItem($item);
$this->game->output->writeln("获得了 [{$item['name']}]");
$this->game->player->npcFlags[$flag] = true;
}
break;
case 'trade':
$this->game->output->writeln("{$npc['name']}: \"{$data['text']}\"");
$this->trade($data['items']);
break;
}
}
private function trade(array $items)
{
// Simple trade implementation
$index = 1;
foreach ($items as $idx => $itemInfo) {
$index++;
$this->game->output->writeln("[{$index}] {$itemInfo['name']} - {$itemInfo['price']} 灵石");
}
$this->game->output->writeln("[0] 取消");
$choice = Input::ask($this->game->output, "购买: ");
if (isset($items[$choice-1])) {
$itemInfo = $items[$choice];
if ($this->game->player->spiritStones >= $itemInfo['price']) {
$this->game->player->spiritStones -= $itemInfo['price'];
// Create item. Since trade items in config are simple, we might need randomItem or createFromSpec
// For now assuming simple items, use randomItem logic or createFromSpec if detailed
// The config has 'type' and 'name'.
$spec = ['type' => $itemInfo['type'], 'name' => $itemInfo['name']];
$item = Item::createFromSpec($spec, $this->game->player->level);
$this->game->player->addItem($item);
$this->game->output->writeln("购买了 [{$item['name']}]");
} else {
$this->game->output->writeln("灵石不足!");
}
}
}
/**
* 处理招募同伴
*/
private function handleRecruit(array $data, array $npc)
{
$player = $this->game->player;
// 检查是否已招募
if (isset($player->partners[$npc['id']])) {
$this->game->output->writeln("{$npc['name']}: \"我已经在队伍中了!\"");
return;
}
// 检查是否有base_stats可招募的NPC必须有
if (!isset($npc['base_stats'])) {
$this->game->output->writeln("{$npc['name']}: \"我还有其他事要忙,不能与你同行。\"");
return;
}
// 检查队伍是否已满
if (count($player->partners) >= $player->maxPartners) {
$this->game->output->writeln("队伍已满!最多只能携带 {$player->maxPartners} 名同伴。");
return;
}
// 检查招募费用
$cost = $data['cost'] ?? 0;
if ($cost > 0 && $player->spiritStones < $cost) {
$this->game->output->writeln("灵石不足!需要 {$cost} 灵石。");
return;
}
// 显示招募对话
$this->game->output->writeln("{$npc['name']}: \"{$data['text']}\"");
$this->game->output->writeln("");
// 显示同伴属性预览
$stats = $npc['base_stats'];
$this->game->output->writeln("========== 同伴属性 ==========");
$patk = $stats['patk'] ?? $stats['atk'] ?? 10;
$matk = $stats['matk'] ?? 5;
$pdef = $stats['pdef'] ?? $stats['def'] ?? 5;
$mdef = $stats['mdef'] ?? 3;
$this->game->output->writeln("生命: {$stats['hp']} 物攻: {$patk} 魔攻: {$matk}");
$this->game->output->writeln("物防: {$pdef} 魔防: {$mdef}");
$this->game->output->writeln("暴击: {$stats['crit']}% 暴伤: {$stats['critdmg']}%");
$this->game->output->writeln("成长: {$stats['growth']}x");
$this->game->output->writeln("==============================");
if ($cost > 0) {
$this->game->output->writeln("招募费用: {$cost} 灵石");
}
$this->game->output->writeln("");
$confirm = Input::ask($this->game->output, "确认招募?[y/n]: ");
if (strtolower($confirm) !== 'y') {
$this->game->output->writeln("取消招募。");
return;
}
// 扣除费用
if ($cost > 0) {
$player->spendSpiritStones($cost);
}
// 创建同伴并添加到队伍
$partner = new Partner([
'id' => $npc['id'],
'name' => $npc['name'],
'level' => 1,
'exp' => 0,
'baseStats' => $npc['base_stats'],
'equip' => [],
'talentWeights' => $npc['talent_weights'] ?? [
'hp' => 1, 'patk' => 1, 'matk' => 1, 'pdef' => 1, 'mdef' => 1, 'crit' => 1, 'critdmg' => 1
],
]);
$player->recruitPartner($partner);
$this->game->saveState();
$this->game->output->writeln("");
$this->game->output->writeln("{$npc['name']} 加入了队伍!");
}
}