PetService.php 15 KB

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