dfpyre 0.4.6__py3-none-any.whl → 0.8.1__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/pyre.py CHANGED
@@ -1,40 +1,27 @@
1
1
  """
2
- A package for externally creating code templates for the DiamondFire Minecraft server.
2
+ A package for making code templates for the DiamondFire Minecraft server.
3
3
 
4
4
  By Amp
5
- 2/24/2024
6
5
  """
7
6
 
8
- import base64
9
- import gzip
10
- import socket
11
- import websocket
12
- import time
13
7
  import json
14
- import os
15
8
  from difflib import get_close_matches
16
9
  import datetime
17
- from typing import Tuple, List, Dict
18
10
  from enum import Enum
19
11
  from mcitemlib.itemlib import Item as NbtItem
12
+ from dfpyre.util import *
20
13
  from dfpyre.items import *
14
+ from dfpyre.scriptgen import generate_script, GeneratorFlags
15
+ from dfpyre.actiondump import CODEBLOCK_DATA, get_default_tags
21
16
 
22
- COL_WARN = '\x1b[33m'
23
- COL_RESET = '\x1b[0m'
24
- COL_SUCCESS = '\x1b[32m'
25
- COL_ERROR = '\x1b[31m'
26
-
27
- CODEBLOCK_DATA_PATH = os.path.join(os.path.dirname(__file__), 'data/data.json')
28
17
 
29
18
  VARIABLE_TYPES = {'txt', 'comp', 'num', 'item', 'loc', 'var', 'snd', 'part', 'pot', 'g_val', 'vec', 'pn_el'}
30
19
  TEMPLATE_STARTERS = {'event', 'entity_event', 'func', 'process'}
20
+ DYNAMIC_CODEBLOCKS = {'func', 'process', 'call_func', 'start_process'}
31
21
 
32
22
  TARGETS = ['Selection', 'Default', 'Killer', 'Damager', 'Shooter', 'Victim', 'AllPlayers', 'Projectile', 'AllEntities', 'AllMobs', 'LastEntity']
33
23
  TARGET_CODEBLOCKS = {'player_action', 'entity_action', 'if_player', 'if_entity'}
34
24
 
35
- VAR_SHORTHAND_CHAR = '$'
36
- VAR_SCOPES = {'g': 'unsaved', 's': 'saved', 'l': 'local', 'i': 'line'}
37
-
38
25
  CODECLIENT_URL = 'ws://localhost:31375'
39
26
 
40
27
 
@@ -51,153 +38,171 @@ class Target(Enum):
51
38
  ALL_MOBS = 9
52
39
  LAST_ENTITY = 10
53
40
 
54
- def getStringValue(self):
41
+ def get_string_value(self):
55
42
  return TARGETS[self.value]
56
43
 
57
44
  DEFAULT_TARGET = Target.SELECTION
58
45
 
59
46
 
47
+ def _convert_args(args):
48
+ return tuple(map(convert_argument, args))
49
+
50
+
60
51
  class CodeBlock:
61
- def __init__(self, name: str, args: Tuple=(), target: Target=DEFAULT_TARGET, data: Dict={}):
62
- self.name = name
52
+ def __init__(self, codeblock_type: str, action_name: str, args: tuple=(), target: Target=DEFAULT_TARGET, data: dict={}, tags: dict[str, str]={}):
53
+ self.type = codeblock_type
54
+ self.action_name = action_name
63
55
  self.args = args
64
56
  self.target = target
65
57
  self.data = data
58
+ self.tags = tags
59
+
66
60
 
67
-
68
- def _warn(message):
69
- print(f'{COL_WARN}! WARNING ! {message}{COL_RESET}')
70
-
71
-
72
- def _warnUnrecognizedName(codeblockType: str, codeblockName: str):
73
- close = get_close_matches(codeblockName, TAGDATA[codeblockType].keys())
61
+ @classmethod
62
+ def new_action(cls, codeblock_type: str, action_name: str, args: tuple, tags: dict[str, str], target: Target=DEFAULT_TARGET) -> "CodeBlock":
63
+ args = _convert_args(args)
64
+ return cls(codeblock_type, action_name, args=args, data={'id': 'block', 'block': codeblock_type, 'action': action_name}, tags=tags, target=target)
65
+
66
+ @classmethod
67
+ def new_data(cls, codeblock_type: str, data_value: str, args: tuple, tags: dict[str, str]) -> "CodeBlock":
68
+ args = _convert_args(args)
69
+ return cls(codeblock_type, 'dynamic', args=args, data={'id': 'block', 'block': codeblock_type, 'data': data_value}, tags=tags)
70
+
71
+ @classmethod
72
+ def new_conditional(cls, codeblock_type: str, action_name: str, args: tuple, tags: dict[str, str], inverted: bool, target: Target=DEFAULT_TARGET) -> "CodeBlock":
73
+ args = _convert_args(args)
74
+ data = {'id': 'block', 'block': codeblock_type, 'action': action_name}
75
+ if inverted:
76
+ data['attribute'] = 'NOT'
77
+ return cls(codeblock_type, action_name, args=args, data=data, tags=tags, target=target)
78
+
79
+ @classmethod
80
+ def new_repeat(cls, action_name: str, args: tuple, tags: dict[str, str], sub_action: str|None, inverted: bool) -> "CodeBlock":
81
+ args = _convert_args(args)
82
+ data = {'id': 'block', 'block': 'repeat', 'action': action_name}
83
+ if inverted:
84
+ data['attribute'] = 'NOT'
85
+ if sub_action is not None:
86
+ data['subAction'] = sub_action
87
+ return cls('repeat', action_name, args=args, data=data, tags=tags)
88
+
89
+ @classmethod
90
+ def new_else(cls) -> "CodeBlock":
91
+ return cls('else', 'else', data={'id': 'block', 'block': 'else'})
92
+
93
+ @classmethod
94
+ def new_bracket(cls, direction: Literal['open', 'close'], bracket_type: Literal['norm', 'repeat']) -> "CodeBlock":
95
+ return cls('bracket', 'bracket', data={'id': 'bracket', 'direct': direction, 'type': bracket_type})
96
+
97
+
98
+ def __repr__(self) -> str:
99
+ if self.action_name == 'dynamic':
100
+ return f'CodeBlock({self.data["block"]}, {self.data["data"]})'
101
+ if self.action_name == 'else':
102
+ return 'CodeBlock(else)'
103
+ if 'block' in self.data:
104
+ return f'CodeBlock({self.data["block"]}, {self.action_name})'
105
+ return f'CodeBlock(bracket, {self.data["type"]}, {self.data["direct"]})'
106
+
107
+
108
+ def build(self, include_tags: bool=True) -> dict:
109
+ """
110
+ Builds a properly formatted block from a CodeBlock object.
111
+ """
112
+ built_block = self.data.copy()
113
+
114
+ # add target if necessary ('Selection' is the default when 'target' is blank)
115
+ if self.type in TARGET_CODEBLOCKS and self.target != DEFAULT_TARGET:
116
+ built_block['target'] = self.target.get_string_value()
117
+
118
+ # add items into args
119
+ final_args = [arg.format(slot) for slot, arg in enumerate(self.args) if arg.type in VARIABLE_TYPES]
120
+
121
+ # check for unrecognized name, add tags
122
+ if self.type not in {'bracket', 'else'}:
123
+ if self.action_name not in CODEBLOCK_DATA[self.type]:
124
+ _warn_unrecognized_name(self.type, self.action_name)
125
+ elif include_tags:
126
+ tags = _get_codeblock_tags(self.type, self.action_name, self.tags)
127
+ if len(final_args) + len(tags) > 27:
128
+ final_args = final_args[:(27-len(tags))] # trim list if over 27 elements
129
+ final_args.extend(tags) # add tags to end
130
+
131
+ built_block['args'] = {'items': final_args}
132
+ return built_block
133
+
134
+
135
+ def _warn_unrecognized_name(codeblock_type: str, codeblock_name: str):
136
+ close = get_close_matches(codeblock_name, CODEBLOCK_DATA[codeblock_type].keys())
74
137
  if close:
75
- _warn(f'Code block name "{codeblockName}" not recognized. Did you mean "{close[0]}"?')
138
+ warn(f'Code block name "{codeblock_name}" not recognized. Did you mean "{close[0]}"?')
76
139
  else:
77
- _warn(f'Code block name "{codeblockName}" not recognized. Try spell checking or retyping without spaces.')
140
+ warn(f'Code block name "{codeblock_name}" not recognized. Try spell checking or retyping without spaces.')
78
141
 
79
142
 
80
- def _loadCodeblockData() -> Tuple:
81
- tagData = {}
82
- if os.path.exists(CODEBLOCK_DATA_PATH):
83
- with open(CODEBLOCK_DATA_PATH, 'r') as f:
84
- tagData = json.load(f)
85
- else:
86
- _warn('data.json not found -- Item tags and error checking will not work.')
87
- return ({}, set(), set())
143
+ def _check_applied_tags(tags: list[dict], applied_tags: dict[str, str], codeblock_name: str) -> dict[str, str]:
144
+ if len(applied_tags) > 0 and len(tags) == 0:
145
+ warn(f'Action "{codeblock_name}" does not have any tags, but still received {len(applied_tags)}.')
146
+ return {}
88
147
 
89
- del tagData['meta']
90
-
91
- allNames = [x for l in [d.keys() for d in tagData.values()] for x in l] # flatten list
92
- return (
93
- tagData,
94
- set(tagData['extras'].keys()),
95
- set(allNames)
96
- )
97
-
98
- TAGDATA, TAGDATA_EXTRAS_KEYS, ALL_CODEBLOCK_NAMES = _loadCodeblockData()
99
-
100
- def _addInverted(data, inverted):
101
- """
102
- If inverted is true, add 'inverted': 'NOT' to data.
103
- """
104
- if inverted:
105
- data['inverted'] = 'NOT'
106
-
107
-
108
- def _convertDataTypes(args):
109
- convertedArgs = []
110
- for value in args:
111
- if type(value) in {int, float}:
112
- convertedArgs.append(num(value))
113
- elif type(value) is str:
114
- if value[0] == VAR_SHORTHAND_CHAR and value[1] in VAR_SCOPES:
115
- varObject = var(value[2:], VAR_SCOPES[value[1]])
116
- convertedArgs.append(varObject)
117
- else:
118
- convertedArgs.append(text(value))
148
+ valid_tags = {}
149
+ tags_formatted = {t['name']: t for t in tags}
150
+ for name, option in applied_tags.items():
151
+ if name not in tags_formatted:
152
+ tag_names_joined = '\n'.join(map(lambda s: ' - '+s, tags_formatted.keys()))
153
+ warn(f'Tag "{name}" does not exist for action "{codeblock_name}". Available tags:\n{tag_names_joined}')
154
+ elif option not in tags_formatted[name]['options']:
155
+ options_joined = '\n'.join(map(lambda s: ' - '+s, tags_formatted[name]['options']))
156
+ warn(f'Tag "{name}" does not have the option "{option}". Available tag options:\n{options_joined}')
119
157
  else:
120
- convertedArgs.append(value)
121
- return tuple(convertedArgs)
158
+ valid_tags[name] = option
159
+ return valid_tags
122
160
 
123
161
 
124
- def _reformatCodeblockTags(tags, codeblockType: str, codeblockName: str):
162
+ def _reformat_codeblock_tags(tags: list[dict], codeblock_type: str, codeblock_action: str, applied_tags: dict[str, str]):
125
163
  """
126
- Turns data.json tag items into DiamondFire formatted tag items
164
+ Turns tag objects into DiamondFire formatted tag items
127
165
  """
128
- reformattedTags = []
129
- for tagItem in tags:
130
- actionValue = codeblockName if 'action' not in tagItem else tagItem['action']
131
- newTagItem = {
166
+
167
+ def format_tag(option: str, name: str):
168
+ return {
132
169
  'item': {
133
170
  'id': 'bl_tag',
134
- 'data': {
135
- 'option': tagItem['option'],
136
- 'tag': tagItem['tag'],
137
- 'action': actionValue,
138
- 'block': codeblockType
139
- }
171
+ 'data': {'option': option, 'tag': name, 'action': codeblock_action, 'block': codeblock_type}
140
172
  },
141
- 'slot': tagItem['slot']
173
+ 'slot': tag_item['slot']
142
174
  }
143
- reformattedTags.append(newTagItem)
144
- return reformattedTags
145
-
146
-
147
- def _getCodeblockTags(codeblockType: str, codeblockName: str):
148
- """
149
- Get tags for the specified codeblock type and name
150
- """
151
- tags = None
152
- if codeblockType in TAGDATA_EXTRAS_KEYS:
153
- tags = TAGDATA['extras'][codeblockType]
154
- else:
155
- tags = TAGDATA[codeblockType].get(codeblockName)
156
- return _reformatCodeblockTags(tags, codeblockType, codeblockName)
157
-
158
-
159
- def _buildBlock(codeblock: CodeBlock, includeTags: bool):
160
- """
161
- Builds a properly formatted block from a CodeBlock object.
162
- """
163
- finalBlock = codeblock.data.copy()
164
- codeblockType = codeblock.data.get('block')
165
-
166
- # add target if necessary ('Selection' is the default when 'target' is blank)
167
- if codeblockType in TARGET_CODEBLOCKS and codeblock.target != DEFAULT_TARGET:
168
- finalBlock['target'] = codeblock.target.getStringValue()
169
175
 
170
- # add items into args
171
- finalArgs = [arg.format(slot) for slot, arg in enumerate(codeblock.args) if arg.type in VARIABLE_TYPES]
172
-
173
- # check for unrecognized name, add tags
174
- if codeblockType is not None: # for brackets
175
- if codeblockType not in TAGDATA_EXTRAS_KEYS and codeblock.name not in ALL_CODEBLOCK_NAMES:
176
- _warnUnrecognizedName(codeblockType, codeblock.name)
177
- elif includeTags:
178
- tags = _getCodeblockTags(codeblockType, codeblock.name)
179
- if len(finalArgs) + len(tags) > 27:
180
- finalArgs = finalArgs[:(27-len(tags))] # trim list if over 27 elements
181
- finalArgs.extend(tags) # add tags to end
182
-
183
- finalBlock['args'] = {'items': finalArgs}
184
- return finalBlock
176
+ valid_applied_tags = _check_applied_tags(tags, applied_tags, codeblock_action)
177
+ reformatted_tags = []
178
+ for tag_item in tags:
179
+ tag_name = tag_item['name']
180
+ tag_option = tag_item['default']
181
+ if tag_name in valid_applied_tags:
182
+ tag_option = valid_applied_tags[tag_name]
185
183
 
184
+ new_tag_item = format_tag(tag_option, tag_name)
185
+ reformatted_tags.append(new_tag_item)
186
+ return reformatted_tags
186
187
 
187
- def _dfEncode(jsonString: str) -> str:
188
+
189
+ def _get_codeblock_tags(codeblock_type: str, codeblock_name: str, applied_tags: dict[str, str]):
188
190
  """
189
- Encodes a stringified json.
191
+ Get tags for the specified codeblock type and name
190
192
  """
191
- encodedString = gzip.compress(jsonString.encode('utf-8'))
192
- return base64.b64encode(encodedString).decode('utf-8')
193
+ action_data = CODEBLOCK_DATA[codeblock_type][codeblock_name]
194
+ if 'deprecatedNote' in action_data:
195
+ warn(f'Action "{codeblock_name}" is deprecated: {action_data["deprecatedNote"]}')
196
+ tags = action_data['tags']
197
+ return _reformat_codeblock_tags(tags, codeblock_type, codeblock_name, applied_tags)
193
198
 
194
199
 
195
- def getTemplateItem(templateCode: str, name: str, author: str) -> NbtItem:
200
+ def _get_template_item(template_code: str, name: str, author: str) -> NbtItem:
196
201
  now = datetime.datetime.now()
197
202
 
198
- templateItem = NbtItem('yellow_shulker_box')
199
- templateItem.set_name(f'&x&f&f&5&c&0&0>> &x&f&f&c&7&0&0{name}')
200
- templateItem.set_lore([
203
+ template_item = NbtItem('yellow_shulker_box')
204
+ template_item.set_name(f'&x&f&f&5&c&0&0>> &x&f&f&c&7&0&0{name}')
205
+ template_item.set_lore([
201
206
  f'&8Author: {author}',
202
207
  f'&8Date: {now.strftime("%Y-%m-%d")}',
203
208
  '',
@@ -205,275 +210,386 @@ def getTemplateItem(templateCode: str, name: str, author: str) -> NbtItem:
205
210
  '&7https://github.com/Amp63/pyre'
206
211
  ])
207
212
 
208
- pbvTag = {
209
- 'hypercube:codetemplatedata': f'{{"author":"{author}","name":"{name}","version": 1,"code":"{templateCode}"}}',
213
+ pbv_tag = {
214
+ 'hypercube:codetemplatedata': f'{{"author":"{author}","name":"{name}","version": 1,"code":"{template_code}"}}',
210
215
  'hypercube:pyre_creation_timestamp': now.timestamp()
211
216
  }
212
- templateItem.set_tag('PublicBukkitValues', pbvTag, raw=True)
217
+ template_item.set_custom_data('PublicBukkitValues', pbv_tag, raw=True)
213
218
 
214
- return templateItem
219
+ return template_item
215
220
 
216
221
 
217
- def sendRecode(templateCode: str, name: str='Unnamed Template', author: str='pyre') -> int:
222
+ class DFTemplate:
218
223
  """
219
- Sends a template to DiamondFire via recode item api.
220
-
221
- :param str templateCode: The code for the template as a base64 string.
222
- :param str name: The name of the template.
223
- :param str author: The author of the template.
224
-
225
- :return: status code
226
- - `0` = Success
227
- - `1` = Connection refused
228
- - `2` = Other socket error
224
+ Represents a DiamondFire code template.
229
225
  """
226
+ def __init__(self, codeblocks: list[CodeBlock], author: str='pyre'):
227
+ self.codeblocks = codeblocks
228
+ self.author = author
230
229
 
231
- templateItem = getTemplateItem(templateCode, name, author)
232
- data = {'type': 'nbt', 'source': f'pyre Template - {name}', 'data': templateItem.get_nbt()}
233
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
234
- try:
235
- s.connect(('127.0.0.1', 31372))
236
- except ConnectionRefusedError:
237
- print(f"""{COL_ERROR}Could not connect to recode item API. Possible problems:
238
- - Minecraft is not open
239
- - Recode is not installed (get it here: https://modrinth.com/mod/recode or join the discord here: https://discord.gg/GWxWtcwA2C){COL_RESET}""")
240
- s.close()
241
- return 1
242
-
243
- s.send((str(data) + '\n').encode('utf-8'))
244
- received = json.loads(s.recv(1024).decode())
245
- status = received['status']
246
- s.close()
247
- time.sleep(0.5)
248
-
249
- if status == 'success':
250
- print(f'{COL_SUCCESS}Template sent to client successfully.{COL_RESET}')
251
- return 0
252
- error = received['error']
253
- print(f'{COL_ERROR}Error sending template: {error}{COL_RESET}')
254
- return 2
255
-
256
-
257
- def sendCodeClient(templateCode: str, name: str='Unnamed Template', author: str='pyre') -> int:
258
- try:
259
- ws = websocket.WebSocket()
260
- ws.connect(CODECLIENT_URL)
261
- print(f'{COL_SUCCESS}Connected. {COL_WARN}Please run /auth in game.{COL_RESET}')
262
-
263
- ws.recv() # auth response
264
230
 
265
- templateItem = getTemplateItem(templateCode, name, author)
266
- command = f'give {templateItem.get_nbt()}'
267
- ws.send(command)
268
- ws.close()
231
+ def _get_template_name(self):
232
+ first_block_data = self.codeblocks[0].data
233
+ if 'data' in first_block_data:
234
+ name = first_block_data['data']
235
+ return name if name else 'Unnamed Template'
236
+ return first_block_data['block'] + '_' + first_block_data['action']
269
237
 
270
- print(f'{COL_SUCCESS}Template sent to client successfully.{COL_RESET}')
271
- return 0
272
-
273
- except Exception as e:
274
- if isinstance(e, ConnectionRefusedError):
275
- print(f'{COL_ERROR}Could not connect to CodeClient API. Possible problems:')
276
- print(f' - Minecraft is not open')
277
- print(f' - CodeClient is not installed (get it here: https://modrinth.com/mod/codeclient)')
278
- print(f' - CodeClient API is not enabled (enable it in CodeClient general settings)')
279
- return 1
280
-
281
- print(f'Connection failed: {e}')
282
- return 2
283
238
 
239
+ def __repr__(self) -> str:
240
+ return f'DFTemplate(name: "{self._get_template_name()}", author: "{self.author}", codeblocks: {len(self.codeblocks)})'
284
241
 
285
- class DFTemplate:
286
- """
287
- Represents a DiamondFire code template.
288
- """
289
- def __init__(self, name: str=None, author: str='pyre'):
290
- self.codeBlocks: List[CodeBlock] = []
291
- self.closebracket = None
292
- self.name = name
293
- self.author = author
294
242
 
243
+ @staticmethod
244
+ def from_code(template_code: str):
245
+ """
246
+ Create a template object from an existing template code.
247
+ """
248
+ template_dict = json.loads(df_decode(template_code))
249
+ codeblocks: list[CodeBlock] = []
250
+ for block_dict in template_dict['blocks']:
251
+ block_tags = get_default_tags(block_dict.get('block'), block_dict.get('action'))
252
+ if 'args' in block_dict:
253
+ block_args = []
254
+ for item_dict in block_dict['args']['items']:
255
+ if item_dict['item'].get('id') == 'bl_tag':
256
+ tag_data = item_dict['item']['data']
257
+ block_tags[tag_data['tag']] = tag_data['option']
258
+ parsed_item = item_from_dict(item_dict['item'])
259
+ if parsed_item is not None:
260
+ block_args.append(parsed_item)
261
+ block_target = Target(TARGETS.index(block_dict['target'])) if 'target' in block_dict else DEFAULT_TARGET
262
+
263
+ codeblock_type = block_dict.get('block')
264
+
265
+ if codeblock_type is None:
266
+ codeblock = CodeBlock.new_bracket(block_dict['direct'], block_dict['type'])
267
+ if codeblock_type == 'else':
268
+ codeblock = CodeBlock.new_else()
269
+ elif codeblock_type in DYNAMIC_CODEBLOCKS:
270
+ codeblock = CodeBlock.new_data(codeblock_type, block_dict['data'], block_args, block_tags)
271
+ elif 'action' in block_dict:
272
+ codeblock = CodeBlock.new_action(codeblock_type, block_dict['action'], block_args, block_tags, block_target)
273
+ codeblocks.append(codeblock)
274
+
275
+ return DFTemplate(codeblocks)
295
276
 
296
- def _setTemplateName(self, firstBlock):
297
- if self.name is not None:
298
- return
299
- if 'data' in firstBlock:
300
- self.name = firstBlock['data']
301
- if not self.name:
302
- self.name = 'Unnamed Template'
277
+
278
+ def insert(self, insert_codeblocks: CodeBlock|list[CodeBlock], index: int=-1) -> "DFTemplate":
279
+ """
280
+ Insert `insert_codeblocks` into this template at `index`.
281
+
282
+ :param CodeBlock|list[CodeBlock] insert_codeblocks: The block(s) to insert
283
+ :param int index: The index to insert at.
284
+ :return: self
285
+ """
286
+ if isinstance(insert_codeblocks, list):
287
+ insert_codeblocks = list(flatten(insert_codeblocks))
288
+ if index == -1:
289
+ self.codeblocks.extend(insert_codeblocks)
290
+ else:
291
+ self.codeblocks[index:index+len(insert_codeblocks)] = insert_codeblocks
292
+ elif isinstance(insert_codeblocks, CodeBlock):
293
+ self.codeblocks.insert(index, insert_codeblocks)
303
294
  else:
304
- self.name = firstBlock['block'] + '_' + firstBlock['action']
295
+ raise PyreException('Expected CodeBlock or list[CodeBlock] to insert.')
296
+ return self
305
297
 
306
298
 
307
- def build(self, includeTags: bool=True) -> str:
299
+ def build(self, include_tags: bool=True) -> str:
308
300
  """
309
301
  Build this template.
310
302
 
311
- :param bool includeTags: If True, include item tags in code blocks. Otherwise omit them.
303
+ :param bool include_tags: If True, include item tags in code blocks. Otherwise omit them.
312
304
  :return: String containing encoded template data.
313
305
  """
314
- templateDictBlocks = [_buildBlock(codeblock, includeTags) for codeblock in self.codeBlocks]
315
- templateDict = {'blocks': templateDictBlocks}
316
- firstBlock = templateDictBlocks[0]
317
- if firstBlock['block'] not in TEMPLATE_STARTERS:
318
- _warn('Template does not start with an event, function, or process.')
319
-
320
- self._setTemplateName(firstBlock)
321
-
322
- print(f'{COL_SUCCESS}Template built successfully.{COL_RESET}')
323
-
324
- jsonString = json.dumps(templateDict, separators=(',', ':'))
325
- return _dfEncode(jsonString)
306
+ template_dict_blocks = [codeblock.build(include_tags) for codeblock in self.codeblocks]
307
+ template_dict = {'blocks': template_dict_blocks}
308
+ first_block = template_dict_blocks[0]
309
+ if first_block['block'] not in TEMPLATE_STARTERS:
310
+ warn('Template does not start with an event, function, or process.')
311
+
312
+ json_string = json.dumps(template_dict, separators=(',', ':'))
313
+ return df_encode(json_string)
326
314
 
327
315
 
328
- def buildAndSend(self, sendMethod: Literal['recode', 'codeclient']='recode', includeTags: bool=True) -> int:
316
+ def build_and_send(self, method: Literal['recode', 'codeclient'], include_tags: bool=True) -> int:
329
317
  """
330
318
  Builds this template and sends it to DiamondFire automatically.
331
319
 
332
- :param bool includeTags: If True, include item tags in code blocks. Otherwise omit them.
320
+ :param bool include_tags: If True, include item tags in code blocks. Otherwise omit them.
333
321
  """
334
- templateCode = self.build(includeTags)
335
- if sendMethod == 'recode':
336
- return sendRecode(templateCode, name=self.name, author=self.author)
337
- if sendMethod == 'codeclient':
338
- return sendCodeClient(templateCode, name=self.name, author=self.author)
339
- return -1
322
+ template_code = self.build(include_tags)
323
+ template_item = _get_template_item(template_code, self._get_template_name(), self.author)
324
+ return template_item.send_to_minecraft(method, 'pyre')
340
325
 
341
-
342
- def clear(self):
326
+
327
+ def generate_script(self, output_path: str, indent_size: int=4, literal_shorthand: bool=True, var_shorthand: bool=False):
343
328
  """
344
- Clears this template's data.
329
+ Generate an equivalent python script for this template.
330
+
331
+ :param str output_path: The file path to write the script to.
332
+ :param int indent_size: The multiple of spaces to add when indenting lines.
333
+ :param bool literal_shorthand: If True, `text` and `num` items will be written as strings and ints respectively.
334
+ :param bool var_shorthand: If True, all variables will be written using variable shorthand.
345
335
  """
346
- self.__init__()
347
-
336
+ flags = GeneratorFlags(indent_size, literal_shorthand, var_shorthand)
337
+ with open(output_path, 'w', encoding='utf-8') as f:
338
+ f.write(generate_script(self, flags))
348
339
 
349
- def _openbracket(self, btype: Literal['norm', 'repeat']='norm'):
350
- bracket = CodeBlock('Bracket', data={'id': 'bracket', 'direct': 'open', 'type': btype})
351
- self.codeBlocks.append(bracket)
352
- self.closebracket = btype
353
-
354
340
 
355
- # command methods
356
- def playerEvent(self, name: str):
357
- cmd = CodeBlock(name, data={'id': 'block', 'block': 'event', 'action': name})
358
- self.codeBlocks.append(cmd)
359
-
341
+ def _assemble_template(starting_block: CodeBlock, codeblocks: list[CodeBlock], author: str|None) -> DFTemplate:
342
+ """
343
+ Create a DFTemplate object from a starting block and a list of codeblocks.
344
+ `codeblocks` can contain nested lists of CodeBlock objects, so it must be flattened.
345
+ """
346
+ if author is None:
347
+ author = 'pyre'
348
+ template_codeblocks = [starting_block] + list(flatten(codeblocks)) # flatten codeblocks list and insert starting block
349
+ return DFTemplate(template_codeblocks, author)
360
350
 
361
- def entityEvent(self, name: str):
362
- cmd = CodeBlock(name, data={'id': 'block', 'block': 'entity_event', 'action': name})
363
- self.codeBlocks.append(cmd)
364
-
365
351
 
366
- def function(self, name: str, *args):
367
- args = _convertDataTypes(args)
368
- cmd = CodeBlock('function', args, data={'id': 'block', 'block': 'func', 'data': name})
369
- self.codeBlocks.append(cmd)
370
-
352
+ def player_event(event_name: str, codeblocks: list[CodeBlock]=(), author: str|None=None) -> DFTemplate:
353
+ """
354
+ Represents a Player Event codeblock.
371
355
 
372
- def process(self, name: str):
373
- cmd = CodeBlock('process', data={'id': 'block', 'block': 'process', 'data': name})
374
- self.codeBlocks.append(cmd)
375
-
356
+ :param str event_name: The name of the event. (Ex: "Join")
357
+ :param list[CodeBlock] codeblocks: The list of codeblocks in this template.
358
+ :param str|None author: The author of this template.
359
+ """
360
+ starting_block = CodeBlock.new_action('event', event_name, (), {})
361
+ return _assemble_template(starting_block, codeblocks, author)
376
362
 
377
- def callFunction(self, name: str, *args):
378
- args = _convertDataTypes(args)
379
- cmd = CodeBlock('call_func', args, data={'id': 'block', 'block': 'call_func', 'data': name})
380
- self.codeBlocks.append(cmd)
381
-
382
363
 
383
- def startProcess(self, name: str):
384
- cmd = CodeBlock('start_process', data={'id': 'block', 'block': 'start_process', 'data': name})
385
- self.codeBlocks.append(cmd)
364
+ def entity_event(event_name: str, codeblocks: list[CodeBlock]=[], author: str|None=None) -> DFTemplate:
365
+ """
366
+ Represents an Entity Event codeblock.
386
367
 
368
+ :param str event_name: The name of the event. (Ex: "EntityDmg")
369
+ :param list[CodeBlock] codeblocks: The list of codeblocks in this template.
370
+ :param str|None author: The author of this template.
371
+ """
372
+ starting_block = CodeBlock.new_action('entity_event', event_name, (), {})
373
+ return _assemble_template(starting_block, codeblocks, author)
387
374
 
388
- def playerAction(self, name: str, *args, target: Target=DEFAULT_TARGET):
389
- args = _convertDataTypes(args)
390
- cmd = CodeBlock(name, args, target=target, data={'id': 'block', 'block': 'player_action', 'action': name})
391
- self.codeBlocks.append(cmd)
392
-
393
375
 
394
- def gameAction(self, name: str, *args):
395
- args = _convertDataTypes(args)
396
- cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'game_action', 'action': name})
397
- self.codeBlocks.append(cmd)
398
-
376
+ def function(function_name: str, *args, tags: dict[str, str]={}, codeblocks: list[CodeBlock]=[], author: str|None=None) -> DFTemplate:
377
+ """
378
+ Represents a Function codeblock.
399
379
 
400
- def entityAction(self, name: str, *args, target: Target=DEFAULT_TARGET):
401
- args = _convertDataTypes(args)
402
- cmd = CodeBlock(name, args, target=target, data={'id': 'block', 'block': 'entity_action', 'action': name})
403
- self.codeBlocks.append(cmd)
404
-
380
+ :param str event_name: The name of the function.
381
+ :param tuple args: The argument items to include.
382
+ :param dict[str, str] tags: The tags to include.
383
+ :param list[CodeBlock] codeblocks: The list of codeblocks in this template.
384
+ :param str|None author: The author of this template.
385
+ """
386
+ starting_block = CodeBlock.new_data('func', function_name, args, tags)
387
+ return _assemble_template(starting_block, codeblocks, author)
405
388
 
406
- def ifPlayer(self, name: str, *args, target: Target=DEFAULT_TARGET, inverted: bool=False):
407
- args = _convertDataTypes(args)
408
- data = {'id': 'block', 'block': 'if_player', 'action': name}
409
- _addInverted(data, inverted)
410
- cmd = CodeBlock(name, args, target=target, data=data)
411
- self.codeBlocks.append(cmd)
412
- self._openbracket()
413
-
414
389
 
415
- def ifVariable(self, name: str, *args, inverted: bool=False):
416
- args = _convertDataTypes(args)
417
- data = {'id': 'block', 'block': 'if_var', 'action': name}
418
- _addInverted(data, inverted)
419
- cmd = CodeBlock(name, args, data=data)
420
- self.codeBlocks.append(cmd)
421
- self._openbracket()
422
-
390
+ def process(process_name: str, *args, tags: dict[str, str]={}, codeblocks: list[CodeBlock]=[], author: str|None=None) -> DFTemplate:
391
+ """
392
+ Represents a Process codeblock.
423
393
 
424
- def ifGame(self, name: str, *args, inverted: bool=False):
425
- args = _convertDataTypes(args)
426
- data = {'id': 'block', 'block': 'if_game', 'action': name}
427
- _addInverted(data, inverted)
428
- cmd = CodeBlock(name, args, data=data)
429
- self.codeBlocks.append(cmd)
430
- self._openbracket()
431
-
394
+ :param str event_name: The name of the process.
395
+ :param tuple args: The argument items to include.
396
+ :param dict[str, str] tags: The tags to include.
397
+ :param list[CodeBlock] codeblocks: The list of codeblocks in this template.
398
+ :param str|None author: The author of this template.
399
+ """
400
+ starting_block = CodeBlock.new_data('process', process_name, args, tags)
401
+ return _assemble_template(starting_block, codeblocks, author)
432
402
 
433
- def ifEntity(self, name: str, *args, target: Target=DEFAULT_TARGET, inverted: bool=False):
434
- args = _convertDataTypes(args)
435
- data = {'id': 'block', 'block': 'if_entity', 'action': name}
436
- _addInverted(data, inverted)
437
- cmd = CodeBlock(name, args, target=target, data=data)
438
- self.codeBlocks.append(cmd)
439
- self._openbracket()
440
403
 
404
+ def call_function(function_name: str, *args) -> CodeBlock:
405
+ """
406
+ Represents a Call Function codeblock.
441
407
 
442
- def else_(self):
443
- cmd = CodeBlock('else', data={'id': 'block', 'block': 'else'})
444
- self.codeBlocks.append(cmd)
445
- self._openbracket()
446
-
408
+ :param str event_name: The name of the function.
409
+ :param tuple args: The argument items to include.
410
+ """
411
+ return CodeBlock.new_data('call_func', function_name, args, {})
447
412
 
448
- def repeat(self, name: str, *args, subAction: str=None):
449
- args = _convertDataTypes(args)
450
- data = {'id': 'block', 'block': 'repeat', 'action': name}
451
- if subAction is not None:
452
- data['subAction'] = subAction
453
- cmd = CodeBlock(name, args, data=data)
454
- self.codeBlocks.append(cmd)
455
- self._openbracket('repeat')
456
413
 
414
+ def start_process(process_name: str, *args, tags: dict[str, str]={}) -> CodeBlock:
415
+ """
416
+ Represents a Call Function codeblock.
457
417
 
458
- def bracket(self, *args):
459
- args = _convertDataTypes(args)
460
- cmd = CodeBlock('Bracket', data={'id': 'bracket', 'direct': 'close', 'type': self.closebracket})
461
- self.codeBlocks.append(cmd)
462
-
418
+ :param str event_name: The name of the function.
419
+ :param tuple args: The argument items to include.
420
+ """
421
+ return CodeBlock.new_data('start_process', process_name, args, tags)
463
422
 
464
- def control(self, name: str, *args):
465
- args = _convertDataTypes(args)
466
- cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'control', 'action': name})
467
- self.codeBlocks.append(cmd)
468
-
469
423
 
470
- def selectObject(self, name: str, *args):
471
- args = _convertDataTypes(args)
472
- cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'select_obj', 'action': name})
473
- self.codeBlocks.append(cmd)
474
-
424
+ def player_action(action_name: str, *args, target: Target=DEFAULT_TARGET, tags: dict[str, str]={}) -> CodeBlock:
425
+ """
426
+ Represents a Player Action codeblock.
427
+
428
+ :param str action_name: The name of the action.
429
+ :param tuple args: The argument items to include.
430
+ :param Target target: The target for the action.
431
+ :param dict[str, str] tags: The tags to include.
432
+ """
433
+ return CodeBlock.new_action('player_action', action_name, args, tags, target=target)
434
+
435
+
436
+ def entity_action(action_name: str, *args, target: Target=DEFAULT_TARGET, tags: dict[str, str]={}) -> CodeBlock:
437
+ """
438
+ Represents an Entity Action codeblock.
439
+
440
+ :param str action_name: The name of the action.
441
+ :param tuple args: The argument items to include.
442
+ :param Target target: The target for the action.
443
+ :param dict[str, str] tags: The tags to include.
444
+ """
445
+ return CodeBlock.new_action('entity_action', action_name, args, tags, target=target)
446
+
447
+
448
+ def game_action(action_name: str, *args, tags: dict[str, str]={}) -> CodeBlock:
449
+ """
450
+ Represents a Game Action codeblock.
451
+
452
+ :param str action_name: The name of the action.
453
+ :param tuple args: The argument items to include.
454
+ :param dict[str, str] tags: The tags to include.
455
+ """
456
+ return CodeBlock.new_action('game_action', action_name, args, tags)
457
+
458
+
459
+ def if_player(action_name: str, *args, target: Target=DEFAULT_TARGET, tags: dict[str, str]={}, inverted: bool=False, codeblocks: list[CodeBlock]=[]) -> list[CodeBlock]:
460
+ """
461
+ Represents an If Player codeblock.
462
+
463
+ :param str action_name: The name of the condition.
464
+ :param tuple args: The argument items to include.
465
+ :param Target target: The target for the condition.
466
+ :param dict[str, str] tags: The tags to include.
467
+ :param bool inverted: Whether the condition should be inverted.
468
+ :param list[CodeBlock] codeblocks: The list of codeblocks inside the brackets.
469
+ """
470
+ return [
471
+ CodeBlock.new_conditional('if_player', action_name, args, tags, inverted, target),
472
+ CodeBlock.new_bracket('open', 'norm')
473
+ ] + list(codeblocks) + [
474
+ CodeBlock.new_bracket('close', 'norm')
475
+ ]
476
+
477
+ def if_entity(action_name: str, *args, target: Target=DEFAULT_TARGET, tags: dict[str, str]={}, inverted: bool=False, codeblocks: list[CodeBlock]=[]) -> list[CodeBlock]:
478
+ """
479
+ Represents an If Entity codeblock.
480
+
481
+ :param str action_name: The name of the condition.
482
+ :param tuple args: The argument items to include.
483
+ :param Target target: The target for the condition.
484
+ :param dict[str, str] tags: The tags to include.
485
+ :param bool inverted: Whether the condition should be inverted.
486
+ :param list[CodeBlock] codeblocks: The list of codeblocks inside the brackets.
487
+ """
488
+ return [
489
+ CodeBlock.new_conditional('if_entity', action_name, args, tags, inverted, target),
490
+ CodeBlock.new_bracket('open', 'norm')
491
+ ] + list(codeblocks) + [
492
+ CodeBlock.new_bracket('close', 'norm')
493
+ ]
494
+
495
+
496
+ def if_game(action_name: str, *args, tags: dict[str, str]={}, inverted: bool=False, codeblocks: list[CodeBlock]=[]) -> list[CodeBlock]:
497
+ """
498
+ Represents an If Game codeblock.
499
+
500
+ :param str action_name: The name of the condition.
501
+ :param tuple args: The argument items to include.
502
+ :param dict[str, str] tags: The tags to include.
503
+ :param bool inverted: Whether the condition should be inverted.
504
+ :param list[CodeBlock] codeblocks: The list of codeblocks inside the brackets.
505
+ """
506
+ return [
507
+ CodeBlock.new_conditional('if_game', action_name, args, tags, inverted),
508
+ CodeBlock.new_bracket('open', 'norm')
509
+ ] + list(codeblocks) + [
510
+ CodeBlock.new_bracket('close', 'norm')
511
+ ]
512
+
513
+
514
+ def if_variable(action_name: str, *args, tags: dict[str, str]={}, inverted: bool=False, codeblocks: list[CodeBlock]=[]) -> list[CodeBlock]:
515
+ """
516
+ Represents an If Variable codeblock.
517
+
518
+ :param str action_name: The name of the condition.
519
+ :param tuple args: The argument items to include.
520
+ :param dict[str, str] tags: The tags to include.
521
+ :param bool inverted: Whether the condition should be inverted.
522
+ :param list[CodeBlock] codeblocks: The list of codeblocks inside the brackets.
523
+ """
524
+ return [
525
+ CodeBlock.new_conditional('if_var', action_name, args, tags, inverted),
526
+ CodeBlock.new_bracket('open', 'norm')
527
+ ] + list(codeblocks) + [
528
+ CodeBlock.new_bracket('close', 'norm')
529
+ ]
530
+
531
+
532
+ def else_(codeblocks: list[CodeBlock]=[]) -> list[CodeBlock]:
533
+ """
534
+ Represents an Else codeblock.
535
+
536
+ :param list[CodeBlock] codeblocks: The list of codeblocks inside the brackets.
537
+ """
538
+ return [
539
+ CodeBlock.new_else(),
540
+ CodeBlock.new_bracket('open', 'norm')
541
+ ] + list(codeblocks) + [
542
+ CodeBlock.new_bracket('close', 'norm')
543
+ ]
475
544
 
476
- def setVariable(self, name: str, *args):
477
- args = _convertDataTypes(args)
478
- cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'set_var', 'action': name})
479
- self.codeBlocks.append(cmd)
545
+
546
+ def repeat(action_name: str, *args, tags: dict[str, str]={}, sub_action: str|None=None, inverted: bool=False, codeblocks: list[CodeBlock]=[]) -> CodeBlock:
547
+ """
548
+ Represents a Repeat codeblock.
549
+
550
+ :param str action_name: The name of the action.
551
+ :param tuple args: The argument items to include.
552
+ :param dict[str, str] tags: The tags to include.
553
+ :param str|None sub_action: The sub-action for the repeat action (Only relevant for `While`)
554
+ :param bool inverted: Whether the sub-action condition should be inverted.
555
+ :param list[CodeBlock] codeblocks: The list of codeblocks inside the brackets.
556
+ """
557
+ return [
558
+ CodeBlock.new_repeat(action_name, args, tags, sub_action, inverted),
559
+ CodeBlock.new_bracket('open', 'repeat')
560
+ ] + list(codeblocks) + [
561
+ CodeBlock.new_bracket('close', 'repeat')
562
+ ]
563
+
564
+
565
+ def control(action_name: str, *args, tags: dict[str, str]={}) -> CodeBlock:
566
+ """
567
+ Represents a Control codeblock.
568
+
569
+ :param str action_name: The name of the action.
570
+ :param tuple args: The argument items to include.
571
+ :param dict[str, str] tags: The tags to include.
572
+ """
573
+ return CodeBlock.new_action('control', action_name, args, tags)
574
+
575
+
576
+ def select_object(action_name: str, *args, tags: dict[str, str]={}) -> CodeBlock:
577
+ """
578
+ Represents a Select Object codeblock.
579
+
580
+ :param str action_name: The name of the action.
581
+ :param tuple args: The argument items to include.
582
+ :param dict[str, str] tags: The tags to include.
583
+ """
584
+ return CodeBlock.new_action('select_obj', action_name, args, tags)
585
+
586
+
587
+ def set_variable(action_name: str, *args, tags: dict[str, str]={}) -> CodeBlock:
588
+ """
589
+ Represents a Set Variable codeblock.
590
+
591
+ :param str action_name: The name of the action.
592
+ :param tuple args: The argument items to include.
593
+ :param dict[str, str] tags: The tags to include.
594
+ """
595
+ return CodeBlock.new_action('set_var', action_name, args, tags)