Skip to content

Commit

Permalink
common emitter logic & basic loot loader/emitter
Browse files Browse the repository at this point in the history
  • Loading branch information
PssbleTrngle committed Oct 12, 2023
1 parent 23aaa5e commit 154889e
Show file tree
Hide file tree
Showing 17 changed files with 472 additions and 140 deletions.
62 changes: 14 additions & 48 deletions src/emit/custom.ts
Original file line number Diff line number Diff line change
@@ -1,68 +1,34 @@
import Rule from '../rule'
import { Acceptor } from '@pssbletrngle/pack-resolver'
import { Recipe } from '../parser/recipe'
import { toJson } from '../textHelper'
import { RecipeDefinition } from '../schema/recipe'
import { createId, Id, IdInput } from '../common/id'
import { createId, IdInput } from '../common/id'
import Registry from '../common/registry'
import { LootTable } from '../loader/loot'
import { PathProvider } from './index'

export interface RegistryProvider<T> {
forEach(consumer: (recipe: T, id: Id) => void): void
}

export default abstract class RuledEmitter<TEntry, TRule extends Rule<TEntry>> {
export default class CustomEmitter<TEntry> {
constructor(private readonly pathProvider: PathProvider) {}

protected constructor(private readonly provider: RegistryProvider<TEntry>) {
protected addCustom(id: IdInput, value: TEntry) {
this.customEntries.set(createId(id), value)
}


private customEntries = new Registry<TEntry>()
private rulesArray: TRule[] = []

protected get rules(): ReadonlyArray<TRule> {
return this.rulesArray
}

clear() {
this.rulesArray = []
}

protected addRule(rule: TRule) {
this.rulesArray.push(rule)
this.customEntries.clear()
}

protected addCustom(
id: IdInput,
value: TEntry
) {
add(id: IdInput, value: TEntry) {
this.customEntries.set(createId(id), value)
}

private async modify(acceptor: Acceptor) {
this.provider.forEach((recipe, id) => {
if (this.customRecipe.has(id)) return

const path = this.recipePath(id)

const rules = this.rules.filter(it => it.matches(id, recipe, this.logger))
if (rules.length === 0) return

const modified = rules.reduce<Recipe | null>((previous, rule) => previous && rule.modify(previous), recipe)

acceptor(path, toJson(modified?.toDefinition() ?? RecipeEmitter.EMPTY_RECIPE))
})
}

private async create(acceptor: Acceptor) {
this.customRecipe.forEach((recipe, id) => {
const path = this.recipePath(id)
acceptor(path, toJson(recipe))
async emit(acceptor: Acceptor) {
this.customEntries.forEach((entry, id) => {
const path = this.pathProvider(id)
acceptor(path, toJson(entry))
})
}

async emit(acceptor: Acceptor) {
await Promise.all([this.modifyRecipes(acceptor), this.createRecipes(acceptor)])
has(id: IdInput) {
return this.customEntries.has(id)
}

}
7 changes: 7 additions & 0 deletions src/emit/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { Id } from '../common/id'

export interface RegistryProvider<T> {
forEach(consumer: (recipe: T, id: Id) => void): void
}

export type PathProvider = (id: Id) => string
64 changes: 64 additions & 0 deletions src/emit/loot.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import { Logger } from '../logger'
import TagsLoader from '../loader/tags'
import { LootTable, LootTableSchema } from '../schema/loot'
import { Acceptor } from '@pssbletrngle/pack-resolver'
import RuledEmitter from './ruled'
import CustomEmitter from './custom'
import { Id, IdInput } from '../common/id'
import LootTableRule from '../rule/lootTable'
import { RegistryProvider } from './index'
import { Predicate } from '../common/ingredient'

export const EMPTY_LOOT_TABLE: LootTable = {
type: 'minecraft:empty',
pools: [],
}

type LootTableTest = Predicate<Id>

export interface LootRules {
// replace(test: IngredientTest, to: Item | ItemTag): void

addLootTable(id: IdInput, value: LootTable): void

removeLootTable(test: LootTableTest): void
}

export default class LootTableEmitter implements LootRules {
private readonly custom = new CustomEmitter<LootTable>(this.lootPath)

private readonly ruled = new RuledEmitter<LootTable, LootTableRule>(
this.logger,
this.lootTables,
this.lootPath,
EMPTY_LOOT_TABLE,
id => this.custom.has(id)
)

constructor(
private readonly logger: Logger,
private readonly lootTables: RegistryProvider<LootTable>,
private readonly tags: TagsLoader
) {}

private lootPath(id: Id) {
return `data/${id.namespace}/loot_tables/${id.path}.json`
}

async emit(acceptor: Acceptor) {
await Promise.all([this.ruled.emit(acceptor), this.custom.emit(acceptor)])
}

addLootTable(id: IdInput, value: LootTable): void {
this.custom.add(id, LootTableSchema.parse(value))
}

removeLootTable(test: LootTableTest): void {
this.ruled.addRule(new LootTableRule([test], () => null))
}

clear() {
this.custom.clear()
this.ruled.clear()
}
}
105 changes: 43 additions & 62 deletions src/emit/recipe.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { RecipeRegistry } from '../loader/recipe'
import { Acceptor } from '@pssbletrngle/pack-resolver'
import { toJson } from '../textHelper'
import {
CommonTest,
Ingredient,
Expand All @@ -12,12 +9,15 @@ import {
import RecipeRule from '../rule/recipe'
import { Logger } from '../logger'
import TagsLoader from '../loader/tags'
import { createId, Id, IdInput, NormalizedId } from '../common/id'
import { Id, IdInput, NormalizedId } from '../common/id'
import { Recipe } from '../parser/recipe'
import { RecipeDefinition } from '../schema/recipe'
import Registry from '../common/registry'
import { resolveIDTest } from '../common/predicates'
import { Result } from '../common/result'
import RuledEmitter from './ruled'
import { RegistryProvider } from './index'
import CustomEmitter from './custom'
import { Acceptor } from '@pssbletrngle/pack-resolver'

type RecipeTest = Readonly<{
id?: CommonTest<NormalizedId>
Expand All @@ -39,67 +39,47 @@ export interface RecipeRules {
removeRecipe(test: RecipeTest): void
}

export default class RecipeEmitter implements RecipeRules {
// TODO conditions
static readonly EMPTY_RECIPE: RecipeDefinition = {
type: 'noop',
conditions: [
{
type: 'forge:false',
export const EMPTY_RECIPE: RecipeDefinition = {
type: 'noop',
conditions: [
{
type: 'forge:false',
},
],
'fabric:load_conditions': [
{
condition: 'fabric:not',
value: {
condition: 'fabric:all_mods_loaded',
values: ['minecraft'],
},
],
'fabric:load_conditions': [
{
condition: 'fabric:not',
value: {
condition: 'fabric:all_mods_loaded',
values: ['minecraft'],
},
},
],
}
},
],
}

private rules: RecipeRule[] = []
private customRecipe = new Registry<RecipeDefinition>()
export default class RecipeEmitter implements RecipeRules {
private readonly custom = new CustomEmitter<RecipeDefinition>(this.recipePath)

private readonly ruled = new RuledEmitter<Recipe, RecipeRule>(
this.logger,
this.registry,
this.recipePath,
EMPTY_RECIPE,
id => this.custom.has(id)
)

constructor(
private readonly logger: Logger,
private readonly registry: RecipeRegistry,
private readonly registry: RegistryProvider<Recipe>,
private readonly tags: TagsLoader
) {}

clear() {
this.rules = []
}

private recipePath(id: Id) {
return `data/${id.namespace}/recipe/${id.path}.json`
}

private async modifyRecipes(acceptor: Acceptor) {
this.registry.forEach((recipe, id) => {
if (this.customRecipe.has(id)) return

const path = this.recipePath(id)

const rules = this.rules.filter(it => it.matches(id, recipe, this.logger))
if (rules.length === 0) return

const modified = rules.reduce<Recipe | null>((previous, rule) => previous && rule.modify(previous), recipe)

acceptor(path, toJson(modified?.toDefinition() ?? RecipeEmitter.EMPTY_RECIPE))
})
}

private async createRecipes(acceptor: Acceptor) {
this.customRecipe.forEach((recipe, id) => {
const path = this.recipePath(id)
acceptor(path, toJson(recipe))
})
}

async emit(acceptor: Acceptor) {
await Promise.all([this.modifyRecipes(acceptor), this.createRecipes(acceptor)])
await Promise.all([this.ruled.emit(acceptor), this.custom.emit(acceptor)])
}

resolveIngredientTest(test: IngredientTest) {
Expand All @@ -119,29 +99,25 @@ export default class RecipeEmitter implements RecipeRules {
return { recipe, ingredient, result }
}

addRule(rule: RecipeRule) {
this.rules.push(rule)
}

addRecipe<TDefinition extends RecipeDefinition, TRecipe extends Recipe<TDefinition>>(
id: IdInput,
value: TDefinition | TRecipe
) {
if (value instanceof Recipe) this.addRecipe(id, value.toDefinition())
else this.customRecipe.set(createId(id), value)
if (value instanceof Recipe) this.custom.add(id, value.toJSON())
else this.custom.add(id, value)
}

removeRecipe(test: RecipeTest) {
const recipePredicates = this.resolveRecipeTest(test)
this.addRule(
this.ruled.addRule(
new RecipeRule(recipePredicates.recipe, recipePredicates.ingredient, recipePredicates.result, () => null)
)
}

replaceResult(test: IngredientTest, value: Result, additionalTest: RecipeTest = {}) {
const predicate = this.resolveIngredientTest(test)
const recipePredicates = this.resolveRecipeTest(additionalTest)
this.addRule(
this.ruled.addRule(
new RecipeRule(
recipePredicates.recipe,
recipePredicates.ingredient,
Expand All @@ -154,7 +130,7 @@ export default class RecipeEmitter implements RecipeRules {
replaceIngredient(test: IngredientTest, value: Ingredient, additionalTest: RecipeTest = {}) {
const predicate = this.resolveIngredientTest(test)
const recipePredicates = this.resolveRecipeTest(additionalTest)
this.addRule(
this.ruled.addRule(
new RecipeRule(
recipePredicates.recipe,
[predicate, ...recipePredicates.ingredient],
Expand All @@ -163,4 +139,9 @@ export default class RecipeEmitter implements RecipeRules {
)
)
}

clear() {
this.custom.clear()
this.ruled.clear()
}
}
47 changes: 47 additions & 0 deletions src/emit/ruled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import Rule from '../rule'
import { Acceptor } from '@pssbletrngle/pack-resolver'
import { toJson } from '../textHelper'
import Registry from '../common/registry'
import { PathProvider, RegistryProvider } from './index'
import { Logger } from '../logger'
import { Id } from '../common/id'

export default class RuledEmitter<TEntry, TRule extends Rule<TEntry>> {
constructor(
private readonly logger: Logger,
private readonly provider: RegistryProvider<TEntry>,
private readonly pathProvider: PathProvider,
private readonly emptyValue: unknown,
private readonly shouldSkip: (id: Id) => boolean = () => true
) {}

private customEntries = new Registry<TEntry>()
private rulesArray: TRule[] = []

protected get rules(): ReadonlyArray<TRule> {
return this.rulesArray
}

clear() {
this.rulesArray = []
}

addRule(rule: TRule) {
this.rulesArray.push(rule)
}

async emit(acceptor: Acceptor) {
this.provider.forEach((recipe, id) => {
if (this.shouldSkip(id)) return

const path = this.pathProvider(id)

const rules = this.rules.filter(it => it.matches(id, recipe, this.logger))
if (rules.length === 0) return

const modified = rules.reduce<TEntry | null>((previous, rule) => previous && rule.modify(previous), recipe)

acceptor(path, toJson(modified ?? this.emptyValue))
})
}
}
6 changes: 5 additions & 1 deletion src/error.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Logger } from './logger'
import { ZodError } from 'zod'

export class IllegalShapeError extends Error {
constructor(message: string, readonly input?: unknown) {
Expand All @@ -15,7 +16,10 @@ export function tryCatching<T>(logger: Logger | undefined, run: () => T): T | nu
return null
}

// TODO catch zod errors?
if (error instanceof ZodError) {
logger?.warn(`unknown shape`, error)
return null
}

throw error
}
Expand Down
Loading

0 comments on commit 154889e

Please sign in to comment.