1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-02 22:11:10 +00:00

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

View File

@ -9,6 +9,7 @@ import (
type LevelSubstitutionRecord struct { type LevelSubstitutionRecord struct {
// Description, reference only. // Description, reference only.
Name string // Name Name string // Name
// This value is used in Levels.txt, in the column 'SubType'. You'll notice // 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 // 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 // groups. If you count each row of a group starting from 0, then you'll

View File

@ -2,51 +2,68 @@ package d2datadict
import ( import (
"log" "log"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
) )
type LevelTypeRecord struct { type LevelTypeRecord struct {
Name string Name string // Name
Id int Id int // Id
Files [32]string Files []string // File 1 -- File 32
Beta bool Beta bool // Beta
Act int Act int // Act
Expansion bool Expansion bool // Expansion
} }
var LevelTypes []LevelTypeRecord var LevelTypes map[d2enum.RegionIdType]*LevelTypeRecord
func LoadLevelTypes(file []byte) { func LoadLevelTypes(file []byte) {
data := strings.Split(string(file), "\r\n")[1:] LevelTypes = make(map[d2enum.RegionIdType]*LevelTypeRecord)
LevelTypes = make([]LevelTypeRecord, len(data)) dict := d2common.LoadDataDictionary(string(file))
for i, j := 0, 0; i < len(data); i, j = i+1, j+1 { for idx := range dict.Data {
idx := -1 record := &LevelTypeRecord{
inc := func() int { Name: dict.GetString("Name", idx),
idx++ Id: dict.GetNumber("Id", idx),
return 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 { LevelTypes[d2enum.RegionIdType(record.Id)] = record
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"
} }
log.Printf("Loaded %d LevelType records", len(LevelTypes)) log.Printf("Loaded %d LevelType records", len(LevelTypes))
} }

View File

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

View File

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

View File

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

View File

@ -16,6 +16,7 @@ type Configuration struct {
FullScreen bool FullScreen bool
RunInBackground bool RunInBackground bool
VsyncEnabled bool VsyncEnabled bool
MaxConnections int
} }
var singleton = getDefaultConfig() 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 // Represents the map data for a specific location
type MapEngine struct { type MapEngine struct {
seed int64 // The map seed seed int64 // The map seed
entities []d2mapentity.MapEntity // Entities on the map entities []d2mapentity.MapEntity // Entities on the map
tiles []d2ds1.TileRecord // The map tiles tiles []d2ds1.TileRecord // The map tiles
size d2common.Size // The size of the map, in tiles size d2common.Size // The size of the map, in tiles
levelType d2datadict.LevelTypeRecord // The level type of this map levelType *d2datadict.LevelTypeRecord // The level type of this map
dt1TileData []d2dt1.Tile // The DT1 tile data dt1TileData []d2dt1.Tile // The DT1 tile data
walkMesh []d2common.PathTile // The walk mesh walkMesh []d2common.PathTile // The walk mesh
startSubTileX int // The starting X position startSubTileX int // The starting X position
startSubTileY int // The starting Y position startSubTileY int // The starting Y position
dt1Files []string // The list of DS1 strings dt1Files []string // The list of DS1 strings
} }
// Creates a new instance of the map engine // 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 // Returns the level type of this map
func (m *MapEngine) LevelType() d2datadict.LevelTypeRecord { func (m *MapEngine) LevelType() *d2datadict.LevelTypeRecord {
return m.levelType 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 baseWalkSpeed = 6.0
var baseRunSpeed = 9.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{ object := &d2datadict.ObjectLookupRecord{
Mode: d2enum.AnimationModePlayerTownNeutral.String(), Mode: d2enum.AnimationModePlayerTownNeutral.String(),
Base: "/data/global/chars", 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) { func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
rand.Seed(mapEngine.Seed()) rand.Seed(mapEngine.Seed())
wilderness1Details := d2datadict.GetLevelDetails(2) wilderness1Details := d2datadict.GetLevelDetailsByLevelId(2)
mapEngine.ResetMap(d2enum.RegionAct1Town, 150, 150) mapEngine.ResetMap(d2enum.RegionAct1Town, 150, 150)
mapWidth := mapEngine.Size().Width mapWidth := mapEngine.Size().Width
mapHeight := mapEngine.Size().Height mapHeight := mapEngine.Size().Height
@ -59,7 +59,7 @@ func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
// West Exit // West Exit
mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height) 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 { } else {
// North Exit // North Exit
mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height) 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) { func generateWilderness1TownEast(mapEngine *d2mapengine.MapEngine, startX, startY int) {
levelDetails := d2datadict.GetLevelDetails(2) levelDetails := d2datadict.GetLevelDetailsByLevelId(2)
fenceNorthStamp := []*d2mapstamp.Stamp{ fenceNorthStamp := []*d2mapstamp.Stamp{
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0), loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0),
@ -102,36 +102,36 @@ func generateWilderness1TownEast(mapEngine *d2mapengine.MapEngine, startX, start
areaRect := d2common.Rectangle{ areaRect := d2common.Rectangle{
Left: startX, Left: startX,
Top: startY+9, Top: startY + 9,
Width: levelDetails.SizeXNormal, Width: levelDetails.SizeXNormal,
Height: levelDetails.SizeYNormal-3, Height: levelDetails.SizeYNormal - 3,
} }
generateWilderness1Contents(mapEngine, areaRect) generateWilderness1Contents(mapEngine, areaRect)
// Draw the north and south fence // Draw the north and south fence
for i := 0; i < 9; i++ { for i := 0; i < 9; i++ {
mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9), startY) 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 // West fence
for i := 1; i < 6; i++ { 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 // East Fence
for i := 1; i < 10; i++ { 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(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal+6)
mapEngine.PlaceStamp(fenceWestEdge, startX, startY+ (levelDetails.SizeYNormal-3) - 45) mapEngine.PlaceStamp(fenceWestEdge, startX, startY+(levelDetails.SizeYNormal-3)-45)
mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal, startY) mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal, startY)
mapEngine.PlaceStamp(fenceSouthEastStamp, startX+levelDetails.SizeXNormal, startY+levelDetails.SizeYNormal+6) mapEngine.PlaceStamp(fenceSouthEastStamp, startX+levelDetails.SizeXNormal, startY+levelDetails.SizeYNormal+6)
} }
func generateWilderness1TownSouth(mapEngine *d2mapengine.MapEngine, startX, startY int) { func generateWilderness1TownSouth(mapEngine *d2mapengine.MapEngine, startX, startY int) {
levelDetails := d2datadict.GetLevelDetails(2) levelDetails := d2datadict.GetLevelDetailsByLevelId(2)
fenceNorthStamp := []*d2mapstamp.Stamp{ fenceNorthStamp := []*d2mapstamp.Stamp{
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0), loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0),
@ -184,7 +184,7 @@ func generateWilderness1TownSouth(mapEngine *d2mapengine.MapEngine, startX, star
} }
func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, startY int) { func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, startY int) {
levelDetails := d2datadict.GetLevelDetails(2) levelDetails := d2datadict.GetLevelDetailsByLevelId(2)
fenceEastEdge := loadPreset(mapEngine, d2wilderness.TreeBoxSouthWest, 0) fenceEastEdge := loadPreset(mapEngine, d2wilderness.TreeBoxSouthWest, 0)
fenceNorthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthWest, 0) fenceNorthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthWest, 0)
@ -218,30 +218,30 @@ func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, start
// Draw the north and south fences // Draw the north and south fences
for i := 0; i < 9; i++ { for i := 0; i < 9; i++ {
if i > 0 && i < 8 { 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) mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9)-1, startY+levelDetails.SizeYNormal-12)
} }
// Draw the east fence // Draw the east fence
for i := 0; i < 6; i++ { 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 // Draw the west fence
for i := 0; i < 9; i++ { 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 // 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(fenceNorthWestStamp, startX, startY-15)
mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal-12) mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal-12)
mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal-9, startY-15) mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal-9, startY-15)
areaRect := d2common.Rectangle{ areaRect := d2common.Rectangle{
Left: startX + 9, Left: startX + 9,
Top: startY-10, Top: startY - 10,
Width: levelDetails.SizeXNormal - 9, Width: levelDetails.SizeXNormal - 9,
Height: levelDetails.SizeYNormal - 2, Height: levelDetails.SizeYNormal - 2,
} }
@ -250,7 +250,7 @@ func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, start
} }
func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2common.Rectangle) { func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2common.Rectangle) {
levelDetails := d2datadict.GetLevelDetails(2) levelDetails := d2datadict.GetLevelDetailsByLevelId(2)
denOfEvil := loadPreset(mapEngine, d2wilderness.DenOfEvilEntrance, 0) denOfEvil := loadPreset(mapEngine, d2wilderness.DenOfEvilEntrance, 0)
denOfEvilLoc := d2common.Point{ denOfEvilLoc := d2common.Point{
@ -295,10 +295,10 @@ func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2common
for numPlaced < 25 { for numPlaced < 25 {
stamp := stuff[rand.Intn(len(stuff))] stamp := stuff[rand.Intn(len(stuff))]
stampRect := d2common.Rectangle { stampRect := d2common.Rectangle{
Left: rect.Left+ rand.Intn(rect.Width) - stamp.Size().Width, Left: rect.Left + rand.Intn(rect.Width) - stamp.Size().Width,
Top: rect.Top+rand.Intn(rect.Height) - stamp.Size().Height, Top: rect.Top + rand.Intn(rect.Height) - stamp.Size().Height,
Width: stamp.Size().Width, Width: stamp.Size().Width,
Height: stamp.Size().Height, Height: stamp.Size().Height,
} }

View File

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

View File

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

View File

@ -20,7 +20,7 @@ import (
type Game struct { type Game struct {
gameClient *d2client.GameClient gameClient *d2client.GameClient
mapRenderer *d2maprenderer.MapRenderer MapRenderer *d2maprenderer.MapRenderer
gameControls *d2player.GameControls // TODO: Hack gameControls *d2player.GameControls // TODO: Hack
localPlayer *d2mapentity.Player localPlayer *d2mapentity.Player
lastRegionType d2enum.RegionIdType lastRegionType d2enum.RegionIdType
@ -35,7 +35,7 @@ func CreateGame(gameClient *d2client.GameClient) *Game {
localPlayer: nil, localPlayer: nil,
lastRegionType: d2enum.RegionNone, lastRegionType: d2enum.RegionNone,
ticksSinceLevelCheck: 0, ticksSinceLevelCheck: 0,
mapRenderer: d2maprenderer.CreateMapRenderer(gameClient.MapEngine), MapRenderer: d2maprenderer.CreateMapRenderer(gameClient.MapEngine),
escapeMenu: NewEscapeMenu(), escapeMenu: NewEscapeMenu(),
} }
result.escapeMenu.OnLoad() result.escapeMenu.OnLoad()
@ -56,11 +56,13 @@ func (v *Game) OnUnload() error {
func (v *Game) Render(screen d2render.Surface) error { func (v *Game) Render(screen d2render.Surface) error {
if v.gameClient.RegenMap { if v.gameClient.RegenMap {
v.gameClient.RegenMap = false v.gameClient.RegenMap = false
v.mapRenderer.RegenerateTileCache() v.MapRenderer.RegenerateTileCache()
} }
screen.Clear(color.Black) if v.MapRenderer != nil {
v.mapRenderer.Render(screen) screen.Clear(color.Black)
v.MapRenderer.Render(screen)
}
if v.gameControls != nil { if v.gameControls != nil {
v.gameControls.Render(screen) v.gameControls.Render(screen)
@ -69,7 +71,7 @@ func (v *Game) Render(screen d2render.Surface) error {
return nil return nil
} }
var hideZoneTextAfterSeconds = 2.0 var zoneTextDuration = 2.0 // seconds
func (v *Game) Advance(tickTime float64) error { func (v *Game) Advance(tickTime float64) error {
if (v.escapeMenu != nil && !v.escapeMenu.IsOpen()) || len(v.gameClient.Players) != 1 { 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 // skip showing zone change text the first time we enter the world
if v.lastRegionType != d2enum.RegionNone && v.lastRegionType != tile.RegionType { notNone := v.lastRegionType != d2enum.RegionNone
//TODO: Should not be using RegionType as an index - this will return incorrect LevelDetails record for most of the zones. differentTileType := v.lastRegionType != tile.RegionType
v.gameControls.SetZoneChangeText(fmt.Sprintf("Entering The %s", d2datadict.LevelDetails[int(tile.RegionType)].LevelDisplayName)) 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.ShowZoneChangeText()
v.gameControls.HideZoneChangeTextAfter(hideZoneTextAfterSeconds) v.gameControls.HideZoneChangeTextAfter(zoneTextDuration)
} }
v.lastRegionType = tile.RegionType v.lastRegionType = tile.RegionType
} }
@ -114,7 +124,9 @@ func (v *Game) Advance(tickTime float64) error {
continue continue
} }
v.localPlayer = player 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() v.gameControls.Load()
d2input.BindHandler(v.gameControls) d2input.BindHandler(v.gameControls)
@ -124,14 +136,16 @@ func (v *Game) Advance(tickTime float64) error {
// Update the camera to focus on the player // Update the camera to focus on the player
if v.localPlayer != nil && !v.gameControls.FreeCam { if v.localPlayer != nil && !v.gameControls.FreeCam {
rx, ry := v.mapRenderer.WorldToOrtho(v.localPlayer.LocationX/5, v.localPlayer.LocationY/5) wx, wy := v.localPlayer.LocationX/5, v.localPlayer.LocationY/5
v.mapRenderer.MoveCameraTo(rx, ry) rx, ry := v.MapRenderer.WorldToOrtho(wx, wy)
v.MapRenderer.MoveCameraTo(rx, ry)
} }
return nil return nil
} }
func (v *Game) OnPlayerMove(x, y float64) { func (v *Game) OnPlayerMove(x2, y2 float64) {
heroPosX := v.localPlayer.LocationX / 5.0 id := v.gameClient.PlayerId
heroPosY := v.localPlayer.LocationY / 5.0 x1, y1 := v.localPlayer.LocationX/5.0, v.localPlayer.LocationY/5.0
v.gameClient.SendPacketToServer(d2netpacket.CreateMovePlayerPacket(v.gameClient.PlayerId, heroPosX, heroPosY, x, y)) 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"` HeroType d2enum.Hero `json:"heroType"`
HeroLevel int `json:"heroLevel"` HeroLevel int `json:"heroLevel"`
Act int `json:"act"` Act int `json:"act"`
Level int `json:"actLevel"`
FilePath string `json:"-"` FilePath string `json:"-"`
Equipment d2inventory.CharacterEquipment `json:"equipment"` Equipment d2inventory.CharacterEquipment `json:"equipment"`
Stats *d2hero.HeroStatsState `json:"stats"` Stats *d2hero.HeroStatsState `json:"stats"`
X float64 `json:"x"` X float64 `json:"x"`
Y float64 `json:"y"` Y float64 `json:"y"`
} }
@ -45,8 +46,8 @@ func GetAllPlayerStates() []*PlayerState {
gameState := LoadPlayerState(path.Join(basePath, file.Name())) gameState := LoadPlayerState(path.Join(basePath, file.Name()))
if gameState == nil || gameState.HeroType == d2enum.HeroNone { if gameState == nil || gameState.HeroType == d2enum.HeroNone {
continue continue
// temporarily loading default class stats if the character was created before saving stats was introduced // temporarily loading default class stats if the character was created before saving stats was introduced
// to be removed in the future // to be removed in the future
} else if gameState.Stats == nil { } else if gameState.Stats == nil {
gameState.Stats = d2hero.CreateHeroStatsState(gameState.HeroType, *d2datadict.CharStats[gameState.HeroType], 1, 0) gameState.Stats = d2hero.CreateHeroStatsState(gameState.HeroType, *d2datadict.CharStats[gameState.HeroType], 1, 0)
gameState.Save() gameState.Save()
@ -83,8 +84,9 @@ func CreatePlayerState(heroName string, hero d2enum.Hero, classStats d2datadict.
result := &PlayerState{ result := &PlayerState{
HeroName: heroName, HeroName: heroName,
HeroType: hero, HeroType: hero,
Act: 1, Act: 0,
Stats: d2hero.CreateHeroStatsState(hero, classStats, 1, 0), Level: 1,
Stats: d2hero.CreateHeroStatsState(hero, classStats, 1, 0),
Equipment: d2inventory.HeroObjects[hero], Equipment: d2inventory.HeroObjects[hero],
FilePath: "", 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 package d2client
import ( import (
"fmt"
"log" "log"
"os" "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/d2mapengine"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" d2cct "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2localclient"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2remoteclient"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
) )
type GameClient struct { type GameClient struct {
clientConnection ClientConnection clientConnection ClientConnection
connectionType d2clientconnectiontype.ClientConnectionType connectionType d2cct.ClientConnectionType
GameState *d2player.PlayerState GameState *d2player.PlayerState
MapEngine *d2mapengine.MapEngine MapEngine *d2mapengine.MapEngine
MapRenderer *d2maprenderer.MapRenderer
realm *d2mapengine.MapRealm
PlayerId string PlayerId string
Players map[string]*d2mapentity.Player Players map[string]*d2mapentity.Player
Seed int64 Seed int64
RegenMap bool RegenMap bool
} }
func Create(connectionType d2clientconnectiontype.ClientConnectionType) (*GameClient, error) { // Using the `clientConnection`, opens a connection and passes the savefile path
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
}
func (g *GameClient) Open(connectionString string, saveFilePath string) error { func (g *GameClient) Open(connectionString string, saveFilePath string) error {
return g.clientConnection.Open(connectionString, saveFilePath) return g.clientConnection.Open(connectionString, saveFilePath)
} }
// Closes the `clientConnection`
func (g *GameClient) Close() error { func (g *GameClient) Close() error {
return g.clientConnection.Close() return g.clientConnection.Close()
} }
// Closes the `clientConnection`
func (g *GameClient) Destroy() error { func (g *GameClient) Destroy() error {
return g.clientConnection.Close() return g.clientConnection.Close()
} }
// Routes the incoming packets to the packet handlers
func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error { func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
switch packet.PacketType { switch packet.PacketType {
case d2netpackettype.GenerateMap:
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 // UNSURE: should we be bubbling up errors from these handler calls?
if regionType == d2enum.RegionAct1Town { case d2netpackettype.UpdateServerInfo:
player.SetIsInTown(true) g.handleUpdateServerInfo(packet)
} else {
player.SetIsInTown(false) case d2netpackettype.AddPlayer:
} g.handleAddPlayer(packet)
player.SetAnimationMode(player.GetAnimationMode().String())
}) case d2netpackettype.GenerateMap:
} g.handleGenerateMap(packet)
case d2netpackettype.MovePlayer:
g.handleMovePlayer(packet)
case d2netpackettype.Ping: case d2netpackettype.Ping:
g.clientConnection.SendPacketToServer(d2netpacket.CreatePongPacket(g.PlayerId)) g.handlePong(packet)
case d2netpackettype.ServerClosed: case d2netpackettype.ServerClosed:
// TODO: Need to be tied into a character save and exit g.handleServerClosed(packet)
log.Print("Server has been closed")
os.Exit(0)
default: default:
log.Fatalf("Invalid packet type: %d", packet.PacketType) log.Fatalf("Invalid packet type: %d", packet.PacketType)
} }
return nil return nil
} }
// Using the `clientConnection`, sends a packet to the server
func (g *GameClient) SendPacketToServer(packet d2netpacket.NetPacket) error { func (g *GameClient) SendPacketToServer(packet d2netpacket.NetPacket) error {
return g.clientConnection.SendPacketToServer(packet) return g.clientConnection.SendPacketToServer(packet)
} }
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"` Name string `json:"name"`
X int `json:"x"` X int `json:"x"`
Y int `json:"y"` Y int `json:"y"`
Act int `json:"act"`
HeroType d2enum.Hero `json:"hero"` HeroType d2enum.Hero `json:"hero"`
Equipment d2inventory.CharacterEquipment `json:"equipment"` Equipment d2inventory.CharacterEquipment `json:"equipment"`
Stats d2hero.HeroStatsState `json:"heroStats"` Stats d2hero.HeroStatsState `json:"heroStats"`
} }
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{ return NetPacket{
PacketType: d2netpackettype.AddPlayer, PacketType: d2netpackettype.AddPlayer,
PacketData: AddPlayerPacket{ PacketData: AddPlayerPacket{
Id: id, Id: id,
Name: name, Name: name,
Act: act,
X: x, X: x,
Y: y, Y: y,
HeroType: heroType, HeroType: heroType,
Equipment: equipment, Equipment: equipment,
Stats: stats, Stats: stats,
}, },
} }
} }

View File

@ -1,19 +1,20 @@
package d2netpacket package d2netpacket
import ( import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
) )
type GenerateMapPacket struct { 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{ return NetPacket{
PacketType: d2netpackettype.GenerateMap, PacketType: d2netpackettype.GenerateMap,
PacketData: GenerateMapPacket{ PacketData: GenerateMapPacket{
RegionType: regionType, ActId: actId,
LevelId: levelId,
}, },
} }

View File

@ -8,13 +8,16 @@ import (
"time" "time"
) )
// ConnectionManager is responsible for cleanup up connections accepted by the game server. As the server communicates over // ConnectionManager is responsible for cleanup up connections accepted by the
// UDP and is stateless we need to implement some loose state management via a ping/pong system. ConnectionManager also handles // 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. // communication for graceful shutdowns.
// //
// retries: # of attempts before the dropping the client // retries: # of attempts before the dropping the client
// interval: How long to wait before each ping/pong test // 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 // status: map of inflight ping/pong requests
type ConnectionManager struct { type ConnectionManager struct {
sync.RWMutex sync.RWMutex
@ -50,7 +53,8 @@ func (c *ConnectionManager) Run() {
func (c *ConnectionManager) checkPeers() { func (c *ConnectionManager) checkPeers() {
for id, connection := range c.gameServer.clientConnections { for id, connection := range c.gameServer.clientConnections {
if connection.GetConnectionType() != d2clientconnectiontype.Local { 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) log.Printf("Cannot ping client id: %s", id)
} }
c.RWMutex.Lock() 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) { func (c *ConnectionManager) Recv(id string) {
c.status[id] = 0 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. // Shutdown will notify all of the clients that the server has been shutdown.
func (c *ConnectionManager) Shutdown() { func (c *ConnectionManager) Shutdown() {
// TODO: Currently this will never actually get called as the go routines are never signaled about the application termination. // TODO: Currently this will never actually get called as the go routines
// Things can be done more cleanly once we have graceful exits however we still need to account for other OS Signals // 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...") log.Print("Notifying clients server is shutting down...")
for _, connection := range c.gameServer.clientConnections { for _, connection := range c.gameServer.clientConnections {
connection.SendPacketToClient(d2netpacket.CreateServerClosedPacket()) 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 package d2server
import ( import (
"bytes"
"compress/gzip"
"encoding/json" "encoding/json"
"fmt"
"io"
"log" "log"
"net" "net"
"strings"
"sync" "sync"
"time"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
packet "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" packettype "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" d2udp "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2server/d2udpclientconnection"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2server/d2udpclientconnection"
"github.com/OpenDiablo2/OpenDiablo2/d2script" "github.com/OpenDiablo2/OpenDiablo2/d2script"
"github.com/robertkrimen/otto"
) )
type GameServer struct { type GameServer struct {
sync.RWMutex sync.RWMutex
version string
clientConnections map[string]ClientConnection clientConnections map[string]ClientConnection
manager *ConnectionManager manager *ConnectionManager
mapEngines []*d2mapengine.MapEngine realm *d2mapengine.MapRealm
scriptEngine *d2script.ScriptEngine scriptEngine *d2script.ScriptEngine
udpConnection *net.UDPConn udpConnection *net.UDPConn
seed int64 seed int64
running bool 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) { srvCon := srv.udpConnection
log.Print("Creating GameServer") packetId := packetData.Id
if singletonServer != nil { clientCon := d2udp.CreateUDPClientConnection(srvCon, packetId, addr)
return
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{ for _, player := range srv.clientConnections {
clientConnections: make(map[string]ClientConnection), player.SendPacketToClient(netPacket)
mapEngines: make([]*d2mapengine.MapEngine, 0),
scriptEngine: d2script.CreateScriptEngine(),
seed: time.Now().UnixNano(),
}
singletonServer.manager = CreateConnectionManager(singletonServer)
mapEngine := d2mapengine.CreateMapEngine()
mapEngine.SetSeed(singletonServer.seed)
mapEngine.ResetMap(d2enum.RegionAct1Town, 100, 100) // TODO: Mapgen - Needs levels.txt stuff
d2mapgen.GenerateAct1Overworld(mapEngine)
singletonServer.mapEngines = append(singletonServer.mapEngines, mapEngine)
singletonServer.scriptEngine.AddFunction("getMapEngines", func(call otto.FunctionCall) otto.Value {
val, err := singletonServer.scriptEngine.ToValue(singletonServer.mapEngines)
if err != nil {
fmt.Print(err.Error())
}
return val
})
if openNetworkServer {
createNetworkServer()
} }
} }
func createNetworkServer() { func (srv *GameServer) handlePong(addr *net.UDPAddr, data string) {
s, err := net.ResolveUDPAddr("udp4", "0.0.0.0:6669") packetData := packet.PlayerConnectionRequestPacket{}
if err != nil { json.Unmarshal([]byte(data), &packetData)
panic(err) srv.manager.Recv(packetData.Id)
}
singletonServer.udpConnection, err = net.ListenUDP("udp4", s)
if err != nil {
panic(err)
}
singletonServer.udpConnection.SetReadBuffer(4096)
} }
func runNetworkServer() { func (srv *GameServer) handlePlayerDisconnectNotification(data string) {
buffer := make([]byte, 4096) var packet packet.PlayerDisconnectRequestPacket
for singletonServer.running { json.Unmarshal([]byte(data), &packet)
_, addr, err := singletonServer.udpConnection.ReadFromUDP(buffer) log.Printf("Received disconnect: %s", packet.Id)
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
} }