diff --git a/src/app/app.ts b/src/app/app.ts
index 25763aeb..fd0c618c 100644
--- a/src/app/app.ts
+++ b/src/app/app.ts
@@ -2,22 +2,27 @@ import { DataModel } from '../model/DataModel'
import { TreeView } from '../view/TreeView'
import { SourceView } from '../view/SourceView'
import { ConditionSchema } from '../minecraft/schemas/Condition'
+import { LootTableSchema } from '../minecraft/schemas/LootTable'
import { SandboxSchema } from './Sandbox'
import { LOCALES } from '../Registries'
const predicateModel = new DataModel(ConditionSchema)
+const lootTableModel = new DataModel(LootTableSchema)
const sandboxModel = new DataModel(SandboxSchema)
-let model = predicateModel
+let model = lootTableModel
const modelSelector = document.createElement('select')
modelSelector.value = 'predicate'
modelSelector.innerHTML = `
+
`
modelSelector.addEventListener('change', evt => {
if (modelSelector.value === 'sandbox') {
model = sandboxModel
+ } else if (modelSelector.value === 'loot-table') {
+ model = lootTableModel
} else {
model = predicateModel
}
diff --git a/src/minecraft/schemas/Collections.ts b/src/minecraft/schemas/Collections.ts
index 8121c836..40534f75 100644
--- a/src/minecraft/schemas/Collections.ts
+++ b/src/minecraft/schemas/Collections.ts
@@ -20,6 +20,40 @@ COLLECTIONS.register('conditions', [
'survives_explosion'
])
+COLLECTIONS.register('loot-entries', [
+ 'empty',
+ 'item',
+ 'tag',
+ 'loot_table',
+ 'alternatives',
+ 'sequence',
+ 'group',
+ 'dynamic'
+])
+
+COLLECTIONS.register('loot-functions', [
+ 'set_count',
+ 'set_damage',
+ 'set_name',
+ 'set_lore',
+ 'set_nbt',
+ 'set_attributes',
+ 'set_contents',
+ 'enchant_randomly',
+ 'enchant_with_levels',
+ 'looting_enchant',
+ 'limit_count',
+ 'furnace_smelt',
+ 'explosion_decay',
+ 'fill_player_head',
+ 'copy_name',
+ 'copy_nbt',
+ 'copy_state',
+ 'apply_bonus',
+ 'exploration_map',
+ 'set_stew_effect'
+])
+
COLLECTIONS.register('enchantments', [
'aqua_affinity',
'bane_of_arthropods',
@@ -232,3 +266,56 @@ COLLECTIONS.register('entity-sources', [
'killer',
'killer_player'
])
+
+COLLECTIONS.register('copy-sources', [
+ 'block_entity',
+ 'this',
+ 'killer',
+ 'killer_player'
+])
+
+COLLECTIONS.register('attributes', [
+ 'generic.max_health',
+ 'generic.follow_range',
+ 'generic.knockback_resistance',
+ 'generic.movement_speed',
+ 'generic.attack_damage',
+ 'generic.armor',
+ 'generic.armor_toughness',
+ 'generic.attack_speed',
+ 'generic.luck',
+ 'horse.jump_strength',
+ 'generic.attack_knockback',
+ 'generic.flying_speed',
+ 'zombie.spawn_reinforcements'
+])
+
+COLLECTIONS.register('map-decorations', [
+ 'mansion',
+ 'monument',
+ 'player',
+ 'frame',
+ 'red_marker',
+ 'blue_marker',
+ 'target_x',
+ 'target_point',
+ 'player_off_map',
+ 'player_off_limits',
+ 'red_x',
+ 'banner_white',
+ 'banner_orange',
+ 'banner_magenta',
+ 'banner_light_blue',
+ 'banner_yellow',
+ 'banner_lime',
+ 'banner_pink',
+ 'banner_gray',
+ 'banner_light_gray',
+ 'banner_cyan',
+ 'banner_purple',
+ 'banner_blue',
+ 'banner_brown',
+ 'banner_green',
+ 'banner_red',
+ 'banner_black'
+])
diff --git a/src/minecraft/schemas/LootTable.ts b/src/minecraft/schemas/LootTable.ts
new file mode 100644
index 00000000..893c6a4f
--- /dev/null
+++ b/src/minecraft/schemas/LootTable.ts
@@ -0,0 +1,247 @@
+import { EnumNode } from '../../nodes/EnumNode';
+import { ResourceNode } from '../nodes/ResourceNode';
+import { NumberNode } from '../../nodes/NumberNode';
+import { BooleanNode } from '../../nodes/BooleanNode';
+import { ObjectNode, Switch, Case } from '../../nodes/ObjectNode';
+import { ListNode } from '../../nodes/ListNode';
+import { RangeNode } from '../nodes/RangeNode';
+import { MapNode } from '../../nodes/MapNode';
+import { StringNode } from '../../nodes/StringNode';
+import { ReferenceNode } from '../../nodes/ReferenceNode';
+import { SCHEMAS, COLLECTIONS } from '../../Registries';
+
+import './Predicates'
+
+const conditions = {
+ conditions: new ListNode(
+ new ReferenceNode('condition')
+ )
+}
+
+const functionsAndConditions = {
+ functions: new ListNode(
+ new ReferenceNode('loot-function')
+ ),
+ ...conditions
+}
+
+SCHEMAS.register('loot-table', new ObjectNode({
+ pools: new ListNode(
+ new ObjectNode({
+ rolls: new RangeNode(),
+ entries: new ListNode(
+ new ReferenceNode('loot-entry')
+ )
+ })
+ ),
+ ...functionsAndConditions
+}, {
+ default: () => ({
+ pools: [
+ {
+ rolls: 1,
+ entries: [
+ {
+ type: 'item',
+ name: 'minecraft:stone'
+ }
+ ]
+ }
+ ]
+ })
+}))
+
+SCHEMAS.register('loot-entry', new ObjectNode({
+ type: new EnumNode(COLLECTIONS.get('loot-entries'), {default: () => 'item'}),
+ weight: new NumberNode({
+ integer: true,
+ min: 1,
+ enable: path => path.pop().get()?.length > 1
+ && !['alternatives', 'group', 'sequence'].includes(path.push('type').get())
+ }),
+ [Switch]: 'type',
+ [Case]: {
+ 'alternatives': {
+ children: new ListNode(
+ new ReferenceNode('loot-entry')
+ ),
+ ...functionsAndConditions
+ },
+ 'dynamic': {
+ name: new StringNode(),
+ ...functionsAndConditions
+ },
+ 'group': {
+ children: new ListNode(
+ new ReferenceNode('loot-entry')
+ ),
+ ...functionsAndConditions
+ },
+ 'item': {
+ name: new StringNode(),
+ ...functionsAndConditions
+ },
+ 'loot_table': {
+ name: new StringNode(),
+ ...functionsAndConditions
+ },
+ 'sequence': {
+ children: new ListNode(
+ new ReferenceNode('loot-entry')
+ ),
+ ...functionsAndConditions
+ },
+ 'tag': {
+ name: new StringNode(),
+ expand: new BooleanNode(),
+ ...functionsAndConditions
+ }
+ }
+}))
+
+SCHEMAS.register('loot-function', new ObjectNode({
+ function: new EnumNode(COLLECTIONS.get('loot-functions'), {default: () => 'set_count'}),
+ [Switch]: 'function',
+ [Case]: {
+ 'apply_bonus': {
+ enchantment: new EnumNode(COLLECTIONS.get('enchantments')),
+ formula: new EnumNode([
+ 'uniform_bonus_count',
+ 'binomial_with_bonus_count',
+ 'ore_drops'
+ ]),
+ parameters: new ObjectNode({
+ bonusMultiplier: new NumberNode({
+ enable: path => path.pop().push('formula').get() === 'uniform_bonus_count'
+ }),
+ extra: new NumberNode({
+ enable: path => path.pop().push('formula').get() === 'binomial_with_bonus_count'
+ }),
+ probability: new NumberNode({
+ enable: path => path.pop().push('formula').get() === 'binomial_with_bonus_count'
+ })
+ }, {
+ enable: path => path.push('formula').get() !== 'ore_drops'
+ }),
+ ...conditions
+ },
+ 'copy_name': {
+ source: new EnumNode(COLLECTIONS.get('copy-sources')),
+ ...conditions
+ },
+ 'copy_nbt': {
+ source: new EnumNode(COLLECTIONS.get('copy-sources')),
+ ops: new ListNode(
+ new ObjectNode({
+ source: new StringNode(),
+ target: new StringNode(),
+ op: new EnumNode(['replace', 'append', 'merge'])
+ })
+ ),
+ ...conditions
+ },
+ 'copy_state': {
+ block: new ResourceNode(COLLECTIONS.get('blocks')),
+ properties: new ListNode(
+ new StringNode()
+ ),
+ ...conditions
+ },
+ 'enchant_randomly': {
+ enchantments: new ListNode(
+ new EnumNode(COLLECTIONS.get('enchantments'))
+ ),
+ ...conditions
+ },
+ 'enchant_with_levels': {
+ levels: new RangeNode(),
+ treasure: new BooleanNode(),
+ ...conditions
+ },
+ 'exploration_map': {
+ destination: new EnumNode(COLLECTIONS.get('structures')),
+ decoration: new EnumNode(COLLECTIONS.get('map-decorations')),
+ zoom: new NumberNode({integer: true}),
+ search_radius: new NumberNode({integer: true}),
+ skip_existing_chunks: new BooleanNode(),
+ ...conditions
+ },
+ 'fill_player_head': {
+ entity: new EnumNode(COLLECTIONS.get('entity-sources')),
+ ...conditions
+ },
+ 'limit_count': {
+ limit: new RangeNode(),
+ ...conditions
+ },
+ 'looting_enchant': {
+ count: new RangeNode(),
+ limit: new NumberNode({integer: true}),
+ ...conditions
+ },
+ 'set_attributes': {
+ modifiers: new ListNode(
+ new ReferenceNode('attribute-modifier')
+ ),
+ ...conditions
+ },
+ 'set_contents': {
+ entries: new ListNode(
+ new ReferenceNode('loot-entry')
+ ),
+ ...conditions
+ },
+ 'set_count': {
+ count: new RangeNode(),
+ ...conditions
+ },
+ 'set_damage': {
+ damage: new RangeNode(),
+ ...conditions
+ },
+ 'set_lore': {
+ entity: new EnumNode(COLLECTIONS.get('entity-sources')),
+ lore: new ListNode(
+ new StringNode()
+ ),
+ replace: new BooleanNode(),
+ ...conditions
+ },
+ 'set_name': {
+ entity: new EnumNode(COLLECTIONS.get('entity-sources')),
+ name: new StringNode(),
+ ...conditions
+ },
+ 'set_nbt': {
+ tag: new StringNode(),
+ ...conditions
+ },
+ 'set_stew_effect': {
+ effects: new ListNode(
+ new ReferenceNode('potion-effect')
+ ),
+ ...conditions
+ }
+ }
+}, {
+ default: () => ({
+ function: 'set_count',
+ count: 1
+ })
+}))
+
+SCHEMAS.register('attribute-modifier', new ObjectNode({
+ attribute: new EnumNode(COLLECTIONS.get('attributes')),
+ name: new StringNode(),
+ amount: new RangeNode(),
+ operation: new EnumNode([
+ 'addition',
+ 'multiply_base',
+ 'multiply_total'
+ ]),
+ slot: new ListNode(
+ new EnumNode(COLLECTIONS.get('slots'))
+ )
+}))
+
+export const LootTableSchema = SCHEMAS.get('loot-table')
diff --git a/src/view/SourceView.ts b/src/view/SourceView.ts
index 85b90a02..1bea7618 100644
--- a/src/view/SourceView.ts
+++ b/src/view/SourceView.ts
@@ -1,6 +1,11 @@
import { DataModel, ModelListener } from "../model/DataModel"
import { Path } from "../model/Path"
+type SourceViewOptions = {
+ indentation?: number | string,
+ rows?: number
+}
+
/**
* JSON representation view of the model.
* Renders the result in a