IndependentProbabilityReward.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  1. <?php
  2. namespace App\Module\Game\Logics;
  3. use App\Module\Game\Dtos\RewardGroupDto;
  4. use App\Module\Game\Dtos\RewardItemDto;
  5. use App\Module\Game\Logics\Reward\Item;
  6. use App\Module\Game\Services\PityService;
  7. use App\Module\Game\Models\GameRewardItem;
  8. use Illuminate\Support\Facades\Log;
  9. /**
  10. * 独立概率模式奖励处理
  11. */
  12. class IndependentProbabilityReward
  13. {
  14. /**
  15. * 处理独立概率模式奖励
  16. *
  17. * @param RewardGroupDto $groupDto 奖励组DTO
  18. * @param int|null $userId 用户ID(用于保底机制)
  19. * @return RewardItemDto[] 要发放的奖励项
  20. */
  21. public static function process(RewardGroupDto $groupDto, ?int $userId = null): array
  22. {
  23. $items = $groupDto->items;
  24. // 应用保底机制调整(如果有用户ID)
  25. if ($userId > 0) {
  26. $items = self::applyPityAdjustments($userId, $items);
  27. }
  28. // 如果不是随机发放,返回所有命中的奖励项
  29. if (!$groupDto->isRandom) {
  30. // 如果不是随机发放,返回所有奖励项
  31. if (!$groupDto->isRandom) {
  32. return array_map([Item::class, 'processQuantity' ], $items);
  33. }
  34. }
  35. // 随机发放:按概率选择指定数量的奖励项
  36. return self::processRandomSelection($groupDto, $items);
  37. }
  38. /**
  39. * 处理所有奖励项(非随机模式)
  40. *
  41. * @param array $items 奖励项列表
  42. * @return RewardItemDto[] 要发放的奖励项
  43. * @deprecated 不是随机模式,直接处理
  44. */
  45. private static function processAllItems(array $items): array
  46. {
  47. $selectedItems = [];
  48. /**
  49. * @var \App\Module\Game\Dtos\RewardItemDto $item
  50. */
  51. foreach ($items as $item) {
  52. // 必中项直接添加
  53. if ($item->isGuaranteed) {
  54. $selectedItems[] = Item::processQuantity($item);
  55. continue;
  56. }
  57. // 检查是否有概率设置
  58. $probability = $item->probability ?? 0;
  59. if ($probability <= 0) {
  60. continue; // 概率为0或未设置,跳过
  61. }
  62. // 生成随机数判断是否命中;
  63. // if (self::checkProbabilityHit($probability)) {
  64. // $selectedItems[] = Item::processQuantity($item);
  65. // }
  66. // 非随机模式 不随机了
  67. $selectedItems[] = Item::processQuantity($item);
  68. }
  69. return $selectedItems;
  70. }
  71. /**
  72. * 处理随机选择(独立概率模式下的随机数量限制)
  73. *
  74. * @param RewardGroupDto $groupDto 奖励组DTO
  75. * @param array $items 奖励项列表
  76. * @return RewardItemDto[] 要发放的奖励项
  77. */
  78. private static function processRandomSelection(RewardGroupDto $groupDto, array $items): array
  79. {
  80. $randomCount = $groupDto->randomCount;
  81. // 分离必中项和普通项
  82. $guaranteedItems = [];
  83. $normalItems = [];
  84. foreach ($items as $item) {
  85. if ($item->isGuaranteed) {
  86. $guaranteedItems[] = $item;
  87. } else {
  88. $normalItems[] = $item;
  89. }
  90. }
  91. // 先处理必中项
  92. $selectedItems = [];
  93. foreach ($guaranteedItems as $item) {
  94. $selectedItems[] = Item::processQuantity($item);
  95. }
  96. // 如果必中项数量已经达到或超过随机数量,直接返回必中项
  97. if (count($selectedItems) >= $randomCount) {
  98. return array_slice($selectedItems, 0, $randomCount);
  99. }
  100. // 计算剩余需要选择的数量
  101. $remainingCount = $randomCount - count($selectedItems);
  102. // 如果没有普通项,直接返回必中项
  103. if (empty($normalItems)) {
  104. return $selectedItems;
  105. }
  106. // 对普通项进行概率判断,收集所有命中的项
  107. $hitItems = [];
  108. foreach (range(1, $remainingCount * 5) as $lun) {
  109. foreach ($normalItems as $item) {
  110. $probability = $item->probability ?? 0;
  111. if ($probability <= 0) {
  112. continue; // 概率为0或未设置,跳过
  113. }
  114. // 生成随机数判断是否命中
  115. if (self::checkProbabilityHit($probability)) {
  116. $hitItems[] = $item;
  117. }
  118. }
  119. if (count($hitItems) >= $remainingCount) {
  120. break;
  121. }
  122. }
  123. // 如果命中的项数量不超过剩余需要的数量,全部添加
  124. if (count($hitItems) <= $remainingCount) {
  125. foreach ($hitItems as $item) {
  126. $selectedItems[] = Item::processQuantity($item);
  127. }
  128. } else {
  129. // 如果命中的项数量超过剩余需要的数量,随机选择
  130. shuffle($hitItems);
  131. $selectedHitItems = array_slice($hitItems, 0, $remainingCount);
  132. foreach ($selectedHitItems as $item) {
  133. $selectedItems[] = Item::processQuantity($item);
  134. }
  135. }
  136. return $selectedItems;
  137. }
  138. /**
  139. * 检查概率是否命中
  140. */
  141. private static function checkProbabilityHit(float $probability): bool
  142. {
  143. if ($probability <= 0) {
  144. return false;
  145. }
  146. if ($probability >= 100) {
  147. return true;
  148. }
  149. $random = mt_rand(1, 10000) / 100; // 生成0.01-100.00的随机数
  150. return $random <= $probability;
  151. }
  152. /**
  153. * 处理奖励项数量(支持随机数量)
  154. * @deprecated
  155. */
  156. private static function processQuantity(RewardItemDto $item): RewardItemDto
  157. {
  158. return Item::processQuantity($item);
  159. }
  160. /**
  161. * 应用保底机制调整
  162. */
  163. private static function applyPityAdjustments(int $userId, array $items): array
  164. {
  165. try {
  166. // 将DTO转换为模型集合以便保底服务处理
  167. $rewardItemModels = collect($items)->map(function ($itemDto) {
  168. return GameRewardItem::find($itemDto->id);
  169. })->filter();
  170. // 应用保底机制调整概率
  171. $adjustedItems = PityService::applyPityAdjustments($userId, $rewardItemModels);
  172. // 将调整后的模型转换回DTO
  173. return $adjustedItems->map(function ($model) {
  174. return RewardItemDto::fromModel($model);
  175. })->toArray();
  176. } catch (\Exception $e) {
  177. Log::warning("独立概率模式:保底机制调整失败", [
  178. 'userId' => $userId,
  179. 'error' => $e->getMessage()
  180. ]);
  181. return $items; // 返回原始项目
  182. }
  183. }
  184. }