mirror of
https://github.com/MarioSpore/Grinch-AP.git
synced 2025-10-21 04:01:32 -06:00

The access rules for "<Environment name> Chest n", "<Environment name> Shrine n" etc. locations recursively called state.can_reach() for the n-1 location name, with the n=1 location being the only location to have the actual access rule set. This patch removes the recursion, instead setting the actual access rule directly on each location, increasing the performance of checking accessibility of n>1 locations. Risk of Rain 2 was already quite fast to generate despite the recursion in the access rules, but with this patch, generating a multiworld with 200 copies of the template RoR2 yaml (and progression balancing disabled through a meta.yaml) goes from about 18s to about 6s for me. From generating the same seed before and after this patch, the same result is produced.
153 lines
8.7 KiB
Python
153 lines
8.7 KiB
Python
from worlds.generic.Rules import set_rule, add_rule
|
|
from BaseClasses import MultiWorld
|
|
from .locations import get_locations
|
|
from .ror2environments import environment_vanilla_orderedstages_table, environment_sotv_orderedstages_table
|
|
from typing import Set, TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from . import RiskOfRainWorld
|
|
|
|
|
|
# Rule to see if it has access to the previous stage
|
|
def has_entrance_access_rule(multiworld: MultiWorld, stage: str, region: str, player: int) -> None:
|
|
rule = lambda state: state.has(region, player) and state.has(stage, player)
|
|
for entrance in multiworld.get_region(region, player).entrances:
|
|
entrance.access_rule = rule
|
|
|
|
|
|
def has_stage_access_rule(multiworld: MultiWorld, stage: str, amount: int, region: str, player: int) -> None:
|
|
rule = lambda state: state.has(region, player) and \
|
|
(state.has(stage, player) or state.count("Progressive Stage", player) >= amount)
|
|
for entrance in multiworld.get_region(region, player).entrances:
|
|
entrance.access_rule = rule
|
|
|
|
|
|
def has_all_items(multiworld: MultiWorld, items: Set[str], region: str, player: int) -> None:
|
|
rule = lambda state: state.has_all(items, player) and state.has(region, player)
|
|
for entrance in multiworld.get_region(region, player).entrances:
|
|
entrance.access_rule = rule
|
|
|
|
|
|
# Checks to see if chest/shrine are accessible
|
|
def has_location_access_rule(multiworld: MultiWorld, environment: str, player: int, item_number: int, item_type: str)\
|
|
-> None:
|
|
location_name = f"{environment}: {item_type} {item_number}"
|
|
if item_type == "Scavenger":
|
|
# scavengers need to be locked till after a full loop since that is when they are capable of spawning.
|
|
# (While technically the requirement is just beating 5 stages, this will ensure that the player will have
|
|
# a long enough run to have enough director credits for scavengers and
|
|
# help prevent being stuck in the same stages until that point).
|
|
multiworld.get_location(location_name, player).access_rule = \
|
|
lambda state: state.has(environment, player) and state.has("Stage 5", player)
|
|
else:
|
|
multiworld.get_location(location_name, player).access_rule = \
|
|
lambda state: state.has(environment, player)
|
|
|
|
|
|
def set_rules(ror2_world: "RiskOfRainWorld") -> None:
|
|
player = ror2_world.player
|
|
multiworld = ror2_world.multiworld
|
|
ror2_options = ror2_world.options
|
|
if ror2_options.goal == "classic":
|
|
# classic mode
|
|
total_locations = ror2_options.total_locations.value # total locations for current player
|
|
else:
|
|
# explore mode
|
|
total_locations = len(
|
|
get_locations(
|
|
chests=ror2_options.chests_per_stage.value,
|
|
shrines=ror2_options.shrines_per_stage.value,
|
|
scavengers=ror2_options.scavengers_per_stage.value,
|
|
scanners=ror2_options.scanner_per_stage.value,
|
|
altars=ror2_options.altars_per_stage.value,
|
|
dlc_sotv=bool(ror2_options.dlc_sotv.value)
|
|
)
|
|
)
|
|
|
|
event_location_step = 25 # set an event location at these locations for "spheres"
|
|
divisions = total_locations // event_location_step
|
|
total_revivals = multiworld.worlds[player].total_revivals # pulling this info we calculated in generate_basic
|
|
|
|
if ror2_options.goal == "classic":
|
|
# classic mode
|
|
if divisions:
|
|
for i in range(1, divisions + 1): # since divisions is the floor of total_locations / 25
|
|
if i * event_location_step != total_locations:
|
|
event_loc = multiworld.get_location(f"Pickup{i * event_location_step}", player)
|
|
set_rule(event_loc,
|
|
lambda state, i=i: state.can_reach(f"ItemPickup{i * event_location_step - 1}",
|
|
"Location", player))
|
|
# we want to create a rule for each of the 25 locations per division
|
|
for n in range(i * event_location_step, (i + 1) * event_location_step + 1):
|
|
if n > total_locations:
|
|
break
|
|
if n == i * event_location_step:
|
|
set_rule(multiworld.get_location(f"ItemPickup{n}", player),
|
|
lambda state, event_item=event_loc.item.name: state.has(event_item, player))
|
|
else:
|
|
set_rule(multiworld.get_location(f"ItemPickup{n}", player),
|
|
lambda state, n=n: state.can_reach(f"ItemPickup{n - 1}", "Location", player))
|
|
set_rule(multiworld.get_location("Victory", player),
|
|
lambda state: state.can_reach(f"ItemPickup{total_locations}", "Location", player))
|
|
if total_revivals or ror2_options.start_with_revive.value:
|
|
add_rule(multiworld.get_location("Victory", player),
|
|
lambda state: state.has("Dio's Best Friend", player,
|
|
total_revivals + ror2_options.start_with_revive))
|
|
|
|
else:
|
|
# explore mode
|
|
chests = ror2_options.chests_per_stage.value
|
|
shrines = ror2_options.shrines_per_stage.value
|
|
newts = ror2_options.altars_per_stage.value
|
|
scavengers = ror2_options.scavengers_per_stage.value
|
|
scanners = ror2_options.scanner_per_stage.value
|
|
for i in range(len(environment_vanilla_orderedstages_table)):
|
|
for environment_name, _ in environment_vanilla_orderedstages_table[i].items():
|
|
# Make sure to go through each location
|
|
if scavengers == 1:
|
|
has_location_access_rule(multiworld, environment_name, player, scavengers, "Scavenger")
|
|
if scanners == 1:
|
|
has_location_access_rule(multiworld, environment_name, player, scanners, "Radio Scanner")
|
|
for chest in range(1, chests + 1):
|
|
has_location_access_rule(multiworld, environment_name, player, chest, "Chest")
|
|
for shrine in range(1, shrines + 1):
|
|
has_location_access_rule(multiworld, environment_name, player, shrine, "Shrine")
|
|
if newts > 0:
|
|
for newt in range(1, newts + 1):
|
|
has_location_access_rule(multiworld, environment_name, player, newt, "Newt Altar")
|
|
if i > 0:
|
|
has_stage_access_rule(multiworld, f"Stage {i}", i, environment_name, player)
|
|
|
|
if ror2_options.dlc_sotv:
|
|
for i in range(len(environment_sotv_orderedstages_table)):
|
|
for environment_name, _ in environment_sotv_orderedstages_table[i].items():
|
|
# Make sure to go through each location
|
|
if scavengers == 1:
|
|
has_location_access_rule(multiworld, environment_name, player, scavengers, "Scavenger")
|
|
if scanners == 1:
|
|
has_location_access_rule(multiworld, environment_name, player, scanners, "Radio Scanner")
|
|
for chest in range(1, chests + 1):
|
|
has_location_access_rule(multiworld, environment_name, player, chest, "Chest")
|
|
for shrine in range(1, shrines + 1):
|
|
has_location_access_rule(multiworld, environment_name, player, shrine, "Shrine")
|
|
if newts > 0:
|
|
for newt in range(1, newts + 1):
|
|
has_location_access_rule(multiworld, environment_name, player, newt, "Newt Altar")
|
|
if i > 0:
|
|
has_stage_access_rule(multiworld, f"Stage {i}", i, environment_name, player)
|
|
has_entrance_access_rule(multiworld, "Hidden Realm: A Moment, Fractured", "Hidden Realm: A Moment, Whole",
|
|
player)
|
|
has_stage_access_rule(multiworld, "Stage 1", 1, "Hidden Realm: Bazaar Between Time", player)
|
|
has_entrance_access_rule(multiworld, "Hidden Realm: Bazaar Between Time", "Void Fields", player)
|
|
has_entrance_access_rule(multiworld, "Stage 5", "Commencement", player)
|
|
has_entrance_access_rule(multiworld, "Stage 5", "Hidden Realm: A Moment, Fractured", player)
|
|
has_entrance_access_rule(multiworld, "Beads of Fealty", "Hidden Realm: A Moment, Whole", player)
|
|
if ror2_options.dlc_sotv:
|
|
has_entrance_access_rule(multiworld, "Stage 5", "The Planetarium", player)
|
|
has_entrance_access_rule(multiworld, "Stage 5", "Void Locus", player)
|
|
if ror2_options.victory == "voidling":
|
|
has_all_items(multiworld, {"Stage 5", "The Planetarium"}, "Commencement", player)
|
|
|
|
# Win Condition
|
|
multiworld.completion_condition[player] = lambda state: state.has("Victory", player)
|