mcemu 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcemu-0.1.0/LICENSE +21 -0
- mcemu-0.1.0/PKG-INFO +42 -0
- mcemu-0.1.0/README.md +2 -0
- mcemu-0.1.0/mcemu/__init__.py +4 -0
- mcemu-0.1.0/mcemu/command_tree/arguments.py +176 -0
- mcemu-0.1.0/mcemu/command_tree/builder.py +10 -0
- mcemu-0.1.0/mcemu/command_tree/dispatcher.py +135 -0
- mcemu-0.1.0/mcemu/command_tree/execute_modifiers.py +77 -0
- mcemu-0.1.0/mcemu/command_tree/execute_node.py +15 -0
- mcemu-0.1.0/mcemu/command_tree/nodes.py +68 -0
- mcemu-0.1.0/mcemu/commands/__init__.py +13 -0
- mcemu-0.1.0/mcemu/commands/data.py +125 -0
- mcemu-0.1.0/mcemu/commands/execute.py +43 -0
- mcemu-0.1.0/mcemu/commands/function_cmds.py +34 -0
- mcemu-0.1.0/mcemu/commands/kill.py +18 -0
- mcemu-0.1.0/mcemu/commands/schedule.py +31 -0
- mcemu-0.1.0/mcemu/commands/scoreboard.py +109 -0
- mcemu-0.1.0/mcemu/commands/summon.py +36 -0
- mcemu-0.1.0/mcemu/commands/tag.py +27 -0
- mcemu-0.1.0/mcemu/commands/tick.py +33 -0
- mcemu-0.1.0/mcemu/commands/tp.py +52 -0
- mcemu-0.1.0/mcemu/context.py +67 -0
- mcemu-0.1.0/mcemu/emulator.py +98 -0
- mcemu-0.1.0/mcemu/entity.py +76 -0
- mcemu-0.1.0/mcemu/exceptions.py +3 -0
- mcemu-0.1.0/mcemu/tokenizer.py +85 -0
- mcemu-0.1.0/mcemu.egg-info/PKG-INFO +42 -0
- mcemu-0.1.0/mcemu.egg-info/SOURCES.txt +33 -0
- mcemu-0.1.0/mcemu.egg-info/dependency_links.txt +1 -0
- mcemu-0.1.0/mcemu.egg-info/requires.txt +5 -0
- mcemu-0.1.0/mcemu.egg-info/top_level.txt +1 -0
- mcemu-0.1.0/pyproject.toml +30 -0
- mcemu-0.1.0/setup.cfg +4 -0
- mcemu-0.1.0/tests/test_exceptions.py +7 -0
- mcemu-0.1.0/tests/test_tokenizer.py +46 -0
mcemu-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Oğuzhan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mcemu-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mcemu
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Minecraft command emulator library
|
|
5
|
+
Author: OguzhanUmutlu
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 Oğuzhan
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/OguzhanUmutlu/mcemu
|
|
29
|
+
Classifier: Programming Language :: Python :: 3
|
|
30
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
31
|
+
Classifier: Operating System :: OS Independent
|
|
32
|
+
Requires-Python: >=3.8
|
|
33
|
+
Description-Content-Type: text/markdown
|
|
34
|
+
License-File: LICENSE
|
|
35
|
+
Provides-Extra: dev
|
|
36
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
37
|
+
Requires-Dist: build>=0.10.0; extra == "dev"
|
|
38
|
+
Requires-Dist: twine>=4.0.0; extra == "dev"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# mcemu
|
|
42
|
+
Minecraft command emulator library for PyPI
|
mcemu-0.1.0/README.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
class CommandSyntaxError(Exception):
|
|
2
|
+
pass
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ArgumentType:
|
|
6
|
+
def get_name(self) -> str:
|
|
7
|
+
return self.__class__.__name__
|
|
8
|
+
|
|
9
|
+
def parse(self, tokens, pos: int):
|
|
10
|
+
raise NotImplementedError()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class IntArgument(ArgumentType):
|
|
14
|
+
def parse(self, tokens, pos: int):
|
|
15
|
+
if pos >= len(tokens):
|
|
16
|
+
raise CommandSyntaxError("Expected integer")
|
|
17
|
+
t = tokens[pos]
|
|
18
|
+
try:
|
|
19
|
+
return int(t.value), pos + 1
|
|
20
|
+
except ValueError:
|
|
21
|
+
raise CommandSyntaxError(f"Expected integer, got '{t.value}'")
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class FloatArgument(ArgumentType):
|
|
25
|
+
def parse(self, tokens, pos: int):
|
|
26
|
+
if pos >= len(tokens):
|
|
27
|
+
raise CommandSyntaxError("Expected float")
|
|
28
|
+
t = tokens[pos]
|
|
29
|
+
try:
|
|
30
|
+
return float(t.value), pos + 1
|
|
31
|
+
except ValueError:
|
|
32
|
+
raise CommandSyntaxError(f"Expected float, got '{t.value}'")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class StringArgument(ArgumentType):
|
|
36
|
+
def parse(self, tokens, pos: int):
|
|
37
|
+
if pos >= len(tokens):
|
|
38
|
+
raise CommandSyntaxError("Expected string")
|
|
39
|
+
t = tokens[pos]
|
|
40
|
+
return t.value, pos + 1
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class GreedyStringArgument(ArgumentType):
|
|
44
|
+
def parse(self, tokens, pos: int):
|
|
45
|
+
if pos >= len(tokens):
|
|
46
|
+
raise CommandSyntaxError("Expected string")
|
|
47
|
+
val = ""
|
|
48
|
+
while pos < len(tokens):
|
|
49
|
+
val += tokens[pos].value + " "
|
|
50
|
+
pos += 1
|
|
51
|
+
return val.strip(), pos
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class PathArgument(ArgumentType):
|
|
55
|
+
def parse(self, tokens, pos: int):
|
|
56
|
+
if pos >= len(tokens):
|
|
57
|
+
raise CommandSyntaxError("Expected path")
|
|
58
|
+
|
|
59
|
+
val = ""
|
|
60
|
+
if tokens[pos].type == "WORD":
|
|
61
|
+
val += tokens[pos].value
|
|
62
|
+
pos += 1
|
|
63
|
+
|
|
64
|
+
while pos < len(tokens):
|
|
65
|
+
t = tokens[pos]
|
|
66
|
+
if t.value in ("/", "-", "_", ":", ".", "\\"):
|
|
67
|
+
val += t.value
|
|
68
|
+
pos += 1
|
|
69
|
+
if pos < len(tokens) and tokens[pos].type == "WORD":
|
|
70
|
+
val += tokens[pos].value
|
|
71
|
+
pos += 1
|
|
72
|
+
else:
|
|
73
|
+
break
|
|
74
|
+
|
|
75
|
+
if not val:
|
|
76
|
+
raise CommandSyntaxError("Expected path")
|
|
77
|
+
return val, pos
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class TimeArgument(ArgumentType):
|
|
81
|
+
def parse(self, tokens, pos: int):
|
|
82
|
+
if pos >= len(tokens):
|
|
83
|
+
raise CommandSyntaxError("Expected time")
|
|
84
|
+
|
|
85
|
+
t = tokens[pos]
|
|
86
|
+
val = t.value
|
|
87
|
+
pos += 1
|
|
88
|
+
|
|
89
|
+
multiplier = 1
|
|
90
|
+
if val.endswith("t"):
|
|
91
|
+
val = val[:-1]
|
|
92
|
+
elif val.endswith("s"):
|
|
93
|
+
val = val[:-1]
|
|
94
|
+
multiplier = 20
|
|
95
|
+
elif val.endswith("d"):
|
|
96
|
+
val = val[:-1]
|
|
97
|
+
multiplier = 24000
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
return int(val) * multiplier, pos
|
|
101
|
+
except ValueError:
|
|
102
|
+
raise CommandSyntaxError(f"Expected time, got '{t.value}'")
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class WordArgument(ArgumentType):
|
|
106
|
+
def parse(self, tokens, pos: int):
|
|
107
|
+
if pos >= len(tokens):
|
|
108
|
+
raise CommandSyntaxError("Expected word")
|
|
109
|
+
|
|
110
|
+
val = tokens[pos].value
|
|
111
|
+
pos += 1
|
|
112
|
+
|
|
113
|
+
while pos < len(tokens):
|
|
114
|
+
t = tokens[pos]
|
|
115
|
+
if t.value in (":", "."):
|
|
116
|
+
val += t.value
|
|
117
|
+
pos += 1
|
|
118
|
+
if pos < len(tokens) and tokens[pos].type == "WORD":
|
|
119
|
+
val += tokens[pos].value
|
|
120
|
+
pos += 1
|
|
121
|
+
else:
|
|
122
|
+
break
|
|
123
|
+
return val, pos
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class ObjectiveArgument(WordArgument):
|
|
127
|
+
def get_name(self) -> str:
|
|
128
|
+
return "objective"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class TargetSelectorData:
|
|
132
|
+
def __init__(self, base: str, arguments: dict = None, is_pseudo: bool = False):
|
|
133
|
+
self.base = base
|
|
134
|
+
self.arguments = arguments
|
|
135
|
+
self.is_pseudo = is_pseudo
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class PseudoSelectorArgument(ArgumentType):
|
|
139
|
+
def get_name(self) -> str:
|
|
140
|
+
return "pseudo_target"
|
|
141
|
+
|
|
142
|
+
def parse(self, tokens, pos: int):
|
|
143
|
+
if pos >= len(tokens):
|
|
144
|
+
raise CommandSyntaxError("Expected target selector or name")
|
|
145
|
+
|
|
146
|
+
t = tokens[pos]
|
|
147
|
+
if t.value == "@":
|
|
148
|
+
pos += 1
|
|
149
|
+
if pos >= len(tokens):
|
|
150
|
+
raise CommandSyntaxError("Incomplete target selector")
|
|
151
|
+
base = tokens[pos].value
|
|
152
|
+
pos += 1
|
|
153
|
+
args = {}
|
|
154
|
+
if pos < len(tokens) and tokens[pos].value == "[":
|
|
155
|
+
pos += 1
|
|
156
|
+
while pos < len(tokens) and tokens[pos].value != "]":
|
|
157
|
+
key_val, pos = WordArgument().parse(tokens, pos)
|
|
158
|
+
if pos >= len(tokens) or tokens[pos].value != "=":
|
|
159
|
+
raise CommandSyntaxError("Expected '=' in selector")
|
|
160
|
+
pos += 1
|
|
161
|
+
val, pos = WordArgument().parse(tokens, pos)
|
|
162
|
+
args[key_val] = val
|
|
163
|
+
if pos < len(tokens) and tokens[pos].value == ",":
|
|
164
|
+
pos += 1
|
|
165
|
+
if pos >= len(tokens) or tokens[pos].value != "]":
|
|
166
|
+
raise CommandSyntaxError("Expected ']'")
|
|
167
|
+
pos += 1
|
|
168
|
+
return TargetSelectorData(base, args, is_pseudo=False), pos
|
|
169
|
+
else:
|
|
170
|
+
val, pos = WordArgument().parse(tokens, pos)
|
|
171
|
+
return TargetSelectorData(val, None, is_pseudo=True), pos
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class SelectorArgument(PseudoSelectorArgument):
|
|
175
|
+
def get_name(self) -> str:
|
|
176
|
+
return "target"
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from .arguments import ArgumentType
|
|
2
|
+
from .nodes import LiteralNode, ArgumentNode
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def literal(name: str) -> LiteralNode:
|
|
6
|
+
return LiteralNode(name)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def argument(name: str, arg_type: ArgumentType) -> ArgumentNode:
|
|
10
|
+
return ArgumentNode(name, arg_type)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from typing import List, Dict, Any, Tuple
|
|
2
|
+
|
|
3
|
+
from .arguments import CommandSyntaxError, TargetSelectorData
|
|
4
|
+
from .nodes import CommandNode, LiteralNode, ArgumentNode
|
|
5
|
+
from ..context import ExecutionContext, resolve_target_selector
|
|
6
|
+
from ..tokenizer import Token
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ParseContext:
|
|
10
|
+
def __init__(self, ctx: ExecutionContext):
|
|
11
|
+
self.ctx = ctx
|
|
12
|
+
self.arguments: Dict[str, Any] = {}
|
|
13
|
+
self.executable = None
|
|
14
|
+
self.modifiers: List[Tuple[Any, Dict[str, Any]]] = []
|
|
15
|
+
|
|
16
|
+
def clone(self):
|
|
17
|
+
new_ctx = ParseContext(self.ctx)
|
|
18
|
+
new_ctx.arguments = self.arguments.copy()
|
|
19
|
+
new_ctx.executable = self.executable
|
|
20
|
+
new_ctx.modifiers = list(self.modifiers)
|
|
21
|
+
return new_ctx
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class CommandDispatcher:
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.root = CommandNode()
|
|
27
|
+
|
|
28
|
+
def register(self, node: CommandNode):
|
|
29
|
+
self.root.children[node.get_name()] = node
|
|
30
|
+
|
|
31
|
+
def get_valid_paths(self, node: CommandNode) -> List[str]:
|
|
32
|
+
return [child.get_usage() for child in node.children.values()]
|
|
33
|
+
|
|
34
|
+
def parse_node(self, node: CommandNode, tokens: List[Token], pos: int, parse_ctx: ParseContext) -> Tuple[
|
|
35
|
+
bool, int, ParseContext, CommandNode]:
|
|
36
|
+
|
|
37
|
+
if isinstance(node, (LiteralNode, ArgumentNode)):
|
|
38
|
+
try:
|
|
39
|
+
val, pos = node.parse(tokens, pos)
|
|
40
|
+
if isinstance(node, ArgumentNode):
|
|
41
|
+
parse_ctx.arguments[node.name] = val
|
|
42
|
+
except CommandSyntaxError:
|
|
43
|
+
return False, pos, parse_ctx, node
|
|
44
|
+
|
|
45
|
+
if node.executable:
|
|
46
|
+
parse_ctx.executable = node.executable
|
|
47
|
+
|
|
48
|
+
if pos >= len(tokens):
|
|
49
|
+
if parse_ctx.executable:
|
|
50
|
+
return True, pos, parse_ctx, node
|
|
51
|
+
return False, pos, parse_ctx, node
|
|
52
|
+
|
|
53
|
+
if node.redirect:
|
|
54
|
+
if node.modifier:
|
|
55
|
+
parse_ctx.modifiers.append((node.modifier, parse_ctx.arguments.copy()))
|
|
56
|
+
return self.parse_node(node.redirect, tokens, pos, parse_ctx)
|
|
57
|
+
|
|
58
|
+
best_fail_node = node
|
|
59
|
+
best_fail_pos = pos
|
|
60
|
+
|
|
61
|
+
for child in node.children.values():
|
|
62
|
+
child_ctx = parse_ctx.clone()
|
|
63
|
+
success, child_pos, final_ctx, last_node = self.parse_node(child, tokens, pos, child_ctx)
|
|
64
|
+
if success:
|
|
65
|
+
return True, child_pos, final_ctx, last_node
|
|
66
|
+
|
|
67
|
+
if child_pos > best_fail_pos:
|
|
68
|
+
best_fail_pos = child_pos
|
|
69
|
+
best_fail_node = last_node
|
|
70
|
+
|
|
71
|
+
return False, best_fail_pos, parse_ctx, best_fail_node
|
|
72
|
+
|
|
73
|
+
def dispatch(self, tokens: List[Token], ctx: ExecutionContext) -> int:
|
|
74
|
+
if not tokens:
|
|
75
|
+
return 0
|
|
76
|
+
|
|
77
|
+
parse_ctx = ParseContext(ctx)
|
|
78
|
+
best_fail_pos = 0
|
|
79
|
+
best_fail_node = self.root
|
|
80
|
+
|
|
81
|
+
for child in self.root.children.values():
|
|
82
|
+
child_ctx = parse_ctx.clone()
|
|
83
|
+
success, pos, final_ctx, last_node = self.parse_node(child, tokens, 0, child_ctx)
|
|
84
|
+
if success:
|
|
85
|
+
if pos < len(tokens):
|
|
86
|
+
print(f"Error parsing: Trailing data at token {pos} ('{tokens[pos].value}')")
|
|
87
|
+
return 0
|
|
88
|
+
return self.execute(final_ctx)
|
|
89
|
+
|
|
90
|
+
if pos > best_fail_pos:
|
|
91
|
+
best_fail_pos = pos
|
|
92
|
+
best_fail_node = last_node
|
|
93
|
+
|
|
94
|
+
err_token = tokens[best_fail_pos].value if best_fail_pos < len(tokens) else "EOF"
|
|
95
|
+
paths = self.get_valid_paths(best_fail_node)
|
|
96
|
+
print(f"Command Error at '{err_token}'. Expected one of: {', '.join(paths)}")
|
|
97
|
+
return 0
|
|
98
|
+
|
|
99
|
+
def _resolve_args(self, args: dict, ctx: ExecutionContext) -> dict:
|
|
100
|
+
resolved_args = {}
|
|
101
|
+
for key, val in args.items():
|
|
102
|
+
if isinstance(val, TargetSelectorData):
|
|
103
|
+
targets = resolve_target_selector(val, ctx)
|
|
104
|
+
if not targets and not val.is_pseudo:
|
|
105
|
+
print(f"No entity was found for selector {val.base}")
|
|
106
|
+
resolved_args[key] = targets
|
|
107
|
+
else:
|
|
108
|
+
resolved_args[key] = val
|
|
109
|
+
return resolved_args
|
|
110
|
+
|
|
111
|
+
def _run_with_modifiers(self, parse_ctx: ParseContext, ctx: ExecutionContext, mod_idx: int) -> int:
|
|
112
|
+
if mod_idx >= len(parse_ctx.modifiers):
|
|
113
|
+
if parse_ctx.executable:
|
|
114
|
+
resolved_args = self._resolve_args(parse_ctx.arguments, ctx)
|
|
115
|
+
return parse_ctx.executable(ctx, **resolved_args)
|
|
116
|
+
return 0
|
|
117
|
+
|
|
118
|
+
modifier, args = parse_ctx.modifiers[mod_idx]
|
|
119
|
+
resolved_args = self._resolve_args(args, ctx)
|
|
120
|
+
|
|
121
|
+
contexts = modifier.modify(ctx, resolved_args)
|
|
122
|
+
|
|
123
|
+
total_res = 0
|
|
124
|
+
for sub_ctx in contexts:
|
|
125
|
+
res = self._run_with_modifiers(parse_ctx, sub_ctx, mod_idx + 1)
|
|
126
|
+
modifier.on_result(sub_ctx, resolved_args, res)
|
|
127
|
+
total_res += res
|
|
128
|
+
|
|
129
|
+
return total_res
|
|
130
|
+
|
|
131
|
+
def execute(self, parse_ctx: ParseContext) -> int:
|
|
132
|
+
return self._run_with_modifiers(parse_ctx, parse_ctx.ctx, 0)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
dispatcher = CommandDispatcher()
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from .arguments import TargetSelectorData
|
|
4
|
+
from ..context import ExecutionContext
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ExecuteModifier:
|
|
8
|
+
def modify(self, ctx: ExecutionContext, args: dict) -> List[ExecutionContext]:
|
|
9
|
+
raise NotImplementedError()
|
|
10
|
+
|
|
11
|
+
def on_result(self, ctx: ExecutionContext, args: dict, result: int):
|
|
12
|
+
pass
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ExecuteAsModifier(ExecuteModifier):
|
|
16
|
+
def modify(self, ctx: ExecutionContext, args: dict) -> List[ExecutionContext]:
|
|
17
|
+
targets = args.get("target", [])
|
|
18
|
+
contexts = []
|
|
19
|
+
for t in targets:
|
|
20
|
+
entity = next((e for e in ctx.world.entities if e.uuid == t or e.name == t), None)
|
|
21
|
+
if entity:
|
|
22
|
+
new_ctx = ctx.clone()
|
|
23
|
+
new_ctx.executor = entity
|
|
24
|
+
contexts.append(new_ctx)
|
|
25
|
+
return contexts
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ExecuteAtModifier(ExecuteModifier):
|
|
29
|
+
def modify(self, ctx: ExecutionContext, args: dict) -> List[ExecutionContext]:
|
|
30
|
+
targets = args.get("target", [])
|
|
31
|
+
contexts = []
|
|
32
|
+
for t in targets:
|
|
33
|
+
entity = next((e for e in ctx.world.entities if e.uuid == t or e.name == t), None)
|
|
34
|
+
if entity:
|
|
35
|
+
new_ctx = ctx.clone()
|
|
36
|
+
new_ctx.position = entity.pos
|
|
37
|
+
contexts.append(new_ctx)
|
|
38
|
+
return contexts
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ExecuteStoreScoreModifier(ExecuteModifier):
|
|
42
|
+
def modify(self, ctx: ExecutionContext, args: dict) -> List[ExecutionContext]:
|
|
43
|
+
return [ctx]
|
|
44
|
+
|
|
45
|
+
def on_result(self, ctx: ExecutionContext, args: dict, result: int):
|
|
46
|
+
targets = args.get("store_target", [])
|
|
47
|
+
objective = args.get("store_objective", "")
|
|
48
|
+
for t in targets:
|
|
49
|
+
ctx.world.set_score(t, objective, int(result))
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class ExecuteStoreResultModifier(ExecuteModifier):
|
|
53
|
+
def modify(self, ctx: ExecutionContext, args: dict) -> List[ExecutionContext]:
|
|
54
|
+
return [ctx]
|
|
55
|
+
|
|
56
|
+
def on_result(self, ctx: ExecutionContext, args: dict, result: int):
|
|
57
|
+
targets = args.get("store_target", [])
|
|
58
|
+
store_type = args.get("store_type", "entity")
|
|
59
|
+
path = args.get("store_path", "")
|
|
60
|
+
multiplier = float(args.get("store_multiplier", 1.0))
|
|
61
|
+
datatype = args.get("store_datatype", "int")
|
|
62
|
+
|
|
63
|
+
from ..commands.data import set_nested_dict
|
|
64
|
+
if "NBTType" in store_type:
|
|
65
|
+
store_type = store_type.split(".")[-1].lower()
|
|
66
|
+
|
|
67
|
+
scaled_res = result * multiplier
|
|
68
|
+
if "int" in datatype.lower():
|
|
69
|
+
scaled_res = int(scaled_res)
|
|
70
|
+
elif "byte" in datatype.lower():
|
|
71
|
+
scaled_res = int(scaled_res) & 0xFF
|
|
72
|
+
|
|
73
|
+
for t in targets:
|
|
74
|
+
target_key = f"{store_type}:{t}"
|
|
75
|
+
if target_key not in ctx.world.nbt_storage:
|
|
76
|
+
ctx.world.nbt_storage[target_key] = {}
|
|
77
|
+
set_nested_dict(ctx.world.nbt_storage[target_key], path, scaled_res)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from typing import List, Any, Tuple
|
|
2
|
+
|
|
3
|
+
from .nodes import CommandNode
|
|
4
|
+
from ..tokenizer import Token
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ExecuteNode(CommandNode):
|
|
8
|
+
def get_name(self) -> str:
|
|
9
|
+
return "execute"
|
|
10
|
+
|
|
11
|
+
def get_usage(self) -> str:
|
|
12
|
+
return "execute <subcommands> run <command>"
|
|
13
|
+
|
|
14
|
+
def parse(self, tokens: List[Token], pos: int) -> Tuple[Any, int]:
|
|
15
|
+
pass
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
from typing import List, Dict, Callable, Any, Tuple
|
|
2
|
+
|
|
3
|
+
from .arguments import ArgumentType, CommandSyntaxError
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class CommandNode:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.children: Dict[str, "CommandNode"] = {}
|
|
9
|
+
self.executable: Callable = None
|
|
10
|
+
self.redirect: "CommandNode" = None
|
|
11
|
+
self.modifier: Any = None
|
|
12
|
+
|
|
13
|
+
def then(self, node: "CommandNode") -> "CommandNode":
|
|
14
|
+
self.children[node.get_name()] = node
|
|
15
|
+
return self
|
|
16
|
+
|
|
17
|
+
def executes(self, func: Callable) -> "CommandNode":
|
|
18
|
+
self.executable = func
|
|
19
|
+
return self
|
|
20
|
+
|
|
21
|
+
def forks(self, node: "CommandNode", modifier: Any = None) -> "CommandNode":
|
|
22
|
+
self.redirect = node
|
|
23
|
+
self.modifier = modifier
|
|
24
|
+
return self
|
|
25
|
+
|
|
26
|
+
def parse(self, tokens: List[Any], pos: int) -> Tuple[Any, int]:
|
|
27
|
+
raise NotImplementedError()
|
|
28
|
+
|
|
29
|
+
def get_name(self) -> str:
|
|
30
|
+
raise NotImplementedError()
|
|
31
|
+
|
|
32
|
+
def get_usage(self) -> str:
|
|
33
|
+
raise NotImplementedError()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class LiteralNode(CommandNode):
|
|
37
|
+
def __init__(self, literal: str):
|
|
38
|
+
super().__init__()
|
|
39
|
+
self.literal = literal
|
|
40
|
+
|
|
41
|
+
def parse(self, tokens: List[Any], pos: int) -> Tuple[Any, int]:
|
|
42
|
+
if pos >= len(tokens):
|
|
43
|
+
raise CommandSyntaxError(f"Expected '{self.literal}'")
|
|
44
|
+
if tokens[pos].value == self.literal:
|
|
45
|
+
return self.literal, pos + 1
|
|
46
|
+
raise CommandSyntaxError(f"Expected '{self.literal}', got '{tokens[pos].value}'")
|
|
47
|
+
|
|
48
|
+
def get_name(self) -> str:
|
|
49
|
+
return self.literal
|
|
50
|
+
|
|
51
|
+
def get_usage(self) -> str:
|
|
52
|
+
return self.literal
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ArgumentNode(CommandNode):
|
|
56
|
+
def __init__(self, name: str, arg_type: ArgumentType):
|
|
57
|
+
super().__init__()
|
|
58
|
+
self.name = name
|
|
59
|
+
self.arg_type = arg_type
|
|
60
|
+
|
|
61
|
+
def parse(self, tokens: List[Any], pos: int) -> Tuple[Any, int]:
|
|
62
|
+
return self.arg_type.parse(tokens, pos)
|
|
63
|
+
|
|
64
|
+
def get_name(self) -> str:
|
|
65
|
+
return self.name
|
|
66
|
+
|
|
67
|
+
def get_usage(self) -> str:
|
|
68
|
+
return f"<{self.name}: {self.arg_type.get_name()}>"
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
from . import data
|
|
2
|
+
from . import execute
|
|
3
|
+
from . import function_cmds
|
|
4
|
+
from . import kill
|
|
5
|
+
from . import schedule
|
|
6
|
+
from . import scoreboard
|
|
7
|
+
from . import summon
|
|
8
|
+
from . import tag
|
|
9
|
+
from . import tick
|
|
10
|
+
from . import tp
|
|
11
|
+
from ..command_tree.dispatcher import dispatcher
|
|
12
|
+
|
|
13
|
+
__all__ = ["dispatcher"]
|