|
|
@@ -9,12 +9,11 @@ use App\Module\Farm\Enums\LAND_STATUS;
|
|
|
use App\Module\Farm\Events\DisasterGeneratedEvent;
|
|
|
use App\Module\Farm\Models\FarmCrop;
|
|
|
use App\Module\Farm\Models\FarmGodBuff;
|
|
|
+use App\Module\Farm\Models\FarmLand;
|
|
|
use App\Module\Farm\Services\DisasterService;
|
|
|
use Illuminate\Support\Collection;
|
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
|
|
-use function Symfony\Component\Translation\t;
|
|
|
-
|
|
|
/**
|
|
|
* 灾害管理逻辑
|
|
|
*/
|
|
|
@@ -100,6 +99,9 @@ class DisasterLogic
|
|
|
throw new \Exception('作物不存在');
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * @var FarmLand $land
|
|
|
+ */
|
|
|
$land = $crop->land;
|
|
|
|
|
|
if (!$land) {
|
|
|
@@ -122,8 +124,8 @@ class DisasterLogic
|
|
|
// 获取种子的灾害抵抗属性
|
|
|
$disasterResistance = $seed->disaster_resistance ?? [];
|
|
|
|
|
|
- // 获取土地的灾害抵抗属性
|
|
|
- $landDisasterResistance = $land->landType->disaster_resistance ?? 0;
|
|
|
+ // 获取土地的灾害抵抗属性(数据库存储百分比,需要除以100转换为小数)
|
|
|
+ $landDisasterResistance = ($land->landType->disaster_resistance ?? 0) / 100;
|
|
|
|
|
|
// 检查用户是否有有效的神灵加持
|
|
|
$activeBuffs = FarmGodBuff::where('user_id', $userId)
|
|
|
@@ -131,18 +133,21 @@ class DisasterLogic
|
|
|
->pluck('buff_type')
|
|
|
->toArray();
|
|
|
|
|
|
- // 尝试生成灾害
|
|
|
- $disasterInfo = $this->tryGenerateDisasterForCrop($crop, $disasterResistance, $landDisasterResistance, $activeBuffs);
|
|
|
+ // 尝试生成灾害(支持多种灾害)
|
|
|
+ $disasterInfos = $this->tryGenerateDisasterForCrop($crop, $disasterResistance, $landDisasterResistance, $activeBuffs);
|
|
|
+
|
|
|
+ if (!empty($disasterInfos)) {
|
|
|
+ $disasterDtos = $this->applyDisastersToCrop($crop, $disasterInfos);
|
|
|
|
|
|
- if ($disasterInfo) {
|
|
|
Log::info('灾害生成成功', [
|
|
|
'user_id' => $userId,
|
|
|
'crop_id' => $crop->id,
|
|
|
'land_id' => $land->id,
|
|
|
- 'disaster_type' => $disasterInfo->type
|
|
|
+ 'disaster_count' => count($disasterInfos),
|
|
|
+ 'disaster_types' => array_column($disasterInfos, 'type')
|
|
|
]);
|
|
|
|
|
|
- return $disasterInfo;
|
|
|
+ return $disasterDtos[0] ?? null; // 返回第一个灾害信息保持兼容性
|
|
|
}
|
|
|
|
|
|
return null;
|
|
|
@@ -158,67 +163,123 @@ class DisasterLogic
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 尝试为作物生成灾害
|
|
|
+ * 尝试为作物生成灾害(支持多种灾害同时发生)
|
|
|
*
|
|
|
* @param FarmCrop $crop
|
|
|
- * @param array $disasterResistance
|
|
|
+ * @param mixed $disasterResistance
|
|
|
* @param float $landDisasterResistance
|
|
|
* @param array $activeBuffs
|
|
|
- * @return DisasterInfoDto|null
|
|
|
+ * @return array 生成的灾害信息数组
|
|
|
*/
|
|
|
- private function tryGenerateDisasterForCrop(FarmCrop $crop, array $disasterResistance, float $landDisasterResistance, array $activeBuffs): ?DisasterInfoDto
|
|
|
+ private function tryGenerateDisasterForCrop(FarmCrop $crop, $disasterResistance, float $landDisasterResistance, array $activeBuffs): array
|
|
|
{
|
|
|
// 灾害类型及其基础概率
|
|
|
$disasterTypes = DisasterService::getRate();
|
|
|
+ $generatedDisasters = [];
|
|
|
+
|
|
|
+ // 对每种灾害类型都进行判定
|
|
|
+ foreach ($disasterTypes as $disasterType => $baseProb) {
|
|
|
+ // 计算最终概率,考虑种子抵抗、土地抵抗和神灵加持
|
|
|
+ $seedResistance = 0;
|
|
|
+ if ($disasterResistance) {
|
|
|
+ if (is_object($disasterResistance) && method_exists($disasterResistance, 'getResistance')) {
|
|
|
+ // 如果是 DisasterResistanceCast 对象(数据库存储百分比,需要除以100转换为小数)
|
|
|
+ $seedResistance = $disasterResistance->getResistance($disasterType) / 100;
|
|
|
+ } elseif (is_array($disasterResistance)) {
|
|
|
+ // 如果是数组格式(数据库存储百分比,需要除以100转换为小数)
|
|
|
+ $seedResistance = ($disasterResistance[DisasterService::getDisasterKey($disasterType)] ?? 0) / 100;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- // 随机选择一种灾害类型
|
|
|
- $randomDisasterType = array_rand($disasterTypes);
|
|
|
- $baseProb = $disasterTypes[$randomDisasterType];
|
|
|
+ $finalProb = $baseProb - $seedResistance - $landDisasterResistance;
|
|
|
|
|
|
- // 计算最终概率,考虑种子抵抗、土地抵抗和神灵加持
|
|
|
- $seedResistance = $disasterResistance[DisasterService::getDisasterKey($randomDisasterType)] ?? 0;
|
|
|
- $finalProb = $baseProb - $seedResistance - $landDisasterResistance;
|
|
|
+ // 如果有对应的神灵加持,则不生成该类型的灾害
|
|
|
+ $buffType = DISASTER_TYPE::getPreventBuffType($disasterType);
|
|
|
+ $hasGodBuff = $buffType && in_array($buffType, $activeBuffs);
|
|
|
+ if ($hasGodBuff) {
|
|
|
+ $finalProb = 0;
|
|
|
+ }
|
|
|
|
|
|
- // 如果有对应的神灵加持,则不生成该类型的灾害
|
|
|
- $buffType = DISASTER_TYPE::getPreventBuffType($randomDisasterType);
|
|
|
- if ($buffType && in_array($buffType, $activeBuffs)) {
|
|
|
- $finalProb = 0;
|
|
|
- }
|
|
|
+ // 确保概率在有效范围内
|
|
|
+ $finalProb = max(0, min(1, $finalProb));
|
|
|
+
|
|
|
+ // 生成随机数
|
|
|
+ $randomNumber = mt_rand(1, 100);
|
|
|
+ $threshold = $finalProb * 100;
|
|
|
|
|
|
- // 确保概率在有效范围内
|
|
|
- $finalProb = max(0, min(1, $finalProb));
|
|
|
+ // 详细的debug日志
|
|
|
+ Log::debug('灾害生成详细信息', [
|
|
|
+ 'crop_id' => $crop->id,
|
|
|
+ 'user_id' => $crop->user_id,
|
|
|
+ 'growth_stage' => $crop->growth_stage->valueInt(),
|
|
|
+ 'disaster_type' => $disasterType,
|
|
|
+ 'disaster_type_name' => DISASTER_TYPE::getName($disasterType),
|
|
|
+ 'base_probability' => $baseProb,
|
|
|
+ 'seed_resistance' => $seedResistance,
|
|
|
+ 'land_resistance' => $landDisasterResistance,
|
|
|
+ 'active_buffs' => $activeBuffs,
|
|
|
+ 'god_buff_type' => $buffType,
|
|
|
+ 'has_god_buff' => $hasGodBuff,
|
|
|
+ 'final_probability' => $finalProb,
|
|
|
+ 'threshold' => $threshold,
|
|
|
+ 'random_number' => $randomNumber,
|
|
|
+ 'will_generate' => $randomNumber <= $threshold,
|
|
|
+ 'disaster_resistance_type' => is_object($disasterResistance) ? get_class($disasterResistance) : gettype($disasterResistance),
|
|
|
+ 'disaster_resistance_raw' => is_object($disasterResistance) ? 'object' : $disasterResistance
|
|
|
+ ]);
|
|
|
|
|
|
- // 随机决定是否生成灾害
|
|
|
- if (mt_rand(1, 100) <= $finalProb * 100) {
|
|
|
- return $this->applyDisasterToCrop($crop, $randomDisasterType);
|
|
|
+ // 随机决定是否生成该类型灾害
|
|
|
+ if ($randomNumber <= $threshold) {
|
|
|
+ $disasterInfo = [
|
|
|
+ 'type' => $disasterType,
|
|
|
+ 'generated_ts' => now()->toDateTimeString(),
|
|
|
+ 'status' => 'active'
|
|
|
+ ];
|
|
|
+
|
|
|
+ $generatedDisasters[] = $disasterInfo;
|
|
|
+
|
|
|
+ Log::debug('灾害生成成功', [
|
|
|
+ 'crop_id' => $crop->id,
|
|
|
+ 'disaster_type' => $disasterType,
|
|
|
+ 'disaster_type_name' => DISASTER_TYPE::getName($disasterType),
|
|
|
+ 'final_probability' => $finalProb
|
|
|
+ ]);
|
|
|
+ } else {
|
|
|
+ Log::info('灾害未生成', [
|
|
|
+ 'crop_id' => $crop->id,
|
|
|
+ 'disaster_type' => $disasterType,
|
|
|
+ 'disaster_type_name' => DISASTER_TYPE::getName($disasterType),
|
|
|
+ 'reason' => '随机数超过阈值',
|
|
|
+ 'random_number' => $randomNumber,
|
|
|
+ 'threshold' => $threshold,
|
|
|
+ 'final_probability' => $finalProb
|
|
|
+ ]);
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
- return null;
|
|
|
+ return $generatedDisasters;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 将灾害应用到作物上
|
|
|
+ * 将多个灾害应用到作物上
|
|
|
*
|
|
|
* @param FarmCrop $crop
|
|
|
- * @param int $disasterType
|
|
|
- * @return DisasterInfoDto
|
|
|
+ * @param array $disasterInfos
|
|
|
+ * @return array
|
|
|
*/
|
|
|
- private function applyDisasterToCrop(FarmCrop $crop, int $disasterType): DisasterInfoDto
|
|
|
+ private function applyDisastersToCrop(FarmCrop $crop, array $disasterInfos): array
|
|
|
{
|
|
|
- // 生成灾害信息
|
|
|
- $disasterInfo = [
|
|
|
- 'type' => $disasterType,
|
|
|
- 'generated_ts' => now()->toDateTimeString(),
|
|
|
- 'status' => 'active'
|
|
|
- ];
|
|
|
+ if (empty($disasterInfos)) {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
|
|
|
// 更新作物灾害信息
|
|
|
- $disasters = $crop->disasters ?? [];
|
|
|
- $disasters[] = $disasterInfo;
|
|
|
+ $disasters = $crop->disasters ?? [];
|
|
|
+ $disasters = array_merge($disasters, $disasterInfos);
|
|
|
$crop->disasters = $disasters;
|
|
|
|
|
|
// 更新土地状态为灾害
|
|
|
- $land = $crop->land;
|
|
|
+ $land = $crop->land;
|
|
|
$land->status = LAND_STATUS::DISASTER;
|
|
|
|
|
|
// 保存更改
|
|
|
@@ -226,13 +287,17 @@ class DisasterLogic
|
|
|
$land->save();
|
|
|
|
|
|
// 触发灾害生成事件
|
|
|
- event(new DisasterGeneratedEvent($crop->user_id, $crop, $disasterType, $disasterInfo));
|
|
|
+ $disasterDtos = [];
|
|
|
+ foreach ($disasterInfos as $disasterInfo) {
|
|
|
+ event(new DisasterGeneratedEvent($crop->user_id, $crop, $disasterInfo['type'], $disasterInfo));
|
|
|
+ $disasterDtos[] = DisasterInfoDto::fromArray($disasterInfo);
|
|
|
+ }
|
|
|
|
|
|
- return DisasterInfoDto::fromArray($disasterInfo);
|
|
|
+ return $disasterDtos;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 批量检查并生成灾害
|
|
|
+ * 批量获取需要检查灾害的作物并处理
|
|
|
*
|
|
|
* @param int|null $checkIntervalMinutes 检查间隔(分钟)
|
|
|
* @return array 生成结果统计
|
|
|
@@ -249,40 +314,45 @@ class DisasterLogic
|
|
|
// 1. 必须在发芽期或生长期
|
|
|
// 2. 当前阶段可以产生灾害 (can_disaster = 1)
|
|
|
// 3. 满足时间检查条件(首次检查或超过检查间隔)
|
|
|
- $crops = FarmCrop::whereIn('growth_stage', [ GROWTH_STAGE::SPROUT, GROWTH_STAGE::GROWTH ])
|
|
|
+ $crops = FarmCrop::whereIn('growth_stage', [GROWTH_STAGE::SPROUT, GROWTH_STAGE::GROWTH])
|
|
|
->where('can_disaster', true)
|
|
|
->where(function ($query) use ($checkTime) {
|
|
|
$query->whereNull('last_disaster_check_time')
|
|
|
- ->orWhere('last_disaster_check_time', '<', $checkTime);
|
|
|
+ ->orWhere('last_disaster_check_time', '<', $checkTime);
|
|
|
})
|
|
|
- ->with([
|
|
|
- 'land.landType', 'seed', 'user.buffs' => function ($query) {
|
|
|
- $query->where('expire_time', '>', now());
|
|
|
- }
|
|
|
- ])
|
|
|
+ ->with(['land.landType', 'seed', 'user.buffs' => function ($query) {
|
|
|
+ $query->where('expire_time', '>', now());
|
|
|
+ }])
|
|
|
->get();
|
|
|
|
|
|
- $totalCount = $crops->count();
|
|
|
+ $totalCount = $crops->count();
|
|
|
$generatedCount = 0;
|
|
|
- $skippedCount = 0;
|
|
|
+ $skippedCount = 0;
|
|
|
|
|
|
foreach ($crops as $crop) {
|
|
|
- $res = $this->generateDisasters($crop);
|
|
|
- if ($res) {
|
|
|
+ $result = $this->generateDisasters($crop);
|
|
|
+
|
|
|
+ if ($result === 'generated') {
|
|
|
$generatedCount++;
|
|
|
- } else {
|
|
|
+ } elseif ($result === 'skipped') {
|
|
|
$skippedCount++;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
return [
|
|
|
- 'total' => $totalCount,
|
|
|
+ 'total' => $totalCount,
|
|
|
'generated' => $generatedCount,
|
|
|
- 'skipped' => $skippedCount
|
|
|
+ 'skipped' => $skippedCount
|
|
|
];
|
|
|
}
|
|
|
|
|
|
- public function generateDisasters(FarmCrop $crop): bool
|
|
|
+ /**
|
|
|
+ * 为单个作物生成灾害
|
|
|
+ *
|
|
|
+ * @param FarmCrop $crop
|
|
|
+ * @return string 处理结果:'generated', 'skipped', 'failed'
|
|
|
+ */
|
|
|
+ public function generateDisasters(FarmCrop $crop): string
|
|
|
{
|
|
|
try {
|
|
|
// 更新检查时间
|
|
|
@@ -291,37 +361,59 @@ class DisasterLogic
|
|
|
// 跳过已经有灾害的土地
|
|
|
if ($crop->land->status === LAND_STATUS::DISASTER) {
|
|
|
$crop->save(); // 仍需更新检查时间
|
|
|
-
|
|
|
- return false;
|
|
|
+ return 'skipped';
|
|
|
}
|
|
|
|
|
|
// 获取相关数据
|
|
|
- $disasterResistance = $crop->seed->disaster_resistance ?? [];
|
|
|
- $landDisasterResistance = $crop->land->landType->disaster_resistance ?? 0;
|
|
|
- $activeBuffs = $crop->user->buffs->pluck('buff_type')->toArray();
|
|
|
+ $disasterResistance = $crop->seed->disaster_resistance ?? null;
|
|
|
+ $landDisasterResistance = ($crop->land->landType->disaster_resistance ?? 0) / 100; // 数据库存储百分比,需要除以100
|
|
|
+ $activeBuffs = $crop->user->buffs->pluck('buff_type')->toArray();
|
|
|
|
|
|
- // 尝试生成灾害
|
|
|
- $disasterInfo = $this->tryGenerateDisasterForCrop($crop, $disasterResistance, $landDisasterResistance, $activeBuffs);
|
|
|
+ // 尝试生成灾害(支持多种灾害)
|
|
|
+ $disasterInfos = $this->tryGenerateDisasterForCrop($crop, $disasterResistance, $landDisasterResistance, $activeBuffs);
|
|
|
+
|
|
|
+ if (!empty($disasterInfos)) {
|
|
|
+ // 应用灾害到作物
|
|
|
+ $this->applyDisastersToCrop($crop, $disasterInfos);
|
|
|
|
|
|
- if ($disasterInfo) {
|
|
|
// 生成灾害后,设置当前阶段不能再产生灾害
|
|
|
$crop->can_disaster = false;
|
|
|
+ $crop->save();
|
|
|
|
|
|
- }
|
|
|
-
|
|
|
- // 保存更改
|
|
|
- $crop->save();
|
|
|
+ Log::info('单个作物灾害生成成功', [
|
|
|
+ 'crop_id' => $crop->id,
|
|
|
+ 'user_id' => $crop->user_id,
|
|
|
+ 'disaster_count' => count($disasterInfos),
|
|
|
+ 'disaster_types' => array_column($disasterInfos, 'type')
|
|
|
+ ]);
|
|
|
|
|
|
- return true;
|
|
|
+ return 'generated';
|
|
|
+ } else {
|
|
|
+ // 没有生成灾害,但需要保存检查时间
|
|
|
+ $crop->save();
|
|
|
+ return 'checked';
|
|
|
+ }
|
|
|
|
|
|
} catch (\Exception $e) {
|
|
|
- Log::error('批量生成灾害失败', [
|
|
|
+ Log::error('单个作物灾害生成失败', [
|
|
|
'crop_id' => $crop->id,
|
|
|
- 'error' => $e->getMessage()
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
+ 'trace' => $e->getTraceAsString()
|
|
|
]);
|
|
|
+
|
|
|
+ return 'failed';
|
|
|
}
|
|
|
+ }
|
|
|
|
|
|
- return false;
|
|
|
+ /**
|
|
|
+ * 批量检查并生成灾害(兼容性方法)
|
|
|
+ *
|
|
|
+ * @param int|null $checkIntervalMinutes 检查间隔(分钟)
|
|
|
+ * @return array 生成结果统计
|
|
|
+ */
|
|
|
+ public function batchGenerateDisasters(?int $checkIntervalMinutes = null): array
|
|
|
+ {
|
|
|
+ return $this->generateDisasterBatchs($checkIntervalMinutes);
|
|
|
}
|
|
|
|
|
|
}
|