diff --git a/src/components/GameCanvas.tsx b/src/components/GameCanvas.tsx
index 3257aeb..d3594bb 100644
--- a/src/components/GameCanvas.tsx
+++ b/src/components/GameCanvas.tsx
@@ -92,7 +92,7 @@ export const GameCanvas = function ({
{
graphNodes.map((graphNode) =>
- graphNode.entity.getDisplayInfo().links.map((link, idx) => {
+ graphNode.entity.inputLinks.map((link, idx) => {
const target = graphNodes.find((gn) => gn.entity.id === link.target);
return target && (
({
- resource: output.resource,
- })),
- }
- this.inputs = {
- pins: source.inputs.pins.map((input) => ({
- resource: input.resource,
- link: input.link,
- })),
- }
+ this.inputLinks = source.inputLinks.pins;
}
public getDisplayInfo(): BehaviourDisplayInfo {
return {
storages: {},
- links: this.inputs.pins.map((input) => input.link),
sockets: [
- ...this.inputs.pins.map((input, index) => ({
+ ...this.inputs.map((input, index) => ({
type: 'input',
index,
- resource: input.resource,
+ resource: input,
} satisfies BehaviourSocket)),
- ...this.outputs.pins.map((output, index) => ({
+ ...this.outputs.map((output, index) => ({
type: 'output',
index,
- resource: output.resource,
+ resource: output,
} satisfies BehaviourSocket)),
],
};
}
+ public abstract get inputs(): Resource[];
+ public abstract get outputs(): Resource[];
+
/** Withdraws up to `amount` of `resource` from this behaviour's storage, returning how much was actually taken. */
public abstract take(resource: Resource, amount: DecimalSource): Decimal;
diff --git a/src/engine/behaviours/converter.ts b/src/engine/behaviours/converter.ts
index 40d06f8..541b135 100644
--- a/src/engine/behaviours/converter.ts
+++ b/src/engine/behaviours/converter.ts
@@ -30,6 +30,14 @@ export class Converter extends Behaviour {
this.outputRates[res as Resource] = new Decimal(rate);
}
+ public get inputs(): Resource[] {
+ return Object.keys(this.inputRates) as Resource[];
+ }
+
+ public get outputs(): Resource[] {
+ return Object.keys(this.outputRates) as Resource[];
+ }
+
/**
* A converter grabs a full batch of each input the moment enough is available — reserving
* ahead for however many cycles (including the one currently underway) the elapsed time could
@@ -44,21 +52,23 @@ export class Converter extends Behaviour {
const reserveCycles = idealCycleTime.div(this.cycleLength).ceil();
let newCycleTime = idealCycleTime;
- for (const input of this.inputs.pins) {
- const rate = this.inputRates[input.resource] ?? Decimal.dZero;
+ for (const [res, rate] of Object.entries(this.inputRates) as [Resource, Decimal][]) {
if (rate.eq(0))
continue;
const target = rate.mul(reserveCycles);
- const have = this.storage[input.resource] ?? Decimal.dZero;
+ const have = this.storage[res] ?? Decimal.dZero;
const shortfall = target.sub(have).max(0);
if (shortfall.gt(0)) {
- const source = resolve(input.link.target);
- if (source)
- this.give(input.resource, source.take(input.link.resource, shortfall));
+ const link = this.inputLinks.find((l) => l.resource === res);
+ if (link) {
+ const source = resolve(link.target);
+ if (source)
+ this.give(res, source.take(link.resource, shortfall));
+ }
}
- const haveNow = this.storage[input.resource] ?? Decimal.dZero;
+ const haveNow = this.storage[res] ?? Decimal.dZero;
newCycleTime = newCycleTime.min(haveNow.mul(this.cycleLength).div(rate));
}
@@ -66,10 +76,10 @@ export class Converter extends Behaviour {
const affordableCycles = this.cycleTime.div(this.cycleLength).floor();
if (affordableCycles.gt(0)) {
- for (const input of this.inputs.pins)
- this.take(input.resource, (this.inputRates[input.resource] ?? Decimal.dZero).mul(affordableCycles));
- for (const output of this.outputs.pins)
- this.give(output.resource, (this.outputRates[output.resource] ?? Decimal.dZero).mul(affordableCycles));
+ for (const [res, rate] of Object.entries(this.inputRates) as [Resource, Decimal][])
+ this.take(res, rate.mul(affordableCycles));
+ for (const [res, rate] of Object.entries(this.outputRates) as [Resource, Decimal][])
+ this.give(res, rate.mul(affordableCycles));
this.cycleTime = this.cycleTime.sub(affordableCycles.mul(this.cycleLength));
}
}
diff --git a/src/engine/behaviours/storage.ts b/src/engine/behaviours/storage.ts
new file mode 100644
index 0000000..31dd4b8
--- /dev/null
+++ b/src/engine/behaviours/storage.ts
@@ -0,0 +1,78 @@
+import Decimal, { DecimalSource } from "break_eternity.js";
+import { BehaviourDisplayInfo, Resource, Resources, SerializedBehaviour, SerializedStorage } from "../../types";
+import { Behaviour } from "./base";
+
+export class Storage extends Behaviour {
+ public readonly capacity: Decimal | undefined;
+ public readonly acceptsResources: Resource[];
+ public readonly items: Partial;
+
+ constructor(source: SerializedBehaviour & SerializedStorage) {
+ if (source.kind !== 'storage')
+ throw new Error('Invalid source');
+ super(source);
+
+ this.capacity = source.capacity ? new Decimal(source.capacity) : undefined;
+ this.acceptsResources = source.acceptsResources;
+ this.items = {};
+ for (const [res, amount] of Object.entries(source.items ?? {}))
+ this.items[res as Resource] = new Decimal(amount);
+ }
+
+ public get inputs(): Resource[] {
+ return this.acceptsResources;
+ }
+
+ public get outputs(): Resource[] {
+ return this.acceptsResources;
+ }
+
+ /**
+ * A storage has no rate of its own — it pulls as much as its linked source is willing to give
+ * (up to whatever room its own capacity leaves), and later hands out as much as whoever calls
+ * `take` asks for (up to whatever it actually has). The speed in either direction is entirely
+ * up to the counterpart, not this behaviour.
+ */
+ public tick(_deltaTime: number, resolve: (id: string) => Behaviour | undefined): void {
+ for (const resource of this.acceptsResources) {
+ const link = this.inputLinks.find((l) => l.resource === resource);
+ if (!link)
+ continue;
+
+ const source = resolve(link.target);
+ if (!source)
+ continue;
+
+ const have = this.items[resource] ?? Decimal.dZero;
+ const room = this.capacity ? this.capacity.sub(have).max(0) : Decimal.dInf;
+ if (room.gt(0))
+ this.give(resource, source.take(link.resource, room));
+ }
+ }
+
+ public take(resource: Resource, amount: DecimalSource): Decimal {
+ const available = this.items[resource] ?? Decimal.dZero;
+ const taken = available.min(amount);
+ this.items[resource] = available.sub(taken);
+ return taken;
+ }
+
+ protected give(resource: Resource, amount: DecimalSource): void {
+ this.items[resource] = (this.items[resource] ?? Decimal.dZero).add(amount);
+ }
+
+ public getStorage(): Partial {
+ return { ...this.items };
+ }
+
+ public getDisplayInfo(): BehaviourDisplayInfo {
+ const info = super.getDisplayInfo();
+ info.storages['Хранилище'] = { ...this.items } as Resources;
+ return info;
+ }
+
+ public reset() {
+ for (const res of Object.keys(this.items))
+ delete (this.items[res as Resource]);
+ }
+}
diff --git a/src/engine/saveLoad.ts b/src/engine/saveLoad.ts
index 4e8350e..268f0c1 100644
--- a/src/engine/saveLoad.ts
+++ b/src/engine/saveLoad.ts
@@ -1,7 +1,8 @@
import { defaultGame } from "../prefabs/game";
-import { SerializedBehaviour, SerializedGame } from "../types";
+import { SerializedBehaviour, SerializedConverter, SerializedGame, SerializedStorage } from "../types";
import { Behaviour } from "./behaviours/base";
import { Converter } from "./behaviours/converter";
+import { Storage } from "./behaviours/storage";
import { Game } from "./game";
export const saveParams = {
@@ -9,33 +10,34 @@ export const saveParams = {
}
export function serializeBehaviour(behaviour: Behaviour): SerializedBehaviour {
+ const base = {
+ id: behaviour.id,
+ kind: behaviour.kind,
+ inputLinks: { pins: behaviour.inputLinks },
+ };
+
switch (behaviour.kind) {
case 'converter': {
const converter = behaviour as Converter;
return {
- id: converter.id,
- kind: 'converter',
+ ...base,
cycleTime: converter.cycleTime,
storage: converter.getStorage(),
- inputs: {
- pins: converter.inputs.pins.map((pin) => ({
- ...pin,
- rate: converter.inputRates[pin.resource] ?? 0,
- })),
- },
- outputs: {
- pins: converter.outputs.pins.map((pin) => ({
- ...pin,
- rate: converter.outputRates[pin.resource] ?? 0,
- })),
- },
rules: {
cycleLength: converter.cycleLength,
inputRates: converter.inputRates,
outputRates: converter.outputRates,
}
- };
+ } as (SerializedBehaviour & SerializedConverter);;
}
+ case 'storage':
+ const storage = behaviour as Storage;
+ return {
+ ...base,
+ capacity: storage.capacity,
+ acceptsResources: storage.acceptsResources,
+ items: storage.getStorage(),
+ } as (SerializedBehaviour & SerializedStorage);
default:
throw new Error('Unknown behaviour');
}
@@ -45,6 +47,8 @@ export function deserializeBehaviour(value: SerializedBehaviour): Behaviour {
switch (value.kind) {
case 'converter':
return new Converter(value);
+ case 'storage':
+ return new Storage(value);
default:
throw new Error('Unknown behaviour');
}
diff --git a/src/prefabs/game.ts b/src/prefabs/game.ts
index 7980b6c..3497179 100644
--- a/src/prefabs/game.ts
+++ b/src/prefabs/game.ts
@@ -9,19 +9,7 @@ export const defaultGame: () => SerializedGame = () => ({
behaviour: {
id: 'forest.1',
kind: 'converter',
- inputs: {
- pins: [],
- },
- outputs: {
- pins: [
- {
- resource: 'tree',
- },
- {
- resource: 'stone',
- },
- ],
- },
+ inputLinks: { pins: [] },
rules: {
cycleLength: 1,
outputRates: { tree: 1, stone: 0.1 },
@@ -34,21 +22,14 @@ export const defaultGame: () => SerializedGame = () => ({
behaviour: {
id: 'sawmill.1',
kind: 'converter',
- inputs: {
+ inputLinks: {
pins: [
{
resource: 'tree',
- link: { target: 'forest.1', resource: 'tree' },
+ target: 'forest.1',
},
]
},
- outputs: {
- pins: [
- {
- resource: 'wood',
- },
- ],
- },
rules: {
cycleLength: 1.8,
inputRates: { tree: 1 },
@@ -57,26 +38,25 @@ export const defaultGame: () => SerializedGame = () => ({
},
},
{
- id: 'burner',
- name: 'Магазин досок',
+ id: 'shop',
+ name: 'Магазин',
behaviour: {
- id: 'burner.1',
- kind: 'converter',
- inputs: {
+ id: 'shop.1',
+ kind: 'storage',
+ inputLinks: {
pins: [
{
resource: 'wood',
- link: { target: 'sawmill.1', resource: 'wood' },
+ target: 'sawmill.1',
},
- ],
- },
- outputs: {
- pins: [],
- },
- rules: {
- cycleLength: 1,
- inputRates: { wood: 1 },
+ {
+ resource: 'stone',
+ target: 'forest.1',
+ }
+ ]
},
+ acceptsResources: ['wood', 'stone'],
+ items: {},
},
},
],
diff --git a/src/types/behaviour.ts b/src/types/behaviour.ts
index fc7fe79..67e881b 100644
--- a/src/types/behaviour.ts
+++ b/src/types/behaviour.ts
@@ -15,25 +15,9 @@ export type BehaviourLink = {
resource: Resource;
}
-export type BehaviourInput = BehaviourResourcePin & {
- link: BehaviourLink;
-}
-
-export type BehaviourInputs = {
- pins: BehaviourInput[];
-}
-
-export type BehaviourOutput = BehaviourResourcePin & {
-}
-
-export type BehaviourOutputs = {
- pins: BehaviourOutput[];
-}
-
export type BehaviourDisplayInfo = {
storages: Record;
sockets: BehaviourSocket[];
- links: BehaviourLink[];
[x: string]: unknown;
}
diff --git a/src/types/serialization.ts b/src/types/serialization.ts
index 2ff79bf..59983da 100644
--- a/src/types/serialization.ts
+++ b/src/types/serialization.ts
@@ -2,24 +2,13 @@ import { Unit } from './unit';
import { DecimalSource } from "break_eternity.js";
import { Resource } from "./resource";
-export type SerializedInput = {
- resource: Resource;
- link: {
- target: SerializedBehaviour['id'],
- resource: Resource,
- }
+export type SerializedInputLink = {
+ target: SerializedBehaviour['id'],
+ resource: Resource,
}
-export type SerializedOutput = {
- resource: Resource;
-}
-
-export type SerializedInputs = {
- pins: SerializedInput[];
-}
-
-export type SerializedOutputs = {
- pins: SerializedOutput[];
+export type SerializedInputLinks = {
+ pins: SerializedInputLink[];
}
export type SerializedConverter = {
@@ -35,12 +24,14 @@ export type SerializedConverter = {
export type SerializedStorage = {
kind: 'storage',
+ capacity?: DecimalSource;
+ acceptsResources: Resource[];
+ items: Partial>;
}
export type SerializedBehaviour = {
id: string;
- inputs: SerializedInputs;
- outputs: SerializedOutputs;
+ inputLinks: SerializedInputLinks;
} & (SerializedConverter | SerializedStorage);
export type SerializedNode = {
diff --git a/src/ui/graph.ts b/src/ui/graph.ts
index 3d48c71..dd14280 100644
--- a/src/ui/graph.ts
+++ b/src/ui/graph.ts
@@ -30,7 +30,7 @@ export async function build2dNodes(units: Unit[], area: { width: number, height:
height: NODE_HEIGHT,
})),
edges: units.flatMap((node) =>
- node.behaviour.getDisplayInfo().links.map((link, idx) => ({
+ node.behaviour.inputLinks.map((link, idx) => ({
id: `${node.behaviour.id}->${link.target}.${idx}`,
sources: [node.behaviour.id],
targets: [link.target],
diff --git a/src/utils/behaviourSort.ts b/src/utils/behaviourSort.ts
index faa61a2..18b486b 100644
--- a/src/utils/behaviourSort.ts
+++ b/src/utils/behaviourSort.ts
@@ -10,8 +10,8 @@ export function orderByDependency(behaviours: Behaviour[]): Behaviour[] {
if (visited.has(behaviour.id))
return;
visited.add(behaviour.id);
- for (const input of behaviour.inputs.pins) {
- const dependency = byId.get(input.link.target);
+ for (const inputLink of behaviour.inputLinks) {
+ const dependency = byId.get(inputLink.target);
if (dependency)
visit(dependency);
}