1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
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
|