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,29 @@
1
+ from .base_loader import BaseTokenizer
2
+ from mud.models.room_json import ResetJson
3
+
4
+
5
+ def load_resets(tokenizer: BaseTokenizer, area):
6
+ """Parse reset lines and store them on the area."""
7
+ while True:
8
+ line = tokenizer.next_line()
9
+ if line is None:
10
+ break
11
+ if line == 'S':
12
+ continue
13
+ if line == '$' or line.startswith('#'):
14
+ # allow outer loader to handle following sections
15
+ tokenizer.index -= 1
16
+ break
17
+ parts = line.split()
18
+ if not parts:
19
+ continue
20
+ cmd = parts[0]
21
+ try:
22
+ nums = [int(p) for p in parts[1:] if p.lstrip('-').isdigit()]
23
+ except ValueError:
24
+ continue
25
+ # pad to four integers
26
+ while len(nums) < 4:
27
+ nums.append(0)
28
+ reset = ResetJson(command=cmd, arg1=nums[0], arg2=nums[1], arg3=nums[2], arg4=nums[3])
29
+ area.resets.append(reset)
@@ -0,0 +1,63 @@
1
+ from mud.models.room import Room, Exit, ExtraDescr
2
+ from mud.registry import room_registry
3
+ from .base_loader import BaseTokenizer
4
+
5
+
6
+ def load_rooms(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':
13
+ break
14
+ vnum = int(line[1:])
15
+ name = tokenizer.next_line().rstrip('~')
16
+ desc = tokenizer.read_string_tilde()
17
+ flags_line = tokenizer.next_line()
18
+ tokens = flags_line.split()
19
+ room_flags = int(tokens[0]) if tokens else 0
20
+ sector_type = int(tokens[-1]) if tokens else 0
21
+ room = Room(vnum=vnum, name=name, description=desc, room_flags=room_flags, sector_type=sector_type, area=area)
22
+ room_registry[vnum] = room
23
+ # parse additional blocks until 'S'
24
+ while True:
25
+ peek = tokenizer.peek_line()
26
+ if peek is None:
27
+ break
28
+ if peek.startswith('D'):
29
+ dir_line = tokenizer.next_line()
30
+ exit_desc = tokenizer.read_string_tilde()
31
+ exit_keywords = tokenizer.read_string_tilde()
32
+ info_line = tokenizer.next_line()
33
+ if info_line is None:
34
+ break
35
+ info_parts = info_line.split()
36
+ exit_flags = info_parts[0] if len(info_parts) >= 1 else '0'
37
+ if len(info_parts) >= 3:
38
+ key = int(info_parts[1])
39
+ to_vnum = int(info_parts[2])
40
+ else:
41
+ key = 0
42
+ to_vnum = 0
43
+ exit_obj = Exit(vnum=to_vnum, key=key, description=exit_desc, keyword=exit_keywords, flags=exit_flags)
44
+ # direction char at Dn
45
+ idx = int(dir_line[1])
46
+ if idx < len(room.exits):
47
+ room.exits[idx] = exit_obj
48
+ continue
49
+ if peek.startswith('E'):
50
+ tokenizer.next_line()
51
+ keyword = tokenizer.next_line().rstrip('~')
52
+ descr = tokenizer.read_string_tilde()
53
+ room.extra_descr.append(ExtraDescr(keyword=keyword, description=descr))
54
+ continue
55
+ if peek == 'S':
56
+ tokenizer.next_line()
57
+ break
58
+ if peek.startswith('#'):
59
+ break
60
+ # consume unknown line
61
+ tokenizer.next_line()
62
+ elif line == '$':
63
+ break
@@ -0,0 +1,41 @@
1
+ from dataclasses import dataclass, field
2
+ from .base_loader import BaseTokenizer
3
+ from mud.registry import shop_registry
4
+
5
+
6
+ @dataclass
7
+ class Shop:
8
+ """Minimal representation of SHOP_DATA."""
9
+
10
+ keeper: int
11
+ buy_types: list[int] = field(default_factory=list)
12
+ profit_buy: int = 100
13
+ profit_sell: int = 100
14
+ open_hour: int = 0
15
+ close_hour: int = 23
16
+
17
+
18
+ def load_shops(tokenizer: BaseTokenizer, area) -> None:
19
+ """Load shop entries and register by keeper vnum."""
20
+ while True:
21
+ line = tokenizer.next_line()
22
+ if line is None:
23
+ break
24
+ line = line.strip()
25
+ if not line or line.startswith('0'):
26
+ break
27
+ parts = line.split()
28
+ keeper = int(parts[0])
29
+ buy_types = [int(p) for p in parts[1:6]]
30
+ profit_buy = int(parts[6]) if len(parts) > 6 else 100
31
+ profit_sell = int(parts[7]) if len(parts) > 7 else 100
32
+ open_hour = int(parts[8]) if len(parts) > 8 else 0
33
+ close_hour = int(parts[9]) if len(parts) > 9 else 23
34
+ shop_registry[keeper] = Shop(
35
+ keeper=keeper,
36
+ buy_types=buy_types,
37
+ profit_buy=profit_buy,
38
+ profit_sell=profit_sell,
39
+ open_hour=open_hour,
40
+ close_hour=close_hour,
41
+ )
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ from mud.models.social import Social, register_social
7
+ from mud.models.social_json import SocialJson
8
+
9
+
10
+ def load_socials(path: str) -> None:
11
+ """Load socials from a JSON file into the registry."""
12
+ with open(Path(path), "r", encoding="utf-8") as f:
13
+ data = json.load(f)
14
+ for entry in data:
15
+ sj = SocialJson(**entry)
16
+ register_social(Social.from_json(sj))
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ from mud.registry import mob_registry
4
+ from .base_loader import BaseTokenizer
5
+
6
+
7
+ def load_specials(tokenizer: BaseTokenizer, area) -> None:
8
+ """Load #SPECIALS section and attach spec_fun names to MobIndex.
9
+
10
+ Format (doc/area.txt § #SPECIALS):
11
+ #SPECIALS
12
+ { M <mob-vnum> <spec-fun> <comment-to-eol> }
13
+ S
14
+ """
15
+ while True:
16
+ line = tokenizer.next_line()
17
+ if line is None:
18
+ break
19
+ s = line.strip()
20
+ if not s:
21
+ continue
22
+ if s == 'S':
23
+ break
24
+ if not s or s.startswith('*'):
25
+ continue
26
+ if s[0] in '{':
27
+ # Lines may be grouped in braces; consume but ignore braces
28
+ s = s.lstrip('{').rstrip('}')
29
+ s = s.strip()
30
+ if not s:
31
+ continue
32
+ if s.startswith('M '):
33
+ parts = s.split()
34
+ # parts: ['M', '<vnum>', '<spec>'] + optional comments
35
+ if len(parts) >= 3:
36
+ try:
37
+ vnum = int(parts[1])
38
+ except ValueError:
39
+ continue
40
+ spec_name = parts[2]
41
+ proto = mob_registry.get(vnum)
42
+ if proto is not None:
43
+ proto.spec_fun = spec_name
44
+ # No return value needed; registry updated in place
45
+
46
+
47
+ def apply_specials_from_json(entries: list[dict]) -> None:
48
+ """Attach spec_fun names from a JSON "specials" list to mob prototypes.
49
+
50
+ Each entry must be a dict with keys: {"mob_vnum": int, "spec": str}.
51
+ Unknown vnums are ignored (matching ROM's tolerant loaders).
52
+ """
53
+ for entry in entries or []:
54
+ try:
55
+ vnum = int(entry.get("mob_vnum"))
56
+ except Exception:
57
+ continue
58
+ spec = entry.get("spec")
59
+ if not spec:
60
+ continue
61
+ proto = mob_registry.get(vnum)
62
+ if proto is not None:
63
+ proto.spec_fun = str(spec)
File without changes
mud/logging/admin.py ADDED
@@ -0,0 +1,40 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime
4
+ from pathlib import Path
5
+
6
+
7
+ def log_admin_command(actor: str, command: str, args: str) -> None:
8
+ """Append a single admin-command entry to log/admin.log.
9
+
10
+ Format: ISO timestamp, actor, command, args (space-joined).
11
+ Creates the log directory if missing.
12
+ """
13
+ Path("log").mkdir(exist_ok=True)
14
+ line = f"{datetime.utcnow().isoformat()}Z\t{actor}\t{command}\t{args}\n"
15
+ (Path("log") / "admin.log").open("a", encoding="utf-8").write(line)
16
+
17
+
18
+ def rotate_admin_log(today: datetime | None = None) -> Path:
19
+ """Rotate admin.log to a date-stamped file once per (real) day.
20
+
21
+ - If ``log/admin.log`` exists, rename it to ``log/admin-YYYYMMDD.log``.
22
+ - Always return the new active path (``log/admin.log``).
23
+ The ``today`` parameter allows tests to inject a deterministic date.
24
+ """
25
+ log_dir = Path("log")
26
+ log_dir.mkdir(exist_ok=True)
27
+ active = log_dir / "admin.log"
28
+ if not active.exists():
29
+ return active
30
+ dt = today or datetime.utcnow()
31
+ dated = log_dir / f"admin-{dt.strftime('%Y%m%d')}.log"
32
+ # Avoid clobbering: if dated file exists, append current log and remove active
33
+ if dated.exists():
34
+ dated.open("a", encoding="utf-8").write(active.read_text(encoding="utf-8"))
35
+ active.unlink()
36
+ else:
37
+ active.rename(dated)
38
+ # Create a fresh active log file
39
+ active.touch()
40
+ return active
@@ -0,0 +1,9 @@
1
+ from pathlib import Path
2
+ from typing import Any, Dict
3
+
4
+
5
+ def log_agent_action(agent_id: str, observation: Dict[str, Any], action: str, result: str) -> None:
6
+ Path("log").mkdir(exist_ok=True)
7
+ log_path = Path("log") / f"agent_{agent_id}.log"
8
+ with log_path.open("a") as f:
9
+ f.write(f"\nOBS: {observation}\nACT: {action}\nRES: {result}\n{'='*40}\n")
mud/math/c_compat.py ADDED
@@ -0,0 +1,27 @@
1
+ """C-compatibility helpers (division/modulo/clamp).
2
+
3
+ Matches C integer division semantics (truncate toward zero), unlike Python's
4
+ "//" which floors toward negative infinity.
5
+ """
6
+ from __future__ import annotations
7
+
8
+
9
+ def c_div(a: int, b: int) -> int:
10
+ """C-style integer division (truncate toward zero)."""
11
+ if b == 0:
12
+ raise ZeroDivisionError("c_div by zero")
13
+ q = abs(a) // abs(b)
14
+ return q if (a >= 0) == (b >= 0) else -q
15
+
16
+
17
+ def c_mod(a: int, b: int) -> int:
18
+ """C-style modulo consistent with c_div: a == b * c_div(a,b) + c_mod(a,b)."""
19
+ if b == 0:
20
+ raise ZeroDivisionError("c_mod by zero")
21
+ return a - b * c_div(a, b)
22
+
23
+
24
+ def urange(low: int, val: int, high: int) -> int:
25
+ """Clamp to [low, high] inclusive, like ROM's URANGE macro."""
26
+ return max(low, min(val, high))
27
+
mud/mobprog.py ADDED
@@ -0,0 +1,72 @@
1
+ """Basic mob program trigger handling and interpreter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from enum import IntFlag
7
+ from typing import Iterable
8
+
9
+ from .models.mob import MobIndex
10
+
11
+
12
+ class Trigger(IntFlag):
13
+ """Bit flags describing mob program trigger types."""
14
+
15
+ ACT = 1 << 0
16
+ BRIBE = 1 << 1
17
+ DEATH = 1 << 2
18
+ ENTRY = 1 << 3
19
+ FIGHT = 1 << 4
20
+ GIVE = 1 << 5
21
+ GREET = 1 << 6
22
+ GRALL = 1 << 7
23
+ KILL = 1 << 8
24
+ HPCNT = 1 << 9
25
+ RANDOM = 1 << 10
26
+ SPEECH = 1 << 11
27
+ EXIT = 1 << 12
28
+ EXALL = 1 << 13
29
+ DELAY = 1 << 14
30
+ SURR = 1 << 15
31
+
32
+
33
+ @dataclass
34
+ class ExecutionResult:
35
+ """Represents a single action performed by the interpreter."""
36
+
37
+ command: str
38
+ argument: str
39
+
40
+
41
+ def run_prog(
42
+ mob: MobIndex, trig: Trigger, *, phrase: str | None = None
43
+ ) -> list[ExecutionResult]:
44
+ """Run mob programs matching *trig* and *phrase*.
45
+
46
+ This very small interpreter only understands ``say`` and ``emote``
47
+ commands and returns the actions performed for testing.
48
+ """
49
+
50
+ results: list[ExecutionResult] = []
51
+ for prog in mob.mprogs:
52
+ if prog.code is None:
53
+ continue
54
+ if not prog.trig_type & int(trig):
55
+ continue
56
+ if prog.trig_phrase and phrase is not None:
57
+ if prog.trig_phrase.lower() not in phrase.lower():
58
+ continue
59
+ results.extend(_execute(prog.code))
60
+ return results
61
+
62
+
63
+ def _execute(code: str) -> Iterable[ExecutionResult]:
64
+ for raw_line in code.strip().splitlines():
65
+ line = raw_line.strip()
66
+ if not line:
67
+ continue
68
+ if line.startswith("say "):
69
+ yield ExecutionResult("say", line[4:].strip())
70
+ elif line.startswith("emote "):
71
+ yield ExecutionResult("emote", line[6:].strip())
72
+ # other mob commands ignored for now
mud/models/__init__.py ADDED
@@ -0,0 +1,106 @@
1
+ """Data models for QuickMUD translated from C structs."""
2
+
3
+ from .area import Area
4
+ from .room import Room, ExtraDescr, Exit
5
+ from .room_json import ResetJson as Reset
6
+ from .mob import MobIndex, MobProgram
7
+ from .obj import ObjIndex, ObjectData, Affect
8
+ from .object import Object
9
+ from .character import Character, PCData
10
+ from .shop import Shop
11
+ from .skill import Skill
12
+ from .help import HelpEntry
13
+ from .social import Social
14
+ from .board import Board
15
+ from .note import Note
16
+ from .constants import (
17
+ Direction,
18
+ Sector,
19
+ Position,
20
+ WearLocation,
21
+ Sex,
22
+ Size,
23
+ ItemType,
24
+ )
25
+
26
+ from .area_json import AreaJson, VnumRangeJson
27
+ from .room_json import (
28
+ RoomJson,
29
+ ExitJson,
30
+ ExtraDescriptionJson as RoomExtraDescriptionJson,
31
+ ResetJson,
32
+ )
33
+ from .object_json import (
34
+ ObjectJson,
35
+ AffectJson as ObjectAffectJson,
36
+ ExtraDescriptionJson as ObjectExtraDescriptionJson,
37
+ )
38
+ from .character_json import CharacterJson, StatsJson, ResourceJson
39
+ from .player_json import PlayerJson
40
+ from .skill_json import SkillJson
41
+ from .shop_json import ShopJson
42
+ from .help_json import HelpJson
43
+ from .social_json import SocialJson
44
+ from .board_json import BoardJson
45
+ from .note_json import NoteJson
46
+ from .json_io import (
47
+ JsonDataclass,
48
+ dataclass_from_dict,
49
+ dataclass_to_dict,
50
+ dump_dataclass,
51
+ load_dataclass,
52
+ )
53
+
54
+ __all__ = [
55
+ "Area",
56
+ "Room",
57
+ "ExtraDescr",
58
+ "Exit",
59
+ "Reset",
60
+ "MobIndex",
61
+ "MobProgram",
62
+ "ObjIndex",
63
+ "ObjectData",
64
+ "Object",
65
+ "Affect",
66
+ "Character",
67
+ "PCData",
68
+ "Shop",
69
+ "Skill",
70
+ "HelpEntry",
71
+ "Social",
72
+ "Board",
73
+ "Note",
74
+ # JSON schema-aligned dataclasses
75
+ "AreaJson",
76
+ "VnumRangeJson",
77
+ "RoomJson",
78
+ "ExitJson",
79
+ "RoomExtraDescriptionJson",
80
+ "ResetJson",
81
+ "ObjectJson",
82
+ "ObjectAffectJson",
83
+ "ObjectExtraDescriptionJson",
84
+ "CharacterJson",
85
+ "StatsJson",
86
+ "ResourceJson",
87
+ "PlayerJson",
88
+ "SkillJson",
89
+ "ShopJson",
90
+ "HelpJson",
91
+ "SocialJson",
92
+ "BoardJson",
93
+ "NoteJson",
94
+ "JsonDataclass",
95
+ "dataclass_from_dict",
96
+ "dataclass_to_dict",
97
+ "load_dataclass",
98
+ "dump_dataclass",
99
+ "Direction",
100
+ "Sector",
101
+ "Position",
102
+ "WearLocation",
103
+ "Sex",
104
+ "Size",
105
+ "ItemType",
106
+ ]
mud/models/area.py ADDED
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+ from dataclasses import dataclass, field
3
+ from typing import List, Optional
4
+
5
+ from .room_json import ResetJson
6
+
7
+
8
+ @dataclass
9
+ class Area:
10
+ """Runtime area container loaded from legacy files."""
11
+ file_name: Optional[str] = None
12
+ name: Optional[str] = None
13
+ credits: Optional[str] = None
14
+ age: int = 0
15
+ nplayer: int = 0
16
+ low_range: int = 0
17
+ high_range: int = 0
18
+ min_vnum: int = 0
19
+ max_vnum: int = 0
20
+ empty: bool = False
21
+ builders: Optional[str] = None
22
+ vnum: int = 0
23
+ area_flags: int = 0
24
+ security: int = 0
25
+ helps: List[object] = field(default_factory=list)
26
+ resets: List[ResetJson] = field(default_factory=list)
27
+ next: Optional['Area'] = None
28
+
29
+ def __repr__(self) -> str:
30
+ return f"<Area vnum={self.vnum} name={self.name!r}>"
31
+
32
+
33
+ area_registry: dict[int, Area] = {}
@@ -0,0 +1,27 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import List
5
+
6
+ from .room_json import RoomJson
7
+ from .character_json import CharacterJson
8
+ from .object_json import ObjectJson
9
+ from .json_io import JsonDataclass
10
+
11
+
12
+ @dataclass
13
+ class VnumRangeJson(JsonDataclass):
14
+ """Minimum and maximum vnums for an area."""
15
+ min: int
16
+ max: int
17
+
18
+
19
+ @dataclass
20
+ class AreaJson(JsonDataclass):
21
+ """Area record matching ``schemas/area.schema.json``."""
22
+ name: str
23
+ vnum_range: VnumRangeJson
24
+ builders: List[str] = field(default_factory=list)
25
+ rooms: List[RoomJson] = field(default_factory=list)
26
+ mobiles: List[CharacterJson] = field(default_factory=list)
27
+ objects: List[ObjectJson] = field(default_factory=list)
mud/models/board.py ADDED
@@ -0,0 +1,49 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import List
5
+ import time
6
+
7
+ from .board_json import BoardJson
8
+ from .note import Note
9
+
10
+
11
+ @dataclass
12
+ class Board:
13
+ """Runtime representation of a message board."""
14
+
15
+ name: str
16
+ description: str
17
+ notes: List[Note] = field(default_factory=list)
18
+
19
+ def post(
20
+ self,
21
+ sender: str,
22
+ subject: str,
23
+ text: str,
24
+ to: str = "all",
25
+ ) -> Note:
26
+ note = Note(
27
+ sender=sender,
28
+ to=to,
29
+ subject=subject,
30
+ text=text,
31
+ timestamp=time.time(),
32
+ )
33
+ self.notes.append(note)
34
+ return note
35
+
36
+ def to_json(self) -> BoardJson:
37
+ return BoardJson(
38
+ name=self.name,
39
+ description=self.description,
40
+ notes=[n.to_json() for n in self.notes],
41
+ )
42
+
43
+ @classmethod
44
+ def from_json(cls, data: BoardJson) -> "Board":
45
+ return cls(
46
+ name=data.name,
47
+ description=data.description,
48
+ notes=[Note.from_json(n) for n in data.notes],
49
+ )
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import List
5
+
6
+ from .json_io import JsonDataclass
7
+ from .note_json import NoteJson
8
+
9
+
10
+ @dataclass
11
+ class BoardJson(JsonDataclass):
12
+ """Schema-aligned representation of a message board."""
13
+
14
+ name: str
15
+ description: str
16
+ notes: List[NoteJson] = field(default_factory=list)