diff --git a/src/app/partners/NeoForge.ts b/src/app/partners/NeoForge.ts new file mode 100644 index 00000000..5048df7c --- /dev/null +++ b/src/app/partners/NeoForge.ts @@ -0,0 +1,492 @@ +import type { CollectionRegistry, INode, ResourceType, SchemaRegistry } from '@mcschema/core' +import { BooleanNode, Case, ChoiceNode, ListNode, MapNode, Mod, NumberNode, ObjectNode, Opt, StringNode as RawStringNode, Switch } from '@mcschema/core' +import type { VersionId } from '../services/Schemas.js' + +const ID = 'neoforge' + +export function initNeoForge(schemas: SchemaRegistry, collections: CollectionRegistry, _version: VersionId) { + const StringNode = RawStringNode.bind(undefined, collections) + + // Homogenous list (ref, list of refs, tag) + 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' } + ) + + // Spawner data + const MobCategorySpawnSettings = Mod( + ObjectNode({ + type: StringNode({ validator: 'resource', params: { pool: 'entity_type' }}), + weight: NumberNode({ integer: true, min: 0 }), + minCount: NumberNode({ integer: true, min: 1 }), + maxCount: NumberNode({ integer: true, min: 1 }), + }), + { + category: () => 'pool', + default: () => [ + { + type: 'minecraft:bat', + weight: 1, + }, + ], + } + ) + + // Generation step carving + const CarvingStep = StringNode({ enum: [ 'air', 'liquid' ] }) + + // Mob category + const MobCategory = StringNode({ enum: [ 'monster', 'creature', 'ambient', 'axolotls', 'underground_water_creature', 'water_creature', 'water_ambient', 'misc' ], additional: true }) + + // Biome modifier types + collections.register(`${ID}:biome_modifier_type`, [ + 'neoforge:none', + 'neoforge:add_features', + 'neoforge:remove_features', + 'neoforge:add_spawns', + 'neoforge:remove_spawns', + 'neoforge:add_carvers', + 'neoforge:remove_carvers', + 'neoforge:add_spawn_costs', + 'neoforge:remove_spawn_costs', + ]) + + // Biome modifiers + schemas.register(`${ID}:biome_modifier`, Mod( + ObjectNode({ + type: StringNode({ validator: 'resource', params: { pool: `${ID}:biome_modifier_type` as any }}), + [Switch]: [{ push: 'type' }], + [Case]: { + 'neoforge:none': {}, + 'neoforge:add_features': { + biomes: Tag('$worldgen/biome'), + features: Tag('$worldgen/placed_feature'), + step: StringNode({ enum: 'decoration_step' }), + }, + 'neoforge:remove_features': { + biomes: Tag('$worldgen/biome'), + features: Tag('$worldgen/placed_feature'), + steps: Opt(ChoiceNode([ + { + type: 'string', + node: StringNode({ enum: 'decoration_step' }), + change: (v: any) => v[0], + }, + { + type: 'list', + node: ListNode(StringNode({ enum: 'decoration_step' })), + change: (v: any) => Array(v), + }, + ])), + }, + 'neoforge:add_spawns': { + biomes: Tag('$worldgen/biome'), + spawners: ChoiceNode([ + { + type: 'object', + node: MobCategorySpawnSettings, + change: (v: any) => v[0], + }, + { + type: 'list', + node: MobCategorySpawnSettings, + change: (v: any) => Array(v), + }, + ]), + }, + 'neoforge:remove_spawns': { + biomes: Tag('$worldgen/biome'), + entity_types: Tag('entity_type'), + }, + 'neoforge:add_carvers': { + biomes: Tag('$worldgen/biome'), + carvers: Tag('$worldgen/configured_carver'), + step: CarvingStep, + }, + 'neoforge:remove_carvers': { + biomes: Tag('$worldgen/biome'), + carvers: Tag('$worldgen/configured_carver'), + steps: Opt(ChoiceNode([ + { + type: 'string', + node: CarvingStep, + change: (v: any) => v[0], + }, + { + type: 'list', + node: ListNode(CarvingStep), + change: (v: any) => Array(v), + }, + ])), + }, + 'neoforge:add_spawn_costs': { + biomes: Tag('$worldgen/biome'), + entity_types: Tag('entity_type'), + spawn_cost: ObjectNode({ + energy_budget: NumberNode(), + charge: NumberNode(), + }), + }, + 'neoforge:remove_spawn_costs': { + biomes: Tag('$worldgen/biome'), + entity_types: Tag('entity_type'), + }, + }, + }, { context: `${ID}.biome_modifier`, disableSwitchContext: true }), + { + default: () => ({ + type: `${ID}:add_features`, + biomes: '#minecraft:is_overworld', + features: 'minecraft:ore_iron_small', + step: 'underground_ores', + }), + } + )) + + // Structure modifier types + collections.register(`${ID}:structure_modifier_type`, [ + 'neoforge:none', + 'neoforge:add_spawns', + 'neoforge:remove_spawns', + 'neoforge:clear_spawns', + ]) + + // Structure modifiers + schemas.register(`${ID}:structure_modifier`, Mod( + ObjectNode({ + type: StringNode({ validator: 'resource', params: { pool: `${ID}:structure_modifier_type` as any }}), + [Switch]: [{ push: 'type' }], + [Case]: { + 'neoforge:none': {}, + 'neoforge:add_spawns': { + structures: Tag('$worldgen/structure'), + spawners: ChoiceNode([ + { + type: 'object', + node: MobCategorySpawnSettings, + change: (v: any) => v[0], + }, + { + type: 'list', + node: MobCategorySpawnSettings, + change: (v: any) => Array(v), + }, + ]), + }, + 'neoforge:remove_spawns': { + structures: Tag('$worldgen/structure'), + entity_types: Tag('entity_type'), + }, + 'neoforge:clear_spawns': { + structures: Tag('$worldgen/structure'), + categories: ChoiceNode([ + { + type: 'string', + node: MobCategory, + change: (v: any) => v[0], + }, + { + type: 'list', + node: MobCategory, + change: (v: any) => Array(v), + }, + ]), + }, + }, + }, { context: `${ID}.structure_modifier`, disableSwitchContext: true }), + { + default: () => ({ + type: `${ID}:add_spawns`, + structures: '#minecraft:village', + spawners: { + type: 'minecraft:bat', + weight: 1, + }, + }), + } + )) + + // Data maps + createDataMap(schemas, collections, 'compostables', 'item', ChoiceNode([ + { + type: 'number', + node: NumberNode({ + min: 0, + max: 1, + }), + change: (v: any) => v?.chance, + }, + { + type: 'object', + node: ObjectNode({ + chance: NumberNode({ + min: 0, + max: 1, + }), + can_villager_compost: Opt(BooleanNode()), + }), + change: (v: any) => ({ + chance: v, + can_villager_compost: false, + }), + }, + ]), (values) => values['minecraft:apple'] = { + chance: 1, + can_villager_compost: true, + } + ) + createDataMap(schemas, collections, 'furnace_fuels', 'item', ChoiceNode([ + { + type: 'number', + node: NumberNode({ + min: 1, + integer: true, + }), + change: (v: any) => v?.burn_time, + }, + { + type: 'object', + node: ObjectNode({ + burn_time: NumberNode({ + min: 1, + integer: true, + }), + }), + change: (v: any) => ({ + burn_time: v, + }), + }, + ]), (values) => values['minecraft:chest'] = { + burn_time: 300, + } + ) + createDataMap(schemas, collections, 'monster_room_mobs', 'entity_type', ChoiceNode([ + { + type: 'number', + node: NumberNode({ + min: 0, + integer: true, + }), + change: (v: any) => v?.weight, + }, + { + type: 'object', + node: ObjectNode({ + weight: NumberNode({ + min: 0, + integer: true, + }), + }), + change: (v: any) => ({ + weight: v, + }), + }, + ]), (values) => values['minecraft:bat'] = { + weight: 5, + }) + createDataMap(schemas, collections, 'oxidizables', 'block', ChoiceNode([ + { + type: 'string', + node: StringNode({ + validator: 'resource', + params: { pool: 'block' }, + }), + change: (v: any) => v?.next_oxidation_stage, + }, + { + type: 'object', + node: ObjectNode({ + next_oxidation_stage: StringNode({ + validator: 'resource', + params: { pool: 'block' }, + }), + }), + change: (v: any) => ({ + next_oxidation_stage: v, + }), + }, + ]), (values) => values['minecraft:grass_block'] = { + next_oxidation_stage: 'minecraft:dirt', + }) + createDataMap(schemas, collections, 'parrot_imitations', 'entity_type', ChoiceNode([ + { + type: 'string', + node: StringNode({ + validator: 'resource', + params: { pool: 'sound_event' as any }, + }), + change: (v: any) => v?.sound, + }, + { + type: 'object', + node: ObjectNode({ + sound: StringNode({ + validator: 'resource', + params: { pool: 'sound_event' as any }, + }), + }), + change: (v: any) => ({ + sound: v, + }), + }, + ]), (values) => values['minecraft:allay'] = { + sound: 'minecraft:entity.allay.ambient_without_item', + }) + createDataMap(schemas, collections, 'raid_hero_gifts', 'villager_profession', ChoiceNode([ + { + type: 'string', + node: StringNode({ + validator: 'resource', + params: { pool: '$loot_table' }, + }), + change: (v: any) => v?.loot_table, + }, + { + type: 'object', + node: ObjectNode({ + loot_table: StringNode({ + validator: 'resource', + params: { pool: '$loot_table' }, + }), + }), + change: (v: any) => ({ + loot_table: v, + }), + }, + ]), (values) => values['minecraft:cleric'] = { + loot_table: 'minecraft:empty', + }) + createDataMap(schemas, collections, 'vibration_frequencies', 'game_event', ChoiceNode([ + { + type: 'number', + node: NumberNode({ + min: 1, + max: 15, + integer: true, + }), + change: (v: any) => v?.frequency, + }, + { + type: 'object', + node: ObjectNode({ + frequency: NumberNode({ + min: 1, + max: 15, + integer: true, + }), + }), + change: (v: any) => ({ + frequency: v, + }), + }, + ]), (values) => values['minecraft:block_change'] = { + frequency: 5, + }) + createDataMap(schemas, collections, 'waxables', 'block', ChoiceNode([ + { + type: 'string', + node: StringNode({ + validator: 'resource', + params: { pool: 'block' }, + }), + change: (v: any) => v?.waxed, + }, + { + type: 'object', + node: ObjectNode({ + waxed: StringNode({ + validator: 'resource', + params: { pool: 'block' }, + }), + }), + change: (v: any) => ({ + waxed: v, + }), + }, + ]), (values) => values['minecraft:dirt'] = { + waxed: 'minecraft:coarse_dirt', + }) +} + +function createDataMap(schemas: SchemaRegistry, collections: CollectionRegistry, dataMap: string, registry: ResourceType, valueNode: INode, def: (values: any) => void) { + const StringNode = RawStringNode.bind(undefined, collections) + + // Ref or tag + const Tag = StringNode({ + validator: 'resource', + params: { pool: registry, allowTag: true }, + }) + + // Create data map + schemas.register(`${ID}:data_map_${dataMap}`, Mod( + ObjectNode({ + replace: Opt(BooleanNode()), + values: MapNode( + Tag, + ChoiceNode([ + { + type: 'direct', + match: () => true, + node: valueNode, + change: (v: any) => v?.value, + }, + { + type: 'replaceable', + match: (v: any) => typeof v === 'object' && v?.value !== undefined, + priority: 1, + node: ObjectNode({ + replace: Opt(BooleanNode()), + value: valueNode, + }), + change: (v: any) => ({ + replace: true, + value: v, + }), + }, + ]), + ), + remove: Opt(ListNode(Tag)), + }, {context: `${ID}.data_map_${dataMap}`, disableSwitchContext: true}), + { + default: () => { + const result = { + values: {}, + } + def(result.values) + + return result + }, + } + )) +} diff --git a/src/app/partners/index.ts b/src/app/partners/index.ts index f4dee4d7..40686fa9 100644 --- a/src/app/partners/index.ts +++ b/src/app/partners/index.ts @@ -2,6 +2,7 @@ import type { CollectionRegistry, SchemaRegistry } from '@mcschema/core' import type { VersionId } from '../services/Schemas.js' import { initImmersiveWeathering } from './ImmersiveWeathering.js' import { initLithostitched } from './Lithostitched.js' +import { initNeoForge } from './NeoForge.js' import { initObsidian } from './Obsidian.js' import { initOhTheTreesYoullGrow } from './OhTheTreesYoullGrow.js' @@ -11,6 +12,7 @@ export * from './Lithostitched.js' export function initPartners(schemas: SchemaRegistry, collections: CollectionRegistry, version: VersionId) { initImmersiveWeathering(schemas, collections) initLithostitched(schemas, collections, version) + initNeoForge(schemas, collections, version) initObsidian(schemas, collections) initOhTheTreesYoullGrow(schemas, collections) } diff --git a/src/app/partners/locales/en.json b/src/app/partners/locales/en.json index 2c25112a..c5cb767e 100644 --- a/src/app/partners/locales/en.json +++ b/src/app/partners/locales/en.json @@ -75,6 +75,20 @@ "lithostitched:modifier_predicate_type.lithostitched:not": "Not", "lithostitched:modifier_predicate_type.lithostitched:true": "True", + "neoforge:biome_modifier_type.neoforge:none": "Disable Biome Modifier", + "neoforge:biome_modifier_type.neoforge:add_features": "Add Features", + "neoforge:biome_modifier_type.neoforge:remove_features": "Remove Features", + "neoforge:biome_modifier_type.neoforge:add_spawns": "Add Mob Spawns", + "neoforge:biome_modifier_type.neoforge:remove_spawns": "Remove Mob Spawns", + "neoforge:biome_modifier_type.neoforge:add_carvers": "Add World Carvers", + "neoforge:biome_modifier_type.neoforge:remove_carvers": "Remove World Carvers", + "neoforge:biome_modifier_type.neoforge:add_spawn_costs": "Add Mob Spawn Costs", + "neoforge:biome_modifier_type.neoforge:remove_spawn_costs": "Remove Mob Spawn Costs", + "neoforge:structure_modifier_type.neoforge:none": "Disable Structure Modifier", + "neoforge:structure_modifier_type.neoforge:add_spawns": "Add Mob Spawns", + "neoforge:structure_modifier_type.neoforge:remove_spawns": "Remove Mob Spawns", + "neoforge:structure_modifier_type.neoforge:clear_spawns": "Clear Mob Spawns", + "obsidian:item.information": "Item Information", "obsidian:item_information.rarity": "Rarity", "obsidian:item_information.creative_tab": "Creative Tab", diff --git a/src/app/schema/renderHtml.tsx b/src/app/schema/renderHtml.tsx index ac4e0acb..11a67a53 100644 --- a/src/app/schema/renderHtml.tsx +++ b/src/app/schema/renderHtml.tsx @@ -15,7 +15,7 @@ import { CachedDecorator, CachedFeature, checkVersion } from '../services/index. import { deepClone, deepEqual, generateColor, generateUUID, hexId, hexToRgb, isObject, newSeed, rgbToHex, stringToColor } from '../Utils.js' import { ModelWrapper } from './ModelWrapper.js' -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', 'rule_block_entity_modifier.type', 'pool_alias_binding.type', 'lithostitched.worldgen_modifier.type', 'lithostitched.modifier_predicate.type', 'ohthetreesyoullgrow.configured_feature.type', 'enchantment_provider.type', 'enchantment_value_effect.type', 'level_based_value.type'] +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', 'rule_block_entity_modifier.type', 'pool_alias_binding.type', 'lithostitched.worldgen_modifier.type', 'lithostitched.modifier_predicate.type', 'ohthetreesyoullgrow.configured_feature.type', 'enchantment_provider.type', 'enchantment_value_effect.type', 'level_based_value.type', 'neoforge.biome_modifier.type', 'neoforge.structure_modifier.type'] const datalistEnums = ['item_stack.components', 'function.set_components.components'] const hiddenFields = ['number_provider.type', 'score_provider.type', 'nbt_provider.type', 'int_provider.type', 'float_provider.type', 'height_provider.type', 'level_based_value.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', 'enchantment.effects.entry.effect'] diff --git a/src/config.json b/src/config.json index 195a8258..a1b89c0c 100644 --- a/src/config.json +++ b/src/config.json @@ -646,6 +646,96 @@ "minVersion": "1.20.2", "wiki": "https://github.com/Apollounknowndev/lithostitched/wiki/Worldgen-Modifiers" }, + { + "id": "neoforge.biome_modifier", + "url": "neoforge/biome-modifier", + "path": "neoforge/biome_modifier", + "tags": ["partners"], + "schema": "neoforge:biome_modifier", + "minVersion": "1.20.2", + "wiki": "https://docs.neoforged.net/docs/worldgen/biomemodifier" + }, + { + "id": "neoforge.data_map_compostables", + "url": "neoforge/data-map-compostables", + "path": "neoforge/data_map_compostables", + "tags": ["partners"], + "schema": "neoforge:data_map_compostables", + "minVersion": "1.20.4", + "wiki": "https://docs.neoforged.net/docs/resources/server/datamaps/builtin#neoforgecompostables" + }, + { + "id": "neoforge.data_map_furnace_fuels", + "url": "neoforge/data-map-furnace-fuels", + "path": "neoforge/data_map_furnace_fuels", + "tags": ["partners"], + "schema": "neoforge:data_map_furnace_fuels", + "minVersion": "1.20.4", + "wiki": "https://docs.neoforged.net/docs/resources/server/datamaps/builtin#neoforgefurnace_fuels" + }, + { + "id": "neoforge.data_map_monster_room_mobs", + "url": "neoforge/data-map-monster-room-mobs", + "path": "neoforge/data_map_monster_room_mobs", + "tags": ["partners"], + "schema": "neoforge:data_map_monster_room_mobs", + "minVersion": "1.20.6", + "wiki": "https://docs.neoforged.net/docs/resources/server/datamaps/builtin#neoforgemonster_room_mobs" + }, + { + "id": "neoforge.data_map_oxidizables", + "url": "neoforge/data-map-oxidizables", + "path": "neoforge/data_map_oxidizables", + "tags": ["partners"], + "schema": "neoforge:data_map_oxidizables", + "minVersion": "1.21", + "wiki": "https://docs.neoforged.net/docs/resources/server/datamaps/builtin#neoforgeoxidizables" + }, + { + "id": "neoforge.data_map_parrot_imitations", + "url": "neoforge/data-map-parrot-imitations", + "path": "neoforge/data_map_parrot_imitations", + "tags": ["partners"], + "schema": "neoforge:data_map_parrot_imitations", + "minVersion": "1.20.4", + "wiki": "https://docs.neoforged.net/docs/resources/server/datamaps/builtin#neoforgeparrot_imitations" + }, + { + "id": "neoforge.data_map_raid_hero_gifts", + "url": "neoforge/data-map-raid-hero-gifts", + "path": "neoforge/data_map_raid_hero_gifts", + "tags": ["partners"], + "schema": "neoforge:data_map_raid_hero_gifts", + "minVersion": "1.20.4", + "wiki": "https://docs.neoforged.net/docs/resources/server/datamaps/builtin#neoforgeraid_hero_gifts" + }, + { + "id": "neoforge.data_map_vibration_frequencies", + "url": "neoforge/data-map-vibration-frequencies", + "path": "neoforge/data_map_vibration_frequencies", + "tags": ["partners"], + "schema": "neoforge:data_map_vibration_frequencies", + "minVersion": "1.20.4", + "wiki": "https://docs.neoforged.net/docs/resources/server/datamaps/builtin#neoforgevibration_frequencies" + }, + { + "id": "neoforge.data_map_waxables", + "url": "neoforge/data-map-waxables", + "path": "neoforge/data_map_waxables", + "tags": ["partners"], + "schema": "neoforge:data_map_waxables", + "minVersion": "1.21", + "wiki": "https://docs.neoforged.net/docs/resources/server/datamaps/builtin#neoforgewaxables" + }, + { + "id": "neoforge.structure_modifier", + "url": "neoforge/structure-modifier", + "path": "neoforge/structure_modifier", + "tags": ["partners"], + "schema": "neoforge:structure_modifier", + "minVersion": "1.20.2", + "wiki": "https://github.com/neoforged/NeoForge/blob/1.21.x/src/main/java/net/neoforged/neoforge/common/world/StructureModifiers.java" + }, { "id": "obsidian.item", "url": "obsidian/item", diff --git a/src/locales/en.json b/src/locales/en.json index 2f2d3312..d258a8f3 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -74,6 +74,16 @@ "generator.lithostitched.worldgen_modifier": "Worldgen Modifier", "generator.loot_table": "Loot Table", "generator.model": "Model", + "generator.neoforge.biome_modifier": "Biome Modifier", + "generator.neoforge.data_map_compostables": "Compostables Data Map", + "generator.neoforge.data_map_furnace_fuels": "Furnace Fuels Data Map", + "generator.neoforge.data_map_monster_room_mobs": "Monster Room Mobs Data Map", + "generator.neoforge.data_map_oxidizables": "Oxidizables Data Map", + "generator.neoforge.data_map_parrot_imitations": "Parrot Imitations Data Map", + "generator.neoforge.data_map_raid_hero_gifts": "Raid Hero Gifts Data Map", + "generator.neoforge.data_map_vibration_frequencies": "Vibration Frequencies Data Map", + "generator.neoforge.data_map_waxables": "Waxables Data Map", + "generator.neoforge.structure_modifier": "Structure Modifier", "generator.not_found": "Cannot find generator \"%0%\"", "generator.obsidian.block": "Obsidian Block", "generator.obsidian.item": "Obsidian Item", @@ -163,6 +173,7 @@ "output_settings": "Output settings", "partner.immersive_weathering": "Immersive Weathering", "partner.lithostitched": "Lithostitched", + "partner.neoforge": "NeoForge", "partner.obsidian": "Obsidian", "partner.ohthetreesyoullgrow": "Oh The Trees You'll Grow", "presets": "Presets",