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[<RequireQualifiedAccess>]
module Duets.Simulation.Simulation
open Aether
open Duets.Common
open Duets.Entities
open Duets.Simulation.Events
open Duets.Simulation.Time.AdvanceTime
open Duets.Simulation.Time.InteractionMinutes
type private TickState =
{ AppliedEffects: Effect list
State: State }
let rec private tick' tickState (nextEffectFns: EffectFn list) : TickState =
match nextEffectFns with
| effectFn :: rest -> effectFn tickState.State |> tickEffect tickState rest
| [] -> tickState
and private tickEffect tickState nextEffectFns effects =
match effects with
| [] -> tick' tickState nextEffectFns
| effect :: restOfEffects ->
(*
Before applying the effect and gathering its associated effects, check if
there's any current modifier that needs to be applied to the effect. For
example, if the character is not inspired, song related effects have
less effect.
*)
let effect =
EffectModifiers.EffectModifiers.modify tickState.State effect
let updatedState = State.Root.applyEffect tickState.State effect
let associatedEffectFns =
[ yield! Events.associatedEffects effect
yield! applyTime effect updatedState ]
(* Tick all the associated effects first, and pass the rest of the
effects that come after the current one that was applied plus all
the other effect functions that are left to be applied.
tickAssociatedEffects will then decide what to apply and what to
discard. *)
tickAssociatedEffects
{ AppliedEffects = tickState.AppliedEffects @ [ effect ]
State = updatedState }
associatedEffectFns
(* Prepend the rest of the effects so that they'll be processed
before the next effects on the chain. *)
((fun _ -> restOfEffects) :: nextEffectFns)
and private tickAssociatedEffects tickState associatedEffects nextEffectFns =
match associatedEffects with
| BreakChain effectFns :: _ ->
(* Breaking the chain means discarding the tail of associated effects
and also the rest of the effect fns that were left to be applied. *)
tick' tickState effectFns
| ContinueChain effectFns :: restOfAssociatedEffects ->
(* When continuing a chain, we pre-pend all the effect functions that
were generated in this associated effect to the actual tail of effect
functions that are left to be applied. *)
effectFns @ nextEffectFns
|> tickAssociatedEffects tickState restOfAssociatedEffects
| [] -> tick' tickState nextEffectFns
and private applyTime effect state =
let totalTurnTime = effectMinutes effect
if totalTurnTime > 0<minute> then
applyTime' state totalTurnTime
else
// The effect didn't consume any time, so no need to do anything.
[]
and private applyTime' state totalTurnTime =
let currentTurnMinutes = Optic.get Lenses.State.turnMinutes_ state
let total = currentTurnMinutes + totalTurnTime
if total >= Config.Time.minutesPerDayMoment then
// Enough time has passed to trigger a new day moment, advance the
// time by the number of day moments that have passed and apply those
// to the current chain.
let totalDayMoments =
total / Config.Time.minutesPerDayMoment |> (*) 1<dayMoments>
[ [ fun state -> advanceDayMoment' state totalDayMoments ]
|> ContinueChain ]
else if total > 0<minute> then
// Not enough time has passed to trigger a new day moment, so just
// update the turn time.
[ [ (Func.toConst [ TurnTimeUpdated total ]) ] |> ContinueChain ]
else
[]
//// Ticks the simulation by applying multiple effects, gathering its associated
/// effects and applying them as well.
/// Returns a tuple with the list of all the effects that were applied in the
/// order in which they were applied and the updated state.
let tickMultiple currentState effects =
let effectFns = fun _ -> effects
let tickResult =
tick'
{ AppliedEffects = []
State = currentState }
(effectFns :: Events.endOfChainEffects)
tickResult.AppliedEffects, tickResult.State
/// Same as `tickMultiple` but with one effect.
let tickOne currentState effect = tickMultiple currentState [ effect ]