From 6a8b9aada15dcade44c814b6f8f5afc2ab83269a Mon Sep 17 00:00:00 2001 From: lord Date: Sat, 1 Aug 2020 16:03:09 -0700 Subject: [PATCH] Minor edits: debug entity frame bounds, debug `spawnmon` command (#666) * adding debug printing boxes for entity bounds * minor edits - adding `spawnmon` command. currently does not have a netpacket, just for debug - adding `GetSize` method to map entities for getting frame bounds (for debug printing) * bug fix --- d2common/d2interface/map_entity.go | 1 + d2core/d2asset/composite.go | 43 ++++++++++++++++ d2core/d2map/d2mapentity/animated_entity.go | 9 ++-- d2core/d2map/d2mapentity/item.go | 15 ++++++ d2core/d2map/d2mapentity/map_entity.go | 9 ++++ d2core/d2map/d2mapentity/npc.go | 5 ++ d2core/d2map/d2mapentity/player.go | 9 ++++ d2core/d2map/d2maprenderer/renderer.go | 54 +++++++++++++++++---- d2core/d2object/object.go | 5 ++ d2game/d2gamescreen/game.go | 20 ++++++++ d2game/d2player/game_controls.go | 28 ++++++++--- 11 files changed, 177 insertions(+), 21 deletions(-) diff --git a/d2common/d2interface/map_entity.go b/d2common/d2interface/map_entity.go index 6d852db4..b5a09f7c 100644 --- a/d2common/d2interface/map_entity.go +++ b/d2common/d2interface/map_entity.go @@ -8,6 +8,7 @@ type MapEntity interface { Advance(tickTime float64) GetPosition() d2vector.Position GetVelocity() d2vector.Vector + GetSize() (width, height int) GetLayer() int GetPositionF() (float64, float64) Name() string diff --git a/d2core/d2asset/composite.go b/d2core/d2asset/composite.go index d1b972f5..fc952336 100644 --- a/d2core/d2asset/composite.go +++ b/d2core/d2asset/composite.go @@ -20,6 +20,12 @@ type Composite struct { direction int equipment [d2enum.CompositeTypeMax]string mode *compositeMode + size *size +} + +type size struct { + Width int + Height int } // CreateComposite creates a Composite from a given ObjectLookupRecord and palettePath. @@ -300,6 +306,43 @@ func (c *Composite) loadCompositeLayer(layerKey, layerValue, animationMode, weap return nil, errors.New("animation not found") } +func (c *Composite) GetSize() (w, h int) { + c.updateSize() + return c.size.Width, c.size.Height +} + +func (c *Composite) updateSize() { + if c.mode == nil { + return + } + + direction := d2cof.Dir64ToCof(c.direction, c.mode.cof.NumberOfDirections) + + biggestW, biggestH := 0, 0 + + for _, layerIndex := range c.mode.cof.Priority[direction][c.mode.frameIndex] { + layer := c.mode.layers[layerIndex] + if layer != nil { + w, h := layer.GetCurrentFrameSize() + + if biggestW < w { + biggestW = w + } + + if biggestH < h { + biggestH = h + } + } + } + + if c.size == nil { + c.size = &size{} + } + + c.size.Width = biggestW + c.size.Height = biggestH +} + func baseString(baseType d2enum.ObjectType) string { switch baseType { case d2enum.ObjectTypePlayer: diff --git a/d2core/d2map/d2mapentity/animated_entity.go b/d2core/d2map/d2mapentity/animated_entity.go index 703471c7..37639fa5 100644 --- a/d2core/d2map/d2mapentity/animated_entity.go +++ b/d2core/d2map/d2mapentity/animated_entity.go @@ -21,16 +21,17 @@ type AnimatedEntity struct { // Render draws this animated entity onto the target func (ae *AnimatedEntity) Render(target d2interface.Surface) { renderOffset := ae.Position.RenderOffset() - target.PushTranslation( - int((renderOffset.X()-renderOffset.Y())*16), - int(((renderOffset.X()+renderOffset.Y())*8)-5), - ) + ox, oy := renderOffset.X(), renderOffset.Y() + tx, ty := int((ox-oy)*16), int((ox+oy)*8)-5 + + target.PushTranslation(tx, ty) defer target.Pop() if ae.highlight { target.PushBrightness(2) defer target.Pop() + ae.highlight = false } diff --git a/d2core/d2map/d2mapentity/item.go b/d2core/d2map/d2mapentity/item.go index 2ac8e6a2..65de7bd6 100644 --- a/d2core/d2map/d2mapentity/item.go +++ b/d2core/d2map/d2mapentity/item.go @@ -39,3 +39,18 @@ func (i *Item) Highlight() { func (i *Item) Name() string { return i.Item.Name() } + +// GetSize returns the current frame size +func (i *Item) GetSize() (width, height int) { + w, h := i.animation.GetCurrentFrameSize() + + if w < minHitboxSize { + w = minHitboxSize + } + + if h < minHitboxSize { + h = minHitboxSize + } + + return w, h +} diff --git a/d2core/d2map/d2mapentity/map_entity.go b/d2core/d2map/d2mapentity/map_entity.go index 775410b7..149f1dd0 100644 --- a/d2core/d2map/d2mapentity/map_entity.go +++ b/d2core/d2map/d2mapentity/map_entity.go @@ -4,6 +4,10 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" ) +const ( + minHitboxSize = 30 +) + // mapEntity represents an entity on the map that can be animated type mapEntity struct { Position d2vector.Position @@ -206,3 +210,8 @@ func (m *mapEntity) Highlight() { func (m *mapEntity) Selectable() bool { return false } + +// GetSize returns the current frame size +func (m *mapEntity) GetSize() (width, height int) { + return minHitboxSize, minHitboxSize +} diff --git a/d2core/d2map/d2mapentity/npc.go b/d2core/d2map/d2mapentity/npc.go index 0a9a8cb2..fb96a3c9 100644 --- a/d2core/d2map/d2mapentity/npc.go +++ b/d2core/d2map/d2mapentity/npc.go @@ -175,3 +175,8 @@ func (v *NPC) GetPosition() d2vector.Position { func (v *NPC) GetVelocity() d2vector.Vector { return v.mapEntity.velocity } + +// GetSize returns the current frame size +func (v* NPC) GetSize() (width, height int) { + return v.composite.GetSize() +} diff --git a/d2core/d2map/d2mapentity/player.go b/d2core/d2map/d2mapentity/player.go index e953b412..29aa0757 100644 --- a/d2core/d2map/d2mapentity/player.go +++ b/d2core/d2map/d2mapentity/player.go @@ -192,3 +192,12 @@ func (p *Player) GetPosition() d2vector.Position { func (p *Player) GetVelocity() d2vector.Vector { return p.mapEntity.velocity } + +// GetSize returns the current frame size +func (p *Player) GetSize() (width, height int) { + width, height = p.composite.GetSize() + // hack: we need to get full size of composite animations, currently only gets legs + height = (height * 2) - (height / 2) + + return width, height +} diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index 44132a0a..55e6f9f4 100644 --- a/d2core/d2map/d2maprenderer/renderer.go +++ b/d2core/d2map/d2maprenderer/renderer.go @@ -361,18 +361,54 @@ func (mr *MapRenderer) renderEntityDebug(target d2interface.Surface) { offX, offY := 40, -40 + entScreenXf, entScreenYf := mr.WorldToScreenF(e.GetPositionF()) + entScreenX := int(math.Floor(entScreenXf)) + entScreenY := int(math.Floor(entScreenYf)) + entityWidth, entityHeight := e.GetSize() + halfWidth, halfHeight := entityWidth/2, entityHeight/2 + l, r := entScreenX-halfWidth, entScreenX+halfWidth + t, b := entScreenY-halfHeight, entScreenY+halfHeight + mx, my := mr.renderer.GetCursorPos() + xWithin := (l <= mx) && (r >= mx) + yWithin := (t <= my) && (b >= my) + within := xWithin && yWithin + + boxLineColor := color.RGBA{255, 0, 255, 128} + boxHoverColor := color.RGBA{255, 255, 0, 128} + + boxColor := boxLineColor + + if within { + boxColor = boxHoverColor + } + + // box mr.viewport.PushTranslationWorld(x, y) target.PushTranslation(screenX, screenY) - target.DrawLine(offX, offY, color.RGBA{255, 255, 255, 128}) - target.PushTranslation(offX+10, offY-20) - target.PushTranslation(-10, -10) - target.DrawRect(200, 50, color.RGBA{0, 0, 0, 64}) - target.Pop() - target.DrawTextf("World (%.2f, %.2f)\nVelocity (%.2f, %.2f)", x, y, vx, vy) - target.Pop() - target.DrawLine(int(vx), int(vy), color.RGBA{64, 255, 0, 255}) - target.Pop() + target.PushTranslation(-halfWidth, -halfHeight) + target.DrawLine(0, entityHeight, boxColor) + target.DrawLine(entityWidth, 0, boxColor) + target.PushTranslation(entityWidth, entityHeight) + target.DrawLine(-entityWidth, 0, boxColor) + target.DrawLine(0, -entityHeight, boxColor) + target.PopN(3) mr.viewport.PopTranslation() + + // hover + if within { + mr.viewport.PushTranslationWorld(x, y) + target.PushTranslation(screenX, screenY) + target.DrawLine(offX, offY, color.RGBA{255, 255, 255, 128}) + target.PushTranslation(offX+10, offY-20) + target.PushTranslation(-10, -10) + target.DrawRect(200, 50, color.RGBA{0, 0, 0, 64}) + target.Pop() + target.DrawTextf("World (%.2f, %.2f)\nVelocity (%.2f, %.2f)", x, y, vx, vy) + target.Pop() + target.DrawLine(int(vx), int(vy), color.RGBA{64, 255, 0, 255}) + target.Pop() + mr.viewport.PopTranslation() + } } } diff --git a/d2core/d2object/object.go b/d2core/d2object/object.go index 5b3f994f..353e8515 100644 --- a/d2core/d2object/object.go +++ b/d2core/d2object/object.go @@ -142,3 +142,8 @@ func (ob *Object) GetPosition() d2vector.Position { func (ob *Object) GetVelocity() d2vector.Vector { return *d2vector.VectorZero() } + +// GetSize returns the current frame size +func (ob *Object) GetSize() (width, height int) { + return ob.composite.GetSize() +} diff --git a/d2game/d2gamescreen/game.go b/d2game/d2gamescreen/game.go index a95a0ec4..0cc83367 100644 --- a/d2game/d2gamescreen/game.go +++ b/d2game/d2gamescreen/game.go @@ -110,6 +110,26 @@ func (v *Game) OnLoad(_ d2screen.LoadingState) { v.debugSpawnItemAtLocation(x, y, codes...) }, ) + + v.terminal.BindAction( + "spawnmon", + "spawn monster at the local player position", + func(name string) { + x := int(v.localPlayer.Position.X()) + y := int(v.localPlayer.Position.Y()) + monstat := d2datadict.MonStats[name] + if monstat == nil { + v.terminal.OutputErrorf("no monstat entry for \"%s\"", name) + return + } + monster, err := d2mapentity.NewNPC(x, y, monstat, 0) + if err != nil { + v.terminal.OutputErrorf("error generating monster \"%s\": %v", name, err) + return + } + v.gameClient.MapEngine.AddEntity(monster) + }, + ) } // OnUnload releases the resources of Gameplay screen diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index 7cd374fb..ee19de34 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -32,11 +32,12 @@ type Panel interface { } const ( - initialMissileID = 59 - expBarWidth = 120.0 - staminaBarWidth = 102.0 - globeHeight = 80 - globeWidth = 80 + initialMissileID = 59 + expBarWidth = 120.0 + staminaBarWidth = 102.0 + globeHeight = 80 + globeWidth = 80 + hoverLabelOuterPad = 5 ) // GameControls represents the game's controls on the screen @@ -423,14 +424,25 @@ func (g *GameControls) Render(target d2interface.Surface) error { continue } + entPos := entity.GetPosition() + entOffset := entPos.RenderOffset() entScreenXf, entScreenYf := g.mapRenderer.WorldToScreenF(entity.GetPositionF()) entScreenX := int(math.Floor(entScreenXf)) entScreenY := int(math.Floor(entScreenYf)) + entityWidth, entityHeight := entity.GetSize() + halfWidth, halfHeight := entityWidth/2, entityHeight/2 + l, r := entScreenX-halfWidth-hoverLabelOuterPad, entScreenX+halfWidth+hoverLabelOuterPad + t, b := entScreenY-halfHeight-hoverLabelOuterPad, entScreenY+halfHeight-hoverLabelOuterPad + mx, my := g.lastMouseX, g.lastMouseY + xWithin := (l <= mx) && (r >= mx) + yWithin := (t <= my) && (b >= my) + within := xWithin && yWithin - if ((entScreenX - 20) <= g.lastMouseX) && ((entScreenX + 20) >= g.lastMouseX) && - ((entScreenY - 80) <= g.lastMouseY) && (entScreenY >= g.lastMouseY) { + if within { + xOff, yOff := int(entOffset.X()), int(entOffset.Y()) g.nameLabel.SetText(entity.Name()) - g.nameLabel.SetPosition(entScreenX, entScreenY-100) + xLabel, yLabel := entScreenX-xOff, entScreenY-yOff-entityHeight-hoverLabelOuterPad + g.nameLabel.SetPosition(xLabel, yLabel) g.nameLabel.Render(target) entity.Highlight()