374 lines
14 KiB
PHP
374 lines
14 KiB
PHP
<?php
|
|
namespace Game\Entities;
|
|
|
|
/**
|
|
* Simple representation of an equipment/consumable item.
|
|
*/
|
|
class Item
|
|
{
|
|
public string $name;
|
|
public string $type; // weapon, armor, consume
|
|
public string $quality; // common, rare, epic, legendary
|
|
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.
|
|
*/
|
|
public static function randomItem(string $type, int $level = 1, ?string $specificName = null): array
|
|
{
|
|
// Load data
|
|
static $data = null;
|
|
if ($data === null) {
|
|
$data = require __DIR__ . '/../../src/Data/items.php';
|
|
}
|
|
|
|
// 品质对照表
|
|
$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';
|
|
elseif ($roll <= 90) $quality = 'rare';
|
|
elseif ($roll <= 98) $quality = 'epic';
|
|
else $quality = 'legendary';
|
|
|
|
$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();
|
|
}
|
|
|
|
$names = $typeConfig['names'] ?? ['未知物品'];
|
|
|
|
if ($specificName !== null && in_array($specificName, $names)) {
|
|
$item->name = $specificName;
|
|
} else {
|
|
$item->name = $names[array_rand($names)];
|
|
}
|
|
|
|
// 初始化属性
|
|
$item->patk = 0;
|
|
$item->matk = 0;
|
|
$item->pdef = 0;
|
|
$item->mdef = 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 {
|
|
// 检查是否有特定物品配置
|
|
$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];
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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)));
|
|
$finalValue = (int)($baseValue + ($level * $growth) + $randomBonus);
|
|
$item->$statKey += $finalValue;
|
|
}
|
|
}
|
|
}
|
|
|
|
$item->desc = "Lv.{$level} {$quality}品质的" . match($type) {
|
|
'weapon' => '武器',
|
|
'armor' => '防具',
|
|
'boots' => '鞋子',
|
|
'ring' => '戒指',
|
|
'necklace' => '项链',
|
|
default => '装备'
|
|
};
|
|
}
|
|
|
|
// 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));
|
|
if (empty($availableWeights)) break;
|
|
|
|
$key = self::weightedRandom($availableWeights);
|
|
$selectedAffixes[] = $key;
|
|
|
|
$usePercent = (bool)rand(0, 1);
|
|
|
|
if ($usePercent) {
|
|
$base = rand(2, 8);
|
|
$v = $base + floor($level / 3);
|
|
$item->affixes[] = "{$affixNames[$key]} +{$v}%";
|
|
} else {
|
|
$qualityMultiplier = match($quality) {
|
|
'legendary' => 2.0,
|
|
'epic' => 1.5,
|
|
'rare' => 1.2,
|
|
default => 1.0
|
|
};
|
|
|
|
$base = rand(2, 8);
|
|
$multiplier = match($key) {
|
|
'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 + ($level * $multiplier)) * $qualityMultiplier);
|
|
$item->affixes[] = "{$affixNames[$key]} +{$v}";
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
/**
|
|
* 创建法术物品 - 支持新的丰富法术系统
|
|
* @param int $spellId 法术ID
|
|
* @param string $quality 品质 (common, rare, epic, legendary)
|
|
* @param int $level 物品等级
|
|
* @return array 法术物品数组
|
|
*/
|
|
public static function createSpell(int $spellId, string $quality = 'common', int $level = 1): array
|
|
{
|
|
static $spellsData = null;
|
|
if ($spellsData === null) {
|
|
$spellsData = require __DIR__ . '/../../src/Data/spells.php';
|
|
}
|
|
|
|
// 查找法术信息
|
|
$spellInfo = null;
|
|
foreach ($spellsData as $category => $spells) {
|
|
if (is_array($spells) && !in_array($category, ['quality_levels', 'upgrades', 'dungeon_spell_drops', 'quality_drop_rates', 'spells_by_quality'])) {
|
|
if (isset($spells[$spellId])) {
|
|
$spellInfo = $spells[$spellId];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!$spellInfo) {
|
|
// 默认法术
|
|
return [
|
|
'id' => uniqid('spell_'),
|
|
'type' => 'spell',
|
|
'name' => '未知法术',
|
|
'quality' => $quality,
|
|
'level' => $level,
|
|
'spellId' => $spellId,
|
|
'enhanceLevel' => 0,
|
|
'calc_type' => 'matk',
|
|
'cost' => 20,
|
|
'spellType' => 'damage_single',
|
|
'desc' => '未知的法术',
|
|
];
|
|
}
|
|
|
|
// 品质映射到数组索引 (common=0, rare=1, epic=2, legendary=3)
|
|
$qualityIndex = match($quality) {
|
|
'common' => 0,
|
|
'rare' => 1,
|
|
'epic' => 2,
|
|
'legendary' => 3,
|
|
default => 0,
|
|
};
|
|
|
|
// 提取品质相关的参数
|
|
$healRatio = $spellInfo['heal_ratio'][$qualityIndex] ?? ($spellInfo['heal_ratio'][0] ?? 0);
|
|
$damageRatio = $spellInfo['damage_ratio'][$qualityIndex] ?? ($spellInfo['damage_ratio'][0] ?? 1.0);
|
|
$healBase = $spellInfo['heal_base'][$qualityIndex] ?? ($spellInfo['heal_base'][0] ?? 0);
|
|
$critBonus = $spellInfo['crit_bonus'][$qualityIndex] ?? ($spellInfo['crit_bonus'][0] ?? 0);
|
|
$critDmgBonus = $spellInfo['crit_dmg_bonus'][$qualityIndex] ?? ($spellInfo['crit_dmg_bonus'][0] ?? 0);
|
|
$enemyCountBonus = $spellInfo['enemy_count_bonus'][$qualityIndex] ?? ($spellInfo['enemy_count_bonus'][0] ?? 0);
|
|
$dispersion = $spellInfo['dispersion'][$qualityIndex] ?? ($spellInfo['dispersion'][0] ?? 1.0);
|
|
$teamBonus = $spellInfo['team_bonus'][$qualityIndex] ?? ($spellInfo['team_bonus'][0] ?? 0);
|
|
$priorityBonus = $spellInfo['priority_bonus'][$qualityIndex] ?? ($spellInfo['priority_bonus'][0] ?? 0);
|
|
|
|
return [
|
|
'id' => uniqid('spell_'),
|
|
'type' => 'spell',
|
|
'name' => $spellInfo['name'],
|
|
'quality' => $quality,
|
|
'level' => $level,
|
|
'spellId' => $spellId,
|
|
'enhanceLevel' => 0,
|
|
'calc_type' => $spellInfo['calc_type'] ?? 'matk',
|
|
'cost' => $spellInfo['cost'] ?? 20,
|
|
'spellType' => $spellInfo['type'] ?? 'damage_single',
|
|
'desc' => $spellInfo['desc'] ?? '',
|
|
|
|
// 品质参数
|
|
'heal_ratio' => $healRatio,
|
|
'damage_ratio' => $damageRatio,
|
|
'heal_base' => $healBase,
|
|
'crit_bonus' => $critBonus,
|
|
'crit_dmg_bonus' => $critDmgBonus,
|
|
'enemy_count_bonus' => $enemyCountBonus,
|
|
'dispersion' => $dispersion,
|
|
'team_bonus' => $teamBonus,
|
|
'priority_bonus' => $priorityBonus,
|
|
];
|
|
}
|
|
|
|
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') {
|
|
'common' => 1, 'rare' => 3, 'epic' => 8, 'legendary' => 20, default => 1
|
|
};
|
|
return (int)($basePrice + ($item['level'] ?? 1) * 0.5);
|
|
}
|
|
|
|
$basePrice = match($item['quality'] ?? 'common') {
|
|
'common' => 5, 'rare' => 15, 'epic' => 40, 'legendary' => 100, default => 5
|
|
};
|
|
|
|
$levelBonus = $basePrice * ($item['level'] ?? 1) * 0.1;
|
|
|
|
$statBonus = 0;
|
|
$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;
|
|
|
|
$affixCount = count($item['affixes'] ?? []);
|
|
$affixBonus = $basePrice * $affixCount * 0.2;
|
|
|
|
return max(1, (int)($basePrice + $levelBonus + $statBonus + $affixBonus));
|
|
}
|
|
}
|