From 1206b9bcfe71d4be77aafc2d9a76da276cbf3742 Mon Sep 17 00:00:00 2001 From: Niklas Widmann Date: Sat, 14 Oct 2023 15:51:16 +0200 Subject: [PATCH] more recipe parsers --- src/loader/index.ts | 49 ++++++- src/loader/loot.ts | 42 +----- src/loader/pack.ts | 39 ++++- src/loader/recipe.ts | 153 ++++++++++++++------ src/loader/tags.ts | 9 +- src/parser/recipe/adAstra/hammering.ts | 41 ++++++ src/parser/recipe/adAstra/inputOutput.ts | 41 ++++++ src/parser/recipe/adAstra/nasaWorkbench.ts | 44 ++++++ src/parser/recipe/adAstra/spaceStation.ts | 37 +++++ src/parser/recipe/botania/apothecary.ts | 2 +- src/parser/recipe/botania/brew.ts | 2 +- src/parser/recipe/botania/elvenTrade.ts | 2 +- src/parser/recipe/botania/gogWrapper.ts | 17 ++- src/parser/recipe/botania/manaInfusion.ts | 2 +- src/parser/recipe/botania/nbtWrapper.ts | 12 +- src/parser/recipe/botania/orechid.ts | 2 +- src/parser/recipe/botania/pureDaisy.ts | 2 +- src/parser/recipe/botania/runicAltar.ts | 2 +- src/parser/recipe/botania/terraPlate.ts | 2 +- src/parser/recipe/create/assembly.ts | 4 +- src/parser/recipe/create/processing.ts | 7 +- src/parser/recipe/farmersdelight/cooking.ts | 2 +- src/parser/recipe/farmersdelight/cutting.ts | 2 +- src/parser/recipe/forge/conditional.ts | 73 ++++++++++ src/parser/recipe/index.ts | 3 +- src/parser/recipe/quark/exclusion.ts | 59 ++++++++ src/parser/recipe/roots/component.ts | 40 +++++ src/parser/recipe/roots/ritual.ts | 41 ++++++ src/parser/recipe/thermal/catalyst.ts | 2 +- src/parser/recipe/thermal/fuel.ts | 37 +++++ src/parser/recipe/thermal/index.ts | 2 +- src/parser/recipe/thermal/treeExtraction.ts | 59 ++++++++ src/parser/recipe/vanilla/shaped.ts | 2 +- src/parser/recipe/vanilla/shapeless.ts | 2 +- src/parser/recipe/vanilla/smelting.ts | 2 +- src/parser/recipe/vanilla/smithing.ts | 2 +- src/parser/recipe/vanilla/stonecutting.ts | 2 +- src/schema/loot.ts | 4 +- src/textHelper.ts | 2 +- test/recipe.test.ts | 4 + test/shared/loaderSetup.ts | 4 +- 41 files changed, 727 insertions(+), 128 deletions(-) create mode 100644 src/parser/recipe/adAstra/hammering.ts create mode 100644 src/parser/recipe/adAstra/inputOutput.ts create mode 100644 src/parser/recipe/adAstra/nasaWorkbench.ts create mode 100644 src/parser/recipe/adAstra/spaceStation.ts create mode 100644 src/parser/recipe/forge/conditional.ts create mode 100644 src/parser/recipe/quark/exclusion.ts create mode 100644 src/parser/recipe/roots/component.ts create mode 100644 src/parser/recipe/roots/ritual.ts create mode 100644 src/parser/recipe/thermal/fuel.ts create mode 100644 src/parser/recipe/thermal/treeExtraction.ts diff --git a/src/loader/index.ts b/src/loader/index.ts index 0d9f12f..a92cc11 100644 --- a/src/loader/index.ts +++ b/src/loader/index.ts @@ -1,5 +1,52 @@ -import { IResolver } from '@pssbletrngle/pack-resolver' +import { Acceptor, IResolver } from '@pssbletrngle/pack-resolver' +import { Id } from '../common/id.js' +import { fromJson } from '../textHelper.js' +import Registry from '../common/registry.js' +import { RegistryProvider } from '../emit/index.js' +import { Logger } from '../logger.js' + +export type AcceptorWithLoader = (logger: Logger, ...paramenters: Parameters) => ReturnType export default interface Loader { loadFrom(resolver: IResolver): Promise } + +export abstract class JsonLoader implements RegistryProvider { + protected readonly registry = new Registry() + + protected abstract parse(logger: Logger, json: unknown, id: Id): T | null + + forEach(consumer: (recipe: T, id: Id) => void): void { + this.registry.forEach(consumer) + } + + private tryParseJson(logger: Logger, content: string) { + try { + return fromJson(content) + } catch (error) { + if (error instanceof SyntaxError) { + logger.warn(`unable to parse json: ${error.message}`, content) + return null + } + throw error + } + } + + readonly accept: AcceptorWithLoader = (logger, path, content) => { + const match = /data\/(?[\w-]+)\/\w+\/(?[\w-/]+).json/.exec(path) + if (!match?.groups) return false + + const { namespace, rest } = match.groups + const id: Id = { namespace, path: rest } + + const json = this.tryParseJson(logger.group(path), content.toString()) + if (!json) return false + + const parsed = this.parse(logger, json, id) + if (!parsed) return false + + this.registry.set(id, parsed) + + return true + } +} diff --git a/src/loader/loot.ts b/src/loader/loot.ts index f0ce520..e32b25f 100644 --- a/src/loader/loot.ts +++ b/src/loader/loot.ts @@ -1,43 +1,13 @@ -import { Logger } from '../logger.js' -import { Acceptor } from '@pssbletrngle/pack-resolver' import { encodeId, Id } from '../common/id.js' -import { fromJson } from '../textHelper.js' -import Registry from '../common/registry.js' import { tryCatching } from '../error.js' -import { RegistryProvider } from '../emit/index.js' import { LootTable, LootTableSchema } from '../schema/loot.js' +import { JsonLoader } from './index.js' +import { Logger } from '../logger.js' -export default class LootTableLoader implements RegistryProvider { - private readonly registry = new Registry() - - constructor(private readonly logger: Logger) {} - - private parse(raw: unknown, id: Id): LootTable | null { - const logger = this.logger.group(`error parsing loot table ${encodeId(id)}`) - return tryCatching(logger, () => { - return LootTableSchema.parse(raw) +export default class LootTableLoader extends JsonLoader { + protected parse(logger: Logger, json: unknown, id: Id): LootTable | null { + return tryCatching(logger.group(`error parsing loot table ${encodeId(id)}`), () => { + return LootTableSchema.parse(json) }) } - - readonly accept: Acceptor = (path, content) => { - // TODO extract common logic - const match = /data\/(?[\w-]+)\/loot_tables\/(?[\w-/]+).json/.exec(path) - if (!match?.groups) return false - - const { namespace, rest } = match.groups - const id: Id = { namespace, path: rest } - - const json = fromJson(content.toString()) - - const parsed = this.parse(json, id) - if (!parsed) return false - - this.registry.set(id, parsed) - - return true - } - - forEach(consumer: (recipe: LootTable, id: Id) => void): void { - this.registry.forEach(consumer) - } } diff --git a/src/loader/pack.ts b/src/loader/pack.ts index 84cb470..a7bad7e 100644 --- a/src/loader/pack.ts +++ b/src/loader/pack.ts @@ -1,8 +1,8 @@ -import { Acceptor, IResolver } from '@pssbletrngle/pack-resolver' +import { Acceptor, IResolver, ResolverInfo } from '@pssbletrngle/pack-resolver' import match from 'minimatch' import { Logger } from '../logger.js' -import Loader from './index.js' -import RecipeLoader from './recipe.js' +import Loader, { AcceptorWithLoader } from './index.js' +import RecipeLoader, { RecipeLoaderAccessor } from './recipe.js' import TagsLoader from './tags.js' import RecipeEmitter, { RecipeRules } from '../emit/recipe.js' import TagEmitter, { TagRules } from '../emit/tags.js' @@ -13,9 +13,9 @@ import LootTableEmitter, { LootRules } from '../emit/loot.js' export default class PackLoader implements Loader { constructor(private readonly logger: Logger) {} - private readonly tagLoader = new TagsLoader(this.logger) - private readonly recipesLoader = new RecipeLoader(this.logger) - private readonly lootLoader = new LootTableLoader(this.logger) + private readonly tagLoader = new TagsLoader() + private readonly recipesLoader = new RecipeLoader() + private readonly lootLoader = new LootTableLoader() private readonly tagEmitter = new TagEmitter(this.logger, this.tagLoader) private readonly recipeEmitter = new RecipeEmitter(this.logger, this.recipesLoader, this.tagLoader) @@ -41,23 +41,46 @@ export default class PackLoader implements Loader { return this.lootEmitter } + get recipeLoader(): RecipeLoaderAccessor { + return this.recipesLoader + } + resolveIngredientTest(test: IngredientTest) { return this.recipeEmitter.resolveIngredientTest(test) } - private acceptors: Record = { + private acceptors: Record = { 'data/*/tags/**/*.json': this.tagLoader.accept, 'data/*/recipes/**/*.json': this.recipesLoader.accept, 'data/*/loot_tables/**/*.json': this.lootLoader.accept, } + async loadFromMultiple(resolvers: ResolverInfo[]) { + await Promise.all( + resolvers.map(({ resolver, name }) => { + const logger = this.logger.group(name) + return resolver.extract((path, content) => { + const acceptor = Object.entries(this.acceptors).find(([pattern]) => match(path, pattern))?.[1] + if (!acceptor) return false + return acceptor(logger, path, content) + }) + }) + ) + + this.freeze() + } + async loadFrom(resolver: IResolver) { await resolver.extract((path, content) => { const acceptor = Object.entries(this.acceptors).find(([pattern]) => match(path, pattern))?.[1] if (!acceptor) return false - return acceptor(path, content) + return acceptor(this.logger, path, content) }) + this.freeze() + } + + private freeze() { this.tagLoader.freeze() } diff --git a/src/loader/recipe.ts b/src/loader/recipe.ts index 47cdfde..1c56f56 100644 --- a/src/loader/recipe.ts +++ b/src/loader/recipe.ts @@ -1,12 +1,9 @@ -import { Acceptor } from '@pssbletrngle/pack-resolver' -import { Logger } from '../logger.js' import RecipeParser, { Recipe } from '../parser/recipe/index.js' import ShapedParser from '../parser/recipe/vanilla/shaped.js' import { RecipeDefinition } from '../schema/recipe.js' -import { Id } from '../common/id.js' +import { encodeId } from '../common/id.js' import ShapelessParser from '../parser/recipe/vanilla/shapeless.js' import CreateProcessingRecipeParser from '../parser/recipe/create/processing.js' -import { fromJson } from '../textHelper.js' import SmeltingParser from '../parser/recipe/vanilla/smelting.js' import SmithingParser from '../parser/recipe/vanilla/smithing.js' import AssemblyRecipeParser from '../parser/recipe/create/assembly.js' @@ -23,24 +20,37 @@ import BrewRecipeParser from '../parser/recipe/botania/brew.js' import ManaInfusionRecipeParser from '../parser/recipe/botania/manaInfusion.js' import GogWrapperRecipeParser from '../parser/recipe/botania/gogWrapper.js' import ApothecaryRecipeParser from '../parser/recipe/botania/apothecary.js' -import Registry from '../common/registry.js' import TerraPlateRecipeParser from '../parser/recipe/botania/terraPlate.js' import PureDaisyRecipeParser from '../parser/recipe/botania/pureDaisy.js' -import { RegistryProvider } from '../emit/index.js' +import { JsonLoader } from './index.js' +import ForgeConditionalRecipeParser from '../parser/recipe/forge/conditional.js' +import HammeringRecipeParser from '../parser/recipe/adAstra/hammering.js' +import InputOutputRecipeParser from '../parser/recipe/adAstra/inputOutput.js' +import NasaWorkbenchRecipeParser from '../parser/recipe/adAstra/nasaWorkbench.js' +import SpaceStationRecipeParser from '../parser/recipe/adAstra/spaceStation.js' +import QuarkExclusionRecipeParser from '../parser/recipe/quark/exclusion.js' +import TreeExtractionRecipeParser from '../parser/recipe/thermal/treeExtraction.js' +import ThermalFuelRecipeParser from '../parser/recipe/thermal/fuel.js' +import RootComponentRecipeParser from '../parser/recipe/roots/component.js' +import RootRitualRecipeParser from '../parser/recipe/roots/ritual.js' +import { ShapelessRecipeParser } from '../parser/index.js' +import { Logger } from '../logger.js' -export default class RecipeLoader implements RegistryProvider { +export interface RecipeLoaderAccessor { + unknownRecipeTypes(): RecipeDefinition[] + registerParser(recipeType: string, parser: RecipeParser): void + ignoreType(recipeType: string): void +} + +export default class RecipeLoader extends JsonLoader implements RecipeLoaderAccessor { private readonly recipeParsers = new Map>() private readonly ignoredRecipeTypes = new Set() - private readonly unknownRecipeTypes = new Set() + private readonly _unknownRecipeTypes = new Map() - private readonly recipes = new Registry() + constructor() { + super() - forEach(consumer: (recipe: Recipe, id: Id) => void) { - this.recipes.forEach(consumer) - } - - constructor(private readonly logger: Logger) { this.registerParser('minecraft:crafting_shaped', new ShapedParser()) this.registerParser('minecraft:crafting_shapeless', new ShapelessParser()) this.registerParser('minecraft:smelting', new SmeltingParser()) @@ -68,6 +78,7 @@ export default class RecipeLoader implements RegistryProvider { this.registerParser('farmersdelight:cooking', new CookingRecipeParser()) this.registerParser('farmersdelight:cutting', new CuttingRecipeParser()) + this.registerParser('farmersrespite:brewing', new CookingRecipeParser()) this.registerParser('thermal:bottler', new ThermalRecipeParser()) this.registerParser('thermal:centrifuge', new ThermalRecipeParser()) @@ -87,6 +98,11 @@ export default class RecipeLoader implements RegistryProvider { this.registerParser('thermal:smelter', new ThermalRecipeParser()) this.registerParser('thermal:smelter_recycle', new ThermalRecipeParser()) this.registerParser('thermal:smelter_catalyst', new ThermalCatalystRecipeParser()) + this.registerParser('thermal:tree_extractor', new TreeExtractionRecipeParser()) + this.registerParser('thermal:compression_fuel', new ThermalFuelRecipeParser()) + this.registerParser('thermal:magmatic_fuel', new ThermalFuelRecipeParser()) + this.registerParser('thermal:gourmand_fuel', new ThermalFuelRecipeParser()) + this.registerParser('thermal:numismatic_fuel', new ThermalFuelRecipeParser()) this.registerParser('botania:nbt_output_wrapper', new NbtWrapperRecipeParser(this)) this.registerParser('botania:orechid', new OrechidRecipeParser()) @@ -108,32 +124,102 @@ export default class RecipeLoader implements RegistryProvider { this.registerParser('botania:gog_alternation', new GogWrapperRecipeParser(this)) this.registerParser('botania:petal_apothecary', new ApothecaryRecipeParser()) - this.ignoredRecipeTypes.add('immersiveengineering:cloche') - this.ignoredRecipeTypes.add('immersiveengineering:crusher') - this.ignoredRecipeTypes.add('immersiveengineering:fermenter') - this.ignoredRecipeTypes.add('immersiveengineering:metal_press') - this.ignoredRecipeTypes.add('immersiveengineering:squeezer') + this.registerParser('theoneprobe:probe_helmet', new ShapedParser()) + + this.registerParser('forge:conditional', new ForgeConditionalRecipeParser(this)) + + this.registerParser('sullysmod:grindstone_polishing', new SmeltingParser()) + + this.registerParser('ad_astra:hammering', new HammeringRecipeParser()) + this.registerParser('ad_astra:cryo_fuel_conversion', new InputOutputRecipeParser()) + this.registerParser('ad_astra:fuel_conversion', new InputOutputRecipeParser()) + this.registerParser('ad_astra:oxygen_conversion', new InputOutputRecipeParser()) + this.registerParser('ad_astra:compressing', new InputOutputRecipeParser()) + this.registerParser('ad_astra:crafting_shaped_space_suit', new ShapedParser()) + this.registerParser('ad_astra:nasa_workbench', new NasaWorkbenchRecipeParser()) + this.registerParser('ad_astra:space_station', new SpaceStationRecipeParser()) + + this.registerParser('quark:exclusion', new QuarkExclusionRecipeParser(this)) + this.registerParser('patchouli:shapeless_book_recipe', new ShapelessRecipeParser()) + + this.registerParser('cofh_core:crafting_shaped_potion', new ShapedParser()) + + this.registerParser('rootsclassic:component', new RootComponentRecipeParser()) + this.registerParser('rootsclassic:ritual', new RootRitualRecipeParser()) + + this.ignoreType('jeed:effect_provider') + this.ignoreType('jeed:potion_provider') + + this.ignoreType('pipez:copy_nbt') + this.ignoreType('pipez:clear_nbt') + + this.ignoreType('refinedstorage:upgrade_with_enchanted_book') + + this.ignoreType('forge:ore_shaped') + + // TODO could to in the future + this.ignoreType('ad_astra:lunarian_trade_simple') + this.ignoreType('ad_astra:lunarian_trade_enchanted_item') + this.ignoreType('ad_astra:lunarian_trade_suspicious_stew') + this.ignoreType('ad_astra:lunarian_trade_enchanted_book') + this.ignoreType('ad_astra:lunarian_trade_dyed_item') + this.ignoreType('ad_astra:lunarian_trade_potioned_item') + + this.ignoreType('supplementaries:trapped_present') + this.ignoreType('supplementaries:weathered_map') + this.ignoreType('supplementaries:soap_clearing') + this.ignoreType('supplementaries:rope_arrow_create') + this.ignoreType('supplementaries:present_dye') + this.ignoreType('supplementaries:rope_arrow_add') + this.ignoreType('supplementaries:item_lore') + this.ignoreType('supplementaries:flag_from_banner') + this.ignoreType('supplementaries:bubble_blower_charge') + this.ignoreType('supplementaries:bamboo_spikes_tipped') + this.ignoreType('supplementaries:antique_book') + this.ignoreType('supplementaries:cauldron_flag_dye') + this.ignoreType('supplementaries:cauldron_flag_clear') + this.ignoreType('supplementaries:cauldron_blackboard') + + this.ignoreType('quark:mixed_exclusion') + this.ignoreType('quark:elytra_duplication') + this.ignoreType('quark:slab_to_block') + + this.ignoreType('immersiveengineering:cloche') + this.ignoreType('immersiveengineering:crusher') + this.ignoreType('immersiveengineering:fermenter') + this.ignoreType('immersiveengineering:metal_press') + this.ignoreType('immersiveengineering:squeezer') + this.ignoreType('immersiveengineering:mineral_mix') + } + + ignoreType(recipeType: string) { + this.ignoredRecipeTypes.add(recipeType) + } + + unknownRecipeTypes() { + return [...this._unknownRecipeTypes.values()] } parse>( + logger: Logger, definition: TDefinition ): TRecipe | null { - const parser = this.recipeParsers.get(definition.type) + const parser = this.recipeParsers.get(encodeId(definition.type)) if (!('type' in definition)) return null if (Object.keys(definition).length <= 1) return null if (this.ignoredRecipeTypes.has(definition.type)) return null if (!parser) { - if (!this.unknownRecipeTypes.has(definition.type)) { - this.logger.warn(`unknown recipe type: '${definition.type}'`, definition) - this.unknownRecipeTypes.add(definition.type) + if (!this._unknownRecipeTypes.has(definition.type)) { + logger.warn(`unknown recipe type: '${definition.type}'`, definition) + this._unknownRecipeTypes.set(definition.type, definition) } return null } try { - return parser.create(definition) as TRecipe + return parser.create(definition, logger) as TRecipe } catch (e) { throw new Error(`Failed to parse recipe with type '${definition.type}'`, { cause: e }) } @@ -143,24 +229,7 @@ export default class RecipeLoader implements RegistryProvider { this.recipeParsers.set(recipeType, parser) } - readonly accept: Acceptor = (path, content) => { - const match = /data\/(?[\w-]+)\/recipes\/(?[\w-/]+).json/.exec(path) - if (!match?.groups) return false - - const { namespace, rest } = match.groups - const id: Id = { namespace, path: rest } - - const parsed: RecipeDefinition = fromJson(content.toString()) - - const recipe = this.parse(parsed) - if (!recipe) return false - - this.recipes.set(id, recipe) - - return true - } - clear() { - this.unknownRecipeTypes.clear() + this._unknownRecipeTypes.clear() } } diff --git a/src/loader/tags.ts b/src/loader/tags.ts index 4627280..7aa89d0 100644 --- a/src/loader/tags.ts +++ b/src/loader/tags.ts @@ -1,10 +1,9 @@ -import { Acceptor } from '@pssbletrngle/pack-resolver' import { orderBy, uniqBy } from 'lodash-es' -import { IdInput, TagInput, encodeId } from '../common/id.js' +import { encodeId, IdInput, TagInput } from '../common/id.js' import Registry from '../common/registry.js' -import { Logger } from '../logger.js' import { TagDefinition, TagEntry } from '../schema/tag.js' import { fromJson } from '../textHelper.js' +import { AcceptorWithLoader } from './index.js' export function entryId(entry: TagEntry) { if (typeof entry === 'string') return entry @@ -103,7 +102,7 @@ class WriteableTagRegistry implements TagRegistry { export default class TagsLoader implements TagRegistryHolder { private registries: Record = {} - constructor(private readonly logger: Logger) { + constructor() { this.registerRegistry('items') this.registerRegistry('blocks') this.registerRegistry('fluids') @@ -133,7 +132,7 @@ export default class TagsLoader implements TagRegistryHolder { return { namespace, registry: registry[1], path, isTag: true } } - readonly accept: Acceptor = (path, content) => { + readonly accept: AcceptorWithLoader = (_, path, content) => { const info = this.parsePath(path) if (!info) return false diff --git a/src/parser/recipe/adAstra/hammering.ts b/src/parser/recipe/adAstra/hammering.ts new file mode 100644 index 0000000..b16eccb --- /dev/null +++ b/src/parser/recipe/adAstra/hammering.ts @@ -0,0 +1,41 @@ +import RecipeParser, { Recipe, replace } from '../index.js' +import { IngredientInput, Predicate } from '../../../common/ingredient.js' +import { RecipeDefinition } from '../../../schema/recipe.js' +import { ResultInput } from '../../../common/result.js' + +export type HammeringRecipeDefinition = RecipeDefinition & + Readonly<{ + ingredients: IngredientInput[] + result: ResultInput + mana?: number + }> + +export class HammeringRecipe extends Recipe { + getIngredients(): IngredientInput[] { + return this.definition.ingredients + } + + getResults(): ResultInput[] { + return [this.definition.result] + } + + replaceIngredient(from: Predicate, to: IngredientInput): Recipe { + return new HammeringRecipe({ + ...this.definition, + ingredients: this.definition.ingredients.map(replace(from, to)), + }) + } + + replaceResult(from: Predicate, to: ResultInput): Recipe { + return new HammeringRecipe({ + ...this.definition, + result: to, + }) + } +} + +export default class HammeringRecipeParser extends RecipeParser { + create(definition: HammeringRecipeDefinition): HammeringRecipe | null { + return new HammeringRecipe(definition) + } +} diff --git a/src/parser/recipe/adAstra/inputOutput.ts b/src/parser/recipe/adAstra/inputOutput.ts new file mode 100644 index 0000000..08bc20d --- /dev/null +++ b/src/parser/recipe/adAstra/inputOutput.ts @@ -0,0 +1,41 @@ +import RecipeParser, { Recipe, replaceOrKeep } from '../index.js' +import { IngredientInput, Predicate } from '../../../common/ingredient.js' +import { RecipeDefinition } from '../../../schema/recipe.js' +import { ResultInput } from '../../../common/result.js' + +export type InputOutputRecipeDefinition = RecipeDefinition & + Readonly<{ + input: IngredientInput + output: ResultInput + mana?: number + }> + +export class InputOutputRecipe extends Recipe { + getIngredients(): IngredientInput[] { + return [this.definition.input] + } + + getResults(): ResultInput[] { + return [this.definition.output] + } + + replaceIngredient(from: Predicate, to: IngredientInput): Recipe { + return new InputOutputRecipe({ + ...this.definition, + input: replaceOrKeep(from, to, this.definition.input), + }) + } + + replaceResult(from: Predicate, to: ResultInput): Recipe { + return new InputOutputRecipe({ + ...this.definition, + output: replaceOrKeep(from, to, this.definition.output), + }) + } +} + +export default class InputOutputRecipeParser extends RecipeParser { + create(definition: InputOutputRecipeDefinition): InputOutputRecipe | null { + return new InputOutputRecipe(definition) + } +} diff --git a/src/parser/recipe/adAstra/nasaWorkbench.ts b/src/parser/recipe/adAstra/nasaWorkbench.ts new file mode 100644 index 0000000..2a44f43 --- /dev/null +++ b/src/parser/recipe/adAstra/nasaWorkbench.ts @@ -0,0 +1,44 @@ +import RecipeParser, { Recipe, replace, replaceOrKeep } from '../index.js' +import { IngredientInput, Predicate } from '../../../common/ingredient.js' +import { RecipeDefinition } from '../../../schema/recipe.js' +import { ResultInput } from '../../../common/result.js' + +export type NasaWorkbenchRecipeDefinition = RecipeDefinition & + Readonly<{ + ingredients: IngredientInput[] + output: ResultInput + mana?: number + }> + +export class NasaWorkbenchRecipe extends Recipe { + getIngredients(): IngredientInput[] { + return this.definition.ingredients + } + + getResults(): ResultInput[] { + return [this.definition.output] + } + + replaceIngredient(from: Predicate, to: IngredientInput): Recipe { + return new NasaWorkbenchRecipe({ + ...this.definition, + ingredients: this.definition.ingredients.map(replace(from, to)), + }) + } + + replaceResult(from: Predicate, to: ResultInput): Recipe { + return new NasaWorkbenchRecipe({ + ...this.definition, + output: replaceOrKeep(from, to, this.definition.output), + }) + } +} + +export default class NasaWorkbenchRecipeParser extends RecipeParser< + NasaWorkbenchRecipeDefinition, + NasaWorkbenchRecipe +> { + create(definition: NasaWorkbenchRecipeDefinition): NasaWorkbenchRecipe | null { + return new NasaWorkbenchRecipe(definition) + } +} diff --git a/src/parser/recipe/adAstra/spaceStation.ts b/src/parser/recipe/adAstra/spaceStation.ts new file mode 100644 index 0000000..f43c8c5 --- /dev/null +++ b/src/parser/recipe/adAstra/spaceStation.ts @@ -0,0 +1,37 @@ +import RecipeParser, { Recipe, replace } from '../index.js' +import { IngredientInput, Predicate } from '../../../common/ingredient.js' +import { RecipeDefinition } from '../../../schema/recipe.js' +import { ResultInput } from '../../../common/result.js' + +export type SpaceStationRecipeDefinition = RecipeDefinition & + Readonly<{ + ingredients: IngredientInput[] + mana?: number + }> + +export class SpaceStationRecipe extends Recipe { + getIngredients(): IngredientInput[] { + return this.definition.ingredients + } + + getResults(): ResultInput[] { + return [] + } + + replaceIngredient(from: Predicate, to: IngredientInput): Recipe { + return new SpaceStationRecipe({ + ...this.definition, + ingredients: this.definition.ingredients.map(replace(from, to)), + }) + } + + replaceResult(): Recipe { + return new SpaceStationRecipe(this.definition) + } +} + +export default class SpaceStationRecipeParser extends RecipeParser { + create(definition: SpaceStationRecipeDefinition): SpaceStationRecipe | null { + return new SpaceStationRecipe(definition) + } +} diff --git a/src/parser/recipe/botania/apothecary.ts b/src/parser/recipe/botania/apothecary.ts index fe1cf61..9ee0b3f 100644 --- a/src/parser/recipe/botania/apothecary.ts +++ b/src/parser/recipe/botania/apothecary.ts @@ -36,7 +36,7 @@ export class ApothecaryRecipe extends Recipe { } export default class ApothecaryRecipeParser extends RecipeParser { - create(definition: ApothecaryRecipeDefinition): ApothecaryRecipe { + create(definition: ApothecaryRecipeDefinition): ApothecaryRecipe | null { return new ApothecaryRecipe(definition) } } diff --git a/src/parser/recipe/botania/brew.ts b/src/parser/recipe/botania/brew.ts index 8ee5153..241ce0a 100644 --- a/src/parser/recipe/botania/brew.ts +++ b/src/parser/recipe/botania/brew.ts @@ -31,7 +31,7 @@ export class BrewRecipe extends Recipe { } export default class BrewRecipeParser extends RecipeParser { - create(definition: BrewRecipeDefinition): BrewRecipe { + create(definition: BrewRecipeDefinition): BrewRecipe | null { return new BrewRecipe(definition) } } diff --git a/src/parser/recipe/botania/elvenTrade.ts b/src/parser/recipe/botania/elvenTrade.ts index 07768cc..18a7b93 100644 --- a/src/parser/recipe/botania/elvenTrade.ts +++ b/src/parser/recipe/botania/elvenTrade.ts @@ -35,7 +35,7 @@ export class ElvenTradeRecipe extends Recipe { } export default class ElvenTradeRecipeParser extends RecipeParser { - create(definition: ElvenTradeRecipeDefinition): ElvenTradeRecipe { + create(definition: ElvenTradeRecipeDefinition): ElvenTradeRecipe | null { return new ElvenTradeRecipe(definition) } } diff --git a/src/parser/recipe/botania/gogWrapper.ts b/src/parser/recipe/botania/gogWrapper.ts index a418d28..c599a74 100644 --- a/src/parser/recipe/botania/gogWrapper.ts +++ b/src/parser/recipe/botania/gogWrapper.ts @@ -3,6 +3,7 @@ import { IngredientInput, Predicate } from '../../../common/ingredient.js' import { RecipeDefinition } from '../../../schema/recipe.js' import RecipeLoader from '../../../loader/recipe.js' import { ResultInput } from '../../../common/result.js' +import { Logger } from '../../../logger.js' export type GogWrapperRecipeDefinition = RecipeDefinition & Readonly<{ @@ -11,13 +12,17 @@ export type GogWrapperRecipeDefinition = RecipeDefinition & gog: RecipeDefinition }> -export class GogWrapperRecipe extends Recipe> { +export class GogWrapperRecipe extends Recipe { constructor( - definition: Omit, + definition: Omit, private readonly base: Recipe, private readonly gog: Recipe ) { - super(definition) + super({ + ...definition, + base: base.toJSON(), + gog: gog.toJSON(), + }) } getIngredients(): IngredientInput[] { @@ -46,9 +51,9 @@ export default class GogWrapperRecipeParser extends RecipeParser { } export default class ManaInfusionRecipeParser extends RecipeParser { - create(definition: ManaInfusionRecipeDefinition): ManaInfusionRecipe { + create(definition: ManaInfusionRecipeDefinition): ManaInfusionRecipe | null { return new ManaInfusionRecipe(definition) } } diff --git a/src/parser/recipe/botania/nbtWrapper.ts b/src/parser/recipe/botania/nbtWrapper.ts index d657d04..c28ed25 100644 --- a/src/parser/recipe/botania/nbtWrapper.ts +++ b/src/parser/recipe/botania/nbtWrapper.ts @@ -3,6 +3,7 @@ import { IngredientInput, Predicate } from '../../../common/ingredient.js' import { RecipeDefinition } from '../../../schema/recipe.js' import RecipeLoader from '../../../loader/recipe.js' import { ResultInput } from '../../../common/result.js' +import { Logger } from '../../../logger.js' export type NbtWrapperRecipeDefinition = RecipeDefinition & Readonly<{ @@ -10,9 +11,12 @@ export type NbtWrapperRecipeDefinition = RecipeDefinition & recipe: RecipeDefinition }> -export class NbtWrapperRecipe extends Recipe> { +export class NbtWrapperRecipe extends Recipe { constructor(definition: Omit, private readonly recipe: Recipe) { - super(definition) + super({ + ...definition, + recipe: recipe.toJSON(), + }) } getIngredients(): IngredientInput[] { @@ -37,8 +41,8 @@ export default class NbtWrapperRecipeParser extends RecipeParser { } export default class OrechidRecipeParser extends RecipeParser { - create(definition: OrechidRecipeDefinition): OrechidRecipe { + create(definition: OrechidRecipeDefinition): OrechidRecipe | null { return new OrechidRecipe(definition) } } diff --git a/src/parser/recipe/botania/pureDaisy.ts b/src/parser/recipe/botania/pureDaisy.ts index 25f4279..cf7510a 100644 --- a/src/parser/recipe/botania/pureDaisy.ts +++ b/src/parser/recipe/botania/pureDaisy.ts @@ -43,7 +43,7 @@ export class PureDaisyRecipe extends Recipe { } export default class PureDaisyRecipeParser extends RecipeParser { - create(definition: PureDaisyRecipeDefinition): PureDaisyRecipe { + create(definition: PureDaisyRecipeDefinition): PureDaisyRecipe | null { return new PureDaisyRecipe(definition) } } diff --git a/src/parser/recipe/botania/runicAltar.ts b/src/parser/recipe/botania/runicAltar.ts index baaac67..c314e49 100644 --- a/src/parser/recipe/botania/runicAltar.ts +++ b/src/parser/recipe/botania/runicAltar.ts @@ -35,7 +35,7 @@ export class RunicAltarRecipe extends Recipe { } export default class RunicAltarRecipeParser extends RecipeParser { - create(definition: RunicAltarRecipeDefinition): RunicAltarRecipe { + create(definition: RunicAltarRecipeDefinition): RunicAltarRecipe | null { return new RunicAltarRecipe(definition) } } diff --git a/src/parser/recipe/botania/terraPlate.ts b/src/parser/recipe/botania/terraPlate.ts index 06a496f..dd51649 100644 --- a/src/parser/recipe/botania/terraPlate.ts +++ b/src/parser/recipe/botania/terraPlate.ts @@ -35,7 +35,7 @@ export class TerraPlateRecipe extends Recipe { } export default class TerraPlateRecipeParser extends RecipeParser { - create(definition: TerraPlateRecipeDefinition): TerraPlateRecipe { + create(definition: TerraPlateRecipeDefinition): TerraPlateRecipe | null { return new TerraPlateRecipe(definition) } } diff --git a/src/parser/recipe/create/assembly.ts b/src/parser/recipe/create/assembly.ts index 7b50cc0..5af30cb 100644 --- a/src/parser/recipe/create/assembly.ts +++ b/src/parser/recipe/create/assembly.ts @@ -13,7 +13,7 @@ export type AssemblyRecipeDefinition = RecipeDefinition & sequence: CreateProcessingRecipeDefinition[] }> -export class AssemblyRecipe extends Recipe { +export class AssemblyRecipe extends Recipe { private readonly sequence: CreateProcessingRecipe[] constructor(protected readonly definition: AssemblyRecipeDefinition) { @@ -52,7 +52,7 @@ export class AssemblyRecipe extends Recipe { } export default class AssemblyRecipeParser extends RecipeParser { - create(definition: AssemblyRecipeDefinition): AssemblyRecipe { + create(definition: AssemblyRecipeDefinition): AssemblyRecipe | null { return new AssemblyRecipe(definition) } } diff --git a/src/parser/recipe/create/processing.ts b/src/parser/recipe/create/processing.ts index d1f7243..43fe2cb 100644 --- a/src/parser/recipe/create/processing.ts +++ b/src/parser/recipe/create/processing.ts @@ -34,8 +34,11 @@ export class CreateProcessingRecipe extends Recipe { - create(definition: CreateProcessingRecipeDefinition): CreateProcessingRecipe { +export default class CreateProcessingRecipeParser extends RecipeParser< + CreateProcessingRecipeDefinition, + CreateProcessingRecipe +> { + create(definition: CreateProcessingRecipeDefinition): CreateProcessingRecipe | null { return new CreateProcessingRecipe(definition) } } diff --git a/src/parser/recipe/farmersdelight/cooking.ts b/src/parser/recipe/farmersdelight/cooking.ts index 7d719e7..988ca2f 100644 --- a/src/parser/recipe/farmersdelight/cooking.ts +++ b/src/parser/recipe/farmersdelight/cooking.ts @@ -40,7 +40,7 @@ export class CookingRecipe extends Recipe { } export default class CookingRecipeParser extends RecipeParser { - create(definition: CookingRecipeDefinition): CookingRecipe { + create(definition: CookingRecipeDefinition): CookingRecipe | null { return new CookingRecipe(definition) } } diff --git a/src/parser/recipe/farmersdelight/cutting.ts b/src/parser/recipe/farmersdelight/cutting.ts index 14c5a81..f013ef4 100644 --- a/src/parser/recipe/farmersdelight/cutting.ts +++ b/src/parser/recipe/farmersdelight/cutting.ts @@ -35,7 +35,7 @@ export class CuttingRecipe extends Recipe { } export default class CuttingRecipeParser extends RecipeParser { - create(definition: CuttingRecipeDefinition): CuttingRecipe { + create(definition: CuttingRecipeDefinition): CuttingRecipe | null { return new CuttingRecipe(definition) } } diff --git a/src/parser/recipe/forge/conditional.ts b/src/parser/recipe/forge/conditional.ts new file mode 100644 index 0000000..8ba9157 --- /dev/null +++ b/src/parser/recipe/forge/conditional.ts @@ -0,0 +1,73 @@ +import RecipeParser, { Recipe } from '../index.js' +import { IngredientInput, Predicate } from '../../../common/ingredient.js' +import { RecipeDefinition } from '../../../schema/recipe.js' +import { ResultInput } from '../../../common/result.js' +import RecipeLoader from '../../../loader/recipe.js' +import { Logger } from '../../../logger.js' + +type WithConditions = { + conditions: unknown[] + recipe: T +} + +export type ForgeConditionalRecipeDefinition = RecipeDefinition & + Readonly<{ + recipes: WithConditions[] + }> + +export class ForgeConditionalRecipe extends Recipe { + constructor( + definition: Omit, + private readonly recipes: WithConditions[] + ) { + super({ + ...definition, + recipes: recipes.map(it => ({ + conditions: it.conditions, + recipe: it.recipe.toJSON(), + })), + }) + } + + getIngredients(): IngredientInput[] { + return this.recipes.flatMap(it => it.recipe.getIngredients()) + } + + getResults(): ResultInput[] { + return this.recipes.flatMap(it => it.recipe.getResults()) + } + + replaceIngredient(from: Predicate, to: IngredientInput): Recipe { + return new ForgeConditionalRecipe( + this.definition, + this.recipes.map(it => ({ ...it, recipe: it.recipe.replaceIngredient(from, to) })) + ) + } + + replaceResult(from: Predicate, to: ResultInput): ForgeConditionalRecipe { + return new ForgeConditionalRecipe( + this.definition, + this.recipes.map(it => ({ ...it, recipe: it.recipe.replaceResult(from, to) })) + ) + } +} + +export default class ForgeConditionalRecipeParser extends RecipeParser< + ForgeConditionalRecipeDefinition, + ForgeConditionalRecipe +> { + constructor(private readonly loader: RecipeLoader) { + super() + } + + create(definition: ForgeConditionalRecipeDefinition, logger: Logger): ForgeConditionalRecipe | null { + const recipes = definition.recipes.map>(it => ({ + conditions: it.conditions, + recipe: this.loader.parse(logger, it.recipe), + })) + + if (recipes.some(it => !it.recipe)) return null + + return new ForgeConditionalRecipe(definition, recipes as WithConditions[]) + } +} diff --git a/src/parser/recipe/index.ts b/src/parser/recipe/index.ts index 916fc5f..d7e4f27 100644 --- a/src/parser/recipe/index.ts +++ b/src/parser/recipe/index.ts @@ -1,6 +1,7 @@ import { IngredientInput, Predicate } from '../../common/ingredient.js' import { RecipeDefinition } from '../../schema/recipe.js' import { ResultInput } from '../../common/result.js' +import { Logger } from '../../logger.js' export function replace(from: Predicate, to: T) { return (it: T) => { @@ -30,5 +31,5 @@ export abstract class Recipe { - abstract create(definition: TDefinition): TRecipe | null + abstract create(definition: TDefinition, logger: Logger): TRecipe | null } diff --git a/src/parser/recipe/quark/exclusion.ts b/src/parser/recipe/quark/exclusion.ts new file mode 100644 index 0000000..e04c2c3 --- /dev/null +++ b/src/parser/recipe/quark/exclusion.ts @@ -0,0 +1,59 @@ +import RecipeParser, { Recipe } from '../index.js' +import { IngredientInput, Predicate } from '../../../common/ingredient.js' +import { RecipeDefinition } from '../../../schema/recipe.js' +import { ResultInput } from '../../../common/result.js' +import RecipeLoader from '../../../loader/recipe.js' +import { Logger } from '../../../logger.js' + +export type QuarkExclusionRecipeDefinition = T & + Readonly<{ + true_type: string + exclusions?: string[] + }> + +export class QuarkExclusionRecipe< + TDefinition extends RecipeDefinition = RecipeDefinition, + TRecipe extends Recipe = Recipe +> extends Recipe> { + constructor(definition: QuarkExclusionRecipeDefinition, private readonly trueRecipe: TRecipe) { + super({ + ...definition, + ...trueRecipe.toJSON(), + true_type: trueRecipe.toJSON().type, + type: definition.type, + }) + } + + getIngredients(): IngredientInput[] { + return this.trueRecipe.getIngredients() + } + + getResults(): ResultInput[] { + return this.trueRecipe.getResults() + } + + replaceIngredient(from: Predicate, to: IngredientInput): QuarkExclusionRecipe { + return new QuarkExclusionRecipe(this.definition, this.trueRecipe.replaceIngredient(from, to) as TRecipe) + } + + replaceResult(from: Predicate, to: ResultInput): QuarkExclusionRecipe { + return new QuarkExclusionRecipe(this.definition, this.trueRecipe.replaceResult(from, to) as TRecipe) + } +} + +export default class QuarkExclusionRecipeParser extends RecipeParser< + QuarkExclusionRecipeDefinition, + QuarkExclusionRecipe +> { + constructor(private readonly loader: RecipeLoader) { + super() + } + + create(definition: QuarkExclusionRecipeDefinition, logger: Logger): QuarkExclusionRecipe | null { + const trueRecipe = this.loader.parse(logger, { ...definition, type: definition.true_type }) + + if (!trueRecipe) return null + + return new QuarkExclusionRecipe(definition, trueRecipe) + } +} diff --git a/src/parser/recipe/roots/component.ts b/src/parser/recipe/roots/component.ts new file mode 100644 index 0000000..3fd5165 --- /dev/null +++ b/src/parser/recipe/roots/component.ts @@ -0,0 +1,40 @@ +import RecipeParser, { Recipe, replace } from '../index.js' +import { IngredientInput, Predicate } from '../../../common/ingredient.js' +import { RecipeDefinition } from '../../../schema/recipe.js' +import { ResultInput } from '../../../common/result.js' + +export type RootComponentRecipeDefinition = RecipeDefinition & + Readonly<{ + effect: string + ingredients: IngredientInput[] + }> + +export class RootComponentRecipe extends Recipe { + getIngredients(): IngredientInput[] { + return this.definition.ingredients + } + + getResults(): ResultInput[] { + return [] + } + + replaceIngredient(from: Predicate, to: IngredientInput): Recipe { + return new RootComponentRecipe({ + ...this.definition, + ingredients: this.definition.ingredients.map(replace(from, to)), + }) + } + + replaceResult(): RootComponentRecipe { + return new RootComponentRecipe(this.definition) + } +} + +export default class RootComponentRecipeParser extends RecipeParser< + RootComponentRecipeDefinition, + RootComponentRecipe +> { + create(definition: RootComponentRecipeDefinition): RootComponentRecipe | null { + return new RootComponentRecipe(definition) + } +} diff --git a/src/parser/recipe/roots/ritual.ts b/src/parser/recipe/roots/ritual.ts new file mode 100644 index 0000000..a09d436 --- /dev/null +++ b/src/parser/recipe/roots/ritual.ts @@ -0,0 +1,41 @@ +import RecipeParser, { Recipe, replace } from '../index.js' +import { IngredientInput, Predicate } from '../../../common/ingredient.js' +import { RecipeDefinition } from '../../../schema/recipe.js' +import { ResultInput } from '../../../common/result.js' + +export type RootRitualRecipeDefinition = RecipeDefinition & + Readonly<{ + color: string + effect: string + level: number + incenses?: IngredientInput[] + ingredients?: IngredientInput[] + }> + +export class RootRitualRecipe extends Recipe { + getIngredients(): IngredientInput[] { + return [...(this.definition.ingredients ?? []), ...(this.definition.incenses ?? [])] + } + + getResults(): ResultInput[] { + return [] + } + + replaceIngredient(from: Predicate, to: IngredientInput): Recipe { + return new RootRitualRecipe({ + ...this.definition, + ingredients: this.definition.ingredients?.map(replace(from, to)), + incenses: this.definition.incenses?.map(replace(from, to)), + }) + } + + replaceResult(): RootRitualRecipe { + return new RootRitualRecipe(this.definition) + } +} + +export default class RootRitualRecipeParser extends RecipeParser { + create(definition: RootRitualRecipeDefinition): RootRitualRecipe | null { + return new RootRitualRecipe(definition) + } +} diff --git a/src/parser/recipe/thermal/catalyst.ts b/src/parser/recipe/thermal/catalyst.ts index 31e0191..6103695 100644 --- a/src/parser/recipe/thermal/catalyst.ts +++ b/src/parser/recipe/thermal/catalyst.ts @@ -38,7 +38,7 @@ export default class ThermalCatalystRecipeParser extends RecipeParser< ThermalCatalystRecipeDefinition, ThermalCatalystRecipe > { - create(definition: ThermalCatalystRecipeDefinition): ThermalCatalystRecipe { + create(definition: ThermalCatalystRecipeDefinition): ThermalCatalystRecipe | null { return new ThermalCatalystRecipe(definition) } } diff --git a/src/parser/recipe/thermal/fuel.ts b/src/parser/recipe/thermal/fuel.ts new file mode 100644 index 0000000..5665a2b --- /dev/null +++ b/src/parser/recipe/thermal/fuel.ts @@ -0,0 +1,37 @@ +import RecipeParser, { Recipe, replaceOrKeep } from '../index.js' +import { IngredientInput, Predicate } from '../../../common/ingredient.js' +import { RecipeDefinition } from '../../../schema/recipe.js' +import { ResultInput } from '../../../common/result.js' + +export type ThermalFuelRecipeDefinition = RecipeDefinition & + Readonly<{ + energy: number + ingredient: IngredientInput + }> + +export class ThermalFuelRecipe extends Recipe { + getIngredients(): IngredientInput[] { + return [this.definition.ingredient] + } + + getResults(): ResultInput[] { + return [] + } + + replaceIngredient(from: Predicate, to: IngredientInput): Recipe { + return new ThermalFuelRecipe({ + ...this.definition, + ingredient: replaceOrKeep(from, to, this.definition.ingredient), + }) + } + + replaceResult(): ThermalFuelRecipe { + return new ThermalFuelRecipe(this.definition) + } +} + +export default class ThermalFuelRecipeParser extends RecipeParser { + create(definition: ThermalFuelRecipeDefinition): ThermalFuelRecipe | null { + return new ThermalFuelRecipe(definition) + } +} diff --git a/src/parser/recipe/thermal/index.ts b/src/parser/recipe/thermal/index.ts index 90cbdf5..34b24a9 100644 --- a/src/parser/recipe/thermal/index.ts +++ b/src/parser/recipe/thermal/index.ts @@ -59,7 +59,7 @@ export class ThermalRecipe extends Recipe { } export default class ThermalRecipeParser extends RecipeParser { - create(definition: ThermalRecipeDefinition): ThermalRecipe { + create(definition: ThermalRecipeDefinition): ThermalRecipe | null { return new ThermalRecipe(definition) } } diff --git a/src/parser/recipe/thermal/treeExtraction.ts b/src/parser/recipe/thermal/treeExtraction.ts new file mode 100644 index 0000000..9cc882f --- /dev/null +++ b/src/parser/recipe/thermal/treeExtraction.ts @@ -0,0 +1,59 @@ +import RecipeParser, { Recipe, replaceOrKeep } from '../index.js' +import { IngredientInput, Predicate } from '../../../common/ingredient.js' +import { RecipeDefinition } from '../../../schema/recipe.js' +import { ResultInput } from '../../../common/result.js' +import { IllegalShapeError } from '../../../error.js' + +type ExtractionBlockInput = string + +export type TreeExtractionRecipeDefinition = RecipeDefinition & + Readonly<{ + leaves: ExtractionBlockInput + trunk: ExtractionBlockInput + result: ResultInput + }> + +function blockToIngredient(input: ExtractionBlockInput): IngredientInput { + if (typeof input !== 'string') throw new IllegalShapeError('unknown block input shape', input) + return { + block: input, + } +} + +function ingredientToBlock(input: IngredientInput): ExtractionBlockInput { + if (input && typeof input === 'object') { + if ('block' in input) return input.block + } + throw new IllegalShapeError('unknown block input shape', input) +} + +export class TreeExtractionRecipe extends Recipe { + getIngredients(): IngredientInput[] { + return [blockToIngredient(this.definition.leaves), blockToIngredient(this.definition.trunk)] + } + + getResults(): ResultInput[] { + return [] + } + + replaceIngredient(from: Predicate, to: IngredientInput): Recipe { + return new TreeExtractionRecipe({ + ...this.definition, + leaves: ingredientToBlock(replaceOrKeep(from, to, this.definition.leaves)), + trunk: ingredientToBlock(replaceOrKeep(from, to, this.definition.trunk)), + }) + } + + replaceResult(): TreeExtractionRecipe { + return new TreeExtractionRecipe(this.definition) + } +} + +export default class TreeExtractionRecipeParser extends RecipeParser< + TreeExtractionRecipeDefinition, + TreeExtractionRecipe +> { + create(definition: TreeExtractionRecipeDefinition): TreeExtractionRecipe | null { + return new TreeExtractionRecipe(definition) + } +} diff --git a/src/parser/recipe/vanilla/shaped.ts b/src/parser/recipe/vanilla/shaped.ts index 02ae725..6a3ae5b 100644 --- a/src/parser/recipe/vanilla/shaped.ts +++ b/src/parser/recipe/vanilla/shaped.ts @@ -36,7 +36,7 @@ export class ShapedRecipe extends Recipe { } export default class ShapedParser extends RecipeParser { - create(definition: ShapedRecipeDefinition): ShapedRecipe { + create(definition: ShapedRecipeDefinition): ShapedRecipe | null { return new ShapedRecipe(definition) } } diff --git a/src/parser/recipe/vanilla/shapeless.ts b/src/parser/recipe/vanilla/shapeless.ts index fbfdf91..62c5be8 100644 --- a/src/parser/recipe/vanilla/shapeless.ts +++ b/src/parser/recipe/vanilla/shapeless.ts @@ -34,7 +34,7 @@ export class ShapelessRecipe extends Recipe { } export default class ShapelessParser extends RecipeParser { - create(definition: ShapelessRecipeDefinition): ShapelessRecipe { + create(definition: ShapelessRecipeDefinition): ShapelessRecipe | null { return new ShapelessRecipe(definition) } } diff --git a/src/parser/recipe/vanilla/smelting.ts b/src/parser/recipe/vanilla/smelting.ts index 3503dbc..a65db92 100644 --- a/src/parser/recipe/vanilla/smelting.ts +++ b/src/parser/recipe/vanilla/smelting.ts @@ -35,7 +35,7 @@ export class SmeltingRecipe extends Recipe { } export default class SmeltingParser extends RecipeParser { - create(definition: SmeltingRecipeDefinition): SmeltingRecipe { + create(definition: SmeltingRecipeDefinition): SmeltingRecipe | null { return new SmeltingRecipe(definition) } } diff --git a/src/parser/recipe/vanilla/smithing.ts b/src/parser/recipe/vanilla/smithing.ts index c180dfa..2100282 100644 --- a/src/parser/recipe/vanilla/smithing.ts +++ b/src/parser/recipe/vanilla/smithing.ts @@ -36,7 +36,7 @@ export class SmithingRecipe extends Recipe { } export default class SmithingParser extends RecipeParser { - create(definition: SmithingRecipeDefinition): SmithingRecipe { + create(definition: SmithingRecipeDefinition): SmithingRecipe | null { return new SmithingRecipe(definition) } } diff --git a/src/parser/recipe/vanilla/stonecutting.ts b/src/parser/recipe/vanilla/stonecutting.ts index 2654a64..4005ecd 100644 --- a/src/parser/recipe/vanilla/stonecutting.ts +++ b/src/parser/recipe/vanilla/stonecutting.ts @@ -41,7 +41,7 @@ export class StonecuttingRecipe extends Recipe { } export default class StonecuttingParser extends RecipeParser { - create(definition: StonecuttingRecipeDefinition): StonecuttingRecipe { + create(definition: StonecuttingRecipeDefinition): StonecuttingRecipe | null { return new StonecuttingRecipe(definition) } } diff --git a/src/schema/loot.ts b/src/schema/loot.ts index ae45de8..bd862ec 100644 --- a/src/schema/loot.ts +++ b/src/schema/loot.ts @@ -3,7 +3,7 @@ import zod from 'zod' const NumberProviderSchema = zod.union([ zod.number(), zod.object({ - type: zod.string(), + type: zod.string().optional(), }), ]) @@ -75,7 +75,7 @@ export const LootPoolSchema = zod.object({ export type LootPool = zod.infer export const LootTableSchema = zod.object({ - type: zod.string(), + type: zod.string().optional(), pools: zod.array(LootPoolSchema).default([]), }) diff --git a/src/textHelper.ts b/src/textHelper.ts index 9c05f9c..66847f5 100644 --- a/src/textHelper.ts +++ b/src/textHelper.ts @@ -2,7 +2,7 @@ import { format } from 'prettier' import json from 'json5' export function fromJson(input: string) { - return json.parse(input) + return json.parse(input.replaceAll('\r\n', '')) } export function toJson(input: unknown) { diff --git a/test/recipe.test.ts b/test/recipe.test.ts index 15e72ce..46631a2 100644 --- a/test/recipe.test.ts +++ b/test/recipe.test.ts @@ -6,6 +6,10 @@ import setupLoader from './shared/loaderSetup.js' const { logger, loader } = setupLoader() it('has no unknown recipe loaders', () => { + expect(loader.recipeLoader.unknownRecipeTypes()).toMatchObject([]) +}) + +it('does not encounter any errors', () => { expect(logger.warn).not.toHaveBeenCalled() expect(logger.error).not.toHaveBeenCalled() }) diff --git a/test/shared/loaderSetup.ts b/test/shared/loaderSetup.ts index 2279b18..12d3da2 100644 --- a/test/shared/loaderSetup.ts +++ b/test/shared/loaderSetup.ts @@ -3,10 +3,12 @@ import { PackLoader } from '../../src/index.js' import createTestResolver from '../mock/TestResolver.js' import { Options } from '@pssbletrngle/pack-resolver' -export default function setupLoader(options?: Partial) { +export default function setupLoader(options?: Partial, block?: (loader: PackLoader) => void) { const logger = createTestLogger() const loader = new PackLoader(logger) + block?.(loader) + beforeAll(async () => { const resolver = createTestResolver(options) await loader.loadFrom(resolver)