|
|
|
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()); |