1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-09-06 19:44:15 -04:00
OpenDiablo2/d2core/d2hero/hero_state_factory.go
gravestench fc87b2be7a
Removing d2datadict singletons (#738)
* Remove weapons, armor, misc, itemCommon, itemTyps datadict singletons

- removed loader calls from d2app
- removed the HeroObjects singleton from `d2core/d2inventory`
- added an InventoryItemFactory in d2inventory
- package-level functions that use data records are now methods of the InventoryItemFactory
- renamed ItemGenerator in d2item to ItemFactory
- package-level functions that use records are now methods of ItemFactory
- d2map.MapEntityFactory now has an item factory instance for creating items
- fixed a bug in unique item record loader where it loaded an empty record
- added a PlayerStateFactory for creating a player state (uses the asset manager)
- updated the test inventory/equipment code in d2player to handle errors from the ItemFactory
- character select and character creation screens have a player state and inventory item factory
- updated item tests to use the item factory

* minor edit

* Removed d2datadict.Experience singleton

added a HeroStatsFactory, much like the other factories. The factory  gets an
asset manager reference in order to use data records.

* removed d2datadict.AutoMagic singleton

* removed d2datadict.AutoMap singleton

* removed d2datadict.BodyLocations singleton

* removed d2datadict.Books singleton

* Removed singletons for level records

- removed loader calls in d2app
- changed type references from d2datadict to d2records
- added a `MapGenerator` in d2mapgen which uses thew asset manager and map engine
- package-level map generation functions are now MapGenerator methods
- `d2datadict.GetLevelDetails(id int)` is now a method of the RecordManager

* remove SkillCalc and MissileCalc singletons

* Removed CharStats and ItemStatCost singletons

- added an ItemStatFactory which uses the asset manager to create stats
- package-level functions for stats in d2item are now StatFactory methods
- changed type references from d2datadict to d2records
- `d2player.GetAllPlayerStates` is now a method of the `PlayerStateFactory`

* Removed DkillDesc and Skills singletons from d2datadict

- removed loader calls from d2app
- diablo2stats.Stat instances are given a reference to the factory for doing record lookups

* update the stats test to use mock a asset manager and stat factory

* fixed diablo2stats tests and diablo2item tests

* removed CompCodes singleton from d2datadict

* remove cubemain singleton from d2datadict

* removed DifficultyLevels singleton from d2datadict

* removed ElemTypes singleton from d2datadict

* removed events.go loader from d2datadict (was unused)

* removed Gems singleton from d2datadict

* removed Hireling and Inventory singletons from d2datadict

* removed MagicPrefix and MagicSuffix singletons from d2datadict

* removed ItemRatios singleton from d2datadict

* removed Missiles singleton from d2datadict

* removed MonModes singleton

* Removed all monster and npc singletons from d2datadict

- MapStamp instances now get a reference to their factory for doing record lookups

* removed SoundEntry and SoundEnviron singletons from d2datadict
2020-09-20 17:52:01 -04:00

236 lines
5.9 KiB
Go

package d2hero
import (
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2records"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
)
// NewHeroStateFactory creates a new HeroStateFactory and initializes it.
func NewHeroStateFactory(asset *d2asset.AssetManager) (*HeroStateFactory, error) {
inventoryItemFactory, err := d2inventory.NewInventoryItemFactory(asset)
if err != nil {
return nil, err
}
factory := &HeroStateFactory{
asset: asset,
InventoryItemFactory: inventoryItemFactory,
}
return factory, nil
}
// HeroStateFactory is responsible for creating player state objects
type HeroStateFactory struct {
asset *d2asset.AssetManager
*d2inventory.InventoryItemFactory
}
// CreateHeroState creates a HeroState instance and returns a pointer to it
func (f *HeroStateFactory) CreateHeroState(
heroName string,
hero d2enum.Hero,
statsState *HeroStatsState,
) (*HeroState, error) {
result := &HeroState{
HeroName: heroName,
HeroType: hero,
Act: 1,
Stats: statsState,
Equipment: f.DefaultHeroItems[hero],
FilePath: "",
}
defaultStats := f.asset.Records.Character.Stats[hero]
skillState, err := f.CreateHeroSkillsState(defaultStats)
if err != nil {
return nil, err
}
result.Skills = skillState
return result, nil
}
// GetAllHeroStates returns all player saves
func (f *HeroStateFactory) GetAllHeroStates() ([]*HeroState, error) {
basePath, _ := f.getGameBaseSavePath()
files, _ := ioutil.ReadDir(basePath)
result := make([]*HeroState, 0)
for _, file := range files {
fileName := file.Name()
if file.IsDir() || len(fileName) < 5 || !strings.EqualFold(fileName[len(fileName)-4:], ".od2") {
continue
}
gameState := f.LoadHeroState(path.Join(basePath, file.Name()))
if gameState == nil || gameState.HeroType == d2enum.HeroNone {
} else if gameState.Stats == nil || gameState.Skills == nil {
// temporarily loading default class stats if the character was created before saving stats/skills was introduced
// to be removed in the future
classStats := f.asset.Records.Character.Stats[gameState.HeroType]
gameState.Stats = f.CreateHeroStatsState(gameState.HeroType, classStats)
skillState, err := f.CreateHeroSkillsState(classStats)
if err != nil {
return nil, err
}
gameState.Skills = skillState
if err := f.Save(gameState); err != nil {
fmt.Printf("failed to save game state!, err: %v\n", err)
}
}
result = append(result, gameState)
}
return result, nil
}
// CreateHeroSkillsState will assemble the hero skills from the class stats record.
func (f *HeroStateFactory) CreateHeroSkillsState(classStats *d2records.CharStatsRecord) (map[int]*HeroSkill, error) {
baseSkills := map[int]*HeroSkill{}
for idx := range classStats.BaseSkill {
skillName := &classStats.BaseSkill[idx]
if len(*skillName) == 0 {
continue
}
skill, err := f.CreateHeroSkill(1, *skillName)
if err != nil {
continue
}
baseSkills[skill.ID] = skill
}
skillRecord, err := f.CreateHeroSkill(1, "Attack")
if err != nil {
return nil, err
}
baseSkills[skillRecord.ID] = skillRecord
return baseSkills, nil
}
// CreateHeroSkill creates an instance of a skill
func (f *HeroStateFactory) CreateHeroSkill(points int, name string) (*HeroSkill, error) {
skillRecord := f.asset.Records.GetSkillByName(name)
if skillRecord == nil {
return nil, fmt.Errorf("Skill not found: %s", name)
}
skillDescRecord, found := f.asset.Records.Skill.Descriptions[skillRecord.Skilldesc]
if !found {
return nil, fmt.Errorf("Skill Description not found: %s", name)
}
result := &HeroSkill{
SkillPoints: points,
SkillRecord: skillRecord,
SkillDescriptionRecord: skillDescRecord,
}
return result, nil
}
// HasGameStates returns true if the player has any previously saved game
func (f *HeroStateFactory) HasGameStates() bool {
basePath, _ := f.getGameBaseSavePath()
files, _ := ioutil.ReadDir(basePath)
return len(files) > 0
}
// CreateTestGameState is used for the map engine previewer
func (f *HeroStateFactory) CreateTestGameState() *HeroState {
result := &HeroState{}
return result
}
// LoadHeroState loads the player state from the file
func (f *HeroStateFactory) LoadHeroState(filePath string) *HeroState {
strData, err := ioutil.ReadFile(filePath)
if err != nil {
return nil
}
result := &HeroState{
FilePath: filePath,
}
err = json.Unmarshal(strData, result)
if err != nil {
return nil
}
// Here, we turn the shallow skill data back into records from the asset manager.
// This is because this factory has a reference to the asset manager with loaded records.
// We cant do this while unmarshalling because there is no reference to the asset manager.
for idx := range result.Skills {
hs := result.Skills[idx]
hs.SkillRecord = f.asset.Records.Skill.Details[hs.shallow.SkillID]
hs.SkillDescriptionRecord = f.asset.Records.Skill.Descriptions[hs.SkillRecord.Skilldesc]
hs.SkillPoints = hs.shallow.SkillPoints
}
return result
}
func (f *HeroStateFactory) getGameBaseSavePath() (string, error) {
configDir, err := os.UserConfigDir()
if err != nil {
return "", err
}
return path.Join(configDir, "OpenDiablo2/Saves"), nil
}
func (f *HeroStateFactory) getFirstFreeFileName() string {
i := 0
basePath, _ := f.getGameBaseSavePath()
for {
filePath := path.Join(basePath, strconv.Itoa(i)+".od2")
if _, err := os.Stat(filePath); os.IsNotExist(err) {
return filePath
}
i++
}
}
// Save saves the player state to a file
func (f *HeroStateFactory) Save(state *HeroState) error {
if state.FilePath == "" {
state.FilePath = f.getFirstFreeFileName()
}
if err := os.MkdirAll(path.Dir(state.FilePath), 0755); err != nil {
return err
}
fileJSON, _ := json.MarshalIndent(state, "", " ")
if err := ioutil.WriteFile(state.FilePath, fileJSON, 0644); err != nil {
return err
}
return nil
}