Created
February 20, 2022 02:11
-
-
Save Bind/269457dfdfbd7764b4b9424a18f30b2f to your computer and use it in GitHub Desktop.
df repeat attack plugin
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//@ts-ignore | |
import { isUnconfirmedMoveTx } from "https://cdn.skypack.dev/@darkforest_eth/serde"; | |
import { | |
html, | |
render, | |
useEffect, | |
useState, | |
useLayoutEffect, | |
//@ts-ignore | |
} from "https://unpkg.com/htm/preact/standalone.module.js"; | |
function unconfirmedDepartures(planet) { | |
return ( | |
planet.transactions | |
?.getTransactions(isUnconfirmedMoveTx) | |
//@ts-ignore | |
.reduce((acc, tx) => acc + tx.intent.forces, 0) || 0 | |
); | |
} | |
function planetCurrentPercentEnergy(planet) { | |
const departures = unconfirmedDepartures(planet); | |
const estimatedEnergy = Math.floor(planet.energy - departures); | |
return Math.floor((estimatedEnergy / planet.energyCap) * 100); | |
} | |
// I use `let` here to sidestep any weird execution env problems | |
let PERCENTAGE_TRIGGER = 75; | |
let PERCENTAGE_SEND = 45; | |
class Repeater { | |
attacks = []; | |
intervalId; | |
constructor() { | |
if (typeof window.__CORELOOP__ == "undefined") { | |
//setup append only interval id storage | |
window.__CORELOOP__ = []; | |
} else { | |
//clear out old intervald | |
console.log("KILLING PREVIOUS INTERVALS"); | |
window.__CORELOOP__.forEach((id) => window.clearInterval(id)); | |
} | |
this.attacks = []; | |
this.intervalId = window.setInterval(this.coreLoop.bind(this), 15000); | |
window.__CORELOOP__.push(this.intervalId); | |
} | |
addAttack(srcId, targetId) { | |
this.attacks.push({ srcId, targetId }); | |
} | |
removeAttack(position) { | |
this.attacks.splice(position, 1); | |
} | |
coreLoop() { | |
this?.attacks?.forEach((a) => { | |
ExecuteAttack(a.srcId, a.targetId); | |
}); | |
} | |
} | |
const ExecuteAttack = (srcId, targetId) => { | |
let srcPlanet = df.getPlanetWithId(srcId); | |
if (!srcPlanet) { | |
// Well shit | |
return; | |
} | |
// Needs updated check getUnconfirmedDepartingForces | |
const departingForces = unconfirmedDepartures(srcPlanet); | |
const TRIGGER_AMOUNT = Math.floor( | |
(srcPlanet.energyCap * PERCENTAGE_TRIGGER) / 100 | |
); | |
const FUZZY_ENERGY = Math.floor(srcPlanet.energy - departingForces); //Best estimate of how much energy is ready to send | |
if (FUZZY_ENERGY > TRIGGER_AMOUNT) { | |
const overflow_send = | |
planetCurrentPercentEnergy(srcPlanet) - | |
(PERCENTAGE_TRIGGER - PERCENTAGE_SEND); | |
const FORCES = Math.floor((srcPlanet.energyCap * overflow_send) / 100); | |
df.move(srcId, targetId, FORCES, 0); | |
} | |
}; | |
let Spacing = { | |
marginLeft: "12px", | |
marginRight: "12px", | |
}; | |
let VerticalSpacing = { | |
marginBottom: "12px", | |
}; | |
let HalfVerticalSpacing = { | |
marginBottom: "6px", | |
}; | |
let Clickable = { | |
cursor: "pointer", | |
textDecoration: "underline", | |
}; | |
let ActionEntry = { | |
marginBottom: "5px", | |
display: "flex", | |
justifyContent: "space-between", | |
color: "", | |
}; | |
function centerPlanet(id) { | |
ui.centerLocationId(id); | |
} | |
function planetShort(locationId) { | |
return locationId.substring(4, 9); | |
} | |
function Attack({ attack, onDelete }) { | |
return html` | |
<div style=${ActionEntry}> | |
<span> | |
<span | |
style=${{ ...Spacing, ...Clickable }} | |
onClick=${() => centerPlanet(attack.srcId)} | |
>${planetShort(attack.srcId)}</span | |
> | |
=> | |
<span | |
style=${{ ...Spacing, ...Clickable }} | |
onClick=${() => centerPlanet(attack.targetId)} | |
>${planetShort(attack.targetId)}</span | |
></span | |
> | |
<button onClick=${onDelete}>X</button> | |
</div> | |
`; | |
} | |
function AddAttack({ onCreate }) { | |
let [planet, setPlanet] = useState(ui.getSelectedPlanet() || undefined); | |
let [source, setSource] = useState(undefined); | |
let [target, setTarget] = useState(undefined); | |
useLayoutEffect(() => { | |
let onClick = () => { | |
setPlanet(ui.getSelectedPlanet()); | |
}; | |
window.addEventListener("click", onClick); | |
return () => { | |
window.removeEventListener("click", onClick); | |
}; | |
}, []); | |
return html` | |
<div style=${{ display: "flex" }}> | |
<button style=${VerticalSpacing} onClick=${() => setSource(planet)}> | |
Set Source | |
</button> | |
<span style=${{ ...Spacing, marginRight: "auto" }} | |
>${source ? planetShort(source.locationId) : "?????"}</span | |
> | |
<button style=${VerticalSpacing} onClick=${() => setTarget(planet)}> | |
Set Target | |
</button> | |
<span style=${{ ...Spacing, marginRight: "auto" }} | |
>${target ? planetShort(target.locationId) : "?????"}</span | |
> | |
<button | |
style=${VerticalSpacing} | |
onClick=${() => | |
target && source && onCreate(source.locationId, target.locationId)} | |
> | |
start | |
</button> | |
</div> | |
`; | |
} | |
function AttackList({ repeater }) { | |
const [attacks, setAttacks] = useState([...repeater.attacks]); | |
useEffect(() => { | |
const id = setInterval(() => { | |
setAttacks([...repeater.attacks]); | |
}, 1000); | |
setAttacks([...repeater.attacks]); | |
return () => clearInterval(id); | |
}, [attacks.length]); | |
let actionList = { | |
maxHeight: "70px", | |
overflowX: "hidden", | |
overflowY: "scroll", | |
}; | |
//@ts-ignore | |
let actionsChildren = attacks.map((action, index) => { | |
return html` | |
<${Attack} | |
attack=${action} | |
onDelete=${() => { | |
repeater.removeAttack(index); | |
}} | |
/> | |
`; | |
}); | |
return html` | |
<h1>Set-up a Recurring Attack</h1> | |
<i style=${{ ...VerticalSpacing, display: "block" }} | |
>Auto-attack when source planet >75% energy | |
</i> | |
<${AddAttack} | |
onCreate=${(source, target) => repeater.addAttack(source, target)} | |
/> | |
<h1 style=${HalfVerticalSpacing}> | |
Recurring Attacks (${actionsChildren.length}) | |
<button | |
style=${{ float: "right" }} | |
onClick=${() => setAttacks([...repeater.attacks])} | |
> | |
refresh | |
</button> | |
</h1> | |
<div style=${actionList}> | |
${actionsChildren.length ? actionsChildren : "No Actions."} | |
</div> | |
`; | |
} | |
function App({ repeater }) { | |
return html`<${AttackList} repeater=${repeater} />`; | |
} | |
class Plugin { | |
repeater; | |
container; | |
root; | |
stop() { | |
window.__CORELOOP__.forEach((id) => window.clearInterval(id)); | |
} | |
constructor() { | |
this.repeater = new Repeater(); | |
this.root = undefined; | |
} | |
/** | |
* Called when plugin is launched with the "run" button. | |
*/ | |
async render(container) { | |
this.container = container; | |
container.style.width = "380px"; | |
this.root = render(html`<${App} repeater=${this.repeater} />`, container); | |
} | |
/** | |
* Called when plugin modal is closed. | |
*/ | |
destroy() { | |
//@ts-ignore | |
window.__CORELOOP__.forEach((id) => window.clearInterval(id)); | |
if (this.container) render(html`<div></div>`, this.container); | |
} | |
} | |
/** | |
* And don't forget to export it! | |
*/ | |
export default Plugin; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment