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