name = '未知怪物'; $monster->expReward = 10; return $monster; } // 2. Pick a monster using weighted random from map config $totalWeight = 0; foreach ($monsterConfig as $m) { $totalWeight += $m['weight'] ?? 100; } $rand = rand(1, $totalWeight); $selectedMonster = null; foreach ($monsterConfig as $m) { $rand -= $m['weight'] ?? 100; if ($rand <= 0) { $selectedMonster = $m; break; } } // Fallback if (!$selectedMonster) { $selectedMonster = $monsterConfig[0]; } // 3. Hydrate monster base stats from maps.php $monster->hydrateFromConfig($selectedMonster); $status = $monster->getStats(); $monster->hp = $status['maxHp']; 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 Actor[] */ 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 = self::create($dungeonId); // 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->maxHp = $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'] ?? 130; $this->expReward = $config['exp'] ?? 0; $this->spiritStoneReward = $config['spirit_stones'] ?? 0; // 根据等级和基础属性分配天赋点 $this->allocateTalentsByLevel(); // 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, $this->level); $this->dropTable[] = [ 'item' => $item, 'rate' => $rate, ]; continue; } if ($type === 'spell') { // Chance to include this spell tome in drop table if (rand(1, 100) > $rate) continue; // spell drop spec should include 'spell_id' (or 'id') to identify the spell $spellId = $drop['spell_id'] ?? ($drop['id'] ?? null); if ($spellId !== null) { static $spellsData = null; if ($spellsData === null) { $spellsData = require __DIR__ . '/../../src/Data/spells.php'; } $spellInfo = null; foreach ($spellsData as $cat => $list) { if (!is_array($list) || in_array($cat, ['quality_levels','upgrades'])) continue; if (isset($list[$spellId])) { $spellInfo = $list[$spellId]; break; } } // Scale tome level by monster level (e.g., level tiers of 5) $tomeLevel = max(1, min(10, (int)ceil($this->level / 5))); $tome = [ 'name' => ($spellInfo['name'] ?? ('法术#' . $spellId)) . '的法术书', 'type' => 'spell_tome', 'quality' => $spellInfo['quality'] ?? ($drop['quality'] ?? 'common'), 'level' => $tomeLevel, 'spell_id' => $spellId, 'spell_name' => $spellInfo['name'] ?? null, 'desc' => $drop['desc'] ?? ('能够学习或提升 ' . ($spellInfo['name'] ?? '未知法术')), ]; // Add to drop table with given rate $this->dropTable[] = [ 'item' => $tome, 'rate' => $rate, ]; } continue; } if (in_array($type, ['weapon', 'armor', 'boots', 'ring', 'necklace'])) { if (rand(1, 100) > $rate) continue; $spec = $drop; unset($spec['rate']); $item = Item::createFromSpecWithConfig($spec, $this->level); $this->equip[$type] = $item; } } $this->applyEquipmentStats(); } /** * 应用装备属性加成到怪物属性 */ // Monster-specific application of equipment is handled by Actor::getStats; applyEquipmentStats remains for legacy callers public function applyEquipmentStats(): void { $this->hp = $this->baseHp; $this->patk = $this->basePatk; $this->matk = $this->baseMatk; $this->pdef = $this->basePdef; $this->mdef = $this->baseMdef; foreach ($this->equip as $item) { if (empty($item)) continue; $this->hp += $item['hp'] ?? 0; $this->patk += $item['patk'] ?? $item['atk'] ?? 0; $this->matk += $item['matk'] ?? 0; $this->pdef += $item['pdef'] ?? $item['def'] ?? 0; $this->mdef += $item['mdef'] ?? 0; $this->crit += $item['crit'] ?? 0; $this->critdmg += $item['critdmg'] ?? 0; } } /** * 获取怪物装备的物品列表(用于战斗胜利时掉落) * @return array */ public function getEquippedItems(): array { $items = []; foreach ($this->equip as $item) { if (!empty($item)) { $items[] = $item; } } return $items; } /** * 随机掉落装备物品(从穿着的装备随机掉落) * @param int $dropRate 掉落概率(0-100) * @return array 掉落的物品列表 */ public function getRandomEquipmentDrops(int $dropRate = 50): array { $drops = []; foreach ($this->equip as $item) { if (!empty($item)) { // 每件装备有独立的掉落概率 if (rand(1, 100) <= $dropRate) { $drops[] = $item; } } } return $drops; } /** * 根据等级和基础属性分配天赋点和权重 * 怪物根据等级获得天赋点,并按基础属性的占比分配权重 */ private function allocateTalentsByLevel(): void { // 每级获得 3 点天赋点(与玩家一致) $talentPoints = ($this->level - 1) * 3; if ($talentPoints <= 0) { return; } // 计算基础属性的权重比(考虑每点天赋的效果) // talentBonus 定义了每点天赋对应的属性增益 $talentBonusMap = [ 'hp' => 10, 'patk' => 5, 'matk' => 4, 'pdef' => 3, 'mdef' => 3, 'crit' => 1, 'critdmg' => 5, ]; // 计算每个属性的权重(基础属性值 / 天赋加成) $weights = [ 'hp' => max(1, (int)($this->baseHp / $talentBonusMap['hp'])), 'patk' => max(1, (int)($this->basePatk / $talentBonusMap['patk'])), 'matk' => max(1, (int)($this->baseMatk / $talentBonusMap['matk'])), 'pdef' => max(1, (int)($this->basePdef / $talentBonusMap['pdef'])), 'mdef' => max(1, (int)($this->baseMdef / $talentBonusMap['mdef'])), 'crit' => max(1, (int)($this->crit / $talentBonusMap['crit'])), 'critdmg' => 0, ]; // dd($weights); // 设置权重 $this->talentWeights = $weights; $this->talentPoints = $talentPoints; // 自动按权重分配天赋点 $this->autoAllocateTalents($talentPoints); } }