hanli/src/Entities/Monster.php
2025-12-09 22:56:41 +08:00

343 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace Game\Entities;
class Monster extends Actor
{
// Monster特有的基础属性不含装备加成
public int $baseHp = 20;
public int $basePatk = 4;
public int $baseMatk = 2;
public int $basePdef = 0;
public int $baseMdef = 0;
// Monster特有的奖励属性
public int $expReward = 10;
public int $spiritStoneReward = 0;
// Monster特有的掉落表
public array $dropTable = [];
public static function create(int $dungeonId): self
{
// Load data
static $maps = null;
if ($maps === null) {
$maps = require __DIR__ . '/../../src/Data/map_new.php';
}
$monster = new self();
// 1. Get monster list for this dungeon
$monsterConfig = $maps[$dungeonId]['monsters'] ?? [];
if (empty($monsterConfig)) {
$monster->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,$dungeonId);
$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,$dungeonId): 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();
static $allItems = null;
if ($allItems === null) {
$allItems = require __DIR__ . '/../../src/Data/item_new.php';
}
// Drops & Equipment & Spells
$drops = $config['drops'] ?? [];
$index = ($dungeonId - 1) * 5;
$drops_eq = array_slice($allItems,$index,5);
$drops = array_merge($drops,$drops_eq);
foreach ($drops as $drop) {
$type = $drop['type'] ?? '';
$rate = $drop['rate'] ?? 20;
// 处理任务物品
if ($type === 'quest_item') {
$item = Item::createQuestItem($drop['name'] ?? '未知道具');
$this->dropTable[] = [
'item' => $item,
'rate' => $rate,
];
continue;
}
if ($type === 'consume') {
$spec = $drop;
unset($spec['rate']);
$item = Item::createConsume($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;
$spec = $drop;
unset($spec['rate']);
$item = Item::createFromSpecWithConfig($spec, $this->level);
$this->equip[$type] = $item;
}
}
// 为怪物配置法术
$this->generateSpells($config);
}
/**
* 为怪物随机生成法术(穿着法术,类似装备)
* 配置示例 1: 'spells' => [1, 3, 10] - 随机从这些法术ID中选择
* 配置示例 2: 'spells' => [
* ['id' => 1, 'name' => '治愈术'],
* ['id' => 10, 'name' => '火焰术'],
* ['id' => 30, 'name' => '防御法术'],
* ] - 为怪物分配具体的法术(可选指定名称)
*/
private function generateSpells(array $config): void
{
$spellConfigs = $config['spells'] ?? [];
if (empty($spellConfigs)) {
return;
}
static $spellsData = null;
if ($spellsData === null) {
$spellsData = require __DIR__ . '/../../src/Data/spells.php';
}
// 处理两种配置格式
$selectedSpells = [];
// 格式2: 配置数组 [['id' => 1, 'name' => '治愈术', 'rate' => 30]]
foreach ($spellConfigs as $spellConfig) {
if (is_array($spellConfig) && isset($spellConfig['id'])) {
$selectedSpells[] = [
'id' => $spellConfig['id'],
'name' => $spellConfig['name'] ?? null,
'rate' => $spellConfig['rate'] ?? 30, // 默认30%掉落概率
];
}
}
foreach ($selectedSpells as $spellConfig) {
$spellId = $spellConfig['id'];
$customName = $spellConfig['name'] ?? null;
$dropRate = $spellConfig['rate'] ?? 30;
if (rand(1, 100) > $dropRate) continue;
// 随机决定法术品质70% 普通, 20% 稀有, 8% 史诗, 2% 传奇
$roll = rand(1, 100);
if ($roll <= 70) $quality = 'common';
elseif ($roll <= 90) $quality = 'rare';
elseif ($roll <= 98) $quality = 'epic';
else $quality = 'legendary';
// 创建法术物品
$spell = Item::createSpell($spellId, $quality, $this->level);
// 如果提供了自定义名称,则覆盖默认名称
if ($customName) {
$spell['name'] = $customName;
}
foreach ($this->skillSlots as $slot => $content) {
if ($content === null) {
$this->skillSlots[$slot] = $spell;
break;
}
}
}
}
/**
* 随机掉落装备物品(从穿着的装备随机掉落)
* @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;
}
/**
* 随机掉落法术(从穿着的法术随机掉落)
* 使用每个法术配置的掉落概率,如果未指定则使用默认值
* @param int $defaultRate 默认掉落概率0-100当法术未指定rate时使用
* @return array 掉落的法术列表
*/
public function getRandomSpellDrops(int $dropRate = 50): array
{
$drops = [];
foreach ($this->skillSlots as $spell) {
if (!empty($spell)) {
// 每个法术有独立的掉落概率
if (rand(1, 100) <= $dropRate) {
$drops[] = $spell;
}
}
}
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);
}
}