CropLogic.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. <?php
  2. namespace App\Module\Farm\Logics;
  3. use App\Module\Farm\Dtos\CropInfoDto;
  4. use App\Module\Farm\Dtos\HarvestResultDto;
  5. use App\Module\Farm\Enums\GROWTH_STAGE;
  6. use App\Module\Farm\Enums\LAND_STATUS;
  7. use App\Module\Farm\Events\CropGrowthStageChangedEvent;
  8. use App\Module\Farm\Events\CropHarvestedEvent;
  9. use App\Module\Farm\Events\CropPlantedEvent;
  10. use App\Module\Farm\Events\DisasterClearedEvent;
  11. use App\Module\Farm\Models\FarmCrop;
  12. use App\Module\Farm\Models\FarmHarvestLog;
  13. use App\Module\Farm\Models\FarmLand;
  14. use App\Module\Farm\Models\FarmSeed;
  15. use App\Module\Farm\Models\FarmSeedOutput;
  16. use App\Module\Farm\Models\FarmSowLog;
  17. use Illuminate\Support\Facades\DB;
  18. use Illuminate\Support\Facades\Log;
  19. use UCore\Db\Helper;
  20. /**
  21. * 作物管理逻辑
  22. */
  23. class CropLogic
  24. {
  25. /**
  26. * 获取作物信息
  27. *
  28. * @param int $cropId
  29. * @return CropInfoDto|null
  30. */
  31. public function getCropInfo(int $cropId): ?CropInfoDto
  32. {
  33. try {
  34. $crop = FarmCrop::find($cropId);
  35. if (!$crop) {
  36. return null;
  37. }
  38. return CropInfoDto::fromModel($crop);
  39. } catch (\Exception $e) {
  40. Log::error('获取作物信息失败', [
  41. 'crop_id' => $cropId,
  42. 'error' => $e->getMessage(),
  43. 'trace' => $e->getTraceAsString()
  44. ]);
  45. return null;
  46. }
  47. }
  48. /**
  49. * 获取土地上的作物信息
  50. *
  51. * @param int $landId
  52. * @return CropInfoDto|null
  53. */
  54. public function getCropByLandId(int $landId): ?CropInfoDto
  55. {
  56. try {
  57. $crop = FarmCrop::where('land_id', $landId)->first();
  58. if (!$crop) {
  59. return null;
  60. }
  61. return CropInfoDto::fromModel($crop);
  62. } catch (\Exception $e) {
  63. Log::error('获取土地作物信息失败', [
  64. 'land_id' => $landId,
  65. 'error' => $e->getMessage(),
  66. 'trace' => $e->getTraceAsString()
  67. ]);
  68. return null;
  69. }
  70. }
  71. /**
  72. * 种植作物
  73. *
  74. * @param int $userId
  75. * @param int $landId
  76. * @param int $seedId
  77. * @return array|null 返回包含CropInfoDto和日志ID的数组,格式为['crop' => CropInfoDto, 'log_id' => int]
  78. * @throws \Exception
  79. */
  80. public function plantCrop(int $userId, int $landId, int $seedId): ?array
  81. {
  82. try {
  83. // 检查是否已开启事务
  84. \UCore\Db\Helper::check_tr();
  85. // 获取土地信息
  86. $land = FarmLand::where('id', $landId)
  87. ->where('user_id', $userId)
  88. ->first();
  89. if (!$land) {
  90. throw new \Exception('土地不存在');
  91. }
  92. // 检查土地状态
  93. if ($land->status !== LAND_STATUS::IDLE->value) {
  94. throw new \Exception('土地状态不允许种植');
  95. }
  96. // 获取种子信息
  97. $seed = FarmSeed::find($seedId);
  98. if (!$seed) {
  99. throw new \Exception('种子不存在');
  100. }
  101. // 创建作物记录
  102. $crop = new FarmCrop();
  103. $crop->land_id = $landId;
  104. $crop->user_id = $userId;
  105. $crop->seed_id = $seedId;
  106. $crop->plant_time = now();
  107. $crop->growth_stage = GROWTH_STAGE::SEED->value;
  108. $crop->stage_start_time = now(); // 设置当前阶段开始时间
  109. $crop->stage_end_time = now()->addSeconds($seed->seed_time);
  110. $crop->disasters = [];
  111. $crop->fertilized = false;
  112. $crop->save();
  113. // 创建种植日志
  114. $sowLog = new FarmSowLog();
  115. $sowLog->user_id = $userId;
  116. $sowLog->land_id = $landId;
  117. $sowLog->crop_id = $crop->id;
  118. $sowLog->seed_id = $seedId;
  119. $sowLog->sow_time = now();
  120. $sowLog->save();
  121. // 更新土地状态
  122. $land->status = LAND_STATUS::PLANTING->value;
  123. $land->save();
  124. // 触发作物种植事件
  125. event(new CropPlantedEvent($userId, $land, $crop));
  126. Log::info('作物种植成功', [
  127. 'user_id' => $userId,
  128. 'land_id' => $landId,
  129. 'seed_id' => $seedId,
  130. 'crop_id' => $crop->id,
  131. 'sow_log_id' => $sowLog->id
  132. ]);
  133. return [
  134. 'crop' => CropInfoDto::fromModel($crop),
  135. 'log_id' => $sowLog->id
  136. ];
  137. } catch (\Exception $e) {
  138. // 回滚事务
  139. DB::rollBack();
  140. Log::error('作物种植失败', [
  141. 'user_id' => $userId,
  142. 'land_id' => $landId,
  143. 'seed_id' => $seedId,
  144. 'error' => $e->getMessage(),
  145. 'trace' => $e->getTraceAsString()
  146. ]);
  147. return null;
  148. }
  149. }
  150. /**
  151. * 收获作物
  152. *
  153. * @param int $userId
  154. * @param int $landId
  155. * @return HarvestResultDto|null
  156. */
  157. public function harvestCrop(int $userId, int $landId): ?HarvestResultDto
  158. {
  159. try {
  160. // 开启事务
  161. DB::beginTransaction();
  162. // 获取土地信息
  163. $land = FarmLand::where('id', $landId)
  164. ->where('user_id', $userId)
  165. ->first();
  166. if (!$land) {
  167. throw new \Exception('土地不存在');
  168. }
  169. // 检查土地状态
  170. if ($land->status !== LAND_STATUS::HARVESTABLE->value) {
  171. throw new \Exception('土地状态不允许收获');
  172. }
  173. // 获取作物信息
  174. $crop = FarmCrop::where('land_id', $landId)->first();
  175. if (!$crop) {
  176. throw new \Exception('作物不存在');
  177. }
  178. // 检查作物生长阶段
  179. if ($crop->growth_stage !== GROWTH_STAGE::MATURE->value) {
  180. throw new \Exception('作物未成熟,不能收获');
  181. }
  182. // 获取种子信息
  183. $seed = $crop->seed;
  184. if (!$seed) {
  185. throw new \Exception('种子信息不存在');
  186. }
  187. // 随机选择产出物品
  188. $outputInfo = $this->getRandomOutput($seed->id);
  189. $outputItemId = $outputInfo['item_id'];
  190. $outputAmount = mt_rand($outputInfo['min_amount'], $outputInfo['max_amount']);
  191. // 创建收获记录
  192. $harvestLog = new FarmHarvestLog();
  193. $harvestLog->user_id = $userId;
  194. $harvestLog->land_id = $landId;
  195. $harvestLog->crop_id = $crop->id;
  196. $harvestLog->seed_id = $seed->id;
  197. $harvestLog->output_amount = $outputAmount;
  198. $harvestLog->harvest_time = now();
  199. $harvestLog->created_at = now();
  200. $harvestLog->save();
  201. // 删除作物记录
  202. $crop->delete();
  203. // 更新土地状态
  204. $land->status = LAND_STATUS::IDLE;
  205. $land->save();
  206. // 提交事务
  207. DB::commit();
  208. // 触发作物收获事件
  209. event(new CropHarvestedEvent($userId, $land, $crop, $harvestLog, $outputItemId, $outputAmount));
  210. Log::info('作物收获成功', [
  211. 'user_id' => $userId,
  212. 'land_id' => $landId,
  213. 'crop_id' => $crop->id,
  214. 'seed_id' => $seed->id,
  215. 'output_item_id' => $outputItemId,
  216. 'output_amount' => $outputAmount,
  217. 'harvest_log_id' => $harvestLog->id
  218. ]);
  219. return HarvestResultDto::fromModel($harvestLog, $outputItemId);
  220. } catch (\Exception $e) {
  221. // 回滚事务
  222. DB::rollBack();
  223. Log::error('作物收获失败', [
  224. 'user_id' => $userId,
  225. 'land_id' => $landId,
  226. 'error' => $e->getMessage(),
  227. 'trace' => $e->getTraceAsString()
  228. ]);
  229. return null;
  230. }
  231. }
  232. /**
  233. * 使用化肥
  234. *
  235. * @param int $userId
  236. * @param int $landId
  237. * @return bool
  238. */
  239. public function useFertilizer(int $userId, int $landId,int $crop_growth_time): bool
  240. {
  241. try {
  242. Helper::check_tr();
  243. // 获取土地信息
  244. /**
  245. * @var FarmLand $land
  246. */
  247. $land = FarmLand::where('id', $landId)
  248. ->where('user_id', $userId)
  249. ->first();
  250. if (!$land) {
  251. throw new \Exception('土地不存在');
  252. }
  253. // 检查土地状态
  254. if ($land->status !== LAND_STATUS::PLANTING->valueInt()) {
  255. throw new \Exception('土地状态不允许使用化肥');
  256. }
  257. // 获取作物信息
  258. $crop = FarmCrop::where('land_id', $landId)->first();
  259. if (!$crop) {
  260. throw new \Exception('作物不存在');
  261. }
  262. // 检查作物生长阶段
  263. if (!GROWTH_STAGE::canUseFertilizer($crop->growth_stage)) {
  264. throw new \Exception('当前生长阶段不能使用化肥');
  265. }
  266. // 检查是否已经使用过化肥
  267. if ($crop->fertilized) {
  268. throw new \Exception('当前阶段已经使用过化肥');
  269. }
  270. // 更新作物信息
  271. $crop->fertilized = true;
  272. // 根据 crop_growth_time 参数减少当前阶段时间
  273. if ($crop->stage_end_time) {
  274. $currentTime = now();
  275. $endTime = $crop->stage_end_time;
  276. $remainingTime = $currentTime->diffInSeconds($endTime, false);
  277. if ($remainingTime > 0) {
  278. // 确保减少的时间不超过剩余时间
  279. $reducedTime = min($crop_growth_time, $remainingTime);
  280. $crop->stage_end_time = $endTime->subSeconds($reducedTime);
  281. Log::info('化肥减少生长时间', [
  282. 'crop_id' => $crop->id,
  283. 'reduced_time' => $reducedTime,
  284. 'original_end_time' => $endTime->toDateTimeString(),
  285. 'new_end_time' => $crop->stage_end_time->toDateTimeString(),
  286. 'stage_start_time' => $crop->stage_start_time->toDateTimeString()
  287. ]);
  288. }
  289. }
  290. $crop->save();
  291. Log::info('使用化肥成功', [
  292. 'user_id' => $userId,
  293. 'land_id' => $landId,
  294. 'crop_id' => $crop->id,
  295. 'growth_stage' => $crop->growth_stage,
  296. 'stage_end_time' => $crop->stage_end_time
  297. ]);
  298. return true;
  299. } catch (\Exception $e) {
  300. Log::error('使用化肥失败', [
  301. 'user_id' => $userId,
  302. 'land_id' => $landId,
  303. 'error' => $e->getMessage(),
  304. 'trace' => $e->getTraceAsString()
  305. ]);
  306. return false;
  307. }
  308. }
  309. /**
  310. * 清理灾害
  311. *
  312. * @param int $userId
  313. * @param int $landId
  314. * @param int $disasterType
  315. * @return bool
  316. */
  317. public function clearDisaster(int $userId, int $landId, int $disasterType): bool
  318. {
  319. try {
  320. // 获取土地信息
  321. $land = FarmLand::where('id', $landId)
  322. ->where('user_id', $userId)
  323. ->first();
  324. if (!$land) {
  325. throw new \Exception('土地不存在');
  326. }
  327. // 检查土地状态
  328. if ($land->status !== LAND_STATUS::DISASTER->value) {
  329. throw new \Exception('土地没有灾害');
  330. }
  331. // 获取作物信息
  332. $crop = FarmCrop::where('land_id', $landId)->first();
  333. if (!$crop) {
  334. throw new \Exception('作物不存在');
  335. }
  336. // 检查灾害是否存在
  337. $disasters = $crop->disasters ?? [];
  338. $disasterIndex = -1;
  339. $disasterInfo = null;
  340. foreach ($disasters as $index => $disaster) {
  341. if (($disaster['type'] ?? 0) == $disasterType && ($disaster['status'] ?? '') === 'active') {
  342. $disasterIndex = $index;
  343. $disasterInfo = $disaster;
  344. break;
  345. }
  346. }
  347. if ($disasterIndex === -1 || !$disasterInfo) {
  348. throw new \Exception('指定类型的灾害不存在');
  349. }
  350. // 更新灾害状态
  351. $disasters[$disasterIndex]['status'] = 'cleared';
  352. $disasters[$disasterIndex]['cleared_at'] = now()->toDateTimeString();
  353. $crop->disasters = $disasters;
  354. // 检查是否还有其他活跃灾害
  355. $hasActiveDisaster = false;
  356. foreach ($disasters as $disaster) {
  357. if (($disaster['status'] ?? '') === 'active') {
  358. $hasActiveDisaster = true;
  359. break;
  360. }
  361. }
  362. // 如果没有其他活跃灾害,更新土地状态
  363. if (!$hasActiveDisaster) {
  364. $land->status = LAND_STATUS::PLANTING->value;
  365. }
  366. // 保存更改
  367. $crop->save();
  368. $land->save();
  369. // 触发灾害清理事件
  370. event(new DisasterClearedEvent($userId, $crop, $disasterType, $disasterInfo));
  371. Log::info('灾害清理成功', [
  372. 'user_id' => $userId,
  373. 'land_id' => $landId,
  374. 'crop_id' => $crop->id,
  375. 'disaster_type' => $disasterType
  376. ]);
  377. return true;
  378. } catch (\Exception $e) {
  379. Log::error('灾害清理失败', [
  380. 'user_id' => $userId,
  381. 'land_id' => $landId,
  382. 'disaster_type' => $disasterType,
  383. 'error' => $e->getMessage(),
  384. 'trace' => $e->getTraceAsString()
  385. ]);
  386. return false;
  387. }
  388. }
  389. /**
  390. * 铲除作物
  391. *
  392. * @param int $userId
  393. * @param int $landId
  394. * @return bool
  395. */
  396. public function removeCrop(int $userId, int $landId): bool
  397. {
  398. try {
  399. // 开启事务
  400. DB::beginTransaction();
  401. // 获取土地信息
  402. $land = FarmLand::where('id', $landId)
  403. ->where('user_id', $userId)
  404. ->first();
  405. if (!$land) {
  406. throw new \Exception('土地不存在');
  407. }
  408. // 检查土地状态
  409. if ($land->status === LAND_STATUS::IDLE->value) {
  410. throw new \Exception('土地上没有作物');
  411. }
  412. // 获取作物信息
  413. $crop = FarmCrop::where('land_id', $landId)->first();
  414. if (!$crop) {
  415. // 如果没有作物但土地状态不是空闲,修正土地状态
  416. $land->status = LAND_STATUS::IDLE->value;
  417. $land->save();
  418. DB::commit();
  419. return true;
  420. }
  421. // 删除作物记录
  422. $crop->delete();
  423. // 更新土地状态
  424. $land->status = LAND_STATUS::IDLE->value;
  425. $land->save();
  426. // 提交事务
  427. DB::commit();
  428. Log::info('铲除作物成功', [
  429. 'user_id' => $userId,
  430. 'land_id' => $landId,
  431. 'crop_id' => $crop->id
  432. ]);
  433. return true;
  434. } catch (\Exception $e) {
  435. // 回滚事务
  436. DB::rollBack();
  437. Log::error('铲除作物失败', [
  438. 'user_id' => $userId,
  439. 'land_id' => $landId,
  440. 'error' => $e->getMessage(),
  441. 'trace' => $e->getTraceAsString()
  442. ]);
  443. return false;
  444. }
  445. }
  446. /**
  447. * 更新作物生长阶段
  448. *
  449. * @param int $cropId
  450. * @return bool
  451. */
  452. public function updateGrowthStage(int $cropId): bool
  453. {
  454. try {
  455. // 获取作物信息
  456. $crop = FarmCrop::find($cropId);
  457. if (!$crop) {
  458. throw new \Exception('作物不存在');
  459. }
  460. // 检查是否需要更新
  461. if (!$crop->stage_end_time || $crop->stage_end_time > now()) {
  462. return false;
  463. }
  464. // 获取当前生长阶段
  465. $oldStage = $crop->growth_stage;
  466. // 计算新的生长阶段
  467. $newStage = $this->calculateNextStage($crop);
  468. // 如果阶段没有变化,不需要更新
  469. if ($newStage === $oldStage) {
  470. return false;
  471. }
  472. // 计算新阶段的结束时间
  473. $stageEndTime = $this->calculateStageEndTime($crop, $newStage);
  474. // 更新作物信息
  475. $crop->growth_stage = $newStage;
  476. $crop->stage_start_time = now(); // 设置新阶段的开始时间
  477. $crop->stage_end_time = $stageEndTime;
  478. $crop->fertilized = false; // 重置施肥状态
  479. $crop->save();
  480. // 触发生长阶段变更事件
  481. event(new CropGrowthStageChangedEvent($crop->user_id, $crop, $oldStage, $newStage));
  482. Log::info('作物生长阶段更新成功', [
  483. 'crop_id' => $cropId,
  484. 'user_id' => $crop->user_id,
  485. 'old_stage' => $oldStage,
  486. 'new_stage' => $newStage,
  487. 'stage_end_time' => $stageEndTime
  488. ]);
  489. return true;
  490. } catch (\Exception $e) {
  491. Log::error('作物生长阶段更新失败', [
  492. 'crop_id' => $cropId,
  493. 'error' => $e->getMessage(),
  494. 'trace' => $e->getTraceAsString()
  495. ]);
  496. return false;
  497. }
  498. }
  499. /**
  500. * 计算下一个生长阶段
  501. *
  502. * @param FarmCrop $crop
  503. * @return int
  504. */
  505. private function calculateNextStage(FarmCrop $crop): int
  506. {
  507. $currentStage = $crop->growth_stage;
  508. // 如果当前是成熟期,且超过一定时间,则进入枯萎期
  509. if ($currentStage === GROWTH_STAGE::MATURE->value) {
  510. // 成熟期持续时间,默认为24小时
  511. $matureDuration = 24 * 60 * 60;
  512. // 如果成熟期已经超过指定时间,则进入枯萎期
  513. if ($crop->stage_end_time && $crop->stage_end_time instanceof \Carbon\Carbon) {
  514. $endTimeMinusDuration = $crop->stage_end_time->copy()->subSeconds($matureDuration);
  515. if (now()->diffInSeconds($endTimeMinusDuration) > $matureDuration) {
  516. return GROWTH_STAGE::WITHERED->value;
  517. }
  518. }
  519. return GROWTH_STAGE::MATURE->value;
  520. }
  521. // 正常阶段递增
  522. return $currentStage + 1;
  523. }
  524. /**
  525. * 计算阶段结束时间
  526. *
  527. * @param FarmCrop $crop
  528. * @param int $stage
  529. * @return \Carbon\Carbon|null
  530. */
  531. private function calculateStageEndTime(FarmCrop $crop, int $stage)
  532. {
  533. $seed = $crop->seed;
  534. if (!$seed) {
  535. return null;
  536. }
  537. $now = now();
  538. switch ($stage) {
  539. case GROWTH_STAGE::SEED->value:
  540. return $now->addSeconds($seed->seed_time);
  541. case GROWTH_STAGE::SPROUT->value:
  542. return $now->addSeconds($seed->sprout_time);
  543. case GROWTH_STAGE::GROWTH->value:
  544. return $now->addSeconds($seed->growth_time);
  545. case GROWTH_STAGE::MATURE->value:
  546. // 成熟期持续24小时后进入枯萎期
  547. return $now->addHours(24);
  548. case GROWTH_STAGE::WITHERED->value:
  549. // 枯萎期没有结束时间
  550. return null;
  551. default:
  552. return null;
  553. }
  554. }
  555. /**
  556. * 获取随机产出
  557. *
  558. * @param int $seedId
  559. * @return array
  560. */
  561. private function getRandomOutput(int $seedId): array
  562. {
  563. // 获取种子的所有产出配置
  564. $outputs = FarmSeedOutput::where('seed_id', $seedId)->get();
  565. if ($outputs->isEmpty()) {
  566. // 如果没有产出配置,使用种子的默认产出
  567. $seed = FarmSeed::find($seedId);
  568. return [
  569. 'item_id' => $seed->item_id,
  570. 'min_amount' => $seed->min_output,
  571. 'max_amount' => $seed->max_output,
  572. ];
  573. }
  574. // 按概率排序
  575. $outputs = $outputs->sortByDesc('probability');
  576. // 获取默认产出
  577. $defaultOutput = $outputs->firstWhere('is_default', true);
  578. // 随机选择产出
  579. $random = mt_rand(1, 100);
  580. $cumulativeProbability = 0;
  581. foreach ($outputs as $output) {
  582. $cumulativeProbability += $output->probability;
  583. if ($random <= $cumulativeProbability) {
  584. return [
  585. 'item_id' => $output->item_id,
  586. 'min_amount' => $output->min_amount,
  587. 'max_amount' => $output->max_amount,
  588. ];
  589. }
  590. }
  591. // 如果随机值超过了所有概率之和,使用默认产出
  592. if ($defaultOutput) {
  593. return [
  594. 'item_id' => $defaultOutput->item_id,
  595. 'min_amount' => $defaultOutput->min_amount,
  596. 'max_amount' => $defaultOutput->max_amount,
  597. ];
  598. }
  599. // 如果没有默认产出,使用第一个产出
  600. $firstOutput = $outputs->first();
  601. return [
  602. 'item_id' => $firstOutput->item_id,
  603. 'min_amount' => $firstOutput->min_amount,
  604. 'max_amount' => $firstOutput->max_amount,
  605. ];
  606. }
  607. }