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.
- mud/__init__.py +0 -0
- mud/__main__.py +40 -0
- mud/account/__init__.py +20 -0
- mud/account/account_manager.py +62 -0
- mud/account/account_service.py +80 -0
- mud/advancement.py +62 -0
- mud/affects/saves.py +123 -0
- mud/agent/__init__.py +0 -0
- mud/agent/agent_protocol.py +19 -0
- mud/agent/character_agent.py +61 -0
- mud/combat/__init__.py +3 -0
- mud/combat/engine.py +189 -0
- mud/commands/__init__.py +3 -0
- mud/commands/admin_commands.py +77 -0
- mud/commands/advancement.py +36 -0
- mud/commands/alias_cmds.py +44 -0
- mud/commands/build.py +18 -0
- mud/commands/combat.py +16 -0
- mud/commands/communication.py +55 -0
- mud/commands/decorators.py +11 -0
- mud/commands/dispatcher.py +206 -0
- mud/commands/healer.py +81 -0
- mud/commands/help.py +14 -0
- mud/commands/imc.py +19 -0
- mud/commands/inspection.py +113 -0
- mud/commands/inventory.py +42 -0
- mud/commands/movement.py +71 -0
- mud/commands/notes.py +44 -0
- mud/commands/shop.py +138 -0
- mud/commands/socials.py +34 -0
- mud/config.py +59 -0
- mud/db/__init__.py +0 -0
- mud/db/init.py +27 -0
- mud/db/migrate_from_files.py +87 -0
- mud/db/migrations.py +7 -0
- mud/db/models.py +98 -0
- mud/db/seed.py +28 -0
- mud/db/session.py +11 -0
- mud/devtools/__init__.py +0 -0
- mud/devtools/agent_demo.py +19 -0
- mud/entrypoint.py +34 -0
- mud/game_loop.py +117 -0
- mud/imc/__init__.py +17 -0
- mud/imc/protocol.py +32 -0
- mud/loaders/__init__.py +27 -0
- mud/loaders/area_loader.py +73 -0
- mud/loaders/base_loader.py +38 -0
- mud/loaders/help_loader.py +17 -0
- mud/loaders/json_area_loader.py +203 -0
- mud/loaders/json_loader.py +285 -0
- mud/loaders/mob_loader.py +104 -0
- mud/loaders/obj_loader.py +76 -0
- mud/loaders/reset_loader.py +29 -0
- mud/loaders/room_loader.py +63 -0
- mud/loaders/shop_loader.py +41 -0
- mud/loaders/social_loader.py +16 -0
- mud/loaders/specials_loader.py +63 -0
- mud/logging/__init__.py +0 -0
- mud/logging/admin.py +40 -0
- mud/logging/agent_trace.py +9 -0
- mud/math/c_compat.py +27 -0
- mud/mobprog.py +72 -0
- mud/models/__init__.py +106 -0
- mud/models/area.py +33 -0
- mud/models/area_json.py +27 -0
- mud/models/board.py +49 -0
- mud/models/board_json.py +16 -0
- mud/models/character.py +195 -0
- mud/models/character_json.py +46 -0
- mud/models/constants.py +423 -0
- mud/models/conversion.py +45 -0
- mud/models/help.py +28 -0
- mud/models/help_json.py +14 -0
- mud/models/json_io.py +64 -0
- mud/models/mob.py +82 -0
- mud/models/note.py +29 -0
- mud/models/note_json.py +16 -0
- mud/models/obj.py +82 -0
- mud/models/object.py +28 -0
- mud/models/object_json.py +40 -0
- mud/models/player_json.py +29 -0
- mud/models/room.py +86 -0
- mud/models/room_json.py +46 -0
- mud/models/shop.py +21 -0
- mud/models/shop_json.py +17 -0
- mud/models/skill.py +25 -0
- mud/models/skill_json.py +20 -0
- mud/models/social.py +78 -0
- mud/models/social_json.py +20 -0
- mud/net/__init__.py +9 -0
- mud/net/ansi.py +27 -0
- mud/net/connection.py +174 -0
- mud/net/protocol.py +57 -0
- mud/net/session.py +21 -0
- mud/net/telnet_server.py +31 -0
- mud/network/__init__.py +0 -0
- mud/network/websocket_server.py +83 -0
- mud/network/websocket_session.py +21 -0
- mud/notes.py +45 -0
- mud/persistence.py +185 -0
- mud/registry.py +5 -0
- mud/scripts/convert_are_to_json.py +162 -0
- mud/scripts/convert_help_are_to_json.py +92 -0
- mud/scripts/convert_player_to_json.py +112 -0
- mud/scripts/convert_shops_to_json.py +64 -0
- mud/scripts/convert_skills_to_json.py +166 -0
- mud/scripts/convert_social_are_to_json.py +92 -0
- mud/scripts/load_test_data.py +17 -0
- mud/security/__init__.py +0 -0
- mud/security/bans.py +112 -0
- mud/security/hash_utils.py +20 -0
- mud/server.py +8 -0
- mud/skills/__init__.py +3 -0
- mud/skills/handlers.py +795 -0
- mud/skills/registry.py +97 -0
- mud/spawning/__init__.py +1 -0
- mud/spawning/mob_spawner.py +13 -0
- mud/spawning/obj_spawner.py +18 -0
- mud/spawning/reset_handler.py +222 -0
- mud/spawning/templates.py +63 -0
- mud/spec_funs.py +57 -0
- mud/time.py +48 -0
- mud/utils/rng_mm.py +123 -0
- mud/wiznet.py +74 -0
- mud/world/__init__.py +11 -0
- mud/world/linking.py +31 -0
- mud/world/look.py +29 -0
- mud/world/movement.py +135 -0
- mud/world/world_state.py +179 -0
- rom24_quickmud_python-1.2.2.dist-info/METADATA +236 -0
- rom24_quickmud_python-1.2.2.dist-info/RECORD +135 -0
- rom24_quickmud_python-1.2.2.dist-info/WHEEL +5 -0
- rom24_quickmud_python-1.2.2.dist-info/entry_points.txt +2 -0
- rom24_quickmud_python-1.2.2.dist-info/licenses/LICENSE +21 -0
- 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
|