76 lines
2.8 KiB
TypeScript
76 lines
2.8 KiB
TypeScript
import { makeAutoObservable } from 'mobx';
|
|
import { LOGS_PER_TREE, TWIGS_PER_BUSH, TWIGS_PER_TREE } from '../constants';
|
|
import { randomId } from '../id';
|
|
import type { NodeKind, NodeResourceType, ResourceNodeData, Vec2 } from '../types';
|
|
|
|
/** A node yields its logs before its twigs. */
|
|
const RESOURCE_ORDER: NodeResourceType[] = ['log', 'twig'];
|
|
|
|
/** A choppable tree or bush. Framework-free — no React/DOM dependency. */
|
|
export class ResourceNode {
|
|
readonly id: string;
|
|
readonly kind: NodeKind;
|
|
x: number;
|
|
y: number;
|
|
/** Remaining yield per resource type; the node disappears once every count reaches 0. */
|
|
remaining: Record<NodeResourceType, number>;
|
|
|
|
constructor(kind: NodeKind, x: number, y: number, remaining: Record<NodeResourceType, number>, id = randomId(kind)) {
|
|
this.id = id;
|
|
this.kind = kind;
|
|
this.x = x;
|
|
this.y = y;
|
|
this.remaining = remaining;
|
|
makeAutoObservable(this, {}, { autoBind: true });
|
|
}
|
|
|
|
/** Builds a fresh tree or bush at `pos`, with its kind's full starting yield. */
|
|
static spawnNew(kind: NodeKind, pos: Vec2): ResourceNode {
|
|
const remaining = kind === 'tree'
|
|
? { log: LOGS_PER_TREE, twig: TWIGS_PER_TREE }
|
|
: { log: 0, twig: TWIGS_PER_BUSH };
|
|
return new ResourceNode(kind, pos.x, pos.y, remaining);
|
|
}
|
|
|
|
/** Reconstructs a node from saved data (a straight deep copy — every field has always been
|
|
* required, so no defensive fallback is needed here). */
|
|
static restore(data: ResourceNodeData): ResourceNode {
|
|
return new ResourceNode(data.kind, data.x, data.y, { ...data.remaining }, data.id);
|
|
}
|
|
|
|
/** The nearest node to `pos`, optionally restricted to one kind, or null if there are none. */
|
|
static findNearestToPosition(nodes: ResourceNode[], pos: Vec2, kind?: NodeKind): ResourceNode | null {
|
|
let nearest: ResourceNode | null = null;
|
|
let nearestDistance = Infinity;
|
|
for (const node of nodes) {
|
|
if (kind && node.kind !== kind)
|
|
continue;
|
|
const distance = Math.hypot(node.x - pos.x, node.y - pos.y);
|
|
if (distance < nearestDistance) {
|
|
nearestDistance = distance;
|
|
nearest = node;
|
|
}
|
|
}
|
|
return nearest;
|
|
}
|
|
|
|
get isDepleted(): boolean {
|
|
return RESOURCE_ORDER.every((resource) => this.remaining[resource] <= 0);
|
|
}
|
|
|
|
/** The next resource this node should yield when chopped, or null once fully depleted. */
|
|
get nextResource(): NodeResourceType | null {
|
|
return RESOURCE_ORDER.find((resource) => this.remaining[resource] > 0) ?? null;
|
|
}
|
|
|
|
/** Removes one unit of its next resource. Returns which resource was taken, or null if already
|
|
* depleted. */
|
|
chopOnce(): NodeResourceType | null {
|
|
const resource = this.nextResource;
|
|
if (!resource)
|
|
return null;
|
|
this.remaining[resource] -= 1;
|
|
return resource;
|
|
}
|
|
}
|