rom24-quickmud-python 1.2.2__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.
Files changed (135) hide show
  1. mud/__init__.py +0 -0
  2. mud/__main__.py +40 -0
  3. mud/account/__init__.py +20 -0
  4. mud/account/account_manager.py +62 -0
  5. mud/account/account_service.py +80 -0
  6. mud/advancement.py +62 -0
  7. mud/affects/saves.py +123 -0
  8. mud/agent/__init__.py +0 -0
  9. mud/agent/agent_protocol.py +19 -0
  10. mud/agent/character_agent.py +61 -0
  11. mud/combat/__init__.py +3 -0
  12. mud/combat/engine.py +189 -0
  13. mud/commands/__init__.py +3 -0
  14. mud/commands/admin_commands.py +77 -0
  15. mud/commands/advancement.py +36 -0
  16. mud/commands/alias_cmds.py +44 -0
  17. mud/commands/build.py +18 -0
  18. mud/commands/combat.py +16 -0
  19. mud/commands/communication.py +55 -0
  20. mud/commands/decorators.py +11 -0
  21. mud/commands/dispatcher.py +206 -0
  22. mud/commands/healer.py +81 -0
  23. mud/commands/help.py +14 -0
  24. mud/commands/imc.py +19 -0
  25. mud/commands/inspection.py +113 -0
  26. mud/commands/inventory.py +42 -0
  27. mud/commands/movement.py +71 -0
  28. mud/commands/notes.py +44 -0
  29. mud/commands/shop.py +138 -0
  30. mud/commands/socials.py +34 -0
  31. mud/config.py +59 -0
  32. mud/db/__init__.py +0 -0
  33. mud/db/init.py +27 -0
  34. mud/db/migrate_from_files.py +87 -0
  35. mud/db/migrations.py +7 -0
  36. mud/db/models.py +98 -0
  37. mud/db/seed.py +28 -0
  38. mud/db/session.py +11 -0
  39. mud/devtools/__init__.py +0 -0
  40. mud/devtools/agent_demo.py +19 -0
  41. mud/entrypoint.py +34 -0
  42. mud/game_loop.py +117 -0
  43. mud/imc/__init__.py +17 -0
  44. mud/imc/protocol.py +32 -0
  45. mud/loaders/__init__.py +27 -0
  46. mud/loaders/area_loader.py +73 -0
  47. mud/loaders/base_loader.py +38 -0
  48. mud/loaders/help_loader.py +17 -0
  49. mud/loaders/json_area_loader.py +203 -0
  50. mud/loaders/json_loader.py +285 -0
  51. mud/loaders/mob_loader.py +104 -0
  52. mud/loaders/obj_loader.py +76 -0
  53. mud/loaders/reset_loader.py +29 -0
  54. mud/loaders/room_loader.py +63 -0
  55. mud/loaders/shop_loader.py +41 -0
  56. mud/loaders/social_loader.py +16 -0
  57. mud/loaders/specials_loader.py +63 -0
  58. mud/logging/__init__.py +0 -0
  59. mud/logging/admin.py +40 -0
  60. mud/logging/agent_trace.py +9 -0
  61. mud/math/c_compat.py +27 -0
  62. mud/mobprog.py +72 -0
  63. mud/models/__init__.py +106 -0
  64. mud/models/area.py +33 -0
  65. mud/models/area_json.py +27 -0
  66. mud/models/board.py +49 -0
  67. mud/models/board_json.py +16 -0
  68. mud/models/character.py +195 -0
  69. mud/models/character_json.py +46 -0
  70. mud/models/constants.py +423 -0
  71. mud/models/conversion.py +45 -0
  72. mud/models/help.py +28 -0
  73. mud/models/help_json.py +14 -0
  74. mud/models/json_io.py +64 -0
  75. mud/models/mob.py +82 -0
  76. mud/models/note.py +29 -0
  77. mud/models/note_json.py +16 -0
  78. mud/models/obj.py +82 -0
  79. mud/models/object.py +28 -0
  80. mud/models/object_json.py +40 -0
  81. mud/models/player_json.py +29 -0
  82. mud/models/room.py +86 -0
  83. mud/models/room_json.py +46 -0
  84. mud/models/shop.py +21 -0
  85. mud/models/shop_json.py +17 -0
  86. mud/models/skill.py +25 -0
  87. mud/models/skill_json.py +20 -0
  88. mud/models/social.py +78 -0
  89. mud/models/social_json.py +20 -0
  90. mud/net/__init__.py +9 -0
  91. mud/net/ansi.py +27 -0
  92. mud/net/connection.py +174 -0
  93. mud/net/protocol.py +57 -0
  94. mud/net/session.py +21 -0
  95. mud/net/telnet_server.py +31 -0
  96. mud/network/__init__.py +0 -0
  97. mud/network/websocket_server.py +83 -0
  98. mud/network/websocket_session.py +21 -0
  99. mud/notes.py +45 -0
  100. mud/persistence.py +185 -0
  101. mud/registry.py +5 -0
  102. mud/scripts/convert_are_to_json.py +162 -0
  103. mud/scripts/convert_help_are_to_json.py +92 -0
  104. mud/scripts/convert_player_to_json.py +112 -0
  105. mud/scripts/convert_shops_to_json.py +64 -0
  106. mud/scripts/convert_skills_to_json.py +166 -0
  107. mud/scripts/convert_social_are_to_json.py +92 -0
  108. mud/scripts/load_test_data.py +17 -0
  109. mud/security/__init__.py +0 -0
  110. mud/security/bans.py +112 -0
  111. mud/security/hash_utils.py +20 -0
  112. mud/server.py +8 -0
  113. mud/skills/__init__.py +3 -0
  114. mud/skills/handlers.py +795 -0
  115. mud/skills/registry.py +97 -0
  116. mud/spawning/__init__.py +1 -0
  117. mud/spawning/mob_spawner.py +13 -0
  118. mud/spawning/obj_spawner.py +18 -0
  119. mud/spawning/reset_handler.py +222 -0
  120. mud/spawning/templates.py +63 -0
  121. mud/spec_funs.py +57 -0
  122. mud/time.py +48 -0
  123. mud/utils/rng_mm.py +123 -0
  124. mud/wiznet.py +74 -0
  125. mud/world/__init__.py +11 -0
  126. mud/world/linking.py +31 -0
  127. mud/world/look.py +29 -0
  128. mud/world/movement.py +135 -0
  129. mud/world/world_state.py +179 -0
  130. rom24_quickmud_python-1.2.2.dist-info/METADATA +236 -0
  131. rom24_quickmud_python-1.2.2.dist-info/RECORD +135 -0
  132. rom24_quickmud_python-1.2.2.dist-info/WHEEL +5 -0
  133. rom24_quickmud_python-1.2.2.dist-info/entry_points.txt +2 -0
  134. rom24_quickmud_python-1.2.2.dist-info/licenses/LICENSE +21 -0
  135. rom24_quickmud_python-1.2.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,195 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field
3
+ from typing import List, Optional, Dict, TYPE_CHECKING
4
+
5
+ from mud.models.constants import AffectFlag
6
+
7
+ if TYPE_CHECKING:
8
+ from mud.models.object import Object
9
+ from mud.models.room import Room
10
+ from mud.db.models import Character as DBCharacter
11
+
12
+
13
+ @dataclass
14
+ class PCData:
15
+ """Subset of PC_DATA from merc.h"""
16
+ pwd: Optional[str] = None
17
+ bamfin: Optional[str] = None
18
+ bamfout: Optional[str] = None
19
+ title: Optional[str] = None
20
+ perm_hit: int = 0
21
+ perm_mana: int = 0
22
+ perm_move: int = 0
23
+ true_sex: int = 0
24
+ last_level: int = 0
25
+ condition: List[int] = field(default_factory=lambda: [0] * 4)
26
+ points: int = 0
27
+ security: int = 0
28
+
29
+
30
+ @dataclass
31
+ class Character:
32
+ """Python representation of CHAR_DATA"""
33
+ name: Optional[str] = None
34
+ short_descr: Optional[str] = None
35
+ long_descr: Optional[str] = None
36
+ description: Optional[str] = None
37
+ prompt: Optional[str] = None
38
+ prefix: Optional[str] = None
39
+ sex: int = 0
40
+ ch_class: int = 0
41
+ race: int = 0
42
+ level: int = 0
43
+ trust: int = 0
44
+ hit: int = 0
45
+ max_hit: int = 0
46
+ mana: int = 0
47
+ max_mana: int = 0
48
+ move: int = 0
49
+ max_move: int = 0
50
+ gold: int = 0
51
+ silver: int = 0
52
+ exp: int = 0
53
+ act: int = 0
54
+ affected_by: int = 0
55
+ position: int = 0
56
+ room: Optional['Room'] = None
57
+ practice: int = 0
58
+ train: int = 0
59
+ skills: Dict[str, int] = field(default_factory=dict)
60
+ carry_weight: int = 0
61
+ carry_number: int = 0
62
+ saving_throw: int = 0
63
+ alignment: int = 0
64
+ hitroll: int = 0
65
+ damroll: int = 0
66
+ wimpy: int = 0
67
+ perm_stat: List[int] = field(default_factory=list)
68
+ mod_stat: List[int] = field(default_factory=list)
69
+ form: int = 0
70
+ parts: int = 0
71
+ size: int = 0
72
+ material: Optional[str] = None
73
+ off_flags: int = 0
74
+ # ROM parity: immunity/resistance/vulnerability bitvectors (merc.h)
75
+ imm_flags: int = 0
76
+ res_flags: int = 0
77
+ vuln_flags: int = 0
78
+ damage: List[int] = field(default_factory=lambda: [0, 0, 0])
79
+ dam_type: int = 0
80
+ start_pos: int = 0
81
+ default_pos: int = 0
82
+ mprog_delay: int = 0
83
+ pcdata: Optional[PCData] = None
84
+ inventory: List['Object'] = field(default_factory=list)
85
+ equipment: Dict[str, 'Object'] = field(default_factory=dict)
86
+ messages: List[str] = field(default_factory=list)
87
+ connection: Optional[object] = None
88
+ is_admin: bool = False
89
+ muted_channels: set[str] = field(default_factory=set)
90
+ banned_channels: set[str] = field(default_factory=set)
91
+ wiznet: int = 0
92
+ # Wait-state (pulses) applied by actions like movement (ROM WAIT_STATE)
93
+ wait: int = 0
94
+ # Daze (pulses) — separate action delay used by ROM combat
95
+ daze: int = 0
96
+ # Armor class per index [AC_PIERCE, AC_BASH, AC_SLASH, AC_EXOTIC]
97
+ armor: List[int] = field(default_factory=lambda: [0, 0, 0, 0])
98
+ # Per-character command aliases: name -> expansion (pre-dispatch)
99
+ aliases: Dict[str, str] = field(default_factory=dict)
100
+ # Optional defense chances (percent) for parity-friendly tests
101
+ shield_block_chance: int = 0
102
+ parry_chance: int = 0
103
+ dodge_chance: int = 0
104
+
105
+ def __repr__(self) -> str:
106
+ return f"<Character name={self.name!r} level={self.level}>"
107
+
108
+ def add_object(self, obj: 'Object') -> None:
109
+ self.inventory.append(obj)
110
+ self.carry_number += 1
111
+ self.carry_weight += getattr(obj.prototype, "weight", 0)
112
+
113
+ def equip_object(self, obj: 'Object', slot: str) -> None:
114
+ if obj in self.inventory:
115
+ self.inventory.remove(obj)
116
+ else:
117
+ self.carry_number += 1
118
+ self.carry_weight += getattr(obj.prototype, "weight", 0)
119
+ self.equipment[slot] = obj
120
+
121
+ def remove_object(self, obj: 'Object') -> None:
122
+ if obj in self.inventory:
123
+ self.inventory.remove(obj)
124
+ else:
125
+ for slot, eq in list(self.equipment.items()):
126
+ if eq is obj:
127
+ del self.equipment[slot]
128
+ break
129
+ self.carry_number -= 1
130
+ self.carry_weight -= getattr(obj.prototype, "weight", 0)
131
+
132
+ # START affects_saves
133
+ def add_affect(
134
+ self,
135
+ flag: AffectFlag,
136
+ *,
137
+ hitroll: int = 0,
138
+ damroll: int = 0,
139
+ saving_throw: int = 0,
140
+ ) -> None:
141
+ """Apply an affect flag and modify core stats."""
142
+ self.affected_by |= flag
143
+ self.hitroll += hitroll
144
+ self.damroll += damroll
145
+ self.saving_throw += saving_throw
146
+
147
+ def has_affect(self, flag: AffectFlag) -> bool:
148
+ return bool(self.affected_by & flag)
149
+
150
+ def remove_affect(
151
+ self,
152
+ flag: AffectFlag,
153
+ *,
154
+ hitroll: int = 0,
155
+ damroll: int = 0,
156
+ saving_throw: int = 0,
157
+ ) -> None:
158
+ """Remove an affect flag and revert stat modifications."""
159
+ self.affected_by &= ~flag
160
+ self.hitroll -= hitroll
161
+ self.damroll -= damroll
162
+ self.saving_throw -= saving_throw
163
+ # END affects_saves
164
+
165
+
166
+ character_registry: list[Character] = []
167
+
168
+
169
+ def from_orm(db_char: 'DBCharacter') -> Character:
170
+ from mud.registry import room_registry
171
+ from mud.models.constants import Position
172
+
173
+ room = room_registry.get(db_char.room_vnum)
174
+ char = Character(
175
+ name=db_char.name,
176
+ level=db_char.level or 0,
177
+ hit=db_char.hp or 0,
178
+ position=int(Position.STANDING), # Default to standing for loaded chars
179
+ )
180
+ char.room = room
181
+ if db_char.player is not None:
182
+ char.is_admin = bool(getattr(db_char.player, "is_admin", False))
183
+ return char
184
+
185
+
186
+ def to_orm(character: Character, player_id: int) -> 'DBCharacter':
187
+ from mud.db.models import Character as DBCharacter
188
+
189
+ return DBCharacter(
190
+ name=character.name,
191
+ level=character.level,
192
+ hp=character.hit,
193
+ room_vnum=character.room.vnum if character.room else None,
194
+ player_id=player_id,
195
+ )
@@ -0,0 +1,46 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Dict, List, Optional
5
+ import builtins
6
+
7
+ from .json_io import JsonDataclass
8
+
9
+
10
+ @dataclass
11
+ class ResourceJson(JsonDataclass):
12
+ """Track current and maximum resource values."""
13
+ current: int
14
+ max: int
15
+
16
+
17
+ @dataclass
18
+ class StatsJson(JsonDataclass):
19
+ """Primary stats and pooled resources."""
20
+ str: int
21
+ int: int
22
+ wis: builtins.int
23
+ dex: builtins.int
24
+ con: builtins.int
25
+ hitpoints: ResourceJson
26
+ mana: ResourceJson
27
+ move: ResourceJson
28
+
29
+
30
+ @dataclass
31
+ class CharacterJson(JsonDataclass):
32
+ """Character record matching ``schemas/character.schema.json``."""
33
+ id: int
34
+ name: str
35
+ description: str
36
+ level: int
37
+ stats: StatsJson
38
+ position: str
39
+ short_description: Optional[str] = None
40
+ long_description: Optional[str] = None
41
+ alignment: int = 0
42
+ gold: int = 0
43
+ silver: int = 0
44
+ skills: Dict[str, int] = field(default_factory=dict)
45
+ inventory: List[int] = field(default_factory=list)
46
+ flags: List[str] = field(default_factory=list)
@@ -0,0 +1,423 @@
1
+ from enum import IntEnum, IntFlag
2
+
3
+
4
+ class Direction(IntEnum):
5
+ """Mapping of direction constants from merc.h"""
6
+
7
+ NORTH = 0
8
+ EAST = 1
9
+ SOUTH = 2
10
+ WEST = 3
11
+ UP = 4
12
+ DOWN = 5
13
+
14
+
15
+ class Sector(IntEnum):
16
+ """Sector types from merc.h"""
17
+
18
+ INSIDE = 0
19
+ CITY = 1
20
+ FIELD = 2
21
+ FOREST = 3
22
+ HILLS = 4
23
+ MOUNTAIN = 5
24
+ WATER_SWIM = 6
25
+ WATER_NOSWIM = 7
26
+ UNUSED = 8
27
+ AIR = 9
28
+ DESERT = 10
29
+ MAX = 11
30
+
31
+
32
+ class Position(IntEnum):
33
+ """Character positions from merc.h"""
34
+
35
+ DEAD = 0
36
+ MORTAL = 1
37
+ INCAP = 2
38
+ STUNNED = 3
39
+ SLEEPING = 4
40
+ RESTING = 5
41
+ SITTING = 6
42
+ FIGHTING = 7
43
+ STANDING = 8
44
+
45
+ # --- Armor Class indices (merc.h) ---
46
+ # AC is better when more negative; indices map to damage types.
47
+ AC_PIERCE = 0
48
+ AC_BASH = 1
49
+ AC_SLASH = 2
50
+ AC_EXOTIC = 3
51
+
52
+
53
+ class WearLocation(IntEnum):
54
+ """Equipment wear locations from merc.h"""
55
+
56
+ NONE = -1
57
+ LIGHT = 0
58
+ FINGER_L = 1
59
+ FINGER_R = 2
60
+ NECK_1 = 3
61
+ NECK_2 = 4
62
+ BODY = 5
63
+ HEAD = 6
64
+ LEGS = 7
65
+ FEET = 8
66
+ HANDS = 9
67
+ ARMS = 10
68
+ SHIELD = 11
69
+ ABOUT = 12
70
+ WAIST = 13
71
+ WRIST_L = 14
72
+ WRIST_R = 15
73
+ WIELD = 16
74
+ HOLD = 17
75
+ FLOAT = 18
76
+
77
+
78
+ class Sex(IntEnum):
79
+ """Biological sex of a character"""
80
+
81
+ NONE = 0
82
+ MALE = 1
83
+ FEMALE = 2
84
+ EITHER = 3
85
+
86
+
87
+ class Size(IntEnum):
88
+ """Character sizes"""
89
+
90
+ TINY = 0
91
+ SMALL = 1
92
+ MEDIUM = 2
93
+ LARGE = 3
94
+ HUGE = 4
95
+ GIANT = 5
96
+
97
+
98
+ class ItemType(IntEnum):
99
+ """Common object types"""
100
+
101
+ LIGHT = 1
102
+ SCROLL = 2
103
+ WAND = 3
104
+ STAFF = 4
105
+ WEAPON = 5
106
+ TREASURE = 8
107
+ ARMOR = 9
108
+ POTION = 10
109
+ CLOTHING = 11
110
+ FURNITURE = 12
111
+ TRASH = 13
112
+ CONTAINER = 15
113
+ DRINK_CON = 17
114
+ KEY = 18
115
+ FOOD = 19
116
+ MONEY = 20
117
+ BOAT = 22
118
+ CORPSE_NPC = 23
119
+ CORPSE_PC = 24
120
+ FOUNTAIN = 25
121
+ PILL = 26
122
+ PROTECT = 27
123
+ MAP = 28
124
+ PORTAL = 29
125
+ WARP_STONE = 30
126
+ ROOM_KEY = 31
127
+ GEM = 32
128
+ JEWELRY = 33
129
+ JUKEBOX = 34
130
+
131
+
132
+ # START affects_saves
133
+ class AffectFlag(IntFlag):
134
+ BLIND = 1 << 0
135
+ INVISIBLE = 1 << 1
136
+ DETECT_EVIL = 1 << 2
137
+ DETECT_INVIS = 1 << 3
138
+ DETECT_MAGIC = 1 << 4
139
+ DETECT_HIDDEN = 1 << 5
140
+ SANCTUARY = 1 << 6
141
+ FAERIE_FIRE = 1 << 7
142
+ INFRARED = 1 << 8
143
+ CURSE = 1 << 9
144
+ UNUSED1 = 1 << 10
145
+ POISON = 1 << 11
146
+ PROTECT_EVIL = 1 << 12
147
+ PROTECT_GOOD = 1 << 13
148
+ SNEAK = 1 << 14
149
+ HIDE = 1 << 15
150
+ SLEEP = 1 << 16
151
+ CHARM = 1 << 17
152
+ FLYING = 1 << 18
153
+ PASS_DOOR = 1 << 19
154
+ UNUSED2 = 1 << 20
155
+ BERSERK = 1 << 21
156
+ CALM = 1 << 22
157
+ HASTE = 1 << 23
158
+ SLOW = 1 << 24
159
+ PLAGUE = 1 << 25
160
+ DARK_VISION = 1 << 26
161
+ UNUSED3 = 1 << 27
162
+ SWIM = 1 << 28
163
+ REGENERATION = 1 << 29
164
+ UNUSED4 = 1 << 30
165
+ UNUSED5 = 1 << 31
166
+
167
+
168
+ # END affects_saves
169
+
170
+ # START damage_types_and_defense_bits
171
+ class DamageType(IntEnum):
172
+ """Damage types mirroring merc.h DAM_* values."""
173
+
174
+ NONE = 0
175
+ BASH = 1
176
+ PIERCE = 2
177
+ SLASH = 3
178
+ FIRE = 4
179
+ COLD = 5
180
+ LIGHTNING = 6
181
+ ACID = 7
182
+ POISON = 8
183
+ NEGATIVE = 9
184
+ HOLY = 10
185
+ ENERGY = 11
186
+ MENTAL = 12
187
+ DISEASE = 13
188
+ DROWNING = 14
189
+ LIGHT = 15
190
+ OTHER = 16
191
+ HARM = 17
192
+ CHARM = 18
193
+ SOUND = 19
194
+
195
+
196
+ # ROM-style DAM_* constants for parity
197
+ DAM_NONE = DamageType.NONE
198
+ DAM_BASH = DamageType.BASH
199
+ DAM_PIERCE = DamageType.PIERCE
200
+ DAM_SLASH = DamageType.SLASH
201
+ DAM_FIRE = DamageType.FIRE
202
+ DAM_COLD = DamageType.COLD
203
+ DAM_LIGHTNING = DamageType.LIGHTNING
204
+ DAM_ACID = DamageType.ACID
205
+ DAM_POISON = DamageType.POISON
206
+ DAM_NEGATIVE = DamageType.NEGATIVE
207
+ DAM_HOLY = DamageType.HOLY
208
+ DAM_ENERGY = DamageType.ENERGY
209
+ DAM_MENTAL = DamageType.MENTAL
210
+ DAM_DISEASE = DamageType.DISEASE
211
+ DAM_DROWNING = DamageType.DROWNING
212
+ DAM_LIGHT = DamageType.LIGHT
213
+ DAM_OTHER = DamageType.OTHER
214
+ DAM_HARM = DamageType.HARM
215
+ DAM_CHARM = DamageType.CHARM
216
+ DAM_SOUND = DamageType.SOUND
217
+
218
+
219
+ class DefenseBit(IntFlag):
220
+ """IMM/RES/VULN bit positions (letters A..Z) mapped to explicit bits.
221
+
222
+ These names are shared across IMM_*, RES_*, VULN_* in ROM.
223
+ """
224
+
225
+ # A..Z → 1<<0 .. 1<<25 (skip U/V/W per merc.h usage here)
226
+ SUMMON = 1 << 0 # A
227
+ CHARM = 1 << 1 # B
228
+ MAGIC = 1 << 2 # C
229
+ WEAPON = 1 << 3 # D
230
+ BASH = 1 << 4 # E
231
+ PIERCE = 1 << 5 # F
232
+ SLASH = 1 << 6 # G
233
+ FIRE = 1 << 7 # H
234
+ COLD = 1 << 8 # I
235
+ LIGHTNING = 1 << 9 # J
236
+ ACID = 1 << 10 # K
237
+ POISON = 1 << 11 # L
238
+ NEGATIVE = 1 << 12 # M
239
+ HOLY = 1 << 13 # N
240
+ ENERGY = 1 << 14 # O
241
+ MENTAL = 1 << 15 # P
242
+ DISEASE = 1 << 16 # Q
243
+ DROWNING = 1 << 17 # R
244
+ LIGHT = 1 << 18 # S
245
+ SOUND = 1 << 19 # T
246
+ # U, V, W unused for these tables in ROM
247
+ WOOD = 1 << 23 # X
248
+ SILVER = 1 << 24 # Y
249
+ IRON = 1 << 25 # Z
250
+
251
+ # END damage_types_and_defense_bits
252
+
253
+ # START imm_res_vuln_flags
254
+ class ImmFlag(IntFlag):
255
+ """IMM_* flags mapped to ROM bit letters (A..Z).
256
+
257
+ Values mirror DefenseBit so code may interchangeably use either.
258
+ """
259
+
260
+ SUMMON = int(DefenseBit.SUMMON)
261
+ CHARM = int(DefenseBit.CHARM)
262
+ MAGIC = int(DefenseBit.MAGIC)
263
+ WEAPON = int(DefenseBit.WEAPON)
264
+ BASH = int(DefenseBit.BASH)
265
+ PIERCE = int(DefenseBit.PIERCE)
266
+ SLASH = int(DefenseBit.SLASH)
267
+ FIRE = int(DefenseBit.FIRE)
268
+ COLD = int(DefenseBit.COLD)
269
+ LIGHTNING = int(DefenseBit.LIGHTNING)
270
+ ACID = int(DefenseBit.ACID)
271
+ POISON = int(DefenseBit.POISON)
272
+ NEGATIVE = int(DefenseBit.NEGATIVE)
273
+ HOLY = int(DefenseBit.HOLY)
274
+ ENERGY = int(DefenseBit.ENERGY)
275
+ MENTAL = int(DefenseBit.MENTAL)
276
+ DISEASE = int(DefenseBit.DISEASE)
277
+ DROWNING = int(DefenseBit.DROWNING)
278
+ LIGHT = int(DefenseBit.LIGHT)
279
+ SOUND = int(DefenseBit.SOUND)
280
+ WOOD = int(DefenseBit.WOOD)
281
+ SILVER = int(DefenseBit.SILVER)
282
+ IRON = int(DefenseBit.IRON)
283
+
284
+
285
+ class ResFlag(IntFlag):
286
+ """RES_* flags mapped to ROM bit letters (A..Z)."""
287
+
288
+ SUMMON = int(DefenseBit.SUMMON)
289
+ CHARM = int(DefenseBit.CHARM)
290
+ MAGIC = int(DefenseBit.MAGIC)
291
+ WEAPON = int(DefenseBit.WEAPON)
292
+ BASH = int(DefenseBit.BASH)
293
+ PIERCE = int(DefenseBit.PIERCE)
294
+ SLASH = int(DefenseBit.SLASH)
295
+ FIRE = int(DefenseBit.FIRE)
296
+ COLD = int(DefenseBit.COLD)
297
+ LIGHTNING = int(DefenseBit.LIGHTNING)
298
+ ACID = int(DefenseBit.ACID)
299
+ POISON = int(DefenseBit.POISON)
300
+ NEGATIVE = int(DefenseBit.NEGATIVE)
301
+ HOLY = int(DefenseBit.HOLY)
302
+ ENERGY = int(DefenseBit.ENERGY)
303
+ MENTAL = int(DefenseBit.MENTAL)
304
+ DISEASE = int(DefenseBit.DISEASE)
305
+ DROWNING = int(DefenseBit.DROWNING)
306
+ LIGHT = int(DefenseBit.LIGHT)
307
+ SOUND = int(DefenseBit.SOUND)
308
+ WOOD = int(DefenseBit.WOOD)
309
+ SILVER = int(DefenseBit.SILVER)
310
+ IRON = int(DefenseBit.IRON)
311
+
312
+
313
+ class VulnFlag(IntFlag):
314
+ """VULN_* flags mapped to ROM bit letters (A..Z)."""
315
+
316
+ SUMMON = int(DefenseBit.SUMMON)
317
+ CHARM = int(DefenseBit.CHARM)
318
+ MAGIC = int(DefenseBit.MAGIC)
319
+ WEAPON = int(DefenseBit.WEAPON)
320
+ BASH = int(DefenseBit.BASH)
321
+ PIERCE = int(DefenseBit.PIERCE)
322
+ SLASH = int(DefenseBit.SLASH)
323
+ FIRE = int(DefenseBit.FIRE)
324
+ COLD = int(DefenseBit.COLD)
325
+ LIGHTNING = int(DefenseBit.LIGHTNING)
326
+ ACID = int(DefenseBit.ACID)
327
+ POISON = int(DefenseBit.POISON)
328
+ NEGATIVE = int(DefenseBit.NEGATIVE)
329
+ HOLY = int(DefenseBit.HOLY)
330
+ ENERGY = int(DefenseBit.ENERGY)
331
+ MENTAL = int(DefenseBit.MENTAL)
332
+ DISEASE = int(DefenseBit.DISEASE)
333
+ DROWNING = int(DefenseBit.DROWNING)
334
+ LIGHT = int(DefenseBit.LIGHT)
335
+ SOUND = int(DefenseBit.SOUND)
336
+ WOOD = int(DefenseBit.WOOD)
337
+ SILVER = int(DefenseBit.SILVER)
338
+ IRON = int(DefenseBit.IRON)
339
+
340
+ # END imm_res_vuln_flags
341
+
342
+ # START extra_flags
343
+ class ExtraFlag(IntFlag):
344
+ """ITEM_* extra flags mapped to ROM bit letters (A..Z)."""
345
+
346
+ GLOW = 1 << 0 # A
347
+ HUM = 1 << 1 # B
348
+ DARK = 1 << 2 # C
349
+ LOCK = 1 << 3 # D
350
+ EVIL = 1 << 4 # E
351
+ INVIS = 1 << 5 # F
352
+ MAGIC = 1 << 6 # G
353
+ NODROP = 1 << 7 # H
354
+ BLESS = 1 << 8 # I
355
+ ANTI_GOOD = 1 << 9 # J
356
+ ANTI_EVIL = 1 << 10 # K
357
+ ANTI_NEUTRAL = 1 << 11 # L
358
+ NOREMOVE = 1 << 12 # M
359
+ INVENTORY = 1 << 13 # N
360
+ NOPURGE = 1 << 14 # O
361
+ ROT_DEATH = 1 << 15 # P
362
+ VIS_DEATH = 1 << 16 # Q
363
+ # R unused in ROM
364
+ NONMETAL = 1 << 18 # S
365
+ NOLOCATE = 1 << 19 # T
366
+ MELT_DROP = 1 << 20 # U
367
+ HAD_TIMER = 1 << 21 # V
368
+ SELL_EXTRACT = 1 << 22 # W
369
+ # X unused in ROM
370
+ BURN_PROOF = 1 << 24 # Y
371
+ NOUNCURSE = 1 << 25 # Z
372
+
373
+ # Legacy constants for compatibility
374
+ ITEM_GLOW = ExtraFlag.GLOW
375
+ ITEM_HUM = ExtraFlag.HUM
376
+ ITEM_DARK = ExtraFlag.DARK
377
+ ITEM_LOCK = ExtraFlag.LOCK
378
+ ITEM_EVIL = ExtraFlag.EVIL
379
+ ITEM_INVIS = ExtraFlag.INVIS
380
+ ITEM_MAGIC = ExtraFlag.MAGIC
381
+ ITEM_NODROP = ExtraFlag.NODROP
382
+ ITEM_BLESS = ExtraFlag.BLESS
383
+ ITEM_ANTI_GOOD = ExtraFlag.ANTI_GOOD
384
+ ITEM_ANTI_EVIL = ExtraFlag.ANTI_EVIL
385
+ ITEM_ANTI_NEUTRAL = ExtraFlag.ANTI_NEUTRAL
386
+ ITEM_NOREMOVE = ExtraFlag.NOREMOVE
387
+ ITEM_INVENTORY = ExtraFlag.INVENTORY
388
+ ITEM_NOPURGE = ExtraFlag.NOPURGE
389
+ ITEM_ROT_DEATH = ExtraFlag.ROT_DEATH
390
+ ITEM_VIS_DEATH = ExtraFlag.VIS_DEATH
391
+ ITEM_NONMETAL = ExtraFlag.NONMETAL
392
+ ITEM_NOLOCATE = ExtraFlag.NOLOCATE
393
+ ITEM_MELT_DROP = ExtraFlag.MELT_DROP
394
+ ITEM_HAD_TIMER = ExtraFlag.HAD_TIMER
395
+ ITEM_SELL_EXTRACT = ExtraFlag.SELL_EXTRACT
396
+ ITEM_BURN_PROOF = ExtraFlag.BURN_PROOF
397
+ ITEM_NOUNCURSE = ExtraFlag.NOUNCURSE
398
+ # END extra_flags
399
+
400
+ # --- Exit/portal flags (merc.h) ---
401
+ # Bits map to letters A..Z; EX_ISDOOR=A (1<<0), EX_CLOSED=B (1<<1)
402
+ EX_ISDOOR = 1 << 0
403
+ EX_CLOSED = 1 << 1
404
+
405
+
406
+ def convert_flags_from_letters(flag_letters: str, flag_enum_class) -> int:
407
+ """Convert ROM letter-based flags (e.g., "ABCD") to integer bitmask.
408
+
409
+ Args:
410
+ flag_letters: String of flag letters from ROM .are file (e.g., "ABCD")
411
+ flag_enum_class: The IntFlag enum class (e.g., ExtraFlag)
412
+
413
+ Returns:
414
+ Integer bitmask combining all flags
415
+ """
416
+ bits = 0
417
+ for ch in flag_letters.strip():
418
+ if 'A' <= ch <= 'Z':
419
+ bits |= 1 << (ord(ch) - ord('A'))
420
+ elif 'a' <= ch <= 'z':
421
+ # Handle lowercase letters as well (some ROM variants use them)
422
+ bits |= 1 << (ord(ch) - ord('a') + 26)
423
+ return flag_enum_class(bits)
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import List, Dict, Tuple
4
+
5
+ from mud.models.object import Object
6
+ from mud.db.models import Character as DBCharacter, ObjectInstance as DBObjectInstance
7
+ from mud.registry import obj_registry
8
+
9
+
10
+ def load_objects_for_character(db_char: DBCharacter) -> Tuple[List[Object], Dict[str, Object]]:
11
+ inventory: List[Object] = []
12
+ equipment: Dict[str, Object] = {}
13
+
14
+ for inst in db_char.objects:
15
+ proto = obj_registry.get(inst.prototype_vnum)
16
+ if not proto:
17
+ continue
18
+ obj = Object(instance_id=inst.id, prototype=proto)
19
+ if inst.location and inst.location.startswith("equipment:"):
20
+ slot = inst.location.split(":", 1)[1]
21
+ equipment[slot] = obj
22
+ else:
23
+ inventory.append(obj)
24
+
25
+ return inventory, equipment
26
+
27
+
28
+ def save_objects_for_character(session, char, db_char: DBCharacter):
29
+ session.query(DBObjectInstance).filter_by(character_id=db_char.id).delete()
30
+
31
+ for obj in char.inventory:
32
+ inst = DBObjectInstance(
33
+ prototype_vnum=obj.prototype.vnum,
34
+ location="inventory",
35
+ character_id=db_char.id,
36
+ )
37
+ session.add(inst)
38
+
39
+ for slot, obj in char.equipment.items():
40
+ inst = DBObjectInstance(
41
+ prototype_vnum=obj.prototype.vnum,
42
+ location=f"equipment:{slot}",
43
+ character_id=db_char.id,
44
+ )
45
+ session.add(inst)