Zones instead of road timers: journeys, flights, meeting stone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
rope 2026-10-24 08:24:50 +01:00
parent 7150b2f89c
commit 0e396ee5d4
7 changed files with 363 additions and 230 deletions

View file

@ -35,16 +35,28 @@ class Content(unittest.TestCase):
for ab in spec["abilities"]:
self.assertIn(ab, content.ABILITIES)
def test_places_link_both_ways_and_hunts_exist(self):
def test_places_sit_in_zones_and_hunts_exist(self):
hunts = set()
for pid, place in content.PLACES.items():
for other in place["links"]:
self.assertIn(pid, content.PLACES[other]["links"], f"{pid} -> {other}")
self.assertIn(place["zone"], content.ZONES)
for hunt, pulls in place.get("hunts", {}).items():
self.assertNotIn((place["zone"], hunt), hunts, "hunts must be unique within a zone")
hunts.add((place["zone"], hunt))
self.assertIn(hunt, content.MOBS)
for pull in pulls:
for mob in pull:
self.assertIn(mob, content.MOBS)
def test_roads_join_zones_through_real_mobs(self):
for road in content.ROADS.values():
for zone in road["zones"]:
self.assertIn(zone, content.ZONES)
self.assertIn(road["rare"], content.MOBS)
for stop in road["stops"]:
for pull in stop["pulls"]:
for mob in pull:
self.assertIn(mob, content.MOBS)
def test_quests_point_at_real_things(self):
for qid, quest in content.QUESTS.items():
self.assertIn(quest["giver"], content.PLACES)
@ -181,8 +193,8 @@ class World(unittest.TestCase):
world = world_with(("fred", "warrior"))
fred = world["players"]["fred"]
self.assertIsNone(game.accept(fred, "kobold_camp_cleanup"))
fred["place"] = "northshire_valley"
self.assertIsNone(game.start_hunt(world, fred, "kobold_vermin", 1000))
self.assertEqual(fred["place"], "northshire_valley")
game.process(world, 1000 + 20 * 60, random.Random(1))
self.assertGreater(fred["level"], 1)
self.assertEqual(fred["quests"]["kobold_camp_cleanup"], [10])
@ -190,7 +202,6 @@ class World(unittest.TestCase):
def test_hunting_stops_after_the_offline_cap(self):
world = world_with(("fred", "warrior"))
fred = world["players"]["fred"]
fred["place"] = "northshire_valley"
game.start_hunt(world, fred, "kobold_vermin", 1000)
game.process(world, 1000 + game.OFFLINE_CAP + 3600, random.Random(1))
self.assertIsNone(fred["activity"])
@ -201,33 +212,73 @@ class World(unittest.TestCase):
fred = world["players"]["fred"]
game.accept(fred, "kobold_camp_cleanup")
fred["quests"]["kobold_camp_cleanup"] = [10]
fred["place"] = "goldshire"
fred["place"] = "sentinel_hill"
self.assertIn("Turn it in", game.turn_in(world, fred, "kobold_camp_cleanup", 2, 0))
fred["place"] = "northshire_abbey"
fred["place"] = "fargodeep" # anywhere in Elwynn will do
self.assertEqual(game.turn_in(world, fred, "kobold_camp_cleanup", None, 0), "Pick a reward")
self.assertIsNone(game.turn_in(world, fred, "kobold_camp_cleanup", 2, 0))
self.assertIn("w/feet/mail/4", fred["bags"])
self.assertIn("investigate_echo_ridge", game.available_quests(fred))
def test_travel_takes_the_shortest_road(self):
minutes, path = game.route("northshire_abbey", "moonbrook")
self.assertEqual(path, ["northshire_abbey", "goldshire", "sentinel_hill", "moonbrook"])
self.assertEqual(minutes, 14)
def test_a_journey_is_a_fight_at_every_stop(self):
world = world_with(("fred", "warrior"))
fred = world["players"]["fred"]
self.assertIsNone(game.travel(world, fred, "goldshire", 1000))
game.process(world, 1000 + 4 * 60 - 1, random.Random(1))
self.assertEqual(fred["place"], "northshire_abbey")
game.process(world, 1000 + 4 * 60, random.Random(1))
self.assertEqual(fred["place"], "goldshire")
game.dev_level(world, fred, 14, 1000)
self.assertIsNone(game.accept(fred, "riverpaw_gnoll_bounty"))
fred["done"].append("riverpaw_gnoll_bounty")
fred["quests"].pop("riverpaw_gnoll_bounty")
self.assertIsNone(game.accept(fred, "report_to_gryan"))
self.assertIsNone(game.journey(world, fred, "westfall", 1000))
self.assertIn("road", game.start_hunt(world, fred, "kobold_vermin", 1000))
fights = 0
for step in range(2000):
game.process(world, 1000 + step, random.Random(step))
if fred["fight"]:
fights += step == 0 or world["fights"][fred["fight"]]["t"] <= 1
if not fred["activity"]:
break
self.assertEqual(fred["place"], "sentinel_hill")
self.assertEqual(fred["zones"], ["elwynn", "westfall"])
self.assertEqual(fred["quests"]["report_to_gryan"], [1])
self.assertGreaterEqual(fights, 3)
def test_dying_on_the_road_sends_you_back(self):
world = world_with(("fred", "mage"))
fred = world["players"]["fred"]
self.assertIsNone(game.journey(world, fred, "westfall", 1000))
game.process(world, 1000 + 600, random.Random(1))
self.assertIsNone(fred["activity"])
self.assertEqual(game.zone_of(fred["place"]), "elwynn")
self.assertEqual(fred["zones"], ["elwynn"])
def test_flying_needs_the_zone_reached_and_the_fare(self):
world = world_with(("fred", "warrior"))
fred = world["players"]["fred"]
self.assertIn("on foot", game.fly(world, fred, "westfall", 0))
fred["zones"].append("westfall")
self.assertIn("money", game.fly(world, fred, "westfall", 0))
fred["copper"] = 100
self.assertIsNone(game.fly(world, fred, "westfall", 0))
self.assertEqual(fred["place"], "sentinel_hill")
self.assertEqual(fred["copper"], 100 - content.FLIGHT_COST)
def test_old_worlds_migrate(self):
world = world_with(("fred", "warrior"))
world["version"] = 1
fred = world["players"]["fred"]
del fred["zones"]
fred["activity"] = {"kind": "travel", "to": "sentinel_hill", "end": 0}
fred["quests"]["report_to_goldshire"] = [0]
game.migrate(world)
self.assertEqual((fred["place"], fred["activity"], fred["zones"]), ("sentinel_hill", None, ["westfall"]))
self.assertEqual(fred["quests"]["report_to_goldshire"], [])
def test_a_group_clears_the_deadmines(self):
world = world_with(*[(uid, cls) for uid, cls in
(("t", "warrior"), ("h", "priest"), ("m", "mage"), ("r", "rogue"), ("x", "hunter"))])
for uid, player in world["players"].items():
game.dev_level(world, player, 20, 1000)
player["place"] = "moonbrook"
world["players"]["t"]["place"] = "sentinel_hill"
leader = world["players"]["t"]
game.create_group(world, leader)
gid = leader["group"]
@ -240,17 +291,20 @@ class World(unittest.TestCase):
loot = sum(len(p["bags"]) for p in world["players"].values())
self.assertGreater(loot, 0)
def test_dungeon_needs_everyone_at_the_door(self):
def test_the_meeting_stone_summons_the_group(self):
world = world_with(("a", "warrior"), ("b", "priest"), dev=False)
a, b = world["players"]["a"], world["players"]["b"]
a["place"] = "moonbrook"
game.create_group(world, a)
self.assertIn("no dungeon", game.enter_dungeon(world, a, 0))
a["place"] = "sentinel_hill"
self.assertIn("at least 2", game.enter_dungeon(world, a, 0))
game.join_group(world, b, a["group"])
game.journey(world, b, "westfall", 0)
self.assertIn("Waiting on B", game.enter_dungeon(world, a, 0))
b["place"] = "moonbrook"
game.stop(world, b, 0)
self.assertIsNone(game.enter_dungeon(world, a, 0))
self.assertEqual(b["activity"]["kind"], "dungeon")
self.assertEqual((b["activity"]["kind"], b["place"]), ("dungeon", "moonbrook"))
self.assertIn("westfall", b["zones"])
game.leave_group(world, b, 10)
self.assertIsNone(b["activity"])
self.assertEqual(b["place"], "moonbrook")