diff --git a/src/partials/freerun.ts b/src/partials/freerun.ts new file mode 100644 index 0000000..8e329be --- /dev/null +++ b/src/partials/freerun.ts @@ -0,0 +1,25 @@ +import { $item, $skill, AsdonMartin, get, have, Macro } from "libram"; +import { Task } from "../task"; + +export type FreeRun = { macro: Macro }; +export const BowlingBall: Omit & FreeRun = { + name: "Bowling Ball Run", + ready: () => get("cosmicBowlingBallReturnCombats") < 1 && get("hasCosmicBowlingBall"), + completed: () => false, + macro: Macro.trySkill($skill`Bowl a Curveball`), +}; + +export const AsdonBumper: Omit & FreeRun = { + name: "Asdon Bumper", + ready: () => AsdonMartin.installed(), + completed: () => get("banishedMonsters").includes("Spring-Loaded Front Bumper"), + macro: Macro.skill($skill`Asdon Martin: Spring-Loaded Front Bumper`), + prepare: () => AsdonMartin.fillTo(50), +}; + +export const Scrapbook: Omit & FreeRun = { + name: "Familiar Scrapbook", + ready: () => have($item`familiar scrapbook`) && get("scrapbookCharges") > 40, + completed: () => get("scrapbookCharges") < 40, + macro: Macro.skill($skill`Show Your Boring Familiar Pictures`), +} \ No newline at end of file diff --git a/src/partials/index.ts b/src/partials/index.ts new file mode 100644 index 0000000..aa515a4 --- /dev/null +++ b/src/partials/index.ts @@ -0,0 +1,128 @@ +import { Outfit } from "../outfit"; +import { Task, TaskRequiredProperties } from "../task"; + +class TaskPartialError extends Error {} + +function validate(task: Partial>): Task { + const missing = TaskRequiredProperties.filter((p) => task[p] === undefined); + if (missing.length > 0) { + throw new TaskPartialError(`Missing properties on task: ${missing.join(",")}`); + } + return task as Task; +} + +function combinef( + a: Partial>, + b: Partial>, + c: "prepare" | "post" +): () => void { + const afun = a[c] ?? (() => true); + const bfun = b[c] ?? (() => true); + return () => { + afun(); + bfun(); + }; +} + +export function extend>, S extends keyof T, Z extends string>( + base: T, + options: Omit & Partial> +): Task { + const opt = options as Partial>; + const merge: Partial>[] = [ + { + name: opt.name ?? base.name, + completed: () => (opt.completed ?? (() => false))() || (base.completed ?? (() => false))(), + }, + ]; + if (typeof opt.do === "function" && typeof base.do === "function") { + const a = opt.do; + const b = base.do; + merge.push({ + do: () => { + a(); + b(); + }, + }); + } else { + merge.push({ + do: opt.do ?? base.do, + }); + } + if ("ready" in opt || "ready" in base) { + merge.push({ + ready: () => + (typeof opt.ready === "function" ? opt.ready() : opt.ready ?? true) && + (typeof base.ready === "function" ? base.ready() : base.ready ?? true), + }); + } + if ("prepare" in opt || "prepare" in base) { + merge.push({ + prepare: combinef(opt, base, "prepare"), + }); + } + if ("post" in opt || "post" in base) { + merge.push({ + prepare: combinef(opt, base, "post"), + }); + } + if ("acquire" in opt || "acquire" in base) { + merge.push({ + acquire: () => [ + ...(typeof opt.acquire === "function" ? opt.acquire() : [...(opt.acquire ?? [])]), + ...(typeof base.acquire === "function" ? base.acquire() : [...(base.acquire ?? [])]), + ], + }); + } + if ("effects" in opt || "effects" in base) { + merge.push({ + effects: () => [ + ...(typeof opt.effects === "function" ? opt.effects() : [...(opt.effects ?? [])]), + ...(typeof base.effects === "function" ? base.effects() : [...(base.effects ?? [])]), + ], + }); + } + if ("choices" in opt || "choices" in base) { + merge.push({ + choices: { ...(base.choices ?? {}), ...(opt.choices ?? {}) }, + }); + } + if ("limit" in opt || "limit" in base) { + merge.push({ + limit: { ...(base.limit ?? {}), ...(opt.limit ?? {}) }, + }); + } + if ("outfit" in opt || "outfit" in base) { + const afun = + typeof opt.outfit === "function" + ? opt.outfit + : () => (opt.outfit instanceof Outfit ? opt.outfit.spec() : opt.outfit ?? {}); + const bfun = + typeof base.outfit === "function" + ? base.outfit + : () => (base.outfit instanceof Outfit ? base.outfit.spec() : base.outfit ?? {}); + merge.push({ + outfit: () => { + return { + ...afun(), + ...bfun(), + }; + }, + }); + } + if ("combat" in opt || "combat" in base) { + const task: Partial> = { + combat: opt.combat ?? base.combat, + }; + merge.push(task); + } + return validate( + merge.reduce((p, a) => { + return { ...a, ...p }; + }) + ); +} + +export function override(base: Partial, options: Partial): Task { + return validate({ ...base, ...options }); +} \ No newline at end of file diff --git a/src/partials/wanderers.ts b/src/partials/wanderers.ts new file mode 100644 index 0000000..2a92a27 --- /dev/null +++ b/src/partials/wanderers.ts @@ -0,0 +1,34 @@ +import { adv1 } from "kolmafia"; +import { $item, get, have } from "libram"; +import { Task } from "../task"; + +export const ProtonGhost: Task = { + name: "Proton Ghost", + ready: () => { + const location = get("ghostLocation"); + if (location) { + return ( + have($item`protonic accelerator pack`) && + get("questPAGhost") !== "unstarted" && + !!get("ghostLocation") + ); + } + return false; + }, + completed: () => get("questPAGhost") === "unstarted", + do: (): void => { + const location = get("ghostLocation"); + if (location) { + adv1(location, 0, ""); + } else { + throw "Could not determine Proton Ghost location!"; + } + }, +}; + +export const VoidMonster: Omit = { + name: "Void Monster", + ready: () => have($item`cursed magnifying glass`) && get("cursedMagnifyingGlassCount") === 13, + completed: () => get("_voidFreeFights") >= 5, + outfit: { offhand: $item`cursed magnifying glass` }, +}; diff --git a/src/task.ts b/src/task.ts index e2262f1..ef9db37 100644 --- a/src/task.ts +++ b/src/task.ts @@ -20,6 +20,8 @@ export type AcquireItem = { get?: () => void; }; +export const TaskRequiredProperties = ["completed", "do", "name"] as const; + export type Task = { name: string; after?: string[];