|
|
|
@ -61,6 +61,7 @@ type Engine struct {
|
|
|
|
roadGraph *RoadGraph
|
|
|
|
roadGraph *RoadGraph
|
|
|
|
sender MessageSender
|
|
|
|
sender MessageSender
|
|
|
|
heroStore *storage.HeroStore
|
|
|
|
heroStore *storage.HeroStore
|
|
|
|
|
|
|
|
townSession *storage.TownSessionStore
|
|
|
|
questStore *storage.QuestStore
|
|
|
|
questStore *storage.QuestStore
|
|
|
|
incomingCh chan IncomingMessage // client commands
|
|
|
|
incomingCh chan IncomingMessage // client commands
|
|
|
|
mu sync.RWMutex
|
|
|
|
mu sync.RWMutex
|
|
|
|
@ -224,6 +225,13 @@ func (e *Engine) SetHeroStore(hs *storage.HeroStore) {
|
|
|
|
e.heroStore = hs
|
|
|
|
e.heroStore = hs
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// SetTownSessionStore sets the Redis-backed mirror for in-town NPC tour state (reconnect recovery).
|
|
|
|
|
|
|
|
func (e *Engine) SetTownSessionStore(ts *storage.TownSessionStore) {
|
|
|
|
|
|
|
|
e.mu.Lock()
|
|
|
|
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
|
|
|
|
e.townSession = ts
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// SetQuestStore sets the quest store used for visit_town progress on town arrival.
|
|
|
|
// SetQuestStore sets the quest store used for visit_town progress on town arrival.
|
|
|
|
func (e *Engine) SetQuestStore(qs *storage.QuestStore) {
|
|
|
|
func (e *Engine) SetQuestStore(qs *storage.QuestStore) {
|
|
|
|
e.mu.Lock()
|
|
|
|
e.mu.Lock()
|
|
|
|
@ -547,6 +555,11 @@ func (e *Engine) sendError(heroID int64, code, message string) {
|
|
|
|
// RegisterHeroMovement creates a HeroMovement for an online hero and sends initial state.
|
|
|
|
// RegisterHeroMovement creates a HeroMovement for an online hero and sends initial state.
|
|
|
|
// Called when a WS client connects.
|
|
|
|
// Called when a WS client connects.
|
|
|
|
func (e *Engine) RegisterHeroMovement(hero *model.Hero) {
|
|
|
|
func (e *Engine) RegisterHeroMovement(hero *model.Hero) {
|
|
|
|
|
|
|
|
if hero == nil {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
e.mergeTownSessionFromRedis(hero)
|
|
|
|
|
|
|
|
|
|
|
|
e.mu.Lock()
|
|
|
|
e.mu.Lock()
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
defer e.mu.Unlock()
|
|
|
|
|
|
|
|
|
|
|
|
@ -584,6 +597,7 @@ func (e *Engine) RegisterHeroMovement(hero *model.Hero) {
|
|
|
|
|
|
|
|
|
|
|
|
hm := NewHeroMovement(hero, e.roadGraph, now)
|
|
|
|
hm := NewHeroMovement(hero, e.roadGraph, now)
|
|
|
|
e.movements[hero.ID] = hm
|
|
|
|
e.movements[hero.ID] = hm
|
|
|
|
|
|
|
|
hm.MarkTownPausePersisted(hm.townPausePersistSignature())
|
|
|
|
hm.SyncToHero()
|
|
|
|
hm.SyncToHero()
|
|
|
|
|
|
|
|
|
|
|
|
e.logger.Info("hero movement registered",
|
|
|
|
e.logger.Info("hero movement registered",
|
|
|
|
@ -639,6 +653,7 @@ func (e *Engine) HeroSocketDetached(heroID int64, lastConnection bool) {
|
|
|
|
"hero_id", heroID,
|
|
|
|
"hero_id", heroID,
|
|
|
|
"last_connection", lastConnection,
|
|
|
|
"last_connection", lastConnection,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
e.syncTownSessionRedisFromHero(heroID, heroSnap)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -1374,7 +1389,80 @@ func (e *Engine) processMovementTick(now time.Time) {
|
|
|
|
continue
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
hm.MarkTownPausePersisted(sig)
|
|
|
|
hm.MarkTownPausePersisted(sig)
|
|
|
|
|
|
|
|
e.syncTownSessionRedis(heroID, hm)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// mergeTownSessionFromRedis overlays a fresher in-town snapshot when Postgres row is stale (e.g. missed town_pause save).
|
|
|
|
|
|
|
|
func (e *Engine) mergeTownSessionFromRedis(hero *model.Hero) {
|
|
|
|
|
|
|
|
if e.townSession == nil {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
snap, err := e.townSession.Load(ctx, hero.ID)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
|
|
if e.logger != nil {
|
|
|
|
|
|
|
|
e.logger.Warn("town session redis load failed", "hero_id", hero.ID, "error", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if snap == nil || snap.State != model.StateInTown || snap.TownPause == nil {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if snap.CurrentTownID > 0 && hero.CurrentTownID != nil && *hero.CurrentTownID != snap.CurrentTownID {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if snap.SavedAtUnixNano <= hero.UpdatedAt.UnixNano() {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
hero.State = model.StateInTown
|
|
|
|
|
|
|
|
hero.MoveState = string(model.StateInTown)
|
|
|
|
|
|
|
|
hero.TownPause = snap.TownPause
|
|
|
|
|
|
|
|
hero.PositionX = snap.PositionX
|
|
|
|
|
|
|
|
hero.PositionY = snap.PositionY
|
|
|
|
|
|
|
|
if snap.CurrentTownID > 0 {
|
|
|
|
|
|
|
|
tid := snap.CurrentTownID
|
|
|
|
|
|
|
|
if hero.CurrentTownID == nil {
|
|
|
|
|
|
|
|
hero.CurrentTownID = new(int64)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
*hero.CurrentTownID = tid
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (e *Engine) syncTownSessionRedis(heroID int64, hm *HeroMovement) {
|
|
|
|
|
|
|
|
if e.townSession == nil || hm == nil || hm.Hero == nil {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
hm.SyncToHero()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
if hm.State == model.StateInTown {
|
|
|
|
|
|
|
|
if err := e.townSession.Save(ctx, heroID, hm.Hero); err != nil && e.logger != nil {
|
|
|
|
|
|
|
|
e.logger.Warn("town session redis save failed", "hero_id", heroID, "error", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := e.townSession.Delete(ctx, heroID); err != nil && e.logger != nil {
|
|
|
|
|
|
|
|
e.logger.Warn("town session redis delete failed", "hero_id", heroID, "error", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
func (e *Engine) syncTownSessionRedisFromHero(heroID int64, h *model.Hero) {
|
|
|
|
|
|
|
|
if e.townSession == nil || h == nil {
|
|
|
|
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
|
|
|
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
if h.State == model.StateInTown {
|
|
|
|
|
|
|
|
if err := e.townSession.Save(ctx, heroID, h); err != nil && e.logger != nil {
|
|
|
|
|
|
|
|
e.logger.Warn("town session redis save failed", "hero_id", heroID, "error", err)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := e.townSession.Delete(ctx, heroID); err != nil && e.logger != nil {
|
|
|
|
|
|
|
|
e.logger.Warn("town session redis delete failed", "hero_id", heroID, "error", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -1389,6 +1477,7 @@ func (e *Engine) persistHeroAfterTownEnter(h *model.Hero) {
|
|
|
|
e.logger.Error("persist hero after town enter", "hero_id", h.ID, "error", err)
|
|
|
|
e.logger.Error("persist hero after town enter", "hero_id", h.ID, "error", err)
|
|
|
|
return
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
e.syncTownSessionRedisFromHero(h.ID, h)
|
|
|
|
e.applyVisitTownQuestProgress(h)
|
|
|
|
e.applyVisitTownQuestProgress(h)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|