mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-11-18 02:16:23 -05: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:
parent
b4cd37fe6a
commit
fe47e51351
@ -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]
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
type LevelSubstitutionRecord struct {
|
||||
// Description, reference only.
|
||||
Name string // Name
|
||||
|
||||
// This value is used in Levels.txt, in the column 'SubType'. You'll notice
|
||||
// that in LvlSub.txt some rows use the same value, we can say they forms
|
||||
// groups. If you count each row of a group starting from 0, then you'll
|
||||
|
@ -2,51 +2,68 @@ package d2datadict
|
||||
|
||||
import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
)
|
||||
|
||||
type LevelTypeRecord struct {
|
||||
Name string
|
||||
Id int
|
||||
Files [32]string
|
||||
Beta bool
|
||||
Act int
|
||||
Expansion bool
|
||||
Name string // Name
|
||||
Id int // Id
|
||||
Files []string // File 1 -- File 32
|
||||
Beta bool // Beta
|
||||
Act int // Act
|
||||
Expansion bool // Expansion
|
||||
}
|
||||
|
||||
var LevelTypes []LevelTypeRecord
|
||||
var LevelTypes map[d2enum.RegionIdType]*LevelTypeRecord
|
||||
|
||||
func LoadLevelTypes(file []byte) {
|
||||
data := strings.Split(string(file), "\r\n")[1:]
|
||||
LevelTypes = make([]LevelTypeRecord, len(data))
|
||||
for i, j := 0, 0; i < len(data); i, j = i+1, j+1 {
|
||||
idx := -1
|
||||
inc := func() int {
|
||||
idx++
|
||||
return idx
|
||||
LevelTypes = make(map[d2enum.RegionIdType]*LevelTypeRecord)
|
||||
dict := d2common.LoadDataDictionary(string(file))
|
||||
for idx := range dict.Data {
|
||||
record := &LevelTypeRecord{
|
||||
Name: dict.GetString("Name", idx),
|
||||
Id: dict.GetNumber("Id", idx),
|
||||
Files: []string{
|
||||
dict.GetString("File 1", idx),
|
||||
dict.GetString("File 2", idx),
|
||||
dict.GetString("File 3", idx),
|
||||
dict.GetString("File 4", idx),
|
||||
dict.GetString("File 5", idx),
|
||||
dict.GetString("File 6", idx),
|
||||
dict.GetString("File 7", idx),
|
||||
dict.GetString("File 8", idx),
|
||||
dict.GetString("File 9", idx),
|
||||
dict.GetString("File 10", idx),
|
||||
dict.GetString("File 11", idx),
|
||||
dict.GetString("File 12", idx),
|
||||
dict.GetString("File 13", idx),
|
||||
dict.GetString("File 14", idx),
|
||||
dict.GetString("File 15", idx),
|
||||
dict.GetString("File 16", idx),
|
||||
dict.GetString("File 17", idx),
|
||||
dict.GetString("File 18", idx),
|
||||
dict.GetString("File 19", idx),
|
||||
dict.GetString("File 20", idx),
|
||||
dict.GetString("File 21", idx),
|
||||
dict.GetString("File 22", idx),
|
||||
dict.GetString("File 23", idx),
|
||||
dict.GetString("File 24", idx),
|
||||
dict.GetString("File 25", idx),
|
||||
dict.GetString("File 26", idx),
|
||||
dict.GetString("File 27", idx),
|
||||
dict.GetString("File 28", idx),
|
||||
dict.GetString("File 29", idx),
|
||||
dict.GetString("File 30", idx),
|
||||
dict.GetString("File 31", idx),
|
||||
dict.GetString("File 32", idx),
|
||||
},
|
||||
Beta: dict.GetNumber("Beta", idx) > 0,
|
||||
Act: dict.GetNumber("Act", idx),
|
||||
Expansion: dict.GetNumber("Expansion", idx) > 0,
|
||||
}
|
||||
if len(data[i]) == 0 {
|
||||
continue
|
||||
}
|
||||
parts := strings.Split(data[i], "\t")
|
||||
if parts[0] == "Expansion" {
|
||||
j--
|
||||
continue
|
||||
}
|
||||
LevelTypes[j].Name = parts[inc()]
|
||||
LevelTypes[j].Id = d2common.StringToInt(parts[inc()])
|
||||
for fileIdx := range LevelTypes[i].Files {
|
||||
LevelTypes[j].Files[fileIdx] = parts[inc()]
|
||||
if LevelTypes[j].Files[fileIdx] == "0" {
|
||||
LevelTypes[j].Files[fileIdx] = ""
|
||||
}
|
||||
|
||||
}
|
||||
LevelTypes[j].Beta = parts[inc()] != "1"
|
||||
LevelTypes[j].Act = d2common.StringToInt(parts[inc()])
|
||||
LevelTypes[j].Expansion = parts[inc()] != "1"
|
||||
LevelTypes[d2enum.RegionIdType(record.Id)] = record
|
||||
}
|
||||
log.Printf("Loaded %d LevelType records", len(LevelTypes))
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ type LevelDetailsRecord struct {
|
||||
AutomapIndex int // Layer
|
||||
|
||||
// sizeX - SizeY in each difficuly. If this is a preset area this sets the
|
||||
// X size for the area. Othervise use the same value here that are used in
|
||||
// X size for the area. Otherwise use the same value here that are used in
|
||||
// lvlprest.txt to set the size for the .ds1 file.
|
||||
SizeXNormal int // SizeX
|
||||
SizeYNormal int // SizeY
|
||||
@ -141,28 +141,14 @@ type LevelDetailsRecord struct {
|
||||
// linked with, but the actuall number of Vis ( 0 - 7 ) is determined by
|
||||
// your actual map (the .ds1 fle).
|
||||
// Example: Normally Cave levels are only using vis 0-3 and wilderness areas 4-7 .
|
||||
LevelLinkId0 int // Vis0
|
||||
LevelLinkId1 int // Vis1
|
||||
LevelLinkId2 int // Vis2
|
||||
LevelLinkId3 int // Vis3
|
||||
LevelLinkId4 int // Vis4
|
||||
LevelLinkId5 int // Vis5
|
||||
LevelLinkId6 int // Vis6
|
||||
LevelLinkId7 int // Vis7
|
||||
WarpLevelId []int // Vis0 -- Vis7
|
||||
|
||||
// This controls the visual graphics then you move the mouse pointer over
|
||||
// an entrance. To show the graphics you use an ID from lvlwarp.txt and the
|
||||
// behavior on the graphics is controlled by lvlwarp.txt. Your Warps must
|
||||
// match your Vis.
|
||||
// Example: If your level uses Vis 3,5,7 then you must also use Warp 3,5,7 .
|
||||
WarpGraphicsId0 int // Warp0
|
||||
WarpGraphicsId1 int // Warp1
|
||||
WarpGraphicsId2 int // Warp2
|
||||
WarpGraphicsId3 int // Warp3
|
||||
WarpGraphicsId4 int // Warp4
|
||||
WarpGraphicsId5 int // Warp5
|
||||
WarpGraphicsId6 int // Warp6
|
||||
WarpGraphicsId7 int // Warp7
|
||||
WarpGraphicsId []int // Warp0 -- Warp7
|
||||
|
||||
// These settings handle the light intensity as well as its RGB components
|
||||
LightIntensity int // Intensity
|
||||
@ -377,7 +363,7 @@ type LevelDetailsRecord struct {
|
||||
|
||||
var LevelDetails map[int]*LevelDetailsRecord
|
||||
|
||||
func GetLevelDetails(id int) *LevelDetailsRecord {
|
||||
func GetLevelDetailsByLevelId(id int) *LevelDetailsRecord {
|
||||
for i := 0; i < len(LevelDetails); i++ {
|
||||
if LevelDetails[i].Id == id {
|
||||
return LevelDetails[i]
|
||||
@ -387,60 +373,138 @@ func GetLevelDetails(id int) *LevelDetailsRecord {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetLevelDetailsByActId(act int) []*LevelDetailsRecord {
|
||||
result := make([]*LevelDetailsRecord, 0)
|
||||
for _, record := range LevelDetails {
|
||||
if act == record.Act {
|
||||
result = append(result, record)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
var actIds []int
|
||||
|
||||
func GetNumberOfActs() int {
|
||||
return len(actIds)
|
||||
}
|
||||
|
||||
func GetActIds() []int {
|
||||
return actIds
|
||||
}
|
||||
|
||||
func GetLevelWarpsByLevelId(id int) []*LevelWarpRecord {
|
||||
result := make([]*LevelWarpRecord, 0)
|
||||
level := LevelDetails[id]
|
||||
for _, warpId := range level.WarpLevelId {
|
||||
if warpId < 0 {
|
||||
continue // there are -1 values for empty entries in the table
|
||||
}
|
||||
result = append(result, LevelWarps[warpId])
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func GetLevelPresetByLevelId(id int) *LevelPresetRecord {
|
||||
for recordId, record := range LevelPresets {
|
||||
if id == recordId {
|
||||
return record
|
||||
}
|
||||
}
|
||||
panic("couldn't find a preset.")
|
||||
}
|
||||
|
||||
func GetFirstLevelIdByActId(actId int) int {
|
||||
recordsForAct := GetLevelDetailsByActId(actId)
|
||||
lowest := -1
|
||||
if len(recordsForAct) > 0 {
|
||||
for _, record := range recordsForAct {
|
||||
// need to account for level ID 0 which is an empty map in act 1
|
||||
if record.Id == 0 {
|
||||
continue
|
||||
}
|
||||
if lowest < 0 {
|
||||
lowest = record.Id
|
||||
continue
|
||||
}
|
||||
if record.Id < lowest {
|
||||
lowest = record.Id
|
||||
}
|
||||
}
|
||||
return lowest
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func AppendIfMissing(slice []int, i int) []int {
|
||||
for _, ele := range slice {
|
||||
if ele == i {
|
||||
return slice
|
||||
}
|
||||
}
|
||||
return append(slice, i)
|
||||
}
|
||||
|
||||
func LoadLevelDetails(file []byte) {
|
||||
dict := d2common.LoadDataDictionary(string(file))
|
||||
numRecords := len(dict.Data)
|
||||
LevelDetails = make(map[int]*LevelDetailsRecord, numRecords)
|
||||
|
||||
actIds = make([]int, 0)
|
||||
|
||||
for idx := range dict.Data {
|
||||
record := &LevelDetailsRecord{
|
||||
Name: dict.GetString("Name ", idx),
|
||||
Id: dict.GetNumber("Id", idx),
|
||||
Palette: dict.GetNumber("Pal", idx),
|
||||
Act: dict.GetNumber("Act", idx),
|
||||
QuestFlag: dict.GetNumber("QuestFlag", idx),
|
||||
QuestFlagExpansion: dict.GetNumber("QuestFlagEx", idx),
|
||||
AutomapIndex: dict.GetNumber("Layer", idx),
|
||||
SizeXNormal: dict.GetNumber("SizeX", idx),
|
||||
SizeYNormal: dict.GetNumber("SizeY", idx),
|
||||
SizeXNightmare: dict.GetNumber("SizeX(N)", idx),
|
||||
SizeYNightmare: dict.GetNumber("SizeY(N)", idx),
|
||||
SizeXHell: dict.GetNumber("SizeX(H)", idx),
|
||||
SizeYHell: dict.GetNumber("SizeY(H)", idx),
|
||||
WorldOffsetX: dict.GetNumber("OffsetX", idx),
|
||||
WorldOffsetY: dict.GetNumber("OffsetY", idx),
|
||||
DependantLevelID: dict.GetNumber("Depend", idx),
|
||||
TeleportFlag: d2enum.TeleportFlag(dict.GetNumber("Teleport", idx)),
|
||||
EnableRain: dict.GetNumber("Rain", idx) > 0,
|
||||
EnableMud: dict.GetNumber("Mud", idx) > 0,
|
||||
EnablePerspective: dict.GetNumber("NoPer", idx) > 0,
|
||||
EnableLineOfSightDraw: dict.GetNumber("LOSDraw", idx) > 0,
|
||||
EnableFloorFliter: dict.GetNumber("FloorFilter", idx) > 0,
|
||||
EnableBlankScreen: dict.GetNumber("BlankScreen", idx) > 0,
|
||||
EnableDrawEdges: dict.GetNumber("DrawEdges", idx) > 0,
|
||||
IsInside: dict.GetNumber("IsInside", idx) > 0,
|
||||
LevelGenerationType: d2enum.LevelGenerationType(dict.GetNumber("DrlgType", idx)),
|
||||
LevelType: dict.GetNumber("LevelType", idx),
|
||||
SubType: dict.GetNumber("SubType", idx),
|
||||
SubTheme: dict.GetNumber("SubTheme", idx),
|
||||
SubWaypoint: dict.GetNumber("SubWaypoint", idx),
|
||||
SubShrine: dict.GetNumber("SubShrine", idx),
|
||||
LevelLinkId0: dict.GetNumber("Vis0", idx),
|
||||
LevelLinkId1: dict.GetNumber("Vis1", idx),
|
||||
LevelLinkId2: dict.GetNumber("Vis2", idx),
|
||||
LevelLinkId3: dict.GetNumber("Vis3", idx),
|
||||
LevelLinkId4: dict.GetNumber("Vis4", idx),
|
||||
LevelLinkId5: dict.GetNumber("Vis5", idx),
|
||||
LevelLinkId6: dict.GetNumber("Vis6", idx),
|
||||
LevelLinkId7: dict.GetNumber("Vis7", idx),
|
||||
WarpGraphicsId0: dict.GetNumber("Warp0", idx),
|
||||
WarpGraphicsId1: dict.GetNumber("Warp1", idx),
|
||||
WarpGraphicsId2: dict.GetNumber("Warp2", idx),
|
||||
WarpGraphicsId3: dict.GetNumber("Warp3", idx),
|
||||
WarpGraphicsId4: dict.GetNumber("Warp4", idx),
|
||||
WarpGraphicsId5: dict.GetNumber("Warp5", idx),
|
||||
WarpGraphicsId6: dict.GetNumber("Warp6", idx),
|
||||
WarpGraphicsId7: dict.GetNumber("Warp7", idx),
|
||||
Name: dict.GetString("Name ", idx),
|
||||
Id: dict.GetNumber("Id", idx),
|
||||
Palette: dict.GetNumber("Pal", idx),
|
||||
Act: dict.GetNumber("Act", idx),
|
||||
QuestFlag: dict.GetNumber("QuestFlag", idx),
|
||||
QuestFlagExpansion: dict.GetNumber("QuestFlagEx", idx),
|
||||
AutomapIndex: dict.GetNumber("Layer", idx),
|
||||
SizeXNormal: dict.GetNumber("SizeX", idx),
|
||||
SizeYNormal: dict.GetNumber("SizeY", idx),
|
||||
SizeXNightmare: dict.GetNumber("SizeX(N)", idx),
|
||||
SizeYNightmare: dict.GetNumber("SizeY(N)", idx),
|
||||
SizeXHell: dict.GetNumber("SizeX(H)", idx),
|
||||
SizeYHell: dict.GetNumber("SizeY(H)", idx),
|
||||
WorldOffsetX: dict.GetNumber("OffsetX", idx),
|
||||
WorldOffsetY: dict.GetNumber("OffsetY", idx),
|
||||
DependantLevelID: dict.GetNumber("Depend", idx),
|
||||
TeleportFlag: d2enum.TeleportFlag(dict.GetNumber("Teleport", idx)),
|
||||
EnableRain: dict.GetNumber("Rain", idx) > 0,
|
||||
EnableMud: dict.GetNumber("Mud", idx) > 0,
|
||||
EnablePerspective: dict.GetNumber("NoPer", idx) > 0,
|
||||
EnableLineOfSightDraw: dict.GetNumber("LOSDraw", idx) > 0,
|
||||
EnableFloorFliter: dict.GetNumber("FloorFilter", idx) > 0,
|
||||
EnableBlankScreen: dict.GetNumber("BlankScreen", idx) > 0,
|
||||
EnableDrawEdges: dict.GetNumber("DrawEdges", idx) > 0,
|
||||
IsInside: dict.GetNumber("IsInside", idx) > 0,
|
||||
LevelGenerationType: d2enum.LevelGenerationType(dict.GetNumber("DrlgType", idx)),
|
||||
LevelType: dict.GetNumber("LevelType", idx),
|
||||
SubType: dict.GetNumber("SubType", idx),
|
||||
SubTheme: dict.GetNumber("SubTheme", idx),
|
||||
SubWaypoint: dict.GetNumber("SubWaypoint", idx),
|
||||
SubShrine: dict.GetNumber("SubShrine", idx),
|
||||
WarpLevelId: []int{
|
||||
dict.GetNumber("Vis0", idx),
|
||||
dict.GetNumber("Vis1", idx),
|
||||
dict.GetNumber("Vis2", idx),
|
||||
dict.GetNumber("Vis3", idx),
|
||||
dict.GetNumber("Vis4", idx),
|
||||
dict.GetNumber("Vis5", idx),
|
||||
dict.GetNumber("Vis6", idx),
|
||||
dict.GetNumber("Vis7", idx),
|
||||
},
|
||||
WarpGraphicsId: []int{
|
||||
dict.GetNumber("Vis0", idx),
|
||||
dict.GetNumber("Vis1", idx),
|
||||
dict.GetNumber("Vis2", idx),
|
||||
dict.GetNumber("Vis3", idx),
|
||||
dict.GetNumber("Vis4", idx),
|
||||
dict.GetNumber("Vis5", idx),
|
||||
dict.GetNumber("Vis6", idx),
|
||||
dict.GetNumber("Vis7", idx),
|
||||
},
|
||||
LightIntensity: dict.GetNumber("Intensity", idx),
|
||||
Red: dict.GetNumber("Red", idx),
|
||||
Green: dict.GetNumber("Green", idx),
|
||||
@ -540,6 +604,7 @@ func LoadLevelDetails(file []byte) {
|
||||
ObjectGroupSpawnChance6: dict.GetNumber("ObjPrb6", idx),
|
||||
ObjectGroupSpawnChance7: dict.GetNumber("ObjPrb7", idx),
|
||||
}
|
||||
actIds = AppendIfMissing(actIds, record.Act)
|
||||
LevelDetails[idx] = record
|
||||
}
|
||||
log.Printf("Loaded %d LevelDetails records", len(LevelDetails))
|
||||
|
@ -11,7 +11,8 @@ package d2enum
|
||||
type LevelGenerationType int
|
||||
|
||||
const (
|
||||
LevelTypeRandomMaze LevelGenerationType = iota
|
||||
LevelTypeNone LevelGenerationType = iota
|
||||
LevelTypeRandomMaze
|
||||
LevelTypePreset
|
||||
LevelTypeWilderness
|
||||
)
|
||||
|
@ -18,6 +18,7 @@ func getDefaultConfig() *Configuration {
|
||||
VsyncEnabled: true,
|
||||
SfxVolume: 1.0,
|
||||
BgmVolume: 0.3,
|
||||
MaxConnections: 8,
|
||||
MpqPath: "C:/Program Files (x86)/Diablo II",
|
||||
MpqLoadOrder: []string{
|
||||
"Patch_D2.mpq",
|
||||
|
@ -16,6 +16,7 @@ type Configuration struct {
|
||||
FullScreen bool
|
||||
RunInBackground bool
|
||||
VsyncEnabled bool
|
||||
MaxConnections int
|
||||
}
|
||||
|
||||
var singleton = getDefaultConfig()
|
||||
|
60
d2core/d2map/d2mapengine/act.go
Normal file
60
d2core/d2map/d2mapengine/act.go
Normal 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()
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
|
6
d2core/d2map/d2mapengine/generator.go
Normal file
6
d2core/d2map/d2mapengine/generator.go
Normal file
@ -0,0 +1,6 @@
|
||||
package d2mapengine
|
||||
|
||||
type MapGenerator interface {
|
||||
init(seed int64, level *MapLevel, engine *MapEngine)
|
||||
generate()
|
||||
}
|
15
d2core/d2map/d2mapengine/generator_maze.go
Normal file
15
d2core/d2map/d2mapengine/generator_maze.go
Normal 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() {}
|
66
d2core/d2map/d2mapengine/generator_preset.go
Normal file
66
d2core/d2map/d2mapengine/generator_preset.go
Normal 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)
|
||||
}
|
15
d2core/d2map/d2mapengine/generator_wilderness.go
Normal file
15
d2core/d2map/d2mapengine/generator_wilderness.go
Normal 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() {}
|
79
d2core/d2map/d2mapengine/level.go
Normal file
79
d2core/d2map/d2mapengine/level.go
Normal 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
|
||||
}
|
114
d2core/d2map/d2mapengine/realm.go
Normal file
114
d2core/d2map/d2mapengine/realm.go
Normal 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)
|
||||
}
|
@ -34,7 +34,7 @@ type Player struct {
|
||||
var baseWalkSpeed = 6.0
|
||||
var baseRunSpeed = 9.0
|
||||
|
||||
func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) *Player {
|
||||
func CreatePlayer(id, name string, ActId, LevelId, x, y, direction int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) *Player {
|
||||
object := &d2datadict.ObjectLookupRecord{
|
||||
Mode: d2enum.AnimationModePlayerTownNeutral.String(),
|
||||
Base: "/data/global/chars",
|
||||
|
@ -26,7 +26,7 @@ func loadPreset(mapEngine *d2mapengine.MapEngine, id, index int) *d2mapstamp.Sta
|
||||
func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
|
||||
|
||||
rand.Seed(mapEngine.Seed())
|
||||
wilderness1Details := d2datadict.GetLevelDetails(2)
|
||||
wilderness1Details := d2datadict.GetLevelDetailsByLevelId(2)
|
||||
mapEngine.ResetMap(d2enum.RegionAct1Town, 150, 150)
|
||||
mapWidth := mapEngine.Size().Width
|
||||
mapHeight := mapEngine.Size().Height
|
||||
@ -59,7 +59,7 @@ func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
|
||||
// West Exit
|
||||
mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height)
|
||||
|
||||
generateWilderness1TownWest(mapEngine, mapWidth-townSize.Width - wilderness1Details.SizeXNormal, mapHeight-wilderness1Details.SizeYNormal)
|
||||
generateWilderness1TownWest(mapEngine, mapWidth-townSize.Width-wilderness1Details.SizeXNormal, mapHeight-wilderness1Details.SizeYNormal)
|
||||
} else {
|
||||
// North Exit
|
||||
mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height)
|
||||
@ -69,7 +69,7 @@ func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) {
|
||||
}
|
||||
|
||||
func generateWilderness1TownEast(mapEngine *d2mapengine.MapEngine, startX, startY int) {
|
||||
levelDetails := d2datadict.GetLevelDetails(2)
|
||||
levelDetails := d2datadict.GetLevelDetailsByLevelId(2)
|
||||
|
||||
fenceNorthStamp := []*d2mapstamp.Stamp{
|
||||
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0),
|
||||
@ -102,36 +102,36 @@ func generateWilderness1TownEast(mapEngine *d2mapengine.MapEngine, startX, start
|
||||
|
||||
areaRect := d2common.Rectangle{
|
||||
Left: startX,
|
||||
Top: startY+9,
|
||||
Top: startY + 9,
|
||||
Width: levelDetails.SizeXNormal,
|
||||
Height: levelDetails.SizeYNormal-3,
|
||||
Height: levelDetails.SizeYNormal - 3,
|
||||
}
|
||||
generateWilderness1Contents(mapEngine, areaRect)
|
||||
|
||||
// Draw the north and south fence
|
||||
for i := 0; i < 9; i++ {
|
||||
mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9), startY)
|
||||
mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9), startY + (levelDetails.SizeYNormal +6))
|
||||
mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9), startY+(levelDetails.SizeYNormal+6))
|
||||
}
|
||||
|
||||
// West fence
|
||||
for i := 1; i < 6; i++ {
|
||||
mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+ (levelDetails.SizeYNormal+6) - (i * 9))
|
||||
mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(levelDetails.SizeYNormal+6)-(i*9))
|
||||
}
|
||||
|
||||
// East Fence
|
||||
for i := 1; i < 10; i++ {
|
||||
mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX + levelDetails.SizeXNormal, startY+(i*9))
|
||||
mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX+levelDetails.SizeXNormal, startY+(i*9))
|
||||
}
|
||||
|
||||
mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+ levelDetails.SizeYNormal+6)
|
||||
mapEngine.PlaceStamp(fenceWestEdge, startX, startY+ (levelDetails.SizeYNormal-3) - 45)
|
||||
mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal+6)
|
||||
mapEngine.PlaceStamp(fenceWestEdge, startX, startY+(levelDetails.SizeYNormal-3)-45)
|
||||
mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal, startY)
|
||||
mapEngine.PlaceStamp(fenceSouthEastStamp, startX+levelDetails.SizeXNormal, startY+levelDetails.SizeYNormal+6)
|
||||
}
|
||||
|
||||
func generateWilderness1TownSouth(mapEngine *d2mapengine.MapEngine, startX, startY int) {
|
||||
levelDetails := d2datadict.GetLevelDetails(2)
|
||||
levelDetails := d2datadict.GetLevelDetailsByLevelId(2)
|
||||
|
||||
fenceNorthStamp := []*d2mapstamp.Stamp{
|
||||
loadPreset(mapEngine, d2wilderness.TreeBorderNorth, 0),
|
||||
@ -184,7 +184,7 @@ func generateWilderness1TownSouth(mapEngine *d2mapengine.MapEngine, startX, star
|
||||
}
|
||||
|
||||
func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, startY int) {
|
||||
levelDetails := d2datadict.GetLevelDetails(2)
|
||||
levelDetails := d2datadict.GetLevelDetailsByLevelId(2)
|
||||
|
||||
fenceEastEdge := loadPreset(mapEngine, d2wilderness.TreeBoxSouthWest, 0)
|
||||
fenceNorthWestStamp := loadPreset(mapEngine, d2wilderness.TreeBorderNorthWest, 0)
|
||||
@ -218,30 +218,30 @@ func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, start
|
||||
// Draw the north and south fences
|
||||
for i := 0; i < 9; i++ {
|
||||
if i > 0 && i < 8 {
|
||||
mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX + (i*9)-1, startY-15)
|
||||
mapEngine.PlaceStamp(fenceNorthStamp[rand.Intn(3)], startX+(i*9)-1, startY-15)
|
||||
}
|
||||
mapEngine.PlaceStamp(fenceSouthStamp[rand.Intn(3)], startX+(i*9)-1, startY+levelDetails.SizeYNormal-12)
|
||||
}
|
||||
|
||||
// Draw the east fence
|
||||
for i := 0; i < 6; i++ {
|
||||
mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX + levelDetails.SizeXNormal-9, startY + (i*9)-6)
|
||||
mapEngine.PlaceStamp(fenceEastStamp[rand.Intn(3)], startX+levelDetails.SizeXNormal-9, startY+(i*9)-6)
|
||||
}
|
||||
|
||||
// Draw the west fence
|
||||
for i := 0; i < 9; i++ {
|
||||
mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY + (i*9)-6)
|
||||
mapEngine.PlaceStamp(fenceWestStamp[rand.Intn(3)], startX, startY+(i*9)-6)
|
||||
}
|
||||
|
||||
// Draw the west fence
|
||||
mapEngine.PlaceStamp(fenceEastEdge, startX + levelDetails.SizeXNormal-9, startY + 39)
|
||||
mapEngine.PlaceStamp(fenceEastEdge, startX+levelDetails.SizeXNormal-9, startY+39)
|
||||
mapEngine.PlaceStamp(fenceNorthWestStamp, startX, startY-15)
|
||||
mapEngine.PlaceStamp(fenceSouthWestStamp, startX, startY+levelDetails.SizeYNormal-12)
|
||||
mapEngine.PlaceStamp(fenceNorthEastStamp, startX+levelDetails.SizeXNormal-9, startY-15)
|
||||
|
||||
areaRect := d2common.Rectangle{
|
||||
Left: startX + 9,
|
||||
Top: startY-10,
|
||||
Top: startY - 10,
|
||||
Width: levelDetails.SizeXNormal - 9,
|
||||
Height: levelDetails.SizeYNormal - 2,
|
||||
}
|
||||
@ -250,7 +250,7 @@ func generateWilderness1TownWest(mapEngine *d2mapengine.MapEngine, startX, start
|
||||
}
|
||||
|
||||
func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2common.Rectangle) {
|
||||
levelDetails := d2datadict.GetLevelDetails(2)
|
||||
levelDetails := d2datadict.GetLevelDetailsByLevelId(2)
|
||||
|
||||
denOfEvil := loadPreset(mapEngine, d2wilderness.DenOfEvilEntrance, 0)
|
||||
denOfEvilLoc := d2common.Point{
|
||||
@ -295,10 +295,10 @@ func generateWilderness1Contents(mapEngine *d2mapengine.MapEngine, rect d2common
|
||||
for numPlaced < 25 {
|
||||
stamp := stuff[rand.Intn(len(stuff))]
|
||||
|
||||
stampRect := d2common.Rectangle {
|
||||
Left: rect.Left+ rand.Intn(rect.Width) - stamp.Size().Width,
|
||||
Top: rect.Top+rand.Intn(rect.Height) - stamp.Size().Height,
|
||||
Width: stamp.Size().Width,
|
||||
stampRect := d2common.Rectangle{
|
||||
Left: rect.Left + rand.Intn(rect.Width) - stamp.Size().Width,
|
||||
Top: rect.Top + rand.Intn(rect.Height) - stamp.Size().Height,
|
||||
Width: stamp.Size().Width,
|
||||
Height: stamp.Size().Height,
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2input"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio"
|
||||
@ -162,11 +163,27 @@ func (v *CharacterSelect) updateCharacterBoxes() {
|
||||
v.characterNameLabel[i].SetText(v.gameStates[idx].HeroName)
|
||||
v.characterStatsLabel[i].SetText("Level 1 " + v.gameStates[idx].HeroType.String())
|
||||
v.characterExpLabel[i].SetText(expText)
|
||||
|
||||
playerId := ""
|
||||
playerName := ""
|
||||
actId := d2datadict.GetActIds()[0]
|
||||
levelId := d2datadict.GetFirstLevelIdByActId(actId)
|
||||
x, y := 0, 0
|
||||
dir := 0
|
||||
heroType := v.gameStates[idx].HeroType
|
||||
heroStats := *v.gameStates[idx].Stats
|
||||
heroEquipment := d2inventory.HeroObjects[v.gameStates[idx].HeroType]
|
||||
// TODO: Generate or load the object from the actual player data...
|
||||
v.characterImage[i] = d2mapentity.CreatePlayer("", "", 0, 0, 0,
|
||||
v.gameStates[idx].HeroType,
|
||||
*v.gameStates[idx].Stats,
|
||||
d2inventory.HeroObjects[v.gameStates[idx].HeroType],
|
||||
v.characterImage[i] = d2mapentity.CreatePlayer(
|
||||
playerId,
|
||||
playerName,
|
||||
actId,
|
||||
levelId,
|
||||
x, y,
|
||||
dir,
|
||||
heroType,
|
||||
heroStats,
|
||||
heroEquipment,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ import (
|
||||
|
||||
type Game struct {
|
||||
gameClient *d2client.GameClient
|
||||
mapRenderer *d2maprenderer.MapRenderer
|
||||
MapRenderer *d2maprenderer.MapRenderer
|
||||
gameControls *d2player.GameControls // TODO: Hack
|
||||
localPlayer *d2mapentity.Player
|
||||
lastRegionType d2enum.RegionIdType
|
||||
@ -35,7 +35,7 @@ func CreateGame(gameClient *d2client.GameClient) *Game {
|
||||
localPlayer: nil,
|
||||
lastRegionType: d2enum.RegionNone,
|
||||
ticksSinceLevelCheck: 0,
|
||||
mapRenderer: d2maprenderer.CreateMapRenderer(gameClient.MapEngine),
|
||||
MapRenderer: d2maprenderer.CreateMapRenderer(gameClient.MapEngine),
|
||||
escapeMenu: NewEscapeMenu(),
|
||||
}
|
||||
result.escapeMenu.OnLoad()
|
||||
@ -56,11 +56,13 @@ func (v *Game) OnUnload() error {
|
||||
func (v *Game) Render(screen d2render.Surface) error {
|
||||
if v.gameClient.RegenMap {
|
||||
v.gameClient.RegenMap = false
|
||||
v.mapRenderer.RegenerateTileCache()
|
||||
v.MapRenderer.RegenerateTileCache()
|
||||
}
|
||||
|
||||
screen.Clear(color.Black)
|
||||
v.mapRenderer.Render(screen)
|
||||
if v.MapRenderer != nil {
|
||||
screen.Clear(color.Black)
|
||||
v.MapRenderer.Render(screen)
|
||||
}
|
||||
|
||||
if v.gameControls != nil {
|
||||
v.gameControls.Render(screen)
|
||||
@ -69,7 +71,7 @@ func (v *Game) Render(screen d2render.Surface) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var hideZoneTextAfterSeconds = 2.0
|
||||
var zoneTextDuration = 2.0 // seconds
|
||||
|
||||
func (v *Game) Advance(tickTime float64) error {
|
||||
if (v.escapeMenu != nil && !v.escapeMenu.IsOpen()) || len(v.gameClient.Players) != 1 {
|
||||
@ -96,11 +98,19 @@ func (v *Game) Advance(tickTime float64) error {
|
||||
}
|
||||
|
||||
// skip showing zone change text the first time we enter the world
|
||||
if v.lastRegionType != d2enum.RegionNone && v.lastRegionType != tile.RegionType {
|
||||
//TODO: Should not be using RegionType as an index - this will return incorrect LevelDetails record for most of the zones.
|
||||
v.gameControls.SetZoneChangeText(fmt.Sprintf("Entering The %s", d2datadict.LevelDetails[int(tile.RegionType)].LevelDisplayName))
|
||||
notNone := v.lastRegionType != d2enum.RegionNone
|
||||
differentTileType := v.lastRegionType != tile.RegionType
|
||||
if notNone && differentTileType {
|
||||
//TODO: Should not be using RegionType as an index - this
|
||||
// will return incorrect LevelDetails record for most of the
|
||||
// zones.
|
||||
levelId := int(tile.RegionType)
|
||||
levelDetails := d2datadict.LevelDetails[levelId]
|
||||
str := "Entering The %s"
|
||||
name := levelDetails.LevelDisplayName
|
||||
v.gameControls.SetZoneChangeText(fmt.Sprintf(str, name))
|
||||
v.gameControls.ShowZoneChangeText()
|
||||
v.gameControls.HideZoneChangeTextAfter(hideZoneTextAfterSeconds)
|
||||
v.gameControls.HideZoneChangeTextAfter(zoneTextDuration)
|
||||
}
|
||||
v.lastRegionType = tile.RegionType
|
||||
}
|
||||
@ -114,7 +124,9 @@ func (v *Game) Advance(tickTime float64) error {
|
||||
continue
|
||||
}
|
||||
v.localPlayer = player
|
||||
v.gameControls = d2player.NewGameControls(player, v.gameClient.MapEngine, v.mapRenderer, v)
|
||||
engine := v.gameClient.MapEngine
|
||||
renderer := v.MapRenderer
|
||||
v.gameControls = d2player.NewGameControls(player, engine, renderer, v)
|
||||
v.gameControls.Load()
|
||||
d2input.BindHandler(v.gameControls)
|
||||
|
||||
@ -124,14 +136,16 @@ func (v *Game) Advance(tickTime float64) error {
|
||||
|
||||
// Update the camera to focus on the player
|
||||
if v.localPlayer != nil && !v.gameControls.FreeCam {
|
||||
rx, ry := v.mapRenderer.WorldToOrtho(v.localPlayer.LocationX/5, v.localPlayer.LocationY/5)
|
||||
v.mapRenderer.MoveCameraTo(rx, ry)
|
||||
wx, wy := v.localPlayer.LocationX/5, v.localPlayer.LocationY/5
|
||||
rx, ry := v.MapRenderer.WorldToOrtho(wx, wy)
|
||||
v.MapRenderer.MoveCameraTo(rx, ry)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Game) OnPlayerMove(x, y float64) {
|
||||
heroPosX := v.localPlayer.LocationX / 5.0
|
||||
heroPosY := v.localPlayer.LocationY / 5.0
|
||||
v.gameClient.SendPacketToServer(d2netpacket.CreateMovePlayerPacket(v.gameClient.PlayerId, heroPosX, heroPosY, x, y))
|
||||
func (v *Game) OnPlayerMove(x2, y2 float64) {
|
||||
id := v.gameClient.PlayerId
|
||||
x1, y1 := v.localPlayer.LocationX/5.0, v.localPlayer.LocationY/5.0
|
||||
movePacket := d2netpacket.CreateMovePlayerPacket(id, x1, y1, x2, y2)
|
||||
v.gameClient.SendPacketToServer(movePacket)
|
||||
}
|
||||
|
@ -20,9 +20,10 @@ type PlayerState struct {
|
||||
HeroType d2enum.Hero `json:"heroType"`
|
||||
HeroLevel int `json:"heroLevel"`
|
||||
Act int `json:"act"`
|
||||
Level int `json:"actLevel"`
|
||||
FilePath string `json:"-"`
|
||||
Equipment d2inventory.CharacterEquipment `json:"equipment"`
|
||||
Stats *d2hero.HeroStatsState `json:"stats"`
|
||||
Stats *d2hero.HeroStatsState `json:"stats"`
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
}
|
||||
@ -45,8 +46,8 @@ func GetAllPlayerStates() []*PlayerState {
|
||||
gameState := LoadPlayerState(path.Join(basePath, file.Name()))
|
||||
if gameState == nil || gameState.HeroType == d2enum.HeroNone {
|
||||
continue
|
||||
// temporarily loading default class stats if the character was created before saving stats was introduced
|
||||
// to be removed in the future
|
||||
// temporarily loading default class stats if the character was created before saving stats was introduced
|
||||
// to be removed in the future
|
||||
} else if gameState.Stats == nil {
|
||||
gameState.Stats = d2hero.CreateHeroStatsState(gameState.HeroType, *d2datadict.CharStats[gameState.HeroType], 1, 0)
|
||||
gameState.Save()
|
||||
@ -83,8 +84,9 @@ func CreatePlayerState(heroName string, hero d2enum.Hero, classStats d2datadict.
|
||||
result := &PlayerState{
|
||||
HeroName: heroName,
|
||||
HeroType: hero,
|
||||
Act: 1,
|
||||
Stats: d2hero.CreateHeroStatsState(hero, classStats, 1, 0),
|
||||
Act: 0,
|
||||
Level: 1,
|
||||
Stats: d2hero.CreateHeroStatsState(hero, classStats, 1, 0),
|
||||
Equipment: d2inventory.HeroObjects[hero],
|
||||
FilePath: "",
|
||||
}
|
||||
|
38
d2networking/d2client/d2client.go
Normal file
38
d2networking/d2client/d2client.go
Normal 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
|
||||
}
|
@ -1,120 +1,164 @@
|
||||
package d2client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen"
|
||||
// "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2localclient"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2remoteclient"
|
||||
d2cct "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
|
||||
)
|
||||
|
||||
type GameClient struct {
|
||||
clientConnection ClientConnection
|
||||
connectionType d2clientconnectiontype.ClientConnectionType
|
||||
connectionType d2cct.ClientConnectionType
|
||||
GameState *d2player.PlayerState
|
||||
MapEngine *d2mapengine.MapEngine
|
||||
MapRenderer *d2maprenderer.MapRenderer
|
||||
realm *d2mapengine.MapRealm
|
||||
PlayerId string
|
||||
Players map[string]*d2mapentity.Player
|
||||
Seed int64
|
||||
RegenMap bool
|
||||
}
|
||||
|
||||
func Create(connectionType d2clientconnectiontype.ClientConnectionType) (*GameClient, error) {
|
||||
result := &GameClient{
|
||||
MapEngine: d2mapengine.CreateMapEngine(), // TODO: Mapgen - Needs levels.txt stuff
|
||||
Players: make(map[string]*d2mapentity.Player),
|
||||
connectionType: connectionType,
|
||||
}
|
||||
|
||||
switch connectionType {
|
||||
case d2clientconnectiontype.LANClient:
|
||||
result.clientConnection = d2remoteclient.Create()
|
||||
case d2clientconnectiontype.LANServer:
|
||||
result.clientConnection = d2localclient.Create(true)
|
||||
case d2clientconnectiontype.Local:
|
||||
result.clientConnection = d2localclient.Create(false)
|
||||
default:
|
||||
return nil, fmt.Errorf("unknown client connection type specified: %d", connectionType)
|
||||
}
|
||||
result.clientConnection.SetClientListener(result)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Using the `clientConnection`, opens a connection and passes the savefile path
|
||||
func (g *GameClient) Open(connectionString string, saveFilePath string) error {
|
||||
return g.clientConnection.Open(connectionString, saveFilePath)
|
||||
}
|
||||
|
||||
// Closes the `clientConnection`
|
||||
func (g *GameClient) Close() error {
|
||||
return g.clientConnection.Close()
|
||||
}
|
||||
|
||||
// Closes the `clientConnection`
|
||||
func (g *GameClient) Destroy() error {
|
||||
return g.clientConnection.Close()
|
||||
}
|
||||
|
||||
// Routes the incoming packets to the packet handlers
|
||||
func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
|
||||
switch packet.PacketType {
|
||||
case d2netpackettype.GenerateMap:
|
||||
mapData := packet.PacketData.(d2netpacket.GenerateMapPacket)
|
||||
switch mapData.RegionType {
|
||||
case d2enum.RegionAct1Town:
|
||||
d2mapgen.GenerateAct1Overworld(g.MapEngine)
|
||||
}
|
||||
g.RegenMap = true
|
||||
case d2netpackettype.UpdateServerInfo:
|
||||
serverInfo := packet.PacketData.(d2netpacket.UpdateServerInfoPacket)
|
||||
g.MapEngine.SetSeed(serverInfo.Seed)
|
||||
g.PlayerId = serverInfo.PlayerId
|
||||
g.Seed = serverInfo.Seed
|
||||
log.Printf("Player id set to %s", serverInfo.PlayerId)
|
||||
case d2netpackettype.AddPlayer:
|
||||
player := packet.PacketData.(d2netpacket.AddPlayerPacket)
|
||||
newPlayer := d2mapentity.CreatePlayer(player.Id, player.Name, player.X, player.Y, 0, player.HeroType, player.Stats, player.Equipment)
|
||||
g.Players[newPlayer.Id] = newPlayer
|
||||
g.MapEngine.AddEntity(newPlayer)
|
||||
case d2netpackettype.MovePlayer:
|
||||
movePlayer := packet.PacketData.(d2netpacket.MovePlayerPacket)
|
||||
player := g.Players[movePlayer.PlayerId]
|
||||
path, _, _ := g.MapEngine.PathFind(movePlayer.StartX, movePlayer.StartY, movePlayer.DestX, movePlayer.DestY)
|
||||
if len(path) > 0 {
|
||||
player.SetPath(path, func() {
|
||||
tile := g.MapEngine.TileAt(player.TileX, player.TileY)
|
||||
if tile == nil {
|
||||
return
|
||||
}
|
||||
|
||||
regionType := tile.RegionType
|
||||
if regionType == d2enum.RegionAct1Town {
|
||||
player.SetIsInTown(true)
|
||||
} else {
|
||||
player.SetIsInTown(false)
|
||||
}
|
||||
player.SetAnimationMode(player.GetAnimationMode().String())
|
||||
})
|
||||
}
|
||||
// UNSURE: should we be bubbling up errors from these handler calls?
|
||||
case d2netpackettype.UpdateServerInfo:
|
||||
g.handleUpdateServerInfo(packet)
|
||||
|
||||
case d2netpackettype.AddPlayer:
|
||||
g.handleAddPlayer(packet)
|
||||
|
||||
case d2netpackettype.GenerateMap:
|
||||
g.handleGenerateMap(packet)
|
||||
|
||||
case d2netpackettype.MovePlayer:
|
||||
g.handleMovePlayer(packet)
|
||||
|
||||
case d2netpackettype.Ping:
|
||||
g.clientConnection.SendPacketToServer(d2netpacket.CreatePongPacket(g.PlayerId))
|
||||
g.handlePong(packet)
|
||||
|
||||
case d2netpackettype.ServerClosed:
|
||||
// TODO: Need to be tied into a character save and exit
|
||||
log.Print("Server has been closed")
|
||||
os.Exit(0)
|
||||
g.handleServerClosed(packet)
|
||||
|
||||
default:
|
||||
log.Fatalf("Invalid packet type: %d", packet.PacketType)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Using the `clientConnection`, sends a packet to the server
|
||||
func (g *GameClient) SendPacketToServer(packet d2netpacket.NetPacket) error {
|
||||
return g.clientConnection.SendPacketToServer(packet)
|
||||
}
|
||||
|
||||
func (g *GameClient) handleUpdateServerInfo(p d2netpacket.NetPacket) {
|
||||
serverInfo := p.PacketData.(d2netpacket.UpdateServerInfoPacket)
|
||||
seed := serverInfo.Seed
|
||||
playerId := serverInfo.PlayerId
|
||||
|
||||
g.Seed = seed
|
||||
g.realm.Init(seed)
|
||||
g.PlayerId = playerId
|
||||
|
||||
log.Printf("Player id set to %s", playerId)
|
||||
}
|
||||
|
||||
func (g *GameClient) handleAddPlayer(p d2netpacket.NetPacket) {
|
||||
player := p.PacketData.(d2netpacket.AddPlayerPacket)
|
||||
levelId := g.realm.GetFirstActLevelId(player.Act)
|
||||
g.MapEngine = g.realm.GetMapEngine(player.Act, levelId)
|
||||
|
||||
pId := player.Id
|
||||
pName := player.Name
|
||||
pAct := player.Act
|
||||
pLvlId := levelId
|
||||
pX := player.X
|
||||
pY := player.Y
|
||||
pDir := 0
|
||||
pHero := player.HeroType
|
||||
pStat := player.Stats
|
||||
pEquip := player.Equipment
|
||||
|
||||
// UNSURE: maybe we should be passing a struct instead of all the vars?
|
||||
newPlayer := d2mapentity.CreatePlayer(
|
||||
pId, pName, pAct, pLvlId, pX, pY, pDir, pHero, pStat, pEquip,
|
||||
)
|
||||
|
||||
g.Players[newPlayer.Id] = newPlayer
|
||||
g.realm.AddPlayer(pId, pAct)
|
||||
g.MapEngine.AddEntity(newPlayer)
|
||||
}
|
||||
|
||||
func (g *GameClient) handleGenerateMap(p d2netpacket.NetPacket) {
|
||||
mapData := p.PacketData.(d2netpacket.GenerateMapPacket)
|
||||
g.realm.GenerateMap(mapData.ActId, mapData.LevelId)
|
||||
engine := g.realm.GetMapEngine(mapData.ActId, mapData.LevelId)
|
||||
g.MapRenderer = d2maprenderer.CreateMapRenderer(engine)
|
||||
g.RegenMap = true
|
||||
}
|
||||
|
||||
func (g *GameClient) handleMovePlayer(p d2netpacket.NetPacket) {
|
||||
movePlayer := p.PacketData.(d2netpacket.MovePlayerPacket)
|
||||
|
||||
player := g.Players[movePlayer.PlayerId]
|
||||
x1, y1 := movePlayer.StartX, movePlayer.StartY
|
||||
x2, y2 := movePlayer.DestX, movePlayer.DestY
|
||||
|
||||
path, _, _ := g.MapEngine.PathFind(x1, y1, x2, y2)
|
||||
|
||||
if len(path) > 0 {
|
||||
player.SetPath(path, func() {
|
||||
tile := g.MapEngine.TileAt(player.TileX, player.TileY)
|
||||
if tile == nil {
|
||||
return
|
||||
}
|
||||
|
||||
regionType := tile.RegionType
|
||||
if regionType == d2enum.RegionAct1Town {
|
||||
player.SetIsInTown(true)
|
||||
} else {
|
||||
player.SetIsInTown(false)
|
||||
}
|
||||
player.SetAnimationMode(player.GetAnimationMode().String())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GameClient) handlePong(p d2netpacket.NetPacket) {
|
||||
pong := d2netpacket.CreatePongPacket(g.PlayerId)
|
||||
g.clientConnection.SendPacketToServer(pong)
|
||||
}
|
||||
|
||||
func (g *GameClient) handleServerClosed(p d2netpacket.NetPacket) {
|
||||
// TODO: Need to be tied into a character save and exit
|
||||
log.Print("Server has been closed")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
@ -12,22 +12,24 @@ type AddPlayerPacket struct {
|
||||
Name string `json:"name"`
|
||||
X int `json:"x"`
|
||||
Y int `json:"y"`
|
||||
Act int `json:"act"`
|
||||
HeroType d2enum.Hero `json:"hero"`
|
||||
Equipment d2inventory.CharacterEquipment `json:"equipment"`
|
||||
Stats d2hero.HeroStatsState `json:"heroStats"`
|
||||
Stats d2hero.HeroStatsState `json:"heroStats"`
|
||||
}
|
||||
|
||||
func CreateAddPlayerPacket(id, name string, x, y int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) NetPacket {
|
||||
func CreateAddPlayerPacket(id, name string, act, x, y int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) NetPacket {
|
||||
return NetPacket{
|
||||
PacketType: d2netpackettype.AddPlayer,
|
||||
PacketData: AddPlayerPacket{
|
||||
Id: id,
|
||||
Name: name,
|
||||
Act: act,
|
||||
X: x,
|
||||
Y: y,
|
||||
HeroType: heroType,
|
||||
Equipment: equipment,
|
||||
Stats: stats,
|
||||
Stats: stats,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -1,19 +1,20 @@
|
||||
package d2netpacket
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
|
||||
)
|
||||
|
||||
type GenerateMapPacket struct {
|
||||
RegionType d2enum.RegionIdType `json:"regionType"`
|
||||
ActId int `json:"actId"`
|
||||
LevelId int `json:"levelId"`
|
||||
}
|
||||
|
||||
func CreateGenerateMapPacket(regionType d2enum.RegionIdType) NetPacket {
|
||||
func CreateGenerateMapPacket(actId, levelId int) NetPacket {
|
||||
return NetPacket{
|
||||
PacketType: d2netpackettype.GenerateMap,
|
||||
PacketData: GenerateMapPacket{
|
||||
RegionType: regionType,
|
||||
ActId: actId,
|
||||
LevelId: levelId,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -8,13 +8,16 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// ConnectionManager is responsible for cleanup up connections accepted by the game server. As the server communicates over
|
||||
// UDP and is stateless we need to implement some loose state management via a ping/pong system. ConnectionManager also handles
|
||||
// ConnectionManager is responsible for cleanup up connections accepted by the
|
||||
// game server. As the server communicates over
|
||||
// UDP and is stateless we need to implement some loose state management via a
|
||||
// ping/pong system. ConnectionManager also handles
|
||||
// communication for graceful shutdowns.
|
||||
//
|
||||
// retries: # of attempts before the dropping the client
|
||||
// interval: How long to wait before each ping/pong test
|
||||
// gameServer: The *GameServer is argument provided for the connection manager to watch over
|
||||
// gameServer: The *GameServer is argument provided for the connection manager
|
||||
// to watch over
|
||||
// status: map of inflight ping/pong requests
|
||||
type ConnectionManager struct {
|
||||
sync.RWMutex
|
||||
@ -50,7 +53,8 @@ func (c *ConnectionManager) Run() {
|
||||
func (c *ConnectionManager) checkPeers() {
|
||||
for id, connection := range c.gameServer.clientConnections {
|
||||
if connection.GetConnectionType() != d2clientconnectiontype.Local {
|
||||
if err := connection.SendPacketToClient(d2netpacket.CreatePingPacket()); err != nil {
|
||||
pingPacket := d2netpacket.CreatePingPacket()
|
||||
if err := connection.SendPacketToClient(pingPacket); err != nil {
|
||||
log.Printf("Cannot ping client id: %s", id)
|
||||
}
|
||||
c.RWMutex.Lock()
|
||||
@ -66,7 +70,8 @@ func (c *ConnectionManager) checkPeers() {
|
||||
}
|
||||
}
|
||||
|
||||
// Recv simply resets the counter, acknowledging we have received a pong from the client.
|
||||
// Recv simply resets the counter, acknowledging we have received a pong from
|
||||
// the client.
|
||||
func (c *ConnectionManager) Recv(id string) {
|
||||
c.status[id] = 0
|
||||
}
|
||||
@ -81,8 +86,10 @@ func (c *ConnectionManager) Drop(id string) {
|
||||
|
||||
// Shutdown will notify all of the clients that the server has been shutdown.
|
||||
func (c *ConnectionManager) Shutdown() {
|
||||
// TODO: Currently this will never actually get called as the go routines are never signaled about the application termination.
|
||||
// Things can be done more cleanly once we have graceful exits however we still need to account for other OS Signals
|
||||
// TODO: Currently this will never actually get called as the go routines
|
||||
// are never signaled about the application termination.
|
||||
// Things can be done more cleanly once we have graceful exits however we
|
||||
// still need to account for other OS Signals
|
||||
log.Print("Notifying clients server is shutting down...")
|
||||
for _, connection := range c.gameServer.clientConnections {
|
||||
connection.SendPacketToClient(d2netpacket.CreateServerClosedPacket())
|
||||
|
243
d2networking/d2server/d2server.go
Normal file
243
d2networking/d2server/d2server.go
Normal 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
|
||||
}
|
@ -1,209 +1,67 @@
|
||||
package d2server
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/gzip"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2server/d2udpclientconnection"
|
||||
packet "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
|
||||
packettype "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
|
||||
d2udp "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2server/d2udpclientconnection"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2script"
|
||||
"github.com/robertkrimen/otto"
|
||||
)
|
||||
|
||||
type GameServer struct {
|
||||
sync.RWMutex
|
||||
version string
|
||||
clientConnections map[string]ClientConnection
|
||||
manager *ConnectionManager
|
||||
mapEngines []*d2mapengine.MapEngine
|
||||
realm *d2mapengine.MapRealm
|
||||
scriptEngine *d2script.ScriptEngine
|
||||
udpConnection *net.UDPConn
|
||||
seed int64
|
||||
running bool
|
||||
maxClients int
|
||||
lastAdvance float64
|
||||
}
|
||||
|
||||
var singletonServer *GameServer
|
||||
func (srv *GameServer) handlePlayerConnRequest(addr *net.UDPAddr, data string) {
|
||||
packetData := &packet.PlayerConnectionRequestPacket{}
|
||||
json.Unmarshal([]byte(data), packetData)
|
||||
|
||||
func Create(openNetworkServer bool) {
|
||||
log.Print("Creating GameServer")
|
||||
if singletonServer != nil {
|
||||
return
|
||||
srvCon := srv.udpConnection
|
||||
packetId := packetData.Id
|
||||
clientCon := d2udp.CreateUDPClientConnection(srvCon, packetId, addr)
|
||||
|
||||
state := packetData.PlayerState
|
||||
clientCon.SetPlayerState(state)
|
||||
OnClientConnected(clientCon)
|
||||
}
|
||||
|
||||
func (srv *GameServer) handleMovePlayer(addr *net.UDPAddr, data string) {
|
||||
packetData := &packet.MovePlayerPacket{}
|
||||
json.Unmarshal([]byte(data), packetData)
|
||||
|
||||
netPacket := packet.NetPacket{
|
||||
PacketType: packettype.MovePlayer,
|
||||
PacketData: packetData,
|
||||
}
|
||||
|
||||
singletonServer = &GameServer{
|
||||
clientConnections: make(map[string]ClientConnection),
|
||||
mapEngines: make([]*d2mapengine.MapEngine, 0),
|
||||
scriptEngine: d2script.CreateScriptEngine(),
|
||||
seed: time.Now().UnixNano(),
|
||||
}
|
||||
|
||||
singletonServer.manager = CreateConnectionManager(singletonServer)
|
||||
|
||||
mapEngine := d2mapengine.CreateMapEngine()
|
||||
mapEngine.SetSeed(singletonServer.seed)
|
||||
mapEngine.ResetMap(d2enum.RegionAct1Town, 100, 100) // TODO: Mapgen - Needs levels.txt stuff
|
||||
d2mapgen.GenerateAct1Overworld(mapEngine)
|
||||
singletonServer.mapEngines = append(singletonServer.mapEngines, mapEngine)
|
||||
|
||||
singletonServer.scriptEngine.AddFunction("getMapEngines", func(call otto.FunctionCall) otto.Value {
|
||||
val, err := singletonServer.scriptEngine.ToValue(singletonServer.mapEngines)
|
||||
if err != nil {
|
||||
fmt.Print(err.Error())
|
||||
}
|
||||
return val
|
||||
})
|
||||
|
||||
if openNetworkServer {
|
||||
createNetworkServer()
|
||||
for _, player := range srv.clientConnections {
|
||||
player.SendPacketToClient(netPacket)
|
||||
}
|
||||
}
|
||||
|
||||
func createNetworkServer() {
|
||||
s, err := net.ResolveUDPAddr("udp4", "0.0.0.0:6669")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
singletonServer.udpConnection, err = net.ListenUDP("udp4", s)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
singletonServer.udpConnection.SetReadBuffer(4096)
|
||||
func (srv *GameServer) handlePong(addr *net.UDPAddr, data string) {
|
||||
packetData := packet.PlayerConnectionRequestPacket{}
|
||||
json.Unmarshal([]byte(data), &packetData)
|
||||
srv.manager.Recv(packetData.Id)
|
||||
}
|
||||
|
||||
func runNetworkServer() {
|
||||
buffer := make([]byte, 4096)
|
||||
for singletonServer.running {
|
||||
_, addr, err := singletonServer.udpConnection.ReadFromUDP(buffer)
|
||||
if err != nil {
|
||||
fmt.Printf("Socket error: %s\n", err)
|
||||
continue
|
||||
}
|
||||
buff := bytes.NewBuffer(buffer)
|
||||
packetTypeId, err := buff.ReadByte()
|
||||
packetType := d2netpackettype.NetPacketType(packetTypeId)
|
||||
reader, err := gzip.NewReader(buff)
|
||||
sb := new(strings.Builder)
|
||||
io.Copy(sb, reader)
|
||||
stringData := sb.String()
|
||||
switch packetType {
|
||||
case d2netpackettype.PlayerConnectionRequest:
|
||||
packetData := d2netpacket.PlayerConnectionRequestPacket{}
|
||||
json.Unmarshal([]byte(stringData), &packetData)
|
||||
clientConnection := d2udpclientconnection.CreateUDPClientConnection(singletonServer.udpConnection, packetData.Id, addr)
|
||||
clientConnection.SetPlayerState(packetData.PlayerState)
|
||||
OnClientConnected(clientConnection)
|
||||
case d2netpackettype.MovePlayer:
|
||||
packetData := d2netpacket.MovePlayerPacket{}
|
||||
json.Unmarshal([]byte(stringData), &packetData)
|
||||
netPacket := d2netpacket.NetPacket{
|
||||
PacketType: packetType,
|
||||
PacketData: packetData,
|
||||
}
|
||||
|
||||
for _, player := range singletonServer.clientConnections {
|
||||
player.SendPacketToClient(netPacket)
|
||||
}
|
||||
case d2netpackettype.Pong:
|
||||
packetData := d2netpacket.PlayerConnectionRequestPacket{}
|
||||
json.Unmarshal([]byte(stringData), &packetData)
|
||||
singletonServer.manager.Recv(packetData.Id)
|
||||
case d2netpackettype.ServerClosed:
|
||||
singletonServer.manager.Shutdown()
|
||||
case d2netpackettype.PlayerDisconnectionNotification:
|
||||
var packet d2netpacket.PlayerDisconnectRequestPacket
|
||||
json.Unmarshal([]byte(stringData), &packet)
|
||||
log.Printf("Received disconnect: %s", packet.Id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Run() {
|
||||
log.Print("Starting GameServer")
|
||||
singletonServer.running = true
|
||||
singletonServer.scriptEngine.RunScript("scripts/server/server.js")
|
||||
if singletonServer.udpConnection != nil {
|
||||
go runNetworkServer()
|
||||
}
|
||||
log.Print("Network server has been started")
|
||||
}
|
||||
|
||||
func Stop() {
|
||||
log.Print("Stopping GameServer")
|
||||
singletonServer.running = false
|
||||
if singletonServer.udpConnection != nil {
|
||||
singletonServer.udpConnection.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func Destroy() {
|
||||
if singletonServer == nil {
|
||||
return
|
||||
}
|
||||
log.Print("Destroying GameServer")
|
||||
Stop()
|
||||
}
|
||||
|
||||
func OnClientConnected(client ClientConnection) {
|
||||
// Temporary position hack --------------------------------------------
|
||||
sx, sy := singletonServer.mapEngines[0].GetStartPosition() // TODO: Another temporary hack
|
||||
clientPlayerState := client.GetPlayerState()
|
||||
clientPlayerState.X = sx
|
||||
clientPlayerState.Y = sy
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
log.Printf("Client connected with an id of %s", client.GetUniqueId())
|
||||
singletonServer.clientConnections[client.GetUniqueId()] = client
|
||||
client.SendPacketToClient(d2netpacket.CreateUpdateServerInfoPacket(singletonServer.seed, client.GetUniqueId()))
|
||||
client.SendPacketToClient(d2netpacket.CreateGenerateMapPacket(d2enum.RegionAct1Town))
|
||||
|
||||
playerState := client.GetPlayerState()
|
||||
createPlayerPacket := d2netpacket.CreateAddPlayerPacket(client.GetUniqueId(), playerState.HeroName, int(sx*5)+3, int(sy*5)+3,
|
||||
playerState.HeroType, *playerState.Stats, playerState.Equipment)
|
||||
for _, connection := range singletonServer.clientConnections {
|
||||
connection.SendPacketToClient(createPlayerPacket)
|
||||
if connection.GetUniqueId() == client.GetUniqueId() {
|
||||
continue
|
||||
}
|
||||
|
||||
conPlayerState := connection.GetPlayerState()
|
||||
client.SendPacketToClient(d2netpacket.CreateAddPlayerPacket(connection.GetUniqueId(), conPlayerState.HeroName,
|
||||
int(conPlayerState.X*5)+3, int(conPlayerState.Y*5)+3, conPlayerState.HeroType, *conPlayerState.Stats, conPlayerState.Equipment))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func OnClientDisconnected(client ClientConnection) {
|
||||
log.Printf("Client disconnected with an id of %s", client.GetUniqueId())
|
||||
delete(singletonServer.clientConnections, client.GetUniqueId())
|
||||
}
|
||||
|
||||
func OnPacketReceived(client ClientConnection, packet d2netpacket.NetPacket) error {
|
||||
switch packet.PacketType {
|
||||
case d2netpackettype.MovePlayer:
|
||||
// TODO: This needs to be verified on the server (here) before sending to other clients....
|
||||
// TODO: Hacky, this should be updated in realtime ----------------
|
||||
// TODO: Verify player id
|
||||
playerState := singletonServer.clientConnections[client.GetUniqueId()].GetPlayerState()
|
||||
playerState.X = packet.PacketData.(d2netpacket.MovePlayerPacket).DestX
|
||||
playerState.Y = packet.PacketData.(d2netpacket.MovePlayerPacket).DestY
|
||||
// ----------------------------------------------------------------
|
||||
for _, player := range singletonServer.clientConnections {
|
||||
player.SendPacketToClient(packet)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
func (srv *GameServer) handlePlayerDisconnectNotification(data string) {
|
||||
var packet packet.PlayerDisconnectRequestPacket
|
||||
json.Unmarshal([]byte(data), &packet)
|
||||
log.Printf("Received disconnect: %s", packet.Id)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user