ChestService.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. <?php
  2. namespace App\Module\GameItems\Services;
  3. use App\Module\Game\Services\ConsumeService;
  4. use App\Module\Game\Services\RewardService;
  5. use App\Module\GameItems\Enums\ITEM_TYPE;
  6. use App\Module\GameItems\Models\Item;
  7. use App\Module\GameItems\Models\ItemChestOpenLog;
  8. use App\Module\GameItems\Models\ItemChestConfig;
  9. use Exception;
  10. use Illuminate\Support\Facades\DB;
  11. use Illuminate\Support\Facades\Log;
  12. /**
  13. * 宝箱服务类 - 使用消耗组/奖励组系统
  14. *
  15. * 提供基于消耗组和奖励组的宝箱开启服务,替代原有的宝箱内容配置系统。
  16. * 该版本使用统一的组系统架构,提供更灵活的宝箱配置和管理功能。
  17. */
  18. class ChestService
  19. {
  20. /**
  21. * 开启宝箱
  22. *
  23. * @param int $userId 用户ID
  24. * @param int $chestId 宝箱ID
  25. * @param int $quantity 开启数量
  26. * @param array $options 选项
  27. * @return array 开启结果
  28. * @throws Exception
  29. */
  30. public static function openChest(int $userId, int $chestId, int $quantity = 1, array $options = []): array
  31. {
  32. // 获取宝箱信息
  33. $chest = Item::findOrFail($chestId);
  34. // 检查是否为宝箱类型
  35. if ($chest->type != ITEM_TYPE::CHEST) {
  36. throw new Exception("物品 {$chestId} 不是宝箱类型");
  37. }
  38. // 获取宝箱配置
  39. $chestConfig = ItemChestConfig::with(['consumeGroup', 'rewardGroup', 'conditionGroup'])
  40. ->where('item_id', $chestId)
  41. ->where('is_active', true)
  42. ->first();
  43. if (!$chestConfig) {
  44. throw new Exception("宝箱 {$chestId} 没有配置新系统或配置未激活");
  45. }
  46. if (!$chestConfig->reward_group_id) {
  47. throw new Exception("宝箱 {$chestId} 没有配置奖励组");
  48. }
  49. // 开始事务
  50. DB::beginTransaction();
  51. try {
  52. $allResults = [];
  53. $totalCosts = [];
  54. // 循环开启每个宝箱
  55. for ($i = 0; $i < $quantity; $i++) {
  56. // 验证并扣除消耗(如果有消耗组配置)
  57. if ($chestConfig->consume_group_id) {
  58. $consumeResult = ConsumeService::executeConsume(
  59. $userId,
  60. $chestConfig->consume_group_id,
  61. "开启宝箱",
  62. $chestId
  63. );
  64. if (!$consumeResult->success) {
  65. throw new Exception("消耗验证失败: " . $consumeResult->message);
  66. }
  67. // 记录消耗详情
  68. $totalCosts = array_merge($totalCosts, $consumeResult->data ?? []);
  69. }
  70. // 消耗宝箱本身
  71. $consumeResult = ItemService::consumeItem(
  72. $userId,
  73. $chestId,
  74. null,
  75. 1,
  76. [
  77. 'source_type' => 'chest_open',
  78. 'source_id' => $chestId,
  79. 'details' => ['quantity' => 1],
  80. 'ip_address' => $options['ip_address'] ?? null,
  81. 'device_info' => $options['device_info'] ?? null,
  82. ]
  83. );
  84. if (!$consumeResult['success']) {
  85. throw new Exception("消耗宝箱失败: " . $consumeResult['message']);
  86. }
  87. // 发放奖励(支持保底机制)
  88. $rewardResult = RewardService::grantRewardWithPity(
  89. $userId,
  90. $chestConfig->reward_group_id,
  91. 'chest_open',
  92. $chestId,
  93. true // 启用保底机制
  94. );
  95. if (!$rewardResult->success) {
  96. throw new Exception("发放奖励失败: " . ($rewardResult->errorMessage ?? '未知错误'));
  97. }
  98. // 记录本次开启结果
  99. $allResults[] = $rewardResult->items;
  100. }
  101. // 记录宝箱开启日志
  102. $openLog = ItemChestOpenLog::create([
  103. 'user_id' => $userId,
  104. 'chest_id' => $chestId,
  105. 'open_quantity' => $quantity,
  106. 'result_items' => $allResults,
  107. 'pity_triggered' => false, // V2版本暂不支持保底机制
  108. 'pity_content_id' => null,
  109. 'open_time' => now(),
  110. 'ip_address' => $options['ip_address'] ?? null,
  111. 'device_info' => $options['device_info'] ?? null,
  112. ]);
  113. DB::commit();
  114. return [
  115. 'success' => true,
  116. 'chest_id' => $chestId,
  117. 'quantity' => $quantity,
  118. 'results' => $allResults,
  119. 'costs' => $totalCosts,
  120. 'log_id' => $openLog->id,
  121. 'version' => 'new',
  122. ];
  123. } catch (Exception $e) {
  124. DB::rollBack();
  125. Log::error('宝箱开启失败', [
  126. 'user_id' => $userId,
  127. 'chest_id' => $chestId,
  128. 'quantity' => $quantity,
  129. 'error' => $e->getMessage(),
  130. 'trace' => $e->getTraceAsString()
  131. ]);
  132. throw $e;
  133. }
  134. }
  135. /**
  136. * 获取宝箱内容预览
  137. *
  138. * @param int $chestId 宝箱ID
  139. * @return array 宝箱内容预览
  140. */
  141. public static function getChestContentPreview(int $chestId): array
  142. {
  143. // 获取宝箱信息
  144. $chest = Item::findOrFail($chestId);
  145. // 检查是否为宝箱类型
  146. if ($chest->type != ITEM_TYPE::CHEST) {
  147. throw new Exception("物品 {$chestId} 不是宝箱类型");
  148. }
  149. // 获取宝箱配置
  150. $chestConfig = ItemChestConfig::with(['consumeGroup.consumeItems', 'rewardGroup.rewardItems', 'conditionGroup'])
  151. ->where('item_id', $chestId)
  152. ->where('is_active', true)
  153. ->first();
  154. $preview = [
  155. 'chest_id' => $chestId,
  156. 'chest_name' => $chest->name,
  157. 'version' => 'new',
  158. 'consume_group' => null,
  159. 'reward_group' => null,
  160. 'condition_group' => null,
  161. 'config_status' => $chestConfig ? 'active' : 'not_configured',
  162. ];
  163. if (!$chestConfig) {
  164. return $preview;
  165. }
  166. // 获取消耗组信息
  167. if ($chestConfig->consumeGroup) {
  168. $preview['consume_group'] = [
  169. 'id' => $chestConfig->consumeGroup->id,
  170. 'name' => $chestConfig->consumeGroup->name,
  171. 'code' => $chestConfig->consumeGroup->code,
  172. 'description' => $chestConfig->consumeGroup->description,
  173. 'items' => $chestConfig->consumeGroup->consumeItems->map(function ($item) {
  174. return [
  175. 'consume_type' => $item->consume_type,
  176. 'target_id' => $item->target_id,
  177. 'quantity' => $item->quantity,
  178. ];
  179. })->toArray(),
  180. ];
  181. }
  182. // 获取奖励组信息
  183. if ($chestConfig->rewardGroup) {
  184. $preview['reward_group'] = [
  185. 'id' => $chestConfig->rewardGroup->id,
  186. 'name' => $chestConfig->rewardGroup->name,
  187. 'code' => $chestConfig->rewardGroup->code,
  188. 'description' => $chestConfig->rewardGroup->description,
  189. 'is_random' => $chestConfig->rewardGroup->is_random,
  190. 'random_count' => $chestConfig->rewardGroup->random_count,
  191. 'reward_mode' => $chestConfig->rewardGroup->reward_mode,
  192. 'items' => $chestConfig->rewardGroup->rewardItems->map(function ($item) {
  193. return [
  194. 'reward_type' => $item->reward_type,
  195. 'target_id' => $item->target_id,
  196. 'quantity' => $item->quantity,
  197. 'weight' => $item->weight,
  198. 'is_guaranteed' => $item->is_guaranteed,
  199. ];
  200. })->toArray(),
  201. ];
  202. }
  203. // 获取条件组信息
  204. if ($chestConfig->conditionGroup) {
  205. $preview['condition_group'] = [
  206. 'id' => $chestConfig->conditionGroup->id,
  207. 'name' => $chestConfig->conditionGroup->name,
  208. 'code' => $chestConfig->conditionGroup->code,
  209. 'description' => $chestConfig->conditionGroup->description,
  210. ];
  211. }
  212. return $preview;
  213. }
  214. /**
  215. * 检查宝箱是否可以开启
  216. *
  217. * @param int $userId 用户ID
  218. * @param int $chestId 宝箱ID
  219. * @param int $quantity 开启数量
  220. * @return array 检查结果
  221. */
  222. public static function canOpenChest(int $userId, int $chestId, int $quantity = 1): array
  223. {
  224. try {
  225. // 获取宝箱信息
  226. $chest = Item::findOrFail($chestId);
  227. // 检查是否为宝箱类型
  228. if ($chest->type != ITEM_TYPE::CHEST) {
  229. return [
  230. 'can_open' => false,
  231. 'message' => "物品 {$chestId} 不是宝箱类型",
  232. ];
  233. }
  234. // 获取宝箱配置
  235. $chestConfig = ItemChestConfig::with(['consumeGroup'])
  236. ->where('item_id', $chestId)
  237. ->where('is_active', true)
  238. ->first();
  239. if (!$chestConfig) {
  240. return [
  241. 'can_open' => false,
  242. 'message' => "宝箱 {$chestId} 没有配置新系统或配置未激活",
  243. ];
  244. }
  245. // 检查是否配置了奖励组
  246. if (!$chestConfig->reward_group_id) {
  247. return [
  248. 'can_open' => false,
  249. 'message' => "宝箱 {$chestId} 没有配置奖励组",
  250. ];
  251. }
  252. // 检查用户是否拥有足够的宝箱
  253. $checkResult = ItemService::checkItemQuantity($userId, $chestId, $quantity);
  254. if (!$checkResult->success) {
  255. return [
  256. 'can_open' => false,
  257. 'message' => $checkResult->message,
  258. ];
  259. }
  260. // 检查消耗组(如果有)
  261. if ($chestConfig->consume_group_id) {
  262. $canConsume = ConsumeService::checkConsume($userId, $chestConfig->consume_group_id);
  263. if (!$canConsume->success) {
  264. return [
  265. 'can_open' => false,
  266. 'message' => "消耗验证失败: " . $canConsume->message,
  267. ];
  268. }
  269. }
  270. return [
  271. 'can_open' => true,
  272. 'message' => '可以开启',
  273. ];
  274. } catch (Exception $e) {
  275. return [
  276. 'can_open' => false,
  277. 'message' => "检查失败: " . $e->getMessage(),
  278. ];
  279. }
  280. }
  281. }