diff --git a/d2common/d2enum/composite_mode.go b/d2common/d2enum/composite_mode.go deleted file mode 100644 index f36a2d4b..00000000 --- a/d2common/d2enum/composite_mode.go +++ /dev/null @@ -1,47 +0,0 @@ -package d2enum - -// CompositeMode defines the composite mode -type CompositeMode int - -const ( - // CompositeModeSourceOver applies a composite based on: - // c_out = c_src + c_dst × (1 - α_src) (Regular alpha blending) - CompositeModeSourceOver CompositeMode = iota + 1 - - // CompositeModeClear applies a composite based on: c_out = 0 - CompositeModeClear - - // CompositeModeCopy applies a composite based on: c_out = c_src - CompositeModeCopy - - // CompositeModeDestination applies a composite based on: c_out = c_dst - CompositeModeDestination - - // CompositeModeDestinationOver applies a composite based on: c_out = c_src × (1 - α_dst) + c_dst - CompositeModeDestinationOver - - // CompositeModeSourceIn applies a composite based on: c_out = c_src × α_dst - CompositeModeSourceIn - - // CompositeModeDestinationIn applies a composite based on: c_out = c_dst × α_src - CompositeModeDestinationIn - - // CompositeModeSourceOut applies a composite based on: c_out = c_src × (1 - α_dst) - CompositeModeSourceOut - - // CompositeModeDestinationOut applies a composite based on: c_out = c_dst × (1 - α_src) - CompositeModeDestinationOut - - // CompositeModeSourceAtop applies a composite based on: c_out = c_src × α_dst + c_dst × (1 - α_src) - CompositeModeSourceAtop - - // CompositeModeDestinationAtop applies a composite based on: c_out = c_src × (1 - α_dst) + c_dst × α_src - CompositeModeDestinationAtop - - // CompositeModeXor applies a composite based on: c_out = c_src × (1 - α_dst) + c_dst × (1 - α_src) - CompositeModeXor - - // CompositeModeLighter applies a composite based on: - // c_out = c_src + c_dst Sum of source and destination (a.k.a. 'plus' or 'additive') - CompositeModeLighter -) diff --git a/d2common/d2interface/animation.go b/d2common/d2interface/animation.go index b5b2cd6e..098ed610 100644 --- a/d2common/d2interface/animation.go +++ b/d2common/d2interface/animation.go @@ -3,6 +3,8 @@ package d2interface import ( "image" "image/color" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" ) // Animation is an animation @@ -35,5 +37,5 @@ type Animation interface { SetColorMod(colorMod color.Color) GetPlayedCount() int ResetPlayedCount() - SetBlend(blend bool) + SetEffect(effect d2enum.DrawEffect) } diff --git a/d2common/d2interface/surface.go b/d2common/d2interface/surface.go index 37e4ef62..422bcc65 100644 --- a/d2common/d2interface/surface.go +++ b/d2common/d2interface/surface.go @@ -18,7 +18,7 @@ type Surface interface { Pop() PopN(n int) PushColor(color color.Color) - PushCompositeMode(mode d2enum.CompositeMode) + PushEffect(effect d2enum.DrawEffect) PushFilter(filter d2enum.Filter) PushTranslation(x, y int) PushBrightness(brightness float64) diff --git a/d2core/d2asset/animation.go b/d2core/d2asset/animation.go index 4ebc047c..49737815 100644 --- a/d2core/d2asset/animation.go +++ b/d2core/d2asset/animation.go @@ -40,12 +40,12 @@ type animationDirection struct { // animation has directionality, play modes, and frame counting type animation struct { directions []animationDirection + effect d2enum.DrawEffect colorMod color.Color frameIndex int directionIndex int lastFrameTime float64 playedCount int - compositeMode d2enum.CompositeMode playMode playMode playLength float64 subStartingFrame int @@ -120,7 +120,7 @@ func (a *animation) Render(target d2iface.Surface) error { target.PushTranslation(frame.offsetX, frame.offsetY) defer target.Pop() - target.PushCompositeMode(a.compositeMode) + target.PushEffect(a.effect) defer target.Pop() target.PushColor(a.colorMod) @@ -148,7 +148,7 @@ func (a *animation) RenderSection(sfc d2iface.Surface, bound image.Rectangle) er frame := direction.frames[a.frameIndex] sfc.PushTranslation(frame.offsetX, frame.offsetY) - sfc.PushCompositeMode(a.compositeMode) + sfc.PushEffect(a.effect) sfc.PushColor(a.colorMod) defer sfc.PopN(3) @@ -305,11 +305,6 @@ func (a *animation) ResetPlayedCount() { a.playedCount = 0 } -// SetBlend sets the Animation alpha blending status -func (a *animation) SetBlend(blend bool) { - if blend { - a.compositeMode = d2enum.CompositeModeLighter - } else { - a.compositeMode = d2enum.CompositeModeSourceOver - } +func (a *animation) SetEffect(e d2enum.DrawEffect) { + a.effect = e } diff --git a/d2core/d2asset/animation_manager.go b/d2core/d2asset/animation_manager.go index 8b5cc3d6..15b51904 100644 --- a/d2core/d2asset/animation_manager.go +++ b/d2core/d2asset/animation_manager.go @@ -53,7 +53,7 @@ func (am *animationManager) LoadAnimation( return nil, err } - animation, err = CreateDC6Animation(am.renderer, animationPath, palette) + animation, err = CreateDC6Animation(am.renderer, animationPath, palette, d2enum.DrawEffectNone) if err != nil { return nil, err } diff --git a/d2core/d2asset/dc6_animation.go b/d2core/d2asset/dc6_animation.go index ba9411d1..76be487f 100644 --- a/d2core/d2asset/dc6_animation.go +++ b/d2core/d2asset/dc6_animation.go @@ -18,7 +18,7 @@ type DC6Animation struct { // CreateDC6Animation creates an Animation from d2dc6.DC6 and d2dat.DATPalette func CreateDC6Animation(renderer d2iface.Renderer, dc6Path string, - palette d2iface.Palette) (d2iface.Animation, error) { + palette d2iface.Palette, effect d2enum.DrawEffect) (d2iface.Animation, error) { dc6, err := loadDC6(dc6Path) if err != nil { return nil, err @@ -29,6 +29,7 @@ func CreateDC6Animation(renderer d2iface.Renderer, dc6Path string, playLength: defaultPlayLength, playLoop: true, originAtBottom: true, + effect: effect, } anim := DC6Animation{ diff --git a/d2core/d2asset/dcc_animation.go b/d2core/d2asset/dcc_animation.go index e6fbeb05..a910daa7 100644 --- a/d2core/d2asset/dcc_animation.go +++ b/d2core/d2asset/dcc_animation.go @@ -15,7 +15,6 @@ import ( type DCCAnimation struct { animation dccPath string - effect d2enum.DrawEffect palette d2iface.Palette renderer d2iface.Renderer } @@ -32,6 +31,7 @@ func CreateDCCAnimation(renderer d2iface.Renderer, dccPath string, palette d2ifa playLength: defaultPlayLength, playLoop: true, directions: make([]animationDirection, dcc.NumberOfDirections), + effect: effect, } DCC := DCCAnimation{ @@ -39,17 +39,8 @@ func CreateDCCAnimation(renderer d2iface.Renderer, dccPath string, palette d2ifa dccPath: dccPath, palette: palette, renderer: renderer, - effect: effect, } - // Really the renderer should take DrawEffects and do the right thing - if effect == d2enum.DrawEffectModulate { - DCC.SetBlend(true) - } - - // Transparency is now no longer handled, it should be done by using PL2 palette and - // picking the appropriate transform for the transparency level - err = DCC.SetDirection(0) if err != nil { return nil, err diff --git a/d2core/d2asset/font.go b/d2core/d2asset/font.go index 911748c5..cc0e5704 100644 --- a/d2core/d2asset/font.go +++ b/d2core/d2asset/font.go @@ -98,7 +98,6 @@ func (f *Font) GetTextMetrics(text string) (width, height int) { // RenderText prints a text using its configured style on a Surface (multi-lines are left-aligned, use label otherwise) func (f *Font) RenderText(text string, target d2interface.Surface) error { f.sheet.SetColorMod(f.color) - f.sheet.SetBlend(false) lines := strings.Split(text, "\n") diff --git a/d2core/d2gui/sprite.go b/d2core/d2gui/sprite.go index 89acc7d0..518d773b 100644 --- a/d2core/d2gui/sprite.go +++ b/d2core/d2gui/sprite.go @@ -1,6 +1,7 @@ package d2gui import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" ) @@ -53,7 +54,6 @@ func createAnimatedSprite(imagePath, palettePath string, direction AnimationDire } else { sprite.animation.PlayBackward() } - sprite.animation.SetBlend(false) sprite.SetVisible(true) return sprite, nil @@ -84,3 +84,7 @@ func (s *Sprite) advance(elapsed float64) error { func (s *Sprite) getSize() (int, int) { return s.animation.GetCurrentFrameSize() } + +func (s *Sprite) SetEffect(e d2enum.DrawEffect) { + s.animation.SetEffect(e) +} diff --git a/d2core/d2map/d2mapentity/missile.go b/d2core/d2map/d2mapentity/missile.go index f0e7b9ed..1e539675 100644 --- a/d2core/d2map/d2mapentity/missile.go +++ b/d2core/d2map/d2mapentity/missile.go @@ -5,6 +5,7 @@ import ( "math" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" ) @@ -27,7 +28,7 @@ func CreateMissile(x, y int, record *d2datadict.MissileRecord) (*Missile, error) animation.SetSubLoop(record.Animation.SubStartingFrame, record.Animation.SubEndingFrame) } - animation.SetBlend(true) + animation.SetEffect(d2enum.DrawEffectModulate) //animation.SetPlaySpeed(float64(record.Animation.AnimationSpeed)) animation.SetPlayLoop(record.Animation.LoopAnimation) animation.PlayForward() diff --git a/d2core/d2render/ebiten/composite_mode_helper.go b/d2core/d2render/ebiten/composite_mode_helper.go deleted file mode 100644 index a0bfa957..00000000 --- a/d2core/d2render/ebiten/composite_mode_helper.go +++ /dev/null @@ -1,72 +0,0 @@ -package ebiten - -import ( - "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" - "github.com/hajimehoshi/ebiten" -) - -func d2ToEbitenCompositeMode(comp d2enum.CompositeMode) ebiten.CompositeMode { - switch comp { - case d2enum.CompositeModeSourceOver: - return ebiten.CompositeModeSourceOver - case d2enum.CompositeModeClear: - return ebiten.CompositeModeClear - case d2enum.CompositeModeCopy: - return ebiten.CompositeModeCopy - case d2enum.CompositeModeDestination: - return ebiten.CompositeModeDestination - case d2enum.CompositeModeDestinationOver: - return ebiten.CompositeModeDestinationOver - case d2enum.CompositeModeSourceIn: - return ebiten.CompositeModeSourceIn - case d2enum.CompositeModeDestinationIn: - return ebiten.CompositeModeDestinationIn - case d2enum.CompositeModeSourceOut: - return ebiten.CompositeModeSourceOut - case d2enum.CompositeModeDestinationOut: - return ebiten.CompositeModeDestinationOut - case d2enum.CompositeModeSourceAtop: - return ebiten.CompositeModeSourceAtop - case d2enum.CompositeModeDestinationAtop: - return ebiten.CompositeModeDestinationAtop - case d2enum.CompositeModeXor: - return ebiten.CompositeModeXor - case d2enum.CompositeModeLighter: - return ebiten.CompositeModeLighter - } - - return ebiten.CompositeModeSourceOver -} - -func ebitenToD2CompositeMode(comp ebiten.CompositeMode) d2enum.CompositeMode { - switch comp { - case ebiten.CompositeModeSourceOver: - return d2enum.CompositeModeSourceOver - case ebiten.CompositeModeClear: - return d2enum.CompositeModeClear - case ebiten.CompositeModeCopy: - return d2enum.CompositeModeCopy - case ebiten.CompositeModeDestination: - return d2enum.CompositeModeDestination - case ebiten.CompositeModeDestinationOver: - return d2enum.CompositeModeDestinationOver - case ebiten.CompositeModeSourceIn: - return d2enum.CompositeModeSourceIn - case ebiten.CompositeModeDestinationIn: - return d2enum.CompositeModeDestinationIn - case ebiten.CompositeModeSourceOut: - return d2enum.CompositeModeSourceOut - case ebiten.CompositeModeDestinationOut: - return d2enum.CompositeModeDestinationOut - case ebiten.CompositeModeSourceAtop: - return d2enum.CompositeModeSourceAtop - case ebiten.CompositeModeDestinationAtop: - return d2enum.CompositeModeDestinationAtop - case ebiten.CompositeModeXor: - return d2enum.CompositeModeXor - case ebiten.CompositeModeLighter: - return d2enum.CompositeModeLighter - } - - return d2enum.CompositeModeSourceOver -} diff --git a/d2core/d2render/ebiten/ebiten_renderer.go b/d2core/d2render/ebiten/ebiten_renderer.go index 9e1cef46..7bbd2659 100644 --- a/d2core/d2render/ebiten/ebiten_renderer.go +++ b/d2core/d2render/ebiten/ebiten_renderer.go @@ -67,7 +67,7 @@ func (r *Renderer) CreateSurface(surface d2interface.Surface) (d2interface.Surfa surface.(*ebitenSurface).image, surfaceState{ filter: ebiten.FilterNearest, - mode: ebiten.CompositeModeSourceOver, + effect: d2enum.DrawEffectNone, }, ) diff --git a/d2core/d2render/ebiten/ebiten_surface.go b/d2core/d2render/ebiten/ebiten_surface.go index 8ca09c41..cfc29596 100644 --- a/d2core/d2render/ebiten/ebiten_surface.go +++ b/d2core/d2render/ebiten/ebiten_surface.go @@ -32,7 +32,7 @@ type ebitenSurface struct { } func createEbitenSurface(img *ebiten.Image, currentState ...surfaceState) *ebitenSurface { - state := surfaceState{} + state := surfaceState{effect: d2enum.DrawEffectNone} if len(currentState) > 0 { state = currentState[0] } @@ -50,9 +50,9 @@ func (s *ebitenSurface) PushTranslation(x, y int) { s.stateCurrent.y += y } -func (s *ebitenSurface) PushCompositeMode(mode d2enum.CompositeMode) { +func (s *ebitenSurface) PushEffect(effect d2enum.DrawEffect) { s.stateStack = append(s.stateStack, s.stateCurrent) - s.stateCurrent.mode = d2ToEbitenCompositeMode(mode) + s.stateCurrent.effect = effect } func (s *ebitenSurface) PushFilter(filter d2enum.Filter) { @@ -87,7 +87,7 @@ func (s *ebitenSurface) PopN(n int) { } func (s *ebitenSurface) Render(sfc d2interface.Surface) error { - opts := &ebiten.DrawImageOptions{CompositeMode: s.stateCurrent.mode} + opts := &ebiten.DrawImageOptions{} opts.GeoM.Translate(float64(s.stateCurrent.x), float64(s.stateCurrent.y)) opts.Filter = s.stateCurrent.filter @@ -99,6 +99,25 @@ func (s *ebitenSurface) Render(sfc d2interface.Surface) error { opts.ColorM.ChangeHSV(0, 1, s.stateCurrent.brightness) } + // Are these correct? who even knows + switch s.stateCurrent.effect { + case d2enum.DrawEffectPctTransparency25: + opts.ColorM.Translate(0, 0, 0, -0.25) + case d2enum.DrawEffectPctTransparency50: + opts.ColorM.Translate(0, 0, 0, -0.50) + case d2enum.DrawEffectPctTransparency75: + opts.ColorM.Translate(0, 0, 0, -0.75) + case d2enum.DrawEffectModulate: + opts.CompositeMode = ebiten.CompositeModeLighter + // TODO: idk what to do when ebiten doesn't exactly match, pick closest? + case d2enum.DrawEffectBurn: + case d2enum.DrawEffectNormal: + case d2enum.DrawEffectMod2XTrans: + case d2enum.DrawEffectMod2X: + case d2enum.DrawEffectNone: + opts.CompositeMode = ebiten.CompositeModeSourceOver + } + var img = sfc.(*ebitenSurface).image return s.image.DrawImage(img, opts) @@ -106,7 +125,7 @@ func (s *ebitenSurface) Render(sfc d2interface.Surface) error { // Renders the section of the animation frame enclosed by bounds func (s *ebitenSurface) RenderSection(sfc d2interface.Surface, bound image.Rectangle) error { - opts := &ebiten.DrawImageOptions{CompositeMode: s.stateCurrent.mode} + opts := &ebiten.DrawImageOptions{} opts.GeoM.Translate(float64(s.stateCurrent.x), float64(s.stateCurrent.y)) opts.Filter = s.stateCurrent.filter @@ -118,6 +137,25 @@ func (s *ebitenSurface) RenderSection(sfc d2interface.Surface, bound image.Recta opts.ColorM.ChangeHSV(0, 1, s.stateCurrent.brightness) } + // Are these correct? who even knows + switch s.stateCurrent.effect { + case d2enum.DrawEffectPctTransparency25: + opts.ColorM.Translate(0, 0, 0, -0.25) + case d2enum.DrawEffectPctTransparency50: + opts.ColorM.Translate(0, 0, 0, -0.50) + case d2enum.DrawEffectPctTransparency75: + opts.ColorM.Translate(0, 0, 0, -0.75) + case d2enum.DrawEffectModulate: + opts.CompositeMode = ebiten.CompositeModeLighter + // TODO: idk what to do when ebiten doesn't exactly match, pick closest? + case d2enum.DrawEffectBurn: + case d2enum.DrawEffectNormal: + case d2enum.DrawEffectMod2XTrans: + case d2enum.DrawEffectMod2X: + case d2enum.DrawEffectNone: + opts.CompositeMode = ebiten.CompositeModeSourceOver + } + var img = sfc.(*ebitenSurface).image return s.image.DrawImage(img.SubImage(bound).(*ebiten.Image), opts) diff --git a/d2core/d2render/ebiten/surface_state.go b/d2core/d2render/ebiten/surface_state.go index 02c0bb9c..985fa54c 100644 --- a/d2core/d2render/ebiten/surface_state.go +++ b/d2core/d2render/ebiten/surface_state.go @@ -3,14 +3,15 @@ package ebiten import ( "image/color" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/hajimehoshi/ebiten" ) type surfaceState struct { - x int - y int - mode ebiten.CompositeMode - filter ebiten.Filter - color color.Color + x int + y int + filter ebiten.Filter + color color.Color brightness float64 + effect d2enum.DrawEffect } diff --git a/d2core/d2ui/button.go b/d2core/d2ui/button.go index 92eec59b..5eb01f18 100644 --- a/d2core/d2ui/button.go +++ b/d2core/d2ui/button.go @@ -137,7 +137,7 @@ func CreateButton(renderer d2interface.Renderer, buttonType ButtonType, text str xOffset := result.width / 2 buttonSprite.SetPosition(0, 0) - buttonSprite.SetBlend(true) + buttonSprite.SetEffect(d2enum.DrawEffectModulate) buttonSprite.RenderSegmented(result.normalSurface, buttonLayout.XSegments, buttonLayout.YSegments, buttonLayout.BaseFrame) lbl.SetPosition(xOffset, textY) @@ -190,10 +190,9 @@ func (v *Button) Activate() { // Render renders the button func (v *Button) Render(target d2interface.Surface) { - target.PushCompositeMode(d2enum.CompositeModeSourceAtop) target.PushFilter(d2enum.FilterNearest) target.PushTranslation(v.x, v.y) - defer target.PopN(3) + defer target.PopN(2) if !v.enabled { target.PushColor(color.RGBA{R: 128, G: 128, B: 128, A: 195}) diff --git a/d2core/d2ui/checkbox.go b/d2core/d2ui/checkbox.go index bda00aab..792f051a 100644 --- a/d2core/d2ui/checkbox.go +++ b/d2core/d2ui/checkbox.go @@ -43,10 +43,9 @@ func CreateCheckbox(renderer d2interface.Renderer, checkState bool) Checkbox { } func (v *Checkbox) Render(target d2interface.Surface) { - target.PushCompositeMode(d2enum.CompositeModeSourceAtop) target.PushTranslation(v.x, v.y) target.PushFilter(d2enum.FilterNearest) - defer target.PopN(3) + defer target.PopN(2) if v.checkState { _ = target.Render(v.checkedImage) diff --git a/d2core/d2ui/sprite.go b/d2core/d2ui/sprite.go index 675147ed..2fad38f5 100644 --- a/d2core/d2ui/sprite.go +++ b/d2core/d2ui/sprite.go @@ -5,6 +5,7 @@ import ( "image" "image/color" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" "github.com/OpenDiablo2/OpenDiablo2/d2common" @@ -179,11 +180,10 @@ func (s *Sprite) SetColorMod(color color.Color) { s.animation.SetColorMod(color) } -// SetBlend sets the animation alpha blending status -func (s *Sprite) SetBlend(blend bool) { - s.animation.SetBlend(blend) -} - func (s *Sprite) Advance(elapsed float64) error { return s.animation.Advance(elapsed) } + +func (s *Sprite) SetEffect(e d2enum.DrawEffect) { + s.animation.SetEffect(e) +} diff --git a/d2game/d2gamescreen/main_menu.go b/d2game/d2gamescreen/main_menu.go index 54d32e3a..4f3db900 100644 --- a/d2game/d2gamescreen/main_menu.go +++ b/d2game/d2gamescreen/main_menu.go @@ -182,14 +182,14 @@ func (v *MainMenu) createLabels(loading d2screen.LoadingState) { func (v *MainMenu) createLogos(loading d2screen.LoadingState) { animation, _ := d2asset.LoadAnimation(d2resource.Diablo2LogoFireLeft, d2resource.PaletteUnits) v.diabloLogoLeft, _ = d2ui.LoadSprite(animation) - v.diabloLogoLeft.SetBlend(true) + v.diabloLogoLeft.SetEffect(d2enum.DrawEffectModulate) v.diabloLogoLeft.PlayForward() v.diabloLogoLeft.SetPosition(400, 120) loading.Progress(0.6) animation, _ = d2asset.LoadAnimation(d2resource.Diablo2LogoFireRight, d2resource.PaletteUnits) v.diabloLogoRight, _ = d2ui.LoadSprite(animation) - v.diabloLogoRight.SetBlend(true) + v.diabloLogoRight.SetEffect(d2enum.DrawEffectModulate) v.diabloLogoRight.PlayForward() v.diabloLogoRight.SetPosition(400, 120) diff --git a/d2game/d2gamescreen/select_hero_class.go b/d2game/d2gamescreen/select_hero_class.go index e11b59e7..f01fb911 100644 --- a/d2game/d2gamescreen/select_hero_class.go +++ b/d2game/d2gamescreen/select_hero_class.go @@ -602,7 +602,8 @@ func advanceSprite(sprite *d2ui.Sprite, elapsed float64) { } } -func loadSprite(animationPath string, position image.Point, playLength int, playLoop, blend bool) *d2ui.Sprite { +func loadSprite(animationPath string, position image.Point, playLength int, playLoop, + blend bool) *d2ui.Sprite { if animationPath == "" { return nil } @@ -615,7 +616,10 @@ func loadSprite(animationPath string, position image.Point, playLength int, play animation.PlayForward() animation.SetPlayLoop(playLoop) - animation.SetBlend(blend) + + if blend { + animation.SetEffect(d2enum.DrawEffectModulate) + } if playLength != 0 { animation.SetPlayLengthMs(playLength) diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index 87530246..a6fc5512 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -412,7 +412,7 @@ func (g *GameControls) Render(target d2interface.Surface) { // Stamina status bar target.PushTranslation(273, 572) - target.PushCompositeMode(d2enum.CompositeModeLighter) + 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)