95 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			95 lines
		
	
	
		
			3.1 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
from __future__ import annotations
 | 
						|
 | 
						|
from collections.abc import Container
 | 
						|
from dataclasses import dataclass, field
 | 
						|
from enum import IntFlag
 | 
						|
 | 
						|
connector_keyword = " to "
 | 
						|
 | 
						|
 | 
						|
def reverse_connection_name(name: str) -> str | None:
 | 
						|
    try:
 | 
						|
        origin, destination = name.split(connector_keyword)
 | 
						|
    except ValueError:
 | 
						|
        return None
 | 
						|
    return f"{destination}{connector_keyword}{origin}"
 | 
						|
 | 
						|
 | 
						|
class MergeFlag(IntFlag):
 | 
						|
    ADD_EXITS = 0
 | 
						|
    REMOVE_EXITS = 1
 | 
						|
 | 
						|
 | 
						|
class RandomizationFlag(IntFlag):
 | 
						|
    NOT_RANDOMIZED = 0
 | 
						|
 | 
						|
    # Randomization options
 | 
						|
    # The first 4 bits are used to mark if an entrance is eligible for randomization according to the entrance randomization options.
 | 
						|
    BIT_PELICAN_TOWN = 1  # 0b0001
 | 
						|
    BIT_NON_PROGRESSION = 1 << 1  # 0b0010
 | 
						|
    BIT_BUILDINGS = 1 << 2  # 0b0100
 | 
						|
    BIT_EVERYTHING = 1 << 3  # 0b1000
 | 
						|
 | 
						|
    # Content flag for entrances exclusions
 | 
						|
    # The next 2 bits are used to mark if an entrance is to be excluded from randomization according to the content options.
 | 
						|
    # Those bits must be removed from an entrance flags when then entrance must be excluded.
 | 
						|
    __UNUSED = 1 << 4  # 0b010000
 | 
						|
    EXCLUDE_MASTERIES = 1 << 5  # 0b100000
 | 
						|
 | 
						|
    # Entrance groups
 | 
						|
    # The last bit is used to add additional qualifiers on entrances to group them
 | 
						|
    # Those bits should be added when an entrance need additional qualifiers.
 | 
						|
    LEAD_TO_OPEN_AREA = 1 << 6
 | 
						|
 | 
						|
    # Tags to apply on connections
 | 
						|
    EVERYTHING = EXCLUDE_MASTERIES | BIT_EVERYTHING
 | 
						|
    BUILDINGS = EVERYTHING | BIT_BUILDINGS
 | 
						|
    NON_PROGRESSION = BUILDINGS | BIT_NON_PROGRESSION
 | 
						|
    PELICAN_TOWN = NON_PROGRESSION | BIT_PELICAN_TOWN
 | 
						|
 | 
						|
 | 
						|
@dataclass(frozen=True)
 | 
						|
class RegionData:
 | 
						|
    name: str
 | 
						|
    exits: tuple[str, ...] = field(default_factory=tuple)
 | 
						|
    flag: MergeFlag = MergeFlag.ADD_EXITS
 | 
						|
 | 
						|
    def __post_init__(self):
 | 
						|
        assert not isinstance(self.exits, str), "Exits must be a tuple of strings, you probably forgot a trailing comma."
 | 
						|
 | 
						|
    def merge_with(self, other: RegionData) -> RegionData:
 | 
						|
        assert self.name == other.name, "Regions must have the same name to be merged"
 | 
						|
 | 
						|
        if other.flag == MergeFlag.REMOVE_EXITS:
 | 
						|
            return self.get_without_exits(other.exits)
 | 
						|
 | 
						|
        merged_exits = self.exits + other.exits
 | 
						|
        assert len(merged_exits) == len(set(merged_exits)), "Two regions getting merged have duplicated exists..."
 | 
						|
 | 
						|
        return RegionData(self.name, merged_exits)
 | 
						|
 | 
						|
    def get_without_exits(self, exits_to_remove: Container[str]) -> RegionData:
 | 
						|
        exits = tuple(exit_ for exit_ in self.exits if exit_ not in exits_to_remove)
 | 
						|
        return RegionData(self.name, exits)
 | 
						|
 | 
						|
 | 
						|
@dataclass(frozen=True)
 | 
						|
class ConnectionData:
 | 
						|
    name: str
 | 
						|
    destination: str
 | 
						|
    flag: RandomizationFlag = RandomizationFlag.NOT_RANDOMIZED
 | 
						|
 | 
						|
    @property
 | 
						|
    def reverse(self) -> str | None:
 | 
						|
        return reverse_connection_name(self.name)
 | 
						|
 | 
						|
    def is_eligible_for_randomization(self, chosen_randomization_flag: RandomizationFlag) -> bool:
 | 
						|
        return chosen_randomization_flag and chosen_randomization_flag in self.flag
 | 
						|
 | 
						|
 | 
						|
@dataclass(frozen=True)
 | 
						|
class ModRegionsData:
 | 
						|
    mod_name: str
 | 
						|
    regions: list[RegionData]
 | 
						|
    connections: list[ConnectionData]
 |