libram 0.4.2 → 0.4.6

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.
Files changed (40) hide show
  1. package/dist/Clan.js +7 -4
  2. package/dist/Copier.js +5 -1
  3. package/dist/Kmail.js +18 -12
  4. package/dist/Path.js +9 -0
  5. package/dist/combat.d.ts +22 -11
  6. package/dist/combat.js +74 -19
  7. package/dist/diet/index.d.ts +9 -0
  8. package/dist/diet/index.js +191 -88
  9. package/dist/diet/knapsack.js +16 -2
  10. package/dist/freerun.js +4 -0
  11. package/dist/index.d.ts +2 -0
  12. package/dist/index.js +1 -0
  13. package/dist/lib.d.ts +2 -0
  14. package/dist/lib.js +22 -2
  15. package/dist/logger.js +1 -3
  16. package/dist/maximize.js +12 -12
  17. package/dist/mood.d.ts +5 -0
  18. package/dist/mood.js +43 -15
  19. package/dist/property.js +5 -1
  20. package/dist/propertyTypes.d.ts +5 -4
  21. package/dist/propertyTyping.d.ts +4 -3
  22. package/dist/propertyTyping.js +4 -0
  23. package/dist/resources/2010/CrownOfThrones.d.ts +3 -3
  24. package/dist/resources/2010/CrownOfThrones.js +7 -7
  25. package/dist/resources/2013/Florist.js +5 -0
  26. package/dist/resources/2014/DNALab.d.ts +47 -0
  27. package/dist/resources/2014/DNALab.js +154 -0
  28. package/dist/resources/2015/MayoClinic.d.ts +2 -0
  29. package/dist/resources/2015/MayoClinic.js +8 -2
  30. package/dist/resources/2017/AsdonMartin.d.ts +27 -0
  31. package/dist/resources/2017/AsdonMartin.js +80 -0
  32. package/dist/resources/2019/Snapper.js +2 -3
  33. package/dist/resources/2020/Guzzlr.d.ts +3 -0
  34. package/dist/resources/2020/Guzzlr.js +14 -1
  35. package/dist/resources/LibramSummon.js +2 -2
  36. package/dist/resources/index.d.ts +3 -1
  37. package/dist/resources/index.js +3 -1
  38. package/dist/utils.d.ts +5 -0
  39. package/dist/utils.js +12 -1
  40. package/package.json +14 -8
@@ -513,18 +513,18 @@ export const ridingFamiliars = [
513
513
  modifier: { ["Hot Damage"]: 20 },
514
514
  },
515
515
  ];
516
- export function valueRider(rider, modifierValueFunction, useLimitedDrops = true) {
517
- const dropValue = !rider.dropPredicate || (rider.dropPredicate() && !useLimitedDrops)
516
+ export function valueRider(rider, modifierValueFunction, ignoreLimitedDrops = false) {
517
+ const dropValue = !rider.dropPredicate || (rider.dropPredicate() && !ignoreLimitedDrops)
518
518
  ? rider.probability * rider.meatVal()
519
519
  : 0;
520
520
  const modifierValue = modifierValueFunction(rider.modifier);
521
521
  return dropValue + modifierValue;
522
522
  }
523
523
  const riderModes = new Map();
524
- export function createRiderMode(name, modifierValueFunction, useLimitedDrops = true, excludeCurrentFamiliar = true) {
524
+ export function createRiderMode(name, modifierValueFunction, ignoreLimitedDrops = false, excludeCurrentFamiliar = true) {
525
525
  return riderModes.set(name, {
526
526
  modifierValueFunction: modifierValueFunction,
527
- useLimitedDrops: useLimitedDrops,
527
+ ignoreLimitedDrops: ignoreLimitedDrops,
528
528
  excludeCurrentFamiliar: excludeCurrentFamiliar,
529
529
  });
530
530
  }
@@ -533,12 +533,12 @@ export function pickRider(mode) {
533
533
  const modeData = riderModes.get(mode);
534
534
  if (!modeData)
535
535
  return null;
536
- const { modifierValueFunction, useLimitedDrops, excludeCurrentFamiliar, } = modeData;
536
+ const { modifierValueFunction, ignoreLimitedDrops, excludeCurrentFamiliar, } = modeData;
537
537
  if (!riderLists.has(mode)) {
538
538
  riderLists.set(mode, ridingFamiliars
539
539
  .filter((rider) => have(rider.familiar))
540
- .sort((a, b) => valueRider(b, modifierValueFunction, useLimitedDrops) -
541
- valueRider(a, modifierValueFunction, useLimitedDrops)));
540
+ .sort((a, b) => valueRider(b, modifierValueFunction, ignoreLimitedDrops) -
541
+ valueRider(a, modifierValueFunction, ignoreLimitedDrops)));
542
542
  }
543
543
  const list = riderLists.get(mode);
544
544
  if (list) {
@@ -1,6 +1,11 @@
1
1
  import { floristAvailable, getFloristPlants, myLocation, visitUrl, } from "kolmafia";
2
2
  import { get } from "../../property";
3
3
  class Flower {
4
+ name;
5
+ id;
6
+ environment;
7
+ modifier;
8
+ territorial;
4
9
  constructor(name, id, environment, modifier, territorial = false) {
5
10
  this.name = name;
6
11
  this.id = id;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Checks if you have DNA lab in inventory or installed
3
+ */
4
+ export declare function have(): boolean;
5
+ /**
6
+ * Checks if you have DNA lab installed
7
+ */
8
+ export declare function installed(): boolean;
9
+ /**
10
+ * Tells you whether you are currently hybridized. When passed with an input of any sort, tells you whether you are currently hybridized with that effect.
11
+ * @param tonic Optional input. When passed, the function returns whether that specific effect is hybridized.
12
+ */
13
+ export declare function isHybridized(tonic?: Effect | Phylum | Item): boolean;
14
+ /**
15
+ * Returns the tonic item associated with a particular phylum.
16
+ * @param phylum The phylum in question.
17
+ * @returns The tonic item associated with that phylum; returns $item`none` for $phylum`none`.
18
+ */
19
+ export declare function getTonic(phylum: Phylum): Item;
20
+ /**
21
+ * Returns the tonic effect associated with a particular phylum.
22
+ * @param phylum The phylum in question.
23
+ * @returns The tonic effect associated with that phylum; returns $effect`none` for $phylum`none`.
24
+ */
25
+ export declare function getEffect(phylum: Phylum): Effect;
26
+ /**
27
+ * Tells you which phylum to hunt down for a given effect or item.
28
+ * @param dnatype The tonic effect or item in question
29
+ * @returns The Phylum associated with that effect or item; null if an invalid choice
30
+ */
31
+ export declare function phylumFor(dnatype: Effect | Item): Phylum | null;
32
+ /**
33
+ * Hybridize yourself with the current contents of your syringe, if possible.
34
+ * @returns Whether or not we succeeded
35
+ */
36
+ export declare function hybridize(): boolean;
37
+ /**
38
+ * Makes tonics with whatever phylum is currently in your syringe
39
+ * @param {number} [amount=1] the number of tonics to make
40
+ * @returns Whether we successfully made tonics; returns true if we made as many as we could, regardless of whether that was the number requested
41
+ */
42
+ export declare function makeTonic(amount?: 1 | 2 | 3): boolean;
43
+ /**
44
+ * Tells you how many tonics you can make the rest of the day.
45
+ * @returns The remaining tonics you can make
46
+ */
47
+ export declare function tonicsLeft(): number;
@@ -0,0 +1,154 @@
1
+ import { cliExecute, getWorkshed, haveEffect, itemAmount } from "kolmafia";
2
+ import { $effect, $item, $phylum } from "../../template-string";
3
+ import { have as haveItem } from "../../lib";
4
+ import { get } from "../../property";
5
+ import { get as getModifier } from "../../modifier";
6
+ import { clamp } from "../../utils";
7
+ const lab = $item `Little Geneticist DNA-Splicing Lab`;
8
+ /**
9
+ * Checks if you have DNA lab in inventory or installed
10
+ */
11
+ export function have() {
12
+ return haveItem(lab) || getWorkshed() === lab;
13
+ }
14
+ /**
15
+ * Checks if you have DNA lab installed
16
+ */
17
+ export function installed() {
18
+ return getWorkshed() === lab;
19
+ }
20
+ const phylaEffects = new Map([
21
+ [$phylum `beast`, $effect `Human-Beast Hybrid`],
22
+ [$phylum `bug`, $effect `Human-Insect Hybrid`],
23
+ [$phylum `constellation`, $effect `Human-Constellation Hybrid`],
24
+ [$phylum `construct`, $effect `Human-Machine Hybrid`],
25
+ [$phylum `demon`, $effect `Human-Demon Hybrid`],
26
+ [$phylum `dude`, $effect `Human-Human Hybrid`],
27
+ [$phylum `elemental`, $effect `Human-Elemental Hybrid`],
28
+ [$phylum `elf`, $effect `Human-Elf Hybrid`],
29
+ [$phylum `fish`, $effect `Human-Fish Hybrid`],
30
+ [$phylum `goblin`, $effect `Human-Goblin Hybrid`],
31
+ [$phylum `hippy`, $effect `Human-Hobo Hybrid`],
32
+ [$phylum `horror`, $effect `Human-Horror Hybrid`],
33
+ [$phylum `humanoid`, $effect `Human-Humanoid Hybrid`],
34
+ [$phylum `mer-kin`, $effect `Human-Mer-kin Hybrid`],
35
+ [$phylum `orc`, $effect `Human-Orc Hybrid`],
36
+ [$phylum `penguin`, $effect `Human-Penguin Hybrid`],
37
+ [$phylum `pirate`, $effect `Human-Pirate Hybrid`],
38
+ [$phylum `plant`, $effect `Human-Plant Hybrid`],
39
+ [$phylum `slime`, $effect `Human-Slime Hybrid`],
40
+ [$phylum `undead`, $effect `Human-Undead Hybrid`],
41
+ [$phylum `weird`, $effect `Human-Weird Thing Hybrid`],
42
+ ]);
43
+ const phylaTonics = new Map([
44
+ [$phylum `beast`, $item `Gene Tonic: Beast`],
45
+ [$phylum `bug`, $item `Gene Tonic: Insect`],
46
+ [$phylum `constellation`, $item `Gene Tonic: Constellation`],
47
+ [$phylum `construct`, $item `Gene Tonic: Construct`],
48
+ [$phylum `demon`, $item `Gene Tonic: Demon`],
49
+ [$phylum `dude`, $item `Gene Tonic: Humanoid`],
50
+ [$phylum `elemental`, $item `Gene Tonic: Elemental`],
51
+ [$phylum `elf`, $item `Gene Tonic: Elf`],
52
+ [$phylum `fish`, $item `Gene Tonic: Fish`],
53
+ [$phylum `goblin`, $item `Gene Tonic: Goblin`],
54
+ [$phylum `hippy`, $item `Gene Tonic: Hobo`],
55
+ [$phylum `horror`, $item `Gene Tonic: Horror`],
56
+ [$phylum `humanoid`, $item `Gene Tonic: Humanoid`],
57
+ [$phylum `mer-kin`, $item `Gene Tonic: Mer-kin`],
58
+ [$phylum `orc`, $item `Gene Tonic: Orc`],
59
+ [$phylum `penguin`, $item `Gene Tonic: Penguin`],
60
+ [$phylum `pirate`, $item `Gene Tonic: Pirate`],
61
+ [$phylum `plant`, $item `Gene Tonic: Plant`],
62
+ [$phylum `slime`, $item `Gene Tonic: Slime`],
63
+ [$phylum `undead`, $item `Gene Tonic: Undead`],
64
+ [$phylum `weird`, $item `Gene Tonic: Weird`],
65
+ ]);
66
+ const tonicEffects = Array.from(phylaEffects.values());
67
+ /**
68
+ * Tells you whether you are currently hybridized. When passed with an input of any sort, tells you whether you are currently hybridized with that effect.
69
+ * @param tonic Optional input. When passed, the function returns whether that specific effect is hybridized.
70
+ */
71
+ export function isHybridized(tonic) {
72
+ if (!tonic)
73
+ return installed() && get("_dnaHybrid");
74
+ const tonicEffect = tonic instanceof Effect
75
+ ? tonic
76
+ : tonic instanceof Phylum
77
+ ? getEffect(tonic)
78
+ : getModifier("Effect", tonic);
79
+ return (tonicEffects.includes(tonicEffect) && haveEffect(tonicEffect) === 2147483647);
80
+ }
81
+ /**
82
+ * Returns the tonic item associated with a particular phylum.
83
+ * @param phylum The phylum in question.
84
+ * @returns The tonic item associated with that phylum; returns $item`none` for $phylum`none`.
85
+ */
86
+ export function getTonic(phylum) {
87
+ return phylaTonics.get(phylum) ?? $item `none`;
88
+ //return $item`none` rather than null because it should never happen.
89
+ }
90
+ /**
91
+ * Returns the tonic effect associated with a particular phylum.
92
+ * @param phylum The phylum in question.
93
+ * @returns The tonic effect associated with that phylum; returns $effect`none` for $phylum`none`.
94
+ */
95
+ export function getEffect(phylum) {
96
+ return phylaEffects.get(phylum) ?? $effect `none`;
97
+ //return $effect`none` rather than null because it should never happen
98
+ }
99
+ /**
100
+ * Tells you which phylum to hunt down for a given effect or item.
101
+ * @param dnatype The tonic effect or item in question
102
+ * @returns The Phylum associated with that effect or item; null if an invalid choice
103
+ */
104
+ export function phylumFor(dnatype) {
105
+ if (dnatype instanceof Effect) {
106
+ const phylumPair = Array.from(phylaEffects.entries()).find(([, effect]) => effect === dnatype);
107
+ return phylumPair ? phylumPair[0] : null;
108
+ }
109
+ else {
110
+ const phylumPair = Array.from(phylaTonics.entries()).find(([, tonic]) => tonic === dnatype);
111
+ return phylumPair ? phylumPair[0] : null;
112
+ }
113
+ }
114
+ /**
115
+ * Hybridize yourself with the current contents of your syringe, if possible.
116
+ * @returns Whether or not we succeeded
117
+ */
118
+ export function hybridize() {
119
+ if (get("_dnaHybrid"))
120
+ return false;
121
+ if (!installed())
122
+ return false;
123
+ const currentSyringe = get("dnaSyringe");
124
+ if (!currentSyringe)
125
+ return false;
126
+ const tonicPotion = getTonic(currentSyringe);
127
+ const expectedEffect = getModifier("Effect", tonicPotion);
128
+ cliExecute("camp dnainject");
129
+ return isHybridized(expectedEffect);
130
+ }
131
+ /**
132
+ * Makes tonics with whatever phylum is currently in your syringe
133
+ * @param {number} [amount=1] the number of tonics to make
134
+ * @returns Whether we successfully made tonics; returns true if we made as many as we could, regardless of whether that was the number requested
135
+ */
136
+ export function makeTonic(amount = 1) {
137
+ if (!installed())
138
+ return false;
139
+ const currentSyringe = get("dnaSyringe");
140
+ if (!currentSyringe)
141
+ return false;
142
+ const tonicPotion = getTonic(currentSyringe);
143
+ const amountToMake = clamp(amount, 0, tonicsLeft());
144
+ const startingAmount = itemAmount(tonicPotion);
145
+ cliExecute(`camp dnapotion ${amountToMake}`);
146
+ return itemAmount(tonicPotion) - startingAmount === amountToMake;
147
+ }
148
+ /**
149
+ * Tells you how many tonics you can make the rest of the day.
150
+ * @returns The remaining tonics you can make
151
+ */
152
+ export function tonicsLeft() {
153
+ return clamp(3 - get("_dnaPotionsMade"), 0, 3);
154
+ }
@@ -5,6 +5,8 @@ export declare const Mayo: {
5
5
  zapine: Item;
6
6
  flex: Item;
7
7
  };
8
+ export declare function installed(): boolean;
9
+ export declare function have(): boolean;
8
10
  /**
9
11
  * Sets mayo minder to a particular mayo, and ensures you have enough of it.
10
12
  * @param mayo Mayo to use
@@ -1,6 +1,6 @@
1
1
  import "core-js/modules/es.object.values";
2
2
  import { buy, getWorkshed, retrieveItem, toInt, use } from "kolmafia";
3
- import { have } from "../../lib";
3
+ import { have as haveItem } from "../../lib";
4
4
  import logger from "../../logger";
5
5
  import { get, withChoice } from "../../property";
6
6
  import { $item } from "../../template-string";
@@ -10,6 +10,12 @@ export const Mayo = {
10
10
  zapine: $item `Mayozapine`,
11
11
  flex: $item `Mayoflex`,
12
12
  };
13
+ export function installed() {
14
+ return getWorkshed() === $item `portable Mayo Clinic`;
15
+ }
16
+ export function have() {
17
+ return haveItem($item `portable Mayo Clinic`) || installed();
18
+ }
13
19
  /**
14
20
  * Sets mayo minder to a particular mayo, and ensures you have enough of it.
15
21
  * @param mayo Mayo to use
@@ -27,7 +33,7 @@ export function setMayoMinder(mayo, quantity = 1) {
27
33
  return false;
28
34
  }
29
35
  retrieveItem(quantity, mayo);
30
- if (!have($item `Mayo Minder™`))
36
+ if (!haveItem($item `Mayo Minder™`))
31
37
  buy($item `Mayo Minder™`);
32
38
  if (get("mayoMinderSetting") !== mayo.name) {
33
39
  withChoice(1076, toInt(mayo) - 8260, () => use($item `Mayo Minder™`));
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Fill your Asdon Martin to the given fuel level in the cheapest way possible
3
+ * @param targetUnits Fuel level to attempt to reach.
4
+ * @returns Whether we succeeded at filling to the target fuel level.
5
+ */
6
+ export declare function fillTo(targetUnits: number): boolean;
7
+ /**
8
+ * Object consisting of the various Asdon driving styles
9
+ */
10
+ export declare const Driving: {
11
+ Obnoxiously: Effect;
12
+ Stealthily: Effect;
13
+ Wastefully: Effect;
14
+ Safely: Effect;
15
+ Recklessly: Effect;
16
+ Intimidatingly: Effect;
17
+ Quickly: Effect;
18
+ Observantly: Effect;
19
+ Waterproofly: Effect;
20
+ };
21
+ /**
22
+ * Attempt to drive with a particular style for a particular number of turns
23
+ * @param style The driving style to use
24
+ * @param turns The number of turns to attempt to get
25
+ * @returns Whether we have at least as many turns as requested of said driving style.
26
+ */
27
+ export declare function drive(style: Effect, turns?: number): boolean;
@@ -0,0 +1,80 @@
1
+ import { cliExecute, getFuel, haveEffect, historicalPrice, isNpcItem, mallPrice, retrieveItem, toInt, visitUrl, } from "kolmafia";
2
+ import { getAverageAdventures } from "../../lib";
3
+ import { $effect, $items } from "../../template-string";
4
+ const fuelSkiplist = $items `cup of "tea", thermos of "whiskey", Lucky Lindy, Bee's Knees, Sockdollager, Ish Kabibble, Hot Socks, Phonus Balonus, Flivver, Sloppy Jalopy, glass of "milk"`;
5
+ function price(item) {
6
+ return historicalPrice(item) === 0 ? mallPrice(item) : historicalPrice(item);
7
+ }
8
+ function calculateFuelEfficiency(it, targetUnits) {
9
+ const units = getAverageAdventures(it);
10
+ return price(it) / Math.min(targetUnits, units);
11
+ }
12
+ function isFuelItem(it) {
13
+ return (!isNpcItem(it) &&
14
+ it.fullness + it.inebriety > 0 &&
15
+ getAverageAdventures(it) > 0 &&
16
+ it.tradeable &&
17
+ it.discardable &&
18
+ !fuelSkiplist.includes(it));
19
+ }
20
+ const potentialFuel = $items ``.filter(isFuelItem);
21
+ function getBestFuel(targetUnits) {
22
+ const key1 = (item) => -getAverageAdventures(item);
23
+ const key2 = (item) => calculateFuelEfficiency(item, targetUnits);
24
+ potentialFuel.sort((x, y) => key1(x) - key1(y));
25
+ potentialFuel.sort((x, y) => key2(x) - key2(y));
26
+ return potentialFuel[0];
27
+ }
28
+ function insertFuel(it, quantity = 1) {
29
+ const result = visitUrl(`campground.php?action=fuelconvertor&pwd&qty=${quantity}&iid=${toInt(it)}&go=Convert%21`);
30
+ return result.includes("The display updates with a");
31
+ }
32
+ /**
33
+ * Fill your Asdon Martin to the given fuel level in the cheapest way possible
34
+ * @param targetUnits Fuel level to attempt to reach.
35
+ * @returns Whether we succeeded at filling to the target fuel level.
36
+ */
37
+ export function fillTo(targetUnits) {
38
+ while (getFuel() < targetUnits) {
39
+ const remaining = targetUnits - getFuel();
40
+ const fuel = getBestFuel(remaining);
41
+ const count = Math.ceil(targetUnits / getAverageAdventures(fuel));
42
+ retrieveItem(count, fuel);
43
+ if (!insertFuel(fuel, count)) {
44
+ throw new Error("Failed to fuel Asdon Martin.");
45
+ }
46
+ }
47
+ return getFuel() >= targetUnits;
48
+ }
49
+ /**
50
+ * Object consisting of the various Asdon driving styles
51
+ */
52
+ export const Driving = {
53
+ Obnoxiously: $effect `Driving Obnoxiously`,
54
+ Stealthily: $effect `Driving Stealthily`,
55
+ Wastefully: $effect `Driving Wastefully`,
56
+ Safely: $effect `Driving Safely`,
57
+ Recklessly: $effect `Driving Recklessly`,
58
+ Intimidatingly: $effect `Driving Intimidatingly`,
59
+ Quickly: $effect `Driving Quickly`,
60
+ Observantly: $effect `Driving Observantly`,
61
+ Waterproofly: $effect `Driving Waterproofly`,
62
+ };
63
+ /**
64
+ * Attempt to drive with a particular style for a particular number of turns
65
+ * @param style The driving style to use
66
+ * @param turns The number of turns to attempt to get
67
+ * @returns Whether we have at least as many turns as requested of said driving style.
68
+ */
69
+ export function drive(style, turns = 1) {
70
+ if (!Object.values(Driving).includes(style))
71
+ return false;
72
+ if (haveEffect(style) >= turns)
73
+ return true;
74
+ const fuelNeeded = 37 * Math.ceil((turns - haveEffect(style)) / 30);
75
+ fillTo(fuelNeeded);
76
+ while (getFuel() >= 37 && haveEffect(style) < turns) {
77
+ cliExecute(`asdonmartin drive ${style.name.replace("Driving ", "")}`);
78
+ }
79
+ return haveEffect(style) >= turns;
80
+ }
@@ -1,4 +1,4 @@
1
- import { cliExecute, haveFamiliar, myFamiliar, toPhylum, useFamiliar, } from "kolmafia";
1
+ import { cliExecute, haveFamiliar, myFamiliar, useFamiliar } from "kolmafia";
2
2
  import { get } from "../../property";
3
3
  const familiar = Familiar.get("Red-Nosed Snapper");
4
4
  /**
@@ -44,8 +44,7 @@ export function have() {
44
44
  * @returns Tracked phylum, or null if no phylum tracked.
45
45
  */
46
46
  export function getTrackedPhylum() {
47
- const phylum = toPhylum(get("redSnapperPhylum"));
48
- return phylum === Phylum.get("none") ? null : phylum;
47
+ return get("redSnapperPhylum");
49
48
  }
50
49
  /**
51
50
  * Set snapper tracking to a certain phylum.
@@ -87,3 +87,6 @@ export declare function havePlatinumBooze(): boolean;
87
87
  * If they have no quest, returns false
88
88
  */
89
89
  export declare function haveBooze(): boolean;
90
+ export declare const ingredientToPlatinumCocktail: Map<Item, Item>;
91
+ export declare const platinumCocktailToIngredient: Map<Item, Item>;
92
+ export declare function getCheapestPlatinumCocktail(): Item;
@@ -1,7 +1,9 @@
1
- import { runChoice, use, visitUrl } from "kolmafia";
1
+ import { mallPrice, runChoice, use, visitUrl } from "kolmafia";
2
+ import { maxBy } from "lodash";
2
3
  import { have as haveItem } from "../../lib";
3
4
  import { get, withChoice } from "../../property";
4
5
  import { $item, $items } from "../../template-string";
6
+ import { invertMap } from "../../utils";
5
7
  export const item = $item `Guzzlr tablet`;
6
8
  export function have() {
7
9
  return haveItem(item);
@@ -164,3 +166,14 @@ export function haveBooze() {
164
166
  return haveItem(booze);
165
167
  }
166
168
  }
169
+ export const ingredientToPlatinumCocktail = new Map([
170
+ [$item `miniature boiler`, $item `Steamboat`],
171
+ [$item `cold wad`, $item `Ghiaccio Colada`],
172
+ [$item `robin's egg`, $item `Nog-on-the-Cob`],
173
+ [$item `mangled finger`, $item `Sourfinger`],
174
+ [$item `Dish of Clarified Butter`, $item `Buttery Boy`],
175
+ ]);
176
+ export const platinumCocktailToIngredient = invertMap(ingredientToPlatinumCocktail);
177
+ export function getCheapestPlatinumCocktail() {
178
+ return (maxBy(Array.from(ingredientToPlatinumCocktail), (ingredientAndCocktail) => mallPrice(ingredientAndCocktail[0])) ?? [$item `Dish of Clarified Butter`, $item `Buttery Boy`])[1];
179
+ }
@@ -1,6 +1,6 @@
1
1
  import maxBy from "lodash/maxBy";
2
2
  import { getSaleValue } from "../lib";
3
- import { countedMapToArray } from "../utils";
3
+ import { sumNumbers } from "../utils";
4
4
  import { expected as candyHeartsExpected, have as candyHeartsHave, summonSkill as candyHeartsSkill, } from "./2007/CandyHearts";
5
5
  import { expected as divineFavorsExpected, have as divineFavorsHave, summonSkill as divineFavorsSkill, } from "./2008/DivineFavors";
6
6
  import { expected as loveSongsExpected, have as loveSongsHave, summonSkill as loveSongsSkill, } from "./2009/LoveSongs";
@@ -62,5 +62,5 @@ export function possibleLibramSummons() {
62
62
  return results;
63
63
  }
64
64
  export function bestLibramToCast() {
65
- return (maxBy(Array.from(possibleLibramSummons().entries()), ([, itemMap]) => getSaleValue(...countedMapToArray(itemMap))) ?? [null])[0];
65
+ return (maxBy(Array.from(possibleLibramSummons().entries()), ([, itemMap]) => sumNumbers(Array.from(itemMap.entries()).map(([item, weight]) => weight * getSaleValue(item)))) ?? [null])[0];
66
66
  }
@@ -1,7 +1,9 @@
1
+ import * as AsdonMartin from "./2017/AsdonMartin";
1
2
  import * as Bandersnatch from "./2009/Bandersnatch";
2
3
  import * as BeachComb from "./2019/BeachComb";
3
4
  import * as ChateauMantegna from "./2015/ChateauMantegna";
4
5
  import * as CrownOfThrones from "./2010/CrownOfThrones";
6
+ import * as DNALab from "./2014/DNALab";
5
7
  import * as FloristFriar from "./2013/Florist";
6
8
  import * as Guzzlr from "./2020/Guzzlr";
7
9
  import * as MayoClinic from "./2015/MayoClinic";
@@ -14,6 +16,6 @@ import * as SpookyPutty from "./2009/SpookyPutty";
14
16
  import * as TunnelOfLove from "./2017/TunnelOfLove";
15
17
  import * as WinterGarden from "./2014/WinterGarden";
16
18
  import * as Witchess from "./2016/Witchess";
17
- export { Bandersnatch, BeachComb, ChateauMantegna, CrownOfThrones, FloristFriar, Guzzlr, MayoClinic, ObtuseAngel, RainDoh, SongBoom, SourceTerminal, Snapper, SpookyPutty, TunnelOfLove, WinterGarden, Witchess, };
19
+ export { AsdonMartin, Bandersnatch, BeachComb, ChateauMantegna, CrownOfThrones, DNALab, FloristFriar, Guzzlr, MayoClinic, ObtuseAngel, RainDoh, SongBoom, SourceTerminal, Snapper, SpookyPutty, TunnelOfLove, WinterGarden, Witchess, };
18
20
  export * from "./putty-likes";
19
21
  export * from "./LibramSummon";
@@ -1,7 +1,9 @@
1
+ import * as AsdonMartin from "./2017/AsdonMartin";
1
2
  import * as Bandersnatch from "./2009/Bandersnatch";
2
3
  import * as BeachComb from "./2019/BeachComb";
3
4
  import * as ChateauMantegna from "./2015/ChateauMantegna";
4
5
  import * as CrownOfThrones from "./2010/CrownOfThrones";
6
+ import * as DNALab from "./2014/DNALab";
5
7
  import * as FloristFriar from "./2013/Florist";
6
8
  import * as Guzzlr from "./2020/Guzzlr";
7
9
  import * as MayoClinic from "./2015/MayoClinic";
@@ -14,6 +16,6 @@ import * as SpookyPutty from "./2009/SpookyPutty";
14
16
  import * as TunnelOfLove from "./2017/TunnelOfLove";
15
17
  import * as WinterGarden from "./2014/WinterGarden";
16
18
  import * as Witchess from "./2016/Witchess";
17
- export { Bandersnatch, BeachComb, ChateauMantegna, CrownOfThrones, FloristFriar, Guzzlr, MayoClinic, ObtuseAngel, RainDoh, SongBoom, SourceTerminal, Snapper, SpookyPutty, TunnelOfLove, WinterGarden, Witchess, };
19
+ export { AsdonMartin, Bandersnatch, BeachComb, ChateauMantegna, CrownOfThrones, DNALab, FloristFriar, Guzzlr, MayoClinic, ObtuseAngel, RainDoh, SongBoom, SourceTerminal, Snapper, SpookyPutty, TunnelOfLove, WinterGarden, Witchess, };
18
20
  export * from "./putty-likes";
19
21
  export * from "./LibramSummon";
package/dist/utils.d.ts CHANGED
@@ -39,3 +39,8 @@ export declare function arrayContains<T, A extends T>(item: T, array: ReadonlyAr
39
39
  * @returns Whether the two arrays are equal, irrespective of order.
40
40
  */
41
41
  export declare function setEqual<T>(a: T[], b: T[]): boolean;
42
+ /**
43
+ * Reverses keys and values for a given map
44
+ * @param map Map to invert
45
+ */
46
+ export declare function invertMap<T1, T2>(map: Map<T1, T2>): Map<T2, T1>;
package/dist/utils.js CHANGED
@@ -37,7 +37,7 @@ export function arrayToCountedMap(array) {
37
37
  return map;
38
38
  }
39
39
  export function countedMapToArray(map) {
40
- return [...map].flatMap(([item, quantity]) => Array(quantity).fill(item));
40
+ return [].concat(...[...map].map(([item, quantity]) => Array(quantity).fill(item)));
41
41
  }
42
42
  export function countedMapToString(map) {
43
43
  return [...map].map(([item, quantity]) => `${quantity} x ${item}`).join(", ");
@@ -74,3 +74,14 @@ export function setEqual(a, b) {
74
74
  return (a.length === b.length &&
75
75
  sortedA.every((item, index) => item === sortedB[index]));
76
76
  }
77
+ /**
78
+ * Reverses keys and values for a given map
79
+ * @param map Map to invert
80
+ */
81
+ export function invertMap(map) {
82
+ const returnValue = new Map();
83
+ for (const [key, value] of map) {
84
+ returnValue.set(value, key);
85
+ }
86
+ return returnValue;
87
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libram",
3
- "version": "0.4.2",
3
+ "version": "0.4.6",
4
4
  "description": "JavaScript helper library for KoLmafia",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -13,7 +13,7 @@
13
13
  "clean": "rm -rf dist",
14
14
  "docs": "yarn run typedoc",
15
15
  "format": "yarn run prettier --write .",
16
- "lint": "yarn run eslint src --ext .ts && yarn run prettier --check .",
16
+ "lint": "yarn run eslint src tools --ext .ts && yarn run prettier --check .",
17
17
  "prepublishOnly": "yarn run build",
18
18
  "updateProps": "yarn run ts-node ./tools/parseDefaultProperties.ts"
19
19
  },
@@ -30,30 +30,36 @@
30
30
  "@babel/plugin-transform-runtime": "^7.15.0",
31
31
  "@babel/preset-env": "^7.15.0",
32
32
  "@babel/preset-typescript": "^7.15.0",
33
+ "@tsconfig/node16": "^1.0.2",
34
+ "@types/jest": "^27.0.1",
33
35
  "@types/lodash-es": "^4.17.4",
36
+ "@types/node": "^16.11.11",
34
37
  "@types/node-fetch": "^2.5.7",
35
- "@typescript-eslint/eslint-plugin": "^4.11.0",
36
- "@typescript-eslint/parser": "^4.11.0",
38
+ "@typescript-eslint/eslint-plugin": "^5.5.0",
39
+ "@typescript-eslint/parser": "^5.5.0",
37
40
  "babel-loader": "^8.2.2",
38
41
  "eslint": "^7.16.0",
39
42
  "eslint-config-prettier": "^8.3.0",
40
43
  "eslint-plugin-import": "^2.24.2",
44
+ "eslint-plugin-jest": "^25.2.3",
41
45
  "eslint-plugin-libram": "^0.1.9",
42
46
  "husky": "^4.3.6",
43
47
  "java-parser": "^1.4.0",
48
+ "jest": "^27.1.0",
44
49
  "lint-staged": ">=10",
45
50
  "node-fetch": "^2.6.1",
46
51
  "prettier": "^2.1.2",
47
- "ts-node": "^9.1.1",
48
- "typedoc": "^0.20.19",
49
- "typescript": "^4.1.3",
52
+ "ts-jest": "^27.0.5",
53
+ "ts-node": "^10.4.0",
54
+ "typedoc": "^0.22.10",
55
+ "typescript": "^4.5.2",
50
56
  "webpack": "^5.10.0",
51
57
  "webpack-cli": "^4.2.0"
52
58
  },
53
59
  "dependencies": {
54
60
  "@babel/runtime-corejs3": "^7.14.9",
55
61
  "core-js": "3.15.2",
56
- "kolmafia": "^1.3.0",
62
+ "kolmafia": "^1.4.0",
57
63
  "lodash": "^4.17.21"
58
64
  },
59
65
  "husky": {