Add --pillars flag to show Circle of Pillars progress
Lists which species have a pillar, grouped by genus, with their ascension rank and the resulting production/storage bonus. --pillars-all also shows the species still missing a pillar. Race table mirrored from the game's src/races.js (63 species). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,7 +23,10 @@ node index.js --max-resources --no-copy < save.txt
|
|||||||
|
|
||||||
Saves are LZString `compressToBase64` of a JSON object (not plain base64). Uses the `lz-string` npm package to match the game's own JS implementation.
|
Saves are LZString `compressToBase64` of a JSON object (not plain base64). Uses the `lz-string` npm package to match the game's own JS implementation.
|
||||||
|
|
||||||
Key top-level sections: `resource`, `race`, `tech`, `city`, `space`, `civic`, `genes`, `prestige`, `settings`, `arpa`.
|
Key top-level sections: `resource`, `race`, `tech`, `city`, `space`, `civic`, `genes`, `prestige`, `settings`, `arpa`, `pillars`.
|
||||||
|
|
||||||
|
### Pillars (`pillars`)
|
||||||
|
Flat map of `<species key>: <rank>` — only species with a pillar appear. Rank is the ascension `alevel()` (1–5, +1 per challenge gene active on that run), *not* the universe. 63 species total (all races except `protoplasm`); the race table lives in `RACES` in `index.js`, mirrored from the game's `src/races.js`. Pillar bonus: +1% production / +2% storage per pillar, with the currently-played species counting 4×.
|
||||||
|
|
||||||
### Prestige structure (`prestige.<name>`)
|
### Prestige structure (`prestige.<name>`)
|
||||||
- `count`: current value
|
- `count`: current value
|
||||||
|
|||||||
@@ -28,6 +28,12 @@ Pass flags for non-interactive, scriptable usage. Reads save data from your **cl
|
|||||||
# List all resources and their current values
|
# List all resources and their current values
|
||||||
node index.js --list
|
node index.js --list
|
||||||
|
|
||||||
|
# Show which species already have a pillar in the Circle of Pillars
|
||||||
|
node index.js --pillars
|
||||||
|
|
||||||
|
# Same, but also list the species still missing a pillar
|
||||||
|
node index.js --pillars-all
|
||||||
|
|
||||||
# Max all capped resources
|
# Max all capped resources
|
||||||
node index.js --max-resources
|
node index.js --max-resources
|
||||||
|
|
||||||
@@ -61,6 +67,8 @@ node index.js --max-resources --no-copy
|
|||||||
| Flag | Description |
|
| Flag | Description |
|
||||||
|------|-------------|
|
|------|-------------|
|
||||||
| `--list` | List all resources in the save, grouped by type (capped, crafted, special, locked) |
|
| `--list` | List all resources in the save, grouped by type (capped, crafted, special, locked) |
|
||||||
|
| `--pillars` | Show Circle of Pillars progress: pillared species grouped by genus, with their rank |
|
||||||
|
| `--pillars-all` | Same as `--pillars`, but also lists species that have no pillar yet |
|
||||||
| `--max-resources` | Set all capped resources (`max > 0`) to their max |
|
| `--max-resources` | Set all capped resources (`max > 0`) to their max |
|
||||||
| `--set=key=N` | Set a specific resource or prestige currency to N (e.g. `--set=Plasmid=100`). Case-insensitive. Can be used multiple times. |
|
| `--set=key=N` | Set a specific resource or prestige currency to N (e.g. `--set=Plasmid=100`). Case-insensitive. Can be used multiple times. |
|
||||||
| `--set-crafted=N` | Set all unlimited/crafted resources (`max == -1`) to N |
|
| `--set-crafted=N` | Set all unlimited/crafted resources (`max == -1`) to N |
|
||||||
|
|||||||
@@ -142,6 +142,151 @@ function setGeology(data) {
|
|||||||
return `Set 4 geology bonuses (max +${(maxBonus * 100).toFixed(0)}% from White Hole level ${whLevel}):\n${entries}`;
|
return `Set 4 geology bonuses (max +${(maxBonus * 100).toFixed(0)}% from White Hole level ${whLevel}):\n${entries}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// All pillar-able species (every race except protoplasm), in game order.
|
||||||
|
// Pillar count target for the Circle of Pillars is RACES.size (63).
|
||||||
|
const RACES = {
|
||||||
|
human: { name: "Human", genus: "humanoid" },
|
||||||
|
elven: { name: "Elf", genus: "humanoid" },
|
||||||
|
orc: { name: "Orc", genus: "humanoid" },
|
||||||
|
cath: { name: "Cath", genus: "carnivore" },
|
||||||
|
wolven: { name: "Wolven", genus: "carnivore" },
|
||||||
|
vulpine: { name: "Vulpine", genus: "carnivore" },
|
||||||
|
centaur: { name: "Centaur", genus: "herbivore" },
|
||||||
|
rhinotaur: { name: "Rhinotaur", genus: "herbivore" },
|
||||||
|
capybara: { name: "Capybara", genus: "herbivore" },
|
||||||
|
porkenari: { name: "Porkenari", genus: "omnivore" },
|
||||||
|
hedgeoken: { name: "Hedgeoken", genus: "omnivore" },
|
||||||
|
kobold: { name: "Kobold", genus: "small" },
|
||||||
|
goblin: { name: "Goblin", genus: "small" },
|
||||||
|
gnome: { name: "Gnome", genus: "small" },
|
||||||
|
ogre: { name: "Ogre", genus: "giant" },
|
||||||
|
cyclops: { name: "Cyclops", genus: "giant" },
|
||||||
|
troll: { name: "Troll", genus: "giant" },
|
||||||
|
tortoisan: { name: "Tortoisan", genus: "reptilian" },
|
||||||
|
gecko: { name: "Gecko", genus: "reptilian" },
|
||||||
|
slitheryn: { name: "Slitheryn", genus: "reptilian" },
|
||||||
|
arraak: { name: "Arraak", genus: "avian" },
|
||||||
|
pterodacti: { name: "Pterodacti", genus: "avian" },
|
||||||
|
dracnid: { name: "Dracnid", genus: "avian" },
|
||||||
|
entish: { name: "Ent", genus: "plant" },
|
||||||
|
cacti: { name: "Cacti", genus: "plant" },
|
||||||
|
pinguicula: { name: "Pinguicula", genus: "plant" },
|
||||||
|
sporgar: { name: "Sporgar", genus: "fungi" },
|
||||||
|
shroomi: { name: "Shroomi", genus: "fungi" },
|
||||||
|
moldling: { name: "Moldling", genus: "fungi" },
|
||||||
|
mantis: { name: "Mantis", genus: "insectoid" },
|
||||||
|
scorpid: { name: "Scorpid", genus: "insectoid" },
|
||||||
|
antid: { name: "Antid", genus: "insectoid" },
|
||||||
|
sharkin: { name: "Sharkin", genus: "aquatic" },
|
||||||
|
octigoran: { name: "Octigoran", genus: "aquatic" },
|
||||||
|
dryad: { name: "Dryad", genus: "fey" },
|
||||||
|
satyr: { name: "Satyr", genus: "fey" },
|
||||||
|
phoenix: { name: "Phoenix", genus: "heat" },
|
||||||
|
salamander: { name: "Salamander", genus: "heat" },
|
||||||
|
yeti: { name: "Yeti", genus: "polar" },
|
||||||
|
wendigo: { name: "Wendigo", genus: "polar" },
|
||||||
|
tuskin: { name: "Tuskin", genus: "sand" },
|
||||||
|
kamel: { name: "Kamel", genus: "sand" },
|
||||||
|
balorg: { name: "Balorg", genus: "demonic" },
|
||||||
|
imp: { name: "Imp", genus: "demonic" },
|
||||||
|
hellspawn: { name: "Hellspawn", genus: "demonic" },
|
||||||
|
seraph: { name: "Seraph", genus: "angelic" },
|
||||||
|
unicorn: { name: "Unicorn", genus: "angelic" },
|
||||||
|
synth: { name: "Synth", genus: "synthetic" },
|
||||||
|
nano: { name: "Nano", genus: "synthetic" },
|
||||||
|
ghast: { name: "Ghast", genus: "eldritch" },
|
||||||
|
shoggoth: { name: "Shoggoth", genus: "eldritch" },
|
||||||
|
dwarf: { name: "Dwarf", genus: "hybrid" },
|
||||||
|
raccoon: { name: "Racconar", genus: "hybrid" },
|
||||||
|
lichen: { name: "Lichen", genus: "hybrid" },
|
||||||
|
wyvern: { name: "Wyvern", genus: "hybrid" },
|
||||||
|
beholder: { name: "Eye-Spector", genus: "hybrid" },
|
||||||
|
djinn: { name: "Djinn", genus: "hybrid" },
|
||||||
|
narwhal: { name: "Narwhalus", genus: "hybrid" },
|
||||||
|
bombardier: { name: "Bombardier", genus: "hybrid" },
|
||||||
|
nephilim: { name: "Nephilim", genus: "hybrid" },
|
||||||
|
junker: { name: "Valdi", genus: "special" },
|
||||||
|
sludge: { name: "Sludge", genus: "special" },
|
||||||
|
ultra_sludge: { name: "Ultra Sludge", genus: "special" },
|
||||||
|
};
|
||||||
|
|
||||||
|
const GENUS_NAMES = {
|
||||||
|
humanoid: "Humanoid",
|
||||||
|
carnivore: "Carnivore",
|
||||||
|
herbivore: "Herbivore",
|
||||||
|
omnivore: "Omnivore",
|
||||||
|
small: "Small",
|
||||||
|
giant: "Giant",
|
||||||
|
reptilian: "Reptilian",
|
||||||
|
avian: "Avian",
|
||||||
|
plant: "Plant",
|
||||||
|
fungi: "Fungi",
|
||||||
|
insectoid: "Insectoid",
|
||||||
|
aquatic: "Aquatic",
|
||||||
|
fey: "Fey",
|
||||||
|
heat: "Heat",
|
||||||
|
polar: "Polar",
|
||||||
|
sand: "Sand",
|
||||||
|
demonic: "Demonic",
|
||||||
|
angelic: "Angelic",
|
||||||
|
synthetic: "Synthetic",
|
||||||
|
eldritch: "Eldritch",
|
||||||
|
hybrid: "Hybrid",
|
||||||
|
special: "Special",
|
||||||
|
};
|
||||||
|
|
||||||
|
function listPillars(data, showAll) {
|
||||||
|
const pillars = data.pillars || {};
|
||||||
|
const current = data.race?.species;
|
||||||
|
const total = Object.keys(RACES).length;
|
||||||
|
const owned = Object.keys(pillars).filter((k) => pillars[k]);
|
||||||
|
|
||||||
|
if (owned.length === 0 && !showAll) {
|
||||||
|
console.log("No pillars in this save (Circle of Pillars not started).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Group races by genus, keeping game order within each genus
|
||||||
|
const byGenus = {};
|
||||||
|
for (const [key, race] of Object.entries(RACES)) {
|
||||||
|
(byGenus[race.genus] ||= []).push(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Circle of Pillars: ${owned.length} / ${total} species pillared\n`);
|
||||||
|
|
||||||
|
for (const [genus, keys] of Object.entries(byGenus)) {
|
||||||
|
const done = keys.filter((k) => pillars[k]);
|
||||||
|
if (done.length === 0 && !showAll) continue;
|
||||||
|
|
||||||
|
const label = GENUS_NAMES[genus] || genus;
|
||||||
|
console.log(`${label} (${done.length}/${keys.length}):`);
|
||||||
|
for (const key of keys) {
|
||||||
|
const rank = pillars[key];
|
||||||
|
const name = RACES[key].name;
|
||||||
|
const marker = key === current ? " <- current species" : "";
|
||||||
|
if (rank) {
|
||||||
|
console.log(` [x] ${name} (${key}) - rank ${rank}${marker}`);
|
||||||
|
} else if (showAll) {
|
||||||
|
console.log(` [ ] ${name} (${key})${marker}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unknown keys (custom races or races added after this list was built)
|
||||||
|
const unknown = owned.filter((k) => !RACES[k]);
|
||||||
|
if (unknown.length > 0) {
|
||||||
|
console.log(`Unrecognized (custom?) species:`);
|
||||||
|
unknown.forEach((k) => console.log(` [x] ${k} - rank ${pillars[k]}`));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pillar bonus: +1% production / +2% storage per pillar, current species counts 4x
|
||||||
|
const active = owned.reduce((sum, k) => sum + (k === current ? 4 : 1), 0);
|
||||||
|
console.log(`\nPillar bonus: +${active}% production, +${active * 2}% storage`);
|
||||||
|
if (!showAll) {
|
||||||
|
console.log(`${total - owned.length} species still missing (use --pillars-all to list them).`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function readFromClipboard() {
|
function readFromClipboard() {
|
||||||
try {
|
try {
|
||||||
return execSync("pbpaste", { encoding: "utf-8" }).trim();
|
return execSync("pbpaste", { encoding: "utf-8" }).trim();
|
||||||
@@ -226,7 +371,9 @@ function main() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasAction = flags.has("--max-resources") || flags.has("--max-time") || flags.has("--max-geology") || flags.has("--max-soldiers") || flags.has("--list") || craftedValue !== null || setValues.length > 0;
|
const mutates = flags.has("--max-resources") || flags.has("--max-time") || flags.has("--max-geology") || flags.has("--max-soldiers") || craftedValue !== null || setValues.length > 0;
|
||||||
|
const inspects = flags.has("--list") || flags.has("--pillars") || flags.has("--pillars-all");
|
||||||
|
const hasAction = mutates || inspects;
|
||||||
|
|
||||||
if (flags.has("--help")) {
|
if (flags.has("--help")) {
|
||||||
console.log(`Usage: node index.js [options] [save-string]
|
console.log(`Usage: node index.js [options] [save-string]
|
||||||
@@ -235,6 +382,8 @@ Running with no flags launches interactive mode.
|
|||||||
|
|
||||||
Options:
|
Options:
|
||||||
--list List all resources in the save with current values
|
--list List all resources in the save with current values
|
||||||
|
--pillars Show which species have a pillar in the Circle of Pillars
|
||||||
|
--pillars-all Same, but also list species without a pillar
|
||||||
--max-resources Set all capped resources to their max
|
--max-resources Set all capped resources to their max
|
||||||
--set=key=N Set a specific resource to N (e.g. --set=Plasmid=100)
|
--set=key=N Set a specific resource to N (e.g. --set=Plasmid=100)
|
||||||
--set-crafted=N Set all unlimited (crafted) resources to N
|
--set-crafted=N Set all unlimited (crafted) resources to N
|
||||||
@@ -262,9 +411,14 @@ Save string can be passed as argument, piped via stdin, or read from clipboard.`
|
|||||||
|
|
||||||
if (flags.has("--list")) {
|
if (flags.has("--list")) {
|
||||||
listResources(data);
|
listResources(data);
|
||||||
if (!flags.has("--max-resources") && !flags.has("--max-soldiers") && !flags.has("--max-geology") && !flags.has("--max-time") && craftedValue === null) {
|
|
||||||
process.exit(0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (flags.has("--pillars") || flags.has("--pillars-all")) {
|
||||||
|
listPillars(data, flags.has("--pillars-all"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inspects && !mutates) {
|
||||||
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (flags.has("--max-resources")) {
|
if (flags.has("--max-resources")) {
|
||||||
@@ -406,6 +560,7 @@ async function interactiveMode() {
|
|||||||
{ value: "max-geology", name: "Max geology bonuses" },
|
{ value: "max-geology", name: "Max geology bonuses" },
|
||||||
{ value: "max-time", name: "Max accelerated time" },
|
{ value: "max-time", name: "Max accelerated time" },
|
||||||
{ value: "list", name: "List all resources" },
|
{ value: "list", name: "List all resources" },
|
||||||
|
{ value: "pillars", name: "Show Circle of Pillars progress" },
|
||||||
...(modified ? [{ value: "save", name: "Save & copy to clipboard" }] : []),
|
...(modified ? [{ value: "save", name: "Save & copy to clipboard" }] : []),
|
||||||
{ value: "exit", name: "Exit" },
|
{ value: "exit", name: "Exit" },
|
||||||
],
|
],
|
||||||
@@ -429,6 +584,12 @@ async function interactiveMode() {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action === "pillars") {
|
||||||
|
const showAll = await confirm({ message: "Also show species without a pillar?", default: false });
|
||||||
|
listPillars(data, showAll);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (action === "max-time") {
|
if (action === "max-time") {
|
||||||
const MAX_AT = 11520;
|
const MAX_AT = 11520;
|
||||||
const old = data.settings?.at ?? 0;
|
const old = data.settings?.at ?? 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user