M12: ljud, inställningar och release v0.1.0
All checks were successful
release / build-release (push) Successful in 1m35s

tools/gensound syntetiserar 21 ljud: fyra stationsmelodier i klockklang
(en per station via melody-fältet eller banans id), ankomstjingel,
ding-dong vid inrullning, dörrsignal vid avgång, spärrens pip och
nekande, kiosk, toalett, lärt ord, bekräfta/varning, byte, personal,
vinst, förlust, flygplan, sorl och menymusik. sfx.go spelar effekter
utifrån vad som hänt i sim.Run sedan förra bildrutan. Ljudlagret har
huvudvolym × effekter/musik och byter inte slinga i onödan.
Inställningar (S på bankartan, sparas): volymer, språk sv/en som laddas
om direkt, fönsterskala, fullskärm (F11 överallt) och tangentöversikt.
Fönsterikon från genart. CHANGELOG.md. Tester för ljudfiler, melodival
och inställningar. Docs, wiki och skärmdumpar uppdaterade.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 08:31:38 +01:00
parent 36a45563ec
commit 86f8130735
47 changed files with 800 additions and 107 deletions

66
internal/game/sfx.go Normal file
View File

@@ -0,0 +1,66 @@
package game
import "gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/sim"
// sfxState är vad ljudlagret senast såg av omgången; skillnader mot nu ger
// effekter (spärr, kiosk, toalett, lärt ord, byte, personal, vinst/förlust).
type sfxState struct {
init bool
gatePasses int
served int
staffCalls int
deniedAt float64
noExitAt float64
learnedAt float64
transferAt float64
flashAt float64
facility bool
mini bool
outcome sim.Outcome
}
// updateSfx spelar effekter för det som hänt sedan förra bildrutan.
func (s *LevelScene) updateSfx(g *Game) {
r := s.run
x := &s.sfx
if x.init {
switch {
case r.GatePasses > x.gatePasses:
g.Audio.Play(SoundGateBeep, 0.7)
case r.DeniedAt >= 0 && r.DeniedAt != x.deniedAt, r.NoExitAt >= 0 && r.NoExitAt != x.noExitAt:
g.Audio.Play(SoundGateDeny, 0.7)
}
if r.Served > x.served {
if x.facility && !r.Facility.Active {
g.Audio.Play(SoundToilet, 0.6)
} else {
g.Audio.Play(SoundKiosk, 0.7)
}
}
if r.LearnedAt >= 0 && r.LearnedAt != x.learnedAt {
g.Audio.Play(SoundLearn, 0.8)
}
if r.TransferAt >= 0 && r.TransferAt != x.transferAt {
g.Audio.Play(SoundTransfer, 0.8)
}
if r.StaffCalls > x.staffCalls {
g.Audio.Play(SoundStaff, 0.7)
}
if r.Mini != nil && !x.mini {
g.Audio.Play(SoundConfirm, 0.5)
}
if r.FlashAt >= 0 && r.FlashAt != x.flashAt && r.FlashKey != sim.FlashTransfer && r.FlashKey != sim.FlashStaff {
g.Audio.Play(SoundCancel, 0.6)
}
if r.Outcome != x.outcome {
switch r.Outcome {
case sim.Won:
g.Audio.Play(SoundWin, 0.9)
case sim.Lost:
g.Audio.Play(SoundLose, 0.9)
}
}
}
*x = sfxState{init: true, gatePasses: r.GatePasses, served: r.Served, staffCalls: r.StaffCalls, deniedAt: r.DeniedAt, noExitAt: r.NoExitAt,
learnedAt: r.LearnedAt, transferAt: r.TransferAt, flashAt: r.FlashAt, facility: r.Facility.Active, mini: r.Mini != nil, outcome: r.Outcome}
}