diff --git a/CHANGELOG.md b/CHANGELOG.md index 03344ae..4164d78 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added support for minecraft 1.17 ## [0.3.1] @@ -61,7 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [Unreleased]: https://github.com/Le0Developer/mcfunction.py/compare/v0.3.1...HEAD -[0.3.0]: https://github.com/Le0Developer/mcfunction.py/compare/v0.3.0...v0.3.1 +[0.3.1]: https://github.com/Le0Developer/mcfunction.py/compare/v0.3.0...v0.3.1 [0.3.0]: https://github.com/Le0Developer/mcfunction.py/compare/v0.2.1...v0.3.0 [0.2.1]: https://github.com/Le0Developer/mcfunction.py/compare/v0.2.0...v0.2.1 [0.2.0]: https://github.com/Le0Developer/mcfunction.py/compare/v0.1.0...v0.2.0 diff --git a/README.md b/README.md index d6a2798..aa52769 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Command parser and reconstructor for minecraft commands in pure python (requires python3.7+). -This project currently supports [minecraft 1.8 - 1.16](<#versions>) +For version compatibility, read [versions](<#versions>). ## Installing @@ -176,7 +176,7 @@ mcfunction.commands[2].command = '# ' ## Versions -This library currently supports **1.8 - 1.16**. +This library currently supports **1.8 - 1.17**. When using a parse function (`parse_command` and `parse_mcfunction`) you can specfiy which version's syntax you want (defaults to the latest version). diff --git a/mcfunction/parser_types.py b/mcfunction/parser_types.py index 62aae58..a305798 100644 --- a/mcfunction/parser_types.py +++ b/mcfunction/parser_types.py @@ -251,6 +251,14 @@ def parse(self, parts: t.Iterator[str]) -> nodes.ParticleNode: match = NamespaceID.namespace.fullmatch(get(parts)) arguments = (nodes.NamespaceIDNode(*match.groups()),) + elif name == 'vibration': + arguments = (Position().parse(parts), Position().parse(parts), + Integer().parse(parts)) + + elif name == 'dust_color_transition': + arguments = tuple(nodes.DoubleNode(float(x)) + for x in get(parts, 7)) + return nodes.ParticleNode(namespace, name, arguments) diff --git a/mcfunction/versions/__init__.py b/mcfunction/versions/__init__.py index bd259f0..7fec6cc 100644 --- a/mcfunction/versions/__init__.py +++ b/mcfunction/versions/__init__.py @@ -131,6 +131,8 @@ def parse(self, command: str, version: MinecraftVersion = None): VERSIONS = [ + MinecraftVersion('1.17', 'mcfunction.versions.mc_1_17', + removed_commands={'replaceitem'}), MinecraftVersion('1.16', 'mcfunction.versions.mc_1_16'), MinecraftVersion('1.15', 'mcfunction.versions.mc_1_15'), MinecraftVersion('1.14', 'mcfunction.versions.mc_1_14'), diff --git a/mcfunction/versions/mc_1_17/__init__.py b/mcfunction/versions/mc_1_17/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mcfunction/versions/mc_1_17/item.py b/mcfunction/versions/mc_1_17/item.py new file mode 100644 index 0000000..7845d97 --- /dev/null +++ b/mcfunction/versions/mc_1_17/item.py @@ -0,0 +1,110 @@ + +from dataclasses import dataclass +import typing as t + +from .. import Command, ParsedCommand, Parser +from ...exceptions import ConstructionException +from ...nodes import EntityNode, IntegerNode, ItemNode, PositionNode, RawNode +from ...parser_types import Any, Entity, Integer, Item, Literal, Position +from ...util import ensure_nodes + + +@dataclass() +class ParsedItemCommand(ParsedCommand): + command: str + + target_type: RawNode + target: t.Union[PositionNode, EntityNode] + slot: RawNode + action: RawNode + + source_type: RawNode = None + source: t.Union[PositionNode, EntityNode] = None + source_slot: RawNode = None + modifier: RawNode = None + item: ItemNode = None + count: IntegerNode = None + + def __str__(self): + base = (f'{self.command} {self.target_type} {self.target} {self.slot} ' + f'{self.action}') + + if self.action.value == 'copy': + ensure_nodes(self, 'source_type', 'source', 'source_slot') + command = f'{base} {self.source_type} {self.source} ' \ + f'{self.source_slot}' + if self.modifier is not None: + return f'{command} {self.modifier}' + return command + + elif self.action.value == 'modify': + ensure_nodes(self, 'modifier') + return f'{base} {self.modifier}' + + elif self.action.value == 'replace': + ensure_nodes(self, 'item') + if self.count is not None: + return f'{base} {self.item} {self.count}' + return f'{base} {self.item}' + + else: + raise ConstructionException( + f'expected action to be \'copy\', \'modfiy\' or \'replace\', ' + f'not {self.action.value!r}' + ) + + +item = Command('item', parsed=ParsedItemCommand) + +TYPES = (('block', Position()), ('entity', Entity())) + +for target_type, target in TYPES: + # item copy [] + for source_type, source in TYPES: + # - item copy + item.add_variation( + Parser(Literal(target_type), 'target_type'), + Parser(target, 'target'), + Parser(Any(), 'slot'), + Parser(Literal('copy'), 'action'), + Parser(Literal(source_type), 'source_type'), + Parser(source, 'source'), + Parser(Any(), 'source_slot'), + Parser(Any(), 'modifier') + ) + # - item copy + item.add_variation( + Parser(Literal(target_type), 'target_type'), + Parser(target, 'target'), + Parser(Any(), 'slot'), + Parser(Literal('copy'), 'action'), + Parser(Literal(source_type), 'source_type'), + Parser(source, 'source'), + Parser(Any(), 'source_slot') + ) + # item modify + item.add_variation( + Parser(Literal(target_type), 'target_type'), + Parser(target, 'target'), + Parser(Any(), 'slot'), + Parser(Literal('modify'), 'action'), + Parser(Any(), 'modifier') + ) + # item replace [] + # - item replace + item.add_variation( + Parser(Literal(target_type), 'target_type'), + Parser(target, 'target'), + Parser(Any(), 'slot'), + Parser(Literal('replace'), 'action'), + Parser(Item(), 'item'), + Parser(Integer(), 'count') + ) + # - item replace + item.add_variation( + Parser(Literal(target_type), 'target_type'), + Parser(target, 'target'), + Parser(Any(), 'slot'), + Parser(Literal('replace'), 'action'), + Parser(Item(), 'item') + ) diff --git a/tests/commands/mc-1.17/test_item.py b/tests/commands/mc-1.17/test_item.py new file mode 100644 index 0000000..f1774ac --- /dev/null +++ b/tests/commands/mc-1.17/test_item.py @@ -0,0 +1,77 @@ + +from mcfunction.versions.mc_1_17.item import item, ParsedItemCommand +from mcfunction.nodes import PositionNode + + +def test_item_copy(): + parsed = item.parse('item block 0 0 0 slot copy block 1 1 1 slot') + parsed: ParsedItemCommand + + assert parsed.target_type.value == 'block' + assert isinstance(parsed.target, PositionNode) + assert parsed.slot.value == 'slot' + assert parsed.action.value == 'copy' + assert parsed.source_type.value == 'block' + assert isinstance(parsed.source, PositionNode) + assert parsed.source_slot.value == 'slot' + + assert str(parsed) == 'item block 0 0 0 slot copy block 1 1 1 slot' + + +def test_item_copy_modifier(): + parsed = item.parse('item block 0 0 0 slot copy block 1 1 1 slot modifier') + parsed: ParsedItemCommand + + assert parsed.target_type.value == 'block' + assert isinstance(parsed.target, PositionNode) + assert parsed.slot.value == 'slot' + assert parsed.action.value == 'copy' + assert parsed.source_type.value == 'block' + assert isinstance(parsed.source, PositionNode) + assert parsed.source_slot.value == 'slot' + assert parsed.modifier.value == 'modifier' + + assert str(parsed) == 'item block 0 0 0 slot copy block 1 1 1 slot ' \ + 'modifier' + + +def test_item_modify(): + parsed = item.parse('item block 0 0 0 slot modify modifier') + parsed: ParsedItemCommand + + assert parsed.target_type.value == 'block' + assert isinstance(parsed.target, PositionNode) + assert parsed.slot.value == 'slot' + assert parsed.action.value == 'modify' + assert parsed.modifier.value == 'modifier' + + assert str(parsed) == 'item block 0 0 0 slot modify modifier' + + +def test_item_replace(): + parsed = item.parse('item block 0 0 0 slot replace test:item') + parsed: ParsedItemCommand + + assert parsed.target_type.value == 'block' + assert isinstance(parsed.target, PositionNode) + assert parsed.slot.value == 'slot' + assert parsed.action.value == 'replace' + assert parsed.item.namespace == 'test' + assert parsed.item.name == 'item' + + assert str(parsed) == 'item block 0 0 0 slot replace test:item' + + +def test_item_replace_count(): + parsed = item.parse('item block 0 0 0 slot replace test:item 42') + parsed: ParsedItemCommand + + assert parsed.target_type.value == 'block' + assert isinstance(parsed.target, PositionNode) + assert parsed.slot.value == 'slot' + assert parsed.action.value == 'replace' + assert parsed.item.namespace == 'test' + assert parsed.item.name == 'item' + assert parsed.count.value == 42 + + assert str(parsed) == 'item block 0 0 0 slot replace test:item 42' diff --git a/tests/test_parser_types.py b/tests/test_parser_types.py index 751b54f..79358cc 100644 --- a/tests/test_parser_types.py +++ b/tests/test_parser_types.py @@ -324,6 +324,8 @@ def test_particle(): 'minecraft:block', 'test:block', 'minecraft:falling_dust', 'test:block', 'minecraft:item', 'test:item', + 'minecraft:vibration', '0', '0', '0', '1', '1', '1', '2', + 'minecraft:dust_color_transition', '0', '1', '2', '3', '4', '5', '6', 'invalid particle', 'minecraft:dust' )) @@ -358,6 +360,18 @@ def test_particle(): assert node.name == 'item' assert isinstance(node.arguments[0], nodes.NamespaceIDNode) + node = particle.parse(parts) + assert node.namespace == 'minecraft' + assert node.name == 'vibration' + assert isinstance(node.arguments[0], nodes.PositionNode) + assert isinstance(node.arguments[1], nodes.PositionNode) + assert isinstance(node.arguments[2], nodes.IntegerNode) + + node = particle.parse(parts) + assert node.namespace == 'minecraft' + assert node.name == 'dust_color_transition' + assert [x.value for x in node.arguments] == [0, 1, 2, 3, 4, 5, 6] + with pytest.raises(ParserException, match='expected valid particle, not .*'): particle.parse(parts)