This commit is contained in:
hantao 2025-12-12 18:23:42 +08:00
parent 81402717b5
commit 3dbe8cbd73
15 changed files with 438 additions and 185 deletions

View File

@ -12,9 +12,8 @@ if (file_exists(__DIR__ . '/../vendor/autoload.php')) {
require 'phar://hanli-idle.phar/vendor/autoload.php';
}
use Game\Console\ConsoleOutput;
use Game\Core\Input;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Output\ConsoleOutput;
use Game\Core\Game;
// 设置终端为 UTF-8

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,22 @@
<?php
namespace Game\Console;
use Game\Core\Colors;
use Symfony\Component\Console\Formatter\OutputFormatterInterface;
use Symfony\Component\Console\Output\ConsoleOutput as Out;
use Symfony\Component\Console\Output\OutputInterface;
class ConsoleOutput extends Out
{
public function __construct(int $verbosity = OutputInterface::VERBOSITY_NORMAL, ?bool $decorated = null, ?OutputFormatterInterface $formatter = null)
{
Out::__construct($verbosity, $decorated, $formatter);
}
public function writeln(string|iterable $messages, int $options = self::OUTPUT_NORMAL)
{
$messages.=Colors::RESET;
parent::writeln($messages,$options);
}
}

154
src/Core/UserManager.php Normal file
View File

@ -0,0 +1,154 @@
<?php
namespace Game\Core;
/**
* 用户管理器 - 简单的文件存储用户系统
*/
class UserManager
{
private string $usersFile;
private array $users = [];
public function __construct()
{
$this->usersFile = __DIR__ . '/../../data/users.json';
// 确保目录存在
$dir = dirname($this->usersFile);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
$this->loadUsers();
}
private function loadUsers(): void
{
if (file_exists($this->usersFile)) {
$data = json_decode(file_get_contents($this->usersFile), true);
if (is_array($data)) {
$this->users = $data;
}
}
}
private function saveUsers(): void
{
file_put_contents($this->usersFile, json_encode($this->users, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT));
}
/**
* 注册新用户
* @return array ['success' => bool, 'message' => string, 'userId' => string|null]
*/
public function register(string $username, string $password): array
{
// 验证用户名
$username = trim($username);
if (strlen($username) < 2 || strlen($username) > 20) {
return ['success' => false, 'message' => '用户名长度需要在2-20个字符之间', 'userId' => null];
}
if (!preg_match('/^[a-zA-Z0-9_\x{4e00}-\x{9fa5}]+$/u', $username)) {
return ['success' => false, 'message' => '用户名只能包含字母、数字、下划线和中文', 'userId' => null];
}
// 检查用户名是否已存在
if ($this->userExists($username)) {
return ['success' => false, 'message' => '用户名已存在', 'userId' => null];
}
// 验证密码
if (strlen($password) < 4) {
return ['success' => false, 'message' => '密码长度至少4个字符', 'userId' => null];
}
// 生成用户ID
$userId = $this->generateUserId($username);
// 保存用户
$this->users[$userId] = [
'id' => $userId,
'username' => $username,
'password' => password_hash($password, PASSWORD_DEFAULT),
'created_at' => date('Y-m-d H:i:s'),
'last_login' => null,
];
$this->saveUsers();
return ['success' => true, 'message' => '注册成功', 'userId' => $userId];
}
/**
* 用户登录
* @return array ['success' => bool, 'message' => string, 'userId' => string|null]
*/
public function login(string $username, string $password): array
{
$username = trim($username);
// 查找用户
$user = $this->findUserByUsername($username);
if (!$user) {
return ['success' => false, 'message' => '用户不存在', 'userId' => null];
}
// 验证密码
if (!password_verify($password, $user['password'])) {
return ['success' => false, 'message' => '密码错误', 'userId' => null];
}
// 更新最后登录时间
$this->users[$user['id']]['last_login'] = date('Y-m-d H:i:s');
$this->saveUsers();
return ['success' => true, 'message' => '登录成功', 'userId' => $user['id']];
}
/**
* 检查用户名是否存在
*/
public function userExists(string $username): bool
{
return $this->findUserByUsername($username) !== null;
}
/**
* 根据用户名查找用户
*/
private function findUserByUsername(string $username): ?array
{
foreach ($this->users as $user) {
if (strcasecmp($user['username'], $username) === 0) {
return $user;
}
}
return null;
}
/**
* 生成用户ID
*/
private function generateUserId(string $username): string
{
// 使用用户名+时间戳的哈希
return substr(md5($username . time() . rand(1000, 9999)), 0, 16);
}
/**
* 获取用户信息
*/
public function getUser(string $userId): ?array
{
return $this->users[$userId] ?? null;
}
/**
* 获取所有用户数量
*/
public function getUserCount(): int
{
return count($this->users);
}
}

View File

@ -454,8 +454,8 @@
"hp": 1,
"patk": 0,
"matk": 0,
"pdef": 999,
"mdef": 999,
"pdef": 100,
"mdef": 100,
"crit": 0,
"critdmg": 100,
"exp": 400,

View File

@ -18,8 +18,8 @@ class Actor
public int $crit = 0;
public float $critdmg = 110.0;
public int $mana = 60;
public int $maxMana = 60;
public int $mana = 100;
public int $maxMana = 100;
// 防护系统
public bool $isProtecting = false; // 是否处于防护状态

View File

@ -3,6 +3,7 @@ namespace Game\Entities;
use Game\Modules\Bag\Consume;
use Game\Modules\Bag\Equipment;
use Game\Modules\Bag\Quest;
use Game\Modules\Bag\Spell;
class Monster extends Actor
@ -84,6 +85,14 @@ class Monster extends Actor
$monsterConfig_allow [] = $item;
}
}
if (empty($monsterConfig_allow)){
if ($dungeonId < 2){
$monsterConfig_allow = $monsterConfig[0];
}else{
$monsterConfig_allow = $monsterConfig;
}
}
// dd($monsterConfig_allow,$dungeonId);
$monsterConfig = $monsterConfig_allow;
$levels = array_column($monsterConfig,'level');
$total = 0;
@ -98,19 +107,18 @@ class Monster extends Actor
$groupSize = rand(2, 4);
}
$group = [];
// Create each enemy independently using weighted random selection
for ($i = 1; $i < $groupSize; $i++) {
for ($i = 1; $i <= $groupSize; $i++) {
$totalWeight = 0;
foreach ($monsterConfig as $m) {
$totalWeight += $m['weight'] ?? 100;
$totalWeight += $m['weight'] ?? 10;
}
$rand = rand(1, $totalWeight);
$selectedConfig = null;
foreach ($monsterConfig as $m) {
$rand -= $m['weight'] ?? 100;
$rand -= $m['weight'] ?? 10;
if ($rand <= 0) {
$selectedConfig = $m;
break;
@ -185,6 +193,9 @@ class Monster extends Actor
}
}
$this->dropTable[] = Consume::createItem($dungeonId,$config['level']);
if ($this->is_boss){
$this->dropTable[] = Quest::createItem($dungeonId);
}
// 为怪物配置法术
$this->generateSpells($config);

View File

@ -25,7 +25,7 @@ class Player extends Actor
public array $partners = []; // 已招募的同伴
// Player特有的小绿瓶回复池
public int $potionPool = 1000; // 小绿瓶初始回复量
public int $potionPool = 10000; // 小绿瓶初始回复量
/**
* 增加灵石

View File

@ -31,7 +31,7 @@ abstract class Item
return Colors::GREEN.'小绿瓶+'.$item['heal'].Colors::RESET;
} else if ($item['type'] == 'spell') {
return Spell::getLineShow($item);
} elseif ($item['type'] == 'quest') {
} elseif ($item['type'] == 'quest_item') {
return Quest::getLineShow($item);
} elseif ($item['type'] == 'consume') {
return Consume::getLineShow($item);
@ -44,7 +44,7 @@ abstract class Item
{
if ($item['type'] == 'spell') {
return Spell::calculateSellPrice($item);
} elseif ($item['type'] == 'quest') {
} elseif ($item['type'] == 'quest_item') {
return Quest::calculateSellPrice($item);
} elseif ($item['type'] == 'consume') {
return Consume::calculateSellPrice($item);

View File

@ -2,6 +2,8 @@
namespace Game\Modules\Bag;
use Game\Core\Colors;
/**
* Simple representation of an equipment/consumable item.
*/
@ -25,6 +27,23 @@ class Quest extends Item
];
}
public static function createItem($dungeonId)
{
$data = file_get_contents(__DIR__.'/../../Data/quest.json');
$data = json_decode($data,true);
$name = $data[$dungeonId];
return [
'id' => uniqid('quest_'),
'type' => 'quest_item',
'rate' => 20,
'name' => $name,
'quality' => 'legendary',
'level' => 1,
'quantity' => 1,
'desc' => '关键任务物品,用于解锁新区域',
];
}
public function toArray(): array
{
return [
@ -38,7 +57,7 @@ class Quest extends Item
public static function getLineShow($item): string
{
// TODO: Implement getLineShow() method.
return Colors::YELLOW.$item['name'].Colors::RESET;
}
public static function getDetailShow($item): array

View File

@ -1,4 +1,5 @@
<?php
namespace Game\Modules;
use Game\Core\Game;
@ -56,9 +57,9 @@ class Battle
$this->blue = Colors::BLUE;
$this->qualityColors = [
'common' => Colors::WHITE,
'rare' => Colors::BLUE,
'epic' => Colors::MAGENTA,
'common' => Colors::WHITE,
'rare' => Colors::BLUE,
'epic' => Colors::MAGENTA,
'legendary' => Colors::YELLOW,
];
}
@ -131,7 +132,7 @@ class Battle
Screen::delay(500000);
// 创建敌人群组
$this->enemies = Monster::createGroup($this->game->dungeonId,$this->player->level);
$this->enemies = Monster::createGroup($this->game->dungeonId, $this->player->level);
$this->totalMaxHp = 0;
foreach ($this->enemies as $enemy) {
@ -174,7 +175,6 @@ class Battle
// 怪物攻击
if ($this->enemiesAttack($out)) {
Screen::delay(1000000, $out);
$this->syncPartnerHp();
return;
}
@ -182,7 +182,6 @@ class Battle
} else {
// 怪物先攻
if ($this->enemiesAttack($out)) {
Screen::delay(1000000, $out);
$this->syncPartnerHp();
return;
}
@ -203,8 +202,6 @@ class Battle
Screen::delay(800000, $out);
if ($result) break;
}
Screen::delay(500000, $out);
// 同步队友HP到Partner对象然后保存状态
$this->syncPartnerHp();
$this->game->saveState();
@ -212,37 +209,17 @@ class Battle
}
}
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, $out); // 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}");
$out->writeln("{$this->bold}{$this->round} 回合{$this->reset} {$this->white}[q] 逃跑{$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}");
$out->writeln(" {$this->red}💀{$this->reset} {$this->white}{$enemy->name}{$this->reset} {$this->red}[已击败]{$this->reset}");
continue;
}
$enemyStats = $enemy->getStats();
@ -251,25 +228,25 @@ class Battle
$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->red}👹{$this->reset} {$this->bold}{$enemy->name}{$this->reset} Lv.{$enemy->level}");
$out->writeln("{$enemyHpBar} {$this->white}{$enemyHpText}{$this->reset}");
$out->writeln("{$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}");
$out->writeln("");
// VS 分隔
$out->writeln("{$this->cyan}{$this->reset} {$this->yellow}⚔️ VS ⚔️{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset}");
$out->writeln(" {$this->yellow}⚔️ VS ⚔️{$this->reset}");
$out->writeln("");
// 玩家信息
$playerHpPercent = $this->player->hp / $stats['maxHp'];
$playerHpBar = $this->renderHpBar($playerHpPercent, 20);
$playerHpText = $this->player->hp . "/" . $stats['maxHp'];
$out->writeln("{$this->cyan}{$this->reset} {$this->green}🧙{$this->reset} {$this->bold}玩家{$this->reset} Lv.{$this->player->level}");
$out->writeln("{$this->cyan}{$this->reset} {$playerHpBar} {$this->white}{$playerHpText}{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset} {$this->blue}🔮 {$stats['mana']}/{$stats['maxMana']}{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset} {$this->yellow}⚔️{$this->reset} {$stats['patk']}/{$stats['matk']} {$this->green}🛡️{$this->reset} {$stats['pdef']}/{$stats['mdef']} {$this->red}💥{$this->reset} {$stats['crit']}/{$stats['critdmg']}%");
$out->writeln("{$this->green}🧙{$this->reset} {$this->bold}玩家{$this->reset} Lv.{$this->player->level}");
$out->writeln("{$playerHpBar} {$this->white}{$playerHpText}{$this->reset}");
$out->writeln("{$this->blue}🔮 {$stats['mana']}/{$stats['maxMana']}{$this->reset}");
$out->writeln("{$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) {
@ -281,20 +258,18 @@ class Battle
$partnerHpText = $partnerHp . "/" . $partnerMaxHp;
$status = $partnerHp > 0 ? "" : " {$this->red}[倒下]{$this->reset}";
$out->writeln("{$this->cyan}{$this->reset} {$this->magenta}👤{$this->reset} {$partner->name} Lv.{$partner->level}{$status}");
$out->writeln("{$this->cyan}{$this->reset} {$partnerHpBar} {$this->white}{$partnerHpText}{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset} {$this->blue}🔮 {$partnerStats['mana']}/{$partnerStats['maxMana']}{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset} {$this->yellow}⚔️{$this->reset} {$partnerStats['patk']}/{$partnerStats['matk']} {$this->green}🛡️{$this->reset} {$partnerStats['pdef']}/{$partnerStats['mdef']} {$this->red}💥{$this->reset} {$partnerStats['crit']}/{$partnerStats['critdmg']}%");
$out->writeln("{$this->magenta}👤{$this->reset} {$partner->name} Lv.{$partner->level}{$status}");
$out->writeln("{$partnerHpBar} {$this->white}{$partnerHpText}{$this->reset}");
$out->writeln("{$this->blue}🔮 {$partnerStats['mana']}/{$partnerStats['maxMana']}{$this->reset}");
$out->writeln("{$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}");
$out->writeln(" {$this->white}战斗日志:{$this->reset}");
}
private function renderHpBar(float $percent, int $width): string
{
$filled = max((int)($percent * $width),0);
$empty = max($width - $filled,0);
$filled = max((int)($percent * $width), 0);
$empty = max($width - $filled, 0);
// 根据血量百分比选择颜色
if ($percent > 0.6) {
@ -341,7 +316,7 @@ class Battle
break;
}
}
if (!$hasSpells || $this->player->mana < 15) {
return 'attack';
}
@ -407,7 +382,7 @@ class Battle
$threat += $target->getThreatBonus();
// 血量比例也会影响威胁值(血量越多越有威胁)
$threat += (int)($stats['hp'] / $stats['maxHp'] * 10);
$threat -= (int)($stats['hp'] / $stats['maxHp'] * 10);
return $threat;
}
@ -453,9 +428,9 @@ class Battle
// 1. 分析战场状态
$opponents = $this->getOpponents($actor);
$allies = $this->getAllies($actor);
$enemyCount = count($opponents);
$lowHpAllies = [];
foreach ($allies as $ally) {
$status = $ally->getStats();
@ -485,9 +460,9 @@ class Battle
'cost' => $actualCost,
'level' => $spellItem['enhanceLevel'] ?? 0
];
$type = $spellItem['spellType'] ?? 'damage_single';
if (isset($availableSpells[$type])) {
$availableSpells[$type][] = $spellData;
}
@ -529,7 +504,7 @@ class Battle
$selected = $availableSpells['all'][array_rand($availableSpells['all'])];
}
if ($selected && in_array($selected['item']['spellType'],['heal_aoe', 'heal_single']) && $lowHpCount == 0){
if ($selected && in_array($selected['item']['spellType'], ['heal_aoe', 'heal_single']) && $lowHpCount == 0) {
return [];
}
@ -563,12 +538,12 @@ class Battle
$casterName = ($caster instanceof Player) ? "" : $caster->name;
$actionVerb = ($caster instanceof Player) ? "施放" : "施放了";
$out->writeln("{$this->cyan}{$this->reset} {$this->magenta}{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}");
$out->writeln("{$this->magenta}{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}");
if ($isCrit) {
$out->writeln("{$this->cyan}{$this->reset} {$this->magenta}{$this->red}{$this->bold}暴击!{$this->reset}{$target->name} 造成 {$this->red}{$damage}{$this->reset} 点魔法伤害!");
$out->writeln("{$this->magenta}{$this->red}{$this->bold}暴击!{$this->reset}{$target->name} 造成 {$this->red}{$damage}{$this->reset} 点魔法伤害!");
} else {
$out->writeln("{$this->cyan}{$this->reset} {$this->magenta}✨ 对 {$target->name} 造成 {$this->green}{$damage}{$this->reset} 点魔法伤害");
$out->writeln("{$this->magenta}✨ 对 {$target->name} 造成 {$this->green}{$damage}{$this->reset} 点魔法伤害");
}
// 应用防护机制:防护角色承受更多伤害
@ -579,12 +554,12 @@ class Battle
// // 如果防护角色正在保护队友,需要显示保护效果
// if ($target->isProtecting && $actualDamage > $damage) {
// $extraDamage = $actualDamage - $damage;
// $out->writeln("{$this->cyan}║{$this->reset} {$this->yellow}🛡️ 防护状态:额外承受 {$extraDamage} 伤害!{$this->reset}");
// $out->writeln(" {$this->yellow}🛡️ 防护状态:额外承受 {$extraDamage} 伤害!{$this->reset}");
// }
if ($target->hp <= 0) {
$target->hp = 0;
$out->writeln("{$this->cyan}{$this->reset} {$this->red}💀 {$target->name} 被击败了!{$this->reset}");
$out->writeln("{$this->red}💀 {$target->name} 被击败了!{$this->reset}");
// 如果是玩家击败了所有敌人
if (($caster instanceof Player || $caster instanceof Partner) && empty($this->getAliveEnemies())) {
@ -616,9 +591,9 @@ class Battle
$casterName = ($caster instanceof Player) ? "" : $caster->name;
$actionVerb = ($caster instanceof Player) ? "施放" : "施放了";
$out->writeln("{$this->cyan}{$this->reset} {$this->magenta}{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}");
$out->writeln("{$this->magenta}{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset} {$this->magenta}✨ 魔法在整个战场爆炸!{$this->reset}");
$out->writeln("{$this->magenta}✨ 魔法在整个战场爆炸!{$this->reset}");
$opponents = $this->getOpponents($caster);
$allOpponentsDefeated = true;
@ -629,30 +604,30 @@ class Battle
$damageResult = SpellCalculator::calculateDamage($spellInfo, $stats, $enemy->getStats(), $damageBonus, true);
$damage = $damageResult['damage'];
$out->writeln("{$this->cyan}{$this->reset} {$enemy->name} 受到 {$damage} 点伤害");
$out->writeln("{$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}");
$out->writeln("{$this->red}💀 {$enemy->name} 被击败了!{$this->reset}");
if ($enemy instanceof Player) {
Screen::delay(500000, $out);
$this->showDefeat($out, $caster);
return true;
Screen::delay(500000, $out);
$this->showDefeat($out, $caster);
return true;
}
} else {
$allOpponentsDefeated = false;
}
}
// 更新同伴血量显示 (如果敌人是同伴)
if (!($caster instanceof Player) && !($caster instanceof Partner)) {
foreach ($this->player->partners as $partner) {
if (isset($this->partnerHp[$partner->id])) {
$this->partnerHp[$partner->id] = $partner->hp;
}
}
foreach ($this->player->partners as $partner) {
if (isset($this->partnerHp[$partner->id])) {
$this->partnerHp[$partner->id] = $partner->hp;
}
}
}
if ($allOpponentsDefeated && ($caster instanceof Player || $caster instanceof Partner) && empty($this->getAliveEnemies())) {
@ -674,7 +649,7 @@ class Battle
$allies = $this->getAllies($caster);
$lowestHpRatio = 1.0;
foreach ($allies as $ally) {
$status = $ally->getStats();
$status = $ally->getStats();
$ratio = $status['hp'] / $status['maxHp'];
if ($ratio < $lowestHpRatio) {
$lowestHpRatio = $ratio;
@ -692,20 +667,20 @@ class Battle
$casterName = ($caster instanceof Player) ? "" : $caster->name;
$actionVerb = ($caster instanceof Player) ? "施放" : "施放了";
$out->writeln("{$this->cyan}{$this->reset} {$this->green}{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}");
$out->writeln("{$this->green}{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}");
$healAmount = SpellCalculator::calculateHeal($spellInfo, $stats, $healBonus);
$actualHeal = $target->heal($healAmount);
// 更新同伴血量显示
if ($target instanceof Partner && isset($this->partnerHp[$target->id])) {
$this->partnerHp[$target->id] = $target->hp;
}
$targetName = ($target === $this->player) ? "" : $target->name;
$out->writeln("{$this->cyan}{$this->reset} {$this->green}💚 {$targetName} 恢复了 {$actualHeal} 点生命值{$this->reset}");
$out->writeln("{$this->green}💚 {$targetName} 恢复了 {$actualHeal} 点生命值{$this->reset}");
return false;
}
@ -722,28 +697,28 @@ class Battle
$casterName = ($caster instanceof Player) ? "" : $caster->name;
$actionVerb = ($caster instanceof Player) ? "施放" : "施放了";
$out->writeln("{$this->cyan}{$this->reset} {$this->green}{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}");
$out->writeln("{$this->green}{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}");
$healAmount = SpellCalculator::calculateHeal($spellInfo, $stats, $healBonus);
$allies = $this->getAllies($caster);
foreach ($allies as $ally) {
if ($ally->hp <= 0) continue;
// 如果是施法者本人全额治疗如果是队友80%效果
$finalHeal = ($ally === $caster) ? $healAmount : (int)($healAmount * 0.9);
$actualHeal = $ally->heal($finalHeal);
// 更新同伴血量显示
if ($ally instanceof Partner && isset($this->partnerHp[$ally->id])) {
$this->partnerHp[$ally->id] = $ally->hp;
}
$allyName = ($ally === $this->player) ? "" : $ally->name;
$out->writeln("{$this->cyan}{$this->reset} {$this->green}💚 {$allyName} 恢复了 {$actualHeal} 点生命值{$this->reset}");
$out->writeln("{$this->green}💚 {$allyName} 恢复了 {$actualHeal} 点生命值{$this->reset}");
}
return false;
@ -764,7 +739,7 @@ class Battle
$casterName = ($caster instanceof Player) ? "" : $caster->name;
$actionVerb = ($caster instanceof Player) ? "施放" : "施放了";
$out->writeln("{$this->cyan}{$this->reset} {$this->cyan}{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset} {$casterName} {$actionVerb} {$qualityColor}{$name}{$this->reset}");
$subtype = $spellInfo['subtype'] ?? '';
@ -778,7 +753,7 @@ class Battle
}
} elseif ($subtype === 'defend') {
// 简单的防御提升逻辑 (目前只是显示实际效果需在伤害计算中支持buff)
$out->writeln("{$this->cyan}{$this->reset} {$this->cyan}🛡️ 防御力提升!{$this->reset}");
$out->writeln("{$this->cyan}🛡️ 防御力提升!{$this->reset}");
}
return false;
@ -848,12 +823,12 @@ class Battle
$protectingAlly->exitProtectMode();
$protectingName = ($protectingAlly instanceof Player) ? "" : $protectingAlly->name;
$out->writeln("{$this->cyan}{$this->reset} {$this->yellow}🛡️{$this->reset} {$protectingName} 退出防护状态。{$this->reset}");
$out->writeln(" {$this->yellow}🛡️{$this->reset} {$protectingName} 退出防护状态。{$this->reset}");
$actor->enterProtectMode();
$actorName = ($actor instanceof Player) ? "" : $actor->name;
$out->writeln("{$this->cyan}{$this->reset} {$this->yellow}🛡️{$this->reset} {$actorName} 主动进入防护状态,为队友抗伤!{$this->reset}");
$out->writeln("{$this->yellow}🛡️{$this->reset} {$actorName} 主动进入防护状态,为队友抗伤!{$this->reset}");
}
return;
}
@ -862,7 +837,7 @@ class Battle
$actor->enterProtectMode();
$actorName = ($actor instanceof Player) ? "" : $actor->name;
$out->writeln("{$this->cyan}{$this->reset} {$this->yellow}🛡️{$this->reset} {$actorName} 主动进入防护状态,为队友抗伤!{$this->reset}");
$out->writeln("{$this->yellow}🛡️{$this->reset} {$actorName} 主动进入防护状态,为队友抗伤!{$this->reset}");
}
}
@ -871,7 +846,7 @@ class Battle
*/
private function executeActorTurn(Actor $actor, $out): bool
{
if ($actor->hp <= 0){
if ($actor->hp <= 0) {
return false;
}
@ -896,7 +871,7 @@ class Battle
$spellInfo = $spellItem;
$spellInfo['type'] = $type;
$stats = $actor->getStats();
if ($type === 'damage_single') {
@ -920,7 +895,7 @@ class Battle
$stats = $actor->getStats();
$targetStats = $target->getStats();
// 计算伤害
$physicalDamage = max(1, $stats['patk'] - $targetStats['pdef']);
// $magicDamage = max(0, $stats['matk'] - $targetStats['mdef']);
@ -933,32 +908,25 @@ class Battle
$actorName = ($actor instanceof Player) ? "" : $actor->name;
$targetName = ($target === $this->player) ? "" : $target->name;
$actionVerb = ($actor instanceof Player) ? "攻击" : "{$targetName} 发起攻击";
if ($actor instanceof Player) {
$out->writeln("{$this->cyan}{$this->reset} {$this->green}{$this->reset} 你攻击 {$targetName}...");
$out->writeln("{$this->green}{$this->reset} 你攻击 {$targetName}...");
} else {
$out->writeln("{$this->cyan}{$this->reset} {$this->red}{$this->reset} {$actorName} {$actionVerb}...");
$out->writeln("{$this->red}{$this->reset} {$actorName} {$actionVerb}...");
}
if ($isCrit) {
$damage = (int)($baseDamage * ($critDmg / 100));
$out->writeln("{$this->cyan}{$this->reset} {$this->red}💥 造成 {$damage} 点暴击伤害!{$this->reset}");
$out->writeln("{$this->red}💥 造成 {$damage} 点暴击伤害!{$this->reset}");
} else {
$damage = $baseDamage;
$out->writeln("{$this->cyan}{$this->reset} {$this->white}⚔️ 造成 {$damage} 点伤害{$this->reset}");
$out->writeln("{$this->white}⚔️ 造成 {$damage} 点伤害{$this->reset}");
}
// 应用防护机制:防护角色承受更多伤害
$actualDamage = (int)($damage * (1 + $target->getProtectDamageTakenBonus()));
$target->hp -= $damage;
// 如果防护角色正在保护队友,需要显示保护效果
// if ($target->isProtecting && $actualDamage > $damage) {
// $extraDamage = $actualDamage - $damage;
// $out->writeln("{$this->cyan}║{$this->reset} {$this->yellow}🛡️ 防护状态:额外承受 {$extraDamage} 伤害!{$this->reset}");
// }
// 蓝量恢复机制
// 攻击者恢复 15 点
$actorRecovered = $actor->recoverMana(15);
@ -972,14 +940,14 @@ class Battle
if ($target->hp <= 0) {
$target->hp = 0;
$out->writeln("{$this->cyan}{$this->reset} {$this->red}💀 {$targetName} 倒下了!{$this->reset}");
$out->writeln("{$this->red}💀 {$targetName} 倒下了!{$this->reset}");
if (($actor instanceof Player || $actor instanceof Partner) && empty($this->getAliveEnemies())) {
Screen::delay(500000, $out);
$this->showVictory($out, $stats);
return true;
}
if ($target instanceof Player) {
Screen::delay(500000, $out);
$this->showDefeat($out, $actor);
@ -1037,17 +1005,11 @@ class Battle
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']}");
$out->writeln("击败: {$this->white}" . implode(', ', array_unique($enemyNames)) . "{$this->reset}");
$out->writeln("血量: {$this->green}{$this->player->hp}{$this->reset}/{$stats['maxHp']}");
// 汇总经验和灵石
$totalExp = 0;
@ -1083,6 +1045,9 @@ class Battle
'heal' => $item['heal'],
'quantity' => 1
];
}else{
unset($item['rate']);
$this->player->addItem($item);
}
}
}
@ -1093,7 +1058,7 @@ class Battle
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}");
$out->writeln("经验: {$this->cyan}+{$totalExp}{$this->reset}{$levelUpMsg}");
// 同伴经验
$alivePartners = $this->getAlivePartners();
@ -1104,20 +1069,20 @@ class Battle
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}");
$out->writeln("{$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}");
$out->writeln("灵石: {$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}");
$out->writeln("魔法: {$this->magenta}+{$actualManaRecover}{$this->reset}");
// 恢复队友魔法值
$alivePartners = $this->getAlivePartners();
@ -1128,15 +1093,11 @@ class Battle
}
if (!empty($allDrops)) {
$out->writeln("{$this->yellow}{$this->reset} {$this->white}掉落:{$this->reset}");
$out->writeln("{$this->white}掉落:{$this->reset}");
foreach ($allDrops as $item) {
$out->writeln(Item::show($item));
}
}
$out->writeln("{$this->yellow}╚══════════════════════════════════════════╝{$this->reset}");
$out->writeln("");
$this->game->saveState();
Screen::delay(1500000, $out);
}
@ -1145,19 +1106,8 @@ class Battle
{
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("");
$out->writeln("你被 {$this->white}{$killerName}{$this->reset} 击败了...");
$out->writeln("{$this->white}不要气馁,休整后再战!{$this->reset}");
$this->game->state = Game::MENU;
Screen::pause($out);
}

View File

@ -97,12 +97,7 @@ class InventoryPanel
foreach ($pageItems as $i => $item) {
$index = $start + $i + 1;
// 装备使用 ItemDisplay 显示
if ($item['type'] == 'quest_item'){
}else{
$displayStr = Item::show($item);
}
$displayStr = Item::show($item);
$out->writeln("[{$index}] {$displayStr}");
}

View File

@ -1,6 +1,7 @@
<?php
namespace Game\Modules;
use Game\Core\Colors;
use Game\Core\Screen;
use Game\Core\Input;
use Game\Core\Game;
@ -14,7 +15,7 @@ class Menu
Screen::clear($this->game->output);
$out = $this->game->output;
$out->writeln("=========================");
$out->writeln(Colors::RESET."=========================");
$out->writeln("[1] 开始战斗");
$out->writeln("[2] 属性面板");
$out->writeln("[3] 背包");

View File

@ -6,8 +6,10 @@ use Game\Modules\Bag\Spell;
require __DIR__ . '/../vendor/autoload.php';
$res = \Game\Entities\Monster::create(1);
dd($res);
$data = file_get_contents('/Users/hant/MyData/study/idle/src/Data/map.json');
$data = json_decode($data,true);
$data = array_column($data,'key_item');
file_put_contents('/Users/hant/MyData/study/idle/src/Data/quest.json',json_encode($data,JSON_UNESCAPED_UNICODE));

View File

@ -4,9 +4,21 @@
* 使用方法: php -S 0.0.0.0:8080 web/server.php
*/
// 设置错误报告
error_reporting(E_ALL);
ini_set('display_errors', 0);
// 自动加载
require_once __DIR__ . '/../vendor/autoload.php';
use Game\Core\UserManager;
// 启动会话
session_start();
// 设置响应头
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
@ -26,29 +38,117 @@ $path = parse_url($requestUri, PHP_URL_PATH);
// 静态文件处理
if ($path === '/' || $path === '/process.html') {
header('Content-Type: text/html; charset=utf-8');
echo '<!DOCTYPE html>
<html>
<head>
<title>凡人修仙传 - Web Terminal Edition</title>
<style>
body { font-family: monospace; background: #000; color: #0f0; margin: 0; padding: 20px; }
#game { height: 600px; overflow-y: auto; border: 1px solid #0f0; padding: 10px; margin-bottom: 10px; }
input { background: #000; color: #0f0; border: 1px solid #0f0; padding: 5px; width: 100%; box-sizing: border-box; }
</style>
</head>
<body>
<div id="game">游戏初始化中...</div>
<input type="text" id="input" placeholder="输入命令..." autofocus>
<script>
console.log("Web game interface loaded");
</script>
</body>
</html>';
readfile(__DIR__ . '/process.html');
exit;
}
// API 路由
$response = ['success' => true, 'message' => 'Web server is running. This is a CLI-based game.'];
$response = ['success' => false, 'message' => '未知请求'];
try {
switch ($path) {
case '/api/register':
$response = handleRegister();
break;
case '/api/login':
$response = handleLogin();
break;
case '/api/logout':
$response = handleLogout();
break;
case '/api/status':
$response = handleStatus();
break;
default:
// 检查是否是静态文件
$filePath = __DIR__ . $path;
if (file_exists($filePath) && is_file($filePath)) {
$ext = pathinfo($path, PATHINFO_EXTENSION);
$mimeTypes = [
'js' => 'application/javascript',
'css' => 'text/css',
'html' => 'text/html',
'png' => 'image/png',
'jpg' => 'image/jpeg',
'gif' => 'image/gif',
];
header('Content-Type: ' . ($mimeTypes[$ext] ?? 'application/octet-stream'));
readfile($filePath);
exit;
}
http_response_code(404);
$response = ['success' => false, 'message' => '未找到'];
}
} catch (Exception $e) {
http_response_code(500);
$response = ['success' => false, 'message' => '服务器错误: ' . $e->getMessage()];
}
http_response_code(200);
echo json_encode($response, JSON_UNESCAPED_UNICODE);
// ============ 处理函数 ============
function getInput(): array
{
$input = file_get_contents('php://input');
return json_decode($input, true) ?? [];
}
function handleRegister(): array
{
$data = getInput();
$username = $data['username'] ?? '';
$password = $data['password'] ?? '';
$userManager = new UserManager();
$result = $userManager->register($username, $password);
if ($result['success']) {
$_SESSION['user_id'] = $result['userId'];
$_SESSION['username'] = $username;
}
return $result;
}
function handleLogin(): array
{
$data = getInput();
$username = $data['username'] ?? '';
$password = $data['password'] ?? '';
$userManager = new UserManager();
$result = $userManager->login($username, $password);
if ($result['success']) {
$_SESSION['user_id'] = $result['userId'];
$_SESSION['username'] = $username;
}
return $result;
}
function handleLogout(): array
{
session_destroy();
return ['success' => true, 'message' => '已退出登录'];
}
function handleStatus(): array
{
if (empty($_SESSION['user_id'])) {
return ['success' => false, 'loggedIn' => false, 'message' => '未登录'];
}
return [
'success' => true,
'loggedIn' => true,
'userId' => $_SESSION['user_id'],
'username' => $_SESSION['username'] ?? '未知',
];
}