Skip to content

Commit e3965ef

Browse files
feat: add weeds and compost (#311)
* feat: add weeds and compost - weeds grow randomly overnight in unoccupied plots - weeds can be turned into compost through crafting which can be crafted into fertilizer - added purchaseable composter - added itemType 'WEED' - refactored harvestPlot a bit to make it easier to understand for weeds vs crops * add field to saveDataStubFactory * more code review feedback updates * categorize weeds in new Foraged Items category and associated recipes into new Recycling area of workshop * add tests for purchaseSmelter * fix string casing in forge * fix: update clearPlot to handle weeds * fix: update JSDoc * fix: prevent shoveled plot animations from replaying * refactor: remove wasJustShoveled from farmhand.crop typedef * refactor: rename resetWasShoveled to updatePlotShoveledState * refactor: rename isRipe to canBeHarvested Co-authored-by: Jeremy Kahn <[email protected]>
1 parent bed2a5b commit e3965ef

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

57 files changed

+603
-90
lines changed

src/components/Farmhand/Farmhand.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,7 @@ export default class Farmhand extends Component {
421421
redirect: '',
422422
room: decodeURIComponent(this.props.match.params.room || DEFAULT_ROOM),
423423
purchasedCombine: 0,
424+
purchasedComposter: 0,
424425
purchasedCowPen: 0,
425426
purchasedField: 0,
426427
purchasedSmelter: 0,
@@ -539,6 +540,7 @@ export default class Farmhand extends Component {
539540
'plantInPlot',
540541
'prependPendingPeerMessage',
541542
'purchaseCombine',
543+
'purchaseComposter',
542544
'purchaseCow',
543545
'purchaseCowPen',
544546
'purchaseField',

src/components/Inventory/Inventory.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const {
2323
STONE,
2424
FUEL,
2525
TOOL_UPGRADE,
26+
WEED,
2627
} = itemType
2728

2829
export const categoryIds = enumify([
@@ -31,6 +32,7 @@ export const categoryIds = enumify([
3132
'CRAFTED_ITEMS',
3233
'CROPS',
3334
'FIELD_TOOLS',
35+
'FORAGED_ITEMS',
3436
'MINED_RESOURCES',
3537
'SEEDS',
3638
'UPGRADES',
@@ -42,6 +44,7 @@ const {
4244
CRAFTED_ITEMS,
4345
CROPS,
4446
FIELD_TOOLS,
47+
FORAGED_ITEMS,
4548
MINED_RESOURCES,
4649
SEEDS,
4750
UPGRADES,
@@ -61,6 +64,7 @@ const itemTypeCategoryMap = Object.freeze({
6164
[SPRINKLER]: FIELD_TOOLS,
6265
[STONE]: MINED_RESOURCES,
6366
[TOOL_UPGRADE]: UPGRADES,
67+
[WEED]: FORAGED_ITEMS,
6468
})
6569

6670
const getItemCategories = () =>
@@ -97,6 +101,7 @@ export const Inventory = ({
97101
{[
98102
[CROPS, 'Crops'],
99103
[SEEDS, 'Seeds'],
104+
[FORAGED_ITEMS, 'Foraged Items'],
100105
[FIELD_TOOLS, 'Field Tools'],
101106
[ANIMAL_PRODUCTS, 'Animal Products'],
102107
[ANIMAL_SUPPLIES, 'Animal Supplies'],

src/components/Inventory/Inventory.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ describe('item sorting', () => {
7575
)
7676
).toEqual({
7777
[categoryIds.CROPS]: [testItem({ id: 'sample-crop-1' })],
78+
[categoryIds.FORAGED_ITEMS]: [],
7879
[categoryIds.MINED_RESOURCES]: [
7980
testItem({ id: 'coal' }),
8081
testItem({ id: 'stone' }),

src/components/Plot/Plot.js

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,12 @@ export const Plot = ({
7474
x,
7575
y,
7676

77-
image = getPlotImage(plotContent),
77+
image = getPlotImage(plotContent, x, y),
7878
lifeStage = plotContent &&
7979
getPlotContentType(plotContent) === itemType.CROP &&
8080
getCropLifeStage(plotContent),
81-
isRipe = lifeStage === cropLifeStage.GROWN,
81+
canBeHarvested = lifeStage === cropLifeStage.GROWN ||
82+
(plotContent && getPlotContentType(plotContent) === itemType.WEED),
8283
}) => {
8384
const item = plotContent ? itemsMap[plotContent.itemId] : null
8485
const daysLeftToMature = getDaysLeftToMature(plotContent)
@@ -101,7 +102,10 @@ export const Plot = ({
101102
}, [initialIsShoveledState, plotContent])
102103

103104
useEffect(() => {
104-
if (plotContent === null) setInitialIsShoveledState(false)
105+
if (plotContent === null) {
106+
setInitialIsShoveledState(false)
107+
setWasJustShoveled(false)
108+
}
105109
}, [plotContent])
106110

107111
const showPlotImage = Boolean(
@@ -120,7 +124,7 @@ export const Plot = ({
120124

121125
// For crops
122126
crop: isCrop,
123-
'is-ripe': isRipe,
127+
'can-be-harvested': canBeHarvested,
124128

125129
// For crops and scarecrows
126130
'can-be-fertilized':
@@ -145,8 +149,8 @@ export const Plot = ({
145149
{...{
146150
className: classNames('square', {
147151
...(isCrop && {
148-
animated: isRipe,
149-
heartBeat: isRipe,
152+
animated: canBeHarvested,
153+
heartBeat: canBeHarvested,
150154
}),
151155
...(wasJustShoveled && {
152156
animated: true,

src/components/Plot/Plot.sass

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
&:hover
3333
cursor: pointer
3434

35-
.harvest-mode &.is-ripe
35+
.harvest-mode &.can-be-harvested
3636
background-color: $colorGreenOk
3737

3838
&:hover
@@ -52,7 +52,7 @@
5252
background-color: $colorYellow
5353
cursor: pointer
5454

55-
.harvest-mode.is-inventory-full &.crop.is-ripe,
55+
.harvest-mode.is-inventory-full &.crop.can-be-harvested,
5656
.cleanup-mode.is-inventory-full &.is-replantable
5757
background-color: $colorRedDanger
5858
cursor: not-allowed
@@ -61,7 +61,7 @@
6161
background-color: $colorGreenOk
6262
cursor: pointer
6363

64-
.cleanup-mode &.is-ripe
64+
.cleanup-mode &.can-be-harvested
6565
background-color: $colorGreenOk
6666
cursor: auto
6767

src/components/Plot/Plot.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@ test('renders is-replantable class', () => {
4949
expect(component.find('.Plot').hasClass('is-replantable')).toBeTruthy()
5050
})
5151

52-
test('renders "is-ripe" class', () => {
52+
test('renders "can-be-harvested" class', () => {
5353
component.setProps({ lifeStage: cropLifeStage.GROWN })
54-
expect(component.find('.Plot').hasClass('is-ripe')).toBeTruthy()
54+
expect(component.find('.Plot').hasClass('can-be-harvested')).toBeTruthy()
5555
})
5656

5757
describe('"can-be-fertilized" class', () => {

src/components/Shop/Shop.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { itemType, toolType } from '../../enums'
2424
import {
2525
INFINITE_STORAGE_LIMIT,
2626
PURCHASEABLE_COMBINES,
27+
PURCHASEABLE_COMPOSTERS,
2728
PURCHASEABLE_COW_PENS,
2829
PURCHASEABLE_FIELD_SIZES,
2930
PURCHASEABLE_SMELTERS,
@@ -55,13 +56,15 @@ const categorizeShopInventory = memoize(shopInventory =>
5556

5657
export const Shop = ({
5758
handleCombinePurchase,
59+
handleComposterPurchase,
5860
handleCowPenPurchase,
5961
handleFieldPurchase,
6062
handleSmelterPurchase,
6163
handleStorageExpansionPurchase,
6264
inventoryLimit,
6365
money,
6466
purchasedCombine,
67+
purchasedComposter,
6568
purchasedCowPen,
6669
purchasedField,
6770
purchasedSmelter,
@@ -211,6 +214,21 @@ export const Shop = ({
211214
/>
212215
</li>
213216
) : null}
217+
<li>
218+
<TierPurchase
219+
{...{
220+
description:
221+
'You can purchase a Composter to turn weeds into fertilizer.',
222+
onBuyClick: handleComposterPurchase,
223+
maxedOutPlaceholder: "You've already purchased the composter!",
224+
purchasedTier: purchasedComposter,
225+
renderTierLabel: ({ type, price }) =>
226+
`${dollarString(price)}: ${type} Composter`,
227+
tiers: PURCHASEABLE_COMPOSTERS,
228+
title: 'Buy composter',
229+
}}
230+
/>
231+
</li>
214232
</ul>
215233
</TabPanel>
216234
</div>

src/components/Workshop/ForgeTabPanel.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export function ForgeTabPanel({
5959
{...{
6060
linkTarget: '_blank',
6161
className: 'markdown',
62-
source: `Forge Recipes are learned by selling resources mined from the field.`,
62+
source: `Forge recipes are learned by selling resources mined from the field.`,
6363
}}
6464
/>
6565
</CardContent>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import React from 'react'
2+
import PropTypes from 'prop-types'
3+
4+
import Card from '@material-ui/core/Card'
5+
import CardContent from '@material-ui/core/CardContent'
6+
import Divider from '@material-ui/core/Divider'
7+
import ReactMarkdown from 'react-markdown'
8+
9+
import { recipeType } from '../../enums'
10+
11+
import { recipeCategories } from '../../data/maps'
12+
13+
import { TabPanel } from './TabPanel'
14+
import { RecipeList } from './RecipeList'
15+
16+
export function RecyclingTabPanel({
17+
currentTab,
18+
index,
19+
learnedRecipes,
20+
setCurrentTab,
21+
}) {
22+
return (
23+
<TabPanel value={currentTab} index={index}>
24+
<RecipeList
25+
learnedRecipes={learnedRecipes}
26+
allRecipes={recipeCategories[recipeType.RECYCLING]}
27+
/>
28+
<Divider />
29+
<ul className="card-list">
30+
<li>
31+
<Card>
32+
<CardContent>
33+
<ReactMarkdown
34+
{...{
35+
linkTarget: '_blank',
36+
className: 'markdown',
37+
source: `Recyling recipes are learned by selling items foraged from the field.`,
38+
}}
39+
/>
40+
</CardContent>
41+
</Card>
42+
</li>
43+
</ul>
44+
</TabPanel>
45+
)
46+
}
47+
48+
RecyclingTabPanel.propTypes = {
49+
currentTab: PropTypes.number.isRequired,
50+
index: PropTypes.number.isRequired,
51+
learnedRecipes: PropTypes.array.isRequired,
52+
setCurrentTab: PropTypes.func.isRequired,
53+
}

src/components/Workshop/Workshop.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,16 @@ import { a11yProps } from './TabPanel'
1515

1616
import { ForgeTabPanel } from './ForgeTabPanel'
1717
import { KitchenTabPanel } from './KitchenTabPanel'
18+
import { RecyclingTabPanel } from './RecyclingTabPanel'
1819

1920
import './Workshop.sass'
2021

21-
const Workshop = ({ learnedRecipes, purchasedSmelter, toolLevels }) => {
22+
const Workshop = ({
23+
learnedRecipes,
24+
purchasedComposter,
25+
purchasedSmelter,
26+
toolLevels,
27+
}) => {
2228
const [currentTab, setCurrentTab] = useState(0)
2329

2430
const learnedKitchenRecipes = Object.keys(learnedRecipes).filter(
@@ -29,8 +35,14 @@ const Workshop = ({ learnedRecipes, purchasedSmelter, toolLevels }) => {
2935
recipeId => recipesMap[recipeId].recipeType === recipeType.FORGE
3036
)
3137

38+
const learnedRecyclingRecipes = Object.keys(learnedRecipes).filter(
39+
recipeId => recipesMap[recipeId].recipeType === recipeType.RECYCLING
40+
)
41+
3242
const showForge = features.MINING && purchasedSmelter
3343

44+
const recyclingTabIndex = showForge ? 2 : 1
45+
3446
return (
3547
<div className="Workshop">
3648
<AppBar position="static" color="primary">
@@ -41,6 +53,9 @@ const Workshop = ({ learnedRecipes, purchasedSmelter, toolLevels }) => {
4153
>
4254
<Tab {...{ label: 'Kitchen', ...a11yProps(0) }} />
4355
{showForge ? <Tab {...{ label: 'Forge', ...a11yProps(1) }} /> : null}
56+
{purchasedComposter ? (
57+
<Tab {...{ label: 'Recycling', ...a11yProps(recyclingTabIndex) }} />
58+
) : null}
4459
</Tabs>
4560
</AppBar>
4661
<KitchenTabPanel
@@ -58,12 +73,21 @@ const Workshop = ({ learnedRecipes, purchasedSmelter, toolLevels }) => {
5873
toolLevels={toolLevels}
5974
/>
6075
) : null}
76+
{purchasedComposter ? (
77+
<RecyclingTabPanel
78+
currentTab={currentTab}
79+
index={recyclingTabIndex}
80+
learnedRecipes={learnedRecyclingRecipes}
81+
setCurrentTab={setCurrentTab}
82+
/>
83+
) : null}
6184
</div>
6285
)
6386
}
6487

6588
Workshop.propTypes = {
6689
learnedRecipes: object.isRequired,
90+
purchasedComposter: number,
6791
purchasedSmelter: number,
6892
toolLevels: object.isRequired,
6993
}

0 commit comments

Comments
 (0)