diff --git a/src/components/Factory/Context/Building/BuildingContext.tsx b/src/components/Factory/Context/Building/BuildingContext.tsx
index 82e4f441a..808bc1fb1 100644
--- a/src/components/Factory/Context/Building/BuildingContext.tsx
+++ b/src/components/Factory/Context/Building/BuildingContext.tsx
@@ -67,6 +67,7 @@ const BuildingContext = ({ building, nodeId }: BuildingContextProps) => {
{
if (productionMethod.inputs.length === 0) return null;
+ const isSpecialBuilding = SPECIAL_BUILDINGS.includes(buildingType);
+
+ // Determine which global resource this special building outputs
+ const globalOutput = isSpecialBuilding
+ ? productionMethod.outputs.find(
+ (o) =>
+ o.resource === "money" ||
+ o.resource === "food" ||
+ o.resource === "knowledge",
+ )?.resource
+ : null;
+
return (
@@ -24,12 +43,40 @@ export const ProductionInputs = ({
) : (
<>
- • {input.amount}x {input.resource}
-
-
- ({RESOURCES.money.icon}{" "}
- {input.amount * RESOURCES[input.resource].value})
+ {`• ${!isSpecialBuilding ? input.amount + "x " : ""}${input.resource}`}
+ {isSpecialBuilding ? (
+
+ (
+ {globalOutput === "money" && (
+ <>
+ {RESOURCES.money.icon} {RESOURCES[input.resource].value}
+ >
+ )}
+ {globalOutput === "food" && (
+ <>
+ {RESOURCES.food.icon}{" "}
+ {getResourceTypeFoodValue(input.resource)}
+ >
+ )}
+ {globalOutput === "knowledge" && (
+ <>
+ {RESOURCES.knowledge.icon}{" "}
+ {/* Knowledge buildings might have custom logic */}
+ >
+ )}
+ )
+
+ ) : (
+ // For regular buildings, show money value and food value (if applicable)
+
+ ({RESOURCES.money.icon}{" "}
+ {input.amount * RESOURCES[input.resource].value}{" "}
+ {getResourceTypeFoodValue(input.resource) > 0 &&
+ `/ ${RESOURCES.food.icon} ${getResourceTypeFoodValue(input.resource) * input.amount}`}
+ )
+
+ )}
>
)}
diff --git a/src/components/Factory/Context/Building/Production/Outputs.tsx b/src/components/Factory/Context/Building/Production/Outputs.tsx
index 02f5d6a1a..4b32c191c 100644
--- a/src/components/Factory/Context/Building/Production/Outputs.tsx
+++ b/src/components/Factory/Context/Building/Production/Outputs.tsx
@@ -1,7 +1,11 @@
import { BlockStack, InlineStack } from "@/components/ui/layout";
import { Text } from "@/components/ui/typography";
-import { isGlobalResource, RESOURCES } from "../../../data/resources";
+import {
+ getResourceTypeFoodValue,
+ isGlobalResource,
+ RESOURCES,
+} from "../../../data/resources";
import type { ProductionMethod } from "../../../types/production";
interface ProductionOutputsProps {
@@ -36,7 +40,10 @@ export const ProductionOutputs = ({
({RESOURCES.money.icon}{" "}
- {output.amount * RESOURCES[output.resource].value})
+ {output.amount * RESOURCES[output.resource].value}{" "}
+ {getResourceTypeFoodValue(output.resource) > 0 &&
+ `/ ${RESOURCES.food.icon} ${getResourceTypeFoodValue(output.resource) * output.amount}`}
+ )
);
diff --git a/src/components/Factory/Context/Building/Production/ProductionMethodSection.tsx b/src/components/Factory/Context/Building/Production/ProductionMethodSection.tsx
index 8237c5746..865dadb11 100644
--- a/src/components/Factory/Context/Building/Production/ProductionMethodSection.tsx
+++ b/src/components/Factory/Context/Building/Production/ProductionMethodSection.tsx
@@ -13,6 +13,7 @@ import { ProductionMethodSwitcher } from "./ProductionMethodSwitcher";
import { ProductionProgress } from "./Progress";
interface ProductionMethodSectionProps {
+ buildingType: string;
productionMethod?: ProductionMethod;
productionState?: ProductionState;
availableMethods?: ProductionMethod[];
@@ -20,6 +21,7 @@ interface ProductionMethodSectionProps {
}
export const ProductionMethodSection = ({
+ buildingType,
productionMethod,
productionState,
availableMethods = [],
@@ -59,7 +61,10 @@ export const ProductionMethodSection = ({
-
+
= {
+ special: "Special",
+ production: "Production",
+ refining: "Refining",
+ utility: "Utility",
+ storage: "Storage",
+};
+
+const CATEGORY_ICONS: Record = {
+ special: "Star",
+ production: "Hammer",
+ refining: "Factory",
+ utility: "Wrench",
+ storage: "Package",
+};
+
+type BuildingFolderProps = {
+ category: BuildingCategory;
+ buildings: BuildingType[];
+};
+
+const BuildingFolder = ({ category, buildings }: BuildingFolderProps) => {
+ const [isOpen, setIsOpen] = useState(false);
+
+ return (
+
+
+
+
+
+ {CATEGORY_LABELS[category]}
+
+ ({buildings.length})
+
+
+
+
+
+
+ {buildings.map((buildingType) => (
+
+ ))}
+
+
+
+ );
+};
+
+export default BuildingFolder;
diff --git a/src/components/Factory/Sidebar/Buildings.tsx b/src/components/Factory/Sidebar/Buildings.tsx
index 62449a1e6..3f4eb5ed1 100644
--- a/src/components/Factory/Sidebar/Buildings.tsx
+++ b/src/components/Factory/Sidebar/Buildings.tsx
@@ -1,20 +1,64 @@
import { BlockStack } from "@/components/ui/layout";
import { Text } from "@/components/ui/typography";
+import { BUILDINGS } from "../data/buildings";
+import type { BuildingCategory, BuildingType } from "../types/buildings";
import { BUILDING_TYPES } from "../types/buildings";
-import BuildingItem from "./BuildingItem";
+import BuildingFolder from "./BuildingFolder";
+
+const CATEGORY_ORDER: BuildingCategory[] = [
+ "special",
+ "production",
+ "refining",
+ "utility",
+ "storage",
+];
const Buildings = () => {
+ // Group buildings by category
+ const buildingsByCategory = getBuildingsByCategory();
+
return (
Buildings
-
- {BUILDING_TYPES.map((buildingType) => (
-
- ))}
+
+ {CATEGORY_ORDER.map((category) => {
+ const buildings = buildingsByCategory.get(category) || [];
+
+ if (buildings.length === 0) return null;
+
+ return (
+
+ );
+ })}
);
};
export default Buildings;
+
+function getBuildingsByCategory() {
+ const grouped = new Map();
+
+ CATEGORY_ORDER.forEach((category) => {
+ grouped.set(category, []);
+ });
+
+ BUILDING_TYPES.forEach((buildingType) => {
+ const building = BUILDINGS[buildingType];
+ const category = building.category;
+
+ if (!grouped.has(category)) {
+ grouped.set(category, []);
+ }
+
+ grouped.get(category)!.push(buildingType);
+ });
+
+ return grouped;
+}
diff --git a/src/components/Factory/data/buildings.ts b/src/components/Factory/data/buildings.ts
index ebc48432c..4c7c02855 100644
--- a/src/components/Factory/data/buildings.ts
+++ b/src/components/Factory/data/buildings.ts
@@ -1,12 +1,38 @@
import type { BuildingClass } from "../types/buildings";
+export const SPECIAL_BUILDINGS = ["firepit", "marketplace", "granary"]; // Buildings with special processing logic that doesn't fit the standard production model
+
export const BUILDINGS: Record = {
+ firepit: {
+ name: "Firepit",
+ icon: "🔥",
+ description: "The centre of civilization",
+ cost: 0,
+ color: "#FF4500",
+ category: "special",
+ productionMethods: [
+ {
+ name: "Survival",
+ inputs: [
+ { resource: "berries", amount: 20 },
+ { resource: "fish", amount: 10 },
+ { resource: "meat", amount: 5 },
+ ],
+ outputs: [
+ { resource: "food", amount: 1 },
+ { resource: "knowledge", amount: 1 },
+ ],
+ days: 1,
+ },
+ ],
+ },
marketplace: {
name: "Marketplace",
- icon: "🏛️",
- description: "Sell goods here",
+ icon: "💲",
+ description: "Sell low, buy high!",
cost: 0,
color: "#FBBF24",
+ category: "special",
productionMethods: [
{
name: "Trading",
@@ -16,19 +42,70 @@ export const BUILDINGS: Record = {
},
],
},
+ library: {
+ name: "Library",
+ icon: "🎓",
+ description: "Nexus of knowledge",
+ cost: 0,
+ color: "#483D8B",
+ category: "special",
+ productionMethods: [
+ {
+ name: "Writing",
+ inputs: [{ resource: "paper", amount: 80, nodes: 4 }],
+ outputs: [{ resource: "knowledge", amount: 1 }],
+ days: 2,
+ },
+ {
+ name: "Reading",
+ inputs: [{ resource: "books", amount: 2, nodes: 2 }],
+ outputs: [{ resource: "knowledge", amount: 1 }],
+ days: 1,
+ },
+ ],
+ },
+ well: {
+ name: "Well",
+ icon: "💧",
+ description: "Provides water",
+ cost: 0,
+ color: "#1E90FF",
+ category: "production",
+ productionMethods: [
+ {
+ name: "Drawing Water",
+ inputs: [],
+ outputs: [{ resource: "water", amount: 100, nodes: 4 }],
+ days: 1,
+ },
+ ],
+ },
woodcutter: {
name: "Woodcutter's Camp",
icon: "🪓",
description: "Produces wood",
cost: 0,
color: "#A0522D",
+ category: "production",
productionMethods: [
{
- name: "Hand Axes",
+ name: "Gathering Sticks",
inputs: [],
outputs: [{ resource: "wood", amount: 10 }],
days: 1,
},
+ {
+ name: "Stone Axes",
+ inputs: [{ resource: "stone", amount: 1 }],
+ outputs: [{ resource: "wood", amount: 20 }],
+ days: 1,
+ },
+ {
+ name: "Hand Saw",
+ inputs: [{ resource: "tools", amount: 2 }],
+ outputs: [{ resource: "wood", amount: 50, nodes: 2 }],
+ days: 1,
+ },
],
},
quarry: {
@@ -37,11 +114,18 @@ export const BUILDINGS: Record = {
description: "Produces stone",
cost: 0,
color: "#708090",
+ category: "production",
productionMethods: [
{
- name: "Hand Axes",
+ name: "Gathering Stones",
inputs: [],
- outputs: [{ resource: "stone", amount: 5, nodes: 2 }],
+ outputs: [{ resource: "stone", amount: 5 }],
+ days: 1,
+ },
+ {
+ name: "Hammer and Chisel",
+ inputs: [{ resource: "tools", amount: 2 }],
+ outputs: [{ resource: "stone", amount: 20, nodes: 2 }],
days: 1,
},
],
@@ -52,6 +136,7 @@ export const BUILDINGS: Record = {
description: "Produces wheat",
cost: 0,
color: "#228B22",
+ category: "production",
productionMethods: [
{
name: "Wheat",
@@ -59,6 +144,18 @@ export const BUILDINGS: Record = {
outputs: [{ resource: "wheat", amount: 60 }],
days: 3,
},
+ {
+ name: "Orchard",
+ inputs: [],
+ outputs: [{ resource: "berries", amount: 100, nodes: 4 }],
+ days: 2,
+ },
+ {
+ name: "Irrigation",
+ inputs: [{ resource: "water", amount: 60, nodes: 3 }],
+ outputs: [{ resource: "wheat", amount: 200, nodes: 4 }],
+ days: 3,
+ },
],
},
sawmill: {
@@ -67,10 +164,14 @@ export const BUILDINGS: Record = {
description: "Turns wood into planks",
cost: 0,
color: "#D2691E",
+ category: "refining",
productionMethods: [
{
- name: "Sandpaper and Hand Saw",
- inputs: [{ resource: "wood", amount: 40, nodes: 2 }],
+ name: "Sandpaper and Saw",
+ inputs: [
+ { resource: "wood", amount: 40, nodes: 2 },
+ { resource: "tools", amount: 1 },
+ ],
outputs: [{ resource: "planks", amount: 20 }],
days: 2,
},
@@ -82,6 +183,7 @@ export const BUILDINGS: Record = {
description: "Turns wood into paper",
cost: 0,
color: "#6A5ACD",
+ category: "refining",
productionMethods: [
{
name: "Pulp and Press",
@@ -94,16 +196,26 @@ export const BUILDINGS: Record = {
pasture: {
name: "Pasture",
icon: "🐄",
- description: "Turns wheat into livestock",
+ description: "Raises livestock",
cost: 0,
color: "#A52A2A",
+ category: "production",
productionMethods: [
{
- name: "Free Range Grazing",
+ name: "Grazing",
inputs: [{ resource: "wheat", amount: 25 }],
outputs: [{ resource: "livestock", amount: 1 }],
days: 10,
},
+ {
+ name: "Feeding",
+ inputs: [
+ { resource: "wheat", amount: 10 },
+ { resource: "water", amount: 20 },
+ ],
+ outputs: [{ resource: "livestock", amount: 1 }],
+ days: 10,
+ },
],
},
butchery: {
@@ -112,6 +224,7 @@ export const BUILDINGS: Record = {
description: "Processes livestock",
cost: 0,
color: "#8B0000",
+ category: "refining",
productionMethods: [
{
name: "Carving Knives",
@@ -130,6 +243,7 @@ export const BUILDINGS: Record = {
description: "Produces books",
cost: 0,
color: "#4B0082",
+ category: "refining",
productionMethods: [
{
name: "Bookbinding",
@@ -142,33 +256,13 @@ export const BUILDINGS: Record = {
},
],
},
- library: {
- name: "Library",
- icon: "🏛️",
- description: "Centre of knowledge",
- cost: 0,
- color: "#483D8B",
- productionMethods: [
- {
- name: "Writing",
- inputs: [{ resource: "paper", amount: 80, nodes: 4 }],
- outputs: [{ resource: "knowledge", amount: 1 }],
- days: 2,
- },
- {
- name: "Reading",
- inputs: [{ resource: "books", amount: 2, nodes: 2 }],
- outputs: [{ resource: "knowledge", amount: 1 }],
- days: 1,
- },
- ],
- },
mill: {
name: "Mill",
icon: "🏭",
description: "Grinds wheat into flour",
cost: 0,
color: "#DAA520",
+ category: "refining",
productionMethods: [
{
name: "Grinding Stones",
@@ -184,11 +278,12 @@ export const BUILDINGS: Record = {
description: "Burns wood into coal",
cost: 0,
color: "#36454F",
+ category: "refining",
productionMethods: [
{
name: "Charcoal Burning",
- inputs: [{ resource: "wood", amount: 50 }],
- outputs: [{ resource: "coal", amount: 50 }],
+ inputs: [{ resource: "wood", amount: 50, nodes: 2 }],
+ outputs: [{ resource: "coal", amount: 50, nodes: 2 }],
days: 2,
},
],
@@ -199,12 +294,14 @@ export const BUILDINGS: Record = {
description: "Bakes flour into bread",
cost: 0,
color: "#F5DEB3",
+ category: "refining",
productionMethods: [
{
name: "Oven Baking",
inputs: [
{ resource: "flour", amount: 10 },
- { resource: "coal", amount: 10 },
+ { resource: "water", amount: 10 },
+ { resource: "coal", amount: 5 },
],
outputs: [{ resource: "bread", amount: 10 }],
days: 2,
@@ -214,16 +311,217 @@ export const BUILDINGS: Record = {
bank: {
name: "Bank",
icon: "🏦",
- description: "Generates money over time",
+ description: "Too big to fail",
cost: 100,
color: "#FFD700",
+ category: "utility",
+ productionMethods: [
+ {
+ name: "Deposit Coins",
+ inputs: [{ resource: "coins", amount: 100, nodes: 4 }],
+ outputs: [{ resource: "money", amount: 500 }],
+ days: 10,
+ },
+ ],
+ },
+ foraging: {
+ name: "Foraging Camp",
+ icon: "🍇",
+ description: "Gathers wild resources",
+ cost: 0,
+ color: "#32CD32",
+ category: "production",
+ productionMethods: [
+ {
+ name: "Gather Berries",
+ inputs: [],
+ outputs: [{ resource: "berries", amount: 10 }],
+ days: 1,
+ },
+ ],
+ },
+ fishing: {
+ name: "Fishing Dock",
+ icon: "🎣",
+ description: "Catches fish from the water",
+ cost: 0,
+ color: "#1E90FF",
+ category: "production",
productionMethods: [
{
- name: "Minting",
+ name: "Handing Nets",
inputs: [],
- outputs: [{ resource: "money", amount: 5 }],
+ outputs: [{ resource: "fish", amount: 5 }],
+ days: 1,
+ },
+ {
+ name: "Spearing Fish",
+ inputs: [{ resource: "tools", amount: 2 }],
+ outputs: [{ resource: "fish", amount: 20, nodes: 2 }],
+ days: 1,
+ },
+ ],
+ },
+ hunting: {
+ name: "Hunting Lodge",
+ icon: "🏹",
+ description: "Hunts wild animals for resources",
+ cost: 0,
+ color: "#8B4513",
+ category: "production",
+ productionMethods: [
+ {
+ name: "Hunting",
+ inputs: [{ resource: "tools", amount: 2 }],
+ outputs: [{ resource: "meat", amount: 5 }],
+ days: 1,
+ },
+ ],
+ },
+ granary: {
+ name: "Granary",
+ icon: "🫙",
+ description: "Stores food for future use",
+ cost: 0,
+ color: "#FFD700",
+ category: "storage",
+ productionMethods: [
+ {
+ name: "Storing Wheat",
+ inputs: [{ resource: "wheat", amount: 1000, nodes: 4 }],
+ outputs: [{ resource: "food", amount: 1 }],
+ days: 1,
+ },
+ {
+ name: "Storing Bread",
+ inputs: [{ resource: "bread", amount: 200, nodes: 4 }],
+ outputs: [{ resource: "food", amount: 1 }],
+ days: 1,
+ },
+ ],
+ },
+ smelter: {
+ name: "Smelter",
+ icon: "🏭",
+ description: "Smelts ores into metal",
+ cost: 0,
+ color: "#B22222",
+ category: "refining",
+ productionMethods: [
+ {
+ name: "Bronze",
+ inputs: [
+ { resource: "copper", amount: 10 },
+ { resource: "tin", amount: 10 },
+ ],
+ outputs: [{ resource: "bronze", amount: 10 }],
+ days: 2,
+ },
+ {
+ name: "Steel",
+ inputs: [
+ { resource: "iron", amount: 20 },
+ { resource: "coal", amount: 40, nodes: 2 },
+ ],
+ outputs: [{ resource: "steel", amount: 10 }],
+ days: 4,
+ },
+ ],
+ },
+ toolsmith: {
+ name: "Toolsmith",
+ icon: "🛠️",
+ description: "Crafts tools for various purposes",
+ cost: 0,
+ color: "#8B4513",
+ category: "refining",
+ productionMethods: [
+ {
+ name: "Stone Tools",
+ inputs: [
+ { resource: "wood", amount: 10 },
+ { resource: "stone", amount: 5 },
+ ],
+ outputs: [{ resource: "tools", amount: 1 }],
+ days: 4,
+ },
+ {
+ name: "Bronze Tools",
+ inputs: [
+ { resource: "wood", amount: 10 },
+ { resource: "bronze", amount: 5 },
+ ],
+ outputs: [{ resource: "tools", amount: 2 }],
days: 3,
},
+ {
+ name: "Iron Tools",
+ inputs: [
+ { resource: "wood", amount: 10 },
+ { resource: "iron", amount: 5 },
+ ],
+ outputs: [{ resource: "tools", amount: 4, nodes: 2 }],
+ days: 2,
+ },
+ {
+ name: "Steel Tools",
+ inputs: [
+ { resource: "wood", amount: 10 },
+ { resource: "steel", amount: 5 },
+ ],
+ outputs: [{ resource: "tools", amount: 8, nodes: 2 }],
+ days: 1,
+ },
+ ],
+ },
+ mine: {
+ name: "Mine",
+ icon: "⛏️",
+ description: "Extracts minerals from the earth",
+ cost: 0,
+ color: "#708090",
+ category: "production",
+ productionMethods: [
+ {
+ name: "Mining Copper",
+ inputs: [{ resource: "tools", amount: 1 }],
+ outputs: [{ resource: "copper", amount: 10 }],
+ days: 2,
+ },
+ {
+ name: "Mining Tin",
+ inputs: [{ resource: "tools", amount: 1 }],
+ outputs: [{ resource: "tin", amount: 10 }],
+ days: 2,
+ },
+ {
+ name: "Mining Iron",
+ inputs: [{ resource: "tools", amount: 2 }],
+ outputs: [{ resource: "iron", amount: 10 }],
+ days: 2,
+ },
+ {
+ name: "Mining Coal",
+ inputs: [{ resource: "tools", amount: 4 }],
+ outputs: [{ resource: "coal", amount: 50, nodes: 2 }],
+ days: 2,
+ },
+ ],
+ },
+ mint: {
+ name: "Mint",
+ icon: "🏦",
+ description: "Produces coins from metal",
+ cost: 0,
+ color: "#FFD700",
+ category: "refining",
+ productionMethods: [
+ {
+ name: "Copper Coins",
+ inputs: [{ resource: "copper", amount: 1 }],
+ outputs: [{ resource: "coins", amount: 10 }],
+ days: 1,
+ },
],
},
} as const satisfies Record;
diff --git a/src/components/Factory/data/resources.ts b/src/components/Factory/data/resources.ts
index b7be0b368..c58bf782d 100644
--- a/src/components/Factory/data/resources.ts
+++ b/src/components/Factory/data/resources.ts
@@ -1,8 +1,11 @@
-import type { Resource, ResourceType } from "../types/resources";
+import { type Resource, type ResourceType } from "../types/resources";
export const RESOURCE_COLORS: Record = {
+ any: "#FFFFFF",
money: "#FFD700",
knowledge: "#6A5ACD",
+ food: "#ADFF2F",
+ water: "#1E90FF",
coins: "#DAA520",
wood: "#8B4513",
stone: "#708090",
@@ -16,17 +19,27 @@ export const RESOURCE_COLORS: Record = {
coal: "#36454F",
flour: "#FFF8DC",
bread: "#F5DEB3",
- any: "#FFFFFF",
+ berries: "#FF69B4",
+ fish: "#1E90FF",
+ tools: "#808080",
+ tin: "#B0C4DE",
+ copper: "#B87333",
+ bronze: "#CD7F32",
+ iron: "#A9A9A9",
+ steel: "#C0C0C0",
};
export const RESOURCE_VALUES: Record = {
+ any: 1,
money: 0,
knowledge: 0,
+ food: 0,
+ water: 0,
coins: 1,
wood: 2,
stone: 3,
wheat: 1,
- planks: 5,
+ planks: 7,
paper: 1,
books: 65,
livestock: 50,
@@ -35,81 +48,112 @@ export const RESOURCE_VALUES: Record = {
coal: 2,
flour: 3,
bread: 6,
- any: 1,
+ berries: 2,
+ fish: 4,
+ tools: 25,
+ tin: 4,
+ copper: 4,
+ bronze: 8,
+ iron: 16,
+ steel: 64,
};
export const RESOURCES = {
+ any: {
+ name: "Any",
+ description: "It could be anything!",
+ color: RESOURCE_COLORS.any,
+ value: RESOURCE_VALUES.any,
+ icon: "❓",
+ },
money: {
name: "Money",
description: "You need money to pay for things!",
color: RESOURCE_COLORS.money,
- icon: "💰",
value: RESOURCE_VALUES.money,
+ icon: "💰",
global: true,
},
knowledge: {
name: "Knowledge",
description: "Knowledge is the quest for a brighter future.",
color: RESOURCE_COLORS.knowledge,
- icon: "🧠",
value: RESOURCE_VALUES.knowledge,
+ icon: "🧠",
global: true,
},
+ food: {
+ name: "Food",
+ description: "Food is essential for survival and growth.",
+ color: RESOURCE_COLORS.food,
+ value: RESOURCE_VALUES.food,
+ foodValue: 1,
+ icon: "🍎",
+ global: true,
+ },
+ water: {
+ name: "Water",
+ description: "Water is essential for life and agriculture.",
+ color: RESOURCE_COLORS.water,
+ value: RESOURCE_VALUES.water,
+ icon: "💧",
+ },
coins: {
name: "Coins",
description: "Coins are a form of currency used for trade.",
color: RESOURCE_COLORS.coins,
- icon: "🪙",
value: RESOURCE_VALUES.coins,
+ icon: "🪙",
},
wood: {
name: "Wood",
description: "Wood is a basic building material.",
color: RESOURCE_COLORS.wood,
- icon: "🪵",
value: RESOURCE_VALUES.wood,
+ icon: "🪵",
},
stone: {
name: "Stone",
description: "Stone is a durable building material.",
color: RESOURCE_COLORS.stone,
- icon: "🪨",
value: RESOURCE_VALUES.stone,
+ icon: "🪨",
},
wheat: {
name: "Wheat",
description: "Wheat is a staple crop used for food production.",
color: RESOURCE_COLORS.wheat,
- icon: "🌾",
value: RESOURCE_VALUES.wheat,
+ foodValue: 1,
+ icon: "🌾",
},
planks: {
name: "Planks",
description: "Planks are processed wood used for construction.",
color: RESOURCE_COLORS.planks,
- icon: "🪚",
value: RESOURCE_VALUES.planks,
+ icon: "🪚",
},
paper: {
name: "Paper",
description: "Paper is used for writing and record-keeping.",
color: RESOURCE_COLORS.paper,
- icon: "📄",
value: RESOURCE_VALUES.paper,
+ icon: "📄",
},
books: {
name: "Books",
description: "Books contain knowledge and information.",
color: RESOURCE_COLORS.books,
- icon: "📚",
value: RESOURCE_VALUES.books,
+ icon: "📚",
},
livestock: {
name: "Livestock",
description: "Livestock are animals raised for food and materials.",
color: RESOURCE_COLORS.livestock,
- icon: "🐄",
value: RESOURCE_VALUES.livestock,
+ icon: "🐄",
},
leather: {
name: "Leather",
@@ -122,37 +166,94 @@ export const RESOURCES = {
name: "Meat",
description: "Meat is a source of food and nutrition.",
color: RESOURCE_COLORS.meat,
- icon: "🍖",
value: RESOURCE_VALUES.meat,
+ foodValue: 5,
+ icon: "🍖",
},
coal: {
name: "Coal",
description: "Coal is a fossil fuel used for energy production.",
color: RESOURCE_COLORS.coal,
- icon: "🪨",
value: RESOURCE_VALUES.coal,
+ icon: "♠️",
},
flour: {
name: "Flour",
description:
"Flour is a powder made from grinding grains, used for baking.",
color: RESOURCE_COLORS.flour,
- icon: "🥖",
value: RESOURCE_VALUES.flour,
+ icon: "🥖",
},
bread: {
name: "Bread",
description: "Bread is a staple food made from flour and water.",
color: RESOURCE_COLORS.bread,
- icon: "🍞",
value: RESOURCE_VALUES.bread,
+ foodValue: 10,
+ icon: "🍞",
},
- any: {
- name: "Any",
- description: "It could be anything!",
- color: RESOURCE_COLORS.any,
- icon: "❓",
- value: RESOURCE_VALUES.any,
+ berries: {
+ name: "Berries",
+ description: "Berries are small, juicy fruits that grow on bushes.",
+ color: RESOURCE_COLORS.berries,
+ value: RESOURCE_VALUES.berries,
+ foodValue: 1,
+ icon: "🍓",
+ },
+ fish: {
+ name: "Fish",
+ description: "Fish are aquatic animals that can be caught for food.",
+ color: RESOURCE_COLORS.fish,
+ value: RESOURCE_VALUES.fish,
+ foodValue: 2,
+ icon: "🐟",
+ },
+ tools: {
+ name: "Tools",
+ description: "Tools are used to perform tasks more efficiently.",
+ color: RESOURCE_COLORS.tools,
+ value: RESOURCE_VALUES.tools,
+ icon: "🛠️",
+ },
+ tin: {
+ name: "Tin",
+ description: "Tin is a metal used for making alloys like bronze.",
+ color: RESOURCE_COLORS.tin,
+ value: RESOURCE_VALUES.tin,
+ icon: "🔩",
+ },
+ copper: {
+ name: "Copper",
+ description:
+ "Copper is a versatile metal used in construction and electronics.",
+ color: RESOURCE_COLORS.copper,
+ value: RESOURCE_VALUES.copper,
+ icon: "🪙",
+ },
+ bronze: {
+ name: "Bronze",
+ description:
+ "Bronze is an alloy of copper and tin, used for tools and weapons.",
+ color: RESOURCE_COLORS.bronze,
+ value: RESOURCE_VALUES.bronze,
+ icon: "⚔️",
+ },
+ iron: {
+ name: "Iron",
+ description:
+ "Iron is a strong metal used for construction, tools, and machinery.",
+ color: RESOURCE_COLORS.iron,
+ value: RESOURCE_VALUES.iron,
+ icon: "⛓️",
+ },
+ steel: {
+ name: "Steel",
+ description:
+ "Steel is an alloy of iron and carbon, known for its strength and durability.",
+ color: RESOURCE_COLORS.steel,
+ value: RESOURCE_VALUES.steel,
+ icon: "🏗️",
},
} as const satisfies Record;
@@ -184,3 +285,32 @@ export const GLOBAL_RESOURCE_KEYS = (
resource !== undefined && "global" in resource && resource.global === true
);
});
+
+export type FoodProducingResourceType = {
+ [K in keyof typeof RESOURCES]: (typeof RESOURCES)[K] extends {
+ foodValue: number;
+ }
+ ? K
+ : never;
+}[keyof typeof RESOURCES];
+
+export function isFoodProducingResourceType(
+ resourceType: ResourceType,
+): resourceType is FoodProducingResourceType {
+ const resource = RESOURCES[resourceType];
+ return (
+ resource !== undefined &&
+ "foodValue" in resource &&
+ typeof resource.foodValue === "number" &&
+ resource.foodValue > 0
+ );
+}
+
+export function getResourceTypeFoodValue(resourceType: ResourceType): number {
+ if (!isFoodProducingResourceType(resourceType)) {
+ return 0;
+ }
+
+ const resource = RESOURCES[resourceType];
+ return resource.foodValue || 0;
+}
diff --git a/src/components/Factory/data/setup.ts b/src/components/Factory/data/setup.ts
index d94f76057..4b077e198 100644
--- a/src/components/Factory/data/setup.ts
+++ b/src/components/Factory/data/setup.ts
@@ -11,7 +11,7 @@ interface setup {
}
const buildings: BuildingSetup[] = [
- { type: "marketplace", position: { x: 0, y: 0 } },
+ { type: "firepit", position: { x: 0, y: 0 } },
];
export const setup: setup = {
diff --git a/src/components/Factory/objects/buildings/createBuildingInstance.ts b/src/components/Factory/objects/buildings/createBuildingInstance.ts
index 4ee21a4fa..0416ec400 100644
--- a/src/components/Factory/objects/buildings/createBuildingInstance.ts
+++ b/src/components/Factory/objects/buildings/createBuildingInstance.ts
@@ -36,6 +36,7 @@ export function createBuildingInstance(
description: building.description,
cost: building.cost,
color: building.color,
+ category: building.category,
...configuration,
};
}
diff --git a/src/components/Factory/objects/production/configureBuildingInstanceForMethod.ts b/src/components/Factory/objects/production/configureBuildingInstanceForMethod.ts
index 7da77f819..a9278f81e 100644
--- a/src/components/Factory/objects/production/configureBuildingInstanceForMethod.ts
+++ b/src/components/Factory/objects/production/configureBuildingInstanceForMethod.ts
@@ -59,8 +59,29 @@ export const configureBuildingInstanceForMethod = (
(input) => !isGlobalResource(input.resource),
);
+ // Count total non-global inputs and outputs
+ const totalInputNodes = productionMethod.inputs.reduce(
+ (sum, input) =>
+ isGlobalResource(input.resource) ? sum : sum + (input.nodes ?? 1),
+ 0,
+ );
+
+ const totalOutputNodes = productionMethod.outputs.reduce(
+ (sum, output) =>
+ isGlobalResource(output.resource) ? sum : sum + (output.nodes ?? 1),
+ 0,
+ );
+
+ // Determine if we should spread handles across all sides
+ const shouldSpreadInputs = !hasNonGlobalOutputs && totalInputNodes > 1;
+ const shouldSpreadOutputs = !hasNonGlobalInputs && totalOutputNodes > 1;
+
// Generate inputs from production method
const inputs: BuildingInput[] = [];
+ let inputPositionIndex = 0;
+ const inputPositions = shouldSpreadInputs
+ ? distributeHandlesAcrossSides(totalInputNodes)
+ : [];
productionMethod.inputs.forEach((input) => {
// Skip global resources - they don't need physical inputs
@@ -80,20 +101,19 @@ export const configureBuildingInstanceForMethod = (
resource: input.resource,
position: existingInput.position,
});
+ if (shouldSpreadInputs) inputPositionIndex++;
});
// If we need more inputs than we had before, create new ones
const remaining = nodeCount - existingInputsForResource.length;
if (remaining > 0) {
- const shouldSpread = !hasNonGlobalOutputs;
- if (shouldSpread) {
- const positions = distributeHandlesAcrossSides(remaining);
- positions.forEach((position) => {
+ if (shouldSpreadInputs) {
+ for (let i = 0; i < remaining; i++) {
inputs.push({
resource: input.resource,
- position,
+ position: inputPositions[inputPositionIndex++],
});
- });
+ }
} else {
for (let i = 0; i < remaining; i++) {
inputs.push({
@@ -105,16 +125,13 @@ export const configureBuildingInstanceForMethod = (
}
} else {
// No existing inputs, create new ones
- const shouldSpread = !hasNonGlobalOutputs;
-
- if (shouldSpread && nodeCount > 1) {
- const positions = distributeHandlesAcrossSides(nodeCount);
- positions.forEach((position) => {
+ if (shouldSpreadInputs) {
+ for (let i = 0; i < nodeCount; i++) {
inputs.push({
resource: input.resource,
- position,
+ position: inputPositions[inputPositionIndex++],
});
- });
+ }
} else {
for (let i = 0; i < nodeCount; i++) {
inputs.push({
@@ -128,6 +145,10 @@ export const configureBuildingInstanceForMethod = (
// Generate outputs from production method
const outputs: BuildingOutput[] = [];
+ let outputPositionIndex = 0;
+ const outputPositions = shouldSpreadOutputs
+ ? distributeHandlesAcrossSides(totalOutputNodes)
+ : [];
productionMethod.outputs.forEach((output) => {
// Skip global resources - they don't need physical outputs
@@ -150,20 +171,19 @@ export const configureBuildingInstanceForMethod = (
resource: output.resource,
position: existingOutput.position,
});
+ if (shouldSpreadOutputs) outputPositionIndex++;
});
// If we need more outputs than we had before, create new ones
const remaining = nodeCount - existingOutputsForResource.length;
if (remaining > 0) {
- const shouldSpread = !hasNonGlobalInputs;
- if (shouldSpread) {
- const positions = distributeHandlesAcrossSides(remaining);
- positions.forEach((position) => {
+ if (shouldSpreadOutputs) {
+ for (let i = 0; i < remaining; i++) {
outputs.push({
resource: output.resource,
- position,
+ position: outputPositions[outputPositionIndex++],
});
- });
+ }
} else {
for (let i = 0; i < remaining; i++) {
outputs.push({
@@ -175,16 +195,13 @@ export const configureBuildingInstanceForMethod = (
}
} else {
// No existing outputs, create new ones
- const shouldSpread = !hasNonGlobalInputs;
-
- if (shouldSpread && nodeCount > 1) {
- const positions = distributeHandlesAcrossSides(nodeCount);
- positions.forEach((position) => {
+ if (shouldSpreadOutputs) {
+ for (let i = 0; i < nodeCount; i++) {
outputs.push({
resource: output.resource,
- position,
+ position: outputPositions[outputPositionIndex++],
});
- });
+ }
} else {
for (let i = 0; i < nodeCount; i++) {
outputs.push({
diff --git a/src/components/Factory/simulation/helpers/processSpecialBuilding.ts b/src/components/Factory/simulation/helpers/processSpecialBuilding.ts
index 14cb79738..4e5124299 100644
--- a/src/components/Factory/simulation/helpers/processSpecialBuilding.ts
+++ b/src/components/Factory/simulation/helpers/processSpecialBuilding.ts
@@ -1,6 +1,11 @@
import type { Node } from "@xyflow/react";
-import { type GlobalResources, RESOURCE_VALUES } from "../../data/resources";
+import {
+ getResourceTypeFoodValue,
+ type GlobalResources,
+ isFoodProducingResourceType,
+ RESOURCE_VALUES,
+} from "../../data/resources";
import { getBuildingInstance } from "../../types/buildings";
import type { BuildingStatistics } from "../../types/statistics";
@@ -113,5 +118,84 @@ export const processSpecialBuilding = (
};
}
- // Add more special buildings here as needed
+ // Firepit & Granary: Converts resources with food value into food (and for fire pit, also knowledge)
+ if (building.type === "firepit" || building.type === "granary") {
+ if (!building.stockpile || building.stockpile.length === 0) {
+ node.data = {
+ ...node.data,
+ buildingInstance: {
+ ...building,
+ productionState: { progress: 0, status: "idle" },
+ },
+ };
+ return;
+ }
+
+ let totalFood = 0;
+ const updatedStockpile = building.stockpile.map((stock) => {
+ // Only process food-producing resources
+ if (isFoodProducingResourceType(stock.resource) && stock.amount > 0) {
+ const foodValue = getResourceTypeFoodValue(stock.resource);
+ const foodForThisResource = stock.amount * foodValue;
+ totalFood += foodForThisResource;
+
+ // Track that these resources were consumed
+ const change = stats.stockpileChanges.find(
+ (c) => c.resource === stock.resource,
+ );
+ if (change) {
+ change.removed += stock.amount;
+ change.net = change.added - change.removed;
+ } else {
+ stats.stockpileChanges.push({
+ resource: stock.resource,
+ removed: stock.amount,
+ added: 0,
+ net: -stock.amount,
+ });
+ }
+
+ // Clear this stockpile
+ return {
+ ...stock,
+ amount: 0,
+ };
+ }
+
+ return stock;
+ });
+
+ // Add food and knowledge to global outputs
+ if (totalFood > 0) {
+ earnedGlobalResources.food =
+ (earnedGlobalResources.food || 0) + totalFood;
+
+ // Track produced food in statistics
+ if (!stats.produced) {
+ stats.produced = {};
+ }
+ stats.produced.food = (stats.produced.food || 0) + totalFood;
+
+ // Also produce 1 knowledge when food is produced
+ if (building.type === "firepit") {
+ earnedGlobalResources.knowledge =
+ (earnedGlobalResources.knowledge || 0) + 1;
+ stats.produced.knowledge = (stats.produced.knowledge || 0) + 1;
+ }
+ }
+
+ const updatedBuilding = {
+ ...building,
+ stockpile: updatedStockpile,
+ productionState: {
+ progress: totalFood > 0 ? 1 : 0,
+ status: totalFood > 0 ? ("complete" as const) : ("idle" as const),
+ },
+ };
+
+ node.data = {
+ ...node.data,
+ buildingInstance: updatedBuilding,
+ };
+ }
};
diff --git a/src/components/Factory/simulation/processDay.ts b/src/components/Factory/simulation/processDay.ts
index d8af4c8f2..ba5c55a95 100644
--- a/src/components/Factory/simulation/processDay.ts
+++ b/src/components/Factory/simulation/processDay.ts
@@ -1,5 +1,6 @@
import type { Edge, Node } from "@xyflow/react";
+import { SPECIAL_BUILDINGS } from "../data/buildings";
import { GLOBAL_RESOURCE_KEYS, type GlobalResources } from "../data/resources";
import { getBuildingInstance } from "../types/buildings";
import type {
@@ -12,8 +13,6 @@ import { advanceProduction } from "./helpers/advanceProduction";
import { processSpecialBuilding } from "./helpers/processSpecialBuilding";
import { transferResourcesEvenlyDownstream } from "./helpers/transferResourcesEvenlyDownstream";
-const SPECIAL_BUILDINGS = ["marketplace"];
-
export const processDay = (
nodes: Node[],
edges: Edge[],
diff --git a/src/components/Factory/types/buildings.ts b/src/components/Factory/types/buildings.ts
index 737f6957e..d358a21a3 100644
--- a/src/components/Factory/types/buildings.ts
+++ b/src/components/Factory/types/buildings.ts
@@ -21,6 +21,13 @@ export type Stockpile = {
breakdown?: Map;
};
+export type BuildingCategory =
+ | "special"
+ | "production"
+ | "refining"
+ | "utility"
+ | "storage";
+
export interface BuildingClass {
name: string;
icon: string;
@@ -28,6 +35,7 @@ export interface BuildingClass {
cost: number;
color: string;
productionMethods: ProductionMethod[];
+ category: BuildingCategory;
}
export interface BuildingInstance extends Omit<
diff --git a/src/components/Factory/types/resources.ts b/src/components/Factory/types/resources.ts
index be38dc69f..dbbce7d4e 100644
--- a/src/components/Factory/types/resources.ts
+++ b/src/components/Factory/types/resources.ts
@@ -4,12 +4,16 @@ export interface Resource {
color: string;
icon: string;
value: number;
+ foodValue?: number;
global?: boolean;
}
const RESOURCE_TYPES = [
+ "any",
"money",
"knowledge",
+ "food",
+ "water",
"wood",
"stone",
"wheat",
@@ -23,7 +27,14 @@ const RESOURCE_TYPES = [
"coal",
"flour",
"bread",
- "any",
+ "berries",
+ "fish",
+ "tools",
+ "copper",
+ "tin",
+ "bronze",
+ "iron",
+ "steel",
] as const;
export type ResourceType = (typeof RESOURCE_TYPES)[number];