libram 0.8.28 → 0.8.30

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 (195) hide show
  1. package/dist/Clan.d.ts +128 -0
  2. package/dist/Clan.js +300 -0
  3. package/dist/Copier.d.ts +9 -0
  4. package/dist/Copier.js +15 -0
  5. package/dist/Dungeon.d.ts +45 -0
  6. package/dist/Dungeon.js +115 -0
  7. package/dist/Kmail.d.ts +133 -0
  8. package/dist/Kmail.js +259 -0
  9. package/dist/actions/ActionSource.d.ts +131 -0
  10. package/dist/actions/ActionSource.js +178 -0
  11. package/dist/actions/Banish.d.ts +16 -0
  12. package/dist/actions/Banish.js +121 -0
  13. package/dist/actions/FreeKill.d.ts +16 -0
  14. package/dist/actions/FreeKill.js +94 -0
  15. package/dist/actions/FreeRun.d.ts +16 -0
  16. package/dist/actions/FreeRun.js +81 -0
  17. package/dist/actions/index.d.ts +4 -0
  18. package/dist/actions/index.js +4 -0
  19. package/dist/ascend.d.ts +83 -0
  20. package/dist/ascend.js +268 -0
  21. package/dist/challengePaths/2014/HeavyRains.d.ts +22 -0
  22. package/dist/challengePaths/2014/HeavyRains.js +75 -0
  23. package/dist/challengePaths/2015/CommunityService.d.ts +125 -0
  24. package/dist/challengePaths/2015/CommunityService.js +334 -0
  25. package/dist/challengePaths/2016/NuclearAutumn.d.ts +13 -0
  26. package/dist/challengePaths/2016/NuclearAutumn.js +21 -0
  27. package/dist/challengePaths/index.d.ts +4 -0
  28. package/dist/challengePaths/index.js +4 -0
  29. package/dist/combat.d.ts +414 -0
  30. package/dist/combat.js +711 -0
  31. package/dist/console.d.ts +12 -0
  32. package/dist/console.js +14 -0
  33. package/dist/counter.d.ts +22 -0
  34. package/dist/counter.js +37 -0
  35. package/dist/diet/index.d.ts +80 -0
  36. package/dist/diet/index.js +682 -0
  37. package/dist/diet/knapsack.d.ts +8 -0
  38. package/dist/diet/knapsack.js +128 -0
  39. package/dist/index.d.ts +29 -0
  40. package/dist/index.js +26 -0
  41. package/dist/lib.d.ts +508 -0
  42. package/dist/lib.js +970 -0
  43. package/dist/logger.d.ts +35 -0
  44. package/dist/logger.js +62 -0
  45. package/dist/maximize.d.ts +122 -0
  46. package/dist/maximize.js +531 -0
  47. package/dist/modifier.d.ts +41 -0
  48. package/dist/modifier.js +160 -0
  49. package/dist/modifierTypes.d.ts +16 -0
  50. package/dist/modifierTypes.js +9 -0
  51. package/dist/mood.d.ts +105 -0
  52. package/dist/mood.js +349 -0
  53. package/dist/moonSign.d.ts +13 -0
  54. package/dist/moonSign.js +25 -0
  55. package/dist/overlappingNames.d.ts +3 -0
  56. package/dist/overlappingNames.js +42 -0
  57. package/dist/property.d.ts +222 -0
  58. package/dist/property.js +385 -0
  59. package/dist/propertyTypes.d.ts +19 -0
  60. package/dist/propertyTypes.js +10 -0
  61. package/dist/propertyTyping.d.ts +65 -0
  62. package/dist/propertyTyping.js +91 -0
  63. package/dist/resources/2007/CandyHearts.d.ts +9 -0
  64. package/dist/resources/2007/CandyHearts.js +24 -0
  65. package/dist/resources/2008/DivineFavors.d.ts +9 -0
  66. package/dist/resources/2008/DivineFavors.js +27 -0
  67. package/dist/resources/2008/Stickers.d.ts +49 -0
  68. package/dist/resources/2008/Stickers.js +84 -0
  69. package/dist/resources/2009/Bandersnatch.d.ts +56 -0
  70. package/dist/resources/2009/Bandersnatch.js +93 -0
  71. package/dist/resources/2009/LoveSongs.d.ts +9 -0
  72. package/dist/resources/2009/LoveSongs.js +24 -0
  73. package/dist/resources/2009/SpookyPutty.d.ts +31 -0
  74. package/dist/resources/2009/SpookyPutty.js +49 -0
  75. package/dist/resources/2010/Brickos.d.ts +9 -0
  76. package/dist/resources/2010/Brickos.js +21 -0
  77. package/dist/resources/2010/CrownOfThrones.d.ts +68 -0
  78. package/dist/resources/2010/CrownOfThrones.js +418 -0
  79. package/dist/resources/2010/LookingGlass.d.ts +29 -0
  80. package/dist/resources/2010/LookingGlass.js +89 -0
  81. package/dist/resources/2011/Gygaxian.d.ts +9 -0
  82. package/dist/resources/2011/Gygaxian.js +24 -0
  83. package/dist/resources/2011/ObtuseAngel.d.ts +33 -0
  84. package/dist/resources/2011/ObtuseAngel.js +51 -0
  85. package/dist/resources/2011/StompingBoots.d.ts +37 -0
  86. package/dist/resources/2011/StompingBoots.js +57 -0
  87. package/dist/resources/2012/RainDoh.d.ts +25 -0
  88. package/dist/resources/2012/RainDoh.js +37 -0
  89. package/dist/resources/2012/ReagnimatedGnome.d.ts +31 -0
  90. package/dist/resources/2012/ReagnimatedGnome.js +46 -0
  91. package/dist/resources/2012/Resolutions.d.ts +9 -0
  92. package/dist/resources/2012/Resolutions.js +28 -0
  93. package/dist/resources/2013/Florist.d.ts +81 -0
  94. package/dist/resources/2013/Florist.js +245 -0
  95. package/dist/resources/2013/JungMan.d.ts +33 -0
  96. package/dist/resources/2013/JungMan.js +69 -0
  97. package/dist/resources/2013/PulledTaffy.d.ts +9 -0
  98. package/dist/resources/2013/PulledTaffy.js +33 -0
  99. package/dist/resources/2014/CrimboShrub.d.ts +42 -0
  100. package/dist/resources/2014/CrimboShrub.js +89 -0
  101. package/dist/resources/2014/DNALab.d.ts +56 -0
  102. package/dist/resources/2014/DNALab.js +162 -0
  103. package/dist/resources/2014/WinterGarden.d.ts +23 -0
  104. package/dist/resources/2014/WinterGarden.js +35 -0
  105. package/dist/resources/2015/BarrelShrine.d.ts +8 -0
  106. package/dist/resources/2015/BarrelShrine.js +25 -0
  107. package/dist/resources/2015/ChateauMantegna.d.ts +54 -0
  108. package/dist/resources/2015/ChateauMantegna.js +100 -0
  109. package/dist/resources/2015/DeckOfEveryCard.d.ts +29 -0
  110. package/dist/resources/2015/DeckOfEveryCard.js +122 -0
  111. package/dist/resources/2015/Dinseylandfill.d.ts +89 -0
  112. package/dist/resources/2015/Dinseylandfill.js +205 -0
  113. package/dist/resources/2015/MayoClinic.d.ts +23 -0
  114. package/dist/resources/2015/MayoClinic.js +49 -0
  115. package/dist/resources/2016/GingerBread.d.ts +32 -0
  116. package/dist/resources/2016/GingerBread.js +73 -0
  117. package/dist/resources/2016/SourceTerminal.d.ts +181 -0
  118. package/dist/resources/2016/SourceTerminal.js +275 -0
  119. package/dist/resources/2016/Witchess.d.ts +19 -0
  120. package/dist/resources/2016/Witchess.js +48 -0
  121. package/dist/resources/2017/AsdonMartin.d.ts +59 -0
  122. package/dist/resources/2017/AsdonMartin.js +238 -0
  123. package/dist/resources/2017/Horsery.d.ts +19 -0
  124. package/dist/resources/2017/Horsery.js +42 -0
  125. package/dist/resources/2017/MummingTrunk.d.ts +8 -0
  126. package/dist/resources/2017/MummingTrunk.js +33 -0
  127. package/dist/resources/2017/Pantogram.d.ts +92 -0
  128. package/dist/resources/2017/Pantogram.js +174 -0
  129. package/dist/resources/2017/Robortender.d.ts +30 -0
  130. package/dist/resources/2017/Robortender.js +90 -0
  131. package/dist/resources/2017/Spacegate.d.ts +86 -0
  132. package/dist/resources/2017/Spacegate.js +178 -0
  133. package/dist/resources/2017/TunnelOfLove.d.ts +39 -0
  134. package/dist/resources/2017/TunnelOfLove.js +120 -0
  135. package/dist/resources/2018/LatteLoversMembersMug.d.ts +392 -0
  136. package/dist/resources/2018/LatteLoversMembersMug.js +303 -0
  137. package/dist/resources/2018/SongBoom.d.ts +33 -0
  138. package/dist/resources/2018/SongBoom.js +55 -0
  139. package/dist/resources/2019/BeachComb.d.ts +72 -0
  140. package/dist/resources/2019/BeachComb.js +118 -0
  141. package/dist/resources/2019/CampAway.d.ts +39 -0
  142. package/dist/resources/2019/CampAway.js +72 -0
  143. package/dist/resources/2019/Snapper.d.ts +33 -0
  144. package/dist/resources/2019/Snapper.js +73 -0
  145. package/dist/resources/2020/Cartography.d.ts +16 -0
  146. package/dist/resources/2020/Cartography.js +48 -0
  147. package/dist/resources/2020/Guzzlr.d.ts +160 -0
  148. package/dist/resources/2020/Guzzlr.js +275 -0
  149. package/dist/resources/2020/RetroCape.d.ts +51 -0
  150. package/dist/resources/2020/RetroCape.js +115 -0
  151. package/dist/resources/2021/CrystalBall.d.ts +14 -0
  152. package/dist/resources/2021/CrystalBall.js +41 -0
  153. package/dist/resources/2021/DaylightShavings.d.ts +40 -0
  154. package/dist/resources/2021/DaylightShavings.js +74 -0
  155. package/dist/resources/2022/AutumnAton.d.ts +78 -0
  156. package/dist/resources/2022/AutumnAton.js +182 -0
  157. package/dist/resources/2022/CombatLoversLocket.d.ts +46 -0
  158. package/dist/resources/2022/CombatLoversLocket.js +83 -0
  159. package/dist/resources/2022/GreyGoose.d.ts +59 -0
  160. package/dist/resources/2022/GreyGoose.js +90 -0
  161. package/dist/resources/2022/JuneCleaver.d.ts +47 -0
  162. package/dist/resources/2022/JuneCleaver.js +69 -0
  163. package/dist/resources/2022/TrainSet.d.ts +146 -0
  164. package/dist/resources/2022/TrainSet.js +228 -0
  165. package/dist/resources/2023/AugustScepter.d.ts +25 -0
  166. package/dist/resources/2023/AugustScepter.js +40 -0
  167. package/dist/resources/2023/BurningLeaves.d.ts +25 -0
  168. package/dist/resources/2023/BurningLeaves.js +74 -0
  169. package/dist/resources/2023/CinchoDeMayo.d.ts +25 -0
  170. package/dist/resources/2023/CinchoDeMayo.js +45 -0
  171. package/dist/resources/2023/ClosedCircuitPayphone.d.ts +80 -0
  172. package/dist/resources/2023/ClosedCircuitPayphone.js +129 -0
  173. package/dist/resources/2023/CursedMonkeyPaw.d.ts +46 -0
  174. package/dist/resources/2023/CursedMonkeyPaw.js +113 -0
  175. package/dist/resources/2024/AprilingBandHelmet.d.ts +57 -0
  176. package/dist/resources/2024/AprilingBandHelmet.js +118 -0
  177. package/dist/resources/2024/ChestMimic.d.ts +43 -0
  178. package/dist/resources/2024/ChestMimic.js +125 -0
  179. package/dist/resources/LibramSummon.d.ts +18 -0
  180. package/dist/resources/LibramSummon.js +74 -0
  181. package/dist/resources/index.d.ts +54 -0
  182. package/dist/resources/index.js +54 -0
  183. package/dist/resources/putty-likes.d.ts +21 -0
  184. package/dist/resources/putty-likes.js +33 -0
  185. package/dist/session.d.ts +169 -0
  186. package/dist/session.js +284 -0
  187. package/dist/since.d.ts +51 -0
  188. package/dist/since.js +108 -0
  189. package/dist/template-string.d.ts +324 -0
  190. package/dist/template-string.js +265 -0
  191. package/dist/url.d.ts +35 -0
  192. package/dist/url.js +67 -0
  193. package/dist/utils.d.ts +185 -0
  194. package/dist/utils.js +264 -0
  195. package/package.json +2 -2
@@ -0,0 +1,682 @@
1
+ import { canEquip, fullnessLimit, historicalAge, historicalPrice, inebrietyLimit, itemType, mallPrice, mallPrices, myFullness, myInebriety, myLevel, myPrimestat, mySpleenUse, npcPrice, spleenLimit, } from "kolmafia";
2
+ import { have } from "../lib";
3
+ import { get as getModifier } from "../modifier";
4
+ import { get } from "../property";
5
+ import { Mayo, installed as mayoInstalled } from "../resources/2015/MayoClinic";
6
+ import { $effect, $item, $items, $skill, $stat } from "../template-string";
7
+ import { notNullish, sum } from "../utils";
8
+ import { knapsack } from "./knapsack";
9
+ function isMonday() {
10
+ // Checking Tuesday's ruby is a hack to see if it's Monday in Arizona.
11
+ return getModifier("Muscle Percent", $item `Tuesday's ruby`) > 0;
12
+ }
13
+ /**
14
+ * Expected adventures from an item given a specified state
15
+ *
16
+ * @todo Include Salty Mouth and potentially other modifiers.
17
+ * @param item Item to consider
18
+ * @param modifiers Consumption modifiers to consider
19
+ * @returns Adventures expected
20
+ */
21
+ function expectedAdventures(item, modifiers) {
22
+ if (item.adventures === "")
23
+ return 0;
24
+ const [min, recordedMax] = item.adventures
25
+ .split(/[-]/)
26
+ .map((s) => parseInt(s));
27
+ const max = recordedMax ?? min;
28
+ 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"))
31
+ ? 1.5
32
+ : 1.3;
33
+ const seasoningAdventures = max - min <= 1 ? 1 : 0.5;
34
+ const garish = modifiers.garish && item.notes?.includes("LASAGNA") && !isMonday();
35
+ const refinedPalate = modifiers.refinedPalate && item.notes?.includes("WINE");
36
+ const pinkyRing = modifiers.pinkyRing && item.notes?.includes("WINE");
37
+ return (sum(interpolated, (baseAdventures) => {
38
+ let adventures = baseAdventures;
39
+ if (modifiers.forkMug) {
40
+ adventures = Math.floor(adventures * forkMugMultiplier);
41
+ }
42
+ if (item.notes?.includes("SAUCY") && modifiers.saucemaven) {
43
+ adventures += myPrimestat() === $stat `Mysticality` ? 5 : 3;
44
+ }
45
+ if (garish)
46
+ adventures += 5;
47
+ if (refinedPalate)
48
+ adventures = Math.floor(adventures * 1.25);
49
+ if (pinkyRing)
50
+ adventures = Math.round(adventures * 1.125);
51
+ if (item.notes?.includes("MARTINI") && modifiers.tuxedoShirt) {
52
+ adventures += 2;
53
+ }
54
+ if (itemType(item) === "food" && modifiers.mayoflex)
55
+ adventures++;
56
+ if (itemType(item) === "food" && modifiers.seasoning)
57
+ adventures += seasoningAdventures;
58
+ if (itemType(item) === "food" && modifiers.whetStone)
59
+ adventures++;
60
+ return adventures;
61
+ }) / interpolated.length);
62
+ }
63
+ export class MenuItem {
64
+ item;
65
+ organ;
66
+ size;
67
+ maximum;
68
+ additionalValue;
69
+ effect;
70
+ priceOverride;
71
+ mayo;
72
+ data;
73
+ static defaultPriceFunction = (item) => npcPrice(item) > 0 ? npcPrice(item) : mallPrice(item);
74
+ static defaultOptions() {
75
+ return new Map([
76
+ [
77
+ $item `distention pill`,
78
+ {
79
+ organ: "food",
80
+ maximum: !have($item `distention pill`) || get("_distentionPillUsed") ? 0 : 1,
81
+ size: -1,
82
+ },
83
+ ],
84
+ [
85
+ $item `synthetic dog hair pill`,
86
+ {
87
+ organ: "booze",
88
+ maximum: !have($item `synthetic dog hair pill`) ||
89
+ get("_syntheticDogHairPillUsed")
90
+ ? 0
91
+ : 1,
92
+ size: -1,
93
+ },
94
+ ],
95
+ [
96
+ $item `cuppa Voraci tea`,
97
+ { organ: "food", maximum: get("_voraciTeaUsed") ? 0 : 1, size: -1 },
98
+ ],
99
+ [
100
+ $item `cuppa Sobrie tea`,
101
+ { organ: "booze", maximum: get("_sobrieTeaUsed") ? 0 : 1, size: -1 },
102
+ ],
103
+ [
104
+ $item `mojo filter`,
105
+ {
106
+ organ: "spleen item",
107
+ maximum: 3 - get("currentMojoFilters"),
108
+ size: -1,
109
+ },
110
+ ],
111
+ [$item `spice melange`, { maximum: get("spiceMelangeUsed") ? 0 : 1 }],
112
+ [
113
+ $item `Ultra Mega Sour Ball`,
114
+ { maximum: get("_ultraMegaSourBallUsed") ? 0 : 1 },
115
+ ],
116
+ [
117
+ $item `The Plumber's mushroom stew`,
118
+ { maximum: get("_plumbersMushroomStewEaten") ? 0 : 1 },
119
+ ],
120
+ [$item `The Mad Liquor`, { maximum: get("_madLiquorDrunk") ? 0 : 1 }],
121
+ [
122
+ $item `Doc Clock's thyme cocktail`,
123
+ { maximum: get("_docClocksThymeCocktailDrunk") ? 0 : 1 },
124
+ ],
125
+ [$item `Mr. Burnsger`, { maximum: get("_mrBurnsgerEaten") ? 0 : 1 }],
126
+ [
127
+ $item `Calzone of Legend`,
128
+ { maximum: get("calzoneOfLegendEaten") ? 0 : 1 },
129
+ ],
130
+ [
131
+ $item `Deep Dish of Legend`,
132
+ { maximum: get("deepDishOfLegendEaten") ? 0 : 1 },
133
+ ],
134
+ [$item `Pizza of Legend`, { maximum: get("pizzaOfLegendEaten") ? 0 : 1 }],
135
+ [
136
+ $item `jar of fermented pickle juice`,
137
+ { maximum: get("_pickleJuiceDrunk") ? 0 : 1 },
138
+ ],
139
+ [
140
+ $item `extra-greasy slider`,
141
+ { maximum: get("_extraGreasySliderEaten") ? 0 : 1 },
142
+ ],
143
+ [$item `voodoo snuff`, { maximum: get("_voodooSnuffUsed") ? 0 : 1 }],
144
+ [
145
+ $item `Ol' Scratch's salad fork`,
146
+ { maximum: get("_saladForkUsed") ? 0 : 1 },
147
+ ],
148
+ [$item `Frosty's frosty mug`, { maximum: get("_frostyMugUsed") ? 0 : 1 }],
149
+ [
150
+ $item `tin cup of mulligan stew`,
151
+ { maximum: get("_mulliganStewEaten") ? 0 : 1 },
152
+ ],
153
+ [
154
+ $item `Hodgman's blanket`,
155
+ { maximum: get("_hodgmansBlanketDrunk") ? 0 : 1 },
156
+ ],
157
+ ]);
158
+ }
159
+ /**
160
+ * Construct a new menu item, possibly with extra properties. Items in MenuItem.defaultOptions have intelligent defaults.
161
+ *
162
+ * @param item Item to add to menu.
163
+ * @param options Options for this menu item
164
+ * @param options.organ Designate item as belonging to a specific organ.
165
+ * @param options.size Override item organ size. Necessary for any non-food/booze/spleen item.
166
+ * @param options.maximum Maximum uses remaining today, or "auto" to check dailyusesleft Mafia property.
167
+ * @param options.additionalValue Additional value (positive) or cost (negative) to consider with item, e.g. from buffs.
168
+ * @param options.effect Effect associated with this menu item (pocket wish effect, sweet synthesis effect, pill keeper potion extension)
169
+ * @param options.mayo Which mayo to use before item (ignored if mayo clinic is not installed or item is not a food)
170
+ * @param options.note Any note to track information about item, to be used later
171
+ */
172
+ constructor(item, options = {}) {
173
+ const { size, organ, maximum, additionalValue, effect, priceOverride, mayo, data, } = {
174
+ ...options,
175
+ ...(MenuItem.defaultOptions().get(item) ?? {}),
176
+ };
177
+ this.item = item;
178
+ if (notNullish(maximum))
179
+ this.maximum = maximum === "auto" ? item.dailyusesleft : maximum;
180
+ if (notNullish(additionalValue))
181
+ this.additionalValue = additionalValue;
182
+ if (notNullish(effect))
183
+ this.effect = effect;
184
+ if (notNullish(priceOverride))
185
+ this.priceOverride = priceOverride;
186
+ if (notNullish(mayo))
187
+ this.mayo = mayo;
188
+ if (notNullish(data))
189
+ this.data = data;
190
+ if (notNullish(organ)) {
191
+ this.organ = organ;
192
+ }
193
+ else {
194
+ const typ = itemType(this.item);
195
+ if (isOrgan(typ))
196
+ this.organ = typ;
197
+ }
198
+ this.size =
199
+ size ??
200
+ (this.organ === "food"
201
+ ? this.item.fullness
202
+ : this.organ === "booze"
203
+ ? this.item.inebriety
204
+ : this.organ === "spleen item"
205
+ ? this.item.spleen
206
+ : 0);
207
+ }
208
+ equals(other) {
209
+ return this.item === other.item && this.effect === other.effect;
210
+ }
211
+ toString() {
212
+ if (this.effect) {
213
+ return `${this.item}:${this.effect}`;
214
+ }
215
+ return this.item.toString();
216
+ }
217
+ price() {
218
+ return this.priceOverride ?? MenuItem.defaultPriceFunction?.(this.item);
219
+ }
220
+ }
221
+ const organs = ["food", "booze", "spleen item"];
222
+ /**
223
+ * @param x Name of thing that might be an organ
224
+ * @returns Whether the string supplied is the name of an organ
225
+ */
226
+ function isOrgan(x) {
227
+ return organs.includes(x);
228
+ }
229
+ class DietPlanner {
230
+ mpa;
231
+ menu;
232
+ mayoLookup;
233
+ fork;
234
+ mug;
235
+ seasoning;
236
+ whetStone;
237
+ spleenValue = 0;
238
+ constructor(mpa, menu) {
239
+ this.mpa = mpa;
240
+ const fork = menu.find((item) => item.item === $item `Ol' Scratch's salad fork`);
241
+ if (fork)
242
+ this.fork = fork;
243
+ const mug = menu.find((item) => item.item === $item `Frosty's frosty mug`);
244
+ if (mug)
245
+ this.mug = mug;
246
+ const seasoning = menu.find((item) => item.item === $item `Special Seasoning`);
247
+ if (seasoning)
248
+ this.seasoning = seasoning;
249
+ const whetStone = menu.find((item) => item.item === $item `whet stone`);
250
+ if (whetStone)
251
+ this.whetStone = whetStone;
252
+ this.mayoLookup = new Map();
253
+ if (mayoInstalled()) {
254
+ for (const mayo of [Mayo.flex, Mayo.zapine]) {
255
+ const menuItem = menu.find((item) => item.item === mayo);
256
+ if (menuItem)
257
+ this.mayoLookup.set(mayo, menuItem);
258
+ }
259
+ }
260
+ this.menu = menu.filter((item) => item.organ);
261
+ if (menu.filter((item) => historicalPrice(item.item) === 0 || historicalAge(item.item) >= 1).length > 100) {
262
+ mallPrices("food");
263
+ mallPrices("booze");
264
+ }
265
+ const spleenItems = menu.filter((item) => itemType(item.item) === "spleen item");
266
+ spleenItems.sort((x, y) => -(this.consumptionValue(x) / x.item.spleen -
267
+ this.consumptionValue(y) / y.item.spleen));
268
+ if (spleenItems.length > 0) {
269
+ // Marginal value for sliders and jars depends on our best unlimited spleen item.
270
+ // TODO: spleenLimit() - mySpleenUse() is a poor estimate.
271
+ const bestMarginalSpleenItem = spleenItems.find((spleenItem) => spleenItem.maximum === undefined ||
272
+ spleenItem.maximum * spleenItem.size >= spleenLimit() - mySpleenUse());
273
+ if (bestMarginalSpleenItem) {
274
+ this.spleenValue = Math.max(0, this.consumptionValue(bestMarginalSpleenItem) /
275
+ bestMarginalSpleenItem.size);
276
+ }
277
+ }
278
+ }
279
+ /**
280
+ * Determine the value of consuming a menu item with any profitable helpers.
281
+ *
282
+ * @param menuItem Menu item to check.
283
+ * @returns Value for consuming that menu item.
284
+ */
285
+ consumptionValue(menuItem) {
286
+ return this.consumptionHelpersAndValue(menuItem, {})[1];
287
+ }
288
+ /**
289
+ * Determine which helpers will be used with a menu item and its resulting value.
290
+ *
291
+ * @param menuItem Menu item to check.
292
+ * @param overrideModifiers Overrides for consumption modifiers, if any.
293
+ * @returns Pair [array of helpers and base menu item, value].
294
+ */
295
+ consumptionHelpersAndValue(menuItem, overrideModifiers) {
296
+ const helpers = [];
297
+ if (itemType(menuItem.item) === "food" && this.mayoLookup.size) {
298
+ const mayo = menuItem.mayo
299
+ ? this.mayoLookup.get(menuItem.mayo)
300
+ : this.mayoLookup.get(Mayo.flex);
301
+ if (mayo)
302
+ helpers.push(mayo);
303
+ }
304
+ const defaultModifiers = {
305
+ forkMug: false,
306
+ seasoning: this.seasoning ? helpers.includes(this.seasoning) : false,
307
+ whetStone: this.whetStone ? helpers.includes(this.whetStone) : false,
308
+ mayoflex: this.mayoLookup.size
309
+ ? helpers.some((item) => item.item === Mayo.flex)
310
+ : false,
311
+ refinedPalate: have($effect `Refined Palate`),
312
+ garish: have($effect `Gar-ish`),
313
+ saucemaven: have($skill `Saucemaven`),
314
+ pinkyRing: have($item `mafia pinky ring`) && canEquip($item `mafia pinky ring`),
315
+ tuxedoShirt: have($item `tuxedo shirt`) && canEquip($item `tuxedo shirt`),
316
+ ...overrideModifiers,
317
+ };
318
+ if (this.seasoning &&
319
+ itemType(menuItem.item) === "food" &&
320
+ this.mpa *
321
+ (expectedAdventures(menuItem.item, {
322
+ ...defaultModifiers,
323
+ seasoning: true,
324
+ }) -
325
+ expectedAdventures(menuItem.item, {
326
+ ...defaultModifiers,
327
+ seasoning: false,
328
+ })) >
329
+ mallPrice($item `Special Seasoning`)) {
330
+ helpers.push(this.seasoning);
331
+ }
332
+ if (this.whetStone &&
333
+ itemType(menuItem.item) === "food" &&
334
+ this.mpa > mallPrice($item `whet stone`)) {
335
+ helpers.push(this.whetStone);
336
+ }
337
+ const forkMug = itemType(menuItem.item) === "food"
338
+ ? this.fork
339
+ : itemType(menuItem.item) === "booze"
340
+ ? this.mug
341
+ : null;
342
+ const forkMugPrice = forkMug ? forkMug.price() : Infinity;
343
+ const baseCost = menuItem.price() + sum(helpers, (item) => item.price());
344
+ const valueRaw = expectedAdventures(menuItem.item, defaultModifiers) * this.mpa -
345
+ baseCost +
346
+ (menuItem.additionalValue ?? 0);
347
+ const valueForkMug = expectedAdventures(menuItem.item, {
348
+ ...defaultModifiers,
349
+ forkMug: true,
350
+ }) *
351
+ this.mpa -
352
+ baseCost -
353
+ forkMugPrice +
354
+ (menuItem.additionalValue ?? 0);
355
+ const valueSpleen = $items `jar of fermented pickle juice, extra-greasy slider`.includes(menuItem.item)
356
+ ? 5 * this.spleenValue
357
+ : 0;
358
+ return forkMug && valueForkMug > valueRaw
359
+ ? [[...helpers, forkMug, menuItem], valueForkMug + valueSpleen]
360
+ : [[...helpers, menuItem], valueRaw + valueSpleen];
361
+ }
362
+ /**
363
+ * Plan an individual organ.
364
+ *
365
+ * @param organ Organ to plan
366
+ * @param capacity Organ capacity.
367
+ * @param overrideModifiers Overrides for consumption modifiers, if any.
368
+ * @returns Pair of [value, menu items and quantities].
369
+ */
370
+ planOrgan(organ, capacity, overrideModifiers = {}) {
371
+ const submenu = this.menu.filter((menuItem) => menuItem.organ === organ && myLevel() >= menuItem.item.levelreq);
372
+ const knapsackValues = submenu.map((menuItem) => [
373
+ ...this.consumptionHelpersAndValue(menuItem, overrideModifiers),
374
+ menuItem.size,
375
+ menuItem.maximum,
376
+ ]);
377
+ return knapsack(knapsackValues, capacity);
378
+ }
379
+ /**
380
+ * Plan organs.
381
+ *
382
+ * @param organCapacities Organ capacities.
383
+ * @param overrideModifiers Overrides for consumption modifiers, if any.
384
+ * @returns Pair of [value, menu items and quantities].
385
+ */
386
+ planOrgans(organCapacities, overrideModifiers = {}) {
387
+ const valuePlans = organCapacities.map(([organ, capacity]) => this.planOrgan(organ, capacity, overrideModifiers));
388
+ return [
389
+ sum(valuePlans, ([value]) => value),
390
+ [].concat(...valuePlans.map(([, plan]) => plan)),
391
+ ];
392
+ }
393
+ /**
394
+ * Plan organs, retrying with and without each trial item. Runtime is
395
+ * proportional to 2 ^ trialItems.length.
396
+ *
397
+ * @param organCapacities Organ capacities.
398
+ * @param trialItems Items to rerun solver with and without.
399
+ * @param overrideModifiers Overrides for consumption modifiers, if any.
400
+ * @returns Pair of [value, menu items and quantities].
401
+ */
402
+ planOrgansWithTrials(organCapacities, trialItems, overrideModifiers) {
403
+ if (trialItems.length === 0) {
404
+ return this.planOrgans(organCapacities, overrideModifiers);
405
+ }
406
+ const [trialItem, organSizes] = trialItems[0];
407
+ if (trialItem.maximum !== undefined && trialItem.maximum <= 0) {
408
+ return this.planOrgansWithTrials(organCapacities, trialItems.slice(1), overrideModifiers);
409
+ }
410
+ const organCapacitiesWithMap = new Map(organCapacities);
411
+ for (const [organ, size] of organSizes) {
412
+ const current = organCapacitiesWithMap.get(organ);
413
+ if (current === undefined) {
414
+ // Organs with no capacity are excluded from the organCapacities map, so this item excluded from the trial.
415
+ // Solves the problem with the diet offering to eat toasted brie after exiting Shrunken Adventurer with 20/15 fullness.
416
+ return this.planOrgansWithTrials(organCapacities, trialItems.slice(1), overrideModifiers);
417
+ }
418
+ organCapacitiesWithMap.set(organ, current - size);
419
+ }
420
+ const organCapacitiesWith = [...organCapacitiesWithMap];
421
+ const isRefinedPalate = (trialItem.item === $item `pocket wish` &&
422
+ trialItem.effect === $effect `Refined Palate`) ||
423
+ trialItem.item === $item `toasted brie`;
424
+ const isGarish = (trialItem.item === $item `pocket wish` &&
425
+ trialItem.effect === $effect `Gar-ish`) ||
426
+ trialItem.item === $item `potion of the field gar`;
427
+ const [valueWithout, planWithout] = this.planOrgansWithTrials(organCapacities, trialItems.slice(1), overrideModifiers);
428
+ const [valueWith, planWith] = this.planOrgansWithTrials(organCapacitiesWith, trialItems.slice(1), {
429
+ ...overrideModifiers,
430
+ ...(isRefinedPalate ? { refinedPalate: true } : {}),
431
+ ...(isGarish ? { garish: true } : {}),
432
+ });
433
+ const [helpersAndItem, value] = this.consumptionHelpersAndValue(trialItem, {});
434
+ return valueWithout > valueWith + value
435
+ ? [valueWithout, planWithout]
436
+ : [valueWith + value, [...planWith, [helpersAndItem, 1]]];
437
+ }
438
+ }
439
+ /**
440
+ * Because the knapsack solver is one-dimensional only, any items that touch
441
+ * multiple organs have to be treated specially. What we do is run the knapsack
442
+ * solver multiple times, trying with + without each interacting item.
443
+ */
444
+ const interactingItems = [
445
+ [
446
+ $item `spice melange`,
447
+ [
448
+ ["food", -3],
449
+ ["booze", -3],
450
+ ],
451
+ ],
452
+ [
453
+ $item `Ultra Mega Sour Ball`,
454
+ [
455
+ ["food", -3],
456
+ ["booze", -3],
457
+ ],
458
+ ],
459
+ [
460
+ $item `The Plumber's mushroom stew`,
461
+ [
462
+ ["food", 3],
463
+ ["booze", -1],
464
+ ],
465
+ ],
466
+ [
467
+ $item `The Mad Liquor`,
468
+ [
469
+ ["food", -1],
470
+ ["booze", 3],
471
+ ],
472
+ ],
473
+ [
474
+ $item `Doc Clock's thyme cocktail`,
475
+ [
476
+ ["food", -2],
477
+ ["booze", 4],
478
+ ],
479
+ ],
480
+ [
481
+ $item `Mr. Burnsger`,
482
+ [
483
+ ["food", 4],
484
+ ["booze", -2],
485
+ ],
486
+ ],
487
+ [$effect `Refined Palate`, []],
488
+ [$item `toasted brie`, [["food", 2]]],
489
+ [$effect `Gar-ish`, []],
490
+ [$item `potion of the field gar`, []],
491
+ ];
492
+ /**
493
+ * Plan out an optimal diet using a knapsack algorithm.
494
+ *
495
+ * @param mpa Meat per adventure value.
496
+ * @param menu Array of MenuItems to consider for diet purposes.
497
+ * @param organCapacities Optional override of each organ's capacity.
498
+ * @returns Array of [menu item and helpers, count].
499
+ */
500
+ function planDiet(mpa, menu, organCapacities = [
501
+ ["food", null],
502
+ ["booze", null],
503
+ ["spleen item", null],
504
+ ]) {
505
+ // FIXME: Figure out a better way to handle overfull organs (e.g. coming out of Ed).
506
+ const resolvedOrganCapacities = organCapacities.map(([organ, size]) => [
507
+ organ,
508
+ size ??
509
+ (organ === "food"
510
+ ? fullnessLimit() - myFullness()
511
+ : organ === "booze"
512
+ ? inebrietyLimit() - myInebriety()
513
+ : organ === "spleen item"
514
+ ? spleenLimit() - mySpleenUse()
515
+ : 0),
516
+ ]);
517
+ /**
518
+ * Per above description, separate out items with cross-organ interaction
519
+ * ("interacting items") for special treatment. These will be checked by
520
+ * running the solver several times.
521
+ */
522
+ const includedInteractingItems = menu
523
+ .map((menuItem) => {
524
+ const interacting = interactingItems.find(([itemOrEffect]) => menuItem.item === itemOrEffect ||
525
+ (menuItem.item === $item `pocket wish` &&
526
+ menuItem.effect === itemOrEffect));
527
+ if (interacting) {
528
+ const [, organSizes] = interacting;
529
+ return [menuItem, organSizes];
530
+ }
531
+ else {
532
+ return null;
533
+ }
534
+ })
535
+ .filter((value) => value !== null);
536
+ // Filter out interacting items from natural consideration.
537
+ const dietPlanner = new DietPlanner(mpa, menu.filter((menuItem) => !includedInteractingItems.some(([interacting]) => interacting === menuItem)));
538
+ /**
539
+ * Because our knapsack solver is one-dimensional, we have to consider
540
+ * each organ separately. Since there are no spleen items that affect
541
+ * stomach/liver, we consider those two first, with an approximation of the
542
+ * value of spleen-cleaning. Afterwards, we see how much spleen we have and
543
+ * plan that.
544
+ */
545
+ const [, planFoodBooze] = dietPlanner.planOrgansWithTrials(resolvedOrganCapacities.filter(([organ, capacity]) => ["food", "booze"].includes(organ) && capacity >= 0), includedInteractingItems, {});
546
+ const spleenCapacity = resolvedOrganCapacities.find(([organ]) => organ === "spleen item");
547
+ if (spleenCapacity) {
548
+ // Count sliders and pickle juice, figure out how much extra spleen we got.
549
+ const additionalSpleen = sum(planFoodBooze, ([items, number]) => items.some((menuItem) => $items `jar of fermented pickle juice, extra-greasy slider`.includes(menuItem.item))
550
+ ? 5 * number
551
+ : 0);
552
+ const [, availableSpleen] = spleenCapacity;
553
+ const [, planSpleen] = dietPlanner.planOrgan("spleen item", availableSpleen + additionalSpleen);
554
+ return [...planFoodBooze, ...planSpleen];
555
+ }
556
+ else {
557
+ return planFoodBooze;
558
+ }
559
+ }
560
+ class DietEntry {
561
+ menuItems;
562
+ quantity;
563
+ constructor(menuItems, quantity) {
564
+ this.menuItems = menuItems;
565
+ this.quantity = quantity;
566
+ }
567
+ target() {
568
+ return this.menuItems[this.menuItems.length - 1];
569
+ }
570
+ helpers() {
571
+ if (this.menuItems.length > 1) {
572
+ return this.menuItems.slice(0, -1);
573
+ }
574
+ return [];
575
+ }
576
+ expectedAdventures(diet) {
577
+ {
578
+ if (this.menuItems.length === 0 || this.quantity === 0) {
579
+ return 0;
580
+ }
581
+ else {
582
+ const items = this.menuItems.map((m) => m.item);
583
+ const targetItem = this.menuItems[this.menuItems.length - 1].item;
584
+ const fork = itemType(targetItem) === "food" &&
585
+ items.includes($item `Ol' Scratch's salad fork`);
586
+ const mug = itemType(targetItem) === "booze" &&
587
+ items.includes($item `Frosty's frosty mug`);
588
+ return (this.quantity *
589
+ expectedAdventures(this.menuItems[this.menuItems.length - 1].item, {
590
+ forkMug: fork || mug,
591
+ seasoning: items.includes($item `Special Seasoning`),
592
+ whetStone: items.includes($item `whet stone`),
593
+ mayoflex: items.includes(Mayo.flex),
594
+ refinedPalate: diet.refinedPalate,
595
+ garish: diet.garish,
596
+ saucemaven: diet.saucemaven,
597
+ pinkyRing: diet.pinkyRing,
598
+ tuxedoShirt: diet.tuxedoShirt,
599
+ }));
600
+ }
601
+ }
602
+ }
603
+ expectedValue(mpa, diet, method = "gross") {
604
+ const gross = mpa * this.expectedAdventures(diet) +
605
+ this.quantity *
606
+ sum(this.menuItems, (menuItem) => menuItem.additionalValue ?? 0);
607
+ if (method === "gross") {
608
+ return gross;
609
+ }
610
+ else {
611
+ return gross - this.expectedPrice();
612
+ }
613
+ }
614
+ expectedPrice() {
615
+ return this.quantity * sum(this.menuItems, (menuItem) => menuItem.price());
616
+ }
617
+ }
618
+ /**
619
+ * A representation of a potential diet
620
+ */
621
+ export class Diet {
622
+ entries;
623
+ constructor(entries = []) {
624
+ this.entries = entries;
625
+ }
626
+ get refinedPalate() {
627
+ return this.entries.some((dietEntry) => dietEntry.menuItems.some((trialItem) => (trialItem.item === $item `pocket wish` &&
628
+ trialItem.effect === $effect `Refined Palate`) ||
629
+ trialItem.item === $item `toasted brie`));
630
+ }
631
+ get garish() {
632
+ return this.entries.some((dietEntry) => dietEntry.menuItems.some((trialItem) => (trialItem.item === $item `pocket wish` &&
633
+ trialItem.effect === $effect `Gar-ish`) ||
634
+ trialItem.item === $item `potion of the field gar`));
635
+ }
636
+ get saucemaven() {
637
+ return have($skill `Saucemaven`);
638
+ }
639
+ get tuxedoShirt() {
640
+ return have($item `tuxedo shirt`) && canEquip($item `tuxedo shirt`);
641
+ }
642
+ get pinkyRing() {
643
+ return have($item `mafia pinky ring`) && canEquip($item `mafia pinky ring`);
644
+ }
645
+ expectedAdventures() {
646
+ return sum(this.entries, (dietEntry) => dietEntry.expectedAdventures(this));
647
+ }
648
+ expectedValue(mpa, method = "gross") {
649
+ return sum(this.entries, (dietEntry) => dietEntry.expectedValue(mpa, this, method));
650
+ }
651
+ expectedPrice() {
652
+ return sum(this.entries, (dietEntry) => dietEntry.expectedPrice());
653
+ }
654
+ copy() {
655
+ return new Diet([...this.entries]);
656
+ }
657
+ static from(rawDiet) {
658
+ const diet = rawDiet.map((item) => {
659
+ const [menuItems, quantity] = item;
660
+ return new DietEntry(menuItems, quantity);
661
+ });
662
+ return new Diet(diet);
663
+ }
664
+ static plan(mpa, menu, organCapacities = {
665
+ food: "auto",
666
+ booze: "auto",
667
+ spleen: "auto",
668
+ }) {
669
+ const { food, booze, spleen } = organCapacities;
670
+ const plannerCapacity = [];
671
+ if (food) {
672
+ plannerCapacity.push(["food", food === "auto" ? null : food]);
673
+ }
674
+ if (booze) {
675
+ plannerCapacity.push(["booze", booze === "auto" ? null : booze]);
676
+ }
677
+ if (spleen) {
678
+ plannerCapacity.push(["spleen item", spleen === "auto" ? null : spleen]);
679
+ }
680
+ return Diet.from(planDiet(mpa, menu, plannerCapacity));
681
+ }
682
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Solve the knapsack problem.
3
+ *
4
+ * @param values Array of {[item, value, weight, maximum]} tuples for knapsack parameter.
5
+ * @param capacity Capacity of knapsack.
6
+ * @returns Tuple {[totalValue, items]} of selected items and total value of those items.
7
+ */
8
+ export declare function knapsack<T>(values: [T, number, number, number?][], capacity: number): [number, [T, number][]];