From 7da1843f49373fa26f8fca0789503cc1c9bac938 Mon Sep 17 00:00:00 2001 From: Tim Sarbin Date: Sun, 26 Jul 2020 14:52:54 -0400 Subject: [PATCH] Lint cleanup (#628) --- d2common/d2data/d2compression/huffman.go | 3 +- d2common/d2data/d2compression/wav.go | 1 + d2common/d2data/d2video/binkdecoder.go | 64 ++++-- .../d2debugutil/internal/assets/assets.go | 3 - d2core/d2input/d2input.go | 9 + d2core/d2input/handler_event.go | 33 +++ d2core/d2input/input_events.go | 109 ---------- d2core/d2input/key_event.go | 21 ++ d2core/d2input/keychars_event.go | 12 ++ d2core/d2input/mouse_event.go | 34 +++ d2core/d2input/mousemove_event.go | 28 +++ d2core/d2map/d2mapgen/act1_overworld.go | 4 +- d2core/d2map/d2maprenderer/renderer.go | 14 +- d2core/d2map/d2maprenderer/viewport.go | 36 ++-- d2core/d2object/init_function.go | 32 +-- d2core/d2ui/button.go | 4 +- d2core/d2ui/checkbox.go | 41 +++- d2core/d2ui/drawable.go | 2 +- d2core/d2ui/scrollbar.go | 35 ++- d2core/d2ui/textbox.go | 63 +++++- d2game/d2gamescreen/game.go | 21 +- d2game/d2gamescreen/main_menu.go | 2 +- d2game/d2gamescreen/select_hero_class.go | 2 +- d2game/d2player/game_controls.go | 196 +++++++++++++---- d2game/d2player/hero_stats_panel.go | 204 +++++++++++++----- d2game/d2player/inventory.go | 110 ++++++++-- 26 files changed, 776 insertions(+), 307 deletions(-) create mode 100644 d2core/d2input/handler_event.go delete mode 100644 d2core/d2input/input_events.go create mode 100644 d2core/d2input/key_event.go create mode 100644 d2core/d2input/keychars_event.go create mode 100644 d2core/d2input/mouse_event.go create mode 100644 d2core/d2input/mousemove_event.go diff --git a/d2common/d2data/d2compression/huffman.go b/d2common/d2data/d2compression/huffman.go index 46dec26f..aaa4e006 100644 --- a/d2common/d2data/d2compression/huffman.go +++ b/d2common/d2data/d2compression/huffman.go @@ -219,7 +219,6 @@ func decode(input *d2common.BitStream, head *linkedNode) *linkedNode { return node } -// TODO: these consts for buildList need better names const ( decompVal1 = 256 decompVal2 = 257 @@ -374,6 +373,7 @@ func buildTree(tail *linkedNode) *linkedNode { } // HuffmanDecompress decompresses huffman-compressed data +//nolint:gomnd // binary decode magic func HuffmanDecompress(data []byte) []byte { comptype := data[0] primes := getPrimes() @@ -399,6 +399,7 @@ Loop: break Loop case 257: newvalue := bitstream.ReadBits(8) + outputstream.PushByte(byte(newvalue)) tail = insertNode(tail, newvalue) default: diff --git a/d2common/d2data/d2compression/wav.go b/d2common/d2data/d2compression/wav.go index 64f12fc1..cebbf52e 100644 --- a/d2common/d2data/d2compression/wav.go +++ b/d2common/d2data/d2compression/wav.go @@ -5,6 +5,7 @@ import ( ) // WavDecompress decompresses wav files +//nolint:gomnd // binary decode magic func WavDecompress(data []byte, channelCount int) []byte { //nolint:funlen doesn't make sense to split Array1 := []int{0x2c, 0x2c} Array2 := make([]int, channelCount) diff --git a/d2common/d2data/d2video/binkdecoder.go b/d2common/d2data/d2video/binkdecoder.go index 49d3494c..9994ef76 100644 --- a/d2common/d2data/d2video/binkdecoder.go +++ b/d2common/d2data/d2video/binkdecoder.go @@ -6,34 +6,54 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common" ) +// BinkVideoMode is the video mode type type BinkVideoMode uint32 const ( - BinkVideoModeNormal BinkVideoMode = 0 - BinkVideoModeHeightDoubled BinkVideoMode = 1 - BinkVideoModeHeightInterlaced BinkVideoMode = 2 - BinkVideoModeWidthDoubled BinkVideoMode = 3 - BinkVideoModeWidthAndHeightDoubled BinkVideoMode = 4 + // BinkVideoModeNormal is a normal video + BinkVideoModeNormal BinkVideoMode = 0 + + // BinkVideoModeHeightDoubled is a height-doubled video + BinkVideoModeHeightDoubled BinkVideoMode = 1 + + // BinkVideoModeHeightInterlaced is a height-interlaced video + BinkVideoModeHeightInterlaced BinkVideoMode = 2 + + // BinkVideoModeWidthDoubled is a width-doubled video + BinkVideoModeWidthDoubled BinkVideoMode = 3 + + // BinkVideoModeWidthAndHeightDoubled is a width and height-doubled video + BinkVideoModeWidthAndHeightDoubled BinkVideoMode = 4 + + // BinkVideoModeWidthAndHeightInterlaced is a width and height interlaced video BinkVideoModeWidthAndHeightInterlaced BinkVideoMode = 5 ) +// BinkAudioAlgorithm represents the type of bink audio algorithm type BinkAudioAlgorithm uint32 const ( + // BinkAudioAlgorithmFFT is the FTT audio algorithm BinkAudioAlgorithmFFT BinkAudioAlgorithm = 0 + + // BinkAudioAlgorithmDCT is the DCT audio algorithm BinkAudioAlgorithmDCT BinkAudioAlgorithm = 1 ) +// BinkAudioTrack represents an audio track type BinkAudioTrack struct { AudioChannels uint16 AudioSampleRateHz uint16 Stereo bool Algorithm BinkAudioAlgorithm - AudioTrackId uint32 + AudioTrackID uint32 } +// BinkDecoder represents the bink decoder type BinkDecoder struct { - videoCodecRevision byte + AudioTracks []BinkAudioTrack + FrameIndexTable []uint32 + streamReader *d2common.StreamReader fileSize uint32 numberOfFrames uint32 largestFrameSizeBytes uint32 @@ -41,39 +61,50 @@ type BinkDecoder struct { VideoHeight uint32 FPS uint32 FrameTimeMS uint32 - streamReader *d2common.StreamReader VideoMode BinkVideoMode + frameIndex uint32 + videoCodecRevision byte HasAlphaPlane bool Grayscale bool - AudioTracks []BinkAudioTrack - FrameIndexTable []uint32 // Mask bit 0, as this is defined as a keyframe - frameIndex uint32 + + // Mask bit 0, as this is defined as a keyframe + } +// CreateBinkDecoder returns a new instance of the bink decoder func CreateBinkDecoder(source []byte) *BinkDecoder { result := &BinkDecoder{ streamReader: d2common.CreateStreamReader(source), } + result.loadHeaderInformation() + return result } +// GetNextFrame gets the next frame func (v *BinkDecoder) GetNextFrame() { - //v.streamReader.SetPosition(uint64(v.FrameIndexTable[i] & 0xFFFFFFFE)) - lengthOfAudioPackets := v.streamReader.GetUInt32() - 4 + //nolint:gocritic // v.streamReader.SetPosition(uint64(v.FrameIndexTable[i] & 0xFFFFFFFE)) + + lengthOfAudioPackets := v.streamReader.GetUInt32() - 4 //nolint:gomnd // decode magic samplesInPacket := v.streamReader.GetUInt32() + v.streamReader.SkipBytes(int(lengthOfAudioPackets)) + log.Printf("Frame %d:\tSamp: %d", v.frameIndex, samplesInPacket) v.frameIndex++ } +//nolint:gomnd // Decoder magic func (v *BinkDecoder) loadHeaderInformation() { v.streamReader.SetPosition(0) headerBytes := v.streamReader.ReadBytes(3) + if string(headerBytes) != "BIK" { log.Fatal("Invalid header for bink video") } + v.videoCodecRevision = v.streamReader.GetByte() v.fileSize = v.streamReader.GetUInt32() v.numberOfFrames = v.streamReader.GetUInt32() @@ -91,20 +122,25 @@ func (v *BinkDecoder) loadHeaderInformation() { v.Grayscale = ((videoFlags >> 17) & 0x1) == 1 numberOfAudioTracks := v.streamReader.GetUInt32() v.AudioTracks = make([]BinkAudioTrack, numberOfAudioTracks) + for i := 0; i < int(numberOfAudioTracks); i++ { v.streamReader.SkipBytes(2) // Unknown v.AudioTracks[i].AudioChannels = v.streamReader.GetUInt16() } + for i := 0; i < int(numberOfAudioTracks); i++ { v.AudioTracks[i].AudioSampleRateHz = v.streamReader.GetUInt16() flags := v.streamReader.GetUInt16() v.AudioTracks[i].Stereo = ((flags >> 13) & 0x1) == 1 v.AudioTracks[i].Algorithm = BinkAudioAlgorithm((flags >> 12) & 0x1) } + for i := 0; i < int(numberOfAudioTracks); i++ { - v.AudioTracks[i].AudioTrackId = v.streamReader.GetUInt32() + v.AudioTracks[i].AudioTrackID = v.streamReader.GetUInt32() } + v.FrameIndexTable = make([]uint32, v.numberOfFrames+1) + for i := 0; i < int(v.numberOfFrames+1); i++ { v.FrameIndexTable[i] = v.streamReader.GetUInt32() } diff --git a/d2common/d2debugutil/internal/assets/assets.go b/d2common/d2debugutil/internal/assets/assets.go index 7956f9e9..288b94bf 100644 --- a/d2common/d2debugutil/internal/assets/assets.go +++ b/d2common/d2debugutil/internal/assets/assets.go @@ -28,9 +28,6 @@ import ( ) const ( - imgWidth = 256 - imgHeight = 128 - CharWidth = 8 CharHeight = 16 ) diff --git a/d2core/d2input/d2input.go b/d2core/d2input/d2input.go index c3a0d3b0..589868d0 100644 --- a/d2core/d2input/d2input.go +++ b/d2core/d2input/d2input.go @@ -2,6 +2,8 @@ package d2input import ( "errors" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" ) var ( @@ -10,3 +12,10 @@ var ( // ErrNotReg shows the input system has no registered handler ErrNotReg = errors.New("input system does not have provided handler") ) + +// Static checks to confirm struct conforms to interface +var _ d2interface.InputEventHandler = &HandlerEvent{} +var _ d2interface.KeyEvent = &KeyEvent{} +var _ d2interface.KeyCharsEvent = &KeyCharsEvent{} +var _ d2interface.MouseEvent = &MouseEvent{} +var _ d2interface.MouseMoveEvent = &MouseMoveEvent{} diff --git a/d2core/d2input/handler_event.go b/d2core/d2input/handler_event.go new file mode 100644 index 00000000..86a30add --- /dev/null +++ b/d2core/d2input/handler_event.go @@ -0,0 +1,33 @@ +package d2input + +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" +) + +// HandlerEvent is an event that EventHandlers will process and respond to +type HandlerEvent struct { + keyMod d2enum.KeyMod + buttonMod d2enum.MouseButtonMod + x int + y int +} + +// KeyMod yields the modifier for a key action +func (e *HandlerEvent) KeyMod() d2enum.KeyMod { + return e.keyMod +} + +// ButtonMod yields the modifier for a button action +func (e *HandlerEvent) ButtonMod() d2enum.MouseButtonMod { + return e.buttonMod +} + +// X returns the x screen coordinate for the event +func (e *HandlerEvent) X() int { + return e.x +} + +// Y returns the y screen coordinate for the event +func (e *HandlerEvent) Y() int { + return e.y +} diff --git a/d2core/d2input/input_events.go b/d2core/d2input/input_events.go deleted file mode 100644 index c6389d20..00000000 --- a/d2core/d2input/input_events.go +++ /dev/null @@ -1,109 +0,0 @@ -package d2input - -import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" -) - -// Static checks to confirm struct conforms to interface -var _ d2interface.InputEventHandler = &HandlerEvent{} -var _ d2interface.KeyEvent = &KeyEvent{} -var _ d2interface.KeyCharsEvent = &KeyCharsEvent{} -var _ d2interface.MouseEvent = &MouseEvent{} -var _ d2interface.MouseMoveEvent = &MouseMoveEvent{} - -// HandlerEvent is an event that EventHandlers will process and respond to -type HandlerEvent struct { - keyMod d2enum.KeyMod - buttonMod d2enum.MouseButtonMod - x int - y int -} - -// KeyMod yields the modifier for a key action -func (e *HandlerEvent) KeyMod() d2enum.KeyMod { - return e.keyMod -} - -// ButtonMod yields the modifier for a button action -func (e *HandlerEvent) ButtonMod() d2enum.MouseButtonMod { - return e.buttonMod -} - -// X returns the x screen coordinate for the event -func (e *HandlerEvent) X() int { - return e.x -} - -//Y returns the y screen coordinate for the event -func (e *HandlerEvent) Y() int { - return e.y -} - -type KeyCharsEvent struct { - HandlerEvent - chars []rune -} - -func (e *KeyCharsEvent) Chars() []rune { - return e.chars -} - -type KeyEvent struct { - HandlerEvent - key d2enum.Key - // Duration represents the number of frames this key has been pressed for - duration int -} - -func (e *KeyEvent) Key() d2enum.Key { - return e.key -} -func (e *KeyEvent) Duration() int { - return e.duration -} - -type MouseEvent struct { - HandlerEvent - mouseButton d2enum.MouseButton -} - -func (e *MouseEvent) KeyMod() d2enum.KeyMod { - return e.HandlerEvent.keyMod -} - -func (e *MouseEvent) ButtonMod() d2enum.MouseButtonMod { - return e.HandlerEvent.buttonMod -} - -func (e *MouseEvent) X() int { - return e.HandlerEvent.x -} - -func (e *MouseEvent) Y() int { - return e.HandlerEvent.y -} - -func (e *MouseEvent) Button() d2enum.MouseButton { - return e.mouseButton -} - -type MouseMoveEvent struct { - HandlerEvent -} - -func (e *MouseMoveEvent) KeyMod() d2enum.KeyMod { - return e.HandlerEvent.keyMod -} - -func (e *MouseMoveEvent) ButtonMod() d2enum.MouseButtonMod { - return e.HandlerEvent.buttonMod -} - -func (e *MouseMoveEvent) X() int { - return e.HandlerEvent.x -} - -func (e *MouseMoveEvent) Y() int { - return e.HandlerEvent.y -} diff --git a/d2core/d2input/key_event.go b/d2core/d2input/key_event.go new file mode 100644 index 00000000..e0c63e49 --- /dev/null +++ b/d2core/d2input/key_event.go @@ -0,0 +1,21 @@ +package d2input + +import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + +// KeyEvent represents key events +type KeyEvent struct { + HandlerEvent + key d2enum.Key + // Duration represents the number of frames this key has been pressed for + duration int +} + +// Key returns the key +func (e *KeyEvent) Key() d2enum.Key { + return e.key +} + +// Duration returns the duration +func (e *KeyEvent) Duration() int { + return e.duration +} diff --git a/d2core/d2input/keychars_event.go b/d2core/d2input/keychars_event.go new file mode 100644 index 00000000..30fac069 --- /dev/null +++ b/d2core/d2input/keychars_event.go @@ -0,0 +1,12 @@ +package d2input + +// KeyCharsEvent represents a key character event +type KeyCharsEvent struct { + HandlerEvent + chars []rune +} + +// Chars returns the characters +func (e *KeyCharsEvent) Chars() []rune { + return e.chars +} diff --git a/d2core/d2input/mouse_event.go b/d2core/d2input/mouse_event.go new file mode 100644 index 00000000..44c60c2b --- /dev/null +++ b/d2core/d2input/mouse_event.go @@ -0,0 +1,34 @@ +package d2input + +import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + +// MouseEvent represents a mouse event +type MouseEvent struct { + HandlerEvent + mouseButton d2enum.MouseButton +} + +// KeyMod returns the key mod +func (e *MouseEvent) KeyMod() d2enum.KeyMod { + return e.HandlerEvent.keyMod +} + +// ButtonMod represents a button mod +func (e *MouseEvent) ButtonMod() d2enum.MouseButtonMod { + return e.HandlerEvent.buttonMod +} + +// X returns the event's X position +func (e *MouseEvent) X() int { + return e.HandlerEvent.x +} + +// Y returns the event's Y position +func (e *MouseEvent) Y() int { + return e.HandlerEvent.y +} + +// Button returns the mouse button +func (e *MouseEvent) Button() d2enum.MouseButton { + return e.mouseButton +} diff --git a/d2core/d2input/mousemove_event.go b/d2core/d2input/mousemove_event.go new file mode 100644 index 00000000..b8f989cf --- /dev/null +++ b/d2core/d2input/mousemove_event.go @@ -0,0 +1,28 @@ +package d2input + +import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + +// MouseMoveEvent represents a mouse movement event +type MouseMoveEvent struct { + HandlerEvent +} + +// KeyMod represents the key mod +func (e *MouseMoveEvent) KeyMod() d2enum.KeyMod { + return e.HandlerEvent.keyMod +} + +// ButtonMod represents the button mod +func (e *MouseMoveEvent) ButtonMod() d2enum.MouseButtonMod { + return e.HandlerEvent.buttonMod +} + +// X represents the X position +func (e *MouseMoveEvent) X() int { + return e.HandlerEvent.x +} + +// Y represents the Y position +func (e *MouseMoveEvent) Y() int { + return e.HandlerEvent.y +} diff --git a/d2core/d2map/d2mapgen/act1_overworld.go b/d2core/d2map/d2mapgen/act1_overworld.go index 7683394d..4421fdfd 100644 --- a/d2core/d2map/d2mapgen/act1_overworld.go +++ b/d2core/d2map/d2mapgen/act1_overworld.go @@ -1,3 +1,4 @@ +//nolint:gomnd package d2mapgen import ( @@ -37,6 +38,7 @@ func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) { townSize := townStamp.Size() log.Printf("Region Path: %s", townStamp.RegionPath()) + if strings.Contains(townStamp.RegionPath(), "E1") { // East Exit mapEngine.PlaceStamp(townStamp, 0, 0) @@ -63,8 +65,6 @@ func GenerateAct1Overworld(mapEngine *d2mapengine.MapEngine) { // North Exit mapEngine.PlaceStamp(townStamp, mapWidth-townSize.Width, mapHeight-townSize.Height) } - - //mapEngine.RegenerateWalkPaths() } func generateWilderness1TownEast(mapEngine *d2mapengine.MapEngine, startX, startY int) { diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index 4ac6642a..07f1d3a8 100644 --- a/d2core/d2map/d2maprenderer/renderer.go +++ b/d2core/d2map/d2maprenderer/renderer.go @@ -17,6 +17,10 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine" ) +const ( + screenMiddleX = 400 +) + // MapRenderer manages the game viewport and Camera. It requests tile and entity data from MapEngine and renders it. type MapRenderer struct { renderer d2interface.Renderer // Used for drawing operations @@ -32,7 +36,8 @@ type MapRenderer struct { } // CreateMapRenderer creates a new MapRenderer, sets the required fields and returns a pointer to it. -func CreateMapRenderer(renderer d2interface.Renderer, mapEngine *d2mapengine.MapEngine, term d2interface.Terminal, startX, startY float64) *MapRenderer { +func CreateMapRenderer(renderer d2interface.Renderer, mapEngine *d2mapengine.MapEngine, + term d2interface.Terminal, startX, startY float64) *MapRenderer { result := &MapRenderer{ renderer: renderer, mapEngine: mapEngine, @@ -93,8 +98,8 @@ func (mr *MapRenderer) SetMapEngine(mapEngine *d2mapengine.MapEngine) { func (mr *MapRenderer) Render(target d2interface.Surface) { mapSize := mr.mapEngine.Size() - stxf, styf := mr.viewport.ScreenToWorld(400, -200) - etxf, etyf := mr.viewport.ScreenToWorld(400, 1050) + stxf, styf := mr.viewport.ScreenToWorld(screenMiddleX, -200) + etxf, etyf := mr.viewport.ScreenToWorld(screenMiddleX, 1050) startX := int(math.Max(0, math.Floor(stxf))) startY := int(math.Max(0, math.Floor(styf))) @@ -322,7 +327,7 @@ func (mr *MapRenderer) renderShadow(tile d2ds1.FloorShadowRecord, target d2inter defer mr.viewport.PushTranslationOrtho(-80, float64(tile.YAdjust)).PopTranslation() target.PushTranslation(mr.viewport.GetTranslationScreen()) - target.PushColor(color.RGBA{R: 255, G: 255, B: 255, A: 160}) + target.PushColor(color.RGBA{R: 255, G: 255, B: 255, A: 160}) //nolint:gomnd // Not a magic number... defer target.PopN(2) @@ -343,6 +348,7 @@ func (mr *MapRenderer) renderMapDebug(mapDebugVisLevel int, target d2interface.S func (mr *MapRenderer) renderEntityDebug(target d2interface.Surface) { entities := *mr.mapEngine.Entities() + for idx := range entities { e := entities[idx] pos := e.GetPosition() diff --git a/d2core/d2map/d2maprenderer/viewport.go b/d2core/d2map/d2maprenderer/viewport.go index 080a02ba..105f1fba 100644 --- a/d2core/d2map/d2maprenderer/viewport.go +++ b/d2core/d2map/d2maprenderer/viewport.go @@ -12,13 +12,15 @@ type worldTrans struct { } const ( - center = 0 - left = 1 - right = 2 + center = 0 + left = 1 + right = 2 + tileWidth = 80 + tileHeight = 40 + half = 2 ) // Viewport is used for converting vectors between screen (pixel), orthogonal (Camera) and world (isometric) space. -// TODO: Has a coordinate (issue #456) type Viewport struct { defaultScreenRect d2common.Rectangle screenRect d2common.Rectangle @@ -68,16 +70,16 @@ func (v *Viewport) ScreenToWorld(x, y int) (worldX, worldY float64) { // OrthoToWorld returns the world position for the given orthogonal coordinates. func (v *Viewport) OrthoToWorld(x, y float64) (worldX, worldY float64) { - worldX = (x/80 + y/40) / 2 - worldY = (y/40 - x/80) / 2 + worldX = (x/80 + y/40) / half + worldY = (y/40 - x/80) / half return worldX, worldY } // WorldToOrtho returns the orthogonal position for the given world coordinates. func (v *Viewport) WorldToOrtho(x, y float64) (orthoX, orthoY float64) { - orthoX = (x - y) * 80 - orthoY = (x + y) * 40 + orthoX = (x - y) * tileWidth + orthoY = (x + y) * tileHeight return orthoX, orthoY } @@ -119,10 +121,10 @@ func (v *Viewport) IsTileVisible(x, y float64) bool { // IsTileRectVisible returns false if none of the tiles rects are within the game screen. func (v *Viewport) IsTileRectVisible(rect d2common.Rectangle) bool { - left := float64((rect.Left - rect.Bottom()) * 80) - top := float64((rect.Left + rect.Top) * 40) - right := float64((rect.Right() - rect.Top) * 80) - bottom := float64((rect.Right() + rect.Bottom()) * 40) + left := float64((rect.Left - rect.Bottom()) * tileWidth) + top := float64((rect.Left + rect.Top) * tileHeight) + right := float64((rect.Right() - rect.Top) * tileWidth) + bottom := float64((rect.Right() + rect.Bottom()) * tileHeight) return v.IsOrthoRectVisible(left, top, right, bottom) } @@ -181,8 +183,8 @@ func (v *Viewport) getCameraOffset() (camX, camY float64) { camX, camY = camPosition.X(), camPosition.Y() } - camX -= float64(v.screenRect.Width / 2) - camY -= float64(v.screenRect.Height / 2) + camX -= float64(v.screenRect.Width / half) + camY -= float64(v.screenRect.Height / half) return camX, camY } @@ -192,8 +194,8 @@ func (v *Viewport) toLeft() { return } - v.screenRect.Width = v.defaultScreenRect.Width / 2 - v.screenRect.Left = v.defaultScreenRect.Left + v.defaultScreenRect.Width/2 + v.screenRect.Width = v.defaultScreenRect.Width / half + v.screenRect.Left = v.defaultScreenRect.Left + v.defaultScreenRect.Width/half v.align = left } @@ -202,7 +204,7 @@ func (v *Viewport) toRight() { return } - v.screenRect.Width = v.defaultScreenRect.Width / 2 + v.screenRect.Width = v.defaultScreenRect.Width / half v.align = right } diff --git a/d2core/d2object/init_function.go b/d2core/d2object/init_function.go index 8546f4bc..66c5a5a2 100644 --- a/d2core/d2object/init_function.go +++ b/d2core/d2object/init_function.go @@ -7,8 +7,8 @@ import ( ) // Finds an init function for the given object -func initObject(ob *Object) bool { - funcs := map[int]func(*Object){ +func initObject(ob *Object) (bool, error) { + funcs := map[int]func(*Object) error{ 8: initTorch, 14: initTorch, 17: initWaypoint, @@ -17,35 +17,41 @@ func initObject(ob *Object) bool { fun, ok := funcs[ob.objectRecord.InitFn] if !ok { - return false + return false, nil } - fun(ob) + if err := fun(ob); err != nil { + return false, err + } - return true + return true, nil } // Initializes torch/brazier type objects -func initTorch(ob *Object) { +func initTorch(ob *Object) error { if ob.objectRecord.HasAnimationMode[d2enum.ObjectAnimationModeOpened] { - ob.setMode(d2enum.ObjectAnimationModeOpened, 0, true) + return ob.setMode(d2enum.ObjectAnimationModeOpened, 0, true) } + + return nil } -func initWaypoint(ob *Object) { +func initWaypoint(ob *Object) error { // Turn these on unconditionally for now, they look nice :) if ob.objectRecord.HasAnimationMode[d2enum.ObjectAnimationModeOpened] { - ob.setMode(d2enum.ObjectAnimationModeOpened, 0, true) + return ob.setMode(d2enum.ObjectAnimationModeOpened, 0, true) } + + return nil } // Randomly spawns in either NU or OP -func initTorchRnd(ob *Object) { +func initTorchRnd(ob *Object) error { n := rand.Intn(2) if n > 0 { - ob.setMode(d2enum.ObjectAnimationModeNeutral, 0, true) - } else { - ob.setMode(d2enum.ObjectAnimationModeOperating, 0, true) + return ob.setMode(d2enum.ObjectAnimationModeNeutral, 0, true) } + + return ob.setMode(d2enum.ObjectAnimationModeOperating, 0, true) } diff --git a/d2core/d2ui/button.go b/d2core/d2ui/button.go index e6544f19..5ed35cc2 100644 --- a/d2core/d2ui/button.go +++ b/d2core/d2ui/button.go @@ -222,7 +222,7 @@ func (v *Button) Activate() { } // Render renders the button -func (v *Button) Render(target d2interface.Surface) { +func (v *Button) Render(target d2interface.Surface) error { target.PushFilter(d2enum.FilterNearest) target.PushTranslation(v.x, v.y) @@ -248,6 +248,8 @@ func (v *Button) Render(target d2interface.Surface) { if err != nil { fmt.Printf("failed to render button surface, err: %v\n", err) } + + return nil } // Toggle negates the toggled state of the button diff --git a/d2core/d2ui/checkbox.go b/d2core/d2ui/checkbox.go index 792f051a..6be58a9e 100644 --- a/d2core/d2ui/checkbox.go +++ b/d2core/d2ui/checkbox.go @@ -7,17 +7,21 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" ) +// Checkbox represents a checkbox UI element type Checkbox struct { - x, y int - checkState bool - visible bool - width, height int - Image d2interface.Surface - checkedImage d2interface.Surface - onClick func() - enabled bool + Image d2interface.Surface + checkedImage d2interface.Surface + x int + y int + width int + height int + onClick func() + checkState bool + visible bool + enabled bool } +// CreateCheckbox creates a new instance of a checkbox func CreateCheckbox(renderer d2interface.Renderer, checkState bool) Checkbox { result := Checkbox{ checkState: checkState, @@ -39,12 +43,15 @@ func CreateCheckbox(renderer d2interface.Renderer, checkState bool) Checkbox { result.checkedImage, _ = renderer.NewSurface(result.width, result.height, d2enum.FilterNearest) _ = checkboxSprite.RenderSegmented(result.checkedImage, 1, 1, 1) + return result } -func (v *Checkbox) Render(target d2interface.Surface) { +// Render renders the checkbox +func (v *Checkbox) Render(target d2interface.Surface) error { target.PushTranslation(v.x, v.y) target.PushFilter(d2enum.FilterNearest) + defer target.PopN(2) if v.checkState { @@ -52,39 +59,50 @@ func (v *Checkbox) Render(target d2interface.Surface) { } else { _ = target.Render(v.Image) } + + return nil } +// Advance does nothing for checkboxes func (v *Checkbox) Advance(elapsed float64) { } +// GetEnabled returns the enabled state of the checkbox func (v *Checkbox) GetEnabled() bool { return v.enabled } +// SetEnabled sets the enabled state of the checkbox func (v *Checkbox) SetEnabled(enabled bool) { v.enabled = enabled } +// SetPressed does nothing for checkboxes func (v *Checkbox) SetPressed(_ bool) { } +// SetCheckState sets the check state of the checkbox func (v *Checkbox) SetCheckState(checkState bool) { v.checkState = checkState } +// GetCheckState returns the check state of the checkbox func (v *Checkbox) GetCheckState() bool { return v.checkState } +// GetPressed returns the pressed state of the checkbox func (v *Checkbox) GetPressed() bool { return v.checkState } +// OnACtivated sets the callback function of the click event for the checkbox func (v *Checkbox) OnActivated(callback func()) { v.onClick = callback } +// Activate activates the checkbox func (v *Checkbox) Activate() { v.checkState = !v.checkState if v.onClick == nil { @@ -93,23 +111,28 @@ func (v *Checkbox) Activate() { v.onClick() } +// GetPosition returns the position of the checkbox func (v *Checkbox) GetPosition() (int, int) { return v.x, v.y } +// GetSize returns the size of the checkbox func (v *Checkbox) GetSize() (int, int) { return v.width, v.height } +// GetVisible returns the visibility state of the checkbox func (v *Checkbox) GetVisible() bool { return v.visible } +// SetPosition sets the position of the checkbox func (v *Checkbox) SetPosition(x int, y int) { v.x = x v.y = y } +// SetVisible sets the visibility of the checkbox func (v *Checkbox) SetVisible(visible bool) { v.visible = visible } diff --git a/d2core/d2ui/drawable.go b/d2core/d2ui/drawable.go index 5e2572e4..31b5f78b 100644 --- a/d2core/d2ui/drawable.go +++ b/d2core/d2ui/drawable.go @@ -6,7 +6,7 @@ import ( // Drawable represents an instance that can be drawn type Drawable interface { - Render(target d2interface.Surface) + Render(target d2interface.Surface) error Advance(elapsed float64) GetSize() (width, height int) SetPosition(x, y int) diff --git a/d2core/d2ui/scrollbar.go b/d2core/d2ui/scrollbar.go index c7268c61..3cfe8cd4 100644 --- a/d2core/d2ui/scrollbar.go +++ b/d2core/d2ui/scrollbar.go @@ -74,27 +74,46 @@ func (v *Scrollbar) GetLastDirChange() int { return v.lastDirChange } -func (v *Scrollbar) Render(target d2interface.Surface) { +func (v *Scrollbar) Render(target d2interface.Surface) error { if !v.visible || v.maxOffset == 0 { - return + return nil } + offset := 0 + if !v.enabled { offset = 2 } + v.scrollbarSprite.SetPosition(v.x, v.y) - v.scrollbarSprite.RenderSegmented(target, 1, 1, 0+offset) - v.scrollbarSprite.SetPosition(v.x, v.y+v.height-10) - v.scrollbarSprite.RenderSegmented(target, 1, 1, 1+offset) - if v.maxOffset == 0 || v.currentOffset < 0 || v.currentOffset > v.maxOffset { - return + + if err := v.scrollbarSprite.RenderSegmented(target, 1, 1, 0+offset); err != nil { + return err } + + v.scrollbarSprite.SetPosition(v.x, v.y+v.height-10) + + if err := v.scrollbarSprite.RenderSegmented(target, 1, 1, 1+offset); err != nil { + return err + } + + if v.maxOffset == 0 || v.currentOffset < 0 || v.currentOffset > v.maxOffset { + return nil + } + v.scrollbarSprite.SetPosition(v.x, v.y+10+v.getBarPosition()) + offset = 0 + if !v.enabled { offset = 1 } - v.scrollbarSprite.RenderSegmented(target, 1, 1, 4+offset) + + if err := v.scrollbarSprite.RenderSegmented(target, 1, 1, 4+offset); err != nil { + return err + } + + return nil } func (v *Scrollbar) Advance(elapsed float64) { diff --git a/d2core/d2ui/textbox.go b/d2core/d2ui/textbox.go index 959463b5..af64fd18 100644 --- a/d2core/d2ui/textbox.go +++ b/d2core/d2ui/textbox.go @@ -5,9 +5,7 @@ import ( "time" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" ) @@ -26,7 +24,8 @@ type TextBox struct { isFocused bool } -func CreateTextbox(renderer d2interface.Renderer) TextBox { +// CreateTextbox creates a new instance of a text box +func CreateTextbox() TextBox { animation, _ := d2asset.LoadAnimation(d2resource.TextBox2, d2resource.PaletteUnits) bgSprite, _ := LoadSprite(animation) tb := TextBox{ @@ -42,41 +41,59 @@ func CreateTextbox(renderer d2interface.Renderer) TextBox { return tb } +// SetFilter sets the text box filter func (v *TextBox) SetFilter(filter string) { v.filter = filter } -func (v *TextBox) Render(target d2interface.Surface) { +// Render renders the text box +func (v *TextBox) Render(target d2interface.Surface) error { if !v.visible { - return + return nil } - v.bgSprite.Render(target) + + if err := v.bgSprite.Render(target); err != nil { + return err + } + v.textLabel.Render(target) + if (time.Now().UnixNano()/1e6)&(1<<8) > 0 { v.lineBar.Render(target) } + + return nil } +// OnKeyChars handles key character events func (v *TextBox) OnKeyChars(event d2interface.KeyCharsEvent) bool { if !v.isFocused || !v.visible || !v.enabled { return false } + newText := string(event.Chars()) + if len(newText) > 0 { v.text += newText + v.SetText(v.text) + return true } + return false } +// OnKeyRepeat handles key repeat events func (v *TextBox) OnKeyRepeat(event d2interface.KeyEvent) bool { if event.Key() == d2enum.KeyBackspace && debounceEvents(event.Duration()) { if len(v.text) >= 1 { v.text = v.text[:len(v.text)-1] } + v.SetText(v.text) } + return false } @@ -85,97 +102,127 @@ func debounceEvents(numFrames int) bool { delay = 30 interval = 3 ) + if numFrames == 1 { return true } + if numFrames >= delay && (numFrames-delay)%interval == 0 { return true } + return false } +// Advance updates the text box func (v *TextBox) Advance(_ float64) { if !v.visible || !v.enabled { return } } +// Update updates the textbox func (v *TextBox) Update() { } +// GetText returns the text box's text func (v *TextBox) GetText() string { return v.text } +// SetText sets the text box's text +//nolint:gomnd // Built-in values func (v *TextBox) SetText(newText string) { result := "" + for _, c := range newText { if !strings.Contains(v.filter, string(c)) { continue } + result += string(c) } + if len(result) > 15 { result = result[0:15] } + v.text = result + for { tw, _ := v.textLabel.GetTextMetrics(result) + if tw > 150 { result = result[1:] continue } + v.lineBar.SetPosition(v.x+6+tw, v.y+3) v.textLabel.SetText(result) + break } } +// GetSize returns the size of the text box func (v *TextBox) GetSize() (width, height int) { return v.bgSprite.GetCurrentFrameSize() } +// SetPosition sets the position of the text box +//nolint:gomnd // Built-in values func (v *TextBox) SetPosition(x, y int) { lw, _ := v.textLabel.GetSize() + v.x = x v.y = y + v.textLabel.SetPosition(v.x+6, v.y+3) v.lineBar.SetPosition(v.x+6+lw, v.y+3) v.bgSprite.SetPosition(v.x, v.y+26) } +// GetPosition returns the position of the text box func (v *TextBox) GetPosition() (x, y int) { return v.x, v.y } +// GetVisible returns the visibility of the text box func (v *TextBox) GetVisible() bool { return v.visible } +// SetVisible sets the visibility of the text box func (v *TextBox) SetVisible(visible bool) { v.visible = visible } +// GetEnabled returns the enabled state of the text box func (v *TextBox) GetEnabled() bool { return v.enabled } +// SetEnabled sets the enabled state of the text box func (v *TextBox) SetEnabled(enabled bool) { v.enabled = enabled } -func (v *TextBox) SetPressed(pressed bool) { +// SetPressed does nothing for text boxes +func (v *TextBox) SetPressed(_ bool) { // no op } +// GetPressed does nothing for text boxes func (v *TextBox) GetPressed() bool { return false } -func (v *TextBox) OnActivated(callback func()) { +// OnActivated handles activation events for the text box +func (v *TextBox) OnActivated(_ func()) { // no op } +// Activate activates the text box func (v *TextBox) Activate() { v.isFocused = true } diff --git a/d2game/d2gamescreen/game.go b/d2game/d2gamescreen/game.go index f6583bac..8134e57f 100644 --- a/d2game/d2gamescreen/game.go +++ b/d2game/d2gamescreen/game.go @@ -120,7 +120,9 @@ func (v *Game) Render(screen d2interface.Surface) error { v.mapRenderer.Render(screen) if v.gameControls != nil { - v.gameControls.Render(screen) + if err := v.gameControls.Render(screen); err != nil { + return err + } } return nil @@ -166,7 +168,9 @@ func (v *Game) Advance(elapsed float64) error { // Bind the game controls to the player once it exists if v.gameControls == nil { - v.bindGameControls() + if err := v.bindGameControls(); err != nil { + return err + } } // Update the camera to focus on the player @@ -180,14 +184,21 @@ func (v *Game) Advance(elapsed float64) error { return nil } -func (v *Game) bindGameControls() { +func (v *Game) bindGameControls() error { for _, player := range v.gameClient.Players { if player.ID != v.gameClient.PlayerID { continue } v.localPlayer = player - v.gameControls = d2player.NewGameControls(v.renderer, player, v.gameClient.MapEngine, v.mapRenderer, v, v.terminal) + + var err error + v.gameControls, err = d2player.NewGameControls(v.renderer, player, v.gameClient.MapEngine, v.mapRenderer, v, v.terminal) + + if err != nil { + return err + } + v.gameControls.Load() if err := v.inputManager.BindHandler(v.gameControls); err != nil { @@ -196,6 +207,8 @@ func (v *Game) bindGameControls() { break } + + return nil } // OnPlayerMove sends the player move action to the server diff --git a/d2game/d2gamescreen/main_menu.go b/d2game/d2gamescreen/main_menu.go index 850e2ce3..c9830e64 100644 --- a/d2game/d2gamescreen/main_menu.go +++ b/d2game/d2gamescreen/main_menu.go @@ -148,7 +148,7 @@ func (v *MainMenu) OnLoad(loading d2screen.LoadingState) { v.createLogos(loading) v.createButtons(loading) - v.tcpJoinGameEntry = d2ui.CreateTextbox(v.renderer) + v.tcpJoinGameEntry = d2ui.CreateTextbox() v.tcpJoinGameEntry.SetPosition(joinGameDialogX, joinGameDialogY) v.tcpJoinGameEntry.SetFilter(joinGameCharacterFilter) d2ui.AddWidget(&v.tcpJoinGameEntry) diff --git a/d2game/d2gamescreen/select_hero_class.go b/d2game/d2gamescreen/select_hero_class.go index c0f3cd07..67a866cc 100644 --- a/d2game/d2gamescreen/select_hero_class.go +++ b/d2game/d2gamescreen/select_hero_class.go @@ -429,7 +429,7 @@ func (v *SelectHeroClass) createButtons() { } func (v *SelectHeroClass) createCheckboxes(renderer d2interface.Renderer) { - v.heroNameTextbox = d2ui.CreateTextbox(renderer) + v.heroNameTextbox = d2ui.CreateTextbox() v.heroNameTextbox.SetPosition(heroNameTextBoxX, heoNameTextBoxY) v.heroNameTextbox.SetVisible(false) d2ui.AddWidget(&v.heroNameTextbox) diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index 52e817eb..7cd374fb 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -23,6 +23,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" ) +// Panel represents the panel at the bottom of the game screen type Panel interface { IsOpen() bool Toggle() @@ -38,10 +39,7 @@ const ( globeWidth = 80 ) -var leftMenuRect = d2common.Rectangle{Left: 0, Top: 0, Width: 400, Height: 600} -var rightMenuRect = d2common.Rectangle{Left: 400, Top: 0, Width: 400, Height: 600} -var bottomMenuRect = d2common.Rectangle{Left: 0, Top: 550, Width: 800, Height: 50} - +// GameControls represents the game's controls on the screen type GameControls struct { renderer d2interface.Renderer // TODO: This shouldn't be a dependency hero *d2mapentity.Player @@ -69,6 +67,7 @@ type GameControls struct { } type ActionableType int + type ActionableRegion struct { ActionableTypeId ActionableType Rect d2common.Rectangle @@ -87,7 +86,7 @@ const ( ) func NewGameControls(renderer d2interface.Renderer, hero *d2mapentity.Player, mapEngine *d2mapengine.MapEngine, - mapRenderer *d2maprenderer.MapRenderer, inputListener InputCallbackListener, term d2interface.Terminal) *GameControls { + mapRenderer *d2maprenderer.MapRenderer, inputListener InputCallbackListener, term d2interface.Terminal) (*GameControls, error) { missileID := initialMissileID term.BindAction("setmissile", "set missile id to summon on right click", func(id int) { missileID = id @@ -104,6 +103,7 @@ func NewGameControls(renderer d2interface.Renderer, hero *d2mapentity.Player, ma // TODO make this depend on the hero type to respect inventory.txt var inventoryRecordKey string + switch hero.Class { case d2enum.HeroAssassin: inventoryRecordKey = "Assassin" @@ -148,11 +148,15 @@ func NewGameControls(renderer d2interface.Renderer, hero *d2mapentity.Player, ma }, } - term.BindAction("freecam", "toggle free camera movement", func() { + err := term.BindAction("freecam", "toggle free camera movement", func() { gc.FreeCam = !gc.FreeCam }) - return gc + if err != nil { + return nil, err + } + + return gc, nil } func (g *GameControls) OnKeyRepeat(event d2interface.KeyEvent) bool { @@ -238,6 +242,7 @@ func (g *GameControls) OnMouseButtonRepeat(event d2interface.MouseEvent) bool { if isLeft && shouldDoLeft && inRect { lastLeftBtnActionTime = now + g.inputListener.OnPlayerMove(px, py) if g.FreeCam { @@ -259,7 +264,9 @@ func (g *GameControls) OnMouseButtonRepeat(event d2interface.MouseEvent) bool { if isRight && shouldDoRight && inRect { lastRightBtnActionTime = now + g.inputListener.OnPlayerCast(g.missileID, px, py) + return true } @@ -283,6 +290,7 @@ func (g *GameControls) OnMouseMove(event d2interface.MouseMoveEvent) bool { func (g *GameControls) OnMouseButtonDown(event d2interface.MouseEvent) bool { mx, my := event.X(), event.Y() + for i := range g.actionableRegions { // If click is on a game control element if g.actionableRegions[i].Rect.IsInRect(mx, my) { @@ -297,13 +305,17 @@ func (g *GameControls) OnMouseButtonDown(event d2interface.MouseEvent) bool { if event.Button() == d2enum.MouseButtonLeft && !g.isInActiveMenusRect(mx, my) { lastLeftBtnActionTime = d2common.Now() + g.inputListener.OnPlayerMove(px, py) + return true } if event.Button() == d2enum.MouseButtonRight && !g.isInActiveMenusRect(mx, my) { lastRightBtnActionTime = d2common.Now() + g.inputListener.OnPlayerCast(g.missileID, px, py) + return true } @@ -335,11 +347,14 @@ func (g *GameControls) Load() { func (g *GameControls) loadUIButtons() { // Run button g.runButton = d2ui.CreateButton(g.renderer, d2ui.ButtonTypeRun, "") + g.runButton.SetPosition(255, 570) g.runButton.OnActivated(func() { g.onToggleRunButton() }) + if g.hero.IsRunToggled() { g.runButton.Toggle() } + d2ui.AddWidget(&g.runButton) } @@ -350,7 +365,6 @@ func (g *GameControls) onToggleRunButton() { g.hero.SetIsRunning(g.hero.IsRunToggled()) } -// ScreenAdvanceHandler func (g *GameControls) Advance(elapsed float64) error { g.mapRenderer.Advance(elapsed) return nil @@ -380,6 +394,12 @@ func (g *GameControls) isRightPanelOpen() bool { } func (g *GameControls) isInActiveMenusRect(px int, py int) bool { + var bottomMenuRect = d2common.Rectangle{Left: 0, Top: 550, Width: 800, Height: 50} + + var leftMenuRect = d2common.Rectangle{Left: 0, Top: 0, Width: 400, Height: 600} + + var rightMenuRect = d2common.Rectangle{Left: 400, Top: 0, Width: 400, Height: 600} + if bottomMenuRect.IsInRect(px, py) { return true } @@ -396,7 +416,7 @@ func (g *GameControls) isInActiveMenusRect(px int, py int) bool { } // TODO: consider caching the panels to single image that is reused. -func (g *GameControls) Render(target d2interface.Surface) { +func (g *GameControls) Render(target d2interface.Surface) error { for entityIdx := range *g.mapEngine.Entities() { entity := (*g.mapEngine.Entities())[entityIdx] if !entity.Selectable() { @@ -413,6 +433,7 @@ func (g *GameControls) Render(target d2interface.Surface) { g.nameLabel.SetPosition(entScreenX, entScreenY-100) g.nameLabel.Render(target) entity.Highlight() + break } } @@ -424,110 +445,213 @@ func (g *GameControls) Render(target d2interface.Surface) { offset := 0 // Left globe holder - g.mainPanel.SetCurrentFrame(0) + if err := g.mainPanel.SetCurrentFrame(0); err != nil { + return err + } + w, _ := g.mainPanel.GetCurrentFrameSize() + g.mainPanel.SetPosition(offset, height) - g.mainPanel.Render(target) + + if err := g.mainPanel.Render(target); err != nil { + return err + } // Health status bar healthPercent := float64(g.hero.Stats.Health) / float64(g.hero.Stats.MaxHealth) hpBarHeight := int(healthPercent * float64(globeHeight)) - g.hpManaStatusSprite.SetCurrentFrame(0) + + if err := g.hpManaStatusSprite.SetCurrentFrame(0); err != nil { + return err + } + g.hpManaStatusSprite.SetPosition(offset+30, height-13) - g.hpManaStatusSprite.RenderSection(target, image.Rect(0, globeHeight-hpBarHeight, globeWidth, globeHeight)) + + if err := g.hpManaStatusSprite.RenderSection(target, image.Rect(0, globeHeight-hpBarHeight, globeWidth, globeHeight)); err != nil { + return err + } // Left globe - g.globeSprite.SetCurrentFrame(0) + if err := g.globeSprite.SetCurrentFrame(0); err != nil { + return err + } + g.globeSprite.SetPosition(offset+28, height-5) - g.globeSprite.Render(target) + + if err := g.globeSprite.Render(target); err != nil { + return err + } offset += w // Left skill - g.skillIcon.SetCurrentFrame(2) + if err := g.skillIcon.SetCurrentFrame(2); err != nil { + return err + } + w, _ = g.skillIcon.GetCurrentFrameSize() + g.skillIcon.SetPosition(offset, height) - g.skillIcon.Render(target) + + if err := g.skillIcon.Render(target); err != nil { + return err + } + offset += w // Left skill selector - g.mainPanel.SetCurrentFrame(1) + if err := g.mainPanel.SetCurrentFrame(1); err != nil { + return err + } + w, _ = g.mainPanel.GetCurrentFrameSize() + g.mainPanel.SetPosition(offset, height) - g.mainPanel.Render(target) + + if err := g.mainPanel.Render(target); err != nil { + return err + } + offset += w // Stamina - g.mainPanel.SetCurrentFrame(2) + if err := g.mainPanel.SetCurrentFrame(2); err != nil { + return err + } + w, _ = g.mainPanel.GetCurrentFrameSize() + g.mainPanel.SetPosition(offset, height) - g.mainPanel.Render(target) + + if err := g.mainPanel.Render(target); err != nil { + return err + } + offset += w // Stamina status bar target.PushTranslation(273, 572) target.PushEffect(d2enum.DrawEffectModulate) + staminaPercent := float64(g.hero.Stats.Stamina) / float64(g.hero.Stats.MaxStamina) + target.DrawRect(int(staminaPercent*staminaBarWidth), 19, color.RGBA{R: 175, G: 136, B: 72, A: 200}) target.PopN(2) // Experience status bar target.PushTranslation(256, 561) + expPercent := float64(g.hero.Stats.Experience) / float64(g.hero.Stats.NextLevelExp) + target.DrawRect(int(expPercent*expBarWidth), 2, color.RGBA{R: 255, G: 255, B: 255, A: 255}) target.Pop() // Center menu button - g.menuButton.SetCurrentFrame(0) + if err := g.menuButton.SetCurrentFrame(0); err != nil { + return err + } + w, _ = g.mainPanel.GetCurrentFrameSize() + g.menuButton.SetPosition((width/2)-8, height-16) - g.menuButton.Render(target) + + if err := g.menuButton.Render(target); err != nil { + return err + } // Potions - g.mainPanel.SetCurrentFrame(3) + if err := g.mainPanel.SetCurrentFrame(3); err != nil { + return err + } + w, _ = g.mainPanel.GetCurrentFrameSize() + g.mainPanel.SetPosition(offset, height) - g.mainPanel.Render(target) + + if err := g.mainPanel.Render(target); err != nil { + return err + } + offset += w // Right skill selector - g.mainPanel.SetCurrentFrame(4) + if err := g.mainPanel.SetCurrentFrame(4); err != nil { + return err + } + w, _ = g.mainPanel.GetCurrentFrameSize() + g.mainPanel.SetPosition(offset, height) - g.mainPanel.Render(target) + + if err := g.mainPanel.Render(target); err != nil { + return err + } + offset += w // Right skill - g.skillIcon.SetCurrentFrame(2) + if err := g.skillIcon.SetCurrentFrame(2); err != nil { + return err + } + w, _ = g.skillIcon.GetCurrentFrameSize() + g.skillIcon.SetPosition(offset, height) - g.skillIcon.Render(target) + + if err := g.skillIcon.Render(target); err != nil { + return err + } + offset += w // Right globe holder - g.mainPanel.SetCurrentFrame(5) + if err := g.mainPanel.SetCurrentFrame(5); err != nil { + return err + } + w, _ = g.mainPanel.GetCurrentFrameSize() + g.mainPanel.SetPosition(offset, height) - g.mainPanel.Render(target) + + if err := g.mainPanel.Render(target); err != nil { + return err + } // Mana status bar manaPercent := float64(g.hero.Stats.Mana) / float64(g.hero.Stats.MaxMana) manaBarHeight := int(manaPercent * float64(globeHeight)) - g.hpManaStatusSprite.SetCurrentFrame(1) + + if err := g.hpManaStatusSprite.SetCurrentFrame(1); err != nil { + return err + } + g.hpManaStatusSprite.SetPosition(offset+7, height-12) - g.hpManaStatusSprite.RenderSection(target, image.Rect(0, globeHeight-manaBarHeight, globeWidth, globeHeight)) + + if err := g.hpManaStatusSprite.RenderSection(target, image.Rect(0, globeHeight-manaBarHeight, globeWidth, globeHeight)); err != nil { + return err + } // Right globe - g.globeSprite.SetCurrentFrame(1) + if err := g.globeSprite.SetCurrentFrame(1); err != nil { + return err + } + g.globeSprite.SetPosition(offset+8, height-8) - g.globeSprite.Render(target) - g.globeSprite.Render(target) + + if err := g.globeSprite.Render(target); err != nil { + return err + } + + if err := g.globeSprite.Render(target); err != nil { + return err + } if g.isZoneTextShown { g.zoneChangeText.SetPosition(width/2, height/4) g.zoneChangeText.Render(target) } + return nil } func (g *GameControls) SetZoneChangeText(text string) { diff --git a/d2game/d2player/hero_stats_panel.go b/d2game/d2player/hero_stats_panel.go index 86290273..e2d0d221 100644 --- a/d2game/d2player/hero_stats_panel.go +++ b/d2game/d2player/hero_stats_panel.go @@ -1,18 +1,18 @@ package d2player import ( - "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" "strconv" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" ) +// PanelText represents text on the panel type PanelText struct { X int Y int @@ -22,6 +22,7 @@ type PanelText struct { AlignCenter bool } +// StatsPanelLabels represents the labels in the status panel type StatsPanelLabels struct { Level d2ui.Label Experience d2ui.Label @@ -38,36 +39,10 @@ type StatsPanelLabels struct { Stamina d2ui.Label } -var StaticTextLabels = []PanelText{ - {X: 110, Y: 100, Text: "Level", Font: d2resource.Font6, AlignCenter: true}, - {X: 200, Y: 100, Text: "Experience", Font: d2resource.Font6, AlignCenter: true}, - {X: 330, Y: 100, Text: "Next Level", Font: d2resource.Font6, AlignCenter: true}, - {X: 100, Y: 150, Text: "Strength", Font: d2resource.Font6}, - {X: 100, Y: 213, Text: "Dexterity", Font: d2resource.Font6}, - {X: 100, Y: 300, Text: "Vitality", Font: d2resource.Font6}, - {X: 100, Y: 360, Text: "Energy", Font: d2resource.Font6}, - {X: 280, Y: 260, Text: "Defense", Font: d2resource.Font6}, - {X: 280, Y: 300, Text: "Stamina", Font: d2resource.Font6, AlignCenter: true}, - {X: 280, Y: 322, Text: "Life", Font: d2resource.Font6, AlignCenter: true}, - {X: 280, Y: 360, Text: "Mana", Font: d2resource.Font6, AlignCenter: true}, - - // can't use "Fire\nResistance" because line spacing is too big and breaks the layout - {X: 310, Y: 395, Text: "Fire", Font: d2resource.Font6, AlignCenter: true}, - {X: 310, Y: 402, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true}, - - {X: 310, Y: 420, Text: "Cold", Font: d2resource.Font6, AlignCenter: true}, - {X: 310, Y: 427, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true}, - - {X: 310, Y: 445, Text: "Lightning", Font: d2resource.Font6, AlignCenter: true}, - {X: 310, Y: 452, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true}, - - {X: 310, Y: 468, Text: "Poison", Font: d2resource.Font6, AlignCenter: true}, - {X: 310, Y: 477, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true}, -} - // stores all the labels that can change during gameplay(e.g. current level, current hp, mana, etc.) var StatValueLabels = make([]d2ui.Label, 13) +// HeroStatsPanel represents the hero status panel type HeroStatsPanel struct { frame *d2ui.Sprite panel *d2ui.Sprite @@ -83,6 +58,7 @@ type HeroStatsPanel struct { isOpen bool } +// NewHeroStatsPanel creates a new hero status panel func NewHeroStatsPanel(renderer d2interface.Renderer, heroName string, heroClass d2enum.Hero, heroState *d2hero.HeroStatsState) *HeroStatsPanel { originX := 0 @@ -99,6 +75,7 @@ func NewHeroStatsPanel(renderer d2interface.Renderer, heroName string, heroClass } } +// Load loads the data for the hero status panel func (s *HeroStatsPanel) Load() { animation, _ := d2asset.LoadAnimation(d2resource.Frame, d2resource.PaletteSky) s.frame, _ = d2ui.LoadSprite(animation) @@ -107,25 +84,30 @@ func (s *HeroStatsPanel) Load() { s.initStatValueLabels() } +// IsOpen returns true if the hero status panel is open func (s *HeroStatsPanel) IsOpen() bool { return s.isOpen } +// Toggle toggles the visibility of the hero status panel func (s *HeroStatsPanel) Toggle() { s.isOpen = !s.isOpen } +// Open opens the hero status panel func (s *HeroStatsPanel) Open() { s.isOpen = true } +// Close closed the hero status panel func (s *HeroStatsPanel) Close() { s.isOpen = false } -func (s *HeroStatsPanel) Render(target d2interface.Surface) { +// Render renders the hero status panel +func (s *HeroStatsPanel) Render(target d2interface.Surface) error { if !s.isOpen { - return + return nil } if s.staticMenuImageCache == nil { @@ -134,53 +116,101 @@ func (s *HeroStatsPanel) Render(target d2interface.Surface) { surface, err := s.renderer.NewSurface(frameWidth*framesCount, frameHeight*framesCount, d2enum.FilterNearest) if err != nil { - return + return err } + s.staticMenuImageCache = &surface - s.renderStaticMenu(*s.staticMenuImageCache) + + if err := s.renderStaticMenu(*s.staticMenuImageCache); err != nil { + return err + } } - target.Render(*s.staticMenuImageCache) + + if err := target.Render(*s.staticMenuImageCache); err != nil { + return err + } + s.renderStatValues(target) + + return nil } -func (s *HeroStatsPanel) renderStaticMenu(target d2interface.Surface) { +func (s *HeroStatsPanel) renderStaticMenu(target d2interface.Surface) error { x, y := s.originX, s.originY // Frame // Top left - s.frame.SetCurrentFrame(0) + if err := s.frame.SetCurrentFrame(0); err != nil { + return err + } + w, h := s.frame.GetCurrentFrameSize() + s.frame.SetPosition(x, y+h) - s.frame.Render(target) + + if err := s.frame.Render(target); err != nil { + return err + } + x += w y += h // Top right - s.frame.SetCurrentFrame(1) + if err := s.frame.SetCurrentFrame(1); err != nil { + return err + } + w, h = s.frame.GetCurrentFrameSize() + s.frame.SetPosition(x, s.originY+h) - s.frame.Render(target) + + if err := s.frame.Render(target); err != nil { + return err + } + x = s.originX // Right - s.frame.SetCurrentFrame(2) + if err := s.frame.SetCurrentFrame(2); err != nil { + return err + } + w, h = s.frame.GetCurrentFrameSize() s.frame.SetPosition(x, y+h) - s.frame.Render(target) + + if err := s.frame.Render(target); err != nil { + return err + } + y += h // Bottom left - s.frame.SetCurrentFrame(3) + if err := s.frame.SetCurrentFrame(3); err != nil { + return err + } + w, h = s.frame.GetCurrentFrameSize() + s.frame.SetPosition(x, y+h) - s.frame.Render(target) + + if err := s.frame.Render(target); err != nil { + return err + } + x += w // Bottom right - s.frame.SetCurrentFrame(4) + if err := s.frame.SetCurrentFrame(4); err != nil { + return err + } + w, h = s.frame.GetCurrentFrameSize() + s.frame.SetPosition(x, y+h) - s.frame.Render(target) + + if err := s.frame.Render(target); err != nil { + return err + } x, y = s.originX, s.originY y += 64 @@ -188,42 +218,106 @@ func (s *HeroStatsPanel) renderStaticMenu(target d2interface.Surface) { // Panel // Top left - s.panel.SetCurrentFrame(0) + if err := s.panel.SetCurrentFrame(0); err != nil { + return err + } + w, h = s.panel.GetCurrentFrameSize() + s.panel.SetPosition(x, y+h) - s.panel.Render(target) + + if err := s.panel.Render(target); err != nil { + return err + } + x += w // Top right - s.panel.SetCurrentFrame(1) + if err := s.panel.SetCurrentFrame(1); err != nil { + return err + } + w, h = s.panel.GetCurrentFrameSize() + s.panel.SetPosition(x, y+h) - s.panel.Render(target) + + if err := s.panel.Render(target); err != nil { + return err + } + y += h // Bottom right - s.panel.SetCurrentFrame(3) + if err := s.panel.SetCurrentFrame(3); err != nil { + return err + } + w, h = s.panel.GetCurrentFrameSize() + s.panel.SetPosition(x, y+h) - s.panel.Render(target) + + if err := s.panel.Render(target); err != nil { + return err + } // Bottom left - s.panel.SetCurrentFrame(2) + if err := s.panel.SetCurrentFrame(2); err != nil { + return err + } + w, h = s.panel.GetCurrentFrameSize() + s.panel.SetPosition(x-w, y+h) - s.panel.Render(target) + + if err := s.panel.Render(target); err != nil { + return err + } var label d2ui.Label + // all static labels are not stored since we use them only once to generate the image cache - for _, textElement := range StaticTextLabels { + + //nolint:gomnd + var staticTextLabels = []PanelText{ + {X: 110, Y: 100, Text: "Level", Font: d2resource.Font6, AlignCenter: true}, + {X: 200, Y: 100, Text: "Experience", Font: d2resource.Font6, AlignCenter: true}, + {X: 330, Y: 100, Text: "Next Level", Font: d2resource.Font6, AlignCenter: true}, + {X: 100, Y: 150, Text: "Strength", Font: d2resource.Font6}, + {X: 100, Y: 213, Text: "Dexterity", Font: d2resource.Font6}, + {X: 100, Y: 300, Text: "Vitality", Font: d2resource.Font6}, + {X: 100, Y: 360, Text: "Energy", Font: d2resource.Font6}, + {X: 280, Y: 260, Text: "Defense", Font: d2resource.Font6}, + {X: 280, Y: 300, Text: "Stamina", Font: d2resource.Font6, AlignCenter: true}, + {X: 280, Y: 322, Text: "Life", Font: d2resource.Font6, AlignCenter: true}, + {X: 280, Y: 360, Text: "Mana", Font: d2resource.Font6, AlignCenter: true}, + + // can't use "Fire\nResistance" because line spacing is too big and breaks the layout + {X: 310, Y: 395, Text: "Fire", Font: d2resource.Font6, AlignCenter: true}, + {X: 310, Y: 402, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true}, + + {X: 310, Y: 420, Text: "Cold", Font: d2resource.Font6, AlignCenter: true}, + {X: 310, Y: 427, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true}, + + {X: 310, Y: 445, Text: "Lightning", Font: d2resource.Font6, AlignCenter: true}, + {X: 310, Y: 452, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true}, + + {X: 310, Y: 468, Text: "Poison", Font: d2resource.Font6, AlignCenter: true}, + {X: 310, Y: 477, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true}, + } + for _, textElement := range staticTextLabels { label = s.createTextLabel(textElement) label.Render(target) } // hero name and class are part of the static image cache since they don't change after we enter the world label = s.createTextLabel(PanelText{X: 165, Y: 72, Text: s.heroName, Font: d2resource.Font16, AlignCenter: true}) + label.Render(target) + label = s.createTextLabel(PanelText{X: 330, Y: 72, Text: s.heroClass.String(), Font: d2resource.Font16, AlignCenter: true}) + label.Render(target) + + return nil } func (s *HeroStatsPanel) initStatValueLabels() { diff --git a/d2game/d2player/inventory.go b/d2game/d2player/inventory.go index c1819720..4ccd2c6c 100644 --- a/d2game/d2player/inventory.go +++ b/d2game/d2player/inventory.go @@ -72,78 +72,148 @@ func (g *Inventory) Load() { g.grid.Add(items...) } -func (g *Inventory) Render(target d2interface.Surface) { +func (g *Inventory) Render(target d2interface.Surface) error { if !g.isOpen { - return + return nil } x, y := g.originX, g.originY // Frame // Top left - g.frame.SetCurrentFrame(5) + if err := g.frame.SetCurrentFrame(5); err != nil { + return err + } + w, h := g.frame.GetCurrentFrameSize() + g.frame.SetPosition(x, y+h) - g.frame.Render(target) + + if err := g.frame.Render(target); err != nil { + return err + } + x += w // Top right - g.frame.SetCurrentFrame(6) + if err := g.frame.SetCurrentFrame(6); err != nil { + return err + } + w, h = g.frame.GetCurrentFrameSize() + g.frame.SetPosition(x, y+h) - g.frame.Render(target) + + if err := g.frame.Render(target); err != nil { + return err + } + x += w y += h // Right - g.frame.SetCurrentFrame(7) + if err := g.frame.SetCurrentFrame(7); err != nil { + return err + } + w, h = g.frame.GetCurrentFrameSize() + g.frame.SetPosition(x-w, y+h) - g.frame.Render(target) + + if err := g.frame.Render(target); err != nil { + return err + } + y += h // Bottom right - g.frame.SetCurrentFrame(8) + if err := g.frame.SetCurrentFrame(8); err != nil { + return err + } + w, h = g.frame.GetCurrentFrameSize() + g.frame.SetPosition(x-w, y+h) - g.frame.Render(target) + + if err := g.frame.Render(target); err != nil { + return err + } + x -= w // Bottom left - g.frame.SetCurrentFrame(9) + if err := g.frame.SetCurrentFrame(9); err != nil { + return err + } + w, h = g.frame.GetCurrentFrameSize() + g.frame.SetPosition(x-w, y+h) - g.frame.Render(target) + + if err := g.frame.Render(target); err != nil { + return err + } x, y = g.originX, g.originY y += 64 // Panel // Top left - g.panel.SetCurrentFrame(4) + if err := g.panel.SetCurrentFrame(4); err != nil { + return err + } + w, h = g.panel.GetCurrentFrameSize() + g.panel.SetPosition(x, y+h) - g.panel.Render(target) + + if err := g.panel.Render(target); err != nil { + return err + } + x += w // Top right - g.panel.SetCurrentFrame(5) + if err := g.panel.SetCurrentFrame(5); err != nil { + return err + } + w, h = g.panel.GetCurrentFrameSize() + g.panel.SetPosition(x, y+h) - g.panel.Render(target) + + if err := g.panel.Render(target); err != nil { + return err + } + y += h // Bottom right - g.panel.SetCurrentFrame(7) + if err := g.panel.SetCurrentFrame(7); err != nil { + return err + } + w, h = g.panel.GetCurrentFrameSize() g.panel.SetPosition(x, y+h) - g.panel.Render(target) + + if err := g.panel.Render(target); err != nil { + return err + } // Bottom left - g.panel.SetCurrentFrame(6) + if err := g.panel.SetCurrentFrame(6); err != nil { + return err + } + w, h = g.panel.GetCurrentFrameSize() + g.panel.SetPosition(x-w, y+h) - g.panel.Render(target) + + if err := g.panel.Render(target); err != nil { + return err + } g.grid.Render(target) + + return nil }