151 lines
5.7 KiB
Go
151 lines
5.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
|
|
"github.com/heroiclabs/nakama-common/runtime"
|
|
)
|
|
|
|
// Interface for registering match handlers
|
|
type BattleRoyaleMatch struct{}
|
|
|
|
// In-memory game state
|
|
type BattleRoyaleMatchState struct {
|
|
presences map[string]runtime.Presence
|
|
emptyTicks int
|
|
}
|
|
|
|
// Run on match start, initializes game state and sets tick rate
|
|
func (m *BattleRoyaleMatch) MatchInit(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, params map[string]interface{}) (interface{}, int, string) {
|
|
state := &BattleRoyaleMatchState{
|
|
presences: map[string]runtime.Presence{},
|
|
emptyTicks: 0,
|
|
}
|
|
tickRate := 1 // MatchLoop invocations per second
|
|
label := ""
|
|
return state, tickRate, label
|
|
}
|
|
|
|
// Run when a user attempts to join or rejoin a match. Responsible for deciding whether or not to let them in.
|
|
func (m *BattleRoyaleMatch) 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) {
|
|
lobbyState, ok := state.(*BattleRoyaleMatchState)
|
|
if !ok {
|
|
logger.Error("State is not a valid lobby state object for MatchJoin.")
|
|
return nil, false, "Failed to join match: match does not exist."
|
|
}
|
|
accepted := true
|
|
rejectedMessage := ""
|
|
|
|
return lobbyState, accepted, rejectedMessage
|
|
}
|
|
|
|
// Run when a user successfully joins a match, registers their presence in the game state
|
|
func (m *BattleRoyaleMatch) MatchJoin(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, presences []runtime.Presence) interface{} {
|
|
lobbyState, ok := state.(*BattleRoyaleMatchState)
|
|
if !ok {
|
|
logger.Error("State is not a valid lobby state object for MatchJoin.")
|
|
return nil
|
|
}
|
|
|
|
for i := 0; i < len(presences); i++ {
|
|
lobbyState.presences[presences[i].GetSessionId()] = presences[i]
|
|
}
|
|
|
|
return lobbyState
|
|
}
|
|
|
|
// Run when a user successfully leaves a match, de-registers their presence in the game state
|
|
func (m *BattleRoyaleMatch) MatchLeave(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, presences []runtime.Presence) interface{} {
|
|
lobbyState, ok := state.(*BattleRoyaleMatchState)
|
|
if !ok {
|
|
logger.Error("State is not a valid lobby state object for MatchLeave.")
|
|
return nil
|
|
}
|
|
|
|
for i := 0; i < len(presences); i++ {
|
|
delete(lobbyState.presences, presences[i].GetSessionId())
|
|
}
|
|
|
|
return lobbyState
|
|
}
|
|
|
|
// Run when a match gets an arbitrary signal from the Nakama runtime (probably from the matchmaker/match lister APIs)
|
|
func (m *BattleRoyaleMatch) MatchSignal(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, data string) (interface{}, string) {
|
|
lobbyState, ok := state.(*BattleRoyaleMatchState)
|
|
if !ok {
|
|
logger.Error("State is not a valid lobby state object for MatchSignal.")
|
|
return nil, "Failed to get valid state for return signal"
|
|
}
|
|
|
|
returnMessage := ""
|
|
return lobbyState, returnMessage
|
|
}
|
|
|
|
// Run when the server enters the graceful shutdown flow. Gives the match a chance to shutdown cleanly within graceSeconds.
|
|
func (m *BattleRoyaleMatch) MatchTerminate(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, graceSeconds int) interface{} {
|
|
lobbyState, ok := state.(*BattleRoyaleMatchState)
|
|
if !ok {
|
|
logger.Error("State is not a valid lobby state object for MatchTerminate.")
|
|
return nil
|
|
}
|
|
|
|
return lobbyState
|
|
}
|
|
|
|
// Main game loop, executed at tickRate per second specified in MatchInit
|
|
func (m *BattleRoyaleMatch) MatchLoop(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, dispatcher runtime.MatchDispatcher, tick int64, state interface{}, messages []runtime.MatchData) interface{} {
|
|
lobbyState, ok := state.(*BattleRoyaleMatchState)
|
|
if !ok {
|
|
logger.Error("State is not a valid lobby state object for MatchLoop.")
|
|
return nil
|
|
}
|
|
|
|
// If we have no presences in the match according to the match state, increment the empty ticks count
|
|
if len(lobbyState.presences) == 0 {
|
|
lobbyState.emptyTicks++
|
|
}
|
|
|
|
// If the match has been empty for more than 100 ticks, end the match by returning nil
|
|
if lobbyState.emptyTicks > 100 {
|
|
return nil
|
|
}
|
|
|
|
return lobbyState
|
|
}
|
|
|
|
// RPC for force-creating a match for debugging/development, separate from the matchmaking process
|
|
func ManualForceCreateBRMatchRPC(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, payload string) (string, error) {
|
|
/*params := make(map[string]interface{})
|
|
|
|
if err := json.Unmarshal([]byte(payload), ¶ms); err != nil {
|
|
return "", err
|
|
}*/
|
|
|
|
modulename := "battle-royale"
|
|
|
|
if matchId, err := nk.MatchCreate(ctx, modulename, make(map[string]interface{})); err != nil {
|
|
return "", err
|
|
} else {
|
|
return matchId, nil
|
|
}
|
|
}
|
|
|
|
// main function for hooking into the nakama runtime, responsible for setting up all handlers
|
|
func InitModule(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, initializer runtime.Initializer) error {
|
|
// Register handlers for match lifecycle
|
|
if err := initializer.RegisterMatch("battle-royale", func(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule) (runtime.Match, error) {
|
|
return &BattleRoyaleMatch{}, nil
|
|
}); err != nil {
|
|
logger.Error("Unable to register match handler: %v", nil)
|
|
return err
|
|
}
|
|
|
|
// Register RPCs
|
|
if err := initializer.RegisterRpc("manual_force_create_br_match_rpc", ManualForceCreateBRMatchRPC); err != nil {
|
|
logger.Error("Unable to register: %v", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|