Files
dockipelago/worlds/evn/regions.py
Jonathan Tinney 7971961166
Some checks failed
Analyze modified files / flake8 (push) Failing after 2m28s
Build / build-win (push) Has been cancelled
Build / build-ubuntu2204 (push) Has been cancelled
ctest / Test C++ ubuntu-latest (push) Has been cancelled
ctest / Test C++ windows-latest (push) Has been cancelled
Analyze modified files / mypy (push) Has been cancelled
Build and Publish Docker Images / Push Docker image to Docker Hub (push) Successful in 5m4s
Native Code Static Analysis / scan-build (push) Failing after 5m2s
type check / pyright (push) Successful in 1m7s
unittests / Test Python 3.11.2 ubuntu-latest (push) Failing after 16m23s
unittests / Test Python 3.12 ubuntu-latest (push) Failing after 28m19s
unittests / Test Python 3.13 ubuntu-latest (push) Failing after 14m49s
unittests / Test hosting with 3.13 on ubuntu-latest (push) Successful in 5m0s
unittests / Test Python 3.13 macos-latest (push) Has been cancelled
unittests / Test Python 3.11 windows-latest (push) Has been cancelled
unittests / Test Python 3.13 windows-latest (push) Has been cancelled
add schedule I, sonic 1/frontiers/heroes, spirit island
2026-04-02 23:46:36 -07:00

159 lines
7.3 KiB
Python

from __future__ import annotations
from typing import TYPE_CHECKING
from BaseClasses import Entrance, Region
from venv import logger
#from .options import ChosenString
from .logics import story_routes, possible_regions
import re
if TYPE_CHECKING:
from .world import EVNWorld
# A region is a container for locations ("checks"), which connects to other regions via "Entrance" objects.
# Many games will model their Regions after physical in-game places, but you can also have more abstract regions.
# For a location to be in logic, its containing region must be reachable.
# The Entrances connecting regions can have rules - more on that in rules.py.
# This makes regions especially useful for traversal logic ("Can the player reach this part of the map?")
# Every location must be inside a region, and you must have at least one region.
# This is why we create regions first, and then later we create the locations (in locations.py).
# Should probably create a subclass that inherits from Region for EVN-specific behavior, similar to how we did for Location.
# TODO: Add the other regions
# REGION_KEYS = {
# "Universe" : [],
# "Fed" : ["Fed"], # Fed story mission string
# "Vellos" : ["Vellos", "Vell-os"],
# "Polaris" : ["Polaris"],
# "Auroran" : ["Auroran"],
# "Rebel" : ["Rebel"],
# "Pirate" : ["Pirate"],
# }
# ### Region subclass helper functions ###
# # Let's make one more helper method before we begin actually creating locations.
# # Here's a function that extracts the value following a specific character in a given text.
# # Google AI
# def get_value_after_char(text, char):
# # Pattern explanation:
# # (?<={char}\s*) - Positive lookbehind: asserts the position is immediately
# # after the specified character and zero or more whitespaces.
# # (.*?) - Capturing group 1: lazily matches any character (except newline)
# # until the end of the line.
# #pattern = re.compile(rf'(?<={re.escape(char)}\s*)(.*?)')
# pattern = re.compile(rf'^(.*?)(?=\s+{re.escape(char)})(.*)$')
# match = pattern.search(text)
# if match:
# # returns the captured group, with leading/trailing whitespace removed
# #return match.group(1).strip()
# return match.group(3).strip()
# return None
# def string_has_value(text, substring):
# return get_value_after_char(text.lower(), substring.lower()) is not None
# def string_has_value_after_char(text, char, substring):
# value = get_value_after_char(text, char)
# if value is None:
# return False
# return substring.lower() in value.lower()
# def can_accept_location(evnregion: Region, location_name: str) -> bool:
# """Helper function to determine if a region can accept a location based on keywords."""
# keywords = REGION_KEYS.get(evnregion.name, [])
# for keyword in keywords:
# #if keyword in location_name:
# if string_has_value_after_char(location_name, ";", keyword):
# return True
# return False
### End Region subclass helper functions ###
def create_and_connect_regions(world: EVNWorld) -> None:
create_all_regions(world)
connect_regions(world)
def create_all_regions(world: EVNWorld) -> None:
# Creating a region is as simple as calling the constructor of the Region class.
#universe = Region("Universe", world.player, world.multiworld)
#fed_string = Region("Fed String", world.player, world.multiworld) # Fed story mission string
# Let's put all these regions in a list.
#regions = [universe, fed_string]
regions = []
# for evregion in REGION_KEYS.keys():
# regions.append(Region(evregion, world.player, world.multiworld))
# TODO: replace this repeating line with a world function, then adjust associated imports
#regions.append(Region("Universe", world.player, world.multiworld)) # we readded to possible_regions bank
chosen_route = world.get_chosen_string()
for regionid in chosen_route["regions"]:
regions.append(Region(possible_regions[regionid]["name"], world.player, world.multiworld))
#logger.info(f"added region {possible_regions[regionid]["name"]}")
# Some regions may only exist if the player enables certain options.
# In our case, the Hammer locks the top middle chest in its own room if the hammer option is enabled.
# if world.options.hammer:
# top_middle_room = Region("Top Middle Room", world.player, world.multiworld)
# regions.append(top_middle_room)
# We now need to add these regions to multiworld.regions so that AP knows about their existence.
world.multiworld.regions += regions
def connect_regions(world: EVNWorld) -> None:
# We have regions now, but still need to connect them to each other.
# But wait, we no longer have access to the region variables we created in create_all_regions()!
# Luckily, once you've submitted your regions to multiworld.regions,
# you can get them at any time using world.get_region(...).
#universe = world.get_region("Universe")
# fed_string = world.get_region("Fed String")
# # universe.connect(fed_string, "Universe to Fed String")
# for evregion in REGION_KEYS.keys():
# if evregion != "Universe":
# universe.connect(world.get_region(evregion), f"Universe to {evregion}")
chosen_route = world.get_chosen_string()
#logger.info(f"story routes: {chosen_route["region_connections"]}")
for fromid, targets in chosen_route["region_connections"].items():
from_r_name = possible_regions[fromid]["name"]
from_region = world.get_region(from_r_name)
for toid in targets:
to_r_name = possible_regions[toid]["name"]
from_region.connect(world.get_region(possible_regions[toid]["name"]), f"{from_r_name} to {to_r_name}")
#logger.info(f"connected {possible_regions[fromid]["name"]} to {possible_regions[toid]["name"]}")
# Okay, now we can get connecting. For this, we need to create Entrances.
# Entrances are inherently one-way, but crucially, AP assumes you can always return to the origin region.
# One way to create an Entrance is by calling the Entrance constructor.
# overworld_to_bottom_right_room = Entrance(world.player, "Overworld to Bottom Right Room", parent=overworld)
# overworld.exits.append(overworld_to_bottom_right_room)
# # You can then connect the Entrance to the target region.
# overworld_to_bottom_right_room.connect(bottom_right_room)
# # An even easier way is to use the region.connect helper.
# overworld.connect(right_room, "Overworld to Right Room")
# right_room.connect(final_boss_room, "Right Room to Final Boss Room")
# # The region.connect helper even allows adding a rule immediately.
# # We'll talk more about rule creation in the set_all_rules() function in rules.py.
# overworld.connect(top_left_room, "Overworld to Top Left Room", lambda state: state.has("Key", world.player))
# # Some Entrances may only exist if the player enables certain options.
# # In our case, the Hammer locks the top middle chest in its own room if the hammer option is enabled.
# # In this case, we previously created an extra "Top Middle Room" region that we now need to connect to Overworld.
# if world.options.hammer:
# top_middle_room = world.get_region("Top Middle Room")
# overworld.connect(top_middle_room, "Overworld to Top Middle Room")