mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 12:11:33 -06:00
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]
|