CropLogic.php 34 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010
  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\Events\LandStatusChangedEvent;
  12. use App\Module\Farm\Models\FarmCrop;
  13. use App\Module\Farm\Models\FarmHarvestLog;
  14. use App\Module\Farm\Models\FarmLand;
  15. use App\Module\Farm\Models\FarmSeed;
  16. use App\Module\Farm\Models\FarmSeedOutput;
  17. use App\Module\Farm\Models\FarmSowLog;
  18. use App\Module\GameItems\Services\ItemService;
  19. use Illuminate\Support\Facades\DB;
  20. use Illuminate\Support\Facades\Log;
  21. use UCore\Db\Helper;
  22. use UCore\Dto\Res;
  23. /**
  24. * 作物管理逻辑
  25. */
  26. class CropLogic
  27. {
  28. /**
  29. * 获取作物信息
  30. *
  31. * @param int $cropId
  32. * @return CropInfoDto|null
  33. */
  34. public function getCropInfo(int $cropId): ?CropInfoDto
  35. {
  36. try {
  37. $crop = FarmCrop::find($cropId);
  38. if (!$crop) {
  39. return null;
  40. }
  41. return CropInfoDto::fromModel($crop);
  42. } catch (\Exception $e) {
  43. Log::error('获取作物信息失败', [
  44. 'crop_id' => $cropId,
  45. 'error' => $e->getMessage(),
  46. 'trace' => $e->getTraceAsString()
  47. ]);
  48. return null;
  49. }
  50. }
  51. /**
  52. * 获取土地上的作物信息
  53. *
  54. * @param int $landId
  55. * @return CropInfoDto|null
  56. */
  57. public function getCropByLandId(int $landId): ?CropInfoDto
  58. {
  59. try {
  60. $crop = FarmCrop::where('land_id', $landId)->first();
  61. if (!$crop) {
  62. return null;
  63. }
  64. return CropInfoDto::fromModel($crop);
  65. } catch (\Exception $e) {
  66. Log::error('获取土地作物信息失败', [
  67. 'land_id' => $landId,
  68. 'error' => $e->getMessage(),
  69. 'trace' => $e->getTraceAsString()
  70. ]);
  71. return null;
  72. }
  73. }
  74. /**
  75. * 种植作物
  76. *
  77. * @param int $userId
  78. * @param int $landId
  79. * @param int $itemId 种子物品ID
  80. * @return array|null 返回包含CropInfoDto和日志ID的数组,格式为['crop' => CropInfoDto, 'log_id' => int]
  81. * @throws \Exception
  82. */
  83. public function plantCrop(int $userId, int $landId, int $itemId): ?array
  84. {
  85. try {
  86. // 检查是否已开启事务
  87. \UCore\Db\Helper::check_tr();
  88. // 获取土地信息
  89. $land = FarmLand::where('id', $landId)
  90. ->where('user_id', $userId)
  91. ->first();
  92. if (!$land) {
  93. throw new \Exception('土地不存在');
  94. }
  95. // 检查土地状态
  96. if ($land->status !== LAND_STATUS::IDLE->value) {
  97. throw new \Exception('土地状态不允许种植');
  98. }
  99. // 根据物品ID获取种子配置信息
  100. $seed = FarmSeed::where('item_id', $itemId)->first();
  101. if (!$seed) {
  102. throw new \Exception('种子配置不存在');
  103. }
  104. $seedId = $seed->id;
  105. // 创建作物记录
  106. $crop = new FarmCrop();
  107. $crop->land_id = $landId;
  108. $crop->user_id = $userId;
  109. $crop->seed_id = $seedId;
  110. $crop->plant_time = now();
  111. $crop->growth_stage = GROWTH_STAGE::SEED->value;
  112. $crop->stage_start_time = now(); // 设置当前阶段开始时间
  113. $crop->stage_end_time = now()->addSeconds($seed->seed_time);
  114. $crop->disasters = [];
  115. $crop->fertilized = false;
  116. $crop->last_disaster_check_time = null; // 初始化灾害检查时间
  117. $crop->can_disaster = false; // 种子期不能产生灾害
  118. $crop->save();
  119. // 创建种植日志
  120. $sowLog = new FarmSowLog();
  121. $sowLog->user_id = $userId;
  122. $sowLog->land_id = $landId;
  123. $sowLog->crop_id = $crop->id;
  124. $sowLog->seed_id = $seedId;
  125. $sowLog->sow_time = now();
  126. $sowLog->save();
  127. // 更新土地状态
  128. $land->status = LAND_STATUS::PLANTING->value;
  129. $land->updateHasCrop();
  130. $land->save();
  131. // 触发作物种植事件
  132. event(new CropPlantedEvent($userId, $land, $crop));
  133. Log::info('作物种植成功', [
  134. 'user_id' => $userId,
  135. 'land_id' => $landId,
  136. 'seed_id' => $seedId,
  137. 'crop_id' => $crop->id,
  138. 'sow_log_id' => $sowLog->id
  139. ]);
  140. return [
  141. 'crop' => CropInfoDto::fromModel($crop),
  142. 'log_id' => $sowLog->id
  143. ];
  144. } catch (\Exception $e) {
  145. // 回滚事务
  146. DB::rollBack();
  147. Log::error('作物种植失败', [
  148. 'user_id' => $userId,
  149. 'land_id' => $landId,
  150. 'seed_id' => $seedId,
  151. 'error' => $e->getMessage(),
  152. 'trace' => $e->getTraceAsString()
  153. ]);
  154. return null;
  155. }
  156. }
  157. /**
  158. * 收获作物
  159. *
  160. * @param int $userId
  161. * @param int $landId
  162. * @return HarvestResultDto|null
  163. */
  164. public function harvestCrop(int $userId, int $landId): Res
  165. {
  166. try {
  167. // 事务检查
  168. Helper::check_tr();
  169. // 获取土地信息
  170. /**
  171. * @var FarmLand $land
  172. */
  173. $land = FarmLand::where('id', $landId)
  174. ->where('user_id', $userId)
  175. ->first();
  176. if (!$land) {
  177. throw new \Exception('土地不存在');
  178. }
  179. // 检查土地状态
  180. // if ($land->status !== LAND_STATUS::HARVESTABLE->value) {
  181. // throw new \Exception('土地状态不允许收获');
  182. // }
  183. // 获取作物信息
  184. $crop = FarmCrop::where('land_id', $landId)->first();
  185. if (!$crop) {
  186. throw new \Exception('作物不存在');
  187. }
  188. // 检查作物生长阶段
  189. if ($crop->growth_stage !== GROWTH_STAGE::MATURE) {
  190. throw new \Exception('作物未成熟,不能收获');
  191. }
  192. // 获取种子信息
  193. $seed = $crop->seed;
  194. if (!$seed) {
  195. throw new \Exception('种子信息不存在');
  196. }
  197. // 使用发芽期确定的最终产出果实ID,如果没有则随机选择
  198. if ($crop->final_output_item_id) {
  199. $outputItemId = $crop->final_output_item_id;
  200. // 获取对应的产出配置来确定数量范围
  201. $outputInfo = $this->getOutputInfoByItemId($seed->id, $outputItemId);
  202. $outputAmount = mt_rand($outputInfo['min_amount'], $outputInfo['max_amount']);
  203. Log::info('使用发芽期确定的最终产出果实', [
  204. 'crop_id' => $crop->id,
  205. 'final_output_item_id' => $outputItemId,
  206. 'output_amount' => $outputAmount
  207. ]);
  208. } else {
  209. // 兼容旧数据:如果没有预设的最终产出果实ID,则随机选择
  210. $outputInfo = $this->getRandomOutput($seed->id);
  211. $outputItemId = $outputInfo['item_id'];
  212. $outputAmount = mt_rand($outputInfo['min_amount'], $outputInfo['max_amount']);
  213. Log::warning('作物没有预设最终产出果实ID,使用随机选择', [
  214. 'crop_id' => $crop->id,
  215. 'seed_id' => $seed->id,
  216. 'random_output_item_id' => $outputItemId
  217. ]);
  218. }
  219. // 创建收获记录
  220. $harvestLog = new FarmHarvestLog();
  221. $harvestLog->user_id = $userId;
  222. $harvestLog->land_id = $landId;
  223. $harvestLog->crop_id = $crop->id;
  224. $harvestLog->seed_id = $seed->id;
  225. $harvestLog->output_amount = $outputAmount;
  226. $harvestLog->harvest_time = now();
  227. $harvestLog->created_at = now();
  228. $harvestLog->save();
  229. // 收获后作物进入枯萎期,而不是直接删除
  230. $oldStage = $crop->growth_stage;
  231. $crop->growth_stage = GROWTH_STAGE::WITHERED;
  232. $crop->stage_start_time = now();
  233. $crop->stage_end_time = null; // 枯萎期没有结束时间
  234. $crop->fertilized = false; // 重置施肥状态
  235. $crop->save();
  236. // 更新土地状态为枯萎状态
  237. $land->status = LAND_STATUS::WITHERED;
  238. $land->updateHasCrop();
  239. $land->save();
  240. // 触发作物生长阶段变更事件(从成熟期到枯萎期)
  241. event(new CropGrowthStageChangedEvent($userId, $crop, $oldStage->value, GROWTH_STAGE::WITHERED->value));
  242. // 触发作物收获事件
  243. event(new CropHarvestedEvent($userId, $land, $crop, $harvestLog, $outputItemId, $outputAmount));
  244. Log::info('作物收获成功,进入枯萎期', [
  245. 'user_id' => $userId,
  246. 'land_id' => $landId,
  247. 'crop_id' => $crop->id,
  248. 'seed_id' => $seed->id,
  249. 'output_item_id' => $outputItemId,
  250. 'output_amount' => $outputAmount,
  251. 'harvest_log_id' => $harvestLog->id,
  252. 'old_stage' => $oldStage->value,
  253. 'new_stage' => GROWTH_STAGE::WITHERED->value,
  254. 'land_status' => LAND_STATUS::WITHERED->value
  255. ]);
  256. // 物品入包
  257. ItemService::addItem($userId, $outputItemId, $outputAmount,[
  258. 'source'=>'FarmHarve',
  259. 'FarmHarvestLog'=>$harvestLog->id
  260. ]);
  261. return Res::success();
  262. } catch (\Exception $e) {
  263. // 回滚事务
  264. Log::error('作物收获失败', [
  265. 'user_id' => $userId,
  266. 'land_id' => $landId,
  267. 'error' => $e->getMessage(),
  268. 'trace' => $e->getTraceAsString()
  269. ]);
  270. return Res::error('');
  271. }
  272. }
  273. /**
  274. * 使用化肥(通过作物ID)
  275. *
  276. * @param int $cropId 作物ID
  277. * @param int $cropGrowthTime 减少的生长时间(秒)
  278. * @return Res
  279. */
  280. public function useFertilizerByCropId(int $cropId, int $cropGrowthTime): Res
  281. {
  282. try {
  283. Helper::check_tr();
  284. // 获取作物信息(防错误机制:确保作物存在)
  285. /**
  286. * @var FarmCrop $crop
  287. */
  288. $crop = FarmCrop::find($cropId);
  289. if (!$crop) {
  290. throw new \Exception('作物不存在');
  291. }
  292. // 防错误机制:基本状态检查,避免意外执行
  293. if ($crop->fertilized) {
  294. throw new \Exception('作物已施肥');
  295. }
  296. // 更新作物信息
  297. $crop->fertilized = true;
  298. // 根据 cropGrowthTime 参数减少当前阶段时间
  299. if ($crop->stage_end_time) {
  300. $currentTime = now();
  301. $endTime = $crop->stage_end_time;
  302. $remainingTime = $currentTime->diffInSeconds($endTime, false);
  303. if ($remainingTime > 0) {
  304. // 确保减少的时间不超过剩余时间
  305. $reducedTime = min($cropGrowthTime, $remainingTime);
  306. // 使用copy()方法创建副本,避免修改原始对象
  307. $newEndTime = $endTime->copy()->subSeconds($reducedTime);
  308. $crop->stage_end_time = $newEndTime;
  309. Log::info('化肥减少生长时间', [
  310. 'crop_id' => $crop->id,
  311. 'reduced_time' => $reducedTime,
  312. 'original_end_time' => $endTime->format('Y-m-d H:i:s'),
  313. 'new_end_time' => $crop->stage_end_time->format('Y-m-d H:i:s'),
  314. 'stage_start_time' => $crop->stage_start_time ? $crop->stage_start_time->format('Y-m-d H:i:s') : null
  315. ]);
  316. event(new CropGrowthStageChangedEvent($crop->user_id, $crop, $crop->growth_stage->value, $crop->growth_stage->value));
  317. } else {
  318. Log::warning('作物已经到达或超过结束时间,无法减少生长时间', [
  319. 'crop_id' => $crop->id,
  320. 'current_time' => $currentTime->format('Y-m-d H:i:s'),
  321. 'stage_end_time' => $endTime->format('Y-m-d H:i:s'),
  322. 'remaining_time' => $remainingTime
  323. ]);
  324. }
  325. }
  326. $crop->save();
  327. Log::info('使用化肥成功', [
  328. 'crop_id' => $crop->id,
  329. 'user_id' => $crop->user_id,
  330. 'land_id' => $crop->land_id,
  331. 'growth_stage' => $crop->growth_stage,
  332. 'stage_end_time' => $crop->stage_end_time
  333. ]);
  334. return Res::success('', [
  335. 'crop_id' => $crop->id,
  336. ]);
  337. } catch (\Exception $e) {
  338. Log::error('使用化肥失败', [
  339. 'crop_id' => $cropId,
  340. 'crop_growth_time' => $cropGrowthTime,
  341. 'error' => $e->getMessage(),
  342. 'trace' => $e->getTraceAsString()
  343. ]);
  344. return Res::error('使用化肥失败');
  345. }
  346. }
  347. /**
  348. * 使用化肥(通过土地ID,兼容旧接口)
  349. *
  350. * @param int $userId
  351. * @param int $landId
  352. * @param int $crop_growth_time
  353. * @return Res
  354. * @deprecated 建议使用 useFertilizerByCropId($cropId, $cropGrowthTime) 方法
  355. */
  356. public function useFertilizer(int $userId, int $landId, int $crop_growth_time): Res
  357. {
  358. try {
  359. Helper::check_tr();
  360. // 获取土地信息(防错误机制:确保土地存在)
  361. /**
  362. * @var FarmLand $land
  363. */
  364. $land = FarmLand::where('id', $landId)
  365. ->where('user_id', $userId)
  366. ->first();
  367. if (!$land) {
  368. throw new \Exception('土地不存在');
  369. }
  370. // 获取作物信息(防错误机制:确保作物存在)
  371. /**
  372. * @var FarmCrop $crop
  373. */
  374. $crop = FarmCrop::where('land_id', $landId)->first();
  375. if (!$crop) {
  376. throw new \Exception('作物不存在');
  377. }
  378. // 防错误机制:基本状态检查,避免意外执行
  379. // 不进行土地状态验证,只进行作物状态验证
  380. // if ($land->status !== LAND_STATUS::PLANTING->valueInt()) {
  381. // Log::warning('土地状态异常,但继续执行施肥', [
  382. // 'land_id' => $landId,
  383. // 'expected_status' => LAND_STATUS::PLANTING->valueInt(),
  384. // 'actual_status' => $land->status
  385. // ]);
  386. // }
  387. if ($crop->fertilized) {
  388. throw new \Exception('作物已施肥');
  389. }
  390. // 更新作物信息
  391. $crop->fertilized = true;
  392. // 根据 crop_growth_time 参数减少当前阶段时间
  393. if ($crop->stage_end_time) {
  394. $currentTime = now();
  395. $endTime = $crop->stage_end_time;
  396. $remainingTime = $currentTime->diffInSeconds($endTime, false);
  397. if ($remainingTime > 0) {
  398. // 确保减少的时间不超过剩余时间
  399. $reducedTime = min($crop_growth_time, $remainingTime);
  400. // 使用copy()方法创建副本,避免修改原始对象
  401. $newEndTime = $endTime->copy()->subSeconds($reducedTime);
  402. $crop->stage_end_time = $newEndTime;
  403. Log::info('化肥减少生长时间', [
  404. 'crop_id' => $crop->id,
  405. 'reduced_time' => $reducedTime,
  406. 'original_end_time' => $endTime->toDateTimeString(),
  407. 'new_end_time' => $crop->stage_end_time->toDateTimeString(),
  408. 'stage_start_time' => $crop->stage_start_time->toDateTimeString()
  409. ]);
  410. } else {
  411. Log::warning('作物已经到达或超过结束时间,无法减少生长时间', [
  412. 'crop_id' => $crop->id,
  413. 'current_time' => $currentTime->toDateTimeString(),
  414. 'stage_end_time' => $endTime->toDateTimeString(),
  415. 'remaining_time' => $remainingTime
  416. ]);
  417. }
  418. }
  419. $crop->save();
  420. Log::info('使用化肥成功', [
  421. 'user_id' => $userId,
  422. 'land_id' => $landId,
  423. 'crop_id' => $crop->id,
  424. 'growth_stage' => $crop->growth_stage,
  425. 'stage_end_time' => $crop->stage_end_time
  426. ]);
  427. return Res::success('',[
  428. 'crop_id' => $crop->id,
  429. ]);
  430. } catch (\Exception $e) {
  431. Log::error('使用化肥失败', [
  432. 'user_id' => $userId,
  433. 'land_id' => $landId,
  434. 'error' => $e->getMessage(),
  435. 'trace' => $e->getTraceAsString()
  436. ]);
  437. return Res::error('使用化肥失败');
  438. }
  439. }
  440. /**
  441. * 清理灾害
  442. *
  443. * @param int $userId
  444. * @param int $landId
  445. * @param int $disasterType
  446. * @return bool
  447. */
  448. public function clearDisaster(int $userId, int $landId, int $disasterType): bool
  449. {
  450. try {
  451. // 获取土地信息
  452. $land = FarmLand::where('id', $landId)
  453. ->where('user_id', $userId)
  454. ->first();
  455. if (!$land) {
  456. throw new \Exception('土地不存在');
  457. }
  458. // 检查土地状态
  459. if ($land->status !== LAND_STATUS::DISASTER->value) {
  460. throw new \Exception('土地没有灾害');
  461. }
  462. // 获取作物信息
  463. $crop = FarmCrop::where('land_id', $landId)->first();
  464. if (!$crop) {
  465. throw new \Exception('作物不存在');
  466. }
  467. // 检查灾害是否存在
  468. $disasters = $crop->disasters ?? [];
  469. $disasterIndex = -1;
  470. $disasterInfo = null;
  471. foreach ($disasters as $index => $disaster) {
  472. if (($disaster['type'] ?? 0) == $disasterType && ($disaster['status'] ?? '') === 'active') {
  473. $disasterIndex = $index;
  474. $disasterInfo = $disaster;
  475. break;
  476. }
  477. }
  478. if ($disasterIndex === -1 || !$disasterInfo) {
  479. throw new \Exception('指定类型的灾害不存在');
  480. }
  481. // 更新灾害状态
  482. $disasters[$disasterIndex]['status'] = 'cleared';
  483. $disasters[$disasterIndex]['cleared_at'] = now()->toDateTimeString();
  484. $crop->disasters = $disasters;
  485. // 检查是否还有其他活跃灾害
  486. $hasActiveDisaster = false;
  487. foreach ($disasters as $disaster) {
  488. if (($disaster['status'] ?? '') === 'active') {
  489. $hasActiveDisaster = true;
  490. break;
  491. }
  492. }
  493. // 如果没有其他活跃灾害,更新土地状态
  494. $oldLandStatus = $land->status;
  495. if (!$hasActiveDisaster) {
  496. $land->status = LAND_STATUS::PLANTING->value;
  497. $land->updateHasCrop();
  498. }
  499. // 保存更改
  500. $crop->save();
  501. $land->save();
  502. // 如果土地状态发生了变化,触发土地状态变更事件
  503. if ($oldLandStatus !== $land->status) {
  504. event(new LandStatusChangedEvent($userId, $landId, $oldLandStatus, $land->status));
  505. }
  506. // 触发灾害清理事件
  507. event(new DisasterClearedEvent($userId, $crop, $disasterType, $disasterInfo));
  508. Log::info('灾害清理成功', [
  509. 'user_id' => $userId,
  510. 'land_id' => $landId,
  511. 'crop_id' => $crop->id,
  512. 'disaster_type' => $disasterType
  513. ]);
  514. return true;
  515. } catch (\Exception $e) {
  516. Log::error('灾害清理失败', [
  517. 'user_id' => $userId,
  518. 'land_id' => $landId,
  519. 'disaster_type' => $disasterType,
  520. 'error' => $e->getMessage(),
  521. 'trace' => $e->getTraceAsString()
  522. ]);
  523. return false;
  524. }
  525. }
  526. /**
  527. * 铲除作物
  528. *
  529. * @param int $userId
  530. * @param int $landId
  531. * @return bool
  532. */
  533. public function removeCrop(int $userId, int $landId): bool
  534. {
  535. try {
  536. // 检查是否已开启事务
  537. \UCore\Db\Helper::check_tr();
  538. // 获取土地信息
  539. $land = FarmLand::where('id', $landId)
  540. ->where('user_id', $userId)
  541. ->first();
  542. if (!$land) {
  543. throw new \Exception('土地不存在');
  544. }
  545. // 检查土地状态
  546. if ($land->status === LAND_STATUS::IDLE->value) {
  547. throw new \Exception('土地上没有作物');
  548. }
  549. // 获取作物信息
  550. $crop = FarmCrop::where('land_id', $landId)->first();
  551. if (!$crop) {
  552. // 如果没有作物但土地状态不是空闲,修正土地状态
  553. $oldLandStatus = $land->status;
  554. $land->status = LAND_STATUS::IDLE->value;
  555. $land->updateHasCrop();
  556. $land->save();
  557. // 记录状态变更信息,由调用方处理事件触发
  558. Log::info('土地状态已修正', [
  559. 'user_id' => $userId,
  560. 'land_id' => $landId,
  561. 'old_status' => $oldLandStatus,
  562. 'new_status' => $land->status
  563. ]);
  564. return true;
  565. }
  566. // 删除作物记录
  567. $crop->delete();
  568. // 记录旧状态
  569. $oldLandStatus = $land->status;
  570. // 更新土地状态
  571. $land->status = LAND_STATUS::IDLE->value;
  572. $land->updateHasCrop();
  573. $land->save();
  574. // 记录状态变更信息,由调用方处理事件触发和事务提交
  575. Log::info('铲除作物成功', [
  576. 'user_id' => $userId,
  577. 'land_id' => $landId,
  578. 'crop_id' => $crop->id,
  579. 'old_status' => $oldLandStatus,
  580. 'new_status' => $land->status
  581. ]);
  582. return true;
  583. } catch (\Exception $e) {
  584. Log::error('铲除作物失败', [
  585. 'user_id' => $userId,
  586. 'land_id' => $landId,
  587. 'error' => $e->getMessage(),
  588. 'trace' => $e->getTraceAsString()
  589. ]);
  590. throw $e; // 重新抛出异常,由调用方处理事务回滚
  591. }
  592. }
  593. /**
  594. * 更新作物生长阶段
  595. *
  596. * @param int $cropId
  597. * @return bool
  598. */
  599. public function updateGrowthStage(int $cropId): bool
  600. {
  601. try {
  602. // 获取作物信息
  603. $crop = FarmCrop::find($cropId);
  604. if (!$crop) {
  605. throw new \Exception('作物不存在');
  606. }
  607. // 检查是否需要更新
  608. if (!$crop->stage_end_time || $crop->stage_end_time > now()) {
  609. return false;
  610. }
  611. // 获取当前生长阶段
  612. $oldStage = $crop->growth_stage;
  613. // 计算新的生长阶段
  614. $newStage = $this->calculateNextStage($crop);
  615. // 如果阶段没有变化,不需要更新
  616. if ($newStage === $oldStage) {
  617. return false;
  618. }
  619. // 计算新阶段的结束时间
  620. $stageEndTime = $this->calculateStageEndTime($crop, $newStage);
  621. // 更新作物信息
  622. $crop->growth_stage = $newStage;
  623. $crop->stage_start_time = now(); // 设置新阶段的开始时间
  624. $crop->stage_end_time = $stageEndTime;
  625. $crop->fertilized = false; // 重置施肥状态
  626. // 如果进入发芽期,必须确定最终产出果实ID
  627. if ($newStage === GROWTH_STAGE::SPROUT->value) {
  628. if (!$crop->final_output_item_id) {
  629. $seed = $crop->seed;
  630. // 如果是神秘种子,使用土地影响逻辑
  631. if ($seed && $seed->type == \App\Module\Farm\Enums\SEED_TYPE::MYSTERIOUS->value) {
  632. $land = $crop->land;
  633. $mysteryLogic = new \App\Module\Farm\Logics\MysterySeeLLogic();
  634. $selectedOutput = $mysteryLogic->selectFinalOutput($seed->id, $land->land_type);
  635. $crop->final_output_item_id = $selectedOutput['item_id'];
  636. Log::info('神秘种子确定最终产出(基于土地影响)', [
  637. 'crop_id' => $crop->id,
  638. 'user_id' => $crop->user_id,
  639. 'seed_id' => $seed->id,
  640. 'land_type' => $land->land_type,
  641. 'final_output_item_id' => $selectedOutput['item_id']
  642. ]);
  643. } else {
  644. // 普通种子使用原有逻辑
  645. $outputInfo = $this->getRandomOutput($crop->seed_id);
  646. $crop->final_output_item_id = $outputInfo['item_id'];
  647. Log::info('作物进入发芽期,确定最终产出果实', [
  648. 'crop_id' => $crop->id,
  649. 'user_id' => $crop->user_id,
  650. 'seed_id' => $crop->seed_id,
  651. 'final_output_item_id' => $crop->final_output_item_id
  652. ]);
  653. }
  654. }
  655. }
  656. // 验证:如果进入成熟期但没有final_output_item_id,这是一个严重错误
  657. if ($newStage === GROWTH_STAGE::MATURE->value && !$crop->final_output_item_id) {
  658. Log::error('严重错误:作物进入成熟期但没有确定最终产出果实ID', [
  659. 'crop_id' => $crop->id,
  660. 'user_id' => $crop->user_id,
  661. 'seed_id' => $crop->seed_id,
  662. 'current_stage' => $oldStage,
  663. 'new_stage' => $newStage
  664. ]);
  665. throw new \Exception("作物ID {$crop->id} 进入成熟期但没有确定最终产出果实ID,这是系统错误");
  666. }
  667. $crop->save();
  668. // 如果进入枯萎期,需要更新土地状态
  669. if ($newStage === GROWTH_STAGE::WITHERED->value) {
  670. $land = $crop->land;
  671. if ($land) {
  672. $land->status = LAND_STATUS::WITHERED;
  673. $land->updateHasCrop();
  674. $land->save();
  675. Log::info('作物进入枯萎期,更新土地状态', [
  676. 'crop_id' => $crop->id,
  677. 'land_id' => $land->id,
  678. 'land_status' => LAND_STATUS::WITHERED->value
  679. ]);
  680. }
  681. }
  682. // 触发生长阶段变更事件
  683. event(new CropGrowthStageChangedEvent($crop->user_id, $crop, $oldStage->value, $newStage));
  684. Log::info('作物生长阶段更新成功', [
  685. 'crop_id' => $cropId,
  686. 'user_id' => $crop->user_id,
  687. 'old_stage' => $oldStage,
  688. 'new_stage' => $newStage,
  689. 'stage_end_time' => $stageEndTime
  690. ]);
  691. return true;
  692. } catch (\Exception $e) {
  693. Log::error('作物生长阶段更新失败', [
  694. 'crop_id' => $cropId,
  695. 'error' => $e->getMessage(),
  696. 'trace' => $e->getTraceAsString()
  697. ]);
  698. return false;
  699. }
  700. }
  701. /**
  702. * 计算下一个生长阶段
  703. *
  704. * @param FarmCrop $crop
  705. * @return int
  706. */
  707. public function calculateNextStage(FarmCrop $crop): int
  708. {
  709. $currentStage = $crop->growth_stage;
  710. // 如果当前是成熟期,检查是否应该进入枯萎期
  711. $currentStageValue = is_object($currentStage) ? $currentStage->value : $currentStage;
  712. if ($currentStageValue === GROWTH_STAGE::MATURE->value) {
  713. // 如果成熟期已经超过结束时间,则进入枯萎期
  714. if ($crop->stage_end_time && now() >= $crop->stage_end_time) {
  715. return GROWTH_STAGE::WITHERED->value;
  716. }
  717. // 否则保持成熟期
  718. return GROWTH_STAGE::MATURE->value;
  719. }
  720. // 使用阶段映射确定下一个阶段
  721. $stageMap = [
  722. GROWTH_STAGE::SEED->value => GROWTH_STAGE::SPROUT->value,
  723. GROWTH_STAGE::SPROUT->value => GROWTH_STAGE::GROWTH->value,
  724. GROWTH_STAGE::GROWTH->value => GROWTH_STAGE::MATURE->value,
  725. GROWTH_STAGE::MATURE->value => GROWTH_STAGE::WITHERED->value,
  726. GROWTH_STAGE::WITHERED->value => GROWTH_STAGE::WITHERED->value, // 枯萎期保持不变
  727. ];
  728. // 确保返回整数值
  729. $currentStageValue = is_object($currentStage) ? $currentStage->value : $currentStage;
  730. return $stageMap[$currentStageValue] ?? GROWTH_STAGE::WITHERED->value;
  731. }
  732. /**
  733. * 计算阶段结束时间
  734. *
  735. * @param FarmCrop $crop
  736. * @param int $stage
  737. * @return \Carbon\Carbon|null
  738. */
  739. private function calculateStageEndTime(FarmCrop $crop, int $stage)
  740. {
  741. $seed = $crop->seed;
  742. if (!$seed) {
  743. return null;
  744. }
  745. $now = now();
  746. switch ($stage) {
  747. case GROWTH_STAGE::SEED->value:
  748. return $now->addSeconds($seed->seed_time);
  749. case GROWTH_STAGE::SPROUT->value:
  750. return $now->addSeconds($seed->sprout_time);
  751. case GROWTH_STAGE::GROWTH->value:
  752. return $now->addSeconds($seed->growth_time);
  753. case GROWTH_STAGE::MATURE->value:
  754. // 成熟期没有 时间限制
  755. // return $now->addHours(24);
  756. return null;
  757. case GROWTH_STAGE::WITHERED->value:
  758. // 枯萎期没有结束时间
  759. return null;
  760. default:
  761. return null;
  762. }
  763. }
  764. /**
  765. * 获取随机产出
  766. *
  767. * @param int $seedId
  768. * @return array
  769. */
  770. public function getRandomOutput(int $seedId): array
  771. {
  772. // 获取种子的所有产出配置
  773. $outputs = FarmSeedOutput::where('seed_id', $seedId)->get();
  774. if ($outputs->isEmpty()) {
  775. // 如果没有产出配置,使用种子的默认产出
  776. $seed = FarmSeed::find($seedId);
  777. return [
  778. 'item_id' => $seed->item_id,
  779. 'min_amount' => $seed->min_output,
  780. 'max_amount' => $seed->max_output,
  781. ];
  782. }
  783. // 按概率排序
  784. $outputs = $outputs->sortByDesc('probability');
  785. // 获取默认产出
  786. $defaultOutput = $outputs->firstWhere('is_default', true);
  787. // 随机选择产出
  788. $random = mt_rand(1, 100);
  789. $cumulativeProbability = 0;
  790. foreach ($outputs as $output) {
  791. $cumulativeProbability += $output->probability;
  792. if ($random <= $cumulativeProbability) {
  793. return [
  794. 'item_id' => $output->item_id,
  795. 'min_amount' => $output->min_amount,
  796. 'max_amount' => $output->max_amount,
  797. ];
  798. }
  799. }
  800. // 如果随机值超过了所有概率之和,使用默认产出
  801. if ($defaultOutput) {
  802. return [
  803. 'item_id' => $defaultOutput->item_id,
  804. 'min_amount' => $defaultOutput->min_amount,
  805. 'max_amount' => $defaultOutput->max_amount,
  806. ];
  807. }
  808. // 如果没有默认产出,使用第一个产出
  809. $firstOutput = $outputs->first();
  810. return [
  811. 'item_id' => $firstOutput->item_id,
  812. 'min_amount' => $firstOutput->min_amount,
  813. 'max_amount' => $firstOutput->max_amount,
  814. ];
  815. }
  816. /**
  817. * 根据物品ID获取产出配置信息
  818. *
  819. * @param int $seedId
  820. * @param int $itemId
  821. * @return array
  822. */
  823. private function getOutputInfoByItemId(int $seedId, int $itemId): array
  824. {
  825. // 获取种子的所有产出配置
  826. $outputs = FarmSeedOutput::where('seed_id', $seedId)->get();
  827. // 查找匹配的产出配置
  828. $targetOutput = $outputs->firstWhere('item_id', $itemId);
  829. if ($targetOutput) {
  830. return [
  831. 'item_id' => $targetOutput->item_id,
  832. 'min_amount' => $targetOutput->min_amount,
  833. 'max_amount' => $targetOutput->max_amount,
  834. ];
  835. }
  836. // 如果没有找到匹配的产出配置,使用种子的默认产出
  837. $seed = FarmSeed::find($seedId);
  838. return [
  839. 'item_id' => $itemId, // 使用传入的物品ID
  840. 'min_amount' => $seed->min_output,
  841. 'max_amount' => $seed->max_output,
  842. ];
  843. }
  844. }