Files
dockipelago/worlds/dk64/randomizer/LogicClasses.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

306 lines
11 KiB
Python

"""Contains classes used in the logic system."""
from __future__ import annotations
from typing import TYPE_CHECKING, Any, Callable, List, Optional, Tuple, Union
from randomizer.Enums.Kongs import Kongs
from randomizer.Enums.Levels import Levels
from randomizer.Enums.Regions import Regions
from randomizer.Enums.Time import Time
from randomizer.Enums.Locations import Locations
from randomizer.Enums.HintRegion import HintRegion, HINT_REGION_PAIRING, MEDAL_REWARD_REGIONS, SHOP_REGIONS
from randomizer.Lists.EnemyTypes import enemy_location_list
if TYPE_CHECKING:
from randomizer.Enums.Collectibles import Collectibles
from randomizer.Enums.Events import Events
from randomizer.Enums.Locations import Locations
from randomizer.Enums.MinigameType import MinigameType
from randomizer.Enums.Transitions import Transitions
from randomizer.Logic import LogicVarHolder
class LocationLogic:
"""Logic for a location."""
def __init__(
self,
id: Union[int, Locations],
logic: Callable,
bonusBarrel: Optional[MinigameType] = None,
isAuxiliary: bool = False,
) -> None:
"""Initialize with given parameters."""
self.id = id
self.logic = logic # Lambda function for accessibility
if id >= Locations.JapesMainEnemy_Start and id <= Locations.IslesMainEnemy_LowerFactoryPath1:
# Handle enemy logic
self.logic = lambda l: logic(l) and enemy_location_list[id].canDropItem(l)
self.bonusBarrel = bonusBarrel # Uses MinigameType enum
self.isAuxiliaryLocation = (
isAuxiliary # For when the Location needs to be in a region but not count as in the region (used for locations that need to be accessible in different regions depending on settings)
)
class Event:
"""Event within a region.
Events act as statically placed items
For example, if Lanky must press a button in region x to open something in region y,
that can be represented as a button press event in region x which is checked for in region y.
"""
def __init__(self, name: Events, logic: Callable) -> None:
"""Initialize with given parameters."""
self.name = name
self.logic = logic # Lambda function for accessibility
class Collectible:
"""Class used for colored bananas and banana coins."""
def __init__(
self,
type: Collectibles,
kong: Kongs,
logic: Callable,
coords: Optional[Tuple[float, float, float]] = None,
amount: int = 1,
enabled: bool = True,
vanilla: bool = True,
name: str = "vanilla",
locked: bool = False,
) -> None:
"""Initialize with given parameters."""
self.type = type
self.kong = kong
self.logic = logic
self.amount = amount
self.coords = coords # None for vanilla collectibles for now. For custom, use (x,y,z) format
self.added = False
self.enabled = enabled
self.vanilla = vanilla
self.name = name
self.locked = locked
class Region:
"""Region contains shufflable locations, events, and transitions to other regions."""
def __init__(
self,
name: str,
hint_name: HintRegion,
level: Levels,
tagbarrel: bool,
deathwarp: Optional[Union[int, TransitionFront, Regions]],
locations: List[Union[LocationLogic, Any]],
events: List[Union[Event, Any]],
transitionFronts: List[Union[TransitionFront, Any]],
restart: Optional[Union[Transitions, int]] = None,
) -> None:
"""Initialize with given parameters."""
self.name = name
self.hint_name = hint_name
self.level = level
self.tagbarrel = tagbarrel
self.deathwarp = None
self.locations = locations
self.events = events
self.exits = transitionFronts # In the context of a region, exits are how you leave the region
self.restart = restart
self.dayAccess = [False] * 5
self.nightAccess = [False] * 5
# If possible to die in this region, add an exit to where dying will take you
# deathwarp is also set to none in regions in which a deathwarp would take you to itself
# Or if there is loading-zone-less free access to the region it would take you to already
if deathwarp is not None:
# If deathwarp is itself an exit class (necessary when deathwarp requires custom logic) just add it directly
if isinstance(deathwarp, TransitionFront):
self.deathwarp = deathwarp
else:
# If deathwarp is -1, indicates to use the default value for it, which is the starting area of the level
if deathwarp == -1:
deathwarp = self.GetDefaultDeathwarp()
if deathwarp is not None:
if isinstance(deathwarp, Regions):
self.deathwarp = TransitionFront(deathwarp, lambda l: True)
else:
self.deathwarp = TransitionFront(Regions(deathwarp), lambda l: True)
self.ResetAccess()
def ResetAccess(self) -> None:
"""Clear access variables set during search."""
# Time access
self.dayAccess = [False] * 5
self.nightAccess = [False] * 5
def GetDefaultDeathwarp(self) -> Regions:
"""Get the default deathwarp depending on the region's level."""
if self.level == Levels.DKIsles:
return Regions.IslesMain
elif self.level == Levels.JungleJapes:
return Regions.JungleJapesEntryHandler
elif self.level == Levels.AngryAztec:
return Regions.AngryAztecEntryHandler
elif self.level == Levels.FranticFactory:
return Regions.FranticFactoryEntryHandler
elif self.level == Levels.GloomyGalleon:
return Regions.GloomyGalleonEntryHandler
elif self.level == Levels.FungiForest:
return Regions.FungiForestEntryHandler
elif self.level == Levels.CrystalCaves:
return Regions.CrystalCavesEntryHandler
elif self.level == Levels.CreepyCastle:
return Regions.CreepyCastleEntryHandler
elif self.level == Levels.HideoutHelm:
return Regions.HideoutHelmEntry
return Regions.GameStart
def getHintRegionName(self) -> str:
"""Convert hint region enum to the name."""
return HINT_REGION_PAIRING.get(self.hint_name, "Unknown Region")
def isMedalRegion(self) -> bool:
"""Return whether the associated hint region is a medal reward region."""
return self.hint_name in MEDAL_REWARD_REGIONS
def isCBRegion(self) -> bool:
"""Return whether the associated hint region requires CBs to access (Bosses and medal rewards)."""
return self.hint_name in MEDAL_REWARD_REGIONS or self.hint_name == HintRegion.Bosses
def isShopRegion(self) -> bool:
"""Return whether the associated hint region is a shop region."""
return self.hint_name in SHOP_REGIONS
class TransitionBack:
"""The exited side of a transition between regions."""
def __init__(self, regionId: Regions, exitName: str, spoilerName: str, reverse: Optional[Transitions] = None) -> None:
"""Initialize with given parameters."""
self.regionId = regionId # Destination region
self.name = exitName
self.spoilerName = spoilerName
self.reverse = reverse # Indicates a reverse direction transition, if one exists
class TransitionFront:
"""The entered side of a transition between regions."""
def __init__(
self,
dest: Regions,
logic: Callable,
exitShuffleId: Optional[Transitions] = None,
assumed: bool = False,
time: Time = Time.Both,
isGlitchTransition: bool = False,
isBananaportTransition: bool = False,
) -> None:
"""Initialize with given parameters."""
self.dest = dest
self.logic = logic # Lambda function for accessibility
self.exitShuffleId = exitShuffleId
self.time = time
self.assumed = assumed # Indicates this is an assumed exit attached to the root
self.isGlitchTransition = isGlitchTransition # Indicates if this is a glitch-logic transition for this entrance
self.isBananaportTransition = isBananaportTransition # Indicates if this transition is due to a Bananaport
class Sphere:
"""A randomizer concept often used in spoiler logs.
A 'sphere' is a collection of locations and items that are accessible
or obtainable with only the items available from earlier, smaller spheres.
Sphere 0 items are what you start with in a seed, sphere 1 items can be
obtained with those items, sphere 2 items can be obtained with sphere 0
and sphere 1 items, and so on.
"""
def __init__(self) -> None:
"""Initialize with given parameters."""
self.seedBeaten = False
self.availableGBs = 0
self.locations: List[Union[LocationLogic, Any]] = []
class ColoredBananaGroup:
"""Stores data for each group of colored bananas."""
def __init__(
self,
*,
group=0,
name="No Location",
map_id=0,
konglist=[],
region=None,
logic=None,
vanilla=False,
locations=[],
) -> None:
"""Initialize with given parameters."""
self.group = group
self.name = name
self.map = map_id
self.kongs = konglist
self.locations = locations # 5 numbers: {int amount, float scale, int x, y, z}
self.region = region
if logic is None:
self.logic = lambda l: True
else:
self.logic = logic
self.selected = False
class Balloon:
"""Stores data for each balloon."""
def __init__(
self,
*,
id=0,
name="No Location",
map_id=0,
speed=0,
konglist=[],
region=None,
logic=None,
vanilla=False,
points=[],
) -> None:
"""Initialize with given parameters."""
self.id = id
self.name = name
self.map = map_id
self.speed = speed
self.kongs = konglist
self.points = points # 3 numbers: [int x, y, z]
self.region = region
if logic is None:
self.logic = lambda l: True
else:
self.logic = logic
self.spawnPoint = self.setSpawnPoint(points)
self.selected = False
def setSpawnPoint(self, points: List[List[int]] = []) -> List[int]:
"""Set the spawn point of a balloon based on its path."""
spawnX = 0.0
spawnY = 0.0
spawnZ = 0.0
for p in points:
spawnX += p[0]
spawnY += p[1]
spawnZ += p[2]
spawnX /= len(points)
spawnY /= len(points)
spawnY -= 100.0 # Most balloons are at least 100 units off the ground
spawnZ /= len(points)
return [int(spawnX), int(spawnY), int(spawnZ)]