Ten scripts that work
The previous pages describe the functions one by one. This one gives whole scripts, to paste as they are: each solves a problem the workers cannot solve on their own.
They are checked at every release — a test re-reads this page, parses each block and refuses any function that no longer exists. What you read here starts.
Change the values marked // ← yours before running anything: coordinates, thresholds, addresses.
fmt and strings must be imported, they are not provided: Sprintf and Contains do not exist on their own. It is the most frequent mistake when copying a script, and the error it gives — undefined symbol 'Sprintf' — does not say that an import is missing.
1. The night watch
It waits for an attack, warns on Discord, and stays quiet the rest of the time. No request to the game while nothing happens.
fmt = import("fmt")
THRESHOLD_SECONDS = 900 // ← yours: alert under fifteen minutes
LogInfo("night watch in place")
for {
a, seen = WaitAttack(0) // 0: wait with no limit
if !seen {
break // the script is stopping, leave cleanly
}
text = fmt.Sprintf("%s is attacking %s, impact in %.0f min",
a.AttackerName, a.DestinationName, a.ArriveIn / 60)
LogWarn(text)
SendDiscord(DISCORD_WEBHOOK, text)
if a.ArriveIn < THRESHOLD_SECONDS {
SendTelegram(0, "URGENT: " + text)
}
}
WaitAttack(0) returns two values: the attack, and a boolean saying whether it came. The boolean turns false when the script stops — that is the loop's exit.
2. Bringing the fleet home at an unpredictable hour
Half past three on the dot, every night, gets noticed. RangeCronExec draws the time at random inside a window.
fmt = import("fmt")
ORIGIN = "1:224:8" // ← yours
SHELTER = "1:224:9" // ← yours: a moon or planet of your own
RangeCronExec("03:00:00", "03:45:00", "sheltering", func() {
c = GetCachedCelestial(ORIGIN)
fleet, err = GetShips(c.GetID())
if err != nil {
LogError("fleet unreadable:", err)
return
}
f = NewFleet()
f.SetOrigin(ORIGIN)
f.SetDestination(SHELTER)
f.SetSpeed(TEN_PERCENT)
f.SetMission(PARK)
f.SetAllShips()
f.SetAllResources()
sent, err = f.SendNow()
if err != nil {
LogError("sheltering refused:", err)
SendDiscord(DISCORD_WEBHOOK, "the fleet did not leave: " + fmt.Sprintf("%v", err))
return
}
LogInfo("fleet away, back at", sent.BackTime)
})
<-OnQuitCh // the script lives until you stop it
Without <-OnQuitCh the script would end at once, taking its task with it.
3. Where to send to come home after you wake up
When you have no moon to park on, you need a destination far enough for the round trip to cover the night.
ORIGIN = "1:224:8" // ← yours
HOURS = 7 // ← yours: the length of your night
c = GetCachedCelestial(ORIGIN)
fleet, err = GetShips(c.GetID())
if err != nil {
LogError("fleet unreadable:", err)
return
}
dest, secs, deut, err = FindDebrisFieldWithMinimumTravelTime(c.GetCoordinate(),
fleet, HOURS * 3600)
if err != nil {
LogWarn("no debris field far enough:", err)
dest, secs, deut, err = FindEmptyPlanetWithMinimumTravelTime(c.GetCoordinate(),
fleet, HOURS * 3600)
}
if err != nil {
LogError("nothing far enough, widen the Scanner's area:", err)
return
}
Printf("%v: %.1f h outbound, %d deuterium", dest, secs / 3600, deut)
These functions cost no request: they read the surveys the Scanner has already brought back. They compute at the slowest speed, the one that stays out longest.
Note the %.1f, not %d. In anko a division returns a float, even between two integers: secs / 3600 is 8.5875, not 8. With %d the line comes out as %!d(float64=8.5875) — measured while playing this recipe on a real account. See Toolbox.
4. Answering a private message
The chat channel wakes the script the second someone writes.
strings = import("strings")
for {
m, seen = WaitChatMessage(0)
if !seen {
break
}
if !m.Private() {
continue // the alliance room is not for us
}
LogInfo(m.SenderName + " writes: " + m.Text)
if strings.Contains(strings.ToLower(m.Text), "gg") {
SendMessage(m.SenderID, "gg to you too")
} else {
SendDiscord(DISCORD_WEBHOOK, m.SenderName + ": " + m.Text)
}
}
Private() is the question that decides everything: a script answering the whole alliance room would soon be unbearable.
5. Watching a target and recalling your fleet
The two moments of a watch, in a single script.
fmt = import("fmt")
PLAYER = "Xylo" // ← yours
err = AddHunterTarget(PLAYER)
if err != nil {
LogError("cannot put under watch:", err)
return
}
StartHunter()
for {
e, seen = WaitHunterTargetActive(0)
if !seen {
break
}
text = fmt.Sprintf("%s is back (before: %d min)", e.PlayerName, e.PreviousActivity)
LogWarn(text)
SendDiscord(DISCORD_WEBHOOK, text)
}
A target waking up while your fleet is out is exactly the moment to recall it. WaitHunterTargetIdle does the opposite: it warns when the place frees up.
6. The morning report
Once a day, what the account did overnight.
fmt = import("fmt")
CronExec("@08h00", func() {
c = GetCachedCelestials()
year, month, day = Date()
lines = fmt.Sprintf("report for %d/%d/%d\n", day, month, year)
total = 0
for i = 0; i < len(c); i++ {
res, err = GetResources(c[i].GetID())
if err != nil {
continue
}
total = total + res.Metal + res.Crystal + res.Deuterium
Sleep(500) // do not chain reads back to back
}
colonies, possible = CountColonies()
lines = lines + fmt.Sprintf("%d celestials, %d/%d colonies\n", len(c), colonies, possible)
lines = lines + fmt.Sprintf("resources in store: %d\n", total)
reports, err = GetEspionageReportMessages()
if err == nil {
lines = lines + fmt.Sprintf("%d espionage reports in the box\n", len(reports))
}
SendDiscord(DISCORD_WEBHOOK, lines)
})
<-OnQuitCh
Sleep(500) between two reads: the pace of requests is the first thing that gets you noticed, and a report is never in a hurry.
7. Handing the evening back to the player
Farming stops at the hour you play, and resumes when you go to bed.
EVENING_START = "20:00:00" // ← yours
EVENING_END = "23:30:00" // ← yours
CronExec(EVENING_START, func() {
err = StopFarmingBot()
if err != nil {
LogError("farming could not stop:", err)
return
}
LogInfo("farming paused, the evening is yours")
})
CronExec(EVENING_END, func() {
err = StartFarmingBot()
if err != nil {
LogError("farming could not resume:", err)
return
}
LogInfo("farming resumed")
})
<-OnQuitCh
Switching a tool on switches the rest off. The phalanx and debris field watching are exclusive: the bot runs only one, and switching it on switches the ordinary workers off. That is the interface's rule, and it applies to scripts — a StartWorker("phalanx") would stop your farming without you asking. See Driving the workers.
The farming functions, for their part, touch only farming.
8. The conductor
A script runs end to end in a single thread: the night watch and the morning report cannot live in the same one. This one does not act on the game — it turns the others on and off.
WATCH = "night-watch" // ← yours: the names of your scripts
REPORT = "report"
CronExec("@22h00", func() {
if IsScriptRunning(WATCH) {
LogInfo("the watch is already running")
return
}
err = StartScript(WATCH)
if err != nil { LogError("watch:", err) }
})
CronExec("@08h00", func() {
// "Paused" is not "stopped": resuming keeps the progress, starting
// again goes back to the first line.
if IsPausedScript(WATCH) {
ResumeScript(WATCH)
}
StopScript(WATCH)
StartScript(REPORT)
})
<-OnQuitCh
The .ank suffix is optional: "night-watch" and "night-watch.ank" name the same script.
A script does not pause itself, and that is an explicit refusal: nobody would be left running to resume it. It may stop itself, and that is a clean exit.
9. Home by eight, at the lowest price
A fleet save is thought of by its end. The backwards computation looks for the slowest speed that still arrives on time — the cheapest in deuterium among those you can still take.
fmt = import("fmt")
ORIGIN = "1:64:8" // ← yours
SHELTER = "1:64:9" // ← yours
BACK = "08:00:00" // ← yours: everything home by then
ships = NewShipsInfos()
ships.Set(LARGECARGO, 20)
speed, deut, when, err = LowestSpeed(BACK, ORIGIN, SHELTER, ships)
if err != nil {
LogError("no speed gets home on time:", err)
return
}
LogInfo(fmt.Sprintf("leave at %s, %v%% speed, %d deuterium",
when.Format("15:04"), speed * 10, deut))
<-ExecAtCh(when, func() {
// One slot stays with the Defender: it is the one that dodges.
slots = GetSlots()
if slots.Total - slots.InUse <= GetFleetSlotsReserved() {
LogWarn("no room left, leaving the Defender some air")
return
}
f = NewFleet()
f.SetOrigin(ORIGIN)
f.SetDestination(SHELTER)
f.SetMission(PARK)
f.SetSpeed(speed)
f.AddShips(LARGECARGO, 20)
f.SetAllResources()
fleet, err = f.SendNow()
if err != nil {
LogError("send:", err)
return
}
LogInfo("fleet away, flight", fleet.ID)
})
LowestSpeed costs six requests: one for the bound, then at most five to binary-search the account's speeds (twenty for a General, ten for the other classes). Call it once, when planning, never in a loop over a hundred targets.
A departure exactly now does not count: by the time you compose the fleet and click, the second has passed, and the next speed up is kept.
10. The morning harvest
Chapters hand out resources for tasks already done. There is no reason not to collect them, except that you have to remember every day.
CHAPTER = 4006 // ← yours: read from the page's address
BASE = "1:64:8" // ← yours
CronExec("@09h00", func() {
err = ChapterClaimAll(CHAPTER)
if err != nil {
LogWarn("chapter:", err) // nothing to collect is not a failure
}
// Then gather: the base receives, everything else feeds it.
err = RepatriateSetAllDestinations(BASE)
if err != nil {
LogError("repatriation:", err)
return
}
err = RepatriateNow()
if err != nil { LogError("repatriation:", err) }
})
<-OnQuitCh
RepatriateSetAllDestinations replaces the list of origins, it does not add to it: a colony you had set aside comes back in. Read the name to the end.
The worker must be on for RepatriateNow to do anything: triggering a worker that is off would do nothing, and the error says so.
What they have in common
Checking the return value. Every call that can fail is tested. A script that ignores its errors runs in appearance and does nothing — the costliest defect there is, because nothing signals it.
<-OnQuitCh at the end of any script that schedules. Without it the script ends and its tasks die with it.
The pace. Sleep between two reads that touch the game. The cache functions — GetCachedCelestials, GetCachedPlayer — do not need it: they ask for nothing.
The webhooks are the ones from your settings. DISCORD_WEBHOOK and SendTelegram(0, …) take what you filled in on the Notifications page; nothing secret sits in your files.