2019-11-10 03:36:53 -05:00
|
|
|
package d2core
|
2019-10-31 21:17:13 -04:00
|
|
|
|
|
|
|
import (
|
2019-11-11 23:48:55 -05:00
|
|
|
"io/ioutil"
|
2019-10-31 21:17:13 -04:00
|
|
|
"log"
|
2019-11-11 23:48:55 -05:00
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2019-10-31 21:17:13 -04:00
|
|
|
"time"
|
2019-11-11 23:48:55 -05:00
|
|
|
|
2019-11-17 00:16:33 -05:00
|
|
|
"github.com/OpenDiablo2/D2Shared/d2common"
|
2019-11-11 23:48:55 -05:00
|
|
|
|
2019-11-17 00:16:33 -05:00
|
|
|
"github.com/OpenDiablo2/D2Shared/d2common/d2enum"
|
2019-10-31 21:17:13 -04:00
|
|
|
)
|
|
|
|
|
2019-11-11 23:48:55 -05:00
|
|
|
/*
|
|
|
|
File Spec
|
|
|
|
--------------------------------------------
|
|
|
|
UINT32 GameState Version
|
|
|
|
INT64 Game Seed
|
|
|
|
BYTE Hero Type
|
2019-11-14 22:20:01 -05:00
|
|
|
BYTE Hero Level
|
2019-11-11 23:48:55 -05:00
|
|
|
BYTE Act
|
|
|
|
BYTE Hero Name Length
|
|
|
|
BYTE[] Hero Name
|
|
|
|
--------------------------------------------
|
|
|
|
*/
|
|
|
|
|
2019-10-31 21:17:13 -04:00
|
|
|
type GameState struct {
|
2019-11-14 22:20:01 -05:00
|
|
|
Seed int64
|
|
|
|
HeroName string
|
|
|
|
HeroType d2enum.Hero
|
|
|
|
HeroLevel int
|
|
|
|
Act int
|
|
|
|
FilePath string
|
|
|
|
Equipment CharacterEquipment
|
2019-11-11 23:48:55 -05:00
|
|
|
}
|
|
|
|
|
2019-11-14 22:20:01 -05:00
|
|
|
const GameStateVersion = uint32(2) // Update this when you make breaking changes
|
2019-11-11 23:48:55 -05:00
|
|
|
|
|
|
|
func HasGameStates() bool {
|
2019-12-17 23:20:06 -05:00
|
|
|
basePath, _ := getGameBaseSavePath()
|
|
|
|
files, _ := ioutil.ReadDir(basePath)
|
2019-11-11 23:48:55 -05:00
|
|
|
return len(files) > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func GetAllGameStates() []*GameState {
|
|
|
|
// TODO: Make this not crash tf out on bad files
|
2019-12-17 23:20:06 -05:00
|
|
|
basePath, _ := getGameBaseSavePath()
|
2019-11-11 23:48:55 -05:00
|
|
|
files, _ := ioutil.ReadDir(basePath)
|
|
|
|
result := make([]*GameState, 0)
|
|
|
|
for _, file := range files {
|
|
|
|
fileName := file.Name()
|
|
|
|
if file.IsDir() || len(fileName) < 5 || strings.ToLower(fileName[len(fileName)-4:]) != ".od2" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
gameState := LoadGameState(path.Join(basePath, file.Name()))
|
|
|
|
if gameState == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
result = append(result, gameState)
|
|
|
|
}
|
|
|
|
return result
|
2019-10-31 21:17:13 -04:00
|
|
|
}
|
|
|
|
|
2019-11-11 23:48:55 -05:00
|
|
|
// CreateTestGameState is used for the map engine previewer
|
|
|
|
func CreateTestGameState() *GameState {
|
2019-10-31 21:17:13 -04:00
|
|
|
result := &GameState{
|
|
|
|
Seed: time.Now().UnixNano(),
|
|
|
|
}
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
|
|
|
func LoadGameState(path string) *GameState {
|
2019-11-11 23:48:55 -05:00
|
|
|
result := &GameState{
|
|
|
|
FilePath: path,
|
|
|
|
}
|
|
|
|
f, err := os.Open(path)
|
|
|
|
if err != nil {
|
|
|
|
log.Panicf(err.Error())
|
|
|
|
}
|
|
|
|
bytes, err := ioutil.ReadAll(f)
|
|
|
|
if err != nil {
|
|
|
|
log.Panicf(err.Error())
|
|
|
|
}
|
2019-11-12 23:44:04 -05:00
|
|
|
defer f.Close()
|
2019-11-11 23:48:55 -05:00
|
|
|
sr := d2common.CreateStreamReader(bytes)
|
2019-11-14 22:20:01 -05:00
|
|
|
if sr.GetUInt32() != GameStateVersion {
|
2019-11-11 23:48:55 -05:00
|
|
|
// Unknown game version
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
result.Seed = sr.GetInt64()
|
|
|
|
result.HeroType = d2enum.Hero(sr.GetByte())
|
2019-11-14 22:20:01 -05:00
|
|
|
result.HeroLevel = int(sr.GetByte())
|
2019-11-11 23:48:55 -05:00
|
|
|
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: "",
|
|
|
|
}
|
|
|
|
result.Save()
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2019-12-17 23:20:06 -05:00
|
|
|
func getGameBaseSavePath() (string, error) {
|
|
|
|
configDir, err := os.UserConfigDir()
|
2019-11-11 23:48:55 -05:00
|
|
|
if err != nil {
|
2019-12-17 23:20:06 -05:00
|
|
|
return "", err
|
2019-11-11 23:48:55 -05:00
|
|
|
}
|
2019-12-17 23:20:06 -05:00
|
|
|
|
|
|
|
return path.Join(configDir, "OpenDiablo2/Saves"), nil
|
2019-11-11 23:48:55 -05:00
|
|
|
}
|
|
|
|
|
2019-11-14 22:20:01 -05:00
|
|
|
func getFirstFreeFileName() string {
|
2019-11-11 23:48:55 -05:00
|
|
|
i := 0
|
2019-12-17 23:20:06 -05:00
|
|
|
basePath, _ := getGameBaseSavePath()
|
2019-11-11 23:48:55 -05:00
|
|
|
for {
|
|
|
|
filePath := path.Join(basePath, strconv.Itoa(i)+".od2")
|
|
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
|
|
return filePath
|
|
|
|
}
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v *GameState) Save() {
|
|
|
|
if v.FilePath == "" {
|
2019-11-14 22:20:01 -05:00
|
|
|
v.FilePath = getFirstFreeFileName()
|
2019-11-11 23:48:55 -05:00
|
|
|
}
|
2019-12-17 23:20:06 -05:00
|
|
|
if err := os.MkdirAll(path.Dir(v.FilePath), 0755); err != nil {
|
|
|
|
log.Panic(err.Error())
|
|
|
|
}
|
2019-11-11 23:48:55 -05:00
|
|
|
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))
|
2019-11-14 22:20:01 -05:00
|
|
|
sr.PushByte(byte(v.HeroLevel))
|
2019-11-11 23:48:55 -05:00
|
|
|
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())
|
|
|
|
}
|
2019-10-31 21:17:13 -04:00
|
|
|
}
|