libram 0.4.3 → 0.4.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 +7 -4
- package/dist/Copier.js +5 -1
- package/dist/Kmail.js +18 -12
- package/dist/Path.js +9 -0
- package/dist/combat.d.ts +22 -1
- package/dist/combat.js +42 -8
- package/dist/diet/index.d.ts +9 -0
- package/dist/diet/index.js +190 -70
- package/dist/diet/knapsack.js +16 -2
- package/dist/freerun.js +4 -0
- package/dist/lib.d.ts +2 -0
- package/dist/lib.js +22 -2
- package/dist/logger.js +1 -3
- package/dist/maximize.js +12 -12
- package/dist/mood.d.ts +5 -0
- package/dist/mood.js +44 -15
- package/dist/property.js +5 -1
- package/dist/propertyTypes.d.ts +5 -4
- package/dist/propertyTyping.d.ts +4 -3
- package/dist/propertyTyping.js +4 -0
- package/dist/resources/2013/Florist.js +5 -0
- package/dist/resources/2014/DNALab.d.ts +47 -0
- package/dist/resources/2014/DNALab.js +154 -0
- package/dist/resources/2015/MayoClinic.d.ts +2 -0
- package/dist/resources/2015/MayoClinic.js +8 -2
- package/dist/resources/2017/AsdonMartin.d.ts +35 -0
- package/dist/resources/2017/AsdonMartin.js +112 -0
- package/dist/resources/2019/Snapper.js +2 -3
- package/dist/resources/index.d.ts +3 -1
- package/dist/resources/index.js +3 -1
- package/package.json +9 -7
- package/dist/Dungeon.d.ts +0 -37
- package/dist/Dungeon.js +0 -87
- package/dist/ring-buffer.d.ts +0 -24
- package/dist/ring-buffer.js +0 -135
package/dist/Clan.js
CHANGED
|
@@ -9,6 +9,7 @@ import { getFoldGroup } from "./lib";
|
|
|
9
9
|
import logger from "./logger";
|
|
10
10
|
import { arrayToCountedMap, countedMapToArray, countedMapToString, notNull, parseNumber, } from "./utils";
|
|
11
11
|
export class ClanError extends Error {
|
|
12
|
+
reason;
|
|
12
13
|
constructor(message, reason) {
|
|
13
14
|
super(message);
|
|
14
15
|
this.reason = reason;
|
|
@@ -38,10 +39,8 @@ const toPlayerId = (player) => typeof player === "string" ? getPlayerId(player)
|
|
|
38
39
|
const LOG_FAX_PATTERN = /(\d{2}\/\d{2}\/\d{2}, \d{2}:\d{2}(?:AM|PM): )<a [^>]+>([^<]+)<\/a>(?: faxed in a (?<monster>.*?))<br>/;
|
|
39
40
|
const WHITELIST_DEGREE_PATTERN = /(?<name>.*?) \(°(?<degree>\d+)\)/;
|
|
40
41
|
export class Clan {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
this.name = name;
|
|
44
|
-
}
|
|
42
|
+
id;
|
|
43
|
+
name;
|
|
45
44
|
static _join(id) {
|
|
46
45
|
const result = visitUrl(`showclan.php?recruiter=1&whichclan=${id}&pwd&whichclan=${id}&action=joinclan&apply=Apply+to+this+Clan&confirm=on`);
|
|
47
46
|
if (!result.includes("clanhalltop.gif")) {
|
|
@@ -143,6 +142,10 @@ export class Clan {
|
|
|
143
142
|
return new Clan(id, name);
|
|
144
143
|
});
|
|
145
144
|
}
|
|
145
|
+
constructor(id, name) {
|
|
146
|
+
this.id = id;
|
|
147
|
+
this.name = name;
|
|
148
|
+
}
|
|
146
149
|
/**
|
|
147
150
|
* Join clan
|
|
148
151
|
*/
|
package/dist/Copier.js
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
export class Copier {
|
|
2
|
+
couldCopy;
|
|
3
|
+
prepare;
|
|
4
|
+
canCopy;
|
|
5
|
+
copiedMonster;
|
|
6
|
+
fightCopy = null;
|
|
2
7
|
constructor(couldCopy, prepare, canCopy, copiedMonster, fightCopy) {
|
|
3
|
-
this.fightCopy = null;
|
|
4
8
|
this.couldCopy = couldCopy;
|
|
5
9
|
this.prepare = prepare;
|
|
6
10
|
this.canCopy = canCopy;
|
package/dist/Kmail.js
CHANGED
|
@@ -2,18 +2,12 @@ import "core-js/modules/es.object.entries";
|
|
|
2
2
|
import { extractItems, extractMeat, isGiftable, toInt, visitUrl, } from "kolmafia";
|
|
3
3
|
import { arrayToCountedMap, chunk } from "./utils";
|
|
4
4
|
export default class Kmail {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
this.date = date;
|
|
12
|
-
this.type = rawKmail.type;
|
|
13
|
-
this.senderId = Number(rawKmail.fromid);
|
|
14
|
-
this.senderName = rawKmail.fromname;
|
|
15
|
-
this.rawMessage = rawKmail.message;
|
|
16
|
-
}
|
|
5
|
+
id;
|
|
6
|
+
date;
|
|
7
|
+
type;
|
|
8
|
+
senderId;
|
|
9
|
+
senderName;
|
|
10
|
+
rawMessage;
|
|
17
11
|
/**
|
|
18
12
|
* Parses a kmail from KoL's native format
|
|
19
13
|
*
|
|
@@ -99,6 +93,18 @@ export default class Kmail {
|
|
|
99
93
|
const baseUrl = `town_sendgift.php?action=Yep.&pwd&fromwhere=0¬e=${message}&insidenote=${insideNote}&towho=${to}`;
|
|
100
94
|
return Kmail._genericSend(to, message, items, meat, 3, (m, itemsQuery, chunkSize) => `${baseUrl}&whichpackage=${chunkSize}${itemsQuery ? `&${itemsQuery}` : ""}&sendmeat=${m}`, ">Package sent.</");
|
|
101
95
|
}
|
|
96
|
+
constructor(rawKmail) {
|
|
97
|
+
const date = new Date(rawKmail.localtime);
|
|
98
|
+
// Date come from KoL formatted with YY and so will be parsed 19YY, which is wrong.
|
|
99
|
+
// We can safely add 100 because if 19YY was a leap year, 20YY will be too!
|
|
100
|
+
date.setFullYear(date.getFullYear() + 100);
|
|
101
|
+
this.id = Number(rawKmail.id);
|
|
102
|
+
this.date = date;
|
|
103
|
+
this.type = rawKmail.type;
|
|
104
|
+
this.senderId = Number(rawKmail.fromid);
|
|
105
|
+
this.senderName = rawKmail.fromname;
|
|
106
|
+
this.rawMessage = rawKmail.message;
|
|
107
|
+
}
|
|
102
108
|
/**
|
|
103
109
|
* Delete the kmail
|
|
104
110
|
*
|
package/dist/Path.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { $classes } from "./template-string";
|
|
2
2
|
export class Path {
|
|
3
|
+
name;
|
|
4
|
+
id;
|
|
5
|
+
hasAllPerms; //here, we define avatar-ness around being its own class
|
|
6
|
+
hasCampground;
|
|
7
|
+
hasTerrarium;
|
|
8
|
+
stomachSize;
|
|
9
|
+
liverSize; //Defined as the lowest inebriety that makes you unable to drink more, just to make it fifteens across the board
|
|
10
|
+
spleenSize;
|
|
11
|
+
classes;
|
|
3
12
|
/**
|
|
4
13
|
*
|
|
5
14
|
* @param name Name of path
|
package/dist/combat.d.ts
CHANGED
|
@@ -120,9 +120,10 @@ export declare class Macro {
|
|
|
120
120
|
* Create a new macro with a condition evaluated at the time of building the macro.
|
|
121
121
|
* @param condition The JS condition.
|
|
122
122
|
* @param ifTrue Continuation to add if the condition is true.
|
|
123
|
+
* @param ifFalse Optional input to turn this into an if...else statement.
|
|
123
124
|
* @returns {Macro} This object itself.
|
|
124
125
|
*/
|
|
125
|
-
static externalIf<T extends Macro>(this: Constructor<T>, condition: boolean, ifTrue: string | Macro): T;
|
|
126
|
+
static externalIf<T extends Macro>(this: Constructor<T>, condition: boolean, ifTrue: string | Macro, ifFalse?: string | Macro): T;
|
|
126
127
|
/**
|
|
127
128
|
* Add a repeat step to the macro.
|
|
128
129
|
* @returns {Macro} This object itself.
|
|
@@ -198,6 +199,26 @@ export declare class Macro {
|
|
|
198
199
|
* @returns {Macro} This object itself.
|
|
199
200
|
*/
|
|
200
201
|
static attack<T extends Macro>(this: Constructor<T>): T;
|
|
202
|
+
/**
|
|
203
|
+
* Create an if_ statement based on what holiday of loathing it currently is. On non-holidays, returns the original macro, unmutated.
|
|
204
|
+
* @param macro The macro to place in the if_ statement
|
|
205
|
+
*/
|
|
206
|
+
ifHolidayWanderer(macro: Macro): this;
|
|
207
|
+
/**
|
|
208
|
+
* Create a new macro starting with an ifHolidayWanderer step.
|
|
209
|
+
* @param macro The macro to place inside the if_ statement
|
|
210
|
+
*/
|
|
211
|
+
static ifHolidayWanderer<T extends Macro>(this: Constructor<T>, macro: Macro): T;
|
|
212
|
+
/**
|
|
213
|
+
* Create an if_ statement based on what holiday of loathing it currently is. On non-holidays, returns the original macro, with the input macro appended.
|
|
214
|
+
* @param macro The macro to place in the if_ statement.
|
|
215
|
+
*/
|
|
216
|
+
ifNotHolidayWanderer(macro: Macro): this;
|
|
217
|
+
/**
|
|
218
|
+
* Create a new macro starting with an ifNotHolidayWanderer step.
|
|
219
|
+
* @param macro The macro to place inside the if_ statement
|
|
220
|
+
*/
|
|
221
|
+
static ifNotHolidayWanderer<T extends Macro>(this: Constructor<T>, macro: Macro): T;
|
|
201
222
|
}
|
|
202
223
|
/**
|
|
203
224
|
* Adventure in a location and handle all combats with a given macro.
|
package/dist/combat.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { adv1, choiceFollowsFight, getAutoAttack, inMultiFight, removeProperty, runCombat, setAutoAttack, toInt, urlEncode, visitUrl, xpath, } from "kolmafia";
|
|
2
2
|
import { $items, $skills } from "./template-string";
|
|
3
3
|
import { get, set } from "./property";
|
|
4
|
+
import { getTodaysHolidayWanderers } from "./lib";
|
|
4
5
|
const MACRO_NAME = "Script Autoattack Macro";
|
|
5
6
|
/**
|
|
6
7
|
* Get the KoL native ID of the macro with name Script Autoattack Macro.
|
|
@@ -68,9 +69,10 @@ export class InvalidMacroError extends Error {
|
|
|
68
69
|
* For example, you can do `Macro.skill('Saucestorm').attack()`.
|
|
69
70
|
*/
|
|
70
71
|
export class Macro {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
static SAVED_MACRO_PROPERTY = "libram_savedMacro";
|
|
73
|
+
static cachedMacroId = null;
|
|
74
|
+
static cachedAutoAttack = null;
|
|
75
|
+
components = [];
|
|
74
76
|
/**
|
|
75
77
|
* Convert macro to string.
|
|
76
78
|
*/
|
|
@@ -256,10 +258,11 @@ export class Macro {
|
|
|
256
258
|
* Create a new macro with a condition evaluated at the time of building the macro.
|
|
257
259
|
* @param condition The JS condition.
|
|
258
260
|
* @param ifTrue Continuation to add if the condition is true.
|
|
261
|
+
* @param ifFalse Optional input to turn this into an if...else statement.
|
|
259
262
|
* @returns {Macro} This object itself.
|
|
260
263
|
*/
|
|
261
|
-
static externalIf(condition, ifTrue) {
|
|
262
|
-
return new this().externalIf(condition, ifTrue);
|
|
264
|
+
static externalIf(condition, ifTrue, ifFalse) {
|
|
265
|
+
return new this().externalIf(condition, ifTrue, ifFalse);
|
|
263
266
|
}
|
|
264
267
|
/**
|
|
265
268
|
* Add a repeat step to the macro.
|
|
@@ -372,10 +375,41 @@ export class Macro {
|
|
|
372
375
|
static attack() {
|
|
373
376
|
return new this().attack();
|
|
374
377
|
}
|
|
378
|
+
/**
|
|
379
|
+
* Create an if_ statement based on what holiday of loathing it currently is. On non-holidays, returns the original macro, unmutated.
|
|
380
|
+
* @param macro The macro to place in the if_ statement
|
|
381
|
+
*/
|
|
382
|
+
ifHolidayWanderer(macro) {
|
|
383
|
+
const todaysWanderers = getTodaysHolidayWanderers();
|
|
384
|
+
if (todaysWanderers.length === 0)
|
|
385
|
+
return this;
|
|
386
|
+
return this.if_(todaysWanderers.map((monster) => `monsterid ${monster.id}`).join(" || "), macro);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Create a new macro starting with an ifHolidayWanderer step.
|
|
390
|
+
* @param macro The macro to place inside the if_ statement
|
|
391
|
+
*/
|
|
392
|
+
static ifHolidayWanderer(macro) {
|
|
393
|
+
return new this().ifHolidayWanderer(macro);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Create an if_ statement based on what holiday of loathing it currently is. On non-holidays, returns the original macro, with the input macro appended.
|
|
397
|
+
* @param macro The macro to place in the if_ statement.
|
|
398
|
+
*/
|
|
399
|
+
ifNotHolidayWanderer(macro) {
|
|
400
|
+
const todaysWanderers = getTodaysHolidayWanderers();
|
|
401
|
+
if (todaysWanderers.length === 0)
|
|
402
|
+
return this.step(macro);
|
|
403
|
+
return this.if_(todaysWanderers.map((monster) => `!monsterid ${monster.id}`).join(" && "), macro);
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Create a new macro starting with an ifNotHolidayWanderer step.
|
|
407
|
+
* @param macro The macro to place inside the if_ statement
|
|
408
|
+
*/
|
|
409
|
+
static ifNotHolidayWanderer(macro) {
|
|
410
|
+
return new this().ifNotHolidayWanderer(macro);
|
|
411
|
+
}
|
|
375
412
|
}
|
|
376
|
-
Macro.SAVED_MACRO_PROPERTY = "libram_savedMacro";
|
|
377
|
-
Macro.cachedMacroId = null;
|
|
378
|
-
Macro.cachedAutoAttack = null;
|
|
379
413
|
/**
|
|
380
414
|
* Adventure in a location and handle all combats with a given macro.
|
|
381
415
|
* To use this function you will need to create a consult script that runs Macro.load().submit() and a CCS that calls that consult script.
|
package/dist/diet/index.d.ts
CHANGED
|
@@ -13,6 +13,15 @@ export declare class MenuItem {
|
|
|
13
13
|
additionalValue?: number;
|
|
14
14
|
wishEffect?: Effect;
|
|
15
15
|
static defaultOptions: Map<Item, MenuItemOptions>;
|
|
16
|
+
/**
|
|
17
|
+
* Construct a new menu item, possibly with extra properties. Items in MenuItem.defaultOptions have intelligent defaults.
|
|
18
|
+
* @param item Item to add to menu.
|
|
19
|
+
* @param options.organ Designate item as belonging to a specific organ.
|
|
20
|
+
* @param options.size Override item organ size. Necessary for any non-food/booze/spleen item.
|
|
21
|
+
* @param options.maximum Maximum uses remaining today, or "auto" to check dailyusesleft Mafia property.
|
|
22
|
+
* @param options.additionalValue Additional value (positive) or cost (negative) to consider with item, e.g. from buffs.
|
|
23
|
+
* @param options.wishEffect If item is a pocket wish, effect to wish for.
|
|
24
|
+
*/
|
|
16
25
|
constructor(item: Item, options?: MenuItemOptions);
|
|
17
26
|
equals(other: MenuItem): boolean;
|
|
18
27
|
toString(): string;
|
package/dist/diet/index.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { fullnessLimit, getWorkshed, inebrietyLimit, itemType, mallPrice, mallPrices, myFullness, myPrimestat, mySpleenUse, npcPrice, spleenLimit, } from "kolmafia";
|
|
1
|
+
import { canEquip, fullnessLimit, getWorkshed, inebrietyLimit, itemType, mallPrice, mallPrices, myFullness, myInebriety, myLevel, myPrimestat, mySpleenUse, npcPrice, spleenLimit, } from "kolmafia";
|
|
2
2
|
import { knapsack } from "./knapsack";
|
|
3
3
|
import { have } from "../lib";
|
|
4
|
+
import { get as getModifier } from "../modifier";
|
|
4
5
|
import { get } from "../property";
|
|
5
6
|
import { $effect, $item, $items, $skill, $stat } from "../template-string";
|
|
6
7
|
import { sum } from "../utils";
|
|
7
|
-
|
|
8
|
-
//
|
|
8
|
+
function isMonday() {
|
|
9
|
+
// Checking Tuesday's ruby is a hack to see if it's Monday in Arizona.
|
|
10
|
+
return getModifier("Muscle Percent", $item `Tuesday's ruby`) > 0;
|
|
11
|
+
}
|
|
12
|
+
// TODO: Include Salty Mouth and potentially other modifiers.
|
|
9
13
|
function expectedAdventures(item, modifiers) {
|
|
10
14
|
if (item.adventures === "")
|
|
11
15
|
return 0;
|
|
@@ -18,6 +22,7 @@ function expectedAdventures(item, modifiers) {
|
|
|
18
22
|
(itemType(item) === "booze" && item.notes?.includes("BEER"))
|
|
19
23
|
? 1.5
|
|
20
24
|
: 1.3;
|
|
25
|
+
const garish = modifiers.garish && item.notes?.includes("LASAGNA") && !isMonday();
|
|
21
26
|
const refinedPalate = modifiers.refinedPalate && item.notes?.includes("WINE");
|
|
22
27
|
const pinkyRing = modifiers.pinkyRing && item.notes?.includes("WINE");
|
|
23
28
|
return (sum(interpolated, (baseAdventures) => {
|
|
@@ -25,6 +30,11 @@ function expectedAdventures(item, modifiers) {
|
|
|
25
30
|
if (modifiers.forkMug) {
|
|
26
31
|
adventures = Math.floor(adventures * forkMugMultiplier);
|
|
27
32
|
}
|
|
33
|
+
if (item.notes?.includes("SAUCY") && modifiers.saucemaven) {
|
|
34
|
+
adventures += myPrimestat() === $stat `Mysticality` ? 5 : 3;
|
|
35
|
+
}
|
|
36
|
+
if (garish)
|
|
37
|
+
adventures += 5;
|
|
28
38
|
if (refinedPalate)
|
|
29
39
|
adventures = Math.floor(adventures * 1.25);
|
|
30
40
|
if (pinkyRing)
|
|
@@ -32,17 +42,81 @@ function expectedAdventures(item, modifiers) {
|
|
|
32
42
|
if (item.notes?.includes("MARTINI") && modifiers.tuxedoShirt) {
|
|
33
43
|
adventures += 2;
|
|
34
44
|
}
|
|
35
|
-
if (have($skill `Saucemaven`) && item.notes?.includes("SAUCY")) {
|
|
36
|
-
adventures += myPrimestat() === $stat `Mysticality` ? 5 : 3;
|
|
37
|
-
}
|
|
38
|
-
if (itemType(item) === "food" && modifiers.seasoning)
|
|
39
|
-
adventures++;
|
|
40
45
|
if (itemType(item) === "food" && modifiers.mayoflex)
|
|
41
46
|
adventures++;
|
|
47
|
+
if (itemType(item) === "food" && modifiers.seasoning)
|
|
48
|
+
adventures++;
|
|
42
49
|
return adventures;
|
|
43
50
|
}) / interpolated.length);
|
|
44
51
|
}
|
|
45
52
|
export class MenuItem {
|
|
53
|
+
item;
|
|
54
|
+
organ;
|
|
55
|
+
size;
|
|
56
|
+
maximum;
|
|
57
|
+
additionalValue;
|
|
58
|
+
wishEffect;
|
|
59
|
+
static defaultOptions = new Map([
|
|
60
|
+
[
|
|
61
|
+
$item `distention pill`,
|
|
62
|
+
{
|
|
63
|
+
organ: "food",
|
|
64
|
+
maximum: !have($item `distention pill`) || get("_distentionPillUsed") ? 0 : 1,
|
|
65
|
+
size: -1,
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
$item `synthetic dog hair pill`,
|
|
70
|
+
{
|
|
71
|
+
organ: "booze",
|
|
72
|
+
maximum: !have($item `synthetic dog hair pill`) ||
|
|
73
|
+
get("_syntheticDogHairPillUsed")
|
|
74
|
+
? 0
|
|
75
|
+
: 1,
|
|
76
|
+
size: -1,
|
|
77
|
+
},
|
|
78
|
+
],
|
|
79
|
+
[
|
|
80
|
+
$item `cuppa Voraci tea`,
|
|
81
|
+
{ organ: "food", maximum: get("_voraciTeaUsed") ? 0 : 1, size: -1 },
|
|
82
|
+
],
|
|
83
|
+
[
|
|
84
|
+
$item `cuppa Sobrie tea`,
|
|
85
|
+
{ organ: "booze", maximum: get("_sobrieTeaUsed") ? 0 : 1, size: -1 },
|
|
86
|
+
],
|
|
87
|
+
[
|
|
88
|
+
$item `mojo filter`,
|
|
89
|
+
{
|
|
90
|
+
organ: "spleen item",
|
|
91
|
+
maximum: 3 - get("currentMojoFilters"),
|
|
92
|
+
size: -1,
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
[$item `spice melange`, { maximum: get("spiceMelangeUsed") ? 0 : 1 }],
|
|
96
|
+
[
|
|
97
|
+
$item `Ultra Mega Sour Ball`,
|
|
98
|
+
{ maximum: get("_ultraMegaSourBallUsed") ? 0 : 1 },
|
|
99
|
+
],
|
|
100
|
+
[
|
|
101
|
+
$item `The Plumber's mushroom stew`,
|
|
102
|
+
{ maximum: get("_plumbersMushroomStewEaten") ? 0 : 1 },
|
|
103
|
+
],
|
|
104
|
+
[$item `The Mad Liquor`, { maximum: get("_madLiquorDrunk") ? 0 : 1 }],
|
|
105
|
+
[
|
|
106
|
+
$item `Doc Clock's thyme cocktail`,
|
|
107
|
+
{ maximum: get("_docClocksThymeCocktailDrunk") ? 0 : 1 },
|
|
108
|
+
],
|
|
109
|
+
[$item `Mr. Burnsger`, { maximum: get("_mrBurnsgerEaten") ? 0 : 1 }],
|
|
110
|
+
]);
|
|
111
|
+
/**
|
|
112
|
+
* Construct a new menu item, possibly with extra properties. Items in MenuItem.defaultOptions have intelligent defaults.
|
|
113
|
+
* @param item Item to add to menu.
|
|
114
|
+
* @param options.organ Designate item as belonging to a specific organ.
|
|
115
|
+
* @param options.size Override item organ size. Necessary for any non-food/booze/spleen item.
|
|
116
|
+
* @param options.maximum Maximum uses remaining today, or "auto" to check dailyusesleft Mafia property.
|
|
117
|
+
* @param options.additionalValue Additional value (positive) or cost (negative) to consider with item, e.g. from buffs.
|
|
118
|
+
* @param options.wishEffect If item is a pocket wish, effect to wish for.
|
|
119
|
+
*/
|
|
46
120
|
constructor(item, options = {}) {
|
|
47
121
|
const { size, organ, maximum, additionalValue, wishEffect } = {
|
|
48
122
|
...options,
|
|
@@ -74,38 +148,19 @@ export class MenuItem {
|
|
|
74
148
|
return npcPrice(this.item) > 0 ? npcPrice(this.item) : mallPrice(this.item);
|
|
75
149
|
}
|
|
76
150
|
}
|
|
77
|
-
MenuItem.defaultOptions = new Map([
|
|
78
|
-
[$item `Mr. Burnsger`, { maximum: "auto" }],
|
|
79
|
-
[
|
|
80
|
-
$item `distention pill`,
|
|
81
|
-
{
|
|
82
|
-
organ: "food",
|
|
83
|
-
maximum: "auto",
|
|
84
|
-
size: -1,
|
|
85
|
-
},
|
|
86
|
-
],
|
|
87
|
-
[
|
|
88
|
-
$item `synthetic dog hair pill`,
|
|
89
|
-
{ organ: "booze", maximum: "auto", size: -1 },
|
|
90
|
-
],
|
|
91
|
-
[$item `cuppa Voraci tea`, { organ: "food", maximum: "auto", size: -1 }],
|
|
92
|
-
[$item `cuppa Sobrie tea`, { organ: "booze", maximum: "auto", size: -1 }],
|
|
93
|
-
[
|
|
94
|
-
$item `mojo filter`,
|
|
95
|
-
{
|
|
96
|
-
organ: "spleen item",
|
|
97
|
-
maximum: 3 - get("currentMojoFilters"),
|
|
98
|
-
size: -1,
|
|
99
|
-
},
|
|
100
|
-
],
|
|
101
|
-
]);
|
|
102
151
|
const organs = ["food", "booze", "spleen item"];
|
|
103
152
|
function isOrgan(x) {
|
|
104
153
|
return organs.includes(x);
|
|
105
154
|
}
|
|
106
155
|
class DietPlanner {
|
|
156
|
+
mpa;
|
|
157
|
+
menu;
|
|
158
|
+
fork;
|
|
159
|
+
mug;
|
|
160
|
+
seasoning;
|
|
161
|
+
mayoflex;
|
|
162
|
+
spleenValue = 0;
|
|
107
163
|
constructor(mpa, menu) {
|
|
108
|
-
this.spleenValue = 0;
|
|
109
164
|
this.mpa = mpa;
|
|
110
165
|
this.fork = menu.find((item) => item.item === $item `Ol' Scratch's salad fork`);
|
|
111
166
|
this.mug = menu.find((item) => item.item === $item `Frosty's frosty mug`);
|
|
@@ -114,8 +169,6 @@ class DietPlanner {
|
|
|
114
169
|
getWorkshed() === $item `portable Mayo Clinic`
|
|
115
170
|
? menu.find((item) => item.item === $item `Mayoflex`)
|
|
116
171
|
: undefined;
|
|
117
|
-
this.pinkyRing = have($item `mafia pinky ring`);
|
|
118
|
-
this.tuxedoShirt = have($item `tuxedo shirt`);
|
|
119
172
|
this.menu = menu.filter((item) => item.organ);
|
|
120
173
|
if (menu.length > 100) {
|
|
121
174
|
mallPrices("food");
|
|
@@ -129,9 +182,20 @@ class DietPlanner {
|
|
|
129
182
|
this.consumptionValue(spleenItems[0]) / spleenItems[0].item.spleen;
|
|
130
183
|
}
|
|
131
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Determine the value of consuming a menu item with any profitable helpers.
|
|
187
|
+
* @param menuItem Menu item to check.
|
|
188
|
+
* @returns Value for consuming that menu item.
|
|
189
|
+
*/
|
|
132
190
|
consumptionValue(menuItem) {
|
|
133
191
|
return this.consumptionHelpersAndValue(menuItem, {})[1];
|
|
134
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* Determine which helpers will be used with a menu item and its resulting value.
|
|
195
|
+
* @param menuItem Menu item to check.
|
|
196
|
+
* @param overrideModifiers Overrides for consumption modifiers, if any.
|
|
197
|
+
* @returns Pair [array of helpers and base menu item, value].
|
|
198
|
+
*/
|
|
135
199
|
consumptionHelpersAndValue(menuItem, overrideModifiers) {
|
|
136
200
|
const helpers = [];
|
|
137
201
|
if (this.seasoning &&
|
|
@@ -148,9 +212,11 @@ class DietPlanner {
|
|
|
148
212
|
forkMug: false,
|
|
149
213
|
seasoning: this.seasoning ? helpers.includes(this.seasoning) : false,
|
|
150
214
|
mayoflex: this.mayoflex ? helpers.includes(this.mayoflex) : false,
|
|
151
|
-
refinedPalate:
|
|
152
|
-
|
|
153
|
-
|
|
215
|
+
refinedPalate: have($effect `Refined Palate`),
|
|
216
|
+
garish: have($effect `Gar-ish`),
|
|
217
|
+
saucemaven: have($skill `Saucemaven`),
|
|
218
|
+
pinkyRing: have($item `mafia pinky ring`) && canEquip($item `mafia pinky ring`),
|
|
219
|
+
tuxedoShirt: have($item `tuxedo shirt`) && canEquip($item `tuxedo shirt`),
|
|
154
220
|
...overrideModifiers,
|
|
155
221
|
};
|
|
156
222
|
const forkMug = itemType(menuItem.item) === "food"
|
|
@@ -178,8 +244,14 @@ class DietPlanner {
|
|
|
178
244
|
? [[...helpers, forkMug, menuItem], valueForkMug + valueSpleen]
|
|
179
245
|
: [[...helpers, menuItem], valueRaw + valueSpleen];
|
|
180
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Plan an individual organ.
|
|
249
|
+
* @param capacity Organ capacity.
|
|
250
|
+
* @param overrideModifiers Overrides for consumption modifiers, if any.
|
|
251
|
+
* @returns Pair of [value, menu items and quantities].
|
|
252
|
+
*/
|
|
181
253
|
planOrgan(organ, capacity, overrideModifiers = {}) {
|
|
182
|
-
const submenu = this.menu.filter((
|
|
254
|
+
const submenu = this.menu.filter((menuItem) => menuItem.organ === organ && myLevel() >= menuItem.item.levelreq);
|
|
183
255
|
const knapsackValues = submenu.map((menuItem) => [
|
|
184
256
|
...this.consumptionHelpersAndValue(menuItem, overrideModifiers),
|
|
185
257
|
menuItem.size,
|
|
@@ -187,6 +259,12 @@ class DietPlanner {
|
|
|
187
259
|
]);
|
|
188
260
|
return knapsack(knapsackValues, capacity);
|
|
189
261
|
}
|
|
262
|
+
/**
|
|
263
|
+
* Plan organs.
|
|
264
|
+
* @param organCapacities Organ capacities.
|
|
265
|
+
* @param overrideModifiers Overrides for consumption modifiers, if any.
|
|
266
|
+
* @returns Pair of [value, menu items and quantities].
|
|
267
|
+
*/
|
|
190
268
|
planOrgans(organCapacities, overrideModifiers = {}) {
|
|
191
269
|
const valuePlans = organCapacities.map(([organ, capacity]) => this.planOrgan(organ, capacity, overrideModifiers));
|
|
192
270
|
return [
|
|
@@ -194,12 +272,23 @@ class DietPlanner {
|
|
|
194
272
|
[].concat(...valuePlans.map(([, plan]) => plan)),
|
|
195
273
|
];
|
|
196
274
|
}
|
|
197
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Plan organs, retrying with and without each trial item. Runtime is
|
|
277
|
+
* proportional to 2 ^ trialItems.length.
|
|
278
|
+
* @param organCapacities Organ capacities.
|
|
279
|
+
* @param trialItems Items to rerun solver with and without.
|
|
280
|
+
* @param overrideModifiers Overrides for consumption modifiers, if any.
|
|
281
|
+
* @returns Pair of [value, menu items and quantities].
|
|
282
|
+
*/
|
|
283
|
+
planOrgansWithTrials(organCapacities, trialItems, overrideModifiers) {
|
|
198
284
|
if (trialItems.length === 0) {
|
|
199
285
|
return this.planOrgans(organCapacities, overrideModifiers);
|
|
200
286
|
}
|
|
201
|
-
const organCapacitiesWithMap = new Map(organCapacities);
|
|
202
287
|
const [trialItem, organSizes] = trialItems[0];
|
|
288
|
+
if (trialItem.maximum !== undefined && trialItem.maximum <= 0) {
|
|
289
|
+
return this.planOrgansWithTrials(organCapacities, trialItems.slice(1), overrideModifiers);
|
|
290
|
+
}
|
|
291
|
+
const organCapacitiesWithMap = new Map(organCapacities);
|
|
203
292
|
for (const [organ, size] of organSizes) {
|
|
204
293
|
const current = organCapacitiesWithMap.get(organ);
|
|
205
294
|
if (current !== undefined) {
|
|
@@ -207,12 +296,18 @@ class DietPlanner {
|
|
|
207
296
|
}
|
|
208
297
|
}
|
|
209
298
|
const organCapacitiesWith = [...organCapacitiesWithMap];
|
|
210
|
-
const isRefinedPalate = trialItem.item === $item `pocket wish` &&
|
|
211
|
-
trialItem.wishEffect === $effect `Refined Palate
|
|
299
|
+
const isRefinedPalate = (trialItem.item === $item `pocket wish` &&
|
|
300
|
+
trialItem.wishEffect === $effect `Refined Palate`) ||
|
|
301
|
+
trialItem.item === $item `toasted brie`;
|
|
302
|
+
const isGarish = (trialItem.item === $item `pocket wish` &&
|
|
303
|
+
trialItem.wishEffect === $effect `Gar-ish`) ||
|
|
304
|
+
trialItem.item === $item `potion of the field gar`;
|
|
212
305
|
const [valueWithout, planWithout] = this.planOrgansWithTrials(organCapacities, trialItems.slice(1), overrideModifiers);
|
|
213
|
-
const [valueWith, planWith] = this.planOrgansWithTrials(organCapacitiesWith, trialItems.slice(1),
|
|
214
|
-
|
|
215
|
-
:
|
|
306
|
+
const [valueWith, planWith] = this.planOrgansWithTrials(organCapacitiesWith, trialItems.slice(1), {
|
|
307
|
+
...overrideModifiers,
|
|
308
|
+
...(isRefinedPalate ? { refinedPalate: true } : {}),
|
|
309
|
+
...(isGarish ? { garish: true } : {}),
|
|
310
|
+
});
|
|
216
311
|
const [helpersAndItem, value] = this.consumptionHelpersAndValue(trialItem, {});
|
|
217
312
|
return valueWithout > valueWith + value
|
|
218
313
|
? [valueWithout, planWithout]
|
|
@@ -267,6 +362,10 @@ const interactingItems = [
|
|
|
267
362
|
["booze", -2],
|
|
268
363
|
],
|
|
269
364
|
],
|
|
365
|
+
[$effect `Refined Palate`, []],
|
|
366
|
+
[$item `toasted brie`, [["food", 2]]],
|
|
367
|
+
[$effect `Gar-ish`, []],
|
|
368
|
+
[$item `potion of the field gar`, []],
|
|
270
369
|
];
|
|
271
370
|
/**
|
|
272
371
|
* Plan out an optimal diet using a knapsack algorithm.
|
|
@@ -280,37 +379,58 @@ export function planDiet(mpa, menu, organCapacities = [
|
|
|
280
379
|
["booze", null],
|
|
281
380
|
["spleen item", null],
|
|
282
381
|
]) {
|
|
283
|
-
|
|
382
|
+
// FIXME: Figure out a better way to handle overfull organs (e.g. coming out of Ed).
|
|
284
383
|
const resolvedOrganCapacities = organCapacities.map(([organ, size]) => [
|
|
285
384
|
organ,
|
|
286
385
|
size ??
|
|
287
386
|
(organ === "food"
|
|
288
|
-
? fullnessLimit() -
|
|
289
|
-
myFullness() +
|
|
290
|
-
(have($item `distention pill`) ? 1 : 0)
|
|
387
|
+
? fullnessLimit() - myFullness()
|
|
291
388
|
: organ === "booze"
|
|
292
|
-
? inebrietyLimit()
|
|
389
|
+
? inebrietyLimit() - myInebriety()
|
|
293
390
|
: organ === "spleen item"
|
|
294
391
|
? spleenLimit() - mySpleenUse()
|
|
295
392
|
: 0),
|
|
296
393
|
]);
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
menuItem.
|
|
305
|
-
|
|
306
|
-
|
|
394
|
+
/**
|
|
395
|
+
* Per above description, separate out items with cross-organ interaction
|
|
396
|
+
* ("interacting items") for special treatment. These will be checked by
|
|
397
|
+
* running the solver several times.
|
|
398
|
+
*/
|
|
399
|
+
const includedInteractingItems = menu
|
|
400
|
+
.map((menuItem) => {
|
|
401
|
+
const interacting = interactingItems.find(([itemOrEffect]) => menuItem.item === itemOrEffect ||
|
|
402
|
+
(menuItem.item === $item `pocket wish` &&
|
|
403
|
+
menuItem.wishEffect === itemOrEffect));
|
|
404
|
+
if (interacting) {
|
|
405
|
+
const [, organSizes] = interacting;
|
|
406
|
+
return [menuItem, organSizes];
|
|
407
|
+
}
|
|
408
|
+
else {
|
|
409
|
+
return null;
|
|
410
|
+
}
|
|
411
|
+
})
|
|
412
|
+
.filter((value) => value !== null);
|
|
413
|
+
// Filter out interacting items from natural consideration.
|
|
414
|
+
const dietPlanner = new DietPlanner(mpa, menu.filter((menuItem) => !includedInteractingItems.some(([interacting]) => interacting === menuItem)));
|
|
415
|
+
/**
|
|
416
|
+
* Because our knapsack solver is one-dimensional, we have to consider
|
|
417
|
+
* each organ separately. Since there are no spleen items that affect
|
|
418
|
+
* stomach/liver, we consider those two first, with an approximation of the
|
|
419
|
+
* value of spleen-cleaning. Afterwards, we see how much spleen we have and
|
|
420
|
+
* plan that.
|
|
421
|
+
*/
|
|
422
|
+
const [, planFoodBooze] = dietPlanner.planOrgansWithTrials(resolvedOrganCapacities.filter(([organ, capacity]) => ["food", "booze"].includes(organ) && capacity >= 0), includedInteractingItems, {});
|
|
423
|
+
const spleenCapacity = resolvedOrganCapacities.find(([organ]) => organ === "spleen item");
|
|
424
|
+
if (spleenCapacity) {
|
|
425
|
+
// Count sliders and pickle juice, figure out how much extra spleen we got.
|
|
426
|
+
const additionalSpleen = sum(planFoodBooze, ([items, number]) => items.some((menuItem) => $items `jar of fermented pickle juice, extra-greasy slider`.includes(menuItem.item))
|
|
427
|
+
? 5 * number
|
|
428
|
+
: 0);
|
|
429
|
+
const [, availableSpleen] = spleenCapacity;
|
|
430
|
+
const [, planSpleen] = dietPlanner.planOrgan("spleen item", availableSpleen + additionalSpleen);
|
|
431
|
+
return [...planFoodBooze, ...planSpleen];
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
return planFoodBooze;
|
|
307
435
|
}
|
|
308
|
-
const [, planFoodBooze] = dietPlanner.planOrgansWithTrials(resolvedOrganCapacities.filter(([organ]) => ["food", "booze"].includes(organ)), includedInteractingItems);
|
|
309
|
-
// Count sliders and pickle juice, figure out how much extra spleen we got.
|
|
310
|
-
const additionalSpleen = sum(planFoodBooze, ([items, number]) => items.some((menuItem) => $items `jar of fermented pickle juice, extra-greasy slider`.includes(menuItem.item))
|
|
311
|
-
? 5 * number
|
|
312
|
-
: 0);
|
|
313
|
-
const [, availableSpleen] = resolvedOrganCapacities.find(([organ]) => organ === "spleen item") ?? ["spleen item", 0];
|
|
314
|
-
const [, planSpleen] = dietPlanner.planOrgan("spleen item", availableSpleen + additionalSpleen);
|
|
315
|
-
return [...planFoodBooze, ...planSpleen];
|
|
316
436
|
}
|
package/dist/diet/knapsack.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { sum } from "../utils";
|
|
2
2
|
class Not {
|
|
3
|
+
thing;
|
|
3
4
|
constructor(thing) {
|
|
4
5
|
this.thing = thing;
|
|
5
6
|
}
|
|
@@ -32,17 +33,30 @@ function aggregate(list, isEqual) {
|
|
|
32
33
|
* @returns Tuple {[totalValue, items]} of selected items and total value of those items.
|
|
33
34
|
*/
|
|
34
35
|
export function knapsack(values, capacity) {
|
|
36
|
+
if (!Number.isFinite(capacity)) {
|
|
37
|
+
throw new Error("Invalid capacity.");
|
|
38
|
+
}
|
|
35
39
|
// Invert negative values into a fake value for not using it.
|
|
36
|
-
const valuesInverted = values.map(([thing, value, weight, maximum]) => (weight < 0 && maximum
|
|
40
|
+
const valuesInverted = values.map(([thing, value, weight, maximum]) => (weight < 0 && maximum !== undefined
|
|
37
41
|
? [new Not(thing), -value, -weight, maximum]
|
|
38
42
|
: [thing, value, weight, maximum]));
|
|
39
|
-
const capacityAdjustment = sum(values, ([, , weight, maximum]) => weight < 0 && maximum ? -weight * maximum : 0);
|
|
43
|
+
const capacityAdjustment = sum(values, ([, , weight, maximum]) => weight < 0 && maximum !== undefined ? -weight * maximum : 0);
|
|
40
44
|
const adjustedCapacity = capacity + capacityAdjustment;
|
|
45
|
+
if (adjustedCapacity < 0) {
|
|
46
|
+
// We don't have enough cleaners to create any space, so can't fit anything.
|
|
47
|
+
return [-Infinity, []];
|
|
48
|
+
}
|
|
41
49
|
// Sort values by weight.
|
|
42
50
|
const valuesSorted = [...valuesInverted].sort((x, y) => x[2] - y[2]);
|
|
43
51
|
// Convert the problem into 0/1 knapsack - just include as many copies as possible of each item.
|
|
44
52
|
const values01 = [].concat(...valuesSorted.map(([thing, value, weight, maximum]) => {
|
|
53
|
+
if (!Number.isFinite(weight) || weight < 0) {
|
|
54
|
+
throw new Error(`Invalid weight ${weight} for ${thing instanceof Not ? `not ${thing.thing}` : thing}`);
|
|
55
|
+
}
|
|
45
56
|
const maxQuantity = maximum ?? Math.floor(adjustedCapacity / weight);
|
|
57
|
+
if (maxQuantity < 0) {
|
|
58
|
+
throw new Error(`Invalid max quantity ${maxQuantity} for ${thing instanceof Not ? `not ${thing.thing}` : thing}`);
|
|
59
|
+
}
|
|
46
60
|
return new Array(maxQuantity).fill([
|
|
47
61
|
thing,
|
|
48
62
|
value,
|