farming-weight 0.10.7 → 0.11.0
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/constants/attributes.d.ts +2 -1
- package/dist/constants/attributes.js +9 -3
- package/dist/constants/attributes.js.map +1 -1
- package/dist/constants/crops.d.ts +4 -1
- package/dist/constants/crops.js +62 -2
- package/dist/constants/crops.js.map +1 -1
- package/dist/constants/garden.js +18 -0
- package/dist/constants/garden.js.map +1 -1
- package/dist/constants/gems.d.ts +14 -1
- package/dist/constants/gems.js +561 -42
- package/dist/constants/gems.js.map +1 -1
- package/dist/constants/personalbests.js +4 -1
- package/dist/constants/personalbests.js.map +1 -1
- package/dist/constants/pests.d.ts +4 -1
- package/dist/constants/pests.js +119 -65
- package/dist/constants/pests.js.map +1 -1
- package/dist/constants/reforges.js +4 -0
- package/dist/constants/reforges.js.map +1 -1
- package/dist/constants/specialcrops.d.ts +15 -1
- package/dist/constants/specialcrops.js +17 -0
- package/dist/constants/specialcrops.js.map +1 -1
- package/dist/constants/specific.d.ts +1 -0
- package/dist/constants/specific.js +10 -0
- package/dist/constants/specific.js.map +1 -1
- package/dist/constants/stats.d.ts +5 -1
- package/dist/constants/stats.js +4 -0
- package/dist/constants/stats.js.map +1 -1
- package/dist/constants/upgrades.d.ts +43 -0
- package/dist/constants/upgrades.js.map +1 -1
- package/dist/constants/weight.js +5 -2
- package/dist/constants/weight.js.map +1 -1
- package/dist/fortune/farmingaccessory.d.ts +3 -1
- package/dist/fortune/farmingaccessory.js +17 -5
- package/dist/fortune/farmingaccessory.js.map +1 -1
- package/dist/fortune/farmingarmor.d.ts +14 -5
- package/dist/fortune/farmingarmor.js +123 -17
- package/dist/fortune/farmingarmor.js.map +1 -1
- package/dist/fortune/farmingequipment.d.ts +4 -2
- package/dist/fortune/farmingequipment.js +27 -5
- package/dist/fortune/farmingequipment.js.map +1 -1
- package/dist/fortune/farmingtool.d.ts +8 -4
- package/dist/fortune/farmingtool.js +61 -15
- package/dist/fortune/farmingtool.js.map +1 -1
- package/dist/fortune/upgradeable.d.ts +7 -2
- package/dist/fortune/upgradeablebase.d.ts +7 -3
- package/dist/fortune/upgradeablebase.js +20 -4
- package/dist/fortune/upgradeablebase.js.map +1 -1
- package/dist/items/accessories.d.ts +3 -14
- package/dist/items/accessories.js +9 -0
- package/dist/items/accessories.js.map +1 -1
- package/dist/items/armor.d.ts +3 -35
- package/dist/items/armor.js +139 -63
- package/dist/items/armor.js.map +1 -1
- package/dist/items/definitions.d.ts +44 -0
- package/dist/items/definitions.js +62 -0
- package/dist/items/definitions.js.map +1 -0
- package/dist/items/equipment.d.ts +2 -2
- package/dist/items/equipment.js +1 -1
- package/dist/items/equipment.js.map +1 -1
- package/dist/items/tools.d.ts +3 -14
- package/dist/items/tools.js +6 -36
- package/dist/items/tools.js.map +1 -1
- package/dist/player/player.d.ts +45 -4
- package/dist/player/player.js +720 -94
- package/dist/player/player.js.map +1 -1
- package/dist/player/playeroptions.d.ts +1 -0
- package/dist/player/playeroptions.js.map +1 -1
- package/dist/upgrades/enchantupgrades.d.ts +3 -2
- package/dist/upgrades/enchantupgrades.js +48 -8
- package/dist/upgrades/enchantupgrades.js.map +1 -1
- package/dist/upgrades/getfortune.d.ts +2 -1
- package/dist/upgrades/getfortune.js +7 -2
- package/dist/upgrades/getfortune.js.map +1 -1
- package/dist/upgrades/getsourceprogress.d.ts +2 -1
- package/dist/upgrades/getsourceprogress.js +53 -3
- package/dist/upgrades/getsourceprogress.js.map +1 -1
- package/dist/upgrades/sources/armorsetsources.js +57 -27
- package/dist/upgrades/sources/armorsetsources.js.map +1 -1
- package/dist/upgrades/sources/cropsources.js +39 -3
- package/dist/upgrades/sources/cropsources.js.map +1 -1
- package/dist/upgrades/sources/dynamicfortunesources.d.ts +10 -2
- package/dist/upgrades/sources/gearsources.js +54 -5
- package/dist/upgrades/sources/gearsources.js.map +1 -1
- package/dist/upgrades/sources/generalsources.js +177 -8
- package/dist/upgrades/sources/generalsources.js.map +1 -1
- package/dist/upgrades/sources/toolsources.js +108 -17
- package/dist/upgrades/sources/toolsources.js.map +1 -1
- package/dist/upgrades/upgrades.d.ts +5 -2
- package/dist/upgrades/upgrades.js +114 -24
- package/dist/upgrades/upgrades.js.map +1 -1
- package/dist/upgrades/upgradeutils.d.ts +6 -0
- package/dist/upgrades/upgradeutils.js +18 -0
- package/dist/upgrades/upgradeutils.js.map +1 -0
- package/dist/util/enchants.d.ts +3 -0
- package/dist/util/enchants.js +18 -12
- package/dist/util/enchants.js.map +1 -1
- package/dist/util/garden.d.ts +1 -0
- package/dist/util/garden.js +8 -2
- package/dist/util/garden.js.map +1 -1
- package/dist/util/gems.d.ts +2 -0
- package/dist/util/gems.js +29 -8
- package/dist/util/gems.js.map +1 -1
- package/dist/util/names.js +23 -0
- package/dist/util/names.js.map +1 -1
- package/package.json +3 -2
package/dist/player/player.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { FARMING_ATTRIBUTE_SHARDS,
|
|
1
|
+
import { FARMING_ATTRIBUTE_SHARDS, getShardStat } from '../constants/attributes.js';
|
|
2
2
|
import { CROP_INFO, Crop, EXPORTABLE_CROP_FORTUNE } from '../constants/crops.js';
|
|
3
3
|
import { fortuneFromPersonalBestContest } from '../constants/personalbests.js';
|
|
4
|
-
import { ANITA_FORTUNE_UPGRADE, COCOA_FORTUNE_UPGRADE, COMMUNITY_CENTER_UPGRADE, FARMING_LEVEL, GARDEN_CROP_UPGRADES, UNLOCKED_PLOTS, } from '../constants/specific.js';
|
|
4
|
+
import { ANITA_FORTUNE_UPGRADE, COCOA_FORTUNE_UPGRADE, COMMUNITY_CENTER_UPGRADE, FARMING_LEVEL, GARDEN_CROP_UPGRADES, UNLOCKED_PLOTS, WRIGGLING_LARVA_SOURCE, } from '../constants/specific.js';
|
|
5
|
+
import { Stat } from '../constants/stats.js';
|
|
5
6
|
import { TEMPORARY_FORTUNE } from '../constants/tempfortune.js';
|
|
6
7
|
import { UpgradeAction, UpgradeCategory } from '../constants/upgrades.js';
|
|
7
8
|
import { FarmingAccessory } from '../fortune/farmingaccessory.js';
|
|
@@ -16,6 +17,7 @@ import { getSourceProgress } from '../upgrades/getsourceprogress.js';
|
|
|
16
17
|
import { getFakeItem } from '../upgrades/itemregistry.js';
|
|
17
18
|
import { CROP_FORTUNE_SOURCES } from '../upgrades/sources/cropsources.js';
|
|
18
19
|
import { GENERAL_FORTUNE_SOURCES } from '../upgrades/sources/generalsources.js';
|
|
20
|
+
import { filterAndSortUpgrades } from '../upgrades/upgradeutils.js';
|
|
19
21
|
import { getCropDisplayName, getItemIdFromCrop } from '../util/names.js';
|
|
20
22
|
import { fortuneFromPestBestiary } from '../util/pests.js';
|
|
21
23
|
import { calculateDetailedDrops } from '../util/ratecalc.js';
|
|
@@ -31,70 +33,96 @@ export class FarmingPlayer {
|
|
|
31
33
|
return this.options.attributes ?? {};
|
|
32
34
|
}
|
|
33
35
|
constructor(options) {
|
|
36
|
+
this.setOptions(options);
|
|
37
|
+
}
|
|
38
|
+
setOptions(options) {
|
|
34
39
|
this.options = options;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
40
|
+
this.populatePets();
|
|
41
|
+
this.populateTools();
|
|
42
|
+
this.populateArmor();
|
|
43
|
+
this.populateEquipment();
|
|
44
|
+
this.populateActiveAccessories();
|
|
45
|
+
this.permFortune = this.getGeneralFortune();
|
|
46
|
+
this.tempFortune = this.getTempFortune();
|
|
47
|
+
}
|
|
48
|
+
populatePets() {
|
|
49
|
+
this.options.pets ??= [];
|
|
50
|
+
if (this.options.pets[0] instanceof FarmingPet) {
|
|
51
|
+
this.pets = this.options.pets.sort((a, b) => b.fortune - a.fortune);
|
|
38
52
|
for (const pet of this.pets)
|
|
39
|
-
pet.setOptions(options);
|
|
53
|
+
pet.setOptions(this.options);
|
|
40
54
|
}
|
|
41
55
|
else {
|
|
42
|
-
this.pets = FarmingPet.fromArray(options.pets, options);
|
|
56
|
+
this.pets = FarmingPet.fromArray(this.options.pets, this.options);
|
|
43
57
|
}
|
|
44
58
|
this.selectedPet = this.options.selectedPet;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
}
|
|
60
|
+
populateTools() {
|
|
61
|
+
this.options.tools ??= [];
|
|
62
|
+
if (this.options.tools[0] instanceof FarmingTool) {
|
|
63
|
+
this.tools = this.options.tools;
|
|
48
64
|
for (const tool of this.tools)
|
|
49
|
-
tool.setOptions(options);
|
|
65
|
+
tool.setOptions(this.options);
|
|
50
66
|
this.tools.sort((a, b) => b.fortune - a.fortune);
|
|
51
67
|
}
|
|
52
68
|
else {
|
|
53
|
-
this.tools = FarmingTool.fromArray(options.tools, options);
|
|
69
|
+
this.tools = FarmingTool.fromArray(this.options.tools, this.options);
|
|
54
70
|
}
|
|
55
71
|
this.selectedTool = this.options.selectedTool ?? this.tools[0];
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
72
|
+
}
|
|
73
|
+
populateArmor() {
|
|
74
|
+
this.options.armor ??= [];
|
|
75
|
+
if (this.options.armor instanceof ArmorSet) {
|
|
76
|
+
this.armorSet = this.options.armor;
|
|
59
77
|
this.armor = this.armorSet.pieces;
|
|
60
78
|
this.armor.sort((a, b) => b.potential - a.potential);
|
|
61
79
|
this.equipment = this.armorSet.equipmentPieces;
|
|
62
80
|
this.equipment.sort((a, b) => b.fortune - a.fortune);
|
|
63
|
-
this.armorSet.setOptions(options);
|
|
81
|
+
this.armorSet.setOptions(this.options);
|
|
64
82
|
}
|
|
65
|
-
else if (options.armor[0] instanceof FarmingArmor) {
|
|
66
|
-
this.armor = options.armor.sort((a, b) => b.potential - a.potential);
|
|
83
|
+
else if (this.options.armor[0] instanceof FarmingArmor) {
|
|
84
|
+
this.armor = this.options.armor.sort((a, b) => b.potential - a.potential);
|
|
67
85
|
for (const a of this.armor)
|
|
68
|
-
a.setOptions(options);
|
|
86
|
+
a.setOptions(this.options);
|
|
69
87
|
this.armorSet = new ArmorSet(this.armor);
|
|
70
88
|
}
|
|
71
89
|
else {
|
|
72
|
-
this.armor = FarmingArmor.fromArray(options.armor, options);
|
|
90
|
+
this.armor = FarmingArmor.fromArray(this.options.armor, this.options);
|
|
73
91
|
this.armorSet = new ArmorSet(this.armor);
|
|
74
92
|
}
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
93
|
+
}
|
|
94
|
+
populateEquipment() {
|
|
95
|
+
this.options.equipment ??= [];
|
|
96
|
+
// If equipment was already set by populateArmor (from ArmorSet), don't overwrite it
|
|
97
|
+
if (this.equipment && this.equipment.length > 0) {
|
|
98
|
+
// Load in equipment to armor set if it's empty
|
|
99
|
+
if (this.armorSet.equipment.filter((e) => e).length === 0) {
|
|
100
|
+
this.armorSet.setEquipment(this.equipment);
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (this.options.equipment[0] instanceof FarmingEquipment) {
|
|
105
|
+
this.equipment = this.options.equipment.sort((a, b) => b.fortune - a.fortune);
|
|
78
106
|
for (const e of this.equipment)
|
|
79
|
-
e.setOptions(options);
|
|
107
|
+
e.setOptions(this.options);
|
|
80
108
|
}
|
|
81
109
|
else {
|
|
82
|
-
this.equipment = FarmingEquipment.fromArray(options.equipment, options);
|
|
110
|
+
this.equipment = FarmingEquipment.fromArray(this.options.equipment, this.options);
|
|
83
111
|
}
|
|
84
112
|
// Load in equipment to armor set if it's empty
|
|
85
113
|
if (this.armorSet.equipment.filter((e) => e).length === 0) {
|
|
86
114
|
this.armorSet.setEquipment(this.equipment);
|
|
87
115
|
}
|
|
88
|
-
|
|
116
|
+
}
|
|
117
|
+
populateActiveAccessories() {
|
|
118
|
+
this.options.accessories ??= [];
|
|
89
119
|
this.activeAccessories = [];
|
|
90
|
-
if (options.accessories[0] instanceof FarmingAccessory) {
|
|
91
|
-
this.accessories = options.accessories.sort((a, b) => b.fortune - a.fortune);
|
|
120
|
+
if (this.options.accessories[0] instanceof FarmingAccessory) {
|
|
121
|
+
this.accessories = this.options.accessories.sort((a, b) => b.fortune - a.fortune);
|
|
92
122
|
}
|
|
93
123
|
else {
|
|
94
|
-
this.accessories = FarmingAccessory.fromArray(options.accessories);
|
|
124
|
+
this.accessories = FarmingAccessory.fromArray(this.options.accessories);
|
|
95
125
|
}
|
|
96
|
-
this.permFortune = this.getGeneralFortune();
|
|
97
|
-
this.tempFortune = this.getTempFortune();
|
|
98
126
|
}
|
|
99
127
|
changeArmor(armor) {
|
|
100
128
|
this.armorSet = new ArmorSet(armor.sort((a, b) => b.fortune - a.fortune));
|
|
@@ -114,17 +142,17 @@ export class FarmingPlayer {
|
|
|
114
142
|
}
|
|
115
143
|
this.permFortune = this.getGeneralFortune();
|
|
116
144
|
}
|
|
117
|
-
getProgress() {
|
|
118
|
-
return getSourceProgress(this, GENERAL_FORTUNE_SOURCES);
|
|
145
|
+
getProgress(stats) {
|
|
146
|
+
return getSourceProgress(this, GENERAL_FORTUNE_SOURCES, false, stats);
|
|
119
147
|
}
|
|
120
|
-
getUpgrades() {
|
|
121
|
-
const
|
|
122
|
-
const
|
|
148
|
+
getUpgrades(options) {
|
|
149
|
+
const stats = options?.stat ? [options.stat] : undefined;
|
|
150
|
+
const upgrades = getSourceProgress(this, GENERAL_FORTUNE_SOURCES, false, stats).flatMap((source) => source.upgrades ?? []);
|
|
151
|
+
const armorSetUpgrades = this.armorSet.getUpgrades(options);
|
|
123
152
|
if (armorSetUpgrades.length > 0) {
|
|
124
153
|
upgrades.push(...armorSetUpgrades);
|
|
125
154
|
}
|
|
126
|
-
upgrades
|
|
127
|
-
return upgrades;
|
|
155
|
+
return filterAndSortUpgrades(upgrades, options);
|
|
128
156
|
}
|
|
129
157
|
getCropUpgrades(crop, tool) {
|
|
130
158
|
const upgrades = [];
|
|
@@ -166,111 +194,160 @@ export class FarmingPlayer {
|
|
|
166
194
|
return upgrades;
|
|
167
195
|
}
|
|
168
196
|
getGeneralFortune() {
|
|
197
|
+
const { value, breakdown } = this.getStatBreakdown(Stat.FarmingFortune);
|
|
198
|
+
this.breakdown = breakdown;
|
|
199
|
+
return value;
|
|
200
|
+
}
|
|
201
|
+
getStat(stat) {
|
|
202
|
+
return this.getStatBreakdown(stat).value;
|
|
203
|
+
}
|
|
204
|
+
getStatBreakdown(stat) {
|
|
169
205
|
let sum = 0;
|
|
170
206
|
const breakdown = {};
|
|
171
207
|
// Plots
|
|
172
|
-
const plots = getFortune(this.options.plots?.length ?? this.options.plotsUnlocked, UNLOCKED_PLOTS);
|
|
208
|
+
const plots = getFortune(this.options.plots?.length ?? this.options.plotsUnlocked, UNLOCKED_PLOTS, stat);
|
|
173
209
|
if (plots > 0) {
|
|
174
210
|
breakdown['Unlocked Plots'] = plots;
|
|
175
211
|
sum += plots;
|
|
176
212
|
}
|
|
177
213
|
// Farming Level
|
|
178
|
-
const level = getFortune(this.options.farmingLevel, FARMING_LEVEL);
|
|
214
|
+
const level = getFortune(this.options.farmingLevel, FARMING_LEVEL, stat);
|
|
179
215
|
if (level > 0) {
|
|
180
216
|
breakdown['Farming Level'] = level;
|
|
181
217
|
sum += level;
|
|
182
218
|
}
|
|
183
219
|
// Bestiary
|
|
184
|
-
if (this.options.bestiaryKills) {
|
|
220
|
+
if (stat === Stat.FarmingFortune && this.options.bestiaryKills) {
|
|
185
221
|
const bestiary = fortuneFromPestBestiary(this.options.bestiaryKills);
|
|
186
222
|
if (bestiary > 0) {
|
|
187
223
|
breakdown['Pest Bestiary'] = bestiary;
|
|
188
224
|
sum += bestiary;
|
|
189
225
|
}
|
|
190
226
|
}
|
|
191
|
-
// Armor
|
|
192
|
-
const
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
227
|
+
// Armor pieces
|
|
228
|
+
for (const piece of this.armorSet.armor) {
|
|
229
|
+
if (!piece)
|
|
230
|
+
continue;
|
|
231
|
+
const val = piece.getStat(stat);
|
|
232
|
+
if (val > 0) {
|
|
233
|
+
breakdown[piece.item.name ?? 'Armor Piece'] = val;
|
|
234
|
+
sum += val;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
// Armor Set Bonuses
|
|
238
|
+
for (const { bonus, count } of this.armorSet.setBonuses) {
|
|
239
|
+
if (count < 2 || count > 4)
|
|
240
|
+
continue;
|
|
241
|
+
const val = bonus.stats?.[count]?.[stat] ?? 0;
|
|
242
|
+
if (val > 0) {
|
|
243
|
+
breakdown[bonus.name] = val;
|
|
244
|
+
sum += val;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Equipment pieces
|
|
248
|
+
for (const piece of this.armorSet.equipment) {
|
|
249
|
+
if (!piece)
|
|
250
|
+
continue;
|
|
251
|
+
const val = piece.getStat(stat);
|
|
252
|
+
if (val > 0) {
|
|
253
|
+
breakdown[piece.item.name ?? 'Equipment Piece'] = val;
|
|
254
|
+
sum += val;
|
|
255
|
+
}
|
|
196
256
|
}
|
|
197
|
-
//
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
257
|
+
// Equipment Set Bonuses
|
|
258
|
+
for (const { bonus, count } of this.armorSet.equipmentSetBonuses) {
|
|
259
|
+
if (count < 2 || count > 4)
|
|
260
|
+
continue;
|
|
261
|
+
const val = bonus.stats?.[count]?.[stat] ?? 0;
|
|
262
|
+
if (val > 0) {
|
|
263
|
+
breakdown[bonus.name] = val;
|
|
264
|
+
sum += val;
|
|
265
|
+
}
|
|
202
266
|
}
|
|
203
267
|
// Anita Bonus
|
|
204
|
-
const anitaBonus = getFortune(this.options.anitaBonus, ANITA_FORTUNE_UPGRADE);
|
|
268
|
+
const anitaBonus = getFortune(this.options.anitaBonus, ANITA_FORTUNE_UPGRADE, stat);
|
|
205
269
|
if (anitaBonus > 0) {
|
|
206
270
|
breakdown['Anita Bonus Drops'] = anitaBonus;
|
|
207
271
|
sum += anitaBonus;
|
|
208
272
|
}
|
|
209
273
|
// Community Center
|
|
210
|
-
const communityCenter = getFortune(this.options.communityCenter, COMMUNITY_CENTER_UPGRADE);
|
|
274
|
+
const communityCenter = getFortune(this.options.communityCenter, COMMUNITY_CENTER_UPGRADE, stat);
|
|
211
275
|
if (communityCenter > 0) {
|
|
212
276
|
breakdown['Community Center'] = communityCenter;
|
|
213
277
|
sum += communityCenter;
|
|
214
278
|
}
|
|
215
279
|
// Selected Pet
|
|
216
280
|
const pet = this.selectedPet;
|
|
217
|
-
if (pet
|
|
218
|
-
|
|
219
|
-
|
|
281
|
+
if (pet) {
|
|
282
|
+
const val = pet.getFortune(stat);
|
|
283
|
+
if (val > 0) {
|
|
284
|
+
breakdown[pet.info.name ?? 'Selected Pet'] = val;
|
|
285
|
+
sum += val;
|
|
286
|
+
}
|
|
220
287
|
}
|
|
221
|
-
// Accessories
|
|
288
|
+
// Accessories
|
|
222
289
|
const families = new Map();
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
290
|
+
const activeAccessories = [];
|
|
291
|
+
const sortedAccessories = [...this.accessories]
|
|
292
|
+
.map((a) => ({ acc: a, val: a.getStat(stat) }))
|
|
293
|
+
.filter((item) => item.val > 0)
|
|
294
|
+
.sort((a, b) => b.val - a.val);
|
|
295
|
+
for (const { acc, val } of sortedAccessories) {
|
|
296
|
+
if (!acc.info.family)
|
|
226
297
|
continue;
|
|
227
|
-
const existing = families.get(
|
|
298
|
+
const existing = families.get(acc.info.family);
|
|
228
299
|
if (!existing) {
|
|
229
|
-
families.set(
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
families.set(accessory.info.family, accessory);
|
|
234
|
-
this.activeAccessories.push(accessory);
|
|
235
|
-
this.activeAccessories = this.activeAccessories.filter((a) => a !== existing);
|
|
300
|
+
families.set(acc.info.family, acc);
|
|
301
|
+
activeAccessories.push(acc);
|
|
302
|
+
breakdown[acc.item.name ?? acc.item.skyblockId ?? 'Accessory'] = val;
|
|
303
|
+
sum += val;
|
|
236
304
|
}
|
|
237
305
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
continue;
|
|
241
|
-
breakdown[accessory.item.name ?? accessory.item.skyblockId ?? 'Accessory [Error]'] = accessory.fortune;
|
|
242
|
-
sum += accessory.fortune;
|
|
306
|
+
if (stat === Stat.FarmingFortune) {
|
|
307
|
+
this.activeAccessories = activeAccessories;
|
|
243
308
|
}
|
|
244
309
|
// Refined Truffles
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
310
|
+
if (stat === Stat.FarmingFortune) {
|
|
311
|
+
const truffles = Math.min(5, this.options.refinedTruffles ?? 0);
|
|
312
|
+
if (truffles > 0) {
|
|
313
|
+
breakdown['Refined Truffles'] = truffles;
|
|
314
|
+
sum += truffles;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
// Wriggling Larva
|
|
318
|
+
if (stat === Stat.BonusPestChance) {
|
|
319
|
+
const used = Math.min(5, this.options.wrigglingLarva ?? 0);
|
|
320
|
+
const val = getFortune(used, WRIGGLING_LARVA_SOURCE, stat);
|
|
321
|
+
if (val > 0) {
|
|
322
|
+
breakdown['Wriggling Larva'] = val;
|
|
323
|
+
sum += val;
|
|
324
|
+
}
|
|
249
325
|
}
|
|
250
326
|
// Attribute Shards
|
|
251
327
|
for (const [shardId, value] of Object.entries(this.attributes)) {
|
|
252
328
|
const shard = FARMING_ATTRIBUTE_SHARDS[shardId];
|
|
253
329
|
if (!shard || value <= 0)
|
|
254
330
|
continue;
|
|
255
|
-
const
|
|
256
|
-
if (
|
|
331
|
+
const val = getShardStat(shard, this, stat);
|
|
332
|
+
if (val <= 0)
|
|
257
333
|
continue;
|
|
258
|
-
breakdown[shard.name] =
|
|
259
|
-
sum +=
|
|
334
|
+
breakdown[shard.name] = val;
|
|
335
|
+
sum += val;
|
|
260
336
|
}
|
|
261
337
|
// Extra Fortune
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
338
|
+
if (stat === Stat.FarmingFortune) {
|
|
339
|
+
for (const extra of this.options.extraFortune ?? []) {
|
|
340
|
+
if (extra.crop)
|
|
341
|
+
continue;
|
|
342
|
+
breakdown[extra.name ?? 'Extra Fortune'] = extra.fortune;
|
|
343
|
+
sum += extra.fortune;
|
|
344
|
+
}
|
|
345
|
+
const temp = this.getTempFortune();
|
|
346
|
+
if (temp > 0) {
|
|
347
|
+
breakdown['Temporary Fortune'] = temp;
|
|
348
|
+
}
|
|
271
349
|
}
|
|
272
|
-
|
|
273
|
-
return sum;
|
|
350
|
+
return { value: sum, breakdown };
|
|
274
351
|
}
|
|
275
352
|
getTempFortune() {
|
|
276
353
|
let sum = 0;
|
|
@@ -360,8 +437,8 @@ export class FarmingPlayer {
|
|
|
360
437
|
breakdown: breakdown,
|
|
361
438
|
};
|
|
362
439
|
}
|
|
363
|
-
getCropProgress(crop) {
|
|
364
|
-
return getSourceProgress({ crop, player: this }, CROP_FORTUNE_SOURCES);
|
|
440
|
+
getCropProgress(crop, stats) {
|
|
441
|
+
return getSourceProgress({ crop, player: this }, CROP_FORTUNE_SOURCES, false, stats);
|
|
365
442
|
}
|
|
366
443
|
getRates(crop, blocksBroken) {
|
|
367
444
|
const tool = this.getBestTool(crop);
|
|
@@ -390,5 +467,554 @@ export class FarmingPlayer {
|
|
|
390
467
|
getSelectedCropTool(crop) {
|
|
391
468
|
return this.selectedTool?.crop === crop ? this.selectedTool : this.getBestTool(crop);
|
|
392
469
|
}
|
|
470
|
+
applyUpgrade(upgrade) {
|
|
471
|
+
if (!upgrade.meta)
|
|
472
|
+
return;
|
|
473
|
+
const { type, itemUuid, key, value, id } = upgrade.meta;
|
|
474
|
+
if (itemUuid) {
|
|
475
|
+
const candidates = [...this.tools, ...this.armor, ...this.equipment, ...this.accessories];
|
|
476
|
+
const target = candidates.find((i) => i.item.uuid === itemUuid);
|
|
477
|
+
if (target) {
|
|
478
|
+
if (type === 'enchant' && key) {
|
|
479
|
+
target.item.enchantments ??= {};
|
|
480
|
+
target.item.enchantments[key] = Number(value);
|
|
481
|
+
// Re-instantiate to update enchantment-based logic
|
|
482
|
+
if (target instanceof FarmingTool) {
|
|
483
|
+
const idx = this.tools.indexOf(target);
|
|
484
|
+
if (idx >= 0) {
|
|
485
|
+
this.tools[idx] = new FarmingTool(target.item, this.options);
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
else if (target instanceof FarmingArmor) {
|
|
489
|
+
const idx = this.armor.indexOf(target);
|
|
490
|
+
if (idx >= 0) {
|
|
491
|
+
const updatedPiece = new FarmingArmor(target.item, this.options);
|
|
492
|
+
this.armor[idx] = updatedPiece;
|
|
493
|
+
this.armorSet.updateArmorSlot(updatedPiece);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
else if (target instanceof FarmingEquipment) {
|
|
497
|
+
const idx = this.equipment.indexOf(target);
|
|
498
|
+
if (idx >= 0) {
|
|
499
|
+
const updatedPiece = new FarmingEquipment(target.item, this.options);
|
|
500
|
+
this.equipment[idx] = updatedPiece;
|
|
501
|
+
this.armorSet.updateEquipmentSlot(updatedPiece);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
else if (target instanceof FarmingAccessory) {
|
|
505
|
+
const idx = this.accessories.indexOf(target);
|
|
506
|
+
if (idx >= 0) {
|
|
507
|
+
this.accessories[idx] = new FarmingAccessory(target.item, this.options);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
else if (type === 'reforge' && id) {
|
|
512
|
+
target.item.attributes ??= {};
|
|
513
|
+
target.item.attributes.modifier = id;
|
|
514
|
+
// Re-initialize to update logic
|
|
515
|
+
if (target instanceof FarmingTool) {
|
|
516
|
+
const idx = this.tools.indexOf(target);
|
|
517
|
+
if (idx >= 0) {
|
|
518
|
+
this.tools[idx] = new FarmingTool(target.item, this.options);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
else if (target instanceof FarmingArmor) {
|
|
522
|
+
const idx = this.armor.indexOf(target);
|
|
523
|
+
if (idx >= 0) {
|
|
524
|
+
const updatedPiece = new FarmingArmor(target.item, this.options);
|
|
525
|
+
this.armor[idx] = updatedPiece;
|
|
526
|
+
this.armorSet.updateArmorSlot(updatedPiece);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
else if (target instanceof FarmingEquipment) {
|
|
530
|
+
const idx = this.equipment.indexOf(target);
|
|
531
|
+
if (idx >= 0) {
|
|
532
|
+
const updatedPiece = new FarmingEquipment(target.item, this.options);
|
|
533
|
+
this.equipment[idx] = updatedPiece;
|
|
534
|
+
this.armorSet.updateEquipmentSlot(updatedPiece);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
else if (target instanceof FarmingAccessory) {
|
|
538
|
+
const idx = this.accessories.indexOf(target);
|
|
539
|
+
if (idx >= 0) {
|
|
540
|
+
this.accessories[idx] = new FarmingAccessory(target.item, this.options);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
else if (type === 'item' && id === 'farming_for_dummies_count') {
|
|
545
|
+
target.item.attributes ??= {};
|
|
546
|
+
target.item.attributes.farming_for_dummies_count = String(value);
|
|
547
|
+
// Re-instantiate so getUpgrades reflects the updated FFD count
|
|
548
|
+
if (target instanceof FarmingTool) {
|
|
549
|
+
const idx = this.tools.indexOf(target);
|
|
550
|
+
if (idx >= 0) {
|
|
551
|
+
this.tools[idx] = new FarmingTool(target.item, this.options);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
else if (type === 'gem' && upgrade.meta.slot && value) {
|
|
556
|
+
target.item.gems ??= {};
|
|
557
|
+
target.item.gems[upgrade.meta.slot] = String(value);
|
|
558
|
+
// Re-instantiate so getUpgrades reflects the updated gem
|
|
559
|
+
if (target instanceof FarmingTool) {
|
|
560
|
+
const idx = this.tools.indexOf(target);
|
|
561
|
+
if (idx >= 0) {
|
|
562
|
+
this.tools[idx] = new FarmingTool(target.item, this.options);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
else if (target instanceof FarmingArmor) {
|
|
566
|
+
const idx = this.armor.indexOf(target);
|
|
567
|
+
if (idx >= 0) {
|
|
568
|
+
const updatedPiece = new FarmingArmor(target.item, this.options);
|
|
569
|
+
this.armor[idx] = updatedPiece;
|
|
570
|
+
this.armorSet.updateArmorSlot(updatedPiece);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
else if (target instanceof FarmingEquipment) {
|
|
574
|
+
const idx = this.equipment.indexOf(target);
|
|
575
|
+
if (idx >= 0) {
|
|
576
|
+
const updatedPiece = new FarmingEquipment(target.item, this.options);
|
|
577
|
+
this.equipment[idx] = updatedPiece;
|
|
578
|
+
this.armorSet.updateEquipmentSlot(updatedPiece);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
else if (target instanceof FarmingAccessory) {
|
|
582
|
+
const idx = this.accessories.indexOf(target);
|
|
583
|
+
if (idx >= 0) {
|
|
584
|
+
this.accessories[idx] = new FarmingAccessory(target.item, this.options);
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
else if (type === 'item' && id === 'rarity_upgrades' && value) {
|
|
589
|
+
target.item.attributes ??= {};
|
|
590
|
+
target.item.attributes.rarity_upgrades = String(value);
|
|
591
|
+
// Recomb affects rarity, which affects stats. Need to reload tool.
|
|
592
|
+
if (target instanceof FarmingTool) {
|
|
593
|
+
const idx = this.tools.indexOf(target);
|
|
594
|
+
if (idx >= 0) {
|
|
595
|
+
this.tools[idx] = new FarmingTool(target.item, this.options);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
else if (target instanceof FarmingArmor) {
|
|
599
|
+
const idx = this.armor.indexOf(target);
|
|
600
|
+
if (idx >= 0) {
|
|
601
|
+
const updatedPiece = new FarmingArmor(target.item, this.options);
|
|
602
|
+
this.armor[idx] = updatedPiece;
|
|
603
|
+
this.armorSet.updateArmorSlot(updatedPiece);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
else if (target instanceof FarmingEquipment) {
|
|
607
|
+
const idx = this.equipment.indexOf(target);
|
|
608
|
+
if (idx >= 0) {
|
|
609
|
+
const updatedPiece = new FarmingEquipment(target.item, this.options);
|
|
610
|
+
this.equipment[idx] = updatedPiece;
|
|
611
|
+
this.armorSet.updateEquipmentSlot(updatedPiece);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
else if (target instanceof FarmingAccessory) {
|
|
615
|
+
const idx = this.accessories.indexOf(target);
|
|
616
|
+
if (idx >= 0) {
|
|
617
|
+
this.accessories[idx] = new FarmingAccessory(target.item, this.options);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
else if (type === 'buy_item' && id) {
|
|
622
|
+
// Tier upgrade: replace the old item with a new one
|
|
623
|
+
const newItem = getFakeItem(id);
|
|
624
|
+
if (newItem) {
|
|
625
|
+
// Transfer enchantments, attributes, gems from old item
|
|
626
|
+
newItem.item.enchantments = {
|
|
627
|
+
...newItem.item.enchantments,
|
|
628
|
+
...target.item.enchantments,
|
|
629
|
+
};
|
|
630
|
+
newItem.item.attributes = {
|
|
631
|
+
...newItem.item.attributes,
|
|
632
|
+
...target.item.attributes,
|
|
633
|
+
};
|
|
634
|
+
newItem.item.gems = {
|
|
635
|
+
...newItem.item.gems,
|
|
636
|
+
...target.item.gems,
|
|
637
|
+
};
|
|
638
|
+
// Preserve the old item's UUID so the item remains trackable
|
|
639
|
+
newItem.item.uuid = target.item.uuid;
|
|
640
|
+
if (target instanceof FarmingTool && newItem instanceof FarmingTool) {
|
|
641
|
+
const idx = this.tools.indexOf(target);
|
|
642
|
+
if (idx >= 0) {
|
|
643
|
+
this.tools[idx] = new FarmingTool(newItem.item, this.options);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
else if (target instanceof FarmingArmor && newItem instanceof FarmingArmor) {
|
|
647
|
+
const idx = this.armor.indexOf(target);
|
|
648
|
+
if (idx >= 0) {
|
|
649
|
+
const updatedPiece = new FarmingArmor(newItem.item, this.options);
|
|
650
|
+
this.armor[idx] = updatedPiece;
|
|
651
|
+
this.armorSet.updateArmorSlot(updatedPiece);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
else if (target instanceof FarmingEquipment && newItem instanceof FarmingEquipment) {
|
|
655
|
+
const idx = this.equipment.indexOf(target);
|
|
656
|
+
if (idx >= 0) {
|
|
657
|
+
const updatedPiece = new FarmingEquipment(newItem.item, this.options);
|
|
658
|
+
this.equipment[idx] = updatedPiece;
|
|
659
|
+
this.armorSet.updateEquipmentSlot(updatedPiece);
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
else if (target instanceof FarmingAccessory && newItem instanceof FarmingAccessory) {
|
|
663
|
+
const idx = this.accessories.indexOf(target);
|
|
664
|
+
if (idx >= 0) {
|
|
665
|
+
this.accessories[idx] = new FarmingAccessory(newItem.item, this.options);
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
this.permFortune = this.getGeneralFortune();
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
else if (type === 'skill') {
|
|
674
|
+
if (key === 'farmingLevel' && value) {
|
|
675
|
+
this.options.farmingLevel = Number(value);
|
|
676
|
+
}
|
|
677
|
+
else if (key === 'anitaBonus' && value) {
|
|
678
|
+
this.options.anitaBonus = Number(value);
|
|
679
|
+
}
|
|
680
|
+
else if (key === 'communityCenter' && value) {
|
|
681
|
+
this.options.communityCenter = Number(value);
|
|
682
|
+
}
|
|
683
|
+
this.permFortune = this.getGeneralFortune();
|
|
684
|
+
}
|
|
685
|
+
else if (type === 'plot' && (value || id)) {
|
|
686
|
+
this.options.plotsUnlocked = Number(value);
|
|
687
|
+
// Also add to plots array if using id (the plot name)
|
|
688
|
+
if (id) {
|
|
689
|
+
this.options.plots ??= [];
|
|
690
|
+
if (!this.options.plots.includes(id)) {
|
|
691
|
+
this.options.plots.push(id);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
this.permFortune = this.getGeneralFortune();
|
|
695
|
+
}
|
|
696
|
+
else if (type === 'attribute' && key && value) {
|
|
697
|
+
this.options.attributes ??= {};
|
|
698
|
+
this.options.attributes[key] = Number(value);
|
|
699
|
+
this.permFortune = this.getGeneralFortune();
|
|
700
|
+
}
|
|
701
|
+
else if (type === 'crop_upgrade' && key && value) {
|
|
702
|
+
this.options.cropUpgrades ??= {};
|
|
703
|
+
// @ts-ignore
|
|
704
|
+
this.options.cropUpgrades[key] = Number(value);
|
|
705
|
+
this.permFortune = this.getGeneralFortune();
|
|
706
|
+
}
|
|
707
|
+
else if (type === 'setting' && key && value) {
|
|
708
|
+
if (key === 'cocoaFortuneUpgrade') {
|
|
709
|
+
this.options.cocoaFortuneUpgrade = Number(value);
|
|
710
|
+
}
|
|
711
|
+
this.permFortune = this.getGeneralFortune();
|
|
712
|
+
}
|
|
713
|
+
else if (type === 'unlock' && id) {
|
|
714
|
+
if (id === 'personal_best') {
|
|
715
|
+
this.options.personalBestsUnlocked = true;
|
|
716
|
+
}
|
|
717
|
+
this.permFortune = this.getGeneralFortune();
|
|
718
|
+
}
|
|
719
|
+
else if (type === 'buy_item' && id) {
|
|
720
|
+
const newItem = getFakeItem(id);
|
|
721
|
+
if (newItem) {
|
|
722
|
+
if (newItem instanceof FarmingTool) {
|
|
723
|
+
const oldIdx = itemUuid ? this.tools.findIndex((t) => t.item.uuid === itemUuid) : -1;
|
|
724
|
+
if (oldIdx >= 0) {
|
|
725
|
+
const oldItem = this.tools[oldIdx];
|
|
726
|
+
// Transfer enchantments, attributes, gems from old item
|
|
727
|
+
newItem.item.enchantments = {
|
|
728
|
+
...newItem.item.enchantments,
|
|
729
|
+
...oldItem.item.enchantments,
|
|
730
|
+
};
|
|
731
|
+
newItem.item.attributes = {
|
|
732
|
+
...newItem.item.attributes,
|
|
733
|
+
...oldItem.item.attributes,
|
|
734
|
+
};
|
|
735
|
+
newItem.item.gems = { ...newItem.item.gems, ...oldItem.item.gems };
|
|
736
|
+
// Re-instantiate to recalculate fortune with transferred properties
|
|
737
|
+
this.tools[oldIdx] = new FarmingTool(newItem.item, this.options);
|
|
738
|
+
}
|
|
739
|
+
else {
|
|
740
|
+
this.tools.push(newItem);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
else if (newItem instanceof FarmingArmor) {
|
|
744
|
+
const oldIdx = itemUuid ? this.armor.findIndex((a) => a.item.uuid === itemUuid) : -1;
|
|
745
|
+
if (oldIdx >= 0) {
|
|
746
|
+
const oldItem = this.armor[oldIdx];
|
|
747
|
+
newItem.item.enchantments = {
|
|
748
|
+
...newItem.item.enchantments,
|
|
749
|
+
...oldItem.item.enchantments,
|
|
750
|
+
};
|
|
751
|
+
newItem.item.attributes = {
|
|
752
|
+
...newItem.item.attributes,
|
|
753
|
+
...oldItem.item.attributes,
|
|
754
|
+
};
|
|
755
|
+
newItem.item.gems = { ...newItem.item.gems, ...oldItem.item.gems };
|
|
756
|
+
this.armor[oldIdx] = new FarmingArmor(newItem.item, this.options);
|
|
757
|
+
}
|
|
758
|
+
else {
|
|
759
|
+
this.armor.push(newItem);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
else if (newItem instanceof FarmingEquipment) {
|
|
763
|
+
const oldIdx = itemUuid ? this.equipment.findIndex((e) => e.item.uuid === itemUuid) : -1;
|
|
764
|
+
if (oldIdx >= 0) {
|
|
765
|
+
const oldItem = this.equipment[oldIdx];
|
|
766
|
+
newItem.item.enchantments = {
|
|
767
|
+
...newItem.item.enchantments,
|
|
768
|
+
...oldItem.item.enchantments,
|
|
769
|
+
};
|
|
770
|
+
newItem.item.attributes = {
|
|
771
|
+
...newItem.item.attributes,
|
|
772
|
+
...oldItem.item.attributes,
|
|
773
|
+
};
|
|
774
|
+
newItem.item.gems = { ...newItem.item.gems, ...oldItem.item.gems };
|
|
775
|
+
this.equipment[oldIdx] = new FarmingEquipment(newItem.item, this.options);
|
|
776
|
+
}
|
|
777
|
+
else {
|
|
778
|
+
this.equipment.push(newItem);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
else if (newItem instanceof FarmingAccessory) {
|
|
782
|
+
const oldIdx = itemUuid ? this.accessories.findIndex((a) => a.item.uuid === itemUuid) : -1;
|
|
783
|
+
if (oldIdx >= 0) {
|
|
784
|
+
const oldItem = this.accessories[oldIdx];
|
|
785
|
+
newItem.item.enchantments = {
|
|
786
|
+
...newItem.item.enchantments,
|
|
787
|
+
...oldItem.item.enchantments,
|
|
788
|
+
};
|
|
789
|
+
newItem.item.attributes = {
|
|
790
|
+
...newItem.item.attributes,
|
|
791
|
+
...oldItem.item.attributes,
|
|
792
|
+
};
|
|
793
|
+
newItem.item.gems = { ...newItem.item.gems, ...oldItem.item.gems };
|
|
794
|
+
this.accessories[oldIdx] = new FarmingAccessory(newItem.item, this.options);
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
this.accessories.push(newItem);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
this.permFortune = this.getGeneralFortune();
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Creates a deep clone of this FarmingPlayer that can be modified without affecting the original.
|
|
806
|
+
*/
|
|
807
|
+
clone() {
|
|
808
|
+
const cloneItems = (items) => {
|
|
809
|
+
return items.map((item) => ({
|
|
810
|
+
...item.item,
|
|
811
|
+
enchantments: { ...item.item.enchantments },
|
|
812
|
+
attributes: { ...item.item.attributes },
|
|
813
|
+
gems: { ...item.item.gems },
|
|
814
|
+
lore: [...(item.item.lore ?? [])],
|
|
815
|
+
}));
|
|
816
|
+
};
|
|
817
|
+
const clonedOptions = {
|
|
818
|
+
...this.options,
|
|
819
|
+
tools: cloneItems(this.tools),
|
|
820
|
+
armor: cloneItems(this.armor),
|
|
821
|
+
equipment: cloneItems(this.equipment),
|
|
822
|
+
accessories: cloneItems(this.accessories),
|
|
823
|
+
pets: this.pets.map((p) => ({ ...p.pet })),
|
|
824
|
+
cropUpgrades: { ...this.options.cropUpgrades },
|
|
825
|
+
milestones: { ...this.options.milestones },
|
|
826
|
+
exportableCrops: { ...this.options.exportableCrops },
|
|
827
|
+
personalBests: { ...this.options.personalBests },
|
|
828
|
+
collection: { ...this.options.collection },
|
|
829
|
+
bestiaryKills: { ...this.options.bestiaryKills },
|
|
830
|
+
attributes: { ...this.options.attributes },
|
|
831
|
+
plots: [...(this.options.plots ?? [])],
|
|
832
|
+
};
|
|
833
|
+
return new FarmingPlayer(clonedOptions);
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Expands an upgrade into a tree of follow-up upgrades.
|
|
837
|
+
* This applies the upgrade on a cloned player and recursively finds upgrades
|
|
838
|
+
* for the same target item.
|
|
839
|
+
*
|
|
840
|
+
* @param upgrade - The upgrade to expand
|
|
841
|
+
* @param options.maxDepth - Maximum recursion depth (default: 10)
|
|
842
|
+
* @param options.crop - Crop for crop-specific fortune calculations
|
|
843
|
+
* @param options.stats - Stats to track (default: [Stat.FarmingFortune])
|
|
844
|
+
* @param options.includeAllTierUpgradeChildren - If true, first-level children of tier upgrades include ALL available upgrades for the new item (default: false)
|
|
845
|
+
*/
|
|
846
|
+
expandUpgrade(upgrade, options) {
|
|
847
|
+
const { maxDepth = 10, crop, stats = [Stat.FarmingFortune], includeAllTierUpgradeChildren = false, } = options ?? {};
|
|
848
|
+
const visited = new Set();
|
|
849
|
+
const usedConflictKeys = new Set();
|
|
850
|
+
return this.buildUpgradeTree(upgrade, 0, maxDepth, crop, visited, usedConflictKeys, stats, includeAllTierUpgradeChildren);
|
|
851
|
+
}
|
|
852
|
+
buildUpgradeTree(upgrade, depth, maxDepth, crop, visited, usedConflictKeys, stats, includeAllTierUpgradeChildren) {
|
|
853
|
+
// Create unique key for this upgrade to detect cycles
|
|
854
|
+
const upgradeKey = this.getUpgradeKey(upgrade);
|
|
855
|
+
if (visited.has(upgradeKey)) {
|
|
856
|
+
// Return a leaf node if we've seen this exact upgrade before
|
|
857
|
+
const currentStats = this.getAllStats(stats, crop);
|
|
858
|
+
return {
|
|
859
|
+
upgrade,
|
|
860
|
+
statsBefore: currentStats,
|
|
861
|
+
statsAfter: currentStats,
|
|
862
|
+
statsGained: {},
|
|
863
|
+
totalCost: upgrade.cost,
|
|
864
|
+
children: [],
|
|
865
|
+
};
|
|
866
|
+
}
|
|
867
|
+
visited.add(upgradeKey);
|
|
868
|
+
// Clone player and apply upgrade
|
|
869
|
+
const clonedPlayer = this.clone();
|
|
870
|
+
const statsBefore = clonedPlayer.getAllStats(stats, crop);
|
|
871
|
+
clonedPlayer.applyUpgrade(upgrade);
|
|
872
|
+
const statsAfter = clonedPlayer.getAllStats(stats, crop);
|
|
873
|
+
const statsGained = this.computeStatsDiff(statsBefore, statsAfter);
|
|
874
|
+
const node = {
|
|
875
|
+
upgrade,
|
|
876
|
+
statsBefore,
|
|
877
|
+
statsAfter,
|
|
878
|
+
statsGained,
|
|
879
|
+
totalCost: upgrade.cost,
|
|
880
|
+
children: [],
|
|
881
|
+
};
|
|
882
|
+
// Stop recursion at max depth
|
|
883
|
+
if (depth >= maxDepth) {
|
|
884
|
+
return node;
|
|
885
|
+
}
|
|
886
|
+
// Add current upgrade's conflict key for children to prevent duplicates in siblings
|
|
887
|
+
const childConflictKeys = new Set(usedConflictKeys);
|
|
888
|
+
if (upgrade.conflictKey) {
|
|
889
|
+
childConflictKeys.add(upgrade.conflictKey);
|
|
890
|
+
}
|
|
891
|
+
// For buy_item (tier) upgrades, also add conflict keys from the original item's available upgrades.
|
|
892
|
+
// This prevents duplicate suggestions - if Squash has Pesterminator 1 available,
|
|
893
|
+
// Fermento's children shouldn't also suggest Pesterminator 1 (do it on Squash instead, it carries over).
|
|
894
|
+
// When includeAllTierUpgradeChildren is true and depth is 0, skip this filtering to show ALL upgrades.
|
|
895
|
+
const skipTierFiltering = includeAllTierUpgradeChildren && depth === 0;
|
|
896
|
+
if (!skipTierFiltering && upgrade.meta?.type === 'buy_item' && upgrade.meta?.itemUuid) {
|
|
897
|
+
const originalItem = this.tools.find((t) => t.item.uuid === upgrade.meta?.itemUuid) ??
|
|
898
|
+
this.armor.find((a) => a.item.uuid === upgrade.meta?.itemUuid) ??
|
|
899
|
+
this.equipment.find((e) => e.item.uuid === upgrade.meta?.itemUuid) ??
|
|
900
|
+
this.accessories.find((a) => a.item.uuid === upgrade.meta?.itemUuid);
|
|
901
|
+
if (originalItem && 'getUpgrades' in originalItem && typeof originalItem.getUpgrades === 'function') {
|
|
902
|
+
for (const originalUpgrade of originalItem.getUpgrades()) {
|
|
903
|
+
if (originalUpgrade.conflictKey) {
|
|
904
|
+
childConflictKeys.add(originalUpgrade.conflictKey);
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
// Find follow-up upgrades for the same target, excluding those with already-used conflict keys
|
|
910
|
+
const primaryStat = stats[0] ?? Stat.FarmingFortune;
|
|
911
|
+
const followUpUpgrades = this.getFollowUpUpgrades(clonedPlayer, upgrade, crop, primaryStat).filter((u) => !u.conflictKey || !childConflictKeys.has(u.conflictKey));
|
|
912
|
+
// Build children (using the cloned player's state)
|
|
913
|
+
for (const followUp of followUpUpgrades) {
|
|
914
|
+
const childNode = clonedPlayer.buildUpgradeTree(followUp, depth + 1, maxDepth, crop, new Set(visited), childConflictKeys, stats, includeAllTierUpgradeChildren);
|
|
915
|
+
node.children.push(childNode);
|
|
916
|
+
}
|
|
917
|
+
// Sort children by primary stat gained (highest first)
|
|
918
|
+
node.children.sort((a, b) => {
|
|
919
|
+
const aGain = a.statsGained[primaryStat] ?? 0;
|
|
920
|
+
const bGain = b.statsGained[primaryStat] ?? 0;
|
|
921
|
+
return bGain - aGain;
|
|
922
|
+
});
|
|
923
|
+
return node;
|
|
924
|
+
}
|
|
925
|
+
getAllStats(stats, crop) {
|
|
926
|
+
const result = {};
|
|
927
|
+
for (const stat of stats) {
|
|
928
|
+
let value = this.getStat(stat);
|
|
929
|
+
// Add crop-specific fortune if applicable
|
|
930
|
+
if (crop && stat === Stat.FarmingFortune) {
|
|
931
|
+
value += this.getCropFortune(crop).fortune;
|
|
932
|
+
}
|
|
933
|
+
if (value !== 0) {
|
|
934
|
+
result[stat] = value;
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
return result;
|
|
938
|
+
}
|
|
939
|
+
computeStatsDiff(before, after) {
|
|
940
|
+
const result = {};
|
|
941
|
+
const allStats = new Set([...Object.keys(before), ...Object.keys(after)]);
|
|
942
|
+
for (const stat of allStats) {
|
|
943
|
+
const beforeVal = before[stat] ?? 0;
|
|
944
|
+
const afterVal = after[stat] ?? 0;
|
|
945
|
+
const diff = afterVal - beforeVal;
|
|
946
|
+
if (diff !== 0) {
|
|
947
|
+
result[stat] = diff;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
return result;
|
|
951
|
+
}
|
|
952
|
+
getUpgradeKey(upgrade) {
|
|
953
|
+
const meta = upgrade.meta;
|
|
954
|
+
if (!meta)
|
|
955
|
+
return `${upgrade.title}`;
|
|
956
|
+
const parts = [meta.type ?? '', meta.id ?? '', meta.itemUuid ?? '', meta.key ?? '', String(meta.value ?? '')];
|
|
957
|
+
return parts.join(':');
|
|
958
|
+
}
|
|
959
|
+
getFollowUpUpgrades(player, appliedUpgrade, crop, primaryStat) {
|
|
960
|
+
const meta = appliedUpgrade.meta;
|
|
961
|
+
if (!meta)
|
|
962
|
+
return [];
|
|
963
|
+
const itemUuid = meta.itemUuid;
|
|
964
|
+
const upgrades = [];
|
|
965
|
+
// Handle buy_item specially - these are item tier upgrades
|
|
966
|
+
// After applying, we want ALL upgrades for the new item, not filtered by type
|
|
967
|
+
// The new item preserves the old UUID, but we search by skyblockId to find the upgraded tier
|
|
968
|
+
if (meta.type === 'buy_item' && meta.id) {
|
|
969
|
+
const newItemId = meta.id;
|
|
970
|
+
const target = player.tools.find((t) => t.item.skyblockId === newItemId) ??
|
|
971
|
+
player.armor.find((a) => a.item.skyblockId === newItemId) ??
|
|
972
|
+
player.equipment.find((e) => e.item.skyblockId === newItemId) ??
|
|
973
|
+
player.accessories.find((a) => a.item.skyblockId === newItemId);
|
|
974
|
+
if (target && 'getUpgrades' in target && typeof target.getUpgrades === 'function') {
|
|
975
|
+
upgrades.push(...target.getUpgrades({ stat: primaryStat }));
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
else if (itemUuid) {
|
|
979
|
+
// Item-specific upgrade - find upgrades for the same item
|
|
980
|
+
const target = player.tools.find((t) => t.item.uuid === itemUuid) ??
|
|
981
|
+
player.armor.find((a) => a.item.uuid === itemUuid) ??
|
|
982
|
+
player.equipment.find((e) => e.item.uuid === itemUuid) ??
|
|
983
|
+
player.accessories.find((a) => a.item.uuid === itemUuid);
|
|
984
|
+
if (target && 'getUpgrades' in target && typeof target.getUpgrades === 'function') {
|
|
985
|
+
const itemUpgrades = target.getUpgrades({ stat: primaryStat });
|
|
986
|
+
// Filter to only include upgrades of the same type (enchant chains, tier upgrades, etc.)
|
|
987
|
+
// For gem upgrades, also match on slot to only show follow-ups for that specific slot
|
|
988
|
+
for (const u of itemUpgrades) {
|
|
989
|
+
if (u.meta?.type === meta.type && u.meta?.key === meta.key) {
|
|
990
|
+
// For gem upgrades, also require matching slot
|
|
991
|
+
if (meta.type === 'gem' && meta.slot && u.meta?.slot !== meta.slot) {
|
|
992
|
+
continue;
|
|
993
|
+
}
|
|
994
|
+
upgrades.push(u);
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
else if (meta.type === 'skill' || meta.type === 'plot' || meta.type === 'attribute') {
|
|
1000
|
+
// General upgrades - find the next level of the same upgrade type
|
|
1001
|
+
const generalUpgrades = player.getUpgrades({ stat: primaryStat });
|
|
1002
|
+
for (const u of generalUpgrades) {
|
|
1003
|
+
if (u.meta?.type === meta.type && u.meta?.key === meta.key) {
|
|
1004
|
+
upgrades.push(u);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
else if (meta.type === 'crop_upgrade' && crop) {
|
|
1009
|
+
// Crop-specific upgrades
|
|
1010
|
+
const cropUpgrades = player.getCropUpgrades(crop);
|
|
1011
|
+
for (const u of cropUpgrades) {
|
|
1012
|
+
if (u.meta?.type === meta.type && u.meta?.key === meta.key) {
|
|
1013
|
+
upgrades.push(u);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
return upgrades;
|
|
1018
|
+
}
|
|
393
1019
|
}
|
|
394
1020
|
//# sourceMappingURL=player.js.map
|