Skip to content

Commit

Permalink
feat: first pieces for The Forest
Browse files Browse the repository at this point in the history
  - add feature flag
  - add new view and insert into navigation
  - add purchaseForest reducer and ui event
  - add some strings for forest notifications
  - add some constants for forest sizes
  - add purchasable upgrade to shop
  • Loading branch information
lstebner committed Jan 16, 2024
1 parent de83ce7 commit 0a09f63
Show file tree
Hide file tree
Showing 17 changed files with 227 additions and 2 deletions.
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ REACT_APP_NAME=$npm_package_name
REACT_APP_VERSION=$npm_package_version

REACT_APP_ENABLE_KEGS=true
REACT_APP_ENABLE_FOREST=false
1 change: 1 addition & 0 deletions .env.development.local
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
REACT_APP_API_ROOT=http://localhost:3001/
REACT_APP_ENABLE_KEGS=true
REACT_APP_ENABLE_FOREST=true

# Silence warnings from dev server
# https://stackoverflow.com/a/70834076
Expand Down
13 changes: 11 additions & 2 deletions src/components/Farmhand/Farmhand.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import { levelAchieved } from '../../utils/levelAchieved'
import {
computeMarketPositions,
createNewField,
createNewForest,
doesMenuObstructStage,
generateCow,
getAvailableShopInventory,
Expand Down Expand Up @@ -116,7 +117,7 @@ import {
SERVER_ERROR,
UPDATE_AVAILABLE,
} from '../../strings'
import { endpoints, rtcConfig, trackerUrls } from '../../config'
import { endpoints, features, rtcConfig, trackerUrls } from '../../config'

import { scarecrow } from '../../data/items'

Expand Down Expand Up @@ -214,6 +215,7 @@ const applyPriceEvents = (valueAdjustments, priceCrashes, priceSurges) => {
* @property {number} experience
* @property {string} farmName
* @property {(?farmhand.plotContent)[][]} field
* @property {(?farmhand.plotContent)[][]} forest
* @property {farmhand.fieldMode} fieldMode
* @property {Function?} getCowAccept https://github.com/dmotz/trystero#receiver
* @property {Function?} getCowReject https://github.com/dmotz/trystero#receiver
Expand Down Expand Up @@ -272,6 +274,7 @@ const applyPriceEvents = (valueAdjustments, priceCrashes, priceSurges) => {
* @property {number} purchasedCowPen
* @property {number} purchasedCellar
* @property {number} purchasedField
* @property {number} purchasedForest
* @property {number} purchasedSmelter
* @property {number} profitabilityStreak
* @property {number} record7dayProfitAverage
Expand Down Expand Up @@ -364,13 +367,17 @@ export default class Farmhand extends FarmhandReducers {
}

get viewList() {
const { CELLAR, COW_PEN, HOME, WORKSHOP } = stageFocusType
const { CELLAR, COW_PEN, HOME, WORKSHOP, FOREST } = stageFocusType
const viewList = [...STANDARD_VIEW_LIST]

if (this.state.showHomeScreen) {
viewList.unshift(HOME)
}

if (this.state.purchasedForest && features.FOREST) {
viewList.push(FOREST)
}

if (this.state.purchasedCowPen) {
viewList.push(COW_PEN)
}
Expand Down Expand Up @@ -437,6 +444,7 @@ export default class Farmhand extends FarmhandReducers {
farmName: 'Unnamed',
field: createNewField(),
fieldMode: OBSERVE,
forest: createNewForest(),
getCowAccept: noop,
getCowReject: noop,
getCowTradeRequest: noop,
Expand Down Expand Up @@ -490,6 +498,7 @@ export default class Farmhand extends FarmhandReducers {
purchasedCowPen: 0,
purchasedCellar: 0,
purchasedField: 0,
purchasedForest: 0,
purchasedSmelter: 0,
sendCowTradeRequest: noop,
showHomeScreen: true,
Expand Down
4 changes: 4 additions & 0 deletions src/components/Farmhand/FarmhandReducers.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,10 @@ export class FarmhandReducers extends Component {
throw new Error('Unimplemented')
}
/** @type BoundReducer */
purchaseForest() {
throw new Error('Unimplemented')
}
/** @type BoundReducer */
purchaseItem() {
throw new Error('Unimplemented')
}
Expand Down
17 changes: 17 additions & 0 deletions src/components/Forest/Forest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import React from 'react'

import FarmhandContext from '../Farmhand/Farmhand.context'

export const Forest = () => {
return <div>'welcome to da forest'</div>
}

export default function Consumer(props) {
return (
<FarmhandContext.Consumer>
{({ gameState, handlers }) => (
<Forest {...{ ...gameState, ...handlers, ...props }} />
)}
</FarmhandContext.Consumer>
)
}
1 change: 1 addition & 0 deletions src/components/Forest/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './Forest'
19 changes: 19 additions & 0 deletions src/components/Shop/Shop.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
PURCHASEABLE_COMPOSTERS,
PURCHASEABLE_COW_PENS,
PURCHASEABLE_FIELD_SIZES,
PURCHASABLE_FOREST_SIZES,
PURCHASEABLE_SMELTERS,
STORAGE_EXPANSION_AMOUNT,
} from '../../constants'
Expand Down Expand Up @@ -60,6 +61,7 @@ export const Shop = ({
handleCowPenPurchase,
handleCellarPurchase,
handleFieldPurchase,
handleForestPurchase,
handleSmelterPurchase,
handleStorageExpansionPurchase,
inventoryLimit,
Expand All @@ -69,6 +71,7 @@ export const Shop = ({
purchasedCowPen,
purchasedCellar,
purchasedField,
purchasedForest,
purchasedSmelter,
shopInventory,
toolLevels,
Expand Down Expand Up @@ -197,6 +200,22 @@ export const Shop = ({
/>
</li>
) : null}
{features.FOREST ? (
<li>
<TierPurchase
{...{
onBuyClick: handleForestPurchase,
maxedOutPlaceholder:
"You've purchased the largest forest available!",
purchasedTier: purchasedForest,
renderTierLabel: ({ columns, price, rows }) =>
`${dollarString(price)}: ${columns} x ${rows}`,
tiers: PURCHASABLE_FOREST_SIZES,
title: purchasedForest ? 'Expand Forest' : 'Purchase Forest',
}}
/>
</li>
) : null}
<li>
<TierPurchase
{...{
Expand Down
2 changes: 2 additions & 0 deletions src/components/Stage/Stage.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { array, arrayOf, string } from 'prop-types'

import FarmhandContext from '../Farmhand/Farmhand.context'
import Field from '../Field'
import Forest from '../Forest'
import Home from '../Home'
import CowPen from '../CowPen'
import Shop from '../Shop'
Expand Down Expand Up @@ -52,6 +53,7 @@ export const Stage = ({ field, stageFocus, viewTitle }) => {
}}
/>
)}
{stageFocus === stageFocusType.FOREST && <Forest />}
{stageFocus === stageFocusType.SHOP && <Shop />}
{stageFocus === stageFocusType.COW_PEN && <CowPen />}
{stageFocus === stageFocusType.WORKSHOP && <Workshop />}
Expand Down
17 changes: 17 additions & 0 deletions src/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,20 @@ export const PURCHASEABLE_FIELD_SIZES = freeze(
])
)

export const INITIAL_FOREST_WIDTH = 4
export const INITIAL_FOREST_HEIGHT = 1

/**
* @type Map<number, farmhand.purchasableFieldSize>
*/
export const PURCHASABLE_FOREST_SIZES = freeze(
new Map([
[1, { columns: 4, rows: 2, price: 100_000 }],
[2, { columns: 4, rows: 3, price: 200_000 }],
[3, { columns: 4, rows: 4, price: 300_000 }],
])
)

export const LARGEST_PURCHASABLE_FIELD_SIZE = /** @type {farmhand.purchaseableFieldSize} */ (PURCHASEABLE_FIELD_SIZES.get(
PURCHASEABLE_FIELD_SIZES.size
))
Expand Down Expand Up @@ -130,6 +144,7 @@ export const PRICE_EVENT_STANDARD_DURATION_DECREASE = 1
export const STAGE_TITLE_MAP = {
[stageFocusType.HOME]: 'Home',
[stageFocusType.FIELD]: 'Field',
[stageFocusType.FOREST]: 'Forest',
[stageFocusType.SHOP]: 'Shop',
[stageFocusType.COW_PEN]: 'Cows',
[stageFocusType.WORKSHOP]: 'Workshop',
Expand Down Expand Up @@ -181,6 +196,7 @@ export const PERSISTED_STATE_KEYS = [
'purchasedCowPen',
'purchasedCellar',
'purchasedField',
'purchasedForest',
'purchasedSmelter',
'record7dayProfitAverage',
'recordProfitabilityStreak',
Expand Down Expand Up @@ -280,6 +296,7 @@ export const EXPERIENCE_VALUES = {
COW_TRADED: 1,
FERMENTATION_RECIPE_MADE: 1,
FIELD_EXPANDED: 5,
FOREST_EXPANDED: 10,
FORGE_RECIPE_MADE: 3,
ITEM_SOLD: 1,
KEG_SOLD: 2,
Expand Down
1 change: 1 addition & 0 deletions src/enums.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ export const stageFocusType = enumify([
'NONE', // Used for testing
'HOME',
'FIELD',
'FOREST',
'SHOP',
'COW_PEN',
'INVENTORY',
Expand Down
1 change: 1 addition & 0 deletions src/game-logic/reducers/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export * from './purchaseCow'
export * from './purchaseCowPen'
export * from './purchaseCellar'
export * from './purchaseField'
export * from './purchaseForest'
export * from './purchaseItem'
export * from './purchaseSmelter'
export * from './purchaseStorageExpansion'
Expand Down
45 changes: 45 additions & 0 deletions src/game-logic/reducers/purchaseForest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { moneyTotal, nullArray } from '../../utils'
import { EXPERIENCE_VALUES, PURCHASABLE_FOREST_SIZES } from '../../constants'
import { FOREST_EXPANDED } from '../../templates'
import { FOREST_AVAILABLE_NOTIFICATION } from '../../strings'

import { addExperience } from './addExperience'
import { showNotification } from './showNotification'

/**
* @param {farmhand.state} state
* @param {number} forestId
* @returns {farmhand.state}
*/
export const purchaseForest = (state, forestId) => {
const { forest, money, purchasedForest } = state
if (purchasedForest >= forestId) {
return state
}

state = addExperience(state, EXPERIENCE_VALUES.FOREST_EXPANDED)

const { columns, price, rows } = PURCHASABLE_FOREST_SIZES.get(forestId)

/*
* FIXME: using FOREST_AVAILABLE_NOTIFICATION here is temporary, this code path will
* ultimately just be for expansion and availability will happen elsewhere, such as
* through leveling up to a certain level
*/
const notificationText =
forestId === 1
? FOREST_AVAILABLE_NOTIFICATION
: FOREST_EXPANDED`${rows * columns}`
state = showNotification(state, notificationText, 'success')

return {
...state,
purchasedForest: forestId,
forest: nullArray(rows).map((_, row) =>
nullArray(columns).map(
(_, column) => (forest[row] && forest[row][column]) || null
)
),
money: moneyTotal(money, -price),
}
}
76 changes: 76 additions & 0 deletions src/game-logic/reducers/purchaseForest.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { testCrop } from '../../test-utils'
import { EXPERIENCE_VALUES, PURCHASABLE_FOREST_SIZES } from '../../constants'
import { FOREST_AVAILABLE_NOTIFICATION } from '../../strings'

import { purchaseForest } from './purchaseForest'

describe('purchaseForest', () => {
test('updates purchasedForest', () => {
const { purchasedForest } = purchaseForest({ purchasedForest: 0 }, 0)
expect(purchasedForest).toEqual(0)
})

test('prevents repurchasing options', () => {
const { purchasedForest } = purchaseForest({ purchasedForest: 2 }, 1)
expect(purchasedForest).toEqual(2)
})

test('deducts money', () => {
const { money } = purchaseForest(
{ todaysNotifications: [], money: 101_000, forest: [[]] },
1
)
expect(money).toEqual(1_000)
})

test('adds experience', () => {
const { experience } = purchaseForest(
{ experience: 0, todaysNotifications: [], forest: [[]] },
1
)

expect(experience).toEqual(EXPERIENCE_VALUES.FOREST_EXPANDED)
})

test('shows notification', () => {
const { todaysNotifications } = purchaseForest(
{ todaysNotifications: [], forest: [[]] },
1
)

expect(todaysNotifications[0].message).toEqual(
FOREST_AVAILABLE_NOTIFICATION
)
})

describe('forest expansion', () => {
test('forest expands without destroying existing data', () => {
const expectedForest = []
const forestSize = PURCHASABLE_FOREST_SIZES.get(1)

for (let y = 0; y < forestSize.rows; y++) {
const row = []
for (let x = 0; x < forestSize.columns; x++) {
row.push(null)
}
expectedForest.push(row)
}

const { forest } = purchaseForest(
{
todaysNotifications: [],
forest: [
[testCrop(), null],
[null, testCrop()],
],
},
1
)

expectedForest[0][0] = testCrop()
expectedForest[1][1] = testCrop()

expect(forest).toEqual(expectedForest)
})
})
})
7 changes: 7 additions & 0 deletions src/handlers/ui-events.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,13 @@ export default {
this.purchaseField(fieldId)
},

/**
* @param {number} forestId
*/
handleForestPurchase(forestId) {
this.purchaseForest(forestId)
},

/**
* @param {number} combineId
*/
Expand Down
2 changes: 2 additions & 0 deletions src/strings.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ export const FORGE_AVAILABLE_NOTIFICATION =
export const RECYCLING_AVAILABLE_NOTIFICATION =
'**Recycling** is now available in the Workshop!'

export const FOREST_AVAILABLE_NOTIFICATION = '**The Forest** is now available!'

export const COW_COLOR_NAMES = {
[cowColors.BLUE]: 'Blue',
[cowColors.BROWN]: 'Brown',
Expand Down
14 changes: 14 additions & 0 deletions src/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -366,3 +366,17 @@ export const KEG_SPOILED_MESSAGE = (_, keg) =>
*/
export const NEW_COW_OFFERED_FOR_TRADE = (_, peerMetadata) =>
`A new cow is being offered for trade by ${getPlayerName(peerMetadata.id)}!`

/**
* @param {TemplatesStringsArray}
* @returns {string}
*/
export const FOREST_UNLOCKED = _ => 'You now have access to the Forest!'

/**
* @param {TemplatesStringsArray}
* @param {number} numTrees
* @returns {string}
*/
export const FOREST_EXPANDED = (_, numTrees) =>
`The Forest has expanded! You can now plant up to ${numTrees} trees.`
Loading

0 comments on commit 0a09f63

Please sign in to comment.