Protobuf changes

This commit is contained in:
bdnugget 2025-01-13 00:31:15 +01:00
parent 4f36c2ee1f
commit 4012a2ed92
9 changed files with 150 additions and 59 deletions

32
actions.proto Normal file
View File

@ -0,0 +1,32 @@
syntax = "proto3";
package actions;
option go_package = "gitea.boner.be/bdnugget/goonserver/actions";
message Action {
enum ActionType {
MOVE = 0;
}
ActionType type = 1;
int32 x = 2;
int32 y = 3;
int32 player_id = 4;
}
message ActionBatch {
int32 player_id = 1;
repeated Action actions = 2;
int64 tick = 3;
}
message PlayerState {
int32 player_id = 1;
int32 x = 2;
int32 y = 3;
}
message ServerMessage {
int32 player_id = 1;
repeated PlayerState players = 2;
int64 current_tick = 3;
}

View File

@ -9,4 +9,13 @@ const (
TileHeight = 2.0 TileHeight = 2.0
TickRate = 600 * time.Millisecond // Server tick rate (600ms) TickRate = 600 * time.Millisecond // Server tick rate (600ms)
serverAddr = "localhost:6969" serverAddr = "localhost:6969"
// RuneScape-style tick rate (600ms)
ServerTickRate = 600 * time.Millisecond
// Client might run at a higher tick rate for smooth rendering
ClientTickRate = 50 * time.Millisecond
// Maximum number of ticks we can get behind before forcing a resync
MaxTickDesync = 5
) )

4
go.mod
View File

@ -1,4 +1,4 @@
module goonscape module gitea.boner.be/bdnugget/goonscape
go 1.23.0 go 1.23.0
@ -13,3 +13,5 @@ require (
golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect golang.org/x/exp v0.0.0-20241009180824-f66d83c29e7c // indirect
golang.org/x/sys v0.26.0 // indirect golang.org/x/sys v0.26.0 // indirect
) )
replace gitea.boner.be/bdnugget/goonserver => ./goonserver

2
go.sum
View File

@ -1,5 +1,3 @@
gitea.boner.be/bdnugget/goonserver v0.0.0-20241011195320-f16e8647dc6b h1:hdhCZH0YGqCsnSl6ru+8I7rxvCyOj5pCtf92urwyruA=
gitea.boner.be/bdnugget/goonserver v0.0.0-20241011195320-f16e8647dc6b/go.mod h1:inR1bKrr/vcTba+G1KzmmY6vssMq9oGNOk836VwPa4c=
github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE= github.com/ebitengine/purego v0.8.0 h1:JbqvnEzRvPpxhCJzJJ2y0RbiZ8nyjccVUrSM3q+GvvE=
github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/ebitengine/purego v0.8.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/gen2brain/raylib-go/raylib v0.0.0-20240930075631-c66f9e2942fe h1:mInjrbJkUglTM7tBmXG+epnPCE744aj15J7vjJwM4gs= github.com/gen2brain/raylib-go/raylib v0.0.0-20240930075631-c66f9e2942fe h1:mInjrbJkUglTM7tBmXG+epnPCE744aj15J7vjJwM4gs=

@ -1 +1 @@
Subproject commit 1d6d3ab2eadf488d6ec0e5ba85005b3e57e372ea Subproject commit f91f72c05d779b99c65b86abdfda205ef8e8f7c0

View File

@ -3,6 +3,7 @@ package main
import ( import (
"fmt" "fmt"
pb "gitea.boner.be/bdnugget/goonserver/actions"
rl "github.com/gen2brain/raylib-go/raylib" rl "github.com/gen2brain/raylib-go/raylib"
) )
@ -40,7 +41,12 @@ func HandleInput(player *Player, camera *rl.Camera) {
// Exclude the first tile (current position) // Exclude the first tile (current position)
if len(path) > 1 { if len(path) > 1 {
player.TargetPath = path[1:] player.TargetPath = path[1:]
player.ActionQueue = append(player.ActionQueue, Action{Type: MoveAction, X: clickedTile.X, Y: clickedTile.Y}) player.ActionQueue = append(player.ActionQueue, &pb.Action{
Type: pb.Action_MOVE,
X: int32(clickedTile.X),
Y: int32(clickedTile.Y),
PlayerId: player.ID,
})
} }
} }
} }

View File

@ -6,7 +6,6 @@ import (
"time" "time"
pb "gitea.boner.be/bdnugget/goonserver/actions" pb "gitea.boner.be/bdnugget/goonserver/actions"
rl "github.com/gen2brain/raylib-go/raylib"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
@ -42,39 +41,51 @@ func ConnectToServer() (net.Conn, int32, error) {
} }
func HandleServerCommunication(conn net.Conn, playerID int32, player *Player, otherPlayers map[int32]*Player) { func HandleServerCommunication(conn net.Conn, playerID int32, player *Player, otherPlayers map[int32]*Player) {
// Ticker for sending actions aligned with server ticks
ticker := time.NewTicker(ServerTickRate)
defer ticker.Stop()
// Goroutine to handle sending player's actions to the server // Goroutine to handle sending player's actions to the server
go func() { go func() {
for { for range ticker.C {
if len(player.ActionQueue) > 0 { if len(player.ActionQueue) > 0 {
// Process the first action in the queue // Bundle all actions that occurred during this tick
actionData := player.ActionQueue[0] actions := make([]*pb.Action, 0, len(player.ActionQueue))
action := &pb.Action{
PlayerId: playerID, for _, actionData := range player.ActionQueue {
Type: pb.Action_MOVE, action := &pb.Action{
X: int32(actionData.X), PlayerId: playerID,
Y: int32(actionData.Y), Type: pb.Action_MOVE,
X: int32(actionData.X),
Y: int32(actionData.Y),
}
actions = append(actions, action)
} }
// Serialize the action // Create a batch message
data, err := proto.Marshal(action) batch := &pb.ActionBatch{
PlayerId: playerID,
Actions: actions,
Tick: player.CurrentTick,
}
// Serialize the batch
data, err := proto.Marshal(batch)
if err != nil { if err != nil {
log.Printf("Failed to marshal action: %v", err) log.Printf("Failed to marshal action batch: %v", err)
continue continue
} }
// Send action to server // Send batch to server
_, err = conn.Write(data) _, err = conn.Write(data)
if err != nil { if err != nil {
log.Printf("Failed to send action to server: %v", err) log.Printf("Failed to send actions to server: %v", err)
return return
} }
// Remove the action from the queue once it's sent // Clear the action queue after sending
player.ActionQueue = player.ActionQueue[1:] player.ActionQueue = player.ActionQueue[:0]
} }
// Add a delay to match the server's tick rate
time.Sleep(TickRate)
} }
}() }()
@ -93,27 +104,25 @@ func HandleServerCommunication(conn net.Conn, playerID int32, player *Player, ot
continue continue
} }
// Check for tick synchronization
tickDiff := serverMessage.CurrentTick - player.CurrentTick
if tickDiff > MaxTickDesync {
log.Printf("Client too far behind (tick diff: %d), forcing resync", tickDiff)
player.ForceResync(serverMessage.Players[playerID])
}
// Update player's current tick
player.CurrentTick = serverMessage.CurrentTick
// Update other players' states // Update other players' states
for _, state := range serverMessage.Players { for _, state := range serverMessage.Players {
if state.PlayerId != playerID { if state.PlayerId != playerID {
if otherPlayer, exists := otherPlayers[state.PlayerId]; exists { if otherPlayer, exists := otherPlayers[state.PlayerId]; exists {
// Instead of directly setting position, set up path for smooth movement // Calculate interpolation based on server tick
targetTile := Tile{X: int(state.X), Y: int(state.Y)} otherPlayer.UpdatePosition(state, ServerTickRate)
if otherPlayer.PosTile != targetTile {
otherPlayer.TargetPath = []Tile{targetTile}
}
} else { } else {
// Initialize new player // Initialize new player
otherPlayers[state.PlayerId] = &Player{ otherPlayers[state.PlayerId] = NewPlayer(state)
PosTile: Tile{X: int(state.X), Y: int(state.Y)},
PosActual: rl.Vector3{
X: float32(state.X * TileSize),
Y: float32(state.Y * TileHeight),
Z: float32(state.Y * TileSize),
},
ID: state.PlayerId,
Speed: 50.0, // Make sure to set the speed for smooth movement
}
} }
} }
} }

View File

@ -1,6 +1,9 @@
package main package main
import ( import (
"time"
pb "gitea.boner.be/bdnugget/goonserver/actions"
rl "github.com/gen2brain/raylib-go/raylib" rl "github.com/gen2brain/raylib-go/raylib"
) )
@ -61,3 +64,38 @@ func (player *Player) MoveTowards(target Tile, deltaTime float32) {
} }
} }
} }
func (p *Player) UpdatePosition(state *pb.PlayerState, tickRate time.Duration) {
targetTile := Tile{X: int(state.X), Y: int(state.Y)}
if p.PosTile != targetTile {
p.PosTile = targetTile
p.LastUpdateTime = time.Now()
p.InterpolationProgress = 0
p.TargetPath = []Tile{targetTile}
}
}
func (p *Player) ForceResync(state *pb.PlayerState) {
p.PosTile = Tile{X: int(state.X), Y: int(state.Y)}
p.PosActual = rl.Vector3{
X: float32(state.X * TileSize),
Y: float32(state.Y * TileHeight),
Z: float32(state.Y * TileSize),
}
p.TargetPath = nil
p.ActionQueue = nil
p.InterpolationProgress = 1.0
}
func NewPlayer(state *pb.PlayerState) *Player {
return &Player{
PosActual: rl.Vector3{
X: float32(state.X * TileSize),
Y: float32(state.Y * TileHeight),
Z: float32(state.Y * TileSize),
},
PosTile: Tile{X: int(state.X), Y: int(state.Y)},
Speed: 50.0,
ID: state.PlayerId,
}
}

View File

@ -1,6 +1,11 @@
package main package main
import rl "github.com/gen2brain/raylib-go/raylib" import (
pb "gitea.boner.be/bdnugget/goonserver/actions"
rl "github.com/gen2brain/raylib-go/raylib"
time "time"
)
type Tile struct { type Tile struct {
X, Y int X, Y int
@ -8,24 +13,16 @@ type Tile struct {
Walkable bool Walkable bool
} }
type ActionType int
const (
MoveAction ActionType = iota
)
type Action struct {
Type ActionType
X, Y int // Target position for movement
}
type Player struct { type Player struct {
PosActual rl.Vector3 PosActual rl.Vector3
PosTile Tile PosTile Tile
TargetPath []Tile TargetPath []Tile
Speed float32 ActionQueue []*pb.Action
ActionQueue []Action // Queue for player actions Speed float32
Model rl.Model Model rl.Model
Texture rl.Texture2D Texture rl.Texture2D
ID int32 ID int32
CurrentTick int64
LastUpdateTime time.Time
InterpolationProgress float32
} }