dfpyre 0.6.2__py3-none-any.whl → 0.7.0__py3-none-any.whl

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.

Potentially problematic release.


This version of dfpyre might be problematic. Click here for more details.

dfpyre/items.py CHANGED
@@ -4,19 +4,28 @@ Contains class definitions for code items.
4
4
 
5
5
  from enum import Enum
6
6
  import re
7
- from typing import Literal, Dict, Any
7
+ from typing import Literal, Any
8
8
  from dfpyre.style import is_ampersand_coded, ampersand_to_minimessage
9
+ from dfpyre.util import PyreException, warn
9
10
  from mcitemlib.itemlib import Item as NbtItem
10
11
 
11
12
 
12
13
  NUMBER_REGEX = r'-?\d*\.?\d+'
14
+ VAR_SHORTHAND_CHAR = '$'
15
+ VAR_SCOPES = {'g': 'unsaved', 's': 'saved', 'l': 'local', 'i': 'line'}
13
16
 
14
17
 
15
- class PyreException(Exception):
16
- pass
18
+ def convert_argument(arg: Any):
19
+ if type(arg) in {int, float}:
20
+ return num(arg)
21
+ elif isinstance(arg, str):
22
+ if len(arg) > 2 and arg[0] == VAR_SHORTHAND_CHAR and arg[1] in VAR_SCOPES:
23
+ return var(arg[2:], VAR_SCOPES[arg[1]])
24
+ return text(arg)
25
+ return arg
17
26
 
18
27
 
19
- def _add_slot(d: Dict, slot: int|None):
28
+ def _add_slot(d: dict, slot: int|None):
20
29
  if slot is not None:
21
30
  d['slot'] = slot
22
31
 
@@ -32,6 +41,9 @@ class item(NbtItem):
32
41
  _add_slot(formatted_dict, slot)
33
42
  return formatted_dict
34
43
 
44
+ def __repr__(self) -> str:
45
+ return f'{self.__class__.__name__}({self.get_id()}, {self.get_count()})'
46
+
35
47
 
36
48
  class string:
37
49
  """
@@ -46,6 +58,9 @@ class string:
46
58
  formatted_dict = {"item": {"id": self.type, "data": {"name": self.value}}}
47
59
  _add_slot(formatted_dict, slot)
48
60
  return formatted_dict
61
+
62
+ def __repr__(self) -> str:
63
+ return f'{self.__class__.__name__}("{self.value}")'
49
64
 
50
65
 
51
66
  class text:
@@ -64,6 +79,9 @@ class text:
64
79
  _add_slot(formatted_dict, slot)
65
80
  return formatted_dict
66
81
 
82
+ def __repr__(self) -> str:
83
+ return f'{self.__class__.__name__}("{self.value}")'
84
+
67
85
 
68
86
  class num:
69
87
  """
@@ -79,6 +97,9 @@ class num:
79
97
  _add_slot(formatted_dict, slot)
80
98
  return formatted_dict
81
99
 
100
+ def __repr__(self) -> str:
101
+ return f'{self.__class__.__name__}({self.value})'
102
+
82
103
 
83
104
  class loc:
84
105
  """
@@ -110,6 +131,9 @@ class loc:
110
131
  _add_slot(formatted_dict, slot)
111
132
  return formatted_dict
112
133
 
134
+ def __repr__(self) -> str:
135
+ return f'{self.__class__.__name__}({self.x}, {self.y}, {self.z}, {self.pitch}, {self.yaw})'
136
+
113
137
 
114
138
  class var:
115
139
  """
@@ -126,6 +150,9 @@ class var:
126
150
  _add_slot(formatted_dict, slot)
127
151
  return formatted_dict
128
152
 
153
+ def __repr__(self) -> str:
154
+ return f'{self.__class__.__name__}({self.scope}, "{self.name}")'
155
+
129
156
 
130
157
  class sound:
131
158
  """
@@ -143,6 +170,9 @@ class sound:
143
170
  _add_slot(formatted_dict, slot)
144
171
  return formatted_dict
145
172
 
173
+ def __repr__(self) -> str:
174
+ return f'{self.__class__.__name__}(pitch: {self.pitch}, volume: {self.vol})'
175
+
146
176
 
147
177
  class particle:
148
178
  """
@@ -157,6 +187,9 @@ class particle:
157
187
  _add_slot(formatted_dict, slot)
158
188
  return formatted_dict
159
189
 
190
+ def __repr__(self) -> str:
191
+ return f'{self.__class__.__name__}({self.particle_data})'
192
+
160
193
 
161
194
  class potion:
162
195
  """
@@ -174,6 +207,9 @@ class potion:
174
207
  _add_slot(formatted_dict, slot)
175
208
  return formatted_dict
176
209
 
210
+ def __repr__(self) -> str:
211
+ return f'{self.__class__.__name__}(effect: {self.name}, duration: {self.dur}, amplifier: {self.amp})'
212
+
177
213
 
178
214
  class gamevalue:
179
215
  """
@@ -190,6 +226,9 @@ class gamevalue:
190
226
  _add_slot(formatted_dict, slot)
191
227
  return formatted_dict
192
228
 
229
+ def __repr__(self) -> str:
230
+ return f'{self.__class__.__name__}({self.name}, target: {self.target})'
231
+
193
232
 
194
233
  class vector:
195
234
  """
@@ -207,6 +246,9 @@ class vector:
207
246
  _add_slot(formatted_dict, slot)
208
247
  return formatted_dict
209
248
 
249
+ def __repr__(self) -> str:
250
+ return f'{self.__class__.__name__}({self.x}, {self.y}, {self.z})'
251
+
210
252
 
211
253
  PARAMETER_TYPE_LOOKUP = ['txt', 'comp', 'num', 'loc', 'vec', 'snd', 'part', 'pot', 'item', 'any', 'var', 'list', 'dict']
212
254
 
@@ -241,7 +283,7 @@ class parameter:
241
283
  self.optional = optional
242
284
  self.description = description
243
285
  self.note = note
244
- self.default_value = default_value
286
+ self.default_value = convert_argument(default_value)
245
287
 
246
288
 
247
289
  def format(self, slot: int):
@@ -259,10 +301,19 @@ class parameter:
259
301
  formatted_dict['item']['data']['description'] = self.description
260
302
  if self.note:
261
303
  formatted_dict['item']['data']['note'] = self.note
262
- if self.default_value is not None and not self.plural and self.optional:
263
- formatted_dict['item']['data']['default_value'] = self.default_value.format(None)['item']
304
+ if self.default_value is not None:
305
+ if not self.optional:
306
+ warn(f'For parameter "{self.name}": Default value cannot be set if optional is False.')
307
+ elif self.plural:
308
+ warn(f'For parameter "{self.name}": Default value cannot be set while plural is True.')
309
+ else:
310
+ formatted_dict['item']['data']['default_value'] = self.default_value.format(None)['item']
264
311
 
265
312
  return formatted_dict
313
+
314
+ def __repr__(self) -> str:
315
+ raw_type = str(self.param_type).partition('.')[2]
316
+ return f'{self.__class__.__name__}({self.name}, type: {raw_type})'
266
317
 
267
318
 
268
319
  def _some_or(value: Any, none_value: Any):
@@ -274,7 +325,7 @@ def _some_or(value: Any, none_value: Any):
274
325
  return value
275
326
 
276
327
 
277
- def item_from_dict(item_dict: Dict) -> object:
328
+ def item_from_dict(item_dict: dict) -> object:
278
329
  item_id = item_dict['id']
279
330
  item_data = item_dict['data']
280
331
 
dfpyre/pyre.py CHANGED
@@ -4,25 +4,18 @@ A package for externally creating code templates for the DiamondFire Minecraft s
4
4
  By Amp
5
5
  """
6
6
 
7
- import base64
8
- import gzip
9
7
  import json
10
- import os
11
8
  from difflib import get_close_matches
12
9
  import datetime
13
- from typing import Tuple, List, Dict
10
+ from typing import Tuple
14
11
  from enum import Enum
15
12
  import socket
16
13
  from mcitemlib.itemlib import Item as NbtItem
14
+ from dfpyre.util import *
17
15
  from dfpyre.items import *
18
16
  from dfpyre.scriptgen import generate_script, GeneratorFlags
17
+ from dfpyre.actiondump import CODEBLOCK_DATA, get_default_tags
19
18
 
20
- COL_WARN = '\x1b[33m'
21
- COL_RESET = '\x1b[0m'
22
- COL_SUCCESS = '\x1b[32m'
23
- COL_ERROR = '\x1b[31m'
24
-
25
- CODEBLOCK_DATA_PATH = os.path.join(os.path.dirname(__file__), 'data/data.json')
26
19
 
27
20
  VARIABLE_TYPES = {'txt', 'comp', 'num', 'item', 'loc', 'var', 'snd', 'part', 'pot', 'g_val', 'vec', 'pn_el'}
28
21
  TEMPLATE_STARTERS = {'event', 'entity_event', 'func', 'process'}
@@ -31,9 +24,6 @@ SINGLE_NAME_CODEBLOCKS = {'func', 'process', 'call_func', 'start_process', 'else
31
24
  TARGETS = ['Selection', 'Default', 'Killer', 'Damager', 'Shooter', 'Victim', 'AllPlayers', 'Projectile', 'AllEntities', 'AllMobs', 'LastEntity']
32
25
  TARGET_CODEBLOCKS = {'player_action', 'entity_action', 'if_player', 'if_entity'}
33
26
 
34
- VAR_SHORTHAND_CHAR = '$'
35
- VAR_SCOPES = {'g': 'unsaved', 's': 'saved', 'l': 'local', 'i': 'line'}
36
-
37
27
  CODECLIENT_URL = 'ws://localhost:31375'
38
28
 
39
29
 
@@ -57,54 +47,59 @@ DEFAULT_TARGET = Target.SELECTION
57
47
 
58
48
 
59
49
  class CodeBlock:
60
- def __init__(self, name: str, args: Tuple=(), target: Target=DEFAULT_TARGET, data: Dict={}):
50
+ def __init__(self, name: str, args: Tuple=(), target: Target=DEFAULT_TARGET, data: dict={}, tags: dict[str, str]={}):
61
51
  self.name = name
62
52
  self.args = args
63
53
  self.target = target
64
54
  self.data = data
55
+ self.tags = tags
65
56
 
66
57
  def __repr__(self) -> str:
67
58
  if self.name in SINGLE_NAME_CODEBLOCKS:
68
59
  if self.name == 'else':
69
60
  return 'CodeBlock(else)'
70
61
  return f'CodeBlock({self.name}, {self.data["data"]})'
71
- elif 'block' in self.data:
62
+ if 'block' in self.data:
72
63
  return f'CodeBlock({self.data["block"]}, {self.name})'
73
64
  return f'CodeBlock(bracket, {self.data["type"]}, {self.data["direct"]})'
74
65
 
75
-
76
- def _warn(message):
77
- print(f'{COL_WARN}! WARNING ! {message}{COL_RESET}')
66
+ def build(self, include_tags: bool=True) -> dict:
67
+ """
68
+ Builds a properly formatted block from a CodeBlock object.
69
+ """
70
+ built_block = self.data.copy()
71
+ codeblock_type = self.data.get('block')
72
+
73
+ # add target if necessary ('Selection' is the default when 'target' is blank)
74
+ if codeblock_type in TARGET_CODEBLOCKS and self.target != DEFAULT_TARGET:
75
+ built_block['target'] = self.target.get_string_value()
76
+
77
+ # add items into args
78
+ final_args = [arg.format(slot) for slot, arg in enumerate(self.args) if arg.type in VARIABLE_TYPES]
79
+
80
+ # check for unrecognized name, add tags
81
+ if codeblock_type is not None: # for brackets
82
+ if self.name not in CODEBLOCK_DATA[codeblock_type]:
83
+ _warn_unrecognized_name(codeblock_type, self.name)
84
+ elif include_tags:
85
+ tags = _get_codeblock_tags(codeblock_type, self.name, self.tags)
86
+ if len(final_args) + len(tags) > 27:
87
+ final_args = final_args[:(27-len(tags))] # trim list if over 27 elements
88
+ final_args.extend(tags) # add tags to end
89
+
90
+ # if final_args:
91
+ built_block['args'] = {'items': final_args}
92
+ return built_block
78
93
 
79
94
 
80
95
  def _warn_unrecognized_name(codeblock_type: str, codeblock_name: str):
81
- close = get_close_matches(codeblock_name, TAGDATA[codeblock_type].keys())
96
+ close = get_close_matches(codeblock_name, CODEBLOCK_DATA[codeblock_type].keys())
82
97
  if close:
83
- _warn(f'Code block name "{codeblock_name}" not recognized. Did you mean "{close[0]}"?')
98
+ warn(f'Code block name "{codeblock_name}" not recognized. Did you mean "{close[0]}"?')
84
99
  else:
85
- _warn(f'Code block name "{codeblock_name}" not recognized. Try spell checking or retyping without spaces.')
100
+ warn(f'Code block name "{codeblock_name}" not recognized. Try spell checking or retyping without spaces.')
86
101
 
87
102
 
88
- def _load_codeblock_data() -> Tuple:
89
- tag_data = {}
90
- if os.path.exists(CODEBLOCK_DATA_PATH):
91
- with open(CODEBLOCK_DATA_PATH, 'r') as f:
92
- tag_data = json.load(f)
93
- else:
94
- _warn('data.json not found -- Item tags and error checking will not work.')
95
- return ({}, set(), set())
96
-
97
- del tag_data['meta']
98
-
99
- all_names = [x for l in [d.keys() for d in tag_data.values()] for x in l] # flatten list
100
- return (
101
- tag_data,
102
- set(tag_data['extras'].keys()),
103
- set(all_names)
104
- )
105
-
106
- TAGDATA, TAGDATA_EXTRAS_KEYS, ALL_CODEBLOCK_NAMES = _load_codeblock_data()
107
-
108
103
  def _add_inverted(data, inverted):
109
104
  """
110
105
  If inverted is true, add 'inverted': 'NOT' to data.
@@ -113,36 +108,48 @@ def _add_inverted(data, inverted):
113
108
  data['inverted'] = 'NOT'
114
109
 
115
110
 
116
- def _convert_data_types(args):
117
- converted_args = []
118
- for value in args:
119
- if type(value) in {int, float}:
120
- converted_args.append(num(value))
121
- elif type(value) is str:
122
- if len(value) > 2 and value[0] == VAR_SHORTHAND_CHAR and value[1] in VAR_SCOPES:
123
- var_object = var(value[2:], VAR_SCOPES[value[1]])
124
- converted_args.append(var_object)
125
- else:
126
- converted_args.append(text(value))
111
+ def _convert_args(args):
112
+ return tuple(map(convert_argument, args))
113
+
114
+
115
+ def _check_applied_tags(tags: list[dict], applied_tags: dict[str, str], codeblock_name: str) -> dict[str, str]:
116
+ if len(applied_tags) > 0 and len(tags) == 0:
117
+ warn(f'Action "{codeblock_name}" does not have any tags, but still received {len(applied_tags)}.')
118
+ return {}
119
+ valid_tags = {}
120
+ tags_formatted = {t['name']: t for t in tags}
121
+ for name, option in applied_tags.items():
122
+ if name not in tags_formatted:
123
+ tag_names_joined = '\n'.join(map(lambda s: ' - '+s, tags_formatted.keys()))
124
+ warn(f'Tag "{name}" does not exist for action "{codeblock_name}". Available tags:\n{tag_names_joined}')
125
+ elif option not in tags_formatted[name]['options']:
126
+ options_joined = '\n'.join(map(lambda s: ' - '+s, tags_formatted[name]['options']))
127
+ warn(f'Tag "{name}" does not have the option "{option}". Available tag options:\n{options_joined}')
127
128
  else:
128
- converted_args.append(value)
129
- return tuple(converted_args)
129
+ valid_tags[name] = option
130
+ return valid_tags
130
131
 
131
132
 
132
- def _reformat_codeblock_tags(tags, codeblock_type: str, codeblock_name: str):
133
+ def _reformat_codeblock_tags(tags: list[dict], codeblock_type: str, codeblock_name: str, applied_tags: dict[str, str]):
133
134
  """
134
- Turns data.json tag items into DiamondFire formatted tag items
135
+ Turns tag objects into DiamondFire formatted tag items
135
136
  """
137
+
138
+ valid_applied_tags = _check_applied_tags(tags, applied_tags, codeblock_name)
136
139
  reformatted_tags = []
137
140
  for tag_item in tags:
138
- action_value = codeblock_name if 'action' not in tag_item else tag_item['action']
141
+ tag_name = tag_item['name']
142
+ tag_option = tag_item['default']
143
+ if tag_name in valid_applied_tags:
144
+ tag_option = valid_applied_tags[tag_name]
145
+
139
146
  new_tag_item = {
140
147
  'item': {
141
148
  'id': 'bl_tag',
142
149
  'data': {
143
- 'option': tag_item['option'],
144
- 'tag': tag_item['tag'],
145
- 'action': action_value,
150
+ 'option': tag_option,
151
+ 'tag': tag_name,
152
+ 'action': codeblock_name,
146
153
  'block': codeblock_type
147
154
  }
148
155
  },
@@ -152,56 +159,15 @@ def _reformat_codeblock_tags(tags, codeblock_type: str, codeblock_name: str):
152
159
  return reformatted_tags
153
160
 
154
161
 
155
- def _get_codeblock_tags(codeblock_type: str, codeblock_name: str):
162
+ def _get_codeblock_tags(codeblock_type: str, codeblock_name: str, applied_tags: dict[str, str]):
156
163
  """
157
164
  Get tags for the specified codeblock type and name
158
165
  """
159
- tags = None
160
- if codeblock_type in TAGDATA_EXTRAS_KEYS:
161
- tags = TAGDATA['extras'][codeblock_type]
162
- else:
163
- tags = TAGDATA[codeblock_type].get(codeblock_name)
164
- return _reformat_codeblock_tags(tags, codeblock_type, codeblock_name)
165
-
166
-
167
- def _build_block(codeblock: CodeBlock, include_tags: bool):
168
- """
169
- Builds a properly formatted block from a CodeBlock object.
170
- """
171
- built_block = codeblock.data.copy()
172
- codeblock_type = codeblock.data.get('block')
173
-
174
- # add target if necessary ('Selection' is the default when 'target' is blank)
175
- if codeblock_type in TARGET_CODEBLOCKS and codeblock.target != DEFAULT_TARGET:
176
- built_block['target'] = codeblock.target.get_string_value()
177
-
178
- # add items into args
179
- final_args = [arg.format(slot) for slot, arg in enumerate(codeblock.args) if arg.type in VARIABLE_TYPES]
180
-
181
- # check for unrecognized name, add tags
182
- if codeblock_type is not None: # for brackets
183
- if codeblock_type not in TAGDATA_EXTRAS_KEYS and codeblock.name not in ALL_CODEBLOCK_NAMES:
184
- _warn_unrecognized_name(codeblock_type, codeblock.name)
185
- elif include_tags:
186
- tags = _get_codeblock_tags(codeblock_type, codeblock.name)
187
- if len(final_args) + len(tags) > 27:
188
- final_args = final_args[:(27-len(tags))] # trim list if over 27 elements
189
- final_args.extend(tags) # add tags to end
190
-
191
- built_block['args'] = {'items': final_args}
192
- return built_block
193
-
194
-
195
- def _df_encode(json_string: str) -> str:
196
- """
197
- Encodes a stringified json.
198
- """
199
- encoded_string = gzip.compress(json_string.encode('utf-8'))
200
- return base64.b64encode(encoded_string).decode('utf-8')
201
-
202
-
203
- def _df_decode(encoded_string: str) -> str:
204
- return gzip.decompress(base64.b64decode(encoded_string)).decode('utf-8')
166
+ action_data = CODEBLOCK_DATA[codeblock_type][codeblock_name]
167
+ if 'deprecatedNote' in action_data:
168
+ warn(f'Action "{codeblock_name}" is deprecated: {action_data["deprecatedNote"]}')
169
+ tags = action_data['tags']
170
+ return _reformat_codeblock_tags(tags, codeblock_type, codeblock_name, applied_tags)
205
171
 
206
172
 
207
173
  def _get_template_item(template_code: str, name: str, author: str) -> NbtItem:
@@ -231,7 +197,7 @@ class DFTemplate:
231
197
  Represents a DiamondFire code template.
232
198
  """
233
199
  def __init__(self, name: str=None, author: str='pyre'):
234
- self.codeblocks: List[CodeBlock] = []
200
+ self.codeblocks: list[CodeBlock] = []
235
201
  self.bracket_stack: list[str] = []
236
202
  self.name = name
237
203
  self.author = author
@@ -246,12 +212,16 @@ class DFTemplate:
246
212
  """
247
213
  Create a template object from an existing template code.
248
214
  """
249
- template_dict = json.loads(_df_decode(template_code))
215
+ template_dict = json.loads(df_decode(template_code))
250
216
  template = DFTemplate()
251
217
  for block_dict in template_dict['blocks']:
218
+ block_tags = get_default_tags(block_dict.get('block'), block_dict.get('action'))
252
219
  if 'args' in block_dict:
253
220
  args = []
254
221
  for item_dict in block_dict['args']['items']:
222
+ if item_dict['item'].get('id') == 'bl_tag':
223
+ tag_data = item_dict['item']['data']
224
+ block_tags[tag_data['tag']] = tag_data['option']
255
225
  parsed_item = item_from_dict(item_dict['item'])
256
226
  if parsed_item is not None:
257
227
  args.append(parsed_item)
@@ -262,7 +232,11 @@ class DFTemplate:
262
232
  codeblock_name = block_dict['block']
263
233
  elif 'action' in block_dict:
264
234
  codeblock_name = block_dict['action']
265
- codeblock = CodeBlock(codeblock_name, args, target, block_dict)
235
+
236
+ if codeblock_name == 'bracket' or block_dict['block'] == 'else':
237
+ codeblock = CodeBlock(codeblock_name, data=block_dict)
238
+ else:
239
+ codeblock = CodeBlock(codeblock_name, args, target, block_dict, tags=block_tags)
266
240
  template.codeblocks.append(codeblock)
267
241
 
268
242
  return template
@@ -296,16 +270,16 @@ class DFTemplate:
296
270
  :param bool include_tags: If True, include item tags in code blocks. Otherwise omit them.
297
271
  :return: String containing encoded template data.
298
272
  """
299
- template_dict_blocks = [_build_block(codeblock, include_tags) for codeblock in self.codeblocks]
273
+ template_dict_blocks = [codeblock.build(include_tags) for codeblock in self.codeblocks]
300
274
  template_dict = {'blocks': template_dict_blocks}
301
275
  first_block = template_dict_blocks[0]
302
276
  if first_block['block'] not in TEMPLATE_STARTERS:
303
- _warn('Template does not start with an event, function, or process.')
277
+ warn('Template does not start with an event, function, or process.')
304
278
 
305
279
  self._set_template_name(first_block)
306
280
 
307
281
  json_string = json.dumps(template_dict, separators=(',', ':'))
308
- return _df_encode(json_string)
282
+ return df_encode(json_string)
309
283
 
310
284
 
311
285
  def build_and_send(self, method: Literal['recode', 'codeclient'], include_tags: bool=True) -> int:
@@ -350,78 +324,79 @@ class DFTemplate:
350
324
  self._add_codeblock(cmd, index)
351
325
 
352
326
 
353
- def function(self, name: str, *args, index: int|None=None):
354
- args = _convert_data_types(args)
355
- cmd = CodeBlock('func', args, data={'id': 'block', 'block': 'func', 'data': name})
327
+ def function(self, name: str, *args, tags: dict[str, str]={}, index: int|None=None):
328
+ args = _convert_args(args)
329
+ cmd = CodeBlock('dynamic', args, data={'id': 'block', 'block': 'func', 'data': name}, tags=tags)
356
330
  self._add_codeblock(cmd, index)
357
331
 
358
332
 
359
- def process(self, name: str, *args, index: int|None=None):
360
- args = _convert_data_types(args)
361
- cmd = CodeBlock('process', args, data={'id': 'block', 'block': 'process', 'data': name})
333
+ def process(self, name: str, *args, tags: dict[str, str]={}, index: int|None=None):
334
+ args = _convert_args(args)
335
+ cmd = CodeBlock('dynamic', args, data={'id': 'block', 'block': 'process', 'data': name}, tags=tags)
362
336
  self._add_codeblock(cmd, index)
363
337
 
364
338
 
365
339
  def call_function(self, name: str, *args, index: int|None=None):
366
- args = _convert_data_types(args)
367
- cmd = CodeBlock('call_func', args, data={'id': 'block', 'block': 'call_func', 'data': name})
340
+ args = _convert_args(args)
341
+ cmd = CodeBlock('dynamic', args, data={'id': 'block', 'block': 'call_func', 'data': name})
368
342
  self._add_codeblock(cmd, index)
369
343
 
370
- def start_process(self, name: str, index: int|None=None):
371
- cmd = CodeBlock('start_process', data={'id': 'block', 'block': 'start_process', 'data': name})
344
+
345
+ def start_process(self, name: str, tags: dict[str, str]={}, index: int|None=None):
346
+ cmd = CodeBlock('dynamic', data={'id': 'block', 'block': 'start_process', 'data': name}, tags=tags)
372
347
  self._add_codeblock(cmd, index)
373
348
 
374
349
 
375
- def player_action(self, name: str, *args, target: Target=DEFAULT_TARGET, index: int|None=None):
376
- args = _convert_data_types(args)
377
- cmd = CodeBlock(name, args, target=target, data={'id': 'block', 'block': 'player_action', 'action': name})
350
+ def player_action(self, name: str, *args, target: Target=DEFAULT_TARGET, tags: dict[str, str]={}, index: int|None=None):
351
+ args = _convert_args(args)
352
+ cmd = CodeBlock(name, args, target=target, data={'id': 'block', 'block': 'player_action', 'action': name}, tags=tags)
378
353
  self._add_codeblock(cmd, index)
379
354
 
380
355
 
381
- def game_action(self, name: str, *args, index: int|None=None):
382
- args = _convert_data_types(args)
383
- cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'game_action', 'action': name})
356
+ def game_action(self, name: str, *args, tags: dict[str, str]={}, index: int|None=None):
357
+ args = _convert_args(args)
358
+ cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'game_action', 'action': name}, tags=tags)
384
359
  self._add_codeblock(cmd, index)
385
360
 
386
361
 
387
- def entity_action(self, name: str, *args, target: Target=DEFAULT_TARGET, index: int|None=None):
388
- args = _convert_data_types(args)
389
- cmd = CodeBlock(name, args, target=target, data={'id': 'block', 'block': 'entity_action', 'action': name})
362
+ def entity_action(self, name: str, *args, target: Target=DEFAULT_TARGET, tags: dict[str, str]={}, index: int|None=None):
363
+ args = _convert_args(args)
364
+ cmd = CodeBlock(name, args, target=target, data={'id': 'block', 'block': 'entity_action', 'action': name}, tags=tags)
390
365
  self._add_codeblock(cmd, index)
391
366
 
392
367
 
393
- def if_player(self, name: str, *args, target: Target=DEFAULT_TARGET, inverted: bool=False, index: int|None=None):
394
- args = _convert_data_types(args)
368
+ def if_player(self, name: str, *args, target: Target=DEFAULT_TARGET, tags: dict[str, str]={}, inverted: bool=False, index: int|None=None):
369
+ args = _convert_args(args)
395
370
  data = {'id': 'block', 'block': 'if_player', 'action': name}
396
371
  _add_inverted(data, inverted)
397
- cmd = CodeBlock(name, args, target=target, data=data)
372
+ cmd = CodeBlock(name, args, target=target, data=data, tags=tags)
398
373
  self._add_codeblock(cmd, index)
399
374
  self._openbracket(index)
400
375
 
401
376
 
402
- def if_variable(self, name: str, *args, inverted: bool=False, index: int|None=None):
403
- args = _convert_data_types(args)
377
+ def if_variable(self, name: str, *args, tags: dict[str, str]={}, inverted: bool=False, index: int|None=None):
378
+ args = _convert_args(args)
404
379
  data = {'id': 'block', 'block': 'if_var', 'action': name}
405
380
  _add_inverted(data, inverted)
406
- cmd = CodeBlock(name, args, data=data)
381
+ cmd = CodeBlock(name, args, data=data, tags=tags)
407
382
  self._add_codeblock(cmd, index)
408
383
  self._openbracket(index)
409
384
 
410
385
 
411
- def if_game(self, name: str, *args, inverted: bool=False, index: int|None=None):
412
- args = _convert_data_types(args)
386
+ def if_game(self, name: str, *args, tags: dict[str, str]={}, inverted: bool=False, index: int|None=None):
387
+ args = _convert_args(args)
413
388
  data = {'id': 'block', 'block': 'if_game', 'action': name}
414
389
  _add_inverted(data, inverted)
415
- cmd = CodeBlock(name, args, data=data)
390
+ cmd = CodeBlock(name, args, data=data, tags=tags)
416
391
  self._add_codeblock(cmd, index)
417
392
  self._openbracket(index)
418
393
 
419
394
 
420
- def if_entity(self, name: str, *args, target: Target=DEFAULT_TARGET, inverted: bool=False, index: int|None=None):
421
- args = _convert_data_types(args)
395
+ def if_entity(self, name: str, *args, target: Target=DEFAULT_TARGET, tags: dict[str, str]={}, inverted: bool=False, index: int|None=None):
396
+ args = _convert_args(args)
422
397
  data = {'id': 'block', 'block': 'if_entity', 'action': name}
423
398
  _add_inverted(data, inverted)
424
- cmd = CodeBlock(name, args, target=target, data=data)
399
+ cmd = CodeBlock(name, args, target=target, data=data, tags=tags)
425
400
  self._add_codeblock(cmd, index)
426
401
  self._openbracket(index)
427
402
 
@@ -432,37 +407,37 @@ class DFTemplate:
432
407
  self._openbracket(index)
433
408
 
434
409
 
435
- def repeat(self, name: str, *args, sub_action: str=None, index: int|None=None):
436
- args = _convert_data_types(args)
410
+ def repeat(self, name: str, *args, tags: dict[str, str]={}, sub_action: str=None, index: int|None=None):
411
+ args = _convert_args(args)
437
412
  data = {'id': 'block', 'block': 'repeat', 'action': name}
438
413
  if sub_action is not None:
439
414
  data['subAction'] = sub_action
440
- cmd = CodeBlock(name, args, data=data)
415
+ cmd = CodeBlock(name, args, data=data, tags=tags)
441
416
  self._add_codeblock(cmd, index)
442
417
  self._openbracket(index, 'repeat')
443
418
 
444
419
 
445
420
  def bracket(self, *args, index: int|None=None):
446
- args = _convert_data_types(args)
421
+ args = _convert_args(args)
447
422
  cmd = CodeBlock('bracket', data={'id': 'bracket', 'direct': 'close', 'type': self.bracket_stack.pop()})
448
423
  self._add_codeblock(cmd, index)
449
424
 
450
425
 
451
- def control(self, name: str, *args, index: int|None=None):
452
- args = _convert_data_types(args)
453
- cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'control', 'action': name})
426
+ def control(self, name: str, *args, tags: dict[str, str]={}, index: int|None=None):
427
+ args = _convert_args(args)
428
+ cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'control', 'action': name}, tags=tags)
454
429
  self._add_codeblock(cmd, index)
455
430
 
456
431
 
457
- def select_object(self, name: str, *args, index: int|None=None):
458
- args = _convert_data_types(args)
459
- cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'select_obj', 'action': name})
432
+ def select_object(self, name: str, *args, tags: dict[str, str]={}, index: int|None=None):
433
+ args = _convert_args(args)
434
+ cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'select_obj', 'action': name}, tags=tags)
460
435
  self._add_codeblock(cmd, index)
461
436
 
462
437
 
463
- def set_variable(self, name: str, *args, index: int|None=None):
464
- args = _convert_data_types(args)
465
- cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'set_var', 'action': name})
438
+ def set_variable(self, name: str, *args, tags: dict[str, str]={}, index: int|None=None):
439
+ args = _convert_args(args)
440
+ cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'set_var', 'action': name}, tags=tags)
466
441
  self._add_codeblock(cmd, index)
467
442
 
468
443
 
@@ -476,6 +451,6 @@ class DFTemplate:
476
451
  :param bool var_shorthand: If True, all variables will be written using variable shorthand.
477
452
  """
478
453
  flags = GeneratorFlags(indent_size, literal_shorthand, var_shorthand)
479
- with open(output_path, 'w') as f:
454
+ with open(output_path, 'w', encoding='utf-8') as f:
480
455
  f.write(generate_script(self, flags))
481
456