Get Linaria actually working, add global cooldown for actions.
This commit is contained in:
parent
974d851767
commit
6408fcd199
71
package-lock.json
generated
71
package-lock.json
generated
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "idle-farm-game",
|
||||
"name": "dionysian-idle",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "idle-farm-game",
|
||||
"name": "dionysian-idle",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@hookform/resolvers": "^5.0.1",
|
||||
|
|
@ -70,6 +70,7 @@
|
|||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"@wyw-in-js/vite": "^0.6.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.15.0",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
|
|
@ -4863,6 +4864,72 @@
|
|||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@wyw-in-js/transform": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@wyw-in-js/transform/-/transform-0.6.0.tgz",
|
||||
"integrity": "sha512-/Rxw8e3KcmSjnqWs/6flmpcNTa4jLw8NViIyNwqem498AfSWXrPnYf3RyjWed9zDVL72q7Xjei6TcRqMupl3Eg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/core": "^7.23.5",
|
||||
"@babel/generator": "^7.23.5",
|
||||
"@babel/helper-module-imports": "^7.22.15",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.23.3",
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/traverse": "^7.23.5",
|
||||
"@babel/types": "^7.23.5",
|
||||
"@wyw-in-js/processor-utils": "0.6.0",
|
||||
"@wyw-in-js/shared": "0.6.0",
|
||||
"babel-merge": "^3.0.0",
|
||||
"cosmiconfig": "^8.0.0",
|
||||
"happy-dom": "^15.11.0",
|
||||
"source-map": "^0.7.4",
|
||||
"stylis": "^4.3.0",
|
||||
"ts-invariant": "^0.10.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wyw-in-js/transform/node_modules/happy-dom": {
|
||||
"version": "15.11.7",
|
||||
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-15.11.7.tgz",
|
||||
"integrity": "sha512-KyrFvnl+J9US63TEzwoiJOQzZBJY7KgBushJA8X61DMbNsH+2ONkDuLDnCnwUiPTF42tLoEmrPyoqbenVA5zrg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^4.5.0",
|
||||
"webidl-conversions": "^7.0.0",
|
||||
"whatwg-mimetype": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@wyw-in-js/transform/node_modules/stylis": {
|
||||
"version": "4.3.6",
|
||||
"resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.6.tgz",
|
||||
"integrity": "sha512-yQ3rwFWRfwNUY7H5vpU0wfdkNSnvnJinhF9830Swlaxl03zsOjCfmX0ugac+3LtK0lYSgwL/KXc8oYL3mG4YFQ==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@wyw-in-js/vite": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@wyw-in-js/vite/-/vite-0.6.0.tgz",
|
||||
"integrity": "sha512-nGeH/dFrjecHRJfxd5/71cJu4V2YWakUmnEoAUpS0iTnVVC4GWjKqt1dLtsJvtKZIm2OeNoTzzdFrP4bxyXYxQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@wyw-in-js/shared": "0.6.0",
|
||||
"@wyw-in-js/transform": "0.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"vite": ">=3.2.7"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.14.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz",
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@
|
|||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"@wyw-in-js/vite": "^0.6.0",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.15.0",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
|
|
|
|||
54
src/App.tsx
54
src/App.tsx
|
|
@ -3,6 +3,7 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from './components/CustomTab
|
|||
import { useGameTick } from './hooks/useGameTick';
|
||||
import Field from './components/Field';
|
||||
import Warehouse from './components/Warehouse';
|
||||
import { ActionCooldown } from './components/ActionCooldown';
|
||||
|
||||
const appContainerStyle: React.CSSProperties = {
|
||||
maxWidth: '1200px',
|
||||
|
|
@ -10,21 +11,6 @@ const appContainerStyle: React.CSSProperties = {
|
|||
padding: '2rem'
|
||||
};
|
||||
|
||||
const headerStyle: React.CSSProperties = {
|
||||
textAlign: 'center',
|
||||
marginBottom: '2rem'
|
||||
};
|
||||
|
||||
const titleStyle: React.CSSProperties = {
|
||||
fontSize: '2.25rem',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '0.5rem'
|
||||
};
|
||||
|
||||
const subtitleStyle: React.CSSProperties = {
|
||||
color: '#6b7280'
|
||||
};
|
||||
|
||||
const tabsListStyles: React.CSSProperties = {
|
||||
display: 'grid',
|
||||
width: '100%',
|
||||
|
|
@ -35,26 +21,24 @@ function App() {
|
|||
useGameTick();
|
||||
|
||||
return (
|
||||
<div style={appContainerStyle}>
|
||||
<header style={headerStyle}>
|
||||
<h1 style={titleStyle}>Idle Farm Game</h1>
|
||||
<p style={subtitleStyle}>Grow crops, harvest, and expand your farm!</p>
|
||||
</header>
|
||||
|
||||
<Tabs defaultValue="fields">
|
||||
<TabsList style={tabsListStyles}>
|
||||
<TabsTrigger value="fields">Fields</TabsTrigger>
|
||||
<TabsTrigger value="warehouse">Warehouse</TabsTrigger>
|
||||
<TabsTrigger value="market" disabled>Market</TabsTrigger>
|
||||
<TabsTrigger value="temple" disabled>Temple</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="fields">
|
||||
<Field />
|
||||
</TabsContent>
|
||||
<TabsContent value="warehouse">
|
||||
<Warehouse />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
<div>
|
||||
<ActionCooldown />
|
||||
<div style={appContainerStyle}>
|
||||
<Tabs defaultValue="fields">
|
||||
<TabsList style={tabsListStyles}>
|
||||
<TabsTrigger value="fields">Fields</TabsTrigger>
|
||||
<TabsTrigger value="warehouse">Warehouse</TabsTrigger>
|
||||
<TabsTrigger value="market" disabled>Market</TabsTrigger>
|
||||
<TabsTrigger value="temple" disabled>Temple</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="fields">
|
||||
<Field />
|
||||
</TabsContent>
|
||||
<TabsContent value="warehouse">
|
||||
<Warehouse />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
59
src/components/ActionCooldown.tsx
Normal file
59
src/components/ActionCooldown.tsx
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
import { useEffect, useState } from 'react';
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { useGameStore } from '../store/useGameStore';
|
||||
|
||||
const CooldownContainer = styled.div`
|
||||
position: relative;
|
||||
height: 1rem;
|
||||
width: 100%;
|
||||
background-color: #e5e7eb;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const ProgressBar = styled.div<{ progress: string }>`
|
||||
height: 100%;
|
||||
background-color: #3b82f6;
|
||||
width: ${props => props.progress};
|
||||
`;
|
||||
|
||||
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]);
|
||||
|
||||
return (
|
||||
<CooldownContainer>
|
||||
<ProgressBar progress={`${progress}%`} />
|
||||
</CooldownContainer>
|
||||
);
|
||||
};
|
||||
|
|
@ -148,32 +148,12 @@ const FieldComponent: React.FC = () => {
|
|||
water,
|
||||
harvest,
|
||||
assignCrop,
|
||||
lastPlantTime,
|
||||
lastWaterTime,
|
||||
lastHarvestTime,
|
||||
gameSpeed,
|
||||
setGameSpeed
|
||||
setGameSpeed,
|
||||
actionCooldown
|
||||
} = useGameStore();
|
||||
|
||||
const [selectedCrop, setSelectedCrop] = useState<string | null>(null);
|
||||
const [cooldowns, setCooldowns] = useState({
|
||||
plant: false,
|
||||
water: false,
|
||||
harvest: false
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
const now = Date.now();
|
||||
setCooldowns({
|
||||
plant: now - lastPlantTime < 2000,
|
||||
water: now - lastWaterTime < 2000,
|
||||
harvest: now - lastHarvestTime < 2000
|
||||
});
|
||||
}, 100);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}, [lastPlantTime, lastWaterTime, lastHarvestTime]);
|
||||
|
||||
const handlePlotClick = (row: number, col: number) => {
|
||||
if (selectedCrop) {
|
||||
|
|
@ -187,6 +167,8 @@ const FieldComponent: React.FC = () => {
|
|||
return "#92400e";
|
||||
};
|
||||
|
||||
const availableSpeeds = [1, 2, 4, 8, 16, 32, 64]
|
||||
|
||||
return (
|
||||
<div style={fieldContainerStyle}>
|
||||
<h2 style={titleStyle}>Fields</h2>
|
||||
|
|
@ -194,24 +176,15 @@ const FieldComponent: React.FC = () => {
|
|||
<div style={speedSelectorContainerStyle}>
|
||||
<p style={speedSelectorLabelStyle}>Game Speed:</p>
|
||||
<div style={speedButtonsContainerStyle}>
|
||||
<button
|
||||
style={getSpeedButtonStyle(gameSpeed === 1)}
|
||||
onClick={() => setGameSpeed(1)}
|
||||
>
|
||||
1x
|
||||
</button>
|
||||
<button
|
||||
style={getSpeedButtonStyle(gameSpeed === 5)}
|
||||
onClick={() => setGameSpeed(5)}
|
||||
>
|
||||
5x
|
||||
</button>
|
||||
<button
|
||||
style={getSpeedButtonStyle(gameSpeed === 10)}
|
||||
onClick={() => setGameSpeed(10)}
|
||||
>
|
||||
10x
|
||||
</button>
|
||||
{availableSpeeds.map(speed => (
|
||||
<button
|
||||
style={getSpeedButtonStyle(gameSpeed === speed)}
|
||||
onClick={() => setGameSpeed(speed)}
|
||||
key={speed}
|
||||
>
|
||||
{speed}x
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -230,23 +203,23 @@ const FieldComponent: React.FC = () => {
|
|||
|
||||
<div style={actionsContainerStyle}>
|
||||
<button
|
||||
style={getActionButtonStyle(cooldowns.plant)}
|
||||
style={getActionButtonStyle(actionCooldown > 0)}
|
||||
onClick={plant}
|
||||
disabled={cooldowns.plant}
|
||||
disabled={actionCooldown > 0}
|
||||
>
|
||||
Plant Crop
|
||||
</button>
|
||||
<button
|
||||
style={getActionButtonStyle(cooldowns.water)}
|
||||
style={getActionButtonStyle(actionCooldown > 0)}
|
||||
onClick={water}
|
||||
disabled={cooldowns.water}
|
||||
disabled={actionCooldown > 0}
|
||||
>
|
||||
Water Crop
|
||||
</button>
|
||||
<button
|
||||
style={getActionButtonStyle(cooldowns.harvest)}
|
||||
style={getActionButtonStyle(actionCooldown > 0)}
|
||||
onClick={harvest}
|
||||
disabled={cooldowns.harvest}
|
||||
disabled={actionCooldown > 0}
|
||||
>
|
||||
Harvest Crop
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -1,26 +1,26 @@
|
|||
import { useEffect, useRef } from 'react';
|
||||
import { useGameStore } from '../store/useGameStore';
|
||||
import { TICK_INTERVAL } from '../constants';
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useGameStore } from '../store/useGameStore'
|
||||
import { TICK_INTERVAL } from '../constants'
|
||||
|
||||
export const useGameTick = () => {
|
||||
const tick = useGameStore(state => state.tick);
|
||||
const gameSpeed = useGameStore(state => state.gameSpeed);
|
||||
const intervalRef = useRef<number | null>(null);
|
||||
const tick = useGameStore(state => state.tick)
|
||||
const gameSpeed = useGameStore(state => state.gameSpeed)
|
||||
const intervalRef = useRef<number | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (intervalRef.current !== null) {
|
||||
clearInterval(intervalRef.current);
|
||||
clearInterval(intervalRef.current)
|
||||
}
|
||||
|
||||
const adjustedInterval = TICK_INTERVAL / gameSpeed;
|
||||
intervalRef.current = window.setInterval(() => {
|
||||
tick();
|
||||
}, adjustedInterval);
|
||||
tick()
|
||||
}, adjustedInterval)
|
||||
|
||||
return () => {
|
||||
if (intervalRef.current !== null) {
|
||||
clearInterval(intervalRef.current);
|
||||
clearInterval(intervalRef.current)
|
||||
}
|
||||
};
|
||||
}, [tick, gameSpeed]); // Add gameSpeed as dependency
|
||||
}
|
||||
}, [tick, gameSpeed])
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import {
|
|||
FIELD_UPGRADE_COSTS,
|
||||
INITIAL_INVENTORY,
|
||||
CROPS,
|
||||
INITIAL_GAME_SPEED
|
||||
INITIAL_GAME_SPEED,
|
||||
COOLDOWN_DURATION
|
||||
} from '../constants';
|
||||
import { GameState, PlotState } from '../types';
|
||||
|
||||
|
|
@ -26,6 +27,7 @@ export const useGameStore = create<GameState & {
|
|||
assignCrop: (row: number, col: number, cropId: string) => void;
|
||||
upgradeField: () => void;
|
||||
setGameSpeed: (speed: number) => void;
|
||||
setActionCooldown: (cooldown: number) => void;
|
||||
}>((set, get) => ({
|
||||
cash: INITIAL_CASH,
|
||||
inventory: INITIAL_INVENTORY,
|
||||
|
|
@ -33,10 +35,8 @@ export const useGameStore = create<GameState & {
|
|||
maxFieldSize: INITIAL_FIELD_SIZE + FIELD_UPGRADE_COSTS.length,
|
||||
fieldUpgradeCosts: FIELD_UPGRADE_COSTS,
|
||||
plots: initializeField(INITIAL_FIELD_SIZE),
|
||||
lastPlantTime: 0,
|
||||
lastWaterTime: 0,
|
||||
lastHarvestTime: 0,
|
||||
gameSpeed: INITIAL_GAME_SPEED,
|
||||
actionCooldown: 0,
|
||||
|
||||
assignCrop: (row, col, cropId) => {
|
||||
set(produce(state => {
|
||||
|
|
@ -45,10 +45,9 @@ export const useGameStore = create<GameState & {
|
|||
},
|
||||
|
||||
plant: () => {
|
||||
const now = Date.now();
|
||||
const { lastPlantTime, plots, inventory } = get();
|
||||
const { plots, inventory, actionCooldown } = get();
|
||||
|
||||
if (now - lastPlantTime < 2000) {
|
||||
if (actionCooldown > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -68,7 +67,7 @@ export const useGameStore = create<GameState & {
|
|||
};
|
||||
|
||||
state.inventory[seedId] = state.inventory[seedId] - 1;
|
||||
state.lastPlantTime = now;
|
||||
state.actionCooldown = COOLDOWN_DURATION;
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
|
@ -78,10 +77,9 @@ export const useGameStore = create<GameState & {
|
|||
},
|
||||
|
||||
water: () => {
|
||||
const now = Date.now();
|
||||
const { lastWaterTime, plots } = get();
|
||||
const { plots, actionCooldown } = get();
|
||||
|
||||
if (now - lastWaterTime < 2000) {
|
||||
if (actionCooldown > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -102,16 +100,15 @@ export const useGameStore = create<GameState & {
|
|||
if (driestRow >= 0 && driestCol >= 0) {
|
||||
set(produce(state => {
|
||||
state.plots[driestRow][driestCol].moisture = 1;
|
||||
state.lastWaterTime = now;
|
||||
state.actionCooldown = COOLDOWN_DURATION;
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
harvest: () => {
|
||||
const now = Date.now();
|
||||
const { lastHarvestTime, plots } = get();
|
||||
const { plots, actionCooldown } = get();
|
||||
|
||||
if (now - lastHarvestTime < 2000) {
|
||||
if (actionCooldown > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -127,7 +124,7 @@ export const useGameStore = create<GameState & {
|
|||
set(produce(state => {
|
||||
state.plots[row][col].current = undefined;
|
||||
state.inventory[yieldItem] = (state.inventory[yieldItem] || 0) + yieldAmount;
|
||||
state.lastHarvestTime = now;
|
||||
state.actionCooldown = COOLDOWN_DURATION;
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
|
@ -136,7 +133,15 @@ export const useGameStore = create<GameState & {
|
|||
},
|
||||
|
||||
tick: () => {
|
||||
const { actionCooldown } = get();
|
||||
|
||||
set(produce(state => {
|
||||
// Update cooldown
|
||||
if (state.actionCooldown > 0) {
|
||||
state.actionCooldown = Math.max(0, state.actionCooldown - (COOLDOWN_DURATION / 20));
|
||||
}
|
||||
|
||||
// Update plots
|
||||
state.plots.forEach((row: PlotState[], rowIndex: number) => {
|
||||
row.forEach((plot: PlotState, colIndex: number) => {
|
||||
if (!plot.current || plot.current.mature) {
|
||||
|
|
@ -184,5 +189,11 @@ export const useGameStore = create<GameState & {
|
|||
set(produce(state => {
|
||||
state.gameSpeed = speed;
|
||||
}));
|
||||
},
|
||||
|
||||
setActionCooldown: (cooldown) => {
|
||||
set(produce(state => {
|
||||
state.actionCooldown = cooldown;
|
||||
}));
|
||||
}
|
||||
}));
|
||||
|
|
|
|||
|
|
@ -33,8 +33,6 @@ export interface GameState {
|
|||
maxFieldSize: number;
|
||||
fieldUpgradeCosts: number[];
|
||||
plots: PlotState[][];
|
||||
lastPlantTime: number;
|
||||
lastWaterTime: number;
|
||||
lastHarvestTime: number;
|
||||
gameSpeed: number;
|
||||
actionCooldown: number;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import path from "path"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import { defineConfig } from "vite"
|
||||
import linaria from "@linaria/vite"
|
||||
import react from "@vitejs/plugin-react"
|
||||
import wyw from '@wyw-in-js/vite'
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react(),
|
||||
linaria({
|
||||
wyw({
|
||||
include: ['**/*.{ts,tsx}'],
|
||||
babelOptions: {
|
||||
presets: ['@babel/preset-typescript', '@babel/preset-react'],
|
||||
|
|
|
|||
Loading…
Reference in a new issue