M7: svårighet, tid, poäng, debuffar, egna kort och sittplatsval
All checks were successful
release / build-release (push) Successful in 1m37s
All checks were successful
release / build-release (push) Successful in 1m37s
- sim/stats.go: Stats (fart, törst, fokus, toalett, tur), debuffar som multiplikatorer, Profile som följer med mellan banor och räknar ned - sim.Run: tidsgräns och maxantal missade tåg förlorar banan; poängformel (tid mot par, pengar, sällskapets mätare, utforskat, avdrag); debuffar tjänas av många steg, lång tid, tom matmätare och seedad otur vid kiosken; stats påverkar fart, mätartakter, läsning och hjälptid - egna kort (individualCards): Member.Card, spärren kräver allas saldo och drar från alla, automaten frågar vems kort, biljetter köps åt alla - sittplatsval (seatSelection): Ticket får ett sittplatssteg med seedade önskemål (fönster/gång/sida/bredvid); missnöjda ger mätar- och poängavdrag - level: maxMissedTrains, individualCards, seatSelection; bana 003 Shibuya (svårighet 3, fyra i sällskapet) och 002 med tidsgräns och sittplatser; ordboken utökad; generatorn är spec-driven - game: tid kvar/missade i HUD, debuffar i meny/HUD/resultat, poäng och förlustorsaker på resultatet, vyer för personval och vagn, skript-pick, flaggan -debuffs; HUD och resultat komprimerade för fyra i sällskapet - plan (Björn 2026-10-24): flera våningar med trappor som M9a, egna kort och sittplatsval som svårighetsinställningar i M7 – i spelbeskrivning, roadmap, testplan - docs/README/wiki med sittplatsbilden Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -32,6 +32,7 @@ type Game struct {
|
||||
Assets *Assets
|
||||
Know *lang.Knowledge // spelarens japanska – följer med mellan banor (sparas i M8)
|
||||
Wallet *sim.Wallet // pengar, kort, telefon, biljett – följer med mellan banor
|
||||
Profile *sim.Profile // aktiva debuffar – följer med mellan banor
|
||||
fixedSeed uint64 // >0: alla banor startar med denna seed (tester/skärmdumpar)
|
||||
scene Scene
|
||||
next Scene
|
||||
@@ -49,6 +50,7 @@ type Options struct {
|
||||
Points int // språkpoäng från start (för tester/skärmdumpar)
|
||||
Seed uint64 // >0: fast seed för banorna (för tester/skärmdumpar)
|
||||
Phone bool // kortet i telefonen från start
|
||||
Debuffs []string // debuffar från start (för tester/skärmdumpar)
|
||||
CardYen int // >=0: kortets startsaldo (för tester/skärmdumpar), -1 = standard
|
||||
}
|
||||
|
||||
@@ -65,6 +67,10 @@ func New(opt Options) (*Game, error) {
|
||||
g.Know.Points = opt.Points
|
||||
g.fixedSeed = opt.Seed
|
||||
g.Wallet = sim.NewWallet()
|
||||
g.Profile = &sim.Profile{}
|
||||
for _, d := range opt.Debuffs {
|
||||
g.Profile.Add(sim.DebuffKind(d), 1)
|
||||
}
|
||||
g.Wallet.Phone = opt.Phone
|
||||
if opt.CardYen >= 0 {
|
||||
g.Wallet.Card = opt.CardYen
|
||||
@@ -75,7 +81,7 @@ func New(opt Options) (*Game, error) {
|
||||
}
|
||||
}
|
||||
if opt.LevelID != "" {
|
||||
s, err := NewLevelScene(a, opt.LevelID, g.Know, g.Wallet, g.seed())
|
||||
s, err := NewLevelScene(a, opt.LevelID, g, g.seed())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -127,3 +133,12 @@ func (g *Game) seed() rng.Seed {
|
||||
}
|
||||
return rng.NewSeed()
|
||||
}
|
||||
|
||||
// levelEnded appliceras exakt en gång när en bana avgjorts: banans nya
|
||||
// debuffar läggs på profilen och alla räknas ned en bana.
|
||||
func (g *Game) levelEnded(run *sim.Run) {
|
||||
for _, d := range run.NewDebuffs {
|
||||
g.Profile.Add(d.Kind, d.LevelsLeft)
|
||||
}
|
||||
g.Profile.EndLevel()
|
||||
}
|
||||
|
||||
@@ -81,19 +81,21 @@ func wordsJustPressed() bool {
|
||||
return virtual.pressed["l"] || inpututil.IsKeyJustPressed(ebiten.KeyL) || gamepadJustPressed(ebiten.StandardGamepadButtonLeftRight)
|
||||
}
|
||||
|
||||
// miniMove: -1/+1 vid just-tryck på pil/WASD (upp/vänster = -1, ner/höger = +1).
|
||||
func miniMove() int {
|
||||
// miniMoveXY: just-tryck på pil/WASD/d-pad som (x, y) i -1/0/+1.
|
||||
func miniMoveXY() (x, y int) {
|
||||
switch {
|
||||
case virtual.pressed["up"] || virtual.pressed["left"] || inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyArrowLeft) ||
|
||||
inpututil.IsKeyJustPressed(ebiten.KeyW) || inpututil.IsKeyJustPressed(ebiten.KeyA) ||
|
||||
gamepadJustPressed(ebiten.StandardGamepadButtonLeftTop) || gamepadJustPressed(ebiten.StandardGamepadButtonLeftLeft):
|
||||
return -1
|
||||
case virtual.pressed["down"] || virtual.pressed["right"] || inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) || inpututil.IsKeyJustPressed(ebiten.KeyArrowRight) ||
|
||||
inpututil.IsKeyJustPressed(ebiten.KeyS) || inpututil.IsKeyJustPressed(ebiten.KeyD) ||
|
||||
gamepadJustPressed(ebiten.StandardGamepadButtonLeftBottom) || gamepadJustPressed(ebiten.StandardGamepadButtonLeftRight):
|
||||
return 1
|
||||
case virtual.pressed["left"] || inpututil.IsKeyJustPressed(ebiten.KeyArrowLeft) || inpututil.IsKeyJustPressed(ebiten.KeyA) || gamepadJustPressed(ebiten.StandardGamepadButtonLeftLeft):
|
||||
x = -1
|
||||
case virtual.pressed["right"] || inpututil.IsKeyJustPressed(ebiten.KeyArrowRight) || inpututil.IsKeyJustPressed(ebiten.KeyD) || gamepadJustPressed(ebiten.StandardGamepadButtonLeftRight):
|
||||
x = 1
|
||||
}
|
||||
return 0
|
||||
switch {
|
||||
case virtual.pressed["up"] || inpututil.IsKeyJustPressed(ebiten.KeyArrowUp) || inpututil.IsKeyJustPressed(ebiten.KeyW) || gamepadJustPressed(ebiten.StandardGamepadButtonLeftTop):
|
||||
y = -1
|
||||
case virtual.pressed["down"] || inpututil.IsKeyJustPressed(ebiten.KeyArrowDown) || inpututil.IsKeyJustPressed(ebiten.KeyS) || gamepadJustPressed(ebiten.StandardGamepadButtonLeftBottom):
|
||||
y = 1
|
||||
}
|
||||
return x, y
|
||||
}
|
||||
|
||||
// confirmHeld: Enter/mellanslag eller A hålls nere (sedelinmatning).
|
||||
|
||||
@@ -45,6 +45,21 @@ func (s *LevelScene) drawTopUp(a *Assets, screen *ebiten.Image, m *minigame.TopU
|
||||
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
|
||||
@@ -85,6 +100,10 @@ func (s *LevelScene) drawTicket(a *Assets, screen *ebiten.Image, m *minigame.Tic
|
||||
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 {
|
||||
@@ -154,3 +173,64 @@ func (s *LevelScene) drawRide(a *Assets, screen *ebiten.Image, m *minigame.Ride,
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -36,16 +36,16 @@ type LevelScene struct {
|
||||
hideMap bool // M döljer/visar minikartan
|
||||
}
|
||||
|
||||
// NewLevelScene laddar banan och ställer spelaren på startrutan. know är
|
||||
// spelarens japanska, som delas mellan banor; seed styr omgångens slump.
|
||||
func NewLevelScene(a *Assets, id string, know *lang.Knowledge, wallet *sim.Wallet, seed rng.Seed) (*LevelScene, error) {
|
||||
// NewLevelScene laddar banan och ställer spelaren på startrutan. Kunskap,
|
||||
// plånbok och debuffar tas från Game (delas mellan banor); seed styr slumpen.
|
||||
func NewLevelScene(a *Assets, id string, g *Game, seed rng.Seed) (*LevelScene, error) {
|
||||
lvl, err := level.LoadFile(filepath.Join(a.LevelsDir, id+".json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &LevelScene{
|
||||
id: id,
|
||||
run: sim.NewRun(lvl, sim.RunOptions{Dict: a.Dict, Know: know, Wallet: wallet, Seed: seed}),
|
||||
run: sim.NewRun(lvl, sim.RunOptions{Dict: a.Dict, Know: g.Know, Wallet: g.Wallet, Debuffs: g.Profile.Debuffs, Seed: seed}),
|
||||
cam: sim.NewCamera(ScreenW, ScreenH, float64(lvl.Width()*TileSize), float64(lvl.Height()*TileSize)),
|
||||
}
|
||||
s.followPlayer()
|
||||
@@ -79,8 +79,10 @@ func (s *LevelScene) followPlayer() {
|
||||
func (s *LevelScene) Update(g *Game) error {
|
||||
if s.run.Mini != nil {
|
||||
// Minispel: all input går dit; Esc avbryter minispelet, inte banan.
|
||||
s.run.StepMini(dt, minigame.Input{Move: miniMove(), Confirm: confirmJustPressed(), Hold: confirmHeld(), Cancel: backJustPressed()})
|
||||
mx, my := miniMoveXY()
|
||||
s.run.StepMini(dt, minigame.Input{Move: my, MoveX: mx, Confirm: confirmJustPressed(), Hold: confirmHeld(), Cancel: backJustPressed()})
|
||||
if s.run.Outcome != sim.Ongoing {
|
||||
g.levelEnded(s.run)
|
||||
g.SetScene(NewResultScene(s))
|
||||
}
|
||||
return nil
|
||||
@@ -107,11 +109,43 @@ func (s *LevelScene) Update(g *Game) error {
|
||||
}
|
||||
s.followPlayer()
|
||||
if s.run.Outcome != sim.Ongoing {
|
||||
g.levelEnded(s.run)
|
||||
g.SetScene(NewResultScene(s))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MiniPick flyttar markören i det pågående minispelet ett steg mot
|
||||
// alternativet med texten text; true när det är valt (skriptläget).
|
||||
func (s *LevelScene) MiniPick(text string) bool {
|
||||
switch m := s.run.Mini.(type) {
|
||||
case *minigame.Ticket:
|
||||
if m.Stage == minigame.TicketPickDest {
|
||||
if m.Options[m.Cursor].Dest == text {
|
||||
return true
|
||||
}
|
||||
s.run.StepMini(0, minigame.Input{Move: 1})
|
||||
return false
|
||||
}
|
||||
case *minigame.TopUp:
|
||||
switch m.Stage {
|
||||
case minigame.TopUpPickMember:
|
||||
if m.Members[m.Cursor] == text {
|
||||
return true
|
||||
}
|
||||
s.run.StepMini(0, minigame.Input{Move: 1})
|
||||
return false
|
||||
case minigame.TopUpPickButton:
|
||||
if m.Buttons[m.Cursor] == text {
|
||||
return true
|
||||
}
|
||||
s.run.StepMini(0, minigame.Input{MoveX: 1})
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// trainTile är rutan "bortom perrongkanten" där tågvagnen ritas: första
|
||||
// väggruta nedanför, till höger, till vänster eller ovanför perrongen.
|
||||
func (s *LevelScene) trainTile(p level.Point) level.Point {
|
||||
@@ -243,14 +277,26 @@ func (s *LevelScene) drawHUD(g *Game, screen *ebiten.Image) {
|
||||
a.Draw(screen, a.T("hud.destination")+": "+dest, destX, 0, colKnown)
|
||||
a.Draw(screen, fmt.Sprintf("%s Lv%d·%d", a.T("hud.japanese"), r.Know.Level(), r.Know.Count()),
|
||||
destX+a.Width(a.T("hud.destination")+": "+dest)+12, 0, colDim)
|
||||
wallet := fmt.Sprintf("%s %3.0fs %s %d¥ %s %d¥", a.T("hud.time"), r.Time, a.T("hud.card"), r.Wallet.Card, a.T("hud.cash"), r.Wallet.Cash)
|
||||
timeStr := fmt.Sprintf("%s %3.0fs", a.T("hud.time"), r.Time)
|
||||
if r.Level.TimeLimitSec > 0 {
|
||||
left := r.TimeLeft()
|
||||
timeStr = fmt.Sprintf("%s %d:%02d", a.T("hud.time_left"), int(left)/60, int(left)%60)
|
||||
}
|
||||
wallet := fmt.Sprintf("%s %s %d¥ %s %d¥", timeStr, a.T("hud.card"), r.Wallet.Card, a.T("hud.cash_short"), r.Wallet.Cash)
|
||||
if r.Level.MaxMissedTrains > 0 {
|
||||
wallet += fmt.Sprintf(" %s %d/%d", a.T("hud.missed_short"), r.MissedTrains, r.Level.MaxMissedTrains)
|
||||
}
|
||||
if r.Wallet.Phone {
|
||||
wallet += fmt.Sprintf(" %s %.0f%%", a.T("hud.phone"), r.Wallet.Battery)
|
||||
}
|
||||
if r.Wallet.Ticket != "" {
|
||||
wallet += " " + a.T("hud.ticket") + " " + r.Wallet.Ticket
|
||||
}
|
||||
a.Draw(screen, wallet, 4, LineH, colDim)
|
||||
walletCol := colDim
|
||||
if r.Level.TimeLimitSec > 0 && r.TimeLeft() < 30 {
|
||||
walletCol = colBad
|
||||
}
|
||||
a.Draw(screen, wallet, 4, LineH, walletCol)
|
||||
s.drawMeters(g, screen)
|
||||
|
||||
if r.Outcome != sim.Ongoing || r.Mini != nil {
|
||||
@@ -278,6 +324,13 @@ func (s *LevelScene) drawHUD(g *Game, screen *ebiten.Image) {
|
||||
}
|
||||
lines = append(lines, a.wrapLine(plainLine(a.T(r.FlashKey), col), ScreenW-8)...)
|
||||
}
|
||||
if debuffs := g.Profile.Debuffs; len(debuffs) > 0 && r.Time < 6 {
|
||||
var parts []string
|
||||
for _, d := range debuffs {
|
||||
parts = append(parts, fmt.Sprintf("%s (%d)", a.T("debuff."+string(d.Kind)), d.LevelsLeft))
|
||||
}
|
||||
lines = append(lines, a.wrapLine(plainLine(a.T("hud.debuffs")+": "+strings.Join(parts, ", "), colBad), ScreenW-8)...)
|
||||
}
|
||||
|
||||
// Inforutans läge: nederkant, men flyttas upp under HUD:en om spelaren
|
||||
// (eller tåget under hen) annars skulle hamna bakom den.
|
||||
@@ -390,10 +443,18 @@ func (s *LevelScene) infoLines(a *Assets) (lines []line, accent color.Color, bar
|
||||
if r.DeniedAt >= 0 && r.Time-r.DeniedAt < deniedShowSec {
|
||||
key := "hud.gate_denied"
|
||||
switch {
|
||||
case lvl.TicketRequired:
|
||||
case lvl.TicketRequired && r.Wallet.Ticket == "":
|
||||
key = sim.FlashNoTicket
|
||||
case lvl.TicketRequired:
|
||||
key = "hud.need_all_tickets"
|
||||
case r.Wallet.Phone && r.Wallet.Battery <= 0:
|
||||
key = "hud.phone_dead"
|
||||
case len(r.MembersWithoutFare()) > 0:
|
||||
var names []string
|
||||
for _, m := range r.MembersWithoutFare() {
|
||||
names = append(names, m.Name)
|
||||
}
|
||||
return []line{plainLine(fmt.Sprintf(a.T(sim.FlashNoCards), strings.Join(names, ", ")), colBad)}, colBad, false
|
||||
}
|
||||
return []line{plainLine(a.T(key), colBad)}, colBad, false
|
||||
}
|
||||
@@ -403,11 +464,11 @@ func (s *LevelScene) infoLines(a *Assets) (lines []line, accent color.Color, bar
|
||||
// drawMeters ritar sällskapets mat/dryck/toa-mätare uppe till höger.
|
||||
func (s *LevelScene) drawMeters(g *Game, screen *ebiten.Image) {
|
||||
members := s.run.Party.Members
|
||||
const w, gap = 14, 4
|
||||
const w, gap = 11, 3
|
||||
x0 := ScreenW - 4 - len(members)*(w+gap)
|
||||
for i, m := range members {
|
||||
x := float32(x0 + i*(w+gap))
|
||||
g.Assets.Draw(screen, string([]rune(m.Name)[:1]), int(x)+3, 0, colDim)
|
||||
g.Assets.Draw(screen, string([]rune(m.Name)[:1]), int(x)+2, 0, colDim)
|
||||
for j, v := range []struct {
|
||||
val float64
|
||||
col color.Color
|
||||
|
||||
@@ -64,7 +64,7 @@ func (m *MenuScene) Update(g *Game) error {
|
||||
if n := len(m.entries); n > 0 {
|
||||
m.sel = (m.sel + menuMove() + n) % n
|
||||
if confirmJustPressed() {
|
||||
s, err := NewLevelScene(g.Assets, m.entries[m.sel].id, g.Know, g.Wallet, g.seed())
|
||||
s, err := NewLevelScene(g.Assets, m.entries[m.sel].id, g, g.seed())
|
||||
if err != nil {
|
||||
m.err = err
|
||||
return nil
|
||||
@@ -100,6 +100,13 @@ func (m *MenuScene) Draw(g *Game, screen *ebiten.Image) {
|
||||
}
|
||||
a.Draw(screen, fmt.Sprintf("%s Lv%d %d %s %s %d %s %d¥ %s %d¥ %s", a.T("hud.japanese"), g.Know.Level(), g.Know.Count(), a.T("hud.words"),
|
||||
a.T("words.points"), g.Know.Points, a.T("hud.card"), g.Wallet.Card, a.T("hud.cash"), g.Wallet.Cash, fmt.Sprintf(a.T("menu.phone"), onoff)), 8, ScreenH-38, colDim)
|
||||
if len(g.Profile.Debuffs) > 0 {
|
||||
var parts []string
|
||||
for _, d := range g.Profile.Debuffs {
|
||||
parts = append(parts, fmt.Sprintf("%s (%d)", a.T("debuff."+string(d.Kind)), d.LevelsLeft))
|
||||
}
|
||||
a.Draw(screen, a.T("hud.debuffs")+": "+strings.Join(parts, ", "), 8, ScreenH-50, colBad)
|
||||
}
|
||||
a.Draw(screen, a.T("menu.hint"), 8, ScreenH-26, colDim)
|
||||
a.Draw(screen, fmt.Sprintf("%s (%s)", buildinfo.Version, buildinfo.Mode), 8, ScreenH-13, colDim)
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ func (r *ResultScene) Update(g *Game) error {
|
||||
case wordsJustPressed():
|
||||
g.SetScene(NewWordsScene(g.Assets, r))
|
||||
case inpututil_R():
|
||||
s, err := NewLevelScene(g.Assets, r.from.id, g.Know, g.Wallet, g.seed())
|
||||
s, err := NewLevelScene(g.Assets, r.from.id, g, g.seed())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -42,26 +42,28 @@ func (r *ResultScene) Draw(g *Game, screen *ebiten.Image) {
|
||||
r.from.Draw(g, screen) // banan i bakgrunden
|
||||
run := r.from.run
|
||||
a := g.Assets
|
||||
vector.DrawFilledRect(screen, 20, 36, ScreenW-40, 120, colPanel, false)
|
||||
vector.DrawFilledRect(screen, 20, 30, ScreenW-40, 138, colPanel, false)
|
||||
|
||||
accent, headline := colBad, a.T("level.lost")
|
||||
if run.Outcome == sim.Won {
|
||||
accent, headline = colGood, a.T("level.cleared")
|
||||
}
|
||||
vector.DrawFilledRect(screen, 20, 36, ScreenW-40, 3, accent, false)
|
||||
a.DrawBig(screen, headline, 28, 44, accent)
|
||||
vector.DrawFilledRect(screen, 20, 30, ScreenW-40, 3, accent, false)
|
||||
a.DrawBig(screen, headline, 28, 36, accent)
|
||||
if run.Outcome == sim.Won {
|
||||
stars := strings.Repeat("★", run.Stars) + strings.Repeat("☆", 3-run.Stars)
|
||||
a.DrawBig(screen, stars, ScreenW-28-a.Width(stars)-8, 44, colWarn)
|
||||
a.DrawBig(screen, stars, ScreenW-28-a.Width(stars)-8, 36, colWarn)
|
||||
}
|
||||
|
||||
y := 62
|
||||
y := 54
|
||||
if run.Boarded == nil && run.Outcome == sim.Lost {
|
||||
a.Draw(screen, a.T(run.LostReason), 28, y, colText)
|
||||
y += LineH
|
||||
}
|
||||
if tr := run.Boarded; tr != nil {
|
||||
a.Draw(screen, fmt.Sprintf("%s%s %s %s%s", tr.Track, a.T("hud.track_suffix_ja"), tr.Line.Get("ja"),
|
||||
tr.Destination.Get("ja"), a.T("hud.for_ja")), 28, y, colKnown)
|
||||
y += LineH
|
||||
a.Draw(screen, fmt.Sprintf("%s %s %s → %s", a.T("hud.track"), tr.Track, tr.Line.Get("romaji"), tr.Destination.Get("romaji")), 28, y, colDim)
|
||||
y += LineH
|
||||
if run.Outcome == sim.Lost {
|
||||
reason := a.T(run.LostReason)
|
||||
if run.LostReason == sim.LostWrongTrain {
|
||||
@@ -77,18 +79,32 @@ func (r *ResultScene) Draw(g *Game, screen *ebiten.Image) {
|
||||
a.Draw(screen, fmt.Sprintf("%s %.1fs (%s %.0fs) %s %d %s %.0f %%", a.T("hud.time"), run.Time, a.T("result.par"), run.ParSec(),
|
||||
a.T("hud.steps"), run.Steps, a.T("result.explored"), 100*run.Fog.Explored(run.Level)), 28, y, colText)
|
||||
y += LineH
|
||||
a.Draw(screen, fmt.Sprintf("%s %d %s %d¥ %s %d¥ %s %d %s %d", a.T("result.gates"), run.GatePasses, a.T("result.card"), run.Wallet.Card,
|
||||
a.T("result.spent"), run.Wallet.Spent, a.T("result.missed"), run.MissedTrains, a.T("result.served"), run.Served), 28, y, colText)
|
||||
a.Draw(screen, fmt.Sprintf("%s %d %s %d¥ %s %d¥ %s %d", a.T("result.gates"), run.GatePasses, a.T("hud.card"), run.Wallet.Card,
|
||||
a.T("result.spent"), run.Wallet.Spent, a.T("result.missed_short"), run.MissedTrains), 28, y, colText)
|
||||
y += LineH
|
||||
if run.Outcome == sim.Won {
|
||||
msg := fmt.Sprintf("+%d %s", run.PointsEarned, a.T("words.points"))
|
||||
sc := run.Score
|
||||
a.Draw(screen, fmt.Sprintf("%s %d", a.T("result.score"), sc.Total), 28, y, colText)
|
||||
y += LineH
|
||||
a.Draw(screen, fmt.Sprintf("%s %d %s %d %s %d %s %d %s %d", strings.ToLower(a.T("hud.time")), sc.Time, a.T("result.money"), sc.Money,
|
||||
a.T("result.meters"), sc.Meters, a.T("result.map"), sc.Explore, a.T("result.penalty"), sc.Penalty), 28, y, colDim)
|
||||
y += LineH
|
||||
msg := fmt.Sprintf("+%d %s", run.PointsEarned, a.T("result.lang_points"))
|
||||
if len(run.LearnedDest) > 0 {
|
||||
msg += " " + a.T("hud.learned") + ": " + strings.Join(run.LearnedDest, " ") + " (" + a.T("result.destination") + ")"
|
||||
}
|
||||
a.Draw(screen, msg, 28, y, colKnown)
|
||||
y += LineH
|
||||
}
|
||||
if len(run.NewDebuffs) > 0 {
|
||||
var parts []string
|
||||
for _, d := range run.NewDebuffs {
|
||||
parts = append(parts, fmt.Sprintf("%s (%d)", a.T("debuff."+string(d.Kind)), d.LevelsLeft))
|
||||
}
|
||||
a.Draw(screen, a.T("result.new_debuffs")+": "+strings.Join(parts, ", "), 28, y, colBad)
|
||||
y += LineH
|
||||
}
|
||||
a.Draw(screen, fmt.Sprintf("%s Lv%d %d %s %s %d (%d %s)", a.T("hud.japanese"), run.Know.Level(), run.Know.Count(),
|
||||
a.T("hud.words"), a.T("words.points"), run.Know.Points, run.Know.Helps, a.T("result.helps")), 28, y, colDim)
|
||||
a.Draw(screen, a.T("result.hint"), 28, 141, colDim)
|
||||
a.Draw(screen, a.T("result.hint"), 28, 155, colDim)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
// hold N håll Enter nere N bildrutor (sedelinmatning)
|
||||
// waittrain vänta tills tåget vid perrongen spelaren står på är inne
|
||||
// waitstop vänta tills tågresan står vid resmålet
|
||||
// pick TEXT flytta markören i minispelet tills alternativet TEXT är valt
|
||||
// press KEY tryck enter | esc | r | h | l | up | down en bildruta
|
||||
// move DIR N gå N rutor åt up | down | left | right (stannar exakt där)
|
||||
// shot PATH spara aktuell bild som PNG (intern upplösning × shotScale)
|
||||
@@ -86,9 +87,9 @@ func parseScript(src string) (*script, error) {
|
||||
return nil, fmt.Errorf("script: okänd riktning %q", f[1])
|
||||
}
|
||||
st.n, _ = strconv.Atoi(f[2])
|
||||
case "shot":
|
||||
case "shot", "pick":
|
||||
if len(f) != 2 {
|
||||
return nil, fmt.Errorf("script: 'shot PATH'")
|
||||
return nil, fmt.Errorf("script: '%s TEXT'", f[0])
|
||||
}
|
||||
st.arg = f[1]
|
||||
case "quit", "waittrain", "waitstop":
|
||||
@@ -124,6 +125,7 @@ type playerTiler interface {
|
||||
type trainWaiter interface {
|
||||
TrainHere() bool
|
||||
RideAtDest() bool
|
||||
MiniPick(text string) bool
|
||||
}
|
||||
|
||||
// tick körs i början av varje Update. frame är senast ritade bild.
|
||||
@@ -191,6 +193,11 @@ func (s *script) tick(g *Game, frame *ebiten.Image) error {
|
||||
if tw, ok := g.scene.(trainWaiter); !ok || tw.RideAtDest() {
|
||||
s.advance()
|
||||
}
|
||||
case "pick":
|
||||
s.counter++
|
||||
if tw, ok := g.scene.(trainWaiter); !ok || tw.MiniPick(st.arg) || s.counter > 60 {
|
||||
s.advance()
|
||||
}
|
||||
case "shot":
|
||||
if err := saveShot(frame, st.arg); err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user