From 0c7468cc55c125489502f81cffa96dbd9ddb700b Mon Sep 17 00:00:00 2001 From: Denis Ranneft Date: Thu, 2 Apr 2026 00:01:48 +0300 Subject: [PATCH] remove move state --- admin-web/index.html | 1 - backend/cmd/server/main.go | 2 +- backend/internal/game/engine.go | 1 - backend/internal/game/engine_bootstrap.go | 4 +- backend/internal/game/movement.go | 1 - backend/internal/handler/admin.go | 3 - backend/internal/model/hero.go | 1 - backend/internal/storage/hero_store.go | 90 +++---------------- .../migrations/000029_remove_move_state.sql | 2 + 9 files changed, 15 insertions(+), 90 deletions(-) create mode 100644 backend/migrations/000029_remove_move_state.sql diff --git a/admin-web/index.html b/admin-web/index.html index 17f5ad9..237597a 100644 --- a/admin-web/index.html +++ b/admin-web/index.html @@ -1138,7 +1138,6 @@ const live = h.adminLiveMovement; const tp = h.townPause; const rows = []; - rows.push(`
moveState
${e(h.moveState)}
`); if (h.currentTownId != null) rows.push(`
currentTownId
${e(h.currentTownId)}
`); if (h.destinationTownId != null) rows.push(`
destinationTownId
${e(h.destinationTownId)}
`); if (h.restKind) rows.push(`
restKind
${e(h.restKind)}
`); diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go index 8d3e509..be45abf 100644 --- a/backend/cmd/server/main.go +++ b/backend/cmd/server/main.go @@ -187,7 +187,7 @@ func main() { WithRewardStores(gearStore, achievementStore, taskStore). WithDigestStore(digestStore) bootCtx, bootCancel := context.WithTimeout(ctx, 3*time.Minute) - game.BootstrapResidentHeroes(bootCtx, engine, heroStore, bootstrapSim, 500, logger) + game.BootstrapResidentHeroes(bootCtx, engine, heroStore, bootstrapSim, logger) bootCancel() // Start game engine (after resident heroes are registered). diff --git a/backend/internal/game/engine.go b/backend/internal/game/engine.go index 174f91c..b701ac0 100644 --- a/backend/internal/game/engine.go +++ b/backend/internal/game/engine.go @@ -1974,7 +1974,6 @@ func (e *Engine) mergeTownSessionFromRedis(hero *model.Hero) { return } hero.State = model.StateInTown - hero.MoveState = string(model.StateInTown) hero.TownPause = snap.TownPause hero.PositionX = snap.PositionX hero.PositionY = snap.PositionY diff --git a/backend/internal/game/engine_bootstrap.go b/backend/internal/game/engine_bootstrap.go index 93a9fc3..1a3b11e 100644 --- a/backend/internal/game/engine_bootstrap.go +++ b/backend/internal/game/engine_bootstrap.go @@ -16,11 +16,11 @@ import ( // 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, limit int, logger *slog.Logger) { +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, limit) + heroes, err := heroStore.ListHeroesForEngineBootstrap(ctx) if err != nil { if logger != nil { logger.Error("engine bootstrap: list heroes", "error", err) diff --git a/backend/internal/game/movement.go b/backend/internal/game/movement.go index 615613d..d6f7b3c 100644 --- a/backend/internal/game/movement.go +++ b/backend/internal/game/movement.go @@ -1320,7 +1320,6 @@ func (hm *HeroMovement) SyncToHero() { } else { hm.Hero.DestinationTownID = nil } - hm.Hero.MoveState = string(hm.State) hm.Hero.RestKind = model.RestKindNone if hm.State == model.StateResting { if hm.ActiveRestKind != model.RestKindNone { diff --git a/backend/internal/handler/admin.go b/backend/internal/handler/admin.go index cbbd455..0c74160 100644 --- a/backend/internal/handler/admin.go +++ b/backend/internal/handler/admin.go @@ -88,7 +88,6 @@ type adminTownTourLiveJSON struct { // adminLiveMovementJSON exposes in-memory movement timers for the admin UI (online heroes only). type adminLiveMovementJSON struct { Online bool `json:"online"` - MoveState string `json:"moveState,omitempty"` RestUntil *time.Time `json:"restUntil,omitempty"` TownLeaveAt *time.Time `json:"townLeaveAt,omitempty"` NextTownNPCRollAt *time.Time `json:"nextTownNPCRollAt,omitempty"` @@ -164,7 +163,6 @@ func buildAdminLiveMovementSnap(hm *game.HeroMovement) *adminLiveMovementJSON { } s := &adminLiveMovementJSON{ Online: true, - MoveState: string(hm.State), } if !hm.RestUntil.IsZero() { t := hm.RestUntil @@ -3126,7 +3124,6 @@ func applyNewPlayerHeroDefaults(hero *model.Hero) { hero.ExcursionPhase = model.ExcursionNone hero.RestKind = model.RestKindNone hero.TownPause = nil - hero.MoveState = string(model.StateWalking) } // resetHeroToLevel1 restores a hero to fresh level 1 defaults, diff --git a/backend/internal/model/hero.go b/backend/internal/model/hero.go index 56bb2ec..8fea9ba 100644 --- a/backend/internal/model/hero.go +++ b/backend/internal/model/hero.go @@ -63,7 +63,6 @@ type Hero struct { // Movement state (persisted to DB for reconnect recovery). CurrentTownID *int64 `json:"currentTownId,omitempty"` DestinationTownID *int64 `json:"destinationTownId,omitempty"` - MoveState string `json:"moveState"` RestKind RestKind `json:"restKind,omitempty"` // ExcursionPhase is set when a mini-adventure session is active (out/wild/return); empty otherwise. ExcursionPhase ExcursionPhase `json:"excursionPhase,omitempty"` diff --git a/backend/internal/storage/hero_store.go b/backend/internal/storage/hero_store.go index b4cf1b4..2d711bd 100644 --- a/backend/internal/storage/hero_store.go +++ b/backend/internal/storage/hero_store.go @@ -30,7 +30,7 @@ const heroSelectQuery = ` h.buff_free_charges_remaining, h.buff_quota_period_end, h.buff_charges, h.position_x, h.position_y, h.potions, h.total_kills, h.elite_kills, h.total_deaths, h.kills_since_death, h.legendary_drops, - h.current_town_id, h.destination_town_id, h.move_state, h.town_pause, + h.current_town_id, h.destination_town_id, h.town_pause, h.last_online_at, h.changelog_ack_version, h.ws_disconnected_at, h.created_at, h.updated_at @@ -266,10 +266,6 @@ func (s *HeroStore) insertNewHeroRow(ctx context.Context, hero *model.Hero) erro hero.CreatedAt = now hero.UpdatedAt = now - if hero.MoveState == "" { - hero.MoveState = string(model.StateWalking) - } - buffChargesJSON := marshalBuffCharges(hero.BuffCharges) query := ` @@ -285,7 +281,7 @@ func (s *HeroStore) insertNewHeroRow(ctx context.Context, hero *model.Hero) erro total_kills, elite_kills, total_deaths, kills_since_death, legendary_drops, last_online_at, created_at, updated_at, - current_town_id, destination_town_id, move_state + current_town_id, destination_town_id ) VALUES ( $1, $2, $3, $4, $5, $6, $7, @@ -298,7 +294,7 @@ func (s *HeroStore) insertNewHeroRow(ctx context.Context, hero *model.Hero) erro $25, $26, $27, $28, $29, $30, $31, $32, - $33, $34, $35 + $33, $34 ) RETURNING id ` @@ -314,7 +310,7 @@ func (s *HeroStore) insertNewHeroRow(ctx context.Context, hero *model.Hero) erro hero.TotalKills, hero.EliteKills, hero.TotalDeaths, hero.KillsSinceDeath, hero.LegendaryDrops, hero.LastOnlineAt, hero.CreatedAt, hero.UpdatedAt, - hero.CurrentTownID, hero.DestinationTownID, hero.MoveState, + hero.CurrentTownID, hero.DestinationTownID, ).Scan(&hero.ID) if err != nil { return fmt.Errorf("insert hero: %w", err) @@ -425,7 +421,6 @@ func (s *HeroStore) CreateHeroWithSpawn(ctx context.Context, telegramID int64, n PositionY: by, CurrentTownID: &birth, DestinationTownID: &dest, - MoveState: string(model.StateWalking), BuffFreeChargesRemaining: model.FreeBuffActivationsPerPeriodRuntime(), } @@ -572,9 +567,8 @@ func (s *HeroStore) Save(ctx context.Context, hero *model.Hero) error { updated_at = $29, destination_town_id = $30, current_town_id = $31, - move_state = $32, - town_pause = $33 - WHERE id = $34 + town_pause = $32 + WHERE id = $33 ` townPauseJSON := marshalTownPause(hero.TownPause) @@ -593,7 +587,6 @@ func (s *HeroStore) Save(ctx context.Context, hero *model.Hero) error { hero.UpdatedAt, hero.DestinationTownID, hero.CurrentTownID, - hero.MoveState, townPauseJSON, hero.ID, ) @@ -661,80 +654,17 @@ func (s *HeroStore) ClearWsDisconnectedAt(ctx context.Context, heroID int64) err return nil } -// ListOfflineHeroes returns heroes that need catch-up: walking heroes stale on the map, -// or heroes resting / in town whose DB row has not been updated recently (offline town timers). -// Heroes with an active WebSocket session are filtered out by the offline simulator (skipIfLive). -func (s *HeroStore) ListOfflineHeroes(ctx context.Context, offlineThreshold time.Duration, limit int) ([]*model.Hero, error) { - if limit <= 0 { - limit = 100 - } - if limit > 500 { - limit = 500 - } - - cutoff := time.Now().Add(-offlineThreshold) - - query := heroSelectQuery + ` - WHERE h.hp > 0 AND h.updated_at < $1 - AND ( - (h.state = 'walking' - AND (h.move_state IS NULL OR h.move_state NOT IN ('in_town', 'resting'))) - OR h.state IN ('resting', 'in_town') - ) - ORDER BY h.updated_at ASC - LIMIT $2 - ` - - rows, err := s.pool.Query(ctx, query, cutoff, limit) - if err != nil { - return nil, fmt.Errorf("list offline heroes: %w", err) - } - defer rows.Close() - - var heroes []*model.Hero - for rows.Next() { - h, err := scanHeroFromRows(rows) - if err != nil { - return nil, fmt.Errorf("list offline heroes scan: %w", err) - } - heroes = append(heroes, h) - } - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("list offline heroes rows: %w", err) - } - for _, h := range heroes { - if err := s.loadHeroGear(ctx, h); err != nil { - return nil, fmt.Errorf("list offline heroes load gear: %w", err) - } - if err := s.loadHeroInventory(ctx, h); err != nil { - return nil, fmt.Errorf("list offline heroes load inventory: %w", err) - } - } - return heroes, nil -} - // ListHeroesForEngineBootstrap returns heroes that should be loaded into the game engine after a cold start: // session ended (ws_disconnected_at set) and simulatable world state. Limit caps memory use. -func (s *HeroStore) ListHeroesForEngineBootstrap(ctx context.Context, limit int) ([]*model.Hero, error) { - if limit <= 0 { - limit = 500 - } - if limit > 2000 { - limit = 2000 - } +func (s *HeroStore) ListHeroesForEngineBootstrap(ctx context.Context) ([]*model.Hero, error) { query := heroSelectQuery + ` WHERE h.hp > 0 AND h.ws_disconnected_at IS NOT NULL - AND ( - (h.state = 'walking' - AND (h.move_state IS NULL OR h.move_state NOT IN ('in_town', 'resting'))) - OR h.state IN ('resting', 'in_town', 'fighting') - ) ORDER BY h.updated_at ASC LIMIT $1 ` - rows, err := s.pool.Query(ctx, query, limit) + rows, err := s.pool.Query(ctx, query) if err != nil { return nil, fmt.Errorf("list heroes for engine bootstrap: %w", err) } @@ -780,7 +710,7 @@ func scanHeroFromRows(rows pgx.Rows) (*model.Hero, error) { &h.BuffFreeChargesRemaining, &h.BuffQuotaPeriodEnd, &buffChargesRaw, &h.PositionX, &h.PositionY, &h.Potions, &h.TotalKills, &h.EliteKills, &h.TotalDeaths, &h.KillsSinceDeath, &h.LegendaryDrops, - &h.CurrentTownID, &h.DestinationTownID, &h.MoveState, &townPauseRaw, + &h.CurrentTownID, &h.DestinationTownID, &townPauseRaw, &h.LastOnlineAt, &h.ChangelogAckVersion, &h.WsDisconnectedAt, &h.CreatedAt, &h.UpdatedAt, @@ -815,7 +745,7 @@ func scanHeroRow(row pgx.Row) (*model.Hero, error) { &h.BuffFreeChargesRemaining, &h.BuffQuotaPeriodEnd, &buffChargesRaw, &h.PositionX, &h.PositionY, &h.Potions, &h.TotalKills, &h.EliteKills, &h.TotalDeaths, &h.KillsSinceDeath, &h.LegendaryDrops, - &h.CurrentTownID, &h.DestinationTownID, &h.MoveState, &townPauseRaw, + &h.CurrentTownID, &h.DestinationTownID, &townPauseRaw, &h.LastOnlineAt, &h.ChangelogAckVersion, &h.WsDisconnectedAt, &h.CreatedAt, &h.UpdatedAt, diff --git a/backend/migrations/000029_remove_move_state.sql b/backend/migrations/000029_remove_move_state.sql new file mode 100644 index 0000000..076211c --- /dev/null +++ b/backend/migrations/000029_remove_move_state.sql @@ -0,0 +1,2 @@ +ALTER TABLE public.heroes + DROP COLUMN IF EXISTS move_state; \ No newline at end of file