feat(industry): add RecipeRegistry with seed recipes for tools and structures
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { RecipeRegistry, type Recipe } from '../recipeRegistry.js';
|
||||
|
||||
describe('RecipeRegistry', () => {
|
||||
it('registers and retrieves a recipe', () => {
|
||||
const registry = new RecipeRegistry();
|
||||
const recipe: Recipe = {
|
||||
id: 'craft_wooden_axe',
|
||||
outputItemId: 'wooden_axe',
|
||||
outputQuantity: 1,
|
||||
inputs: [{ itemId: 'log', quantity: 2 }, { itemId: 'stone', quantity: 1 }],
|
||||
source: 'seed',
|
||||
};
|
||||
registry.register(recipe);
|
||||
expect(registry.get('craft_wooden_axe')).toEqual(recipe);
|
||||
});
|
||||
|
||||
it('throws on duplicate registration', () => {
|
||||
const registry = new RecipeRegistry();
|
||||
const recipe: Recipe = {
|
||||
id: 'test',
|
||||
outputItemId: 'x',
|
||||
outputQuantity: 1,
|
||||
inputs: [],
|
||||
source: 'seed',
|
||||
};
|
||||
registry.register(recipe);
|
||||
expect(() => registry.register(recipe)).toThrow();
|
||||
});
|
||||
|
||||
it('returns undefined for unknown recipe', () => {
|
||||
const registry = new RecipeRegistry();
|
||||
expect(registry.get('nonexistent')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('getAll returns all registered recipes', () => {
|
||||
const registry = new RecipeRegistry();
|
||||
registry.register({ id: 'a', outputItemId: 'x', outputQuantity: 1, inputs: [], source: 'seed' });
|
||||
registry.register({ id: 'b', outputItemId: 'y', outputQuantity: 1, inputs: [], source: 'seed' });
|
||||
expect(registry.getAll()).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('findCraftable returns recipes NPC has materials for', () => {
|
||||
const registry = new RecipeRegistry();
|
||||
registry.register({
|
||||
id: 'craft_wooden_axe',
|
||||
outputItemId: 'wooden_axe',
|
||||
outputQuantity: 1,
|
||||
inputs: [{ itemId: 'log', quantity: 2 }, { itemId: 'stone', quantity: 1 }],
|
||||
source: 'seed',
|
||||
});
|
||||
registry.register({
|
||||
id: 'craft_hammer',
|
||||
outputItemId: 'hammer',
|
||||
outputQuantity: 1,
|
||||
inputs: [{ itemId: 'log', quantity: 2 }, { itemId: 'stone', quantity: 1 }],
|
||||
source: 'seed',
|
||||
});
|
||||
const inv = new Map([['log', 3], ['stone', 1]]);
|
||||
const craftable = registry.findCraftable(inv);
|
||||
expect(craftable).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('findCraftable excludes recipes without enough materials', () => {
|
||||
const registry = new RecipeRegistry();
|
||||
registry.register({
|
||||
id: 'craft_wooden_axe',
|
||||
outputItemId: 'wooden_axe',
|
||||
outputQuantity: 1,
|
||||
inputs: [{ itemId: 'log', quantity: 2 }, { itemId: 'stone', quantity: 1 }],
|
||||
source: 'seed',
|
||||
});
|
||||
const inv = new Map([['log', 1]]);
|
||||
expect(registry.findCraftable(inv)).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('createDefault has seed recipes for axe, hammer, stockpile, workbench', () => {
|
||||
const registry = RecipeRegistry.createDefault();
|
||||
expect(registry.get('craft_wooden_axe')).toBeDefined();
|
||||
expect(registry.get('craft_hammer')).toBeDefined();
|
||||
expect(registry.get('build_stockpile')).toBeDefined();
|
||||
expect(registry.get('build_workbench')).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -55,6 +55,20 @@ export class ItemRegistry {
|
||||
category: 'resource',
|
||||
source: 'seed',
|
||||
});
|
||||
registry.register({
|
||||
id: 'wooden_axe',
|
||||
name: 'Wooden Axe',
|
||||
description: 'A crude axe made from logs and stone',
|
||||
category: 'tool',
|
||||
source: 'seed',
|
||||
});
|
||||
registry.register({
|
||||
id: 'hammer',
|
||||
name: 'Hammer',
|
||||
description: 'A basic hammer for construction',
|
||||
category: 'tool',
|
||||
source: 'seed',
|
||||
});
|
||||
return registry;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import type { Inventory } from './inventoryHelpers.js';
|
||||
|
||||
export interface Recipe {
|
||||
id: string;
|
||||
outputItemId: string;
|
||||
outputQuantity: number;
|
||||
inputs: { itemId: string; quantity: number }[];
|
||||
workshopType?: string; // required workshop (undefined = hand-craftable)
|
||||
toolRequired?: string; // required tool in inventory (undefined = none)
|
||||
source: 'seed' | 'invented';
|
||||
}
|
||||
|
||||
export class RecipeRegistry {
|
||||
private recipes = new Map<string, Recipe>();
|
||||
|
||||
register(recipe: Recipe): void {
|
||||
if (this.recipes.has(recipe.id)) {
|
||||
throw new Error(`Recipe '${recipe.id}' already registered`);
|
||||
}
|
||||
this.recipes.set(recipe.id, recipe);
|
||||
}
|
||||
|
||||
get(id: string): Recipe | undefined {
|
||||
return this.recipes.get(id);
|
||||
}
|
||||
|
||||
getAll(): Recipe[] {
|
||||
return [...this.recipes.values()];
|
||||
}
|
||||
|
||||
findCraftable(inv: Inventory, workshopType?: string): Recipe[] {
|
||||
return this.getAll().filter(recipe => {
|
||||
if (recipe.workshopType && recipe.workshopType !== workshopType) return false;
|
||||
return recipe.inputs.every(input => (inv.get(input.itemId) ?? 0) >= input.quantity);
|
||||
});
|
||||
}
|
||||
|
||||
static createDefault(): RecipeRegistry {
|
||||
const registry = new RecipeRegistry();
|
||||
|
||||
registry.register({
|
||||
id: 'craft_wooden_axe',
|
||||
outputItemId: 'wooden_axe',
|
||||
outputQuantity: 1,
|
||||
inputs: [{ itemId: 'log', quantity: 2 }, { itemId: 'stone', quantity: 1 }],
|
||||
source: 'seed',
|
||||
});
|
||||
|
||||
registry.register({
|
||||
id: 'craft_hammer',
|
||||
outputItemId: 'hammer',
|
||||
outputQuantity: 1,
|
||||
inputs: [{ itemId: 'log', quantity: 2 }, { itemId: 'stone', quantity: 1 }],
|
||||
source: 'seed',
|
||||
});
|
||||
|
||||
registry.register({
|
||||
id: 'build_stockpile',
|
||||
outputItemId: 'stockpile',
|
||||
outputQuantity: 1,
|
||||
inputs: [{ itemId: 'log', quantity: 4 }],
|
||||
source: 'seed',
|
||||
});
|
||||
|
||||
registry.register({
|
||||
id: 'build_workbench',
|
||||
outputItemId: 'workbench',
|
||||
outputQuantity: 1,
|
||||
inputs: [{ itemId: 'log', quantity: 3 }, { itemId: 'stone', quantity: 2 }],
|
||||
source: 'seed',
|
||||
});
|
||||
|
||||
return registry;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user