Add our first upgrade to the temple.

This commit is contained in:
Ryan Lanny Jenkins 2025-05-19 23:35:45 -05:00
parent 9644cc4b4a
commit 6d72c8c0f9
5 changed files with 201 additions and 6 deletions

View file

@ -9,6 +9,7 @@ import { useGameTick } from './hooks/useGameTick'
import Field from './components/Field' import Field from './components/Field'
import Warehouse from './components/Warehouse' import Warehouse from './components/Warehouse'
import Market from './components/Market' import Market from './components/Market'
import Temple from './components/Temple'
import { ActionCooldown } from './components/ActionCooldown' import { ActionCooldown } from './components/ActionCooldown'
import { useSaveSystem } from './store/useSaveSystem' import { useSaveSystem } from './store/useSaveSystem'
import { Console } from './components/Console' import { Console } from './components/Console'
@ -38,9 +39,7 @@ function App() {
<TabsTrigger value="fields">Fields</TabsTrigger> <TabsTrigger value="fields">Fields</TabsTrigger>
<TabsTrigger value="warehouse">Warehouse</TabsTrigger> <TabsTrigger value="warehouse">Warehouse</TabsTrigger>
<TabsTrigger value="market">Market</TabsTrigger> <TabsTrigger value="market">Market</TabsTrigger>
<TabsTrigger value="temple" disabled> <TabsTrigger value="temple">Temple</TabsTrigger>
Temple
</TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="fields"> <TabsContent value="fields">
<Field /> <Field />
@ -51,6 +50,9 @@ function App() {
<TabsContent value="market"> <TabsContent value="market">
<Market /> <Market />
</TabsContent> </TabsContent>
<TabsContent value="temple">
<Temple />
</TabsContent>
</Tabs> </Tabs>
<Console /> <Console />
</div> </div>

121
src/components/Temple.tsx Normal file
View file

@ -0,0 +1,121 @@
import React from 'react'
import { useGameStore } from '../store/useGameStore'
import { UPGRADES } from '../constants'
import { styled } from '@linaria/react'
import type { UpgradeCost } from '../types'
const TempleContainer = styled.div`
padding: 1rem;
`
const Title = styled.h2`
font-size: 1.5rem;
font-weight: bold;
margin-bottom: 1rem;
text-align: center;
`
const UpgradesGrid = styled.div`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 1rem;
margin-top: 1rem;
`
const UpgradeCard = styled.div<{ purchased?: boolean }>`
background-color: ${props => props.purchased ? '#e5e7eb' : '#f3f4f6'};
border: 1px solid #d1d5db;
border-radius: 0.5rem;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
`
const UpgradeName = styled.h3`
font-size: 1.25rem;
font-weight: 500;
margin: 0;
`
const UpgradeDescription = styled.p`
color: #4b5563;
margin: 0;
`
const UpgradeCost = styled.div`
display: flex;
flex-direction: column;
gap: 0.25rem;
margin-top: 0.5rem;
`
const CostItem = styled.div`
display: flex;
align-items: center;
gap: 0.5rem;
`
const PurchaseButton = styled.button<{ disabled?: boolean }>`
padding: 0.5rem 1rem;
border-radius: 0.25rem;
background-color: ${props => props.disabled ? '#9ca3af' : '#3b82f6'};
color: white;
font-weight: bold;
cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
border: none;
margin-top: auto;
`
const TempleComponent: React.FC = () => {
const { inventory, purchasedUpgrades, purchaseUpgrade } = useGameStore()
const handlePurchase = (upgradeId: string) => {
purchaseUpgrade(upgradeId)
}
const canAffordUpgrade = (costs: UpgradeCost[]) => {
return costs.every(cost =>
inventory[cost.itemId] !== undefined &&
inventory[cost.itemId] >= cost.amount
)
}
return (
<TempleContainer>
<Title>Temple</Title>
<UpgradesGrid>
{Object.values(UPGRADES).map((upgrade) => {
const isPurchased = purchasedUpgrades.includes(upgrade.id)
const canAfford = canAffordUpgrade(upgrade.cost)
return (
<UpgradeCard key={upgrade.id} purchased={isPurchased}>
<UpgradeName>{upgrade.name}</UpgradeName>
<UpgradeDescription>{upgrade.description}</UpgradeDescription>
<UpgradeCost>
<span>Cost:</span>
{upgrade.cost.map((cost, index) => (
<CostItem key={index}>
<span>{cost.amount} {cost.itemId}</span>
<span>
(You have: {inventory[cost.itemId] || 0})
</span>
</CostItem>
))}
</UpgradeCost>
<PurchaseButton
onClick={() => handlePurchase(upgrade.id)}
disabled={isPurchased || !canAfford}
>
{isPurchased ? 'Purchased' : 'Purchase'}
</PurchaseButton>
</UpgradeCard>
)
})}
</UpgradesGrid>
</TempleContainer>
)
}
export default TempleComponent

View file

@ -1,4 +1,4 @@
import { CropDefinitions } from '../types' import { CropDefinitions, Upgrade } from '../types'
export const INITIAL_CASH = 50 export const INITIAL_CASH = 50
export const INITIAL_FIELD_SIZE = 3 export const INITIAL_FIELD_SIZE = 3
@ -38,6 +38,18 @@ export const INITIAL_INVENTORY = {
celery_seed: 8, celery_seed: 8,
} }
export const UPGRADES: Record<string, Upgrade> = {
aqueous_vigor_1: {
id: 'aqueous_vigor_1',
name: 'Aqueous Vigor I',
description: 'Reduces watering cooldown by 25%',
cost: [{
itemId: 'celery',
amount: 10
}]
}
}
export interface MarketItem { export interface MarketItem {
id: string id: string
name: string name: string

View file

@ -9,6 +9,7 @@ import {
INITIAL_GAME_SPEED, INITIAL_GAME_SPEED,
COOLDOWN_DURATION, COOLDOWN_DURATION,
MARKET_ITEMS, MARKET_ITEMS,
UPGRADES,
} from '../constants' } from '../constants'
import { GameState, PlotState } from '../types' import { GameState, PlotState } from '../types'
@ -79,6 +80,7 @@ export const useGameStore = create<
sellItem: (itemId: string) => void sellItem: (itemId: string) => void
saveToSlot: (slot: number) => void saveToSlot: (slot: number) => void
loadFromSlot: (slot: number) => void loadFromSlot: (slot: number) => void
purchaseUpgrade: (upgradeId: string) => void
} }
>((set, get) => ({ >((set, get) => ({
cash: INITIAL_CASH, cash: INITIAL_CASH,
@ -91,6 +93,7 @@ export const useGameStore = create<
actionCooldown: 0, actionCooldown: 0,
tickCount: 0, tickCount: 0,
consoleMessages: [], consoleMessages: [],
purchasedUpgrades: [],
assignCrop: (row, col, cropId) => { assignCrop: (row, col, cropId) => {
set( set(
@ -129,7 +132,7 @@ export const useGameStore = create<
}, },
water: (row, col) => { water: (row, col) => {
const { plots, actionCooldown } = get() const { plots, actionCooldown, purchasedUpgrades } = get()
if (actionCooldown > 0) { if (actionCooldown > 0) {
return return
@ -137,10 +140,21 @@ export const useGameStore = create<
const plot = plots[row][col] const plot = plots[row][col]
if (plot.current && plot.moisture < 1) { if (plot.current && plot.moisture < 1) {
// Calculate cooldown reduction from upgrades
let cooldownReduction = 0
if (purchasedUpgrades.includes('aqueous_vigor_1')) {
cooldownReduction += 0.25 // 25% reduction
}
const finalCooldown = Math.max(
0,
COOLDOWN_DURATION * (1 - cooldownReduction)
)
set( set(
produce((state) => { produce((state) => {
state.plots[row][col].moisture = 1 state.plots[row][col].moisture = 1
state.actionCooldown = COOLDOWN_DURATION state.actionCooldown = finalCooldown
}), }),
) )
} }
@ -331,6 +345,7 @@ export const useGameStore = create<
sellItem, sellItem,
saveToSlot, saveToSlot,
loadFromSlot, loadFromSlot,
purchaseUpgrade,
...gameState ...gameState
} = state } = state
saveGame(slot, gameState) saveGame(slot, gameState)
@ -342,4 +357,36 @@ export const useGameStore = create<
set(savedState) set(savedState)
} }
}, },
purchaseUpgrade: (upgradeId) => {
const { inventory, purchasedUpgrades } = get()
const upgrade = UPGRADES[upgradeId]
if (!upgrade || purchasedUpgrades.includes(upgradeId)) {
return
}
// Check if player can afford all costs
const canAfford = upgrade.cost.every(
cost => inventory[cost.itemId] !== undefined && inventory[cost.itemId] >= cost.amount
)
if (!canAfford) {
return
}
set(
produce((state) => {
// Deduct all costs
upgrade.cost.forEach(cost => {
state.inventory[cost.itemId] -= cost.amount
})
state.purchasedUpgrades.push(upgradeId)
addConsoleMessage(
state,
`Purchased upgrade: ${upgrade.name}`
)
}),
)
},
})) }))

View file

@ -36,6 +36,18 @@ export interface ConsoleMessage {
timestamp: number timestamp: number
} }
export interface UpgradeCost {
itemId: string
amount: number
}
export interface Upgrade {
id: string
name: string
description: string
cost: UpgradeCost[]
}
export interface GameState { export interface GameState {
cash: number cash: number
inventory: Record<string, number> inventory: Record<string, number>
@ -46,6 +58,7 @@ export interface GameState {
gameSpeed: number gameSpeed: number
actionCooldown: number actionCooldown: number
consoleMessages: ConsoleMessage[] consoleMessages: ConsoleMessage[]
purchasedUpgrades: string[]
} }
export interface MarketTransaction { export interface MarketTransaction {