libram 0.9.5 → 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/Clan.js CHANGED
@@ -92,7 +92,7 @@ export class Clan {
92
92
  startingClan.join();
93
93
  }
94
94
  }
95
- static withStash(clanIdOrName, items, // eslint-disable-line @typescript-eslint/no-explicit-any, @typescript-eslint/explicit-module-boundary-types
95
+ static withStash(clanIdOrName, items, // eslint-disable-line @typescript-eslint/no-explicit-any
96
96
  callback) {
97
97
  return Clan._withStash(() => Clan.with(clanIdOrName, (clan) => clan.take(items)), (borrowed) => Clan.with(clanIdOrName, (clan) => clan.put(borrowed)), callback);
98
98
  }
@@ -1,5 +1,5 @@
1
- import { equippedItem, familiarWeight, getPower, haveEquipped, myAdventures, myBasestat, myBuffedstat, myFamiliar, myMaxhp, myThrall, myTurncount, numericModifier, print, runChoice, toSlot, visitUrl, weightAdjustment, } from "kolmafia";
2
- import { have } from "../../lib.js";
1
+ import { equippedItem, getPower, haveEquipped, myAdventures, myBasestat, myBuffedstat, myFamiliar, myMaxhp, myThrall, myTurncount, numericModifier, print, runChoice, toSlot, visitUrl, } from "kolmafia";
2
+ import { have, totalFamiliarWeight } from "../../lib.js";
3
3
  import { Requirement } from "../../maximize.js";
4
4
  import { get } from "../../property.js";
5
5
  import { MummingTrunk } from "../../resources/index.js";
@@ -18,7 +18,6 @@ const statCommunityServicePredictor = (stat) => {
18
18
  : stat)));
19
19
  };
20
20
  const visitCouncil = () => visitUrl("council.php");
21
- const baseWeight = () => have($effect `Fidoxene`) ? 20 : familiarWeight(myFamiliar());
22
21
  function hypotheticalModifier(modifier, ...effects) {
23
22
  const newEffects = effects.filter((e) => !have(e));
24
23
  return (numericModifier(modifier) +
@@ -240,7 +239,8 @@ export default class CommunityService {
240
239
  static Mysticality = new CommunityService(3, "Mysticality", "Build Playground Mazes", statCommunityServicePredictor($stat `Mysticality`), new Requirement(["Mysticality"], {}));
241
240
  static Moxie = new CommunityService(4, "Moxie", "Feed Conspirators", statCommunityServicePredictor($stat `Moxie`), new Requirement(["Moxie"], {}));
242
241
  static FamiliarWeight = new CommunityService(5, "Familiar Weight", "Breed More Collies", (...effects) => 60 -
243
- Math.floor((baseWeight() + hypotheticalModifier("Familiar Weight", ...effects)) /
242
+ Math.floor((totalFamiliarWeight(myFamiliar(), false) +
243
+ hypotheticalModifier("Familiar Weight", ...effects)) /
244
244
  5), new Requirement(["Familiar Weight"], {}));
245
245
  static WeaponDamage = new CommunityService(6, "Weapon Damage", "Reduce Gazelle Population", (...effects) => {
246
246
  const weaponPower = getPower(equippedItem($slot `weapon`));
@@ -266,7 +266,7 @@ export default class CommunityService {
266
266
  }, new Requirement(["Weapon Damage", "Weapon Damage Percent"], {}));
267
267
  static SpellDamage = new CommunityService(7, "Spell Damage", "Make Sausage", (...effects) => {
268
268
  const dragonfishDamage = myFamiliar() === $familiar `Magic Dragonfish`
269
- ? numericModifier($familiar `Magic Dragonfish`, "Spell Damage Percent", baseWeight() + weightAdjustment(), $item.none)
269
+ ? numericModifier($familiar `Magic Dragonfish`, "Spell Damage Percent", totalFamiliarWeight(), $item.none)
270
270
  : 0;
271
271
  // We add 0.001 because the floor function sometimes introduces weird rounding errors
272
272
  return (60 -
@@ -280,9 +280,9 @@ export default class CommunityService {
280
280
  const noncombatRate = -1 * hypotheticalModifier("Combat Rate", ...effects);
281
281
  const unsoftcappedRate = (rate) => rate > 25 ? 25 + (rate - 25) * 5 : rate;
282
282
  const currentFamiliarModifier = -1 *
283
- numericModifier(myFamiliar(), "Combat Rate", familiarWeight(myFamiliar()) + numericModifier("Familiar Weight"), equippedItem($slot `familiar`));
283
+ numericModifier(myFamiliar(), "Combat Rate", totalFamiliarWeight(), equippedItem($slot `familiar`));
284
284
  const newFamiliarModifier = -1 *
285
- numericModifier(myFamiliar(), "Combat Rate", familiarWeight(myFamiliar()) +
285
+ numericModifier(myFamiliar(), "Combat Rate", totalFamiliarWeight(myFamiliar(), false) +
286
286
  hypotheticalModifier("Familiar Weight", ...effects), equippedItem($slot `familiar`));
287
287
  const adjustedRate = unsoftcappedRate(noncombatRate) -
288
288
  unsoftcappedRate(currentFamiliarModifier) +
@@ -294,10 +294,10 @@ export default class CommunityService {
294
294
  const mummingBuff = mummingCostume && mummingCostume[0] === "Item Drop"
295
295
  ? mummingCostume[1]
296
296
  : 0;
297
- const familiarItemDrop = numericModifier(myFamiliar(), "Item Drop", baseWeight() + weightAdjustment(), equippedItem($slot `familiar`)) +
297
+ const familiarItemDrop = numericModifier(myFamiliar(), "Item Drop", totalFamiliarWeight(), equippedItem($slot `familiar`)) +
298
298
  mummingBuff -
299
299
  numericModifier(equippedItem($slot `familiar`), "Item Drop");
300
- const familiarBoozeDrop = numericModifier(myFamiliar(), "Booze Drop", baseWeight() + weightAdjustment(), equippedItem($slot `familiar`)) - numericModifier(equippedItem($slot `familiar`), "Booze Drop");
300
+ const familiarBoozeDrop = numericModifier(myFamiliar(), "Booze Drop", totalFamiliarWeight(), equippedItem($slot `familiar`)) - numericModifier(equippedItem($slot `familiar`), "Booze Drop");
301
301
  // Champagne doubling does NOT count for CS, so we undouble
302
302
  const multiplier = haveEquipped($item `broken champagne bottle`) &&
303
303
  get("garbageChampagneCharge") > 0
@@ -318,8 +318,8 @@ export default class CommunityService {
318
318
  preventEquip: $items `broken champagne bottle`,
319
319
  }));
320
320
  static HotRes = new CommunityService(10, "Hot Resistance", "Clean Steam Tunnels", (...effects) => {
321
- const currentFamiliarModifier = numericModifier(myFamiliar(), "Hot Resistance", familiarWeight(myFamiliar()) + numericModifier("Familiar Weight"), equippedItem($slot `familiar`));
322
- const newFamiliarModifier = numericModifier(myFamiliar(), "Hot Resistance", familiarWeight(myFamiliar()) +
321
+ const currentFamiliarModifier = numericModifier(myFamiliar(), "Hot Resistance", totalFamiliarWeight(), equippedItem($slot `familiar`));
322
+ const newFamiliarModifier = numericModifier(myFamiliar(), "Hot Resistance", totalFamiliarWeight(myFamiliar(), false) +
323
323
  hypotheticalModifier("Familiar Weight", ...effects), equippedItem($slot `familiar`));
324
324
  return (60 -
325
325
  (hypotheticalModifier("Hot Resistance", ...effects) -
@@ -22,6 +22,8 @@ export declare class MenuItem<T> {
22
22
  priceOverride?: number;
23
23
  mayo?: Item;
24
24
  data?: T;
25
+ private priceCached?;
26
+ private itemTypeCached?;
25
27
  static defaultPriceFunction: (item: Item) => number;
26
28
  static defaultOptions<T>(): Map<Item, MenuItemOptions<T>>;
27
29
  /**
@@ -41,6 +43,7 @@ export declare class MenuItem<T> {
41
43
  equals(other: MenuItem<T>): boolean;
42
44
  toString(): string;
43
45
  price(): number;
46
+ itemType(): string;
44
47
  }
45
48
  declare const organs: readonly ["food", "booze", "spleen item"];
46
49
  type Organ = (typeof organs)[number];
@@ -1,5 +1,5 @@
1
1
  import { canEquip, fullnessLimit, historicalAge, historicalPrice, inebrietyLimit, itemType, mallPrice, mallPrices, myFullness, myInebriety, myLevel, myPrimestat, mySpleenUse, npcPrice, spleenLimit, } from "kolmafia";
2
- import { have } from "../lib.js";
2
+ import { getRange, have } from "../lib.js";
3
3
  import { get as getModifier } from "../modifier.js";
4
4
  import { get } from "../property.js";
5
5
  import { Mayo, installed as mayoInstalled, } from "../resources/2015/MayoClinic.js";
@@ -10,27 +10,29 @@ function isMonday() {
10
10
  // Checking Tuesday's ruby is a hack to see if it's Monday in Arizona.
11
11
  return getModifier("Muscle Percent", $item `Tuesday's ruby`) > 0;
12
12
  }
13
+ function seasoningAdventures(item) {
14
+ const [min, max] = getRange(item.adventures);
15
+ return max - min <= 1 ? 1 : 0.5;
16
+ }
17
+ /**
13
18
  /**
14
19
  * Expected adventures from an item given a specified state
15
20
  *
16
21
  * @todo Include Salty Mouth and potentially other modifiers.
17
- * @param item Item to consider
22
+ * @param menuItem Menu item to consider
18
23
  * @param modifiers Consumption modifiers to consider
19
24
  * @returns Adventures expected
20
25
  */
21
- function expectedAdventures(item, modifiers) {
26
+ function expectedAdventures(menuItem, modifiers) {
27
+ const item = menuItem.item;
22
28
  if (item.adventures === "")
23
29
  return 0;
24
- const [min, recordedMax] = item.adventures
25
- .split(/[-]/)
26
- .map((s) => parseInt(s));
27
- const max = recordedMax ?? min;
30
+ const [min, max] = getRange(item.adventures);
28
31
  const interpolated = [...new Array(max - min + 1).keys()].map((n) => n + min);
29
- const forkMugMultiplier = (itemType(item) === "food" && item.notes?.includes("SALAD")) ||
30
- (itemType(item) === "booze" && item.notes?.includes("BEER"))
32
+ const forkMugMultiplier = (menuItem.itemType() === "food" && item.notes?.includes("SALAD")) ||
33
+ (menuItem.itemType() === "booze" && item.notes?.includes("BEER"))
31
34
  ? 1.5
32
35
  : 1.3;
33
- const seasoningAdventures = max - min <= 1 ? 1 : 0.5;
34
36
  const aioliAdventures = item.fullness;
35
37
  const garish = modifiers.garish && item.notes?.includes("LASAGNA") && !isMonday();
36
38
  const refinedPalate = modifiers.refinedPalate && item.notes?.includes("WINE");
@@ -52,13 +54,15 @@ function expectedAdventures(item, modifiers) {
52
54
  if (item.notes?.includes("MARTINI") && modifiers.tuxedoShirt) {
53
55
  adventures += 2;
54
56
  }
55
- if (itemType(item) === "food" && modifiers.mayoflex)
57
+ if (menuItem.itemType() === "food" && modifiers.mayoflex)
56
58
  adventures++;
57
- if (itemType(item) === "food" && modifiers.seasoning)
58
- adventures += seasoningAdventures;
59
- if (itemType(item) === "food" && modifiers.aioli)
59
+ if (menuItem.itemType() === "food" && modifiers.seasoning) {
60
+ adventures += seasoningAdventures(item);
61
+ }
62
+ if (menuItem.itemType() === "food" && modifiers.aioli) {
60
63
  adventures += aioliAdventures;
61
- if (itemType(item) === "food" && modifiers.whetStone)
64
+ }
65
+ if (menuItem.itemType() === "food" && modifiers.whetStone)
62
66
  adventures++;
63
67
  return adventures;
64
68
  }) / interpolated.length);
@@ -73,6 +77,8 @@ export class MenuItem {
73
77
  priceOverride;
74
78
  mayo;
75
79
  data;
80
+ priceCached;
81
+ itemTypeCached;
76
82
  static defaultPriceFunction = (item) => npcPrice(item) > 0 ? npcPrice(item) : mallPrice(item);
77
83
  static defaultOptions() {
78
84
  return new Map([
@@ -218,7 +224,17 @@ export class MenuItem {
218
224
  return this.item.toString();
219
225
  }
220
226
  price() {
221
- return this.priceOverride ?? MenuItem.defaultPriceFunction?.(this.item);
227
+ if (!this.priceCached) {
228
+ this.priceCached =
229
+ this.priceOverride ?? MenuItem.defaultPriceFunction(this.item);
230
+ }
231
+ return this.priceCached;
232
+ }
233
+ itemType() {
234
+ if (!this.itemTypeCached) {
235
+ this.itemTypeCached = itemType(this.item);
236
+ }
237
+ return this.itemTypeCached;
222
238
  }
223
239
  }
224
240
  const organs = ["food", "booze", "spleen item"];
@@ -239,6 +255,18 @@ class DietPlanner {
239
255
  whetStone;
240
256
  aioli;
241
257
  spleenValue = 0;
258
+ baseDefaultModifiers = {
259
+ forkMug: false,
260
+ seasoning: false,
261
+ whetStone: false,
262
+ aioli: false,
263
+ mayoflex: false,
264
+ refinedPalate: have($effect `Refined Palate`),
265
+ garish: have($effect `Gar-ish`),
266
+ saucemaven: have($skill `Saucemaven`),
267
+ pinkyRing: have($item `mafia pinky ring`) && canEquip($item `mafia pinky ring`),
268
+ tuxedoShirt: have($item `tuxedo shirt`) && canEquip($item `tuxedo shirt`),
269
+ };
242
270
  constructor(mpa, menu) {
243
271
  this.mpa = mpa;
244
272
  const fork = menu.find((item) => item.item === $item `Ol' Scratch's salad fork`);
@@ -301,7 +329,7 @@ class DietPlanner {
301
329
  */
302
330
  consumptionHelpersAndValue(menuItem, overrideModifiers) {
303
331
  const helpers = [];
304
- if (itemType(menuItem.item) === "food" && this.mayoLookup.size) {
332
+ if (menuItem.itemType() === "food" && this.mayoLookup.size) {
305
333
  const mayo = menuItem.mayo
306
334
  ? this.mayoLookup.get(menuItem.mayo)
307
335
  : this.mayoLookup.get(Mayo.flex);
@@ -309,55 +337,38 @@ class DietPlanner {
309
337
  helpers.push(mayo);
310
338
  }
311
339
  const defaultModifiers = {
312
- forkMug: false,
313
- seasoning: this.seasoning ? helpers.includes(this.seasoning) : false,
314
- whetStone: this.whetStone ? helpers.includes(this.whetStone) : false,
315
- aioli: this.aioli ? helpers.includes(this.aioli) : false,
340
+ ...this.baseDefaultModifiers,
316
341
  mayoflex: this.mayoLookup.size
317
342
  ? helpers.some((item) => item.item === Mayo.flex)
318
343
  : false,
319
- refinedPalate: have($effect `Refined Palate`),
320
- garish: have($effect `Gar-ish`),
321
- saucemaven: have($skill `Saucemaven`),
322
- pinkyRing: have($item `mafia pinky ring`) && canEquip($item `mafia pinky ring`),
323
- tuxedoShirt: have($item `tuxedo shirt`) && canEquip($item `tuxedo shirt`),
324
344
  ...overrideModifiers,
325
345
  };
326
346
  if (this.seasoning &&
327
- itemType(menuItem.item) === "food" &&
328
- this.mpa *
329
- (expectedAdventures(menuItem.item, {
330
- ...defaultModifiers,
331
- seasoning: true,
332
- }) -
333
- expectedAdventures(menuItem.item, {
334
- ...defaultModifiers,
335
- seasoning: false,
336
- })) >
337
- this.seasoning.price()) {
347
+ menuItem.itemType() === "food" &&
348
+ this.mpa * seasoningAdventures(menuItem.item) > this.seasoning.price()) {
338
349
  helpers.push(this.seasoning);
339
350
  }
340
351
  if (this.whetStone &&
341
- itemType(menuItem.item) === "food" &&
352
+ menuItem.itemType() === "food" &&
342
353
  this.mpa > this.whetStone.price()) {
343
354
  helpers.push(this.whetStone);
344
355
  }
345
356
  if (this.aioli &&
346
- itemType(menuItem.item) === "food" &&
357
+ menuItem.itemType() === "food" &&
347
358
  this.mpa * menuItem.item.fullness > this.aioli.price()) {
348
359
  helpers.push(this.aioli);
349
360
  }
350
- const forkMug = itemType(menuItem.item) === "food"
361
+ const forkMug = menuItem.itemType() === "food"
351
362
  ? this.fork
352
- : itemType(menuItem.item) === "booze"
363
+ : menuItem.itemType() === "booze"
353
364
  ? this.mug
354
365
  : null;
355
366
  const forkMugPrice = forkMug ? forkMug.price() : Infinity;
356
367
  const baseCost = menuItem.price() + sum(helpers, (item) => item.price());
357
- const valueRaw = expectedAdventures(menuItem.item, defaultModifiers) * this.mpa -
368
+ const valueRaw = expectedAdventures(menuItem, defaultModifiers) * this.mpa -
358
369
  baseCost +
359
370
  (menuItem.additionalValue ?? 0);
360
- const valueForkMug = expectedAdventures(menuItem.item, {
371
+ const valueForkMug = expectedAdventures(menuItem, {
361
372
  ...defaultModifiers,
362
373
  forkMug: true,
363
374
  }) *
@@ -599,7 +610,7 @@ class DietEntry {
599
610
  const mug = itemType(targetItem) === "booze" &&
600
611
  items.includes($item `Frosty's frosty mug`);
601
612
  return (this.quantity *
602
- expectedAdventures(this.menuItems[this.menuItems.length - 1].item, {
613
+ expectedAdventures(this.menuItems[this.menuItems.length - 1], {
603
614
  forkMug: fork || mug,
604
615
  seasoning: items.includes($item `Special Seasoning`),
605
616
  whetStone: items.includes($item `whet stone`),
package/dist/lib.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** @module GeneralLibrary */
2
- import { Effect, Element, Familiar, Item, Location, Monster, runCombat, Servant, Skill, Slot, Stat, Thrall } from "kolmafia";
2
+ import { Effect, Element, Familiar, Item, Location, Monster, runCombat, Servant, Skill, Slot, Stat, Thrall, Coinmaster, MafiaClass } from "kolmafia";
3
3
  /**
4
4
  * Determines the current maximum Accordion Thief songs the player can have in their head
5
5
  *
@@ -220,12 +220,18 @@ export declare function canUse(item: Item): boolean;
220
220
  * @param thing Thing that can have a mafia "none" value
221
221
  * @returns The thing specified or `null`
222
222
  */
223
- export declare function noneToNull<T>(thing: T): T | null;
223
+ export declare function noneToNull<T extends MafiaClass>(thing: T): T | null;
224
+ /**
225
+ * Parse the sort of range that KoLmafia encodes as a string
226
+ * @param range KoLmafia-style range string
227
+ * @returns Tuple of integers representing range
228
+ */
229
+ export declare function getRange(range: string): [number, number];
224
230
  /**
225
231
  * Determine the average value from the sort of range that KoLmafia encodes as a string
226
232
  *
227
233
  * @param range KoLmafia-style range string
228
- * @returns Average value fo range
234
+ * @returns Average value for range
229
235
  */
230
236
  export declare function getAverage(range: string): number;
231
237
  /**
@@ -519,4 +525,38 @@ export declare const getScalingRate: (monster: Monster) => number;
519
525
  * @returns The current scaling cap of the monster, based on your current in-game state
520
526
  */
521
527
  export declare const getScalingCap: (monster: Monster) => number;
528
+ /**
529
+ * Wrap a specified action in mafia's `batchOpen` and `batchClose`
530
+ *
531
+ * @param action Action to perform while using mafia's batching feature
532
+ * @returns The return value of the action
533
+ */
534
+ export declare function withBatch<T>(action: () => T): T;
535
+ export declare const bulkAutosell: (items: Map<Item, number>) => boolean;
536
+ export declare const bulkPutCloset: (items: Map<Item, number>) => boolean;
537
+ export declare const bulkPutDisplay: (items: Map<Item, number>) => boolean;
538
+ export declare const bulkPutStash: (items: Map<Item, number>) => boolean;
539
+ export declare const bulkTakeCloset: (items: Map<Item, number>) => boolean;
540
+ export declare const bulkTakeDisplay: (items: Map<Item, number>) => boolean;
541
+ export declare const bulkTakeShop: (items: Map<Item, number>) => boolean;
542
+ export declare const bulkTakeStash: (items: Map<Item, number>) => boolean;
543
+ export declare const bulkTakeStorage: (items: Map<Item, number>) => boolean;
544
+ export declare const bulkPutShop: (items: Map<Item, {
545
+ quantity?: number;
546
+ limit?: number;
547
+ price: number;
548
+ }>) => boolean;
549
+ export declare const bulkSell: (coinmaster: Coinmaster, items: Map<Item, number>) => boolean;
550
+ export declare const bulkRepriceShop: (items: Map<Item, {
551
+ quantity?: number;
552
+ limit?: number;
553
+ price: number;
554
+ }>) => boolean;
555
+ /**
556
+ * Calculate the total weight of a given familiar, including soup & modifiers
557
+ * @param familiar The familiar to use--defaults to your current one
558
+ * @param considerAdjustment Whether to include your `weightAdjustment` in the calculation
559
+ * @returns The total weight of the given familiar
560
+ */
561
+ export declare function totalFamiliarWeight(familiar?: Familiar, considerAdjustment?: boolean): number;
522
562
  export {};
package/dist/lib.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /** @module GeneralLibrary */
2
- import { appearanceRates, autosellPrice, availableAmount, booleanModifier, choiceFollowsFight, cliExecute, currentRound, Effect, elementalResistance, equip, equippedItem, extractItems as kolmafiaExtractItems, Familiar, fullnessLimit, getCampground, getCounters, getPlayerId, getPlayerName, getRelated, handlingChoice, haveEffect, haveFamiliar, haveServant, haveSkill, holiday, inebrietyLimit, inMultiFight, Item, Location, mallPrice, Monster, myClass, myEffects, myFamiliar, myFullness, myInebriety, myPath, myPrimestat, mySpleenUse, myThrall, myTurncount, numericModifier, Path, Servant, Skill, Slot, spleenLimit, Thrall, todayToString, toItem, toSkill, totalTurnsPlayed, visitUrl, xpath, monsterEval, } from "kolmafia";
2
+ import { appearanceRates, autosellPrice, availableAmount, booleanModifier, choiceFollowsFight, cliExecute, currentRound, Effect, elementalResistance, equip, equippedItem, extractItems as kolmafiaExtractItems, Familiar, fullnessLimit, getCampground, getCounters, getPlayerId, getPlayerName, getRelated, handlingChoice, haveEffect, haveFamiliar, haveServant, haveSkill, holiday, inebrietyLimit, inMultiFight, Item, Location, mallPrice, Monster, myClass, myEffects, myFamiliar, myFullness, myInebriety, myPath, myPrimestat, mySpleenUse, myThrall, myTurncount, numericModifier, Path, Servant, Skill, Slot, spleenLimit, Thrall, todayToString, toItem, toSkill, totalTurnsPlayed, visitUrl, xpath, monsterEval, batchOpen, batchClose, autosell, putCloset, putDisplay, putShop, putStash, sell, takeCloset, takeDisplay, takeShop, takeStash, takeStorage, repriceShop, familiarWeight, weightAdjustment, MafiaClasses, } from "kolmafia";
3
3
  import logger from "./logger.js";
4
4
  import { get } from "./property.js";
5
5
  import { $class, $effect, $element, $familiar, $item, $items, $monsters, $skill, $stat, } from "./template-string.js";
6
- import { makeByXFunction, chunk, notNull } from "./utils.js";
6
+ import { makeByXFunction, chunk, notNull, clamp } from "./utils.js";
7
7
  /**
8
8
  * Determines the current maximum Accordion Thief songs the player can have in their head
9
9
  *
@@ -362,12 +362,10 @@ export function getBanishedMonsters() {
362
362
  Item.get(`tomayohawk-style reflex hammer`),
363
363
  null,
364
364
  ].includes(banisherItem)) {
365
- if (Skill.get(banisher) === $skill.none) {
366
- break;
367
- }
368
- else {
369
- result.set(Skill.get(banisher), Monster.get(foe));
370
- }
365
+ const skill = $skill.get(banisher);
366
+ if (!skill)
367
+ continue;
368
+ result.set(skill, Monster.get(foe));
371
369
  }
372
370
  else {
373
371
  result.set(banisherItem, Monster.get(foe));
@@ -407,32 +405,30 @@ export function canUse(item) {
407
405
  * @returns The thing specified or `null`
408
406
  */
409
407
  export function noneToNull(thing) {
410
- if (thing instanceof Effect) {
411
- return thing === Effect.none ? null : thing;
412
- }
413
- if (thing instanceof Familiar) {
414
- return thing === Familiar.none ? null : thing;
415
- }
416
- if (thing instanceof Item) {
417
- return thing === Item.none ? null : thing;
418
- }
419
- return thing;
408
+ const type = MafiaClasses.find((t) => thing instanceof t);
409
+ return type && thing === type.none ? null : thing;
410
+ }
411
+ /**
412
+ * Parse the sort of range that KoLmafia encodes as a string
413
+ * @param range KoLmafia-style range string
414
+ * @returns Tuple of integers representing range
415
+ */
416
+ export function getRange(range) {
417
+ const [lower, upper] = range
418
+ .match(/^(-?\d+)(?:-(-?\d+))?$/)
419
+ ?.slice(1, 3)
420
+ .map((v) => parseInt(v)) ?? [0];
421
+ return [lower, Number.isNaN(upper) || upper === undefined ? lower : upper];
420
422
  }
421
423
  /**
422
424
  * Determine the average value from the sort of range that KoLmafia encodes as a string
423
425
  *
424
426
  * @param range KoLmafia-style range string
425
- * @returns Average value fo range
427
+ * @returns Average value for range
426
428
  */
427
429
  export function getAverage(range) {
428
- if (range.indexOf("-") < 0)
429
- return Number(range);
430
- const [, lower, upper] = range.match(/(-?[0-9]+)-(-?[0-9]+)/) ?? [
431
- "0",
432
- "0",
433
- "0",
434
- ];
435
- return (Number(lower) + Number(upper)) / 2;
430
+ const [min, max] = getRange(range);
431
+ return (min + max) / 2;
436
432
  }
437
433
  /**
438
434
  * Deternube tge average adventures expected from consuming an Item
@@ -623,6 +619,10 @@ export function findFairyMultiplier(familiar) {
623
619
  const itemBonus = numericModifier(familiar, "Item Drop", 1, $item.none);
624
620
  if (itemBonus === 0)
625
621
  return 0;
622
+ // Assumes you're using LED candle; returns effective weight multiplier
623
+ if (familiar === $familiar `Jill-of-All-Trades`)
624
+ return 1.5;
625
+ // Working out the multiplier based on the Item Drop at 1lb
626
626
  return Math.pow(Math.sqrt(itemBonus + 55 / 4 + 3) - Math.sqrt(55) / 2, 2);
627
627
  }
628
628
  export const holidayWanderers = new Map([
@@ -832,7 +832,6 @@ export function freeCrafts(type = "all") {
832
832
  : 0) +
833
833
  effectCrafts($effect `Inigo's Incantation of Inspiration`) +
834
834
  effectCrafts($effect `Craft Tea`) +
835
- // eslint-disable-next-line libram/verify-constants
836
835
  effectCrafts($effect `Cooking Concentrate`);
837
836
  const food = type === "food" ? 5 - get("_cookbookbatCrafting") : 0;
838
837
  const smith = type === "smith" ? 5 - get("_thorsPliersCrafting") : 0;
@@ -996,3 +995,114 @@ export const getScalingRate = makeScalerCalcFunction(scalerRates, SCALE_RATE_PAT
996
995
  * @returns The current scaling cap of the monster, based on your current in-game state
997
996
  */
998
997
  export const getScalingCap = makeScalerCalcFunction(scalerCaps, SCALE_CAP_PATTERN);
998
+ /**
999
+ * Wrap a specified action in mafia's `batchOpen` and `batchClose`
1000
+ *
1001
+ * @param action Action to perform while using mafia's batching feature
1002
+ * @returns The return value of the action
1003
+ */
1004
+ export function withBatch(action) {
1005
+ batchOpen();
1006
+ try {
1007
+ return action();
1008
+ }
1009
+ finally {
1010
+ batchClose();
1011
+ }
1012
+ }
1013
+ const makeBulkFunction = (action) => (items) => {
1014
+ batchOpen();
1015
+ for (const [item, quantity] of items.entries())
1016
+ action(quantity, item);
1017
+ return batchClose();
1018
+ };
1019
+ /*
1020
+ * Autosell items in bulk
1021
+ */
1022
+ export const bulkAutosell = makeBulkFunction(autosell);
1023
+ /*
1024
+ * Closet items in bulk
1025
+ * Note: each item transfer will still consume one request
1026
+ */
1027
+ export const bulkPutCloset = makeBulkFunction(putCloset);
1028
+ /*
1029
+ * Display items in bulk
1030
+ */
1031
+ export const bulkPutDisplay = makeBulkFunction(putDisplay);
1032
+ /*
1033
+ * Deposit items into your clan stash in bulk
1034
+ */
1035
+ export const bulkPutStash = makeBulkFunction(putStash);
1036
+ /*
1037
+ * Remove items from your closet in bulk
1038
+ * Note: each item transfer will still consume one request
1039
+ */
1040
+ export const bulkTakeCloset = makeBulkFunction(takeCloset);
1041
+ /*
1042
+ * Remove items from your display case in bulk
1043
+ */
1044
+ export const bulkTakeDisplay = makeBulkFunction(takeDisplay);
1045
+ /*
1046
+ * Remove items from your shop in bulk
1047
+ */
1048
+ export const bulkTakeShop = makeBulkFunction(takeShop);
1049
+ /*
1050
+ * Withdraw items from your clan stash in bulk
1051
+ * Note: each item transfer will still consume one request
1052
+ */
1053
+ export const bulkTakeStash = makeBulkFunction(takeStash);
1054
+ /*
1055
+ * Remove items from your Hagnk's in bulk
1056
+ */
1057
+ export const bulkTakeStorage = makeBulkFunction(takeStorage);
1058
+ /*
1059
+ * Mallsell items in bulk
1060
+ */
1061
+ export const bulkPutShop = (items) => {
1062
+ batchOpen();
1063
+ for (const [item, { quantity, limit, price }] of items.entries()) {
1064
+ if (quantity) {
1065
+ putShop(price, limit ?? 0, quantity, item);
1066
+ }
1067
+ else {
1068
+ putShop(price, limit ?? 0, item);
1069
+ }
1070
+ }
1071
+ return batchClose();
1072
+ };
1073
+ /*
1074
+ * Coinmaster-sell items to the same coinmaster in bulk
1075
+ */
1076
+ export const bulkSell = (coinmaster, items) => {
1077
+ batchOpen();
1078
+ for (const [item, quantity] of items.entries())
1079
+ sell(coinmaster, quantity, item);
1080
+ return batchClose();
1081
+ };
1082
+ /*
1083
+ * Reprice items in your mallstore in bulk
1084
+ */
1085
+ export const bulkRepriceShop = (items) => {
1086
+ batchOpen();
1087
+ for (const [item, { limit, price }] of items.entries()) {
1088
+ if (limit) {
1089
+ repriceShop(price, limit, item);
1090
+ }
1091
+ else {
1092
+ repriceShop(price, item);
1093
+ }
1094
+ }
1095
+ return batchClose();
1096
+ };
1097
+ /**
1098
+ * Calculate the total weight of a given familiar, including soup & modifiers
1099
+ * @param familiar The familiar to use--defaults to your current one
1100
+ * @param considerAdjustment Whether to include your `weightAdjustment` in the calculation
1101
+ * @returns The total weight of the given familiar
1102
+ */
1103
+ export function totalFamiliarWeight(familiar = myFamiliar(), considerAdjustment = true) {
1104
+ return (clamp(familiarWeight(myFamiliar()), have($effect `Fidoxene`) ? 20 : 0, Infinity) +
1105
+ familiar.soupWeight +
1106
+ (considerAdjustment ? weightAdjustment() : 0) +
1107
+ (familiar.feasted ? 10 : 0));
1108
+ }
package/dist/lib.test.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { getPlayerId, getPlayerName } from "kolmafia";
2
2
  import { describe, it, expect, vi } from "vitest";
3
- import { getPlayerFromIdOrName, getPlayerIdFromName, getPlayerNameFromId, } from "./lib.js";
3
+ import { getPlayerFromIdOrName, getPlayerIdFromName, getPlayerNameFromId, getRange, } from "./lib.js";
4
4
  vi.mocked(getPlayerName).mockImplementation((id) => {
5
5
  switch (id) {
6
6
  case 1:
@@ -140,3 +140,23 @@ describe(getPlayerFromIdOrName, () => {
140
140
  });
141
141
  });
142
142
  });
143
+ describe("getRange", () => {
144
+ it.each([
145
+ // Normal range
146
+ ["1-2", [1, 2]],
147
+ // Single number
148
+ ["1", [1, 1]],
149
+ // Negative single number
150
+ ["-1", [-1, -1]],
151
+ // Invalid
152
+ ["1-", [0, 0]],
153
+ // Negative upper bound
154
+ ["-5--1", [-5, -1]],
155
+ // Unusual order (let this slide)
156
+ ["10--1", [10, -1]],
157
+ // Ending in zero
158
+ ["-5-0", [-5, 0]],
159
+ ])("should return the range for %p", (input, expected) => {
160
+ expect(getRange(input)).toEqual(expected);
161
+ });
162
+ });
@@ -2,6 +2,7 @@ import { Item, Slot } from "kolmafia";
2
2
  export type MaximizeOptions = {
3
3
  updateOnFamiliarChange: boolean;
4
4
  updateOnCanEquipChanged: boolean;
5
+ updateOnLocationChange: boolean;
5
6
  useOutfitCaching: boolean;
6
7
  forceEquip: Item[];
7
8
  preventEquip: Item[];
package/dist/maximize.js CHANGED
@@ -1,4 +1,4 @@
1
- import { availableAmount, bjornifyFamiliar, canEquip, cliExecute, enthroneFamiliar, equip, equippedAmount, equippedItem, getProperty, haveEquipped, isWearingOutfit, Item, maximize, myBasestat, myBjornedFamiliar, myEnthronedFamiliar, myFamiliar, outfit, Slot, } from "kolmafia";
1
+ import { availableAmount, bjornifyFamiliar, canEquip, cliExecute, enthroneFamiliar, equip, equippedAmount, equippedItem, getProperty, haveEquipped, isWearingOutfit, Item, maximize, myBasestat, myBjornedFamiliar, myEnthronedFamiliar, myFamiliar, myLocation, outfit, Slot, } from "kolmafia";
2
2
  import { have } from "./lib.js";
3
3
  import logger from "./logger.js";
4
4
  import { $effect, $familiar, $item, $slot, $slots, $stats, } from "./template-string.js";
@@ -18,6 +18,7 @@ export function mergeMaximizeOptions(defaultOptions, addendums) {
18
18
  updateOnFamiliarChange: addendums.updateOnFamiliarChange ?? defaultOptions.updateOnFamiliarChange,
19
19
  updateOnCanEquipChanged: addendums.updateOnCanEquipChanged ??
20
20
  defaultOptions.updateOnCanEquipChanged,
21
+ updateOnLocationChange: addendums.updateOnLocationChange ?? defaultOptions.updateOnLocationChange,
21
22
  useOutfitCaching: addendums.useOutfitCaching ?? defaultOptions.useOutfitCaching,
22
23
  forceEquip: [...defaultOptions.forceEquip, ...(addendums.forceEquip ?? [])],
23
24
  preventEquip: [
@@ -41,6 +42,7 @@ export function mergeMaximizeOptions(defaultOptions, addendums) {
41
42
  const defaultMaximizeOptions = {
42
43
  updateOnFamiliarChange: true,
43
44
  updateOnCanEquipChanged: true,
45
+ updateOnLocationChange: false,
44
46
  useOutfitCaching: true,
45
47
  forceEquip: [],
46
48
  preventEquip: [],
@@ -420,6 +422,7 @@ export function maximizeCached(objectives, options = {}) {
420
422
  .map((slot) => `${slot}:${equippedItem(slot)}`)
421
423
  .sort(),
422
424
  have($effect `Offhand Remarkable`),
425
+ options.updateOnLocationChange && myLocation(),
423
426
  ].join("; ");
424
427
  const cacheEntry = checkCache(cacheKey, fullOptions);
425
428
  if (cacheEntry && !forceUpdate) {
@@ -473,7 +476,7 @@ export class Requirement {
473
476
  merge(other) {
474
477
  const optionsA = this.maximizeOptions;
475
478
  const optionsB = other.maximizeOptions;
476
- const optionalBooleans = mergeOptionalOptions(optionsA, optionsB, "updateOnFamiliarChange", "updateOnCanEquipChanged", "forceUpdate");
479
+ const optionalBooleans = mergeOptionalOptions(optionsA, optionsB, "updateOnFamiliarChange", "updateOnCanEquipChanged", "updateOnLocationChange", "forceUpdate");
477
480
  return new Requirement([...this.maximizeParameters, ...other.maximizeParameters], {
478
481
  ...optionalBooleans,
479
482
  forceEquip: [