diff options
| author | Florian Fischer <florian.fischer@muhq.space> | 2025-09-08 22:32:22 +0200 |
|---|---|---|
| committer | Florian Fischer <florian.fischer@muhq.space> | 2025-09-08 22:32:22 +0200 |
| commit | ddd9cc924977201f7d2bc162c0d5fe4f86841ef1 (patch) | |
| tree | 67ae8a7f34cd1e119b09e0c1925dca52855b70c4 | |
| parent | 6438cef725e6310c49b5f4ef615b2ee82261b11b (diff) | |
| download | muhqs-game-ddd9cc924977201f7d2bc162c0d5fe4f86841ef1.tar.gz muhqs-game-ddd9cc924977201f7d2bc162c0d5fe4f86841ef1.zip | |
support action targeting from the stack buffer
| -rw-r--r-- | README.md | 3 | ||||
| -rw-r--r-- | go/client/game.go | 37 | ||||
| -rw-r--r-- | go/ui/buffer.go | 75 |
3 files changed, 97 insertions, 18 deletions
@@ -120,8 +120,7 @@ Things that should be done eventually: - [ ] implement game log - [ ] implement log with hoverable components - [ ] support big card grids (for sealed) - - [ ] implement declared action targeting - - [ ] implement action targeting + - [X] implement declared action targeting - [ ] Game Logic - [ ] implement constrained X-Cost - [ ] implement HandCardSelection with condition diff --git a/go/client/game.go b/go/client/game.go index 5ab70df2..8a3bcae3 100644 --- a/go/client/game.go +++ b/go/client/game.go @@ -62,7 +62,7 @@ type Game struct { storeButton *ui.SimpleButton cardsInStore int - stackBuffer *ui.Buffer + stackBuffer *ui.StackBuffer prompt *ui.Prompt messageBuffer *ui.Buffer triggers []*game.TriggeredAction @@ -132,10 +132,6 @@ func newGame(app *app, gameState game.State) *Game { g.reset() }) - g.stackBuffer = ui.NewBuffer( - g.Width-STACK_BUFFER_WIDTH, g.passButton.Y-400, - STACK_BUFFER_WIDTH, STACK_BUFFER_HEIGHT) - g.messageBuffer = ui.NewBuffer( MESSAGE_BUFFER_MARGIN, g.Height-DEFAULT_BUTTON_HEIGHT-MESSAGE_BUFFER_HEIGHT-MESSAGE_BUFFER_MARGIN, @@ -272,6 +268,21 @@ func (g *Game) showWinners() { }) } +func (g *Game) updateStack() { + if g.visible(g.stackBuffer) { + g.hide(g.stackBuffer) + } + + if !g.gameState.Stack().IsEmpty() { + g.stackBuffer = ui.NewStackBuffer( + g.Width-STACK_BUFFER_WIDTH, g.passButton.Y-400, + STACK_BUFFER_WIDTH, STACK_BUFFER_HEIGHT, + g.gameState.Stack().Actions) + + g.show(g.stackBuffer) + } +} + func (g *Game) showStores() { if g.storesVisible { return @@ -468,6 +479,8 @@ func (g *Game) addHighlight(obj any, color color.Color) { g.mapView.AddHighlightTile(obj, color) case *game.Unit: g.mapView.AddHighlightPermanent(obj, color) + case game.Action: + g.stackBuffer.AddHighlight(obj, color) default: log.Panicf("Unhandled highlight of type %T", obj) } @@ -488,6 +501,9 @@ func (g *Game) clearMapHighlights() { func (g *Game) clearHighlights() { g.clearMapHighlights() g.handLayer.ClearHighlights() + if g.stackBuffer != nil { + g.stackBuffer.ClearHighlights() + } } func (g *Game) declareAction(a game.Action) { @@ -610,18 +626,11 @@ func (g *Game) handlePlayerNotifications() { if n.Error != nil { log.Fatal(n.Error) } - a := n.Context.(game.Action) - g.stackBuffer.AddLine(a.String()) - if !g.gameState.Stack().IsEmpty() && !g.visible(g.stackBuffer) { - g.AddWidget(g.stackBuffer) - } + g.updateStack() case game.ResolvedActionNotification: g.clearMapHighlights() - g.stackBuffer.RemoveLast() - if g.gameState.Stack().IsEmpty() { - g.RemoveWidget(g.stackBuffer) - } + g.updateStack() g.mapView.ForceRedraw() case game.TargetSelectionPrompt: diff --git a/go/ui/buffer.go b/go/ui/buffer.go index d2a4667e..c44bbdd0 100644 --- a/go/ui/buffer.go +++ b/go/ui/buffer.go @@ -8,6 +8,7 @@ import ( "github.com/hajimehoshi/ebiten/v2/text/v2" "muhq.space/muhqs-game/go/font" + "muhq.space/muhqs-game/go/game" ) var ( @@ -18,6 +19,7 @@ var ( type Buffer struct { WidgetBase lines []string + highlights map[int]color.Color pos int font *text.GoTextFace lineSpacing float64 @@ -30,6 +32,7 @@ func NewBuffer(x, y int, width, height int) *Buffer { b := &Buffer{ NewWidgetBase(x, y, width, height), []string{}, + make(map[int]color.Color), 0, font.Font18, 1.5, @@ -45,14 +48,18 @@ func (b *Buffer) render() *ebiten.Image { img := ebiten.NewImage(b.Width, b.Height) img.Fill(b.bg) var y float64 = -b.font.Size - for _, line := range b.lines[b.pos:] { + for i, line := range b.lines[b.pos:] { _, h := text.Measure(line, b.font, b.font.Size*b.lineSpacing) y += h if y > float64(b.Height) { break } op := &text.DrawOptions{} - op.ColorScale.ScaleWithColor(b.fg) + if clr, highlighted := b.highlights[i]; highlighted { + op.ColorScale.ScaleWithColor(clr) + } else { + op.ColorScale.ScaleWithColor(b.fg) + } op.GeoM.Translate(b.xMargin, float64(y)) op.LineSpacing = b.font.Size * b.lineSpacing text.Draw(img, line, b.font, op) @@ -129,3 +136,67 @@ func (b *Buffer) Fg(fg color.Color) *Buffer { b.fg = fg return b } + +type StackBuffer struct { + Buffer + actions []game.Action +} + +func NewStackBuffer(x, y int, width, height int, actions []game.Action) *StackBuffer { + sb := &StackBuffer{Buffer: *NewBuffer(x, y, width, height)} + // This is needed because we do not use the pointer created in NewBuffer + sb.renderImpl = func() *ebiten.Image { return sb.render() } + + for _, action := range actions { + sb.AddAction(action) + } + return sb +} + +func (b *StackBuffer) AddAction(a game.Action) { + b.AddLine(a.String()) + b.actions = append(b.actions, a) +} + +func (b *StackBuffer) AddHighlight(a game.Action, clr color.Color) { + for i, ba := range b.actions { + if a == ba { + b.highlights[i] = clr + } + } + b.ForceRedraw() +} + +func (b *StackBuffer) ClearHighlights() { + b.highlights = make(map[int]color.Color) + b.ForceRedraw() +} + +func (b *StackBuffer) RemoveLast() { + b.Buffer.RemoveLast() + + // Remove potential highlight + i := len(b.actions) - 1 + if _, found := b.highlights[i]; found { + delete(b.highlights, i) + } + + b.actions = b.actions[:i] +} + +func (b *StackBuffer) FindObjectAt(x, y int) any { + if !b.Contains(x, y) { + return nil + } + + var _y float64 = 0 + for i, line := range b.lines[b.pos:] { + _, h := text.Measure(line, b.font, b.font.Size*b.lineSpacing) + _y += h + + if int(_y)+b.Y >= y { + return b.actions[i] + } + } + return nil +} |
