151 lines
4.8 KiB
Go
Executable File
151 lines
4.8 KiB
Go
Executable File
package main
|
||
|
||
import (
|
||
"context"
|
||
"database/sql"
|
||
"fmt"
|
||
"math/rand"
|
||
"os"
|
||
"strings"
|
||
"time"
|
||
|
||
"wuziqi-server/config"
|
||
"wuziqi-server/handlers"
|
||
|
||
"github.com/heroiclabs/nakama-common/runtime"
|
||
)
|
||
|
||
func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
|
||
logger.Info("Nakama Go Module Loaded - Refactored Version")
|
||
|
||
// 随机数种子
|
||
rand.Seed(time.Now().UnixNano())
|
||
|
||
// 如果存在,从环境变量初始化后端 URL
|
||
envURL := os.Getenv("MINESWEEPER_BACKEND_URL")
|
||
if envURL != "" {
|
||
newURL := strings.TrimSuffix(envURL, "/")
|
||
config.SetBackendBaseURL(newURL)
|
||
logger.Info("Setting BackendBaseURL from environment: %s", newURL)
|
||
}
|
||
|
||
// 注册比赛处理器
|
||
if err := initializer.RegisterMatch("animal_minesweeper", func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule) (runtime.Match, error) {
|
||
logger.Debug("Creating new MatchHandler instance")
|
||
return &handlers.MatchHandler{}, nil
|
||
}); err != nil {
|
||
logger.Error("Unable to register match: %v", err)
|
||
return err
|
||
}
|
||
|
||
// 注册匹配器匹配钩子
|
||
if err := initializer.RegisterMatchmakerMatched(func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, entries []runtime.MatchmakerEntry) (string, error) {
|
||
logger.Info("Matchmaker matched! Creating authoritative match for %d players", len(entries))
|
||
|
||
// 1. 遍历所有玩家,提取有效的 game_type
|
||
// 我们不能只看第一个玩家,因为有时某些玩家的属性可能为空或丢失
|
||
var gameType string
|
||
|
||
// 调试日志:打印所有玩家的 properties
|
||
for i, entry := range entries {
|
||
props := entry.GetProperties()
|
||
var pType string
|
||
if props != nil {
|
||
if gt, ok := props["game_type"]; ok {
|
||
if gtStr, ok := gt.(string); ok {
|
||
pType = gtStr
|
||
}
|
||
}
|
||
}
|
||
logger.Debug("Matchmaker Entry [%d] UserID: %s, Properties: %v, GameType: %s", i, entry.GetPresence().GetUserId(), props, pType)
|
||
|
||
// 如果还没找到主 gameType 且当前玩家有,则采用
|
||
if gameType == "" && pType != "" {
|
||
gameType = pType
|
||
}
|
||
}
|
||
|
||
// 如果遍历完还是空的,说明没人带 game_type,直接拒绝匹配!
|
||
// 严禁使用默认值,防止未携带属性的玩家被错误分配
|
||
// 如果遍历完还是空的,说明没人带 game_type,直接拒绝匹配!
|
||
if gameType == "" {
|
||
errMsg := "CRITICAL: No game_type found in any matchmaker entry. Creating REJECT match."
|
||
logger.Error(errMsg)
|
||
// Don't return error, create a reject match
|
||
gameType = "REJECT_mismatch_no_type"
|
||
}
|
||
|
||
// 2. 安全校验:再次遍历,确保所有玩家都明确携带了 game_type 且一致
|
||
for i, entry := range entries {
|
||
var entryGameType string
|
||
props := entry.GetProperties()
|
||
if props != nil {
|
||
if gt, ok := props["game_type"]; ok {
|
||
if gtStr, ok := gt.(string); ok {
|
||
entryGameType = gtStr
|
||
}
|
||
}
|
||
}
|
||
|
||
// 强制要求每位玩家必须携带 game_type
|
||
if entryGameType == "" {
|
||
errMsg := fmt.Sprintf("CRITICAL: Player %s matched without game_type property! Creating REJECT match.", entry.GetPresence().GetUserId())
|
||
logger.Error(errMsg)
|
||
gameType = "REJECT_mismatch_missing_type"
|
||
break
|
||
}
|
||
|
||
// 一致性检查
|
||
// Note: If gameType is already REJECT, we don't need to check.
|
||
if !strings.HasPrefix(gameType, "REJECT_") && entryGameType != gameType {
|
||
errMsg := fmt.Sprintf("CRITICAL: Matchmaker matched players with DIFFERENT game_types! Target=%s, Entry[%d]=%s. Creating REJECT match.", gameType, i, entryGameType)
|
||
logger.Error(errMsg)
|
||
gameType = "REJECT_mismatch_mixed_types"
|
||
break
|
||
}
|
||
}
|
||
|
||
// 将 game_type 作为参数传递给 MatchCreate
|
||
params := map[string]interface{}{
|
||
"game_type": gameType,
|
||
}
|
||
|
||
matchId, err := nk.MatchCreate(ctx, "animal_minesweeper", params)
|
||
if err != nil {
|
||
logger.Error("Failed to create match: %v", err)
|
||
return "", err
|
||
}
|
||
|
||
logger.Info("Created authoritative match: %s (GameType: %s)", matchId, gameType)
|
||
return matchId, nil
|
||
}); err != nil {
|
||
logger.Error("Unable to register matchmaker matched hook: %v", err)
|
||
return err
|
||
}
|
||
|
||
logger.Info("Match registration completed successfully")
|
||
|
||
// 注册 RPC
|
||
if err := initializer.RegisterRpc("list_matches", handlers.RpcListMatches); err != nil {
|
||
logger.Error("Unable to register rpc: %v", err)
|
||
return err
|
||
}
|
||
|
||
if err := initializer.RegisterRpc("find_my_match", handlers.RpcFindMyMatch); err != nil {
|
||
logger.Error("Unable to register rpc: %v", err)
|
||
return err
|
||
}
|
||
|
||
if err := initializer.RegisterRpc("get_online_count", handlers.RpcGetOnlineCount); err != nil {
|
||
logger.Error("Unable to register rpc: %v", err)
|
||
return err
|
||
}
|
||
|
||
if err := initializer.RegisterRpc("get_leaderboard", handlers.RpcGetLeaderboard); err != nil {
|
||
logger.Error("Unable to register rpc: %v", err)
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|