diff --git a/d2asset/animation_manager.go b/d2asset/animation_manager.go deleted file mode 100644 index 5f2e2511..00000000 --- a/d2asset/animation_manager.go +++ /dev/null @@ -1,61 +0,0 @@ -package d2asset - -import ( - "errors" - "fmt" - "path/filepath" - "strings" -) - -type animationManager struct { - cache *cache -} - -func createAnimationManager() *animationManager { - return &animationManager{cache: createCache(AnimationBudget)} -} - -func (sm *animationManager) loadAnimation(animationPath, palettePath string, transparency int) (*Animation, error) { - cachePath := fmt.Sprintf("%s;%s;%d", animationPath, palettePath, transparency) - if animation, found := sm.cache.retrieve(cachePath); found { - return animation.(*Animation).clone(), nil - } - - var animation *Animation - switch strings.ToLower(filepath.Ext(animationPath)) { - case ".dc6": - dc6, err := loadDC6(animationPath, palettePath) - if err != nil { - return nil, err - } - - animation, err = createAnimationFromDC6(dc6) - if err != nil { - return nil, err - } - case ".dcc": - dcc, err := loadDCC(animationPath) - if err != nil { - return nil, err - } - - palette, err := loadPalette(palettePath) - if err != nil { - return nil, err - } - - animation, err = createAnimationFromDCC(dcc, palette, transparency) - if err != nil { - return nil, err - } - - default: - return nil, errors.New("unknown animation format") - } - - if err := sm.cache.insert(cachePath, animation.clone(), 1); err != nil { - return nil, err - } - - return animation, nil -} diff --git a/d2asset/archive_manager.go b/d2asset/archive_manager.go deleted file mode 100644 index 42646a15..00000000 --- a/d2asset/archive_manager.go +++ /dev/null @@ -1,100 +0,0 @@ -package d2asset - -import ( - "errors" - "path" - "sync" - - "github.com/OpenDiablo2/OpenDiablo2/d2corecommon" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2mpq" -) - -type archiveEntry struct { - archivePath string - hashEntryMap d2mpq.HashEntryMap -} - -type archiveManager struct { - cache *cache - config *d2corecommon.Configuration - entries []archiveEntry - mutex sync.Mutex -} - -func createArchiveManager(config *d2corecommon.Configuration) *archiveManager { - return &archiveManager{cache: createCache(ArchiveBudget), config: config} -} - -func (am *archiveManager) loadArchiveForFile(filePath string) (*d2mpq.MPQ, error) { - am.mutex.Lock() - defer am.mutex.Unlock() - - if err := am.cacheArchiveEntries(); err != nil { - return nil, err - } - - for _, archiveEntry := range am.entries { - if archiveEntry.hashEntryMap.Contains(filePath) { - return am.loadArchive(archiveEntry.archivePath) - } - } - - return nil, errors.New("file not found") -} - -func (am *archiveManager) fileExistsInArchive(filePath string) (bool, error) { - am.mutex.Lock() - defer am.mutex.Unlock() - - if err := am.cacheArchiveEntries(); err != nil { - return false, err - } - - for _, archiveEntry := range am.entries { - if archiveEntry.hashEntryMap.Contains(filePath) { - return true, nil - } - } - - return false, nil -} - -func (am *archiveManager) loadArchive(archivePath string) (*d2mpq.MPQ, error) { - if archive, found := am.cache.retrieve(archivePath); found { - return archive.(*d2mpq.MPQ), nil - } - - archive, err := d2mpq.Load(archivePath) - if err != nil { - return nil, err - } - - if err := am.cache.insert(archivePath, archive, int(archive.Data.ArchiveSize)); err != nil { - return nil, err - } - - return archive, nil -} - -func (am *archiveManager) cacheArchiveEntries() error { - if len(am.entries) == len(am.config.MpqLoadOrder) { - return nil - } - - am.entries = nil - - for _, archiveName := range am.config.MpqLoadOrder { - archivePath := path.Join(am.config.MpqPath, archiveName) - archive, err := am.loadArchive(archivePath) - if err != nil { - return err - } - - am.entries = append( - am.entries, - archiveEntry{archivePath, archive.HashEntryMap}, - ) - } - - return nil -} diff --git a/d2asset/asset_manager.go b/d2asset/asset_manager.go deleted file mode 100644 index 218a9a69..00000000 --- a/d2asset/asset_manager.go +++ /dev/null @@ -1,181 +0,0 @@ -package d2asset - -import ( - "errors" - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2corecommon" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2cof" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2dc6" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2dcc" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2mpq" - "github.com/OpenDiablo2/OpenDiablo2/d2term" -) - -const ( - // In megabytes - ArchiveBudget = 1024 * 1024 * 512 - FileBudget = 1024 * 1024 * 32 - - // In counts - PaletteBudget = 64 - AnimationBudget = 64 -) - -var ( - ErrHasInit error = errors.New("asset system is already initialized") - ErrNoInit error = errors.New("asset system is not initialized") -) - -type assetManager struct { - archiveManager *archiveManager - fileManager *fileManager - paletteManager *paletteManager - animationManager *animationManager -} - -var singleton *assetManager - -func Initialize(config *d2corecommon.Configuration) error { - if singleton != nil { - return ErrHasInit - } - - var ( - archiveManager = createArchiveManager(config) - fileManager = createFileManager(config, archiveManager) - paletteManager = createPaletteManager() - animationManager = createAnimationManager() - ) - - singleton = &assetManager{ - archiveManager, - fileManager, - paletteManager, - animationManager, - } - - d2term.BindAction("assetspam", "display verbose asset manager logs", func(verbose bool) { - if verbose { - d2term.OutputInfo("asset manager verbose logging enabled") - } else { - d2term.OutputInfo("asset manager verbose logging disabled") - } - - archiveManager.cache.verbose = verbose - fileManager.cache.verbose = verbose - paletteManager.cache.verbose = verbose - animationManager.cache.verbose = verbose - }) - - d2term.BindAction("assetstat", "display asset manager cache statistics", func() { - d2term.OutputInfo("archive cache: %f%%", float64(archiveManager.cache.weight)/float64(archiveManager.cache.budget)*100.0) - d2term.OutputInfo("file cache: %f%%", float64(fileManager.cache.weight)/float64(fileManager.cache.budget)*100.0) - d2term.OutputInfo("palette cache: %f%%", float64(paletteManager.cache.weight)/float64(paletteManager.cache.budget)*100.0) - d2term.OutputInfo("animation cache: %f%%", float64(animationManager.cache.weight)/float64(animationManager.cache.budget)*100.0) - }) - - d2term.BindAction("assetclear", "clear asset manager cache", func() { - archiveManager.cache.clear() - fileManager.cache.clear() - paletteManager.cache.clear() - animationManager.cache.clear() - }) - - return nil -} - -func Shutdown() { - singleton = nil -} - -func LoadArchive(archivePath string) (*d2mpq.MPQ, error) { - if singleton == nil { - return nil, ErrNoInit - } - - return singleton.archiveManager.loadArchive(archivePath) -} - -func LoadFile(filePath string) ([]byte, error) { - if singleton == nil { - return nil, ErrNoInit - } - - data, err := singleton.fileManager.loadFile(filePath) - if err != nil { - log.Printf("error loading file %s (%v)", filePath, err.Error()) - } - - return data, err -} - -func FileExists(filePath string) (bool, error) { - if singleton == nil { - return false, ErrNoInit - } - - return singleton.fileManager.fileExists(filePath) -} - -func LoadAnimation(animationPath, palettePath string) (*Animation, error) { - return LoadAnimationWithTransparency(animationPath, palettePath, 255) -} - -func LoadAnimationWithTransparency(animationPath, palettePath string, transparency int) (*Animation, error) { - if singleton == nil { - return nil, ErrNoInit - } - - return singleton.animationManager.loadAnimation(animationPath, palettePath, transparency) -} - -func LoadComposite(object *d2datadict.ObjectLookupRecord, palettePath string) (*Composite, error) { - return createComposite(object, palettePath), nil -} - -func loadPalette(palettePath string) (*d2datadict.PaletteRec, error) { - if singleton == nil { - return nil, ErrNoInit - } - - return singleton.paletteManager.loadPalette(palettePath) -} - -func loadDC6(dc6Path, palettePath string) (*d2dc6.DC6File, error) { - dc6Data, err := LoadFile(dc6Path) - if err != nil { - return nil, err - } - - paletteData, err := loadPalette(palettePath) - if err != nil { - return nil, err - } - - dc6, err := d2dc6.LoadDC6(dc6Data, *paletteData) - if err != nil { - return nil, err - } - - return &dc6, nil -} - -func loadDCC(dccPath string) (*d2dcc.DCC, error) { - dccData, err := LoadFile(dccPath) - if err != nil { - return nil, err - } - - return d2dcc.LoadDCC(dccData) -} - -func loadCOF(cofPath string) (*d2cof.COF, error) { - cofData, err := LoadFile(cofPath) - if err != nil { - return nil, err - } - - return d2cof.LoadCOF(cofData) -} diff --git a/d2asset/file_manager.go b/d2asset/file_manager.go deleted file mode 100644 index 3caba598..00000000 --- a/d2asset/file_manager.go +++ /dev/null @@ -1,61 +0,0 @@ -package d2asset - -import ( - "strings" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2corecommon" -) - -type fileManager struct { - cache *cache - archiveManager *archiveManager - config *d2corecommon.Configuration -} - -func createFileManager(config *d2corecommon.Configuration, archiveManager *archiveManager) *fileManager { - return &fileManager{createCache(FileBudget), archiveManager, config} -} - -func (fm *fileManager) loadFile(filePath string) ([]byte, error) { - filePath = fm.fixupFilePath(filePath) - if value, found := fm.cache.retrieve(filePath); found { - return value.([]byte), nil - } - - archive, err := fm.archiveManager.loadArchiveForFile(filePath) - if err != nil { - return nil, err - } - - data, err := archive.ReadFile(filePath) - if err != nil { - return nil, err - } - - if err := fm.cache.insert(filePath, data, len(data)); err != nil { - return nil, err - } - - return data, nil -} - -func (fm *fileManager) fileExists(filePath string) (bool, error) { - filePath = fm.fixupFilePath(filePath) - return fm.archiveManager.fileExistsInArchive(filePath) -} - -func (fm *fileManager) fixupFilePath(filePath string) string { - filePath = strings.ReplaceAll(filePath, "{LANG}", fm.config.Language) - if strings.ToUpper(d2resource.LanguageCode) == "CHI" { - filePath = strings.ReplaceAll(filePath, "{LANG_FONT}", fm.config.Language) - } else { - filePath = strings.ReplaceAll(filePath, "{LANG_FONT}", "latin") - } - - filePath = strings.ToLower(filePath) - filePath = strings.ReplaceAll(filePath, `/`, "\\") - filePath = strings.TrimPrefix(filePath, "\\") - - return filePath -} diff --git a/d2asset/palette_manager.go b/d2asset/palette_manager.go deleted file mode 100644 index 8e76e6d0..00000000 --- a/d2asset/palette_manager.go +++ /dev/null @@ -1,28 +0,0 @@ -package d2asset - -import ( - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict" -) - -type paletteManager struct { - cache *cache -} - -func createPaletteManager() *paletteManager { - return &paletteManager{createCache(PaletteBudget)} -} - -func (pm *paletteManager) loadPalette(palettePath string) (*d2datadict.PaletteRec, error) { - if palette, found := pm.cache.retrieve(palettePath); found { - return palette.(*d2datadict.PaletteRec), nil - } - - paletteData, err := LoadFile(palettePath) - if err != nil { - return nil, err - } - - palette := d2datadict.CreatePalette("", paletteData) - pm.cache.insert(palettePath, &palette, 1) - return &palette, nil -} diff --git a/d2audio/audio_provider.go b/d2audio/audio_provider.go deleted file mode 100644 index 0a077b39..00000000 --- a/d2audio/audio_provider.go +++ /dev/null @@ -1,82 +0,0 @@ -package d2audio - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2asset" - "github.com/hajimehoshi/ebiten/audio" - "github.com/hajimehoshi/ebiten/audio/wav" -) - -// Manager provides sound -type Manager struct { - audioContext *audio.Context // The Audio context - bgmAudio *audio.Player // The audio player - lastBgm string - sfxVolume float64 - bgmVolume float64 -} - -// CreateManager creates a sound provider -func CreateManager() *Manager { - result := &Manager{} - audioContext, err := audio.NewContext(44100) - if err != nil { - log.Fatal(err) - } - result.audioContext = audioContext - return result -} - -// PlayBGM plays an infinitely looping background track -func (v *Manager) PlayBGM(song string) { - if v.lastBgm == song { - return - } - v.lastBgm = song - if song == "" && v.bgmAudio != nil && v.bgmAudio.IsPlaying() { - _ = v.bgmAudio.Pause() - return - } - go func() { - if v.bgmAudio != nil { - err := v.bgmAudio.Close() - if err != nil { - log.Panic(err) - } - } - audioData, err := d2asset.LoadFile(song) - if err != nil { - panic(err) - } - d, err := wav.Decode(v.audioContext, audio.BytesReadSeekCloser(audioData)) - if err != nil { - log.Fatal(err) - } - s := audio.NewInfiniteLoop(d, d.Length()) - v.bgmAudio, err = audio.NewPlayer(v.audioContext, s) - if err != nil { - log.Fatal(err) - } - v.bgmAudio.SetVolume(v.bgmVolume) - // Play the infinite-length stream. This never ends. - err = v.bgmAudio.Rewind() - if err != nil { - panic(err) - } - err = v.bgmAudio.Play() - if err != nil { - panic(err) - } - }() -} - -func (v *Manager) LoadSoundEffect(sfx string) *SoundEffect { - result := CreateSoundEffect(sfx, v.audioContext, v.sfxVolume) - return result -} - -func (v *Manager) SetVolumes(bgmVolume, sfxVolume float64) { - v.sfxVolume = sfxVolume - v.bgmVolume = bgmVolume -} diff --git a/d2asset/cache.go b/d2common/cache.go similarity index 72% rename from d2asset/cache.go rename to d2common/cache.go index 8efd619a..3699f3da 100644 --- a/d2asset/cache.go +++ b/d2common/cache.go @@ -1,4 +1,4 @@ -package d2asset +package d2common import ( "errors" @@ -14,7 +14,7 @@ type cacheNode struct { weight int } -type cache struct { +type Cache struct { head *cacheNode tail *cacheNode lookup map[string]*cacheNode @@ -24,16 +24,27 @@ type cache struct { mutex sync.Mutex } -func createCache(budget int) *cache { - return &cache{lookup: make(map[string]*cacheNode), budget: budget} +func CreateCache(budget int) *Cache { + return &Cache{lookup: make(map[string]*cacheNode), budget: budget} +} +func (c *Cache) SetCacheVerbose(verbose bool) { + c.verbose = verbose } -func (c *cache) insert(key string, value interface{}, weight int) error { +func (c *Cache) GetWeight() int { + return c.weight +} + +func (c *Cache) GetBudget() int { + return c.budget +} + +func (c *Cache) Insert(key string, value interface{}, weight int) error { c.mutex.Lock() defer c.mutex.Unlock() if _, found := c.lookup[key]; found { - return errors.New("key already exists in cache") + return errors.New("key already exists in Cache") } node := &cacheNode{ @@ -61,7 +72,7 @@ func (c *cache) insert(key string, value interface{}, weight int) error { if c.verbose { log.Printf( - "warning -- cache is evicting %s (%d) for %s (%d); spare weight is now %d", + "warning -- Cache is evicting %s (%d) for %s (%d); spare weight is now %d", c.tail.key, c.tail.weight, key, @@ -76,7 +87,7 @@ func (c *cache) insert(key string, value interface{}, weight int) error { return nil } -func (c *cache) retrieve(key string) (interface{}, bool) { +func (c *Cache) Retrieve(key string) (interface{}, bool) { c.mutex.Lock() defer c.mutex.Unlock() @@ -110,7 +121,7 @@ func (c *cache) retrieve(key string) (interface{}, bool) { return node.value, true } -func (c *cache) clear() { +func (c *Cache) Clear() { c.mutex.Lock() defer c.mutex.Unlock() diff --git a/d2common/composite_mode.go b/d2common/composite_mode.go new file mode 100644 index 00000000..7f481575 --- /dev/null +++ b/d2common/composite_mode.go @@ -0,0 +1,46 @@ +package d2common + +type CompositeMode int + +const ( + // Regular alpha blending + // c_out = c_src + c_dst × (1 - α_src) + CompositeModeSourceOver CompositeMode = CompositeMode(1) + + // c_out = 0 + CompositeModeClear CompositeMode = CompositeMode(2) + + // c_out = c_src + CompositeModeCopy CompositeMode = CompositeMode(3) + + // c_out = c_dst + CompositeModeDestination CompositeMode = CompositeMode(4) + + // c_out = c_src × (1 - α_dst) + c_dst + CompositeModeDestinationOver CompositeMode = CompositeMode(5) + + // c_out = c_src × α_dst + CompositeModeSourceIn CompositeMode = CompositeMode(6) + + // c_out = c_dst × α_src + CompositeModeDestinationIn CompositeMode = CompositeMode(7) + + // c_out = c_src × (1 - α_dst) + CompositeModeSourceOut CompositeMode = CompositeMode(8) + + // c_out = c_dst × (1 - α_src) + CompositeModeDestinationOut CompositeMode = CompositeMode(9) + + // c_out = c_src × α_dst + c_dst × (1 - α_src) + CompositeModeSourceAtop CompositeMode = CompositeMode(10) + + // c_out = c_src × (1 - α_dst) + c_dst × α_src + CompositeModeDestinationAtop CompositeMode = CompositeMode(11) + + // c_out = c_src × (1 - α_dst) + c_dst × (1 - α_src) + CompositeModeXor CompositeMode = CompositeMode(12) + + // Sum of source and destination (a.k.a. 'plus' or 'additive') + // c_out = c_src + c_dst + CompositeModeLighter CompositeMode = CompositeMode(13) +) diff --git a/d2corecommon/configuration.go b/d2common/d2config/configuration.go similarity index 58% rename from d2corecommon/configuration.go rename to d2common/d2config/configuration.go index 27438e0a..198a860a 100644 --- a/d2corecommon/configuration.go +++ b/d2common/d2config/configuration.go @@ -1,9 +1,6 @@ -package d2corecommon +package d2config import ( - "encoding/json" - "log" - "os" "os/user" "path" "runtime" @@ -24,56 +21,7 @@ type Configuration struct { BgmVolume float64 } -func LoadConfiguration() *Configuration { - configDir, err := os.UserConfigDir() - if err != nil { - return getDefaultConfiguration() - } - - configDir = path.Join(configDir, "OpenDiablo2") - configPath := path.Join(configDir, "config.json") - log.Printf("loading configuration file from %s...", configPath) - configFile, err := os.Open(configPath) - defer configFile.Close() - - if err == nil { - var config Configuration - decoder := json.NewDecoder(configFile) - if err := decoder.Decode(&config); err == nil { - return &config - } - } - - return getDefaultConfiguration() -} - -func (c *Configuration) Save() error { - configDir, err := os.UserConfigDir() - if err != nil { - return err - } - - configDir = path.Join(configDir, "OpenDiablo2") - if err := os.MkdirAll(configDir, 0755); err != nil { - return err - } - - configPath := path.Join(configDir, "config.json") - log.Printf("saving configuration file to %s...", configPath) - configFile, err := os.Create(configPath) - if err != nil { - return err - } - - encoder := json.NewEncoder(configFile) - encoder.SetIndent("", " ") - if err := encoder.Encode(c); err != nil { - return err - } - - return nil -} - +/* func getConfigurationPath() string { configDir, err := os.UserConfigDir() if err != nil { @@ -82,11 +30,11 @@ func getConfigurationPath() string { return path.Join(configDir, "OpenDiablo2/config.json") } - +*/ func getDefaultConfiguration() *Configuration { config := &Configuration{ Language: "ENG", - FullScreen: true, + FullScreen: false, Scale: 1, TicksPerSecond: -1, RunInBackground: true, @@ -100,7 +48,7 @@ func getDefaultConfiguration() *Configuration { "d2xmusic.mpq", "d2xtalk.mpq", "d2xvideo.mpq", - "github.com/OpenDiablo2/OpenDiablo2/d2data.mpq", + "d2datadict.mpq", "d2char.mpq", "d2music.mpq", "d2sfx.mpq", diff --git a/d2common/d2config/d2config.go b/d2common/d2config/d2config.go new file mode 100644 index 00000000..f2102ecc --- /dev/null +++ b/d2common/d2config/d2config.go @@ -0,0 +1,82 @@ +package d2config + +import ( + "encoding/json" + "errors" + "log" + "os" + "path" +) + +var ( + ErrNotInit = errors.New("configuration is not initialized") + ErrHasInit = errors.New("configuration has already been initialized") +) + +var singleton *Configuration + +func Initialize() error { + if singleton != nil { + return ErrHasInit + } + configDir, err := os.UserConfigDir() + if err != nil { + singleton = getDefaultConfiguration() + return nil + } + + configDir = path.Join(configDir, "OpenDiablo2") + configPath := path.Join(configDir, "config.json") + log.Printf("loading configuration file from %s...", configPath) + configFile, err := os.Open(configPath) + + if err == nil { + var config Configuration + decoder := json.NewDecoder(configFile) + defer configFile.Close() + if err := decoder.Decode(&config); err == nil { + singleton = &config + return nil + } + } + + singleton = getDefaultConfiguration() + return nil +} + +func Save() error { + if singleton == nil { + return ErrNotInit + } + configDir, err := os.UserConfigDir() + if err != nil { + return err + } + + configDir = path.Join(configDir, "OpenDiablo2") + if err := os.MkdirAll(configDir, 0755); err != nil { + return err + } + + configPath := path.Join(configDir, "config.json") + log.Printf("saving configuration file to %s...", configPath) + configFile, err := os.Create(configPath) + if err != nil { + return err + } + + encoder := json.NewEncoder(configFile) + encoder.SetIndent("", " ") + if err := encoder.Encode(singleton); err != nil { + return err + } + + return nil +} + +func Get() (*Configuration, error) { + if singleton == nil { + return nil, ErrNotInit + } + return singleton, nil +} diff --git a/d2data/animation_data.go b/d2common/d2data/animation_data.go similarity index 87% rename from d2data/animation_data.go rename to d2common/d2data/animation_data.go index c1342c45..d6387b53 100644 --- a/d2data/animation_data.go +++ b/d2common/d2data/animation_data.go @@ -4,10 +4,6 @@ import ( "log" "strings" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - "github.com/OpenDiablo2/OpenDiablo2/d2common" ) @@ -27,9 +23,8 @@ type AnimationDataRecord struct { var AnimationData map[string][]*AnimationDataRecord // LoadAnimationData loads the animation data table into the global AnimationData dictionary -func LoadAnimationData(fileProvider d2interface.FileProvider) { +func LoadAnimationData(rawData []byte) { AnimationData = make(map[string][]*AnimationDataRecord) - rawData := fileProvider.LoadFile(d2resource.AnimationData) streamReader := d2common.CreateStreamReader(rawData) for !streamReader.Eof() { dataCount := int(streamReader.GetInt32()) diff --git a/d2data/d2compression/huffman.go b/d2common/d2data/d2compression/huffman.go similarity index 100% rename from d2data/d2compression/huffman.go rename to d2common/d2data/d2compression/huffman.go diff --git a/d2data/d2compression/wav.go b/d2common/d2data/d2compression/wav.go similarity index 100% rename from d2data/d2compression/wav.go rename to d2common/d2data/d2compression/wav.go diff --git a/d2common/d2data/d2datadict/armor.go b/d2common/d2data/d2datadict/armor.go new file mode 100644 index 00000000..81790fbd --- /dev/null +++ b/d2common/d2data/d2datadict/armor.go @@ -0,0 +1,14 @@ +package d2datadict + +import ( + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" +) + +var Armors map[string]*ItemCommonRecord + +func LoadArmors(file []byte) { + Armors = *LoadCommonItems(file, d2enum.InventoryItemTypeArmor) + log.Printf("Loaded %d armors", len(Armors)) +} diff --git a/d2data/d2datadict/item_common.go b/d2common/d2data/d2datadict/item_common.go similarity index 88% rename from d2data/d2datadict/item_common.go rename to d2common/d2data/d2datadict/item_common.go index 712ebfe3..1ee88199 100644 --- a/d2data/d2datadict/item_common.go +++ b/d2common/d2data/d2datadict/item_common.go @@ -7,8 +7,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" ) type ItemCommonRecord struct { @@ -119,7 +117,6 @@ type ItemCommonRecord struct { Nameable bool // if true, item can be personalized - // weapon params BarbOneOrTwoHanded bool // if true, barb can wield this in one or two hands UsesTwoHands bool // if true, it's a 2handed weapon @@ -136,7 +133,7 @@ type ItemCommonRecord struct { WeaponClass string // what kind of attack does this weapon have (i.e. determines attack animations) WeaponClass2Hand string // what kind of attack when wielded with two hands HitClass string // determines sounds/graphic effects when attacking - SpawnStack int // unknown, something to do with stack size when spawned (sold maybe?) + SpawnStack int // unknown, something to do with stack size when spawned (sold maybe?) SpecialFeature string // Just a comment @@ -147,24 +144,24 @@ type ItemCommonRecord struct { // misc params FlavorText string // unknown, probably just for reference - Transmogrify bool // if true, can be turned into another item via right click + Transmogrify bool // if true, can be turned into another item via right click TransmogCode string // the 3 char code representing the item this becomes via transmog - TransmogMin int // min amount of the transmog item to create - TransmogMax int // max '' + TransmogMin int // min amount of the transmog item to create + TransmogMax int // max '' AutoBelt bool // if true, item is put into your belt when picked up - SpellIcon int // which icon to display when used? Is this always -1? - SpellType int // determines what kind of function is used when you use this item - OverlayState string // name of the overlay state to be applied upon use of this item - CureOverlayStates [2]string // name of the overlay states that are removed upon use of this item - EffectLength int // timer for timed usage effects + SpellIcon int // which icon to display when used? Is this always -1? + SpellType int // determines what kind of function is used when you use this item + OverlayState string // name of the overlay state to be applied upon use of this item + CureOverlayStates [2]string // name of the overlay states that are removed upon use of this item + EffectLength int // timer for timed usage effects UsageStats [3]ItemUsageStat // stat boosts applied upon usage - + SpellDescriptionType int // specifies how to format the usage description // 0 = none, 1 = use desc string, 2 = use desc string + calc value SpellDescriptionString string // points to a string containing the description - SpellDescriptionCalc d2common.CalcString // a calc string what value to display + SpellDescriptionCalc d2common.CalcString // a calc string what value to display BetterGem string // 3 char code pointing to the gem this upgrades to (non if not applicable) @@ -184,17 +181,15 @@ type ItemVendorParams struct { MagicLevel uint8 } - - // Loading Functions var CommonItems map[string]*ItemCommonRecord -func LoadCommonItems(fileProvider d2interface.FileProvider, filepath string, source d2enum.InventoryItemType) *map[string]*ItemCommonRecord { +func LoadCommonItems(file []byte, source d2enum.InventoryItemType) *map[string]*ItemCommonRecord { if CommonItems == nil { CommonItems = make(map[string]*ItemCommonRecord) } items := make(map[string]*ItemCommonRecord) - data := strings.Split(string(fileProvider.LoadFile(filepath)), "\r\n") + data := strings.Split(string(file), "\r\n") mapping := MapHeaders(data[0]) for lineno, line := range data { if lineno == 0 { @@ -324,14 +319,14 @@ func createCommonItemRecord(line string, mapping *map[string]int, source d2enum. MaxMissileDamage: MapLoadInt(&r, mapping, "maxmisdam"), MissileSpeed: MapLoadInt(&r, mapping, "misspeed"), ExtraRange: MapLoadInt(&r, mapping, "rangeadder"), - + RequiredDexterity: MapLoadInt(&r, mapping, "reqdex"), - + WeaponClass: MapLoadString(&r, mapping, "wclass"), WeaponClass2Hand: MapLoadString(&r, mapping, "2handedwclass"), - - HitClass: MapLoadString(&r, mapping, "hit class"), - SpawnStack: MapLoadInt(&r, mapping, "spawnstack"), + + HitClass: MapLoadString(&r, mapping, "hit class"), + SpawnStack: MapLoadInt(&r, mapping, "spawnstack"), SpecialFeature: MapLoadString(&r, mapping, "special"), @@ -346,30 +341,30 @@ func createCommonItemRecord(line string, mapping *map[string]int, source d2enum. TransmogCode: MapLoadString(&r, mapping, "TMogType"), TransmogMin: MapLoadInt(&r, mapping, "TMogMin"), TransmogMax: MapLoadInt(&r, mapping, "TMogMax"), - + AutoBelt: MapLoadBool(&r, mapping, "autobelt"), - - SpellIcon: MapLoadInt(&r, mapping, "spellicon"), - SpellType: MapLoadInt(&r, mapping, "pSpell"), - OverlayState: MapLoadString(&r, mapping, "state"), + + SpellIcon: MapLoadInt(&r, mapping, "spellicon"), + SpellType: MapLoadInt(&r, mapping, "pSpell"), + OverlayState: MapLoadString(&r, mapping, "state"), CureOverlayStates: [2]string{ MapLoadString(&r, mapping, "cstate1"), MapLoadString(&r, mapping, "cstate2"), }, - EffectLength: MapLoadInt(&r, mapping, "len"), - UsageStats: createItemUsageStats(&r, mapping), - + EffectLength: MapLoadInt(&r, mapping, "len"), + UsageStats: createItemUsageStats(&r, mapping), + SpellDescriptionType: MapLoadInt(&r, mapping, "spelldesc"), // 0 = none, 1 = use desc string, 2 = use desc string + calc value SpellDescriptionString: MapLoadString(&r, mapping, "spelldescstr"), SpellDescriptionCalc: d2common.CalcString(MapLoadString(&r, mapping, "spelldesccalc")), - + BetterGem: MapLoadString(&r, mapping, "BetterGem"), - - Multibuy: MapLoadBool(&r, mapping, "multibuy"), + + Multibuy: MapLoadBool(&r, mapping, "multibuy"), } return result -} +} func createItemVendorParams(r *[]string, mapping *map[string]int) map[string]*ItemVendorParams { vs := make([]string, 17) @@ -395,11 +390,11 @@ func createItemVendorParams(r *[]string, mapping *map[string]int) map[string]*It for _, name := range vs { wvp := ItemVendorParams{ - Min: MapLoadInt(r, mapping, name + "Min"), - Max: MapLoadInt(r, mapping, name + "Max"), - MagicMin: MapLoadInt(r, mapping, name + "MagicMin"), - MagicMax: MapLoadInt(r, mapping, name + "MagicMax"), - MagicLevel: MapLoadUint8(r, mapping, name + "MagicLvl"), + Min: MapLoadInt(r, mapping, name+"Min"), + Max: MapLoadInt(r, mapping, name+"Max"), + MagicMin: MapLoadInt(r, mapping, name+"MagicMin"), + MagicMax: MapLoadInt(r, mapping, name+"MagicMax"), + MagicLevel: MapLoadUint8(r, mapping, name+"MagicLvl"), } result[name] = &wvp } @@ -409,8 +404,8 @@ func createItemVendorParams(r *[]string, mapping *map[string]int) map[string]*It func createItemUsageStats(r *[]string, mapping *map[string]int) [3]ItemUsageStat { result := [3]ItemUsageStat{} for i := 0; i < 3; i++ { - result[i].Stat = MapLoadString(r, mapping, "stat" + strconv.Itoa(i)) - result[i].Calc = d2common.CalcString(MapLoadString(r, mapping, "calc" + strconv.Itoa(i))) + result[i].Stat = MapLoadString(r, mapping, "stat"+strconv.Itoa(i)) + result[i].Calc = d2common.CalcString(MapLoadString(r, mapping, "calc"+strconv.Itoa(i))) } return result } diff --git a/d2data/d2datadict/level_presets.go b/d2common/d2data/d2datadict/level_presets.go similarity index 87% rename from d2data/d2datadict/level_presets.go rename to d2common/d2data/d2datadict/level_presets.go index 2cbcd59e..d87c780b 100644 --- a/d2data/d2datadict/level_presets.go +++ b/d2common/d2data/d2datadict/level_presets.go @@ -4,11 +4,7 @@ import ( "log" "strings" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - - dh "github.com/OpenDiablo2/OpenDiablo2/d2helper" + dh "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" ) type LevelPresetRecord struct { @@ -75,9 +71,9 @@ func createLevelPresetRecord(props []string) LevelPresetRecord { var LevelPresets map[int]LevelPresetRecord -func LoadLevelPresets(fileProvider d2interface.FileProvider) { +func LoadLevelPresets(file []byte) { LevelPresets = make(map[int]LevelPresetRecord) - data := strings.Split(string(fileProvider.LoadFile(d2resource.LevelPreset)), "\r\n")[1:] + data := strings.Split(string(file), "\r\n")[1:] for _, line := range data { if len(line) == 0 { continue diff --git a/d2data/d2datadict/level_types.go b/d2common/d2data/d2datadict/level_types.go similarity index 75% rename from d2data/d2datadict/level_types.go rename to d2common/d2data/d2datadict/level_types.go index 192d717f..aed8c0d8 100644 --- a/d2data/d2datadict/level_types.go +++ b/d2common/d2data/d2datadict/level_types.go @@ -4,11 +4,7 @@ import ( "log" "strings" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - - dh "github.com/OpenDiablo2/OpenDiablo2/d2helper" + dh "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" ) type LevelTypeRecord struct { @@ -22,8 +18,8 @@ type LevelTypeRecord struct { var LevelTypes []LevelTypeRecord -func LoadLevelTypes(fileProvider d2interface.FileProvider) { - data := strings.Split(string(fileProvider.LoadFile(d2resource.LevelType)), "\r\n")[1:] +func LoadLevelTypes(file []byte) { + data := strings.Split(string(file), "\r\n")[1:] LevelTypes = make([]LevelTypeRecord, len(data)) for i, j := 0, 0; i < len(data); i, j = i+1, j+1 { idx := -1 diff --git a/d2data/d2datadict/level_warp.go b/d2common/d2data/d2datadict/level_warp.go similarity index 84% rename from d2data/d2datadict/level_warp.go rename to d2common/d2data/d2datadict/level_warp.go index 8e221f1c..11f5a208 100644 --- a/d2data/d2datadict/level_warp.go +++ b/d2common/d2data/d2datadict/level_warp.go @@ -3,10 +3,6 @@ package d2datadict import ( "log" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - "github.com/OpenDiablo2/OpenDiablo2/d2common" ) @@ -27,9 +23,8 @@ type LevelWarpRecord struct { var LevelWarps map[int]*LevelWarpRecord -func LoadLevelWarps(fileProvider d2interface.FileProvider) { +func LoadLevelWarps(levelWarpData []byte) { LevelWarps = make(map[int]*LevelWarpRecord) - levelWarpData := fileProvider.LoadFile(d2resource.LevelWarp) streamReader := d2common.CreateStreamReader(levelWarpData) numRecords := int(streamReader.GetInt32()) for i := 0; i < numRecords; i++ { diff --git a/d2data/d2datadict/map_helper.go b/d2common/d2data/d2datadict/map_helper.go similarity index 93% rename from d2data/d2datadict/map_helper.go rename to d2common/d2data/d2datadict/map_helper.go index 8b523504..d6e26795 100644 --- a/d2data/d2datadict/map_helper.go +++ b/d2common/d2data/d2datadict/map_helper.go @@ -1,8 +1,9 @@ package d2datadict import ( - dh "github.com/OpenDiablo2/OpenDiablo2/d2helper" "strings" + + dh "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" ) func MapHeaders(line string) map[string]int { diff --git a/d2common/d2data/d2datadict/misc.go b/d2common/d2data/d2datadict/misc.go new file mode 100644 index 00000000..fc4ff3e0 --- /dev/null +++ b/d2common/d2data/d2datadict/misc.go @@ -0,0 +1,14 @@ +package d2datadict + +import ( + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" +) + +var MiscItems map[string]*ItemCommonRecord + +func LoadMiscItems(file []byte) { + MiscItems = *LoadCommonItems(file, d2enum.InventoryItemTypeItem) + log.Printf("Loaded %d misc items", len(MiscItems)) +} diff --git a/d2data/d2datadict/missiles.go b/d2common/d2data/d2datadict/missiles.go similarity index 98% rename from d2data/d2datadict/missiles.go rename to d2common/d2data/d2datadict/missiles.go index 555a6896..13c6e721 100644 --- a/d2data/d2datadict/missiles.go +++ b/d2common/d2data/d2datadict/missiles.go @@ -6,11 +6,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - - dh "github.com/OpenDiablo2/OpenDiablo2/d2helper" + dh "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" ) type MissileCalcParam struct { @@ -297,9 +293,9 @@ func createMissileRecord(line string) MissileRecord { var Missiles map[int]*MissileRecord -func LoadMissiles(fileProvider d2interface.FileProvider) { +func LoadMissiles(file []byte) { Missiles = make(map[int]*MissileRecord) - data := strings.Split(string(fileProvider.LoadFile(d2resource.Missiles)), "\r\n")[1:] + data := strings.Split(string(file), "\r\n")[1:] for _, line := range data { if len(line) == 0 { continue diff --git a/d2common/d2data/d2datadict/monstats.go b/d2common/d2data/d2datadict/monstats.go new file mode 100644 index 00000000..beb50bcd --- /dev/null +++ b/d2common/d2data/d2datadict/monstats.go @@ -0,0 +1,11 @@ +package d2datadict + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common" +) + +var MonStatsDictionary *d2common.DataDictionary + +func LoadMonStats(file []byte) { + MonStatsDictionary = d2common.LoadDataDictionary(string(file)) +} diff --git a/d2data/d2datadict/object_lookup.go b/d2common/d2data/d2datadict/object_lookup.go similarity index 100% rename from d2data/d2datadict/object_lookup.go rename to d2common/d2data/d2datadict/object_lookup.go diff --git a/d2data/d2datadict/object_query.go b/d2common/d2data/d2datadict/object_query.go similarity index 100% rename from d2data/d2datadict/object_query.go rename to d2common/d2data/d2datadict/object_query.go diff --git a/d2data/d2datadict/object_query_test.go b/d2common/d2data/d2datadict/object_query_test.go similarity index 100% rename from d2data/d2datadict/object_query_test.go rename to d2common/d2data/d2datadict/object_query_test.go diff --git a/d2data/d2datadict/object_types.go b/d2common/d2data/d2datadict/object_types.go similarity index 74% rename from d2data/d2datadict/object_types.go rename to d2common/d2data/d2datadict/object_types.go index 5df5f5c1..f69b50fa 100644 --- a/d2data/d2datadict/object_types.go +++ b/d2common/d2data/d2datadict/object_types.go @@ -4,10 +4,6 @@ import ( "log" "strings" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - "github.com/OpenDiablo2/OpenDiablo2/d2common" ) @@ -18,8 +14,7 @@ type ObjectTypeRecord struct { var ObjectTypes []ObjectTypeRecord -func LoadObjectTypes(fileProvider d2interface.FileProvider) { - objectTypeData := fileProvider.LoadFile(d2resource.ObjectType) +func LoadObjectTypes(objectTypeData []byte) { streamReader := d2common.CreateStreamReader(objectTypeData) count := streamReader.GetInt32() ObjectTypes = make([]ObjectTypeRecord, count) diff --git a/d2data/d2datadict/objects.go b/d2common/d2data/d2datadict/objects.go similarity index 97% rename from d2data/d2datadict/objects.go rename to d2common/d2data/d2datadict/objects.go index f9c8b54a..15b8a09c 100644 --- a/d2data/d2datadict/objects.go +++ b/d2common/d2data/d2datadict/objects.go @@ -4,11 +4,7 @@ import ( "log" "strings" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - - dh "github.com/OpenDiablo2/OpenDiablo2/d2helper" + dh "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" ) // An ObjectRecord represents the settings for one type of object from objects.txt @@ -339,9 +335,9 @@ func createObjectRecord(props []string) ObjectRecord { var Objects map[int]*ObjectRecord -func LoadObjects(fileProvider d2interface.FileProvider) { +func LoadObjects(file []byte) { Objects = make(map[int]*ObjectRecord) - data := strings.Split(string(fileProvider.LoadFile(d2resource.ObjectDetails)), "\r\n")[1:] + data := strings.Split(string(file), "\r\n")[1:] for _, line := range data { if len(line) == 0 { continue diff --git a/d2common/d2data/d2datadict/palette.go b/d2common/d2data/d2datadict/palette.go new file mode 100644 index 00000000..91161d94 --- /dev/null +++ b/d2common/d2data/d2datadict/palette.go @@ -0,0 +1,41 @@ +package d2datadict + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" +) + +// PaletteRGB represents a color in a palette +type PaletteRGB struct { + R, G, B uint8 +} + +// PaletteType represents a palette +type PaletteRec struct { + Name d2enum.PaletteType + Colors [256]PaletteRGB +} + +var Palettes map[d2enum.PaletteType]PaletteRec + +// CreatePalette creates a palette +func CreatePalette(name d2enum.PaletteType, data []byte) PaletteRec { + result := PaletteRec{Name: name} + + for i := 0; i <= 255; i++ { + result.Colors[i] = PaletteRGB{ + B: data[i*3], + G: data[(i*3)+1], + R: data[(i*3)+2], + } + } + return result +} + +func LoadPalette(paletteType d2enum.PaletteType, file []byte) { + if Palettes == nil { + Palettes = make(map[d2enum.PaletteType]PaletteRec) + } + palette := CreatePalette(paletteType, file) + Palettes[paletteType] = palette + +} diff --git a/d2data/d2datadict/sounds.go b/d2common/d2data/d2datadict/sounds.go similarity index 89% rename from d2data/d2datadict/sounds.go rename to d2common/d2data/d2datadict/sounds.go index e6f6a3fc..71f5d3a2 100644 --- a/d2data/d2datadict/sounds.go +++ b/d2common/d2data/d2datadict/sounds.go @@ -4,11 +4,7 @@ import ( "log" "strings" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - - dh "github.com/OpenDiablo2/OpenDiablo2/d2helper" + dh "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" ) // SoundEntry represents a sound entry @@ -80,9 +76,9 @@ func createSoundEntry(soundLine string) SoundEntry { var Sounds map[string]SoundEntry -func LoadSounds(fileProvider d2interface.FileProvider) { +func LoadSounds(file []byte) { Sounds = make(map[string]SoundEntry) - soundData := strings.Split(string(fileProvider.LoadFile(d2resource.SoundSettings)), "\r\n")[1:] + soundData := strings.Split(string(file), "\r\n")[1:] for _, line := range soundData { if len(line) == 0 { continue diff --git a/d2data/d2datadict/unique_items.go b/d2common/d2data/d2datadict/unique_items.go similarity index 93% rename from d2data/d2datadict/unique_items.go rename to d2common/d2data/d2datadict/unique_items.go index 7500d2cb..2450a3aa 100644 --- a/d2data/d2datadict/unique_items.go +++ b/d2common/d2data/d2datadict/unique_items.go @@ -6,11 +6,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - - dh "github.com/OpenDiablo2/OpenDiablo2/d2helper" + dh "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" ) type UniqueItemRecord struct { @@ -120,9 +116,9 @@ func createUniqueItemProperty(r *[]string, inc func() int) UniqueItemProperty { var UniqueItems map[string]*UniqueItemRecord -func LoadUniqueItems(fileProvider d2interface.FileProvider) { +func LoadUniqueItems(file []byte) { UniqueItems = make(map[string]*UniqueItemRecord) - data := strings.Split(string(fileProvider.LoadFile(d2resource.UniqueItems)), "\r\n")[1:] + data := strings.Split(string(file), "\r\n")[1:] for _, line := range data { if len(line) == 0 { continue diff --git a/d2common/d2data/d2datadict/weapons.go b/d2common/d2data/d2datadict/weapons.go new file mode 100644 index 00000000..acffdeb4 --- /dev/null +++ b/d2common/d2data/d2datadict/weapons.go @@ -0,0 +1,14 @@ +package d2datadict + +import ( + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" +) + +var Weapons map[string]*ItemCommonRecord + +func LoadWeapons(file []byte) { + Weapons = *LoadCommonItems(file, d2enum.InventoryItemTypeWeapon) + log.Printf("Loaded %d weapons", len(Weapons)) +} diff --git a/d2data/d2video/binkdecoder.go b/d2common/d2data/d2video/binkdecoder.go similarity index 100% rename from d2data/d2video/binkdecoder.go rename to d2common/d2data/d2video/binkdecoder.go diff --git a/d2data/object.go b/d2common/d2data/object.go similarity index 81% rename from d2data/object.go rename to d2common/d2data/object.go index 56e368b4..e4b5a62d 100644 --- a/d2data/object.go +++ b/d2common/d2data/object.go @@ -2,7 +2,7 @@ package d2data import ( "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" ) type Object struct { diff --git a/d2data/d2cof/cof.go b/d2common/d2fileformats/d2cof/cof.go similarity index 100% rename from d2data/d2cof/cof.go rename to d2common/d2fileformats/d2cof/cof.go index 9e1c81d9..6af6ab62 100644 --- a/d2data/d2cof/cof.go +++ b/d2common/d2fileformats/d2cof/cof.go @@ -3,9 +3,9 @@ package d2cof import ( "strings" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" ) type COF struct { diff --git a/d2data/d2cof/coflayer.go b/d2common/d2fileformats/d2cof/coflayer.go similarity index 100% rename from d2data/d2cof/coflayer.go rename to d2common/d2fileformats/d2cof/coflayer.go diff --git a/d2data/d2dc6/dc6.go b/d2common/d2fileformats/d2dc6/dc6.go similarity index 98% rename from d2data/d2dc6/dc6.go rename to d2common/d2fileformats/d2dc6/dc6.go index 840326d7..296db42f 100644 --- a/d2data/d2dc6/dc6.go +++ b/d2common/d2fileformats/d2dc6/dc6.go @@ -4,7 +4,7 @@ import ( "encoding/binary" "log" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/go-restruct/restruct" ) diff --git a/d2data/d2dc6/dc6.ksy b/d2common/d2fileformats/d2dc6/dc6.ksy similarity index 100% rename from d2data/d2dc6/dc6.ksy rename to d2common/d2fileformats/d2dc6/dc6.ksy diff --git a/d2data/d2dcc/common_data.go b/d2common/d2fileformats/d2dcc/common_data.go similarity index 100% rename from d2data/d2dcc/common_data.go rename to d2common/d2fileformats/d2dcc/common_data.go diff --git a/d2data/d2dcc/dcc.go b/d2common/d2fileformats/d2dcc/dcc.go similarity index 100% rename from d2data/d2dcc/dcc.go rename to d2common/d2fileformats/d2dcc/dcc.go diff --git a/d2data/d2dcc/dcc_cell.go b/d2common/d2fileformats/d2dcc/dcc_cell.go similarity index 100% rename from d2data/d2dcc/dcc_cell.go rename to d2common/d2fileformats/d2dcc/dcc_cell.go diff --git a/d2data/d2dcc/dcc_direction.go b/d2common/d2fileformats/d2dcc/dcc_direction.go similarity index 99% rename from d2data/d2dcc/dcc_direction.go rename to d2common/d2fileformats/d2dcc/dcc_direction.go index edeb692f..fcb04e91 100644 --- a/d2data/d2dcc/dcc_direction.go +++ b/d2common/d2fileformats/d2dcc/dcc_direction.go @@ -4,7 +4,7 @@ import ( "log" "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2helper" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" ) type DCCDirection struct { diff --git a/d2data/d2dcc/dcc_direction_frame.go b/d2common/d2fileformats/d2dcc/dcc_direction_frame.go similarity index 100% rename from d2data/d2dcc/dcc_direction_frame.go rename to d2common/d2fileformats/d2dcc/dcc_direction_frame.go diff --git a/d2data/d2dcc/dcc_pixel_buffer_entry.go b/d2common/d2fileformats/d2dcc/dcc_pixel_buffer_entry.go similarity index 100% rename from d2data/d2dcc/dcc_pixel_buffer_entry.go rename to d2common/d2fileformats/d2dcc/dcc_pixel_buffer_entry.go diff --git a/d2data/d2ds1/common_data.go b/d2common/d2fileformats/d2ds1/common_data.go similarity index 100% rename from d2data/d2ds1/common_data.go rename to d2common/d2fileformats/d2ds1/common_data.go diff --git a/d2data/d2ds1/ds1.go b/d2common/d2fileformats/d2ds1/ds1.go similarity index 98% rename from d2data/d2ds1/ds1.go rename to d2common/d2fileformats/d2ds1/ds1.go index 92e945db..ee76b4e1 100644 --- a/d2data/d2ds1/ds1.go +++ b/d2common/d2fileformats/d2ds1/ds1.go @@ -2,10 +2,10 @@ package d2ds1 import ( "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2data" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict" - "github.com/OpenDiablo2/OpenDiablo2/d2helper" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" ) type DS1 struct { diff --git a/d2data/d2ds1/floor_shadow_record.go b/d2common/d2fileformats/d2ds1/floor_shadow_record.go similarity index 100% rename from d2data/d2ds1/floor_shadow_record.go rename to d2common/d2fileformats/d2ds1/floor_shadow_record.go diff --git a/d2data/d2ds1/substitution_group.go b/d2common/d2fileformats/d2ds1/substitution_group.go similarity index 100% rename from d2data/d2ds1/substitution_group.go rename to d2common/d2fileformats/d2ds1/substitution_group.go diff --git a/d2data/d2ds1/substitution_record.go b/d2common/d2fileformats/d2ds1/substitution_record.go similarity index 100% rename from d2data/d2ds1/substitution_record.go rename to d2common/d2fileformats/d2ds1/substitution_record.go diff --git a/d2data/d2ds1/tilerecord.go b/d2common/d2fileformats/d2ds1/tilerecord.go similarity index 100% rename from d2data/d2ds1/tilerecord.go rename to d2common/d2fileformats/d2ds1/tilerecord.go diff --git a/d2data/d2ds1/wallrecord.go b/d2common/d2fileformats/d2ds1/wallrecord.go similarity index 100% rename from d2data/d2ds1/wallrecord.go rename to d2common/d2fileformats/d2ds1/wallrecord.go diff --git a/d2data/d2dt1/block.go b/d2common/d2fileformats/d2dt1/block.go similarity index 100% rename from d2data/d2dt1/block.go rename to d2common/d2fileformats/d2dt1/block.go diff --git a/d2data/d2dt1/dt1.go b/d2common/d2fileformats/d2dt1/dt1.go similarity index 100% rename from d2data/d2dt1/dt1.go rename to d2common/d2fileformats/d2dt1/dt1.go diff --git a/d2data/d2dt1/material.go b/d2common/d2fileformats/d2dt1/material.go similarity index 100% rename from d2data/d2dt1/material.go rename to d2common/d2fileformats/d2dt1/material.go diff --git a/d2data/d2dt1/subtile.go b/d2common/d2fileformats/d2dt1/subtile.go similarity index 100% rename from d2data/d2dt1/subtile.go rename to d2common/d2fileformats/d2dt1/subtile.go diff --git a/d2data/d2dt1/subtile_test.go b/d2common/d2fileformats/d2dt1/subtile_test.go similarity index 100% rename from d2data/d2dt1/subtile_test.go rename to d2common/d2fileformats/d2dt1/subtile_test.go diff --git a/d2data/d2dt1/tile.go b/d2common/d2fileformats/d2dt1/tile.go similarity index 100% rename from d2data/d2dt1/tile.go rename to d2common/d2fileformats/d2dt1/tile.go diff --git a/d2data/d2mpq/crypto_buff.go b/d2common/d2fileformats/d2mpq/crypto_buff.go similarity index 100% rename from d2data/d2mpq/crypto_buff.go rename to d2common/d2fileformats/d2mpq/crypto_buff.go diff --git a/d2data/d2mpq/hash_entry_map.go b/d2common/d2fileformats/d2mpq/hash_entry_map.go similarity index 100% rename from d2data/d2mpq/hash_entry_map.go rename to d2common/d2fileformats/d2mpq/hash_entry_map.go diff --git a/d2data/d2mpq/mpq.go b/d2common/d2fileformats/d2mpq/mpq.go similarity index 100% rename from d2data/d2mpq/mpq.go rename to d2common/d2fileformats/d2mpq/mpq.go diff --git a/d2data/d2mpq/mpq_file_record.go b/d2common/d2fileformats/d2mpq/mpq_file_record.go similarity index 100% rename from d2data/d2mpq/mpq_file_record.go rename to d2common/d2fileformats/d2mpq/mpq_file_record.go diff --git a/d2data/d2mpq/mpq_stream.go b/d2common/d2fileformats/d2mpq/mpq_stream.go similarity index 98% rename from d2data/d2mpq/mpq_stream.go rename to d2common/d2fileformats/d2mpq/mpq_stream.go index 67947ba5..9442fa9b 100644 --- a/d2data/d2mpq/mpq_stream.go +++ b/d2common/d2fileformats/d2mpq/mpq_stream.go @@ -10,10 +10,10 @@ import ( "log" "strings" - "github.com/OpenDiablo2/OpenDiablo2/d2helper" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" "github.com/JoshVarga/blast" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2compression" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2compression" ) // Stream represents a stream of data in an MPQ archive diff --git a/d2helper/math.go b/d2common/d2helper/math.go similarity index 100% rename from d2helper/math.go rename to d2common/d2helper/math.go diff --git a/d2helper/stringutils.go b/d2common/d2helper/stringutils.go similarity index 100% rename from d2helper/stringutils.go rename to d2common/d2helper/stringutils.go diff --git a/d2helper/timeutils.go b/d2common/d2helper/timeutils.go similarity index 100% rename from d2helper/timeutils.go rename to d2common/d2helper/timeutils.go diff --git a/d2common/d2interface/audio_provider.go b/d2common/d2interface/audio_provider.go new file mode 100644 index 00000000..98f16e19 --- /dev/null +++ b/d2common/d2interface/audio_provider.go @@ -0,0 +1,7 @@ +package d2interface + +type AudioProvider interface { + PlayBGM(song string) + LoadSoundEffect(sfx string) (SoundEffect, error) + SetVolumes(bgmVolume, sfxVolume float64) +} diff --git a/d2common/d2interface/file_provider.go b/d2common/d2interface/file_provider.go deleted file mode 100644 index 08ff5f22..00000000 --- a/d2common/d2interface/file_provider.go +++ /dev/null @@ -1,7 +0,0 @@ -package d2interface - -// FileProvider is an instance that can provide different types of files -type FileProvider interface { - LoadFile(fileName string) []byte - //LoadSprite(fileName string, palette enums.PaletteType) *d2render.Sprite -} diff --git a/d2common/d2interface/renderer.go b/d2common/d2interface/renderer.go new file mode 100644 index 00000000..097394e7 --- /dev/null +++ b/d2common/d2interface/renderer.go @@ -0,0 +1,20 @@ +package d2interface + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common" +) + +type Renderer interface { + GetRendererName() string + SetWindowIcon(fileName string) + Run(f func(d2common.Surface) error, width, height int, title string) error + IsDrawingSkipped() bool + CreateSurface(surface d2common.Surface) (error, d2common.Surface) + NewSurface(width, height int, filter d2common.Filter) (error, d2common.Surface) + IsFullScreen() (bool, error) + SetFullScreen(fullScreen bool) error + SetVSyncEnabled(vsync bool) error + GetVSyncEnabled() (bool, error) + GetCursorPos() (int, int, error) + CurrentFPS() float64 +} diff --git a/d2common/d2interface/sound_effect.go b/d2common/d2interface/sound_effect.go new file mode 100644 index 00000000..343a67aa --- /dev/null +++ b/d2common/d2interface/sound_effect.go @@ -0,0 +1,6 @@ +package d2interface + +type SoundEffect interface { + Play() + Stop() +} diff --git a/d2common/filter.go b/d2common/filter.go new file mode 100644 index 00000000..7a4d5fdb --- /dev/null +++ b/d2common/filter.go @@ -0,0 +1,15 @@ +package d2common + +// Filter represents the type of texture filter to be used when an image is maginified or minified. +type Filter int + +const ( + // FilterDefault represents the default filter. + FilterDefault Filter = 0 + + // FilterNearest represents nearest (crisp-edged) filter + FilterNearest Filter = Filter(1) + + // FilterLinear represents linear filter + FilterLinear Filter = Filter(2) +) diff --git a/d2common/surface.go b/d2common/surface.go new file mode 100644 index 00000000..51bc0df3 --- /dev/null +++ b/d2common/surface.go @@ -0,0 +1,22 @@ +package d2common + +import ( + "image/color" +) + +type Surface interface { + Clear(color color.Color) error + DrawRect(width, height int, color color.Color) + DrawLine(x, y int, color color.Color) + DrawText(format string, params ...interface{}) + GetSize() (width, height int) + GetDepth() int + Pop() + PopN(n int) + PushColor(color color.Color) + PushCompositeMode(mode CompositeMode) + PushFilter(filter Filter) + PushTranslation(x, y int) + Render(surface Surface) error + ReplacePixels(pixels []byte) error +} diff --git a/d2common/text_dictionary.go b/d2common/text_dictionary.go index 2a488f31..761f608e 100644 --- a/d2common/text_dictionary.go +++ b/d2common/text_dictionary.go @@ -3,10 +3,6 @@ package d2common import ( "log" "strconv" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" ) type textDictionaryHashEntry struct { @@ -28,16 +24,17 @@ func TranslateString(key string) string { return result } -func LoadTextDictionary(fileProvider d2interface.FileProvider) { - lookupTable = make(map[string]string) - loadDictionary(fileProvider, d2resource.PatchStringTable) - loadDictionary(fileProvider, d2resource.ExpansionStringTable) - loadDictionary(fileProvider, d2resource.StringTable) - log.Printf("Loaded %d entries from the string table", len(lookupTable)) +func GetDictionaryEntryCount() int { + if lookupTable == nil { + return 0 + } + return len(lookupTable) } -func loadDictionary(fileProvider d2interface.FileProvider, dictionaryName string) { - dictionaryData := fileProvider.LoadFile(dictionaryName) +func LoadDictionary(dictionaryData []byte) { + if lookupTable == nil { + lookupTable = make(map[string]string) + } br := CreateStreamReader(dictionaryData) // CRC if _, err := br.ReadBytes(2); err != nil { diff --git a/d2core/d2archivemanager/archive_manager.go b/d2core/d2archivemanager/archive_manager.go new file mode 100644 index 00000000..37399b00 --- /dev/null +++ b/d2core/d2archivemanager/archive_manager.go @@ -0,0 +1,123 @@ +package d2archivemanager + +import ( + "errors" + "path" + "sync" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2config" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2mpq" +) + +type archiveEntry struct { + archivePath string + hashEntryMap d2mpq.HashEntryMap +} + +type ArchiveManager struct { + cache *d2common.Cache + config *d2config.Configuration + entries []archiveEntry + mutex sync.Mutex +} + +const ( + ArchiveBudget = 1024 * 1024 * 512 +) + +func CreateArchiveManager(config *d2config.Configuration) *ArchiveManager { + return &ArchiveManager{cache: d2common.CreateCache(ArchiveBudget), config: config} +} + +func (am *ArchiveManager) SetCacheVerbose(verbose bool) { + am.cache.SetCacheVerbose(verbose) +} + +func (am *ArchiveManager) GetCacheWeight() int { + return am.cache.GetWeight() +} + +func (am *ArchiveManager) GetCacheBudget() int { + return am.cache.GetBudget() +} + +func (am *ArchiveManager) ClearCache() { + am.cache.Clear() +} + +func (am *ArchiveManager) LoadArchiveForFile(filePath string) (*d2mpq.MPQ, error) { + am.mutex.Lock() + defer am.mutex.Unlock() + + if err := am.CacheArchiveEntries(); err != nil { + return nil, err + } + + for _, archiveEntry := range am.entries { + if archiveEntry.hashEntryMap.Contains(filePath) { + return am.LoadArchive(archiveEntry.archivePath) + } + } + + return nil, errors.New("file not found") +} + +func (am *ArchiveManager) FileExistsInArchive(filePath string) (bool, error) { + am.mutex.Lock() + defer am.mutex.Unlock() + + if err := am.CacheArchiveEntries(); err != nil { + return false, err + } + + for _, archiveEntry := range am.entries { + if archiveEntry.hashEntryMap.Contains(filePath) { + return true, nil + } + } + + return false, nil +} + +func (am *ArchiveManager) LoadArchive(archivePath string) (*d2mpq.MPQ, error) { + if archive, found := am.cache.Retrieve(archivePath); found { + return archive.(*d2mpq.MPQ), nil + } + + archive, err := d2mpq.Load(archivePath) + if err != nil { + return nil, err + } + + if err := am.cache.Insert(archivePath, archive, int(archive.Data.ArchiveSize)); err != nil { + return nil, err + } + + return archive, nil +} + +func (am *ArchiveManager) CacheArchiveEntries() error { + if len(am.entries) == len(am.config.MpqLoadOrder) { + return nil + } + + am.entries = nil + + for _, archiveName := range am.config.MpqLoadOrder { + archivePath := path.Join(am.config.MpqPath, archiveName) + archive, err := am.LoadArchive(archivePath) + if err != nil { + return err + } + + am.entries = append( + am.entries, + archiveEntry{archivePath, archive.HashEntryMap}, + ) + } + + return nil +} diff --git a/d2render/animated_entity.go b/d2core/d2assetmanager/animated_entity.go similarity index 93% rename from d2render/animated_entity.go rename to d2core/d2assetmanager/animated_entity.go index 2941772e..4adf1889 100644 --- a/d2render/animated_entity.go +++ b/d2core/d2assetmanager/animated_entity.go @@ -1,15 +1,15 @@ -package d2render +package d2assetmanager import ( "math" "math/rand" - "github.com/OpenDiablo2/OpenDiablo2/d2helper" + "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2asset" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" ) // AnimatedEntity represents an entity on the map that can be animated @@ -27,12 +27,12 @@ type AnimatedEntity struct { action int32 repetitions int - composite *d2asset.Composite + composite *Composite } // CreateAnimatedEntity creates an instance of AnimatedEntity func CreateAnimatedEntity(x, y int32, object *d2datadict.ObjectLookupRecord, palettePath string) (*AnimatedEntity, error) { - composite, err := d2asset.LoadComposite(object, palettePath) + composite, err := LoadComposite(object, palettePath) if err != nil { return nil, err } @@ -71,7 +71,7 @@ func (v AnimatedEntity) Wait() bool { } // Render draws this animated entity onto the target -func (v *AnimatedEntity) Render(target *d2surface.Surface) { +func (v *AnimatedEntity) Render(target d2common.Surface) { target.PushTranslation( int(v.offsetX)+int((v.subcellX-v.subcellY)*16), int(v.offsetY)+int(((v.subcellX+v.subcellY)*8)-5), diff --git a/d2core/d2assetmanager/asset_manager.go b/d2core/d2assetmanager/asset_manager.go new file mode 100644 index 00000000..26707d2b --- /dev/null +++ b/d2core/d2assetmanager/asset_manager.go @@ -0,0 +1,246 @@ +package d2assetmanager + +import ( + "errors" + "fmt" + "log" + "path/filepath" + "strings" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2config" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2archivemanager" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2filemanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2cof" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dc6" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2mpq" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2term" +) + +const ( + AnimationBudget = 64 +) + +var ( + ErrHasInit error = errors.New("asset system is already initialized") + ErrNoInit error = errors.New("asset system is not initialized") +) + +type AssetManager struct { + archiveManager *d2archivemanager.ArchiveManager + fileManager *d2filemanager.FileManager + paletteManager *PaletteManager + cache *d2common.Cache +} + +var singleton *AssetManager + +func Initialize() error { + if singleton != nil { + return ErrHasInit + } + config, _ := d2config.Get() + var ( + archiveManager = d2archivemanager.CreateArchiveManager(config) + fileManager = d2filemanager.CreateFileManager(config, archiveManager) + paletteManager = CreatePaletteManager() + //animationManager = d2animationmanager.CreateAnimationManager() + ) + + singleton = &AssetManager{ + archiveManager, + fileManager, + paletteManager, + nil, + } + singleton.cache = d2common.CreateCache(AnimationBudget) + + d2term.BindAction("assetspam", "display verbose asset manager logs", func(verbose bool) { + if verbose { + d2term.OutputInfo("asset manager verbose logging enabled") + } else { + d2term.OutputInfo("asset manager verbose logging disabled") + } + + archiveManager.SetCacheVerbose(verbose) + fileManager.SetCacheVerbose(verbose) + paletteManager.SetCacheVerbose(verbose) + }) + + d2term.BindAction("assetstat", "display asset manager cache statistics", func() { + d2term.OutputInfo("archive cache: %f%%", float64(archiveManager.GetCacheWeight())/float64(archiveManager.GetCacheBudget())*100.0) + d2term.OutputInfo("file cache: %f%%", float64(fileManager.GetCacheWeight())/float64(fileManager.GetCacheBudget())*100.0) + d2term.OutputInfo("palette cache: %f%%", float64(paletteManager.GetCacheWeight())/float64(paletteManager.GetCacheBudget())*100.0) + //d2term.OutputInfo("animation cache: %f%%", float64(GetCacheWeight())/float64(GetCacheBudget())*100.0) + }) + + d2term.BindAction("assetclear", "clear asset manager cache", func() { + archiveManager.ClearCache() + fileManager.ClearCache() + paletteManager.ClearCache() + //am.ClearCache() + }) + + return nil +} + +func Shutdown() { + singleton = nil +} + +func LoadArchive(archivePath string) (*d2mpq.MPQ, error) { + if singleton == nil { + return nil, ErrNoInit + } + + return singleton.archiveManager.LoadArchive(archivePath) +} + +func LoadFile(filePath string) ([]byte, error) { + if singleton == nil { + return nil, ErrNoInit + } + + data, err := singleton.fileManager.LoadFile(filePath) + if err != nil { + log.Printf("error loading file %s (%v)", filePath, err.Error()) + } + + return data, err +} + +func FileExists(filePath string) (bool, error) { + if singleton == nil { + return false, ErrNoInit + } + + return singleton.fileManager.FileExists(filePath) +} + +func LoadAnimation(animationPath, palettePath string) (*d2render.Animation, error) { + return LoadAnimationWithTransparency(animationPath, palettePath, 255) +} + +func LoadAnimationWithTransparency(animationPath, palettePath string, transparency int) (*d2render.Animation, error) { + if singleton == nil { + return nil, ErrNoInit + } + + return singleton.LoadAnimation(animationPath, palettePath, transparency) +} + +func LoadComposite(object *d2datadict.ObjectLookupRecord, palettePath string) (*Composite, error) { + return CreateComposite(object, palettePath), nil +} + +func loadPalette(palettePath string) (*d2datadict.PaletteRec, error) { + if singleton == nil { + return nil, ErrNoInit + } + + return singleton.paletteManager.LoadPalette(palettePath) +} + +func loadDC6(dc6Path, palettePath string) (*d2dc6.DC6File, error) { + dc6Data, err := LoadFile(dc6Path) + if err != nil { + return nil, err + } + + paletteData, err := loadPalette(palettePath) + if err != nil { + return nil, err + } + + dc6, err := d2dc6.LoadDC6(dc6Data, *paletteData) + if err != nil { + return nil, err + } + + return &dc6, nil +} + +func loadDCC(dccPath string) (*d2dcc.DCC, error) { + dccData, err := LoadFile(dccPath) + if err != nil { + return nil, err + } + + return d2dcc.LoadDCC(dccData) +} + +func loadCOF(cofPath string) (*d2cof.COF, error) { + cofData, err := LoadFile(cofPath) + if err != nil { + return nil, err + } + + return d2cof.LoadCOF(cofData) +} + +func (am *AssetManager) SetCacheVerbose(verbose bool) { + am.cache.SetCacheVerbose(verbose) +} + +func (am *AssetManager) ClearCache() { + am.cache.Clear() +} + +func (am *AssetManager) GetCacheWeight() int { + return am.cache.GetWeight() +} + +func (am *AssetManager) GetCacheBudget() int { + return am.cache.GetBudget() +} + +func (am *AssetManager) LoadAnimation(animationPath, palettePath string, transparency int) (*d2render.Animation, error) { + cachePath := fmt.Sprintf("%s;%s;%d", animationPath, palettePath, transparency) + if animation, found := am.cache.Retrieve(cachePath); found { + return animation.(*d2render.Animation).Clone(), nil + } + + var animation *d2render.Animation + switch strings.ToLower(filepath.Ext(animationPath)) { + case ".dc6": + dc6, err := loadDC6(animationPath, palettePath) + if err != nil { + return nil, err + } + + animation, err = d2render.CreateAnimationFromDC6(dc6) + if err != nil { + return nil, err + } + case ".dcc": + dcc, err := loadDCC(animationPath) + if err != nil { + return nil, err + } + + palette, err := loadPalette(palettePath) + if err != nil { + return nil, err + } + + animation, err = d2render.CreateAnimationFromDCC(dcc, palette, transparency) + if err != nil { + return nil, err + } + + default: + return nil, errors.New("unknown animation format") + } + + if err := am.cache.Insert(cachePath, animation.Clone(), 1); err != nil { + return nil, err + } + + return animation, nil +} diff --git a/d2asset/composite.go b/d2core/d2assetmanager/composite.go similarity index 91% rename from d2asset/composite.go rename to d2core/d2assetmanager/composite.go index 43d0b8e3..d459304d 100644 --- a/d2asset/composite.go +++ b/d2core/d2assetmanager/composite.go @@ -1,15 +1,18 @@ -package d2asset +package d2assetmanager import ( "errors" "fmt" "strings" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2data" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2dcc" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc" ) type Composite struct { @@ -18,7 +21,7 @@ type Composite struct { mode *compositeMode } -func createComposite(object *d2datadict.ObjectLookupRecord, palettePath string) *Composite { +func CreateComposite(object *d2datadict.ObjectLookupRecord, palettePath string) *Composite { return &Composite{object: object, palettePath: palettePath} } @@ -45,7 +48,7 @@ func (c *Composite) Advance(elapsed float64) error { return nil } -func (c *Composite) Render(target *d2surface.Surface) error { +func (c *Composite) Render(target d2common.Surface) error { if c.mode == nil { return nil } @@ -105,7 +108,7 @@ type compositeMode struct { directionCount int playedCount int - layers []*Animation + layers []*d2render.Animation drawOrder [][]d2enum.CompositeType frameCount int @@ -140,7 +143,7 @@ func (c *Composite) createMode(animationMode, weaponClass string, direction int) weaponClass: weaponClass, direction: direction, directionCount: cof.NumberOfDirections, - layers: make([]*Animation, d2enum.CompositeTypeMax), + layers: make([]*d2render.Animation, d2enum.CompositeTypeMax), frameCount: animationData[0].FramesPerDirection, animationSpeed: 1.0 / ((float64(animationData[0].AnimationSpeed) * 25.0) / 256.0), } @@ -245,7 +248,7 @@ func (c *Composite) createMode(animationMode, weaponClass string, direction int) return mode, nil } -func loadCompositeLayer(object *d2datadict.ObjectLookupRecord, layerKey, layerValue, animationMode, weaponClass, palettePath string, transparency int) (*Animation, error) { +func loadCompositeLayer(object *d2datadict.ObjectLookupRecord, layerKey, layerValue, animationMode, weaponClass, palettePath string, transparency int) (*d2render.Animation, error) { animationPaths := []string{ fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dcc", object.Base, object.Token, layerKey, object.Token, layerKey, layerValue, animationMode, weaponClass), fmt.Sprintf("%s/%s/%s/%s%s%s%s%s.dcc", object.Base, object.Token, layerKey, object.Token, layerKey, layerValue, animationMode, "HTH"), diff --git a/d2core/d2assetmanager/palette_manager.go b/d2core/d2assetmanager/palette_manager.go new file mode 100644 index 00000000..655f3936 --- /dev/null +++ b/d2core/d2assetmanager/palette_manager.go @@ -0,0 +1,49 @@ +package d2assetmanager + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" +) + +type PaletteManager struct { + cache *d2common.Cache +} + +const ( + PaletteBudget = 64 +) + +func CreatePaletteManager() *PaletteManager { + return &PaletteManager{d2common.CreateCache(PaletteBudget)} +} + +func (pm *PaletteManager) SetCacheVerbose(verbose bool) { + pm.cache.SetCacheVerbose(verbose) +} + +func (pm *PaletteManager) ClearCache() { + pm.cache.Clear() +} + +func (pm *PaletteManager) GetCacheWeight() int { + return pm.cache.GetWeight() +} + +func (pm *PaletteManager) GetCacheBudget() int { + return pm.cache.GetBudget() +} + +func (pm *PaletteManager) LoadPalette(palettePath string) (*d2datadict.PaletteRec, error) { + if palette, found := pm.cache.Retrieve(palettePath); found { + return palette.(*d2datadict.PaletteRec), nil + } + + paletteData, err := LoadFile(palettePath) + if err != nil { + return nil, err + } + + palette := d2datadict.CreatePalette("", paletteData) + pm.cache.Insert(palettePath, &palette, 1) + return &palette, nil +} diff --git a/d2core/d2audio/d2audio.go b/d2core/d2audio/d2audio.go new file mode 100644 index 00000000..ba286a53 --- /dev/null +++ b/d2core/d2audio/d2audio.go @@ -0,0 +1,47 @@ +package d2audio + +import ( + "errors" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" +) + +var singleton d2interface.AudioProvider + +var ( + ErrHasInit error = errors.New("audio system is already initialized") + ErrNotInit error = errors.New("audio system has not been initialized") +) + +// CreateManager creates a sound provider +func Initialize(audioProvider d2interface.AudioProvider) error { + if singleton != nil { + return ErrHasInit + } + singleton = audioProvider + return nil +} + +// PlayBGM plays an infinitely looping background track +func PlayBGM(song string) error { + if singleton == nil { + return ErrNotInit + } + singleton.PlayBGM(song) + return nil +} + +func LoadSoundEffect(sfx string) (d2interface.SoundEffect, error) { + if singleton == nil { + return nil, ErrNotInit + } + return singleton.LoadSoundEffect(sfx) +} + +func SetVolumes(bgmVolume, sfxVolume float64) error { + if singleton == nil { + return ErrNotInit + } + singleton.SetVolumes(bgmVolume, sfxVolume) + return nil +} diff --git a/d2core/d2audio/ebiten/ebiten_audio_provider.go b/d2core/d2audio/ebiten/ebiten_audio_provider.go new file mode 100644 index 00000000..c9f25ac1 --- /dev/null +++ b/d2core/d2audio/ebiten/ebiten_audio_provider.go @@ -0,0 +1,83 @@ +package ebiten + +import ( + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + "github.com/hajimehoshi/ebiten/audio/wav" + + "github.com/hajimehoshi/ebiten/audio" +) + +type EbitenAudioProvider struct { + audioContext *audio.Context // The Audio context + bgmAudio *audio.Player // The audio player + lastBgm string + sfxVolume float64 + bgmVolume float64 +} + +func CreateAudio() (*EbitenAudioProvider, error) { + result := &EbitenAudioProvider{} + var err error + result.audioContext, err = audio.NewContext(44100) + if err != nil { + log.Fatal(err) + return nil, err + } + return result, nil +} + +func (eap *EbitenAudioProvider) PlayBGM(song string) { + if eap.lastBgm == song { + return + } + eap.lastBgm = song + if song == "" && eap.bgmAudio != nil && eap.bgmAudio.IsPlaying() { + _ = eap.bgmAudio.Pause() + return + } + go func() { + if eap.bgmAudio != nil { + err := eap.bgmAudio.Close() + if err != nil { + log.Panic(err) + } + } + audioData, err := d2assetmanager.LoadFile(song) + if err != nil { + panic(err) + } + d, err := wav.Decode(eap.audioContext, audio.BytesReadSeekCloser(audioData)) + if err != nil { + log.Fatal(err) + } + s := audio.NewInfiniteLoop(d, d.Length()) + eap.bgmAudio, err = audio.NewPlayer(eap.audioContext, s) + if err != nil { + log.Fatal(err) + } + eap.bgmAudio.SetVolume(eap.bgmVolume) + // Play the infinite-length stream. This never ends. + err = eap.bgmAudio.Rewind() + if err != nil { + panic(err) + } + err = eap.bgmAudio.Play() + if err != nil { + panic(err) + } + }() +} + +func (eap *EbitenAudioProvider) LoadSoundEffect(sfx string) (d2interface.SoundEffect, error) { + result := CreateSoundEffect(sfx, eap.audioContext, eap.sfxVolume) // TODO: Split + return result, nil +} + +func (eap *EbitenAudioProvider) SetVolumes(bgmVolume, sfxVolume float64) { + eap.sfxVolume = sfxVolume + eap.bgmVolume = bgmVolume +} diff --git a/d2audio/sound_effect.go b/d2core/d2audio/ebiten/ebiten_sound_effect.go similarity index 66% rename from d2audio/sound_effect.go rename to d2core/d2audio/ebiten/ebiten_sound_effect.go index 5559ab30..0d476b03 100644 --- a/d2audio/sound_effect.go +++ b/d2core/d2audio/ebiten/ebiten_sound_effect.go @@ -1,22 +1,20 @@ -package d2audio +package ebiten import ( "log" - "github.com/OpenDiablo2/OpenDiablo2/d2asset" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict" - - "github.com/hajimehoshi/ebiten/audio/wav" - + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" "github.com/hajimehoshi/ebiten/audio" + "github.com/hajimehoshi/ebiten/audio/wav" ) -type SoundEffect struct { +type EbitenSoundEffect struct { player *audio.Player } -func CreateSoundEffect(sfx string, context *audio.Context, volume float64) *SoundEffect { - result := &SoundEffect{} +func CreateSoundEffect(sfx string, context *audio.Context, volume float64) *EbitenSoundEffect { + result := &EbitenSoundEffect{} var soundFile string if _, exists := d2datadict.Sounds[sfx]; exists { soundEntry := d2datadict.Sounds[sfx] @@ -25,7 +23,7 @@ func CreateSoundEffect(sfx string, context *audio.Context, volume float64) *Soun soundFile = sfx } - audioData, err := d2asset.LoadFile(soundFile) + audioData, err := d2assetmanager.LoadFile(soundFile) if err != nil { panic(err) } @@ -44,11 +42,11 @@ func CreateSoundEffect(sfx string, context *audio.Context, volume float64) *Soun return result } -func (v *SoundEffect) Play() { +func (v *EbitenSoundEffect) Play() { v.player.Rewind() v.player.Play() } -func (v *SoundEffect) Stop() { +func (v *EbitenSoundEffect) Stop() { v.player.Pause() } diff --git a/d2core/d2filemanager/file_manager.go b/d2core/d2filemanager/file_manager.go new file mode 100644 index 00000000..55da6cb1 --- /dev/null +++ b/d2core/d2filemanager/file_manager.go @@ -0,0 +1,84 @@ +package d2filemanager + +import ( + "strings" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2archivemanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2config" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" +) + +const ( + FileBudget = 1024 * 1024 * 32 +) + +type FileManager struct { + cache *d2common.Cache + archiveManager *d2archivemanager.ArchiveManager + config *d2config.Configuration +} + +func CreateFileManager(config *d2config.Configuration, archiveManager *d2archivemanager.ArchiveManager) *FileManager { + return &FileManager{d2common.CreateCache(FileBudget), archiveManager, config} +} + +func (fm *FileManager) SetCacheVerbose(verbose bool) { + fm.cache.SetCacheVerbose(verbose) +} + +func (fm *FileManager) ClearCache() { + fm.cache.Clear() +} + +func (fm *FileManager) GetCacheWeight() int { + return fm.cache.GetWeight() +} + +func (fm *FileManager) GetCacheBudget() int { + return fm.cache.GetBudget() +} +func (fm *FileManager) LoadFile(filePath string) ([]byte, error) { + filePath = fm.fixupFilePath(filePath) + if value, found := fm.cache.Retrieve(filePath); found { + return value.([]byte), nil + } + + archive, err := fm.archiveManager.LoadArchiveForFile(filePath) + if err != nil { + return nil, err + } + + data, err := archive.ReadFile(filePath) + if err != nil { + return nil, err + } + + if err := fm.cache.Insert(filePath, data, len(data)); err != nil { + return nil, err + } + + return data, nil +} + +func (fm *FileManager) FileExists(filePath string) (bool, error) { + filePath = fm.fixupFilePath(filePath) + return fm.archiveManager.FileExistsInArchive(filePath) +} + +func (fm *FileManager) fixupFilePath(filePath string) string { + filePath = strings.ReplaceAll(filePath, "{LANG}", fm.config.Language) + if strings.ToUpper(d2resource.LanguageCode) == "CHI" { + filePath = strings.ReplaceAll(filePath, "{LANG_FONT}", fm.config.Language) + } else { + filePath = strings.ReplaceAll(filePath, "{LANG_FONT}", "latin") + } + + filePath = strings.ToLower(filePath) + filePath = strings.ReplaceAll(filePath, `/`, "\\") + filePath = strings.TrimPrefix(filePath, "\\") + + return filePath +} diff --git a/d2core/game_state.go b/d2core/d2gamestate/game_state.go similarity index 96% rename from d2core/game_state.go rename to d2core/d2gamestate/game_state.go index 5d15e888..60ae6873 100644 --- a/d2core/game_state.go +++ b/d2core/d2gamestate/game_state.go @@ -1,4 +1,4 @@ -package d2core +package d2gamestate import ( "io/ioutil" @@ -9,6 +9,8 @@ import ( "strings" "time" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" @@ -34,7 +36,7 @@ type GameState struct { HeroLevel int Act int FilePath string - Equipment CharacterEquipment + Equipment d2hero.CharacterEquipment } const GameStateVersion = uint32(2) // Update this when you make breaking changes diff --git a/d2core/character_equipment.go b/d2core/d2hero/character_equipment.go similarity index 95% rename from d2core/character_equipment.go rename to d2core/d2hero/character_equipment.go index 725fa71f..5c9f0dcb 100644 --- a/d2core/character_equipment.go +++ b/d2core/d2hero/character_equipment.go @@ -1,4 +1,4 @@ -package d2core +package d2hero type CharacterEquipment struct { Head *InventoryItemArmor // Head diff --git a/d2core/hero.go b/d2core/d2hero/hero.go similarity index 80% rename from d2core/hero.go rename to d2core/d2hero/hero.go index 6f322db9..ff3fd3dd 100644 --- a/d2core/hero.go +++ b/d2core/d2hero/hero.go @@ -1,15 +1,15 @@ -package d2core +package d2hero 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/d2data/d2datadict" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" ) type Hero struct { - AnimatedEntity *d2render.AnimatedEntity + AnimatedEntity *d2assetmanager.AnimatedEntity Equipment CharacterEquipment mode d2enum.AnimationMode direction int @@ -32,7 +32,7 @@ func CreateHero(x, y int32, direction int, heroType d2enum.Hero, equipment Chara LH: equipment.LeftHand.ItemCode(), } - entity, err := d2render.CreateAnimatedEntity(x, y, object, d2resource.PaletteUnits) + entity, err := d2assetmanager.CreateAnimatedEntity(x, y, object, d2resource.PaletteUnits) if err != nil { panic(err) } @@ -52,7 +52,7 @@ func (v *Hero) Advance(tickTime float64) { v.AnimatedEntity.Advance(tickTime) } -func (v *Hero) Render(target *d2surface.Surface) { +func (v *Hero) Render(target d2common.Surface) { v.AnimatedEntity.Render(target) } diff --git a/d2core/hero_objects.go b/d2core/d2hero/hero_objects.go similarity index 98% rename from d2core/hero_objects.go rename to d2core/d2hero/hero_objects.go index 155b0533..8acf890c 100644 --- a/d2core/hero_objects.go +++ b/d2core/d2hero/hero_objects.go @@ -1,4 +1,4 @@ -package d2core +package d2hero import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" diff --git a/d2core/inventory_item_armor.go b/d2core/d2hero/inventory_item_armor.go similarity index 95% rename from d2core/inventory_item_armor.go rename to d2core/d2hero/inventory_item_armor.go index 2aa027f6..f71a81cc 100644 --- a/d2core/inventory_item_armor.go +++ b/d2core/d2hero/inventory_item_armor.go @@ -1,10 +1,10 @@ -package d2core +package d2hero import ( "log" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict" ) type InventoryItemArmor struct { diff --git a/d2core/inventory_item_weapon.go b/d2core/d2hero/inventory_item_weapon.go similarity index 95% rename from d2core/inventory_item_weapon.go rename to d2core/d2hero/inventory_item_weapon.go index 6fc77f2f..3f925092 100644 --- a/d2core/inventory_item_weapon.go +++ b/d2core/d2hero/inventory_item_weapon.go @@ -1,10 +1,10 @@ -package d2core +package d2hero import ( "log" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict" ) type InventoryItemWeapon struct { diff --git a/d2input/d2input.go b/d2core/d2input/d2input.go similarity index 100% rename from d2input/d2input.go rename to d2core/d2input/d2input.go diff --git a/d2input/input_manager.go b/d2core/d2input/input_manager.go similarity index 100% rename from d2input/input_manager.go rename to d2core/d2input/input_manager.go diff --git a/d2core/npc.go b/d2core/d2npc/npc.go similarity index 80% rename from d2core/npc.go rename to d2core/d2npc/npc.go index 9637f237..10ff2938 100644 --- a/d2core/npc.go +++ b/d2core/d2npc/npc.go @@ -1,23 +1,21 @@ -package d2core +package d2npc import ( "github.com/OpenDiablo2/OpenDiablo2/d2common" - + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" ) type NPC struct { - AnimatedEntity *d2render.AnimatedEntity + AnimatedEntity *d2assetmanager.AnimatedEntity HasPaths bool Paths []d2common.Path path int } func CreateNPC(x, y int32, object *d2datadict.ObjectLookupRecord, direction int) *NPC { - entity, err := d2render.CreateAnimatedEntity(x, y, object, d2resource.PaletteUnits) + entity, err := d2assetmanager.CreateAnimatedEntity(x, y, object, d2resource.PaletteUnits) if err != nil { panic(err) } @@ -45,7 +43,7 @@ func (v *NPC) SetPaths(paths []d2common.Path) { v.HasPaths = len(paths) > 0 } -func (v *NPC) Render(target *d2surface.Surface) { +func (v *NPC) Render(target d2common.Surface) { v.AnimatedEntity.Render(target) } diff --git a/d2asset/animation.go b/d2core/d2render/animation.go similarity index 88% rename from d2asset/animation.go rename to d2core/d2render/animation.go index 79cdf43c..c4c568ae 100644 --- a/d2asset/animation.go +++ b/d2core/d2render/animation.go @@ -1,18 +1,16 @@ -package d2asset +package d2render import ( "errors" "image/color" "math" - "github.com/OpenDiablo2/OpenDiablo2/d2helper" + "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2dc6" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2dcc" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" - - "github.com/hajimehoshi/ebiten" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dc6" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dcc" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" ) type playMode int @@ -29,7 +27,7 @@ type animationFrame struct { offsetX int offsetY int - image *ebiten.Image + image d2common.Surface } type animationDirection struct { @@ -43,7 +41,7 @@ type Animation struct { lastFrameTime float64 playedCount int - compositeMode ebiten.CompositeMode + compositeMode d2common.CompositeMode colorMod color.Color playMode playMode @@ -51,7 +49,7 @@ type Animation struct { playLoop bool } -func createAnimationFromDCC(dcc *d2dcc.DCC, palette *d2datadict.PaletteRec, transparency int) (*Animation, error) { +func CreateAnimationFromDCC(dcc *d2dcc.DCC, palette *d2datadict.PaletteRec, transparency int) (*Animation, error) { animation := &Animation{ playLength: 1.0, playLoop: true, @@ -85,7 +83,7 @@ func createAnimationFromDCC(dcc *d2dcc.DCC, palette *d2datadict.PaletteRec, tran } } - image, err := ebiten.NewImage(frameWidth, frameHeight, ebiten.FilterNearest) + err, image := NewSurface(frameWidth, frameHeight, d2common.FilterNearest) if err != nil { return nil, err } @@ -113,14 +111,14 @@ func createAnimationFromDCC(dcc *d2dcc.DCC, palette *d2datadict.PaletteRec, tran return animation, nil } -func createAnimationFromDC6(dc6 *d2dc6.DC6File) (*Animation, error) { +func CreateAnimationFromDC6(dc6 *d2dc6.DC6File) (*Animation, error) { animation := &Animation{ playLength: 1.0, playLoop: true, } for frameIndex, dc6Frame := range dc6.Frames { - image, err := ebiten.NewImage(int(dc6Frame.Width), int(dc6Frame.Height), ebiten.FilterNearest) + err, image := NewSurface(int(dc6Frame.Width), int(dc6Frame.Height), d2common.FilterNearest) if err != nil { return nil, err } @@ -147,7 +145,7 @@ func createAnimationFromDC6(dc6 *d2dc6.DC6File) (*Animation, error) { return animation, nil } -func (a *Animation) clone() *Animation { +func (a *Animation) Clone() *Animation { animation := *a return &animation } @@ -193,7 +191,7 @@ func (a *Animation) Advance(elapsed float64) error { return nil } -func (a *Animation) Render(target *d2surface.Surface) error { +func (a *Animation) Render(target d2common.Surface) error { direction := a.directions[a.directionIndex] frame := direction.frames[a.frameIndex] @@ -326,8 +324,8 @@ func (a *Animation) ResetPlayedCount() { func (a *Animation) SetBlend(blend bool) { if blend { - a.compositeMode = ebiten.CompositeModeLighter + a.compositeMode = d2common.CompositeModeLighter } else { - a.compositeMode = ebiten.CompositeModeSourceOver + a.compositeMode = d2common.CompositeModeSourceOver } } diff --git a/d2render/d2mapengine/camera.go b/d2core/d2render/d2mapengine/camera.go similarity index 100% rename from d2render/d2mapengine/camera.go rename to d2core/d2render/d2mapengine/camera.go diff --git a/d2render/d2mapengine/engine.go b/d2core/d2render/d2mapengine/engine.go similarity index 85% rename from d2render/d2mapengine/engine.go rename to d2core/d2render/d2mapengine/engine.go index ce8afd99..c622681e 100644 --- a/d2render/d2mapengine/engine.go +++ b/d2core/d2render/d2mapengine/engine.go @@ -3,21 +3,23 @@ package d2mapengine import ( "strings" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gamestate" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2audio" - "github.com/OpenDiablo2/OpenDiablo2/d2core" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" ) type MapEntity interface { - Render(target *d2surface.Surface) + Render(target d2common.Surface) Advance(tickTime float64) GetPosition() (float64, float64) } type MapEngine struct { - soundManager *d2audio.Manager - gameState *d2core.GameState + gameState *d2gamestate.GameState debugVisLevel int @@ -27,11 +29,10 @@ type MapEngine struct { camera Camera } -func CreateMapEngine(gameState *d2core.GameState, soundManager *d2audio.Manager) *MapEngine { +func CreateMapEngine(gameState *d2gamestate.GameState) *MapEngine { engine := &MapEngine{ - gameState: gameState, - soundManager: soundManager, - viewport: NewViewport(0, 0, 800, 600), + gameState: gameState, + viewport: NewViewport(0, 0, 800, 600), } engine.viewport.SetCamera(&engine.camera) @@ -90,7 +91,7 @@ func (me *MapEngine) GenerateMap(regionType d2enum.RegionIdType, levelPreset int } func (me *MapEngine) GenerateAct1Overworld() { - me.soundManager.PlayBGM("/data/global/music/Act1/town1.wav") // TODO: Temp stuff here + d2audio.PlayBGM("/data/global/music/Act1/town1.wav") // TODO: Temp stuff here region, entities := loadRegion(me.gameState.Seed, 0, 0, d2enum.RegionAct1Town, 1, -1) me.regions = append(me.regions, region) @@ -133,7 +134,7 @@ func (me *MapEngine) Advance(tickTime float64) { } } -func (me *MapEngine) Render(target *d2surface.Surface) { +func (me *MapEngine) Render(target d2common.Surface) { for _, region := range me.regions { if region.isVisbile(me.viewport) { region.renderPass1(me.viewport, target) diff --git a/d2render/d2mapengine/region.go b/d2core/d2render/d2mapengine/region.go similarity index 90% rename from d2render/d2mapengine/region.go rename to d2core/d2render/d2mapengine/region.go index ee8d16bc..c99ca7c7 100644 --- a/d2render/d2mapengine/region.go +++ b/d2core/d2render/d2mapengine/region.go @@ -7,21 +7,20 @@ import ( "math/rand" "strconv" - "github.com/hajimehoshi/ebiten" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2npc" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2helper" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" - "github.com/OpenDiablo2/OpenDiablo2/d2asset" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2ds1" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2dt1" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2core" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2ds1" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2dt1" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" ) type MapRegion struct { @@ -34,7 +33,7 @@ type MapRegion struct { palette d2datadict.PaletteRec startX float64 startY float64 - imageCacheRecords map[uint32]*ebiten.Image + imageCacheRecords map[uint32]d2common.Surface seed int64 currentFrame int lastFrameTime float64 @@ -44,7 +43,7 @@ func loadRegion(seed int64, tileOffsetX, tileOffsetY int, levelType d2enum.Regio region := &MapRegion{ levelType: d2datadict.LevelTypes[levelType], levelPreset: d2datadict.LevelPresets[levelPreset], - imageCacheRecords: map[uint32]*ebiten.Image{}, + imageCacheRecords: map[uint32]d2common.Surface{}, seed: seed, } @@ -55,7 +54,7 @@ func loadRegion(seed int64, tileOffsetX, tileOffsetY int, levelType d2enum.Regio for _, levelTypeDt1 := range region.levelType.Files { if len(levelTypeDt1) != 0 && levelTypeDt1 != "" && levelTypeDt1 != "0" { - fileData, err := d2asset.LoadFile("/data/global/tiles/" + levelTypeDt1) + fileData, err := d2assetmanager.LoadFile("/data/global/tiles/" + levelTypeDt1) if err != nil { panic(err) } @@ -78,7 +77,7 @@ func loadRegion(seed int64, tileOffsetX, tileOffsetY int, levelType d2enum.Regio } region.regionPath = levelFilesToPick[levelIndex] - fileData, err := d2asset.LoadFile("/data/global/tiles/" + region.regionPath) + fileData, err := d2assetmanager.LoadFile("/data/global/tiles/" + region.regionPath) if err != nil { panic(err) } @@ -137,13 +136,13 @@ func (mr *MapRegion) loadEntities() []MapEntity { switch object.Lookup.Type { case d2datadict.ObjectTypeCharacter: if object.Lookup.Base != "" && object.Lookup.Token != "" && object.Lookup.TR != "" { - npc := d2core.CreateNPC(int32(worldX), int32(worldY), object.Lookup, 0) + npc := d2npc.CreateNPC(int32(worldX), int32(worldY), object.Lookup, 0) npc.SetPaths(object.Paths) entities = append(entities, npc) } case d2datadict.ObjectTypeItem: if object.ObjectInfo != nil && object.ObjectInfo.Draw && object.Lookup.Base != "" && object.Lookup.Token != "" { - entity, err := d2render.CreateAnimatedEntity(int32(worldX), int32(worldY), object.Lookup, d2resource.PaletteUnits) + entity, err := d2assetmanager.CreateAnimatedEntity(int32(worldX), int32(worldY), object.Lookup, d2resource.PaletteUnits) if err != nil { panic(err) } @@ -231,7 +230,7 @@ func (mr *MapRegion) getTileWorldPosition(tileX, tileY int) (float64, float64) { return float64(tileX + mr.tileRect.Left), float64(tileY + mr.tileRect.Top) } -func (mr *MapRegion) renderPass1(viewport *Viewport, target *d2surface.Surface) { +func (mr *MapRegion) renderPass1(viewport *Viewport, target d2common.Surface) { for tileY := range mr.ds1.Tiles { for tileX, tile := range mr.ds1.Tiles[tileY] { worldX, worldY := mr.getTileWorldPosition(tileX, tileY) @@ -244,7 +243,7 @@ func (mr *MapRegion) renderPass1(viewport *Viewport, target *d2surface.Surface) } } -func (mr *MapRegion) renderPass2(entities []MapEntity, viewport *Viewport, target *d2surface.Surface) { +func (mr *MapRegion) renderPass2(entities []MapEntity, viewport *Viewport, target d2common.Surface) { for tileY := range mr.ds1.Tiles { for tileX, tile := range mr.ds1.Tiles[tileY] { worldX, worldY := mr.getTileWorldPosition(tileX, tileY) @@ -267,7 +266,7 @@ func (mr *MapRegion) renderPass2(entities []MapEntity, viewport *Viewport, targe } } -func (mr *MapRegion) renderPass3(viewport *Viewport, target *d2surface.Surface) { +func (mr *MapRegion) renderPass3(viewport *Viewport, target d2common.Surface) { for tileY := range mr.ds1.Tiles { for tileX, tile := range mr.ds1.Tiles[tileY] { worldX, worldY := mr.getTileWorldPosition(tileX, tileY) @@ -280,7 +279,7 @@ func (mr *MapRegion) renderPass3(viewport *Viewport, target *d2surface.Surface) } } -func (mr *MapRegion) renderTilePass1(tile d2ds1.TileRecord, viewport *Viewport, target *d2surface.Surface) { +func (mr *MapRegion) renderTilePass1(tile d2ds1.TileRecord, viewport *Viewport, target d2common.Surface) { for _, wall := range tile.Walls { if !wall.Hidden && wall.Prop1 != 0 && wall.Type.LowerWall() { mr.renderWall(wall, viewport, target) @@ -300,7 +299,7 @@ func (mr *MapRegion) renderTilePass1(tile d2ds1.TileRecord, viewport *Viewport, } } -func (mr *MapRegion) renderTilePass2(tile d2ds1.TileRecord, viewport *Viewport, target *d2surface.Surface) { +func (mr *MapRegion) renderTilePass2(tile d2ds1.TileRecord, viewport *Viewport, target d2common.Surface) { for _, wall := range tile.Walls { if !wall.Hidden && wall.Type.UpperWall() { mr.renderWall(wall, viewport, target) @@ -308,7 +307,7 @@ func (mr *MapRegion) renderTilePass2(tile d2ds1.TileRecord, viewport *Viewport, } } -func (mr *MapRegion) renderTilePass3(tile d2ds1.TileRecord, viewport *Viewport, target *d2surface.Surface) { +func (mr *MapRegion) renderTilePass3(tile d2ds1.TileRecord, viewport *Viewport, target d2common.Surface) { for _, wall := range tile.Walls { if wall.Type == d2enum.Roof { mr.renderWall(wall, viewport, target) @@ -316,8 +315,8 @@ func (mr *MapRegion) renderTilePass3(tile d2ds1.TileRecord, viewport *Viewport, } } -func (mr *MapRegion) renderFloor(tile d2ds1.FloorShadowRecord, viewport *Viewport, target *d2surface.Surface) { - var img *ebiten.Image +func (mr *MapRegion) renderFloor(tile d2ds1.FloorShadowRecord, viewport *Viewport, target d2common.Surface) { + var img d2common.Surface if !tile.Animated { img = mr.getImageCacheRecord(tile.Style, tile.Sequence, 0, tile.RandomIndex) } else { @@ -337,7 +336,7 @@ func (mr *MapRegion) renderFloor(tile d2ds1.FloorShadowRecord, viewport *Viewpor target.Render(img) } -func (mr *MapRegion) renderWall(tile d2ds1.WallRecord, viewport *Viewport, target *d2surface.Surface) { +func (mr *MapRegion) renderWall(tile d2ds1.WallRecord, viewport *Viewport, target d2common.Surface) { img := mr.getImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tile.RandomIndex) if img == nil { log.Printf("Render called on uncached wall {%v,%v,%v}", tile.Style, tile.Sequence, tile.Type) @@ -353,7 +352,7 @@ func (mr *MapRegion) renderWall(tile d2ds1.WallRecord, viewport *Viewport, targe target.Render(img) } -func (mr *MapRegion) renderShadow(tile d2ds1.FloorShadowRecord, viewport *Viewport, target *d2surface.Surface) { +func (mr *MapRegion) renderShadow(tile d2ds1.FloorShadowRecord, viewport *Viewport, target d2common.Surface) { img := mr.getImageCacheRecord(tile.Style, tile.Sequence, 13, tile.RandomIndex) if img == nil { log.Printf("Render called on uncached shadow {%v,%v}", tile.Style, tile.Sequence) @@ -370,7 +369,7 @@ func (mr *MapRegion) renderShadow(tile d2ds1.FloorShadowRecord, viewport *Viewpo target.Render(img) } -func (mr *MapRegion) renderDebug(debugVisLevel int, viewport *Viewport, target *d2surface.Surface) { +func (mr *MapRegion) renderDebug(debugVisLevel int, viewport *Viewport, target d2common.Surface) { for tileY := range mr.ds1.Tiles { for tileX := range mr.ds1.Tiles[tileY] { worldX, worldY := mr.getTileWorldPosition(tileX, tileY) @@ -381,7 +380,7 @@ func (mr *MapRegion) renderDebug(debugVisLevel int, viewport *Viewport, target * } } -func (mr *MapRegion) renderTileDebug(x, y int, debugVisLevel int, viewport *Viewport, target *d2surface.Surface) { +func (mr *MapRegion) renderTileDebug(x, y int, debugVisLevel int, viewport *Viewport, target d2common.Surface) { if debugVisLevel > 0 { subtileColor := color.RGBA{80, 80, 255, 100} tileColor := color.RGBA{255, 255, 255, 255} @@ -449,12 +448,12 @@ func (mr *MapRegion) generateTileCache() { } } -func (mr *MapRegion) getImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte) *ebiten.Image { +func (mr *MapRegion) getImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte) d2common.Surface { lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex) return mr.imageCacheRecords[lookupIndex] } -func (mr *MapRegion) setImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte, image *ebiten.Image) { +func (mr *MapRegion) setImageCacheRecord(style, sequence byte, tileType d2enum.TileType, randomIndex byte, image d2common.Surface) { lookupIndex := uint32(style)<<24 | uint32(sequence)<<16 | uint32(tileType)<<8 | uint32(randomIndex) mr.imageCacheRecords[lookupIndex] = image } @@ -497,7 +496,7 @@ func (mr *MapRegion) generateFloorCache(tile *d2ds1.FloorShadowRecord, tileX, ti } tileYOffset := d2helper.AbsInt32(tileYMinimum) tileHeight := d2helper.AbsInt32(tileData[i].Height) - image, _ := ebiten.NewImage(int(tileData[i].Width), int(tileHeight), ebiten.FilterNearest) + _, image := d2render.NewSurface(int(tileData[i].Width), int(tileHeight), d2common.FilterNearest) pixels := make([]byte, 4*tileData[i].Width*tileHeight) mr.decodeTileGfxData(tileData[i].Blocks, &pixels, tileYOffset, tileData[i].Width) image.ReplacePixels(pixels) @@ -532,7 +531,7 @@ func (mr *MapRegion) generateShadowCache(tile *d2ds1.FloorShadowRecord, tileX, t return } - image, _ := ebiten.NewImage(int(tileData.Width), int(tileHeight), ebiten.FilterNearest) + _, image := d2render.NewSurface(int(tileData.Width), int(tileHeight), d2common.FilterNearest) pixels := make([]byte, 4*tileData.Width*int32(tileHeight)) mr.decodeTileGfxData(tileData.Blocks, &pixels, tileYOffset, tileData.Width) image.ReplacePixels(pixels) @@ -593,7 +592,7 @@ func (mr *MapRegion) generateWallCache(tile *d2ds1.WallRecord, tileX, tileY int) return } - image, _ := ebiten.NewImage(160, int(realHeight), ebiten.FilterNearest) + _, image := d2render.NewSurface(160, int(realHeight), d2common.FilterNearest) pixels := make([]byte, 4*160*realHeight) mr.decodeTileGfxData(tileData.Blocks, &pixels, tileYOffset, 160) diff --git a/d2render/d2mapengine/viewport.go b/d2core/d2render/d2mapengine/viewport.go similarity index 100% rename from d2render/d2mapengine/viewport.go rename to d2core/d2render/d2mapengine/viewport.go diff --git a/d2core/d2render/d2render.go b/d2core/d2render/d2render.go new file mode 100644 index 00000000..6c34a925 --- /dev/null +++ b/d2core/d2render/d2render.go @@ -0,0 +1,108 @@ +package d2render + +import ( + "errors" + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" +) + +var ( + ErrHasInit error = errors.New("rendering system is already initialized") + ErrNotInit error = errors.New("rendering system has not been initialized") + ErrInvalidRenderer error = errors.New("invalid rendering system specified") +) + +var singleton d2interface.Renderer + +func Initialize(rend d2interface.Renderer) error { + if singleton != nil { + return d2input.ErrHasInit + } + singleton = rend + log.Printf("Initialized the %s renderer...", singleton.GetRendererName()) + return nil +} + +func SetWindowIcon(fileName string) error { + if singleton == nil { + return ErrNotInit + } + singleton.SetWindowIcon(fileName) + return nil +} + +func Run(f func(d2common.Surface) error, width, height int, title string) error { + if singleton == nil { + return ErrNotInit + } + singleton.Run(f, width, height, title) + return nil +} + +func IsDrawingSkipped() (error, bool) { + if singleton == nil { + return ErrNotInit, true + } + return nil, singleton.IsDrawingSkipped() +} + +func CreateSurface(surface d2common.Surface) (error, d2common.Surface) { + if singleton == nil { + return ErrNotInit, nil + } + return singleton.CreateSurface(surface) +} + +func NewSurface(width, height int, filter d2common.Filter) (error, d2common.Surface) { + if singleton == nil { + return ErrNotInit, nil + } + return singleton.NewSurface(width, height, filter) +} + +func IsFullScreen() (bool, error) { + if singleton == nil { + return false, ErrNotInit + } + return singleton.IsFullScreen() +} + +func SetFullScreen(fullScreen bool) error { + if singleton == nil { + return ErrNotInit + } + return singleton.SetFullScreen(fullScreen) +} + +func SetVSyncEnabled(vsync bool) error { + if singleton == nil { + return ErrNotInit + } + return singleton.SetVSyncEnabled(vsync) +} + +func GetVSyncEnabled() (bool, error) { + if singleton == nil { + return false, ErrNotInit + } + return singleton.GetVSyncEnabled() +} + +func GetCursorPos() (int, int, error) { + if singleton == nil { + return 0, 0, ErrNotInit + } + return singleton.GetCursorPos() +} + +func CurrentFPS() (float64, error) { + if singleton == nil { + return 0, ErrNotInit + } + return singleton.CurrentFPS(), nil +} diff --git a/d2corehelper/color_convert.go b/d2core/d2render/ebiten/color_convert.go similarity index 98% rename from d2corehelper/color_convert.go rename to d2core/d2render/ebiten/color_convert.go index 8f44a6a0..2a90383b 100644 --- a/d2corehelper/color_convert.go +++ b/d2core/d2render/ebiten/color_convert.go @@ -1,4 +1,4 @@ -package d2corehelper +package ebiten import ( "image/color" diff --git a/d2core/d2render/ebiten/composite_mode_helper.go b/d2core/d2render/ebiten/composite_mode_helper.go new file mode 100644 index 00000000..8db98306 --- /dev/null +++ b/d2core/d2render/ebiten/composite_mode_helper.go @@ -0,0 +1,72 @@ +package ebiten + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/hajimehoshi/ebiten" +) + +func d2ToEbitenCompositeMode(comp d2common.CompositeMode) ebiten.CompositeMode { + switch comp { + case d2common.CompositeModeSourceOver: + return ebiten.CompositeModeSourceOver + case d2common.CompositeModeClear: + return ebiten.CompositeModeClear + case d2common.CompositeModeCopy: + return ebiten.CompositeModeCopy + case d2common.CompositeModeDestination: + return ebiten.CompositeModeDestination + case d2common.CompositeModeDestinationOver: + return ebiten.CompositeModeDestinationOver + case d2common.CompositeModeSourceIn: + return ebiten.CompositeModeSourceIn + case d2common.CompositeModeDestinationIn: + return ebiten.CompositeModeDestinationIn + case d2common.CompositeModeSourceOut: + return ebiten.CompositeModeSourceOut + case d2common.CompositeModeDestinationOut: + return ebiten.CompositeModeDestinationOut + case d2common.CompositeModeSourceAtop: + return ebiten.CompositeModeSourceAtop + case d2common.CompositeModeDestinationAtop: + return ebiten.CompositeModeDestinationAtop + case d2common.CompositeModeXor: + return ebiten.CompositeModeXor + case d2common.CompositeModeLighter: + return ebiten.CompositeModeLighter + } + + return ebiten.CompositeModeSourceOver +} + +func ebitenToD2CompositeMode(comp ebiten.CompositeMode) d2common.CompositeMode { + switch comp { + case ebiten.CompositeModeSourceOver: + return d2common.CompositeModeSourceOver + case ebiten.CompositeModeClear: + return d2common.CompositeModeClear + case ebiten.CompositeModeCopy: + return d2common.CompositeModeCopy + case ebiten.CompositeModeDestination: + return d2common.CompositeModeDestination + case ebiten.CompositeModeDestinationOver: + return d2common.CompositeModeDestinationOver + case ebiten.CompositeModeSourceIn: + return d2common.CompositeModeSourceIn + case ebiten.CompositeModeDestinationIn: + return d2common.CompositeModeDestinationIn + case ebiten.CompositeModeSourceOut: + return d2common.CompositeModeSourceOut + case ebiten.CompositeModeDestinationOut: + return d2common.CompositeModeDestinationOut + case ebiten.CompositeModeSourceAtop: + return d2common.CompositeModeSourceAtop + case ebiten.CompositeModeDestinationAtop: + return d2common.CompositeModeDestinationAtop + case ebiten.CompositeModeXor: + return d2common.CompositeModeXor + case ebiten.CompositeModeLighter: + return d2common.CompositeModeLighter + } + + return d2common.CompositeModeSourceOver +} diff --git a/d2core/d2render/ebiten/ebiten_renderer.go b/d2core/d2render/ebiten/ebiten_renderer.go new file mode 100644 index 00000000..031a84b8 --- /dev/null +++ b/d2core/d2render/ebiten/ebiten_renderer.go @@ -0,0 +1,116 @@ +package ebiten + +import ( + "image" + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2config" + + "github.com/hajimehoshi/ebiten" + "github.com/hajimehoshi/ebiten/ebitenutil" +) + +type EbitenRenderer struct { +} + +func CreateRenderer() (*EbitenRenderer, error) { + result := &EbitenRenderer{} + + config, err := d2config.Get() + if err != nil { + log.Fatal(err) + return nil, err + } + + ebiten.SetCursorVisible(false) + ebiten.SetFullscreen(config.FullScreen) + ebiten.SetRunnableInBackground(config.RunInBackground) + ebiten.SetVsyncEnabled(config.VsyncEnabled) + ebiten.SetMaxTPS(config.TicksPerSecond) + + return result, nil +} + +func (*EbitenRenderer) GetRendererName() string { + return "Ebiten" +} + +func (*EbitenRenderer) SetWindowIcon(fileName string) { + _, iconImage, err := ebitenutil.NewImageFromFile(fileName, ebiten.FilterLinear) + if err == nil { + ebiten.SetWindowIcon([]image.Image{iconImage}) + } + +} + +func (r *EbitenRenderer) IsDrawingSkipped() bool { + return ebiten.IsDrawingSkipped() +} + +func (r *EbitenRenderer) Run(f func(surface d2common.Surface) error, width, height int, title string) error { + config, err := d2config.Get() + if err != nil { + log.Fatal(err) + return err + } + + return ebiten.Run(func(img *ebiten.Image) error { + err := f(&ebitenSurface{image: img}) + if err != nil { + return err + } + return nil + }, width, height, config.Scale, title) +} + +func (r *EbitenRenderer) CreateSurface(surface d2common.Surface) (error, d2common.Surface) { + result := &ebitenSurface{ + image: surface.(*ebitenSurface).image, + stateCurrent: surfaceState{ + filter: ebiten.FilterNearest, + mode: ebiten.CompositeModeSourceOver, + }, + } + return nil, result +} + +func (r *EbitenRenderer) NewSurface(width, height int, filter d2common.Filter) (error, d2common.Surface) { + ebitenFilter := d2ToEbitenFilter(filter) + img, err := ebiten.NewImage(width, height, ebitenFilter) + if err != nil { + return err, nil + } + result := &ebitenSurface{ + image: img, + } + return nil, result +} + +func (r *EbitenRenderer) IsFullScreen() (bool, error) { + return ebiten.IsFullscreen(), nil +} + +func (r *EbitenRenderer) SetFullScreen(fullScreen bool) error { + ebiten.SetFullscreen(fullScreen) + return nil +} + +func (r *EbitenRenderer) SetVSyncEnabled(vsync bool) error { + ebiten.SetVsyncEnabled(vsync) + return nil +} + +func (r *EbitenRenderer) GetVSyncEnabled() (bool, error) { + return ebiten.IsVsyncEnabled(), nil +} + +func (r *EbitenRenderer) GetCursorPos() (int, int, error) { + cx, cy := ebiten.CursorPosition() + return cx, cy, nil +} + +func (r *EbitenRenderer) CurrentFPS() float64 { + return ebiten.CurrentFPS() +} diff --git a/d2render/d2surface/surface.go b/d2core/d2render/ebiten/ebiten_surface.go similarity index 53% rename from d2render/d2surface/surface.go rename to d2core/d2render/ebiten/ebiten_surface.go index 11055b7c..3803776d 100644 --- a/d2render/d2surface/surface.go +++ b/d2core/d2render/ebiten/ebiten_surface.go @@ -1,60 +1,43 @@ -package d2surface +package ebiten import ( "fmt" "image/color" - "github.com/OpenDiablo2/OpenDiablo2/d2corehelper" + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/ebitenutil" ) -type surfaceState struct { - x int - y int - mode ebiten.CompositeMode - filter ebiten.Filter - color color.Color -} - -type Surface struct { +type ebitenSurface struct { stateStack []surfaceState stateCurrent surfaceState image *ebiten.Image } -func CreateSurface(image *ebiten.Image) *Surface { - return &Surface{ - image: image, - stateCurrent: surfaceState{ - filter: ebiten.FilterNearest, - mode: ebiten.CompositeModeSourceOver, - }, - } -} - -func (s *Surface) PushTranslation(x, y int) { +func (s *ebitenSurface) PushTranslation(x, y int) { s.stateStack = append(s.stateStack, s.stateCurrent) s.stateCurrent.x += x s.stateCurrent.y += y } -func (s *Surface) PushCompositeMode(mode ebiten.CompositeMode) { +func (s *ebitenSurface) PushCompositeMode(mode d2common.CompositeMode) { s.stateStack = append(s.stateStack, s.stateCurrent) - s.stateCurrent.mode = mode + s.stateCurrent.mode = d2ToEbitenCompositeMode(mode) } -func (s *Surface) PushFilter(filter ebiten.Filter) { +func (s *ebitenSurface) PushFilter(filter d2common.Filter) { s.stateStack = append(s.stateStack, s.stateCurrent) - s.stateCurrent.filter = filter + s.stateCurrent.filter = d2ToEbitenFilter(filter) } -func (s *Surface) PushColor(color color.Color) { +func (s *ebitenSurface) PushColor(color color.Color) { s.stateStack = append(s.stateStack, s.stateCurrent) s.stateCurrent.color = color } -func (s *Surface) Pop() { +func (s *ebitenSurface) Pop() { count := len(s.stateStack) if count == 0 { panic("empty stack") @@ -64,28 +47,29 @@ func (s *Surface) Pop() { s.stateStack = s.stateStack[:count-1] } -func (s *Surface) PopN(n int) { +func (s *ebitenSurface) PopN(n int) { for i := 0; i < n; i++ { s.Pop() } } -func (s *Surface) Render(image *ebiten.Image) error { +func (s *ebitenSurface) Render(sfc d2common.Surface) error { opts := &ebiten.DrawImageOptions{CompositeMode: s.stateCurrent.mode} opts.GeoM.Translate(float64(s.stateCurrent.x), float64(s.stateCurrent.y)) opts.Filter = s.stateCurrent.filter if s.stateCurrent.color != nil { - opts.ColorM = d2corehelper.ColorToColorM(s.stateCurrent.color) + opts.ColorM = ColorToColorM(s.stateCurrent.color) } - return s.image.DrawImage(image, opts) + var img = sfc.(*ebitenSurface).image + return s.image.DrawImage(img, opts) } -func (s *Surface) DrawText(format string, params ...interface{}) { +func (s *ebitenSurface) DrawText(format string, params ...interface{}) { ebitenutil.DebugPrintAt(s.image, fmt.Sprintf(format, params...), s.stateCurrent.x, s.stateCurrent.y) } -func (s *Surface) DrawLine(x, y int, color color.Color) { +func (s *ebitenSurface) DrawLine(x, y int, color color.Color) { ebitenutil.DrawLine( s.image, float64(s.stateCurrent.x), @@ -96,7 +80,7 @@ func (s *Surface) DrawLine(x, y int, color color.Color) { ) } -func (s *Surface) DrawRect(width, height int, color color.Color) { +func (s *ebitenSurface) DrawRect(width, height int, color color.Color) { ebitenutil.DrawRect( s.image, float64(s.stateCurrent.x), @@ -107,14 +91,18 @@ func (s *Surface) DrawRect(width, height int, color color.Color) { ) } -func (s *Surface) Clear(color color.Color) error { +func (s *ebitenSurface) Clear(color color.Color) error { return s.image.Fill(color) } -func (s *Surface) GetSize() (int, int) { +func (s *ebitenSurface) GetSize() (int, int) { return s.image.Size() } -func (s *Surface) GetDepth() int { +func (s *ebitenSurface) GetDepth() int { return len(s.stateStack) } + +func (s *ebitenSurface) ReplacePixels(pixels []byte) error { + return s.image.ReplacePixels(pixels) +} diff --git a/d2core/d2render/ebiten/filter_helper.go b/d2core/d2render/ebiten/filter_helper.go new file mode 100644 index 00000000..e4a79086 --- /dev/null +++ b/d2core/d2render/ebiten/filter_helper.go @@ -0,0 +1,32 @@ +package ebiten + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/hajimehoshi/ebiten" +) + +func d2ToEbitenFilter(filter d2common.Filter) ebiten.Filter { + switch filter { + case d2common.FilterDefault: + return ebiten.FilterDefault + case d2common.FilterLinear: + return ebiten.FilterLinear + case d2common.FilterNearest: + return ebiten.FilterNearest + } + + return ebiten.FilterDefault +} + +func ebitenToD2Filter(filter ebiten.Filter) d2common.Filter { + switch filter { + case ebiten.FilterDefault: + return d2common.FilterDefault + case ebiten.FilterLinear: + return d2common.FilterLinear + case ebiten.FilterNearest: + return d2common.FilterNearest + } + + return d2common.FilterDefault +} diff --git a/d2core/d2render/ebiten/surface_state.go b/d2core/d2render/ebiten/surface_state.go new file mode 100644 index 00000000..5b9086e7 --- /dev/null +++ b/d2core/d2render/ebiten/surface_state.go @@ -0,0 +1,15 @@ +package ebiten + +import ( + "image/color" + + "github.com/hajimehoshi/ebiten" +) + +type surfaceState struct { + x int + y int + mode ebiten.CompositeMode + filter ebiten.Filter + color color.Color +} diff --git a/d2core/d2render/render_type.go b/d2core/d2render/render_type.go new file mode 100644 index 00000000..7cc372dd --- /dev/null +++ b/d2core/d2render/render_type.go @@ -0,0 +1,8 @@ +package d2render + +// Defines the type of rendering engine to use +type RenderType int + +const ( + Ebiten = RenderType(1) +) diff --git a/d2render/sprite.go b/d2core/d2render/sprite.go similarity index 80% rename from d2render/sprite.go rename to d2core/d2render/sprite.go index b3f1c467..fbcd1dd5 100644 --- a/d2render/sprite.go +++ b/d2core/d2render/sprite.go @@ -1,38 +1,32 @@ package d2render import ( + "errors" "image/color" - "github.com/OpenDiablo2/OpenDiablo2/d2asset" - "github.com/OpenDiablo2/OpenDiablo2/d2helper" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" + "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" ) type Sprite struct { x int y int - animation *d2asset.Animation + animation *Animation } -func LoadSprite(animationPath, palettePath string) (*Sprite, error) { - animation, err := d2asset.LoadAnimation(animationPath, palettePath) - if err != nil { - return nil, err - } +var ( + ErrNoAnimation error = errors.New("No animation was specified") +) +func LoadSprite(animation *Animation) (*Sprite, error) { + if animation == nil { + return nil, ErrNoAnimation + } return &Sprite{animation: animation}, nil } -func MustLoadSprite(animationPath, palettePath string) *Sprite { - sprite, err := LoadSprite(animationPath, palettePath) - if err != nil { - panic(err) - } - - return sprite -} - -func (s *Sprite) Render(target *d2surface.Surface) error { +func (s *Sprite) Render(target d2common.Surface) error { _, frameHeight := s.animation.GetCurrentFrameSize() target.PushTranslation(s.x, s.y-frameHeight) @@ -40,7 +34,7 @@ func (s *Sprite) Render(target *d2surface.Surface) error { return s.animation.Render(target) } -func (s *Sprite) RenderSegmented(target *d2surface.Surface, segmentsX, segmentsY, frameOffset int) error { +func (s *Sprite) RenderSegmented(target d2common.Surface, segmentsX, segmentsY, frameOffset int) error { var currentY int for y := 0; y < segmentsY; y++ { var currentX int diff --git a/d2core/d2scene/blizzard_intro.go b/d2core/d2scene/blizzard_intro.go deleted file mode 100644 index 18056d3d..00000000 --- a/d2core/d2scene/blizzard_intro.go +++ /dev/null @@ -1,43 +0,0 @@ -package d2scene - -import ( - "github.com/OpenDiablo2/OpenDiablo2/d2asset" - "github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2video" - "github.com/hajimehoshi/ebiten" -) - -type BlizzardIntro struct { - sceneProvider d2coreinterface.SceneProvider - videoDecoder *d2video.BinkDecoder -} - -func CreateBlizzardIntro(sceneProvider d2coreinterface.SceneProvider) *BlizzardIntro { - result := &BlizzardIntro{sceneProvider: sceneProvider} - - return result -} - -func (v *BlizzardIntro) Load() []func() { - return []func(){ - func() { - videoBytes, err := d2asset.LoadFile("/data/local/video/BlizNorth640x480.bik") - if err != nil { - panic(err) - } - v.videoDecoder = d2video.CreateBinkDecoder(videoBytes) - }, - } -} - -func (v *BlizzardIntro) Unload() { - -} - -func (v *BlizzardIntro) Render(screen *ebiten.Image) { - -} - -func (v *BlizzardIntro) Update(tickTime float64) { - -} diff --git a/d2core/d2scenemanager/d2scenemanager.go b/d2core/d2scenemanager/d2scenemanager.go new file mode 100644 index 00000000..347056bc --- /dev/null +++ b/d2core/d2scenemanager/d2scenemanager.go @@ -0,0 +1,100 @@ +package d2scenemanager + +import ( + "math" + "runtime" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" +) + +var nextScene Scene // The next scene to be loaded at the end of the game loop +var currentScene Scene // The current scene being rendered +var loadingIndex int // Determines which load function is currently being called +var thingsToLoad []func() // The load functions for the next scene +var loadingProgress float64 // LoadingProcess is a range between 0.0 and 1.0. If set, loading screen displays. +var stepLoadingSize float64 // The size for each loading step + +// SetNextScene tells the engine what scene to load on the next update cycle +func SetNextScene(scene Scene) { + nextScene = scene +} + +func GetCurrentScene() Scene { + return currentScene +} + +// updateScene handles the scene maintenance for the engine +func UpdateScene() { + if nextScene == nil { + if thingsToLoad != nil { + if loadingIndex < len(thingsToLoad) { + thingsToLoad[loadingIndex]() + loadingIndex++ + if loadingIndex < len(thingsToLoad) { + StepLoading() + } else { + FinishLoading() + thingsToLoad = nil + } + return + } + } + return + } + if currentScene != nil { + currentScene.Unload() + runtime.GC() + } + currentScene = nextScene + nextScene = nil + d2ui.Reset() + thingsToLoad = currentScene.Load() + loadingIndex = 0 + SetLoadingStepSize(1.0 / float64(len(thingsToLoad))) + ResetLoading() +} + +func Advance(time float64) { + if currentScene == nil { + return + } + currentScene.Advance(time) +} + +func Render(surface d2common.Surface) { + if currentScene == nil { + return + } + currentScene.Render(surface) +} + +// SetLoadingStepSize sets the size of the loading step +func SetLoadingStepSize(size float64) { + stepLoadingSize = size +} + +// ResetLoading resets the loading progress +func ResetLoading() { + loadingProgress = 0.0 +} + +// StepLoading increments the loading progress +func StepLoading() { + loadingProgress = math.Min(1.0, loadingProgress+stepLoadingSize) +} + +// FinishLoading terminates the loading phase +func FinishLoading() { + loadingProgress = 1.0 +} + +// IsLoading returns true if the engine is currently in a loading state +func IsLoading() bool { + return loadingProgress < 1.0 +} + +func GetLoadingProgress() float64 { + return loadingProgress +} diff --git a/d2corecommon/d2coreinterface/scene.go b/d2core/d2scenemanager/scene.go similarity index 53% rename from d2corecommon/d2coreinterface/scene.go rename to d2core/d2scenemanager/scene.go index ab115d44..fe26db2d 100644 --- a/d2corecommon/d2coreinterface/scene.go +++ b/d2core/d2scenemanager/scene.go @@ -1,11 +1,13 @@ -package d2coreinterface +package d2scenemanager -import "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common" +) // Scene defines the function necessary for scene management type Scene interface { Load() []func() Unload() - Render(target *d2surface.Surface) + Render(target d2common.Surface) Advance(tickTime float64) } diff --git a/d2term/d2term.go b/d2core/d2term/d2term.go similarity index 92% rename from d2term/d2term.go rename to d2core/d2term/d2term.go index 174e14fc..01fc2163 100644 --- a/d2term/d2term.go +++ b/d2core/d2term/d2term.go @@ -4,8 +4,9 @@ import ( "errors" "log" - "github.com/OpenDiablo2/OpenDiablo2/d2input" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" + "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" ) var ( @@ -101,7 +102,7 @@ func UnbindAction(name string) error { return singleton.unbindAction(name) } -func Render(surface *d2surface.Surface) error { +func Render(surface d2common.Surface) error { if singleton == nil { return ErrNotInit } diff --git a/d2term/terminal.go b/d2core/d2term/terminal.go similarity index 98% rename from d2term/terminal.go rename to d2core/d2term/terminal.go index d2812a74..9538f3b6 100644 --- a/d2term/terminal.go +++ b/d2core/d2term/terminal.go @@ -13,9 +13,10 @@ import ( "strconv" "strings" - "github.com/OpenDiablo2/OpenDiablo2/d2helper" - "github.com/OpenDiablo2/OpenDiablo2/d2input" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" + "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" ) const ( @@ -235,7 +236,7 @@ func (t *terminal) OnKeyChars(event d2input.KeyCharsEvent) bool { return handled } -func (t *terminal) render(surface *d2surface.Surface) error { +func (t *terminal) render(surface d2common.Surface) error { if !t.isVisible() { return nil } diff --git a/d2render/d2ui/button.go b/d2core/d2ui/button.go similarity index 73% rename from d2render/d2ui/button.go rename to d2core/d2ui/button.go index 64097bc6..dceeaf2c 100644 --- a/d2render/d2ui/button.go +++ b/d2core/d2ui/button.go @@ -4,12 +4,13 @@ import ( "image" "image/color" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" - - "github.com/hajimehoshi/ebiten" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" ) // ButtonType defines the type of button @@ -87,19 +88,19 @@ var ButtonLayouts = map[ButtonType]ButtonLayout{ // Button defines a standard wide UI button type Button struct { - enabled bool - x, y int - width, height int - visible bool - pressed bool - toggled bool - normalImage *ebiten.Image - pressedImage *ebiten.Image - toggledImage *ebiten.Image - pressedToggledImage *ebiten.Image - disabledImage *ebiten.Image - buttonLayout ButtonLayout - onClick func() + enabled bool + x, y int + width, height int + visible bool + pressed bool + toggled bool + normalSurface d2common.Surface + pressedSurface d2common.Surface + toggledSurface d2common.Surface + pressedToggledSurface d2common.Surface + disabledSurface d2common.Surface + buttonLayout ButtonLayout + onClick func() } // CreateButton creates an instance of Button @@ -115,7 +116,8 @@ func CreateButton(buttonType ButtonType, text string) Button { result.buttonLayout = buttonLayout font := GetFont(buttonLayout.FontPath, d2resource.PaletteUnits) - buttonSprite, _ := d2render.LoadSprite(buttonLayout.ResourceName, buttonLayout.PaletteName) + animation, _ := d2assetmanager.LoadAnimation(buttonLayout.ResourceName, buttonLayout.PaletteName) + buttonSprite, _ := d2render.LoadSprite(animation) totalButtonTypes := buttonSprite.GetFrameCount() / (buttonLayout.XSegments * buttonLayout.YSegments) for i := 0; i < buttonLayout.XSegments; i++ { w, _, _ := buttonSprite.GetFrameSize(i) @@ -126,39 +128,34 @@ func CreateButton(buttonType ButtonType, text string) Button { result.height += h } - result.normalImage, _ = ebiten.NewImage(int(result.width), int(result.height), ebiten.FilterNearest) - normalSurface := d2surface.CreateSurface(result.normalImage) + _, result.normalSurface = d2render.NewSurface(int(result.width), int(result.height), d2common.FilterNearest) _, fontHeight := font.GetTextMetrics(text) textY := int((result.height/2)-(int(fontHeight)/2)) + buttonLayout.TextOffset buttonSprite.SetPosition(0, 0) buttonSprite.SetBlend(true) - buttonSprite.RenderSegmented(normalSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame) - font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, normalSurface) + buttonSprite.RenderSegmented(result.normalSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame) + font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, result.normalSurface) if buttonLayout.AllowFrameChange { if totalButtonTypes > 1 { - result.pressedImage, _ = ebiten.NewImage(int(result.width), int(result.height), ebiten.FilterNearest) - pressedSurface := d2surface.CreateSurface(result.pressedImage) - buttonSprite.RenderSegmented(pressedSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+1) - font.Render(-2, textY+2, text, color.RGBA{100, 100, 100, 255}, pressedSurface) + _, result.pressedSurface = d2render.NewSurface(int(result.width), int(result.height), d2common.FilterNearest) + buttonSprite.RenderSegmented(result.pressedSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+1) + font.Render(-2, textY+2, text, color.RGBA{100, 100, 100, 255}, result.pressedSurface) } if totalButtonTypes > 2 { - result.toggledImage, _ = ebiten.NewImage(int(result.width), int(result.height), ebiten.FilterNearest) - toggledSurface := d2surface.CreateSurface(result.toggledImage) - buttonSprite.RenderSegmented(toggledSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+2) - font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, toggledSurface) + _, result.toggledSurface = d2render.NewSurface(int(result.width), int(result.height), d2common.FilterNearest) + buttonSprite.RenderSegmented(result.toggledSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+2) + font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, result.toggledSurface) } if totalButtonTypes > 3 { - result.pressedToggledImage, _ = ebiten.NewImage(int(result.width), int(result.height), ebiten.FilterNearest) - pressedToggledSurface := d2surface.CreateSurface(result.pressedToggledImage) - buttonSprite.RenderSegmented(pressedToggledSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+3) - font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, pressedToggledSurface) + _, result.pressedToggledSurface = d2render.NewSurface(int(result.width), int(result.height), d2common.FilterNearest) + buttonSprite.RenderSegmented(result.pressedToggledSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame+3) + font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, result.pressedToggledSurface) } if buttonLayout.DisabledFrame != -1 { - result.disabledImage, _ = ebiten.NewImage(int(result.width), int(result.height), ebiten.FilterNearest) - disabledSurface := d2surface.CreateSurface(result.disabledImage) - buttonSprite.RenderSegmented(disabledSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.DisabledFrame) - font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, disabledSurface) + _, result.disabledSurface = d2render.NewSurface(int(result.width), int(result.height), d2common.FilterNearest) + buttonSprite.RenderSegmented(result.disabledSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.DisabledFrame) + font.Render(0, textY, text, color.RGBA{100, 100, 100, 255}, result.disabledSurface) } } return result @@ -178,24 +175,24 @@ func (v *Button) Activate() { } // Render renders the button -func (v *Button) Render(target *d2surface.Surface) { - target.PushCompositeMode(ebiten.CompositeModeSourceAtop) - target.PushFilter(ebiten.FilterNearest) +func (v *Button) Render(target d2common.Surface) { + target.PushCompositeMode(d2common.CompositeModeSourceAtop) + target.PushFilter(d2common.FilterNearest) target.PushTranslation(v.x, v.y) defer target.PopN(3) if !v.enabled { target.PushColor(color.RGBA{128, 128, 128, 195}) defer target.Pop() - target.Render(v.disabledImage) + target.Render(v.disabledSurface) } else if v.toggled && v.pressed { - target.Render(v.pressedToggledImage) + target.Render(v.pressedToggledSurface) } else if v.pressed { - target.Render(v.pressedImage) + target.Render(v.pressedSurface) } else if v.toggled { - target.Render(v.toggledImage) + target.Render(v.toggledSurface) } else { - target.Render(v.normalImage) + target.Render(v.normalSurface) } } diff --git a/d2render/d2ui/checkbox.go b/d2core/d2ui/checkbox.go similarity index 65% rename from d2render/d2ui/checkbox.go rename to d2core/d2ui/checkbox.go index 5d0ccb42..7335d6e4 100644 --- a/d2render/d2ui/checkbox.go +++ b/d2core/d2ui/checkbox.go @@ -1,10 +1,10 @@ package d2ui import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" - "github.com/hajimehoshi/ebiten" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" ) type Checkbox struct { @@ -12,8 +12,8 @@ type Checkbox struct { checkState bool visible bool width, height int - Image *ebiten.Image - checkedImage *ebiten.Image + Image d2common.Surface + checkedImage d2common.Surface onClick func() enabled bool } @@ -26,24 +26,24 @@ func CreateCheckbox(checkState bool) Checkbox { height: 0, enabled: true, } - checkboxSprite, _ := d2render.LoadSprite(d2resource.Checkbox, d2resource.PaletteFechar) + + animation, _ := d2assetmanager.LoadAnimation(d2resource.Checkbox, d2resource.PaletteFechar) + checkboxSprite, _ := d2render.LoadSprite(animation) result.width, result.height, _ = checkboxSprite.GetFrameSize(0) checkboxSprite.SetPosition(0, 0) - result.Image, _ = ebiten.NewImage(int(result.width), int(result.height), ebiten.FilterNearest) - surface := d2surface.CreateSurface(result.Image) - checkboxSprite.RenderSegmented(surface, 1, 1, 0) + _, result.Image = d2render.NewSurface(int(result.width), int(result.height), d2common.FilterNearest) + checkboxSprite.RenderSegmented(result.Image, 1, 1, 0) - result.checkedImage, _ = ebiten.NewImage(int(result.width), int(result.height), ebiten.FilterNearest) - checkedSurface := d2surface.CreateSurface(result.checkedImage) - checkboxSprite.RenderSegmented(checkedSurface, 1, 1, 1) + _, result.checkedImage = d2render.NewSurface(int(result.width), int(result.height), d2common.FilterNearest) + checkboxSprite.RenderSegmented(result.checkedImage, 1, 1, 1) return result } -func (v *Checkbox) Render(target *d2surface.Surface) { - target.PushCompositeMode(ebiten.CompositeModeSourceAtop) +func (v *Checkbox) Render(target d2common.Surface) { + target.PushCompositeMode(d2common.CompositeModeSourceAtop) target.PushTranslation(v.x, v.y) - target.PushFilter(ebiten.FilterNearest) + target.PushFilter(d2common.FilterNearest) defer target.PopN(3) if v.checkState { diff --git a/d2core/d2ui/d2ui.go b/d2core/d2ui/d2ui.go new file mode 100644 index 00000000..411d3675 --- /dev/null +++ b/d2core/d2ui/d2ui.go @@ -0,0 +1,150 @@ +package d2ui + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" + "github.com/hajimehoshi/ebiten" +) + +// CursorButton represents a mouse button +type CursorButton uint8 + +const ( + // CursorButtonLeft represents the left mouse button + CursorButtonLeft CursorButton = 1 + // CursorButtonRight represents the right mouse button + CursorButtonRight CursorButton = 2 +) + +var widgets []Widget +var cursorSprite *d2render.Sprite +var cursorButtons CursorButton +var pressedIndex int +var CursorX int +var CursorY int +var clickSfx d2interface.SoundEffect +var waitForLeftMouseUp bool + +func Initialize(curSprite *d2render.Sprite) { + cursorSprite = curSprite + pressedIndex = -1 + clickSfx, _ = d2audio.LoadSoundEffect(d2resource.SFXButtonClick) + waitForLeftMouseUp = false +} + +// Reset resets the state of the UI manager. Typically called for new scenes +func Reset() { + widgets = make([]Widget, 0) + pressedIndex = -1 + waitForLeftMouseUp = true +} + +// AddWidget adds a widget to the UI manager +func AddWidget(widget Widget) { + widgets = append(widgets, widget) +} + +func WaitForMouseRelease() { + waitForLeftMouseUp = true +} + +// Render renders all of the UI elements +func Render(target d2common.Surface) { + for _, widget := range widgets { + if widget.GetVisible() { + widget.Render(target) + } + } + + cx, cy := ebiten.CursorPosition() + cursorSprite.SetPosition(cx, cy) + cursorSprite.Render(target) +} + +// Update updates all of the UI elements +func Advance(elapsed float64) { + for _, widget := range widgets { + if widget.GetVisible() { + widget.Advance(elapsed) + } + } + + cursorButtons = 0 + if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { + if !waitForLeftMouseUp { + cursorButtons |= CursorButtonLeft + } + } else { + if waitForLeftMouseUp { + waitForLeftMouseUp = false + } + } + if ebiten.IsMouseButtonPressed(ebiten.MouseButtonRight) { + cursorButtons |= CursorButtonRight + } + CursorX, CursorY = ebiten.CursorPosition() + if CursorButtonPressed(CursorButtonLeft) { + found := false + for i, widget := range widgets { + if !widget.GetVisible() || !widget.GetEnabled() { + continue + } + wx, wy := widget.GetPosition() + ww, wh := widget.GetSize() + if CursorX >= wx && CursorX <= wx+int(ww) && CursorY >= wy && CursorY <= wy+int(wh) { + widget.SetPressed(true) + if pressedIndex == -1 { + found = true + pressedIndex = i + clickSfx.Play() + } else if pressedIndex > -1 && pressedIndex != i { + widgets[i].SetPressed(false) + } else { + found = true + } + } else { + widget.SetPressed(false) + } + } + if !found { + if pressedIndex > -1 { + widgets[pressedIndex].SetPressed(false) + } else { + pressedIndex = -2 + } + } + } else { + if pressedIndex > -1 { + widget := widgets[pressedIndex] + wx, wy := widget.GetPosition() + ww, wh := widget.GetSize() + if CursorX >= wx && CursorX <= wx+int(ww) && CursorY >= wy && CursorY <= wy+int(wh) { + widget.Activate() + } + } else { + for _, widget := range widgets { + if !widget.GetVisible() || !widget.GetEnabled() { + continue + } + widget.SetPressed(false) + } + } + pressedIndex = -1 + } +} + +// CursorButtonPressed determines if the specified button has been pressed +func CursorButtonPressed(button CursorButton) bool { + return cursorButtons&button > 0 +} + +func KeyPressed(key ebiten.Key) bool { + return ebiten.IsKeyPressed(key) +} + +func GetCursorSprite() *d2render.Sprite { + return cursorSprite +} diff --git a/d2corecommon/d2coreinterface/drawable.go b/d2core/d2ui/drawable.go similarity index 65% rename from d2corecommon/d2coreinterface/drawable.go rename to d2core/d2ui/drawable.go index fdd5cb56..06b35931 100644 --- a/d2corecommon/d2coreinterface/drawable.go +++ b/d2core/d2ui/drawable.go @@ -1,10 +1,12 @@ -package d2coreinterface +package d2ui -import "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common" +) // Drawable represents an instance that can be drawn type Drawable interface { - Render(target *d2surface.Surface) + Render(target d2common.Surface) Advance(elapsed float64) GetSize() (width, height int) SetPosition(x, y int) diff --git a/d2render/d2ui/font.go b/d2core/d2ui/font.go similarity index 88% rename from d2render/d2ui/font.go rename to d2core/d2ui/font.go index 6661b916..19825c29 100644 --- a/d2render/d2ui/font.go +++ b/d2core/d2ui/font.go @@ -4,11 +4,13 @@ import ( "image/color" "strings" - "github.com/OpenDiablo2/OpenDiablo2/d2helper" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" - "github.com/OpenDiablo2/OpenDiablo2/d2asset" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" + "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" "encoding/binary" @@ -48,9 +50,10 @@ func CreateFont(font string, palettePath string) *Font { metrics: make(map[uint16]FontSize), } // bug: performance issue when using CJK fonts, because ten thousand frames will be rendered PER font - result.fontSprite, _ = d2render.LoadSprite(font+".dc6", palettePath) + animation, _ := d2assetmanager.LoadAnimation(font+".dc6", palettePath) + result.fontSprite, _ = d2render.LoadSprite(animation) woo := "Woo!\x01" - fontData, err := d2asset.LoadFile(font + ".tbl") + fontData, err := d2assetmanager.LoadFile(font + ".tbl") if err != nil { panic(err) } @@ -105,7 +108,7 @@ func (v *Font) GetTextMetrics(text string) (width, height int) { } // Render draws the font on the target surface -func (v *Font) Render(x, y int, text string, color color.Color, target *d2surface.Surface) { +func (v *Font) Render(x, y int, text string, color color.Color, target d2common.Surface) { v.fontSprite.SetColorMod(color) v.fontSprite.SetBlend(false) diff --git a/d2render/d2ui/label.go b/d2core/d2ui/label.go similarity index 82% rename from d2render/d2ui/label.go rename to d2core/d2ui/label.go index 34fe81fd..b1a8068c 100644 --- a/d2render/d2ui/label.go +++ b/d2core/d2ui/label.go @@ -3,8 +3,9 @@ package d2ui import ( "image/color" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" - "github.com/hajimehoshi/ebiten" + "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" ) // LabelAlignment represents a label's alignment @@ -28,7 +29,7 @@ type Label struct { Height int Alignment LabelAlignment font *Font - imageData *ebiten.Image + imageData d2common.Surface Color color.Color } @@ -43,7 +44,7 @@ func CreateLabel(fontPath, palettePath string) Label { } // Render draws the label on the screen -func (v *Label) Render(target *d2surface.Surface) { +func (v *Label) Render(target d2common.Surface) { if len(v.text) == 0 { return } @@ -56,8 +57,8 @@ func (v *Label) Render(target *d2surface.Surface) { x, y = v.X-int(v.Width), v.Y } - target.PushFilter(ebiten.FilterNearest) - target.PushCompositeMode(ebiten.CompositeModeSourceAtop) + target.PushFilter(d2common.FilterNearest) + target.PushCompositeMode(d2common.CompositeModeSourceAtop) target.PushTranslation(x, y) defer target.PopN(3) @@ -81,8 +82,8 @@ func (v *Label) cacheImage() { width, height := v.font.GetTextMetrics(v.text) v.Width = width v.Height = height - v.imageData, _ = ebiten.NewImage(int(width), int(height), ebiten.FilterNearest) - surface := d2surface.CreateSurface(v.imageData) + _, v.imageData = d2render.NewSurface(int(width), int(height), d2common.FilterNearest) + _, surface := d2render.CreateSurface(v.imageData) v.font.Render(0, 0, v.text, v.Color, surface) } diff --git a/d2render/d2ui/scrollbar.go b/d2core/d2ui/scrollbar.go similarity index 89% rename from d2render/d2ui/scrollbar.go rename to d2core/d2ui/scrollbar.go index 01f8a0c1..8cb69a6e 100644 --- a/d2render/d2ui/scrollbar.go +++ b/d2core/d2ui/scrollbar.go @@ -1,9 +1,10 @@ package d2ui import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" "github.com/hajimehoshi/ebiten" ) @@ -19,7 +20,8 @@ type Scrollbar struct { } func CreateScrollbar(x, y, height int) Scrollbar { - scrollbarSprite, _ := d2render.LoadSprite(d2resource.Scrollbar, d2resource.PaletteSky) + animation, _ := d2assetmanager.LoadAnimation(d2resource.Scrollbar, d2resource.PaletteSky) + scrollbarSprite, _ := d2render.LoadSprite(animation) result := Scrollbar{ visible: true, enabled: true, @@ -73,7 +75,7 @@ func (v *Scrollbar) GetLastDirChange() int { return v.lastDirChange } -func (v *Scrollbar) Render(target *d2surface.Surface) { +func (v *Scrollbar) Render(target d2common.Surface) { if !v.visible || v.maxOffset == 0 { return } diff --git a/d2render/d2ui/textbox.go b/d2core/d2ui/textbox.go similarity index 88% rename from d2render/d2ui/textbox.go rename to d2core/d2ui/textbox.go index ca660221..376da86c 100644 --- a/d2render/d2ui/textbox.go +++ b/d2core/d2ui/textbox.go @@ -4,11 +4,14 @@ import ( "strings" "time" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/hajimehoshi/ebiten/inpututil" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" "github.com/hajimehoshi/ebiten" ) @@ -25,7 +28,8 @@ type TextBox struct { } func CreateTextbox() TextBox { - bgSprite, _ := d2render.LoadSprite(d2resource.TextBox2, d2resource.PaletteUnits) + animation, _ := d2assetmanager.LoadAnimation(d2resource.TextBox2, d2resource.PaletteUnits) + bgSprite, _ := d2render.LoadSprite(animation) result := TextBox{ bgSprite: bgSprite, textLabel: CreateLabel(d2resource.FontFormal11, d2resource.PaletteUnits), @@ -52,7 +56,7 @@ func repeatingKeyPressed(key ebiten.Key) bool { return false } -func (v *TextBox) Render(target *d2surface.Surface) { +func (v *TextBox) Render(target d2common.Surface) { if !v.visible { return } diff --git a/d2render/d2ui/widget.go b/d2core/d2ui/widget.go similarity index 67% rename from d2render/d2ui/widget.go rename to d2core/d2ui/widget.go index 0e63bdb9..3b53c380 100644 --- a/d2render/d2ui/widget.go +++ b/d2core/d2ui/widget.go @@ -1,12 +1,8 @@ package d2ui -import ( - "github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface" -) - // Widget defines an object that is a UI widget type Widget interface { - d2coreinterface.Drawable + Drawable GetEnabled() bool SetEnabled(enabled bool) SetPressed(pressed bool) diff --git a/d2core/engine.go b/d2core/engine.go deleted file mode 100644 index bcc20042..00000000 --- a/d2core/engine.go +++ /dev/null @@ -1,247 +0,0 @@ -package d2core - -import ( - "log" - "math" - "runtime" - "strconv" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2helper" - - "github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface" - "github.com/OpenDiablo2/OpenDiablo2/d2input" - "github.com/OpenDiablo2/OpenDiablo2/d2term" - - "github.com/OpenDiablo2/OpenDiablo2/d2render" - - "github.com/OpenDiablo2/OpenDiablo2/d2data" - - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2datadict" - - "github.com/OpenDiablo2/OpenDiablo2/d2audio" - - "github.com/OpenDiablo2/OpenDiablo2/d2common" - - "github.com/OpenDiablo2/OpenDiablo2/d2asset" - "github.com/OpenDiablo2/OpenDiablo2/d2corecommon" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" - - "github.com/hajimehoshi/ebiten" -) - -// Engine is the core OpenDiablo2 engine -type Engine struct { - Settings *d2corecommon.Configuration // Engine configuration settings from json file - CheckedPatch map[string]bool // First time we check a file, we'll check if it's in the patch. This notes that we've already checked that. - LoadingSprite *d2render.Sprite // The sprite shown when loading stuff - loadingProgress float64 // LoadingProcess is a range between 0.0 and 1.0. If set, loading screen displays. - loadingIndex int // Determines which load function is currently being called - thingsToLoad []func() // The load functions for the next scene - stepLoadingSize float64 // The size for each loading step - CurrentScene d2coreinterface.Scene // The current scene being rendered - UIManager *d2ui.Manager // The UI manager - SoundManager *d2audio.Manager // The sound manager - nextScene d2coreinterface.Scene // The next scene to be loaded at the end of the game loop - lastTime float64 // Last time we updated the scene - showFPS bool - timeScale float64 -} - -// CreateEngine creates and instance of the OpenDiablo2 engine -func CreateEngine() *Engine { - result := &Engine{ - lastTime: d2helper.Now(), - timeScale: 1.0, - } - - result.Settings = d2corecommon.LoadConfiguration() - if err := result.Settings.Save(); err != nil { - log.Printf("could not load settings: %v", err) - } - - d2asset.Initialize(result.Settings) - d2resource.LanguageCode = result.Settings.Language - d2datadict.LoadPalettes(nil, result) - d2common.LoadTextDictionary(result) - d2datadict.LoadLevelTypes(result) - d2datadict.LoadLevelPresets(result) - d2datadict.LoadLevelWarps(result) - d2datadict.LoadObjectTypes(result) - d2datadict.LoadObjects(result) - d2datadict.LoadWeapons(result) - d2datadict.LoadArmors(result) - d2datadict.LoadMiscItems(result) - d2datadict.LoadUniqueItems(result) - d2datadict.LoadMissiles(result) - d2datadict.LoadSounds(result) - d2data.LoadAnimationData(result) - d2datadict.LoadMonStats(result) - LoadHeroObjects() - result.SoundManager = d2audio.CreateManager() - result.SoundManager.SetVolumes(result.Settings.BgmVolume, result.Settings.SfxVolume) - result.UIManager = d2ui.CreateManager(*result.SoundManager) - result.LoadingSprite, _ = d2render.LoadSprite(d2resource.LoadingScreen, d2resource.PaletteLoading) - loadingSpriteSizeX, loadingSpriteSizeY := result.LoadingSprite.GetCurrentFrameSize() - result.LoadingSprite.SetPosition(int(400-(loadingSpriteSizeX/2)), int(300+(loadingSpriteSizeY/2))) - - d2term.BindAction("timescale", "set scalar for elapsed time", func(scale float64) { - if scale <= 0 { - d2term.OutputError("invalid time scale value") - } else { - d2term.OutputInfo("timescale changed from %f to %f", result.timeScale, scale) - result.timeScale = scale - } - }) - - d2input.BindHandler(result) - - return result -} - -func (v *Engine) LoadFile(fileName string) []byte { - data, _ := d2asset.LoadFile(fileName) - return data -} - -// IsLoading returns true if the engine is currently in a loading state -func (v Engine) IsLoading() bool { - return v.loadingProgress < 1.0 -} - -// updateScene handles the scene maintenance for the engine -func (v *Engine) updateScene() { - if v.nextScene == nil { - if v.thingsToLoad != nil { - if v.loadingIndex < len(v.thingsToLoad) { - v.thingsToLoad[v.loadingIndex]() - v.loadingIndex++ - if v.loadingIndex < len(v.thingsToLoad) { - v.StepLoading() - } else { - v.FinishLoading() - v.thingsToLoad = nil - } - return - } - } - return - } - if v.CurrentScene != nil { - v.CurrentScene.Unload() - runtime.GC() - } - v.CurrentScene = v.nextScene - v.nextScene = nil - v.UIManager.Reset() - v.thingsToLoad = v.CurrentScene.Load() - v.loadingIndex = 0 - v.SetLoadingStepSize(1.0 / float64(len(v.thingsToLoad))) - v.ResetLoading() -} - -func (e *Engine) OnKeyDown(event d2input.KeyEvent) bool { - if event.Key == d2input.KeyEnter && event.KeyMod == d2input.KeyModAlt { - ebiten.SetFullscreen(!ebiten.IsFullscreen()) - return true - } - - if event.Key == d2input.KeyF6 { - e.showFPS = !e.showFPS - return true - } - - if event.Key == d2input.KeyF8 { - ebiten.SetVsyncEnabled(!ebiten.IsVsyncEnabled()) - return true - } - - return false -} - -// Advance updates the internal state of the engine -func (v *Engine) Advance() { - v.updateScene() - if v.CurrentScene == nil { - log.Fatal("no scene loaded") - } - - if v.IsLoading() { - return - } - - currentTime := d2helper.Now() - deltaTime := (currentTime - v.lastTime) * v.timeScale - v.lastTime = currentTime - - v.CurrentScene.Advance(deltaTime) - v.UIManager.Advance(deltaTime) - - d2term.Advance(deltaTime) - d2input.Advance(deltaTime) -} - -// Draw draws the game -func (v Engine) Render(target *d2surface.Surface) { - if v.loadingProgress < 1.0 { - v.LoadingSprite.SetCurrentFrame(int(d2helper.Max(0, d2helper.Min(uint32(v.LoadingSprite.GetFrameCount()-1), uint32(float64(v.LoadingSprite.GetFrameCount()-1)*v.loadingProgress))))) - v.LoadingSprite.Render(target) - } else { - if v.CurrentScene == nil { - log.Fatal("no scene loaded") - } - v.CurrentScene.Render(target) - v.UIManager.Render(target) - } - if v.showFPS { - target.PushTranslation(5, 565) - target.DrawText("vsync:" + strconv.FormatBool(ebiten.IsVsyncEnabled()) + "\nFPS:" + strconv.Itoa(int(ebiten.CurrentFPS()))) - target.Pop() - - cx, cy := ebiten.CursorPosition() - - var m runtime.MemStats - runtime.ReadMemStats(&m) - - target.PushTranslation(680, 0) - target.DrawText("Alloc " + strconv.FormatInt(int64(m.Alloc)/1024/1024, 10)) - target.PushTranslation(0, 16) - target.DrawText("Pause " + strconv.FormatInt(int64(m.PauseTotalNs/1024/1024), 10)) - target.PushTranslation(0, 16) - target.DrawText("HeapSys " + strconv.FormatInt(int64(m.HeapSys/1024/1024), 10)) - target.PushTranslation(0, 16) - target.DrawText("NumGC " + strconv.FormatInt(int64(m.NumGC), 10)) - target.PushTranslation(0, 16) - target.DrawText("Coords " + strconv.FormatInt(int64(cx), 10) + "," + strconv.FormatInt(int64(cy), 10)) - target.PopN(5) - } - - d2term.Render(target) -} - -// SetNextScene tells the engine what scene to load on the next update cycle -func (v *Engine) SetNextScene(nextScene d2coreinterface.Scene) { - v.nextScene = nextScene -} - -// SetLoadingStepSize sets the size of the loading step -func (v *Engine) SetLoadingStepSize(size float64) { - v.stepLoadingSize = size -} - -// ResetLoading resets the loading progress -func (v *Engine) ResetLoading() { - v.loadingProgress = 0.0 -} - -// StepLoading increments the loading progress -func (v *Engine) StepLoading() { - v.loadingProgress = math.Min(1.0, v.loadingProgress+v.stepLoadingSize) -} - -// FinishLoading terminates the loading phase -func (v *Engine) FinishLoading() { - v.loadingProgress = 1.0 -} diff --git a/d2corecommon/d2coreinterface/scene_provider.go b/d2corecommon/d2coreinterface/scene_provider.go deleted file mode 100644 index d689d1fc..00000000 --- a/d2corecommon/d2coreinterface/scene_provider.go +++ /dev/null @@ -1,6 +0,0 @@ -package d2coreinterface - -// SceneProvider provides the ability to change scenes -type SceneProvider interface { - SetNextScene(nextScene Scene) -} diff --git a/d2data/d2datadict/armor.go b/d2data/d2datadict/armor.go deleted file mode 100644 index d74c7827..00000000 --- a/d2data/d2datadict/armor.go +++ /dev/null @@ -1,18 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" -) - -var Armors map[string]*ItemCommonRecord - -func LoadArmors(fileProvider d2interface.FileProvider) { - Armors = *LoadCommonItems(fileProvider, d2resource.Armor, d2enum.InventoryItemTypeArmor) - log.Printf("Loaded %d armors", len(Armors)) -} diff --git a/d2data/d2datadict/misc.go b/d2data/d2datadict/misc.go deleted file mode 100644 index 2540605a..00000000 --- a/d2data/d2datadict/misc.go +++ /dev/null @@ -1,18 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" -) - -var MiscItems map[string]*ItemCommonRecord - -func LoadMiscItems(fileProvider d2interface.FileProvider) { - MiscItems = *LoadCommonItems(fileProvider, d2resource.Misc, d2enum.InventoryItemTypeItem) - log.Printf("Loaded %d misc items", len(MiscItems)) -} diff --git a/d2data/d2datadict/monstats.go b/d2data/d2datadict/monstats.go deleted file mode 100644 index 47f5be6f..00000000 --- a/d2data/d2datadict/monstats.go +++ /dev/null @@ -1,13 +0,0 @@ -package d2datadict - -import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" -) - -var MonStatsDictionary *d2common.DataDictionary - -func LoadMonStats(fileProvider d2interface.FileProvider) { - MonStatsDictionary = d2common.LoadDataDictionary(string(fileProvider.LoadFile(d2resource.MonStats))) -} diff --git a/d2data/d2datadict/palette.go b/d2data/d2datadict/palette.go deleted file mode 100644 index 499fceff..00000000 --- a/d2data/d2datadict/palette.go +++ /dev/null @@ -1,50 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" -) - -// PaletteRGB represents a color in a palette -type PaletteRGB struct { - R, G, B uint8 -} - -// PaletteType represents a palette -type PaletteRec struct { - Name d2enum.PaletteType - Colors [256]PaletteRGB -} - -var Palettes map[d2enum.PaletteType]PaletteRec - -// CreatePalette creates a palette -func CreatePalette(name d2enum.PaletteType, data []byte) PaletteRec { - result := PaletteRec{Name: name} - - for i := 0; i <= 255; i++ { - result.Colors[i] = PaletteRGB{ - B: data[i*3], - G: data[(i*3)+1], - R: data[(i*3)+2], - } - } - return result -} - -func LoadPalettes(mpqFiles map[string]string, fileProvider d2interface.FileProvider) { - Palettes = make(map[d2enum.PaletteType]PaletteRec) - for _, pal := range []string{ - "act1", "act2", "act3", "act4", "act5", "endgame", "endgame2", "fechar", "loading", - "menu0", "menu1", "menu2", "menu3", "menu4", "sky", "static", "trademark", "units", - } { - filePath := `data\global\palette\` + pal + `\pal.dat` - paletteName := d2enum.PaletteType(pal) - palette := CreatePalette(paletteName, fileProvider.LoadFile(filePath)) - Palettes[paletteName] = palette - } - log.Printf("Loaded %d palettes", len(Palettes)) -} diff --git a/d2data/d2datadict/weapons.go b/d2data/d2datadict/weapons.go deleted file mode 100644 index 22fc08ac..00000000 --- a/d2data/d2datadict/weapons.go +++ /dev/null @@ -1,18 +0,0 @@ -package d2datadict - -import ( - "log" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" -) - -var Weapons map[string]*ItemCommonRecord - -func LoadWeapons(fileProvider d2interface.FileProvider) { - Weapons = *LoadCommonItems(fileProvider, d2resource.Weapons, d2enum.InventoryItemTypeWeapon) - log.Printf("Loaded %d weapons", len(Weapons)) -} diff --git a/d2game/d2game.go b/d2game/d2game.go new file mode 100644 index 00000000..1e1c56b7 --- /dev/null +++ b/d2game/d2game.go @@ -0,0 +1,155 @@ +package d2game + +import ( + "log" + "runtime" + "strconv" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2scenemanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2term" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" +) + +var loadingSprite *d2render.Sprite // The sprite shown when loading stuff +var lastTime float64 // Last time we updated the scene +var showFPS bool +var timeScale float64 + +type bsForInputHanding struct { +} + +var bsHandler *bsForInputHanding + +func Initialize(loadingSpr *d2render.Sprite) error { + bsHandler = &bsForInputHanding{} + loadingSprite = loadingSpr + timeScale = 1.0 + lastTime = d2helper.Now() + d2input.BindHandler(bsHandler) + + return nil +} + +func Run(gitBranch string) error { + if err := d2render.Run(update, 800, 600, "OpenDiablo 2 ("+gitBranch+")"); err != nil { + log.Fatal(err) + } + return nil +} + +func SetTimeScale(scale float64) { + timeScale = scale +} + +func GetTimeScale() float64 { + return timeScale +} + +func (bs *bsForInputHanding) OnKeyDown(event d2input.KeyEvent) bool { + if event.Key == d2input.KeyEnter && event.KeyMod == d2input.KeyModAlt { + isFullScreen, _ := d2render.IsFullScreen() + d2render.SetFullScreen(!isFullScreen) + return true + } + + if event.Key == d2input.KeyF6 { + showFPS = !showFPS + return true + } + + if event.Key == d2input.KeyF8 { + enabled, _ := d2render.GetVSyncEnabled() + d2render.SetVSyncEnabled(!enabled) + return true + } + + return false +} + +// Advance updates the internal state of the engine +func Advance() { + d2scenemanager.UpdateScene() + if d2scenemanager.GetCurrentScene() == nil { + log.Fatal("no scene loaded") + } + + if d2scenemanager.IsLoading() { + return + } + + currentTime := d2helper.Now() + deltaTime := (currentTime - lastTime) * timeScale + lastTime = currentTime + + d2scenemanager.Advance(deltaTime) + d2ui.Advance(deltaTime) + d2term.Advance(deltaTime) + d2input.Advance(deltaTime) +} + +// Draw draws the game +func render(target d2common.Surface) { + if d2scenemanager.GetLoadingProgress() < 1.0 { + loadingSprite.SetCurrentFrame(int(d2helper.Max(0, + d2helper.Min(uint32(loadingSprite.GetFrameCount()-1), + uint32(float64(loadingSprite.GetFrameCount()-1)*d2scenemanager.GetLoadingProgress()))))) + loadingSprite.Render(target) + } else { + if d2scenemanager.GetCurrentScene() == nil { + log.Fatal("no scene loaded") + } + d2scenemanager.Render(target) + d2ui.Render(target) + } + if showFPS { + target.PushTranslation(5, 565) + vsyncEnabled, _ := d2render.GetVSyncEnabled() + fps, _ := d2render.CurrentFPS() + target.DrawText("vsync:" + strconv.FormatBool(vsyncEnabled) + "\nFPS:" + strconv.Itoa(int(fps))) + target.Pop() + + cx, cy, _ := d2render.GetCursorPos() + + var m runtime.MemStats + runtime.ReadMemStats(&m) + + target.PushTranslation(680, 0) + target.DrawText("Alloc " + strconv.FormatInt(int64(m.Alloc)/1024/1024, 10)) + target.PushTranslation(0, 16) + target.DrawText("Pause " + strconv.FormatInt(int64(m.PauseTotalNs/1024/1024), 10)) + target.PushTranslation(0, 16) + target.DrawText("HeapSys " + strconv.FormatInt(int64(m.HeapSys/1024/1024), 10)) + target.PushTranslation(0, 16) + target.DrawText("NumGC " + strconv.FormatInt(int64(m.NumGC), 10)) + target.PushTranslation(0, 16) + target.DrawText("Coords " + strconv.FormatInt(int64(cx), 10) + "," + strconv.FormatInt(int64(cy), 10)) + target.PopN(5) + } + + d2term.Render(target) +} + +func update(screen d2common.Surface) error { + Advance() + err, drawingSkipped := d2render.IsDrawingSkipped() + if err != nil { + return err + } + if !drawingSkipped { + _, surface := d2render.CreateSurface(screen) + render(surface) + if surface.GetDepth() > 0 { + panic("detected surface stack leak") + } + } + + return nil +} diff --git a/d2player/game_controls.go b/d2game/d2player/game_controls.go similarity index 73% rename from d2player/game_controls.go rename to d2game/d2player/game_controls.go index 0640c5bb..2bc35dad 100644 --- a/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -1,12 +1,13 @@ package d2player import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2core" - "github.com/OpenDiablo2/OpenDiablo2/d2input" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render/d2mapengine" ) type Panel interface { @@ -17,7 +18,7 @@ type Panel interface { } type GameControls struct { - hero *d2core.Hero + hero *d2hero.Hero mapEngine *d2mapengine.MapEngine inventory *Inventory @@ -28,7 +29,7 @@ type GameControls struct { skillIcon *d2render.Sprite } -func NewGameControls(hero *d2core.Hero, mapEngine *d2mapengine.MapEngine) *GameControls { +func NewGameControls(hero *d2hero.Hero, mapEngine *d2mapengine.MapEngine) *GameControls { return &GameControls{ hero: hero, mapEngine: mapEngine, @@ -56,15 +57,23 @@ func (g *GameControls) OnMouseButtonDown(event d2input.MouseEvent) bool { } func (g *GameControls) Load() { - g.globeSprite, _ = d2render.LoadSprite(d2resource.GameGlobeOverlap, d2resource.PaletteSky) - g.mainPanel, _ = d2render.LoadSprite(d2resource.GamePanels, d2resource.PaletteSky) - g.menuButton, _ = d2render.LoadSprite(d2resource.MenuButton, d2resource.PaletteSky) - g.skillIcon, _ = d2render.LoadSprite(d2resource.GenericSkills, d2resource.PaletteSky) + animation, _ := d2assetmanager.LoadAnimation(d2resource.GameGlobeOverlap, d2resource.PaletteSky) + g.globeSprite, _ = d2render.LoadSprite(animation) + + animation, _ = d2assetmanager.LoadAnimation(d2resource.GamePanels, d2resource.PaletteSky) + g.mainPanel, _ = d2render.LoadSprite(animation) + + animation, _ = d2assetmanager.LoadAnimation(d2resource.MenuButton, d2resource.PaletteSky) + g.menuButton, _ = d2render.LoadSprite(animation) + + animation, _ = d2assetmanager.LoadAnimation(d2resource.GenericSkills, d2resource.PaletteSky) + g.skillIcon, _ = d2render.LoadSprite(animation) + g.inventory.Load() } // TODO: consider caching the panels to single image that is reused. -func (g *GameControls) Render(target *d2surface.Surface) { +func (g *GameControls) Render(target d2common.Surface) { g.inventory.Render(target) width, height := target.GetSize() diff --git a/d2player/inventory.go b/d2game/d2player/inventory.go similarity index 74% rename from d2player/inventory.go rename to d2game/d2player/inventory.go index d35a8907..ff081a54 100644 --- a/d2player/inventory.go +++ b/d2game/d2player/inventory.go @@ -1,10 +1,11 @@ package d2player import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2core" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" ) type Inventory struct { @@ -43,20 +44,23 @@ func (g *Inventory) Close() { } func (g *Inventory) Load() { - g.frame, _ = d2render.LoadSprite(d2resource.Frame, d2resource.PaletteSky) - g.panel, _ = d2render.LoadSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky) + animation, _ := d2assetmanager.LoadAnimation(d2resource.Frame, d2resource.PaletteSky) + g.frame, _ = d2render.LoadSprite(animation) + + animation, _ = d2assetmanager.LoadAnimation(d2resource.InventoryCharacterPanel, d2resource.PaletteSky) + g.panel, _ = d2render.LoadSprite(animation) items := []InventoryItem{ - d2core.GetWeaponItemByCode("wnd"), - d2core.GetWeaponItemByCode("sst"), - d2core.GetWeaponItemByCode("jav"), - d2core.GetArmorItemByCode("buc"), - d2core.GetWeaponItemByCode("clb"), + d2hero.GetWeaponItemByCode("wnd"), + d2hero.GetWeaponItemByCode("sst"), + d2hero.GetWeaponItemByCode("jav"), + d2hero.GetArmorItemByCode("buc"), + d2hero.GetWeaponItemByCode("clb"), } g.grid.Add(items...) } -func (g *Inventory) Render(target *d2surface.Surface) { +func (g *Inventory) Render(target d2common.Surface) { if !g.isOpen { return } diff --git a/d2player/inventory_grid.go b/d2game/d2player/inventory_grid.go similarity index 93% rename from d2player/inventory_grid.go rename to d2game/d2player/inventory_grid.go index 67a69fb4..016644fb 100644 --- a/d2player/inventory_grid.go +++ b/d2game/d2player/inventory_grid.go @@ -3,10 +3,14 @@ package d2player import ( "errors" "fmt" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" ) type InventoryItem interface { @@ -89,7 +93,6 @@ func (g *ItemGrid) Add(items ...InventoryItem) (int, error) { // Load reads the inventory sprites for items into local cache for rendering. func (g *ItemGrid) Load(items ...InventoryItem) { var itemSprite *d2render.Sprite - var err error for _, item := range items { if _, exists := g.sprites[item.ItemCode()]; exists { @@ -98,7 +101,7 @@ func (g *ItemGrid) Load(items ...InventoryItem) { } // TODO: Put the pattern into D2Shared - itemSprite, err = d2render.LoadSprite( + animation, err := d2assetmanager.LoadAnimation( fmt.Sprintf("/data/global/items/inv%s.dc6", item.ItemCode()), d2resource.PaletteSky, ) @@ -106,6 +109,7 @@ func (g *ItemGrid) Load(items ...InventoryItem) { log.Printf("failed to load sprite for item (%s): %v", item.ItemCode(), err) continue } + itemSprite, err = d2render.LoadSprite(animation) g.sprites[item.ItemCode()] = itemSprite } @@ -180,7 +184,7 @@ func (g *ItemGrid) Remove(item InventoryItem) { g.items = g.items[:n] } -func (g *ItemGrid) Render(target *d2surface.Surface) { +func (g *ItemGrid) Render(target d2common.Surface) { for _, item := range g.items { if item == nil { continue diff --git a/d2player/inventory_grid_test.go b/d2game/d2player/inventory_grid_test.go similarity index 100% rename from d2player/inventory_grid_test.go rename to d2game/d2player/inventory_grid_test.go diff --git a/d2game/d2scene/blizzard_intro.go b/d2game/d2scene/blizzard_intro.go new file mode 100644 index 00000000..65da605f --- /dev/null +++ b/d2game/d2scene/blizzard_intro.go @@ -0,0 +1,41 @@ +package d2scene + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2video" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + "github.com/hajimehoshi/ebiten" +) + +type BlizzardIntro struct { + videoDecoder *d2video.BinkDecoder +} + +func CreateBlizzardIntro() *BlizzardIntro { + result := &BlizzardIntro{} + + return result +} + +func (v *BlizzardIntro) Load() []func() { + return []func(){ + func() { + videoBytes, err := d2assetmanager.LoadFile("/data/local/video/BlizNorth640x480.bik") + if err != nil { + panic(err) + } + v.videoDecoder = d2video.CreateBinkDecoder(videoBytes) + }, + } +} + +func (v *BlizzardIntro) Unload() { + +} + +func (v *BlizzardIntro) Render(screen *ebiten.Image) { + +} + +func (v *BlizzardIntro) Update(tickTime float64) { + +} diff --git a/d2core/d2scene/character_select.go b/d2game/d2scene/character_select.go similarity index 81% rename from d2core/d2scene/character_select.go rename to d2game/d2scene/character_select.go index 66c567be..35963e90 100644 --- a/d2core/d2scene/character_select.go +++ b/d2game/d2scene/character_select.go @@ -6,23 +6,26 @@ import ( "os" "strings" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gamestate" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2scenemanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio" + "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2audio" + dh "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2core" - "github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface" - dh "github.com/OpenDiablo2/OpenDiablo2/d2helper" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" "github.com/hajimehoshi/ebiten" ) type CharacterSelect struct { - uiManager *d2ui.Manager - soundManager *d2audio.Manager - sceneProvider d2coreinterface.SceneProvider background *d2render.Sprite newCharButton d2ui.Button convertCharButton d2ui.Button @@ -39,73 +42,71 @@ type CharacterSelect struct { characterNameLabel [8]d2ui.Label characterStatsLabel [8]d2ui.Label characterExpLabel [8]d2ui.Label - characterImage [8]*d2core.Hero - gameStates []*d2core.GameState + characterImage [8]*d2hero.Hero + gameStates []*d2gamestate.GameState selectedCharacter int mouseButtonPressed bool showDeleteConfirmation bool } -func CreateCharacterSelect(sceneProvider d2coreinterface.SceneProvider, uiManager *d2ui.Manager, soundManager *d2audio.Manager) *CharacterSelect { +func CreateCharacterSelect() *CharacterSelect { result := &CharacterSelect{ selectedCharacter: -1, - uiManager: uiManager, - sceneProvider: sceneProvider, - soundManager: soundManager, } return result } func (v *CharacterSelect) Load() []func() { - v.soundManager.PlayBGM(d2resource.BGMTitle) + d2audio.PlayBGM(d2resource.BGMTitle) return []func(){ func() { - v.background, _ = d2render.LoadSprite(d2resource.CharacterSelectionBackground, d2resource.PaletteSky) + animation, _ := d2assetmanager.LoadAnimation(d2resource.CharacterSelectionBackground, d2resource.PaletteSky) + v.background, _ = d2render.LoadSprite(animation) v.background.SetPosition(0, 0) }, func() { v.newCharButton = d2ui.CreateButton(d2ui.ButtonTypeTall, dh.CombineStrings(dh.SplitIntoLinesWithMaxWidth(d2common.TranslateString("#831"), 15))) v.newCharButton.SetPosition(33, 468) v.newCharButton.OnActivated(func() { v.onNewCharButtonClicked() }) - v.uiManager.AddWidget(&v.newCharButton) + d2ui.AddWidget(&v.newCharButton) }, func() { v.convertCharButton = d2ui.CreateButton(d2ui.ButtonTypeTall, dh.CombineStrings(dh.SplitIntoLinesWithMaxWidth(d2common.TranslateString("#825"), 15))) v.convertCharButton.SetPosition(233, 468) v.convertCharButton.SetEnabled(false) - v.uiManager.AddWidget(&v.convertCharButton) + d2ui.AddWidget(&v.convertCharButton) }, func() { v.deleteCharButton = d2ui.CreateButton(d2ui.ButtonTypeTall, dh.CombineStrings(dh.SplitIntoLinesWithMaxWidth(d2common.TranslateString("#832"), 15))) v.deleteCharButton.OnActivated(func() { v.onDeleteCharButtonClicked() }) v.deleteCharButton.SetPosition(433, 468) - v.uiManager.AddWidget(&v.deleteCharButton) + d2ui.AddWidget(&v.deleteCharButton) }, func() { v.exitButton = d2ui.CreateButton(d2ui.ButtonTypeMedium, d2common.TranslateString("#970")) v.exitButton.SetPosition(33, 537) v.exitButton.OnActivated(func() { v.onExitButtonClicked() }) - v.uiManager.AddWidget(&v.exitButton) + d2ui.AddWidget(&v.exitButton) }, func() { v.deleteCharCancelButton = d2ui.CreateButton(d2ui.ButtonTypeOkCancel, d2common.TranslateString("#4231")) v.deleteCharCancelButton.SetPosition(282, 308) v.deleteCharCancelButton.SetVisible(false) v.deleteCharCancelButton.OnActivated(func() { v.onDeleteCharacterCancelClicked() }) - v.uiManager.AddWidget(&v.deleteCharCancelButton) + d2ui.AddWidget(&v.deleteCharCancelButton) }, func() { v.deleteCharOkButton = d2ui.CreateButton(d2ui.ButtonTypeOkCancel, d2common.TranslateString("#4227")) v.deleteCharOkButton.SetPosition(422, 308) v.deleteCharOkButton.SetVisible(false) v.deleteCharOkButton.OnActivated(func() { v.onDeleteCharacterConfirmClicked() }) - v.uiManager.AddWidget(&v.deleteCharOkButton) + d2ui.AddWidget(&v.deleteCharOkButton) }, func() { v.okButton = d2ui.CreateButton(d2ui.ButtonTypeMedium, d2common.TranslateString("#971")) v.okButton.SetPosition(625, 537) v.okButton.OnActivated(func() { v.onOkButtonClicked() }) - v.uiManager.AddWidget(&v.okButton) + d2ui.AddWidget(&v.okButton) }, func() { v.d2HeroTitle = d2ui.CreateLabel(d2resource.Font42, d2resource.PaletteUnits) @@ -120,17 +121,19 @@ func (v *CharacterSelect) Load() []func() { v.deleteCharConfirmLabel.SetPosition(400, 185) }, func() { - v.selectionBox, _ = d2render.LoadSprite(d2resource.CharacterSelectionSelectBox, d2resource.PaletteSky) + animation, _ := d2assetmanager.LoadAnimation(d2resource.CharacterSelectionSelectBox, d2resource.PaletteSky) + v.selectionBox, _ = d2render.LoadSprite(animation) v.selectionBox.SetPosition(37, 86) }, func() { - v.okCancelBox, _ = d2render.LoadSprite(d2resource.PopUpOkCancel, d2resource.PaletteFechar) + animation, _ := d2assetmanager.LoadAnimation(d2resource.PopUpOkCancel, d2resource.PaletteFechar) + v.okCancelBox, _ = d2render.LoadSprite(animation) v.okCancelBox.SetPosition(270, 175) }, func() { v.charScrollbar = d2ui.CreateScrollbar(586, 87, 369) v.charScrollbar.OnActivated(func() { v.onScrollUpdate() }) - v.uiManager.AddWidget(&v.charScrollbar) + d2ui.AddWidget(&v.charScrollbar) }, func() { for i := 0; i < 8; i++ { @@ -172,30 +175,30 @@ 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] = d2core.CreateHero( + v.characterImage[i] = d2hero.CreateHero( 0, 0, 0, v.gameStates[idx].HeroType, - d2core.HeroObjects[v.gameStates[idx].HeroType], + d2hero.HeroObjects[v.gameStates[idx].HeroType], ) } } func (v *CharacterSelect) onNewCharButtonClicked() { - v.sceneProvider.SetNextScene(CreateSelectHeroClass(v.sceneProvider, v.uiManager, v.soundManager)) + d2scenemanager.SetNextScene(CreateSelectHeroClass()) } func (v *CharacterSelect) onExitButtonClicked() { - mainMenu := CreateMainMenu(v.sceneProvider, v.uiManager, v.soundManager) + mainMenu := CreateMainMenu() mainMenu.ShowTrademarkScreen = false - v.sceneProvider.SetNextScene(mainMenu) + d2scenemanager.SetNextScene(mainMenu) } func (v *CharacterSelect) Unload() { } -func (v *CharacterSelect) Render(screen *d2surface.Surface) { +func (v *CharacterSelect) Render(screen d2common.Surface) { v.background.RenderSegmented(screen, 4, 3, 0) v.d2HeroTitle.Render(screen) actualSelectionIndex := v.selectedCharacter - (v.charScrollbar.GetCurrentOffset() * 2) @@ -294,7 +297,7 @@ func (v *CharacterSelect) toggleDeleteCharacterDialog(showDialog bool) { } func (v *CharacterSelect) refreshGameStates() { - v.gameStates = d2core.GetAllGameStates() + v.gameStates = d2gamestate.GetAllGameStates() v.updateCharacterBoxes() if len(v.gameStates) > 0 { v.selectedCharacter = 0 @@ -309,5 +312,5 @@ func (v *CharacterSelect) refreshGameStates() { } func (v *CharacterSelect) onOkButtonClicked() { - v.sceneProvider.SetNextScene(CreateGame(v.sceneProvider, v.uiManager, v.soundManager, v.gameStates[v.selectedCharacter])) + d2scenemanager.SetNextScene(CreateGame(v.gameStates[v.selectedCharacter])) } diff --git a/d2core/d2scene/credits.go b/d2game/d2scene/credits.go similarity index 82% rename from d2core/d2scene/credits.go rename to d2game/d2scene/credits.go index c439c652..0f67775f 100644 --- a/d2core/d2scene/credits.go +++ b/d2game/d2scene/credits.go @@ -8,19 +8,18 @@ import ( "path" "strings" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2scenemanager" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2asset" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - - "github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2audio" - dh "github.com/OpenDiablo2/OpenDiablo2/d2helper" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" + dh "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" ) type labelItem struct { @@ -31,9 +30,6 @@ type labelItem struct { // Credits represents the credits scene type Credits struct { - uiManager *d2ui.Manager - soundManager *d2audio.Manager - sceneProvider d2coreinterface.SceneProvider creditsBackground *d2render.Sprite exitButton d2ui.Button creditsText []string @@ -44,11 +40,8 @@ type Credits struct { } // CreateCredits creates an instance of the credits scene -func CreateCredits(sceneProvider d2coreinterface.SceneProvider, uiManager *d2ui.Manager, soundManager *d2audio.Manager) *Credits { +func CreateCredits() *Credits { result := &Credits{ - uiManager: uiManager, - soundManager: soundManager, - sceneProvider: sceneProvider, labels: make([]*labelItem, 0), cycleTime: 0, doneWithCredits: false, @@ -78,17 +71,18 @@ func (v *Credits) LoadContributors() []string { func (v *Credits) Load() []func() { return []func(){ func() { - v.creditsBackground, _ = d2render.LoadSprite(d2resource.CreditsBackground, d2resource.PaletteSky) + animation, _ := d2assetmanager.LoadAnimation(d2resource.CreditsBackground, d2resource.PaletteSky) + v.creditsBackground, _ = d2render.LoadSprite(animation) v.creditsBackground.SetPosition(0, 0) }, func() { v.exitButton = d2ui.CreateButton(d2ui.ButtonTypeMedium, d2common.TranslateString("#970")) v.exitButton.SetPosition(33, 543) v.exitButton.OnActivated(func() { v.onExitButtonClicked() }) - v.uiManager.AddWidget(&v.exitButton) + d2ui.AddWidget(&v.exitButton) }, func() { - fileData, err := d2asset.LoadFile(d2resource.CreditsText) + fileData, err := d2assetmanager.LoadFile(d2resource.CreditsText) if err != nil { panic(err) } @@ -108,7 +102,7 @@ func (v *Credits) Unload() { } // Render renders the credits scene -func (v *Credits) Render(screen *d2surface.Surface) { +func (v *Credits) Render(screen d2common.Surface) { v.creditsBackground.RenderSegmented(screen, 4, 3, 0) for _, label := range v.labels { if label.Available { @@ -144,9 +138,9 @@ func (v *Credits) Advance(tickTime float64) { } func (v *Credits) onExitButtonClicked() { - mainMenu := CreateMainMenu(v.sceneProvider, v.uiManager, v.soundManager) + mainMenu := CreateMainMenu() mainMenu.ShowTrademarkScreen = false - v.sceneProvider.SetNextScene(mainMenu) + d2scenemanager.SetNextScene(mainMenu) } func (v *Credits) addNextItem() { diff --git a/d2core/d2scene/game.go b/d2game/d2scene/game.go similarity index 58% rename from d2core/d2scene/game.go rename to d2game/d2scene/game.go index a38a7cb0..26444fde 100644 --- a/d2core/d2scene/game.go +++ b/d2game/d2scene/game.go @@ -3,43 +3,38 @@ package d2scene import ( "image/color" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gamestate" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2audio" - "github.com/OpenDiablo2/OpenDiablo2/d2core" - "github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface" - "github.com/OpenDiablo2/OpenDiablo2/d2input" - "github.com/OpenDiablo2/OpenDiablo2/d2player" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render/d2mapengine" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" + "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player" ) type Game struct { - gameState *d2core.GameState - uiManager *d2ui.Manager - soundManager *d2audio.Manager - sceneProvider d2coreinterface.SceneProvider + gameState *d2gamestate.GameState pentSpinLeft *d2render.Sprite pentSpinRight *d2render.Sprite testLabel d2ui.Label mapEngine *d2mapengine.MapEngine - hero *d2core.Hero + hero *d2hero.Hero gameControls *d2player.GameControls } func CreateGame( - sceneProvider d2coreinterface.SceneProvider, - uiManager *d2ui.Manager, - soundManager *d2audio.Manager, - gameState *d2core.GameState, + gameState *d2gamestate.GameState, ) *Game { result := &Game{ - gameState: gameState, - uiManager: uiManager, - soundManager: soundManager, - sceneProvider: sceneProvider, + gameState: gameState, } return result } @@ -47,13 +42,15 @@ func CreateGame( func (v *Game) Load() []func() { return []func(){ func() { - v.pentSpinLeft, _ = d2render.LoadSprite(d2resource.PentSpin, d2resource.PaletteSky) + animation, _ := d2assetmanager.LoadAnimation(d2resource.PentSpin, d2resource.PaletteSky) + v.pentSpinLeft, _ = d2render.LoadSprite(animation) v.pentSpinLeft.PlayBackward() v.pentSpinLeft.SetPlayLengthMs(475) v.pentSpinLeft.SetPosition(100, 300) }, func() { - v.pentSpinRight, _ = d2render.LoadSprite(d2resource.PentSpin, d2resource.PaletteSky) + animation, _ := d2assetmanager.LoadAnimation(d2resource.PentSpin, d2resource.PaletteSky) + v.pentSpinRight, _ = d2render.LoadSprite(animation) v.pentSpinRight.PlayForward() v.pentSpinRight.SetPlayLengthMs(475) v.pentSpinRight.SetPosition(650, 300) @@ -65,11 +62,11 @@ func (v *Game) Load() []func() { v.testLabel.SetPosition(400, 250) }, func() { - v.mapEngine = d2mapengine.CreateMapEngine(v.gameState, v.soundManager) + v.mapEngine = d2mapengine.CreateMapEngine(v.gameState) v.mapEngine.GenerateMap(d2enum.RegionAct1Town, 1, 0) startX, startY := v.mapEngine.GetStartPosition() - v.hero = d2core.CreateHero( + v.hero = d2hero.CreateHero( int32(startX*5)+3, int32(startY*5)+3, 0, @@ -90,7 +87,7 @@ func (v *Game) Unload() { d2input.UnbindHandler(v.gameControls) } -func (v Game) Render(screen *d2surface.Surface) { +func (v Game) Render(screen d2common.Surface) { screen.Clear(color.Black) v.mapEngine.Render(screen) v.gameControls.Render(screen) diff --git a/d2core/d2scene/main_menu.go b/d2game/d2scene/main_menu.go similarity index 76% rename from d2core/d2scene/main_menu.go rename to d2game/d2scene/main_menu.go index 315e33c7..4518c339 100644 --- a/d2core/d2scene/main_menu.go +++ b/d2game/d2scene/main_menu.go @@ -8,25 +8,24 @@ import ( "os/exec" "runtime" - "github.com/OpenDiablo2/OpenDiablo2/d2core" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gamestate" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2scenemanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2render" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface" - - "github.com/OpenDiablo2/OpenDiablo2/d2audio" "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" ) // MainMenu represents the main menu type MainMenu struct { - uiManager *d2ui.Manager - soundManager *d2audio.Manager - sceneProvider d2coreinterface.SceneProvider trademarkBackground *d2render.Sprite background *d2render.Sprite diabloLogoLeft *d2render.Sprite @@ -50,11 +49,8 @@ type MainMenu struct { } // CreateMainMenu creates an instance of MainMenu -func CreateMainMenu(sceneProvider d2coreinterface.SceneProvider, uiManager *d2ui.Manager, soundManager *d2audio.Manager) *MainMenu { +func CreateMainMenu() *MainMenu { result := &MainMenu{ - uiManager: uiManager, - soundManager: soundManager, - sceneProvider: sceneProvider, ShowTrademarkScreen: true, leftButtonHeld: true, } @@ -63,7 +59,7 @@ func CreateMainMenu(sceneProvider d2coreinterface.SceneProvider, uiManager *d2ui // Load is called to load the resources for the main menu func (v *MainMenu) Load() []func() { - v.soundManager.PlayBGM(d2resource.BGMTitle) + d2audio.PlayBGM(d2resource.BGMTitle) return []func(){ func() { v.versionLabel = d2ui.CreateLabel(d2resource.FontFormal12, d2resource.PaletteStatic) @@ -101,31 +97,37 @@ func (v *MainMenu) Load() []func() { v.openDiabloLabel.SetPosition(400, 580) }, func() { - v.background, _ = d2render.LoadSprite(d2resource.GameSelectScreen, d2resource.PaletteSky) + animation, _ := d2assetmanager.LoadAnimation(d2resource.GameSelectScreen, d2resource.PaletteSky) + v.background, _ = d2render.LoadSprite(animation) v.background.SetPosition(0, 0) }, func() { - v.trademarkBackground, _ = d2render.LoadSprite(d2resource.TrademarkScreen, d2resource.PaletteSky) + animation, _ := d2assetmanager.LoadAnimation(d2resource.TrademarkScreen, d2resource.PaletteSky) + v.trademarkBackground, _ = d2render.LoadSprite(animation) v.trademarkBackground.SetPosition(0, 0) }, func() { - v.diabloLogoLeft, _ = d2render.LoadSprite(d2resource.Diablo2LogoFireLeft, d2resource.PaletteUnits) + animation, _ := d2assetmanager.LoadAnimation(d2resource.Diablo2LogoFireLeft, d2resource.PaletteUnits) + v.diabloLogoLeft, _ = d2render.LoadSprite(animation) v.diabloLogoLeft.SetBlend(true) v.diabloLogoLeft.PlayForward() v.diabloLogoLeft.SetPosition(400, 120) }, func() { - v.diabloLogoRight, _ = d2render.LoadSprite(d2resource.Diablo2LogoFireRight, d2resource.PaletteUnits) + animation, _ := d2assetmanager.LoadAnimation(d2resource.Diablo2LogoFireRight, d2resource.PaletteUnits) + v.diabloLogoRight, _ = d2render.LoadSprite(animation) v.diabloLogoRight.SetBlend(true) v.diabloLogoRight.PlayForward() v.diabloLogoRight.SetPosition(400, 120) }, func() { - v.diabloLogoLeftBack, _ = d2render.LoadSprite(d2resource.Diablo2LogoBlackLeft, d2resource.PaletteUnits) + animation, _ := d2assetmanager.LoadAnimation(d2resource.Diablo2LogoBlackLeft, d2resource.PaletteUnits) + v.diabloLogoLeftBack, _ = d2render.LoadSprite(animation) v.diabloLogoLeftBack.SetPosition(400, 120) }, func() { - v.diabloLogoRightBack, _ = d2render.LoadSprite(d2resource.Diablo2LogoBlackRight, d2resource.PaletteUnits) + animation, _ := d2assetmanager.LoadAnimation(d2resource.Diablo2LogoBlackRight, d2resource.PaletteUnits) + v.diabloLogoRightBack, _ = d2render.LoadSprite(animation) v.diabloLogoRightBack.SetPosition(400, 120) }, func() { @@ -133,47 +135,47 @@ func (v *MainMenu) Load() []func() { v.exitDiabloButton.SetPosition(264, 535) v.exitDiabloButton.SetVisible(!v.ShowTrademarkScreen) v.exitDiabloButton.OnActivated(func() { v.onExitButtonClicked() }) - v.uiManager.AddWidget(&v.exitDiabloButton) + d2ui.AddWidget(&v.exitDiabloButton) }, func() { v.creditsButton = d2ui.CreateButton(d2ui.ButtonTypeShort, d2common.TranslateString("#1627")) v.creditsButton.SetPosition(264, 505) v.creditsButton.SetVisible(!v.ShowTrademarkScreen) v.creditsButton.OnActivated(func() { v.onCreditsButtonClicked() }) - v.uiManager.AddWidget(&v.creditsButton) + d2ui.AddWidget(&v.creditsButton) }, func() { v.cinematicsButton = d2ui.CreateButton(d2ui.ButtonTypeShort, d2common.TranslateString("#1639")) v.cinematicsButton.SetPosition(401, 505) v.cinematicsButton.SetVisible(!v.ShowTrademarkScreen) - v.uiManager.AddWidget(&v.cinematicsButton) + d2ui.AddWidget(&v.cinematicsButton) }, func() { v.singlePlayerButton = d2ui.CreateButton(d2ui.ButtonTypeWide, d2common.TranslateString("#1620")) v.singlePlayerButton.SetPosition(264, 290) v.singlePlayerButton.SetVisible(!v.ShowTrademarkScreen) v.singlePlayerButton.OnActivated(func() { v.onSinglePlayerClicked() }) - v.uiManager.AddWidget(&v.singlePlayerButton) + d2ui.AddWidget(&v.singlePlayerButton) }, func() { v.githubButton = d2ui.CreateButton(d2ui.ButtonTypeWide, "PROJECT WEBSITE") v.githubButton.SetPosition(264, 330) v.githubButton.SetVisible(!v.ShowTrademarkScreen) v.githubButton.OnActivated(func() { v.onGithubButtonClicked() }) - v.uiManager.AddWidget(&v.githubButton) + d2ui.AddWidget(&v.githubButton) }, func() { v.mapTestButton = d2ui.CreateButton(d2ui.ButtonTypeWide, "MAP ENGINE TEST") v.mapTestButton.SetPosition(264, 450) v.mapTestButton.SetVisible(!v.ShowTrademarkScreen) v.mapTestButton.OnActivated(func() { v.onMapTestClicked() }) - v.uiManager.AddWidget(&v.mapTestButton) + d2ui.AddWidget(&v.mapTestButton) }, } } func (v *MainMenu) onMapTestClicked() { - v.sceneProvider.SetNextScene(CreateMapEngineTest(v.sceneProvider, v.uiManager, v.soundManager, 0, 1)) + d2scenemanager.SetNextScene(CreateMapEngineTest(0, 1)) } func openbrowser(url string) { @@ -197,11 +199,11 @@ func openbrowser(url string) { func (v *MainMenu) onSinglePlayerClicked() { // Go here only if existing characters are available to select - if d2core.HasGameStates() { - v.sceneProvider.SetNextScene(CreateCharacterSelect(v.sceneProvider, v.uiManager, v.soundManager)) + if d2gamestate.HasGameStates() { + d2scenemanager.SetNextScene(CreateCharacterSelect()) return } - v.sceneProvider.SetNextScene(CreateSelectHeroClass(v.sceneProvider, v.uiManager, v.soundManager)) + d2scenemanager.SetNextScene(CreateSelectHeroClass()) } func (v *MainMenu) onGithubButtonClicked() { @@ -213,7 +215,7 @@ func (v *MainMenu) onExitButtonClicked() { } func (v *MainMenu) onCreditsButtonClicked() { - v.sceneProvider.SetNextScene(CreateCredits(v.sceneProvider, v.uiManager, v.soundManager)) + d2scenemanager.SetNextScene(CreateCredits()) } // Unload unloads the data for the main menu @@ -222,7 +224,7 @@ func (v *MainMenu) Unload() { } // Render renders the main menu -func (v *MainMenu) Render(screen *d2surface.Surface) { +func (v *MainMenu) Render(screen d2common.Surface) { if v.ShowTrademarkScreen { v.trademarkBackground.RenderSegmented(screen, 4, 3, 0) } else { @@ -251,11 +253,11 @@ func (v *MainMenu) Advance(tickTime float64) { v.diabloLogoRight.Advance(tickTime) if v.ShowTrademarkScreen { - if v.uiManager.CursorButtonPressed(d2ui.CursorButtonLeft) { + if d2ui.CursorButtonPressed(d2ui.CursorButtonLeft) { if v.leftButtonHeld { return } - v.uiManager.WaitForMouseRelease() + d2ui.WaitForMouseRelease() v.leftButtonHeld = true v.ShowTrademarkScreen = false v.exitDiabloButton.SetVisible(true) diff --git a/d2core/d2scene/map_engine_testing.go b/d2game/d2scene/map_engine_testing.go similarity index 83% rename from d2core/d2scene/map_engine_testing.go rename to d2game/d2scene/map_engine_testing.go index 77552b6a..9a2ebce5 100644 --- a/d2core/d2scene/map_engine_testing.go +++ b/d2game/d2scene/map_engine_testing.go @@ -4,16 +4,20 @@ import ( "math" "os" - "github.com/OpenDiablo2/OpenDiablo2/d2core" - "github.com/OpenDiablo2/OpenDiablo2/d2input" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gamestate" - "github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface" + "github.com/OpenDiablo2/OpenDiablo2/d2common" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2scenemanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2audio" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2mapengine" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render/d2mapengine" ) type RegionSpec struct { @@ -78,11 +82,8 @@ var regions []RegionSpec = []RegionSpec{ } type MapEngineTest struct { - uiManager *d2ui.Manager - soundManager *d2audio.Manager - sceneProvider d2coreinterface.SceneProvider - gameState *d2core.GameState - mapEngine *d2mapengine.MapEngine + gameState *d2gamestate.GameState + mapEngine *d2mapengine.MapEngine //TODO: this is region specific properties, should be refactored for multi-region rendering currentRegion int @@ -93,18 +94,15 @@ type MapEngineTest struct { debugVisLevel int } -func CreateMapEngineTest(sceneProvider d2coreinterface.SceneProvider, uiManager *d2ui.Manager, soundManager *d2audio.Manager, currentRegion int, levelPreset int) *MapEngineTest { +func CreateMapEngineTest(currentRegion int, levelPreset int) *MapEngineTest { result := &MapEngineTest{ - uiManager: uiManager, - soundManager: soundManager, - sceneProvider: sceneProvider, currentRegion: currentRegion, levelPreset: levelPreset, fileIndex: 0, regionSpec: RegionSpec{}, filesCount: 0, } - result.gameState = d2core.CreateTestGameState() + result.gameState = d2gamestate.CreateTestGameState() return result } @@ -135,7 +133,7 @@ func (v *MapEngineTest) LoadRegionByIndex(n int, levelPreset, fileIndex int) { if n == 0 { v.mapEngine.GenerateAct1Overworld() } else { - v.mapEngine = d2mapengine.CreateMapEngine(v.gameState, v.soundManager) // necessary for map name update + v.mapEngine = d2mapengine.CreateMapEngine(v.gameState) // necessary for map name update v.mapEngine.GenerateMap(d2enum.RegionIdType(n), levelPreset, fileIndex) } @@ -145,10 +143,10 @@ func (v *MapEngineTest) LoadRegionByIndex(n int, levelPreset, fileIndex int) { func (v *MapEngineTest) Load() []func() { // TODO: Game seed comes from the game state object d2input.BindHandler(v) - v.soundManager.PlayBGM("") + d2audio.PlayBGM("") return []func(){ func() { - v.mapEngine = d2mapengine.CreateMapEngine(v.gameState, v.soundManager) + v.mapEngine = d2mapengine.CreateMapEngine(v.gameState) v.LoadRegionByIndex(v.currentRegion, v.levelPreset, v.fileIndex) }, } @@ -158,10 +156,10 @@ func (v *MapEngineTest) Unload() { d2input.UnbindHandler(v) } -func (v *MapEngineTest) Render(screen *d2surface.Surface) { +func (v *MapEngineTest) Render(screen d2common.Surface) { v.mapEngine.Render(screen) - screenX := v.uiManager.CursorX - screenY := v.uiManager.CursorY + + screenX, screenY, _ := d2render.GetCursorPos() worldX, worldY := v.mapEngine.ScreenToWorld(screenX, screenY) subtileX := int(math.Ceil(math.Mod((worldX*10), 10))) / 2 subtileY := int(math.Ceil(math.Mod((worldY*10), 10))) / 2 @@ -247,13 +245,13 @@ func (met *MapEngineTest) OnKeyDown(event d2input.KeyEvent) bool { if event.Key == d2input.KeyN { if event.KeyMod == d2input.KeyModControl { met.fileIndex = increment(met.fileIndex, 0, met.filesCount-1) - met.sceneProvider.SetNextScene(met) + d2scenemanager.SetNextScene(met) } else if event.KeyMod == d2input.KeyModShift { met.levelPreset = increment(met.levelPreset, met.regionSpec.startPresetIndex, met.regionSpec.endPresetIndex) - met.sceneProvider.SetNextScene(met) + d2scenemanager.SetNextScene(met) } else { met.currentRegion = increment(met.currentRegion, 0, len(regions)) - met.sceneProvider.SetNextScene(met) + d2scenemanager.SetNextScene(met) } return true @@ -262,13 +260,13 @@ func (met *MapEngineTest) OnKeyDown(event d2input.KeyEvent) bool { if event.Key == d2input.KeyP { if event.KeyMod == d2input.KeyModControl { met.fileIndex = decrement(met.fileIndex, 0, met.filesCount-1) - met.sceneProvider.SetNextScene(met) + d2scenemanager.SetNextScene(met) } else if event.KeyMod == d2input.KeyModShift { met.levelPreset = decrement(met.levelPreset, met.regionSpec.startPresetIndex, met.regionSpec.endPresetIndex) - met.sceneProvider.SetNextScene(met) + d2scenemanager.SetNextScene(met) } else { met.currentRegion = decrement(met.currentRegion, 0, len(regions)) - met.sceneProvider.SetNextScene(met) + d2scenemanager.SetNextScene(met) } return true diff --git a/d2core/d2scene/select_hero_class.go b/d2game/d2scene/select_hero_class.go similarity index 77% rename from d2core/d2scene/select_hero_class.go rename to d2game/d2scene/select_hero_class.go index 883990a7..98f82cd3 100644 --- a/d2core/d2scene/select_hero_class.go +++ b/d2game/d2scene/select_hero_class.go @@ -4,21 +4,25 @@ import ( "image" "image/color" - "github.com/OpenDiablo2/OpenDiablo2/d2core" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gamestate" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2scenemanager" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2render" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2corecommon/d2coreinterface" "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2audio" - dh "github.com/OpenDiablo2/OpenDiablo2/d2helper" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2ui" + dh "github.com/OpenDiablo2/OpenDiablo2/d2common/d2helper" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" ) type HeroRenderInfo struct { @@ -32,8 +36,8 @@ type HeroRenderInfo struct { BackWalkSprite *d2render.Sprite BackWalkSpriteOverlay *d2render.Sprite SelectionBounds image.Rectangle - SelectSfx *d2audio.SoundEffect - DeselectSfx *d2audio.SoundEffect + SelectSfx d2interface.SoundEffect + DeselectSfx d2interface.SoundEffect } func (hri *HeroRenderInfo) Advance(elapsed float64) { @@ -48,9 +52,6 @@ func (hri *HeroRenderInfo) Advance(elapsed float64) { } type SelectHeroClass struct { - uiManager *d2ui.Manager - soundManager *d2audio.Manager - sceneProvider d2coreinterface.SceneProvider bgImage *d2render.Sprite campfire *d2render.Sprite headingLabel d2ui.Label @@ -70,11 +71,8 @@ type SelectHeroClass struct { hardcoreCharLabel d2ui.Label } -func CreateSelectHeroClass(sceneProvider d2coreinterface.SceneProvider, uiManager *d2ui.Manager, soundManager *d2audio.Manager) *SelectHeroClass { +func CreateSelectHeroClass() *SelectHeroClass { result := &SelectHeroClass{ - uiManager: uiManager, - sceneProvider: sceneProvider, - soundManager: soundManager, heroRenderInfo: make(map[d2enum.Hero]*HeroRenderInfo), selectedHero: d2enum.HeroNone, } @@ -82,10 +80,10 @@ func CreateSelectHeroClass(sceneProvider d2coreinterface.SceneProvider, uiManage } func (v *SelectHeroClass) Load() []func() { - v.soundManager.PlayBGM(d2resource.BGMTitle) + d2audio.PlayBGM(d2resource.BGMTitle) return []func(){ func() { - v.bgImage, _ = d2render.LoadSprite(d2resource.CharacterSelectBackground, d2resource.PaletteFechar) + v.bgImage = loadSprite(d2resource.CharacterSelectBackground, d2resource.PaletteFechar) v.bgImage.SetPosition(0, 0) }, func() { @@ -116,7 +114,7 @@ func (v *SelectHeroClass) Load() []func() { v.heroDesc3Label.SetPosition(400, 130) }, func() { - v.campfire, _ = d2render.LoadSprite(d2resource.CharacterSelectCampfire, d2resource.PaletteFechar) + v.campfire = loadSprite(d2resource.CharacterSelectCampfire, d2resource.PaletteFechar) v.campfire.SetPosition(380, 335) v.campfire.PlayForward() v.campfire.SetBlend(true) @@ -125,7 +123,7 @@ func (v *SelectHeroClass) Load() []func() { v.exitButton = d2ui.CreateButton(d2ui.ButtonTypeMedium, d2common.TranslateString("#970")) v.exitButton.SetPosition(33, 537) v.exitButton.OnActivated(func() { v.onExitButtonClicked() }) - v.uiManager.AddWidget(&v.exitButton) + d2ui.AddWidget(&v.exitButton) }, func() { v.okButton = d2ui.CreateButton(d2ui.ButtonTypeMedium, d2common.TranslateString("#971")) @@ -133,7 +131,7 @@ func (v *SelectHeroClass) Load() []func() { v.okButton.OnActivated(func() { v.onOkButtonClicked() }) v.okButton.SetVisible(false) v.okButton.SetEnabled(false) - v.uiManager.AddWidget(&v.okButton) + d2ui.AddWidget(&v.okButton) }, func() { v.heroNameLabel = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits) @@ -146,13 +144,13 @@ func (v *SelectHeroClass) Load() []func() { v.heroNameTextbox = d2ui.CreateTextbox() v.heroNameTextbox.SetPosition(318, 493) v.heroNameTextbox.SetVisible(false) - v.uiManager.AddWidget(&v.heroNameTextbox) + d2ui.AddWidget(&v.heroNameTextbox) }, func() { v.expansionCheckbox = d2ui.CreateCheckbox(true) v.expansionCheckbox.SetPosition(318, 526) v.expansionCheckbox.SetVisible(false) - v.uiManager.AddWidget(&v.expansionCheckbox) + d2ui.AddWidget(&v.expansionCheckbox) }, func() { v.expansionCharLabel = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits) @@ -165,7 +163,7 @@ func (v *SelectHeroClass) Load() []func() { v.hardcoreCheckbox = d2ui.CreateCheckbox(false) v.hardcoreCheckbox.SetPosition(318, 548) v.hardcoreCheckbox.SetVisible(false) - v.uiManager.AddWidget(&v.hardcoreCheckbox) + d2ui.AddWidget(&v.hardcoreCheckbox) }, func() { v.hardcoreCharLabel = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits) @@ -177,17 +175,17 @@ func (v *SelectHeroClass) Load() []func() { func() { v.heroRenderInfo[d2enum.HeroBarbarian] = &HeroRenderInfo{ d2enum.HeroStanceIdle, - d2render.MustLoadSprite(d2resource.CharacterSelectBarbarianUnselected, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelectBarbarianUnselectedH, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelectBarbarianForwardWalk, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelectBarbarianForwardWalkOverlay, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelectBarbarianSelected, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectBarbarianUnselected, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectBarbarianUnselectedH, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectBarbarianForwardWalk, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectBarbarianForwardWalkOverlay, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectBarbarianSelected, d2resource.PaletteFechar), nil, - d2render.MustLoadSprite(d2resource.CharacterSelectBarbarianBackWalk, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectBarbarianBackWalk, d2resource.PaletteFechar), nil, image.Rectangle{Min: image.Point{364, 201}, Max: image.Point{90, 170}}, - v.soundManager.LoadSoundEffect(d2resource.SFXBarbarianSelect), - v.soundManager.LoadSoundEffect(d2resource.SFXBarbarianDeselect), + loadSoundEffect(d2resource.SFXBarbarianSelect), + loadSoundEffect(d2resource.SFXBarbarianDeselect), } v.heroRenderInfo[d2enum.HeroBarbarian].IdleSprite.SetPosition(400, 330) v.heroRenderInfo[d2enum.HeroBarbarian].IdleSprite.PlayForward() @@ -211,17 +209,17 @@ func (v *SelectHeroClass) Load() []func() { func() { v.heroRenderInfo[d2enum.HeroSorceress] = &HeroRenderInfo{ d2enum.HeroStanceIdle, - d2render.MustLoadSprite(d2resource.CharacterSelecSorceressUnselected, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecSorceressUnselectedH, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecSorceressForwardWalk, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecSorceressForwardWalkOverlay, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecSorceressSelected, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecSorceressSelectedOverlay, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecSorceressBackWalk, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecSorceressBackWalkOverlay, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecSorceressUnselected, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecSorceressUnselectedH, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecSorceressForwardWalk, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecSorceressForwardWalkOverlay, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecSorceressSelected, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecSorceressSelectedOverlay, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecSorceressBackWalk, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecSorceressBackWalkOverlay, d2resource.PaletteFechar), image.Rectangle{Min: image.Point{580, 240}, Max: image.Point{65, 160}}, - v.soundManager.LoadSoundEffect(d2resource.SFXSorceressSelect), - v.soundManager.LoadSoundEffect(d2resource.SFXSorceressDeselect), + loadSoundEffect(d2resource.SFXSorceressSelect), + loadSoundEffect(d2resource.SFXSorceressDeselect), } v.heroRenderInfo[d2enum.HeroSorceress].IdleSprite.SetPosition(626, 352) v.heroRenderInfo[d2enum.HeroSorceress].IdleSprite.PlayForward() @@ -258,17 +256,17 @@ func (v *SelectHeroClass) Load() []func() { func() { v.heroRenderInfo[d2enum.HeroNecromancer] = &HeroRenderInfo{ d2enum.HeroStanceIdle, - d2render.MustLoadSprite(d2resource.CharacterSelectNecromancerUnselected, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelectNecromancerUnselectedH, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecNecromancerForwardWalk, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecNecromancerForwardWalkOverlay, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecNecromancerSelected, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecNecromancerSelectedOverlay, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecNecromancerBackWalk, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecNecromancerBackWalkOverlay, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectNecromancerUnselected, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectNecromancerUnselectedH, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecNecromancerForwardWalk, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecNecromancerForwardWalkOverlay, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecNecromancerSelected, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecNecromancerSelectedOverlay, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecNecromancerBackWalk, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecNecromancerBackWalkOverlay, d2resource.PaletteFechar), image.Rectangle{Min: image.Point{265, 220}, Max: image.Point{55, 175}}, - v.soundManager.LoadSoundEffect(d2resource.SFXNecromancerSelect), - v.soundManager.LoadSoundEffect(d2resource.SFXNecromancerDeselect), + loadSoundEffect(d2resource.SFXNecromancerSelect), + loadSoundEffect(d2resource.SFXNecromancerDeselect), } v.heroRenderInfo[d2enum.HeroNecromancer].IdleSprite.SetPosition(300, 335) v.heroRenderInfo[d2enum.HeroNecromancer].IdleSprite.PlayForward() @@ -303,17 +301,17 @@ func (v *SelectHeroClass) Load() []func() { func() { v.heroRenderInfo[d2enum.HeroPaladin] = &HeroRenderInfo{ d2enum.HeroStanceIdle, - d2render.MustLoadSprite(d2resource.CharacterSelectPaladinUnselected, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelectPaladinUnselectedH, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecPaladinForwardWalk, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecPaladinForwardWalkOverlay, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecPaladinSelected, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectPaladinUnselected, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectPaladinUnselectedH, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecPaladinForwardWalk, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecPaladinForwardWalkOverlay, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecPaladinSelected, d2resource.PaletteFechar), nil, - d2render.MustLoadSprite(d2resource.CharacterSelecPaladinBackWalk, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecPaladinBackWalk, d2resource.PaletteFechar), nil, image.Rectangle{Min: image.Point{490, 210}, Max: image.Point{65, 180}}, - v.soundManager.LoadSoundEffect(d2resource.SFXPaladinSelect), - v.soundManager.LoadSoundEffect(d2resource.SFXPaladinDeselect), + loadSoundEffect(d2resource.SFXPaladinSelect), + loadSoundEffect(d2resource.SFXPaladinDeselect), } v.heroRenderInfo[d2enum.HeroPaladin].IdleSprite.SetPosition(521, 338) v.heroRenderInfo[d2enum.HeroPaladin].IdleSprite.PlayForward() @@ -340,17 +338,17 @@ func (v *SelectHeroClass) Load() []func() { func() { v.heroRenderInfo[d2enum.HeroAmazon] = &HeroRenderInfo{ d2enum.HeroStanceIdle, - d2render.MustLoadSprite(d2resource.CharacterSelectAmazonUnselected, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelectAmazonUnselectedH, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelecAmazonForwardWalk, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectAmazonUnselected, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectAmazonUnselectedH, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecAmazonForwardWalk, d2resource.PaletteFechar), nil, - d2render.MustLoadSprite(d2resource.CharacterSelecAmazonSelected, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecAmazonSelected, d2resource.PaletteFechar), nil, - d2render.MustLoadSprite(d2resource.CharacterSelecAmazonBackWalk, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelecAmazonBackWalk, d2resource.PaletteFechar), nil, image.Rectangle{Min: image.Point{70, 220}, Max: image.Point{55, 200}}, - v.soundManager.LoadSoundEffect(d2resource.SFXAmazonSelect), - v.soundManager.LoadSoundEffect(d2resource.SFXAmazonDeselect), + loadSoundEffect(d2resource.SFXAmazonSelect), + loadSoundEffect(d2resource.SFXAmazonDeselect), } v.heroRenderInfo[d2enum.HeroAmazon].IdleSprite.SetPosition(100, 339) v.heroRenderInfo[d2enum.HeroAmazon].IdleSprite.PlayForward() @@ -373,17 +371,17 @@ func (v *SelectHeroClass) Load() []func() { func() { v.heroRenderInfo[d2enum.HeroAssassin] = &HeroRenderInfo{ d2enum.HeroStanceIdle, - d2render.MustLoadSprite(d2resource.CharacterSelectAssassinUnselected, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelectAssassinUnselectedH, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelectAssassinForwardWalk, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectAssassinUnselected, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectAssassinUnselectedH, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectAssassinForwardWalk, d2resource.PaletteFechar), nil, - d2render.MustLoadSprite(d2resource.CharacterSelectAssassinSelected, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectAssassinSelected, d2resource.PaletteFechar), nil, - d2render.MustLoadSprite(d2resource.CharacterSelectAssassinBackWalk, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectAssassinBackWalk, d2resource.PaletteFechar), nil, image.Rectangle{Min: image.Point{175, 235}, Max: image.Point{50, 180}}, - v.soundManager.LoadSoundEffect(d2resource.SFXAssassinSelect), - v.soundManager.LoadSoundEffect(d2resource.SFXAssassinDeselect), + loadSoundEffect(d2resource.SFXAssassinSelect), + loadSoundEffect(d2resource.SFXAssassinDeselect), } v.heroRenderInfo[d2enum.HeroAssassin].IdleSprite.SetPosition(231, 365) v.heroRenderInfo[d2enum.HeroAssassin].IdleSprite.PlayForward() @@ -406,17 +404,17 @@ func (v *SelectHeroClass) Load() []func() { func() { v.heroRenderInfo[d2enum.HeroDruid] = &HeroRenderInfo{ d2enum.HeroStanceIdle, - d2render.MustLoadSprite(d2resource.CharacterSelectDruidUnselected, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelectDruidUnselectedH, d2resource.PaletteFechar), - d2render.MustLoadSprite(d2resource.CharacterSelectDruidForwardWalk, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectDruidUnselected, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectDruidUnselectedH, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectDruidForwardWalk, d2resource.PaletteFechar), nil, - d2render.MustLoadSprite(d2resource.CharacterSelectDruidSelected, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectDruidSelected, d2resource.PaletteFechar), nil, - d2render.MustLoadSprite(d2resource.CharacterSelectDruidBackWalk, d2resource.PaletteFechar), + loadSprite(d2resource.CharacterSelectDruidBackWalk, d2resource.PaletteFechar), nil, image.Rectangle{Min: image.Point{680, 220}, Max: image.Point{70, 195}}, - v.soundManager.LoadSoundEffect(d2resource.SFXDruidSelect), - v.soundManager.LoadSoundEffect(d2resource.SFXDruidDeselect), + loadSoundEffect(d2resource.SFXDruidSelect), + loadSoundEffect(d2resource.SFXDruidDeselect), } v.heroRenderInfo[d2enum.HeroDruid].IdleSprite.SetPosition(720, 370) v.heroRenderInfo[d2enum.HeroDruid].IdleSprite.PlayForward() @@ -448,15 +446,15 @@ func (v *SelectHeroClass) Unload() { } func (v SelectHeroClass) onExitButtonClicked() { - v.sceneProvider.SetNextScene(CreateCharacterSelect(v.sceneProvider, v.uiManager, v.soundManager)) + d2scenemanager.SetNextScene(CreateCharacterSelect()) } func (v SelectHeroClass) onOkButtonClicked() { - gameState := d2core.CreateGameState(v.heroNameTextbox.GetText(), v.selectedHero, v.hardcoreCheckbox.GetCheckState()) - v.sceneProvider.SetNextScene(CreateGame(v.sceneProvider, v.uiManager, v.soundManager, gameState)) + gameState := d2gamestate.CreateGameState(v.heroNameTextbox.GetText(), v.selectedHero, v.hardcoreCheckbox.GetCheckState()) + d2scenemanager.SetNextScene(CreateGame(gameState)) } -func (v *SelectHeroClass) Render(screen *d2surface.Surface) { +func (v *SelectHeroClass) Render(screen d2common.Surface) { v.bgImage.RenderSegmented(screen, 4, 3, 0) v.headingLabel.Render(screen) if v.selectedHero != d2enum.HeroNone { @@ -485,6 +483,7 @@ func (v *SelectHeroClass) Render(screen *d2surface.Surface) { func (v *SelectHeroClass) Advance(tickTime float64) { canSelect := true + v.campfire.Advance(tickTime) for _, info := range v.heroRenderInfo { info.Advance(tickTime) if info.Stance != d2enum.HeroStanceIdle && info.Stance != d2enum.HeroStanceIdleSelected && info.Stance != d2enum.HeroStanceSelected { @@ -528,11 +527,11 @@ func (v *SelectHeroClass) updateHeroSelectionHover(hero d2enum.Hero, canSelect b if renderInfo.Stance == d2enum.HeroStanceSelected { return } - mouseX := v.uiManager.CursorX - mouseY := v.uiManager.CursorY + mouseX := d2ui.CursorX + mouseY := d2ui.CursorY b := renderInfo.SelectionBounds mouseHover := (mouseX >= b.Min.X) && (mouseX <= b.Min.X+b.Max.X) && (mouseY >= b.Min.Y) && (mouseY <= b.Min.Y+b.Max.Y) - if mouseHover && v.uiManager.CursorButtonPressed(d2ui.CursorButtonLeft) { + if mouseHover && d2ui.CursorButtonPressed(d2ui.CursorButtonLeft) { v.heroNameTextbox.SetVisible(true) v.okButton.SetVisible(true) v.expansionCheckbox.SetVisible(true) @@ -572,7 +571,7 @@ func (v *SelectHeroClass) updateHeroSelectionHover(hero d2enum.Hero, canSelect b } -func (v *SelectHeroClass) renderHero(screen *d2surface.Surface, hero d2enum.Hero) { +func (v *SelectHeroClass) renderHero(screen d2common.Surface, hero d2enum.Hero) { renderInfo := v.heroRenderInfo[hero] switch renderInfo.Stance { case d2enum.HeroStanceIdle: @@ -659,7 +658,7 @@ func setSpriteToFirstFrame(sprite *d2render.Sprite) { } } -func drawSprite(sprite *d2render.Sprite, target *d2surface.Surface) { +func drawSprite(sprite *d2render.Sprite, target d2common.Surface) { if sprite != nil { sprite.Render(target) } @@ -670,3 +669,14 @@ func advanceSprite(sprite *d2render.Sprite, elapsed float64) { sprite.Advance(elapsed) } } + +func loadSprite(animationPath, palettePath string) *d2render.Sprite { + animation, _ := d2assetmanager.LoadAnimation(animationPath, palettePath) + sprite, _ := d2render.LoadSprite(animation) + return sprite +} + +func loadSoundEffect(sfx string) d2interface.SoundEffect { + result, _ := d2audio.LoadSoundEffect(sfx) + return result +} diff --git a/d2render/animated_entity_test.go b/d2render/animated_entity_test.go deleted file mode 100644 index 3396a079..00000000 --- a/d2render/animated_entity_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package d2render - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestAngleToDirection_16Directions(t *testing.T) { - - numberOfDirections := 16 - - angle := 45.0 - for i := 0; i < numberOfDirections; i++ { - assert.Equal(t, i, angleToDirection(angle, numberOfDirections)) - angle += 22.5 - } - - angle = 50.0 - for i := 0; i < numberOfDirections; i++ { - assert.Equal(t, i, angleToDirection(angle, numberOfDirections)) - angle += 22.5 - } - - angle = 40.0 - for i := 0; i < numberOfDirections; i++ { - assert.Equal(t, i, angleToDirection(angle, numberOfDirections)) - angle += 22.5 - } - -} - -func TestAngleToDirection_8Directions(t *testing.T) { - - numberOfDirections := 8 - - angle := 45.0 - for i := 0; i < numberOfDirections; i++ { - assert.Equal(t, i, angleToDirection(angle, numberOfDirections)) - angle += 45 - } - - angle = 50.0 - for i := 0; i < numberOfDirections; i++ { - assert.Equal(t, i, angleToDirection(angle, numberOfDirections)) - angle += 45 - } - - angle = 40.0 - for i := 0; i < numberOfDirections; i++ { - assert.Equal(t, i, angleToDirection(angle, numberOfDirections)) - angle += 45 - } - -} - -func TestAngleToDirection_1Direction(t *testing.T) { - angle := 0.0 - for i := 0; i < 120; i++ { - assert.Equal(t, 0, angleToDirection(angle, 1)) - angle += 3 - } -} - -func TestAngleToDirection_0Directions(t *testing.T) { - angle := 0.0 - for i := 0; i < 120; i++ { - assert.Equal(t, 0, angleToDirection(angle, 0)) - angle += 3 - } -} diff --git a/d2render/d2ui/manager.go b/d2render/d2ui/manager.go deleted file mode 100644 index e842556b..00000000 --- a/d2render/d2ui/manager.go +++ /dev/null @@ -1,153 +0,0 @@ -package d2ui - -import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" - "github.com/OpenDiablo2/OpenDiablo2/d2audio" - "github.com/OpenDiablo2/OpenDiablo2/d2render" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" - "github.com/hajimehoshi/ebiten" -) - -// CursorButton represents a mouse button -type CursorButton uint8 - -const ( - // CursorButtonLeft represents the left mouse button - CursorButtonLeft CursorButton = 1 - // CursorButtonRight represents the right mouse button - CursorButtonRight CursorButton = 2 -) - -// Manager represents the UI manager -type Manager struct { - widgets []Widget - cursorSprite *d2render.Sprite - cursorButtons CursorButton - pressedIndex int - CursorX int - CursorY int - clickSfx *d2audio.SoundEffect - waitForLeftMouseUp bool -} - -// CreateManager creates a new instance of a UI manager -func CreateManager(soundManager d2audio.Manager) *Manager { - cursorSprite, _ := d2render.LoadSprite(d2resource.CursorDefault, d2resource.PaletteUnits) - result := &Manager{ - pressedIndex: -1, - cursorSprite: cursorSprite, - clickSfx: soundManager.LoadSoundEffect(d2resource.SFXButtonClick), - waitForLeftMouseUp: false, - } - return result -} - -// Reset resets the state of the UI manager. Typically called for new scenes -func (v *Manager) Reset() { - v.widgets = make([]Widget, 0) - v.pressedIndex = -1 - v.waitForLeftMouseUp = true -} - -// AddWidget adds a widget to the UI manager -func (v *Manager) AddWidget(widget Widget) { - v.widgets = append(v.widgets, widget) -} - -func (v *Manager) WaitForMouseRelease() { - v.waitForLeftMouseUp = true -} - -// Render renders all of the UI elements -func (v *Manager) Render(target *d2surface.Surface) { - for _, widget := range v.widgets { - if widget.GetVisible() { - widget.Render(target) - } - } - - cx, cy := ebiten.CursorPosition() - v.cursorSprite.SetPosition(cx, cy) - v.cursorSprite.Render(target) -} - -// Update updates all of the UI elements -func (v *Manager) Advance(elapsed float64) { - for _, widget := range v.widgets { - if widget.GetVisible() { - widget.Advance(elapsed) - } - } - - v.cursorButtons = 0 - if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { - if !v.waitForLeftMouseUp { - v.cursorButtons |= CursorButtonLeft - } - } else { - if v.waitForLeftMouseUp { - v.waitForLeftMouseUp = false - } - } - if ebiten.IsMouseButtonPressed(ebiten.MouseButtonRight) { - v.cursorButtons |= CursorButtonRight - } - v.CursorX, v.CursorY = ebiten.CursorPosition() - if v.CursorButtonPressed(CursorButtonLeft) { - found := false - for i, widget := range v.widgets { - if !widget.GetVisible() || !widget.GetEnabled() { - continue - } - wx, wy := widget.GetPosition() - ww, wh := widget.GetSize() - if v.CursorX >= wx && v.CursorX <= wx+int(ww) && v.CursorY >= wy && v.CursorY <= wy+int(wh) { - widget.SetPressed(true) - if v.pressedIndex == -1 { - found = true - v.pressedIndex = i - v.clickSfx.Play() - } else if v.pressedIndex > -1 && v.pressedIndex != i { - v.widgets[i].SetPressed(false) - } else { - found = true - } - } else { - widget.SetPressed(false) - } - } - if !found { - if v.pressedIndex > -1 { - v.widgets[v.pressedIndex].SetPressed(false) - } else { - v.pressedIndex = -2 - } - } - } else { - if v.pressedIndex > -1 { - widget := v.widgets[v.pressedIndex] - wx, wy := widget.GetPosition() - ww, wh := widget.GetSize() - if v.CursorX >= wx && v.CursorX <= wx+int(ww) && v.CursorY >= wy && v.CursorY <= wy+int(wh) { - widget.Activate() - } - } else { - for _, widget := range v.widgets { - if !widget.GetVisible() || !widget.GetEnabled() { - continue - } - widget.SetPressed(false) - } - } - v.pressedIndex = -1 - } -} - -// CursorButtonPressed determines if the specified button has been pressed -func (v *Manager) CursorButtonPressed(button CursorButton) bool { - return v.cursorButtons&button > 0 -} - -func (v *Manager) KeyPressed(key ebiten.Key) bool { - return ebiten.IsKeyPressed(key) -} diff --git a/main.go b/main.go index 82cea5ea..b740b195 100644 --- a/main.go +++ b/main.go @@ -1,20 +1,39 @@ package main import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common" - "github.com/OpenDiablo2/OpenDiablo2/d2data/d2mpq" - "image" "log" - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2scene" - "github.com/OpenDiablo2/OpenDiablo2/d2input" - "github.com/OpenDiablo2/OpenDiablo2/d2render/d2surface" - "github.com/OpenDiablo2/OpenDiablo2/d2term" + ebiten2 "github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio/ebiten" - "github.com/hajimehoshi/ebiten/ebitenutil" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2assetmanager" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2audio" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2scenemanager" + "github.com/OpenDiablo2/OpenDiablo2/d2game/d2scene" + + "github.com/OpenDiablo2/OpenDiablo2/d2game" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render/ebiten" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2config" + + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2input" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2render" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2term" + + "github.com/OpenDiablo2/OpenDiablo2/d2common" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2mpq" - "github.com/OpenDiablo2/OpenDiablo2/d2core" - "github.com/hajimehoshi/ebiten" "gopkg.in/alecthomas/kingpin.v2" ) @@ -23,59 +42,231 @@ var GitBranch string // GitCommit is set by the CI build process to the commit hash var GitCommit string -var d2Engine *d2core.Engine var region = kingpin.Arg("region", "Region type id").Int() var preset = kingpin.Arg("preset", "Level preset").Int() func main() { - d2input.Initialize() - d2term.Initialize() - d2term.BindLogger() - - //procs := runtime.GOMAXPROCS(16) - //log.Printf("Setting gomaxprocs to 16, it was previously set to %d", procs) - //runtime.LockOSThread() - //defer runtime.UnlockOSThread() - //defer profile.Start(profile.ProfilePath(".")).Stop() + d2common.SetBuildInfo(GitBranch, GitCommit) + log.SetFlags(log.Lshortfile) + log.Println("OpenDiablo2 - Open source Diablo 2 engine") if len(GitBranch) == 0 { GitBranch = "Local Build" GitCommit = "" } - d2common.SetBuildInfo(GitBranch, GitCommit) - log.SetFlags(log.Lshortfile) - log.Println("OpenDiablo2 - Open source Diablo 2 engine") - _, iconImage, err := ebitenutil.NewImageFromFile("d2logo.png", ebiten.FilterLinear) - if err == nil { - ebiten.SetWindowIcon([]image.Image{iconImage}) + + err := initializeEverything() + if err != nil { + log.Fatal(err) + return } - d2mpq.InitializeCryptoBuffer() - d2Engine = d2core.CreateEngine() + kingpin.Parse() if *region == 0 { - d2Engine.SetNextScene(d2scene.CreateMainMenu(d2Engine, d2Engine.UIManager, d2Engine.SoundManager)) + d2scenemanager.SetNextScene(d2scene.CreateMainMenu()) } else { - d2Engine.SetNextScene(d2scene.CreateMapEngineTest(d2Engine, d2Engine.UIManager, d2Engine.SoundManager, *region, *preset)) + d2scenemanager.SetNextScene(d2scene.CreateMapEngineTest(*region, *preset)) } - ebiten.SetCursorVisible(false) - ebiten.SetFullscreen(d2Engine.Settings.FullScreen) - ebiten.SetRunnableInBackground(d2Engine.Settings.RunInBackground) - ebiten.SetVsyncEnabled(d2Engine.Settings.VsyncEnabled) - ebiten.SetMaxTPS(d2Engine.Settings.TicksPerSecond) - if err := ebiten.Run(update, 800, 600, d2Engine.Settings.Scale, "OpenDiablo 2 ("+GitBranch+")"); err != nil { + err = d2game.Run(GitBranch) + if err != nil { log.Fatal(err) + return } } -func update(screen *ebiten.Image) error { - d2Engine.Advance() - if !ebiten.IsDrawingSkipped() { - surface := d2surface.CreateSurface(screen) - d2Engine.Render(surface) - if surface.GetDepth() > 0 { - panic("detected surface stack leak") - } +func loadTextDictionary() bool { + var fileData []byte + var err error + + toLoad := []string{ + d2resource.PatchStringTable, + d2resource.ExpansionStringTable, + d2resource.StringTable, } + for _, item := range toLoad { + fileData, err = d2assetmanager.LoadFile(item) + if err != nil { + log.Fatal(err) + return false + } + d2common.LoadDictionary(fileData) + } + log.Printf("Loaded %d entries from the string table", d2common.GetDictionaryEntryCount()) + return true +} + +func loadPalettes() bool { + for _, pal := range []string{ + "act1", "act2", "act3", "act4", "act5", "endgame", "endgame2", "fechar", "loading", + "menu0", "menu1", "menu2", "menu3", "menu4", "sky", "static", "trademark", "units", + } { + filePath := `data\global\palette\` + pal + `\pal.dat` + paletteType := d2enum.PaletteType(pal) + file, _ := d2assetmanager.LoadFile(filePath) + d2datadict.LoadPalette(paletteType, file) + } + log.Printf("Loaded %d palettes", len(d2datadict.Palettes)) + return true +} + +func initializeEverything() error { + var err error + + err = d2config.Initialize() + if err != nil { + return err + } + + renderer, err := ebiten.CreateRenderer() + if err != nil { + return err + } + + err = d2render.Initialize(renderer) + if err != nil { + return err + } + + err = d2input.Initialize() + if err != nil { + return err + } + + err = d2term.Initialize() + if err != nil { + return err + } + d2term.BindLogger() + + d2assetmanager.Initialize() + + err = d2render.SetWindowIcon("d2logo.png") + if err != nil { + return err + } + + config, err := d2config.Get() + if err != nil { + log.Fatal(err) + return err + } + + var audioProvider *ebiten2.EbitenAudioProvider + audioProvider, err = ebiten2.CreateAudio() + d2audio.Initialize(audioProvider) + d2audio.SetVolumes(config.BgmVolume, config.SfxVolume) + + d2mpq.InitializeCryptoBuffer() + + settings, _ := d2config.Get() + d2resource.LanguageCode = settings.Language + + var file []byte + + loadPalettes() + + loadTextDictionary() + + file, err = d2assetmanager.LoadFile(d2resource.LevelType) + if err != nil { + return err + } + d2datadict.LoadLevelTypes(file) + + file, err = d2assetmanager.LoadFile(d2resource.LevelPreset) + if err != nil { + return err + } + d2datadict.LoadLevelPresets(file) + + file, err = d2assetmanager.LoadFile(d2resource.LevelWarp) + if err != nil { + return err + } + d2datadict.LoadLevelWarps(file) + + file, err = d2assetmanager.LoadFile(d2resource.ObjectType) + if err != nil { + return err + } + d2datadict.LoadObjectTypes(file) + + file, err = d2assetmanager.LoadFile(d2resource.ObjectDetails) + if err != nil { + return err + } + d2datadict.LoadObjects(file) + + file, err = d2assetmanager.LoadFile(d2resource.Weapons) + if err != nil { + return err + } + d2datadict.LoadWeapons(file) + + file, err = d2assetmanager.LoadFile(d2resource.Armor) + if err != nil { + return err + } + d2datadict.LoadArmors(file) + + file, err = d2assetmanager.LoadFile(d2resource.Misc) + if err != nil { + return err + } + d2datadict.LoadMiscItems(file) + + file, err = d2assetmanager.LoadFile(d2resource.UniqueItems) + if err != nil { + return err + } + d2datadict.LoadUniqueItems(file) + + file, err = d2assetmanager.LoadFile(d2resource.Missiles) + if err != nil { + return err + } + d2datadict.LoadMissiles(file) + + file, err = d2assetmanager.LoadFile(d2resource.SoundSettings) + if err != nil { + return err + } + d2datadict.LoadSounds(file) + + file, err = d2assetmanager.LoadFile(d2resource.AnimationData) + if err != nil { + return err + } + d2data.LoadAnimationData(file) + + file, err = d2assetmanager.LoadFile(d2resource.MonStats) + if err != nil { + return err + } + d2datadict.LoadMonStats(file) + + animation, _ := d2assetmanager.LoadAnimation(d2resource.LoadingScreen, d2resource.PaletteLoading) + loadingSprite, _ := d2render.LoadSprite(animation) + loadingSpriteSizeX, loadingSpriteSizeY := loadingSprite.GetCurrentFrameSize() + loadingSprite.SetPosition(int(400-(loadingSpriteSizeX/2)), int(300+(loadingSpriteSizeY/2))) + err = d2game.Initialize(loadingSprite) + if err != nil { + return err + } + + animation, _ = d2assetmanager.LoadAnimation(d2resource.CursorDefault, d2resource.PaletteUnits) + cursorSprite, _ := d2render.LoadSprite(animation) + d2ui.Initialize(cursorSprite) + + d2term.BindAction("timescale", "set scalar for elapsed time", func(scale float64) { + if scale <= 0 { + d2term.OutputError("invalid time scale value") + } else { + d2term.OutputInfo("timescale changed from %f to %f", d2game.GetTimeScale(), scale) + d2game.SetTimeScale(scale) + } + }) + return nil }