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/core/items.py ADDED
@@ -0,0 +1,580 @@
1
+ """
2
+ Class definitions for code items.
3
+ """
4
+
5
+ from abc import ABC, abstractmethod
6
+ from enum import Enum
7
+ import re
8
+ from typing import Literal, Any, Union
9
+ import websocket
10
+ from mcitemlib.itemlib import Item as NbtItem, MCItemlibException
11
+ from rapidnbt import DoubleTag, StringTag, CompoundTag
12
+ from dfpyre.util.style import is_ampersand_coded, ampersand_to_minimessage
13
+ from dfpyre.util.util import PyreException, warn, is_number, COL_SUCCESS, COL_ERROR, COL_RESET
14
+ from dfpyre.gen.action_literals import GAME_VALUE_NAME, SOUND_NAME, POTION_NAME
15
+
16
+
17
+ VAR_ITEM_TYPES = [
18
+ 'String', 'Str', 'Text', 'Number', 'Num', 'Item', 'Location', 'Loc',
19
+ 'Variable', 'Var', 'Sound', 'Snd', 'Particle', 'Potion', 'Pot', 'GameValue',
20
+ 'Vector', 'Vec', 'ParameterType', 'Parameter'
21
+ ]
22
+
23
+ __all__ = ['convert_literals', 'item_from_dict', 'CodeItem', 'ArgValue', 'VAR_ITEM_TYPES'] + VAR_ITEM_TYPES
24
+
25
+
26
+ PARAMETER_TYPE_LOOKUP = ['txt', 'comp', 'num', 'loc', 'vec', 'snd', 'part', 'pot', 'item', 'any', 'var', 'list', 'dict']
27
+
28
+ VAR_SHORTHAND_REGEX = r'^\$([gsli]) (.+)$'
29
+ VAR_SCOPE_LOOKUP = {'g': 'unsaved', 's': 'saved', 'l': 'local', 'i': 'line'}
30
+
31
+ CODECLIENT_URL = 'ws://localhost:31375'
32
+
33
+ # Valid types that can be passed as args for a CodeBlock function
34
+ ArgValue = Union["CodeItem", str, int, float]
35
+
36
+
37
+ def _add_slot(d: dict, slot: int|None):
38
+ if slot is not None:
39
+ d['slot'] = slot
40
+
41
+
42
+ class CodeItem(ABC):
43
+ type = '_codeitem'
44
+
45
+ def __init__(self, slot: int|None):
46
+ self.slot = slot
47
+
48
+ @abstractmethod
49
+ def format(self, slot: int|None) -> dict[str, dict[str, Any]]:
50
+ """
51
+ Returns a dictionary containing this code item's data formatted
52
+ for a DF template JSON.
53
+ """
54
+
55
+
56
+ def convert_literals(arg: ArgValue) -> CodeItem:
57
+ if type(arg) in {int, float}:
58
+ return Number(arg)
59
+
60
+ elif isinstance(arg, str):
61
+ shorthand_match = re.match(VAR_SHORTHAND_REGEX, arg)
62
+ if shorthand_match:
63
+ scope = VAR_SCOPE_LOOKUP[shorthand_match.group(1)]
64
+ return Variable(shorthand_match.group(2), scope)
65
+
66
+ return String(arg)
67
+
68
+ return arg
69
+
70
+
71
+ class String(CodeItem):
72
+ """
73
+ Represents a DiamondFire string object. (`txt`)
74
+ """
75
+ type = 'txt'
76
+
77
+ def __init__(self, value: str, slot: int|None=None):
78
+ super().__init__(slot)
79
+ self.value = value
80
+
81
+ def format(self, slot: int|None):
82
+ formatted_dict = {"item": {"id": self.type, "data": {"name": self.value}}}
83
+ _add_slot(formatted_dict, self.slot or slot)
84
+ return formatted_dict
85
+
86
+ def __repr__(self) -> str:
87
+ return f'{self.__class__.__name__}("{self.value}")'
88
+
89
+ Str = String # String alias
90
+
91
+
92
+ class Text(CodeItem):
93
+ """
94
+ Represents a DiamondFire styled text object (`comp`)
95
+ """
96
+ type = 'comp'
97
+
98
+ def __init__(self, value: str, slot: int|None=None):
99
+ super().__init__(slot)
100
+
101
+ if is_ampersand_coded(value):
102
+ value = ampersand_to_minimessage(value)
103
+ self.value = value
104
+
105
+ def format(self, slot: int|None):
106
+ formatted_dict = {"item": {"id": self.type, "data": {"name": self.value}}}
107
+ _add_slot(formatted_dict, self.slot or slot)
108
+ return formatted_dict
109
+
110
+ def __repr__(self) -> str:
111
+ return f'{self.__class__.__name__}("{self.value}")'
112
+
113
+
114
+ class Number(CodeItem):
115
+ """
116
+ Represents a DiamondFire number object.
117
+ """
118
+ type = 'num'
119
+
120
+ def __init__(self, num: int|float|str, slot: int|None=None):
121
+ super().__init__(slot)
122
+ self.value = num
123
+
124
+ def format(self, slot: int|None):
125
+ formatted_dict = {"item": {"id": self.type, "data": {"name": str(self.value)}}}
126
+ _add_slot(formatted_dict, self.slot or slot)
127
+ return formatted_dict
128
+
129
+ def __repr__(self) -> str:
130
+ return f'{self.__class__.__name__}({self.value})'
131
+
132
+ Num = Number # Number alias
133
+
134
+
135
+ class Item(CodeItem, NbtItem):
136
+ """
137
+ Represents a Minecraft item.
138
+ """
139
+ type = 'item'
140
+
141
+ def __init__(self, item_id: str, count: int=1, slot: int | None=None):
142
+ NbtItem.__init__(self, item_id, count)
143
+ CodeItem.__init__(self, slot)
144
+
145
+ def format(self, slot: int|None):
146
+ formatted_dict = {"item": {"id": self.type, "data": {"item": self.get_snbt()}}}
147
+ _add_slot(formatted_dict, self.slot or slot)
148
+ return formatted_dict
149
+
150
+ def __repr__(self) -> str:
151
+ return f'{self.__class__.__name__}({self.get_id()}, {self.get_count()})'
152
+
153
+ def set_tag(self, tag_name: str, tag_value: str|int|float|String|Number):
154
+ """
155
+ Add a DiamondFire custom tag to this item.
156
+ """
157
+ if isinstance(tag_value, String):
158
+ tag = StringTag(tag_value.value)
159
+ elif isinstance(tag_value, str):
160
+ tag = StringTag(tag_value)
161
+ elif isinstance(tag_value, Number):
162
+ tag = DoubleTag(float(tag_value.value))
163
+ elif isinstance(tag_value, (int, float)):
164
+ tag = DoubleTag(float(tag_value))
165
+
166
+ try:
167
+ custom_data_tag = self.get_component('minecraft:custom_data')
168
+ if 'PublicBukkitValues' in custom_data_tag:
169
+ pbv_tag = custom_data_tag['PublicBukkitValues']
170
+ else:
171
+ pbv_tag = CompoundTag()
172
+ except MCItemlibException:
173
+ custom_data_tag = CompoundTag()
174
+ pbv_tag = CompoundTag()
175
+
176
+ custom_data_tag['PublicBukkitValues'] = pbv_tag
177
+
178
+ pbv_tag[f'hypercube:{tag_name}'] = tag
179
+ self.set_component('minecraft:custom_data', custom_data_tag)
180
+
181
+ def get_tag(self, tag_name: str) -> str|float|None:
182
+ """
183
+ Get a DiamondFire custom tag from this item.
184
+ """
185
+ try:
186
+ custom_data_tag = self.get_component('minecraft:custom_data')
187
+ except MCItemlibException:
188
+ return None
189
+
190
+ if custom_data_tag.is_null():
191
+ return None
192
+
193
+ pbv_tag = custom_data_tag['PublicBukkitValues']
194
+ if pbv_tag.is_null():
195
+ return None
196
+
197
+ df_tag_value = pbv_tag[f'hypercube:{tag_name}']
198
+ if df_tag_value.is_null():
199
+ return None
200
+
201
+ if isinstance(df_tag_value, DoubleTag):
202
+ return float(df_tag_value)
203
+ if isinstance(df_tag_value, StringTag):
204
+ return str(df_tag_value)
205
+
206
+ def remove_tag(self, tag_name: str):
207
+ """
208
+ Remove a DiamondFire custom tag from this item.
209
+
210
+ Raises:
211
+ MCItemibException: If the tag does not exist
212
+ """
213
+ custom_data_tag = self.get_component('minecraft:custom_data')
214
+ pbv_tag = custom_data_tag['PublicBukkitValues']
215
+ del pbv_tag[f'hypercube:{tag_name}']
216
+
217
+ return True
218
+
219
+ def send_to_minecraft(self):
220
+ """
221
+ Sends this item to Minecraft automatically.
222
+ """
223
+ try:
224
+ ws = websocket.WebSocket()
225
+ ws.connect(CODECLIENT_URL)
226
+ print(f'{COL_SUCCESS}Connected.{COL_RESET}')
227
+
228
+ command = f'give {self.get_snbt()}'
229
+ ws.send(command)
230
+ ws.close()
231
+
232
+ print(f'{COL_SUCCESS}Item sent to client successfully.{COL_RESET}')
233
+ return 0
234
+
235
+ except ConnectionRefusedError as e:
236
+ print(f'{COL_ERROR}Could not connect to CodeClient API. Possible problems:')
237
+ print(f' - Minecraft is not open')
238
+ print(f' - CodeClient is not installed (get it here: https://modrinth.com/mod/codeclient)')
239
+ print(f' - CodeClient API is not enabled (enable it in CodeClient general settings){COL_RESET}')
240
+ return 1
241
+
242
+ except Exception as e:
243
+ print(f'Connection failed: {e}')
244
+ return 2
245
+
246
+
247
+ class Location(CodeItem):
248
+ """
249
+ Represents a DiamondFire location object.
250
+ """
251
+ type = 'loc'
252
+
253
+ def __init__(self, x: float=0, y: float=0, z: float=0, pitch: float=0, yaw: float=0, slot: int | None=None):
254
+ super().__init__(slot)
255
+ self.x = float(x)
256
+ self.y = float(y)
257
+ self.z = float(z)
258
+ self.pitch = float(pitch)
259
+ self.yaw = float(yaw)
260
+
261
+ def format(self, slot: int|None):
262
+ formatted_dict = {"item": {
263
+ "id": self.type,
264
+ "data": {
265
+ "isBlock": False,
266
+ "loc": {
267
+ "x": self.x,
268
+ "y": self.y,
269
+ "z": self.z,
270
+ "pitch": self.pitch,
271
+ "yaw": self.yaw
272
+ }
273
+ }
274
+ }}
275
+ _add_slot(formatted_dict, self.slot or slot)
276
+ return formatted_dict
277
+
278
+ def __repr__(self) -> str:
279
+ return f'{self.__class__.__name__}({self.x}, {self.y}, {self.z}, {self.pitch}, {self.yaw})'
280
+
281
+ Loc = Location # Location alias
282
+
283
+
284
+ class Variable(CodeItem):
285
+ """
286
+ Represents a DiamondFire variable object.
287
+ """
288
+ type = 'var'
289
+
290
+ def __init__(self, name: str, scope: Literal['unsaved', 'game', 'saved', 'local', 'line']='unsaved', slot: int | None=None):
291
+ super().__init__(slot)
292
+ self.name = name
293
+
294
+ if scope == 'game':
295
+ scope = 'unsaved'
296
+ self.scope = scope
297
+
298
+ def format(self, slot: int|None):
299
+ formatted_dict = {"item": {"id": self.type,"data": {"name": self.name, "scope": self.scope}}}
300
+ _add_slot(formatted_dict, self.slot or slot)
301
+ return formatted_dict
302
+
303
+ def __repr__(self) -> str:
304
+ return f'{self.__class__.__name__}({self.scope}, "{self.name}")'
305
+
306
+ Var = Variable # Variable alias
307
+
308
+
309
+ class Sound(CodeItem):
310
+ """
311
+ Represents a DiamondFire sound object.
312
+ """
313
+ type = 'snd'
314
+
315
+ def __init__(self, name: SOUND_NAME, pitch: float=1.0, vol: float=2.0, slot: int | None=None):
316
+ super().__init__(slot)
317
+ if name not in set(SOUND_NAME.__args__):
318
+ warn(f'Sound name "{name}" not found.')
319
+
320
+ self.name = name
321
+ self.pitch = pitch
322
+ self.vol = vol
323
+
324
+ def format(self, slot: int|None):
325
+ formatted_dict = {"item": {"id": self.type,"data": {"sound": self.name, "pitch": self.pitch, "vol": self.vol}}}
326
+ _add_slot(formatted_dict, self.slot or slot)
327
+ return formatted_dict
328
+
329
+ def __repr__(self) -> str:
330
+ return f'{self.__class__.__name__}(pitch: {self.pitch}, volume: {self.vol})'
331
+
332
+ Snd = Sound # Sound alias
333
+
334
+
335
+ class Particle(CodeItem):
336
+ """
337
+ Represents a DiamondFire particle object.
338
+ """
339
+ type = 'part'
340
+
341
+ def __init__(self, particle_data: dict, slot: int | None=None):
342
+ super().__init__(slot)
343
+ self.particle_data = particle_data
344
+
345
+ def format(self, slot: int|None):
346
+ formatted_dict = {"item": {"id": self.type, "data": self.particle_data}}
347
+ _add_slot(formatted_dict, self.slot or slot)
348
+ return formatted_dict
349
+
350
+ def __repr__(self) -> str:
351
+ return f'{self.__class__.__name__}({self.particle_data})'
352
+
353
+
354
+ class Potion(CodeItem):
355
+ """
356
+ Represents a DiamondFire potion object.
357
+ """
358
+ type = 'pot'
359
+
360
+ def __init__(self, name: POTION_NAME, dur: int=1000000, amp: int=0, slot: int | None=None):
361
+ super().__init__(slot)
362
+
363
+ if name not in set(POTION_NAME.__args__):
364
+ warn(f'Potion name "{name}" not found.')
365
+
366
+ self.name = name
367
+ self.dur = dur
368
+ self.amp = amp
369
+
370
+ def format(self, slot: int|None):
371
+ formatted_dict = {"item": {"id": self.type,"data": {"pot": self.name, "dur": self.dur, "amp": self.amp}}}
372
+ _add_slot(formatted_dict, self.slot or slot)
373
+ return formatted_dict
374
+
375
+ def __repr__(self) -> str:
376
+ return f'{self.__class__.__name__}(effect: {self.name}, duration: {self.dur}, amplifier: {self.amp})'
377
+
378
+ Pot = Potion # Potion alias
379
+
380
+
381
+ class GameValue(CodeItem):
382
+ """
383
+ Represents a DiamondFire game value object.
384
+ """
385
+ type = 'g_val'
386
+
387
+ def __init__(self, name: GAME_VALUE_NAME, target: str='Default', slot: int | None=None):
388
+ super().__init__(slot)
389
+
390
+ if name not in set(GAME_VALUE_NAME.__args__):
391
+ warn(f'Game value name "{name}" not found.')
392
+
393
+ self.name = name
394
+ self.target = target
395
+
396
+ def format(self, slot: int|None):
397
+ formatted_dict = {"item": {"id": self.type, "data": {"type": self.name, "target": self.target}}}
398
+ _add_slot(formatted_dict, self.slot or slot)
399
+ return formatted_dict
400
+
401
+ def __repr__(self) -> str:
402
+ return f'{self.__class__.__name__}({self.name}, target: {self.target})'
403
+
404
+
405
+ class Vector(CodeItem):
406
+ """
407
+ Represents a DiamondFire vector object.
408
+ """
409
+ type = 'vec'
410
+
411
+ def __init__(self, x: float=0.0, y: float=0.0, z: float=0.0, slot: int | None=None):
412
+ super().__init__(slot)
413
+ self.x = float(x)
414
+ self.y = float(y)
415
+ self.z = float(z)
416
+
417
+ def format(self, slot: int|None):
418
+ formatted_dict = {"item": {"id": self.type, "data": {"x": self.x, "y": self.y, "z": self.z}}}
419
+ _add_slot(formatted_dict, self.slot or slot)
420
+ return formatted_dict
421
+
422
+ def __repr__(self) -> str:
423
+ return f'{self.__class__.__name__}({self.x}, {self.y}, {self.z})'
424
+
425
+ Vec = Vector # Vector alias
426
+
427
+
428
+ class ParameterType(Enum):
429
+ STRING = 0
430
+ TEXT = 1
431
+ NUMBER = 2
432
+ LOCATION = 3
433
+ VECTOR = 4
434
+ SOUND = 5
435
+ PARTICLE = 6
436
+ POTION_EFFECT = 7
437
+ ITEM = 8
438
+ ANY = 9
439
+ VAR = 10
440
+ LIST = 11
441
+ DICT = 12
442
+
443
+ def get_string_value(self) -> str:
444
+ return PARAMETER_TYPE_LOOKUP[self.value]
445
+
446
+ class Parameter(CodeItem):
447
+ """
448
+ Represents a DiamondFire parameter object.
449
+ """
450
+ type = 'pn_el'
451
+
452
+ def __init__(self, name: str, param_type: ParameterType, plural: bool=False, optional: bool=False,
453
+ description: str="", note: str="", default_value=None, slot: int | None=None):
454
+ super().__init__(slot)
455
+ self.name = name
456
+ self.param_type = param_type
457
+ self.plural = plural
458
+ self.optional = optional
459
+ self.description = description
460
+ self.note = note
461
+ self.default_value = convert_literals(default_value)
462
+
463
+ def format(self, slot: int):
464
+ formatted_dict = {"item": {
465
+ "id": self.type,
466
+ "data": {
467
+ "name": self.name,
468
+ "type": self.param_type.get_string_value(),
469
+ "plural": self.plural,
470
+ "optional": self.optional,
471
+ }}
472
+ }
473
+ _add_slot(formatted_dict, self.slot or slot)
474
+
475
+ if self.description:
476
+ formatted_dict['item']['data']['description'] = self.description
477
+ if self.note:
478
+ formatted_dict['item']['data']['note'] = self.note
479
+ if self.default_value is not None:
480
+ if not self.optional:
481
+ warn(f'For parameter "{self.name}": Default value cannot be set if optional is False.')
482
+ elif self.plural:
483
+ warn(f'For parameter "{self.name}": Default value cannot be set while plural is True.')
484
+ else:
485
+ formatted_dict['item']['data']['default_value'] = self.default_value.format(None)['item']
486
+
487
+ return formatted_dict
488
+
489
+ def __repr__(self) -> str:
490
+ raw_type = str(self.param_type).partition('.')[2]
491
+ return f'{self.__class__.__name__}({self.name}, type: {raw_type})'
492
+
493
+
494
+ class _Tag(CodeItem):
495
+ """
496
+ Represents a CodeBlock action tag.
497
+ """
498
+ type = 'bl_tag'
499
+
500
+ def __init__(self, tag_data: dict, slot: int | None=None):
501
+ super().__init__(slot)
502
+ self.tag_data = tag_data
503
+
504
+ def format(self, slot: int|None):
505
+ formatted_dict = {"item": {"id": self.type, "data": self.tag_data}}
506
+ _add_slot(formatted_dict, self.slot or slot)
507
+ return formatted_dict
508
+
509
+ def __repr__(self) -> str:
510
+ return f'{self.__class__.__name__}({self.tag_data})'
511
+
512
+
513
+ def item_from_dict(item_dict: dict, preserve_item_slots: bool):
514
+ item_id = item_dict['item']['id']
515
+ item_data = item_dict['item']['data']
516
+ item_slot = item_dict['slot'] if preserve_item_slots else None
517
+
518
+ if item_id == 'item':
519
+ item = Item.from_snbt(item_data['item'])
520
+ item.slot = item_slot
521
+ return item
522
+
523
+ elif item_id == 'txt':
524
+ return String(item_data['name'], item_slot)
525
+
526
+ elif item_id == 'comp':
527
+ return Text(item_data['name'], item_slot)
528
+
529
+ elif item_id == 'num':
530
+ num_value = item_data['name']
531
+ if is_number(num_value):
532
+ num_value = float(item_data['name'])
533
+ if num_value % 1 == 0:
534
+ num_value = int(num_value)
535
+ return Number(num_value, item_slot)
536
+ return Number(num_value, item_slot)
537
+
538
+ elif item_id == 'loc':
539
+ item_loc = item_data['loc']
540
+ return Location(item_loc['x'], item_loc['y'], item_loc['z'], item_loc['pitch'], item_loc['yaw'], item_slot)
541
+
542
+ elif item_id == 'var':
543
+ return Variable(item_data['name'], item_data['scope'], item_slot)
544
+
545
+ elif item_id == 'snd':
546
+ return Sound(item_data['sound'], item_data['pitch'], item_data['vol'], item_slot)
547
+
548
+ elif item_id == 'part':
549
+ return Particle(item_data, item_slot)
550
+
551
+ elif item_id == 'pot':
552
+ return Potion(item_data['pot'], item_data['dur'], item_data['amp'], item_slot)
553
+
554
+ elif item_id == 'g_val':
555
+ return GameValue(item_data['type'], item_data['target'], item_slot)
556
+
557
+ elif item_id == 'vec':
558
+ return Vector(item_data['x'], item_data['y'], item_data['z'], item_slot)
559
+
560
+ elif item_id == 'pn_el':
561
+ description = item_data.get('description') or ''
562
+ note = item_data.get('note') or ''
563
+ param_type = ParameterType(PARAMETER_TYPE_LOOKUP.index(item_data['type']))
564
+ if item_data['optional']:
565
+ if 'default_value' in item_data:
566
+ default_value_dict = {'item': item_data['default_value'], 'slot': None}
567
+ default_value_item = item_from_dict(default_value_dict, preserve_item_slots)
568
+ return Parameter(item_data['name'], param_type, item_data['plural'], True, description, note, default_value_item, item_slot)
569
+ return Parameter(item_data['name'], param_type, item_data['plural'], True, description, note, slot=item_slot)
570
+ return Parameter(item_data['name'], param_type, item_data['plural'], False, description, note, slot=item_slot)
571
+
572
+ elif item_id == 'bl_tag':
573
+ if 'variable' in item_data:
574
+ return _Tag(item_data, item_slot)
575
+
576
+ elif item_id == 'hint': # Ignore hints
577
+ return
578
+
579
+ else:
580
+ raise PyreException(f'Unrecognized item id `{item_id}`')