Add portal loading from zone TOML files
Zone TOML files can now define portals using [[portals]] sections. Each portal specifies coordinates (x, y), a target (zone_name:x,y), and a label. Optional aliases are supported. Portals are automatically created and placed in the zone when it loads.
This commit is contained in:
parent
cb3ad6a547
commit
b3801f780f
2 changed files with 171 additions and 0 deletions
|
|
@ -6,6 +6,7 @@ import logging
|
|||
import tomllib
|
||||
from pathlib import Path
|
||||
|
||||
from mudlib.portal import Portal
|
||||
from mudlib.zone import Zone
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
|
@ -77,6 +78,36 @@ def load_zone(path: Path) -> Zone:
|
|||
spawn_y=spawn_y,
|
||||
)
|
||||
|
||||
# Load portals
|
||||
portals_data = data.get("portals", [])
|
||||
for portal_dict in portals_data:
|
||||
# Parse target string "zone_name:x,y"
|
||||
target = portal_dict["target"]
|
||||
try:
|
||||
target_zone, coords = target.split(":")
|
||||
target_x, target_y = map(int, coords.split(","))
|
||||
except ValueError:
|
||||
log.warning(
|
||||
"skipping portal '%s' at (%d, %d): malformed target '%s'",
|
||||
portal_dict["label"],
|
||||
portal_dict["x"],
|
||||
portal_dict["y"],
|
||||
target,
|
||||
)
|
||||
continue
|
||||
|
||||
# Create portal (automatically added to zone._contents via Object.__post_init__)
|
||||
Portal(
|
||||
name=portal_dict["label"],
|
||||
aliases=portal_dict.get("aliases", []),
|
||||
target_zone=target_zone,
|
||||
target_x=target_x,
|
||||
target_y=target_y,
|
||||
location=zone,
|
||||
x=portal_dict["x"],
|
||||
y=portal_dict["y"],
|
||||
)
|
||||
|
||||
return zone
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -211,3 +211,143 @@ rows = [
|
|||
assert zone.spawn_y == 0
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
|
||||
|
||||
def test_load_zone_with_portals():
|
||||
"""Load a zone with portals defined."""
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
|
||||
f.write("""
|
||||
name = "portal_zone"
|
||||
description = "a zone with portals"
|
||||
width = 10
|
||||
height = 10
|
||||
|
||||
[terrain]
|
||||
rows = [
|
||||
"..........",
|
||||
"..........",
|
||||
"..........",
|
||||
"..........",
|
||||
"..........",
|
||||
"..........",
|
||||
"..........",
|
||||
"..........",
|
||||
"..........",
|
||||
"..........",
|
||||
]
|
||||
|
||||
[[portals]]
|
||||
x = 5
|
||||
y = 3
|
||||
target = "tavern:1,1"
|
||||
label = "tavern door"
|
||||
|
||||
[[portals]]
|
||||
x = 2
|
||||
y = 7
|
||||
target = "forest:10,5"
|
||||
label = "forest path"
|
||||
""")
|
||||
temp_path = pathlib.Path(f.name)
|
||||
|
||||
try:
|
||||
zone = load_zone(temp_path)
|
||||
|
||||
# Find portals in zone contents
|
||||
portals = [obj for obj in zone._contents if obj.__class__.__name__ == "Portal"]
|
||||
assert len(portals) == 2
|
||||
|
||||
# Check first portal (tavern door at 5,3)
|
||||
tavern_portal = [p for p in portals if p.name == "tavern door"][0]
|
||||
assert tavern_portal.x == 5
|
||||
assert tavern_portal.y == 3
|
||||
assert tavern_portal.target_zone == "tavern"
|
||||
assert tavern_portal.target_x == 1
|
||||
assert tavern_portal.target_y == 1
|
||||
assert tavern_portal.location == zone
|
||||
|
||||
# Check second portal (forest path at 2,7)
|
||||
forest_portal = [p for p in portals if p.name == "forest path"][0]
|
||||
assert forest_portal.x == 2
|
||||
assert forest_portal.y == 7
|
||||
assert forest_portal.target_zone == "forest"
|
||||
assert forest_portal.target_x == 10
|
||||
assert forest_portal.target_y == 5
|
||||
assert forest_portal.location == zone
|
||||
|
||||
# Verify portals are at correct coordinates
|
||||
contents_at_5_3 = zone.contents_at(5, 3)
|
||||
assert tavern_portal in contents_at_5_3
|
||||
|
||||
contents_at_2_7 = zone.contents_at(2, 7)
|
||||
assert forest_portal in contents_at_2_7
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
|
||||
|
||||
def test_load_zone_without_portals():
|
||||
"""Load a zone without portals section works fine."""
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
|
||||
f.write("""
|
||||
name = "no_portals"
|
||||
description = "a zone without portals"
|
||||
width = 3
|
||||
height = 3
|
||||
|
||||
[terrain]
|
||||
rows = [
|
||||
"...",
|
||||
"...",
|
||||
"...",
|
||||
]
|
||||
""")
|
||||
temp_path = pathlib.Path(f.name)
|
||||
|
||||
try:
|
||||
zone = load_zone(temp_path)
|
||||
|
||||
# Should have no portals
|
||||
portals = [obj for obj in zone._contents if obj.__class__.__name__ == "Portal"]
|
||||
assert len(portals) == 0
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
|
||||
|
||||
def test_load_zone_portal_with_aliases():
|
||||
"""Portal can have optional aliases field in TOML."""
|
||||
with tempfile.NamedTemporaryFile(mode="w", suffix=".toml", delete=False) as f:
|
||||
f.write("""
|
||||
name = "alias_zone"
|
||||
description = "a zone with aliased portal"
|
||||
width = 5
|
||||
height = 5
|
||||
|
||||
[terrain]
|
||||
rows = [
|
||||
".....",
|
||||
".....",
|
||||
".....",
|
||||
".....",
|
||||
".....",
|
||||
]
|
||||
|
||||
[[portals]]
|
||||
x = 2
|
||||
y = 2
|
||||
target = "elsewhere:0,0"
|
||||
label = "mysterious gateway"
|
||||
aliases = ["gateway", "gate", "portal"]
|
||||
""")
|
||||
temp_path = pathlib.Path(f.name)
|
||||
|
||||
try:
|
||||
zone = load_zone(temp_path)
|
||||
|
||||
portals = [obj for obj in zone._contents if obj.__class__.__name__ == "Portal"]
|
||||
assert len(portals) == 1
|
||||
|
||||
portal = portals[0]
|
||||
assert portal.name == "mysterious gateway"
|
||||
assert portal.aliases == ["gateway", "gate", "portal"]
|
||||
finally:
|
||||
temp_path.unlink()
|
||||
|
|
|
|||
Loading…
Reference in a new issue