Compare commits
No commits in common. "b1bc592a28472ef35d313e9be6f1f70ad2faf2d8" and "453910282a039b0d8c393ea3cdd30fdf1a363374" have entirely different histories.
b1bc592a28
...
453910282a
|
|
@ -1,4 +1,4 @@
|
||||||
import { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
import { styled } from '@linaria/react'
|
import { styled } from '@linaria/react'
|
||||||
import { useGameTick } from './hooks/useGameTick'
|
import { useGameTick } from './hooks/useGameTick'
|
||||||
import Field from './components/Field'
|
import Field from './components/Field'
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { useGameStore } from '../store/useGameStore'
|
import { useGameStore } from '../store/useGameStore'
|
||||||
import { CROPS, ITEMS, secondsToTicks } from '../constants'
|
import { CROPS, ITEMS } from '../constants'
|
||||||
import { FieldTool, PlotState } from '../types'
|
import { FieldTool, PlotState } from '../types'
|
||||||
import Modal from './Modal'
|
import Modal from './Modal'
|
||||||
|
|
||||||
|
|
@ -105,15 +105,17 @@ const getMaturityProgressContainerStyle = (): React.CSSProperties => ({
|
||||||
|
|
||||||
const getMaturityProgressBarStyle = (
|
const getMaturityProgressBarStyle = (
|
||||||
progress: number,
|
progress: number,
|
||||||
cropId: string,
|
total: number,
|
||||||
): React.CSSProperties => ({
|
): React.CSSProperties => ({
|
||||||
width: `${Math.min(
|
position: 'absolute',
|
||||||
100,
|
bottom: 0,
|
||||||
(progress / secondsToTicks(CROPS[cropId].growthTimeSeconds)) * 100,
|
left: 0,
|
||||||
)}%`,
|
width: `${Math.min((progress / total) * 100, 100)}%`,
|
||||||
height: '100%',
|
height: '10px',
|
||||||
backgroundColor: '#22c55e',
|
backgroundColor: '#ff5722',
|
||||||
transition: 'width 0.5s ease-in-out',
|
transition: 'width 0.3s',
|
||||||
|
zIndex: 6,
|
||||||
|
boxShadow: '0 0 3px rgba(0, 0, 0, 0.5)',
|
||||||
})
|
})
|
||||||
|
|
||||||
const plotInfoStyle: React.CSSProperties = {
|
const plotInfoStyle: React.CSSProperties = {
|
||||||
|
|
@ -166,7 +168,7 @@ const PlotInfoModal: React.FC<PlotInfoModalProps> = ({ plot, onClose }) => {
|
||||||
<span>
|
<span>
|
||||||
{formatPercentage(
|
{formatPercentage(
|
||||||
plot.current.progress /
|
plot.current.progress /
|
||||||
secondsToTicks(CROPS[plot.current.cropId].growthTimeSeconds),
|
CROPS[plot.current.cropId].growthTicks,
|
||||||
)}
|
)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -322,7 +324,7 @@ const FieldComponent: React.FC = () => {
|
||||||
<div
|
<div
|
||||||
style={getMaturityProgressBarStyle(
|
style={getMaturityProgressBarStyle(
|
||||||
plot.current.progress,
|
plot.current.progress,
|
||||||
plot.current.cropId,
|
CROPS[plot.current.cropId].growthTicks,
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import {
|
import React, {
|
||||||
useState,
|
useState,
|
||||||
useImperativeHandle,
|
useImperativeHandle,
|
||||||
forwardRef,
|
forwardRef,
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,6 @@ const TempleComponent: React.FC = () => {
|
||||||
pray,
|
pray,
|
||||||
piety,
|
piety,
|
||||||
stamina,
|
stamina,
|
||||||
milestones,
|
|
||||||
} = useGameStore()
|
} = useGameStore()
|
||||||
|
|
||||||
const handlePurchase = (upgradeId: string) => {
|
const handlePurchase = (upgradeId: string) => {
|
||||||
|
|
@ -137,13 +136,44 @@ const TempleComponent: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if can afford
|
// Check if can afford
|
||||||
return canAffordUpgrade(upgrade.cost)
|
if (!canAffordUpgrade(upgrade.cost)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check requirements
|
||||||
|
if (
|
||||||
|
upgrade.id === 'aqueous_vigor_2' &&
|
||||||
|
!purchasedUpgrades.includes('aqueous_vigor_1')
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
upgrade.id === 'aqua_diffundere_2' &&
|
||||||
|
!purchasedUpgrades.includes('aqua_diffundere_1')
|
||||||
|
) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const getUpgradeStatus = (upgrade: Upgrade) => {
|
const getUpgradeStatus = (upgrade: Upgrade) => {
|
||||||
if (purchasedUpgrades.includes(upgrade.id)) {
|
if (purchasedUpgrades.includes(upgrade.id)) {
|
||||||
return 'Purchased'
|
return 'Purchased'
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
upgrade.id === 'aqueous_vigor_2' &&
|
||||||
|
!purchasedUpgrades.includes('aqueous_vigor_1')
|
||||||
|
) {
|
||||||
|
return 'Requires Aqueous Vigor I'
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
upgrade.id === 'aqua_diffundere_2' &&
|
||||||
|
!purchasedUpgrades.includes('aqua_diffundere_1')
|
||||||
|
) {
|
||||||
|
return 'Requires Aqua Diffundere I'
|
||||||
|
}
|
||||||
if (!canAffordUpgrade(upgrade.cost)) {
|
if (!canAffordUpgrade(upgrade.cost)) {
|
||||||
return 'Cannot Afford'
|
return 'Cannot Afford'
|
||||||
}
|
}
|
||||||
|
|
@ -163,9 +193,7 @@ const TempleComponent: React.FC = () => {
|
||||||
</PrayerSection>
|
</PrayerSection>
|
||||||
|
|
||||||
<UpgradesGrid>
|
<UpgradesGrid>
|
||||||
{Object.values(UPGRADES)
|
{Object.values(UPGRADES).map((upgrade) => {
|
||||||
.filter(upgrade => !upgrade.isAvailable || upgrade.isAvailable({ purchasedUpgrades, milestones }))
|
|
||||||
.map((upgrade) => {
|
|
||||||
const isPurchased = purchasedUpgrades.includes(upgrade.id)
|
const isPurchased = purchasedUpgrades.includes(upgrade.id)
|
||||||
const canPurchase = canPurchaseUpgrade(upgrade)
|
const canPurchase = canPurchaseUpgrade(upgrade)
|
||||||
const status = getUpgradeStatus(upgrade)
|
const status = getUpgradeStatus(upgrade)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import { CropDefinitions, Upgrade, Equipment } from '../types'
|
||||||
export const INITIAL_CASH = 50
|
export const INITIAL_CASH = 50
|
||||||
export const INITIAL_FIELD_SIZE = 3
|
export const INITIAL_FIELD_SIZE = 3
|
||||||
export const COOLDOWN_DURATION = 2000 // 2 seconds in milliseconds
|
export const COOLDOWN_DURATION = 2000 // 2 seconds in milliseconds
|
||||||
export const TICK_INTERVAL = 1000 // 1 second in milliseconds
|
export const TICK_INTERVAL = 5000
|
||||||
export const GAME_SPEEDS = {
|
export const GAME_SPEEDS = {
|
||||||
NORMAL: 1,
|
NORMAL: 1,
|
||||||
FAST: 5,
|
FAST: 5,
|
||||||
|
|
@ -13,17 +13,12 @@ export const INITIAL_GAME_SPEED = GAME_SPEEDS.NORMAL
|
||||||
|
|
||||||
export const FIELD_UPGRADE_COSTS = [100, 1000, 10000, 100000]
|
export const FIELD_UPGRADE_COSTS = [100, 1000, 10000, 100000]
|
||||||
|
|
||||||
// Helper function to convert seconds to ticks based on current tick interval
|
|
||||||
export const secondsToTicks = (seconds: number): number => {
|
|
||||||
return Math.ceil((seconds * 1000) / TICK_INTERVAL)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const CROPS: CropDefinitions = {
|
export const CROPS: CropDefinitions = {
|
||||||
celery: {
|
celery: {
|
||||||
id: 'celery',
|
id: 'celery',
|
||||||
name: 'Celery',
|
name: 'Celery',
|
||||||
growthTimeSeconds: 10,
|
growthTicks: 36,
|
||||||
waterPerSecond: 0.20, // 1/5 water per tick = 0.04 water per second
|
waterPerTick: 1 / 20,
|
||||||
yield: 1,
|
yield: 1,
|
||||||
yieldType: 'celery',
|
yieldType: 'celery',
|
||||||
fertilityDepletion: 0.1,
|
fertilityDepletion: 0.1,
|
||||||
|
|
@ -32,8 +27,8 @@ export const CROPS: CropDefinitions = {
|
||||||
corn: {
|
corn: {
|
||||||
id: 'corn',
|
id: 'corn',
|
||||||
name: 'Corn',
|
name: 'Corn',
|
||||||
growthTimeSeconds: 600,
|
growthTicks: 120,
|
||||||
waterPerSecond: 0.005, // 1/40 water per tick = 0.005 water per second
|
waterPerTick: 1 / 40,
|
||||||
yield: 5,
|
yield: 5,
|
||||||
yieldType: 'corn',
|
yieldType: 'corn',
|
||||||
fertilityDepletion: 0.3,
|
fertilityDepletion: 0.3,
|
||||||
|
|
@ -42,26 +37,26 @@ export const CROPS: CropDefinitions = {
|
||||||
olive: {
|
olive: {
|
||||||
id: 'olive',
|
id: 'olive',
|
||||||
name: 'Olive Tree',
|
name: 'Olive Tree',
|
||||||
growthTimeSeconds: 1000,
|
growthTicks: 200,
|
||||||
waterPerSecond: 0.004, // 1/50 water per tick = 0.004 water per second
|
waterPerTick: 1 / 50,
|
||||||
yield: 5,
|
yield: 5,
|
||||||
yieldType: 'olive',
|
yieldType: 'olive',
|
||||||
fertilityDepletion: 0.1,
|
fertilityDepletion: 0.1,
|
||||||
seedType: 'olive', // Uses olives as seeds
|
seedType: 'olive', // Uses olives as seeds
|
||||||
isPerennial: true,
|
isPerennial: true,
|
||||||
regrowthTimeSeconds: 750, // 150 ticks = 750 seconds
|
regrowthProgress: 150, // Resets to 150 ticks after harvest
|
||||||
},
|
},
|
||||||
grape_vine: {
|
grape_vine: {
|
||||||
id: 'grape_vine',
|
id: 'grape_vine',
|
||||||
name: 'Grape Vine',
|
name: 'Grape Vine',
|
||||||
growthTimeSeconds: 1250,
|
growthTicks: 250,
|
||||||
waterPerSecond: 0.00444, // 1/45 water per tick = 0.00444 water per second
|
waterPerTick: 1 / 45,
|
||||||
yield: 2,
|
yield: 2,
|
||||||
yieldType: 'grape',
|
yieldType: 'grape',
|
||||||
fertilityDepletion: 0.15,
|
fertilityDepletion: 0.15,
|
||||||
seedType: 'grape_seed',
|
seedType: 'grape_seed',
|
||||||
isPerennial: true,
|
isPerennial: true,
|
||||||
regrowthTimeSeconds: 1000, // 200 ticks = 1000 seconds
|
regrowthProgress: 200, // Resets to 200 ticks after harvest
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -70,50 +65,6 @@ export const INITIAL_INVENTORY = {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UPGRADES: Record<string, Upgrade> = {
|
export const UPGRADES: Record<string, Upgrade> = {
|
||||||
stamina_1: {
|
|
||||||
id: 'stamina_1',
|
|
||||||
name: 'Stamina I',
|
|
||||||
description: 'Increases maximum stamina by 5',
|
|
||||||
cost: [
|
|
||||||
{
|
|
||||||
itemId: 'celery',
|
|
||||||
amount: 20,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
stamina_2: {
|
|
||||||
id: 'stamina_2',
|
|
||||||
name: 'Stamina II',
|
|
||||||
description: 'Increases maximum stamina by 8',
|
|
||||||
cost: [
|
|
||||||
{
|
|
||||||
itemId: 'corn',
|
|
||||||
amount: 20,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
stamina_3: {
|
|
||||||
id: 'stamina_3',
|
|
||||||
name: 'Stamina III',
|
|
||||||
description: 'Increases maximum stamina by 12',
|
|
||||||
cost: [
|
|
||||||
{
|
|
||||||
itemId: 'olives',
|
|
||||||
amount: 100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
stamina_4: {
|
|
||||||
id: 'stamina_4',
|
|
||||||
name: 'Stamina IV',
|
|
||||||
description: 'Increases maximum stamina by 15',
|
|
||||||
cost: [
|
|
||||||
{
|
|
||||||
itemId: 'grapes',
|
|
||||||
amount: 100,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
aqueous_vigor_1: {
|
aqueous_vigor_1: {
|
||||||
id: 'aqueous_vigor_1',
|
id: 'aqueous_vigor_1',
|
||||||
name: 'Aqueous Vigor I',
|
name: 'Aqueous Vigor I',
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,6 @@ import {
|
||||||
ITEMS,
|
ITEMS,
|
||||||
UPGRADES,
|
UPGRADES,
|
||||||
EQUIPMENT,
|
EQUIPMENT,
|
||||||
TICK_INTERVAL,
|
|
||||||
secondsToTicks,
|
|
||||||
} from '../constants'
|
} from '../constants'
|
||||||
import { GameState, PlotState, Equipment } from '../types'
|
import { GameState, PlotState, Equipment } from '../types'
|
||||||
|
|
||||||
|
|
@ -66,10 +64,9 @@ const progressRandomImmaturePlot = (
|
||||||
const plot = state.plots[randomPlot.row][randomPlot.col]
|
const plot = state.plots[randomPlot.row][randomPlot.col]
|
||||||
|
|
||||||
const crop = CROPS[plot.current!.cropId]
|
const crop = CROPS[plot.current!.cropId]
|
||||||
const growthTicks = secondsToTicks(crop.growthTimeSeconds)
|
const actualGrowthAmount = crop.growthTicks * growthAmount
|
||||||
const actualGrowthAmount = growthTicks * growthAmount
|
|
||||||
plot.current!.progress = Math.min(
|
plot.current!.progress = Math.min(
|
||||||
growthTicks,
|
crop.growthTicks,
|
||||||
plot.current!.progress + actualGrowthAmount,
|
plot.current!.progress + actualGrowthAmount,
|
||||||
)
|
)
|
||||||
return true
|
return true
|
||||||
|
|
@ -225,10 +222,9 @@ export const useGameStore = create<
|
||||||
produce((state) => {
|
produce((state) => {
|
||||||
if (crop.isPerennial) {
|
if (crop.isPerennial) {
|
||||||
// For perennial crops, reset progress instead of clearing
|
// For perennial crops, reset progress instead of clearing
|
||||||
const regrowthTicks = secondsToTicks(crop.regrowthTimeSeconds!)
|
|
||||||
state.plots[row][col].current = {
|
state.plots[row][col].current = {
|
||||||
cropId: plot.current!.cropId,
|
cropId: plot.current!.cropId,
|
||||||
progress: regrowthTicks,
|
progress: crop.regrowthProgress!,
|
||||||
mature: false,
|
mature: false,
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -274,26 +270,42 @@ export const useGameStore = create<
|
||||||
set(
|
set(
|
||||||
produce((state) => {
|
produce((state) => {
|
||||||
// Regenerate stamina
|
// Regenerate stamina
|
||||||
state.stamina = Math.min(
|
if (state.stamina < state.maxStamina) {
|
||||||
state.maxStamina,
|
const oldStamina = state.stamina
|
||||||
state.stamina + 0.5,
|
state.stamina = Math.min(state.maxStamina, state.stamina + 1)
|
||||||
)
|
|
||||||
|
|
||||||
// Process each plot
|
// Increment milestone counter for stamina gained
|
||||||
|
if (state.stamina > oldStamina) {
|
||||||
|
const staminaGained = state.stamina - oldStamina
|
||||||
|
state.milestones.stamina_gained = (state.milestones.stamina_gained || 0) + staminaGained
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update plots
|
||||||
state.plots.forEach((row: PlotState[], rowIndex: number) => {
|
state.plots.forEach((row: PlotState[], rowIndex: number) => {
|
||||||
row.forEach((plot: PlotState, colIndex: number) => {
|
row.forEach((plot: PlotState, colIndex: number) => {
|
||||||
// Regenerate fertility every 100 ticks
|
// Regenerate fertility every 100 ticks
|
||||||
if (state.tickCount % 100 === 0) {
|
if (state.tickCount % 100 === 0 && plot.fertility < 1) {
|
||||||
state.plots[rowIndex][colIndex].fertility = Math.min(
|
state.plots[rowIndex][colIndex].fertility = Math.min(
|
||||||
1,
|
1,
|
||||||
plot.fertility + 0.01,
|
plot.fertility + 0.1,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plot.current && !plot.current.mature) {
|
if (!plot.current || plot.current.mature) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const crop = CROPS[plot.current.cropId]
|
const crop = CROPS[plot.current.cropId]
|
||||||
const growthTicks = secondsToTicks(crop.growthTimeSeconds)
|
const waterNeeded = crop.waterPerTick
|
||||||
const waterNeeded = crop.waterPerSecond * (TICK_INTERVAL / 1000)
|
|
||||||
|
// Check if water is running low (less than 25% of what's needed)
|
||||||
|
if (plot.moisture < waterNeeded * 0.25 && plot.moisture > 0) {
|
||||||
|
addConsoleMessage(
|
||||||
|
state,
|
||||||
|
`Plot (${rowIndex + 1},${colIndex + 1}) ${crop.name} is running low on water!`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Only grow if fertility is above 0.2
|
// Only grow if fertility is above 0.2
|
||||||
if (plot.moisture >= waterNeeded && plot.fertility >= 0.2) {
|
if (plot.moisture >= waterNeeded && plot.fertility >= 0.2) {
|
||||||
|
|
@ -304,7 +316,7 @@ export const useGameStore = create<
|
||||||
}
|
}
|
||||||
|
|
||||||
const newProgress = plot.current.progress + growthRate
|
const newProgress = plot.current.progress + growthRate
|
||||||
const mature = newProgress >= growthTicks
|
const mature = newProgress >= crop.growthTicks
|
||||||
|
|
||||||
state.plots[rowIndex][colIndex].moisture =
|
state.plots[rowIndex][colIndex].moisture =
|
||||||
plot.moisture - waterNeeded
|
plot.moisture - waterNeeded
|
||||||
|
|
@ -320,7 +332,6 @@ export const useGameStore = create<
|
||||||
|
|
||||||
state.plots[rowIndex][colIndex].current.mature = mature
|
state.plots[rowIndex][colIndex].current.mature = mature
|
||||||
}
|
}
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
@ -494,21 +505,6 @@ export const useGameStore = create<
|
||||||
})
|
})
|
||||||
state.purchasedUpgrades.push(upgradeId)
|
state.purchasedUpgrades.push(upgradeId)
|
||||||
addConsoleMessage(state, `Purchased upgrade: ${upgrade.name}`)
|
addConsoleMessage(state, `Purchased upgrade: ${upgrade.name}`)
|
||||||
|
|
||||||
// Handle stamina upgrades
|
|
||||||
if (upgradeId === 'stamina_1') {
|
|
||||||
state.maxStamina += 5
|
|
||||||
state.stamina = Math.min(state.maxStamina, state.stamina + 5)
|
|
||||||
} else if (upgradeId === 'stamina_2') {
|
|
||||||
state.maxStamina += 8
|
|
||||||
state.stamina = Math.min(state.maxStamina, state.stamina + 8)
|
|
||||||
} else if (upgradeId === 'stamina_3') {
|
|
||||||
state.maxStamina += 12
|
|
||||||
state.stamina = Math.min(state.maxStamina, state.stamina + 12)
|
|
||||||
} else if (upgradeId === 'stamina_4') {
|
|
||||||
state.maxStamina += 15
|
|
||||||
state.stamina = Math.min(state.maxStamina, state.stamina + 15)
|
|
||||||
}
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
export interface Crop {
|
export interface Crop {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
growthTimeSeconds: number
|
growthTicks: number
|
||||||
waterPerSecond: number
|
waterPerTick: number
|
||||||
yield: number
|
yield: number
|
||||||
yieldType: string
|
yieldType: string
|
||||||
fertilityDepletion: number
|
fertilityDepletion: number
|
||||||
seedType: string
|
seedType: string
|
||||||
isPerennial?: boolean
|
isPerennial?: boolean
|
||||||
regrowthTimeSeconds?: number
|
regrowthProgress?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CropDefinitions {
|
export interface CropDefinitions {
|
||||||
|
|
@ -48,7 +48,6 @@ export interface Upgrade {
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
cost: UpgradeCost[]
|
cost: UpgradeCost[]
|
||||||
isAvailable?: (state: Pick<GameState, 'purchasedUpgrades' | 'milestones'>) => boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Recipe {
|
export interface Recipe {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue