M9b: folk, filer, reklam, sorl och vagnval
All checks were successful
release / build-release (push) Successful in 1m33s
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:
144
internal/sim/npc.go
Normal file
144
internal/sim/npc.go
Normal file
@@ -0,0 +1,144 @@
|
||||
package sim
|
||||
|
||||
import (
|
||||
"math/rand/v2"
|
||||
|
||||
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/level"
|
||||
"gitea.brasse-pc.eu/brasse/metro-puzzle-jp/internal/rng"
|
||||
)
|
||||
|
||||
// NPC är en passagerare som rör sig i stationen: vandrar mellan platser,
|
||||
// eller har klivit av ett tåg och är på väg till utgången. NPC:er tar plats
|
||||
// i rutnätet – spelaren kan inte gå in i dem – men går själva igenom
|
||||
// varandra och spelaren, så att ingen kan låsa fast någon annan.
|
||||
type NPC struct {
|
||||
Mover *Mover
|
||||
Target level.Point
|
||||
Kind NPCKind
|
||||
Pause float64 // sekunder kvar att stå still
|
||||
path []level.Point
|
||||
Done bool // på väg ut och framme: tas bort
|
||||
}
|
||||
|
||||
// NPCKind är vad passageraren håller på med.
|
||||
type NPCKind int
|
||||
|
||||
// Slag av passagerare.
|
||||
const (
|
||||
Wander NPCKind = iota // går mellan kiosker, toaletter, skyltar och perronger
|
||||
Exit // kom med tåget, går mot utgången (startrutan)
|
||||
)
|
||||
|
||||
// Folk: antal per trängselnivå och fart.
|
||||
const (
|
||||
NPCPerCrowd = 2 // vandrare per crowd-steg vid banstart
|
||||
NPCAlightBase = 1 // extra avstigande per tåg (+ crowd)
|
||||
NPCSpeed = 3.5 // rutor/s
|
||||
NPCPauseMinSec = 1.0
|
||||
NPCPauseMaxSec = 3.0
|
||||
)
|
||||
|
||||
// npcTargets är rutor som folk vill till.
|
||||
func npcTargets(l *level.Level) []level.Point {
|
||||
var out []level.Point
|
||||
for _, t := range []level.Tile{level.Kiosk, level.Toilet, level.Sign, level.TicketMachine, level.Platform, level.WrongPlatform, level.Spawn} {
|
||||
out = append(out, l.Find(t)...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// spawnNPCs skapar banans vandrare på slumpade golvrutor (seedat).
|
||||
func spawnNPCs(l *level.Level, n int, r *rand.Rand) []*NPC {
|
||||
var floorTiles []level.Point
|
||||
for _, t := range []level.Tile{level.Floor, level.LaneLeft, level.LaneRight} {
|
||||
floorTiles = append(floorTiles, l.Find(t)...)
|
||||
}
|
||||
targets := npcTargets(l)
|
||||
var out []*NPC
|
||||
for i := 0; i < n && len(floorTiles) > 0 && len(targets) > 0; i++ {
|
||||
start := floorTiles[r.IntN(len(floorTiles))]
|
||||
npc := &NPC{Mover: NewMover(start, NPCSpeed), Kind: Wander, Target: targets[r.IntN(len(targets))]}
|
||||
out = append(out, npc)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// step för en NPC framåt: följer sin väg, pausar vid målet och väljer nytt.
|
||||
func (n *NPC) step(l *level.Level, dt float64, r *rand.Rand) {
|
||||
if n.Done {
|
||||
return
|
||||
}
|
||||
if n.Pause > 0 {
|
||||
n.Pause -= dt
|
||||
return
|
||||
}
|
||||
if !n.Mover.Moving() && n.Mover.Tile == n.Target {
|
||||
n.arrived(l, r)
|
||||
return
|
||||
}
|
||||
if len(n.path) < 2 || n.path[0] != n.Mover.Tile {
|
||||
n.path = l.Path(n.Mover.Tile, n.Target, l.CanEnter)
|
||||
}
|
||||
if len(n.path) < 2 {
|
||||
n.arrived(l, r) // framme eller oåtkomligt mål: välj nytt
|
||||
return
|
||||
}
|
||||
next := n.path[1]
|
||||
if n.Mover.Tile == next || (next.Z != n.Mover.Tile.Z) {
|
||||
n.path = n.path[1:]
|
||||
return
|
||||
}
|
||||
want := Right
|
||||
switch {
|
||||
case next.X < n.Mover.Tile.X:
|
||||
want = Left
|
||||
case next.Y > n.Mover.Tile.Y:
|
||||
want = Down
|
||||
case next.Y < n.Mover.Tile.Y:
|
||||
want = Up
|
||||
}
|
||||
if n.Mover.Moving() {
|
||||
want = None
|
||||
if n.Mover.Target == next {
|
||||
want = n.Mover.Facing
|
||||
}
|
||||
}
|
||||
before := n.Mover.Tile
|
||||
// Bara nästa ruta på vägen får påbörjas: annars fortsätter resten av
|
||||
// bildrutans rörelse rakt fram förbi hörn och mål, och NPC:n pendlar
|
||||
// kring rutor den aldrig stannar exakt på.
|
||||
onlyNext := func(p level.Point) bool { return p == next && l.CanEnter(p) }
|
||||
n.Mover.Step(dt, want, onlyNext)
|
||||
if n.Mover.Tile != before {
|
||||
switch l.At(n.Mover.Tile) { // trappor flyttar även folk
|
||||
case level.StairsUp:
|
||||
n.Mover.Tile = n.Mover.Tile.Up()
|
||||
n.Mover.Target = n.Mover.Tile
|
||||
case level.StairsDown:
|
||||
n.Mover.Tile = n.Mover.Tile.Down()
|
||||
n.Mover.Target = n.Mover.Tile
|
||||
}
|
||||
n.path = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NPC) arrived(l *level.Level, r *rand.Rand) {
|
||||
if n.Kind == Exit {
|
||||
n.Done = true
|
||||
return
|
||||
}
|
||||
targets := npcTargets(l)
|
||||
if len(targets) > 0 {
|
||||
n.Target = targets[r.IntN(len(targets))]
|
||||
}
|
||||
n.Pause = NPCPauseMinSec + (NPCPauseMaxSec-NPCPauseMinSec)*r.Float64()
|
||||
n.path = nil
|
||||
}
|
||||
|
||||
// Occupies rapporterar om NPC:n står på eller är på väg in i rutan.
|
||||
func (n *NPC) Occupies(p level.Point) bool {
|
||||
return !n.Done && (n.Mover.Tile == p || (n.Mover.Moving() && n.Mover.Target == p))
|
||||
}
|
||||
|
||||
// newNPCRand ger slumpen för folket.
|
||||
func newNPCRand(seed rng.Seed) *rand.Rand { return seed.Derive("npc").New() }
|
||||
Reference in New Issue
Block a user