hy-farm/muchang/common/services/PetBattleService.php
2026-05-30 20:23:54 +08:00

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,
];
}
}