251 lines
8.0 KiB
PHP
251 lines
8.0 KiB
PHP
<?php
|
|
/**
|
|
* Player stamina storage and recovery.
|
|
*/
|
|
|
|
namespace common\services;
|
|
|
|
use common\models\User;
|
|
use common\utils\RedisKeyUtil;
|
|
use RedisLock\RedisLock;
|
|
use Yii;
|
|
use yii\base\Object;
|
|
|
|
class StaminaService extends Object
|
|
{
|
|
const MAX_STAMINA = 20;
|
|
const STEAL_COST = 10;
|
|
const RECOVER_AMOUNT = 2;
|
|
const RECOVER_INTERVAL = 3600;
|
|
|
|
public static function getClientData($userID)
|
|
{
|
|
$userDbData = User::findById($userID, false);
|
|
if (!$userDbData) {
|
|
return self::defaultClientData();
|
|
}
|
|
|
|
self::recover($userDbData, time());
|
|
self::save($userDbData);
|
|
return self::formatClientData($userDbData);
|
|
}
|
|
|
|
public static function consumeSteal($userID)
|
|
{
|
|
return self::withStaminaLock($userID, function ($userID) {
|
|
return self::consumeStealUnlocked($userID);
|
|
});
|
|
}
|
|
|
|
public static function withStaminaLock($userID, callable $callback)
|
|
{
|
|
$lock = new RedisLock(Yii::$app->redis, RedisKeyUtil::lockUserStamina($userID), RedisLock::FLAG_CATCH_EXCEPTIONS);
|
|
if (!$lock->acquire(10)) {
|
|
$userDbData = User::findById($userID, false);
|
|
return [
|
|
'ok' => false,
|
|
'status' => ResultStatusServices::RS_SAME_TIME_OPERATION,
|
|
'data' => $userDbData ? self::formatClientData($userDbData) : self::defaultClientData(),
|
|
];
|
|
}
|
|
|
|
try {
|
|
return call_user_func($callback, $userID);
|
|
} finally {
|
|
$lock->release();
|
|
}
|
|
}
|
|
|
|
public static function consumeStealUnlocked($userID)
|
|
{
|
|
$check = self::checkStealUnlocked($userID);
|
|
if (empty($check['ok'])) {
|
|
return $check;
|
|
}
|
|
|
|
$userDbData = User::findById($userID, false);
|
|
$now = time();
|
|
$wasFull = (int)$userDbData->stamina >= (int)$userDbData->stamina_max;
|
|
$userDbData->stamina -= self::STEAL_COST;
|
|
if ($wasFull) {
|
|
$userDbData->stamina_recover_time = $now;
|
|
}
|
|
self::save($userDbData);
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => ResultStatusServices::RS_OPERATION_SUCCESS,
|
|
'data' => self::formatClientData($userDbData),
|
|
];
|
|
}
|
|
|
|
public static function checkStealUnlocked($userID)
|
|
{
|
|
$userDbData = User::findById($userID, false);
|
|
if (!$userDbData) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => ResultStatusServices::RS_NOT_FIND_USER,
|
|
'data' => self::defaultClientData(),
|
|
];
|
|
}
|
|
|
|
$now = time();
|
|
self::recover($userDbData, $now);
|
|
if ((int)$userDbData->stamina < self::STEAL_COST) {
|
|
self::save($userDbData);
|
|
return [
|
|
'ok' => false,
|
|
'status' => ResultStatusServices::RS_STAMINA_NOT_ENOUGH,
|
|
'data' => self::formatClientData($userDbData),
|
|
];
|
|
}
|
|
|
|
self::save($userDbData);
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => ResultStatusServices::RS_OPERATION_SUCCESS,
|
|
'data' => self::formatClientData($userDbData),
|
|
];
|
|
}
|
|
|
|
public static function increaseMax($userID, $amount, $fillAddedCapacity = true)
|
|
{
|
|
if ($amount <= 0) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => ResultStatusServices::RS_PARAM_ERROR,
|
|
'data' => self::getClientData($userID),
|
|
];
|
|
}
|
|
return self::setMax($userID, self::currentMax($userID) + (int)$amount, $fillAddedCapacity);
|
|
}
|
|
|
|
public static function setMax($userID, $maxStamina, $fillAddedCapacity = true)
|
|
{
|
|
$lock = new RedisLock(Yii::$app->redis, RedisKeyUtil::lockUserStamina($userID), RedisLock::FLAG_CATCH_EXCEPTIONS);
|
|
if (!$lock->acquire(10)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => ResultStatusServices::RS_SAME_TIME_OPERATION,
|
|
'data' => self::getClientData($userID),
|
|
];
|
|
}
|
|
|
|
try {
|
|
$userDbData = User::findById($userID, false);
|
|
if (!$userDbData) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => ResultStatusServices::RS_NOT_FIND_USER,
|
|
'data' => self::defaultClientData(),
|
|
];
|
|
}
|
|
|
|
self::recover($userDbData, time());
|
|
$oldMax = self::normalizeMax((int)$userDbData->stamina_max);
|
|
$newMax = self::normalizeMax((int)$maxStamina);
|
|
$userDbData->stamina_max = $newMax;
|
|
if ($fillAddedCapacity && $newMax > $oldMax) {
|
|
$userDbData->stamina = min($newMax, (int)$userDbData->stamina + ($newMax - $oldMax));
|
|
} else {
|
|
$userDbData->stamina = min((int)$userDbData->stamina, $newMax);
|
|
}
|
|
if ((int)$userDbData->stamina >= $newMax) {
|
|
$userDbData->stamina_recover_time = time();
|
|
}
|
|
self::save($userDbData);
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => ResultStatusServices::RS_OPERATION_SUCCESS,
|
|
'data' => self::formatClientData($userDbData),
|
|
];
|
|
} finally {
|
|
$lock->release();
|
|
}
|
|
}
|
|
|
|
private static function currentMax($userID)
|
|
{
|
|
$userDbData = User::findById($userID, false);
|
|
if (!$userDbData) {
|
|
return self::MAX_STAMINA;
|
|
}
|
|
return self::normalizeMax((int)$userDbData->stamina_max);
|
|
}
|
|
|
|
private static function recover(User $userDbData, $now)
|
|
{
|
|
$maxStamina = self::normalizeMax((int)$userDbData->stamina_max);
|
|
$userDbData->stamina_max = $maxStamina;
|
|
$userDbData->stamina = max(0, min($maxStamina, (int)$userDbData->stamina));
|
|
|
|
if ((int)$userDbData->stamina >= $maxStamina) {
|
|
$userDbData->stamina = $maxStamina;
|
|
$userDbData->stamina_recover_time = $now;
|
|
return;
|
|
}
|
|
|
|
if ((int)$userDbData->stamina_recover_time <= 0) {
|
|
$userDbData->stamina_recover_time = $now;
|
|
return;
|
|
}
|
|
|
|
$elapsed = $now - (int)$userDbData->stamina_recover_time;
|
|
if ($elapsed < self::RECOVER_INTERVAL) {
|
|
return;
|
|
}
|
|
|
|
$recoverTimes = (int)floor($elapsed / self::RECOVER_INTERVAL);
|
|
$userDbData->stamina = min($maxStamina, (int)$userDbData->stamina + $recoverTimes * self::RECOVER_AMOUNT);
|
|
if ((int)$userDbData->stamina >= $maxStamina) {
|
|
$userDbData->stamina_recover_time = $now;
|
|
} else {
|
|
$userDbData->stamina_recover_time += $recoverTimes * self::RECOVER_INTERVAL;
|
|
}
|
|
}
|
|
|
|
private static function save(User $userDbData)
|
|
{
|
|
return $userDbData->save(true, ['stamina', 'stamina_max', 'stamina_recover_time']);
|
|
}
|
|
|
|
private static function formatClientData(User $userDbData)
|
|
{
|
|
$maxStamina = self::normalizeMax((int)$userDbData->stamina_max);
|
|
$stamina = max(0, min($maxStamina, (int)$userDbData->stamina));
|
|
$nextRecoverTime = 0;
|
|
if ($stamina < $maxStamina) {
|
|
$nextRecoverTime = (int)$userDbData->stamina_recover_time + self::RECOVER_INTERVAL;
|
|
}
|
|
|
|
return [
|
|
'stamina' => $stamina,
|
|
'stamina_max' => $maxStamina,
|
|
'stamina_next_recover_time' => $nextRecoverTime,
|
|
'stamina_recover_amount' => self::RECOVER_AMOUNT,
|
|
'stamina_recover_interval' => self::RECOVER_INTERVAL,
|
|
'stamina_cost_steal' => self::STEAL_COST,
|
|
];
|
|
}
|
|
|
|
private static function defaultClientData()
|
|
{
|
|
return [
|
|
'stamina' => self::MAX_STAMINA,
|
|
'stamina_max' => self::MAX_STAMINA,
|
|
'stamina_next_recover_time' => 0,
|
|
'stamina_recover_amount' => self::RECOVER_AMOUNT,
|
|
'stamina_recover_interval' => self::RECOVER_INTERVAL,
|
|
'stamina_cost_steal' => self::STEAL_COST,
|
|
];
|
|
}
|
|
|
|
private static function normalizeMax($maxStamina)
|
|
{
|
|
return max(1, (int)$maxStamina);
|
|
}
|
|
}
|