hy-farm/muchang/common/services/LandService.php
2026-05-29 19:54:56 +08:00

579 lines
22 KiB
PHP

<?php
/**
* Created by PhpStorm.
* User: zn
* Date: 2017/6/2
* Time: 10:35
*/
namespace common\services;
use common\models\Interaction;
use common\models\Land;
use common\modules\achievement\AchievementUtil;
use common\modules\vip\vo\CardUtil;
use common\utils\EnumUtil;
use common\utils\RedisKeyUtil;
use RedisLock\RedisLock;
use Yii;
use yii\base\Object;
/**
* 农田帮助类
* Class LandService
* @package common\services
*/
class LandService extends Object
{
const STEAL_MIN_PERCENT = 5;
const STEAL_MAX_PERCENT = 15;
const STEAL_CROP_MAX_PERCENT = 50;
const STEAL_DAILY_TARGET_LIMIT = 3;
/**
* 根据要升级的等级查找可以升级的农田
* @param $landConfigList
* @param $level
* @return Land
*/
public static function findLandUpgradeItemByLevel($landConfigList, $level)
{
if ($level == 0) return null;
//要查找的等级
$findLevel = $level - 1;
//查找农田
$findLandDbData = null;
foreach ($landConfigList as $item) {
if ($item['level'] == $findLevel) return $item;
}
//未找到,递归查找上一等级
return self::findLandUpgradeItemByLevel($landConfigList, $findLevel);
}
/**
* 播种
* @param $farmID
* @param $position
* @param $itemID
* @return int
*/
public static function sowSeeds($farmID, $position, $itemID, $isMysterySeed = 0)
{
try {
$landDbData = Land::findByFarmIDAndPosition($farmID, $position);
if (!$landDbData) return ResultStatusServices::RS_NOT_FIND_IN_DB;
//判断农田是否已经有农作物
if ($landDbData->crop_id != 0) return ResultStatusServices::RS_LAND_HAS_CROP;
$itemConfig = GameConfigServices::getParamByPath('item_list.' . $itemID);
if (empty($itemConfig)) return ResultStatusServices::RS_NOT_FIND_IN_CONFIG;
$cropConfig = GameConfigServices::getParamByPath('crop_list.' . $itemConfig['effect_id']);
if (empty($cropConfig)) return ResultStatusServices::RS_NOT_FIND_IN_CONFIG;
//修改农田数据
$landDbData->crop_id = $cropConfig['id'];
$landDbData->crop_start_time = time();
$landDbData->crop_gather_time = time() + intval($cropConfig['seedling_time']) + intval($cropConfig['grow_time']) + intval($cropConfig['ripe_time']);
$landDbData->is_mystery_seed = $isMysterySeed;
if (!$landDbData->save()) return ResultStatusServices::RS_SAVE_DB_ERROR;
} finally {
}
return ResultStatusServices::RS_OPERATION_SUCCESS;
}
/**
* 收获作物
* @param $farmID
* @param $position
* @param $output
* @param $itemID
* @return int
*/
public static function gatherCrop($farmID, $position, &$output, &$itemID)
{
$output = 0;
$itemID = null;
//加锁
$lock = new RedisLock(Yii::$app->redis, RedisKeyUtil::lockLand($farmID, $position), RedisLock::FLAG_CATCH_EXCEPTIONS);
if (!$lock->acquire(10)) return ResultStatusServices::RS_SAME_TIME_OPERATION;
try {
$landDbData = Land::findByFarmIDAndPosition($farmID, $position);
if (!$landDbData) return ResultStatusServices::RS_NOT_FIND_IN_DB;
//检查农作物
if ($landDbData->crop_id == 0) return ResultStatusServices::RS_LAND_NOT_CROP;
//判断时间,农作物是否可收获
if ($landDbData->is_ripe != 1) return ResultStatusServices::RS_CROP_NOT_RIPE;
//检查是否已收获过
if ($landDbData->has_gather != 0) return ResultStatusServices::RS_CROP_HAD_GATHER;
//获取配置
$cropConfig = GameConfigServices::getParamByPath('crop_list.' . $landDbData->crop_id);
if (empty($cropConfig)) return ResultStatusServices::RS_NOT_FIND_IN_CONFIG;
//设置收获的物品
$itemID = $cropConfig['item_id'];
//收获产量
$output = $landDbData->crop_num;
//设置已收获标志
$landDbData->has_gather = 1;
//保存数据
if (!$landDbData->save()) return ResultStatusServices::RS_SAVE_DB_ERROR;
} finally {
$lock->release();
}
return ResultStatusServices::RS_OPERATION_SUCCESS;
}
/**
* 更新所有农作物状态
* @param $farmID
* @param $farmLevel
* @return int
*/
public static function updateAllCropStatus($userID, $farmID, $farmLevel)
{
$landDbList = Land::findAll(['farm_id' => $farmID]);
foreach ($landDbList as $land) {
//检查是否需要更新
if ($land->crop_id != 0) {
//更新单个农作物状态
$rs = self::updateCropStatus($userID, $farmID, $land->position, $farmLevel);
if ($rs != ResultStatusServices::RS_OPERATION_SUCCESS)
return $rs;
}
}
return ResultStatusServices::RS_OPERATION_SUCCESS;
}
/**
* 更新单个农作物状态
* @param $farmID
* @param $position
* @param $farmLevel
* @return int
*/
public static function updateCropStatus($userID, $farmID, $position, $farmLevel)
{
//加锁
$lock = new RedisLock(Yii::$app->redis, RedisKeyUtil::lockLand($farmID, $position), RedisLock::FLAG_CATCH_EXCEPTIONS);
if (!$lock->acquire(10)) return ResultStatusServices::RS_SAME_TIME_OPERATION;
try {
$landDbData = Land::findRedis($farmID, $position);
if (!$landDbData) return ResultStatusServices::RS_NOT_FIND_IN_DB;
//检查农作物
if ($landDbData->crop_id == 0) return ResultStatusServices::RS_LAND_NOT_CROP;
//农作物是收获状态,直接返回
if ($landDbData->is_ripe == 1) return ResultStatusServices::RS_OPERATION_SUCCESS;
$cropConfig = GameConfigServices::getParamByPath('crop_list.' . $landDbData->crop_id);
if (NewbieGuideService::isNewbieCrop($landDbData->crop_id)) {
if (time() >= self::getLandCropGatherTime($landDbData)) {
$landDbData->crop_num = NewbieGuideService::NEWBIE_REWARD_GOLD;
$landDbData->crop_min_num = NewbieGuideService::NEWBIE_REWARD_GOLD;
$landDbData->has_dry = 0;
$landDbData->has_bug = 0;
$landDbData->has_grass = 0;
$landDbData->is_ripe = 1;
if (!$landDbData->save()) return ResultStatusServices::RS_SAVE_DB_ERROR;
}
return ResultStatusServices::RS_OPERATION_SUCCESS;
}
$change = false;
//判断是否达到收获时间
if (time() < self::getLandCropGatherTime($landDbData)) {
//未成熟,随机病虫害
$growPhaseTime1 = $landDbData->crop_start_time + $cropConfig['seedling_time'];
if (time() > $growPhaseTime1) {
//种子期不随机虫
//计算需要随机的次数
if ($landDbData->disease_rand_time == 0)
$lastRandTime = time() - $landDbData->crop_start_time - $cropConfig['seedling_time'];
else
$lastRandTime = time() - $landDbData->disease_rand_time;
$time = GameConfigServices::getCommonNumByID(GameConfigServices::COMMON_DISEASE_RAND_TIME);
if ($time == 0) $time = 1;
$randCount = intval($lastRandTime / $time);
if ($randCount <= 0) $randCount = 0;
if ($randCount > 10) $randCount = 10;
//随机病害
for ($i = 0; $i < $randCount; $i++) {
if ($landDbData->has_dry == 0 && RandServices::randLandDisease(GameConfigServices::COMMON_DRY_RAND_NUM))
$landDbData->has_dry = 1;
if ($landDbData->has_bug == 0 && RandServices::randLandDisease(GameConfigServices::COMMON_BUG_RAND_NUM))
$landDbData->has_bug = 1;
if ($landDbData->has_grass == 0 && RandServices::randLandDisease(GameConfigServices::COMMON_GRASS_RAND_NUM))
$landDbData->has_grass = 1;
}
//更新随机时间
if ($randCount > 0)
$landDbData->disease_rand_time = time();
$change = true;
}
} else {
//农作物成熟,计算产量
//农场增产量
$farmConfig = GameConfigServices::getParamByPath('farm_level.' . $farmLevel);
$farmOutput = intval($farmConfig['output']);
//农作物产量
$cropOutput = intval($cropConfig['output']);
//农田等级产量
$landConfig = GameConfigServices::getLandUpgradeByLevelAndPosition($landDbData->level, $landDbData->position);
$landOutput = intval($landConfig['output']);
//vip
// $vip = CardUtil::findByUserID($userID);
// $vipConfig = GameConfigServices::getParamByPath('vip_list.' . $vip->level);
// $vipCount = 0;
// if (!empty($vipConfig)) $vipCount = (int)$vipConfig['fruit'];
//总产量
// $totalOutput = $farmOutput + $cropOutput + $landOutput ;
$addPercent = floatval($farmOutput) / 100 + floatval($landOutput) / 100;
$totalOutput = $cropOutput * (1 + $addPercent);
//计算保底产量
$minOutputPercent = GameConfigServices::getCommonNumByID(GameConfigServices::COMMON_MIN_OUTPUT_ID) / 100;
$landDbData->crop_min_num = $totalOutput * $minOutputPercent;
if ($landDbData->crop_min_num < 0) $landDbData->crop_min_num = 0;
//干旱减产百分比
$dryReductionPercent = 0;
if ($landDbData->has_dry)
$dryReductionPercent = floatval(GameConfigServices::getCommonNumByID(GameConfigServices::COMMON_DRY_ID)) / 100;
//虫害减产百分比
$bugReductionPercent = 0;
if ($landDbData->has_bug)
$bugReductionPercent = floatval(GameConfigServices::getCommonNumByID(GameConfigServices::COMMON_BUG_ID)) / 100;
//杂草减产百分比
$grassReductionPercent = 0;
if ($landDbData->has_grass)
$grassReductionPercent = floatval(GameConfigServices::getCommonNumByID(GameConfigServices::COMMON_GRASS_ID)) / 100;
//总减产百分比
$totalReductionPercent = $dryReductionPercent + $bugReductionPercent + $grassReductionPercent;
//最终产量
$landDbData->crop_num = $totalOutput * (1 - $totalReductionPercent);
if ($landDbData->crop_num < $landDbData->crop_min_num) $landDbData->crop_num = $landDbData->crop_min_num;
$landDbData->crop_num = intval($landDbData->crop_num);
//更改成熟状态
$landDbData->is_ripe = 1;
$change = true;
}
//保存
if ($change && !$landDbData->save()) ResultStatusServices::RS_SAVE_DB_ERROR;
} finally {
$lock->release();
}
return ResultStatusServices::RS_OPERATION_SUCCESS;
}
/**
* 清理农田
* @param $farmID
* @param $position
* @return int
*/
public static function clearLand($farmID, $position)
{
try {
$landDbData = Land::findByFarmIDAndPosition($farmID, $position);
if (!$landDbData) return ResultStatusServices::RS_NOT_FIND_IN_DB;
//检查是否需要清理
if ($landDbData->crop_id == 0) return ResultStatusServices::RS_LAND_NOT_NEED_CLEAR;
$landDbData->crop_id = 0;
$landDbData->crop_start_time = 0;
$landDbData->is_ripe = 0;
$landDbData->crop_num = 0;
$landDbData->crop_min_num = 0;
$landDbData->has_dry = 0;
$landDbData->has_bug = 0;
$landDbData->has_grass = 0;
$landDbData->has_gather = 0;
$landDbData->disease_rand_time = 0;
//保存
if (!$landDbData->save()) ResultStatusServices::RS_SAVE_DB_ERROR;
} finally {
}
return ResultStatusServices::RS_OPERATION_SUCCESS;
}
/**
* 初始化新数据
* @param Land $landDbData
* @param $farmID
* @param $position
* @return bool
*/
public static function initNewFarmDataAndSave(Land $landDbData, $farmID, $position)
{
$landDbData->farm_id = $farmID;
$landDbData->position = $position;
return $landDbData->save();
}
/**获取农田可交互类型
* @param $fromUserID
* @param $beUserID
* @param $landDbData
* @return array
*/
public static function getLandInteractionType($fromUserID, $beUserID, Land $landDbData)
{
if (!$landDbData || $landDbData->crop_id == 0) return [];
$typeList = [];
//是否偷取过
if (self::canStealLand($fromUserID, $beUserID, $landDbData)) {
array_push($typeList, EnumUtil::INTERACTION_TYPE_STEAL);
}
//是否可以放虫
if (!$landDbData->is_ripe && !$landDbData->has_bug &&
!Interaction::findRedis($fromUserID, $beUserID, $landDbData->crop_start_time, $landDbData->position, EnumUtil::INTERACTION_TYPE_BUG)
) {
array_push($typeList, EnumUtil::INTERACTION_TYPE_BUG);
}
//是否可以放草
if (!$landDbData->is_ripe && !$landDbData->has_grass &&
!Interaction::findRedis($fromUserID, $beUserID, $landDbData->crop_start_time, $landDbData->position, EnumUtil::INTERACTION_TYPE_GRASS)
) {
array_push($typeList, EnumUtil::INTERACTION_TYPE_GRASS);
}
//是否可以浇水
if (!$landDbData->is_ripe && $landDbData->has_dry &&
!Interaction::findRedis($fromUserID, $beUserID, $landDbData->crop_start_time, $landDbData->position, EnumUtil::INTERACTION_TYPE_WATERING)
) {
array_push($typeList, EnumUtil::INTERACTION_TYPE_WATERING);
}
//是否可以除虫
if (!$landDbData->is_ripe && $landDbData->has_bug &&
!Interaction::findRedis($fromUserID, $beUserID, $landDbData->crop_start_time, $landDbData->position, EnumUtil::INTERACTION_TYPE_DISINSECTION)
) {
array_push($typeList, EnumUtil::INTERACTION_TYPE_DISINSECTION);
}
//是否可以除草
if (!$landDbData->is_ripe && $landDbData->has_grass &&
!Interaction::findRedis($fromUserID, $beUserID, $landDbData->crop_start_time, $landDbData->position, EnumUtil::INTERACTION_TYPE_WEEDING)
) {
array_push($typeList, EnumUtil::INTERACTION_TYPE_WEEDING);
}
return $typeList;
}
/**
* 偷菜
* @param $farmID
* @param $position
* @param $stealCount
* @return int
*/
public static function steal($farmID, $position, &$stealCount, $fromUserID = null, $beUserID = null)
{
//加锁
$lock = new RedisLock(Yii::$app->redis, RedisKeyUtil::lockLand($farmID, $position), RedisLock::FLAG_CATCH_EXCEPTIONS);
if (!$lock->acquire(10)) return ResultStatusServices::RS_SAME_TIME_OPERATION;
try {
$landDbData = Land::findByFarmIDAndPosition($farmID, $position);
if (!$landDbData) return ResultStatusServices::RS_NOT_FIND_IN_DB;
//检查农作物
if ($landDbData->crop_id == 0) return ResultStatusServices::RS_LAND_NOT_CROP;
//农作物是否成熟状态
if ($landDbData->is_ripe != 1) return ResultStatusServices::RS_CROP_NOT_RIPE;
//农作物是否被收获过
if ($landDbData->has_gather == 1) return ResultStatusServices::RS_CROP_HAD_GATHER;
if ($fromUserID && $beUserID) {
if (!self::canStealLand($fromUserID, $beUserID, $landDbData))
return ResultStatusServices::RS_INTERACTION_CAN_NOT_STEAL;
}
$stolenTotal = $beUserID ? self::getCropStolenCount($beUserID, $landDbData) : 0;
$totalOutput = self::getStealTotalOutput($landDbData, $stolenTotal);
$maxStolenTotal = intval(ceil($totalOutput * self::STEAL_CROP_MAX_PERCENT / 100));
$protectedOutput = $totalOutput - $maxStolenTotal;
$availableStealCount = $maxStolenTotal - $stolenTotal;
if ($availableStealCount <= 0 || $landDbData->crop_num <= $protectedOutput)
return ResultStatusServices::RS_CROP_STEAL_NOT_NUM;
$stealPercent = mt_rand(self::STEAL_MIN_PERCENT, self::STEAL_MAX_PERCENT);
$stealCount = intval(ceil($totalOutput * $stealPercent / 100));
if ($stealCount < 1) $stealCount = 1;
if ($stealCount > $availableStealCount) $stealCount = $availableStealCount;
if ($landDbData->crop_num - $stealCount < $protectedOutput)
$stealCount = $landDbData->crop_num - $protectedOutput;
//没有可偷取的数量
if ($stealCount <= 0)
return ResultStatusServices::RS_CROP_STEAL_NOT_NUM;
//偷取
$landDbData->crop_num -= $stealCount;
//保存
if (!$landDbData->save()) ResultStatusServices::RS_SAVE_DB_ERROR;
} finally {
$lock->release();
}
return ResultStatusServices::RS_OPERATION_SUCCESS;
}
public static function canStealLand($fromUserID, $beUserID, Land $landDbData)
{
if (!$landDbData || $landDbData->crop_id == 0) return false;
if (!$landDbData->is_ripe || $landDbData->has_gather) return false;
if (!$fromUserID || !$beUserID || $fromUserID == $beUserID) return false;
if (Interaction::findRedis($fromUserID, $beUserID, $landDbData->crop_start_time, $landDbData->position, EnumUtil::INTERACTION_TYPE_STEAL))
return false;
if (!self::hasDailyStealTargetQuota($fromUserID, $beUserID)) return false;
$stolenTotal = self::getCropStolenCount($beUserID, $landDbData);
$totalOutput = self::getStealTotalOutput($landDbData, $stolenTotal);
$maxStolenTotal = intval(ceil($totalOutput * self::STEAL_CROP_MAX_PERCENT / 100));
if ($maxStolenTotal - $stolenTotal <= 0) return false;
return $landDbData->crop_num > ($totalOutput - $maxStolenTotal);
}
public static function hasDailyStealTargetQuota($fromUserID, $beUserID)
{
$dayStart = strtotime('today');
$dayEnd = $dayStart + 86400;
$targetList = Interaction::find()
->select('be_user_id')
->distinct()
->where([
'from_user_id' => $fromUserID,
'type' => EnumUtil::INTERACTION_TYPE_STEAL,
])
->andWhere(['>=', 'created_at', $dayStart])
->andWhere(['<', 'created_at', $dayEnd])
->column();
$targetList = array_map('intval', $targetList);
if (in_array(intval($beUserID), $targetList)) return true;
return count($targetList) < self::STEAL_DAILY_TARGET_LIMIT;
}
public static function getCropStolenCount($beUserID, Land $landDbData)
{
if (!$beUserID || !$landDbData) return 0;
$sum = Interaction::find()
->where([
'be_user_id' => $beUserID,
'land_create_time' => $landDbData->crop_start_time,
'position' => $landDbData->position,
'type' => EnumUtil::INTERACTION_TYPE_STEAL,
])
->sum('num');
return intval($sum);
}
private static function getStealTotalOutput(Land $landDbData, $stolenTotal)
{
$totalOutput = intval($landDbData->crop_num) + intval($stolenTotal);
if ($totalOutput <= 0 && $landDbData->crop_min_num > 0)
$totalOutput = intval($landDbData->crop_min_num * 2);
return max(1, $totalOutput);
}
/**获取农田收获时间
* @param Land $landDbData
* @return int
*/
public static function getLandCropGatherTime(Land $landDbData)
{
$cropConfig = GameConfigServices::getParamByPath('crop_list.' . $landDbData->crop_id);
if (empty($cropConfig)) return $landDbData->crop_start_time;
//配置表生长周期是单个周期的时间差,为了方便计算,把生长周期变成从播种开始的时间
return $landDbData->crop_start_time + $cropConfig['seedling_time'] + $cropConfig['grow_time'] + $cropConfig['ripe_time'];
}
/**
* 获取农作物当前阶段
* @param Land $landDbData
* @return int
*/
public static function getLandCropPhase(Land $landDbData)
{
$cropConfig = GameConfigServices::getParamByPath('crop_list.' . $landDbData->crop_id);
//配置表生长周期是单个周期的时间差,为了方便计算,把生长周期变成从播种开始的时间
$growPhaseTime1 = $landDbData->crop_start_time + $cropConfig['seedling_time'];
$growPhaseTime2 = $growPhaseTime1 + $cropConfig['grow_time'];
$growPhaseTime3 = $growPhaseTime2 + $cropConfig['ripe_time'];
//每个阶段还需要的时间
if (time() < $growPhaseTime1) return EnumUtil::CROP_PHASE_SEED;
elseif (time() < $growPhaseTime2) return EnumUtil::CROP_PHASE_SEEDLING;
elseif (time() < $growPhaseTime3) return EnumUtil::CROP_PHASE_GROW;
else return EnumUtil::CROP_PHASE_RIPE;
}
}