254 lines
9.1 KiB
PHP
254 lines
9.1 KiB
PHP
<?php
|
|
/**
|
|
* Pet battle gate for stealing crops.
|
|
*/
|
|
|
|
namespace common\services;
|
|
|
|
use common\models\PetBattleParticipant;
|
|
use common\models\User;
|
|
use common\modules\pet\PetUtil;
|
|
use common\modules\pet\vo\Pet;
|
|
use common\utils\RedisKeyUtil;
|
|
use RedisLock\RedisLock;
|
|
use Yii;
|
|
use yii\base\Object;
|
|
|
|
class PetBattleService extends Object
|
|
{
|
|
const POWER_DIRECT_WIN_NUMERATOR = 12;
|
|
const POWER_DIRECT_WIN_DENOMINATOR = 10;
|
|
const WEAK_DURATION = 600;
|
|
const WEAK_RECOVER_PERCENT = 20;
|
|
|
|
const RESULT_NO_BATTLE = 'no_battle';
|
|
const RESULT_DIRECT_WIN = 'direct_win';
|
|
const RESULT_DAMAGE = 'damage';
|
|
const RESULT_WEAK_OPENED = 'weak_opened';
|
|
const RESULT_WEAK_STEAL = 'weak_steal';
|
|
const RESULT_WEAK_BLOCKED = 'weak_blocked';
|
|
|
|
public static function recoverWeakIfExpiredByUserID($userID)
|
|
{
|
|
$user = User::findById($userID, false);
|
|
if ($user) {
|
|
self::recoverWeakIfExpired($user);
|
|
}
|
|
}
|
|
|
|
public static function recoverWeakIfExpired(User $user)
|
|
{
|
|
$weakUntil = (int)$user->pet_weak_until;
|
|
if ($weakUntil <= 0 || $weakUntil > time()) {
|
|
return;
|
|
}
|
|
|
|
$petID = (int)$user->pet_weak_pet_id;
|
|
if ($petID > 0) {
|
|
$pet = Pet::findByPetID($petID);
|
|
if ($pet && (int)$pet->user_id == (int)$user->id) {
|
|
$pet->hp = max(1, (int)ceil($pet->maxHp() * self::WEAK_RECOVER_PERCENT / 100));
|
|
$pet->save(true, ['hp', 'updated_at']);
|
|
}
|
|
}
|
|
|
|
PetBattleParticipant::updateAll(
|
|
['active' => 0],
|
|
['be_user_id' => (int)$user->id, 'active' => 1]
|
|
);
|
|
|
|
$user->pet_weak_until = 0;
|
|
$user->pet_weak_pet_id = 0;
|
|
$user->save(true, ['pet_weak_until', 'pet_weak_pet_id', 'updated_at']);
|
|
}
|
|
|
|
public static function resolveSteal($fromUserID, $beUserID)
|
|
{
|
|
$lock = new RedisLock(Yii::$app->redis, RedisKeyUtil::lockPetBattle($beUserID), RedisLock::FLAG_CATCH_EXCEPTIONS);
|
|
if (!$lock->acquire(10)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => ResultStatusServices::RS_SAME_TIME_OPERATION,
|
|
'data' => self::battleData(false, self::RESULT_NO_BATTLE, true),
|
|
];
|
|
}
|
|
|
|
try {
|
|
$beUser = User::findById($beUserID, false);
|
|
if (!$beUser) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => ResultStatusServices::RS_NOT_FIND_USER,
|
|
'data' => self::battleData(false, self::RESULT_NO_BATTLE, true),
|
|
];
|
|
}
|
|
|
|
self::recoverWeakIfExpired($beUser);
|
|
$now = time();
|
|
if ((int)$beUser->pet_weak_until > $now) {
|
|
return self::resolveWeakSteal($fromUserID, $beUser, $now);
|
|
}
|
|
|
|
$fromPet = self::findBattleReadyPet($fromUserID);
|
|
$bePet = self::findBattleReadyPet($beUserID);
|
|
if (!$fromPet || !$bePet) {
|
|
return [
|
|
'ok' => true,
|
|
'status' => ResultStatusServices::RS_OPERATION_SUCCESS,
|
|
'data' => self::battleData(false, self::RESULT_NO_BATTLE, true, 0, 0, $fromPet, $bePet),
|
|
];
|
|
}
|
|
|
|
self::normalizePetHp($fromPet);
|
|
self::normalizePetHp($bePet);
|
|
if (self::isDirectWin($fromPet, $bePet)) {
|
|
return [
|
|
'ok' => true,
|
|
'status' => ResultStatusServices::RS_OPERATION_SUCCESS,
|
|
'data' => self::battleData(true, self::RESULT_DIRECT_WIN, true, 0, 0, $fromPet, $bePet),
|
|
];
|
|
}
|
|
|
|
$damage = max(1, $fromPet->attackValue() - $bePet->defenseValue());
|
|
$bePet->hp = max(0, $bePet->currentHp() - $damage);
|
|
if (!$bePet->save(true, ['hp', 'updated_at'])) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => ResultStatusServices::RS_SAVE_DB_ERROR,
|
|
'data' => self::battleData(true, self::RESULT_DAMAGE, false, $damage, 0, $fromPet, $bePet),
|
|
];
|
|
}
|
|
|
|
self::recordDamage($fromUserID, $beUserID, (int)$bePet->id, $damage);
|
|
if ((int)$bePet->hp <= 0) {
|
|
$weakUntil = $now + self::WEAK_DURATION;
|
|
$beUser->pet_weak_until = $weakUntil;
|
|
$beUser->pet_weak_pet_id = (int)$bePet->id;
|
|
if (!$beUser->save(true, ['pet_weak_until', 'pet_weak_pet_id', 'updated_at'])) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => ResultStatusServices::RS_SAVE_DB_ERROR,
|
|
'data' => self::battleData(true, self::RESULT_WEAK_OPENED, true, $damage, $weakUntil, $fromPet, $bePet),
|
|
];
|
|
}
|
|
self::activateParticipants($beUserID, (int)$bePet->id, $weakUntil);
|
|
return [
|
|
'ok' => true,
|
|
'status' => ResultStatusServices::RS_OPERATION_SUCCESS,
|
|
'data' => self::battleData(true, self::RESULT_WEAK_OPENED, true, $damage, $weakUntil, $fromPet, $bePet),
|
|
];
|
|
}
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => ResultStatusServices::RS_OPERATION_SUCCESS,
|
|
'data' => self::battleData(true, self::RESULT_DAMAGE, false, $damage, 0, $fromPet, $bePet),
|
|
];
|
|
} finally {
|
|
$lock->release();
|
|
}
|
|
}
|
|
|
|
private static function resolveWeakSteal($fromUserID, User $beUser, $now)
|
|
{
|
|
$bePet = Pet::findByPetID((int)$beUser->pet_weak_pet_id);
|
|
$weakUntil = (int)$beUser->pet_weak_until;
|
|
if (!$bePet || !PetBattleParticipant::hasActive((int)$beUser->id, (int)$bePet->id, $fromUserID, $now)) {
|
|
return [
|
|
'ok' => false,
|
|
'status' => ResultStatusServices::RS_PET_BATTLE_NOT_PARTICIPATED,
|
|
'data' => self::battleData(true, self::RESULT_WEAK_BLOCKED, false, 0, $weakUntil, null, $bePet),
|
|
];
|
|
}
|
|
|
|
return [
|
|
'ok' => true,
|
|
'status' => ResultStatusServices::RS_OPERATION_SUCCESS,
|
|
'data' => self::battleData(true, self::RESULT_WEAK_STEAL, true, 0, $weakUntil, null, $bePet),
|
|
];
|
|
}
|
|
|
|
private static function findBattleReadyPet($userID)
|
|
{
|
|
$pet = Pet::findEnterWarPetByUserID($userID);
|
|
if (!$pet || (int)$pet->end_brooding_time > time()) {
|
|
return null;
|
|
}
|
|
PetUtil::updateFeedTime($pet);
|
|
$pet->save(true, ['feed', 'last_feed_update_time', 'updated_at']);
|
|
self::normalizePetHp($pet);
|
|
if ((int)$pet->feed <= 0 || $pet->currentHp() <= 0) {
|
|
return null;
|
|
}
|
|
return $pet;
|
|
}
|
|
|
|
private static function normalizePetHp(Pet $pet)
|
|
{
|
|
$currentHp = $pet->currentHp();
|
|
if ((int)$pet->hp !== $currentHp) {
|
|
$pet->hp = $currentHp;
|
|
$pet->save(true, ['hp', 'updated_at']);
|
|
}
|
|
}
|
|
|
|
private static function isDirectWin(Pet $fromPet, Pet $bePet)
|
|
{
|
|
return (int)$fromPet->fighting_capacity * self::POWER_DIRECT_WIN_DENOMINATOR >=
|
|
(int)$bePet->fighting_capacity * self::POWER_DIRECT_WIN_NUMERATOR;
|
|
}
|
|
|
|
private static function recordDamage($fromUserID, $beUserID, $bePetID, $damage)
|
|
{
|
|
$participant = PetBattleParticipant::findCurrent($beUserID, $bePetID, $fromUserID);
|
|
if (!$participant) {
|
|
$participant = new PetBattleParticipant();
|
|
$participant->from_user_id = $fromUserID;
|
|
$participant->be_user_id = $beUserID;
|
|
$participant->be_pet_id = $bePetID;
|
|
$participant->weak_until = 0;
|
|
$participant->active = 0;
|
|
$participant->damage_total = 0;
|
|
}
|
|
$participant->damage_total += max(0, (int)$damage);
|
|
$participant->save();
|
|
}
|
|
|
|
private static function activateParticipants($beUserID, $bePetID, $weakUntil)
|
|
{
|
|
PetBattleParticipant::updateAll(
|
|
['weak_until' => $weakUntil, 'active' => 1],
|
|
['be_user_id' => $beUserID, 'be_pet_id' => $bePetID, 'weak_until' => 0]
|
|
);
|
|
}
|
|
|
|
private static function battleData($triggered, $result, $allowSteal, $damage = 0, $weakUntil = 0, Pet $fromPet = null, Pet $bePet = null)
|
|
{
|
|
return [
|
|
'triggered' => $triggered ? 1 : 0,
|
|
'allow_steal' => $allowSteal ? 1 : 0,
|
|
'result' => $result,
|
|
'damage' => (int)$damage,
|
|
'weak_until' => (int)$weakUntil,
|
|
'attacker_pet' => self::petData($fromPet),
|
|
'defender_pet' => self::petData($bePet),
|
|
];
|
|
}
|
|
|
|
private static function petData(Pet $pet = null)
|
|
{
|
|
if (!$pet) {
|
|
return null;
|
|
}
|
|
return [
|
|
'id' => (int)$pet->id,
|
|
'user_id' => (int)$pet->user_id,
|
|
'hp' => $pet->currentHp(),
|
|
'max_hp' => $pet->maxHp(),
|
|
'attack' => $pet->attackValue(),
|
|
'defense' => $pet->defenseValue(),
|
|
'fighting_capacity' => (int)$pet->fighting_capacity,
|
|
];
|
|
}
|
|
}
|