diff --git a/src/app/pages/Home.tsx b/src/app/pages/Home.tsx
index 8e7f0b13..6bfd6a20 100644
--- a/src/app/pages/Home.tsx
+++ b/src/app/pages/Home.tsx
@@ -22,6 +22,7 @@ export function Home({}: Props) {
)}
+
diff --git a/src/app/pages/Partners.tsx b/src/app/pages/Partners.tsx
new file mode 100644
index 00000000..2338382d
--- /dev/null
+++ b/src/app/pages/Partners.tsx
@@ -0,0 +1,27 @@
+import config from '../../config.json'
+import { ToolCard } from '../components'
+import { useLocale, useTitle } from '../contexts'
+import { cleanUrl } from '../Utils'
+
+const partners = [...new Set(config.generators
+ .filter(g => g.partner !== undefined)
+ .map(g => g.partner as string)
+)]
+
+interface Props {
+ path?: string,
+}
+export function Partners({}: Props) {
+ const { locale } = useLocale()
+ useTitle(locale('title.partners'))
+
+ return
+
+ {partners.map(p =>
+ {config.generators.filter(g => g.partner === p).map(g =>
+
+ )}
+ )}
+
+
+}
diff --git a/src/app/pages/index.ts b/src/app/pages/index.ts
index fa112ff0..f562da75 100644
--- a/src/app/pages/index.ts
+++ b/src/app/pages/index.ts
@@ -4,6 +4,7 @@ export * from './Generator'
export * from './Guide'
export * from './Guides'
export * from './Home'
+export * from './Partners'
export * from './Project'
export * from './Sounds'
export * from './Versions'
diff --git a/src/app/partners/ImmersiveWeathering.ts b/src/app/partners/ImmersiveWeathering.ts
new file mode 100644
index 00000000..ac3da960
--- /dev/null
+++ b/src/app/partners/ImmersiveWeathering.ts
@@ -0,0 +1,210 @@
+import type { CollectionRegistry, ResourceType, SchemaRegistry } from '@mcschema/core'
+import { BooleanNode, Case, ChoiceNode, ListNode, MapNode, NumberNode, ObjectNode, Opt, Reference as RawReference, StringNode as RawStringNode, Switch } from '@mcschema/core'
+
+const ID = 'immersive_weathering'
+
+export function initImmersiveWeathering(schemas: SchemaRegistry, collections: CollectionRegistry) {
+ const Reference = RawReference.bind(undefined, schemas)
+ const StringNode = RawStringNode.bind(undefined, collections)
+
+ const Tag = (id: Exclude
) => ChoiceNode([
+ {
+ type: 'string',
+ node: StringNode({ validator: 'resource', params: { pool: id, allowTag: true } }),
+ change: (v: unknown) => {
+ if (Array.isArray(v) && typeof v[0] === 'string' && !v[0].startsWith('#')) {
+ return v[0]
+ }
+ return undefined
+ },
+ },
+ {
+ type: 'list',
+ node: ListNode(
+ StringNode({ validator: 'resource', params: { pool: id } })
+ ),
+ change: (v: unknown) => {
+ if (typeof v === 'string' && !v.startsWith('#')) {
+ return [v]
+ }
+ return []
+ },
+ },
+ ], { choiceContext: 'tag' })
+
+ schemas.register(`${ID}:block_growth`, ObjectNode({
+ area_condition: Reference(`${ID}:area_condition`),
+ position_predicates: Opt(ListNode(
+ Reference(`${ID}:position_test`)
+ )),
+ growth_chance: NumberNode({ min: 0, max: 1 }),
+ growth_for_face: ListNode(
+ ObjectNode({
+ direction: Opt(StringNode({ enum: 'direction' })),
+ weight: Opt(NumberNode({ integer: true })),
+ growth: ListNode(
+ ObjectNode({
+ data: Reference(`${ID}:block_pair`),
+ weight: NumberNode({ integer: true }),
+ })
+ ),
+ }, { category: 'pool' })
+ ),
+ owners: ListNode(
+ StringNode({ validator: 'resource', params: { pool: 'block' } })
+ ),
+ replacing_target: Reference(`${ID}:rule_test`),
+ target_self: Opt(BooleanNode()),
+ destroy_target: Opt(BooleanNode()),
+ }, { context: `${ID}.block_growth` }))
+
+ schemas.register(`${ID}:area_condition`, ObjectNode({
+ type: StringNode({ enum: ['generate_if_not_too_many', 'neighbor_based_generation'] }),
+ [Switch]: [{ push: 'type' }],
+ [Case]: {
+ generate_if_not_too_many: {
+ radiusX: NumberNode({ integer: true }),
+ radiusY: NumberNode({ integer: true }),
+ radiusZ: NumberNode({ integer: true }),
+ requiredAmount: NumberNode({ integer: true }),
+ yOffset: Opt(NumberNode({ integer: true })),
+ must_have: Opt(Reference(`${ID}:rule_test`)),
+ must_not_have: Opt(Reference(`${ID}:rule_test`)),
+ includes: Opt(Tag('block')),
+ },
+ neighbor_based_generation: {
+ must_have: Reference(`${ID}:rule_test`),
+ must_not_have: Opt(Reference(`${ID}:rule_test`)),
+ required_amount: Opt(NumberNode({ integer: true })),
+ directions: ListNode(
+ StringNode({ enum: 'direction' })
+ ),
+ },
+ },
+ }, { context: `${ID}.area_condition` }))
+
+ schemas.register(`${ID}:block_pair`, ObjectNode({
+ block: Reference(`${ID}:block_state`),
+ above_block: Opt(Reference(`${ID}:block_state`)),
+ }, { context: `${ID}.block_pair` }))
+
+ schemas.register(`${ID}:block_state`, ObjectNode({
+ Name: StringNode({ validator: 'resource', params: { pool: 'block' } }),
+ Properties: Opt(MapNode(
+ StringNode(),
+ StringNode(),
+ )),
+ }, { context: 'block_state' }))
+
+ schemas.register(`${ID}:position_test`, ObjectNode({
+ predicate_type: StringNode({ enum: ['biome_match', 'day_test', 'nand', 'precipitation_test', 'temperature_range'] }),
+ [Switch]: [{ push: 'predicate_type' }],
+ [Case]: {
+ biome_match: {
+ biomes: Tag('$worldgen/biome'),
+ },
+ day_test: {
+ day: BooleanNode(),
+ },
+ nand: {
+ predicates: ListNode(
+ Reference(`${ID}:position_test`)
+ ),
+ },
+ precipitation_test: {
+ precipitation: StringNode({ enum: ['none', 'rain', 'snow']}),
+ },
+ temperature_range: {
+ min: NumberNode(),
+ max: NumberNode(),
+ use_local_pos: Opt(BooleanNode()),
+ },
+ },
+ }, { context: `${ID}.position_test`, category: 'predicate' }))
+
+ collections.register(`${ID}:rule_test`, [
+ ...collections.get('rule_test'),
+ 'immersive_weathering:block_set_match',
+ 'immersive_weathering:fluid_match',
+ 'immersive_weathering:tree_log',
+ ])
+
+ schemas.register(`${ID}:rule_test`, ObjectNode({
+ predicate_type: StringNode({ validator: 'resource', params: { pool: `${ID}:rule_test` as any } }),
+ [Switch]: [{ push: 'predicate_type' }],
+ [Case]: {
+ 'minecraft:block_match': {
+ block: StringNode({ validator: 'resource', params: { pool: 'block' } }),
+ },
+ 'minecraft:blockstate_match': {
+ block_state: Reference('block_state'),
+ },
+ 'minecraft:random_block_match': {
+ block: StringNode({ validator: 'resource', params: { pool: 'block' } }),
+ probability: NumberNode({ min: 0, max: 1 }),
+ },
+ 'minecraft:random_blockstate_match': {
+ block_state: Reference('block_state'),
+ probability: NumberNode({ min: 0, max: 1 }),
+ },
+ 'minecraft:tag_match': {
+ tag: StringNode({ validator: 'resource', params: { pool: '$tag/block' }}),
+ },
+ 'immersive_weathering:block_set_match': {
+ blocks: Tag('block'),
+ probability: Opt(NumberNode({ min: 0, max: 1 })),
+ },
+ 'immersive_weathering:fluid_match': {
+ fluid: StringNode({ validator: 'resource', params: { pool: 'fluid' } }),
+ },
+ },
+ }, { context: 'rule_test', disableSwitchContext: true }))
+
+ collections.register('block_growth', [
+ 'immersive_weathering:brain_coral',
+ 'immersive_weathering:bubble_coral',
+ 'immersive_weathering:cracked_mud_rivers',
+ 'immersive_weathering:crimson_nylium',
+ 'immersive_weathering:cryosol',
+ 'immersive_weathering:farmland_rare_weeds',
+ 'immersive_weathering:farmland_weeds',
+ 'immersive_weathering:fire_coral',
+ 'immersive_weathering:fire_soot',
+ 'immersive_weathering:fluvisol',
+ 'immersive_weathering:grass_base',
+ 'immersive_weathering:grass_block_badlands',
+ 'immersive_weathering:grass_block_bamboo_jungle',
+ 'immersive_weathering:grass_block_birch_forest',
+ 'immersive_weathering:grass_block_dark_forest',
+ 'immersive_weathering:grass_block_flower_forest',
+ 'immersive_weathering:grass_block_forest',
+ 'immersive_weathering:grass_block_jungle',
+ 'immersive_weathering:grass_block_lush_caves',
+ 'immersive_weathering:grass_block_old_growth_spruce',
+ 'immersive_weathering:grass_block_plains',
+ 'immersive_weathering:grass_block_sunflower_plains',
+ 'immersive_weathering:grass_block_swamp',
+ 'immersive_weathering:grass_block_taiga',
+ 'immersive_weathering:grass_block_wooded_badlands',
+ 'immersive_weathering:hanging_roots',
+ 'immersive_weathering:horn_coral',
+ 'immersive_weathering:humus',
+ 'immersive_weathering:icicle_growth',
+ 'immersive_weathering:large_fern',
+ 'immersive_weathering:magma',
+ 'immersive_weathering:mycelium',
+ 'immersive_weathering:podzol',
+ 'immersive_weathering:red_sand_weathering',
+ 'immersive_weathering:rooted_dirt',
+ 'immersive_weathering:rooted_grass',
+ 'immersive_weathering:sand_weathering',
+ 'immersive_weathering:sapling',
+ 'immersive_weathering:sapling_nether',
+ 'immersive_weathering:silt',
+ 'immersive_weathering:tall_grass',
+ 'immersive_weathering:tall_seagrass',
+ 'immersive_weathering:tube_coral',
+ 'immersive_weathering:vertisol',
+ 'immersive_weathering:warped_nylium',
+ ])
+}
diff --git a/src/app/partners/index.ts b/src/app/partners/index.ts
new file mode 100644
index 00000000..9db8bad4
--- /dev/null
+++ b/src/app/partners/index.ts
@@ -0,0 +1,8 @@
+import type { CollectionRegistry, SchemaRegistry } from '@mcschema/core'
+import { initImmersiveWeathering } from './ImmersiveWeathering'
+
+export * from './ImmersiveWeathering'
+
+export function initPartners(schemas: SchemaRegistry, collections: CollectionRegistry) {
+ initImmersiveWeathering(schemas, collections)
+}
diff --git a/src/app/partners/locales/en.json b/src/app/partners/locales/en.json
new file mode 100644
index 00000000..26f8dacb
--- /dev/null
+++ b/src/app/partners/locales/en.json
@@ -0,0 +1,60 @@
+{
+ "immersive_weathering.area_condition.type": "Type",
+ "immersive_weathering.area_condition.type.generate_if_not_too_many": "Generate if not too many",
+ "immersive_weathering.area_condition.type.neighbor_based_generation": "Neighbor based generation",
+ "immersive_weathering.area_condition.generate_if_not_too_many.radiusX": "Radius X",
+ "immersive_weathering.area_condition.generate_if_not_too_many.radiusY": "Radius Y",
+ "immersive_weathering.area_condition.generate_if_not_too_many.radiusZ": "Radius Z",
+ "immersive_weathering.area_condition.generate_if_not_too_many.requiredAmount": "Required amount",
+ "immersive_weathering.area_condition.generate_if_not_too_many.yOffset": "Y offset",
+ "immersive_weathering.area_condition.generate_if_not_too_many.must_have": "Must have",
+ "immersive_weathering.area_condition.generate_if_not_too_many.must_not_have": "Must not have",
+ "immersive_weathering.area_condition.generate_if_not_too_many.includes": "Includes",
+ "immersive_weathering.area_condition.neighbor_based_generation.must_have": "Must have",
+ "immersive_weathering.area_condition.neighbor_based_generation.must_not_have": "Must not have",
+ "immersive_weathering.area_condition.neighbor_based_generation.required_amount": "Required amount",
+ "immersive_weathering.area_condition.neighbor_based_generation.directions": "Directions",
+ "immersive_weathering.area_condition.neighbor_based_generation.directions.entry": "Direction",
+ "immersive_weathering.block_growth.area_condition": "Area conditions",
+ "immersive_weathering.block_growth.position_predicates": "Position predicates",
+ "immersive_weathering.block_growth.position_predicates.entry": "Position test",
+ "immersive_weathering.block_growth.growth_chance": "Growth chance",
+ "immersive_weathering.block_growth.growth_for_face": "Growth for face",
+ "immersive_weathering.block_growth.growth_for_face.entry": "Face",
+ "immersive_weathering.block_growth.growth_for_face.entry.direction": "Direction",
+ "immersive_weathering.block_growth.growth_for_face.entry.weight": "Weight",
+ "immersive_weathering.block_growth.growth_for_face.entry.growth": "Growth",
+ "immersive_weathering.block_growth.growth_for_face.entry.growth.entry.data": "Block pair",
+ "immersive_weathering.block_growth.growth_for_face.entry.growth.entry.weight": "Weight",
+ "immersive_weathering.block_growth.owners": "Owners",
+ "immersive_weathering.block_growth.owners.entry": "Block",
+ "immersive_weathering.block_growth.replacing_target": "Replacing target",
+ "immersive_weathering.block_growth.target_self": "Target self",
+ "immersive_weathering.block_growth.destroy_target": "Destroy target",
+ "immersive_weathering.block_pair.block": "Block",
+ "immersive_weathering.block_pair.above_block": "Above block",
+ "immersive_weathering.position_test.predicate_type": "Predicate type",
+ "immersive_weathering.position_test.predicate_type.biome_match": "Biome match",
+ "immersive_weathering.position_test.predicate_type.day_test": "Day test",
+ "immersive_weathering.position_test.predicate_type.nand": "NAND",
+ "immersive_weathering.position_test.predicate_type.precipitation_test": "Precipitation test",
+ "immersive_weathering.position_test.predicate_type.temperature_range": "Temperature range",
+ "immersive_weathering.position_test.biome_match.biomes": "Biomes",
+ "immersive_weathering.position_test.day_test.day": "Day",
+ "immersive_weathering.position_test.nand.predicates": "Predicates",
+ "immersive_weathering.position_test.precipitation_test.precipitation": "Precipitation",
+ "immersive_weathering.position_test.temperature_range.min": "Min",
+ "immersive_weathering.position_test.temperature_range.max": "Max",
+ "immersive_weathering.position_test.temperature_range.use_local_pos": "Use local pos",
+ "immersive_weathering:rule_test.always_true": "Always true",
+ "immersive_weathering:rule_test.block_match": "Block match",
+ "immersive_weathering:rule_test.blockstate_match": "Block state match",
+ "immersive_weathering:rule_test.random_block_match": "Random block match",
+ "immersive_weathering:rule_test.random_blockstate_match": "Random block state match",
+ "immersive_weathering:rule_test.tag_match": "Tag match",
+ "immersive_weathering:rule_test.immersive_weathering:block_set_match": "Block set match",
+ "immersive_weathering:rule_test.immersive_weathering:fluid_match": "Fluid match",
+ "immersive_weathering:rule_test.immersive_weathering:tree_log": "Tree log",
+ "rule_test.blocks": "Blocks",
+ "rule_test.fluid": "Fluid"
+}
diff --git a/src/app/schema/renderHtml.tsx b/src/app/schema/renderHtml.tsx
index ba474c32..24e498dd 100644
--- a/src/app/schema/renderHtml.tsx
+++ b/src/app/schema/renderHtml.tsx
@@ -15,7 +15,7 @@ import { ModelWrapper } from './ModelWrapper'
const selectRegistries = ['loot_table.type', 'loot_entry.type', 'function.function', 'condition.condition', 'criterion.trigger', 'recipe.type', 'dimension.generator.type', 'dimension.generator.biome_source.type', 'dimension.generator.biome_source.preset', 'carver.type', 'feature.type', 'decorator.type', 'feature.tree.minimum_size.type', 'block_state_provider.type', 'trunk_placer.type', 'foliage_placer.type', 'tree_decorator.type', 'int_provider.type', 'float_provider.type', 'height_provider.type', 'structure_feature.type', 'surface_builder.type', 'processor.processor_type', 'rule_test.predicate_type', 'pos_rule_test.predicate_type', 'template_element.element_type', 'block_placer.type', 'block_predicate.type', 'material_rule.type', 'material_condition.type', 'structure_placement.type', 'density_function.type', 'root_placer.type', 'entity.type_specific.cat.variant', 'entity.type_specific.frog.variant']
const hiddenFields = ['number_provider.type', 'score_provider.type', 'nbt_provider.type', 'int_provider.type', 'float_provider.type', 'height_provider.type']
const flattenedFields = ['feature.config', 'decorator.config', 'int_provider.value', 'float_provider.value', 'block_state_provider.simple_state_provider.state', 'block_state_provider.rotated_block_provider.state', 'block_state_provider.weighted_state_provider.entries.entry.data', 'rule_test.block_state', 'structure_feature.config', 'surface_builder.config', 'template_pool.elements.entry.element', 'decorator.block_survives_filter.state', 'material_rule.block.result_state']
-const inlineFields = ['loot_entry.type', 'function.function', 'condition.condition', 'criterion.trigger', 'dimension.generator.type', 'dimension.generator.biome_source.type', 'feature.type', 'decorator.type', 'block_state_provider.type', 'feature.tree.minimum_size.type', 'trunk_placer.type', 'foliage_placer.type', 'tree_decorator.type', 'block_placer.type', 'rule_test.predicate_type', 'processor.processor_type', 'template_element.element_type', 'nbt_operation.op', 'number_provider.value', 'score_provider.name', 'score_provider.target', 'nbt_provider.source', 'nbt_provider.target', 'generator_biome.biome', 'block_predicate.type', 'material_rule.type', 'material_condition.type', 'density_function.type', 'root_placer.type', 'entity.type_specific.type']
+const inlineFields = ['loot_entry.type', 'function.function', 'condition.condition', 'criterion.trigger', 'dimension.generator.type', 'dimension.generator.biome_source.type', 'feature.type', 'decorator.type', 'block_state_provider.type', 'feature.tree.minimum_size.type', 'trunk_placer.type', 'foliage_placer.type', 'tree_decorator.type', 'block_placer.type', 'rule_test.predicate_type', 'processor.processor_type', 'template_element.element_type', 'nbt_operation.op', 'number_provider.value', 'score_provider.name', 'score_provider.target', 'nbt_provider.source', 'nbt_provider.target', 'generator_biome.biome', 'block_predicate.type', 'material_rule.type', 'material_condition.type', 'density_function.type', 'root_placer.type', 'entity.type_specific.type', 'immersive_weathering.area_condition.type', 'immersive_weathering.block_growth.growth_for_face.entry.direction', 'immersive_weathering.position_test.predicate_type']
const nbtFields = ['function.set_nbt.tag', 'advancement.display.icon.nbt', 'text_component_object.nbt', 'entity.nbt', 'block.nbt', 'item.nbt']
const fixedLists = ['generator_biome.parameters.temperature', 'generator_biome.parameters.humidity', 'generator_biome.parameters.continentalness', 'generator_biome.parameters.erosion', 'generator_biome.parameters.depth', 'generator_biome.parameters.weirdness', 'feature.end_spike.crystal_beam_target', 'feature.end_gateway.exit', 'decorator.block_filter.offset', 'block_predicate.matching_blocks.offset', 'block_predicate.matching_fluids.offset', 'model_element.from', 'model_element.to', 'model_element.rotation.origin', 'model_element.faces.uv', 'item_transform.rotation', 'item_transform.translation', 'item_transform.scale', 'generator_structure.random_spread.locate_offset']
const collapsedFields = ['noise_settings.surface_rule', 'noise_settings.noise.terrain_shaper']
diff --git a/src/app/services/DataFetcher.ts b/src/app/services/DataFetcher.ts
index 4ba063e1..4b3f36c4 100644
--- a/src/app/services/DataFetcher.ts
+++ b/src/app/services/DataFetcher.ts
@@ -83,8 +83,13 @@ export async function fetchPreset(versionId: VersionId, registry: string, id: st
console.debug(`[fetchPreset] ${versionId} ${registry} ${id}`)
const version = config.versions.find(v => v.id === versionId)!
try {
- const type = ['blockstates', 'models'].includes(registry) ? 'assets' : 'data'
- const url = `${mcmeta(version, type)}/${type}/minecraft/${registry}/${id}.json`
+ let url
+ if (id.startsWith('immersive_weathering:')) {
+ url = `https://raw.githubusercontent.com/AstralOrdana/Immersive-Weathering/main/src/main/resources/data/immersive_weathering/block_growths/${id.slice(21)}.json`
+ } else {
+ const type = ['blockstates', 'models'].includes(registry) ? 'assets' : 'data'
+ url = `${mcmeta(version, type)}/${type}/minecraft/${registry}/${id}.json`
+ }
const res = await fetch(url)
return await res.json()
} catch (e) {
diff --git a/src/app/services/Schemas.ts b/src/app/services/Schemas.ts
index a4de0f92..aad2ddcb 100644
--- a/src/app/services/Schemas.ts
+++ b/src/app/services/Schemas.ts
@@ -1,6 +1,7 @@
import type { CollectionRegistry, INode, SchemaRegistry } from '@mcschema/core'
import { ChoiceNode, DataModel, Reference, StringNode } from '@mcschema/core'
import config from '../../config.json'
+import { initPartners } from '../partners'
import { message } from '../Utils'
import { fetchData } from './DataFetcher'
@@ -58,6 +59,7 @@ async function getVersion(id: VersionId): Promise {
const blockStates: BlockStateRegistry = {}
await fetchData(id, collections, blockStates)
const schemas = mcschema.getSchemas(collections)
+ initPartners(schemas, collections)
Versions[id] = { collections, schemas, blockStates }
return Versions[id]
} catch (e) {
diff --git a/src/config.json b/src/config.json
index ad58ff8d..6756e481 100644
--- a/src/config.json
+++ b/src/config.json
@@ -266,6 +266,15 @@
"path": "models",
"category": "assets",
"schema": "model"
+ },
+ {
+ "id": "block_growth",
+ "url": "partners/immersive-weathering/block-growth",
+ "path": "block_growths",
+ "category": "partners",
+ "partner": "immersive_weathering",
+ "schema": "immersive_weathering:block_growth",
+ "minVersion": "1.18.2"
}
]
}
diff --git a/src/locales/en.json b/src/locales/en.json
index 38a372a5..895f898b 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -88,9 +88,12 @@
"title.generator_category": "%0% Generators",
"title.guides": "Data Pack Guides",
"title.home": "Data Pack Generators",
+ "title.partners": "Partners",
"title.project": "%0% Project",
"title.sounds": "Sound Explorer",
"title.versions": "Versions Explorer",
+ "partner.immersive_weathering": "Immersive Weathering",
+ "partner.immersive_weathering.block_growth": "Block Growth",
"presets": "Presets",
"preview": "Visualize",
"preview.auto_scroll": "Auto scroll",
diff --git a/src/styles/global.css b/src/styles/global.css
index d3d05d9e..c22579f7 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -819,9 +819,14 @@ main.has-preview {
grid-template-columns: 1fr 1fr;
gap: 0 8px;
align-items: flex-start;
+ grid-template-rows: min-content 1fr repeat(6, auto);
}
-.home > *:nth-child(n+3) {
+.home > *:first-child {
+ grid-row: 1 / 3;
+}
+
+.home > *:nth-child(n+4) {
grid-column: 1 / 3;
}