Remove the action cooldown system, implement a stamina regen system.
This commit is contained in:
parent
d5b950b541
commit
54b9ec842a
49
src/App.tsx
49
src/App.tsx
|
|
@ -1,31 +1,32 @@
|
||||||
import React, { useState, useEffect } from 'react'
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import { styled } from '@linaria/react'
|
||||||
|
import { useGameTick } from './hooks/useGameTick'
|
||||||
|
import Field from './components/Field'
|
||||||
|
import Warehouse from './components/Warehouse'
|
||||||
|
import Market from './components/Market'
|
||||||
|
import Temple from './components/Temple'
|
||||||
|
import { StaminaDisplay } from './components/StaminaDisplay'
|
||||||
|
import { useSaveSystem } from './store/useSaveSystem'
|
||||||
|
import { Console } from './components/Console'
|
||||||
|
import { GodModal } from './components/GodModal'
|
||||||
import {
|
import {
|
||||||
Tabs,
|
Tabs,
|
||||||
TabsContent,
|
TabsContent,
|
||||||
TabsList,
|
TabsList,
|
||||||
TabsTrigger,
|
TabsTrigger,
|
||||||
} from './components/CustomTabs'
|
} from './components/CustomTabs'
|
||||||
import { useGameTick } from './hooks/useGameTick'
|
|
||||||
import Field from './components/Field'
|
|
||||||
import Warehouse from './components/Warehouse'
|
|
||||||
import Market from './components/Market'
|
|
||||||
import Temple from './components/Temple'
|
|
||||||
import { ActionCooldown } from './components/ActionCooldown'
|
|
||||||
import { useSaveSystem } from './store/useSaveSystem'
|
|
||||||
import { Console } from './components/Console'
|
|
||||||
import { GodModal } from './components/GodModal'
|
|
||||||
|
|
||||||
const appContainerStyle: React.CSSProperties = {
|
const AppContainer = styled.div`
|
||||||
maxWidth: '1200px',
|
max-width: 1200px;
|
||||||
margin: '0 auto',
|
margin: 0 auto;
|
||||||
padding: '2rem',
|
padding: 4rem 2rem 2rem;
|
||||||
}
|
`
|
||||||
|
|
||||||
const tabsListStyles: React.CSSProperties = {
|
const StyledTabsList = styled(TabsList)`
|
||||||
display: 'grid',
|
display: grid;
|
||||||
width: '100%',
|
width: 100%;
|
||||||
gridTemplateColumns: 'repeat(4, 1fr)',
|
grid-template-columns: repeat(4, 1fr);
|
||||||
}
|
`
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
useGameTick()
|
useGameTick()
|
||||||
|
|
@ -57,15 +58,15 @@ function App() {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ActionCooldown />
|
<StaminaDisplay />
|
||||||
<div style={appContainerStyle}>
|
<AppContainer>
|
||||||
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
<Tabs value={activeTab} onValueChange={setActiveTab}>
|
||||||
<TabsList style={tabsListStyles}>
|
<StyledTabsList>
|
||||||
<TabsTrigger value="fields">Fields (1)</TabsTrigger>
|
<TabsTrigger value="fields">Fields (1)</TabsTrigger>
|
||||||
<TabsTrigger value="warehouse">Warehouse (2)</TabsTrigger>
|
<TabsTrigger value="warehouse">Warehouse (2)</TabsTrigger>
|
||||||
<TabsTrigger value="market">Market (3)</TabsTrigger>
|
<TabsTrigger value="market">Market (3)</TabsTrigger>
|
||||||
<TabsTrigger value="temple">Temple (4)</TabsTrigger>
|
<TabsTrigger value="temple">Temple (4)</TabsTrigger>
|
||||||
</TabsList>
|
</StyledTabsList>
|
||||||
<TabsContent value="fields">
|
<TabsContent value="fields">
|
||||||
<Field />
|
<Field />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
@ -80,7 +81,7 @@ function App() {
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
<Console />
|
<Console />
|
||||||
</div>
|
</AppContainer>
|
||||||
{isGodModalOpen && <GodModal onClose={() => setIsGodModalOpen(false)} />}
|
{isGodModalOpen && <GodModal onClose={() => setIsGodModalOpen(false)} />}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
import { useEffect, useState } from 'react'
|
|
||||||
import { styled } from '@linaria/react'
|
|
||||||
|
|
||||||
import { useGameStore } from '../store/useGameStore'
|
|
||||||
|
|
||||||
const CooldownContainer = styled.div`
|
|
||||||
position: fixed;
|
|
||||||
top: 1rem;
|
|
||||||
left: 1rem;
|
|
||||||
width: 48px;
|
|
||||||
height: 48px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
`
|
|
||||||
|
|
||||||
const DialSVG = styled.svg`
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
`
|
|
||||||
|
|
||||||
const DialBackground = styled.circle`
|
|
||||||
fill: #e5e7eb;
|
|
||||||
`
|
|
||||||
|
|
||||||
const DialProgress = styled.path`
|
|
||||||
fill: #3b82f6;
|
|
||||||
`
|
|
||||||
|
|
||||||
const calculatePieWedge = (progress: number, size: number) => {
|
|
||||||
// Convert progress to go counter-clockwise from top
|
|
||||||
const adjustedProgress = 100 - progress
|
|
||||||
const center = size / 2
|
|
||||||
const radius = size / 2 - 1
|
|
||||||
const angle = (adjustedProgress / 100) * 360
|
|
||||||
|
|
||||||
// Always start from top (90 degrees in SVG coordinates)
|
|
||||||
const startAngle = 90
|
|
||||||
const endAngle = startAngle + angle
|
|
||||||
|
|
||||||
// Convert angles to radians
|
|
||||||
const startAngleRad = (startAngle * Math.PI) / 180
|
|
||||||
const endAngleRad = (endAngle * Math.PI) / 180
|
|
||||||
|
|
||||||
// Calculate points
|
|
||||||
const startX = center + radius * Math.cos(startAngleRad)
|
|
||||||
const startY = center - radius * Math.sin(startAngleRad)
|
|
||||||
const endX = center + radius * Math.cos(endAngleRad)
|
|
||||||
const endY = center - radius * Math.sin(endAngleRad)
|
|
||||||
|
|
||||||
// Create the arc flag
|
|
||||||
const largeArcFlag = angle <= 180 ? '0' : '1'
|
|
||||||
|
|
||||||
// Create the SVG path - moving counter-clockwise
|
|
||||||
return `
|
|
||||||
M ${center},${center}
|
|
||||||
L ${startX},${startY}
|
|
||||||
A ${radius} ${radius} 0 ${largeArcFlag} 0 ${endX} ${endY}
|
|
||||||
L ${center},${center}
|
|
||||||
`.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ActionCooldown = () => {
|
|
||||||
const { actionCooldown, setActionCooldown } = useGameStore()
|
|
||||||
const [progress, setProgress] = useState(0)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (actionCooldown <= 0) {
|
|
||||||
setProgress(0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const startTime = Date.now()
|
|
||||||
const duration = actionCooldown
|
|
||||||
|
|
||||||
const updateProgress = () => {
|
|
||||||
const elapsed = Date.now() - startTime
|
|
||||||
const newProgress = Math.min(100, (elapsed / duration) * 100)
|
|
||||||
|
|
||||||
if (newProgress >= 100) {
|
|
||||||
setProgress(0)
|
|
||||||
setActionCooldown(0)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
setProgress(newProgress)
|
|
||||||
requestAnimationFrame(updateProgress)
|
|
||||||
}
|
|
||||||
|
|
||||||
const animationFrame = requestAnimationFrame(updateProgress)
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
cancelAnimationFrame(animationFrame)
|
|
||||||
}
|
|
||||||
}, [actionCooldown, setActionCooldown])
|
|
||||||
|
|
||||||
// Don't render if there's no active cooldown
|
|
||||||
if (actionCooldown <= 0) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CooldownContainer>
|
|
||||||
<DialSVG viewBox="0 0 32 32">
|
|
||||||
<DialBackground cx="16" cy="16" r="15" />
|
|
||||||
<DialProgress d={calculatePieWedge(progress, 32)} />
|
|
||||||
</DialSVG>
|
|
||||||
</CooldownContainer>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
@ -85,8 +85,8 @@ export const Tabs: React.FC<TabsProps> = ({ value, onValueChange, children }) =>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TabsList: React.FC<TabsListProps> = ({ children, style }) => {
|
export const TabsList: React.FC<TabsListProps> = ({ children, ...props }) => {
|
||||||
return <div style={{ ...tabsListContainerStyle, ...style }}>{children}</div>
|
return <div style={{ ...tabsListContainerStyle }} {...props}>{children}</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const TabsTrigger: React.FC<TabsTriggerProps> = ({
|
export const TabsTrigger: React.FC<TabsTriggerProps> = ({
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,7 @@ const RecipeDetails = styled.div`
|
||||||
`
|
`
|
||||||
|
|
||||||
const EquipmentModal = ({ equipmentId, onClose }: EquipmentModalProps) => {
|
const EquipmentModal = ({ equipmentId, onClose }: EquipmentModalProps) => {
|
||||||
const { inventory, configureEquipment, useEquipment, actionCooldown, equipment } = useGameStore()
|
const { inventory, configureEquipment, useEquipment, stamina, equipment } = useGameStore()
|
||||||
const equipmentInstance = equipment[equipmentId]
|
const equipmentInstance = equipment[equipmentId]
|
||||||
const equipmentDef = EQUIPMENT[equipmentInstance.type]
|
const equipmentDef = EQUIPMENT[equipmentInstance.type]
|
||||||
|
|
||||||
|
|
@ -99,7 +99,7 @@ const EquipmentModal = ({ equipmentId, onClose }: EquipmentModalProps) => {
|
||||||
|
|
||||||
const canUse =
|
const canUse =
|
||||||
!equipmentInstance.isProcessing &&
|
!equipmentInstance.isProcessing &&
|
||||||
actionCooldown === 0 &&
|
stamina >= 1 &&
|
||||||
selectedRecipe &&
|
selectedRecipe &&
|
||||||
(inventory[selectedRecipe.inputItem] || 0) >= selectedRecipe.inputAmount
|
(inventory[selectedRecipe.inputItem] || 0) >= selectedRecipe.inputAmount
|
||||||
|
|
||||||
|
|
@ -163,14 +163,14 @@ const EquipmentModal = ({ equipmentId, onClose }: EquipmentModalProps) => {
|
||||||
Output: {selectedRecipe.outputAmount} {ITEMS[selectedRecipe.outputItem].name}
|
Output: {selectedRecipe.outputAmount} {ITEMS[selectedRecipe.outputItem].name}
|
||||||
</p>
|
</p>
|
||||||
<p>Processing time: {selectedRecipe.processTicks} ticks</p>
|
<p>Processing time: {selectedRecipe.processTicks} ticks</p>
|
||||||
<p>Cooldown: {selectedRecipe.cooldownDuration / 1000} seconds</p>
|
<p>Stamina cost: 1</p>
|
||||||
</Section>
|
</Section>
|
||||||
|
|
||||||
<ActionButton
|
<ActionButton
|
||||||
disabled={!canUse}
|
disabled={!canUse}
|
||||||
onClick={handleUse}
|
onClick={handleUse}
|
||||||
>
|
>
|
||||||
{equipmentInstance.isProcessing ? 'Processing...' : 'Use'}
|
{equipmentInstance.isProcessing ? 'Processing...' : stamina < 1 ? 'No Stamina' : 'Use'}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,7 @@ const FieldComponent: React.FC = () => {
|
||||||
harvest,
|
harvest,
|
||||||
remove,
|
remove,
|
||||||
inventory,
|
inventory,
|
||||||
actionCooldown,
|
stamina,
|
||||||
} = useGameStore()
|
} = useGameStore()
|
||||||
|
|
||||||
const [selectedSeed, setSelectedSeed] = useState<string | null>(null)
|
const [selectedSeed, setSelectedSeed] = useState<string | null>(null)
|
||||||
|
|
@ -199,7 +199,7 @@ const FieldComponent: React.FC = () => {
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
|
|
||||||
const handlePlotClick = (row: number, col: number) => {
|
const handlePlotClick = (row: number, col: number) => {
|
||||||
if (actionCooldown > 0) return
|
if (stamina < 1) return
|
||||||
|
|
||||||
switch (selectedTool) {
|
switch (selectedTool) {
|
||||||
case 'plant':
|
case 'plant':
|
||||||
|
|
|
||||||
66
src/components/StaminaDisplay.tsx
Normal file
66
src/components/StaminaDisplay.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { styled } from '@linaria/react'
|
||||||
|
import { useGameStore } from '../store/useGameStore'
|
||||||
|
|
||||||
|
const StaminaContainer = styled.div`
|
||||||
|
position: fixed;
|
||||||
|
top: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
width: 200px;
|
||||||
|
height: 24px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: left;
|
||||||
|
background-color: #4b5563; /* Grey for depleted stamina */
|
||||||
|
border: 3px solid #000;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 2px 2px 0 rgba(0, 0, 0, 0.2);
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 2;
|
||||||
|
`
|
||||||
|
|
||||||
|
const StaminaFill = styled.div<{ width: number }>`
|
||||||
|
height: 100%;
|
||||||
|
width: ${props => props.width + '%'};
|
||||||
|
background-color: #22c55e;
|
||||||
|
transition: width 0.2s linear;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
rgba(255, 255, 255, 0.2) 0%,
|
||||||
|
rgba(255, 255, 255, 0) 50%,
|
||||||
|
rgba(0, 0, 0, 0.1) 100%
|
||||||
|
);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
const StaminaText = styled.div`
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
text-align: center;
|
||||||
|
color: #fff;
|
||||||
|
font-weight: bold;
|
||||||
|
text-shadow: 1px 1px 0 #000;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
z-index: 1;
|
||||||
|
`
|
||||||
|
|
||||||
|
export const StaminaDisplay = () => {
|
||||||
|
const { stamina, maxStamina } = useGameStore()
|
||||||
|
const staminaPercentage = (stamina / maxStamina) * 100
|
||||||
|
|
||||||
|
return (
|
||||||
|
<StaminaContainer>
|
||||||
|
<StaminaFill width={staminaPercentage} />
|
||||||
|
<StaminaText>
|
||||||
|
{stamina}/{maxStamina}
|
||||||
|
</StaminaText>
|
||||||
|
</StaminaContainer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -114,7 +114,7 @@ const TempleComponent: React.FC = () => {
|
||||||
purchaseUpgrade,
|
purchaseUpgrade,
|
||||||
pray,
|
pray,
|
||||||
piety,
|
piety,
|
||||||
actionCooldown,
|
stamina,
|
||||||
} = useGameStore()
|
} = useGameStore()
|
||||||
|
|
||||||
const handlePurchase = (upgradeId: string) => {
|
const handlePurchase = (upgradeId: string) => {
|
||||||
|
|
@ -186,9 +186,9 @@ const TempleComponent: React.FC = () => {
|
||||||
|
|
||||||
<PrayerSection>
|
<PrayerSection>
|
||||||
<PietyDisplay>Piety Level: {piety}</PietyDisplay>
|
<PietyDisplay>Piety Level: {piety}</PietyDisplay>
|
||||||
<PrayerButton onClick={() => pray()} disabled={actionCooldown > 0}>
|
<PrayerButton onClick={() => pray()} disabled={stamina < 1}>
|
||||||
🙏 Pray to the Gods
|
🙏 Pray to the Gods
|
||||||
{actionCooldown > 0 && ` (${(actionCooldown / 1000).toFixed(1)}s)`}
|
{stamina < 1 && ' (No Stamina)'}
|
||||||
</PrayerButton>
|
</PrayerButton>
|
||||||
</PrayerSection>
|
</PrayerSection>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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 = 12000 // 12 seconds in milliseconds
|
export const TICK_INTERVAL = 5000
|
||||||
export const GAME_SPEEDS = {
|
export const GAME_SPEEDS = {
|
||||||
NORMAL: 1,
|
NORMAL: 1,
|
||||||
FAST: 5,
|
FAST: 5,
|
||||||
|
|
@ -68,7 +68,7 @@ export const UPGRADES: Record<string, Upgrade> = {
|
||||||
aqueous_vigor_1: {
|
aqueous_vigor_1: {
|
||||||
id: 'aqueous_vigor_1',
|
id: 'aqueous_vigor_1',
|
||||||
name: 'Aqueous Vigor I',
|
name: 'Aqueous Vigor I',
|
||||||
description: 'Reduces watering cooldown by 25%',
|
description: 'Reduces watering stamina cost by 25%',
|
||||||
cost: [
|
cost: [
|
||||||
{
|
{
|
||||||
itemId: 'celery',
|
itemId: 'celery',
|
||||||
|
|
@ -80,7 +80,7 @@ export const UPGRADES: Record<string, Upgrade> = {
|
||||||
id: 'aqueous_vigor_2',
|
id: 'aqueous_vigor_2',
|
||||||
name: 'Aqueous Vigor II',
|
name: 'Aqueous Vigor II',
|
||||||
description:
|
description:
|
||||||
'Reduces watering cooldown by an additional 25% (requires Aqueous Vigor I)',
|
'Reduces watering stamina cost by an additional 25% (requires Aqueous Vigor I)',
|
||||||
cost: [
|
cost: [
|
||||||
{
|
{
|
||||||
itemId: 'celery',
|
itemId: 'celery',
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import {
|
||||||
INITIAL_INVENTORY,
|
INITIAL_INVENTORY,
|
||||||
CROPS,
|
CROPS,
|
||||||
INITIAL_GAME_SPEED,
|
INITIAL_GAME_SPEED,
|
||||||
COOLDOWN_DURATION,
|
|
||||||
ITEMS,
|
ITEMS,
|
||||||
UPGRADES,
|
UPGRADES,
|
||||||
EQUIPMENT,
|
EQUIPMENT,
|
||||||
|
|
@ -29,27 +28,6 @@ const initializeField = (size: number): PlotState[][] => {
|
||||||
|
|
||||||
const SAVE_SLOT_PREFIX = 'dionysian_idle_save_'
|
const SAVE_SLOT_PREFIX = 'dionysian_idle_save_'
|
||||||
|
|
||||||
const saveGame = (slot: number, state: GameState) => {
|
|
||||||
try {
|
|
||||||
const saveData = JSON.stringify(state)
|
|
||||||
localStorage.setItem(`${SAVE_SLOT_PREFIX}${slot}`, saveData)
|
|
||||||
return true
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to save game:', error)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const loadGame = (slot: number): GameState | null => {
|
|
||||||
try {
|
|
||||||
const saveData = localStorage.getItem(`${SAVE_SLOT_PREFIX}${slot}`)
|
|
||||||
if (!saveData) return null
|
|
||||||
return JSON.parse(saveData)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load game:', error)
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const hasSaveInSlot = (slot: number): boolean => {
|
export const hasSaveInSlot = (slot: number): boolean => {
|
||||||
return !!localStorage.getItem(`${SAVE_SLOT_PREFIX}${slot}`)
|
return !!localStorage.getItem(`${SAVE_SLOT_PREFIX}${slot}`)
|
||||||
|
|
@ -105,7 +83,6 @@ export const useGameStore = create<
|
||||||
tick: () => void
|
tick: () => void
|
||||||
upgradeField: () => void
|
upgradeField: () => void
|
||||||
setGameSpeed: (speed: number) => void
|
setGameSpeed: (speed: number) => void
|
||||||
setActionCooldown: (cooldown: number) => void
|
|
||||||
buyItem: (itemId: string) => void
|
buyItem: (itemId: string) => void
|
||||||
sellItem: (itemId: string) => void
|
sellItem: (itemId: string) => void
|
||||||
purchaseUpgrade: (upgradeId: string) => void
|
purchaseUpgrade: (upgradeId: string) => void
|
||||||
|
|
@ -130,7 +107,8 @@ export const useGameStore = create<
|
||||||
fieldUpgradeCosts: FIELD_UPGRADE_COSTS,
|
fieldUpgradeCosts: FIELD_UPGRADE_COSTS,
|
||||||
plots: initializeField(INITIAL_FIELD_SIZE),
|
plots: initializeField(INITIAL_FIELD_SIZE),
|
||||||
gameSpeed: INITIAL_GAME_SPEED,
|
gameSpeed: INITIAL_GAME_SPEED,
|
||||||
actionCooldown: 0,
|
stamina: 10,
|
||||||
|
maxStamina: 10,
|
||||||
tickCount: 0,
|
tickCount: 0,
|
||||||
consoleMessages: [],
|
consoleMessages: [],
|
||||||
purchasedUpgrades: [],
|
purchasedUpgrades: [],
|
||||||
|
|
@ -140,9 +118,9 @@ export const useGameStore = create<
|
||||||
milestones: {},
|
milestones: {},
|
||||||
|
|
||||||
plant: (row, col, cropId) => {
|
plant: (row, col, cropId) => {
|
||||||
const { plots, inventory, actionCooldown } = get()
|
const { plots, inventory, stamina } = get()
|
||||||
|
|
||||||
if (actionCooldown > 0) {
|
if (stamina < 1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,36 +138,36 @@ export const useGameStore = create<
|
||||||
}
|
}
|
||||||
|
|
||||||
state.inventory[seedId] = state.inventory[seedId] - 1
|
state.inventory[seedId] = state.inventory[seedId] - 1
|
||||||
state.actionCooldown = COOLDOWN_DURATION
|
state.stamina = Math.max(0, state.stamina - 1)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
water: (row, col) => {
|
water: (row, col) => {
|
||||||
const { plots, actionCooldown, purchasedUpgrades } = get()
|
const { plots, stamina, purchasedUpgrades } = get()
|
||||||
|
|
||||||
if (actionCooldown > 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
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 (multiplicative)
|
// Calculate stamina cost reduction from upgrades (multiplicative)
|
||||||
let cooldownMultiplier = 1
|
let staminaMultiplier = 1
|
||||||
if (purchasedUpgrades.includes('aqueous_vigor_1')) {
|
if (purchasedUpgrades.includes('aqueous_vigor_1')) {
|
||||||
cooldownMultiplier *= 0.75 // 25% reduction
|
staminaMultiplier *= 0.75 // 25% reduction
|
||||||
}
|
}
|
||||||
if (purchasedUpgrades.includes('aqueous_vigor_2')) {
|
if (purchasedUpgrades.includes('aqueous_vigor_2')) {
|
||||||
cooldownMultiplier *= 0.75 // Additional 25% reduction
|
staminaMultiplier *= 0.75 // Additional 25% reduction
|
||||||
}
|
}
|
||||||
|
|
||||||
const finalCooldown = Math.max(0, COOLDOWN_DURATION * cooldownMultiplier)
|
const staminaCost = Math.max(1, Math.floor(staminaMultiplier))
|
||||||
|
|
||||||
|
if (stamina < staminaCost) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
set(
|
set(
|
||||||
produce((state) => {
|
produce((state) => {
|
||||||
state.plots[row][col].moisture = 1
|
state.plots[row][col].moisture = 1
|
||||||
state.actionCooldown = finalCooldown
|
state.stamina = Math.max(0, state.stamina - staminaCost)
|
||||||
|
|
||||||
// Handle water diffusion to adjacent plots
|
// Handle water diffusion to adjacent plots
|
||||||
const diffusionAmount = purchasedUpgrades.includes('aqua_diffundere_2')
|
const diffusionAmount = purchasedUpgrades.includes('aqua_diffundere_2')
|
||||||
|
|
@ -199,26 +177,24 @@ export const useGameStore = create<
|
||||||
: 0
|
: 0
|
||||||
|
|
||||||
if (diffusionAmount > 0) {
|
if (diffusionAmount > 0) {
|
||||||
// Check and water adjacent plots (up, right, down, left)
|
// Apply diffusion to adjacent plots
|
||||||
const adjacentPositions = [
|
const adjacentCoords = [
|
||||||
[row - 1, col], // up
|
[row - 1, col],
|
||||||
[row, col + 1], // right
|
[row + 1, col],
|
||||||
[row + 1, col], // down
|
[row, col - 1],
|
||||||
[row, col - 1], // left
|
[row, col + 1],
|
||||||
]
|
]
|
||||||
|
|
||||||
adjacentPositions.forEach(([adjRow, adjCol]) => {
|
adjacentCoords.forEach(([r, c]) => {
|
||||||
// Check if the adjacent position is within bounds
|
|
||||||
if (
|
if (
|
||||||
adjRow >= 0 &&
|
r >= 0 &&
|
||||||
adjRow < state.plots.length &&
|
r < state.plots.length &&
|
||||||
adjCol >= 0 &&
|
c >= 0 &&
|
||||||
adjCol < state.plots[0].length
|
c < state.plots[0].length
|
||||||
) {
|
) {
|
||||||
const adjPlot = state.plots[adjRow][adjCol]
|
state.plots[r][c].moisture = Math.min(
|
||||||
state.plots[adjRow][adjCol].moisture = Math.min(
|
|
||||||
1,
|
1,
|
||||||
adjPlot.moisture + diffusionAmount
|
state.plots[r][c].moisture + diffusionAmount,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
@ -229,9 +205,9 @@ export const useGameStore = create<
|
||||||
},
|
},
|
||||||
|
|
||||||
harvest: (row, col) => {
|
harvest: (row, col) => {
|
||||||
const { plots, actionCooldown } = get()
|
const { plots, stamina } = get()
|
||||||
|
|
||||||
if (actionCooldown > 0) {
|
if (stamina < 1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -261,7 +237,7 @@ export const useGameStore = create<
|
||||||
0,
|
0,
|
||||||
state.plots[row][col].fertility - crop.fertilityDepletion,
|
state.plots[row][col].fertility - crop.fertilityDepletion,
|
||||||
)
|
)
|
||||||
state.actionCooldown = COOLDOWN_DURATION
|
state.stamina = Math.max(0, state.stamina - 1)
|
||||||
|
|
||||||
// Track milestone for crop harvest
|
// Track milestone for crop harvest
|
||||||
const milestoneKey = `crops_harvested_${crop.id}`
|
const milestoneKey = `crops_harvested_${crop.id}`
|
||||||
|
|
@ -272,9 +248,9 @@ export const useGameStore = create<
|
||||||
},
|
},
|
||||||
|
|
||||||
remove: (row, col) => {
|
remove: (row, col) => {
|
||||||
const { plots, actionCooldown } = get()
|
const { plots, stamina } = get()
|
||||||
|
|
||||||
if (actionCooldown > 0) {
|
if (stamina < 1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -283,7 +259,7 @@ export const useGameStore = create<
|
||||||
set(
|
set(
|
||||||
produce((state) => {
|
produce((state) => {
|
||||||
state.plots[row][col].current = undefined
|
state.plots[row][col].current = undefined
|
||||||
state.actionCooldown = COOLDOWN_DURATION
|
state.stamina = Math.max(0, state.stamina - 1)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -292,6 +268,18 @@ export const useGameStore = create<
|
||||||
tick: () => {
|
tick: () => {
|
||||||
set(
|
set(
|
||||||
produce((state) => {
|
produce((state) => {
|
||||||
|
// Regenerate stamina
|
||||||
|
if (state.stamina < state.maxStamina) {
|
||||||
|
const oldStamina = state.stamina
|
||||||
|
state.stamina = Math.min(state.maxStamina, state.stamina + 1)
|
||||||
|
|
||||||
|
// 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
|
// 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) => {
|
||||||
|
|
@ -412,14 +400,6 @@ export const useGameStore = create<
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
||||||
setActionCooldown: (cooldown) => {
|
|
||||||
set(
|
|
||||||
produce((state) => {
|
|
||||||
state.actionCooldown = cooldown
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
buyItem: (itemId) => {
|
buyItem: (itemId) => {
|
||||||
const { cash, landPurchases } = get()
|
const { cash, landPurchases } = get()
|
||||||
const item = ITEMS[itemId]
|
const item = ITEMS[itemId]
|
||||||
|
|
@ -529,9 +509,9 @@ export const useGameStore = create<
|
||||||
},
|
},
|
||||||
|
|
||||||
pray: () => {
|
pray: () => {
|
||||||
const { actionCooldown, piety } = get()
|
const { stamina, piety } = get()
|
||||||
|
|
||||||
if (actionCooldown > 0) {
|
if (stamina < 1) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -562,7 +542,7 @@ export const useGameStore = create<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state.actionCooldown = 3000
|
state.stamina = Math.max(0, state.stamina - 1)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
@ -614,13 +594,13 @@ export const useGameStore = create<
|
||||||
},
|
},
|
||||||
|
|
||||||
useEquipment: (equipmentId) => {
|
useEquipment: (equipmentId) => {
|
||||||
const { equipment, inventory, actionCooldown } = get()
|
const { equipment, inventory, stamina } = get()
|
||||||
const equipmentInstance = equipment[equipmentId]
|
const equipmentInstance = equipment[equipmentId]
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!equipmentInstance ||
|
!equipmentInstance ||
|
||||||
equipmentInstance.isProcessing ||
|
equipmentInstance.isProcessing ||
|
||||||
actionCooldown > 0 ||
|
stamina < 1 ||
|
||||||
!equipmentInstance.selectedRecipeId
|
!equipmentInstance.selectedRecipeId
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
|
|
@ -645,7 +625,7 @@ export const useGameStore = create<
|
||||||
// Start processing
|
// Start processing
|
||||||
state.equipment[equipmentId].isProcessing = true
|
state.equipment[equipmentId].isProcessing = true
|
||||||
state.equipment[equipmentId].progress = 0
|
state.equipment[equipmentId].progress = 0
|
||||||
state.actionCooldown = recipe.cooldownDuration
|
state.stamina = Math.max(0, state.stamina - 1)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -85,7 +85,8 @@ export interface GameState {
|
||||||
fieldUpgradeCosts: number[]
|
fieldUpgradeCosts: number[]
|
||||||
plots: PlotState[][]
|
plots: PlotState[][]
|
||||||
gameSpeed: number
|
gameSpeed: number
|
||||||
actionCooldown: number
|
stamina: number
|
||||||
|
maxStamina: number
|
||||||
consoleMessages: ConsoleMessage[]
|
consoleMessages: ConsoleMessage[]
|
||||||
purchasedUpgrades: string[]
|
purchasedUpgrades: string[]
|
||||||
piety: number
|
piety: number
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue