1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-09 09:20:44 +00:00
OpenDiablo2/d2networking/d2server/game_server.go
danhale-git 6367929688
Tidy remote_client_connection.go and resolve lint warnings (#522)
* Added automap.go stub

* Fixed bad error handling

* Handle header error from io.Copy()

* d2client.ClientConnection renamed to ServerConnection

Having two interfaces named ClientConnection in adjacent packages was a point of confusion for me. Renamed the client ClientConnection to ServerConnection since clients connect to servers and servers to clients.

* Fix lint warnings in remote_client_connection.go

* Tidying and lint warnings in remote_client_connection.go

* Switch statement in remote_client_connection.go now has matching blocks.

* RemoteClientConnection.serverListener refactor.

The switch statement is still repetitive but now it's separate. I think we would need generics to make it smaller.

* Receiver name change from l to r.

* Comments and other adjustments.

* Old commits

* Fixed bad error handling

* Cleaned up remote_client_connection.go

* SendPacketToClient removed.
2020-07-02 16:29:28 -04:00

299 lines
10 KiB
Go

package d2server
import (
"bytes"
"compress/gzip"
"encoding/json"
"fmt"
"io"
"log"
"net"
"strings"
"sync"
"time"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2server/d2udpclientconnection"
"github.com/OpenDiablo2/OpenDiablo2/d2script"
"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 {
sync.RWMutex
clientConnections map[string]ClientConnection
manager *ConnectionManager
mapEngines []*d2mapengine.MapEngine
scriptEngine *d2script.ScriptEngine
udpConnection *net.UDPConn
seed int64
running bool
}
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) {
log.Print("Creating GameServer")
if singletonServer != nil {
return
}
singletonServer = &GameServer{
clientConnections: make(map[string]ClientConnection),
mapEngines: make([]*d2mapengine.MapEngine, 0),
scriptEngine: d2script.CreateScriptEngine(),
seed: time.Now().UnixNano(),
}
singletonServer.manager = CreateConnectionManager(singletonServer)
mapEngine := d2mapengine.CreateMapEngine()
mapEngine.SetSeed(singletonServer.seed)
mapEngine.ResetMap(d2enum.RegionAct1Town, 100, 100) // TODO: Mapgen - Needs levels.txt stuff
d2mapgen.GenerateAct1Overworld(mapEngine)
singletonServer.mapEngines = append(singletonServer.mapEngines, mapEngine)
singletonServer.scriptEngine.AddFunction("getMapEngines", func(call otto.FunctionCall) otto.Value {
val, err := singletonServer.scriptEngine.ToValue(singletonServer.mapEngines)
if err != nil {
fmt.Print(err.Error())
}
return val
})
if openNetworkServer {
createNetworkServer()
}
}
func createNetworkServer() {
s, err := net.ResolveUDPAddr("udp4", "0.0.0.0:6669")
if err != nil {
panic(err)
}
singletonServer.udpConnection, err = net.ListenUDP("udp4", s)
if err != nil {
panic(err)
}
err = singletonServer.udpConnection.SetReadBuffer(4096)
if err != nil {
log.Print("GameServer: error setting UDP read buffer:", err)
}
}
// runNetworkServer runs a while loop, reading from the GameServer's UDP
// connection.
func runNetworkServer() {
buffer := make([]byte, 4096)
for singletonServer.running {
_, addr, err := singletonServer.udpConnection.ReadFromUDP(buffer)
if err != nil {
fmt.Printf("Socket error: %s\n", err)
continue
}
buff := bytes.NewBuffer(buffer)
packetTypeId, err := buff.ReadByte()
packetType := d2netpackettype.NetPacketType(packetTypeId)
reader, err := gzip.NewReader(buff)
sb := new(strings.Builder)
// This will throw errors where packets are not compressed. This doesn't
// break anything, so the gzip.ErrHeader error, is currently ignored to
// avoid noisy logging.
written, err := io.Copy(sb, reader)
if err != nil && err != gzip.ErrHeader {
log.Printf("GameServer: error copying bytes from %v packet: %s", packetType, err)
}
if written == 0 {
log.Printf("GameServer: empty packet %v packet received", packetType)
continue
}
stringData := sb.String()
switch packetType {
case d2netpackettype.PlayerConnectionRequest:
packetData := d2netpacket.PlayerConnectionRequestPacket{}
err := json.Unmarshal([]byte(stringData), &packetData)
if err != nil {
log.Printf("GameServer: error unmarshalling packet of type %T: %s", packetData, err)
continue
}
clientConnection := d2udpclientconnection.CreateUDPClientConnection(singletonServer.udpConnection, packetData.Id, addr)
clientConnection.SetPlayerState(packetData.PlayerState)
OnClientConnected(clientConnection)
case d2netpackettype.MovePlayer:
packetData := d2netpacket.MovePlayerPacket{}
err := json.Unmarshal([]byte(stringData), &packetData)
if err != nil {
log.Printf("GameServer: error unmarshalling %T: %s", packetData, err)
continue
}
netPacket := d2netpacket.NetPacket{
PacketType: packetType,
PacketData: packetData,
}
for _, player := range singletonServer.clientConnections {
err = player.SendPacketToClient(netPacket)
if err != nil {
log.Printf("GameServer: error sending %T to client %s: %s", packetData, player.GetUniqueId(), err)
}
}
case d2netpackettype.Pong:
packetData := d2netpacket.PlayerConnectionRequestPacket{}
err := json.Unmarshal([]byte(stringData), &packetData)
if err != nil {
log.Printf("GameServer: error unmarshalling packet of type %T: %s", packetData, err)
continue
}
singletonServer.manager.Recv(packetData.Id)
case d2netpackettype.ServerClosed:
singletonServer.manager.Shutdown()
case d2netpackettype.PlayerDisconnectionNotification:
var packet d2netpacket.PlayerDisconnectRequestPacket
err := json.Unmarshal([]byte(stringData), &packet)
if err != nil {
log.Printf("GameServer: error unmarshalling packet of type %T: %s", packet, err)
continue
}
log.Printf("Received disconnect: %s", packet.Id)
}
}
}
// Run sets GameServer.running to true and call runNetworkServer
// in a goroutine.
func Run() {
log.Print("Starting GameServer")
singletonServer.running = true
_, err := singletonServer.scriptEngine.RunScript("scripts/server/server.js")
if err != nil {
log.Printf("GameServer: error initializing debug script: %s", err)
}
if singletonServer.udpConnection != nil {
go runNetworkServer()
}
log.Print("Network server has been started")
}
// Stop sets GameServer.running to false and closes the
// GameServer's UDP connection.
func Stop() {
log.Print("Stopping GameServer")
singletonServer.running = false
if singletonServer.udpConnection != nil {
err := singletonServer.udpConnection.Close()
if err != nil {
log.Printf("GameServer: error when trying to close UDP connection: %s", err)
}
}
}
// Destroy calls Stop() if the server exists.
func Destroy() {
if singletonServer == nil {
return
}
log.Print("Destroying GameServer")
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) {
// Temporary position hack --------------------------------------------
sx, sy := singletonServer.mapEngines[0].GetStartPosition() // TODO: Another temporary hack
clientPlayerState := client.GetPlayerState()
clientPlayerState.X = sx
clientPlayerState.Y = sy
// --------------------------------------------------------------------
log.Printf("Client connected with an id of %s", client.GetUniqueId())
singletonServer.clientConnections[client.GetUniqueId()] = client
err := client.SendPacketToClient(d2netpacket.CreateUpdateServerInfoPacket(singletonServer.seed, client.GetUniqueId()))
if err != nil {
log.Printf("GameServer: error sending UpdateServerInfoPacket to client %s: %s", client.GetUniqueId(), err)
}
err = client.SendPacketToClient(d2netpacket.CreateGenerateMapPacket(d2enum.RegionAct1Town))
if err != nil {
log.Printf("GameServer: error sending GenerateMapPacket to client %s: %s", client.GetUniqueId(), err)
}
playerState := client.GetPlayerState()
createPlayerPacket := d2netpacket.CreateAddPlayerPacket(client.GetUniqueId(), playerState.HeroName, int(sx*5)+3, int(sy*5)+3,
playerState.HeroType, *playerState.Stats, playerState.Equipment)
for _, connection := range singletonServer.clientConnections {
err := connection.SendPacketToClient(createPlayerPacket)
if err != nil {
log.Printf("GameServer: error sending %T to client %s: %s", createPlayerPacket, connection.GetUniqueId(), err)
}
if connection.GetUniqueId() == client.GetUniqueId() {
continue
}
conPlayerState := connection.GetPlayerState()
err = client.SendPacketToClient(d2netpacket.CreateAddPlayerPacket(connection.GetUniqueId(), conPlayerState.HeroName,
int(conPlayerState.X*5)+3, int(conPlayerState.Y*5)+3, conPlayerState.HeroType, *conPlayerState.Stats, conPlayerState.Equipment))
if err != nil {
log.Printf("GameServer: error sending CreateAddPlayerPacket to client %s: %s", connection.GetUniqueId(), err)
}
}
}
// OnClientDisconnected removes the given client from the list
// of client connections.
func OnClientDisconnected(client ClientConnection) {
log.Printf("Client disconnected with an id of %s", 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 {
switch packet.PacketType {
case d2netpackettype.MovePlayer:
// TODO: This needs to be verified on the server (here) before sending to other clients....
// TODO: Hacky, this should be updated in realtime ----------------
// TODO: Verify player id
playerState := singletonServer.clientConnections[client.GetUniqueId()].GetPlayerState()
playerState.X = packet.PacketData.(d2netpacket.MovePlayerPacket).DestX
playerState.Y = packet.PacketData.(d2netpacket.MovePlayerPacket).DestY
// ----------------------------------------------------------------
for _, player := range singletonServer.clientConnections {
err := player.SendPacketToClient(packet)
if err != nil {
log.Printf("GameServer: error sending %T to client %s: %s", packet, player.GetUniqueId(), err)
}
}
case d2netpackettype.CastSkill:
for _, player := range singletonServer.clientConnections {
err := player.SendPacketToClient(packet)
if err != nil {
log.Printf("GameServer: error sending %T to client %s: %s", packet, player.GetUniqueId(), err)
}
}
}
return nil
}