notfff il y a 7 mois
Parent
commit
fefd482798

+ 200 - 0
app/Module/Farm/AdminControllers/FarmMysterySeeLandEffectController.php

@@ -0,0 +1,200 @@
+<?php
+
+namespace App\Module\Farm\AdminControllers;
+
+use App\Module\Farm\AdminControllers\Helper\FilterHelper;
+use App\Module\Farm\AdminControllers\Helper\FormHelper;
+use App\Module\Farm\AdminControllers\Helper\GridHelper;
+use App\Module\Farm\Models\FarmSeed;
+use App\Module\Farm\Models\FarmLandType;
+use App\Module\Farm\Repositories\FarmMysterySeeLandEffectRepository;
+use Dcat\Admin\Grid;
+use Dcat\Admin\Show;
+use Dcat\Admin\Form;
+use UCore\DcatAdmin\AdminController;
+use Spatie\RouteAttributes\Attributes\Resource;
+
+/**
+ * 神秘种子土地影响配置管理控制器
+ */
+#[Resource('farm-mystery-seed-land-effects', names: 'dcat.admin.farm-mystery-seed-land-effects')]
+class FarmMysterySeeLandEffectController extends AdminController
+{
+    /**
+     * 页面标题
+     *
+     * @var string
+     */
+    protected $title = '神秘种子土地影响配置管理';
+
+    /**
+     * 页面描述
+     *
+     * @var string
+     */
+    protected $description = '管理神秘种子在不同土地类型上的产出概率影响配置';
+
+    /**
+     * 构建表格
+     *
+     * @return Grid
+     */
+    protected function grid()
+    {
+        return Grid::make(new FarmMysterySeeLandEffectRepository(['seed', 'landType']), function (Grid $grid) {
+            $helper = new GridHelper($grid, $this);
+
+            $helper->columnId();
+
+            $grid->column('seed.name', '种子名称')->label('primary');
+
+            $grid->column('landType.name', '土地类型')->label('info');
+
+            $grid->column('output_item_id', '产出物品ID')->display(function ($value) {
+                return "<span class='badge badge-secondary'>{$value}</span>";
+            });
+
+            $grid->column('probability_modifier', '概率修正值')->display(function ($value) {
+                $color = $value > 0 ? 'success' : ($value < 0 ? 'danger' : 'secondary');
+                $sign = $value > 0 ? '+' : '';
+                return "<span class='badge badge-{$color}'>{$sign}{$value}%</span>";
+            });
+
+            $grid->column('probability_override', '概率覆盖值')->display(function ($value) {
+                return $value !== null ? "<span class='badge badge-warning'>{$value}%</span>" : '<span class="text-muted">-</span>';
+            });
+
+            $grid->column('adjustment_type', '调整类型')->display(function ($value, $column, $model) {
+                return $model->probability_override !== null ?
+                    '<span class="badge badge-warning">覆盖</span>' :
+                    '<span class="badge badge-info">修正</span>';
+            });
+
+            $grid->column('is_active', '状态')->bool();
+
+            $helper->columnCreatedAt();
+
+            $grid->filter(function (Grid\Filter $filter) {
+                $filterHelper = new FilterHelper($filter, $this);
+
+                $filterHelper->equalId();
+
+                $filter->equal('seed_id', '种子')->select(function () {
+                    return FarmSeed::pluck('name', 'id')->toArray();
+                });
+
+                $filter->equal('land_type_id', '土地类型')->select(function () {
+                    return FarmLandType::pluck('name', 'id')->toArray();
+                });
+
+                $filter->equal('is_active', '状态')->select([
+                    1 => '启用',
+                    0 => '禁用'
+                ]);
+
+                $filterHelper->betweenDatetime('created_at', '创建时间');
+            });
+        });
+    }
+
+    /**
+     * 构建详情页
+     *
+     * @param mixed $id
+     * @return Show
+     */
+    protected function detail($id)
+    {
+        return Show::make($id, new FarmMysterySeeLandEffectRepository(), function (Show $show) {
+
+            $show->field('id', 'ID');
+            $show->field('seed.name', '种子名称');
+            $show->field('landType.name', '土地类型');
+            $show->field('output_item_id', '产出物品ID');
+            $show->field('probability_modifier', '概率修正值')->as(function ($value) {
+                $sign = $value > 0 ? '+' : '';
+                return "{$sign}{$value}%";
+            });
+            $show->field('probability_override', '概率覆盖值')->as(function ($value) {
+                return $value !== null ? "{$value}%" : '未设置';
+            });
+            $show->field('adjustment_type', '调整类型')->as(function ($value, $model) {
+                return $model->probability_override !== null ? '概率覆盖' : '概率修正';
+            });
+            $show->field('is_active', '状态')->using([1 => '启用', 0 => '禁用']);
+            $show->field('created_at', '创建时间');
+            $show->field('updated_at', '更新时间');
+        });
+    }
+
+    /**
+     * 构建表单
+     *
+     * @return Form
+     */
+    protected function form()
+    {
+        return Form::make(new FarmMysterySeeLandEffectRepository(), function (Form $form) {
+            $helper = new FormHelper($form, $this);
+
+            $form->display('id', 'ID');
+
+            $helper->selectSeed('seed_id', '种子');
+
+            $form->select('land_type_id', '土地类型')
+                ->options(function () {
+                    return FarmLandType::pluck('name', 'id')->toArray();
+                })
+                ->required()
+                ->help('选择土地类型');
+
+            $form->number('output_item_id', '产出物品ID')
+                ->required()
+                ->min(1)
+                ->help('产出物品的ID');
+
+            $form->decimal('probability_modifier', '概率修正值')
+                ->default(0.0000)
+                ->help('百分比值,可为负数。如:5.0000 表示 +5%,-3.0000 表示 -3%');
+
+            $form->decimal('probability_override', '概率覆盖值')
+                ->help('百分比值,直接覆盖原概率。如:25.0000 表示覆盖为25%。优先级高于修正值,留空则使用修正值');
+
+            $helper->switch('is_active', '是否启用');
+
+            $form->display('created_at', '创建时间');
+            $form->display('updated_at', '更新时间');
+
+            $form->divider();
+
+            $form->html('
+                <div class="alert alert-info">
+                    <h5>配置说明:</h5>
+                    <ul>
+                        <li><strong>概率修正值</strong>:在原概率基础上增减,如原概率10%,修正值+5%,最终概率15%</li>
+                        <li><strong>概率覆盖值</strong>:直接替换原概率,如覆盖值25%,最终概率就是25%</li>
+                        <li><strong>优先级</strong>:覆盖值 > 修正值,如果设置了覆盖值,修正值将被忽略</li>
+                        <li><strong>应用场景</strong>:修正值适合微调,覆盖值适合大幅改变</li>
+                    </ul>
+                </div>
+            ');
+
+            $form->saving(function (Form $form) {
+                // 保存前的数据验证和处理
+                if ($form->probability_override !== null && $form->probability_override < 0) {
+                    return $form->response()->error('概率覆盖值不能为负数');
+                }
+
+                if ($form->probability_modifier < -100 || $form->probability_modifier > 100) {
+                    return $form->response()->error('概率修正值应在-100到100之间');
+                }
+            });
+
+            $form->saved(function (Form $form) {
+                // 保存后清除相关缓存
+                $mysteryLogic = new \App\Module\Farm\Logics\MysterySeeLLogic();
+                $mysteryLogic->clearCache($form->model()->seed_id, $form->model()->land_type_id);
+            });
+        });
+    }
+}

+ 29 - 9
app/Module/Farm/Logics/CropLogic.php

@@ -744,15 +744,35 @@ class CropLogic
             // 如果进入发芽期,必须确定最终产出果实ID
             if ($newStage === GROWTH_STAGE::SPROUT->value) {
                 if (!$crop->final_output_item_id) {
-                    $outputInfo = $this->getRandomOutput($crop->seed_id);
-                    $crop->final_output_item_id = $outputInfo['item_id'];
-
-                    Log::info('作物进入发芽期,确定最终产出果实', [
-                        'crop_id' => $crop->id,
-                        'user_id' => $crop->user_id,
-                        'seed_id' => $crop->seed_id,
-                        'final_output_item_id' => $crop->final_output_item_id
-                    ]);
+                    $seed = $crop->seed;
+
+                    // 如果是神秘种子,使用土地影响逻辑
+                    if ($seed && $seed->type == \App\Module\Farm\Enums\SEED_TYPE::MYSTERIOUS->value) {
+                        $land = $crop->land;
+                        $mysteryLogic = new \App\Module\Farm\Logics\MysterySeeLLogic();
+                        $selectedOutput = $mysteryLogic->selectFinalOutput($seed->id, $land->land_type);
+
+                        $crop->final_output_item_id = $selectedOutput['item_id'];
+
+                        Log::info('神秘种子确定最终产出(基于土地影响)', [
+                            'crop_id' => $crop->id,
+                            'user_id' => $crop->user_id,
+                            'seed_id' => $seed->id,
+                            'land_type' => $land->land_type,
+                            'final_output_item_id' => $selectedOutput['item_id']
+                        ]);
+                    } else {
+                        // 普通种子使用原有逻辑
+                        $outputInfo = $this->getRandomOutput($crop->seed_id);
+                        $crop->final_output_item_id = $outputInfo['item_id'];
+
+                        Log::info('作物进入发芽期,确定最终产出果实', [
+                            'crop_id' => $crop->id,
+                            'user_id' => $crop->user_id,
+                            'seed_id' => $crop->seed_id,
+                            'final_output_item_id' => $crop->final_output_item_id
+                        ]);
+                    }
                 }
             }
 

+ 267 - 0
app/Module/Farm/Logics/MysterySeeLLogic.php

@@ -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();
+        }
+    }
+}

+ 111 - 0
app/Module/Farm/Models/FarmMysterySeeLandEffect.php

@@ -0,0 +1,111 @@
+<?php
+
+namespace App\Module\Farm\Models;
+
+use UCore\ModelCore;
+use Illuminate\Database\Eloquent\Relations\BelongsTo;
+
+/**
+ * 神秘种子土地影响配置模型
+ * 
+ * @property int $id
+ * @property int $seed_id 种子ID
+ * @property int $land_type_id 土地类型ID
+ * @property int $output_item_id 产出物品ID
+ * @property float $probability_modifier 概率修正值
+ * @property float|null $probability_override 概率覆盖值
+ * @property bool $is_active 是否启用
+ * @property \Carbon\Carbon $created_at
+ * @property \Carbon\Carbon $updated_at
+ * 
+ * @property-read FarmSeed $seed 关联的种子
+ * @property-read FarmLandType $landType 关联的土地类型
+ */
+class FarmMysterySeeLandEffect extends ModelCore
+{
+    protected $table = 'farm_mystery_seed_land_effects';
+
+    // attrlist start
+    protected $fillable = [
+        'seed_id',
+        'land_type_id',
+        'output_item_id',
+        'probability_modifier',
+        'probability_override',
+        'is_active'
+    ];
+    // attrlist end
+
+    protected $casts = [
+        'probability_modifier' => 'decimal:4',
+        'probability_override' => 'decimal:4',
+        'is_active' => 'boolean'
+    ];
+
+    /**
+     * 关联种子
+     */
+    public function seed(): BelongsTo
+    {
+        return $this->belongsTo(FarmSeed::class, 'seed_id');
+    }
+
+    /**
+     * 关联土地类型
+     */
+    public function landType(): BelongsTo
+    {
+        return $this->belongsTo(FarmLandType::class, 'land_type_id');
+    }
+
+    /**
+     * 获取调整类型
+     * 
+     * @return string
+     */
+    public function getAdjustmentTypeAttribute(): string
+    {
+        return $this->probability_override !== null ? 'override' : 'modifier';
+    }
+
+    /**
+     * 获取最终概率值
+     * 
+     * @param float $baseProbability 基础概率
+     * @return float
+     */
+    public function getFinalProbability(float $baseProbability): float
+    {
+        if ($this->probability_override !== null) {
+            // 使用覆盖值
+            return $this->probability_override;
+        } else {
+            // 使用修正值
+            return $baseProbability + $this->probability_modifier;
+        }
+    }
+
+    /**
+     * 作用域:启用的配置
+     */
+    public function scopeActive($query)
+    {
+        return $query->where('is_active', true);
+    }
+
+    /**
+     * 作用域:指定种子的配置
+     */
+    public function scopeForSeed($query, int $seedId)
+    {
+        return $query->where('seed_id', $seedId);
+    }
+
+    /**
+     * 作用域:指定土地类型的配置
+     */
+    public function scopeForLandType($query, int $landTypeId)
+    {
+        return $query->where('land_type_id', $landTypeId);
+    }
+}

+ 18 - 0
app/Module/Farm/Repositories/FarmMysterySeeLandEffectRepository.php

@@ -0,0 +1,18 @@
+<?php
+
+namespace App\Module\Farm\Repositories;
+
+use App\Module\Farm\Models\FarmMysterySeeLandEffect;
+use UCore\DcatAdmin\Repository\EloquentRepository;
+
+
+/**
+ * 神秘种子土地影响配置仓库
+ */
+class FarmMysterySeeLandEffectRepository extends EloquentRepository
+{
+    protected $eloquentClass=FarmMysterySeeLandEffect::class;
+
+
+    
+}

+ 43 - 0
database/migrations/2025_05_27_180000_create_farm_mystery_seed_land_effects_table.php

@@ -0,0 +1,43 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+    /**
+     * Run the migrations.
+     */
+    public function up(): void
+    {
+        Schema::create('farm_mystery_seed_land_effects', function (Blueprint $table) {
+            $table->id()->comment('主键ID');
+            $table->unsignedBigInteger('seed_id')->comment('种子ID(神秘种子)');
+            $table->unsignedBigInteger('land_type_id')->comment('土地类型ID');
+            $table->unsignedBigInteger('output_item_id')->comment('产出物品ID');
+            $table->decimal('probability_modifier', 7, 4)->default(0.0000)->comment('概率修正值(百分比,可为负数)');
+            $table->decimal('probability_override', 7, 4)->nullable()->comment('概率覆盖值(百分比,覆盖种子的物品原产出概率,优先级高于修正值)');
+            $table->boolean('is_active')->default(true)->comment('是否启用');
+            $table->timestamps();
+
+            // 索引
+            $table->unique(['seed_id', 'land_type_id', 'output_item_id'], 'unique_seed_land_output');
+            $table->index('seed_id', 'idx_seed_id');
+            $table->index('land_type_id', 'idx_land_type_id');
+            $table->index('output_item_id', 'idx_output_item_id');
+
+            // 外键约束
+            $table->foreign('seed_id')->references('id')->on('farm_seeds')->onDelete('cascade');
+            $table->foreign('land_type_id')->references('id')->on('farm_land_types')->onDelete('cascade');
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('farm_mystery_seed_land_effects');
+    }
+};

+ 74 - 0
database/seeders/FarmMysterySeeLandEffectSeeder.php

@@ -0,0 +1,74 @@
+<?php
+
+namespace Database\Seeders;
+
+use Illuminate\Database\Seeder;
+use Illuminate\Support\Facades\DB;
+use Carbon\Carbon;
+
+class FarmMysterySeeLandEffectSeeder extends Seeder
+{
+    /**
+     * Run the database seeds.
+     */
+    public function run(): void
+    {
+        $now = Carbon::now();
+        
+        // 神秘种子(ID=1)在不同土地类型上的产出概率调整
+        // 假设基础产出配置:物品2(10%)、物品3(10%,默认)、物品4(10%)
+        
+        $effects = [
+            // 普通土地:无影响
+            ['seed_id' => 1, 'land_type_id' => 1, 'output_item_id' => 2, 'probability_modifier' => 0.0000, 'probability_override' => null, 'is_active' => true],
+            ['seed_id' => 1, 'land_type_id' => 1, 'output_item_id' => 3, 'probability_modifier' => 0.0000, 'probability_override' => null, 'is_active' => true],
+            ['seed_id' => 1, 'land_type_id' => 1, 'output_item_id' => 4, 'probability_modifier' => 0.0000, 'probability_override' => null, 'is_active' => true],
+
+            // 红土地:提升物品2概率,降低物品4概率
+            ['seed_id' => 1, 'land_type_id' => 2, 'output_item_id' => 2, 'probability_modifier' => 5.0000, 'probability_override' => null, 'is_active' => true],
+            ['seed_id' => 1, 'land_type_id' => 2, 'output_item_id' => 3, 'probability_modifier' => 0.0000, 'probability_override' => null, 'is_active' => true],
+            ['seed_id' => 1, 'land_type_id' => 2, 'output_item_id' => 4, 'probability_modifier' => -3.0000, 'probability_override' => null, 'is_active' => true],
+
+            // 黑土地:进一步提升物品2概率
+            ['seed_id' => 1, 'land_type_id' => 3, 'output_item_id' => 2, 'probability_modifier' => 10.0000, 'probability_override' => null, 'is_active' => true],
+            ['seed_id' => 1, 'land_type_id' => 3, 'output_item_id' => 3, 'probability_modifier' => -2.0000, 'probability_override' => null, 'is_active' => true],
+            ['seed_id' => 1, 'land_type_id' => 3, 'output_item_id' => 4, 'probability_modifier' => -5.0000, 'probability_override' => null, 'is_active' => true],
+
+            // 金土地:大幅提升稀有物品概率,物品2使用覆盖值
+            ['seed_id' => 1, 'land_type_id' => 4, 'output_item_id' => 2, 'probability_modifier' => 0.0000, 'probability_override' => 25.0000, 'is_active' => true],
+            ['seed_id' => 1, 'land_type_id' => 4, 'output_item_id' => 3, 'probability_modifier' => -5.0000, 'probability_override' => null, 'is_active' => true],
+            ['seed_id' => 1, 'land_type_id' => 4, 'output_item_id' => 4, 'probability_modifier' => 8.0000, 'probability_override' => null, 'is_active' => true],
+
+            // 蓝土地:平衡提升所有稀有物品
+            ['seed_id' => 1, 'land_type_id' => 5, 'output_item_id' => 2, 'probability_modifier' => 12.0000, 'probability_override' => null, 'is_active' => true],
+            ['seed_id' => 1, 'land_type_id' => 5, 'output_item_id' => 3, 'probability_modifier' => -3.0000, 'probability_override' => null, 'is_active' => true],
+            ['seed_id' => 1, 'land_type_id' => 5, 'output_item_id' => 4, 'probability_modifier' => 12.0000, 'probability_override' => null, 'is_active' => true],
+
+            // 紫土地:极大提升最稀有物品概率,物品2和物品4使用覆盖值
+            ['seed_id' => 1, 'land_type_id' => 6, 'output_item_id' => 2, 'probability_modifier' => 0.0000, 'probability_override' => 30.0000, 'is_active' => true],
+            ['seed_id' => 1, 'land_type_id' => 6, 'output_item_id' => 3, 'probability_modifier' => -8.0000, 'probability_override' => null, 'is_active' => true],
+            ['seed_id' => 1, 'land_type_id' => 6, 'output_item_id' => 4, 'probability_modifier' => 0.0000, 'probability_override' => 25.0000, 'is_active' => true],
+        ];
+
+        // 为每个配置添加时间戳
+        foreach ($effects as &$effect) {
+            $effect['created_at'] = $now;
+            $effect['updated_at'] = $now;
+        }
+
+        // 批量插入数据
+        DB::table('farm_mystery_seed_land_effects')->insert($effects);
+
+        $this->command->info('神秘种子土地影响配置数据已插入完成!');
+        $this->command->info('共插入 ' . count($effects) . ' 条配置记录');
+        
+        // 显示配置概览
+        $this->command->info('配置概览:');
+        $this->command->info('- 普通土地:无影响(基础概率)');
+        $this->command->info('- 红土地:物品2 +5%,物品4 -3%');
+        $this->command->info('- 黑土地:物品2 +10%,物品3 -2%,物品4 -5%');
+        $this->command->info('- 金土地:物品2 覆盖25%,物品3 -5%,物品4 +8%');
+        $this->command->info('- 蓝土地:物品2 +12%,物品3 -3%,物品4 +12%');
+        $this->command->info('- 紫土地:物品2 覆盖30%,物品3 -8%,物品4 覆盖25%');
+    }
+}

+ 268 - 0
tests/Unit/Farm/MysterySeeLLogicTest.php

@@ -0,0 +1,268 @@
+<?php
+
+namespace Tests\Unit\Farm;
+
+use Tests\TestCase;
+use App\Module\Farm\Logics\MysterySeeLLogic;
+use App\Module\Farm\Models\FarmMysterySeeLandEffect;
+use App\Module\Farm\Models\FarmSeedOutput;
+use App\Module\Farm\Models\FarmSeed;
+use Illuminate\Foundation\Testing\RefreshDatabase;
+use Illuminate\Support\Facades\Cache;
+
+class MysterySeeLLogicTest extends TestCase
+{
+    use RefreshDatabase;
+
+    protected MysterySeeLLogic $logic;
+
+    protected function setUp(): void
+    {
+        parent::setUp();
+        $this->logic = new MysterySeeLLogic();
+        
+        // 清除缓存
+        Cache::flush();
+    }
+
+    /**
+     * 测试概率覆盖值优先级高于修正值
+     */
+    public function testProbabilityOverrideVsModifier()
+    {
+        // 创建测试种子
+        $seed = FarmSeed::factory()->create(['id' => 1]);
+        
+        // 创建基础产出配置
+        FarmSeedOutput::factory()->create([
+            'seed_id' => 1,
+            'item_id' => 2,
+            'probability' => 10.0000,
+            'min_amount' => 1,
+            'max_amount' => 5,
+            'is_default' => false
+        ]);
+
+        // 创建测试数据:同时设置修正值和覆盖值
+        FarmMysterySeeLandEffect::factory()->create([
+            'seed_id' => 1,
+            'land_type_id' => 6,
+            'output_item_id' => 2,
+            'probability_modifier' => 5.0000,    // 修正值 +5%
+            'probability_override' => 30.0000,   // 覆盖值 30%
+            'is_active' => true
+        ]);
+
+        $result = $this->logic->calculateAdjustedProbabilities(1, 6);
+
+        // 应该使用覆盖值30%,而不是基础概率+修正值
+        $item2Result = collect($result)->firstWhere('item_id', 2);
+        $this->assertEquals(30.0000, $item2Result['adjusted_probability']);
+        $this->assertEquals('override', $item2Result['adjustment_type']);
+    }
+
+    /**
+     * 测试没有覆盖值时使用修正值
+     */
+    public function testProbabilityModifierWhenNoOverride()
+    {
+        // 创建测试种子
+        $seed = FarmSeed::factory()->create(['id' => 1]);
+        
+        // 创建基础产出配置
+        FarmSeedOutput::factory()->create([
+            'seed_id' => 1,
+            'item_id' => 2,
+            'probability' => 10.0000,
+            'min_amount' => 1,
+            'max_amount' => 5,
+            'is_default' => false
+        ]);
+
+        // 创建测试数据:只设置修正值
+        FarmMysterySeeLandEffect::factory()->create([
+            'seed_id' => 1,
+            'land_type_id' => 2,
+            'output_item_id' => 2,
+            'probability_modifier' => 5.0000,    // 修正值 +5%
+            'probability_override' => null,      // 无覆盖值
+            'is_active' => true
+        ]);
+
+        $result = $this->logic->calculateAdjustedProbabilities(1, 2);
+
+        // 应该使用基础概率+修正值
+        $item2Result = collect($result)->firstWhere('item_id', 2);
+        $this->assertEquals(15.0000, $item2Result['adjusted_probability']); // 10% + 5%
+        $this->assertEquals('modifier', $item2Result['adjustment_type']);
+    }
+
+    /**
+     * 测试概率边界值处理
+     */
+    public function testProbabilityBoundaries()
+    {
+        // 创建测试种子
+        $seed = FarmSeed::factory()->create(['id' => 1]);
+        
+        // 创建基础产出配置
+        FarmSeedOutput::factory()->create([
+            'seed_id' => 1,
+            'item_id' => 2,
+            'probability' => 5.0000,
+            'min_amount' => 1,
+            'max_amount' => 5,
+            'is_default' => false
+        ]);
+
+        // 测试负值修正导致概率小于0的情况
+        FarmMysterySeeLandEffect::factory()->create([
+            'seed_id' => 1,
+            'land_type_id' => 1,
+            'output_item_id' => 2,
+            'probability_modifier' => -10.0000,  // -10%,会导致负值
+            'probability_override' => null,
+            'is_active' => true
+        ]);
+
+        $result = $this->logic->calculateAdjustedProbabilities(1, 1);
+
+        // 概率应该被限制在0以上
+        $item2Result = collect($result)->firstWhere('item_id', 2);
+        $this->assertEquals(0.0000, $item2Result['adjusted_probability']);
+    }
+
+    /**
+     * 测试随机选择逻辑
+     */
+    public function testRandomSelection()
+    {
+        // 创建测试数据
+        $outputs = [
+            [
+                'item_id' => 2,
+                'min_amount' => 1,
+                'max_amount' => 5,
+                'adjusted_probability' => 50.0000,
+                'is_default' => false
+            ],
+            [
+                'item_id' => 3,
+                'min_amount' => 2,
+                'max_amount' => 6,
+                'adjusted_probability' => 30.0000,
+                'is_default' => true
+            ],
+            [
+                'item_id' => 4,
+                'min_amount' => 3,
+                'max_amount' => 7,
+                'adjusted_probability' => 20.0000,
+                'is_default' => false
+            ]
+        ];
+
+        // 多次测试随机选择,验证结果在预期范围内
+        $results = [];
+        for ($i = 0; $i < 100; $i++) {
+            $reflection = new \ReflectionClass($this->logic);
+            $method = $reflection->getMethod('randomSelectOutput');
+            $method->setAccessible(true);
+            
+            $result = $method->invoke($this->logic, $outputs);
+            $results[] = $result['item_id'];
+        }
+
+        // 验证所有结果都在预期的物品ID范围内
+        $uniqueResults = array_unique($results);
+        $this->assertContains(2, $uniqueResults);
+        $this->assertContains(3, $uniqueResults);
+        $this->assertContains(4, $uniqueResults);
+    }
+
+    /**
+     * 测试缓存功能
+     */
+    public function testCacheFunction()
+    {
+        // 创建测试种子
+        $seed = FarmSeed::factory()->create(['id' => 1]);
+        
+        // 创建基础产出配置
+        FarmSeedOutput::factory()->create([
+            'seed_id' => 1,
+            'item_id' => 2,
+            'probability' => 10.0000,
+            'min_amount' => 1,
+            'max_amount' => 5,
+            'is_default' => false
+        ]);
+
+        // 创建土地影响配置
+        FarmMysterySeeLandEffect::factory()->create([
+            'seed_id' => 1,
+            'land_type_id' => 1,
+            'output_item_id' => 2,
+            'probability_modifier' => 5.0000,
+            'probability_override' => null,
+            'is_active' => true
+        ]);
+
+        // 第一次调用,应该从数据库查询
+        $result1 = $this->logic->calculateAdjustedProbabilities(1, 1);
+        
+        // 第二次调用,应该从缓存获取
+        $result2 = $this->logic->calculateAdjustedProbabilities(1, 1);
+        
+        // 结果应该相同
+        $this->assertEquals($result1, $result2);
+        
+        // 清除缓存
+        $this->logic->clearCache(1, 1);
+        
+        // 再次调用,应该重新从数据库查询
+        $result3 = $this->logic->calculateAdjustedProbabilities(1, 1);
+        $this->assertEquals($result1, $result3);
+    }
+
+    /**
+     * 测试默认产出处理
+     */
+    public function testDefaultOutputHandling()
+    {
+        // 创建测试种子
+        $seed = FarmSeed::factory()->create([
+            'id' => 1,
+            'item_id' => 100,
+            'min_output' => 1,
+            'max_output' => 3
+        ]);
+
+        // 不创建任何产出配置,测试默认处理
+        $result = $this->logic->calculateAdjustedProbabilities(1, 1);
+
+        // 应该返回种子的默认配置
+        $this->assertCount(1, $result);
+        $this->assertEquals(100, $result[0]['item_id']);
+        $this->assertEquals(1, $result[0]['min_amount']);
+        $this->assertEquals(3, $result[0]['max_amount']);
+        $this->assertEquals(100, $result[0]['adjusted_probability']);
+        $this->assertEquals('default', $result[0]['adjustment_type']);
+    }
+
+    /**
+     * 测试异常处理
+     */
+    public function testExceptionHandling()
+    {
+        // 测试不存在的种子ID
+        $this->expectException(\Exception::class);
+        $this->expectExceptionMessage('种子ID 999 不存在');
+        
+        $reflection = new \ReflectionClass($this->logic);
+        $method = $reflection->getMethod('getDefaultOutput');
+        $method->setAccessible(true);
+        
+        $method->invoke($this->logic, 999);
+    }
+}