1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-09-29 22:56:07 -04:00

D2networking resolve lint issues (#492)

* Added automap.go stub

* Handle errors in original AutoMap.txt file

* Completed AutoMapRecord struct and comments

* AutoMap loader implemented

* Update from base repo

* Comments added to d2netpacket and d2netpackettype.

Note, the Overview for d2netpacket is in net_packet.go. It could be placed in a doc.go file but net_packet.go seemed appropriate in this case.

* Comments added to d2server

* client_connection.go missed from previous commit

* Comments added to d2client

* Doc.go added to d2networking and other corrections
This commit is contained in:
danhale-git 2020-06-29 22:01:26 +01:00 committed by GitHub
parent aae565d528
commit 5ea6ada452
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 241 additions and 62 deletions

View File

@ -2,6 +2,8 @@ package d2networking
import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
// ClientListener is an interface used to pass packet data from
// ClientConnections to GameServer and GameClient.
type ClientListener interface { type ClientListener interface {
OnPacketReceived(packet d2netpacket.NetPacket) error OnPacketReceived(packet d2netpacket.NetPacket) error
} }

View File

@ -5,6 +5,8 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
) )
// ClientConnection is an interface for abstracting local and
// remote server connections.
type ClientConnection interface { type ClientConnection interface {
Open(connectionString string, saveFilePath string) error Open(connectionString string, saveFilePath string) error
Close() error Close() error

View File

@ -1,9 +1,12 @@
package d2clientconnectiontype package d2clientconnectiontype
// ClientConnectionType is an enum referring to types implementing
// d2server.ClientConnection and d2client.ClientConnection.
type ClientConnectionType int type ClientConnectionType int
//
const ( const (
Local ClientConnectionType = iota Local ClientConnectionType = iota // Local client
LANServer LANServer // Server
LANClient LANClient // Remote client
) )

View File

@ -1,3 +1,4 @@
// Package d2localclient facilitates communication between a local client and server.
package d2localclient package d2localclient
import ( import (
@ -9,25 +10,33 @@ import (
uuid "github.com/satori/go.uuid" uuid "github.com/satori/go.uuid"
) )
// LocalClientConnection is the implementation of ClientConnection
// for a local client.
type LocalClientConnection struct { type LocalClientConnection struct {
clientListener d2networking.ClientListener clientListener d2networking.ClientListener // The game client
uniqueId string uniqueId string // Unique ID generated on construction
openNetworkServer bool openNetworkServer bool // True if this is a server
playerState *d2player.PlayerState playerState *d2player.PlayerState // Local player state
} }
// GetUniqueId returns LocalClientConnection.uniqueId.
func (l LocalClientConnection) GetUniqueId() string { func (l LocalClientConnection) GetUniqueId() string {
return l.uniqueId return l.uniqueId
} }
// GetConnectionType returns an enum representing the connection type.
// See: d2clientconnectiontype
func (l LocalClientConnection) GetConnectionType() d2clientconnectiontype.ClientConnectionType { func (l LocalClientConnection) GetConnectionType() d2clientconnectiontype.ClientConnectionType {
return d2clientconnectiontype.Local return d2clientconnectiontype.Local
} }
// SendPacketToClient passes a packet to the game client for processing.
func (l *LocalClientConnection) SendPacketToClient(packet d2netpacket.NetPacket) error { func (l *LocalClientConnection) SendPacketToClient(packet d2netpacket.NetPacket) error {
return l.clientListener.OnPacketReceived(packet) return l.clientListener.OnPacketReceived(packet)
} }
// Create constructs a new LocalClientConnection and returns
// a pointer to it.
func Create(openNetworkServer bool) *LocalClientConnection { func Create(openNetworkServer bool) *LocalClientConnection {
result := &LocalClientConnection{ result := &LocalClientConnection{
uniqueId: uuid.NewV4().String(), uniqueId: uuid.NewV4().String(),
@ -37,6 +46,7 @@ func Create(openNetworkServer bool) *LocalClientConnection {
return result return result
} }
// Open creates a new GameServer, runs the server and connects this client to it.
func (l *LocalClientConnection) Open(connectionString string, saveFilePath string) error { func (l *LocalClientConnection) Open(connectionString string, saveFilePath string) error {
l.SetPlayerState(d2player.LoadPlayerState(saveFilePath)) l.SetPlayerState(d2player.LoadPlayerState(saveFilePath))
d2server.Create(l.openNetworkServer) d2server.Create(l.openNetworkServer)
@ -45,6 +55,7 @@ func (l *LocalClientConnection) Open(connectionString string, saveFilePath strin
return nil return nil
} }
// Close disconnects from the server and destroys it.
func (l *LocalClientConnection) Close() error { func (l *LocalClientConnection) Close() error {
l.SendPacketToServer(d2netpacket.CreateServerClosedPacket()) l.SendPacketToServer(d2netpacket.CreateServerClosedPacket())
d2server.OnClientDisconnected(l) d2server.OnClientDisconnected(l)
@ -52,19 +63,23 @@ func (l *LocalClientConnection) Close() error {
return nil return nil
} }
// SendPacketToServer calls d2server.OnPacketReceived with the given packet.
func (l *LocalClientConnection) SendPacketToServer(packet d2netpacket.NetPacket) error { func (l *LocalClientConnection) SendPacketToServer(packet d2netpacket.NetPacket) error {
// TODO: This is going to blow up if the server has ceased to be. // TODO: This is going to blow up if the server has ceased to be.
return d2server.OnPacketReceived(l, packet) return d2server.OnPacketReceived(l, packet)
} }
// SetClientListener sets LocalClientConnection.clientListener to the given value.
func (l *LocalClientConnection) SetClientListener(listener d2networking.ClientListener) { func (l *LocalClientConnection) SetClientListener(listener d2networking.ClientListener) {
l.clientListener = listener l.clientListener = listener
} }
// GetPlayerState returns LocalClientConnection.playerState.
func (l *LocalClientConnection) GetPlayerState() *d2player.PlayerState { func (l *LocalClientConnection) GetPlayerState() *d2player.PlayerState {
return l.playerState return l.playerState
} }
// SetPlayerState sets LocalClientConnection.playerState to the given value.
func (l *LocalClientConnection) SetPlayerState(playerState *d2player.PlayerState) { func (l *LocalClientConnection) SetPlayerState(playerState *d2player.PlayerState) {
l.playerState = playerState l.playerState = playerState
} }

View File

@ -1,3 +1,4 @@
// Package d2remoteclient facilitates communication between a remote client and server.
package d2remoteclient package d2remoteclient
import ( import (
@ -5,12 +6,13 @@ import (
"compress/gzip" "compress/gzip"
"encoding/json" "encoding/json"
"fmt" "fmt"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
"io" "io"
"log" "log"
"net" "net"
"strings" "strings"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
"github.com/OpenDiablo2/OpenDiablo2/d2networking" "github.com/OpenDiablo2/OpenDiablo2/d2networking"
@ -19,25 +21,33 @@ import (
uuid "github.com/satori/go.uuid" uuid "github.com/satori/go.uuid"
) )
// RemoteClientConnection is the implementation of ClientConnection
// for a remote client.
type RemoteClientConnection struct { type RemoteClientConnection struct {
clientListener d2networking.ClientListener clientListener d2networking.ClientListener // The GameClient
uniqueId string uniqueId string // Unique ID generated on construction
udpConnection *net.UDPConn udpConnection *net.UDPConn // UDP connection to the server
active bool active bool // The connection is currently open
} }
// GetUniqueId returns RemoteClientConnection.uniqueId.
func (l RemoteClientConnection) GetUniqueId() string { func (l RemoteClientConnection) GetUniqueId() string {
return l.uniqueId return l.uniqueId
} }
// GetConnectionType returns an enum representing the connection type.
// See: d2clientconnectiontype
func (l RemoteClientConnection) GetConnectionType() d2clientconnectiontype.ClientConnectionType { func (l RemoteClientConnection) GetConnectionType() d2clientconnectiontype.ClientConnectionType {
return d2clientconnectiontype.LANClient return d2clientconnectiontype.LANClient
} }
func (l *RemoteClientConnection) SendPacketToClient(packet d2netpacket.NetPacket) error { // WHAT IS THIS // SendPacketToClient passes a packet to the game client for processing.
func (l *RemoteClientConnection) SendPacketToClient(packet d2netpacket.NetPacket) error {
return l.clientListener.OnPacketReceived(packet) return l.clientListener.OnPacketReceived(packet)
} }
// Create constructs a new RemoteClientConnection
// and returns a pointer to it.
func Create() *RemoteClientConnection { func Create() *RemoteClientConnection {
result := &RemoteClientConnection{ result := &RemoteClientConnection{
uniqueId: uuid.NewV4().String(), uniqueId: uuid.NewV4().String(),
@ -46,6 +56,8 @@ func Create() *RemoteClientConnection {
return result return result
} }
// Open runs serverListener() in a goroutine to continuously read UDP packets.
// It also sends a PlayerConnectionRequestPacket packet to the server (see d2netpacket).
func (l *RemoteClientConnection) Open(connectionString string, saveFilePath string) error { func (l *RemoteClientConnection) Open(connectionString string, saveFilePath string) error {
if !strings.Contains(connectionString, ":") { if !strings.Contains(connectionString, ":") {
connectionString += ":6669" connectionString += ":6669"
@ -75,6 +87,8 @@ func (l *RemoteClientConnection) Open(connectionString string, saveFilePath stri
return nil return nil
} }
// Close informs the server that this client has disconnected and sets
// RemoteClientConnection.active to false.
func (l *RemoteClientConnection) Close() error { func (l *RemoteClientConnection) Close() error {
l.active = false l.active = false
l.SendPacketToServer(d2netpacket.CreatePlayerDisconnectRequestPacket(l.GetUniqueId())) l.SendPacketToServer(d2netpacket.CreatePlayerDisconnectRequestPacket(l.GetUniqueId()))
@ -82,6 +96,8 @@ func (l *RemoteClientConnection) Close() error {
return nil return nil
} }
// SendPacketToServer compresses the JSON encoding of a NetPacket and
// sends it to the server.
func (l *RemoteClientConnection) SendPacketToServer(packet d2netpacket.NetPacket) error { func (l *RemoteClientConnection) SendPacketToServer(packet d2netpacket.NetPacket) error {
data, err := json.Marshal(packet.PacketData) data, err := json.Marshal(packet.PacketData)
if err != nil { if err != nil {
@ -98,10 +114,13 @@ func (l *RemoteClientConnection) SendPacketToServer(packet d2netpacket.NetPacket
return nil return nil
} }
// SetClientListener sets RemoteClientConnection.clientListener to the given value.
func (l *RemoteClientConnection) SetClientListener(listener d2networking.ClientListener) { func (l *RemoteClientConnection) SetClientListener(listener d2networking.ClientListener) {
l.clientListener = listener l.clientListener = listener
} }
// serverListener runs a while loop, reading from the GameServer's UDP
// connection.
func (l *RemoteClientConnection) serverListener() { func (l *RemoteClientConnection) serverListener() {
buffer := make([]byte, 4096) buffer := make([]byte, 4096)
for l.active { for l.active {

View File

@ -0,0 +1,7 @@
// Package d2client provides client side connection, map and entity
// management.
/*
GameClient declares the ClientConnection interface to interact with
local and remote servers. LocalClientConnection is the host and
creates the game server (see d2server).*/
package d2client

View File

@ -22,17 +22,20 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
) )
// GameClient manages a connection to d2server.GameServer
// and keeps a synchronised copy of the map and entities.
type GameClient struct { type GameClient struct {
clientConnection ClientConnection clientConnection ClientConnection // Abstract local/remote connection
connectionType d2clientconnectiontype.ClientConnectionType connectionType d2clientconnectiontype.ClientConnectionType // Type of connection (local or remote)
GameState *d2player.PlayerState GameState *d2player.PlayerState // local player state
MapEngine *d2mapengine.MapEngine MapEngine *d2mapengine.MapEngine // Map and entities
PlayerId string PlayerId string // ID of the local player
Players map[string]*d2mapentity.Player Players map[string]*d2mapentity.Player // IDs of the other players
Seed int64 Seed int64 // Map seed
RegenMap bool RegenMap bool // Regenerate tile cache on render (map has changed)
} }
// Create constructs a new GameClient and returns a pointer to it.
func Create(connectionType d2clientconnectiontype.ClientConnectionType) (*GameClient, error) { func Create(connectionType d2clientconnectiontype.ClientConnectionType) (*GameClient, error) {
result := &GameClient{ result := &GameClient{
MapEngine: d2mapengine.CreateMapEngine(), // TODO: Mapgen - Needs levels.txt stuff MapEngine: d2mapengine.CreateMapEngine(), // TODO: Mapgen - Needs levels.txt stuff
@ -54,18 +57,26 @@ func Create(connectionType d2clientconnectiontype.ClientConnectionType) (*GameCl
return result, nil return result, nil
} }
// Open creates the server and connects to it if the client is local.
// If the client is remote it sends a PlayerConnectionRequestPacket to the
// server (see d2netpacket).
func (g *GameClient) Open(connectionString string, saveFilePath string) error { func (g *GameClient) Open(connectionString string, saveFilePath string) error {
return g.clientConnection.Open(connectionString, saveFilePath) return g.clientConnection.Open(connectionString, saveFilePath)
} }
// Close destroys the server if the client is local. For remote clients
// it sends a DisconnectRequestPacket (see d2netpacket).
func (g *GameClient) Close() error { func (g *GameClient) Close() error {
return g.clientConnection.Close() return g.clientConnection.Close()
} }
// Destroy does the same thing as Close.
func (g *GameClient) Destroy() error { func (g *GameClient) Destroy() error {
return g.clientConnection.Close() return g.clientConnection.Close()
} }
// OnPacketReceived is called by the ClientConection and processes incoming
// packets.
func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error { func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
switch packet.PacketType { switch packet.PacketType {
case d2netpackettype.GenerateMap: case d2netpackettype.GenerateMap:
@ -145,6 +156,8 @@ func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
return nil return nil
} }
// SendPacketToServer calls server.OnPacketReceived if the client is local.
// If it is remote the NetPacket sent over a UDP connection to the server.
func (g *GameClient) SendPacketToServer(packet d2netpacket.NetPacket) error { func (g *GameClient) SendPacketToServer(packet d2netpacket.NetPacket) error {
return g.clientConnection.SendPacketToServer(packet) return g.clientConnection.SendPacketToServer(packet)
} }

View File

@ -1,20 +1,28 @@
// Package d2netpackettype defines the enumerable NetPacketType.
package d2netpackettype package d2netpackettype
// NetPacketType is an enum referring to all packet types in package
// d2netpacket.
type NetPacketType uint32 type NetPacketType uint32
// Warning: Do NOT re-arrange the order of these packet values unless you want to //(Except NetPacket which declares a NetPacketType to specify the packet body
// break compatibility between clients of slightly different versions. // type. See d2netpackettype.NetPacket.)
// Also note that the packet id is a byte, so if we use more than 256 //
// of these then we are doing something very wrong. // Warning
//
// Do NOT re-arrange the order of these packet values unless you want to
// break compatibility between clients of slightly different versions.
// Also note that the packet id is a byte, so if we use more than 256 of
// these then we are doing something very wrong.
const ( const (
UpdateServerInfo NetPacketType = iota UpdateServerInfo NetPacketType = iota // Sent by the server, client sets the given player ID and map seed
GenerateMap // Sent by the server to generate a map GenerateMap // Sent by the server, client generates a map
AddPlayer // Server sends to the client to add a player AddPlayer // Sent by the server, client adds a player
MovePlayer // Sent to the client or server to indicate player movement MovePlayer // Sent by client or server, moves a player entity
PlayerConnectionRequest // Client sends to server to request a connection PlayerConnectionRequest // Sent by the remote client when connecting
PlayerDisconnectionNotification // Client notifies the server that it is disconnecting PlayerDisconnectionNotification // Sent by the remote client when disconnecting
Ping // Ping message type Ping // Requests a Pong packet
Pong // Pong message type Pong // Responds to a Ping packet
ServerClosed // Local host has closed the server ServerClosed // Sent by the local host when it has closed the server
CastSkill // Sent to the client or server to indicate entity casting skill CastSkill // Sent by client or server, indicates entity casting skill
) )

View File

@ -1,7 +1,22 @@
// Package d2netpacket defines types which are encoded to JSON and sent in network
// packet payloads.
/*
Package d2netpacket/d2netpackettype defines a uint32 enumerable representing each
packet type.
A struct is defined for each packet type. Each struct comes with a function which
returns a NetPacket declaring the type enum (header) followed by the associated
struct (body). The NetPacket is marshalled to JSON for transport. On receipt of
the packet, the enum is read as a single byte then the remaining data (the struct)
is unmarshalled to the type associated with the type enum.*/
package d2netpacket package d2netpacket
import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
// NetPacket is used to wrap and send all packet types under d2netpacket.
// When decoding a packet: First the PacketType byte is read, then the
// PacketData is unmarshalled to a struct of the type associated with
// PacketType.
type NetPacket struct { type NetPacket struct {
PacketType d2netpackettype.NetPacketType `json:"packetType"` PacketType d2netpackettype.NetPacketType `json:"packetType"`
PacketData interface{} `json:"packetData"` PacketData interface{} `json:"packetData"`

View File

@ -7,6 +7,9 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
) )
// AddPlayerPacket contains the data required to create a Player entity.
// It is sent by the server to create the entity for a newly connected
// player on a client.
type AddPlayerPacket struct { type AddPlayerPacket struct {
Id string `json:"id"` Id string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
@ -14,9 +17,11 @@ type AddPlayerPacket struct {
Y int `json:"y"` Y int `json:"y"`
HeroType d2enum.Hero `json:"hero"` HeroType d2enum.Hero `json:"hero"`
Equipment d2inventory.CharacterEquipment `json:"equipment"` Equipment d2inventory.CharacterEquipment `json:"equipment"`
Stats d2hero.HeroStatsState `json:"heroStats"` Stats d2hero.HeroStatsState `json:"heroStats"`
} }
// CreateAddPlayerPacket returns a NetPacket which declares an
// AddPlayerPacket with the data in given parameters.
func CreateAddPlayerPacket(id, name string, x, y int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) NetPacket { func CreateAddPlayerPacket(id, name string, x, y int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) NetPacket {
return NetPacket{ return NetPacket{
PacketType: d2netpackettype.AddPlayer, PacketType: d2netpackettype.AddPlayer,
@ -27,7 +32,7 @@ func CreateAddPlayerPacket(id, name string, x, y int, heroType d2enum.Hero, stat
Y: y, Y: y,
HeroType: heroType, HeroType: heroType,
Equipment: equipment, Equipment: equipment,
Stats: stats, Stats: stats,
}, },
} }
} }

View File

@ -5,10 +5,15 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
) )
// GenerateMapPacket contains an enumerable representing a region. It
// is sent by the server to generate the map for the given region on
// a client.
type GenerateMapPacket struct { type GenerateMapPacket struct {
RegionType d2enum.RegionIdType `json:"regionType"` RegionType d2enum.RegionIdType `json:"regionType"`
} }
// CreateGenerateMapPacket returns a NetPacket which declares a
// GenerateMapPacket with the given regionType.
func CreateGenerateMapPacket(regionType d2enum.RegionIdType) NetPacket { func CreateGenerateMapPacket(regionType d2enum.RegionIdType) NetPacket {
return NetPacket{ return NetPacket{
PacketType: d2netpackettype.GenerateMap, PacketType: d2netpackettype.GenerateMap,

View File

@ -2,8 +2,9 @@ package d2netpacket
import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
// MovePlayerPacket contains a movement command for a specific player entity.
// It is sent by the server to move a player entity on a client.
// TODO: Need to handle being on different maps // TODO: Need to handle being on different maps
type MovePlayerPacket struct { type MovePlayerPacket struct {
PlayerId string `json:"playerId"` PlayerId string `json:"playerId"`
StartX float64 `json:"startX"` StartX float64 `json:"startX"`
@ -12,6 +13,8 @@ type MovePlayerPacket struct {
DestY float64 `json:"destY"` DestY float64 `json:"destY"`
} }
// CreateMovePlayerPacket returns a NetPacket which declares a MovePlayerPacket
// with the given ID and movement command.
func CreateMovePlayerPacket(playerId string, startX, startY, destX, destY float64) NetPacket { func CreateMovePlayerPacket(playerId string, startX, startY, destX, destY float64) NetPacket {
return NetPacket{ return NetPacket{
PacketType: d2netpackettype.MovePlayer, PacketType: d2netpackettype.MovePlayer,

View File

@ -1,14 +1,19 @@
package d2netpacket package d2netpacket
import ( import (
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
"time" "time"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
) )
// PingPacket contains the time at which it was sent. It is sent by the
// server and instructs the client to respond with a Pong packet.
type PingPacket struct { type PingPacket struct {
TS time.Time `json:"ts"` TS time.Time `json:"ts"`
} }
// CreatePingPacket returns a NetPacket which declares a GenerateMapPacket
// with the the current time.
func CreatePingPacket() NetPacket { func CreatePingPacket() NetPacket {
return NetPacket{ return NetPacket{
PacketType: d2netpackettype.Ping, PacketType: d2netpackettype.Ping,

View File

@ -2,8 +2,10 @@ package d2netpacket
import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
// CastPacket contains a cast command for an entity. It is sent by the server
// and instructs the client to trigger the use of the given skill on the given
// entity.
// TODO: Need to handle being on different maps // TODO: Need to handle being on different maps
type CastPacket struct { type CastPacket struct {
SourceEntityID string `json:"sourceEntityId"` SourceEntityID string `json:"sourceEntityId"`
SkillID int `json:"skillId"` SkillID int `json:"skillId"`
@ -12,6 +14,8 @@ type CastPacket struct {
TargetEntityID string `json:"targetEntityId"` TargetEntityID string `json:"targetEntityId"`
} }
// CreateCastPacket returns a NetPacket which declares a CastPacket with the
// given skill command.
func CreateCastPacket(entityID string, skillID int, targetX, targetY float64) NetPacket { func CreateCastPacket(entityID string, skillID int, targetX, targetY float64) NetPacket {
return NetPacket{ return NetPacket{
PacketType: d2netpackettype.CastSkill, PacketType: d2netpackettype.CastSkill,

View File

@ -5,11 +5,15 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
) )
// PlayerConnectionRequestPacket contains a player ID and game state.
// It is sent by a remote client to initiate a connection (join a game).
type PlayerConnectionRequestPacket struct { type PlayerConnectionRequestPacket struct {
Id string `json:"id"` Id string `json:"id"`
PlayerState *d2player.PlayerState `json:"gameState"` PlayerState *d2player.PlayerState `json:"gameState"`
} }
// CreatePlayerConnectionRequestPacket returns a NetPacket which defines a
// PlayerConnectionRequestPacket with the given ID and game state.
func CreatePlayerConnectionRequestPacket(id string, playerState *d2player.PlayerState) NetPacket { func CreatePlayerConnectionRequestPacket(id string, playerState *d2player.PlayerState) NetPacket {
return NetPacket{ return NetPacket{
PacketType: d2netpackettype.PlayerConnectionRequest, PacketType: d2netpackettype.PlayerConnectionRequest,

View File

@ -5,11 +5,15 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
) )
// PlayerDisconnectRequestPacket contains a player ID and game state.
// It is sent by a remote client to close the connection (leave a game).
type PlayerDisconnectRequestPacket struct { type PlayerDisconnectRequestPacket struct {
Id string `json:"id"` Id string `json:"id"`
PlayerState *d2player.PlayerState `json:"gameState"` PlayerState *d2player.PlayerState `json:"gameState"` // TODO: remove this? It isn't used.
} }
// CreatePlayerDisconnectRequestPacket returns a NetPacket which defines a
// PlayerDisconnectRequestPacket with the given ID.
func CreatePlayerDisconnectRequestPacket(id string) NetPacket { func CreatePlayerDisconnectRequestPacket(id string) NetPacket {
return NetPacket{ return NetPacket{
PacketType: d2netpackettype.PlayerDisconnectionNotification, PacketType: d2netpackettype.PlayerDisconnectionNotification,

View File

@ -1,15 +1,20 @@
package d2netpacket package d2netpacket
import ( import (
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
"time" "time"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
) )
// PongPacket contains the time at which it was sent and the ID of the
// client. It is sent by the client in response to a Pong packet.
type PongPacket struct { type PongPacket struct {
ID string `json:"id"` ID string `json:"id"`
TS time.Time `json:"ts"` TS time.Time `json:"ts"`
} }
// CreatePongPacket returns a NetPacket which declares a PongPacket with
// the current time and given ID.
func CreatePongPacket(id string) NetPacket { func CreatePongPacket(id string) NetPacket {
return NetPacket{ return NetPacket{
PacketType: d2netpackettype.Pong, PacketType: d2netpackettype.Pong,

View File

@ -1,14 +1,19 @@
package d2netpacket package d2netpacket
import ( import (
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
"time" "time"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
) )
// ServerClosedPacket contains the current time. It is sent by the server
// to inform a client that the server has shut down.
type ServerClosedPacket struct { type ServerClosedPacket struct {
TS time.Time `json:"ts"` TS time.Time `json:"ts"`
} }
// CreateServerClosedPacket returns a NetPacket which declares a
// ServerClosedPacket with the current time.
func CreateServerClosedPacket() NetPacket { func CreateServerClosedPacket() NetPacket {
return NetPacket{ return NetPacket{
PacketType: d2netpackettype.ServerClosed, PacketType: d2netpackettype.ServerClosed,

View File

@ -2,11 +2,15 @@ package d2netpacket
import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
// UpdateServerInfoPacket contains the ID for a player and the map seed.
// It is sent by the server to synchronise these values on the client.
type UpdateServerInfoPacket struct { type UpdateServerInfoPacket struct {
Seed int64 `json:"seed"` Seed int64 `json:"seed"`
PlayerId string `json:"playerId"` PlayerId string `json:"playerId"`
} }
// CreateUpdateServerInfoPacket returns a NetPacket which declares an
// UpdateServerInfoPacket with the given player ID and map seed.
func CreateUpdateServerInfoPacket(seed int64, playerId string) NetPacket { func CreateUpdateServerInfoPacket(seed int64, playerId string) NetPacket {
return NetPacket{ return NetPacket{
PacketType: d2netpackettype.UpdateServerInfo, PacketType: d2netpackettype.UpdateServerInfo,

View File

@ -6,6 +6,8 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
) )
// ClientConnection is an interface for abstracting local and remote
// clients.
type ClientConnection interface { type ClientConnection interface {
GetUniqueId() string GetUniqueId() string
GetConnectionType() d2clientconnectiontype.ClientConnectionType GetConnectionType() d2clientconnectiontype.ClientConnectionType

View File

@ -1,29 +1,29 @@
package d2server package d2server
import ( import (
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
"log" "log"
"sync" "sync"
"time" "time"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
) )
// ConnectionManager is responsible for cleanup up connections accepted by the game server. As the server communicates over // ConnectionManager is responsible for cleanup up connections accepted by the game server.
// UDP and is stateless we need to implement some loose state management via a ping/pong system. ConnectionManager also handles // As the server communicates over UDP and is stateless we need to implement some loose state
// communication for graceful shutdowns. // management via a ping/pong system. ConnectionManager also handles communication for
// // graceful shutdowns.
// retries: # of attempts before the dropping the client
// interval: How long to wait before each ping/pong test
// gameServer: The *GameServer is argument provided for the connection manager to watch over
// status: map of inflight ping/pong requests
type ConnectionManager struct { type ConnectionManager struct {
sync.RWMutex sync.RWMutex
retries int retries int // Number of attempts before the dropping the client
interval time.Duration interval time.Duration // How long to wait before each ping/pong test
gameServer *GameServer gameServer *GameServer // The GameServer with the connections being managed
status map[string]int status map[string]int // Map of inflight ping/pong requests
} }
// CreateConnectionManager constructs a new ConnectionManager and calls
// ConnectionManager.Run() in a goroutine before retuning a pointer to
// the new ConnectionManager.
func CreateConnectionManager(gameServer *GameServer) *ConnectionManager { func CreateConnectionManager(gameServer *GameServer) *ConnectionManager {
manager := &ConnectionManager{ manager := &ConnectionManager{
retries: 3, retries: 3,

View File

@ -1,24 +1,31 @@
// Package d2udpclientconnection provides an implementation of a UDP client connection with a game state.
package d2udpclientconnection package d2udpclientconnection
import ( import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"encoding/json" "encoding/json"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
"net" "net"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
) )
// UDPClientConnection is the implementation of the
// d2server.ClientConnection interface to represent remote client from the
// server perspective.
type UDPClientConnection struct { type UDPClientConnection struct {
id string id string // ID of the associated RemoteClientConnection
address *net.UDPAddr address *net.UDPAddr // IP address of the associated RemoteClientConnection
udpConnection *net.UDPConn udpConnection *net.UDPConn // Server's UDP Connection
playerState *d2player.PlayerState playerState *d2player.PlayerState // Client's game state
} }
// CreateUDPClientConnection constructs a new UDPClientConnection and
// returns a pointer to it.
func CreateUDPClientConnection(udpConnection *net.UDPConn, id string, address *net.UDPAddr) *UDPClientConnection { func CreateUDPClientConnection(udpConnection *net.UDPConn, id string, address *net.UDPAddr) *UDPClientConnection {
result := &UDPClientConnection{ result := &UDPClientConnection{
id: id, id: id,
@ -29,14 +36,19 @@ func CreateUDPClientConnection(udpConnection *net.UDPConn, id string, address *n
return result return result
} }
// GetUniqueId returns UDPClientConnection.id
func (u UDPClientConnection) GetUniqueId() string { func (u UDPClientConnection) GetUniqueId() string {
return u.id return u.id
} }
// GetConnectionType returns an enum representing the connection type.
// See: d2clientconnectiontype.
func (u UDPClientConnection) GetConnectionType() d2clientconnectiontype.ClientConnectionType { func (u UDPClientConnection) GetConnectionType() d2clientconnectiontype.ClientConnectionType {
return d2clientconnectiontype.LANClient return d2clientconnectiontype.LANClient
} }
// SendPacketToClient compresses the JSON encoding of a NetPacket and
// sends it to the client.
func (u *UDPClientConnection) SendPacketToClient(packet d2netpacket.NetPacket) error { func (u *UDPClientConnection) SendPacketToClient(packet d2netpacket.NetPacket) error {
data, err := json.Marshal(packet.PacketData) data, err := json.Marshal(packet.PacketData)
if err != nil { if err != nil {
@ -55,10 +67,12 @@ func (u *UDPClientConnection) SendPacketToClient(packet d2netpacket.NetPacket) e
return nil return nil
} }
// SetPlayerState sets UDP.playerState to the given value.
func (u *UDPClientConnection) SetPlayerState(playerState *d2player.PlayerState) { func (u *UDPClientConnection) SetPlayerState(playerState *d2player.PlayerState) {
u.playerState = playerState u.playerState = playerState
} }
// GetPlayerState returns UDPClientConnection.playerState.
func (u *UDPClientConnection) GetPlayerState() *d2player.PlayerState { func (u *UDPClientConnection) GetPlayerState() *d2player.PlayerState {
return u.playerState return u.playerState
} }

View File

@ -0,0 +1,5 @@
// Package d2server provides connection management and client synchronisation.
/*
Data is encoded to JSON and compressed using gzip. Transport is over UDP.
The server is authoritative for both local and remote clients.*/
package d2server

View File

@ -24,6 +24,9 @@ import (
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
) )
// GameServer owns the authoritative copy of the map and entities
// It accepts incoming connections from local (host) and remote
// clients.
type GameServer struct { type GameServer struct {
sync.RWMutex sync.RWMutex
clientConnections map[string]ClientConnection clientConnections map[string]ClientConnection
@ -37,6 +40,11 @@ type GameServer struct {
var singletonServer *GameServer var singletonServer *GameServer
// Create constructs a new GameServer and assigns it as a singleton. It
// also generates the initial map and entities for the server.
//
// If openNetworkServer is true, the GameServer starts listening for UDP
// packets.
func Create(openNetworkServer bool) { func Create(openNetworkServer bool) {
log.Print("Creating GameServer") log.Print("Creating GameServer")
if singletonServer != nil { if singletonServer != nil {
@ -84,6 +92,8 @@ func createNetworkServer() {
singletonServer.udpConnection.SetReadBuffer(4096) singletonServer.udpConnection.SetReadBuffer(4096)
} }
// runNetworkServer runs a while loop, reading from the GameServer's UDP
// connection.
func runNetworkServer() { func runNetworkServer() {
buffer := make([]byte, 4096) buffer := make([]byte, 4096)
for singletonServer.running { for singletonServer.running {
@ -131,6 +141,8 @@ func runNetworkServer() {
} }
} }
// Run sets GameServer.running to true and call runNetworkServer
// in a goroutine.
func Run() { func Run() {
log.Print("Starting GameServer") log.Print("Starting GameServer")
singletonServer.running = true singletonServer.running = true
@ -141,6 +153,8 @@ func Run() {
log.Print("Network server has been started") log.Print("Network server has been started")
} }
// Stop sets GameServer.running to false and closes the
// GameServer's UDP connection.
func Stop() { func Stop() {
log.Print("Stopping GameServer") log.Print("Stopping GameServer")
singletonServer.running = false singletonServer.running = false
@ -149,6 +163,7 @@ func Stop() {
} }
} }
// Destroy calls Stop() if the server exists.
func Destroy() { func Destroy() {
if singletonServer == nil { if singletonServer == nil {
return return
@ -157,6 +172,14 @@ func Destroy() {
Stop() Stop()
} }
// OnClientConnected initializes the given ClientConnection. It sends the
// following packets to the newly connected client: UpdateServerInfoPacket,
// GenerateMapPacket, AddPlayerPacket.
//
// It also sends AddPlayerPackets for each other player entity to the new
// player and vice versa, so all player entities exist on all clients.
//
// For more information, see d2networking.d2netpacket.
func OnClientConnected(client ClientConnection) { func OnClientConnected(client ClientConnection) {
// Temporary position hack -------------------------------------------- // Temporary position hack --------------------------------------------
sx, sy := singletonServer.mapEngines[0].GetStartPosition() // TODO: Another temporary hack sx, sy := singletonServer.mapEngines[0].GetStartPosition() // TODO: Another temporary hack
@ -186,11 +209,14 @@ func OnClientConnected(client ClientConnection) {
} }
// OnClientDisconnected removes the given client from the list
// of client connections.
func OnClientDisconnected(client ClientConnection) { func OnClientDisconnected(client ClientConnection) {
log.Printf("Client disconnected with an id of %s", client.GetUniqueId()) log.Printf("Client disconnected with an id of %s", client.GetUniqueId())
delete(singletonServer.clientConnections, client.GetUniqueId()) delete(singletonServer.clientConnections, client.GetUniqueId())
} }
// OnPacketReceived is called by the local client to 'send' a packet to the server.
func OnPacketReceived(client ClientConnection, packet d2netpacket.NetPacket) error { func OnPacketReceived(client ClientConnection, packet d2netpacket.NetPacket) error {
switch packet.PacketType { switch packet.PacketType {
case d2netpackettype.MovePlayer: case d2netpackettype.MovePlayer:

4
d2networking/doc.go Normal file
View File

@ -0,0 +1,4 @@
// Package d2networking provides client and server implementations for OpenDiablo2.
/*
The server is authoritative and communicates with local and remote clients over UDP.*/
package d2networking