Minor changes to project layout (#276)

* Minor changes to reduce interdependencies on modules.
This commit is contained in:
Tim Sarbin 2020-01-31 23:18:11 -05:00 committed by GitHub
parent 6832a5a0db
commit 2461142fbd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
139 changed files with 2614 additions and 1935 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,7 @@
package d2interface
type AudioProvider interface {
PlayBGM(song string)
LoadSoundEffect(sfx string) (SoundEffect, error)
SetVolumes(bgmVolume, sfxVolume float64)
}

View File

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

View File

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

View File

@ -0,0 +1,6 @@
package d2interface
type SoundEffect interface {
Play()
Stop()
}

15
d2common/filter.go Normal file
View File

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

22
d2common/surface.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

47
d2core/d2audio/d2audio.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,4 +1,4 @@
package d2core
package d2hero
type CharacterEquipment struct {
Head *InventoryItemArmor // Head

View File

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

View File

@ -1,4 +1,4 @@
package d2core
package d2hero
import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

108
d2core/d2render/d2render.go Normal file
View File

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

View File

@ -1,4 +1,4 @@
package d2corehelper
package ebiten
import (
"image/color"

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More