RoR2: Seekers of the Storm (SOTS) DLC Support (#5569)

This commit is contained in:
Rjosephson
2026-03-10 13:05:59 -06:00
committed by GitHub
parent 47e581bc30
commit 56c2272bfd
11 changed files with 302 additions and 71 deletions

View File

@@ -4,7 +4,10 @@ from .items import RiskOfRainItem, item_table, item_pool_weights, offset, filler
from .locations import RiskOfRainLocation, item_pickups, get_locations
from .rules import set_rules
from .ror2environments import environment_vanilla_table, environment_vanilla_orderedstages_table, \
environment_sotv_orderedstages_table, environment_sotv_table, collapse_dict_list_vertical, shift_by_offset
environment_sotv_orderedstages_table, environment_sotv_table, environment_sost_orderedstages_table, \
environment_sost_table, collapse_dict_list_vertical, shift_by_offset, environment_vanilla_variants_table, \
environment_vanilla_variant_orderedstages_table, environment_sots_variants_table, \
environment_sots_variants_orderedstages_table
from BaseClasses import Item, ItemClassification, Tutorial
from .options import ItemWeights, ROR2Options, ror2_option_groups
@@ -46,7 +49,7 @@ class RiskOfRainWorld(World):
}
location_name_to_id = item_pickups
required_client_version = (0, 5, 0)
required_client_version = (0, 6, 4)
web = RiskOfWeb()
total_revivals: int
@@ -62,7 +65,9 @@ class RiskOfRainWorld(World):
scavengers=self.options.scavengers_per_stage.value,
scanners=self.options.scanner_per_stage.value,
altars=self.options.altars_per_stage.value,
dlc_sotv=bool(self.options.dlc_sotv.value)
dlc_sotv=bool(self.options.dlc_sotv.value),
dlc_sots=bool(self.options.dlc_sots.value),
stage_variants=bool(self.options.stage_variants)
)
)
self.total_revivals = int(self.options.total_revivals.value / 100 *
@@ -71,6 +76,8 @@ class RiskOfRainWorld(World):
self.total_revivals -= 1
if self.options.victory == "voidling" and not self.options.dlc_sotv:
self.options.victory.value = self.options.victory.option_any
if self.options.victory == "falseson" and not self.options.dlc_sots:
self.options.victory.value = self.options.victory.option_any
def create_regions(self) -> None:
@@ -105,16 +112,39 @@ class RiskOfRainWorld(World):
# figure out all available ordered stages for each tier
environment_available_orderedstages_table = environment_vanilla_orderedstages_table
environments_pool = shift_by_offset(environment_vanilla_table, environment_offset)
# Vanilla Variants
if self.options.stage_variants:
environment_available_orderedstages_table = \
collapse_dict_list_vertical(environment_available_orderedstages_table,
environment_vanilla_variant_orderedstages_table)
if self.options.dlc_sotv:
environment_available_orderedstages_table = \
collapse_dict_list_vertical(environment_available_orderedstages_table,
environment_sotv_orderedstages_table)
if self.options.dlc_sots:
environment_available_orderedstages_table = \
collapse_dict_list_vertical(environment_available_orderedstages_table,
environment_sost_orderedstages_table)
if self.options.dlc_sots and self.options.stage_variants:
environment_available_orderedstages_table = \
collapse_dict_list_vertical(environment_available_orderedstages_table,
environment_sots_variants_orderedstages_table)
environments_pool = shift_by_offset(environment_vanilla_table, environment_offset)
if self.options.stage_variants:
environment_offset_table = shift_by_offset(environment_vanilla_variants_table, environment_offset)
environments_pool = {**environments_pool, **environment_offset_table}
if self.options.dlc_sotv:
environment_offset_table = shift_by_offset(environment_sotv_table, environment_offset)
environments_pool = {**environments_pool, **environment_offset_table}
if self.options.dlc_sots:
environment_offset_table = shift_by_offset(environment_sost_table, environment_offset)
environments_pool = {**environments_pool, **environment_offset_table}
# SOTS Variant Environments
if self.options.dlc_sots and self.options.stage_variants:
environment_offset_table = shift_by_offset(environment_sots_variants_table, environment_offset)
environments_pool = {**environments_pool, **environment_offset_table}
# percollect starting environment for stage 1
unlock = self.random.choices(list(environment_available_orderedstages_table[0].keys()), k=1)
self.multiworld.push_precollected(self.create_item(unlock[0]))
@@ -146,7 +176,9 @@ class RiskOfRainWorld(World):
scavengers=self.options.scavengers_per_stage.value,
scanners=self.options.scanner_per_stage.value,
altars=self.options.altars_per_stage.value,
dlc_sotv=bool(self.options.dlc_sotv.value)
dlc_sotv=bool(self.options.dlc_sotv.value),
dlc_sots=bool(self.options.dlc_sots.value),
stage_variants=bool(self.options.stage_variants)
)
)
# Create junk items
@@ -223,7 +255,7 @@ class RiskOfRainWorld(World):
"chests_per_stage", "shrines_per_stage", "scavengers_per_stage",
"scanner_per_stage", "altars_per_stage", "total_revivals",
"start_with_revive", "final_stage_death", "death_link", "require_stages",
"progressive_stages", casing="camel")
"progressive_stages", "stage_variants", "show_seer_portals", casing="camel")
return {
**options_dict,
"seed": "".join(self.random.choice(string.digits) for _ in range(16)),
@@ -254,7 +286,7 @@ class RiskOfRainWorld(World):
event_loc.place_locked_item(RiskOfRainItem("Stage 5", ItemClassification.progression, None, self.player))
event_loc.show_in_spoiler = False
event_region.locations.append(event_loc)
event_loc.access_rule = lambda state: state.has("Sky Meadow", self.player)
event_loc.access_rule = lambda state: state.has("Sky Meadow", self.player) or state.has("Helminth Hatchery", self.player)
victory_region = self.multiworld.get_region("Victory", self.player)
victory_event = RiskOfRainLocation(self.player, "Victory", None, victory_region)

View File

@@ -0,0 +1,6 @@
{
"game": "Risk of Rain 2",
"minimum_ap_version": "0.6.4",
"world_version": "1.5.0",
"authors": ["Kindasneaki"]
}

View File

@@ -88,12 +88,21 @@ Explore Mode items are:
* `Commencement`
* `All the Hidden Realms`
Dlc_Sotv items
DLC Survivors of the Void (SOTV) items
* `Siphoned Forest`
* `Aphelian Sanctuary`
* `Sulfur Pools`
* `Void Locus`
DLC Seekers of the Storm (SOTS) items
* `Shattered Abodes`, `Vicious Falls`, `Disturbed Impact`
* `Reformed Altar`
* `Treeborn Colony`, `Golden Dieback`
* `Prime Meridian`
* `Helminth Hatchery`
When an explore item is granted, it will unlock that environment and will now be accessible! The
game will still pick randomly which environment is next, but it will first check to see if they are available. If you have
multiple of the next environments unlocked, it will weight the game to have a ***higher chance*** to go to one you

View File

@@ -23,6 +23,13 @@ all necessary dependencies as well.
Click on the `Start modded` button in the top left in `r2modman` to start the game with the Archipelago mod installed.
### Troubleshooting
* The mod doesn't show up in game!
* `r2modman` looks for the game at its default directory. If you have the game installed somewhere else,
you can update `r2modman` by going to `Settings > Change Risk of Rain 2 folder`
and selecting the correct directory.
## Configuring your YAML File
### What is a YAML and why do I need one?
You can see the [basic multiworld setup guide](/tutorial/Archipelago/setup/en) here on the Archipelago website to learn
@@ -59,6 +66,7 @@ also optionally connect to the multiworld using the text client, which can be fo
### In-Game Commands
These commands are to be used in-game by using ``Ctrl + Alt + ` `` and then typing the following:
- `archipelago_reconnect` Reconnect to AP.
- `archipelago_connect <url> <port> <slot> [password]` example: "archipelago_connect archipelago.gg 38281 SlotName".
- `archipelago_deathlink true/false` Toggle deathlink.
- `archipelago_disconnect` Disconnect from AP.

View File

@@ -3,7 +3,8 @@ from BaseClasses import Location
from .options import TotalLocations, ChestsPerEnvironment, ShrinesPerEnvironment, ScavengersPerEnvironment, \
ScannersPerEnvironment, AltarsPerEnvironment
from .ror2environments import compress_dict_list_horizontal, environment_vanilla_orderedstages_table, \
environment_sotv_orderedstages_table
environment_sotv_orderedstages_table, environment_sost_orderedstages_table, \
environment_sots_variants_orderedstages_table, environment_vanilla_variant_orderedstages_table
class RiskOfRainLocation(Location):
@@ -57,13 +58,20 @@ def get_environment_locations(chests: int, shrines: int, scavengers: int, scanne
return locations
def get_locations(chests: int, shrines: int, scavengers: int, scanners: int, altars: int, dlc_sotv: bool) \
def get_locations(chests: int, shrines: int, scavengers: int, scanners: int, altars: int, dlc_sotv: bool,
dlc_sots: bool, stage_variants: bool) \
-> Dict[str, int]:
"""Get a dictionary of locations for the orderedstage environments with the locations from the parameters."""
locations = {}
orderedstages = compress_dict_list_horizontal(environment_vanilla_orderedstages_table)
if stage_variants:
orderedstages.update(compress_dict_list_horizontal(environment_vanilla_variant_orderedstages_table))
if dlc_sotv:
orderedstages.update(compress_dict_list_horizontal(environment_sotv_orderedstages_table))
if dlc_sots:
orderedstages.update(compress_dict_list_horizontal(environment_sost_orderedstages_table))
if dlc_sots and stage_variants:
orderedstages.update(compress_dict_list_horizontal(environment_sots_variants_orderedstages_table))
# for every environment, generate the respective locations
for environment_name, environment_index in orderedstages.items():
locations.update(get_environment_locations(
@@ -86,4 +94,6 @@ location_table.update(get_locations(
scanners=ScannersPerEnvironment.range_end,
altars=AltarsPerEnvironment.range_end,
dlc_sotv=True,
dlc_sots=True,
stage_variants=True
))

View File

@@ -22,8 +22,9 @@ class Goal(Choice):
class Victory(Choice):
"""
Mithrix: Defeat Mithrix in Commencement
Voidling: Defeat the Voidling in The Planetarium (DLC required! Will select any if not enabled.)
Voidling: Defeat the Voidling in The Planetarium (SOTV DLC required! Will select any if not enabled.)
Limbo: Defeat the Scavenger in Hidden Realm: A Moment, Whole
Falseson: Defeat False son and gift an item to the altar in Prime Meridian (SOTS DLC required! Will select any if not enabled.)
Any: Any victory in the game will count. See Final Stage Death for additional ways.
"""
display_name = "Victory Condition"
@@ -31,6 +32,7 @@ class Victory(Choice):
option_mithrix = 1
option_voidling = 2
option_limbo = 3
option_falseson = 4
default = 0
@@ -138,18 +140,26 @@ class FinalStageDeath(Toggle):
If not use the following to tell if final stage death will count:
Victory: mithrix - only dying in Commencement will count.
Victory: voidling - only dying in The Planetarium will count.
Victory: limbo - Obliterating yourself will count."""
Victory: limbo - Obliterating yourself will count.
Victory: falseson - only dying in Prime Meridian will count."""
display_name = "Final Stage Death is Win"
class DLC_SOTV(Toggle):
"""
Enable if you are using SOTV DLC.
Enable if you are using Survivors of the Void DLC.
Affects environment availability for Explore Mode.
Adds Void Items into the item pool
"""
display_name = "Enable DLC - SOTV"
class DLC_SOTS(Toggle):
"""
Enable if you are using Seekers of the Storm DLC.
Affects environment availability for Explore Mode.
"""
display_name = "Enable DLC - SOTS"
class RequireStages(DefaultOnToggle):
"""Add Stage items to the pool to block access to the next set of environments."""
@@ -162,6 +172,23 @@ class ProgressiveStages(DefaultOnToggle):
display_name = "Progressive Stages"
class StageVariants(Toggle):
"""Enable if you want to include stage variants in the environment pool.
Stages included are:
- Distant Roost (2)
- Titanic Plains (2)
SOTS DLC Enabled:
- Vicious Falls
- Shattered Abodes
- Golden Dieback"""
display_name = "Include Stage Variants"
class ShowSeerPortals(DefaultOnToggle):
"""Shows Seer Portals at the teleporter to allow choosing the next environment."""
display_name = "Show Seer Portals"
class GreenScrap(Range):
"""Weight of Green Scraps in the item pool.
@@ -384,6 +411,8 @@ ror2_option_groups = [
AltarsPerEnvironment,
RequireStages,
ProgressiveStages,
StageVariants,
ShowSeerPortals,
]),
OptionGroup("Classic Mode Options", [
TotalLocations,
@@ -427,8 +456,11 @@ class ROR2Options(PerGameCommonOptions):
start_with_revive: StartWithRevive
final_stage_death: FinalStageDeath
dlc_sotv: DLC_SOTV
dlc_sots: DLC_SOTS
require_stages: RequireStages
progressive_stages: ProgressiveStages
stage_variants: StageVariants
show_seer_portals: ShowSeerPortals
death_link: DeathLink
item_pickup_step: ItemPickupStep
shrine_use_step: ShrineUseStep

View File

@@ -18,13 +18,10 @@ def create_explore_regions(ror2_world: "RiskOfRainWorld") -> None:
multiworld = ror2_world.multiworld
# Default Locations
non_dlc_regions: Dict[str, RoRRegionData] = {
"Menu": RoRRegionData(None, ["Distant Roost", "Distant Roost (2)",
"Titanic Plains", "Titanic Plains (2)",
"Menu": RoRRegionData(None, ["Distant Roost", "Titanic Plains",
"Verdant Falls"]),
"Distant Roost": RoRRegionData([], ["OrderedStage_1"]),
"Distant Roost (2)": RoRRegionData([], ["OrderedStage_1"]),
"Titanic Plains": RoRRegionData([], ["OrderedStage_1"]),
"Titanic Plains (2)": RoRRegionData([], ["OrderedStage_1"]),
"Verdant Falls": RoRRegionData([], ["OrderedStage_1"]),
"Abandoned Aqueduct": RoRRegionData([], ["OrderedStage_2"]),
"Wetland Aspect": RoRRegionData([], ["OrderedStage_2"]),
@@ -35,12 +32,30 @@ def create_explore_regions(ror2_world: "RiskOfRainWorld") -> None:
"Sundered Grove": RoRRegionData([], ["OrderedStage_4"]),
"Sky Meadow": RoRRegionData([], ["Hidden Realm: Bulwark's Ambry", "OrderedStage_5"]),
}
non_dlc_variant_regions: Dict[str, RoRRegionData] = {
"Distant Roost (2)": RoRRegionData([], ["OrderedStage_1"]),
"Titanic Plains (2)": RoRRegionData([], ["OrderedStage_1"]),
}
# SOTV Regions
dlc_regions: Dict[str, RoRRegionData] = {
dlc_sotv_regions: Dict[str, RoRRegionData] = {
"Siphoned Forest": RoRRegionData([], ["OrderedStage_1"]),
"Aphelian Sanctuary": RoRRegionData([], ["OrderedStage_2"]),
"Sulfur Pools": RoRRegionData([], ["OrderedStage_3"])
}
dlc_sost_regions: Dict[str, RoRRegionData] = {
"Shattered Abodes": RoRRegionData([], ["OrderedStage_1"]),
"Reformed Altar": RoRRegionData([], ["OrderedStage_2", "Treeborn Colony"]),
"Treeborn Colony": RoRRegionData([], ["OrderedStage_3", "Prime Meridian"]),
"Helminth Hatchery": RoRRegionData([], ["Hidden Realm: Bulwark's Ambry", "OrderedStage_5"]),
}
dlc_sots_variant_regions: Dict[str, RoRRegionData] = {
"Viscous Falls": RoRRegionData([], ["OrderedStage_1"]),
"Disturbed Impact": RoRRegionData([], ["OrderedStage_1"]),
"Golden Dieback": RoRRegionData([], ["OrderedStage_3", "Prime Meridian"]),
}
other_regions: Dict[str, RoRRegionData] = {
"Commencement": RoRRegionData(None, ["Victory", "Petrichor V"]),
"OrderedStage_5": RoRRegionData(None, ["Hidden Realm: A Moment, Fractured",
@@ -61,10 +76,15 @@ def create_explore_regions(ror2_world: "RiskOfRainWorld") -> None:
"Hidden Realm: Bazaar Between Time": RoRRegionData(None, ["Void Fields"]),
"Hidden Realm: Gilded Coast": RoRRegionData(None, None)
}
dlc_other_regions: Dict[str, RoRRegionData] = {
dlc_sotv_other_regions: Dict[str, RoRRegionData] = {
"The Planetarium": RoRRegionData(None, ["Victory", "Petrichor V"]),
"Void Locus": RoRRegionData(None, ["The Planetarium"])
}
dlc_sost_other_regions: Dict[str, RoRRegionData] = {
"Prime Meridian": RoRRegionData(None, ["Victory", "Petrichor V"]),
}
# Totals of each item
chests = int(ror2_options.chests_per_stage)
shrines = int(ror2_options.shrines_per_stage)
@@ -72,8 +92,14 @@ def create_explore_regions(ror2_world: "RiskOfRainWorld") -> None:
scanners = int(ror2_options.scanner_per_stage)
newt = int(ror2_options.altars_per_stage)
all_location_regions = {**non_dlc_regions}
if ror2_options.stage_variants:
all_location_regions.update(non_dlc_variant_regions)
if ror2_options.dlc_sotv:
all_location_regions = {**non_dlc_regions, **dlc_regions}
all_location_regions.update(dlc_sotv_regions)
if ror2_options.dlc_sots:
all_location_regions.update(dlc_sost_regions)
if ror2_options.dlc_sots and ror2_options.stage_variants:
all_location_regions.update(dlc_sots_variant_regions)
# Locations
for key in all_location_regions:
@@ -99,25 +125,52 @@ def create_explore_regions(ror2_world: "RiskOfRainWorld") -> None:
all_location_regions[key].locations.append(f"{key}: Newt Altar {i + 1}")
regions_pool: Dict = {**all_location_regions, **other_regions}
# DLC Locations
# Non DLC Variant Locations
if ror2_options.stage_variants:
non_dlc_regions["Menu"].region_exits.append("Distant Roost (2)")
non_dlc_regions["Menu"].region_exits.append("Titanic Plains (2)")
# SOTV DLC Locations
if ror2_options.dlc_sotv:
non_dlc_regions["Menu"].region_exits.append("Siphoned Forest")
other_regions["OrderedStage_1"].region_exits.append("Aphelian Sanctuary")
other_regions["OrderedStage_2"].region_exits.append("Sulfur Pools")
other_regions["Void Fields"].region_exits.append("Void Locus")
other_regions["Commencement"].region_exits.append("The Planetarium")
regions_pool: Dict = {**all_location_regions, **other_regions, **dlc_other_regions}
# SOTS DLC Locations
if ror2_options.dlc_sots:
non_dlc_regions["Menu"].region_exits.append("Shattered Abodes")
other_regions["OrderedStage_1"].region_exits.append("Reformed Altar")
other_regions["OrderedStage_4"].region_exits.append("Helminth Hatchery")
# SOTS Variant Locations
if ror2_options.dlc_sots and ror2_options.stage_variants:
non_dlc_regions["Menu"].region_exits.append("Viscous Falls")
non_dlc_regions["Menu"].region_exits.append("Disturbed Impact")
dlc_sost_regions["Reformed Altar"].region_exits.append("Golden Dieback")
if ror2_options.dlc_sotv:
regions_pool.update(dlc_sotv_other_regions)
if ror2_options.dlc_sots:
regions_pool.update(dlc_sost_other_regions)
# Check to see if Victory needs to be removed from regions
if ror2_options.victory == "mithrix":
other_regions["Hidden Realm: A Moment, Whole"].region_exits.pop(0)
dlc_other_regions["The Planetarium"].region_exits.pop(0)
dlc_sotv_other_regions["The Planetarium"].region_exits.pop(0)
dlc_sost_other_regions["Prime Meridian"].region_exits.pop(0)
elif ror2_options.victory == "voidling":
other_regions["Commencement"].region_exits.pop(0)
other_regions["Hidden Realm: A Moment, Whole"].region_exits.pop(0)
dlc_sost_other_regions["Prime Meridian"].region_exits.pop(0)
elif ror2_options.victory == "limbo":
other_regions["Commencement"].region_exits.pop(0)
dlc_other_regions["The Planetarium"].region_exits.pop(0)
dlc_sotv_other_regions["The Planetarium"].region_exits.pop(0)
dlc_sost_other_regions["Prime Meridian"].region_exits.pop(0)
elif ror2_options.victory == "falseson":
other_regions["Commencement"].region_exits.pop(0)
other_regions["Hidden Realm: A Moment, Whole"].region_exits.pop(0)
dlc_sotv_other_regions["The Planetarium"].region_exits.pop(0)
# Create all the regions
for name, data in regions_pool.items():

View File

@@ -4,11 +4,14 @@ from typing import Dict, List, TypeVar
environment_vanilla_orderedstage_1_table: Dict[str, int] = {
"Distant Roost": 7, # blackbeach
"Distant Roost (2)": 8, # blackbeach2
"Titanic Plains": 15, # golemplains
"Titanic Plains (2)": 16, # golemplains2
"Verdant Falls": 28, # lakes
}
environment_vanilla_variant_orderedstage_1_table: Dict[str, int] = {
"Distant Roost (2)": 8, # blackbeach2
"Titanic Plains (2)": 16, # golemplains2
}
environment_vanilla_orderedstage_2_table: Dict[str, int] = {
"Abandoned Aqueduct": 17, # goolake
"Wetland Aspect": 12, # foggyswamp
@@ -54,6 +57,34 @@ environment_sotv_special_table: Dict[str, int] = {
"The Planetarium": 45, # voidraid
}
environment_sost_orderstage_1_table: Dict[str, int] = {
"Shattered Abodes": 54, # village
}
environment_sost_variant_orderstage_1_table: Dict[str, int] = {
"Viscous Falls": 34, # lakesnight
"Disturbed Impact": 55, # villagenight
}
environment_sost_orderstage_2_table: Dict[str, int] = {
"Reformed Altar": 36, # lemuriantemple
}
environment_sost_orderstage_3_table: Dict[str, int] = {
"Treeborn Colony": 21, # habitat
}
environment_sost_variant_orderstage_3_table: Dict[str, int] = {
"Golden Dieback": 22, # habitatfall
}
environment_sost_orderstage_5_table: Dict[str, int] = {
"Helminth Hatchery": 23, # helminthroost
}
environment_sost_special_table: Dict[str, int] = {
"Prime Meridian": 40, # meridian
}
X = TypeVar("X")
Y = TypeVar("Y")
@@ -100,18 +131,32 @@ environment_vanilla_orderedstages_table = \
environment_vanilla_table = \
{**compress_dict_list_horizontal(environment_vanilla_orderedstages_table),
**environment_vanilla_hidden_realm_table, **environment_vanilla_special_table}
# Vanilla Variants
environment_vanilla_variant_orderedstages_table = \
[environment_vanilla_variant_orderedstage_1_table]
environment_vanilla_variants_table = \
{**compress_dict_list_horizontal(environment_vanilla_variant_orderedstages_table)}
# SoTV
environment_sotv_orderedstages_table = \
[environment_sotv_orderedstage_1_table, environment_sotv_orderedstage_2_table,
environment_sotv_orderedstage_3_table]
environment_sotv_table = \
{**compress_dict_list_horizontal(environment_sotv_orderedstages_table), **environment_sotv_special_table}
# SoST
environment_sost_orderedstages_table = \
[environment_sost_orderstage_1_table, environment_sost_orderstage_2_table,
environment_sost_orderstage_3_table, {}, environment_sost_orderstage_5_table] # There is no new stage 4 in SoST
environment_sost_table = \
{**compress_dict_list_horizontal(environment_sost_orderedstages_table), **environment_sost_special_table}
# SOTS Variants
environment_sots_variants_orderedstages_table = \
[environment_sost_variant_orderstage_1_table, {}, environment_sost_variant_orderstage_3_table]
environment_sots_variants_table = \
{**compress_dict_list_horizontal(environment_sots_variants_orderedstages_table)}
environment_non_orderedstages_table = \
{**environment_vanilla_hidden_realm_table, **environment_vanilla_special_table, **environment_sotv_special_table}
environment_orderedstages_table = \
collapse_dict_list_vertical(environment_vanilla_orderedstages_table, environment_sotv_orderedstages_table)
environment_all_table = {**environment_vanilla_table, **environment_sotv_table}
environment_all_table = {**environment_vanilla_table, **environment_sotv_table, **environment_sost_table,
**environment_vanilla_variants_table, **environment_sots_variants_table}
def shift_by_offset(dictionary: Dict[str, int], offset: int) -> Dict[str, int]:

View File

@@ -1,7 +1,9 @@
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 .ror2environments import environment_vanilla_orderedstages_table, environment_sotv_orderedstages_table, \
environment_sost_orderedstages_table, environment_vanilla_variant_orderedstages_table, \
environment_sots_variants_orderedstages_table
from typing import Set, TYPE_CHECKING
if TYPE_CHECKING:
@@ -43,6 +45,24 @@ def has_location_access_rule(multiworld: MultiWorld, environment: str, player: i
multiworld.get_location(location_name, player).access_rule = \
lambda state: state.has(environment, player)
def explore_environment_location_rules(table, multiworld, player, chests, shrines, newts, scavengers, scanners):
for i in range(len(table)):
for environment_name, _ in 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)
def set_rules(ror2_world: "RiskOfRainWorld") -> None:
player = ror2_world.player
@@ -60,7 +80,9 @@ def set_rules(ror2_world: "RiskOfRainWorld") -> None:
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)
dlc_sotv=bool(ror2_options.dlc_sotv.value),
dlc_sots=bool(ror2_options.dlc_sots.value),
stage_variants=bool(ror2_options.stage_variants)
)
)
@@ -101,40 +123,25 @@ def set_rules(ror2_world: "RiskOfRainWorld") -> None:
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)
# Vanilla stages
explore_environment_location_rules(environment_vanilla_orderedstages_table, multiworld, player, chests, shrines, newts,
scavengers, scanners)
# Vanilla Variant stages
if ror2_options.stage_variants:
explore_environment_location_rules(environment_vanilla_variant_orderedstages_table, multiworld, player, chests, shrines, newts,
scavengers, scanners)
# SoTv stages
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)
explore_environment_location_rules(environment_sotv_orderedstages_table, multiworld, player, chests, shrines,
newts, scavengers, scanners)
# SoTS stages
if ror2_options.dlc_sots:
explore_environment_location_rules(environment_sost_orderedstages_table, multiworld, player, chests, shrines,
newts, scavengers, scanners)
if ror2_options.dlc_sots and ror2_options.stage_variants:
explore_environment_location_rules(environment_sots_variants_orderedstages_table, multiworld, player, chests, shrines,
newts, scavengers, scanners)
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)
@@ -147,6 +154,8 @@ def set_rules(ror2_world: "RiskOfRainWorld") -> None:
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)
if ror2_options.dlc_sots:
has_entrance_access_rule(multiworld, "Stage 5", "Prime Meridian", player)
# Win Condition
multiworld.completion_condition[player] = lambda state: state.has("Victory", player)

View File

@@ -4,23 +4,33 @@ from . import RoR2TestBase
class DLCTest(RoR2TestBase):
options = {
"dlc_sotv": "true",
"victory": "any"
"victory": "any",
"dlc_sots": "true",
}
def test_commencement_victory(self) -> None:
self.collect_all_but(["Commencement", "The Planetarium", "Hidden Realm: A Moment, Whole", "Victory"])
self.collect_all_but(["Commencement", "The Planetarium", "Hidden Realm: A Moment, Whole", "Prime Meridian",
"Victory"])
self.assertBeatable(False)
self.collect_by_name("Commencement")
self.assertBeatable(True)
def test_planetarium_victory(self) -> None:
self.collect_all_but(["Commencement", "The Planetarium", "Hidden Realm: A Moment, Whole", "Victory"])
self.collect_all_but(["Commencement", "The Planetarium", "Hidden Realm: A Moment, Whole", "Prime Meridian",
"Victory"])
self.assertBeatable(False)
self.collect_by_name("The Planetarium")
self.assertBeatable(True)
def test_moment_whole_victory(self) -> None:
self.collect_all_but(["Commencement", "The Planetarium", "Hidden Realm: A Moment, Whole", "Victory"])
self.collect_all_but(["Commencement", "The Planetarium", "Hidden Realm: A Moment, Whole", "Prime Meridian",
"Victory"])
self.assertBeatable(False)
self.collect_by_name("Hidden Realm: A Moment, Whole")
self.assertBeatable(True)
def test_false_son_victory(self) -> None:
self.collect_all_but(["Commencement", "The Planetarium", "Hidden Realm: A Moment, Whole", "Prime Meridian",
"Victory"])
self.assertBeatable(False)
self.collect_by_name("Prime Meridian")
self.assertBeatable(True)

View File

@@ -0,0 +1,17 @@
from . import RoR2TestBase
class FalseSonGoalTest(RoR2TestBase):
options = {
"dlc_sots": "true",
"victory": "falseson",
"stage_variants": "true"
}
def test_false_son(self) -> None:
self.collect_all_but(["Prime Meridian", "Victory"])
self.assertFalse(self.can_reach_region("Prime Meridian"))
self.assertBeatable(False)
self.collect_by_name("Prime Meridian")
self.assertTrue(self.can_reach_region("Prime Meridian"))
self.assertBeatable(True)