package main import ( "context" "database/sql" "encoding/json" "github.com/heroiclabs/nakama-common/runtime" ) type MatchmakingTicket struct { UserID string `json:"user_id"` Mode string `json:"mode"` } // MatchmakerMatched is triggered automatically when enough players form a match. func MatchmakerMatched( ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, entries []runtime.MatchmakerEntry, ) (string, error) { if len(entries) != 2 { logger.Warn("MatchmakerMatched triggered but incorrect player count: %d", len(entries)) return "", runtime.NewError("requires exactly 2 players", 3) } // Extract player data ticketA := entries[0] ticketB := entries[1] propsA := ticketA.GetProperties() propsB := ticketB.GetProperties() modeA, okA := propsA["mode"].(string) modeB, okB := propsB["mode"].(string) if !okA || !okB { logger.Error("Matchmaker ticket missing mode property") return "", runtime.NewError("matchmaking requires game mode", 13) } // ✅ Ensure both players queued for the same mode if modeA != modeB { logger.Warn("Players queued for different modes: %s != %s", modeA, modeB) return "", runtime.NewError("players must select same mode", 3) } matchParams := map[string]interface{}{ "mode": modeA, } // ✅ Create authoritative match instance matchID, err := nk.MatchCreate(ctx, "tictactoe", matchParams) if err != nil { logger.Error("MatchCreate failed: %v", err) return "", runtime.NewError("failed to create match", 13) } logger.Info("✅ Match created: %s — mode=%s — players=%s, %s", matchID, modeA, ticketA.GetPresence().GetUserId(), ticketB.GetPresence().GetUserId(), ) // ✅ Return match ID so Nakama notifies clients over WebSocket return matchID, nil } // RPC to leave matchmaking queue 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) } logger.Info("✅ Matchmaking ticket removed: %s", input.Ticket) return "{}", nil }