@jeremyckahn/farmhand 1.23.2 → 1.23.4

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 (169) hide show
  1. package/README.md +5 -0
  2. package/dist/assets/index-4ynbl-GC.js +392 -0
  3. package/dist/assets/index-4ynbl-GC.js.map +1 -0
  4. package/dist/assets/index-CaYdR-lC.css +1 -0
  5. package/dist/assets/jersey-10-latin-400-normal-DrL73VYW.woff2 +0 -0
  6. package/dist/assets/jersey-10-latin-ext-400-normal-uIKqQSaG.woff2 +0 -0
  7. package/dist/assets/jersey-20-latin-400-normal-bNi78tgO.woff2 +0 -0
  8. package/dist/assets/jersey-20-latin-ext-400-normal-BO3lmWef.woff2 +0 -0
  9. package/dist/assets/jersey-25-latin-400-normal-BuYJnnt7.woff2 +0 -0
  10. package/dist/assets/jersey-25-latin-ext-400-normal-CP5Jl3WN.woff2 +0 -0
  11. package/dist/index.html +2 -2
  12. package/dist/service-worker.js +1 -1
  13. package/dist/service-worker.js.map +1 -1
  14. package/package.json +7 -12
  15. package/src/common/services/randomNumber.test.ts +61 -1
  16. package/src/common/services/randomNumber.ts +36 -1
  17. package/src/common/utils.ts +22 -13
  18. package/src/components/AccountingView/AccountingView.tsx +7 -13
  19. package/src/components/Achievement/Achievement.tsx +2 -15
  20. package/src/components/AchievementsView/AchievementsView.tsx +2 -7
  21. package/src/components/AnimatedNumber/AnimatedNumber.tsx +0 -6
  22. package/src/components/AppBar/AppBar.tsx +21 -17
  23. package/src/components/Cellar/Cellar.tsx +0 -2
  24. package/src/components/Cellar/CellarInventoryTabPanel.tsx +0 -6
  25. package/src/components/Cellar/FermentationTabPanel.tsx +0 -6
  26. package/src/components/Cellar/Keg.tsx +0 -5
  27. package/src/components/Cellar/TabPanel/index.tsx +0 -7
  28. package/src/components/Cellar/WinemakingTabPanel.tsx +0 -6
  29. package/src/components/ContextPane/ContextPane.tsx +0 -6
  30. package/src/components/CowCard/Bloodline/Bloodline.tsx +0 -6
  31. package/src/components/CowCard/CowCard.tsx +7 -39
  32. package/src/components/CowCard/Subheader/Subheader.tsx +21 -44
  33. package/src/components/CowPen/Cow.tsx +5 -11
  34. package/src/components/CowPen/CowPen.test.tsx +4 -4
  35. package/src/components/CowPen/CowPen.tsx +5 -16
  36. package/src/components/CowPenContextMenu/CowPenContextMenu.tsx +2 -18
  37. package/src/components/CowPenContextMenu/TabPanel/index.tsx +0 -7
  38. package/src/components/Farmhand/Farmhand.context.tsx +4 -3
  39. package/src/components/Farmhand/Farmhand.tsx +11 -15
  40. package/src/components/Farmhand/FarmhandReducers.tsx +1 -2
  41. package/src/components/Farmhand/FarmhandService.test.ts +6 -9
  42. package/src/components/Farmhand/hooks/useFarmhandDayAdvancement.ts +2 -6
  43. package/src/components/Farmhand/hooks/useFarmhandNetwork.test.ts +2 -2
  44. package/src/components/Farmhand/hooks/useFarmhandNetwork.ts +15 -31
  45. package/src/components/Farmhand/useFarmhand.test.ts +7 -5
  46. package/src/components/Farmhand/useFarmhand.ts +24 -15
  47. package/src/components/FermentationRecipeList/FermentationRecipe.tsx +0 -5
  48. package/src/components/FermentationRecipeList/FermentationRecipeList.test.tsx +10 -11
  49. package/src/components/FermentationRecipeList/FermentationRecipeList.tsx +2 -3
  50. package/src/components/Field/Field.tsx +15 -50
  51. package/src/components/Forest/Forest.tsx +10 -7
  52. package/src/components/ForestPlot/ForestPlot.tsx +5 -5
  53. package/src/components/ForestQuickSelect/ForestQuickSelect.tsx +0 -12
  54. package/src/components/Home/Home.tsx +2 -7
  55. package/src/components/Home/SnowBackground.tsx +1 -1
  56. package/src/components/IngredientsList/IngredientsList.tsx +0 -7
  57. package/src/components/Inventory/Inventory.test.tsx +4 -4
  58. package/src/components/Inventory/Inventory.tsx +5 -12
  59. package/src/components/Item/Item.tsx +11 -29
  60. package/src/components/ItemList/ItemList.tsx +4 -11
  61. package/src/components/KeybindingsView/KeybindingsView.tsx +0 -2
  62. package/src/components/LogView/LogView.tsx +6 -28
  63. package/src/components/Navigation/DayAndProgressContainer.tsx +0 -8
  64. package/src/components/Navigation/Navigation.tsx +12 -33
  65. package/src/components/NotificationSystem/NotificationSystem.test.tsx +45 -3
  66. package/src/components/NotificationSystem/NotificationSystem.tsx +45 -32
  67. package/src/components/OnlinePeersView/OnlinePeer/OnlinePeer.test.tsx +2 -2
  68. package/src/components/OnlinePeersView/OnlinePeer/OnlinePeer.tsx +0 -11
  69. package/src/components/OnlinePeersView/OnlinePeersView.tsx +0 -10
  70. package/src/components/PixelIcon/createPixelIcon.test.tsx +59 -0
  71. package/src/components/PixelIcon/createPixelIcon.tsx +97 -0
  72. package/src/components/PixelIcon/icons.ts +445 -0
  73. package/src/components/PixelIcon/index.ts +3 -0
  74. package/src/components/Plot/Plot.tsx +3 -3
  75. package/src/components/PriceEventView/PriceEventView.tsx +0 -6
  76. package/src/components/ProgressBar/ProgressBar.tsx +9 -8
  77. package/src/components/QuantityInput/QuantityInput.tsx +6 -11
  78. package/src/components/QuickSelect/QuickSelect.tsx +0 -10
  79. package/src/components/Recipe/Recipe.tsx +0 -9
  80. package/src/components/RecipeList/RecipeList.tsx +0 -6
  81. package/src/components/SearchBar/SearchBar.tsx +4 -10
  82. package/src/components/SettingsView/RandomSeedInput.test.tsx +2 -4
  83. package/src/components/SettingsView/RandomSeedInput.tsx +5 -8
  84. package/src/components/SettingsView/SettingsView.test.tsx +21 -0
  85. package/src/components/SettingsView/SettingsView.tsx +24 -18
  86. package/src/components/Shop/Shop.tsx +0 -20
  87. package/src/components/Shop/TabPanel/index.tsx +0 -7
  88. package/src/components/Stage/Stage.tsx +11 -20
  89. package/src/components/StatsView/StatsView.tsx +0 -18
  90. package/src/components/TierPurchase/TierPurchase.tsx +0 -12
  91. package/src/components/Toolbelt/Toolbelt.tsx +5 -10
  92. package/src/components/UpdateNotifier/UpdateNotifier.tsx +33 -5
  93. package/src/components/UpgradePurchase/UpgradePurchase.tsx +0 -10
  94. package/src/components/WineRecipeList/WineRecipe.test.tsx +2 -2
  95. package/src/components/WineRecipeList/WineRecipe.tsx +0 -5
  96. package/src/components/WineRecipeList/WineRecipeList.tsx +2 -3
  97. package/src/components/Workshop/ForgeTabPanel.tsx +0 -8
  98. package/src/components/Workshop/KitchenTabPanel.tsx +0 -7
  99. package/src/components/Workshop/RecyclingTabPanel.tsx +0 -7
  100. package/src/components/Workshop/TabPanel/index.tsx +0 -7
  101. package/src/components/Workshop/WoodChipperTabPanel.tsx +0 -7
  102. package/src/components/Workshop/Workshop.tsx +0 -9
  103. package/src/components/Workshop/getUpgradesAvailable.ts +1 -1
  104. package/src/constants.ts +18 -13
  105. package/src/data/crops/grape.ts +13 -15
  106. package/src/data/maps.test.ts +3 -4
  107. package/src/data/maps.ts +27 -29
  108. package/src/enums.ts +47 -19
  109. package/src/factories/OreFactory.ts +2 -1
  110. package/src/farmhand.d.ts +6 -3
  111. package/src/game-logic/reducers/applyPrecipitation.ts +4 -4
  112. package/src/game-logic/reducers/generatePriceEvents.ts +6 -3
  113. package/src/game-logic/reducers/harvestForestPlot.ts +1 -2
  114. package/src/game-logic/reducers/makeFermentationRecipe.ts +2 -3
  115. package/src/game-logic/reducers/minePlot.test.ts +2 -2
  116. package/src/game-logic/reducers/minePlot.ts +1 -1
  117. package/src/game-logic/reducers/processCowFertilizerProduction.test.ts +16 -14
  118. package/src/game-logic/reducers/processFeedingCows.test.ts +2 -5
  119. package/src/game-logic/reducers/purchaseCow.ts +2 -7
  120. package/src/game-logic/reducers/showNotification.test.ts +20 -0
  121. package/src/game-logic/reducers/showNotification.ts +4 -2
  122. package/src/game-logic/reducers/updateAchievements.ts +1 -1
  123. package/src/game-logic/reducers/updateFinancialRecords.test.ts +21 -25
  124. package/src/game-logic/reducers/updateFinancialRecords.ts +2 -5
  125. package/src/game-logic/reducers/updateInventoryRecordsForNextDay.test.ts +12 -14
  126. package/src/handlers/ui-events.test.ts +44 -42
  127. package/src/handlers/ui-events.tsx +18 -3
  128. package/src/hooks/usePrefersReducedMotion.ts +10 -6
  129. package/src/index.tsx +0 -2
  130. package/src/mui-theme.ts +258 -37
  131. package/src/scripts/generate-crop-table.ts +3 -2
  132. package/src/setupTests.ts +18 -12
  133. package/src/strings.ts +1 -1
  134. package/src/styles/fonts.ts +73 -0
  135. package/src/styles/pixel.ts +151 -0
  136. package/src/styles/sx.ts +46 -33
  137. package/src/styles/tokens.ts +11 -0
  138. package/src/test-utils/index.ts +1 -0
  139. package/src/utils/chooseRandom.ts +4 -1
  140. package/src/utils/chooseRandomIndex.ts +6 -2
  141. package/src/utils/getCowImage.ts +22 -27
  142. package/src/utils/getMaxYieldOfFermentationRecipe.ts +2 -2
  143. package/src/utils/getPeerMetadata.ts +1 -1
  144. package/src/utils/getPlotImage.test.ts +14 -14
  145. package/src/utils/getPlotImage.ts +10 -10
  146. package/src/utils/getRandomUnlockedCrop.ts +5 -3
  147. package/src/utils/getRandomizedLifespan.test.ts +2 -8
  148. package/src/utils/getValueAdjustmentStream.test.ts +9 -0
  149. package/src/utils/getValueAdjustmentStream.ts +5 -0
  150. package/src/utils/isItemAFarmProduct.ts +2 -2
  151. package/src/utils/shouldPrecipitateToday.ts +3 -1
  152. package/src/utils/shouldStormToday.ts +2 -1
  153. package/src/utils/transformStateDataForImport.ts +3 -5
  154. package/dist/assets/francois-one-latin-400-normal-DD1rFI7H.woff2 +0 -0
  155. package/dist/assets/francois-one-latin-400-normal-DNXs7uOx.woff +0 -0
  156. package/dist/assets/francois-one-latin-ext-400-normal-CwRdWRZD.woff2 +0 -0
  157. package/dist/assets/francois-one-latin-ext-400-normal-JvkpT0Av.woff +0 -0
  158. package/dist/assets/francois-one-vietnamese-400-normal-BLIKeG5Y.woff2 +0 -0
  159. package/dist/assets/francois-one-vietnamese-400-normal-CuWeHlXs.woff +0 -0
  160. package/dist/assets/index-CNcrTYN-.css +0 -1
  161. package/dist/assets/index-DNPchtK9.js +0 -844
  162. package/dist/assets/index-DNPchtK9.js.map +0 -1
  163. package/dist/assets/public-sans-latin-400-normal-8Rpg0ruU.woff2 +0 -0
  164. package/dist/assets/public-sans-latin-400-normal-SBbinRkI.woff +0 -0
  165. package/dist/assets/public-sans-latin-ext-400-normal-B02wwOdZ.woff +0 -0
  166. package/dist/assets/public-sans-latin-ext-400-normal-mk90oQqJ.woff2 +0 -0
  167. package/dist/assets/public-sans-vietnamese-400-normal-BO1meqcx.woff +0 -0
  168. package/dist/assets/public-sans-vietnamese-400-normal-TxZB8OSn.woff2 +0 -0
  169. package/src/fontsource.d.ts +0 -3
package/src/styles/sx.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { lighten, Theme } from '@mui/material/styles/index.js'
2
2
 
3
+ import { pixelBevel, pixelDropShadowFilter, pixelFrameSx, px } from './pixel.js'
3
4
  import { breakpoints, colors, layout } from './tokens.js'
4
5
 
5
6
  // NOTE: These are plain object literals (not typed as SxProps<Theme>) so
@@ -26,17 +27,23 @@ export const squareImgSx = {
26
27
 
27
28
  // Replaces the `card-style` mixin. Merge cardStyleSelectedSx on top of this
28
29
  // when the card's `is-selected` state is active.
30
+ //
31
+ // Cards are drawn as pixel art panels with a 9-slice frame (see pixel.ts).
32
+ // Use `backgroundColor` rather than the `background` shorthand when
33
+ // overriding the color of anything built on this: the shorthand resets the
34
+ // `backgroundClip` that keeps the frame's corners and shadow transparent.
29
35
  export const cardStyleSx = (theme: Theme) => ({
30
- background: colors.cardBackground,
31
- border: 'solid 2px #b68000',
32
- transition: theme.transitions.create('background', {
36
+ ...pixelFrameSx({ outline: colors.cardOutline, shadow: true }),
37
+ backgroundColor: colors.cardBackground,
38
+ boxShadow: pixelBevel(),
39
+ transition: theme.transitions.create('background-color', {
33
40
  duration: theme.transitions.duration.enteringScreen,
34
41
  easing: theme.transitions.easing.easeOut,
35
42
  }),
36
43
  })
37
44
 
38
45
  export const cardStyleSelectedSx = {
39
- background: lighten(colors.cardBackground, 0.4),
46
+ backgroundColor: lighten(colors.cardBackground, 0.4),
40
47
  } as const
41
48
 
42
49
  // Replaces the `center-tabs` mixin.
@@ -50,8 +57,11 @@ export const centerTabsSx = {
50
57
  marginTop: '4em',
51
58
  },
52
59
  '& [role="tabpanel"]': {
53
- background: 'rgba(255, 255, 255, 0.5)',
54
- borderRadius: '0 0 0.5em 0.5em',
60
+ ...pixelFrameSx({ outline: colors.cardOutline }),
61
+ // The tabs above this panel already draw the shared top edge.
62
+ borderTopWidth: 0,
63
+ borderImageWidth: `0 ${px(1)} ${px(1)}`,
64
+ backgroundColor: 'rgba(255, 255, 255, 0.5)',
55
65
  padding: '0.5em 0.5em 0.5em',
56
66
  '& .card-list > li:last-child': {
57
67
  marginBottom: 0,
@@ -61,7 +71,7 @@ export const centerTabsSx = {
61
71
 
62
72
  // Replaces the `sprite-shadow` mixin.
63
73
  export const spriteShadowSx = {
64
- filter: 'drop-shadow(2px 2px 2px rgba(100, 100, 100, 0.4))',
74
+ filter: pixelDropShadowFilter,
65
75
  } as const
66
76
 
67
77
  // Shared by QuickSelect and ForestQuickSelect: both render the same fixed
@@ -85,34 +95,36 @@ export const quickSelectSx = (theme: Theme, isMenuOpen: boolean) => ({
85
95
  display: isMenuOpen ? 'none' : undefined,
86
96
  },
87
97
  },
88
- [`@media (orientation: landscape) and (min-height: ${breakpoints.largePhone}px)`]: {
89
- left: 'auto',
90
- maxHeight: 'calc(100vh - 20em)',
91
- minWidth: '4em',
92
- overflow: 'auto',
93
- right: '0.75em',
94
- top: '9em',
95
- transform: 'none',
96
- bottom: '8em',
97
- },
98
+ [`@media (orientation: landscape) and (min-height: ${breakpoints.largePhone}px)`]:
99
+ {
100
+ left: 'auto',
101
+ maxHeight: 'calc(100vh - 20em)',
102
+ minWidth: '4em',
103
+ overflow: 'auto',
104
+ right: '0.75em',
105
+ top: '9em',
106
+ transform: 'none',
107
+ bottom: '8em',
108
+ },
98
109
  '@media (orientation: portrait)': {
99
110
  display: isMenuOpen ? 'none' : undefined,
100
111
  },
101
112
  '& .MuiGrid-root': {
102
- borderRadius: '0.25em',
103
113
  overflowX: 'scroll',
104
114
  overflowY: 'hidden',
105
115
  padding: '0.5em',
106
116
  position: 'relative',
107
- [`@media (orientation: landscape) and (min-height: ${breakpoints.largePhone}px)`]: {
108
- overflowX: 'hidden',
109
- flexDirection: 'column',
110
- },
111
- '& > *': {
112
- [`@media (orientation: landscape) and (min-height: ${breakpoints.largePhone}px)`]: {
113
- alignItems: 'center',
114
- width: '3em',
117
+ [`@media (orientation: landscape) and (min-height: ${breakpoints.largePhone}px)`]:
118
+ {
119
+ overflowX: 'hidden',
120
+ flexDirection: 'column',
115
121
  },
122
+ '& > *': {
123
+ [`@media (orientation: landscape) and (min-height: ${breakpoints.largePhone}px)`]:
124
+ {
125
+ alignItems: 'center',
126
+ width: '3em',
127
+ },
116
128
  },
117
129
  },
118
130
  '& .Toolbelt': {
@@ -122,12 +134,13 @@ export const quickSelectSx = (theme: Theme, isMenuOpen: boolean) => ({
122
134
  },
123
135
  '& .MuiDivider-root': {
124
136
  margin: '0 0.5em',
125
- [`@media (orientation: landscape) and (min-height: ${breakpoints.largePhone}px)`]: {
126
- margin: '0.5em 0',
127
- height: '1px',
128
- width: '100%',
129
- borderRightWidth: 0,
130
- borderBottomWidth: 'thin',
131
- },
137
+ [`@media (orientation: landscape) and (min-height: ${breakpoints.largePhone}px)`]:
138
+ {
139
+ margin: '0.5em 0',
140
+ height: '1px',
141
+ width: '100%',
142
+ borderRightWidth: 0,
143
+ borderBottomWidth: 'thin',
144
+ },
132
145
  },
133
146
  })
@@ -11,6 +11,10 @@ export const colors = {
11
11
  error: '#ce0000',
12
12
  success: '#2e7d32',
13
13
  cardBackground: '#ffe3a1',
14
+ cardOutline: '#8a5c00',
15
+ dialogOutline: '#1f4f78',
16
+ neutralOutline: '#2b2118',
17
+ disabledOutline: 'rgba(0, 0, 0, 0.26)',
14
18
  // Matches the legacy Sass `color.adjust($card-background, $lightness: -10%)`
15
19
  // for cards nested inside other cards. MUI's `darken()` helper uses a
16
20
  // different (multiplicative) algorithm and produces a visibly duller color.
@@ -40,6 +44,13 @@ export const breakpoints = {
40
44
  xl: 1920,
41
45
  } as const
42
46
 
47
+ // Pixel fonts. `display` is used for headings, buttons and other UI labels;
48
+ // `body` is used for running text.
49
+ export const fonts = {
50
+ display: '"Farmhand Display", sans-serif',
51
+ body: '"Farmhand Body", sans-serif',
52
+ } as const
53
+
43
54
  export const layout = {
44
55
  cardMaxWidth: 550,
45
56
  sidebarWidth: '22em',
@@ -193,6 +193,7 @@ export const testState = (
193
193
  latestNotification: null,
194
194
  newDayNotifications: [],
195
195
  notificationLog: [],
196
+ notificationDuration: 6000,
196
197
  peers: {},
197
198
  peerRoom: null,
198
199
  pendingPeerMessages: [],
@@ -1,3 +1,6 @@
1
+ import { randomStream } from '../enums.js'
2
+
1
3
  import { chooseRandomIndex } from './chooseRandomIndex.js'
2
4
 
3
- export const chooseRandom = <T>(list: T[]): T => list[chooseRandomIndex(list)]
5
+ export const chooseRandom = <T>(list: T[], stream?: randomStream): T =>
6
+ list[chooseRandomIndex(list, stream)]
@@ -1,5 +1,9 @@
1
1
  import { random } from '../common/utils.js'
2
+ import { randomStream } from '../enums.js'
2
3
 
3
- export const chooseRandomIndex = <T>(list: T[]): number =>
4
+ export const chooseRandomIndex = <T>(
5
+ list: T[],
6
+ stream?: randomStream
7
+ ): number =>
4
8
  // TODO: Fix statistical bias by using Math.floor(random() * list.length).
5
- Math.round(random() * (list.length - 1))
9
+ Math.round(random(stream) * (list.length - 1))
@@ -17,17 +17,6 @@ const Jimp = configureJimp({
17
17
  })
18
18
 
19
19
  const colorizeCowTemplate = (() => {
20
- // TODO: Remove unused cowImageWidth, cowImageHeight, and
21
- // cowImageFactoryCanvas variables.
22
- // Instantiating a canvas directly in the module scope also poses runtime
23
- // risks in non-browser/SSR environments.
24
- const cowImageWidth = 48
25
- const cowImageHeight = 48
26
- const cowImageFactoryCanvas = document.createElement('canvas')
27
-
28
- cowImageFactoryCanvas.setAttribute('height', String(cowImageHeight))
29
- cowImageFactoryCanvas.setAttribute('width', String(cowImageWidth))
30
-
31
20
  const cachedCowImages: Record<string, string> = {}
32
21
 
33
22
  // https://stackoverflow.com/a/5624139
@@ -65,23 +54,29 @@ const colorizeCowTemplate = (() => {
65
54
  )
66
55
  const image = await Jimp.read(cowTemplateBuffer)
67
56
 
68
- image.scan(0, 0, image.bitmap.width, image.bitmap.height, function(x, y) {
69
- const { r, g, b } = Jimp.intToRGBA(image.getPixelColor(x, y))
70
-
71
- // rgb(102, 102, 102) represents the color to replace in the template
72
- // source images (#666).
73
- if (r === 102 && g === 102 && b === 102) {
74
- const cowColorRgb = hexToRgb(COW_COLORS_HEX_MAP[color])
75
- const colorNumber = Jimp.rgbaToInt(
76
- cowColorRgb.r,
77
- cowColorRgb.g,
78
- cowColorRgb.b,
79
- 255
80
- )
81
-
82
- image.setPixelColor(colorNumber, x, y)
57
+ image.scan(
58
+ 0,
59
+ 0,
60
+ image.bitmap.width,
61
+ image.bitmap.height,
62
+ function (x, y) {
63
+ const { r, g, b } = Jimp.intToRGBA(image.getPixelColor(x, y))
64
+
65
+ // rgb(102, 102, 102) represents the color to replace in the template
66
+ // source images (#666).
67
+ if (r === 102 && g === 102 && b === 102) {
68
+ const cowColorRgb = hexToRgb(COW_COLORS_HEX_MAP[color])
69
+ const colorNumber = Jimp.rgbaToInt(
70
+ cowColorRgb.r,
71
+ cowColorRgb.g,
72
+ cowColorRgb.b,
73
+ 255
74
+ )
75
+
76
+ image.setPixelColor(colorNumber, x, y)
77
+ }
83
78
  }
84
- })
79
+ )
85
80
 
86
81
  cachedCowImages[imageKey] = await image.getBase64Async(Jimp.MIME_PNG)
87
82
 
@@ -12,8 +12,8 @@ export const getMaxYieldOfFermentationRecipe = (
12
12
  ): number => {
13
13
  const {
14
14
  [fermentationRecipe.id]: itemQuantityInInventory = 0,
15
- [(itemsMap as Record<string, farmhand.item>).salt
16
- .id]: saltQuantityInInventory = 0,
15
+ [(itemsMap as Record<string, farmhand.item>).salt.id]:
16
+ saltQuantityInInventory = 0,
17
17
  } = getInventoryQuantityMap(inventory)
18
18
 
19
19
  const maxSaltYieldPotential = Math.floor(
@@ -18,5 +18,5 @@ export const getPeerMetadata = (
18
18
  ),
19
19
  })
20
20
 
21
- return (reducedState as unknown) as farmhand.peerMetadata
21
+ return reducedState as unknown as farmhand.peerMetadata
22
22
  }
@@ -8,7 +8,7 @@ import { getPlotContentFromItemId } from './getPlotContentFromItemId.js'
8
8
 
9
9
  describe('getPlotImage', () => {
10
10
  test('returns null when no plotContent is provided', () => {
11
- expect(getPlotImage((null as unknown) as farmhand.plotContent, 0, 0)).toBe(
11
+ expect(getPlotImage(null as unknown as farmhand.plotContent, 0, 0)).toBe(
12
12
  null
13
13
  )
14
14
  })
@@ -18,40 +18,40 @@ describe('getPlotImage', () => {
18
18
 
19
19
  expect(
20
20
  getPlotImage(
21
- (testCrop({
21
+ testCrop({
22
22
  itemId,
23
23
  daysWatered: 0,
24
- }) as unknown) as farmhand.plotContent,
24
+ }) as unknown as farmhand.plotContent,
25
25
  0,
26
26
  0
27
27
  )
28
28
  ).toBe(itemImages['carrot-seed'])
29
29
  expect(
30
30
  getPlotImage(
31
- (testCrop({
31
+ testCrop({
32
32
  itemId,
33
33
  daysWatered: 1,
34
- }) as unknown) as farmhand.plotContent,
34
+ }) as unknown as farmhand.plotContent,
35
35
  0,
36
36
  0
37
37
  )
38
38
  ).toBe(itemImages['carrot-seed'])
39
39
  expect(
40
40
  getPlotImage(
41
- (testCrop({
41
+ testCrop({
42
42
  itemId,
43
43
  daysWatered: 3,
44
- }) as unknown) as farmhand.plotContent,
44
+ }) as unknown as farmhand.plotContent,
45
45
  0,
46
46
  0
47
47
  )
48
48
  ).toBe(itemImages['carrot-growing-2'])
49
49
  expect(
50
50
  getPlotImage(
51
- (testCrop({
51
+ testCrop({
52
52
  itemId,
53
53
  daysWatered: 5,
54
- }) as unknown) as farmhand.plotContent,
54
+ }) as unknown as farmhand.plotContent,
55
55
  0,
56
56
  0
57
57
  )
@@ -61,9 +61,9 @@ describe('getPlotImage', () => {
61
61
  test('returns item image for oreId', () => {
62
62
  expect(
63
63
  getPlotImage(
64
- (getPlotContentFromItemId(
64
+ getPlotContentFromItemId(
65
65
  silverOre.id
66
- ) as unknown) as farmhand.plotContent,
66
+ ) as unknown as farmhand.plotContent,
67
67
  0,
68
68
  0
69
69
  )
@@ -83,16 +83,16 @@ describe('getPlotImage', () => {
83
83
  }
84
84
 
85
85
  expect(
86
- getPlotImage((shoveledPlot as unknown) as farmhand.shoveledPlot, 0, 0)
86
+ getPlotImage(shoveledPlot as unknown as farmhand.shoveledPlot, 0, 0)
87
87
  ).toBe((itemImages as Record<string, string>)[silverOre.id])
88
88
  })
89
89
 
90
90
  test('returns item image for other content', () => {
91
91
  expect(
92
92
  getPlotImage(
93
- (getPlotContentFromItemId(
93
+ getPlotContentFromItemId(
94
94
  'sprinkler'
95
- ) as unknown) as farmhand.plotContent,
95
+ ) as unknown as farmhand.plotContent,
96
96
  0,
97
97
  0
98
98
  )
@@ -11,21 +11,21 @@ const { SEED, GROWING, GROWN } = cropLifeStage
11
11
  const isPlotContent = (obj: unknown = {}): obj is farmhand.plotContent =>
12
12
  Boolean(
13
13
  obj &&
14
- typeof obj === 'object' &&
15
- 'itemId' in obj &&
16
- 'fertilizerType' in obj &&
17
- obj.itemId &&
18
- obj.fertilizerType
14
+ typeof obj === 'object' &&
15
+ 'itemId' in obj &&
16
+ 'fertilizerType' in obj &&
17
+ obj.itemId &&
18
+ obj.fertilizerType
19
19
  )
20
20
 
21
21
  const isShoveledPlot = (obj: unknown = {}): obj is farmhand.shoveledPlot =>
22
22
  Boolean(
23
23
  obj &&
24
- typeof obj === 'object' &&
25
- 'isShoveled' in obj &&
26
- 'daysUntilClear' in obj &&
27
- obj.isShoveled &&
28
- obj.daysUntilClear
24
+ typeof obj === 'object' &&
25
+ 'isShoveled' in obj &&
26
+ 'daysUntilClear' in obj &&
27
+ obj.isShoveled &&
28
+ obj.daysUntilClear
29
29
  )
30
30
 
31
31
  const isPlotContentACrop = (
@@ -1,16 +1,18 @@
1
1
  import { itemsMap } from '../data/maps.js'
2
+ import { randomStream } from '../enums.js'
2
3
 
3
4
  import { chooseRandom } from './chooseRandom.js'
4
5
  import { chooseRandomIndex } from './chooseRandomIndex.js'
5
6
  import { getFinalCropItemIdFromSeedItemId } from './getFinalCropItemIdFromSeedItemId.js'
6
7
 
7
8
  export const getRandomUnlockedCrop = (
8
- unlockedSeedItemIds: Array<string>
9
+ unlockedSeedItemIds: Array<string>,
10
+ stream?: randomStream
9
11
  ): farmhand.item => {
10
- const seedItemId = chooseRandom(unlockedSeedItemIds)
12
+ const seedItemId = chooseRandom(unlockedSeedItemIds, stream)
11
13
  const seedItem = itemsMap[seedItemId]
12
14
  const variationIdx = Array.isArray(seedItem.growsInto)
13
- ? chooseRandomIndex(seedItem.growsInto)
15
+ ? chooseRandomIndex(seedItem.growsInto, stream)
14
16
  : 0
15
17
 
16
18
  const finalCropItemId = getFinalCropItemIdFromSeedItemId(
@@ -5,19 +5,13 @@ describe('getRandomizedLifespan', () => {
5
5
  // First call: floor roll (max variance -> 200 * (1 - 1 * 0.05) = 190).
6
6
  // All subsequent calls: extension roll of 0, which is immediately
7
7
  // beaten by getTreeDeathChance(0) (0.05), so the extension is 0.
8
- vitest
9
- .spyOn(Math, 'random')
10
- .mockReturnValueOnce(1)
11
- .mockReturnValue(0)
8
+ vitest.spyOn(Math, 'random').mockReturnValueOnce(1).mockReturnValue(0)
12
9
 
13
10
  expect(getRandomizedLifespan(200)).toBe(190)
14
11
  })
15
12
 
16
13
  test('floors at exactly the default when the first roll is 0', () => {
17
- vitest
18
- .spyOn(Math, 'random')
19
- .mockReturnValueOnce(0)
20
- .mockReturnValue(0)
14
+ vitest.spyOn(Math, 'random').mockReturnValueOnce(0).mockReturnValue(0)
21
15
 
22
16
  expect(getRandomizedLifespan(200)).toBe(200)
23
17
  })
@@ -0,0 +1,9 @@
1
+ import { getValueAdjustmentStream } from './getValueAdjustmentStream.js'
2
+
3
+ describe('getValueAdjustmentStream', () => {
4
+ test('returns the price adjustment stream for an item', () => {
5
+ expect(getValueAdjustmentStream('carrot-seed')).toEqual(
6
+ 'valueAdjustment:carrot-seed'
7
+ )
8
+ })
9
+ })
@@ -0,0 +1,5 @@
1
+ import { valueAdjustmentStream } from '../enums.js'
2
+
3
+ export const getValueAdjustmentStream = (
4
+ itemId: string
5
+ ): valueAdjustmentStream => `valueAdjustment:${itemId}`
@@ -14,6 +14,6 @@ const FARM_PRODUCT_TYPES: itemType[] = [
14
14
  export const isItemAFarmProduct = (item: farmhand.item): boolean =>
15
15
  Boolean(
16
16
  isItemAGrownCrop(item) ||
17
- isItemAGrownFruit(item) ||
18
- FARM_PRODUCT_TYPES.includes(item.type)
17
+ isItemAGrownFruit(item) ||
18
+ FARM_PRODUCT_TYPES.includes(item.type)
19
19
  )
@@ -1,4 +1,6 @@
1
1
  import { random } from '../common/utils.js'
2
2
  import { PRECIPITATION_CHANCE } from '../constants.js'
3
+ import { randomStream } from '../enums.js'
3
4
 
4
- export const shouldPrecipitateToday = () => random() < PRECIPITATION_CHANCE
5
+ export const shouldPrecipitateToday = () =>
6
+ random(randomStream.PRECIPITATION) < PRECIPITATION_CHANCE
@@ -1,4 +1,5 @@
1
1
  import { random } from '../common/utils.js'
2
2
  import { STORM_CHANCE } from '../constants.js'
3
+ import { randomStream } from '../enums.js'
3
4
 
4
- export const shouldStormToday = () => random() < STORM_CHANCE
5
+ export const shouldStormToday = () => random(randomStream.STORM) < STORM_CHANCE
@@ -37,10 +37,8 @@ export const transformStateDataForImport = (
37
37
  // TODO: Add defensive check safeguards for sanitizedState.cowBreedingPen
38
38
  // and sanitizedState.cowInventory to prevent TypeError crashes during
39
39
  // corrupt/legacy state imports.
40
- const {
41
- cowId1,
42
- cowId2,
43
- } = sanitizedState.cowBreedingPen as farmhand.state['cowBreedingPen']
40
+ const { cowId1, cowId2 } =
41
+ sanitizedState.cowBreedingPen as farmhand.state['cowBreedingPen']
44
42
 
45
43
  const cowPenIdMap = (sanitizedState.cowInventory as farmhand.cow[]).reduce(
46
44
  (acc: Record<string, farmhand.cow>, cow: farmhand.cow) => {
@@ -108,5 +106,5 @@ export const transformStateDataForImport = (
108
106
  }
109
107
  }
110
108
 
111
- return (sanitizedState as unknown) as farmhand.state
109
+ return sanitizedState as unknown as farmhand.state
112
110
  }
@@ -1 +0,0 @@
1
- :root{--animate-duration:1s;--animate-delay:1s;--animate-repeat:1}.animated{animation-duration:var(--animate-duration);animation-fill-mode:both}.animated.infinite{animation-iteration-count:infinite}.animated.repeat-1{animation-iteration-count:var(--animate-repeat)}.animated.repeat-2{animation-iteration-count:calc(var(--animate-repeat) * 2)}.animated.repeat-3{animation-iteration-count:calc(var(--animate-repeat) * 3)}.animated.delay-1s{animation-delay:var(--animate-delay)}.animated.delay-2s{animation-delay:calc(var(--animate-delay) * 2)}.animated.delay-3s{animation-delay:calc(var(--animate-delay) * 3)}.animated.delay-4s{animation-delay:calc(var(--animate-delay) * 4)}.animated.delay-5s{animation-delay:calc(var(--animate-delay) * 5)}.animated.faster{animation-duration:calc(var(--animate-duration) / 2)}.animated.fast{animation-duration:calc(var(--animate-duration) * .8)}.animated.slow{animation-duration:calc(var(--animate-duration) * 2)}.animated.slower{animation-duration:calc(var(--animate-duration) * 3)}@media print,(prefers-reduced-motion:reduce){.animated{transition-duration:1ms!important;animation-duration:1ms!important;animation-iteration-count:1!important}.animated[class*=Out]{opacity:0}}@keyframes heartBeat{0%{transform:scale(1)}14%{transform:scale(1.3)}28%{transform:scale(1)}42%{transform:scale(1.3)}70%{transform:scale(1)}}.heartBeat{animation-name:heartBeat;animation-duration:calc(var(--animate-duration) * 1.3);animation-timing-function:ease-in-out}@font-face{font-family:Francois One;font-style:normal;font-display:swap;font-weight:400;src:url(./francois-one-vietnamese-400-normal-BLIKeG5Y.woff2)format("woff2"),url(./francois-one-vietnamese-400-normal-CuWeHlXs.woff)format("woff");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Francois One;font-style:normal;font-display:swap;font-weight:400;src:url(./francois-one-latin-ext-400-normal-CwRdWRZD.woff2)format("woff2"),url(./francois-one-latin-ext-400-normal-JvkpT0Av.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Francois One;font-style:normal;font-display:swap;font-weight:400;src:url(./francois-one-latin-400-normal-DD1rFI7H.woff2)format("woff2"),url(./francois-one-latin-400-normal-DNXs7uOx.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Public Sans;font-style:normal;font-display:swap;font-weight:400;src:url(./public-sans-vietnamese-400-normal-TxZB8OSn.woff2)format("woff2"),url(./public-sans-vietnamese-400-normal-BO1meqcx.woff)format("woff");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Public Sans;font-style:normal;font-display:swap;font-weight:400;src:url(./public-sans-latin-ext-400-normal-mk90oQqJ.woff2)format("woff2"),url(./public-sans-latin-ext-400-normal-B02wwOdZ.woff)format("woff");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Public Sans;font-style:normal;font-display:swap;font-weight:400;src:url(./public-sans-latin-400-normal-8Rpg0ruU.woff2)format("woff2"),url(./public-sans-latin-400-normal-SBbinRkI.woff)format("woff");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}