dfpyre 0.4.5__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,433 +1,595 @@
1
- """
2
- A package for externally creating code templates for the DiamondFire Minecraft server.
3
-
4
- By Amp
5
- 12/29/2023
6
- """
7
-
8
- import base64
9
- import gzip
10
- import socket
11
- import time
12
- import json
13
- import os
14
- from difflib import get_close_matches
15
- import datetime
16
- from typing import Tuple
17
- from enum import Enum
18
- from mcitemlib.itemlib import Item as NbtItem
19
- from dfpyre.items import *
20
-
21
- COL_WARN = '\x1b[33m'
22
- COL_RESET = '\x1b[0m'
23
- COL_SUCCESS = '\x1b[32m'
24
- COL_ERROR = '\x1b[31m'
25
-
26
- CODEBLOCK_DATA_PATH = os.path.join(os.path.dirname(__file__), 'data/data.json')
27
-
28
- VARIABLE_TYPES = {'txt', 'comp', 'num', 'item', 'loc', 'var', 'snd', 'part', 'pot', 'g_val', 'vec', 'pn_el'}
29
- TEMPLATE_STARTERS = {'event', 'entity_event', 'func', 'process'}
30
-
31
- TARGETS = ['Selection', 'Default', 'Killer', 'Damager', 'Shooter', 'Victim', 'AllPlayers', 'Projectile', 'AllEntities', 'AllMobs', 'LastEntity']
32
- TARGET_CODEBLOCKS = {'player_action', 'entity_action', 'if_player', 'if_entity'}
33
-
34
- VAR_SHORTHAND_CHAR = '$'
35
- VAR_SCOPES = {'g': 'unsaved', 's': 'saved', 'l': 'local', 'i': 'line'}
36
-
37
-
38
- class Target(Enum):
39
- SELECTION = 0
40
- DEFAULT = 1
41
- KILLER = 2
42
- DAMAGER = 3
43
- SHOOTER = 4
44
- VICTIM = 5
45
- ALL_PLAYERS = 6
46
- PROJECTILE = 7
47
- ALL_ENTITIES = 8
48
- ALL_MOBS = 9
49
- LAST_ENTITY = 10
50
-
51
- def get_string_value(self):
52
- return TARGETS[self.value]
53
-
54
- DEFAULT_TARGET = Target.SELECTION
55
-
56
-
57
- class CodeBlock:
58
- def __init__(self, name: str, args: Tuple=(), target: Target=DEFAULT_TARGET, data={}):
59
- self.name = name
60
- self.args = args
61
- self.target = target
62
- self.data = data
63
-
64
-
65
- def _warn(message):
66
- print(f'{COL_WARN}! WARNING ! {message}{COL_RESET}')
67
-
68
-
69
- def _warnUnrecognizedName(codeblockType: str, codeblockName: str):
70
- close = get_close_matches(codeblockName, TAGDATA[codeblockType].keys())
71
- if close:
72
- _warn(f'Code block name "{codeblockName}" not recognized. Did you mean "{close[0]}"?')
73
- else:
74
- _warn(f'Code block name "{codeblockName}" not recognized. Try spell checking or retyping without spaces.')
75
-
76
-
77
- def _loadCodeblockData() -> Tuple:
78
- tagData = {}
79
- if os.path.exists(CODEBLOCK_DATA_PATH):
80
- with open(CODEBLOCK_DATA_PATH, 'r') as f:
81
- tagData = json.load(f)
82
- else:
83
- _warn('data.json not found -- Item tags and error checking will not work.')
84
- return ({}, set(), set())
85
-
86
- del tagData['meta']
87
-
88
- allNames = [x for l in [d.keys() for d in tagData.values()] for x in l] # flatten list
89
- return (
90
- tagData,
91
- set(tagData['extras'].keys()),
92
- set(allNames)
93
- )
94
-
95
- TAGDATA, TAGDATA_EXTRAS_KEYS, ALL_CODEBLOCK_NAMES = _loadCodeblockData()
96
-
97
- def _addInverted(data, inverted):
98
- """
99
- If inverted is true, add 'inverted': 'NOT' to data.
100
- """
101
- if inverted:
102
- data['inverted'] = 'NOT'
103
-
104
-
105
- def _convertDataTypes(args):
106
- convertedArgs = []
107
- for value in args:
108
- if type(value) in {int, float}:
109
- convertedArgs.append(num(value))
110
- elif type(value) is str:
111
- if value[0] == VAR_SHORTHAND_CHAR and value[1] in VAR_SCOPES:
112
- varObject = var(value[2:], VAR_SCOPES[value[1]])
113
- convertedArgs.append(varObject)
114
- else:
115
- convertedArgs.append(text(value))
116
- else:
117
- convertedArgs.append(value)
118
- return tuple(convertedArgs)
119
-
120
-
121
- def _reformatCodeblockTags(tags, codeblockType: str, codeblockName: str):
122
- """
123
- Turns data.json tag items into DiamondFire formatted tag items
124
- """
125
- reformattedTags = []
126
- for tagItem in tags:
127
- actionValue = codeblockName if 'action' not in tagItem else tagItem['action']
128
- newTagItem = {
129
- 'item': {
130
- 'id': 'bl_tag',
131
- 'data': {
132
- 'option': tagItem['option'],
133
- 'tag': tagItem['tag'],
134
- 'action': actionValue,
135
- 'block': codeblockType
136
- }
137
- },
138
- 'slot': tagItem['slot']
139
- }
140
- reformattedTags.append(newTagItem)
141
- return reformattedTags
142
-
143
-
144
- def _getCodeblockTags(codeblockType: str, codeblockName: str):
145
- """
146
- Get tags for the specified codeblock type and name
147
- """
148
- tags = None
149
- if codeblockType in TAGDATA_EXTRAS_KEYS:
150
- tags = TAGDATA['extras'][codeblockType]
151
- else:
152
- tags = TAGDATA[codeblockType].get(codeblockName)
153
- return _reformatCodeblockTags(tags, codeblockType, codeblockName)
154
-
155
-
156
- def _buildBlock(codeblock: CodeBlock, includeTags: bool):
157
- """
158
- Builds a properly formatted block from a CodeBlock object.
159
- """
160
- finalBlock = codeblock.data.copy()
161
- codeblockType = codeblock.data.get('block')
162
-
163
- # add target if necessary ('Selection' is the default when 'target' is blank)
164
- if codeblockType in TARGET_CODEBLOCKS and codeblock.target != DEFAULT_TARGET:
165
- finalBlock['target'] = codeblock.target.get_string_value()
166
-
167
- # add items into args
168
- finalArgs = [arg.format(slot) for slot, arg in enumerate(codeblock.args) if arg.type in VARIABLE_TYPES]
169
-
170
- # check for unrecognized name, add tags
171
- if codeblockType is not None: # for brackets
172
- if codeblockType not in TAGDATA_EXTRAS_KEYS and codeblock.name not in ALL_CODEBLOCK_NAMES:
173
- _warnUnrecognizedName(codeblockType, codeblock.name)
174
- elif includeTags:
175
- tags = _getCodeblockTags(codeblockType, codeblock.name)
176
- if len(finalArgs) + len(tags) > 27:
177
- finalArgs = finalArgs[:(27-len(tags))] # trim list if over 27 elements
178
- finalArgs.extend(tags) # add tags to end
179
-
180
- finalBlock['args'] = {'items': finalArgs}
181
- return finalBlock
182
-
183
-
184
- def _dfEncode(jsonString: str) -> str:
185
- """
186
- Encodes a stringified json.
187
- """
188
- encodedString = gzip.compress(jsonString.encode('utf-8'))
189
- return base64.b64encode(encodedString).decode('utf-8')
190
-
191
-
192
- def sendToDf(templateCode: str, name: str='Unnamed Template', author: str='pyre') -> int:
193
- """
194
- Sends a template to DiamondFire via recode item api.
195
-
196
- :param str templateCode: The code for the template as a base64 string.
197
- :param str name: The name of the template.
198
- :param str author: The author of the template.
199
- """
200
- now = datetime.datetime.now()
201
-
202
- templateItem = NbtItem('yellow_shulker_box')
203
- templateItem.set_name(f'&x&f&f&5&c&0&0>> &x&f&f&c&7&0&0{name}')
204
- templateItem.set_lore([
205
- f'&8Author: {author}',
206
- f'&8Date: {now.strftime('%Y-%m-%d')}',
207
- '',
208
- '&7This template was generated by &6pyre&7.',
209
- '&7https://github.com/Amp63/pyre'
210
- ])
211
-
212
- pbvTag = {
213
- 'hypercube:codetemplatedata': f'{{"author":"{author}","name":"{name}","version": 1,"code":"{templateCode}"}}',
214
- 'hypercube:pyre_creation_timestamp': now.timestamp()
215
- }
216
- templateItem.set_tag('PublicBukkitValues', pbvTag, raw=True)
217
-
218
- data = {'type': 'nbt', 'source': f'pyre Template - {name}', 'data': templateItem.get_nbt()}
219
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
220
- try:
221
- s.connect(('127.0.0.1', 31372))
222
- except ConnectionRefusedError:
223
- print(f"""{COL_ERROR}Could not connect to recode item API. Possible problems:
224
- - Minecraft is not open
225
- - Recode is not installed (get it here: https://modrinth.com/mod/recode or join the discord here: https://discord.gg/GWxWtcwA2C){COL_RESET}""")
226
- s.close()
227
- return 1
228
-
229
- s.send((str(data) + '\n').encode('utf-8'))
230
- received = json.loads(s.recv(1024).decode())
231
- status = received['status']
232
- s.close()
233
- time.sleep(0.5)
234
-
235
- if status == 'success':
236
- print(f'{COL_SUCCESS}Template sent to client successfully.{COL_RESET}')
237
- return 0
238
- error = received['error']
239
- print(f'{COL_ERROR}Error sending template: {error}{COL_RESET}')
240
- return 2
241
-
242
-
243
- class DFTemplate:
244
- """
245
- Represents a DiamondFire code template.
246
- """
247
- def __init__(self, name: str=None, author: str='pyre'):
248
- self.codeBlocks = []
249
- self.closebracket = None
250
- self.name = name
251
- self.author = author
252
-
253
-
254
- def _setTemplateName(self, firstBlock):
255
- if self.name is not None:
256
- return
257
- if 'data' in firstBlock:
258
- self.name = firstBlock['data']
259
- if not self.name:
260
- self.name = 'Unnamed Template'
261
- else:
262
- self.name = firstBlock['block'] + '_' + firstBlock['action']
263
-
264
-
265
- def build(self, includeTags: bool=True) -> str:
266
- """
267
- Build this template.
268
-
269
- :param bool includeTags: If True, include item tags in code blocks. Otherwise omit them.
270
- :return: String containing encoded template data.
271
- """
272
- templateDictBlocks = [_buildBlock(codeblock, includeTags) for codeblock in self.codeBlocks]
273
- templateDict = {'blocks': templateDictBlocks}
274
- firstBlock = templateDictBlocks[0]
275
- if firstBlock['block'] not in TEMPLATE_STARTERS:
276
- _warn('Template does not start with an event, function, or process.')
277
-
278
- self._setTemplateName(firstBlock)
279
-
280
- print(f'{COL_SUCCESS}Template built successfully.{COL_RESET}')
281
-
282
- jsonString = json.dumps(templateDict, separators=(',', ':'))
283
- return _dfEncode(jsonString)
284
-
285
-
286
- def buildAndSend(self, includeTags: bool=True):
287
- """
288
- Builds this template and sends it to DiamondFire automatically.
289
-
290
- :param bool includeTags: If True, include item tags in code blocks. Otherwise omit them.
291
- """
292
- templateCode = self.build(includeTags)
293
- sendToDf(templateCode, name=self.name, author=self.author)
294
-
295
-
296
- def clear(self):
297
- """
298
- Clears this template's data.
299
- """
300
- self.__init__()
301
-
302
-
303
- def _openbracket(self, btype: str='norm'):
304
- bracket = CodeBlock('Bracket', data={'id': 'bracket', 'direct': 'open', 'type': btype})
305
- self.codeBlocks.append(bracket)
306
- self.closebracket = btype
307
-
308
-
309
- # command methods
310
- def playerEvent(self, name: str):
311
- cmd = CodeBlock(name, data={'id': 'block', 'block': 'event', 'action': name})
312
- self.codeBlocks.append(cmd)
313
-
314
-
315
- def entityEvent(self, name: str):
316
- cmd = CodeBlock(name, data={'id': 'block', 'block': 'entity_event', 'action': name})
317
- self.codeBlocks.append(cmd)
318
-
319
-
320
- def function(self, name: str, *args):
321
- args = _convertDataTypes(args)
322
- cmd = CodeBlock('function', args, data={'id': 'block', 'block': 'func', 'data': name})
323
- self.codeBlocks.append(cmd)
324
-
325
-
326
- def process(self, name: str):
327
- cmd = CodeBlock('process', data={'id': 'block', 'block': 'process', 'data': name})
328
- self.codeBlocks.append(cmd)
329
-
330
-
331
- def callFunction(self, name: str, *args):
332
- args = _convertDataTypes(args)
333
- cmd = CodeBlock('call_func', args, data={'id': 'block', 'block': 'call_func', 'data': name})
334
- self.codeBlocks.append(cmd)
335
-
336
-
337
- def startProcess(self, name: str):
338
- cmd = CodeBlock('start_process', data={'id': 'block', 'block': 'start_process', 'data': name})
339
- self.codeBlocks.append(cmd)
340
-
341
-
342
- def playerAction(self, name: str, *args, target: Target=DEFAULT_TARGET):
343
- args = _convertDataTypes(args)
344
- cmd = CodeBlock(name, args, target=target, data={'id': 'block', 'block': 'player_action', 'action': name})
345
- self.codeBlocks.append(cmd)
346
-
347
-
348
- def gameAction(self, name: str, *args):
349
- args = _convertDataTypes(args)
350
- cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'game_action', 'action': name})
351
- self.codeBlocks.append(cmd)
352
-
353
-
354
- def entityAction(self, name: str, *args, target: Target=DEFAULT_TARGET):
355
- args = _convertDataTypes(args)
356
- cmd = CodeBlock(name, args, target=target, data={'id': 'block', 'block': 'entity_action', 'action': name})
357
- self.codeBlocks.append(cmd)
358
-
359
-
360
- def ifPlayer(self, name: str, *args, target: Target=DEFAULT_TARGET, inverted: bool=False):
361
- args = _convertDataTypes(args)
362
- data = {'id': 'block', 'block': 'if_player', 'action': name}
363
- _addInverted(data, inverted)
364
- cmd = CodeBlock(name, args, target=target, data=data)
365
- self.codeBlocks.append(cmd)
366
- self._openbracket()
367
-
368
-
369
- def ifVariable(self, name: str, *args, inverted: bool=False):
370
- args = _convertDataTypes(args)
371
- data = {'id': 'block', 'block': 'if_var', 'action': name}
372
- _addInverted(data, inverted)
373
- cmd = CodeBlock(name, args, data=data)
374
- self.codeBlocks.append(cmd)
375
- self._openbracket()
376
-
377
-
378
- def ifGame(self, name: str, *args, inverted: bool=False):
379
- args = _convertDataTypes(args)
380
- data = {'id': 'block', 'block': 'if_game', 'action': name}
381
- _addInverted(data, inverted)
382
- cmd = CodeBlock(name, args, data=data)
383
- self.codeBlocks.append(cmd)
384
- self._openbracket()
385
-
386
-
387
- def ifEntity(self, name: str, *args, target: Target=DEFAULT_TARGET, inverted: bool=False):
388
- args = _convertDataTypes(args)
389
- data = {'id': 'block', 'block': 'if_entity', 'action': name}
390
- _addInverted(data, inverted)
391
- cmd = CodeBlock(name, args, target=target, data=data)
392
- self.codeBlocks.append(cmd)
393
- self._openbracket()
394
-
395
-
396
- def else_(self):
397
- cmd = CodeBlock('else', data={'id': 'block', 'block': 'else'})
398
- self.codeBlocks.append(cmd)
399
- self._openbracket()
400
-
401
-
402
- def repeat(self, name: str, *args, subAction: str=None):
403
- args = _convertDataTypes(args)
404
- data = {'id': 'block', 'block': 'repeat', 'action': name}
405
- if subAction is not None:
406
- data['subAction'] = subAction
407
- cmd = CodeBlock(name, args, data=data)
408
- self.codeBlocks.append(cmd)
409
- self._openbracket('repeat')
410
-
411
-
412
- def bracket(self, *args):
413
- args = _convertDataTypes(args)
414
- cmd = CodeBlock('Bracket', data={'id': 'bracket', 'direct': 'close', 'type': self.closebracket})
415
- self.codeBlocks.append(cmd)
416
-
417
-
418
- def control(self, name: str, *args):
419
- args = _convertDataTypes(args)
420
- cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'control', 'action': name})
421
- self.codeBlocks.append(cmd)
422
-
423
-
424
- def selectObject(self, name: str, *args):
425
- args = _convertDataTypes(args)
426
- cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'select_obj', 'action': name})
427
- self.codeBlocks.append(cmd)
428
-
429
-
430
- def setVariable(self, name: str, *args):
431
- args = _convertDataTypes(args)
432
- cmd = CodeBlock(name, args, data={'id': 'block', 'block': 'set_var', 'action': name})
433
- self.codeBlocks.append(cmd)
1
+ """
2
+ A package for making code templates for the DiamondFire Minecraft server.
3
+
4
+ By Amp
5
+ """
6
+
7
+ import json
8
+ from difflib import get_close_matches
9
+ import datetime
10
+ from enum import Enum
11
+ from mcitemlib.itemlib import Item as NbtItem
12
+ from dfpyre.util import *
13
+ from dfpyre.items import *
14
+ from dfpyre.scriptgen import generate_script, GeneratorFlags
15
+ from dfpyre.actiondump import CODEBLOCK_DATA, get_default_tags
16
+
17
+
18
+ VARIABLE_TYPES = {'txt', 'comp', 'num', 'item', 'loc', 'var', 'snd', 'part', 'pot', 'g_val', 'vec', 'pn_el'}
19
+ TEMPLATE_STARTERS = {'event', 'entity_event', 'func', 'process'}
20
+ DYNAMIC_CODEBLOCKS = {'func', 'process', 'call_func', 'start_process'}
21
+
22
+ TARGETS = ['Selection', 'Default', 'Killer', 'Damager', 'Shooter', 'Victim', 'AllPlayers', 'Projectile', 'AllEntities', 'AllMobs', 'LastEntity']
23
+ TARGET_CODEBLOCKS = {'player_action', 'entity_action', 'if_player', 'if_entity'}
24
+
25
+ CODECLIENT_URL = 'ws://localhost:31375'
26
+
27
+
28
+ class Target(Enum):
29
+ SELECTION = 0
30
+ DEFAULT = 1
31
+ KILLER = 2
32
+ DAMAGER = 3
33
+ SHOOTER = 4
34
+ VICTIM = 5
35
+ ALL_PLAYERS = 6
36
+ PROJECTILE = 7
37
+ ALL_ENTITIES = 8
38
+ ALL_MOBS = 9
39
+ LAST_ENTITY = 10
40
+
41
+ def get_string_value(self):
42
+ return TARGETS[self.value]
43
+
44
+ DEFAULT_TARGET = Target.SELECTION
45
+
46
+
47
+ def _convert_args(args):
48
+ return tuple(map(convert_argument, args))
49
+
50
+
51
+ class CodeBlock:
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
55
+ self.args = args
56
+ self.target = target
57
+ self.data = data
58
+ self.tags = tags
59
+
60
+
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())
137
+ if close:
138
+ warn(f'Code block name "{codeblock_name}" not recognized. Did you mean "{close[0]}"?')
139
+ else:
140
+ warn(f'Code block name "{codeblock_name}" not recognized. Try spell checking or retyping without spaces.')
141
+
142
+
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 {}
147
+
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}')
157
+ else:
158
+ valid_tags[name] = option
159
+ return valid_tags
160
+
161
+
162
+ def _reformat_codeblock_tags(tags: list[dict], codeblock_type: str, codeblock_action: str, applied_tags: dict[str, str]):
163
+ """
164
+ Turns tag objects into DiamondFire formatted tag items
165
+ """
166
+
167
+ def format_tag(option: str, name: str):
168
+ return {
169
+ 'item': {
170
+ 'id': 'bl_tag',
171
+ 'data': {'option': option, 'tag': name, 'action': codeblock_action, 'block': codeblock_type}
172
+ },
173
+ 'slot': tag_item['slot']
174
+ }
175
+
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]
183
+
184
+ new_tag_item = format_tag(tag_option, tag_name)
185
+ reformatted_tags.append(new_tag_item)
186
+ return reformatted_tags
187
+
188
+
189
+ def _get_codeblock_tags(codeblock_type: str, codeblock_name: str, applied_tags: dict[str, str]):
190
+ """
191
+ Get tags for the specified codeblock type and name
192
+ """
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)
198
+
199
+
200
+ def _get_template_item(template_code: str, name: str, author: str) -> NbtItem:
201
+ now = datetime.datetime.now()
202
+
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([
206
+ f'&8Author: {author}',
207
+ f'&8Date: {now.strftime("%Y-%m-%d")}',
208
+ '',
209
+ '&7This template was generated by &6pyre&7.',
210
+ '&7https://github.com/Amp63/pyre'
211
+ ])
212
+
213
+ pbv_tag = {
214
+ 'hypercube:codetemplatedata': f'{{"author":"{author}","name":"{name}","version": 1,"code":"{template_code}"}}',
215
+ 'hypercube:pyre_creation_timestamp': now.timestamp()
216
+ }
217
+ template_item.set_custom_data('PublicBukkitValues', pbv_tag, raw=True)
218
+
219
+ return template_item
220
+
221
+
222
+ class DFTemplate:
223
+ """
224
+ Represents a DiamondFire code template.
225
+ """
226
+ def __init__(self, codeblocks: list[CodeBlock], author: str='pyre'):
227
+ self.codeblocks = codeblocks
228
+ self.author = author
229
+
230
+
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']
237
+
238
+
239
+ def __repr__(self) -> str:
240
+ return f'DFTemplate(name: "{self._get_template_name()}", author: "{self.author}", codeblocks: {len(self.codeblocks)})'
241
+
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)
276
+
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)
294
+ else:
295
+ raise PyreException('Expected CodeBlock or list[CodeBlock] to insert.')
296
+ return self
297
+
298
+
299
+ def build(self, include_tags: bool=True) -> str:
300
+ """
301
+ Build this template.
302
+
303
+ :param bool include_tags: If True, include item tags in code blocks. Otherwise omit them.
304
+ :return: String containing encoded template data.
305
+ """
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)
314
+
315
+
316
+ def build_and_send(self, method: Literal['recode', 'codeclient'], include_tags: bool=True) -> int:
317
+ """
318
+ Builds this template and sends it to DiamondFire automatically.
319
+
320
+ :param bool include_tags: If True, include item tags in code blocks. Otherwise omit them.
321
+ """
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')
325
+
326
+
327
+ def generate_script(self, output_path: str, indent_size: int=4, literal_shorthand: bool=True, var_shorthand: bool=False):
328
+ """
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.
335
+ """
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))
339
+
340
+
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)
350
+
351
+
352
+ def player_event(event_name: str, codeblocks: list[CodeBlock]=(), author: str|None=None) -> DFTemplate:
353
+ """
354
+ Represents a Player Event codeblock.
355
+
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)
362
+
363
+
364
+ def entity_event(event_name: str, codeblocks: list[CodeBlock]=[], author: str|None=None) -> DFTemplate:
365
+ """
366
+ Represents an Entity Event codeblock.
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)
374
+
375
+
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.
379
+
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)
388
+
389
+
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.
393
+
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)
402
+
403
+
404
+ def call_function(function_name: str, *args) -> CodeBlock:
405
+ """
406
+ Represents a Call Function codeblock.
407
+
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, {})
412
+
413
+
414
+ def start_process(process_name: str, *args, tags: dict[str, str]={}) -> CodeBlock:
415
+ """
416
+ Represents a Call Function codeblock.
417
+
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)
422
+
423
+
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
+ ]
544
+
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)