添加怪物群

This commit is contained in:
hantao 2025-12-02 13:45:20 +08:00
parent 0c6cbd669a
commit b4ec385827
12 changed files with 757 additions and 674 deletions

File diff suppressed because one or more lines are too long

View File

@ -97,7 +97,7 @@ class Game
} }
$this->dungeonId = $data['dungeonId'] ?? $this->dungeonId; $this->dungeonId = $data['dungeonId'] ?? $this->dungeonId;
$this->state = $data['state'] ?? $this->state; $this->state = self::MENU;
} }
} }
} }

View File

@ -34,8 +34,10 @@ class ItemDisplay
// 属性名称 // 属性名称
private static array $statNames = [ private static array $statNames = [
'atk' => '攻击', 'patk' => '物攻',
'def' => '防御', 'matk' => '魔攻',
'pdef' => '物防',
'mdef' => '魔防',
'hp' => '生命', 'hp' => '生命',
'crit' => '暴击', 'crit' => '暴击',
'critdmg' => '暴伤', 'critdmg' => '暴伤',
@ -296,7 +298,7 @@ class ItemDisplay
// 简要属性 // 简要属性
$stats = []; $stats = [];
foreach (['atk', 'def', 'hp'] as $key) { foreach (['patk', 'matk', 'pdef', 'mdef', 'hp'] as $key) {
$value = $item[$key] ?? 0; $value = $item[$key] ?? 0;
if ($value > 0) { if ($value > 0) {
$statName = self::$statNames[$key] ?? $key; $statName = self::$statNames[$key] ?? $key;

View File

@ -8,8 +8,10 @@
return [ return [
// 词条名称定义 // 词条名称定义
'affix_definitions' => [ 'affix_definitions' => [
'atk' => '攻击', 'patk' => '物攻',
'def' => '防御', 'matk' => '魔攻',
'pdef' => '物防',
'mdef' => '魔防',
'hp' => '生命值', 'hp' => '生命值',
'crit' => '暴击率', 'crit' => '暴击率',
'critdmg' => '暴击伤害', 'critdmg' => '暴击伤害',
@ -20,11 +22,52 @@ return [
'weapon' => [ 'weapon' => [
'names' => ['铁剑', '玄铁剑', '青钢剑', '寒冰剑', '烈焰刀', '雷霆锤'], 'names' => ['铁剑', '玄铁剑', '青钢剑', '寒冰剑', '烈焰刀', '雷霆锤'],
'fixed_primary' => [ 'fixed_primary' => [
'atk' => ['base' => [5, 12, 22, 38], 'growth' => 1.8], 'patk' => ['base' => [4, 10, 18, 30], 'growth' => 1.5],
'matk' => ['base' => [2, 5, 10, 18], 'growth' => 0.8],
],
// 特定物品的属性偏向配置 (覆盖默认 fixed_primary)
'specific_config' => [
'铁剑' => [
'fixed_primary' => [
'patk' => ['base' => [6, 14, 24, 40], 'growth' => 1.8], // 高物攻
'matk' => ['base' => [0, 0, 0, 0], 'growth' => 0.0], // 无魔攻
]
],
'玄铁剑' => [
'fixed_primary' => [
'patk' => ['base' => [8, 18, 30, 50], 'growth' => 2.0], // 更高物攻
'matk' => ['base' => [0, 0, 0, 0], 'growth' => 0.0],
]
],
'青钢剑' => [
'fixed_primary' => [
'patk' => ['base' => [5, 12, 22, 35], 'growth' => 1.6],
'matk' => ['base' => [1, 3, 6, 10], 'growth' => 0.5],
]
],
'寒冰剑' => [
'fixed_primary' => [
'patk' => ['base' => [2, 5, 10, 15], 'growth' => 0.8], // 低物攻
'matk' => ['base' => [6, 15, 28, 45], 'growth' => 2.0], // 高魔攻
]
],
'烈焰刀' => [
'fixed_primary' => [
'patk' => ['base' => [4, 10, 18, 30], 'growth' => 1.5], // 均衡
'matk' => ['base' => [4, 10, 18, 30], 'growth' => 1.5],
]
],
'雷霆锤' => [
'fixed_primary' => [
'patk' => ['base' => [7, 16, 28, 45], 'growth' => 1.9],
'matk' => ['base' => [2, 5, 10, 18], 'growth' => 0.8],
]
],
], ],
'random_primary_pool' => [ 'random_primary_pool' => [
'crit' => ['weight' => 50, 'base' => [1, 3, 5, 10], 'growth' => 0.3], 'crit' => ['weight' => 40, 'base' => [1, 3, 5, 10], 'growth' => 0.3],
'critdmg' => ['weight' => 50, 'base' => [3, 8, 14, 24], 'growth' => 0.7], 'critdmg' => ['weight' => 40, 'base' => [3, 8, 14, 24], 'growth' => 0.7],
'matk' => ['weight' => 20, 'base' => [2, 5, 10, 18], 'growth' => 0.8],
], ],
'random_primary_count' => [ 'random_primary_count' => [
'common' => [0, 0], 'common' => [0, 0],
@ -33,18 +76,63 @@ return [
'legendary' => [1, 2], 'legendary' => [1, 2],
], ],
'affix_weights' => [ 'affix_weights' => [
'atk' => 40, 'patk' => 30,
'crit' => 30, 'matk' => 20,
'critdmg' => 30, 'crit' => 25,
'hp' => 15, 'critdmg' => 25,
'def' => 10, 'hp' => 10,
], ],
], ],
'armor' => [ 'armor' => [
'names' => ['布衣', '皮甲', '铁甲', '精钢甲', '玄武甲', '龙鳞甲'], 'names' => ['布衣', '皮甲', '铁甲', '精钢甲', '玄武甲', '龙鳞甲', '法袍', '灵纹袍'],
'fixed_primary' => [ 'fixed_primary' => [
'def' => ['base' => [3, 8, 15, 26], 'growth' => 0.8], 'pdef' => ['base' => [2, 6, 12, 20], 'growth' => 0.6],
'mdef' => ['base' => [1, 4, 8, 15], 'growth' => 0.5],
],
'specific_config' => [
'布衣' => [
'fixed_primary' => [
'pdef' => ['base' => [1, 3, 6, 10], 'growth' => 0.4],
'mdef' => ['base' => [1, 3, 6, 10], 'growth' => 0.4],
]
],
'皮甲' => [
'fixed_primary' => [
'pdef' => ['base' => [3, 7, 14, 22], 'growth' => 0.7],
'mdef' => ['base' => [1, 2, 4, 8], 'growth' => 0.3],
]
],
'铁甲' => [
'fixed_primary' => [
'pdef' => ['base' => [5, 12, 20, 35], 'growth' => 1.0], // 高物防
'mdef' => ['base' => [0, 1, 2, 5], 'growth' => 0.2], // 低魔防
]
],
'精钢甲' => [
'fixed_primary' => [
'pdef' => ['base' => [7, 15, 25, 45], 'growth' => 1.2],
'mdef' => ['base' => [1, 3, 6, 10], 'growth' => 0.3],
]
],
'玄武甲' => [
'fixed_primary' => [
'pdef' => ['base' => [10, 20, 35, 60], 'growth' => 1.5],
'mdef' => ['base' => [5, 10, 18, 30], 'growth' => 0.8],
]
],
'法袍' => [
'fixed_primary' => [
'pdef' => ['base' => [1, 3, 6, 12], 'growth' => 0.4],
'mdef' => ['base' => [5, 12, 20, 35], 'growth' => 1.0], // 高魔防
]
],
'灵纹袍' => [
'fixed_primary' => [
'pdef' => ['base' => [2, 5, 10, 18], 'growth' => 0.5],
'mdef' => ['base' => [8, 18, 30, 50], 'growth' => 1.3], // 极高魔防
]
],
], ],
'random_primary_pool' => [ 'random_primary_pool' => [
'hp' => ['weight' => 100, 'base' => [10, 25, 45, 75], 'growth' => 3.5], 'hp' => ['weight' => 100, 'base' => [10, 25, 45, 75], 'growth' => 3.5],
@ -56,18 +144,19 @@ return [
'legendary' => [1, 1], 'legendary' => [1, 1],
], ],
'affix_weights' => [ 'affix_weights' => [
'def' => 40, 'pdef' => 30,
'mdef' => 30,
'hp' => 40, 'hp' => 40,
'atk' => 10, 'patk' => 5,
'crit' => 15, 'matk' => 5,
'critdmg' => 15,
], ],
], ],
'boots' => [ 'boots' => [
'names' => ['布鞋', '皮靴', '铁靴', '疾风靴', '幽步靴', '龙鳞靴'], 'names' => ['布鞋', '皮靴', '铁靴', '疾风靴', '幽步靴', '龙鳞靴'],
'fixed_primary' => [ 'fixed_primary' => [
'def' => ['base' => [2, 5, 10, 18], 'growth' => 0.5], 'pdef' => ['base' => [1, 4, 8, 15], 'growth' => 0.4],
'mdef' => ['base' => [1, 3, 6, 12], 'growth' => 0.3],
], ],
'random_primary_pool' => [ 'random_primary_pool' => [
'hp' => ['weight' => 60, 'base' => [8, 20, 35, 60], 'growth' => 2.5], 'hp' => ['weight' => 60, 'base' => [8, 20, 35, 60], 'growth' => 2.5],
@ -80,10 +169,10 @@ return [
'legendary' => [1, 2], 'legendary' => [1, 2],
], ],
'affix_weights' => [ 'affix_weights' => [
'def' => 35, 'pdef' => 25,
'hp' => 35, 'mdef' => 25,
'hp' => 30,
'crit' => 20, 'crit' => 20,
'critdmg' => 10,
], ],
], ],
@ -93,8 +182,9 @@ return [
'crit' => ['base' => [2, 5, 8, 12], 'growth' => 0.4], 'crit' => ['base' => [2, 5, 8, 12], 'growth' => 0.4],
], ],
'random_primary_pool' => [ 'random_primary_pool' => [
'critdmg' => ['weight' => 60, 'base' => [5, 12, 20, 35], 'growth' => 0.8], 'critdmg' => ['weight' => 40, 'base' => [5, 12, 20, 35], 'growth' => 0.8],
'atk' => ['weight' => 40, 'base' => [3, 8, 15, 25], 'growth' => 1.0], 'patk' => ['weight' => 30, 'base' => [2, 6, 12, 20], 'growth' => 0.8],
'matk' => ['weight' => 30, 'base' => [2, 6, 12, 20], 'growth' => 0.8],
], ],
'random_primary_count' => [ 'random_primary_count' => [
'common' => [0, 0], 'common' => [0, 0],
@ -103,10 +193,10 @@ return [
'legendary' => [1, 2], 'legendary' => [1, 2],
], ],
'affix_weights' => [ 'affix_weights' => [
'crit' => 35, 'crit' => 30,
'critdmg' => 35, 'critdmg' => 30,
'atk' => 20, 'patk' => 20,
'hp' => 10, 'matk' => 20,
], ],
], ],
@ -116,7 +206,8 @@ return [
'hp' => ['base' => [15, 35, 60, 100], 'growth' => 4.0], 'hp' => ['base' => [15, 35, 60, 100], 'growth' => 4.0],
], ],
'random_primary_pool' => [ 'random_primary_pool' => [
'def' => ['weight' => 50, 'base' => [2, 6, 12, 20], 'growth' => 0.6], 'pdef' => ['weight' => 25, 'base' => [1, 5, 10, 18], 'growth' => 0.5],
'mdef' => ['weight' => 25, 'base' => [1, 5, 10, 18], 'growth' => 0.5],
'critdmg' => ['weight' => 50, 'base' => [3, 8, 15, 25], 'growth' => 0.5], 'critdmg' => ['weight' => 50, 'base' => [3, 8, 15, 25], 'growth' => 0.5],
], ],
'random_primary_count' => [ 'random_primary_count' => [
@ -126,10 +217,11 @@ return [
'legendary' => [1, 2], 'legendary' => [1, 2],
], ],
'affix_weights' => [ 'affix_weights' => [
'hp' => 40, 'hp' => 30,
'def' => 30, 'pdef' => 20,
'atk' => 15, 'mdef' => 20,
'crit' => 15, 'crit' => 15,
'critdmg' => 15,
], ],
], ],

View File

@ -13,21 +13,24 @@
// ========== 通用装备模板 ========== // ========== 通用装备模板 ==========
$weaponTemplate = [ $weaponTemplate = [
'fixed_primary' => [ 'fixed_primary' => [
'atk' => ['base' => [5, 12, 22, 38], 'growth' => 1.8], 'patk' => ['base' => [4, 10, 18, 30], 'growth' => 1.5],
'matk' => ['base' => [2, 5, 10, 18], 'growth' => 0.8],
], ],
'random_primary_pool' => [ 'random_primary_pool' => [
'crit' => ['weight' => 50, 'base' => [1, 3, 5, 10], 'growth' => 0.3], 'crit' => ['weight' => 40, 'base' => [1, 3, 5, 10], 'growth' => 0.3],
'critdmg' => ['weight' => 50, 'base' => [3, 8, 14, 24], 'growth' => 0.7], 'critdmg' => ['weight' => 40, 'base' => [3, 8, 14, 24], 'growth' => 0.7],
'matk' => ['weight' => 20, 'base' => [2, 5, 10, 18], 'growth' => 0.8],
], ],
'random_primary_count' => [ 'random_primary_count' => [
'common' => [0, 0], 'rare' => [0, 1], 'epic' => [1, 1], 'legendary' => [1, 2] 'common' => [0, 0], 'rare' => [0, 1], 'epic' => [1, 1], 'legendary' => [1, 2]
], ],
'affix_weights' => ['atk' => 40, 'crit' => 30, 'critdmg' => 30, 'hp' => 15, 'def' => 10], 'affix_weights' => ['patk' => 30, 'matk' => 20, 'crit' => 25, 'critdmg' => 25, 'hp' => 10],
]; ];
$armorTemplate = [ $armorTemplate = [
'fixed_primary' => [ 'fixed_primary' => [
'def' => ['base' => [3, 8, 15, 26], 'growth' => 0.8], 'pdef' => ['base' => [2, 6, 12, 20], 'growth' => 0.6],
'mdef' => ['base' => [1, 4, 8, 15], 'growth' => 0.5],
], ],
'random_primary_pool' => [ 'random_primary_pool' => [
'hp' => ['weight' => 100, 'base' => [10, 25, 45, 75], 'growth' => 3.5], 'hp' => ['weight' => 100, 'base' => [10, 25, 45, 75], 'growth' => 3.5],
@ -35,7 +38,7 @@ $armorTemplate = [
'random_primary_count' => [ 'random_primary_count' => [
'common' => [0, 0], 'rare' => [0, 1], 'epic' => [1, 1], 'legendary' => [1, 1], 'common' => [0, 0], 'rare' => [0, 1], 'epic' => [1, 1], 'legendary' => [1, 1],
], ],
'affix_weights' => ['def' => 40, 'hp' => 40, 'atk' => 10, 'crit' => 15, 'critdmg' => 15], 'affix_weights' => ['pdef' => 30, 'mdef' => 30, 'hp' => 40, 'patk' => 5, 'matk' => 5],
]; ];
$ringTemplate = [ $ringTemplate = [
@ -43,13 +46,14 @@ $ringTemplate = [
'crit' => ['base' => [2, 5, 8, 12], 'growth' => 0.4], 'crit' => ['base' => [2, 5, 8, 12], 'growth' => 0.4],
], ],
'random_primary_pool' => [ 'random_primary_pool' => [
'critdmg' => ['weight' => 60, 'base' => [5, 12, 20, 35], 'growth' => 0.8], 'critdmg' => ['weight' => 40, 'base' => [5, 12, 20, 35], 'growth' => 0.8],
'atk' => ['weight' => 40, 'base' => [3, 8, 15, 25], 'growth' => 1.0], 'patk' => ['weight' => 30, 'base' => [2, 6, 12, 20], 'growth' => 0.8],
'matk' => ['weight' => 30, 'base' => [2, 6, 12, 20], 'growth' => 0.8],
], ],
'random_primary_count' => [ 'random_primary_count' => [
'common' => [0, 0], 'rare' => [0, 1], 'epic' => [1, 1], 'legendary' => [1, 2], 'common' => [0, 0], 'rare' => [0, 1], 'epic' => [1, 1], 'legendary' => [1, 2],
], ],
'affix_weights' => ['crit' => 35, 'critdmg' => 35, 'atk' => 20, 'hp' => 10], 'affix_weights' => ['crit' => 30, 'critdmg' => 30, 'patk' => 20, 'matk' => 20],
]; ];
$necklaceTemplate = [ $necklaceTemplate = [
@ -57,18 +61,20 @@ $necklaceTemplate = [
'hp' => ['base' => [15, 35, 60, 100], 'growth' => 4.0], 'hp' => ['base' => [15, 35, 60, 100], 'growth' => 4.0],
], ],
'random_primary_pool' => [ 'random_primary_pool' => [
'def' => ['weight' => 50, 'base' => [2, 6, 12, 20], 'growth' => 0.6], 'pdef' => ['weight' => 25, 'base' => [1, 5, 10, 18], 'growth' => 0.5],
'mdef' => ['weight' => 25, 'base' => [1, 5, 10, 18], 'growth' => 0.5],
'critdmg' => ['weight' => 50, 'base' => [3, 8, 15, 25], 'growth' => 0.5], 'critdmg' => ['weight' => 50, 'base' => [3, 8, 15, 25], 'growth' => 0.5],
], ],
'random_primary_count' => [ 'random_primary_count' => [
'common' => [0, 0], 'rare' => [0, 1], 'epic' => [1, 1], 'legendary' => [1, 2], 'common' => [0, 0], 'rare' => [0, 1], 'epic' => [1, 1], 'legendary' => [1, 2],
], ],
'affix_weights' => ['hp' => 40, 'def' => 30, 'atk' => 15, 'crit' => 15], 'affix_weights' => ['hp' => 30, 'pdef' => 20, 'mdef' => 20, 'crit' => 15, 'critdmg' => 15],
]; ];
$bootsTemplate = [ $bootsTemplate = [
'fixed_primary' => [ 'fixed_primary' => [
'def' => ['base' => [2, 5, 10, 18], 'growth' => 0.5], 'pdef' => ['base' => [1, 4, 8, 15], 'growth' => 0.4],
'mdef' => ['base' => [1, 3, 6, 12], 'growth' => 0.3],
], ],
'random_primary_pool' => [ 'random_primary_pool' => [
'hp' => ['weight' => 60, 'base' => [8, 20, 35, 60], 'growth' => 2.5], 'hp' => ['weight' => 60, 'base' => [8, 20, 35, 60], 'growth' => 2.5],
@ -77,7 +83,7 @@ $bootsTemplate = [
'random_primary_count' => [ 'random_primary_count' => [
'common' => [0, 0], 'rare' => [0, 1], 'epic' => [1, 1], 'legendary' => [1, 2], 'common' => [0, 0], 'rare' => [0, 1], 'epic' => [1, 1], 'legendary' => [1, 2],
], ],
'affix_weights' => ['def' => 35, 'hp' => 35, 'crit' => 20, 'critdmg' => 10], 'affix_weights' => ['pdef' => 25, 'mdef' => 25, 'hp' => 30, 'crit' => 20],
]; ];
return [ return [
@ -119,6 +125,9 @@ return [
['type' => 'armor', 'name' => '皮甲', 'rate' => 10] + $armorTemplate, ['type' => 'armor', 'name' => '皮甲', 'rate' => 10] + $armorTemplate,
['type' => 'consume', 'name' => '黄龙丹', 'rate' => 25, 'heal' => 50], ['type' => 'consume', 'name' => '黄龙丹', 'rate' => 25, 'heal' => 50],
], ],
'minions' => [
['name' => '野狼帮帮众', 'hp' => 30, 'patk' => 5, 'matk' => 2, 'pdef' => 0, 'mdef' => 0, 'exp' => 10, 'count' => 2],
],
'weight' => 30, 'weight' => 30,
], ],
[ [
@ -132,10 +141,13 @@ return [
'exp' => 100, 'exp' => 100,
'spirit_stones' => 20, 'spirit_stones' => 20,
'drops' => [ 'drops' => [
['type' => 'weapon', 'name' => '眨眼剑法', 'quality' => 'rare', 'atk' => 15, 'rate' => 20, 'affixes' => ['crit' => 5]], ['type' => 'weapon', 'name' => '眨眼剑法', 'quality' => 'rare', 'patk' => 15, 'rate' => 20, 'affixes' => ['crit' => 5]],
['type' => 'necklace', 'name' => '长生锁', 'rate' => 15] + $necklaceTemplate, ['type' => 'necklace', 'name' => '长生锁', 'rate' => 15] + $necklaceTemplate,
['type' => 'consume', 'name' => '清灵散', 'rate' => 40, 'heal' => 80], ['type' => 'consume', 'name' => '清灵散', 'rate' => 40, 'heal' => 80],
], ],
'minions' => [
['name' => '铁奴', 'hp' => 80, 'patk' => 12, 'pdef' => 8, 'exp' => 30, 'count' => 1],
],
'weight' => 10, 'weight' => 10,
], ],
], ],
@ -188,7 +200,7 @@ return [
'exp' => 150, 'exp' => 150,
'spirit_stones' => 40, 'spirit_stones' => 40,
'drops' => [ 'drops' => [
['type' => 'weapon', 'name' => '青叶法器', 'quality' => 'rare', 'atk' => 25, 'rate' => 20], ['type' => 'weapon', 'name' => '青叶法器', 'quality' => 'rare', 'matk' => 25, 'rate' => 20],
['type' => 'ring', 'name' => '储物戒', 'rate' => 15] + $ringTemplate, ['type' => 'ring', 'name' => '储物戒', 'rate' => 15] + $ringTemplate,
['type' => 'consume', 'name' => '合气丹', 'rate' => 30, 'heal' => 100], ['type' => 'consume', 'name' => '合气丹', 'rate' => 30, 'heal' => 100],
], ],
@ -243,8 +255,8 @@ return [
'exp' => 300, 'exp' => 300,
'spirit_stones' => 100, 'spirit_stones' => 100,
'drops' => [ 'drops' => [
['type' => 'weapon', 'name' => '金竺笔', 'quality' => 'epic', 'atk' => 45, 'rate' => 15, 'affixes' => ['atk' => 10]], ['type' => 'weapon', 'name' => '金竺笔', 'quality' => 'epic', 'matk' => 45, 'rate' => 15, 'affixes' => ['matk' => 10]],
['type' => 'armor', 'name' => '墨蛟甲', 'quality' => 'epic', 'def' => 30, 'rate' => 15], ['type' => 'armor', 'name' => '墨蛟甲', 'quality' => 'epic', 'pdef' => 20, 'mdef' => 15, 'rate' => 15],
['type' => 'consume', 'name' => '筑基丹', 'rate' => 50, 'heal' => 500], ['type' => 'consume', 'name' => '筑基丹', 'rate' => 50, 'heal' => 500],
], ],
'weight' => 15, 'weight' => 15,
@ -302,7 +314,7 @@ return [
'exp' => 400, 'exp' => 400,
'spirit_stones' => 150, 'spirit_stones' => 150,
'drops' => [ 'drops' => [
['type' => 'weapon', 'name' => '烈焰刀', 'quality' => 'epic', 'atk' => 70, 'rate' => 20], ['type' => 'weapon', 'name' => '烈焰刀', 'quality' => 'epic', 'patk' => 50, 'matk' => 30, 'rate' => 20],
['type' => 'ring', 'name' => '传音符', 'rate' => 20] + $ringTemplate, ['type' => 'ring', 'name' => '传音符', 'rate' => 20] + $ringTemplate,
['type' => 'consume', 'name' => '定颜丹', 'rate' => 10, 'heal' => 800], ['type' => 'consume', 'name' => '定颜丹', 'rate' => 10, 'heal' => 800],
], ],
@ -358,8 +370,8 @@ return [
'exp' => 600, 'exp' => 600,
'spirit_stones' => 250, 'spirit_stones' => 250,
'drops' => [ 'drops' => [
['type' => 'weapon', 'name' => '血灵钻', 'quality' => 'epic', 'atk' => 100, 'rate' => 20, 'affixes' => ['crit' => 10]], ['type' => 'weapon', 'name' => '血灵钻', 'quality' => 'epic', 'matk' => 100, 'rate' => 20, 'affixes' => ['crit' => 10]],
['type' => 'armor', 'name' => '血灵甲', 'quality' => 'epic', 'def' => 60, 'rate' => 20], ['type' => 'armor', 'name' => '血灵甲', 'quality' => 'epic', 'pdef' => 40, 'mdef' => 30, 'rate' => 20],
['type' => 'consume', 'name' => '血灵丹', 'rate' => 30, 'heal' => 1000], ['type' => 'consume', 'name' => '血灵丹', 'rate' => 30, 'heal' => 1000],
], ],
'weight' => 15, 'weight' => 15,
@ -414,7 +426,7 @@ return [
'exp' => 1000, 'exp' => 1000,
'spirit_stones' => 400, 'spirit_stones' => 400,
'drops' => [ 'drops' => [
['type' => 'weapon', 'name' => '青元剑', 'quality' => 'legendary', 'atk' => 150, 'rate' => 15, 'affixes' => ['atk' => 15]], ['type' => 'weapon', 'name' => '青元剑', 'quality' => 'legendary', 'patk' => 100, 'matk' => 80, 'rate' => 15, 'affixes' => ['patk' => 15]],
['type' => 'consume', 'name' => '虚天鼎碎片', 'rate' => 10, 'heal' => 2000], // 剧情物品作为高回复药 ['type' => 'consume', 'name' => '虚天鼎碎片', 'rate' => 10, 'heal' => 2000], // 剧情物品作为高回复药
['type' => 'ring', 'name' => '黑煞戒', 'rate' => 20] + $ringTemplate, ['type' => 'ring', 'name' => '黑煞戒', 'rate' => 20] + $ringTemplate,
], ],
@ -473,7 +485,7 @@ return [
'exp' => 1500, 'exp' => 1500,
'spirit_stones' => 600, 'spirit_stones' => 600,
'drops' => [ 'drops' => [
['type' => 'weapon', 'name' => '引魂钟', 'quality' => 'epic', 'atk' => 200, 'rate' => 20], ['type' => 'weapon', 'name' => '引魂钟', 'quality' => 'epic', 'matk' => 200, 'rate' => 20],
['type' => 'boots', 'name' => '踏浪靴', 'rate' => 15] + $bootsTemplate, ['type' => 'boots', 'name' => '踏浪靴', 'rate' => 15] + $bootsTemplate,
['type' => 'consume', 'name' => '降尘丹', 'rate' => 25, 'heal' => 1500], ['type' => 'consume', 'name' => '降尘丹', 'rate' => 25, 'heal' => 1500],
], ],
@ -529,7 +541,7 @@ return [
'exp' => 2500, 'exp' => 2500,
'spirit_stones' => 1000, 'spirit_stones' => 1000,
'drops' => [ 'drops' => [
['type' => 'weapon', 'name' => '天都尸火', 'quality' => 'legendary', 'atk' => 300, 'rate' => 15, 'affixes' => ['critdmg' => 20]], ['type' => 'weapon', 'name' => '天都尸火', 'quality' => 'legendary', 'matk' => 300, 'rate' => 15, 'affixes' => ['critdmg' => 20]],
['type' => 'consume', 'name' => '补天丹', 'rate' => 10, 'heal' => 3000], ['type' => 'consume', 'name' => '补天丹', 'rate' => 10, 'heal' => 3000],
['type' => 'necklace', 'name' => '虚天鼎', 'quality' => 'legendary', 'hp' => 2000, 'rate' => 5], ['type' => 'necklace', 'name' => '虚天鼎', 'quality' => 'legendary', 'hp' => 2000, 'rate' => 5],
], ],
@ -585,8 +597,8 @@ return [
'exp' => 4000, 'exp' => 4000,
'spirit_stones' => 1500, 'spirit_stones' => 1500,
'drops' => [ 'drops' => [
['type' => 'weapon', 'name' => '金蛟剪', 'quality' => 'legendary', 'atk' => 450, 'rate' => 15, 'affixes' => ['crit' => 15]], ['type' => 'weapon', 'name' => '金蛟剪', 'quality' => 'legendary', 'patk' => 300, 'matk' => 200, 'rate' => 15, 'affixes' => ['crit' => 15]],
['type' => 'armor', 'name' => '金蛟鳞甲', 'quality' => 'legendary', 'def' => 250, 'rate' => 15], ['type' => 'armor', 'name' => '金蛟鳞甲', 'quality' => 'legendary', 'pdef' => 180, 'mdef' => 120, 'rate' => 15],
['type' => 'consume', 'name' => '九曲灵参', 'rate' => 10, 'heal' => 5000], ['type' => 'consume', 'name' => '九曲灵参', 'rate' => 10, 'heal' => 5000],
], ],
'weight' => 15, 'weight' => 15,
@ -644,7 +656,7 @@ return [
'exp' => 5000, 'exp' => 5000,
'spirit_stones' => 2000, 'spirit_stones' => 2000,
'drops' => [ 'drops' => [
['type' => 'weapon', 'name' => '落云剑', 'quality' => 'epic', 'atk' => 500, 'rate' => 20], ['type' => 'weapon', 'name' => '落云剑', 'quality' => 'epic', 'patk' => 350, 'matk' => 250, 'rate' => 20],
['type' => 'necklace', 'name' => '定魂珠', 'rate' => 15] + $necklaceTemplate, ['type' => 'necklace', 'name' => '定魂珠', 'rate' => 15] + $necklaceTemplate,
['type' => 'consume', 'name' => '培婴丹', 'rate' => 25, 'heal' => 3000], ['type' => 'consume', 'name' => '培婴丹', 'rate' => 25, 'heal' => 3000],
], ],
@ -700,8 +712,8 @@ return [
'exp' => 10000, 'exp' => 10000,
'spirit_stones' => 5000, 'spirit_stones' => 5000,
'drops' => [ 'drops' => [
['type' => 'weapon', 'name' => '黑风旗', 'quality' => 'legendary', 'atk' => 800, 'rate' => 15, 'affixes' => ['atk' => 20]], ['type' => 'weapon', 'name' => '黑风旗', 'quality' => 'legendary', 'matk' => 800, 'rate' => 15, 'affixes' => ['matk' => 20]],
['type' => 'armor', 'name' => '魔龙甲', 'quality' => 'legendary', 'def' => 500, 'rate' => 15], ['type' => 'armor', 'name' => '魔龙甲', 'quality' => 'legendary', 'pdef' => 350, 'mdef' => 250, 'rate' => 15],
['type' => 'consume', 'name' => '万年灵乳', 'rate' => 20, 'heal' => 8000], ['type' => 'consume', 'name' => '万年灵乳', 'rate' => 20, 'heal' => 8000],
], ],
'weight' => 15, 'weight' => 15,
@ -759,7 +771,7 @@ return [
'exp' => 15000, 'exp' => 15000,
'spirit_stones' => 8000, 'spirit_stones' => 8000,
'drops' => [ 'drops' => [
['type' => 'weapon', 'name' => '八灵尺', 'quality' => 'legendary', 'atk' => 1200, 'rate' => 15, 'affixes' => ['crit' => 20]], ['type' => 'weapon', 'name' => '八灵尺', 'quality' => 'legendary', 'matk' => 1200, 'rate' => 15, 'affixes' => ['crit' => 20]],
['type' => 'ring', 'name' => '雪晶珠', 'quality' => 'legendary', 'crit' => 15, 'rate' => 15], ['type' => 'ring', 'name' => '雪晶珠', 'quality' => 'legendary', 'crit' => 15, 'rate' => 15],
['type' => 'consume', 'name' => '回阳水', 'rate' => 10, 'heal' => 10000], ['type' => 'consume', 'name' => '回阳水', 'rate' => 10, 'heal' => 10000],
], ],
@ -815,8 +827,8 @@ return [
'exp' => 30000, 'exp' => 30000,
'spirit_stones' => 15000, 'spirit_stones' => 15000,
'drops' => [ 'drops' => [
['type' => 'weapon', 'name' => '青竹蜂云剑', 'quality' => 'legendary', 'atk' => 2000, 'rate' => 20, 'affixes' => ['atk' => 30, 'crit' => 20]], ['type' => 'weapon', 'name' => '青竹蜂云剑', 'quality' => 'legendary', 'patk' => 1500, 'matk' => 1000, 'rate' => 20, 'affixes' => ['patk' => 30, 'crit' => 20]],
['type' => 'armor', 'name' => '五行甲', 'quality' => 'legendary', 'def' => 1500, 'rate' => 20], ['type' => 'armor', 'name' => '五行甲', 'quality' => 'legendary', 'pdef' => 1000, 'mdef' => 1000, 'rate' => 20],
['type' => 'consume', 'name' => '飞升令', 'rate' => 100, 'heal' => 99999], // 象征性物品 ['type' => 'consume', 'name' => '飞升令', 'rate' => 100, 'heal' => 99999], // 象征性物品
], ],
'weight' => 15, 'weight' => 15,

View File

@ -11,7 +11,7 @@ return [
'min_level' => 1, 'min_level' => 1,
'desc' => '韩立在七玄门最好的朋友,虽无灵根但武功高强。', 'desc' => '韩立在七玄门最好的朋友,虽无灵根但武功高强。',
'base_stats' => [ 'base_stats' => [
'hp' => 100, 'patk' => 15, 'matk' => 5, 'pdef' => 5, 'mdef' => 3, 'crit' => 10, 'critdmg' => 150, 'growth' => 1.2 'hp' => 100, 'patk' => 15, 'matk' => 5, 'pdef' => 5, 'mdef' => 3, 'crit' => 10, 'critdmg' => 130, 'growth' => 1.2
], ],
// 天赋权重:武功型,偏攻击和暴击 // 天赋权重:武功型,偏攻击和暴击
'talent_weights' => [ 'talent_weights' => [

View File

@ -3,9 +3,6 @@ namespace Game\Entities;
/** /**
* Simple representation of an equipment/consumable item. * Simple representation of an equipment/consumable item.
*
* The game only needs an associative array for storage, but having a class
* makes random generation easier and keeps the logic tidy.
*/ */
class Item class Item
{ {
@ -15,12 +12,19 @@ class Item
public int $level = 1; // Item level public int $level = 1; // Item level
private array $affixes; private array $affixes;
// Stats
public int $patk = 0;
public int $matk = 0;
public int $pdef = 0;
public int $mdef = 0;
public int $hp = 0;
public int $crit = 0;
public int $critdmg = 0;
public int $heal = 0;
public string $desc = '';
/** /**
* Generate a random item of the given type and level. * Generate a random item of the given type and level.
* @param string $type Item type (weapon, armor, etc.)
* @param int $level Item level
* @param string|null $specificName If provided, use this name instead of random selection
* @return array Item data array
*/ */
public static function randomItem(string $type, int $level = 1, ?string $specificName = null): array public static function randomItem(string $type, int $level = 1, ?string $specificName = null): array
{ {
@ -30,323 +34,7 @@ class Item
$data = require __DIR__ . '/../../src/Data/items.php'; $data = require __DIR__ . '/../../src/Data/items.php';
} }
// 品质对照表affix 数量和索引 // 品质对照表
$qualityConfig = [
'common' => ['affixes' => 0, 'index' => 0],
'rare' => ['affixes' => 1, 'index' => 1],
'epic' => ['affixes' => 2, 'index' => 2],
'legendary' => ['affixes' => 3, 'index' => 3],
];
// 随机品质
$roll = rand(1, 100);
if ($roll <= 70) $quality = 'common'; // 70% 普通
elseif ($roll <= 90) $quality = 'rare'; // 20% 稀有
elseif ($roll <= 98) $quality = 'epic'; // 8% 史诗
else $quality = 'legendary'; // 2% 传说
$item = new self();
$item->type = $type;
$item->quality = $quality;
$item->level = $level;
// 获取品质对应的词条数量和属性索引
$affixCount = $qualityConfig[$quality]['affixes'];
$qualityIndex = $qualityConfig[$quality]['index'];
// ===========================
// ① 基础名称与基础属性 (Scaling with Level)
// ===========================
$typeConfig = $data['types'][$type] ?? null;
if (!$typeConfig) {
$item->name = '未知物品';
$item->desc = '未知';
return $item->toArray(); // Helper method needed or manual array return
}
$names = $typeConfig['names'] ?? ['未知物品'];
// Use specific name if provided, otherwise random selection
if ($specificName !== null && in_array($specificName, $names)) {
$item->name = $specificName;
} else {
$item->name = $names[array_rand($names)];
}
// ===========================
// ① 生成主属性(固定 + 随机)
// ===========================
// 初始化所有可能的属性为0
$item->atk = 0;
$item->def = 0;
$item->hp = 0;
$item->crit = 0;
$item->critdmg = 0;
if ($type === 'consume') {
// 消耗品保持原有逻辑
$baseStats = $typeConfig['base_stats'] ?? [0,0,0,0];
$growth = $typeConfig['growth'] ?? 0;
$item->heal = $baseStats[$qualityIndex] + ($level * $growth) + rand(0, 10);
$item->desc = "Lv.{$level} {$quality}品质的药剂";
} else {
// 装备:固定主属性 + 随机主属性
$fixedPrimaries = $typeConfig['fixed_primary'] ?? [];
$randomPool = $typeConfig['random_primary_pool'] ?? [];
$randomCountConfig = $typeConfig['random_primary_count'][$quality] ?? [0, 0];
// Step 1: 生成固定主属性(必定存在)
foreach ($fixedPrimaries as $statKey => $statConfig) {
$baseValue = $statConfig['base'][$qualityIndex];
$growth = $statConfig['growth'];
// 主属性数值 = 基础值 + 等级成长 + 随机波动
$randomBonus = rand(0, max(1, (int)($baseValue * 0.15))); // 15%随机波动
$finalValue = (int)($baseValue + ($level * $growth) + $randomBonus);
// 赋值到对应属性
switch ($statKey) {
case 'atk':
$item->atk = $finalValue;
break;
case 'def':
$item->def = $finalValue;
break;
case 'hp':
$item->hp = $finalValue;
break;
case 'crit':
$item->crit = $finalValue;
break;
case 'critdmg':
$item->critdmg = $finalValue;
break;
}
}
// Step 2: 生成随机主属性(根据品质决定数量)
if (!empty($randomPool)) {
[$minCount, $maxCount] = $randomCountConfig;
$randomCount = rand($minCount, $maxCount);
if ($randomCount > 0) {
// 使用权重随机选择额外主属性(不重复)
$selectedRandoms = [];
$availableStats = $randomPool;
for ($i = 0; $i < $randomCount && !empty($availableStats); $i++) {
// 提取权重
$weights = [];
foreach ($availableStats as $statKey => $statConfig) {
$weights[$statKey] = $statConfig['weight'];
}
// 权重随机选择
$selectedStat = self::weightedRandom($weights);
$selectedRandoms[] = $selectedStat;
// 移除已选择的属性(避免重复)
unset($availableStats[$selectedStat]);
}
// 生成随机主属性数值
foreach ($selectedRandoms as $statKey) {
$statConfig = $randomPool[$statKey];
$baseValue = $statConfig['base'][$qualityIndex];
$growth = $statConfig['growth'];
// 主属性数值 = 基础值 + 等级成长 + 随机波动
$randomBonus = rand(0, max(1, (int)($baseValue * 0.15))); // 15%随机波动
$finalValue = (int)($baseValue + ($level * $growth) + $randomBonus);
// 赋值到对应属性
switch ($statKey) {
case 'atk':
$item->atk = $finalValue;
break;
case 'def':
$item->def = $finalValue;
break;
case 'hp':
$item->hp = $finalValue;
break;
case 'crit':
$item->crit = $finalValue;
break;
case 'critdmg':
$item->critdmg = $finalValue;
break;
}
}
}
}
// 设置描述
$item->desc = "Lv.{$level} {$quality}品质的" . match($type) {
'weapon' => '武器',
'armor' => '防具',
'ring' => '戒指',
'necklace' => '项链',
default => '装备'
};
}
// ===========================
// ② 词条池(固定值 + 百分比)
// ===========================
$affixNames = $data['affix_definitions'];
$affixWeights = $typeConfig['affix_weights'] ?? [];
// ===========================
// ③ 随机 affix使用权重系统避免重复
// ===========================
$selectedAffixes = [];
for ($i = 0; $i < $affixCount; $i++) {
if ($type == 'consume') break;
if (empty($affixWeights)) break;
// 移除已选择的词条,避免重复
$availableWeights = array_diff_key($affixWeights, array_flip($selectedAffixes));
if (empty($availableWeights)) break;
// 使用权重随机选择词条
$key = self::weightedRandom($availableWeights);
$selectedAffixes[] = $key;
// 固定 or 百分比?
$usePercent = (bool)rand(0, 1);
if ($usePercent) {
// Percentages scale slower but better for higher levels
$base = rand(2, 8); // 提高百分比基础值
$v = $base + floor($level / 3); // 每3级增加1%
$item->affixes[] = "{$affixNames[$key]} +{$v}%";
} else {
// Flat values scale with level and quality
$qualityMultiplier = match($quality) {
'legendary' => 2.0,
'epic' => 1.5,
'rare' => 1.2,
default => 1.0
};
$base = rand(2, 8);
$multiplier = match($key) {
'atk' => 1.5,
'def' => 1.0,
'hp' => 8,
'crit' => 0.5,
'critdmg' => 0.8,
default => 1
};
$v = floor(($base + ($level * $multiplier)) * $qualityMultiplier);
$item->affixes[] = "{$affixNames[$key]} +{$v}";
}
}
// ===========================
// ④ 返回数组格式
// ===========================
return [
'name' => $item->name,
'type' => $item->type,
'quality' => $item->quality,
'level' => $item->level,
'atk' => $item->atk ?? 0,
'def' => $item->def ?? 0,
'hp' => $item->hp ?? 0,
'crit' => $item->crit ?? 0,
'critdmg' => $item->critdmg ?? 0,
'heal' => $item->heal ?? 0,
'affixes' => $item->affixes ?? [],
'desc' => $item->desc,
];
}
/**
* Weighted random selection helper
* @param array $weights Associative array of key => weight
* @return string Selected key
*/
private static function weightedRandom(array $weights): string
{
$totalWeight = array_sum($weights);
$rand = rand(1, $totalWeight);
foreach ($weights as $key => $weight) {
$rand -= $weight;
if ($rand <= 0) {
return $key;
}
}
// Fallback to first key
return array_key_first($weights);
}
// ===========================
// ⑤ 生成特定物品(用于掉落)
// ===========================
/**
* Generate a specific item based on a configuration array.
* Expected keys:
* - type: Item type (weapon, armor, etc.)
* - name (optional): Specific item name (e.g., '铁剑'). If provided, this exact item will be generated with random quality/affixes
* - quality (optional): Force specific quality (overrides random quality)
* - level (optional): Item level
* - affixes (optional): Force specific affixes (overrides random affixes)
*/
public static function createFromSpec(array $spec, int $baseLevel): array
{
$type = $spec['type'] ?? 'weapon';
$quality = $spec['quality'] ?? null;
$itemLevel = $spec['level'] ?? $baseLevel;
$affixes = $spec['affixes'] ?? [];
$specificName = $spec['name'] ?? null;
// Load data definitions
static $data = null;
if ($data === null) {
$data = require __DIR__ . '/../../src/Data/items.php';
}
// Generate item with specific name (if provided)
$item = self::randomItem($type, $itemLevel, $specificName);
// Override quality if specified (otherwise uses random quality from randomItem)
if ($quality) {
$item['quality'] = $quality;
}
// Override affixes if specified (otherwise uses random affixes from randomItem)
if (!empty($affixes)) {
$item['affixes'] = $affixes;
}
return $item;
}
/**
* 使用详细配置生成装备(支持 maps.php 中的 fixed_primary, random_primary_pool 等)
* @param array $spec 掉落配置
* @param int $baseLevel 基础等级
* @return array
*/
public static function createFromSpecWithConfig(array $spec, int $baseLevel): array
{
$type = $spec['type'] ?? 'weapon';
$name = $spec['name'] ?? '未知装备';
$itemLevel = $spec['level'] ?? $baseLevel;
// 品质配置
$qualityConfig = [ $qualityConfig = [
'common' => ['affixes' => 0, 'index' => 0], 'common' => ['affixes' => 0, 'index' => 0],
'rare' => ['affixes' => 1, 'index' => 1], 'rare' => ['affixes' => 1, 'index' => 1],
@ -361,61 +49,76 @@ class Item
elseif ($roll <= 98) $quality = 'epic'; elseif ($roll <= 98) $quality = 'epic';
else $quality = 'legendary'; else $quality = 'legendary';
// 如果配置指定了品质,则使用指定品质 $item = new self();
if (isset($spec['quality'])) { $item->type = $type;
$quality = $spec['quality']; $item->quality = $quality;
$item->level = $level;
$affixCount = $qualityConfig[$quality]['affixes'];
$qualityIndex = $qualityConfig[$quality]['index'];
$typeConfig = $data['types'][$type] ?? null;
if (!$typeConfig) {
$item->name = '未知物品';
$item->desc = '未知';
return $item->toArray();
} }
$qualityIndex = $qualityConfig[$quality]['index']; $names = $typeConfig['names'] ?? ['未知物品'];
$affixCount = $qualityConfig[$quality]['affixes'];
if ($specificName !== null && in_array($specificName, $names)) {
$item->name = $specificName;
} else {
$item->name = $names[array_rand($names)];
}
// 初始化属性 // 初始化属性
$item = [ $item->patk = 0;
'name' => $name, $item->matk = 0;
'type' => $type, $item->pdef = 0;
'quality' => $quality, $item->mdef = 0;
'level' => $itemLevel, $item->hp = 0;
'atk' => 0, $item->crit = 0;
'def' => 0, $item->critdmg = 0;
'hp' => 0,
'crit' => 0,
'critdmg' => 0,
'heal' => 0,
'affixes' => [],
'desc' => "Lv.{$itemLevel} {$quality}品质",
];
// ===== 处理固定主属性 ===== if ($type === 'consume') {
$fixedPrimary = $spec['fixed_primary'] ?? []; $baseStats = $typeConfig['base_stats'] ?? [0,0,0,0];
foreach ($fixedPrimary as $statKey => $statConfig) { $growth = $typeConfig['growth'] ?? 0;
$baseValues = $statConfig['base'] ?? [0, 0, 0, 0]; $item->heal = $baseStats[$qualityIndex] + ($level * $growth) + rand(0, 10);
$growth = $statConfig['growth'] ?? 0; $item->desc = "Lv.{$level} {$quality}品质的药剂";
} else {
// 检查是否有特定物品配置
$specificConfig = $typeConfig['specific_config'][$item->name] ?? [];
$baseValue = $baseValues[$qualityIndex] ?? $baseValues[0]; // 合并配置:优先使用特定配置,否则使用默认配置
$fixedPrimaries = $specificConfig['fixed_primary'] ?? $typeConfig['fixed_primary'] ?? [];
$randomPool = $specificConfig['random_primary_pool'] ?? $typeConfig['random_primary_pool'] ?? [];
$randomCountConfig = $typeConfig['random_primary_count'][$quality] ?? [0, 0];
// 1. 固定主属性
foreach ($fixedPrimaries as $statKey => $statConfig) {
$baseValue = $statConfig['base'][$qualityIndex];
$growth = $statConfig['growth'];
$randomBonus = rand(0, max(1, (int)($baseValue * 0.15))); $randomBonus = rand(0, max(1, (int)($baseValue * 0.15)));
$finalValue = (int)($baseValue + ($itemLevel * $growth) + $randomBonus); $finalValue = (int)($baseValue + ($level * $growth) + $randomBonus);
$item->$statKey = $finalValue;
$item[$statKey] = $finalValue;
} }
// ===== 处理随机主属性 ===== // 2. 随机主属性
$randomPool = $spec['random_primary_pool'] ?? [];
$randomCountConfig = $spec['random_primary_count'][$quality] ?? [0, 0];
if (!empty($randomPool)) { if (!empty($randomPool)) {
[$minCount, $maxCount] = $randomCountConfig; [$minCount, $maxCount] = $randomCountConfig;
$randomCount = rand($minCount, $maxCount); $randomCount = rand($minCount, $maxCount);
if ($randomCount > 0) { if ($randomCount > 0) {
$availableStats = $randomPool;
$selectedRandoms = []; $selectedRandoms = [];
$availableStats = $randomPool;
for ($i = 0; $i < $randomCount && !empty($availableStats); $i++) { for ($i = 0; $i < $randomCount && !empty($availableStats); $i++) {
$weights = []; $weights = [];
foreach ($availableStats as $statKey => $statConfig) { foreach ($availableStats as $statKey => $statConfig) {
$weights[$statKey] = $statConfig['weight'] ?? 50; $weights[$statKey] = $statConfig['weight'];
} }
$selectedStat = self::weightedRandom($weights); $selectedStat = self::weightedRandom($weights);
$selectedRandoms[] = $selectedStat; $selectedRandoms[] = $selectedStat;
unset($availableStats[$selectedStat]); unset($availableStats[$selectedStat]);
@ -423,42 +126,33 @@ class Item
foreach ($selectedRandoms as $statKey) { foreach ($selectedRandoms as $statKey) {
$statConfig = $randomPool[$statKey]; $statConfig = $randomPool[$statKey];
$baseValues = $statConfig['base'] ?? [0, 0, 0, 0]; $baseValue = $statConfig['base'][$qualityIndex];
$growth = $statConfig['growth'] ?? 0; $growth = $statConfig['growth'];
$baseValue = $baseValues[$qualityIndex] ?? $baseValues[0];
$randomBonus = rand(0, max(1, (int)($baseValue * 0.15))); $randomBonus = rand(0, max(1, (int)($baseValue * 0.15)));
$finalValue = (int)($baseValue + ($itemLevel * $growth) + $randomBonus); $finalValue = (int)($baseValue + ($level * $growth) + $randomBonus);
$item->$statKey += $finalValue;
$item[$statKey] = ($item[$statKey] ?? 0) + $finalValue;
} }
} }
} }
// ===== 生成词条 ===== $item->desc = "Lv.{$level} {$quality}品质的" . match($type) {
static $data = null; 'weapon' => '武器',
if ($data === null) { 'armor' => '防具',
$data = require __DIR__ . '/../../src/Data/items.php'; 'boots' => '鞋子',
'ring' => '戒指',
'necklace' => '项链',
default => '装备'
};
} }
$affixNames = $data['affix_definitions'] ?? [ // 3. 词条
'atk' => '攻击', $affixNames = $data['affix_definitions'];
'def' => '防御', $affixWeights = $typeConfig['affix_weights'] ?? [];
'hp' => '生命值', $item->affixes = [];
'crit' => '暴击率',
'critdmg' => '暴击伤害',
];
$affixWeights = $spec['affix_weights'] ?? [
'atk' => 25,
'def' => 25,
'hp' => 25,
'crit' => 15,
'critdmg' => 10,
];
$selectedAffixes = []; $selectedAffixes = [];
for ($i = 0; $i < $affixCount; $i++) { for ($i = 0; $i < $affixCount; $i++) {
if ($type == 'consume') break;
if (empty($affixWeights)) break; if (empty($affixWeights)) break;
$availableWeights = array_diff_key($affixWeights, array_flip($selectedAffixes)); $availableWeights = array_diff_key($affixWeights, array_flip($selectedAffixes));
@ -471,8 +165,8 @@ class Item
if ($usePercent) { if ($usePercent) {
$base = rand(2, 8); $base = rand(2, 8);
$v = $base + floor($itemLevel / 3); $v = $base + floor($level / 3);
$item['affixes'][] = "{$affixNames[$key]} +{$v}%"; $item->affixes[] = "{$affixNames[$key]} +{$v}%";
} else { } else {
$qualityMultiplier = match($quality) { $qualityMultiplier = match($quality) {
'legendary' => 2.0, 'legendary' => 2.0,
@ -483,76 +177,109 @@ class Item
$base = rand(2, 8); $base = rand(2, 8);
$multiplier = match($key) { $multiplier = match($key) {
'atk' => 1.5, 'patk' => 1.5,
'def' => 1.0, 'matk' => 1.5,
'pdef' => 1.0,
'mdef' => 1.0,
'hp' => 8, 'hp' => 8,
'crit' => 0.5, 'crit' => 0.5,
'critdmg' => 0.8, 'critdmg' => 0.8,
default => 1 default => 1
}; };
$v = floor(($base + ($itemLevel * $multiplier)) * $qualityMultiplier); $v = floor(($base + ($level * $multiplier)) * $qualityMultiplier);
$item['affixes'][] = "{$affixNames[$key]} +{$v}"; $item->affixes[] = "{$affixNames[$key]} +{$v}";
} }
} }
// 设置描述 return $item->toArray();
$item['desc'] = "Lv.{$itemLevel} {$quality}品质的" . match($type) { }
'weapon' => '武器',
'armor' => '防具', private static function weightedRandom(array $weights): string
'boots' => '鞋子', {
'ring' => '戒指', $totalWeight = array_sum($weights);
'necklace' => '项链', $rand = rand(1, $totalWeight);
default => '装备' foreach ($weights as $key => $weight) {
}; $rand -= $weight;
if ($rand <= 0) return $key;
}
return array_key_first($weights);
}
public static function createFromSpec(array $spec, int $baseLevel): array
{
$type = $spec['type'] ?? 'weapon';
$quality = $spec['quality'] ?? null;
$itemLevel = $spec['level'] ?? $baseLevel;
$affixes = $spec['affixes'] ?? [];
$specificName = $spec['name'] ?? null;
$item = self::randomItem($type, $itemLevel, $specificName);
if ($quality) $item['quality'] = $quality;
if (!empty($affixes)) $item['affixes'] = $affixes;
// Allow overriding specific stats if provided in spec (e.g. from maps.php drops)
foreach (['patk', 'matk', 'pdef', 'mdef', 'hp', 'crit', 'critdmg'] as $stat) {
if (isset($spec[$stat])) {
$item[$stat] = $spec[$stat];
}
}
return $item; return $item;
} }
/** public static function createFromSpecWithConfig(array $spec, int $baseLevel): array
* 计算物品的售价(灵石) {
* 基于品质、等级、属性数量计算 return self::createFromSpec($spec, $baseLevel);
*/ }
public function toArray(): array
{
return [
'name' => $this->name,
'type' => $this->type,
'quality' => $this->quality,
'level' => $this->level,
'patk' => $this->patk,
'matk' => $this->matk,
'pdef' => $this->pdef,
'mdef' => $this->mdef,
'hp' => $this->hp,
'crit' => $this->crit,
'critdmg' => $this->critdmg,
'heal' => $this->heal,
'affixes' => $this->affixes ?? [],
'desc' => $this->desc,
];
}
public static function calculateSellPrice(array $item): int public static function calculateSellPrice(array $item): int
{ {
// 消耗品售价低 if (($item['type'] ?? '') === 'consume') {
if ($item['type'] === 'consume') { $basePrice = match($item['quality'] ?? 'common') {
$basePrice = match($item['quality']) { 'common' => 1, 'rare' => 3, 'epic' => 8, 'legendary' => 20, default => 1
'common' => 1,
'rare' => 3,
'epic' => 8,
'legendary' => 20,
default => 1
}; };
return (int)($basePrice + ($item['level'] ?? 1) * 0.5); return (int)($basePrice + ($item['level'] ?? 1) * 0.5);
} }
// 装备基础价格(根据品质) $basePrice = match($item['quality'] ?? 'common') {
$basePrice = match($item['quality']) { 'common' => 5, 'rare' => 15, 'epic' => 40, 'legendary' => 100, default => 5
'common' => 5,
'rare' => 15,
'epic' => 40,
'legendary' => 100,
default => 5
}; };
// 等级加成每级增加基础价格的10%
$levelBonus = $basePrice * ($item['level'] ?? 1) * 0.1; $levelBonus = $basePrice * ($item['level'] ?? 1) * 0.1;
// 主属性加成
$statBonus = 0; $statBonus = 0;
$statBonus += ($item['atk'] ?? 0) * 0.5; $statBonus += ($item['patk'] ?? 0) * 0.5;
$statBonus += ($item['def'] ?? 0) * 0.5; $statBonus += ($item['matk'] ?? 0) * 0.5;
$statBonus += ($item['pdef'] ?? 0) * 0.5;
$statBonus += ($item['mdef'] ?? 0) * 0.5;
$statBonus += ($item['hp'] ?? 0) * 0.1; $statBonus += ($item['hp'] ?? 0) * 0.1;
$statBonus += ($item['crit'] ?? 0) * 1.0; $statBonus += ($item['crit'] ?? 0) * 1.0;
$statBonus += ($item['critdmg'] ?? 0) * 0.3; $statBonus += ($item['critdmg'] ?? 0) * 0.3;
// 词条加成每个词条增加20%
$affixCount = count($item['affixes'] ?? []); $affixCount = count($item['affixes'] ?? []);
$affixBonus = $basePrice * $affixCount * 0.2; $affixBonus = $basePrice * $affixCount * 0.2;
$totalPrice = $basePrice + $levelBonus + $statBonus + $affixBonus; return max(1, (int)($basePrice + $levelBonus + $statBonus + $affixBonus));
return max(1, (int)$totalPrice);
} }
} }

View File

@ -64,62 +64,116 @@ class Monster
} }
// 3. Hydrate monster base stats from maps.php // 3. Hydrate monster base stats from maps.php
$monster->name = $selectedMonster['name']; $monster->hydrateFromConfig($selectedMonster);
$monster->level = $selectedMonster['level'];
$monster->baseHp = $selectedMonster['hp'];
$monster->hp = $selectedMonster['hp'];
$monster->basePatk = $selectedMonster['patk'] ?? $selectedMonster['atk'] ?? 4;
$monster->patk = $monster->basePatk;
$monster->baseMatk = $selectedMonster['matk'] ?? 2;
$monster->matk = $monster->baseMatk;
$monster->basePdef = $selectedMonster['pdef'] ?? $selectedMonster['def'] ?? 0;
$monster->pdef = $monster->basePdef;
$monster->baseMdef = $selectedMonster['mdef'] ?? 0;
$monster->mdef = $monster->baseMdef;
$monster->crit = $selectedMonster['crit'] ?? 5;
$monster->critdmg = $selectedMonster['critdmg'] ?? 150;
$monster->expReward = $selectedMonster['exp'];
$monster->spiritStoneReward = $selectedMonster['spirit_stones'] ?? 0;
// 4. Generate equipment from drops and equip them return $monster;
$drops = $selectedMonster['drops'] ?? []; }
/**
* Create a group of monsters with random, diverse enemies (1-5 monsters)
* Each monster is independently selected from the dungeon's monster pool using weighted random selection
* @return Monster[]
*/
public static function createGroup(int $dungeonId): array
{
// Load data
static $maps = null;
if ($maps === null) {
$maps = require __DIR__ . '/../../src/Data/maps.php';
}
$monsterConfig = $maps[$dungeonId]['monsters'] ?? [];
if (empty($monsterConfig)) {
return [self::create($dungeonId)];
}
// Determine group size (1-5 enemies)
$groupSize = rand(1, 5);
$group = [];
// Create each enemy independently using weighted random selection
for ($i = 0; $i < $groupSize; $i++) {
$totalWeight = 0;
foreach ($monsterConfig as $m) {
$totalWeight += $m['weight'] ?? 100;
}
$rand = rand(1, $totalWeight);
$selectedConfig = null;
foreach ($monsterConfig as $m) {
$rand -= $m['weight'] ?? 100;
if ($rand <= 0) {
$selectedConfig = $m;
break;
}
}
if (!$selectedConfig) {
$selectedConfig = $monsterConfig[0];
}
// Create monster from selected config
$monster = new self();
$monster->hydrateFromConfig($selectedConfig);
// Add suffix to distinguish multiple monsters of same type
if ($groupSize > 1) {
$monster->name .= " (" . ($i + 1) . ")";
}
$group[] = $monster;
}
return $group;
}
public function hydrateFromConfig(array $config): void
{
$this->name = $config['name'];
$this->level = $config['level'] ?? 1;
$this->baseHp = $config['hp'] ?? 20;
$this->hp = $this->baseHp;
$this->basePatk = $config['patk'] ?? $config['atk'] ?? 4;
$this->patk = $this->basePatk;
$this->baseMatk = $config['matk'] ?? 2;
$this->matk = $this->baseMatk;
$this->basePdef = $config['pdef'] ?? $config['def'] ?? 0;
$this->pdef = $this->basePdef;
$this->baseMdef = $config['mdef'] ?? 0;
$this->mdef = $this->baseMdef;
$this->crit = $config['crit'] ?? 5;
$this->critdmg = $config['critdmg'] ?? 150;
$this->expReward = $config['exp'] ?? 0;
$this->spiritStoneReward = $config['spirit_stones'] ?? 0;
// Drops & Equipment
$drops = $config['drops'] ?? [];
foreach ($drops as $drop) { foreach ($drops as $drop) {
$type = $drop['type'] ?? ''; $type = $drop['type'] ?? '';
$rate = $drop['rate'] ?? 0; $rate = $drop['rate'] ?? 0;
// 消耗品放入掉落表,不装备
if ($type === 'consume') { if ($type === 'consume') {
$spec = $drop; $spec = $drop;
unset($spec['rate']); unset($spec['rate']);
$item = Item::createFromSpec($spec, $monster->level); $item = Item::createFromSpec($spec, $this->level);
$monster->dropTable[] = [ $this->dropTable[] = [
'item' => $item, 'item' => $item,
'rate' => $rate, 'rate' => $rate,
]; ];
continue; continue;
} }
// 装备类:生成并装备
if (in_array($type, ['weapon', 'armor', 'boots', 'ring', 'necklace'])) { if (in_array($type, ['weapon', 'armor', 'boots', 'ring', 'necklace'])) {
// 先判断是否生成这件装备(使用掉落率) if (rand(1, 100) > $rate) continue;
if (rand(1, 100) > $rate) {
continue; // 没有掉落这件装备
}
// 使用掉落配置生成装备
$spec = $drop; $spec = $drop;
unset($spec['rate']); unset($spec['rate']);
$item = Item::createFromSpecWithConfig($spec, $monster->level); $item = Item::createFromSpecWithConfig($spec, $this->level);
$this->equip[$type] = $item;
// 装备到对应槽位(覆盖已有的)
$monster->equip[$type] = $item;
} }
} }
// 5. Apply equipment stats to monster $this->applyEquipmentStats();
$monster->applyEquipmentStats();
return $monster;
} }
/** /**

View File

@ -8,6 +8,7 @@ class Partner
public int $level = 1; public int $level = 1;
public int $exp = 0; public int $exp = 0;
public int $maxExp = 100; public int $maxExp = 100;
public int $hp = 100; // 当前血量
public array $baseStats = []; public array $baseStats = [];
public array $equip = []; // weapon, armor, ring, boots, necklace public array $equip = []; // weapon, armor, ring, boots, necklace
@ -56,6 +57,10 @@ class Partner
$this->equip = $data['equip'] ?? []; $this->equip = $data['equip'] ?? [];
$this->talents = $data['talents'] ?? $this->talents; $this->talents = $data['talents'] ?? $this->talents;
$this->talentWeights = $data['talentWeights'] ?? $this->talentWeights; $this->talentWeights = $data['talentWeights'] ?? $this->talentWeights;
// 设置当前血量为最大血量
$stats = $this->getStats();
$this->hp = $data['hp'] ?? $stats['maxHp'];
} }
/** /**
@ -166,6 +171,7 @@ class Partner
/** /**
* 根据权重自动分配天赋点 * 根据权重自动分配天赋点
* 重要: HP生命值总是必须至少加1点
*/ */
private function autoAllocateTalent(int $points): void private function autoAllocateTalent(int $points): void
{ {
@ -184,6 +190,12 @@ class Partner
$remaining -= $share; $remaining -= $share;
} }
// 关键: 确保 HP 至少获得1点HP是必须的
if ($allocated['hp'] < 1) {
$allocated['hp'] = 1;
$remaining--;
}
// 剩余点数按权重优先分配 // 剩余点数按权重优先分配
$sortedTalents = $this->talentWeights; $sortedTalents = $this->talentWeights;
arsort($sortedTalents); arsort($sortedTalents);
@ -223,4 +235,18 @@ class Partner
{ {
return array_sum($this->talents); 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; // 返回实际恢复的血量
}
} }

View File

@ -12,7 +12,8 @@ use Game\Entities\Partner;
class Battle class Battle
{ {
public Player $player; public Player $player;
public Monster $monster; /** @var Monster[] */
public array $enemies = [];
/** @var array<string, int> 同伴当前HP */ /** @var array<string, int> 同伴当前HP */
private array $partnerHp = []; private array $partnerHp = [];
@ -35,7 +36,7 @@ class Battle
private string $reset = "\033[0m"; private string $reset = "\033[0m";
private int $round = 0; private int $round = 0;
private int $monsterMaxHp = 0; private int $totalMaxHp = 0;
public function __construct(public Game $game) public function __construct(public Game $game)
{ {
@ -49,8 +50,8 @@ class Battle
{ {
$this->partnerHp = []; $this->partnerHp = [];
foreach ($this->player->partners as $partner) { foreach ($this->player->partners as $partner) {
$stats = $partner->getStats(); // 从Partner对象的hp属性读取允许队友在战斗外也能恢复
$this->partnerHp[$partner->id] = $stats['maxHp']; $this->partnerHp[$partner->id] = $partner->hp;
} }
} }
@ -68,6 +69,31 @@ class Battle
return $alive; return $alive;
} }
/**
* 将战斗中的队友HP同步回Partner对象
*/
private function syncPartnerHp(): void
{
foreach ($this->player->partners as $partner) {
$partner->hp = $this->partnerHp[$partner->id] ?? 0;
}
}
/**
* 获取存活的敌人
* @return Monster[]
*/
private function getAliveEnemies(): array
{
$alive = [];
foreach ($this->enemies as $enemy) {
if ($enemy->hp > 0) {
$alive[] = $enemy;
}
}
return $alive;
}
public function start() public function start()
{ {
$out = $this->game->output; $out = $this->game->output;
@ -77,8 +103,15 @@ class Battle
while ($this->player->hp > 0) { while ($this->player->hp > 0) {
Screen::delay(500000); Screen::delay(500000);
$this->monster = Monster::create($this->game->dungeonId);
$this->monsterMaxHp = $this->monster->hp; // 创建敌人群组
$this->enemies = Monster::createGroup($this->game->dungeonId);
$this->totalMaxHp = 0;
foreach ($this->enemies as $enemy) {
$this->totalMaxHp += $enemy->hp;
}
$this->round = 0; $this->round = 0;
// 显示遭遇界面 // 显示遭遇界面
@ -88,7 +121,10 @@ class Battle
// 战斗循环 // 战斗循环
while (true) { while (true) {
if ($this->checkExit($out)) return; if ($this->checkExit($out)) {
$this->syncPartnerHp();
return;
}
$this->round++; $this->round++;
$this->renderBattleScreen($out, $playerFirst); $this->renderBattleScreen($out, $playerFirst);
@ -105,23 +141,31 @@ class Battle
Screen::delay(800000); Screen::delay(800000);
if ($result) break; if ($result) break;
if ($this->checkExit($out)) return; if ($this->checkExit($out)) {
$this->syncPartnerHp();
return;
}
// 怪物攻击 // 怪物攻击
if ($this->monsterAttack($out)) { if ($this->enemiesAttack($out)) {
Screen::delay(1000000); Screen::delay(1000000);
$this->syncPartnerHp();
return; return;
} }
Screen::delay(800000); Screen::delay(800000);
} else { } else {
// 怪物先攻 // 怪物先攻
if ($this->monsterAttack($out)) { if ($this->enemiesAttack($out)) {
Screen::delay(1000000); Screen::delay(1000000);
$this->syncPartnerHp();
return; return;
} }
Screen::delay(800000); Screen::delay(800000);
if ($this->checkExit($out)) return; if ($this->checkExit($out)) {
$this->syncPartnerHp();
return;
}
// 玩家攻击 // 玩家攻击
$result = $this->playerAttack($out); $result = $this->playerAttack($out);
@ -135,6 +179,8 @@ class Battle
} }
Screen::delay(500000); Screen::delay(500000);
// 同步队友HP到Partner对象然后保存状态
$this->syncPartnerHp();
$this->game->saveState(); $this->game->saveState();
} }
} }
@ -148,7 +194,11 @@ class Battle
$out->writeln(""); $out->writeln("");
$out->writeln(" {$this->red}⚔️ 遭遇敌人!{$this->reset}"); $out->writeln(" {$this->red}⚔️ 遭遇敌人!{$this->reset}");
$out->writeln(""); $out->writeln("");
$out->writeln(" {$this->bold}{$this->white}{$this->monster->name}{$this->reset} {$this->cyan}Lv.{$this->monster->level}{$this->reset}");
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("");
$out->writeln("{$this->yellow}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{$this->reset}"); $out->writeln("{$this->yellow}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{$this->reset}");
Screen::delay(1000000); // 1秒 Screen::delay(1000000); // 1秒
@ -163,12 +213,29 @@ class Battle
$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} {$this->bold}{$this->round} 回合{$this->reset} {$this->white}[q] 逃跑{$this->reset} {$this->cyan}{$this->reset}");
$out->writeln("{$this->cyan}╠══════════════════════════════════════════╣{$this->reset}"); $out->writeln("{$this->cyan}╠══════════════════════════════════════════╣{$this->reset}");
// 怪物信息 // 敌人信息
$monsterHpPercent = max(0, $this->monster->hp) / $this->monsterMaxHp; foreach ($this->enemies as $enemy) {
$monsterHpBar = $this->renderHpBar($monsterHpPercent, 20); if ($enemy->hp <= 0) {
$monsterHpText = max(0, $this->monster->hp) . "/" . $this->monsterMaxHp; $out->writeln("{$this->cyan}{$this->reset} {$this->red}💀{$this->reset} {$this->white}{$enemy->name}{$this->reset} {$this->red}[已击败]{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset} {$this->red}👹{$this->reset} {$this->bold}{$this->monster->name}{$this->reset} Lv.{$this->monster->level}"); continue;
$out->writeln("{$this->cyan}{$this->reset} {$monsterHpBar} {$this->white}{$monsterHpText}{$this->reset}"); }
$enemyHpPercent = max(0, $enemy->hp) / $enemy->baseHp; // 使用baseHp作为最大值近似或者应该在hydrate时保存maxHp
// 实际上Monster没有maxHp属性hp初始值就是最大值。但在战斗中hp会减少。
// 我们需要知道最大HP。Monster::create时hp=baseHp+equipHp。
// 简单起见假设当前hp <= 初始hp。如果需要精确显示条应该在Monster类加maxHp。
// 这里暂时用 $enemy->baseHp + equipHp 估算,或者直接存一个 maxHp。
// 为了简单,我们假设满血是初始状态。
// 更好的做法是Monster类加一个maxHp属性。
// 暂时用 $enemy->hp / $enemy->hp (如果满血) ... 不行。
// 让我们修改Monster类加maxHp? 或者这里不显示条,只显示数值?
// 或者我们假定 create 出来的 hp 就是 maxHp。
// $enemy->maxHp = $enemy->hp; // 在create里做最好。
// 这里先只显示数值吧,或者大概估算。
$hpText = max(0, $enemy->hp);
$out->writeln("{$this->cyan}{$this->reset} {$this->red}👹{$this->reset} {$this->bold}{$enemy->name}{$this->reset} Lv.{$enemy->level} HP: {$this->red}{$hpText}{$this->reset}");
}
$out->writeln("{$this->cyan}{$this->reset}"); $out->writeln("{$this->cyan}{$this->reset}");
// VS 分隔 // VS 分隔
@ -204,7 +271,7 @@ class Battle
private function renderHpBar(float $percent, int $width): string private function renderHpBar(float $percent, int $width): string
{ {
$filled = (int)($percent * $width); $filled = (int)($percent * $width);
$empty = $width - $filled; $empty = max($width - $filled,0);
// 根据血量百分比选择颜色 // 根据血量百分比选择颜色
if ($percent > 0.6) { if ($percent > 0.6) {
@ -221,7 +288,16 @@ class Battle
private function determineFirstStrike(): bool private function determineFirstStrike(): bool
{ {
$levelDiff = $this->player->level - $this->monster->level; // Use the leader's level for comparison
$leader = $this->enemies[count($this->enemies) - 1]; // Assume leader is last or first?
// In createGroup we put minions first, so leader is last.
// Let's just use the highest level enemy.
$maxLevel = 0;
foreach ($this->enemies as $e) {
if ($e->level > $maxLevel) $maxLevel = $e->level;
}
$levelDiff = $this->player->level - $maxLevel;
$playerChance = 50; $playerChance = 50;
$levelBonus = max(-30, min(30, $levelDiff * 5)); $levelBonus = max(-30, min(30, $levelDiff * 5));
$playerChance += $levelBonus; $playerChance += $levelBonus;
@ -233,9 +309,20 @@ class Battle
{ {
$stats = $this->player->getStats(); $stats = $this->player->getStats();
// Target first alive enemy
$target = null;
foreach ($this->enemies as $enemy) {
if ($enemy->hp > 0) {
$target = $enemy;
break;
}
}
if (!$target) return true; // All dead
// 计算物理伤害和魔法伤害 // 计算物理伤害和魔法伤害
$physicalDamage = max(1, $stats['patk'] - $this->monster->pdef); $physicalDamage = max(1, $stats['patk'] - $target->pdef);
$magicDamage = max(0, $stats['matk'] - $this->monster->mdef); $magicDamage = max(0, $stats['matk'] - $target->mdef);
$baseDamage = $physicalDamage + $magicDamage; $baseDamage = $physicalDamage + $magicDamage;
$critRate = $stats['crit']; $critRate = $stats['crit'];
@ -245,22 +332,27 @@ class Battle
if ($isCrit) { if ($isCrit) {
$damage = (int)($baseDamage * ($critDmg / 100)); $damage = (int)($baseDamage * ($critDmg / 100));
$out->writeln("{$this->cyan}{$this->reset} {$this->green}{$this->reset}发起攻击... {$this->red}{$this->bold}暴击!{$this->reset}"); $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}"); $out->writeln("{$this->cyan}{$this->reset} {$this->red}💥 造成 {$damage} 点伤害!{$this->reset}");
} else { } else {
$damage = $baseDamage; $damage = $baseDamage;
$out->writeln("{$this->cyan}{$this->reset} {$this->green}{$this->reset}发起攻击..."); $out->writeln("{$this->cyan}{$this->reset} {$this->green}{$this->reset}攻击 {$target->name}...");
$out->writeln("{$this->cyan}{$this->reset} {$this->white}⚔️ 造成 {$damage} 点伤害{$this->reset}"); $out->writeln("{$this->cyan}{$this->reset} {$this->white}⚔️ 造成 {$damage} 点伤害{$this->reset}");
} }
$this->monster->hp -= $damage; $target->hp -= $damage;
if ($this->monster->hp <= 0) { if ($target->hp <= 0) {
$this->monster->hp = 0; $target->hp = 0;
$out->writeln("{$this->cyan}{$this->reset} {$this->red}💀 {$target->name} 被击败了!{$this->reset}");
// Check if all enemies are dead
if (empty($this->getAliveEnemies())) {
Screen::delay(500000); Screen::delay(500000);
$this->showVictory($out, $stats); $this->showVictory($out, $stats);
return true; return true;
} }
}
return false; return false;
} }
@ -273,11 +365,22 @@ class Battle
$alivePartners = $this->getAlivePartners(); $alivePartners = $this->getAlivePartners();
foreach ($alivePartners as $partner) { foreach ($alivePartners as $partner) {
// Target first alive enemy
$target = null;
foreach ($this->enemies as $enemy) {
if ($enemy->hp > 0) {
$target = $enemy;
break;
}
}
if (!$target) return true; // All dead
$stats = $partner->getStats(); $stats = $partner->getStats();
// 计算物理伤害和魔法伤害 // 计算物理伤害和魔法伤害
$physicalDamage = max(1, $stats['patk'] - $this->monster->pdef); $physicalDamage = max(1, $stats['patk'] - $target->pdef);
$magicDamage = max(0, $stats['matk'] - $this->monster->mdef); $magicDamage = max(0, $stats['matk'] - $target->mdef);
$baseDamage = $physicalDamage + $magicDamage; $baseDamage = $physicalDamage + $magicDamage;
$critRate = $stats['crit']; $critRate = $stats['crit'];
@ -287,22 +390,26 @@ class Battle
if ($isCrit) { if ($isCrit) {
$damage = (int)($baseDamage * ($critDmg / 100)); $damage = (int)($baseDamage * ($critDmg / 100));
$out->writeln("{$this->cyan}{$this->reset} {$this->magenta}{$this->reset} {$partner->name} 发起攻击... {$this->red}{$this->bold}暴击!{$this->reset}"); $out->writeln("{$this->cyan}{$this->reset} {$this->magenta}{$this->reset} {$partner->name} 攻击 {$target->name}... {$this->red}{$this->bold}暴击!{$this->reset}");
$out->writeln("{$this->cyan}{$this->reset} {$this->red}💥 造成 {$damage} 点伤害!{$this->reset}"); $out->writeln("{$this->cyan}{$this->reset} {$this->red}💥 造成 {$damage} 点伤害!{$this->reset}");
} else { } else {
$damage = $baseDamage; $damage = $baseDamage;
$out->writeln("{$this->cyan}{$this->reset} {$this->magenta}{$this->reset} {$partner->name} 发起攻击..."); $out->writeln("{$this->cyan}{$this->reset} {$this->magenta}{$this->reset} {$partner->name} 攻击 {$target->name}...");
$out->writeln("{$this->cyan}{$this->reset} {$this->white}⚔️ 造成 {$damage} 点伤害{$this->reset}"); $out->writeln("{$this->cyan}{$this->reset} {$this->white}⚔️ 造成 {$damage} 点伤害{$this->reset}");
} }
$this->monster->hp -= $damage; $target->hp -= $damage;
if ($this->monster->hp <= 0) { if ($target->hp <= 0) {
$this->monster->hp = 0; $target->hp = 0;
$out->writeln("{$this->cyan}{$this->reset} {$this->red}💀 {$target->name} 被击败了!{$this->reset}");
if (empty($this->getAliveEnemies())) {
Screen::delay(500000); Screen::delay(500000);
$this->showVictory($out, $this->player->getStats()); $this->showVictory($out, $this->player->getStats());
return true; return true;
} }
}
Screen::delay(400000); // 每个同伴攻击间隔 Screen::delay(400000); // 每个同伴攻击间隔
} }
@ -310,8 +417,11 @@ class Battle
return false; return false;
} }
private function monsterAttack($out): bool private function enemiesAttack($out): bool
{ {
$aliveEnemies = $this->getAliveEnemies();
foreach ($aliveEnemies as $enemy) {
// 选择攻击目标:玩家或存活的同伴 // 选择攻击目标:玩家或存活的同伴
$alivePartners = $this->getAlivePartners(); $alivePartners = $this->getAlivePartners();
$targets = ['player']; $targets = ['player'];
@ -325,11 +435,11 @@ class Battle
if ($target === 'player') { if ($target === 'player') {
// 攻击玩家 // 攻击玩家
$playerStats = $this->player->getStats(); $playerStats = $this->player->getStats();
$physicalDamage = max(1, $this->monster->patk - $playerStats['pdef']); $physicalDamage = max(1, $enemy->patk - $playerStats['pdef']);
$magicDamage = max(0, $this->monster->matk - $playerStats['mdef']); $magicDamage = max(0, $enemy->matk - $playerStats['mdef']);
$damage = $physicalDamage + $magicDamage; $damage = $physicalDamage + $magicDamage;
$out->writeln("{$this->cyan}{$this->reset} {$this->red}{$this->reset} {$this->monster->name} 向你发起攻击..."); $out->writeln("{$this->cyan}{$this->reset} {$this->red}{$this->reset} {$enemy->name} 向你发起攻击...");
$out->writeln("{$this->cyan}{$this->reset} {$this->red}💢 你受到 {$damage} 点伤害{$this->reset}"); $out->writeln("{$this->cyan}{$this->reset} {$this->red}💢 你受到 {$damage} 点伤害{$this->reset}");
$this->player->hp -= $damage; $this->player->hp -= $damage;
@ -337,18 +447,18 @@ class Battle
if ($this->player->hp <= 0) { if ($this->player->hp <= 0) {
$this->player->hp = 0; $this->player->hp = 0;
Screen::delay(500000); Screen::delay(500000);
$this->showDefeat($out); $this->showDefeat($out, $enemy);
return true; return true;
} }
} else { } else {
// 攻击同伴 // 攻击同伴
$partner = $this->player->partners[$target]; $partner = $this->player->partners[$target];
$partnerStats = $partner->getStats(); $partnerStats = $partner->getStats();
$physicalDamage = max(1, $this->monster->patk - $partnerStats['pdef']); $physicalDamage = max(1, $enemy->patk - $partnerStats['pdef']);
$magicDamage = max(0, $this->monster->matk - $partnerStats['mdef']); $magicDamage = max(0, $enemy->matk - $partnerStats['mdef']);
$damage = $physicalDamage + $magicDamage; $damage = $physicalDamage + $magicDamage;
$out->writeln("{$this->cyan}{$this->reset} {$this->red}{$this->reset} {$this->monster->name}{$partner->name} 发起攻击..."); $out->writeln("{$this->cyan}{$this->reset} {$this->red}{$this->reset} {$enemy->name}{$partner->name} 发起攻击...");
$out->writeln("{$this->cyan}{$this->reset} {$this->red}💢 {$partner->name} 受到 {$damage} 点伤害{$this->reset}"); $out->writeln("{$this->cyan}{$this->reset} {$this->red}💢 {$partner->name} 受到 {$damage} 点伤害{$this->reset}");
$this->partnerHp[$target] -= $damage; $this->partnerHp[$target] -= $damage;
@ -358,6 +468,8 @@ class Battle
$out->writeln("{$this->cyan}{$this->reset} {$this->red}💀 {$partner->name} 倒下了!{$this->reset}"); $out->writeln("{$this->cyan}{$this->reset} {$this->red}💀 {$partner->name} 倒下了!{$this->reset}");
} }
} }
Screen::delay(400000);
}
return false; return false;
} }
@ -371,21 +483,45 @@ class Battle
$out->writeln("{$this->yellow}{$this->reset} {$this->green}{$this->bold}🎉 胜 利 🎉{$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} {$this->yellow}{$this->reset}");
$out->writeln("{$this->yellow}╠══════════════════════════════════════════╣{$this->reset}"); $out->writeln("{$this->yellow}╠══════════════════════════════════════════╣{$this->reset}");
$out->writeln("{$this->yellow}{$this->reset} 击败: {$this->white}{$this->monster->name}{$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->yellow}{$this->reset} 血量: {$this->green}{$this->player->hp}{$this->reset}/{$stats['maxHp']}");
// 汇总经验和灵石
$totalExp = 0;
$totalStones = 0;
$allDrops = [];
foreach ($this->enemies as $enemy) {
$totalExp += $enemy->expReward;
$totalStones += $enemy->spiritStoneReward;
// 掉落
foreach ($enemy->getEquippedItems() as $item) {
$this->player->addItem($item);
$allDrops[] = $item;
}
foreach ($enemy->dropTable as $drop) {
if (rand(1, 100) <= $drop['rate']) {
$this->player->addItem($drop['item']);
$allDrops[] = $drop['item'];
}
}
}
// 经验 // 经验
$exp = $this->monster->expReward;
$levelUpMsg = ""; $levelUpMsg = "";
if ($this->player->gainExp($exp)) { if ($this->player->gainExp($totalExp)) {
$levelUpMsg = " {$this->yellow}🎊 升级! Lv.{$this->player->level}{$this->reset}"; $levelUpMsg = " {$this->yellow}🎊 升级! Lv.{$this->player->level}{$this->reset}";
} }
$out->writeln("{$this->yellow}{$this->reset} 经验: {$this->cyan}+{$exp}{$this->reset}{$levelUpMsg}"); $out->writeln("{$this->yellow}{$this->reset} 经验: {$this->cyan}+{$totalExp}{$this->reset}{$levelUpMsg}");
// 同伴经验(存活的同伴获得经验) // 同伴经验
$alivePartners = $this->getAlivePartners(); $alivePartners = $this->getAlivePartners();
if (!empty($alivePartners)) { if (!empty($alivePartners)) {
$partnerExp = (int)($exp * 0.8); // 同伴获得80%经验 $partnerExp = (int)($totalExp * 0.8);
foreach ($alivePartners as $partner) { foreach ($alivePartners as $partner) {
$partnerLevelUp = ""; $partnerLevelUp = "";
if ($partner->gainExp($partnerExp)) { if ($partner->gainExp($partnerExp)) {
@ -396,32 +532,14 @@ class Battle
} }
// 灵石 // 灵石
$spiritStones = $this->monster->spiritStoneReward; if ($totalStones > 0) {
if ($spiritStones > 0) { $this->player->addSpiritStones($totalStones);
$this->player->addSpiritStones($spiritStones); $out->writeln("{$this->yellow}{$this->reset} 灵石: {$this->yellow}+{$totalStones}{$this->reset}");
$out->writeln("{$this->yellow}{$this->reset} 灵石: {$this->yellow}+{$spiritStones}{$this->reset}");
} }
// 掉落怪物装备 if (!empty($allDrops)) {
$drops = [];
// 1. 掉落怪物的装备100%掉落)
foreach ($this->monster->getEquippedItems() as $item) {
$this->player->addItem($item);
$drops[] = $item;
}
// 2. 消耗品按概率掉落
foreach ($this->monster->dropTable as $drop) {
if (rand(1, 100) <= $drop['rate']) {
$this->player->addItem($drop['item']);
$drops[] = $drop['item'];
}
}
if (!empty($drops)) {
$out->writeln("{$this->yellow}{$this->reset} {$this->white}掉落:{$this->reset}"); $out->writeln("{$this->yellow}{$this->reset} {$this->white}掉落:{$this->reset}");
foreach ($drops as $item) { foreach ($allDrops as $item) {
$out->writeln("{$this->yellow}{$this->reset} " . ItemDisplay::renderDrop($item, "")); $out->writeln("{$this->yellow}{$this->reset} " . ItemDisplay::renderDrop($item, ""));
} }
} }
@ -430,19 +548,21 @@ class Battle
$out->writeln(""); $out->writeln("");
$this->game->saveState(); $this->game->saveState();
Screen::delay(1500000); // 1.5秒,让玩家看清战利品 Screen::delay(1500000);
} }
private function showDefeat($out) private function showDefeat($out, ?Monster $killer = null)
{ {
Screen::clear($out); Screen::clear($out);
$killerName = $killer ? $killer->name : "敌人";
$out->writeln(""); $out->writeln("");
$out->writeln("{$this->red}╔══════════════════════════════════════════╗{$this->reset}"); $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->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->bold}💀 战 败 💀{$this->reset} {$this->red}{$this->reset}");
$out->writeln("{$this->red}{$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}");
$out->writeln("{$this->red}{$this->reset} 你被 {$this->white}{$this->monster->name}{$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}");
$out->writeln("{$this->red}{$this->reset} {$this->white}不要气馁,休整后再战!{$this->reset}"); $out->writeln("{$this->red}{$this->reset} {$this->white}不要气馁,休整后再战!{$this->reset}");
$out->writeln("{$this->red}╚══════════════════════════════════════════╝{$this->reset}"); $out->writeln("{$this->red}╚══════════════════════════════════════════╝{$this->reset}");

View File

@ -186,16 +186,66 @@ class InventoryPanel
$stats = $player->getStats(); $stats = $player->getStats();
$maxHp = $stats['maxHp']; $maxHp = $stats['maxHp'];
// 检查是否已满血 // 检查玩家血量
if ($player->hp >= $maxHp) { $playerNeedsHeal = $player->hp < $maxHp;
$out->writeln("你的生命值已满,无需使用!");
// 检查队友血量
$partnersNeedHeal = [];
foreach ($player->partners as $partner) {
$partnerStats = $partner->getStats();
$partnerMaxHp = $partnerStats['maxHp'];
if ($partner->hp < $partnerMaxHp) {
$partnersNeedHeal[] = $partner;
}
}
// 如果都不需要恢复
if (!$playerNeedsHeal && empty($partnersNeedHeal)) {
$out->writeln("你和队友的生命值都已满,无需使用!");
Screen::sleep(1); Screen::sleep(1);
return; return;
} }
// 使用 heal 方法恢复生命,不超过上限 // 选择恢复目标
$target = null;
if ($playerNeedsHeal && !empty($partnersNeedHeal)) {
// 需要询问玩家选择
$out->writeln("");
$out->writeln("选择恢复目标:");
$out->writeln("[1] 恢复自己");
foreach ($partnersNeedHeal as $i => $partner) {
$out->writeln("[" . ($i + 2) . "] 恢复 {$partner->name}");
}
$choice = Screen::input($out, "选择:");
$choiceInt = (int)$choice;
if ($choiceInt === 1) {
$target = 'player';
} elseif ($choiceInt >= 2 && $choiceInt - 2 < count($partnersNeedHeal)) {
$target = $partnersNeedHeal[$choiceInt - 2];
} else {
$out->writeln("无效选择");
Screen::sleep(1);
return;
}
} elseif ($playerNeedsHeal) {
$target = 'player';
} elseif (!empty($partnersNeedHeal)) {
$target = $partnersNeedHeal[0];
}
// 使用消耗品进行恢复
if ($target === 'player') {
$actualHeal = $player->heal($item['heal']); $actualHeal = $player->heal($item['heal']);
$out->writeln("你使用了 {$item['name']},恢复了 {$actualHeal} HP(当前: {$player->hp}/{$maxHp})"); $out->writeln("你使用了 {$item['name']},恢复了 {$actualHeal} HP(当前: {$player->hp}/{$maxHp})");
} else {
// 恢复队友
$partnerStats = $target->getStats();
$partnerMaxHp = $partnerStats['maxHp'];
$actualHeal = $target->heal($item['heal']);
$out->writeln("你使用了 {$item['name']} 来恢复 {$target->name},恢复了 {$actualHeal} HP(当前: {$target->hp}/{$partnerMaxHp})");
}
// Decrease quantity or remove // Decrease quantity or remove
if (($player->inventory[$index]['quantity'] ?? 1) > 1) { if (($player->inventory[$index]['quantity'] ?? 1) > 1) {

View File

@ -63,11 +63,11 @@ class TalentPanel
$this->game->output->writeln("{$this->cyan}{$this->reset} 暴击: {$this->yellow}{$stats['crit']}%{$this->reset} 暴伤: {$this->yellow}{$stats['critdmg']}%{$this->reset}"); $this->game->output->writeln("{$this->cyan}{$this->reset} 暴击: {$this->yellow}{$stats['crit']}%{$this->reset} 暴伤: {$this->yellow}{$stats['critdmg']}%{$this->reset}");
$this->game->output->writeln("{$this->cyan}╚════════════════════════════════════════╝{$this->reset}"); $this->game->output->writeln("{$this->cyan}╚════════════════════════════════════════╝{$this->reset}");
$this->game->output->writeln(""); $this->game->output->writeln("");
$this->game->output->writeln("[1-5] 加点 | [r] 重置天赋 | [99] 返回"); $this->game->output->writeln("[1-5] 加点 | [r] 重置天赋 | [0] 返回");
$choice = Input::ask($this->game->output, "请选择: "); $choice = Input::ask($this->game->output, "请选择: ");
if ($choice == 99) { if ($choice == 0) {
$this->game->state = Game::MENU; $this->game->state = Game::MENU;
return; return;
} }