avrae-ls 0.3.1__py3-none-any.whl → 0.4.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.
avrae_ls/api.py CHANGED
@@ -23,7 +23,7 @@ class _DirMixin:
23
23
  METHODS: ClassVar[list[str]] = []
24
24
 
25
25
  def __dir__(self) -> list[str]:
26
- data_keys = list(getattr(self, "data", {}).keys())
26
+ data_keys = list(getattr(self, "_data", {}).keys())
27
27
  return sorted(set(self.ATTRS + self.METHODS + data_keys))
28
28
 
29
29
  def __post_init__(self) -> None:
@@ -57,21 +57,21 @@ class SimpleRollResult(_DirMixin):
57
57
  # === Context API ===
58
58
  @dataclass
59
59
  class GuildAPI(_DirMixin):
60
- data: Mapping[str, Any] = field(default_factory=dict)
60
+ _data: Mapping[str, Any] = field(default_factory=dict)
61
61
  ATTRS: ClassVar[list[str]] = ["name", "id"]
62
62
  METHODS: ClassVar[list[str]] = ["servsettings"]
63
63
 
64
64
  @property
65
65
  def name(self) -> str:
66
- return str(self.data.get("name", "Guild"))
66
+ return str(self._data.get("name", "Guild"))
67
67
 
68
68
  @property
69
69
  def id(self) -> int | None:
70
- raw = self.data.get("id")
70
+ raw = self._data.get("id")
71
71
  return int(raw) if raw is not None else None
72
72
 
73
73
  def servsettings(self) -> Mapping[str, Any] | None:
74
- return self.data.get("servsettings")
74
+ return self._data.get("servsettings")
75
75
 
76
76
  def __getitem__(self, item: str) -> Any:
77
77
  if isinstance(item, int):
@@ -87,17 +87,17 @@ class GuildAPI(_DirMixin):
87
87
 
88
88
  @dataclass
89
89
  class CategoryAPI(_DirMixin):
90
- data: Mapping[str, Any] = field(default_factory=dict)
90
+ _data: Mapping[str, Any] = field(default_factory=dict)
91
91
  ATTRS: ClassVar[list[str]] = ["name", "id"]
92
92
  METHODS: ClassVar[list[str]] = []
93
93
 
94
94
  @property
95
95
  def name(self) -> str:
96
- return str(self.data.get("name", "Category"))
96
+ return str(self._data.get("name", "Category"))
97
97
 
98
98
  @property
99
99
  def id(self) -> int | None:
100
- raw = self.data.get("id")
100
+ raw = self._data.get("id")
101
101
  return int(raw) if raw is not None else None
102
102
 
103
103
  def __getitem__(self, item: str) -> Any:
@@ -106,32 +106,32 @@ class CategoryAPI(_DirMixin):
106
106
 
107
107
  @dataclass
108
108
  class ChannelAPI(_DirMixin):
109
- data: Mapping[str, Any] = field(default_factory=dict)
109
+ _data: Mapping[str, Any] = field(default_factory=dict)
110
110
  ATTRS: ClassVar[list[str]] = ["name", "id", "topic", "category", "parent"]
111
111
  METHODS: ClassVar[list[str]] = []
112
112
 
113
113
  @property
114
114
  def name(self) -> str:
115
- return str(self.data.get("name", "channel"))
115
+ return str(self._data.get("name", "channel"))
116
116
 
117
117
  @property
118
118
  def id(self) -> int | None:
119
- raw = self.data.get("id")
119
+ raw = self._data.get("id")
120
120
  return int(raw) if raw is not None else None
121
121
 
122
122
  @property
123
123
  def topic(self) -> str | None:
124
- val = self.data.get("topic")
124
+ val = self._data.get("topic")
125
125
  return str(val) if val is not None else None
126
126
 
127
127
  @property
128
128
  def category(self) -> CategoryAPI | None:
129
- cat = self.data.get("category")
129
+ cat = self._data.get("category")
130
130
  return CategoryAPI(cat) if cat is not None else None
131
131
 
132
132
  @property
133
133
  def parent(self) -> ChannelAPI | None:
134
- parent = self.data.get("parent")
134
+ parent = self._data.get("parent")
135
135
  return ChannelAPI(parent) if parent is not None else None
136
136
 
137
137
  def __getitem__(self, item: str) -> Any:
@@ -140,17 +140,17 @@ class ChannelAPI(_DirMixin):
140
140
 
141
141
  @dataclass
142
142
  class RoleAPI(_DirMixin):
143
- data: Mapping[str, Any] = field(default_factory=dict)
143
+ _data: Mapping[str, Any] = field(default_factory=dict)
144
144
  ATTRS: ClassVar[list[str]] = ["name", "id"]
145
145
  METHODS: ClassVar[list[str]] = []
146
146
 
147
147
  @property
148
148
  def name(self) -> str:
149
- return str(self.data.get("name", "Role"))
149
+ return str(self._data.get("name", "Role"))
150
150
 
151
151
  @property
152
152
  def id(self) -> int | None:
153
- raw = self.data.get("id")
153
+ raw = self._data.get("id")
154
154
  return int(raw) if raw is not None else None
155
155
 
156
156
  def __getitem__(self, item: str) -> Any:
@@ -159,29 +159,29 @@ class RoleAPI(_DirMixin):
159
159
 
160
160
  @dataclass
161
161
  class AuthorAPI(_DirMixin):
162
- data: Mapping[str, Any] = field(default_factory=dict)
162
+ _data: Mapping[str, Any] = field(default_factory=dict)
163
163
  ATTRS: ClassVar[list[str]] = ["name", "id", "discriminator", "display_name", "roles"]
164
164
  METHODS: ClassVar[list[str]] = ["get_roles"]
165
165
 
166
166
  @property
167
167
  def name(self) -> str:
168
- return str(self.data.get("name", "User"))
168
+ return str(self._data.get("name", "User"))
169
169
 
170
170
  @property
171
171
  def id(self) -> int | None:
172
- raw = self.data.get("id")
172
+ raw = self._data.get("id")
173
173
  return int(raw) if raw is not None else None
174
174
 
175
175
  @property
176
176
  def discriminator(self) -> str:
177
- return str(self.data.get("discriminator", "0000"))
177
+ return str(self._data.get("discriminator", "0000"))
178
178
 
179
179
  @property
180
180
  def display_name(self) -> str:
181
- return str(self.data.get("display_name", self.name))
181
+ return str(self._data.get("display_name", self.name))
182
182
 
183
183
  def get_roles(self) -> list[RoleAPI]:
184
- roles = self.data.get("roles") or []
184
+ roles = self._data.get("roles") or []
185
185
  return [RoleAPI(r) for r in roles]
186
186
 
187
187
  @property
@@ -197,48 +197,48 @@ class AuthorAPI(_DirMixin):
197
197
 
198
198
  @dataclass
199
199
  class AliasContextAPI(_DirMixin):
200
- data: Mapping[str, Any] = field(default_factory=dict)
200
+ _data: Mapping[str, Any] = field(default_factory=dict)
201
201
  ATTRS: ClassVar[list[str]] = ["guild", "channel", "author", "prefix", "alias", "message_id"]
202
202
  METHODS: ClassVar[list[str]] = []
203
203
 
204
204
  @property
205
205
  def guild(self) -> GuildAPI | None:
206
206
  """Guild info for the alias invocation (server context)."""
207
- guild_data = self.data.get("guild")
207
+ guild_data = self._data.get("guild")
208
208
  return GuildAPI(guild_data) if guild_data is not None else None
209
209
 
210
210
  @property
211
211
  def channel(self) -> ChannelAPI | None:
212
212
  """Channel where the alias was invoked."""
213
- channel_data = self.data.get("channel")
213
+ channel_data = self._data.get("channel")
214
214
  return ChannelAPI(channel_data) if channel_data is not None else None
215
215
 
216
216
  @property
217
217
  def author(self) -> AuthorAPI | None:
218
218
  """User who invoked the alias."""
219
- author_data = self.data.get("author")
219
+ author_data = self._data.get("author")
220
220
  return AuthorAPI(author_data) if author_data is not None else None
221
221
 
222
222
  @property
223
223
  def prefix(self) -> str | None:
224
224
  """Command prefix that triggered the alias (e.g., `!`)."""
225
- val = self.data.get("prefix")
225
+ val = self._data.get("prefix")
226
226
  return str(val) if val is not None else None
227
227
 
228
228
  @property
229
229
  def alias(self) -> str | None:
230
230
  """Alias name that was run."""
231
- val = self.data.get("alias")
231
+ val = self._data.get("alias")
232
232
  return str(val) if val is not None else None
233
233
 
234
234
  @property
235
235
  def message_id(self) -> int | None:
236
236
  """Discord message id for the invocation."""
237
- raw = self.data.get("message_id")
237
+ raw = self._data.get("message_id")
238
238
  return int(raw) if raw is not None else None
239
239
 
240
240
  def __getattr__(self, item: str) -> Any:
241
- return self.data.get(item)
241
+ return self._data.get(item)
242
242
 
243
243
  def __getitem__(self, item: str) -> Any:
244
244
  return getattr(self, str(item))
@@ -305,7 +305,7 @@ _SKILL_ABILITIES = {
305
305
 
306
306
  @dataclass
307
307
  class AliasBaseStats(_DirMixin):
308
- data: Mapping[str, Any]
308
+ _data: Mapping[str, Any]
309
309
  prof_bonus_override: int | None = None
310
310
  ATTRS: ClassVar[list[str]] = [
311
311
  "prof_bonus",
@@ -322,31 +322,31 @@ class AliasBaseStats(_DirMixin):
322
322
  def prof_bonus(self) -> int:
323
323
  if self.prof_bonus_override is not None:
324
324
  return self.prof_bonus_override
325
- return _safe_int(self.data.get("prof_bonus"), 2)
325
+ return _safe_int(self._data.get("prof_bonus"), 2)
326
326
 
327
327
  @property
328
328
  def strength(self) -> int:
329
- return _safe_int(self.data.get("strength"), 10)
329
+ return _safe_int(self._data.get("strength"), 10)
330
330
 
331
331
  @property
332
332
  def dexterity(self) -> int:
333
- return _safe_int(self.data.get("dexterity"), 10)
333
+ return _safe_int(self._data.get("dexterity"), 10)
334
334
 
335
335
  @property
336
336
  def constitution(self) -> int:
337
- return _safe_int(self.data.get("constitution"), 10)
337
+ return _safe_int(self._data.get("constitution"), 10)
338
338
 
339
339
  @property
340
340
  def intelligence(self) -> int:
341
- return _safe_int(self.data.get("intelligence"), 10)
341
+ return _safe_int(self._data.get("intelligence"), 10)
342
342
 
343
343
  @property
344
344
  def wisdom(self) -> int:
345
- return _safe_int(self.data.get("wisdom"), 10)
345
+ return _safe_int(self._data.get("wisdom"), 10)
346
346
 
347
347
  @property
348
348
  def charisma(self) -> int:
349
- return _safe_int(self.data.get("charisma"), 10)
349
+ return _safe_int(self._data.get("charisma"), 10)
350
350
 
351
351
  def get_mod(self, stat: str) -> int:
352
352
  stat_lower = str(stat).lower()
@@ -385,14 +385,14 @@ class AliasBaseStats(_DirMixin):
385
385
 
386
386
  @dataclass
387
387
  class AliasLevels(_DirMixin):
388
- data: Mapping[str, Any]
388
+ _data: Mapping[str, Any]
389
389
  ATTRS: ClassVar[list[str]] = ["total_level"]
390
390
  METHODS: ClassVar[list[str]] = ["get"]
391
391
 
392
392
  @property
393
393
  def total_level(self) -> int | float:
394
394
  total = 0
395
- for _, value in self.data.items():
395
+ for _, value in self._data.items():
396
396
  try:
397
397
  total += value
398
398
  except Exception:
@@ -403,7 +403,7 @@ class AliasLevels(_DirMixin):
403
403
  return total
404
404
 
405
405
  def get(self, cls_name: str, default: int | float = 0) -> int | float:
406
- val = self.data.get(str(cls_name))
406
+ val = self._data.get(str(cls_name))
407
407
  if val is None:
408
408
  return default
409
409
  try:
@@ -412,40 +412,40 @@ class AliasLevels(_DirMixin):
412
412
  return default
413
413
 
414
414
  def __iter__(self) -> Iterable[tuple[str, Any]]:
415
- return iter(self.data.items())
415
+ return iter(self._data.items())
416
416
 
417
417
  def __getitem__(self, item: str) -> Any:
418
- return self.data[item]
418
+ return self._data[item]
419
419
 
420
420
 
421
421
  @dataclass
422
422
  class AliasAttack(_DirMixin):
423
- data: Mapping[str, Any]
423
+ _data: Mapping[str, Any]
424
424
  parent_statblock: Mapping[str, Any]
425
425
  ATTRS: ClassVar[list[str]] = ["name", "verb", "proper", "activation_type", "raw"]
426
426
  METHODS: ClassVar[list[str]] = []
427
427
 
428
428
  @property
429
429
  def name(self) -> str:
430
- return str(self.data.get("name", "Attack"))
430
+ return str(self._data.get("name", "Attack"))
431
431
 
432
432
  @property
433
433
  def verb(self) -> str | None:
434
- val = self.data.get("verb")
434
+ val = self._data.get("verb")
435
435
  return str(val) if val is not None else None
436
436
 
437
437
  @property
438
438
  def proper(self) -> bool:
439
- return bool(self.data.get("proper", False))
439
+ return bool(self._data.get("proper", False))
440
440
 
441
441
  @property
442
442
  def activation_type(self) -> int | None:
443
- raw = self.data.get("activation_type")
443
+ raw = self._data.get("activation_type")
444
444
  return int(raw) if raw is not None else None
445
445
 
446
446
  @property
447
447
  def raw(self) -> Mapping[str, Any]:
448
- return self.data.get("raw", self.data)
448
+ return self._data.get("raw", self._data)
449
449
 
450
450
  def __str__(self) -> str:
451
451
  damage = self.raw.get("damage")
@@ -483,17 +483,17 @@ class AliasAttackList(_DirMixin):
483
483
 
484
484
  @dataclass
485
485
  class AliasSkill(_DirMixin):
486
- data: Mapping[str, Any]
486
+ _data: Mapping[str, Any]
487
487
  ATTRS: ClassVar[list[str]] = ["value", "prof", "bonus", "adv"]
488
488
  METHODS: ClassVar[list[str]] = ["d20"]
489
489
 
490
490
  @property
491
491
  def value(self) -> int:
492
- return _safe_int(self.data.get("value"), 0)
492
+ return _safe_int(self._data.get("value"), 0)
493
493
 
494
494
  @property
495
495
  def prof(self) -> float | int:
496
- raw = self.data.get("prof")
496
+ raw = self._data.get("prof")
497
497
  try:
498
498
  return float(raw)
499
499
  except Exception:
@@ -501,11 +501,11 @@ class AliasSkill(_DirMixin):
501
501
 
502
502
  @property
503
503
  def bonus(self) -> int:
504
- return _safe_int(self.data.get("bonus"), 0)
504
+ return _safe_int(self._data.get("bonus"), 0)
505
505
 
506
506
  @property
507
507
  def adv(self) -> bool | None:
508
- val = self.data.get("adv")
508
+ val = self._data.get("adv")
509
509
  if val is None:
510
510
  return None
511
511
  return bool(val)
@@ -545,7 +545,7 @@ class AliasSkill(_DirMixin):
545
545
 
546
546
  @dataclass
547
547
  class AliasSkills(_DirMixin):
548
- data: Mapping[str, Any]
548
+ _data: Mapping[str, Any]
549
549
  prof_bonus: int
550
550
  abilities: Mapping[str, int]
551
551
  ATTRS: ClassVar[list[str]] = list(_SKILL_ABILITIES.keys())
@@ -559,7 +559,7 @@ class AliasSkills(_DirMixin):
559
559
 
560
560
  def _get_skill(self, name: str) -> AliasSkill:
561
561
  normalized = _SKILL_CANONICAL.get(name.lower().replace(" ", "").replace("_", ""), name)
562
- skill_data = self.data.get(normalized) or self.data.get(name) or {}
562
+ skill_data = self._data.get(normalized) or self._data.get(name) or {}
563
563
  if not skill_data:
564
564
  ability = _SKILL_ABILITIES.get(normalized)
565
565
  ability_mod = 0
@@ -579,7 +579,7 @@ class AliasSkills(_DirMixin):
579
579
 
580
580
  @dataclass
581
581
  class AliasSaves(_DirMixin):
582
- data: Mapping[str, Any]
582
+ _data: Mapping[str, Any]
583
583
  prof_bonus: int
584
584
  abilities: Mapping[str, int]
585
585
  ATTRS: ClassVar[list[str]] = []
@@ -587,7 +587,7 @@ class AliasSaves(_DirMixin):
587
587
 
588
588
  def get(self, base_stat: str) -> AliasSkill:
589
589
  normalized = base_stat.lower()
590
- raw = self.data.get(normalized) or self.data.get(base_stat) or {}
590
+ raw = self._data.get(normalized) or self._data.get(base_stat) or {}
591
591
  if isinstance(raw, (int, float)):
592
592
  raw = {"value": raw}
593
593
  if not raw:
@@ -612,14 +612,14 @@ class ResistanceEntry:
612
612
 
613
613
  @dataclass
614
614
  class AliasResistances(_DirMixin):
615
- data: Mapping[str, Any]
615
+ _data: Mapping[str, Any]
616
616
  ATTRS: ClassVar[list[str]] = ["resist", "vuln", "immune", "neutral"]
617
617
  METHODS: ClassVar[list[str]] = ["is_resistant", "is_immune", "is_vulnerable", "is_neutral"]
618
618
 
619
619
  @staticmethod
620
- def _entries(key: str, data: Mapping[str, Any]) -> list[ResistanceEntry]:
620
+ def _entries(key: str, _data: Mapping[str, Any]) -> list[ResistanceEntry]:
621
621
  entries = []
622
- for entry in data.get(key, []):
622
+ for entry in _data.get(key, []):
623
623
  entries.append(
624
624
  ResistanceEntry(
625
625
  dtype=str(entry.get("dtype", "")),
@@ -631,19 +631,19 @@ class AliasResistances(_DirMixin):
631
631
 
632
632
  @property
633
633
  def resist(self) -> list[ResistanceEntry]:
634
- return self._entries("resist", self.data)
634
+ return self._entries("resist", self._data)
635
635
 
636
636
  @property
637
637
  def vuln(self) -> list[ResistanceEntry]:
638
- return self._entries("vuln", self.data)
638
+ return self._entries("vuln", self._data)
639
639
 
640
640
  @property
641
641
  def immune(self) -> list[ResistanceEntry]:
642
- return self._entries("immune", self.data)
642
+ return self._entries("immune", self._data)
643
643
 
644
644
  @property
645
645
  def neutral(self) -> list[ResistanceEntry]:
646
- return self._entries("neutral", self.data)
646
+ return self._entries("neutral", self._data)
647
647
 
648
648
  def is_resistant(self, damage_type: str) -> bool:
649
649
  token = str(damage_type).lower()
@@ -675,32 +675,32 @@ class AliasResistances(_DirMixin):
675
675
 
676
676
  @dataclass
677
677
  class AliasSpellbookSpell(_DirMixin):
678
- data: Mapping[str, Any]
678
+ _data: Mapping[str, Any]
679
679
  ATTRS: ClassVar[list[str]] = ["name", "dc", "sab", "mod", "prepared"]
680
680
  METHODS: ClassVar[list[str]] = []
681
681
 
682
682
  @property
683
683
  def name(self) -> str:
684
- return str(self.data.get("name", "Spell"))
684
+ return str(self._data.get("name", "Spell"))
685
685
 
686
686
  @property
687
687
  def dc(self) -> int | None:
688
- raw = self.data.get("dc")
688
+ raw = self._data.get("dc")
689
689
  return int(raw) if raw is not None else None
690
690
 
691
691
  @property
692
692
  def sab(self) -> int | None:
693
- raw = self.data.get("sab")
693
+ raw = self._data.get("sab")
694
694
  return int(raw) if raw is not None else None
695
695
 
696
696
  @property
697
697
  def mod(self) -> int | None:
698
- raw = self.data.get("mod")
698
+ raw = self._data.get("mod")
699
699
  return int(raw) if raw is not None else None
700
700
 
701
701
  @property
702
702
  def prepared(self) -> bool:
703
- return bool(self.data.get("prepared", True))
703
+ return bool(self._data.get("prepared", True))
704
704
 
705
705
  def __getitem__(self, item: str) -> Any:
706
706
  return getattr(self, str(item))
@@ -711,7 +711,7 @@ class AliasSpellbookSpell(_DirMixin):
711
711
 
712
712
  @dataclass
713
713
  class AliasSpellbook(_DirMixin):
714
- data: MutableMapping[str, Any]
714
+ _data: MutableMapping[str, Any]
715
715
  _spells_cache: list[AliasSpellbookSpell] = field(default_factory=list, init=False)
716
716
  ATTRS: ClassVar[list[str]] = [
717
717
  "dc",
@@ -739,40 +739,40 @@ class AliasSpellbook(_DirMixin):
739
739
 
740
740
  @property
741
741
  def dc(self) -> int:
742
- return _safe_int(self.data.get("dc"), 10)
742
+ return _safe_int(self._data.get("dc"), 10)
743
743
 
744
744
  @property
745
745
  def sab(self) -> int:
746
- return _safe_int(self.data.get("sab"), 0)
746
+ return _safe_int(self._data.get("sab"), 0)
747
747
 
748
748
  @property
749
749
  def caster_level(self) -> int:
750
- return _safe_int(self.data.get("caster_level"), 0)
750
+ return _safe_int(self._data.get("caster_level"), 0)
751
751
 
752
752
  @property
753
753
  def spell_mod(self) -> int:
754
- return _safe_int(self.data.get("spell_mod"), 0)
754
+ return _safe_int(self._data.get("spell_mod"), 0)
755
755
 
756
756
  @property
757
757
  def spells(self) -> list[AliasSpellbookSpell]:
758
758
  if not self._spells_cache:
759
- spell_list = self.data.get("spells") or []
759
+ spell_list = self._data.get("spells") or []
760
760
  self._spells_cache = [AliasSpellbookSpell(s) for s in spell_list]
761
761
  return self._spells_cache
762
762
 
763
763
  @property
764
764
  def pact_slot_level(self) -> int | None:
765
- raw = self.data.get("pact_slot_level")
765
+ raw = self._data.get("pact_slot_level")
766
766
  return int(raw) if raw is not None else None
767
767
 
768
768
  @property
769
769
  def num_pact_slots(self) -> int | None:
770
- raw = self.data.get("num_pact_slots")
770
+ raw = self._data.get("num_pact_slots")
771
771
  return int(raw) if raw is not None else None
772
772
 
773
773
  @property
774
774
  def max_pact_slots(self) -> int | None:
775
- raw = self.data.get("max_pact_slots")
775
+ raw = self._data.get("max_pact_slots")
776
776
  return int(raw) if raw is not None else None
777
777
 
778
778
  def find(self, spell_name: str) -> list[AliasSpellbookSpell]:
@@ -785,17 +785,17 @@ class AliasSpellbook(_DirMixin):
785
785
  return f"{slots}/{max_slots}"
786
786
 
787
787
  def get_max_slots(self, level: int) -> int:
788
- slots = self.data.get("max_slots") or {}
788
+ slots = self._data.get("max_slots") or {}
789
789
  return _safe_int(slots.get(int(level)), 0)
790
790
 
791
791
  def get_slots(self, level: int) -> int:
792
- slots = self.data.get("slots") or {}
792
+ slots = self._data.get("slots") or {}
793
793
  if int(level) == 0:
794
794
  return 1
795
795
  return _safe_int(slots.get(int(level)), 0)
796
796
 
797
797
  def set_slots(self, level: int, value: int, pact: bool = True) -> int:
798
- slots = self.data.setdefault("slots", {})
798
+ slots = self._data.setdefault("slots", {})
799
799
  slots[int(level)] = int(value)
800
800
  return slots[int(level)]
801
801
 
@@ -804,16 +804,16 @@ class AliasSpellbook(_DirMixin):
804
804
  return self.set_slots(level, max(0, current - 1))
805
805
 
806
806
  def reset_slots(self) -> None:
807
- slots = self.data.get("slots") or {}
808
- max_slots = self.data.get("max_slots") or {}
807
+ slots = self._data.get("slots") or {}
808
+ max_slots = self._data.get("max_slots") or {}
809
809
  for level, maximum in max_slots.items():
810
810
  slots[int(level)] = _safe_int(maximum)
811
- self.data["slots"] = slots
811
+ self._data["slots"] = slots
812
812
 
813
813
  def reset_pact_slots(self) -> None:
814
814
  if self.max_pact_slots is None:
815
815
  return
816
- self.data["num_pact_slots"] = self.max_pact_slots
816
+ self._data["num_pact_slots"] = self.max_pact_slots
817
817
 
818
818
  def remaining_casts_of(self, spell: str, level: int) -> str:
819
819
  return self.slots_str(level)
@@ -833,14 +833,14 @@ class AliasSpellbook(_DirMixin):
833
833
 
834
834
  @dataclass
835
835
  class AliasCoinpurse(_DirMixin):
836
- data: MutableMapping[str, Any] = field(default_factory=dict)
836
+ _data: MutableMapping[str, Any] = field(default_factory=dict)
837
837
  ATTRS: ClassVar[list[str]] = ["pp", "gp", "ep", "sp", "cp", "total"]
838
838
  METHODS: ClassVar[list[str]] = ["coin_str", "compact_str", "modify_coins", "set_coins", "autoconvert", "get_coins"]
839
839
 
840
840
  def __getattr__(self, item: str) -> Any:
841
841
  if item in {"pp", "gp", "ep", "sp", "cp"}:
842
- return _safe_int(self.data.get(item), 0)
843
- return self.data.get(item)
842
+ return _safe_int(self._data.get(item), 0)
843
+ return self._data.get(item)
844
844
 
845
845
  def __getitem__(self, item: str) -> Any:
846
846
  return getattr(self, str(item))
@@ -866,26 +866,26 @@ class AliasCoinpurse(_DirMixin):
866
866
  cp: int = 0,
867
867
  autoconvert: bool = True,
868
868
  ) -> dict[str, Any]:
869
- self.data["pp"] = self.pp + int(pp)
870
- self.data["gp"] = self.gp + int(gp)
871
- self.data["ep"] = self.ep + int(ep)
872
- self.data["sp"] = self.sp + int(sp)
873
- self.data["cp"] = self.cp + int(cp)
869
+ self._data["pp"] = self.pp + int(pp)
870
+ self._data["gp"] = self.gp + int(gp)
871
+ self._data["ep"] = self.ep + int(ep)
872
+ self._data["sp"] = self.sp + int(sp)
873
+ self._data["cp"] = self.cp + int(cp)
874
874
  return self.get_coins()
875
875
 
876
876
  def set_coins(self, pp: int, gp: int, ep: int, sp: int, cp: int) -> None:
877
- self.data["pp"] = int(pp)
878
- self.data["gp"] = int(gp)
879
- self.data["ep"] = int(ep)
880
- self.data["sp"] = int(sp)
881
- self.data["cp"] = int(cp)
877
+ self._data["pp"] = int(pp)
878
+ self._data["gp"] = int(gp)
879
+ self._data["ep"] = int(ep)
880
+ self._data["sp"] = int(sp)
881
+ self._data["cp"] = int(cp)
882
882
 
883
883
  def autoconvert(self) -> None:
884
884
  total_cp = int(self.total * 100)
885
- self.data["pp"], remainder = divmod(total_cp, 1000)
886
- self.data["gp"], remainder = divmod(remainder, 100)
887
- self.data["ep"], remainder = divmod(remainder, 50)
888
- self.data["sp"], self.data["cp"] = divmod(remainder, 10)
885
+ self._data["pp"], remainder = divmod(total_cp, 1000)
886
+ self._data["gp"], remainder = divmod(remainder, 100)
887
+ self._data["ep"], remainder = divmod(remainder, 50)
888
+ self._data["sp"], self._data["cp"] = divmod(remainder, 10)
889
889
 
890
890
  def get_coins(self) -> dict[str, Any]:
891
891
  return {"pp": self.pp, "gp": self.gp, "ep": self.ep, "sp": self.sp, "cp": self.cp, "total": self.total}
@@ -893,23 +893,23 @@ class AliasCoinpurse(_DirMixin):
893
893
 
894
894
  @dataclass
895
895
  class AliasDeathSaves(_DirMixin):
896
- data: MutableMapping[str, Any] = field(default_factory=dict)
896
+ _data: MutableMapping[str, Any] = field(default_factory=dict)
897
897
  ATTRS: ClassVar[list[str]] = ["successes", "fails"]
898
898
  METHODS: ClassVar[list[str]] = ["succeed", "fail", "is_stable", "is_dead", "reset"]
899
899
 
900
900
  @property
901
901
  def successes(self) -> int:
902
- return _safe_int(self.data.get("successes"), 0)
902
+ return _safe_int(self._data.get("successes"), 0)
903
903
 
904
904
  @property
905
905
  def fails(self) -> int:
906
- return _safe_int(self.data.get("fails"), 0)
906
+ return _safe_int(self._data.get("fails"), 0)
907
907
 
908
908
  def succeed(self, num: int = 1) -> None:
909
- self.data["successes"] = self.successes + int(num)
909
+ self._data["successes"] = self.successes + int(num)
910
910
 
911
911
  def fail(self, num: int = 1) -> None:
912
- self.data["fails"] = self.fails + int(num)
912
+ self._data["fails"] = self.fails + int(num)
913
913
 
914
914
  def is_stable(self) -> bool:
915
915
  return self.successes >= 3 and self.fails < 3
@@ -918,8 +918,8 @@ class AliasDeathSaves(_DirMixin):
918
918
  return self.fails >= 3
919
919
 
920
920
  def reset(self) -> None:
921
- self.data["successes"] = 0
922
- self.data["fails"] = 0
921
+ self._data["successes"] = 0
922
+ self._data["fails"] = 0
923
923
 
924
924
  def __str__(self) -> str:
925
925
  return f"{self.successes} successes / {self.fails} failures"
@@ -927,7 +927,7 @@ class AliasDeathSaves(_DirMixin):
927
927
 
928
928
  @dataclass
929
929
  class AliasAction(_DirMixin):
930
- data: Mapping[str, Any]
930
+ _data: Mapping[str, Any]
931
931
  parent_statblock: Mapping[str, Any]
932
932
  ATTRS: ClassVar[list[str]] = ["name", "activation_type", "activation_type_name", "description", "snippet"]
933
933
  METHODS: ClassVar[list[str]] = []
@@ -935,29 +935,29 @@ class AliasAction(_DirMixin):
935
935
  @property
936
936
  def name(self) -> str:
937
937
  """Action name."""
938
- return str(self.data.get("name", "Action"))
938
+ return str(self._data.get("name", "Action"))
939
939
 
940
940
  @property
941
941
  def activation_type(self) -> int | None:
942
942
  """Numeric activation type (matches Avrae constants)."""
943
- raw = self.data.get("activation_type")
943
+ raw = self._data.get("activation_type")
944
944
  return int(raw) if raw is not None else None
945
945
 
946
946
  @property
947
947
  def activation_type_name(self) -> str | None:
948
948
  """Human-readable activation type (e.g., ACTION, BONUS_ACTION)."""
949
- val = self.data.get("activation_type_name")
949
+ val = self._data.get("activation_type_name")
950
950
  return str(val) if val is not None else None
951
951
 
952
952
  @property
953
953
  def description(self) -> str:
954
954
  """Long description of the action."""
955
- return str(self.data.get("description", ""))
955
+ return str(self._data.get("description", ""))
956
956
 
957
957
  @property
958
958
  def snippet(self) -> str:
959
959
  """Short snippet shown in the sheet for the action."""
960
- return str(self.data.get("snippet", self.description))
960
+ return str(self._data.get("snippet", self.description))
961
961
 
962
962
  def __str__(self) -> str:
963
963
  return f"**{self.name}**: {self.description}"
@@ -976,7 +976,7 @@ class AliasAction(_DirMixin):
976
976
 
977
977
  @dataclass
978
978
  class AliasCustomCounter(_DirMixin):
979
- data: MutableMapping[str, Any] = field(default_factory=dict)
979
+ _data: MutableMapping[str, Any] = field(default_factory=dict)
980
980
  ATTRS: ClassVar[list[str]] = [
981
981
  "name",
982
982
  "title",
@@ -993,48 +993,48 @@ class AliasCustomCounter(_DirMixin):
993
993
 
994
994
  @property
995
995
  def name(self) -> str:
996
- return str(self.data.get("name", "Counter"))
996
+ return str(self._data.get("name", "Counter"))
997
997
 
998
998
  @property
999
999
  def title(self) -> str | None:
1000
- val = self.data.get("title")
1000
+ val = self._data.get("title")
1001
1001
  return str(val) if val is not None else None
1002
1002
 
1003
1003
  @property
1004
1004
  def desc(self) -> str | None:
1005
- val = self.data.get("desc")
1005
+ val = self._data.get("desc")
1006
1006
  return str(val) if val is not None else None
1007
1007
 
1008
1008
  @property
1009
1009
  def value(self) -> int:
1010
- return _safe_int(self.data.get("value"), 0)
1010
+ return _safe_int(self._data.get("value"), 0)
1011
1011
 
1012
1012
  @property
1013
1013
  def max(self) -> int:
1014
- return _safe_int(self.data.get("max"), 2**31 - 1)
1014
+ return _safe_int(self._data.get("max"), 2**31 - 1)
1015
1015
 
1016
1016
  @property
1017
1017
  def min(self) -> int:
1018
- return _safe_int(self.data.get("min"), -(2**31))
1018
+ return _safe_int(self._data.get("min"), -(2**31))
1019
1019
 
1020
1020
  @property
1021
1021
  def reset_on(self) -> str | None:
1022
- val = self.data.get("reset_on")
1022
+ val = self._data.get("reset_on")
1023
1023
  return str(val) if val is not None else None
1024
1024
 
1025
1025
  @property
1026
1026
  def display_type(self) -> str | None:
1027
- val = self.data.get("display_type")
1027
+ val = self._data.get("display_type")
1028
1028
  return str(val) if val is not None else None
1029
1029
 
1030
1030
  @property
1031
1031
  def reset_to(self) -> int | None:
1032
- raw = self.data.get("reset_to")
1032
+ raw = self._data.get("reset_to")
1033
1033
  return int(raw) if raw is not None else None
1034
1034
 
1035
1035
  @property
1036
1036
  def reset_by(self) -> str | None:
1037
- val = self.data.get("reset_by")
1037
+ val = self._data.get("reset_by")
1038
1038
  return str(val) if val is not None else None
1039
1039
 
1040
1040
  def mod(self, value: int, strict: bool = False) -> int:
@@ -1046,7 +1046,7 @@ class AliasCustomCounter(_DirMixin):
1046
1046
  if val > self.max or val < self.min:
1047
1047
  raise ValueError("Counter out of bounds")
1048
1048
  val = max(self.min, min(val, self.max))
1049
- self.data["value"] = val
1049
+ self._data["value"] = val
1050
1050
  return val
1051
1051
 
1052
1052
  def reset(self) -> dict[str, Any]:
@@ -1085,7 +1085,7 @@ class AliasCustomCounter(_DirMixin):
1085
1085
  # === StatBlock + Character ===
1086
1086
  @dataclass
1087
1087
  class AliasStatBlock(_DirMixin):
1088
- data: MutableMapping[str, Any] = field(default_factory=dict)
1088
+ _data: MutableMapping[str, Any] = field(default_factory=dict)
1089
1089
  ATTRS: ClassVar[list[str]] = [
1090
1090
  "name",
1091
1091
  "stats",
@@ -1104,32 +1104,32 @@ class AliasStatBlock(_DirMixin):
1104
1104
  METHODS: ClassVar[list[str]] = ["set_hp", "modify_hp", "hp_str", "reset_hp", "set_temp_hp"]
1105
1105
 
1106
1106
  def _prof_bonus(self) -> int:
1107
- stats = self.data.get("stats") or {}
1107
+ stats = self._data.get("stats") or {}
1108
1108
  if "prof_bonus" in stats:
1109
1109
  return _safe_int(stats.get("prof_bonus"), 2)
1110
- levels = self.data.get("levels") or self.data.get("class_levels") or {}
1110
+ levels = self._data.get("levels") or self._data.get("class_levels") or {}
1111
1111
  total_level = sum(_safe_int(val, 0) for val in levels.values()) or 1
1112
1112
  return max(2, 2 + (math.ceil(total_level / 4)))
1113
1113
 
1114
1114
  @property
1115
1115
  def name(self) -> str:
1116
1116
  """Character or statblock name."""
1117
- return str(self.data.get("name", "Statblock"))
1117
+ return str(self._data.get("name", "Statblock"))
1118
1118
 
1119
1119
  @property
1120
1120
  def stats(self) -> AliasBaseStats:
1121
1121
  """Ability scores and proficiency bonus helper."""
1122
- return AliasBaseStats(self.data.get("stats") or {}, prof_bonus_override=self._prof_bonus())
1122
+ return AliasBaseStats(self._data.get("stats") or {}, prof_bonus_override=self._prof_bonus())
1123
1123
 
1124
1124
  @property
1125
1125
  def levels(self) -> AliasLevels:
1126
1126
  """Class levels keyed by class name."""
1127
- return AliasLevels(self.data.get("levels") or self.data.get("class_levels") or {})
1127
+ return AliasLevels(self._data.get("levels") or self._data.get("class_levels") or {})
1128
1128
 
1129
1129
  @property
1130
1130
  def attacks(self) -> AliasAttackList:
1131
1131
  """Attacks available on the statblock."""
1132
- return AliasAttackList(self.data.get("attacks") or [], self.data)
1132
+ return AliasAttackList(self._data.get("attacks") or [], self._data)
1133
1133
 
1134
1134
  @property
1135
1135
  def skills(self) -> AliasSkills:
@@ -1142,7 +1142,7 @@ class AliasStatBlock(_DirMixin):
1142
1142
  "wisdom": self.stats.wisdom,
1143
1143
  "charisma": self.stats.charisma,
1144
1144
  }
1145
- return AliasSkills(self.data.get("skills") or {}, self._prof_bonus(), abilities)
1145
+ return AliasSkills(self._data.get("skills") or {}, self._prof_bonus(), abilities)
1146
1146
 
1147
1147
  @property
1148
1148
  def saves(self) -> AliasSaves:
@@ -1161,51 +1161,51 @@ class AliasStatBlock(_DirMixin):
1161
1161
  "wis": self.stats.wisdom,
1162
1162
  "cha": self.stats.charisma,
1163
1163
  }
1164
- return AliasSaves(self.data.get("saves") or {}, self._prof_bonus(), abilities)
1164
+ return AliasSaves(self._data.get("saves") or {}, self._prof_bonus(), abilities)
1165
1165
 
1166
1166
  @property
1167
1167
  def resistances(self) -> AliasResistances:
1168
1168
  """Damage resistances, immunities, and vulnerabilities."""
1169
- return AliasResistances(self.data.get("resistances") or {})
1169
+ return AliasResistances(self._data.get("resistances") or {})
1170
1170
 
1171
1171
  @property
1172
1172
  def ac(self) -> int | None:
1173
1173
  """Armor class."""
1174
- raw = self.data.get("ac")
1174
+ raw = self._data.get("ac")
1175
1175
  return int(raw) if raw is not None else None
1176
1176
 
1177
1177
  @property
1178
1178
  def max_hp(self) -> int | None:
1179
1179
  """Maximum hit points."""
1180
- raw = self.data.get("max_hp")
1180
+ raw = self._data.get("max_hp")
1181
1181
  return int(raw) if raw is not None else None
1182
1182
 
1183
1183
  @property
1184
1184
  def hp(self) -> int | None:
1185
1185
  """Current hit points."""
1186
- raw = self.data.get("hp")
1186
+ raw = self._data.get("hp")
1187
1187
  return int(raw) if raw is not None else None
1188
1188
 
1189
1189
  @property
1190
1190
  def temp_hp(self) -> int:
1191
1191
  """Temporary hit points."""
1192
- return _safe_int(self.data.get("temp_hp"), 0)
1192
+ return _safe_int(self._data.get("temp_hp"), 0)
1193
1193
 
1194
1194
  @property
1195
1195
  def spellbook(self) -> AliasSpellbook:
1196
1196
  """Known/prepared spells grouped by level."""
1197
- return AliasSpellbook(self.data.get("spellbook") or {})
1197
+ return AliasSpellbook(self._data.get("spellbook") or {})
1198
1198
 
1199
1199
  @property
1200
1200
  def creature_type(self) -> str | None:
1201
1201
  """Creature type (e.g., humanoid, undead)."""
1202
- val = self.data.get("creature_type")
1202
+ val = self._data.get("creature_type")
1203
1203
  return str(val) if val is not None else None
1204
1204
 
1205
1205
  def set_hp(self, new_hp: int) -> int:
1206
1206
  """Set current hit points."""
1207
- self.data["hp"] = int(new_hp)
1208
- return self.data["hp"]
1207
+ self._data["hp"] = int(new_hp)
1208
+ return self._data["hp"]
1209
1209
 
1210
1210
  def modify_hp(self, amount: int, ignore_temp: bool = False, overflow: bool = True) -> int:
1211
1211
  """Adjust hit points by `amount`, respecting overflow limits when requested."""
@@ -1213,7 +1213,7 @@ class AliasStatBlock(_DirMixin):
1213
1213
  new_hp = hp + int(amount)
1214
1214
  if not overflow and self.max_hp is not None:
1215
1215
  new_hp = max(0, min(new_hp, self.max_hp))
1216
- self.data["hp"] = new_hp
1216
+ self._data["hp"] = new_hp
1217
1217
  return new_hp
1218
1218
 
1219
1219
  def hp_str(self) -> str:
@@ -1223,17 +1223,17 @@ class AliasStatBlock(_DirMixin):
1223
1223
  def reset_hp(self) -> int:
1224
1224
  """Restore to max HP and clear temp HP."""
1225
1225
  if self.max_hp is not None:
1226
- self.data["hp"] = self.max_hp
1227
- self.data["temp_hp"] = 0
1226
+ self._data["hp"] = self.max_hp
1227
+ self._data["temp_hp"] = 0
1228
1228
  return self.hp or 0
1229
1229
 
1230
1230
  def set_temp_hp(self, new_temp: int) -> int:
1231
1231
  """Set temporary hit points."""
1232
- self.data["temp_hp"] = int(new_temp)
1232
+ self._data["temp_hp"] = int(new_temp)
1233
1233
  return self.temp_hp
1234
1234
 
1235
1235
  def __getattr__(self, item: str) -> Any:
1236
- return self.data.get(item)
1236
+ return self._data.get(item)
1237
1237
 
1238
1238
  def __getitem__(self, item: str) -> Any:
1239
1239
  if isinstance(item, int) or (isinstance(item, str) and item.isdigit()):
@@ -1284,88 +1284,88 @@ class CharacterAPI(AliasStatBlock):
1284
1284
  ]
1285
1285
 
1286
1286
  def _consumable_map(self) -> MutableMapping[str, MutableMapping[str, Any]]:
1287
- consumables = self.data.setdefault("consumables", {})
1287
+ consumables = self._data.setdefault("consumables", {})
1288
1288
  if isinstance(consumables, list):
1289
1289
  # normalize list payloads to map
1290
1290
  mapped: dict[str, MutableMapping[str, Any]] = {}
1291
1291
  for item in consumables:
1292
1292
  name = str(item.get("name", f"cc_{len(mapped)}"))
1293
1293
  mapped[name] = dict(item)
1294
- self.data["consumables"] = mapped
1294
+ self._data["consumables"] = mapped
1295
1295
  consumables = mapped
1296
1296
  return consumables # type: ignore[return-value]
1297
1297
 
1298
1298
  @property
1299
1299
  def actions(self) -> list[AliasAction]:
1300
1300
  """Actions on the character sheet (mapped from Beyond/custom actions)."""
1301
- acts = self.data.get("actions") or []
1302
- return [AliasAction(a, self.data) for a in acts]
1301
+ acts = self._data.get("actions") or []
1302
+ return [AliasAction(a, self._data) for a in acts]
1303
1303
 
1304
1304
  @property
1305
1305
  def coinpurse(self) -> AliasCoinpurse:
1306
1306
  """Coin totals by denomination."""
1307
- return AliasCoinpurse(self.data.get("coinpurse") or {})
1307
+ return AliasCoinpurse(self._data.get("coinpurse") or {})
1308
1308
 
1309
1309
  @property
1310
1310
  def csettings(self) -> Mapping[str, Any]:
1311
1311
  """Character settings blob."""
1312
- return self.data.get("csettings", {})
1312
+ return self._data.get("csettings", {})
1313
1313
 
1314
1314
  @property
1315
1315
  def race(self) -> str | None:
1316
1316
  """Race label."""
1317
- val = self.data.get("race")
1317
+ val = self._data.get("race")
1318
1318
  return str(val) if val is not None else None
1319
1319
 
1320
1320
  @property
1321
1321
  def background(self) -> str | None:
1322
1322
  """Background name."""
1323
- val = self.data.get("background")
1323
+ val = self._data.get("background")
1324
1324
  return str(val) if val is not None else None
1325
1325
 
1326
1326
  @property
1327
1327
  def owner(self) -> int | None:
1328
1328
  """Discord user id of the owning account."""
1329
- raw = self.data.get("owner")
1329
+ raw = self._data.get("owner")
1330
1330
  return int(raw) if raw is not None else None
1331
1331
 
1332
1332
  @property
1333
1333
  def upstream(self) -> str | None:
1334
1334
  """Upstream character id (e.g., Beyond character slug)."""
1335
- val = self.data.get("upstream")
1335
+ val = self._data.get("upstream")
1336
1336
  return str(val) if val is not None else None
1337
1337
 
1338
1338
  @property
1339
1339
  def sheet_type(self) -> str | None:
1340
1340
  """Source sheet provider (beyond, custom, etc.)."""
1341
- val = self.data.get("sheet_type")
1341
+ val = self._data.get("sheet_type")
1342
1342
  return str(val) if val is not None else None
1343
1343
 
1344
1344
  @property
1345
1345
  def cvars(self) -> Mapping[str, Any]:
1346
1346
  """Character variables (string values)."""
1347
- return dict(self.data.get("cvars") or {})
1347
+ return dict(self._data.get("cvars") or {})
1348
1348
 
1349
1349
  def get_cvar(self, name: str, default: Any = None) -> Optional[str]:
1350
1350
  """Fetch a character variable, returning `default` if missing."""
1351
- val = self.data.setdefault("cvars", {}).get(str(name), default)
1351
+ val = self._data.setdefault("cvars", {}).get(str(name), default)
1352
1352
  return str(val) if val is not None else default
1353
1353
 
1354
1354
  def set_cvar(self, name: str, val: str) -> Optional[str]:
1355
1355
  """Sets a character variable. Avrae stores cvars as strings."""
1356
1356
  str_val = str(val) if val is not None else None
1357
- self.data.setdefault("cvars", {})[str(name)] = str_val
1357
+ self._data.setdefault("cvars", {})[str(name)] = str_val
1358
1358
  return str_val
1359
1359
 
1360
1360
  def set_cvar_nx(self, name: str, val: str) -> str:
1361
1361
  """Set a character variable only if it does not already exist."""
1362
- cvars = self.data.setdefault("cvars", {})
1362
+ cvars = self._data.setdefault("cvars", {})
1363
1363
  str_val = str(val) if val is not None else None
1364
1364
  return cvars.setdefault(str(name), str_val)
1365
1365
 
1366
1366
  def delete_cvar(self, name: str) -> Optional[str]:
1367
1367
  """Delete a character variable and return its old value if present."""
1368
- return self.data.setdefault("cvars", {}).pop(str(name), None)
1368
+ return self._data.setdefault("cvars", {}).pop(str(name), None)
1369
1369
 
1370
1370
  @property
1371
1371
  def consumables(self) -> list[AliasCustomCounter]:
@@ -1509,18 +1509,18 @@ class CharacterAPI(AliasStatBlock):
1509
1509
  @property
1510
1510
  def death_saves(self) -> AliasDeathSaves:
1511
1511
  """Death save successes/failures."""
1512
- return AliasDeathSaves(self.data.get("death_saves") or {})
1512
+ return AliasDeathSaves(self._data.get("death_saves") or {})
1513
1513
 
1514
1514
  @property
1515
1515
  def description(self) -> str | None:
1516
1516
  """Character description/biography."""
1517
- val = self.data.get("description")
1517
+ val = self._data.get("description")
1518
1518
  return str(val) if val is not None else None
1519
1519
 
1520
1520
  @property
1521
1521
  def image(self) -> str | None:
1522
1522
  """Avatar or sheet image URL."""
1523
- val = self.data.get("image")
1523
+ val = self._data.get("image")
1524
1524
  return str(val) if val is not None else None
1525
1525
 
1526
1526
 
@@ -1530,7 +1530,7 @@ MAX_COMBAT_METADATA_SIZE = 100000
1530
1530
 
1531
1531
  @dataclass
1532
1532
  class SimpleEffect(_DirMixin):
1533
- data: MutableMapping[str, Any]
1533
+ _data: MutableMapping[str, Any]
1534
1534
  ATTRS: ClassVar[list[str]] = [
1535
1535
  "name",
1536
1536
  "duration",
@@ -1549,61 +1549,61 @@ class SimpleEffect(_DirMixin):
1549
1549
 
1550
1550
  @property
1551
1551
  def name(self) -> str:
1552
- return str(self.data.get("name", "Effect"))
1552
+ return str(self._data.get("name", "Effect"))
1553
1553
 
1554
1554
  @property
1555
1555
  def duration(self) -> int | None:
1556
- raw = self.data.get("duration")
1556
+ raw = self._data.get("duration")
1557
1557
  return int(raw) if raw is not None else None
1558
1558
 
1559
1559
  @property
1560
1560
  def remaining(self) -> int | None:
1561
- raw = self.data.get("remaining")
1561
+ raw = self._data.get("remaining")
1562
1562
  return int(raw) if raw is not None else None
1563
1563
 
1564
1564
  @property
1565
1565
  def effect(self) -> Mapping[str, Any]:
1566
- return self.data.get("effects") or self.data.get("effect") or {}
1566
+ return self._data.get("effects") or self._data.get("effect") or {}
1567
1567
 
1568
1568
  @property
1569
1569
  def attacks(self) -> list[Mapping[str, Any]]:
1570
- return list(self.data.get("attacks") or [])
1570
+ return list(self._data.get("attacks") or [])
1571
1571
 
1572
1572
  @property
1573
1573
  def buttons(self) -> list[Mapping[str, Any]]:
1574
- return list(self.data.get("buttons") or [])
1574
+ return list(self._data.get("buttons") or [])
1575
1575
 
1576
1576
  @property
1577
1577
  def conc(self) -> bool:
1578
- return bool(self.data.get("conc") or self.data.get("concentration", False))
1578
+ return bool(self._data.get("conc") or self._data.get("concentration", False))
1579
1579
 
1580
1580
  @property
1581
1581
  def desc(self) -> str | None:
1582
- val = self.data.get("desc")
1582
+ val = self._data.get("desc")
1583
1583
  return str(val) if val is not None else None
1584
1584
 
1585
1585
  @property
1586
1586
  def ticks_on_end(self) -> bool:
1587
- return bool(self.data.get("ticks_on_end") or self.data.get("end_on_turn_end", False))
1587
+ return bool(self._data.get("ticks_on_end") or self._data.get("end_on_turn_end", False))
1588
1588
 
1589
1589
  @property
1590
1590
  def combatant_name(self) -> str | None:
1591
- val = self.data.get("combatant_name")
1591
+ val = self._data.get("combatant_name")
1592
1592
  return str(val) if val is not None else None
1593
1593
 
1594
1594
  @property
1595
1595
  def parent(self) -> "SimpleEffect" | None:
1596
- parent = self.data.get("parent")
1596
+ parent = self._data.get("parent")
1597
1597
  return SimpleEffect(parent) if parent else None
1598
1598
 
1599
1599
  @property
1600
1600
  def children(self) -> list["SimpleEffect"]:
1601
- return [SimpleEffect(c) for c in self.data.get("children", [])]
1601
+ return [SimpleEffect(c) for c in self._data.get("children", [])]
1602
1602
 
1603
1603
  def set_parent(self, parent: "SimpleEffect") -> None:
1604
1604
  if not isinstance(parent, SimpleEffect):
1605
1605
  raise TypeError("Parent effect must be a SimpleEffect.")
1606
- self.data["parent"] = parent.data
1606
+ self._data["parent"] = parent._data
1607
1607
 
1608
1608
  def __getitem__(self, item: str) -> Any:
1609
1609
  return getattr(self, str(item))
@@ -1640,68 +1640,68 @@ class SimpleCombatant(AliasStatBlock):
1640
1640
 
1641
1641
  def __post_init__(self) -> None:
1642
1642
  super().__post_init__()
1643
- self.data.setdefault("type", "combatant")
1643
+ self._data.setdefault("type", "combatant")
1644
1644
 
1645
1645
  @property
1646
1646
  def id(self) -> str | None:
1647
1647
  """Unique combatant id."""
1648
- val = self.data.get("id")
1648
+ val = self._data.get("id")
1649
1649
  return str(val) if val is not None else None
1650
1650
 
1651
1651
  @property
1652
1652
  def effects(self) -> list[SimpleEffect]:
1653
1653
  """Active effects on the combatant."""
1654
- return [SimpleEffect(e) for e in self.data.get("effects", [])]
1654
+ return [SimpleEffect(e) for e in self._data.get("effects", [])]
1655
1655
 
1656
1656
  @property
1657
1657
  def init(self) -> int:
1658
1658
  """Initiative score."""
1659
- return _safe_int(self.data.get("init"), 0)
1659
+ return _safe_int(self._data.get("init"), 0)
1660
1660
 
1661
1661
  @property
1662
1662
  def initmod(self) -> int:
1663
1663
  """Initiative modifier."""
1664
- return _safe_int(self.data.get("initmod"), 0)
1664
+ return _safe_int(self._data.get("initmod"), 0)
1665
1665
 
1666
1666
  @property
1667
1667
  def type(self) -> str:
1668
1668
  """Combatant type (combatant/group)."""
1669
- return str(self.data.get("type", "combatant"))
1669
+ return str(self._data.get("type", "combatant"))
1670
1670
 
1671
1671
  @property
1672
1672
  def note(self) -> str | None:
1673
1673
  """DM note attached to the combatant."""
1674
- val = self.data.get("note")
1674
+ val = self._data.get("note")
1675
1675
  return str(val) if val is not None else None
1676
1676
 
1677
1677
  @property
1678
1678
  def controller(self) -> int | None:
1679
1679
  """Discord id of the controller (if any)."""
1680
- raw = self.data.get("controller")
1680
+ raw = self._data.get("controller")
1681
1681
  return int(raw) if raw is not None else None
1682
1682
 
1683
1683
  @property
1684
1684
  def group(self) -> str | None:
1685
1685
  """Group name the combatant belongs to."""
1686
- val = self.data.get("group")
1686
+ val = self._data.get("group")
1687
1687
  return str(val) if val is not None else None
1688
1688
 
1689
1689
  @property
1690
1690
  def race(self) -> str | None:
1691
1691
  """Race/creature type label."""
1692
- val = self.data.get("race")
1692
+ val = self._data.get("race")
1693
1693
  return str(val) if val is not None else None
1694
1694
 
1695
1695
  @property
1696
1696
  def monster_name(self) -> str | None:
1697
1697
  """Monster name if this combatant represents a monster."""
1698
- val = self.data.get("monster_name")
1698
+ val = self._data.get("monster_name")
1699
1699
  return str(val) if val is not None else None
1700
1700
 
1701
1701
  @property
1702
1702
  def is_hidden(self) -> bool:
1703
1703
  """Whether the combatant is hidden in the tracker."""
1704
- return bool(self.data.get("is_hidden", False))
1704
+ return bool(self._data.get("is_hidden", False))
1705
1705
 
1706
1706
  def save(self, ability: str, adv: bool | None = None) -> SimpleRollResult:
1707
1707
  """Roll a saving throw using the combatant's stats."""
@@ -1740,28 +1740,28 @@ class SimpleCombatant(AliasStatBlock):
1740
1740
 
1741
1741
  def set_ac(self, ac: int) -> None:
1742
1742
  """Set armor class."""
1743
- self.data["ac"] = int(ac)
1743
+ self._data["ac"] = int(ac)
1744
1744
 
1745
1745
  def set_maxhp(self, maxhp: int) -> None:
1746
1746
  """Set maximum HP."""
1747
- self.data["max_hp"] = int(maxhp)
1747
+ self._data["max_hp"] = int(maxhp)
1748
1748
 
1749
1749
  def set_init(self, init: int) -> None:
1750
1750
  """Set initiative score."""
1751
- self.data["init"] = int(init)
1751
+ self._data["init"] = int(init)
1752
1752
 
1753
1753
  def set_name(self, name: str) -> None:
1754
1754
  """Rename the combatant."""
1755
- self.data["name"] = str(name)
1755
+ self._data["name"] = str(name)
1756
1756
 
1757
1757
  def set_group(self, group: str | None) -> str | None:
1758
1758
  """Assign the combatant to a group."""
1759
- self.data["group"] = str(group) if group is not None else None
1759
+ self._data["group"] = str(group) if group is not None else None
1760
1760
  return self.group
1761
1761
 
1762
1762
  def set_note(self, note: str) -> None:
1763
1763
  """Attach/update a DM note."""
1764
- self.data["note"] = str(note) if note is not None else None
1764
+ self._data["note"] = str(note) if note is not None else None
1765
1765
 
1766
1766
  def get_effect(self, name: str, strict: bool = False) -> SimpleEffect | None:
1767
1767
  """Find an effect by name (optionally requiring exact match)."""
@@ -1809,12 +1809,12 @@ class SimpleCombatant(AliasStatBlock):
1809
1809
  if parent is not None:
1810
1810
  if not isinstance(parent, SimpleEffect):
1811
1811
  raise TypeError("Parent effect must be a SimpleEffect.")
1812
- payload["parent"] = parent.data
1813
- effects = self.data.setdefault("effects", [])
1812
+ payload["parent"] = parent._data
1813
+ effects = self._data.setdefault("effects", [])
1814
1814
  existing = self.get_effect(name, strict=True)
1815
1815
  if existing:
1816
1816
  try:
1817
- effects.remove(existing.data)
1817
+ effects.remove(existing._data)
1818
1818
  except ValueError:
1819
1819
  pass
1820
1820
  effects.append(payload)
@@ -1825,45 +1825,45 @@ class SimpleCombatant(AliasStatBlock):
1825
1825
  effect = self.get_effect(name, strict)
1826
1826
  if effect:
1827
1827
  try:
1828
- self.data.setdefault("effects", []).remove(effect.data)
1828
+ self._data.setdefault("effects", []).remove(effect._data)
1829
1829
  except ValueError:
1830
1830
  pass
1831
1831
 
1832
1832
 
1833
1833
  @dataclass
1834
1834
  class SimpleGroup(_DirMixin):
1835
- data: MutableMapping[str, Any] = field(default_factory=dict)
1835
+ _data: MutableMapping[str, Any] = field(default_factory=dict)
1836
1836
  ATTRS: ClassVar[list[str]] = ["combatants", "type", "init", "name", "id"]
1837
1837
  METHODS: ClassVar[list[str]] = ["get_combatant", "set_init"]
1838
1838
 
1839
1839
  def __post_init__(self) -> None:
1840
1840
  super().__post_init__()
1841
- self.data.setdefault("type", "group")
1841
+ self._data.setdefault("type", "group")
1842
1842
 
1843
1843
  @property
1844
1844
  def combatants(self) -> list[SimpleCombatant]:
1845
1845
  """Members of the group."""
1846
- return [SimpleCombatant(c) for c in self.data.get("combatants", [])]
1846
+ return [SimpleCombatant(c) for c in self._data.get("combatants", [])]
1847
1847
 
1848
1848
  @property
1849
1849
  def type(self) -> str:
1850
1850
  """Group type identifier (always 'group')."""
1851
- return str(self.data.get("type", "group"))
1851
+ return str(self._data.get("type", "group"))
1852
1852
 
1853
1853
  @property
1854
1854
  def init(self) -> int:
1855
1855
  """Initiative score for the group."""
1856
- return _safe_int(self.data.get("init"), 0)
1856
+ return _safe_int(self._data.get("init"), 0)
1857
1857
 
1858
1858
  @property
1859
1859
  def name(self) -> str:
1860
1860
  """Group name."""
1861
- return str(self.data.get("name", "Group"))
1861
+ return str(self._data.get("name", "Group"))
1862
1862
 
1863
1863
  @property
1864
1864
  def id(self) -> str | None:
1865
1865
  """Group id."""
1866
- val = self.data.get("id")
1866
+ val = self._data.get("id")
1867
1867
  return str(val) if val is not None else None
1868
1868
 
1869
1869
  def get_combatant(self, name: str, strict: bool | None = None) -> SimpleCombatant | None:
@@ -1883,7 +1883,7 @@ class SimpleGroup(_DirMixin):
1883
1883
  return None
1884
1884
 
1885
1885
  def set_init(self, init: int) -> None:
1886
- self.data["init"] = int(init)
1886
+ self._data["init"] = int(init)
1887
1887
 
1888
1888
  def __getitem__(self, item: str) -> Any:
1889
1889
  return getattr(self, str(item))
@@ -1891,30 +1891,30 @@ class SimpleGroup(_DirMixin):
1891
1891
 
1892
1892
  @dataclass
1893
1893
  class SimpleCombat(_DirMixin):
1894
- data: MutableMapping[str, Any] = field(default_factory=dict)
1894
+ _data: MutableMapping[str, Any] = field(default_factory=dict)
1895
1895
  ATTRS: ClassVar[list[str]] = ["combatants", "groups", "me", "current", "name", "round_num", "turn_num", "metadata"]
1896
1896
  METHODS: ClassVar[list[str]] = ["get_combatant", "get_group", "set_metadata", "get_metadata", "delete_metadata", "set_round", "end_round"]
1897
1897
 
1898
1898
  @property
1899
1899
  def combatants(self) -> list[SimpleCombatant]:
1900
1900
  """All combatants in the encounter."""
1901
- return [SimpleCombatant(c) for c in self.data.get("combatants", [])]
1901
+ return [SimpleCombatant(c) for c in self._data.get("combatants", [])]
1902
1902
 
1903
1903
  @property
1904
1904
  def groups(self) -> list[SimpleGroup]:
1905
1905
  """Combatant groups in the encounter."""
1906
- return [SimpleGroup(g) for g in self.data.get("groups", [])]
1906
+ return [SimpleGroup(g) for g in self._data.get("groups", [])]
1907
1907
 
1908
1908
  @property
1909
1909
  def me(self) -> SimpleCombatant | None:
1910
1910
  """The player's combatant if present."""
1911
- me_data = self.data.get("me")
1911
+ me_data = self._data.get("me")
1912
1912
  return SimpleCombatant(me_data) if me_data is not None else None
1913
1913
 
1914
1914
  @property
1915
1915
  def current(self) -> SimpleCombatant | SimpleGroup | None:
1916
1916
  """Current turn holder (combatant or group)."""
1917
- cur = self.data.get("current")
1917
+ cur = self._data.get("current")
1918
1918
  if cur is None:
1919
1919
  return None
1920
1920
  if cur.get("type") == "group":
@@ -1924,23 +1924,23 @@ class SimpleCombat(_DirMixin):
1924
1924
  @property
1925
1925
  def name(self) -> str | None:
1926
1926
  """Name of the combat encounter."""
1927
- val = self.data.get("name")
1927
+ val = self._data.get("name")
1928
1928
  return str(val) if val is not None else None
1929
1929
 
1930
1930
  @property
1931
1931
  def round_num(self) -> int:
1932
1932
  """Current round number."""
1933
- return _safe_int(self.data.get("round_num"), 1)
1933
+ return _safe_int(self._data.get("round_num"), 1)
1934
1934
 
1935
1935
  @property
1936
1936
  def turn_num(self) -> int:
1937
1937
  """Current turn number within the round."""
1938
- return _safe_int(self.data.get("turn_num"), 1)
1938
+ return _safe_int(self._data.get("turn_num"), 1)
1939
1939
 
1940
1940
  @property
1941
1941
  def metadata(self) -> MutableMapping[str, Any]:
1942
1942
  """Free-form metadata key/value store for the combat."""
1943
- return self.data.setdefault("metadata", {})
1943
+ return self._data.setdefault("metadata", {})
1944
1944
 
1945
1945
  def get_combatant(self, name: str, strict: bool | None = None) -> SimpleCombatant | None:
1946
1946
  """Find a combatant by name (strict, substring, or fuzzy)."""
@@ -1993,15 +1993,15 @@ class SimpleCombat(_DirMixin):
1993
1993
 
1994
1994
  def set_round(self, round_num: int) -> None:
1995
1995
  """Advance combat to the specified round number."""
1996
- self.data["round_num"] = int(round_num)
1996
+ self._data["round_num"] = int(round_num)
1997
1997
 
1998
1998
  def end_round(self) -> None:
1999
1999
  """Increment round number and reset turn counter."""
2000
- self.data["turn_num"] = 0
2001
- self.data["round_num"] = self.round_num + 1
2000
+ self._data["turn_num"] = 0
2001
+ self._data["round_num"] = self.round_num + 1
2002
2002
 
2003
2003
  def __getattr__(self, item: str) -> Any:
2004
- return self.data.get(item)
2004
+ return self._data.get(item)
2005
2005
 
2006
2006
  def __getitem__(self, item: str) -> Any:
2007
2007
  return getattr(self, str(item))