Files
metro-puzzle-jp/internal/game/scene_skills.go
claude 915c684dc2
All checks were successful
release / build-release (push) Successful in 1m46s
M8: progression, items, bankarta och sparfil
- internal/progress: katalog (skills.json 12 noder, items.json 25 items)
  med validering; XP/nivåer (150·n(n+1)/2) och skillpoäng; skillträd med
  krav och kostnad; items per plats med buffar, genvägar (JR Pass, kartor,
  powerbank) och färgton; banresultat och upplåsning (huvudbanor i ordning,
  sidospår efter föräldern); 33 %-regeln som test
- internal/save: versionerad JSON med kunskap, plånbok, debuffar,
  progression och pågående bana + seed; atomisk skrivning; migrering;
  ~/.config/metro-puzzle-jp/save.json resp. %AppData%
- sim: RunOptions får Extra/FogRadius/Landmarks/FreeGates/RevealAll/
  InfiniteBattery; luck påverkar trängselzonen; Fog.RevealAll och extra
  landmärken
- game: bankarta i spelbrädesstil ersätter menyn (⟳ återupptar med sparad
  seed), skillträd (T), garderob (I), butik i kiosken (B), XP/nivå/items/
  upplåsningar på resultatet, spelaren tonas av skins; autospar vid banstart,
  efter banan och vid köp; flaggor -save/-nosave
- level: kind/order/parent/rewards/shop; ny sidobana 002b Kabukicho som
  belönar skinnet Kimono; 001 belönar Salaryman-skinnet; butikssortiment
  på 002/003
- tools/shots: demo-sparfil (build/, aldrig den riktiga), bilder av karta,
  skills, garderob och butik; alla körningar med -nosave
- docs/README/wiki uppdaterade

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-24 11:29:30 +02:00

114 lines
3.1 KiB
Go

package game
import (
"fmt"
"image/color"
"sort"
"strings"
"github.com/hajimehoshi/ebiten/v2"
"github.com/hajimehoshi/ebiten/v2/vector"
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/progress"
)
// SkillsScene är skillträdet: grenar med noder, kostnad i skillpoäng och krav.
type SkillsScene struct {
back Scene
skills []progress.Skill
sel int
top int
msg string
}
const skillRows = 8
// NewSkillsScene listar skills gren för gren.
func NewSkillsScene(g *Game, back Scene) *SkillsScene {
sk := append([]progress.Skill(nil), g.Catalog.Skills...)
sort.SliceStable(sk, func(i, j int) bool {
if sk[i].Branch != sk[j].Branch {
return sk[i].Branch < sk[j].Branch
}
return len(sk[i].Requires) < len(sk[j].Requires)
})
return &SkillsScene{back: back, skills: sk}
}
func (s *SkillsScene) Update(g *Game) error {
if backJustPressed() || skillsJustPressed() {
g.SetScene(s.back)
return nil
}
if n := len(s.skills); n > 0 {
s.sel = (s.sel + menuMove() + n) % n
if s.sel < s.top {
s.top = s.sel
}
if s.sel >= s.top+skillRows {
s.top = s.sel - skillRows + 1
}
if confirmJustPressed() {
sk := s.skills[s.sel]
switch {
case g.Progress.Has(sk.ID):
s.msg = g.Assets.T("skills.already")
case g.Progress.Unlock(g.Catalog, sk.ID):
s.msg = fmt.Sprintf(g.Assets.T("skills.unlocked"), sk.Name.Get(UILang))
g.Save()
case g.Progress.SkillPoints < sk.Cost:
s.msg = fmt.Sprintf(g.Assets.T("skills.need_points"), sk.Cost)
default:
s.msg = g.Assets.T("skills.need_req")
}
}
}
return nil
}
func (s *SkillsScene) Draw(g *Game, screen *ebiten.Image) {
a := g.Assets
p := g.Progress
screen.Fill(color.RGBA{24, 20, 37, 255})
vector.DrawFilledRect(screen, 0, 0, ScreenW, 22, color.RGBA{38, 43, 68, 255}, false)
a.DrawBig(screen, a.T("skills.title"), 8, 5, colText)
hdr := fmt.Sprintf("%s %d %s %d %s %d", a.T("map.skill_points"), p.SkillPoints, a.T("map.level"), p.Level(), a.T("skills.to_next"), progress_XPToNext(p.XP))
a.Draw(screen, hdr, ScreenW-8-a.Width(hdr), 6, colKnown)
y0 := 30
for i := s.top; i < len(s.skills) && i < s.top+skillRows; i++ {
sk := s.skills[i]
y := y0 + (i-s.top)*LineH
if i == s.sel {
vector.DrawFilledRect(screen, 4, float32(y-1), ScreenW-8, LineH, colBar, false)
}
col, status := colDim, strings.Repeat("●", sk.Cost)
switch {
case p.Has(sk.ID):
col, status = colGood, "✓"
case p.CanUnlock(g.Catalog, sk.ID):
col = colKnown
}
a.Draw(screen, sk.Branch, 8, y, colDim)
a.Draw(screen, sk.Name.Get(UILang), 80, y, col)
if len(sk.Requires) > 0 {
req := make([]string, len(sk.Requires))
for k, r := range sk.Requires {
req[k] = g.Catalog.Skill(r).Name.Get(UILang)
}
a.Draw(screen, "← "+strings.Join(req, ", "), 190, y, colDim)
}
a.Draw(screen, status, ScreenW-8-a.Width(status), y, col)
}
if len(s.skills) > 0 {
sel := s.skills[s.sel]
for i, l := range a.wrapLine(plainLine(sel.Desc.Get(UILang), colText), ScreenW-16) {
a.drawLines(screen, []line{l}, 8, 130+i*LineH)
}
}
if s.msg != "" {
a.Draw(screen, s.msg, 8, ScreenH-26, colKnown)
}
a.Draw(screen, a.T("skills.hint"), 8, ScreenH-13, colDim)
}