PetService.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  1. <?php
  2. namespace App\Module\Pet\Services;
  3. use App\Module\Pet\Dtos\PetDataDto;
  4. use App\Module\Pet\Enums\PetStatus;
  5. use App\Module\Pet\Factories\PetDtoFactory;
  6. use App\Module\Pet\Logic\PetLogic;
  7. use App\Module\Pet\Models\PetUser;
  8. use App\Module\Pet\Models\PetSkill;
  9. use App\Module\Pet\Models\PetLevelConfig;
  10. use Exception;
  11. use Illuminate\Support\Collection;
  12. use Illuminate\Support\Facades\DB;
  13. use Illuminate\Support\Facades\Log;
  14. use UCore\Db\Helper;
  15. /**
  16. * 宠物服务类
  17. *
  18. * 提供宠物相关的服务,包括获取用户宠物列表、创建宠物、宠物升级、
  19. * 宠物喂养、宠物洗髓、宠物技能使用等功能。该类是宠物模块对外提供
  20. * 服务的主要入口,封装了宠物操作的复杂逻辑。
  21. *
  22. * 注意:本服务类不负责事务管理,调用方需要自行开启和管理事务。
  23. * 对于涉及数据修改的方法,强烈建议在调用前开启事务,以确保数据一致性。
  24. */
  25. class PetService
  26. {
  27. /**
  28. * 获取用户宠物列表
  29. *
  30. * @param int $userId 用户ID
  31. * @param array $filters 过滤条件
  32. * @return Collection|PetUser[]
  33. */
  34. public static function getUserPets(int $userId, array $filters = []): Collection
  35. {
  36. $query = PetUser::where('user_id', $userId);
  37. // 应用过滤条件
  38. if (isset($filters['status'])) {
  39. $query->where('status', $filters['status']);
  40. }
  41. if (isset($filters['grade'])) {
  42. $query->where('grade', $filters['grade']);
  43. }
  44. if (isset($filters['min_level'])) {
  45. $query->where('level', '>=', $filters['min_level']);
  46. }
  47. if (isset($filters['max_level'])) {
  48. $query->where('level', '<=', $filters['max_level']);
  49. }
  50. return $query->get();
  51. }
  52. /**
  53. * 创建宠物
  54. *
  55. * 注意:调用此方法前,请确保已开启数据库事务
  56. *
  57. * @param int $userId 用户ID
  58. * @param string $name 宠物名称
  59. * @param int|null $grade 宠物品阶,如果为null则随机生成(1一品,2二品,3三品,4四品)
  60. * @param array $options 其他选项
  61. * @return array 创建结果
  62. * @throws Exception
  63. */
  64. public static function createPet(int $userId, string $name, ?int $grade = null, array $options = []): array
  65. {
  66. // 验证事务是否已开启
  67. Helper::check_tr();
  68. try {
  69. // 创建宠物逻辑
  70. $petLogic = new PetLogic();
  71. // 创建宠物
  72. $result = $petLogic->createPet($userId, $name, $grade, $options);
  73. return [
  74. 'success' => true,
  75. 'pet_id' => $result['pet_id'],
  76. 'name' => $name,
  77. 'grade' => $result['grade'],
  78. 'level' => 1,
  79. 'message' => '宠物创建成功'
  80. ];
  81. } catch (Exception $e) {
  82. Log::error('创建宠物失败', [
  83. 'user_id' => $userId,
  84. 'name' => $name,
  85. 'error' => $e->getMessage()
  86. ]);
  87. throw $e;
  88. }
  89. }
  90. /**
  91. * 宠物升级
  92. *
  93. * 注意:调用此方法前,请确保已开启数据库事务
  94. *
  95. * @param int $userId 用户ID
  96. * @param int $petId 宠物ID
  97. * @return array 升级结果
  98. * @throws Exception
  99. */
  100. public static function levelUpPet(int $userId, int $petId): array
  101. {
  102. // 验证事务是否已开启
  103. Helper::check_tr();
  104. try {
  105. // 宠物逻辑
  106. $petLogic = new PetLogic();
  107. // 获取宠物信息
  108. $pet = PetUser::where('id', $petId)
  109. ->where('user_id', $userId)
  110. ->first();
  111. if (!$pet) {
  112. throw new Exception("宠物不存在或不属于该用户");
  113. }
  114. // 执行升级逻辑
  115. $result = $petLogic->levelUpPet($petId);
  116. return [
  117. 'success' => true,
  118. 'pet_id' => $petId,
  119. 'old_level' => $result['old_level'],
  120. 'new_level' => $result['new_level'],
  121. 'unlocked_skills' => $result['unlocked_skills'] ?? [],
  122. 'message' => '宠物升级成功'
  123. ];
  124. } catch (Exception $e) {
  125. Log::error('宠物升级失败', [
  126. 'user_id' => $userId,
  127. 'pet_id' => $petId,
  128. 'error' => $e->getMessage()
  129. ]);
  130. throw $e;
  131. }
  132. }
  133. /**
  134. * 宠物喂养
  135. *
  136. * 注意:调用此方法前,请确保已开启数据库事务
  137. *
  138. * @param int $userId 用户ID
  139. * @param int $petId 宠物ID
  140. * @param int $itemId 物品ID(狗粮)
  141. * @param int $amount 数量
  142. * @return array 喂养结果
  143. * @throws Exception
  144. */
  145. public static function feedPet(int $userId, int $petId, int $itemId, int $amount = 1): array
  146. {
  147. // 验证事务是否已开启
  148. Helper::check_tr();
  149. try {
  150. // 宠物逻辑
  151. $petLogic = new PetLogic();
  152. // 获取宠物信息
  153. $pet = PetUser::where('id', $petId)
  154. ->where('user_id', $userId)
  155. ->first();
  156. if (!$pet) {
  157. throw new Exception("宠物不存在或不属于该用户");
  158. }
  159. // 检查宠物状态
  160. if ($pet->status !== PetStatus::NORMAL) {
  161. throw new Exception("宠物当前状态不允许喂养");
  162. }
  163. // 执行喂养逻辑
  164. $result = $petLogic->feedPet($petId, $itemId, $amount);
  165. return [
  166. 'success' => true,
  167. 'pet_id' => $petId,
  168. 'item_id' => $itemId,
  169. 'amount' => $amount,
  170. 'exp_gained' => $result['exp_gained'],
  171. 'stamina_gained' => $result['stamina_gained'],
  172. 'level_up' => $result['level_up'],
  173. 'message' => '宠物喂养成功'
  174. ];
  175. } catch (Exception $e) {
  176. Log::error('宠物喂养失败', [
  177. 'user_id' => $userId,
  178. 'pet_id' => $petId,
  179. 'item_id' => $itemId,
  180. 'amount' => $amount,
  181. 'error' => $e->getMessage()
  182. ]);
  183. throw $e;
  184. }
  185. }
  186. /**
  187. * 宠物洗髓
  188. *
  189. * 注意:调用此方法前,请确保已开启数据库事务
  190. *
  191. * @param int $userId 用户ID
  192. * @param int $petId 宠物ID
  193. * @param int $itemId 洗髓道具ID,如果为0则使用钻石
  194. * @return array 洗髓结果
  195. * @throws Exception
  196. */
  197. public static function remouldPet(int $userId, int $petId, int $itemId = 0): array
  198. {
  199. // 验证事务是否已开启
  200. Helper::check_tr();
  201. try {
  202. // 宠物逻辑
  203. $petLogic = new PetLogic();
  204. // 获取宠物信息
  205. $pet = PetUser::where('id', $petId)
  206. ->where('user_id', $userId)
  207. ->first();
  208. if (!$pet) {
  209. throw new Exception("宠物不存在或不属于该用户");
  210. }
  211. // 检查宠物状态
  212. if ($pet->status !== PetStatus::NORMAL) {
  213. throw new Exception("宠物当前状态不允许洗髓");
  214. }
  215. // 执行洗髓逻辑
  216. $result = $petLogic->remouldPet($petId, $itemId);
  217. return [
  218. 'success' => true,
  219. 'pet_id' => $petId,
  220. 'old_grade' => $result['old_grade'],
  221. 'new_grade' => $result['new_grade'],
  222. 'item_id' => $itemId,
  223. 'message' => '宠物洗髓成功'
  224. ];
  225. } catch (Exception $e) {
  226. Log::error('宠物洗髓失败', [
  227. 'user_id' => $userId,
  228. 'pet_id' => $petId,
  229. 'item_id' => $itemId,
  230. 'error' => $e->getMessage()
  231. ]);
  232. throw $e;
  233. }
  234. }
  235. /**
  236. * 使用宠物技能
  237. *
  238. * 注意:调用此方法前,请确保已开启数据库事务
  239. *
  240. * @param int $userId 用户ID
  241. * @param int $petId 宠物ID
  242. * @param int $skillId 技能ID
  243. * @param array $params 技能参数
  244. * @return array 技能使用结果
  245. * @throws Exception
  246. */
  247. public static function useSkill(int $userId, int $petId, int $skillId, array $params = []): array
  248. {
  249. // 验证事务是否已开启
  250. Helper::check_tr();
  251. try {
  252. // 宠物逻辑
  253. $petLogic = new PetLogic();
  254. // 获取宠物信息
  255. $pet = PetUser::where('id', $petId)
  256. ->where('user_id', $userId)
  257. ->first();
  258. if (!$pet) {
  259. throw new Exception("宠物不存在或不属于该用户");
  260. }
  261. // 获取技能信息
  262. $skill = PetSkill::find($skillId);
  263. if (!$skill) {
  264. throw new Exception("技能不存在");
  265. }
  266. // 检查宠物状态
  267. if ($pet->status !== PetStatus::NORMAL) {
  268. throw new Exception("宠物当前状态不允许使用技能");
  269. }
  270. // 检查宠物等级是否满足技能要求
  271. if ($pet->level < $skill->min_level) {
  272. throw new Exception("宠物等级不足,无法使用该技能");
  273. }
  274. // 检查宠物体力是否足够
  275. if ($pet->stamina < $skill->stamina_cost) {
  276. throw new Exception("宠物体力不足,无法使用该技能");
  277. }
  278. // 执行技能使用逻辑
  279. $result = $petLogic->useSkill($petId, $skillId, $params);
  280. return [
  281. 'success' => true,
  282. 'pet_id' => $petId,
  283. 'skill_id' => $skillId,
  284. 'stamina_cost' => $skill->stamina_cost,
  285. 'effect_result' => $result['effect_result'],
  286. 'message' => '技能使用成功'
  287. ];
  288. } catch (Exception $e) {
  289. Log::error('使用宠物技能失败', [
  290. 'user_id' => $userId,
  291. 'pet_id' => $petId,
  292. 'skill_id' => $skillId,
  293. 'params' => $params,
  294. 'error' => $e->getMessage()
  295. ]);
  296. throw $e;
  297. }
  298. }
  299. /**
  300. * 获取宠物状态
  301. *
  302. * @param int $userId 用户ID
  303. * @param int $petId 宠物ID
  304. * @return PetDataDto 宠物状态信息,参考DataPet结构,包括技能情况,不包含争霸赛情况
  305. * @throws Exception
  306. */
  307. public static function getPetStatus(int $userId, int $petId): PetDataDto
  308. {
  309. // 获取宠物信息
  310. $pet = PetUser::where('id', $petId)
  311. ->where('user_id', $userId)
  312. ->first();
  313. if (!$pet) {
  314. throw new Exception("宠物不存在或不属于该用户");
  315. }
  316. // 计算宠物战力
  317. $petLogic = new PetLogic();
  318. $fightingCapacity = $petLogic->calculatePower($petId);
  319. // 使用工厂类创建宠物DTO
  320. $petDto = PetDtoFactory::createPetDataDto($pet, $fightingCapacity);
  321. // 直接返回DTO对象
  322. return $petDto;
  323. }
  324. /**
  325. * 更新宠物状态
  326. *
  327. * 注意:调用此方法前,请确保已开启数据库事务
  328. *
  329. * @param int $userId 用户ID
  330. * @param int $petId 宠物ID
  331. * @param PetStatus $status 新状态
  332. * @param string $reason 变更原因
  333. * @return bool 是否更新成功
  334. * @throws Exception
  335. */
  336. public static function updatePetStatus(int $userId, int $petId, PetStatus $status, string $reason = ''): bool
  337. {
  338. // 验证事务是否已开启
  339. Helper::check_tr();
  340. try {
  341. // 宠物逻辑
  342. $petLogic = new PetLogic();
  343. // 获取宠物信息
  344. $pet = PetUser::where('id', $petId)
  345. ->where('user_id', $userId)
  346. ->first();
  347. if (!$pet) {
  348. throw new Exception("宠物不存在或不属于该用户");
  349. }
  350. // 记录旧状态
  351. $oldStatus = $pet->status;
  352. // 执行状态变更逻辑
  353. $result = $petLogic->changeStatus($petId, $status, $reason);
  354. return true;
  355. } catch (Exception $e) {
  356. Log::error('更新宠物状态失败', [
  357. 'user_id' => $userId,
  358. 'pet_id' => $petId,
  359. 'status' => $status->value,
  360. 'reason' => $reason,
  361. 'error' => $e->getMessage()
  362. ]);
  363. throw $e;
  364. }
  365. }
  366. /**
  367. * 恢复宠物体力
  368. *
  369. * 注意:调用此方法前,请确保已开启数据库事务
  370. *
  371. * @param int $userId 用户ID
  372. * @param int $petId 宠物ID
  373. * @param int $minutes 经过的分钟数
  374. * @return array 恢复结果
  375. * @throws Exception
  376. */
  377. public static function recoverStamina(int $userId, int $petId, int $minutes): array
  378. {
  379. // 验证事务是否已开启
  380. Helper::check_tr();
  381. try {
  382. // 宠物逻辑
  383. $petLogic = new PetLogic();
  384. // 获取宠物信息
  385. $pet = PetUser::where('id', $petId)
  386. ->where('user_id', $userId)
  387. ->first();
  388. if (!$pet) {
  389. throw new Exception("宠物不存在或不属于该用户");
  390. }
  391. // 执行体力恢复逻辑
  392. $recoveredStamina = $petLogic->recoverStamina($petId, $minutes);
  393. return [
  394. 'success' => true,
  395. 'pet_id' => $petId,
  396. 'recovered_stamina' => $recoveredStamina,
  397. 'current_stamina' => $pet->fresh()->stamina,
  398. 'message' => '宠物体力恢复成功'
  399. ];
  400. } catch (Exception $e) {
  401. Log::error('恢复宠物体力失败', [
  402. 'user_id' => $userId,
  403. 'pet_id' => $petId,
  404. 'minutes' => $minutes,
  405. 'error' => $e->getMessage()
  406. ]);
  407. throw $e;
  408. }
  409. }
  410. /**
  411. * 获取宠物可用技能列表
  412. *
  413. * @param int $userId 用户ID
  414. * @param int $petId 宠物ID
  415. * @return array 技能列表
  416. * @throws Exception
  417. */
  418. public static function getPetSkills(int $userId, int $petId): array
  419. {
  420. // 获取宠物信息
  421. $pet = PetUser::where('id', $petId)
  422. ->where('user_id', $userId)
  423. ->first();
  424. if (!$pet) {
  425. throw new Exception("宠物不存在或不属于该用户");
  426. }
  427. // 获取所有技能
  428. $allSkills = PetSkill::all();
  429. // 筛选出宠物等级可用的技能
  430. $availableSkills = $allSkills->filter(function ($skill) use ($pet) {
  431. return $pet->level >= $skill->min_level;
  432. });
  433. // 获取技能使用记录,计算冷却时间
  434. $skillLogs = $pet->skillLogs()
  435. ->orderBy('used_at', 'desc')
  436. ->get()
  437. ->groupBy('skill_id');
  438. $result = [];
  439. foreach ($availableSkills as $skill) {
  440. $lastUsed = null;
  441. $cooldownRemaining = 0;
  442. if (isset($skillLogs[$skill->id]) && $skillLogs[$skill->id]->count() > 0) {
  443. $lastUsed = $skillLogs[$skill->id][0]->used_at;
  444. $cooldownSeconds = $skill->cool_down;
  445. $secondsSinceLastUse = now()->diffInSeconds($lastUsed);
  446. $cooldownRemaining = max(0, $cooldownSeconds - $secondsSinceLastUse);
  447. }
  448. $result[] = [
  449. 'skill_id' => $skill->id,
  450. 'skill_name' => $skill->skill_name,
  451. 'stamina_cost' => $skill->stamina_cost,
  452. 'cool_down' => $skill->cool_down,
  453. 'effect_desc' => $skill->effect_desc,
  454. 'min_level' => $skill->min_level,
  455. 'last_used' => $lastUsed ? $lastUsed->format('Y-m-d H:i:s') : null,
  456. 'cooldown_remaining' => $cooldownRemaining,
  457. 'is_available' => $cooldownRemaining === 0 && $pet->stamina >= $skill->stamina_cost
  458. ];
  459. }
  460. return $result;
  461. }
  462. }