Refactor d2map (#468)

* WIP refactor of d2map stuff

* more d2map refactor

adding realm init to game client
passing map engine from client and server into realm at init
change `generate map packet` to have act and level index as data

* client explodes, but getting there

* realm now initializes, networking works, but map generators dont currently do anything

* changed the way that level type records are loaded

* fixed funcs for level data lookups

* started implementing level generator, currently crashing

* client no longer exploding

* d2networking refactor

put exports into d2client.go and d2server.go
kept GameClient and GameServer methods into their respective files
made methods for packet handlers instead of the giant switch statements

* bugfix: getting first level id by act

* minor refactor of gamescreen for readability

* towns now generate on server start, create player takes act and level id as args, levels have their own map engine
This commit is contained in:
dk 2020-06-26 13:50:24 -07:00 committed by GitHub
parent b4cd37fe6a
commit fe47e51351
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1100 additions and 433 deletions

View File

@ -31,13 +31,13 @@ type LevelPresetRecord struct {
}
// CreateLevelPresetRecord parses a row from lvlprest.txt into a LevelPresetRecord
func createLevelPresetRecord(props []string) LevelPresetRecord {
func createLevelPresetRecord(props []string) *LevelPresetRecord {
i := -1
inc := func() int {
i++
return i
}
result := LevelPresetRecord{
result := &LevelPresetRecord{
Name: props[inc()],
DefinitionId: d2common.StringToInt(props[inc()]),
LevelId: d2common.StringToInt(props[inc()]),
@ -69,10 +69,10 @@ func createLevelPresetRecord(props []string) LevelPresetRecord {
return result
}
var LevelPresets map[int]LevelPresetRecord
var LevelPresets map[int]*LevelPresetRecord
func LoadLevelPresets(file []byte) {
LevelPresets = make(map[int]LevelPresetRecord)
LevelPresets = make(map[int]*LevelPresetRecord)
data := strings.Split(string(file), "\r\n")[1:]
for _, line := range data {
if len(line) == 0 {
@ -88,7 +88,7 @@ func LoadLevelPresets(file []byte) {
log.Printf("Loaded %d level presets", len(LevelPresets))
}
func LevelPreset(id int) LevelPresetRecord {
func LevelPreset(id int) *LevelPresetRecord {
for i := 0; i < len(LevelPresets); i++ {
if LevelPresets[i].DefinitionId == id {
return LevelPresets[i]

View File

@ -9,6 +9,7 @@ import (
type LevelSubstitutionRecord struct {
// Description, reference only.
Name string // Name
// This value is used in Levels.txt, in the column 'SubType'. You'll notice
// that in LvlSub.txt some rows use the same value, we can say they forms
// groups. If you count each row of a group starting from 0, then you'll

View File

@ -2,51 +2,68 @@ package d2datadict
import (
"log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
type LevelTypeRecord struct {
Name string
Id int
Files [32]string
Beta bool
Act int
Expansion bool
Name string // Name
Id int // Id
Files []string // File 1 -- File 32
Beta bool // Beta
Act int // Act
Expansion bool // Expansion
}
var LevelTypes []LevelTypeRecord
var LevelTypes map[d2enum.RegionIdType]*LevelTypeRecord
func LoadLevelTypes(file []byte) {
data := strings.Split(string(file), "\r\n")[1:]
LevelTypes = make([]LevelTypeRecord, len(data))
for i, j := 0, 0; i < len(data); i, j = i+1, j+1 {
idx := -1
inc := func() int {
idx++
return idx
LevelTypes = make(map[d2enum.RegionIdType]*LevelTypeRecord)
dict := d2common.LoadDataDictionary(string(file))
for idx := range dict.Data {
record := &LevelTypeRecord{
Name: dict.GetString("Name", idx),
Id: dict.GetNumber("Id", idx),
Files: []string{
dict.GetString("File 1", idx),
dict.GetString("File 2", idx),
dict.GetString("File 3", idx),
dict.GetString("File 4", idx),
dict.GetString("File 5", idx),
dict.GetString("File 6", idx),
dict.GetString("File 7", idx),
dict.GetString("File 8", idx),
dict.GetString("File 9", idx),
dict.GetString("File 10", idx),
dict.GetString("File 11", idx),
dict.GetString("File 12", idx),
dict.GetString("File 13", idx),
dict.GetString("File 14", idx),
dict.GetString("File 15", idx),
dict.GetString("File 16", idx),
dict.GetString("File 17", idx),
dict.GetString("File 18", idx),
dict.GetString("File 19", idx),
dict.GetString("File 20", idx),
dict.GetString("File 21", idx),
dict.GetString("File 22", idx),
dict.GetString("File 23", idx),
dict.GetString("File 24", idx),
dict.GetString("File 25", idx),
dict.GetString("File 26", idx),
dict.GetString("File 27", idx),
dict.GetString("File 28", idx),
dict.GetString("File 29", idx),
dict.GetString("File 30", idx),
dict.GetString("File 31", idx),
dict.GetString("File 32", idx),
},
Beta: dict.GetNumber("Beta", idx) > 0,
Act: dict.GetNumber("Act", idx),
Expansion: dict.GetNumber("Expansion", idx) > 0,
}
if len(data[i]) == 0 {
continue
}
parts := strings.Split(data[i], "\t")
if parts[0] == "Expansion" {
j--
continue
}
LevelTypes[j].Name = parts[inc()]
LevelTypes[j].Id = d2common.StringToInt(parts[inc()])
for fileIdx := range LevelTypes[i].Files {
LevelTypes[j].Files[fileIdx] = parts[inc()]
if LevelTypes[j].Files[fileIdx] == "0" {
LevelTypes[j].Files[fileIdx] = ""
}
}
LevelTypes[j].Beta = parts[inc()] != "1"
LevelTypes[j].Act = d2common.StringToInt(parts[inc()])
LevelTypes[j].Expansion = parts[inc()] != "1"
LevelTypes[d2enum.RegionIdType(record.Id)] = record
}
log.Printf("Loaded %d LevelType records", len(LevelTypes))
}

View File

@ -38,7 +38,7 @@ type LevelDetailsRecord struct {
AutomapIndex int // Layer
// sizeX - SizeY in each difficuly. If this is a preset area this sets the
// X size for the area. Othervise use the same value here that are used in
// X size for the area. Otherwise use the same value here that are used in
// lvlprest.txt to set the size for the .ds1 file.
SizeXNormal int // SizeX
SizeYNormal int // SizeY
@ -141,28 +141,14 @@ type LevelDetailsRecord struct {
// linked with, but the actuall number of Vis ( 0 - 7 ) is determined by
// your actual map (the .ds1 fle).
// Example: Normally Cave levels are only using vis 0-3 and wilderness areas 4-7 .
LevelLinkId0 int // Vis0
LevelLinkId1 int // Vis1
LevelLinkId2 int // Vis2
LevelLinkId3 int // Vis3
LevelLinkId4 int // Vis4
LevelLinkId5 int // Vis5
LevelLinkId6 int // Vis6
LevelLinkId7 int // Vis7
WarpLevelId []int // Vis0 -- Vis7
// This controls the visual graphics then you move the mouse pointer over
// an entrance. To show the graphics you use an ID from lvlwarp.txt and the
// behavior on the graphics is controlled by lvlwarp.txt. Your Warps must
// match your Vis.
// Example: If your level uses Vis 3,5,7 then you must also use Warp 3,5,7 .
WarpGraphicsId0 int // Warp0
WarpGraphicsId1 int // Warp1
WarpGraphicsId2 int // Warp2
WarpGraphicsId3 int // Warp3
WarpGraphicsId4 int // Warp4
WarpGraphicsId5 int // Warp5
WarpGraphicsId6 int // Warp6
WarpGraphicsId7 int // Warp7
WarpGraphicsId []int // Warp0 -- Warp7
// These settings handle the light intensity as well as its RGB components
LightIntensity int // Intensity
@ -377,7 +363,7 @@ type LevelDetailsRecord struct {
var LevelDetails map[int]*LevelDetailsRecord
func GetLevelDetails(id int) *LevelDetailsRecord {
func GetLevelDetailsByLevelId(id int) *LevelDetailsRecord {
for i := 0; i < len(LevelDetails); i++ {
if LevelDetails[i].Id == id {
return LevelDetails[i]
@ -387,60 +373,138 @@ func GetLevelDetails(id int) *LevelDetailsRecord {
return nil
}
func GetLevelDetailsByActId(act int) []*LevelDetailsRecord {
result := make([]*LevelDetailsRecord, 0)
for _, record := range LevelDetails {
if act == record.Act {
result = append(result, record)
}
}
return result
}
var actIds []int
func GetNumberOfActs() int {
return len(actIds)
}
func GetActIds() []int {
return actIds
}
func GetLevelWarpsByLevelId(id int) []*LevelWarpRecord {
result := make([]*LevelWarpRecord, 0)
level := LevelDetails[id]
for _, warpId := range level.WarpLevelId {
if warpId < 0 {
continue // there are -1 values for empty entries in the table
}
result = append(result, LevelWarps[warpId])
}
return result
}
func GetLevelPresetByLevelId(id int) *LevelPresetRecord {
for recordId, record := range LevelPresets {
if id == recordId {
return record
}
}
panic("couldn't find a preset.")
}
func GetFirstLevelIdByActId(actId int) int {
recordsForAct := GetLevelDetailsByActId(actId)
lowest := -1
if len(recordsForAct) > 0 {
for _, record := range recordsForAct {
// need to account for level ID 0 which is an empty map in act 1
if record.Id == 0 {
continue
}
if lowest < 0 {
lowest = record.Id
continue
}
if record.Id < lowest {
lowest = record.Id
}
}
return lowest
}
return 0
}
func AppendIfMissing(slice []int, i int) []int {
for _, ele := range slice {
if ele == i {
return slice
}
}
return append(slice, i)
}
func LoadLevelDetails(file []byte) {
dict := d2common.LoadDataDictionary(string(file))
numRecords := len(dict.Data)
LevelDetails = make(map[int]*LevelDetailsRecord, numRecords)
actIds = make([]int, 0)
for idx := range dict.Data {
record := &LevelDetailsRecord{
Name: dict.GetString("Name ", idx),
Id: dict.GetNumber("Id", idx),
Palette: dict.GetNumber("Pal", idx),
Act: dict.GetNumber("Act", idx),
QuestFlag: dict.GetNumber("QuestFlag", idx),
QuestFlagExpansion: dict.GetNumber("QuestFlagEx", idx),
AutomapIndex: dict.GetNumber("Layer", idx),
SizeXNormal: dict.GetNumber("SizeX", idx),
SizeYNormal: dict.GetNumber("SizeY", idx),
SizeXNightmare: dict.GetNumber("SizeX(N)", idx),
SizeYNightmare: dict.GetNumber("SizeY(N)", idx),
SizeXHell: dict.GetNumber("SizeX(H)", idx),
SizeYHell: dict.GetNumber("SizeY(H)", idx),
WorldOffsetX: dict.GetNumber("OffsetX", idx),
WorldOffsetY: dict.GetNumber("OffsetY", idx),
DependantLevelID: dict.GetNumber("Depend", idx),
TeleportFlag: d2enum.TeleportFlag(dict.GetNumber("Teleport", idx)),
EnableRain: dict.GetNumber("Rain", idx) > 0,
EnableMud: dict.GetNumber("Mud", idx) > 0,
EnablePerspective: dict.GetNumber("NoPer", idx) > 0,
EnableLineOfSightDraw: dict.GetNumber("LOSDraw", idx) > 0,
EnableFloorFliter: dict.GetNumber("FloorFilter", idx) > 0,
EnableBlankScreen: dict.GetNumber("BlankScreen", idx) > 0,
EnableDrawEdges: dict.GetNumber("DrawEdges", idx) > 0,
IsInside: dict.GetNumber("IsInside", idx) > 0,
LevelGenerationType: d2enum.LevelGenerationType(dict.GetNumber("DrlgType", idx)),
LevelType: dict.GetNumber("LevelType", idx),
SubType: dict.GetNumber("SubType", idx),
SubTheme: dict.GetNumber("SubTheme", idx),
SubWaypoint: dict.GetNumber("SubWaypoint", idx),
SubShrine: dict.GetNumber("SubShrine", idx),
LevelLinkId0: dict.GetNumber("Vis0", idx),
LevelLinkId1: dict.GetNumber("Vis1", idx),
LevelLinkId2: dict.GetNumber("Vis2", idx),
LevelLinkId3: dict.GetNumber("Vis3", idx),
LevelLinkId4: dict.GetNumber("Vis4", idx),
LevelLinkId5: dict.GetNumber("Vis5", idx),
LevelLinkId6: dict.GetNumber("Vis6", idx),
LevelLinkId7: dict.GetNumber("Vis7", idx),
WarpGraphicsId0: dict.GetNumber("Warp0", idx),
WarpGraphicsId1: dict.GetNumber("Warp1", idx),
WarpGraphicsId2: dict.GetNumber("Warp2", idx),
WarpGraphicsId3: dict.GetNumber("Warp3", idx),
WarpGraphicsId4: dict.GetNumber("Warp4", idx),
WarpGraphicsId5: dict.GetNumber("Warp5", idx),
WarpGraphicsId6: dict.GetNumber("Warp6", idx),
WarpGraphicsId7: dict.GetNumber("Warp7", idx),
Name: dict.GetString("Name ", idx),
Id: dict.GetNumber("Id", idx),
Palette: dict.GetNumber("Pal", idx),
Act: dict.GetNumber("Act", idx),
QuestFlag: dict.GetNumber("QuestFlag", idx),
QuestFlagExpansion: dict.GetNumber("QuestFlagEx", idx),
AutomapIndex: dict.GetNumber("Layer", idx),
SizeXNormal: dict.GetNumber("SizeX", idx),
SizeYNormal: dict.GetNumber("SizeY", idx),
SizeXNightmare: dict.GetNumber("SizeX(N)", idx),
SizeYNightmare: dict.GetNumber("SizeY(N)", idx),
SizeXHell: dict.GetNumber("SizeX(H)", idx),
SizeYHell: dict.GetNumber("SizeY(H)", idx),
WorldOffsetX: dict.GetNumber("OffsetX", idx),
WorldOffsetY: dict.GetNumber("OffsetY", idx),
DependantLevelID: dict.GetNumber("Depend", idx),
TeleportFlag: d2enum.TeleportFlag(dict.GetNumber("Teleport", idx)),
EnableRain: dict.GetNumber("Rain", idx) > 0,
EnableMud: dict.GetNumber("Mud", idx) > 0,
EnablePerspective: dict.GetNumber("NoPer", idx) > 0,
EnableLineOfSightDraw: dict.GetNumber("LOSDraw", idx) > 0,
EnableFloorFliter: dict.GetNumber("FloorFilter", idx) > 0,
EnableBlankScreen: dict.GetNumber("BlankScreen", idx) > 0,
EnableDrawEdges: dict.GetNumber("DrawEdges", idx) > 0,
IsInside: dict.GetNumber("IsInside", idx) > 0,
LevelGenerationType: d2enum.LevelGenerationType(dict.GetNumber("DrlgType", idx)),
LevelType: dict.GetNumber("LevelType", idx),
SubType: dict.GetNumber("SubType", idx),
SubTheme: dict.GetNumber("SubTheme", idx),
SubWaypoint: dict.GetNumber("SubWaypoint", idx),
SubShrine: dict.GetNumber("SubShrine", idx),
WarpLevelId: []int{
dict.GetNumber("Vis0", idx),
dict.GetNumber("Vis1", idx),
dict.GetNumber("Vis2", idx),
dict.GetNumber("Vis3", idx),
dict.GetNumber("Vis4", idx),
dict.GetNumber("Vis5", idx),
dict.GetNumber("Vis6", idx),
dict.GetNumber("Vis7", idx),
},
WarpGraphicsId: []int{
dict.GetNumber("Vis0", idx),
dict.GetNumber("Vis1", idx),
dict.GetNumber("Vis2", idx),
dict.GetNumber("Vis3", idx),
dict.GetNumber("Vis4", idx),
dict.GetNumber("Vis5", idx),
dict.GetNumber("Vis6", idx),
dict.GetNumber("Vis7", idx),
},
LightIntensity: dict.GetNumber("Intensity", idx),
Red: dict.GetNumber("Red", idx),
Green: dict.GetNumber("Green", idx),
@ -540,6 +604,7 @@ func LoadLevelDetails(file []byte) {
ObjectGroupSpawnChance6: dict.GetNumber("ObjPrb6", idx),
ObjectGroupSpawnChance7: dict.GetNumber("ObjPrb7", idx),
}
actIds = AppendIfMissing(actIds, record.Act)
LevelDetails[idx] = record
}
log.Printf("Loaded %d LevelDetails records", len(LevelDetails))

View File

@ -11,7 +11,8 @@ package d2enum
type LevelGenerationType int
const (
LevelTypeRandomMaze LevelGenerationType = iota
LevelTypeNone LevelGenerationType = iota
LevelTypeRandomMaze
LevelTypePreset
LevelTypeWilderness
)

View File

@ -18,6 +18,7 @@ func getDefaultConfig() *Configuration {
VsyncEnabled: true,
SfxVolume: 1.0,
BgmVolume: 0.3,
MaxConnections: 8,
MpqPath: "C:/Program Files (x86)/Diablo II",
MpqLoadOrder: []string{
"Patch_D2.mpq",

View File

@ -16,6 +16,7 @@ type Configuration struct {
FullScreen bool
RunInBackground bool
VsyncEnabled bool
MaxConnections int
}
var singleton = getDefaultConfig()

View File

@ -0,0 +1,60 @@
package d2mapengine
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
// "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
type MapAct struct {
realm *MapRealm
id int
levels map[int]*MapLevel
}
func (act *MapAct) isActive() bool {
for _, level := range act.levels {
if level.isActive() {
return true
}
}
return false
}
func (act *MapAct) Advance(elapsed float64) {
if !act.isActive() {
return
}
for _, level := range act.levels {
level.Advance(elapsed)
}
}
func (act *MapAct) Init(realm *MapRealm, actIndex int) {
act.realm = realm
act.levels = make(map[int]*MapLevel)
act.id = actIndex
actLevelRecords := d2datadict.GetLevelDetailsByActId(actIndex)
log.Printf("Initializing Act %d", actIndex)
for _, record := range actLevelRecords {
level := &MapLevel{}
levelId := record.Id
level.Init(act, levelId)
act.levels[levelId] = level
}
act.GenerateTown() // ensures that starting point is known for first player
}
func (act *MapAct) GenerateTown() {
townId := d2datadict.GetFirstLevelIdByActId(act.id)
act.levels[townId].GenerateMap()
}
func (act *MapAct) GenerateMap(levelId int) {
log.Printf("Generating map in Act %d", act.id)
act.levels[levelId].GenerateMap()
}

View File

@ -20,16 +20,16 @@ import (
// Represents the map data for a specific location
type MapEngine struct {
seed int64 // The map seed
entities []d2mapentity.MapEntity // Entities on the map
tiles []d2ds1.TileRecord // The map tiles
size d2common.Size // The size of the map, in tiles
levelType d2datadict.LevelTypeRecord // The level type of this map
dt1TileData []d2dt1.Tile // The DT1 tile data
walkMesh []d2common.PathTile // The walk mesh
startSubTileX int // The starting X position
startSubTileY int // The starting Y position
dt1Files []string // The list of DS1 strings
seed int64 // The map seed
entities []d2mapentity.MapEntity // Entities on the map
tiles []d2ds1.TileRecord // The map tiles
size d2common.Size // The size of the map, in tiles
levelType *d2datadict.LevelTypeRecord // The level type of this map
dt1TileData []d2dt1.Tile // The DT1 tile data
walkMesh []d2common.PathTile // The walk mesh
startSubTileX int // The starting X position
startSubTileY int // The starting Y position
dt1Files []string // The list of DS1 strings
}
// Creates a new instance of the map engine
@ -114,7 +114,7 @@ func (m *MapEngine) FindTile(style, sequence, tileType int32) d2dt1.Tile {
}
// Returns the level type of this map
func (m *MapEngine) LevelType() d2datadict.LevelTypeRecord {
func (m *MapEngine) LevelType() *d2datadict.LevelTypeRecord {
return m.levelType
}

View File

@ -0,0 +1,6 @@
package d2mapengine
type MapGenerator interface {
init(seed int64, level *MapLevel, engine *MapEngine)
generate()
}

View File

@ -0,0 +1,15 @@
package d2mapengine
type MapGeneratorMaze struct {
seed int64
level *MapLevel
engine *MapEngine
}
func (m *MapGeneratorMaze) init(s int64, l *MapLevel, e *MapEngine) {
m.seed = s
m.level = l
m.engine = e
}
func (m *MapGeneratorMaze) generate() {}

View File

@ -0,0 +1,66 @@
package d2mapengine
import (
"log"
"math/rand"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapstamp"
)
type MapGeneratorPreset struct {
seed int64
level *MapLevel
engine *MapEngine
}
func (m *MapGeneratorPreset) init(s int64, l *MapLevel, e *MapEngine) {
m.seed = s
m.level = l
m.engine = e
}
func (m *MapGeneratorPreset) generate() {
rand.Seed(m.seed)
////////////////////////////////////////////////////////////////////// FIXME
// TODO: we need to set the difficulty level of the realm in order to pull
// the right data from level details. testing this for now with normal diff
// NOTE: we would be setting difficulty level in the realm when a host
// is connected (the first player)
diffTestKey := "Normal"
m.level.act.realm.difficulty = d2datadict.DifficultyLevels[diffTestKey] // hack
////////////////////////////////////////////////////////////////////////////
difficulty := m.level.act.realm.difficulty
details := m.level.details
tileW, tileH := 0, 0
switch difficulty.Name {
case "Normal":
tileW = details.SizeXNormal
tileH = details.SizeYNormal
case "Nightmare":
tileW = details.SizeXNightmare
tileH = details.SizeYNightmare
case "Hell":
tileW = details.SizeXHell
tileH = details.SizeYHell
}
// TODO: we shouldn't need to cast this to a RegionIdType
// In the long run, we aren't going to be using hardcoded enumerations
// we had initially made a list of them for testing, but not necessary now
levelTypeId := d2enum.RegionIdType(m.level.details.LevelType)
levelPresetId := m.level.preset.DefinitionId
m.engine.ResetMap(levelTypeId, tileW+1, tileH+1)
m.engine.levelType = m.level.types
stamp := d2mapstamp.LoadStamp(levelTypeId, levelPresetId, -1)
stampRegionPath := stamp.RegionPath()
log.Printf("Region Path: %s", stampRegionPath)
m.engine.PlaceStamp(stamp, 0, 0)
}

View File

@ -0,0 +1,15 @@
package d2mapengine
type MapGeneratorWilderness struct {
seed int64
level *MapLevel
engine *MapEngine
}
func (m *MapGeneratorWilderness) init(s int64, l *MapLevel, e *MapEngine) {
m.seed = s
m.level = l
m.engine = e
}
func (m *MapGeneratorWilderness) generate() {}

View File

@ -0,0 +1,79 @@
package d2mapengine
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
)
type MapLevel struct {
act *MapAct
details *d2datadict.LevelDetailsRecord
preset *d2datadict.LevelPresetRecord
warps []*d2datadict.LevelWarpRecord
substitutions *d2datadict.LevelSubstitutionRecord
types *d2datadict.LevelTypeRecord
generator MapGenerator
mapEngine *MapEngine
isInit bool
isGenerated bool
}
func (level *MapLevel) isActive() bool {
// TODO: a level is active only if there is a player in the level
// or in an adjacent level
return true
}
func (level *MapLevel) Advance(elapsed float64) {
if !level.isActive() {
return
}
level.mapEngine.Advance(elapsed)
}
func (level *MapLevel) Init(act *MapAct, levelId int) {
if level.isInit {
return
}
if levelId < 1 {
levelId = 1 // there is a Nonetype map at index 0 in levels.txt
}
level.act = act
level.details = d2datadict.GetLevelDetailsByLevelId(levelId)
level.preset = d2datadict.GetLevelPresetByLevelId(levelId)
level.warps = d2datadict.GetLevelWarpsByLevelId(levelId)
level.substitutions = d2datadict.LevelSubstitutions[level.details.SubType]
level.types = d2datadict.LevelTypes[d2enum.RegionIdType(level.details.LevelType)]
level.isInit = true
level.mapEngine = &MapEngine{}
level.mapEngine.seed = level.act.realm.seed
switch level.details.LevelGenerationType {
case d2enum.LevelTypeNone:
level.generator = nil
case d2enum.LevelTypeRandomMaze:
level.generator = &MapGeneratorMaze{}
case d2enum.LevelTypeWilderness:
level.generator = &MapGeneratorWilderness{}
case d2enum.LevelTypePreset:
level.generator = &MapGeneratorPreset{}
}
seed := act.realm.seed
if level.generator != nil {
log.Printf("Initializing Level: %s", level.details.Name)
level.generator.init(seed, level, level.mapEngine)
}
}
func (level *MapLevel) GenerateMap() {
if level.isGenerated {
return
}
log.Printf("Generating Level: %s", level.details.Name)
level.generator.generate()
level.mapEngine.RegenerateWalkPaths()
level.isGenerated = true
}

View File

@ -0,0 +1,114 @@
package d2mapengine
import (
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
)
/*
A MapRealm represents the state of the maps/levels/quests for a server
A MapRealm has MapActs
A MapAct has MapLevels
A MapLevel has:
a MapEngine
a MapGenerator for the level
data records from the txt files for the level
The MapRealm is created by the game server
The first player to connect to the realm becomes the host
The host determines the difficulty and which quests are completed
The Realm, Acts, and Levels do not advance unless they are `active`
Nothing happens in a realm unless it is active
Levels do not generate maps until the level becomes `active`
A Level is active if a player is within it OR in an adjacent level
An Act is active if one of its levels is active
The Realm is active if and only if one of its Acts is active
*/
type MapRealm struct {
seed int64
difficulty *d2datadict.DifficultyLevelRecord
acts map[int]*MapAct
players map[string]string
host string
}
// Checks if the realm is in an active state
func (realm *MapRealm) isActive() bool {
return realm.hasActiveActs()
}
// Checks if there is an active act
func (realm *MapRealm) hasActiveActs() bool {
for _, act := range realm.acts {
if act.isActive() {
return true
}
}
return false
}
// Advances the realm, which advances the acts, which advances the levels...
func (realm *MapRealm) Advance(elapsed float64) {
if !realm.isActive() {
return
}
for _, act := range realm.acts {
act.Advance(elapsed)
}
}
// Sets the host of the realm, which determines quest availability for players
func (realm *MapRealm) SetHost(id string) {
if player, found := realm.players[id]; found {
realm.host = player
log.Printf("Host is now %s", id)
}
}
// Adds a player to the realm
func (realm *MapRealm) AddPlayer(id string, actId int) {
realm.players[id] = id
if realm.host == "" {
realm.SetHost(id)
}
}
// Removes a player from the realm
func (realm *MapRealm) RemovePlayer(id string) {
delete(realm.players, id)
}
// Initialize the realm
func (realm *MapRealm) Init(seed int64) {
// realm.playerStates = make(map[string]*d2mapentitiy.Player)
log.Printf("Initializing Realm...")
realm.seed = seed
actIds := d2datadict.GetActIds()
realm.acts = make(map[int]*MapAct)
realm.players = make(map[string]string)
for _, actId := range actIds {
act := &MapAct{}
realm.acts[actId] = act
act.Init(realm, actId)
}
}
func (realm *MapRealm) GenerateMap(actId, levelId int) {
realm.acts[actId].GenerateMap(levelId)
}
func (realm *MapRealm) GetMapEngine(actId, levelId int) *MapEngine {
return realm.acts[actId].levels[levelId].mapEngine
}
func (realm *MapRealm) GetFirstActLevelId(actId int) int {
return d2datadict.GetFirstLevelIdByActId(actId)
}

View File

@ -34,7 +34,7 @@ type Player struct {
var baseWalkSpeed = 6.0
var baseRunSpeed = 9.0
func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) *Player {
func CreatePlayer(id, name string, ActId, LevelId, x, y, direction int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) *Player {
object := &d2datadict.ObjectLookupRecord{
Mode: d2enum.AnimationModePlayerTownNeutral.String(),
Base: "/data/global/chars",

View File

@ -26,7 +26,7 @@ func loadPreset(mapEngine *d2mapengine.MapEngine, id, index int) *d2mapstamp.Sta
func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
rand.Seed(mapEngine.Seed())
wilderness1Details := d2datadict.GetLevelDetails(2)
wilderness1Details := d2datadict.GetLevelDetailsByLevelId(2)
mapEngine.ResetMap(d2enum.RegionAct1Town, 150, 150)
mapWidth := mapEngine.Size().Width
mapHeight := mapEngine.Size().Height
@ -59,7 +59,7 @@ func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
// West Exit
mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height)
generateWilderness1TownWest(mapEngine, mapWidth-townSize.Width - wilderness1Details.SizeXNormal, mapHeight-wilderness1Details.SizeYNormal)
generateWilderness1TownWest(mapEngine, mapWidth-townSize.Width-wilderness1Details.SizeXNormal, mapHeight-wilderness1Details.SizeYNormal)
} else {
// North Exit
mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height)
@ -69,7 +69,7 @@ func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
}
func generateWilderness1TownEast(mapEngine *d2mapengine.MapEngine, startX, startY int) {
levelDetails := d2datadict.GetLevelDetails(2)
levelDetails := d2datadict.GetLevelDetailsByLevelId(2)
fenceNorthStamp := []*d2mapstamp.Stamp{
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0),
@ -102,36 +102,36 @@ func generateWilderness1TownEast(mapEngine *d2mapengine.MapEngine, startX, start
areaRect := d2common.Rectangle{
Left: startX,
Top: startY+9,
Top: startY + 9,
Width: levelDetails.SizeXNormal,
Height: levelDetails.SizeYNormal-3,
Height: levelDetails.SizeYNormal - 3,
}
generateWilderness1Contents(mapEngine, areaRect)
// Draw the north and south fence
for i := 0; i < 9; i++ {
mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9), startY)
mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9), startY + (levelDetails.SizeYNormal +6))
mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9), startY+(levelDetails.SizeYNormal+6))
}
// West fence
for i := 1; i < 6; i++ {
mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+ (levelDetails.SizeYNormal+6) - (i * 9))
mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(levelDetails.SizeYNormal+6)-(i*9))
}
// East Fence
for i := 1; i < 10; i++ {
mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX + levelDetails.SizeXNormal, startY+(i*9))
mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX+levelDetails.SizeXNormal, startY+(i*9))
}
mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+ levelDetails.SizeYNormal+6)
mapEngine.PlaceStamp(fenceWestEdge, startX, startY+ (levelDetails.SizeYNormal-3) - 45)
mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal+6)
mapEngine.PlaceStamp(fenceWestEdge, startX, startY+(levelDetails.SizeYNormal-3)-45)
mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal, startY)
mapEngine.PlaceStamp(fenceSouthEastStamp, startX+levelDetails.SizeXNormal, startY+levelDetails.SizeYNormal+6)
}
func generateWilderness1TownSouth(mapEngine *d2mapengine.MapEngine, startX, startY int) {
levelDetails := d2datadict.GetLevelDetails(2)
levelDetails := d2datadict.GetLevelDetailsByLevelId(2)
fenceNorthStamp := []*d2mapstamp.Stamp{
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0),
@ -184,7 +184,7 @@ func generateWilderness1TownSouth(mapEngine *d2mapengine.MapEngine, startX, star
}
func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, startY int) {
levelDetails := d2datadict.GetLevelDetails(2)
levelDetails := d2datadict.GetLevelDetailsByLevelId(2)
fenceEastEdge := loadPreset(mapEngine, d2wilderness.TreeBoxSouthWest, 0)
fenceNorthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthWest, 0)
@ -218,30 +218,30 @@ func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, start
// Draw the north and south fences
for i := 0; i < 9; i++ {
if i > 0 && i < 8 {
mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX + (i*9)-1, startY-15)
mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9)-1, startY-15)
}
mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9)-1, startY+levelDetails.SizeYNormal-12)
}
// Draw the east fence
for i := 0; i < 6; i++ {
mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX + levelDetails.SizeXNormal-9, startY + (i*9)-6)
mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX+levelDetails.SizeXNormal-9, startY+(i*9)-6)
}
// Draw the west fence
for i := 0; i < 9; i++ {
mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY + (i*9)-6)
mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(i*9)-6)
}
// Draw the west fence
mapEngine.PlaceStamp(fenceEastEdge, startX + levelDetails.SizeXNormal-9, startY + 39)
mapEngine.PlaceStamp(fenceEastEdge, startX+levelDetails.SizeXNormal-9, startY+39)
mapEngine.PlaceStamp(fenceNorthWestStamp, startX, startY-15)
mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal-12)
mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal-9, startY-15)
areaRect := d2common.Rectangle{
Left: startX + 9,
Top: startY-10,
Top: startY - 10,
Width: levelDetails.SizeXNormal - 9,
Height: levelDetails.SizeYNormal - 2,
}
@ -250,7 +250,7 @@ func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, start
}
func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2common.Rectangle) {
levelDetails := d2datadict.GetLevelDetails(2)
levelDetails := d2datadict.GetLevelDetailsByLevelId(2)
denOfEvil := loadPreset(mapEngine, d2wilderness.DenOfEvilEntrance, 0)
denOfEvilLoc := d2common.Point{
@ -295,10 +295,10 @@ func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2common
for numPlaced < 25 {
stamp := stuff[rand.Intn(len(stuff))]
stampRect := d2common.Rectangle {
Left: rect.Left+ rand.Intn(rect.Width) - stamp.Size().Width,
Top: rect.Top+rand.Intn(rect.Height) - stamp.Size().Height,
Width: stamp.Size().Width,
stampRect := d2common.Rectangle{
Left: rect.Left + rand.Intn(rect.Width) - stamp.Size().Width,
Top: rect.Top + rand.Intn(rect.Height) - stamp.Size().Height,
Width: stamp.Size().Width,
Height: stamp.Size().Height,
}

View File

@ -18,11 +18,11 @@ import (
// Represents a pre-fabricated map stamp that can be placed on a map
type Stamp struct {
regionPath string // The file path of the region
levelType d2datadict.LevelTypeRecord // The level type id for this stamp
levelPreset d2datadict.LevelPresetRecord // The level preset id for this stamp
tiles []d2dt1.Tile // The tiles contained on this stamp
ds1 *d2ds1.DS1 // The backing DS1 file for this stamp
regionPath string // The file path of the region
levelType *d2datadict.LevelTypeRecord // The level type id for this stamp
levelPreset *d2datadict.LevelPresetRecord // The level preset id for this stamp
tiles []d2dt1.Tile // The tiles contained on this stamp
ds1 *d2ds1.DS1 // The backing DS1 file for this stamp
}
// Loads a stamp based on the supplied parameters
@ -83,12 +83,12 @@ func (mr *Stamp) Size() d2common.Size {
}
// Gets the level preset id
func (mr *Stamp) LevelPreset() d2datadict.LevelPresetRecord {
func (mr *Stamp) LevelPreset() *d2datadict.LevelPresetRecord {
return mr.levelPreset
}
// Returns the level type id
func (mr *Stamp) LevelType() d2datadict.LevelTypeRecord {
func (mr *Stamp) LevelType() *d2datadict.LevelTypeRecord {
return mr.levelType
}

View File

@ -9,6 +9,7 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2input"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio"
@ -162,11 +163,27 @@ func (v *CharacterSelect) updateCharacterBoxes() {
v.characterNameLabel[i].SetText(v.gameStates[idx].HeroName)
v.characterStatsLabel[i].SetText("Level 1 " + v.gameStates[idx].HeroType.String())
v.characterExpLabel[i].SetText(expText)
playerId := ""
playerName := ""
actId := d2datadict.GetActIds()[0]
levelId := d2datadict.GetFirstLevelIdByActId(actId)
x, y := 0, 0
dir := 0
heroType := v.gameStates[idx].HeroType
heroStats := *v.gameStates[idx].Stats
heroEquipment := d2inventory.HeroObjects[v.gameStates[idx].HeroType]
// TODO: Generate or load the object from the actual player data...
v.characterImage[i] = d2mapentity.CreatePlayer("", "", 0, 0, 0,
v.gameStates[idx].HeroType,
*v.gameStates[idx].Stats,
d2inventory.HeroObjects[v.gameStates[idx].HeroType],
v.characterImage[i] = d2mapentity.CreatePlayer(
playerId,
playerName,
actId,
levelId,
x, y,
dir,
heroType,
heroStats,
heroEquipment,
)
}
}

View File

@ -20,7 +20,7 @@ import (
type Game struct {
gameClient *d2client.GameClient
mapRenderer *d2maprenderer.MapRenderer
MapRenderer *d2maprenderer.MapRenderer
gameControls *d2player.GameControls // TODO: Hack
localPlayer *d2mapentity.Player
lastRegionType d2enum.RegionIdType
@ -35,7 +35,7 @@ func CreateGame(gameClient *d2client.GameClient) *Game {
localPlayer: nil,
lastRegionType: d2enum.RegionNone,
ticksSinceLevelCheck: 0,
mapRenderer: d2maprenderer.CreateMapRenderer(gameClient.MapEngine),
MapRenderer: d2maprenderer.CreateMapRenderer(gameClient.MapEngine),
escapeMenu: NewEscapeMenu(),
}
result.escapeMenu.OnLoad()
@ -56,11 +56,13 @@ func (v *Game) OnUnload() error {
func (v *Game) Render(screen d2render.Surface) error {
if v.gameClient.RegenMap {
v.gameClient.RegenMap = false
v.mapRenderer.RegenerateTileCache()
v.MapRenderer.RegenerateTileCache()
}
screen.Clear(color.Black)
v.mapRenderer.Render(screen)
if v.MapRenderer != nil {
screen.Clear(color.Black)
v.MapRenderer.Render(screen)
}
if v.gameControls != nil {
v.gameControls.Render(screen)
@ -69,7 +71,7 @@ func (v *Game) Render(screen d2render.Surface) error {
return nil
}
var hideZoneTextAfterSeconds = 2.0
var zoneTextDuration = 2.0 // seconds
func (v *Game) Advance(tickTime float64) error {
if (v.escapeMenu != nil && !v.escapeMenu.IsOpen()) || len(v.gameClient.Players) != 1 {
@ -96,11 +98,19 @@ func (v *Game) Advance(tickTime float64) error {
}
// skip showing zone change text the first time we enter the world
if v.lastRegionType != d2enum.RegionNone && v.lastRegionType != tile.RegionType {
//TODO: Should not be using RegionType as an index - this will return incorrect LevelDetails record for most of the zones.
v.gameControls.SetZoneChangeText(fmt.Sprintf("Entering The %s", d2datadict.LevelDetails[int(tile.RegionType)].LevelDisplayName))
notNone := v.lastRegionType != d2enum.RegionNone
differentTileType := v.lastRegionType != tile.RegionType
if notNone && differentTileType {
//TODO: Should not be using RegionType as an index - this
// will return incorrect LevelDetails record for most of the
// zones.
levelId := int(tile.RegionType)
levelDetails := d2datadict.LevelDetails[levelId]
str := "Entering The %s"
name := levelDetails.LevelDisplayName
v.gameControls.SetZoneChangeText(fmt.Sprintf(str, name))
v.gameControls.ShowZoneChangeText()
v.gameControls.HideZoneChangeTextAfter(hideZoneTextAfterSeconds)
v.gameControls.HideZoneChangeTextAfter(zoneTextDuration)
}
v.lastRegionType = tile.RegionType
}
@ -114,7 +124,9 @@ func (v *Game) Advance(tickTime float64) error {
continue
}
v.localPlayer = player
v.gameControls = d2player.NewGameControls(player, v.gameClient.MapEngine, v.mapRenderer, v)
engine := v.gameClient.MapEngine
renderer := v.MapRenderer
v.gameControls = d2player.NewGameControls(player, engine, renderer, v)
v.gameControls.Load()
d2input.BindHandler(v.gameControls)
@ -124,14 +136,16 @@ func (v *Game) Advance(tickTime float64) error {
// Update the camera to focus on the player
if v.localPlayer != nil && !v.gameControls.FreeCam {
rx, ry := v.mapRenderer.WorldToOrtho(v.localPlayer.LocationX/5, v.localPlayer.LocationY/5)
v.mapRenderer.MoveCameraTo(rx, ry)
wx, wy := v.localPlayer.LocationX/5, v.localPlayer.LocationY/5
rx, ry := v.MapRenderer.WorldToOrtho(wx, wy)
v.MapRenderer.MoveCameraTo(rx, ry)
}
return nil
}
func (v *Game) OnPlayerMove(x, y float64) {
heroPosX := v.localPlayer.LocationX / 5.0
heroPosY := v.localPlayer.LocationY / 5.0
v.gameClient.SendPacketToServer(d2netpacket.CreateMovePlayerPacket(v.gameClient.PlayerId, heroPosX, heroPosY, x, y))
func (v *Game) OnPlayerMove(x2, y2 float64) {
id := v.gameClient.PlayerId
x1, y1 := v.localPlayer.LocationX/5.0, v.localPlayer.LocationY/5.0
movePacket := d2netpacket.CreateMovePlayerPacket(id, x1, y1, x2, y2)
v.gameClient.SendPacketToServer(movePacket)
}

View File

@ -20,9 +20,10 @@ type PlayerState struct {
HeroType d2enum.Hero `json:"heroType"`
HeroLevel int `json:"heroLevel"`
Act int `json:"act"`
Level int `json:"actLevel"`
FilePath string `json:"-"`
Equipment d2inventory.CharacterEquipment `json:"equipment"`
Stats *d2hero.HeroStatsState `json:"stats"`
Stats *d2hero.HeroStatsState `json:"stats"`
X float64 `json:"x"`
Y float64 `json:"y"`
}
@ -45,8 +46,8 @@ func GetAllPlayerStates() []*PlayerState {
gameState := LoadPlayerState(path.Join(basePath, file.Name()))
if gameState == nil || gameState.HeroType == d2enum.HeroNone {
continue
// temporarily loading default class stats if the character was created before saving stats was introduced
// to be removed in the future
// temporarily loading default class stats if the character was created before saving stats was introduced
// to be removed in the future
} else if gameState.Stats == nil {
gameState.Stats = d2hero.CreateHeroStatsState(gameState.HeroType, *d2datadict.CharStats[gameState.HeroType], 1, 0)
gameState.Save()
@ -83,8 +84,9 @@ func CreatePlayerState(heroName string, hero d2enum.Hero, classStats d2datadict.
result := &PlayerState{
HeroName: heroName,
HeroType: hero,
Act: 1,
Stats: d2hero.CreateHeroStatsState(hero, classStats, 1, 0),
Act: 0,
Level: 1,
Stats: d2hero.CreateHeroStatsState(hero, classStats, 1, 0),
Equipment: d2inventory.HeroObjects[hero],
FilePath: "",
}

View File

@ -0,0 +1,38 @@
package d2client
import (
"fmt"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
d2cct "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2localclient"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2remoteclient"
)
// Creates a connections to the server and returns a game client instance
func Create(connectionType d2cct.ClientConnectionType) (*GameClient, error) {
result := &GameClient{
// TODO: Mapgen - Needs levels.txt stuff
MapEngine: d2mapengine.CreateMapEngine(),
Players: make(map[string]*d2mapentity.Player),
connectionType: connectionType,
realm: &d2mapengine.MapRealm{},
}
switch connectionType {
case d2cct.LANClient:
result.clientConnection = d2remoteclient.Create()
case d2cct.LANServer:
openSocket := true
result.clientConnection = d2localclient.Create(openSocket)
case d2cct.Local:
dontOpenSocket := false
result.clientConnection = d2localclient.Create(dontOpenSocket)
default:
str := "unknown client connection type specified: %d"
return nil, fmt.Errorf(str, connectionType)
}
result.clientConnection.SetClientListener(result)
return result, nil
}

View File

@ -1,120 +1,164 @@
package d2client
import (
"fmt"
"log"
"os"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen"
// "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2localclient"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2remoteclient"
d2cct "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
)
type GameClient struct {
clientConnection ClientConnection
connectionType d2clientconnectiontype.ClientConnectionType
connectionType d2cct.ClientConnectionType
GameState *d2player.PlayerState
MapEngine *d2mapengine.MapEngine
MapRenderer *d2maprenderer.MapRenderer
realm *d2mapengine.MapRealm
PlayerId string
Players map[string]*d2mapentity.Player
Seed int64
RegenMap bool
}
func Create(connectionType d2clientconnectiontype.ClientConnectionType) (*GameClient, error) {
result := &GameClient{
MapEngine: d2mapengine.CreateMapEngine(), // TODO: Mapgen - Needs levels.txt stuff
Players: make(map[string]*d2mapentity.Player),
connectionType: connectionType,
}
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)
}
result.clientConnection.SetClientListener(result)
return result, nil
}
// Using the `clientConnection`, opens a connection and passes the savefile path
func (g *GameClient) Open(connectionString string, saveFilePath string) error {
return g.clientConnection.Open(connectionString, saveFilePath)
}
// Closes the `clientConnection`
func (g *GameClient) Close() error {
return g.clientConnection.Close()
}
// Closes the `clientConnection`
func (g *GameClient) Destroy() error {
return g.clientConnection.Close()
}
// Routes the incoming packets to the packet handlers
func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
switch packet.PacketType {
case d2netpackettype.GenerateMap:
mapData := packet.PacketData.(d2netpacket.GenerateMapPacket)
switch mapData.RegionType {
case d2enum.RegionAct1Town:
d2mapgen.GenerateAct1Overworld(g.MapEngine)
}
g.RegenMap = true
case d2netpackettype.UpdateServerInfo:
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)
case d2netpackettype.AddPlayer:
player := packet.PacketData.(d2netpacket.AddPlayerPacket)
newPlayer := d2mapentity.CreatePlayer(player.Id, player.Name, player.X, player.Y, 0, player.HeroType, player.Stats, player.Equipment)
g.Players[newPlayer.Id] = newPlayer
g.MapEngine.AddEntity(newPlayer)
case d2netpackettype.MovePlayer:
movePlayer := packet.PacketData.(d2netpacket.MovePlayerPacket)
player := g.Players[movePlayer.PlayerId]
path, _, _ := g.MapEngine.PathFind(movePlayer.StartX, movePlayer.StartY, movePlayer.DestX, movePlayer.DestY)
if len(path) > 0 {
player.SetPath(path, func() {
tile := g.MapEngine.TileAt(player.TileX, player.TileY)
if tile == nil {
return
}
regionType := tile.RegionType
if regionType == d2enum.RegionAct1Town {
player.SetIsInTown(true)
} else {
player.SetIsInTown(false)
}
player.SetAnimationMode(player.GetAnimationMode().String())
})
}
// UNSURE: should we be bubbling up errors from these handler calls?
case d2netpackettype.UpdateServerInfo:
g.handleUpdateServerInfo(packet)
case d2netpackettype.AddPlayer:
g.handleAddPlayer(packet)
case d2netpackettype.GenerateMap:
g.handleGenerateMap(packet)
case d2netpackettype.MovePlayer:
g.handleMovePlayer(packet)
case d2netpackettype.Ping:
g.clientConnection.SendPacketToServer(d2netpacket.CreatePongPacket(g.PlayerId))
g.handlePong(packet)
case d2netpackettype.ServerClosed:
// TODO: Need to be tied into a character save and exit
log.Print("Server has been closed")
os.Exit(0)
g.handleServerClosed(packet)
default:
log.Fatalf("Invalid packet type: %d", packet.PacketType)
}
return nil
}
// Using the `clientConnection`, sends a packet to the server
func (g *GameClient) SendPacketToServer(packet d2netpacket.NetPacket) error {
return g.clientConnection.SendPacketToServer(packet)
}
func (g *GameClient) handleUpdateServerInfo(p d2netpacket.NetPacket) {
serverInfo := p.PacketData.(d2netpacket.UpdateServerInfoPacket)
seed := serverInfo.Seed
playerId := serverInfo.PlayerId
g.Seed = seed
g.realm.Init(seed)
g.PlayerId = playerId
log.Printf("Player id set to %s", playerId)
}
func (g *GameClient) handleAddPlayer(p d2netpacket.NetPacket) {
player := p.PacketData.(d2netpacket.AddPlayerPacket)
levelId := g.realm.GetFirstActLevelId(player.Act)
g.MapEngine = g.realm.GetMapEngine(player.Act, levelId)
pId := player.Id
pName := player.Name
pAct := player.Act
pLvlId := levelId
pX := player.X
pY := player.Y
pDir := 0
pHero := player.HeroType
pStat := player.Stats
pEquip := player.Equipment
// UNSURE: maybe we should be passing a struct instead of all the vars?
newPlayer := d2mapentity.CreatePlayer(
pId, pName, pAct, pLvlId, pX, pY, pDir, pHero, pStat, pEquip,
)
g.Players[newPlayer.Id] = newPlayer
g.realm.AddPlayer(pId, pAct)
g.MapEngine.AddEntity(newPlayer)
}
func (g *GameClient) handleGenerateMap(p d2netpacket.NetPacket) {
mapData := p.PacketData.(d2netpacket.GenerateMapPacket)
g.realm.GenerateMap(mapData.ActId, mapData.LevelId)
engine := g.realm.GetMapEngine(mapData.ActId, mapData.LevelId)
g.MapRenderer = d2maprenderer.CreateMapRenderer(engine)
g.RegenMap = true
}
func (g *GameClient) handleMovePlayer(p d2netpacket.NetPacket) {
movePlayer := p.PacketData.(d2netpacket.MovePlayerPacket)
player := g.Players[movePlayer.PlayerId]
x1, y1 := movePlayer.StartX, movePlayer.StartY
x2, y2 := movePlayer.DestX, movePlayer.DestY
path, _, _ := g.MapEngine.PathFind(x1, y1, x2, y2)
if len(path) > 0 {
player.SetPath(path, func() {
tile := g.MapEngine.TileAt(player.TileX, player.TileY)
if tile == nil {
return
}
regionType := tile.RegionType
if regionType == d2enum.RegionAct1Town {
player.SetIsInTown(true)
} else {
player.SetIsInTown(false)
}
player.SetAnimationMode(player.GetAnimationMode().String())
})
}
}
func (g *GameClient) handlePong(p d2netpacket.NetPacket) {
pong := d2netpacket.CreatePongPacket(g.PlayerId)
g.clientConnection.SendPacketToServer(pong)
}
func (g *GameClient) handleServerClosed(p d2netpacket.NetPacket) {
// TODO: Need to be tied into a character save and exit
log.Print("Server has been closed")
os.Exit(0)
}

View File

@ -12,22 +12,24 @@ type AddPlayerPacket struct {
Name string `json:"name"`
X int `json:"x"`
Y int `json:"y"`
Act int `json:"act"`
HeroType d2enum.Hero `json:"hero"`
Equipment d2inventory.CharacterEquipment `json:"equipment"`
Stats d2hero.HeroStatsState `json:"heroStats"`
Stats d2hero.HeroStatsState `json:"heroStats"`
}
func CreateAddPlayerPacket(id, name string, x, y int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) NetPacket {
func CreateAddPlayerPacket(id, name string, act, x, y int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) NetPacket {
return NetPacket{
PacketType: d2netpackettype.AddPlayer,
PacketData: AddPlayerPacket{
Id: id,
Name: name,
Act: act,
X: x,
Y: y,
HeroType: heroType,
Equipment: equipment,
Stats: stats,
Stats: stats,
},
}
}

View File

@ -1,19 +1,20 @@
package d2netpacket
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
)
type GenerateMapPacket struct {
RegionType d2enum.RegionIdType `json:"regionType"`
ActId int `json:"actId"`
LevelId int `json:"levelId"`
}
func CreateGenerateMapPacket(regionType d2enum.RegionIdType) NetPacket {
func CreateGenerateMapPacket(actId, levelId int) NetPacket {
return NetPacket{
PacketType: d2netpackettype.GenerateMap,
PacketData: GenerateMapPacket{
RegionType: regionType,
ActId: actId,
LevelId: levelId,
},
}

View File

@ -8,13 +8,16 @@ import (
"time"
)
// ConnectionManager is responsible for cleanup up connections accepted by the game server. As the server communicates over
// UDP and is stateless we need to implement some loose state management via a ping/pong system. ConnectionManager also handles
// ConnectionManager is responsible for cleanup up connections accepted by the
// game server. As the server communicates over
// UDP and is stateless we need to implement some loose state 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
// gameServer: The *GameServer is argument provided for the connection manager
// to watch over
// status: map of inflight ping/pong requests
type ConnectionManager struct {
sync.RWMutex
@ -50,7 +53,8 @@ func (c *ConnectionManager) Run() {
func (c *ConnectionManager) checkPeers() {
for id, connection := range c.gameServer.clientConnections {
if connection.GetConnectionType() != d2clientconnectiontype.Local {
if err := connection.SendPacketToClient(d2netpacket.CreatePingPacket()); err != nil {
pingPacket := d2netpacket.CreatePingPacket()
if err := connection.SendPacketToClient(pingPacket); err != nil {
log.Printf("Cannot ping client id: %s", id)
}
c.RWMutex.Lock()
@ -66,7 +70,8 @@ func (c *ConnectionManager) checkPeers() {
}
}
// Recv simply resets the counter, acknowledging we have received a pong from the client.
// Recv simply resets the counter, acknowledging we have received a pong from
// the client.
func (c *ConnectionManager) Recv(id string) {
c.status[id] = 0
}
@ -81,8 +86,10 @@ func (c *ConnectionManager) Drop(id string) {
// Shutdown will notify all of the clients that the server has been shutdown.
func (c *ConnectionManager) Shutdown() {
// TODO: Currently this will never actually get called as the go routines are never signaled about the application termination.
// Things can be done more cleanly once we have graceful exits however we still need to account for other OS Signals
// TODO: Currently this will never actually get called as the go routines
// are never signaled about the application termination.
// Things can be done more cleanly once we have graceful exits however we
// still need to account for other OS Signals
log.Print("Notifying clients server is shutting down...")
for _, connection := range c.gameServer.clientConnections {
connection.SendPacketToClient(d2netpacket.CreateServerClosedPacket())

View File

@ -0,0 +1,243 @@
package d2server
import (
"bytes"
"compress/gzip"
"fmt"
"github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2config"
"io"
"log"
"net"
"strings"
"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/d2common/d2data/d2datadict"
packet "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
packettype "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
"github.com/OpenDiablo2/OpenDiablo2/d2script"
"github.com/robertkrimen/otto"
)
var singletonServer *GameServer
func advance() {
now := d2common.Now()
elapsed := now - singletonServer.lastAdvance
singletonServer.realm.Advance(elapsed)
singletonServer.lastAdvance = now
}
func Create(openNetworkServer bool) {
log.Print("Creating GameServer")
if singletonServer != nil {
return
}
config := d2config.Get()
maxConnections := config.MaxConnections
seed := time.Now().UnixNano()
singletonServer = &GameServer{
clientConnections: make(map[string]ClientConnection),
realm: &d2mapengine.MapRealm{},
scriptEngine: d2script.CreateScriptEngine(),
seed: seed,
maxClients: maxConnections,
lastAdvance: d2common.Now(),
}
singletonServer.realm.Init(seed)
singletonServer.manager = CreateConnectionManager(singletonServer)
// mapEngine := d2mapengine.CreateMapEngine()
// mapEngine.SetSeed(singletonServer.seed)
// TODO: Mapgen - Needs levels.txt stuff
// mapEngine.ResetMap(d2enum.RegionAct1Town, 100, 100)
// d2mapgen.GenerateAct1Overworld(mapEngine)
// singletonServer.mapEngines = append(singletonServer.mapEngines, mapEngine)
addScriptEngineFunctions()
if openNetworkServer {
createNetworkServer()
}
}
func addScriptEngineFunctions() {
singletonServer.scriptEngine.AddFunction("getMapEngines", ottoTestFunc)
}
func ottoTestFunc(call otto.FunctionCall) otto.Value {
val, err := singletonServer.scriptEngine.ToValue(singletonServer.realm)
if err != nil {
fmt.Print(err.Error())
}
return val
}
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)
}
singletonServer.udpConnection.SetReadBuffer(4096)
}
func runNetworkServer() {
buffer := make([]byte, 4096)
srv := singletonServer
for srv.running {
advance()
_, addr, err := srv.udpConnection.ReadFromUDP(buffer)
if err != nil {
fmt.Printf("Socket error: %s\n", err)
continue
}
buff := bytes.NewBuffer(buffer)
packetTypeId, err := buff.ReadByte()
packetType := packettype.NetPacketType(packetTypeId)
reader, err := gzip.NewReader(buff)
sb := new(strings.Builder)
io.Copy(sb, reader)
stringData := sb.String()
switch packetType {
case packettype.PlayerConnectionRequest:
srv.handlePlayerConnRequest(addr, stringData)
case packettype.MovePlayer:
srv.handleMovePlayer(addr, stringData)
case packettype.Pong:
srv.handlePong(addr, stringData)
case packettype.ServerClosed:
srv.manager.Shutdown()
case packettype.PlayerDisconnectionNotification:
srv.handlePlayerDisconnectNotification(stringData)
}
}
}
func Run() {
log.Print("Starting GameServer")
singletonServer.running = true
singletonServer.scriptEngine.RunScript("scripts/server/server.js")
if singletonServer.udpConnection != nil {
go runNetworkServer()
}
log.Print("Network server has been started")
}
func Stop() {
log.Print("Stopping GameServer")
singletonServer.running = false
if singletonServer.udpConnection != nil {
singletonServer.udpConnection.Close()
}
}
func Destroy() {
if singletonServer == nil {
return
}
log.Print("Destroying GameServer")
Stop()
}
func OnClientConnected(client ClientConnection) {
srv := singletonServer
realm := srv.realm
seed := srv.seed
state := client.GetPlayerState()
actId := state.Act
levelId := d2datadict.GetFirstLevelIdByActId(actId)
engine := realm.GetMapEngine(actId, levelId)
// params for AddPlayer packet, of new player
id := client.GetUniqueId()
name := state.HeroName
hero := state.HeroType
stats := *state.Stats
equip := state.Equipment
x, y := engine.GetStartPosition()
state.X = x
state.Y = y
infoPacket := packet.CreateUpdateServerInfoPacket(seed, id)
mapgenPacket := packet.CreateGenerateMapPacket(actId, levelId)
// UNSURE: maybe we should pass a struct instead of all of these args
addNew := packet.CreateAddPlayerPacket(
id, name, actId, int(x*5), int(y*5), hero, stats, equip,
)
srv.clientConnections[id] = client
client.SendPacketToClient(infoPacket)
client.SendPacketToClient(mapgenPacket)
log.Printf("Client connected with an id of %s", id)
realm.AddPlayer(id, state.Act)
// for each connection, send the AddPlayer packet for the new player
for _, connection := range srv.clientConnections {
conId := connection.GetUniqueId()
connection.SendPacketToClient(addNew)
if conId == id {
continue
}
// send an AddPlayer for existing connections to the new player
conState := connection.GetPlayerState()
conActId := conState.Act
conName := conState.HeroName
conHero := conState.HeroType
conEquip := conState.Equipment
conStats := *conState.Stats
conX, conY := 0, 0
addExisting := packet.CreateAddPlayerPacket(
conId, conName, conActId, conX, conY, conHero, conStats, conEquip,
)
client.SendPacketToClient(addExisting)
}
}
func OnClientDisconnected(client ClientConnection) {
log.Printf("Client disconnected with an id of %s", client.GetUniqueId())
clientId := client.GetUniqueId()
delete(singletonServer.clientConnections, clientId)
singletonServer.realm.RemovePlayer(clientId)
}
func OnPacketReceived(client ClientConnection, p packet.NetPacket) error {
switch p.PacketType {
case packettype.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 = p.PacketData.(packet.MovePlayerPacket).DestX
playerState.Y = p.PacketData.(packet.MovePlayerPacket).DestY
// ----------------------------------------------------------------
for _, player := range singletonServer.clientConnections {
player.SendPacketToClient(p)
}
}
return nil
}

View File

@ -1,209 +1,67 @@
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"
packet "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
packettype "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
d2udp "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2server/d2udpclientconnection"
"github.com/OpenDiablo2/OpenDiablo2/d2script"
"github.com/robertkrimen/otto"
)
type GameServer struct {
sync.RWMutex
version string
clientConnections map[string]ClientConnection
manager *ConnectionManager
mapEngines []*d2mapengine.MapEngine
realm *d2mapengine.MapRealm
scriptEngine *d2script.ScriptEngine
udpConnection *net.UDPConn
seed int64
running bool
maxClients int
lastAdvance float64
}
var singletonServer *GameServer
func (srv *GameServer) handlePlayerConnRequest(addr *net.UDPAddr, data string) {
packetData := &packet.PlayerConnectionRequestPacket{}
json.Unmarshal([]byte(data), packetData)
func Create(openNetworkServer bool) {
log.Print("Creating GameServer")
if singletonServer != nil {
return
srvCon := srv.udpConnection
packetId := packetData.Id
clientCon := d2udp.CreateUDPClientConnection(srvCon, packetId, addr)
state := packetData.PlayerState
clientCon.SetPlayerState(state)
OnClientConnected(clientCon)
}
func (srv *GameServer) handleMovePlayer(addr *net.UDPAddr, data string) {
packetData := &packet.MovePlayerPacket{}
json.Unmarshal([]byte(data), packetData)
netPacket := packet.NetPacket{
PacketType: packettype.MovePlayer,
PacketData: packetData,
}
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()
for _, player := range srv.clientConnections {
player.SendPacketToClient(netPacket)
}
}
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)
}
singletonServer.udpConnection.SetReadBuffer(4096)
func (srv *GameServer) handlePong(addr *net.UDPAddr, data string) {
packetData := packet.PlayerConnectionRequestPacket{}
json.Unmarshal([]byte(data), &packetData)
srv.manager.Recv(packetData.Id)
}
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)
io.Copy(sb, reader)
stringData := sb.String()
switch packetType {
case d2netpackettype.PlayerConnectionRequest:
packetData := d2netpacket.PlayerConnectionRequestPacket{}
json.Unmarshal([]byte(stringData), &packetData)
clientConnection := d2udpclientconnection.CreateUDPClientConnection(singletonServer.udpConnection, packetData.Id, addr)
clientConnection.SetPlayerState(packetData.PlayerState)
OnClientConnected(clientConnection)
case d2netpackettype.MovePlayer:
packetData := d2netpacket.MovePlayerPacket{}
json.Unmarshal([]byte(stringData), &packetData)
netPacket := d2netpacket.NetPacket{
PacketType: packetType,
PacketData: packetData,
}
for _, player := range singletonServer.clientConnections {
player.SendPacketToClient(netPacket)
}
case d2netpackettype.Pong:
packetData := d2netpacket.PlayerConnectionRequestPacket{}
json.Unmarshal([]byte(stringData), &packetData)
singletonServer.manager.Recv(packetData.Id)
case d2netpackettype.ServerClosed:
singletonServer.manager.Shutdown()
case d2netpackettype.PlayerDisconnectionNotification:
var packet d2netpacket.PlayerDisconnectRequestPacket
json.Unmarshal([]byte(stringData), &packet)
log.Printf("Received disconnect: %s", packet.Id)
}
}
}
func Run() {
log.Print("Starting GameServer")
singletonServer.running = true
singletonServer.scriptEngine.RunScript("scripts/server/server.js")
if singletonServer.udpConnection != nil {
go runNetworkServer()
}
log.Print("Network server has been started")
}
func Stop() {
log.Print("Stopping GameServer")
singletonServer.running = false
if singletonServer.udpConnection != nil {
singletonServer.udpConnection.Close()
}
}
func Destroy() {
if singletonServer == nil {
return
}
log.Print("Destroying GameServer")
Stop()
}
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
client.SendPacketToClient(d2netpacket.CreateUpdateServerInfoPacket(singletonServer.seed, client.GetUniqueId()))
client.SendPacketToClient(d2netpacket.CreateGenerateMapPacket(d2enum.RegionAct1Town))
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 {
connection.SendPacketToClient(createPlayerPacket)
if connection.GetUniqueId() == client.GetUniqueId() {
continue
}
conPlayerState := connection.GetPlayerState()
client.SendPacketToClient(d2netpacket.CreateAddPlayerPacket(connection.GetUniqueId(), conPlayerState.HeroName,
int(conPlayerState.X*5)+3, int(conPlayerState.Y*5)+3, conPlayerState.HeroType, *conPlayerState.Stats, conPlayerState.Equipment))
}
}
func OnClientDisconnected(client ClientConnection) {
log.Printf("Client disconnected with an id of %s", client.GetUniqueId())
delete(singletonServer.clientConnections, client.GetUniqueId())
}
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 {
player.SendPacketToClient(packet)
}
}
return nil
func (srv *GameServer) handlePlayerDisconnectNotification(data string) {
var packet packet.PlayerDisconnectRequestPacket
json.Unmarshal([]byte(data), &packet)
log.Printf("Received disconnect: %s", packet.Id)
}