1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-11-18 18:36:12 -05:00
OpenDiablo2/d2networking/d2server/game_server.go

453 lines
13 KiB
Go
Raw Normal View History

package d2server
import (
"context"
2020-06-18 14:11:04 -04:00
"encoding/json"
"errors"
"fmt"
"log"
2020-06-18 14:11:04 -04:00
"net"
"sync"
"time"
2020-09-12 16:25:09 -04:00
"github.com/robertkrimen/otto"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
remove d2asset singleton (#726) * export d2asset singleton * add *d2asset.AssetManager to d2app - d2app now has a reference to an asset manager which it will use for loading - added asset loader methods to the asset manager - functions in d2asset are now wrappers for asset manager methods * add asset manager reference to audio provider - d2app asset manager reference is now passed to audio provider - asset manager is created in main.go for now to pass into audio provider - CreateSoundEffect is now a method, no longer exported, uses the asset manager reference * d2app passes asset manager refence to map engine test * in d2asset, all calls to LoadFile replaced with call to Singleton.Loadfile * blizzard intro and credits screen - d2app passes reference to the asset manager to these screens * asset manager for d2map - adding MapStampFactory, takes an asset manager reference - embedded MapStampFactory into the MapEngine - LoadStamp is now a method of the MapStampFactory * d2asset: removed LoadFileStream, LoadFile, and FileExists * d2gui changes - singleton now has an asset manager reference - calls to d2asset loader functions removed - createButton is now a method of LayoutManager - moved LayoutEntry to its own file * map entity factory - Map engine has an embedded map entity factory - Map stamp factory gets a reference to the map engine's entity factory - Stamps are given a reference to the map engine entity factory when created - Character select gets a map entity factory - Embedded the stamp factory into the MapEngine * asset manager for d2ui - d2ui is passed an asset manager reference when created - all calls to d2asset loader functions in d2ui now refer to the asset manager - d2gamescreen gets a ui manager when created - help overlay is now passed a ui manager when created * d2gamescreen + d2player: asset manager references added an asset manager reference to - inventory panel + inventory grid - mini panel - game controls - help overlay - character select - main menu - select hero class - hero stats panel * Removed d2asset.LoadAnimation all references to this function have been replaced with calls to the asset manager method * adding asset to help overlay, bugfix for 4d59c91 * Removed d2asset.LoadFont and d2asset.LoadAnimationWithEffect all references to these have been replaced with calls to the asset manager methods * MapRenderer now gets an asset manager reference * removed d2asset.LoadPalette all references have been replaced with calls to an asset manager instance * merged d2object with d2mapentity d2object was only being used to create objects in the map, so the provider function is now a method of the map entity factory. calls to d2asset have been removed. * removed d2asset.LoadComposite all calls are now made to the asset manager method * removed d2asset singleton all singleton references have been removed, a single instance of the asset manager is passed around the entire app * rename Initialize to NewAssetManager
2020-09-12 16:51:30 -04:00
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
2020-09-12 16:25:09 -04:00
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2server/d2tcpclientconnection"
2020-06-18 14:11:04 -04:00
"github.com/OpenDiablo2/OpenDiablo2/d2script"
)
const (
2020-09-12 16:25:09 -04:00
port = "6669"
chunkSize int = 4096 // nolint:deadcode,unused,varcheck // WIP
subtilesPerTile = 5
middleOfTileOffset = 3
)
var (
2020-09-12 16:25:09 -04:00
errPlayerAlreadyExists = errors.New("player already exists")
errServerFull = errors.New("server full") // Server currently at maximum TCP connections
)
// GameServer manages a copy of the map and entities as well as manages packet routing and connections.
// It can accept connections from localhost as well remote clients. It can also be started in a standalone mode.
type GameServer struct {
sync.RWMutex
connections map[string]ClientConnection
listener net.Listener
networkServer bool
ctx context.Context
cancel context.CancelFunc
remove d2asset singleton (#726) * export d2asset singleton * add *d2asset.AssetManager to d2app - d2app now has a reference to an asset manager which it will use for loading - added asset loader methods to the asset manager - functions in d2asset are now wrappers for asset manager methods * add asset manager reference to audio provider - d2app asset manager reference is now passed to audio provider - asset manager is created in main.go for now to pass into audio provider - CreateSoundEffect is now a method, no longer exported, uses the asset manager reference * d2app passes asset manager refence to map engine test * in d2asset, all calls to LoadFile replaced with call to Singleton.Loadfile * blizzard intro and credits screen - d2app passes reference to the asset manager to these screens * asset manager for d2map - adding MapStampFactory, takes an asset manager reference - embedded MapStampFactory into the MapEngine - LoadStamp is now a method of the MapStampFactory * d2asset: removed LoadFileStream, LoadFile, and FileExists * d2gui changes - singleton now has an asset manager reference - calls to d2asset loader functions removed - createButton is now a method of LayoutManager - moved LayoutEntry to its own file * map entity factory - Map engine has an embedded map entity factory - Map stamp factory gets a reference to the map engine's entity factory - Stamps are given a reference to the map engine entity factory when created - Character select gets a map entity factory - Embedded the stamp factory into the MapEngine * asset manager for d2ui - d2ui is passed an asset manager reference when created - all calls to d2asset loader functions in d2ui now refer to the asset manager - d2gamescreen gets a ui manager when created - help overlay is now passed a ui manager when created * d2gamescreen + d2player: asset manager references added an asset manager reference to - inventory panel + inventory grid - mini panel - game controls - help overlay - character select - main menu - select hero class - hero stats panel * Removed d2asset.LoadAnimation all references to this function have been replaced with calls to the asset manager method * adding asset to help overlay, bugfix for 4d59c91 * Removed d2asset.LoadFont and d2asset.LoadAnimationWithEffect all references to these have been replaced with calls to the asset manager methods * MapRenderer now gets an asset manager reference * removed d2asset.LoadPalette all references have been replaced with calls to an asset manager instance * merged d2object with d2mapentity d2object was only being used to create objects in the map, so the provider function is now a method of the map entity factory. calls to d2asset have been removed. * removed d2asset.LoadComposite all calls are now made to the asset manager method * removed d2asset singleton all singleton references have been removed, a single instance of the asset manager is passed around the entire app * rename Initialize to NewAssetManager
2020-09-12 16:51:30 -04:00
asset *d2asset.AssetManager
mapEngines []*d2mapengine.MapEngine
2020-06-18 14:11:04 -04:00
scriptEngine *d2script.ScriptEngine
seed int64
maxConnections int
packetManagerChan chan []byte
heroStateFactory *d2hero.HeroStateFactory
}
// NewGameServer builds a new GameServer that can be started
//
// ctx: required context item
// networkServer: true = 0.0.0.0 | false = 127.0.0.1
// maxConnections (default: 8): maximum number of TCP connections allowed open
remove d2asset singleton (#726) * export d2asset singleton * add *d2asset.AssetManager to d2app - d2app now has a reference to an asset manager which it will use for loading - added asset loader methods to the asset manager - functions in d2asset are now wrappers for asset manager methods * add asset manager reference to audio provider - d2app asset manager reference is now passed to audio provider - asset manager is created in main.go for now to pass into audio provider - CreateSoundEffect is now a method, no longer exported, uses the asset manager reference * d2app passes asset manager refence to map engine test * in d2asset, all calls to LoadFile replaced with call to Singleton.Loadfile * blizzard intro and credits screen - d2app passes reference to the asset manager to these screens * asset manager for d2map - adding MapStampFactory, takes an asset manager reference - embedded MapStampFactory into the MapEngine - LoadStamp is now a method of the MapStampFactory * d2asset: removed LoadFileStream, LoadFile, and FileExists * d2gui changes - singleton now has an asset manager reference - calls to d2asset loader functions removed - createButton is now a method of LayoutManager - moved LayoutEntry to its own file * map entity factory - Map engine has an embedded map entity factory - Map stamp factory gets a reference to the map engine's entity factory - Stamps are given a reference to the map engine entity factory when created - Character select gets a map entity factory - Embedded the stamp factory into the MapEngine * asset manager for d2ui - d2ui is passed an asset manager reference when created - all calls to d2asset loader functions in d2ui now refer to the asset manager - d2gamescreen gets a ui manager when created - help overlay is now passed a ui manager when created * d2gamescreen + d2player: asset manager references added an asset manager reference to - inventory panel + inventory grid - mini panel - game controls - help overlay - character select - main menu - select hero class - hero stats panel * Removed d2asset.LoadAnimation all references to this function have been replaced with calls to the asset manager method * adding asset to help overlay, bugfix for 4d59c91 * Removed d2asset.LoadFont and d2asset.LoadAnimationWithEffect all references to these have been replaced with calls to the asset manager methods * MapRenderer now gets an asset manager reference * removed d2asset.LoadPalette all references have been replaced with calls to an asset manager instance * merged d2object with d2mapentity d2object was only being used to create objects in the map, so the provider function is now a method of the map entity factory. calls to d2asset have been removed. * removed d2asset.LoadComposite all calls are now made to the asset manager method * removed d2asset singleton all singleton references have been removed, a single instance of the asset manager is passed around the entire app * rename Initialize to NewAssetManager
2020-09-12 16:51:30 -04:00
func NewGameServer(asset *d2asset.AssetManager, networkServer bool,
maxConnections ...int) (*GameServer,
error) {
if len(maxConnections) == 0 {
maxConnections = []int{8}
}
heroStateFactory, err := d2hero.NewHeroStateFactory(asset)
if err != nil {
return nil, err
}
ctx, cancel := context.WithCancel(context.Background())
gameServer := &GameServer{
ctx: ctx,
cancel: cancel,
remove d2asset singleton (#726) * export d2asset singleton * add *d2asset.AssetManager to d2app - d2app now has a reference to an asset manager which it will use for loading - added asset loader methods to the asset manager - functions in d2asset are now wrappers for asset manager methods * add asset manager reference to audio provider - d2app asset manager reference is now passed to audio provider - asset manager is created in main.go for now to pass into audio provider - CreateSoundEffect is now a method, no longer exported, uses the asset manager reference * d2app passes asset manager refence to map engine test * in d2asset, all calls to LoadFile replaced with call to Singleton.Loadfile * blizzard intro and credits screen - d2app passes reference to the asset manager to these screens * asset manager for d2map - adding MapStampFactory, takes an asset manager reference - embedded MapStampFactory into the MapEngine - LoadStamp is now a method of the MapStampFactory * d2asset: removed LoadFileStream, LoadFile, and FileExists * d2gui changes - singleton now has an asset manager reference - calls to d2asset loader functions removed - createButton is now a method of LayoutManager - moved LayoutEntry to its own file * map entity factory - Map engine has an embedded map entity factory - Map stamp factory gets a reference to the map engine's entity factory - Stamps are given a reference to the map engine entity factory when created - Character select gets a map entity factory - Embedded the stamp factory into the MapEngine * asset manager for d2ui - d2ui is passed an asset manager reference when created - all calls to d2asset loader functions in d2ui now refer to the asset manager - d2gamescreen gets a ui manager when created - help overlay is now passed a ui manager when created * d2gamescreen + d2player: asset manager references added an asset manager reference to - inventory panel + inventory grid - mini panel - game controls - help overlay - character select - main menu - select hero class - hero stats panel * Removed d2asset.LoadAnimation all references to this function have been replaced with calls to the asset manager method * adding asset to help overlay, bugfix for 4d59c91 * Removed d2asset.LoadFont and d2asset.LoadAnimationWithEffect all references to these have been replaced with calls to the asset manager methods * MapRenderer now gets an asset manager reference * removed d2asset.LoadPalette all references have been replaced with calls to an asset manager instance * merged d2object with d2mapentity d2object was only being used to create objects in the map, so the provider function is now a method of the map entity factory. calls to d2asset have been removed. * removed d2asset.LoadComposite all calls are now made to the asset manager method * removed d2asset singleton all singleton references have been removed, a single instance of the asset manager is passed around the entire app * rename Initialize to NewAssetManager
2020-09-12 16:51:30 -04:00
asset: asset,
connections: make(map[string]ClientConnection),
networkServer: networkServer,
maxConnections: maxConnections[0],
packetManagerChan: make(chan []byte),
mapEngines: make([]*d2mapengine.MapEngine, 0),
scriptEngine: d2script.CreateScriptEngine(),
seed: time.Now().UnixNano(),
heroStateFactory: heroStateFactory,
}
remove d2asset singleton (#726) * export d2asset singleton * add *d2asset.AssetManager to d2app - d2app now has a reference to an asset manager which it will use for loading - added asset loader methods to the asset manager - functions in d2asset are now wrappers for asset manager methods * add asset manager reference to audio provider - d2app asset manager reference is now passed to audio provider - asset manager is created in main.go for now to pass into audio provider - CreateSoundEffect is now a method, no longer exported, uses the asset manager reference * d2app passes asset manager refence to map engine test * in d2asset, all calls to LoadFile replaced with call to Singleton.Loadfile * blizzard intro and credits screen - d2app passes reference to the asset manager to these screens * asset manager for d2map - adding MapStampFactory, takes an asset manager reference - embedded MapStampFactory into the MapEngine - LoadStamp is now a method of the MapStampFactory * d2asset: removed LoadFileStream, LoadFile, and FileExists * d2gui changes - singleton now has an asset manager reference - calls to d2asset loader functions removed - createButton is now a method of LayoutManager - moved LayoutEntry to its own file * map entity factory - Map engine has an embedded map entity factory - Map stamp factory gets a reference to the map engine's entity factory - Stamps are given a reference to the map engine entity factory when created - Character select gets a map entity factory - Embedded the stamp factory into the MapEngine * asset manager for d2ui - d2ui is passed an asset manager reference when created - all calls to d2asset loader functions in d2ui now refer to the asset manager - d2gamescreen gets a ui manager when created - help overlay is now passed a ui manager when created * d2gamescreen + d2player: asset manager references added an asset manager reference to - inventory panel + inventory grid - mini panel - game controls - help overlay - character select - main menu - select hero class - hero stats panel * Removed d2asset.LoadAnimation all references to this function have been replaced with calls to the asset manager method * adding asset to help overlay, bugfix for 4d59c91 * Removed d2asset.LoadFont and d2asset.LoadAnimationWithEffect all references to these have been replaced with calls to the asset manager methods * MapRenderer now gets an asset manager reference * removed d2asset.LoadPalette all references have been replaced with calls to an asset manager instance * merged d2object with d2mapentity d2object was only being used to create objects in the map, so the provider function is now a method of the map entity factory. calls to d2asset have been removed. * removed d2asset.LoadComposite all calls are now made to the asset manager method * removed d2asset singleton all singleton references have been removed, a single instance of the asset manager is passed around the entire app * rename Initialize to NewAssetManager
2020-09-12 16:51:30 -04:00
mapEngine := d2mapengine.CreateMapEngine(asset)
mapEngine.SetSeed(gameServer.seed)
mapEngine.ResetMap(d2enum.RegionAct1Town, 100, 100)
Removing d2datadict singletons (#738) * Remove weapons, armor, misc, itemCommon, itemTyps datadict singletons - removed loader calls from d2app - removed the HeroObjects singleton from `d2core/d2inventory` - added an InventoryItemFactory in d2inventory - package-level functions that use data records are now methods of the InventoryItemFactory - renamed ItemGenerator in d2item to ItemFactory - package-level functions that use records are now methods of ItemFactory - d2map.MapEntityFactory now has an item factory instance for creating items - fixed a bug in unique item record loader where it loaded an empty record - added a PlayerStateFactory for creating a player state (uses the asset manager) - updated the test inventory/equipment code in d2player to handle errors from the ItemFactory - character select and character creation screens have a player state and inventory item factory - updated item tests to use the item factory * minor edit * Removed d2datadict.Experience singleton added a HeroStatsFactory, much like the other factories. The factory gets an asset manager reference in order to use data records. * removed d2datadict.AutoMagic singleton * removed d2datadict.AutoMap singleton * removed d2datadict.BodyLocations singleton * removed d2datadict.Books singleton * Removed singletons for level records - removed loader calls in d2app - changed type references from d2datadict to d2records - added a `MapGenerator` in d2mapgen which uses thew asset manager and map engine - package-level map generation functions are now MapGenerator methods - `d2datadict.GetLevelDetails(id int)` is now a method of the RecordManager * remove SkillCalc and MissileCalc singletons * Removed CharStats and ItemStatCost singletons - added an ItemStatFactory which uses the asset manager to create stats - package-level functions for stats in d2item are now StatFactory methods - changed type references from d2datadict to d2records - `d2player.GetAllPlayerStates` is now a method of the `PlayerStateFactory` * Removed DkillDesc and Skills singletons from d2datadict - removed loader calls from d2app - diablo2stats.Stat instances are given a reference to the factory for doing record lookups * update the stats test to use mock a asset manager and stat factory * fixed diablo2stats tests and diablo2item tests * removed CompCodes singleton from d2datadict * remove cubemain singleton from d2datadict * removed DifficultyLevels singleton from d2datadict * removed ElemTypes singleton from d2datadict * removed events.go loader from d2datadict (was unused) * removed Gems singleton from d2datadict * removed Hireling and Inventory singletons from d2datadict * removed MagicPrefix and MagicSuffix singletons from d2datadict * removed ItemRatios singleton from d2datadict * removed Missiles singleton from d2datadict * removed MonModes singleton * Removed all monster and npc singletons from d2datadict - MapStamp instances now get a reference to their factory for doing record lookups * removed SoundEntry and SoundEnviron singletons from d2datadict
2020-09-20 17:52:01 -04:00
mapGen, err := d2mapgen.NewMapGenerator(asset, mapEngine)
if err != nil {
return nil, err
}
mapGen.GenerateAct1Overworld()
2020-06-18 14:11:04 -04:00
gameServer.mapEngines = append(gameServer.mapEngines, mapEngine)
gameServer.scriptEngine.AddFunction("getMapEngines", func(call otto.FunctionCall) otto.Value {
val, err := gameServer.scriptEngine.ToValue(gameServer.mapEngines)
if err != nil {
fmt.Print(err.Error())
}
return val
})
2020-06-18 14:11:04 -04:00
return gameServer, nil
2020-06-18 14:11:04 -04:00
}
// Start essentially starts all of the game server go routines as well as begins listening for connection. This will
// return an error if it is unable to bind to a socket.
func (g *GameServer) Start() error {
2020-09-12 16:25:09 -04:00
listenerAddress := "127.0.0.1:" + port
if g.networkServer {
2020-09-12 16:25:09 -04:00
listenerAddress = "0.0.0.0:" + port
}
2020-06-18 14:11:04 -04:00
log.Printf("Starting Game Server @ %s\n", listenerAddress)
2020-09-12 16:25:09 -04:00
l, err := net.Listen("tcp4", listenerAddress)
if err != nil {
return err
2020-06-18 14:11:04 -04:00
}
g.listener = l
go g.packetManager()
go func() {
for {
c, err := g.listener.Accept()
if err != nil {
log.Printf("Unable to accept connection: %s\n", err)
2020-09-06 01:17:33 -04:00
return
}
go g.handleConnection(c)
}
}()
return nil
}
2020-09-12 16:25:09 -04:00
// Stop stops the game server
func (g *GameServer) Stop() {
g.Lock()
g.cancel()
2020-09-12 16:25:09 -04:00
if err := g.listener.Close(); err != nil {
log.Printf("failed to close the listener %s, err: %v\n", g.listener.Addr(), err)
}
}
// packetManager is meant to be started as a Goroutine and is used to manage routing of packets to clients.
func (g *GameServer) packetManager() {
defer close(g.packetManagerChan)
for {
select {
// If the server is stopped we need to clean up the packet manager goroutine
case <-g.ctx.Done():
return
case p := <-g.packetManagerChan:
switch d2netpacket.InspectPacketType(p) {
case d2netpackettype.PlayerConnectionRequest:
player, err := d2netpacket.UnmarshalNetPacket(p)
if err != nil {
log.Printf("Unable to unmarshal PlayerConnectionRequestPacket: %s\n", err)
}
2020-09-12 16:25:09 -04:00
g.sendPacketToClients(player)
case d2netpackettype.MovePlayer:
move, err := d2netpacket.UnmarshalNetPacket(p)
if err != nil {
log.Println(err)
continue
}
2020-09-12 16:25:09 -04:00
g.sendPacketToClients(move)
case d2netpackettype.CastSkill:
castSkill, err := d2netpacket.UnmarshalNetPacket(p)
if err != nil {
log.Println(err)
continue
}
g.sendPacketToClients(castSkill)
case d2netpackettype.SpawnItem:
item, err := d2netpacket.UnmarshalNetPacket(p)
if err != nil {
log.Println(err)
continue
}
2020-09-12 16:25:09 -04:00
g.sendPacketToClients(item)
case d2netpackettype.ServerClosed:
g.Stop()
}
}
2020-06-18 14:11:04 -04:00
}
}
func (g *GameServer) sendPacketToClients(packet d2netpacket.NetPacket) {
for _, c := range g.connections {
2020-09-12 16:25:09 -04:00
if err := c.SendPacketToClient(packet); err != nil {
log.Printf("GameServer: error sending packet: %s to client %s: %s", packet.PacketType, c.GetUniqueID(), err)
}
}
}
// handleConnection accepts an individual connection and starts pooling for new packets. It is recommended this is called
// via Go Routine. Context should be a property of the GameServer Struct.
func (g *GameServer) handleConnection(conn net.Conn) {
var connected int
2020-09-12 16:25:09 -04:00
var packet d2netpacket.NetPacket
2020-09-12 16:25:09 -04:00
log.Printf("Accepting connection: %s\n", conn.RemoteAddr().String())
defer func() {
if err := conn.Close(); err != nil {
log.Printf("failed to close the connection: %s\n", conn.RemoteAddr())
}
}()
decoder := json.NewDecoder(conn)
for {
err := decoder.Decode(&packet)
if err != nil {
log.Println(err)
return // exit this connection as we could not read the first packet
}
// If this is the first packet we are seeing from this specific connection we first need to see if the client
// is sending a valid request. If this is a valid request, we will register it and flip the connected switch
// to.
if connected == 0 {
if packet.PacketType != d2netpackettype.PlayerConnectionRequest {
log.Printf("Closing connection with %s: did not receive new player connection request...\n", conn.RemoteAddr().String())
}
if err := g.registerConnection(packet.PacketData, conn); err != nil {
switch err {
2020-09-12 16:25:09 -04:00
case errServerFull: // Server is currently full and not accepting new connections.
_, errServerFullPacket := conn.Write(d2netpacket.MarshalPacket(d2netpacket.CreateServerFullPacket()))
log.Println(errServerFullPacket)
2020-09-12 16:25:09 -04:00
case errPlayerAlreadyExists: // Player is already registered and did not disconnection correctly.
log.Println(err)
}
return
}
connected = 1
}
select {
case <-g.ctx.Done():
return
default:
g.packetManagerChan <- packet.PacketData
}
}
}
// registerConnection accepts a PlayerConnectionRequestPacket and thread safely updates the connection pool
//
// Errors:
2020-09-12 16:25:09 -04:00
// - errServerFull
// - errPlayerAlreadyExists
func (g *GameServer) registerConnection(b []byte, conn net.Conn) error {
g.Lock()
// check to see if the server is full
if len(g.connections) >= g.maxConnections {
2020-09-12 16:25:09 -04:00
return errServerFull
}
// if it is not full, unmarshal the playerConnectionRequest
packet, err := d2netpacket.UnmarshalPlayerConnectionRequest(b)
if err != nil {
log.Printf("Failed to unmarshal PlayerConnectionRequest: %s\n", err)
}
// check to see if the player is already registered
if _, ok := g.connections[packet.ID]; ok {
2020-09-12 16:25:09 -04:00
return errPlayerAlreadyExists
}
// Client a new TCP Client Connection and add it to the connections map
client := d2tcpclientconnection.CreateTCPClientConnection(conn, packet.ID)
client.SetPlayerState(packet.PlayerState)
log.Printf("Client connected with an id of %s", client.GetUniqueID())
g.connections[client.GetUniqueID()] = client
// Temporary position hack --------------------------------------------
// https://github.com/OpenDiablo2/OpenDiablo2/issues/829
sx, sy := g.mapEngines[0].GetStartPosition()
clientPlayerState := client.GetPlayerState()
clientPlayerState.X = sx
clientPlayerState.Y = sy
// ---------
// This really should be deferred however to much time will be spend holding a lock when we attempt to send a packet
g.Unlock()
g.handleClientConnection(client, sx, sy)
return nil
}
// 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 (g *GameServer) OnClientConnected(client ClientConnection) {
// Temporary position hack --------------------------------------------
// https://github.com/OpenDiablo2/OpenDiablo2/issues/829
sx, sy := g.mapEngines[0].GetStartPosition()
clientPlayerState := client.GetPlayerState()
clientPlayerState.X = sx
clientPlayerState.Y = sy
// --------------------------------------------------------------------
log.Printf("Client connected with an id of %s", client.GetUniqueID())
g.connections[client.GetUniqueID()] = client
g.handleClientConnection(client, sx, sy)
2020-09-12 16:25:09 -04:00
}
func (g *GameServer) handleClientConnection(client ClientConnection, x, y float64) {
err := client.SendPacketToClient(d2netpacket.CreateUpdateServerInfoPacket(g.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()
// these are in subtiles
2020-09-12 16:25:09 -04:00
playerX := int(x*subtilesPerTile) + middleOfTileOffset
playerY := int(y*subtilesPerTile) + middleOfTileOffset
d2hero.HydrateSkills(playerState.Skills, g.asset)
2020-09-12 16:25:09 -04:00
createPlayerPacket := d2netpacket.CreateAddPlayerPacket(
client.GetUniqueID(),
playerState.HeroName,
playerX,
playerY,
playerState.HeroType,
playerState.Stats,
playerState.Skills,
2020-09-12 16:25:09 -04:00
playerState.Equipment,
playerState.LeftSkill,
playerState.RightSkill,
2020-09-12 16:25:09 -04:00
)
for _, connection := range g.connections {
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()
playerX := int(conPlayerState.X*subtilesPerTile) + middleOfTileOffset
playerY := int(conPlayerState.Y*subtilesPerTile) + middleOfTileOffset
2020-09-12 16:25:09 -04:00
err = client.SendPacketToClient(
d2netpacket.CreateAddPlayerPacket(
connection.GetUniqueID(),
conPlayerState.HeroName,
playerX,
playerY,
conPlayerState.HeroType,
conPlayerState.Stats,
conPlayerState.Skills,
2020-09-12 16:25:09 -04:00
conPlayerState.Equipment,
conPlayerState.LeftSkill,
conPlayerState.RightSkill,
2020-09-12 16:25:09 -04:00
),
)
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 (g *GameServer) OnClientDisconnected(client ClientConnection) {
log.Printf("Client disconnected with an id of %s", client.GetUniqueID())
delete(g.connections, client.GetUniqueID())
}
// OnPacketReceived is called by the local client to 'send' a packet to the server.
// nolint:gocyclo // switch statement on packet type makes sense, no need to change
func (g *GameServer) OnPacketReceived(client ClientConnection, packet d2netpacket.NetPacket) error {
if g == nil {
return errors.New("game server is nil")
}
switch packet.PacketType {
case d2netpackettype.MovePlayer:
movePacket, err := d2netpacket.UnmarshalMovePlayer(packet.PacketData)
if err != nil {
return err
}
playerState := g.connections[client.GetUniqueID()].GetPlayerState()
playerState.X = movePacket.DestX
playerState.Y = movePacket.DestY
g.sendPacketToClients(packet)
case d2netpackettype.CastSkill, d2netpackettype.SpawnItem:
g.sendPacketToClients(packet)
case d2netpackettype.SavePlayer:
savePacket, err := d2netpacket.UnmarshalSavePlayer(packet.PacketData)
if err != nil {
return err
}
playerState := g.connections[client.GetUniqueID()].GetPlayerState()
playerState.LeftSkill = savePacket.LeftSkill
playerState.RightSkill = savePacket.RightSkill
err = g.heroStateFactory.Save(playerState)
if err != nil {
log.Printf("GameServer: error saving saving Player: %s", err)
}
default:
log.Printf("GameServer: received unknown packet %T", packet)
}
return nil
}