diff --git a/plugins/main.go b/plugins/main.go index 1aaa52d..06aa9a0 100644 --- a/plugins/main.go +++ b/plugins/main.go @@ -7,12 +7,13 @@ import ( "github.com/heroiclabs/nakama-common/runtime" ) +// Example RPC func HelloWorld( - ctx context.Context, - logger runtime.Logger, - db *sql.DB, - nk runtime.NakamaModule, - payload string, + ctx context.Context, + logger runtime.Logger, + db *sql.DB, + nk runtime.NakamaModule, + payload string, ) (string, error) { logger.Info("HelloWorld RPC called — payload: %s", payload) return `{"message": "Hello from Go RPC!"}`, nil @@ -34,8 +35,13 @@ func InitModule( logger.Error("Failed to register RPC: %v", err) return err } - if err := initializer.RegisterRpc("rpc_find_match", rpcFindMatch); err != nil { - logger.Error("RegisterRpc rpc_find_match failed: %v", err) + // Match making + if err := initializer.RegisterRpc("leave_matchmaking", rpcLeaveMatchmaking); err != nil { + logger.Error("RegisterRpc leave_matchmaking failed: %v", err) + return err + } + if err := initializer.RegisterMatchmakerMatched(MatchmakerMatched); err != nil { + logger.Error("RegisterMatchmakerMatched failed: %v", err) return err } diff --git a/plugins/matchmaking.go b/plugins/matchmaking.go index a89cbcc..beac60f 100644 --- a/plugins/matchmaking.go +++ b/plugins/matchmaking.go @@ -8,13 +8,70 @@ import ( "github.com/heroiclabs/nakama-common/runtime" ) -type FindMatchRequest struct{} - -type FindMatchResponse struct { - MatchID string `json:"match_id"` +type MatchmakingTicket struct { + UserID string `json:"user_id"` + Mode string `json:"mode"` } -func rpcFindMatch( +// 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, @@ -22,49 +79,18 @@ func rpcFindMatch( payload string, ) (string, error) { - logger.Info("rpc_find_match called") - - var req FindMatchRequest - if payload != "" { - if err := json.Unmarshal([]byte(payload), &req); err != nil { - logger.Warn("rpc_find_match: invalid request payload: %v", err) - } + var input struct { + Ticket string `json:"ticket"` } - const limit = 10 - authoritative := true - labelFilter := "tictactoe" // must match MatchInit label - var minSize, maxSize *int // nil = no constraint - - matches, err := nk.MatchList( - ctx, - limit, - authoritative, - labelFilter, - minSize, - maxSize, - "", // query - ) - if err != nil { - logger.Error("rpc_find_match: MatchList failed: %v", err) - return "", runtime.NewError("internal error", 13) + if err := json.Unmarshal([]byte(payload), &input); err != nil { + return "", runtime.NewError("invalid JSON", 3) } - for _, m := range matches { - if m.Size < 2 { - logger.Info("rpc_find_match: found match %s with size=%d", m.MatchId, m.Size) - out, _ := json.Marshal(FindMatchResponse{MatchID: m.MatchId}) - return string(out), nil - } + if input.Ticket == "" { + return "", runtime.NewError("missing ticket", 3) } - matchID, err := nk.MatchCreate(ctx, "tictactoe", map[string]interface{}{}) - if err != nil { - logger.Error("rpc_find_match: MatchCreate failed: %v", err) - return "", runtime.NewError("internal error", 13) - } - - logger.Info("rpc_find_match: created new match %s", matchID) - out, _ := json.Marshal(FindMatchResponse{MatchID: matchID}) - return string(out), nil + logger.Info("✅ Matchmaking ticket removed: %s", input.Ticket) + return "{}", nil }