Compare commits
5 Commits
05bff41e35
...
9a5eb6433f
| Author | SHA1 | Date | |
|---|---|---|---|
| 9a5eb6433f | |||
| d2e1625c43 | |||
| 0ffd44eccf | |||
| 1cff247906 | |||
| e593d81942 |
|
|
@ -2,7 +2,9 @@
|
|||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" packagePrefix="Game\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/vendor" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
|
|
|||
|
|
@ -28,6 +28,11 @@
|
|||
<component name="PhpStanOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
<component name="PhpUnit">
|
||||
<phpunit_settings>
|
||||
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" />
|
||||
</phpunit_settings>
|
||||
</component>
|
||||
<component name="PsalmOptionsConfiguration">
|
||||
<option name="transferred" value="true" />
|
||||
</component>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"player":{"hp":110,"maxHp":50,"patk":10,"matk":5,"pdef":5,"mdef":3,"crit":5,"critdmg":130,"level":3,"exp":160,"maxExp":225,"inventory":[{"name":"雷霆锤","type":"weapon","quality":"common","level":1,"patk":8,"matk":3,"pdef":0,"mdef":0,"hp":0,"crit":0,"critdmg":0,"heal":0,"affixes":[],"desc":"Lv.1 common品质的武器","id":"692fa0f61aa45","quantity":1}],"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":91,"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,"1":2,"10":2},"partners":[{"id":"li_feiyu","name":"厉飞雨","level":2,"exp":132,"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":0}
|
||||
{"player":{"hp":270,"maxHp":0,"patk":10,"matk":5,"pdef":5,"mdef":3,"crit":5,"critdmg":130,"level":11,"exp":1337,"maxExp":5743,"inventory":[{"name":"玄铁剑","type":"weapon","quality":"common","level":1,"patk":11,"matk":0,"pdef":0,"mdef":0,"hp":0,"crit":0,"critdmg":0,"heal":0,"affixes":[],"desc":"Lv.1 common品质的武器","id":"6930554849acb","quantity":1},{"name":"金疮药","type":"consume","quality":"common","level":1,"patk":0,"matk":0,"pdef":0,"mdef":0,"hp":0,"crit":0,"critdmg":0,"heal":35,"affixes":[],"desc":"Lv.1 common品质的药剂","id":"6930578fb29f6","quantity":91},{"name":"培元丹","type":"consume","quality":"common","level":5,"patk":0,"matk":0,"pdef":0,"mdef":0,"hp":0,"crit":0,"critdmg":0,"heal":61,"affixes":[],"desc":"Lv.5 common品质的药剂","id":"693057959f5c2","quantity":1},{"name":"玄铁剑","type":"weapon","quality":"common","level":1,"patk":10,"matk":1,"pdef":0,"mdef":0,"hp":0,"crit":0,"critdmg":0,"heal":0,"affixes":[],"desc":"Lv.1 common品质的武器","id":"69305a28d318c","quantity":1},{"name":"金疮药","type":"consume","quality":"common","level":1,"patk":0,"matk":0,"pdef":0,"mdef":0,"hp":0,"crit":0,"critdmg":0,"heal":37,"affixes":[],"desc":"Lv.1 common品质的药剂","id":"69305a3d522a2","quantity":1}],"equip":{"weapon":{"name":"烈焰刀","type":"weapon","quality":"legendary","level":1,"patk":35,"matk":50,"pdef":0,"mdef":0,"hp":0,"crit":0,"critdmg":27,"heal":0,"affixes":["暴击率 +7","物攻 +15","魔攻 +8%"],"desc":"Lv.1 legendary品质的武器","id":"693041a47105c","quantity":1,"enhanceLevel":0},"armor":{"name":"皮甲","type":"armor","quality":"legendary","level":3,"patk":0,"matk":0,"pdef":26,"mdef":8,"hp":92,"crit":0,"critdmg":0,"heal":0,"affixes":["物攻 +3%","魔攻 +8%","物防 +16"],"desc":"Lv.3 legendary品质的防具","id":"693044ffc1381","quantity":1,"enhanceLevel":0},"necklace":{"name":"金链","type":"necklace","quality":"rare","level":5,"patk":0,"matk":0,"pdef":0,"mdef":0,"hp":57,"crit":0,"critdmg":0,"heal":0,"affixes":["物防 +9"],"desc":"Lv.5 rare品质的项链","id":"693046bc441b6","quantity":1,"enhanceLevel":0}},"spiritStones":3286,"npcFlags":[],"talentPoints":15,"talents":{"hp":12,"patk":0,"matk":0,"pdef":0,"mdef":0,"crit":0,"critdmg":0},"mana":100,"maxMana":100,"spells":[],"spellBooks":{"3":1,"1":40,"10":36},"partners":[{"id":"li_feiyu","name":"厉飞雨","level":13,"exp":301,"maxExp":337,"equip":{"weapon":{"name":"玄铁剑","type":"weapon","quality":"rare","level":5,"patk":15,"matk":1,"pdef":0,"mdef":0,"hp":0,"crit":0,"critdmg":0,"heal":0,"affixes":["暴击 +5"],"desc":"Lv.5 common品质的武器","id":"693055329e36e","quantity":1}},"talents":{"hp":36,"patk":3,"matk":0,"pdef":0,"mdef":0,"crit":0,"critdmg":0},"talentWeights":{"hp":1,"patk":1,"matk":1,"pdef":1,"mdef":1,"crit":1,"critdmg":1},"mana":100,"maxMana":100,"spells":[],"spellBooks":[],"hp":338,"maxHp":0,"patk":15,"matk":5,"pdef":5,"mdef":3,"crit":10,"critdmg":130}]},"dungeonId":1,"state":2}
|
||||
|
|
@ -123,7 +123,6 @@ class Game
|
|||
'level' => $partner->level,
|
||||
'exp' => $partner->exp,
|
||||
'maxExp' => $partner->maxExp,
|
||||
'baseStats' => $partner->baseStats,
|
||||
'equip' => $partner->equip,
|
||||
'talents' => $partner->talents,
|
||||
'talentWeights' => $partner->talentWeights,
|
||||
|
|
@ -131,6 +130,16 @@ class Game
|
|||
'maxMana' => $partner->maxMana,
|
||||
'spells' => $partner->spells,
|
||||
'spellBooks' => $partner->spellBooks,
|
||||
|
||||
'hp' => $partner->hp,
|
||||
'maxHp' => $partner->maxHp,
|
||||
'patk' => $partner->patk,
|
||||
'matk' => $partner->matk,
|
||||
'pdef' => $partner->pdef,
|
||||
'mdef' => $partner->mdef,
|
||||
'crit' => $partner->crit,
|
||||
'critdmg' => $partner->critdmg,
|
||||
|
||||
];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ return [
|
|||
'exp' => 100,
|
||||
'spirit_stones' => 20,
|
||||
'drops' => [
|
||||
['type' => 'weapon', 'name' => '眨眼剑法', 'quality' => 'rare', 'patk' => 15, 'rate' => 20, 'affixes' => ['crit' => 5]],
|
||||
['type' => 'weapon', 'name' => '眨眼剑法', 'quality' => 'rare', 'patk' => 15, 'rate' => 20],
|
||||
['type' => 'necklace', 'name' => '长生锁', 'rate' => 15] + $necklaceTemplate,
|
||||
['type' => 'consume', 'name' => '清灵散', 'rate' => 40, 'heal' => 80],
|
||||
],
|
||||
|
|
@ -255,7 +255,7 @@ return [
|
|||
'exp' => 300,
|
||||
'spirit_stones' => 100,
|
||||
'drops' => [
|
||||
['type' => 'weapon', 'name' => '金竺笔', 'quality' => 'epic', 'matk' => 45, 'rate' => 15, 'affixes' => ['matk' => 10]],
|
||||
['type' => 'weapon', 'name' => '金竺笔', 'quality' => 'epic', 'matk' => 45, 'rate' => 15],
|
||||
['type' => 'armor', 'name' => '墨蛟甲', 'quality' => 'epic', 'pdef' => 20, 'mdef' => 15, 'rate' => 15],
|
||||
['type' => 'consume', 'name' => '筑基丹', 'rate' => 50, 'heal' => 500],
|
||||
],
|
||||
|
|
@ -370,7 +370,7 @@ return [
|
|||
'exp' => 600,
|
||||
'spirit_stones' => 250,
|
||||
'drops' => [
|
||||
['type' => 'weapon', 'name' => '血灵钻', 'quality' => 'epic', 'matk' => 100, 'rate' => 20, 'affixes' => ['crit' => 10]],
|
||||
['type' => 'weapon', 'name' => '血灵钻', 'quality' => 'epic', 'matk' => 100, 'rate' => 20],
|
||||
['type' => 'armor', 'name' => '血灵甲', 'quality' => 'epic', 'pdef' => 40, 'mdef' => 30, 'rate' => 20],
|
||||
['type' => 'consume', 'name' => '血灵丹', 'rate' => 30, 'heal' => 1000],
|
||||
],
|
||||
|
|
@ -426,7 +426,7 @@ return [
|
|||
'exp' => 1000,
|
||||
'spirit_stones' => 400,
|
||||
'drops' => [
|
||||
['type' => 'weapon', 'name' => '青元剑', 'quality' => 'legendary', 'patk' => 100, 'matk' => 80, 'rate' => 15, 'affixes' => ['patk' => 15]],
|
||||
['type' => 'weapon', 'name' => '青元剑', 'quality' => 'legendary', 'patk' => 100, 'matk' => 80, 'rate' => 15],
|
||||
['type' => 'consume', 'name' => '虚天鼎碎片', 'rate' => 10, 'heal' => 2000], // 剧情物品作为高回复药
|
||||
['type' => 'ring', 'name' => '黑煞戒', 'rate' => 20] + $ringTemplate,
|
||||
],
|
||||
|
|
@ -541,7 +541,7 @@ return [
|
|||
'exp' => 2500,
|
||||
'spirit_stones' => 1000,
|
||||
'drops' => [
|
||||
['type' => 'weapon', 'name' => '天都尸火', 'quality' => 'legendary', 'matk' => 300, 'rate' => 15, 'affixes' => ['critdmg' => 20]],
|
||||
['type' => 'weapon', 'name' => '天都尸火', 'quality' => 'legendary', 'matk' => 300, 'rate' => 15],
|
||||
['type' => 'consume', 'name' => '补天丹', 'rate' => 10, 'heal' => 3000],
|
||||
['type' => 'necklace', 'name' => '虚天鼎', 'quality' => 'legendary', 'hp' => 2000, 'rate' => 5],
|
||||
],
|
||||
|
|
@ -597,7 +597,7 @@ return [
|
|||
'exp' => 4000,
|
||||
'spirit_stones' => 1500,
|
||||
'drops' => [
|
||||
['type' => 'weapon', 'name' => '金蛟剪', 'quality' => 'legendary', 'patk' => 300, 'matk' => 200, 'rate' => 15, 'affixes' => ['crit' => 15]],
|
||||
['type' => 'weapon', 'name' => '金蛟剪', 'quality' => 'legendary', 'patk' => 300, 'matk' => 200, 'rate' => 15],
|
||||
['type' => 'armor', 'name' => '金蛟鳞甲', 'quality' => 'legendary', 'pdef' => 180, 'mdef' => 120, 'rate' => 15],
|
||||
['type' => 'consume', 'name' => '九曲灵参', 'rate' => 10, 'heal' => 5000],
|
||||
],
|
||||
|
|
@ -712,7 +712,7 @@ return [
|
|||
'exp' => 10000,
|
||||
'spirit_stones' => 5000,
|
||||
'drops' => [
|
||||
['type' => 'weapon', 'name' => '黑风旗', 'quality' => 'legendary', 'matk' => 800, 'rate' => 15, 'affixes' => ['matk' => 20]],
|
||||
['type' => 'weapon', 'name' => '黑风旗', 'quality' => 'legendary', 'matk' => 800, 'rate' => 15],
|
||||
['type' => 'armor', 'name' => '魔龙甲', 'quality' => 'legendary', 'pdef' => 350, 'mdef' => 250, 'rate' => 15],
|
||||
['type' => 'consume', 'name' => '万年灵乳', 'rate' => 20, 'heal' => 8000],
|
||||
],
|
||||
|
|
@ -771,7 +771,7 @@ return [
|
|||
'exp' => 15000,
|
||||
'spirit_stones' => 8000,
|
||||
'drops' => [
|
||||
['type' => 'weapon', 'name' => '八灵尺', 'quality' => 'legendary', 'matk' => 1200, 'rate' => 15, 'affixes' => ['crit' => 20]],
|
||||
['type' => 'weapon', 'name' => '八灵尺', 'quality' => 'legendary', 'matk' => 1200, 'rate' => 15],
|
||||
['type' => 'ring', 'name' => '雪晶珠', 'quality' => 'legendary', 'crit' => 15, 'rate' => 15],
|
||||
['type' => 'consume', 'name' => '回阳水', 'rate' => 10, 'heal' => 10000],
|
||||
],
|
||||
|
|
@ -827,7 +827,7 @@ return [
|
|||
'exp' => 30000,
|
||||
'spirit_stones' => 15000,
|
||||
'drops' => [
|
||||
['type' => 'weapon', 'name' => '青竹蜂云剑', 'quality' => 'legendary', 'patk' => 1500, 'matk' => 1000, 'rate' => 20, 'affixes' => ['patk' => 30, 'crit' => 20]],
|
||||
['type' => 'weapon', 'name' => '青竹蜂云剑', 'quality' => 'legendary', 'patk' => 1500, 'matk' => 1000, 'rate' => 20],
|
||||
['type' => 'armor', 'name' => '五行甲', 'quality' => 'legendary', 'pdef' => 1000, 'mdef' => 1000, 'rate' => 20],
|
||||
['type' => 'consume', 'name' => '飞升令', 'rate' => 100, 'heal' => 99999], // 象征性物品
|
||||
],
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ return [
|
|||
'min_level' => 1,
|
||||
'desc' => '韩立在七玄门最好的朋友,虽无灵根但武功高强。',
|
||||
'base_stats' => [
|
||||
'hp' => 100, 'patk' => 15, 'matk' => 5, 'pdef' => 5, 'mdef' => 3, 'crit' => 10, 'critdmg' => 130, 'growth' => 1.2
|
||||
'maxHp' => 100, 'patk' => 15, 'matk' => 5, 'pdef' => 5, 'mdef' => 3, 'crit' => 10, 'critdmg' => 130
|
||||
],
|
||||
// 天赋权重:武功型,偏攻击和暴击
|
||||
'talent_weights' => [
|
||||
|
|
@ -122,7 +122,7 @@ return [
|
|||
'min_level' => 50,
|
||||
'desc' => '寄宿在虚天鼎中的妖族器灵,实为灵界银月狼族玲珑仙子分魂。',
|
||||
'base_stats' => [
|
||||
'hp' => 2000, 'patk' => 250, 'matk' => 150, 'pdef' => 100, 'mdef' => 80, 'crit' => 20, 'critdmg' => 180, 'growth' => 2.5
|
||||
'maxHp' => 2000, 'patk' => 250, 'matk' => 150, 'pdef' => 100, 'mdef' => 80, 'crit' => 20, 'critdmg' => 180
|
||||
],
|
||||
// 天赋权重:妖族器灵,偏攻击和暴伤
|
||||
'talent_weights' => [
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ namespace Game\Entities;
|
|||
|
||||
class Actor
|
||||
{
|
||||
// Common properties shared by Player, Partner, Monster
|
||||
// Common properties shared by Player, Partner, Monster, NPC
|
||||
public string $name = '';
|
||||
public int $level = 1;
|
||||
public int $exp = 0;
|
||||
|
|
@ -29,15 +29,51 @@ class Actor
|
|||
|
||||
public int $spiritStones = 0;
|
||||
|
||||
// 天赋系统(所有Actor都支持)
|
||||
public int $talentPoints = 0;
|
||||
public array $talents = [
|
||||
'hp' => 0,
|
||||
'patk' => 0,
|
||||
'matk' => 0,
|
||||
'pdef' => 0,
|
||||
'mdef' => 0,
|
||||
'crit' => 0,
|
||||
'critdmg' => 0,
|
||||
];
|
||||
|
||||
// 天赋权重(用于自动分配)
|
||||
public array $talentWeights = [
|
||||
'hp' => 1,
|
||||
'patk' => 1,
|
||||
'matk' => 1,
|
||||
'pdef' => 1,
|
||||
'mdef' => 1,
|
||||
'crit' => 1,
|
||||
'critdmg' => 1,
|
||||
];
|
||||
|
||||
// 天赋每点提供的属性
|
||||
public static array $talentBonus = [
|
||||
'hp' => 10,
|
||||
'patk' => 5,
|
||||
'matk' => 4,
|
||||
'pdef' => 3,
|
||||
'mdef' => 3,
|
||||
'crit' => 1,
|
||||
'critdmg' => 5,
|
||||
];
|
||||
|
||||
/**
|
||||
* Heal by amount, not exceeding max HP. Returns actual healed amount.
|
||||
*/
|
||||
public function heal(int $amount): int
|
||||
{
|
||||
$maxHp = $this->maxHp ?? ($this->baseHp ?? ($this->hp ?? 0));
|
||||
$old = $this->hp ?? 0;
|
||||
$this->hp = min(($this->hp ?? 0) + $amount, $maxHp);
|
||||
return $this->hp - $old;
|
||||
$stats = $this->getStats();
|
||||
$maxHp = $stats['maxHp'];
|
||||
$oldHp = $this->hp;
|
||||
$this->hp = min($this->hp + $amount, $maxHp);
|
||||
|
||||
return $this->hp - $oldHp;
|
||||
}
|
||||
|
||||
public function fullHeal(): void
|
||||
|
|
@ -74,7 +110,91 @@ class Actor
|
|||
}
|
||||
|
||||
/**
|
||||
* Unified getStats used by Player, Partner, Monster.
|
||||
* 获取天赋属性加成
|
||||
*/
|
||||
public function getTalentStats(): array
|
||||
{
|
||||
return [
|
||||
'maxHp' => $this->talents['hp'] * self::$talentBonus['hp'],
|
||||
'patk' => $this->talents['patk'] * self::$talentBonus['patk'],
|
||||
'matk' => $this->talents['matk'] * self::$talentBonus['matk'],
|
||||
'pdef' => $this->talents['pdef'] * self::$talentBonus['pdef'],
|
||||
'mdef' => $this->talents['mdef'] * self::$talentBonus['mdef'],
|
||||
'crit' => $this->talents['crit'] * self::$talentBonus['crit'],
|
||||
'critdmg' => $this->talents['critdmg'] * self::$talentBonus['critdmg'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 天赋分配
|
||||
*/
|
||||
public function allocateTalent(string $talent, int $points): bool
|
||||
{
|
||||
if ($points < 0 || $points > $this->talentPoints) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($this->talents[$talent])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->talents[$talent] += $points;
|
||||
$this->talentPoints -= $points;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动分配天赋(根据权重)
|
||||
*/
|
||||
public function autoAllocateTalents(int $points): void
|
||||
{
|
||||
if ($points <= 0 || empty($this->talentWeights)) {
|
||||
$this->talentPoints += $points;
|
||||
return;
|
||||
}
|
||||
|
||||
$totalWeight = array_sum($this->talentWeights);
|
||||
if ($totalWeight <= 0) {
|
||||
$this->talentPoints += $points;
|
||||
return;
|
||||
}
|
||||
|
||||
// 按照权重比例分配天赋点
|
||||
foreach ($this->talents as $talent => &$value) {
|
||||
if (isset($this->talentWeights[$talent])) {
|
||||
$weight = $this->talentWeights[$talent];
|
||||
$allocation = (int)($points * ($weight / $totalWeight));
|
||||
$value += $allocation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得经验并检查升级
|
||||
*/
|
||||
public function gainExp(int $amount): bool
|
||||
{
|
||||
$this->exp += $amount;
|
||||
|
||||
$leveled = false;
|
||||
while ($this->exp >= $this->maxExp) {
|
||||
$this->exp -= $this->maxExp;
|
||||
$this->level++;
|
||||
|
||||
// 分配天赋点(每级3点)
|
||||
$this->autoAllocateTalents(3);
|
||||
|
||||
// 增加下一级所需的经验
|
||||
$this->maxExp = (int)($this->maxExp * 1.2);
|
||||
|
||||
$leveled = true;
|
||||
}
|
||||
|
||||
return $leveled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unified getStats used by Player, Partner, Monster, NPC.
|
||||
* Merges base stats, equipment bonuses, affixes, and optional talent modifiers provided by subclasses.
|
||||
*/
|
||||
public function getStats(): array
|
||||
|
|
@ -82,6 +202,7 @@ class Actor
|
|||
// Base stats
|
||||
$base = [
|
||||
'maxHp' => $this->maxHp ?? ($this->baseHp ?? ($this->hp ?? 0)),
|
||||
'hp' => $this->hp,
|
||||
'patk' => $this->patk ?? 0,
|
||||
'matk' => $this->matk ?? 0,
|
||||
'pdef' => $this->pdef ?? 0,
|
||||
|
|
@ -92,12 +213,10 @@ class Actor
|
|||
|
||||
$percentBonuses = array_fill_keys(array_keys($base), 0);
|
||||
|
||||
// 1. If subclass provides talent-like stats, merge them
|
||||
if (method_exists($this, 'getTalentStats')) {
|
||||
$talent = $this->getTalentStats();
|
||||
foreach ($talent as $k => $v) {
|
||||
if (isset($base[$k])) $base[$k] += $v;
|
||||
}
|
||||
// 1. Apply talent bonuses
|
||||
$talent = $this->getTalentStats();
|
||||
foreach ($talent as $k => $v) {
|
||||
if (isset($base[$k])) $base[$k] += $v;
|
||||
}
|
||||
|
||||
// 2. Apply equipment base stats and collect affix percent bonuses
|
||||
|
|
|
|||
|
|
@ -3,16 +3,19 @@ namespace Game\Entities;
|
|||
|
||||
class Monster extends Actor
|
||||
{
|
||||
public string $name = '普通怪物';
|
||||
public int $level = 1;
|
||||
public int $baseHp = 20; // 基础HP(不含装备加成)
|
||||
public int $basePatk = 4; // 基础物理攻击(不含装备加成)
|
||||
public int $baseMatk = 2; // 基础魔法攻击(不含装备加成)
|
||||
public int $basePdef = 0; // 基础物理防御(不含装备加成)
|
||||
public int $baseMdef = 0; // 基础魔法防御(不含装备加成)
|
||||
// Monster特有的基础属性(不含装备加成)
|
||||
public int $baseHp = 20;
|
||||
public int $basePatk = 4;
|
||||
public int $baseMatk = 2;
|
||||
public int $basePdef = 0;
|
||||
public int $baseMdef = 0;
|
||||
|
||||
// Monster特有的奖励属性
|
||||
public int $expReward = 10;
|
||||
public int $spiritStoneReward = 0; // 灵石奖励
|
||||
public array $dropTable = []; // 消耗品掉落表 ['item' => [...], 'rate' => 0.5]
|
||||
public int $spiritStoneReward = 0;
|
||||
|
||||
// Monster特有的掉落表
|
||||
public array $dropTable = [];
|
||||
|
||||
public static function create(int $dungeonId): self
|
||||
{
|
||||
|
|
@ -231,6 +234,7 @@ class Monster extends Actor
|
|||
$this->critdmg += $item['critdmg'] ?? 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取怪物装备的物品列表(用于战斗胜利时掉落)
|
||||
* @return array
|
||||
|
|
@ -245,4 +249,23 @@ class Monster extends Actor
|
|||
}
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机掉落装备物品(从穿着的装备随机掉落)
|
||||
* @param int $dropRate 掉落概率(0-100)
|
||||
* @return array 掉落的物品列表
|
||||
*/
|
||||
public function getRandomEquipmentDrops(int $dropRate = 50): array
|
||||
{
|
||||
$drops = [];
|
||||
foreach ($this->equip as $item) {
|
||||
if (!empty($item)) {
|
||||
// 每件装备有独立的掉落概率
|
||||
if (rand(1, 100) <= $dropRate) {
|
||||
$drops[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $drops;
|
||||
}
|
||||
}
|
||||
111
src/Entities/NPC.php
Normal file
111
src/Entities/NPC.php
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
namespace Game\Entities;
|
||||
|
||||
/**
|
||||
* NPC Class - Represents non-player characters
|
||||
* NPCs can be treated like other players/actors in terms of stat calculation
|
||||
* They support equipment, talents, and can participate in battles like partners
|
||||
*/
|
||||
class NPC extends Actor
|
||||
{
|
||||
// NPC特有属性
|
||||
public string $id = '';
|
||||
public string $title = '';
|
||||
public string $desc = '';
|
||||
public int $minLevel = 1;
|
||||
|
||||
// NPC特有的基础属性(不含装备加成)
|
||||
public int $baseHp = 20;
|
||||
public int $basePatk = 4;
|
||||
public int $baseMatk = 2;
|
||||
public int $basePdef = 0;
|
||||
public int $baseMdef = 0;
|
||||
|
||||
// NPC特有的奖励属性
|
||||
public int $expReward = 10;
|
||||
public int $spiritStoneReward = 0;
|
||||
|
||||
// NPC特有的掉落表
|
||||
public array $dropTable = [];
|
||||
|
||||
// NPC特有的成长系数
|
||||
public float $growth = 1.1;
|
||||
|
||||
/**
|
||||
* 从配置创建NPC实例
|
||||
*/
|
||||
public static function createFromConfig(array $config): self
|
||||
{
|
||||
$npc = new self();
|
||||
|
||||
$npc->id = $config['id'] ?? '';
|
||||
$npc->name = $config['name'] ?? 'Unknown NPC';
|
||||
$npc->title = $config['title'] ?? '';
|
||||
$npc->desc = $config['desc'] ?? '';
|
||||
$npc->minLevel = $config['min_level'] ?? 1;
|
||||
|
||||
// Base stats
|
||||
if (isset($config['base_stats'])) {
|
||||
$npc->baseHp = $config['base_stats']['hp'] ?? 20;
|
||||
$npc->basePatk = $config['base_stats']['patk'] ?? 4;
|
||||
$npc->baseMatk = $config['base_stats']['matk'] ?? 2;
|
||||
$npc->basePdef = $config['base_stats']['pdef'] ?? 0;
|
||||
$npc->baseMdef = $config['base_stats']['mdef'] ?? 0;
|
||||
$npc->crit = $config['base_stats']['crit'] ?? 5;
|
||||
$npc->critdmg = $config['base_stats']['critdmg'] ?? 130;
|
||||
$npc->growth = $config['base_stats']['growth'] ?? 1.1;
|
||||
}
|
||||
|
||||
// Talent weights
|
||||
if (isset($config['talent_weights'])) {
|
||||
$npc->talentWeights = $config['talent_weights'];
|
||||
}
|
||||
|
||||
// Default level 1
|
||||
$npc->level = 1;
|
||||
$npc->hp = $npc->baseHp;
|
||||
$npc->patk = $npc->basePatk;
|
||||
$npc->matk = $npc->baseMatk;
|
||||
$npc->pdef = $npc->basePdef;
|
||||
$npc->mdef = $npc->baseMdef;
|
||||
|
||||
// Initialize mana
|
||||
$npc->maxMana = 100;
|
||||
$npc->mana = 100;
|
||||
|
||||
return $npc;
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化法力值(基于等级)
|
||||
*/
|
||||
public function initializeMana(): void
|
||||
{
|
||||
$this->maxMana = 100 + ($this->level * 10);
|
||||
$this->mana = $this->maxMana;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加已装备物品
|
||||
*/
|
||||
public function equipItem(string $type, array $item): void
|
||||
{
|
||||
if (in_array($type, ['weapon', 'armor', 'boots', 'ring', 'necklace'])) {
|
||||
$this->equip[$type] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取所有已装备物品(用于掉落,如同怪物)
|
||||
*/
|
||||
public function getEquippedItems(): array
|
||||
{
|
||||
$items = [];
|
||||
foreach ($this->equip as $item) {
|
||||
if (!empty($item)) {
|
||||
$items[] = $item;
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +1,9 @@
|
|||
<?php
|
||||
namespace Game\Entities;
|
||||
|
||||
class Partner
|
||||
class Partner extends Actor
|
||||
{
|
||||
public string $id;
|
||||
public string $name;
|
||||
public array $baseStats = [];
|
||||
public array $equip = []; // weapon, armor, ring, boots, necklace
|
||||
|
||||
// 法术系统
|
||||
public array $spells = []; // 已学习的法术ID列表
|
||||
public array $spellBooks = []; // 拥有的法术资源书
|
||||
|
||||
// 天赋系统
|
||||
public array $talents = [
|
||||
'hp' => 0,
|
||||
'patk' => 0,
|
||||
'matk' => 0,
|
||||
'pdef' => 0,
|
||||
'mdef' => 0,
|
||||
'crit' => 0,
|
||||
'critdmg' => 0,
|
||||
];
|
||||
|
||||
// 天赋权重(决定升级时自动加点的倾向)
|
||||
public array $talentWeights = [
|
||||
'hp' => 1,
|
||||
'patk' => 1,
|
||||
'matk' => 1,
|
||||
'pdef' => 1,
|
||||
'mdef' => 1,
|
||||
'crit' => 1,
|
||||
'critdmg' => 1,
|
||||
];
|
||||
|
||||
// 天赋每点提供的属性(与Player相同)
|
||||
// Partner特有的天赋加成(与 Actor 不同)
|
||||
public static array $talentBonus = [
|
||||
'hp' => 20,
|
||||
'patk' => 5,
|
||||
|
|
@ -52,126 +21,25 @@ class Partner
|
|||
$this->level = $data['level'] ?? 1;
|
||||
$this->exp = $data['exp'] ?? 0;
|
||||
$this->maxExp = $data['maxExp'] ?? (int)(100 * pow(1.5, $this->level - 1));
|
||||
$this->baseStats = $data['baseStats'] ?? [];
|
||||
|
||||
$this->patk = $data['patk'] ;
|
||||
$this->matk = $data['matk'] ;
|
||||
$this->pdef = $data['pdef'] ;
|
||||
$this->mdef = $data['mdef'] ;
|
||||
$this->crit = $data['crit'] ;
|
||||
$this->critdmg = $data['critdmg'] ?? 130;
|
||||
$this->maxHp = $data['maxHp'];
|
||||
$this->hp = $data['hp'] ?? $data['maxHp'];
|
||||
// 装备和天赋系统
|
||||
$this->equip = $data['equip'] ?? [];
|
||||
$this->talents = $data['talents'] ?? $this->talents;
|
||||
$this->talentWeights = $data['talentWeights'] ?? $this->talentWeights;
|
||||
|
||||
// 加载法术系统数据
|
||||
$this->mana = $data['mana'] ?? $this->mana;
|
||||
$this->maxMana = $data['maxMana'] ?? $this->maxMana;
|
||||
$this->maxMana = $data['maxMana'] ?? 100;
|
||||
$this->spells = $data['spells'] ?? $this->spells;
|
||||
$this->spellBooks = $data['spellBooks'] ?? $this->spellBooks;
|
||||
|
||||
// 设置当前血量为最大血量
|
||||
$stats = $this->getStats();
|
||||
$this->hp = $data['hp'] ?? $stats['maxHp'];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取天赋提供的属性加成
|
||||
*/
|
||||
public function getTalentStats(): array
|
||||
{
|
||||
return [
|
||||
'maxHp' => $this->talents['hp'] * self::$talentBonus['hp'],
|
||||
'patk' => $this->talents['patk'] * self::$talentBonus['patk'],
|
||||
'matk' => $this->talents['matk'] * self::$talentBonus['matk'],
|
||||
'pdef' => $this->talents['pdef'] * self::$talentBonus['pdef'],
|
||||
'mdef' => $this->talents['mdef'] * self::$talentBonus['mdef'],
|
||||
'crit' => $this->talents['crit'] * self::$talentBonus['crit'],
|
||||
'critdmg' => $this->talents['critdmg'] * self::$talentBonus['critdmg'],
|
||||
];
|
||||
}
|
||||
|
||||
public function getStats(): array
|
||||
{
|
||||
// Calculate base stats based on level and growth
|
||||
$growth = $this->baseStats['growth'] ?? 1.0;
|
||||
$levelFactor = $this->level - 1;
|
||||
|
||||
// 获取天赋加成
|
||||
$talentStats = $this->getTalentStats();
|
||||
|
||||
// 向后兼容:支持旧的 atk/def 键
|
||||
$basePatk = $this->baseStats['patk'] ?? $this->baseStats['atk'] ?? 10;
|
||||
$baseMatk = $this->baseStats['matk'] ?? 5;
|
||||
$basePdef = $this->baseStats['pdef'] ?? $this->baseStats['def'] ?? 5;
|
||||
$baseMdef = $this->baseStats['mdef'] ?? 3;
|
||||
|
||||
$stats = [
|
||||
'maxHp' => (int)(($this->baseStats['hp'] ?? 100) * (1 + $levelFactor * $growth * 0.1)) + $talentStats['maxHp'],
|
||||
'patk' => (int)($basePatk * (1 + $levelFactor * $growth * 0.1)) + $talentStats['patk'],
|
||||
'matk' => (int)($baseMatk * (1 + $levelFactor * $growth * 0.1)) + $talentStats['matk'],
|
||||
'pdef' => (int)($basePdef * (1 + $levelFactor * $growth * 0.1)) + $talentStats['pdef'],
|
||||
'mdef' => (int)($baseMdef * (1 + $levelFactor * $growth * 0.1)) + $talentStats['mdef'],
|
||||
'crit' => ($this->baseStats['crit'] ?? 5) + $talentStats['crit'],
|
||||
'critdmg' => ($this->baseStats['critdmg'] ?? 150) + $talentStats['critdmg'],
|
||||
];
|
||||
|
||||
$percentBonuses = [
|
||||
'maxHp' => 0, 'patk' => 0, 'matk' => 0, 'pdef' => 0, 'mdef' => 0, 'crit' => 0, 'critdmg' => 0
|
||||
];
|
||||
|
||||
// Add Equipment Stats
|
||||
foreach ($this->equip as $item) {
|
||||
if (empty($item)) continue;
|
||||
|
||||
$enhanceLevel = $item['enhanceLevel'] ?? 0;
|
||||
$enhanceMultiplier = 1 + ($enhanceLevel * 0.05);
|
||||
|
||||
$stats['maxHp'] += (int)(($item['hp'] ?? 0) * $enhanceMultiplier);
|
||||
$stats['patk'] += (int)(($item['patk'] ?? 0) * $enhanceMultiplier);
|
||||
$stats['matk'] += (int)(($item['matk'] ?? 0) * $enhanceMultiplier);
|
||||
$stats['pdef'] += (int)(($item['pdef'] ?? 0) * $enhanceMultiplier);
|
||||
$stats['mdef'] += (int)(($item['mdef'] ?? 0) * $enhanceMultiplier);
|
||||
$stats['crit'] += (int)(($item['crit'] ?? 0) * $enhanceMultiplier);
|
||||
$stats['critdmg'] += (int)(($item['critdmg'] ?? 0) * $enhanceMultiplier);
|
||||
|
||||
if (!empty($item['affixes'])) {
|
||||
foreach ($item['affixes'] as $affix) {
|
||||
$this->parseAffix($affix, $stats, $percentBonuses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply Percent Bonuses
|
||||
foreach ($stats as $key => $val) {
|
||||
if (isset($percentBonuses[$key]) && $percentBonuses[$key] > 0) {
|
||||
$stats[$key] = (int)($val * (1 + $percentBonuses[$key] / 100));
|
||||
}
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
private function parseAffix(string $affix, array &$flatStats, array &$percentStats)
|
||||
{
|
||||
if (preg_match('/(物攻|魔攻|物防|魔防|生命值|暴击率|暴击伤害)\s+\+(\d+)(%?)/', $affix, $matches)) {
|
||||
$name = $matches[1];
|
||||
$value = (int)$matches[2];
|
||||
$isPercent = $matches[3] === '%';
|
||||
|
||||
$key = match ($name) {
|
||||
'物攻' => 'patk',
|
||||
'魔攻' => 'matk',
|
||||
'物防' => 'pdef',
|
||||
'魔防' => 'mdef',
|
||||
'生命值' => 'maxHp',
|
||||
'暴击' => 'crit',
|
||||
'暴击率' => 'crit',
|
||||
'暴击伤害' => 'critdmg',
|
||||
default => null
|
||||
};
|
||||
|
||||
if ($key) {
|
||||
if ($isPercent && !in_array($key, ['crit', 'critdmg'])) {
|
||||
$percentStats[$key] += $value;
|
||||
} else {
|
||||
$flatStats[$key] += $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -217,6 +85,9 @@ class Partner
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Partner特有的升级逻辑,自动分配天赋点
|
||||
*/
|
||||
public function gainExp(int $amount): bool
|
||||
{
|
||||
$this->exp += $amount;
|
||||
|
|
@ -225,7 +96,7 @@ class Partner
|
|||
$this->exp -= $this->maxExp;
|
||||
$this->maxExp = (int)($this->maxExp * 1.5);
|
||||
|
||||
// 升级时自动分配3点天赋(根据权重)
|
||||
// 升级时自动分配3点天赋(根据权重,且 HP 至少加1点)
|
||||
$this->autoAllocateTalent(3);
|
||||
|
||||
return true;
|
||||
|
|
@ -241,54 +112,6 @@ class Partner
|
|||
return array_sum($this->talents);
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复队友血量
|
||||
*/
|
||||
public function heal(int $amount): int
|
||||
{
|
||||
$stats = $this->getStats();
|
||||
$maxHp = $stats['maxHp'];
|
||||
|
||||
$oldHp = $this->hp;
|
||||
$this->hp = min($this->hp + $amount, $maxHp);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查是否已学习法术
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -6,30 +6,7 @@ use Game\Entities\Partner;
|
|||
class Player extends Actor
|
||||
{
|
||||
|
||||
// 天赋系统
|
||||
public int $talentPoints = 0; // 可用天赋点
|
||||
public array $talents = [ // 已分配的天赋点
|
||||
'hp' => 0, // 每点 +20 生命
|
||||
'patk' => 0, // 每点 +5 物攻
|
||||
'matk' => 0, // 每点 +4 魔攻
|
||||
'pdef' => 0, // 每点 +3 物防
|
||||
'mdef' => 0, // 每点 +3 魔防
|
||||
'crit' => 0, // 每点 +2% 暴击
|
||||
'critdmg' => 0, // 每点 +10% 暴伤
|
||||
];
|
||||
|
||||
// 天赋每点提供的属性
|
||||
public static array $talentBonus = [
|
||||
'hp' => 10,
|
||||
'patk' => 5,
|
||||
'matk' => 4,
|
||||
'pdef' => 3,
|
||||
'mdef' => 3,
|
||||
'crit' => 1,
|
||||
'critdmg' => 5,
|
||||
];
|
||||
|
||||
// 天赋名称
|
||||
// Player特有的天赋名称
|
||||
public static array $talentNames = [
|
||||
'hp' => '生命',
|
||||
'patk' => '物攻',
|
||||
|
|
@ -40,6 +17,13 @@ class Player extends Actor
|
|||
'critdmg' => '暴伤',
|
||||
];
|
||||
|
||||
// Player特有的NPC交互标记
|
||||
public array $npcFlags = [];
|
||||
|
||||
// Player特有的同伴系统
|
||||
public int $maxPartners = 2; // 最多可携带同伴数
|
||||
public array $partners = []; // 已招募的同伴
|
||||
|
||||
/**
|
||||
* 增加灵石
|
||||
*/
|
||||
|
|
@ -60,62 +44,6 @@ class Player extends Actor
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复生命值,不超过上限
|
||||
* @param int $amount 恢复量
|
||||
* @return int 实际恢复量
|
||||
*/
|
||||
public function heal(int $amount): int
|
||||
{
|
||||
$stats = $this->getStats();
|
||||
$maxHp = $stats['maxHp'];
|
||||
|
||||
$oldHp = $this->hp;
|
||||
$this->hp = min($this->hp + $amount, $maxHp);
|
||||
|
||||
return $this->hp - $oldHp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 完全恢复生命值
|
||||
*/
|
||||
public function fullHeal(): void
|
||||
{
|
||||
$stats = $this->getStats();
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 学习法术
|
||||
|
|
@ -188,6 +116,9 @@ class Player extends Actor
|
|||
return $this->spellBooks[$spellId] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Player特有的经验获取,升级时会恢复生命值
|
||||
*/
|
||||
public function gainExp(int $amount): bool
|
||||
{
|
||||
$this->exp += $amount;
|
||||
|
|
@ -196,8 +127,8 @@ class Player extends Actor
|
|||
$this->exp -= $this->maxExp;
|
||||
$this->maxExp = (int)($this->maxExp * 1.5);
|
||||
|
||||
// 升级获得天赋点(每级3点)
|
||||
$this->talentPoints += 3;
|
||||
// 升级获得天赋点(每级3点)并通过 autoAllocateTalents 自动分配
|
||||
$this->autoAllocateTalents(3);
|
||||
|
||||
// 升级时恢复全部生命值
|
||||
$this->fullHeal();
|
||||
|
|
@ -208,7 +139,7 @@ class Player extends Actor
|
|||
}
|
||||
|
||||
/**
|
||||
* 分配天赋点
|
||||
* Player特有的分配天赋点(返回布尔值)
|
||||
*/
|
||||
public function allocateTalent(string $talent, int $points = 1): bool
|
||||
{
|
||||
|
|
@ -226,7 +157,7 @@ class Player extends Actor
|
|||
}
|
||||
|
||||
/**
|
||||
* 重置天赋(返还所有点数)
|
||||
* Player特有的重置天赋(返还所有点数)
|
||||
*/
|
||||
public function resetTalents(): int
|
||||
{
|
||||
|
|
@ -240,23 +171,6 @@ class Player extends Actor
|
|||
return $total;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取天赋提供的属性加成
|
||||
*/
|
||||
public function getTalentStats(): array
|
||||
{
|
||||
$stats = [
|
||||
'maxHp' => $this->talents['hp'] * self::$talentBonus['hp'],
|
||||
'patk' => $this->talents['patk'] * self::$talentBonus['patk'],
|
||||
'matk' => $this->talents['matk'] * self::$talentBonus['matk'],
|
||||
'pdef' => $this->talents['pdef'] * self::$talentBonus['pdef'],
|
||||
'mdef' => $this->talents['mdef'] * self::$talentBonus['mdef'],
|
||||
'crit' => $this->talents['crit'] * self::$talentBonus['crit'],
|
||||
'critdmg' => $this->talents['critdmg'] * self::$talentBonus['critdmg'],
|
||||
];
|
||||
return $stats;
|
||||
}
|
||||
|
||||
public function addItem(array $item)
|
||||
{
|
||||
// Handle stacking for consumables
|
||||
|
|
@ -283,20 +197,6 @@ class Player extends Actor
|
|||
$this->inventory[] = $item;
|
||||
}
|
||||
|
||||
/** @var array<int, array> */
|
||||
public array $inventory = [];
|
||||
|
||||
public array $equip = [];
|
||||
|
||||
/** @var array<string, bool> */
|
||||
public array $npcFlags = [];
|
||||
|
||||
/** @var array<string, Partner> 同伴列表 */
|
||||
public array $partners = [];
|
||||
|
||||
/** @var int 最大同伴数量 */
|
||||
public int $maxPartners = 2;
|
||||
|
||||
/**
|
||||
* 招募同伴
|
||||
*/
|
||||
|
|
@ -328,99 +228,4 @@ class Player extends Actor
|
|||
{
|
||||
return $this->partners[$partnerId] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate total stats including base, talents, equipment, and affixes.
|
||||
* @return array ['maxHp' => int, 'patk' => int, 'matk' => int, 'pdef' => int, 'mdef' => int, 'crit' => int, 'critdmg' => float]
|
||||
*/
|
||||
public function getStats(): array
|
||||
{
|
||||
// 获取天赋加成
|
||||
$talentStats = $this->getTalentStats();
|
||||
|
||||
$stats = [
|
||||
'maxHp' => $this->maxHp + $talentStats['maxHp'],
|
||||
'patk' => $this->patk + $talentStats['patk'],
|
||||
'matk' => $this->matk + $talentStats['matk'],
|
||||
'pdef' => $this->pdef + $talentStats['pdef'],
|
||||
'mdef' => $this->mdef + $talentStats['mdef'],
|
||||
'crit' => $this->crit + $talentStats['crit'],
|
||||
'critdmg' => $this->critdmg + $talentStats['critdmg'],
|
||||
];
|
||||
|
||||
$percentBonuses = [
|
||||
'maxHp' => 0,
|
||||
'patk' => 0,
|
||||
'matk' => 0,
|
||||
'pdef' => 0,
|
||||
'mdef' => 0,
|
||||
'crit' => 0,
|
||||
'critdmg' => 0,
|
||||
];
|
||||
|
||||
// 1. Add Equipment Base Stats & Parse Affixes
|
||||
foreach ($this->equip as $item) {
|
||||
if (empty($item)) continue;
|
||||
|
||||
// 计算强化加成(每级+5%)
|
||||
$enhanceLevel = $item['enhanceLevel'] ?? 0;
|
||||
$enhanceMultiplier = 1 + ($enhanceLevel * 0.05);
|
||||
|
||||
// Base Stats (主属性) - 应用强化加成
|
||||
$stats['maxHp'] += (int)(($item['hp'] ?? 0) * $enhanceMultiplier);
|
||||
$stats['patk'] += (int)(($item['patk'] ?? 0) * $enhanceMultiplier);
|
||||
$stats['matk'] += (int)(($item['matk'] ?? 0) * $enhanceMultiplier);
|
||||
$stats['pdef'] += (int)(($item['pdef'] ?? 0) * $enhanceMultiplier);
|
||||
$stats['mdef'] += (int)(($item['mdef'] ?? 0) * $enhanceMultiplier);
|
||||
$stats['crit'] += (int)(($item['crit'] ?? 0) * $enhanceMultiplier);
|
||||
$stats['critdmg'] += (int)(($item['critdmg'] ?? 0) * $enhanceMultiplier);
|
||||
|
||||
// Affixes (副属性)
|
||||
if (!empty($item['affixes'])) {
|
||||
foreach ($item['affixes'] as $affix) {
|
||||
$this->parseAffix($affix, $stats, $percentBonuses);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Apply Percentage Bonuses
|
||||
foreach ($stats as $key => $val) {
|
||||
if (isset($percentBonuses[$key]) && $percentBonuses[$key] > 0) {
|
||||
$stats[$key] = (int)($val * (1 + $percentBonuses[$key] / 100));
|
||||
}
|
||||
}
|
||||
|
||||
return $stats;
|
||||
}
|
||||
|
||||
private function parseAffix(string $affix, array &$flatStats, array &$percentStats)
|
||||
{
|
||||
// Example: "物攻 +5% (T1)" or "生命值 +20 (T1)"
|
||||
if (preg_match('/(物攻|魔攻|物防|魔防|生命值|暴击率|暴击伤害)\s+\+(\d+)(%?)/', $affix, $matches)) {
|
||||
$name = $matches[1];
|
||||
$value = (int)$matches[2];
|
||||
$isPercent = $matches[3] === '%';
|
||||
|
||||
$key = match ($name) {
|
||||
'物攻' => 'patk',
|
||||
'魔攻' => 'matk',
|
||||
'物防' => 'pdef',
|
||||
'魔防' => 'mdef',
|
||||
'生命值' => 'maxHp',
|
||||
'暴击' => 'crit',
|
||||
'暴击率' => 'crit',
|
||||
'暴击伤害' => 'critdmg',
|
||||
default => null
|
||||
};
|
||||
|
||||
if ($key) {
|
||||
// Crit and CritDmg are always treated as flat additions (even if they have %)
|
||||
if ($isPercent && !in_array($key, ['crit', 'critdmg'])) {
|
||||
$percentStats[$key] += $value;
|
||||
} else {
|
||||
$flatStats[$key] += $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -868,8 +868,8 @@ class Battle
|
|||
$totalExp += $enemy->expReward;
|
||||
$totalStones += $enemy->spiritStoneReward;
|
||||
|
||||
// 掉落
|
||||
foreach ($enemy->getEquippedItems() as $item) {
|
||||
// 掉落 - 从怪物穿着的装备随机掉落
|
||||
foreach ($enemy->getRandomEquipmentDrops(50) as $item) {
|
||||
$this->player->addItem($item);
|
||||
$allDrops[] = $item;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
<?php
|
||||
|
||||
namespace Game\Modules;
|
||||
|
||||
use Game\Core\Game;
|
||||
|
|
@ -6,6 +7,7 @@ use Game\Core\Screen;
|
|||
use Game\Core\Input;
|
||||
use Game\Entities\Item;
|
||||
use Game\Entities\Partner;
|
||||
use Game\Entities\NPC;
|
||||
|
||||
class NpcPanel
|
||||
{
|
||||
|
|
@ -21,7 +23,7 @@ class NpcPanel
|
|||
while (true) {
|
||||
Screen::clear($this->game->output);
|
||||
$this->game->output->writeln("========== 拜访故人 ==========");
|
||||
|
||||
|
||||
// Filter NPCs based on level
|
||||
$availableNpcs = [];
|
||||
foreach ($this->npcs as $npc) {
|
||||
|
|
@ -34,7 +36,7 @@ class NpcPanel
|
|||
$this->game->output->writeln("目前没有故人可以拜访。");
|
||||
} else {
|
||||
foreach ($availableNpcs as $index => $npc) {
|
||||
$idx = $index+1;
|
||||
$idx = $index + 1;
|
||||
$this->game->output->writeln("[{$idx}] {$npc['name']} <{$npc['title']}>");
|
||||
}
|
||||
}
|
||||
|
|
@ -49,8 +51,8 @@ class NpcPanel
|
|||
return;
|
||||
}
|
||||
|
||||
if (isset($availableNpcs[$choice-1])) {
|
||||
$this->interact($availableNpcs[$choice-1]);
|
||||
if (isset($availableNpcs[$choice - 1])) {
|
||||
$this->interact($availableNpcs[$choice - 1]);
|
||||
} else {
|
||||
$this->game->output->writeln("无效选择");
|
||||
Screen::sleep(1);
|
||||
|
|
@ -58,6 +60,27 @@ class NpcPanel
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从配置创建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) {
|
||||
|
|
@ -65,12 +88,12 @@ class NpcPanel
|
|||
$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) {
|
||||
$label = match ($key) {
|
||||
'talk' => '交谈',
|
||||
'gift' => '领取赠礼',
|
||||
'trade' => '交易',
|
||||
|
|
@ -93,9 +116,9 @@ class NpcPanel
|
|||
$choice = Input::ask($this->game->output, "请选择: ");
|
||||
|
||||
if ($choice == 0) return;
|
||||
|
||||
if (isset($actionKeys[$choice-1])) {
|
||||
$actionType = $actionKeys[$choice-1];
|
||||
|
||||
if (isset($actionKeys[$choice - 1])) {
|
||||
$actionType = $actionKeys[$choice - 1];
|
||||
$actionData = $actions[$actionType];
|
||||
$this->handleAction($actionType, $actionData, $npc);
|
||||
Screen::pause($this->game->output);
|
||||
|
|
@ -124,14 +147,14 @@ class NpcPanel
|
|||
$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']}: \"东西已经给你了,别太贪心。\"");
|
||||
|
|
@ -146,7 +169,7 @@ class NpcPanel
|
|||
$this->game->player->npcFlags[$flag] = true;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
case 'trade':
|
||||
$this->game->output->writeln("{$npc['name']}: \"{$data['text']}\"");
|
||||
$this->trade($data['items']);
|
||||
|
|
@ -165,7 +188,7 @@ class NpcPanel
|
|||
$this->game->output->writeln("[0] 取消");
|
||||
|
||||
$choice = Input::ask($this->game->output, "购买: ");
|
||||
if (isset($items[$choice-1])) {
|
||||
if (isset($items[$choice - 1])) {
|
||||
$itemInfo = $items[$choice];
|
||||
if ($this->game->player->spiritStones >= $itemInfo['price']) {
|
||||
$this->game->player->spiritStones -= $itemInfo['price'];
|
||||
|
|
@ -225,10 +248,9 @@ class NpcPanel
|
|||
$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("生命: {$stats['maxHp']} 物攻: {$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) {
|
||||
|
|
@ -255,9 +277,7 @@ class NpcPanel
|
|||
'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
|
||||
],
|
||||
...$npc['base_stats']
|
||||
]);
|
||||
|
||||
$player->recruitPartner($partner);
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ class PartnerPanel
|
|||
$this->game->output->writeln("经验: {$this->cyan}{$partner->exp}/{$partner->maxExp}{$this->reset}");
|
||||
$this->game->output->writeln("");
|
||||
$this->game->output->writeln("{$this->white}--- 属性 ---{$this->reset}");
|
||||
$this->game->output->writeln("生命值: {$this->green}{$stats['maxHp']}{$this->reset}");
|
||||
$this->game->output->writeln("生命值: {$this->red}{$stats['hp']}{$this->reset}/{$stats['maxHp']}");
|
||||
$this->game->output->writeln("物理攻击: {$this->red}{$stats['patk']}{$this->reset}");
|
||||
$this->game->output->writeln("魔法攻击: {$this->cyan}{$stats['matk']}{$this->reset}");
|
||||
$this->game->output->writeln("物理防御: {$this->red}{$stats['pdef']}{$this->reset}");
|
||||
|
|
|
|||
43
test/Test.php
Normal file
43
test/Test.php
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
require __DIR__ . '/../vendor/autoload.php';
|
||||
|
||||
$play = new \Game\Entities\Partner(json_decode(' {
|
||||
"id": "li_feiyu",
|
||||
"name": "厉飞雨",
|
||||
"level": 10,
|
||||
"exp": 0,
|
||||
"maxExp": 100,
|
||||
"equip": [],
|
||||
"talents": {
|
||||
"hp": 0,
|
||||
"patk": 0,
|
||||
"matk": 0,
|
||||
"pdef": 0,
|
||||
"mdef": 0,
|
||||
"crit": 0,
|
||||
"critdmg": 0
|
||||
},
|
||||
"talentWeights": {
|
||||
"hp": 1,
|
||||
"patk": 1,
|
||||
"matk": 1,
|
||||
"pdef": 1,
|
||||
"mdef": 1,
|
||||
"crit": 1,
|
||||
"critdmg": 1
|
||||
},
|
||||
"mana": 0,
|
||||
"maxMana": 100,
|
||||
"spells": [],
|
||||
"spellBooks": [],
|
||||
"hp": 100,
|
||||
"maxHp": 100,
|
||||
"patk": 15,
|
||||
"matk": 5,
|
||||
"pdef": 5,
|
||||
"mdef": 3,
|
||||
"crit": 10,
|
||||
"critdmg": 130
|
||||
}',true));
|
||||
|
||||
var_dump($play->getStats());
|
||||
Loading…
Reference in New Issue
Block a user