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,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)
|
mud/logging/__init__.py
ADDED
|
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] = {}
|
mud/models/area_json.py
ADDED
|
@@ -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
|
+
)
|
mud/models/board_json.py
ADDED
|
@@ -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)
|