Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 13 additions & 5 deletions src/components/Factory/Canvas/GameCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import { useEffect, useRef, useState } from "react";

import { BlockStack } from "@/components/ui/layout";

import type { GlobalResources } from "../data/resources";
import { setup } from "../data/setup";
import { createBuildingNode } from "../objects/buildings/createBuildingNode";
import { useGlobalResources } from "../providers/GlobalResourcesProvider";
import { processDay } from "../simulation/processDay";
import type { DayStatistics } from "../types/statistics";
Expand All @@ -36,7 +38,7 @@ const edgeTypes: Record<string, ComponentType<any>> = {

interface GameCanvasProps extends ReactFlowProps {
onDayAdvance?: (
globalOutputs: Record<string, number>,
globalResources: GlobalResources,
statistics: DayStatistics,
) => void;
triggerAdvance?: number;
Expand All @@ -59,7 +61,13 @@ const GameCanvas = ({
const prevTriggerRef = useRef(0);

useEffect(() => {
setNodes(setup.buildings);
const newNodes = setup.buildings?.map((building) =>
createBuildingNode(building.type, building.position),
);

if (newNodes) {
setNodes(newNodes);
}
}, [setNodes]);

// Process day advancement
Expand All @@ -69,16 +77,16 @@ const GameCanvas = ({

prevTriggerRef.current = triggerAdvance;

const { updatedNodes, globalOutputs, statistics } = processDay(
const { updatedNodes, statistics } = processDay(
nodes,
edges,
currentDay,
resources,
);

setNodes(updatedNodes);
updateResources(globalOutputs);
onDayAdvance?.(globalOutputs, statistics);
updateResources(statistics.global.earned);
onDayAdvance?.(statistics.global.resources, statistics);
}, [
triggerAdvance,
currentDay,
Expand Down
8 changes: 6 additions & 2 deletions src/components/Factory/Canvas/Handles/BuildingInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import { cn } from "@/lib/utils";

import { RESOURCE_COLORS } from "../../data/resources";
import {
type Building,
type BuildingInput as BuildingInputConfig,
type BuildingInstance,
} from "../../types/buildings";
import { isLightColor } from "../../utils/color";
import { layoutHandleAtPosition } from "./utils";
Expand All @@ -25,7 +25,7 @@ const BuildingInput = ({
groupIndex,
totalInGroup,
}: {
building: Building;
building: BuildingInstance;
input: BuildingInputConfig;
selected?: boolean;
index: number;
Expand All @@ -34,6 +34,10 @@ const BuildingInput = ({
}) => {
const { resource, position } = input;

if (!position) {
return null;
}

return (
<Handle
type="target"
Expand Down
8 changes: 6 additions & 2 deletions src/components/Factory/Canvas/Handles/BuildingOutput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { cn } from "@/lib/utils";

import { RESOURCE_COLORS } from "../../data/resources";
import {
type Building,
type BuildingInstance,
type BuildingOutput as BuildingOutputConfig,
} from "../../types/buildings";
import { isLightColor } from "../../utils/color";
Expand All @@ -25,7 +25,7 @@ const BuildingOutput = ({
groupIndex,
totalInGroup,
}: {
building: Building;
building: BuildingInstance;
output: BuildingOutputConfig;
selected?: boolean;
index: number;
Expand All @@ -34,6 +34,10 @@ const BuildingOutput = ({
}) => {
const { resource, position } = output;

if (!position) {
return null;
}

return (
<Handle
type="source"
Expand Down
72 changes: 40 additions & 32 deletions src/components/Factory/Canvas/Nodes/Building.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ import { cn } from "@/lib/utils";
import { useContextPanel } from "@/providers/ContextPanelProvider";

import BuildingContext from "../../Context/BuildingContext";
import { RESOURCES } from "../../data/resources";
import { isBuildingData } from "../../types/buildings";
import { isGlobalResource } from "../../data/resources";
import type {
BuildingInput as BuildingInputType,
BuildingOutput as BuildingOutputType,
} from "../../types/buildings";
import { getBuildingInstance, isBuildingInstance } from "../../types/buildings";
import { rotateBuilding } from "../../utils/rotation";
import BuildingInput from "../Handles/BuildingInput";
import BuildingOutput from "../Handles/BuildingOutput";
Expand All @@ -28,7 +32,7 @@ const Building = ({ id, data, selected }: NodeProps) => {
if (!selected) return;

const handleKeyPress = (event: KeyboardEvent) => {
if (!isBuildingData(data)) return;
if (!isBuildingInstance(data)) return;

if (event.key === "r" || event.key === "R") {
event.preventDefault();
Expand All @@ -42,16 +46,17 @@ const Building = ({ id, data, selected }: NodeProps) => {
return () => window.removeEventListener("keydown", handleKeyPress);
}, [selected, id, data, updateNodeData, updateNodeInternals]);

// Handle context panel - update on data changes
useEffect(() => {
if (selected) {
// Get the latest node data from React Flow
const currentNode = getNode(id);
const currentData = currentNode?.data || data;
const buildingInstance = getBuildingInstance(data);

if (!isBuildingData(currentData)) return;
if (!buildingInstance) {
setContent(<InvalidBuildingNode />);
setContextPanelOpen(true);
return;
}

setContent(<BuildingContext building={currentData} nodeId={id} />);
setContent(<BuildingContext building={buildingInstance} nodeId={id} />);
setContextPanelOpen(true);
}

Expand All @@ -60,26 +65,22 @@ const Building = ({ id, data, selected }: NodeProps) => {
clearContent();
}
};
}, [
selected,
data,
id,
getNode,
setContent,
clearContent,
setContextPanelOpen,
]);

if (!isBuildingData(data)) {
return (
<div className="px-6 py-4 shadow-lg rounded-lg bg-red-50 border-4 border-red-500">
<div className="font-bold text-lg text-red-900">Invalid Building</div>
<div className="text-sm text-red-700 mt-1">Data is not valid</div>
</div>
);
}, [selected, data, getNode, setContent, clearContent, setContextPanelOpen]);

const instance = getBuildingInstance(data);

if (!instance) {
return <InvalidBuildingNode />;
}

const { icon, name, description, color, inputs = [], outputs = [] } = data;
const {
icon,
name,
description,
color,
inputs = [],
outputs = [],
} = instance;

// Calculate position counts
const inputCounts = countBuildingIO(inputs);
Expand All @@ -96,7 +97,7 @@ const Building = ({ id, data, selected }: NodeProps) => {
{inputs.map((input, globalIndex) => {
if (!input.position) return null;

const isGlobal = RESOURCES[input.resource]?.global;
const isGlobal = isGlobalResource(input.resource);
if (isGlobal) return;

const posIndex = inputIndexAtPosition[input.position] || 0;
Expand All @@ -105,7 +106,7 @@ const Building = ({ id, data, selected }: NodeProps) => {
return (
<BuildingInput
key={globalIndex}
building={data}
building={instance}
input={input}
selected={selected}
index={globalIndex}
Expand All @@ -130,7 +131,7 @@ const Building = ({ id, data, selected }: NodeProps) => {
{outputs.map((output, globalIndex) => {
if (!output.position) return null;

const isGlobal = RESOURCES[output.resource]?.global;
const isGlobal = isGlobalResource(output.resource);
if (isGlobal) return;

const posIndex = outputIndexAtPosition[output.position] || 0;
Expand All @@ -139,7 +140,7 @@ const Building = ({ id, data, selected }: NodeProps) => {
return (
<BuildingOutput
key={globalIndex}
building={data}
building={instance}
output={output}
selected={selected}
index={globalIndex}
Expand All @@ -154,7 +155,7 @@ const Building = ({ id, data, selected }: NodeProps) => {

export default Building;

function countBuildingIO(ios: (BuildingInput | BuildingOutput)[]) {
function countBuildingIO(ios: (BuildingInputType | BuildingOutputType)[]) {
const counts: Record<string, number> = {};
ios.forEach((io) => {
if (!io.position) return;
Expand All @@ -163,3 +164,10 @@ function countBuildingIO(ios: (BuildingInput | BuildingOutput)[]) {
});
return counts;
}

const InvalidBuildingNode = () => (
<div className="px-6 py-4 shadow-lg rounded-lg bg-red-50 border-4 border-red-500">
<div className="font-bold text-lg text-red-900">Invalid Building</div>
<div className="text-sm text-red-700 mt-1">Data is not valid</div>
</div>
);
24 changes: 6 additions & 18 deletions src/components/Factory/Canvas/callbacks/onDrop.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import type { Node, ReactFlowInstance } from "@xyflow/react";
import type { DragEvent } from "react";

import type { Building } from "../../types/buildings";

let nodeIdCounter = 0;
import { createBuildingNode } from "../../objects/buildings/createBuildingNode";

export const createOnDrop = (
reactFlowInstance: ReactFlowInstance | undefined,
Expand All @@ -14,11 +12,12 @@ export const createOnDrop = (

if (!reactFlowInstance) return;

const buildingData = event.dataTransfer.getData("application/reactflow");
if (!buildingData) return;
const droppedBuildingData = event.dataTransfer.getData(
"application/reactflow",
);

try {
const { building } = JSON.parse(buildingData) as { building: Building };
const { buildingType } = JSON.parse(droppedBuildingData);

const position = reactFlowInstance.screenToFlowPosition({
x: event.clientX,
Expand All @@ -32,18 +31,7 @@ export const createOnDrop = (
position.y -= offsetY;
}

const newNode: Node = {
id: `${building.id}-${nodeIdCounter++}`,
type: "building",
position,
data: {
...building,
label: building.name,
},
draggable: true,
deletable: true,
selectable: true,
};
const newNode = createBuildingNode(buildingType, position);

setNodes((nds) => [...nds, newNode]);
} catch (error) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { Progress } from "@/components/ui/progress";
import { Text } from "@/components/ui/typography";
import { pluralize } from "@/utils/string";

import { RESOURCES } from "../../data/resources";
import type { ProductionMethod, ProductionState } from "../../types/buildings";
import { isGlobalResource, RESOURCES } from "../../data/resources";
import type { ProductionMethod, ProductionState } from "../../types/production";

interface ProductionMethodSectionProps {
productionMethod?: ProductionMethod;
Expand Down Expand Up @@ -80,7 +80,7 @@ export const ProductionMethodSection = ({
Outputs:
</Text>
{productionMethod.outputs.map((output, idx) => {
if (RESOURCES[output.resource].global) {
if (isGlobalResource(output.resource)) {
return (
<InlineStack key={idx} gap="2">
<Text size="sm">
Expand Down
21 changes: 13 additions & 8 deletions src/components/Factory/Context/Building/StockpileSection.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { BlockStack, InlineStack } from "@/components/ui/layout";
import { Text } from "@/components/ui/typography";

import { RESOURCES } from "../../data/resources";
import { isGlobalResource, RESOURCES } from "../../data/resources";
import { useStatistics } from "../../providers/StatisticProvider";
import type { Building, Stockpile } from "../../types/buildings";
import type { BuildingInstance, Stockpile } from "../../types/buildings";
import type { ResourceType } from "../../types/resources";
import type { StockpileChange } from "../../types/statistics";

interface StockpileSectionProps {
nodeId: string;
stockpile: Stockpile[];
building: Building;
building: BuildingInstance;
}

export const StockpileSection = ({
Expand All @@ -20,8 +21,8 @@ export const StockpileSection = ({
const { lastDayStats } = useStatistics();
const statistics = lastDayStats?.buildings.get(nodeId);

const hasGlobalOutputs = building.productionMethod?.outputs.some(
(output) => RESOURCES[output.resource]?.global,
const hasGlobalOutputs = building.productionMethod?.outputs.some((output) =>
isGlobalResource(output.resource),
);

if (stockpile.length === 0) {
Expand Down Expand Up @@ -87,7 +88,10 @@ export const StockpileSection = ({
Total: {stock.amount} / {stock.maxAmount}
</Text>
<Text size="xs" tone="subdued">
• Expected Value: {RESOURCES.money.icon} {totalValue}
</Text>
<Text size="xs" tone="subdued">
Expected Value: {RESOURCES.money.icon} {totalValue}
</Text>
</InlineStack>
</BlockStack>
Expand Down Expand Up @@ -122,8 +126,9 @@ export const StockpileSection = ({
{hasGlobalOutputs && statistics?.produced && (
<BlockStack gap="1">
{Object.entries(statistics.produced).map(([resource, amount]) => (
<Text key={resource} size="sm" className="text-green-600">
Last Day: +{amount} {RESOURCES[resource]?.icon}
<Text key={resource} size="xs" className="text-green-600">
Previous Day: +{amount}{" "}
{RESOURCES[resource as ResourceType]?.icon}
</Text>
))}
</BlockStack>
Expand Down
4 changes: 2 additions & 2 deletions src/components/Factory/Context/BuildingContext.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { BlockStack } from "@/components/ui/layout";
import { Separator } from "@/components/ui/separator";

import type { Building } from "../types/buildings";
import type { BuildingInstance } from "../types/buildings";
import { BuildingDescription } from "./Building/BuildingDescription";
import { ConnectionsSection } from "./Building/ConnectionsSection";
import { ProductionMethodSection } from "./Building/ProductionMethodSection";
import { StockpileSection } from "./Building/StockpileSection";
import { ContextHeader } from "./shared/ContextHeader";

interface BuildingContextProps {
building: Building;
building: BuildingInstance;
nodeId: string;
}

Expand Down
Loading
Loading