@jeremyckahn/farmhand 1.23.2 → 1.23.3

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 (131) hide show
  1. package/README.md +1 -0
  2. package/dist/assets/{index-DNPchtK9.js → index-Dihg7UqQ.js} +15 -15
  3. package/dist/assets/{index-DNPchtK9.js.map → index-Dihg7UqQ.js.map} +1 -1
  4. package/dist/index.html +1 -1
  5. package/dist/service-worker.js +1 -1
  6. package/dist/service-worker.js.map +1 -1
  7. package/package.json +4 -5
  8. package/src/common/services/randomNumber.test.ts +61 -1
  9. package/src/common/services/randomNumber.ts +36 -1
  10. package/src/common/utils.ts +22 -13
  11. package/src/components/AccountingView/AccountingView.tsx +4 -12
  12. package/src/components/Achievement/Achievement.tsx +0 -13
  13. package/src/components/AchievementsView/AchievementsView.tsx +2 -7
  14. package/src/components/AnimatedNumber/AnimatedNumber.tsx +0 -6
  15. package/src/components/AppBar/AppBar.tsx +0 -10
  16. package/src/components/Cellar/Cellar.tsx +0 -2
  17. package/src/components/Cellar/CellarInventoryTabPanel.tsx +0 -6
  18. package/src/components/Cellar/FermentationTabPanel.tsx +0 -6
  19. package/src/components/Cellar/Keg.tsx +0 -5
  20. package/src/components/Cellar/TabPanel/index.tsx +0 -7
  21. package/src/components/Cellar/WinemakingTabPanel.tsx +0 -6
  22. package/src/components/ContextPane/ContextPane.tsx +0 -6
  23. package/src/components/CowCard/Bloodline/Bloodline.tsx +0 -5
  24. package/src/components/CowCard/CowCard.tsx +0 -26
  25. package/src/components/CowCard/Subheader/Subheader.tsx +11 -26
  26. package/src/components/CowPen/CowPen.tsx +4 -15
  27. package/src/components/CowPenContextMenu/CowPenContextMenu.tsx +0 -16
  28. package/src/components/CowPenContextMenu/TabPanel/index.tsx +0 -7
  29. package/src/components/Farmhand/Farmhand.context.tsx +4 -3
  30. package/src/components/Farmhand/Farmhand.tsx +4 -11
  31. package/src/components/Farmhand/FarmhandReducers.tsx +1 -2
  32. package/src/components/Farmhand/FarmhandService.test.ts +6 -9
  33. package/src/components/Farmhand/hooks/useFarmhandDayAdvancement.ts +2 -6
  34. package/src/components/Farmhand/hooks/useFarmhandNetwork.test.ts +2 -2
  35. package/src/components/Farmhand/hooks/useFarmhandNetwork.ts +15 -31
  36. package/src/components/Farmhand/useFarmhand.test.ts +7 -5
  37. package/src/components/Farmhand/useFarmhand.ts +12 -13
  38. package/src/components/FermentationRecipeList/FermentationRecipe.tsx +0 -5
  39. package/src/components/FermentationRecipeList/FermentationRecipeList.test.tsx +10 -11
  40. package/src/components/FermentationRecipeList/FermentationRecipeList.tsx +2 -3
  41. package/src/components/Field/Field.tsx +13 -48
  42. package/src/components/Forest/Forest.tsx +10 -7
  43. package/src/components/ForestPlot/ForestPlot.tsx +5 -5
  44. package/src/components/ForestQuickSelect/ForestQuickSelect.tsx +0 -12
  45. package/src/components/Home/Home.tsx +0 -6
  46. package/src/components/Home/SnowBackground.tsx +1 -1
  47. package/src/components/IngredientsList/IngredientsList.tsx +0 -7
  48. package/src/components/Inventory/Inventory.test.tsx +4 -4
  49. package/src/components/Inventory/Inventory.tsx +3 -11
  50. package/src/components/Item/Item.tsx +0 -21
  51. package/src/components/ItemList/ItemList.tsx +4 -11
  52. package/src/components/KeybindingsView/KeybindingsView.tsx +0 -2
  53. package/src/components/LogView/LogView.tsx +0 -6
  54. package/src/components/Navigation/DayAndProgressContainer.tsx +0 -7
  55. package/src/components/Navigation/Navigation.tsx +0 -23
  56. package/src/components/NotificationSystem/NotificationSystem.test.tsx +4 -1
  57. package/src/components/NotificationSystem/NotificationSystem.tsx +5 -12
  58. package/src/components/OnlinePeersView/OnlinePeer/OnlinePeer.test.tsx +2 -2
  59. package/src/components/OnlinePeersView/OnlinePeer/OnlinePeer.tsx +0 -11
  60. package/src/components/OnlinePeersView/OnlinePeersView.tsx +0 -10
  61. package/src/components/Plot/Plot.tsx +3 -3
  62. package/src/components/PriceEventView/PriceEventView.tsx +0 -6
  63. package/src/components/ProgressBar/ProgressBar.tsx +0 -5
  64. package/src/components/QuantityInput/QuantityInput.tsx +0 -9
  65. package/src/components/QuickSelect/QuickSelect.tsx +0 -10
  66. package/src/components/Recipe/Recipe.tsx +0 -9
  67. package/src/components/RecipeList/RecipeList.tsx +0 -6
  68. package/src/components/SearchBar/SearchBar.tsx +0 -6
  69. package/src/components/SettingsView/RandomSeedInput.test.tsx +2 -4
  70. package/src/components/SettingsView/RandomSeedInput.tsx +5 -8
  71. package/src/components/SettingsView/SettingsView.test.tsx +21 -0
  72. package/src/components/SettingsView/SettingsView.tsx +24 -16
  73. package/src/components/Shop/Shop.tsx +0 -20
  74. package/src/components/Shop/TabPanel/index.tsx +0 -7
  75. package/src/components/Stage/Stage.tsx +11 -20
  76. package/src/components/StatsView/StatsView.tsx +0 -15
  77. package/src/components/TierPurchase/TierPurchase.tsx +0 -12
  78. package/src/components/Toolbelt/Toolbelt.tsx +5 -10
  79. package/src/components/UpgradePurchase/UpgradePurchase.tsx +0 -10
  80. package/src/components/WineRecipeList/WineRecipe.test.tsx +2 -2
  81. package/src/components/WineRecipeList/WineRecipe.tsx +0 -5
  82. package/src/components/WineRecipeList/WineRecipeList.tsx +2 -3
  83. package/src/components/Workshop/ForgeTabPanel.tsx +0 -8
  84. package/src/components/Workshop/KitchenTabPanel.tsx +0 -7
  85. package/src/components/Workshop/RecyclingTabPanel.tsx +0 -7
  86. package/src/components/Workshop/TabPanel/index.tsx +0 -7
  87. package/src/components/Workshop/WoodChipperTabPanel.tsx +0 -7
  88. package/src/components/Workshop/Workshop.tsx +0 -9
  89. package/src/components/Workshop/getUpgradesAvailable.ts +1 -1
  90. package/src/constants.ts +18 -13
  91. package/src/data/crops/grape.ts +13 -15
  92. package/src/data/maps.test.ts +3 -4
  93. package/src/data/maps.ts +27 -29
  94. package/src/enums.ts +47 -19
  95. package/src/factories/OreFactory.ts +2 -1
  96. package/src/farmhand.d.ts +2 -3
  97. package/src/game-logic/reducers/applyPrecipitation.ts +4 -4
  98. package/src/game-logic/reducers/generatePriceEvents.ts +6 -3
  99. package/src/game-logic/reducers/harvestForestPlot.ts +1 -2
  100. package/src/game-logic/reducers/makeFermentationRecipe.ts +2 -3
  101. package/src/game-logic/reducers/minePlot.test.ts +2 -2
  102. package/src/game-logic/reducers/minePlot.ts +1 -1
  103. package/src/game-logic/reducers/processCowFertilizerProduction.test.ts +16 -14
  104. package/src/game-logic/reducers/processFeedingCows.test.ts +2 -5
  105. package/src/game-logic/reducers/purchaseCow.ts +2 -7
  106. package/src/game-logic/reducers/updateAchievements.ts +1 -1
  107. package/src/game-logic/reducers/updateFinancialRecords.test.ts +21 -25
  108. package/src/game-logic/reducers/updateFinancialRecords.ts +2 -5
  109. package/src/game-logic/reducers/updateInventoryRecordsForNextDay.test.ts +12 -14
  110. package/src/handlers/ui-events.test.ts +44 -42
  111. package/src/handlers/ui-events.tsx +10 -0
  112. package/src/hooks/usePrefersReducedMotion.ts +10 -6
  113. package/src/mui-theme.ts +4 -3
  114. package/src/scripts/generate-crop-table.ts +3 -2
  115. package/src/styles/sx.ts +29 -25
  116. package/src/test-utils/index.ts +1 -0
  117. package/src/utils/chooseRandom.ts +4 -1
  118. package/src/utils/chooseRandomIndex.ts +6 -2
  119. package/src/utils/getCowImage.ts +22 -16
  120. package/src/utils/getMaxYieldOfFermentationRecipe.ts +2 -2
  121. package/src/utils/getPeerMetadata.ts +1 -1
  122. package/src/utils/getPlotImage.test.ts +14 -14
  123. package/src/utils/getPlotImage.ts +10 -10
  124. package/src/utils/getRandomUnlockedCrop.ts +5 -3
  125. package/src/utils/getRandomizedLifespan.test.ts +2 -8
  126. package/src/utils/getValueAdjustmentStream.test.ts +9 -0
  127. package/src/utils/getValueAdjustmentStream.ts +5 -0
  128. package/src/utils/isItemAFarmProduct.ts +2 -2
  129. package/src/utils/shouldPrecipitateToday.ts +3 -1
  130. package/src/utils/shouldStormToday.ts +2 -1
  131. package/src/utils/transformStateDataForImport.ts +3 -5
@@ -48,8 +48,10 @@ describe('UI Event Handlers', () => {
48
48
  isAwaitingCowTradeRequest: false,
49
49
  currentDialogView: dialogView.NONE,
50
50
  showHomeScreen: true,
51
+ notificationDuration: 6000,
51
52
  },
52
53
  setState: vi.fn(),
54
+ props: { localforage: { setItem: vi.fn().mockResolvedValue(undefined) } },
53
55
  purchaseItem: vi.fn(),
54
56
  makeRecipe: vi.fn(),
55
57
  makeFermentationRecipe: vi.fn(),
@@ -152,9 +154,8 @@ describe('UI Event Handlers', () => {
152
154
 
153
155
  describe('handleViewChangeButtonClick', () => {
154
156
  test('navigates to selected view', () => {
155
- const handler = uiEventHandlers.handleViewChangeButtonClick.bind(
156
- mockContext
157
- )
157
+ const handler =
158
+ uiEventHandlers.handleViewChangeButtonClick.bind(mockContext)
158
159
 
159
160
  handler(stageFocusType.SHOP)
160
161
 
@@ -172,7 +173,7 @@ describe('UI Event Handlers', () => {
172
173
  enablesFieldMode: fieldMode.PLANT,
173
174
  })
174
175
 
175
- handler((mockItem as unknown) as Parameters<typeof handler>[0])
176
+ handler(mockItem as unknown as Parameters<typeof handler>[0])
176
177
 
177
178
  expect(mockContext.setState).toHaveBeenCalledWith({
178
179
  fieldMode: fieldMode.PLANT,
@@ -183,12 +184,11 @@ describe('UI Event Handlers', () => {
183
184
 
184
185
  describe('handleForestItemSelectClick', () => {
185
186
  test('selects the sapling and switches to PLANT mode', () => {
186
- const handler = uiEventHandlers.handleForestItemSelectClick.bind(
187
- mockContext
188
- )
187
+ const handler =
188
+ uiEventHandlers.handleForestItemSelectClick.bind(mockContext)
189
189
  const mockItem = testItem({ id: 'test-sapling' })
190
190
 
191
- handler((mockItem as unknown) as Parameters<typeof handler>[0])
191
+ handler(mockItem as unknown as Parameters<typeof handler>[0])
192
192
 
193
193
  // Regression: selecting a sapling must back out of whatever field
194
194
  // mode (e.g. CHOP, FERTILIZE) was previously active, or clicking an
@@ -219,7 +219,7 @@ describe('UI Event Handlers', () => {
219
219
  test('opens menu when explicitly requested', () => {
220
220
  const handler = uiEventHandlers.handleMenuToggle.bind(mockContext)
221
221
 
222
- handler((true as unknown) as Parameters<typeof handler>[0])
222
+ handler(true as unknown as Parameters<typeof handler>[0])
223
223
 
224
224
  const stateUpdater = mockContext.setState.mock.calls[0][0]
225
225
  const newState = stateUpdater({ isMenuOpen: false })
@@ -305,9 +305,8 @@ describe('UI Event Handlers', () => {
305
305
 
306
306
  describe('handleClickDialogViewButton', () => {
307
307
  test('displays requested dialog screen', () => {
308
- const handler = uiEventHandlers.handleClickDialogViewButton.bind(
309
- mockContext
310
- )
308
+ const handler =
309
+ uiEventHandlers.handleClickDialogViewButton.bind(mockContext)
311
310
 
312
311
  handler(dialogView.FARMERS_LOG)
313
312
 
@@ -350,9 +349,8 @@ describe('UI Event Handlers', () => {
350
349
 
351
350
  describe('handleFieldActionRangeChange', () => {
352
351
  test('adjusts tool range preview size', () => {
353
- const handler = uiEventHandlers.handleFieldActionRangeChange.bind(
354
- mockContext
355
- )
352
+ const handler =
353
+ uiEventHandlers.handleFieldActionRangeChange.bind(mockContext)
356
354
 
357
355
  handler(3)
358
356
 
@@ -413,9 +411,8 @@ describe('UI Event Handlers', () => {
413
411
 
414
412
  describe('handleShowHomeScreenChange', () => {
415
413
  test('enables home screen display', () => {
416
- const handler = uiEventHandlers.handleShowHomeScreenChange.bind(
417
- mockContext
418
- )
414
+ const handler =
415
+ uiEventHandlers.handleShowHomeScreenChange.bind(mockContext)
419
416
 
420
417
  handler(null, true)
421
418
 
@@ -426,9 +423,8 @@ describe('UI Event Handlers', () => {
426
423
 
427
424
  test('navigates away from home when disabling home screen while viewing it', () => {
428
425
  mockContext.state.stageFocus = stageFocusType.HOME
429
- const handler = uiEventHandlers.handleShowHomeScreenChange.bind(
430
- mockContext
431
- )
426
+ const handler =
427
+ uiEventHandlers.handleShowHomeScreenChange.bind(mockContext)
432
428
 
433
429
  handler(null, false)
434
430
 
@@ -439,6 +435,19 @@ describe('UI Event Handlers', () => {
439
435
  })
440
436
  })
441
437
 
438
+ describe('notification duration handlers', () => {
439
+ test('updates notification duration in milliseconds from slider seconds', () => {
440
+ const handler =
441
+ uiEventHandlers.handleNotificationDurationChange.bind(mockContext)
442
+
443
+ handler(new Event('change'), 8)
444
+
445
+ expect(mockContext.setState).toHaveBeenCalledWith({
446
+ notificationDuration: 8000,
447
+ })
448
+ })
449
+ })
450
+
442
451
  describe('handleRNGSeedChange', () => {
443
452
  test('applies custom random seed for reproducible gameplay', () => {
444
453
  const handler = uiEventHandlers.handleRNGSeedChange.bind(mockContext)
@@ -465,9 +474,8 @@ describe('UI Event Handlers', () => {
465
474
 
466
475
  describe('navigation handlers', () => {
467
476
  test('moves to next view', () => {
468
- const handler = uiEventHandlers.handleClickNextMenuButton.bind(
469
- mockContext
470
- )
477
+ const handler =
478
+ uiEventHandlers.handleClickNextMenuButton.bind(mockContext)
471
479
 
472
480
  handler()
473
481
 
@@ -475,9 +483,8 @@ describe('UI Event Handlers', () => {
475
483
  })
476
484
 
477
485
  test('moves to previous view', () => {
478
- const handler = uiEventHandlers.handleClickPreviousMenuButton.bind(
479
- mockContext
480
- )
486
+ const handler =
487
+ uiEventHandlers.handleClickPreviousMenuButton.bind(mockContext)
481
488
 
482
489
  handler()
483
490
 
@@ -503,9 +510,8 @@ describe('UI Event Handlers', () => {
503
510
  })
504
511
 
505
512
  test('increases inventory capacity', () => {
506
- const handler = uiEventHandlers.handleStorageExpansionPurchase.bind(
507
- mockContext
508
- )
513
+ const handler =
514
+ uiEventHandlers.handleStorageExpansionPurchase.bind(mockContext)
509
515
 
510
516
  handler()
511
517
 
@@ -515,9 +521,8 @@ describe('UI Event Handlers', () => {
515
521
 
516
522
  describe('loan handlers', () => {
517
523
  test('reduces debt by payment amount', () => {
518
- const handler = uiEventHandlers.handleClickLoanPaydownButton.bind(
519
- mockContext
520
- )
524
+ const handler =
525
+ uiEventHandlers.handleClickLoanPaydownButton.bind(mockContext)
521
526
 
522
527
  handler(500)
523
528
 
@@ -525,9 +530,8 @@ describe('UI Event Handlers', () => {
525
530
  })
526
531
 
527
532
  test('borrows money increasing debt', () => {
528
- const handler = uiEventHandlers.handleClickTakeOutLoanButton.bind(
529
- mockContext
530
- )
533
+ const handler =
534
+ uiEventHandlers.handleClickTakeOutLoanButton.bind(mockContext)
531
535
 
532
536
  handler(1000)
533
537
 
@@ -537,9 +541,8 @@ describe('UI Event Handlers', () => {
537
541
 
538
542
  describe('utility handlers', () => {
539
543
  test('resets all saved game data', () => {
540
- const handler = uiEventHandlers.handleClearPersistedDataClick.bind(
541
- mockContext
542
- )
544
+ const handler =
545
+ uiEventHandlers.handleClearPersistedDataClick.bind(mockContext)
543
546
 
544
547
  handler()
545
548
 
@@ -555,9 +558,8 @@ describe('UI Event Handlers', () => {
555
558
  })
556
559
 
557
560
  test('shows or hides chat window', () => {
558
- const handler = uiEventHandlers.handleChatRoomOpenStateChange.bind(
559
- mockContext
560
- )
561
+ const handler =
562
+ uiEventHandlers.handleChatRoomOpenStateChange.bind(mockContext)
561
563
 
562
564
  handler(true)
563
565
 
@@ -460,6 +460,16 @@ export default {
460
460
  this.setState({ showNotifications: checked })
461
461
  },
462
462
 
463
+ handleNotificationDurationChange(
464
+ this: Farmhand,
465
+ _event: React.SyntheticEvent | Event,
466
+ value: number | number[]
467
+ ) {
468
+ if (typeof value === 'number') {
469
+ this.setState({ notificationDuration: value * 1000 })
470
+ }
471
+ },
472
+
463
473
  handleClickNotificationIndicator(this: Farmhand) {
464
474
  this.openDialogView(dialogView.FARMERS_LOG)
465
475
  },
@@ -27,18 +27,22 @@ export const usePrefersReducedMotion = (): boolean => {
27
27
  mediaQueryList.addEventListener('change', listener)
28
28
  } else if ('addListener' in mediaQueryList) {
29
29
  // Compatibility for legacy mediaQueryList implementations
30
- ;((mediaQueryList as unknown) as {
31
- addListener: (cb: (e: MediaQueryListEvent) => void) => void
32
- }).addListener(listener)
30
+ ;(
31
+ mediaQueryList as unknown as {
32
+ addListener: (cb: (e: MediaQueryListEvent) => void) => void
33
+ }
34
+ ).addListener(listener)
33
35
  }
34
36
 
35
37
  return () => {
36
38
  if (mediaQueryList.removeEventListener) {
37
39
  mediaQueryList.removeEventListener('change', listener)
38
40
  } else if ('removeListener' in mediaQueryList) {
39
- ;((mediaQueryList as unknown) as {
40
- removeListener: (cb: (e: MediaQueryListEvent) => void) => void
41
- }).removeListener(listener)
41
+ ;(
42
+ mediaQueryList as unknown as {
43
+ removeListener: (cb: (e: MediaQueryListEvent) => void) => void
44
+ }
45
+ ).removeListener(listener)
42
46
  }
43
47
  }
44
48
  }, [])
package/src/mui-theme.ts CHANGED
@@ -49,9 +49,10 @@ const globalStyleOverrides = {
49
49
  },
50
50
  'input[type="number"]': { minWidth: '140px' },
51
51
  th: { fontWeight: 'bold' },
52
- 'h1, h2, h3, h4, h5, h6, legend, td, th, .MuiTypography-h1, .MuiTypography-h2, .MuiTypography-h3, .MuiTypography-h4, .MuiTypography-h5, .MuiTypography-h6, .MuiButtonBase-root': {
53
- fontFamily: '"Francois One", sans-serif',
54
- },
52
+ 'h1, h2, h3, h4, h5, h6, legend, td, th, .MuiTypography-h1, .MuiTypography-h2, .MuiTypography-h3, .MuiTypography-h4, .MuiTypography-h5, .MuiTypography-h6, .MuiButtonBase-root':
53
+ {
54
+ fontFamily: '"Francois One", sans-serif',
55
+ },
55
56
  '.danger-text': { color: colors.error },
56
57
  '.success-text': { color: colors.success },
57
58
  hr: { background: 'none' },
@@ -18,8 +18,9 @@ function getCropImage(
18
18
  if (Array.isArray(seedItem.growsInto)) {
19
19
  return `![${
20
20
  cropItem.name
21
- }](https://raw.githubusercontent.com/jeremyckahn/farmhand/main/src/img/items/${(cropItem as any)
22
- .imageId || cropItem.id}.png)`
21
+ }](https://raw.githubusercontent.com/jeremyckahn/farmhand/main/src/img/items/${
22
+ (cropItem as any).imageId || cropItem.id
23
+ }.png)`
23
24
  } else {
24
25
  return `![${cropItem.name}](https://raw.githubusercontent.com/jeremyckahn/farmhand/main/src/img/items/${cropItem.id}.png)`
25
26
  }
package/src/styles/sx.ts CHANGED
@@ -85,16 +85,17 @@ export const quickSelectSx = (theme: Theme, isMenuOpen: boolean) => ({
85
85
  display: isMenuOpen ? 'none' : undefined,
86
86
  },
87
87
  },
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
- },
88
+ [`@media (orientation: landscape) and (min-height: ${breakpoints.largePhone}px)`]:
89
+ {
90
+ left: 'auto',
91
+ maxHeight: 'calc(100vh - 20em)',
92
+ minWidth: '4em',
93
+ overflow: 'auto',
94
+ right: '0.75em',
95
+ top: '9em',
96
+ transform: 'none',
97
+ bottom: '8em',
98
+ },
98
99
  '@media (orientation: portrait)': {
99
100
  display: isMenuOpen ? 'none' : undefined,
100
101
  },
@@ -104,15 +105,17 @@ export const quickSelectSx = (theme: Theme, isMenuOpen: boolean) => ({
104
105
  overflowY: 'hidden',
105
106
  padding: '0.5em',
106
107
  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',
108
+ [`@media (orientation: landscape) and (min-height: ${breakpoints.largePhone}px)`]:
109
+ {
110
+ overflowX: 'hidden',
111
+ flexDirection: 'column',
115
112
  },
113
+ '& > *': {
114
+ [`@media (orientation: landscape) and (min-height: ${breakpoints.largePhone}px)`]:
115
+ {
116
+ alignItems: 'center',
117
+ width: '3em',
118
+ },
116
119
  },
117
120
  },
118
121
  '& .Toolbelt': {
@@ -122,12 +125,13 @@ export const quickSelectSx = (theme: Theme, isMenuOpen: boolean) => ({
122
125
  },
123
126
  '& .MuiDivider-root': {
124
127
  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
- },
128
+ [`@media (orientation: landscape) and (min-height: ${breakpoints.largePhone}px)`]:
129
+ {
130
+ margin: '0.5em 0',
131
+ height: '1px',
132
+ width: '100%',
133
+ borderRightWidth: 0,
134
+ borderBottomWidth: 'thin',
135
+ },
132
136
  },
133
137
  })
@@ -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))
@@ -65,23 +65,29 @@ const colorizeCowTemplate = (() => {
65
65
  )
66
66
  const image = await Jimp.read(cowTemplateBuffer)
67
67
 
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)
68
+ image.scan(
69
+ 0,
70
+ 0,
71
+ image.bitmap.width,
72
+ image.bitmap.height,
73
+ function (x, y) {
74
+ const { r, g, b } = Jimp.intToRGBA(image.getPixelColor(x, y))
75
+
76
+ // rgb(102, 102, 102) represents the color to replace in the template
77
+ // source images (#666).
78
+ if (r === 102 && g === 102 && b === 102) {
79
+ const cowColorRgb = hexToRgb(COW_COLORS_HEX_MAP[color])
80
+ const colorNumber = Jimp.rgbaToInt(
81
+ cowColorRgb.r,
82
+ cowColorRgb.g,
83
+ cowColorRgb.b,
84
+ 255
85
+ )
86
+
87
+ image.setPixelColor(colorNumber, x, y)
88
+ }
83
89
  }
84
- })
90
+ )
85
91
 
86
92
  cachedCowImages[imageKey] = await image.getBase64Async(Jimp.MIME_PNG)
87
93
 
@@ -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