PityService.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. <?php
  2. namespace App\Module\Game\Services;
  3. use App\Module\Game\Models\GameRewardGroup;
  4. use App\Module\Game\Models\GameRewardItem;
  5. use App\Module\Game\Models\GameRewardGroupPityCount;
  6. use Illuminate\Support\Collection;
  7. /**
  8. * 保底机制服务类
  9. *
  10. * 提供奖励组保底机制的核心功能,包括保底计数管理、概率调整、保底触发等。
  11. */
  12. class PityService
  13. {
  14. /**
  15. * 获取用户在指定奖励组的保底计数
  16. *
  17. * @param int $userId 用户ID
  18. * @param int $rewardGroupId 奖励组ID
  19. * @return Collection
  20. */
  21. public static function getUserPityCounts(int $userId, int $rewardGroupId): Collection
  22. {
  23. return GameRewardGroupPityCount::where('user_id', $userId)
  24. ->where('reward_group_id', $rewardGroupId)
  25. ->with('rewardItem')
  26. ->get();
  27. }
  28. /**
  29. * 初始化用户的保底计数
  30. *
  31. * @param int $userId 用户ID
  32. * @param int $rewardGroupId 奖励组ID
  33. * @return void
  34. */
  35. public static function initializePityCounts(int $userId, int $rewardGroupId): void
  36. {
  37. // 获取奖励组中启用保底的奖励项
  38. $pityEnabledItems = GameRewardItem::where('group_id', $rewardGroupId)
  39. ->where('pity_enabled', true)
  40. ->where('pity_threshold', '>', 0)
  41. ->get();
  42. foreach ($pityEnabledItems as $item) {
  43. // 检查是否已存在保底计数记录
  44. $existingCount = GameRewardGroupPityCount::where('user_id', $userId)
  45. ->where('reward_group_id', $rewardGroupId)
  46. ->where('reward_item_id', $item->id)
  47. ->first();
  48. if (!$existingCount) {
  49. // 创建新的保底计数记录
  50. GameRewardGroupPityCount::create([
  51. 'user_id' => $userId,
  52. 'reward_group_id' => $rewardGroupId,
  53. 'reward_item_id' => $item->id,
  54. 'count' => 0,
  55. 'pity_threshold' => $item->pity_threshold,
  56. 'last_attempt_at' => now(),
  57. ]);
  58. }
  59. }
  60. }
  61. /**
  62. * 应用保底机制调整奖励权重
  63. *
  64. * @param int $userId 用户ID
  65. * @param Collection $rewardItems 奖励项集合
  66. * @return Collection 调整后的奖励项集合
  67. */
  68. public static function applyPityAdjustments(int $userId, Collection $rewardItems): Collection
  69. {
  70. if ($rewardItems->isEmpty()) {
  71. return $rewardItems;
  72. }
  73. $rewardGroupId = $rewardItems->first()->group_id;
  74. // 初始化保底计数(如果需要)
  75. self::initializePityCounts($userId, $rewardGroupId);
  76. // 获取用户的保底计数
  77. $pityCounts = self::getUserPityCounts($userId, $rewardGroupId);
  78. $pityCountsMap = $pityCounts->keyBy('reward_item_id');
  79. // 调整每个奖励项的权重
  80. return $rewardItems->map(function (GameRewardItem $item) use ($pityCountsMap) {
  81. $pityCount = $pityCountsMap->get($item->id);
  82. if ($pityCount && $item->pity_enabled && $item->pity_threshold > 0) {
  83. // 计算调整后的权重
  84. $adjustedWeight = $pityCount->getAdjustedWeight(
  85. $item->weight,
  86. $item->pity_weight_factor
  87. );
  88. // 创建一个新的奖励项实例,避免修改原始数据
  89. $adjustedItem = clone $item;
  90. $adjustedItem->weight = $adjustedWeight;
  91. // 添加保底信息到额外数据中
  92. $extraData = $adjustedItem->extra_data ?: [];
  93. $extraData['pity_info'] = [
  94. 'current_count' => $pityCount->count,
  95. 'threshold' => $pityCount->pity_threshold,
  96. 'is_at_threshold' => $pityCount->isAtPityThreshold(),
  97. 'original_weight' => $item->weight,
  98. 'adjusted_weight' => $adjustedWeight,
  99. ];
  100. $adjustedItem->extra_data = $extraData;
  101. return $adjustedItem;
  102. }
  103. return $item;
  104. });
  105. }
  106. /**
  107. * 更新保底计数
  108. *
  109. * @param int $userId 用户ID
  110. * @param int $rewardGroupId 奖励组ID
  111. * @param array $obtainedItemIds 获得的奖励项ID数组
  112. * @return void
  113. */
  114. public static function updatePityCounts(int $userId, int $rewardGroupId, array $obtainedItemIds): void
  115. {
  116. // 获取用户的保底计数
  117. $pityCounts = self::getUserPityCounts($userId, $rewardGroupId);
  118. foreach ($pityCounts as $pityCount) {
  119. if (in_array($pityCount->reward_item_id, $obtainedItemIds)) {
  120. // 获得了该奖励项,重置计数
  121. $pityCount->resetCount();
  122. } else {
  123. // 未获得该奖励项,增加计数
  124. $pityCount->incrementCount();
  125. }
  126. }
  127. }
  128. /**
  129. * 检查是否有保底触发
  130. *
  131. * @param int $userId 用户ID
  132. * @param int $rewardGroupId 奖励组ID
  133. * @return array 触发保底的奖励项ID数组
  134. */
  135. public static function checkPityTriggers(int $userId, int $rewardGroupId): array
  136. {
  137. $pityCounts = self::getUserPityCounts($userId, $rewardGroupId);
  138. $triggeredItems = [];
  139. foreach ($pityCounts as $pityCount) {
  140. if ($pityCount->isAtPityThreshold()) {
  141. $triggeredItems[] = $pityCount->reward_item_id;
  142. }
  143. }
  144. return $triggeredItems;
  145. }
  146. /**
  147. * 获取用户保底状态信息
  148. *
  149. * @param int $userId 用户ID
  150. * @param int $rewardGroupId 奖励组ID
  151. * @return array
  152. */
  153. public static function getPityStatus(int $userId, int $rewardGroupId): array
  154. {
  155. $pityCounts = self::getUserPityCounts($userId, $rewardGroupId);
  156. $status = [];
  157. foreach ($pityCounts as $pityCount) {
  158. $rewardItem = $pityCount->rewardItem;
  159. $status[] = [
  160. 'reward_item_id' => $pityCount->reward_item_id,
  161. 'reward_item_name' => $rewardItem ? $rewardItem->getTargetName() : '未知奖励',
  162. 'current_count' => $pityCount->count,
  163. 'threshold' => $pityCount->pity_threshold,
  164. 'progress_percentage' => $pityCount->pity_threshold > 0
  165. ? round(($pityCount->count / $pityCount->pity_threshold) * 100, 2)
  166. : 0,
  167. 'is_at_threshold' => $pityCount->isAtPityThreshold(),
  168. 'last_attempt_at' => $pityCount->last_attempt_at,
  169. 'last_hit_at' => $pityCount->last_hit_at,
  170. ];
  171. }
  172. return $status;
  173. }
  174. /**
  175. * 重置用户的所有保底计数
  176. *
  177. * @param int $userId 用户ID
  178. * @param int $rewardGroupId 奖励组ID
  179. * @return void
  180. */
  181. public static function resetAllPityCounts(int $userId, int $rewardGroupId): void
  182. {
  183. GameRewardGroupPityCount::where('user_id', $userId)
  184. ->where('reward_group_id', $rewardGroupId)
  185. ->update([
  186. 'count' => 0,
  187. 'last_hit_at' => now(),
  188. ]);
  189. }
  190. /**
  191. * 清理过期的保底计数记录
  192. *
  193. * @param int $daysOld 清理多少天前的记录
  194. * @return int 清理的记录数量
  195. */
  196. public static function cleanupOldPityCounts(int $daysOld = 90): int
  197. {
  198. $cutoffDate = now()->subDays($daysOld);
  199. return GameRewardGroupPityCount::where('last_attempt_at', '<', $cutoffDate)
  200. ->where('count', 0)
  201. ->delete();
  202. }
  203. }