You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
83 lines
2.3 KiB
Go
83 lines
2.3 KiB
Go
// Resident hero policy (engine memory):
|
|
// - After the last WebSocket disconnect, the hero stays in Engine.movements; the world keeps ticking.
|
|
// - Cold start: ListHeroesForEngineBootstrap loads rows with ws_disconnected_at set (cap 500 default in main).
|
|
// - Full hero row is saved every offlineDisconnectedFullSaveInterval while heroSubscriber reports false.
|
|
package game
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"github.com/denisovdennis/autohero/internal/model"
|
|
"github.com/denisovdennis/autohero/internal/storage"
|
|
)
|
|
|
|
// BootstrapResidentHeroes loads heroes whose WebSocket session had ended before this process started,
|
|
// catches up wall time using the same batch path as server-downtime recovery, then registers them
|
|
// in the engine so movement and combat continue without a live subscriber.
|
|
func BootstrapResidentHeroes(ctx context.Context, e *Engine, heroStore *storage.HeroStore, sim *OfflineSimulator, logger *slog.Logger) {
|
|
if e == nil || heroStore == nil || sim == nil {
|
|
return
|
|
}
|
|
heroes, err := heroStore.ListHeroesForEngineBootstrap(ctx)
|
|
if err != nil {
|
|
if logger != nil {
|
|
logger.Error("engine bootstrap: list heroes", "error", err)
|
|
}
|
|
return
|
|
}
|
|
now := time.Now()
|
|
for _, h := range heroes {
|
|
if h == nil {
|
|
continue
|
|
}
|
|
e.mu.Lock()
|
|
_, already := e.movements[h.ID]
|
|
rg := e.roadGraph
|
|
e.mu.Unlock()
|
|
if already || rg == nil {
|
|
continue
|
|
}
|
|
|
|
e.mergeTownSessionFromRedis(h)
|
|
|
|
if err := sim.SimulateHeroAt(ctx, h, now, true); err != nil {
|
|
if logger != nil {
|
|
logger.Error("engine bootstrap: catch-up sim", "hero_id", h.ID, "error", err)
|
|
}
|
|
continue
|
|
}
|
|
|
|
e.mu.Lock()
|
|
if e.roadGraph == nil {
|
|
e.mu.Unlock()
|
|
return
|
|
}
|
|
if _, taken := e.movements[h.ID]; taken {
|
|
e.mu.Unlock()
|
|
continue
|
|
}
|
|
hm := NewHeroMovement(h, e.roadGraph, now)
|
|
e.movements[h.ID] = hm
|
|
hm.MarkTownPausePersisted(hm.townPausePersistSignature())
|
|
hm.SyncToHero()
|
|
if hm.State == model.StateFighting {
|
|
if _, exists := e.combats[h.ID]; !exists {
|
|
en := PickEnemyForHero(h)
|
|
if en.Slug != "" {
|
|
e.startCombatLocked(hm.Hero, &en)
|
|
} else {
|
|
hm.State = model.StateWalking
|
|
hm.Hero.State = model.StateWalking
|
|
}
|
|
}
|
|
}
|
|
e.mu.Unlock()
|
|
|
|
if logger != nil {
|
|
logger.Info("engine bootstrap: resident hero registered", "hero_id", h.ID)
|
|
}
|
|
}
|
|
}
|