I see there is some confusion about the meaning of delay. The requirements contradict the use of a ‘delay’, so probably that needs some explanation.
When I search the program code of example apps for EVE Online, I see the term ‘delay’ used in several places. One example is the bot-step-delay app-setting offered by many of the apps for EVE Online. That delay means the time between bot steps, the time during which the bot is not running. This ‘not running’ also means that it does not even read from the game client, which means it would not see the ‘neutral’ coming back.
Extending that delay is used typically to save computing resources during phases when we are waiting for some progress in the game. In EVE Online, it could be during warp or when we are in an asteroid belt waiting for the ore hold to fill.
In short, you can add a delay, too, but that does not implement the timer.
To implement a timer, expand the app state model to remember the time. We could store the time you last saw the ‘neutral’, but since we want a randomized duration, it is better to store the end time instead. In each step, the bot then looks at the reading from the game and reset the timer if necessary.
The most popular apps for EVE Online use a framework that separates the bot memory from the decision function (‘policy’), so I use that as the basis to explain the implementation.
Let’s refresh our memory on what that means for the data flow in our app. This diagram illustrates the data flow in this kind of framework:
Based on that framework, we divide the implementation of the new timer as follows:
In the function that updates the app memory for each reading, compute the new timer end time.
In the function that decides the next action, compare the timer end time from memory with the current time. If the timer end time is less than the current time, you branch into the undock function.
Comparing the two times is the easier part: In the frameworks mentioned above, you have the .eventContext.timeInMilliseconds field in the decision context to get the current time:
Now let’s look closer at how we update and store the timer end time.
This expression should do it:
Is a ‘neutral’ present in the current reading from the game client?
Yes → there is no end time. (We can use the Maybe.Nothing from the core library for this case. We can use the Maybe Int type for the field that stores the end time)
No → Was there a ‘neutral’ present in the previous reading from the game client?
Yes → That means the last ‘neutral’ just disappeared, so compute a new end time.
No → Return the timer end time from the previous app memory state.
You can add an app-setting to support configuring the duration, and you can also use randomization here. There is already this example app that offers those two things at once:
With that break-duration setting, you can choose the minimum and the maximum duration in the same setting value, and the app picks a random value in that interval.
Thinking about it again, I found a simpler solution than explained in the first response: We can store only the last time we saw a neutral in the bot memory structure. With this memory, we can use the time as an entropy source to do the randomization.
With this new approach, the update function is simpler. We no longer need to derive the difference between the current and the previous reading. Instead, we can update a single new field like this:
neutralInLocalLastTime =
if context.readingFromGameClient |> isNeutralVisibleInLocalFromWholeReading |> Result.withDefault True then
Just { timeInMilliseconds = context.timeInMilliseconds }
else
botMemoryBefore.neutralInLocalLastTime
To check if a neutral is present in the first place, I reused the older implementation. This code moved into a dedicated function isNeutralVisibleInLocalFromWholeReading.