|
|
|
|
@ -329,6 +329,31 @@ func (e *Engine) RegisterHeroMovement(hero *model.Hero) {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
|
|
|
|
|
// Reconnect while the previous socket is still tearing down: keep live movement so we
|
|
|
|
|
// do not replace (x,y) and route with a stale DB snapshot.
|
|
|
|
|
if existing, ok := e.movements[hero.ID]; ok {
|
|
|
|
|
existing.Hero.RefreshDerivedCombatStats(now)
|
|
|
|
|
e.logger.Info("hero movement reattached (existing session)",
|
|
|
|
|
"hero_id", hero.ID,
|
|
|
|
|
"state", existing.State,
|
|
|
|
|
"pos_x", existing.CurrentX,
|
|
|
|
|
"pos_y", existing.CurrentY,
|
|
|
|
|
)
|
|
|
|
|
if e.sender != nil {
|
|
|
|
|
e.sender.SendToHero(hero.ID, "hero_state", existing.Hero)
|
|
|
|
|
if route := existing.RoutePayload(); route != nil {
|
|
|
|
|
e.sender.SendToHero(hero.ID, "route_assigned", route)
|
|
|
|
|
}
|
|
|
|
|
if cs, ok := e.combats[hero.ID]; ok {
|
|
|
|
|
e.sender.SendToHero(hero.ID, "combat_start", model.CombatStartPayload{
|
|
|
|
|
Enemy: enemyToInfo(&cs.Enemy),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hm := NewHeroMovement(hero, e.roadGraph, now)
|
|
|
|
|
e.movements[hero.ID] = hm
|
|
|
|
|
|
|
|
|
|
@ -357,24 +382,33 @@ func (e *Engine) RegisterHeroMovement(hero *model.Hero) {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// UnregisterHeroMovement removes movement state and persists hero to DB.
|
|
|
|
|
// Called when a WS client disconnects.
|
|
|
|
|
func (e *Engine) UnregisterHeroMovement(heroID int64) {
|
|
|
|
|
// HeroSocketDetached persists hero state on every WS disconnect and removes in-memory
|
|
|
|
|
// movement only when lastConnection is true (no other tabs/sockets for this hero).
|
|
|
|
|
func (e *Engine) HeroSocketDetached(heroID int64, lastConnection bool) {
|
|
|
|
|
e.mu.Lock()
|
|
|
|
|
hm, ok := e.movements[heroID]
|
|
|
|
|
if ok {
|
|
|
|
|
hm.SyncToHero()
|
|
|
|
|
delete(e.movements, heroID)
|
|
|
|
|
if lastConnection {
|
|
|
|
|
delete(e.movements, heroID)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var heroSnap *model.Hero
|
|
|
|
|
if ok {
|
|
|
|
|
heroSnap = hm.Hero
|
|
|
|
|
}
|
|
|
|
|
e.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
if ok && e.heroStore != nil {
|
|
|
|
|
if ok && e.heroStore != nil && heroSnap != nil {
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
|
|
|
defer cancel()
|
|
|
|
|
if err := e.heroStore.Save(ctx, hm.Hero); err != nil {
|
|
|
|
|
e.logger.Error("failed to save hero on disconnect", "hero_id", heroID, "error", err)
|
|
|
|
|
if err := e.heroStore.Save(ctx, heroSnap); err != nil {
|
|
|
|
|
e.logger.Error("failed to save hero on ws disconnect", "hero_id", heroID, "error", err)
|
|
|
|
|
} else {
|
|
|
|
|
e.logger.Info("hero state persisted on disconnect", "hero_id", heroID)
|
|
|
|
|
e.logger.Info("hero state persisted on ws disconnect",
|
|
|
|
|
"hero_id", heroID,
|
|
|
|
|
"last_connection", lastConnection,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|