Lint cleanup (#628)

This commit is contained in:
Tim Sarbin 2020-07-26 14:52:54 -04:00 committed by GitHub
parent 53599928f7
commit 7da1843f49
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 776 additions and 307 deletions

View File

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

View File

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

View File

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

View File

@ -28,9 +28,6 @@ import (
)
const (
imgWidth = 256
imgHeight = 128
CharWidth = 8
CharHeight = 16
)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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