|
|
|
@ -30,7 +30,7 @@ const heroSelectQuery = `
|
|
|
|
h.buff_free_charges_remaining, h.buff_quota_period_end, h.buff_charges,
|
|
|
|
h.buff_free_charges_remaining, h.buff_quota_period_end, h.buff_charges,
|
|
|
|
h.position_x, h.position_y, h.potions,
|
|
|
|
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.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.last_online_at, h.changelog_ack_version,
|
|
|
|
h.ws_disconnected_at,
|
|
|
|
h.ws_disconnected_at,
|
|
|
|
h.created_at, h.updated_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.CreatedAt = now
|
|
|
|
hero.UpdatedAt = now
|
|
|
|
hero.UpdatedAt = now
|
|
|
|
|
|
|
|
|
|
|
|
if hero.MoveState == "" {
|
|
|
|
|
|
|
|
hero.MoveState = string(model.StateWalking)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
buffChargesJSON := marshalBuffCharges(hero.BuffCharges)
|
|
|
|
buffChargesJSON := marshalBuffCharges(hero.BuffCharges)
|
|
|
|
|
|
|
|
|
|
|
|
query := `
|
|
|
|
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,
|
|
|
|
total_kills, elite_kills, total_deaths, kills_since_death, legendary_drops,
|
|
|
|
last_online_at,
|
|
|
|
last_online_at,
|
|
|
|
created_at, updated_at,
|
|
|
|
created_at, updated_at,
|
|
|
|
current_town_id, destination_town_id, move_state
|
|
|
|
current_town_id, destination_town_id
|
|
|
|
) VALUES (
|
|
|
|
) VALUES (
|
|
|
|
$1, $2,
|
|
|
|
$1, $2,
|
|
|
|
$3, $4, $5, $6, $7,
|
|
|
|
$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,
|
|
|
|
$25, $26, $27, $28, $29,
|
|
|
|
$30,
|
|
|
|
$30,
|
|
|
|
$31, $32,
|
|
|
|
$31, $32,
|
|
|
|
$33, $34, $35
|
|
|
|
$33, $34
|
|
|
|
) RETURNING id
|
|
|
|
) 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.TotalKills, hero.EliteKills, hero.TotalDeaths, hero.KillsSinceDeath, hero.LegendaryDrops,
|
|
|
|
hero.LastOnlineAt,
|
|
|
|
hero.LastOnlineAt,
|
|
|
|
hero.CreatedAt, hero.UpdatedAt,
|
|
|
|
hero.CreatedAt, hero.UpdatedAt,
|
|
|
|
hero.CurrentTownID, hero.DestinationTownID, hero.MoveState,
|
|
|
|
hero.CurrentTownID, hero.DestinationTownID,
|
|
|
|
).Scan(&hero.ID)
|
|
|
|
).Scan(&hero.ID)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("insert hero: %w", err)
|
|
|
|
return fmt.Errorf("insert hero: %w", err)
|
|
|
|
@ -425,7 +421,6 @@ func (s *HeroStore) CreateHeroWithSpawn(ctx context.Context, telegramID int64, n
|
|
|
|
PositionY: by,
|
|
|
|
PositionY: by,
|
|
|
|
CurrentTownID: &birth,
|
|
|
|
CurrentTownID: &birth,
|
|
|
|
DestinationTownID: &dest,
|
|
|
|
DestinationTownID: &dest,
|
|
|
|
MoveState: string(model.StateWalking),
|
|
|
|
|
|
|
|
BuffFreeChargesRemaining: model.FreeBuffActivationsPerPeriodRuntime(),
|
|
|
|
BuffFreeChargesRemaining: model.FreeBuffActivationsPerPeriodRuntime(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
@ -572,9 +567,8 @@ func (s *HeroStore) Save(ctx context.Context, hero *model.Hero) error {
|
|
|
|
updated_at = $29,
|
|
|
|
updated_at = $29,
|
|
|
|
destination_town_id = $30,
|
|
|
|
destination_town_id = $30,
|
|
|
|
current_town_id = $31,
|
|
|
|
current_town_id = $31,
|
|
|
|
move_state = $32,
|
|
|
|
town_pause = $32
|
|
|
|
town_pause = $33
|
|
|
|
WHERE id = $33
|
|
|
|
WHERE id = $34
|
|
|
|
|
|
|
|
`
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
|
|
townPauseJSON := marshalTownPause(hero.TownPause)
|
|
|
|
townPauseJSON := marshalTownPause(hero.TownPause)
|
|
|
|
@ -593,7 +587,6 @@ func (s *HeroStore) Save(ctx context.Context, hero *model.Hero) error {
|
|
|
|
hero.UpdatedAt,
|
|
|
|
hero.UpdatedAt,
|
|
|
|
hero.DestinationTownID,
|
|
|
|
hero.DestinationTownID,
|
|
|
|
hero.CurrentTownID,
|
|
|
|
hero.CurrentTownID,
|
|
|
|
hero.MoveState,
|
|
|
|
|
|
|
|
townPauseJSON,
|
|
|
|
townPauseJSON,
|
|
|
|
hero.ID,
|
|
|
|
hero.ID,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
@ -661,80 +654,17 @@ func (s *HeroStore) ClearWsDisconnectedAt(ctx context.Context, heroID int64) err
|
|
|
|
return nil
|
|
|
|
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:
|
|
|
|
// 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.
|
|
|
|
// 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) {
|
|
|
|
func (s *HeroStore) ListHeroesForEngineBootstrap(ctx context.Context) ([]*model.Hero, error) {
|
|
|
|
if limit <= 0 {
|
|
|
|
|
|
|
|
limit = 500
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if limit > 2000 {
|
|
|
|
|
|
|
|
limit = 2000
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
query := heroSelectQuery + `
|
|
|
|
query := heroSelectQuery + `
|
|
|
|
WHERE h.hp > 0 AND h.ws_disconnected_at IS NOT NULL
|
|
|
|
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
|
|
|
|
ORDER BY h.updated_at ASC
|
|
|
|
LIMIT $1
|
|
|
|
LIMIT $1
|
|
|
|
`
|
|
|
|
`
|
|
|
|
|
|
|
|
|
|
|
|
rows, err := s.pool.Query(ctx, query, limit)
|
|
|
|
rows, err := s.pool.Query(ctx, query)
|
|
|
|
if err != nil {
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("list heroes for engine bootstrap: %w", err)
|
|
|
|
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.BuffFreeChargesRemaining, &h.BuffQuotaPeriodEnd, &buffChargesRaw,
|
|
|
|
&h.PositionX, &h.PositionY, &h.Potions,
|
|
|
|
&h.PositionX, &h.PositionY, &h.Potions,
|
|
|
|
&h.TotalKills, &h.EliteKills, &h.TotalDeaths, &h.KillsSinceDeath, &h.LegendaryDrops,
|
|
|
|
&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.LastOnlineAt, &h.ChangelogAckVersion,
|
|
|
|
&h.WsDisconnectedAt,
|
|
|
|
&h.WsDisconnectedAt,
|
|
|
|
&h.CreatedAt, &h.UpdatedAt,
|
|
|
|
&h.CreatedAt, &h.UpdatedAt,
|
|
|
|
@ -815,7 +745,7 @@ func scanHeroRow(row pgx.Row) (*model.Hero, error) {
|
|
|
|
&h.BuffFreeChargesRemaining, &h.BuffQuotaPeriodEnd, &buffChargesRaw,
|
|
|
|
&h.BuffFreeChargesRemaining, &h.BuffQuotaPeriodEnd, &buffChargesRaw,
|
|
|
|
&h.PositionX, &h.PositionY, &h.Potions,
|
|
|
|
&h.PositionX, &h.PositionY, &h.Potions,
|
|
|
|
&h.TotalKills, &h.EliteKills, &h.TotalDeaths, &h.KillsSinceDeath, &h.LegendaryDrops,
|
|
|
|
&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.LastOnlineAt, &h.ChangelogAckVersion,
|
|
|
|
&h.WsDisconnectedAt,
|
|
|
|
&h.WsDisconnectedAt,
|
|
|
|
&h.CreatedAt, &h.UpdatedAt,
|
|
|
|
&h.CreatedAt, &h.UpdatedAt,
|
|
|
|
|