Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Add support for item components #1131

Merged
merged 28 commits into from
Jun 15, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
52740d3
begin work
Trivaxy May 11, 2024
dc6c238
potentially fix error
Trivaxy May 11, 2024
bd7d1a6
make ParticleNode refer to ItemNode
Trivaxy May 11, 2024
6c8fdff
Rudimentary item component syntax support
Trivaxy May 12, 2024
3e0f112
✨ Implement various command parsers and tree patch changes (#1128)
misode May 12, 2024
4e05d80
fix `ItemOldNode.is` and `ItemNewNode.is`
Trivaxy May 13, 2024
797cfdf
report error when duplicate components are defined
Trivaxy May 13, 2024
ea2a2cb
make duplicate components error localize properly
Trivaxy May 13, 2024
6f975f3
make error for duplicate components consistent
Trivaxy May 13, 2024
e51dad1
make error reporting for duplicates smarter
Trivaxy May 13, 2024
0b3af75
rename `item_old` and `item_new`
Trivaxy May 13, 2024
27c395a
(very buggy) new item predicate syntax
Trivaxy May 17, 2024
4697366
undo changes to `list.ts`
Trivaxy May 17, 2024
e4bd254
Merge branch 'main' into pr/Trivaxy/1131-1
misode May 30, 2024
11a5537
Implement a few of SPGoding's suggestions
misode May 30, 2024
a5752fd
Refactor nodes to separate item stack and predicate instead of old an…
misode May 30, 2024
05f1010
Remove unused function
misode May 30, 2024
6ed3a53
Update snapshots
misode May 30, 2024
28be8e5
Use correct symbol categories + change duplicate check
misode May 30, 2024
3d8f860
Start of completers
misode May 30, 2024
a6d7d51
Refactor component test parser
misode May 30, 2024
8612a2e
fix equality component tests only expecting compounds
Trivaxy Jun 15, 2024
0b89a61
finish item predicates
Trivaxy Jun 15, 2024
89cdf23
Merge remote-tracking branch 'upstream/main' into item-components
Trivaxy Jun 15, 2024
e6f6a5d
fix weird merge
Trivaxy Jun 15, 2024
f0077d7
fix linting
Trivaxy Jun 15, 2024
5cfdfa1
return component test nodes to being infallible
Trivaxy Jun 15, 2024
3be2227
... fix lint. i forgot to make this automatic
Trivaxy Jun 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/core/src/symbol/Symbol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ export type TagFileCategory = (typeof TagFileCategories)[number]
export const FileCategories = Object.freeze(
[
'advancement',
'chat_type',
'damage_type',
'dimension',
'dimension_type',
'function',
Expand All @@ -127,6 +129,8 @@ export const FileCategories = Object.freeze(
'predicate',
'recipe',
'structure',
'trim_material',
'trim_pattern',
...TagFileCategories,
...WorldgenFileCategories,
] as const,
Expand Down
7 changes: 6 additions & 1 deletion packages/java-edition/src/dependency/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,12 @@ export const PackVersionMap: Record<number, RegExp | undefined> = {
7: /^1\.17.*$/,
8: /^1\.18(\.1)?$/,
9: /^1\.18.*$/,
10: /^1\.19.*$/,
10: /^1\.19(\.[1-3])?$/,
12: /^1\.19.*$/,
15: /^1\.20(\.1)?$/,
18: /^1\.20\.2$/,
26: /^1\.20\.[3-4]$/,
41: /^1\.20\.[5-6]$/,
}

export interface PackMcmeta {
Expand Down
48 changes: 42 additions & 6 deletions packages/java-edition/src/mcfunction/checker/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import * as mcf from '@spyglassmc/mcfunction'
import * as nbt from '@spyglassmc/nbt'
import { getTagValues } from '../../common/index.js'
import { text_component } from '../../json/checker/data/text_component.js'
import type { EntitySelectorInvertableArgumentValueNode } from '../node/index.js'
import type {
ComponentListNode,
EntitySelectorInvertableArgumentValueNode,
} from '../node/index.js'
import { ItemOldNode } from '../node/index.js'
import { BlockNode, EntityNode, ItemNode, ParticleNode } from '../node/index.js'

export const command: core.Checker<mcf.CommandNode> = (node, ctx) => {
Expand Down Expand Up @@ -162,14 +166,46 @@ const entity: core.SyncChecker<EntityNode> = (node, ctx) => {
}

const item: core.SyncChecker<ItemNode> = (node, ctx) => {
if (!node.nbt) {
if (!ItemNode.hasUserData(node)) {
return
}

nbt.checker.index(
'item',
core.ResourceLocationNode.toString(node.id, 'full'),
)(node.nbt, ctx)
if (ItemOldNode.is(node)) {
nbt.checker.index(
'item',
core.ResourceLocationNode.toString(node.id, 'full'),
)(node.nbt!, ctx)
} else {
const groupedComponents = new Map<
string,
core.PairNode<core.ResourceLocationNode, nbt.NbtNode>[]
>()

node.components!.children.forEach(component => {
const componentName = core.ResourceLocationNode.toString(
component.key!,
'full',
)

if (!groupedComponents.has(componentName)) {
groupedComponents.set(componentName, [])
}

groupedComponents.get(componentName)!.push(component)
})

groupedComponents.forEach((components, componentName) => {
if (components.length > 1) {
components.forEach(component => {
ctx.err.report(
localize('duplicate-components', componentName),
component.key!.range,
core.ErrorSeverity.Error,
)
})
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use core.LanguageErrorInfo.related to report only one error for this with the duplicated keys' locations added in the info.related field (core.Location.create() would be helpful to create the entries for the field)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vscode also creates an error for each duplicate key in a json file for example, imo this is the most clean

})
}
}

const particle: core.SyncChecker<ParticleNode> = (node, ctx) => {
Expand Down
22 changes: 22 additions & 0 deletions packages/java-edition/src/mcfunction/common/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,25 @@ export const SwizzleArgumentValues = [
'zxy',
'zyx',
]

export const HeightmapValues = [
'motion_blocking',
'motion_blocking_no_leaves',
'ocean_floor',
'ocean_floor_wg',
'world_surface',
'world_surface_wg',
]

export const RotationValues = [
'none',
'clockwise_90',
'180',
'counterclockwise_90',
]

export const MirrorValues = [
'none',
'left_right',
'front_back',
]
31 changes: 26 additions & 5 deletions packages/java-edition/src/mcfunction/completer/argument.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {
Arrayable,
Completer,
CompleterContext,
MetaRegistry,
RegistryCategory,
WorldgenFileCategory,
Expand All @@ -27,18 +28,23 @@ import * as json from '@spyglassmc/json'
import { localeQuote, localize } from '@spyglassmc/locales'
import type * as mcf from '@spyglassmc/mcfunction'
import { getTagValues } from '../../common/index.js'
import { ReleaseVersion } from '../../dependency/common.js'
import {
ColorArgumentValues,
EntityAnchorArgumentValues,
GamemodeArgumentValues,
HeightmapValues,
ItemSlotArgumentValues,
MirrorValues,
OperationArgumentValues,
RotationValues,
ScoreboardSlotArgumentValues,
SwizzleArgumentValues,
} from '../common/index.js'
import type {
BlockStatesNode,
EntitySelectorArgumentsNode,
ItemOldNode,
} from '../node/index.js'
import {
BlockNode,
Expand All @@ -53,9 +59,17 @@ import {
} from '../node/index.js'
import type { ArgumentTreeNode } from '../tree/index.js'

function getItemStackFormat(ctx: CompleterContext): 'old' | 'new' {
const release = ctx.config.env.gameVersion as ReleaseVersion | undefined
SPGoding marked this conversation as resolved.
Show resolved Hide resolved
return (release === undefined || ReleaseVersion.cmp(release, '1.20.5') < 0)
? 'old'
: 'new'
}

export const getMockNodes: mcf.completer.MockNodesGetter = (
rawTreeNode,
range,
ctx,
): Arrayable<AstNode> => {
const treeNode = rawTreeNode as ArgumentTreeNode

Expand Down Expand Up @@ -98,6 +112,8 @@ export const getMockNodes: mcf.completer.MockNodesGetter = (
case 'minecraft:entity':
case 'minecraft:game_profile':
return EntitySelectorNode.mock(range)
case 'minecraft:heightmap':
return LiteralNode.mock(range, { pool: HeightmapValues })
case 'minecraft:entity_anchor':
return LiteralNode.mock(range, { pool: EntityAnchorArgumentValues })
case 'minecraft:entity_summon':
Expand All @@ -111,11 +127,11 @@ export const getMockNodes: mcf.completer.MockNodesGetter = (
case 'minecraft:item_enchantment':
return ResourceLocationNode.mock(range, { category: 'enchantment' })
case 'minecraft:item_predicate':
return ItemNode.mock(range, true)
return ItemNode.mock(range, true, getItemStackFormat(ctx))
case 'minecraft:item_slot':
return LiteralNode.mock(range, { pool: ItemSlotArgumentValues })
case 'minecraft:item_stack':
return ItemNode.mock(range, false)
return ItemNode.mock(range, false, getItemStackFormat(ctx))
case 'minecraft:mob_effect':
return ResourceLocationNode.mock(range, { category: 'mob_effect' })
case 'minecraft:objective':
Expand All @@ -130,6 +146,7 @@ export const getMockNodes: mcf.completer.MockNodesGetter = (
case 'minecraft:particle':
return ParticleNode.mock(range)
case 'minecraft:resource':
case 'minecraft:resource_key':
case 'minecraft:resource_or_tag':
return ResourceLocationNode.mock(range, {
category: ResourceLocation.shorten(treeNode.properties.registry) as
Expand All @@ -152,6 +169,10 @@ export const getMockNodes: mcf.completer.MockNodesGetter = (
return LiteralNode.mock(range, { pool: SwizzleArgumentValues })
case 'minecraft:team':
return SymbolNode.mock(range, { category: 'team' })
case 'minecraft:template_mirror':
return LiteralNode.mock(range, { pool: MirrorValues })
case 'minecraft:template_rotation':
return LiteralNode.mock(range, { pool: RotationValues })
case 'minecraft:vec2':
return VectorNode.mock(range, { dimension: 2, integersOnly: true })
case 'minecraft:vec3':
Expand Down Expand Up @@ -243,7 +264,7 @@ const coordinate: Completer<CoordinateNode> = (node, _ctx) => {
return [CompletionItem.create('~', node)]
}

const item: Completer<ItemNode> = (node, ctx) => {
const item: Completer<ItemOldNode> = (node, ctx) => {
const ans: CompletionItem[] = []
if (Range.contains(node.id, ctx.offset, true)) {
ans.push(...completer.resourceLocation(node.id, ctx))
Expand Down Expand Up @@ -299,7 +320,7 @@ const particle: Completer<ParticleNode> = (node, ctx) => {
VectorNode.mock(ctx.offset, { dimension: 3 }),
],
falling_dust: [BlockNode.mock(ctx.offset, false)],
item: [ItemNode.mock(ctx.offset, false)],
item: [ItemNode.mock(ctx.offset, false, getItemStackFormat(ctx))],
sculk_charge: [FloatNode.mock(ctx.offset)],
shriek: [IntegerNode.mock(ctx.offset)],
vibration: [
Expand Down Expand Up @@ -434,7 +455,7 @@ export function register(meta: MetaRegistry) {
selectorArguments,
)
meta.registerCompleter<IntRangeNode>('mcfunction:int_range', intRange)
meta.registerCompleter<ItemNode>('mcfunction:item', item)
meta.registerCompleter<ItemOldNode>('mcfunction:item', item)
meta.registerCompleter<ObjectiveCriteriaNode>(
'mcfunction:objective_criteria',
objectiveCriteria,
Expand Down
55 changes: 52 additions & 3 deletions packages/java-edition/src/mcfunction/node/argument.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,22 +279,60 @@ export interface FloatRangeNode extends core.AstNode {
value: [number | undefined, number | undefined]
}

export interface ItemNode extends core.AstNode {
export interface ItemOldNode extends core.AstNode {
type: 'mcfunction:item'
children: (core.ResourceLocationNode | nbt.NbtCompoundNode)[]
id: core.ResourceLocationNode
nbt?: nbt.NbtCompoundNode
}
export namespace ItemOldNode {
export function is(node: core.AstNode | undefined): node is ItemOldNode {
return (node as ItemOldNode | undefined)?.type === 'mcfunction:item' &&
'nbt' in (node as ItemNode)
}
}

export interface ItemNewNode extends core.AstNode {
type: 'mcfunction:item'
misode marked this conversation as resolved.
Show resolved Hide resolved
children: (core.ResourceLocationNode | ComponentListNode)[]
id: core.ResourceLocationNode
misode marked this conversation as resolved.
Show resolved Hide resolved
components?: ComponentListNode
}

export namespace ItemNewNode {
export function is(node: core.AstNode | undefined): node is ItemNewNode {
return (node as ItemNewNode | undefined)?.type === 'mcfunction:item' &&
'components' in (node as ItemNode)
misode marked this conversation as resolved.
Show resolved Hide resolved
}
}

export type ItemNode = ItemOldNode | ItemNewNode

export namespace ItemNode {
export function is(node: core.AstNode | undefined): node is ItemNode {
return (node as ItemNode | undefined)?.type === 'mcfunction:item'
return (
ItemOldNode.is(node) || ItemNewNode.is(node)
)
}

export function mock(range: core.RangeLike, isPredicate: boolean): ItemNode {
export function hasUserData(node: ItemNode): boolean {
if (ItemOldNode.is(node)) {
return !!node.nbt
} else {
return !!node.components
}
}

export function mock(
range: core.RangeLike,
isPredicate: boolean,
format: 'old' | 'new',
): ItemNode {
const id = core.ResourceLocationNode.mock(range, {
category: 'item',
allowTag: isPredicate,
})

return {
type: 'mcfunction:item',
range: core.Range.get(range),
Expand All @@ -304,6 +342,17 @@ export namespace ItemNode {
}
}

export interface ComponentListNode extends core.AstNode {
type: 'mcfunction:component_list'
children: core.PairNode<core.ResourceLocationNode, nbt.NbtNode>[]
}

export namespace ComponentListNode {
export function is(node: core.AstNode): node is ComponentListNode {
return (node as ComponentListNode).type === 'mcfunction:component_list'
}
}

export interface IntRangeNode extends core.AstNode {
type: 'mcfunction:int_range'
children: (core.IntegerNode | core.LiteralNode)[]
Expand Down
Loading