dfpyre 0.4.2__py3-none-any.whl → 0.10.5__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/__init__.py CHANGED
@@ -1 +1,3 @@
1
- from dfpyre.pyre import *
1
+ from dfpyre.core.template import *
2
+ from dfpyre.export.action_classes import *
3
+ from dfpyre.export.block_functions import *
@@ -0,0 +1,277 @@
1
+ import os
2
+ import json
3
+ from typing import Literal
4
+ from dataclasses import dataclass
5
+ from dfpyre.util.util import warn
6
+
7
+
8
+ ACTIONDUMP_PATH = os.path.join(os.path.dirname(__file__), '../data/actiondump_min.json')
9
+ DEPRECATED_ACTIONS_PATH = os.path.join(os.path.dirname(__file__), '../data/deprecated_actions.json')
10
+
11
+ CODEBLOCK_ID_LOOKUP = {
12
+ 'PLAYER ACTION': 'player_action',
13
+ 'ENTITY ACTION': 'entity_action',
14
+ 'GAME ACTION': 'game_action',
15
+ 'SET VARIABLE': 'set_var',
16
+ 'IF PLAYER': 'if_player',
17
+ 'IF ENTITY': 'if_entity',
18
+ 'IF GAME': 'if_game',
19
+ 'IF VARIABLE': 'if_var',
20
+ 'REPEAT': 'repeat',
21
+ 'SELECT OBJECT': 'select_obj',
22
+ 'CONTROL': 'control',
23
+ 'PLAYER EVENT': 'event',
24
+ 'ENTITY EVENT': 'entity_event',
25
+ 'FUNCTION': 'func',
26
+ 'CALL FUNCTION': 'call_func',
27
+ 'PROCESS': 'process',
28
+ 'START PROCESS': 'start_process',
29
+ }
30
+
31
+
32
+ VariableType = Literal['VARIABLE', 'NUMBER', 'TEXT', 'COMPONENT', 'ANY_TYPE', 'DICT', 'LIST', 'LOCATION', 'NONE', 'SOUND', 'PARTICLE', 'VECTOR', 'POTION', 'ITEM']
33
+
34
+
35
+ @dataclass
36
+ class CodeblockDataEntry:
37
+ name: str
38
+ id: str
39
+ description: str
40
+ examples: list[str]
41
+
42
+
43
+ @dataclass
44
+ class TagOption:
45
+ name: str
46
+ description: str | None
47
+
48
+
49
+ @dataclass
50
+ class ActionTag:
51
+ name: str
52
+ options: list[TagOption]
53
+ default: str
54
+ slot: int
55
+
56
+
57
+ @dataclass
58
+ class ActionArgument:
59
+ type: VariableType
60
+ plural: bool
61
+ optional: bool
62
+ description: str | None
63
+ notes: str | None
64
+
65
+
66
+ @dataclass
67
+ class ActionDataEntry:
68
+ tags: list[ActionTag]
69
+ required_rank: Literal['None', 'Noble', 'Emperor', 'Mythic', 'Overlord']
70
+ arguments: list[tuple[ActionArgument, ...]]
71
+ return_values: list[VariableType]
72
+ description: str | None
73
+ is_deprecated: bool
74
+ deprecated_note: str | None
75
+
76
+
77
+ @dataclass
78
+ class ActiondumpResult:
79
+ codeblock_data: dict[str, CodeblockDataEntry]
80
+ action_data: dict[str, dict[str, ActionDataEntry]]
81
+ game_values: dict[str, VariableType]
82
+ sound_names: list[str]
83
+ potion_names: list[str]
84
+
85
+
86
+ def get_action_tags(action_data: dict) -> list[ActionTag]:
87
+ action_tags = []
88
+ for tag_data in action_data['tags']:
89
+ options: list[TagOption] = []
90
+ for option in tag_data['options']:
91
+ option_desc = option['icon']['description']
92
+ if option_desc:
93
+ option_desc = ' '.join(option_desc)
94
+ else:
95
+ option_desc = None
96
+ options.append(TagOption(name=option['name'], description=option_desc))
97
+
98
+ converted_tag = ActionTag(
99
+ name=tag_data['name'],
100
+ options=options,
101
+ default=tag_data['defaultOption'],
102
+ slot=tag_data['slot']
103
+ )
104
+ action_tags.append(converted_tag)
105
+ return action_tags
106
+
107
+
108
+ def get_action_args(action_data: dict) -> list[ActionArgument]:
109
+ icon = action_data['icon']
110
+ if 'arguments' not in icon:
111
+ return []
112
+
113
+ parsed_arguments: list[ActionArgument] = []
114
+ argument_union_list: list[ActionArgument] = []
115
+ add_to_union = False
116
+ arguments = icon['arguments']
117
+ for arg_data in arguments:
118
+ if 'type' not in arg_data:
119
+ if arg_data.get('text') == 'OR':
120
+ add_to_union = True
121
+ continue
122
+
123
+ if argument_union_list and not add_to_union:
124
+ parsed_arguments.append(tuple(argument_union_list))
125
+ argument_union_list = []
126
+
127
+ arg_description = arg_data['description']
128
+ if arg_description:
129
+ arg_description = ' '.join(arg_description)
130
+ else:
131
+ arg_description = None
132
+
133
+ arg_notes = arg_data['notes']
134
+ if arg_notes:
135
+ arg_notes = ' '.join(arg_notes[0])
136
+ else:
137
+ arg_notes = None
138
+
139
+ argument_union_list.append(ActionArgument(
140
+ type=arg_data['type'],
141
+ plural=arg_data['plural'],
142
+ optional=arg_data['optional'],
143
+ description=arg_description,
144
+ notes=arg_notes
145
+ ))
146
+ add_to_union = False
147
+
148
+ if argument_union_list:
149
+ parsed_arguments.append(tuple(argument_union_list))
150
+
151
+ return parsed_arguments
152
+
153
+
154
+ def get_action_return_values(action_data: dict) -> list[VariableType]:
155
+ icon = action_data['icon']
156
+ if 'arguments' not in icon:
157
+ return []
158
+
159
+ parsed_return_values: list[VariableType] = []
160
+ return_values = icon['returnValues']
161
+ for value_data in return_values:
162
+ if 'type' not in value_data:
163
+ continue
164
+ parsed_return_values.append(value_data['type'])
165
+
166
+ return parsed_return_values
167
+
168
+
169
+ def parse_codeblock_data(raw_codeblock_data: list[dict]) -> dict[str, CodeblockDataEntry]:
170
+ parsed_data: dict[str, CodeblockDataEntry] = {}
171
+ for raw_data in raw_codeblock_data:
172
+ identifier = raw_data['identifier']
173
+ description = ' '.join(raw_data['item']['description'])
174
+ parsed_codeblock = CodeblockDataEntry(
175
+ name=raw_data['name'],
176
+ id=identifier,
177
+ description=description,
178
+ examples=raw_data['item']['example']
179
+ )
180
+ parsed_data[identifier] = parsed_codeblock
181
+
182
+ return parsed_data
183
+
184
+
185
+ def parse_action_data(raw_action_data: list[dict]):
186
+ all_action_data = {n: {} for n in CODEBLOCK_ID_LOOKUP.values()}
187
+ all_action_data['else'] = dict()
188
+
189
+ with open(DEPRECATED_ACTIONS_PATH, 'r', encoding='utf-8') as f:
190
+ all_deprecated_actions: dict = json.loads(f.read())
191
+
192
+ for action_data in raw_action_data:
193
+ action_tags = get_action_tags(action_data)
194
+
195
+ required_rank = action_data['icon']['requiredRank']
196
+
197
+ action_arguments = get_action_args(action_data)
198
+ action_return_values = get_action_return_values(action_data)
199
+
200
+ action_description = action_data['icon']['description']
201
+ if action_description:
202
+ action_description = ' '.join(action_description)
203
+ else:
204
+ action_description = None
205
+
206
+ dep_note = action_data['icon']['deprecatedNote']
207
+ if dep_note:
208
+ dep_note = ' '.join(dep_note)
209
+ else:
210
+ dep_note = None
211
+
212
+ codeblock_type = CODEBLOCK_ID_LOOKUP[action_data['codeblockName']]
213
+
214
+ deprecated_actions = all_deprecated_actions.get(codeblock_type) or {}
215
+ action_name = action_data['name']
216
+ is_deprecated = action_name in deprecated_actions
217
+
218
+ parsed_action_data = ActionDataEntry(
219
+ tags=action_tags,
220
+ required_rank=required_rank,
221
+ arguments=action_arguments,
222
+ return_values=action_return_values,
223
+ description=action_description,
224
+ is_deprecated=is_deprecated,
225
+ deprecated_note=dep_note
226
+ )
227
+ all_action_data[codeblock_type][action_name] = parsed_action_data
228
+
229
+ return all_action_data
230
+
231
+
232
+ def parse_actiondump() -> ActiondumpResult:
233
+ if not os.path.isfile(ACTIONDUMP_PATH):
234
+ warn('Actiondump not found -- Item tags and error checking will not work.')
235
+ return ActiondumpResult(codeblock_data={}, action_data={}, game_values=[], sound_names=[], potion_names=[])
236
+
237
+ with open(ACTIONDUMP_PATH, 'r', encoding='utf-8') as f:
238
+ actiondump: dict = json.loads(f.read())
239
+
240
+ codeblock_data = parse_codeblock_data(actiondump['codeblocks'])
241
+ all_action_data = parse_action_data(actiondump['actions'])
242
+
243
+ game_values: dict[str, VariableType] = {}
244
+ for game_value in actiondump['gameValues']:
245
+ icon = game_value['icon']
246
+ game_values[icon['name']] = icon['returnType']
247
+
248
+ sound_names: list[str] = []
249
+ for sound in actiondump['sounds']:
250
+ sound_names.append(sound['icon']['name'])
251
+
252
+ potion_names: list[str] = []
253
+ for potion in actiondump['potions']:
254
+ potion_names.append(potion['icon']['name'])
255
+
256
+ return ActiondumpResult(
257
+ codeblock_data=codeblock_data,
258
+ action_data=all_action_data,
259
+ game_values=game_values,
260
+ sound_names=sound_names,
261
+ potion_names=potion_names
262
+ )
263
+
264
+
265
+ ACTIONDUMP = parse_actiondump()
266
+ ACTION_DATA = ACTIONDUMP.action_data
267
+
268
+
269
+ def get_default_tags(codeblock_type: str|None, codeblock_action: str|None) -> dict[str, str]:
270
+ if not codeblock_type or not codeblock_action:
271
+ return {}
272
+ if codeblock_type not in ACTION_DATA:
273
+ return {}
274
+ if codeblock_action not in ACTION_DATA[codeblock_type]:
275
+ return {}
276
+
277
+ return {t.name: t.default for t in ACTION_DATA[codeblock_type][codeblock_action].tags}
@@ -0,0 +1,207 @@
1
+ from typing import Literal
2
+ from enum import Enum
3
+ from difflib import get_close_matches
4
+ from dfpyre.util.util import warn, flatten
5
+ from dfpyre.core.items import convert_literals
6
+ from dfpyre.core.actiondump import ACTION_DATA, ActionTag
7
+
8
+
9
+ VARIABLE_TYPES = {'txt', 'comp', 'num', 'item', 'loc', 'var', 'snd', 'part', 'pot', 'g_val', 'vec', 'pn_el', 'bl_tag'}
10
+ TEMPLATE_STARTERS = {'event', 'entity_event', 'func', 'process'}
11
+ EVENT_CODEBLOCKS = {'event', 'entity_event'}
12
+ CONDITIONAL_CODEBLOCKS = {'if_player', 'if_var', 'if_game', 'if_entity'}
13
+ TARGET_CODEBLOCKS = {'player_action', 'entity_action', 'if_player', 'if_entity'}
14
+ TARGETS = ['Selection', 'Default', 'Killer', 'Damager', 'Shooter', 'Victim', 'AllPlayers', 'Projectile', 'AllEntities', 'AllMobs', 'LastEntity']
15
+
16
+
17
+ class Target(Enum):
18
+ SELECTION = 0
19
+ DEFAULT = 1
20
+ KILLER = 2
21
+ DAMAGER = 3
22
+ SHOOTER = 4
23
+ VICTIM = 5
24
+ ALL_PLAYERS = 6
25
+ PROJECTILE = 7
26
+ ALL_ENTITIES = 8
27
+ ALL_MOBS = 9
28
+ LAST_ENTITY = 10
29
+
30
+ def get_string_value(self):
31
+ return TARGETS[self.value]
32
+
33
+ DEFAULT_TARGET = Target.SELECTION
34
+
35
+
36
+ def _warn_unrecognized_name(codeblock_type: str, codeblock_name: str):
37
+ close = get_close_matches(codeblock_name, ACTION_DATA[codeblock_type].keys())
38
+ if close:
39
+ warn(f'Code block name "{codeblock_name}" not recognized. Did you mean "{close[0]}"?')
40
+ else:
41
+ warn(f'Code block name "{codeblock_name}" not recognized. Try spell checking or retyping without spaces.')
42
+
43
+
44
+ def _check_applied_tags(tags: list[ActionTag], applied_tags: dict[str, str], codeblock_name: str) -> dict[str, str]:
45
+ if len(applied_tags) > 0 and len(tags) == 0:
46
+ warn(f'Action "{codeblock_name}" does not have any tags, but still received {len(applied_tags)}.')
47
+ return {}
48
+
49
+ valid_tags = {}
50
+ tags_lookup = {t.name: t for t in tags}
51
+
52
+ for name, option in applied_tags.items():
53
+ option_strings = [o.name for o in tags_lookup[name].options]
54
+
55
+ if name not in tags_lookup:
56
+ tag_names_joined = '\n'.join(map(lambda s: ' - '+s, tags_lookup.keys()))
57
+ warn(f'Tag "{name}" does not exist for action "{codeblock_name}". Available tags:\n{tag_names_joined}')
58
+ elif option not in option_strings:
59
+ options_joined = '\n'.join(map(lambda s: ' - '+s, option_strings))
60
+ warn(f'Tag "{name}" does not have the option "{option}". Available tag options:\n{options_joined}')
61
+ else:
62
+ valid_tags[name] = option
63
+ return valid_tags
64
+
65
+
66
+ def _reformat_codeblock_tags(tags: list[ActionTag], codeblock_type: str, codeblock_action: str, applied_tags: dict[str, str]) -> list[dict]:
67
+ """
68
+ Turns tag objects into DiamondFire formatted tag items.
69
+ """
70
+
71
+ def format_tag(option: str, name: str):
72
+ return {
73
+ 'item': {
74
+ 'id': 'bl_tag',
75
+ 'data': {'option': option, 'tag': name, 'action': codeblock_action, 'block': codeblock_type}
76
+ },
77
+ 'slot': tag_item.slot
78
+ }
79
+
80
+ valid_applied_tags = _check_applied_tags(tags, applied_tags, codeblock_action)
81
+ reformatted_tags = []
82
+ for tag_item in tags:
83
+ tag_name = tag_item.name
84
+ tag_option = tag_item.default
85
+
86
+ if tag_name in valid_applied_tags:
87
+ tag_option = valid_applied_tags[tag_name]
88
+
89
+ new_tag_item = format_tag(tag_option, tag_name)
90
+ reformatted_tags.append(new_tag_item)
91
+ return reformatted_tags
92
+
93
+
94
+ def _get_codeblock_tags(codeblock_type: str, codeblock_name: str, applied_tags: dict[str, str]) -> list[dict]:
95
+ """
96
+ Get tags for the specified codeblock type and name.
97
+ """
98
+ action_data = ACTION_DATA[codeblock_type][codeblock_name]
99
+ if action_data.is_deprecated:
100
+ warn(f'Action "{codeblock_name}" is deprecated: {action_data.deprecated_note}')
101
+ tags = action_data.tags
102
+ return _reformat_codeblock_tags(tags, codeblock_type, codeblock_name, applied_tags)
103
+
104
+
105
+ class CodeBlock:
106
+ def __init__(self, codeblock_type: str, action_name: str, args: tuple=(), target: Target=DEFAULT_TARGET, data: dict={}, tags: dict[str, str]={}):
107
+ self.type = codeblock_type
108
+ self.action_name = action_name
109
+ self.args = [convert_literals(a) for a in flatten(args) if a is not None]
110
+ self.target = target
111
+ self.data = data
112
+ self.tags = tags
113
+
114
+
115
+ @classmethod
116
+ def new_action(cls, codeblock_type: str, action_name: str, args: tuple, tags: dict[str, str], target: Target=DEFAULT_TARGET) -> "CodeBlock":
117
+ return cls(codeblock_type, action_name, args=args, data={'id': 'block', 'block': codeblock_type, 'action': action_name}, tags=tags, target=target)
118
+
119
+ @classmethod
120
+ def new_data(cls, codeblock_type: str, data_value: str, args: tuple, tags: dict[str, str]) -> "CodeBlock":
121
+ return cls(codeblock_type, 'dynamic', args=args, data={'id': 'block', 'block': codeblock_type, 'data': data_value}, tags=tags)
122
+
123
+ @classmethod
124
+ def new_event(cls, codeblock_type: str, action_name: str, ls_cancel: bool):
125
+ data = {'id': 'block', 'block': codeblock_type, 'action': action_name}
126
+ if ls_cancel:
127
+ data['attribute'] = 'LS-CANCEL'
128
+ return cls(codeblock_type, action_name, data=data)
129
+
130
+ @classmethod
131
+ def new_conditional(cls, codeblock_type: str, action_name: str, args: tuple, tags: dict[str, str], inverted: bool, target: Target=DEFAULT_TARGET) -> "CodeBlock":
132
+ data = {'id': 'block', 'block': codeblock_type, 'action': action_name}
133
+ if inverted:
134
+ data['attribute'] = 'NOT'
135
+ return cls(codeblock_type, action_name, args=args, data=data, tags=tags, target=target)
136
+
137
+ @classmethod
138
+ def new_subaction_block(cls, codeblock_type: str, action_name: str, args: tuple, tags: dict[str, str], sub_action: str|None, inverted: bool) -> "CodeBlock":
139
+ data = {'id': 'block', 'block': codeblock_type, 'action': action_name}
140
+ if sub_action is not None:
141
+ data['subAction'] = sub_action
142
+ if inverted:
143
+ data['attribute'] = 'NOT'
144
+ return cls(codeblock_type, action_name, args=args, data=data, tags=tags)
145
+
146
+ @classmethod
147
+ def new_else(cls) -> "CodeBlock":
148
+ return cls('else', 'else', data={'id': 'block', 'block': 'else'})
149
+
150
+ @classmethod
151
+ def new_bracket(cls, direction: Literal['open', 'close'], bracket_type: Literal['norm', 'repeat']) -> "CodeBlock":
152
+ return cls('bracket', 'bracket', data={'id': 'bracket', 'direct': direction, 'type': bracket_type})
153
+
154
+
155
+ def __repr__(self) -> str:
156
+ if self.action_name == 'dynamic':
157
+ return f'CodeBlock({self.data["block"]}, {self.data["data"]})'
158
+ if self.action_name == 'else':
159
+ return 'CodeBlock(else)'
160
+ if 'block' in self.data:
161
+ return f'CodeBlock({self.data["block"]}, {self.action_name})'
162
+ return f'CodeBlock(bracket, {self.data["type"]}, {self.data["direct"]})'
163
+
164
+
165
+ def get_length(self) -> int:
166
+ """
167
+ Returns the width of this codeblock in Minecraft blocks.
168
+ """
169
+ if self.type in CONDITIONAL_CODEBLOCKS or self.type in {'repeat', 'else'}:
170
+ return 1
171
+ if self.type == 'bracket' and self.data['direct'] == 'open':
172
+ return 1
173
+ return 2
174
+
175
+
176
+ def build(self) -> dict:
177
+ """
178
+ Builds a properly formatted block from a CodeBlock object.
179
+ """
180
+ built_block = self.data.copy()
181
+
182
+ # Add target if necessary ('Selection' is the default when 'target' is blank)
183
+ if self.type in TARGET_CODEBLOCKS and self.target != DEFAULT_TARGET:
184
+ built_block['target'] = self.target.get_string_value()
185
+
186
+ # Add items into args
187
+ final_args = [arg.format(slot) for slot, arg in enumerate(self.args) if arg.type in VARIABLE_TYPES]
188
+ already_applied_tags: dict[str, dict] = {a['item']['data']['tag']: a for a in final_args if a['item']['id'] == 'bl_tag'}
189
+
190
+ # Check for unrecognized name, add tags
191
+ if self.type not in {'bracket', 'else'}:
192
+ if self.action_name not in ACTION_DATA[self.type]:
193
+ _warn_unrecognized_name(self.type, self.action_name)
194
+
195
+ tags = _get_codeblock_tags(self.type, self.action_name, self.tags)
196
+ for i, tag_data in enumerate(tags):
197
+ already_applied_tag_data = already_applied_tags.get(tag_data['item']['data']['tag'])
198
+ if already_applied_tag_data is not None:
199
+ tags[i] = already_applied_tag_data
200
+
201
+ if len(final_args) + len(tags) > 27:
202
+ final_args = final_args[:(27-len(tags))] # Trim list if over 27 elements
203
+
204
+ final_args.extend(tags) # Add tags to end
205
+
206
+ built_block['args'] = {'items': final_args}
207
+ return built_block