Идейный наследник ItemRoulette, который умер вместе со старым форумом.
Минимальная версия языка: php7. Под 5 перетачивайте синтаксис языка, а то я уже не помню нюансов. Константы я брал из потолка исходя из здравого смысла и ролевых игр.
Логика расчета наград:
1. Модификатор сложности битвы:
- Базовый множитель = 1.0
- За каждый уровень противника выше игрока: +20%
- За каждый уровень противника ниже игрока: -30%
- За каждого дополнительного противника (если уровень >= игрока): +10%
- Если игрок в команде: делим на корень из количества союзников
2. Расчет опыта:
- Базовый опыт * модификатор сложности * бонус игрока
- Ограничен константой maxExp
3. Расчет денег:
- Базовый шанс получения: 70%
- Сумма: база * модификатор сложности * бонус игрока
- Распределение: 60% малая сумма, 30% средняя, 10% большая
4. Выпадение предметов:
- Начальный шанс: 50% * модификатор сложности
- После каждого предмета: шанс * (1 - itemDropChanceStep)
- Качество определяется взвешенной случайностью с учетом бонусов
Основные компоненты:
1. Модификатор сложности:
- Учитывает разницу уровней (бонус за сильных, штраф за слабых противников)
- Учитывает количество врагов (только если они не слабее игрока)
- Делит награду на команду через квадратный корень (2 игрока = /1.41, 4 игрока = /2)
2. Система опыта:
- Всегда выдается
- База * модификатор сложности * бонус игрока
- Добавлена случайность ±20% для разнообразия
3. Система денег:
- 70% шанс получить деньги
- Взвешенное распределение: чаще мало, реже много
- Учитывает модификаторы и бонусы
4. Система предметов:
- Начальный шанс 50% * модификатор сложности
- После каждого предмета шанс падает на 20% (настраивается)
- Качество: common (88.9%), uncommon (10%), rare (1%), legendary (0.1%)
- Уникальные предметы только с уникальных боссов
5. Гибкость:
- Все константы вынесены и настраиваются
- Поддержка бонусов игрока на все типы наград
- Легко расширяется новыми параметрами
Награды выдаются в методе giveRewardsToPlayer. Туда надо подставить свои функции выдачи денег и предметов. Сами ID выдаваемых предметов либо подсовывать в $ranges в методе generateRandomItemId, либо уже самостоятельно выкусывать из базы.
/**
* Класс для расчета и выдачи наград игрокам
*/
class RewardCalculator
{
// Константы по умолчанию
private const DEFAULT_MAX_EXP = 10000;
private const DEFAULT_MAX_MONEY = 10000;
private const DEFAULT_ITEM_DROP_CHANCE_STEP = 0.20; // 20%
// Базовые значения наград
private const BASE_EXP = 100;
private const BASE_MONEY = 50;
private const BASE_ITEM_DROP_CHANCE = 0.5; // 50%
// Модификаторы сложности
private const LEVEL_BONUS_MULTIPLIER = 0.2; // +20% за каждый уровень выше
private const LEVEL_PENALTY_MULTIPLIER = 0.3; // -30% за каждый уровень ниже
private const ENEMY_COUNT_BONUS = 0.1; // +10% за каждого дополнительного противника
// Шансы на деньги
private const MONEY_DROP_CHANCE = 0.7; // 70%
// Качество предметов и их шансы (в промилле для точности)
private const ITEM_QUALITIES = [
'common' => 889, // 88.9%
'uncommon' => 100, // 10% (1:10)
'rare' => 10, // 1% (1:100)
'legendary' => 1, // 0.1% (1:1000)
'unique' => 0 // Только для уникальных противников
];
private $maxExp;
private $maxMoney;
private $itemDropChanceStep;
public function __construct(
int $maxExp = self::DEFAULT_MAX_EXP,
int $maxMoney = self::DEFAULT_MAX_MONEY,
float $itemDropChanceStep = self::DEFAULT_ITEM_DROP_CHANCE_STEP
) {
$this->maxExp = $maxExp;
$this->maxMoney = $maxMoney;
$this->itemDropChanceStep = $itemDropChanceStep;
}
/**
* Рассчитывает и выдает награды игроку
*
* @param array $battleData Данные о битве
* [
* 'playerLevel' => int,
* 'enemyLevel' => int,
* 'enemyCount' => int,
* 'teamSize' => int, // Количество союзников (1 = соло)
* 'isUniqueEnemy' => bool,
* 'playerBonuses' => [
* 'expBonus' => float, // Множитель опыта (1.0 = +0%, 1.5 = +50%)
* 'moneyBonus' => float, // Множитель денег
* 'itemQualityBonus' => [ // Бонус к шансу качества
* 'uncommon' => float,
* 'rare' => float,
* 'legendary' => float
* ]
* ]
* ]
*
* @return array Массив с наградами
*/
public function calculateRewards(array $battleData): array
{
// Извлекаем данные
$playerLevel = $battleData['playerLevel'];
$enemyLevel = $battleData['enemyLevel'];
$enemyCount = $battleData['enemyCount'] ?? 1;
$teamSize = $battleData['teamSize'] ?? 1;
$isUniqueEnemy = $battleData['isUniqueEnemy'] ?? false;
$playerBonuses = $battleData['playerBonuses'] ?? [];
// Рассчитываем модификатор сложности битвы
$difficultyModifier = $this->calculateDifficultyModifier(
$playerLevel,
$enemyLevel,
$enemyCount,
$teamSize
);
$rewards = [
'experience' => 0,
'money' => 0,
'items' => []
];
// 1. Расчет опыта (всегда выдается)
$rewards['experience'] = $this->calculateExperience(
$difficultyModifier,
$playerBonuses['expBonus'] ?? 1.0
);
// 2. Расчет денег (с шансом)
if ($this->rollChance(self::MONEY_DROP_CHANCE)) {
$rewards['money'] = $this->calculateMoney(
$difficultyModifier,
$playerBonuses['moneyBonus'] ?? 1.0
);
}
// 3. Расчет предметов
$rewards['items'] = $this->calculateItems(
$difficultyModifier,
$isUniqueEnemy,
$playerBonuses['itemQualityBonus'] ?? []
);
// Выдаем награды игроку
$this->giveRewardsToPlayer($rewards);
return $rewards;
}
/**
* Рассчитывает модификатор сложности битвы
*/
private function calculateDifficultyModifier(
int $playerLevel,
int $enemyLevel,
int $enemyCount,
int $teamSize
): float {
$modifier = 1.0;
// Модификатор за разницу уровней
$levelDiff = $enemyLevel - $playerLevel;
if ($levelDiff > 0) {
// Противник сильнее - бонус
$modifier += $levelDiff * self::LEVEL_BONUS_MULTIPLIER;
} elseif ($levelDiff < 0) {
// Противник слабее - штраф
$modifier -= abs($levelDiff) * self::LEVEL_PENALTY_MULTIPLIER;
$modifier = max(0.1, $modifier); // Минимум 10% от базы
}
// Модификатор за количество противников (только если они не слабее)
if ($enemyLevel >= $playerLevel && $enemyCount > 1) {
$modifier += ($enemyCount - 1) * self::ENEMY_COUNT_BONUS;
}
// Модификатор за команду (уменьшает награду)
if ($teamSize > 1) {
$modifier /= sqrt($teamSize);
}
return max(0.1, $modifier); // Минимальный модификатор 0.1
}
/**
* Рассчитывает количество опыта
*/
private function calculateExperience(float $difficultyModifier, float $expBonus): int
{
$exp = self::BASE_EXP * $difficultyModifier * $expBonus;
// Добавляем случайность ±20%
$exp *= mt_rand(80, 120) / 100;
return min(round($exp), $this->maxExp);
}
/**
* Рассчитывает количество денег
*/
private function calculateMoney(float $difficultyModifier, float $moneyBonus): int
{
$baseMoney = self::BASE_MONEY * $difficultyModifier * $moneyBonus;
// Взвешенное распределение суммы
$roll = mt_rand(1, 100);
if ($roll <= 60) {
// Малая сумма (50-80% от базы)
$money = $baseMoney * mt_rand(50, 80) / 100;
} elseif ($roll <= 90) {
// Средняя сумма (80-120% от базы)
$money = $baseMoney * mt_rand(80, 120) / 100;
} else {
// Большая сумма (120-200% от базы)
$money = $baseMoney * mt_rand(120, 200) / 100;
}
return min(round($money), $this->maxMoney);
}
/**
* Рассчитывает выпавшие предметы
*/
private function calculateItems(
float $difficultyModifier,
bool $isUniqueEnemy,
array $qualityBonuses
): array {
$items = [];
$currentDropChance = self::BASE_ITEM_DROP_CHANCE * $difficultyModifier;
// Пытаемся выдать предметы, пока есть шанс
while ($this->rollChance($currentDropChance)) {
$quality = $this->determineItemQuality($isUniqueEnemy, $qualityBonuses);
$items[] = [
'quality' => $quality,
'id' => $this->generateRandomItemId($quality)
];
// Уменьшаем шанс для следующего предмета
$currentDropChance *= (1 - $this->itemDropChanceStep);
// Прерываем, если шанс стал слишком мал
if ($currentDropChance < 0.01) {
break;
}
}
return $items;
}
/**
* Определяет качество выпавшего предмета
*/
private function determineItemQuality(bool $isUniqueEnemy, array $bonuses): string
{
// Для уникальных противников есть шанс на уникальный предмет
if ($isUniqueEnemy && $this->rollChance(0.1)) { // 10% шанс уникального с уникального босса
return 'unique';
}
// Копируем шансы и применяем бонусы
$qualities = self::ITEM_QUALITIES;
// Применяем бонусы к шансам (увеличиваем шанс редких качеств)
if (isset($bonuses['legendary'])) {
$qualities['legendary'] = round($qualities['legendary'] * $bonuses['legendary']);
}
if (isset($bonuses['rare'])) {
$qualities['rare'] = round($qualities['rare'] * $bonuses['rare']);
}
if (isset($bonuses['uncommon'])) {
$qualities['uncommon'] = round($qualities['uncommon'] * $bonuses['uncommon']);
}
// Пересчитываем шанс обычного качества
$totalSpecial = $qualities['uncommon'] + $qualities['rare'] + $qualities['legendary'];
$qualities['common'] = 1000 - $totalSpecial;
// Выбираем качество
$roll = mt_rand(1, 1000);
$cumulative = 0;
foreach ($qualities as $quality => $chance) {
$cumulative += $chance;
if ($roll <= $cumulative) {
return $quality;
}
}
return 'common'; // На всякий случай
}
/**
* Генерирует случайный ID предмета на основе качества
*/
private function generateRandomItemId(string $quality): int
{
// Псевдо-генерация ID в зависимости от качества
$ranges = [
'common' => [1000, 2000],
'uncommon' => [2001, 3000],
'rare' => [3001, 4000],
'legendary' => [4001, 5000],
'unique' => [5001, 6000]
];
return mt_rand($ranges[$quality][0], $ranges[$quality][1]);
}
/**
* Проверяет, выпал ли шанс
*/
private function rollChance(float $chance): bool
{
return mt_rand(1, 10000) <= ($chance * 10000);
}
/**
* Выдает награды игроку (псевдокод)
*/
private function giveRewardsToPlayer(array $rewards): void
{
// Player::giveExperience($rewards['experience']);
if ($rewards['money'] > 0) {
// Player::giveMoney($rewards['money']);
}
foreach ($rewards['items'] as $item) {
// Player::giveItem($item['id'], $item['quality']);
}
}
}
Использование:
// Пример использования
$calculator = new RewardCalculator();
// Битва: игрок 10 уровня против 2х монстров 12 уровня
$rewards = $calculator->calculateRewards([
'playerLevel' => 10,
'enemyLevel' => 12,
'enemyCount' => 2,
'teamSize' => 1, // Соло
'isUniqueEnemy' => false,
'playerBonuses' => [
'expBonus' => 1.2, // +20% к опыту
'moneyBonus' => 1.0,
'itemQualityBonus' => [
'rare' => 2.0, // Удваиваем шанс редких предметов
]
]
]);
echo "<pre>";
print_r($rewards);
echo "</pre>";
// Результат:
// [
// 'experience' => 168, // Базовый опыт с модификаторами
// 'money' => 75, // Если выпал шанс
// 'items' => [ // Массив выпавших предметов
// ['quality' => 'common', 'id' => 1523],
// ['quality' => 'uncommon', 'id' => 2341]
// ]
// ]
Из косяков: я не придумал как красиво обыграть расчёт групповых поединков, поэтому в массив $battleData playerLevel и enemyLevel прилетают целым числом. Что сюда засовывать если есть несколько противников разного уровня придумывайте сами, потому что как балансировать награды Игрок[5], Игрок[12] против Игрок[8], Игрок[8], Игрок[10] — сложна, сложна, ничего не понятно!