namespace LivingVillage.Desktop open System open LivingVillage.Kernel open LivingVillage.Kernel.Sim open LivingVillage.Desktop.VillagePresentation module SampleScript = type SampleStage = | ApproachDoor | InspectInterior | LeaveDoorstep | ApproachNpc | ReadDialogueOptions | CloseDialogue | WalkAway | SampleComplete type SampleAction = | NoAction | MoveTo of Vec2 | PushM5 of M5Command type SampleState = { Stage: SampleStage StageFrames: int TotalFrames: int DialogueTarget: NpcId option TimedOut: bool } let initial : SampleState = { Stage = ApproachDoor StageFrames = 0 TotalFrames = 0 DialogueTarget = None TimedOut = false } let private tileCenter (tile: TilePosition) : Vec2 = { X = float32 (tile.X * Sim.tilePixels + Sim.tilePixels / 2) Y = float32 (tile.Y * Sim.tilePixels + Sim.tilePixels / 2) } let doorWorldCenter : Vec2 = let home = (createScene ()).Homes |> List.head tileCenter (worldTileForSceneTile home.Door) let private leaveDoorstepCenter : Vec2 = tileCenter (worldTileForSceneTile { X = 3; Y = 5 }) let private walkAwayCenter : Vec2 = tileCenter (worldTileForSceneTile { X = 6; Y = 7 }) let private arriveRadius = 12.0f let private inspectFrames = 150 let private promptHoldFrames = 60 let private menuHoldFrames = 60 let private dialogueHoldFrames = 120 let private totalFrameLimit = 20000 let private distanceSquared (a: Vec2) (b: Vec2) : float32 = let dx = b.X - a.X let dy = b.Y - a.Y dx * dx + dy * dy let private timeoutFor (stage: SampleStage) : int = match stage with | ApproachDoor -> 1800 | InspectInterior -> 600 | LeaveDoorstep -> 1800 | ApproachNpc -> 5400 | ReadDialogueOptions -> 600 | CloseDialogue -> 300 | WalkAway -> 3600 | SampleComplete -> Int32.MaxValue let private advance (stage: SampleStage) (state: SampleState) : SampleState = { state with Stage = stage; StageFrames = 0 } let private nearestChatableNpc (world: World) : Npc option = world.Npcs |> Array.filter chatableForChat |> Array.sortBy (fun npc -> distanceSquared world.Avatar.Pos npc.Pos, npc.Id) |> Array.tryHead let private timedOut (state: SampleState) : SampleState * SampleAction = { state with Stage = SampleComplete; TimedOut = true }, NoAction let step (world: World) (view: M5View) (state: SampleState) : SampleState * SampleAction = let state = { state with StageFrames = state.StageFrames + 1; TotalFrames = state.TotalFrames + 1 } if state.TotalFrames > totalFrameLimit then timedOut state elif state.StageFrames >= timeoutFor state.Stage then timedOut state else match state.Stage with | SampleComplete -> state, NoAction | ApproachDoor -> match view.HomeMode with | Inside _ -> advance InspectInterior state, NoAction | Outside -> if distanceSquared world.Avatar.Pos doorWorldCenter <= arriveRadius * arriveRadius then state, PushM5 Interact else state, MoveTo doorWorldCenter | InspectInterior -> match view.HomeMode with | Outside -> advance LeaveDoorstep state, NoAction | Inside _ -> if state.StageFrames >= inspectFrames then state, PushM5 Interact else state, NoAction | LeaveDoorstep -> match view.HomeMode with | Inside _ -> state, PushM5 Interact | Outside -> if distanceSquared world.Avatar.Pos leaveDoorstepCenter <= arriveRadius * arriveRadius then advance ApproachNpc state, NoAction else state, MoveTo leaveDoorstepCenter | ApproachNpc -> match M5Interaction.resolveAction world Outside with | Some { Action = TalkTo target } -> if state.StageFrames >= promptHoldFrames then { advance ReadDialogueOptions state with DialogueTarget = Some target }, PushM5 Interact else state, NoAction | _ -> match nearestChatableNpc world with | Some npc -> state, MoveTo npc.Pos | None -> state, NoAction | ReadDialogueOptions -> if view.Panel = DialoguePanel && view.Menu.IsSome then if state.StageFrames >= menuHoldFrames then state, PushM5 Intent1 else state, NoAction else advance CloseDialogue state, NoAction | CloseDialogue -> if state.StageFrames >= dialogueHoldFrames then advance WalkAway state, PushM5 ClosePanel else state, NoAction | WalkAway -> if distanceSquared world.Avatar.Pos walkAwayCenter <= arriveRadius * arriveRadius then advance SampleComplete state, NoAction else state, MoveTo walkAwayCenter let runFrame (world: World) (view: M5View) (state: SampleState) : World * M5View * SampleState = let nextState, action = step world view state let nextWorld, nextView = match action with | NoAction -> world, view | PushM5 command -> M5Interaction.apply command world view | MoveTo target -> let dx = target.X - world.Avatar.Pos.X let dy = target.Y - world.Avatar.Pos.Y let length = sqrt (dx * dx + dy * dy) let input = if length > 0.001f then { MoveX = dx / length; MoveY = dy / length } else { MoveX = 0.0f; MoveY = 0.0f } let movedWorld = Sim.step { Input = input } world movedWorld, view nextWorld, M5Interaction.refreshPrompt nextWorld nextView, nextState