feat: 实现 AI 钓鱼与百家乐游戏的参与逻辑,并支持后台面板配置开关
This commit is contained in:
@@ -128,6 +128,33 @@ class AiHeartbeatCommand extends Command
|
||||
);
|
||||
}
|
||||
|
||||
// 7. 钓鱼小游戏随机参与逻辑
|
||||
$fishingEnabled = Sysparam::getValue('chatbot_fishing_enabled', '0') === '1';
|
||||
$fishingChance = (int) Sysparam::getValue('chatbot_fishing_chance', '5'); // 默认 5% 概率
|
||||
if ($fishingEnabled && $fishingChance > 0 && rand(1, 100) <= $fishingChance && \App\Models\GameConfig::isEnabled('fishing')) {
|
||||
$cost = (int) (\App\Models\GameConfig::param('fishing', 'fishing_cost') ?? Sysparam::getValue('fishing_cost', '5'));
|
||||
if ($user->jjb >= $cost) {
|
||||
// 先扣除费用
|
||||
$this->currencyService->change(
|
||||
$user, 'gold', -$cost,
|
||||
CurrencySource::FISHING_COST,
|
||||
"AI小班长钓鱼抛竿消耗 {$cost} 金币",
|
||||
1,
|
||||
);
|
||||
|
||||
// 模拟玩家等待时间
|
||||
$waitMin = (int) (\App\Models\GameConfig::param('fishing', 'fishing_wait_min') ?? Sysparam::getValue('fishing_wait_min', '8'));
|
||||
$waitMax = (int) (\App\Models\GameConfig::param('fishing', 'fishing_wait_max') ?? Sysparam::getValue('fishing_wait_max', '15'));
|
||||
$waitTime = rand($waitMin, $waitMax);
|
||||
|
||||
// 延迟派发收竿事件(AI目前统一将事件播报到房间 1,或者拿 active room ids)
|
||||
$activeRoomIds = $this->chatState->getAllActiveRoomIds();
|
||||
$roomId = ! empty($activeRoomIds) ? $activeRoomIds[0] : 1;
|
||||
|
||||
\App\Jobs\AiFishingJob::dispatch($user, $roomId)->delay(now()->addSeconds($waitTime));
|
||||
}
|
||||
}
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@@ -48,8 +48,13 @@ class AiProviderController extends Controller
|
||||
$chatbotEnabled = Sysparam::getValue('chatbot_enabled', '0') === '1';
|
||||
$chatbotMaxGold = Sysparam::getValue('chatbot_max_gold', '5000');
|
||||
$chatbotMaxDailyRewards = Sysparam::getValue('chatbot_max_daily_rewards', '1');
|
||||
$chatbotFishingEnabled = Sysparam::getValue('chatbot_fishing_enabled', '0') === '1';
|
||||
$chatbotBaccaratEnabled = Sysparam::getValue('chatbot_baccarat_enabled', '0') === '1';
|
||||
|
||||
return view('admin.ai-providers.index', compact('providers', 'chatbotEnabled', 'chatbotMaxGold', 'chatbotMaxDailyRewards'));
|
||||
return view('admin.ai-providers.index', compact(
|
||||
'providers', 'chatbotEnabled', 'chatbotMaxGold',
|
||||
'chatbotMaxDailyRewards', 'chatbotFishingEnabled', 'chatbotBaccaratEnabled'
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -60,6 +65,8 @@ class AiProviderController extends Controller
|
||||
$data = $request->validate([
|
||||
'chatbot_max_gold' => 'required|integer|min:1',
|
||||
'chatbot_max_daily_rewards' => 'required|integer|min:1',
|
||||
'chatbot_fishing_enabled' => 'required|in:0,1',
|
||||
'chatbot_baccarat_enabled' => 'required|in:0,1',
|
||||
]);
|
||||
Sysparam::updateOrCreate(
|
||||
['alias' => 'chatbot_max_gold'],
|
||||
@@ -79,6 +86,24 @@ class AiProviderController extends Controller
|
||||
);
|
||||
Sysparam::clearCache('chatbot_max_daily_rewards');
|
||||
|
||||
Sysparam::updateOrCreate(
|
||||
['alias' => 'chatbot_fishing_enabled'],
|
||||
[
|
||||
'body' => $data['chatbot_fishing_enabled'],
|
||||
'guidetxt' => 'AI 参与钓鱼游戏开关',
|
||||
]
|
||||
);
|
||||
Sysparam::clearCache('chatbot_fishing_enabled');
|
||||
|
||||
Sysparam::updateOrCreate(
|
||||
['alias' => 'chatbot_baccarat_enabled'],
|
||||
[
|
||||
'body' => $data['chatbot_baccarat_enabled'],
|
||||
'guidetxt' => 'AI 参与百家乐游戏开关',
|
||||
]
|
||||
);
|
||||
Sysparam::clearCache('chatbot_baccarat_enabled');
|
||||
|
||||
return back()->with('success', '全局设置保存成功!');
|
||||
}
|
||||
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Enums\CurrencySource;
|
||||
use App\Events\MessageSent;
|
||||
use App\Models\FishingEvent;
|
||||
use App\Models\GameConfig;
|
||||
use App\Models\Sysparam;
|
||||
use App\Services\ChatStateService;
|
||||
use App\Services\FishingService;
|
||||
use App\Services\ShopService;
|
||||
use App\Services\UserCurrencyService;
|
||||
use App\Services\VipService;
|
||||
@@ -38,6 +37,7 @@ class FishingController extends Controller
|
||||
private readonly VipService $vipService,
|
||||
private readonly UserCurrencyService $currencyService,
|
||||
private readonly ShopService $shopService,
|
||||
private readonly FishingService $fishingService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -179,52 +179,8 @@ class FishingController extends Controller
|
||||
$cooldown = (int) (GameConfig::param('fishing', 'fishing_cooldown') ?? Sysparam::getValue('fishing_cooldown', '300'));
|
||||
Redis::setex("fishing:cd:{$user->id}", $cooldown, time());
|
||||
|
||||
// 3. 随机决定钓鱼结果
|
||||
$result = $this->randomFishResult();
|
||||
|
||||
// 4. 通过统一积分服务更新经验和金币
|
||||
$expMul = $this->vipService->getExpMultiplier($user);
|
||||
$jjbMul = $this->vipService->getJjbMultiplier($user);
|
||||
if ($result['exp'] !== 0) {
|
||||
$finalExp = $result['exp'] > 0 ? (int) round($result['exp'] * $expMul) : $result['exp'];
|
||||
$this->currencyService->change(
|
||||
$user, 'exp', $finalExp, CurrencySource::FISHING_GAIN,
|
||||
"钓鱼收竿:{$result['message']}", $id,
|
||||
);
|
||||
}
|
||||
if ($result['jjb'] !== 0) {
|
||||
$finalJjb = $result['jjb'] > 0 ? (int) round($result['jjb'] * $jjbMul) : $result['jjb'];
|
||||
$this->currencyService->change(
|
||||
$user, 'gold', $finalJjb, CurrencySource::FISHING_GAIN,
|
||||
"钓鱼收竿:{$result['message']}", $id,
|
||||
);
|
||||
}
|
||||
$user->refresh();
|
||||
|
||||
// 5. 广播钓鱼结果到聊天室
|
||||
// 若使用自动钓鱼卡,在消息末尾附加购买推广小标签(其他人点击可打开商店)
|
||||
$autoFishingMinutesLeft = $this->shopService->getActiveAutoFishingMinutesLeft($user);
|
||||
$promoTag = $autoFishingMinutesLeft > 0
|
||||
? ' <span onclick="window.openShopModal&&window.openShopModal()" '
|
||||
.'style="display:inline-block;margin-left:6px;padding:1px 7px;background:#e9e4f5;'
|
||||
.'color:#6d4fa8;border-radius:10px;font-size:10px;cursor:pointer;font-weight:bold;vertical-align:middle;'
|
||||
.'border:1px solid #d0c4ec;" title="点击购买自动钓鱼卡">🎣 自动钓鱼卡</span>'
|
||||
: '';
|
||||
|
||||
$sysMsg = [
|
||||
'id' => $this->chatState->nextMessageId($id),
|
||||
'room_id' => $id,
|
||||
'from_user' => '钓鱼播报',
|
||||
'to_user' => '大家',
|
||||
'content' => "{$result['emoji']} 【{$user->username}】{$result['message']}{$promoTag}",
|
||||
'is_secret' => false,
|
||||
'font_color' => $result['exp'] >= 0 ? '#16a34a' : '#dc2626',
|
||||
'action' => '',
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
|
||||
$this->chatState->pushMessage($id, $sysMsg);
|
||||
broadcast(new MessageSent($id, $sysMsg));
|
||||
// 3. 随机决定钓鱼结果并广播(直接调用服务)
|
||||
$result = $this->fishingService->processCatch($user, $id, false);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
@@ -234,33 +190,4 @@ class FishingController extends Controller
|
||||
'cooldown_seconds' => $cooldown, // 前端自动钓鱼卡循环等待用
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机钓鱼结果(从数据库 fishing_events 加权随机抽取)
|
||||
*
|
||||
* 若数据库中无激活事件,回退到兜底结果。
|
||||
*
|
||||
* @return array{emoji: string, message: string, exp: int, jjb: int}
|
||||
*/
|
||||
private function randomFishResult(): array
|
||||
{
|
||||
$event = FishingEvent::rollOne();
|
||||
|
||||
// 数据库无事件时的兜底
|
||||
if (! $event) {
|
||||
return [
|
||||
'emoji' => '🐟',
|
||||
'message' => '钓到一条小鱼,获得金币10',
|
||||
'exp' => 0,
|
||||
'jjb' => 10,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'emoji' => $event->emoji,
|
||||
'message' => $event->message,
|
||||
'exp' => $event->exp,
|
||||
'jjb' => $event->jjb,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
150
app/Jobs/AiBaccaratBetJob.php
Normal file
150
app/Jobs/AiBaccaratBetJob.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:AI小班长自动参与百家乐下注
|
||||
*
|
||||
* 在每局百家乐开启时延迟调度执行:
|
||||
* 1. 检查是否存在连输休息惩罚(1小时)
|
||||
* 2. 检查可用金币,确保留存底金
|
||||
* 3. 获取近期路单进行简单决策
|
||||
* 4. 提交下注
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Enums\CurrencySource;
|
||||
use App\Models\BaccaratBet;
|
||||
use App\Models\BaccaratRound;
|
||||
use App\Models\GameConfig;
|
||||
use App\Models\Sysparam;
|
||||
use App\Models\User;
|
||||
use App\Services\UserCurrencyService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
class AiBaccaratBetJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(
|
||||
public readonly BaccaratRound $round,
|
||||
) {}
|
||||
|
||||
public function handle(UserCurrencyService $currency): void
|
||||
{
|
||||
// 1. 检查总开关与游戏开关
|
||||
if (Sysparam::getValue('chatbot_enabled', '0') !== '1' || ! GameConfig::isEnabled('baccarat')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$round = $this->round->fresh();
|
||||
if (! $round || ! $round->isBettingOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = User::where('username', 'AI小班长')->first();
|
||||
if (! $user) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 检查连输惩罚超时
|
||||
if (Redis::exists('ai_baccarat_timeout')) {
|
||||
return; // 还在禁赛期
|
||||
}
|
||||
|
||||
// 3. 检查余额与限额
|
||||
$config = GameConfig::forGame('baccarat')?->params ?? [];
|
||||
$minBet = (int) ($config['min_bet'] ?? 100);
|
||||
$maxBet = (int) ($config['max_bet'] ?? 50000);
|
||||
|
||||
// 至少保留 2000 金币底仓
|
||||
$availableGold = ($user->jjb ?? 0) - 2000;
|
||||
if ($availableGold < $minBet) {
|
||||
return; // 资金不足以支撑最小下注
|
||||
}
|
||||
|
||||
// 下注金额:可用余额的 2% ~ 5%,并在 min_bet 和 max_bet 之间
|
||||
$percent = rand(2, 5) / 100.0;
|
||||
$amount = (int) round($availableGold * $percent);
|
||||
$amount = max($minBet, min($amount, $maxBet));
|
||||
|
||||
// 如果依然大于实际 jjb (保险兜底),则放弃
|
||||
if ($amount > $user->jjb) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 4. 决策逻辑:简单分析近期路单
|
||||
// 取最近 10 局
|
||||
$recentResults = BaccaratRound::query()
|
||||
->where('status', 'settled')
|
||||
->orderByDesc('id')
|
||||
->limit(10)
|
||||
->pluck('result')
|
||||
->toArray();
|
||||
|
||||
$bigCount = count(array_filter($recentResults, fn ($r) => $r === 'big'));
|
||||
$smallCount = count(array_filter($recentResults, fn ($r) => $r === 'small'));
|
||||
|
||||
// 基础策略:追逐热点 (跟大部队) 或 均值回归 (逆势)
|
||||
// 这里做一个简单的随机倾向:
|
||||
$strategy = rand(1, 100);
|
||||
if ($strategy <= 10) {
|
||||
$betType = 'triple'; // 10% 概率博豹子
|
||||
} elseif ($bigCount > $smallCount) {
|
||||
// 大偏热,70%概率顺势买大,30%逆势买小
|
||||
$betType = rand(1, 100) <= 70 ? 'big' : 'small';
|
||||
} elseif ($smallCount > $bigCount) {
|
||||
$betType = rand(1, 100) <= 70 ? 'small' : 'big';
|
||||
} else {
|
||||
$betType = rand(0, 1) ? 'big' : 'small';
|
||||
}
|
||||
|
||||
// 5. 执行下注 (同 BaccaratController::bet 逻辑)
|
||||
DB::transaction(function () use ($user, $round, $betType, $amount, $currency) {
|
||||
// 幂等:同一局只能下一注
|
||||
$existing = BaccaratBet::query()
|
||||
->where('round_id', $round->id)
|
||||
->where('user_id', $user->id)
|
||||
->lockForUpdate()
|
||||
->exists();
|
||||
|
||||
if ($existing) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 扣除金币
|
||||
$currency->change(
|
||||
$user,
|
||||
'gold',
|
||||
-$amount,
|
||||
CurrencySource::BACCARAT_BET,
|
||||
"AI小班长百家乐 #{$round->id} 押 ".match ($betType) {
|
||||
'big' => '大', 'small' => '小', default => '豹子'
|
||||
},
|
||||
);
|
||||
|
||||
// 写入下注记录
|
||||
BaccaratBet::create([
|
||||
'round_id' => $round->id,
|
||||
'user_id' => $user->id,
|
||||
'bet_type' => $betType,
|
||||
'amount' => $amount,
|
||||
'status' => 'pending',
|
||||
]);
|
||||
|
||||
// 更新局次汇总统计
|
||||
$field = 'total_bet_'.$betType;
|
||||
$round->increment($field, $amount);
|
||||
$round->increment('bet_count');
|
||||
});
|
||||
}
|
||||
}
|
||||
45
app/Jobs/AiFishingJob.php
Normal file
45
app/Jobs/AiFishingJob.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:AI小班长钓鱼动作异步任务
|
||||
*
|
||||
* 模拟 AI 抛竿等待一定时间后收竿的过程。
|
||||
* 收竿时自动调用 FishingService 结算结果并播报。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Services\FishingService;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class AiFishingJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
/**
|
||||
* @param User $aiUser AI小班长
|
||||
* @param int $roomId 此事件广播的房间ID
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly User $aiUser,
|
||||
public readonly int $roomId,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 任务执行
|
||||
*/
|
||||
public function handle(FishingService $fishingService): void
|
||||
{
|
||||
// 通过业务服务计算钓鱼结果并广播,标记 isAi 为 true
|
||||
$fishingService->processCatch($this->aiUser, $this->roomId, true);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,7 @@ use App\Services\UserCurrencyService;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Queue\Queueable;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
class CloseBaccaratRoundJob implements ShouldQueue
|
||||
{
|
||||
@@ -103,6 +104,10 @@ class CloseBaccaratRoundJob implements ShouldQueue
|
||||
$bet->update(['status' => 'lost', 'payout' => 0]);
|
||||
$losers[] = "{$username}-{$bet->amount}";
|
||||
|
||||
if ($username === 'AI小班长') {
|
||||
$this->handleAiLoseStreak();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -121,9 +126,17 @@ class CloseBaccaratRoundJob implements ShouldQueue
|
||||
);
|
||||
$totalPayout += $payout;
|
||||
$winners[] = "{$username}+".number_format($payout);
|
||||
|
||||
if ($username === 'AI小班长') {
|
||||
Redis::del('ai_baccarat_lose_streak'); // 赢了清空连输
|
||||
}
|
||||
} else {
|
||||
$bet->update(['status' => 'lost', 'payout' => 0]);
|
||||
$losers[] = "{$username}-".number_format($bet->amount);
|
||||
|
||||
if ($username === 'AI小班长') {
|
||||
$this->handleAiLoseStreak();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -149,6 +162,18 @@ class CloseBaccaratRoundJob implements ShouldQueue
|
||||
$this->pushResultMessage($round, $chatState, $winners, $losers);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理 AI 小班长连输逻辑
|
||||
*/
|
||||
private function handleAiLoseStreak(): void
|
||||
{
|
||||
$streak = Redis::incr('ai_baccarat_lose_streak');
|
||||
if ($streak >= 3) {
|
||||
Redis::setex('ai_baccarat_timeout', 3600, 'timeout'); // 连输三次,停赛1小时
|
||||
Redis::del('ai_baccarat_lose_streak');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向公屏发送开奖结果系统消息(含各用户输赢情况)。
|
||||
*
|
||||
|
||||
@@ -89,6 +89,13 @@ class OpenBaccaratRoundJob implements ShouldQueue
|
||||
broadcast(new MessageSent(1, $msg));
|
||||
SaveMessageJob::dispatch($msg);
|
||||
|
||||
// 如果允许 AI 参与,延迟一定时间派发 AI 下注任务
|
||||
$baccaratEnabled = \App\Models\Sysparam::getValue('chatbot_baccarat_enabled', '0') === '1';
|
||||
if (\App\Models\Sysparam::getValue('chatbot_enabled', '0') === '1' && $baccaratEnabled) {
|
||||
$aiDelay = rand(10, min(40, max(10, $betSeconds - 5))); // 随机在 10 ~ (倒数前5秒) 之间下注
|
||||
\App\Jobs\AiBaccaratBetJob::dispatch($round)->delay(now()->addSeconds($aiDelay));
|
||||
}
|
||||
|
||||
// 在下注截止时安排结算任务
|
||||
CloseBaccaratRoundJob::dispatch($round)->delay($closesAt);
|
||||
}
|
||||
|
||||
122
app/Services/FishingService.php
Normal file
122
app/Services/FishingService.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* 文件功能:钓鱼核心业务逻辑
|
||||
*
|
||||
* 提取自 FishingController,供玩家请求和 AI 挂机脚本复用。
|
||||
*
|
||||
* @author ChatRoom Laravel
|
||||
*
|
||||
* @version 1.0.0
|
||||
*/
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Enums\CurrencySource;
|
||||
use App\Events\MessageSent;
|
||||
use App\Models\FishingEvent;
|
||||
use App\Models\User;
|
||||
|
||||
class FishingService
|
||||
{
|
||||
public function __construct(
|
||||
private readonly ChatStateService $chatState,
|
||||
private readonly VipService $vipService,
|
||||
private readonly UserCurrencyService $currencyService,
|
||||
private readonly ShopService $shopService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* 处理收竿逻辑:计算结果、发放积分并全服广播。
|
||||
*
|
||||
* @param User $user 收竿的用户实体
|
||||
* @param int $roomId 所在房间 ID
|
||||
* @param bool $isAi 是否为 AI 调用(用于影响文案或标签)
|
||||
*/
|
||||
public function processCatch(User $user, int $roomId, bool $isAi = false): array
|
||||
{
|
||||
// 1. 随机决定钓鱼结果
|
||||
$result = $this->randomFishResult();
|
||||
|
||||
// 2. 更新经验和金币
|
||||
$expMul = $this->vipService->getExpMultiplier($user);
|
||||
$jjbMul = $this->vipService->getJjbMultiplier($user);
|
||||
|
||||
if ($result['exp'] !== 0) {
|
||||
$finalExp = $result['exp'] > 0 ? (int) round($result['exp'] * $expMul) : $result['exp'];
|
||||
$this->currencyService->change(
|
||||
$user, 'exp', $finalExp, CurrencySource::FISHING_GAIN,
|
||||
"钓鱼收竿:{$result['message']}", $roomId,
|
||||
);
|
||||
}
|
||||
|
||||
if ($result['jjb'] !== 0) {
|
||||
$finalJjb = $result['jjb'] > 0 ? (int) round($result['jjb'] * $jjbMul) : $result['jjb'];
|
||||
$this->currencyService->change(
|
||||
$user, 'gold', $finalJjb, CurrencySource::FISHING_GAIN,
|
||||
"钓鱼收竿:{$result['message']}", $roomId,
|
||||
);
|
||||
}
|
||||
|
||||
$user->refresh();
|
||||
|
||||
// 3. 广播钓鱼结果到聊天室
|
||||
$promoTag = '';
|
||||
if (! $isAi) {
|
||||
$autoFishingMinutesLeft = $this->shopService->getActiveAutoFishingMinutesLeft($user);
|
||||
$promoTag = $autoFishingMinutesLeft > 0
|
||||
? ' <span onclick="window.openShopModal&&window.openShopModal()" '
|
||||
.'style="display:inline-block;margin-left:6px;padding:1px 7px;background:#e9e4f5;'
|
||||
.'color:#6d4fa8;border-radius:10px;font-size:10px;cursor:pointer;font-weight:bold;vertical-align:middle;'
|
||||
.'border:1px solid #d0c4ec;" title="点击购买自动钓鱼卡">🎣 自动钓鱼卡</span>'
|
||||
: '';
|
||||
}
|
||||
|
||||
$sysMsg = [
|
||||
'id' => $this->chatState->nextMessageId($roomId),
|
||||
'room_id' => $roomId,
|
||||
'from_user' => '钓鱼播报',
|
||||
'to_user' => '大家',
|
||||
'content' => "{$result['emoji']} 【{$user->username}】{$result['message']}{$promoTag}",
|
||||
'is_secret' => false,
|
||||
'font_color' => $result['exp'] >= 0 ? '#16a34a' : '#dc2626',
|
||||
'action' => '',
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
|
||||
$this->chatState->pushMessage($roomId, $sysMsg);
|
||||
broadcast(new MessageSent($roomId, $sysMsg));
|
||||
// 发送完需持久化,不过 controller 里并未直接看到 SaveMessageJob, 但 AIheartbeat 里有。
|
||||
// 这里就先维持原样,只 broadcast 和 pushMessage
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机钓鱼结果(从数据库 fishing_events 加权随机抽取)
|
||||
*
|
||||
* 若数据库中无激活事件,回退到兜底结果。
|
||||
*
|
||||
* @return array{emoji: string, message: string, exp: int, jjb: int}
|
||||
*/
|
||||
public function randomFishResult(): array
|
||||
{
|
||||
$event = FishingEvent::rollOne();
|
||||
|
||||
if (! $event) {
|
||||
return [
|
||||
'emoji' => '🐟',
|
||||
'message' => '钓到一条小鱼,获得金币10',
|
||||
'exp' => 0,
|
||||
'jjb' => 10,
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
'emoji' => $event->emoji,
|
||||
'message' => $event->message,
|
||||
'exp' => $event->exp,
|
||||
'jjb' => $event->jjb,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -112,6 +112,22 @@
|
||||
<span class="absolute right-3 top-2.5 text-gray-400 text-sm">次</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">AI 参与钓鱼游戏</label>
|
||||
<select name="chatbot_fishing_enabled"
|
||||
class="w-40 border border-gray-300 rounded-md p-2 text-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
<option value="1" {{ $chatbotFishingEnabled ? 'selected' : '' }}>✅ 开启</option>
|
||||
<option value="0" {{ !$chatbotFishingEnabled ? 'selected' : '' }}>⛔ 关闭</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-sm font-bold text-gray-700 mb-1">AI 参与百家乐</label>
|
||||
<select name="chatbot_baccarat_enabled"
|
||||
class="w-40 border border-gray-300 rounded-md p-2 text-sm focus:ring-indigo-500 focus:border-indigo-500">
|
||||
<option value="1" {{ $chatbotBaccaratEnabled ? 'selected' : '' }}>✅ 开启</option>
|
||||
<option value="0" {{ !$chatbotBaccaratEnabled ? 'selected' : '' }}>⛔ 关闭</option>
|
||||
</select>
|
||||
</div>
|
||||
<button type="submit"
|
||||
class="px-6 py-2 bg-slate-800 text-white rounded-md font-bold hover:bg-slate-900 text-sm transition h-[38px]">
|
||||
保存运行参数
|
||||
|
||||
Reference in New Issue
Block a user