160 lines
5.2 KiB
Go
160 lines
5.2 KiB
Go
package control
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"github.com/heroiclabs/nakama-common/runtime"
|
|
"github.com/josephbmanley/family/server/plugin/entities"
|
|
"github.com/josephbmanley/family/server/plugin/gamemap"
|
|
)
|
|
|
|
const maxRenderDistance int = 32
|
|
|
|
// OpCode represents a enum for valid OpCodes
|
|
// used by the match logic
|
|
type OpCode int64
|
|
|
|
const (
|
|
// OpCodeTileUpdate is used for tile updates
|
|
OpCodeTileUpdate = 1
|
|
// OpCodeUpdatePosition is used for player position updates
|
|
OpCodeUpdatePosition = 2
|
|
)
|
|
|
|
// Match is the object registered
|
|
// as a runtime.Match interface
|
|
type Match struct{}
|
|
|
|
// MatchState holds information that is passed between
|
|
// Nakama match methods
|
|
type MatchState struct {
|
|
presences map[string]runtime.Presence
|
|
players map[string]entities.PlayerEntity
|
|
inputs map[string]string
|
|
worldMap *gamemap.WorldMap
|
|
}
|
|
|
|
// GetPrecenseList returns an array of current precenes in an array
|
|
func (state *MatchState) GetPrecenseList() []runtime.Presence {
|
|
precenseList := []runtime.Presence{}
|
|
for _, precense := range state.presences {
|
|
precenseList = append(precenseList, precense)
|
|
}
|
|
return precenseList
|
|
}
|
|
|
|
// MatchInit is called when a new match is created
|
|
func (m *Match) MatchInit(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, params map[string]interface{}) (interface{}, int, string) {
|
|
|
|
state := &MatchState{
|
|
presences: map[string]runtime.Presence{},
|
|
inputs: map[string]string{},
|
|
players: map[string]entities.PlayerEntity{},
|
|
worldMap: gamemap.IntializeMap(),
|
|
}
|
|
tickRate := 10
|
|
label := "{\"name\": \"Game World\"}"
|
|
|
|
return state, tickRate, label
|
|
}
|
|
|
|
// MatchJoinAttempt is called when a player tried to join a match
|
|
// and validates their attempt
|
|
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) {
|
|
mState, ok := state.(*MatchState)
|
|
if !ok {
|
|
logger.Error("Invalid match state on join attempt!")
|
|
return state, false, "Invalid match state!"
|
|
}
|
|
|
|
// Validate user is not already connected
|
|
if _, ok := mState.presences[presence.GetUserId()]; ok {
|
|
return mState, false, "User already logged in."
|
|
} else {
|
|
return mState, true, ""
|
|
}
|
|
|
|
}
|
|
|
|
// MatchJoin is called when a player successfully joins the match
|
|
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, ok := state.(*MatchState)
|
|
if !ok {
|
|
logger.Error("Invalid match state on join!")
|
|
return state
|
|
}
|
|
|
|
for _, precense := range presences {
|
|
|
|
// Add presence to map
|
|
mState.presences[precense.GetUserId()] = precense
|
|
|
|
player := entities.PlayerEntity{
|
|
X: 16,
|
|
Y: 16,
|
|
Presence: precense,
|
|
}
|
|
|
|
mState.players[precense.GetUserId()] = player
|
|
|
|
// Get intial tile data around player
|
|
if regionData, err := mState.worldMap.GetJSONRegionAround(player.X, player.Y, maxRenderDistance); err != nil {
|
|
logger.Error(err.Error())
|
|
} else {
|
|
|
|
// Broadcast tile data to client
|
|
if sendErr := dispatcher.BroadcastMessage(OpCodeTileUpdate, regionData, []runtime.Presence{precense}, precense, true); sendErr != nil {
|
|
logger.Error(sendErr.Error())
|
|
}
|
|
}
|
|
}
|
|
return mState
|
|
}
|
|
|
|
// MatchLeave is called when a player leaves the match
|
|
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{} {
|
|
mState, ok := state.(*MatchState)
|
|
if !ok {
|
|
logger.Error("Invalid match state on leave!")
|
|
return state
|
|
}
|
|
for _, presence := range presences {
|
|
delete(mState.presences, presence.GetUserId())
|
|
delete(mState.players, presence.GetUserId())
|
|
}
|
|
return mState
|
|
}
|
|
|
|
// MatchLoop is code that is executed every tick
|
|
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, ok := state.(*MatchState)
|
|
if !ok {
|
|
logger.Error("Invalid match state on leave!")
|
|
return state
|
|
}
|
|
for _, message := range messages {
|
|
if message.GetOpCode() == OpCodeUpdatePosition {
|
|
player := mState.players[message.GetUserId()]
|
|
|
|
if response, err := player.ParsePositionRequest(message.GetData()); err == nil {
|
|
player.UpdateBasedOnResponse(response)
|
|
if jsonObject, err := player.GetPosJSON(); err == nil {
|
|
dispatcher.BroadcastMessage(OpCodeUpdatePosition, jsonObject, mState.GetPrecenseList(), player.Presence, false)
|
|
logger.Info("Yes")
|
|
} else {
|
|
logger.Error(fmt.Sprintf("Failed to get player json: %s", err.Error))
|
|
}
|
|
} else {
|
|
logger.Error(fmt.Sprintf("Failed to parse update pos request: %s", err.Error))
|
|
}
|
|
}
|
|
}
|
|
return mState
|
|
}
|
|
|
|
// MatchTerminate is code that is executed when the match ends
|
|
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{} {
|
|
return state
|
|
}
|