添加怪物群
This commit is contained in:
parent
0c6cbd669a
commit
b4ec385827
|
|
@ -97,7 +97,7 @@ class Game
|
|||
}
|
||||
|
||||
$this->dungeonId = $data['dungeonId'] ?? $this->dungeonId;
|
||||
$this->state = $data['state'] ?? $this->state;
|
||||
$this->state = self::MENU;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,8 +34,10 @@ class ItemDisplay
|
|||
|
||||
// 属性名称
|
||||
private static array $statNames = [
|
||||
'atk' => '攻击',
|
||||
'def' => '防御',
|
||||
'patk' => '物攻',
|
||||
'matk' => '魔攻',
|
||||
'pdef' => '物防',
|
||||
'mdef' => '魔防',
|
||||
'hp' => '生命',
|
||||
'crit' => '暴击',
|
||||
'critdmg' => '暴伤',
|
||||
|
|
@ -296,7 +298,7 @@ class ItemDisplay
|
|||
|
||||
// 简要属性
|
||||
$stats = [];
|
||||
foreach (['atk', 'def', 'hp'] as $key) {
|
||||
foreach (['patk', 'matk', 'pdef', 'mdef', 'hp'] as $key) {
|
||||
$value = $item[$key] ?? 0;
|
||||
if ($value > 0) {
|
||||
$statName = self::$statNames[$key] ?? $key;
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@
|
|||
return [
|
||||
// 词条名称定义
|
||||
'affix_definitions' => [
|
||||
'atk' => '攻击',
|
||||
'def' => '防御',
|
||||
'patk' => '物攻',
|
||||
'matk' => '魔攻',
|
||||
'pdef' => '物防',
|
||||
'mdef' => '魔防',
|
||||
'hp' => '生命值',
|
||||
'crit' => '暴击率',
|
||||
'critdmg' => '暴击伤害',
|
||||
|
|
@ -20,11 +22,52 @@ return [
|
|||
'weapon' => [
|
||||
'names' => ['铁剑', '玄铁剑', '青钢剑', '寒冰剑', '烈焰刀', '雷霆锤'],
|
||||
'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' => [
|
||||
'crit' => ['weight' => 50, 'base' => [1, 3, 5, 10], 'growth' => 0.3],
|
||||
'critdmg' => ['weight' => 50, 'base' => [3, 8, 14, 24], 'growth' => 0.7],
|
||||
'crit' => ['weight' => 40, 'base' => [1, 3, 5, 10], 'growth' => 0.3],
|
||||
'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' => [
|
||||
'common' => [0, 0],
|
||||
|
|
@ -33,18 +76,63 @@ return [
|
|||
'legendary' => [1, 2],
|
||||
],
|
||||
'affix_weights' => [
|
||||
'atk' => 40,
|
||||
'crit' => 30,
|
||||
'critdmg' => 30,
|
||||
'hp' => 15,
|
||||
'def' => 10,
|
||||
'patk' => 30,
|
||||
'matk' => 20,
|
||||
'crit' => 25,
|
||||
'critdmg' => 25,
|
||||
'hp' => 10,
|
||||
],
|
||||
],
|
||||
|
||||
'armor' => [
|
||||
'names' => ['布衣', '皮甲', '铁甲', '精钢甲', '玄武甲', '龙鳞甲'],
|
||||
'names' => ['布衣', '皮甲', '铁甲', '精钢甲', '玄武甲', '龙鳞甲', '法袍', '灵纹袍'],
|
||||
'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' => [
|
||||
'hp' => ['weight' => 100, 'base' => [10, 25, 45, 75], 'growth' => 3.5],
|
||||
|
|
@ -56,18 +144,19 @@ return [
|
|||
'legendary' => [1, 1],
|
||||
],
|
||||
'affix_weights' => [
|
||||
'def' => 40,
|
||||
'pdef' => 30,
|
||||
'mdef' => 30,
|
||||
'hp' => 40,
|
||||
'atk' => 10,
|
||||
'crit' => 15,
|
||||
'critdmg' => 15,
|
||||
'patk' => 5,
|
||||
'matk' => 5,
|
||||
],
|
||||
],
|
||||
|
||||
'boots' => [
|
||||
'names' => ['布鞋', '皮靴', '铁靴', '疾风靴', '幽步靴', '龙鳞靴'],
|
||||
'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' => [
|
||||
'hp' => ['weight' => 60, 'base' => [8, 20, 35, 60], 'growth' => 2.5],
|
||||
|
|
@ -80,10 +169,10 @@ return [
|
|||
'legendary' => [1, 2],
|
||||
],
|
||||
'affix_weights' => [
|
||||
'def' => 35,
|
||||
'hp' => 35,
|
||||
'pdef' => 25,
|
||||
'mdef' => 25,
|
||||
'hp' => 30,
|
||||
'crit' => 20,
|
||||
'critdmg' => 10,
|
||||
],
|
||||
],
|
||||
|
||||
|
|
@ -93,8 +182,9 @@ return [
|
|||
'crit' => ['base' => [2, 5, 8, 12], 'growth' => 0.4],
|
||||
],
|
||||
'random_primary_pool' => [
|
||||
'critdmg' => ['weight' => 60, 'base' => [5, 12, 20, 35], 'growth' => 0.8],
|
||||
'atk' => ['weight' => 40, 'base' => [3, 8, 15, 25], 'growth' => 1.0],
|
||||
'critdmg' => ['weight' => 40, 'base' => [5, 12, 20, 35], 'growth' => 0.8],
|
||||
'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' => [
|
||||
'common' => [0, 0],
|
||||
|
|
@ -103,10 +193,10 @@ return [
|
|||
'legendary' => [1, 2],
|
||||
],
|
||||
'affix_weights' => [
|
||||
'crit' => 35,
|
||||
'critdmg' => 35,
|
||||
'atk' => 20,
|
||||
'hp' => 10,
|
||||
'crit' => 30,
|
||||
'critdmg' => 30,
|
||||
'patk' => 20,
|
||||
'matk' => 20,
|
||||
],
|
||||
],
|
||||
|
||||
|
|
@ -116,7 +206,8 @@ return [
|
|||
'hp' => ['base' => [15, 35, 60, 100], 'growth' => 4.0],
|
||||
],
|
||||
'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],
|
||||
],
|
||||
'random_primary_count' => [
|
||||
|
|
@ -126,10 +217,11 @@ return [
|
|||
'legendary' => [1, 2],
|
||||
],
|
||||
'affix_weights' => [
|
||||
'hp' => 40,
|
||||
'def' => 30,
|
||||
'atk' => 15,
|
||||
'hp' => 30,
|
||||
'pdef' => 20,
|
||||
'mdef' => 20,
|
||||
'crit' => 15,
|
||||
'critdmg' => 15,
|
||||
],
|
||||
],
|
||||
|
||||
|
|
|
|||
|
|
@ -13,21 +13,24 @@
|
|||
// ========== 通用装备模板 ==========
|
||||
$weaponTemplate = [
|
||||
'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' => [
|
||||
'crit' => ['weight' => 50, 'base' => [1, 3, 5, 10], 'growth' => 0.3],
|
||||
'critdmg' => ['weight' => 50, 'base' => [3, 8, 14, 24], 'growth' => 0.7],
|
||||
'crit' => ['weight' => 40, 'base' => [1, 3, 5, 10], 'growth' => 0.3],
|
||||
'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' => [
|
||||
'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 = [
|
||||
'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' => [
|
||||
'hp' => ['weight' => 100, 'base' => [10, 25, 45, 75], 'growth' => 3.5],
|
||||
|
|
@ -35,7 +38,7 @@ $armorTemplate = [
|
|||
'random_primary_count' => [
|
||||
'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 = [
|
||||
|
|
@ -43,13 +46,14 @@ $ringTemplate = [
|
|||
'crit' => ['base' => [2, 5, 8, 12], 'growth' => 0.4],
|
||||
],
|
||||
'random_primary_pool' => [
|
||||
'critdmg' => ['weight' => 60, 'base' => [5, 12, 20, 35], 'growth' => 0.8],
|
||||
'atk' => ['weight' => 40, 'base' => [3, 8, 15, 25], 'growth' => 1.0],
|
||||
'critdmg' => ['weight' => 40, 'base' => [5, 12, 20, 35], 'growth' => 0.8],
|
||||
'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' => [
|
||||
'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 = [
|
||||
|
|
@ -57,18 +61,20 @@ $necklaceTemplate = [
|
|||
'hp' => ['base' => [15, 35, 60, 100], 'growth' => 4.0],
|
||||
],
|
||||
'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],
|
||||
],
|
||||
'random_primary_count' => [
|
||||
'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 = [
|
||||
'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' => [
|
||||
'hp' => ['weight' => 60, 'base' => [8, 20, 35, 60], 'growth' => 2.5],
|
||||
|
|
@ -77,7 +83,7 @@ $bootsTemplate = [
|
|||
'random_primary_count' => [
|
||||
'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 [
|
||||
|
|
@ -119,6 +125,9 @@ return [
|
|||
['type' => 'armor', 'name' => '皮甲', 'rate' => 10] + $armorTemplate,
|
||||
['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,
|
||||
],
|
||||
[
|
||||
|
|
@ -132,10 +141,13 @@ return [
|
|||
'exp' => 100,
|
||||
'spirit_stones' => 20,
|
||||
'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' => 'consume', 'name' => '清灵散', 'rate' => 40, 'heal' => 80],
|
||||
],
|
||||
'minions' => [
|
||||
['name' => '铁奴', 'hp' => 80, 'patk' => 12, 'pdef' => 8, 'exp' => 30, 'count' => 1],
|
||||
],
|
||||
'weight' => 10,
|
||||
],
|
||||
],
|
||||
|
|
@ -188,7 +200,7 @@ return [
|
|||
'exp' => 150,
|
||||
'spirit_stones' => 40,
|
||||
'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' => 'consume', 'name' => '合气丹', 'rate' => 30, 'heal' => 100],
|
||||
],
|
||||
|
|
@ -243,8 +255,8 @@ return [
|
|||
'exp' => 300,
|
||||
'spirit_stones' => 100,
|
||||
'drops' => [
|
||||
['type' => 'weapon', 'name' => '金竺笔', 'quality' => 'epic', 'atk' => 45, 'rate' => 15, 'affixes' => ['atk' => 10]],
|
||||
['type' => 'armor', 'name' => '墨蛟甲', 'quality' => 'epic', 'def' => 30, 'rate' => 15],
|
||||
['type' => 'weapon', 'name' => '金竺笔', 'quality' => 'epic', 'matk' => 45, 'rate' => 15, 'affixes' => ['matk' => 10]],
|
||||
['type' => 'armor', 'name' => '墨蛟甲', 'quality' => 'epic', 'pdef' => 20, 'mdef' => 15, 'rate' => 15],
|
||||
['type' => 'consume', 'name' => '筑基丹', 'rate' => 50, 'heal' => 500],
|
||||
],
|
||||
'weight' => 15,
|
||||
|
|
@ -302,7 +314,7 @@ return [
|
|||
'exp' => 400,
|
||||
'spirit_stones' => 150,
|
||||
'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' => 'consume', 'name' => '定颜丹', 'rate' => 10, 'heal' => 800],
|
||||
],
|
||||
|
|
@ -358,8 +370,8 @@ return [
|
|||
'exp' => 600,
|
||||
'spirit_stones' => 250,
|
||||
'drops' => [
|
||||
['type' => 'weapon', 'name' => '血灵钻', 'quality' => 'epic', 'atk' => 100, 'rate' => 20, 'affixes' => ['crit' => 10]],
|
||||
['type' => 'armor', 'name' => '血灵甲', 'quality' => 'epic', 'def' => 60, 'rate' => 20],
|
||||
['type' => 'weapon', 'name' => '血灵钻', 'quality' => 'epic', 'matk' => 100, 'rate' => 20, 'affixes' => ['crit' => 10]],
|
||||
['type' => 'armor', 'name' => '血灵甲', 'quality' => 'epic', 'pdef' => 40, 'mdef' => 30, 'rate' => 20],
|
||||
['type' => 'consume', 'name' => '血灵丹', 'rate' => 30, 'heal' => 1000],
|
||||
],
|
||||
'weight' => 15,
|
||||
|
|
@ -414,7 +426,7 @@ return [
|
|||
'exp' => 1000,
|
||||
'spirit_stones' => 400,
|
||||
'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' => 'ring', 'name' => '黑煞戒', 'rate' => 20] + $ringTemplate,
|
||||
],
|
||||
|
|
@ -473,7 +485,7 @@ return [
|
|||
'exp' => 1500,
|
||||
'spirit_stones' => 600,
|
||||
'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' => 'consume', 'name' => '降尘丹', 'rate' => 25, 'heal' => 1500],
|
||||
],
|
||||
|
|
@ -529,7 +541,7 @@ return [
|
|||
'exp' => 2500,
|
||||
'spirit_stones' => 1000,
|
||||
'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' => 'necklace', 'name' => '虚天鼎', 'quality' => 'legendary', 'hp' => 2000, 'rate' => 5],
|
||||
],
|
||||
|
|
@ -585,8 +597,8 @@ return [
|
|||
'exp' => 4000,
|
||||
'spirit_stones' => 1500,
|
||||
'drops' => [
|
||||
['type' => 'weapon', 'name' => '金蛟剪', 'quality' => 'legendary', 'atk' => 450, 'rate' => 15, 'affixes' => ['crit' => 15]],
|
||||
['type' => 'armor', 'name' => '金蛟鳞甲', 'quality' => 'legendary', 'def' => 250, 'rate' => 15],
|
||||
['type' => 'weapon', 'name' => '金蛟剪', 'quality' => 'legendary', 'patk' => 300, 'matk' => 200, 'rate' => 15, 'affixes' => ['crit' => 15]],
|
||||
['type' => 'armor', 'name' => '金蛟鳞甲', 'quality' => 'legendary', 'pdef' => 180, 'mdef' => 120, 'rate' => 15],
|
||||
['type' => 'consume', 'name' => '九曲灵参', 'rate' => 10, 'heal' => 5000],
|
||||
],
|
||||
'weight' => 15,
|
||||
|
|
@ -644,7 +656,7 @@ return [
|
|||
'exp' => 5000,
|
||||
'spirit_stones' => 2000,
|
||||
'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' => 'consume', 'name' => '培婴丹', 'rate' => 25, 'heal' => 3000],
|
||||
],
|
||||
|
|
@ -700,8 +712,8 @@ return [
|
|||
'exp' => 10000,
|
||||
'spirit_stones' => 5000,
|
||||
'drops' => [
|
||||
['type' => 'weapon', 'name' => '黑风旗', 'quality' => 'legendary', 'atk' => 800, 'rate' => 15, 'affixes' => ['atk' => 20]],
|
||||
['type' => 'armor', 'name' => '魔龙甲', 'quality' => 'legendary', 'def' => 500, 'rate' => 15],
|
||||
['type' => 'weapon', 'name' => '黑风旗', 'quality' => 'legendary', 'matk' => 800, 'rate' => 15, 'affixes' => ['matk' => 20]],
|
||||
['type' => 'armor', 'name' => '魔龙甲', 'quality' => 'legendary', 'pdef' => 350, 'mdef' => 250, 'rate' => 15],
|
||||
['type' => 'consume', 'name' => '万年灵乳', 'rate' => 20, 'heal' => 8000],
|
||||
],
|
||||
'weight' => 15,
|
||||
|
|
@ -759,7 +771,7 @@ return [
|
|||
'exp' => 15000,
|
||||
'spirit_stones' => 8000,
|
||||
'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' => 'consume', 'name' => '回阳水', 'rate' => 10, 'heal' => 10000],
|
||||
],
|
||||
|
|
@ -815,8 +827,8 @@ return [
|
|||
'exp' => 30000,
|
||||
'spirit_stones' => 15000,
|
||||
'drops' => [
|
||||
['type' => 'weapon', 'name' => '青竹蜂云剑', 'quality' => 'legendary', 'atk' => 2000, 'rate' => 20, 'affixes' => ['atk' => 30, 'crit' => 20]],
|
||||
['type' => 'armor', 'name' => '五行甲', 'quality' => 'legendary', 'def' => 1500, 'rate' => 20],
|
||||
['type' => 'weapon', 'name' => '青竹蜂云剑', 'quality' => 'legendary', 'patk' => 1500, 'matk' => 1000, 'rate' => 20, 'affixes' => ['patk' => 30, 'crit' => 20]],
|
||||
['type' => 'armor', 'name' => '五行甲', 'quality' => 'legendary', 'pdef' => 1000, 'mdef' => 1000, 'rate' => 20],
|
||||
['type' => 'consume', 'name' => '飞升令', 'rate' => 100, 'heal' => 99999], // 象征性物品
|
||||
],
|
||||
'weight' => 15,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ return [
|
|||
'min_level' => 1,
|
||||
'desc' => '韩立在七玄门最好的朋友,虽无灵根但武功高强。',
|
||||
'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' => [
|
||||
|
|
|
|||
|
|
@ -3,9 +3,6 @@ namespace Game\Entities;
|
|||
|
||||
/**
|
||||
* 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
|
||||
{
|
||||
|
|
@ -15,12 +12,19 @@ class Item
|
|||
public int $level = 1; // Item level
|
||||
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.
|
||||
* @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
|
||||
{
|
||||
|
|
@ -30,323 +34,7 @@ class Item
|
|||
$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 = [
|
||||
'common' => ['affixes' => 0, 'index' => 0],
|
||||
'rare' => ['affixes' => 1, 'index' => 1],
|
||||
|
|
@ -357,108 +45,114 @@ class Item
|
|||
// 随机品质
|
||||
$roll = rand(1, 100);
|
||||
if ($roll <= 70) $quality = 'common';
|
||||
elseif ($roll <= 90) $quality = 'rare';
|
||||
elseif ($roll <= 98) $quality = 'epic';
|
||||
else $quality = 'legendary';
|
||||
elseif ($roll <= 90) $quality = 'rare';
|
||||
elseif ($roll <= 98) $quality = 'epic';
|
||||
else $quality = 'legendary';
|
||||
|
||||
// 如果配置指定了品质,则使用指定品质
|
||||
if (isset($spec['quality'])) {
|
||||
$quality = $spec['quality'];
|
||||
$item = new self();
|
||||
$item->type = $type;
|
||||
$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'];
|
||||
$affixCount = $qualityConfig[$quality]['affixes'];
|
||||
$names = $typeConfig['names'] ?? ['未知物品'];
|
||||
|
||||
if ($specificName !== null && in_array($specificName, $names)) {
|
||||
$item->name = $specificName;
|
||||
} else {
|
||||
$item->name = $names[array_rand($names)];
|
||||
}
|
||||
|
||||
// 初始化属性
|
||||
$item = [
|
||||
'name' => $name,
|
||||
'type' => $type,
|
||||
'quality' => $quality,
|
||||
'level' => $itemLevel,
|
||||
'atk' => 0,
|
||||
'def' => 0,
|
||||
'hp' => 0,
|
||||
'crit' => 0,
|
||||
'critdmg' => 0,
|
||||
'heal' => 0,
|
||||
'affixes' => [],
|
||||
'desc' => "Lv.{$itemLevel} {$quality}品质",
|
||||
];
|
||||
$item->patk = 0;
|
||||
$item->matk = 0;
|
||||
$item->pdef = 0;
|
||||
$item->mdef = 0;
|
||||
$item->hp = 0;
|
||||
$item->crit = 0;
|
||||
$item->critdmg = 0;
|
||||
|
||||
// ===== 处理固定主属性 =====
|
||||
$fixedPrimary = $spec['fixed_primary'] ?? [];
|
||||
foreach ($fixedPrimary as $statKey => $statConfig) {
|
||||
$baseValues = $statConfig['base'] ?? [0, 0, 0, 0];
|
||||
$growth = $statConfig['growth'] ?? 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 {
|
||||
// 检查是否有特定物品配置
|
||||
$specificConfig = $typeConfig['specific_config'][$item->name] ?? [];
|
||||
|
||||
// 合并配置:优先使用特定配置,否则使用默认配置
|
||||
$fixedPrimaries = $specificConfig['fixed_primary'] ?? $typeConfig['fixed_primary'] ?? [];
|
||||
$randomPool = $specificConfig['random_primary_pool'] ?? $typeConfig['random_primary_pool'] ?? [];
|
||||
$randomCountConfig = $typeConfig['random_primary_count'][$quality] ?? [0, 0];
|
||||
|
||||
$baseValue = $baseValues[$qualityIndex] ?? $baseValues[0];
|
||||
$randomBonus = rand(0, max(1, (int)($baseValue * 0.15)));
|
||||
$finalValue = (int)($baseValue + ($itemLevel * $growth) + $randomBonus);
|
||||
// 1. 固定主属性
|
||||
foreach ($fixedPrimaries as $statKey => $statConfig) {
|
||||
$baseValue = $statConfig['base'][$qualityIndex];
|
||||
$growth = $statConfig['growth'];
|
||||
$randomBonus = rand(0, max(1, (int)($baseValue * 0.15)));
|
||||
$finalValue = (int)($baseValue + ($level * $growth) + $randomBonus);
|
||||
$item->$statKey = $finalValue;
|
||||
}
|
||||
|
||||
$item[$statKey] = $finalValue;
|
||||
}
|
||||
// 2. 随机主属性
|
||||
if (!empty($randomPool)) {
|
||||
[$minCount, $maxCount] = $randomCountConfig;
|
||||
$randomCount = rand($minCount, $maxCount);
|
||||
|
||||
// ===== 处理随机主属性 =====
|
||||
$randomPool = $spec['random_primary_pool'] ?? [];
|
||||
$randomCountConfig = $spec['random_primary_count'][$quality] ?? [0, 0];
|
||||
if ($randomCount > 0) {
|
||||
$selectedRandoms = [];
|
||||
$availableStats = $randomPool;
|
||||
|
||||
if (!empty($randomPool)) {
|
||||
[$minCount, $maxCount] = $randomCountConfig;
|
||||
$randomCount = rand($minCount, $maxCount);
|
||||
|
||||
if ($randomCount > 0) {
|
||||
$availableStats = $randomPool;
|
||||
$selectedRandoms = [];
|
||||
|
||||
for ($i = 0; $i < $randomCount && !empty($availableStats); $i++) {
|
||||
$weights = [];
|
||||
foreach ($availableStats as $statKey => $statConfig) {
|
||||
$weights[$statKey] = $statConfig['weight'] ?? 50;
|
||||
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]);
|
||||
}
|
||||
|
||||
$selectedStat = self::weightedRandom($weights);
|
||||
$selectedRandoms[] = $selectedStat;
|
||||
unset($availableStats[$selectedStat]);
|
||||
}
|
||||
|
||||
foreach ($selectedRandoms as $statKey) {
|
||||
$statConfig = $randomPool[$statKey];
|
||||
$baseValues = $statConfig['base'] ?? [0, 0, 0, 0];
|
||||
$growth = $statConfig['growth'] ?? 0;
|
||||
|
||||
$baseValue = $baseValues[$qualityIndex] ?? $baseValues[0];
|
||||
$randomBonus = rand(0, max(1, (int)($baseValue * 0.15)));
|
||||
$finalValue = (int)($baseValue + ($itemLevel * $growth) + $randomBonus);
|
||||
|
||||
$item[$statKey] = ($item[$statKey] ?? 0) + $finalValue;
|
||||
foreach ($selectedRandoms as $statKey) {
|
||||
$statConfig = $randomPool[$statKey];
|
||||
$baseValue = $statConfig['base'][$qualityIndex];
|
||||
$growth = $statConfig['growth'];
|
||||
$randomBonus = rand(0, max(1, (int)($baseValue * 0.15)));
|
||||
$finalValue = (int)($baseValue + ($level * $growth) + $randomBonus);
|
||||
$item->$statKey += $finalValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$item->desc = "Lv.{$level} {$quality}品质的" . match($type) {
|
||||
'weapon' => '武器',
|
||||
'armor' => '防具',
|
||||
'boots' => '鞋子',
|
||||
'ring' => '戒指',
|
||||
'necklace' => '项链',
|
||||
default => '装备'
|
||||
};
|
||||
}
|
||||
|
||||
// ===== 生成词条 =====
|
||||
static $data = null;
|
||||
if ($data === null) {
|
||||
$data = require __DIR__ . '/../../src/Data/items.php';
|
||||
}
|
||||
|
||||
$affixNames = $data['affix_definitions'] ?? [
|
||||
'atk' => '攻击',
|
||||
'def' => '防御',
|
||||
'hp' => '生命值',
|
||||
'crit' => '暴击率',
|
||||
'critdmg' => '暴击伤害',
|
||||
];
|
||||
|
||||
$affixWeights = $spec['affix_weights'] ?? [
|
||||
'atk' => 25,
|
||||
'def' => 25,
|
||||
'hp' => 25,
|
||||
'crit' => 15,
|
||||
'critdmg' => 10,
|
||||
];
|
||||
// 3. 词条
|
||||
$affixNames = $data['affix_definitions'];
|
||||
$affixWeights = $typeConfig['affix_weights'] ?? [];
|
||||
$item->affixes = [];
|
||||
|
||||
$selectedAffixes = [];
|
||||
for ($i = 0; $i < $affixCount; $i++) {
|
||||
if ($type == 'consume') break;
|
||||
if (empty($affixWeights)) break;
|
||||
|
||||
$availableWeights = array_diff_key($affixWeights, array_flip($selectedAffixes));
|
||||
|
|
@ -471,8 +165,8 @@ class Item
|
|||
|
||||
if ($usePercent) {
|
||||
$base = rand(2, 8);
|
||||
$v = $base + floor($itemLevel / 3);
|
||||
$item['affixes'][] = "{$affixNames[$key]} +{$v}%";
|
||||
$v = $base + floor($level / 3);
|
||||
$item->affixes[] = "{$affixNames[$key]} +{$v}%";
|
||||
} else {
|
||||
$qualityMultiplier = match($quality) {
|
||||
'legendary' => 2.0,
|
||||
|
|
@ -483,76 +177,109 @@ class Item
|
|||
|
||||
$base = rand(2, 8);
|
||||
$multiplier = match($key) {
|
||||
'atk' => 1.5,
|
||||
'def' => 1.0,
|
||||
'patk' => 1.5,
|
||||
'matk' => 1.5,
|
||||
'pdef' => 1.0,
|
||||
'mdef' => 1.0,
|
||||
'hp' => 8,
|
||||
'crit' => 0.5,
|
||||
'critdmg' => 0.8,
|
||||
default => 1
|
||||
};
|
||||
$v = floor(($base + ($itemLevel * $multiplier)) * $qualityMultiplier);
|
||||
$item['affixes'][] = "{$affixNames[$key]} +{$v}";
|
||||
$v = floor(($base + ($level * $multiplier)) * $qualityMultiplier);
|
||||
$item->affixes[] = "{$affixNames[$key]} +{$v}";
|
||||
}
|
||||
}
|
||||
|
||||
// 设置描述
|
||||
$item['desc'] = "Lv.{$itemLevel} {$quality}品质的" . match($type) {
|
||||
'weapon' => '武器',
|
||||
'armor' => '防具',
|
||||
'boots' => '鞋子',
|
||||
'ring' => '戒指',
|
||||
'necklace' => '项链',
|
||||
default => '装备'
|
||||
};
|
||||
return $item->toArray();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算物品的售价(灵石)
|
||||
* 基于品质、等级、属性数量计算
|
||||
*/
|
||||
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
|
||||
{
|
||||
// 消耗品售价低
|
||||
if ($item['type'] === 'consume') {
|
||||
$basePrice = match($item['quality']) {
|
||||
'common' => 1,
|
||||
'rare' => 3,
|
||||
'epic' => 8,
|
||||
'legendary' => 20,
|
||||
default => 1
|
||||
if (($item['type'] ?? '') === 'consume') {
|
||||
$basePrice = match($item['quality'] ?? 'common') {
|
||||
'common' => 1, 'rare' => 3, 'epic' => 8, 'legendary' => 20, default => 1
|
||||
};
|
||||
return (int)($basePrice + ($item['level'] ?? 1) * 0.5);
|
||||
}
|
||||
|
||||
// 装备基础价格(根据品质)
|
||||
$basePrice = match($item['quality']) {
|
||||
'common' => 5,
|
||||
'rare' => 15,
|
||||
'epic' => 40,
|
||||
'legendary' => 100,
|
||||
default => 5
|
||||
$basePrice = match($item['quality'] ?? 'common') {
|
||||
'common' => 5, 'rare' => 15, 'epic' => 40, 'legendary' => 100, default => 5
|
||||
};
|
||||
|
||||
// 等级加成(每级增加基础价格的10%)
|
||||
$levelBonus = $basePrice * ($item['level'] ?? 1) * 0.1;
|
||||
|
||||
// 主属性加成
|
||||
$statBonus = 0;
|
||||
$statBonus += ($item['atk'] ?? 0) * 0.5;
|
||||
$statBonus += ($item['def'] ?? 0) * 0.5;
|
||||
$statBonus += ($item['patk'] ?? 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['crit'] ?? 0) * 1.0;
|
||||
$statBonus += ($item['critdmg'] ?? 0) * 0.3;
|
||||
|
||||
// 词条加成(每个词条增加20%)
|
||||
$affixCount = count($item['affixes'] ?? []);
|
||||
$affixBonus = $basePrice * $affixCount * 0.2;
|
||||
|
||||
$totalPrice = $basePrice + $levelBonus + $statBonus + $affixBonus;
|
||||
|
||||
return max(1, (int)$totalPrice);
|
||||
return max(1, (int)($basePrice + $levelBonus + $statBonus + $affixBonus));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,62 +64,116 @@ class Monster
|
|||
}
|
||||
|
||||
// 3. Hydrate monster base stats from maps.php
|
||||
$monster->name = $selectedMonster['name'];
|
||||
$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;
|
||||
$monster->hydrateFromConfig($selectedMonster);
|
||||
|
||||
// 4. Generate equipment from drops and equip them
|
||||
$drops = $selectedMonster['drops'] ?? [];
|
||||
return $monster;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
$type = $drop['type'] ?? '';
|
||||
$rate = $drop['rate'] ?? 0;
|
||||
|
||||
// 消耗品放入掉落表,不装备
|
||||
if ($type === 'consume') {
|
||||
$spec = $drop;
|
||||
unset($spec['rate']);
|
||||
$item = Item::createFromSpec($spec, $monster->level);
|
||||
$monster->dropTable[] = [
|
||||
$item = Item::createFromSpec($spec, $this->level);
|
||||
$this->dropTable[] = [
|
||||
'item' => $item,
|
||||
'rate' => $rate,
|
||||
];
|
||||
continue;
|
||||
}
|
||||
|
||||
// 装备类:生成并装备
|
||||
if (in_array($type, ['weapon', 'armor', 'boots', 'ring', 'necklace'])) {
|
||||
// 先判断是否生成这件装备(使用掉落率)
|
||||
if (rand(1, 100) > $rate) {
|
||||
continue; // 没有掉落这件装备
|
||||
}
|
||||
|
||||
// 使用掉落配置生成装备
|
||||
if (rand(1, 100) > $rate) continue;
|
||||
$spec = $drop;
|
||||
unset($spec['rate']);
|
||||
$item = Item::createFromSpecWithConfig($spec, $monster->level);
|
||||
|
||||
// 装备到对应槽位(覆盖已有的)
|
||||
$monster->equip[$type] = $item;
|
||||
$item = Item::createFromSpecWithConfig($spec, $this->level);
|
||||
$this->equip[$type] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Apply equipment stats to monster
|
||||
$monster->applyEquipmentStats();
|
||||
|
||||
return $monster;
|
||||
$this->applyEquipmentStats();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ class Partner
|
|||
public int $level = 1;
|
||||
public int $exp = 0;
|
||||
public int $maxExp = 100;
|
||||
public int $hp = 100; // 当前血量
|
||||
|
||||
public array $baseStats = [];
|
||||
public array $equip = []; // weapon, armor, ring, boots, necklace
|
||||
|
|
@ -56,6 +57,10 @@ class Partner
|
|||
$this->equip = $data['equip'] ?? [];
|
||||
$this->talents = $data['talents'] ?? $this->talents;
|
||||
$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
|
||||
{
|
||||
|
|
@ -184,6 +190,12 @@ class Partner
|
|||
$remaining -= $share;
|
||||
}
|
||||
|
||||
// 关键: 确保 HP 至少获得1点(HP是必须的)
|
||||
if ($allocated['hp'] < 1) {
|
||||
$allocated['hp'] = 1;
|
||||
$remaining--;
|
||||
}
|
||||
|
||||
// 剩余点数按权重优先分配
|
||||
$sortedTalents = $this->talentWeights;
|
||||
arsort($sortedTalents);
|
||||
|
|
@ -223,4 +235,18 @@ 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; // 返回实际恢复的血量
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ use Game\Entities\Partner;
|
|||
class Battle
|
||||
{
|
||||
public Player $player;
|
||||
public Monster $monster;
|
||||
/** @var Monster[] */
|
||||
public array $enemies = [];
|
||||
|
||||
/** @var array<string, int> 同伴当前HP */
|
||||
private array $partnerHp = [];
|
||||
|
|
@ -35,7 +36,7 @@ class Battle
|
|||
private string $reset = "\033[0m";
|
||||
|
||||
private int $round = 0;
|
||||
private int $monsterMaxHp = 0;
|
||||
private int $totalMaxHp = 0;
|
||||
|
||||
public function __construct(public Game $game)
|
||||
{
|
||||
|
|
@ -49,8 +50,8 @@ class Battle
|
|||
{
|
||||
$this->partnerHp = [];
|
||||
foreach ($this->player->partners as $partner) {
|
||||
$stats = $partner->getStats();
|
||||
$this->partnerHp[$partner->id] = $stats['maxHp'];
|
||||
// 从Partner对象的hp属性读取,允许队友在战斗外也能恢复
|
||||
$this->partnerHp[$partner->id] = $partner->hp;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +69,31 @@ class Battle
|
|||
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()
|
||||
{
|
||||
$out = $this->game->output;
|
||||
|
|
@ -77,8 +103,15 @@ class Battle
|
|||
|
||||
while ($this->player->hp > 0) {
|
||||
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;
|
||||
|
||||
// 显示遭遇界面
|
||||
|
|
@ -88,7 +121,10 @@ class Battle
|
|||
|
||||
// 战斗循环
|
||||
while (true) {
|
||||
if ($this->checkExit($out)) return;
|
||||
if ($this->checkExit($out)) {
|
||||
$this->syncPartnerHp();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->round++;
|
||||
$this->renderBattleScreen($out, $playerFirst);
|
||||
|
|
@ -105,23 +141,31 @@ class Battle
|
|||
Screen::delay(800000);
|
||||
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);
|
||||
$this->syncPartnerHp();
|
||||
return;
|
||||
}
|
||||
Screen::delay(800000);
|
||||
} else {
|
||||
// 怪物先攻
|
||||
if ($this->monsterAttack($out)) {
|
||||
if ($this->enemiesAttack($out)) {
|
||||
Screen::delay(1000000);
|
||||
$this->syncPartnerHp();
|
||||
return;
|
||||
}
|
||||
Screen::delay(800000);
|
||||
|
||||
if ($this->checkExit($out)) return;
|
||||
if ($this->checkExit($out)) {
|
||||
$this->syncPartnerHp();
|
||||
return;
|
||||
}
|
||||
|
||||
// 玩家攻击
|
||||
$result = $this->playerAttack($out);
|
||||
|
|
@ -135,6 +179,8 @@ class Battle
|
|||
}
|
||||
|
||||
Screen::delay(500000);
|
||||
// 同步队友HP到Partner对象,然后保存状态
|
||||
$this->syncPartnerHp();
|
||||
$this->game->saveState();
|
||||
}
|
||||
}
|
||||
|
|
@ -148,7 +194,11 @@ class Battle
|
|||
$out->writeln("");
|
||||
$out->writeln(" {$this->red}⚔️ 遭遇敌人!{$this->reset}");
|
||||
$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("{$this->yellow}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━{$this->reset}");
|
||||
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}");
|
||||
|
||||
// 怪物信息
|
||||
$monsterHpPercent = max(0, $this->monster->hp) / $this->monsterMaxHp;
|
||||
$monsterHpBar = $this->renderHpBar($monsterHpPercent, 20);
|
||||
$monsterHpText = max(0, $this->monster->hp) . "/" . $this->monsterMaxHp;
|
||||
$out->writeln("{$this->cyan}║{$this->reset} {$this->red}👹{$this->reset} {$this->bold}{$this->monster->name}{$this->reset} Lv.{$this->monster->level}");
|
||||
$out->writeln("{$this->cyan}║{$this->reset} {$monsterHpBar} {$this->white}{$monsterHpText}{$this->reset}");
|
||||
// 敌人信息
|
||||
foreach ($this->enemies as $enemy) {
|
||||
if ($enemy->hp <= 0) {
|
||||
$out->writeln("{$this->cyan}║{$this->reset} {$this->red}💀{$this->reset} {$this->white}{$enemy->name}{$this->reset} {$this->red}[已击败]{$this->reset}");
|
||||
continue;
|
||||
}
|
||||
$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}");
|
||||
|
||||
// VS 分隔
|
||||
|
|
@ -204,7 +271,7 @@ class Battle
|
|||
private function renderHpBar(float $percent, int $width): string
|
||||
{
|
||||
$filled = (int)($percent * $width);
|
||||
$empty = $width - $filled;
|
||||
$empty = max($width - $filled,0);
|
||||
|
||||
// 根据血量百分比选择颜色
|
||||
if ($percent > 0.6) {
|
||||
|
|
@ -221,7 +288,16 @@ class Battle
|
|||
|
||||
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;
|
||||
$levelBonus = max(-30, min(30, $levelDiff * 5));
|
||||
$playerChance += $levelBonus;
|
||||
|
|
@ -232,10 +308,21 @@ class Battle
|
|||
private function playerAttack($out): bool
|
||||
{
|
||||
$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);
|
||||
$magicDamage = max(0, $stats['matk'] - $this->monster->mdef);
|
||||
$physicalDamage = max(1, $stats['patk'] - $target->pdef);
|
||||
$magicDamage = max(0, $stats['matk'] - $target->mdef);
|
||||
$baseDamage = $physicalDamage + $magicDamage;
|
||||
|
||||
$critRate = $stats['crit'];
|
||||
|
|
@ -245,21 +332,26 @@ class Battle
|
|||
|
||||
if ($isCrit) {
|
||||
$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}");
|
||||
} else {
|
||||
$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}");
|
||||
}
|
||||
|
||||
$this->monster->hp -= $damage;
|
||||
$target->hp -= $damage;
|
||||
|
||||
if ($this->monster->hp <= 0) {
|
||||
$this->monster->hp = 0;
|
||||
Screen::delay(500000);
|
||||
$this->showVictory($out, $stats);
|
||||
return true;
|
||||
if ($target->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);
|
||||
$this->showVictory($out, $stats);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
@ -273,11 +365,22 @@ class Battle
|
|||
$alivePartners = $this->getAlivePartners();
|
||||
|
||||
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();
|
||||
|
||||
// 计算物理伤害和魔法伤害
|
||||
$physicalDamage = max(1, $stats['patk'] - $this->monster->pdef);
|
||||
$magicDamage = max(0, $stats['matk'] - $this->monster->mdef);
|
||||
$physicalDamage = max(1, $stats['patk'] - $target->pdef);
|
||||
$magicDamage = max(0, $stats['matk'] - $target->mdef);
|
||||
$baseDamage = $physicalDamage + $magicDamage;
|
||||
|
||||
$critRate = $stats['crit'];
|
||||
|
|
@ -287,21 +390,25 @@ class Battle
|
|||
|
||||
if ($isCrit) {
|
||||
$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}");
|
||||
} else {
|
||||
$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}");
|
||||
}
|
||||
|
||||
$this->monster->hp -= $damage;
|
||||
$target->hp -= $damage;
|
||||
|
||||
if ($this->monster->hp <= 0) {
|
||||
$this->monster->hp = 0;
|
||||
Screen::delay(500000);
|
||||
$this->showVictory($out, $this->player->getStats());
|
||||
return true;
|
||||
if ($target->hp <= 0) {
|
||||
$target->hp = 0;
|
||||
$out->writeln("{$this->cyan}║{$this->reset} {$this->red}💀 {$target->name} 被击败了!{$this->reset}");
|
||||
|
||||
if (empty($this->getAliveEnemies())) {
|
||||
Screen::delay(500000);
|
||||
$this->showVictory($out, $this->player->getStats());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
Screen::delay(400000); // 每个同伴攻击间隔
|
||||
|
|
@ -310,53 +417,58 @@ class Battle
|
|||
return false;
|
||||
}
|
||||
|
||||
private function monsterAttack($out): bool
|
||||
private function enemiesAttack($out): bool
|
||||
{
|
||||
// 选择攻击目标:玩家或存活的同伴
|
||||
$alivePartners = $this->getAlivePartners();
|
||||
$targets = ['player'];
|
||||
foreach ($alivePartners as $partner) {
|
||||
$targets[] = $partner->id;
|
||||
}
|
||||
|
||||
$targetIdx = array_rand($targets);
|
||||
$target = $targets[$targetIdx];
|
||||
|
||||
if ($target === 'player') {
|
||||
// 攻击玩家
|
||||
$playerStats = $this->player->getStats();
|
||||
$physicalDamage = max(1, $this->monster->patk - $playerStats['pdef']);
|
||||
$magicDamage = max(0, $this->monster->matk - $playerStats['mdef']);
|
||||
$damage = $physicalDamage + $magicDamage;
|
||||
|
||||
$out->writeln("{$this->cyan}║{$this->reset} {$this->red}➤{$this->reset} {$this->monster->name} 向你发起攻击...");
|
||||
$out->writeln("{$this->cyan}║{$this->reset} {$this->red}💢 你受到 {$damage} 点伤害{$this->reset}");
|
||||
|
||||
$this->player->hp -= $damage;
|
||||
|
||||
if ($this->player->hp <= 0) {
|
||||
$this->player->hp = 0;
|
||||
Screen::delay(500000);
|
||||
$this->showDefeat($out);
|
||||
return true;
|
||||
$aliveEnemies = $this->getAliveEnemies();
|
||||
|
||||
foreach ($aliveEnemies as $enemy) {
|
||||
// 选择攻击目标:玩家或存活的同伴
|
||||
$alivePartners = $this->getAlivePartners();
|
||||
$targets = ['player'];
|
||||
foreach ($alivePartners as $partner) {
|
||||
$targets[] = $partner->id;
|
||||
}
|
||||
} else {
|
||||
// 攻击同伴
|
||||
$partner = $this->player->partners[$target];
|
||||
$partnerStats = $partner->getStats();
|
||||
$physicalDamage = max(1, $this->monster->patk - $partnerStats['pdef']);
|
||||
$magicDamage = max(0, $this->monster->matk - $partnerStats['mdef']);
|
||||
$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}💢 {$partner->name} 受到 {$damage} 点伤害{$this->reset}");
|
||||
$targetIdx = array_rand($targets);
|
||||
$target = $targets[$targetIdx];
|
||||
|
||||
$this->partnerHp[$target] -= $damage;
|
||||
if ($target === 'player') {
|
||||
// 攻击玩家
|
||||
$playerStats = $this->player->getStats();
|
||||
$physicalDamage = max(1, $enemy->patk - $playerStats['pdef']);
|
||||
$magicDamage = max(0, $enemy->matk - $playerStats['mdef']);
|
||||
$damage = $physicalDamage + $magicDamage;
|
||||
|
||||
if ($this->partnerHp[$target] <= 0) {
|
||||
$this->partnerHp[$target] = 0;
|
||||
$out->writeln("{$this->cyan}║{$this->reset} {$this->red}💀 {$partner->name} 倒下了!{$this->reset}");
|
||||
$out->writeln("{$this->cyan}║{$this->reset} {$this->red}➤{$this->reset} {$enemy->name} 向你发起攻击...");
|
||||
$out->writeln("{$this->cyan}║{$this->reset} {$this->red}💢 你受到 {$damage} 点伤害{$this->reset}");
|
||||
|
||||
$this->player->hp -= $damage;
|
||||
|
||||
if ($this->player->hp <= 0) {
|
||||
$this->player->hp = 0;
|
||||
Screen::delay(500000);
|
||||
$this->showDefeat($out, $enemy);
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// 攻击同伴
|
||||
$partner = $this->player->partners[$target];
|
||||
$partnerStats = $partner->getStats();
|
||||
$physicalDamage = max(1, $enemy->patk - $partnerStats['pdef']);
|
||||
$magicDamage = max(0, $enemy->matk - $partnerStats['mdef']);
|
||||
$damage = $physicalDamage + $magicDamage;
|
||||
|
||||
$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}");
|
||||
|
||||
$this->partnerHp[$target] -= $damage;
|
||||
|
||||
if ($this->partnerHp[$target] <= 0) {
|
||||
$this->partnerHp[$target] = 0;
|
||||
$out->writeln("{$this->cyan}║{$this->reset} {$this->red}💀 {$partner->name} 倒下了!{$this->reset}");
|
||||
}
|
||||
}
|
||||
Screen::delay(400000);
|
||||
}
|
||||
|
||||
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->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']}");
|
||||
|
||||
// 汇总经验和灵石
|
||||
$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 = "";
|
||||
if ($this->player->gainExp($exp)) {
|
||||
if ($this->player->gainExp($totalExp)) {
|
||||
$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();
|
||||
if (!empty($alivePartners)) {
|
||||
$partnerExp = (int)($exp * 0.8); // 同伴获得80%经验
|
||||
$partnerExp = (int)($totalExp * 0.8);
|
||||
foreach ($alivePartners as $partner) {
|
||||
$partnerLevelUp = "";
|
||||
if ($partner->gainExp($partnerExp)) {
|
||||
|
|
@ -396,32 +532,14 @@ class Battle
|
|||
}
|
||||
|
||||
// 灵石
|
||||
$spiritStones = $this->monster->spiritStoneReward;
|
||||
if ($spiritStones > 0) {
|
||||
$this->player->addSpiritStones($spiritStones);
|
||||
$out->writeln("{$this->yellow}║{$this->reset} 灵石: {$this->yellow}+{$spiritStones}{$this->reset}");
|
||||
if ($totalStones > 0) {
|
||||
$this->player->addSpiritStones($totalStones);
|
||||
$out->writeln("{$this->yellow}║{$this->reset} 灵石: {$this->yellow}+{$totalStones}{$this->reset}");
|
||||
}
|
||||
|
||||
// 掉落怪物装备
|
||||
$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)) {
|
||||
if (!empty($allDrops)) {
|
||||
$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, ""));
|
||||
}
|
||||
}
|
||||
|
|
@ -430,19 +548,21 @@ class Battle
|
|||
$out->writeln("");
|
||||
|
||||
$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);
|
||||
$killerName = $killer ? $killer->name : "敌人";
|
||||
|
||||
$out->writeln("");
|
||||
$out->writeln("{$this->red}╔══════════════════════════════════════════╗{$this->reset}");
|
||||
$out->writeln("{$this->red}║{$this->reset} {$this->red}║{$this->reset}");
|
||||
$out->writeln("{$this->red}║{$this->reset} {$this->red}{$this->bold}💀 战 败 ! 💀{$this->reset} {$this->red}║{$this->reset}");
|
||||
$out->writeln("{$this->red}║{$this->reset} {$this->red}║{$this->reset}");
|
||||
$out->writeln("{$this->red}╠══════════════════════════════════════════╣{$this->reset}");
|
||||
$out->writeln("{$this->red}║{$this->reset} 你被 {$this->white}{$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} {$this->white}不要气馁,休整后再战!{$this->reset}");
|
||||
$out->writeln("{$this->red}╚══════════════════════════════════════════╝{$this->reset}");
|
||||
|
|
|
|||
|
|
@ -186,16 +186,66 @@ class InventoryPanel
|
|||
$stats = $player->getStats();
|
||||
$maxHp = $stats['maxHp'];
|
||||
|
||||
// 检查是否已满血
|
||||
if ($player->hp >= $maxHp) {
|
||||
$out->writeln("你的生命值已满,无需使用!");
|
||||
// 检查玩家血量
|
||||
$playerNeedsHeal = $player->hp < $maxHp;
|
||||
|
||||
// 检查队友血量
|
||||
$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);
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用 heal 方法恢复生命,不超过上限
|
||||
$actualHeal = $player->heal($item['heal']);
|
||||
$out->writeln("你使用了 {$item['name']},恢复了 {$actualHeal} HP!(当前: {$player->hp}/{$maxHp})");
|
||||
// 选择恢复目标
|
||||
$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']);
|
||||
$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
|
||||
if (($player->inventory[$index]['quantity'] ?? 1) > 1) {
|
||||
|
|
|
|||
|
|
@ -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->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, "请选择: ");
|
||||
|
||||
if ($choice == 99) {
|
||||
if ($choice == 0) {
|
||||
$this->game->state = Game::MENU;
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user