diff --git a/.gitignore b/.gitignore index c11e5425..a5cafb8e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /OpenDiablo2.exe /OpenDiablo2 **/*.pprof +tags diff --git a/d2core/d2gamestate/d2gamestate.go b/d2core/d2gamestate/d2gamestate.go index 40b178f2..8a20e748 100644 --- a/d2core/d2gamestate/d2gamestate.go +++ b/d2core/d2gamestate/d2gamestate.go @@ -1,6 +1,7 @@ package d2gamestate import ( + "encoding/json" "io/ioutil" "log" "os" @@ -9,33 +10,19 @@ import ( "strings" "time" - "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" ) -/* - File Spec - -------------------------------------------- - UINT32 GameState Version - INT64 Game Seed - BYTE Hero Type - BYTE Hero Level - BYTE Act - BYTE Hero Name Length - BYTE[] Hero Name - -------------------------------------------- -*/ - type GameState struct { - Seed int64 - HeroName string - HeroType d2enum.Hero - HeroLevel int - Act int - FilePath string - Equipment d2inventory.CharacterEquipment + Seed int64 `json:"seed"` // TODO: Seed needs to be regenerated every time the game starts + HeroName string `json:"heroName"` + HeroType d2enum.Hero `json:"heroType"` + HeroLevel int `json:"heroLevel"` + Act int `json:"act"` + FilePath string `json:"-"` + Equipment d2inventory.CharacterEquipment `json:"equipment"` } const GameStateVersion = uint32(2) // Update this when you make breaking changes @@ -47,7 +34,6 @@ func HasGameStates() bool { } func GetAllGameStates() []*GameState { - // TODO: Make this not crash tf out on bad files basePath, _ := getGameBaseSavePath() files, _ := ioutil.ReadDir(basePath) result := make([]*GameState, 0) @@ -74,41 +60,31 @@ func CreateTestGameState() *GameState { } func LoadGameState(path string) *GameState { + strData, err := ioutil.ReadFile(path) + if err != nil { + return nil + } + result := &GameState{ FilePath: path, } - f, err := os.Open(path) + err = json.Unmarshal(strData, result) if err != nil { - log.Panicf(err.Error()) - } - bytes, err := ioutil.ReadAll(f) - if err != nil { - log.Panicf(err.Error()) - } - defer f.Close() - sr := d2common.CreateStreamReader(bytes) - if sr.GetUInt32() != GameStateVersion { - // Unknown game version return nil } - result.Seed = sr.GetInt64() - result.HeroType = d2enum.Hero(sr.GetByte()) - result.HeroLevel = int(sr.GetByte()) - result.Act = int(sr.GetByte()) - heroNameLen := sr.GetByte() - heroName, _ := sr.ReadBytes(int(heroNameLen)) - result.HeroName = string(heroName) return result } func CreateGameState(heroName string, hero d2enum.Hero, hardcore bool) *GameState { result := &GameState{ - HeroName: heroName, - HeroType: hero, - Act: 1, - Seed: time.Now().UnixNano(), - FilePath: "", + HeroName: heroName, + HeroType: hero, + Act: 1, + Seed: time.Now().UnixNano(), + Equipment: d2inventory.HeroObjects[hero], + FilePath: "", } + result.Save() return result } @@ -141,22 +117,6 @@ func (v *GameState) Save() { if err := os.MkdirAll(path.Dir(v.FilePath), 0755); err != nil { log.Panic(err.Error()) } - f, err := os.Create(v.FilePath) - if err != nil { - log.Panicf(err.Error()) - } - defer f.Close() - sr := d2common.CreateStreamWriter() - sr.PushUint32(GameStateVersion) - sr.PushInt64(v.Seed) - sr.PushByte(byte(v.HeroType)) - sr.PushByte(byte(v.HeroLevel)) - sr.PushByte(byte(v.Act)) - sr.PushByte(byte(len(v.HeroName))) - for _, ch := range v.HeroName { - sr.PushByte(byte(ch)) - } - if _, err := f.Write(sr.GetBytes()); err != nil { - log.Panicf(err.Error()) - } + fileJson, _ := json.MarshalIndent(v, "", " ") + ioutil.WriteFile(v.FilePath, fileJson, 0644) } diff --git a/d2core/d2inventory/character_equipment.go b/d2core/d2inventory/character_equipment.go index 4a0e62f0..e220be7f 100644 --- a/d2core/d2inventory/character_equipment.go +++ b/d2core/d2inventory/character_equipment.go @@ -1,13 +1,13 @@ package d2inventory type CharacterEquipment struct { - Head *InventoryItemArmor // Head - Torso *InventoryItemArmor // TR - Legs *InventoryItemArmor // Legs - RightArm *InventoryItemArmor // RA - LeftArm *InventoryItemArmor // LA - LeftHand *InventoryItemWeapon // LH - RightHand *InventoryItemWeapon // RH - Shield *InventoryItemArmor // SH + Head *InventoryItemArmor `json:"head"` // Head + Torso *InventoryItemArmor `json:"torso"` // TR + Legs *InventoryItemArmor `json:"legs"` // Legs + RightArm *InventoryItemArmor `json:"rightArm"` // RA + LeftArm *InventoryItemArmor `json:"leftArm"` // LA + LeftHand *InventoryItemWeapon `json:"leftHand"` // LH + RightHand *InventoryItemWeapon `json:"rightHand"` // RH + Shield *InventoryItemArmor `json:"shield"` // SH // S1-S8? } diff --git a/d2core/d2inventory/inventory_item_armor.go b/d2core/d2inventory/inventory_item_armor.go index ef2e2978..86bf7e6b 100644 --- a/d2core/d2inventory/inventory_item_armor.go +++ b/d2core/d2inventory/inventory_item_armor.go @@ -8,13 +8,13 @@ import ( ) type InventoryItemArmor struct { - inventorySizeX int - inventorySizeY int - inventorySlotX int - inventorySlotY int - itemName string - itemCode string - armorClass string + InventorySizeX int `json:"inventorySizeX"` + InventorySizeY int `json:"inventorySizeY"` + InventorySlotX int `json:"inventorySlotX"` + InventorySlotY int `json:"inventorySlotY"` + ItemName string `json:"itemName"` + ItemCode string `json:"itemCode"` + ArmorClass string `json:"armorClass"` } func GetArmorItemByCode(code string) *InventoryItemArmor { @@ -23,19 +23,19 @@ func GetArmorItemByCode(code string) *InventoryItemArmor { log.Fatalf("Could not find armor entry for code '%s'", code) } return &InventoryItemArmor{ - inventorySizeX: result.InventoryWidth, - inventorySizeY: result.InventoryHeight, - itemName: result.Name, - itemCode: result.Code, - armorClass: "lit", // TODO: Where does this come from? + InventorySizeX: result.InventoryWidth, + InventorySizeY: result.InventoryHeight, + ItemName: result.Name, + ItemCode: result.Code, + ArmorClass: "lit", // TODO: Where does this come from? } } -func (v *InventoryItemArmor) ArmorClass() string { - if v == nil || v.itemCode == "" { +func (v *InventoryItemArmor) GetArmorClass() string { + if v == nil || v.ItemCode == "" { return "lit" } - return v.armorClass + return v.ArmorClass } func (v *InventoryItemArmor) InventoryItemName() string { @@ -43,7 +43,7 @@ func (v *InventoryItemArmor) InventoryItemName() string { return "" } - return v.itemName + return v.ItemName } func (v *InventoryItemArmor) InventoryItemType() d2enum.InventoryItemType { @@ -51,25 +51,25 @@ func (v *InventoryItemArmor) InventoryItemType() d2enum.InventoryItemType { } func (v *InventoryItemArmor) InventoryGridSize() (int, int) { - return v.inventorySizeX, v.inventorySizeY + return v.InventorySizeX, v.InventorySizeY } func (v *InventoryItemArmor) InventoryGridSlot() (int, int) { - return v.inventorySlotX, v.inventorySlotY + return v.InventorySlotX, v.InventorySlotY } func (v *InventoryItemArmor) SetInventoryGridSlot(x int, y int) { - v.inventorySlotX, v.inventorySlotY = x, y + v.InventorySlotX, v.InventorySlotY = x, y } func (v *InventoryItemArmor) Serialize() []byte { return []byte{} } -func (v *InventoryItemArmor) ItemCode() string { +func (v *InventoryItemArmor) GetItemCode() string { if v == nil { return "" } - return v.itemCode + return v.ItemCode } diff --git a/d2core/d2inventory/inventory_item_weapon.go b/d2core/d2inventory/inventory_item_weapon.go index 776b3960..95688d8b 100644 --- a/d2core/d2inventory/inventory_item_weapon.go +++ b/d2core/d2inventory/inventory_item_weapon.go @@ -8,14 +8,14 @@ import ( ) type InventoryItemWeapon struct { - inventorySizeX int - inventorySizeY int - inventorySlotX int - inventorySlotY int - itemName string - itemCode string - weaponClass string - weaponClassOffHand string + InventorySizeX int `json:"inventorySizeX"` + InventorySizeY int `json:"inventorySizeY"` + InventorySlotX int `json:"inventorySlotX"` + InventorySlotY int `json:"inventorySlotY"` + ItemName string `json:"itemName"` + ItemCode string `json:"itemCode"` + WeaponClass string `json:"weaponClass"` + WeaponClassOffHand string `json:"weaponClassOffHand"` } func GetWeaponItemByCode(code string) *InventoryItemWeapon { @@ -25,34 +25,34 @@ func GetWeaponItemByCode(code string) *InventoryItemWeapon { log.Fatalf("Could not find weapon entry for code '%s'", code) } return &InventoryItemWeapon{ - inventorySizeX: result.InventoryWidth, - inventorySizeY: result.InventoryHeight, - itemName: result.Name, - itemCode: result.Code, - weaponClass: result.WeaponClass, - weaponClassOffHand: result.WeaponClass2Hand, + InventorySizeX: result.InventoryWidth, + InventorySizeY: result.InventoryHeight, + ItemName: result.Name, + ItemCode: result.Code, + WeaponClass: result.WeaponClass, + WeaponClassOffHand: result.WeaponClass2Hand, } } -func (v *InventoryItemWeapon) WeaponClass() string { - if v == nil || v.itemCode == "" { +func (v *InventoryItemWeapon) GetWeaponClass() string { + if v == nil || v.ItemCode == "" { return "hth" } - return v.weaponClass + return v.WeaponClass } -func (v *InventoryItemWeapon) WeaponClassOffHand() string { - if v == nil || v.itemCode == "" { +func (v *InventoryItemWeapon) GetWeaponClassOffHand() string { + if v == nil || v.ItemCode == "" { return "" } - return v.weaponClassOffHand + return v.WeaponClassOffHand } func (v *InventoryItemWeapon) InventoryItemName() string { if v == nil { return "" } - return v.itemName + return v.ItemName } func (v *InventoryItemWeapon) InventoryItemType() d2enum.InventoryItemType { @@ -60,24 +60,24 @@ func (v *InventoryItemWeapon) InventoryItemType() d2enum.InventoryItemType { } func (v *InventoryItemWeapon) InventoryGridSize() (int, int) { - return v.inventorySizeX, v.inventorySizeY + return v.InventorySizeX, v.InventorySizeY } func (v *InventoryItemWeapon) InventoryGridSlot() (int, int) { - return v.inventorySlotX, v.inventorySlotY + return v.InventorySlotX, v.InventorySlotY } func (v *InventoryItemWeapon) SetInventoryGridSlot(x int, y int) { - v.inventorySlotX, v.inventorySlotY = x, y + v.InventorySlotX, v.InventorySlotY = x, y } func (v *InventoryItemWeapon) Serialize() []byte { return []byte{} } -func (v *InventoryItemWeapon) ItemCode() string { +func (v *InventoryItemWeapon) GetItemCode() string { if v == nil { return "" } - return v.itemCode + return v.ItemCode } diff --git a/d2core/d2map/engine.go b/d2core/d2map/engine.go index b4975435..ae86f37b 100644 --- a/d2core/d2map/engine.go +++ b/d2core/d2map/engine.go @@ -1,6 +1,7 @@ package d2map import ( + "log" "math" "strings" @@ -8,10 +9,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gamestate" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2term" ) type MapEntity interface { @@ -21,31 +19,25 @@ type MapEntity interface { } type MapEngine struct { - gameState *d2gamestate.GameState - - debugVisLevel int - + seed int64 regions []*MapRegion entities MapEntitiesSearcher - viewport *Viewport - camera Camera } -func CreateMapEngine(gameState *d2gamestate.GameState) *MapEngine { +func CreateMapEngine() *MapEngine { engine := &MapEngine{ - gameState: gameState, - viewport: NewViewport(0, 0, 800, 600), - entities: NewRangeSearcher(), + seed: 0, + entities: NewRangeSearcher(), } - d2term.BindAction("mapdebugvis", "set map debug visualization level", func(level int) { - engine.debugVisLevel = level - }) - - engine.viewport.SetCamera(&engine.camera) return engine } +func (m *MapEngine) SetSeed(seed int64) { + log.Printf("Setting map engine seed to %d", seed) + m.seed = seed +} + func (m *MapEngine) GetStartPosition() (float64, float64) { var startX, startY float64 if len(m.regions) > 0 { @@ -67,45 +59,25 @@ func (m *MapEngine) GetCenterPosition() (float64, float64) { return centerX, centerY } -func (m *MapEngine) MoveCameraTo(x, y float64) { - m.camera.MoveTo(x, y) -} - -func (m *MapEngine) MoveCameraBy(x, y float64) { - m.camera.MoveBy(x, y) -} - -func (m *MapEngine) ScreenToWorld(x, y int) (float64, float64) { - return m.viewport.ScreenToWorld(x, y) -} - -func (m *MapEngine) ScreenToOrtho(x, y int) (float64, float64) { - return m.viewport.ScreenToOrtho(x, y) -} - -func (m *MapEngine) WorldToOrtho(x, y float64) (float64, float64) { - return m.viewport.WorldToOrtho(x, y) -} - -func (m *MapEngine) GenerateMap(regionType d2enum.RegionIdType, levelPreset int, fileIndex int) { - region, entities := loadRegion(m.gameState.Seed, 0, 0, regionType, levelPreset, fileIndex) +func (m *MapEngine) GenerateMap(regionType d2enum.RegionIdType, levelPreset int, fileIndex int, cacheTiles bool) { + region, entities := loadRegion(m.seed, 0, 0, regionType, levelPreset, fileIndex, cacheTiles) m.regions = append(m.regions, region) m.entities.Add(entities...) } -func (m *MapEngine) GenerateAct1Overworld() { - d2audio.PlayBGM("/data/global/music/Act1/town1.wav") // TODO: Temp stuff here +func (m *MapEngine) GenerateAct1Overworld(cacheTiles bool) { + //d2audio.PlayBGM("/data/global/music/Act1/town1.wav") // TODO: Temp stuff here - region, entities := loadRegion(m.gameState.Seed, 0, 0, d2enum.RegionAct1Town, 1, -1) + region, entities := loadRegion(m.seed, 0, 0, d2enum.RegionAct1Town, 1, -1, cacheTiles) m.regions = append(m.regions, region) m.entities.Add(entities...) if strings.Contains(region.regionPath, "E1") { - region, entities := loadRegion(m.gameState.Seed, region.tileRect.Width-1, 0, d2enum.RegionAct1Town, 2, -1) + region, entities := loadRegion(m.seed, region.tileRect.Width-1, 0, d2enum.RegionAct1Town, 2, -1, cacheTiles) m.AppendRegion(region) m.entities.Add(entities...) } else if strings.Contains(region.regionPath, "S1") { - region, entities := loadRegion(m.gameState.Seed, 0, region.tileRect.Height-1, d2enum.RegionAct1Town, 3, -1) + region, entities := loadRegion(m.seed, 0, region.tileRect.Height-1, d2enum.RegionAct1Town, 3, -1, cacheTiles) m.AppendRegion(region) m.entities.Add(entities...) } @@ -140,9 +112,9 @@ func (m *MapEngine) RemoveEntity(entity MapEntity) { func (m *MapEngine) Advance(tickTime float64) { for _, region := range m.regions { - if region.isVisbile(m.viewport) { - region.advance(tickTime) - } + //if region.isVisbile(m.viewport) { + region.advance(tickTime) + //} } for _, entity := range m.entities.All() { @@ -152,17 +124,6 @@ func (m *MapEngine) Advance(tickTime float64) { m.entities.Update() } -func (m *MapEngine) Render(target d2render.Surface) { - for _, region := range m.regions { - if region.isVisbile(m.viewport) { - region.renderPass1(m.viewport, target) - region.renderDebug(m.debugVisLevel, m.viewport, target) - region.renderPass2(m.entities, m.viewport, target) - region.renderPass3(m.viewport, target) - } - } -} - func (m *MapEngine) PathFind(startX, startY, endX, endY float64) (path []astar.Pather, distance float64, found bool) { startTileX := int(math.Floor(startX)) startTileY := int(math.Floor(startY)) diff --git a/d2core/d2map/hero.go b/d2core/d2map/player.go similarity index 56% rename from d2core/d2map/hero.go rename to d2core/d2map/player.go index 79299fe8..f37b63e7 100644 --- a/d2core/d2map/hero.go +++ b/d2core/d2map/player.go @@ -8,28 +8,29 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" ) -type Hero struct { +type Player struct { *AnimatedComposite Equipment d2inventory.CharacterEquipment + Id string mode d2enum.AnimationMode direction int } -func CreateHero(x, y int, direction int, heroType d2enum.Hero, equipment d2inventory.CharacterEquipment) *Hero { +func CreatePlayer(id string, x, y int, direction int, heroType d2enum.Hero, equipment d2inventory.CharacterEquipment) *Player { object := &d2datadict.ObjectLookupRecord{ Mode: d2enum.AnimationModePlayerNeutral.String(), Base: "/data/global/chars", Token: heroType.GetToken(), - Class: equipment.RightHand.WeaponClass(), - SH: equipment.Shield.ItemCode(), + Class: equipment.RightHand.GetWeaponClass(), + SH: equipment.Shield.GetItemCode(), // TODO: Offhand class? - HD: equipment.Head.ArmorClass(), - TR: equipment.Torso.ArmorClass(), - LG: equipment.Legs.ArmorClass(), - RA: equipment.RightArm.ArmorClass(), - LA: equipment.LeftArm.ArmorClass(), - RH: equipment.RightHand.ItemCode(), - LH: equipment.LeftHand.ItemCode(), + HD: equipment.Head.GetArmorClass(), + TR: equipment.Torso.GetArmorClass(), + LG: equipment.Legs.GetArmorClass(), + RA: equipment.RightArm.GetArmorClass(), + LA: equipment.LeftArm.GetArmorClass(), + RH: equipment.RightHand.GetItemCode(), + LH: equipment.LeftHand.GetItemCode(), } entity, err := CreateAnimatedComposite(x, y, object, d2resource.PaletteUnits) @@ -37,25 +38,26 @@ func CreateHero(x, y int, direction int, heroType d2enum.Hero, equipment d2inven panic(err) } - result := &Hero{ + result := &Player{ + Id: id, AnimatedComposite: entity, Equipment: equipment, mode: d2enum.AnimationModePlayerTownNeutral, direction: direction, } - result.SetMode(result.mode.String(), equipment.RightHand.WeaponClass(), direction) + result.SetMode(result.mode.String(), equipment.RightHand.GetWeaponClass(), direction) return result } -func (v *Hero) Advance(tickTime float64) { +func (v *Player) Advance(tickTime float64) { v.Step(tickTime) v.AnimatedComposite.Advance(tickTime) } -func (v *Hero) Render(target d2render.Surface) { +func (v *Player) Render(target d2render.Surface) { v.AnimatedComposite.Render(target) } -func (v *Hero) GetPosition() (float64, float64) { +func (v *Player) GetPosition() (float64, float64) { return v.AnimatedComposite.GetPosition() } diff --git a/d2core/d2map/region.go b/d2core/d2map/region.go index 59a0366a..11bc1e71 100644 --- a/d2core/d2map/region.go +++ b/d2core/d2map/region.go @@ -92,7 +92,7 @@ type MapRegion struct { walkableArea [][]PathTile } -func loadRegion(seed int64, tileOffsetX, tileOffsetY int, levelType d2enum.RegionIdType, levelPreset int, fileIndex int) (*MapRegion, []MapEntity) { +func loadRegion(seed int64, tileOffsetX, tileOffsetY int, levelType d2enum.RegionIdType, levelPreset int, fileIndex int, cacheTiles bool) (*MapRegion, []MapEntity) { region := &MapRegion{ levelType: d2datadict.LevelTypes[levelType], levelPreset: d2datadict.LevelPresets[levelPreset], @@ -145,8 +145,11 @@ func loadRegion(seed int64, tileOffsetX, tileOffsetY int, levelType d2enum.Regio entities := region.loadEntities() region.loadSpecials() - region.generateTileCache() region.generateWalkableMatrix() + + if cacheTiles { + region.generateTileCache() + } return region, entities } diff --git a/d2core/d2map/renderer.go b/d2core/d2map/renderer.go new file mode 100644 index 00000000..9d1a36c3 --- /dev/null +++ b/d2core/d2map/renderer.go @@ -0,0 +1,60 @@ +package d2map + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2term" +) + +type MapRenderer struct { + mapEngine *MapEngine + viewport *Viewport + camera Camera + debugVisLevel int +} + +func CreateMapRenderer(mapEngine *MapEngine) *MapRenderer { + result := &MapRenderer{ + mapEngine: mapEngine, + viewport: NewViewport(0, 0, 800, 600), + } + result.viewport.SetCamera(&result.camera) + d2term.BindAction("mapdebugvis", "set map debug visualization level", func(level int) { + result.debugVisLevel = level + }) + return result +} + +func (m *MapRenderer) SetMapEngine(mapEngine *MapEngine) { + m.mapEngine = mapEngine +} + +func (m *MapRenderer) Render(target d2render.Surface) { + for _, region := range m.mapEngine.regions { + if region.isVisbile(m.viewport) { + region.renderPass1(m.viewport, target) + region.renderDebug(m.debugVisLevel, m.viewport, target) + region.renderPass2(m.mapEngine.entities, m.viewport, target) + region.renderPass3(m.viewport, target) + } + } +} + +func (m *MapRenderer) MoveCameraTo(x, y float64) { + m.camera.MoveTo(x, y) +} + +func (m *MapRenderer) MoveCameraBy(x, y float64) { + m.camera.MoveBy(x, y) +} + +func (m *MapRenderer) ScreenToWorld(x, y int) (float64, float64) { + return m.viewport.ScreenToWorld(x, y) +} + +func (m *MapRenderer) ScreenToOrtho(x, y int) (float64, float64) { + return m.viewport.ScreenToOrtho(x, y) +} + +func (m *MapRenderer) WorldToOrtho(x, y float64) (float64, float64) { + return m.viewport.WorldToOrtho(x, y) +} diff --git a/d2game/d2gamescene/character_select.go b/d2game/d2gamescene/character_select.go index 02affb03..d11466ff 100644 --- a/d2game/d2gamescene/character_select.go +++ b/d2game/d2gamescene/character_select.go @@ -6,6 +6,9 @@ import ( "os" "strings" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" + "github.com/hajimehoshi/ebiten" "github.com/OpenDiablo2/OpenDiablo2/d2common" @@ -39,7 +42,7 @@ type CharacterSelect struct { characterNameLabel [8]d2ui.Label characterStatsLabel [8]d2ui.Label characterExpLabel [8]d2ui.Label - characterImage [8]*d2map.Hero + characterImage [8]*d2map.Player gameStates []*d2gamestate.GameState selectedCharacter int mouseButtonPressed bool @@ -155,10 +158,7 @@ func (v *CharacterSelect) updateCharacterBoxes() { v.characterStatsLabel[i].SetText("Level 1 " + v.gameStates[idx].HeroType.String()) v.characterExpLabel[i].SetText(expText) // TODO: Generate or load the object from the actual player data... - v.characterImage[i] = d2map.CreateHero( - 0, - 0, - 0, + v.characterImage[i] = d2map.CreatePlayer("", 0, 0, 0, v.gameStates[idx].HeroType, d2inventory.HeroObjects[v.gameStates[idx].HeroType], ) @@ -293,5 +293,7 @@ func (v *CharacterSelect) refreshGameStates() { } func (v *CharacterSelect) onOkButtonClicked() { - d2scene.SetNextScene(CreateGame(v.gameStates[v.selectedCharacter])) + gameClient, _ := d2client.Create(d2clientconnectiontype.Local) + gameClient.Open(v.gameStates[v.selectedCharacter].FilePath) + d2scene.SetNextScene(CreateGame(gameClient)) } diff --git a/d2game/d2gamescene/game.go b/d2game/d2gamescene/game.go index a8a1b9ca..7939c3f6 100644 --- a/d2game/d2gamescene/game.go +++ b/d2game/d2gamescene/game.go @@ -3,86 +3,108 @@ package d2gamescene import ( "image/color" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gamestate" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map" + + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" ) type Game struct { - gameState *d2gamestate.GameState - pentSpinLeft *d2ui.Sprite - pentSpinRight *d2ui.Sprite - testLabel d2ui.Label - mapEngine *d2map.MapEngine - hero *d2map.Hero - gameControls *d2player.GameControls + //pentSpinLeft *d2ui.Sprite + //pentSpinRight *d2ui.Sprite + //testLabel d2ui.Label + gameClient *d2client.GameClient + mapRenderer *d2map.MapRenderer + gameControls *d2player.GameControls // TODO: Hack + localPlayer *d2map.Player } -func CreateGame(gameState *d2gamestate.GameState) *Game { - return &Game{gameState: gameState} +func CreateGame(gameClient *d2client.GameClient) *Game { + return &Game{ + gameClient: gameClient, + gameControls: nil, + localPlayer: nil, + mapRenderer: d2map.CreateMapRenderer(gameClient.MapEngine), + } } func (v *Game) OnLoad() error { - animation, _ := d2asset.LoadAnimation(d2resource.PentSpin, d2resource.PaletteSky) - v.pentSpinLeft, _ = d2ui.LoadSprite(animation) - v.pentSpinLeft.PlayBackward() - v.pentSpinLeft.SetPlayLengthMs(475) - v.pentSpinLeft.SetPosition(100, 300) + //animation, _ := d2asset.LoadAnimation(d2resource.PentSpin, d2resource.PaletteSky) + //v.pentSpinLeft, _ = d2ui.LoadSprite(animation) + //v.pentSpinLeft.PlayBackward() + //v.pentSpinLeft.SetPlayLengthMs(475) + //v.pentSpinLeft.SetPosition(100, 300) + // + //animation, _ = d2asset.LoadAnimation(d2resource.PentSpin, d2resource.PaletteSky) + //v.pentSpinRight, _ = d2ui.LoadSprite(animation) + //v.pentSpinRight.PlayForward() + //v.pentSpinRight.SetPlayLengthMs(475) + //v.pentSpinRight.SetPosition(650, 300) + // + //v.testLabel = d2ui.CreateLabel(d2resource.Font42, d2resource.PaletteUnits) + //v.testLabel.Alignment = d2ui.LabelAlignCenter + //v.testLabel.SetText("Soon :tm:") + //v.testLabel.SetPosition(400, 250) - animation, _ = d2asset.LoadAnimation(d2resource.PentSpin, d2resource.PaletteSky) - v.pentSpinRight, _ = d2ui.LoadSprite(animation) - v.pentSpinRight.PlayForward() - v.pentSpinRight.SetPlayLengthMs(475) - v.pentSpinRight.SetPosition(650, 300) + /* + startX, startY := v.mapEngine.GetStartPosition() + v.hero = d2map.CreateHero( + int(startX*5)+3, + int(startY*5)+3, + 0, + v.gameState.HeroType, + v.gameState.Equipment, + ) + v.mapEngine.AddEntity(v.hero) - v.testLabel = d2ui.CreateLabel(d2resource.Font42, d2resource.PaletteUnits) - v.testLabel.Alignment = d2ui.LabelAlignCenter - v.testLabel.SetText("Soon :tm:") - v.testLabel.SetPosition(400, 250) - - v.mapEngine = d2map.CreateMapEngine(v.gameState) - v.mapEngine.GenerateMap(d2enum.RegionAct1Town, 1, 0) - - startX, startY := v.mapEngine.GetStartPosition() - v.hero = d2map.CreateHero( - int(startX*5)+3, - int(startY*5)+3, - 0, - v.gameState.HeroType, - v.gameState.Equipment, - ) - v.mapEngine.AddEntity(v.hero) - - v.gameControls = d2player.NewGameControls(v.hero, v.mapEngine) - v.gameControls.Load() - d2input.BindHandler(v.gameControls) + v.gameControls = d2player.NewGameControls(v.hero, v.mapEngine) + v.gameControls.Load() + d2input.BindHandler(v.gameControls) + */ return nil } func (v *Game) OnUnload() error { - d2input.UnbindHandler(v.gameControls) + d2input.UnbindHandler(v.gameControls) // TODO: hack return nil } func (v *Game) Render(screen d2render.Surface) error { screen.Clear(color.Black) - v.mapEngine.Render(screen) - v.gameControls.Render(screen) + v.mapRenderer.Render(screen) + if v.gameControls != nil { + v.gameControls.Render(screen) + } return nil } func (v *Game) Advance(tickTime float64) error { - v.mapEngine.Advance(tickTime) + v.gameClient.MapEngine.Advance(tickTime) // TODO: Hack - rx, ry := v.mapEngine.WorldToOrtho(v.hero.AnimatedComposite.LocationX/5, v.hero.AnimatedComposite.LocationY/5) - v.mapEngine.MoveCameraTo(rx, ry) + // Bind the game controls to the player once it exists + if v.gameControls == nil { + for _, player := range v.gameClient.Players { + if player.Id != v.gameClient.PlayerId { + continue + } + v.localPlayer = player + v.gameControls = d2player.NewGameControls(player, v.gameClient.MapEngine, v.mapRenderer, v.gameClient) + v.gameControls.Load() + d2input.BindHandler(v.gameControls) + + break + } + } + + // Update the camera to focus on the player + if v.localPlayer != nil { + rx, ry := v.mapRenderer.WorldToOrtho(v.localPlayer.AnimatedComposite.LocationX/5, v.localPlayer.AnimatedComposite.LocationY/5) + v.mapRenderer.MoveCameraTo(rx, ry) + } return nil } diff --git a/d2game/d2gamescene/map_engine_testing.go b/d2game/d2gamescene/map_engine_testing.go index 2ebc7f1b..ae5bdafc 100644 --- a/d2game/d2gamescene/map_engine_testing.go +++ b/d2game/d2gamescene/map_engine_testing.go @@ -76,8 +76,9 @@ var regions = []RegionSpec{ } type MapEngineTest struct { - gameState *d2gamestate.GameState - mapEngine *d2map.MapEngine + gameState *d2gamestate.GameState + mapEngine *d2map.MapEngine + mapRenderer *d2map.MapRenderer //TODO: this is region specific properties, should be refactored for multi-region rendering currentRegion int @@ -125,20 +126,22 @@ func (met *MapEngineTest) LoadRegionByIndex(n int, levelPreset, fileIndex int) { } if n == 0 { - met.mapEngine.GenerateAct1Overworld() + met.mapEngine.GenerateAct1Overworld(true) } else { - met.mapEngine = d2map.CreateMapEngine(met.gameState) // necessary for map name update - met.mapEngine.GenerateMap(d2enum.RegionIdType(n), levelPreset, fileIndex) + met.mapEngine = d2map.CreateMapEngine() // necessary for map name update + met.mapEngine.GenerateMap(d2enum.RegionIdType(n), levelPreset, fileIndex, true) + met.mapRenderer.SetMapEngine(met.mapEngine) } - met.mapEngine.MoveCameraTo(met.mapEngine.WorldToOrtho(met.mapEngine.GetCenterPosition())) + met.mapRenderer.MoveCameraTo(met.mapRenderer.WorldToOrtho(met.mapEngine.GetCenterPosition())) } func (met *MapEngineTest) OnLoad() error { // TODO: Game seed comes from the game state object d2input.BindHandler(met) - met.mapEngine = d2map.CreateMapEngine(met.gameState) + met.mapEngine = d2map.CreateMapEngine() + met.mapRenderer = d2map.CreateMapRenderer(met.mapEngine) met.LoadRegionByIndex(met.currentRegion, met.levelPreset, met.fileIndex) return nil @@ -150,10 +153,10 @@ func (met *MapEngineTest) OnUnload() error { } func (met *MapEngineTest) Render(screen d2render.Surface) error { - met.mapEngine.Render(screen) + met.mapRenderer.Render(screen) screenX, screenY := d2render.GetCursorPos() - worldX, worldY := met.mapEngine.ScreenToWorld(screenX, screenY) + worldX, worldY := met.mapRenderer.ScreenToWorld(screenX, screenY) //subtileX := int(math.Ceil(math.Mod(worldX*10, 10))) / 2 //subtileY := int(math.Ceil(math.Mod(worldY*10, 10))) / 2 @@ -277,22 +280,22 @@ func (met *MapEngineTest) OnKeyRepeat(event d2input.KeyEvent) bool { } if event.Key == d2input.KeyDown { - met.mapEngine.MoveCameraBy(0, moveSpeed) + met.mapRenderer.MoveCameraBy(0, moveSpeed) return true } if event.Key == d2input.KeyUp { - met.mapEngine.MoveCameraBy(0, -moveSpeed) + met.mapRenderer.MoveCameraBy(0, -moveSpeed) return true } if event.Key == d2input.KeyRight { - met.mapEngine.MoveCameraBy(moveSpeed, 0) + met.mapRenderer.MoveCameraBy(moveSpeed, 0) return true } if event.Key == d2input.KeyLeft { - met.mapEngine.MoveCameraBy(-moveSpeed, 0) + met.mapRenderer.MoveCameraBy(-moveSpeed, 0) return true } diff --git a/d2game/d2gamescene/select_hero_class.go b/d2game/d2gamescene/select_hero_class.go index 06830587..b3e5c241 100644 --- a/d2game/d2gamescene/select_hero_class.go +++ b/d2game/d2gamescene/select_hero_class.go @@ -4,6 +4,9 @@ import ( "image" "image/color" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" + "github.com/OpenDiablo2/OpenDiablo2/d2common" dh "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" @@ -422,7 +425,9 @@ func (v SelectHeroClass) onExitButtonClicked() { func (v SelectHeroClass) onOkButtonClicked() { gameState := d2gamestate.CreateGameState(v.heroNameTextbox.GetText(), v.selectedHero, v.hardcoreCheckbox.GetCheckState()) - d2scene.SetNextScene(CreateGame(gameState)) + gameClient, _ := d2client.Create(d2clientconnectiontype.Local) + gameClient.Open(gameState.FilePath) + d2scene.SetNextScene(CreateGame(gameClient)) } func (v *SelectHeroClass) Render(screen d2render.Surface) error { diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index b34ffd30..7103352a 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -3,7 +3,6 @@ package d2player import ( "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" @@ -11,6 +10,8 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2term" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" ) type Panel interface { @@ -24,10 +25,12 @@ type Panel interface { var missileID = 59 type GameControls struct { - hero *d2map.Hero - mapEngine *d2map.MapEngine - inventory *Inventory - heroStats *HeroStats + hero *d2map.Player + mapEngine *d2map.MapEngine + mapRenderer *d2map.MapRenderer + inventory *Inventory + heroStats *HeroStats + gameClient *d2client.GameClient // UI globeSprite *d2ui.Sprite @@ -36,16 +39,19 @@ type GameControls struct { skillIcon *d2ui.Sprite } -func NewGameControls(hero *d2map.Hero, mapEngine *d2map.MapEngine) *GameControls { +func NewGameControls(hero *d2map.Player, mapEngine *d2map.MapEngine, mapRenderer *d2map.MapRenderer, + gameClient *d2client.GameClient) *GameControls { d2term.BindAction("setmissile", "set missile id to summon on right click", func(id int) { missileID = id }) return &GameControls{ - hero: hero, - mapEngine: mapEngine, - inventory: NewInventory(), - heroStats: NewHeroStats(), + hero: hero, + mapEngine: mapEngine, + gameClient: gameClient, + mapRenderer: mapRenderer, + inventory: NewInventory(), + heroStats: NewHeroStats(), } } @@ -63,21 +69,22 @@ func (g *GameControls) OnKeyDown(event d2input.KeyEvent) bool { } func (g *GameControls) OnMouseButtonDown(event d2input.MouseEvent) bool { - px, py := g.mapEngine.ScreenToWorld(event.X, event.Y) + px, py := g.mapRenderer.ScreenToWorld(event.X, event.Y) px = float64(int(px*10)) / 10.0 py = float64(int(py*10)) / 10.0 heroPosX := g.hero.AnimatedComposite.LocationX / 5.0 heroPosY := g.hero.AnimatedComposite.LocationY / 5.0 if event.Button == d2input.MouseButtonLeft { - path, _, found := g.mapEngine.PathFind(heroPosX, heroPosY, px, py) - if found { - g.hero.AnimatedComposite.SetPath(path, func() { - g.hero.AnimatedComposite.SetAnimationMode( - d2enum.AnimationModeObjectNeutral.String(), - ) - }) - } + g.gameClient.SendPacketToServer(d2netpacket.CreateMovePlayerPacket(g.gameClient.PlayerId, heroPosX, heroPosY, px, py)) + //path, _, found := g.mapEngine.PathFind(heroPosX, heroPosY, px, py) + //if found { + // g.hero.AnimatedComposite.SetPath(path, func() { + // g.hero.AnimatedComposite.SetAnimationMode( + // d2enum.AnimationModeObjectNeutral.String(), + // ) + // }) + //} return true } diff --git a/d2game/d2player/inventory_grid.go b/d2game/d2player/inventory_grid.go index 2c3b234b..8a47f49c 100644 --- a/d2game/d2player/inventory_grid.go +++ b/d2game/d2player/inventory_grid.go @@ -14,7 +14,7 @@ import ( type InventoryItem interface { InventoryGridSize() (width int, height int) - ItemCode() string + GetItemCode() string InventoryGridSlot() (x int, y int) SetInventoryGridSlot(x int, y int) } @@ -94,23 +94,23 @@ func (g *ItemGrid) Load(items ...InventoryItem) { var itemSprite *d2ui.Sprite for _, item := range items { - if _, exists := g.sprites[item.ItemCode()]; exists { + if _, exists := g.sprites[item.GetItemCode()]; exists { // Already loaded, don't reload. continue } // TODO: Put the pattern into D2Shared animation, err := d2asset.LoadAnimation( - fmt.Sprintf("/data/global/items/inv%s.dc6", item.ItemCode()), + fmt.Sprintf("/data/global/items/inv%s.dc6", item.GetItemCode()), d2resource.PaletteSky, ) if err != nil { - log.Printf("failed to load sprite for item (%s): %v", item.ItemCode(), err) + log.Printf("failed to load sprite for item (%s): %v", item.GetItemCode(), err) continue } itemSprite, err = d2ui.LoadSprite(animation) - g.sprites[item.ItemCode()] = itemSprite + g.sprites[item.GetItemCode()] = itemSprite } } @@ -156,7 +156,7 @@ func (g *ItemGrid) canFit(x int, y int, item InventoryItem) bool { func (g *ItemGrid) Set(x int, y int, item InventoryItem) error { if !g.canFit(x, y, item) { - return fmt.Errorf("can not set item (%s) to position (%v, %v)", item.ItemCode(), x, y) + return fmt.Errorf("can not set item (%s) to position (%v, %v)", item.GetItemCode(), x, y) } g.set(x, y, item) return nil @@ -189,7 +189,7 @@ func (g *ItemGrid) Render(target d2render.Surface) { continue } - itemSprite := g.sprites[item.ItemCode()] + itemSprite := g.sprites[item.GetItemCode()] if itemSprite == nil { // In case it failed to load. // TODO: fallback to something diff --git a/d2game/d2player/inventory_grid_test.go b/d2game/d2player/inventory_grid_test.go index 0301a2fd..fe882bdb 100644 --- a/d2game/d2player/inventory_grid_test.go +++ b/d2game/d2player/inventory_grid_test.go @@ -17,7 +17,7 @@ func (t *TestItem) InventoryGridSize() (int, int) { return t.width, t.height } -func (t *TestItem) ItemCode() string { +func (t *TestItem) GetItemCode() string { return "" } diff --git a/d2networking/client_listener.go b/d2networking/client_listener.go new file mode 100644 index 00000000..d658d7cf --- /dev/null +++ b/d2networking/client_listener.go @@ -0,0 +1,7 @@ +package d2networking + +import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" + +type ClientListener interface { + OnPacketReceived(packet d2netpacket.NetPacket) error +} diff --git a/d2networking/d2client/client_connection.go b/d2networking/d2client/client_connection.go new file mode 100644 index 00000000..fb6458ba --- /dev/null +++ b/d2networking/d2client/client_connection.go @@ -0,0 +1,13 @@ +package d2client + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2networking" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" +) + +type ClientConnection interface { + Open(connectionString string) error + Close() error + SendPacketToServer(packet d2netpacket.NetPacket) error + SetClientListener(listener d2networking.ClientListener) +} diff --git a/d2networking/d2client/d2clientconnectiontype/connectiontype.go b/d2networking/d2client/d2clientconnectiontype/connectiontype.go new file mode 100644 index 00000000..b310eb71 --- /dev/null +++ b/d2networking/d2client/d2clientconnectiontype/connectiontype.go @@ -0,0 +1,7 @@ +package d2clientconnectiontype + +type ClientConnectionType int + +const ( + Local ClientConnectionType = 1 +) diff --git a/d2networking/d2client/d2localclient/local_client_connection.go b/d2networking/d2client/d2localclient/local_client_connection.go new file mode 100644 index 00000000..c1f8e9c3 --- /dev/null +++ b/d2networking/d2client/d2localclient/local_client_connection.go @@ -0,0 +1,55 @@ +package d2localclient + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2networking" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2server" + uuid "github.com/satori/go.uuid" +) + +type LocalClientConnection struct { + clientListener d2networking.ClientListener + uniqueId string +} + +func (l LocalClientConnection) GetUniqueId() string { + return l.uniqueId +} + +func (l LocalClientConnection) GetConnectionType() string { + return "Local Client" +} + +func (l *LocalClientConnection) SendPacketToClient(packet d2netpacket.NetPacket) error { + return l.clientListener.OnPacketReceived(packet) +} + +func Create() *LocalClientConnection { + result := &LocalClientConnection{ + uniqueId: uuid.NewV4().String(), + } + + return result +} + +func (l *LocalClientConnection) Open(gameStatePath string) error { + d2server.Create(gameStatePath) + go d2server.Run() + d2server.OnClientConnected(l) + return nil +} + +func (l *LocalClientConnection) Close() error { + d2server.OnClientDisconnected(l) + d2server.Destroy() + return nil +} + +func (l *LocalClientConnection) SendPacketToServer(packet d2netpacket.NetPacket) error { + // TODO: This is going to blow up if the server has ceased to be. + return d2server.OnPacketReceived(l, packet) +} + +func (l *LocalClientConnection) SetClientListener(listener d2networking.ClientListener) { + l.clientListener = listener +} diff --git a/d2networking/d2client/game_client.go b/d2networking/d2client/game_client.go new file mode 100644 index 00000000..723cd53e --- /dev/null +++ b/d2networking/d2client/game_client.go @@ -0,0 +1,94 @@ +package d2client + +import ( + "fmt" + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gamestate" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map" + + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2localclient" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" +) + +type GameClient struct { + clientConnection ClientConnection + GameState *d2gamestate.GameState + MapEngine *d2map.MapEngine + PlayerId string + Players map[string]*d2map.Player +} + +func Create(connectionType d2clientconnectiontype.ClientConnectionType) (*GameClient, error) { + result := &GameClient{ + MapEngine: d2map.CreateMapEngine(), + Players: make(map[string]*d2map.Player, 0), + } + + switch connectionType { + case d2clientconnectiontype.Local: + result.clientConnection = d2localclient.Create() + result.clientConnection.SetClientListener(result) + default: + return nil, fmt.Errorf("unknown client connection type specified: %d", connectionType) + } + + return result, nil +} + +func (g *GameClient) Open(connectionString string) error { + return g.clientConnection.Open(connectionString) +} + +func (g *GameClient) Close() error { + return g.clientConnection.Close() +} + +func (g *GameClient) Destroy() error { + return g.clientConnection.Close() +} + +func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error { + switch packet.PacketType { + case d2netpackettype.GenerateMap: + mapData := packet.PacketData.(d2netpacket.GenerateMapPacket) + g.MapEngine.GenerateMap(mapData.RegionType, mapData.LevelPreset, mapData.FileIndex, true) + break + case d2netpackettype.UpdateServerInfo: + serverInfo := packet.PacketData.(d2netpacket.UpdateServerInfoPacket) + g.MapEngine.SetSeed(serverInfo.Seed) + g.PlayerId = serverInfo.PlayerId + log.Printf("Player id set to %s", serverInfo.PlayerId) + break + case d2netpackettype.AddPlayer: + player := packet.PacketData.(d2netpacket.AddPlayerPacket) + newPlayer := d2map.CreatePlayer(player.Id, player.X, player.Y, 0, player.HeroType, player.Equipment) + g.Players[newPlayer.Id] = newPlayer + g.MapEngine.AddEntity(newPlayer) + break + case d2netpackettype.MovePlayer: + movePlayer := packet.PacketData.(d2netpacket.MovePlayerPacket) + player := g.Players[movePlayer.PlayerId] + path, _, found := g.MapEngine.PathFind(movePlayer.StartX, movePlayer.StartY, movePlayer.DestX, movePlayer.DestY) + if found { + player.AnimatedComposite.SetPath(path, func() { + player.AnimatedComposite.SetAnimationMode( + d2enum.AnimationModeObjectNeutral.String(), + ) + }) + } + break + default: + log.Fatalf("Invalid packet type: %d", packet.PacketType) + } + return nil +} + +func (g *GameClient) SendPacketToServer(packet d2netpacket.NetPacket) error { + return g.clientConnection.SendPacketToServer(packet) +} diff --git a/d2networking/d2netpacket/d2netpackettype/message_type.go b/d2networking/d2netpacket/d2netpackettype/message_type.go new file mode 100644 index 00000000..829080c6 --- /dev/null +++ b/d2networking/d2netpacket/d2netpackettype/message_type.go @@ -0,0 +1,12 @@ +package d2netpackettype + +type NetPacketType uint32 + +// Warning: Do NOT re-arrange the order of these packet values unless you want to +// break compatibility between clients of slightly different versions. +const ( + UpdateServerInfo NetPacketType = iota + GenerateMap + AddPlayer + MovePlayer +) diff --git a/d2networking/d2netpacket/net_packet.go b/d2networking/d2netpacket/net_packet.go new file mode 100644 index 00000000..fba0f9cc --- /dev/null +++ b/d2networking/d2netpacket/net_packet.go @@ -0,0 +1,8 @@ +package d2netpacket + +import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" + +type NetPacket struct { + PacketType d2netpackettype.NetPacketType `json:"packetType"` + PacketData interface{} `json:"packetData"` +} diff --git a/d2networking/d2netpacket/packet_add_player.go b/d2networking/d2netpacket/packet_add_player.go new file mode 100644 index 00000000..60ac3429 --- /dev/null +++ b/d2networking/d2netpacket/packet_add_player.go @@ -0,0 +1,28 @@ +package d2netpacket + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" +) + +type AddPlayerPacket struct { + Id string `json:"id"` + X int `json:"x"` + Y int `json:"y"` + HeroType d2enum.Hero `json:"hero"` + Equipment d2inventory.CharacterEquipment `json:"equipment"` +} + +func CreateAddPlayerPacket(id string, x, y int, heroType d2enum.Hero, equipment d2inventory.CharacterEquipment) NetPacket { + return NetPacket{ + PacketType: d2netpackettype.AddPlayer, + PacketData: AddPlayerPacket{ + Id: id, + X: x, + Y: y, + HeroType: heroType, + Equipment: equipment, + }, + } +} diff --git a/d2networking/d2netpacket/packet_generate_map.go b/d2networking/d2netpacket/packet_generate_map.go new file mode 100644 index 00000000..76e941ef --- /dev/null +++ b/d2networking/d2netpacket/packet_generate_map.go @@ -0,0 +1,24 @@ +package d2netpacket + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" +) + +type GenerateMapPacket struct { + RegionType d2enum.RegionIdType `json:"regionType"` + LevelPreset int `json:"levelPreset"` + FileIndex int `json:"fileIndex"` +} + +func CreateGenerateMapPacket(regionType d2enum.RegionIdType, levelPreset int, fileIndex int) NetPacket { + return NetPacket{ + PacketType: d2netpackettype.GenerateMap, + PacketData: GenerateMapPacket{ + RegionType: regionType, + LevelPreset: levelPreset, + FileIndex: fileIndex, + }, + } + +} diff --git a/d2networking/d2netpacket/packet_move_player.go b/d2networking/d2netpacket/packet_move_player.go new file mode 100644 index 00000000..9338082e --- /dev/null +++ b/d2networking/d2netpacket/packet_move_player.go @@ -0,0 +1,26 @@ +package d2netpacket + +import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" + +// TODO: Need to handle being on different maps + +type MovePlayerPacket struct { + PlayerId string `json:"playerId"` + StartX float64 `json:"startX"` + StartY float64 `json:"startY"` + DestX float64 `json:"destX"` + DestY float64 `json:"destY"` +} + +func CreateMovePlayerPacket(playerId string, startX, startY, destX, destY float64) NetPacket { + return NetPacket{ + PacketType: d2netpackettype.MovePlayer, + PacketData: MovePlayerPacket{ + PlayerId: playerId, + StartX: startX, + StartY: startY, + DestX: destX, + DestY: destY, + }, + } +} diff --git a/d2networking/d2netpacket/packet_update_server_info.go b/d2networking/d2netpacket/packet_update_server_info.go new file mode 100644 index 00000000..8ea94b40 --- /dev/null +++ b/d2networking/d2netpacket/packet_update_server_info.go @@ -0,0 +1,18 @@ +package d2netpacket + +import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" + +type UpdateServerInfoPacket struct { + Seed int64 `json:"seed"` + PlayerId string `json:"playerId"` +} + +func CreateUpdateServerInfoPacket(seed int64, playerId string) NetPacket { + return NetPacket{ + PacketType: d2netpackettype.UpdateServerInfo, + PacketData: UpdateServerInfoPacket{ + Seed: seed, + PlayerId: playerId, + }, + } +} diff --git a/d2networking/d2server/client_connection.go b/d2networking/d2server/client_connection.go new file mode 100644 index 00000000..7bb98400 --- /dev/null +++ b/d2networking/d2server/client_connection.go @@ -0,0 +1,9 @@ +package d2server + +import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" + +type ClientConnection interface { + GetUniqueId() string + GetConnectionType() string + SendPacketToClient(packet d2netpacket.NetPacket) error +} diff --git a/d2networking/d2server/game_server.go b/d2networking/d2server/game_server.go new file mode 100644 index 00000000..10f72f53 --- /dev/null +++ b/d2networking/d2server/game_server.go @@ -0,0 +1,88 @@ +package d2server + +import ( + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gamestate" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" +) + +type GameServer struct { + gameState *d2gamestate.GameState + clientConnections map[string]ClientConnection + mapEngines []*d2map.MapEngine +} + +var singletonServer *GameServer + +func Create(gameStatePath string) { + log.Print("Creating GameServer") + if singletonServer != nil { + return + } + + singletonServer = &GameServer{ + clientConnections: make(map[string]ClientConnection), + mapEngines: make([]*d2map.MapEngine, 0), + gameState: d2gamestate.LoadGameState(gameStatePath), + } + + mapEngine := d2map.CreateMapEngine() + mapEngine.GenerateMap(d2enum.RegionAct1Town, 1, 0, false) + singletonServer.mapEngines = append(singletonServer.mapEngines, mapEngine) +} + +func Run() { + log.Print("Starting GameServer") +} + +func Stop() { + log.Print("Stopping GameServer") +} + +func Destroy() { + if singletonServer == nil { + return + } + log.Print("Destroying GameServer") + Stop() +} + +func OnClientConnected(client ClientConnection) { + log.Printf("Client connected with an id of %s", client.GetUniqueId()) + singletonServer.clientConnections[client.GetUniqueId()] = client + client.SendPacketToClient(d2netpacket.CreateUpdateServerInfoPacket(singletonServer.gameState.Seed, client.GetUniqueId())) + client.SendPacketToClient(d2netpacket.CreateGenerateMapPacket(d2enum.RegionAct1Town, 1, 0)) + + // TODO: This needs to use a real method of loading characters instead of cloning the 'save file character' + sx, sy := singletonServer.mapEngines[0].GetStartPosition() // TODO: Another temporary hack + createPlayerPacket := d2netpacket.CreateAddPlayerPacket(client.GetUniqueId(), int(sx*5)+3, int(sy*5)+3, + singletonServer.gameState.HeroType, singletonServer.gameState.Equipment) + for _, connection := range singletonServer.clientConnections { + connection.SendPacketToClient(createPlayerPacket) + } + +} + +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.... + for _, player := range singletonServer.clientConnections { + player.SendPacketToClient(packet) + } + break + } + return nil +} diff --git a/go.mod b/go.mod index 11270668..958598bf 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,9 @@ require ( github.com/beefsack/go-astar v0.0.0-20171024231011-f324bbb0d6f7 github.com/go-restruct/restruct v0.0.0-20191227155143-5734170a48a1 github.com/hajimehoshi/ebiten v1.11.0-alpha.2.0.20200102072751-e66f1fb71e2e + github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect + github.com/pebbe/zmq4 v1.2.1 // indirect + github.com/satori/go.uuid v1.2.0 github.com/stretchr/testify v1.4.0 golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 // indirect golang.org/x/mobile v0.0.0-20191115022231-f0c40035f2ba // indirect diff --git a/go.sum b/go.sum index 164ce5fa..db4026dc 100644 --- a/go.sum +++ b/go.sum @@ -34,11 +34,18 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-sqlite3 v1.13.0 h1:LnJI81JidiW9r7pS/hXe6cFeO5EXNq7KbfvoJLRI69c= +github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U= +github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= +github.com/pebbe/zmq4 v1.2.1 h1:jrXQW3mD8Si2mcSY/8VBs2nNkK/sKCOEM0rHAfxyc8c= +github.com/pebbe/zmq4 v1.2.1/go.mod h1:7N4y5R18zBiu3l0vajMUWQgZyjv464prE8RCyBcmnZM= github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= diff --git a/main.go b/main.go index fd404927..b63557bb 100644 --- a/main.go +++ b/main.go @@ -13,6 +13,8 @@ import ( "strconv" "sync" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory" + "gopkg.in/alecthomas/kingpin.v2" "github.com/OpenDiablo2/OpenDiablo2/d2common" @@ -182,6 +184,8 @@ func initialize() error { return err } + d2inventory.LoadHeroObjects() + d2ui.Initialize() return nil