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
|
import tomllib
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from mudlib.portal import Portal
|
||||||
from mudlib.zone import Zone
|
from mudlib.zone import Zone
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
@ -77,6 +78,36 @@ def load_zone(path: Path) -> Zone:
|
||||||
spawn_y=spawn_y,
|
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
|
return zone
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -211,3 +211,143 @@ rows = [
|
||||||
assert zone.spawn_y == 0
|
assert zone.spawn_y == 0
|
||||||
finally:
|
finally:
|
||||||
temp_path.unlink()
|
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