Implement server-side movement
This commit is contained in:
		@ -3,6 +3,7 @@ package control
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"database/sql"
 | 
						"database/sql"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
	"github.com/heroiclabs/nakama-common/runtime"
 | 
						"github.com/heroiclabs/nakama-common/runtime"
 | 
				
			||||||
	"github.com/josephbmanley/family/server/plugin/entities"
 | 
						"github.com/josephbmanley/family/server/plugin/entities"
 | 
				
			||||||
	"github.com/josephbmanley/family/server/plugin/gamemap"
 | 
						"github.com/josephbmanley/family/server/plugin/gamemap"
 | 
				
			||||||
@ -17,6 +18,8 @@ type OpCode int64
 | 
				
			|||||||
const (
 | 
					const (
 | 
				
			||||||
	// OpCodeTileUpdate is used for tile updates
 | 
						// OpCodeTileUpdate is used for tile updates
 | 
				
			||||||
	OpCodeTileUpdate = 1
 | 
						OpCodeTileUpdate = 1
 | 
				
			||||||
 | 
						// OpCodeUpdatePosition is used for player position updates
 | 
				
			||||||
 | 
						OpCodeUpdatePosition = 2
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Match is the object registered
 | 
					// Match is the object registered
 | 
				
			||||||
@ -32,6 +35,15 @@ type MatchState struct {
 | 
				
			|||||||
	worldMap  *gamemap.WorldMap
 | 
						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
 | 
					// 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) {
 | 
					func (m *Match) MatchInit(ctx context.Context, logger runtime.Logger, db *sql.DB, nk runtime.NakamaModule, params map[string]interface{}) (interface{}, int, string) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -81,6 +93,7 @@ func (m *Match) MatchJoin(ctx context.Context, logger runtime.Logger, db *sql.DB
 | 
				
			|||||||
		player := entities.PlayerEntity{
 | 
							player := entities.PlayerEntity{
 | 
				
			||||||
			X:        16,
 | 
								X:        16,
 | 
				
			||||||
			Y:        16,
 | 
								Y:        16,
 | 
				
			||||||
 | 
								Presence: precense,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		mState.players[precense.GetUserId()] = player
 | 
							mState.players[precense.GetUserId()] = player
 | 
				
			||||||
@ -108,18 +121,32 @@ func (m *Match) MatchLeave(ctx context.Context, logger runtime.Logger, db *sql.D
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	for _, presence := range presences {
 | 
						for _, presence := range presences {
 | 
				
			||||||
		delete(mState.presences, presence.GetUserId())
 | 
							delete(mState.presences, presence.GetUserId())
 | 
				
			||||||
 | 
							delete(mState.players, presence.GetUserId())
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return mState
 | 
						return mState
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MatchLoop is code that is executed every tick
 | 
					// 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{} {
 | 
					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:
 | 
						mState, ok := state.(*MatchState)
 | 
				
			||||||
	// - Process the messages received.
 | 
						if !ok {
 | 
				
			||||||
	// - Update the match state based on the messages and time elapsed.
 | 
							logger.Error("Invalid match state on leave!")
 | 
				
			||||||
	// - Broadcast new data messages to match participants.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return state
 | 
							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)
 | 
				
			||||||
 | 
									dispatcher.BroadcastMessage(OpCodeUpdatePosition, []byte{}, mState.GetPrecenseList(), player.Presence, false)
 | 
				
			||||||
 | 
									logger.Info("Yes")
 | 
				
			||||||
 | 
								} 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
 | 
					// MatchTerminate is code that is executed when the match ends
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,55 @@
 | 
				
			|||||||
package entities
 | 
					package entities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"github.com/heroiclabs/nakama-common/runtime"
 | 
				
			||||||
 | 
						"strconv"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// PlayerEntity is the go struct representing the player's location
 | 
					// PlayerEntity is the go struct representing the player's location
 | 
				
			||||||
type PlayerEntity struct {
 | 
					type PlayerEntity struct {
 | 
				
			||||||
	X int
 | 
						Presence runtime.Presence
 | 
				
			||||||
	Y int
 | 
						X        float64
 | 
				
			||||||
 | 
						Y        float64
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// PlayerPosResponse struct that represents client data
 | 
				
			||||||
 | 
					type PlayerPosResponse struct {
 | 
				
			||||||
 | 
						X string
 | 
				
			||||||
 | 
						Y string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ParsePositionRequest parses data from client
 | 
				
			||||||
 | 
					func (p *PlayerEntity) ParsePositionRequest(data []byte) (PlayerPosResponse, error) {
 | 
				
			||||||
 | 
						var response PlayerPosResponse
 | 
				
			||||||
 | 
						err := json.Unmarshal(data, &response)
 | 
				
			||||||
 | 
						return response, err
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//UpdateBasedOnResponse updates the player object based on a response object
 | 
				
			||||||
 | 
					func (p *PlayerEntity) UpdateBasedOnResponse(response PlayerPosResponse) error {
 | 
				
			||||||
 | 
						if fx, err := strconv.ParseFloat(response.X, 64); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							p.X = fx
 | 
				
			||||||
 | 
							if fy, err := strconv.ParseFloat(response.Y, 64); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								p.Y = fy
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetPosJSON returns the player's position as a JSON object
 | 
				
			||||||
 | 
					func (p *PlayerEntity) GetPosJSON() ([]byte, error) {
 | 
				
			||||||
 | 
						playerMap := map[string]string{
 | 
				
			||||||
 | 
							"player": p.Presence.GetUserId(),
 | 
				
			||||||
 | 
							"x":      fmt.Sprintf("%f", p.X),
 | 
				
			||||||
 | 
							"y":      fmt.Sprintf("%f", p.Y),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						jsonData, err := json.Marshal(playerMap)
 | 
				
			||||||
 | 
						return jsonData, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -27,21 +27,22 @@ func (m WorldMap) GetJSONRegion(startX, endX, startY, endY int) ([]byte, error)
 | 
				
			|||||||
	for x := startX; x < endX; x++ {
 | 
						for x := startX; x < endX; x++ {
 | 
				
			||||||
		regionMap[x] = map[int]int{}
 | 
							regionMap[x] = map[int]int{}
 | 
				
			||||||
		for y := startY; y < endY; y++ {
 | 
							for y := startY; y < endY; y++ {
 | 
				
			||||||
			if result, err := m.GetTile(x, y); err != nil {
 | 
					
 | 
				
			||||||
				return nil, err
 | 
								// GetTile and ignore out of bounds errors
 | 
				
			||||||
			} else {
 | 
								result, _ := m.GetTile(x, y)
 | 
				
			||||||
			regionMap[x][y] = result
 | 
								regionMap[x][y] = result
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	jsonString, err := json.Marshal(regionMap)
 | 
						jsonString, err := json.Marshal(regionMap)
 | 
				
			||||||
	return jsonString, err
 | 
						return jsonString, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetJSONRegionAround returns a JSON object of tile data from a center point
 | 
					// GetJSONRegionAround returns a JSON object of tile data from a center point
 | 
				
			||||||
func (m WorldMap) GetJSONRegionAround(centerX, centerY, regionRadius int) ([]byte, error) {
 | 
					func (m WorldMap) GetJSONRegionAround(centerX float64, centerY float64, regionRadius int) ([]byte, error) {
 | 
				
			||||||
	jsonString, err := m.GetJSONRegion(centerX-regionRadius, centerX+regionRadius, centerY-regionRadius, centerY+regionRadius)
 | 
						var xCenter int = int(centerX)
 | 
				
			||||||
 | 
						var yCenter int = int(centerY)
 | 
				
			||||||
 | 
						jsonString, err := m.GetJSONRegion(xCenter-regionRadius, xCenter+regionRadius, yCenter-regionRadius, yCenter+regionRadius)
 | 
				
			||||||
	return jsonString, err
 | 
						return jsonString, err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user