PetLogic.php 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. <?php
  2. namespace App\Module\Pet\Logic;
  3. use App\Module\GameItems\Services\ItemService;
  4. use App\Module\Pet\Enums\PetStatus;
  5. use App\Module\Pet\Events\PetLevelUpEvent;
  6. use App\Module\Pet\Events\PetRemouldEvent;
  7. use App\Module\Pet\Events\PetSkillUsedEvent;
  8. use App\Module\Pet\Events\PetStatusChangedEvent;
  9. use App\Module\Pet\Models\PetConfig;
  10. use App\Module\Pet\Models\PetLevelConfig;
  11. use App\Module\Pet\Models\PetRemouldLog;
  12. use App\Module\Pet\Models\PetSkill;
  13. use App\Module\Pet\Models\PetSkillLog;
  14. use App\Module\Pet\Models\PetUser;
  15. use App\Module\Pet\Validators\PetFoodValidator;
  16. use Exception;
  17. use Illuminate\Support\Facades\DB;
  18. use Illuminate\Support\Facades\Log;
  19. /**
  20. * 宠物逻辑类
  21. *
  22. * 处理宠物相关的业务逻辑,包括创建宠物、升级宠物、喂养宠物、
  23. * 洗髓宠物、使用技能等功能。该类是宠物模块内部使用的核心逻辑类,
  24. * 不对外提供服务,由PetService调用。
  25. */
  26. class PetLogic
  27. {
  28. /**
  29. * 物品服务
  30. *
  31. * @var ItemService
  32. */
  33. protected $itemService;
  34. /**
  35. * 宠物食物验证器
  36. *
  37. * @var PetFoodValidator
  38. */
  39. protected $petFoodValidator;
  40. /**
  41. * 构造函数
  42. */
  43. public function __construct()
  44. {
  45. $this->itemService = new ItemService();
  46. $this->petFoodValidator = new PetFoodValidator();
  47. }
  48. /**
  49. * 创建宠物
  50. *
  51. * @param int $userId 用户ID
  52. * @param string $name 宠物名称
  53. * @param int|null $grade 宠物品阶,如果为null则随机生成(1一品,2二品,3三品,4四品)
  54. * @param array $options 其他选项
  55. * @return array 创建结果
  56. * @throws Exception
  57. */
  58. public function createPet(int $userId, string $name, ?int $grade = null, array $options = []): array
  59. {
  60. // 验证宠物名称
  61. if (empty($name) || mb_strlen($name) > 20) {
  62. throw new Exception("宠物名称不能为空且不能超过20个字符");
  63. }
  64. // 检查用户宠物数量限制
  65. $petCount = PetUser::where('user_id', $userId)->count();
  66. $maxPets = config('pet.max_pets_per_user', 3);
  67. if ($petCount >= $maxPets) {
  68. throw new Exception("已达到最大宠物数量限制: {$maxPets}");
  69. }
  70. // 如果未指定品阶,则根据概率随机生成
  71. if ($grade === null) {
  72. $grade = $this->generateRandomGrade();
  73. }
  74. // 获取宠物配置
  75. $petConfig = PetConfig::where('pet_type', $options['pet_type'] ?? 'default')->first();
  76. if (!$petConfig) {
  77. $petConfig = PetConfig::first(); // 使用默认配置
  78. if (!$petConfig) {
  79. throw new Exception("宠物配置不存在");
  80. }
  81. }
  82. // 创建宠物
  83. $pet = new PetUser();
  84. $pet->user_id = $userId;
  85. $pet->name = $name;
  86. $pet->grade = $grade;
  87. $pet->level = 1;
  88. $pet->experience = 0;
  89. $pet->stamina = $petConfig->stamina_max ?? 100;
  90. $pet->status = PetStatus::NORMAL;
  91. $pet->save();
  92. Log::info('宠物创建成功', [
  93. 'user_id' => $userId,
  94. 'pet_id' => $pet->id,
  95. 'name' => $name,
  96. 'grade' => $grade->value
  97. ]);
  98. return [
  99. 'pet_id' => $pet->id,
  100. 'grade' => $grade
  101. ];
  102. }
  103. /**
  104. * 根据概率随机生成宠物品阶
  105. *
  106. * @return int 品阶值(1一品,2二品,3三品,4四品)
  107. */
  108. protected function generateRandomGrade(): int
  109. {
  110. $probabilities = config('pet.grade_probability', [
  111. 1 => 0.6, // 一品阶:60%
  112. 2 => 0.25, // 二品阶:25%
  113. 3 => 0.1, // 三品阶:10%
  114. 4 => 0.05 // 四品阶:5%
  115. ]);
  116. $rand = mt_rand(1, 100) / 100;
  117. $cumulative = 0;
  118. foreach ($probabilities as $grade => $probability) {
  119. $cumulative += $probability;
  120. if ($rand <= $cumulative) {
  121. return $grade;
  122. }
  123. }
  124. // 默认返回一品
  125. return 1;
  126. }
  127. /**
  128. * 宠物升级
  129. *
  130. * @param int $petId 宠物ID
  131. * @return array 升级结果
  132. * @throws Exception
  133. */
  134. public function levelUpPet(int $petId): array
  135. {
  136. // 获取宠物信息
  137. $pet = PetUser::findOrFail($petId);
  138. // 获取当前等级配置
  139. $currentLevelConfig = PetLevelConfig::where('level', $pet->level)->first();
  140. if (!$currentLevelConfig) {
  141. throw new Exception("宠物等级配置不存在");
  142. }
  143. // 获取下一级配置
  144. $nextLevelConfig = PetLevelConfig::where('level', $pet->level + 1)->first();
  145. if (!$nextLevelConfig) {
  146. throw new Exception("宠物已达到最大等级");
  147. }
  148. // 检查经验值是否足够
  149. if ($pet->experience < $nextLevelConfig->exp_required) {
  150. throw new Exception("经验值不足,无法升级");
  151. }
  152. // 记录旧等级
  153. $oldLevel = $pet->level;
  154. // 升级宠物
  155. $pet->level += 1;
  156. $pet->save();
  157. // 获取新解锁的技能
  158. $unlockedSkills = [];
  159. if ($nextLevelConfig->unlock_skills) {
  160. $unlockedSkills = json_decode($nextLevelConfig->unlock_skills, true);
  161. }
  162. // 触发宠物升级事件
  163. event(new PetLevelUpEvent(
  164. $pet->user_id,
  165. $pet->id,
  166. $oldLevel,
  167. $pet->level,
  168. $unlockedSkills
  169. ));
  170. Log::info('宠物升级成功', [
  171. 'pet_id' => $petId,
  172. 'old_level' => $oldLevel,
  173. 'new_level' => $pet->level,
  174. 'unlocked_skills' => $unlockedSkills
  175. ]);
  176. return [
  177. 'old_level' => $oldLevel,
  178. 'new_level' => $pet->level,
  179. 'unlocked_skills' => $unlockedSkills
  180. ];
  181. }
  182. /**
  183. * 宠物喂养
  184. *
  185. * @param int $petId 宠物ID
  186. * @param int $itemId 物品ID(狗粮)
  187. * @param int $amount 数量
  188. * @return array 喂养结果
  189. * @throws Exception
  190. */
  191. public function feedPet(int $petId, int $itemId, int $amount): array
  192. {
  193. // 获取宠物信息
  194. $pet = PetUser::findOrFail($petId);
  195. // 验证物品是否为宠物口粮
  196. if (!$this->petFoodValidator->validate($itemId, [])) {
  197. throw new Exception("该物品不是宠物口粮");
  198. }
  199. // 获取物品信息
  200. $item = $this->itemService->getItemInfo($itemId);
  201. if (!$item) {
  202. throw new Exception("物品不存在");
  203. }
  204. // 消耗物品
  205. $consumeResult = $this->itemService->consumeItem(
  206. $pet->user_id,
  207. $itemId,
  208. null,
  209. $amount,
  210. [
  211. 'source_type' => 'pet_feed',
  212. 'source_id' => $petId,
  213. 'details' => ['pet_id' => $petId]
  214. ]
  215. );
  216. if (!$consumeResult['success']) {
  217. throw new Exception("物品消耗失败: " . ($consumeResult['message'] ?? '未知错误'));
  218. }
  219. // 计算获得的经验值和体力
  220. $expGained = $item->pet_exp * $amount;
  221. $staminaGained = $item->pet_power * $amount;
  222. // 更新宠物状态为喂养中
  223. $oldStatus = $pet->status;
  224. $pet->status = PetStatus::FEEDING;
  225. $pet->save();
  226. // 触发宠物状态变更事件
  227. event(new PetStatusChangedEvent(
  228. $pet->user_id,
  229. $pet->id,
  230. $oldStatus,
  231. PetStatus::FEEDING,
  232. 'pet_feed',
  233. ['item_id' => $itemId, 'amount' => $amount]
  234. ));
  235. // 增加经验值
  236. $levelUpOccurred = $this->addExperience($petId, $expGained);
  237. // 恢复体力
  238. $this->addStamina($petId, $staminaGained);
  239. // 喂养完成后恢复正常状态
  240. $pet->refresh();
  241. $pet->status = PetStatus::NORMAL;
  242. $pet->save();
  243. // 触发宠物状态变更事件
  244. event(new PetStatusChangedEvent(
  245. $pet->user_id,
  246. $pet->id,
  247. PetStatus::FEEDING,
  248. PetStatus::NORMAL,
  249. 'pet_feed_complete',
  250. ['item_id' => $itemId, 'amount' => $amount]
  251. ));
  252. Log::info('宠物喂养成功', [
  253. 'pet_id' => $petId,
  254. 'item_id' => $itemId,
  255. 'amount' => $amount,
  256. 'exp_gained' => $expGained,
  257. 'stamina_gained' => $staminaGained,
  258. 'level_up' => $levelUpOccurred
  259. ]);
  260. return [
  261. 'exp_gained' => $expGained,
  262. 'stamina_gained' => $staminaGained,
  263. 'level_up' => $levelUpOccurred
  264. ];
  265. }
  266. /**
  267. * 增加宠物经验值
  268. *
  269. * @param int $petId 宠物ID
  270. * @param int $expAmount 经验值数量
  271. * @return bool 是否触发升级
  272. */
  273. protected function addExperience(int $petId, int $expAmount): bool
  274. {
  275. // 获取宠物信息
  276. $pet = PetUser::findOrFail($petId);
  277. // 增加经验值
  278. $pet->experience += $expAmount;
  279. $pet->save();
  280. // 检查是否可以升级
  281. $nextLevelConfig = PetLevelConfig::where('level', $pet->level + 1)->first();
  282. if ($nextLevelConfig && $pet->experience >= $nextLevelConfig->exp_required) {
  283. try {
  284. $this->levelUpPet($petId);
  285. return true;
  286. } catch (Exception $e) {
  287. Log::error('宠物升级失败', [
  288. 'pet_id' => $petId,
  289. 'error' => $e->getMessage()
  290. ]);
  291. return false;
  292. }
  293. }
  294. return false;
  295. }
  296. /**
  297. * 增加宠物体力
  298. *
  299. * @param int $petId 宠物ID
  300. * @param int $staminaAmount 体力数量
  301. * @return int 实际增加的体力值
  302. */
  303. protected function addStamina(int $petId, int $staminaAmount): int
  304. {
  305. // 获取宠物信息
  306. $pet = PetUser::findOrFail($petId);
  307. // 获取宠物等级配置
  308. $levelConfig = PetLevelConfig::where('level', $pet->level)->first();
  309. $maxStamina = $levelConfig ? $levelConfig->stamina_max : 100;
  310. // 计算实际增加的体力值
  311. $oldStamina = $pet->stamina;
  312. $newStamina = min($maxStamina, $oldStamina + $staminaAmount);
  313. $actualGained = $newStamina - $oldStamina;
  314. // 更新体力值
  315. $pet->stamina = $newStamina;
  316. $pet->save();
  317. return $actualGained;
  318. }
  319. /**
  320. * 宠物洗髓
  321. *
  322. * @param int $petId 宠物ID
  323. * @param int $itemId 洗髓道具ID,如果为0则使用钻石
  324. * @return array 洗髓结果
  325. * @throws Exception
  326. */
  327. public function remouldPet(int $petId, int $itemId = 0): array
  328. {
  329. // 获取宠物信息
  330. $pet = PetUser::findOrFail($petId);
  331. // 记录旧品阶
  332. $oldGrade = $pet->grade;
  333. // 如果使用道具
  334. if ($itemId > 0) {
  335. // 验证物品是否为洗髓道具
  336. $remouldItemId = config('pet.remould_cost.item_id');
  337. if ($itemId != $remouldItemId) {
  338. throw new Exception("该物品不是洗髓道具");
  339. }
  340. // 消耗物品
  341. $consumeResult = $this->itemService->consumeItem(
  342. $pet->user_id,
  343. $itemId,
  344. null,
  345. 1,
  346. [
  347. 'source_type' => 'pet_remould',
  348. 'source_id' => $petId,
  349. 'details' => ['pet_id' => $petId]
  350. ]
  351. );
  352. if (!$consumeResult['success']) {
  353. throw new Exception("物品消耗失败: " . ($consumeResult['message'] ?? '未知错误'));
  354. }
  355. } else {
  356. // 使用钻石
  357. $diamondCost = config('pet.remould_cost.diamond', 50);
  358. // TODO: 消耗钻石的逻辑,需要调用相关服务
  359. // 这里需要根据实际项目中的钻石消耗方式进行实现
  360. // 暂时使用占位代码
  361. $diamondConsumeSuccess = true;
  362. if (!$diamondConsumeSuccess) {
  363. throw new Exception("钻石不足,无法进行洗髓");
  364. }
  365. }
  366. // 随机生成新品阶
  367. $newGrade = $this->generateRandomGrade();
  368. // 更新宠物品阶
  369. $pet->grade = $newGrade;
  370. $pet->save();
  371. // 记录洗髓日志
  372. PetRemouldLog::create([
  373. 'pet_id' => $petId,
  374. 'old_grade' => $oldGrade,
  375. 'new_grade' => $newGrade,
  376. 'remould_time' => now()
  377. ]);
  378. // 触发宠物洗髓事件
  379. event(new PetRemouldEvent(
  380. $pet->user_id,
  381. $pet->id,
  382. $oldGrade,
  383. $newGrade
  384. ));
  385. Log::info('宠物洗髓成功', [
  386. 'pet_id' => $petId,
  387. 'old_grade' => $oldGrade,
  388. 'new_grade' => $newGrade,
  389. 'item_id' => $itemId
  390. ]);
  391. return [
  392. 'old_grade' => $oldGrade,
  393. 'new_grade' => $newGrade
  394. ];
  395. }
  396. /**
  397. * 使用宠物技能
  398. *
  399. * @param int $petId 宠物ID
  400. * @param int $skillId 技能ID
  401. * @param array $params 技能参数
  402. * @return array 技能使用结果
  403. * @throws Exception
  404. */
  405. public function useSkill(int $petId, int $skillId, array $params = []): array
  406. {
  407. // 获取宠物信息
  408. $pet = PetUser::findOrFail($petId);
  409. // 获取技能信息
  410. $skill = PetSkill::findOrFail($skillId);
  411. // 检查技能冷却时间
  412. $lastUsed = PetSkillLog::where('pet_id', $petId)
  413. ->where('skill_id', $skillId)
  414. ->orderBy('used_at', 'desc')
  415. ->first();
  416. if ($lastUsed) {
  417. $cooldownSeconds = $skill->cool_down;
  418. $secondsSinceLastUse = now()->diffInSeconds($lastUsed->used_at);
  419. if ($secondsSinceLastUse < $cooldownSeconds) {
  420. $remainingCooldown = $cooldownSeconds - $secondsSinceLastUse;
  421. throw new Exception("技能冷却中,还需等待 {$remainingCooldown} 秒");
  422. }
  423. }
  424. // 检查体力是否足够
  425. if ($pet->stamina < $skill->stamina_cost) {
  426. throw new Exception("体力不足,无法使用技能");
  427. }
  428. // 消耗体力
  429. $pet->stamina -= $skill->stamina_cost;
  430. $pet->save();
  431. // 执行技能效果
  432. $effectResult = $this->executeSkillEffect($pet, $skill, $params);
  433. // 记录技能使用日志
  434. $skillLog = PetSkillLog::create([
  435. 'pet_id' => $petId,
  436. 'skill_id' => $skillId,
  437. 'used_at' => now(),
  438. 'effect_result' => json_encode($effectResult)
  439. ]);
  440. // 触发宠物技能使用事件
  441. event(new PetSkillUsedEvent(
  442. $pet->user_id,
  443. $pet->id,
  444. $skillId,
  445. $params
  446. ));
  447. Log::info('宠物技能使用成功', [
  448. 'pet_id' => $petId,
  449. 'skill_id' => $skillId,
  450. 'params' => $params,
  451. 'effect_result' => $effectResult
  452. ]);
  453. return [
  454. 'effect_result' => $effectResult
  455. ];
  456. }
  457. /**
  458. * 执行技能效果
  459. *
  460. * @param PetUser $pet 宠物对象
  461. * @param PetSkill $skill 技能对象
  462. * @param array $params 技能参数
  463. * @return array 技能效果结果
  464. */
  465. protected function executeSkillEffect(PetUser $pet, PetSkill $skill, array $params): array
  466. {
  467. // 根据技能名称执行不同的效果
  468. switch ($skill->skill_name) {
  469. case '自动收菜':
  470. // 调用农场模块的收获接口
  471. // 这里需要根据实际项目中的农场模块接口进行实现
  472. // 暂时使用占位代码
  473. $harvestResult = [
  474. 'success' => true,
  475. 'harvested_count' => mt_rand(1, 5),
  476. 'items' => []
  477. ];
  478. return $harvestResult;
  479. case '自动播种':
  480. // 调用农场模块的播种接口
  481. // 这里需要根据实际项目中的农场模块接口进行实现
  482. // 暂时使用占位代码
  483. $plantResult = [
  484. 'success' => true,
  485. 'planted_count' => mt_rand(1, 5),
  486. 'seeds' => []
  487. ];
  488. return $plantResult;
  489. case '灾害防护':
  490. // 设置农场模块的灾害防护标志
  491. // 这里需要根据实际项目中的农场模块接口进行实现
  492. // 暂时使用占位代码
  493. $protectResult = [
  494. 'success' => true,
  495. 'protected_type' => $params['disaster_type'] ?? 'all',
  496. 'duration' => 3600 // 1小时
  497. ];
  498. return $protectResult;
  499. default:
  500. return [
  501. 'success' => false,
  502. 'message' => '未知技能效果'
  503. ];
  504. }
  505. }
  506. /**
  507. * 变更宠物状态
  508. *
  509. * @param int $petId 宠物ID
  510. * @param PetStatus $status 新状态
  511. * @param string $reason 变更原因
  512. * @return bool 是否变更成功
  513. */
  514. public function changeStatus(int $petId, PetStatus $status, string $reason = ''): bool
  515. {
  516. // 获取宠物信息
  517. $pet = PetUser::findOrFail($petId);
  518. // 记录旧状态
  519. $oldStatus = $pet->status;
  520. // 如果状态相同,则不需要变更
  521. if ($oldStatus === $status) {
  522. return true;
  523. }
  524. // 更新状态
  525. $pet->status = $status;
  526. $pet->save();
  527. // 触发宠物状态变更事件
  528. event(new PetStatusChangedEvent(
  529. $pet->user_id,
  530. $pet->id,
  531. $oldStatus,
  532. $status,
  533. $reason
  534. ));
  535. Log::info('宠物状态变更成功', [
  536. 'pet_id' => $petId,
  537. 'old_status' => $oldStatus->value,
  538. 'new_status' => $status->value,
  539. 'reason' => $reason
  540. ]);
  541. return true;
  542. }
  543. /**
  544. * 恢复宠物体力
  545. *
  546. * @param int $petId 宠物ID
  547. * @param int $minutes 经过的分钟数
  548. * @return int 恢复的体力值
  549. */
  550. public function recoverStamina(int $petId, int $minutes): int
  551. {
  552. // 获取宠物信息
  553. $pet = PetUser::findOrFail($petId);
  554. // 获取宠物等级配置
  555. $levelConfig = PetLevelConfig::where('level', $pet->level)->first();
  556. $maxStamina = $levelConfig ? $levelConfig->stamina_max : 100;
  557. $recoveryRate = $levelConfig ? $levelConfig->stamina_recovery_rate : 5;
  558. // 计算恢复的体力值
  559. $recoveryAmount = $recoveryRate * $minutes;
  560. $oldStamina = $pet->stamina;
  561. $newStamina = min($maxStamina, $oldStamina + $recoveryAmount);
  562. $actualRecovered = $newStamina - $oldStamina;
  563. // 更新体力值
  564. if ($actualRecovered > 0) {
  565. $pet->stamina = $newStamina;
  566. $pet->save();
  567. Log::info('宠物体力恢复成功', [
  568. 'pet_id' => $petId,
  569. 'minutes' => $minutes,
  570. 'recovery_rate' => $recoveryRate,
  571. 'old_stamina' => $oldStamina,
  572. 'new_stamina' => $newStamina,
  573. 'actual_recovered' => $actualRecovered
  574. ]);
  575. }
  576. return $actualRecovered;
  577. }
  578. /**
  579. * 计算宠物战力
  580. *
  581. * @param int $petId 宠物ID
  582. * @return int 战力值
  583. */
  584. public function calculatePower(int $petId): int
  585. {
  586. // 获取宠物信息
  587. $pet = PetUser::findOrFail($petId);
  588. // 获取宠物等级配置
  589. $levelConfig = PetLevelConfig::where('level', $pet->level)->first();
  590. // 基础战力
  591. $basePower = $levelConfig ? ($levelConfig->numeric_attributes['base_power'] ?? 100) : 100;
  592. // 品阶加成
  593. $gradeBonus = config('pet.grade_attribute_bonus.' . $pet->grade, 0);
  594. // 计算最终战力
  595. $power = $basePower * (1 + $gradeBonus);
  596. return (int)$power;
  597. }
  598. }