Files
a301_server/internal/bossraid/service.go
tolelom 61cf47070d
All checks were successful
Server CI/CD / deploy (push) Successful in 1m34s
feat: 보스 레이드 방 관리 모듈 추가
MMO 서버/데디케이트 서버 연동을 위한 내부 API 엔드포인트 구현:
- POST /api/internal/bossraid/entry — 파티 입장 요청 (방 생성)
- POST /api/internal/bossraid/start — 세션 시작 보고
- POST /api/internal/bossraid/complete — 클리어 보고 + TOL Chain 보상 지급
- POST /api/internal/bossraid/fail — 실패 보고
- GET /api/internal/bossraid/room — 방 조회

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 17:14:03 +09:00

179 lines
5.1 KiB
Go

package bossraid
import (
"encoding/json"
"fmt"
"log"
"time"
"github.com/tolelom/tolchain/core"
)
type Service struct {
repo *Repository
rewardGrant func(username string, tokenAmount uint64, assets []core.MintAssetPayload) error
}
func NewService(repo *Repository) *Service {
return &Service{repo: repo}
}
// SetRewardGranter sets the callback for granting rewards via blockchain.
func (s *Service) SetRewardGranter(fn func(username string, tokenAmount uint64, assets []core.MintAssetPayload) error) {
s.rewardGrant = fn
}
// RequestEntry creates a new boss room for a party.
// Returns the room with assigned session name.
func (s *Service) RequestEntry(usernames []string, bossID int) (*BossRoom, error) {
if len(usernames) == 0 {
return nil, fmt.Errorf("플레이어 목록이 비어있습니다")
}
if len(usernames) > 3 {
return nil, fmt.Errorf("최대 3명까지 입장할 수 있습니다")
}
// Check if any player is already in an active room
for _, username := range usernames {
count, err := s.repo.CountActiveByUsername(username)
if err != nil {
return nil, fmt.Errorf("플레이어 상태 확인 실패: %w", err)
}
if count > 0 {
return nil, fmt.Errorf("플레이어 %s가 이미 보스 레이드 중입니다", username)
}
}
playersJSON, err := json.Marshal(usernames)
if err != nil {
return nil, fmt.Errorf("플레이어 목록 직렬화 실패: %w", err)
}
sessionName := fmt.Sprintf("BossRaid_%d_%d", bossID, time.Now().UnixNano())
room := &BossRoom{
SessionName: sessionName,
BossID: bossID,
Status: StatusWaiting,
MaxPlayers: 3,
Players: string(playersJSON),
}
if err := s.repo.Create(room); err != nil {
return nil, fmt.Errorf("방 생성 실패: %w", err)
}
return room, nil
}
// StartRaid marks a room as in_progress.
func (s *Service) StartRaid(sessionName string) (*BossRoom, error) {
room, err := s.repo.FindBySessionName(sessionName)
if err != nil {
return nil, fmt.Errorf("방을 찾을 수 없습니다: %w", err)
}
if room.Status != StatusWaiting {
return nil, fmt.Errorf("시작할 수 없는 상태입니다: %s", room.Status)
}
now := time.Now()
room.Status = StatusInProgress
room.StartedAt = &now
if err := s.repo.Update(room); err != nil {
return nil, fmt.Errorf("상태 업데이트 실패: %w", err)
}
return room, nil
}
// PlayerReward describes the reward for a single player.
type PlayerReward struct {
Username string `json:"username"`
TokenAmount uint64 `json:"tokenAmount"`
Assets []core.MintAssetPayload `json:"assets"`
}
// RewardResult holds the result of granting a reward to one player.
type RewardResult struct {
Username string `json:"username"`
Success bool `json:"success"`
Error string `json:"error,omitempty"`
}
// CompleteRaid marks a room as completed and grants rewards via blockchain.
func (s *Service) CompleteRaid(sessionName string, rewards []PlayerReward) (*BossRoom, []RewardResult, error) {
room, err := s.repo.FindBySessionName(sessionName)
if err != nil {
return nil, nil, fmt.Errorf("방을 찾을 수 없습니다: %w", err)
}
if room.Status != StatusInProgress {
return nil, nil, fmt.Errorf("완료할 수 없는 상태입니다: %s", room.Status)
}
// Validate reward recipients are room players
var players []string
if err := json.Unmarshal([]byte(room.Players), &players); err != nil {
return nil, nil, fmt.Errorf("플레이어 목록 파싱 실패: %w", err)
}
playerSet := make(map[string]bool, len(players))
for _, p := range players {
playerSet[p] = true
}
for _, r := range rewards {
if !playerSet[r.Username] {
return nil, nil, fmt.Errorf("보상 대상 %s가 방의 플레이어가 아닙니다", r.Username)
}
}
// Mark room completed
now := time.Now()
room.Status = StatusCompleted
room.CompletedAt = &now
if err := s.repo.Update(room); err != nil {
return nil, nil, fmt.Errorf("상태 업데이트 실패: %w", err)
}
// Grant rewards
results := make([]RewardResult, 0, len(rewards))
if s.rewardGrant != nil {
for _, r := range rewards {
grantErr := s.rewardGrant(r.Username, r.TokenAmount, r.Assets)
result := RewardResult{Username: r.Username, Success: grantErr == nil}
if grantErr != nil {
result.Error = grantErr.Error()
log.Printf("보상 지급 실패: %s: %v", r.Username, grantErr)
}
results = append(results, result)
}
}
return room, results, nil
}
// FailRaid marks a room as failed.
func (s *Service) FailRaid(sessionName string) (*BossRoom, error) {
room, err := s.repo.FindBySessionName(sessionName)
if err != nil {
return nil, fmt.Errorf("방을 찾을 수 없습니다: %w", err)
}
if room.Status != StatusWaiting && room.Status != StatusInProgress {
return nil, fmt.Errorf("실패 처리할 수 없는 상태입니다: %s", room.Status)
}
now := time.Now()
room.Status = StatusFailed
room.CompletedAt = &now
if err := s.repo.Update(room); err != nil {
return nil, fmt.Errorf("상태 업데이트 실패: %w", err)
}
return room, nil
}
// GetRoom returns a room by session name.
func (s *Service) GetRoom(sessionName string) (*BossRoom, error) {
return s.repo.FindBySessionName(sessionName)
}