Przeglądaj źródła

feat(pet): 增加宠物技能使用记录和优化技能冷却逻辑

- 在宠物信息页面添加最近技能使用记录表格
-优化技能冷却时间计算逻辑,处理未来时间异常
- 在使用技能前增加冷却时间检查和体力检查
- 修改技能效果执行流程,确保技能成功激活后再消耗体力- 增加技能重复激活检查,防止同一技能在冷却时间内被重复激活
notfff 7 miesięcy temu
rodzic
commit
72a5fdf4de

+ 148 - 1
app/Module/Game/AdminControllers/FarmUserSummaryController.php

@@ -16,6 +16,7 @@ use App\Module\GameItems\Enums\ITEM_TYPE;
 use App\Module\GameItems\Models\ItemUser;
 use App\Module\Pet\Models\PetUser;
 use App\Module\Pet\Models\PetActiveSkill;
+use App\Module\Pet\Models\PetSkillLog;
 use App\Module\Pet\Enums\PetStatus;
 use App\Module\User\Models\User;
 use Dcat\Admin\Grid;
@@ -711,7 +712,7 @@ class FarmUserSummaryController extends AdminController
         ];
 
         // 创建宠物表格
-        $headers = ['宠物名称', '品阶', '等级', '经验', '体力', '状态', '创建时间'];
+        $headers = ['宠物ID','宠物名称', '品阶', '等级', '经验', '体力', '状态', '创建时间'];
         $rows = [];
 
         $gradeNames = [
@@ -748,6 +749,7 @@ class FarmUserSummaryController extends AdminController
                 };
 
                 $rows[] = [
+                    $pet->id,
                     $pet->name,
                     $gradeName,
                     $pet->level,
@@ -856,6 +858,8 @@ class FarmUserSummaryController extends AdminController
 
         if ($activeSkills->isEmpty()) {
             $content = $statsHtml . '<div class="alert alert-info">该用户的宠物还没有使用过生活技能</div>';
+            // 即使没有激活技能,也显示历史使用记录
+            $content .= $this->getRecentSkillUsageSection($petIds);
             return new Card('宠物生活技能情况', $content);
         }
 
@@ -948,6 +952,9 @@ class FarmUserSummaryController extends AdminController
 
         $content = $statsHtml . $table->render();
 
+        // 添加最近10次技能使用记录
+        $content .= $this->getRecentSkillUsageSection($petIds);
+
         // 添加说明信息
         $content .= '<div class="alert alert-info mt-3">';
         $content .= '<strong>说明:</strong><br>';
@@ -960,6 +967,146 @@ class FarmUserSummaryController extends AdminController
         return new Card('宠物生活技能情况', $content);
     }
 
+    /**
+     * 获取最近技能使用记录区域
+     *
+     * @param array $petIds 宠物ID数组
+     * @return string HTML内容
+     */
+    protected function getRecentSkillUsageSection(array $petIds): string
+    {
+        if (empty($petIds)) {
+            return '';
+        }
+
+        // 获取最近10次技能使用记录
+        $recentLogs = PetSkillLog::with(['pet', 'skill'])
+            ->whereIn('pet_id', $petIds)
+            ->orderBy('used_at', 'desc')
+            ->limit(10)
+            ->get();
+
+        if ($recentLogs->isEmpty()) {
+            return '<div class="mt-4"><h5>最近技能使用记录</h5><div class="alert alert-info">暂无技能使用记录</div></div>';
+        }
+
+        // 创建技能使用记录表格
+        $headers = ['使用时间', '宠物名称', '技能名称', '体力消耗', '技能效果', '状态'];
+        $rows = [];
+
+        foreach ($recentLogs as $log) {
+            try {
+                $petName = $log->pet ? $log->pet->name : "宠物{$log->pet_id}";
+                $skillName = $log->skill ? $log->skill->skill_name : "技能{$log->skill_id}";
+
+                // 安全处理时间格式
+                $usedAt = '未知时间';
+                if ($log->used_at) {
+                    try {
+                        if ($log->used_at instanceof \Carbon\Carbon) {
+                            $usedAt = $log->used_at->format('Y-m-d H:i:s');
+                        } else {
+                            // 尝试解析字符串时间
+                            $usedAt = date('Y-m-d H:i:s', strtotime($log->used_at));
+                        }
+                    } catch (\Exception $e) {
+                        $usedAt = (string) $log->used_at;
+                    }
+                }
+
+                $staminaCost = $log->skill ? $log->skill->stamina_cost : '-';
+
+                // 解析技能效果结果
+                $effectResult = '';
+                $status = '<span class="badge badge-success">成功</span>';
+
+                if (!empty($log->effect_result)) {
+                    try {
+                        $effectData = json_decode($log->effect_result, true);
+                        if (is_array($effectData)) {
+                            $effectItems = [];
+
+                            // 根据技能类型显示不同的效果信息
+                            if (isset($effectData['skill_type'])) {
+                                $effectItems[] = "类型: {$effectData['skill_type']}";
+                            }
+                            if (isset($effectData['duration'])) {
+                                $hours = round($effectData['duration'] / 3600, 1);
+                                $effectItems[] = "持续: {$hours}小时";
+                            }
+                            if (isset($effectData['end_time'])) {
+                                $endTime = date('H:i', strtotime($effectData['end_time']));
+                                $effectItems[] = "结束: {$endTime}";
+                            }
+                            if (isset($effectData['message'])) {
+                                $effectItems[] = $effectData['message'];
+                            }
+
+                            // 检查是否有错误
+                            if (isset($effectData['success']) && $effectData['success'] === false) {
+                                $status = '<span class="badge badge-danger">失败</span>';
+                                if (isset($effectData['message'])) {
+                                    $effectItems = [$effectData['message']];
+                                }
+                            }
+
+                            $effectResult = implode('<br>', $effectItems);
+                        } else {
+                            $effectResult = $log->effect_result;
+                        }
+                    } catch (\Exception $e) {
+                        $effectResult = '数据解析错误';
+                        $status = '<span class="badge badge-warning">异常</span>';
+                    }
+                } else {
+                    $effectResult = '无效果记录';
+                }
+
+                $rows[] = [
+                    $usedAt,
+                    $petName,
+                    $skillName,
+                    $staminaCost,
+                    $effectResult ?: '-',
+                    $status
+                ];
+
+            } catch (\Throwable $e) {
+                // 如果出现异常,添加一个错误行
+                $usedAtDisplay = '未知';
+                if ($log->used_at) {
+                    try {
+                        if ($log->used_at instanceof \Carbon\Carbon) {
+                            $usedAtDisplay = $log->used_at->format('Y-m-d H:i:s');
+                        } else {
+                            $usedAtDisplay = (string) $log->used_at;
+                        }
+                    } catch (\Exception $e) {
+                        $usedAtDisplay = '时间格式错误';
+                    }
+                }
+
+                $rows[] = [
+                    $usedAtDisplay,
+                    $log->pet ? $log->pet->name : '未知',
+                    $log->skill ? $log->skill->skill_name : '未知',
+                    '-',
+                    '数据解析错误',
+                    '<span class="badge badge-danger">错误</span>'
+                ];
+            }
+        }
+
+        $table = new Table($headers, $rows);
+
+        $html = '<div class="mt-4">';
+        $html .= '<h5>最近技能使用记录 <small class="text-muted">(最近10次)</small></h5>';
+        $html .= $table->render();
+        $html .= '</div>';
+
+        return $html;
+    }
+
     /**
      * 神像buff信息卡片
      *

+ 16 - 1
app/Module/Pet/Factories/PetDtoFactory.php

@@ -91,7 +91,22 @@ class PetDtoFactory
             if (isset($skillLogs[$skill->id]) && $skillLogs[$skill->id]->count() > 0) {
                 $lastUsed = $skillLogs[$skill->id][0]->used_at;
                 $cooldownSeconds = $skill->cool_down;
-                $secondsSinceLastUse = now()->diffInSeconds($lastUsed);
+                $now = now();
+
+                // 确保时间计算的正确性
+                if ($lastUsed instanceof \Carbon\Carbon) {
+                    $secondsSinceLastUse = $now->diffInSeconds($lastUsed, false);
+                } else {
+                    // 如果不是Carbon对象,尝试解析
+                    $lastUsed = \Carbon\Carbon::parse($lastUsed);
+                    $secondsSinceLastUse = $now->diffInSeconds($lastUsed, false);
+                }
+
+                // 如果secondsSinceLastUse为负数,说明lastUsed时间在未来,这是异常情况
+                if ($secondsSinceLastUse < 0) {
+                    $secondsSinceLastUse = 0;
+                }
+
                 $cooldownRemaining = max(0, $cooldownSeconds - $secondsSinceLastUse);
                 $canUse = $cooldownRemaining === 0;
             }

+ 102 - 9
app/Module/Pet/Logic/PetLogic.php

@@ -520,10 +520,45 @@ class PetLogic
 
         if ($lastUsed) {
             $cooldownSeconds = $skill->cool_down;
-            $secondsSinceLastUse = now()->diffInSeconds($lastUsed->used_at);
+            $now = now();
+            $lastUsedTime = $lastUsed->used_at;
+
+            // 确保时间计算的正确性
+            if ($lastUsedTime instanceof \Carbon\Carbon) {
+                $secondsSinceLastUse = $now->diffInSeconds($lastUsedTime, false);
+            } else {
+                // 如果不是Carbon对象,尝试解析
+                $lastUsedTime = \Carbon\Carbon::parse($lastUsedTime);
+                $secondsSinceLastUse = $now->diffInSeconds($lastUsedTime, false);
+            }
+
+            // 如果secondsSinceLastUse为负数,说明lastUsed时间在未来,这是异常情况
+            if ($secondsSinceLastUse < 0) {
+                Log::warning('检测到异常的技能使用时间', [
+                    'pet_id' => $petId,
+                    'skill_id' => $skillId,
+                    'last_used_at' => $lastUsedTime->toDateTimeString(),
+                    'current_time' => $now->toDateTimeString(),
+                    'seconds_diff' => $secondsSinceLastUse
+                ]);
+                // 异常情况下,重置为0,允许技能使用
+                $secondsSinceLastUse = 0;
+            }
 
             if ($secondsSinceLastUse < $cooldownSeconds) {
                 $remainingCooldown = $cooldownSeconds - $secondsSinceLastUse;
+
+                // 记录详细的冷却信息用于调试
+                Log::info('技能冷却检查', [
+                    'pet_id' => $petId,
+                    'skill_id' => $skillId,
+                    'cooldown_seconds' => $cooldownSeconds,
+                    'seconds_since_last_use' => $secondsSinceLastUse,
+                    'remaining_cooldown' => $remainingCooldown,
+                    'last_used_at' => $lastUsedTime->toDateTimeString(),
+                    'current_time' => $now->toDateTimeString()
+                ]);
+
                 throw new Exception("技能冷却中,还需等待 {$remainingCooldown} 秒");
             }
         }
@@ -533,14 +568,28 @@ class PetLogic
             throw new Exception("体力不足,无法使用技能");
         }
 
-        // 消耗体力
+        // 先执行技能效果,确保技能能够成功激活
+        $effectResult = $this->executeSkillEffect($pet, $skill, $params);
+
+        // 检查技能效果是否成功
+        if (isset($effectResult['success']) && $effectResult['success'] === false) {
+            // 技能效果执行失败,记录失败日志但不消耗体力,不进入冷却
+            Log::warning('宠物技能使用失败', [
+                'pet_id' => $petId,
+                'skill_id' => $skillId,
+                'params' => $params,
+                'effect_result' => $effectResult,
+                'reason' => $effectResult['message'] ?? '技能使用失败'
+            ]);
+
+            throw new Exception($effectResult['message'] ?? '技能使用失败');
+        }
+
+        // 技能效果成功,消耗体力
         $pet->stamina -= $skill->stamina_cost;
         $pet->save();
 
-        // 执行技能效果
-        $effectResult = $this->executeSkillEffect($pet, $skill, $params);
-
-        // 记录技能使用日志
+        // 记录技能使用日志(只有成功时才记录)
         $skillLog = PetSkillLog::create([
             'pet_id' => $petId,
             'skill_id' => $skillId,
@@ -580,15 +629,17 @@ class PetLogic
     {
         // 根据技能名称执行不同的效果
         switch ($skill->skill_name) {
-            case '自动收':
+            case '自动收':
                 return $this->activateAutoHarvestSkill($pet, $skill, $params);
 
-            case '自动种':
+            case '自动种':
                 return $this->activateAutoPlantSkill($pet, $skill, $params);
 
             case '灾害防护':
                 return $this->activateDisasterProtectionSkill($pet, $skill, $params);
-
+            //自动除草
+            //自动浇水
+            //自动杀虫
             default:
                 return [
                     'success' => false,
@@ -607,6 +658,20 @@ class PetLogic
      */
     protected function activateAutoHarvestSkill(PetUser $pet, PetSkill $skill, array $params): array
     {
+        // 检查是否已有相同技能在激活中
+        $existingActiveSkill = \App\Module\Pet\Models\PetActiveSkill::where('pet_id', $pet->id)
+            ->where('skill_name', $skill->skill_name)
+            ->where('status', 'active')
+            ->where('end_time', '>', now())
+            ->first();
+
+        if ($existingActiveSkill) {
+            return [
+                'success' => false,
+                'message' => '该技能已经在激活中,无法重复激活'
+            ];
+        }
+
         // 技能持续时间(默认2小时)
         $duration = $params['duration'] ?? 7200; // 2小时
         $endTime = now()->addSeconds($duration);
@@ -653,6 +718,20 @@ class PetLogic
      */
     protected function activateAutoPlantSkill(PetUser $pet, PetSkill $skill, array $params): array
     {
+        // 检查是否已有相同技能在激活中
+        $existingActiveSkill = \App\Module\Pet\Models\PetActiveSkill::where('pet_id', $pet->id)
+            ->where('skill_name', $skill->skill_name)
+            ->where('status', 'active')
+            ->where('end_time', '>', now())
+            ->first();
+
+        if ($existingActiveSkill) {
+            return [
+                'success' => false,
+                'message' => '该技能已经在激活中,无法重复激活'
+            ];
+        }
+
         // 技能持续时间(默认4小时)
         $duration = $params['duration'] ?? 14400; // 4小时
         $endTime = now()->addSeconds($duration);
@@ -700,6 +779,20 @@ class PetLogic
      */
     protected function activateDisasterProtectionSkill(PetUser $pet, PetSkill $skill, array $params): array
     {
+        // 检查是否已有相同技能在激活中
+        $existingActiveSkill = \App\Module\Pet\Models\PetActiveSkill::where('pet_id', $pet->id)
+            ->where('skill_name', $skill->skill_name)
+            ->where('status', 'active')
+            ->where('end_time', '>', now())
+            ->first();
+
+        if ($existingActiveSkill) {
+            return [
+                'success' => false,
+                'message' => '该技能已经在激活中,无法重复激活'
+            ];
+        }
+
         // 技能持续时间(默认6小时)
         $duration = $params['duration'] ?? 21600; // 6小时
         $endTime = now()->addSeconds($duration);

+ 10 - 18
app/Module/Pet/Models/PetSkillLog.php

@@ -8,13 +8,13 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
 /**
  * 宠物技能使用日志模型
  *
- * field start 
- * @property  int  $id  
- * @property  int  $pet_id  
- * @property  int  $skill_id  
- * @property  string  $used_at  
+ * field start
+ * @property  int  $id
+ * @property  int  $pet_id
+ * @property  int  $skill_id
+ * @property  string  $used_at
  * @property  array  $effect_result  技能使用结果
- * @property  \Carbon\Carbon  $created_at  
+ * @property  \Carbon\Carbon  $created_at
  * field end
  */
 class PetSkillLog extends ModelCore
@@ -26,7 +26,7 @@ class PetSkillLog extends ModelCore
      */
     protected $table = 'pet_skill_logs';
 
-    // attrlist start 
+    // attrlist start
     protected $fillable = [
         'id',
         'pet_id',
@@ -36,16 +36,6 @@ class PetSkillLog extends ModelCore
     ];
     // attrlist end
 
-    /**
-     * 应该被转换为日期的属性
-     *
-     * @var array
-     */
-    protected $dates = [
-        'used_at',
-        'created_at',
-    ];
-
     /**
      * 应该被转换为原生类型的属性
      *
@@ -54,6 +44,8 @@ class PetSkillLog extends ModelCore
     protected $casts = [
         'pet_id' => 'integer',
         'skill_id' => 'integer',
+        'used_at' => 'datetime',
+        'created_at' => 'datetime',
         'effect_result' => 'json',
     ];
 
@@ -64,7 +56,7 @@ class PetSkillLog extends ModelCore
      */
     public function pet(): BelongsTo
     {
-        return $this->belongsTo(Pet::class, 'pet_id');
+        return $this->belongsTo(PetUser::class, 'pet_id');
     }
 
     /**

+ 17 - 2
app/Module/Pet/Services/PetService.php

@@ -301,7 +301,7 @@ class PetService
 
             // 获取技能信息
             /**
-             * @var PetSkill $skill  
+             * @var PetSkill $skill
              */
             $skill = PetSkill::find($skillId);
             if (!$skill) {
@@ -519,7 +519,22 @@ class PetService
             if (isset($skillLogs[$skill->id]) && $skillLogs[$skill->id]->count() > 0) {
                 $lastUsed = $skillLogs[$skill->id][0]->used_at;
                 $cooldownSeconds = $skill->cool_down;
-                $secondsSinceLastUse = now()->diffInSeconds($lastUsed);
+                $now = now();
+
+                // 确保时间计算的正确性
+                if ($lastUsed instanceof \Carbon\Carbon) {
+                    $secondsSinceLastUse = $now->diffInSeconds($lastUsed, false);
+                } else {
+                    // 如果不是Carbon对象,尝试解析
+                    $lastUsed = \Carbon\Carbon::parse($lastUsed);
+                    $secondsSinceLastUse = $now->diffInSeconds($lastUsed, false);
+                }
+
+                // 如果secondsSinceLastUse为负数,说明lastUsed时间在未来,这是异常情况
+                if ($secondsSinceLastUse < 0) {
+                    $secondsSinceLastUse = 0;
+                }
+
                 $cooldownRemaining = max(0, $cooldownSeconds - $secondsSinceLastUse);
             }
 

+ 17 - 1
app/Module/Pet/Validators/PetSkillUseValidator.php

@@ -69,7 +69,23 @@ class PetSkillUseValidator extends Validator
 
             if ($lastUsed) {
                 $cooldownSeconds = $skill->cool_down;
-                $secondsSinceLastUse = now()->diffInSeconds($lastUsed->used_at);
+                $now = now();
+                $lastUsedTime = $lastUsed->used_at;
+
+                // 确保时间计算的正确性
+                if ($lastUsedTime instanceof \Carbon\Carbon) {
+                    $secondsSinceLastUse = $now->diffInSeconds($lastUsedTime, false);
+                } else {
+                    // 如果不是Carbon对象,尝试解析
+                    $lastUsedTime = \Carbon\Carbon::parse($lastUsedTime);
+                    $secondsSinceLastUse = $now->diffInSeconds($lastUsedTime, false);
+                }
+
+                // 如果secondsSinceLastUse为负数,说明lastUsed时间在未来,这是异常情况
+                if ($secondsSinceLastUse < 0) {
+                    // 异常情况下,重置为0,允许技能使用
+                    $secondsSinceLastUse = 0;
+                }
 
                 if ($secondsSinceLastUse < $cooldownSeconds) {
                     $remainingCooldown = $cooldownSeconds - $secondsSinceLastUse;