All checks were successful
release / build-release (push) Successful in 1m33s
Folk på stationen (NPC:er som vandrar, kliver av tåg och går mot utgången, blockerar spelaren men inte varandra), vänster-/högerfiler som bromsar på fel sida, bredare gångar, reklamaffischer (clutter), japanskt sorl i loop (noise), vagnval med kvinnovagn (stort avdrag med man i sällskapet, tidsstyrd), kömarkeringar på perrongen, trängsel per vagn (bonus för tom vagn, svettig-debuff och svårare resa i full vagn, släpp förbi passagerare) och att folk kliver av innan man får gå på. Ny bana 005 Ueno. Skärmdumpsverktyget kan filtrera på bana/skript. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
294 lines
11 KiB
Go
294 lines
11 KiB
Go
package game
|
|
|
|
import (
|
|
"fmt"
|
|
"image"
|
|
"image/color"
|
|
"strings"
|
|
|
|
"github.com/hajimehoshi/ebiten/v2"
|
|
"github.com/hajimehoshi/ebiten/v2/vector"
|
|
|
|
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/lang"
|
|
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/minigame"
|
|
)
|
|
|
|
// Minispelen ritas som en panel över banan. Logiken bor i internal/minigame.
|
|
|
|
func (s *LevelScene) drawMinigame(g *Game, screen *ebiten.Image) {
|
|
a := g.Assets
|
|
r := s.run
|
|
panel := image.Rect(12, 28, ScreenW-12, ScreenH-10)
|
|
vector.DrawFilledRect(screen, float32(panel.Min.X), float32(panel.Min.Y), float32(panel.Dx()), float32(panel.Dy()), colPanel, false)
|
|
vector.DrawFilledRect(screen, float32(panel.Min.X), float32(panel.Min.Y), float32(panel.Dx()), 3, colKnown, false)
|
|
switch m := r.Mini.(type) {
|
|
case *minigame.TopUp:
|
|
s.drawTopUp(a, screen, m, panel)
|
|
case *minigame.Ticket:
|
|
s.drawTicket(a, screen, m, panel)
|
|
case *minigame.Crowd:
|
|
s.drawCrowd(a, screen, m, panel)
|
|
case *minigame.Ride:
|
|
s.drawRide(a, screen, m, panel)
|
|
case *minigame.CarPick:
|
|
s.drawCarPick(a, screen, m, panel)
|
|
}
|
|
}
|
|
|
|
// drawCarPick: vagnarna i rad, rosa för kvinnovagn, figurer för trängsel,
|
|
// kömarkeringar under, och en fas där folk kliver av först.
|
|
func (s *LevelScene) drawCarPick(a *Assets, screen *ebiten.Image, m *minigame.CarPick, p image.Rectangle) {
|
|
x, y := p.Min.X+8, p.Min.Y+8
|
|
a.DrawBig(screen, "乗車", x, y, colKnown)
|
|
a.Draw(screen, a.T("mini.car.title"), x+36, y+2, colText)
|
|
y += 24
|
|
if m.Alighting > 0 {
|
|
a.Draw(screen, a.T("mini.car.alighting"), x, y, colWarn)
|
|
w := float32(p.Dx() - 16)
|
|
vector.DrawFilledRect(screen, float32(x), float32(y+LineH+2), w, 4, colBar, false)
|
|
vector.DrawFilledRect(screen, float32(x), float32(y+LineH+2), w*float32(1-m.Alighting/minigame.AlightSec), 4, colKnown, false)
|
|
} else {
|
|
a.Draw(screen, a.T("mini.car.pick"), x, y, colDim)
|
|
}
|
|
y += LineH + 12
|
|
carW := min(64, (p.Dx()-16)/max(1, len(m.Cars))-4)
|
|
for i, c := range m.Cars {
|
|
cx := x + i*(carW+4)
|
|
body := color.RGBA{230, 234, 240, 255}
|
|
if c.WomenOnly {
|
|
body = color.RGBA{255, 160, 205, 255}
|
|
}
|
|
vector.DrawFilledRect(screen, float32(cx), float32(y), float32(carW), 30, body, false)
|
|
vector.DrawFilledRect(screen, float32(cx), float32(y+30), float32(carW), 3, colBad, false)
|
|
if i == m.Cursor && m.Alighting <= 0 {
|
|
vector.DrawFilledRect(screen, float32(cx), float32(y-4), float32(carW), 2, colKnown, false)
|
|
}
|
|
a.Draw(screen, fmt.Sprintf("%d%s", c.Index, a.T("hud.car_suffix_ja")), cx+3, y+2, colPanel)
|
|
// figurer = trängsel
|
|
for k := 0; k <= c.Crowd; k++ {
|
|
px := cx + 4 + k*7
|
|
vector.DrawFilledRect(screen, float32(px), float32(y+16), 4, 10, color.RGBA{60, 70, 100, 255}, false)
|
|
vector.DrawFilledRect(screen, float32(px), float32(y+13), 4, 3, color.RGBA{240, 200, 160, 255}, false)
|
|
}
|
|
if c.WomenOnly {
|
|
a.Draw(screen, "女性専用", cx+2, y+34, color.RGBA{255, 150, 200, 255})
|
|
} else {
|
|
a.Draw(screen, a.T("car.crowd."+minigame.CrowdNames[c.Crowd]), cx+2, y+34, colDim)
|
|
}
|
|
// kömarkering under vagnen
|
|
vector.DrawFilledRect(screen, float32(cx+carW/2-6), float32(y+48), 12, 2, colWarn, false)
|
|
vector.DrawFilledRect(screen, float32(cx+carW/2-1), float32(y+50), 2, 4, colWarn, false)
|
|
}
|
|
if s.run.Party.HasMen() {
|
|
a.Draw(screen, a.T("mini.car.men"), x, p.Max.Y-28, colWarn)
|
|
}
|
|
a.Draw(screen, a.T("mini.car.hint"), x, p.Max.Y-16, colDim)
|
|
}
|
|
|
|
// jp ritar japansk text sedd genom spelarens kunskap (kända ord i gult).
|
|
func (s *LevelScene) jp(a *Assets, screen *ebiten.Image, text string, x, y int) {
|
|
rd := lang.Render(a.Dict, s.run.Know, text, UILang)
|
|
a.drawLines(screen, []line{shownLine(rd.Original, colText, false)}, x, y)
|
|
}
|
|
|
|
func (s *LevelScene) drawTopUp(a *Assets, screen *ebiten.Image, m *minigame.TopUp, p image.Rectangle) {
|
|
x, y := p.Min.X+8, p.Min.Y+8
|
|
a.DrawBig(screen, "チャージ", x, y, colKnown)
|
|
a.Draw(screen, a.T("mini.topup.title"), x+60, y+2, colText)
|
|
y += 20
|
|
switch m.Stage {
|
|
case minigame.TopUpPickMember:
|
|
a.Draw(screen, a.T("mini.topup.member"), x, y, colDim)
|
|
y += LineH + 4
|
|
for i, name := range m.Members {
|
|
col := colText
|
|
if i == m.Cursor {
|
|
vector.DrawFilledRect(screen, float32(x-2), float32(y+i*LineH-1), 200, LineH, colBar, false)
|
|
col = colKnown
|
|
}
|
|
bal := s.run.Wallet.Card
|
|
if i > 0 {
|
|
bal = s.run.Party.Members[i].Card
|
|
}
|
|
a.Draw(screen, fmt.Sprintf("%-8s %5d¥", name, bal), x, y+i*LineH, col)
|
|
}
|
|
case minigame.TopUpPickButton:
|
|
a.Draw(screen, a.T("mini.topup.pick"), x, y, colDim)
|
|
y += LineH + 4
|
|
for i, b := range m.Buttons {
|
|
bx, by := x+(i%2)*140, y+(i/2)*26
|
|
col := colBar
|
|
if i == m.Cursor {
|
|
col = color.RGBA{90, 105, 136, 255}
|
|
}
|
|
vector.DrawFilledRect(screen, float32(bx), float32(by), 132, 22, col, false)
|
|
s.jp(a, screen, b, bx+6, by+5)
|
|
}
|
|
if m.Penalty > 0 {
|
|
a.Draw(screen, "ピッ! "+a.T("mini.topup.wrong"), x, y+56, colBad)
|
|
}
|
|
case minigame.TopUpPickAmount:
|
|
a.Draw(screen, a.T("mini.topup.amount"), x, y, colDim)
|
|
y += LineH + 4
|
|
for i, amt := range m.Amounts {
|
|
col := colText
|
|
if i == m.Cursor {
|
|
vector.DrawFilledRect(screen, float32(x-2), float32(y+i*LineH-1), 120, LineH, colBar, false)
|
|
col = colKnown
|
|
}
|
|
a.Draw(screen, fmt.Sprintf("%d円 (%d¥)", amt, amt), x, y+i*LineH, col)
|
|
}
|
|
case minigame.TopUpFeed:
|
|
a.Draw(screen, fmt.Sprintf(a.T("mini.topup.feed"), m.Amount), x, y, colText)
|
|
y += LineH + 8
|
|
vector.DrawFilledRect(screen, float32(x), float32(y), float32(p.Dx()-16), 8, colBar, false)
|
|
vector.DrawFilledRect(screen, float32(x), float32(y), float32(float64(p.Dx()-16)*m.Fill), 8, colGood, false)
|
|
}
|
|
a.Draw(screen, a.T("mini.cancel"), x, p.Max.Y-16, colDim)
|
|
}
|
|
|
|
func (s *LevelScene) drawTicket(a *Assets, screen *ebiten.Image, m *minigame.Ticket, p image.Rectangle) {
|
|
x, y := p.Min.X+8, p.Min.Y+8
|
|
a.DrawBig(screen, "きっぷ", x, y, colKnown)
|
|
a.Draw(screen, a.T("mini.ticket.title"), x+48, y+2, colText)
|
|
y += 20
|
|
if m.Stage == minigame.TicketPickSeat {
|
|
s.drawSeats(a, screen, m, p, x, y)
|
|
return
|
|
}
|
|
a.Draw(screen, fmt.Sprintf(a.T("mini.ticket.hint"), s.run.Level.Destination.Get("romaji"), m.Cash), x, y, colDim)
|
|
y += LineH + 4
|
|
for i, o := range m.Options {
|
|
ry := y + i*LineH
|
|
col := colText
|
|
if i == m.Cursor {
|
|
vector.DrawFilledRect(screen, float32(x-2), float32(ry-1), float32(p.Dx()-12), LineH, colBar, false)
|
|
}
|
|
if o.Fare > m.Cash {
|
|
col = colDim
|
|
}
|
|
s.jp(a, screen, o.Dest, x, ry)
|
|
fare := fmt.Sprintf("%d円", o.Fare)
|
|
a.Draw(screen, fare, p.Max.X-10-a.Width(fare), ry, col)
|
|
}
|
|
a.Draw(screen, a.T("mini.cancel"), x, p.Max.Y-16, colDim)
|
|
}
|
|
|
|
func (s *LevelScene) drawCrowd(a *Assets, screen *ebiten.Image, m *minigame.Crowd, p image.Rectangle) {
|
|
x, y := p.Min.X+8, p.Min.Y+8
|
|
a.DrawBig(screen, "満員", x, y, colBad)
|
|
a.Draw(screen, a.T("mini.crowd.title"), x+36, y+2, colText)
|
|
y += 24
|
|
a.Draw(screen, fmt.Sprintf(a.T("mini.crowd.hint"), m.MaxTries-m.Attempts), x, y, colDim)
|
|
y += LineH + 10
|
|
w := float32(p.Dx() - 16)
|
|
vector.DrawFilledRect(screen, float32(x), float32(y), w, 14, colBar, false)
|
|
vector.DrawFilledRect(screen, float32(x)+w*float32(m.ZoneLo), float32(y), w*float32(m.ZoneHi-m.ZoneLo), 14, colGood, false)
|
|
vector.DrawFilledRect(screen, float32(x)+w*float32(m.Pos)-1, float32(y)-3, 3, 20, colText, false)
|
|
}
|
|
|
|
func (s *LevelScene) drawRide(a *Assets, screen *ebiten.Image, m *minigame.Ride, p image.Rectangle) {
|
|
x, y := p.Min.X+8, p.Min.Y+8
|
|
a.DrawBig(screen, "次は", x, y, colDim)
|
|
a.Draw(screen, a.T("mini.ride.title"), x+36, y+2, colText)
|
|
y += 22
|
|
cur := m.Current()
|
|
a.DrawBig(screen, cur, x, y, colText)
|
|
rd := lang.Render(a.Dict, s.run.Know, cur, UILang)
|
|
a.drawLines(screen, []line{shownLine(rd.Partial, colText, false)}, x+a.Width(cur)*12/10+12, y+2)
|
|
lvl := s.run.Level
|
|
if lang.ShowEnglish(lvl.ID, m.Index, 77, lvl.Difficulty) {
|
|
if e := a.Dict.Lookup(cur); e != nil {
|
|
a.Draw(screen, e.Romaji, x, y+16, colEnglish)
|
|
}
|
|
}
|
|
y += 34
|
|
// Linjen: en prick per station, den aktuella större.
|
|
for i := range m.Stops {
|
|
cx := x + 6 + i*22
|
|
col := colDim
|
|
if i == m.Index {
|
|
col = colText
|
|
}
|
|
if i < m.Index {
|
|
col = colBar
|
|
}
|
|
vector.DrawFilledRect(screen, float32(cx), float32(y+3), 14, 2, colBar, false)
|
|
vector.DrawFilledRect(screen, float32(cx-3), float32(y), 6, 8, col, false)
|
|
}
|
|
y += 14
|
|
vector.DrawFilledRect(screen, float32(x), float32(y), float32(p.Dx()-16), 3, colBar, false)
|
|
vector.DrawFilledRect(screen, float32(x), float32(y), float32(float64(p.Dx()-16)*(1-m.Timer/m.StopSec)), 3, colKnown, false)
|
|
hint := a.T("mini.ride.hint")
|
|
if dest := lvl.Destination.Get("romaji"); dest != "" {
|
|
hint = strings.Replace(hint, "%s", dest, 1)
|
|
}
|
|
if m.NeedsLetPass() {
|
|
a.Draw(screen, a.T("mini.ride.let_pass"), x, p.Max.Y-30, colBad)
|
|
} else if m.Crowd > 0 {
|
|
a.Draw(screen, fmt.Sprintf("%s: %s", a.T("mini.ride.crowd"), a.T("car.crowd."+minigame.CrowdNames[m.Crowd])), x, p.Max.Y-30, colDim)
|
|
}
|
|
a.Draw(screen, hint, x, p.Max.Y-16, colGood)
|
|
}
|
|
|
|
// prefText beskriver ett önskemål.
|
|
func prefText(a *Assets, m *minigame.Ticket, p minigame.SeatPref) string {
|
|
switch p.Kind {
|
|
case minigame.PrefWindow:
|
|
return a.T("seat.window")
|
|
case minigame.PrefAisle:
|
|
return a.T("seat.aisle")
|
|
case minigame.PrefLeft:
|
|
return a.T("seat.left")
|
|
case minigame.PrefRight:
|
|
return a.T("seat.right")
|
|
default:
|
|
return fmt.Sprintf(a.T("seat.with"), m.Members[p.With])
|
|
}
|
|
}
|
|
|
|
// drawSeats ritar vagnen: tre rader, fönster V | gång | fönster H, och
|
|
// sällskapets önskemål till höger.
|
|
func (s *LevelScene) drawSeats(a *Assets, screen *ebiten.Image, m *minigame.Ticket, p image.Rectangle, x, y int) {
|
|
a.Draw(screen, fmt.Sprintf(a.T("mini.seat.hint"), m.Members[m.Current]), x, y, colDim)
|
|
y += LineH + 4
|
|
const sw, sh, gap = 22, 18, 10
|
|
a.Draw(screen, a.T("seat.front"), x+2, y-2, colDim)
|
|
y += 10
|
|
for row := 0; row < minigame.SeatRows; row++ {
|
|
for col := 0; col < minigame.SeatCols; col++ {
|
|
sx := x + col*(sw+2)
|
|
if col >= 2 {
|
|
sx += gap
|
|
}
|
|
sy := y + row*(sh+3)
|
|
seat := minigame.Seat{Row: row, Col: col}
|
|
bg := colBar
|
|
if m.SeatTaken(seat) {
|
|
bg = color.RGBA{90, 105, 136, 255}
|
|
}
|
|
vector.DrawFilledRect(screen, float32(sx), float32(sy), sw, sh, bg, false)
|
|
if seat == m.Seat {
|
|
vector.DrawFilledRect(screen, float32(sx), float32(sy), sw, 2, colKnown, false)
|
|
vector.DrawFilledRect(screen, float32(sx), float32(sy+sh-2), sw, 2, colKnown, false)
|
|
}
|
|
for i, as := range m.Assigned {
|
|
if as == seat {
|
|
a.Draw(screen, string([]rune(m.Members[i])[:1]), sx+8, sy+3, colText)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Önskemål.
|
|
px := x + 4*(sw+2) + gap + 14
|
|
a.Draw(screen, a.T("mini.seat.prefs"), px, y-10, colDim)
|
|
for i, pr := range m.Prefs {
|
|
col := colText
|
|
if pr.Member == m.Current {
|
|
col = colKnown
|
|
}
|
|
a.Draw(screen, m.Members[pr.Member]+": "+prefText(a, m, pr), px, y+i*LineH, col)
|
|
}
|
|
a.Draw(screen, a.T("mini.cancel"), x, p.Max.Y-16, colDim)
|
|
}
|