diff --git a/server/plugin/control/control.go b/server/plugin/control/control.go index 2eb9cc1..3aceab0 100644 --- a/server/plugin/control/control.go +++ b/server/plugin/control/control.go @@ -7,14 +7,21 @@ import ( "github.com/josephbmanley/family/server/plugin/gamemap" ) +// OpCode represents a enum for valid OpCodes +// used by the match logic type OpCode int64 const ( + // OpCodeTileUpdate is used for tile updates OpCodeTileUpdate = 1 ) +// 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 inputs map[string]string @@ -23,6 +30,7 @@ type MatchState struct { worldMap *gamemap.WorldMap } +// 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{ @@ -38,12 +46,16 @@ func (m *Match) MatchInit(ctx context.Context, logger runtime.Logger, db *sql.DB 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 { @@ -52,32 +64,39 @@ func (m *Match) MatchJoinAttempt(ctx context.Context, logger runtime.Logger, db } +// 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, false, "Invalid match state!" + return state } + for _, precense := range presences { + + // Add presence to map mState.presences[precense.GetUserId()] = precense + // Set player spawn pos mState.positions[precense.GetUserId()] = map[string]int{"x": 16, "y": 16} mState.names[precense.GetUserId()] = "User" - if regionData, err := mState.worldMap.GetJsonRegion(16-8, 16+8, 16-8, 16+8); err != nil { + // Get intial tile data around player + if regionData, err := mState.worldMap.GetJSONRegionAround(16, 16, 8); err != nil { logger.Error(err.Error()) - return mState } 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 } } } 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 { @@ -90,6 +109,7 @@ func (m *Match) MatchLeave(ctx context.Context, logger runtime.Logger, db *sql.D 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{} { // Custom code to: // - Process the messages received. @@ -99,6 +119,7 @@ func (m *Match) MatchLoop(ctx context.Context, logger runtime.Logger, db *sql.DB return state } +// 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 } diff --git a/server/plugin/gamemap/gamemap.go b/server/plugin/gamemap/gamemap.go index e2cc927..55bc45a 100644 --- a/server/plugin/gamemap/gamemap.go +++ b/server/plugin/gamemap/gamemap.go @@ -5,12 +5,14 @@ import ( "fmt" ) +// WorldMap is the data structure used game world type WorldMap struct { data [64][64]int max_x int max_y int } +// GetTile method is used to grab a tile value with error checking func (m WorldMap) GetTile(x int, y int) (int, error) { if x > m.max_x || y > m.max_y { return -1, fmt.Errorf("Map out of bounds error: %d, %d", x, y) @@ -18,11 +20,13 @@ func (m WorldMap) GetTile(x int, y int) (int, error) { return m.data[x][y], nil } -func (m WorldMap) GetJsonRegion(start_x, end_x, start_y, end_y int) ([]byte, error) { +// GetJSONRegion method returns a JSON object containing the tile values of everything +// within a given range +func (m WorldMap) GetJSONRegion(startX, endX, startY, endY int) ([]byte, error) { regionMap := map[int]map[int]int{} - for x := start_x; x < end_x; x++ { + for x := startX; x < endX; x++ { regionMap[x] = map[int]int{} - for y := start_y; y < end_y; y++ { + for y := startY; y < endY; y++ { if result, err := m.GetTile(x, y); err != nil { return nil, err } else { @@ -35,6 +39,14 @@ func (m WorldMap) GetJsonRegion(start_x, end_x, start_y, end_y int) ([]byte, err return jsonString, err } +// GetJSONRegionAround returns a JSON object of tile data from a center point +func (m WorldMap) GetJSONRegionAround(centerX, centerY, regionRadius int) ([]byte, error) { + jsonString, err := m.GetJSONRegion(centerX-regionRadius, centerX+regionRadius, centerY-regionRadius, centerY+regionRadius) + return jsonString, err +} + +// IntializeMap is a method that helps easily +// generate WorldMap objects func IntializeMap() *WorldMap { worldMap := new(WorldMap) worldMap.max_x = 64 diff --git a/server/plugin/rpc/rpc.go b/server/plugin/rpc/rpc.go index 8635a53..ea5855d 100644 --- a/server/plugin/rpc/rpc.go +++ b/server/plugin/rpc/rpc.go @@ -9,27 +9,20 @@ import ( func getFirstWorld(ctx context.Context, logger runtime.Logger, nk runtime.NakamaModule) (string, error) { // List existing matches - // that have been 1 & 4 players + // that have been 1 & 32 players minSize := 1 - maxSize := 31 - //5, false, "", &minSize, &maxSize, "" + maxSize := 32 + + // Lists server authorative servers if matches, err := nk.MatchList(ctx, 1, true, "", &minSize, &maxSize, ""); err != nil { logger.Printf("Failed to list matches when grabing first world! Error: %v\n", err) return "", err } else { - - //For debug purposes - for _, match := range matches { - logger.Info("Found match with id: %s", match.GetMatchId()) - } - // If no matches exist, create one if len(matches) <= 0 { // Create match - //params := map[string]interface{}{} matchID, createErr := nk.MatchCreate(ctx, "control", map[string]interface{}{}) - //return nakama.match_create("world_control", {}) // Return if creation error if createErr != nil {