Refactored server flow
This commit is contained in:
parent
6f49400770
commit
bf08205c7d
@ -149,10 +149,9 @@ render_target_update_mode = 4
|
||||
[node name="CameraPivot" type="Node3D" parent="Viewport/SubViewport"]
|
||||
|
||||
[node name="Camera" type="Camera3D" parent="Viewport/SubViewport/CameraPivot" node_paths=PackedStringArray("center")]
|
||||
transform = Transform3D(0.707107, 0.353554, -0.612372, 0, 0.866026, 0.5, 0.707107, -0.353554, 0.612372, -10, 10, 10)
|
||||
projection = 1
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 20, 20)
|
||||
current = true
|
||||
fov = 90.0
|
||||
fov = 30.0
|
||||
size = 10.0
|
||||
far = 100.0
|
||||
script = ExtResource("18_wogcj")
|
||||
|
@ -5,6 +5,7 @@ extends Camera3D
|
||||
|
||||
func _ready():
|
||||
Global.camera = self
|
||||
look_at(center.position)
|
||||
|
||||
func _process(delta):
|
||||
if Global.player == null:
|
||||
|
@ -1,19 +1,20 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"server/game/packet"
|
||||
)
|
||||
|
||||
// Chat is used for chat messages.
|
||||
func (game *Game) Chat(data []byte, address *net.UDPAddr, server *Server) error {
|
||||
player := game.players.Get(address)
|
||||
fmt.Printf("[%s] %s\n", player.Name, string(data))
|
||||
func (game *Game) Chat(data []byte, address *net.UDPAddr) error {
|
||||
player := game.players.ByAddress(address)
|
||||
|
||||
if player == nil {
|
||||
return ErrUnknownAddress
|
||||
}
|
||||
|
||||
newData := []byte{}
|
||||
newData = AppendString(newData, player.ID)
|
||||
newData = AppendStringBytes(newData, data)
|
||||
game.Broadcast(packet.Chat, newData)
|
||||
game.Broadcast(Chat, newData)
|
||||
return nil
|
||||
}
|
||||
|
@ -5,25 +5,25 @@ var accounts = map[string]*Account{
|
||||
ID: "4J6qpK1ve",
|
||||
Name: "user0",
|
||||
Password: "password",
|
||||
Position: Vector3{5, 0, 0},
|
||||
Position: Vector3{3, 0, 0},
|
||||
},
|
||||
"user1": {
|
||||
ID: "I_vyeZamg",
|
||||
Name: "user1",
|
||||
Password: "password",
|
||||
Position: Vector3{-5, 0, 0},
|
||||
Position: Vector3{-3, 0, 0},
|
||||
},
|
||||
"user2": {
|
||||
ID: "VJOK1ckvx",
|
||||
Name: "user2",
|
||||
Password: "password",
|
||||
Position: Vector3{0, 0, 5},
|
||||
Position: Vector3{0, 0, 3},
|
||||
},
|
||||
"user3": {
|
||||
ID: "EkCcqbwFl",
|
||||
Name: "user3",
|
||||
Password: "password",
|
||||
Position: Vector3{0, 0, -5},
|
||||
Position: Vector3{0, 0, -3},
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -4,87 +4,78 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"server/game/packet"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Game represents the entire state of the game server.
|
||||
type Game struct {
|
||||
server *Server
|
||||
router Router
|
||||
players *PlayerManager
|
||||
}
|
||||
|
||||
// New creates a new game.
|
||||
func New() *Game {
|
||||
return &Game{
|
||||
game := &Game{
|
||||
server: NewServer(),
|
||||
players: NewPlayerManager(),
|
||||
}
|
||||
|
||||
game.router.Get(Ping, game.Ping)
|
||||
game.router.Get(Login, game.Login)
|
||||
game.router.Get(Move, game.Move)
|
||||
game.router.Get(Jump, game.Jump)
|
||||
game.router.Get(Chat, game.Chat)
|
||||
return game
|
||||
}
|
||||
|
||||
// Run starts all game systems.
|
||||
func (game *Game) Run() {
|
||||
game.start()
|
||||
physics := time.NewTicker(20 * time.Millisecond)
|
||||
statistics := time.NewTicker(time.Second)
|
||||
clean := time.NewTicker(time.Second)
|
||||
close := make(chan os.Signal, 1)
|
||||
signal.Notify(close, os.Interrupt)
|
||||
<-close
|
||||
|
||||
go game.server.Run(4242)
|
||||
|
||||
for {
|
||||
select {
|
||||
case p := <-game.server.incoming:
|
||||
game.router.handle(p)
|
||||
|
||||
case <-physics.C:
|
||||
game.players.Each(func(c *Player) {
|
||||
c.Tick()
|
||||
})
|
||||
|
||||
case <-statistics.C:
|
||||
fmt.Printf("%d players | %d packets\n", game.players.Count(), game.server.PacketCount())
|
||||
game.server.ResetPacketCount()
|
||||
|
||||
case <-clean.C:
|
||||
game.players.Clean(5 * time.Second)
|
||||
|
||||
case <-close:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Broadcast sends the packet to all players.
|
||||
func (game *Game) Broadcast(code byte, data []byte) {
|
||||
game.players.Each(func(other *Player) bool {
|
||||
game.players.Each(func(other *Player) {
|
||||
game.server.Send(code, data, other.address)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// BroadcastOthers sends the packet to all other players except the original sender.
|
||||
func (game *Game) BroadcastOthers(code byte, data []byte, exclude *Player) {
|
||||
game.players.Each(func(other *Player) bool {
|
||||
game.players.Each(func(other *Player) {
|
||||
if other == exclude {
|
||||
return true
|
||||
return
|
||||
}
|
||||
|
||||
game.server.Send(code, data, other.address)
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// start starts all game systems.
|
||||
func (game *Game) start() {
|
||||
go game.network()
|
||||
go game.physics()
|
||||
go game.statistics()
|
||||
}
|
||||
|
||||
// network will listen for new packets and process them.
|
||||
func (game *Game) network() {
|
||||
game.server.SetHandler(packet.Ping, game.Ping)
|
||||
game.server.SetHandler(packet.Login, game.Login)
|
||||
game.server.SetHandler(packet.PlayerMove, game.Move)
|
||||
game.server.SetHandler(packet.PlayerJump, game.Jump)
|
||||
game.server.SetHandler(packet.Chat, game.Chat)
|
||||
game.server.Run(4242)
|
||||
}
|
||||
|
||||
// physics periodically runs the Tick function for each player.
|
||||
func (game *Game) physics() {
|
||||
updater := time.NewTicker(20 * time.Millisecond)
|
||||
|
||||
for range updater.C {
|
||||
game.players.Each(func(c *Player) bool {
|
||||
c.Tick()
|
||||
return true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// statistics periodically shows server statistics on the command line.
|
||||
func (game *Game) statistics() {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
|
||||
for range ticker.C {
|
||||
fmt.Printf("%d players | %d packets\n", game.players.Count(), game.server.PacketCount())
|
||||
game.server.ResetPacketCount()
|
||||
}
|
||||
}
|
||||
|
@ -3,4 +3,4 @@ package game
|
||||
import "net"
|
||||
|
||||
// Handler is a byte code specific packet handler.
|
||||
type Handler func([]byte, *net.UDPAddr, *Server) error
|
||||
type Handler func([]byte, *net.UDPAddr) error
|
||||
|
@ -2,12 +2,16 @@ package game
|
||||
|
||||
import (
|
||||
"net"
|
||||
"server/game/packet"
|
||||
)
|
||||
|
||||
// Jump broadcasts the jump action.
|
||||
func (game *Game) Jump(data []byte, address *net.UDPAddr, server *Server) error {
|
||||
player := game.players.Get(address)
|
||||
game.BroadcastOthers(packet.PlayerJump, []byte(player.ID), player)
|
||||
func (game *Game) Jump(data []byte, address *net.UDPAddr) error {
|
||||
player := game.players.ByAddress(address)
|
||||
|
||||
if player == nil {
|
||||
return ErrUnknownAddress
|
||||
}
|
||||
|
||||
game.BroadcastOthers(Jump, []byte(player.ID), player)
|
||||
return nil
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"server/game/packet"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -14,43 +13,71 @@ const (
|
||||
Failure = 1
|
||||
)
|
||||
|
||||
var (
|
||||
ErrAlreadyLoggedIn = errors.New("already logged in")
|
||||
ErrUnknownAccount = errors.New("unknown account")
|
||||
ErrWrongPassword = errors.New("wrong password")
|
||||
)
|
||||
|
||||
// Login checks the account credentials and gives a network peer access to an account.
|
||||
func (game *Game) Login(data []byte, address *net.UDPAddr, server *Server) error {
|
||||
if game.players.Get(address) != nil {
|
||||
server.Send(packet.Login, []byte{Failure}, address)
|
||||
return errors.New("already logged in")
|
||||
func (game *Game) Login(data []byte, address *net.UDPAddr) error {
|
||||
player := game.players.ByAddress(address)
|
||||
|
||||
if player != nil {
|
||||
game.server.Send(Login, []byte{Failure}, address)
|
||||
return ErrAlreadyLoggedIn
|
||||
}
|
||||
|
||||
username, password, err := getLoginData(data)
|
||||
|
||||
if err != nil {
|
||||
server.Send(packet.Login, []byte{Failure}, address)
|
||||
game.server.Send(Login, []byte{Failure}, address)
|
||||
return err
|
||||
}
|
||||
|
||||
account := GetAccountByName(username)
|
||||
|
||||
if account == nil {
|
||||
server.Send(packet.Login, []byte{Failure}, address)
|
||||
return errors.New("unknown account name")
|
||||
game.server.Send(Login, []byte{Failure}, address)
|
||||
return ErrUnknownAccount
|
||||
}
|
||||
|
||||
if password != "password" {
|
||||
server.Send(packet.Login, []byte{Failure}, address)
|
||||
return errors.New("login failure")
|
||||
game.server.Send(Login, []byte{Failure}, address)
|
||||
return ErrWrongPassword
|
||||
}
|
||||
|
||||
player = game.players.ByAccount(account.ID)
|
||||
|
||||
if player != nil {
|
||||
game.reconnect(player, address)
|
||||
} else {
|
||||
game.connect(account, address)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (game *Game) reconnect(player *Player, address *net.UDPAddr) {
|
||||
player.KeepAlive()
|
||||
game.players.ChangeAddress(player, address)
|
||||
game.sendLoginSuccess(player)
|
||||
player.OnConnect()
|
||||
}
|
||||
|
||||
func (game *Game) connect(account *Account, address *net.UDPAddr) {
|
||||
player := NewPlayer(address, account, game)
|
||||
player.authToken = createAuthToken()
|
||||
player.KeepAlive()
|
||||
|
||||
response := []byte{Success}
|
||||
response = AppendString(response, account.ID)
|
||||
response = AppendString(response, player.authToken)
|
||||
server.Send(packet.Login, response, address)
|
||||
|
||||
game.sendLoginSuccess(player)
|
||||
game.players.Add(player)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (game *Game) sendLoginSuccess(player *Player) {
|
||||
response := []byte{Success}
|
||||
response = AppendString(response, player.ID)
|
||||
response = AppendString(response, player.authToken)
|
||||
game.server.Send(Login, response, player.address)
|
||||
}
|
||||
|
||||
func getLoginData(data []byte) (string, string, error) {
|
||||
|
@ -2,14 +2,23 @@ package game
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
"net"
|
||||
"server/game/packet"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnknownAddress = errors.New("unknown address")
|
||||
)
|
||||
|
||||
// Move updates the location and direction the client is currently moving towards.
|
||||
func (game *Game) Move(data []byte, address *net.UDPAddr, server *Server) error {
|
||||
player := game.players.Get(address)
|
||||
func (game *Game) Move(data []byte, address *net.UDPAddr) error {
|
||||
player := game.players.ByAddress(address)
|
||||
|
||||
if player == nil {
|
||||
return ErrUnknownAddress
|
||||
}
|
||||
|
||||
player.Position.X = math.Float32frombits(binary.LittleEndian.Uint32(data))
|
||||
player.Position.Z = math.Float32frombits(binary.LittleEndian.Uint32(data[4:]))
|
||||
|
||||
@ -17,6 +26,6 @@ func (game *Game) Move(data []byte, address *net.UDPAddr, server *Server) error
|
||||
update = AppendFloat(update, player.Position.X)
|
||||
update = AppendFloat(update, player.Position.Z)
|
||||
|
||||
game.BroadcastOthers(packet.PlayerMove, update, player)
|
||||
game.BroadcastOthers(Move, update, player)
|
||||
return nil
|
||||
}
|
||||
|
@ -1,4 +1,8 @@
|
||||
package packet
|
||||
package game
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
const (
|
||||
Ping = 1
|
||||
@ -6,8 +10,13 @@ const (
|
||||
Logout = 3
|
||||
PlayerAdd = 10
|
||||
PlayerRemove = 11
|
||||
PlayerMove = 12
|
||||
PlayerJump = 13
|
||||
Move = 12
|
||||
Jump = 13
|
||||
PlayerAttack = 14
|
||||
Chat = 20
|
||||
)
|
||||
|
||||
type Packet struct {
|
||||
Data []byte
|
||||
Address *net.UDPAddr
|
||||
}
|
@ -2,15 +2,14 @@ package game
|
||||
|
||||
import (
|
||||
"net"
|
||||
"server/game/packet"
|
||||
)
|
||||
|
||||
// Ping is used as a heartbeat and latency check.
|
||||
func (game *Game) Ping(data []byte, address *net.UDPAddr, server *Server) error {
|
||||
server.Send(packet.Ping, data, address)
|
||||
func (game *Game) Ping(data []byte, address *net.UDPAddr) error {
|
||||
game.server.Send(Ping, data, address)
|
||||
|
||||
if game.players.Contains(address) {
|
||||
game.players.Get(address).KeepAlive()
|
||||
game.players.ByAddress(address).KeepAlive()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -3,7 +3,6 @@ package game
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"server/game/packet"
|
||||
"time"
|
||||
)
|
||||
|
||||
@ -54,18 +53,16 @@ func (player *Player) OnConnect() {
|
||||
players := player.game.players
|
||||
server := player.game.server
|
||||
|
||||
players.Each(func(other *Player) bool {
|
||||
server.Send(packet.PlayerAdd, other.State(), player.address)
|
||||
players.Each(func(other *Player) {
|
||||
server.Send(PlayerAdd, other.State(), player.address)
|
||||
|
||||
if other != player {
|
||||
server.Send(packet.PlayerAdd, player.State(), other.address)
|
||||
server.Send(PlayerAdd, player.State(), other.address)
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
func (player *Player) OnDisconnect() {
|
||||
fmt.Printf("%s disconnected.\n", player.Name)
|
||||
player.game.BroadcastOthers(packet.PlayerRemove, []byte(player.ID), player)
|
||||
player.game.BroadcastOthers(PlayerRemove, []byte(player.ID), player)
|
||||
}
|
||||
|
@ -9,47 +9,48 @@ import (
|
||||
|
||||
// PlayerManager keeps tracks of all players.
|
||||
type PlayerManager struct {
|
||||
players sync.Map
|
||||
count atomic.Int64
|
||||
players sync.Map
|
||||
accounts sync.Map
|
||||
count atomic.Int64
|
||||
}
|
||||
|
||||
// NewPlayerManager creates a new player manager.
|
||||
func NewPlayerManager() *PlayerManager {
|
||||
m := &PlayerManager{}
|
||||
timeout := 5 * time.Second
|
||||
interval := time.Second
|
||||
|
||||
go func() {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for range ticker.C {
|
||||
now := time.Now()
|
||||
|
||||
m.players.Range(func(key, value interface{}) bool {
|
||||
player := value.(*Player)
|
||||
|
||||
if !player.lastPacket.IsZero() && now.After(player.lastPacket.Add(timeout)) {
|
||||
m.players.Delete(key)
|
||||
m.count.Add(-1)
|
||||
player.OnDisconnect()
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
}()
|
||||
|
||||
return m
|
||||
return &PlayerManager{}
|
||||
}
|
||||
|
||||
// Add adds a new player with the given address and account.
|
||||
func (m *PlayerManager) Clean(timeout time.Duration) {
|
||||
now := time.Now()
|
||||
|
||||
m.players.Range(func(key, value interface{}) bool {
|
||||
player := value.(*Player)
|
||||
|
||||
if !player.lastPacket.IsZero() && now.After(player.lastPacket.Add(timeout)) {
|
||||
m.players.Delete(key)
|
||||
m.accounts.Delete(player.ID)
|
||||
m.count.Add(-1)
|
||||
player.OnDisconnect()
|
||||
}
|
||||
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Add adds a new player.
|
||||
func (m *PlayerManager) Add(player *Player) {
|
||||
m.players.Store(player.address.String(), player)
|
||||
m.accounts.Store(player.ID, player)
|
||||
m.count.Add(1)
|
||||
player.OnConnect()
|
||||
}
|
||||
|
||||
// ChangeAddress changes the address of a player.
|
||||
func (m *PlayerManager) ChangeAddress(player *Player, address *net.UDPAddr) {
|
||||
m.players.Delete(player.address.String())
|
||||
player.address = address
|
||||
m.players.Store(player.address.String(), player)
|
||||
}
|
||||
|
||||
// Contains tells you whether the address is already a registered client.
|
||||
func (m *PlayerManager) Contains(address *net.UDPAddr) bool {
|
||||
_, exists := m.players.Load(address.String())
|
||||
@ -62,14 +63,15 @@ func (m *PlayerManager) Count() int {
|
||||
}
|
||||
|
||||
// Each calls the callback function for each client.
|
||||
func (m *PlayerManager) Each(callback func(*Player) bool) {
|
||||
func (m *PlayerManager) Each(callback func(*Player)) {
|
||||
m.players.Range(func(key, value any) bool {
|
||||
return callback(value.(*Player))
|
||||
callback(value.(*Player))
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
// Get either returns a new or existing client for the requested address.
|
||||
func (m *PlayerManager) Get(address *net.UDPAddr) *Player {
|
||||
// ByAddress returns an existing client for the requested address.
|
||||
func (m *PlayerManager) ByAddress(address *net.UDPAddr) *Player {
|
||||
obj, exists := m.players.Load(address.String())
|
||||
|
||||
if !exists {
|
||||
@ -78,3 +80,14 @@ func (m *PlayerManager) Get(address *net.UDPAddr) *Player {
|
||||
|
||||
return obj.(*Player)
|
||||
}
|
||||
|
||||
// ByAccount returns the player with the given account ID.
|
||||
func (m *PlayerManager) ByAccount(id string) *Player {
|
||||
obj, exists := m.accounts.Load(id)
|
||||
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
|
||||
return obj.(*Player)
|
||||
}
|
||||
|
38
server/game/Router.go
Normal file
38
server/game/Router.go
Normal file
@ -0,0 +1,38 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Router processes packets by passing them to the correct handler.
|
||||
type Router struct {
|
||||
handlers [256]Handler
|
||||
}
|
||||
|
||||
// Get sets the handler for the given byte code.
|
||||
func (ph *Router) Get(code byte, handler Handler) {
|
||||
ph.handlers[code] = handler
|
||||
}
|
||||
|
||||
// handle deals with an incoming packet.
|
||||
func (ph *Router) handle(p *Packet) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println(p)
|
||||
fmt.Println(r)
|
||||
}
|
||||
}()
|
||||
|
||||
handler := ph.handlers[p.Data[0]]
|
||||
|
||||
if handler == nil {
|
||||
fmt.Printf("No callback registered for packet type %d\n", p.Data[0])
|
||||
return
|
||||
}
|
||||
|
||||
err := handler(p.Data[1:], p.Address)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
}
|
@ -5,16 +5,21 @@ import (
|
||||
"net"
|
||||
)
|
||||
|
||||
const ChannelBufferSize = 4096
|
||||
|
||||
// Server represents a UDP server.
|
||||
type Server struct {
|
||||
handlers [256]Handler
|
||||
socket *net.UDPConn
|
||||
incoming chan *Packet
|
||||
packetCount int
|
||||
}
|
||||
|
||||
// NewServer creates a new server.
|
||||
func NewServer() *Server {
|
||||
return &Server{}
|
||||
return &Server{
|
||||
incoming: make(chan *Packet, ChannelBufferSize),
|
||||
}
|
||||
}
|
||||
|
||||
// SetHandler sets the handler for the given byte code.
|
||||
@ -80,24 +85,9 @@ func (s *Server) read() {
|
||||
continue
|
||||
}
|
||||
|
||||
s.handle(buffer[:n], addr)
|
||||
tmp := make([]byte, n)
|
||||
copy(tmp, buffer)
|
||||
s.incoming <- &Packet{Data: tmp, Address: addr}
|
||||
s.packetCount++
|
||||
}
|
||||
}
|
||||
|
||||
// handle deals with an incoming packet.
|
||||
func (s *Server) handle(data []byte, addr *net.UDPAddr) {
|
||||
handler := s.handlers[data[0]]
|
||||
|
||||
if handler == nil {
|
||||
fmt.Printf("No callback registered for packet type %d\n", data[0])
|
||||
return
|
||||
}
|
||||
|
||||
err := handler(data[1:], addr, s)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
|
||||
s.packetCount++
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user