- Implement MatchmakerMatched callback for true matchmaking flow - Enforce strict 1v1 pairing (ignore non-2 player matches) - Read `mode` from matchmaker ticket properties - Prevent mismatched-mode players from being paired - Automatically create authoritative `tictactoe` match when valid pair found - Provide match parameters so match handler receives selected mode - Improve logging for debugging and visibility Ensures clean, mode-aware matchmaking queues and proper server-side match creation.
86 lines
1.9 KiB
Go
86 lines
1.9 KiB
Go
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 with %d players", len(entries))
|
||
return "", nil
|
||
}
|
||
|
||
propsA := entries[0].GetProperties()
|
||
propsB := entries[1].GetProperties()
|
||
|
||
modeA, okA := propsA["mode"].(string)
|
||
modeB, okB := propsB["mode"].(string)
|
||
|
||
if !okA || !okB {
|
||
logger.Warn("MatchmakerMatched missing mode property — ignoring")
|
||
return "", nil
|
||
}
|
||
|
||
// ✅ If modes don’t match, let Nakama find another pairing
|
||
if modeA != modeB {
|
||
logger.Warn("Mode mismatch %s vs %s — retrying matchmaking", modeA, modeB)
|
||
return "", nil
|
||
}
|
||
|
||
// ✅ Create authoritative match
|
||
matchParams := map[string]interface{}{
|
||
"mode": modeA,
|
||
}
|
||
|
||
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", matchID, modeA)
|
||
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
|
||
}
|