种子与作物系统是农场模块的核心玩法组件,负责管理种子类型、作物生长周期、灾害系统和收获机制。玩家通过种植不同类型的种子,经历生长过程,最终收获作物获得奖励。
enum SEED_TYPE: int {
case NORMAL = 1; // 普通种子
case MYSTERY = 2; // 神秘种子
case GIANT = 3; // 巨化种子
}
| ID | 名称 | 种子期(秒) | 发芽期(秒) | 生长期(秒) | 最小产出 | 最大产出 |
|---|---|---|---|---|---|---|
| 1 | 萝卜种子 | 1800 | 3600 | 7200 | 1000 | 1500 |
| 2 | 辣椒种子 | 3600 | 7200 | 14400 | 1500 | 2000 |
| 3 | 苹果种子 | 7200 | 14400 | 28800 | 2000 | 2500 |
| 4 | 西瓜种子 | 10800 | 21600 | 43200 | 2500 | 3000 |
| 5 | 草莓种子 | 14400 | 28800 | 57600 | 3000 | 3500 |
| 6 | 南瓜种子 | 18000 | 36000 | 72000 | 3500 | 4000 |
| 7 | 核桃种子 | 21600 | 43200 | 86400 | 4000 | 4500 |
| 8 | 可可种子 | 25200 | 50400 | 100800 | 4500 | 5000 |
| 9 | 人参种子 | 28800 | 57600 | 115200 | 5000 | 5500 |
| 10 | 玫瑰种子 | 32400 | 64800 | 129600 | 5500 | 6000 |
神秘种子在种植时随机转化为一种普通种子,但有小概率转化为巨化种子。
| 概率 | 结果 |
|---|---|
| 90% | 随机普通种子 |
| 10% | 随机巨化种子 |
巨化种子是普通种子的强化版本,生长周期相同但产量更高。
| ID | 名称 | 对应普通种子 | 产量倍率 |
|---|---|---|---|
| 101 | 巨化草莓种子 | 草莓种子 | 1.5 |
| 102 | 巨化南瓜种子 | 南瓜种子 | 1.5 |
| 103 | 巨化核桃种子 | 核桃种子 | 1.5 |
| 104 | 巨化可可种子 | 可可种子 | 1.5 |
| 105 | 巨化人参种子 | 人参种子 | 1.5 |
| 106 | 巨化玫瑰种子 | 玫瑰种子 | 1.5 |
enum CROP_GROWTH_STAGE: int {
case SEED = 1; // 种子期
case SPROUT = 2; // 发芽期
case GROWING = 3; // 生长期
case MATURE = 4; // 成熟期
case WITHERED = 5; // 枯萎期
}
| 阶段 | 持续时间 | 可用化肥 | 灾害概率 | 状态转换 |
|---|---|---|---|---|
| 种子期 | 种子配置 | 是 | 低 | 自动进入发芽期 |
| 发芽期 | 种子配置 | 是 | 中 | 自动进入生长期 |
| 生长期 | 种子配置 | 是 | 高 | 自动进入成熟期 |
| 成熟期 | 24小时 | 否 | 无 | 超时进入枯萎期 |
| 枯萎期 | 永久 | 否 | 无 | 需手动铲除 |
每个生长阶段(种子期/发芽期/生长期)可使用一次化肥加速:
enum DISASTER_TYPE: int {
case DROUGHT = 1; // 干旱
case PEST = 2; // 虫害
case WEED = 3; // 杂草
}
| 灾害类型 | 减产比例 | 处理道具 | 处理效果 |
|---|---|---|---|
| 干旱 | 5% | 洒水壶 | 完全消除 |
| 虫害 | 5% | 杀虫剂 | 完全消除 |
| 杂草 | 5% | 除草剂 | 完全消除 |
神灵加持系统可以防止特定类型的灾害生成:
灾害生成逻辑会检查用户是否有对应的神灵加持,如果有则不会生成该类型的灾害:
// 检查用户是否有神灵加持
$godBuffs = $this->godBuffRepository->getUserActiveBuffs($userId);
// 根据神灵加持调整可能生成的灾害类型
if ($this->hasGodBuff($godBuffs, GOD_BUFF_TYPE::RAIN)) {
// 有雨露之神加持,不生成干旱灾害
$disasterTypes = [DISASTER_TYPE::PEST, DISASTER_TYPE::WEED];
}
else if ($this->hasGodBuff($godBuffs, GOD_BUFF_TYPE::WEED)) {
// 有屠草之神加持,不生成杂草灾害
$disasterTypes = [DISASTER_TYPE::DROUGHT, DISASTER_TYPE::PEST];
}
else if ($this->hasGodBuff($godBuffs, GOD_BUFF_TYPE::PEST)) {
// 有拭虫之神加持,不生成虫害灾害
$disasterTypes = [DISASTER_TYPE::DROUGHT, DISASTER_TYPE::WEED];
}
else {
// 没有神灵加持,所有灾害类型都可能生成
$disasterTypes = [DISASTER_TYPE::DROUGHT, DISASTER_TYPE::PEST, DISASTER_TYPE::WEED];
}
灾害直接影响最终产量计算:
最终产量 = 基础产量 × (1 + 土地加成) × (1 + 房屋加成) × (1 - 灾害减产总和)
作物必须处于成熟期才能收获。
基础产量 = 随机(种子最小产量, 种子最大产量)
土地加成 = 根据土地类型确定的百分比
房屋加成 = 根据房屋等级确定的百分比
灾害减产 = 每个未处理的灾害减产5%
最终产量 = 基础产量 × (1 + 土地加成) × (1 + 房屋加成) × (1 - 灾害减产总和)
丰收之神加持可以确保收获时获得最高产量:
// 获取种子配置
$seedConfig = $this->seedRepository->find($crop->seed_id);
// 检查用户是否有丰收之神加持
$hasHarvestBuff = $this->godBuffRepository->hasActiveBuff($crop->user_id, GOD_BUFF_TYPE::HARVEST);
// 如果有丰收之神加持,直接使用最大产量
if ($hasHarvestBuff) {
$baseOutput = $seedConfig->max_output;
} else {
// 随机生成基础产量
$baseOutput = rand($seedConfig->min_output, $seedConfig->max_output);
}
有丰收之神加持的用户在收获作物时,基础产量直接使用种子配置的最大产量值,而不是在最小产量和最大产量之间随机生成,这样可以确保获得最高的收益。
如果计算结果低于最低产量,则按最低产量发放;如果超过最高产量,则按最高产量发放。
种子信息采用混合定义方案,即在物品模块和农场模块中分别存储不同类型的种子属性:
物品模块负责:
农场模块负责:
通过在农场模块中添加关联字段,建立与物品模块的映射关系:
item_id:种子对应的物品IDoutput_item_id:收获产出的物品ID| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| name | varchar | 种子名称 |
| type | tinyint | 种子类型 |
| item_id | bigint | 对应的物品ID |
| output_item_id | bigint | 产出的物品ID |
| seed_time | int | 种子期时间(秒) |
| sprout_time | int | 发芽期时间(秒) |
| growth_time | int | 生长期时间(秒) |
| min_output | int | 最小产出 |
| max_output | int | 最大产出 |
| disaster_resistance | json | 灾害抵抗 |
| display_attributes | json | 显示属性 |
| created_at | timestamp | 创建时间 |
| updated_at | timestamp | 更新时间 |
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| land_id | bigint | 土地ID |
| seed_id | bigint | 种子ID |
| plant_time | timestamp | 种植时间 |
| growth_stage | tinyint | 生长阶段 |
| stage_end_time | timestamp | 阶段结束时间 |
| disasters | json | 灾害情况 |
| fertilized | boolean | 是否使用化肥 |
| created_at | timestamp | 创建时间 |
| updated_at | timestamp | 更新时间 |
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| user_id | bigint | 用户ID |
| land_id | bigint | 土地ID |
| crop_id | bigint | 作物ID |
| seed_id | bigint | 种子ID |
| output_amount | int | 产出数量 |
| harvest_time | timestamp | 收获时间 |
| created_at | timestamp | 创建时间 |
{
"disasters": [
{"type": 1, "occurred_at": "2023-05-01 12:00:00"},
{"type": 3, "occurred_at": "2023-05-01 14:30:00"}
]
}
id字段land_id、seed_id、user_idgrowth_stage(加速查询特定生长阶段的作物)stage_end_time(用于生长阶段更新检查)/**
* 在指定土地上种植作物
*
* @param int $landId 土地ID
* @param int $seedId 种子ID
* @return FarmCrop 创建的作物对象
* @throws LandNotEmptyException 土地非空异常
* @throws SeedNotFoundException 种子不存在异常
* @throws InsufficientSeedsException 种子不足异常
*/
public function plantCrop(int $landId, int $seedId): FarmCrop
/**
* 对作物使用化肥
*
* @param int $cropId 作物ID
* @param int $fertilizerId 化肥物品ID
* @return bool 使用是否成功
* @throws CropNotFertilizableException 作物不可施肥异常
* @throws AlreadyFertilizedException 已经施肥异常
* @throws InsufficientFertilizerException 化肥不足异常
*/
public function useFertilizer(int $cropId, int $fertilizerId): bool
/**
* 处理作物灾害
*
* @param int $cropId 作物ID
* @param int $disasterType 灾害类型
* @param int $toolId 处理工具物品ID
* @return bool 处理是否成功
* @throws DisasterNotFoundException 灾害不存在异常
* @throws InsufficientToolException 工具不足异常
*/
public function handleDisaster(int $cropId, int $disasterType, int $toolId): bool
/**
* 收获作物
*
* @param int $cropId 作物ID
* @return int 收获数量
* @throws CropNotMatureException 作物未成熟异常
*/
public function harvestCrop(int $cropId): int
/**
* 铲除作物
*
* @param int $cropId 作物ID
* @return bool 铲除是否成功
*/
public function removeCrop(int $cropId): bool
/**
* 更新作物生长状态
* 此方法应由定时任务调用
*
* @return int 处理的作物数量
*/
public function updateCropGrowth(): int
/**
* 为作物生成灾害
* 此方法应由定时任务调用
*
* @return int 生成的灾害数量
*/
public function generateDisasters(): int
/**
* 处理神秘种子的随机转化
*
* @param int $mysterySeedId 神秘种子ID
* @return int 转化后的种子ID
*/
public function processMysterySeeds(int $mysterySeedId): int
/**
* 计算作物的最终产量
*
* @param FarmCrop $crop 作物对象
* @param FarmLand $land 土地对象
* @param int $houseLevel 房屋等级
* @return int 最终产量
*/
public function calculateOutput(FarmCrop $crop, FarmLand $land, int $houseLevel): int
/**
* 计算灾害生成概率
*
* @param FarmCrop $crop 作物对象
* @param FarmLand $land 土地对象
* @return float 灾害生成概率
*/
public function calculateDisasterProbability(FarmCrop $crop, FarmLand $land): float
/**
* 计算各生长阶段的结束时间
*
* @param FarmSeed $seed 种子对象
* @param DateTime $plantTime 种植时间
* @return array 各阶段结束时间
*/
public function calculateGrowthStageEndTimes(FarmSeed $seed, DateTime $plantTime): array
前端应展示作物的以下信息:
前端应提供以下作物操作:
农场模块需要调用物品模块的以下接口:
// 检查用户是否拥有足够的物品
$hasItem = $this->itemService->checkUserItem($userId, $itemId, $amount);
// 消耗用户物品
$result = $this->itemService->consumeUserItem($userId, $itemId, $amount, $reason);
// 添加物品到用户背包
$result = $this->itemService->addUserItem($userId, $itemId, $amount, $reason);
// 获取物品信息
$itemInfo = $this->itemService->getItemInfo($itemId);
/**
* 在指定土地上种植作物
*/
public function plantCrop(int $landId, int $seedId): array
{
return DB::transaction(function () use ($landId, $seedId) {
// 获取土地和种子信息
$land = $this->landRepository->find($landId);
$seedConfig = $this->seedRepository->find($seedId);
$userId = $land->user_id;
// 处理神秘种子
if ($seedConfig->type == SEED_TYPE::MYSTERY) {
$seedId = $this->seedService->processMysterySeeds($seedId);
$seedConfig = $this->seedRepository->find($seedId);
}
// 检查用户是否拥有该种子物品
$hasItem = $this->itemService->checkUserItem($userId, $seedConfig->item_id, 1);
if (!$hasItem) {
throw new SeedException("用户没有足够的种子");
}
// 消耗种子物品
$this->itemService->consumeUserItem($userId, $seedConfig->item_id, 1, "种植作物");
// 创建作物记录
// ...
return ['crop_id' => $crop->id, 'land_id' => $landId];
});
}
/**
* 收获作物
*/
public function harvestCrop(int $cropId): array
{
return DB::transaction(function () use ($cropId) {
// 获取作物信息
$crop = $this->cropRepository->find($cropId);
$seedConfig = $this->seedRepository->find($crop->seed_id);
$land = $this->landRepository->find($crop->land_id);
$userId = $crop->user_id;
// 验证作物状态
// ...
// 计算产出数量
$outputAmount = $this->calculateOutput($crop, $land, $houseLevel);
// 添加产出物品到用户背包
$this->itemService->addUserItem(
$userId,
$seedConfig->output_item_id,
$outputAmount,
"收获作物"
);
// 记录收获日志
// ...
return [
'output_item_id' => $seedConfig->output_item_id,
'amount' => $outputAmount
];
});
}
/**
* 对作物使用化肥
*/
public function useFertilizer(int $cropId, int $fertilizerId): bool
{
return DB::transaction(function () use ($cropId, $fertilizerId) {
// 获取作物信息
$crop = $this->cropRepository->find($cropId);
$userId = $crop->user_id;
// 验证作物状态
// ...
// 检查用户是否拥有该化肥物品
$hasItem = $this->itemService->checkUserItem($userId, $fertilizerId, 1);
if (!$hasItem) {
throw new CropException("用户没有足够的化肥");
}
// 消耗化肥物品
$this->itemService->consumeUserItem($userId, $fertilizerId, 1, "使用化肥");
// 更新作物生长时间
// ...
return true;
});
}
种子信息采用混合定义方案,在物品模块和农场模块中分别存储不同类型的属性:
物品模块中存储:
农场模块中存储:
数据访问示例:
// 获取完整的种子信息
public function getCompleteSeedInfo(int $seedId): array
{
// 获取农场模块中的种子信息
$seedInfo = $this->seedRepository->find($seedId);
// 获取物品模块中的种子物品信息
$itemInfo = $this->itemService->getItemInfo($seedInfo->item_id);
// 获取物品模块中的产出物品信息
$outputItemInfo = $this->itemService->getItemInfo($seedInfo->output_item_id);
// 合并信息返回
return [
'seed_id' => $seedInfo->id,
'name' => $seedInfo->name,
'growth_times' => [
'seed' => $seedInfo->seed_time,
'sprout' => $seedInfo->sprout_time,
'growth' => $seedInfo->growth_time
],
'output' => [
'min' => $seedInfo->min_output,
'max' => $seedInfo->max_output
],
'item' => [
'id' => $itemInfo['id'],
'icon' => $itemInfo['icon'],
'description' => $itemInfo['description']
],
'output_item' => [
'id' => $outputItemInfo['id'],
'name' => $outputItemInfo['name']
]
];
}
未来可以添加季节系统,不同季节适合种植不同作物:
系统设计预留了添加新种子类型的可能性,只需:
未来可以添加特殊事件系统,如: