|
|
@@ -0,0 +1,267 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace App\Module\Farm\Logics;
|
|
|
+
|
|
|
+use App\Module\Farm\Models\FarmMysterySeeLandEffect;
|
|
|
+use App\Module\Farm\Models\FarmSeedOutput;
|
|
|
+use App\Module\Farm\Models\FarmSeed;
|
|
|
+use Illuminate\Support\Facades\Log;
|
|
|
+use Illuminate\Support\Facades\Cache;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 神秘种子业务逻辑
|
|
|
+ *
|
|
|
+ * 处理神秘种子在不同土地类型上的概率调整和产出选择
|
|
|
+ */
|
|
|
+class MysterySeeLLogic
|
|
|
+{
|
|
|
+ /**
|
|
|
+ * 为神秘种子选择最终产出(在发芽期调用)
|
|
|
+ *
|
|
|
+ * @param int $seedId 种子ID
|
|
|
+ * @param int $landTypeId 土地类型ID
|
|
|
+ * @return array 选择的产出信息 ['item_id' => int, 'min_amount' => int, 'max_amount' => int]
|
|
|
+ */
|
|
|
+ public function selectFinalOutput(int $seedId, int $landTypeId): array
|
|
|
+ {
|
|
|
+ try {
|
|
|
+ // 获取调整后的概率分布
|
|
|
+ $adjustedOutputs = $this->calculateAdjustedProbabilities($seedId, $landTypeId);
|
|
|
+
|
|
|
+ // 执行随机选择
|
|
|
+ $selectedOutput = $this->randomSelectOutput($adjustedOutputs);
|
|
|
+
|
|
|
+ Log::info('神秘种子最终产出选择完成', [
|
|
|
+ 'seed_id' => $seedId,
|
|
|
+ 'land_type_id' => $landTypeId,
|
|
|
+ 'selected_output' => $selectedOutput,
|
|
|
+ 'adjusted_outputs_count' => count($adjustedOutputs)
|
|
|
+ ]);
|
|
|
+
|
|
|
+ return $selectedOutput;
|
|
|
+
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ Log::error('神秘种子产出选择失败', [
|
|
|
+ 'seed_id' => $seedId,
|
|
|
+ 'land_type_id' => $landTypeId,
|
|
|
+ 'error' => $e->getMessage(),
|
|
|
+ 'trace' => $e->getTraceAsString()
|
|
|
+ ]);
|
|
|
+
|
|
|
+ // 失败时使用默认逻辑
|
|
|
+ return $this->getDefaultOutput($seedId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算神秘种子在特定土地上的调整后概率
|
|
|
+ *
|
|
|
+ * @param int $seedId 种子ID
|
|
|
+ * @param int $landTypeId 土地类型ID
|
|
|
+ * @return array 调整后的产出配置
|
|
|
+ */
|
|
|
+ public function calculateAdjustedProbabilities(int $seedId, int $landTypeId): array
|
|
|
+ {
|
|
|
+ // 1. 获取基础产出配置
|
|
|
+ $baseOutputs = FarmSeedOutput::where('seed_id', $seedId)
|
|
|
+ ->get();
|
|
|
+
|
|
|
+ if ($baseOutputs->isEmpty()) {
|
|
|
+ // 如果没有产出配置,使用种子的默认配置
|
|
|
+ return $this->getDefaultOutputConfig($seedId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 获取土地影响配置(使用缓存)
|
|
|
+ $cacheKey = "mystery_seed_land_effects_{$seedId}_{$landTypeId}";
|
|
|
+ $landEffects = Cache::remember($cacheKey, 3600, function () use ($seedId, $landTypeId) {
|
|
|
+ return FarmMysterySeeLandEffect::forSeed($seedId)
|
|
|
+ ->forLandType($landTypeId)
|
|
|
+ ->active()
|
|
|
+ ->get()
|
|
|
+ ->keyBy('output_item_id');
|
|
|
+ });
|
|
|
+
|
|
|
+ // 3. 应用概率调整(优先级:覆盖值 > 修正值)
|
|
|
+ $adjustedOutputs = [];
|
|
|
+ foreach ($baseOutputs as $output) {
|
|
|
+ $baseProbability = $output->probability;
|
|
|
+ $landEffect = $landEffects->get($output->item_id);
|
|
|
+
|
|
|
+ // 优先使用覆盖值,其次使用修正值
|
|
|
+ if ($landEffect && $landEffect->probability_override !== null) {
|
|
|
+ // 使用覆盖值直接替换原概率
|
|
|
+ $adjustedProbability = $landEffect->probability_override;
|
|
|
+ $adjustmentType = 'override';
|
|
|
+ } else {
|
|
|
+ // 使用修正值调整原概率
|
|
|
+ $modifier = $landEffect ? $landEffect->probability_modifier : 0;
|
|
|
+ $adjustedProbability = $baseProbability + $modifier;
|
|
|
+ $adjustmentType = 'modifier';
|
|
|
+ }
|
|
|
+
|
|
|
+ // 确保概率在合理范围内 (0-100)
|
|
|
+ $adjustedProbability = max(0, min(100, $adjustedProbability));
|
|
|
+
|
|
|
+ $adjustedOutputs[] = [
|
|
|
+ 'item_id' => $output->item_id,
|
|
|
+ 'min_amount' => $output->min_amount,
|
|
|
+ 'max_amount' => $output->max_amount,
|
|
|
+ 'original_probability' => $baseProbability,
|
|
|
+ 'adjusted_probability' => $adjustedProbability,
|
|
|
+ 'adjustment_type' => $adjustmentType,
|
|
|
+ 'is_default' => $output->is_default
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 概率归一化(可选)
|
|
|
+ return $this->normalizeProbabilities($adjustedOutputs);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 基于概率随机选择产出
|
|
|
+ *
|
|
|
+ * @param array $outputs 产出配置数组
|
|
|
+ * @return array 选择的产出信息
|
|
|
+ */
|
|
|
+ private function randomSelectOutput(array $outputs): array
|
|
|
+ {
|
|
|
+ if (empty($outputs)) {
|
|
|
+ throw new \Exception('没有可用的产出配置');
|
|
|
+ }
|
|
|
+
|
|
|
+ $random = mt_rand(1, 10000) / 100; // 精确到0.01%
|
|
|
+ $cumulativeProbability = 0;
|
|
|
+
|
|
|
+ foreach ($outputs as $output) {
|
|
|
+ $cumulativeProbability += $output['adjusted_probability'];
|
|
|
+
|
|
|
+ if ($random <= $cumulativeProbability) {
|
|
|
+ return [
|
|
|
+ 'item_id' => $output['item_id'],
|
|
|
+ 'min_amount' => $output['min_amount'],
|
|
|
+ 'max_amount' => $output['max_amount']
|
|
|
+ ];
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果随机值超出范围,返回默认产出
|
|
|
+ $defaultOutput = collect($outputs)->firstWhere('is_default', true);
|
|
|
+ if ($defaultOutput) {
|
|
|
+ return [
|
|
|
+ 'item_id' => $defaultOutput['item_id'],
|
|
|
+ 'min_amount' => $defaultOutput['min_amount'],
|
|
|
+ 'max_amount' => $defaultOutput['max_amount']
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 最后的保险:返回第一个产出
|
|
|
+ $firstOutput = $outputs[0] ?? null;
|
|
|
+ if ($firstOutput) {
|
|
|
+ return [
|
|
|
+ 'item_id' => $firstOutput['item_id'],
|
|
|
+ 'min_amount' => $firstOutput['min_amount'],
|
|
|
+ 'max_amount' => $firstOutput['max_amount']
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ throw new \Exception('神秘种子没有有效的产出配置');
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 归一化概率分布
|
|
|
+ *
|
|
|
+ * @param array $outputs 产出配置数组
|
|
|
+ * @return array 归一化后的产出配置
|
|
|
+ */
|
|
|
+ private function normalizeProbabilities(array $outputs): array
|
|
|
+ {
|
|
|
+ $totalProbability = array_sum(array_column($outputs, 'adjusted_probability'));
|
|
|
+
|
|
|
+ // 如果总概率为0,使用默认产出
|
|
|
+ if ($totalProbability <= 0) {
|
|
|
+ foreach ($outputs as &$output) {
|
|
|
+ if ($output['is_default']) {
|
|
|
+ $output['adjusted_probability'] = 100;
|
|
|
+ } else {
|
|
|
+ $output['adjusted_probability'] = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return $outputs;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果需要归一化到100%(可选,根据需求决定是否启用)
|
|
|
+ // if ($totalProbability != 100) {
|
|
|
+ // $scaleFactor = 100 / $totalProbability;
|
|
|
+ // foreach ($outputs as &$output) {
|
|
|
+ // $output['adjusted_probability'] *= $scaleFactor;
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+
|
|
|
+ return $outputs;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取默认产出(当出现错误时使用)
|
|
|
+ *
|
|
|
+ * @param int $seedId
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ private function getDefaultOutput(int $seedId): array
|
|
|
+ {
|
|
|
+ $seed = FarmSeed::find($seedId);
|
|
|
+
|
|
|
+ if (!$seed) {
|
|
|
+ throw new \Exception("种子ID {$seedId} 不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'item_id' => $seed->item_id,
|
|
|
+ 'min_amount' => $seed->min_output,
|
|
|
+ 'max_amount' => $seed->max_output
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取默认产出配置
|
|
|
+ *
|
|
|
+ * @param int $seedId
|
|
|
+ * @return array
|
|
|
+ */
|
|
|
+ private function getDefaultOutputConfig(int $seedId): array
|
|
|
+ {
|
|
|
+ $seed = FarmSeed::find($seedId);
|
|
|
+
|
|
|
+ if (!$seed) {
|
|
|
+ throw new \Exception("种子ID {$seedId} 不存在");
|
|
|
+ }
|
|
|
+
|
|
|
+ return [[
|
|
|
+ 'item_id' => $seed->item_id,
|
|
|
+ 'min_amount' => $seed->min_output,
|
|
|
+ 'max_amount' => $seed->max_output,
|
|
|
+ 'original_probability' => 100,
|
|
|
+ 'adjusted_probability' => 100,
|
|
|
+ 'adjustment_type' => 'default',
|
|
|
+ 'is_default' => true
|
|
|
+ ]];
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清除缓存
|
|
|
+ *
|
|
|
+ * @param int|null $seedId 种子ID,为null时清除所有
|
|
|
+ * @param int|null $landTypeId 土地类型ID,为null时清除所有
|
|
|
+ */
|
|
|
+ public function clearCache(?int $seedId = null, ?int $landTypeId = null): void
|
|
|
+ {
|
|
|
+ if ($seedId && $landTypeId) {
|
|
|
+ // 清除特定组合的缓存
|
|
|
+ $cacheKey = "mystery_seed_land_effects_{$seedId}_{$landTypeId}";
|
|
|
+ Cache::forget($cacheKey);
|
|
|
+ } else {
|
|
|
+ // 清除所有相关缓存(使用标签或前缀匹配)
|
|
|
+ // 这里简化处理,实际项目中可以使用更精确的缓存清理策略
|
|
|
+ Cache::flush();
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|