├── .eslintignore ├── manifest.json ├── .gitignore ├── .prettierrc.js ├── .prettierignore ├── .vscode ├── settings.json └── tasks.json ├── KoLmafia ├── dependencies.txt └── scripts │ └── instantsccs │ └── instantsccs_choice.ash ├── src ├── engine │ ├── task.ts │ └── engine.ts ├── tasks │ ├── coilwire.ts │ ├── donate.ts │ ├── noncombat.ts │ ├── familiarweight.ts │ ├── hotres.ts │ ├── stat.ts │ ├── weapondamage.ts │ ├── spelldamage.ts │ └── boozedrop.ts ├── familiars.ts ├── combat.ts ├── outfit.ts ├── main.ts ├── resources.ts └── sim.ts ├── babel.config.js ├── tsconfig.json ├── .eslintrc.json ├── package.json ├── webpack.config.js ├── .github └── workflows │ └── build.yml ├── LEVELING.md ├── LICENSE ├── RECOMMENDATIONS.md └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | /KoLmafia/scripts/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "root_directory": "KoLmafia" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | KoLmafia/scripts/instantsccs/instantsccs.js -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | printWidth: 100, 3 | endOfLine: "auto", 4 | }; 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Generated bundles should not be formatted 2 | /node_modules/ 3 | /KoLmafia/ 4 | .DS_Store 5 | node_modules 6 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.tabSize": 2, 4 | "editor.formatOnSave": true 5 | } 6 | -------------------------------------------------------------------------------- /KoLmafia/dependencies.txt: -------------------------------------------------------------------------------- 1 | github Ezandora/Bastille 2 | github Ezandora/Briefcase 3 | github Ezandora/Detective-Solver 4 | github Ezandora/Voting-Booth 5 | -------------------------------------------------------------------------------- /src/engine/task.ts: -------------------------------------------------------------------------------- 1 | import { Quest as BaseQuest, Task as BaseTask } from "grimoire-kolmafia"; 2 | 3 | export type Quest = BaseQuest; 4 | export type Task = BaseTask; 5 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-undef 2 | module.exports = function (api) { 3 | api.cache(true); 4 | return { 5 | presets: [ 6 | "@babel/preset-typescript", 7 | [ 8 | "@babel/preset-env", 9 | { 10 | targets: { rhino: "1.7.13" }, 11 | }, 12 | ], 13 | ], 14 | plugins: [ 15 | "@babel/plugin-proposal-class-properties", 16 | "@babel/plugin-proposal-object-rest-spread", 17 | ], 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /src/tasks/coilwire.ts: -------------------------------------------------------------------------------- 1 | import { Quest } from "../engine/task"; 2 | import { CommunityService } from "libram"; 3 | import { logTestSetup } from "../lib"; 4 | 5 | export const CoilWireQuest: Quest = { 6 | name: "Coil Wire", 7 | completed: () => CommunityService.CoilWire.isDone(), 8 | tasks: [ 9 | { 10 | name: "Test", 11 | completed: () => CommunityService.CoilWire.isDone(), 12 | do: () => CommunityService.CoilWire.run(() => logTestSetup(CommunityService.CoilWire)), 13 | limit: { tries: 1 }, 14 | }, 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "npm", 6 | "script": "build", 7 | "group": { 8 | "kind": "build", 9 | "isDefault": true 10 | }, 11 | "problemMatcher": [], 12 | "label": "npm: build", 13 | "detail": "yarn run build:types && yarn run build:js" 14 | }, 15 | { 16 | "type": "npm", 17 | "script": "lint", 18 | "problemMatcher": [], 19 | "label": "npm: lint", 20 | "detail": "eslint src && prettier --check .", 21 | "group": { 22 | "kind": "test", 23 | "isDefault": true 24 | } 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "rootDir": "src", 4 | "noEmit": true, 5 | "allowUnreachableCode": false, 6 | "allowUnusedLabels": false, 7 | "declaration": false, 8 | "forceConsistentCasingInFileNames": true, 9 | "lib": ["es2018"], 10 | "module": "commonjs", 11 | "noEmitOnError": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "noImplicitReturns": true, 14 | "pretty": true, 15 | "strict": true, 16 | "target": "es2018", 17 | }, 18 | "include": ["src/**/*.ts", "test/**/*.ts"], 19 | "isolatedModules": true /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */, 20 | } 21 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"], 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 2020, 6 | "sourceType": "module" 7 | }, 8 | "plugins": ["@typescript-eslint", "libram"], 9 | "rules": { 10 | "block-scoped-var": "error", 11 | "eol-last": "error", 12 | "eqeqeq": "error", 13 | "no-trailing-spaces": "error", 14 | "no-var": "error", 15 | "prefer-arrow-callback": "error", 16 | "prefer-const": "error", 17 | "prefer-template": "error", 18 | "sort-imports": [ 19 | "error", 20 | { 21 | "ignoreCase": true, 22 | "ignoreDeclarationSort": true 23 | } 24 | ], 25 | "no-unused-vars": "off", 26 | "@typescript-eslint/no-unused-vars": "error", 27 | "libram/verify-constants": "error" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /KoLmafia/scripts/instantsccs/instantsccs_choice.ash: -------------------------------------------------------------------------------- 1 | // Adapted from loopsmol_choice.ash 2 | void main(int choice, string page) 3 | { 4 | string[int] options = available_choice_options(); 5 | int[string] priority; 6 | 7 | switch (choice) { 8 | case 1525: 9 | priority = { 10 | "Throw a second dart quickly":60, 11 | "Deal 25-50% more damage":800, 12 | "You are less impressed by bullseyes":10, 13 | "25% Better bullseye targeting":20, 14 | "Extra stats from stats targets":40, 15 | "Butt awareness":30, 16 | "Add Hot Damage":1000, 17 | "Add Cold Damage":1000, 18 | "Add Sleaze Damage":1000, 19 | "Add Spooky Damage":1000, 20 | "Add Stench Damage":1000, 21 | "Expand your dart capacity by 1":50, 22 | "Bullseyes do not impress you much":9, 23 | "25% More Accurate bullseye targeting":19, 24 | "Deal 25-50% extra damage":10000, 25 | "Increase Dart Deleveling from deleveling targets":100, 26 | "Deal 25-50% greater damage":10000, 27 | "Extra stats from stats targets":39, 28 | "25% better chance to hit bullseyes":18, 29 | }; 30 | int top = 999999999; 31 | int pick = 1; 32 | 33 | foreach i,x in available_choice_options() { 34 | if (priority[x] == 0) { 35 | print(`dart perk "{x}" not in priority list`,"red"); 36 | continue; 37 | } 38 | if (priority[x] < top) { 39 | top = priority[x]; 40 | pick = i; 41 | } 42 | } 43 | run_choice(pick); 44 | break; 45 | 46 | default: 47 | return; 48 | } 49 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "instantsccs", 3 | "version": "1.1.0", 4 | "description": "A low shiny SCCS script", 5 | "main": "instantsccs", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "yarn run build:types && yarn run build:js", 9 | "build:types": "tsc", 10 | "build:js": "webpack", 11 | "lint": "eslint src && prettier --check .", 12 | "watch": "webpack --watch --progress" 13 | }, 14 | "devDependencies": { 15 | "@babel/cli": "^7.14.8", 16 | "@babel/core": "^7.15.0", 17 | "@babel/plugin-proposal-class-properties": "^7.14.5", 18 | "@babel/plugin-proposal-object-rest-spread": "^7.14.7", 19 | "@babel/preset-env": "^7.15.0", 20 | "@babel/preset-typescript": "^7.15.0", 21 | "@typescript-eslint/eslint-plugin": "^6.20.0", 22 | "@typescript-eslint/parser": "^6.20.0", 23 | "babel-loader": "^8.2.2", 24 | "eslint": "^8.56.0", 25 | "eslint-config-prettier": "^9.1.0", 26 | "eslint-plugin-libram": "^0.4.30", 27 | "prettier": "^3.2.4", 28 | "typescript": "^5.3.3", 29 | "webpack": "^5.61.0", 30 | "webpack-cli": "^4.8.0" 31 | }, 32 | "dependencies": { 33 | "core-js": "^3.35.1", 34 | "grimoire-kolmafia": "^0.3.33", 35 | "kolmafia": "^5.28605.0", 36 | "libram": "^0.11.10" 37 | }, 38 | "author": "Pantocyclus", 39 | "license": "MIT", 40 | "repository": "git+https://github.com/Pantocyclus/InstantSCCS.git", 41 | "keywords": [ 42 | "KoLMafia", 43 | "JS", 44 | "TS" 45 | ], 46 | "bugs": { 47 | "url": "https://github.com/Pantocyclus/InstantSCCS/issues" 48 | }, 49 | "homepage": "https://github.com/Pantocyclus/InstantSCCS#readme" 50 | } 51 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | 3 | /* eslint-disable @typescript-eslint/no-var-requires */ 4 | const path = require("path"); 5 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 6 | const webpack = require("webpack"); // does this have a purpose? or can it just get deleted? 7 | const packageData = require("./package.json"); 8 | /* eslint-enable @typescript-eslint/no-var-requires */ 9 | 10 | module.exports = { 11 | entry: { 12 | // Define files webpack will emit, does not need to correspond 1:1 with every typescript file 13 | // You need an emitted file for each entrypoint into your code, e.g. the main script and the ccs or ccs consult script it calls 14 | instantsccs: "./src/main.ts", 15 | }, 16 | // Turns on tree-shaking and minification in the default Terser minifier 17 | // https://webpack.js.org/plugins/terser-webpack-plugin/ 18 | mode: "production", 19 | devtool: false, 20 | output: { 21 | path: path.resolve(__dirname, "KoLmafia", "scripts", packageData.name), 22 | filename: "[name].js", 23 | libraryTarget: "commonjs", 24 | }, 25 | resolve: { 26 | extensions: [".ts", ".tsx", ".js", ".json"], 27 | }, 28 | module: { 29 | rules: [ 30 | { 31 | // Include ts, tsx, js, and jsx files. 32 | test: /\.(ts|js)x?$/, 33 | // exclude: /node_modules/, 34 | loader: "babel-loader", 35 | }, 36 | ], 37 | }, 38 | optimization: { 39 | // Disable compression because it makes debugging more difficult for KolMafia 40 | minimize: false, 41 | }, 42 | performance: { 43 | // Disable the warning about assets exceeding the recommended size because this isn't a website script 44 | hints: false, 45 | }, 46 | plugins: [], 47 | externals: { 48 | // Necessary to allow kolmafia imports. 49 | kolmafia: "commonjs kolmafia", 50 | // Add any ASH scripts you would like to use here to allow importing. E.g.: 51 | // "canadv.ash": "commonjs canadv.ash", 52 | }, 53 | }; 54 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build instantsccs 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | paths: 7 | - "src/**" 8 | - "package.json" 9 | pull_request: 10 | branches: [main] 11 | paths: 12 | - "src/**" 13 | - "package.json" 14 | workflow_dispatch: 15 | 16 | env: 17 | # Default version of Node.js for jobs 18 | node-version: "18" 19 | 20 | jobs: 21 | lint-all: 22 | runs-on: ubuntu-latest 23 | 24 | steps: 25 | # Checkout source code 26 | - name: Checkout 27 | uses: actions/checkout@v3 28 | 29 | # Setup Nodejs 30 | - name: Use Node.js ${{ env.node-version }} 31 | uses: actions/setup-node@v3 32 | with: 33 | node-version: ${{ env.node-version }} 34 | cache: yarn 35 | - run: yarn install --immutable --immutable-cache 36 | - run: yarn run lint 37 | 38 | build-push: 39 | runs-on: ubuntu-latest 40 | needs: [lint-all] 41 | if: github.ref == 'refs/heads/main' 42 | 43 | steps: 44 | # Checkout source code 45 | - name: Checkout 46 | uses: actions/checkout@v3 47 | 48 | # Setup Nodejs 49 | - name: Use Node.js ${{ env.node-version }} 50 | uses: actions/setup-node@v3 51 | with: 52 | node-version: ${{ env.node-version }} 53 | cache: "npm" 54 | 55 | # Setup Yarn 56 | - name: Install yarn 57 | run: npm install -g yarn 58 | 59 | # Install dependencies 60 | - name: Install build dependencies 61 | run: yarn install --frozen-lockfile 62 | 63 | # Build script 64 | - run: | 65 | yarn build 66 | mkdir BUILD 67 | cp -r KoLmafia/scripts BUILD/scripts 68 | cp -r KoLmafia/dependencies.txt BUILD/dependencies.txt 69 | cp -r README.md BUILD/README.md 70 | 71 | # Synchronise & push BUILD directory to release branch 72 | - name: Configure Git information 73 | run: | 74 | git config --global user.name "Build Script" 75 | git config --global user.email "<>" 76 | 77 | - name: Synchronize & push into release branch 78 | run: | 79 | git fetch --all 80 | echo "Switching into release" 81 | git stash && git stash clear 82 | git switch release 83 | 84 | # Push build into the release branch 85 | shopt -s extglob 86 | rm -rf !(BUILD) 87 | mv BUILD/scripts scripts 88 | mv BUILD/dependencies.txt dependencies.txt 89 | mv BUILD/README.md README.md 90 | rm -rf BUILD 91 | git add scripts 92 | git add dependencies.txt 93 | git add README.md 94 | git commit -m "Automated build" 95 | git push -f origin release 96 | -------------------------------------------------------------------------------- /src/familiars.ts: -------------------------------------------------------------------------------- 1 | import { Familiar } from "kolmafia"; 2 | import { $familiar, $familiars, $item, get, have } from "libram"; 3 | import { camelFightsLeft, haveAndNotExcluded, haveCBBIngredients } from "./lib"; 4 | 5 | function nanorhino(allowAttackingFamiliars = false): Familiar { 6 | return allowAttackingFamiliars && get("_nanorhinoCharge") === 100 7 | ? $familiar`Nanorhino` 8 | : $familiar.none; 9 | } 10 | 11 | export function cookbookbat(): Familiar { 12 | return !haveCBBIngredients(true) ? $familiar`Cookbookbat` : $familiar.none; 13 | } 14 | 15 | function shorterOrderCook(allowAttackingFamiliars = true): Familiar { 16 | return allowAttackingFamiliars && !have($item`short stack of pancakes`) 17 | ? $familiar`Shorter-Order Cook` 18 | : $familiar.none; 19 | } 20 | 21 | function garbageFire(): Familiar { 22 | return !have($item`burning newspaper`) ? $familiar`Garbage Fire` : $familiar.none; 23 | } 24 | 25 | export function sombrero(allowAttackingFamiliars = true): Familiar { 26 | const sombreros = [ 27 | ...(allowAttackingFamiliars 28 | ? $familiars`Jill-of-All-Trades, Patriotic Eagle, Galloping Grill` 29 | : []), 30 | $familiar`Baby Sandworm`, 31 | $familiar`Hovering Sombrero`, 32 | ].filter((fam) => have(fam)); 33 | return sombreros.length > 0 ? sombreros[0] : $familiar.none; 34 | } 35 | 36 | function rockinRobin(): Familiar { 37 | return !have($item`robin's egg`) ? $familiar`Rockin' Robin` : $familiar.none; 38 | } 39 | 40 | function optimisticCandle(): Familiar { 41 | return !have($item`glob of melted wax`) ? $familiar`Optimistic Candle` : $familiar.none; 42 | } 43 | 44 | export function melodramedary(): Familiar { 45 | return have($familiar`Melodramedary`) && 46 | camelFightsLeft() >= Math.ceil((100 - get("camelSpit")) / 3.0) && 47 | get("camelSpit") < 100 48 | ? $familiar`Melodramedary` 49 | : $familiar.none; 50 | } 51 | 52 | function hoboInSheepsClothing(): Familiar { 53 | return have($familiar`Hobo in Sheep's Clothing`) && !have($item`grubby wool`, 2) 54 | ? $familiar`Hobo in Sheep's Clothing` 55 | : $familiar.none; 56 | } 57 | 58 | function miniKiwi(): Familiar { 59 | return have($familiar`Mini Kiwi`) && !have($item`mini kiwi`, 10) 60 | ? $familiar`Mini Kiwi` 61 | : $familiar.none; 62 | } 63 | 64 | export function chooseFamiliar(allowAttackingFamiliars = true): Familiar { 65 | const defaultFam = have($familiar`Cookbookbat`) ? $familiar`Cookbookbat` : $familiar.none; 66 | const familiars = [ 67 | cookbookbat, 68 | shorterOrderCook, 69 | garbageFire, 70 | nanorhino, 71 | optimisticCandle, 72 | rockinRobin, 73 | melodramedary, 74 | hoboInSheepsClothing, 75 | miniKiwi, 76 | sombrero, 77 | ] 78 | .map((fn) => fn(allowAttackingFamiliars)) 79 | .filter((fam) => haveAndNotExcluded(fam)); 80 | return familiars.length > 0 ? familiars[0] : defaultFam; 81 | } 82 | -------------------------------------------------------------------------------- /src/combat.ts: -------------------------------------------------------------------------------- 1 | import { mpCost, toInt } from "kolmafia"; 2 | import { $item, $skill, $stat, get, have, StrictMacro } from "libram"; 3 | import { mainStat } from "./lib"; 4 | 5 | const damageSkill = mainStat === $stat`Muscle` ? $skill`Lunging Thrust-Smack` : $skill`Saucegeyser`; 6 | 7 | export default class Macro extends StrictMacro { 8 | kill(useCinch = false): Macro { 9 | const macroHead = this.trySkill($skill`Curse of Weaksauce`) 10 | .trySkill($skill`Micrometeorite`) 11 | .trySkill($skill`Sing Along`) 12 | .trySkill($skill`Surprisingly Sweet Stab`) 13 | .trySkill($skill`Surprisingly Sweet Slash`) 14 | .if_( 15 | `!mpbelow ${mpCost($skill`Stuffed Mortar Shell`)}`, 16 | Macro.trySkill($skill`Stuffed Mortar Shell`), 17 | ); 18 | 19 | return (useCinch ? macroHead.trySkill($skill`Cincho: Confetti Extravaganza`) : macroHead) 20 | .while_(`!mpbelow ${damageSkill} && hasskill ${toInt(damageSkill)}`, Macro.skill(damageSkill)) 21 | .while_( 22 | `!mpbelow ${mpCost($skill`Saucestorm`)} && hasskill ${toInt($skill`Saucestorm`)}`, 23 | Macro.skill($skill`Saucestorm`), 24 | ) 25 | .attack() 26 | .repeat(); 27 | } 28 | 29 | static kill(): Macro { 30 | return new Macro().kill(); 31 | } 32 | 33 | banish(): Macro { 34 | return Macro.trySkill($skill`Feel Hatred`) 35 | .trySkill($skill`Reflex Hammer`) 36 | .trySkill($skill`Throw Latte on Opponent`) 37 | .trySkill($skill`KGB tranquilizer dart`) 38 | .trySkill($skill`Snokebomb`); 39 | } 40 | 41 | static banish(): Macro { 42 | return new Macro().banish(); 43 | } 44 | 45 | default(useCinch = false): Macro { 46 | return this.kill(useCinch); 47 | } 48 | 49 | static default(useCinch = false): Macro { 50 | return new Macro().default(useCinch); 51 | } 52 | } 53 | 54 | export function main(): void { 55 | Macro.load().submit(); 56 | } 57 | 58 | export function haveFreeKill(): boolean { 59 | // TODO: Support for Parka YR 60 | const haveXRay = have($item`Lil' Doctor™ bag`) && get("_chestXRayUsed") < 3; 61 | const haveShatteringPunch = have($skill`Shattering Punch`) && get("_shatteringPunchUsed") < 3; 62 | const haveMobHit = have($skill`Gingerbread Mob Hit`) && !get("_gingerbreadMobHitUsed"); 63 | 64 | return haveXRay || haveShatteringPunch || haveMobHit; 65 | } 66 | 67 | export function haveMotherSlimeBanish(): boolean { 68 | const haveSnokeBomb = have($skill`Snokebomb`) && get("_snokebombUsed") < 3; 69 | const haveKGBTranquilizer = 70 | have($item`Kremlin's Greatest Briefcase`) && get("_kgbTranquilizerDartUses") < 3; 71 | 72 | return haveSnokeBomb || haveKGBTranquilizer; 73 | } 74 | 75 | export function haveFreeBanish(): boolean { 76 | const haveFeelHatred = have($skill`Feel Hatred`) && get("_feelHatredUsed") < 3; 77 | const haveReflexHammer = have($item`Lil' Doctor™ bag`) && get("_reflexHammerUsed") < 3; 78 | const haveThrowLatte = have($item`latte lovers member's mug`) && !get("_latteBanishUsed"); 79 | 80 | return haveFeelHatred || haveReflexHammer || haveThrowLatte || haveMotherSlimeBanish(); 81 | } 82 | -------------------------------------------------------------------------------- /src/outfit.ts: -------------------------------------------------------------------------------- 1 | import { OutfitSpec } from "grimoire-kolmafia"; 2 | import { cliExecute, equip, equippedItem, Item, myPrimestat } from "kolmafia"; 3 | import { $effect, $item, $skill, $slot, $stat, DaylightShavings, examine, get, have } from "libram"; 4 | import { havePowerlevelingZoneBound, mainStatMaximizerStr } from "./lib"; 5 | import { chooseFamiliar } from "./familiars"; 6 | 7 | export function garbageShirt(): void { 8 | if ( 9 | have($item`January's Garbage Tote`) && 10 | get("garbageShirtCharge") > 0 && 11 | have($skill`Torso Awareness`) 12 | ) { 13 | if (get("garbageShirtCharge") === 1) { 14 | if (equippedItem($slot`shirt`) === $item`makeshift garbage shirt`) 15 | equip($slot`shirt`, $item.none); 16 | } else { 17 | if (!have($item`makeshift garbage shirt`)) cliExecute("fold makeshift garbage shirt"); 18 | equip($slot`shirt`, $item`makeshift garbage shirt`); 19 | } 20 | } 21 | } 22 | 23 | export function unbreakableUmbrella(): void { 24 | if (have($item`unbreakable umbrella`) && get("umbrellaState") !== "broken") 25 | cliExecute("umbrella ml"); 26 | } 27 | 28 | export function docBag(): void { 29 | if (have($item`Lil' Doctor™ bag`) && get("_chestXRayUsed") < 3) 30 | equip($slot`acc3`, $item`Lil' Doctor™ bag`); 31 | } 32 | 33 | export function sugarItemsAboutToBreak(): Item[] { 34 | const sugarItems = [ 35 | { id: 4180, item: $item`sugar shank` }, 36 | { id: 4181, item: $item`sugar chapeau` }, 37 | { id: 4182, item: $item`sugar shorts` }, 38 | ]; 39 | return sugarItems 40 | .map((entry) => { 41 | const { id, item } = entry; 42 | const itemAboutToBreak = parseInt(get(`sugarCounter${id.toString()}`), 10) >= 30; 43 | return itemAboutToBreak ? [item] : []; 44 | }) 45 | .reduce((a, b) => a.concat(b)); 46 | } 47 | 48 | export function avoidDaylightShavingsHelm(): boolean { 49 | return ( 50 | DaylightShavings.nextBuff() === $effect`Musician's Musician's Moustache` || 51 | DaylightShavings.hasBuff() || 52 | !have($item`Daylight Shavings Helmet`) 53 | ); 54 | } 55 | 56 | function useCandyCaneSword(): boolean { 57 | if (!have($item`candy cane sword cane`) || get("instant_saveCandySword", false)) return false; 58 | examine($item`candy cane sword cane`); 59 | if (get("_surprisinglySweetSlashUsed") < 11 || get("_surprisinglySweetStabUsed") < 11) { 60 | return true; 61 | } 62 | return false; 63 | } 64 | 65 | function chooseWeapon(): Item | undefined { 66 | // eslint-disable-next-line libram/verify-constants 67 | if (!havePowerlevelingZoneBound() && have($item`Monodent of the Sea`)) 68 | // eslint-disable-next-line libram/verify-constants 69 | return $item`Monodent of the Sea`; 70 | else if (useCandyCaneSword()) return $item`candy cane sword cane`; 71 | else if (have($item`fish hatchet`)) return $item`fish hatchet`; 72 | else if (have($item`bass clarinet`)) return $item`bass clarinet`; 73 | else if (myPrimestat() === $stat`Muscle` && have($item`June cleaver`)) return $item`June cleaver`; 74 | return undefined; 75 | } 76 | 77 | export function baseOutfit(allowAttackingFamiliars = true): OutfitSpec { 78 | return { 79 | hat: avoidDaylightShavingsHelm() ? undefined : $item`Daylight Shavings Helmet`, 80 | weapon: chooseWeapon(), 81 | offhand: $item`unbreakable umbrella`, 82 | acc1: myPrimestat() === $stat`Mysticality` ? $item`codpiece` : undefined, 83 | acc2: 84 | have($item`Cincho de Mayo`) && get("_cinchUsed") <= 95 && !get("instant_saveCinch", false) 85 | ? $item`Cincho de Mayo` 86 | : undefined, 87 | acc3: $item`spring shoes`, 88 | familiar: chooseFamiliar(allowAttackingFamiliars), 89 | modifier: `1 ${mainStatMaximizerStr}, 0.95 ML, 6 ${mainStatMaximizerStr} exp, 30 ${mainStatMaximizerStr} experience percent, -equip tinsel tights, -equip wad of used tape`, 90 | avoid: [ 91 | ...sugarItemsAboutToBreak(), 92 | ...(avoidDaylightShavingsHelm() ? [$item`Daylight Shavings Helmet`] : []), 93 | ], 94 | }; 95 | } 96 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | cliExecute, 3 | myAdventures, 4 | myAscensions, 5 | nowToString, 6 | print, 7 | setAutoAttack, 8 | turnsPlayed, 9 | userConfirm, 10 | visitUrl, 11 | } from "kolmafia"; 12 | import { 13 | checkGithubVersion, 14 | computeCombatFrequency, 15 | convertMilliseconds, 16 | simpleDateDiff, 17 | } from "./lib"; 18 | import { get, set, setCombatFlags, sinceKolmafiaRevision } from "libram"; 19 | import { Engine } from "./engine/engine"; 20 | import { Args, getTasks } from "grimoire-kolmafia"; 21 | import { Quest, Task } from "./engine/task"; 22 | import { HPQuest, MoxieQuest, MuscleQuest, MysticalityQuest } from "./tasks/stat"; 23 | import { LevelingQuest } from "./tasks/leveling"; 24 | import { CoilWireQuest } from "./tasks/coilwire"; 25 | import { RunStartQuest } from "./tasks/runstart"; 26 | import { FamiliarWeightQuest } from "./tasks/familiarweight"; 27 | import { NoncombatQuest } from "./tasks/noncombat"; 28 | import { BoozeDropQuest } from "./tasks/boozedrop"; 29 | import { HotResQuest } from "./tasks/hotres"; 30 | import { WeaponDamageQuest } from "./tasks/weapondamage"; 31 | import { DonateQuest } from "./tasks/donate"; 32 | import { SpellDamageQuest } from "./tasks/spelldamage"; 33 | import { checkRequirements } from "./sim"; 34 | import { checkResources } from "./resources"; 35 | 36 | const timeProperty = "fullday_elapsedTime"; 37 | 38 | export const args = Args.create("InstantSCCS", "An automated low-shiny SCCS script.", { 39 | confirm: Args.boolean({ 40 | help: "If the user must confirm execution of each task.", 41 | default: false, 42 | }), 43 | sim: Args.flag({ help: "Check if you have the requirements to run this script.", setting: "" }), 44 | savedresources: Args.flag({ 45 | help: "Check which resources you have current set to be saved.", 46 | setting: "", 47 | }), 48 | }); 49 | 50 | export function main(command?: string): void { 51 | sinceKolmafiaRevision(28726); 52 | checkGithubVersion(); 53 | 54 | Args.fill(args, command); 55 | if (args.help) { 56 | Args.showHelp(args); 57 | return; 58 | } 59 | if (args.sim) { 60 | checkRequirements(); 61 | return; 62 | } 63 | if (args.savedresources) { 64 | checkResources(); 65 | return; 66 | } 67 | 68 | if (runComplete()) { 69 | print("Community Service complete!", "purple"); 70 | return; 71 | } 72 | 73 | const setTimeNow = get(timeProperty, -1) === -1; 74 | if (setTimeNow) set(timeProperty, nowToString("yyyyMMddhhmmssSSS")); 75 | 76 | // Some checks to align mafia prefs 77 | visitUrl("museum.php?action=icehouse"); 78 | visitUrl("main.php"); 79 | cliExecute("refresh all"); 80 | 81 | // This does not factor in Offhand Remarkable, since if we do have it 82 | // we want to be able to benefit from it in the sdmg and wdmg tests 83 | // Running fam test -> NC test allows us to use OHR in the NC test and carry it on into the subsequent tests 84 | // Running NC test -> fam test would result in the OHR (obtained in the NC test) potentially being completely burnt in the fam test 85 | const swapFamAndNCTests = 86 | !get("instant_skipAutomaticOptimizations", false) && computeCombatFrequency() <= -95; 87 | 88 | const questList: Quest[] = [ 89 | RunStartQuest, 90 | CoilWireQuest, 91 | LevelingQuest, 92 | MysticalityQuest, 93 | HPQuest, 94 | MoxieQuest, 95 | MuscleQuest, 96 | swapFamAndNCTests ? NoncombatQuest : FamiliarWeightQuest, 97 | swapFamAndNCTests ? FamiliarWeightQuest : NoncombatQuest, 98 | BoozeDropQuest, 99 | HotResQuest, 100 | WeaponDamageQuest, 101 | SpellDamageQuest, 102 | DonateQuest, 103 | ]; 104 | const tasks: Task[] = getTasks(questList); 105 | 106 | print("Running the Quests in the following order:", "blue"); 107 | questList.forEach((quest) => print(quest.name, "blue")); 108 | 109 | const engine = new Engine(tasks); 110 | try { 111 | setAutoAttack(0); 112 | setCombatFlags({ flag: "aabosses", value: true }, { flag: "bothcombatinterf", value: false }); 113 | 114 | while (!runComplete()) { 115 | const task = engine.getNextTask(); 116 | if (task === undefined) throw "Unable to find available task, but the run is not complete"; 117 | if (args.confirm && !userConfirm(`Executing task ${task.name}, should we continue?`)) { 118 | throw `User rejected execution of task ${task.name}`; 119 | } 120 | if (task.ready !== undefined && !task.ready()) throw `Task ${task.name} is not ready`; 121 | engine.execute(task); 122 | } 123 | 124 | print("Community Service complete!", "purple"); 125 | print(`Adventures used: ${turnsPlayed()}`, "purple"); 126 | print(`Adventures remaining: ${myAdventures()}`, "purple"); 127 | print( 128 | `Time: ${convertMilliseconds( 129 | simpleDateDiff( 130 | get(timeProperty, nowToString("yyyyMMddhhmmssSSS")), 131 | nowToString("yyyyMMddhhmmssSSS"), 132 | ), 133 | )} since first run today started`, 134 | "purple", 135 | ); 136 | set(timeProperty, -1); 137 | } finally { 138 | engine.destruct(); 139 | } 140 | } 141 | 142 | function runComplete(): boolean { 143 | return get("kingLiberated") && get("lastEmptiedStorage") === myAscensions(); 144 | } 145 | -------------------------------------------------------------------------------- /src/tasks/donate.ts: -------------------------------------------------------------------------------- 1 | import { 2 | cliExecute, 3 | fullnessLimit, 4 | inebrietyLimit, 5 | myAscensions, 6 | myFullness, 7 | myInebriety, 8 | mySpleenUse, 9 | print, 10 | spleenLimit, 11 | turnsPlayed, 12 | } from "kolmafia"; 13 | import { 14 | $effect, 15 | $effects, 16 | $item, 17 | CommunityService, 18 | get, 19 | have, 20 | sumNumbers, 21 | uneffect, 22 | } from "libram"; 23 | import { 24 | farmingResourceResources, 25 | freeBanishResources, 26 | freeFightResources, 27 | freeKillResources, 28 | notableSkillResources, 29 | potentiallyFreeFightResources, 30 | trackedResource, 31 | } from "../engine/engine"; 32 | import { Quest } from "../engine/task"; 33 | import { testModifiers } from "../lib"; 34 | 35 | function printResourceUsage(tResource: trackedResource): void { 36 | const resource = tResource.resource; 37 | const name = tResource.name; 38 | const n = tResource.maxUses; 39 | 40 | const localResourceValue = get(`_instant_${resource}`.replace("__", "_"), "") 41 | .split(",") 42 | .join(", "); 43 | const resourceValue = 44 | typeof resource === "string" 45 | ? get(resource) 46 | : get(`_instant_${resource}_used`.replace("__", "_"), ""); 47 | const resourceValueLength = 48 | resourceValue.toString() !== "" ? resourceValue.toString().split(",").length : 0; 49 | 50 | if (typeof resourceValue === "boolean" || resourceValue === "true" || resourceValue === "false") 51 | print( 52 | `${name}: ${resourceValue || resourceValue === "true" ? n ?? 1 : 0}/${ 53 | n ?? "?" 54 | } ${localResourceValue}`, 55 | ); 56 | else if ( 57 | typeof resourceValue === "string" && 58 | (isNaN(parseInt(resourceValue)) || 59 | resourceValue.includes(",") || 60 | parseInt(resourceValue) > Math.abs(n ?? 1)) 61 | ) 62 | print( 63 | `${name}: ${resourceValueLength > (n ?? 1) ? n ?? 1 : resourceValueLength}/${ 64 | n ?? "?" 65 | } ${localResourceValue}`, 66 | ); 67 | else { 68 | if (n && !isNaN(parseInt(resourceValue)) && n < 0) { 69 | print(`${name}: ${-n - parseInt(resourceValue)}/${-n} ${localResourceValue}`); 70 | } else { 71 | print(`${name}: ${resourceValue}/${n ?? "?"} ${localResourceValue}`); 72 | } 73 | } 74 | } 75 | 76 | function logResourceUsage(): void { 77 | // Track resources used 78 | // Banishes 79 | print(""); 80 | print("Resource Tracking", "blue"); 81 | [ 82 | { header: "Banishes Used:", resourceArr: freeBanishResources }, 83 | { header: "Free Kills Used:", resourceArr: freeKillResources }, 84 | { header: "Notable Skills Used:", resourceArr: notableSkillResources }, 85 | { header: "Free Fights Used:", resourceArr: freeFightResources }, 86 | { header: "Potentially Free Fights Used:", resourceArr: potentiallyFreeFightResources }, 87 | { header: "Farming Resources:", resourceArr: farmingResourceResources }, 88 | ].map(({ header, resourceArr }) => { 89 | print(header); 90 | resourceArr.map(printResourceUsage); 91 | print(""); 92 | }); 93 | 94 | print( 95 | `Pulls Used: ${get("_roninStoragePulls") 96 | .split(",") 97 | .map((id) => (id.length > 0 ? $item`${id}`.name : "")) 98 | .join(", ")}`, 99 | ); 100 | print(""); 101 | 102 | // Organs Used 103 | print("Organs Used:"); 104 | print( 105 | `Stomach: ${myFullness()}/${fullnessLimit()} ${get("_instant_fullness", "") 106 | .split(",") 107 | .join(", ")}`, 108 | ); 109 | print( 110 | `Liver: ${myInebriety()}/${inebrietyLimit()} ${get("_instant_inebriety", "") 111 | .split(",") 112 | .join(", ")}`, 113 | ); 114 | print( 115 | `Spleen: ${mySpleenUse()}/${spleenLimit()} ${get("_instant_spleenUse", "") 116 | .split(",") 117 | .join(", ")}`, 118 | ); 119 | print( 120 | `Sweat Remaining: ${get("sweat")}/100, Sweat Out Some Booze: ${get("_sweatOutSomeBoozeUsed")}/3`, 121 | ); 122 | 123 | // Adventures Used 124 | print(""); 125 | print("Test Summary:"); 126 | 127 | const tests = Array.from(testModifiers.keys()); 128 | tests.forEach((whichTest) => 129 | print(`${whichTest.statName}: ${get(`_CSTest${whichTest.id}`, "?")}`), 130 | ); 131 | print( 132 | `Leveling: ${ 133 | turnsPlayed() - sumNumbers(tests.map((whichTest) => get(`_CSTest${whichTest.id}`, 0))) 134 | }`, 135 | ); 136 | print(`Adventures used: ${turnsPlayed()}`); 137 | 138 | print(""); 139 | } 140 | 141 | export const DonateQuest: Quest = { 142 | name: "Donate", 143 | tasks: [ 144 | { 145 | name: "Test", 146 | completed: () => get("kingLiberated"), 147 | do: () => CommunityService.donate(), 148 | limit: { tries: 1 }, 149 | }, 150 | { 151 | name: "Empty Hagnks", 152 | completed: () => get("lastEmptiedStorage") === myAscensions(), 153 | do: (): void => { 154 | logResourceUsage(); 155 | print("Emptying Hagnks!", "purple"); 156 | print("Please wait for up to 1 minute...", "blue"); 157 | cliExecute("hagnk all"); 158 | }, 159 | limit: { tries: 1 }, 160 | }, 161 | { 162 | name: "Shrug Negative Effects", 163 | completed: () => !have($effect`Feeling Lost`) && !have($effect`Cowrruption`), 164 | do: (): void => { 165 | for (const ef of $effects`Feeling Lost, Cowrruption, Cold Hearted`) { 166 | if (have(ef)) uneffect(ef); 167 | } 168 | }, 169 | limit: { tries: 1 }, 170 | }, 171 | ], 172 | }; 173 | -------------------------------------------------------------------------------- /src/tasks/noncombat.ts: -------------------------------------------------------------------------------- 1 | import { Quest } from "../engine/task"; 2 | import { 3 | buy, 4 | cliExecute, 5 | create, 6 | Effect, 7 | equippedItem, 8 | getCampground, 9 | numericModifier, 10 | print, 11 | runChoice, 12 | use, 13 | useSkill, 14 | visitUrl, 15 | } from "kolmafia"; 16 | import { 17 | $effect, 18 | $familiar, 19 | $item, 20 | $skill, 21 | $slot, 22 | CommunityService, 23 | get, 24 | have, 25 | uneffect, 26 | } from "libram"; 27 | import { 28 | acquiredOrExcluded, 29 | handleCustomBusks, 30 | handleCustomPulls, 31 | logTestSetup, 32 | tryAcquiringEffect, 33 | tryAcquiringEffects, 34 | wishFor, 35 | } from "../lib"; 36 | import { CombatStrategy } from "grimoire-kolmafia"; 37 | import Macro from "../combat"; 38 | 39 | const comTestMaximizerString = `-raw combat rate`; 40 | 41 | export const NoncombatQuest: Quest = { 42 | name: "Noncombat", 43 | completed: () => CommunityService.Noncombat.isDone(), 44 | tasks: [ 45 | { 46 | name: "Buy Porkpie-mounted Popper", 47 | ready: () => have($item`Clan VIP Lounge key`), 48 | completed: () => have($item`porkpie-mounted popper`), 49 | do: () => buy($item`porkpie-mounted popper`, 1), 50 | limit: { tries: 1 }, 51 | }, 52 | { 53 | name: "Photobooth NC Photo", 54 | ready: () => have($item`Clan VIP Lounge key`), 55 | completed: () => have($effect`Wild and Westy!`) || get("_photoBoothEffects", 0) >= 3, 56 | do: () => cliExecute("photobooth effect wild"), 57 | limit: { tries: 1 }, 58 | }, 59 | { 60 | name: "Use Shadow Lodestone", 61 | ready: () => have($item`Rufus's shadow lodestone`), 62 | completed: () => acquiredOrExcluded($effect`Shadow Waters`), 63 | do: (): void => { 64 | visitUrl("place.php?whichplace=town_right&action=townright_shadowrift"); 65 | runChoice(2); 66 | }, 67 | choices: { 68 | 1500: 2, 69 | }, 70 | combat: new CombatStrategy().macro(Macro.abort()), 71 | limit: { tries: 1 }, 72 | }, 73 | { 74 | name: "Favorite Bird (NC)", 75 | completed: () => 76 | !have($skill`Visit your Favorite Bird`) || 77 | acquiredOrExcluded($effect`Blessing of your favorite Bird`) || 78 | get("_favoriteBirdVisited") || 79 | !get("yourFavoriteBirdMods").includes("Combat Frequency") || 80 | get("instant_saveFavoriteBird", false), 81 | do: () => useSkill($skill`Visit your Favorite Bird`), 82 | limit: { tries: 1 }, 83 | }, 84 | { 85 | name: "Obscuri Tea", 86 | completed: () => 87 | acquiredOrExcluded($effect`Obscuri Tea`) || 88 | get("_pottedTeaTreeUsed") || 89 | get("instant_saveTeaTree", false) || 90 | getCampground()["potted tea tree"] === undefined, 91 | do: () => { 92 | cliExecute(`teatree cuppa Obscuri tea`); 93 | use($item`cuppa Obscuri tea`, 1); 94 | }, 95 | limit: { tries: 1 }, 96 | }, 97 | { 98 | name: "Test", 99 | completed: () => CommunityService.Noncombat.isDone(), 100 | prepare: (): void => { 101 | if (have($item`Jurassic Parka`) && get("parkaMode") !== "pterodactyl") 102 | cliExecute("parka pterodactyl"); 103 | if ( 104 | get("_kgbClicksUsed") < 22 && 105 | have($item`Kremlin's Greatest Briefcase`) && 106 | !get("instant_saveKGBClicks", false) 107 | ) 108 | cliExecute(`Briefcase e ${comTestMaximizerString}`); 109 | const usefulEffects: Effect[] = [ 110 | $effect`A Rose by Any Other Material`, 111 | $effect`Feeling Lonely`, 112 | $effect`Feeling Sneaky`, 113 | $effect`Gummed Shoes`, 114 | $effect`Hiding From Seekers`, 115 | $effect`Invisible Avatar`, 116 | $effect`Silent Running`, 117 | $effect`Smooth Movements`, 118 | $effect`The Sonata of Sneakiness`, 119 | $effect`Throwing Some Shade`, 120 | $effect`Ultra-Soft Steps`, 121 | 122 | // Famwt for Disgeist 123 | $effect`Blood Bond`, 124 | $effect`Leash of Linguini`, 125 | $effect`Empathy`, 126 | $effect`Puzzle Champ`, 127 | ]; 128 | tryAcquiringEffects(usefulEffects, true); 129 | if (!handleCustomPulls("instant_comTestPulls", comTestMaximizerString)) { 130 | cliExecute(`maximize ${comTestMaximizerString}`); // To avoid maximizer bug, we invoke this once more 131 | } 132 | handleCustomBusks("instant_comTestBusks"); 133 | 134 | if ( 135 | // Seems to be a bug where numericModifier doesn't recognize the -10 granted by an unbreakable umbrella, so check for that manually 136 | have($skill`Aug. 13th: Left/Off Hander's Day!`) && 137 | !get("instant_saveAugustScepter", false) && 138 | (numericModifier(equippedItem($slot`off-hand`), "Combat Rate") < 0 || 139 | equippedItem($slot`off-hand`) === $item`unbreakable umbrella`) && 140 | CommunityService.Noncombat.actualCost() > 1 && 141 | CommunityService.FamiliarWeight.isDone() // Only do this after the famwt test is done (if it isn't, we really shouldn't have shifted NC before famwt) 142 | ) { 143 | tryAcquiringEffect($effect`Offhand Remarkable`); 144 | } 145 | 146 | if ( 147 | CommunityService.Noncombat.actualCost() >= 7 && 148 | (have($item`mini kiwi`, 3) || have($item`mini kiwi antimilitaristic hippy petition`)) 149 | ) { 150 | if ( 151 | !have($item`mini kiwi antimilitaristic hippy petition`) && 152 | !have($effect`Hippy Antimilitarism`) 153 | ) 154 | create($item`mini kiwi antimilitaristic hippy petition`, 1); 155 | tryAcquiringEffect($effect`Hippy Antimilitarism`); 156 | } 157 | 158 | // If it saves us >= 6 turns, try using a wish 159 | if (CommunityService.Noncombat.actualCost() >= 7) wishFor($effect`Disquiet Riot`); 160 | }, 161 | do: (): void => { 162 | const maxTurns = get("instant_comTestTurnLimit", 12); 163 | const testTurns = CommunityService.Noncombat.actualCost(); 164 | if (testTurns > maxTurns) { 165 | print(`Expected to take ${testTurns}, which is more than ${maxTurns}.`, "red"); 166 | print("Either there was a bug, or you are under-prepared for this test", "red"); 167 | print("Manually complete the test if you think this is fine.", "red"); 168 | print( 169 | "You may also increase the turn limit by typing 'set instant_comTestTurnLimit='", 170 | "red", 171 | ); 172 | } 173 | CommunityService.Noncombat.run(() => logTestSetup(CommunityService.Noncombat), maxTurns); 174 | }, 175 | outfit: { 176 | familiar: have($familiar`Peace Turkey`) ? $familiar`Peace Turkey` : $familiar`Disgeist`, 177 | modifier: comTestMaximizerString, 178 | }, 179 | post: (): void => { 180 | uneffect($effect`The Sonata of Sneakiness`); 181 | }, 182 | limit: { tries: 1 }, 183 | }, 184 | ], 185 | }; 186 | -------------------------------------------------------------------------------- /LEVELING.md: -------------------------------------------------------------------------------- 1 | ### How to reduce turns spent on finding CBB ingredients 2 | 3 | In the process of powerleveling, the script also seeks to craft the [CBB](https://wiki.kingdomofloathing.com/Cookbookbat) foods laid out in the [run plan](https://github.com/Pantocyclus/InstantSCCS/blob/main/RUNPLAN.md) (as the foods also provide helpful stat% buffs for the stat tests). Thus, it would continue spending turns looking for the CBB ingredient drops necessary to craft all the foods it needs to craft (even though you are already overlevelled\*) - the CBB only drops 3 identical ingredients on every 11th turn, and the unmodified script requires the CBB to drop its ingredients for a total of 6 times\*\* (totalling 66 turns with the CBB for 3x6=18 total ingredients).
4 | 5 | To remedy this, you will have to [tell the script which CBB foods you no longer need](https://github.com/Pantocyclus/InstantSCCS?tab=readme-ov-file#im-pretty-shiny---can-i-get-the-script-to-save-certain-resourcesorgans) (for turngen and their corresponding stat% buffs). Note that you do not have to exclude every CBB food; you will only need to exclude some. Further note that, for accounts that are not too shiny, one may observe a reduction in turns spent leveling together with an increase in turns spent on the stat tests. You should remove CBB foods accordingly to strike a good balance (where the decrease in leveling turns more than offsets the increase in test turns).
6 | 7 | The common advice as to the general approach for deciding which CBB foods to exclude is as follows: One should note that the CBB ingredients are [seeded](https://docs.google.com/spreadsheets/d/10j0B1DTw64a-CaaBwMjiCJTTsGWOx0h4_KWpiAItB8s/edit). Specifically, note that for CS Day 1, the seeded CBB ingredient drops for Saucerors are in the following fixed order:
8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 |
Drop #IngredientsTotal
BJSPBJSP
13
233
3333
4336
5366
6369
82 | 83 | Furthermore, the script seeks to craft the following foods:
84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 |
CBB FoodsIngredients Needed
BJSP
plain calzone22
baked veggie ricotta casserole22
roasted vegetable of Jarlsberg (stats)2
roasted vegetable of Jarlsberg (item)2
Pete's Rich Ricotta2
honey bun of Boris1
Pete's wiley whey bar1
Total367
148 | 149 | By excluding Pete's wiley whey bar (at a cost of [50% moxie](https://wiki.kingdomofloathing.com/Awfully_Wily) and 5 turngen while saving 1 fullness), we can craft all the remaining CBB foods with just 3J 3B 3SP 3SP 3J (total: 3B 6J 6SP) - 5 CBB drops instead of 6!
150 | 151 | On the other hand, only excluding both of the roasted vegetable of Jarlsbergs (and nothing else) would have no effect on the leveling turns taken, since we would still need 6 CBB drops in order to fully satisfy the remaining ingredients required\*\*\*.
152 | 153 | You may, of course, remove any CBB foods as necessary in order to reduce organ usage. However, to specifically target reducing turns spent finding CBB ingredients, consider looking at which foods you can exclude (without taking too big a hit on the CS tests) that would bring down the number of CBB drops required to craft the remaining non-excluded foods. Here are some suggested food exclusions you may wish to consider:
154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 |
CBB Drops ReducedFoods ExcludedFullness SavedSignificant Buffs Lost
1Pete's wiley whey bar150% mox
2roasted vegetable of Jarlsberg x2, Pete's wiley whey bar3100% myst, 50% mox, 100% item
2baked veggie ricotta casserole, roasted vegetable of Jarlsberg3200% mox, 100% myst OR 100% item
3plain calzone, baked veggie ricotta casserole, roasted vegetable of Jarlsberg5200% mus, 200% mox, 100% myst OR 100% item
3baked veggie ricotta casserole, Pete's Rich Ricotta3300% mox
197 | 198 | \*It does stop powerleveling once you hit level 20.
199 | \*\*The [autumn-aton](https://wiki.kingdomofloathing.com/Autumn-aton) also drops random/non-seeded CBB ingredients if you have a cookbookbat. This may result in the script obtaining sufficient ingredients earlier (even without running through all 6 seeded CBB drops); however, this also adds a large deal of variance to the script's turncount.
200 | \*\*\*Refering to the seeded drop table, by the 5th drop we would still be lacking 1 SP (current total: 3B 6J 6SP, required total: 3B 2J 7SP).
201 | -------------------------------------------------------------------------------- /src/tasks/familiarweight.ts: -------------------------------------------------------------------------------- 1 | import { CombatStrategy } from "grimoire-kolmafia"; 2 | import { 3 | alliedRadio, 4 | cliExecute, 5 | create, 6 | Effect, 7 | equippedItem, 8 | familiarWeight, 9 | getCampground, 10 | haveEffect, 11 | itemAmount, 12 | mySign, 13 | numericModifier, 14 | print, 15 | retrieveItem, 16 | toInt, 17 | use, 18 | useFamiliar, 19 | visitUrl, 20 | } from "kolmafia"; 21 | import { 22 | $effect, 23 | $familiar, 24 | $familiars, 25 | $item, 26 | $location, 27 | $skill, 28 | $slot, 29 | AprilingBandHelmet, 30 | CommunityService, 31 | get, 32 | have, 33 | } from "libram"; 34 | import Macro from "../combat"; 35 | import { avoidDaylightShavingsHelm, sugarItemsAboutToBreak } from "../outfit"; 36 | import { Quest } from "../engine/task"; 37 | import { 38 | acquiredOrExcluded, 39 | chooseHeaviestEquippedFamiliar, 40 | handleCustomBusks, 41 | handleCustomPulls, 42 | haveAndNotExcluded, 43 | logTestSetup, 44 | tryAcquiringEffect, 45 | tryAcquiringEffects, 46 | } from "../lib"; 47 | import { chooseFamiliar } from "../familiars"; 48 | 49 | const famTestMaximizerString = "familiar weight, -equip dented scepter"; 50 | 51 | export const FamiliarWeightQuest: Quest = { 52 | name: "Familiar Weight", 53 | completed: () => CommunityService.FamiliarWeight.isDone(), 54 | tasks: [ 55 | { 56 | name: "Tune Moon to Platypus", 57 | completed: () => 58 | !have($item`hewn moon-rune spoon`) || 59 | get("moonTuned") || 60 | get("instant_saveMoonTune", false) || 61 | mySign() === "Platypus", 62 | do: (): void => { 63 | cliExecute("spoon platypus"); 64 | }, 65 | }, 66 | { 67 | name: "Fold Burning Newspaper", 68 | completed: () => !have($item`burning newspaper`), 69 | do: () => cliExecute("create burning paper crane"), 70 | limit: { tries: 1 }, 71 | }, 72 | { 73 | name: "Grubby Wool Pants", 74 | completed: () => !have($item`grubby wool`) || have($item`grubby wool trousers`), 75 | do: () => create($item`grubby wool trousers`, 1), 76 | limit: { tries: 1 }, 77 | }, 78 | { 79 | name: "Meteor Shower", 80 | completed: () => 81 | acquiredOrExcluded($effect`Meteor Showered`) || 82 | !have($item`Fourth of May Cosplay Saber`) || 83 | !have($skill`Meteor Lore`) || 84 | get("_saberForceUses") >= 5, 85 | do: $location`The Dire Warren`, 86 | combat: new CombatStrategy().macro( 87 | Macro.trySkill($skill`Meteor Shower`) 88 | .trySkill($skill`Use the Force`) 89 | .abort(), 90 | ), 91 | outfit: () => ({ 92 | weapon: $item`Fourth of May Cosplay Saber`, 93 | familiar: chooseFamiliar(false), 94 | avoid: [ 95 | ...sugarItemsAboutToBreak(), 96 | ...(avoidDaylightShavingsHelm() ? [$item`Daylight Shavings Helmet`] : []), 97 | ], 98 | }), 99 | choices: { 1387: 3 }, 100 | limit: { tries: 1 }, 101 | }, 102 | { 103 | name: "Loyal Tea", 104 | completed: () => 105 | acquiredOrExcluded($effect`Loyal Tea`) || 106 | get("_pottedTeaTreeUsed") || 107 | get("instant_saveTeaTree", false) || 108 | getCampground()["potted tea tree"] === undefined, 109 | do: () => { 110 | cliExecute(`teatree cuppa Loyal tea`); 111 | use($item`cuppa Loyal tea`, 1); 112 | }, 113 | limit: { tries: 1 }, 114 | }, 115 | { 116 | name: "Wildsun Boon", 117 | completed: () => 118 | !have($item`Allied Radio Backpack`) || 119 | // eslint-disable-next-line libram/verify-constants 120 | acquiredOrExcluded($effect`Wildsun Boon`) || 121 | get("_alliedRadioWildsunBoon", false) || 122 | get("_alliedRadioDropsUsed", 0) >= 3 - get("instant_saveAlliedRadio", 0), 123 | do: () => alliedRadio("Wildsun Boon"), 124 | limit: { tries: 1 }, 125 | }, 126 | { 127 | name: "Test", 128 | completed: () => CommunityService.FamiliarWeight.isDone(), 129 | prepare: (): void => { 130 | const usefulEffects: Effect[] = [ 131 | $effect`A Girl Named Sue`, 132 | $effect`Billiards Belligerence`, 133 | $effect`Blood Bond`, 134 | $effect`Boxing Day Glow`, 135 | $effect`Chorale of Companionship`, 136 | $effect`Do I Know You From Somewhere?`, 137 | $effect`Empathy`, 138 | $effect`Fidoxene`, 139 | $effect`Heart of Green`, 140 | $effect`Kindly Resolve`, 141 | $effect`Leash of Linguini`, 142 | $effect`Puzzle Champ`, 143 | $effect`Robot Friends`, 144 | $effect`Shortly Stacked`, 145 | $effect`Thoughtful Empathy`, 146 | ]; 147 | tryAcquiringEffects(usefulEffects, true); 148 | handleCustomPulls("instant_famTestPulls", famTestMaximizerString); 149 | handleCustomBusks("instant_famTestBusks"); 150 | 151 | if (have($item`love song of icy revenge`)) 152 | use( 153 | Math.min( 154 | 4 - Math.floor(haveEffect($effect`Cold Hearted`) / 5), 155 | itemAmount($item`love song of icy revenge`), 156 | ), 157 | $item`love song of icy revenge`, 158 | ); 159 | 160 | const heaviestWeight = chooseHeaviestEquippedFamiliar( 161 | $familiars``.filter((f) => f !== $familiar`Homemade Robot`), 162 | ).expectedWeight; 163 | const commaWeight = 6 + 11 * get("homemadeRobotUpgrades"); 164 | const useComma = 165 | $familiars`Comma Chameleon, Homemade Robot`.every((fam) => haveAndNotExcluded(fam)) && 166 | commaWeight > heaviestWeight; 167 | if ( 168 | $familiars`Comma Chameleon, Homemade Robot`.every((fam) => haveAndNotExcluded(fam)) && 169 | get("homemadeRobotUpgrades") < 9 170 | ) { 171 | print( 172 | `Comma Chameleon is not at max weight, use ${ 173 | 9 - get("homemadeRobotUpgrades") 174 | } more parts on Homemade Robot.`, 175 | "red", 176 | ); 177 | } 178 | const useTrainbot = 179 | haveAndNotExcluded($familiar`Mini-Trainbot`) && 180 | familiarWeight($familiar`Mini-Trainbot`) + 25 > heaviestWeight; 181 | const useParrot = 182 | haveAndNotExcluded($familiar`Exotic Parrot`) && 183 | familiarWeight($familiar`Exotic Parrot`) + 15 > heaviestWeight; 184 | 185 | const haveFamEquip = // We only need to check for robot gear since that has special handling 186 | have($item`box of Familiar Jacks`) || (useComma && have($item`homemade robot gear`)); 187 | if ( 188 | ((have($skill`Summon Clip Art`) && !get("instant_saveClipArt", false)) || // Either we can summon a box of jacks 189 | haveFamEquip) && // or we already have one 190 | (useTrainbot || useParrot || useComma) 191 | ) { 192 | if (!have($item`box of Familiar Jacks`) && have($skill`Summon Clip Art`)) 193 | create($item`box of Familiar Jacks`, 1); 194 | if (useComma) { 195 | if (!have($item`homemade robot gear`)) { 196 | useFamiliar($familiar`Homemade Robot`); 197 | use($item`box of Familiar Jacks`, 1); 198 | } 199 | useFamiliar($familiar`Comma Chameleon`); 200 | visitUrl( 201 | `inv_equip.php?which=2&action=equip&whichitem=${toInt( 202 | $item`homemade robot gear`, 203 | )}&pwd`, 204 | ); 205 | visitUrl("charpane.php"); 206 | } else { 207 | if (useTrainbot) useFamiliar($familiar`Mini-Trainbot`); 208 | else useFamiliar($familiar`Exotic Parrot`); 209 | use($item`box of Familiar Jacks`, 1); 210 | } 211 | 212 | cliExecute("maximize familiar weight"); 213 | } 214 | 215 | if ( 216 | have($item`Apriling band piccolo`) && 217 | get("_aprilBandPiccoloUses") < 3 && 218 | CommunityService.FamiliarWeight.actualCost() > 1 219 | ) { 220 | retrieveItem($item`Apriling band piccolo`); // We can't play the piccolo if it's equipped on a non-current familiar 221 | Array(3 - get("_aprilBandPiccoloUses")).forEach(() => 222 | AprilingBandHelmet.play($item`Apriling band piccolo`), 223 | ); 224 | } 225 | 226 | if ( 227 | have($skill`Aug. 13th: Left/Off Hander's Day!`) && 228 | !get("instant_saveAugustScepter", false) && 229 | numericModifier(equippedItem($slot`off-hand`), "Familiar Weight") > 0 && 230 | CommunityService.FamiliarWeight.actualCost() > 1 && 231 | CommunityService.FamiliarWeight.actualCost() <= 26 // We should really only be using this here if we have a chance of carrying OHR over to the other tests 232 | ) { 233 | tryAcquiringEffect($effect`Offhand Remarkable`); 234 | } 235 | }, 236 | do: (): void => { 237 | const maxTurns = get("instant_famTestTurnLimit", 50); 238 | const testTurns = CommunityService.FamiliarWeight.actualCost(); 239 | if (testTurns > maxTurns) { 240 | print(`Expected to take ${testTurns}, which is more than ${maxTurns}.`, "red"); 241 | print("Either there was a bug, or you are under-prepared for this test", "red"); 242 | print("Manually complete the test if you think this is fine.", "red"); 243 | print( 244 | "You may also increase the turn limit by typing 'set instant_famTestTurnLimit='", 245 | "red", 246 | ); 247 | } 248 | CommunityService.FamiliarWeight.run( 249 | () => logTestSetup(CommunityService.FamiliarWeight), 250 | maxTurns, 251 | ); 252 | }, 253 | outfit: () => ({ 254 | modifier: famTestMaximizerString, 255 | familiar: chooseHeaviestEquippedFamiliar( 256 | $familiars``.filter((f) => f !== $familiar`Homemade Robot`), 257 | ).familiar, 258 | }), 259 | limit: { tries: 1 }, 260 | }, 261 | ], 262 | }; 263 | -------------------------------------------------------------------------------- /src/tasks/hotres.ts: -------------------------------------------------------------------------------- 1 | import { CombatStrategy } from "grimoire-kolmafia"; 2 | import { 3 | buy, 4 | cliExecute, 5 | create, 6 | drink, 7 | Effect, 8 | getCampground, 9 | inebrietyLimit, 10 | itemAmount, 11 | myInebriety, 12 | print, 13 | use, 14 | useFamiliar, 15 | useSkill, 16 | visitUrl, 17 | } from "kolmafia"; 18 | import { 19 | $coinmaster, 20 | $effect, 21 | $familiar, 22 | $item, 23 | $location, 24 | $monster, 25 | $skill, 26 | CombatLoversLocket, 27 | CommunityService, 28 | get, 29 | have, 30 | uneffect, 31 | } from "libram"; 32 | import { Quest } from "../engine/task"; 33 | import { 34 | acquiredOrExcluded, 35 | handleCustomBusks, 36 | handleCustomPulls, 37 | haveAndNotExcluded, 38 | logTestSetup, 39 | tryAcquiringEffect, 40 | tryAcquiringEffects, 41 | tryAcquiringOdeToBooze, 42 | useParkaSpit, 43 | wishFor, 44 | } from "../lib"; 45 | import { sugarItemsAboutToBreak } from "../outfit"; 46 | import Macro from "../combat"; 47 | import { chooseFamiliar } from "../familiars"; 48 | 49 | const hotTestMaximizerString = "hot res"; 50 | 51 | export const HotResQuest: Quest = { 52 | name: "Hot Res", 53 | completed: () => CommunityService.HotRes.isDone(), 54 | tasks: [ 55 | { 56 | name: "Grab Spitball", 57 | completed: () => 58 | !have($item`April Shower Thoughts shield`) || 59 | itemAmount($item`glob of wet paper`) - 1 < get("instant_saveShowerGlobs", 0) || 60 | have($effect`Everything Looks Yellow`) || 61 | have($item`spitball`), 62 | do: () => { 63 | buy($coinmaster`Using your Shower Thoughts`, 1, $item`spitball`); 64 | }, 65 | limit: { tries: 1 }, 66 | }, 67 | { 68 | name: "Reminisce Factory Worker (female)", 69 | prepare: (): void => { 70 | if (useParkaSpit) { 71 | cliExecute("parka dilophosaur"); 72 | } else if (!have($item`yellow rocket`) && !have($effect`Everything Looks Yellow`)) { 73 | buy($item`yellow rocket`, 1); 74 | } 75 | }, 76 | completed: () => 77 | CombatLoversLocket.monstersReminisced().includes($monster`factory worker (female)`) || 78 | !CombatLoversLocket.availableLocketMonsters().includes($monster`factory worker (female)`) || 79 | get("instant_saveLocketFactoryWorker", false), 80 | do: () => CombatLoversLocket.reminisce($monster`factory worker (female)`), 81 | outfit: () => ({ 82 | back: $item`vampyric cloake`, 83 | shirt: $item`Jurassic Parka`, 84 | weapon: $item`Fourth of May Cosplay Saber`, 85 | offhand: have($skill`Double-Fisted Skull Smashing`) 86 | ? $item`industrial fire extinguisher` 87 | : have($effect`Everything Looks Yellow`) 88 | ? $item`April Shower Thoughts shield` 89 | : $item`Roman Candelabra`, 90 | familiar: chooseFamiliar(false), 91 | modifier: "Item Drop", 92 | avoid: sugarItemsAboutToBreak(), 93 | }), 94 | choices: { 1387: 3 }, 95 | combat: new CombatStrategy().macro( 96 | Macro.trySkill($skill`Become a Cloud of Mist`) 97 | .trySkill($skill`Fire Extinguisher: Foam Yourself`) 98 | .trySkill($skill`Use the Force`) 99 | .trySkill($skill`Shocking Lick`) 100 | .if_( 101 | "!haseffect Everything Looks Yellow", 102 | Macro.externalIf(useParkaSpit, Macro.trySkill($skill`Spit jurassic acid`)) 103 | .tryItem($item`spitball`) 104 | .trySkill($skill`Blow the Yellow Candle!`) 105 | .tryItem($item`yellow rocket`), 106 | ) 107 | .externalIf( 108 | have($item`April Shower Thoughts shield`), 109 | Macro.trySkill($skill`Northern Explosion`), 110 | ) 111 | .default(), 112 | ), 113 | limit: { tries: 1 }, 114 | }, 115 | { 116 | name: "Grab Foam Suit", 117 | completed: () => 118 | acquiredOrExcluded($effect`Fireproof Foam Suit`) || 119 | !have($item`Fourth of May Cosplay Saber`) || 120 | get("_saberForceUses") >= 5 || 121 | !have($item`industrial fire extinguisher`) || 122 | !have($skill`Double-Fisted Skull Smashing`), 123 | do: $location`The Dire Warren`, 124 | outfit: { 125 | back: $item`vampyric cloake`, 126 | weapon: $item`Fourth of May Cosplay Saber`, 127 | offhand: $item`industrial fire extinguisher`, 128 | familiar: $familiar`Cookbookbat`, 129 | modifier: "Item Drop", 130 | }, 131 | choices: { 1387: 3 }, 132 | combat: new CombatStrategy().macro( 133 | Macro.trySkill($skill`Become a Cloud of Mist`) 134 | .skill($skill`Fire Extinguisher: Foam Yourself`) 135 | .skill($skill`Use the Force`) 136 | .abort(), 137 | ), 138 | limit: { tries: 1 }, 139 | }, 140 | { 141 | name: "Drink Boris Beer", 142 | completed: () => 143 | acquiredOrExcluded($effect`Beery Cool`) || 144 | ((!have($item`bowl of cottage cheese`) || !have($item`Yeast of Boris`)) && 145 | !have($item`Boris's beer`)) || 146 | myInebriety() >= inebrietyLimit() || 147 | get("instant_saveBorisBeer", false), 148 | do: (): void => { 149 | tryAcquiringOdeToBooze(); 150 | if (have($item`Yeast of Boris`) && have($item`bowl of cottage cheese`)) 151 | create($item`Boris's beer`, 1); 152 | if (have($item`Boris's beer`)) drink($item`Boris's beer`, 1); 153 | uneffect($effect`Ode to Booze`); 154 | }, 155 | limit: { tries: 1 }, 156 | }, 157 | { 158 | name: "Horsery", 159 | completed: () => get("_horsery") === "pale horse" || !get("horseryAvailable"), 160 | do: () => cliExecute("horsery pale"), 161 | limit: { tries: 1 }, 162 | }, 163 | { 164 | name: "Metal Meteoroid", 165 | completed: () => !have($item`metal meteoroid`) || have($item`meteorite guard`), 166 | do: () => create($item`meteorite guard`, 1), 167 | limit: { tries: 1 }, 168 | }, 169 | { 170 | name: "Grubby Wool Scarf", 171 | completed: () => !have($item`grubby wool`) || have($item`grubby wool scarf`), 172 | do: () => create($item`grubby wool scarf`, 1), 173 | limit: { tries: 1 }, 174 | }, 175 | { 176 | name: "Favorite Bird (Hot Res)", 177 | completed: () => 178 | !have($skill`Visit your Favorite Bird`) || 179 | acquiredOrExcluded($effect`Blessing of your favorite Bird`) || 180 | get("_favoriteBirdVisited") || 181 | !get("yourFavoriteBirdMods").includes("Hot Resistance") || 182 | get("instant_saveFavoriteBird", false), 183 | do: () => useSkill($skill`Visit your Favorite Bird`), 184 | limit: { tries: 1 }, 185 | }, 186 | { 187 | name: "Embers-Only Jacket", 188 | completed: () => 189 | !have($item`Sept-Ember Censer`) || 190 | have($item`embers-only jacket`) || 191 | get("instant_saveEmbers", false) || 192 | get("availableSeptEmbers") === 0, 193 | do: () => 194 | visitUrl("shop.php?whichshop=september&action=buyitem&quantity=1&whichrow=1515&pwd"), // Grab Jacket 195 | limit: { tries: 1 }, 196 | }, 197 | { 198 | name: "Frost Tea", 199 | completed: () => 200 | acquiredOrExcluded($effect`Frost Tea`) || 201 | get("_pottedTeaTreeUsed") || 202 | get("instant_saveTeaTree", false) || 203 | getCampground()["potted tea tree"] === undefined, 204 | do: () => { 205 | cliExecute(`teatree cuppa Frost tea`); 206 | use($item`cuppa Frost tea`, 1); 207 | }, 208 | limit: { tries: 1 }, 209 | }, 210 | { 211 | name: "Test", 212 | prepare: (): void => { 213 | cliExecute("retrocape vampire hold"); 214 | if (get("parkaMode") !== "pterodactyl") cliExecute("parka pterodactyl"); 215 | if ( 216 | get("_kgbClicksUsed") < 22 && 217 | have($item`Kremlin's Greatest Briefcase`) && 218 | !get("instant_saveKGBClicks", false) 219 | ) 220 | cliExecute("Briefcase e hot"); 221 | 222 | const usefulEffects: Effect[] = [ 223 | $effect`Amazing`, 224 | $effect`Astral Shell`, 225 | $effect`Egged On`, 226 | $effect`Elemental Saucesphere`, 227 | $effect`Feeling Peaceful`, 228 | $effect`Hot-Headed`, 229 | $effect`Rainbowolin`, 230 | $effect`Rainbow Vaccine`, 231 | 232 | // Famwt Buffs 233 | $effect`Blood Bond`, 234 | $effect`Empathy`, 235 | $effect`Leash of Linguini`, 236 | $effect`Robot Friends`, 237 | $effect`Thoughtful Empathy`, 238 | ]; 239 | tryAcquiringEffects(usefulEffects, true); 240 | handleCustomPulls("instant_hotTestPulls", hotTestMaximizerString); 241 | handleCustomBusks("instant_hotTestBusks"); 242 | 243 | if ( 244 | CommunityService.HotRes.actualCost() >= 4 && 245 | (have($item`mini kiwi`, 3) || have($item`mini kiwi illicit antibiotic`)) 246 | ) { 247 | if ( 248 | !have($item`mini kiwi illicit antibiotic`) && 249 | !acquiredOrExcluded($effect`Incredibly Healthy`) 250 | ) 251 | create($item`mini kiwi illicit antibiotic`, 1); 252 | tryAcquiringEffect($effect`Incredibly Healthy`); 253 | } 254 | 255 | // If it saves us >= 6 turns, try using a wish 256 | if (CommunityService.HotRes.actualCost() >= 7) wishFor($effect`Fireproof Lips`); 257 | 258 | if ( 259 | CommunityService.HotRes.actualCost() > 1 && 260 | have($skill`Summon Clip Art`) && 261 | !get("instant_saveClipArt", false) && 262 | haveAndNotExcluded($familiar`Exotic Parrot`) && 263 | !have($item`cracker`) 264 | ) { 265 | if (!have($item`box of Familiar Jacks`)) create($item`box of Familiar Jacks`, 1); 266 | useFamiliar($familiar`Exotic Parrot`); 267 | use($item`box of Familiar Jacks`, 1); 268 | } 269 | }, 270 | completed: () => CommunityService.HotRes.isDone(), 271 | do: (): void => { 272 | const maxTurns = get("instant_hotTestTurnLimit", 35); 273 | const testTurns = CommunityService.HotRes.actualCost(); 274 | if (testTurns > maxTurns) { 275 | print(`Expected to take ${testTurns}, which is more than ${maxTurns}.`, "red"); 276 | print("Either there was a bug, or you are under-prepared for this test", "red"); 277 | print("Manually complete the test if you think this is fine.", "red"); 278 | print( 279 | "You may also increase the turn limit by typing 'set instant_hotTestTurnLimit='", 280 | "red", 281 | ); 282 | } 283 | CommunityService.HotRes.run(() => logTestSetup(CommunityService.HotRes), maxTurns); 284 | }, 285 | outfit: { 286 | modifier: hotTestMaximizerString, 287 | familiar: $familiar`Exotic Parrot`, 288 | }, 289 | post: (): void => { 290 | if (get("_horsery") === "pale horse") cliExecute("horsery dark"); 291 | }, 292 | limit: { tries: 1 }, 293 | }, 294 | ], 295 | }; 296 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /src/engine/engine.ts: -------------------------------------------------------------------------------- 1 | import { Task } from "./task"; 2 | import { Engine as BaseEngine, Outfit, outfitSlots } from "grimoire-kolmafia"; 3 | import { 4 | $effect, 5 | $familiar, 6 | $item, 7 | $skill, 8 | get, 9 | have, 10 | PropertiesManager, 11 | set, 12 | undelay, 13 | uneffect, 14 | } from "libram"; 15 | import { 16 | Item, 17 | itemAmount, 18 | myFullness, 19 | myHp, 20 | myInebriety, 21 | myMaxhp, 22 | mySpleenUse, 23 | print, 24 | toInt, 25 | toItem, 26 | totalFreeRests, 27 | useSkill, 28 | } from "kolmafia"; 29 | import { excludedFamiliars } from "../resources"; 30 | 31 | export class trackedResource { 32 | resource: string | Item; 33 | name: string; 34 | maxUses?: number; 35 | 36 | constructor(resource: string | Item, name: string, maxUses?: number) { 37 | this.resource = resource; 38 | this.name = name; 39 | if (maxUses) this.maxUses = maxUses; 40 | } 41 | } 42 | 43 | export const freeBanishResources: trackedResource[] = [ 44 | new trackedResource("_feelHatredUsed", "Feel Hatred", 3), 45 | new trackedResource("_reflexHammerUsed", "Reflex Hammer", 3), 46 | new trackedResource("_latteRefillsUsed", "Latte Refills", 3), 47 | new trackedResource("_kgbTranquilizerDartUses", "KGB Tranquilizers", 3), 48 | new trackedResource("_snokebombUsed", "Snokebomb", 3), 49 | ]; 50 | 51 | export const freeKillResources: trackedResource[] = [ 52 | new trackedResource("_chestXRayUsed", "Chest X-Ray", 3), 53 | new trackedResource("_shatteringPunchUsed", "Shattering Punch", 3), 54 | new trackedResource("_gingerbreadMobHitUsed", "Gingerbread Mob Hit", 1), 55 | new trackedResource("_missileLauncherUsed", "Missile Launcher", 1), 56 | new trackedResource("_CSParkaYRUsed", "Parka YR"), 57 | ]; 58 | 59 | export const notableSkillResources: trackedResource[] = [ 60 | new trackedResource("_saberForceUses", "Saber Forces", 5), 61 | new trackedResource("_monstersMapped", "Monsters Mapped", 3), 62 | new trackedResource("_feelEnvyUsed", "Feel Envy", 3), 63 | new trackedResource("_sourceTerminalDigitizeUses", "Digitize", 3), 64 | new trackedResource("_sourceTerminalPortscanUses", "Portscan", 3), 65 | new trackedResource("_sourceTerminalEnhanceUses", "Source Terminal Enhances", 3), 66 | new trackedResource("_sourceTerminalDuplicateUses", "Duplicate", 1), 67 | ]; 68 | 69 | export const freeFightResources: trackedResource[] = [ 70 | new trackedResource("_shadowAffinityToday", "Shadow Rift", 11), 71 | new trackedResource("_snojoFreeFights", "Snojo", 10), 72 | new trackedResource("_neverendingPartyFreeTurns", "NEP", 10), 73 | new trackedResource("_witchessFights", "Witchess", 5), 74 | new trackedResource("_machineTunnelsAdv", "DMT", 5), 75 | new trackedResource("_loveTunnelUsed", "LOV Tunnel", 3), 76 | new trackedResource("_voteFreeFights", "Voters", 3), 77 | new trackedResource("_godLobsterFights", "God Lobster", 3), 78 | new trackedResource("_speakeasyFreeFights", "Oliver's Place", 3), 79 | new trackedResource("_aprilBandTomUses", "Apriling Band Quad Tom", 3), 80 | new trackedResource("_eldritchHorrorEvoked", "Eldritch Tentacle", 1), 81 | new trackedResource("_sausageFights", "Sausage Goblins"), 82 | ]; 83 | 84 | export const potentiallyFreeFightResources: trackedResource[] = [ 85 | new trackedResource("_backUpUses", "Backup Camera", 11), 86 | new trackedResource("_leafMonstersFought", "Flaming Leaflets", 5), 87 | new trackedResource("_locketMonstersFought", "Locket Reminisces", 3), 88 | new trackedResource("_photocopyUsed", "Fax Machine", 1), 89 | new trackedResource("_chateauMonsterFought", "Chateau Painting", 1), 90 | ]; 91 | 92 | export const farmingResourceResources: trackedResource[] = [ 93 | new trackedResource("_powerfulGloveBatteryPowerUsed", "Powerful Glove Charges", 100), 94 | new trackedResource("_cinchUsed", "Cinch", 100), 95 | new trackedResource("_kgbClicksUsed", "KGB Clicks", 22), 96 | new trackedResource("_deckCardsDrawn", "Deck Draws", 15), 97 | new trackedResource("_mimicEggsObtained", "Mimic Eggs", 11), 98 | new trackedResource("_macrometeoriteUses", "Macrometeorites", 10), 99 | new trackedResource($item`battery (AAA)`, "Batteries (AAA)", 7), 100 | new trackedResource("availableSeptEmbers", "Sept Embers", -7), 101 | new trackedResource($item`pocket wish`, "Pocket Wishes (Genie + BofA)", 6), 102 | new trackedResource("_augSkillsCasts", "August Scepter Charges", 5), 103 | new trackedResource("_monkeyPawWishesUsed", "Monkey Paw Wishes", 5), 104 | new trackedResource("_beretBuskingUses", "Beret Busks", 5), 105 | new trackedResource("tomeSummons", "Tome Summons", 3), 106 | new trackedResource($item`peppermint sprout`, "Peppermint Sprout", 3), 107 | new trackedResource("_monsterHabitatsRecalled", "Monster Habitats", 3), 108 | new trackedResource("_alliedRadioDropsUsed", "Allied Radio", 3), 109 | new trackedResource("_aprilBandInstruments", "April Band Instruments", 2), 110 | new trackedResource("_favoriteBirdVisited", "Favorite Bird", 1), 111 | new trackedResource("_clanFortuneBuffUsed", "Zatara Consult", 1), 112 | new trackedResource("_floundryItemCreated", "Clan Floundry", 1), 113 | new trackedResource("_gingerbreadCityNoonCompleted", "GingerbreadCity Noon", 1), 114 | new trackedResource("_gingerbreadCityMidnightCompleted", "GingerbreadCity Midnight", 1), 115 | new trackedResource("_pantogramModifier", "Pantogram", 1), 116 | new trackedResource("_cargoPocketEmptied", "Cargo Shorts", 1), 117 | new trackedResource("_freePillKeeperUsed", "Pillkeeper", 1), 118 | new trackedResource("_alliedRadioMaterielIntel", "Materiel Intel", 1), 119 | new trackedResource("_pottedTeaTreeUsed", "Tea Tree", 1), 120 | new trackedResource("timesRested", "Free Rests", totalFreeRests()), 121 | ]; 122 | 123 | export const trackedResources: trackedResource[] = [ 124 | ...freeBanishResources, 125 | ...freeKillResources, 126 | ...notableSkillResources, 127 | ...freeFightResources, 128 | ...potentiallyFreeFightResources, 129 | ...farmingResourceResources, 130 | ]; 131 | 132 | export class Engine extends BaseEngine { 133 | public getNextTask(): Task | undefined { 134 | return this.tasks.find((task) => !task.completed() && (task.ready ? task.ready() : true)); 135 | } 136 | 137 | public execute(task: Task): void { 138 | const originalValues = trackedResources.map(({ resource }) => 139 | typeof resource === "string" 140 | ? [resource, get(resource).toString()] 141 | : [resource.name, `${itemAmount(resource)}`], 142 | ); 143 | const organUsage = () => [myFullness(), myInebriety(), mySpleenUse()]; 144 | const originalOrgans = organUsage(); 145 | this.checkLimits(task, undefined); 146 | 147 | super.execute(task); 148 | if (have($effect`Beaten Up`)) { 149 | if ( 150 | [ 151 | // "Poetic Justice", // grimoire automatically re-runs certain tasks here (https://github.com/loathers/grimoire/blob/main/src/engine.ts#L525) 152 | // "Lost and Found", // this includes all cleaver non-combats, so the script would never see these in lastEncounter 153 | "Sssshhsssblllrrggghsssssggggrrgglsssshhssslblgl", 154 | ].includes(get("lastEncounter")) 155 | ) 156 | uneffect($effect`Beaten Up`); 157 | else throw "Fight was lost; stop."; 158 | } 159 | originalValues.forEach(([resource, val]) => { 160 | const trackingMafiaPref = get(resource, "").toString().length > 0; 161 | if ( 162 | trackingMafiaPref 163 | ? val !== get(resource).toString() 164 | : itemAmount(toItem(resource)) < toInt(val) 165 | ) { 166 | const s = `_instant_${resource}`.replace("__", "_"); 167 | const arr = get(s, "").split(","); 168 | arr.push(task.name); 169 | set(s, arr.filter((v, i, a) => v.length > 0 && a.indexOf(v) === i).join(",")); 170 | if (!trackingMafiaPref) { 171 | const usagePref = `${s}_used`.replace("__", "_"); 172 | set(usagePref, get(usagePref, 0) + toInt(val) - itemAmount(toItem(resource))); 173 | } 174 | } 175 | }); 176 | organUsage().forEach((organUse, idx) => { 177 | if (organUse !== originalOrgans[idx]) { 178 | const s = `_instant_${["fullness", "inebriety", "spleenUse"][idx]}`; 179 | const arr = get(s, "").split(","); 180 | arr.push(task.name); 181 | set(s, arr.filter((v, i, a) => v.length > 0 && a.indexOf(v) === i).join(",")); 182 | } 183 | }); 184 | if (task.completed()) { 185 | print(`${task.name} completed!`, "blue"); 186 | } else { 187 | print(`${task.name} not completed!`, "blue"); 188 | } 189 | } 190 | 191 | createOutfit(task: Task): Outfit { 192 | // Handle unequippables in outfit here 193 | const spec = undelay(task.outfit); 194 | if (spec === undefined) { 195 | return new Outfit(); 196 | } 197 | 198 | if (spec.familiar && !have(spec.familiar)) { 199 | print(`Ignoring using a familiar because we don't have ${spec.familiar}`, "red"); 200 | spec.familiar = $familiar.none; 201 | } else if (excludedFamiliars.includes(spec.familiar ?? $familiar.none)) { 202 | print(`Refusing to use excluded familiar ${spec.familiar}`, "red"); 203 | } 204 | 205 | if (spec instanceof Outfit) { 206 | const badSlots = Array.from(spec.equips.entries()) 207 | .filter(([, it]) => !have(it) && it !== $item.none) 208 | .map(([s]) => s); 209 | badSlots.forEach((s) => { 210 | print(`Ignoring slot ${s} because we don't have ${spec.equips.get(s) ?? ""}`, "red"); 211 | spec.equips.delete(s); 212 | }); 213 | return spec.clone(); 214 | } 215 | 216 | // spec is an OutfitSpec 217 | for (const slotName of outfitSlots) { 218 | const itemOrItems = spec[slotName]; 219 | if (itemOrItems) { 220 | if (itemOrItems instanceof Item) { 221 | if (!have(itemOrItems) && itemOrItems !== null) { 222 | print(`Ignoring slot ${slotName} because we don't have ${itemOrItems}`, "red"); 223 | spec[slotName] = undefined; 224 | } 225 | } else { 226 | if (!itemOrItems.some((it) => have(it) && it !== null)) { 227 | print( 228 | `Ignoring slot ${slotName} because we don't have ${itemOrItems 229 | .map((it) => it.name) 230 | .join(", ")}`, 231 | "red", 232 | ); 233 | spec[slotName] = undefined; 234 | } 235 | } 236 | } 237 | } 238 | return Outfit.from(spec, new Error("Failed to equip outfit")); 239 | } 240 | 241 | dress(task: Task, outfit: Outfit): void { 242 | super.dress(task, outfit); 243 | } 244 | 245 | prepare(task: Task): void { 246 | super.prepare(task); 247 | if (task.combat !== undefined && myHp() < myMaxhp() * 0.9) useSkill($skill`Cannelloni Cocoon`); 248 | } 249 | 250 | initPropertiesManager(manager: PropertiesManager): void { 251 | super.initPropertiesManager(manager); 252 | const bannedAutoRestorers = [ 253 | "sleep on your clan sofa", 254 | "rest in your campaway tent", 255 | "rest at the chateau", 256 | "rest at your campground", 257 | "free rest", 258 | ]; 259 | const bannedAutoHpRestorers = [...bannedAutoRestorers]; 260 | const bannedAutoMpRestorers = [...bannedAutoRestorers]; 261 | const hpItems = get("hpAutoRecoveryItems") 262 | .split(";") 263 | .filter((s) => !bannedAutoHpRestorers.includes(s)) 264 | .join(";"); 265 | const mpItems = Array.from( 266 | new Set([...get("mpAutoRecoveryItems").split(";"), "doc galaktik's invigorating tonic"]), 267 | ) 268 | .filter((s) => !bannedAutoMpRestorers.includes(s)) 269 | .join(";"); 270 | manager.set({ 271 | autoSatisfyWithCloset: false, 272 | hpAutoRecovery: -0.05, 273 | mpAutoRecovery: -0.05, 274 | maximizerCombinationLimit: 0, 275 | hpAutoRecoveryItems: hpItems, 276 | mpAutoRecoveryItems: mpItems, 277 | shadowLabyrinthGoal: "effects", 278 | choiceAdventureScript: "instantsccs_choice.ash", 279 | requireBoxServants: false, 280 | }); 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /src/tasks/stat.ts: -------------------------------------------------------------------------------- 1 | import { buy, create, Effect, itemAmount, print, Stat, use, useSkill } from "kolmafia"; 2 | import { 3 | $coinmaster, 4 | $effect, 5 | $effects, 6 | $item, 7 | $items, 8 | $skill, 9 | $stat, 10 | CommunityService, 11 | ensureEffect, 12 | get, 13 | have, 14 | MayamCalendar, 15 | uneffect, 16 | withChoice, 17 | } from "libram"; 18 | import { Quest } from "../engine/task"; 19 | import { 20 | acquiredOrExcluded, 21 | handleCustomBusks, 22 | handleCustomPulls, 23 | logTestSetup, 24 | mainStat, 25 | reagentBalancerEffect, 26 | reagentBalancerItem, 27 | tryAcquiringEffects, 28 | useCenser, 29 | } from "../lib"; 30 | 31 | const hpTestMaximizerString = "HP, switch disembodied hand, -switch left-hand man"; 32 | const musTestMaximizerString = "Muscle, switch disembodied hand, -switch left-hand man"; 33 | const mystTestMaximizerString = "Mysticality, switch disembodied hand, -switch left-hand man"; 34 | const moxTestMaximizerString = "Mox, switch disembodied hand, -switch left-hand man"; 35 | 36 | function useBalancerForTest(testStat: Stat): void { 37 | if (testStat === mainStat) { 38 | return; 39 | } 40 | if (!have(reagentBalancerEffect) && !have(reagentBalancerItem)) { 41 | create(reagentBalancerItem, 1); 42 | } 43 | ensureEffect(reagentBalancerEffect); 44 | } 45 | 46 | export const HPQuest: Quest = { 47 | name: "HP", 48 | tasks: [ 49 | { 50 | name: "Mayam Calendar (Post-leveling)", 51 | completed: () => 52 | get("instant_saveMayamCalendar", false) || 53 | get("_mayamSymbolsUsed").includes("explosion") || 54 | !have($item`Mayam Calendar`), 55 | do: (): void => { 56 | const sym3 = useCenser ? "cheese" : "wall"; 57 | MayamCalendar.submit( 58 | MayamCalendar.toCombinationString(["eye", "bottle", sym3, "explosion"]), 59 | ); 60 | }, 61 | limit: { tries: 1 }, 62 | }, 63 | { 64 | name: "Test", 65 | completed: () => CommunityService.HP.isDone(), 66 | prepare: (): void => { 67 | useBalancerForTest($stat`Muscle`); 68 | $effects`Ur-Kel's Aria of Annoyance, Aloysius' Antiphon of Aptitude, Ode to Booze`.forEach( 69 | (ef) => uneffect(ef), 70 | ); 71 | const usefulEffects: Effect[] = [ 72 | $effect`A Few Extra Pounds`, 73 | $effect`Big`, 74 | $effect`Hulkien`, 75 | $effect`Mariachi Mood`, 76 | $effect`Patience of the Tortoise`, 77 | $effect`Power Ballad of the Arrowsmith`, 78 | $effect`Quiet Determination`, 79 | $effect`Reptilian Fortitude`, 80 | $effect`Saucemastery`, 81 | $effect`Seal Clubbing Frenzy`, 82 | $effect`Song of Starch`, 83 | $effect`Stevedave's Shanty of Superiority`, 84 | $effect`Triple-Sized`, 85 | ]; 86 | tryAcquiringEffects(usefulEffects, true); 87 | handleCustomPulls("instant_hpTestPulls", hpTestMaximizerString); 88 | handleCustomBusks("instant_hpTestBusks"); 89 | }, 90 | do: (): void => { 91 | const maxTurns = get("instant_hpTestTurnLimit", 1); 92 | const testTurns = CommunityService.HP.actualCost(); 93 | if (testTurns > maxTurns) { 94 | print(`Expected to take ${testTurns}, which is more than ${maxTurns}.`, "red"); 95 | print("Either there was a bug, or you are under-prepared for this test", "red"); 96 | print("Manually complete the test if you think this is fine.", "red"); 97 | print( 98 | "You may also increase the turn limit by typing 'set instant_hpTestTurnLimit='", 99 | "red", 100 | ); 101 | } 102 | CommunityService.HP.run(() => logTestSetup(CommunityService.HP), maxTurns); 103 | }, 104 | outfit: { modifier: hpTestMaximizerString }, 105 | limit: { tries: 1 }, 106 | }, 107 | ], 108 | }; 109 | 110 | export const MuscleQuest: Quest = { 111 | name: "Muscle", 112 | tasks: [ 113 | { 114 | name: "Test", 115 | completed: () => CommunityService.Muscle.isDone(), 116 | prepare: (): void => { 117 | useBalancerForTest($stat`Muscle`); 118 | if ( 119 | !acquiredOrExcluded($effect`Phorcefullness`) && 120 | !have($item`philter of phorce`) && 121 | $items`scrumptious reagent, lemon`.every((it) => have(it)) 122 | ) { 123 | create($item`philter of phorce`, 1); 124 | } 125 | const usefulEffects: Effect[] = [ 126 | $effect`Big`, 127 | $effect`Disco over Matter`, 128 | $effect`Disdain of the War Snapper`, 129 | $effect`Feeling Excited`, 130 | $effect`Go Get 'Em, Tiger!`, 131 | $effect`Hulkien`, 132 | $effect`Macaroni Coating`, 133 | $effect`Patience of the Tortoise`, 134 | $effect`Phorcefullness`, 135 | $effect`Power Ballad of the Arrowsmith`, 136 | $effect`Quiet Determination`, 137 | $effect`Rage of the Reindeer`, 138 | $effect`Song of Bravado`, 139 | $effect`Stevedave's Shanty of Superiority`, 140 | $effect`Strength of the Tortoise`, 141 | $effect`Triple-Sized`, 142 | ]; 143 | tryAcquiringEffects(usefulEffects, true); 144 | handleCustomPulls("instant_musTestPulls", musTestMaximizerString); 145 | handleCustomBusks("instant_musTestBusks"); 146 | }, 147 | do: (): void => { 148 | const maxTurns = get("instant_musTestTurnLimit", 2); 149 | const testTurns = CommunityService.Muscle.actualCost(); 150 | if (testTurns > maxTurns) { 151 | print(`Expected to take ${testTurns}, which is more than ${maxTurns}.`, "red"); 152 | print("Either there was a bug, or you are under-prepared for this test", "red"); 153 | print("Manually complete the test if you think this is fine.", "red"); 154 | print( 155 | "You may also increase the turn limit by typing 'set instant_musTestTurnLimit='", 156 | "red", 157 | ); 158 | } 159 | CommunityService.Muscle.run(() => logTestSetup(CommunityService.Muscle), maxTurns); 160 | }, 161 | outfit: { modifier: musTestMaximizerString }, 162 | post: (): void => { 163 | uneffect($effect`Power Ballad of the Arrowsmith`); 164 | }, 165 | limit: { tries: 1 }, 166 | }, 167 | ], 168 | }; 169 | 170 | export const MysticalityQuest: Quest = { 171 | name: "Mysticality", 172 | tasks: [ 173 | { 174 | name: "Test", 175 | completed: () => CommunityService.Mysticality.isDone(), 176 | prepare: (): void => { 177 | useBalancerForTest($stat`Mysticality`); 178 | if ( 179 | !acquiredOrExcluded($effect`Mystically Oiled`) && 180 | !have($item`ointment of the occult`) && 181 | $items`scrumptious reagent, grapefruit`.every((it) => have(it)) 182 | ) { 183 | create($item`ointment of the occult`, 1); 184 | } 185 | const usefulEffects: Effect[] = [ 186 | $effect`Big`, 187 | $effect`Disdain of She-Who-Was`, 188 | $effect`Feeling Excited`, 189 | $effect`Glittering Eyelashes`, 190 | $effect`Hulkien`, 191 | $effect`Mariachi Moisture`, 192 | $effect`Mystically Oiled`, 193 | $effect`Saucemastery`, 194 | $effect`Song of Bravado`, 195 | $effect`Stevedave's Shanty of Superiority`, 196 | $effect`The Magical Mojomuscular Melody`, 197 | $effect`Triple-Sized`, 198 | $effect`Tubes of Universal Meat`, 199 | $effect`Pasta Oneness`, 200 | $effect`Quiet Judgement`, 201 | ]; 202 | tryAcquiringEffects(usefulEffects, true); 203 | handleCustomPulls("instant_mystTestPulls", mystTestMaximizerString); 204 | handleCustomBusks("instant_mystTestBusks"); 205 | }, 206 | do: (): void => { 207 | const maxTurns = get("instant_mystTestTurnLimit", 1); 208 | const testTurns = CommunityService.Mysticality.actualCost(); 209 | if (testTurns > maxTurns) { 210 | print(`Expected to take ${testTurns}, which is more than ${maxTurns}.`, "red"); 211 | print("Either there was a bug, or you are under-prepared for this test", "red"); 212 | print("Manually complete the test if you think this is fine.", "red"); 213 | print( 214 | "You may also increase the turn limit by typing 'set instant_mystTestTurnLimit='", 215 | "red", 216 | ); 217 | } 218 | CommunityService.Mysticality.run( 219 | () => logTestSetup(CommunityService.Mysticality), 220 | maxTurns, 221 | ); 222 | }, 223 | outfit: { modifier: mystTestMaximizerString }, 224 | post: (): void => { 225 | uneffect($effect`The Magical Mojomuscular Melody`); 226 | }, 227 | limit: { tries: 1 }, 228 | }, 229 | ], 230 | }; 231 | 232 | export const MoxieQuest: Quest = { 233 | name: "Moxie", 234 | tasks: [ 235 | { 236 | // This is also useful for the BoozeDrop test, but we can grab the +10%mox here first 237 | name: "High Heels", 238 | completed: () => 239 | have($item`red-soled high heels`) || 240 | !have($item`2002 Mr. Store Catalog`) || 241 | get("availableMrStore2002Credits") <= get("instant_saveCatalogCredits", 0) || 242 | get("instant_skipHighHeels", false), 243 | do: (): void => { 244 | if (!have($item`Letter from Carrie Bradshaw`)) { 245 | buy($coinmaster`Mr. Store 2002`, 1, $item`Letter from Carrie Bradshaw`); 246 | } 247 | withChoice(1506, 3, () => use($item`Letter from Carrie Bradshaw`)); 248 | }, 249 | limit: { tries: 1 }, 250 | }, 251 | { 252 | name: "Test", 253 | completed: () => CommunityService.Moxie.isDone(), 254 | prepare: (): void => { 255 | useBalancerForTest($stat`Moxie`); 256 | const usefulEffects: Effect[] = [ 257 | // $effect`Amazing`, 258 | $effect`Big`, 259 | $effect`Blessing of the Bird`, 260 | $effect`Blubbered Up`, 261 | $effect`Butt-Rock Hair`, 262 | $effect`Disco Fever`, 263 | $effect`Disco Smirk`, 264 | $effect`Disco State of Mind`, 265 | $effect`Feeling Excited`, 266 | $effect`Hulkien`, 267 | $effect`Lubricating Sauce`, 268 | $effect`The Moxious Madrigal`, 269 | $effect`Triple-Sized`, 270 | $effect`Penne Fedora`, 271 | $effect`Pomp & Circumsands`, 272 | $effect`Quiet Desperation`, 273 | $effect`Slippery as a Seal`, 274 | $effect`Song of Bravado`, 275 | $effect`Stevedave's Shanty of Superiority`, 276 | $effect`Unrunnable Face`, 277 | ]; 278 | tryAcquiringEffects(usefulEffects, true); 279 | handleCustomPulls("instant_moxTestPulls", moxTestMaximizerString); 280 | handleCustomBusks("instant_moxTestBusks"); 281 | if (have($skill`Acquire Rhinestones`)) useSkill($skill`Acquire Rhinestones`); 282 | if (have($item`rhinestone`)) use($item`rhinestone`, itemAmount($item`rhinestone`)); 283 | }, 284 | do: (): void => { 285 | const maxTurns = get("instant_moxTestTurnLimit", 5); 286 | const testTurns = CommunityService.Moxie.actualCost(); 287 | if (testTurns > maxTurns) { 288 | print(`Expected to take ${testTurns}, which is more than ${maxTurns}.`, "red"); 289 | print("Either there was a bug, or you are under-prepared for this test", "red"); 290 | print("Manually complete the test if you think this is fine.", "red"); 291 | print( 292 | "You may also increase the turn limit by typing 'set instant_moxTestTurnLimit='", 293 | "red", 294 | ); 295 | } 296 | CommunityService.Moxie.run(() => logTestSetup(CommunityService.Moxie), maxTurns); 297 | }, 298 | outfit: { modifier: moxTestMaximizerString }, 299 | limit: { tries: 1 }, 300 | }, 301 | ], 302 | }; 303 | -------------------------------------------------------------------------------- /src/tasks/weapondamage.ts: -------------------------------------------------------------------------------- 1 | import { CombatStrategy, OutfitSpec } from "grimoire-kolmafia"; 2 | import { 3 | buy, 4 | cliExecute, 5 | create, 6 | Effect, 7 | equippedItem, 8 | getCampground, 9 | haveEquipped, 10 | inebrietyLimit, 11 | myBasestat, 12 | myClass, 13 | myHash, 14 | myHp, 15 | myInebriety, 16 | myMaxhp, 17 | myMeat, 18 | myThrall, 19 | numericModifier, 20 | outfit, 21 | print, 22 | restoreHp, 23 | retrieveItem, 24 | use, 25 | useSkill, 26 | visitUrl, 27 | } from "kolmafia"; 28 | import { 29 | $class, 30 | $effect, 31 | $effects, 32 | $familiar, 33 | $item, 34 | $items, 35 | $location, 36 | $skill, 37 | $slot, 38 | $stat, 39 | $thrall, 40 | BloodCubicZirconia, 41 | clamp, 42 | Clan, 43 | CommunityService, 44 | get, 45 | have, 46 | SongBoom, 47 | } from "libram"; 48 | import Macro, { haveFreeBanish, haveMotherSlimeBanish } from "../combat"; 49 | import { sugarItemsAboutToBreak } from "../outfit"; 50 | import { Quest } from "../engine/task"; 51 | import { 52 | acquiredOrExcluded, 53 | attemptRestoringMpWithFreeRests, 54 | handleCustomBusks, 55 | handleCustomPulls, 56 | logTestSetup, 57 | motherSlimeClan, 58 | startingClan, 59 | tryAcquiringEffect, 60 | tryAcquiringEffects, 61 | tryAcquiringOdeToBooze, 62 | wishFor, 63 | } from "../lib"; 64 | import { powerlevelingLocation } from "./leveling"; 65 | import { chooseFamiliar } from "../familiars"; 66 | 67 | const attemptKFH = have($skill`Kung Fu Hustler`) && have($familiar`Disembodied Hand`); 68 | const wpnTestMaximizerString = "weapon dmg, switch disembodied hand, -switch left-hand man"; 69 | 70 | export const WeaponDamageQuest: Quest = { 71 | name: "Weapon Damage", 72 | completed: () => CommunityService.WeaponDamage.isDone(), 73 | tasks: [ 74 | { 75 | name: "Drink Sockdollager", 76 | completed: () => 77 | acquiredOrExcluded($effect`In a Lather`) || 78 | myInebriety() >= inebrietyLimit() - 1 || 79 | myMeat() < 500 || 80 | get("instant_saveSockdollager", false), 81 | do: (): void => { 82 | tryAcquiringOdeToBooze(); 83 | visitUrl(`clan_viplounge.php?preaction=speakeasydrink&drink=6&pwd=${+myHash()}`); // Sockdollager 84 | }, 85 | limit: { tries: 1 }, 86 | }, 87 | { 88 | name: "Potion of Potency", 89 | completed: () => 90 | have($item`potion of potency`) || 91 | acquiredOrExcluded($effect`Pronounced Potency`) || 92 | !have($item`scrumptious reagent`) || 93 | !have($item`orange`), 94 | do: () => create($item`potion of potency`, 1), 95 | limit: { tries: 1 }, 96 | }, 97 | { 98 | name: "Carol Ghost Buff", 99 | prepare: (): void => { 100 | restoreHp(clamp(1000, myMaxhp() / 2, myMaxhp())); 101 | attemptRestoringMpWithFreeRests(50); 102 | }, 103 | completed: () => 104 | !have($familiar`Ghost of Crimbo Carols`) || 105 | !haveFreeBanish() || 106 | acquiredOrExcluded($effect`Do You Crush What I Crush?`) || 107 | $effects`Do You Crush What I Crush?, Holiday Yoked, Let It Snow/Boil/Stink/Frighten/Grease, All I Want For Crimbo Is Stuff, Crimbo Wrapping`.some( 108 | (ef) => have(ef), 109 | ), 110 | do: $location`The Dire Warren`, 111 | combat: new CombatStrategy().macro(Macro.banish().abort()), 112 | outfit: { 113 | offhand: $item`latte lovers member's mug`, 114 | acc1: $item`Kremlin's Greatest Briefcase`, 115 | acc2: $item`Lil' Doctor™ bag`, 116 | familiar: $familiar`Ghost of Crimbo Carols`, 117 | famequip: $item.none, 118 | }, 119 | limit: { tries: 1 }, 120 | }, 121 | { 122 | name: "Inner Elf", 123 | prepare: (): void => { 124 | if (have($item`Jurassic Parka`)) cliExecute("parka pterodactyl"); 125 | restoreHp(clamp(1000, myMaxhp() / 2, myMaxhp())); 126 | if ( 127 | myHp() > 30 && 128 | !$items`Jurassic Parka, Eight Days a Week Pill Keeper`.some((i) => haveEquipped(i)) 129 | ) { 130 | tryAcquiringEffect($effect`Blood Bubble`); 131 | restoreHp(clamp(1000, myMaxhp() / 2, myMaxhp())); 132 | } 133 | attemptRestoringMpWithFreeRests(50); 134 | Clan.join(motherSlimeClan); 135 | }, 136 | completed: () => 137 | !have($familiar`Machine Elf`) || 138 | !haveMotherSlimeBanish() || 139 | acquiredOrExcluded($effect`Inner Elf`) || 140 | motherSlimeClan === "", 141 | do: $location`The Slime Tube`, 142 | combat: new CombatStrategy().macro( 143 | Macro.trySkill($skill`KGB tranquilizer dart`) 144 | .trySkill($skill`Snokebomb`) 145 | .abort(), 146 | ), 147 | choices: { 326: 1 }, 148 | outfit: { 149 | shirt: $item`Jurassic Parka`, 150 | acc1: $item`Kremlin's Greatest Briefcase`, 151 | acc2: $item`Eight Days a Week Pill Keeper`, 152 | familiar: $familiar`Machine Elf`, 153 | modifier: "init", 154 | }, 155 | post: () => Clan.join(startingClan), 156 | limit: { tries: 1 }, 157 | }, 158 | { 159 | name: "Glob of Melted Wax", 160 | completed: () => !have($item`glob of melted wax`) || have($item`wax hand`), 161 | do: (): void => { 162 | visitUrl("inv_use.php?whichitem=9310&which=3&pwd"); 163 | visitUrl("choice.php?whichchoice=1218&option=2&pwd"); 164 | visitUrl("main.php"); 165 | }, 166 | limit: { tries: 1 }, 167 | }, 168 | { 169 | name: "Meteor Shower", 170 | completed: () => 171 | acquiredOrExcluded($effect`Meteor Showered`) || 172 | !have($item`Fourth of May Cosplay Saber`) || 173 | !have($skill`Meteor Lore`) || 174 | get("_saberForceUses") >= 5, 175 | do: attemptKFH ? powerlevelingLocation() : $location`The Dire Warren`, 176 | combat: new CombatStrategy().macro( 177 | Macro.trySkill($skill`Meteor Shower`) 178 | .trySkill($skill`%fn, spit on me!`) 179 | .trySkill($skill`Use the Force`) 180 | .abort(), 181 | ), 182 | outfit: (): OutfitSpec => { 183 | return attemptKFH 184 | ? { 185 | weapon: $item.none, 186 | offhand: $item.none, 187 | familiar: $familiar`Disembodied Hand`, 188 | famequip: $item`Fourth of May Cosplay Saber`, 189 | avoid: sugarItemsAboutToBreak(), 190 | } 191 | : { 192 | weapon: $item`Fourth of May Cosplay Saber`, 193 | familiar: get("camelSpit") >= 100 ? $familiar`Melodramedary` : chooseFamiliar(false), 194 | avoid: sugarItemsAboutToBreak(), 195 | }; 196 | }, 197 | choices: { 1387: 3 }, 198 | limit: { tries: 1 }, 199 | }, 200 | { 201 | name: "Favorite Bird (Weapon Damage)", 202 | completed: () => 203 | !have($skill`Visit your Favorite Bird`) || 204 | acquiredOrExcluded($effect`Blessing of your favorite Bird`) || 205 | get("_favoriteBirdVisited") || 206 | !get("yourFavoriteBirdMods").includes("Weapon Damage") || 207 | get("instant_saveFavoriteBird", false), 208 | do: () => useSkill($skill`Visit your Favorite Bird`), 209 | limit: { tries: 1 }, 210 | }, 211 | { 212 | name: "Twen Tea", 213 | completed: () => 214 | acquiredOrExcluded($effect`Twen Tea`) || 215 | get("_pottedTeaTreeUsed") || 216 | get("instant_saveTeaTree", false) || 217 | getCampground()["potted tea tree"] === undefined, 218 | do: () => { 219 | cliExecute(`teatree cuppa Twen tea`); 220 | use($item`cuppa Twen tea`, 1); 221 | }, 222 | limit: { tries: 1 }, 223 | }, 224 | { 225 | name: "BCZ Blood Bath", 226 | completed: () => 227 | !BloodCubicZirconia.have() || 228 | // eslint-disable-next-line libram/verify-constants 229 | BloodCubicZirconia.timesCast($skill`BCZ: Blood Bath`) > 0 || 230 | get("instant_saveBCZBloodBath", false) || 231 | // eslint-disable-next-line libram/verify-constants 232 | acquiredOrExcluded($effect`Bloodbathed`), 233 | do: () => { 234 | // eslint-disable-next-line libram/verify-constants 235 | useSkill($skill`BCZ: Blood Bath`); 236 | }, 237 | outfit: { 238 | // eslint-disable-next-line libram/verify-constants 239 | acc1: $item`blood cubic zirconia`, 240 | }, 241 | limit: { tries: 1 }, 242 | }, 243 | { 244 | name: "Stick-Knife Trick", 245 | ready: () => 246 | get("instant_stickKnifeOutfit") !== "" && 247 | myClass() === $class`Pastamancer` && 248 | have($item`Stick-Knife of Loathing`) && 249 | (have($skill`Bind Undead Elbow Macaroni`) || myThrall() === $thrall`Elbow Macaroni`), 250 | completed: () => 251 | haveEquipped($item`Stick-Knife of Loathing`) || 252 | have($familiar`Disembodied Hand`) || 253 | myBasestat($stat`Mysticality`) < 150 || 254 | myBasestat($stat`Muscle`) >= 150, 255 | do: (): void => { 256 | if (myThrall() !== $thrall`Elbow Macaroni`) useSkill($skill`Bind Undead Elbow Macaroni`); 257 | outfit(get("instant_stickKnifeOutfit")); 258 | }, 259 | limit: { tries: 1 }, 260 | }, 261 | { 262 | name: "Test", 263 | prepare: (): void => { 264 | if (have($item`SongBoom™ BoomBox`)) SongBoom.setSong("These Fists Were Made for Punchin'"); 265 | if (!have($item`goofily-plumed helmet`)) buy($item`goofily-plumed helmet`, 1); 266 | if ( 267 | have($item`Ye Wizard's Shack snack voucher`) && 268 | !acquiredOrExcluded($effect`Wasabi With You`) 269 | ) 270 | retrieveItem($item`wasabi marble soda`); 271 | const usefulEffects: Effect[] = [ 272 | $effect`Barrel Chested`, 273 | $effect`Billiards Belligerence`, 274 | $effect`Bow-Legged Swagger`, 275 | $effect`Carol of the Bulls`, 276 | $effect`Cowrruption`, 277 | $effect`Destructive Resolve`, 278 | $effect`Disdain of the War Snapper`, 279 | $effect`Faboooo`, 280 | $effect`Feeling Punchy`, 281 | $effect`Frenzied, Bloody`, 282 | $effect`Grumpy and Ornery`, 283 | $effect`Imported Strength`, 284 | $effect`Jackasses' Symphony of Destruction`, 285 | $effect`Lack of Body-Building`, 286 | $effect`Pronounced Potency`, 287 | $effect`Rage of the Reindeer`, 288 | $effect`Rictus of Yeg`, 289 | $effect`Seeing Red`, 290 | $effect`Scowl of the Auk`, 291 | $effect`Song of the North`, 292 | $effect`Tenacity of the Snapper`, 293 | $effect`The Power of LOV`, 294 | $effect`Wasabi With You`, 295 | $effect`Weapon of Mass Destruction`, 296 | ]; 297 | tryAcquiringEffects(usefulEffects, true); 298 | handleCustomPulls("instant_weaponTestPulls", wpnTestMaximizerString); 299 | handleCustomBusks("instant_weaponTestBusks"); 300 | 301 | if ( 302 | have($skill`Aug. 13th: Left/Off Hander's Day!`) && 303 | !get("instant_saveAugustScepter", false) && 304 | numericModifier(equippedItem($slot`off-hand`), "Weapon Damage") + 305 | numericModifier(equippedItem($slot`off-hand`), "Weapon Damage Percent") > 306 | 0 && 307 | CommunityService.WeaponDamage.actualCost() > 1 308 | ) { 309 | tryAcquiringEffect($effect`Offhand Remarkable`); 310 | } 311 | 312 | // If it saves us >= 6 turns, try using a wish 313 | if (CommunityService.WeaponDamage.actualCost() >= 7) wishFor($effect`Outer Wolf™`); 314 | $effects`Spit Upon, Pyramid Power`.forEach((ef) => { 315 | if (CommunityService.WeaponDamage.actualCost() >= 5) wishFor(ef); // The effects each save 2 turns on spelltest as well 316 | }); 317 | 318 | if ( 319 | !acquiredOrExcluded($effect`Rictus of Yeg`) && 320 | CommunityService.WeaponDamage.actualCost() >= 5 && 321 | !get("_cargoPocketEmptied") && 322 | have($item`Cargo Cultist Shorts`) && 323 | !get("instant_saveCargoShorts", false) 324 | ) { 325 | visitUrl("inventory.php?action=pocket"); 326 | visitUrl("choice.php?whichchoice=1420&option=1&pocket=284"); 327 | tryAcquiringEffect($effect`Rictus of Yeg`); 328 | } 329 | }, 330 | completed: () => CommunityService.WeaponDamage.isDone(), 331 | do: (): void => { 332 | const maxTurns = get("instant_wpnTestTurnLimit", 35); 333 | const testTurns = CommunityService.WeaponDamage.actualCost(); 334 | if (testTurns > maxTurns) { 335 | print(`Expected to take ${testTurns}, which is more than ${maxTurns}.`, "red"); 336 | print("Either there was a bug, or you are under-prepared for this test", "red"); 337 | print("Manually complete the test if you think this is fine.", "red"); 338 | print( 339 | "You may also increase the turn limit by typing 'set instant_wpnTestTurnLimit='", 340 | "red", 341 | ); 342 | } 343 | CommunityService.WeaponDamage.run( 344 | () => logTestSetup(CommunityService.WeaponDamage), 345 | maxTurns, 346 | ); 347 | }, 348 | outfit: { modifier: wpnTestMaximizerString }, 349 | limit: { tries: 1 }, 350 | }, 351 | ], 352 | }; 353 | -------------------------------------------------------------------------------- /src/tasks/spelldamage.ts: -------------------------------------------------------------------------------- 1 | import { CombatStrategy } from "grimoire-kolmafia"; 2 | import { 3 | buy, 4 | cliExecute, 5 | create, 6 | drink, 7 | Effect, 8 | elementalResistance, 9 | equip, 10 | equippedItem, 11 | haveEquipped, 12 | inebrietyLimit, 13 | myAdventures, 14 | myBasestat, 15 | myClass, 16 | myHp, 17 | myInebriety, 18 | myLevel, 19 | myMaxhp, 20 | myMeat, 21 | myThrall, 22 | numericModifier, 23 | outfit, 24 | print, 25 | restoreHp, 26 | retrieveItem, 27 | useSkill, 28 | visitUrl, 29 | } from "kolmafia"; 30 | import { 31 | $class, 32 | $effect, 33 | $effects, 34 | $element, 35 | $familiar, 36 | $item, 37 | $items, 38 | $location, 39 | $skill, 40 | $slot, 41 | $stat, 42 | $thrall, 43 | BloodCubicZirconia, 44 | clamp, 45 | Clan, 46 | CommunityService, 47 | get, 48 | have, 49 | } from "libram"; 50 | import { Quest } from "../engine/task"; 51 | import { 52 | acquiredOrExcluded, 53 | attemptRestoringMpWithFreeRests, 54 | handleCustomBusks, 55 | handleCustomPulls, 56 | logTestSetup, 57 | motherSlimeClan, 58 | startingClan, 59 | tryAcquiringEffect, 60 | tryAcquiringEffects, 61 | tryAcquiringOdeToBooze, 62 | } from "../lib"; 63 | import Macro, { haveFreeBanish, haveMotherSlimeBanish } from "../combat"; 64 | import { sugarItemsAboutToBreak } from "../outfit"; 65 | import { chooseFamiliar } from "../familiars"; 66 | 67 | let triedDeepDark = false; 68 | const spellTestMaximizerString = "spell dmg, switch disembodied hand, -switch left-hand man"; 69 | 70 | export const SpellDamageQuest: Quest = { 71 | name: "Spell Damage", 72 | completed: () => CommunityService.SpellDamage.isDone(), 73 | tasks: [ 74 | { 75 | name: "Concentrated Cordial of Concentration", 76 | completed: () => 77 | myClass() !== $class`Accordion Thief` || // Must be a Accordion Thief for guild infiltration 78 | myLevel() < 9 || // Need at least L9 to access other guilds 79 | !have($item`tearaway pants`) || // Need tearaway pants for free access to Moxie guild 80 | !have($skill`Superhuman Cocktailcrafting`) || // Need to upgrade soda water into tonic water 81 | (get("reagentSummons") !== 0 && 82 | !have($item`scrumptious reagent`) && 83 | !have($item`scrumdiddlyumptious solution`)) || // Need a spare reagent 84 | (myMeat() < 1070 && 85 | !have($item`scrumdiddlyumptious solution`) && 86 | !have($item`delectable catalyst`)) || // Need enough meat to purchase a delectable catalyst 87 | have($item`concentrated cordial of concentration`) || 88 | acquiredOrExcluded($effect`Concentrated Concentration`), 89 | do: (): void => { 90 | visitUrl("guild.php?place=challenge"); // Ensure free access to Moxie guild 91 | if (get("reagentSummons") === 0) useSkill($skill`Advanced Saucecrafting`, 1); 92 | create($item`concentrated cordial of concentration`); 93 | }, 94 | outfit: { 95 | pants: $item`tearaway pants`, 96 | }, 97 | limit: { tries: 1 }, 98 | }, 99 | { 100 | name: "Cordial of Concentration", 101 | completed: () => 102 | (get("reagentSummons") !== 0 && !have($item`scrumptious reagent`)) || // Need a spare reagent 103 | acquiredOrExcluded($effect`Concentration`) || 104 | have($item`cordial of concentration`), 105 | do: (): void => { 106 | if (get("reagentSummons") === 0) useSkill($skill`Advanced Saucecrafting`, 1); 107 | create($item`cordial of concentration`); 108 | }, 109 | limit: { tries: 1 }, 110 | }, 111 | { 112 | name: "Simmer", 113 | completed: () => acquiredOrExcluded($effect`Simmering`) || !have($skill`Simmer`), 114 | do: () => useSkill($skill`Simmer`), 115 | outfit: { 116 | offhand: $item`April Shower Thoughts shield`, 117 | }, 118 | limit: { tries: 1 }, 119 | }, 120 | { 121 | name: "Cargo Shorts", 122 | ready: () => !get("instant_saveCargoShorts", false), 123 | completed: () => get("_cargoPocketEmptied") || !have($item`Cargo Cultist Shorts`), 124 | do: (): void => { 125 | visitUrl("inventory.php?action=pocket"); 126 | visitUrl("choice.php?whichchoice=1420&option=1&pocket=177"); 127 | }, 128 | limit: { tries: 1 }, 129 | }, 130 | { 131 | name: "Carol Ghost Buff", 132 | prepare: (): void => { 133 | restoreHp(clamp(1000, myMaxhp() / 2, myMaxhp())); 134 | attemptRestoringMpWithFreeRests(50); 135 | }, 136 | completed: () => 137 | !have($familiar`Ghost of Crimbo Carols`) || 138 | !haveFreeBanish() || 139 | acquiredOrExcluded($effect`Do You Crush What I Crush?`) || 140 | $effects`Do You Crush What I Crush?, Holiday Yoked, Let It Snow/Boil/Stink/Frighten/Grease, All I Want For Crimbo Is Stuff, Crimbo Wrapping`.some( 141 | (ef) => have(ef), 142 | ), 143 | do: $location`The Dire Warren`, 144 | combat: new CombatStrategy().macro(Macro.banish().abort()), 145 | outfit: { 146 | offhand: $item`latte lovers member's mug`, 147 | acc1: $item`Kremlin's Greatest Briefcase`, 148 | acc2: $item`Lil' Doctor™ bag`, 149 | familiar: $familiar`Ghost of Crimbo Carols`, 150 | famequip: $item.none, 151 | }, 152 | limit: { tries: 1 }, 153 | }, 154 | { 155 | name: "Inner Elf", 156 | prepare: (): void => { 157 | if (have($item`Jurassic Parka`)) cliExecute("parka pterodactyl"); 158 | restoreHp(clamp(1000, myMaxhp() / 2, myMaxhp())); 159 | if ( 160 | myHp() > 30 && 161 | !$items`Jurassic Parka, Eight Days a Week Pill Keeper`.some((i) => haveEquipped(i)) 162 | ) { 163 | tryAcquiringEffect($effect`Blood Bubble`); 164 | restoreHp(clamp(1000, myMaxhp() / 2, myMaxhp())); 165 | } 166 | attemptRestoringMpWithFreeRests(50); 167 | Clan.join(motherSlimeClan); 168 | }, 169 | completed: () => 170 | !have($familiar`Machine Elf`) || 171 | !haveMotherSlimeBanish() || 172 | acquiredOrExcluded($effect`Inner Elf`) || 173 | motherSlimeClan === "", 174 | do: $location`The Slime Tube`, 175 | combat: new CombatStrategy().macro( 176 | Macro.trySkill($skill`KGB tranquilizer dart`) 177 | .trySkill($skill`Snokebomb`) 178 | .abort(), 179 | ), 180 | choices: { 326: 1 }, 181 | outfit: { 182 | shirt: $item`Jurassic Parka`, 183 | acc1: $item`Kremlin's Greatest Briefcase`, 184 | acc2: $item`Eight Days a Week Pill Keeper`, 185 | familiar: $familiar`Machine Elf`, 186 | modifier: "init", 187 | }, 188 | post: () => Clan.join(startingClan), 189 | limit: { tries: 1 }, 190 | }, 191 | { 192 | name: "Meteor Shower", 193 | completed: () => 194 | acquiredOrExcluded($effect`Meteor Showered`) || 195 | !have($item`Fourth of May Cosplay Saber`) || 196 | !have($skill`Meteor Lore`) || 197 | get("_saberForceUses") >= 5, 198 | do: $location`The Dire Warren`, 199 | combat: new CombatStrategy().macro( 200 | Macro.trySkill($skill`Meteor Shower`) 201 | .trySkill($skill`%fn, spit on me!`) 202 | .trySkill($skill`Use the Force`) 203 | .abort(), 204 | ), 205 | outfit: () => ({ 206 | weapon: $item`Fourth of May Cosplay Saber`, 207 | familiar: get("camelSpit") >= 100 ? $familiar`Melodramedary` : chooseFamiliar(false), 208 | avoid: sugarItemsAboutToBreak(), 209 | }), 210 | choices: { 1387: 3 }, 211 | limit: { tries: 1 }, 212 | }, 213 | { 214 | name: "Deep Dark Visions", 215 | completed: () => 216 | acquiredOrExcluded($effect`Visions of the Deep Dark Deeps`) || 217 | !have($skill`Deep Dark Visions`) || 218 | triedDeepDark, 219 | prepare: () => tryAcquiringEffects($effects`Astral Shell, Elemental Saucesphere`), 220 | do: (): void => { 221 | triedDeepDark = true; 222 | const resist = 1 - elementalResistance($element`spooky`) / 100; 223 | const neededHp = Math.max(500, myMaxhp() * 4 * resist); 224 | if (myMaxhp() < neededHp) return; 225 | if (myHp() < neededHp) restoreHp(neededHp); 226 | tryAcquiringEffect($effect`Visions of the Deep Dark Deeps`); 227 | }, 228 | outfit: { modifier: "HP 500max, Spooky Resistance", familiar: $familiar`Exotic Parrot` }, 229 | limit: { tries: 1 }, 230 | }, 231 | { 232 | name: "BCZ Dial it up to 11", 233 | completed: () => 234 | !BloodCubicZirconia.have() || 235 | // eslint-disable-next-line libram/verify-constants 236 | BloodCubicZirconia.timesCast($skill`BCZ: Dial it up to 11`) > 0 || 237 | get("instant_saveBCZDialitup", false) || 238 | // eslint-disable-next-line libram/verify-constants 239 | acquiredOrExcluded($effect`Up To 11`), 240 | do: () => { 241 | // eslint-disable-next-line libram/verify-constants 242 | useSkill($skill`BCZ: Dial it up to 11`); 243 | }, 244 | outfit: { 245 | // eslint-disable-next-line libram/verify-constants 246 | acc1: $item`blood cubic zirconia`, 247 | }, 248 | limit: { tries: 1 }, 249 | }, 250 | { 251 | name: "Stick-Knife Trick", 252 | ready: () => 253 | get("instant_stickKnifeOutfit") !== "" && 254 | myClass() === $class`Pastamancer` && 255 | have($item`Stick-Knife of Loathing`) && 256 | (have($skill`Bind Undead Elbow Macaroni`) || myThrall() === $thrall`Elbow Macaroni`), 257 | completed: () => 258 | haveEquipped($item`Stick-Knife of Loathing`) || 259 | have($familiar`Disembodied Hand`) || 260 | myBasestat($stat`Mysticality`) < 150 || 261 | myBasestat($stat`Muscle`) >= 150, 262 | do: (): void => { 263 | if (myThrall() !== $thrall`Elbow Macaroni`) useSkill($skill`Bind Undead Elbow Macaroni`); 264 | outfit(get("instant_stickKnifeOutfit")); 265 | }, 266 | limit: { tries: 1 }, 267 | }, 268 | { 269 | name: "Test", 270 | prepare: (): void => { 271 | if (!have($item`obsidian nutcracker`)) buy($item`obsidian nutcracker`, 1); 272 | if ( 273 | have($item`Ye Wizard's Shack snack voucher`) && 274 | !acquiredOrExcluded($effect`Pisces in the Skyces`) 275 | ) 276 | retrieveItem($item`tobiko marble soda`); 277 | const usefulEffects: Effect[] = [ 278 | $effect`AAA-Charged`, 279 | $effect`Arched Eyebrow of the Archmage`, 280 | $effect`Carol of the Hells`, 281 | $effect`Concentrated Concentration`, 282 | $effect`Concentration`, 283 | $effect`Cowrruption`, 284 | $effect`Destructive Resolve`, 285 | $effect`Elron's Explosive Etude`, 286 | $effect`Grumpy and Ornery`, 287 | $effect`Imported Strength`, 288 | $effect`Jackasses' Symphony of Destruction`, 289 | $effect`Mental A-cue-ity`, 290 | $effect`Pisces in the Skyces`, 291 | $effect`Song of Sauce`, 292 | $effect`Sigils of Yeg`, 293 | $effect`Spirit of Peppermint`, 294 | $effect`The Magic of LOV`, 295 | $effect`Warlock, Warstock, and Warbarrel`, 296 | $effect`We're All Made of Starfish`, 297 | ]; 298 | tryAcquiringEffects(usefulEffects, true); 299 | handleCustomPulls("instant_spellTestPulls", spellTestMaximizerString); 300 | handleCustomBusks("instant_spellTestBusks"); 301 | 302 | const wines = $items`Sacramento wine, distilled fortified wine`; 303 | while ( 304 | CommunityService.SpellDamage.actualCost() > myAdventures() && 305 | myInebriety() < inebrietyLimit() && 306 | wines.some((booze) => have(booze)) 307 | ) { 308 | tryAcquiringOdeToBooze(); 309 | drink(wines.filter((booze) => have(booze))[0], 1); 310 | } 311 | 312 | if ( 313 | have($skill`Aug. 13th: Left/Off Hander's Day!`) && 314 | !get("instant_saveAugustScepter", false) && 315 | numericModifier(equippedItem($slot`off-hand`), "Spell Damage") + 316 | numericModifier(equippedItem($slot`off-hand`), "Spell Damage Percent") > 317 | 0 && 318 | CommunityService.SpellDamage.actualCost() > 1 319 | ) { 320 | tryAcquiringEffect($effect`Offhand Remarkable`); 321 | } 322 | }, 323 | completed: () => CommunityService.SpellDamage.isDone(), 324 | do: (): void => { 325 | const maxTurns = get("instant_spellTestTurnLimit", 55); 326 | const testTurns = CommunityService.SpellDamage.actualCost(); 327 | if (testTurns > maxTurns) { 328 | print(`Expected to take ${testTurns}, which is more than ${maxTurns}.`, "red"); 329 | print("Either there was a bug, or you are under-prepared for this test", "red"); 330 | print("Manually complete the test if you think this is fine.", "red"); 331 | print( 332 | "You may also increase the turn limit by typing 'set instant_spellTestTurnLimit='", 333 | "red", 334 | ); 335 | } 336 | CommunityService.SpellDamage.run( 337 | () => logTestSetup(CommunityService.SpellDamage), 338 | maxTurns, 339 | ); 340 | }, 341 | outfit: { modifier: spellTestMaximizerString }, 342 | post: (): void => { 343 | if (have($skill`Spirit of Nothing`)) useSkill($skill`Spirit of Nothing`); 344 | if (have($familiar`Left-Hand Man`)) equip($familiar`Left-Hand Man`, $item.none); 345 | }, 346 | limit: { tries: 1 }, 347 | }, 348 | ], 349 | }; 350 | -------------------------------------------------------------------------------- /src/resources.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Effect, 3 | Familiar, 4 | print, 5 | printHtml, 6 | toEffect, 7 | toFamiliar, 8 | toInt, 9 | totalFreeRests, 10 | } from "kolmafia"; 11 | import { $effect, $effects, $skill, get, have, set } from "libram"; 12 | 13 | class Resource { 14 | pref: string; 15 | help: string | ((s?: string | number) => string); 16 | effects: Effect[]; 17 | default_value: number; 18 | 19 | constructor( 20 | pref: string, 21 | help: string | ((s?: string | number) => string), 22 | effects?: Effect[], 23 | default_value?: number, 24 | ) { 25 | this.pref = pref; 26 | this.help = help; 27 | const prefIsNumber = !isNaN(Number(get(pref, ""))) && get(pref, "").length > 0; 28 | this.effects = (!prefIsNumber ? get(pref, false) : true) && effects ? effects : []; 29 | this.default_value = default_value === undefined ? 1 : default_value; 30 | if (default_value !== undefined && !prefIsNumber) set(pref, default_value); 31 | } 32 | } 33 | 34 | const consumptionResources: Resource[] = [ 35 | new Resource( 36 | "instant_skipDistilledFortifiedWine", 37 | "Do not grab the DFW lucky adventure (if you have numberology)", 38 | ), 39 | new Resource( 40 | "instant_saveAstralPilsners", 41 | (n) => `Save ${n}/6 astral pilsners (set a number)`, 42 | [], 43 | get("instant_saveAstralPilsners", false) ? 6 : 0, 44 | ), 45 | new Resource("instant_saveEuclideanAngle", "Do not pull a non-Euclidean Angle"), 46 | new Resource("instant_saveAbstraction", "Do not pull an Abstraction: Category"), 47 | new Resource("instant_savePerfectFreeze", "Do not craft and drink a perfect drink"), 48 | new Resource( 49 | "instant_saveHoneyBun", 50 | "Do not eat a Honey Bun of Boris for the stats test", 51 | $effects`Motherly Loved`, 52 | ), 53 | new Resource( 54 | "instant_saveRoastedVegetableStats", 55 | "Do not eat a Roasted Vegetable of Jarlsberg for the stats test", 56 | $effects`Wizard Sight`, 57 | ), 58 | new Resource( 59 | "instant_saveRichRicotta", 60 | "Do not eat a Pete's Rich Ricotta for the stats test", 61 | $effects`Rippin' Ricotta`, 62 | ), 63 | new Resource("instant_saveWileyWheyBar", "Do not eat a Pete's Wiley Whey Bar for the stats test"), 64 | new Resource( 65 | "instant_saveRicottaCasserole", 66 | "Do not eat a Bake Veggie Ricotta Casserole for the stats test", 67 | ), 68 | new Resource( 69 | "instant_savePlainCalzone", 70 | "Do not eat a Plain Calzone", 71 | $effects`Angering Pizza Purists`, 72 | ), 73 | new Resource("instant_saveBeesKnees", "Do not buy and drink Bee's Knees"), 74 | new Resource("instant_saveSockdollager", "Do not buy and drink a sockdollager"), 75 | new Resource("instant_saveBorisBeer", "Do not drink a Boris's Beer for the hot test"), 76 | new Resource( 77 | "instant_saveRoastedVegetableItem", 78 | "Do not eat a Roasted Vegetable of Jarlsberg for the item test", 79 | ), 80 | new Resource("instant_saveSacramentoWine", "Do not drink a Sacramento Wine for the item test"), 81 | new Resource( 82 | "instant_savePillkeeper", 83 | "Do not acquire Hulkien, Rainbowolin and Fidoxene", 84 | $effects`Hulkien, Rainbowolin, Fidoxene`, 85 | ), 86 | new Resource("instant_skipSynthExp", "Do not use synth for the Xp% buff"), 87 | new Resource("instant_skipSynthCold", "Do not use synth for the Cold Res buff"), 88 | new Resource( 89 | "instant_saveBodySpradium", 90 | "Do not chew the body spradium if we have it", 91 | $effects`Boxing Day Glow`, 92 | ), 93 | new Resource( 94 | "instant_skipCabernetSauvignon", 95 | "Do not summon and drink a bottle of Cabernet Sauvignon", 96 | $effects`Cabernet Hunter`, 97 | ), 98 | ]; 99 | 100 | const encounterResources: Resource[] = [ 101 | new Resource( 102 | "instant_saveWitchess", 103 | "Do not fight witchess monsters nor acquire Puzzle Champ", 104 | $effects`Puzzle Champ`, 105 | ), 106 | new Resource( 107 | "instant_saveBackups", 108 | (n) => `Save ${n}/11 backups (set a number)`, 109 | [], 110 | get("instant_saveBackups", false) ? 11 : 0, 111 | ), 112 | new Resource( 113 | "instant_skipEarlyTrainsetMeat", 114 | "Do not spend an adventure in the Dire Warren pre-coil grabbing meat from the trainset", 115 | ), 116 | new Resource("instant_saveLocketRedSkeleton", "Do not reminisce a Red Skeleton"), 117 | new Resource("instant_saveLocketWitchessKing", "Do not reminisce a Witchess King"), 118 | new Resource("instant_saveLocketFactoryWorker", "Do not reminisce a Factory Worker (female)"), 119 | new Resource( 120 | "instant_skipMappingNinja", 121 | "Do not attempt to grab a li'l ninja costume for your tot", 122 | ), 123 | new Resource( 124 | "instant_saveSBForInnerElf", 125 | (n) => `Save ${n}/3 Snokebombs for Inner Elf`, 126 | [], 127 | get("instant_saveSBForInnerElf", false) ? 2 : 0, 128 | ), 129 | new Resource( 130 | "instant_skipBishopsForRoyalty", 131 | "Save 3 Witchess fights for the Queen, King and Witch", 132 | ), 133 | new Resource("instant_skipCyclopsEyedrops", "Do not spend a clover on Cyclops Eyedrops"), 134 | new Resource( 135 | "instant_saveCyberRealmFights", 136 | (n) => 137 | `Save ${n}/${have($skill`OVERCLOCK(10)`) ? 10 : 0} CyberRealm free fights (set a number)`, 138 | [], 139 | get("instant_saveCyberRealmFights", false) ? 10 : 0, 140 | ), 141 | ]; 142 | 143 | const farmingResources: Resource[] = [ 144 | new Resource("instant_savePorquoise", "Do not autosell your porquoise"), 145 | new Resource("instant_saveFloundry", "Do not create a codpiece"), 146 | new Resource("instant_saveFortuneTeller", "Do not consult Zatara for buffs", [ 147 | $effect`A Girl Named Sue`, 148 | $effect`There's No N in Love`, 149 | $effect`Meet the Meat`, 150 | $effect`Gunther Than Thou`, 151 | $effect`Everybody Calls Him Gorgon`, 152 | $effect`They Call Him Shifty Because...`, 153 | ]), 154 | new Resource( 155 | "instant_saveSnackVoucher", 156 | "Do not use your snack voucher", 157 | $effects`Wasabi With You, Pisces in the Skyces`, 158 | ), 159 | new Resource("instant_saveClipArt", "Only summon borrowed time"), 160 | new Resource("instant_saveDeck", "Do not use any deck summons"), 161 | new Resource("instant_saveBarrelShrine", "Do not get the barrel shrine buff", [ 162 | $effect`Barrel Chested`, 163 | $effect`Pork Barrel`, 164 | $effect`Warlock, Warstock, and Warbarrel`, 165 | $effect`Beer Barrel Polka`, 166 | ]), 167 | new Resource( 168 | "instant_saveTerminal", 169 | "Do not acquire items.enh and substats.enh", 170 | $effects`items.enh, substats.enh`, 171 | ), 172 | new Resource( 173 | "instant_saveCopDollars", 174 | "Do not acquire shoe gum with cop dollars", 175 | $effects`Gummed Shoes`, 176 | ), 177 | new Resource("instant_saveLeafFights", "Do not use burning leaf free-fights to charge camel"), 178 | new Resource("instant_saveKGBClicks", "Do not use any KGB clicks"), 179 | new Resource("instant_saveGenie", "Do not use any genie wishes"), 180 | new Resource("instant_saveMonkeysPaw", "Do not use any monkey's paw wishes"), 181 | new Resource("instant_savePantogram", "Do not use your pantogram"), 182 | new Resource("instant_saveMummingTrunk", "Do not use your mumming trunk"), 183 | new Resource( 184 | "instant_savePowerfulGlove", 185 | "Do not acquire Triple-Sized and Invisible Avatar", 186 | $effects`Triple-Sized, Invisible Avatar`, 187 | ), 188 | new Resource("instant_saveCargoShorts", "Do not use a pull from your Cargo Shorts"), 189 | new Resource("instant_savePowerSeed", "Do not use any batteries", $effects`AAA-Charged`), 190 | new Resource("instant_saveMayday", "Do not use your Mayday survival package"), 191 | new Resource("instant_savePumpkins", "Do not use harvested pumpkins"), 192 | new Resource("instant_saveSugar", "Do not spend tome uses on sugar shorts/chapeau/shank"), 193 | new Resource("instant_saveGarden", "Do not harvest your garden"), 194 | new Resource("instant_saveMoonTune", "Do not tune the moon for familiar weight test"), 195 | new Resource("instant_saveCinch", "Do not spend any cinch for leveling"), 196 | new Resource( 197 | "instant_saveFreeRests", 198 | (n) => `Save ${n}/${totalFreeRests()} free rests (set a number)`, 199 | [], 200 | get("instant_saveFreeRests", false) ? totalFreeRests() : 0, 201 | ), 202 | new Resource( 203 | "instant_saveCatalogCredits", 204 | (n) => `Save ${n}/3 Mr. Store Catalog Credits (set a number)`, 205 | [], 206 | get("instant_saveCatalogCredits", false) ? 3 : 0, 207 | ), 208 | new Resource( 209 | "instant_skipHighHeels", 210 | "Do not grab red-soled high heels from the Mr. Store Catalog", 211 | ), 212 | new Resource("instant_skipMeatButler", "Do not grab the meat butler from the Mr. Store Catalog"), 213 | new Resource( 214 | "instant_saveNumberology", 215 | (n) => `Save ${n}/${get("skillLevel144") > 3 ? 3 : get("skillLevel144")} Numberology casts`, 216 | [], 217 | get("instant_saveNumberology", false) ? 3 : 0, 218 | ), 219 | new Resource( 220 | "instant_saveFavoriteBird", 221 | "Do not use Visit your Favorite Bird on any of the tests", 222 | ), 223 | new Resource( 224 | "instant_saveAugustScepter", 225 | "Do not use any August Scepter skills", 226 | $effects`Incredibly Well Lit, Offhand Remarkable`, 227 | ), 228 | new Resource( 229 | "instant_saveMonsterHabitats", 230 | (n) => `Save ${n}/3 Recall Facts: Monster Habitats! casts`, 231 | [], 232 | get("instant_saveMonsterHabitats", false) ? 3 : 0, 233 | ), 234 | new Resource("instant_saveMimicEggs", "Do not acquire any Chest Mimic eggs"), 235 | new Resource("instant_saveAprilingBandQuadTom", "Do not acquire the Apriling Band Quad Tom"), 236 | new Resource("instant_saveAprilingBandSaxophone", "Do not acquire the Apriling Band Saxophone"), 237 | new Resource("instant_saveAprilingBandStaff", "Do not acquire the Apriling Band Staff"), 238 | new Resource("instant_saveAprilingBandPiccolo", "Do not acquire the Apriling Band Piccolo"), 239 | new Resource("instant_saveEmbers", "Do not use any Sept-Ember Censer Embers"), 240 | new Resource( 241 | "instant_skipBembershootForJacket", 242 | "Acquire 2 bembershoots and 1 jacket instead of 3 bembershoots", 243 | ), 244 | new Resource("instant_savePhotoboothProps", "Do not acquire photobooth props"), 245 | new Resource("instant_saveStillsuit", "Do not drink stillsuit distillate for the item test"), 246 | new Resource( 247 | "instant_saveTeaTree", 248 | "Do not use any teas from the Potted Tea Tree", 249 | $effects`Frost Tea, Loyal Tea, Obscuri Tea, Serendipi Tea, Toast Tea`, 250 | ), 251 | new Resource( 252 | "instant_saveAlliedRadio", 253 | (n) => `Save ${n}/3 Allied Radio Supply Drop Requests`, 254 | [], 255 | get("instant_saveAlliedRadio", false) ? 3 : 0, 256 | ), 257 | // eslint-disable-next-line libram/verify-constants 258 | new Resource("instant_saveBCZBloodBath", "Do not use BCZ: Blood Bath", $effects`Bloodbathed`), 259 | new Resource( 260 | "instant_saveBCZDialitup", 261 | "Do not use BCZ: Dial it up to 11 for the spell damage test", 262 | // eslint-disable-next-line libram/verify-constants 263 | $effects`Up to 11`, 264 | ), 265 | ]; 266 | 267 | const otherResources: Resource[] = [ 268 | new Resource( 269 | "instant_skipGovernment", 270 | "Do not attempt to unlock the beach with meat to grab an anticheese", 271 | ), 272 | new Resource( 273 | "instant_skipAutomaticOptimizations", 274 | "Do not conduct automatic optimization of the route", 275 | ), 276 | new Resource("instant_saveCandySword", "Do not use Candy Cane Sword Cane's Stab and Slash"), 277 | new Resource("instant_saveMayamCalendar", "Do not Consider the Calendar"), 278 | new Resource("instant_skipPatrioticScreech", "Do not use Patriotic Screech to banish constructs"), 279 | new Resource("instant_skipLeprecondo", "Do not (re)configure Leprecondo"), 280 | new Resource( 281 | "instant_saveShowerGlobs", 282 | (n) => `Save at least ${n} globs of wet paper`, 283 | [], 284 | get("instant_saveShowerGlobs", false) ? 4 : 0, 285 | ), 286 | new Resource( 287 | "instant_prioritizeParkaSpit", 288 | "Prefer Spitting Jurassic Acid (with 100 turn cd) over other turn-taking YRs (with 75 turn cd)", 289 | ), 290 | ]; 291 | 292 | const allResources = [ 293 | ...consumptionResources, 294 | ...encounterResources, 295 | ...farmingResources, 296 | ...otherResources, 297 | ]; 298 | 299 | const automaticallyExcludedBuffs = Array.from( 300 | allResources.map((resource) => resource.effects).filter((efs) => efs.length > 0), 301 | ).reduce((acc, val) => acc.concat(val), []); 302 | const manuallyExcludedBuffs = get("instant_explicitlyExcludedBuffs", "") 303 | .split(",") 304 | .filter((s) => s.length > 0) 305 | .map((s) => toEffect(s)); 306 | export const forbiddenEffects = [ 307 | ...automaticallyExcludedBuffs, 308 | ...manuallyExcludedBuffs.filter((ef) => !automaticallyExcludedBuffs.includes(ef)), 309 | ]; 310 | export const excludedFamiliars = get("instant_explicitlyExcludedFamiliars") 311 | .split(",") 312 | .map((i) => toFamiliar(toInt(i))); 313 | 314 | function printResources(resources: Resource[]) { 315 | resources 316 | .sort((a, b) => (a.pref < b.pref ? -1 : 1)) 317 | .forEach((resource) => { 318 | const prefValue = get(resource.pref, ""); 319 | const prefOn = 320 | get(resource.pref, false) || 321 | (!isNaN(Number(get(resource.pref, ""))) && Number(get(resource.pref, "")) > 0); 322 | const symbol = prefOn ? "✓" : "X"; 323 | const color = prefOn ? "black" : "#888888"; 324 | print( 325 | `${symbol} ${resource.pref} - ${ 326 | typeof resource.help === "string" ? resource.help : resource.help(prefValue) 327 | }`, 328 | color, 329 | ); 330 | }); 331 | print(); 332 | } 333 | 334 | export function checkResources(): void { 335 | printHtml( 336 | "Legend (prefname - helptext): ✓ Saved Resource / X Usable Resource", 337 | ); 338 | print("--- Resources pertaining to consumption / organs ---", "blue"); 339 | printResources(consumptionResources); 340 | print("--- Resources pertaining to combat / turn-taking encounters ---", "blue"); 341 | printResources(encounterResources); 342 | print("--- Resources pertaining to aftercore farming profits ---", "blue"); 343 | printResources(farmingResources); 344 | print("--- Other resources ---", "blue"); 345 | printResources(otherResources); 346 | 347 | print("The following are all the buffs we will not acquire in run:"); 348 | forbiddenEffects.forEach((ef) => print(`- ${ef.name}`)); 349 | print("The following are all the familliars we will not use during leveling:"); 350 | Familiar.all() 351 | .filter((fam) => excludedFamiliars.includes(fam)) 352 | .forEach((fam) => print(`- ${fam}`)); 353 | print(); 354 | print("Type 'set =' in the CLI to set your preferences"); 355 | print( 356 | "Type 'set instant_explicitlyExcludedBuffs=,...,' to exclude getting specific effects", 357 | ); 358 | print("(e.g. 'set instant_explicitlyExcludedBuffs=2106' to exclude substats.enh (id = 2106)"); 359 | print("without excluding acquiring items.enh from the Source Terminal)"); 360 | print( 361 | "Type 'set instant_explicitlyExcludedFamiliars=,...,' to exclude using specific familiars during leveling", 362 | ); 363 | print("Type 'ash remove_property(\"\")' to delete a preference"); 364 | } 365 | -------------------------------------------------------------------------------- /RECOMMENDATIONS.md: -------------------------------------------------------------------------------- 1 | ### Useful IotMs 2 | 3 | If you're looking to modify the script yourself, you may also consider these useful IotMs. Any IotM that grants access to a scaler zone helps save a pull on the daypass, which may then be used on other useful pulls (e.g. [non-Euclidean angle](https://wiki.kingdomofloathing.com/Non-Euclidean_angle) or [SPF 451 lip balm](https://wiki.kingdomofloathing.com/SPF_451_lip_balm)).
4 | Once you start getting more and more [free fight sources](https://wiki.kingdomofloathing.com/Free_Fights), the less reliant you will be on the [trainset](https://wiki.kingdomofloathing.com/Model_train_set) and [bowling ball](https://wiki.kingdomofloathing.com/Cosmic_bowling_ball) for xp (they may be swapped out). Shinies that reduce turncount on the tests will then reduce the reliance on the [cookbookbat](https://wiki.kingdomofloathing.com/Cookbookbat) for turngen.
5 | Note that the various IotMs may have turnsaves in the same area, so having access to a bunch of them would result in fewer turns saved than the naive sum of the estimates listed here.
6 | 7 | | IotM | Use | Estimated turns saved | 8 | | ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | --------------------- | 9 | | [Neverending Party invitation envelope](https://wiki.kingdomofloathing.com/Neverending_Party_invitation_envelope) | scalers and free fights | 25 | 10 | | [heart-shaped crate](http://kol.coldfront.net/thekolwiki/index.php/heart-shaped%20crate) | free fights, wdmg, sdmg, xp, fam wt and turngen | 16 | 11 | | [January's Garbage Tote](https://wiki.kingdomofloathing.com/January%27s_Garbage_Tote) | xp | 19\* | 12 | | [mint-in-box Powerful Glove](https://wiki.kingdomofloathing.com/Mint-in-box_Powerful_Glove) | stat%, wdmg, sdmg and NC | 18\* | 13 | | [Backup camera](https://wiki.kingdomofloathing.com/Backup_camera) | free fights | 11 | 14 | | [airplane charter: Spring Break Beach](https://wiki.kingdomofloathing.com/Airplane_charter:_Spring_Break_Beach) | scalers | 12 | 15 | | [airplane charter: Conspiracy Island](https://wiki.kingdomofloathing.com/Airplane_charter:_Conspiracy_Island) | scalers | 12 | 16 | | [airplane charter: Dinseylandfill](https://wiki.kingdomofloathing.com/Airplane_charter:_Dinseylandfill) | scalers | 12 | 17 | | [airplane charter: That 70s Volcano](https://wiki.kingdomofloathing.com/Airplane_charter:_That_70s_Volcano) | scalers | 12 | 18 | | [airplane charter: The Glaciest](https://wiki.kingdomofloathing.com/Airplane_charter:_The_Glaciest) | scalers | 12 | 19 | | [emotion chip](https://wiki.kingdomofloathing.com/Emotion_chip) | item, NC and hot res | 9 | 20 | | [Distant Woods Getaway Brochure](https://wiki.kingdomofloathing.com/Distant_Woods_Getaway_Brochure) | xp | 9 | 21 | | [power seed](https://wiki.kingdomofloathing.com/Power_seed) | free kill, item and sdmg | 7 | 22 | | [designer sweatpants (new old stock)](https://wiki.kingdomofloathing.com/Designer_sweatpants) | hot res | 6 | 23 | | [Fourth of May Cosplay Saber kit](https://wiki.kingdomofloathing.com/Fourth_of_May_Cosplay_Saber_kit) | hot res and free YR | 6\*\* | 24 | 25 | ![image](https://user-images.githubusercontent.com/98746573/222919119-1d34363c-e712-4413-8ff2-bbc9b6f1aace.png)
26 | Note that the base route grabs [cookbookbat](https://wiki.kingdomofloathing.com/Cookbookbat) ingredients 6 times while the other routes only grab it 5 times (which results in one less epic food turngen from the [Pete's wiley whey bar](https://wiki.kingdomofloathing.com/Pete%27s_wiley_whey_bar))
27 | \*Enables alternative routing that potentially saves another ~11 turns.
28 | \*\*The [Fourth of May Cosplay Saber](https://wiki.kingdomofloathing.com/Fourth_of_May_Cosplay_Saber) combos really well with the [industrial fire extinguisher](https://wiki.kingdomofloathing.com/Industrial_fire_extinguisher), saving an additional ~35 turns (from [Fireproof Foam Suit](https://wiki.kingdomofloathing.com/Fireproof_Foam_Suit)).
29 | It also enables the [Pocket Meteor Guide](https://wiki.kingdomofloathing.com/Pocket_Meteor_Guide) in saving an additional ~16 turns on the wdmg, sdmg and famwt tests.
30 | Both benefit from an additional +4 turns saved in enabling [cowrruption](https://wiki.kingdomofloathing.com/Cowrruption) to be carried over to the sdmg test.
31 | This is not meant to be an exhaustive list, and the turns saved are only relevant to this particular route. There are many other strong IotMs, including [corked genie bottle](https://wiki.kingdomofloathing.com/Corked_genie_bottle), [Pack of Every Card](https://wiki.kingdomofloathing.com/Pack_of_Every_Card), [Source terminal](https://wiki.kingdomofloathing.com/Source_terminal), [Witchess Set](https://wiki.kingdomofloathing.com/Witchess_Set) and [suspicious package](https://wiki.kingdomofloathing.com/Suspicious_package) just to name a few.
32 | 33 | ### Highly-Recommended Resources 34 | 35 | Merely satisfying the necessary requirements likely leaves you miles away from being able to complete a one-day run. Because we do not rely heavily on too many IotMs to carry us through the SCCS run, you are highly recommended to have the following bunch of skills and familiars to enable this run. The small +stat% buffs matter a lot for the offstat tests, and each of these resources will save 1 or more turns in your run.
36 | 37 | If you're missing any of the skills, do take a look at the list of supported IotMs to see if said IotMs can make up for the deficiencies.
38 | 39 | | Familiars | Use | 40 | | -------------------------------------------------------------------- | -------- | 41 | | [Disgeist]() | NC test | 42 | | [Exotic Parrot](https://wiki.kingdomofloathing.com/Exotic_Parrot) | hot test | 43 | 44 | | Skills | Use | 45 | | --------------------------------------------------------------------------------------------------------------------- | ----------------------- | 46 | | [Inscrutable Gaze]() | xp% | 47 | | [Song of Bravado]() | stat% | 48 | | [Get Big](https://wiki.kingdomofloathing.com/Get_Big) | stat% | 49 | | [Stevedave's Shanty of Superiority]() | stat% | 50 | | [The Ode to Booze](https://wiki.kingdomofloathing.com/The_Ode_to_Booze) | turngen | 51 | | [Pizza Lover](https://wiki.kingdomofloathing.com/Pizza_Lover) | turngen + xp | 52 | | [Empathy of the Newt](https://wiki.kingdomofloathing.com/Empathy_of_the_Newt) | fam wt | 53 | | [Leash of Linguini]() | fam wt | 54 | | [Amphibian Sympathy](https://wiki.kingdomofloathing.com/Amphibian_Sympathy) | fam wt | 55 | | [The Sonata of Sneakiness]() | non-combat | 56 | | [Smooth Movement](https://wiki.kingdomofloathing.com/Smooth_Movement) | non-combat | 57 | | [Asbestos Heart](https://wiki.kingdomofloathing.com/Asbestos_Heart) | hot resist | 58 | | [Elemental Saucesphere]() | hot resist | 59 | | [Tolerance of the Kitchen](https://wiki.kingdomofloathing.com/Tolerance_of_the_Kitchen) | hot resist | 60 | | [Astral Shell]() | hot resist | 61 | | [Crimbo Training: Coal Taster](https://wiki.kingdomofloathing.com/Crimbo_Training:_Coal_Taster) | hot resist | 62 | | [Bow-Legged Swagger]() | doubles wdmg | 63 | | [Steely-Eyed Squint]() | doubles item drop | 64 | | [Shattering Punch](https://wiki.kingdomofloathing.com/Shattering_Punch) | free kill | 65 | | [Gingerbread Mob Hit](https://wiki.kingdomofloathing.com/Gingerbread_Mob_Hit) | free kill | 66 | | [Snokebomb](https://wiki.kingdomofloathing.com/Snokebomb) | free banish | 67 | | [Saucegeyser](https://wiki.kingdomofloathing.com/Saucegeyser) | attacking spell | 68 | | [Advanced Saucecrafting](https://wiki.kingdomofloathing.com/Advanced_Saucecrafting) | saucecrafting | 69 | | [The Way of Sauce](https://wiki.kingdomofloathing.com/The_Way_of_Sauce) | saucecrafting | 70 | | [Impetuous Sauciness](https://wiki.kingdomofloathing.com/Impetuous_Sauciness) | saucecrafting | 71 | | [Expert Corner-Cutter](https://wiki.kingdomofloathing.com/Expert_Corner-Cutter) | saucecrafting | 72 | | [Prevent Scurvy and Sobriety](https://wiki.kingdomofloathing.com/Prevent_Scurvy_and_Sobriety) | saucecrafting + turngen | 73 | | [Perfect Freeze](https://wiki.kingdomofloathing.com/Perfect_Freeze) | turngen | 74 | | [Drinking to Drink](https://wiki.kingdomofloathing.com/Drinking_to_Drink) | turngen | 75 | | [Cannelloni Cocoon](https://wiki.kingdomofloathing.com/Cannelloni_Cocoon) | hp regen | 76 | | [Inner Sauce](https://wiki.kingdomofloathing.com/Inner_Sauce) | mp regen | 77 | | [Soul Saucery](https://wiki.kingdomofloathing.com/Soul_Saucery) | mp regen | 78 | | [Curse of Weaksauce](https://wiki.kingdomofloathing.com/Curse_of_Weaksauce) | mp regen | 79 | | [Double-Fisted Skull Smashing](https://wiki.kingdomofloathing.com/Double-Fisted_Skull_Smashing) | stat test | 80 | | [Carol of the Bulls]() | wdmg test | 81 | | [Carol of the Hells]() | sdmg test | 82 | | [Song of Sauce]() | sdmg test | 83 | | [Song of the North]() | wdmg test | 84 | | [Simmer](https://wiki.kingdomofloathing.com/Simmer) | sdmg test | 85 | | [Always Never Not Guzzling](https://wiki.kingdomofloathing.com/Always_Never_Not_Guzzling) | item test | 86 | | [Crimbo Training: Bartender](https://wiki.kingdomofloathing.com/Crimbo_Training:_Bartender) | item test | 87 | 88 | ### Other Recommendations 89 | 90 | You are also strongly recommended to have the following resources. However, unlike the resources above, none of them provide much turngen/turn savings individually (although together they do make up quite a bit), and these may also be easily replaced with more shinies.
91 | 92 | | Skills | Use | 93 | | ----------------------------------------------------------------------------------------------------------------------- | ---------- | 94 | | [Pride of the Puffin]() | ML | 95 | | [Drescher's Annoying Noise]() | ML | 96 | | [Ur-Kel's Aria of Annoyance]() | ML | 97 | | [Master Saucier](https://wiki.kingdomofloathing.com/Master_Saucier) | sdmg test | 98 | | [Subtle and Quick to Anger](https://wiki.kingdomofloathing.com/Subtle_and_Quick_to_Anger) | sdmg test | 99 | | [Fat Leon's Phat Loot Lyric]() | item test | 100 | | [Mad Looting Skillz](https://wiki.kingdomofloathing.com/Mad_Looting_Skillz) | item test | 101 | | [Object Quasi-Permanence](https://wiki.kingdomofloathing.com/Object_Quasi-Permanence) | item test | 102 | | [Powers of Observatiogn](https://wiki.kingdomofloathing.com/Powers_of_Observatiogn) | item test | 103 | | [Singer's Faithful Ocelot]() | item test | 104 | | [Thief Among the Honorable](https://wiki.kingdomofloathing.com/Thief_Among_the_Honorable) | item test | 105 | | [Natural Born Scrabbler](https://wiki.kingdomofloathing.com/Natural_Born_Scrabbler) | item test | 106 | | [Jackasses' Symphony of Destruction]() | wdmg test | 107 | | [Scowl of the Auk]() | wdmg test | 108 | | [Rage of the Reindeer]() | wdmg test | 109 | | [Tenacity of the Snapper]() | wdmg test | 110 | | [Claws of the Walrus](https://wiki.kingdomofloathing.com/Claws_of_the_Walrus) | wdmg test | 111 | | [Blessing of the War Snapper]() | wdmg test | 112 | | [Evoke Eldritch Horror](https://wiki.kingdomofloathing.com/Evoke_Eldritch_Horror) | free fight | 113 | 114 | | Useful stuff | Use | 115 | | -------------------------------------------------------------------------- | ----- | 116 | | Fully Upgraded [Telescope](https://wiki.kingdomofloathing.com/A_Telescope) | stat% | 117 | -------------------------------------------------------------------------------- /src/tasks/boozedrop.ts: -------------------------------------------------------------------------------- 1 | import { Quest } from "../engine/task"; 2 | import { 3 | adv1, 4 | alliedRadio, 5 | autosell, 6 | buy, 7 | cliExecute, 8 | create, 9 | drink, 10 | eat, 11 | Effect, 12 | equip, 13 | faxbot, 14 | getCampground, 15 | getWorkshed, 16 | hermit, 17 | inebrietyLimit, 18 | inMuscleSign, 19 | itemAmount, 20 | myInebriety, 21 | myMaxhp, 22 | myMeat, 23 | print, 24 | restoreHp, 25 | retrieveItem, 26 | use, 27 | useFamiliar, 28 | useSkill, 29 | visitUrl, 30 | } from "kolmafia"; 31 | import { 32 | $coinmaster, 33 | $effect, 34 | $effects, 35 | $familiar, 36 | $item, 37 | $location, 38 | $monster, 39 | $skill, 40 | $slot, 41 | AprilingBandHelmet, 42 | clamp, 43 | Clan, 44 | CommunityService, 45 | DaylightShavings, 46 | get, 47 | have, 48 | StillSuit, 49 | TrainSet, 50 | uneffect, 51 | unequip, 52 | withChoice, 53 | withProperty, 54 | } from "libram"; 55 | import { 56 | canConfigure, 57 | Cycle, 58 | setConfiguration, 59 | Station, 60 | } from "libram/dist/resources/2022/TrainSet"; 61 | import { 62 | acquiredOrExcluded, 63 | attemptRestoringMpWithFreeRests, 64 | handleCustomBusks, 65 | handleCustomPulls, 66 | haveAndNotExcluded, 67 | logTestSetup, 68 | tryAcquiringEffects, 69 | tryAcquiringOdeToBooze, 70 | wishFor, 71 | } from "../lib"; 72 | import { sugarItemsAboutToBreak } from "../outfit"; 73 | import { CombatStrategy } from "grimoire-kolmafia"; 74 | import Macro, { haveFreeBanish } from "../combat"; 75 | import { chooseFamiliar } from "../familiars"; 76 | 77 | const boozeTestMaximizerString = 78 | "1 Item Drop, 2 Booze Drop, -equip broken champagne bottle, switch disembodied hand, -switch left-hand man"; 79 | 80 | export const BoozeDropQuest: Quest = { 81 | name: "Booze Drop", 82 | completed: () => CommunityService.BoozeDrop.isDone(), 83 | tasks: [ 84 | { 85 | name: "Carol Ghost Buff", 86 | prepare: (): void => { 87 | restoreHp(clamp(1000, myMaxhp() / 2, myMaxhp())); 88 | attemptRestoringMpWithFreeRests(50); 89 | }, 90 | completed: () => 91 | !have($familiar`Ghost of Crimbo Carols`) || 92 | !haveFreeBanish() || 93 | acquiredOrExcluded($effect`All I Want For Crimbo Is Stuff`) || 94 | $effects`Do You Crush What I Crush?, Holiday Yoked, Let It Snow/Boil/Stink/Frighten/Grease, All I Want For Crimbo Is Stuff, Crimbo Wrapping`.some( 95 | (ef) => have(ef), 96 | ), 97 | do: $location`The Dire Warren`, 98 | combat: new CombatStrategy().macro(Macro.banish().abort()), 99 | outfit: { 100 | offhand: $item`latte lovers member's mug`, 101 | acc1: $item`Kremlin's Greatest Briefcase`, 102 | acc2: $item`Lil' Doctor™ bag`, 103 | familiar: $familiar`Ghost of Crimbo Carols`, 104 | famequip: $item.none, 105 | }, 106 | limit: { tries: 1 }, 107 | }, 108 | { 109 | name: "Configure Trainset", 110 | completed: () => 111 | (getWorkshed() === $item`model train set` && !canConfigure()) || !TrainSet.have(), 112 | do: (): void => { 113 | const offset = get("trainsetPosition") % 8; 114 | const newStations: TrainSet.Station[] = []; 115 | const stations = [ 116 | Station.COAL_HOPPER, // double hot resist 117 | Station.TOWER_FROZEN, // hot resist 118 | Station.GAIN_MEAT, // meat 119 | Station.TOWER_FIZZY, // mp regen 120 | Station.BRAIN_SILO, // myst stats 121 | Station.VIEWING_PLATFORM, // all stats 122 | Station.WATER_BRIDGE, // +ML 123 | Station.CANDY_FACTORY, // candies 124 | ] as Cycle; 125 | for (let i = 0; i < 8; i++) { 126 | const newPos = (i + offset) % 8; 127 | newStations[newPos] = stations[i]; 128 | } 129 | setConfiguration(newStations as Cycle); 130 | }, 131 | limit: { tries: 1 }, 132 | }, 133 | { 134 | name: "Acquire Clover", 135 | completed: () => 136 | have($item`Apriling band saxophone`) || 137 | have($item`11-leaf clover`) || 138 | get("_cloversPurchased") >= 2 || 139 | acquiredOrExcluded($effect`One Very Clear Eye`) || 140 | get("instant_skipCyclopsEyedrops", false), 141 | do: (): void => { 142 | buy(1, $item`chewing gum on a string`); 143 | use(1, $item`chewing gum on a string`); 144 | hermit($item`11-leaf clover`, 1); 145 | }, 146 | limit: { tries: 50 }, 147 | }, 148 | { 149 | name: "Get Cyclops Eyedrops", 150 | completed: () => 151 | have($item`cyclops eyedrops`) || 152 | acquiredOrExcluded($effect`One Very Clear Eye`) || 153 | get("instant_skipCyclopsEyedrops", false), 154 | do: (): void => { 155 | if (have($item`Apriling band saxophone`) && !have($effect`Lucky!`)) 156 | AprilingBandHelmet.play($item`Apriling band saxophone`); 157 | if (!have($effect`Lucky!`)) use($item`11-leaf clover`); 158 | if (!have($item`cyclops eyedrops`)) adv1($location`The Limerick Dungeon`, -1); 159 | }, 160 | limit: { tries: 1 }, 161 | }, 162 | { 163 | name: "Acquire Government", 164 | completed: () => 165 | !have($item`government cheese`) || 166 | get("lastAnticheeseDay") > 0 || 167 | acquiredOrExcluded($effect`I See Everything Thrice!`) || 168 | get("instant_skipGovernment", false), 169 | do: (): void => { 170 | inMuscleSign() 171 | ? retrieveItem($item`bitchin' meatcar`) 172 | : retrieveItem($item`Desert Bus pass`); 173 | if (!have($item`Desert Bus pass`) && !have($item`bitchin' meatcar`)) { 174 | autosell($item`government cheese`, itemAmount($item`government cheese`)); 175 | return; 176 | } 177 | visitUrl("place.php?whichplace=desertbeach&action=db_nukehouse"); 178 | retrieveItem($item`government`); 179 | }, 180 | limit: { tries: 1 }, 181 | }, 182 | { 183 | name: "Fax Ungulith", 184 | completed: () => get("_photocopyUsed") || have($item`corrupted marrow`), 185 | do: (): void => { 186 | if (have($item`photocopied monster`) && get("photocopyMonster") !== $monster`ungulith`) { 187 | cliExecute("fax send"); 188 | } 189 | 190 | // If we're whitelisted to the CSLooping clan, use that to grab the ungulith instead 191 | if (Clan.getWhitelisted().find((c) => c.name.toLowerCase() === "csloopers unite")) { 192 | Clan.with("CSLoopers Unite", () => cliExecute("fax receive")); 193 | } else { 194 | if (!visitUrl("messages.php?box=Outbox").includes("#3626664")) { 195 | print("Requesting whitelist to CS clan...", "blue"); 196 | cliExecute("csend to 3626664 || Requesting access to CS clan"); 197 | } 198 | cliExecute("chat"); 199 | } 200 | 201 | if ( 202 | (have($item`photocopied monster`) || faxbot($monster`ungulith`)) && 203 | get("photocopyMonster") === $monster`ungulith` 204 | ) { 205 | use($item`photocopied monster`); 206 | } 207 | }, 208 | outfit: () => ({ 209 | hat: 210 | DaylightShavings.nextBuff() === $effect`Musician's Musician's Moustache` && 211 | !DaylightShavings.hasBuff() && 212 | have($item`Daylight Shavings Helmet`) 213 | ? $item`Daylight Shavings Helmet` 214 | : undefined, 215 | back: $item`vampyric cloake`, 216 | weapon: $item`Fourth of May Cosplay Saber`, 217 | offhand: have($skill`Double-Fisted Skull Smashing`) 218 | ? $item`industrial fire extinguisher` 219 | : undefined, 220 | familiar: chooseFamiliar(false), 221 | modifier: "myst", 222 | avoid: sugarItemsAboutToBreak(), 223 | }), 224 | choices: { 1387: 3 }, 225 | combat: new CombatStrategy().macro( 226 | Macro.trySkill($skill`Bowl Straight Up`) 227 | .trySkill($skill`Become a Bat`) 228 | .trySkill($skill`Fire Extinguisher: Polar Vortex`) 229 | .trySkill($skill`Use the Force`) 230 | .default(), 231 | ), 232 | limit: { tries: 5 }, 233 | }, 234 | { 235 | name: "Eat roasted vegetable of Jarlsberg", 236 | completed: () => 237 | acquiredOrExcluded($effect`Wizard Sight`) || 238 | get("instant_saveRoastedVegetableItem", false) || 239 | (!have($item`roasted vegetable of Jarlsberg`) && 240 | itemAmount($item`Vegetable of Jarlsberg`) < 2), 241 | do: (): void => { 242 | if ( 243 | itemAmount($item`Vegetable of Jarlsberg`) >= 2 && 244 | !have($item`roasted vegetable of Jarlsberg`) 245 | ) 246 | create($item`roasted vegetable of Jarlsberg`, 1); 247 | eat($item`roasted vegetable of Jarlsberg`, 1); 248 | }, 249 | limit: { tries: 1 }, 250 | }, 251 | { 252 | name: "Drink Sacramento Wine", 253 | completed: () => 254 | acquiredOrExcluded($effect`Sacré Mental`) || 255 | !have($item`Sacramento wine`) || 256 | myInebriety() >= inebrietyLimit() || 257 | get("instant_saveSacramentoWine", false), 258 | do: (): void => { 259 | if (myInebriety() < inebrietyLimit()) { 260 | tryAcquiringOdeToBooze(); 261 | drink($item`Sacramento wine`, 1); 262 | uneffect($effect`Ode to Booze`); 263 | } 264 | }, 265 | limit: { tries: 1 }, 266 | }, 267 | { 268 | name: "Drink Cabernet Sauvignon", 269 | prepare: (): void => { 270 | if (haveAndNotExcluded($familiar`Left-Hand Man`)) { 271 | useFamiliar($familiar`Left-Hand Man`); 272 | unequip($slot`familiar`); 273 | } 274 | }, 275 | completed: () => 276 | acquiredOrExcluded($effect`Cabernet Hunter`) || 277 | (!have($item`bottle of Cabernet Sauvignon`) && 278 | (!have($skill`Aug. 31st: Cabernet Sauvignon Day!`) || 279 | get("instant_saveAugustScepter", false))) || 280 | myInebriety() + 3 > inebrietyLimit() || 281 | get("instant_skipCabernetSauvignon", false), 282 | do: (): void => { 283 | if (!have($item`bottle of Cabernet Sauvignon`)) 284 | useSkill($skill`Aug. 31st: Cabernet Sauvignon Day!`); 285 | if (myInebriety() + 3 <= inebrietyLimit()) { 286 | tryAcquiringOdeToBooze(); 287 | drink($item`bottle of Cabernet Sauvignon`); 288 | uneffect($effect`Ode to Booze`); 289 | } 290 | }, 291 | limit: { tries: 1 }, 292 | }, 293 | { 294 | name: "Deck Wheel of Fortune", 295 | completed: () => 296 | get("_deckCardsDrawn") > 10 || 297 | acquiredOrExcluded($effect`Fortune of the Wheel`) || 298 | !have($item`Deck of Every Card`) || 299 | get("instant_saveDeck", false), 300 | do: (): void => { 301 | cliExecute("cheat fortune"); 302 | }, 303 | limit: { tries: 1 }, 304 | }, 305 | { 306 | name: "Power Seed", 307 | completed: () => 308 | !have($item`potted power plant`) || 309 | (itemAmount($item`battery (AAA)`) < 5 && !have($item`battery (lantern)`)) || 310 | acquiredOrExcluded($effect`Lantern-Charged`) || 311 | get("instant_savePowerSeed", false), 312 | do: (): void => { 313 | if (itemAmount($item`battery (AAA)`) >= 5) create($item`battery (lantern)`, 1); 314 | use($item`battery (lantern)`, 1); 315 | }, 316 | limit: { tries: 1 }, 317 | }, 318 | { 319 | name: "Pumpkin Juice", 320 | completed: () => 321 | acquiredOrExcluded($effect`Juiced and Jacked`) || 322 | (!have($item`pumpkin`) && !have($item`pumpkin juice`)) || 323 | get("instant_savePumpkins", false), 324 | do: (): void => { 325 | if (!have($item`pumpkin juice`)) create($item`pumpkin juice`, 1); 326 | use($item`pumpkin juice`, 1); 327 | }, 328 | limit: { tries: 1 }, 329 | }, 330 | { 331 | name: "Loathing Idol Microphone", 332 | completed: () => 333 | acquiredOrExcluded($effect`Spitting Rhymes`) || 334 | !have($item`2002 Mr. Store Catalog`) || 335 | get("availableMrStore2002Credits") <= get("instant_saveCatalogCredits", 0), 336 | do: (): void => { 337 | if (!have($item`Loathing Idol Microphone`)) { 338 | buy($coinmaster`Mr. Store 2002`, 1, $item`Loathing Idol Microphone`); 339 | } 340 | withChoice(1505, 3, () => use($item`Loathing Idol Microphone`)); 341 | }, 342 | limit: { tries: 1 }, 343 | }, 344 | { 345 | name: "Favorite Bird (Item)", 346 | completed: () => 347 | !have($skill`Visit your Favorite Bird`) || 348 | acquiredOrExcluded($effect`Blessing of your favorite Bird`) || 349 | get("_favoriteBirdVisited") || 350 | !get("yourFavoriteBirdMods").includes("Item Drops") || 351 | get("instant_saveFavoriteBird", false), 352 | do: () => useSkill($skill`Visit your Favorite Bird`), 353 | limit: { tries: 1 }, 354 | }, 355 | { 356 | name: "Mini Kiwi Icepick", 357 | completed: () => have($item`mini kiwi icepick`) || !have($item`mini kiwi`, 4), 358 | do: () => create($item`mini kiwi icepick`, 1), 359 | limit: { tries: 1 }, 360 | }, 361 | { 362 | name: "Buy Oversized Sparkler", 363 | ready: () => 364 | myMeat() >= 1000 || 365 | (have($item`mini kiwi icepick`) && 366 | !have($skill`Double-Fisted Skull Smashing`) && 367 | !have($familiar`Disembodied Hand`)), 368 | completed: () => have($item`oversized sparkler`), 369 | do: () => buy($item`oversized sparkler`, 1), 370 | limit: { tries: 1 }, 371 | }, 372 | { 373 | name: "Set Apriling Band Helmet (Booze)", 374 | completed: () => 375 | !AprilingBandHelmet.canChangeSong() || 376 | acquiredOrExcluded($effect`Apriling Band Celebration Bop`), 377 | do: () => AprilingBandHelmet.conduct($effect`Apriling Band Celebration Bop`), 378 | limit: { tries: 1 }, 379 | }, 380 | { 381 | name: "Wet Shower Radio", 382 | completed: () => 383 | !have($item`April Shower Thoughts shield`) || 384 | itemAmount($item`glob of wet paper`) - 1 < get("instant_saveShowerGlobs", 0) || 385 | have($item`wet shower radio`), 386 | do: () => { 387 | buy($coinmaster`Using your Shower Thoughts`, 1, $item`wet shower radio`); 388 | }, 389 | limit: { tries: 1 }, 390 | }, 391 | { 392 | name: "Materiel Intel", 393 | completed: () => 394 | !have($item`Allied Radio Backpack`) || 395 | acquiredOrExcluded($effect`Materiel Intel`) || 396 | get("_alliedRadioMaterielIntel", false) || 397 | get("_alliedRadioDropsUsed", 0) >= 3 - get("instant_saveAlliedRadio", 0), 398 | do: () => alliedRadio("materiel intel"), 399 | limit: { tries: 1 }, 400 | }, 401 | { 402 | name: "Serendipi Tea", 403 | completed: () => 404 | acquiredOrExcluded($effect`Serendipi Tea`) || 405 | get("_pottedTeaTreeUsed") || 406 | get("instant_saveTeaTree", false) || 407 | getCampground()["potted tea tree"] === undefined, 408 | do: () => { 409 | cliExecute(`teatree cuppa Serendipi tea`); 410 | use($item`cuppa Serendipi tea`, 1); 411 | }, 412 | limit: { tries: 1 }, 413 | }, 414 | { 415 | name: "Test", 416 | prepare: (): void => { 417 | const usefulEffects: Effect[] = [ 418 | $effect`Beer Barrel Polka`, 419 | $effect`Blessing of the Bird`, 420 | $effect`Crunching Leaves`, 421 | $effect`Fat Leon's Phat Loot Lyric`, 422 | // $effect`Feeling Lost`, 423 | $effect`Fortunate Resolve`, 424 | $effect`Heart of Lavender`, 425 | $effect`I See Everything Thrice!`, 426 | $effect`Incredibly Well Lit`, 427 | $effect`items.enh`, 428 | $effect`Joyful Resolve`, 429 | $effect`Lubricating Sauce`, 430 | $effect`One Very Clear Eye`, 431 | $effect`Pork Barrel`, 432 | $effect`Nearly All-Natural`, 433 | $effect`The Ballad of Richie Thingfinder`, 434 | $effect`The Spirit of Taking`, 435 | $effect`There's No N in Love`, 436 | $effect`Singer's Faithful Ocelot`, 437 | $effect`Steely-Eyed Squint`, 438 | $effect`Uncucumbered`, 439 | ]; 440 | tryAcquiringEffects(usefulEffects, true); 441 | 442 | if ( 443 | haveAndNotExcluded($familiar`Trick-or-Treating Tot`) && 444 | have($item`li'l ninja costume`) 445 | ) { 446 | useFamiliar($familiar`Trick-or-Treating Tot`); 447 | equip($slot`familiar`, $item`li'l ninja costume`); 448 | } 449 | handleCustomPulls("instant_boozeTestPulls", boozeTestMaximizerString); 450 | handleCustomBusks("instant_boozeTestBusks"); 451 | 452 | if ( 453 | CommunityService.BoozeDrop.actualCost() > 1 && 454 | StillSuit.distillateModifier("Item Drop") >= 15 && 455 | !get("instant_saveStillsuit", false) && 456 | myInebriety() + 1 < inebrietyLimit() && 457 | !acquiredOrExcluded($effect`Buzzed on Distillate`) 458 | ) 459 | StillSuit.drinkDistillate(); 460 | 461 | // If it saves us >= 6 turns, try using a wish 462 | if (CommunityService.BoozeDrop.actualCost() >= 7) wishFor($effect`Infernal Thirst`); 463 | }, 464 | completed: () => CommunityService.BoozeDrop.isDone(), 465 | do: (): void => { 466 | const maxTurns = get("instant_boozeTestTurnLimit", 30); 467 | const testTurns = CommunityService.BoozeDrop.actualCost(); 468 | if (testTurns > maxTurns) { 469 | print(`Expected to take ${testTurns}, which is more than ${maxTurns}.`, "red"); 470 | print("Either there was a bug, or you are under-prepared for this test", "red"); 471 | print("Manually complete the test if you think this is fine.", "red"); 472 | print( 473 | "You may also increase the turn limit by typing 'set instant_boozeTestTurnLimit='", 474 | "red", 475 | ); 476 | } 477 | // Temporary fix till CommunityService and MummingTrunk gets fixed in Libram 478 | withProperty("_mummeryMods", "", () => 479 | CommunityService.BoozeDrop.run(() => logTestSetup(CommunityService.BoozeDrop), maxTurns), 480 | ); 481 | }, 482 | outfit: { 483 | modifier: boozeTestMaximizerString, 484 | }, 485 | limit: { tries: 1 }, 486 | }, 487 | ], 488 | }; 489 | -------------------------------------------------------------------------------- /src/sim.ts: -------------------------------------------------------------------------------- 1 | import { Familiar, Item, Monster, print, printHtml, Skill, storageAmount } from "kolmafia"; 2 | import { 3 | $familiar, 4 | $item, 5 | $monster, 6 | $skill, 7 | CombatLoversLocket, 8 | get, 9 | have, 10 | Lifestyle, 11 | permedSkills, 12 | } from "libram"; 13 | import { have as haveTrainSet } from "libram/dist/resources/2022/TrainSet"; 14 | 15 | class Hardcoded { 16 | have: boolean; 17 | name: string; 18 | 19 | constructor(have: boolean, name: string) { 20 | this.have = have; 21 | this.name = name; 22 | } 23 | } 24 | 25 | type Thing = Item | Familiar | Skill | Monster | Hardcoded; 26 | interface Requirement { 27 | thing: Thing | Thing[]; 28 | why: string; 29 | optional?: boolean; 30 | recommended?: boolean; 31 | } 32 | 33 | /** 34 | * Return: a list of all things required to run the script. 35 | */ 36 | function buildIotmList(): Requirement[] { 37 | return [ 38 | { 39 | thing: $item`Clan VIP Lounge key`, 40 | why: "Many test improvements", 41 | }, 42 | { 43 | thing: new Hardcoded(haveTrainSet(), "Model train set"), 44 | why: "Leveling", 45 | }, 46 | { 47 | thing: new Hardcoded( 48 | have($item`cosmic bowling ball`) || 49 | storageAmount($item`cosmic bowling ball`) > 0 || 50 | get("cosmicBowlingBallReturnCombats", -1) >= 0 || 51 | have($item`cursed monkey's paw`), 52 | "Cosmic bowling ball (or Cursed Monkey's Paw)", 53 | ), 54 | why: "Leveling + banish", 55 | }, 56 | { 57 | thing: $item`cursed monkey's paw`, 58 | why: "Leveling + many test improvements", 59 | optional: true, 60 | recommended: true, 61 | }, 62 | { 63 | thing: $item`Cincho de Mayo`, 64 | why: "Leveling", 65 | optional: true, 66 | }, 67 | { 68 | thing: $familiar`Cookbookbat`, 69 | why: "Turngen, stat tests", 70 | }, 71 | { 72 | thing: $item`combat lover's locket`, 73 | why: "Summons for various tests", 74 | optional: true, 75 | recommended: true, 76 | }, 77 | { 78 | thing: $item`unbreakable umbrella`, 79 | why: "Various leveling and test improvements", 80 | optional: true, 81 | recommended: true, 82 | }, 83 | { 84 | thing: $item`closed-circuit pay phone`, 85 | why: "Free fights, Non-combat, Item Drop", 86 | optional: true, 87 | recommended: true, 88 | }, 89 | { 90 | thing: new Hardcoded( 91 | have($item`one-day ticket to Dinseylandfill`) || 92 | storageAmount($item`one-day ticket to Dinseylandfill`) > 0 || 93 | get("stenchAirportAlways") || 94 | get("spookyAirportAlways") || 95 | get("hotAirportAlways") || 96 | get("coldAirportAlways") || 97 | get("sleazeAirportAlways") || 98 | get("neverendingPartyAlways"), 99 | "Scaler Zone Access", 100 | ), 101 | why: "Scalers for leveling", 102 | }, 103 | { 104 | thing: $item`backup camera`, 105 | why: "More fights from locket", 106 | optional: true, 107 | }, 108 | { 109 | thing: $item`January's Garbage Tote`, 110 | why: "XP for leveling", 111 | optional: true, 112 | }, 113 | { 114 | thing: $item`Kramco Sausage-o-Matic™`, 115 | why: "Free fights, Turngen", 116 | optional: true, 117 | }, 118 | { 119 | thing: $skill`Just the Facts`, 120 | why: "More fights from locket, more wishes from rift", 121 | optional: true, 122 | }, 123 | ]; 124 | } 125 | 126 | function buildLocketList(): Requirement[] { 127 | return [ 128 | { 129 | thing: $monster`red skeleton`, 130 | why: "Weapon Damage", 131 | optional: true, 132 | recommended: true, 133 | }, 134 | { 135 | thing: $monster`factory worker (female)`, 136 | why: "Hot Resistance", 137 | optional: true, 138 | recommended: true, 139 | }, 140 | { 141 | thing: $monster`Witchess King`, 142 | why: "Weapon Damage, Muscle %", 143 | optional: true, 144 | recommended: true, 145 | }, 146 | ]; 147 | } 148 | 149 | function buildMiscList(): Requirement[] { 150 | return [ 151 | { 152 | thing: $familiar`Disgeist`, 153 | why: "Non-combat", 154 | optional: true, 155 | recommended: true, 156 | }, 157 | { 158 | thing: $familiar`Exotic Parrot`, 159 | why: "Hot test", 160 | optional: true, 161 | recommended: true, 162 | }, 163 | { 164 | thing: $skill`Inscrutable Gaze`, 165 | why: "Leveling", 166 | optional: true, 167 | recommended: true, 168 | }, 169 | { 170 | thing: $skill`Song of Bravado`, 171 | why: "Stat %", 172 | optional: true, 173 | recommended: true, 174 | }, 175 | { 176 | thing: $skill`Get Big`, 177 | why: "Stat %", 178 | optional: true, 179 | recommended: true, 180 | }, 181 | { 182 | thing: $skill`Stevedave's Shanty of Superiority`, 183 | why: "Stat %", 184 | optional: true, 185 | recommended: true, 186 | }, 187 | { 188 | thing: $skill`The Ode to Booze`, 189 | why: "Adventures", 190 | optional: true, 191 | recommended: true, 192 | }, 193 | { 194 | thing: $skill`Pizza Lover`, 195 | why: "Adventures + XP", 196 | optional: true, 197 | recommended: true, 198 | }, 199 | { 200 | thing: $skill`Empathy of the Newt`, 201 | why: "Familiar weight", 202 | optional: true, 203 | recommended: true, 204 | }, 205 | { 206 | thing: $skill`Leash of Linguini`, 207 | why: "Familiar weight", 208 | optional: true, 209 | recommended: true, 210 | }, 211 | { 212 | thing: $skill`Amphibian Sympathy`, 213 | why: "Familiar weight", 214 | optional: true, 215 | recommended: true, 216 | }, 217 | { 218 | thing: $skill`The Sonata of Sneakiness`, 219 | why: "Non-combat", 220 | optional: true, 221 | recommended: true, 222 | }, 223 | { 224 | thing: $skill`Smooth Movement`, 225 | why: "Non-combat", 226 | optional: true, 227 | recommended: true, 228 | }, 229 | { 230 | thing: $skill`Asbestos Heart`, 231 | why: "Hot Resistance", 232 | optional: true, 233 | recommended: true, 234 | }, 235 | { 236 | thing: $skill`Elemental Saucesphere`, 237 | why: "Hot Resistance", 238 | optional: true, 239 | recommended: true, 240 | }, 241 | { 242 | thing: $skill`Tolerance of the Kitchen`, 243 | why: "Hot Resistance", 244 | optional: true, 245 | recommended: true, 246 | }, 247 | { 248 | thing: $skill`Astral Shell`, 249 | why: "Hot Resistance", 250 | optional: true, 251 | recommended: true, 252 | }, 253 | { 254 | thing: $skill`Crimbo Training: Coal Taster`, 255 | why: "Hot Resistance", 256 | optional: true, 257 | recommended: true, 258 | }, 259 | { 260 | thing: $skill`Bow-Legged Swagger`, 261 | why: "Weapon Damage", 262 | optional: true, 263 | recommended: true, 264 | }, 265 | { 266 | thing: $skill`Steely-Eyed Squint`, 267 | why: "Item Drop", 268 | optional: true, 269 | recommended: true, 270 | }, 271 | { 272 | thing: $skill`Shattering Punch`, 273 | why: "Free kill", 274 | optional: true, 275 | recommended: true, 276 | }, 277 | { 278 | thing: $skill`Gingerbread Mob Hit`, 279 | why: "Free kill", 280 | optional: true, 281 | recommended: true, 282 | }, 283 | { 284 | thing: $skill`Snokebomb`, 285 | why: "Banish", 286 | }, 287 | { 288 | thing: $skill`Saucegeyser`, 289 | why: "Combat spell", 290 | }, 291 | { 292 | thing: $skill`Advanced Saucecrafting`, 293 | why: "Saucecrafting", 294 | }, 295 | { 296 | thing: $skill`The Way of Sauce`, 297 | why: "Saucecrafting", 298 | optional: true, 299 | recommended: true, 300 | }, 301 | { 302 | thing: $skill`Impetuous Sauciness`, 303 | why: "Saucecrafting", 304 | optional: true, 305 | recommended: true, 306 | }, 307 | { 308 | thing: $skill`Expert Corner-Cutter`, 309 | why: "Saucecrafting", 310 | optional: true, 311 | recommended: true, 312 | }, 313 | { 314 | thing: $skill`Prevent Scurvy and Sobriety`, 315 | why: "Saucecrafting + turngen", 316 | optional: true, 317 | recommended: true, 318 | }, 319 | { 320 | thing: $skill`Perfect Freeze`, 321 | why: "Turngen", 322 | optional: true, 323 | recommended: true, 324 | }, 325 | { 326 | thing: $skill`Drinking to Drink`, 327 | why: "Turngen", 328 | optional: true, 329 | recommended: true, 330 | }, 331 | { 332 | thing: $skill`Cannelloni Cocoon`, 333 | why: "HP Regen", 334 | optional: true, 335 | recommended: true, 336 | }, 337 | { 338 | thing: $skill`Soul Saucery`, 339 | why: "MP Regen", 340 | optional: true, 341 | recommended: true, 342 | }, 343 | { 344 | thing: $skill`Curse of Weaksauce`, 345 | why: "MP Regen", 346 | optional: true, 347 | recommended: true, 348 | }, 349 | { 350 | thing: $skill`Inner Sauce`, 351 | why: "MP Regen", 352 | optional: true, 353 | recommended: true, 354 | }, 355 | { 356 | thing: $skill`Double-Fisted Skull Smashing`, 357 | why: "Stat test", 358 | optional: true, 359 | recommended: true, 360 | }, 361 | { 362 | thing: new Hardcoded( 363 | // These unknownRecipe properties are false when the user knows the recipe 364 | !get("unknownRecipe10972"), 365 | "Recipe of Yore: Roasted vegetable of Jarlsberg", 366 | ), 367 | why: "Food we'll cook in-run", 368 | }, 369 | { 370 | thing: new Hardcoded( 371 | !get("unknownRecipe10974"), 372 | "Recipe of Yore: Pete's Pete's wily whey bar", 373 | ), 374 | why: "Food we'll cook in-run", 375 | }, 376 | { 377 | thing: new Hardcoded(!get("unknownRecipe10975"), "Recipe of Yore: Pete's rich ricotta"), 378 | why: "Food we'll cook in-run", 379 | }, 380 | { 381 | thing: new Hardcoded(!get("unknownRecipe10976"), "Recipe of Before Yore: Boris's beer"), 382 | why: "Booze we'll brew in-run", 383 | }, 384 | { 385 | thing: new Hardcoded(!get("unknownRecipe10977"), "Recipe of Yore: honey bun of Boris"), 386 | why: "Food we'll cook in-run", 387 | }, 388 | { 389 | thing: new Hardcoded(!get("unknownRecipe10978"), "Recipe of Yore: Boris's bread"), 390 | why: "Food we'll cook in-run", 391 | }, 392 | { 393 | thing: new Hardcoded( 394 | !get("unknownRecipe10988"), 395 | "Recipe of Yore: baked veggie ricotta casserole", 396 | ), 397 | why: "Food we'll cook in-run", 398 | }, 399 | { 400 | thing: new Hardcoded(!get("unknownRecipe10989"), "Recipe of Yore: plain calzone"), 401 | why: "Food we'll cook in-run", 402 | }, 403 | { 404 | thing: new Hardcoded( 405 | (() => { 406 | // We don't need an ice house if we have Peridot 407 | if (have($item`Peridot of Peril`)) return true; 408 | // We don't need an ice house if we can map the novelty skeleton 409 | if (have($skill`Map the Monsters`)) return true; 410 | 411 | const banishes = get("banishedMonsters").split(":"); 412 | const iceHouseIndex = banishes.map((string) => string.toLowerCase()).indexOf("ice house"); 413 | if (iceHouseIndex === -1) return false; 414 | return ["remaindered skeleton", "factory-irregular skeleton", "swarm of skulls"].includes( 415 | banishes[iceHouseIndex - 1], 416 | ); 417 | })(), 418 | "Peridot / Cartography / Ice Housed Skeleton Store Monster", 419 | ), 420 | why: "Ensures Novelty Tropical Skeleton", 421 | }, 422 | { 423 | thing: new Hardcoded( 424 | get("knownAscensions") >= 10, 425 | "Access to all-purpose flower in the Gift Shop", 426 | ), 427 | why: "Muscle test", 428 | }, 429 | { 430 | thing: $skill`Pride of the Puffin`, 431 | why: "Monster Level", 432 | optional: true, 433 | }, 434 | { 435 | thing: $skill`Drescher's Annoying Noise`, 436 | why: "Monster Level", 437 | optional: true, 438 | }, 439 | { 440 | thing: $skill`Ur-Kel's Aria of Annoyance`, 441 | why: "Monster Level", 442 | optional: true, 443 | }, 444 | { 445 | thing: $skill`Master Saucier`, 446 | why: "Spell Damage", 447 | optional: true, 448 | }, 449 | { 450 | thing: $skill`Subtle and Quick to Anger`, 451 | why: "Spell Damage", 452 | optional: true, 453 | }, 454 | { 455 | thing: $skill`Simmer`, 456 | why: "Spell Damage", 457 | optional: true, 458 | recommended: true, 459 | }, 460 | { 461 | thing: $skill`Always Never Not Guzzling`, 462 | why: "Item Drop", 463 | optional: true, 464 | recommended: true, 465 | }, 466 | { 467 | thing: $skill`Fat Leon's Phat Loot Lyric`, 468 | why: "Item Drop", 469 | optional: true, 470 | }, 471 | { 472 | thing: $skill`Mad Looting Skillz`, 473 | why: "Item Drop", 474 | optional: true, 475 | }, 476 | { 477 | thing: $skill`Object Quasi-Permanence`, 478 | why: "Item Drop", 479 | optional: true, 480 | }, 481 | { 482 | thing: $skill`Powers of Observatiogn`, 483 | why: "Item Drop", 484 | optional: true, 485 | }, 486 | { 487 | thing: $skill`Bind Spice Ghost`, 488 | why: "Item Drop", 489 | optional: true, 490 | }, 491 | { 492 | thing: $skill`Thief Among the Honorable`, 493 | why: "Item Drop", 494 | optional: true, 495 | }, 496 | { 497 | thing: $skill`Natural Born Scrabbler`, 498 | why: "Item Drop", 499 | optional: true, 500 | }, 501 | { 502 | thing: $skill`20/20 Vision`, 503 | why: "Item Drop", 504 | optional: true, 505 | }, 506 | { 507 | thing: $skill`Carol of the Bulls`, 508 | why: "Weapon Damage", 509 | optional: true, 510 | recommended: true, 511 | }, 512 | { 513 | thing: $skill`Carol of the Hells`, 514 | why: "Spell Damage", 515 | optional: true, 516 | recommended: true, 517 | }, 518 | { 519 | thing: $skill`Song of Sauce`, 520 | why: "Spell Damage", 521 | optional: true, 522 | recommended: true, 523 | }, 524 | { 525 | thing: $skill`Song of the North`, 526 | why: "Weapon Damage", 527 | optional: true, 528 | recommended: true, 529 | }, 530 | { 531 | thing: $skill`Jackasses' Symphony of Destruction`, 532 | why: "Weapon Damage", 533 | optional: true, 534 | }, 535 | { 536 | thing: $skill`Scowl of the Auk`, 537 | why: "Weapon Damage", 538 | optional: true, 539 | }, 540 | { 541 | thing: $skill`Rage of the Reindeer`, 542 | why: "Weapon Damage", 543 | optional: true, 544 | }, 545 | { 546 | thing: $skill`Tenacity of the Snapper`, 547 | why: "Weapon Damage", 548 | optional: true, 549 | }, 550 | { 551 | thing: $skill`Claws of the Walrus`, 552 | why: "Weapon Damage", 553 | optional: true, 554 | }, 555 | { 556 | thing: $skill`Blessing of the War Snapper`, 557 | why: "Weapon Damage", 558 | optional: true, 559 | }, 560 | { 561 | thing: $skill`Evoke Eldritch Horror`, 562 | why: "Free Fight", 563 | optional: true, 564 | }, 565 | 566 | { 567 | thing: $item`Calzone of Legend`, 568 | why: "Turngen + Stat %", 569 | }, 570 | { 571 | thing: $item`Deep Dish of Legend`, 572 | why: "Turngen + Stat %", 573 | }, 574 | { 575 | thing: $item`Pizza of Legend`, 576 | why: "Turngen + Stat %", 577 | }, 578 | { 579 | thing: new Hardcoded( 580 | have($item`borrowed time`) || 581 | storageAmount($item`borrowed time`) > 0 || 582 | have($skill`Summon Clip Art`), 583 | "borrowed time", 584 | ), 585 | why: "Turngen", 586 | }, 587 | { 588 | thing: $item`non-Euclidean angle`, 589 | why: "XP %", 590 | optional: true, 591 | }, 592 | { 593 | thing: $item`abstraction: category`, 594 | why: "XP %", 595 | optional: true, 596 | }, 597 | ]; 598 | } 599 | 600 | function checkThing(thing: Thing): [boolean, string] { 601 | if (thing instanceof Hardcoded) return [thing.have, thing.name]; 602 | if (thing instanceof Familiar) return [have(thing), thing.hatchling.name]; 603 | if (thing instanceof Skill) 604 | return [ 605 | [Lifestyle.softcore, Lifestyle.hardcore].some( 606 | (lifestyle) => lifestyle === permedSkills().get(thing), 607 | ), 608 | thing.name, 609 | ]; 610 | if (thing instanceof Monster) 611 | return [new Set(CombatLoversLocket.unlockedLocketMonsters()).has(thing), thing.name]; 612 | return [have(thing) || storageAmount(thing) > 0, thing.name]; 613 | } 614 | 615 | function check(req: Requirement): [boolean, string, Requirement] { 616 | if (Array.isArray(req.thing)) { 617 | const checks = req.thing.map(checkThing); 618 | 619 | return [ 620 | checks.find((res) => res[0]) !== undefined, 621 | checks.map((res) => res[1]).join(" OR "), 622 | req, 623 | ]; 624 | } else { 625 | const res = checkThing(req.thing); 626 | return [res[0], res[1], req]; 627 | } 628 | } 629 | 630 | export function checkRequirements(): void { 631 | let missing_optional = 0; 632 | let missing = 0; 633 | 634 | const categories: [string, Requirement[]][] = [ 635 | ["IoTMs (Necessary)", buildIotmList().filter((req) => !req.optional)], 636 | ["Miscellany (Necessary)", buildMiscList().filter((req) => !req.optional)], 637 | [ 638 | "IoTMs (Highly Recommended)", 639 | buildIotmList().filter((req) => req.optional && req.recommended), 640 | ], 641 | [ 642 | "Miscellany (Highly Recommended)", 643 | buildMiscList().filter((req) => req.optional && req.recommended), 644 | ], 645 | ["Combat Lover's Locket Monsters (Highly Recommended)", buildLocketList()], 646 | ["IoTMs (Optional)", buildIotmList().filter((req) => req.optional && !req.recommended)], 647 | ["Miscellany (Optional)", buildMiscList().filter((req) => req.optional && !req.recommended)], 648 | ]; 649 | printHtml( 650 | "Checking your character... Legend: ✓ Have / X Missing & Required / X Missing & Optional", 651 | ); 652 | for (const [name, requirements] of categories) { 653 | if (requirements.length === 0) continue; 654 | 655 | const requirements_info: [boolean, string, Requirement][] = requirements.map(check); 656 | print(name, "blue"); 657 | for (const [have_it, name, req] of requirements_info.sort((a, b) => a[1].localeCompare(b[1]))) { 658 | const color = have_it ? "#888888" : req.optional ? "black" : "red"; 659 | const symbol = have_it ? "✓" : "X"; 660 | if (!have_it && req.optional) missing_optional++; 661 | if (!have_it && !req.optional) missing++; 662 | print(`${symbol} ${name} - ${req.why}`, color); 663 | } 664 | print(""); 665 | } 666 | 667 | // Print the count of missing things 668 | if (missing > 0) { 669 | print( 670 | `You are missing ${missing} required things. This script will not yet work for you.`, 671 | "red", 672 | ); 673 | if (missing_optional > 0) print(`You are also missing ${missing_optional} optional things.`); 674 | } else { 675 | if (missing_optional > 0) { 676 | print( 677 | `You are missing ${missing_optional} optional things. This script should work, but it could do better.`, 678 | ); 679 | } else { 680 | print(`You have everything! You are the shiniest star. This script should work great.`); 681 | } 682 | } 683 | } 684 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InstantSCCS 2 | 3 | `InstantSCCS` is a softcore one-day Community Service script meant for looping in Kingdom of Loathing, and is designed to work for low-to-mid-shiny accounts (i.e. accounts with minimal expensive items/skills). The user is expected to have a bunch of softcore-permed skills, and at least ~7 IotMs in order to enable this (one of which is the [Clan VIP Lounge key](https://wiki.kingdomofloathing.com/Clan_VIP_Lounge_key)).
4 | 5 | ## Installation 6 | 7 | To install the script, use the following command in the KoLMafia CLI.
8 | 9 | ```text 10 | git checkout https://github.com/Pantocyclus/instantsccs.git release 11 | ``` 12 | 13 | ## Usage 14 | 15 | For those who are interested in using `InstantSCCS` as is, the following sections detail the prerequisites, choices in Valhalla, and required resources.
16 | 17 | ### Before Ascending 18 | 19 | - Ensure that you have the following items (which will be pulled/used during the run): 1x [one-day ticket to Dinseylandfill](https://wiki.kingdomofloathing.com/One-day_ticket_to_Dinseylandfill), 1x [Calzone of Legend](https://wiki.kingdomofloathing.com/Calzone_of_Legend), 1x [Deep Dish of Legend](https://wiki.kingdomofloathing.com/Deep_Dish_of_Legend), 1x [Pizza of Legend](https://wiki.kingdomofloathing.com/Pizza_of_Legend) and 1x [borrowed time](https://wiki.kingdomofloathing.com/Borrowed_time). 20 | - If you have [any](https://wiki.kingdomofloathing.com/Neverending_Party_invitation_envelope) [one](https://wiki.kingdomofloathing.com/Airplane_charter:_Spring_Break_Beach) [of](https://wiki.kingdomofloathing.com/Airplane_charter:_Conspiracy_Island) [the](https://wiki.kingdomofloathing.com/Airplane_charter:_Dinseylandfill) [scaler](https://wiki.kingdomofloathing.com/Airplane_charter:_That_70s_Volcano) [zones](https://wiki.kingdomofloathing.com/Airplane_charter:_The_Glaciest) or a [Tome of Clip Art](https://wiki.kingdomofloathing.com/Tome_of_Clip_Art), you may want to have a [non-Euclidean angle](https://wiki.kingdomofloathing.com/Non-Euclidean_angle) available (for more efficient powerleveling). 21 | - If you have both a scaler zone and a [Tome of Clip Art](https://wiki.kingdomofloathing.com/Tome_of_Clip_Art), you may want to have both a [non-Euclidean angle](https://wiki.kingdomofloathing.com/Non-Euclidean_angle) and an [Abstraction: Category](https://wiki.kingdomofloathing.com/Abstraction:_category) before ascending. 22 | - Ensure that you have access to a clan with a fully stocked [VIP lounge](https://wiki.kingdomofloathing.com/VIP_Lounge). Also ensure that the [Clan Floundry]() has sufficient stocks of cod to pull a [codpiece](https://wiki.kingdomofloathing.com/Codpiece). 23 | - Have any one of the [factory-irregular skeleton](https://wiki.kingdomofloathing.com/Factory-irregular_skeleton), [remaindered skeleton](https://wiki.kingdomofloathing.com/Remaindered_skeleton) or [swarm of skulls](https://wiki.kingdomofloathing.com/Swarm_of_skulls) banished in your [ice house](https://wiki.kingdomofloathing.com/Ice_house). 24 | - Have a [factory worker (female)](), [Witchess King](https://wiki.kingdomofloathing.com/Witchess_King) and [red skeleton](https://wiki.kingdomofloathing.com/Red_skeleton) registered in your [combat lover's locket](https://wiki.kingdomofloathing.com/Combat_lover%27s_locket). 25 | - Have at least 10 ascensions so that you can purchase an [all-purpose flower](https://wiki.kingdomofloathing.com/All-purpose_flower) from [The Gift Shop](https://wiki.kingdomofloathing.com/The_Gift_Shop); this should include at least 5 100% familiar runs so that you have the [astral pet sweater](https://wiki.kingdomofloathing.com/Astral_pet_sweater) unlocked. 26 | - Have the following [cookbookbat](https://wiki.kingdomofloathing.com/Cookbookbat) recipes read: [honey bun of Boris](https://wiki.kingdomofloathing.com/Recipe_of_Before_Yore:_honey_bun_of_Boris), [Pete's wiley whey bar](https://wiki.kingdomofloathing.com/Pete%27s_wiley_whey_bar), [Boris's bread](https://wiki.kingdomofloathing.com/Recipe_of_Before_Yore:_Boris%27s_bread), [roasted vegetable of Jarlsberg](https://wiki.kingdomofloathing.com/Recipe_of_Before_Yore:_roasted_vegetable_of_J.), [Pete's rich ricotta](https://wiki.kingdomofloathing.com/Recipe_of_Before_Yore:_Pete%27s_rich_ricotta), [plain calzone](https://wiki.kingdomofloathing.com/Recipe_of_Before_Yore:_plain_calzone) and [baked veggie ricotta](https://wiki.kingdomofloathing.com/Recipe_of_Before_Yore:_baked_veggie_ricotta). 27 | - You should run `instantsccs sim` to check if you have all the necessary requirements.
28 | 29 | - Note that while not a lot of requirements are listed as necessary, you are highly encouraged to have most, if not all, of the highly recommended resources (or have shinies to make up for whichever is lacking). 30 | - The script will not break if you are lacking any particular non-necessary requirement, but it will also not guarantee you success for a one-day ascension if all you have are only the necessary requirements and nothing else. 31 | - It will, however, almost certainly break for low shinies if any of the requirements marked "Necessary" are missing. 32 | - Slightly shinier accounts may still make it through without any issues if the [other various supported IotMs](https://github.com/Pantocyclus/InstantSCCS/blob/main/ITEMS.md) are able to make up for any of the missing necessary items. 33 | 34 | ![image](https://user-images.githubusercontent.com/98746573/225634734-8792246c-cea1-4f4a-81c5-315b252500d6.png) 35 | 36 | ### In Valhalla 37 | 38 | Because we rely heavily on the [cookbookbat](https://wiki.kingdomofloathing.com/Cookbookbat) ingredients, (1) the most basic route only works for [Saucerors](https://wiki.kingdomofloathing.com/Sauceror), and (2) the [astral six-pack](https://wiki.kingdomofloathing.com/Astral_six-pack) is the only useful astral consumable since it doesn't compete with the stomach-space required by the [cookbookbat](https://wiki.kingdomofloathing.com/Cookbookbat). The [pet sweater](https://wiki.kingdomofloathing.com/Astral_pet_sweater) allows us to benefit from the [Disgeist]() in the NC test, and [The Opossum](https://wiki.kingdomofloathing.com/The_Opossum) gives us +5 turngen and free +11ML from the [Mind Control Device](https://wiki.kingdomofloathing.com/Mind_control_device).
39 | 40 | - [astral six-pack](https://wiki.kingdomofloathing.com/Astral_six-pack) from The Deli Lama
41 | - [astral pet sweater](https://wiki.kingdomofloathing.com/Astral_pet_sweater) from Pet Heaven
42 | - [Sauceror](https://wiki.kingdomofloathing.com/Sauceror)
43 | - [The Opossum](https://wiki.kingdomofloathing.com/The_Opossum)
44 | - [Softcore](https://wiki.kingdomofloathing.com/Ascension#Normal_Difficulty)
45 | 46 | ### Required IotMs 47 | 48 | IotMs are incredibly expensive, and they tend to increase in price the longer they have existed due to the artificial supply limit. Unfortunately, they are incredibly powerful too, and so we will need to rely on them to enable a 1-day SCCS. There is a hard requirement on the [Clan VIP Lounge key](https://wiki.kingdomofloathing.com/Clan_VIP_Lounge_key), as it is one of the few "IotMs" that are recurring (and thus are not gated by the same artificial supply limit as mentioned above), and it provides access to >= 30 Mr. A's-worth of IotMs.
49 | One of the hardest tasks in CS is leveling, due to the limited resources we have access to to optimise for the stat tests (HP, Mus, Myst, Mox). The other required IotMs thus have to provide incredible statgain and/or turngen/turnsave potential. The current routing is built around the following 6 other IotMs.
50 | 51 | | IotM | Use | 52 | | --------------------------------------------------------------------------------------- | ----------- | 53 | | [Clan VIP Lounge key](https://wiki.kingdomofloathing.com/Clan_VIP_Lounge_key) | many things | 54 | | [model train set](https://wiki.kingdomofloathing.com/Model_train_set) | xp | 55 | | [cosmic bowling ball](https://wiki.kingdomofloathing.com/Cosmic_bowling_ball) | xp + banish | 56 | | [cookbookbat](https://wiki.kingdomofloathing.com/Cookbookbat) | turngen | 57 | | [unbreakable umbrella](https://wiki.kingdomofloathing.com/Unbreakable_umbrella) | many things | 58 | | [combat lover's locket](https://wiki.kingdomofloathing.com/Combat_lover%27s_locket) | many things | 59 | | [closed-circuit pay phone](https://wiki.kingdomofloathing.com/Closed-circuit_pay_phone) | many things | 60 | 61 | As of April 2023, the introduction of the [cursed monkey's paw](https://wiki.kingdomofloathing.com/Cursed_monkey%27s_paw) (which was released after this route was planned) could potentially wholly replace the cosmic bowling ball and unbreakable umbrella - the paw is currently supported but a minimal run with this has not been tested, so this remains a (highly likely) hypothetical.
62 | 63 | ### Absolutely Non-Negotiable Requirements 64 | 65 | Following the routing laid out in the Basic Run Plan, this script will almost definitely fail if you do not meet all of the following requirements:
66 | 67 | - [Ascend](https://wiki.kingdomofloathing.com/Ascend) as a [Sauceror](https://wiki.kingdomofloathing.com/Sauceror) into [softcore](https://wiki.kingdomofloathing.com/Ascension#Normal_Difficulty) [Community Service](https://wiki.kingdomofloathing.com/Community_Service)
68 | - Own a [Clan VIP Lounge key](https://wiki.kingdomofloathing.com/Clan_VIP_Lounge_key), [model train set](https://wiki.kingdomofloathing.com/Model_train_set), [cosmic bowling ball](https://wiki.kingdomofloathing.com/Cosmic_bowling_ball) and [cookbookbat](https://wiki.kingdomofloathing.com/Cookbookbat)
69 | - Have [Advanced Saucecrafting](https://wiki.kingdomofloathing.com/Advanced_Saucecrafting), [Saucegeyser](https://wiki.kingdomofloathing.com/Saucegeyser) and [Snokebomb](https://wiki.kingdomofloathing.com/Snokebomb) softcore permed
70 | - Have a [Pizza of Legend](https://wiki.kingdomofloathing.com/Pizza_of_Legend), [Deep Dish of Legend](https://wiki.kingdomofloathing.com/Deep_Dish_of_Legend) and [Calzone of Legend](https://wiki.kingdomofloathing.com/Calzone_of_Legend) available in [Hagnk's](https://wiki.kingdomofloathing.com/Hagnk%27s_Ancestral_Mini-Storage)
71 | - Have access to [any](https://wiki.kingdomofloathing.com/Neverending_Party_invitation_envelope) [one](https://wiki.kingdomofloathing.com/Airplane_charter:_Spring_Break_Beach) [of](https://wiki.kingdomofloathing.com/Airplane_charter:_Conspiracy_Island) [the](https://wiki.kingdomofloathing.com/Airplane_charter:_Dinseylandfill) [scaler](https://wiki.kingdomofloathing.com/Airplane_charter:_That_70s_Volcano) [zones](https://wiki.kingdomofloathing.com/Airplane_charter:_The_Glaciest), either by owning the charters or a [daypass](https://wiki.kingdomofloathing.com/One-day_ticket_to_Dinseylandfill)
72 | - Have either the [factory-irregular skeleton](https://wiki.kingdomofloathing.com/Factory-irregular_skeleton), [remaindered skeleton](https://wiki.kingdomofloathing.com/Remaindered_skeleton) or [swarm of skulls](https://wiki.kingdomofloathing.com/Swarm_of_skulls) banished in your [ice house](https://wiki.kingdomofloathing.com/Ice_house) 73 | - Have at least 10 ascensions
74 | - Know all of the following [cookbookbat](https://wiki.kingdomofloathing.com/Cookbookbat) recipes: [honey bun of Boris](https://wiki.kingdomofloathing.com/Recipe_of_Before_Yore:_honey_bun_of_Boris), [Pete's wiley whey bar](https://wiki.kingdomofloathing.com/Pete%27s_wiley_whey_bar), [Boris's bread](https://wiki.kingdomofloathing.com/Recipe_of_Before_Yore:_Boris%27s_bread), [roasted vegetable of Jarlsberg](https://wiki.kingdomofloathing.com/Recipe_of_Before_Yore:_roasted_vegetable_of_J.), [Pete's rich ricotta](https://wiki.kingdomofloathing.com/Recipe_of_Before_Yore:_Pete%27s_rich_ricotta), [plain calzone](https://wiki.kingdomofloathing.com/Recipe_of_Before_Yore:_plain_calzone) and [baked veggie ricotta](https://wiki.kingdomofloathing.com/Recipe_of_Before_Yore:_baked_veggie_ricotta).
75 | 76 | Run `instantsccs sim` to ensure you have all the necessary requirements before you ascend!
77 | 78 | Many recent updates in the later half of 2023, which includes support for various new IotMs, means that many of these are no longer hard requirements. However, the enabling features of these requirements are non-replaceable.
79 | For example, the [ice house](https://wiki.kingdomofloathing.com/Ice_house) may be replaced by owning a [Comprehensive Cartographic Compendium](https://wiki.kingdomofloathing.com/Comprehensive_Cartographic_Compendium) so that we are able to force a combat with the [novelty tropical skeleton](https://wiki.kingdomofloathing.com/Novelty_tropical_skeleton). If you have enough turnsaves from owning [various other supported IotMs](https://github.com/Pantocyclus/InstantSCCS/blob/main/ITEMS.md), you might also be able to completely skip owning a [cookbookbat](https://wiki.kingdomofloathing.com/Cookbookbat) and pulling/consuming any of its related foods.
80 | As stated in the [FAQ](https://github.com/Pantocyclus/InstantSCCS/?tab=readme-ov-file#faq), at the shiniest levels, this script supports running as a [hardcore](https://wiki.kingdomofloathing.com/Hardcore) (0 pulls) [seal clubber](https://wiki.kingdomofloathing.com/Seal_Clubber) (not sauceror), with a competitive turncount and organ usage (0 cookbookbat foods eaten and 0 VIP clan lounge drinks drunk) as compared to many other high-shiny scripts.

81 | 82 | ### Basic Run Plan 83 | 84 | See the run plan [here](https://github.com/Pantocyclus/InstantSCCS/blob/main/RUNPLAN.md), which also provides a summary of the resources/organs used in the most basic route.
85 | 86 | ## FAQ 87 | 88 | ### Does this work in HC? 89 | 90 | This script is hardcoded to eat the 3x T4 cookbookbat foods (which are all pulled), and it is highly unlikely that you will be able to generate enough ingredients to cook all of them in HC (without any pulls). However, it can be (and has been) done, although this is strongly discouraged.
91 | 92 | ### Does this script work for other classes / Why Sauceror? 93 | 94 | There is currently only 1 facial expression that gives xp% - Inscrutable Gaze (for myst xp%). For low shinies, this can make or break the run, so this largely limits us to only myst classes.
95 | 96 | With minimal resources, we will also need to craft both the offstat T3 cookbookbat foods for the stat% they give (to clear the stat tests), which necessitates having all the different ingredients dropping before our powerleveling ends in ~55 turns (excess turns result in increased turncount, requiring more turngen). This rules out Pastamancer and Disco Bandit.
97 | 98 | We will also need 2 Vegetable of Jarlsberg drops in those same ~55 turns in order to craft the myst T2 (for item%; on top of the T3 foods we're crafting above), and this additionally rules out Turtle Tamer and Accordion Thief.
99 | 100 | As an added benefit, the MP regen from curse of weaksauce and soul food from being a Sauceror, as well as the additional crafted reagent potions for powerleveling, tends to be invaluable to low shiny accounts.
101 | 102 | However, if you are sufficiently shiny, the script has been proven to work with all classes. Note that this requires having 0 reliance on any CBB foods that are crafted in run. Additionally, we require [Prevent Scurvy and Sobriety](https://wiki.kingdomofloathing.com/Prevent_Scurvy_and_Sobriety) to be permed for Mus classes; for Mox classes, you must have an [Evil Olive](https://wiki.kingdomofloathing.com/Evil_Olive) available in your [locket](https://wiki.kingdomofloathing.com/Combat_lover%27s_locket), or own both a [Chest Mimic](https://wiki.kingdomofloathing.com/Chest_Mimic) familiar and an [Apriling band helmet](https://wiki.kingdomofloathing.com/Apriling_band_helmet).
103 | 104 | ### What IotMs are currently supported and how are they being used by the script? 105 | 106 | InstantSCCS supports a very large number of IotMs, but, as a generalist script, may not be able to eke out every last benefit from each IotM. For exact specifics, refer to [this list](https://github.com/Pantocyclus/InstantSCCS/blob/main/ITEMS.md).
107 | 108 | ### I'm pretty shiny - can I get the script to save certain resources/organs? 109 | 110 | Run `instantsccs savedresources` to see a list of preferences you can set to save specific resources. You may also explicitly exclude acquiring certain buffs by typing `set instant_explicitlyExcludedBuffs=` (and confirming that the correct buffs have been excluded in the savedresources printout).
111 | 112 | ![image](https://github.com/Pantocyclus/InstantSCCS/assets/98746573/3e836c0b-5e89-4a47-8b9c-bfb618ddfba0)
113 | 114 | Similarly, you may exclude using certain familiars during the leveling phase by typing `set instant_explicitlyExcludedFamiliars=` (and confirming that the correct familiars have been excluded in the savedresources printout).
115 | 116 | ### My settings are such that the script no longer uses all 5 softcore pulls. Can I make the script pull and use some other resources? 117 | 118 | You can make the necessary softcore pulls prior to running InstantSCCS. This generally should be equipments (e.g. [Staff of the Roaring Hearth](https://wiki.kingdomofloathing.com/Staff_of_the_Roaring_Hearth), [repaid diaper](https://wiki.kingdomofloathing.com/Repaid_diaper), [meteorite necklace](https://wiki.kingdomofloathing.com/Meteorite_necklace) etc), since they would automatically be equipped by the maximizer for various tasks/tests.
119 | 120 | For potions and consumables, you may set `instant_TestPulls=` to pull and use them right before triggering the tests. For example, `set instant_spellTestPulls=5020,10607` to automatically pull and use the [tobiko marble soda](https://wiki.kingdomofloathing.com/Tobiko_marble_soda) and [Yeg's Motel hand soap](https://wiki.kingdomofloathing.com/Yeg%27s_Motel_hand_soap) for the Spell Damage Test (`` should be one of `hp|mus|myst|mox|hot|com|fam|spell|weapon|booze`).
121 | 122 | For equipment and other stuff, `set instant_prePulls=` to pull said items just prior to the leveling portion of the script, or `set instant_freeFightPulls=` to pull and use these items just prior to the scaling free fights so as not to burn turns of these effects.
123 | 124 | ### I am severely overleveling. What preferences can I change to make the script spend fewer turns powerleveling? 125 | 126 | The number of turns spent on powerleveling is not solely dependent on your level, but, unintuitively, depends on whether you have collected sufficient CBB ingredients to craft the foods that the script wants to consume (assuming you have a cookbookbat).
127 | 128 | If you're severely overleveled, the likely bottleneck would be the script trying to find the last few CBB ingredients (for the food buffs that you no longer need). The solution would thus be to exclude certain specific CBB foods to prevent the script from unnecessarily looking for their ingredients.
129 | 130 | For more details, refer to the following [post](https://github.com/Pantocyclus/InstantSCCS/blob/main/LEVELING.md).
131 | 132 | ### I'm looking to improve my CS runs - what IotMs and skills should I go for next? 133 | 134 | `instantsccs sim` groups various resources by how impactful they are. You may also refer to [this slightly more comprehensive list](https://github.com/Pantocyclus/InstantSCCS/blob/main/RECOMMENDATIONS.md) for suggestions.
135 | 136 | ### I don't have a lot of the recommended skills. Will this script still work for me? 137 | 138 | If you are decently shiny, probably. The list of skills is meant to give a rough gauge of what is required to prevent the script from failing in general, which could happen for various reasons, including
139 | 140 | - Running out of HP (cannelloni cocoon)
141 | - Running out of MP (inner sauce, curse of weaksauce, soul saucery)
142 | - Running out of turns, either from turngen or high turn-taking tests/leveling tasks (almost everything else)
143 | 144 | The script might still work if you have enough IotMs to make up for the loss in turnsaves from lacking various skills (i.e. the skills are listed to indicate that if you have nothing else, you'll need these in order to be able to complete the run).
145 | 146 | If you meet all of the hardcoded requirements, and have a few of the recent IotMs which are supported (newer IotMs tend to be stronger due to powercreep), it's highly possible that this script will work for you (since many powerful IotMs tend to do a lot for turnsaving, which is the sole purpose for many of the other "highly-recommended" skills).
147 | 148 | Note that we are already filling up all our organs in this route, so you shouldn't expect to have additional turngen (e.g. from locketed + backed up witchess bishops and knights; sausage goblins still work and are supported). Your IotMs will have to make up for the missing skills purely in turnsaves.
149 | 150 | ### I can't survive the early fights! What do I do? 151 | 152 | If you're scripting your own run, try eating the Deep Dish of Legend early (this is already done in the script above). It gives +100%hp and +300%mus, which should help you survive a few more hits from the monsters. However, this does come at the cost of possibly not carrying this buff over to the NC test to buff your Disgeist, thus losing you 5%NC (increasing your turncount by 3).
153 | 154 | ### What range of shininess is this script suitable for? 155 | 156 | This script supports runs from anywhere between 90-320 turns (assuming no manual pulls; correct as of September 2023).If you are able to cap all the stat tests without using any CBB foods (including the T4 ones) because you have access to a bunch of free fights, stat% and xp% buffs, the script now fully supports running without CBB and can be pretty close to optimal (you might even want to consider setting `_instant_skipOfLegend` in your wrapper prior to running the script to save all 5 pulls for other manual turncutting pulls [or to run it in HC]).
157 | 158 | However, you may also consider using one of these other scripts listed [here](https://loathers.github.io/CS-Scripting-Resources.html) instead to eke out that last bit of efficiency. For example, [this personal script](https://github.com/Pantocyclus/InstantHCCS) is able to achieve a ~1/91 HCCS with fewer resources and organs used as compared to the ~1/93 HCCS (yes, HC) that I get with InstantSCCS (with my preferences already set to largely optimize for profits).
159 | 160 | ### Why is InstantSCCS not using the S.I.T. Course Completion Certificate? 161 | 162 | The drops from the S.I.T. Course Completion Certificate aren't used in the route, so it is up to the user to decide which course they would like to commit to for the day before invoking InstantSCCS.
163 | 164 | ### Is there a way to automate the acquisition of the necessary T4 CBB foods/astral choices in Valhalla? 165 | 166 | As with the usage of the S.I.T Course Voucher prior to invoking InstantSCCS, you may find a few community looping scripts/wrappers that would do so for you. At the present moment these are not natively shipped together with InstantSCCS.
167 | 168 | ### I'm having some issues with faxing in an ungulith. Does the script support locketing the ungulith instead? 169 | 170 | Consider getting a whitelist to CSLoopers Unite, a clan with an ungulith in the fax machine, set up specifically to address faxbot issues (the clan does not whitelist any faxbots so as to prevent accidental faxing in of other monsters). The script automatically sends a kmail to my clan sitter - Pantocyclus (#3626664), but you may also kmail me on my main - WARriorer (#1634187) to request a whitelist.
171 | 172 | ### Does the script support switching between a clans (e.g. for VIP Lounge items and a clan with Mother Slime set up for Inner Elf)? 173 | 174 | The script assumes you are already in the VIP clan (i.e. you should whitelist into your VIP clan before running the script).
175 | 176 | If you are already whitelisted to CSLoopers Unite, the script will already have access to Mother Slime. Otherwise, you will have to `set instant_motherSlimeClan=` for InstantSCCS to attempt grabbing Inner Elf - this may be the same clan as your VIP clan, or a different one altogether (note that if this is set, the script will default to using your clan of choice instead, as it reduces congestion of the CSLoopers Unite Slime Tube).
177 | 178 | The script also defaults to using the VIP clan for the [Floundry](). To change this, you will also need to `set instant_floundryClan=`.
179 | 180 | ### How do I get the script to acquire busks for leveling and/or the tests? 181 | 182 | Similar to the custom pulls, you may set `instant_TestBusks=,,...` (for example: `set instant_spellTestBusks=4:830,5:980` to attempt getting 830DA and 980DA for the 4th and 5th busks respectively) to acquire specific [busks](https://wiki.kingdomofloathing.com/Beret_Busking). You may also set `instant_preBusks` (and/or `instant_freeFightBusks`) to acquire busks for powerleveling.
183 | 184 | Note that the script
185 | 186 | - indexes buffs from 1 (i.e. cast 1, 2, 3, 4, 5)
187 | - does not acquire busks by default
188 | - does not burn off busks to hit the desired cast number (i.e. If no busks have been cast upon reaching `instant_spellTestBusks=4:830,5:980`, neither busk will be cast; you may consider padding the preference with a power you're already aiming to get e.g. `instant_spellTestBusks=1:830,2:830,3:830,4:830,5:980`)
189 | - currently only considers purchasing from [The Armory and Leggery](https://wiki.kingdomofloathing.com/The_Armory_and_Leggery), and does not consider [armorcrafting](https://wiki.kingdomofloathing.com/Armorcrafting)/[meatsmithing](https://wiki.kingdomofloathing.com/Meatsmithing), [coinmasters](https://wiki.kolmafia.us/index.php/Coinmaster) or [forcing drops](https://wiki.kingdomofloathing.com/Yellow_ray_strategy) from [copied monsters](https://wiki.kingdomofloathing.com/Copying) yet
190 | - does not automatically acquire [Hammertime](https://wiki.kingdomofloathing.com/Hammertime) (consider adding the [too legit potion](https://wiki.kingdomofloathing.com/Too_legit_potion) to the custom pulls - e.g. `set instant_spellTestPulls=5258` to have Hammertime for the spell test busks)
191 | - throws an error if it fails to acquire the specified busk at the specified cast
192 | 193 | Do consider using [this](https://beret.loathers.net/) to help you plan out the busks you need.
194 | --------------------------------------------------------------------------------