farming-weight 0.14.2 → 0.15.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.
Files changed (318) hide show
  1. package/README.md +45 -18
  2. package/dist/constants/attributes.d.ts +12 -7
  3. package/dist/constants/attributes.js +17 -39
  4. package/dist/constants/attributes.js.map +1 -1
  5. package/dist/constants/chips.d.ts +1 -6
  6. package/dist/constants/chips.js +11 -15
  7. package/dist/constants/chips.js.map +1 -1
  8. package/dist/constants/crops.d.ts +28 -4
  9. package/dist/constants/crops.js +65 -2
  10. package/dist/constants/crops.js.map +1 -1
  11. package/dist/constants/enchants.d.ts +1 -1
  12. package/dist/constants/enchants.js +117 -68
  13. package/dist/constants/enchants.js.map +1 -1
  14. package/dist/constants/garden.js +152 -137
  15. package/dist/constants/garden.js.map +1 -1
  16. package/dist/constants/pests.d.ts +3 -2
  17. package/dist/constants/pests.js +5 -0
  18. package/dist/constants/pests.js.map +1 -1
  19. package/dist/constants/reforge-types.d.ts +57 -0
  20. package/dist/constants/reforge-types.js +40 -0
  21. package/dist/constants/reforge-types.js.map +1 -0
  22. package/dist/constants/reforges.d.ts +8 -55
  23. package/dist/constants/reforges.js +10 -635
  24. package/dist/constants/reforges.js.map +1 -1
  25. package/dist/constants/specific.js +11 -11
  26. package/dist/constants/specific.js.map +1 -1
  27. package/dist/constants/stats.d.ts +1 -0
  28. package/dist/constants/stats.js +2 -0
  29. package/dist/constants/stats.js.map +1 -1
  30. package/dist/constants/tempfortune.d.ts +5 -0
  31. package/dist/constants/tempfortune.js +36 -7
  32. package/dist/constants/tempfortune.js.map +1 -1
  33. package/dist/constants/upgrades.d.ts +36 -3
  34. package/dist/constants/upgrades.js +12 -0
  35. package/dist/constants/upgrades.js.map +1 -1
  36. package/dist/crops/special.d.ts +1 -1
  37. package/dist/crops/special.js.map +1 -1
  38. package/dist/effects/environment.d.ts +12 -0
  39. package/dist/effects/environment.js +33 -0
  40. package/dist/effects/environment.js.map +1 -0
  41. package/dist/effects/index.d.ts +4 -0
  42. package/dist/effects/index.js +5 -0
  43. package/dist/effects/index.js.map +1 -0
  44. package/dist/effects/matcher.d.ts +23 -0
  45. package/dist/effects/matcher.js +108 -0
  46. package/dist/effects/matcher.js.map +1 -0
  47. package/dist/effects/resolver.d.ts +63 -0
  48. package/dist/effects/resolver.js +239 -0
  49. package/dist/effects/resolver.js.map +1 -0
  50. package/dist/effects/summary.d.ts +5 -0
  51. package/dist/effects/summary.js +21 -0
  52. package/dist/effects/summary.js.map +1 -0
  53. package/dist/effects/types.d.ts +147 -0
  54. package/dist/effects/types.js +9 -0
  55. package/dist/effects/types.js.map +1 -0
  56. package/dist/features/composter/upgrades.js +5 -5
  57. package/dist/features/composter/upgrades.js.map +1 -1
  58. package/dist/fortune/farmingaccessory.d.ts +9 -1
  59. package/dist/fortune/farmingaccessory.js +24 -13
  60. package/dist/fortune/farmingaccessory.js.map +1 -1
  61. package/dist/fortune/farmingarmor.d.ts +21 -10
  62. package/dist/fortune/farmingarmor.js +89 -29
  63. package/dist/fortune/farmingarmor.js.map +1 -1
  64. package/dist/fortune/farmingequipment.d.ts +10 -1
  65. package/dist/fortune/farmingequipment.js +44 -11
  66. package/dist/fortune/farmingequipment.js.map +1 -1
  67. package/dist/fortune/farmingpet.d.ts +16 -0
  68. package/dist/fortune/farmingpet.js +207 -0
  69. package/dist/fortune/farmingpet.js.map +1 -1
  70. package/dist/fortune/farmingtool.d.ts +13 -6
  71. package/dist/fortune/farmingtool.js +60 -44
  72. package/dist/fortune/farmingtool.js.map +1 -1
  73. package/dist/fortune/lotuspiecebonus.d.ts +6 -0
  74. package/dist/fortune/lotuspiecebonus.js +20 -0
  75. package/dist/fortune/lotuspiecebonus.js.map +1 -0
  76. package/dist/fortune/upgradeable.d.ts +2 -4
  77. package/dist/fortune/upgradeablebase.d.ts +2 -4
  78. package/dist/fortune/upgradeablebase.js +4 -10
  79. package/dist/fortune/upgradeablebase.js.map +1 -1
  80. package/dist/index.d.ts +4 -0
  81. package/dist/index.js +4 -0
  82. package/dist/index.js.map +1 -1
  83. package/dist/items/accessories/base.d.ts +3 -0
  84. package/dist/items/accessories/base.js +4 -0
  85. package/dist/items/accessories/base.js.map +1 -0
  86. package/dist/items/accessories/fermento-family.d.ts +91 -0
  87. package/dist/items/accessories/fermento-family.js +125 -0
  88. package/dist/items/accessories/fermento-family.js.map +1 -0
  89. package/dist/items/accessories/freshly-baked.d.ts +80 -0
  90. package/dist/items/accessories/freshly-baked.js +119 -0
  91. package/dist/items/accessories/freshly-baked.js.map +1 -0
  92. package/dist/items/accessories/index.d.ts +25 -0
  93. package/dist/items/accessories/index.js +25 -0
  94. package/dist/items/accessories/index.js.map +1 -0
  95. package/dist/items/accessories/power-relic.d.ts +18 -0
  96. package/dist/items/accessories/power-relic.js +37 -0
  97. package/dist/items/accessories/power-relic.js.map +1 -0
  98. package/dist/items/accessories/special.d.ts +24 -0
  99. package/dist/items/accessories/special.js +41 -0
  100. package/dist/items/accessories/special.js.map +1 -0
  101. package/dist/items/accessories.d.ts +2 -3
  102. package/dist/items/accessories.js +1 -137
  103. package/dist/items/accessories.js.map +1 -1
  104. package/dist/items/armor/biohazard.js +4 -4
  105. package/dist/items/armor/biohazard.js.map +1 -1
  106. package/dist/items/armor/cropie.d.ts +4 -0
  107. package/dist/items/armor/cropie.js +10 -4
  108. package/dist/items/armor/cropie.js.map +1 -1
  109. package/dist/items/armor/farm.js +4 -4
  110. package/dist/items/armor/farm.js.map +1 -1
  111. package/dist/items/armor/fermento.d.ts +4 -0
  112. package/dist/items/armor/fermento.js +10 -4
  113. package/dist/items/armor/fermento.js.map +1 -1
  114. package/dist/items/armor/groups.d.ts +2 -0
  115. package/dist/items/armor/groups.js +17 -0
  116. package/dist/items/armor/groups.js.map +1 -0
  117. package/dist/items/armor/helianthus.js +4 -4
  118. package/dist/items/armor/helianthus.js.map +1 -1
  119. package/dist/items/armor/melon.d.ts +4 -0
  120. package/dist/items/armor/melon.js +10 -4
  121. package/dist/items/armor/melon.js.map +1 -1
  122. package/dist/items/armor/mushroom.js +4 -4
  123. package/dist/items/armor/mushroom.js.map +1 -1
  124. package/dist/items/armor/rabbit.js +4 -4
  125. package/dist/items/armor/rabbit.js.map +1 -1
  126. package/dist/items/armor/special.js +4 -4
  127. package/dist/items/armor/special.js.map +1 -1
  128. package/dist/items/armor/squash.d.ts +4 -0
  129. package/dist/items/armor/squash.js +10 -4
  130. package/dist/items/armor/squash.js.map +1 -1
  131. package/dist/items/equipment/blossom.js +4 -4
  132. package/dist/items/equipment/blossom.js.map +1 -1
  133. package/dist/items/equipment/lotus.js +4 -4
  134. package/dist/items/equipment/lotus.js.map +1 -1
  135. package/dist/items/equipment/pesthunter.js +4 -4
  136. package/dist/items/equipment/pesthunter.js.map +1 -1
  137. package/dist/items/equipment/special.js +2 -2
  138. package/dist/items/equipment/special.js.map +1 -1
  139. package/dist/items/gems/base.d.ts +19 -0
  140. package/dist/items/gems/base.js +60 -0
  141. package/dist/items/gems/base.js.map +1 -0
  142. package/dist/items/gems/index.d.ts +1 -0
  143. package/dist/items/gems/index.js +2 -0
  144. package/dist/items/gems/index.js.map +1 -0
  145. package/dist/items/pets/bee.d.ts +9 -0
  146. package/dist/items/pets/bee.js +33 -1
  147. package/dist/items/pets/bee.js.map +1 -1
  148. package/dist/items/pets/chicken.js +1 -1
  149. package/dist/items/pets/chicken.js.map +1 -1
  150. package/dist/items/pets/elephant.js +1 -1
  151. package/dist/items/pets/elephant.js.map +1 -1
  152. package/dist/items/pets/hedgehog.d.ts +5 -0
  153. package/dist/items/pets/hedgehog.js +6 -1
  154. package/dist/items/pets/hedgehog.js.map +1 -1
  155. package/dist/items/pets/mooshroom-cow.js +1 -1
  156. package/dist/items/pets/mooshroom-cow.js.map +1 -1
  157. package/dist/items/pets/mosquito.js +1 -1
  158. package/dist/items/pets/mosquito.js.map +1 -1
  159. package/dist/items/pets/pig.d.ts +5 -0
  160. package/dist/items/pets/pig.js +19 -14
  161. package/dist/items/pets/pig.js.map +1 -1
  162. package/dist/items/pets/rabbit.js +1 -1
  163. package/dist/items/pets/rabbit.js.map +1 -1
  164. package/dist/items/pets/rose-dragon.js +12 -14
  165. package/dist/items/pets/rose-dragon.js.map +1 -1
  166. package/dist/items/pets/slug.js +1 -1
  167. package/dist/items/pets/slug.js.map +1 -1
  168. package/dist/items/pets.d.ts +1 -1
  169. package/dist/items/pets.js +18 -4
  170. package/dist/items/pets.js.map +1 -1
  171. package/dist/items/reforges/base.d.ts +18 -0
  172. package/dist/items/reforges/base.js +54 -0
  173. package/dist/items/reforges/base.js.map +1 -0
  174. package/dist/items/reforges/blessed.d.ts +7 -0
  175. package/dist/items/reforges/blessed.js +90 -0
  176. package/dist/items/reforges/blessed.js.map +1 -0
  177. package/dist/items/reforges/blooming.d.ts +4 -0
  178. package/dist/items/reforges/blooming.js +43 -0
  179. package/dist/items/reforges/blooming.js.map +1 -0
  180. package/dist/items/reforges/bountiful.d.ts +4 -0
  181. package/dist/items/reforges/bountiful.js +63 -0
  182. package/dist/items/reforges/bountiful.js.map +1 -0
  183. package/dist/items/reforges/bustling.d.ts +4 -0
  184. package/dist/items/reforges/bustling.js +26 -0
  185. package/dist/items/reforges/bustling.js.map +1 -0
  186. package/dist/items/reforges/deep-fried.d.ts +7 -0
  187. package/dist/items/reforges/deep-fried.js +38 -0
  188. package/dist/items/reforges/deep-fried.js.map +1 -0
  189. package/dist/items/reforges/earthy.d.ts +4 -0
  190. package/dist/items/reforges/earthy.js +26 -0
  191. package/dist/items/reforges/earthy.js.map +1 -0
  192. package/dist/items/reforges/green-thumb.d.ts +4 -0
  193. package/dist/items/reforges/green-thumb.js +43 -0
  194. package/dist/items/reforges/green-thumb.js.map +1 -0
  195. package/dist/items/reforges/index.d.ts +16 -0
  196. package/dist/items/reforges/index.js +43 -0
  197. package/dist/items/reforges/index.js.map +1 -0
  198. package/dist/items/reforges/mantid.d.ts +4 -0
  199. package/dist/items/reforges/mantid.js +43 -0
  200. package/dist/items/reforges/mantid.js.map +1 -0
  201. package/dist/items/reforges/mossy.d.ts +4 -0
  202. package/dist/items/reforges/mossy.js +44 -0
  203. package/dist/items/reforges/mossy.js.map +1 -0
  204. package/dist/items/reforges/overpriced.d.ts +4 -0
  205. package/dist/items/reforges/overpriced.js +43 -0
  206. package/dist/items/reforges/overpriced.js.map +1 -0
  207. package/dist/items/reforges/robust.d.ts +4 -0
  208. package/dist/items/reforges/robust.js +22 -0
  209. package/dist/items/reforges/robust.js.map +1 -0
  210. package/dist/items/reforges/rooted.d.ts +4 -0
  211. package/dist/items/reforges/rooted.js +43 -0
  212. package/dist/items/reforges/rooted.js.map +1 -0
  213. package/dist/items/reforges/squeaky.d.ts +4 -0
  214. package/dist/items/reforges/squeaky.js +47 -0
  215. package/dist/items/reforges/squeaky.js.map +1 -0
  216. package/dist/items/sources/attributes.d.ts +119 -0
  217. package/dist/items/sources/attributes.js +285 -0
  218. package/dist/items/sources/attributes.js.map +1 -0
  219. package/dist/items/sources/base.d.ts +33 -0
  220. package/dist/items/sources/base.js +13 -0
  221. package/dist/items/sources/base.js.map +1 -0
  222. package/dist/items/sources/chips.d.ts +54 -0
  223. package/dist/items/sources/chips.js +100 -0
  224. package/dist/items/sources/chips.js.map +1 -0
  225. package/dist/items/sources/effects-util.d.ts +10 -0
  226. package/dist/items/sources/effects-util.js +35 -0
  227. package/dist/items/sources/effects-util.js.map +1 -0
  228. package/dist/items/sources/enchants.d.ts +28 -0
  229. package/dist/items/sources/enchants.js +87 -0
  230. package/dist/items/sources/enchants.js.map +1 -0
  231. package/dist/items/sources/gems.d.ts +9 -0
  232. package/dist/items/sources/gems.js +35 -0
  233. package/dist/items/sources/gems.js.map +1 -0
  234. package/dist/items/sources/index.d.ts +6 -0
  235. package/dist/items/sources/index.js +7 -0
  236. package/dist/items/sources/index.js.map +1 -0
  237. package/dist/items/sources/reforges.d.ts +8 -0
  238. package/dist/items/sources/reforges.js +10 -0
  239. package/dist/items/sources/reforges.js.map +1 -0
  240. package/dist/items/tools/cactus-knife.js +6 -6
  241. package/dist/items/tools/cactus-knife.js.map +1 -1
  242. package/dist/items/tools/cocoa-chopper.js +6 -6
  243. package/dist/items/tools/cocoa-chopper.js.map +1 -1
  244. package/dist/items/tools/dicers.js +6 -6
  245. package/dist/items/tools/dicers.js.map +1 -1
  246. package/dist/items/tools/fungi-cutter.js +6 -6
  247. package/dist/items/tools/fungi-cutter.js.map +1 -1
  248. package/dist/items/tools/mathematical-hoes.d.ts +5 -5
  249. package/dist/items/tools/mathematical-hoes.js +26 -26
  250. package/dist/items/tools/mathematical-hoes.js.map +1 -1
  251. package/dist/items/tools/special.js +2 -2
  252. package/dist/items/tools/special.js.map +1 -1
  253. package/dist/items/types/pets.d.ts +0 -2
  254. package/dist/player/player.d.ts +60 -7
  255. package/dist/player/player.js +318 -128
  256. package/dist/player/player.js.map +1 -1
  257. package/dist/player/playeroptions.d.ts +25 -1
  258. package/dist/player/playeroptions.js.map +1 -1
  259. package/dist/upgrades/enchantupgrades.js +22 -8
  260. package/dist/upgrades/enchantupgrades.js.map +1 -1
  261. package/dist/upgrades/getsourceprogress.js +7 -0
  262. package/dist/upgrades/getsourceprogress.js.map +1 -1
  263. package/dist/upgrades/groups.d.ts +3 -0
  264. package/dist/upgrades/groups.js +86 -0
  265. package/dist/upgrades/groups.js.map +1 -0
  266. package/dist/upgrades/itemcatalog.d.ts +3 -0
  267. package/dist/upgrades/itemcatalog.js +21 -0
  268. package/dist/upgrades/itemcatalog.js.map +1 -0
  269. package/dist/upgrades/itemregistry.d.ts +1 -2
  270. package/dist/upgrades/itemregistry.js +23 -3
  271. package/dist/upgrades/itemregistry.js.map +1 -1
  272. package/dist/upgrades/sources/accessorysources.js +1 -1
  273. package/dist/upgrades/sources/accessorysources.js.map +1 -1
  274. package/dist/upgrades/sources/armorsetsources.js +16 -16
  275. package/dist/upgrades/sources/armorsetsources.js.map +1 -1
  276. package/dist/upgrades/sources/cropsources.js +10 -5
  277. package/dist/upgrades/sources/cropsources.js.map +1 -1
  278. package/dist/upgrades/sources/dynamicfortunesources.d.ts +5 -2
  279. package/dist/upgrades/sources/effectsources.d.ts +4 -0
  280. package/dist/upgrades/sources/effectsources.js +60 -0
  281. package/dist/upgrades/sources/effectsources.js.map +1 -0
  282. package/dist/upgrades/sources/gearsources.js +33 -8
  283. package/dist/upgrades/sources/gearsources.js.map +1 -1
  284. package/dist/upgrades/sources/generalsources.d.ts +3 -2
  285. package/dist/upgrades/sources/generalsources.js +313 -22
  286. package/dist/upgrades/sources/generalsources.js.map +1 -1
  287. package/dist/upgrades/sources/toolsources.js +64 -84
  288. package/dist/upgrades/sources/toolsources.js.map +1 -1
  289. package/dist/upgrades/upgradekeys.d.ts +2 -0
  290. package/dist/upgrades/upgradekeys.js +5 -0
  291. package/dist/upgrades/upgradekeys.js.map +1 -0
  292. package/dist/upgrades/upgrades.d.ts +5 -7
  293. package/dist/upgrades/upgrades.js +142 -43
  294. package/dist/upgrades/upgrades.js.map +1 -1
  295. package/dist/upgrades/upgradeutils.d.ts +2 -4
  296. package/dist/upgrades/upgradeutils.js +26 -6
  297. package/dist/upgrades/upgradeutils.js.map +1 -1
  298. package/dist/util/bestiary.d.ts +10 -0
  299. package/dist/util/bestiary.js +41 -0
  300. package/dist/util/bestiary.js.map +1 -0
  301. package/dist/util/enchants.d.ts +6 -0
  302. package/dist/util/enchants.js +20 -5
  303. package/dist/util/enchants.js.map +1 -1
  304. package/dist/util/gems.js +4 -30
  305. package/dist/util/gems.js.map +1 -1
  306. package/dist/util/itemstats.d.ts +3 -0
  307. package/dist/util/itemstats.js +20 -2
  308. package/dist/util/itemstats.js.map +1 -1
  309. package/dist/util/ratecalc-effects.d.ts +64 -0
  310. package/dist/util/ratecalc-effects.js +252 -0
  311. package/dist/util/ratecalc-effects.js.map +1 -0
  312. package/dist/util/ratecalc.d.ts +0 -33
  313. package/dist/util/ratecalc.js +0 -187
  314. package/dist/util/ratecalc.js.map +1 -1
  315. package/dist/util/skyblocktime.d.ts +1 -0
  316. package/dist/util/skyblocktime.js +4 -0
  317. package/dist/util/skyblocktime.js.map +1 -1
  318. package/package.json +2 -1
@@ -1,22 +1,31 @@
1
1
  import { normalizeAttributes } from '../constants/attributes.js';
2
2
  import { getChipInputLevel, getChipLevel, getChipTempMultiplierPerLevel, normalizeChipId, normalizeChipLevels, } from '../constants/chips.js';
3
3
  import { CROP_INFO } from '../constants/crops.js';
4
+ import { compareRarity } from '../constants/reforge-types.js';
4
5
  import { getContributoryStats, Stat } from '../constants/stats.js';
5
6
  import { TEMPORARY_FORTUNE } from '../constants/tempfortune.js';
6
- import { UpgradeAction, UpgradeCategory } from '../constants/upgrades.js';
7
+ import { getQueryStats, UpgradeAction, UpgradeCategory, } from '../constants/upgrades.js';
8
+ import { buildEffectEnvironment } from '../effects/environment.js';
9
+ import { resolveOverbloomBreakdown, resolveStatBreakdown } from '../effects/resolver.js';
10
+ import { effectsToSummaries } from '../effects/summary.js';
7
11
  import { FarmingAccessory } from '../fortune/farmingaccessory.js';
8
12
  import { ArmorSet, FarmingArmor } from '../fortune/farmingarmor.js';
9
13
  import { FarmingEquipment } from '../fortune/farmingequipment.js';
10
14
  import { FarmingPet } from '../fortune/farmingpet.js';
11
15
  import { FarmingTool } from '../fortune/farmingtool.js';
12
16
  import { FarmingPets } from '../items/pets.js';
17
+ import { FARMING_ATTRIBUTE_SHARD_CLASSES } from '../items/sources/attributes.js';
18
+ import { GARDEN_CHIP_CLASSES } from '../items/sources/chips.js';
13
19
  import { FARMING_TOOLS } from '../items/tools.js';
14
20
  import { getSourceProgress } from '../upgrades/getsourceprogress.js';
21
+ import { withGroupedUpgrades } from '../upgrades/groups.js';
15
22
  import { getFakeItem } from '../upgrades/itemregistry.js';
16
23
  import { CROP_FORTUNE_SOURCES } from '../upgrades/sources/cropsources.js';
24
+ import { collectCropFortuneSourceEffects, collectGeneralFortuneSourceEffects, } from '../upgrades/sources/effectsources.js';
17
25
  import { GENERAL_FORTUNE_SOURCES } from '../upgrades/sources/generalsources.js';
18
26
  import { filterAndSortUpgrades } from '../upgrades/upgradeutils.js';
19
- import { calculateDetailedDrops } from '../util/ratecalc.js';
27
+ import { nextRarity, previousRarity } from '../util/itemstats.js';
28
+ import { calculateDetailedDropsFromEffects } from '../util/ratecalc-effects.js';
20
29
  import { createFarmingWeightCalculator } from '../weight/weightcalc.js';
21
30
  export function createFarmingPlayer(options) {
22
31
  return new FarmingPlayer(options);
@@ -125,9 +134,11 @@ export class FarmingPlayer {
125
134
  let pool = [];
126
135
  if (this.options.accessories[0] instanceof FarmingAccessory) {
127
136
  pool = this.options.accessories.sort((a, b) => b.fortune - a.fortune);
137
+ for (const acc of pool)
138
+ acc.setOptions(this.options);
128
139
  }
129
140
  else {
130
- pool = FarmingAccessory.fromArray(this.options.accessories);
141
+ pool = FarmingAccessory.fromArray(this.options.accessories, this.options);
131
142
  }
132
143
  // Filter by unique family (keep highest rarity/fortune)
133
144
  const familyMap = new Map();
@@ -147,15 +158,21 @@ export class FarmingPlayer {
147
158
  this.activeAccessories = [...familyMap.values(), ...others].sort((a, b) => b.fortune - a.fortune);
148
159
  this.accessories = pool;
149
160
  }
161
+ syncActiveAccessories() {
162
+ this.options.accessories = this.accessories;
163
+ this.populateActiveAccessories();
164
+ }
150
165
  changeArmor(armor) {
151
166
  this.armorSet = new ArmorSet(armor.sort((a, b) => b.fortune - a.fortune));
152
167
  }
153
168
  selectTool(tool) {
154
169
  this.selectedTool = tool;
170
+ this.options.selectedTool = tool;
155
171
  this.permFortune = this.getGeneralFortune();
156
172
  }
157
173
  selectPet(pet) {
158
174
  this.selectedPet = pet;
175
+ this.options.selectedPet = pet;
159
176
  this.permFortune = this.getGeneralFortune();
160
177
  }
161
178
  setStrength(strength) {
@@ -168,14 +185,47 @@ export class FarmingPlayer {
168
185
  getProgress(stats) {
169
186
  return getSourceProgress(this, GENERAL_FORTUNE_SOURCES, false, stats);
170
187
  }
188
+ getPetProgress(stats) {
189
+ return this.pets.flatMap((pet) => pet.getProgress(stats, this));
190
+ }
171
191
  getUpgrades(options) {
172
- const stats = options?.stat ? [options.stat] : undefined;
192
+ const hasExplicitStats = (options?.stats?.length ?? 0) > 0 || options?.stat !== undefined;
193
+ const stats = hasExplicitStats ? getQueryStats(options) : undefined;
173
194
  const upgrades = getSourceProgress(this, GENERAL_FORTUNE_SOURCES, false, stats).flatMap((source) => source.upgrades ?? []);
174
195
  const armorSetUpgrades = this.armorSet.getUpgrades(options);
175
196
  if (armorSetUpgrades.length > 0) {
176
197
  upgrades.push(...armorSetUpgrades);
177
198
  }
178
- return filterAndSortUpgrades(upgrades, options);
199
+ // For non-FarmingFortune stats (e.g. Overbloom), tool upgrades aren't tied to a single
200
+ // crop and won't be surfaced by getCropUpgrades, so include them here.
201
+ if (stats?.some((stat) => stat !== Stat.FarmingFortune)) {
202
+ const tools = this.options.selectedCrop !== undefined
203
+ ? [this.getSelectedCropTool(this.options.selectedCrop)].filter((tool) => tool !== undefined)
204
+ : this.tools;
205
+ for (const tool of tools) {
206
+ upgrades.push(...tool.getUpgrades(options));
207
+ }
208
+ }
209
+ const filtered = filterAndSortUpgrades(upgrades, options);
210
+ return options?.includeUpgradeGroups ? withGroupedUpgrades(filtered) : filtered;
211
+ }
212
+ getStatView(query) {
213
+ const stats = query.stats.length > 0 ? query.stats : [Stat.FarmingFortune];
214
+ const totals = {};
215
+ const breakdowns = {};
216
+ for (const stat of stats) {
217
+ totals[stat] = this.getStat(stat, query.crop);
218
+ breakdowns[stat] = this.getStatBreakdown(stat, query.crop);
219
+ }
220
+ const env = this.buildEnvironment(query.crop);
221
+ const effects = effectsToSummaries(this.collectEffects(env), stats);
222
+ const upgrades = this.getUpgrades({ stats });
223
+ return {
224
+ totals,
225
+ breakdowns,
226
+ effects,
227
+ upgrades,
228
+ };
179
229
  }
180
230
  getCropUpgrades(crop, tool) {
181
231
  const upgrades = [];
@@ -196,16 +246,25 @@ export class FarmingPlayer {
196
246
  const startingInfo = FARMING_TOOLS[CROP_INFO[crop].startingTool];
197
247
  if (startingInfo) {
198
248
  const fakeItem = getFakeItem(startingInfo.skyblockId, this.options);
249
+ const startingToolFortune = fakeItem?.getStat(CROP_INFO[crop].fortuneType, crop) ?? 0;
199
250
  upgrades.push({
200
251
  title: startingInfo.name,
201
252
  action: UpgradeAction.Purchase,
202
- increase: fakeItem?.getFortune() ?? 0,
253
+ increase: startingToolFortune,
254
+ stats: {
255
+ [CROP_INFO[crop].fortuneType]: startingToolFortune,
256
+ },
203
257
  wiki: startingInfo.wiki,
258
+ purchase: startingInfo.skyblockId,
204
259
  max: fakeItem?.getProgress()?.reduce((acc, p) => acc + p.max, 0) ?? 0,
205
260
  category: UpgradeCategory.Item,
206
261
  cost: {
207
262
  copper: 250,
208
263
  },
264
+ meta: {
265
+ type: 'buy_item',
266
+ id: startingInfo.skyblockId,
267
+ },
209
268
  });
210
269
  }
211
270
  }
@@ -227,8 +286,68 @@ export class FarmingPlayer {
227
286
  }
228
287
  return val;
229
288
  }
230
- getStat(stat) {
231
- const breakdown = this.getStatBreakdown(stat);
289
+ /**
290
+ * Build the canonical {@link EffectEnvironment} for this player + crop.
291
+ * Thin wrapper over {@link buildEffectEnvironment}; provided so callers
292
+ * never have to import the helper directly.
293
+ */
294
+ buildEnvironment(crop) {
295
+ return buildEffectEnvironment(this, crop);
296
+ }
297
+ /**
298
+ * Aggregate the declarative {@link Effect}[] from every active source on
299
+ * the player: armor set (armor + equipment + set bonuses, including each
300
+ * piece's reforge & enchants), the selected tool, active accessories, the
301
+ * selected pet, every attribute shard, and every garden chip.
302
+ *
303
+ * Sources with a `getActive` guard that returns `active: false` are
304
+ * skipped. Sources whose `getEffects` returns `[]` contribute nothing.
305
+ *
306
+ * This method is the single seam consumed by the new effect-resolver
307
+ * pipeline (`getStat`, `getRates`). It does **not** apply scopes - that's
308
+ * the resolver's job - so the returned list includes every effect the
309
+ * player could plausibly emit, with their declarative scopes intact.
310
+ */
311
+ collectEffects(env) {
312
+ const effects = [];
313
+ effects.push(...collectGeneralFortuneSourceEffects(this));
314
+ // Armor set: armor pieces, equipment pieces, and set bonuses.
315
+ // (Per-piece reforge/enchant effects are emitted by each piece.)
316
+ effects.push(...this.armorSet.getEffects(env));
317
+ // Tool: for crop-scoped calculations, use the selected/best tool for
318
+ // that crop. For crop-agnostic stat queries, use the selected tool.
319
+ const tool = env.crop ? this.getSelectedCropTool(env.crop) : this.selectedTool;
320
+ if (tool) {
321
+ effects.push(...tool.getEffects(env));
322
+ }
323
+ // Active accessories only - the same filtering `getStatBreakdown`
324
+ // applies, so we don't double-count Helianthus etc.
325
+ for (const accessory of this.activeAccessories) {
326
+ effects.push(...accessory.getEffects(env));
327
+ }
328
+ // Selected pet.
329
+ if (this.selectedPet) {
330
+ effects.push(...this.selectedPet.getEffects(env, this));
331
+ }
332
+ // Attribute shards.
333
+ for (const shard of Object.values(FARMING_ATTRIBUTE_SHARD_CLASSES)) {
334
+ const active = shard.getActive?.(this, env);
335
+ if (active && active.active === false)
336
+ continue;
337
+ effects.push(...shard.getEffects(this, env));
338
+ }
339
+ // Garden chips.
340
+ for (const chip of Object.values(GARDEN_CHIP_CLASSES)) {
341
+ const active = chip.getActive?.(this, env);
342
+ if (active && active.active === false)
343
+ continue;
344
+ effects.push(...chip.getEffects(this, env));
345
+ }
346
+ effects.push(...collectCropFortuneSourceEffects(this, env));
347
+ return effects;
348
+ }
349
+ getStat(stat, targetCrop) {
350
+ const breakdown = this.getStatBreakdown(stat, targetCrop);
232
351
  return Object.values(breakdown).reduce((acc, val) => acc + val.value, 0);
233
352
  }
234
353
  getStatBreakdown(stat, targetCrop) {
@@ -241,110 +360,16 @@ export class FarmingPlayer {
241
360
  breakdown[name].value += value;
242
361
  }
243
362
  };
244
- // Identify all stats that contribute to the requested stat
245
363
  const contributingStats = getContributoryStats(stat);
246
- // General Sources
247
- // Always run general sources (they apply to everything)
248
- for (const source of GENERAL_FORTUNE_SOURCES) {
249
- // If this source is handled by an active accessory, skip it here to avoid double counting
250
- if (this.activeAccessories.some((a) => a.info.name === source.name))
251
- continue;
252
- if (source.exists && !source.exists(this))
253
- continue;
254
- for (const targetStat of contributingStats) {
255
- let val = 0;
256
- if (source.currentStat) {
257
- val = source.currentStat(this, targetStat) ?? 0;
258
- }
259
- else if (source.current && targetStat === Stat.FarmingFortune) {
260
- val = source.current(this) ?? 0;
261
- }
262
- add(source.name, val, targetStat);
263
- }
264
- }
265
- // Crop Sources
266
- // Only run if we have a target crop
267
- if (targetCrop) {
268
- for (const source of CROP_FORTUNE_SOURCES) {
269
- // Helianthus Relic Family is handled by activeAccessories
270
- if (source.name === 'Helianthus Relic Family')
271
- continue;
272
- const ctx = { player: this, crop: targetCrop };
273
- if (source.exists && !source.exists(ctx))
274
- continue;
275
- for (const targetStat of contributingStats) {
276
- let val = 0;
277
- // For Crop Sources, strictly match the crop's fortune type.
278
- if (source.currentStat) {
279
- val = source.currentStat(ctx, targetStat) ?? 0;
280
- }
281
- else if (source.current && targetStat === CROP_INFO[targetCrop].fortuneType) {
282
- val = source.current(ctx) ?? 0;
283
- }
284
- add(source.name, val, targetStat);
285
- }
286
- }
287
- }
288
- // Pets
289
- const pet = this.selectedPet;
290
- if (pet) {
291
- for (const targetStat of contributingStats) {
292
- // Pass the player for abilities that depend on player context (e.g., Mosquito sugar cane fortune)
293
- const val = pet.getFortune(targetStat, this);
294
- add(pet.info.name ?? 'Selected Pet', val, targetStat);
295
- }
296
- }
297
- // Tools
298
- if (!targetCrop && this.selectedTool) {
299
- for (const targetStat of contributingStats) {
300
- const val = this.selectedTool.getStat(targetStat);
301
- add(this.selectedTool.info.name, val, targetStat);
302
- }
303
- }
304
- // Equipment
305
- for (const piece of this.armorSet.equipment) {
306
- if (!piece)
307
- continue;
308
- for (const targetStat of contributingStats) {
309
- const val = piece.getStat(targetStat);
310
- add(piece.info.name, val, targetStat);
311
- }
312
- }
313
- // Armor
314
- for (const piece of this.armorSet.armor) {
315
- if (!piece)
316
- continue;
317
- for (const targetStat of contributingStats) {
318
- const val = piece.getStat(targetStat);
319
- add(piece.info.name, val, targetStat);
320
- }
321
- }
322
- // Armor Set Bonuses
323
- for (const { bonus, count } of this.armorSet.setBonuses) {
324
- if (count < 2 || count > 4)
325
- continue;
326
- for (const targetStat of contributingStats) {
327
- const val = bonus.stats?.[count]?.[targetStat] ?? 0;
328
- add(bonus.name, val, targetStat);
329
- }
330
- }
331
- // Equipment Set Bonuses
332
- for (const { bonus, count } of this.armorSet.equipmentSetBonuses) {
333
- if (count < 2 || count > 4)
334
- continue;
335
- for (const targetStat of contributingStats) {
336
- const val = bonus.stats?.[count]?.[targetStat] ?? 0;
337
- add(bonus.name, val, targetStat);
338
- }
339
- }
340
- // Accessories
341
- for (const acc of this.activeAccessories) {
342
- // If the accessory is restricted to specific crops, check validity
343
- if (acc.info.crops && (!targetCrop || !acc.info.crops.includes(targetCrop)))
344
- continue;
345
- for (const targetStat of contributingStats) {
346
- const val = acc.getStat(targetStat);
347
- add(acc.info.name, val, targetStat);
364
+ const env = this.buildEnvironment(targetCrop);
365
+ const effects = this.collectEffects(env);
366
+ const statContext = { env, crop: targetCrop };
367
+ for (const targetStat of contributingStats) {
368
+ const resolved = targetStat === Stat.Overbloom
369
+ ? resolveOverbloomBreakdown(effects, statContext, Stat.Overbloom)
370
+ : resolveStatBreakdown(effects, targetStat, statContext);
371
+ for (const [name, value] of Object.entries(resolved)) {
372
+ add(name, value, targetStat);
348
373
  }
349
374
  }
350
375
  // Temporary Fortune
@@ -373,9 +398,11 @@ export class FarmingPlayer {
373
398
  const petName = this.selectedPet.info.name ?? 'Selected Pet';
374
399
  // Get the stat type from the late breakdown entries (they specify their stat)
375
400
  const lateBreakdownEntries = lateResult.breakdown ? Object.values(lateResult.breakdown) : [];
376
- const lateStat = lateBreakdownEntries[0]?.stat ?? Stat.FarmingFortune;
401
+ const contributingLateEntry = lateBreakdownEntries.find((entry) => contributingStats.includes(entry.stat));
402
+ const lateStat = contributingLateEntry?.stat ?? lateBreakdownEntries[0]?.stat ?? Stat.FarmingFortune;
403
+ const lateResultContributes = contributingStats.includes(lateStat);
377
404
  // Add late additive effects to the pet's entry
378
- if (lateResult.additive) {
405
+ if (lateResultContributes && lateResult.additive) {
379
406
  if (breakdown[petName]) {
380
407
  breakdown[petName].value += lateResult.additive;
381
408
  }
@@ -384,7 +411,7 @@ export class FarmingPlayer {
384
411
  }
385
412
  }
386
413
  // Apply multiplier to total fortune (add as reduction to pet's entry)
387
- if (lateResult.multiplier !== undefined && lateResult.multiplier !== 1) {
414
+ if (lateResultContributes && lateResult.multiplier !== undefined && lateResult.multiplier !== 1) {
388
415
  const reduction = baseFortune * (lateResult.multiplier - 1);
389
416
  if (breakdown[petName]) {
390
417
  breakdown[petName].value += reduction;
@@ -416,9 +443,12 @@ export class FarmingPlayer {
416
443
  continue;
417
444
  const fortune = source.fortune(this.options.temporaryFortune);
418
445
  if (fortune) {
419
- const boosted = fortune * hyperchargeMultiplier;
420
- breakdown[source.name] = { value: boosted, stat: Stat.FarmingFortune };
421
- sum += boosted;
446
+ const stat = source.stat ?? Stat.FarmingFortune;
447
+ // Hypercharge chip only boosts farming fortune sources, not Overbloom or other stats.
448
+ const boosted = stat === Stat.FarmingFortune ? fortune * hyperchargeMultiplier : fortune;
449
+ breakdown[source.name] = { value: boosted, stat };
450
+ if (stat === Stat.FarmingFortune)
451
+ sum += boosted;
422
452
  }
423
453
  }
424
454
  this.tempFortuneBreakdown = breakdown;
@@ -447,15 +477,34 @@ export class FarmingPlayer {
447
477
  getRates(crop, blocksBroken) {
448
478
  const tool = this.getBestTool(crop);
449
479
  const cropFortune = this.getCropFortune(crop, tool);
450
- const fortune = this.permFortune + this.tempFortune + cropFortune.fortune;
451
- return calculateDetailedDrops({
452
- crop: crop,
453
- blocksBroken: blocksBroken,
480
+ const fortune = cropFortune.fortune;
481
+ const env = this.buildEnvironment(crop);
482
+ const effects = this.collectEffects(env);
483
+ return calculateDetailedDropsFromEffects({
484
+ crop,
485
+ blocksBroken,
454
486
  farmingFortune: fortune,
487
+ armorPieces: this.armorSet.specialDropsCount(crop),
455
488
  bountiful: tool?.bountiful ?? false,
456
489
  mooshroom: this.selectedPet?.type === FarmingPets.MooshroomCow,
490
+ maxTool: tool?.getCurrentLevelProgress().maxed ?? false,
491
+ chips: this.options.chips,
492
+ pet: this.selectedPet,
493
+ effects,
494
+ env,
457
495
  });
458
496
  }
497
+ getUpgradeRateImpact(upgrade, options) {
498
+ const before = this.getRates(options.crop, options.blocksBroken);
499
+ const clonedPlayer = this.clone();
500
+ clonedPlayer.applyUpgrade(upgrade);
501
+ const after = clonedPlayer.getRates(options.crop, options.blocksBroken);
502
+ return {
503
+ before,
504
+ after,
505
+ delta: diffDetailedDrops(before, after),
506
+ };
507
+ }
459
508
  getWeightCalc(info) {
460
509
  return createFarmingWeightCalculator({
461
510
  collection: this.options.collection,
@@ -480,6 +529,35 @@ export class FarmingPlayer {
480
529
  if (!upgrade.meta)
481
530
  return;
482
531
  const { type, itemUuid, key, value, id } = upgrade.meta;
532
+ if (type === 'upgrade_group') {
533
+ for (const groupedUpgrade of upgrade.groupedUpgrades ?? []) {
534
+ this.applyUpgrade(groupedUpgrade);
535
+ }
536
+ this.permFortune = this.getGeneralFortune();
537
+ return;
538
+ }
539
+ if ((type === 'pet_level' || type === 'pet_item') && itemUuid) {
540
+ const index = this.pets.findIndex((pet) => pet.pet.uuid === itemUuid);
541
+ const target = this.pets[index];
542
+ if (target) {
543
+ const nextPetData = { ...target.pet };
544
+ if (type === 'pet_level' && value) {
545
+ nextPetData.exp = target.getXpForLevel(Number(value));
546
+ }
547
+ else if (type === 'pet_item' && id) {
548
+ nextPetData.heldItem = id;
549
+ }
550
+ const updatedPet = new FarmingPet(nextPetData, this.options);
551
+ this.pets[index] = updatedPet;
552
+ this.options.pets = this.pets;
553
+ if (this.selectedPet?.pet.uuid === itemUuid) {
554
+ this.selectedPet = updatedPet;
555
+ this.options.selectedPet = updatedPet;
556
+ }
557
+ this.permFortune = this.getGeneralFortune();
558
+ }
559
+ return;
560
+ }
483
561
  if (itemUuid) {
484
562
  const candidates = [...this.tools, ...this.armor, ...this.equipment, ...this.accessories];
485
563
  const target = candidates.find((i) => i.item.uuid === itemUuid);
@@ -491,7 +569,11 @@ export class FarmingPlayer {
491
569
  if (target instanceof FarmingTool) {
492
570
  const idx = this.tools.indexOf(target);
493
571
  if (idx >= 0) {
494
- this.tools[idx] = new FarmingTool(target.item, this.options);
572
+ const updatedTool = new FarmingTool(target.item, this.options);
573
+ this.tools[idx] = updatedTool;
574
+ if (this.selectedTool === target) {
575
+ this.selectedTool = updatedTool;
576
+ }
495
577
  }
496
578
  }
497
579
  else if (target instanceof FarmingArmor) {
@@ -524,7 +606,11 @@ export class FarmingPlayer {
524
606
  if (target instanceof FarmingTool) {
525
607
  const idx = this.tools.indexOf(target);
526
608
  if (idx >= 0) {
527
- this.tools[idx] = new FarmingTool(target.item, this.options);
609
+ const updatedTool = new FarmingTool(target.item, this.options);
610
+ this.tools[idx] = updatedTool;
611
+ if (this.selectedTool === target) {
612
+ this.selectedTool = updatedTool;
613
+ }
528
614
  }
529
615
  }
530
616
  else if (target instanceof FarmingArmor) {
@@ -602,6 +688,7 @@ export class FarmingPlayer {
602
688
  else if (type === 'item' && id === 'rarity_upgrades' && value) {
603
689
  target.item.attributes ??= {};
604
690
  target.item.attributes.rarity_upgrades = String(value);
691
+ setItemRarityAttribute(target.item, nextRarity(target.rarity));
605
692
  // Recomb affects rarity, which affects stats. Need to reload tool.
606
693
  if (target instanceof FarmingTool) {
607
694
  const idx = this.tools.indexOf(target);
@@ -649,6 +736,7 @@ export class FarmingPlayer {
649
736
  ...newItem.item.gems,
650
737
  ...target.item.gems,
651
738
  };
739
+ setItemRarityAttribute(newItem.item, getUpgradedItemRarity(target.rarity, target.info.maxRarity, newItem.info.maxRarity));
652
740
  // Preserve the old item's UUID so the item remains trackable
653
741
  newItem.item.uuid = target.item.uuid;
654
742
  if (target instanceof FarmingTool && newItem instanceof FarmingTool) {
@@ -674,6 +762,7 @@ export class FarmingPlayer {
674
762
  else if (target instanceof FarmingEquipment && newItem instanceof FarmingEquipment) {
675
763
  const idx = this.equipment.indexOf(target);
676
764
  if (idx >= 0) {
765
+ target.applyTierUpgradeStateTo(newItem);
677
766
  const updatedPiece = new FarmingEquipment(newItem.item, this.options);
678
767
  this.equipment[idx] = updatedPiece;
679
768
  this.armorSet.updateEquipmentSlot(updatedPiece);
@@ -688,6 +777,10 @@ export class FarmingPlayer {
688
777
  this.permFortune = this.getGeneralFortune();
689
778
  }
690
779
  }
780
+ if (target instanceof FarmingAccessory) {
781
+ this.syncActiveAccessories();
782
+ }
783
+ this.permFortune = this.getGeneralFortune();
691
784
  }
692
785
  }
693
786
  else if (type === 'skill') {
@@ -737,12 +830,22 @@ export class FarmingPlayer {
737
830
  if (key === 'cocoaFortuneUpgrade') {
738
831
  this.options.cocoaFortuneUpgrade = Number(value);
739
832
  }
833
+ else if (key === 'dnaMilestone') {
834
+ this.options.dnaMilestone = Number(value);
835
+ }
836
+ else if (key === 'refinedTruffles') {
837
+ this.options.refinedTruffles = Number(value);
838
+ }
740
839
  this.permFortune = this.getGeneralFortune();
741
840
  }
742
841
  else if (type === 'unlock' && id) {
743
842
  if (id === 'personal_best') {
744
843
  this.options.personalBestsUnlocked = true;
745
844
  }
845
+ else if (id === 'exportable_crop' && key) {
846
+ this.options.exportableCrops ??= {};
847
+ this.options.exportableCrops[key] = true;
848
+ }
746
849
  this.permFortune = this.getGeneralFortune();
747
850
  }
748
851
  else if (type === 'buy_item' && id) {
@@ -785,7 +888,9 @@ export class FarmingPlayer {
785
888
  this.armor[oldIdx] = new FarmingArmor(newItem.item, this.options);
786
889
  }
787
890
  else {
788
- this.armor.push(newItem);
891
+ const addedPiece = new FarmingArmor(newItem.item, this.options);
892
+ this.armor.push(addedPiece);
893
+ this.armorSet.updateArmorSlot(addedPiece);
789
894
  }
790
895
  }
791
896
  else if (newItem instanceof FarmingEquipment) {
@@ -801,10 +906,13 @@ export class FarmingPlayer {
801
906
  ...oldItem.item.attributes,
802
907
  };
803
908
  newItem.item.gems = { ...newItem.item.gems, ...oldItem.item.gems };
909
+ oldItem.applyTierUpgradeStateTo(newItem);
804
910
  this.equipment[oldIdx] = new FarmingEquipment(newItem.item, this.options);
805
911
  }
806
912
  else {
807
- this.equipment.push(newItem);
913
+ const addedPiece = new FarmingEquipment(newItem.item, this.options);
914
+ this.equipment.push(addedPiece);
915
+ this.armorSet.updateEquipmentSlot(addedPiece);
808
916
  }
809
917
  }
810
918
  else if (newItem instanceof FarmingAccessory) {
@@ -826,6 +934,9 @@ export class FarmingPlayer {
826
934
  this.accessories.push(newItem);
827
935
  }
828
936
  }
937
+ if (newItem instanceof FarmingAccessory) {
938
+ this.syncActiveAccessories();
939
+ }
829
940
  this.permFortune = this.getGeneralFortune();
830
941
  }
831
942
  }
@@ -843,6 +954,8 @@ export class FarmingPlayer {
843
954
  lore: [...(item.item.lore ?? [])],
844
955
  }));
845
956
  };
957
+ const selectedToolUuid = this.selectedTool?.item.uuid;
958
+ const selectedPetUuid = this.selectedPet?.pet.uuid;
846
959
  const clonedOptions = {
847
960
  ...this.options,
848
961
  tools: cloneItems(this.tools),
@@ -858,8 +971,21 @@ export class FarmingPlayer {
858
971
  bestiaryKills: { ...this.options.bestiaryKills },
859
972
  attributes: { ...this.options.attributes },
860
973
  plots: [...(this.options.plots ?? [])],
974
+ selectedTool: undefined,
975
+ selectedPet: undefined,
861
976
  };
862
- return new FarmingPlayer(clonedOptions);
977
+ const clonedPlayer = new FarmingPlayer(clonedOptions);
978
+ if (selectedToolUuid) {
979
+ const selectedTool = clonedPlayer.tools.find((tool) => tool.item.uuid === selectedToolUuid);
980
+ if (selectedTool)
981
+ clonedPlayer.selectTool(selectedTool);
982
+ }
983
+ if (selectedPetUuid) {
984
+ const selectedPet = clonedPlayer.pets.find((pet) => pet.pet.uuid === selectedPetUuid);
985
+ if (selectedPet)
986
+ clonedPlayer.selectPet(selectedPet);
987
+ }
988
+ return clonedPlayer;
863
989
  }
864
990
  /**
865
991
  * Expands an upgrade into a tree of follow-up upgrades.
@@ -908,6 +1034,25 @@ export class FarmingPlayer {
908
1034
  totalCost: upgrade.cost,
909
1035
  children: [],
910
1036
  };
1037
+ if (upgrade.meta?.type === 'upgrade_group') {
1038
+ const sequentialPlayer = this.clone();
1039
+ for (const groupedUpgrade of upgrade.groupedUpgrades ?? []) {
1040
+ const childStatsBefore = sequentialPlayer.getAllStats(stats, crop);
1041
+ const childPlayer = sequentialPlayer.clone();
1042
+ childPlayer.applyUpgrade(groupedUpgrade);
1043
+ const childStatsAfter = childPlayer.getAllStats(stats, crop);
1044
+ node.children.push({
1045
+ upgrade: groupedUpgrade,
1046
+ statsBefore: childStatsBefore,
1047
+ statsAfter: childStatsAfter,
1048
+ statsGained: this.computeStatsDiff(childStatsBefore, childStatsAfter),
1049
+ totalCost: groupedUpgrade.cost,
1050
+ children: [],
1051
+ });
1052
+ sequentialPlayer.applyUpgrade(groupedUpgrade);
1053
+ }
1054
+ return node;
1055
+ }
911
1056
  // Stop recursion at max depth
912
1057
  if (depth >= maxDepth) {
913
1058
  return node;
@@ -1004,9 +1149,12 @@ export class FarmingPlayer {
1004
1149
  const target = player.tools.find((t) => t.item.uuid === itemUuid) ??
1005
1150
  player.armor.find((a) => a.item.uuid === itemUuid) ??
1006
1151
  player.equipment.find((e) => e.item.uuid === itemUuid) ??
1007
- player.accessories.find((a) => a.item.uuid === itemUuid);
1152
+ player.accessories.find((a) => a.item.uuid === itemUuid) ??
1153
+ player.pets.find((p) => p.pet.uuid === itemUuid);
1008
1154
  if (target && 'getUpgrades' in target && typeof target.getUpgrades === 'function') {
1009
- const itemUpgrades = target.getUpgrades({ stat: primaryStat });
1155
+ const itemUpgrades = target instanceof FarmingPet
1156
+ ? target.getUpgrades({ stat: primaryStat }, player)
1157
+ : target.getUpgrades({ stat: primaryStat });
1010
1158
  // Filter to only include upgrades of the same type (enchant chains, tier upgrades, etc.)
1011
1159
  // For gem upgrades, also match on slot to only show follow-ups for that specific slot
1012
1160
  for (const u of itemUpgrades) {
@@ -1041,4 +1189,46 @@ export class FarmingPlayer {
1041
1189
  return upgrades;
1042
1190
  }
1043
1191
  }
1192
+ function diffDetailedDrops(before, after) {
1193
+ const items = diffRecord(before.items, after.items);
1194
+ const rngItems = diffRecord(before.rngItems ?? {}, after.rngItems ?? {});
1195
+ const currencies = diffRecord(before.currencies, after.currencies);
1196
+ return {
1197
+ collection: after.collection - before.collection,
1198
+ npcCoins: after.npcCoins - before.npcCoins,
1199
+ coinSources: diffRecord(before.coinSources, after.coinSources),
1200
+ otherCollection: diffRecord(before.otherCollection, after.otherCollection),
1201
+ items,
1202
+ currencies,
1203
+ rngItems,
1204
+ totalItems: sumRecord(items) + sumRecord(rngItems),
1205
+ };
1206
+ }
1207
+ function diffRecord(before, after) {
1208
+ const result = {};
1209
+ const keys = new Set([...Object.keys(before), ...Object.keys(after)]);
1210
+ for (const key of keys) {
1211
+ const delta = (after[key] ?? 0) - (before[key] ?? 0);
1212
+ if (delta !== 0) {
1213
+ result[key] = delta;
1214
+ }
1215
+ }
1216
+ return result;
1217
+ }
1218
+ function sumRecord(record) {
1219
+ return Object.values(record).reduce((sum, value) => sum + value, 0);
1220
+ }
1221
+ function getUpgradedItemRarity(currentRarity, currentMaxRarity, nextMaxRarity) {
1222
+ const currentBaseRarity = previousRarity(currentMaxRarity);
1223
+ const nextBaseRarity = previousRarity(nextMaxRarity);
1224
+ const rarityIncrease = compareRarity(currentRarity, currentBaseRarity);
1225
+ if (rarityIncrease > 0) {
1226
+ return nextRarity(nextBaseRarity);
1227
+ }
1228
+ return nextBaseRarity;
1229
+ }
1230
+ function setItemRarityAttribute(item, rarity) {
1231
+ item.attributes ??= {};
1232
+ item.attributes.rarity = rarity;
1233
+ }
1044
1234
  //# sourceMappingURL=player.js.map