Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make tearaway pants a viable wanderer target #2123

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions packages/garbo-lib/src/wanderer/eightbit.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { eightBitPoints, Location } from "kolmafia";
import { DraggableFight, WandererFactoryOptions, WandererTarget } from "./lib";
import { $item, $location, get, have } from "libram";
import { $item, $location, $slot, get, have } from "libram";

export const bonusColor = ["black", "blue", "green", "red"] as const;
export type BonusColor = (typeof bonusColor)[number];
Expand All @@ -22,7 +22,8 @@ function value(color: BonusColor, options: WandererFactoryOptions) {
return (
(options.itemValue($item`fat loot token`) *
eightBitPoints(locationColor[color])) /
denominator
denominator -
options.slotCost($slot`off-hand`)
);
}

Expand Down
54 changes: 43 additions & 11 deletions packages/garbo-lib/src/wanderer/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Effect,
inebrietyLimit,
isDarkMode,
Item,
Expand All @@ -7,6 +8,7 @@ import {
myInebriety,
myTotalTurnsSpent,
print,
Slot,
totalTurnsPlayed,
} from "kolmafia";
import {
Expand Down Expand Up @@ -37,6 +39,7 @@ import { lovebugsFactory } from "./lovebugs";
import { freefightFactory } from "./freefight";
import { eightbitFactory } from "./eightbit";
import { gingerbreadFactory } from "./gingerbreadcity";
import { tearawayPantsFactory } from "./tearawaypants";

export type { DraggableFight };

Expand All @@ -54,6 +57,7 @@ const wanderFactories: WandererFactory[] = [
guzzlrFactory,
eightbitFactory,
gingerbreadFactory,
tearawayPantsFactory,
];

function bestWander(
Expand Down Expand Up @@ -134,20 +138,31 @@ function wanderWhere(
return candidate.location;
}
}
export type WanderOptions = {
export type WanderOptions<T extends string> = {
wanderer: DraggableFight;
drunkSafe?: boolean;
allowEquipment?: boolean;
mode?: T;
};

export type WanderDetails = DraggableFight | WanderOptions;
export type WanderDetails<T extends string> = DraggableFight | WanderOptions<T>;

const defaultWanderOptions = {
drunkSafe: true,
allowEquipment: true,
};

export class WandererManager {
export type WandererManagerOptions<T extends string> = {
[k in keyof WandererFactoryOptions]: k extends
| "digitizesRemaining"
| "estimatedTurns"
? WandererFactoryOptions[k]
: WandererFactoryOptions[k] extends (...args: infer A) => infer R
? (mode: T | null, ...args: A) => R
: WandererFactoryOptions[k];
};

export class WandererManager<T extends string = never> {
private unsupportedChoices = new Map<
Location,
Delayed<
Expand Down Expand Up @@ -244,13 +259,25 @@ export class WandererManager {

cacheKey = "";
targets: Partial<{ [x in `${DraggableFight}:${boolean}`]: Location }> = {};
options: WandererFactoryOptions;
options: WandererManagerOptions<T>;

constructor(options: WandererFactoryOptions) {
constructor(options: WandererManagerOptions<T>) {
this.options = options;
}

getTarget(wanderer: WanderDetails): Location {
getOptions(mode: T | null = null): WandererFactoryOptions {
return {
...this.options,
freeFightExtraValue: (loc: Location) =>
this.options.freeFightExtraValue(mode, loc),
itemValue: (item: Item) => this.options.itemValue(mode, item),
effectValue: (effect: Effect, duration: number) =>
this.options.effectValue(mode, effect, duration),
slotCost: (slot: Slot) => this.options.slotCost(mode, slot),
};
}

getTarget(wanderer: WanderDetails<T>): Location {
const { draggableFight, options } = isDraggableFight(wanderer)
? { draggableFight: wanderer, options: {} }
: { draggableFight: wanderer.wanderer, options: wanderer };
Expand All @@ -268,7 +295,7 @@ export class WandererManager {

return sober() || !drunkSafe
? (this.targets[`${draggableFight}:${allowEquipment}`] ??= wanderWhere(
this.options,
this.getOptions(options.mode),
draggableFight,
[],
locationSkipList,
Expand All @@ -283,20 +310,25 @@ export class WandererManager {
* @returns Map of choice numbers to decisions
*/
getChoices(
target: WanderDetails | Location,
target: WanderDetails<T> | Location,
takeTurnForProfit = this.options.takeTurnForProfit,
): {
[choice: number]: string | number;
} {
const location =
target instanceof Location ? target : this.getTarget(target);
const mode =
target instanceof Location || isDraggableFight(target)
? null
: target.mode;
const options = this.getOptions(mode);
const valueOfTurn = takeTurnForProfit
? (this.options.valueOfAdventure ?? 0) +
sum(getActiveEffects(), (e) => this.options.effectValue(e, 1))
sum(getActiveEffects(), (e) => options.effectValue(e, 1))
: Infinity;
return undelay(
this.unsupportedChoices.get(location) ?? {},
this.options,
options,
valueOfTurn,
);
}
Expand All @@ -305,7 +337,7 @@ export class WandererManager {
this.targets = {};
}

getEquipment(wanderer: WanderDetails): Item[] {
getEquipment(wanderer: WanderDetails<T>): Item[] {
return this.equipment.get(this.getTarget(wanderer)) ?? [];
}
}
2 changes: 2 additions & 0 deletions packages/garbo-lib/src/wanderer/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
Location,
Monster,
numericFact,
Slot,
toItem,
use,
} from "kolmafia";
Expand Down Expand Up @@ -59,6 +60,7 @@ export type WandererFactoryOptions = {
digitzesRemaining?: (turns: number) => number;
valueOfAdventure?: number;
takeTurnForProfit?: boolean;
slotCost: (slot: Slot) => number;
};

export type WandererFactory = (
Expand Down
31 changes: 31 additions & 0 deletions packages/garbo-lib/src/wanderer/tearawaypants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { canAdventure, Location } from "kolmafia";
import { DraggableFight, WandererFactoryOptions, WandererTarget } from "./lib";
import { $locations, $slot, get, TearawayPants } from "libram";

const VALID_DRAGGABLE_TYPES: DraggableFight[] = [
"backup",
"yellow ray",
"freerun",
];

export function tearawayPantsFactory(
type: DraggableFight,
locationSkiplist: Location[],
options: WandererFactoryOptions,
): WandererTarget[] {
if (!VALID_DRAGGABLE_TYPES.includes(type) || !TearawayPants.have()) {
return [];
}
return $locations`The Fun-Guy Mansion, The Fungal Nethers`
.filter((l) => canAdventure(l) && !locationSkiplist.includes(l))
.map(
(l) =>
new WandererTarget(
`Tearaway Pants ${l}`,
l,
TearawayPants.plantsAdventureChance() *
(options.valueOfAdventure ?? get("valueOfAdventure")) -
options.slotCost($slot`pants`),
),
);
}
2 changes: 1 addition & 1 deletion packages/garbo/src/familiar/barfFamiliar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ function getCachedOutfitValues(fam: Familiar) {
).dress();

const outfit = outfitSlots.map((slot) => equippedItem(slot));
const bonuses = bonusGear(BonusEquipMode.MEAT_TARGET, false);
const bonuses = bonusGear(BonusEquipMode.MEAT_TARGET, 0, false);

const values = {
weight: sum(outfit, (eq: Item) => getModifier("Familiar Weight", eq)),
Expand Down
14 changes: 8 additions & 6 deletions packages/garbo/src/garboWanderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,18 @@ import { Potion } from "./potions";
import { copyTargetCount } from "./target/fights";
import { digitizedMonstersRemainingForTurns } from "./lib";

let _wanderer: WandererManager | undefined;
export function wanderer(): WandererManager {
type WandererMode = "free" | "target";
let _wanderer: WandererManager<WandererMode> | undefined;
export function wanderer(): WandererManager<WandererMode> {
if (!_wanderer) {
_wanderer = new WandererManager({
_wanderer = new WandererManager<WandererMode>({
ascend: globalOptions.ascend,
estimatedTurns: estimatedGarboTurns,
itemValue: garboValue,
effectValue: (effect: Effect, duration: number) =>
itemValue: (_, item) => garboValue(item),
effectValue: (_, effect: Effect, duration: number) =>
new Potion($item.none, { effect, duration }).gross(copyTargetCount()),
prioritizeCappingGuzzlr: get("garbo_prioritizeCappingGuzzlr", false),
freeFightExtraValue: (location: Location) =>
freeFightExtraValue: (_, location: Location) =>
freeFightFamiliarData({ location }).expectedValue,
digitzesRemaining: digitizedMonstersRemainingForTurns,
plentifulMonsters: [
Expand All @@ -32,6 +33,7 @@ export function wanderer(): WandererManager {
],
valueOfAdventure: get("valueOfAdventure"),
takeTurnForProfit: true,
slotCost: () => 0,
});
}
return _wanderer;
Expand Down
2 changes: 1 addition & 1 deletion packages/garbo/src/outfit/barf.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export function computeBarfOutfit(
outfit.equip($item`Kramco Sausage-o-Matic™`);
}

outfit.bonuses = bonusGear(BonusEquipMode.BARF, !sim);
outfit.bonuses = bonusGear(BonusEquipMode.BARF, 0, !sim);
const bjornalike = bestBjornalike(outfit);
if (bjornalike) {
outfit.setBonus(bjornalike, bjornChoice.value);
Expand Down
1 change: 1 addition & 0 deletions packages/garbo/src/outfit/dropsgear.ts
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ function simpleTargetCrits(mode: BonusEquipMode): Map<Item, number> {

export function bonusGear(
mode: BonusEquipMode,
plantRate: number,
valueCircumstantialBonus = true,
): Map<Item, number> {
return new Map<Item, number>([
Expand Down
43 changes: 39 additions & 4 deletions packages/garbo/src/outfit/free.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { Outfit, OutfitSpec } from "grimoire-kolmafia";
import { Location, toJson } from "kolmafia";
import { $familiar, $item, $items, get, Guzzlr, SourceTerminal } from "libram";
import { appearanceRates, Location, toJson } from "kolmafia";
import {
$familiar,
$item,
$items,
$monster,
$phylum,
get,
Guzzlr,
SourceTerminal,
sum,
} from "libram";
import { WanderDetails } from "garbo-lib";

import { freeFightFamiliar } from "../familiar";
Expand All @@ -9,7 +19,11 @@ import { wanderer } from "../garboWanderer";

import { chooseBjorn } from "./bjorn";
import { bonusGear } from "./dropsgear";
import { cleaverCheck, validateGarbageFoldable } from "./lib";
import {
cleaverCheck,
extractDraggableType,
validateGarbageFoldable,
} from "./lib";

type MenuOptions = {
canChooseMacro?: boolean;
Expand Down Expand Up @@ -66,7 +80,28 @@ export function freeFightOutfit(
if (get("_vampyreCloakeFormUses") < 10) {
outfit.setBonus($item`vampyric cloake`, 500);
}
bonusGear(mode).forEach((value, item) => outfit.addBonus(item, value));

const dragType = options.wanderOptions
? extractDraggableType(options.wanderOptions)
: null;

const location =
(options.location ?? options.wanderOptions)
? wanderer().getTarget(options.wanderOptions)
: null;
const plantRate =
location && [null, "backup", "wanderer"].includes(dragType)
? 0
: sum(Object.entries(appearanceRates(location)), ([monster, rate]) =>
$monster.get(monster)?.phylum === $phylum`plant` ? rate : 0,
) /
sum(Object.entries(appearanceRates(location)), ([monster, rate]) =>
$monster.get(monster) ? rate : 0,
);

bonusGear(mode, plantRate).forEach((value, item) =>
outfit.addBonus(item, value),
);

if (outfit.familiar !== $familiar`Grey Goose`) {
outfit.setBonus($item`tiny stillsuit`, 500);
Expand Down
6 changes: 6 additions & 0 deletions packages/garbo/src/outfit/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { copyTargetCount } from "../target";
import { meatFamiliar } from "../familiar";
import { targetMeat } from "../lib";
import { digitizedMonstersRemaining } from "../turns";
import { DraggableFight, WanderDetails } from "garbo-lib";

export function bestBjornalike(outfit: Outfit): Item | null {
const bjornalikes = $items`Buddy Bjorn, Crown of Thrones`.filter((item) =>
Expand Down Expand Up @@ -163,3 +164,8 @@ export function validateGarbageFoldable(spec: OutfitSpec): void {
}
}
}

export function extractDraggableType(details: WanderDetails): DraggableFight {
if (typeof details === "string") return details;
return details.wanderer;
}
2 changes: 2 additions & 0 deletions packages/garbo/src/outfit/target.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
$item,
$items,
$location,
$phylum,
Environment,
Guzzlr,
have,
Expand Down Expand Up @@ -71,6 +72,7 @@ export function meatTargetOutfit(

outfit.bonuses = bonusGear(
targettingMeat() ? BonusEquipMode.MEAT_TARGET : BonusEquipMode.FREE,
globalOptions.target.phylum === $phylum`plant` ? 1 : 0,
);
const bjornalike = bestBjornalike(outfit);

Expand Down
Loading