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.

135 lines
5.8 KiB
Go

package model
import "time"
// ExcursionPhase tracks where the hero is within a mini-adventure session.
// The lifecycle is: Out → Wild → Return → (back to road, phase cleared).
// For KindTown, Phase is usually ExcursionWild while using attractor movement during wander/rest.
type ExcursionPhase string
const (
ExcursionNone ExcursionPhase = ""
ExcursionOut ExcursionPhase = "out" // moving off-road into the forest
ExcursionWild ExcursionPhase = "wild" // in the wilderness (encounters happen here)
ExcursionReturn ExcursionPhase = "return" // returning to the road (encounters still possible)
)
// ExcursionKind distinguishes roadside rest vs walking adventure vs in-town tour.
type ExcursionKind string
const (
ExcursionKindNone ExcursionKind = ""
ExcursionKindRoadside ExcursionKind = "roadside"
ExcursionKindAdventure ExcursionKind = "adventure"
ExcursionKindTown ExcursionKind = "town"
)
// TownTourPhase is the sub-state machine while ExcursionKind == town (StateInTown).
type TownTourPhase string
const (
TownTourPhaseWander TownTourPhase = "wander"
TownTourPhaseNpcApproach TownTourPhase = "npc_approach"
TownTourPhaseNpcWelcome TownTourPhase = "npc_welcome"
TownTourPhaseNpcService TownTourPhase = "npc_service"
TownTourPhaseRest TownTourPhase = "rest"
)
// ExcursionSession holds the live state of an active mini-adventure (off-road excursion) or town tour.
// When Phase == ExcursionNone the session is inactive and all other fields are zero-valued (except Kind for town cleared on leave).
type ExcursionSession struct {
Kind ExcursionKind
Phase ExcursionPhase
StartedAt time.Time
// OutUntil / WildUntil / ReturnUntil: legacy time-based FSM (ignored when Kind is set).
OutUntil time.Time
WildUntil time.Time
ReturnUntil time.Time
// DepthWorldUnits is used to place forest attractors (perpendicular distance from road spine).
DepthWorldUnits float64
// RoadFreezeWaypoint / RoadFreezeFraction capture road progress at the moment the hero
// left the road, so it can be restored exactly when the excursion ends.
RoadFreezeWaypoint int
RoadFreezeFraction float64
// Attractor-based movement (Kind != ""): hero walks in world space toward AttractorX/Y.
StartX, StartY float64
AttractorX, AttractorY float64
AttractorSet bool
// Adventure-only: wall-time when wandering should end (then return to road).
AdventureEndsAt time.Time
// Adventure / town wander: next time to pick a new wander attractor (wild phase).
WanderNextAt time.Time
// PendingReturnAfterCombat: adventure timer elapsed; wait for combat end then enter return phase.
PendingReturnAfterCombat bool
// --- Town tour (Kind == ExcursionKindTown) ---
TownTourPhase string
// TownTourEndsAt: wall-time when the hero should leave the town (may defer until idle).
TownTourEndsAt time.Time
TownTourNpcID int64
// Stand point near NPC during approach / welcome / service.
TownTourStandX float64
TownTourStandY float64
// TownWelcomeUntil: npc_welcome phase deadline (30s, shifted while dialog open).
TownWelcomeUntil time.Time
// TownServiceUntil: npc_service phase max wall time (4 min, shifted while UI open).
TownServiceUntil time.Time
// TownRestUntil: in-town rest phase end.
TownRestUntil time.Time
TownExitPending bool
// Client has NPCDialog (welcome or service) open — shifts welcome/service deadlines.
TownTourDialogOpen bool
// Client has NPCInteraction panel open — shifts service deadline; with dialog shifts welcome too.
TownTourInteractionOpen bool
}
// Active reports whether an excursion session is in progress.
func (s *ExcursionSession) Active() bool {
if s == nil {
return false
}
if s.Kind == ExcursionKindTown {
return s.TownTourPhase != ""
}
return s.Phase != ExcursionNone
}
// ExcursionPersisted is the JSON-serialisable subset of ExcursionSession stored in the
// heroes.town_pause JSONB column so that reconnect / offline catch-up can resume mid-adventure.
type ExcursionPersisted struct {
Kind string `json:"kind,omitempty"`
Phase string `json:"phase,omitempty"`
StartedAt *time.Time `json:"startedAt,omitempty"`
OutUntil *time.Time `json:"outUntil,omitempty"`
WildUntil *time.Time `json:"wildUntil,omitempty"`
ReturnUntil *time.Time `json:"returnUntil,omitempty"`
DepthWorldUnits float64 `json:"depthWorldUnits,omitempty"`
RoadFreezeWaypoint int `json:"roadFreezeWaypoint,omitempty"`
RoadFreezeFraction float64 `json:"roadFreezeFraction,omitempty"`
StartX float64 `json:"startX,omitempty"`
StartY float64 `json:"startY,omitempty"`
AttractorX float64 `json:"attractorX,omitempty"`
AttractorY float64 `json:"attractorY,omitempty"`
AttractorSet bool `json:"attractorSet,omitempty"`
AdventureEndsAt *time.Time `json:"adventureEndsAt,omitempty"`
WanderNextAt *time.Time `json:"wanderNextAt,omitempty"`
PendingReturnAfterCombat bool `json:"pendingReturnAfterCombat,omitempty"`
TownTourPhase string `json:"townTourPhase,omitempty"`
TownTourEndsAt *time.Time `json:"townTourEndsAt,omitempty"`
TownTourNpcID int64 `json:"townTourNpcId,omitempty"`
TownTourStandX float64 `json:"townTourStandX,omitempty"`
TownTourStandY float64 `json:"townTourStandY,omitempty"`
TownWelcomeUntil *time.Time `json:"townWelcomeUntil,omitempty"`
TownServiceUntil *time.Time `json:"townServiceUntil,omitempty"`
TownRestUntil *time.Time `json:"townRestUntil,omitempty"`
TownExitPending bool `json:"townExitPending,omitempty"`
TownTourDialogOpen bool `json:"townTourDialogOpen,omitempty"`
TownTourInteractionOpen bool `json:"townTourInteractionOpen,omitempty"`
}