M10: byten, hemresan och levelcheck
All checks were successful
release / build-release (push) Successful in 1m37s
All checks were successful
release / build-release (push) Successful in 1m37s
Resor med byten: en bana kan ha next som pekar på en delbana (kind leg, syns inte på bankartan). När man klivit av rätt tåg fortsätter samma omgång på nästa station med klocka, plånbok, sällskap och räknare; par-tiden adderas, dimma och folk är nya, en Byte!-banner visas och HUD:en visar resmålet via bytesstationen. Tidsgräns och missade tåg gäller hela resan. level.ValidateChains kräver sammanhängande kedjor. Nya resor: 007 Akihabara -> Ueno -> Asakusa och finalen 008 Tokyo (två våningar) -> Nippori -> Narita flygplats. Slutscen när sista huvudbanan klaras första gången: planet lyfter, resan summeras, eftertexter; Progress.StoryCleared sparas; flaggan -ending. tools/levelcheck ritar banorna och rapporterar problem. HUD-nivån kortas när mätarna tar plats; poängraden radbryts. Skärmdumpar och docs uppdaterade. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -26,12 +26,20 @@ const (
|
||||
learnedShowSec = 3.0
|
||||
progressedShowSec = 2.5
|
||||
flashShowSec = 2.5
|
||||
transferBannerSec = 3.0
|
||||
)
|
||||
|
||||
// Mätarnas bredd och mellanrum uppe till höger (per medlem).
|
||||
const (
|
||||
meterW = 11
|
||||
meterGap = 3
|
||||
)
|
||||
|
||||
// LevelScene är själva spelandet av en bana. All spellogik ligger i sim.Run;
|
||||
// scenen översätter input och ritar.
|
||||
type LevelScene struct {
|
||||
id string
|
||||
level *level.Level // aktuell delbana (byts när resan fortsätter på nästa station)
|
||||
run *sim.Run
|
||||
cam *sim.Camera
|
||||
animT float64
|
||||
@@ -58,14 +66,21 @@ func NewLevelScene(g *Game, id string, seed rng.Seed) (*LevelScene, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := &LevelScene{
|
||||
id: id,
|
||||
run: sim.NewRun(lvl, g.runOptions(id, seed)),
|
||||
cam: sim.NewCamera(ScreenW, ScreenH, float64(lvl.Width()*TileSize), float64(lvl.Height()*TileSize)),
|
||||
}
|
||||
s := &LevelScene{id: id, run: sim.NewRun(lvl, g.runOptions(id, seed))}
|
||||
s.rng = rand.New(rand.NewPCG(1, 2))
|
||||
s.rebind(g)
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// rebind bygger om allt som hör till den aktuella delbanan: kamera, räls,
|
||||
// affischer, melodifaser och bakgrundsprat. Körs vid start och vid byte.
|
||||
func (s *LevelScene) rebind(g *Game) {
|
||||
lvl := s.run.Level
|
||||
s.level = lvl
|
||||
s.cam = sim.NewCamera(ScreenW, ScreenH, float64(lvl.Width()*TileSize), float64(lvl.Height()*TileSize))
|
||||
s.cam.Margin = TileSize // rälsen ligger i kantraden: visa en ruta utanför kartan
|
||||
s.lastPhase = map[level.Tile]level.Phase{}
|
||||
s.rng = rand.New(rand.NewPCG(1, 2))
|
||||
s.particles = nil
|
||||
s.buildTracks()
|
||||
s.buildPosters()
|
||||
g.startChatter(lvl.Noise)
|
||||
@@ -73,7 +88,6 @@ func NewLevelScene(g *Game, id string, seed rng.Seed) (*LevelScene, error) {
|
||||
s.lastPhase[tr.tile], _ = s.run.PlatformPhase(tr.tile)
|
||||
}
|
||||
s.followPlayer()
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// TrackPhaseName är fasen för närmaste perrong inom indikatoravståndet
|
||||
@@ -145,6 +159,9 @@ func (s *LevelScene) Update(g *Game) error {
|
||||
// Minispel: all input går dit; Esc avbryter minispelet, inte banan.
|
||||
mx, my := miniMoveXY()
|
||||
s.run.StepMini(dt, minigame.Input{Move: my, MoveX: mx, Confirm: confirmJustPressed(), Hold: confirmHeld(), Cancel: backJustPressed()})
|
||||
if s.run.Level != s.level {
|
||||
s.rebind(g) // bytet: resan fortsätter på nästa station
|
||||
}
|
||||
if s.run.Outcome != sim.Ongoing {
|
||||
s.outcome = g.levelEnded(s.run, s.id)
|
||||
g.SetScene(NewResultScene(s))
|
||||
@@ -299,6 +316,14 @@ func (s *LevelScene) Draw(g *Game, screen *ebiten.Image) {
|
||||
s.drawIndicator(screen, ox, oy)
|
||||
|
||||
s.drawHUD(g, screen)
|
||||
if r := s.run; r.TransferAt >= 0 && r.Time-r.TransferAt < transferBannerSec && r.Mini == nil && r.Outcome == sim.Ongoing {
|
||||
txt := g.Assets.T("hud.transfer_done")
|
||||
w := g.Assets.Width(txt) + 16
|
||||
x := (ScreenW - w) / 2
|
||||
vector.DrawFilledRect(screen, float32(x), 60, float32(w), LineH+8, colPanel, false)
|
||||
vector.DrawFilledRect(screen, float32(x), 60, float32(w), 2, colWarn, false)
|
||||
g.Assets.Draw(screen, txt, x+8, 64, colWarn)
|
||||
}
|
||||
if s.run.Mini != nil {
|
||||
s.drawMinigame(g, screen)
|
||||
}
|
||||
@@ -346,27 +371,41 @@ func (s *LevelScene) drawHUD(g *Game, screen *ebiten.Image) {
|
||||
a := g.Assets
|
||||
vector.DrawFilledRect(screen, 0, 0, ScreenW, 24, color.RGBA{24, 20, 37, 200}, false)
|
||||
|
||||
dest := r.Level.Destination.Get("romaji")
|
||||
if r.Know.ShowsDestinationJP() { // hint från nivå 1: resmålet på japanska, att matcha mot skyltarna
|
||||
dest := r.FinalDestination().Get("romaji")
|
||||
if !r.IsLastLeg() { // resa med byte: den här delbanan slutar vid bytesstationen
|
||||
dest += " " + a.T("hud.via") + " " + r.Level.Destination.Get("romaji")
|
||||
}
|
||||
if r.Know.ShowsDestinationJP() { // hint från nivå 1: delbanans mål på japanska, att matcha mot skyltarna
|
||||
dest += " " + r.Level.Destination.Get("ja")
|
||||
}
|
||||
name := r.Level.Name.Get(UILang)
|
||||
if r.Level.NumFloors() > 1 {
|
||||
name += " " + r.Level.FloorName(r.Player.Tile.Z)
|
||||
}
|
||||
if r.Legs() > 1 {
|
||||
name += fmt.Sprintf(" %d/%d", r.LegNo(), r.Legs())
|
||||
}
|
||||
a.Draw(screen, name, 4, 0, colText)
|
||||
destX := 4 + a.Width(name) + 12
|
||||
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)
|
||||
// Japanska-nivån får plats till vänster om mätarna; annars kortas den.
|
||||
jpX := destX + a.Width(a.T("hud.destination")+": "+dest) + 12
|
||||
metersX := ScreenW - 4 - len(r.Party.Members)*(meterW+meterGap)
|
||||
jp := fmt.Sprintf("%s Lv%d·%d", a.T("hud.japanese"), r.Know.Level(), r.Know.Count())
|
||||
if jpX+a.Width(jp) > metersX-4 {
|
||||
jp = fmt.Sprintf("Lv%d", r.Know.Level())
|
||||
}
|
||||
if jpX+a.Width(jp) <= metersX-4 {
|
||||
a.Draw(screen, jp, jpX, 0, colDim)
|
||||
}
|
||||
timeStr := fmt.Sprintf("%s %3.0fs", a.T("hud.time"), r.Time)
|
||||
if r.Level.TimeLimitSec > 0 {
|
||||
if r.TimeLimit() > 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.MaxMissed() > 0 {
|
||||
wallet += fmt.Sprintf(" %s %d/%d", a.T("hud.missed_short"), r.MissedTrains, r.MaxMissed())
|
||||
}
|
||||
if r.Wallet.Phone {
|
||||
wallet += fmt.Sprintf(" %s %.0f%%", a.T("hud.phone"), r.Wallet.Battery)
|
||||
@@ -375,7 +414,7 @@ func (s *LevelScene) drawHUD(g *Game, screen *ebiten.Image) {
|
||||
wallet += " " + a.T("hud.ticket") + " " + r.Wallet.Ticket
|
||||
}
|
||||
walletCol := colDim
|
||||
if r.Level.TimeLimitSec > 0 && r.TimeLeft() < 30 {
|
||||
if r.TimeLimit() > 0 && r.TimeLeft() < 30 {
|
||||
walletCol = colBad
|
||||
}
|
||||
a.Draw(screen, wallet, 4, LineH, walletCol)
|
||||
@@ -385,9 +424,6 @@ func (s *LevelScene) drawHUD(g *Game, screen *ebiten.Image) {
|
||||
return // resultatpanelen, minispelet eller butiken tar över; ingen inforuta eller minikarta
|
||||
}
|
||||
lines, accent, bar := s.infoLines(a)
|
||||
if len(lines) == 0 {
|
||||
s.drawModeHint(a, screen)
|
||||
}
|
||||
if entry := a.Dict.Lookup(r.Learned); entry != nil && r.Time-r.LearnedAt < learnedShowSec {
|
||||
lines = append(lines, plainLine(fmt.Sprintf("%s: %s = %s (%s)", a.T("hud.learned"), r.Learned,
|
||||
entry.Translation(UILang), entry.Romaji), colKnown))
|
||||
@@ -402,7 +438,7 @@ func (s *LevelScene) drawHUD(g *Game, screen *ebiten.Image) {
|
||||
}
|
||||
lines = append(lines, a.wrapLine(plainLine(strings.Join(parts, " "), colWarn), ScreenW-8)...)
|
||||
}
|
||||
if r.Flashing(flashShowSec) {
|
||||
if r.Flashing(flashShowSec) && r.FlashKey != sim.FlashTransfer { // bytet har sin egen banner
|
||||
col := colBad
|
||||
if r.FlashKey == sim.FlashRescan {
|
||||
col = colWarn
|
||||
@@ -426,6 +462,9 @@ func (s *LevelScene) drawHUD(g *Game, screen *ebiten.Image) {
|
||||
// (eller tåget under hen) annars skulle hamna bakom den.
|
||||
var box image.Rectangle
|
||||
hasBox := len(lines) > 0
|
||||
if !hasBox {
|
||||
s.drawModeHint(a, screen) // lägesrutan bara när ingen inforuta visas
|
||||
}
|
||||
if hasBox {
|
||||
h := 8 + LineH*len(lines)
|
||||
if bar {
|
||||
@@ -579,7 +618,7 @@ 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 = 11, 3
|
||||
const w, gap = meterW, meterGap
|
||||
x0 := ScreenW - 4 - len(members)*(w+gap)
|
||||
for i, m := range members {
|
||||
x := float32(x0 + i*(w+gap))
|
||||
|
||||
Reference in New Issue
Block a user