import Decimal, { DecimalSource } from "break_eternity.js"; import { Resource, Resources, SerializedConverter, SerializedNodeBehaviour as SerializedBehaviour, SerializedNodeBehaviour } from "../../types"; export type BehaviourResourcePin = { resource: Resource; } export type BehaviourSocket = { type: 'input' | 'output'; index: number; // within its type resource: Resource; } export type BehaviourLink = { target: Behaviour['id'], 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; } export abstract class Behaviour { public readonly id: string; public readonly kind: string; public readonly inputs: BehaviourInputs; public readonly outputs: BehaviourOutputs; constructor(source: SerializedBehaviour) { this.id = source.id; this.kind = source.kind; this.outputs = { pins: source.outputs.pins.map((output) => ({ resource: output.resource, })), } this.inputs = { pins: source.inputs.pins.map((input) => ({ resource: input.resource, link: input.link, })), } } public getDisplayInfo(): BehaviourDisplayInfo { return { storages: {}, links: this.inputs.pins.map((input) => input.link), sockets: [ ...this.inputs.pins.map((input, index) => ({ type: 'input', index, resource: input.resource, } satisfies BehaviourSocket)), ...this.outputs.pins.map((output, index) => ({ type: 'output', index, resource: output.resource, } satisfies BehaviourSocket)), ], }; } /** 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; protected abstract give(resource: Resource, amount: DecimalSource): void; abstract produce(times: DecimalSource): void; /** Pulls into this behaviour's own storage the same number of cycles this tick's `tick()` call just completed. */ abstract pull(resolve: (id: string) => Behaviour | undefined): void; abstract reset(): void; abstract tick(deltaTime: number): void; } export class Converter extends Behaviour { protected readonly storage: Partial; public cycleTime: Decimal; public readonly cycleLength: Decimal; public readonly inputRates: Partial; public readonly outputRates: Partial; /** Number of full cycles completed by the most recent `tick()` call, consumed by `pull()`. */ private pendingCycles: Decimal = Decimal.dZero; constructor(source: SerializedNodeBehaviour & SerializedConverter) { if (source.kind !== 'converter') throw new Error('Invalid source'); super(source); this.cycleTime = new Decimal(source.cycleTime); this.cycleLength = new Decimal(source.rules.cycleLength); this.storage = {}; for (const [res, amount] of Object.entries(source.storage ?? {})) this.storage[res as Resource] = new Decimal(amount); this.inputRates = {}; for (const [res, rate] of Object.entries(source.rules.inputRates ?? {})) this.inputRates[res as Resource] = new Decimal(rate); this.outputRates = {}; for (const [res, rate] of Object.entries(source.rules.outputRates ?? {})) this.outputRates[res as Resource] = new Decimal(rate); } public tick(deltaTime: number): void { this.cycleTime = this.cycleTime.add(deltaTime); const fullCycles = this.cycleTime.div(this.cycleLength).floor(); this.cycleTime = this.cycleTime.sub(fullCycles.mul(this.cycleLength)); this.pendingCycles = fullCycles; this.produce(fullCycles); } /** Fills this converter's own storage from its outputs' production rates. */ public produce(times: DecimalSource): void { for (const output of this.outputs.pins) this.give(output.resource, (this.outputRates[output.resource] ?? Decimal.dZero).mul(times)); } /** Pulls resources into this converter's storage from whatever each input pin is linked to, scaled by the cycles this tick's `tick()` just completed. */ public pull(resolve: (id: string) => Behaviour | undefined): void { for (const input of this.inputs.pins) { const source = resolve(input.link.target); if (!source) continue; const rate = this.inputRates[input.resource] ?? Decimal.dZero; const taken = source.take(input.link.resource, rate.mul(this.pendingCycles)); this.give(input.resource, taken); } } public take(resource: Resource, amount: DecimalSource): Decimal { const available = this.storage[resource] ?? Decimal.dZero; const taken = available.min(amount); this.storage[resource] = available.sub(taken); return taken; } protected give(resource: Resource, amount: DecimalSource): void { this.storage[resource] = (this.storage[resource] ?? Decimal.dZero).add(amount); } public getStorage(): Partial { return { ...this.storage }; } public getDisplayInfo(): BehaviourDisplayInfo { const info = super.getDisplayInfo(); info.storages['Буфер'] = { ...this.storage } as Resources; return { ...info, raw: { cycleTime: this.cycleTime, cycleLength: this.cycleLength, inputRates: this.inputRates, outputRates: this.outputRates, }, } as any; } public reset() { for (const res of Object.keys(this.storage)) delete (this.storage[res as Resource]); } } // export class Storage extends Behaviour { // public static type: string = 'storage'; // } export const behaviours = { 'converter': Converter, // 'storage': , } satisfies Record;