diff --git a/ColorConvert.go b/Common/ColorConvert.go similarity index 94% rename from ColorConvert.go rename to Common/ColorConvert.go index c0c537c1..db232e63 100644 --- a/ColorConvert.go +++ b/Common/ColorConvert.go @@ -1,4 +1,4 @@ -package OpenDiablo2 +package Common import ( "image/color" @@ -32,6 +32,7 @@ func now() int64 { return monotonicClock } +// ColorToColorM converts a normal color to a color matrix func ColorToColorM(clr color.Color) ebiten.ColorM { // RGBA() is in [0 - 0xffff]. Adjust them in [0 - 0xff]. cr, cg, cb, ca := clr.RGBA() diff --git a/Math.go b/Common/Math.go similarity index 89% rename from Math.go rename to Common/Math.go index 7f2d03c1..da4f0471 100644 --- a/Math.go +++ b/Common/Math.go @@ -1,4 +1,4 @@ -package OpenDiablo2 +package Common // Min returns the lower of two values func Min(a, b uint32) uint32 { @@ -16,7 +16,7 @@ func Max(a, b uint32) uint32 { return b } -// Max returns the higher of two values +// MaxInt32 returns the higher of two values func MaxInt32(a, b int32) int32 { if a > b { return a diff --git a/Palette.go b/Common/Palette.go similarity index 96% rename from Palette.go rename to Common/Palette.go index 67317096..bf74c14a 100644 --- a/Palette.go +++ b/Common/Palette.go @@ -1,4 +1,4 @@ -package OpenDiablo2 +package Common import "github.com/essial/OpenDiablo2/Palettes" diff --git a/Sprite.go b/Common/Sprite.go similarity index 99% rename from Sprite.go rename to Common/Sprite.go index 3e404759..4560bbbb 100644 --- a/Sprite.go +++ b/Common/Sprite.go @@ -1,4 +1,4 @@ -package OpenDiablo2 +package Common import ( "encoding/binary" diff --git a/Common/SpriteProvider.go b/Common/SpriteProvider.go new file mode 100644 index 00000000..37cf15e4 --- /dev/null +++ b/Common/SpriteProvider.go @@ -0,0 +1,8 @@ +package Common + +import "github.com/essial/OpenDiablo2/Palettes" + +// SpriteProvider is an instance that can provide sprites +type SpriteProvider interface { + LoadSprite(fileName string, palette Palettes.Palette) *Sprite +} diff --git a/Engine.go b/Engine.go index 227676bd..e7eabaab 100644 --- a/Engine.go +++ b/Engine.go @@ -11,6 +11,7 @@ import ( "github.com/essial/OpenDiablo2/Common" "github.com/essial/OpenDiablo2/Palettes" "github.com/essial/OpenDiablo2/ResourcePaths" + "github.com/essial/OpenDiablo2/UI" "github.com/hajimehoshi/ebiten" "github.com/hajimehoshi/ebiten/audio" @@ -40,22 +41,22 @@ const ( // Engine is the core OpenDiablo2 engine type Engine struct { - Settings EngineConfig // Engine configuration settings from json file - Files map[string]string // Map that defines which files are in which MPQs - Palettes map[Palettes.Palette]Palette // Color palettes - SoundEntries map[string]SoundEntry // Sound configurations - CursorSprite *Sprite // The sprite shown for cursors - LoadingSprite *Sprite // The sprite shown when loading stuff - CursorX int // X position of the cursor - CursorY int // Y position of the cursor - CursorButtons CursorButton // The buttons that are currently being pressed - LoadingProgress float64 // LoadingProcess is a range between 0.0 and 1.0. If set, loading screen displays. - CurrentScene Common.SceneInterface // The current scene being rendered - nextScene Common.SceneInterface // The next scene to be loaded at the end of the game loop - fontCache map[string]*MPQFont // The font cash - audioContext *audio.Context // The Audio context - bgmAudio *audio.Player // The audio player - fullscreenKey bool // When true, the fullscreen toggle is still being pressed + Settings EngineConfig // Engine configuration settings from json file + Files map[string]string // Map that defines which files are in which MPQs + Palettes map[Palettes.Palette]Common.Palette // Color palettes + SoundEntries map[string]SoundEntry // Sound configurations + LoadingSprite *Common.Sprite // The sprite shown when loading stuff + CursorX int // X position of the cursor + CursorY int // Y position of the cursor + CursorButtons CursorButton // The buttons that are currently being pressed + LoadingProgress float64 // LoadingProcess is a range between 0.0 and 1.0. If set, loading screen displays. + CurrentScene Common.SceneInterface // The current scene being rendered + UIManager *UI.Manager // The UI manager + nextScene Common.SceneInterface // The next scene to be loaded at the end of the game loop + fontCache map[string]*MPQFont // The font cash + audioContext *audio.Context // The Audio context + bgmAudio *audio.Player // The audio player + fullscreenKey bool // When true, the fullscreen toggle is still being pressed } // CreateEngine creates and instance of the OpenDiablo2 engine @@ -70,12 +71,12 @@ func CreateEngine() *Engine { result.mapMpqFiles() result.loadPalettes() result.loadSoundEntries() + result.UIManager = UI.CreateManager(result) audioContext, err := audio.NewContext(22050) if err != nil { log.Fatal(err) } result.audioContext = audioContext - result.CursorSprite = result.LoadSprite(ResourcePaths.CursorDefault, Palettes.Units) result.LoadingSprite = result.LoadSprite(ResourcePaths.LoadingScreen, Palettes.Loading) loadingSpriteSizeX, loadingSpriteSizeY := result.LoadingSprite.GetSize() result.LoadingSprite.MoveTo(int(400-(loadingSpriteSizeX/2)), int(300+(loadingSpriteSizeY/2))) @@ -146,7 +147,7 @@ func (v *Engine) IsLoading() bool { } func (v *Engine) loadPalettes() { - v.Palettes = make(map[Palettes.Palette]Palette) + v.Palettes = make(map[Palettes.Palette]Common.Palette) log.Println("loading palettes") for file := range v.Files { if strings.Index(file, "/data/global/palette/") != 0 || strings.Index(file, ".dat") != len(file)-4 { @@ -154,7 +155,7 @@ func (v *Engine) loadPalettes() { } nameParts := strings.Split(file, `/`) paletteName := Palettes.Palette(nameParts[len(nameParts)-2]) - palette := CreatePalette(paletteName, v.GetFile(file)) + palette := Common.CreatePalette(paletteName, v.GetFile(file)) v.Palettes[paletteName] = palette } } @@ -173,9 +174,9 @@ func (v *Engine) loadSoundEntries() { } // LoadSprite loads a sprite from the game's data files -func (v *Engine) LoadSprite(fileName string, palette Palettes.Palette) *Sprite { +func (v *Engine) LoadSprite(fileName string, palette Palettes.Palette) *Common.Sprite { data := v.GetFile(fileName) - sprite := CreateSprite(data, v.Palettes[palette]) + sprite := Common.CreateSprite(data, v.Palettes[palette]) return sprite } @@ -189,6 +190,7 @@ func (v *Engine) updateScene() { } v.CurrentScene = v.nextScene v.nextScene = nil + v.UIManager.Reset() v.CurrentScene.Load() } @@ -212,6 +214,10 @@ func (v *Engine) Update() { if v.CurrentScene == nil { log.Fatal("no scene loaded") } + + if v.IsLoading() { + return + } v.CursorButtons = 0 if ebiten.IsMouseButtonPressed(ebiten.MouseButtonLeft) { v.CursorButtons |= CursorButtonLeft @@ -220,23 +226,22 @@ func (v *Engine) Update() { v.CursorButtons |= CursorButtonRight } v.CurrentScene.Update() + v.UIManager.Update() } // Draw draws the game func (v *Engine) Draw(screen *ebiten.Image) { v.CursorX, v.CursorY = ebiten.CursorPosition() if v.LoadingProgress < 1.0 { - v.LoadingSprite.Frame = uint8(Max(0, Min(uint32(len(v.LoadingSprite.Frames)-1), uint32(float64(len(v.LoadingSprite.Frames)-1)*v.LoadingProgress)))) + v.LoadingSprite.Frame = uint8(Common.Max(0, Common.Min(uint32(len(v.LoadingSprite.Frames)-1), uint32(float64(len(v.LoadingSprite.Frames)-1)*v.LoadingProgress)))) v.LoadingSprite.Draw(screen) } else { if v.CurrentScene == nil { log.Fatal("no scene loaded") } v.CurrentScene.Render(screen) + v.UIManager.Draw(screen) } - - v.CursorSprite.MoveTo(v.CursorX, v.CursorY) - v.CursorSprite.Draw(screen) } // SetNextScene tells the engine what scene to load on the next update cycle diff --git a/MPQFont.go b/MPQFont.go index 1bb7288e..1172b269 100644 --- a/MPQFont.go +++ b/MPQFont.go @@ -1,6 +1,9 @@ package OpenDiablo2 -import "github.com/essial/OpenDiablo2/Palettes" +import ( + "github.com/essial/OpenDiablo2/Common" + "github.com/essial/OpenDiablo2/Palettes" +) // MPQFontSize represents the size of a character in a font type MPQFontSize struct { @@ -11,7 +14,7 @@ type MPQFontSize struct { // MPQFont represents a font type MPQFont struct { Engine *Engine - FontSprite *Sprite + FontSprite *Common.Sprite Metrics map[uint8]MPQFontSize } diff --git a/MPQStream.go b/MPQStream.go index 8cccd431..cdaa5f09 100644 --- a/MPQStream.go +++ b/MPQStream.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/JoshVarga/blast" + "github.com/essial/OpenDiablo2/Common" "github.com/essial/OpenDiablo2/Compression" ) @@ -84,7 +85,7 @@ func (v *MPQStream) readInternalSingleUnit(buffer []byte, offset, count uint32) v.loadSingleUnit() } - bytesToCopy := Min(uint32(len(v.CurrentData))-v.CurrentPosition, count) + bytesToCopy := Common.Min(uint32(len(v.CurrentData))-v.CurrentPosition, count) copy(buffer[offset:offset+bytesToCopy], v.CurrentData[v.CurrentPosition:v.CurrentPosition+bytesToCopy]) v.CurrentPosition += bytesToCopy return bytesToCopy @@ -93,7 +94,7 @@ func (v *MPQStream) readInternalSingleUnit(buffer []byte, offset, count uint32) func (v *MPQStream) readInternal(buffer []byte, offset, count uint32) uint32 { v.bufferData() localPosition := v.CurrentPosition % v.BlockSize - bytesToCopy := Min(uint32(len(v.CurrentData))-localPosition, count) + bytesToCopy := Common.Min(uint32(len(v.CurrentData))-localPosition, count) if bytesToCopy <= 0 { return 0 } @@ -107,7 +108,7 @@ func (v *MPQStream) bufferData() { if requiredBlock == v.CurrentBlockIndex { return } - expectedLength := Min(v.BlockTableEntry.UncompressedFileSize-(requiredBlock*v.BlockSize), v.BlockSize) + expectedLength := Common.Min(v.BlockTableEntry.UncompressedFileSize-(requiredBlock*v.BlockSize), v.BlockSize) v.CurrentData = v.loadBlock(requiredBlock, expectedLength) v.CurrentBlockIndex = requiredBlock } diff --git a/SceneMainMenu.go b/SceneMainMenu.go index 63a21f58..69db2876 100644 --- a/SceneMainMenu.go +++ b/SceneMainMenu.go @@ -3,6 +3,7 @@ package OpenDiablo2 import ( "image/color" + "github.com/essial/OpenDiablo2/Common" "github.com/essial/OpenDiablo2/Palettes" "github.com/essial/OpenDiablo2/ResourcePaths" @@ -12,12 +13,12 @@ import ( // MainMenu represents the main menu type MainMenu struct { engine *Engine - trademarkBackground *Sprite - background *Sprite - diabloLogoLeft *Sprite - diabloLogoRight *Sprite - diabloLogoLeftBack *Sprite - diabloLogoRightBack *Sprite + trademarkBackground *Common.Sprite + background *Common.Sprite + diabloLogoLeft *Common.Sprite + diabloLogoRight *Common.Sprite + diabloLogoLeftBack *Common.Sprite + diabloLogoRightBack *Common.Sprite copyrightLabel *UILabel copyrightLabel2 *UILabel showTrademarkScreen bool diff --git a/UI/Manager.go b/UI/Manager.go new file mode 100644 index 00000000..33bea991 --- /dev/null +++ b/UI/Manager.go @@ -0,0 +1,45 @@ +package UI + +import ( + "github.com/essial/OpenDiablo2/Common" + "github.com/essial/OpenDiablo2/Palettes" + "github.com/essial/OpenDiablo2/ResourcePaths" + "github.com/hajimehoshi/ebiten" +) + +// Manager represents the UI manager +type Manager struct { + widgets []*Widget + cursorSprite *Common.Sprite +} + +// CreateManager creates a new instance of a UI manager +func CreateManager(provider Common.SpriteProvider) *Manager { + result := &Manager{ + widgets: make([]*Widget, 0), + cursorSprite: provider.LoadSprite(ResourcePaths.CursorDefault, Palettes.Units), + } + return result +} + +// Reset resets the state of the UI manager. Typically called for new scenes +func (v *Manager) Reset() { + v.widgets = make([]*Widget, 0) +} + +// AddWidget adds a widget to the UI manager +func (v *Manager) AddWidget(widget *Widget) { + v.widgets = append(v.widgets, widget) +} + +// Draw renders all of the UI elements +func (v *Manager) Draw(screen *ebiten.Image) { + cx, cy := ebiten.CursorPosition() + v.cursorSprite.MoveTo(cx, cy) + v.cursorSprite.Draw(screen) +} + +// Update updates all of the UI elements +func (v *Manager) Update() { + +} diff --git a/UILabel.go b/UILabel.go index 072388e6..b4d15872 100644 --- a/UILabel.go +++ b/UILabel.go @@ -3,18 +3,24 @@ package OpenDiablo2 import ( "image/color" + "github.com/essial/OpenDiablo2/Common" "github.com/essial/OpenDiablo2/Palettes" "github.com/hajimehoshi/ebiten" ) +// UILabelAlignment represents a label's alignment type UILabelAlignment uint8 const ( - UILabelAlignLeft UILabelAlignment = 0 + // UILabelAlignLeft represents a left-aligned label + UILabelAlignLeft UILabelAlignment = 0 + // UILabelAlignCenter represents a center-aligned label UILabelAlignCenter UILabelAlignment = 1 - UILabelAlignRight UILabelAlignment = 2 + // UILabelAlignRight represents a right-aligned label + UILabelAlignRight UILabelAlignment = 2 ) +// UILabel represents a user interface label type UILabel struct { text string X int @@ -64,11 +70,12 @@ func (v *UILabel) calculateSize() (uint32, uint32) { for _, ch := range v.text { metric := v.font.Metrics[uint8(ch)] width += uint32(metric.Width) - height = Max(height, uint32(metric.Height)) + height = Common.Max(height, uint32(metric.Height)) } return width, height } +// MoveTo moves the label to the specified location func (v *UILabel) MoveTo(x, y int) { v.X = x v.Y = y @@ -94,6 +101,7 @@ func (v *UILabel) cacheImage() { } } +// SetText sets the label's text func (v *UILabel) SetText(newText string) { if v.text == newText { return