diff --git a/Common/Drawable.go b/Common/Drawable.go new file mode 100644 index 00000000..bd301b81 --- /dev/null +++ b/Common/Drawable.go @@ -0,0 +1,13 @@ +package Common + +import "github.com/hajimehoshi/ebiten" + +// Drawable represents an instance that can be drawn +type Drawable interface { + Draw(target *ebiten.Image) + GetSize() (uint32, uint32) + MoveTo(x, y int) + GetLocation() (int, int) + GetVisible() bool + SetVisible(bool) +} diff --git a/Engine.go b/Engine.go index 3bf1f539..227676bd 100644 --- a/Engine.go +++ b/Engine.go @@ -9,6 +9,7 @@ import ( "sync" "github.com/essial/OpenDiablo2/Common" + "github.com/essial/OpenDiablo2/Palettes" "github.com/essial/OpenDiablo2/ResourcePaths" "github.com/hajimehoshi/ebiten" @@ -27,31 +28,34 @@ type EngineConfig struct { MpqLoadOrder []string } -// Engine is the core OpenDiablo2 engine +// CursorButton represents a mouse button type CursorButton uint8 const ( - CursorButtonLeft CursorButton = 1 + // CursorButtonLeft represents the left mouse button + CursorButtonLeft CursorButton = 1 + // CursorButtonRight represents the right mouse button CursorButtonRight CursorButton = 2 ) +// 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[string]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]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 } // CreateEngine creates and instance of the OpenDiablo2 engine @@ -71,8 +75,8 @@ func CreateEngine() *Engine { log.Fatal(err) } result.audioContext = audioContext - result.CursorSprite = result.LoadSprite(ResourcePaths.CursorDefault, result.Palettes["units"]) - result.LoadingSprite = result.LoadSprite(ResourcePaths.LoadingScreen, result.Palettes["loading"]) + 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))) result.SetNextScene(CreateMainMenu(result)) @@ -142,14 +146,14 @@ func (v *Engine) IsLoading() bool { } func (v *Engine) loadPalettes() { - v.Palettes = make(map[string]Palette) + v.Palettes = make(map[Palettes.Palette]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 { continue } nameParts := strings.Split(file, `/`) - paletteName := nameParts[len(nameParts)-2] + paletteName := Palettes.Palette(nameParts[len(nameParts)-2]) palette := CreatePalette(paletteName, v.GetFile(file)) v.Palettes[paletteName] = palette } @@ -169,9 +173,9 @@ func (v *Engine) loadSoundEntries() { } // LoadSprite loads a sprite from the game's data files -func (v *Engine) LoadSprite(fileName string, palette Palette) Sprite { +func (v *Engine) LoadSprite(fileName string, palette Palettes.Palette) *Sprite { data := v.GetFile(fileName) - sprite := CreateSprite(data, palette) + sprite := CreateSprite(data, v.Palettes[palette]) return sprite } @@ -241,13 +245,13 @@ func (v *Engine) SetNextScene(nextScene Common.SceneInterface) { } // GetFont creates or loads an existing font -func (v *Engine) GetFont(font, palette string) *MPQFont { - cacheItem, exists := v.fontCache[font+"_"+palette] +func (v *Engine) GetFont(font string, palette Palettes.Palette) *MPQFont { + cacheItem, exists := v.fontCache[font+"_"+string(palette)] if exists { return cacheItem } - newFont := CreateMPQFont(v, font, v.Palettes[palette]) - v.fontCache[font+"_"+palette] = newFont + newFont := CreateMPQFont(v, font, palette) + v.fontCache[font+"_"+string(palette)] = newFont return newFont } diff --git a/MPQFont.go b/MPQFont.go index 731078d5..1bb7288e 100644 --- a/MPQFont.go +++ b/MPQFont.go @@ -1,18 +1,22 @@ package OpenDiablo2 +import "github.com/essial/OpenDiablo2/Palettes" + +// MPQFontSize represents the size of a character in a font type MPQFontSize struct { Width uint8 Height uint8 } +// MPQFont represents a font type MPQFont struct { Engine *Engine - FontSprite Sprite + FontSprite *Sprite Metrics map[uint8]MPQFontSize } // CreateMPQFont creates an instance of a MPQ Font -func CreateMPQFont(engine *Engine, font string, palette Palette) *MPQFont { +func CreateMPQFont(engine *Engine, font string, palette Palettes.Palette) *MPQFont { result := &MPQFont{ Engine: engine, Metrics: make(map[uint8]MPQFontSize), diff --git a/Palette.go b/Palette.go index a77b544c..67317096 100644 --- a/Palette.go +++ b/Palette.go @@ -1,17 +1,20 @@ package OpenDiablo2 +import "github.com/essial/OpenDiablo2/Palettes" + +// PaletteRGB represents a color in a palette type PaletteRGB struct { R, G, B uint8 } // Palette represents a palette type Palette struct { - Name string + Name Palettes.Palette Colors [256]PaletteRGB } // CreatePalette creates a palette -func CreatePalette(name string, data []byte) Palette { +func CreatePalette(name Palettes.Palette, data []byte) Palette { result := Palette{Name: name} for i := 0; i <= 255; i++ { diff --git a/Palettes/Palettes.go b/Palettes/Palettes.go new file mode 100644 index 00000000..577f3fe4 --- /dev/null +++ b/Palettes/Palettes.go @@ -0,0 +1,43 @@ +package Palettes + +// Palette represents a named palette +type Palette string + +const ( + // Act1 palette + Act1 Palette = "act1" + // Act2 palette + Act2 Palette = "act2" + // Act3 palette + Act3 Palette = "act3" + // Act4 palette + Act4 Palette = "act4" + // Act5 palette + Act5 Palette = "act5" + // EndGame palette + EndGame Palette = "endgame" + // EndGame2 palette + EndGame2 Palette = "endgame2" + // Fechar palette + Fechar Palette = "fechar" + // Loading palette + Loading Palette = "loading" + // Menu0 palette + Menu0 Palette = "menu0" + // Menu1 palette + Menu1 Palette = "menu1" + // Menu2 palette + Menu2 Palette = "menu2" + // Menu3 palette + Menu3 Palette = "menu3" + // Menu4 palette + Menu4 Palette = "menu4" + // Sky palette + Sky Palette = "sky" + // Static palette + Static Palette = "static" + // Trademark palette + Trademark Palette = "trademark" + // Units palette + Units Palette = "units" +) diff --git a/SceneMainMenu.go b/SceneMainMenu.go index fdc8ee2a..63a21f58 100644 --- a/SceneMainMenu.go +++ b/SceneMainMenu.go @@ -3,119 +3,127 @@ package OpenDiablo2 import ( "image/color" + "github.com/essial/OpenDiablo2/Palettes" + "github.com/essial/OpenDiablo2/ResourcePaths" "github.com/hajimehoshi/ebiten" ) +// MainMenu represents the main menu type MainMenu struct { - Engine *Engine - TrademarkBackground Sprite - Background Sprite - DiabloLogoLeft Sprite - DiabloLogoRight Sprite - DiabloLogoLeftBack Sprite - DiabloLogoRightBack Sprite - CopyrightLabel *UILabel - CopyrightLabel2 *UILabel - ShowTrademarkScreen bool - LeftButtonHeld bool + engine *Engine + trademarkBackground *Sprite + background *Sprite + diabloLogoLeft *Sprite + diabloLogoRight *Sprite + diabloLogoLeftBack *Sprite + diabloLogoRightBack *Sprite + copyrightLabel *UILabel + copyrightLabel2 *UILabel + showTrademarkScreen bool + leftButtonHeld bool } +// CreateMainMenu creates an instance of MainMenu func CreateMainMenu(engine *Engine) *MainMenu { result := &MainMenu{ - Engine: engine, - ShowTrademarkScreen: true, + engine: engine, + showTrademarkScreen: true, } return result } +// Load is called to load the resources for the main menu func (v *MainMenu) Load() { - v.Engine.PlayBGM(ResourcePaths.BGMTitle) + v.engine.PlayBGM(ResourcePaths.BGMTitle) go func() { loadStep := 1.0 / 8.0 - v.Engine.LoadingProgress = 0 + v.engine.LoadingProgress = 0 { - v.CopyrightLabel = CreateUILabel(v.Engine, ResourcePaths.FontFormal12, "static") - v.CopyrightLabel.Alignment = UILabelAlignCenter - v.CopyrightLabel.SetText("Diablo 2 is © Copyright 2000-2016 Blizzard Entertainment") - v.CopyrightLabel.ColorMod = color.RGBA{188, 168, 140, 255} - v.CopyrightLabel.MoveTo(400, 500) - v.Engine.LoadingProgress += loadStep + v.copyrightLabel = CreateUILabel(v.engine, ResourcePaths.FontFormal12, Palettes.Static) + v.copyrightLabel.Alignment = UILabelAlignCenter + v.copyrightLabel.SetText("Diablo 2 is © Copyright 2000-2016 Blizzard Entertainment") + v.copyrightLabel.ColorMod = color.RGBA{188, 168, 140, 255} + v.copyrightLabel.MoveTo(400, 500) + v.engine.LoadingProgress += loadStep } { - v.CopyrightLabel2 = CreateUILabel(v.Engine, ResourcePaths.FontFormal12, "static") - v.CopyrightLabel2.Alignment = UILabelAlignCenter - v.CopyrightLabel2.SetText("All Rights Reserved.") - v.CopyrightLabel2.ColorMod = color.RGBA{188, 168, 140, 255} - v.CopyrightLabel2.MoveTo(400, 525) - v.Engine.LoadingProgress += loadStep + v.copyrightLabel2 = CreateUILabel(v.engine, ResourcePaths.FontFormal12, Palettes.Static) + v.copyrightLabel2.Alignment = UILabelAlignCenter + v.copyrightLabel2.SetText("All Rights Reserved.") + v.copyrightLabel2.ColorMod = color.RGBA{188, 168, 140, 255} + v.copyrightLabel2.MoveTo(400, 525) + v.engine.LoadingProgress += loadStep } { - v.Background = v.Engine.LoadSprite(ResourcePaths.GameSelectScreen, v.Engine.Palettes["sky"]) - v.Background.MoveTo(0, 0) - v.Engine.LoadingProgress += loadStep + v.background = v.engine.LoadSprite(ResourcePaths.GameSelectScreen, Palettes.Sky) + v.background.MoveTo(0, 0) + v.engine.LoadingProgress += loadStep } { - v.TrademarkBackground = v.Engine.LoadSprite(ResourcePaths.TrademarkScreen, v.Engine.Palettes["sky"]) - v.TrademarkBackground.MoveTo(0, 0) - v.Engine.LoadingProgress += loadStep + v.trademarkBackground = v.engine.LoadSprite(ResourcePaths.TrademarkScreen, Palettes.Sky) + v.trademarkBackground.MoveTo(0, 0) + v.engine.LoadingProgress += loadStep } { - v.DiabloLogoLeft = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoFireLeft, v.Engine.Palettes["units"]) - v.DiabloLogoLeft.Blend = true - v.DiabloLogoLeft.Animate = true - v.DiabloLogoLeft.MoveTo(400, 120) - v.Engine.LoadingProgress += loadStep + v.diabloLogoLeft = v.engine.LoadSprite(ResourcePaths.Diablo2LogoFireLeft, Palettes.Units) + v.diabloLogoLeft.Blend = true + v.diabloLogoLeft.Animate = true + v.diabloLogoLeft.MoveTo(400, 120) + v.engine.LoadingProgress += loadStep } { - v.DiabloLogoRight = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoFireRight, v.Engine.Palettes["units"]) - v.DiabloLogoRight.Blend = true - v.DiabloLogoRight.Animate = true - v.DiabloLogoRight.MoveTo(400, 120) - v.Engine.LoadingProgress += loadStep + v.diabloLogoRight = v.engine.LoadSprite(ResourcePaths.Diablo2LogoFireRight, Palettes.Units) + v.diabloLogoRight.Blend = true + v.diabloLogoRight.Animate = true + v.diabloLogoRight.MoveTo(400, 120) + v.engine.LoadingProgress += loadStep } { - v.DiabloLogoLeftBack = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoBlackLeft, v.Engine.Palettes["units"]) - v.DiabloLogoLeftBack.MoveTo(400, 120) - v.Engine.LoadingProgress += loadStep + v.diabloLogoLeftBack = v.engine.LoadSprite(ResourcePaths.Diablo2LogoBlackLeft, Palettes.Units) + v.diabloLogoLeftBack.MoveTo(400, 120) + v.engine.LoadingProgress += loadStep } { - v.DiabloLogoRightBack = v.Engine.LoadSprite(ResourcePaths.Diablo2LogoBlackRight, v.Engine.Palettes["units"]) - v.DiabloLogoRightBack.MoveTo(400, 120) - v.Engine.LoadingProgress = 1.0 + v.diabloLogoRightBack = v.engine.LoadSprite(ResourcePaths.Diablo2LogoBlackRight, Palettes.Units) + v.diabloLogoRightBack.MoveTo(400, 120) + v.engine.LoadingProgress = 1.0 } }() } +// Unload unloads the data for the main menu func (v *MainMenu) Unload() { } +// Render renders the main menu func (v *MainMenu) Render(screen *ebiten.Image) { - if v.ShowTrademarkScreen { - v.TrademarkBackground.DrawSegments(screen, 4, 3, 0) + if v.showTrademarkScreen { + v.trademarkBackground.DrawSegments(screen, 4, 3, 0) } else { - v.Background.DrawSegments(screen, 4, 3, 0) + v.background.DrawSegments(screen, 4, 3, 0) } - v.DiabloLogoLeftBack.Draw(screen) - v.DiabloLogoRightBack.Draw(screen) - v.DiabloLogoLeft.Draw(screen) - v.DiabloLogoRight.Draw(screen) + v.diabloLogoLeftBack.Draw(screen) + v.diabloLogoRightBack.Draw(screen) + v.diabloLogoLeft.Draw(screen) + v.diabloLogoRight.Draw(screen) - if v.ShowTrademarkScreen { - v.CopyrightLabel.Draw(screen) - v.CopyrightLabel2.Draw(screen) + if v.showTrademarkScreen { + v.copyrightLabel.Draw(screen) + v.copyrightLabel2.Draw(screen) } else { } } +// Update runs the update logic on the main menu func (v *MainMenu) Update() { - if v.ShowTrademarkScreen { - if v.Engine.CursorButtonPressed(CursorButtonLeft) { - v.LeftButtonHeld = true - v.ShowTrademarkScreen = false + if v.showTrademarkScreen { + if v.engine.CursorButtonPressed(CursorButtonLeft) { + v.leftButtonHeld = true + v.showTrademarkScreen = false } return } diff --git a/Sprite.go b/Sprite.go index 7ac563fa..3e404759 100644 --- a/Sprite.go +++ b/Sprite.go @@ -8,6 +8,7 @@ import ( "github.com/hajimehoshi/ebiten" ) +// Sprite represents a type of object in D2 that is comprised of one or more frames and directions type Sprite struct { Directions uint32 FramesPerDirection uint32 @@ -18,8 +19,10 @@ type Sprite struct { LastFrameTime time.Time Animate bool ColorMod color.Color + visible bool } +// SpriteFrame represents a single frame of a sprite type SpriteFrame struct { Flip uint32 Width uint32 @@ -33,8 +36,9 @@ type SpriteFrame struct { Image *ebiten.Image } -func CreateSprite(data []byte, palette Palette) Sprite { - result := Sprite{ +// CreateSprite creates an instance of a sprite +func CreateSprite(data []byte, palette Palette) *Sprite { + result := &Sprite{ X: 50, Y: 50, Frame: 0, @@ -121,6 +125,7 @@ func CreateSprite(data []byte, palette Palette) Sprite { return result } +// GetSize returns the size of the sprite func (v *Sprite) GetSize() (uint32, uint32) { frame := v.Frames[uint32(v.Frame)+(uint32(v.Direction)*v.FramesPerDirection)] return frame.Width, frame.Height @@ -159,6 +164,7 @@ func (v *Sprite) Draw(target *ebiten.Image) { target.DrawImage(frame.Image, opts) } +// DrawSegments draws the sprite via a grid of segments func (v *Sprite) DrawSegments(target *ebiten.Image, xSegments, ySegments, offset int) { v.updateAnimation() yOffset := int32(0) @@ -191,3 +197,8 @@ func (v *Sprite) MoveTo(x, y int) { v.X = x v.Y = y } + +// GetLocation returns the location of the sprite +func (v *Sprite) GetLocation() (int, int) { + return v.X, v.Y +} diff --git a/UI/Button.go b/UI/Button.go new file mode 100644 index 00000000..40a60f41 --- /dev/null +++ b/UI/Button.go @@ -0,0 +1,8 @@ +package UI + +// Button defines an object that acts like a button +type Button interface { + Widget + isPressed() bool + setPressed(bool) +} diff --git a/UI/Widget.go b/UI/Widget.go new file mode 100644 index 00000000..0f032b54 --- /dev/null +++ b/UI/Widget.go @@ -0,0 +1,12 @@ +package UI + +import ( + "github.com/essial/OpenDiablo2/Common" +) + +// Widget defines an object that is a UI widget +type Widget interface { + Common.Drawable + getEnabled() bool + setEnabled(bool) +} diff --git a/UILabel.go b/UILabel.go index 162de468..072388e6 100644 --- a/UILabel.go +++ b/UILabel.go @@ -3,6 +3,7 @@ package OpenDiablo2 import ( "image/color" + "github.com/essial/OpenDiablo2/Palettes" "github.com/hajimehoshi/ebiten" ) @@ -27,7 +28,7 @@ type UILabel struct { } // CreateUILabel creates a new instance of a UI label -func CreateUILabel(engine *Engine, font, palette string) *UILabel { +func CreateUILabel(engine *Engine, font string, palette Palettes.Palette) *UILabel { result := &UILabel{ Alignment: UILabelAlignLeft, ColorMod: nil,