Summary
Allow the player to plant seeds in the world, tend growing crops, and harvest food from fully grown plants.
Background
Currently the only way for the player to obtain food is to gather it from pre-spawned world entities (apples from oak trees, bananas from jungle trees). Farming gives the player a renewable food source they can cultivate. The implementation should follow existing entity and interaction patterns: seeds and crops are placeable DrawableEntity subclasses, growth is driven by TickCounter, and harvesting reuses the existing left-click gather action in WorldScreen. Note: if PR #344 is merged before this, any new logging should use getLogger from src/gameLogging/logger.py.
Requirements
New Entities
src/entity/wheatSeed.py — WheatSeed, solid=False, not a food item, placeholder asset assets/images/wheatSeed.png
src/entity/youngCrop.py — YoungCrop, solid=False, stores tickPlanted, placeholder asset assets/images/youngCrop.png
src/entity/matureCrop.py — MatureCrop, solid=False, stores tickPlanted, placeholder asset assets/images/matureCrop.png
src/entity/wheat.py — Wheat(Food), solid=False, energy 10–20, placeholder asset assets/images/wheat.png
Growth Config
- Add
cropGrowthTicks integer to config.yml (default 1800 — 1 minute per stage at 30 tps) and load it in Config (src/config/config.py)
- Growth proceeds in two stages:
YoungCrop → MatureCrop after cropGrowthTicks ticks, then MatureCrop → harvestable (signals readiness; harvesting converts it to Wheat in the player's inventory)
Growth Logic
- Add a
tickCrops(tick, config) method to Room (src/world/room.py), following the same pattern as tickExcrement()
- Each tick, iterate all locations; for any
YoungCrop or MatureCrop whose tick - tickPlanted >= cropGrowthTicks, replace it with the next stage entity
- Call
tickCrops from the world screen tick loop in WorldScreen alongside tickExcrement
Planting
WheatSeed must be added to the crafting registry (src/crafting/recipeRegistry.py) — e.g. 1× Grass → 3× WheatSeed — so the player has a reliable way to obtain seeds
- Planting uses the existing right-click place action (
executePlaceAction() in worldScreen.py): when the selected hotbar item is a WheatSeed and the target location contains Grass but no solid entity, remove the Grass, consume the seed from inventory, and place a YoungCrop with tickPlanted = tickCounter.getTick()
- If the target location does not contain
Grass, set a status message via status.set("Must plant on grass") and consume no items
Harvesting
- When the player left-clicks (
executeGatherAction()) on a MatureCrop, remove it from the room, add a Wheat entity to the player's inventory via placeIntoFirstAvailableInventorySlot, and set status "Harvested Wheat"
YoungCrop cannot be harvested — clicking it sets status "Crop is not ready"
- Add
WheatSeed, YoungCrop, MatureCrop, and Wheat to canBePickedUp() in worldScreen.py (seeds and young crops can be picked back up to replant or cancel; mature crops are harvested only)
Persistence
- Add all four new entity types to the entity registries in
roomJsonReaderWriter.py and inventoryJsonReaderWriter.py, following the existing registry pattern
YoungCrop and MatureCrop must persist tickPlanted so growth state survives save/load, following the same pattern as Excrement.tickCreated
- Update
schemas/room.json if any new required fields are added to entity JSON
README
- Add farming controls to the Controls table in
README.md (right-click to plant seed, left-click to harvest mature crop)
Acceptance Criteria
Summary
Allow the player to plant seeds in the world, tend growing crops, and harvest food from fully grown plants.
Background
Currently the only way for the player to obtain food is to gather it from pre-spawned world entities (apples from oak trees, bananas from jungle trees). Farming gives the player a renewable food source they can cultivate. The implementation should follow existing entity and interaction patterns: seeds and crops are placeable
DrawableEntitysubclasses, growth is driven byTickCounter, and harvesting reuses the existing left-click gather action inWorldScreen. Note: if PR #344 is merged before this, any new logging should usegetLoggerfromsrc/gameLogging/logger.py.Requirements
New Entities
src/entity/wheatSeed.py—WheatSeed, solid=False, not a food item, placeholder assetassets/images/wheatSeed.pngsrc/entity/youngCrop.py—YoungCrop, solid=False, storestickPlanted, placeholder assetassets/images/youngCrop.pngsrc/entity/matureCrop.py—MatureCrop, solid=False, storestickPlanted, placeholder assetassets/images/matureCrop.pngsrc/entity/wheat.py—Wheat(Food), solid=False, energy 10–20, placeholder assetassets/images/wheat.pngGrowth Config
cropGrowthTicksinteger toconfig.yml(default1800— 1 minute per stage at 30 tps) and load it inConfig(src/config/config.py)YoungCrop→MatureCropaftercropGrowthTicksticks, thenMatureCrop→ harvestable (signals readiness; harvesting converts it toWheatin the player's inventory)Growth Logic
tickCrops(tick, config)method toRoom(src/world/room.py), following the same pattern astickExcrement()YoungCroporMatureCropwhosetick - tickPlanted >= cropGrowthTicks, replace it with the next stage entitytickCropsfrom the world screen tick loop inWorldScreenalongsidetickExcrementPlanting
WheatSeedmust be added to the crafting registry (src/crafting/recipeRegistry.py) — e.g. 1×Grass→ 3×WheatSeed— so the player has a reliable way to obtain seedsexecutePlaceAction()inworldScreen.py): when the selected hotbar item is aWheatSeedand the target location containsGrassbut no solid entity, remove theGrass, consume the seed from inventory, and place aYoungCropwithtickPlanted = tickCounter.getTick()Grass, set a status message viastatus.set("Must plant on grass")and consume no itemsHarvesting
executeGatherAction()) on aMatureCrop, remove it from the room, add aWheatentity to the player's inventory viaplaceIntoFirstAvailableInventorySlot, and set status"Harvested Wheat"YoungCropcannot be harvested — clicking it sets status"Crop is not ready"WheatSeed,YoungCrop,MatureCrop, andWheattocanBePickedUp()inworldScreen.py(seeds and young crops can be picked back up to replant or cancel; mature crops are harvested only)Persistence
roomJsonReaderWriter.pyandinventoryJsonReaderWriter.py, following the existing registry patternYoungCropandMatureCropmust persisttickPlantedso growth state survives save/load, following the same pattern asExcrement.tickCreatedschemas/room.jsonif any new required fields are added to entity JSONREADME
README.md(right-click to plant seed, left-click to harvest mature crop)Acceptance Criteria
WheatSeedcan be crafted fromGrassand placed on grass tiles via right-clickYoungCrop→MatureCropover twocropGrowthTicksintervalsMatureCropaddsWheat(food) to the player's inventoryYoungCropshows "Crop is not ready" and does nothingtickPlanted) is preserved across save/load cyclesroomJsonReaderWriter.pyandinventoryJsonReaderWriter.pyYoungCropandMatureCrop