diff --git a/d2common/d2data/d2datadict/level_presets.go b/d2common/d2data/d2datadict/level_presets.go index 30a2db5e..ca33891f 100644 --- a/d2common/d2data/d2datadict/level_presets.go +++ b/d2common/d2data/d2datadict/level_presets.go @@ -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] diff --git a/d2common/d2data/d2datadict/level_sub.go b/d2common/d2data/d2datadict/level_sub.go index b880f87d..e8e524b4 100644 --- a/d2common/d2data/d2datadict/level_sub.go +++ b/d2common/d2data/d2datadict/level_sub.go @@ -9,7 +9,6 @@ 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 diff --git a/d2common/d2data/d2datadict/level_types.go b/d2common/d2data/d2datadict/level_types.go index f3e357c5..b7a576b8 100644 --- a/d2common/d2data/d2datadict/level_types.go +++ b/d2common/d2data/d2datadict/level_types.go @@ -2,68 +2,51 @@ package d2datadict import ( "log" + "strings" "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" ) type LevelTypeRecord struct { - Name string // Name - Id int // Id - Files []string // File 1 -- File 32 - Beta bool // Beta - Act int // Act - Expansion bool // Expansion + Name string + Id int + Files [32]string + Beta bool + Act int + Expansion bool } -var LevelTypes map[d2enum.RegionIdType]*LevelTypeRecord +var LevelTypes []LevelTypeRecord func LoadLevelTypes(file []byte) { - 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, + 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[d2enum.RegionIdType(record.Id)] = record + 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" } log.Printf("Loaded %d LevelType records", len(LevelTypes)) } diff --git a/d2common/d2data/d2datadict/levels.go b/d2common/d2data/d2datadict/levels.go index f1b937b0..280d45e6 100644 --- a/d2common/d2data/d2datadict/levels.go +++ b/d2common/d2data/d2datadict/levels.go @@ -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. Otherwise use the same value here that are used in + // X size for the area. Othervise 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,14 +141,28 @@ 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 . - WarpLevelId []int // Vis0 -- Vis7 + LevelLinkId0 int // Vis0 + 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 // 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 . - WarpGraphicsId []int // Warp0 -- Warp7 + WarpGraphicsId0 int // Warp0 + 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 LightIntensity int // Intensity @@ -363,7 +377,7 @@ type LevelDetailsRecord struct { var LevelDetails map[int]*LevelDetailsRecord -func GetLevelDetailsByLevelId(id int) *LevelDetailsRecord { +func GetLevelDetails(id int) *LevelDetailsRecord { for i := 0; i < len(LevelDetails); i++ { if LevelDetails[i].Id == id { return LevelDetails[i] @@ -373,138 +387,60 @@ func GetLevelDetailsByLevelId(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), - 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), - }, + 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), LightIntensity: dict.GetNumber("Intensity", idx), Red: dict.GetNumber("Red", idx), Green: dict.GetNumber("Green", idx), @@ -604,7 +540,6 @@ 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)) diff --git a/d2common/d2enum/level_generation_types.go b/d2common/d2enum/level_generation_types.go index b98c4dc2..7f11bee1 100644 --- a/d2common/d2enum/level_generation_types.go +++ b/d2common/d2enum/level_generation_types.go @@ -11,8 +11,7 @@ package d2enum type LevelGenerationType int const ( - LevelTypeNone LevelGenerationType = iota - LevelTypeRandomMaze + LevelTypeRandomMaze LevelGenerationType = iota LevelTypePreset LevelTypeWilderness ) diff --git a/d2core/d2config/config.go b/d2core/d2config/config.go index b7293b27..aefdfa80 100644 --- a/d2core/d2config/config.go +++ b/d2core/d2config/config.go @@ -18,7 +18,6 @@ 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", diff --git a/d2core/d2config/d2config.go b/d2core/d2config/d2config.go index dbf8005d..665f6ad6 100644 --- a/d2core/d2config/d2config.go +++ b/d2core/d2config/d2config.go @@ -16,7 +16,6 @@ type Configuration struct { FullScreen bool RunInBackground bool VsyncEnabled bool - MaxConnections int } var singleton = getDefaultConfig() diff --git a/d2core/d2map/d2mapengine/act.go b/d2core/d2map/d2mapengine/act.go deleted file mode 100644 index 63ef8fde..00000000 --- a/d2core/d2map/d2mapengine/act.go +++ /dev/null @@ -1,60 +0,0 @@ -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() -} diff --git a/d2core/d2map/d2mapengine/engine.go b/d2core/d2map/d2mapengine/engine.go index 07d34a4e..1fae3662 100644 --- a/d2core/d2map/d2mapengine/engine.go +++ b/d2core/d2map/d2mapengine/engine.go @@ -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 } diff --git a/d2core/d2map/d2mapengine/generator.go b/d2core/d2map/d2mapengine/generator.go deleted file mode 100644 index ac935565..00000000 --- a/d2core/d2map/d2mapengine/generator.go +++ /dev/null @@ -1,6 +0,0 @@ -package d2mapengine - -type MapGenerator interface { - init(seed int64, level *MapLevel, engine *MapEngine) - generate() -} diff --git a/d2core/d2map/d2mapengine/generator_maze.go b/d2core/d2map/d2mapengine/generator_maze.go deleted file mode 100644 index 8958a871..00000000 --- a/d2core/d2map/d2mapengine/generator_maze.go +++ /dev/null @@ -1,15 +0,0 @@ -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() {} diff --git a/d2core/d2map/d2mapengine/generator_preset.go b/d2core/d2map/d2mapengine/generator_preset.go deleted file mode 100644 index 9fe3e9a2..00000000 --- a/d2core/d2map/d2mapengine/generator_preset.go +++ /dev/null @@ -1,66 +0,0 @@ -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) -} diff --git a/d2core/d2map/d2mapengine/generator_wilderness.go b/d2core/d2map/d2mapengine/generator_wilderness.go deleted file mode 100644 index 363d40e2..00000000 --- a/d2core/d2map/d2mapengine/generator_wilderness.go +++ /dev/null @@ -1,15 +0,0 @@ -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() {} diff --git a/d2core/d2map/d2mapengine/level.go b/d2core/d2map/d2mapengine/level.go deleted file mode 100644 index 2279a82f..00000000 --- a/d2core/d2map/d2mapengine/level.go +++ /dev/null @@ -1,79 +0,0 @@ -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 -} diff --git a/d2core/d2map/d2mapengine/realm.go b/d2core/d2map/d2mapengine/realm.go deleted file mode 100644 index 33edc914..00000000 --- a/d2core/d2map/d2mapengine/realm.go +++ /dev/null @@ -1,114 +0,0 @@ -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) -} diff --git a/d2core/d2map/d2mapentity/player.go b/d2core/d2map/d2mapentity/player.go index 3111765c..38b1ecce 100644 --- a/d2core/d2map/d2mapentity/player.go +++ b/d2core/d2map/d2mapentity/player.go @@ -34,7 +34,7 @@ type Player struct { var baseWalkSpeed = 6.0 var baseRunSpeed = 9.0 -func CreatePlayer(id, name string, ActId, LevelId, x, y, direction int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) *Player { +func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) *Player { object := &d2datadict.ObjectLookupRecord{ Mode: d2enum.AnimationModePlayerTownNeutral.String(), Base: "/data/global/chars", diff --git a/d2core/d2map/d2mapgen/act1_overworld.go b/d2core/d2map/d2mapgen/act1_overworld.go index 5c190d4f..fd215af6 100644 --- a/d2core/d2map/d2mapgen/act1_overworld.go +++ b/d2core/d2map/d2mapgen/act1_overworld.go @@ -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.GetLevelDetailsByLevelId(2) + wilderness1Details := d2datadict.GetLevelDetails(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.GetLevelDetailsByLevelId(2) + levelDetails := d2datadict.GetLevelDetails(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.GetLevelDetailsByLevelId(2) + levelDetails := d2datadict.GetLevelDetails(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.GetLevelDetailsByLevelId(2) + levelDetails := d2datadict.GetLevelDetails(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.GetLevelDetailsByLevelId(2) + levelDetails := d2datadict.GetLevelDetails(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, } diff --git a/d2core/d2map/d2mapstamp/stamp.go b/d2core/d2map/d2mapstamp/stamp.go index b97d428a..0bd4b22b 100644 --- a/d2core/d2map/d2mapstamp/stamp.go +++ b/d2core/d2map/d2mapstamp/stamp.go @@ -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 } diff --git a/d2game/d2gamescreen/character_select.go b/d2game/d2gamescreen/character_select.go index 4a844a99..0def4683 100644 --- a/d2game/d2gamescreen/character_select.go +++ b/d2game/d2gamescreen/character_select.go @@ -9,7 +9,6 @@ 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" @@ -163,27 +162,11 @@ 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( - playerId, - playerName, - actId, - levelId, - x, y, - dir, - heroType, - heroStats, - heroEquipment, + v.characterImage[i] = d2mapentity.CreatePlayer("", "", 0, 0, 0, + v.gameStates[idx].HeroType, + *v.gameStates[idx].Stats, + d2inventory.HeroObjects[v.gameStates[idx].HeroType], ) } } diff --git a/d2game/d2gamescreen/game.go b/d2game/d2gamescreen/game.go index f39f31c3..f9453db3 100644 --- a/d2game/d2gamescreen/game.go +++ b/d2game/d2gamescreen/game.go @@ -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,13 +56,11 @@ 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() } - if v.MapRenderer != nil { - screen.Clear(color.Black) - v.MapRenderer.Render(screen) - } + screen.Clear(color.Black) + v.mapRenderer.Render(screen) if v.gameControls != nil { v.gameControls.Render(screen) @@ -71,7 +69,7 @@ func (v *Game) Render(screen d2render.Surface) error { return nil } -var zoneTextDuration = 2.0 // seconds +var hideZoneTextAfterSeconds = 2.0 func (v *Game) Advance(tickTime float64) error { if (v.escapeMenu != nil && !v.escapeMenu.IsOpen()) || len(v.gameClient.Players) != 1 { @@ -98,19 +96,11 @@ func (v *Game) Advance(tickTime float64) error { } // skip showing zone change text the first time we enter the world - 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)) + 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)) v.gameControls.ShowZoneChangeText() - v.gameControls.HideZoneChangeTextAfter(zoneTextDuration) + v.gameControls.HideZoneChangeTextAfter(hideZoneTextAfterSeconds) } v.lastRegionType = tile.RegionType } @@ -124,9 +114,7 @@ func (v *Game) Advance(tickTime float64) error { continue } v.localPlayer = player - engine := v.gameClient.MapEngine - renderer := v.MapRenderer - v.gameControls = d2player.NewGameControls(player, engine, renderer, v) + v.gameControls = d2player.NewGameControls(player, v.gameClient.MapEngine, v.mapRenderer, v) v.gameControls.Load() d2input.BindHandler(v.gameControls) @@ -136,16 +124,14 @@ func (v *Game) Advance(tickTime float64) error { // Update the camera to focus on the player if v.localPlayer != nil && !v.gameControls.FreeCam { - wx, wy := v.localPlayer.LocationX/5, v.localPlayer.LocationY/5 - rx, ry := v.MapRenderer.WorldToOrtho(wx, wy) - v.MapRenderer.MoveCameraTo(rx, ry) + rx, ry := v.mapRenderer.WorldToOrtho(v.localPlayer.LocationX/5, v.localPlayer.LocationY/5) + v.mapRenderer.MoveCameraTo(rx, ry) } return nil } -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) +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)) } diff --git a/d2game/d2player/player_state.go b/d2game/d2player/player_state.go index 68da3e76..3149746b 100644 --- a/d2game/d2player/player_state.go +++ b/d2game/d2player/player_state.go @@ -20,10 +20,9 @@ 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"` } @@ -46,8 +45,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() @@ -84,9 +83,8 @@ func CreatePlayerState(heroName string, hero d2enum.Hero, classStats d2datadict. result := &PlayerState{ HeroName: heroName, HeroType: hero, - Act: 0, - Level: 1, - Stats: d2hero.CreateHeroStatsState(hero, classStats, 1, 0), + Act: 1, + Stats: d2hero.CreateHeroStatsState(hero, classStats, 1, 0), Equipment: d2inventory.HeroObjects[hero], FilePath: "", } diff --git a/d2networking/d2client/d2client.go b/d2networking/d2client/d2client.go deleted file mode 100644 index 5a9d58b7..00000000 --- a/d2networking/d2client/d2client.go +++ /dev/null @@ -1,38 +0,0 @@ -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 -} diff --git a/d2networking/d2client/game_client.go b/d2networking/d2client/game_client.go index e87ecd9c..cbdaff3b 100644 --- a/d2networking/d2client/game_client.go +++ b/d2networking/d2client/game_client.go @@ -1,164 +1,120 @@ 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" - d2cct "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" + "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/d2netpackettype" ) type GameClient struct { clientConnection ClientConnection - connectionType d2cct.ClientConnectionType + connectionType d2clientconnectiontype.ClientConnectionType GameState *d2player.PlayerState MapEngine *d2mapengine.MapEngine - MapRenderer *d2maprenderer.MapRenderer - realm *d2mapengine.MapRealm PlayerId string Players map[string]*d2mapentity.Player Seed int64 RegenMap bool } -// Using the `clientConnection`, opens a connection and passes the savefile path +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 +} + 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 { - - // 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) - + 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: - g.handleMovePlayer(packet) + 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()) + }) + } case d2netpackettype.Ping: - g.handlePong(packet) - + g.clientConnection.SendPacketToServer(d2netpacket.CreatePongPacket(g.PlayerId)) case d2netpackettype.ServerClosed: - g.handleServerClosed(packet) - + // TODO: Need to be tied into a character save and exit + log.Print("Server has been closed") + os.Exit(0) 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) -} diff --git a/d2networking/d2netpacket/packet_add_player.go b/d2networking/d2netpacket/packet_add_player.go index c9405d6d..1843614d 100644 --- a/d2networking/d2netpacket/packet_add_player.go +++ b/d2networking/d2netpacket/packet_add_player.go @@ -12,24 +12,22 @@ 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, act, x, y int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) NetPacket { +func CreateAddPlayerPacket(id, name string, 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, }, } } diff --git a/d2networking/d2netpacket/packet_generate_map.go b/d2networking/d2netpacket/packet_generate_map.go index 6a6cb3c3..178bd6ff 100644 --- a/d2networking/d2netpacket/packet_generate_map.go +++ b/d2networking/d2netpacket/packet_generate_map.go @@ -1,20 +1,19 @@ package d2netpacket import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" ) type GenerateMapPacket struct { - ActId int `json:"actId"` - LevelId int `json:"levelId"` + RegionType d2enum.RegionIdType `json:"regionType"` } -func CreateGenerateMapPacket(actId, levelId int) NetPacket { +func CreateGenerateMapPacket(regionType d2enum.RegionIdType) NetPacket { return NetPacket{ PacketType: d2netpackettype.GenerateMap, PacketData: GenerateMapPacket{ - ActId: actId, - LevelId: levelId, + RegionType: regionType, }, } diff --git a/d2networking/d2server/connection_manager.go b/d2networking/d2server/connection_manager.go index 1a815032..1bd7fb63 100644 --- a/d2networking/d2server/connection_manager.go +++ b/d2networking/d2server/connection_manager.go @@ -8,16 +8,13 @@ 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 @@ -53,8 +50,7 @@ func (c *ConnectionManager) Run() { func (c *ConnectionManager) checkPeers() { for id, connection := range c.gameServer.clientConnections { if connection.GetConnectionType() != d2clientconnectiontype.Local { - pingPacket := d2netpacket.CreatePingPacket() - if err := connection.SendPacketToClient(pingPacket); err != nil { + if err := connection.SendPacketToClient(d2netpacket.CreatePingPacket()); err != nil { log.Printf("Cannot ping client id: %s", id) } c.RWMutex.Lock() @@ -70,8 +66,7 @@ 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 } @@ -86,10 +81,8 @@ 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()) diff --git a/d2networking/d2server/d2server.go b/d2networking/d2server/d2server.go deleted file mode 100644 index d5764933..00000000 --- a/d2networking/d2server/d2server.go +++ /dev/null @@ -1,243 +0,0 @@ -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 -} diff --git a/d2networking/d2server/game_server.go b/d2networking/d2server/game_server.go index 6918349d..d28f13a6 100644 --- a/d2networking/d2server/game_server.go +++ b/d2networking/d2server/game_server.go @@ -1,67 +1,209 @@ 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" - 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/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2server/d2udpclientconnection" "github.com/OpenDiablo2/OpenDiablo2/d2script" + "github.com/robertkrimen/otto" ) type GameServer struct { sync.RWMutex - version string clientConnections map[string]ClientConnection manager *ConnectionManager - realm *d2mapengine.MapRealm + mapEngines []*d2mapengine.MapEngine scriptEngine *d2script.ScriptEngine udpConnection *net.UDPConn seed int64 running bool - maxClients int - lastAdvance float64 } -func (srv *GameServer) handlePlayerConnRequest(addr *net.UDPAddr, data string) { - packetData := &packet.PlayerConnectionRequestPacket{} - json.Unmarshal([]byte(data), packetData) +var singletonServer *GameServer - 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, +func Create(openNetworkServer bool) { + log.Print("Creating GameServer") + if singletonServer != nil { + return } - for _, player := range srv.clientConnections { - player.SendPacketToClient(netPacket) + singletonServer = &GameServer{ + clientConnections: make(map[string]ClientConnection), + mapEngines: make([]*d2mapengine.MapEngine, 0), + scriptEngine: d2script.CreateScriptEngine(), + seed: time.Now().UnixNano(), + } + + singletonServer.manager = CreateConnectionManager(singletonServer) + + mapEngine := d2mapengine.CreateMapEngine() + mapEngine.SetSeed(singletonServer.seed) + mapEngine.ResetMap(d2enum.RegionAct1Town, 100, 100) // TODO: Mapgen - Needs levels.txt stuff + d2mapgen.GenerateAct1Overworld(mapEngine) + singletonServer.mapEngines = append(singletonServer.mapEngines, mapEngine) + + singletonServer.scriptEngine.AddFunction("getMapEngines", func(call otto.FunctionCall) otto.Value { + val, err := singletonServer.scriptEngine.ToValue(singletonServer.mapEngines) + if err != nil { + fmt.Print(err.Error()) + } + return val + }) + + if openNetworkServer { + createNetworkServer() } } -func (srv *GameServer) handlePong(addr *net.UDPAddr, data string) { - packetData := packet.PlayerConnectionRequestPacket{} - json.Unmarshal([]byte(data), &packetData) - srv.manager.Recv(packetData.Id) +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) handlePlayerDisconnectNotification(data string) { - var packet packet.PlayerDisconnectRequestPacket - json.Unmarshal([]byte(data), &packet) - log.Printf("Received disconnect: %s", packet.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 }