๐Ÿ“ฆ sleepyfran / duets

๐Ÿ“„ Effect.fs ยท 299 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
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299[<RequireQualifiedAccess>]
module rec Duets.Cli.Effect

open Duets.Agents
open Duets.Cli.Components
open Duets.Cli.Text
open Duets.Common
open Duets.Entities
open Duets.Entities.SituationTypes
open Duets.Simulation
open FSharp.Data.UnitSystems.SI.UnitNames

/// <summary>
/// Applies an effect to the simulation and displays any message or action that
/// is associated with that effect. For example, transferring money displays a
/// message with the transaction.
/// </summary>
/// <param name="effect">Effect to apply</param>
let apply effect =
    Simulation.tickOne (State.get ()) effect ||> digest

/// <summary>
/// Calls <c>apply</c> one time for each given effect in the list.
/// </summary>
/// <param name="effects">Effects to apply</param>
let applyMultiple effects =
    effects |> Simulation.tickMultiple (State.get ()) ||> digest

let private digest effects state =
    State.set state

    effects |> Seq.tap Log.appendEffect |> Seq.iter displayEffect

let private displayEffect effect =
    match effect with
    | AlbumReleased(_, releasedAlbum) ->
        Studio.commonAlbumReleased releasedAlbum.Album.Name |> showMessage
    | AlbumReviewsReceived(_, releasedAlbum) ->
        $"The reviews for your album {releasedAlbum.Album.Name} just came in!"
        |> showMessage

        let accepted = showConfirmationPrompt "Do you want to see them now?"

        if accepted then
            showReviews releasedAlbum
        else
            "Reviews are always accessible via the phone"
            |> Styles.faded
            |> showMessage
    | BandSwitchedGenre(band, Diff(prevGenre, currGenre)) ->
        $"Your band {band.Name} is now playing {currGenre |> Styles.genre} instead of {prevGenre |> Styles.genre}"
        |> showMessage
    | CareerAccept(_, job) ->
        let place =
            job.Location
            |> World.Coordinates.toPlaceCoordinates
            ||> Queries.World.placeInCityById

        Career.careerChange job place.Name |> showMessage
    | CareerLeave(_, job) ->
        let place =
            job.Location
            |> World.Coordinates.toPlaceCoordinates
            ||> Queries.World.placeInCityById

        Career.careerLeft job place.Name |> showMessage
    | CareerPromoted(job, salary) ->
        let place =
            job.Location
            |> World.Coordinates.toPlaceCoordinates
            ||> Queries.World.placeInCityById

        Career.careerPromoted job place.Name salary |> showMessage
    | CareerShiftPerformed(_, _, payment) ->
        Career.workShiftFinished payment |> showMessage
    | CharacterAttributeChanged(_, attr, Diff(previous, current)) ->
        match attr with
        | CharacterAttribute.Drunkenness ->
            if previous < current then
                (* We are drinking more. *)
                match current with
                | amount when amount < 25 -> Events.feelingTipsy
                | amount when amount < 50 -> Events.feelingDrunk
                | _ -> Events.feelingReallyDrunk
                |> showMessage
            else
                (* Character is sobering up. *)
                match (previous, current) with
                | prev, curr when prev > 25 && curr <= 25 ->
                    Events.soberingTipsy |> showMessage
                | prev, curr when prev > 50 && curr <= 50 ->
                    Events.soberingDrunk |> showMessage
                | _ -> ()
        | _ -> ()
    | CharacterHealthDepleted _ ->
        lineBreak ()
        wait 5000<millisecond>
        showMessage Events.healthDepletedFirst
        wait 5000<millisecond>
        showMessage Events.healthDepletedSecond
        wait 5000<millisecond>
        lineBreak ()
    | CharacterHospitalized _ ->
        showMessage Events.hospitalized
        lineBreak ()
    | CharacterMoodletsChanged(_, Diff(prevMoodlets, currMoodlets)) ->
        (currMoodlets, prevMoodlets)
        ||> Set.difference
        |> Set.toList
        |> List.iter (fun moodlet ->
            match moodlet.MoodletType with
            | MoodletType.JetLagged ->
                "You feel a bit confused because of the time difference, you might feel more tired than usual"
            | MoodletType.NotInspired ->
                "You've been composing too much and you're not feeling inspired anymore. Try waiting a few days..."
            | MoodletType.TiredOfTouring ->
                "You've had a lot of concerts lately and you're feeling tired. Try waiting a few days..."
            |> Styles.warning
            |> showMessage)
    | ConcertScheduled(_, ScheduledConcert(concert, _)) ->
        showConcertDetails concert
    | ConcertFinished(_, pastConcert, income) ->
        let concert = Concert.fromPast pastConcert

        let quality =
            match pastConcert with
            | PerformedConcert(_, quality) -> quality
            | _ -> 0<quality>

        match quality with
        | q when q < 35<quality> -> Concert.finishedPoorly
        | q when q < 75<quality> -> Concert.finishedNormally
        | _ -> Concert.finishedGreat
        |> showMessage

        Concert.concertSummary concert income |> showMessage
    | ConcertCancelled(band, FailedConcert(concert, reason)) ->
        let place = Queries.World.placeInCityById concert.CityId concert.VenueId

        match reason with
        | BandDidNotMakeIt -> Concert.failedBandMissing band place concert
        | CharacterPassedOut -> Concert.failedCharacterPassedOut
        |> showMessage
    | ItemAddedToCharacterInventory item ->
        Items.itemAddedCharacterToInventory item |> showMessage
    | ItemAddedToBandInventory(_, item, quantity) ->
        Items.itemAddedToBandInventory item quantity |> showMessage
    | ItemRemovedFromCharacterInventory item ->
        Items.itemRemovedFromCharacterInventory item |> showMessage
    | MerchSold(_, items, income) ->
        let itemsSold = items |> Seq.sumBy snd

        $"You sold {itemsSold} items and made a total of {Styles.money income} on top of the concert ticket sales"
        |> Styles.success
        |> showMessage
    | MoneyTransferred(holder, transaction) ->
        Phone.bankAppTransferSuccess holder transaction |> showMessage
    | NotificationShown notification ->
        let createCalendarNotification typeText date dayMoment =
            $"{typeText} scheduled for {Date.simple date |> Styles.time} @ {Generic.dayMomentName dayMoment |> Styles.time}"
            |> Styles.highlight
            |> showNotification "Upcoming event"

        let createRentalNotification text =
            text |> Styles.highlight |> showNotification "Upcoming payment"

        let createDeliveryNotification text =
            text |> Styles.highlight |> showNotification "Delivery"

        match notification with
        | Notification.CalendarEvent(CalendarEventType.Flight flight) ->
            createCalendarNotification
                $"Flight from {Generic.cityName flight.Origin |> Styles.place}"
                flight.Date
                flight.DayMoment
        | Notification.CalendarEvent(CalendarEventType.Concert concert) ->
            let venue =
                Queries.World.placeInCityById concert.CityId concert.VenueId

            createCalendarNotification
                $"Concert at {venue.Name |> Styles.place} in {Generic.cityName concert.CityId |> Styles.place}"
                concert.Date
                concert.DayMoment

        | Notification.DeliveryArrived(cityId, placeId, DeliveryType.Merchandise) ->
            let place = Queries.World.placeInCityById cityId placeId

            $"Your delivery of merchandise is ready at {place.Name |> Styles.place} in {Generic.cityName cityId}"
            |> createDeliveryNotification
        | Notification.RentalNotification(RentalNotificationType.RentalDueTomorrow rental) ->
            let cityId, _ = rental.Coords
            let place = rental.Coords ||> Queries.World.placeInCityById

            $"Your rental of {place.Name} in {Generic.cityName cityId |> Styles.place} will expire tomorrow if you don't pay the next rent.\nYou can do so from your phone's bank app"
            |> createRentalNotification
        | Notification.RentalNotification(RentalNotificationType.RentalDueInOneWeek rental) ->
            let cityId, _ = rental.Coords
            let place = rental.Coords ||> Queries.World.placeInCityById

            $"Your rental of {place.Name} in {Generic.cityName cityId |> Styles.place} is set to expire in one week unless you pay the next rent.\nHead over to your phone's bank app to do so"
            |> createRentalNotification
    | PlaceClosed place ->
        lineBreak ()

        Styles.danger $"{place.Name} is closing and they're kicking you out."
        |> showMessage
    | GamePlayed result ->
        lineBreak ()

        let gameResultMessage simpleResult =
            match simpleResult with
            | SimpleResult.Win ->
                "You won against a random stranger" |> Styles.success
            | SimpleResult.Lose ->
                "You lost against a random stranger" |> Styles.error

        match result with
        | PlayResult.Darts result ->
            ([ "Throwing darts..." |> Styles.progress
               "Picking up darts..." |> Styles.progress
               "Repeating all over again..." |> Styles.progress ],
             1<second>)
            ||> showProgressBarSync

            gameResultMessage result |> showMessage
        | PlayResult.Pool result ->
            ([ "Racking the balls..." |> Styles.progress
               "Breaking..." |> Styles.progress
               "Taking a shot..." |> Styles.progress ],
             1<second>)
            ||> showProgressBarSync

            gameResultMessage result |> showMessage
        | PlayResult.VideoGame ->
            "You pick up the controller..." |> showMessage

            wait 2000<millisecond>

            Interaction.videoGameResults
            |> List.sample
            |> Styles.success
            |> showMessage
    | RentalKickedOut _ ->
        "Since your rental has ran out, you need to go somewhere else"
        |> Styles.error
        |> showMessage
    | RentalExpired rental ->
        lineBreak ()

        let expiredPlace = rental.Coords ||> Queries.World.placeInCityById
        let cityId, _ = rental.Coords

        match rental.RentalType with
        | Seasonal _ ->
            $"You didn't pay this seasons's rent, so you can no longer access {expiredPlace.Name |> Styles.place} in {Generic.cityName cityId |> Styles.place}"
        | OneTime _ ->
            $"You rental of {expiredPlace.Name |> Styles.place} in {Generic.cityName cityId |> Styles.place} has expired, so you can no longer access it"
        |> Styles.warning
        |> showNotification "Rental expired"
    | SituationChanged(Concert(Preparing _)) ->
        showTip
            "Concert starting"
            $"""It's time to prepare for the concert! If you have any merchandise, you can set up a stand to sell it by the {"bar" |> Styles.place}. Otherwise, head to the {"stage" |> Styles.place} to do a soundcheck"""
        |> Styles.information
        |> showMessage
    | SongImproved(_, Diff(before, after)) ->
        let (Unfinished(_, _, previousQuality)) = before
        let (Unfinished(_, _, currentQuality)) = after

        Rehearsal.improveSongCanBeFurtherImproved (
            previousQuality,
            currentQuality
        )
        |> showMessage
    | SongPracticed(_, Finished(song, _)) ->
        Rehearsal.practiceSongImproved song.Name song.Practice |> showMessage
    | SongDiscarded(_, Unfinished(song, _, _)) ->
        Rehearsal.discardSongDiscarded song.Name |> showMessage
    | SongFinished(_, Finished(song, quality), _) ->
        Rehearsal.finishSongFinished (song.Name, quality) |> showMessage
    | SkillImproved(character, Diff(before, after)) ->
        let (skill, previousLevel) = before
        let (_, currentLevel) = after

        Skill.skillImproved
            character.Name
            character.Gender
            skill
            previousLevel
            currentLevel
        |> showMessage
    | Wait _ ->
        let today = Queries.Calendar.today (State.get ())

        let currentDayMoment = Calendar.Query.dayMomentOf today

        Command.waitResult today currentDayMoment |> showMessage
    | _ -> ()