json(['active' => false]); } $roomId = $this->roomScopeService->resolveRequestRoomId($request); if (! $this->roomScopeService->isRoomAllowedForGame('mystery_box', $roomId)) { return response()->json(['active' => false]); } $box = MysteryBox::currentOpenBox($roomId); if (! $box) { return response()->json(['active' => false]); } // 计算剩余时间 $secondsLeft = $box->expires_at ? max(0, now()->diffInSeconds($box->expires_at, false)) : null; return response()->json([ 'active' => true, 'box_id' => $box->id, 'box_type' => $box->box_type, 'type_name' => $box->typeName(), 'type_emoji' => $box->typeEmoji(), 'passcode' => $box->passcode, 'seconds_left' => $secondsLeft, ]); } /** * 用户用暗号领取箱子。 */ public function claim(Request $request): JsonResponse { if (! GameConfig::isEnabled('mystery_box')) { return response()->json(['ok' => false, 'message' => '神秘箱子功能未开放。']); } $passcode = strtoupper(trim((string) $request->input('passcode', ''))); if ($passcode === '') { return response()->json(['ok' => false, 'message' => '请输入暗号。']); } $user = $request->user(); $roomId = $this->roomScopeService->resolveRequestRoomId($request, $user); if (! $this->roomScopeService->isRoomAllowedForGame('mystery_box', $roomId)) { return response()->json(['ok' => false, 'message' => '当前房间未开启神秘箱子。'], 403); } return DB::transaction(function () use ($user, $passcode, $roomId): JsonResponse { // 查找匹配暗号的可领取箱子(加锁防并发) $box = MysteryBox::query() ->where('room_id', $roomId) ->where('passcode', $passcode) ->where('status', 'open') ->where(fn ($q) => $q->whereNull('expires_at')->orWhere('expires_at', '>', now())) ->lockForUpdate() ->first(); if (! $box) { return response()->json(['ok' => false, 'message' => '暗号不正确,或箱子已被领走/已过期。']); } // ① 随机奖励金额 $reward = $box->rollReward(); // ② 货币变更 $source = $reward >= 0 ? CurrencySource::MYSTERY_BOX : CurrencySource::MYSTERY_BOX_TRAP; $remark = $reward >= 0 ? "神秘箱子【{$box->typeName()}】奖励" : '神秘箱子【黑化箱】陷阱扣除'; $this->currency->change($user, 'gold', $reward, $source, $remark, $box->room_id); // ③ 写领取记录 + 更新箱子状态 MysteryBoxClaim::create([ 'mystery_box_id' => $box->id, 'user_id' => $user->id, 'reward_amount' => $reward, ]); $box->update(['status' => 'claimed']); // ④ 公屏广播结果 $user->refresh(); $this->broadcastResult($box, $user->username, $reward); return response()->json([ 'ok' => true, 'reward' => $reward, 'balance' => $user->jjb ?? 0, 'message' => $reward >= 0 ? "🎉 恭喜!开箱获得 +{$reward} 金币!" : '☠️ 中了黑化陷阱!扣除 '.abs($reward).' 金币!', ]); }); } /** * 公屏广播开箱结果。 * * @param MysteryBox $box 箱子实例 * @param string $username 领取者用户名 * @param int $reward 奖励金额(正/负) */ private function broadcastResult(MysteryBox $box, string $username, int $reward): void { $emoji = $box->typeEmoji(); $typeName = $box->typeName(); if ($reward >= 0) { $content = "{$emoji} 【{$username}】抢到{$typeName},获得 💰".number_format($reward).' 金币!'; $color = $box->box_type === 'rare' ? '#c4b5fd' : '#34d399'; } else { $content = "☠️ 【{$username}】踩中黑化陷阱,扣除 💰".number_format(abs($reward)).' 金币!'; $color = '#f87171'; } $msg = [ 'id' => $this->chatState->nextMessageId((int) $box->room_id), 'room_id' => (int) $box->room_id, 'from_user' => '系统传音', 'to_user' => '大家', 'content' => $content, 'is_secret' => false, 'font_color' => $color, 'action' => '大声宣告', 'sent_at' => now()->toDateTimeString(), ]; $this->chatState->pushMessage((int) $box->room_id, $msg); broadcast(new MessageSent((int) $box->room_id, $msg)); SaveMessageJob::dispatch($msg); } }