Compare commits
No commits in common. "9edb03007983e2d97fd84281cc511a445beec8c5" and "5724da535af839436f9906377fab1ff5ca143f9d" have entirely different histories.
9edb030079
...
5724da535a
|
|
@ -1,12 +0,0 @@
|
|||
import eslint from '@eslint/js'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
eslint.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
{
|
||||
rules: {
|
||||
'@typescript-eslint/no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }]
|
||||
}
|
||||
}
|
||||
)
|
||||
1368
package-lock.json
generated
1368
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -8,8 +8,7 @@
|
|||
"build": "vite build",
|
||||
"type-check": "tsc --noEmit",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint --fix ./src",
|
||||
"style": "prettier --write src/"
|
||||
"lint": "prettier --write src/"
|
||||
},
|
||||
"dependencies": {
|
||||
"@linaria/core": "^6.3.0",
|
||||
|
|
@ -28,10 +27,8 @@
|
|||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"@wyw-in-js/vite": "^0.6.0",
|
||||
"eslint": "^9.27.0",
|
||||
"prettier": "3.5.3",
|
||||
"typescript": "^5.8.3",
|
||||
"typescript-eslint": "^8.32.1",
|
||||
"vite": "^6.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,181 +0,0 @@
|
|||
import { styled } from '@linaria/react'
|
||||
import { useGameStore } from '../store/useGameStore'
|
||||
import { EQUIPMENT, ITEMS } from '../constants'
|
||||
import Modal from './Modal'
|
||||
|
||||
interface EquipmentModalProps {
|
||||
equipmentId: string
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
|
||||
|
||||
const Section = styled.div`
|
||||
margin-bottom: 1.5rem;
|
||||
`
|
||||
|
||||
const SectionTitle = styled.h3`
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
`
|
||||
|
||||
const ActionButton = styled.button<{ disabled?: boolean }>`
|
||||
background-color: ${props => props.disabled ? '#9ca3af' : '#4f46e5'};
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.375rem;
|
||||
border: none;
|
||||
cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
|
||||
font-size: 1rem;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
`
|
||||
|
||||
const ProgressBar = styled.div`
|
||||
width: 100%;
|
||||
height: 0.5rem;
|
||||
background-color: #e5e7eb;
|
||||
border-radius: 0.25rem;
|
||||
margin-top: 0.5rem;
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
const ProgressFill = styled.div<{ width: number }>`
|
||||
height: 100%;
|
||||
background-color: #4f46e5;
|
||||
transition: width 0.1s linear;
|
||||
width: ${props => props.width}%;
|
||||
`
|
||||
|
||||
const RecipeButton = styled.button<{ selected?: boolean; disabled?: boolean }>`
|
||||
background-color: ${props => props.selected ? '#e0e7ff' : '#f3f4f6'};
|
||||
border: 1px solid ${props => props.selected ? '#4f46e5' : '#d1d5db'};
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
opacity: ${props => props.disabled ? 0.5 : 1};
|
||||
`
|
||||
|
||||
const RecipeIcon = styled.span`
|
||||
font-size: 1.25rem;
|
||||
`
|
||||
|
||||
const RecipeInfo = styled.div`
|
||||
flex: 1;
|
||||
`
|
||||
|
||||
const RecipeName = styled.div`
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.25rem;
|
||||
`
|
||||
|
||||
const RecipeDetails = styled.div`
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
`
|
||||
|
||||
const EquipmentModal = ({ equipmentId, onClose }: EquipmentModalProps) => {
|
||||
const { inventory, configureEquipment, useEquipment, actionCooldown, equipment } = useGameStore()
|
||||
const equipmentInstance = equipment[equipmentId]
|
||||
const equipmentDef = EQUIPMENT[equipmentInstance.type]
|
||||
|
||||
const handleUse = () => {
|
||||
useEquipment(equipmentInstance.id)
|
||||
}
|
||||
|
||||
const handleRecipeSelect = (recipeId: string) => {
|
||||
configureEquipment(equipmentInstance.id, recipeId)
|
||||
}
|
||||
|
||||
const selectedRecipe = equipmentDef.recipes.find(r => r.id === equipmentInstance.selectedRecipeId)
|
||||
const progress = selectedRecipe ? (equipmentInstance.progress / selectedRecipe.processTicks) * 100 : 0
|
||||
|
||||
const canUse =
|
||||
!equipmentInstance.isProcessing &&
|
||||
actionCooldown === 0 &&
|
||||
selectedRecipe &&
|
||||
(inventory[selectedRecipe.inputItem] || 0) >= selectedRecipe.inputAmount
|
||||
|
||||
const modalTitle = (
|
||||
<>
|
||||
<span>{equipmentDef.emoji}</span>
|
||||
{equipmentDef.name}
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose} title={modalTitle}>
|
||||
<Section>
|
||||
<SectionTitle>Select Recipe</SectionTitle>
|
||||
{equipmentDef.recipes.map((recipe) => (
|
||||
<RecipeButton
|
||||
key={recipe.id}
|
||||
selected={recipe.id === equipmentInstance.selectedRecipeId}
|
||||
onClick={() => handleRecipeSelect(recipe.id)}
|
||||
disabled={equipmentInstance.isProcessing}
|
||||
>
|
||||
<RecipeIcon>
|
||||
{ITEMS[recipe.inputItem].emoji} → {ITEMS[recipe.outputItem].emoji}
|
||||
</RecipeIcon>
|
||||
<RecipeInfo>
|
||||
<RecipeName>{recipe.name}</RecipeName>
|
||||
<RecipeDetails>
|
||||
{recipe.inputAmount} {ITEMS[recipe.inputItem].name} → {recipe.outputAmount} {ITEMS[recipe.outputItem].name}
|
||||
<br />
|
||||
Time: {recipe.processTicks} ticks, Cooldown: {recipe.cooldownDuration / 1000}s
|
||||
</RecipeDetails>
|
||||
</RecipeInfo>
|
||||
</RecipeButton>
|
||||
))}
|
||||
</Section>
|
||||
|
||||
{selectedRecipe && (
|
||||
<>
|
||||
<Section>
|
||||
<SectionTitle>Status</SectionTitle>
|
||||
{equipmentInstance.isProcessing ? (
|
||||
<>
|
||||
<p>Processing {selectedRecipe.name}...</p>
|
||||
<ProgressBar>
|
||||
<ProgressFill width={progress} />
|
||||
</ProgressBar>
|
||||
</>
|
||||
) : (
|
||||
<p>Ready to use</p>
|
||||
)}
|
||||
</Section>
|
||||
|
||||
<Section>
|
||||
<SectionTitle>Requirements</SectionTitle>
|
||||
<p>
|
||||
Input: {selectedRecipe.inputAmount} {ITEMS[selectedRecipe.inputItem].name}
|
||||
{' '}
|
||||
(You have: {inventory[selectedRecipe.inputItem] || 0})
|
||||
</p>
|
||||
<p>
|
||||
Output: {selectedRecipe.outputAmount} {ITEMS[selectedRecipe.outputItem].name}
|
||||
</p>
|
||||
<p>Processing time: {selectedRecipe.processTicks} ticks</p>
|
||||
<p>Cooldown: {selectedRecipe.cooldownDuration / 1000} seconds</p>
|
||||
</Section>
|
||||
|
||||
<ActionButton
|
||||
disabled={!canUse}
|
||||
onClick={handleUse}
|
||||
>
|
||||
{equipmentInstance.isProcessing ? 'Processing...' : 'Use'}
|
||||
</ActionButton>
|
||||
</>
|
||||
)}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default EquipmentModal
|
||||
|
|
@ -2,7 +2,6 @@ import React, { useState } from 'react'
|
|||
import { useGameStore } from '../store/useGameStore'
|
||||
import { CROPS } from '../constants'
|
||||
import { FieldTool, PlotState } from '../types'
|
||||
import Modal from './Modal'
|
||||
|
||||
const fieldContainerStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
|
|
@ -147,6 +146,39 @@ const getMaturityProgressBarStyle = (
|
|||
boxShadow: '0 0 3px rgba(0, 0, 0, 0.5)',
|
||||
})
|
||||
|
||||
const modalOverlayStyle: React.CSSProperties = {
|
||||
position: 'fixed',
|
||||
top: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
zIndex: 1000,
|
||||
}
|
||||
|
||||
const modalContentStyle: React.CSSProperties = {
|
||||
backgroundColor: 'white',
|
||||
padding: '2rem',
|
||||
borderRadius: '0.5rem',
|
||||
maxWidth: '400px',
|
||||
width: '90%',
|
||||
position: 'relative',
|
||||
}
|
||||
|
||||
const modalCloseButtonStyle: React.CSSProperties = {
|
||||
position: 'absolute',
|
||||
top: '0.5rem',
|
||||
right: '0.5rem',
|
||||
padding: '0.5rem',
|
||||
border: 'none',
|
||||
background: 'none',
|
||||
cursor: 'pointer',
|
||||
fontSize: '1.5rem',
|
||||
}
|
||||
|
||||
const plotInfoStyle: React.CSSProperties = {
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
|
|
@ -171,43 +203,49 @@ const PlotInfoModal: React.FC<PlotInfoModalProps> = ({ plot, onClose }) => {
|
|||
const formatPercentage = (value: number) => `${Math.round(value * 100)}%`
|
||||
|
||||
return (
|
||||
<Modal onClose={onClose} title="Plot Information">
|
||||
<div style={plotInfoStyle}>
|
||||
<div style={plotInfoItemStyle}>
|
||||
<span>Current Crop:</span>
|
||||
<span>
|
||||
{plot.current
|
||||
? `${CROPS[plot.current.cropId].name}${
|
||||
plot.current.mature ? ' (Mature)' : ''
|
||||
}`
|
||||
: 'None'}
|
||||
</span>
|
||||
</div>
|
||||
<div style={plotInfoItemStyle}>
|
||||
<span>Intended Crop:</span>
|
||||
<span>{plot.intended ? CROPS[plot.intended].name : 'None'}</span>
|
||||
</div>
|
||||
<div style={plotInfoItemStyle}>
|
||||
<span>Water Level:</span>
|
||||
<span>{formatPercentage(plot.moisture)}</span>
|
||||
</div>
|
||||
<div style={plotInfoItemStyle}>
|
||||
<span>Fertility Level:</span>
|
||||
<span>{formatPercentage(plot.fertility)}</span>
|
||||
</div>
|
||||
{plot.current && !plot.current.mature && (
|
||||
<div style={modalOverlayStyle} onClick={onClose}>
|
||||
<div style={modalContentStyle} onClick={(e) => e.stopPropagation()}>
|
||||
<button style={modalCloseButtonStyle} onClick={onClose}>
|
||||
×
|
||||
</button>
|
||||
<h3 style={{ marginTop: 0 }}>Plot Information</h3>
|
||||
<div style={plotInfoStyle}>
|
||||
<div style={plotInfoItemStyle}>
|
||||
<span>Growth Progress:</span>
|
||||
<span>Current Crop:</span>
|
||||
<span>
|
||||
{formatPercentage(
|
||||
plot.current.progress /
|
||||
CROPS[plot.current.cropId].growthTicks,
|
||||
)}
|
||||
{plot.current
|
||||
? `${CROPS[plot.current.cropId].name}${
|
||||
plot.current.mature ? ' (Mature)' : ''
|
||||
}`
|
||||
: 'None'}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div style={plotInfoItemStyle}>
|
||||
<span>Intended Crop:</span>
|
||||
<span>{plot.intended ? CROPS[plot.intended].name : 'None'}</span>
|
||||
</div>
|
||||
<div style={plotInfoItemStyle}>
|
||||
<span>Water Level:</span>
|
||||
<span>{formatPercentage(plot.moisture)}</span>
|
||||
</div>
|
||||
<div style={plotInfoItemStyle}>
|
||||
<span>Fertility Level:</span>
|
||||
<span>{formatPercentage(plot.fertility)}</span>
|
||||
</div>
|
||||
{plot.current && !plot.current.mature && (
|
||||
<div style={plotInfoItemStyle}>
|
||||
<span>Growth Progress:</span>
|
||||
<span>
|
||||
{formatPercentage(
|
||||
plot.current.progress /
|
||||
CROPS[plot.current.cropId].growthTicks,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import React, {
|
||||
useEffect,
|
||||
useState,
|
||||
useImperativeHandle,
|
||||
forwardRef,
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
import React from 'react'
|
||||
import { styled } from '@linaria/react'
|
||||
|
||||
interface ModalProps {
|
||||
onClose: () => void
|
||||
children: React.ReactNode
|
||||
title?: React.ReactNode
|
||||
maxWidth?: string
|
||||
minWidth?: string
|
||||
}
|
||||
|
||||
const ModalOverlay = styled.div`
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 1000;
|
||||
`
|
||||
|
||||
const ModalContent = styled.div<{ maxWidth?: string; minWidth?: string }>`
|
||||
background-color: white;
|
||||
padding: 2rem;
|
||||
border-radius: 0.5rem;
|
||||
min-width: ${props => props.minWidth || '300px'};
|
||||
max-width: ${props => props.maxWidth || '500px'};
|
||||
width: 90%;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const CloseButton = styled.button`
|
||||
position: absolute;
|
||||
top: 0.5rem;
|
||||
right: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 1.5rem;
|
||||
color: #6b7280;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
&:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
`
|
||||
|
||||
const Title = styled.h2`
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding-right: 2rem;
|
||||
`
|
||||
|
||||
const Modal: React.FC<ModalProps> = ({ onClose, children, title, maxWidth, minWidth }) => {
|
||||
return (
|
||||
<ModalOverlay onClick={onClose}>
|
||||
<ModalContent onClick={(e) => e.stopPropagation()} maxWidth={maxWidth} minWidth={minWidth}>
|
||||
<CloseButton onClick={onClose}>×</CloseButton>
|
||||
{title && <Title>{title}</Title>}
|
||||
{children}
|
||||
</ModalContent>
|
||||
</ModalOverlay>
|
||||
)
|
||||
}
|
||||
|
||||
export default Modal
|
||||
|
|
@ -1,139 +1,68 @@
|
|||
import React, { useState } from 'react'
|
||||
import { styled } from '@linaria/react'
|
||||
import React from 'react'
|
||||
import { useGameStore } from '../store/useGameStore'
|
||||
import { ITEMS, EQUIPMENT } from '../constants'
|
||||
import EquipmentModal from './EquipmentModal'
|
||||
import { Equipment } from '../types'
|
||||
import { ITEMS } from '../constants'
|
||||
|
||||
const WarehouseContainer = styled.div`
|
||||
padding: 1rem;
|
||||
`
|
||||
const warehouseContainerStyle: React.CSSProperties = {
|
||||
padding: '1rem',
|
||||
}
|
||||
|
||||
const Title = styled.h2`
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
margin-bottom: 1rem;
|
||||
text-align: center;
|
||||
`
|
||||
const titleStyle: React.CSSProperties = {
|
||||
fontSize: '1.5rem',
|
||||
fontWeight: 'bold',
|
||||
marginBottom: '1rem',
|
||||
textAlign: 'center',
|
||||
}
|
||||
|
||||
const CashDisplay = styled.div`
|
||||
text-align: center;
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1rem;
|
||||
background-color: #dcfce7;
|
||||
border-radius: 0.375rem;
|
||||
`
|
||||
const cashDisplayStyle: React.CSSProperties = {
|
||||
textAlign: 'center',
|
||||
marginBottom: '1.5rem',
|
||||
padding: '1rem',
|
||||
backgroundColor: '#dcfce7',
|
||||
borderRadius: '0.375rem',
|
||||
}
|
||||
|
||||
const CashAmount = styled.h3`
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
`
|
||||
const cashAmountStyle: React.CSSProperties = {
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 500,
|
||||
}
|
||||
|
||||
const InventoryTitle = styled.h3`
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.5rem;
|
||||
`
|
||||
const inventoryTitleStyle: React.CSSProperties = {
|
||||
fontSize: '1.25rem',
|
||||
fontWeight: 500,
|
||||
marginBottom: '0.5rem',
|
||||
}
|
||||
|
||||
const InventoryGrid = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
`
|
||||
const getInventoryGridStyle = (): React.CSSProperties => {
|
||||
const style: React.CSSProperties = {
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'repeat(2, 1fr)',
|
||||
gap: '1rem',
|
||||
marginTop: '1rem',
|
||||
}
|
||||
|
||||
const InventoryItem = styled.div`
|
||||
background-color: #f3f4f6;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
`
|
||||
return style
|
||||
}
|
||||
|
||||
const ItemIcon = styled.div`
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
`
|
||||
const inventoryItemStyle: React.CSSProperties = {
|
||||
backgroundColor: '#f3f4f6',
|
||||
border: '1px solid #d1d5db',
|
||||
borderRadius: '0.5rem',
|
||||
padding: '1rem',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center',
|
||||
textAlign: 'center',
|
||||
}
|
||||
|
||||
const ItemName = styled.h4`
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.25rem;
|
||||
`
|
||||
const itemIconStyle: React.CSSProperties = {
|
||||
fontSize: '1.5rem',
|
||||
marginBottom: '0.5rem',
|
||||
}
|
||||
|
||||
const EquipmentTitle = styled.h3`
|
||||
font-size: 1.25rem;
|
||||
font-weight: 500;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
`
|
||||
|
||||
const EquipmentGrid = styled.div`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-top: 1rem;
|
||||
`
|
||||
|
||||
const EquipmentItem = styled.div`
|
||||
background-color: #f3f4f6;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
`
|
||||
|
||||
const ProgressBarContainer = styled.div`
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 0.25rem;
|
||||
background-color: #e5e7eb;
|
||||
border-top-left-radius: 0.5rem;
|
||||
border-top-right-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
`
|
||||
|
||||
const ProgressFill = styled.div<{ width: string }>`
|
||||
height: 100%;
|
||||
background-color: #4f46e5;
|
||||
width: ${props => props.width};
|
||||
`
|
||||
|
||||
const EquipmentIcon = styled.div`
|
||||
font-size: 2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
`
|
||||
|
||||
const EquipmentName = styled.h4`
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.25rem;
|
||||
`
|
||||
|
||||
const EquipmentStatus = styled.p`
|
||||
font-size: 0.875rem;
|
||||
color: #6b7280;
|
||||
`
|
||||
|
||||
const BuyEquipmentButton = styled.button<{ disabled?: boolean }>`
|
||||
background-color: ${props => props.disabled ? '#9ca3af' : '#4f46e5'};
|
||||
color: white;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.375rem;
|
||||
border: none;
|
||||
cursor: ${props => props.disabled ? 'not-allowed' : 'pointer'};
|
||||
font-size: 1rem;
|
||||
width: 100%;
|
||||
margin-top: 1rem;
|
||||
`
|
||||
const itemNameStyle: React.CSSProperties = {
|
||||
fontWeight: 500,
|
||||
marginBottom: '0.25rem',
|
||||
}
|
||||
|
||||
const formatItemName = (id: string) => {
|
||||
return ITEMS[id]?.name || id
|
||||
|
|
@ -144,104 +73,31 @@ const getItemIcon = (id: string) => {
|
|||
}
|
||||
|
||||
const WarehouseComponent: React.FC = () => {
|
||||
const { inventory, cash, equipment, buyEquipment } = useGameStore()
|
||||
const [selectedEquipment, setSelectedEquipment] = useState<Equipment | null>(null)
|
||||
|
||||
const handleEquipmentClick = (equipment: Equipment) => {
|
||||
setSelectedEquipment(equipment)
|
||||
}
|
||||
|
||||
const handleBuyEquipment = (equipmentId: string) => {
|
||||
buyEquipment(equipmentId)
|
||||
}
|
||||
const { inventory, cash } = useGameStore()
|
||||
|
||||
return (
|
||||
<WarehouseContainer>
|
||||
<Title>Warehouse</Title>
|
||||
<div style={warehouseContainerStyle}>
|
||||
<h2 style={titleStyle}>Warehouse</h2>
|
||||
|
||||
<CashDisplay>
|
||||
<CashAmount>Cash: ${cash.toFixed(2)}</CashAmount>
|
||||
</CashDisplay>
|
||||
<div style={cashDisplayStyle}>
|
||||
<h3 style={cashAmountStyle}>Cash: ${cash.toFixed(2)}</h3>
|
||||
</div>
|
||||
|
||||
<InventoryTitle>Inventory:</InventoryTitle>
|
||||
<h3 style={inventoryTitleStyle}>Inventory:</h3>
|
||||
|
||||
<InventoryGrid>
|
||||
<div style={getInventoryGridStyle()}>
|
||||
{Object.entries(inventory).map(
|
||||
([id, count]) =>
|
||||
count > 0 && (
|
||||
<InventoryItem key={id}>
|
||||
<ItemIcon>{getItemIcon(id)}</ItemIcon>
|
||||
<ItemName>{formatItemName(id)}</ItemName>
|
||||
<div key={id} style={inventoryItemStyle}>
|
||||
<div style={itemIconStyle}>{getItemIcon(id)}</div>
|
||||
<h4 style={itemNameStyle}>{formatItemName(id)}</h4>
|
||||
<p>Quantity: {count}</p>
|
||||
</InventoryItem>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</InventoryGrid>
|
||||
|
||||
<EquipmentTitle>Equipment:</EquipmentTitle>
|
||||
|
||||
<EquipmentGrid>
|
||||
{/* Available equipment to buy */}
|
||||
{Object.entries(EQUIPMENT).map(([id, equipmentDef]) => {
|
||||
const canBuy = cash >= equipmentDef.cost
|
||||
return (
|
||||
<EquipmentItem key={`buy_${id}`}>
|
||||
<EquipmentIcon>{equipmentDef.emoji}</EquipmentIcon>
|
||||
<EquipmentName>{equipmentDef.name}</EquipmentName>
|
||||
<EquipmentStatus>Cost: ${equipmentDef.cost}</EquipmentStatus>
|
||||
<BuyEquipmentButton
|
||||
disabled={!canBuy}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
handleBuyEquipment(id)
|
||||
}}
|
||||
>
|
||||
Buy
|
||||
</BuyEquipmentButton>
|
||||
</EquipmentItem>
|
||||
)
|
||||
})}
|
||||
|
||||
{/* Owned equipment */}
|
||||
{Object.entries(equipment).map(([id, equipmentInstance]) => {
|
||||
const recipe = equipmentInstance.selectedRecipeId
|
||||
? EQUIPMENT[equipmentInstance.type].recipes.find(r => r.id === equipmentInstance.selectedRecipeId)
|
||||
: null
|
||||
const progress = recipe && equipmentInstance.isProcessing
|
||||
? (equipmentInstance.progress / recipe.processTicks) * 100
|
||||
: 0
|
||||
|
||||
return (
|
||||
<EquipmentItem
|
||||
key={id}
|
||||
onClick={() => handleEquipmentClick(equipmentInstance)}
|
||||
>
|
||||
{equipmentInstance.isProcessing && (
|
||||
<ProgressBarContainer>
|
||||
<ProgressFill width={`${progress}%`} />
|
||||
</ProgressBarContainer>
|
||||
)}
|
||||
<EquipmentIcon>{equipmentInstance.emoji}</EquipmentIcon>
|
||||
<EquipmentName>{equipmentInstance.name}</EquipmentName>
|
||||
<EquipmentStatus>
|
||||
{equipmentInstance.isProcessing
|
||||
? 'Processing...'
|
||||
: equipmentInstance.selectedRecipeId
|
||||
? `Ready (${recipe?.name})`
|
||||
: 'Not configured'}
|
||||
</EquipmentStatus>
|
||||
</EquipmentItem>
|
||||
)
|
||||
})}
|
||||
</EquipmentGrid>
|
||||
|
||||
{selectedEquipment && (
|
||||
<EquipmentModal
|
||||
equipmentId={selectedEquipment.id}
|
||||
onClose={() => setSelectedEquipment(null)}
|
||||
/>
|
||||
)}
|
||||
</WarehouseContainer>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { CropDefinitions, Upgrade, Equipment } from '../types'
|
||||
import { CropDefinitions, Upgrade } from '../types'
|
||||
|
||||
export const INITIAL_CASH = 50
|
||||
export const INITIAL_FIELD_SIZE = 3
|
||||
|
|
@ -116,40 +116,6 @@ export interface Item {
|
|||
sellPrice: number | null // null means not sellable
|
||||
}
|
||||
|
||||
export const EQUIPMENT: Record<string, Equipment> = {
|
||||
press: {
|
||||
id: 'press',
|
||||
type: 'press',
|
||||
name: 'Press',
|
||||
emoji: '🛢️',
|
||||
cost: 100,
|
||||
recipes: [
|
||||
{
|
||||
id: 'olive_oil',
|
||||
name: 'Olive Oil',
|
||||
inputItem: 'olive',
|
||||
inputAmount: 10,
|
||||
outputItem: 'olive_oil',
|
||||
outputAmount: 1,
|
||||
processTicks: 10,
|
||||
cooldownDuration: 10000, // 10 seconds
|
||||
},
|
||||
{
|
||||
id: 'grape_juice',
|
||||
name: 'Grape Juice',
|
||||
inputItem: 'grape',
|
||||
inputAmount: 20,
|
||||
outputItem: 'grape_juice',
|
||||
outputAmount: 1,
|
||||
processTicks: 20,
|
||||
cooldownDuration: 15000, // 15 seconds
|
||||
},
|
||||
],
|
||||
progress: 0,
|
||||
isProcessing: false,
|
||||
},
|
||||
}
|
||||
|
||||
export const ITEMS: Record<string, Item> = {
|
||||
additional_land: {
|
||||
id: 'additional_land',
|
||||
|
|
@ -193,32 +159,4 @@ export const ITEMS: Record<string, Item> = {
|
|||
buyPrice: 15,
|
||||
sellPrice: null,
|
||||
},
|
||||
olive_oil: {
|
||||
id: 'olive_oil',
|
||||
name: 'Olive Oil',
|
||||
emoji: '🫒',
|
||||
buyPrice: null,
|
||||
sellPrice: 50,
|
||||
},
|
||||
grape: {
|
||||
id: 'grape',
|
||||
name: 'Grape',
|
||||
emoji: '🍇',
|
||||
buyPrice: null,
|
||||
sellPrice: 20,
|
||||
},
|
||||
grape_juice: {
|
||||
id: 'grape_juice',
|
||||
name: 'Grape Juice',
|
||||
emoji: '🍇juice',
|
||||
buyPrice: null,
|
||||
sellPrice: 100,
|
||||
},
|
||||
wine: {
|
||||
id: 'wine',
|
||||
name: 'Wine',
|
||||
emoji: '🍷',
|
||||
buyPrice: null,
|
||||
sellPrice: 100,
|
||||
},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,9 +10,8 @@ import {
|
|||
COOLDOWN_DURATION,
|
||||
ITEMS,
|
||||
UPGRADES,
|
||||
EQUIPMENT,
|
||||
} from '../constants'
|
||||
import { GameState, PlotState, Equipment } from '../types'
|
||||
import { GameState, PlotState } from '../types'
|
||||
|
||||
const initializeField = (size: number): PlotState[][] => {
|
||||
return Array(size)
|
||||
|
|
@ -113,9 +112,6 @@ export const useGameStore = create<
|
|||
loadFromSlot: (slot: number) => void
|
||||
purchaseUpgrade: (upgradeId: string) => void
|
||||
pray: () => void
|
||||
buyEquipment: (equipmentId: string) => void
|
||||
configureEquipment: (equipmentId: string, recipeId: string) => void
|
||||
useEquipment: (equipmentId: string) => void
|
||||
}
|
||||
>((set, get) => ({
|
||||
cash: INITIAL_CASH,
|
||||
|
|
@ -131,7 +127,6 @@ export const useGameStore = create<
|
|||
purchasedUpgrades: [],
|
||||
piety: 50,
|
||||
landPurchases: 0,
|
||||
equipment: {},
|
||||
|
||||
assignCrop: (row, col, cropId) => {
|
||||
set(
|
||||
|
|
@ -346,36 +341,6 @@ export const useGameStore = create<
|
|||
})
|
||||
})
|
||||
|
||||
// Update equipment progress
|
||||
Object.entries(state.equipment).forEach(([, equipment]) => {
|
||||
// TODO: this type cast shouldn't be necessary
|
||||
const equipmentInstance = equipment as Equipment
|
||||
if (equipmentInstance.isProcessing) {
|
||||
const recipe = EQUIPMENT[equipmentInstance.type].recipes.find(
|
||||
r => r.id === equipmentInstance.selectedRecipeId
|
||||
)
|
||||
if (!recipe) return
|
||||
|
||||
equipmentInstance.progress += 1
|
||||
|
||||
// Check if processing is complete
|
||||
if (equipmentInstance.progress >= recipe.processTicks) {
|
||||
equipmentInstance.isProcessing = false
|
||||
equipmentInstance.progress = 0
|
||||
|
||||
// Add output items
|
||||
state.inventory[recipe.outputItem] =
|
||||
(state.inventory[recipe.outputItem] || 0) +
|
||||
recipe.outputAmount
|
||||
|
||||
addConsoleMessage(
|
||||
state,
|
||||
`${EQUIPMENT[equipmentInstance.type].name} produced ${recipe.outputAmount} ${ITEMS[recipe.outputItem].name}`
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
state.tickCount = state.tickCount + 1
|
||||
}),
|
||||
)
|
||||
|
|
@ -421,7 +386,7 @@ export const useGameStore = create<
|
|||
},
|
||||
|
||||
buyItem: (itemId) => {
|
||||
const { cash, landPurchases } = get()
|
||||
const { cash, landPurchases, plots, fieldSize } = get()
|
||||
const item = ITEMS[itemId]
|
||||
|
||||
if (!item || item.buyPrice === null || item.buyPrice === undefined) {
|
||||
|
|
@ -499,7 +464,23 @@ export const useGameStore = create<
|
|||
|
||||
saveToSlot: (slot: number) => {
|
||||
const state = get()
|
||||
const { ...gameState } = state
|
||||
const {
|
||||
plant,
|
||||
water,
|
||||
harvest,
|
||||
tick,
|
||||
assignCrop,
|
||||
upgradeField,
|
||||
setGameSpeed,
|
||||
setActionCooldown,
|
||||
buyItem,
|
||||
sellItem,
|
||||
saveToSlot,
|
||||
loadFromSlot,
|
||||
purchaseUpgrade,
|
||||
pray,
|
||||
...gameState
|
||||
} = state
|
||||
saveGame(slot, gameState)
|
||||
},
|
||||
|
||||
|
|
@ -579,87 +560,4 @@ export const useGameStore = create<
|
|||
}),
|
||||
)
|
||||
},
|
||||
|
||||
buyEquipment: (equipmentId) => {
|
||||
const { cash, equipment } = get()
|
||||
const equipmentDef = EQUIPMENT[equipmentId]
|
||||
|
||||
if (!equipmentDef || cash < equipmentDef.cost) {
|
||||
return
|
||||
}
|
||||
|
||||
// Generate a unique ID for this instance of equipment
|
||||
const instanceId = `${equipmentId}_${Object.keys(equipment).length + 1}`
|
||||
|
||||
set(
|
||||
produce((state) => {
|
||||
state.cash -= equipmentDef.cost
|
||||
state.equipment[instanceId] = {
|
||||
id: instanceId,
|
||||
type: equipmentId,
|
||||
progress: 0,
|
||||
isProcessing: false,
|
||||
}
|
||||
addConsoleMessage(state, `Purchased ${equipmentDef.name}`)
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
||||
configureEquipment: (equipmentId, recipeId) => {
|
||||
const { equipment } = get()
|
||||
const equipmentInstance = equipment[equipmentId]
|
||||
|
||||
if (!equipmentInstance || equipmentInstance.isProcessing) {
|
||||
return
|
||||
}
|
||||
|
||||
// Verify the recipe exists for this equipment
|
||||
const recipe = EQUIPMENT[equipmentInstance.type].recipes.find(r => r.id === recipeId)
|
||||
if (!recipe) {
|
||||
return
|
||||
}
|
||||
|
||||
set(
|
||||
produce((state) => {
|
||||
state.equipment[equipmentId].selectedRecipeId = recipeId
|
||||
}),
|
||||
)
|
||||
},
|
||||
|
||||
useEquipment: (equipmentId) => {
|
||||
const { equipment, inventory, actionCooldown } = get()
|
||||
const equipmentInstance = equipment[equipmentId]
|
||||
|
||||
if (
|
||||
!equipmentInstance ||
|
||||
equipmentInstance.isProcessing ||
|
||||
actionCooldown > 0 ||
|
||||
!equipmentInstance.selectedRecipeId
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
const recipe = EQUIPMENT[equipmentInstance.type].recipes.find(r => r.id === equipmentInstance.selectedRecipeId)
|
||||
if (!recipe) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if we have enough input items
|
||||
const inputCount = inventory[recipe.inputItem] || 0
|
||||
if (inputCount < recipe.inputAmount) {
|
||||
return
|
||||
}
|
||||
|
||||
set(
|
||||
produce((state) => {
|
||||
// Deduct input items
|
||||
state.inventory[recipe.inputItem] -= recipe.inputAmount
|
||||
|
||||
// Start processing
|
||||
state.equipment[equipmentId].isProcessing = true
|
||||
state.equipment[equipmentId].progress = 0
|
||||
state.actionCooldown = recipe.cooldownDuration
|
||||
}),
|
||||
)
|
||||
},
|
||||
}))
|
||||
|
|
|
|||
|
|
@ -51,33 +51,6 @@ export interface Upgrade {
|
|||
cost: UpgradeCost[]
|
||||
}
|
||||
|
||||
export interface Recipe {
|
||||
id: string
|
||||
name: string
|
||||
inputItem: string
|
||||
inputAmount: number
|
||||
outputItem: string
|
||||
outputAmount: number
|
||||
processTicks: number
|
||||
cooldownDuration: number
|
||||
}
|
||||
|
||||
export interface Equipment {
|
||||
id: string
|
||||
type: string
|
||||
name: string
|
||||
emoji: string
|
||||
cost: number
|
||||
recipes: Recipe[]
|
||||
selectedRecipeId?: string
|
||||
progress: number
|
||||
isProcessing: boolean
|
||||
}
|
||||
|
||||
export interface EquipmentState {
|
||||
[id: string]: Equipment
|
||||
}
|
||||
|
||||
export interface GameState {
|
||||
cash: number
|
||||
inventory: Record<string, number>
|
||||
|
|
@ -91,7 +64,6 @@ export interface GameState {
|
|||
purchasedUpgrades: string[]
|
||||
piety: number
|
||||
landPurchases: number
|
||||
equipment: EquipmentState
|
||||
}
|
||||
|
||||
export interface MarketTransaction {
|
||||
|
|
|
|||
Loading…
Reference in a new issue