PetStealLogic.php 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. <?php
  2. namespace App\Module\Pet\Logic;
  3. use App\Module\Pet\Models\PetUser;
  4. use App\Module\Pet\Models\PetStealLog;
  5. use App\Module\Pet\Enums\PetStatus;
  6. use App\Module\Pet\Dtos\StealResultDto;
  7. use App\Module\Farm\Models\FarmCrop;
  8. use App\Module\Farm\Models\FarmLand;
  9. use App\Module\Farm\Enums\GROWTH_STAGE;
  10. use App\Module\Pet\Events\PetUpdateEvent;
  11. use App\Module\Farm\Services\PickService;
  12. use Illuminate\Support\Facades\Log;
  13. /**
  14. * 宠物偷菜逻辑类
  15. *
  16. * 处理宠物偷菜的核心业务逻辑
  17. */
  18. class PetStealLogic
  19. {
  20. /**
  21. * 执行偷菜操作
  22. *
  23. * @param int $stealerId 偷菜者用户ID
  24. * @param int $targetUserId 被偷者用户ID
  25. * @param int $plantId 作物ID
  26. * @param int $petId 宠物ID
  27. * @return StealResultDto 偷菜结果
  28. * @throws \Exception 偷菜失败时抛出异常
  29. */
  30. public static function stealCrop(int $stealerId, int $targetUserId, int $plantId, int $petId): StealResultDto
  31. {
  32. // 检查事务是否已开启
  33. \UCore\Db\Helper::check_tr();
  34. try {
  35. // 1. 获取作物信息
  36. $crop = FarmCrop::find($plantId);
  37. if (!$crop) {
  38. throw new \Exception('作物不存在');
  39. }
  40. $landId = $crop->land_id;
  41. // 2. 基础验证
  42. $canStealResult = static::canSteal($stealerId, $targetUserId, $landId);
  43. if (!$canStealResult['can_steal']) {
  44. throw new \Exception($canStealResult['reason']);
  45. }
  46. // 3. 获取指定的偷菜者宠物
  47. $stealerPet = PetUser::where('id', $petId)
  48. ->where('user_id', $stealerId)
  49. ->where('status', PetStatus::NORMAL)
  50. ->where('stamina', '>=', 20)
  51. ->first();
  52. if (!$stealerPet) {
  53. throw new \Exception('指定的宠物不存在、不属于当前用户、状态异常或体力不足');
  54. }
  55. // 4. 验证作物状态
  56. if ($crop->growth_stage !== GROWTH_STAGE::MATURE) {
  57. throw new \Exception('作物尚未成熟,无法偷取');
  58. }
  59. // 4. 获取被偷者宠物(用于防御判定)
  60. $targetPet = PetUser::where('user_id', $targetUserId)
  61. ->where('status', PetStatus::NORMAL)
  62. ->first();
  63. // 5. 防御判定
  64. $defended = false;
  65. $stealSuccess = true;
  66. $targetStaminaCost = 0;
  67. if ($targetPet && $targetPet->stamina >= 10) {
  68. // 有宠物且有体力,进行防御判定
  69. if ($targetPet->level >= $stealerPet->level) {
  70. // 防御成功
  71. $defended = true;
  72. $stealSuccess = false;
  73. $targetStaminaCost = 10;
  74. }
  75. }
  76. // 6. 扣除偷菜者体力(偷菜是瞬时操作,不改变状态)
  77. $stealerPet->stamina -= 20;
  78. $stealerPet->save();
  79. // 触发偷菜者宠物更新事件
  80. event(new PetUpdateEvent($stealerId, $stealerPet->id));
  81. // 7. 扣除被偷者体力(如果防御成功)
  82. if ($defended && $targetPet) {
  83. $targetPet->stamina -= 10;
  84. $targetPet->save();
  85. // 触发被偷者宠物更新事件
  86. event(new PetUpdateEvent($targetUserId, $targetPet->id));
  87. }
  88. $actualPickAmount = 0;
  89. $pickLogId = null;
  90. $itemId = null;
  91. // 8. 如果偷菜成功,执行摘取
  92. if ($stealSuccess) {
  93. // 检查是否可以摘取
  94. if ($crop->pickable_amount <= 0) {
  95. throw new \Exception('没有可摘取的作物');
  96. }
  97. // 计算偷取数量(5-20个随机)
  98. $pickAmount = rand(5,20);
  99. $maxPickable = $crop->pickable_amount;
  100. $actualPickAmount = min($pickAmount, $maxPickable);
  101. if ($actualPickAmount > 0) {
  102. // 调用Farm模块的摘取服务进行正规摘取
  103. $pickResult = PickService::pickCrop(
  104. $stealerId,
  105. $crop->id,
  106. $actualPickAmount,
  107. 'pet_steal',
  108. $stealerPet->id
  109. );
  110. // 获取摘取结果信息
  111. $itemId = $pickResult->itemId;
  112. $pickLogId = $pickResult->pickLogId;
  113. // 重新获取作物信息(摘取后数据已更新)
  114. $crop->refresh();
  115. Log::info('偷菜摘取成功', [
  116. 'stealer_id' => $stealerId,
  117. 'crop_id' => $crop->id,
  118. 'pick_amount' => $actualPickAmount,
  119. 'item_id' => $itemId,
  120. 'pick_log_id' => $pickLogId,
  121. ]);
  122. }
  123. }
  124. // 9. 记录偷菜日志
  125. $stealLog = PetStealLog::create([
  126. 'stealer_user_id' => $stealerId,
  127. 'stealer_pet_id' => $stealerPet->id,
  128. 'target_user_id' => $targetUserId,
  129. 'target_pet_id' => $targetPet?->id,
  130. 'land_id' => $landId,
  131. 'crop_id' => $crop->id,
  132. 'success' => $stealSuccess,
  133. 'defended' => $defended,
  134. 'steal_amount' => $actualPickAmount,
  135. 'stealer_stamina_cost' => 20,
  136. 'target_stamina_cost' => $targetStaminaCost,
  137. 'pick_log_id' => $pickLogId,
  138. ]);
  139. // 10. 记录日志
  140. Log::info('宠物偷菜操作完成', [
  141. 'stealer_id' => $stealerId,
  142. 'target_id' => $targetUserId,
  143. 'land_id' => $landId,
  144. 'success' => $stealSuccess,
  145. 'defended' => $defended,
  146. 'steal_amount' => $actualPickAmount,
  147. ]);
  148. // 13. 返回结果
  149. if ($stealSuccess) {
  150. return StealResultDto::success(
  151. $actualPickAmount,
  152. $itemId ?? 0,
  153. $pickLogId,
  154. $stealLog->id,
  155. $stealerPet->id,
  156. $targetPet?->id
  157. );
  158. } else {
  159. return StealResultDto::defended(
  160. $stealLog->id,
  161. $stealerPet->id,
  162. $targetPet?->id
  163. );
  164. }
  165. } catch (\Exception $e) {
  166. // 记录失败日志
  167. $stealLog = PetStealLog::create([
  168. 'stealer_user_id' => $stealerId,
  169. 'stealer_pet_id' => $stealerPet?->id ?? null,
  170. 'target_user_id' => $targetUserId,
  171. 'land_id' => $landId,
  172. 'success' => false,
  173. 'defended' => false,
  174. 'steal_amount' => 0,
  175. 'stealer_stamina_cost' => 20,
  176. 'target_stamina_cost' => 0,
  177. 'fail_reason' => $e->getMessage(),
  178. ]);
  179. // 偷菜是瞬时操作,无需恢复状态
  180. Log::warning('宠物偷菜操作失败', [
  181. 'stealer_id' => $stealerId,
  182. 'target_id' => $targetUserId,
  183. 'land_id' => $landId,
  184. 'error' => $e->getMessage(),
  185. ]);
  186. // 重新抛出异常,不隐藏错误
  187. throw $e;
  188. }
  189. }
  190. /**
  191. * 检查是否可以偷菜
  192. *
  193. * @param int $stealerId 偷菜者用户ID
  194. * @param int $targetUserId 被偷者用户ID
  195. * @param int $landId 土地ID
  196. * @return array 检查结果 ['can_steal' => bool, 'reason' => string]
  197. */
  198. public static function canSteal(int $stealerId, int $targetUserId, int $landId): array
  199. {
  200. // 1. 不能偷自己的菜
  201. if ($stealerId === $targetUserId) {
  202. return [ 'can_steal' => false, 'reason' => '不能偷自己的菜' ];
  203. }
  204. // 2. 检查偷菜者是否有可用宠物
  205. $stealerPet = PetUser::where('user_id', $stealerId)
  206. ->where('status', PetStatus::NORMAL)
  207. ->where('stamina', '>=', 20)
  208. ->first();
  209. if (!$stealerPet) {
  210. return [ 'can_steal' => false, 'reason' => '没有可用的宠物或体力不足' ];
  211. }
  212. // 3. 检查土地是否存在
  213. $land = FarmLand::where('id', $landId)
  214. ->where('user_id', $targetUserId)
  215. ->first();
  216. if (!$land) {
  217. // 检查土地是否存在但属于其他用户
  218. $actualLand = FarmLand::where('id', $landId)->first();
  219. if ($actualLand) {
  220. return [
  221. 'can_steal' => false, 'reason' => "土地{$landId}存在但属于用户{$actualLand->user_id},不属于目标用户{$targetUserId}"
  222. ];
  223. }
  224. return [ 'can_steal' => false, 'reason' => "土地{$landId}不存在" ];
  225. }
  226. // 4. 检查是否有成熟作物
  227. $crop = FarmCrop::where('land_id', $landId)
  228. ->where('growth_stage', GROWTH_STAGE::MATURE)
  229. ->first();
  230. if (!$crop) {
  231. return [ 'can_steal' => false, 'reason' => '没有可偷的成熟作物' ];
  232. }
  233. // 5. 检查偷菜次数限制
  234. if (!PetStealLog::canStealLand($landId)) {
  235. return [ 'can_steal' => false, 'reason' => '该作物已被偷取5次,无法继续偷取' ];
  236. }
  237. // 6. 检查作物是否可摘取
  238. if ($crop->pickable_amount <= 0) {
  239. return [ 'can_steal' => false, 'reason' => '没有可摘取的作物' ];
  240. }
  241. return [ 'can_steal' => true, 'reason' => '' ];
  242. }
  243. /**
  244. * 获取土地的偷菜信息
  245. *
  246. * @param int $landId 土地ID
  247. * @return array 偷菜信息
  248. */
  249. public static function getStealInfo(int $landId): array
  250. {
  251. $successfulStealCount = PetStealLog::getSuccessfulStealCount($landId);
  252. $canSteal = PetStealLog::canStealLand($landId);
  253. return [
  254. 'land_id' => $landId,
  255. 'successful_steal_count' => $successfulStealCount,
  256. 'max_steal_times' => 5,
  257. 'remaining_steal_times' => max(0, 5 - $successfulStealCount),
  258. 'can_steal' => $canSteal,
  259. ];
  260. }
  261. /**
  262. * 获取用户的偷菜统计
  263. *
  264. * @param int $userId 用户ID
  265. * @return array 偷菜统计信息
  266. */
  267. public static function getStealStats(int $userId): array
  268. {
  269. $todayStealCount = PetStealLog::getTodayStealCount($userId);
  270. $todayBeingStolenCount = PetStealLog::getTodayBeingStolenCount($userId);
  271. // 获取总偷菜次数
  272. $totalStealCount = PetStealLog::where('stealer_user_id', $userId)->count();
  273. $totalSuccessfulStealCount = PetStealLog::where('stealer_user_id', $userId)
  274. ->where('success', true)
  275. ->count();
  276. // 获取总被偷次数
  277. $totalBeingStolenCount = PetStealLog::where('target_user_id', $userId)->count();
  278. $totalBeingDefendedCount = PetStealLog::where('target_user_id', $userId)
  279. ->where('defended', true)
  280. ->count();
  281. return [
  282. 'user_id' => $userId,
  283. 'today' => [
  284. 'steal_count' => $todayStealCount,
  285. 'being_stolen_count' => $todayBeingStolenCount,
  286. ],
  287. 'total' => [
  288. 'steal_count' => $totalStealCount,
  289. 'successful_steal_count' => $totalSuccessfulStealCount,
  290. 'steal_success_rate' => $totalStealCount > 0 ? round($totalSuccessfulStealCount / $totalStealCount * 100, 2) : 0,
  291. 'being_stolen_count' => $totalBeingStolenCount,
  292. 'defended_count' => $totalBeingDefendedCount,
  293. 'defend_success_rate' => $totalBeingStolenCount > 0 ? round($totalBeingDefendedCount / $totalBeingStolenCount * 100, 2) : 0,
  294. ],
  295. ];
  296. }
  297. }