๐Ÿ“ฆ sleepyfran / duets

๐Ÿ“„ Drive.Command.fs ยท 240 lines
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
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240namespace Duets.Cli.Components.Commands

open Duets.Agents
open Duets.Cli
open Duets.Cli.Components
open Duets.Cli.SceneIndex
open Duets.Cli.Text
open Duets.Cli.Text.Prompts
open Duets.Entities
open Duets.Simulation

type private DriveChoice =
    | DriveWithinCity
    | DriveToCity of CityId

[<RequireQualifiedAccess>]
module DriveCommand =
    /// Command that allows the player to travel within a city and to another
    /// city (if connected by road) by driving a car.
    let rec create currentCarPosition car reachableCities =
        { Name = "drive"
          Description =
            "Allows you to drive your car to a place within the current city, or to a different, reachable city"
          Handler =
            fun _ ->
                showSeparator None
                lineBreak ()

                // Ask if driving within city or to another city
                let choice = chooseDriveType reachableCities

                match choice with
                | None ->
                    Travel.driveCancelled |> showMessage
                    lineBreak ()
                    Scene.World
                | Some DriveWithinCity -> driveWithinCity currentCarPosition car
                | Some(DriveToCity destinationCityId) ->
                    driveToCity destinationCityId currentCarPosition car }

    and private chooseDriveType reachableCities =
        let choices =
            [| ("Within current city", DriveWithinCity)
               yield!
                   reachableCities
                   |> List.map (fun (cityId, _) ->
                       (Generic.cityName cityId, DriveToCity cityId)) |]

        showOptionalChoicePrompt
            "Where do you want to drive to?"
            Generic.cancel
            fst
            choices
        |> Option.map snd

    and private driveWithinCity currentCarPosition car =
        let destination = showMap ()

        match destination with
        | None ->
            Travel.driveCancelled |> showMessage
            lineBreak ()
            Scene.World
        | Some place ->
            planAndConfirmDriveWithinCity place currentCarPosition car

    and private planAndConfirmDriveWithinCity
        (destination: Place)
        currentCarCoords
        car
        =
        let state = State.get ()

        showSeparator None
        Travel.driveCalculatingRoute |> showMessage
        lineBreak ()

        let planResult = Vehicles.Car.planWithinCityDrive state destination

        match planResult with
        | Error Vehicles.Car.AlreadyAtDestination ->
            Travel.driveAlreadyAtDestination |> showMessage
            lineBreak ()
            Scene.World
        | Error Vehicles.Car.CannotReachDestination ->
            Travel.driveCannotReachDestination |> showMessage
            lineBreak ()
            Scene.World
        | Ok(_, travelTime) ->
            Travel.driveRouteEstimate travelTime destination.Name |> showMessage
            lineBreak ()

            let confirmed = showConfirmationPrompt Travel.driveConfirmRoute

            if confirmed then
                driveToDestinationWithinCity destination currentCarCoords car
            else
                Travel.driveCancelled |> showMessage
                lineBreak ()
                Scene.World

    and private driveToDestinationWithinCity
        (destination: Place)
        currentCarCoords
        car
        =
        let state = State.get ()

        showSeparator None
        Travel.driveStarting destination.Name |> showMessage
        lineBreak ()

        wait 500<millisecond>

        takeWithinCityDrive destination.Name car

        let moveEffects =
            Vehicles.Car.driveWithinCity state destination currentCarCoords car

        Effect.applyMultiple moveEffects

        Scene.WorldAfterMovement

    and private driveToCity destinationCityId currentCarPosition car =
        let state = State.get ()

        let planResult =
            Vehicles.Car.planIntercityDrive state destinationCityId car

        match planResult with
        | Error Vehicles.Car.AlreadyAtDestination ->
            Travel.driveAlreadyAtDestination |> showMessage
            lineBreak ()
            Scene.World
        | Error Vehicles.Car.CannotReachDestination ->
            Travel.driveCannotReachDestination |> showMessage
            lineBreak ()
            Scene.World
        | Ok(distance, travelTime, tripDuration) ->
            showSeparator None

            Travel.driveIntercityEstimate
                (Generic.cityName destinationCityId)
                distance
                travelTime
            |> showMessage

            lineBreak ()

            Travel.driveIntercityWarning |> showMessage
            lineBreak ()

            let confirmed = showConfirmationPrompt Travel.driveConfirmRoute

            if confirmed then
                executeIntercityDrive
                    destinationCityId
                    currentCarPosition
                    car
                    tripDuration
            else
                Travel.driveCancelled |> showMessage
                lineBreak ()
                Scene.World

    and private executeIntercityDrive
        destinationCityId
        currentCarPosition
        car
        tripDuration
        =
        let state = State.get ()

        showSeparator None
        Travel.driveStarting (Generic.cityName destinationCityId) |> showMessage
        lineBreak ()

        wait 500<millisecond>

        takeIntercityDrive destinationCityId car

        let moveEffects =
            Vehicles.Car.driveToCity
                state
                destinationCityId
                currentCarPosition
                car
                tripDuration

        Effect.applyMultiple moveEffects

        Scene.WorldAfterMovement

    and private takeWithinCityDrive destinationName car =
        let state = State.get ()

        generateWithinCityDrivingMoment state destinationName car
        wait 2000<millisecond>

        showSeparator None
        Travel.driveArrivedAtDestination destinationName |> showMessage
        lineBreak ()

    and private takeIntercityDrive destinationCityId car =
        let state = State.get ()
        let originCityId, _, _ = state.CurrentPosition

        generateIntercityDrivingMoment state originCityId destinationCityId car
        wait 2000<millisecond>

        showSeparator None

        Travel.driveArrivedAtDestination (Generic.cityName destinationCityId)
        |> showMessage

        lineBreak ()

    and private generateWithinCityDrivingMoment state destinationName car =
        Driving.createWithinCityDrivingMomentPrompt state destinationName car
        |> LanguageModel.streamMessage
        |> streamStyled Styles.event

        lineBreak ()

    and private generateIntercityDrivingMoment
        state
        originCityId
        destinationCityId
        car
        =
        Driving.createIntercityDrivingMomentPrompt
            state
            originCityId
            destinationCityId
            car
        |> LanguageModel.streamMessage
        |> streamStyled Styles.event

        lineBreak ()