package game import ( "fmt" "image/color" "math" "sort" "strings" "github.com/hajimehoshi/ebiten/v2" "github.com/hajimehoshi/ebiten/v2/vector" "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/sim" ) // EndingScene är storyns slut: sällskapet flyger hem, resan summeras och // eftertexterna rullar. Enter går vidare mellan delarna, Esc hoppar till // bankartan. type EndingScene struct { t float64 // sekunder i aktuell del phase int // 0 flyget, 1 summeringen, 2 eftertexterna lines []string } const ( endingFlightSec = 7.0 // flyget innan summeringen kommer av sig själv endingCreditsPxS = 14.0 // eftertexternas rullhastighet ) // NewEndingScene startar hemresan. func NewEndingScene(g *Game) *EndingScene { return &EndingScene{lines: strings.Split(g.Assets.T("ending.credits"), "\n")} } func (e *EndingScene) Update(g *Game) error { e.t += dt switch { case backJustPressed(): g.SetScene(NewMapScene(g)) return nil case confirmJustPressed(): e.phase++ e.t = 0 } if e.phase == 0 && e.t > endingFlightSec { e.phase, e.t = 1, 0 } if e.phase == 2 && e.t*endingCreditsPxS > float64(len(e.lines)*LineH+ScreenH) { e.phase++ // eftertexterna har rullat förbi } if e.phase > 2 { g.SetScene(NewMapScene(g)) } return nil } func (e *EndingScene) Draw(g *Game, screen *ebiten.Image) { a := g.Assets e.drawSky(screen) switch e.phase { case 0: e.drawFlight(g, screen) a.DrawBig(screen, a.T("ending.title"), 24, 20, colText) a.Draw(screen, a.T("ending.sub"), 24, 44, colDim) case 1: e.drawSummary(g, screen) default: e.drawCredits(g, screen) } a.Draw(screen, a.T("ending.hint"), 8, ScreenH-12, colDim) } // drawSky är en skymningshimmel i band, från mörkblått upptill till orange // vid horisonten, med långsamt drivande moln. func (e *EndingScene) drawSky(screen *ebiten.Image) { top, bottom := [3]float64{30, 32, 80}, [3]float64{230, 120, 70} const bands = 12 for i := 0; i < bands; i++ { f := float64(i) / float64(bands-1) col := color.RGBA{uint8(top[0] + (bottom[0]-top[0])*f), uint8(top[1] + (bottom[1]-top[1])*f), uint8(top[2] + (bottom[2]-top[2])*f), 255} vector.DrawFilledRect(screen, 0, float32(i*ScreenH/bands), ScreenW, float32(ScreenH/bands+1), col, false) } cloud := color.RGBA{250, 220, 200, 120} for i := 0; i < 5; i++ { x := math.Mod(float64(i)*73+e.t*(4+float64(i)), ScreenW+60) - 30 y := float64(40 + i*22) vector.DrawFilledRect(screen, float32(x), float32(y), 28, 6, cloud, false) vector.DrawFilledRect(screen, float32(x+6), float32(y-4), 14, 5, cloud, false) } vector.DrawFilledRect(screen, 0, ScreenH-20, ScreenW, 20, color.RGBA{24, 20, 37, 255}, false) // marken } // drawFlight: planet lyfter åt höger medan sällskapet vinkar på marken. func (e *EndingScene) drawFlight(g *Game, screen *ebiten.Image) { a := g.Assets f := min(1, e.t/endingFlightSec) px := -40 + f*(ScreenW+80) py := 120 - f*80 + 3*math.Sin(e.t*2) var op ebiten.DrawImageOptions op.GeoM.Scale(2, 2) op.GeoM.Translate(px, py) screen.DrawImage(a.Plane, &op) for i := range memberTints { var o ebiten.DrawImageOptions t := memberTints[i] o.ColorScale.Scale(t[0], t[1], t[2], 1) o.GeoM.Translate(float64(ScreenW/2-50+i*20), ScreenH-20-TileSize+math.Abs(2*math.Sin(e.t*3+float64(i)))) screen.DrawImage(a.PlayerFrame(sim.Up, true, e.t+float64(i)*0.3), &o) } } // drawSummary är resan i siffror: banor, stjärnor, poäng, ord och souvenirer. func (e *EndingScene) drawSummary(g *Game, screen *ebiten.Image) { a := g.Assets p := g.Progress vector.DrawFilledRect(screen, 16, 14, ScreenW-32, ScreenH-40, colPanel, false) vector.DrawFilledRect(screen, 16, 14, ScreenW-32, 2, colGood, false) a.DrawBig(screen, a.T("ending.summary"), 24, 20, colGood) levels, cleared, score := 0, 0, 0 for _, l := range g.Levels { if l.IsLeg() { continue } levels++ if r := p.Levels[l.ID]; r != nil && r.Cleared { cleared++ score += r.Score } } y := 44 rows := []string{ fmt.Sprintf("%s: %d/%d", a.T("ending.levels"), cleared, levels), fmt.Sprintf("%s: %d/%d", a.T("ending.stars"), p.TotalStars(), 3*levels), fmt.Sprintf("%s: %d", a.T("ending.score"), score), fmt.Sprintf("%s: %d", a.T("ending.words"), g.Know.Count()), } for _, r := range rows { a.Draw(screen, r, 24, y, colText) y += LineH } var items []string for id := range p.Items { if it := g.Catalog.Item(id); it != nil { items = append(items, it.Name.Get(UILang)) } } sort.Strings(items) souvenirs := a.T("ending.none") if len(items) > 0 { souvenirs = strings.Join(items, ", ") } for _, l := range a.wrapLine(plainLine(a.T("ending.items")+": "+souvenirs, colKnown), ScreenW-48) { a.drawLines(screen, []line{l}, 24, y) y += LineH } y += 4 for _, l := range a.wrapLine(plainLine(a.T("ending.story_done"), colWarn), ScreenW-48) { a.drawLines(screen, []line{l}, 24, y) y += LineH } } // drawCredits rullar eftertexterna nerifrån och upp. func (e *EndingScene) drawCredits(g *Game, screen *ebiten.Image) { a := g.Assets y0 := ScreenH - int(e.t*endingCreditsPxS) for i, l := range e.lines { y := y0 + i*LineH if y < -LineH || y > ScreenH { continue } col := colDim if i == 0 || (i > 0 && e.lines[i-1] == "" && i+1 < len(e.lines) && e.lines[i+1] != "") { col = colText // rubriker } a.Draw(screen, l, (ScreenW-a.Width(l))/2, y, col) } }