DismantleService.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. <?php
  2. namespace App\Module\GameItems\Services;
  3. use App\Module\GameItems\Models\Item;
  4. use App\Module\GameItems\Models\ItemDismantleRule;
  5. use App\Module\GameItems\Models\ItemDismantleLog;
  6. use App\Module\GameItems\Models\ItemUser;
  7. use Exception;
  8. use Illuminate\Support\Facades\DB;
  9. use Illuminate\Support\Facades\Log;
  10. /**
  11. * 物品分解服务类
  12. *
  13. * 提供物品分解相关的服务,包括分解物品、获取分解预览等功能。
  14. * 该类是物品分解模块对外提供服务的主要入口,封装了物品分解的复杂逻辑。
  15. *
  16. * 所有方法均为静态方法,可直接通过类名调用。
  17. */
  18. class DismantleService
  19. {
  20. /**
  21. * 分解物品
  22. *
  23. * @param int $userId 用户ID
  24. * @param int $itemId 物品ID
  25. * @param int|null $instanceId 物品实例ID(单独属性物品)
  26. * @param int $quantity 数量
  27. * @param array $options 选项
  28. * @return array|bool 分解结果或失败标志
  29. */
  30. public static function dismantleItem(int $userId, int $itemId, ?int $instanceId, int $quantity, array $options = [])
  31. {
  32. try {
  33. // 获取物品信息
  34. $item = Item::findOrFail($itemId);
  35. // 检查物品是否可分解
  36. if (!$item->can_dismantle) {
  37. throw new Exception("该物品不可分解");
  38. }
  39. // 检查用户是否拥有该物品
  40. $userItem = ItemUser::where('user_id', $userId)
  41. ->where('item_id', $itemId);
  42. if ($instanceId) {
  43. $userItem->where('instance_id', $instanceId);
  44. }
  45. $userItem = $userItem->first();
  46. if (!$userItem || $userItem->quantity < $quantity) {
  47. throw new Exception("物品数量不足");
  48. }
  49. // 获取分解规则
  50. $rule = self::getDismantleRule($itemId);
  51. if (!$rule) {
  52. throw new Exception("没有找到适用的分解规则");
  53. }
  54. // 开启事务
  55. DB::beginTransaction();
  56. // 消耗物品
  57. ItemService::consumeItem($userId, $itemId, $instanceId, $quantity, [
  58. 'source_type' => 'dismantle',
  59. 'source_id' => $rule->id,
  60. 'details' => [
  61. 'rule_id' => $rule->id,
  62. 'rule_name' => $rule->name
  63. ]
  64. ]);
  65. // 获取分解结果
  66. $dismantleResults = [];
  67. for ($i = 0; $i < $quantity; $i++) {
  68. $results = $rule->getDismantleResults();
  69. foreach ($results as $result) {
  70. // 添加到分解结果
  71. if (isset($dismantleResults[$result['item_id']])) {
  72. $dismantleResults[$result['item_id']]['quantity'] += $result['quantity'];
  73. } else {
  74. $dismantleResults[$result['item_id']] = $result;
  75. }
  76. }
  77. }
  78. // 添加分解结果物品到用户背包
  79. $addedItems = [];
  80. foreach ($dismantleResults as $resultItemId => $resultData) {
  81. $resultQuantity = $resultData['quantity'];
  82. if ($resultQuantity > 0) {
  83. // 添加物品到用户背包
  84. $addResult = ItemService::addItem($userId, $resultItemId, $resultQuantity, [
  85. 'source_type' => 'dismantle',
  86. 'source_id' => $rule->id,
  87. 'details' => [
  88. 'rule_id' => $rule->id,
  89. 'rule_name' => $rule->name,
  90. 'original_item_id' => $itemId,
  91. 'original_item_name' => $item->name
  92. ]
  93. ]);
  94. $addedItems[] = [
  95. 'item_id' => $resultItemId,
  96. 'item_name' => $resultData['item_name'],
  97. 'quantity' => $resultQuantity,
  98. 'instance_id' => $addResult['instance_id'] ?? null
  99. ];
  100. }
  101. }
  102. // 计算返还金币
  103. $coinReturn = 0;
  104. if ($rule->coin_return_rate > 0 && $item->sell_price > 0) {
  105. $coinReturn = floor($item->sell_price * $rule->coin_return_rate * $quantity);
  106. if ($coinReturn > 0) {
  107. // TODO: 添加金币到用户账户
  108. // 这里需要调用Fund模块的服务来添加金币
  109. }
  110. }
  111. // 创建分解记录
  112. $dismantleLog = new ItemDismantleLog([
  113. 'user_id' => $userId,
  114. 'rule_id' => $rule->id,
  115. 'item_id' => $itemId,
  116. 'instance_id' => $instanceId,
  117. 'quantity' => $quantity,
  118. 'results' => $addedItems,
  119. 'coin_return' => $coinReturn,
  120. 'dismantle_time' => now(),
  121. 'ip_address' => $options['ip_address'] ?? request()->ip(),
  122. 'device_info' => $options['device_info'] ?? request()->userAgent(),
  123. ]);
  124. $dismantleLog->save();
  125. // 提交事务
  126. DB::commit();
  127. // 返回结果
  128. return [
  129. 'success' => true,
  130. 'dismantled_item' => [
  131. 'item_id' => $itemId,
  132. 'item_name' => $item->name,
  133. 'quantity' => $quantity,
  134. 'instance_id' => $instanceId
  135. ],
  136. 'results' => $addedItems,
  137. 'coin_return' => $coinReturn
  138. ];
  139. } catch (Exception $e) {
  140. // 回滚事务
  141. if (DB::transactionLevel() > 0) {
  142. DB::rollBack();
  143. }
  144. Log::error('分解物品失败', [
  145. 'user_id' => $userId,
  146. 'item_id' => $itemId,
  147. 'instance_id' => $instanceId,
  148. 'quantity' => $quantity,
  149. 'error' => $e->getMessage(),
  150. 'trace' => $e->getTraceAsString()
  151. ]);
  152. return false;
  153. }
  154. }
  155. /**
  156. * 获取物品分解预览
  157. *
  158. * @param int $userId 用户ID
  159. * @param int $itemId 物品ID
  160. * @param int|null $instanceId 物品实例ID(单独属性物品)
  161. * @return array 分解预览
  162. */
  163. public static function getDismantlePreview(int $userId, int $itemId, ?int $instanceId = null): array
  164. {
  165. try {
  166. // 获取物品信息
  167. $item = Item::findOrFail($itemId);
  168. // 检查物品是否可分解
  169. if (!$item->can_dismantle) {
  170. return [
  171. 'can_dismantle' => false,
  172. 'reason' => '该物品不可分解'
  173. ];
  174. }
  175. // 获取分解规则
  176. $rule = self::getDismantleRule($itemId);
  177. if (!$rule) {
  178. return [
  179. 'can_dismantle' => false,
  180. 'reason' => '没有找到适用的分解规则'
  181. ];
  182. }
  183. // 获取分解结果预览
  184. $results = $rule->results()->with('resultItem')->get()->map(function ($result) {
  185. return [
  186. 'item_id' => $result->result_item_id,
  187. 'item_name' => $result->resultItem->name,
  188. 'min_quantity' => $result->min_quantity,
  189. 'max_quantity' => $result->max_quantity,
  190. 'chance' => $result->chance
  191. ];
  192. })->toArray();
  193. // 计算返还金币
  194. $coinReturn = 0;
  195. if ($rule->coin_return_rate > 0 && $item->sell_price > 0) {
  196. $coinReturn = floor($item->sell_price * $rule->coin_return_rate);
  197. }
  198. return [
  199. 'can_dismantle' => true,
  200. 'item' => [
  201. 'item_id' => $itemId,
  202. 'item_name' => $item->name,
  203. 'instance_id' => $instanceId
  204. ],
  205. 'possible_results' => $results,
  206. 'coin_return' => $coinReturn
  207. ];
  208. } catch (Exception $e) {
  209. Log::error('获取分解预览失败', [
  210. 'user_id' => $userId,
  211. 'item_id' => $itemId,
  212. 'instance_id' => $instanceId,
  213. 'error' => $e->getMessage(),
  214. 'trace' => $e->getTraceAsString()
  215. ]);
  216. return [
  217. 'can_dismantle' => false,
  218. 'reason' => '系统错误'
  219. ];
  220. }
  221. }
  222. /**
  223. * 获取物品的分解规则
  224. *
  225. * @param int $itemId 物品ID
  226. * @return ItemDismantleRule|null 分解规则
  227. */
  228. private static function getDismantleRule(int $itemId): ?ItemDismantleRule
  229. {
  230. // 获取物品信息
  231. $item = Item::find($itemId);
  232. if (!$item) {
  233. return null;
  234. }
  235. // 获取物品稀有度(从numeric_attributes中获取,如果没有则默认为1)
  236. $rarity = 1;
  237. if ($item->numeric_attributes && isset($item->numeric_attributes['rarity'])) {
  238. $rarity = (int)$item->numeric_attributes['rarity'];
  239. }
  240. // 1. 优先查找针对特定物品的规则(包含稀有度匹配)
  241. $rule = ItemDismantleRule::where('item_id', $itemId)
  242. ->where('is_active', true)
  243. ->where('min_rarity', '<=', $rarity)
  244. ->where('max_rarity', '>=', $rarity)
  245. ->orderBy('priority', 'desc')
  246. ->first();
  247. if ($rule) {
  248. return $rule;
  249. }
  250. // 2. 查找针对物品分类的规则(包含稀有度匹配)
  251. $rule = ItemDismantleRule::where('category_id', $item->category_id)
  252. ->whereNull('item_id')
  253. ->where('is_active', true)
  254. ->where('min_rarity', '<=', $rarity)
  255. ->where('max_rarity', '>=', $rarity)
  256. ->orderBy('priority', 'desc')
  257. ->first();
  258. if ($rule) {
  259. return $rule;
  260. }
  261. // 3. 查找通用规则(包含稀有度匹配)
  262. $rule = ItemDismantleRule::whereNull('item_id')
  263. ->whereNull('category_id')
  264. ->where('is_active', true)
  265. ->where('min_rarity', '<=', $rarity)
  266. ->where('max_rarity', '>=', $rarity)
  267. ->orderBy('priority', 'desc')
  268. ->first();
  269. return $rule;
  270. }
  271. }