Kepler. Documentation · Scripts 325 functions available right now Français

Driving the workers

A script can switch the bot's automations on and off, and ask which one is running. This is the only family that acts on the bot itself rather than on the game.

What these functions really do

They reproduce the click in the interface, not an in-memory start. The setting is saved, it survives a restart of the bot, and the tool exclusivity rule applies exactly as if you had ticked the box yourself.

That difference matters. A toggle that lived only in memory would be undone at the next launch, with nothing to say so: your script would have switched farming on, and you would find the account idle the next day without understanding why.

The Start… and Stop… functions return an error, nil when the change happened. The IsRunning… ones return a boolean.

The error is a Kepler addition: the reference tool returns nothing. A copied script calls these as statements and reads no return, so nothing breaks; a script written for Kepler, on the other hand, can check.

For the reads, false means "I could not read" as much as "the worker is off". The failure goes to the log, never silently.

Switching on and off

FunctionReturns
StartBrain() / StopBrain()1: an errorBrain. Build queues and research.
StartScanner() / StopScanner()1: an errorThe scanner. System sweeps.
StartHunter() / StopHunter()1: an errorThe hunter. Target tracking.
StartSleepMode() / StopSleepMode()1: an errorSleep mode. See the warning below.
StartFarmingBot() / StopFarmingBot()1: an errorFarming. StartFarmingBot() gives the farmer a session when it has none.
StartFarmSession(session)1: an errorStarts one named session, like the ▶ on its row.
StartExpeditionsBot() / StopExpeditionsBot()1: an errorExpeditions.
StartColonizerBot() / StopColonizerBot()1: an errorThe colonizer.
StartDiscoveryBot() / StopDiscoveryBot()1: an errorDiscovery.
StartDefenderBot() / StopDefenderBot()1: an errorThe defender.
err = StopFarmingBot()
if err != nil { LogError("farming:", err) }
StartExpeditionsBot()
StartBrain()
StopBrain()
StartScanner()
StopScanner()
StartHunter()
StopHunter()
StartColonizerBot()
StopColonizerBot()
StartDiscoveryBot()
StopDiscoveryBot()
StartDefenderBot()
StopDefenderBot()
StopExpeditionsBot()

A worker may refuse to start, and it says why

Test the error. It does not merely say "that did not work": it says what is missing, and that is most often a setting.

err = StartWorker("timer")
if err != nil {
	LogWarn("the timer will not start:", err)
	// enter a Discord webhook or tick Telegram, here or in the bot
	// settings: without a channel, the monitoring would warn nobody
}

Measured by running this script on a real account: the timer requires an alert channel, the Colonizer a starting point, and so on. A script that ignored that return would believe it had switched on a worker that never started.

An unknown worker name is refused too, and named: unknown worker "cephalopode".

Asking which one runs

FunctionReturns
IsRunningBrainBot()1: a booleanTrue if Brain is on.
IsRunningFarmingBot()1: a booleanTrue if farming is on.
IsRunningExpeditionsBot()1: a boolean
IsRunningColonizerBot()1: a boolean
IsRunningDiscoveryBot()1: a boolean
IsRunningDefenderBot()1: a boolean
if IsRunningFarmingBot() {
  Print("farming is running")
}
Print(IsRunningBrainBot(), IsRunningExpeditionsBot(), IsRunningColonizerBot())
Print(IsRunningDiscoveryBot(), IsRunningDefenderBot())

There is no IsRunning for the scanner, the hunter or sleep mode: the reference tool gives none, and we do not invent a name no copied script would call. The general form below covers them.

The general form

Kepler has workers the reference tool does not: repatriation, spying, the phalanx, debris field watching, the timer. Three functions take the worker's name and cover them all.

FunctionReturns
StartWorker(name)1: an errorSwitches the named worker on.
StopWorker(name)1: an errorSwitches it off.
IsWorkerRunning(name)1: a booleanSays whether it is on.
ListWorkers()1: a listThe accepted names, in English.

The emergency stop

One call silences the bot: the session closes, the workers are suspended, the account's other scripts are paused, and every game call fails for as long as the pause lasts.

This is the safety net for the forgotten script. A loop left running that polls the server no longer sends a single request: its calls fail with "account not connected", so you get errors in your console instead of a ban.

FunctionReturnsWhat it does
PauseBot()1: an errorCuts all communication with the game.
ResumeBot()1: an errorGives it back.
IsBotPaused()1: a booleanSays where things stand.
LogOut()1: an errorAlias of PauseBot, for scripts coming from Ninja.
Login()1: an errorAlias of ResumeBot.

The script that calls PauseBot is never paused itself, otherwise it could never call ResumeBot and the account would stay silent.

A paused account is no longer defended. The Defender stops watching, just as during sleep mode. This shelters your account from your own scripts, not from an attacker.

If the script ends without lifting the pause, the "Wake up" button on the account card lifts it too. You are never stuck.

if MyReasons() {
	PauseBot()
	SleepMin(30)
	ResumeBot()
}
for name in ListWorkers() {
  Print(name, IsWorkerRunning(name))
}
err = StartWorker("repatriate")
if err != nil { LogError("repatriation:", err) }
StopWorker("spyer")

The names are lowercase and unaccented: brain, defender, expeditions, sleep, scanner, farmer, colonizer, repatriate, supply, hunter, discovery, spyer, phalanx, militarydebris, colonywatch, timer, scheduledflights. An unknown name returns an error that names it, rather than doing nothing.

supply feeds the Brain's planets from banks. It is set up in the Supply tab of the Brain page, and stopping it does not stop the building: the Brain carries on, it simply waits on its own production.

The reverse does not hold: switching the Brain off pauses supply. Nothing is delivered while the Brain is off, and deliveries resume on their own when it is switched back on, from freshly worked-out needs. IsWorkerRunning("supply") still returns the switch: it can answer true during that pause, and a Planning slot that switches supply on while the Brain is off sends nothing.

Repatriate spares a planet that is waiting for a delivery. It no longer does so when supply or the Brain is off: no delivery will come, and the planet must not be left exposed.

colonywatch is not the coloniser. colonizer settles new planets; colonywatch watches for colonies appearing around the players you follow.

Composing a farm session

FunctionReturns
NewFarmSession()1: a builderTo chain, then BuildFarmSession().

The builder is set method by method, then BuildFarmSession() saves the session and makes it active, like the interface's "Start" button. It returns two values: the created session and an error.

It does not switch farming on for you: StartFarmingBot() does that. Keeping the two apart lets you prepare a session without sending it off at once.

What StartFarmingBot() picks to run

It does more than flip the switch: it gives the farmer a session when it has none, in this order.

1. An active session: it just switches farming on. That is the case of a script that has just created one. 2. A paused session: it resumes where it stopped. Any other one would start again from the first system. 3. Whatever waits in the queue, in the order the sessions entered it. 4. Otherwise, the first saved session, in creation order. 5. No session at all: it returns an error and does nothing. A farmer switched on with nothing to run is exactly what this avoids.

StartFarmSession(session) is for when that choice is not what you want: it starts the one you name, like the ▶ on its row in the Farmer tab.

session, err = f.BuildFarmSession()
StartFarmSession(session.ID)
origin = GetCachedCelestials()[0]
f = NewFarmSession()
f.SetName("night watch")
f.SetOrigin(origin)
f.SetRangeAroundOrigin(30)
f.SetMinimumResourcesToAttack(300000)
f.SetAdditionalCargo(10)
f.SetAttackDelay(2, 5)
f.SetPriorityRatio(1, 1.5, 2)
f.SetUsePathfinders(false)
f.SetIgnoreActivity(true)
f.SetFastAttacking(true)
session, err = f.BuildFarmSession()
if err != nil { LogError("session:", err) }
Print("session", session.ID, "range", session.Range)
StartFarmingBot()

The settings that carry

SetOrigin, SetOriginCoord, SetRange, SetGalaxyRange, SetRangeAroundOrigin, SetName, SetMinimumResourcesToAttack, SetAdditionalCargo, SetMinimumPlayerRank, SetUsePathfinders, SetIgnoreActivity, SetFastAttacking, SetSmallCargosFirst, SetPriorityRatio, SetAttackDelay.

Three deserve a word.

SetRange(galaxy, systemFrom, systemTo) keeps your bounds, inclusive, and the galaxy you ask for. It approximated for a long time - half the width of the interval became a radius around the origin - then it kept the systems but refused any galaxy other than the origin's, having no way to sweep elsewhere. The campaign can now cross galaxies, and that refusal is gone.

A galaxy of zero means "all of them". On a wrapping universe, a span that starts after it ends goes past the edge: SetRange(1, 480, 20) covers 480 to 499 then 1 to 20.

SetGalaxyRange(from, to) is a Kepler extra, with no equivalent in the reference tool, which only bounds one galaxy at a time. SetGalaxyRange(1, 4) together with SetRange(0, 200, 350) sweeps systems 200 to 350 of galaxies 1 to 4. Zero means "all of them" on either side - and with no system bounds, those are WHOLE galaxies: four of them are close to two thousand systems, and a campaign spends days there.

SetRangeAroundOrigin(range) is still there for whoever prefers a radius. It puts the campaign back in automatic mode and clears all four bounds: the last call wins, either way.

SetOriginCoord(galaxy, system, position, type) does not need the session. SetOrigin has to resolve a celestial, so it waits for the connection; this one names the coordinate directly. The type is "planet" or "moon", any other word is refused rather than guessed.

SetMinimumPlayerRank does not mean what it looks like. Rank 1 is the best player: "up to 5000" therefore keeps the top five thousand and drops the rest. Zero takes them all.

SetFastAttacking does not mean what it means in Ninja. There, the "Fast attacking" box sends the raids as soon as the espionage reports come in, while the spying goes on. In Kepler, SetFastAttacking(true) picks the "Never wait (risky)" strategy: each slot goes out again as soon as it frees up, instead of waiting for the whole wave to come home. A script brought over from Ninja that calls it therefore changes strategy, not the way the campaign spies. Ninja's behaviour exists in Kepler as "Raid as soon as the first reports arrive": a box in the campaign window, unticked by default, that no script function sets yet.

The settings that came from Ninja

SetProbes, SetFarmSpeed, SetMinimumDefensesToIgnore, SetAttackPlayersWithDefenses, SetMinimumStorageToIgnore, SetEspionageProbeRaids, SetDeleteCombatReports.

They all have a control in the campaign window since 18/09/2026. These functions remain the way a script reaches the same fields: a script and a click set the very same thing, and the window carries over what it does not show rather than wiping it.

Each one still defaults to what the bot did before it existed, so a session set up by hand behaves exactly as before.

SetProbes(n) sets the probes per espionage, both on the sweep and on the re-spy before firing. Without it, the count set in your OGame account.

SetFarmSpeed(speed) sets the raid speed, with the percentage constants: SetFarmSpeed(FIFTY_PERCENT). Without it, full speed. A value out of bounds is refused rather than quietly clamped.

SetMinimumDefensesToIgnore(n) accepts a target whose seen defence is strictly below n. The name comes from Ninja and reads the other way round: it is the defence at which the target starts being ignored. With 1 you therefore get Kepler's default rule, which accepts no defence at all.

SetAttackPlayersWithDefenses(false) closes that threshold again, whatever the order of the calls. Set to true it does nothing: the threshold decides.

SetDeleteCombatReports(true) moves each raid's combat report to the bin, once its loot has been counted.

The game's mailbox locks up entirely when too many messages pile up — inbox and bin together — and nothing can be done in it any more. A session produces one report per raid: without this setting, a farm running for nights ends up blocking its own player's mailbox.

Four conditions before anything is deleted, and none is decorative: the report must carry the ID of the fleet we sent, that ID must not be zero, the report must have a number of its own, and its target must be the one we aimed at. A report of an attack suffered is therefore out of reach.

The game's action moves to the bin and does not destroy, so Kepler does both: the deletion, then the purge of that tab's bin. Deleting without purging would only fill the other half of the counter that jams the message system.

SetMinimumStorageToIgnore(metal, crystal, deut) skips targets whose storage buildings are too small: below these levels, the position is not attacked. The name comes from Ninja and reads that way round - the minimum below which you ignore. SetMinimumStorageToIgnore(4, 0, 0) asks for a metal storage of level 4 at least and says nothing about the other two. Zero asks for nothing.

A planet with small storage cannot build anything up: you empty it once, and coming back no longer pays.

This setting costs requests. Storage levels are not in the message list, only in each report's detail: asking for them is one request per retained target where the session makes one for all of them. Nothing is read while the three values are zero, and only targets that passed every other filter are opened.

A target you have not seen is skipped. A report taken with too few probes does not show the buildings: it does not say the storage is small, it says you did not look hard enough. Same rule as for defence.

SetEspionageProbeRaids(true) has espionage probes carry the loot instead of cargo ships.

They are the fastest ship in the game and cost only crystal, but each carries only five units: probe raiding is a matter of numbers. On loot a neighbour also wants, speed is what decides, and losing probes costs nothing next to a cargo.

The universe has the last word. Where probe raiding is disabled, a probe's hold is zero: Kepler asks the game and falls back to cargo ships, rather than letting a session set this way never take off.

What spying needs stays home. The session sets aside what its last spy pass actually spent - so many probes per target, as many targets as it aimed at - and raids only with the surplus. With no surplus, it falls back to cargo ships. Without that reserve, the next round would have nothing left to look with and would attack on stale reports.

Two refusals never loosen. A fleet sitting on the target always refuses: it shoots back, and it is worth more than defence. And a report that could not establish the figure, for want of probes, always refuses too: it does not say the planet is empty, it says you did not look hard enough.

Starting from the nearest celestial

SetAttackFromNearestPlanet(true) and SetAttackFromNearestMoon(true) do the same thing here, and saying so beats pretending there are two settings: the reference tool separates planet and moon, Kepler takes whichever of the two is best placed and puts the moon ahead of its planet at equal distance. It carries the same fleet without announcing anything to the neighbourhood, and what leaves it does not leave the planet bare. SetStartFromNearest(true) is the Kepler name for the same thing.

Either one turns it on, and false only turns it off if nothing else asked for it: a script writing SetAttackFromNearestPlanet(true) then SetAttackFromNearestMoon(false) clearly wants the mode.

PROBES AND RAIDS LEAVE FROM THE SAME PLACE, never one without the other. A "from nearest" that only moved the probes would be exactly the flaw we hold against other tools.

A galaxy where the account owns nothing is skipped. The calculation refuses to cross a galaxy: a flight that does gets noticed, and the game charges hours for it. The session writes it to the log rather than sweeping into the void. That is what makes SetGalaxyRange usable: without it, a four-galaxy span from a fixed origin would send probes on half-hour flights each way.

SetOrigin becomes optional. On an automatic span, the range then applies around each of the account's celestials, and overlapping zones are swept only once.

The settings with no equivalent

One of Ninja's settings does not exist in Kepler's session. It is accepted and does nothing, so a copied script does not stop, and BuildFarmSession() names it in the bot's log as well as in the Ignored field of what it returns.

SetIgnoreSleepMode. It will not leave that list: punching an exception through sleep would reopen the part that has cost the most defects.

Print("ignored:", len(session.Ignored))

One session at a time, the rest queued

Farming runs only one: two would fight over slots and probes. But BuildFarmSession() no longer refuses when a session is running - it queues the new one, and returns its id as usual.

And it says so, through two fields of the returned session: Queued is true while it waits, QueueRank gives its place starting at 1. Without them the two cases looked exactly alike, and a script had no way to tell that its session was waiting instead of running.

session, err = f.BuildFarmSession()
if session.Queued {
    LogInfo("waiting its turn, place", session.QueueRank)
}

So a script can build thirteen in a row: the first becomes active, the other twelve wait their turn in the order you created them. When a session finishes, farming picks up the next one on its own. The Farmer tab shows them as "queued - 3 of 12".

Three things worth knowing:

  • stopping or abandoning clears the queue, and the log says how many sessions were dropped. AbortAllFarmingSessions() does the same, and also removes every session;
  • pausing keeps it: a suspended session resumes where it was, and what followed it still follows it;
  • if the Planning also drives farming, its programme comes first: a range's queue empties before the scripts' one.

A row's ▶ button still refuses a second session: it launches the one you pointed at, and queueing it would be a surprise. That is what the + button next to it is for: + to wait its turn, ▶ to leave right away.

The screen's queue and the scripts' queue are the same one. The twelve sessions a script queues show up in the "Queue" panel at the bottom of the Farmer tab: you see the running order, you can take one out - the session is not deleted - and start the first one with a click. A session deleted while it was waiting is skipped quietly: the turn goes to the next one.

The expeditions strategy

FunctionReturns
GetExpeditionsStrategy()1: a string"majority", "all" or "never".
SetExpeditionsStrategy(name)1: an errorChanges it, setting saved.

This is the "Strategy" menu of the expeditions in the panel: "majority" waits for most of the expedition slots to be back before the next wave, "all" waits for every one of them, "never" does not wait. The setting is saved like the menu and applies from the next wave.

Any other name is refused, and the error lists the three. A misspelt name quietly turned into "majority" would let the script believe it got what it asked for.

if GetExpeditionsStrategy() != "all" {
	err = SetExpeditionsStrategy("all")
	if err != nil { LogError("strategy:", err) }
}

The fleet slot reserve

FunctionReturns
GetFleetSlotsReserved()1: an integerHow many slots are kept free.
SetFleetSlotsReserved(n)1: an errorChanges that number, setting saved.

This figure is what leaves the Defender room to dodge. A dodge is a fleet send: with no free slot it does not happen, and the attack lands on a full planet. Everything that flies in Kepler respects that reserve — the night fleet save, scheduled flights, the counter-probe, the Lifeform Expedition. Your script is the only thing that can eat into it without noticing.

slots = GetSlots()
kept = GetFleetSlotsReserved()
if slots.Total - slots.InUse <= kept {
	LogInfo("leaving room for the Defender")
	return
}

SetFleetSlotsReserved saves the setting, like the field in the panel: it survives a restart. Zero is accepted — it means "I handle my own dodging" — a negative number is refused.

Sparing players, positions, alliances

The farmer only raids inactive players, which already covers the neighbour you get along with: someone who plays is never attacked. What remains is what the bot cannot guess — the inactive member of your own alliance, the friend who stopped playing, the position you got burned on once.

This list lives in the database, belongs to the account rather than to a campaign: it therefore applies to every bot on that account, and it survives a stop as well as a restart.

It is set in the account's Settings, "Spared players" tab, or from scripts. The functions below read and write the same list as the screen.

FunctionReturns
IgnorePlayer(player)1: an errorSpares a player. Name or id.
IgnorePlanet(coord)1: an errorSpares a position, moon included.
IgnoreAlliance(tag)1: an errorSpares every known member of an alliance.
UnignorePlayer(player)1: an errorRemoves it from the list.
UnignorePlanet(coord)1: an error
UnignoreAlliance(tag)1: an error
IsPlayerIgnored(player)1: a booleanWhether it is on the list.
IsPlanetIgnored(coord)1: a boolean
IsAllianceIgnored(tag)1: a boolean
GetIgnored()2: the list and an errorThe whole list, all three kinds together.
IgnorePlayer("Xylo")          // by name, as on screen
IgnorePlayer(4242)            // or by id
IgnorePlanet("1:42:8")        // the planet AND its moon
IgnoreAlliance("SLD")         // every member the Scanner has seen

if IsPlayerIgnored("Xylo") {
	Print("left alone")
}

list, err = GetIgnored()
for e in list {
	// e.Kind is "player", "planet" or "alliance".
	// e.Key holds the id, coordinate or tag; e.Label is what to display.
	Printf("%s : %s", e.Kind, e.Label)
}

What the farmer does with it, and when

Two filters, not one. The first drops the position while reading the galaxy, before a single probe. The second drops the report when picking targets to attack — and that one matters most: a campaign resumed in its attack phase never re-reads the galaxy, and the reports it works from are those of the whole inbox, including any left there by another tool.

The log says spared target when this happens. The other reasons for skipping a position are silent.

What honours the list, and what deliberately does not

What honours it: the farmer, in the galaxy *and* when picking its attacks; the spyer, because probing leaves a trace exactly as attacking does; and FindInactivePlanetWithMinimumTravelTime, the only one of the four searches that names a player.

What does not, deliberately: the Galaxy screen, where you must still see whom you spare, and the night fleet save — where you park a fleet has nothing to do with whom you raid.

The hunter's spy button warns in the log, and sends anyway.

The hunter's button is a deliberate act on players you named by hand: refusing the send in silence would take back a choice you have just made. The contradiction is reported, not arbitrated.

Nor is this list the Defender whitelist, which says the opposite: "those I do not defend myself against". Sparing an alliance of two hundred members must never stop you dodging their attacks.

Two signatures differ from the reference documentation

It takes an int64 everywhere. We cannot follow it on two points, and those two functions refuse a number rather than accepting it silently:

  • IgnorePlanet expects a coordinate, not a planet id. The game does give that id when reading the galaxy, but the readings only keep the coordinate: an id would be stored and would never catch anything.
  • IgnoreAlliance expects the tag, not an alliance id. v13 gives no alliance id in galaxy data at all.

A tag is compared without case or spaces. It can change overnight, where a player id never does: that is the limit of the notion, not a shortcoming here.

A player absent from the readings is refused

As with the watch list: IgnorePlayer resolves the name from the Scanner's readings and refuses what it cannot find there. Storing an entry that would never match anyone would make the script believe it spares someone.

An alliance, on the other hand, can be added before the Scanner has been past: its members are recognised as the readings come in.

The farmer's blacklist

What the farmer no longer spies on or attacks. It does not play the same part as the spared list: that one protects friends, and also applies to spying; the blacklist sets aside targets that no longer produce, and only applies to the farmer.

A position enters it on its own once three reports in a row show exactly the same resources, below the campaign's minimum loot: the planet no longer produces, its storage is full but small. A report that changes or is worth a raid resets its count, and takes it off the list if it got there on its own. Such an entry only holds for the player who owned the planet: taken over by someone else, it is spied on again, and only set aside after three identical reports under the new owner. It only sets aside the planet, not its moon. A position added by hand sets aside both, and never leaves the list on its own.

It is shown and managed in the "Blacklist" tab of the Farmer page, or from scripts. Additions and removals take effect from the farmer's next pass.

FunctionReturns
GetFarmBlacklist()2: the list and an errorThe whole list, players and positions together.
AddToFarmBlacklist(target)1: an errorA player, by name or id, or a position.
RemoveFromFarmBlacklist(target)1: an errorRemoving what is not there is not an error; a name nothing knows is one.
ClearFarmBlacklist()1: an errorEmpties it, automatic entries included.
AddToFarmBlacklist("1:42:8")      // by hand: the planet AND its moon
AddToFarmBlacklist("Xylo")        // a player, by name
AddToFarmBlacklist(4242)          // or by id

entries, err = GetFarmBlacklist()
for e in entries {
	// e.Kind is "player" or "planet", as with GetIgnored;
	// e.Source is "auto" or "manual".
	// e.Key holds the id or the coordinate; e.Label is for display.
	if e.Source == "auto" {
		RemoveFromFarmBlacklist(e.Key)   // give back only the automatic entries
	}
}

ClearFarmBlacklist()

A position is named without its type: "M:1:42:8" and "1:42:8" are the same entry. A player missing from the Scanner's readings is refused when added, but can always be removed: by id, by the name shown in the list, or by e.Key as GetFarmBlacklist returns it.

Removing a position, or clearing the list, also resets its count of identical spy reports: it only comes back after three new identical reports, not the first one.

Composing a watch list

StartHunter() switches the watch on. It does not say who to watch: that list lives in the database and changes as the game goes. Switching the hunter on over an empty list surveys nothing.

FunctionReturns
GetHunterTargets()2: the list and an errorThe players under watch.
AddHunterTarget(player)1: an errorPuts a player under watch.
RemoveHunterTarget(player)1: an errorRemoves them, history included.
PauseHunterTarget(player, paused)1: an errorSuspends them without losing their history.
AddHunterTarget("Xylo")          // by name, as on screen
AddHunterTarget(4242)            // or by id
StartHunter()

targets, err = GetHunterTargets()
for c in targets {
	Printf("%s: %d positions, activity %d", c.PlayerName, c.Positions, c.Activity)
}

The name works as well as the id. The reference tool takes only the id; the name is what you read on screen, and so what a script writes most often. Both forms lead to the same target: the bot resolves the name from its galaxy readings, once, on adding — because a player can rename themselves, their id cannot.

Adding the same player twice does not duplicate them. The list is kept by id.

A player absent from the readings is refused

err = AddHunterTarget(999999)
// player no. 999999 not found in the readings:
// has the Scanner been past them yet?

This is our second departure from the reference tool, which accepts any id. The watch works on known positions: on each pass it surveys the celestials the Scanner has seen. A target with no known position would therefore never return anything, and your script would believe it was watching someone.

The refusal tells you what to do: let the Scanner cover the player's area, then call AddHunterTarget again.

What a target carries

Field
PlayerID, PlayerName, Alliance, RankWho they are.
PositionsHow many celestials are known to be theirs.
ActivityThe last survey of all their positions: 15 when one is active right now, otherwise the shortest timer found, 0 when there is nothing anywhere.
PausedThe watch is suspended.
NotifyTimerAn alert is armed on their return.
LastCheck, AddedAtUnix dates, 0 if never surveyed.

Suspend rather than remove

RemoveHunterTarget also erases the player's activity history, which is the whole point of a long-standing watch. PauseHunterTarget(player, true) stops the surveys and keeps the series: that is what you want while a target is on holiday.

Two traps

Sleep mode is not a worker like the others. StartSleepMode() does not put the account to sleep there and then: it switches the worker on, and that worker then follows the schedule set in the panel. If the mode is not automatic and no manual sleep is set, nothing at all will happen.

When the sleep window does open, however, the session closes and your script stops running along with the other workers. A script that switches sleep on can therefore stop itself later, at an hour it did not choose.

Switching a tool on switches the rest off. The phalanx, debris field watching and the other tools are exclusive: the bot runs only one, and switching one on switches the ordinary workers off. That is the interface's rule, and it applies here too. A StartWorker("phalanx") can therefore stop your farming without you asking for it.

One script driving others

FunctionReturns
GetScripts()1: a list of namesEvery script on the account.
GetRunningScripts()1: a list of namesThose running right now.
IsScriptRunning(name)1: a booleanIs that one running.
IsPausedScript(name)1: a booleanIs that one paused.
StartScript(name)1: an errorStarts it and returns at once.
StopScript(name)1: an errorStops it.
PauseScript(name)1: an errorSuspends it.
ResumeScript(name)1: an errorResumes it where it was.

What this is for. A script runs end to end in a single thread: what it does, it does in sequence. An account that wants a night watch, a farm and a daily report therefore has three scripts — and something has to decide which one runs when. That is the job of a conductor script: it does not act on the game, it turns the others on and off.

// The conductor: the watch at night, the report in the morning.
CronExec("@22h00", func() {
	StartScript("night-watch")
})
CronExec("@08h00", func() {
	StopScript("night-watch")
	StartScript("report")
})

The .ank suffix is optional. StartScript("watch") and StartScript("watch.ank") name the same script: storage adds the suffix by itself, and demanding it would fail half the calls on a script that is right there.

StartScript returns at once, it does not wait for the started script to finish. And the started script does not die with the one that started it: once away, the two are independent.

Three refusals worth knowing.

  • A script does not restart itself: that means nothing.
  • A script does not pause itself. It would be a permanent block: nobody is left running to resume it, and the account keeps a frozen script that looks alive.
  • A script may stop itself, and that is a clean exit. The gesture does not block: it cancels, and the script stops at the next statement. Exit() does the same thing under another name.

"Paused" is not "stopped". A paused script is absent from GetRunningScripts, but starting it again would send it back to its first line. IsPausedScript exists for that distinction: read it before choosing between StartScript and ResumeScript.

Knowing what the farm is doing

FunctionReturns
IsFarmSessionOngoing()1: a booleanIs a campaign working right now.
IsPausedFarmingBot()1: a booleanIs a campaign paused.
FarmingBotSessionsCount()1: an integerHow many campaigns are running.
PauseFarmingBot()1: an errorSuspends the running campaign.
ResumeFarmingBot()1: an errorResumes it where it stopped.
AbortAllFarmingSessions()1: an errorStops everything and removes every campaign, the running one and those from the Farmer tab included.

A paused campaign makes IsFarmSessionOngoing return false: it is not working. The distinction matters — a script waiting for a campaign to end before starting something else would otherwise wait forever in front of a campaign nobody will resume.

A difference from Ninja, better read here than in game. The reference tool runs several campaigns at once; Kepler runs one at a time, because two would fight over slots and probes and nobody could tell which one emptied what. FarmingBotSessionsCount is therefore zero or one, never more: a copied script comparing it to three will never get its match.

Repatriating right now

FunctionReturns
RepatriateNow()1: an errorRuns a repatriation pass without waiting.

The worker sleeps between passes. A script that has just emptied a planet wants the resources to leave now, not at the next wake-up.

It sets nothing: destinations, thresholds and ships stay as configured in the panel. It only moves the clock forward. The worker must be on — triggering a worker that is off would do nothing, and the error says so.

Gathering everything in one place

FunctionReturns
RepatriateSetAllDestinations(celestial)1: an errorThat celestial receives, all the others feed it.

Read the name to the end: "All". The given celestial becomes the destination, and all the others become origins. The previous list of origins is REPLACED, not added to: a colony you had set aside — while building there — comes back in.

The other destinations are wiped. The settings page accepts several, and each celestial repatriates to the nearest one; this call leaves exactly one, yours. That is what the name promises, but an account with three banks has only one left after the call.

It launches nothing: RepatriateNow triggers a pass.

RepatriateSetAllDestinations("1:64:8")
RepatriateNow()

The Brain queue

FunctionReturns
ClearAllConstructionQueues()1: an errorEmpties the queue of every celestial in Queue mode.
AddItemToQueue(celestial, what, number)1: an errorAdds a line at the end of a celestial's queue.

Both write the queue that Brain reads, exactly like the Brain page does: they start nothing in the game. Brain does the building afterwards, on its pass, if it is switched on (StartBrain()).

A Kepler line is a level to reach, not an action. In the reference tool, AddItemToQueue(c, SOLARPLANT, 0) means "one more level". Here the new line aims at the next level after what is already built, under construction or already asked for in the queue, so two calls in a row do ask for two levels, and a solar plant whose level 11 is being built gets the line "solar plant 12". The current level and the construction in progress are read from the game at call time, two or three requests.

  • Building, research, lifeform: the number is ignored, except -1.
  • Ship, defense: the number is a quantity, at least one. AddItemToQueue(c, BOMBER, 0) returns an error.
  • -1, tearing down, is not supported and returns an error that says so: the queue cannot go down a level. TearDown tears down right away.
  • A lifeform item that does not belong to the celestial's species is refused: its page does not show it, so its level cannot be read.
  • What the Brain page does not offer is refused: the crawler, a mine or a research on a moon, a lifeform item on a moon. The line would be dead.
  • A ship reserved for another class is refused: the Reaper outside the General class, the Pathfinder outside the Discoverer class. The game would not build it.

A celestial that is off with an empty queue switches to Queue mode: a line in a queue nobody reads would do nothing. A celestial off that keeps a queue is refused, with nothing touched: switching it back on would also build its old lines. A celestial in AI mode is refused too: switching it to Queue would throw its plan away. Either way, that is your call to make on the page.

ClearAllConstructionQueues only empties celestials in Queue mode. The queue kept by a celestial in AI mode or switched off stays as it is, just like with the page's "Empty all queues" button.

During the built-in browser's manual mode, ClearAllConstructionQueues goes through at once: it only touches the bot's queue and asks nothing of the game. AddItemToQueue, on the other hand, waits for the mode to end, since it may have to read the celestial's level in the game.

ClearAllConstructionQueues()
c = GetCachedCelestial("1:2:3")
err = AddItemToQueue(c.GetID(), METALMINE, 0)
if err != nil { LogError("queue:", err) }
AddItemToQueue(c.GetID(), METALMINE, 0)  // one more level again
AddItemToQueue(c.GetID(), BOMBER, 5)

What the bot is doing with this account

FunctionReturns
IsStarted()1: a booleanIs the bot running for this account.
IsActive()1: a booleanWill it start it again next launch.

The two questions look alike and do not have the same answer. IsActive reads the account's CHECKBOX: an active but stopped account will come back, an inactive one will not. IsStarted reads the live state.

Seen from inside a script, IsStarted is always true — a script does not run on a stopped bot. It is useful after a long wait: the session may have closed in the meantime.

Sleep, and the Defender's interval

FunctionReturns
GetNextSleepTime()1: a time, or nilThe next time it goes to sleep.
GetNextWakeTime()1: a time, or nilThe next time it wakes.
SetDefenderCheckInterval(min, max)1: an errorThe round's interval, in seconds.
TriggerFleetSave()1: an errorSends the fleet out now, using the sleep-mode settings.

Both times are the ones that will actually happen, random offset included. The bot shifts its bedtime by a few minutes so it does not shut down on the round second every evening; a script trusting the time typed into the panel would get cut off mid-gesture.

end = GetNextSleepTime()
if end != nil && end.Sub(Now()).Minutes() < 10 {
	LogInfo("the account sleeps soon, not starting the campaign")
	return
}

nil means something: sleep is not automatic, or the session is not open. That is why these two return a pointer rather than a date — a zero date would read as year 1, and a script comparing times would see a bedtime already past.

SetDefenderCheckInterval takes TWO bounds, in seconds, and the bot draws at random between them on every round: a fixed interval is recognisable from the other side, which is the whole point of having two. The setting is saved, like the fields in the panel. A bound that is zero or the wrong way round is refused rather than silently corrected.

TriggerFleetSave sends the fleet out now, using the celestials ticked in sleep mode, their destinations and their splitting. It sets nothing: a script that wants to send something else composes its own flight with SendFleet.

It works even when sleep mode is off, and that is deliberate. Fleet save is configured under sleep mode, but whoever does not use sleep mode is precisely the one the gesture serves most.

It waits until the end. Several celestials splitting their fleets take tens of minutes; the script gets control back once everything has left, which is the only way to write "save the fleet THEN shut everything down". Stopping the script stops the fleet save.

The error is raised only if NOTHING left - closed session, no celestial ticked, a version without fleet save. A partial departure does not raise it: it goes to the log and to a full-screen warning, just as when the button is clicked, because it is the player who can do something about it, not the script. The reference documentation returns nothing at all; a TriggerFleetSave() copied as-is therefore works here too.

err = TriggerFleetSave()
if err != nil {
	LogWarn("nothing left: " + err.Error())
}

Pausing and stopping are not the same thing, and that is what justifies two functions rather than one. PauseFarmingBot keeps the progress: resuming picks up at the system reached. StopFarmingBot erases it: the campaign will start again from the first. On a three-hundred-system campaign, that is a night's work.

if IsFarmSessionOngoing() {
	PauseFarmingBot()          // giving the slots back to the Defender
	SleepMin(30)
	ResumeFarmingBot()         // and back to where we left off
}

ResumeFarmingBot finds which one on its own: only one campaign at a time can be paused. With none paused it returns an error rather than starting whichever it finds - starting it would send it back to the first system, which is exactly what we were avoiding.

AbortAllFarmingSessions is the "start from scratch" gesture, and it works exactly as in Ninja: it removes every campaign, whatever its state - running, paused, queued or finished -, the ones your scripts composed as well as the ones from the Farmer tab. A script that aborts in the evening and composes again in the morning from the celestial where its fleet spent the night no longer finds yesterday's campaigns under the new ones.

AbortAllFarmingSessions()   // in the evening: nothing runs, nothing from yesterday

During the built-in browser's manual mode, AbortAllFarmingSessions goes through at once: it only removes campaigns and asks nothing of the game.

The ones from the Farmer tab go too, even the one running. A Planning that named a removed campaign says so at its next edge, as for a campaign deleted by hand: it keeps the one running if there is one, and otherwise leaves farming off rather than switching it on with nothing to do. The screen's "Stop everything" button removes nothing.

Raids already in flight are not recalled: they come home normally, and nothing relaunches a removed campaign - not their return, not the queue, not a restart of the bot, not a Kepler tab left open since the day before, whatever its page.

The daily loot history is kept per account, not per campaign: removing a campaign erases nothing from it. But the loot of a wave still flying when you abort does not go into it, and the loot of waves that are back but not yet counted only goes in the next time farming is switched on, provided the bot has not restarted in between. The same goes for "Stop everything".

Call StartFarmingBot() after composing the day's campaigns: right after the abort there is nothing left to start and it returns an error; and if a campaign was composed in the meantime, on screen for instance, it starts that one and the new ones queue up behind it.