summaryrefslogtreecommitdiff
path: root/src/LivingVillage.Desktop/SampleScript.fs
diff options
context:
space:
mode:
Diffstat (limited to 'src/LivingVillage.Desktop/SampleScript.fs')
-rw-r--r--src/LivingVillage.Desktop/SampleScript.fs161
1 files changed, 161 insertions, 0 deletions
diff --git a/src/LivingVillage.Desktop/SampleScript.fs b/src/LivingVillage.Desktop/SampleScript.fs
new file mode 100644
index 0000000..c072161
--- /dev/null
+++ b/src/LivingVillage.Desktop/SampleScript.fs
@@ -0,0 +1,161 @@
+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 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 -> 120
+ | 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 } ->
+ { advance ReadDialogueOptions state with DialogueTarget = Some target }, PushM5 Interact
+ | _ ->
+ match nearestChatableNpc world with
+ | Some npc -> state, MoveTo npc.Pos
+ | None -> state, NoAction
+ | ReadDialogueOptions ->
+ if view.Panel = DialoguePanel && view.Menu.IsSome then
+ state, PushM5 Intent1
+ 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