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 }