From 1a02c6ed43bd641d8988ad4a214052ffa5807926 Mon Sep 17 00:00:00 2001 From: Eugene Blikh Date: Tue, 28 Apr 2026 03:41:43 +0300 Subject: [PATCH] systems-power: drawHud, room dimming, right-click input systems-power --- main.go | 32 +++++++++++++++++-- render.go | 85 +++++++++++++++++++++++++++++++++++++++++++++----- web/index.html | 2 +- 3 files changed, 108 insertions(+), 11 deletions(-) diff --git a/main.go b/main.go index 45d0c18732f76f042fac01f7c32469f3757cb614..d281e5ec1d842b47e94eaa47a426f6f7f68ca214 100644 --- a/main.go +++ b/main.go @@ -15,6 +15,7 @@ type Game struct { crew []Crew selectedCrew int lastTime time.Time + systems []System } // Update advances simulation by dt seconds and handles input. @@ -35,6 +36,15 @@ func (g *Game) Update() error { if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonLeft) { cx, cy := ebiten.CursorPosition() + if cy >= HudY { + // HUD strip: route to power management. + if role, ok := hudHitTest(cx, cy); ok { + addPower(g.systems, role, ReactorCap) + } + // HUD-band miss (gutter / out-of-columns) is a no-op; do not fall through. + return nil + } + // Below HudY: crew-select / move logic. tx, ty := cx/TilePx, cy/TilePx for i := range g.crew { @@ -51,16 +61,27 @@ func (g *Game) Update() error { } } + if inpututil.IsMouseButtonJustPressed(ebiten.MouseButtonRight) { + cx, cy := ebiten.CursorPosition() + if cy >= HudY { + if role, ok := hudHitTest(cx, cy); ok { + removePower(g.systems, role) + } + // HUD-band miss is a no-op. + } + } + return nil } // Draw renders the whole frame. func (g *Game) Draw(screen *ebiten.Image) { screen.Fill(color.RGBA{10, 15, 25, 255}) - drawShip(screen, g.ship) + drawShip(screen, g.ship, g.systems) for i, c := range g.crew { drawCrew(screen, c, i == g.selectedCrew) } + drawHud(screen, g.systems) } // Layout fixes the virtual resolution; the window scales to fit. @@ -72,9 +93,16 @@ func main() { ship := NewPlayerShip() walk := walkableTiles(ship) crew := NewStartingCrew() + systems := NewStartingSystems() ebiten.SetWindowSize(1280, 720) ebiten.SetWindowTitle("ftl-shape") - if err := ebiten.RunGame(&Game{ship: ship, walk: walk, crew: crew, selectedCrew: -1}); err != nil { + if err := ebiten.RunGame(&Game{ + ship: ship, + walk: walk, + crew: crew, + selectedCrew: -1, + systems: systems, + }); err != nil { panic(err) } } diff --git a/render.go b/render.go index 878c778e6f2382ec4484a1807d63310503f3cb4b..9bfa2d65398a94599213b77bd9bd95cf227b63b4 100644 --- a/render.go +++ b/render.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "image/color" "github.com/hajimehoshi/ebiten/v2" @@ -35,23 +36,41 @@ func roleColor(role RoomRole) color.RGBA { } } -// drawShip draws every room of the ship. Rendering delegates to -// drawRoom so per-room logic lives in one place. -func drawShip(screen *ebiten.Image, s Ship) { +// dimRoleColor returns ~60% brightness of the powered tint for a role. +// Used by drawRoom when the system is unpowered. +func dimRoleColor(role RoomRole) color.RGBA { + c := roleColor(role) + return color.RGBA{ + R: uint8(uint16(c.R) * 6 / 10), + G: uint8(uint16(c.G) * 6 / 10), + B: uint8(uint16(c.B) * 6 / 10), + A: 255, + } +} + +// drawShip draws every room of the ship, dimming rooms whose system has no +// power. Rendering delegates to drawRoom so per-room logic lives in one place. +func drawShip(screen *ebiten.Image, s Ship, sys []System) { for _, r := range s.Rooms { - drawRoom(screen, r) + powered := sys[int(r.Role)].PowerLevel > 0 + drawRoom(screen, r, powered) } } -// drawRoom draws a single room: a role-tinted fill, a 1-px border, -// and the role name printed inside. -func drawRoom(screen *ebiten.Image, r Room) { +// drawRoom draws a single room: a role-tinted fill (dimmed when unpowered), +// a 1-px border, and the role name printed inside. +func drawRoom(screen *ebiten.Image, r Room, powered bool) { x := float32(r.GridX * TilePx) y := float32(r.GridY * TilePx) w := float32(r.GridW * TilePx) h := float32(r.GridH * TilePx) - fill := roleColor(r.Role) + var fill color.RGBA + if powered { + fill = roleColor(r.Role) + } else { + fill = dimRoleColor(r.Role) + } vector.DrawFilledRect(screen, x, y, w, h, fill, false) border := color.RGBA{230, 230, 230, 255} @@ -65,6 +84,56 @@ func drawRoom(screen *ebiten.Image, r Room) { ebitenutil.DebugPrintAt(screen, r.Name, int(x)+3, int(y)+2) } +// drawHud renders the power management strip at the bottom of the screen. +// It shows a reactor readout on the left and five per-system power columns +// on the right. Cells are stacked bottom-up; filled cells use the system's +// full roleColor, empty cells use dimRoleColor, each with a 1-px white outline. +func drawHud(screen *ebiten.Image, sys []System) { + // Reactor readout. + ebitenutil.DebugPrintAt(screen, + fmt.Sprintf("Reactor: %d/%d", reactorUsed(sys), ReactorCap), + 8, HudY+8) + + const cellW = 12 + const cellH = 10 + const cellGap = 2 + outline := color.RGBA{255, 255, 255, 255} + + // Role labels in display order (index == int(RoomRole)). + roleLabels := [HudColCount]string{"Pilot", "Weap", "Shld", "Med", "Eng"} + + for i := 0; i < HudColCount; i++ { + role := RoomRole(i) + s := sys[i] + colLeft := HudX + i*HudColW + + // Role label at the top of the column. + ebitenutil.DebugPrintAt(screen, roleLabels[i], colLeft+4, HudY+4) + + // Cells stacked bottom-up. Row 0 is at the bottom of the HUD strip. + for j := 0; j < s.MaxLevel; j++ { + // Compute y so that row 0 is at the bottom. We allocate space + // starting below the label (label height ~12px). + cellBottom := HudY + (VirtualH - HudY) - cellGap + cy := cellBottom - (j+1)*(cellH+cellGap) + cx := colLeft + 4 + + var fill color.RGBA + if j < s.PowerLevel { + fill = roleColor(role) + } else { + fill = dimRoleColor(role) + } + vector.DrawFilledRect(screen, float32(cx), float32(cy), float32(cellW), float32(cellH), fill, false) + // 1-px white outline on every cell. + vector.DrawFilledRect(screen, float32(cx), float32(cy), float32(cellW), 1, outline, false) + vector.DrawFilledRect(screen, float32(cx), float32(cy+cellH-1), float32(cellW), 1, outline, false) + vector.DrawFilledRect(screen, float32(cx), float32(cy), 1, float32(cellH), outline, false) + vector.DrawFilledRect(screen, float32(cx+cellW-1), float32(cy), 1, float32(cellH), outline, false) + } + } +} + const CrewRadius = 6 var white = color.RGBA{255, 255, 255, 255} diff --git a/web/index.html b/web/index.html index 34515d2cad87f52d871f6ffc34fad4929b4efbf4..13e5b0e17d1363a77890aca84e1b55e833dd4dcb 100644 --- a/web/index.html +++ b/web/index.html @@ -5,7 +5,7 @@ ftl-shape - +