Initial work to separate client and server logic (#330)

* Switched to json formatted characters

* Added infrastructure for networking

* Minor updates.

* more updates for map engine/rendering

* More map engine changes and fixes
This commit is contained in:
Tim Sarbin 2020-06-13 18:32:09 -04:00 committed by GitHub
parent 515b66736d
commit 52f8cd6d0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 726 additions and 297 deletions

1
.gitignore vendored
View File

@ -8,3 +8,4 @@
/OpenDiablo2.exe
/OpenDiablo2
**/*.pprof
tags

View File

@ -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)
}

View File

@ -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?
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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))

View File

@ -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()
}

View File

@ -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
}

60
d2core/d2map/renderer.go Normal file
View File

@ -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)
}

View File

@ -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))
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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

View File

@ -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 ""
}

View File

@ -0,0 +1,7 @@
package d2networking
import "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket"
type ClientListener interface {
OnPacketReceived(packet d2netpacket.NetPacket) error
}

View File

@ -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)
}

View File

@ -0,0 +1,7 @@
package d2clientconnectiontype
type ClientConnectionType int
const (
Local ClientConnectionType = 1
)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
)

View File

@ -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"`
}

View File

@ -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,
},
}
}

View File

@ -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,
},
}
}

View File

@ -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,
},
}
}

View File

@ -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,
},
}
}

View File

@ -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
}

View File

@ -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
}

3
go.mod
View File

@ -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

7
go.sum
View File

@ -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=

View File

@ -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