M10c: vem betalar i kiosken och spärrar utan återvändo
All checks were successful
release / build-release (push) Successful in 2m7s

Björns tillägg 2026-10-24. Med egna kort och fler i sällskapet frågar
kiosken vem som betalar (minigame.Payer: kontant, spelarens kort eller en
följares kort; den som inte har råd nekas, Esc avbryter). Avgiften dras
nu när man passerar spärren in i det betalda området (level.InsideGates),
inte på spärrrutan. Banflaggan oneWayGates: incheckad släpps inte ut igen
förrän man åkt; Enter vid spärren kallar på personal (10 s) som låser upp
korten; ny inpassering kostar igen. På i 005, 007/007b och 008/008b.
Skriptkommandon urge och push. Docs, wiki och skärmdumpar uppdaterade.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-24 22:12:13 +02:00
parent 4ae6a1d24c
commit f04d6a88f9
26 changed files with 674 additions and 64 deletions

View File

@@ -32,9 +32,44 @@ func (s *LevelScene) drawMinigame(g *Game, screen *ebiten.Image) {
s.drawRide(a, screen, m, panel)
case *minigame.CarPick:
s.drawCarPick(a, screen, m, panel)
case *minigame.Payer:
s.drawPayer(a, screen, m, panel)
}
}
// drawPayer: vem betalar i kiosken – kontant, spelarens kort eller en
// följares kort, med saldon; de som inte har råd i rött.
func (s *LevelScene) drawPayer(a *Assets, screen *ebiten.Image, m *minigame.Payer, 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.payer.title"), x+60, y+2, colText)
y += 20
a.Draw(screen, fmt.Sprintf(a.T("mini.payer.price"), m.Price), x, y, colDim)
y += LineH + 4
for i, o := range m.Options {
name, kind := o.Name, a.T("mini.payer.card")
if o.Cash {
name, kind = a.T("mini.payer.cash"), ""
}
col := colText
if !m.CanAfford(i) {
col = colBad
}
if i == m.Cursor {
vector.DrawFilledRect(screen, float32(x-2), float32(y+i*LineH-1), 220, LineH, colBar, false)
if m.CanAfford(i) {
col = colKnown
}
}
a.Draw(screen, fmt.Sprintf("%-8s %5d¥ %s", name, o.Yen, kind), x, y+i*LineH, col)
}
y += len(m.Options)*LineH + 4
if m.Refused {
a.Draw(screen, a.T("mini.payer.short"), x, y, colBad)
}
a.Draw(screen, a.T("mini.payer.hint"), x, p.Max.Y-14, colDim)
}
// 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) {

View File

@@ -141,6 +141,18 @@ func (s *LevelScene) drawModeHint(a *Assets, screen *ebiten.Image) {
a.Draw(screen, txt, 4, y+3, col)
}
// ForceUrge gör en i sällskapet törstig (skriptläget: "urge NAMN" för
// skärmdumpar av kiosken).
func (s *LevelScene) ForceUrge(name string) bool {
for _, m := range s.run.Party.Members {
if m.Name == name {
m.Meters.Drink, m.Urge = 5, sim.Thirsty
return true
}
}
return false
}
// LevelOver rapporterar om banan är avgjord (skriptläget: "waitover").
func (s *LevelScene) LevelOver() bool { return s.run.Outcome != sim.Ongoing }
@@ -222,6 +234,13 @@ func (s *LevelScene) MiniPick(text string) bool {
s.run.StepMini(0, minigame.Input{MoveX: 1})
return false
}
case *minigame.Payer:
o := m.Options[m.Cursor]
if o.Name == text || (text == "cash" && o.Cash) {
return true
}
s.run.StepMini(0, minigame.Input{MoveX: 1})
return false
case *minigame.TopUp:
switch m.Stage {
case minigame.TopUpPickMember:
@@ -492,6 +511,9 @@ func (s *LevelScene) drawHUD(g *Game, screen *ebiten.Image) {
if r.Facility.Active {
p = r.FacilityProgress()
}
if r.Staff.Active {
p = r.StaffProgress()
}
by := float32(y + h - 7)
vector.DrawFilledRect(screen, 4, by, ScreenW-8, 4, colBar, false)
vector.DrawFilledRect(screen, 4, by, float32((ScreenW-8)*p), 4, colKnown, false)
@@ -567,6 +589,17 @@ func (s *LevelScene) infoLines(a *Assets) (lines []line, accent color.Color, bar
}
switch lvl.At(tile) {
case level.Gate:
switch {
case r.Staff.Active:
return []line{plainLine(a.T("hud.staff_busy"), colDim)}, colGood, true
case r.NoExitAt >= 0 && r.Time-r.NoExitAt < deniedShowSec:
return a.wrapLine(plainLine(fmt.Sprintf(a.T("hud.no_exit"), sim.StaffSec), colBad), maxW), colBad, false
case r.NeedsStaff():
return []line{plainLine(fmt.Sprintf(a.T("hud.staff_prompt"), sim.StaffSec), colWarn)}, colWarn, false
case lvl.OneWayGates && r.StaffUnlocked:
return []line{plainLine(a.T("hud.unlocked_prompt"), colGood)}, colGood, false
}
case level.Toilet:
if r.Facility.Active {
return []line{plainLine(a.T("hud.toilet_busy"), colDim)}, colGood, true

View File

@@ -88,11 +88,20 @@ 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", "pick", "waitphase":
case "shot", "pick", "waitphase", "urge":
if len(f) != 2 {
return nil, fmt.Errorf("script: '%s TEXT'", f[0])
}
st.arg = f[1]
case "push": // håll en riktning N bildrutor oavsett om spelaren kommer någonstans (t.ex. mot en spärr)
if len(f) != 3 {
return nil, fmt.Errorf("script: 'push DIR N'")
}
st.dir = parseDir(f[1])
if st.dir == sim.None {
return nil, fmt.Errorf("script: okänd riktning %q", f[1])
}
st.n, _ = strconv.Atoi(f[2])
case "quit", "waittrain", "waitstop", "waitover":
default:
return nil, fmt.Errorf("script: okänt kommando %q", f[0])
@@ -128,6 +137,7 @@ type trainWaiter interface {
RideAtDest() bool
LevelOver() bool
MiniPick(text string) bool
ForceUrge(name string) bool
TrackPhaseName() string
}
@@ -201,6 +211,17 @@ func (s *script) tick(g *Game, frame *ebiten.Image) error {
if tw, ok := g.scene.(trainWaiter); !ok || tw.LevelOver() || s.counter > 60*120 {
s.advance()
}
case "urge":
if tw, ok := g.scene.(trainWaiter); ok {
tw.ForceUrge(st.arg)
}
s.advance()
case "push":
virtual.held = st.dir
s.counter++
if s.counter >= st.n {
s.advance()
}
case "waitphase":
s.counter++
if tw, ok := g.scene.(trainWaiter); !ok || tw.TrackPhaseName() == st.arg || s.counter > 60*60 {