Adding a delay to undock

Currently having an issue with adding a delay to when a neutral enters a system, the original snippet that I believe is for undocking is:

|> Maybe.withDefault 
                                    (if 1 < (subsetOfUsersWithNoGoodStanding |> List.length) then
                                        (describeBranch "Stay docked." waitForProgressInGame)
                                    else
                                        (undockUsingStationWindow context))

and wanting to achieve something like this:

|> Maybe.withDefault 
                                    (if 1 < (subsetOfUsersWithNoGoodStanding |> List.length) then
                                        (describeBranch "Stay docked." waitForProgressInGame)
                                    else
                                        (delay 5000 (undockUsingStationWindow context)))

However unsure on how to add the delay function.

In order to reduce misunderstanding I would require it to do this:

  • Neutral enters system
  • Bot sees this and docks into a station
  • all neutrals leave local and bot enters into a configurable countdown (preferably with a randomised aspect to it, e.g 2-5 minutes, 5-10 minutes
  • if a neutral comes back into local, the counter is reset again and stars over
  • once the timer has finished and no neutral in local, bot undocks.

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:

data flow in bot app 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

You can see the complete implementation in the program code at Demo adding an undock timer as described by RedSky · Viir/bots@026f0a7 · GitHub

To check if a neutral is present in the first place, I reused the older implementation. This code moved into a dedicated function isNeutralVisibleInLocalFromWholeReading.