libram 0.5.4 → 0.5.5

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.
@@ -3,7 +3,9 @@ import { Macro } from "../combat";
3
3
  import { Requirement } from "../maximize";
4
4
  import { sum } from "../utils";
5
5
  function mergeConstraints(...allConstraints) {
6
- const familiars = allConstraints.map((constraints) => constraints.familiar);
6
+ const familiars = allConstraints
7
+ .map((constraints) => constraints.familiar)
8
+ .filter((familiar) => familiar);
7
9
  if (familiars.length > 1) {
8
10
  // Inconsistent requirements.
9
11
  return null;
@@ -20,7 +22,7 @@ function mergeConstraints(...allConstraints) {
20
22
  }
21
23
  return success;
22
24
  },
23
- familiar: familiars[0],
25
+ familiar: familiars.find((familiar) => familiar),
24
26
  cost: () => sum(allConstraints, (constraints) => constraints.cost?.() ?? 0),
25
27
  };
26
28
  }
@@ -138,7 +140,8 @@ function filterAction(action, constraints) {
138
140
  export function findActionSource(actions, constraints = {}) {
139
141
  return (actions
140
142
  .filter((actions) => filterAction(actions, constraints))
141
- .sort((a, b) => a.cost() - b.cost())[0] ?? null);
143
+ .sort((a, b) => a.cost() - b.cost())
144
+ .find((action) => action) ?? null);
142
145
  }
143
146
  /**
144
147
  * Count available action sources subject to constraints. Note that, if
@@ -3,14 +3,16 @@ import { Macro } from "../combat";
3
3
  import { ensureEffect, getFoldGroup, getSongCount, getSongLimit, have, } from "../lib";
4
4
  import { Requirement } from "../maximize";
5
5
  import { get } from "../property";
6
- import { $effect, $familiar, $item, $items, $skill } from "../template-string";
6
+ import { $effect, $item, $items, $skill } from "../template-string";
7
7
  import { ActionSource, findActionSource, } from "./ActionSource";
8
8
  import * as Bandersnatch from "../resources/2009/Bandersnatch";
9
+ import * as StompingBoots from "../resources/2011/StompingBoots";
9
10
  import * as AsdonMartin from "../resources/2017/AsdonMartin";
10
11
  // Value of _lastCombatStarted the last time we updated scrapbook charges.
11
12
  let scrapbookChargesLastUpdated = get("_lastCombatStarted");
12
13
  // Free unlimited source every 30 turns.
13
14
  // Does not work on special monsters so needs a backup, see tryFindFreeRun.
15
+ // banishedMonsters isn't updated if the free run succeeds on an unbanishable monster
14
16
  const asdonMartinSource = new ActionSource($skill `Asdon Martin: Spring-Loaded Front Bumper`, () => {
15
17
  if (!AsdonMartin.installed())
16
18
  return 0;
@@ -21,29 +23,25 @@ const asdonMartinSource = new ActionSource($skill `Asdon Martin: Spring-Loaded F
21
23
  if (bumperIndex === -1)
22
24
  return 1;
23
25
  return myTurncount() - parseInt(banishes[bumperIndex + 1]) > 30 ? 1 : 0;
24
- }, Macro.skill($skill `Asdon Martin: Spring-Loaded Front Bumper`), {
26
+ }, Macro.trySkill($skill `Asdon Martin: Spring-Loaded Front Bumper`), {
25
27
  preparation: () => AsdonMartin.fillTo(50),
26
28
  });
27
29
  const freeRunSources = [
28
30
  // Free limited sources
29
- new ActionSource($familiar `Frumious Bandersnatch`, () => have($familiar `Frumious Bandersnatch`) &&
30
- (have($effect `Ode to Booze`) || getSongCount() < getSongLimit()) &&
31
- Bandersnatch.getRemainingRunaways() > 0
32
- ? 0
31
+ new ActionSource(Bandersnatch.familiar, () => (have($effect `Ode to Booze`) || getSongCount() < getSongLimit()) &&
32
+ Bandersnatch.couldRunaway()
33
+ ? Bandersnatch.getRemainingRunaways()
33
34
  : 0, Macro.step("runaway"), {
34
35
  equipmentRequirements: () => new Requirement(["Familiar Weight"], {}),
35
36
  preparation: () => {
36
37
  ensureEffect($effect `Ode to Booze`);
37
38
  return have($effect `Ode to Booze`);
38
39
  },
39
- familiar: () => $familiar `Frumious Bandersnatch`,
40
+ familiar: () => Bandersnatch.familiar,
40
41
  }),
41
- new ActionSource($familiar `Pair of Stomping Boots`, () => have($familiar `Pair of Stomping Boots`) &&
42
- Bandersnatch.getRemainingRunaways() > 0
43
- ? 0
44
- : 0, Macro.step("runaway"), {
42
+ new ActionSource(StompingBoots.familiar, () => StompingBoots.couldRunaway() ? StompingBoots.getRemainingRunaways() : 0, Macro.step("runaway"), {
45
43
  equipmentRequirements: () => new Requirement(["Familiar Weight"], {}),
46
- familiar: () => $familiar `Pair of Stomping Boots`,
44
+ familiar: () => StompingBoots.familiar,
47
45
  }),
48
46
  new ActionSource($skill `Snokebomb`, () => (have($skill `Snokebomb`) ? 3 - get("_snokebombUsed") : 0), Macro.skill($skill `Snokebomb`), {
49
47
  preparation: () => restoreMp(50),
@@ -1,7 +1,7 @@
1
1
  export declare const familiar: Familiar;
2
2
  /**
3
3
  * Returns true if the player has the Frumious Bandersnatch in their
4
- * terrariukm
4
+ * terrarium
5
5
  */
6
6
  export declare function have(): boolean;
7
7
  /**
@@ -38,7 +38,7 @@ export declare function canRunaway(): boolean;
38
38
  /**
39
39
  * Prepare a Bandersnatch runaway.
40
40
  *
41
- * This will cast Ode to Booze and equip take your Bandersnatch with you.
41
+ * This will cast Ode to Booze and take your Bandersnatch with you.
42
42
  * If any of those steps fail, it will return false.
43
43
  *
44
44
  * @param songsToRemove Ordered list of songs that could be shrugged to make room for Ode to Booze
@@ -5,7 +5,7 @@ import { have as _have, canRememberSong, getActiveSongs, isCurrentFamiliar, unef
5
5
  export const familiar = $familiar `Frumious Bandersnatch`;
6
6
  /**
7
7
  * Returns true if the player has the Frumious Bandersnatch in their
8
- * terrariukm
8
+ * terrarium
9
9
  */
10
10
  export function have() {
11
11
  return _have(familiar);
@@ -57,7 +57,7 @@ export function canRunaway() {
57
57
  /**
58
58
  * Prepare a Bandersnatch runaway.
59
59
  *
60
- * This will cast Ode to Booze and equip take your Bandersnatch with you.
60
+ * This will cast Ode to Booze and take your Bandersnatch with you.
61
61
  * If any of those steps fail, it will return false.
62
62
  *
63
63
  * @param songsToRemove Ordered list of songs that could be shrugged to make room for Ode to Booze
@@ -0,0 +1,44 @@
1
+ export declare const familiar: Familiar;
2
+ /**
3
+ * Returns true if the player has the Pair of Stomping Boots in their
4
+ * terrarium
5
+ */
6
+ export declare function have(): boolean;
7
+ /**
8
+ * Returns the number of free runaways that have already been used
9
+ * @see Bandersnatch with which the Stomping Boots shares a counter
10
+ */
11
+ export declare function getRunaways(): number;
12
+ /**
13
+ * Returns the total number of free runaways that the player can
14
+ * get from their Stomping Boots
15
+ *
16
+ * @param considerWeightAdjustment Include familiar weight modifiers
17
+ */
18
+ export declare function getMaxRunaways(considerWeightAdjustment?: boolean): number;
19
+ /**
20
+ * Returns the number of remaining free runaways the player can
21
+ * get from their Stomping Boots
22
+ *
23
+ * @param considerWeightAdjustment
24
+ */
25
+ export declare function getRemainingRunaways(considerWeightAdjustment?: boolean): number;
26
+ /**
27
+ * Returns true if the player could use their Stomping Boots to
28
+ * get a free run in theory
29
+ *
30
+ * @param considerWeightAdjustment Include familiar weight modifiers
31
+ */
32
+ export declare function couldRunaway(considerWeightAdjustment?: boolean): boolean;
33
+ /**
34
+ * Returns true if the player can use their Stomping Boots to get a
35
+ * free run right now
36
+ */
37
+ export declare function canRunaway(): boolean;
38
+ /**
39
+ * Prepare a Stomping Boots runaway.
40
+ *
41
+ * This will take your Stomping Boots with you.
42
+ * If any of those steps fail, it will return false.
43
+ */
44
+ export declare function prepareRunaway(): boolean;
@@ -0,0 +1,63 @@
1
+ import { familiarWeight, useFamiliar, weightAdjustment } from "kolmafia";
2
+ import { get } from "../../property";
3
+ import { $familiar } from "../../template-string";
4
+ import { have as _have, isCurrentFamiliar } from "../../lib";
5
+ export const familiar = $familiar `Pair of Stomping Boots`;
6
+ /**
7
+ * Returns true if the player has the Pair of Stomping Boots in their
8
+ * terrarium
9
+ */
10
+ export function have() {
11
+ return _have(familiar);
12
+ }
13
+ /**
14
+ * Returns the number of free runaways that have already been used
15
+ * @see Bandersnatch with which the Stomping Boots shares a counter
16
+ */
17
+ export function getRunaways() {
18
+ return get("_banderRunaways");
19
+ }
20
+ /**
21
+ * Returns the total number of free runaways that the player can
22
+ * get from their Stomping Boots
23
+ *
24
+ * @param considerWeightAdjustment Include familiar weight modifiers
25
+ */
26
+ export function getMaxRunaways(considerWeightAdjustment = true) {
27
+ const weightBuffs = considerWeightAdjustment ? weightAdjustment() : 0;
28
+ return Math.floor((familiarWeight(familiar) + weightBuffs) / 5);
29
+ }
30
+ /**
31
+ * Returns the number of remaining free runaways the player can
32
+ * get from their Stomping Boots
33
+ *
34
+ * @param considerWeightAdjustment
35
+ */
36
+ export function getRemainingRunaways(considerWeightAdjustment = true) {
37
+ return Math.max(0, getMaxRunaways(considerWeightAdjustment) - getRunaways());
38
+ }
39
+ /**
40
+ * Returns true if the player could use their Stomping Boots to
41
+ * get a free run in theory
42
+ *
43
+ * @param considerWeightAdjustment Include familiar weight modifiers
44
+ */
45
+ export function couldRunaway(considerWeightAdjustment = true) {
46
+ return have() && getRemainingRunaways(considerWeightAdjustment) > 0;
47
+ }
48
+ /**
49
+ * Returns true if the player can use their Stomping Boots to get a
50
+ * free run right now
51
+ */
52
+ export function canRunaway() {
53
+ return isCurrentFamiliar(familiar) && couldRunaway();
54
+ }
55
+ /**
56
+ * Prepare a Stomping Boots runaway.
57
+ *
58
+ * This will take your Stomping Boots with you.
59
+ * If any of those steps fail, it will return false.
60
+ */
61
+ export function prepareRunaway() {
62
+ return useFamiliar(familiar);
63
+ }
@@ -2,6 +2,12 @@ import "core-js/modules/es.object.values";
2
2
  import { canInteract, cliExecute, getFuel, getWorkshed, haveEffect, historicalAge, historicalPrice, isNpcItem, mallPrice, mallPrices, retrieveItem, toInt, visitUrl, } from "kolmafia";
3
3
  import { getAverageAdventures, have as haveItem } from "../../lib";
4
4
  import { $effect, $item, $items } from "../../template-string";
5
+ var PriceAge;
6
+ (function (PriceAge) {
7
+ PriceAge[PriceAge["HISTORICAL"] = 0] = "HISTORICAL";
8
+ PriceAge[PriceAge["RECENT"] = 1] = "RECENT";
9
+ PriceAge[PriceAge["TODAY"] = 2] = "TODAY";
10
+ })(PriceAge || (PriceAge = {}));
5
11
  /**
6
12
  * Returns whether or not we have the Asdon installed in the workshed at present.
7
13
  */
@@ -18,13 +24,34 @@ const fuelSkiplist = $items `cup of "tea", thermos of "whiskey", Lucky Lindy, Be
18
24
  function priceTooOld(item) {
19
25
  return historicalPrice(item) === 0 || historicalAge(item) >= 7;
20
26
  }
21
- function price(item) {
22
- return priceTooOld(item) ? mallPrice(item) : historicalPrice(item);
27
+ // Return mall max if historicalPrice returns -1.
28
+ function historicalPriceOrMax(item) {
29
+ const historical = historicalPrice(item);
30
+ return historical < 0 ? 999999999 : historical;
31
+ }
32
+ // Return mall max if mallPrice returns -1.
33
+ function mallPriceOrMax(item) {
34
+ const mall = mallPrice(item);
35
+ return mall < 0 ? 999999999 : mall;
36
+ }
37
+ function price(item, priceAge) {
38
+ switch (priceAge) {
39
+ case PriceAge.HISTORICAL: {
40
+ const historical = historicalPriceOrMax(item);
41
+ return historical === 0 ? mallPriceOrMax(item) : historical;
42
+ }
43
+ case PriceAge.RECENT:
44
+ return priceTooOld(item)
45
+ ? mallPriceOrMax(item)
46
+ : historicalPriceOrMax(item);
47
+ case PriceAge.TODAY:
48
+ return mallPriceOrMax(item);
49
+ }
23
50
  }
24
51
  // Efficiency in meat per fuel.
25
- function calculateFuelEfficiency(it, targetUnits, usePrecisePrice = false) {
52
+ function calculateFuelUnitCost(it, targetUnits, priceAge = PriceAge.RECENT) {
26
53
  const units = getAverageAdventures(it);
27
- return ((usePrecisePrice ? price(it) : mallPrice(it)) / Math.min(targetUnits, units));
54
+ return price(it, priceAge) / Math.min(targetUnits, units);
28
55
  }
29
56
  function isFuelItem(it) {
30
57
  return (!isNpcItem(it) &&
@@ -34,21 +61,33 @@ function isFuelItem(it) {
34
61
  it.discardable &&
35
62
  !fuelSkiplist.includes(it));
36
63
  }
37
- const potentialFuel = $items ``.filter(isFuelItem);
38
64
  function getBestFuel(targetUnits) {
39
- if (potentialFuel.filter(priceTooOld).length > 100) {
65
+ // Three stages.
66
+ // 1. Filter to reasonable items using historical cost (within 5x of historical best).
67
+ const allFuel = $items ``.filter(isFuelItem);
68
+ if (allFuel.filter((item) => historicalPrice(item) === 0).length > 100) {
69
+ mallPrices("food");
70
+ mallPrices("booze");
71
+ }
72
+ const keyHistorical = (item) => calculateFuelUnitCost(item, targetUnits, PriceAge.HISTORICAL);
73
+ allFuel.sort((x, y) => keyHistorical(x) - keyHistorical(y));
74
+ const bestUnitCost = keyHistorical(allFuel[0]);
75
+ const firstBadIndex = allFuel.findIndex((item) => keyHistorical(item) > 5 * bestUnitCost);
76
+ const potentialFuel = firstBadIndex > 0 ? allFuel.slice(0, firstBadIndex) : allFuel;
77
+ // 2. Filter to top 10 candidates using prices at most a week old.
78
+ if (potentialFuel.filter((item) => priceTooOld(item)).length > 100) {
40
79
  mallPrices("food");
41
80
  mallPrices("booze");
42
81
  }
43
82
  const key1 = (item) => -getAverageAdventures(item);
44
- const key2 = (item) => calculateFuelEfficiency(item, targetUnits);
83
+ const key2 = (item) => calculateFuelUnitCost(item, targetUnits, PriceAge.RECENT);
45
84
  potentialFuel.sort((x, y) => key1(x) - key1(y));
46
85
  potentialFuel.sort((x, y) => key2(x) - key2(y));
47
- // Get precise price for the top candidates.
86
+ // 3. Find result using precise price for those top candidates.
48
87
  const candidates = potentialFuel.slice(0, 10);
49
- const key3 = (item) => calculateFuelEfficiency(item, targetUnits, true);
88
+ const key3 = (item) => calculateFuelUnitCost(item, targetUnits, PriceAge.TODAY);
50
89
  candidates.sort((x, y) => key3(x) - key3(y));
51
- if (calculateFuelEfficiency(candidates[0], targetUnits, true) > 100) {
90
+ if (calculateFuelUnitCost(candidates[0], targetUnits, PriceAge.TODAY) > 100) {
52
91
  throw new Error("Could not identify any fuel with efficiency better than 100 meat per fuel. " +
53
92
  "This means something went wrong.");
54
93
  }
@@ -15,9 +15,10 @@ import * as Snapper from "./2019/Snapper";
15
15
  import * as SongBoom from "./2018/SongBoom";
16
16
  import * as SourceTerminal from "./2016/SourceTerminal";
17
17
  import * as SpookyPutty from "./2009/SpookyPutty";
18
+ import * as StompingBoots from "./2011/StompingBoots";
18
19
  import * as TunnelOfLove from "./2017/TunnelOfLove";
19
20
  import * as WinterGarden from "./2014/WinterGarden";
20
21
  import * as Witchess from "./2016/Witchess";
21
- export { AsdonMartin, Bandersnatch, BeachComb, ChateauMantegna, CrownOfThrones, CrystalBall, DNALab, FloristFriar, Guzzlr, Latte, MayoClinic, ObtuseAngel, RainDoh, SongBoom, SourceTerminal, Snapper, SpookyPutty, TunnelOfLove, WinterGarden, Witchess, };
22
+ export { AsdonMartin, Bandersnatch, BeachComb, ChateauMantegna, CrownOfThrones, CrystalBall, DNALab, FloristFriar, Guzzlr, Latte, MayoClinic, ObtuseAngel, RainDoh, Snapper, SongBoom, SourceTerminal, SpookyPutty, StompingBoots, TunnelOfLove, WinterGarden, Witchess, };
22
23
  export * from "./putty-likes";
23
24
  export * from "./LibramSummon";
@@ -15,9 +15,10 @@ import * as Snapper from "./2019/Snapper";
15
15
  import * as SongBoom from "./2018/SongBoom";
16
16
  import * as SourceTerminal from "./2016/SourceTerminal";
17
17
  import * as SpookyPutty from "./2009/SpookyPutty";
18
+ import * as StompingBoots from "./2011/StompingBoots";
18
19
  import * as TunnelOfLove from "./2017/TunnelOfLove";
19
20
  import * as WinterGarden from "./2014/WinterGarden";
20
21
  import * as Witchess from "./2016/Witchess";
21
- export { AsdonMartin, Bandersnatch, BeachComb, ChateauMantegna, CrownOfThrones, CrystalBall, DNALab, FloristFriar, Guzzlr, Latte, MayoClinic, ObtuseAngel, RainDoh, SongBoom, SourceTerminal, Snapper, SpookyPutty, TunnelOfLove, WinterGarden, Witchess, };
22
+ export { AsdonMartin, Bandersnatch, BeachComb, ChateauMantegna, CrownOfThrones, CrystalBall, DNALab, FloristFriar, Guzzlr, Latte, MayoClinic, ObtuseAngel, RainDoh, Snapper, SongBoom, SourceTerminal, SpookyPutty, StompingBoots, TunnelOfLove, WinterGarden, Witchess, };
22
23
  export * from "./putty-likes";
23
24
  export * from "./LibramSummon";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libram",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "JavaScript helper library for KoLmafia",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",