Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save creepteks/adfcc8888c53e7119b9c02a3c2adcfb8 to your computer and use it in GitHub Desktop.
Save creepteks/adfcc8888c53e7119b9c02a3c2adcfb8 to your computer and use it in GitHub Desktop.
Nakama authoritative backend with matchmaking
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"database/sql"
"encoding/json"
"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("backend loaded.")
if err := initializer.RegisterMatch("PvP", CreateMatchInternal); err != nil {
return err
}
if err := initializer.RegisterMatchmakerMatched(DoMatchmaking); err != nil {
logger.Error("Unable to register: %v", err)
return err
}
return nil
}
func DoMatchmaking(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, entries []runtime.MatchmakerEntry) (string, error) {
for _, e := range entries {
logger.Info("Matched user '%s' named '%s'", e.GetPresence().GetUserId(), e.GetPresence().GetUsername())
for k, v := range e.GetProperties() {
logger.Info("Matched on '%s' value '%v'", k, v)
}
}
matchId, err := nk.MatchCreate(ctx, "davaaPvP", map[string]interface{}{"invited": entries, "debug": true})
if err != nil {
return "", err
}
return matchId, nil
}
func CreateMatchInternal(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule) (runtime.Match, error) {
return &Match{}, nil
}
type MatchState struct {
debug bool
presences map[string]runtime.Presence
opponents map[string]runtime.Presence
}
type Match struct{}
func (m *Match) MatchInit(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, params map[string]interface{}) (interface{}, int, string) {
var debug bool
if d, ok := params["debug"]; ok {
if dv, ok := d.(bool); ok {
debug = dv
}
}
state := &MatchState{
debug: debug,
presences: make(map[string]runtime.Presence),
opponents: make(map[string]runtime.Presence),
}
if state.debug {
logger.Printf("match init, starting with debug: %v", state.debug)
}
entries := params["invited"].([]runtime.MatchmakerEntry)
for i := 0; i < len(entries); i++ {
p := entries[(i+1)%len(entries)].GetPresence()
state.opponents[entries[i].GetPresence().GetUsername()] = p
logger.Info("setting %v 's opponent : %v", entries[i].GetPresence().GetUsername(), entries[(i+1)%len(entries)].GetPresence().GetUsername())
}
label := "skill=100-150"
tickRate := 20 // ticks per second
return state, tickRate, label
}
func (m *Match) MatchJoinAttempt(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, presence runtime.Presence, metadata map[string]string) (interface{}, bool, string) {
if state.(*MatchState).debug {
logger.Printf("match join attempt username %v user_id %v session_id %v node %v with metadata %v", presence.GetUsername(), presence.GetUserId(), presence.GetSessionId(), presence.GetNodeId(), metadata)
}
return state, true, ""
}
func (m *Match) MatchJoin(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, presences []runtime.Presence) interface{} {
mState := state.(*MatchState)
if mState.debug {
for _, presence := range presences {
logger.Printf("match join username %v user_id %v session_id %v node %v", presence.GetUsername(), presence.GetUserId(), presence.GetSessionId(), presence.GetNodeId())
}
}
return mState
}
func (m *Match) MatchLeave(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, presences []runtime.Presence) interface{} {
if state.(*MatchState).debug {
for _, presence := range presences {
logger.Printf("match leave username %v user_id %v session_id %v node %v", presence.GetUsername(), presence.GetUserId(), presence.GetSessionId(), presence.GetNodeId())
}
}
return state
}
func (m *Match) MatchLoop(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, messages []runtime.MatchData) interface{} {
mState, _ := state.(*MatchState)
if state.(*MatchState).debug {
logger.Printf("match loop match_id %v tick %v", ctx.Value(runtime.RUNTIME_CTX_MATCH_ID), tick)
logger.Printf("match loop match_id %v message count %v", ctx.Value(runtime.RUNTIME_CTX_MATCH_ID), len(messages))
}
for _, message := range messages {
target := mState.opponents[message.GetUsername()]
dispatcher.BroadcastMessage(message.GetOpCode(), message.GetData(), []runtime.Presence{target}, nil, false)
}
return mState
}
func (m *Match) MatchTerminate(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, graceSeconds int) interface{} {
if state.(*MatchState).debug {
logger.Printf("match terminate match_id %v tick %v", ctx.Value(runtime.RUNTIME_CTX_MATCH_ID), tick)
logger.Printf("match terminate match_id %v grace seconds %v", ctx.Value(runtime.RUNTIME_CTX_MATCH_ID), graceSeconds)
}
return nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment