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,203 @@
1
+ """JSON area loader - loads areas from converted JSON files."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+ from typing import Dict, Any
6
+
7
+ from mud.models.area_json import AreaJson
8
+ from mud.models.json_io import load_dataclass
9
+ from mud.models.area import Area
10
+ from mud.models.room import Room, Exit, ExtraDescr
11
+ from mud.models.mob import MobIndex
12
+ from mud.models.obj import ObjIndex
13
+ from mud.registry import room_registry, mob_registry, obj_registry, area_registry
14
+
15
+
16
+ def load_area_from_json(json_file_path: str) -> Area:
17
+ """Load an area from a JSON file and populate registries."""
18
+
19
+ with open(json_file_path, 'r', encoding='utf-8') as f:
20
+ # Load raw JSON data instead of using the dataclass loader to handle missing fields
21
+ data = json.load(f)
22
+
23
+ # Create the Area object
24
+ area = Area(
25
+ file_name=Path(json_file_path).name,
26
+ name=data.get('name', 'Unknown Area'),
27
+ min_vnum=data.get('vnum_range', {}).get('min', 0),
28
+ max_vnum=data.get('vnum_range', {}).get('max', 0),
29
+ builders=', '.join(data.get('builders', [])),
30
+ vnum=data.get('vnum_range', {}).get('min', 0),
31
+ age=15, # Default age for areas
32
+ empty=False
33
+ )
34
+
35
+ # Register the area
36
+ area_key = area.min_vnum
37
+ if (
38
+ area_key != 0
39
+ and area_key in area_registry
40
+ and area_registry[area_key].file_name != area.file_name
41
+ ):
42
+ raise ValueError(f"duplicate area vnum {area_key}")
43
+ area_registry[area_key] = area
44
+
45
+ # Load rooms
46
+ for room_data in data.get('rooms', []):
47
+ room = _json_to_room(room_data, area)
48
+ room_registry[room.vnum] = room
49
+
50
+ # Load mobiles
51
+ for mob_data in data.get('mobiles', []):
52
+ mob = _json_to_mob(mob_data, area)
53
+ mob_registry[mob.vnum] = mob
54
+
55
+ # Load objects
56
+ for obj_data in data.get('objects', []):
57
+ obj = _json_to_obj(obj_data, area)
58
+ obj_registry[obj.vnum] = obj
59
+
60
+ return area
61
+
62
+
63
+ def _json_to_room(room_data: Dict[str, Any], area: Area) -> Room:
64
+ """Convert room JSON data to Room model."""
65
+ room = Room(
66
+ vnum=room_data.get('id', 0),
67
+ name=room_data.get('name', 'Unnamed Room'),
68
+ description=room_data.get('description', 'No description.'),
69
+ area=area,
70
+ sector_type=_sector_name_to_int(room_data.get('sector_type', 'inside')),
71
+ room_flags=_flags_to_int(room_data.get('flags', []))
72
+ )
73
+
74
+ # Handle exits - initialize with None for all directions
75
+ room.exits = [None] * 10
76
+ exits_data = room_data.get('exits', {})
77
+ if exits_data:
78
+ direction_map = {
79
+ 'north': 0, 'east': 1, 'south': 2, 'west': 3, 'up': 4, 'down': 5,
80
+ 'northeast': 6, 'northwest': 7, 'southeast': 8, 'southwest': 9
81
+ }
82
+
83
+ for direction, exit_data in exits_data.items():
84
+ if direction in direction_map:
85
+ dir_num = direction_map[direction]
86
+ exit = Exit(
87
+ vnum=exit_data.get('to_room', 0),
88
+ exit_info=_flags_to_int(exit_data.get('flags', [])),
89
+ keyword=exit_data.get('keyword'),
90
+ description=exit_data.get('description')
91
+ )
92
+ room.exits[dir_num] = exit
93
+
94
+ # Handle extra descriptions
95
+ extra_descriptions = room_data.get('extra_descriptions', [])
96
+ for extra_desc in extra_descriptions:
97
+ room.extra_descr.append(ExtraDescr(
98
+ keyword=extra_desc.get('keyword'),
99
+ description=extra_desc.get('description')
100
+ ))
101
+
102
+ return room
103
+
104
+
105
+ def _json_to_mob(mob_data: Dict[str, Any], area: Area) -> MobIndex:
106
+ """Convert mobile JSON data to MobIndex model."""
107
+ mob = MobIndex(
108
+ vnum=mob_data.get('id', 0),
109
+ player_name=mob_data.get('name', 'unnamed mobile'),
110
+ short_descr=mob_data.get('short_description', mob_data.get('name', 'unnamed mobile')),
111
+ long_descr=mob_data.get('long_description', ''),
112
+ description=mob_data.get('description', 'No description.'),
113
+ area=area,
114
+ level=mob_data.get('level', 1),
115
+ alignment=mob_data.get('alignment', 0),
116
+ act=_flags_to_int(mob_data.get('flags', [])),
117
+ new_format=True
118
+ )
119
+
120
+ return mob
121
+
122
+
123
+ def _json_to_obj(obj_data: Dict[str, Any], area: Area) -> ObjIndex:
124
+ """Convert object JSON data to ObjIndex model."""
125
+ obj = ObjIndex(
126
+ vnum=obj_data.get('id', 0),
127
+ name=obj_data.get('name', 'unnamed object'),
128
+ short_descr=obj_data.get('short_description', obj_data.get('name', 'unnamed object')),
129
+ description=obj_data.get('description', 'No description.'),
130
+ area=area,
131
+ item_type=_item_type_to_int(obj_data.get('item_type', 'trash')),
132
+ extra_flags=_flags_to_int(obj_data.get('flags', [])),
133
+ wear_flags=_flags_to_int(obj_data.get('wear_flags', [])),
134
+ level=obj_data.get('level', 0),
135
+ weight=obj_data.get('weight', 0),
136
+ cost=obj_data.get('cost', 0),
137
+ material=obj_data.get('material', 'unknown')
138
+ )
139
+
140
+ # Handle values array
141
+ values = obj_data.get('values', [0, 0, 0, 0, 0])
142
+ obj.value = values[:5] if len(values) >= 5 else values + [0] * (5 - len(values))
143
+
144
+ return obj
145
+
146
+
147
+ def _sector_name_to_int(sector_name: str) -> int:
148
+ """Convert sector name to integer."""
149
+ sector_map = {
150
+ 'inside': 0, 'city': 1, 'field': 2, 'forest': 3, 'hills': 4,
151
+ 'mountain': 5, 'water_swim': 6, 'water_noswim': 7, 'underwater': 8, 'air': 9,
152
+ 'desert': 10, 'unknown': 11
153
+ }
154
+ return sector_map.get(sector_name, 0)
155
+
156
+
157
+ def _item_type_to_int(item_type: str) -> int:
158
+ """Convert item type name to integer."""
159
+ type_map = {
160
+ 'light': 1, 'scroll': 2, 'wand': 3, 'staff': 4, 'weapon': 5,
161
+ 'fireweapon': 6, 'missile': 7, 'treasure': 8, 'armor': 9, 'potion': 10,
162
+ 'clothing': 11, 'furniture': 12, 'trash': 13, 'oldtrap': 14, 'container': 15,
163
+ 'note': 16, 'drinkcon': 17, 'key': 18, 'food': 19, 'money': 20,
164
+ 'pen': 21, 'boat': 22, 'corpse': 23, 'corpse_pc': 24, 'fountain': 25,
165
+ 'pill': 26, 'protect': 27, 'map': 28, 'portal': 29, 'warp_stone': 30,
166
+ 'room_key': 31, 'gem': 32, 'jewelry': 33, 'jukebox': 34
167
+ }
168
+ return type_map.get(item_type, 13) # Default to 'trash'
169
+
170
+
171
+ def _flags_to_int(flags_list) -> int:
172
+ """Convert list of flag names to integer bitfield."""
173
+ if not flags_list:
174
+ return 0
175
+
176
+ # For now, return 0 as we don't have the flag mappings
177
+ # This can be extended later with proper flag name to bit mappings
178
+ return 0
179
+
180
+
181
+ def load_all_areas_from_json(areas_dir: str = "data/areas") -> None:
182
+ """Load all areas from JSON files in the specified directory."""
183
+ areas_path = Path(areas_dir)
184
+
185
+ if not areas_path.exists():
186
+ raise FileNotFoundError(f"Areas directory not found: {areas_dir}")
187
+
188
+ json_files = list(areas_path.glob("*.json"))
189
+ if not json_files:
190
+ raise FileNotFoundError(f"No JSON files found in: {areas_dir}")
191
+
192
+ print(f"Loading {len(json_files)} areas from JSON files...")
193
+
194
+ for json_file in sorted(json_files):
195
+ try:
196
+ area = load_area_from_json(str(json_file))
197
+ print(f"✅ Loaded area: {area.name} ({area.min_vnum}-{area.max_vnum})")
198
+ except Exception as e:
199
+ print(f"❌ Failed to load {json_file.name}: {e}")
200
+ raise
201
+
202
+ print(f"🎯 Successfully loaded {len(area_registry)} areas, {len(room_registry)} rooms, "
203
+ f"{len(mob_registry)} mobs, {len(obj_registry)} objects")
@@ -0,0 +1,285 @@
1
+ """
2
+ JSON area loader for QuickMUD.
3
+
4
+ This loader reads areas from JSON files created by the convert_are_to_json.py script
5
+ with complete field mapping from the original ROM .are format.
6
+ """
7
+
8
+ import json
9
+ import logging
10
+ from pathlib import Path
11
+ from typing import Dict, Any, List
12
+
13
+ from ..models.area import Area
14
+ from ..models.room import Room, Exit, ExtraDescr
15
+ from ..models.mob import MobIndex
16
+ from ..models.obj import ObjIndex, Affect
17
+ from ..models.room_json import ResetJson
18
+ from mud.models.constants import Direction, Sector, Sex, ITEM_INVENTORY
19
+ from mud.registry import area_registry, room_registry, mob_registry, obj_registry
20
+
21
+
22
+ def _rom_flags_to_int(flags_str: str) -> int:
23
+ """Convert ROM-style letter flags to integer bitfield.
24
+
25
+ ROM uses letters A-Z and aa-dd to represent bit positions:
26
+ A=1<<0, B=1<<1, ..., Z=1<<25, aa=1<<26, bb=1<<27, cc=1<<28, dd=1<<29
27
+ """
28
+ if not flags_str or flags_str == '0':
29
+ return 0
30
+
31
+ result = 0
32
+
33
+ # Handle single characters and double characters
34
+ i = 0
35
+ while i < len(flags_str):
36
+ if i + 1 < len(flags_str) and flags_str[i:i+2] in ['aa', 'bb', 'cc', 'dd']:
37
+ # Double character flags (26-29)
38
+ double_char = flags_str[i:i+2]
39
+ if double_char == 'aa':
40
+ result |= 1 << 26
41
+ elif double_char == 'bb':
42
+ result |= 1 << 27
43
+ elif double_char == 'cc':
44
+ result |= 1 << 28
45
+ elif double_char == 'dd':
46
+ result |= 1 << 29
47
+ i += 2
48
+ else:
49
+ # Single character flags (0-25)
50
+ char = flags_str[i]
51
+ if 'A' <= char <= 'Z':
52
+ result |= 1 << (ord(char) - ord('A'))
53
+ elif 'a' <= char <= 'z':
54
+ result |= 1 << (ord(char) - ord('a'))
55
+ i += 1
56
+
57
+ return result
58
+
59
+ logger = logging.getLogger(__name__)
60
+
61
+
62
+ def load_area_from_json(json_file_path: str) -> Area:
63
+ """Load a complete area from JSON file with all ROM fields."""
64
+
65
+ with open(json_file_path, 'r') as f:
66
+ data = json.load(f)
67
+
68
+ # Handle two different JSON formats
69
+ if 'area' in data:
70
+ # Format 2: Nested structure with area object
71
+ area_data = data['area']
72
+ area = Area(
73
+ vnum=area_data.get('vnum', 0),
74
+ name=area_data.get('name', ''),
75
+ file_name=area_data.get('filename', Path(json_file_path).stem),
76
+ low_range=area_data.get('min_level', 0),
77
+ high_range=area_data.get('max_level', 0),
78
+ builders=area_data.get('builders', ''),
79
+ credits=area_data.get('credits', ''),
80
+ min_vnum=area_data.get('min_vnum', area_data.get('vnum', 0)),
81
+ max_vnum=area_data.get('max_vnum', area_data.get('vnum', 0)),
82
+ area_flags=area_data.get('area_flags', 0),
83
+ security=area_data.get('security', 0),
84
+ )
85
+ rooms_key = 'rooms'
86
+ mobs_key = 'mobs'
87
+ objects_key = 'objects'
88
+ else:
89
+ # Format 1: Root-level structure with vnum_range
90
+ vnum_range = data.get('vnum_range', {'min': 0, 'max': 0})
91
+ area = Area(
92
+ vnum=vnum_range.get('min', 0), # Use min vnum as area vnum
93
+ name=data.get('name', ''),
94
+ file_name=Path(json_file_path).stem,
95
+ low_range=vnum_range.get('min', 0),
96
+ high_range=vnum_range.get('max', 0),
97
+ builders=', '.join(data.get('builders', [])),
98
+ min_vnum=vnum_range.get('min', 0),
99
+ max_vnum=vnum_range.get('max', 0),
100
+ area_flags=0, # Not in JSON, default to 0
101
+ security=0, # Not in JSON, default to 0
102
+ )
103
+ rooms_key = 'rooms'
104
+ mobs_key = 'mobiles' # Different key in format 1
105
+ objects_key = 'objects'
106
+
107
+ # Register area
108
+ area_registry[area.vnum] = area
109
+
110
+ # Load rooms
111
+ _load_rooms_from_json(data.get(rooms_key, []), area)
112
+
113
+ # Load mobs
114
+ _load_mobs_from_json(data.get(mobs_key, []), area)
115
+
116
+ # Load objects
117
+ _load_objects_from_json(data.get(objects_key, []), area)
118
+
119
+ # Load area-level resets
120
+ for reset_data in data.get('resets', []):
121
+ reset = ResetJson(
122
+ command=reset_data['command'],
123
+ arg1=reset_data['arg1'],
124
+ arg2=reset_data['arg2'],
125
+ arg3=reset_data['arg3'],
126
+ arg4=reset_data['arg4'],
127
+ )
128
+ area.resets.append(reset)
129
+
130
+ logger.info(f"Loaded area {area.name} from JSON with {len(data.get(rooms_key, []))} rooms, "
131
+ f"{len(data.get(mobs_key, []))} mobs, {len(data.get(objects_key, []))} objects, "
132
+ f"{len(data.get('resets', []))} resets")
133
+
134
+ return area
135
+
136
+
137
+ def _load_rooms_from_json(rooms_data: List[Dict[str, Any]], area: Area) -> None:
138
+ """Load rooms from JSON data with complete field mapping."""
139
+
140
+ for room_data in rooms_data:
141
+ # Parse sector type
142
+ sector_str = room_data.get('sector_type', 'inside')
143
+ try:
144
+ if sector_str.isdigit():
145
+ sector = Sector(int(sector_str))
146
+ else:
147
+ sector = Sector[sector_str.upper()]
148
+ except (ValueError, KeyError):
149
+ logger.warning(f"Unknown sector type: {sector_str}, defaulting to INSIDE")
150
+ sector = Sector.INSIDE
151
+
152
+ # Create room
153
+ room = Room(
154
+ vnum=room_data['id'],
155
+ name=room_data.get('name', ''),
156
+ description=room_data.get('description', ''),
157
+ sector_type=sector,
158
+ room_flags=room_data.get('flags', 0),
159
+ area=area,
160
+ )
161
+
162
+ # Load exits
163
+ exits_data = room_data.get('exits', {})
164
+ for direction_name, exit_data in exits_data.items():
165
+ try:
166
+ direction = Direction[direction_name.upper()]
167
+ exit_obj = Exit(
168
+ vnum=exit_data.get('to_room', 0),
169
+ description=exit_data.get('description', ''),
170
+ keyword=exit_data.get('keyword', ''),
171
+ flags=exit_data.get('flags', '0'),
172
+ key=exit_data.get('key', 0),
173
+ )
174
+ room.exits[direction.value] = exit_obj
175
+ except KeyError:
176
+ logger.warning(f"Unknown direction: {direction_name}")
177
+
178
+ # Load extra descriptions
179
+ for extra_data in room_data.get('extra_descriptions', []):
180
+ extra_desc = ExtraDescr(
181
+ keyword=extra_data['keyword'],
182
+ description=extra_data['description']
183
+ )
184
+ room.extra_descr.append(extra_desc)
185
+
186
+ room_registry[room.vnum] = room
187
+
188
+
189
+ def _load_mobs_from_json(mobs_data: List[Dict[str, Any]], area: Area) -> None:
190
+ """Load mobs from JSON data with complete field mapping."""
191
+
192
+ for mob_data in mobs_data:
193
+ # Parse sex
194
+ sex_str = mob_data.get('sex', 'none')
195
+ try:
196
+ sex = Sex[sex_str.upper()]
197
+ except KeyError:
198
+ logger.warning(f"Unknown sex: {sex_str}, defaulting to NONE")
199
+ sex = Sex.NONE
200
+
201
+ # Create mob with all fields
202
+ mob = MobIndex(
203
+ vnum=mob_data['id'],
204
+ player_name=mob_data.get('player_name', ''),
205
+ short_descr=mob_data.get('name', ''),
206
+ long_descr=mob_data.get('long_description', ''),
207
+ description=mob_data.get('description', ''),
208
+ race=mob_data.get('race', ''),
209
+ act_flags=mob_data.get('act_flags', ''),
210
+ affected_by=mob_data.get('affected_by', ''),
211
+ alignment=mob_data.get('alignment', 0),
212
+ group=mob_data.get('group', 0),
213
+ level=mob_data.get('level', 1),
214
+ thac0=mob_data.get('thac0', 20),
215
+ ac=mob_data.get('ac', '1d1+0'),
216
+ hit_dice=mob_data.get('hit_dice', '1d1+0'),
217
+ mana_dice=mob_data.get('mana_dice', '1d1+0'),
218
+ damage_dice=mob_data.get('damage_dice', '1d4+0'),
219
+ damage_type=mob_data.get('damage_type', 'beating'),
220
+ ac_pierce=mob_data.get('ac_pierce', 0),
221
+ ac_bash=mob_data.get('ac_bash', 0),
222
+ ac_slash=mob_data.get('ac_slash', 0),
223
+ ac_exotic=mob_data.get('ac_exotic', 0),
224
+ offensive=mob_data.get('offensive', ''),
225
+ immune=mob_data.get('immune', ''),
226
+ resist=mob_data.get('resist', ''),
227
+ vuln=mob_data.get('vuln', ''),
228
+ start_pos=mob_data.get('start_pos', 'standing'),
229
+ default_pos=mob_data.get('default_pos', 'standing'),
230
+ sex=sex,
231
+ wealth=mob_data.get('wealth', 0),
232
+ form=mob_data.get('form', '0'),
233
+ parts=mob_data.get('parts', '0'),
234
+ size=mob_data.get('size', 'medium'),
235
+ material=mob_data.get('material', '0'),
236
+ area=area,
237
+ )
238
+
239
+ mob_registry[mob.vnum] = mob
240
+
241
+
242
+ def _load_objects_from_json(objects_data: List[Dict[str, Any]], area: Area) -> None:
243
+ """Load objects from JSON data with complete field mapping."""
244
+
245
+ for obj_data in objects_data:
246
+ # Create object with all fields
247
+ obj = ObjIndex(
248
+ vnum=obj_data['id'],
249
+ short_descr=obj_data.get('name', ''),
250
+ description=obj_data.get('description', ''),
251
+ material=obj_data.get('material', ''),
252
+ item_type=obj_data.get('item_type', 'trash'),
253
+ extra_flags=_rom_flags_to_int(obj_data.get('extra_flags', '')),
254
+ wear_flags=obj_data.get('wear_flags', ''),
255
+ weight=obj_data.get('weight', 0),
256
+ cost=obj_data.get('cost', 0),
257
+ condition=obj_data.get('condition', 'P'),
258
+ value=obj_data.get('values', [0, 0, 0, 0, 0]),
259
+ affects=obj_data.get('affects', []),
260
+ extra_descr=obj_data.get('extra_descriptions', []),
261
+ area=area,
262
+ )
263
+
264
+ obj_registry[obj.vnum] = obj
265
+
266
+
267
+ def load_all_areas_from_json(json_dir: str) -> Dict[int, Area]:
268
+ """Load all areas from JSON files in a directory."""
269
+
270
+ json_path = Path(json_dir)
271
+ if not json_path.exists():
272
+ logger.error(f"JSON directory not found: {json_dir}")
273
+ return {}
274
+
275
+ areas = {}
276
+
277
+ for json_file in json_path.glob("*.json"):
278
+ try:
279
+ area = load_area_from_json(str(json_file))
280
+ areas[area.vnum] = area
281
+ except Exception as e:
282
+ logger.error(f"Failed to load {json_file}: {e}")
283
+
284
+ logger.info(f"Loaded {len(areas)} areas from JSON")
285
+ return areas
@@ -0,0 +1,104 @@
1
+ from mud.models.mob import MobIndex
2
+ from mud.registry import mob_registry
3
+ from .base_loader import BaseTokenizer
4
+
5
+
6
+ def load_mobiles(tokenizer: BaseTokenizer, area):
7
+ while True:
8
+ line = tokenizer.next_line()
9
+ if line is None:
10
+ break
11
+ if line.startswith('#'):
12
+ if line == '#0' or line.startswith('#$'):
13
+ break
14
+ vnum = int(line[1:])
15
+ player_name = tokenizer.next_line().rstrip('~')
16
+ short_descr = tokenizer.next_line().rstrip('~')
17
+ long_descr = tokenizer.read_string_tilde()
18
+ desc = tokenizer.read_string_tilde()
19
+ race = tokenizer.next_line().rstrip('~')
20
+
21
+ # Parse act flags, affected flags, alignment, group
22
+ act_line = tokenizer.next_line().split()
23
+ act_flags = act_line[0] if len(act_line) > 0 else ''
24
+ affected_by = act_line[1] if len(act_line) > 1 else ''
25
+ alignment = int(act_line[2]) if len(act_line) > 2 and act_line[2].lstrip('-').isdigit() else 0
26
+ group = int(act_line[3]) if len(act_line) > 3 and act_line[3].isdigit() else 0
27
+
28
+ # Parse level, thac0, ac, hitnodice, hitdicenum, hitdicesides, manadice, damroll, damdice
29
+ stats_line = tokenizer.next_line().split()
30
+ level = int(stats_line[0]) if len(stats_line) > 0 and stats_line[0].isdigit() else 1
31
+ thac0 = int(stats_line[1]) if len(stats_line) > 1 and stats_line[1].lstrip('-').isdigit() else 20
32
+ ac = stats_line[2] if len(stats_line) > 2 else '1d1+0'
33
+ hit_dice = stats_line[3] if len(stats_line) > 3 else '1d1+0'
34
+ mana_dice = stats_line[4] if len(stats_line) > 4 else '1d1+0'
35
+ damage_dice = stats_line[5] if len(stats_line) > 5 else '1d4+0'
36
+ damage_type = stats_line[6] if len(stats_line) > 6 else 'beating'
37
+
38
+ # Parse armor class values
39
+ ac_line = tokenizer.next_line().split()
40
+ ac_pierce = int(ac_line[0]) if len(ac_line) > 0 and ac_line[0].lstrip('-').isdigit() else 0
41
+ ac_bash = int(ac_line[1]) if len(ac_line) > 1 and ac_line[1].lstrip('-').isdigit() else 0
42
+ ac_slash = int(ac_line[2]) if len(ac_line) > 2 and ac_line[2].lstrip('-').isdigit() else 0
43
+ ac_exotic = int(ac_line[3]) if len(ac_line) > 3 and ac_line[3].lstrip('-').isdigit() else 0
44
+
45
+ # Parse off/imm/res/vuln flags
46
+ flags_line = tokenizer.next_line().split()
47
+ offensive = flags_line[0] if len(flags_line) > 0 else ''
48
+ immune = flags_line[1] if len(flags_line) > 1 else ''
49
+ resist = flags_line[2] if len(flags_line) > 2 else ''
50
+ vuln = flags_line[3] if len(flags_line) > 3 else ''
51
+
52
+ # Parse position, sex, wealth
53
+ pos_line = tokenizer.next_line().split()
54
+ start_pos = pos_line[0] if len(pos_line) > 0 else 'standing'
55
+ default_pos = pos_line[1] if len(pos_line) > 1 else 'standing'
56
+ sex = pos_line[2] if len(pos_line) > 2 else 'neutral'
57
+ wealth = int(pos_line[3]) if len(pos_line) > 3 and pos_line[3].isdigit() else 0
58
+
59
+ # Parse form/parts/size/material
60
+ form_line = tokenizer.next_line().split()
61
+ form = form_line[0] if len(form_line) > 0 else '0'
62
+ parts = form_line[1] if len(form_line) > 1 else '0'
63
+ size = form_line[2] if len(form_line) > 2 else 'medium'
64
+ material = form_line[3] if len(form_line) > 3 else '0'
65
+
66
+ mob = MobIndex(
67
+ vnum=vnum,
68
+ player_name=player_name,
69
+ short_descr=short_descr,
70
+ long_descr=long_descr,
71
+ description=desc,
72
+ race=race,
73
+ act_flags=act_flags,
74
+ affected_by=affected_by,
75
+ alignment=alignment,
76
+ group=group,
77
+ level=level,
78
+ thac0=thac0,
79
+ ac=ac,
80
+ hit_dice=hit_dice,
81
+ mana_dice=mana_dice,
82
+ damage_dice=damage_dice,
83
+ damage_type=damage_type,
84
+ ac_pierce=ac_pierce,
85
+ ac_bash=ac_bash,
86
+ ac_slash=ac_slash,
87
+ ac_exotic=ac_exotic,
88
+ offensive=offensive,
89
+ immune=immune,
90
+ resist=resist,
91
+ vuln=vuln,
92
+ start_pos=start_pos,
93
+ default_pos=default_pos,
94
+ sex=sex,
95
+ wealth=wealth,
96
+ form=form,
97
+ parts=parts,
98
+ size=size,
99
+ material=material,
100
+ area=area
101
+ )
102
+ mob_registry[vnum] = mob
103
+ elif line == '$':
104
+ break
@@ -0,0 +1,76 @@
1
+ from mud.models.obj import ObjIndex
2
+ from mud.registry import obj_registry
3
+ from .base_loader import BaseTokenizer
4
+
5
+
6
+ def load_objects(tokenizer: BaseTokenizer, area):
7
+ while True:
8
+ line = tokenizer.next_line()
9
+ if line is None:
10
+ break
11
+ if line.startswith('#'):
12
+ if line == '#0' or line.startswith('#$'):
13
+ break
14
+ vnum = int(line[1:])
15
+ name = tokenizer.next_line().rstrip('~')
16
+ short_descr = tokenizer.next_line().rstrip('~')
17
+ desc = tokenizer.read_string_tilde()
18
+ extra = tokenizer.read_string_tilde()
19
+
20
+ # Parse item type and extra flags
21
+ type_flags_line = tokenizer.next_line().split()
22
+ item_type = type_flags_line[0] if len(type_flags_line) > 0 else 'trash'
23
+ extra_flags = type_flags_line[1] if len(type_flags_line) > 1 else ''
24
+ wear_flags = type_flags_line[2] if len(type_flags_line) > 2 else ''
25
+
26
+ # Parse values
27
+ values_line = tokenizer.next_line().split()
28
+ value0 = int(values_line[0]) if len(values_line) > 0 and values_line[0].lstrip('-').isdigit() else 0
29
+ value1 = int(values_line[1]) if len(values_line) > 1 and values_line[1].lstrip('-').isdigit() else 0
30
+ value2 = int(values_line[2]) if len(values_line) > 2 and values_line[2].lstrip('-').isdigit() else 0
31
+ value3 = int(values_line[3]) if len(values_line) > 3 and values_line[3].lstrip('-').isdigit() else 0
32
+ value4 = int(values_line[4]) if len(values_line) > 4 and values_line[4].lstrip('-').isdigit() else 0
33
+
34
+ # Parse weight, cost, condition
35
+ weight_cost = tokenizer.next_line().split()
36
+ weight = int(weight_cost[0]) if len(weight_cost) > 0 and weight_cost[0].isdigit() else 0
37
+ cost = int(weight_cost[1]) if len(weight_cost) > 1 and weight_cost[1].isdigit() else 0
38
+ condition = weight_cost[2] if len(weight_cost) > 2 else 'P'
39
+
40
+ obj = ObjIndex(
41
+ vnum=vnum,
42
+ name=name,
43
+ short_descr=short_descr,
44
+ description=desc,
45
+ material=extra,
46
+ item_type=item_type,
47
+ extra_flags=extra_flags,
48
+ wear_flags=wear_flags,
49
+ value=[value0, value1, value2, value3, value4],
50
+ weight=weight,
51
+ cost=cost,
52
+ condition=condition,
53
+ area=area,
54
+ )
55
+ obj_registry[vnum] = obj
56
+
57
+ # Process extra descriptions and affects
58
+ while True:
59
+ peek = tokenizer.peek_line()
60
+ if peek is None or peek.startswith('#') or peek == '$':
61
+ break
62
+ if peek.startswith('E'):
63
+ tokenizer.next_line() # consume 'E'
64
+ keyword = tokenizer.next_line().rstrip('~')
65
+ descr = tokenizer.read_string_tilde()
66
+ obj.extra_descr.append({'keyword': keyword, 'description': descr})
67
+ elif peek.startswith('A'):
68
+ tokenizer.next_line() # consume 'A'
69
+ affect_line = tokenizer.next_line().split()
70
+ location = int(affect_line[0]) if len(affect_line) > 0 and affect_line[0].lstrip('-').isdigit() else 0
71
+ modifier = int(affect_line[1]) if len(affect_line) > 1 and affect_line[1].lstrip('-').isdigit() else 0
72
+ obj.affects.append({'location': location, 'modifier': modifier})
73
+ else:
74
+ break
75
+ elif line == '$':
76
+ break