package modules import ( "context" "database/sql" "encoding/json" "github.com/heroiclabs/nakama-common/runtime" ) type MatchmakingTicket struct { Game string `json:"game"` // e.g. "tictactoe", "battleship" Mode string `json:"mode"` // e.g. "classic", "ranked", "blitz" } // -------------------------------------------------- // GENERIC MATCHMAKER — Supports ALL Games & Modes // -------------------------------------------------- func MatchmakerMatched( ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, entries []runtime.MatchmakerEntry, ) (string, error) { if len(entries) == 0 { return "", nil } // Extract the first player's desired properties props0 := entries[0].GetProperties() game, okGame := props0["game"].(string) mode, okMode := props0["mode"].(string) if !okGame || !okMode { logger.Error("MatchmakerMatched: Missing 'game' or 'mode' properties.") return "", runtime.NewError("missing matchmaking properties", 3) } // Ensure ALL players match game + mode for _, e := range entries { p := e.GetProperties() g, okG := p["game"].(string) m, okM := p["mode"].(string) if !okG || !okM || g != game || m != mode { logger.Warn("MatchmakerMatched: Player properties do not match — retrying matchmaking.") return "", nil } } // Create the correct authoritative match handler. // This depends on how "game" was registered in main.go. // Example: initializer.RegisterMatch("tictactoe", NewGenericMatch(TicTacToeRules)) matchParams := map[string]interface{}{ "game": game, "mode": mode, } matchID, err := nk.MatchCreate(ctx, "generic", matchParams) if err != nil { logger.Error("MatchmakerMatched: MatchCreate failed: %v", err) return "", runtime.NewError("failed to create match", 13) } logger.Info("✔ Match created game=%s mode=%s id=%s", game, mode, matchID) return matchID, nil } // -------------------------------------------------- // RPC: Leave matchmaking (generic cancel API) // -------------------------------------------------- func RpcLeaveMatchmaking( ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string, ) (string, error) { var input struct { Ticket string `json:"ticket"` } if err := json.Unmarshal([]byte(payload), &input); err != nil { return "", runtime.NewError("invalid JSON", 3) } if input.Ticket == "" { return "", runtime.NewError("missing ticket", 3) } // Client removes ticket locally — server doesn't need to do anything logger.Info("✔ Player left matchmaking: ticket=%s", input.Ticket) return "{}", nil }