From 479c00eada790dd4ed86f16c030ac421b9718faa Mon Sep 17 00:00:00 2001 From: Camiel van Schoonhoven Date: Thu, 12 Feb 2026 11:52:04 -0800 Subject: [PATCH] hackdays: Buildings have Production Methods --- .../Factory/Canvas/Edges/ResourceEdge.tsx | 39 +++ src/components/Factory/Canvas/GameCanvas.tsx | 29 +-- .../Factory/Canvas/Nodes/Building.tsx | 20 ++ .../Canvas/callbacks/isValidConnection.ts | 30 +++ .../Factory/Canvas/callbacks/onConnect.ts | 1 - .../Factory/Context/BuildingContext.tsx | 158 ++++++++++++ .../Factory/Context/ResourceContext.tsx | 64 +++++ .../Factory/Sidebar/BuildingItem.tsx | 3 +- src/components/Factory/Sidebar/Resources.tsx | 5 +- src/components/Factory/data/buildings.ts | 232 ++++++++++++++++++ src/components/Factory/data/resources.ts | 106 +++++++- src/components/Factory/types/buildings.ts | 25 ++ src/components/Factory/types/resources.ts | 39 ++- 13 files changed, 711 insertions(+), 40 deletions(-) create mode 100644 src/components/Factory/Canvas/callbacks/isValidConnection.ts create mode 100644 src/components/Factory/Context/BuildingContext.tsx create mode 100644 src/components/Factory/Context/ResourceContext.tsx diff --git a/src/components/Factory/Canvas/Edges/ResourceEdge.tsx b/src/components/Factory/Canvas/Edges/ResourceEdge.tsx index 2e121e5ac..3cb1d0404 100644 --- a/src/components/Factory/Canvas/Edges/ResourceEdge.tsx +++ b/src/components/Factory/Canvas/Edges/ResourceEdge.tsx @@ -1,13 +1,19 @@ import type { EdgeProps } from "@xyflow/react"; import { BaseEdge, getBezierPath, useEdges } from "@xyflow/react"; +import { useEffect } from "react"; +import { useContextPanel } from "@/providers/ContextPanelProvider"; + +import ResourceContext from "../../Context/ResourceContext"; import { isResourceData } from "../../types/resources"; const ResourceEdge = ({ id, data, + source, sourceX, sourceY, + target, targetX, targetY, sourcePosition, @@ -18,6 +24,11 @@ const ResourceEdge = ({ }: EdgeProps) => { const edges = useEdges(); const hasAnySelectedEdge = edges.some((edge) => edge.selected); + const { + setContent, + clearContent, + setOpen: setContextPanelOpen, + } = useContextPanel(); const [edgePath] = getBezierPath({ sourceX, @@ -36,6 +47,33 @@ const ResourceEdge = ({ ? "#BCBCBC" : baseColor; + useEffect(() => { + if (selected && isResourceData(data)) { + setContent( + , + ); + setContextPanelOpen(true); + } + + return () => { + if (selected) { + clearContent(); + } + }; + }, [ + selected, + data, + source, + target, + setContent, + clearContent, + setContextPanelOpen, + ]); + return ( { const onConnect = createOnConnect(setEdges); const onDrop = createOnDrop(reactFlowInstance, setNodes); - - const isValidConnection = (connection: Connection | Edge) => { - if (connection.source === connection.target) return false; - - const sourceResource = extractResource(connection.sourceHandle); - const targetResource = extractResource(connection.targetHandle); - - if ( - sourceResource !== "any" && - targetResource !== "any" && - sourceResource !== targetResource - ) { - return false; - } - - const hasExistingConnection = edges.some( - (edge) => - (edge.source === connection.source && - edge.sourceHandle === connection.sourceHandle) || - (edge.target === connection.target && - edge.targetHandle === connection.targetHandle), - ); - - return !hasExistingConnection; - }; + const isValidConnection = createIsValidConnection(edges); const onNodesDelete = (deleted: Node[]) => { console.log("Nodes deleted:", deleted); diff --git a/src/components/Factory/Canvas/Nodes/Building.tsx b/src/components/Factory/Canvas/Nodes/Building.tsx index 825f79d69..02fc91395 100644 --- a/src/components/Factory/Canvas/Nodes/Building.tsx +++ b/src/components/Factory/Canvas/Nodes/Building.tsx @@ -6,7 +6,9 @@ import { import { useEffect, useMemo } from "react"; import { cn } from "@/lib/utils"; +import { useContextPanel } from "@/providers/ContextPanelProvider"; +import BuildingContext from "../../Context/BuildingContext"; import { isBuildingData } from "../../types/buildings"; import { rotateBuilding } from "../../utils/rotation"; import BuildingInput from "../Handles/BuildingInput"; @@ -15,6 +17,11 @@ import BuildingOutput from "../Handles/BuildingOutput"; const Building = ({ id, data, selected }: NodeProps) => { const { updateNodeData } = useReactFlow(); const updateNodeInternals = useUpdateNodeInternals(); + const { + setContent, + clearContent, + setOpen: setContextPanelOpen, + } = useContextPanel(); useEffect(() => { if (!selected) return; @@ -34,6 +41,19 @@ const Building = ({ id, data, selected }: NodeProps) => { return () => window.removeEventListener("keydown", handleKeyPress); }, [selected, id, data, updateNodeData, updateNodeInternals]); + useEffect(() => { + if (selected && isBuildingData(data)) { + setContent(); + setContextPanelOpen(true); + } + + return () => { + if (selected) { + clearContent(); + } + }; + }, [selected, data, setContent, clearContent, setContextPanelOpen]); + if (!isBuildingData(data)) { return (
diff --git a/src/components/Factory/Canvas/callbacks/isValidConnection.ts b/src/components/Factory/Canvas/callbacks/isValidConnection.ts new file mode 100644 index 000000000..9c52ebce3 --- /dev/null +++ b/src/components/Factory/Canvas/callbacks/isValidConnection.ts @@ -0,0 +1,30 @@ +import type { Connection, Edge } from "@xyflow/react"; + +import { extractResource } from "../../utils/string"; + +export const createIsValidConnection = (edges: Edge[]) => { + return (connection: Connection | Edge) => { + if (connection.source === connection.target) return false; + + const sourceResource = extractResource(connection.sourceHandle); + const targetResource = extractResource(connection.targetHandle); + + if ( + sourceResource !== "any" && + targetResource !== "any" && + sourceResource !== targetResource + ) { + return false; + } + + const hasExistingConnection = edges.some( + (edge) => + (edge.source === connection.source && + edge.sourceHandle === connection.sourceHandle) || + (edge.target === connection.target && + edge.targetHandle === connection.targetHandle), + ); + + return !hasExistingConnection; + }; +}; diff --git a/src/components/Factory/Canvas/callbacks/onConnect.ts b/src/components/Factory/Canvas/callbacks/onConnect.ts index 2cd2cfb59..6eb924119 100644 --- a/src/components/Factory/Canvas/callbacks/onConnect.ts +++ b/src/components/Factory/Canvas/callbacks/onConnect.ts @@ -36,7 +36,6 @@ export const createOnConnect = ( }; setEdges((eds) => { - // Remove any existing edges from the same source handle OR to the same target handle const filteredEdges = eds.filter( (edge) => !( diff --git a/src/components/Factory/Context/BuildingContext.tsx b/src/components/Factory/Context/BuildingContext.tsx new file mode 100644 index 000000000..1795b8705 --- /dev/null +++ b/src/components/Factory/Context/BuildingContext.tsx @@ -0,0 +1,158 @@ +import { Icon } from "@/components/ui/icon"; +import { BlockStack, InlineStack } from "@/components/ui/layout"; +import { Separator } from "@/components/ui/separator"; +import { Text } from "@/components/ui/typography"; +import { pluralize } from "@/utils/string"; + +import type { Building } from "../types/buildings"; + +interface BuildingContextProps { + building: Building; +} + +const BuildingContext = ({ building }: BuildingContextProps) => { + const { + icon, + name, + description, + cost, + productionMethod, + inputs = [], + outputs = [], + stockpile = [], + } = building; + + return ( + + + + {icon} {name} + + + + + {description} + + + {cost !== undefined && ( + + + Cost: {cost} + + )} + + + + + + Production Method + + + {!!productionMethod?.name && ( + + + {productionMethod.name} + + )} + + {productionMethod && ( + + {productionMethod.inputs.length > 0 && ( + + + Inputs: + + {productionMethod.inputs.map((input, idx) => ( + + + • {input.amount}x {input.resource} + + + ))} + + )} + + {productionMethod.outputs.length > 0 && ( + + + Outputs: + + {productionMethod.outputs.map((output, idx) => ( + + + • {output.amount}x {output.resource} + + + ))} + + )} + + + + {`${productionMethod.days} ${pluralize(productionMethod.days, "day")}`} + + + )} + + {!productionMethod && ( + + No production method defined + + )} + + + + + + + Stockpile + + {stockpile.length > 0 ? ( + + {stockpile.map((stock, idx) => ( + + + {stock.resource}: {stock.amount} / {stock.maxAmount} + +
+
+
+ + ))} + + ) : ( + + No stockpile + + )} + + + + + + + Connections + + + + Inputs: {inputs.length} + + + Outputs: {outputs.length} + + + + + ); +}; + +export default BuildingContext; diff --git a/src/components/Factory/Context/ResourceContext.tsx b/src/components/Factory/Context/ResourceContext.tsx new file mode 100644 index 000000000..20763f1de --- /dev/null +++ b/src/components/Factory/Context/ResourceContext.tsx @@ -0,0 +1,64 @@ +import { BlockStack, InlineStack } from "@/components/ui/layout"; +import { Separator } from "@/components/ui/separator"; +import { Text } from "@/components/ui/typography"; + +import type { Resource } from "../types/resources"; + +interface ResourceContextProps { + resource: Resource; + sourceNodeId?: string; + targetNodeId?: string; +} + +const ResourceContext = ({ + resource, + sourceNodeId, + targetNodeId, +}: ResourceContextProps) => { + const { name, description, color, icon } = resource; + + return ( + + + + {icon} {name} + + + +
+ + + {description} + + + + + + + Connection + + + {sourceNodeId && ( + + From: {sourceNodeId} + + )} + {targetNodeId && ( + + To: {targetNodeId} + + )} + + + + ); +}; + +export default ResourceContext; diff --git a/src/components/Factory/Sidebar/BuildingItem.tsx b/src/components/Factory/Sidebar/BuildingItem.tsx index 745453e35..6697e854a 100644 --- a/src/components/Factory/Sidebar/BuildingItem.tsx +++ b/src/components/Factory/Sidebar/BuildingItem.tsx @@ -4,6 +4,7 @@ import { useCallback } from "react"; import { InlineStack } from "@/components/ui/layout"; import { cn } from "@/lib/utils"; +import { RESOURCES } from "../data/resources"; import type { Building } from "../types/buildings"; interface BuildingItemProps { @@ -55,7 +56,7 @@ const BuildingItem = ({ building }: BuildingItemProps) => { {building.description} - 💰 {building.cost} + {RESOURCES.coins.icon} {building.cost}
diff --git a/src/components/Factory/Sidebar/Resources.tsx b/src/components/Factory/Sidebar/Resources.tsx index 01ef9cee4..1b76015d2 100644 --- a/src/components/Factory/Sidebar/Resources.tsx +++ b/src/components/Factory/Sidebar/Resources.tsx @@ -1,9 +1,12 @@ import { SidebarGroup, SidebarGroupLabel } from "@/components/ui/sidebar"; +import { RESOURCES } from "../data/resources"; + const Resources = () => { return ( - 💰 0 + {RESOURCES.coins.icon} 0 + {RESOURCES.knowledge.icon} 0 ); }; diff --git a/src/components/Factory/data/buildings.ts b/src/components/Factory/data/buildings.ts index ebdf40414..72280d2d1 100644 --- a/src/components/Factory/data/buildings.ts +++ b/src/components/Factory/data/buildings.ts @@ -16,6 +16,14 @@ export const BUILDINGS: Building[] = [ { resource: "any", position: Position.Top }, { resource: "any", position: Position.Bottom }, ], + productionMethod: { + name: "Trading", + inputs: [{ resource: "any", amount: 1 }], + outputs: [], + globalOutputs: [{ resource: "coins", amount: 10 }], + days: 1, + }, + stockpile: [{ resource: "any", amount: 0, maxAmount: 1000 }], }, { id: "woodcutter", @@ -25,6 +33,13 @@ export const BUILDINGS: Building[] = [ cost: 0, color: "#A0522D", outputs: [{ resource: "wood", position: Position.Right }], + productionMethod: { + name: "Hand Axes", + inputs: [], + outputs: [{ resource: "wood", amount: 10 }], + days: 1, + }, + stockpile: [{ resource: "wood", amount: 0, maxAmount: 100 }], }, { id: "quarry", @@ -34,6 +49,13 @@ export const BUILDINGS: Building[] = [ cost: 0, color: "#708090", outputs: [{ resource: "stone", position: Position.Right }], + productionMethod: { + name: "Hand Axes", + inputs: [], + outputs: [{ resource: "stone", amount: 5 }], + days: 1, + }, + stockpile: [{ resource: "stone", amount: 0, maxAmount: 50 }], }, { id: "farm", @@ -43,6 +65,13 @@ export const BUILDINGS: Building[] = [ cost: 0, color: "#228B22", outputs: [{ resource: "wheat", position: Position.Right }], + productionMethod: { + name: "Wheat", + inputs: [], + outputs: [{ resource: "wheat", amount: 60 }], + days: 3, + }, + stockpile: [{ resource: "wheat", amount: 0, maxAmount: 600 }], }, { id: "sawmill", @@ -56,5 +85,208 @@ export const BUILDINGS: Building[] = [ { resource: "wood", position: Position.Left }, ], outputs: [{ resource: "planks", position: Position.Right }], + productionMethod: { + name: "Sandpaper and Hand Saw", + inputs: [{ resource: "wood", amount: 40 }], + outputs: [{ resource: "planks", amount: 10 }], + days: 2, + }, + stockpile: [ + { resource: "wood", amount: 0, maxAmount: 400 }, + { resource: "planks", amount: 0, maxAmount: 200 }, + ], + }, + { + id: "papermill", + name: "Papermill", + icon: "🏭", + description: "Turns wood into paper", + cost: 0, + color: "#6A5ACD", + inputs: [{ resource: "wood", position: Position.Left }], + outputs: [ + { resource: "paper", position: Position.Right }, + { resource: "paper", position: Position.Right }, + ], + productionMethod: { + name: "Pulp and Press", + inputs: [{ resource: "wood", amount: 20 }], + outputs: [{ resource: "paper", amount: 40 }], + days: 2, + }, + stockpile: [ + { resource: "wood", amount: 0, maxAmount: 200 }, + { resource: "paper", amount: 0, maxAmount: 400 }, + ], + }, + { + id: "pasture", + name: "Pasture", + icon: "🐄", + description: "Turns wheat into livestock", + cost: 0, + color: "#A52A2A", + inputs: [{ resource: "wheat", position: Position.Left }], + outputs: [{ resource: "livestock", position: Position.Right }], + productionMethod: { + name: "Free Range Grazing", + inputs: [{ resource: "wheat", amount: 25 }], + outputs: [{ resource: "livestock", amount: 1 }], + days: 5, + }, + stockpile: [ + { resource: "wheat", amount: 0, maxAmount: 250 }, + { resource: "livestock", amount: 0, maxAmount: 10 }, + ], + }, + { + id: "butchery", + name: "Butchery", + icon: "🔪", + description: "Processes livestock", + cost: 0, + color: "#8B0000", + inputs: [{ resource: "livestock", position: Position.Left }], + outputs: [ + { resource: "meat", position: Position.Right }, + { resource: "leather", position: Position.Right }, + ], + productionMethod: { + name: "Carving Knives", + inputs: [{ resource: "livestock", amount: 1 }], + outputs: [ + { resource: "meat", amount: 5 }, + { resource: "leather", amount: 2 }, + ], + days: 3, + }, + stockpile: [ + { resource: "livestock", amount: 0, maxAmount: 10 }, + { resource: "meat", amount: 0, maxAmount: 50 }, + { resource: "leather", amount: 0, maxAmount: 20 }, + ], + }, + { + id: "bookbinder", + name: "Bookbinder", + icon: "📚", + description: "Produces books", + cost: 0, + color: "#4B0082", + inputs: [ + { resource: "paper", position: Position.Left }, + { resource: "leather", position: Position.Left }, + ], + outputs: [{ resource: "books", position: Position.Right }], + productionMethod: { + name: "Bookbinding", + inputs: [ + { resource: "paper", amount: 20 }, + { resource: "leather", amount: 2 }, + ], + outputs: [{ resource: "books", amount: 1 }], + days: 5, + }, + stockpile: [ + { resource: "paper", amount: 0, maxAmount: 200 }, + { resource: "leather", amount: 0, maxAmount: 20 }, + { resource: "books", amount: 0, maxAmount: 10 }, + ], + }, + { + id: "library", + name: "Library", + icon: "🏛️", + description: "Centre of knowledge", + cost: 0, + color: "#483D8B", + inputs: [ + { resource: "paper", position: Position.Top }, + { resource: "books", position: Position.Left }, + ], + productionMethod: { + name: "Research", + inputs: [ + { resource: "paper", amount: 10 }, + { resource: "books", amount: 1 }, + ], + outputs: [], + globalOutputs: [{ resource: "knowledge", amount: 1 }], + days: 1, + }, + stockpile: [ + { resource: "paper", amount: 0, maxAmount: 100 }, + { resource: "books", amount: 0, maxAmount: 10 }, + ], + }, + { + id: "mill", + name: "Mill", + icon: "🏭", + description: "Grinds wheat into flour", + cost: 0, + color: "#DAA520", + inputs: [ + { resource: "wheat", position: Position.Left }, + { resource: "wheat", position: Position.Left }, + ], + outputs: [{ resource: "flour", position: Position.Right }], + productionMethod: { + name: "Grinding Stones", + inputs: [{ resource: "wheat", amount: 40 }], + outputs: [{ resource: "flour", amount: 50 }], + days: 2, + }, + stockpile: [ + { resource: "wheat", amount: 0, maxAmount: 400 }, + { resource: "flour", amount: 0, maxAmount: 500 }, + ], + }, + { + id: "kiln", + name: "Kiln", + icon: "🏭", + description: "Burns wood into coal", + cost: 0, + color: "#36454F", + inputs: [{ resource: "wood", position: Position.Left }], + outputs: [{ resource: "coal", position: Position.Right }], + productionMethod: { + name: "Charcoal Burning", + inputs: [{ resource: "wood", amount: 50 }], + outputs: [{ resource: "coal", amount: 50 }], + days: 2, + }, + stockpile: [ + { resource: "wood", amount: 0, maxAmount: 50 }, + { resource: "coal", amount: 0, maxAmount: 50 }, + ], + }, + { + id: "bakery", + name: "Bakery", + icon: "🍞", + description: "Bakes flour into bread", + cost: 0, + color: "#F5DEB3", + inputs: [ + { resource: "flour", position: Position.Left }, + { resource: "coal", position: Position.Top }, + ], + outputs: [{ resource: "bread", position: Position.Right }], + productionMethod: { + name: "Oven Baking", + inputs: [ + { resource: "flour", amount: 20 }, + { resource: "coal", amount: 10 }, + ], + outputs: [{ resource: "bread", amount: 5 }], + days: 1, + }, + stockpile: [ + { resource: "flour", amount: 0, maxAmount: 200 }, + { resource: "bread", amount: 0, maxAmount: 50 }, + { resource: "coal", amount: 0, maxAmount: 100 }, + ], }, ]; diff --git a/src/components/Factory/data/resources.ts b/src/components/Factory/data/resources.ts index 3b4f0a03a..ce8c8c6b8 100644 --- a/src/components/Factory/data/resources.ts +++ b/src/components/Factory/data/resources.ts @@ -1,17 +1,113 @@ import type { Resource, ResourceType } from "../types/resources"; export const RESOURCE_COLORS: Record = { + coins: "#DAA520", wood: "#8B4513", stone: "#708090", wheat: "#F4A460", planks: "#D2691E", + paper: "#FFFFE0", + books: "#FFD700", + livestock: "#A52A2A", + leather: "#DEB887", + meat: "#FF6347", + knowledge: "#6A5ACD", + coal: "#36454F", + flour: "#FFF8DC", + bread: "#F5DEB3", any: "#FFFFFF", }; export const RESOURCES: Record = { - wood: { type: "wood", color: RESOURCE_COLORS.wood }, - stone: { type: "stone", color: RESOURCE_COLORS.stone }, - wheat: { type: "wheat", color: RESOURCE_COLORS.wheat }, - planks: { type: "planks", color: RESOURCE_COLORS.planks }, - any: { type: "any", color: RESOURCE_COLORS.any }, + coins: { + name: "coins", + description: "Coins are a form of currency used for trade.", + color: RESOURCE_COLORS.coins, + icon: "💰", + }, + wood: { + name: "wood", + description: "Wood is a basic building material.", + color: RESOURCE_COLORS.wood, + icon: "🪵", + }, + stone: { + name: "stone", + description: "Stone is a durable building material.", + color: RESOURCE_COLORS.stone, + icon: "🪨", + }, + wheat: { + name: "wheat", + description: "Wheat is a staple crop used for food production.", + color: RESOURCE_COLORS.wheat, + icon: "🌾", + }, + planks: { + name: "planks", + description: "Planks are processed wood used for construction.", + color: RESOURCE_COLORS.planks, + icon: "🪚", + }, + paper: { + name: "paper", + description: "Paper is used for writing and record-keeping.", + color: RESOURCE_COLORS.paper, + icon: "📄", + }, + books: { + name: "books", + description: "Books contain knowledge and information.", + color: RESOURCE_COLORS.books, + icon: "📚", + }, + livestock: { + name: "livestock", + description: "Livestock are animals raised for food and materials.", + color: RESOURCE_COLORS.livestock, + icon: "🐄", + }, + leather: { + name: "leather", + description: "Leather is a durable material made from animal hides.", + color: RESOURCE_COLORS.leather, + icon: "👞", + }, + meat: { + name: "meat", + description: "Meat is a source of food and nutrition.", + color: RESOURCE_COLORS.meat, + icon: "🍖", + }, + knowledge: { + name: "knowledge", + description: "Knowledge represents the understanding and information.", + color: RESOURCE_COLORS.knowledge, + icon: "🧠", + }, + coal: { + name: "coal", + description: "Coal is a fossil fuel used for energy production.", + color: RESOURCE_COLORS.coal, + icon: "🪨", + }, + flour: { + name: "flour", + description: + "Flour is a powder made from grinding grains, used for baking.", + color: RESOURCE_COLORS.flour, + icon: "🥖", + }, + bread: { + name: "bread", + description: "Bread is a staple food made from flour and water.", + color: RESOURCE_COLORS.bread, + icon: "🍞", + }, + any: { + name: "any", + description: "Represents any type of resource.", + color: RESOURCE_COLORS.any, + icon: "❓", + }, }; diff --git a/src/components/Factory/types/buildings.ts b/src/components/Factory/types/buildings.ts index f91907ecc..cda18616c 100644 --- a/src/components/Factory/types/buildings.ts +++ b/src/components/Factory/types/buildings.ts @@ -12,6 +12,29 @@ export type BuildingOutput = { position: Position; }; +export type ProductionMethod = { + name: string; + inputs: Array<{ + resource: ResourceType; + amount: number; + }>; + outputs: Array<{ + resource: ResourceType; + amount: number; + }>; + globalOutputs?: Array<{ + resource: ResourceType; + amount: number; + }>; + days: number; +}; + +export type Stockpile = { + resource: ResourceType; + amount: number; + maxAmount: number; +}; + export type BuildingType = | "woodcutter" | "quarry" @@ -28,6 +51,8 @@ export interface Building { color: string; inputs?: BuildingInput[]; outputs?: BuildingOutput[]; + productionMethod?: ProductionMethod; + stockpile?: Stockpile[]; } export function isBuildingData(data: any): data is Building { diff --git a/src/components/Factory/types/resources.ts b/src/components/Factory/types/resources.ts index 3754af594..83861ad8c 100644 --- a/src/components/Factory/types/resources.ts +++ b/src/components/Factory/types/resources.ts @@ -1,9 +1,26 @@ -export type ResourceType = "wood" | "stone" | "wheat" | "planks" | "any"; +export type ResourceType = + | "wood" + | "stone" + | "wheat" + | "planks" + | "paper" + | "books" + | "livestock" + | "leather" + | "meat" + | "knowledge" + | "coins" + | "coal" + | "flour" + | "bread" + | "any"; export interface Resource { - type: ResourceType; - quantity?: number; + name: string; + description: string; color: string; + icon: string; + quantity?: number; } const RESOURCE_TYPES = [ @@ -11,6 +28,16 @@ const RESOURCE_TYPES = [ "stone", "wheat", "planks", + "paper", + "books", + "livestock", + "leather", + "meat", + "knowledge", + "coins", + "coal", + "flour", + "bread", "any", ] as const satisfies readonly ResourceType[]; @@ -22,7 +49,9 @@ export function isResourceData(data: any): data is Resource { return ( typeof data === "object" && data !== null && - typeof data.type === "string" && - typeof data.color === "string" + typeof data.color === "string" && + typeof data.name === "string" && + typeof data.description === "string" && + typeof data.icon === "string" ); }