Saving and loading game data, Fixes #868 #799 (#883)

* saving of player should be done on the server. That's also where the loading happens.

* refactor nearly everything, but this time it looks not that bad...

* MAke Linter happy

* Typo... uuups
This commit is contained in:
Thomas Christlieb 2020-10-31 19:30:08 +01:00 committed by GitHub
parent fb8e25ebdb
commit 40cc421f51
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 136 additions and 42 deletions

View File

@ -7,14 +7,16 @@ import (
// HeroState stores the state of the player
type HeroState struct {
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"`
Stats *HeroStatsState `json:"stats"`
Skills map[int]*HeroSkill `json:"skills"`
X float64 `json:"x"`
Y float64 `json:"y"`
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"`
Stats *HeroStatsState `json:"stats"`
Skills map[int]*HeroSkill `json:"skills"`
X float64 `json:"x"`
Y float64 `json:"y"`
LeftSkill int `json:"leftSkill"`
RightSkill int `json:"rightSkill"`
}

View File

@ -64,7 +64,8 @@ func NewAnimatedEntity(x, y int, animation d2interface.Animation) *AnimatedEntit
// NewPlayer creates a new player entity and returns a pointer to it.
func (f *MapEntityFactory) NewPlayer(id, name string, x, y, direction int, heroType d2enum.Hero,
stats *d2hero.HeroStatsState, skills map[int]*d2hero.HeroSkill, equipment *d2inventory.CharacterEquipment) *Player {
stats *d2hero.HeroStatsState, skills map[int]*d2hero.HeroSkill, equipment *d2inventory.CharacterEquipment,
leftSkill, rightSkill int) *Player {
layerEquipment := &[d2enum.CompositeTypeMax]string{
d2enum.CompositeTypeHead: equipment.Head.GetArmorClass(),
d2enum.CompositeTypeTorso: equipment.Torso.GetArmorClass(),
@ -89,16 +90,14 @@ func (f *MapEntityFactory) NewPlayer(id, name string, x, y, direction int, heroT
statsState := f.HeroStateFactory.CreateHeroStatsState(heroType, defaultCharStats)
heroState, _ := f.CreateHeroState(name, heroType, statsState)
attackSkillID := 0
result := &Player{
mapEntity: newMapEntity(x, y),
composite: composite,
Equipment: equipment,
Stats: heroState.Stats,
Skills: heroState.Skills,
// https://github.com/OpenDiablo2/OpenDiablo2/issues/799
LeftSkill: heroState.Skills[attackSkillID],
RightSkill: heroState.Skills[attackSkillID],
mapEntity: newMapEntity(x, y),
composite: composite,
Equipment: equipment,
Stats: heroState.Stats,
Skills: heroState.Skills,
LeftSkill: heroState.Skills[leftSkill],
RightSkill: heroState.Skills[rightSkill],
name: name,
Class: heroType,
//nameLabel: d2ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteStatic),
@ -108,7 +107,6 @@ func (f *MapEntityFactory) NewPlayer(id, name string, x, y, direction int, heroT
}
result.mapEntity.uuid = id
// https://github.com/OpenDiablo2/OpenDiablo2/issues/799
result.SetSpeed(baseWalkSpeed)
result.mapEntity.directioner = result.rotate
err = composite.SetMode(d2enum.PlayerAnimationModeTownNeutral, equipment.RightHand.GetWeaponClass())

View File

@ -353,6 +353,8 @@ func (v *CharacterSelect) updateCharacterBoxes() {
v.gameStates[idx].Stats,
v.gameStates[idx].Skills,
&equipment,
v.gameStates[idx].LeftSkill,
v.gameStates[idx].RightSkill,
)
}
}

View File

@ -186,6 +186,10 @@ func (v *Game) OnUnload() error {
return err
}
if err := v.OnPlayerSave(); err != nil {
return err
}
if err := v.gameClient.Close(); err != nil {
return err
}
@ -309,14 +313,26 @@ func (v *Game) OnPlayerMove(targetX, targetY float64) {
worldPosition := v.localPlayer.Position.World()
playerID, worldX, worldY := v.gameClient.PlayerID, worldPosition.X(), worldPosition.Y()
createPlayerPacket := d2netpacket.CreateMovePlayerPacket(playerID, worldX, worldY, targetX, targetY)
err := v.gameClient.SendPacketToServer(createPlayerPacket)
createMovePlayerPacket := d2netpacket.CreateMovePlayerPacket(playerID, worldX, worldY, targetX, targetY)
err := v.gameClient.SendPacketToServer(createMovePlayerPacket)
if err != nil {
fmt.Printf(moveErrStr, v.gameClient.PlayerID, targetX, targetY)
}
}
// OnPlayerSave instructs the server to save our player data
func (v *Game) OnPlayerSave() error {
playerState := v.gameClient.Players[v.gameClient.PlayerID]
err := v.gameClient.SendPacketToServer(d2netpacket.CreateSavePlayerPacket(playerState))
if err != nil {
return err
}
return nil
}
// OnPlayerCast sends the casting skill action to the server
func (v *Game) OnPlayerCast(skillID int, targetX, targetY float64) {
err := v.gameClient.SendPacketToServer(d2netpacket.CreateCastPacket(v.gameClient.PlayerID, skillID, targetX, targetY))

View File

@ -801,7 +801,6 @@ func (g *GameControls) Load() {
log.Print(err)
}
// https://github.com/OpenDiablo2/OpenDiablo2/issues/799
genericSkillsSprite, err := g.ui.NewSprite(d2resource.GenericSkills, d2resource.PaletteSky)
if err != nil {
log.Print(err)

View File

@ -207,7 +207,7 @@ func (g *GameClient) handleAddPlayerPacket(packet d2netpacket.NetPacket) error {
d2hero.HydrateSkills(player.Skills, g.asset)
newPlayer := g.MapEngine.NewPlayer(player.ID, player.Name, player.X, player.Y, 0,
player.HeroType, player.Stats, player.Skills, &player.Equipment)
player.HeroType, player.Stats, player.Skills, &player.Equipment, player.LeftSkill, player.RightSkill)
g.Players[newPlayer.ID()] = newPlayer
g.MapEngine.AddEntity(newPlayer)

View File

@ -30,6 +30,7 @@ const (
ServerClosed // Sent by the local host when it has closed the server
CastSkill // Sent by client or server, indicates entity casting skill
SpawnItem // Sent by server
SavePlayer // Sent by the client, saves the player
UnknownPacketType = 666
)
@ -47,6 +48,7 @@ func (n NetPacketType) String() string {
ServerClosed: "ServerClosed",
CastSkill: "CastSkill",
SpawnItem: "SpawnItem",
SavePlayer: "SavePlayer",
}
return strings[n]

View File

@ -14,29 +14,34 @@ import (
// It is sent by the server to create the entity for a newly connected
// player on a client.
type AddPlayerPacket struct {
ID string `json:"id"`
Name string `json:"name"`
X int `json:"x"`
Y int `json:"y"`
HeroType d2enum.Hero `json:"hero"`
Equipment d2inventory.CharacterEquipment `json:"equipment"`
Stats *d2hero.HeroStatsState `json:"heroStats"`
Skills map[int]*d2hero.HeroSkill `json:"heroSkills"`
ID string `json:"id"`
Name string `json:"name"`
X int `json:"x"`
Y int `json:"y"`
HeroType d2enum.Hero `json:"hero"`
Equipment d2inventory.CharacterEquipment `json:"equipment"`
Stats *d2hero.HeroStatsState `json:"heroStats"`
Skills map[int]*d2hero.HeroSkill `json:"heroSkills"`
LeftSkill int `json:"leftSkill"`
RightSkill int `json:"rightSkill"`
}
// CreateAddPlayerPacket returns a NetPacket which declares an
// AddPlayerPacket with the data in given parameters.
func CreateAddPlayerPacket(id, name string, x, y int, heroType d2enum.Hero,
stats *d2hero.HeroStatsState, skills map[int]*d2hero.HeroSkill, equipment d2inventory.CharacterEquipment) NetPacket {
stats *d2hero.HeroStatsState, skills map[int]*d2hero.HeroSkill, equipment d2inventory.CharacterEquipment,
leftSkill, rightSkill int) NetPacket {
addPlayerPacket := AddPlayerPacket{
ID: id,
Name: name,
X: x,
Y: y,
HeroType: heroType,
Equipment: equipment,
Stats: stats,
Skills: skills,
ID: id,
Name: name,
X: x,
Y: y,
HeroType: heroType,
Equipment: equipment,
Stats: stats,
Skills: skills,
LeftSkill: leftSkill,
RightSkill: rightSkill,
}
b, err := json.Marshal(addPlayerPacket)

View File

@ -0,0 +1,44 @@
package d2netpacket
import (
"encoding/json"
"log"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
)
// SavePlayerPacket has the actual selected left and right skill
// the Server has to check if these skills are actually allowed for the Player
type SavePlayerPacket struct {
LeftSkill int `json:"leftSkill"`
RightSkill int `json:"rightSkill"`
}
// CreateSavePlayerPacket sends a packet which instructs the server to save the Player
func CreateSavePlayerPacket(playerState *d2mapentity.Player) NetPacket {
savePlayerData := SavePlayerPacket{
LeftSkill: playerState.LeftSkill.ID,
RightSkill: playerState.RightSkill.ID,
}
b, err := json.Marshal(savePlayerData)
if err != nil {
log.Print(err)
}
return NetPacket{
PacketType: d2netpackettype.SavePlayer,
PacketData: b,
}
}
// UnmarshalSavePlayer unmarshalls the given data to a SavePlayerPacket struct
func UnmarshalSavePlayer(packet []byte) (SavePlayerPacket, error) {
var p SavePlayerPacket
if err := json.Unmarshal(packet, &p); err != nil {
return p, err
}
return p, nil
}

View File

@ -50,6 +50,7 @@ type GameServer struct {
seed int64
maxConnections int
packetManagerChan chan []byte
heroStateFactory *d2hero.HeroStateFactory
}
// https://github.com/OpenDiablo2/OpenDiablo2/issues/824
@ -68,6 +69,11 @@ func NewGameServer(asset *d2asset.AssetManager, networkServer bool,
maxConnections = []int{8}
}
heroStateFactory, err := d2hero.NewHeroStateFactory(asset)
if err != nil {
return nil, err
}
ctx, cancel := context.WithCancel(context.Background())
gameServer := &GameServer{
@ -81,6 +87,7 @@ func NewGameServer(asset *d2asset.AssetManager, networkServer bool,
mapEngines: make([]*d2mapengine.MapEngine, 0),
scriptEngine: d2script.CreateScriptEngine(),
seed: time.Now().UnixNano(),
heroStateFactory: heroStateFactory,
}
// https://github.com/OpenDiablo2/OpenDiablo2/issues/827
@ -365,6 +372,8 @@ func handleClientConnection(gameServer *GameServer, client ClientConnection, x,
playerState.Stats,
playerState.Skills,
playerState.Equipment,
playerState.LeftSkill,
playerState.RightSkill,
)
for _, connection := range gameServer.connections {
@ -390,6 +399,8 @@ func handleClientConnection(gameServer *GameServer, client ClientConnection, x,
conPlayerState.Stats,
conPlayerState.Skills,
conPlayerState.Equipment,
conPlayerState.LeftSkill,
conPlayerState.RightSkill,
),
)
@ -407,6 +418,7 @@ func OnClientDisconnected(client ClientConnection) {
}
// OnPacketReceived is called by the local client to 'send' a packet to the server.
// nolint:gocyclo // switch statement on packet type makes sense, no need to change
func OnPacketReceived(client ClientConnection, packet d2netpacket.NetPacket) error {
if singletonServer == nil {
return errors.New("singleton server is nil")
@ -443,6 +455,20 @@ func OnPacketReceived(client ClientConnection, packet d2netpacket.NetPacket) err
log.Printf("GameServer: error sending %T to client %s: %s", packet, player.GetUniqueID(), err)
}
}
case d2netpackettype.SavePlayer:
savePacket, err := d2netpacket.UnmarshalSavePlayer(packet.PacketData)
if err != nil {
return err
}
playerState := singletonServer.connections[client.GetUniqueID()].GetPlayerState()
playerState.LeftSkill = savePacket.LeftSkill
playerState.RightSkill = savePacket.RightSkill
err = singletonServer.heroStateFactory.Save(playerState)
if err != nil {
log.Printf("GameServer: error saving saving Player: %s", err)
}
default:
log.Printf("GameServer: received unknown packet %T", packet)
}