198 lines
6.5 KiB
TypeScript
198 lines
6.5 KiB
TypeScript
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<string, Resources>;
|
|
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<Resources>;
|
|
public cycleTime: Decimal;
|
|
public readonly cycleLength: Decimal;
|
|
public readonly inputRates: Partial<Resources>;
|
|
public readonly outputRates: Partial<Resources>;
|
|
/** 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<Resources> {
|
|
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<string, { new(source: never): Behaviour }>;
|