Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?

@preston_vanloon's plugins

Be sure to watch the YouTube stream where we talk about how these plugins work in depth.

These two plugins helped me get into the top 10 of the dark forest score contest for v0.5.

auto-conquer is a plugin that would find the best planets to conquer. It is currently tweaked for late game, so you'll have to read the code and change parameters if you are just starting out!

auto-silver is a plugin to re-distribute silver mines to available planets. It's also tweaked for late game, ignoring silver mines with small amounts of silver.

auto-upgrade is a plugin to upgrade my planets with defense, range, and speed in that order. It's tweaked for late game too, only for high level planets.

const {
html,
render,
useState,
useEffect,
useLayoutEffect
} = await import('https://unpkg.com/htm/preact/standalone.module.js');
const {
eachLimit
} = await import('https://cdn.skypack.dev/async-es');
const {
hasArtifact,
} = await import('https://plugins.zkga.me/utils/utils.js');
let {
move,
} = await import('https://plugins.zkga.me/utils/queued-move.js');
const whitelist = [
"0x3755f7bc5f88894db84534acba8ac333442cb94d", // Agent K
"0x5f3d286f20cb174570957868a96f4fe252ae6b02", // bitcoin louis
"0x82d94dc968e69706728ba2b5996b1eb048e825d4", // Some unlucky noob that spawned near me
"0x67d715ba08d84a030105150bbee57d6cffb147cc", // lightsout
];
// Planets that should be taken in revenge.
const revengePlanets = [
// Agent K sent attacks from these planets.
"000017700009dba4d45193ee0bba6e9b062c55b31a33835614ae9b68fded8019",
"000039900021d769455bde90b7b326b367d66fa6696f352d212f8b9e18c58ca9",
]
function beep() {
var snd = new Audio("data:audio/wav;base64,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qJrx4KOQjahZxWbcZgztj2c49nKmkId44S71j0c8eV9yDK6uPRzx5X18eDvjvQ6yKo9ZSS6l//8elePK/Lf//IInrOF/FvDoADYAGBMGb7FtErm5MXMlmPAJQVgWta7Zx2go+8xJ0UiCb8LHHdftWyLJE0QIAIsI+UbXu67dZMjmgDGCGl1H+vpF4NSDckSIkk7Vd+sxEhBQMRU8j/12UIRhzSaUdQ+rQU5kGeFxm+hb1oh6pWWmv3uvmReDl0UnvtapVaIzo1jZbf/pD6ElLqSX+rUmOQNpJFa/r+sa4e/pBlAABoAAAAA3CUgShLdGIxsY7AUABPRrgCABdDuQ5GC7DqPQCgbbJUAoRSUj+NIEig0YfyWUho1VBBBA//uQZB4ABZx5zfMakeAAAAmwAAAAF5F3P0w9GtAAACfAAAAAwLhMDmAYWMgVEG1U0FIGCBgXBXAtfMH10000EEEEEECUBYln03TTTdNBDZopopYvrTTdNa325mImNg3TTPV9q3pmY0xoO6bv3r00y+IDGid/9aaaZTGMuj9mpu9Mpio1dXrr5HERTZSmqU36A3CumzN/9Robv/Xx4v9ijkSRSNLQhAWumap82WRSBUqXStV/YcS+XVLnSS+WLDroqArFkMEsAS+eWmrUzrO0oEmE40RlMZ5+ODIkAyKAGUwZ3mVKmcamcJnMW26MRPgUw6j+LkhyHGVGYjSUUKNpuJUQoOIAyDvEyG8S5yfK6dhZc0Tx1KI/gviKL6qvvFs1+bWtaz58uUNnryq6kt5RzOCkPWlVqVX2a/EEBUdU1KrXLf40GoiiFXK///qpoiDXrOgqDR38JB0bw7SoL+ZB9o1RCkQjQ2CBYZKd/+VJxZRRZlqSkKiws0WFxUyCwsKiMy7hUVFhIaCrNQsKkTIsLivwKKigsj8XYlwt/WKi2N4d//uQRCSAAjURNIHpMZBGYiaQPSYyAAABLAAAAAAAACWAAAAApUF/Mg+0aohSIRobBAsMlO//Kk4soosy1JSFRYWaLC4qZBYWFRGZdwqKiwkNBVmoWFSJkWFxX4FFRQWR+LsS4W/rFRb/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////VEFHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAU291bmRib3kuZGUAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAwNGh0dHA6Ly93d3cuc291bmRib3kuZGUAAAAAAAAAACU=");
snd.play();
}
function distanceSort(a, b) {
return a[1] - b[1];
}
function attackEfficiencySort(a, b) {
return a.forces - b.forces;
}
// Highest level first.
function levelSort(a, b) {
return b.planetLevel - a.planetLevel;
}
function lowestEnergySort(a, b) {
return a.energy - b.energy;
}
function highestEnergySort(a, b) {
return b.energy - a.energy;
}
function highestEnergyCapSort(a, b) {
return b.energyCap - a.energyCap;
}
function distance(fromLoc, toLoc) {
return Math.sqrt(
(fromLoc.coords.x - toLoc.coords.x) ** 2 +
(fromLoc.coords.y - toLoc.coords.y) ** 2
);
}
function planetPower(planet) {
return (planet.energy * planet.defense) / 100;
}
function planetPercentEnergy(planet, percentCap = 25) {
const unconfirmedDepartures = planet.unconfirmedDepartures.reduce(
(acc, dep) => {
return acc + dep.forces;
},
0
);
const FUZZY_ENERGY = Math.floor(planet.energy - unconfirmedDepartures);
return (FUZZY_ENERGY * percentCap) / 100;
}
function getEnergyArrival(fromId, toId, forces) {
const sync = df.getPlanetWithId(toId);
return (
df.getEnergyArrivingForMove(fromId, toId, forces) / (sync.defense / 100)
);
}
const concurrentAttackLimit = 6;
function isAttackingAtCapacity(attacking, target) {
return attacking[target.locationId] !== undefined && (attacking[target.locationId].count >= concurrentAttackLimit || attacking[target.locationId].energy > planetPower(target));
}
let VerticalSpacing = {
marginBottom: "12px",
};
function App() {
return html`
<h1 style=${VerticalSpacing}>
Want to conquer everything?
<button
style=${{ float: "right" }}
onClick=${() => {
conquer();
}}
>
Yes - Conquer!
</button>
</h1>
`;
}
function conquer(wait) {
if (wait) {
if (df.getUnconfirmedMoves().length > 0) {
console.log("There are unconfirmed moves. Not doing anything.")
return
}
}
const planetsUnderAttack = df.getAllVoyages().filter(v => v.player != df.account).map(v => df.getPlanetWithId(v.toPlanet)).filter(p => p.owner == df.account).map(p => p.locationId);
let planets = df.getMyPlanets()
.filter((p) => p.planetLevel > 1 && p.silverGrowth == 0 && (p.energy-p.unconfirmedDepartures.reduce((acc, curr) => acc += curr.forces, 0)) / p.energyCap > 0.75 && !planetsUnderAttack.includes(p.locationId))
.sort(highestEnergySort);
let attacking = df.getAllVoyages()
.filter((v) => v.player == df.account)
.reduce((acc, v) => {
if (!acc[v.toPlanet]) {
acc[v.toPlanet] = {count:0, energy:0};
}
acc[v.toPlanet].count++;
acc[v.toPlanet].energy += v.energyArriving;
return acc;
}, {});
// let moves = [];
let moves = df.getUnconfirmedMoves().length;
planets.forEach(planet => {
const fromId = planet.locationId;
const candidates = df
.getPlanetsInRange(fromId, 50)
.filter((p) => p.owner !== df.account && (p.planetLevel > 2 || hasArtifact(p))) // Ignore level 1 planets, waste of time, except to grief/protect from other players, also get artifacts.
.sort(highestEnergyCapSort).map(p => [p]); // map to be the same as the distance thing.
// .map((to) => {
// const fromLoc = df.getLocationOfPlanet(fromId);
// const toLoc = df.getLocationOfPlanet(to.locationId);
// return [to, distance(fromLoc, toLoc)];
// }).sort(distanceSort);
let attacked = false;
for(const candidate of candidates) {
if (moves >= 100) { // Limit unconfirmed moves to 100 to prevent OOM.
return;
}
const target = candidate[0];
if (isAttackingAtCapacity(attacking, target) || (target.owner === "0x0000000000000000000000000000000000000000" && target.planetLevel < 2) || whitelist.includes(target.owner)) {
continue;
}
const minEnergy = df.getEnergyNeededForMove(
fromId,
target.locationId,
1
);
let forces = Math.floor(
df.getEnergyNeededForMove(
fromId,
target.locationId,
planetPower(target) + target.energyCap*0.1 // Take planet and give it 10% energy.
));
// If this planet can attack, even a little bit, then do it.
if (forces >= planet.energy) {
if (minEnergy < planet.energyCap*0.85 && planet.energy >= planet.energyCap*0.9 && target.owner === "0x0000000000000000000000000000000000000000" && target.planetLevel > 4) {
forces = Math.floor(planet.energyCap*0.85)
} else {
continue;
}
}
if (attacking[target.locationId] === undefined) {
attacking[target.locationId] = {count:0, energy:0};
}
attacking[target.locationId].count++;
attacking[target.locationId].energy += getEnergyArrival(fromId, target.locationId, forces);
try {
move(fromId, target.locationId, forces, 0);
moves++;
} catch (e) {
console.error(e);
break;
}
attacked = true;
break;
}
// If there is nothing to attack and we are at max cap, give the energy to another planet.
if (!attacked && planet.energy >= planet.energyCap*0.99 && planet.unconfirmedDepartures.length === 0) { // Disabled for now.
const cs = df.getPlanetsInRange(fromId, 50)
.filter((p) => p.owner === df.account && p.energy < p.energyCap-10000 && p.planetLevel > planet.planetLevel)
.map((to) => {
const fromLoc = df.getLocationOfPlanet(fromId);
const toLoc = df.getLocationOfPlanet(to.locationId);
return [to, distance(fromLoc, toLoc)];
}).sort(distanceSort)
.map((p) => p[0]);
// Fill up other planets with at least 1k, to reduce these small moves.
for (const target of cs) {
let forces = Math.floor(
df.getEnergyNeededForMove(
fromId,
target.locationId,
1000,
));
if (forces > planet.energy*0.95) {
continue;
}
forces = Math.floor(planet.energy*0.95);
if (forces < 1000) {
continue;
}
try {
move(fromId, target.locationId, forces, 0);
moves++;
} catch (e) {
console.error(e);
}
break;
}
}
});
// moves.sort(attackEfficiencySort)
}
class AutoConquer {
constructor() {
this.root = null;
this.container = null;
}
/**
* Called when plugin is launched with the "run" button.
*/
async render(container) {
this.container = container;
container.style.width = "450px";
this.root = render(html`<${App} />`, container);
}
}
/**
* And don't forget to register it!
*/
plugin.register(new AutoConquer());
/**
* Silver rebalancer
* @prestonvanloon
*
* Work in progress...
*/
const {
html,
render,
} = await import('https://unpkg.com/htm/preact/standalone.module.js');
let {
move,
} = await import('https://plugins.zkga.me/utils/queued-move.js');
function distanceSort(a, b) {
return a[1] - b[1];
}
// Decending by how much silver is needed.
function silverCapSort(a, b) {
return (b.silverCap-b.silver)-(a.silverCap-a.silver);
}
//Descending by how much silver the planet has.
function silverSort(a, b) {
return b.silver - a.silver;
}
// Decending by points (highest first).
function pointsSort(a, b) {
return (b.energyCap*b.planetLevel)-(a.energyCap*b.planetLevel);
}
function distance(fromLoc, toLoc) {
return Math.sqrt(
(fromLoc.coords.x - toLoc.coords.x) ** 2 +
(fromLoc.coords.y - toLoc.coords.y) ** 2
);
}
function planetPower(planet) {
return (planet.energy * planet.defense) / 100;
}
function planetPercentEnergy(planet, percentCap = 25) {
const unconfirmedDepartures = planet.unconfirmedDepartures.reduce(
(acc, dep) => {
return acc + dep.forces;
},
0
);
const FUZZY_ENERGY = Math.floor(planet.energy - unconfirmedDepartures);
return (FUZZY_ENERGY * percentCap) / 100;
}
let VerticalSpacing = {
marginBottom: "12px",
};
function App() {
return html`
<h1 style=${VerticalSpacing}>
Redistribute full(ish) silver mines?
<button
style=${{ float: "right" }}
onClick=${() => {
rebalance();
}}
>
Yes - Distribute!
</button>
</h1>
`;
}
function rebalance(wait) {
if (wait) {
if (df.getUnconfirmedMoves().length > 0) {
console.log("There are unconfirmed moves. Not doing anything.")
return
}
}
let planets = df.getMyPlanets()
.filter((p) => p.silverGrowth > 0 && p.silver >= 1000 && p.silver >= p.silverCap*0.8)
.sort(silverSort);
let attacking = df.getAllVoyages()
.filter((v) => v.player == df.account && v.silverMoved > 0)
.reduce((acc, curr) => {
const ID = curr.toPlanet;
if (!acc[ID]) {
acc[ID] = 0;
}
acc[ID] += curr.silverMoved;
return acc;
}, {});
planets.forEach(planet => {
const fromId = planet.locationId;
const candidates = df
.getPlanetsInRange(fromId, 50)
.filter((p) => p.owner == df.account && p.planetLevel > 2 && p.silverCap > 0 && p.silver != p.silverCap && p.silverGrowth === 0)
.sort(silverCapSort);
for(const candidate of candidates) {
const target = candidate;
const FORCES = Math.floor(
df.getEnergyNeededForMove(
fromId,
target.locationId,
1,
));
if (FORCES >= planet.energy) {
continue;
}
let inFlight = attacking[target.locationId];
if (inFlight > 0) { //temporary disable this inflight stuff... It keeps sending too much silver.
continue;
} else {
inFlight = 0;
}
const silver = Math.floor(Math.min(planet.silver, target.silverCap-(target.silver+inFlight)));
if (silver < 100) {
continue;
}
move(fromId, target.locationId, FORCES, silver);
if (!attacking[target.locationId]) {
attacking[target.locationId] = 0;
}
attacking[target.locationId] += silver;
break;
}
});
}
class AutoSilver {
/**
* Called when plugin is launched with the "run" button.
*/
async render(container) {
this.container = container;
container.style.width = "450px";
this.root = render(html`<${App} />`, container);
}
}
/**
* And don't forget to register it!
*/
plugin.register(new AutoSilver());
const {
html,
render,
useState,
useLayoutEffect,
} = await import('https://unpkg.com/htm/preact/standalone.module.js');
const {
Defense,
Range,
Speed,
} = await import('https://plugins.zkga.me/game/Icons.js');
const {
UpgradeBranchName,
SpaceType,
canStatUpgrade,
canPlanetUpgrade,
} = await import('https://plugins.zkga.me/utils/utils.js')
function doUpgrade(planet, branch) {
if (planet && canPlanetUpgrade(planet) && canStatUpgrade(planet, branch)) {
df.upgrade(planet.locationId, branch)
}
}
function upgrade() {
let planets = df.getMyPlanets().filter(canPlanetUpgrade).filter(p => p.planetLevel > 3);
for (const planet of planets) {
if (planet.unconfirmedUpgrades.length > 0) {
continue;
}
if (canStatUpgrade(planet, UpgradeBranchName.Defense)) {
doUpgrade(planet, UpgradeBranchName.Defense);
} else if (canStatUpgrade(planet, UpgradeBranchName.Range)) {
doUpgrade(planet, UpgradeBranchName.Range);
} else if (canStatUpgrade(planet, UpgradeBranchName.Speed)) {
doUpgrade(planet, UpgradeBranchName.Speed);
}
}
}
let VerticalSpacing = {
marginBottom: "12px",
};
function App() {
return html`
<h1 style=${VerticalSpacing}>
Upgrade planets?
<button
style=${{ float: "right" }}
onClick=${() => {
upgrade();
}}
>
Yes - Upgrade!
</button>
</h1>
`;
}
class AutoUpgrade {
/**
* Called when plugin is launched with the "run" button.
*/
async render(container) {
this.container = container;
container.style.width = "450px";
this.root = render(html`<${App} />`, container);
}
}
/**
* And don't forget to register it!
*/
plugin.register(new AutoUpgrade());
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment