M9b: folk, filer, reklam, sorl och vagnval
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>
This commit is contained in:
2026-10-24 12:33:44 +02:00
parent 8fca14e42d
commit de61568e44
49 changed files with 1568 additions and 136 deletions

View File

@@ -33,14 +33,19 @@ const (
Kiosk Tile = 'K' // kiosk/automat för mat och dryck
StairsUp Tile = 'U' // trappa upp: leder till 'D' på samma x,y en våning upp
StairsDown Tile = 'D' // trappa ner: leder till 'U' på samma x,y en våning ner
LaneLeft Tile = '<' // fil för gång uppåt/åt vänster (bredare gångar: håll till vänster)
LaneRight Tile = '>' // fil för gång neråt/åt höger
)
var knownTiles = map[Tile]bool{
Wall: true, Floor: true, Spawn: true, Platform: true, WrongPlatform: true,
Gate: true, TicketMachine: true, Sign: true, Toilet: true, Kiosk: true,
StairsUp: true, StairsDown: true,
StairsUp: true, StairsDown: true, LaneLeft: true, LaneRight: true,
}
// IsLane rapporterar om rutan är en markerad fil.
func (t Tile) IsLane() bool { return t == LaneLeft || t == LaneRight }
// IsStairs rapporterar om rutan är en trappa.
func (t Tile) IsStairs() bool { return t == StairsUp || t == StairsDown }
@@ -162,13 +167,34 @@ func (s Schedule) Present(t float64) bool {
// Train är ett tåg (eller en buss) som avgår från en perrong i banan.
type Train struct {
Platform string `json:"platform"` // vilket perrong-tecken (P/p) tåget står vid
Track string `json:"track"` // spårnummer som visas på skyltar/HUD, t.ex. "2"
Line Text `json:"line"` // linjens namn
Destination Text `json:"destination"` // slutstation
Correct bool `json:"correct"` // tar spelaren till rätt resmål
Crowded bool `json:"crowded"` // fullt tåg: minispel för att få plats
Schedule Schedule `json:"schedule"`
Platform string `json:"platform"` // vilket perrong-tecken (P/p) tåget står vid
Track string `json:"track"` // spårnummer som visas på skyltar/HUD, t.ex. "2"
Line Text `json:"line"` // linjens namn
Destination Text `json:"destination"` // slutstation
Correct bool `json:"correct"` // tar spelaren till rätt resmål
Crowded bool `json:"crowded"` // fullt tåg: minispel för att få plats (gäller alla vagnar)
Cars int `json:"cars"` // antal vagnar (0 = DefaultCars)
WomenOnlyCar int `json:"womenOnlyCar"` // 1-baserat vagnnummer för kvinnovagn, 0 = ingen
WomenOnlyFrom int `json:"womenOnlyFrom"` // kvinnovagn gäller från den här bantiden (s); 0 och To=0 = alltid
WomenOnlyTo int `json:"womenOnlyTo"` // …till den här bantiden (s)
Schedule Schedule `json:"schedule"`
}
// DefaultCars och gränser för vagnar.
const (
DefaultCars = 3
MaxCars = 8
)
// WomenOnlyActive rapporterar om kvinnovagnen gäller vid bantiden t.
func (tr *Train) WomenOnlyActive(t float64) bool {
if tr.WomenOnlyCar == 0 {
return false
}
if tr.WomenOnlyFrom == 0 && tr.WomenOnlyTo == 0 {
return true
}
return t >= float64(tr.WomenOnlyFrom) && t < float64(tr.WomenOnlyTo)
}
// SignText är texten på en skyltruta ('i').
@@ -199,7 +225,10 @@ type Level struct {
Order int `json:"order"` // huvudbanornas ordning på bankartan (1, 2, …); sidobanor ärver förälderns
Parent string `json:"parent"` // sidobanans huvudbana – låses upp när den är klarad
Rewards Rewards `json:"rewards"`
Shop []string `json:"shop"` // items som kiosken säljer på banan (tom = seedat urval)
Shop []string `json:"shop"` // items som kiosken säljer på banan (tom = seedat urval)
Crowd int `json:"crowd"` // folk på stationen 0–3 (NPC:er)
Clutter int `json:"clutter"` // visuell röra 0–3 (affischer, lockskyltar)
Noise int `json:"noise"` // ljudröra 0–3 (bakgrundsprat)
Name Text `json:"name"`
Difficulty int `json:"difficulty"` // 1 (lätt) … 5 (svår)
Destination Text `json:"destination"` // dit spelaren ska
@@ -402,6 +431,15 @@ func (l *Level) Validate() error {
if tr.Correct {
correct++
}
if tr.Cars == 0 {
tr.Cars = DefaultCars
}
if tr.Cars < 1 || tr.Cars > MaxCars {
return fmt.Errorf("level %s: tåg %d har %d vagnar (1–%d)", l.ID, i, tr.Cars, MaxCars)
}
if tr.WomenOnlyCar < 0 || tr.WomenOnlyCar > tr.Cars || tr.WomenOnlyFrom < 0 || tr.WomenOnlyTo < tr.WomenOnlyFrom {
return fmt.Errorf("level %s: tåg %d har ogiltig kvinnovagn (%d av %d, %d–%d s)", l.ID, i, tr.WomenOnlyCar, tr.Cars, tr.WomenOnlyFrom, tr.WomenOnlyTo)
}
sc := &tr.Schedule
if sc.IntervalSec == 0 {
sc.IntervalSec = DefaultIntervalSec
@@ -443,6 +481,11 @@ func (l *Level) Validate() error {
if l.MaxMissedTrains < 0 || l.TimeLimitSec < 0 {
return fmt.Errorf("level %s: negativ tidsgräns/maxantal missade tåg", l.ID)
}
for name, v := range map[string]int{"crowd": l.Crowd, "clutter": l.Clutter, "noise": l.Noise} {
if v < 0 || v > 3 {
return fmt.Errorf("level %s: %s måste vara 0–3", l.ID, name)
}
}
if l.Party > MaxParty {
return fmt.Errorf("level %s: sällskapet får vara högst %d", l.ID, MaxParty)
}