2020-06-13 18:32:09 -04:00
|
|
|
package d2client
|
|
|
|
|
|
|
|
import (
|
2020-06-26 17:12:19 -04:00
|
|
|
"fmt"
|
2020-06-13 18:32:09 -04:00
|
|
|
"log"
|
2020-06-22 20:31:42 -04:00
|
|
|
"os"
|
2020-06-13 18:32:09 -04:00
|
|
|
|
2020-06-26 20:03:00 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
2020-07-11 11:24:04 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
2020-07-21 08:50:45 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
2020-06-21 18:40:37 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
|
2020-07-11 11:24:04 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen"
|
2020-06-21 18:40:37 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
|
2020-06-26 17:12:19 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2localclient"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2remoteclient"
|
2020-06-13 18:32:09 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
|
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
|
2020-07-11 11:24:04 -04:00
|
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2script"
|
2020-06-13 18:32:09 -04:00
|
|
|
)
|
|
|
|
|
2020-07-17 22:11:16 -04:00
|
|
|
const (
|
|
|
|
numSubtilesPerTile = 5
|
|
|
|
)
|
|
|
|
|
2020-06-29 17:01:26 -04:00
|
|
|
// GameClient manages a connection to d2server.GameServer
|
2020-07-17 22:11:16 -04:00
|
|
|
// and keeps a synchronized copy of the map and entities.
|
2020-06-13 18:32:09 -04:00
|
|
|
type GameClient struct {
|
2020-07-02 16:29:28 -04:00
|
|
|
clientConnection ServerConnection // Abstract local/remote connection
|
2020-06-29 17:01:26 -04:00
|
|
|
connectionType d2clientconnectiontype.ClientConnectionType // Type of connection (local or remote)
|
2020-07-11 11:24:04 -04:00
|
|
|
scriptEngine *d2script.ScriptEngine
|
|
|
|
GameState *d2player.PlayerState // local player state
|
|
|
|
MapEngine *d2mapengine.MapEngine // Map and entities
|
2020-07-17 22:11:16 -04:00
|
|
|
PlayerID string // ID of the local player
|
2020-07-11 11:24:04 -04:00
|
|
|
Players map[string]*d2mapentity.Player // IDs of the other players
|
|
|
|
Seed int64 // Map seed
|
|
|
|
RegenMap bool // Regenerate tile cache on render (map has changed)
|
2020-06-13 18:32:09 -04:00
|
|
|
}
|
|
|
|
|
2020-06-29 17:01:26 -04:00
|
|
|
// Create constructs a new GameClient and returns a pointer to it.
|
2020-07-11 11:24:04 -04:00
|
|
|
func Create(connectionType d2clientconnectiontype.ClientConnectionType, scriptEngine *d2script.ScriptEngine) (*GameClient, error) {
|
2020-06-26 17:12:19 -04:00
|
|
|
result := &GameClient{
|
|
|
|
MapEngine: d2mapengine.CreateMapEngine(), // TODO: Mapgen - Needs levels.txt stuff
|
|
|
|
Players: make(map[string]*d2mapentity.Player),
|
|
|
|
connectionType: connectionType,
|
2020-07-11 11:24:04 -04:00
|
|
|
scriptEngine: scriptEngine,
|
2020-06-26 17:12:19 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
switch connectionType {
|
|
|
|
case d2clientconnectiontype.LANClient:
|
|
|
|
result.clientConnection = d2remoteclient.Create()
|
|
|
|
case d2clientconnectiontype.LANServer:
|
|
|
|
result.clientConnection = d2localclient.Create(true)
|
|
|
|
case d2clientconnectiontype.Local:
|
|
|
|
result.clientConnection = d2localclient.Create(false)
|
|
|
|
default:
|
|
|
|
return nil, fmt.Errorf("unknown client connection type specified: %d", connectionType)
|
|
|
|
}
|
2020-07-17 22:11:16 -04:00
|
|
|
|
2020-06-26 17:12:19 -04:00
|
|
|
result.clientConnection.SetClientListener(result)
|
2020-07-17 22:11:16 -04:00
|
|
|
|
2020-06-26 17:12:19 -04:00
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2020-06-29 17:01:26 -04:00
|
|
|
// 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).
|
2020-07-17 22:11:16 -04:00
|
|
|
func (g *GameClient) Open(connectionString, saveFilePath string) error {
|
2020-07-11 11:24:04 -04:00
|
|
|
switch g.connectionType {
|
|
|
|
case d2clientconnectiontype.LANServer, d2clientconnectiontype.Local:
|
|
|
|
g.scriptEngine.AllowEval()
|
|
|
|
}
|
2020-07-17 22:11:16 -04:00
|
|
|
|
2020-06-18 14:11:04 -04:00
|
|
|
return g.clientConnection.Open(connectionString, saveFilePath)
|
2020-06-13 18:32:09 -04:00
|
|
|
}
|
|
|
|
|
2020-06-29 17:01:26 -04:00
|
|
|
// Close destroys the server if the client is local. For remote clients
|
|
|
|
// it sends a DisconnectRequestPacket (see d2netpacket).
|
2020-06-13 18:32:09 -04:00
|
|
|
func (g *GameClient) Close() error {
|
2020-07-11 11:24:04 -04:00
|
|
|
switch g.connectionType {
|
|
|
|
case d2clientconnectiontype.LANServer, d2clientconnectiontype.Local:
|
|
|
|
g.scriptEngine.DisallowEval()
|
|
|
|
}
|
2020-07-17 22:11:16 -04:00
|
|
|
|
2020-06-13 18:32:09 -04:00
|
|
|
return g.clientConnection.Close()
|
|
|
|
}
|
|
|
|
|
2020-06-29 17:01:26 -04:00
|
|
|
// Destroy does the same thing as Close.
|
2020-06-13 18:32:09 -04:00
|
|
|
func (g *GameClient) Destroy() error {
|
2020-07-11 11:24:04 -04:00
|
|
|
return g.Close()
|
2020-06-13 18:32:09 -04:00
|
|
|
}
|
|
|
|
|
2020-06-29 17:01:26 -04:00
|
|
|
// OnPacketReceived is called by the ClientConection and processes incoming
|
|
|
|
// packets.
|
2020-06-13 18:32:09 -04:00
|
|
|
func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
|
|
|
|
switch packet.PacketType {
|
2020-06-26 17:12:19 -04:00
|
|
|
case d2netpackettype.GenerateMap:
|
2020-07-17 22:11:16 -04:00
|
|
|
if err := g.handleGenerateMapPacket(packet); err != nil {
|
|
|
|
return err
|
2020-06-26 17:12:19 -04:00
|
|
|
}
|
2020-06-13 18:32:09 -04:00
|
|
|
case d2netpackettype.UpdateServerInfo:
|
2020-07-17 22:11:16 -04:00
|
|
|
if err := g.handleUpdateServerInfoPacket(packet); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-06-13 18:32:09 -04:00
|
|
|
case d2netpackettype.AddPlayer:
|
2020-07-17 22:11:16 -04:00
|
|
|
if err := g.handleAddPlayerPacket(packet); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-06-13 18:32:09 -04:00
|
|
|
case d2netpackettype.MovePlayer:
|
2020-07-17 22:11:16 -04:00
|
|
|
if err := g.handleMovePlayerPacket(packet); err != nil {
|
|
|
|
return err
|
2020-06-26 17:12:19 -04:00
|
|
|
}
|
2020-06-26 20:03:00 -04:00
|
|
|
case d2netpackettype.CastSkill:
|
2020-07-17 22:11:16 -04:00
|
|
|
if err := g.handleCastSkillPacket(packet); err != nil {
|
2020-06-26 20:03:00 -04:00
|
|
|
return err
|
|
|
|
}
|
2020-06-22 20:31:42 -04:00
|
|
|
case d2netpackettype.Ping:
|
2020-07-17 22:11:16 -04:00
|
|
|
if err := g.handlePingPacket(); err != nil {
|
2020-06-30 20:01:51 -04:00
|
|
|
log.Printf("GameClient: error responding to server ping: %s", err)
|
|
|
|
}
|
2020-07-02 16:29:28 -04:00
|
|
|
case d2netpackettype.PlayerDisconnectionNotification:
|
|
|
|
// Not implemented
|
|
|
|
log.Printf("RemoteClientConnection: received disconnect: %s", packet.PacketData)
|
2020-06-22 20:31:42 -04:00
|
|
|
case d2netpackettype.ServerClosed:
|
2020-06-26 17:12:19 -04:00
|
|
|
// TODO: Need to be tied into a character save and exit
|
|
|
|
log.Print("Server has been closed")
|
|
|
|
os.Exit(0)
|
2020-06-13 18:32:09 -04:00
|
|
|
default:
|
|
|
|
log.Fatalf("Invalid packet type: %d", packet.PacketType)
|
|
|
|
}
|
2020-07-17 22:11:16 -04:00
|
|
|
|
2020-06-13 18:32:09 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2020-06-29 17:01:26 -04:00
|
|
|
// SendPacketToServer calls server.OnPacketReceived if the client is local.
|
|
|
|
// If it is remote the NetPacket sent over a UDP connection to the server.
|
2020-06-13 18:32:09 -04:00
|
|
|
func (g *GameClient) SendPacketToServer(packet d2netpacket.NetPacket) error {
|
|
|
|
return g.clientConnection.SendPacketToServer(packet)
|
|
|
|
}
|
2020-07-17 22:11:16 -04:00
|
|
|
|
|
|
|
func (g *GameClient) handleGenerateMapPacket(packet d2netpacket.NetPacket) error {
|
|
|
|
mapData := packet.PacketData.(d2netpacket.GenerateMapPacket)
|
|
|
|
|
|
|
|
if mapData.RegionType == d2enum.RegionAct1Town {
|
|
|
|
d2mapgen.GenerateAct1Overworld(g.MapEngine)
|
|
|
|
}
|
|
|
|
|
|
|
|
g.RegenMap = true
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *GameClient) handleUpdateServerInfoPacket(packet d2netpacket.NetPacket) error {
|
|
|
|
serverInfo := packet.PacketData.(d2netpacket.UpdateServerInfoPacket)
|
|
|
|
g.MapEngine.SetSeed(serverInfo.Seed)
|
|
|
|
g.PlayerID = serverInfo.PlayerID
|
|
|
|
g.Seed = serverInfo.Seed
|
|
|
|
log.Printf("Player id set to %s", serverInfo.PlayerID)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *GameClient) handleAddPlayerPacket(packet d2netpacket.NetPacket) error {
|
|
|
|
player := packet.PacketData.(d2netpacket.AddPlayerPacket)
|
|
|
|
newPlayer := d2mapentity.CreatePlayer(player.ID, player.Name, player.X, player.Y, 0,
|
|
|
|
player.HeroType, player.Stats, &player.Equipment)
|
|
|
|
|
2020-07-23 12:56:50 -04:00
|
|
|
g.Players[newPlayer.ID] = newPlayer
|
2020-07-17 22:11:16 -04:00
|
|
|
g.MapEngine.AddEntity(newPlayer)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *GameClient) handleMovePlayerPacket(packet d2netpacket.NetPacket) error {
|
|
|
|
movePlayer := packet.PacketData.(d2netpacket.MovePlayerPacket)
|
|
|
|
player := g.Players[movePlayer.PlayerID]
|
2020-07-21 08:50:45 -04:00
|
|
|
start := d2vector.NewPositionTile(movePlayer.StartX, movePlayer.StartY)
|
|
|
|
dest := d2vector.NewPositionTile(movePlayer.DestX, movePlayer.DestY)
|
|
|
|
path := g.MapEngine.PathFind(start, dest)
|
2020-07-17 22:11:16 -04:00
|
|
|
|
|
|
|
if len(path) > 0 {
|
|
|
|
player.SetPath(path, func() {
|
|
|
|
tilePosition := player.Position.Tile()
|
|
|
|
tile := g.MapEngine.TileAt(int(tilePosition.X()), int(tilePosition.Y()))
|
|
|
|
|
|
|
|
if tile == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
regionType := tile.RegionType
|
|
|
|
if regionType == d2enum.RegionAct1Town {
|
|
|
|
player.SetIsInTown(true)
|
|
|
|
} else {
|
|
|
|
player.SetIsInTown(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
err := player.SetAnimationMode(player.GetAnimationMode())
|
|
|
|
|
|
|
|
if err != nil {
|
2020-07-23 12:56:50 -04:00
|
|
|
log.Printf("GameClient: error setting animation mode for player %s: %s", player.ID, err)
|
2020-07-17 22:11:16 -04:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *GameClient) handleCastSkillPacket(packet d2netpacket.NetPacket) error {
|
|
|
|
playerCast := packet.PacketData.(d2netpacket.CastPacket)
|
|
|
|
player := g.Players[playerCast.SourceEntityID]
|
|
|
|
|
|
|
|
player.SetCasting()
|
|
|
|
player.ClearPath()
|
|
|
|
|
|
|
|
// currently hardcoded to missile skill
|
|
|
|
missile, err := d2mapentity.CreateMissile(
|
|
|
|
int(player.Position.X()),
|
|
|
|
int(player.Position.Y()),
|
|
|
|
d2datadict.Missiles[playerCast.SkillID],
|
|
|
|
)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
rads := d2common.GetRadiansBetween(
|
|
|
|
player.Position.X(),
|
|
|
|
player.Position.Y(),
|
|
|
|
playerCast.TargetX*numSubtilesPerTile,
|
|
|
|
playerCast.TargetY*numSubtilesPerTile,
|
|
|
|
)
|
|
|
|
|
|
|
|
missile.SetRadians(rads, func() {
|
|
|
|
g.MapEngine.RemoveEntity(missile)
|
|
|
|
})
|
|
|
|
|
|
|
|
g.MapEngine.AddEntity(missile)
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (g *GameClient) handlePingPacket() error {
|
|
|
|
pongPacket := d2netpacket.CreatePongPacket(g.PlayerID)
|
|
|
|
err := g.clientConnection.SendPacketToServer(pongPacket)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|