mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-09 10:06:35 -05:00
changes to d2components, d2systems, d2ui, d2enum
go.mod, go.sum: * updating akara, bugfix in akara.EntityManager.RemoveEntity d2core * adding d2core/d2label * adding d2core/d2bitmapfont d2ui * exporting some constants for use elsewhere d2components * added bitmap font component (for ui labels) * added FileLoaded tag component to simplify asset loading filters * added locale component * FilePath component renamed to File * sprite component now contains the sprite and palette path as strings * adding ui label component d2enum * added locale as file type for file "/data/local/use" d2systems * changed most info prints to debug prints * removed unused scene graph testing file (oops!) * terminal is now rendered above mouse cursor scene * adding ui widget system for use by the game object factory * adding test scene for ui labels created with the ui widget system d2systems/AppBootstrap * added command line args for profiler * `--testscene labels` launches the label test * now adds the local file for processing * game loop init logic now inside of Init method (the call to world.Update does this) d2systems/AssetLoader * loads the locale file and adds a locale component that other systems can use * adds a FileLoaded component after finished loading a file which other systems can use (like the loading scene) d2systems/FileSourceResolver * Now looks for and uses the locale for language/charset filepath substitution d2systems/GameClientBootstrap * game loop init moved to end of AppBootstrap.Init d2systems/GameObjectFactory * embedding UI widget factory system d2systems/BaseScene * made base scene a little more clear by breaking the process into more methods d2systems/LoadingScene * simplified the entity subscriptions by using the new FileLoaded component d2systems/SceneObjectFactory * adding method for adding labels, buttons to scenes (buttons still WIP) d2systems/SceneSpriteSystem * the sprite system now maintains a cache of rendered sprites
This commit is contained in:
parent
2e814f29b0
commit
deb63a95c8
@ -21,4 +21,5 @@ const (
|
||||
FileTypeDT1
|
||||
FileTypeWAV
|
||||
FileTypeD2
|
||||
FileTypeLocale
|
||||
)
|
||||
|
128
d2core/d2bitmapfont/bitmap_font.go
Normal file
128
d2core/d2bitmapfont/bitmap_font.go
Normal file
@ -0,0 +1,128 @@
|
||||
package d2bitmapfont
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"image/color"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math"
|
||||
)
|
||||
|
||||
func New(s d2interface.Sprite, table []byte, col color.Color) *BitmapFont {
|
||||
return &BitmapFont{
|
||||
Sprite: s,
|
||||
Table: table,
|
||||
Color: col,
|
||||
}
|
||||
}
|
||||
|
||||
type Glyph struct {
|
||||
frame int
|
||||
width int
|
||||
height int
|
||||
}
|
||||
|
||||
// BitmapFont represents a rasterized font, made from a font Table, sprite, and palette
|
||||
type BitmapFont struct {
|
||||
Sprite d2interface.Sprite
|
||||
Table []byte
|
||||
Glyphs map[rune]Glyph
|
||||
Color color.Color
|
||||
}
|
||||
|
||||
// SetColor sets the fonts Color
|
||||
func (f *BitmapFont) SetColor(c color.Color) {
|
||||
f.Color = c
|
||||
}
|
||||
|
||||
// GetTextMetrics returns the dimensions of the BitmapFont element in pixels
|
||||
func (f *BitmapFont) GetTextMetrics(text string) (width, height int) {
|
||||
if f.Glyphs == nil {
|
||||
f.initGlyphs()
|
||||
}
|
||||
|
||||
var (
|
||||
lineWidth int
|
||||
lineHeight int
|
||||
)
|
||||
|
||||
for _, c := range text {
|
||||
if c == '\n' {
|
||||
width = d2math.MaxInt(width, lineWidth)
|
||||
height += lineHeight
|
||||
lineWidth = 0
|
||||
lineHeight = 0
|
||||
} else if glyph, ok := f.Glyphs[c]; ok {
|
||||
lineWidth += glyph.width
|
||||
lineHeight = d2math.MaxInt(lineHeight, glyph.height)
|
||||
}
|
||||
}
|
||||
|
||||
width = d2math.MaxInt(width, lineWidth)
|
||||
height += lineHeight
|
||||
|
||||
return width, height
|
||||
}
|
||||
|
||||
// RenderText prints a text using its configured style on a Surface (multi-lines are left-aligned, use label otherwise)
|
||||
func (f *BitmapFont) RenderText(text string, target d2interface.Surface) error {
|
||||
if f.Glyphs == nil {
|
||||
f.initGlyphs()
|
||||
}
|
||||
|
||||
f.Sprite.SetColorMod(f.Color)
|
||||
|
||||
lines := strings.Split(text, "\n")
|
||||
|
||||
for _, line := range lines {
|
||||
var (
|
||||
lineHeight int
|
||||
lineLength int
|
||||
)
|
||||
|
||||
for _, c := range line {
|
||||
glyph, ok := f.Glyphs[c]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
if err := f.Sprite.SetCurrentFrame(glyph.frame); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f.Sprite.Render(target)
|
||||
|
||||
lineHeight = d2math.MaxInt(lineHeight, glyph.height)
|
||||
lineLength++
|
||||
|
||||
target.PushTranslation(glyph.width, 0)
|
||||
}
|
||||
|
||||
target.PopN(lineLength)
|
||||
target.PushTranslation(0, lineHeight)
|
||||
}
|
||||
|
||||
target.PopN(len(lines))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *BitmapFont) initGlyphs() {
|
||||
_, maxCharHeight := f.Sprite.GetFrameBounds()
|
||||
|
||||
glyphs := make(map[rune]Glyph)
|
||||
|
||||
for i := 12; i < len(f.Table); i += 14 {
|
||||
code := rune(binary.LittleEndian.Uint16(f.Table[i : i+2]))
|
||||
|
||||
var glyph Glyph
|
||||
glyph.frame = int(binary.LittleEndian.Uint16(f.Table[i+8 : i+10]))
|
||||
glyph.width = int(f.Table[i+3])
|
||||
glyph.height = maxCharHeight
|
||||
|
||||
glyphs[code] = glyph
|
||||
}
|
||||
|
||||
f.Glyphs = glyphs
|
||||
}
|
42
d2core/d2components/bitmap_font.go
Normal file
42
d2core/d2components/bitmap_font.go
Normal file
@ -0,0 +1,42 @@
|
||||
package d2components
|
||||
|
||||
import (
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2bitmapfont"
|
||||
)
|
||||
|
||||
// static check that BitmapFont implements Component
|
||||
var _ akara.Component = &BitmapFont{}
|
||||
|
||||
// BitmapFont represent a font made from a font table, a sprite, and a palette (d2 files)
|
||||
type BitmapFont struct {
|
||||
*d2bitmapfont.BitmapFont
|
||||
}
|
||||
|
||||
// New creates a new BitmapFont.
|
||||
func (*BitmapFont) New() akara.Component {
|
||||
return &BitmapFont{}
|
||||
}
|
||||
|
||||
// BitmapFontFactory is a wrapper for the generic component factory that returns BitmapFont component instances.
|
||||
// This can be embedded inside of a system to give them the methods for adding, retrieving, and removing a BitmapFont.
|
||||
type BitmapFontFactory struct {
|
||||
BitmapFont *akara.ComponentFactory
|
||||
}
|
||||
|
||||
// AddBitmapFont adds a BitmapFont component to the given entity and returns it
|
||||
func (m *BitmapFontFactory) AddBitmapFont(id akara.EID) *BitmapFont {
|
||||
return m.BitmapFont.Add(id).(*BitmapFont)
|
||||
}
|
||||
|
||||
// GetBitmapFont returns the BitmapFont component for the given entity, and a bool for whether or not it exists
|
||||
func (m *BitmapFontFactory) GetBitmapFont(id akara.EID) (*BitmapFont, bool) {
|
||||
component, found := m.BitmapFont.Get(id)
|
||||
if !found {
|
||||
return nil, found
|
||||
}
|
||||
|
||||
return component.(*BitmapFont), found
|
||||
}
|
||||
|
38
d2core/d2components/file_loaded.go
Normal file
38
d2core/d2components/file_loaded.go
Normal file
@ -0,0 +1,38 @@
|
||||
//nolint:dupl,golint,stylecheck // component declarations are supposed to look the same
|
||||
package d2components
|
||||
|
||||
import (
|
||||
"github.com/gravestench/akara"
|
||||
)
|
||||
|
||||
// static check that FileLoaded implements Component
|
||||
var _ akara.Component = &FileLoaded{}
|
||||
|
||||
// FileLoaded is used to flag file entities as having been loaded. it is an empty struct.
|
||||
type FileLoaded struct {}
|
||||
|
||||
// New returns a FileLoaded component. By default, it contains an empty string.
|
||||
func (*FileLoaded) New() akara.Component {
|
||||
return &FileLoaded{}
|
||||
}
|
||||
|
||||
// FileLoadedFactory is a wrapper for the generic component factory that returns FileLoaded component instances.
|
||||
// This can be embedded inside of a system to give them the methods for adding, retrieving, and removing a FileLoaded.
|
||||
type FileLoadedFactory struct {
|
||||
FileLoaded *akara.ComponentFactory
|
||||
}
|
||||
|
||||
// AddFileLoaded adds a FileLoaded component to the given entity and returns it
|
||||
func (m *FileLoadedFactory) AddFileLoaded(id akara.EID) *FileLoaded {
|
||||
return m.FileLoaded.Add(id).(*FileLoaded)
|
||||
}
|
||||
|
||||
// GetFileLoaded returns the FileLoaded component for the given entity, and a bool for whether or not it exists
|
||||
func (m *FileLoadedFactory) GetFileLoaded(id akara.EID) (*FileLoaded, bool) {
|
||||
component, found := m.FileLoaded.Get(id)
|
||||
if !found {
|
||||
return nil, found
|
||||
}
|
||||
|
||||
return component.(*FileLoaded), found
|
||||
}
|
@ -5,36 +5,36 @@ import (
|
||||
"github.com/gravestench/akara"
|
||||
)
|
||||
|
||||
// static check that FilePath implements Component
|
||||
var _ akara.Component = &FilePath{}
|
||||
// static check that File implements Component
|
||||
var _ akara.Component = &File{}
|
||||
|
||||
// FilePath represents a file path for a file
|
||||
type FilePath struct {
|
||||
// File represents a file as a path
|
||||
type File struct {
|
||||
Path string
|
||||
}
|
||||
|
||||
// New returns a FilePath component. By default, it contains an empty string.
|
||||
func (*FilePath) New() akara.Component {
|
||||
return &FilePath{}
|
||||
// New returns a File component. By default, it contains an empty string.
|
||||
func (*File) New() akara.Component {
|
||||
return &File{}
|
||||
}
|
||||
|
||||
// FilePathFactory is a wrapper for the generic component factory that returns FilePath component instances.
|
||||
// This can be embedded inside of a system to give them the methods for adding, retrieving, and removing a FilePath.
|
||||
type FilePathFactory struct {
|
||||
FilePath *akara.ComponentFactory
|
||||
// FileFactory is a wrapper for the generic component factory that returns File component instances.
|
||||
// This can be embedded inside of a system to give them the methods for adding, retrieving, and removing a File.
|
||||
type FileFactory struct {
|
||||
File *akara.ComponentFactory
|
||||
}
|
||||
|
||||
// AddFilePath adds a FilePath component to the given entity and returns it
|
||||
func (m *FilePathFactory) AddFilePath(id akara.EID) *FilePath {
|
||||
return m.FilePath.Add(id).(*FilePath)
|
||||
// AddFile adds a File component to the given entity and returns it
|
||||
func (m *FileFactory) AddFile(id akara.EID) *File {
|
||||
return m.File.Add(id).(*File)
|
||||
}
|
||||
|
||||
// GetFilePath returns the FilePath component for the given entity, and a bool for whether or not it exists
|
||||
func (m *FilePathFactory) GetFilePath(id akara.EID) (*FilePath, bool) {
|
||||
component, found := m.FilePath.Get(id)
|
||||
// GetFile returns the File component for the given entity, and a bool for whether or not it exists
|
||||
func (m *FileFactory) GetFile(id akara.EID) (*File, bool) {
|
||||
component, found := m.File.Get(id)
|
||||
if !found {
|
||||
return nil, found
|
||||
}
|
||||
|
||||
return component.(*FilePath), found
|
||||
return component.(*File), found
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ var _ akara.Component = &FileSource{}
|
||||
// AbstractSource is the abstract representation of what a file source is
|
||||
type AbstractSource interface {
|
||||
Path() string // the path of the source itself
|
||||
Open(path *FilePath) (d2interface.DataStream, error)
|
||||
Open(path *File) (d2interface.DataStream, error)
|
||||
}
|
||||
|
||||
// FileSource contains an embedded file source interface, something that can open files
|
||||
|
41
d2core/d2components/locale.go
Normal file
41
d2core/d2components/locale.go
Normal file
@ -0,0 +1,41 @@
|
||||
//nolint:dupl,golint,stylecheck // component declarations are supposed to look the same
|
||||
package d2components
|
||||
|
||||
import (
|
||||
"github.com/gravestench/akara"
|
||||
)
|
||||
|
||||
// static check that Locale implements Component
|
||||
var _ akara.Component = &Locale{}
|
||||
|
||||
// Locale represents a file as a path
|
||||
type Locale struct {
|
||||
Code byte
|
||||
String string
|
||||
}
|
||||
|
||||
// New returns a Locale component. By default, it contains an empty string.
|
||||
func (*Locale) New() akara.Component {
|
||||
return &Locale{}
|
||||
}
|
||||
|
||||
// LocaleFactory is a wrapper for the generic component factory that returns Locale component instances.
|
||||
// This can be embedded inside of a system to give them the methods for adding, retrieving, and removing a Locale.
|
||||
type LocaleFactory struct {
|
||||
Locale *akara.ComponentFactory
|
||||
}
|
||||
|
||||
// AddLocale adds a Locale component to the given entity and returns it
|
||||
func (m *LocaleFactory) AddLocale(id akara.EID) *Locale {
|
||||
return m.Locale.Add(id).(*Locale)
|
||||
}
|
||||
|
||||
// GetLocale returns the Locale component for the given entity, and a bool for whether or not it exists
|
||||
func (m *LocaleFactory) GetLocale(id akara.EID) (*Locale, bool) {
|
||||
component, found := m.Locale.Get(id)
|
||||
if !found {
|
||||
return nil, found
|
||||
}
|
||||
|
||||
return component.(*Locale), found
|
||||
}
|
@ -13,6 +13,7 @@ var _ akara.Component = &Sprite{}
|
||||
// Sprite is a component that contains a width and height
|
||||
type Sprite struct {
|
||||
d2interface.Sprite
|
||||
SpritePath, PalettePath string
|
||||
}
|
||||
|
||||
// New returns an animation component. By default, it contains a nil instance of an animation.
|
||||
|
43
d2core/d2components/ui_label.go
Normal file
43
d2core/d2components/ui_label.go
Normal file
@ -0,0 +1,43 @@
|
||||
//nolint:dupl,golint,stylecheck // component declarations are supposed to look the same
|
||||
package d2components
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2label"
|
||||
"github.com/gravestench/akara"
|
||||
)
|
||||
|
||||
// static check that Label implements Component
|
||||
var _ akara.Component = &Label{}
|
||||
|
||||
// Label represents a ui label. It contains an embedded *d2label.Label
|
||||
type Label struct {
|
||||
*d2label.Label
|
||||
}
|
||||
|
||||
// New returns a Label component. By default, it contains an empty string.
|
||||
func (*Label) New() akara.Component {
|
||||
return &Label{
|
||||
d2label.New(),
|
||||
}
|
||||
}
|
||||
|
||||
// LabelFactory is a wrapper for the generic component factory that returns Label component instances.
|
||||
// This can be embedded inside of a system to give them the methods for adding, retrieving, and removing a Label.
|
||||
type LabelFactory struct {
|
||||
Label *akara.ComponentFactory
|
||||
}
|
||||
|
||||
// AddLabel adds a Label component to the given entity and returns it
|
||||
func (m *LabelFactory) AddLabel(id akara.EID) *Label {
|
||||
return m.Label.Add(id).(*Label)
|
||||
}
|
||||
|
||||
// GetLabel returns the Label component for the given entity, and a bool for whether or not it exists
|
||||
func (m *LabelFactory) GetLabel(id akara.EID) (*Label, bool) {
|
||||
component, found := m.Label.Get(id)
|
||||
if !found {
|
||||
return nil, found
|
||||
}
|
||||
|
||||
return component.(*Label), found
|
||||
}
|
184
d2core/d2label/label.go
Normal file
184
d2core/d2label/label.go
Normal file
@ -0,0 +1,184 @@
|
||||
package d2label
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"image/color"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2bitmapfont"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
||||
)
|
||||
|
||||
// New creates a new label, initializing the unexported fields
|
||||
func New() *Label {
|
||||
return &Label{
|
||||
colors: map[int]color.Color{0: color.White},
|
||||
}
|
||||
}
|
||||
|
||||
// Label represents a user interface label
|
||||
type Label struct {
|
||||
dirty bool // used to flag when to re-render the label
|
||||
text string
|
||||
textWithColorTokens string
|
||||
Alignment d2ui.HorizontalAlign
|
||||
Font *d2bitmapfont.BitmapFont
|
||||
colors map[int]color.Color
|
||||
backgroundColor color.Color
|
||||
}
|
||||
|
||||
func (v *Label) Render(target d2interface.Surface) {
|
||||
lines := strings.Split(v.GetText(), "\n")
|
||||
yOffset := 0
|
||||
|
||||
lastColor := v.colors[0]
|
||||
v.Font.SetColor(lastColor)
|
||||
|
||||
for _, line := range lines {
|
||||
lw, lh := v.GetTextMetrics(line)
|
||||
characters := []rune(line)
|
||||
|
||||
if v.backgroundColor != nil {
|
||||
target.Clear(v.backgroundColor)
|
||||
}
|
||||
|
||||
target.PushTranslation(v.GetAlignOffset(lw), yOffset)
|
||||
|
||||
for idx := range characters {
|
||||
character := string(characters[idx])
|
||||
charWidth, _ := v.GetTextMetrics(character)
|
||||
|
||||
if v.colors[idx] != nil {
|
||||
lastColor = v.colors[idx]
|
||||
v.Font.SetColor(lastColor)
|
||||
}
|
||||
|
||||
_ = v.Font.RenderText(character, target)
|
||||
|
||||
target.PushTranslation(charWidth, 0)
|
||||
}
|
||||
|
||||
target.PopN(len(characters))
|
||||
|
||||
yOffset += lh
|
||||
|
||||
target.Pop()
|
||||
}
|
||||
|
||||
v.dirty = false
|
||||
}
|
||||
|
||||
// IsDirty returns if the label needs to be re-rendered
|
||||
func (v *Label) IsDirty() bool {
|
||||
return v.dirty
|
||||
}
|
||||
|
||||
// GetSize returns the size of the label
|
||||
func (v *Label) GetSize() (width, height int) {
|
||||
return v.Font.GetTextMetrics(v.text)
|
||||
}
|
||||
|
||||
// GetTextMetrics returns the width and height of the enclosing rectangle in Pixels.
|
||||
func (v *Label) GetTextMetrics(text string) (width, height int) {
|
||||
return v.Font.GetTextMetrics(text)
|
||||
}
|
||||
|
||||
// SetText sets the label's text
|
||||
func (v *Label) SetText(newText string) {
|
||||
if v.text == newText {
|
||||
return
|
||||
}
|
||||
|
||||
v.text = newText
|
||||
v.dirty = true
|
||||
v.textWithColorTokens = v.processColorTokens(v.text)
|
||||
}
|
||||
|
||||
// GetText returns the label's textWithColorTokens
|
||||
func (v *Label) GetText() string {
|
||||
return v.text
|
||||
}
|
||||
|
||||
// SetBackgroundColor sets the background highlight color
|
||||
func (v *Label) SetBackgroundColor(c color.Color) {
|
||||
v.dirty = true
|
||||
v.backgroundColor = c
|
||||
}
|
||||
|
||||
func (v *Label) processColorTokens(str string) string {
|
||||
tokenMatch := regexp.MustCompile(d2ui.ColorTokenMatch)
|
||||
tokenStrMatch := regexp.MustCompile(d2ui.ColorStrMatch)
|
||||
empty := []byte("")
|
||||
|
||||
tokenPosition := 0
|
||||
|
||||
withoutTokens := string(tokenMatch.ReplaceAll([]byte(str), empty)) // remove tokens from string
|
||||
|
||||
matches := tokenStrMatch.FindAll([]byte(str), -1)
|
||||
|
||||
if len(matches) == 0 {
|
||||
v.colors[0] = getColor(d2ui.ColorTokenWhite)
|
||||
}
|
||||
|
||||
// we find the index of each token and update the color map.
|
||||
// the key in the map is the starting index of each color token, the value is the color
|
||||
for idx := range matches {
|
||||
match := matches[idx]
|
||||
matchToken := tokenMatch.Find(match)
|
||||
matchStr := string(tokenMatch.ReplaceAll(match, empty))
|
||||
token := d2ui.ColorToken(matchToken)
|
||||
|
||||
theColor := getColor(token)
|
||||
if theColor == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if v.colors == nil {
|
||||
v.colors = make(map[int]color.Color)
|
||||
}
|
||||
|
||||
v.colors[tokenPosition] = theColor
|
||||
|
||||
tokenPosition += len(matchStr)
|
||||
}
|
||||
|
||||
return withoutTokens
|
||||
}
|
||||
|
||||
func (v *Label) GetAlignOffset(textWidth int) int {
|
||||
switch v.Alignment {
|
||||
case d2ui.HorizontalAlignLeft:
|
||||
return 0
|
||||
case d2ui.HorizontalAlignCenter:
|
||||
return -textWidth / 2
|
||||
case d2ui.HorizontalAlignRight:
|
||||
return -textWidth
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func getColor(token d2ui.ColorToken) color.Color {
|
||||
// https://github.com/OpenDiablo2/OpenDiablo2/issues/823
|
||||
colors := map[d2ui.ColorToken]color.Color{
|
||||
d2ui.ColorTokenGrey: d2util.Color(d2ui.ColorGrey100Alpha),
|
||||
d2ui.ColorTokenWhite: d2util.Color(d2ui.ColorWhite100Alpha),
|
||||
d2ui.ColorTokenBlue: d2util.Color(d2ui.ColorBlue100Alpha),
|
||||
d2ui.ColorTokenYellow: d2util.Color(d2ui.ColorYellow100Alpha),
|
||||
d2ui.ColorTokenGreen: d2util.Color(d2ui.ColorGreen100Alpha),
|
||||
d2ui.ColorTokenGold: d2util.Color(d2ui.ColorGold100Alpha),
|
||||
d2ui.ColorTokenOrange: d2util.Color(d2ui.ColorOrange100Alpha),
|
||||
d2ui.ColorTokenRed: d2util.Color(d2ui.ColorRed100Alpha),
|
||||
d2ui.ColorTokenBlack: d2util.Color(d2ui.ColorBlack100Alpha),
|
||||
}
|
||||
|
||||
chosen := colors[token]
|
||||
|
||||
if chosen == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return chosen
|
||||
}
|
@ -2,9 +2,12 @@ package d2systems
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/pkg/profile"
|
||||
"gopkg.in/alecthomas/kingpin.v2"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
@ -33,6 +36,9 @@ const (
|
||||
|
||||
skipSplashArg = "nosplash"
|
||||
skipSplashDesc = "skip the ebiten splash screen"
|
||||
|
||||
profilerArg = "profile"
|
||||
profilerDesc = "Profiles the program, one of (cpu, mem, block, goroutine, trace, thread, mutex)"
|
||||
)
|
||||
|
||||
// static check that the game config system implements the system interface
|
||||
@ -46,7 +52,7 @@ type AppBootstrap struct {
|
||||
subscribedFiles *akara.Subscription
|
||||
subscribedConfigs *akara.Subscription
|
||||
d2components.GameConfigFactory
|
||||
d2components.FilePathFactory
|
||||
d2components.FileFactory
|
||||
d2components.FileTypeFactory
|
||||
d2components.FileHandleFactory
|
||||
d2components.FileSourceFactory
|
||||
@ -58,16 +64,21 @@ func (m *AppBootstrap) Init(world *akara.World) {
|
||||
|
||||
m.setupLogger()
|
||||
|
||||
m.Info("initializing ...")
|
||||
m.Debug("initializing ...")
|
||||
|
||||
m.setupSubscriptions()
|
||||
m.setupFactories()
|
||||
m.injectSystems()
|
||||
m.setupConfigSources()
|
||||
m.setupConfigFile()
|
||||
m.setupLocaleFile()
|
||||
m.parseCommandLineArgs()
|
||||
|
||||
m.Info("... initialization complete!")
|
||||
m.Debug("... initialization complete!")
|
||||
|
||||
if err := m.World.Update(0); err != nil {
|
||||
m.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (m *AppBootstrap) setupLogger() {
|
||||
@ -76,14 +87,14 @@ func (m *AppBootstrap) setupLogger() {
|
||||
}
|
||||
|
||||
func (m *AppBootstrap) setupSubscriptions() {
|
||||
m.Info("setting up component subscriptions")
|
||||
m.Debug("setting up component subscriptions")
|
||||
|
||||
// we are going to check entities that dont yet have loaded asset types
|
||||
filesToCheck := m.NewComponentFilter().
|
||||
Require( // files that need to be loaded
|
||||
&d2components.FileType{},
|
||||
&d2components.FileHandle{},
|
||||
&d2components.FilePath{},
|
||||
&d2components.File{},
|
||||
).
|
||||
Forbid( // files which have been loaded
|
||||
&d2components.GameConfig{},
|
||||
@ -109,10 +120,10 @@ func (m *AppBootstrap) setupSubscriptions() {
|
||||
}
|
||||
|
||||
func (m *AppBootstrap) setupFactories() {
|
||||
m.Info("setting up component factories")
|
||||
m.Debug("setting up component factories")
|
||||
|
||||
m.InjectComponent(&d2components.GameConfig{}, &m.GameConfig)
|
||||
m.InjectComponent(&d2components.FilePath{}, &m.FilePath)
|
||||
m.InjectComponent(&d2components.File{}, &m.File)
|
||||
m.InjectComponent(&d2components.FileType{}, &m.FileType)
|
||||
m.InjectComponent(&d2components.FileHandle{}, &m.FileHandle)
|
||||
m.InjectComponent(&d2components.FileSource{}, &m.FileSource)
|
||||
@ -145,7 +156,7 @@ func (m *AppBootstrap) setupConfigSources() {
|
||||
e1, e2 := m.NewEntity(), m.NewEntity()
|
||||
|
||||
// add file path components to these entities
|
||||
fp1, fp2 := m.AddFilePath(e1), m.AddFilePath(e2)
|
||||
fp1, fp2 := m.AddFile(e1), m.AddFile(e2)
|
||||
|
||||
// the first entity gets a filepath for the od2 directory, this one is checked first
|
||||
// eg. if OD2 binary is in `~/src/OpenDiablo2/`, then this directory is checked first for a config file
|
||||
@ -168,10 +179,16 @@ func (m *AppBootstrap) setupConfigSources() {
|
||||
|
||||
func (m *AppBootstrap) setupConfigFile() {
|
||||
// add an entity that will get picked up by the game config system and loaded
|
||||
m.AddFilePath(m.NewEntity()).Path = configFileName
|
||||
m.AddFile(m.NewEntity()).Path = configFileName
|
||||
m.Infof("setting up config file `%s` for processing", configFileName)
|
||||
}
|
||||
|
||||
func (m *AppBootstrap) setupLocaleFile() {
|
||||
// add an entity that will get picked up by the game config system and loaded
|
||||
m.AddFile(m.NewEntity()).Path = d2resource.LocalLanguage
|
||||
m.Infof("setting up locale file `%s` for processing", d2resource.LocalLanguage)
|
||||
}
|
||||
|
||||
// Update will look for the first entity with a game config component
|
||||
// and then add the mpq's as file sources
|
||||
func (m *AppBootstrap) Update() {
|
||||
@ -180,7 +197,7 @@ func (m *AppBootstrap) Update() {
|
||||
return
|
||||
}
|
||||
|
||||
m.Infof("found %d new configs to parse", len(configs))
|
||||
m.Debugf("found %d new configs to parse", len(configs))
|
||||
|
||||
firstConfigEntityID := configs[0]
|
||||
|
||||
@ -203,12 +220,13 @@ func (m *AppBootstrap) initMpqSources(cfg *d2components.GameConfig) {
|
||||
m.Infof("adding mpq: %s", fullMpqFilePath)
|
||||
|
||||
// make a new entity for the mpq file source
|
||||
mpqSource := m.AddFilePath(m.NewEntity())
|
||||
mpqSource := m.AddFile(m.NewEntity())
|
||||
mpqSource.Path = fullMpqFilePath
|
||||
}
|
||||
}
|
||||
|
||||
func (m *AppBootstrap) parseCommandLineArgs() {
|
||||
profilerOptions := kingpin.Flag(profilerArg, profilerDesc).String()
|
||||
sceneTest := kingpin.Flag(sceneTestArg, sceneTestDesc).String()
|
||||
server := kingpin.Flag(serverArg, serverDesc).Bool()
|
||||
enableCounter := kingpin.Flag(counterArg, counterDesc).Bool()
|
||||
@ -216,6 +234,8 @@ func (m *AppBootstrap) parseCommandLineArgs() {
|
||||
|
||||
kingpin.Parse()
|
||||
|
||||
m.parseProfilerOptions(*profilerOptions)
|
||||
|
||||
if *enableCounter {
|
||||
m.World.AddSystem(&UpdateCounter{})
|
||||
}
|
||||
@ -227,6 +247,7 @@ func (m *AppBootstrap) parseCommandLineArgs() {
|
||||
|
||||
m.World.AddSystem(&RenderSystem{})
|
||||
m.World.AddSystem(&InputSystem{})
|
||||
m.World.AddSystem(&GameObjectFactory{})
|
||||
|
||||
switch *sceneTest {
|
||||
case "splash":
|
||||
@ -244,7 +265,53 @@ func (m *AppBootstrap) parseCommandLineArgs() {
|
||||
case "terminal":
|
||||
m.Info("running terminal scene")
|
||||
m.World.AddSystem(NewTerminalScene())
|
||||
case "labels":
|
||||
m.Info("running label test scene")
|
||||
m.World.AddSystem(NewLabelTestScene())
|
||||
default:
|
||||
m.World.AddSystem(&GameClientBootstrap{})
|
||||
}
|
||||
}
|
||||
|
||||
func (m *AppBootstrap) parseProfilerOptions(profileOption string) interface{ Stop() } {
|
||||
var options []func(*profile.Profile)
|
||||
|
||||
switch strings.ToLower(strings.Trim(profileOption, " ")) {
|
||||
case "cpu":
|
||||
m.Debug("CPU profiling is enabled.")
|
||||
|
||||
options = append(options, profile.CPUProfile)
|
||||
case "mem":
|
||||
m.Debug("Memory profiling is enabled.")
|
||||
|
||||
options = append(options, profile.MemProfile)
|
||||
case "block":
|
||||
m.Debug("Block profiling is enabled.")
|
||||
|
||||
options = append(options, profile.BlockProfile)
|
||||
case "goroutine":
|
||||
m.Debug("Goroutine profiling is enabled.")
|
||||
|
||||
options = append(options, profile.GoroutineProfile)
|
||||
case "trace":
|
||||
m.Debug("Trace profiling is enabled.")
|
||||
|
||||
options = append(options, profile.TraceProfile)
|
||||
case "thread":
|
||||
m.Debug("Thread creation profiling is enabled.")
|
||||
|
||||
options = append(options, profile.ThreadcreationProfile)
|
||||
case "mutex":
|
||||
m.Debug("Mutex profiling is enabled.")
|
||||
|
||||
options = append(options, profile.MutexProfile)
|
||||
}
|
||||
|
||||
options = append(options, profile.ProfilePath("./pprof/"))
|
||||
|
||||
if len(options) > 1 {
|
||||
return profile.Start(options...)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package d2systems
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"io"
|
||||
|
||||
"github.com/gravestench/akara"
|
||||
@ -40,7 +41,8 @@ type AssetLoaderSystem struct {
|
||||
fileSub *akara.Subscription
|
||||
sourceSub *akara.Subscription
|
||||
cache *d2cache.Cache
|
||||
d2components.FilePathFactory
|
||||
localeString string // related to file "/data/local/use"
|
||||
d2components.FileFactory
|
||||
d2components.FileTypeFactory
|
||||
d2components.FileHandleFactory
|
||||
d2components.FileSourceFactory
|
||||
@ -56,6 +58,9 @@ type AssetLoaderSystem struct {
|
||||
d2components.Dt1Factory
|
||||
d2components.WavFactory
|
||||
d2components.AnimationDataFactory
|
||||
d2components.LocaleFactory
|
||||
d2components.BitmapFontFactory
|
||||
d2components.FileLoadedFactory
|
||||
}
|
||||
|
||||
// Init injects component maps related to various asset types
|
||||
@ -64,7 +69,7 @@ func (m *AssetLoaderSystem) Init(world *akara.World) {
|
||||
|
||||
m.setupLogger()
|
||||
|
||||
m.Info("initializing ...")
|
||||
m.Debug("initializing ...")
|
||||
|
||||
m.setupSubscriptions()
|
||||
m.setupFactories()
|
||||
@ -78,29 +83,18 @@ func (m *AssetLoaderSystem) setupLogger() {
|
||||
}
|
||||
|
||||
func (m *AssetLoaderSystem) setupSubscriptions() {
|
||||
m.Info("setting up component subscriptions")
|
||||
m.Debug("setting up component subscriptions")
|
||||
|
||||
// we are going to check entities that dont yet have loaded asset types
|
||||
filesToLoad := m.NewComponentFilter().
|
||||
Require(
|
||||
&d2components.FilePath{}, // we want to process entities with these file components
|
||||
&d2components.File{}, // we want to process entities with these file components
|
||||
&d2components.FileType{},
|
||||
&d2components.FileHandle{},
|
||||
).
|
||||
Forbid(
|
||||
&d2components.FileSource{}, // but we forbid files that are already loaded
|
||||
&d2components.GameConfig{},
|
||||
&d2components.StringTable{},
|
||||
&d2components.DataDictionary{},
|
||||
&d2components.Palette{},
|
||||
&d2components.PaletteTransform{},
|
||||
&d2components.Cof{},
|
||||
&d2components.Dc6{},
|
||||
&d2components.Dcc{},
|
||||
&d2components.Ds1{},
|
||||
&d2components.Dt1{},
|
||||
&d2components.Wav{},
|
||||
&d2components.AnimationData{},
|
||||
&d2components.FileSource{},
|
||||
&d2components.FileLoaded{},
|
||||
).
|
||||
Build()
|
||||
|
||||
@ -113,9 +107,9 @@ func (m *AssetLoaderSystem) setupSubscriptions() {
|
||||
}
|
||||
|
||||
func (m *AssetLoaderSystem) setupFactories() {
|
||||
m.Info("setting up component factories")
|
||||
m.Debug("setting up component factories")
|
||||
|
||||
m.InjectComponent(&d2components.FilePath{}, &m.FilePath)
|
||||
m.InjectComponent(&d2components.File{}, &m.File)
|
||||
m.InjectComponent(&d2components.FileType{}, &m.FileType)
|
||||
m.InjectComponent(&d2components.FileHandle{}, &m.FileHandle)
|
||||
m.InjectComponent(&d2components.FileSource{}, &m.FileSource)
|
||||
@ -131,6 +125,9 @@ func (m *AssetLoaderSystem) setupFactories() {
|
||||
m.InjectComponent(&d2components.Dt1{}, &m.Dt1)
|
||||
m.InjectComponent(&d2components.Wav{}, &m.Wav)
|
||||
m.InjectComponent(&d2components.AnimationData{}, &m.AnimationData)
|
||||
m.InjectComponent(&d2components.Locale{}, &m.Locale)
|
||||
m.InjectComponent(&d2components.BitmapFont{}, &m.BitmapFont)
|
||||
m.InjectComponent(&d2components.FileLoaded{}, &m.FileLoaded)
|
||||
}
|
||||
|
||||
// Update processes all of the Entities in the subscription of file entities that need to be processed
|
||||
@ -142,7 +139,7 @@ func (m *AssetLoaderSystem) Update() {
|
||||
|
||||
func (m *AssetLoaderSystem) loadAsset(id akara.EID) {
|
||||
// make sure everything is kosher
|
||||
fp, found := m.GetFilePath(id)
|
||||
fp, found := m.GetFile(id)
|
||||
if !found {
|
||||
m.Errorf("filepath component not found for entity %d", id)
|
||||
return
|
||||
@ -166,6 +163,8 @@ func (m *AssetLoaderSystem) loadAsset(id akara.EID) {
|
||||
return
|
||||
}
|
||||
|
||||
m.Debugf("Loading file: %s", fp.Path)
|
||||
|
||||
// make sure to seek back to 0 if the filehandle was cached
|
||||
_, _ = fh.Data.Seek(0, 0)
|
||||
|
||||
@ -218,6 +217,8 @@ func (m *AssetLoaderSystem) assignFromCache(id akara.EID, path string, t d2enum.
|
||||
m.AddAnimationData(id).AnimationData = entry.(*d2animdata.AnimationData)
|
||||
}
|
||||
|
||||
m.AddFileLoaded(id)
|
||||
|
||||
return found
|
||||
}
|
||||
|
||||
@ -225,58 +226,58 @@ func (m *AssetLoaderSystem) assignFromCache(id akara.EID, path string, t d2enum.
|
||||
func (m *AssetLoaderSystem) parseAndCache(id akara.EID, path string, t d2enum.FileType, data []byte) {
|
||||
switch t {
|
||||
case d2enum.FileTypeStringTable:
|
||||
m.Infof("Loading string table: %s", path)
|
||||
m.Debugf("Loading string table: %s", path)
|
||||
m.loadStringTable(id, path, data)
|
||||
case d2enum.FileTypeFontTable:
|
||||
m.Infof("Loading font table: %s", path)
|
||||
m.Debugf("Loading font table: %s", path)
|
||||
m.loadFontTable(id, path, data)
|
||||
case d2enum.FileTypeDataDictionary:
|
||||
m.Infof("Loading data dictionary: %s", path)
|
||||
m.Debugf("Loading data dictionary: %s", path)
|
||||
m.loadDataDictionary(id, path, data)
|
||||
case d2enum.FileTypePalette:
|
||||
m.Infof("Loading palette: %s", path)
|
||||
m.Debugf("Loading palette: %s", path)
|
||||
|
||||
if err := m.loadPalette(id, path, data); err != nil {
|
||||
m.Error(err.Error())
|
||||
}
|
||||
case d2enum.FileTypePaletteTransform:
|
||||
m.Infof("Loading palette transform: %s", path)
|
||||
m.Debugf("Loading palette transform: %s", path)
|
||||
|
||||
if err := m.loadPaletteTransform(id, path, data); err != nil {
|
||||
m.Error(err.Error())
|
||||
}
|
||||
case d2enum.FileTypeCOF:
|
||||
m.Infof("Loading COF: %s", path)
|
||||
m.Debugf("Loading COF: %s", path)
|
||||
|
||||
if err := m.loadCOF(id, path, data); err != nil {
|
||||
m.Error(err.Error())
|
||||
}
|
||||
case d2enum.FileTypeDC6:
|
||||
m.Infof("Loading DC6: %s", path)
|
||||
m.Debugf("Loading DC6: %s", path)
|
||||
|
||||
if err := m.loadDC6(id, path, data); err != nil {
|
||||
m.Error(err.Error())
|
||||
}
|
||||
case d2enum.FileTypeDCC:
|
||||
m.Infof("Loading DCC: %s", path)
|
||||
m.Debugf("Loading DCC: %s", path)
|
||||
|
||||
if err := m.loadDCC(id, path, data); err != nil {
|
||||
m.Error(err.Error())
|
||||
}
|
||||
case d2enum.FileTypeDS1:
|
||||
m.Infof("Loading DS1: %s", path)
|
||||
m.Debugf("Loading DS1: %s", path)
|
||||
|
||||
if err := m.loadDS1(id, path, data); err != nil {
|
||||
m.Error(err.Error())
|
||||
}
|
||||
case d2enum.FileTypeDT1:
|
||||
m.Infof("Loading DT1: %s", path)
|
||||
m.Debugf("Loading DT1: %s", path)
|
||||
|
||||
if err := m.loadDT1(id, path, data); err != nil {
|
||||
m.Error(err.Error())
|
||||
}
|
||||
case d2enum.FileTypeWAV:
|
||||
m.Infof("Loading WAV: %s", path)
|
||||
m.Debugf("Loading WAV: %s", path)
|
||||
|
||||
fh, found := m.GetFileHandle(id)
|
||||
if !found {
|
||||
@ -285,12 +286,27 @@ func (m *AssetLoaderSystem) parseAndCache(id akara.EID, path string, t d2enum.Fi
|
||||
|
||||
m.loadWAV(id, path, fh.Data)
|
||||
case d2enum.FileTypeD2:
|
||||
m.Infof("Loading animation data: %s", path)
|
||||
m.Debugf("Loading animation data: %s", path)
|
||||
|
||||
if err := m.loadAnimationData(id, path, data); err != nil {
|
||||
m.Error(err.Error())
|
||||
}
|
||||
case d2enum.FileTypeLocale:
|
||||
m.Debugf("Loading locale: %s", path)
|
||||
|
||||
m.loadLocale(id, data)
|
||||
}
|
||||
|
||||
m.AddFileLoaded(id)
|
||||
}
|
||||
|
||||
func (m *AssetLoaderSystem) loadLocale(id akara.EID, data []byte) {
|
||||
locale := m.AddLocale(id)
|
||||
|
||||
locale.Code = data[0]
|
||||
locale.String = d2resource.GetLanguageLiteral(locale.Code)
|
||||
|
||||
m.localeString = locale.String
|
||||
}
|
||||
|
||||
func (m *AssetLoaderSystem) loadStringTable(id akara.EID, path string, data []byte) {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package d2systems
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
@ -35,7 +36,7 @@ const (
|
||||
//
|
||||
// A file source can be something like an MPQ archive or a file system directory on the host machine.
|
||||
//
|
||||
// A file handle is a primitive representation of a loaded file; something that has data
|
||||
// A file handle is a primitive representation of a loaded file; something that has data
|
||||
// in the form of a byte slice, but has not been parsed into a more meaningful struct, like a DC6 animation.
|
||||
type FileHandleResolver struct {
|
||||
akara.BaseSubscriberSystem
|
||||
@ -43,10 +44,16 @@ type FileHandleResolver struct {
|
||||
cache *d2cache.Cache
|
||||
filesToLoad *akara.Subscription
|
||||
sourcesToUse *akara.Subscription
|
||||
d2components.FilePathFactory
|
||||
localesToCheck *akara.Subscription
|
||||
locale struct {
|
||||
charset string
|
||||
language string
|
||||
}
|
||||
d2components.FileFactory
|
||||
d2components.FileTypeFactory
|
||||
d2components.FileSourceFactory
|
||||
d2components.FileHandleFactory
|
||||
d2components.LocaleFactory
|
||||
}
|
||||
|
||||
// Init initializes the system with the given world
|
||||
@ -57,12 +64,12 @@ func (m *FileHandleResolver) Init(world *akara.World) {
|
||||
|
||||
m.setupLogger()
|
||||
|
||||
m.Info("initializing ...")
|
||||
m.Debug("initializing ...")
|
||||
|
||||
m.setupSubscriptions()
|
||||
m.setupFactories()
|
||||
|
||||
m.Info("... initialization complete!")
|
||||
m.Debug("... initialization complete!")
|
||||
}
|
||||
|
||||
func (m *FileHandleResolver) setupLogger() {
|
||||
@ -71,11 +78,11 @@ func (m *FileHandleResolver) setupLogger() {
|
||||
}
|
||||
|
||||
func (m *FileHandleResolver) setupSubscriptions() {
|
||||
m.Info("setting up component subscriptions")
|
||||
m.Debug("setting up component subscriptions")
|
||||
// this filter is for entities that have a file path and file type but no file handle.
|
||||
filesToLoad := m.NewComponentFilter().
|
||||
Require(
|
||||
&d2components.FilePath{},
|
||||
&d2components.File{},
|
||||
&d2components.FileType{},
|
||||
).
|
||||
Forbid(
|
||||
@ -88,17 +95,23 @@ func (m *FileHandleResolver) setupSubscriptions() {
|
||||
Require(&d2components.FileSource{}).
|
||||
Build()
|
||||
|
||||
localesToCheck := m.NewComponentFilter().
|
||||
Require(&d2components.Locale{}).
|
||||
Build()
|
||||
|
||||
m.filesToLoad = m.AddSubscription(filesToLoad)
|
||||
m.sourcesToUse = m.AddSubscription(sourcesToUse)
|
||||
m.localesToCheck = m.AddSubscription(localesToCheck)
|
||||
}
|
||||
|
||||
func (m *FileHandleResolver) setupFactories() {
|
||||
m.Info("setting up component factories")
|
||||
m.Debug("setting up component factories")
|
||||
|
||||
m.InjectComponent(&d2components.FilePath{}, &m.FilePath)
|
||||
m.InjectComponent(&d2components.File{}, &m.File)
|
||||
m.InjectComponent(&d2components.FileType{}, &m.FileType)
|
||||
m.InjectComponent(&d2components.FileHandle{}, &m.FileHandle)
|
||||
m.InjectComponent(&d2components.FileSource{}, &m.FileSource)
|
||||
m.InjectComponent(&d2components.Locale{}, &m.Locale)
|
||||
}
|
||||
|
||||
// Update iterates over entities which have not had a file handle resolved.
|
||||
@ -107,6 +120,20 @@ func (m *FileHandleResolver) setupFactories() {
|
||||
func (m *FileHandleResolver) Update() {
|
||||
filesToLoad := m.filesToLoad.GetEntities()
|
||||
sourcesToUse := m.sourcesToUse.GetEntities()
|
||||
locales := m.localesToCheck.GetEntities()
|
||||
|
||||
if m.locale.charset == "" && m.locale.language == "" {
|
||||
for _, eid := range locales {
|
||||
locale, _ := m.GetLocale(eid)
|
||||
m.locale.language = locale.String
|
||||
m.locale.charset = d2resource.GetFontCharset(locale.String)
|
||||
m.RemoveEntity(eid)
|
||||
}
|
||||
|
||||
if m.locale.charset != "" && m.locale.language != "" {
|
||||
m.Infof("locale set to `%s`", m.locale.language)
|
||||
}
|
||||
}
|
||||
|
||||
for _, fileID := range filesToLoad {
|
||||
for _, sourceID := range sourcesToUse {
|
||||
@ -119,7 +146,7 @@ func (m *FileHandleResolver) Update() {
|
||||
|
||||
// try to load a file with a source, returns true if loaded
|
||||
func (m *FileHandleResolver) loadFileWithSource(fileID, sourceID akara.EID) bool {
|
||||
fp, found := m.GetFilePath(fileID)
|
||||
fp, found := m.GetFile(fileID)
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
@ -134,16 +161,16 @@ func (m *FileHandleResolver) loadFileWithSource(fileID, sourceID akara.EID) bool
|
||||
return false
|
||||
}
|
||||
|
||||
sourceFp, found := m.GetFilePath(sourceID)
|
||||
sourceFp, found := m.GetFile(sourceID)
|
||||
if !found {
|
||||
return false
|
||||
}
|
||||
|
||||
// replace the locale tokens if present
|
||||
if strings.Contains(fp.Path, languageTokenFont) {
|
||||
fp.Path = strings.ReplaceAll(fp.Path, languageTokenFont, "latin")
|
||||
} else if strings.Contains(fp.Path, languageTokenStringTable) {
|
||||
fp.Path = strings.ReplaceAll(fp.Path, languageTokenStringTable, "ENG")
|
||||
if strings.Contains(fp.Path, languageTokenFont) && m.locale.charset != "" {
|
||||
fp.Path = strings.ReplaceAll(fp.Path, d2resource.LanguageFontToken, m.locale.charset)
|
||||
} else if strings.Contains(fp.Path, languageTokenStringTable) && m.locale.language != "" {
|
||||
fp.Path = strings.ReplaceAll(fp.Path, d2resource.LanguageTableToken, m.locale.language)
|
||||
}
|
||||
|
||||
cacheKey := m.makeCacheKey(fp.Path, sourceFp.Path)
|
||||
@ -167,7 +194,7 @@ func (m *FileHandleResolver) loadFileWithSource(fileID, sourceID akara.EID) bool
|
||||
}
|
||||
|
||||
tryPath := strings.ReplaceAll(fp.Path, "sfx", "music")
|
||||
tmpComponent := &d2components.FilePath{Path: tryPath}
|
||||
tmpComponent := &d2components.File{Path: tryPath}
|
||||
|
||||
cacheKey = m.makeCacheKey(tryPath, sourceFp.Path)
|
||||
if entry, found := m.cache.Retrieve(cacheKey); found {
|
||||
@ -186,7 +213,7 @@ func (m *FileHandleResolver) loadFileWithSource(fileID, sourceID akara.EID) bool
|
||||
fp.Path = tryPath
|
||||
}
|
||||
|
||||
m.Infof("resolved `%s` with source `%s`", fp.Path, sourceFp.Path)
|
||||
m.Debugf("resolved `%s` with source `%s`", fp.Path, sourceFp.Path)
|
||||
|
||||
component := m.AddFileHandle(fileID)
|
||||
component.Data = data
|
||||
|
@ -24,15 +24,15 @@ func Test_FileHandleResolver_Process(t *testing.T) {
|
||||
|
||||
world := akara.NewWorld(cfg)
|
||||
|
||||
filePaths := typeSys.FilePathFactory
|
||||
filePaths := typeSys.FileFactory
|
||||
fileHandles := handleSys.FileHandleFactory
|
||||
|
||||
sourceEntity := world.NewEntity()
|
||||
source := filePaths.AddFilePath(sourceEntity)
|
||||
source := filePaths.AddFile(sourceEntity)
|
||||
source.Path = testDataPath
|
||||
|
||||
fileEntity := world.NewEntity()
|
||||
file := filePaths.AddFilePath(fileEntity)
|
||||
file := filePaths.AddFile(fileEntity)
|
||||
file.Path = "testfile_a.txt"
|
||||
|
||||
_ = world.Update(0)
|
||||
|
@ -25,8 +25,8 @@ const (
|
||||
type FileSourceResolver struct {
|
||||
akara.BaseSubscriberSystem
|
||||
*d2util.Logger
|
||||
filesToCheck *akara.Subscription
|
||||
d2components.FilePathFactory
|
||||
filesToCheck *akara.Subscription
|
||||
d2components.FileFactory
|
||||
d2components.FileTypeFactory
|
||||
d2components.FileSourceFactory
|
||||
}
|
||||
@ -37,12 +37,12 @@ func (m *FileSourceResolver) Init(world *akara.World) {
|
||||
|
||||
m.setupLogger()
|
||||
|
||||
m.Info("initializing ...")
|
||||
m.Debug("initializing ...")
|
||||
|
||||
m.setupSubscriptions()
|
||||
m.setupFactories()
|
||||
|
||||
m.Info("... initialization complete!")
|
||||
m.Debug("... initialization complete!")
|
||||
}
|
||||
|
||||
func (m *FileSourceResolver) setupLogger() {
|
||||
@ -51,12 +51,12 @@ func (m *FileSourceResolver) setupLogger() {
|
||||
}
|
||||
|
||||
func (m *FileSourceResolver) setupSubscriptions() {
|
||||
m.Info("setting up component subscriptions")
|
||||
m.Debug("setting up component subscriptions")
|
||||
|
||||
// subscribe to entities with a file type and file path, but no file source type
|
||||
filesToCheck := m.NewComponentFilter().
|
||||
Require(
|
||||
&d2components.FilePath{},
|
||||
&d2components.File{},
|
||||
&d2components.FileType{},
|
||||
).
|
||||
Forbid(
|
||||
@ -68,24 +68,22 @@ func (m *FileSourceResolver) setupSubscriptions() {
|
||||
}
|
||||
|
||||
func (m *FileSourceResolver) setupFactories() {
|
||||
m.Info("setting up component factories")
|
||||
m.Debug("setting up component factories")
|
||||
|
||||
m.InjectComponent(&d2components.FilePath{}, &m.FilePath)
|
||||
m.InjectComponent(&d2components.File{}, &m.File)
|
||||
m.InjectComponent(&d2components.FileType{}, &m.FileType)
|
||||
m.InjectComponent(&d2components.FileSource{}, &m.FileSource)
|
||||
}
|
||||
|
||||
// Update iterates over entities from its subscription, and checks if it can be used as a file source
|
||||
func (m *FileSourceResolver) Update() {
|
||||
for subIdx := range m.Subscriptions {
|
||||
for _, sourceEntityID := range m.Subscriptions[subIdx].GetEntities() {
|
||||
m.processSourceEntity(sourceEntityID)
|
||||
}
|
||||
for _, eid := range m.filesToCheck.GetEntities() {
|
||||
m.processSourceEntity(eid)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *FileSourceResolver) processSourceEntity(id akara.EID) {
|
||||
fp, found := m.GetFilePath(id)
|
||||
fp, found := m.GetFile(id)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
@ -109,10 +107,10 @@ func (m *FileSourceResolver) processSourceEntity(id akara.EID) {
|
||||
|
||||
m.AddFileSource(id).AbstractSource = instance
|
||||
|
||||
m.Infof("using MPQ source for `%s`", fp.Path)
|
||||
m.Debugf("adding MPQ source: `%s`", fp.Path)
|
||||
case d2enum.FileTypeDirectory:
|
||||
m.AddFileSource(id).AbstractSource = m.makeFileSystemSource(fp.Path)
|
||||
m.Infof("using FILESYSTEM source for `%s`", fp.Path)
|
||||
m.Debugf("adding FILESYSTEM source: `%s`", fp.Path)
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +123,7 @@ type fsSource struct {
|
||||
rootDir string
|
||||
}
|
||||
|
||||
func (s *fsSource) Open(path *d2components.FilePath) (d2interface.DataStream, error) {
|
||||
func (s *fsSource) Open(path *d2components.File) (d2interface.DataStream, error) {
|
||||
fileData, err := os.Open(s.fullPath(path.Path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -156,7 +154,7 @@ type mpqSource struct {
|
||||
mpq d2interface.Archive
|
||||
}
|
||||
|
||||
func (s *mpqSource) Open(path *d2components.FilePath) (d2interface.DataStream, error) {
|
||||
func (s *mpqSource) Open(path *d2components.File) (d2interface.DataStream, error) {
|
||||
fileData, err := s.mpq.ReadFileStream(s.cleanMpqPath(path.Path))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -21,11 +21,11 @@ func Test_FileSourceResolution(t *testing.T) {
|
||||
|
||||
world := akara.NewWorld(cfg)
|
||||
|
||||
filePaths := typeSys.FilePathFactory
|
||||
filePaths := typeSys.FileFactory
|
||||
fileSources := sourceSys.FileSourceFactory
|
||||
|
||||
sourceEntity := world.NewEntity()
|
||||
sourceFp := filePaths.AddFilePath(sourceEntity)
|
||||
sourceFp := filePaths.AddFile(sourceEntity)
|
||||
sourceFp.Path = testDataPath
|
||||
|
||||
_ = world.Update(0)
|
||||
|
@ -1,6 +1,7 @@
|
||||
package d2systems
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@ -30,7 +31,7 @@ type FileTypeResolver struct {
|
||||
akara.BaseSubscriberSystem
|
||||
*d2util.Logger
|
||||
filesToCheck *akara.Subscription
|
||||
d2components.FilePathFactory
|
||||
d2components.FileFactory
|
||||
d2components.FileTypeFactory
|
||||
}
|
||||
|
||||
@ -40,7 +41,7 @@ func (m *FileTypeResolver) Init(world *akara.World) {
|
||||
|
||||
m.setupLogger()
|
||||
|
||||
m.Info("initializing ...")
|
||||
m.Debug("initializing ...")
|
||||
|
||||
m.setupFactories()
|
||||
m.setupSubscriptions()
|
||||
@ -52,7 +53,7 @@ func (m *FileTypeResolver) setupLogger() {
|
||||
}
|
||||
|
||||
func (m *FileTypeResolver) setupFactories() {
|
||||
m.InjectComponent(&d2components.FilePath{}, &m.FilePath)
|
||||
m.InjectComponent(&d2components.File{}, &m.File)
|
||||
m.InjectComponent(&d2components.FileType{}, &m.FileType)
|
||||
}
|
||||
|
||||
@ -60,7 +61,7 @@ func (m *FileTypeResolver) setupSubscriptions() {
|
||||
// we subscribe only to entities that have a filepath
|
||||
// and have not yet been given a file type
|
||||
filesToCheck := m.NewComponentFilter().
|
||||
Require(&d2components.FilePath{}).
|
||||
Require(&d2components.File{}).
|
||||
Forbid(&d2components.FileType{}).
|
||||
Build()
|
||||
|
||||
@ -76,17 +77,25 @@ func (m *FileTypeResolver) Update() {
|
||||
|
||||
//nolint:gocyclo // this big switch statement is unfortunate, but necessary
|
||||
func (m *FileTypeResolver) determineFileType(id akara.EID) {
|
||||
fp, found := m.GetFilePath(id)
|
||||
fp, found := m.GetFile(id)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
ft := m.AddFileType(id)
|
||||
|
||||
// try to immediately load as an mpq
|
||||
if _, err := d2mpq.Load(fp.Path); err == nil {
|
||||
ft.Type = d2enum.FileTypeMPQ
|
||||
return
|
||||
}
|
||||
|
||||
// special case for the locale file
|
||||
if fp.Path == d2resource.LocalLanguage {
|
||||
ft.Type = d2enum.FileTypeLocale
|
||||
return
|
||||
}
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(fp.Path))
|
||||
|
||||
switch ext {
|
||||
|
@ -13,7 +13,7 @@ func TestNewFileTypeResolver_KnownType(t *testing.T) {
|
||||
world := akara.NewWorld(akara.NewWorldConfig().With(typeSys))
|
||||
|
||||
e := world.NewEntity()
|
||||
typeSys.AddFilePath(e).Path = "/some/path/to/a/file.dcc"
|
||||
typeSys.AddFile(e).Path = "/some/path/to/a/file.dcc"
|
||||
|
||||
if len(typeSys.filesToCheck.GetEntities()) != 1 {
|
||||
t.Error("entity with file path not added to file type typeSys subscription")
|
||||
@ -41,7 +41,7 @@ func TestNewFileTypeResolver_UnknownType(t *testing.T) {
|
||||
|
||||
e := world.NewEntity()
|
||||
|
||||
fp := typeSys.AddFilePath(e)
|
||||
fp := typeSys.AddFile(e)
|
||||
fp.Path = "/some/path/to/a/file.XYZ"
|
||||
|
||||
_ = world.Update(0)
|
||||
|
@ -26,15 +26,11 @@ func (m *GameClientBootstrap) Init(world *akara.World) {
|
||||
|
||||
m.setupLogger()
|
||||
|
||||
m.Info("initializing ...")
|
||||
m.Debug("initializing ...")
|
||||
|
||||
m.injectSystems()
|
||||
|
||||
m.Info("initialization complete")
|
||||
|
||||
if err := m.World.Update(0); err != nil {
|
||||
m.Error(err.Error())
|
||||
}
|
||||
m.Debug("initialization complete")
|
||||
}
|
||||
|
||||
func (m *GameClientBootstrap) setupLogger() {
|
||||
@ -72,6 +68,6 @@ func (m *GameClientBootstrap) injectSystems() {
|
||||
|
||||
// Update does nothing, but exists to satisfy the `akara.System` interface
|
||||
func (m *GameClientBootstrap) Update() {
|
||||
m.Info("game client bootstrap complete, deactivating")
|
||||
m.Debug("game client bootstrap complete, deactivating")
|
||||
m.RemoveSystem(m)
|
||||
}
|
||||
|
@ -34,7 +34,7 @@ type GameConfigSystem struct {
|
||||
filesToCheck *akara.Subscription
|
||||
gameConfigs *akara.Subscription
|
||||
d2components.GameConfigFactory
|
||||
d2components.FilePathFactory
|
||||
d2components.FileFactory
|
||||
d2components.FileTypeFactory
|
||||
d2components.FileHandleFactory
|
||||
d2components.FileSourceFactory
|
||||
@ -48,7 +48,7 @@ func (m *GameConfigSystem) Init(world *akara.World) {
|
||||
|
||||
m.setupLogger()
|
||||
|
||||
m.Info("initializing ...")
|
||||
m.Debug("initializing ...")
|
||||
|
||||
m.setupFactories()
|
||||
m.setupSubscriptions()
|
||||
@ -60,9 +60,9 @@ func (m *GameConfigSystem) setupLogger() {
|
||||
}
|
||||
|
||||
func (m *GameConfigSystem) setupFactories() {
|
||||
m.Info("setting up component factories")
|
||||
m.Debug("setting up component factories")
|
||||
|
||||
m.InjectComponent(&d2components.FilePath{}, &m.FilePath)
|
||||
m.InjectComponent(&d2components.File{}, &m.File)
|
||||
m.InjectComponent(&d2components.FileType{}, &m.FileType)
|
||||
m.InjectComponent(&d2components.FileHandle{}, &m.FileHandle)
|
||||
m.InjectComponent(&d2components.FileSource{}, &m.FileSource)
|
||||
@ -71,12 +71,12 @@ func (m *GameConfigSystem) setupFactories() {
|
||||
}
|
||||
|
||||
func (m *GameConfigSystem) setupSubscriptions() {
|
||||
m.Info("setting up component subscriptions")
|
||||
m.Debug("setting up component subscriptions")
|
||||
|
||||
// we are going to check entities that dont yet have loaded asset types
|
||||
filesToCheck := m.NewComponentFilter().
|
||||
Require(
|
||||
&d2components.FilePath{},
|
||||
&d2components.File{},
|
||||
&d2components.FileType{},
|
||||
&d2components.FileHandle{},
|
||||
).
|
||||
@ -112,7 +112,7 @@ func (m *GameConfigSystem) Update() {
|
||||
|
||||
func (m *GameConfigSystem) checkForNewConfig(entities []akara.EID) {
|
||||
for _, eid := range entities {
|
||||
fp, found := m.GetFilePath(eid)
|
||||
fp, found := m.GetFile(eid)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
@ -23,8 +23,8 @@ func Test_GameConfigSystem_Bootstrap(t *testing.T) {
|
||||
|
||||
world := akara.NewWorld(cfg)
|
||||
|
||||
cfgSys.AddFilePath(world.NewEntity()).Path = testDataPath
|
||||
cfgSys.AddFilePath(world.NewEntity()).Path = "config.json"
|
||||
cfgSys.AddFile(world.NewEntity()).Path = testDataPath
|
||||
cfgSys.AddFile(world.NewEntity()).Path = "config.json"
|
||||
|
||||
// at this point the world has initialized the sceneSystems. when the world
|
||||
// updates it should process the config dir to a source and then
|
||||
|
@ -20,6 +20,7 @@ type GameObjectFactory struct {
|
||||
*d2util.Logger
|
||||
*SpriteFactory
|
||||
*ShapeSystem
|
||||
*UIWidgetFactory
|
||||
}
|
||||
|
||||
// Init will initialize the Game Object Factory by injecting all of the factory subsystems into the world
|
||||
@ -28,7 +29,7 @@ func (t *GameObjectFactory) Init(world *akara.World) {
|
||||
|
||||
t.setupLogger()
|
||||
|
||||
t.Info("initializing ...")
|
||||
t.Debug("initializing ...")
|
||||
|
||||
t.injectSubSystems()
|
||||
}
|
||||
@ -39,13 +40,15 @@ func (t *GameObjectFactory) setupLogger() {
|
||||
}
|
||||
|
||||
func (t *GameObjectFactory) injectSubSystems() {
|
||||
t.Info("creating sprite factory")
|
||||
t.SpriteFactory = NewSpriteFactorySubsystem(t.BaseSystem, t.Logger)
|
||||
t.Debug("creating sprite factory")
|
||||
t.SpriteFactory = NewSpriteFactory(t.BaseSystem, t.Logger)
|
||||
t.ShapeSystem = NewShapeSystem(t.BaseSystem, t.Logger)
|
||||
t.UIWidgetFactory = NewUIWidgetFactory(t.BaseSystem, t.Logger, t.SpriteFactory, t.ShapeSystem)
|
||||
}
|
||||
|
||||
// Update updates all the sub-sceneSystems
|
||||
func (t *GameObjectFactory) Update() {
|
||||
t.SpriteFactory.Update()
|
||||
t.ShapeSystem.Update()
|
||||
t.UIWidgetFactory.Update()
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ func (m *InputSystem) Init(world *akara.World) {
|
||||
|
||||
m.setupLogger()
|
||||
|
||||
m.Info("initializing ...")
|
||||
m.Debug("initializing ...")
|
||||
|
||||
m.setupFactories()
|
||||
m.setupSubscriptions()
|
||||
@ -52,14 +52,14 @@ func (m *InputSystem) setupLogger() {
|
||||
}
|
||||
|
||||
func (m *InputSystem) setupFactories() {
|
||||
m.Info("setting up component factories")
|
||||
m.Debug("setting up component factories")
|
||||
|
||||
m.InjectComponent(&d2components.GameConfig{}, &m.GameConfig)
|
||||
m.InjectComponent(&d2components.Interactive{}, &m.Interactive)
|
||||
}
|
||||
|
||||
func (m *InputSystem) setupSubscriptions() {
|
||||
m.Info("setting up component subscriptions")
|
||||
m.Debug("setting up component subscriptions")
|
||||
|
||||
interactives := m.NewComponentFilter().
|
||||
Require(&d2components.Interactive{}).
|
||||
|
@ -33,7 +33,7 @@ func (m *MovementSystem) Init(world *akara.World) {
|
||||
m.Logger = d2util.NewLogger()
|
||||
m.SetPrefix(logPrefixMovementSystem)
|
||||
|
||||
m.Info("initializing ...")
|
||||
m.Debug("initializing ...")
|
||||
|
||||
m.InjectComponent(&d2components.Transform{}, &m.Transform)
|
||||
m.InjectComponent(&d2components.Velocity{}, &m.Velocity)
|
||||
|
@ -52,7 +52,7 @@ func (m *RenderSystem) Init(world *akara.World) {
|
||||
|
||||
m.setupLogger()
|
||||
|
||||
m.Info("initializing ...")
|
||||
m.Debug("initializing ...")
|
||||
|
||||
m.setupFactories()
|
||||
m.setupSubscriptions()
|
||||
@ -120,7 +120,7 @@ func (m *RenderSystem) Update() {
|
||||
}
|
||||
|
||||
func (m *RenderSystem) createRenderer() {
|
||||
m.Info("creating renderer instance")
|
||||
m.Debug("creating renderer instance")
|
||||
|
||||
configs := m.configs.GetEntities()
|
||||
if len(configs) < 1 {
|
||||
@ -220,7 +220,7 @@ func (m *RenderSystem) updateWorld() error {
|
||||
}
|
||||
|
||||
func (m *RenderSystem) StartGameLoop() error {
|
||||
m.Infof("starting game loop ...")
|
||||
m.Info("starting game loop ...")
|
||||
|
||||
return m.renderer.Run(m.render, m.updateWorld, 800, 600, gameTitle)
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ func (s *BaseScene) Init(world *akara.World) {
|
||||
}
|
||||
|
||||
func (s *BaseScene) boot() {
|
||||
s.Info("base scene booting ...")
|
||||
s.Debug("base scene booting ...")
|
||||
|
||||
s.Add = &sceneObjectFactory{
|
||||
BaseScene: s,
|
||||
@ -119,47 +119,15 @@ func (s *BaseScene) boot() {
|
||||
|
||||
s.Add.SetPrefix(fmt.Sprintf("%s -> %s", s.key, "Object Factory"))
|
||||
|
||||
for idx := range s.Systems {
|
||||
if rendersys, ok := s.Systems[idx].(*RenderSystem); ok && s.sceneSystems.RenderSystem == nil {
|
||||
s.sceneSystems.RenderSystem = rendersys
|
||||
continue
|
||||
}
|
||||
s.bindRequiredSystems()
|
||||
|
||||
if inputSys, ok := s.Systems[idx].(*InputSystem); ok && s.sceneSystems.InputSystem == nil {
|
||||
s.sceneSystems.InputSystem = inputSys
|
||||
continue
|
||||
}
|
||||
|
||||
if objFactory, ok := s.Systems[idx].(*GameObjectFactory); ok && s.sceneSystems.GameObjectFactory == nil {
|
||||
s.sceneSystems.GameObjectFactory = objFactory
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
if s.sceneSystems.RenderSystem == nil {
|
||||
s.Info("waiting for render system ...")
|
||||
return
|
||||
}
|
||||
|
||||
if s.sceneSystems.RenderSystem.renderer == nil {
|
||||
s.Info("waiting for renderer instance ...")
|
||||
return
|
||||
}
|
||||
|
||||
if s.sceneSystems.InputSystem == nil {
|
||||
s.Info("waiting for input system")
|
||||
return
|
||||
}
|
||||
|
||||
if s.sceneSystems.GameObjectFactory == nil {
|
||||
s.Info("waiting for game object factory ...")
|
||||
if !s.requiredSystemsPresent() {
|
||||
return
|
||||
}
|
||||
|
||||
s.setupFactories()
|
||||
|
||||
s.sceneSystems.SpriteFactory.RenderSystem = s.sceneSystems.RenderSystem
|
||||
s.sceneSystems.ShapeSystem.RenderSystem = s.sceneSystems.RenderSystem
|
||||
s.setupSceneObjectFactories()
|
||||
|
||||
const (
|
||||
defaultWidth = 800
|
||||
@ -168,12 +136,67 @@ func (s *BaseScene) boot() {
|
||||
|
||||
s.Add.Viewport(mainViewport, defaultWidth, defaultHeight)
|
||||
|
||||
s.Info("base scene booted!")
|
||||
s.Debug("base scene booted!")
|
||||
s.booted = true
|
||||
}
|
||||
|
||||
func (s *BaseScene) bindRequiredSystems() {
|
||||
for idx := range s.Systems {
|
||||
noRenderSys := s.sceneSystems.RenderSystem == nil
|
||||
noInputSys := s.sceneSystems.InputSystem == nil
|
||||
noObjectFactory := s.sceneSystems.GameObjectFactory == nil
|
||||
|
||||
sys := s.Systems[idx]
|
||||
|
||||
if rendersys, found := sys.(*RenderSystem); found && noRenderSys {
|
||||
s.sceneSystems.RenderSystem = rendersys
|
||||
continue
|
||||
}
|
||||
|
||||
if inputSys, found := sys.(*InputSystem); found && noInputSys {
|
||||
s.sceneSystems.InputSystem = inputSys
|
||||
continue
|
||||
}
|
||||
|
||||
if objFactory, found := sys.(*GameObjectFactory); found && noObjectFactory {
|
||||
s.sceneSystems.GameObjectFactory = objFactory
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *BaseScene) requiredSystemsPresent() bool {
|
||||
if s.sceneSystems.RenderSystem == nil {
|
||||
s.Debug("waiting for render system ...")
|
||||
return false
|
||||
}
|
||||
|
||||
if s.sceneSystems.RenderSystem.renderer == nil {
|
||||
s.Debug("waiting for renderer instance ...")
|
||||
return false
|
||||
}
|
||||
|
||||
if s.sceneSystems.InputSystem == nil {
|
||||
s.Debug("waiting for input system")
|
||||
return false
|
||||
}
|
||||
|
||||
if s.sceneSystems.GameObjectFactory == nil {
|
||||
s.Debug("waiting for game object factory ...")
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *BaseScene) setupSceneObjectFactories() {
|
||||
s.sceneSystems.SpriteFactory.RenderSystem = s.sceneSystems.RenderSystem
|
||||
s.sceneSystems.ShapeSystem.RenderSystem = s.sceneSystems.RenderSystem
|
||||
s.sceneSystems.UIWidgetFactory.RenderSystem = s.sceneSystems.RenderSystem
|
||||
}
|
||||
|
||||
func (s *BaseScene) setupFactories() {
|
||||
s.Info("setting up component factories")
|
||||
s.Debug("setting up component factories")
|
||||
|
||||
s.InjectComponent(&d2components.MainViewport{}, &s.MainViewport)
|
||||
s.InjectComponent(&d2components.Viewport{}, &s.Viewport)
|
||||
|
@ -47,7 +47,7 @@ type EbitenSplashScene struct {
|
||||
func (s *EbitenSplashScene) Init(world *akara.World) {
|
||||
s.World = world
|
||||
|
||||
s.Info("initializing ...")
|
||||
s.Debug("initializing ...")
|
||||
}
|
||||
|
||||
func (s *EbitenSplashScene) boot() {
|
||||
@ -126,7 +126,7 @@ func (s *EbitenSplashScene) createSplash() {
|
||||
interactive.InputVector.SetMouseButton(d2input.MouseButtonLeft)
|
||||
|
||||
interactive.Callback = func() bool {
|
||||
s.Info("hiding splash scene")
|
||||
s.Debug("hiding splash scene")
|
||||
|
||||
s.timeElapsed = splashTimeout
|
||||
|
||||
@ -151,7 +151,7 @@ func (s *EbitenSplashScene) updateSplash() {
|
||||
if vpAlpha.Alpha <= 0 {
|
||||
vpAlpha.Alpha = 0
|
||||
|
||||
s.Info("finished, deactivating")
|
||||
s.Debug("finished, deactivating")
|
||||
s.SetActive(false)
|
||||
}
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
package d2systems
|
@ -46,7 +46,7 @@ type LoadingScene struct {
|
||||
func (s *LoadingScene) Init(world *akara.World) {
|
||||
s.World = world
|
||||
|
||||
s.Info("initializing ...")
|
||||
s.Debug("initializing ...")
|
||||
|
||||
s.backgroundColor = color.Black
|
||||
|
||||
@ -54,94 +54,51 @@ func (s *LoadingScene) Init(world *akara.World) {
|
||||
}
|
||||
|
||||
func (s *LoadingScene) setupSubscriptions() {
|
||||
s.Info("setting up component subscriptions")
|
||||
s.Debug("setting up component subscriptions")
|
||||
|
||||
stage1 := s.NewComponentFilter().
|
||||
Require(
|
||||
&d2components.FilePath{},
|
||||
&d2components.File{},
|
||||
).
|
||||
Forbid( // but we forbid files that are already loaded
|
||||
&d2components.FileType{},
|
||||
&d2components.FileHandle{},
|
||||
&d2components.FileLoaded{},
|
||||
&d2components.FileSource{},
|
||||
&d2components.GameConfig{},
|
||||
&d2components.StringTable{},
|
||||
&d2components.DataDictionary{},
|
||||
&d2components.Palette{},
|
||||
&d2components.PaletteTransform{},
|
||||
&d2components.Cof{},
|
||||
&d2components.Dc6{},
|
||||
&d2components.Dcc{},
|
||||
&d2components.Ds1{},
|
||||
&d2components.Dt1{},
|
||||
&d2components.Wav{},
|
||||
&d2components.AnimationData{},
|
||||
).
|
||||
Build()
|
||||
|
||||
stage2 := s.NewComponentFilter().
|
||||
Require(
|
||||
&d2components.FilePath{},
|
||||
&d2components.File{},
|
||||
&d2components.FileType{},
|
||||
).
|
||||
Forbid( // but we forbid files that are already loaded
|
||||
&d2components.FileHandle{},
|
||||
&d2components.FileLoaded{},
|
||||
&d2components.FileSource{},
|
||||
&d2components.GameConfig{},
|
||||
&d2components.StringTable{},
|
||||
&d2components.DataDictionary{},
|
||||
&d2components.Palette{},
|
||||
&d2components.PaletteTransform{},
|
||||
&d2components.Cof{},
|
||||
&d2components.Dc6{},
|
||||
&d2components.Dcc{},
|
||||
&d2components.Ds1{},
|
||||
&d2components.Dt1{},
|
||||
&d2components.Wav{},
|
||||
&d2components.AnimationData{},
|
||||
).
|
||||
Build()
|
||||
|
||||
stage3 := s.NewComponentFilter().
|
||||
Require(
|
||||
&d2components.FilePath{},
|
||||
&d2components.File{},
|
||||
&d2components.FileType{},
|
||||
&d2components.FileHandle{},
|
||||
).
|
||||
Forbid( // but we forbid files that are already loaded
|
||||
&d2components.FileLoaded{},
|
||||
&d2components.FileSource{},
|
||||
&d2components.GameConfig{},
|
||||
&d2components.StringTable{},
|
||||
&d2components.DataDictionary{},
|
||||
&d2components.Palette{},
|
||||
&d2components.PaletteTransform{},
|
||||
&d2components.Cof{},
|
||||
&d2components.Dc6{},
|
||||
&d2components.Dcc{},
|
||||
&d2components.Ds1{},
|
||||
&d2components.Dt1{},
|
||||
&d2components.Wav{},
|
||||
&d2components.AnimationData{},
|
||||
).
|
||||
Build()
|
||||
|
||||
// we want to know about loaded files, too
|
||||
stage4 := s.NewComponentFilter().
|
||||
RequireOne(
|
||||
Require(
|
||||
&d2components.File{},
|
||||
&d2components.FileType{},
|
||||
&d2components.FileHandle{},
|
||||
&d2components.FileLoaded{},
|
||||
).
|
||||
Forbid( // but we forbid files that are already loaded
|
||||
&d2components.FileSource{},
|
||||
&d2components.GameConfig{},
|
||||
&d2components.StringTable{},
|
||||
&d2components.DataDictionary{},
|
||||
&d2components.Palette{},
|
||||
&d2components.PaletteTransform{},
|
||||
&d2components.Cof{},
|
||||
&d2components.Dc6{},
|
||||
&d2components.Dcc{},
|
||||
&d2components.Ds1{},
|
||||
&d2components.Dt1{},
|
||||
&d2components.Wav{},
|
||||
&d2components.AnimationData{},
|
||||
).
|
||||
Build()
|
||||
|
||||
@ -163,7 +120,7 @@ func (s *LoadingScene) boot() {
|
||||
}
|
||||
|
||||
func (s *LoadingScene) createLoadingScreen() {
|
||||
s.Info("creating loading screen")
|
||||
s.Info("creating loading sprite")
|
||||
s.loadingSprite = s.Add.Sprite(0, 0, d2resource.LoadingScreen, d2resource.PaletteLoading)
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,7 @@ type MainMenuScene struct {
|
||||
func (s *MainMenuScene) Init(world *akara.World) {
|
||||
s.World = world
|
||||
|
||||
s.Info("initializing ...")
|
||||
s.Debug("initializing ...")
|
||||
}
|
||||
|
||||
func (s *MainMenuScene) boot() {
|
||||
@ -73,7 +73,7 @@ func (s *MainMenuScene) boot() {
|
||||
}
|
||||
|
||||
func (s *MainMenuScene) setupViewports() {
|
||||
s.Info("setting up viewports")
|
||||
s.Debug("setting up viewports")
|
||||
|
||||
imgPath := d2resource.GameSelectScreen
|
||||
palPath := d2resource.PaletteSky
|
||||
@ -82,7 +82,7 @@ func (s *MainMenuScene) setupViewports() {
|
||||
}
|
||||
|
||||
func (s *MainMenuScene) createBackground() {
|
||||
s.Info("creating background")
|
||||
s.Debug("creating background")
|
||||
|
||||
imgPath := d2resource.GameSelectScreen
|
||||
palPath := d2resource.PaletteSky
|
||||
@ -91,7 +91,7 @@ func (s *MainMenuScene) createBackground() {
|
||||
}
|
||||
|
||||
func (s *MainMenuScene) createLogo() {
|
||||
s.Info("creating logo")
|
||||
s.Debug("creating logo")
|
||||
|
||||
const (
|
||||
logoX, logoY = 400, 120
|
||||
@ -108,11 +108,11 @@ func (s *MainMenuScene) createLogo() {
|
||||
}
|
||||
|
||||
func (s *MainMenuScene) createButtons() {
|
||||
s.Info("creating buttons")
|
||||
s.Debug("creating buttons")
|
||||
}
|
||||
|
||||
func (s *MainMenuScene) createTrademarkScreen() {
|
||||
s.Info("creating trademark screen")
|
||||
s.Debug("creating trademark screen")
|
||||
|
||||
imgPath := d2resource.TrademarkScreen
|
||||
palPath := d2resource.PaletteSky
|
||||
@ -124,7 +124,7 @@ func (s *MainMenuScene) createTrademarkScreen() {
|
||||
interactive.InputVector.SetMouseButton(d2input.MouseButtonLeft)
|
||||
|
||||
interactive.Callback = func() bool {
|
||||
s.Info("hiding trademark sprite")
|
||||
s.Debug("hiding trademark sprite")
|
||||
|
||||
alpha := s.AddAlpha(s.sprites.trademark)
|
||||
|
||||
@ -166,7 +166,7 @@ func (s *MainMenuScene) Update() {
|
||||
}
|
||||
|
||||
if !s.logoInit {
|
||||
s.Info("attempting logo sprite init")
|
||||
s.Debug("attempting logo sprite init")
|
||||
s.initLogoSprites()
|
||||
}
|
||||
|
||||
@ -201,7 +201,7 @@ func (s *MainMenuScene) initLogoSprites() {
|
||||
}
|
||||
}
|
||||
|
||||
s.Info("initializing logo sprites")
|
||||
s.Debug("initializing logo sprites")
|
||||
|
||||
for _, id := range logoSprites {
|
||||
sprite, _ := s.GetSprite(id)
|
||||
|
@ -44,7 +44,7 @@ type MouseCursorScene struct {
|
||||
func (s *MouseCursorScene) Init(world *akara.World) {
|
||||
s.World = world
|
||||
|
||||
s.Info("initializing ...")
|
||||
s.Debug("initializing ...")
|
||||
}
|
||||
|
||||
func (s *MouseCursorScene) boot() {
|
||||
@ -61,7 +61,7 @@ func (s *MouseCursorScene) boot() {
|
||||
}
|
||||
|
||||
func (s *MouseCursorScene) createMouseCursor() {
|
||||
s.Info("creating mouse cursor")
|
||||
s.Debug("creating mouse cursor")
|
||||
s.cursor = s.Add.Sprite(0, 0, d2resource.CursorDefault, d2resource.PaletteUnits)
|
||||
}
|
||||
|
||||
@ -136,7 +136,7 @@ func (s *MouseCursorScene) registerTerminalCommands() {
|
||||
}
|
||||
|
||||
func (s *MouseCursorScene) registerDebugCommand() {
|
||||
s.Info("registering debug command")
|
||||
s.Debug("registering debug command")
|
||||
|
||||
const (
|
||||
command = "debug_mouse"
|
||||
|
@ -87,3 +87,30 @@ func (s *sceneObjectFactory) Rectangle(x, y, width, height int, c color.Color) a
|
||||
|
||||
return eid
|
||||
}
|
||||
|
||||
func (s *sceneObjectFactory) Button(x, y float64, imgPath, palPath string) akara.EID {
|
||||
s.Debug("creating button")
|
||||
|
||||
eid := s.sceneSystems.UIWidgetFactory.Button(x, y, imgPath, palPath)
|
||||
|
||||
s.addBasicComponents(eid)
|
||||
|
||||
transform := s.AddTransform(eid)
|
||||
transform.Translation.X, transform.Translation.Y = float64(x), float64(y)
|
||||
|
||||
s.SceneObjects = append(s.SceneObjects, eid)
|
||||
|
||||
return eid
|
||||
}
|
||||
|
||||
func (s *sceneObjectFactory) Label(fontPath, spritePath, palettePath string) akara.EID {
|
||||
s.Debug("creating label")
|
||||
|
||||
eid := s.sceneSystems.UIWidgetFactory.Label(fontPath, spritePath, palettePath)
|
||||
|
||||
s.addBasicComponents(eid)
|
||||
|
||||
s.SceneObjects = append(s.SceneObjects, eid)
|
||||
|
||||
return eid
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ package d2systems
|
||||
const (
|
||||
scenePriorityMainMenu = iota
|
||||
scenePriorityLoading
|
||||
scenePriorityTerminal
|
||||
scenePriorityMouseCursor
|
||||
scenePriorityTerminal
|
||||
scenePriorityEbitenSplash
|
||||
)
|
||||
|
@ -44,7 +44,7 @@ type ShapeSystem struct {
|
||||
func (t *ShapeSystem) Init(world *akara.World) {
|
||||
t.World = world
|
||||
|
||||
t.Info("initializing sprite factory ...")
|
||||
t.Debug("initializing sprite factory ...")
|
||||
|
||||
t.setupFactories()
|
||||
t.setupSubscriptions()
|
||||
|
@ -1,6 +1,8 @@
|
||||
package d2systems
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
@ -13,11 +15,16 @@ const (
|
||||
fmtCreateSpriteErr = "could not create sprite from image `%s` and palette `%s`"
|
||||
)
|
||||
|
||||
// NewSpriteFactorySubsystem creates a new sprite factory which is intended
|
||||
const (
|
||||
spriteCacheBudget = 1024
|
||||
)
|
||||
|
||||
// NewSpriteFactory creates a new sprite factory which is intended
|
||||
// to be embedded in the game object factory system.
|
||||
func NewSpriteFactorySubsystem(b akara.BaseSystem, l *d2util.Logger) *SpriteFactory {
|
||||
func NewSpriteFactory(b akara.BaseSystem, l *d2util.Logger) *SpriteFactory {
|
||||
sys := &SpriteFactory{
|
||||
Logger: l,
|
||||
cache: d2cache.CreateCache(spriteCacheBudget),
|
||||
}
|
||||
|
||||
sys.BaseSystem = b
|
||||
@ -33,13 +40,13 @@ type spriteLoadQueueEntry struct {
|
||||
|
||||
type spriteLoadQueue = map[akara.EID]spriteLoadQueueEntry
|
||||
|
||||
// SpriteFactory is responsible for queueing sprites to be loaded (as spriteations),
|
||||
// SpriteFactory is responsible for queueing sprites to be loaded (as sprites),
|
||||
// as well as binding the spriteation to a renderer if one is present (which generates the sprite surfaces).
|
||||
type SpriteFactory struct {
|
||||
akara.BaseSubscriberSystem
|
||||
*d2util.Logger
|
||||
RenderSystem *RenderSystem
|
||||
d2components.FilePathFactory
|
||||
d2components.FileFactory
|
||||
d2components.TransformFactory
|
||||
d2components.Dc6Factory
|
||||
d2components.DccFactory
|
||||
@ -51,13 +58,14 @@ type SpriteFactory struct {
|
||||
loadQueue spriteLoadQueue
|
||||
spritesToRender *akara.Subscription
|
||||
spritesToUpdate *akara.Subscription
|
||||
cache d2interface.Cache
|
||||
}
|
||||
|
||||
// Init the sprite factory, injecting the necessary components
|
||||
func (t *SpriteFactory) Init(world *akara.World) {
|
||||
t.World = world
|
||||
|
||||
t.Info("initializing sprite factory ...")
|
||||
t.Debug("initializing sprite factory ...")
|
||||
|
||||
t.setupFactories()
|
||||
t.setupSubscriptions()
|
||||
@ -66,7 +74,7 @@ func (t *SpriteFactory) Init(world *akara.World) {
|
||||
}
|
||||
|
||||
func (t *SpriteFactory) setupFactories() {
|
||||
t.InjectComponent(&d2components.FilePath{}, &t.FilePath)
|
||||
t.InjectComponent(&d2components.File{}, &t.File)
|
||||
t.InjectComponent(&d2components.Transform{}, &t.Transform)
|
||||
t.InjectComponent(&d2components.Dc6{}, &t.Dc6)
|
||||
t.InjectComponent(&d2components.Dcc{}, &t.Dcc)
|
||||
@ -79,12 +87,12 @@ func (t *SpriteFactory) setupFactories() {
|
||||
|
||||
func (t *SpriteFactory) setupSubscriptions() {
|
||||
spritesToRender := t.NewComponentFilter().
|
||||
Require(&d2components.Sprite{}). // we want to process entities that have an spriteation ...
|
||||
Require(&d2components.Sprite{}). // we want to process entities that have an sprite ...
|
||||
Forbid(&d2components.Texture{}). // ... but are missing a surface
|
||||
Build()
|
||||
|
||||
spritesToUpdate := t.NewComponentFilter().
|
||||
Require(&d2components.Sprite{}). // we want to process entities that have an spriteation ...
|
||||
Require(&d2components.Sprite{}). // we want to process entities that have an sprite ...
|
||||
Require(&d2components.Texture{}). // ... but are missing a surface
|
||||
Build()
|
||||
|
||||
@ -92,8 +100,8 @@ func (t *SpriteFactory) setupSubscriptions() {
|
||||
t.spritesToUpdate = t.AddSubscription(spritesToUpdate)
|
||||
}
|
||||
|
||||
// Update processes the load queue which attempting to create spriteations, as well as
|
||||
// binding existing spriteations to a renderer if one is present.
|
||||
// Update processes the load queue which attempting to create sprites, as well as
|
||||
// binding existing sprites to a renderer if one is present.
|
||||
func (t *SpriteFactory) Update() {
|
||||
for spriteID := range t.loadQueue {
|
||||
t.tryCreatingSprite(spriteID)
|
||||
@ -116,8 +124,8 @@ func (t *SpriteFactory) Sprite(x, y float64, imgPath, palPath string) akara.EID
|
||||
transform.Translation.X, transform.Translation.Y = x, y
|
||||
|
||||
imgID, palID := t.NewEntity(), t.NewEntity()
|
||||
t.AddFilePath(imgID).Path = imgPath
|
||||
t.AddFilePath(palID).Path = palPath
|
||||
t.AddFile(imgID).Path = imgPath
|
||||
t.AddFile(palID).Path = palPath
|
||||
|
||||
t.loadQueue[spriteID] = spriteLoadQueueEntry{
|
||||
spriteImage: imgID,
|
||||
@ -144,12 +152,12 @@ func (t *SpriteFactory) tryCreatingSprite(id akara.EID) {
|
||||
entry := t.loadQueue[id]
|
||||
imageID, paletteID := entry.spriteImage, entry.spritePalette
|
||||
|
||||
imagePath, found := t.GetFilePath(imageID)
|
||||
imageFile, found := t.GetFile(imageID)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
palettePath, found := t.GetFilePath(paletteID)
|
||||
paletteFile, found := t.GetFile(paletteID)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
@ -163,23 +171,33 @@ func (t *SpriteFactory) tryCreatingSprite(id akara.EID) {
|
||||
|
||||
var err error
|
||||
|
||||
if dc6, found := t.GetDc6(imageID); found {
|
||||
sprite, err = t.createDc6Sprite(dc6, palette)
|
||||
cacheKey := spriteCacheKey(imageFile.Path, paletteFile.Path)
|
||||
if iface, found := t.cache.Retrieve(cacheKey); found {
|
||||
sprite = iface.(d2interface.Sprite)
|
||||
}
|
||||
|
||||
if dcc, found := t.GetDcc(imageID); found {
|
||||
if dc6, found := t.GetDc6(imageID); found && sprite == nil {
|
||||
sprite, err = t.createDc6Sprite(dc6, palette)
|
||||
_ = t.cache.Insert(cacheKey, sprite, 1)
|
||||
}
|
||||
|
||||
if dcc, found := t.GetDcc(imageID); found && sprite == nil {
|
||||
sprite, err = t.createDccSprite(dcc, palette)
|
||||
_ = t.cache.Insert(cacheKey, sprite, 1)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
t.Errorf(fmtCreateSpriteErr, imagePath.Path, palettePath.Path)
|
||||
t.Errorf(fmtCreateSpriteErr, imageFile.Path, paletteFile.Path)
|
||||
|
||||
t.RemoveEntity(id)
|
||||
t.RemoveEntity(imageID)
|
||||
t.RemoveEntity(paletteID)
|
||||
}
|
||||
|
||||
t.AddSprite(id).Sprite = sprite
|
||||
spriteComponent := t.AddSprite(id)
|
||||
spriteComponent.Sprite = sprite
|
||||
spriteComponent.SpritePath = imageFile.Path
|
||||
spriteComponent.PalettePath = paletteFile.Path
|
||||
|
||||
delete(t.loadQueue, id)
|
||||
}
|
||||
@ -263,3 +281,7 @@ func (t *SpriteFactory) createDccSprite(
|
||||
) (d2interface.Sprite, error) {
|
||||
return d2sprite.NewDCCSprite(dcc.DCC, pal.Palette, 0)
|
||||
}
|
||||
|
||||
func spriteCacheKey(imgpath, palpath string) string {
|
||||
return fmt.Sprintf("%s::%s", imgpath, palpath)
|
||||
}
|
||||
|
@ -51,13 +51,13 @@ type TerminalScene struct {
|
||||
func (s *TerminalScene) Init(world *akara.World) {
|
||||
s.World = world
|
||||
|
||||
s.Info("initializing ...")
|
||||
s.Debug("initializing ...")
|
||||
|
||||
s.setupSubscriptions()
|
||||
}
|
||||
|
||||
func (s *TerminalScene) setupSubscriptions() {
|
||||
s.Info("setting up component subscriptions")
|
||||
s.Debug("setting up component subscriptions")
|
||||
|
||||
commandsToRegister := s.NewComponentFilter().
|
||||
Require(
|
||||
|
112
d2core/d2systems/scene_test_label.go
Normal file
112
d2core/d2systems/scene_test_label.go
Normal file
@ -0,0 +1,112 @@
|
||||
package d2systems
|
||||
|
||||
import (
|
||||
"image/color"
|
||||
"math/rand"
|
||||
|
||||
"github.com/gravestench/akara"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2components"
|
||||
)
|
||||
|
||||
const (
|
||||
sceneKeyLabelTest = "Label Test Scene"
|
||||
)
|
||||
|
||||
// NewLabelTestScene creates a new main menu scene. This is the first screen that the user
|
||||
// will see when launching the game.
|
||||
func NewLabelTestScene() *LabelTestScene {
|
||||
scene := &LabelTestScene{
|
||||
BaseScene: NewBaseScene(sceneKeyLabelTest),
|
||||
}
|
||||
|
||||
return scene
|
||||
}
|
||||
|
||||
// static check that LabelTestScene implements the scene interface
|
||||
var _ d2interface.Scene = &LabelTestScene{}
|
||||
|
||||
// LabelTestScene represents the game's main menu, where users can select single or multi player,
|
||||
// or start the map engine test.
|
||||
type LabelTestScene struct {
|
||||
*BaseScene
|
||||
booted bool
|
||||
labels *akara.Subscription
|
||||
}
|
||||
|
||||
// Init the main menu scene
|
||||
func (s *LabelTestScene) Init(world *akara.World) {
|
||||
s.World = world
|
||||
|
||||
labels := s.World.NewComponentFilter().Require(&d2components.Label{}).Build()
|
||||
s.labels = s.World.AddSubscription(labels)
|
||||
|
||||
s.Debug("initializing ...")
|
||||
}
|
||||
|
||||
func (s *LabelTestScene) boot() {
|
||||
if !s.BaseScene.booted {
|
||||
s.BaseScene.boot()
|
||||
return
|
||||
}
|
||||
|
||||
s.createLabels()
|
||||
|
||||
s.booted = true
|
||||
}
|
||||
|
||||
func (s *LabelTestScene) createLabels() {
|
||||
for idx := 0; idx < 1000; idx++ {
|
||||
l := s.Add.Label("LOLWUT", d2resource.Font24, d2resource.PaletteStatic)
|
||||
trs := s.AddTransform(l)
|
||||
trs.Translation.Set(rand.Float64()*800, rand.Float64()*600, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Update the main menu scene
|
||||
func (s *LabelTestScene) Update() {
|
||||
if s.Paused() {
|
||||
return
|
||||
}
|
||||
|
||||
for _, eid := range s.labels.GetEntities() {
|
||||
//s.setLabelBackground(eid)
|
||||
s.updatePosition(eid)
|
||||
}
|
||||
|
||||
if !s.booted {
|
||||
s.boot()
|
||||
}
|
||||
|
||||
s.BaseScene.Update()
|
||||
}
|
||||
|
||||
func (s *LabelTestScene) setLabelBackground(eid akara.EID) {
|
||||
label, found := s.GetLabel(eid)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
label.SetBackgroundColor(color.Black)
|
||||
}
|
||||
|
||||
func (s *LabelTestScene) updatePosition(eid akara.EID) {
|
||||
trs, found := s.GetTransform(eid)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
x, y, z := trs.Translation.AddScalar(1).XYZ()
|
||||
|
||||
if x > 800 {
|
||||
x -= 800
|
||||
}
|
||||
|
||||
if y > 600 {
|
||||
y -= 600
|
||||
}
|
||||
|
||||
trs.Translation.Set(x, y, z)
|
||||
}
|
300
d2core/d2systems/scene_widget_system.go
Normal file
300
d2core/d2systems/scene_widget_system.go
Normal file
@ -0,0 +1,300 @@
|
||||
package d2systems
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2cache"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2bitmapfont"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2components"
|
||||
"github.com/gravestench/akara"
|
||||
"image/color"
|
||||
)
|
||||
|
||||
const (
|
||||
fontCacheBudget = 64
|
||||
)
|
||||
|
||||
// NewWidgetFactory creates a new ui widget factory which is intended
|
||||
// to be embedded in the game object factory system.
|
||||
func NewUIWidgetFactory(
|
||||
b akara.BaseSystem,
|
||||
l *d2util.Logger,
|
||||
spriteFactory *SpriteFactory,
|
||||
shapeFactory *ShapeSystem,
|
||||
) *UIWidgetFactory {
|
||||
sys := &UIWidgetFactory{
|
||||
Logger: l,
|
||||
SpriteFactory: spriteFactory,
|
||||
ShapeSystem: shapeFactory,
|
||||
bitmapFontCache: d2cache.CreateCache(fontCacheBudget),
|
||||
buttonLoadQueue: make(buttonLoadQueue),
|
||||
labelLoadQueue: make(labelLoadQueue),
|
||||
}
|
||||
|
||||
sys.BaseSystem = b
|
||||
|
||||
sys.World.AddSystem(sys)
|
||||
|
||||
return sys
|
||||
}
|
||||
|
||||
type buttonLoadQueueEntry struct {
|
||||
sprite, palette akara.EID
|
||||
}
|
||||
|
||||
type buttonLoadQueue = map[akara.EID]buttonLoadQueueEntry
|
||||
|
||||
type labelLoadQueueEntry struct {
|
||||
table, sprite akara.EID
|
||||
}
|
||||
|
||||
type labelLoadQueue = map[akara.EID]labelLoadQueueEntry
|
||||
|
||||
// UIWidgetFactory is responsible for creating UI widgets like buttons and tabs
|
||||
type UIWidgetFactory struct {
|
||||
akara.BaseSubscriberSystem
|
||||
*d2util.Logger
|
||||
*RenderSystem
|
||||
*SpriteFactory
|
||||
*ShapeSystem
|
||||
buttonLoadQueue
|
||||
labelLoadQueue
|
||||
bitmapFontCache d2interface.Cache
|
||||
d2components.FileFactory
|
||||
d2components.TransformFactory
|
||||
d2components.InteractiveFactory
|
||||
d2components.FontTableFactory
|
||||
d2components.PaletteFactory
|
||||
d2components.BitmapFontFactory
|
||||
d2components.LabelFactory
|
||||
labelsToUpdate *akara.Subscription
|
||||
booted bool
|
||||
}
|
||||
|
||||
// Init the ui widget factory, injecting the necessary components
|
||||
func (t *UIWidgetFactory) Init(world *akara.World) {
|
||||
t.World = world
|
||||
|
||||
t.Debug("initializing ui widget factory ...")
|
||||
|
||||
t.setupFactories()
|
||||
t.setupSubscriptions()
|
||||
}
|
||||
|
||||
func (t *UIWidgetFactory) setupFactories() {
|
||||
t.InjectComponent(&d2components.File{}, &t.File)
|
||||
t.InjectComponent(&d2components.Transform{}, &t.Transform)
|
||||
t.InjectComponent(&d2components.Interactive{}, &t.Interactive)
|
||||
t.InjectComponent(&d2components.FontTable{}, &t.FontTable)
|
||||
t.InjectComponent(&d2components.Palette{}, &t.Palette)
|
||||
t.InjectComponent(&d2components.BitmapFont{}, &t.BitmapFont)
|
||||
t.InjectComponent(&d2components.Label{}, &t.LabelFactory.Label)
|
||||
}
|
||||
|
||||
func (t *UIWidgetFactory) setupSubscriptions() {
|
||||
labelsToUpdate := t.NewComponentFilter().
|
||||
Require(&d2components.Label{}).
|
||||
Build()
|
||||
|
||||
t.labelsToUpdate = t.AddSubscription(labelsToUpdate)
|
||||
}
|
||||
|
||||
func (t *UIWidgetFactory) boot() {
|
||||
if t.RenderSystem == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if t.RenderSystem.renderer == nil {
|
||||
return
|
||||
}
|
||||
|
||||
t.booted = true
|
||||
}
|
||||
|
||||
// Update processes the load queues and update the widgets. The load queues are necessary because
|
||||
// UI widgets are composed of a bunch of things, which each need to be loaded by other systems (like the asset loader)
|
||||
func (t *UIWidgetFactory) Update() {
|
||||
if !t.booted {
|
||||
t.boot()
|
||||
return
|
||||
}
|
||||
|
||||
for labelEID := range t.labelLoadQueue {
|
||||
t.processLabel(labelEID)
|
||||
}
|
||||
|
||||
for _, labelEID := range t.labelsToUpdate.GetEntities() {
|
||||
t.renderLabel(labelEID)
|
||||
}
|
||||
}
|
||||
|
||||
// Label creates a label widget.
|
||||
//
|
||||
// The font is assumed to be a path for two files, omiting the file extension
|
||||
//
|
||||
// Basically, diablo2 stored bitmap fonts as two files, a glyph table and sprite.
|
||||
//
|
||||
// For example, specifying this font: /data/local/FONT/ENG/fontexocet10
|
||||
//
|
||||
// will use these two files:
|
||||
//
|
||||
// /data/local/FONT/ENG/fontexocet10.dc6
|
||||
//
|
||||
// /data/local/FONT/ENG/fontexocet10.tbl
|
||||
func (t *UIWidgetFactory) Label(text, font, palettePath string) akara.EID {
|
||||
tablePath := font + ".tbl"
|
||||
spritePath := font + ".dc6"
|
||||
|
||||
labelEID := t.NewEntity()
|
||||
|
||||
tableEID := t.NewEntity()
|
||||
t.AddFile(tableEID).Path = tablePath
|
||||
|
||||
spriteEID := t.SpriteFactory.Sprite(0, 0, spritePath, palettePath)
|
||||
|
||||
t.labelLoadQueue[labelEID] = labelLoadQueueEntry{
|
||||
table: tableEID,
|
||||
sprite: spriteEID,
|
||||
}
|
||||
|
||||
label := t.AddLabel(labelEID)
|
||||
label.SetText(text)
|
||||
|
||||
return labelEID
|
||||
}
|
||||
|
||||
// Label creates a label widget
|
||||
func (t *UIWidgetFactory) processLabel(labelEID akara.EID) {
|
||||
bmfComponent, found := t.GetBitmapFont(labelEID)
|
||||
if !found {
|
||||
t.addBitmapFontForLabel(labelEID)
|
||||
return
|
||||
}
|
||||
|
||||
bmfComponent.Sprite.BindRenderer(t.renderer)
|
||||
|
||||
label, found := t.GetLabel(labelEID)
|
||||
if !found {
|
||||
label = t.AddLabel(labelEID)
|
||||
}
|
||||
|
||||
label.Font = bmfComponent.BitmapFont
|
||||
|
||||
t.RemoveEntity(t.labelLoadQueue[labelEID].table)
|
||||
|
||||
delete(t.labelLoadQueue, labelEID)
|
||||
}
|
||||
|
||||
func (t *UIWidgetFactory) renderLabel(labelEID akara.EID) {
|
||||
label, found := t.GetLabel(labelEID)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
bmf, found := t.GetBitmapFont(labelEID)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
if label.Font != bmf.BitmapFont {
|
||||
label.Font = bmf.BitmapFont
|
||||
}
|
||||
|
||||
if !label.IsDirty() {
|
||||
return
|
||||
}
|
||||
|
||||
texture, found := t.RenderSystem.GetTexture(labelEID)
|
||||
if !found {
|
||||
texture = t.RenderSystem.AddTexture(labelEID)
|
||||
}
|
||||
|
||||
texture.Texture = t.renderer.NewSurface(label.GetSize())
|
||||
|
||||
label.Render(texture.Texture)
|
||||
}
|
||||
|
||||
func (t *UIWidgetFactory) addBitmapFontForLabel(labelEID akara.EID) {
|
||||
// get the load queue
|
||||
entry, found := t.labelLoadQueue[labelEID]
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
// make sure the components have been loaded (by the asset loader)
|
||||
_, tableFound := t.GetFontTable(entry.table)
|
||||
_, spriteFound := t.GetSprite(entry.sprite)
|
||||
|
||||
if !(tableFound && spriteFound) {
|
||||
return
|
||||
}
|
||||
|
||||
// now we check the cache, see if we can just pull a pre-rendered bitmap font
|
||||
tableFile, found := t.GetFile(entry.table)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
sprite, found := t.GetSprite(entry.sprite)
|
||||
if !found {
|
||||
return
|
||||
}
|
||||
|
||||
cacheKey := fontCacheKey(tableFile.Path, sprite.SpritePath, sprite.PalettePath)
|
||||
|
||||
if iface, found := t.bitmapFontCache.Retrieve(cacheKey); found {
|
||||
// we found it, add the bitmap font component and set the embedded struct to what we retrieved
|
||||
t.AddBitmapFont(labelEID).BitmapFont = iface.(*d2bitmapfont.BitmapFont)
|
||||
delete(t.labelLoadQueue, labelEID)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
bmf := t.createBitmapFont(entry)
|
||||
if bmf == nil {
|
||||
return
|
||||
}
|
||||
|
||||
// we need to create and cache the bitmap font
|
||||
if err := t.bitmapFontCache.Insert(cacheKey, bmf, 1); err != nil {
|
||||
t.Warning(err.Error())
|
||||
}
|
||||
|
||||
t.AddBitmapFont(labelEID).BitmapFont = bmf
|
||||
}
|
||||
|
||||
func (t *UIWidgetFactory) createBitmapFont(entry labelLoadQueueEntry) *d2bitmapfont.BitmapFont {
|
||||
// make sure the components have been loaded (by the asset loader)
|
||||
table, tableFound := t.GetFontTable(entry.table)
|
||||
sprite, spriteFound := t.GetSprite(entry.sprite)
|
||||
|
||||
if !(tableFound && spriteFound) || sprite.Sprite == nil || table.Data == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return d2bitmapfont.New(sprite.Sprite, table.Data, color.White)
|
||||
}
|
||||
|
||||
func fontCacheKey(t, s, p string) string {
|
||||
return fmt.Sprintf("%s::%s::%s", t, s, p)
|
||||
}
|
||||
|
||||
// Button creates a button ui widget
|
||||
func (t *UIWidgetFactory) Button(x, y float64, imgPath, palPath string) akara.EID {
|
||||
buttonEID := t.NewEntity()
|
||||
|
||||
//transform := t.AddTransform(buttonEID)
|
||||
//transform.Translation.X, transform.Translation.Y = x, y
|
||||
//
|
||||
//imgID, palID := t.NewEntity(), t.NewEntity()
|
||||
//t.AddFile(imgID).Path = imgPath
|
||||
//t.AddFile(palID).Path = palPath
|
||||
//
|
||||
//t.buttonLoadQueue[buttonEID] = buttonLoadQueueEntry{
|
||||
// spriteImage: imgID,
|
||||
// spritePalette: palID,
|
||||
//}
|
||||
|
||||
return buttonEID
|
||||
}
|
@ -38,7 +38,7 @@ func (t *TimeScaleSystem) Init(world *akara.World) {
|
||||
t.Logger = d2util.NewLogger()
|
||||
t.SetPrefix(logPrefixTimeScaleSystem)
|
||||
|
||||
t.Info("initializing ...")
|
||||
t.Debug("initializing ...")
|
||||
|
||||
t.InjectComponent(&d2components.CommandRegistration{}, &t.CommandRegistration)
|
||||
t.InjectComponent(&d2components.Dirty{}, &t.Dirty)
|
||||
|
@ -30,7 +30,7 @@ func (u *UpdateCounter) Init(world *akara.World) {
|
||||
u.SetActive(false)
|
||||
}
|
||||
|
||||
u.Info("initializing")
|
||||
u.Debug("initializing")
|
||||
}
|
||||
|
||||
func (u *UpdateCounter) setupLogger() {
|
||||
|
@ -711,6 +711,11 @@ func getButtonLayouts() map[ButtonType]ButtonLayout {
|
||||
}
|
||||
}
|
||||
|
||||
// GetButtonLayout returns a button layout for the given button type
|
||||
func GetButtonLayout(t ButtonType) ButtonLayout {
|
||||
return getButtonLayouts()[t]
|
||||
}
|
||||
|
||||
var _ Widget = &Button{} // static check to ensure button implements widget
|
||||
|
||||
// Button defines a standard wide UI button
|
||||
|
@ -5,10 +5,11 @@ import "fmt"
|
||||
// ColorToken is a string which is used inside of label strings to set font color.
|
||||
type ColorToken string
|
||||
|
||||
// Color token formatting and pattern matching utility strings
|
||||
const (
|
||||
colorTokenFmt = `%s%s`
|
||||
colorTokenMatch = `\[[^\]]+\]` // nolint:gosec // has nothing to to with credentials
|
||||
colorStrMatch = colorTokenMatch + `[^\[]+`
|
||||
ColorTokenFmt = `%s%s`
|
||||
ColorTokenMatch = `\[[^\]]+\]` // nolint:gosec // has nothing to to with credentials
|
||||
ColorStrMatch = ColorTokenMatch + `[^\[]+`
|
||||
)
|
||||
|
||||
// Color tokens for colored labels
|
||||
@ -41,18 +42,18 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
colorGrey100Alpha = 0x69_69_69_ff
|
||||
colorWhite100Alpha = 0xff_ff_ff_ff
|
||||
colorBlue100Alpha = 0x69_69_ff_ff
|
||||
colorYellow100Alpha = 0xff_ff_64_ff
|
||||
colorGreen100Alpha = 0x00_ff_00_ff
|
||||
colorGold100Alpha = 0xc7_b3_77_ff
|
||||
colorOrange100Alpha = 0xff_a8_00_ff
|
||||
colorRed100Alpha = 0xff_77_77_ff
|
||||
colorBlack100Alpha = 0x00_00_00_ff
|
||||
ColorGrey100Alpha = 0x69_69_69_ff
|
||||
ColorWhite100Alpha = 0xff_ff_ff_ff
|
||||
ColorBlue100Alpha = 0x69_69_ff_ff
|
||||
ColorYellow100Alpha = 0xff_ff_64_ff
|
||||
ColorGreen100Alpha = 0x00_ff_00_ff
|
||||
ColorGold100Alpha = 0xc7_b3_77_ff
|
||||
ColorOrange100Alpha = 0xff_a8_00_ff
|
||||
ColorRed100Alpha = 0xff_77_77_ff
|
||||
ColorBlack100Alpha = 0x00_00_00_ff
|
||||
)
|
||||
|
||||
// ColorTokenize formats the string with the given color token
|
||||
func ColorTokenize(s string, t ColorToken) string {
|
||||
return fmt.Sprintf(colorTokenFmt, t, s)
|
||||
return fmt.Sprintf(ColorTokenFmt, t, s)
|
||||
}
|
||||
|
@ -114,8 +114,8 @@ func (v *Label) SetBackgroundColor(c color.Color) {
|
||||
}
|
||||
|
||||
func (v *Label) processColorTokens(str string) string {
|
||||
tokenMatch := regexp.MustCompile(colorTokenMatch)
|
||||
tokenStrMatch := regexp.MustCompile(colorStrMatch)
|
||||
tokenMatch := regexp.MustCompile(ColorTokenMatch)
|
||||
tokenStrMatch := regexp.MustCompile(ColorStrMatch)
|
||||
empty := []byte("")
|
||||
|
||||
tokenPosition := 0
|
||||
@ -175,15 +175,15 @@ func (v *Label) Advance(elapsed float64) error {
|
||||
func getColor(token ColorToken) color.Color {
|
||||
// https://github.com/OpenDiablo2/OpenDiablo2/issues/823
|
||||
colors := map[ColorToken]color.Color{
|
||||
ColorTokenGrey: d2util.Color(colorGrey100Alpha),
|
||||
ColorTokenWhite: d2util.Color(colorWhite100Alpha),
|
||||
ColorTokenBlue: d2util.Color(colorBlue100Alpha),
|
||||
ColorTokenYellow: d2util.Color(colorYellow100Alpha),
|
||||
ColorTokenGreen: d2util.Color(colorGreen100Alpha),
|
||||
ColorTokenGold: d2util.Color(colorGold100Alpha),
|
||||
ColorTokenOrange: d2util.Color(colorOrange100Alpha),
|
||||
ColorTokenRed: d2util.Color(colorRed100Alpha),
|
||||
ColorTokenBlack: d2util.Color(colorBlack100Alpha),
|
||||
ColorTokenGrey: d2util.Color(ColorGrey100Alpha),
|
||||
ColorTokenWhite: d2util.Color(ColorWhite100Alpha),
|
||||
ColorTokenBlue: d2util.Color(ColorBlue100Alpha),
|
||||
ColorTokenYellow: d2util.Color(ColorYellow100Alpha),
|
||||
ColorTokenGreen: d2util.Color(ColorGreen100Alpha),
|
||||
ColorTokenGold: d2util.Color(ColorGold100Alpha),
|
||||
ColorTokenOrange: d2util.Color(ColorOrange100Alpha),
|
||||
ColorTokenRed: d2util.Color(ColorRed100Alpha),
|
||||
ColorTokenBlack: d2util.Color(ColorBlack100Alpha),
|
||||
}
|
||||
|
||||
chosen := colors[token]
|
||||
|
2
go.mod
2
go.mod
@ -8,7 +8,7 @@ require (
|
||||
github.com/alecthomas/units v0.0.0-20201120081800-1786d5ef83d4 // indirect
|
||||
github.com/go-restruct/restruct v1.2.0-alpha
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/gravestench/akara v0.0.0-20201206061149-9be03b4110f2
|
||||
github.com/gravestench/akara v0.0.0-20201208183338-ab0934060133
|
||||
github.com/gravestench/pho v0.0.0-20201029002250-f9afbd637e4d
|
||||
github.com/hajimehoshi/ebiten/v2 v2.0.1
|
||||
github.com/pkg/profile v1.5.0
|
||||
|
2
go.sum
2
go.sum
@ -23,6 +23,8 @@ github.com/gravestench/akara v0.0.0-20201203202918-85b8a01d1130 h1:09fkM2hfORgZJ
|
||||
github.com/gravestench/akara v0.0.0-20201203202918-85b8a01d1130/go.mod h1:fTeda1SogMg5Lkd4lXMEd/Pk/a5/gQuLGaAI2rn1PBQ=
|
||||
github.com/gravestench/akara v0.0.0-20201206061149-9be03b4110f2 h1:mOIIK6AgIyaEslKsu+tsguzFWaMLGjlMuUKqOlABhGk=
|
||||
github.com/gravestench/akara v0.0.0-20201206061149-9be03b4110f2/go.mod h1:fTeda1SogMg5Lkd4lXMEd/Pk/a5/gQuLGaAI2rn1PBQ=
|
||||
github.com/gravestench/akara v0.0.0-20201208183338-ab0934060133 h1:P9XM5k63US1EavI+23k9GY84xNRnRtg0sT9rlCr4ew4=
|
||||
github.com/gravestench/akara v0.0.0-20201208183338-ab0934060133/go.mod h1:fTeda1SogMg5Lkd4lXMEd/Pk/a5/gQuLGaAI2rn1PBQ=
|
||||
github.com/gravestench/pho v0.0.0-20201029002250-f9afbd637e4d h1:CP+/y9SAdv9LifYvicxYdQNmzugykEahAiUhYolMROM=
|
||||
github.com/gravestench/pho v0.0.0-20201029002250-f9afbd637e4d/go.mod h1:yi5GHMLLWtHhs9tz3q1csUlgGKz5MhZoJcxV8NFBtkk=
|
||||
github.com/hajimehoshi/bitmapfont/v2 v2.1.0/go.mod h1:2BnYrkTQGThpr/CY6LorYtt/zEPNzvE/ND69CRTaHMs=
|
||||
|
Loading…
Reference in New Issue
Block a user