RoR2: 1.20 content update (#1396)
## Adding in Explore Mode: Features include: * Added in `environments` to be items. * `Location checks` are now `environment based` instead of being able to get them from anywhere. * Added in support for the `DLC Survivors of the void` which include `Void Items` and `3 new maps` that come with it. (option added to use DLC) --------- Co-authored-by: Dogpetkid <dogpetkid@gmail.com>
This commit is contained in:
parent
fb1a9e9c5a
commit
cae1e683e2
|
@ -1,13 +1,13 @@
|
||||||
from typing import Dict
|
|
||||||
from BaseClasses import Item
|
from BaseClasses import Item
|
||||||
from .Options import ItemWeights
|
from .Options import ItemWeights
|
||||||
|
from .RoR2Environments import *
|
||||||
|
|
||||||
|
|
||||||
class RiskOfRainItem(Item):
|
class RiskOfRainItem(Item):
|
||||||
game: str = "Risk of Rain 2"
|
game: str = "Risk of Rain 2"
|
||||||
|
|
||||||
|
|
||||||
# 37000 - 38000
|
# 37000 - 37699, 38000
|
||||||
item_table: Dict[str, int] = {
|
item_table: Dict[str, int] = {
|
||||||
"Dio's Best Friend": 37001,
|
"Dio's Best Friend": 37001,
|
||||||
"Common Item": 37002,
|
"Common Item": 37002,
|
||||||
|
@ -19,9 +19,24 @@ item_table: Dict[str, int] = {
|
||||||
"Item Scrap, White": 37008,
|
"Item Scrap, White": 37008,
|
||||||
"Item Scrap, Green": 37009,
|
"Item Scrap, Green": 37009,
|
||||||
"Item Scrap, Red": 37010,
|
"Item Scrap, Red": 37010,
|
||||||
"Item Scrap, Yellow": 37011
|
"Item Scrap, Yellow": 37011,
|
||||||
|
"Void Item": 37012
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# 37700 - 37699
|
||||||
|
##################################################
|
||||||
|
# environments
|
||||||
|
|
||||||
|
environment_offest = 37700
|
||||||
|
|
||||||
|
# add ALL environments into the item table
|
||||||
|
environment_offset_table = shift_by_offset(environment_ALL_table, environment_offest)
|
||||||
|
item_table.update(shift_by_offset(environment_ALL_table, environment_offest))
|
||||||
|
# use the sotv dlc in the item table so that all names can be looked up regardless of use
|
||||||
|
|
||||||
|
# end of environments
|
||||||
|
##################################################
|
||||||
|
|
||||||
default_weights: Dict[str, int] = {
|
default_weights: Dict[str, int] = {
|
||||||
"Item Scrap, Green": 16,
|
"Item Scrap, Green": 16,
|
||||||
"Item Scrap, Red": 4,
|
"Item Scrap, Red": 4,
|
||||||
|
@ -32,6 +47,7 @@ default_weights: Dict[str, int] = {
|
||||||
"Legendary Item": 8,
|
"Legendary Item": 8,
|
||||||
"Boss Item": 4,
|
"Boss Item": 4,
|
||||||
"Lunar Item": 16,
|
"Lunar Item": 16,
|
||||||
|
"Void Item": 16,
|
||||||
"Equipment": 32
|
"Equipment": 32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,6 +61,7 @@ new_weights: Dict[str, int] = {
|
||||||
"Legendary Item": 10,
|
"Legendary Item": 10,
|
||||||
"Boss Item": 5,
|
"Boss Item": 5,
|
||||||
"Lunar Item": 10,
|
"Lunar Item": 10,
|
||||||
|
"Void Item": 16,
|
||||||
"Equipment": 20
|
"Equipment": 20
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,6 +75,7 @@ uncommon_weights: Dict[str, int] = {
|
||||||
"Legendary Item": 10,
|
"Legendary Item": 10,
|
||||||
"Boss Item": 5,
|
"Boss Item": 5,
|
||||||
"Lunar Item": 15,
|
"Lunar Item": 15,
|
||||||
|
"Void Item": 16,
|
||||||
"Equipment": 20
|
"Equipment": 20
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +89,7 @@ legendary_weights: Dict[str, int] = {
|
||||||
"Legendary Item": 100,
|
"Legendary Item": 100,
|
||||||
"Boss Item": 5,
|
"Boss Item": 5,
|
||||||
"Lunar Item": 15,
|
"Lunar Item": 15,
|
||||||
|
"Void Item": 16,
|
||||||
"Equipment": 20
|
"Equipment": 20
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +103,7 @@ lunartic_weights: Dict[str, int] = {
|
||||||
"Legendary Item": 0,
|
"Legendary Item": 0,
|
||||||
"Boss Item": 0,
|
"Boss Item": 0,
|
||||||
"Lunar Item": 100,
|
"Lunar Item": 100,
|
||||||
|
"Void Item": 0,
|
||||||
"Equipment": 0
|
"Equipment": 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +117,7 @@ chaos_weights: Dict[str, int] = {
|
||||||
"Legendary Item": 30,
|
"Legendary Item": 30,
|
||||||
"Boss Item": 20,
|
"Boss Item": 20,
|
||||||
"Lunar Item": 60,
|
"Lunar Item": 60,
|
||||||
|
"Void Item": 60,
|
||||||
"Equipment": 40
|
"Equipment": 40
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -110,6 +131,7 @@ no_scraps_weights: Dict[str, int] = {
|
||||||
"Legendary Item": 15,
|
"Legendary Item": 15,
|
||||||
"Boss Item": 5,
|
"Boss Item": 5,
|
||||||
"Lunar Item": 10,
|
"Lunar Item": 10,
|
||||||
|
"Void Item": 16,
|
||||||
"Equipment": 25
|
"Equipment": 25
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,6 +145,7 @@ even_weights: Dict[str, int] = {
|
||||||
"Legendary Item": 1,
|
"Legendary Item": 1,
|
||||||
"Boss Item": 1,
|
"Boss Item": 1,
|
||||||
"Lunar Item": 1,
|
"Lunar Item": 1,
|
||||||
|
"Void Item": 1,
|
||||||
"Equipment": 1
|
"Equipment": 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +159,21 @@ scraps_only: Dict[str, int] = {
|
||||||
"Legendary Item": 0,
|
"Legendary Item": 0,
|
||||||
"Boss Item": 0,
|
"Boss Item": 0,
|
||||||
"Lunar Item": 0,
|
"Lunar Item": 0,
|
||||||
|
"Void Item": 0,
|
||||||
|
"Equipment": 0
|
||||||
|
}
|
||||||
|
|
||||||
|
void_weights: Dict[str, int] = {
|
||||||
|
"Item Scrap, Green": 0,
|
||||||
|
"Item Scrap, Red": 0,
|
||||||
|
"Item Scrap, Yellow": 0,
|
||||||
|
"Item Scrap, White": 0,
|
||||||
|
"Common Item": 0,
|
||||||
|
"Uncommon Item": 0,
|
||||||
|
"Legendary Item": 0,
|
||||||
|
"Boss Item": 0,
|
||||||
|
"Lunar Item": 0,
|
||||||
|
"Void Item": 100,
|
||||||
"Equipment": 0
|
"Equipment": 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,7 +186,8 @@ item_pool_weights: Dict[int, Dict[str, int]] = {
|
||||||
ItemWeights.option_chaos: chaos_weights,
|
ItemWeights.option_chaos: chaos_weights,
|
||||||
ItemWeights.option_no_scraps: no_scraps_weights,
|
ItemWeights.option_no_scraps: no_scraps_weights,
|
||||||
ItemWeights.option_even: even_weights,
|
ItemWeights.option_even: even_weights,
|
||||||
ItemWeights.option_scraps_only: scraps_only
|
ItemWeights.option_scraps_only: scraps_only,
|
||||||
|
ItemWeights.option_void: void_weights,
|
||||||
}
|
}
|
||||||
|
|
||||||
lookup_id_to_name: Dict[int, str] = {id: name for name, id in item_table.items()}
|
lookup_id_to_name: Dict[int, str] = {id: name for name, id in item_table.items()}
|
||||||
|
|
|
@ -1,13 +1,119 @@
|
||||||
from typing import Dict
|
from typing import Tuple
|
||||||
from BaseClasses import Location
|
from BaseClasses import Location
|
||||||
from .Options import TotalLocations
|
from .Options import TotalLocations
|
||||||
|
from .Options import ChestsPerEnvironment
|
||||||
|
from .Options import ShrinesPerEnvironment
|
||||||
|
from .Options import ScavengersPerEnvironment
|
||||||
|
from .Options import ScannersPerEnvironment
|
||||||
|
from .Options import AltarsPerEnvironment
|
||||||
|
from .RoR2Environments import *
|
||||||
|
|
||||||
|
|
||||||
class RiskOfRainLocation(Location):
|
class RiskOfRainLocation(Location):
|
||||||
game: str = "Risk of Rain 2"
|
game: str = "Risk of Rain 2"
|
||||||
|
|
||||||
|
|
||||||
# 37006 - 37506
|
ror2_locations_start_id = 38000
|
||||||
item_pickups: Dict[str, int] = {
|
|
||||||
f"ItemPickup{i+1}": 37000+i for i in range(TotalLocations.range_end)
|
|
||||||
}
|
def get_classic_item_pickups(n: int) -> Dict[str, int]:
|
||||||
|
"""Get n ItemPickups, capped at the max value for TotalLocations"""
|
||||||
|
n = max(n, 0)
|
||||||
|
n = min(n, TotalLocations.range_end)
|
||||||
|
return { f"ItemPickup{i+1}": ror2_locations_start_id+i for i in range(n) }
|
||||||
|
|
||||||
|
|
||||||
|
item_pickups = get_classic_item_pickups(TotalLocations.range_end)
|
||||||
|
location_table = item_pickups
|
||||||
|
|
||||||
|
|
||||||
|
def environment_abreviation(long_name:str) -> str:
|
||||||
|
"""convert long environment names to initials"""
|
||||||
|
abrev = ""
|
||||||
|
# go through every word finding a letter (or number) for an initial
|
||||||
|
for word in long_name.split():
|
||||||
|
initial = word[0]
|
||||||
|
for letter in word:
|
||||||
|
if letter.isalnum():
|
||||||
|
initial = letter
|
||||||
|
break
|
||||||
|
abrev+= initial
|
||||||
|
return abrev
|
||||||
|
|
||||||
|
# highest numbered orderedstages (this is so we can treat the easily caculate the check ids based on the environment and location "offset")
|
||||||
|
highest_orderedstage: int= max(compress_dict_list_horizontal(environment_orderedstages_table).values())
|
||||||
|
|
||||||
|
ror2_locations_start_orderedstage = ror2_locations_start_id + TotalLocations.range_end
|
||||||
|
|
||||||
|
class orderedstage_location:
|
||||||
|
"""A class to behave like a struct for storing the offsets of location types in the allocated space per orderedstage environments."""
|
||||||
|
# TODO is there a better, more generic way to do this?
|
||||||
|
offset_ChestsPerEnvironment = 0
|
||||||
|
offset_ShrinesPerEnvironment = offset_ChestsPerEnvironment + ChestsPerEnvironment.range_end
|
||||||
|
offset_ScavengersPerEnvironment = offset_ShrinesPerEnvironment + ShrinesPerEnvironment.range_end
|
||||||
|
offset_ScannersPerEnvironment = offset_ScavengersPerEnvironment + ScavengersPerEnvironment.range_end
|
||||||
|
offset_AltarsPerEnvironment = offset_ScannersPerEnvironment + ScannersPerEnvironment.range_end
|
||||||
|
|
||||||
|
# total space allocated to the locations in a single orderedstage environment
|
||||||
|
allocation = offset_AltarsPerEnvironment + AltarsPerEnvironment.range_end
|
||||||
|
|
||||||
|
def get_environment_locations(chests:int, shrines:int, scavengers:int, scanners:int, altars:int, environment: Tuple[str, int]) -> Dict[str, int]:
|
||||||
|
"""Get the locations within a specific environment"""
|
||||||
|
environment_name = environment[0]
|
||||||
|
environment_index = environment[1]
|
||||||
|
locations = {}
|
||||||
|
|
||||||
|
# due to this mapping, since environment ids are not consecutive, there are lots of "wasted" id numbers
|
||||||
|
# TODO perhaps a hashing algorithm could be used to compress this range and save "wasted" ids
|
||||||
|
environment_start_id = environment_index * orderedstage_location.allocation + ror2_locations_start_orderedstage
|
||||||
|
for n in range(chests):
|
||||||
|
locations.update({f"{environment_name}: Chest {n+1}": n + orderedstage_location.offset_ChestsPerEnvironment + environment_start_id})
|
||||||
|
for n in range(shrines):
|
||||||
|
locations.update({f"{environment_name}: Shrine {n+1}": n + orderedstage_location.offset_ShrinesPerEnvironment + environment_start_id})
|
||||||
|
for n in range(scavengers):
|
||||||
|
locations.update({f"{environment_name}: Scavenger {n+1}": n + orderedstage_location.offset_ScavengersPerEnvironment + environment_start_id})
|
||||||
|
for n in range(scanners):
|
||||||
|
locations.update({f"{environment_name}: Radio Scanner {n+1}": n + orderedstage_location.offset_ScannersPerEnvironment + environment_start_id})
|
||||||
|
for n in range(altars):
|
||||||
|
locations.update({f"{environment_name}: Newt Altar {n+1}": n + orderedstage_location.offset_AltarsPerEnvironment + environment_start_id})
|
||||||
|
return locations
|
||||||
|
|
||||||
|
def get_locations(chests:int, shrines:int, scavengers:int, scanners:int, altars:int, dlc_sotv:bool) -> Dict[str, int]:
|
||||||
|
"""Get a dictionary of locations for the ordedstage environments with the locations from the parameters."""
|
||||||
|
locations = {}
|
||||||
|
orderedstages = compress_dict_list_horizontal(environment_vanilla_orderedstages_table)
|
||||||
|
if(dlc_sotv): orderedstages.update(compress_dict_list_horizontal(environment_sotv_orderedstages_table))
|
||||||
|
# for every environment, generate the respective locations
|
||||||
|
for environment_name, environment_index in orderedstages.items():
|
||||||
|
# locations = locations | orderedstage_location.get_environment_locations(
|
||||||
|
locations.update(orderedstage_location.get_environment_locations(
|
||||||
|
chests=chests,
|
||||||
|
shrines=shrines,
|
||||||
|
scavengers=scavengers,
|
||||||
|
scanners=scanners,
|
||||||
|
altars=altars,
|
||||||
|
environment=(environment_name, environment_index)
|
||||||
|
))
|
||||||
|
return locations
|
||||||
|
|
||||||
|
def getall_locations(dlc_sotv:bool=True) -> Dict[str, int]:
|
||||||
|
"""
|
||||||
|
Get all locations in ordered stages.
|
||||||
|
Set dlc_sotv to true for the SOTV DLC to be included.
|
||||||
|
"""
|
||||||
|
# to get all locations, attempt using as many locations as possible
|
||||||
|
return orderedstage_location.get_locations(
|
||||||
|
chests=ChestsPerEnvironment.range_end,
|
||||||
|
shrines=ShrinesPerEnvironment.range_end,
|
||||||
|
scavengers=ScavengersPerEnvironment.range_end,
|
||||||
|
scanners=ScannersPerEnvironment.range_end,
|
||||||
|
altars=AltarsPerEnvironment.range_end,
|
||||||
|
dlc_sotv=dlc_sotv
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ror2_location_post_orderedstage = ror2_locations_start_orderedstage + highest_orderedstage*orderedstage_location.allocation
|
||||||
|
location_table.update(orderedstage_location.getall_locations())
|
||||||
|
# use the sotv dlc in the lookup table so that all ids can be looked up regardless of use
|
||||||
|
|
||||||
|
lookup_id_to_name: Dict[int, str] = {id: name for name, id in location_table.items()}
|
||||||
|
|
|
@ -1,31 +1,99 @@
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
from Options import Option, DefaultOnToggle, Range, Choice
|
from Options import Option, Toggle, DefaultOnToggle, DeathLink, Range, Choice
|
||||||
|
|
||||||
|
|
||||||
|
# NOTE be aware that since the range of item ids that RoR2 uses is based off of the maximums of checks
|
||||||
|
# Be careful when changing the range_end values not to go into another game's IDs
|
||||||
|
# NOTE that these changes to range_end must also be reflected in the RoR2 client so it understands the same ids.
|
||||||
|
|
||||||
|
class Goal(Choice):
|
||||||
|
"""
|
||||||
|
Classic Mode: Every Item pickup increases fills a progress bar which gives location checks.
|
||||||
|
|
||||||
|
Explore Mode: Each environment will have location checks within each environment.
|
||||||
|
environments will be locked in the item pool until received.
|
||||||
|
"""
|
||||||
|
display_name = "Game Mode"
|
||||||
|
option_classic = 0
|
||||||
|
option_explore = 1
|
||||||
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
class TotalLocations(Range):
|
class TotalLocations(Range):
|
||||||
"""Number of location checks which are added to the Risk of Rain playthrough."""
|
"""Classic Mode: Number of location checks which are added to the Risk of Rain playthrough."""
|
||||||
display_name = "Total Locations"
|
display_name = "Total Locations"
|
||||||
range_start = 10
|
range_start = 40
|
||||||
range_end = 250
|
range_end = 250
|
||||||
default = 20
|
default = 40
|
||||||
|
|
||||||
|
|
||||||
|
class ChestsPerEnvironment(Range):
|
||||||
|
"""Explore Mode: The number of chest locations per environment."""
|
||||||
|
display_name = "Chests per Environment"
|
||||||
|
range_start = 2
|
||||||
|
range_end = 20
|
||||||
|
default = 10
|
||||||
|
|
||||||
|
|
||||||
|
class ShrinesPerEnvironment(Range):
|
||||||
|
"""Explore Mode: The number of shrine locations per environment."""
|
||||||
|
display_name = "Shrines per Environment"
|
||||||
|
range_start = 2
|
||||||
|
range_end = 20
|
||||||
|
default = 5
|
||||||
|
|
||||||
|
|
||||||
|
class ScavengersPerEnvironment(Range):
|
||||||
|
"""Explore Mode: The number of scavenger locations per environment."""
|
||||||
|
display_name = "Scavenger per Environment"
|
||||||
|
range_start = 0
|
||||||
|
range_end = 1
|
||||||
|
default = 1
|
||||||
|
|
||||||
|
class ScannersPerEnvironment(Range):
|
||||||
|
"""Explore Mode: The number of scanners locations per environment."""
|
||||||
|
display_name = "Radio Scanners per Environment"
|
||||||
|
range_start = 0
|
||||||
|
range_end = 1
|
||||||
|
default = 1
|
||||||
|
|
||||||
|
class AltarsPerEnvironment(Range):
|
||||||
|
"""Explore Mode: The number of altars locations per environment."""
|
||||||
|
display_name = "Newts Per Environment"
|
||||||
|
range_start = 0
|
||||||
|
range_end = 2
|
||||||
|
default = 1
|
||||||
|
|
||||||
class TotalRevivals(Range):
|
class TotalRevivals(Range):
|
||||||
"""Total Percentage of `Dio's Best Friend` item put in the item pool."""
|
"""Total Percentage of `Dio's Best Friend` item put in the item pool."""
|
||||||
display_name = "Total Percentage Revivals Available"
|
display_name = "Total Revives"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 10
|
range_end = 10
|
||||||
default = 4
|
default = 4
|
||||||
|
|
||||||
|
|
||||||
class ItemPickupStep(Range):
|
class ItemPickupStep(Range):
|
||||||
"""Number of items to pick up before an AP Check is completed.
|
"""
|
||||||
|
Number of items to pick up before an AP Check is completed.
|
||||||
Setting to 1 means every other pickup.
|
Setting to 1 means every other pickup.
|
||||||
Setting to 2 means every third pickup. So on..."""
|
Setting to 2 means every third pickup. So on...
|
||||||
|
"""
|
||||||
display_name = "Item Pickup Step"
|
display_name = "Item Pickup Step"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 5
|
range_end = 5
|
||||||
default = 2
|
default = 1
|
||||||
|
|
||||||
|
class ShrineUseStep(Range):
|
||||||
|
"""
|
||||||
|
Explore Mode:
|
||||||
|
Number of shrines to use up before an AP Check is completed.
|
||||||
|
Setting to 1 means every other pickup.
|
||||||
|
Setting to 2 means every third pickup. So on...
|
||||||
|
"""
|
||||||
|
display_name = "Shrine use Step"
|
||||||
|
range_start = 0
|
||||||
|
range_end = 3
|
||||||
|
default = 0
|
||||||
|
|
||||||
|
|
||||||
class AllowLunarItems(DefaultOnToggle):
|
class AllowLunarItems(DefaultOnToggle):
|
||||||
|
@ -38,13 +106,33 @@ class StartWithRevive(DefaultOnToggle):
|
||||||
display_name = "Start with a Revive"
|
display_name = "Start with a Revive"
|
||||||
|
|
||||||
|
|
||||||
class FinalStageDeath(DefaultOnToggle):
|
class FinalStageDeath(Toggle):
|
||||||
"""Death on the final boss stage counts as a win."""
|
"""Death on the final boss stage counts as a win."""
|
||||||
display_name = "Final Stage Death is Win"
|
display_name = "Final Stage Death is Win"
|
||||||
|
|
||||||
|
|
||||||
|
class BeginWithLoop(Toggle):
|
||||||
|
"""
|
||||||
|
Enable to precollect a full loop of environments.
|
||||||
|
Only has an effect with Explore Mode.
|
||||||
|
"""
|
||||||
|
display_name = "Begin With Loop"
|
||||||
|
|
||||||
|
|
||||||
|
class DLC_SOTV(Toggle):
|
||||||
|
"""
|
||||||
|
Enable if you are using SOTV DLC.
|
||||||
|
Affects environment availability for Explore Mode.
|
||||||
|
Adds Void Items into the item pool
|
||||||
|
"""
|
||||||
|
display_name = "Enable DLC - SOTV"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class GreenScrap(Range):
|
class GreenScrap(Range):
|
||||||
"""Weight of Green Scraps in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
"""Weight of Green Scraps in the item pool.
|
||||||
|
|
||||||
|
(Ignored unless Item Weight Presets is 'No')"""
|
||||||
display_name = "Green Scraps"
|
display_name = "Green Scraps"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 100
|
range_end = 100
|
||||||
|
@ -52,7 +140,9 @@ class GreenScrap(Range):
|
||||||
|
|
||||||
|
|
||||||
class RedScrap(Range):
|
class RedScrap(Range):
|
||||||
"""Weight of Red Scraps in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
"""Weight of Red Scraps in the item pool.
|
||||||
|
|
||||||
|
(Ignored unless Item Weight Presets is 'No')"""
|
||||||
display_name = "Red Scraps"
|
display_name = "Red Scraps"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 100
|
range_end = 100
|
||||||
|
@ -60,7 +150,9 @@ class RedScrap(Range):
|
||||||
|
|
||||||
|
|
||||||
class YellowScrap(Range):
|
class YellowScrap(Range):
|
||||||
"""Weight of yellow scraps in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
"""Weight of yellow scraps in the item pool.
|
||||||
|
|
||||||
|
(Ignored unless Item Weight Presets is 'No')"""
|
||||||
display_name = "Yellow Scraps"
|
display_name = "Yellow Scraps"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 100
|
range_end = 100
|
||||||
|
@ -68,7 +160,9 @@ class YellowScrap(Range):
|
||||||
|
|
||||||
|
|
||||||
class WhiteScrap(Range):
|
class WhiteScrap(Range):
|
||||||
"""Weight of white scraps in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
"""Weight of white scraps in the item pool.
|
||||||
|
|
||||||
|
(Ignored unless Item Weight Presets is 'No')"""
|
||||||
display_name = "White Scraps"
|
display_name = "White Scraps"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 100
|
range_end = 100
|
||||||
|
@ -76,7 +170,9 @@ class WhiteScrap(Range):
|
||||||
|
|
||||||
|
|
||||||
class CommonItem(Range):
|
class CommonItem(Range):
|
||||||
"""Weight of common items in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
"""Weight of common items in the item pool.
|
||||||
|
|
||||||
|
(Ignored unless Item Weight Presets is 'No')"""
|
||||||
display_name = "Common Items"
|
display_name = "Common Items"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 100
|
range_end = 100
|
||||||
|
@ -84,7 +180,9 @@ class CommonItem(Range):
|
||||||
|
|
||||||
|
|
||||||
class UncommonItem(Range):
|
class UncommonItem(Range):
|
||||||
"""Weight of uncommon items in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
"""Weight of uncommon items in the item pool.
|
||||||
|
|
||||||
|
(Ignored unless Item Weight Presets is 'No')"""
|
||||||
display_name = "Uncommon Items"
|
display_name = "Uncommon Items"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 100
|
range_end = 100
|
||||||
|
@ -92,7 +190,9 @@ class UncommonItem(Range):
|
||||||
|
|
||||||
|
|
||||||
class LegendaryItem(Range):
|
class LegendaryItem(Range):
|
||||||
"""Weight of legendary items in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
"""Weight of legendary items in the item pool.
|
||||||
|
|
||||||
|
(Ignored unless Item Weight Presets is 'No')"""
|
||||||
display_name = "Legendary Items"
|
display_name = "Legendary Items"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 100
|
range_end = 100
|
||||||
|
@ -100,7 +200,9 @@ class LegendaryItem(Range):
|
||||||
|
|
||||||
|
|
||||||
class BossItem(Range):
|
class BossItem(Range):
|
||||||
"""Weight of boss items in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
"""Weight of boss items in the item pool.
|
||||||
|
|
||||||
|
(Ignored unless Item Weight Presets is 'No')"""
|
||||||
display_name = "Boss Items"
|
display_name = "Boss Items"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 100
|
range_end = 100
|
||||||
|
@ -108,36 +210,54 @@ class BossItem(Range):
|
||||||
|
|
||||||
|
|
||||||
class LunarItem(Range):
|
class LunarItem(Range):
|
||||||
"""Weight of lunar items in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
"""Weight of lunar items in the item pool.
|
||||||
|
|
||||||
|
(Ignored unless Item Weight Presets is 'No')"""
|
||||||
display_name = "Lunar Items"
|
display_name = "Lunar Items"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 100
|
range_end = 100
|
||||||
default = 16
|
default = 16
|
||||||
|
|
||||||
|
|
||||||
|
class VoidItem(Range):
|
||||||
|
"""Weight of void items in the item pool.
|
||||||
|
|
||||||
|
(Ignored unless Item Weight Presets is 'No')
|
||||||
|
|
||||||
|
(Ignored if Enable DLC - SOTV is 'No') """
|
||||||
|
display_name = "Void Items"
|
||||||
|
range_start = 0
|
||||||
|
range_end = 100
|
||||||
|
default = 16
|
||||||
|
|
||||||
|
|
||||||
class Equipment(Range):
|
class Equipment(Range):
|
||||||
"""Weight of equipment items in the item pool. (Ignored unless Item Weight Presets is 'No')"""
|
"""Weight of equipment items in the item pool.
|
||||||
|
|
||||||
|
(Ignored unless Item Weight Presets is 'No')"""
|
||||||
display_name = "Equipment"
|
display_name = "Equipment"
|
||||||
range_start = 0
|
range_start = 0
|
||||||
range_end = 100
|
range_end = 100
|
||||||
default = 32
|
default = 32
|
||||||
|
|
||||||
|
|
||||||
class ItemPoolPresetToggle(DefaultOnToggle):
|
class ItemPoolPresetToggle(Toggle):
|
||||||
"""Will use the item weight presets when set to true, otherwise will use the custom set item pool weights."""
|
"""Will use the item weight presets when set to true, otherwise will use the custom set item pool weights."""
|
||||||
display_name = "Use Item Weight Presets"
|
display_name = "Use Item Weight Presets"
|
||||||
|
|
||||||
|
|
||||||
class ItemWeights(Choice):
|
class ItemWeights(Choice):
|
||||||
"""Preset choices for determining the weights of the item pool.
|
"""Set item_pool_presets to true if you want to use one of these presets.
|
||||||
New is a test for a potential adjustment to the default weights.
|
Preset choices for determining the weights of the item pool.
|
||||||
Uncommon puts a large number of uncommon items in the pool.
|
- New is a test for a potential adjustment to the default weights.
|
||||||
Legendary puts a large number of legendary items in the pool.
|
- Uncommon puts a large number of uncommon items in the pool.
|
||||||
Lunartic makes everything a lunar item.
|
- Legendary puts a large number of legendary items in the pool.
|
||||||
Chaos generates the pool completely at random with rarer items having a slight cap to prevent this option being too easy.
|
- Lunartic makes everything a lunar item.
|
||||||
No Scraps removes all scrap items from the item pool.
|
- Chaos generates the pool completely at random with rarer items having a slight cap to prevent this option being too easy.
|
||||||
Even generates the item pool with every item having an even weight.
|
- No Scraps removes all scrap items from the item pool.
|
||||||
Scraps Only will be only scrap items in the item pool."""
|
- Even generates the item pool with every item having an even weight.
|
||||||
|
- Scraps Only will be only scrap items in the item pool.
|
||||||
|
- Void makes everything a void item."""
|
||||||
display_name = "Item Weights"
|
display_name = "Item Weights"
|
||||||
option_default = 0
|
option_default = 0
|
||||||
option_new = 1
|
option_new = 1
|
||||||
|
@ -148,6 +268,7 @@ class ItemWeights(Choice):
|
||||||
option_no_scraps = 6
|
option_no_scraps = 6
|
||||||
option_even = 7
|
option_even = 7
|
||||||
option_scraps_only = 8
|
option_scraps_only = 8
|
||||||
|
option_void = 9
|
||||||
|
|
||||||
|
|
||||||
# define a dictionary for the weights of the generated item pool.
|
# define a dictionary for the weights of the generated item pool.
|
||||||
|
@ -161,17 +282,28 @@ ror2_weights: Dict[str, type(Option)] = {
|
||||||
"legendary_item": LegendaryItem,
|
"legendary_item": LegendaryItem,
|
||||||
"boss_item": BossItem,
|
"boss_item": BossItem,
|
||||||
"lunar_item": LunarItem,
|
"lunar_item": LunarItem,
|
||||||
|
"void_item": VoidItem,
|
||||||
"equipment": Equipment
|
"equipment": Equipment
|
||||||
}
|
}
|
||||||
|
|
||||||
ror2_options: Dict[str, type(Option)] = {
|
ror2_options: Dict[str, type(Option)] = {
|
||||||
"total_locations": TotalLocations,
|
"goal": Goal,
|
||||||
"total_revivals": TotalRevivals,
|
"total_locations": TotalLocations,
|
||||||
"start_with_revive": StartWithRevive,
|
"chests_per_stage": ChestsPerEnvironment,
|
||||||
"final_stage_death": FinalStageDeath,
|
"shrines_per_stage": ShrinesPerEnvironment,
|
||||||
"item_pickup_step": ItemPickupStep,
|
"scavengers_per_stage": ScavengersPerEnvironment,
|
||||||
"enable_lunar": AllowLunarItems,
|
"scanner_per_stage": ScannersPerEnvironment,
|
||||||
"item_weights": ItemWeights,
|
"altars_per_stage": AltarsPerEnvironment,
|
||||||
"item_pool_presets": ItemPoolPresetToggle,
|
"total_revivals": TotalRevivals,
|
||||||
|
"start_with_revive": StartWithRevive,
|
||||||
|
"final_stage_death": FinalStageDeath,
|
||||||
|
"begin_with_loop": BeginWithLoop,
|
||||||
|
"dlc_sotv": DLC_SOTV,
|
||||||
|
"death_link": DeathLink,
|
||||||
|
"item_pickup_step": ItemPickupStep,
|
||||||
|
"shrine_use_step": ShrineUseStep,
|
||||||
|
"enable_lunar": AllowLunarItems,
|
||||||
|
"item_weights": ItemWeights,
|
||||||
|
"item_pool_presets": ItemPoolPresetToggle,
|
||||||
**ror2_weights
|
**ror2_weights
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,126 @@
|
||||||
|
from typing import Dict, List, NamedTuple, Optional
|
||||||
|
|
||||||
|
from BaseClasses import MultiWorld, Region, RegionType, Entrance
|
||||||
|
from .Locations import location_table, RiskOfRainLocation
|
||||||
|
|
||||||
|
|
||||||
|
class RoRRegionData(NamedTuple):
|
||||||
|
locations: Optional[List[str]]
|
||||||
|
region_exits: Optional[List[str]]
|
||||||
|
|
||||||
|
|
||||||
|
def create_regions(multiworld: MultiWorld, player: int):
|
||||||
|
# Default Locations
|
||||||
|
non_dlc_regions: Dict[str, RoRRegionData] = {
|
||||||
|
"Menu": RoRRegionData(None, ["Distant Roost", "Distant Roost (2)", "Titanic Plains", "Titanic Plains (2)"]),
|
||||||
|
"Distant Roost": RoRRegionData([], ["OrderedStage_1"]),
|
||||||
|
"Distant Roost (2)": RoRRegionData([], ["OrderedStage_1"]),
|
||||||
|
"Titanic Plains": RoRRegionData([], ["OrderedStage_1"]),
|
||||||
|
"Titanic Plains (2)": RoRRegionData([], ["OrderedStage_1"]),
|
||||||
|
"Abandoned Aqueduct": RoRRegionData([], ["OrderedStage_2"]),
|
||||||
|
"Wetland Aspect": RoRRegionData([], ["OrderedStage_2"]),
|
||||||
|
"Rallypoint Delta": RoRRegionData([], ["OrderedStage_3"]),
|
||||||
|
"Scorched Acres": RoRRegionData([], ["OrderedStage_3"]),
|
||||||
|
"Abyssal Depths": RoRRegionData([], ["OrderedStage_4"]),
|
||||||
|
"Siren's Call": RoRRegionData([], ["OrderedStage_4"]),
|
||||||
|
"Sundered Grove": RoRRegionData([], ["OrderedStage_4"]),
|
||||||
|
"Sky Meadow": RoRRegionData([], ["Hidden Realm: Bulwark's Ambry", "OrderedStage_5"]),
|
||||||
|
}
|
||||||
|
# SOTV Regions
|
||||||
|
dlc_regions: Dict[str, RoRRegionData] = {
|
||||||
|
"Siphoned Forest": RoRRegionData([], ["OrderedStage_1"]),
|
||||||
|
"Aphelian Sanctuary": RoRRegionData([], ["OrderedStage_2"]),
|
||||||
|
"Sulfur Pools": RoRRegionData([], ["OrderedStage_3"])
|
||||||
|
}
|
||||||
|
other_regions: Dict[str, RoRRegionData] = {
|
||||||
|
"Commencement": RoRRegionData(None, ["Victory"]),
|
||||||
|
"OrderedStage_5": RoRRegionData(None, ["Hidden Realm: A Moment, Fractured", "Commencement"]),
|
||||||
|
"OrderedStage_1": RoRRegionData(None, ["Hidden Realm: Bazaar Between Time",
|
||||||
|
"Hidden Realm: Gilded Coast", "Abandoned Aqueduct", "Wetland Aspect"]),
|
||||||
|
"OrderedStage_2": RoRRegionData(None, ["Rallypoint Delta", "Scorched Acres"]),
|
||||||
|
"OrderedStage_3": RoRRegionData(None, ["Abyssal Depths", "Siren's Call", "Sundered Grove"]),
|
||||||
|
"OrderedStage_4": RoRRegionData(None, ["Sky Meadow"]),
|
||||||
|
"Hidden Realm: A Moment, Fractured": RoRRegionData(None, ["Hidden Realm: A Moment, Whole"]),
|
||||||
|
"Hidden Realm: A Moment, Whole": RoRRegionData(None, ["Victory"]),
|
||||||
|
"Void Fields": RoRRegionData(None, []),
|
||||||
|
"Victory": RoRRegionData(None, None),
|
||||||
|
"Petrichor V": RoRRegionData(None, ["Victory"]),
|
||||||
|
"Hidden Realm: Bulwark's Ambry": RoRRegionData(None, None),
|
||||||
|
"Hidden Realm: Bazaar Between Time": RoRRegionData(None, ["Void Fields"]),
|
||||||
|
"Hidden Realm: Gilded Coast": RoRRegionData(None, None)
|
||||||
|
}
|
||||||
|
dlc_other_regions: Dict[str, RoRRegionData] = {
|
||||||
|
"The Planetarium": RoRRegionData(None, ["Victory"]),
|
||||||
|
"Void Locus": RoRRegionData(None, ["The Planetarium"])
|
||||||
|
}
|
||||||
|
# Totals of each item
|
||||||
|
chests = int(multiworld.chests_per_stage[player])
|
||||||
|
shrines = int(multiworld.shrines_per_stage[player])
|
||||||
|
scavengers = int(multiworld.scavengers_per_stage[player])
|
||||||
|
scanners = int(multiworld.scanner_per_stage[player])
|
||||||
|
newt = int(multiworld.altars_per_stage[player])
|
||||||
|
all_location_regions = {**non_dlc_regions}
|
||||||
|
if multiworld.dlc_sotv[player]:
|
||||||
|
all_location_regions = {**non_dlc_regions, **dlc_regions}
|
||||||
|
|
||||||
|
# Locations
|
||||||
|
for key in all_location_regions:
|
||||||
|
if key == "Menu":
|
||||||
|
continue
|
||||||
|
# Chests
|
||||||
|
for i in range(0, chests):
|
||||||
|
all_location_regions[key].locations.append(f"{key}: Chest {i + 1}")
|
||||||
|
# Shrines
|
||||||
|
for i in range(0, shrines):
|
||||||
|
all_location_regions[key].locations.append(f"{key}: Shrine {i + 1}")
|
||||||
|
# Scavengers
|
||||||
|
if scavengers > 0:
|
||||||
|
for i in range(0, scavengers):
|
||||||
|
all_location_regions[key].locations.append(f"{key}: Scavenger {i + 1}")
|
||||||
|
# Radio Scanners
|
||||||
|
if scanners > 0:
|
||||||
|
for i in range(0, scanners):
|
||||||
|
all_location_regions[key].locations.append(f"{key}: Radio Scanner {i + 1}")
|
||||||
|
# Newt Altars
|
||||||
|
if newt > 0:
|
||||||
|
for i in range(0, newt):
|
||||||
|
all_location_regions[key].locations.append(f"{key}: Newt Altar {i + 1}")
|
||||||
|
regions_pool: Dict = {**all_location_regions, **other_regions}
|
||||||
|
|
||||||
|
# DLC Locations
|
||||||
|
if multiworld.dlc_sotv[player]:
|
||||||
|
non_dlc_regions["Menu"].region_exits.append("Siphoned Forest")
|
||||||
|
other_regions["OrderedStage_2"].region_exits.append("Aphelian Sanctuary")
|
||||||
|
other_regions["OrderedStage_3"].region_exits.append("Sulfur Pools")
|
||||||
|
other_regions["Commencement"].region_exits.append("The Planetarium")
|
||||||
|
other_regions["Void Fields"].region_exits.append("Void Locus")
|
||||||
|
regions_pool: Dict = {**all_location_regions, **other_regions, **dlc_other_regions}
|
||||||
|
|
||||||
|
# Create all the regions
|
||||||
|
for name, data in regions_pool.items():
|
||||||
|
multiworld.regions.append(create_region(multiworld, player, name, data))
|
||||||
|
|
||||||
|
# Connect all the regions to their exits
|
||||||
|
for name, data in regions_pool.items():
|
||||||
|
create_connections_in_regions(multiworld, player, name, data)
|
||||||
|
|
||||||
|
|
||||||
|
def create_region(multiworld: MultiWorld, player: int, name: str, data: RoRRegionData):
|
||||||
|
region = Region(name, RegionType.Generic, name, player, multiworld)
|
||||||
|
if data.locations:
|
||||||
|
for location_name in data.locations:
|
||||||
|
location_data = location_table.get(location_name)
|
||||||
|
location = RiskOfRainLocation(player, location_name, location_data, region)
|
||||||
|
region.locations.append(location)
|
||||||
|
|
||||||
|
return region
|
||||||
|
|
||||||
|
|
||||||
|
def create_connections_in_regions(multiworld: MultiWorld, player: int, name: str, data: RoRRegionData):
|
||||||
|
region = multiworld.get_region(name, player)
|
||||||
|
if data.region_exits:
|
||||||
|
for region_exit in data.region_exits:
|
||||||
|
r_exit_stage = Entrance(player, region_exit, region)
|
||||||
|
exit_region = multiworld.get_region(region_exit, player)
|
||||||
|
r_exit_stage.connect(exit_region)
|
||||||
|
region.exits.append(r_exit_stage)
|
|
@ -0,0 +1,118 @@
|
||||||
|
from typing import Dict, List, TypeVar
|
||||||
|
|
||||||
|
# TODO probably move to Locations
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
environment_vanilla_orderedstage_2_table: Dict[str, int] = {
|
||||||
|
"Abandoned Aqueduct": 17, # goolake
|
||||||
|
"Wetland Aspect": 12, # foggyswamp
|
||||||
|
}
|
||||||
|
environment_vanilla_orderedstage_3_table: Dict[str, int] = {
|
||||||
|
"Rallypoint Delta": 13, # frozenwall
|
||||||
|
"Scorched Acres": 47, # wispgraveyard
|
||||||
|
}
|
||||||
|
environment_vanilla_orderedstage_4_table: Dict[str, int] = {
|
||||||
|
"Abyssal Depths": 10, # dampcavesimple
|
||||||
|
"Siren's Call": 37, # shipgraveyard
|
||||||
|
"Sundered Grove": 35, # rootjungle
|
||||||
|
}
|
||||||
|
environment_vanilla_orderedstage_5_table: Dict[str, int] = {
|
||||||
|
"Sky Meadow": 38, # skymeadow
|
||||||
|
}
|
||||||
|
|
||||||
|
environment_vanilla_hidden_realm_table: Dict[str, int] = {
|
||||||
|
"Hidden Realm: Bulwark's Ambry": 5, # artifactworld
|
||||||
|
"Hidden Realm: Bazaar Between Time": 6, # bazaar
|
||||||
|
"Hidden Realm: Gilded Coast": 14, # goldshores
|
||||||
|
"Hidden Realm: A Moment, Whole": 27, # limbo
|
||||||
|
"Hidden Realm: A Moment, Fractured": 33, # mysteryspace
|
||||||
|
}
|
||||||
|
|
||||||
|
environment_vanilla_special_table: Dict[str, int] = {
|
||||||
|
"Void Fields": 4, # arena
|
||||||
|
"Commencement": 32, # moon2
|
||||||
|
}
|
||||||
|
|
||||||
|
environment_sotv_orderedstage_1_table: Dict[str, int] = {
|
||||||
|
"Siphoned Forest": 39, # snowyforest
|
||||||
|
}
|
||||||
|
environment_sotv_orderedstage_2_table: Dict[str, int] = {
|
||||||
|
"Aphelian Sanctuary": 3, # ancientloft
|
||||||
|
}
|
||||||
|
environment_sotv_orderedstage_3_table: Dict[str, int] = {
|
||||||
|
"Sulfur Pools": 41, # sulfurpools
|
||||||
|
}
|
||||||
|
environment_sotv_orderedstage_4_table: Dict[str, int] = { }
|
||||||
|
environment_sotv_orderedstage_5_table: Dict[str, int] = { }
|
||||||
|
|
||||||
|
# TODO idk much and idc much about simulacrum, is there a forced order or something?
|
||||||
|
environment_sotv_simulacrum_table: Dict[str, int] = {
|
||||||
|
"The Simulacrum (Aphelian Sanctuary)": 20, # itancientloft
|
||||||
|
"The Simulacrum (Abyssal Depths)": 21, # itdampcave
|
||||||
|
"The Simulacrum (Rallypoint Delta)": 22, # itfrozenwall
|
||||||
|
"The Simulacrum (Titanic Plains)": 23, # itgolemplains
|
||||||
|
"The Simulacrum (Abandoned Aqueduct)": 24, # itgoolake
|
||||||
|
"The Simulacrum (Commencement)": 25, # itmoon
|
||||||
|
"The Simulacrum (Sky Meadow)": 26, # itskymeadow
|
||||||
|
}
|
||||||
|
|
||||||
|
environment_sotv_special_table: Dict[str, int] = {
|
||||||
|
"Void Locus": 45, # voidstage
|
||||||
|
"The Planetarium": 46, # voidraid
|
||||||
|
}
|
||||||
|
|
||||||
|
X = TypeVar("X")
|
||||||
|
Y = TypeVar("Y")
|
||||||
|
|
||||||
|
|
||||||
|
def compress_dict_list_horizontal(list_of_dict: List[Dict[X, Y]]) -> Dict[X, Y]:
|
||||||
|
"""Combine all dictionaries in a list together into one dictionary."""
|
||||||
|
compressed: Dict[X,Y] = {}
|
||||||
|
for individual in list_of_dict: compressed.update(individual)
|
||||||
|
return compressed
|
||||||
|
|
||||||
|
def collapse_dict_list_vertical(list_of_dict1: List[Dict[X, Y]], *args: List[Dict[X, Y]]) -> List[Dict[X, Y]]:
|
||||||
|
"""Combine all parallel dictionaries in lists together to make a new list of dictionaries of the same length."""
|
||||||
|
# find the length of the longest list
|
||||||
|
length = len(list_of_dict1)
|
||||||
|
for list_of_dictN in args:
|
||||||
|
length = max(length, len(list_of_dictN))
|
||||||
|
|
||||||
|
# create a combined list with a length the same as the longest list
|
||||||
|
collapsed = [{}] * (length)
|
||||||
|
# The reason the list_of_dict1 is not directly used to make collapsed is
|
||||||
|
# side effects can occur if all the dictionaries are not manually unioned.
|
||||||
|
|
||||||
|
# merge contents from list_of_dict1
|
||||||
|
for i in range(len(list_of_dict1)):
|
||||||
|
collapsed[i] = {**collapsed[i], **list_of_dict1[i]}
|
||||||
|
|
||||||
|
# merge contents of remaining lists_of_dicts
|
||||||
|
for list_of_dictN in args:
|
||||||
|
for i in range(len(list_of_dictN)):
|
||||||
|
collapsed[i] = {**collapsed[i], **list_of_dictN[i]}
|
||||||
|
|
||||||
|
return collapsed
|
||||||
|
|
||||||
|
# TODO potentially these should only be created when they are directly referenced (unsure of the space/time cost of creating these initially)
|
||||||
|
|
||||||
|
environment_vanilla_orderedstages_table = [ environment_vanilla_orderedstage_1_table, environment_vanilla_orderedstage_2_table, environment_vanilla_orderedstage_3_table, environment_vanilla_orderedstage_4_table, environment_vanilla_orderedstage_5_table ]
|
||||||
|
environment_vanilla_table = {**compress_dict_list_horizontal(environment_vanilla_orderedstages_table), **environment_vanilla_hidden_realm_table, **environment_vanilla_special_table}
|
||||||
|
|
||||||
|
environment_sotv_orderedstages_table = [ environment_sotv_orderedstage_1_table, environment_sotv_orderedstage_2_table, environment_sotv_orderedstage_3_table, environment_sotv_orderedstage_4_table, environment_sotv_orderedstage_5_table ]
|
||||||
|
environment_sotv_non_simulacrum_table = {**compress_dict_list_horizontal(environment_sotv_orderedstages_table), **environment_sotv_special_table}
|
||||||
|
environment_sotv_table = {**environment_sotv_non_simulacrum_table}
|
||||||
|
|
||||||
|
environment_non_orderedstages_table = {**environment_vanilla_hidden_realm_table, **environment_vanilla_special_table, **environment_sotv_simulacrum_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}
|
||||||
|
|
||||||
|
|
||||||
|
def shift_by_offset(dictionary: Dict[str, int], offset:int) -> Dict[str, int]:
|
||||||
|
"""Shift all indexes in a dictionary by an offset"""
|
||||||
|
return {name:index+offset for name, index in dictionary.items()}
|
|
@ -1,33 +1,154 @@
|
||||||
from BaseClasses import MultiWorld
|
from BaseClasses import MultiWorld, CollectionState
|
||||||
from worlds.generic.Rules import set_rule, add_rule
|
from worlds.generic.Rules import set_rule, add_rule
|
||||||
|
from .Locations import orderedstage_location
|
||||||
|
from .RoR2Environments import environment_vanilla_orderedstages_table, environment_sotv_orderedstages_table, \
|
||||||
|
environment_orderedstages_table
|
||||||
|
|
||||||
|
|
||||||
def set_rules(world: MultiWorld, player: int) -> None:
|
# Rule to see if it has access to the previous stage
|
||||||
total_locations = world.total_locations[player].value # total locations for current player
|
def has_entrance_access_rule(multiworld: MultiWorld, stage: str, entrance: str, player: int):
|
||||||
|
multiworld.get_entrance(entrance, player).access_rule = \
|
||||||
|
lambda state: state.has(entrance, player) and state.has(stage, player)
|
||||||
|
|
||||||
|
|
||||||
|
# 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):
|
||||||
|
if item_number == 1:
|
||||||
|
multiworld.get_location(f"{environment}: {item_type} {item_number}", player).access_rule = \
|
||||||
|
lambda state: state.has(environment, player)
|
||||||
|
if item_type == "Scavenger":
|
||||||
|
multiworld.get_location(f"{environment}: {item_type} {item_number}", player).access_rule = \
|
||||||
|
lambda state: state.has(environment, player) and state.has("Stage_4", player)
|
||||||
|
else:
|
||||||
|
multiworld.get_location(f"{environment}: {item_type} {item_number}", player).access_rule = \
|
||||||
|
lambda state: check_location(state, environment, player, item_number, item_type)
|
||||||
|
|
||||||
|
|
||||||
|
def check_location(state, environment: str, player: int, item_number: int, item_name: str):
|
||||||
|
return state.can_reach(f"{environment}: {item_name} {item_number - 1}", "Location", player)
|
||||||
|
|
||||||
|
|
||||||
|
# unlock event to next set of stages
|
||||||
|
def get_stage_event(multiworld: MultiWorld, player: int, stage_number: int):
|
||||||
|
if not multiworld.dlc_sotv[player]:
|
||||||
|
environment_name = multiworld.random.choices(list(environment_vanilla_orderedstages_table[stage_number].keys()), k=1)
|
||||||
|
else:
|
||||||
|
environment_name = multiworld.random.choices(list(environment_orderedstages_table[stage_number].keys()), k=1)
|
||||||
|
multiworld.get_location(f"Stage_{stage_number+1}", player).access_rule = \
|
||||||
|
lambda state: get_one_of_the_stages(state, environment_name[0], player)
|
||||||
|
|
||||||
|
|
||||||
|
def get_one_of_the_stages(state: CollectionState, stage: str, player: int):
|
||||||
|
return state.has(stage, player)
|
||||||
|
|
||||||
|
|
||||||
|
def set_rules(multiworld: MultiWorld, player: int) -> None:
|
||||||
|
|
||||||
|
if multiworld.goal[player] == "classic":
|
||||||
|
# classic mode
|
||||||
|
total_locations = multiworld.total_locations[player].value # total locations for current player
|
||||||
|
else:
|
||||||
|
# explore mode
|
||||||
|
total_locations = len(
|
||||||
|
orderedstage_location.get_locations(
|
||||||
|
chests=multiworld.chests_per_stage[player].value,
|
||||||
|
shrines=multiworld.shrines_per_stage[player].value,
|
||||||
|
scavengers=multiworld.scavengers_per_stage[player].value,
|
||||||
|
scanners=multiworld.scanner_per_stage[player].value,
|
||||||
|
altars=multiworld.altars_per_stage[player].value,
|
||||||
|
dlc_sotv=multiworld.dlc_sotv[player].value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
event_location_step = 25 # set an event location at these locations for "spheres"
|
event_location_step = 25 # set an event location at these locations for "spheres"
|
||||||
divisions = total_locations // event_location_step
|
divisions = total_locations // event_location_step
|
||||||
total_revivals = world.worlds[player].total_revivals # pulling this info we calculated in generate_basic
|
total_revivals = multiworld.worlds[player].total_revivals # pulling this info we calculated in generate_basic
|
||||||
|
|
||||||
if divisions:
|
if multiworld.goal[player] == "classic":
|
||||||
for i in range(1, divisions): # since divisions is the floor of total_locations / 25
|
# classic mode
|
||||||
event_loc = world.get_location(f"Pickup{i * event_location_step}", player)
|
if divisions:
|
||||||
set_rule(event_loc,
|
for i in range(1, divisions): # since divisions is the floor of total_locations / 25
|
||||||
lambda state, i=i: state.can_reach(f"ItemPickup{i * event_location_step - 1}", "Location", player))
|
event_loc = multiworld.get_location(f"Pickup{i * event_location_step}", player)
|
||||||
for n in range(i * event_location_step, (i + 1) * event_location_step): # we want to create a rule for each of the 25 locations per division
|
set_rule(event_loc,
|
||||||
if n == i * event_location_step:
|
lambda state, i=i: state.can_reach(f"ItemPickup{i * event_location_step - 1}", "Location", player))
|
||||||
set_rule(world.get_location(f"ItemPickup{n}", player),
|
for n in range(i * event_location_step, (i + 1) * event_location_step): # we want to create a rule for each of the 25 locations per division
|
||||||
lambda state, event_item=event_loc.item.name: state.has(event_item, player))
|
if n == i * event_location_step:
|
||||||
else:
|
set_rule(multiworld.get_location(f"ItemPickup{n}", player),
|
||||||
set_rule(world.get_location(f"ItemPickup{n}", player),
|
lambda state, event_item=event_loc.item.name: state.has(event_item, player))
|
||||||
lambda state, n=n: state.can_reach(f"ItemPickup{n - 1}", "Location", player))
|
else:
|
||||||
for i in range(divisions * event_location_step, total_locations+1):
|
set_rule(multiworld.get_location(f"ItemPickup{n}", player),
|
||||||
set_rule(world.get_location(f"ItemPickup{i}", player),
|
lambda state, n=n: state.can_reach(f"ItemPickup{n - 1}", "Location", player))
|
||||||
lambda state, i=i: state.can_reach(f"ItemPickup{i - 1}", "Location", player))
|
for i in range(divisions * event_location_step, total_locations+1):
|
||||||
set_rule(world.get_location("Victory", player),
|
set_rule(multiworld.get_location(f"ItemPickup{i}", player),
|
||||||
lambda state: state.can_reach(f"ItemPickup{total_locations}", "Location", player))
|
lambda state, i=i: state.can_reach(f"ItemPickup{i - 1}", "Location", player))
|
||||||
if total_revivals or world.start_with_revive[player].value:
|
set_rule(multiworld.get_location("Victory", player),
|
||||||
add_rule(world.get_location("Victory", player),
|
lambda state: state.can_reach(f"ItemPickup{total_locations}", "Location", player))
|
||||||
lambda state: state.has("Dio's Best Friend", player,
|
if total_revivals or multiworld.start_with_revive[player].value:
|
||||||
total_revivals + world.start_with_revive[player]))
|
add_rule(multiworld.get_location("Victory", player),
|
||||||
|
lambda state: state.has("Dio's Best Friend", player,
|
||||||
|
total_revivals + multiworld.start_with_revive[player]))
|
||||||
|
|
||||||
world.completion_condition[player] = lambda state: state.has("Victory", player)
|
elif multiworld.goal[player] == "explore":
|
||||||
|
# When explore_mode is used,
|
||||||
|
# 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.)
|
||||||
|
|
||||||
|
for location in multiworld.get_locations():
|
||||||
|
if location.player != player: continue # ignore all checks that don't belong to this player
|
||||||
|
if "Scavenger" in location.name:
|
||||||
|
add_rule(location, lambda state: state.has("Stage_5", player))
|
||||||
|
# Regions
|
||||||
|
chests = multiworld.chests_per_stage[player]
|
||||||
|
shrines = multiworld.shrines_per_stage[player]
|
||||||
|
newts = multiworld.altars_per_stage[player]
|
||||||
|
scavengers = multiworld.scavengers_per_stage[player]
|
||||||
|
scanners = multiworld.scanner_per_stage[player]
|
||||||
|
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_entrance_access_rule(multiworld, f"Stage_{i}", environment_name, player)
|
||||||
|
get_stage_event(multiworld, player, i)
|
||||||
|
|
||||||
|
if multiworld.dlc_sotv[player]:
|
||||||
|
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_entrance_access_rule(multiworld, f"Stage_{i}", environment_name, player)
|
||||||
|
has_entrance_access_rule(multiworld, f"Sky Meadow", "Hidden Realm: Bulwark's Ambry", player)
|
||||||
|
has_entrance_access_rule(multiworld, f"Hidden Realm: A Moment, Fractured", "Hidden Realm: A Moment, Whole", player)
|
||||||
|
has_entrance_access_rule(multiworld, f"Stage_1", "Hidden Realm: Gilded Coast", player)
|
||||||
|
has_entrance_access_rule(multiworld, f"Stage_1", "Hidden Realm: Bazaar Between Time", player)
|
||||||
|
has_entrance_access_rule(multiworld, f"Hidden Realm: Bazaar Between Time", "Void Fields", player)
|
||||||
|
has_entrance_access_rule(multiworld, f"Stage_5", "Commencement", player)
|
||||||
|
has_entrance_access_rule(multiworld, f"Stage_5", "Hidden Realm: A Moment, Fractured", player)
|
||||||
|
if multiworld.dlc_sotv[player]:
|
||||||
|
has_entrance_access_rule(multiworld, f"Stage_5", "Void Locus", player)
|
||||||
|
has_entrance_access_rule(multiworld, f"Void Locus", "The Planetarium", player)
|
||||||
|
# Win Condition
|
||||||
|
multiworld.completion_condition[player] = lambda state: state.has("Victory", player)
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import string
|
import string
|
||||||
from typing import Dict, List
|
|
||||||
|
|
||||||
from BaseClasses import Entrance, Item, ItemClassification, MultiWorld, Region, RegionType, Tutorial
|
from .Items import RiskOfRainItem, item_table, item_pool_weights, environment_offest
|
||||||
from worlds.AutoWorld import WebWorld, World
|
from .Locations import RiskOfRainLocation, get_classic_item_pickups, item_pickups, orderedstage_location
|
||||||
from .Items import RiskOfRainItem, item_pool_weights, item_table
|
|
||||||
from .Locations import RiskOfRainLocation, item_pickups
|
|
||||||
from .Options import ItemWeights, ror2_options
|
|
||||||
from .Rules import set_rules
|
from .Rules import set_rules
|
||||||
|
from .RoR2Environments import *
|
||||||
|
|
||||||
client_version = 1
|
from BaseClasses import Region, RegionType, Entrance, Item, ItemClassification, MultiWorld, Tutorial
|
||||||
|
from .Options import ror2_options, ItemWeights
|
||||||
|
from worlds.AutoWorld import World, WebWorld
|
||||||
|
from .Regions import create_regions
|
||||||
|
|
||||||
|
|
||||||
class RiskOfWeb(WebWorld):
|
class RiskOfWeb(WebWorld):
|
||||||
|
@ -35,20 +35,58 @@ class RiskOfRainWorld(World):
|
||||||
item_name_to_id = item_table
|
item_name_to_id = item_table
|
||||||
location_name_to_id = item_pickups
|
location_name_to_id = item_pickups
|
||||||
|
|
||||||
data_version = 4
|
data_version = 6
|
||||||
|
required_client_version = (0, 3, 7)
|
||||||
web = RiskOfWeb()
|
web = RiskOfWeb()
|
||||||
total_revivals: int
|
total_revivals: int
|
||||||
|
|
||||||
def generate_early(self) -> None:
|
def generate_early(self) -> None:
|
||||||
# figure out how many revivals should exist in the pool
|
# figure out how many revivals should exist in the pool
|
||||||
|
if self.multiworld.goal[self.player] == "classic":
|
||||||
|
total_locations = self.multiworld.total_locations[self.player].value
|
||||||
|
else:
|
||||||
|
total_locations = len(
|
||||||
|
orderedstage_location.get_locations(
|
||||||
|
chests=self.multiworld.chests_per_stage[self.player].value,
|
||||||
|
shrines=self.multiworld.shrines_per_stage[self.player].value,
|
||||||
|
scavengers=self.multiworld.scavengers_per_stage[self.player].value,
|
||||||
|
scanners=self.multiworld.scanner_per_stage[self.player].value,
|
||||||
|
altars=self.multiworld.altars_per_stage[self.player].value,
|
||||||
|
dlc_sotv=self.multiworld.dlc_sotv[self.player].value
|
||||||
|
)
|
||||||
|
)
|
||||||
self.total_revivals = int(self.multiworld.total_revivals[self.player].value / 100 *
|
self.total_revivals = int(self.multiworld.total_revivals[self.player].value / 100 *
|
||||||
self.multiworld.total_locations[self.player].value)
|
total_locations)
|
||||||
|
# self.total_revivals = self.multiworld.total_revivals[self.player].value
|
||||||
def generate_basic(self) -> None:
|
|
||||||
# shortcut for starting_inventory... The start_with_revive option lets you start with a Dio's Best Friend
|
|
||||||
if self.multiworld.start_with_revive[self.player].value:
|
if self.multiworld.start_with_revive[self.player].value:
|
||||||
|
self.total_revivals -= 1
|
||||||
|
|
||||||
|
def create_items(self) -> None:
|
||||||
|
# shortcut for starting_inventory... The start_with_revive option lets you start with a Dio's Best Friend
|
||||||
|
if self.multiworld.start_with_revive[self.player]:
|
||||||
self.multiworld.push_precollected(self.multiworld.create_item("Dio's Best Friend", self.player))
|
self.multiworld.push_precollected(self.multiworld.create_item("Dio's Best Friend", self.player))
|
||||||
|
|
||||||
|
environments_pool = {}
|
||||||
|
# only mess with the environments if they are set as items
|
||||||
|
if self.multiworld.goal[self.player] == "explore":
|
||||||
|
|
||||||
|
# figure out all available ordered stages for each tier
|
||||||
|
environment_available_orderedstages_table = environment_vanilla_orderedstages_table
|
||||||
|
if self.multiworld.dlc_sotv[self.player]:
|
||||||
|
environment_available_orderedstages_table = collapse_dict_list_vertical(environment_available_orderedstages_table, environment_sotv_orderedstages_table)
|
||||||
|
|
||||||
|
environments_pool = shift_by_offset(environment_vanilla_table, environment_offest)
|
||||||
|
|
||||||
|
if self.multiworld.dlc_sotv[self.player]:
|
||||||
|
environment_offset_table = shift_by_offset(environment_sotv_table, environment_offest)
|
||||||
|
environments_pool = {**environments_pool, **environment_offset_table}
|
||||||
|
environments_to_precollect = 5 if self.multiworld.begin_with_loop[self.player].value else 1
|
||||||
|
# percollect environments for each stage (or just stage 1)
|
||||||
|
for i in range(environments_to_precollect):
|
||||||
|
unlock = self.multiworld.random.choices(list(environment_available_orderedstages_table[i].keys()), k=1)
|
||||||
|
self.multiworld.push_precollected(self.create_item(unlock[0]))
|
||||||
|
environments_pool.pop(unlock[0])
|
||||||
|
|
||||||
# if presets are enabled generate junk_pool from the selected preset
|
# if presets are enabled generate junk_pool from the selected preset
|
||||||
pool_option = self.multiworld.item_weights[self.player].value
|
pool_option = self.multiworld.item_weights[self.player].value
|
||||||
junk_pool: Dict[str, int] = {}
|
junk_pool: Dict[str, int] = {}
|
||||||
|
@ -70,61 +108,120 @@ class RiskOfRainWorld(World):
|
||||||
"Legendary Item": self.multiworld.legendary_item[self.player].value,
|
"Legendary Item": self.multiworld.legendary_item[self.player].value,
|
||||||
"Boss Item": self.multiworld.boss_item[self.player].value,
|
"Boss Item": self.multiworld.boss_item[self.player].value,
|
||||||
"Lunar Item": self.multiworld.lunar_item[self.player].value,
|
"Lunar Item": self.multiworld.lunar_item[self.player].value,
|
||||||
|
"Void Item": self.multiworld.void_item[self.player].value,
|
||||||
"Equipment": self.multiworld.equipment[self.player].value
|
"Equipment": self.multiworld.equipment[self.player].value
|
||||||
}
|
}
|
||||||
|
|
||||||
# remove lunar items from the pool if they're disabled in the yaml unless lunartic is rolled
|
# remove lunar items from the pool if they're disabled in the yaml unless lunartic is rolled
|
||||||
if not (self.multiworld.enable_lunar[self.player] or pool_option == ItemWeights.option_lunartic):
|
if not self.multiworld.enable_lunar[self.player] or pool_option == ItemWeights.option_lunartic:
|
||||||
junk_pool.pop("Lunar Item")
|
junk_pool.pop("Lunar Item")
|
||||||
|
# remove void items from the pool
|
||||||
|
if not self.multiworld.dlc_sotv[self.player] or pool_option == ItemWeights.option_void:
|
||||||
|
junk_pool.pop("Void Item")
|
||||||
|
|
||||||
# Generate item pool
|
# Generate item pool
|
||||||
itempool: List = []
|
itempool: List = []
|
||||||
# Add revive items for the player
|
# Add revive items for the player
|
||||||
itempool += ["Dio's Best Friend"] * self.total_revivals
|
itempool += ["Dio's Best Friend"] * self.total_revivals
|
||||||
|
|
||||||
|
for env_name, _ in environments_pool.items():
|
||||||
|
itempool += [env_name]
|
||||||
|
|
||||||
|
# precollected environments are popped from the pool so counting like this is valid
|
||||||
|
nonjunk_item_count = self.total_revivals + len(environments_pool)
|
||||||
|
if self.multiworld.goal[self.player] == "classic":
|
||||||
|
# classic mode
|
||||||
|
total_locations = self.multiworld.total_locations[self.player].value
|
||||||
|
else:
|
||||||
|
# explore mode
|
||||||
|
total_locations = len(
|
||||||
|
orderedstage_location.get_locations(
|
||||||
|
chests=self.multiworld.chests_per_stage[self.player].value,
|
||||||
|
shrines=self.multiworld.shrines_per_stage[self.player].value,
|
||||||
|
scavengers=self.multiworld.scavengers_per_stage[self.player].value,
|
||||||
|
scanners=self.multiworld.scanner_per_stage[self.player].value,
|
||||||
|
altars=self.multiworld.altars_per_stage[self.player].value,
|
||||||
|
dlc_sotv=self.multiworld.dlc_sotv[self.player].value
|
||||||
|
)
|
||||||
|
)
|
||||||
|
junk_item_count = total_locations - nonjunk_item_count
|
||||||
# Fill remaining items with randomly generated junk
|
# Fill remaining items with randomly generated junk
|
||||||
itempool += self.multiworld.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()),
|
itempool += self.multiworld.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()),
|
||||||
k=self.multiworld.total_locations[self.player].value - self.total_revivals)
|
k=junk_item_count)
|
||||||
|
|
||||||
# Convert itempool into real items
|
# Convert itempool into real items
|
||||||
itempool = list(map(lambda name: self.create_item(name), itempool))
|
itempool = list(map(lambda name: self.create_item(name), itempool))
|
||||||
|
|
||||||
self.multiworld.itempool += itempool
|
self.multiworld.itempool += itempool
|
||||||
|
|
||||||
def set_rules(self) -> None:
|
def set_rules(self) -> None:
|
||||||
set_rules(self.multiworld, self.player)
|
set_rules(self.multiworld, self.player)
|
||||||
|
|
||||||
def create_regions(self) -> None:
|
def create_regions(self) -> None:
|
||||||
menu = create_region(self.multiworld, self.player, "Menu")
|
|
||||||
petrichor = create_region(self.multiworld, self.player, "Petrichor V",
|
|
||||||
[f"ItemPickup{i + 1}" for i in range(self.multiworld.total_locations[self.player].value)])
|
|
||||||
|
|
||||||
connection = Entrance(self.player, "Lobby", menu)
|
if self.multiworld.goal[self.player] == "classic":
|
||||||
menu.exits.append(connection)
|
# classic mode
|
||||||
connection.connect(petrichor)
|
menu = create_region(self.multiworld, self.player, "Menu")
|
||||||
|
self.multiworld.regions.append(menu)
|
||||||
|
# By using a victory region, we can define it as being connected to by several regions
|
||||||
|
# which can then determine the availability of the victory.
|
||||||
|
victory_region = create_region(self.multiworld, self.player, "Victory")
|
||||||
|
self.multiworld.regions.append(victory_region)
|
||||||
|
petrichor = create_region(self.multiworld, self.player, "Petrichor V",
|
||||||
|
get_classic_item_pickups(self.multiworld.total_locations[self.player].value))
|
||||||
|
self.multiworld.regions.append(petrichor)
|
||||||
|
|
||||||
self.multiworld.regions += [menu, petrichor]
|
# classic mode can get to victory from the beginning of the game
|
||||||
|
to_victory = Entrance(self.player, "beating game", petrichor)
|
||||||
|
petrichor.exits.append(to_victory)
|
||||||
|
to_victory.connect(victory_region)
|
||||||
|
|
||||||
|
connection = Entrance(self.player, "Lobby", menu)
|
||||||
|
menu.exits.append(connection)
|
||||||
|
connection.connect(petrichor)
|
||||||
|
else:
|
||||||
|
# explore mode
|
||||||
|
create_regions(self.multiworld, self.player)
|
||||||
|
|
||||||
create_events(self.multiworld, self.player)
|
create_events(self.multiworld, self.player)
|
||||||
|
|
||||||
def fill_slot_data(self):
|
def fill_slot_data(self):
|
||||||
return {
|
return {
|
||||||
"itemPickupStep": self.multiworld.item_pickup_step[self.player].value,
|
"itemPickupStep": self.multiworld.item_pickup_step[self.player].value,
|
||||||
|
"shrineUseStep": self.multiworld.shrine_use_step[self.player].value,
|
||||||
|
"goal": self.multiworld.goal[self.player].value,
|
||||||
"seed": "".join(self.multiworld.per_slot_randoms[self.player].choice(string.digits) for _ in range(16)),
|
"seed": "".join(self.multiworld.per_slot_randoms[self.player].choice(string.digits) for _ in range(16)),
|
||||||
"totalLocations": self.multiworld.total_locations[self.player].value,
|
"totalLocations": self.multiworld.total_locations[self.player].value,
|
||||||
|
"chestsPerStage": self.multiworld.chests_per_stage[self.player].value,
|
||||||
|
"shrinesPerStage": self.multiworld.shrines_per_stage[self.player].value,
|
||||||
|
"scavengersPerStage": self.multiworld.scavengers_per_stage[self.player].value,
|
||||||
|
"scannerPerStage": self.multiworld.scanner_per_stage[self.player].value,
|
||||||
|
"altarsPerStage": self.multiworld.altars_per_stage[self.player].value,
|
||||||
"totalRevivals": self.multiworld.total_revivals[self.player].value,
|
"totalRevivals": self.multiworld.total_revivals[self.player].value,
|
||||||
"startWithDio": self.multiworld.start_with_revive[self.player].value,
|
"startWithDio": self.multiworld.start_with_revive[self.player].value,
|
||||||
"FinalStageDeath": self.multiworld.final_stage_death[self.player].value
|
"finalStageDeath": self.multiworld.final_stage_death[self.player].value,
|
||||||
|
"deathLink": self.multiworld.death_link[self.player].value,
|
||||||
}
|
}
|
||||||
|
|
||||||
def create_item(self, name: str) -> Item:
|
def create_item(self, name: str) -> Item:
|
||||||
item_id = item_table[name]
|
item_id = item_table[name]
|
||||||
|
classification = ItemClassification.filler
|
||||||
if name == "Dio's Best Friend":
|
if name == "Dio's Best Friend":
|
||||||
classification = ItemClassification.progression
|
classification = ItemClassification.progression
|
||||||
elif name in {"Equipment", "Legendary Item"}:
|
elif name in {"Legendary Item", "Boss Item"}:
|
||||||
classification = ItemClassification.useful
|
classification = ItemClassification.useful
|
||||||
else:
|
elif name == "Lunar Item":
|
||||||
classification = ItemClassification.filler
|
classification = ItemClassification.trap
|
||||||
|
|
||||||
|
# Only check for an item to be a environment unlock if those are known to be in the pool.
|
||||||
|
# This should shave down comparions.
|
||||||
|
|
||||||
|
elif name in environment_ALL_table.keys():
|
||||||
|
if name in {"Void Fields", "Hidden Realm: Bazaar Between Time", "Hidden Realm: Bulwark's Ambry",
|
||||||
|
"Hidden Realm: Gilded Coast,"}:
|
||||||
|
classification = ItemClassification.useful
|
||||||
|
else:
|
||||||
|
classification = ItemClassification.progression
|
||||||
|
|
||||||
item = RiskOfRainItem(name, classification, item_id, self.player)
|
item = RiskOfRainItem(name, classification, item_id, self.player)
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
@ -135,23 +232,31 @@ def create_events(world: MultiWorld, player: int) -> None:
|
||||||
if total_locations / 25 == num_of_events:
|
if total_locations / 25 == num_of_events:
|
||||||
num_of_events -= 1
|
num_of_events -= 1
|
||||||
world_region = world.get_region("Petrichor V", player)
|
world_region = world.get_region("Petrichor V", player)
|
||||||
|
if world.goal[player] == "classic":
|
||||||
|
# only setup Pickups when using classic_mode
|
||||||
|
for i in range(num_of_events):
|
||||||
|
event_loc = RiskOfRainLocation(player, f"Pickup{(i + 1) * 25}", None, world_region)
|
||||||
|
event_loc.place_locked_item(RiskOfRainItem(f"Pickup{(i + 1) * 25}", ItemClassification.progression, None, player))
|
||||||
|
event_loc.access_rule = \
|
||||||
|
lambda state, i=i: state.can_reach(f"ItemPickup{((i + 1) * 25) - 1}", "Location", player)
|
||||||
|
world_region.locations.append(event_loc)
|
||||||
|
elif world.goal[player] == "explore":
|
||||||
|
for n in range(1, 6):
|
||||||
|
|
||||||
for i in range(num_of_events):
|
event_region = world.get_region(f"OrderedStage_{n}", player)
|
||||||
event_loc = RiskOfRainLocation(player, f"Pickup{(i + 1) * 25}", None, world_region)
|
event_loc = RiskOfRainLocation(player, f"Stage_{n}", None, event_region)
|
||||||
event_loc.place_locked_item(RiskOfRainItem(f"Pickup{(i + 1) * 25}", ItemClassification.progression, None, player))
|
event_loc.place_locked_item(RiskOfRainItem(f"Stage_{n}", ItemClassification.progression, None, player))
|
||||||
event_loc.access_rule(lambda state, i=i: state.can_reach(f"ItemPickup{((i + 1) * 25) - 1}", player))
|
event_loc.show_in_spoiler = False
|
||||||
world_region.locations.append(event_loc)
|
event_region.locations.append(event_loc)
|
||||||
|
|
||||||
victory_event = RiskOfRainLocation(player, "Victory", None, world_region)
|
victory_region = world.get_region("Victory", player)
|
||||||
|
victory_event = RiskOfRainLocation(player, "Victory", None, victory_region)
|
||||||
victory_event.place_locked_item(RiskOfRainItem("Victory", ItemClassification.progression, None, player))
|
victory_event.place_locked_item(RiskOfRainItem("Victory", ItemClassification.progression, None, player))
|
||||||
world_region.locations.append(victory_event)
|
world_region.locations.append(victory_event)
|
||||||
|
|
||||||
|
|
||||||
def create_region(world: MultiWorld, player: int, name: str, locations: List[str] = None) -> Region:
|
def create_region(world: MultiWorld, player: int, name: str, locations: Dict[str, int] = {}) -> Region:
|
||||||
ret = Region(name, RegionType.Generic, name, player, world)
|
ret = Region(name, RegionType.Generic, name, player, world)
|
||||||
if locations:
|
for location_name, location_id in locations.items():
|
||||||
for location in locations:
|
ret.locations.append(RiskOfRainLocation(player, location_name, location_id, ret))
|
||||||
loc_id = item_pickups[location]
|
|
||||||
location = RiskOfRainLocation(player, location, loc_id, ret)
|
|
||||||
ret.locations.append(location)
|
|
||||||
return ret
|
return ret
|
||||||
|
|
|
@ -12,19 +12,34 @@ functionality in which certain chests (made clear via a location check progress
|
||||||
multiworld. The items that _would have been_ in those chests will be returned to the Risk of Rain player via grants by
|
multiworld. The items that _would have been_ in those chests will be returned to the Risk of Rain player via grants by
|
||||||
other players in other worlds.
|
other players in other worlds.
|
||||||
|
|
||||||
|
There are two modes in risk of rain. Classic Mode and Explore Mode
|
||||||
|
|
||||||
|
Classic Mode:
|
||||||
|
|
||||||
|
- Classic mode implements pure multiworld
|
||||||
|
functionality in which certain chests (made clear via a location check progress bar) will send an item out to the
|
||||||
|
multiworld. The items that _would have been_ in those chests will be returned to the Risk of Rain player via grants by
|
||||||
|
other players in other worlds.
|
||||||
|
|
||||||
|
Explore Mode:
|
||||||
|
|
||||||
|
- Just like in Classic mode chests will send out an item to the multiworld. The difference is that each environment
|
||||||
|
will have a set amount that can be sent out and shrines along with other things that will need to be checked.
|
||||||
|
Also, each environment is an item and, you'll need it to be able to access it.
|
||||||
|
|
||||||
## What is the goal of Risk of Rain 2 in Archipelago?
|
## What is the goal of Risk of Rain 2 in Archipelago?
|
||||||
|
|
||||||
Just like in the original game, any way to "beat the game or obliterate" counts as a win. By default, if you die while
|
Just like in the original game, any way to "beat the game or obliterate" counts as a win. There is a setting that
|
||||||
on a final boss stage, that also counts as a win. (You can turn this off in your player settings.) **You do not need to
|
if you die while on a final boss stage, that also counts as a win.(You can turn this on in your player settings.)
|
||||||
complete all the location checks** to win; any item you don't collect is automatically sent out to the multiworld when
|
**You do not need to complete all the location checks** to win; any item you don't collect may be released if the
|
||||||
you meet your goal.
|
server options allow.
|
||||||
|
|
||||||
If you die before you accomplish your goal, you can start a new run. You will start the run with any items that you
|
If you die before you accomplish your goal, you can start a new run. You will start the run with any items that you
|
||||||
received from other players. Any items that you picked up the "normal" way will be lost.
|
received from other players. Any items that you picked up the "normal" way will be lost.
|
||||||
|
|
||||||
Note, you can play Simulacrum mode as part of an Archipelago, but you can't achieve any of the victory conditions in
|
Note, you can play Simulacrum mode as part of an Archipelago, but you can't achieve any of the victory conditions in
|
||||||
Simulacrum. So you could, for example, collect most of your items through a Simulacrum run, then finish a normal mode
|
Simulacrum. So you could, for example, collect most of your items through a Simulacrum run(only works in classic mode),
|
||||||
run while keeping the items you received via the multiworld.
|
then finish a normal mode run while keeping the items you received via the multiworld.
|
||||||
|
|
||||||
## Can you play multiplayer?
|
## Can you play multiplayer?
|
||||||
|
|
||||||
|
@ -38,6 +53,8 @@ settings apply, so each Risk of Rain 2 player slot in the multiworld needs to be
|
||||||
for example, have two players trade off hosting and making progress on each other's player slot, but a single co-op
|
for example, have two players trade off hosting and making progress on each other's player slot, but a single co-op
|
||||||
instance can't make progress towards multiple player slots in the multiworld.
|
instance can't make progress towards multiple player slots in the multiworld.
|
||||||
|
|
||||||
|
Explore mode is untested in multiplayer and will likely not work until a later release.
|
||||||
|
|
||||||
## What Risk of Rain items can appear in other players' worlds?
|
## What Risk of Rain items can appear in other players' worlds?
|
||||||
|
|
||||||
The Risk of Rain items are:
|
The Risk of Rain items are:
|
||||||
|
@ -49,6 +66,7 @@ The Risk of Rain items are:
|
||||||
* `Lunar Item` (Blue items)
|
* `Lunar Item` (Blue items)
|
||||||
* `Equipment` (Orange items)
|
* `Equipment` (Orange items)
|
||||||
* `Dio's Best Friend` (Used if you set the YAML setting `total_revives_available` above `0`)
|
* `Dio's Best Friend` (Used if you set the YAML setting `total_revives_available` above `0`)
|
||||||
|
* `Void Item` (Purple items) (needs dlc_sotv: enabled)
|
||||||
|
|
||||||
Each item grants you a random in-game item from the category it belongs to.
|
Each item grants you a random in-game item from the category it belongs to.
|
||||||
|
|
||||||
|
@ -57,6 +75,29 @@ in-game item of that tier will appear in the Risk of Rain player's inventory. If
|
||||||
the player already has an equipment item equipped then the _item that was equipped_ will be dropped on the ground and _
|
the player already has an equipment item equipped then the _item that was equipped_ will be dropped on the ground and _
|
||||||
the new equipment_ will take it's place. (If you want the old one back, pick it up.)
|
the new equipment_ will take it's place. (If you want the old one back, pick it up.)
|
||||||
|
|
||||||
|
Explore Mode items are:
|
||||||
|
|
||||||
|
* `Titanic Plains (1)`, `Titanic Plains (2)`, `Distant Roost (1)`, `Distant Roost (2)`
|
||||||
|
* `Abandoned Aqueduct`, `Wetland Aspect`
|
||||||
|
* `Rallypoint Delta`, `Scorched Acres`
|
||||||
|
* `Abyssal Depths`, `Siren's Call`, `Sundered Grove`
|
||||||
|
* `Sky Meadow`
|
||||||
|
* `Commencement`
|
||||||
|
* `All the Hidden Realms`
|
||||||
|
|
||||||
|
Dlc_Sotv items
|
||||||
|
* `Siphoned Forest`
|
||||||
|
* `Aphelian Sanctuary`
|
||||||
|
* `Sulfur Pools`
|
||||||
|
* `Void Locus`
|
||||||
|
|
||||||
|
When a explore item is granted it will unlock that environment and will now be accessible to progress to victory! The
|
||||||
|
game will still pick randomly which environment is next but it will first check to see if they are available. If you have
|
||||||
|
them unlocked it will weight the game to have a ***higher chance*** to go to one you have checks versus one you have
|
||||||
|
already completed. You will still not be able to goto a stage 3 environment from a stage 1 environment.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### How many items are there?
|
### How many items are there?
|
||||||
|
|
||||||
Since a Risk of Rain 2 run can go on indefinitely, you have to configure how many collectible items (also known as
|
Since a Risk of Rain 2 run can go on indefinitely, you have to configure how many collectible items (also known as
|
||||||
|
@ -65,6 +106,10 @@ to 250** items. The number of items will be randomized between all players, so y
|
||||||
item pickup step based on how many items the other players in the multiworld have. (Around 100 seems to be a good
|
item pickup step based on how many items the other players in the multiworld have. (Around 100 seems to be a good
|
||||||
ballpark if you want to have a similar number of items to most other games.)
|
ballpark if you want to have a similar number of items to most other games.)
|
||||||
|
|
||||||
|
In explore mode the amount of checks base on how many **chests, shrines, scavengers, radio scanners and, newt altars**
|
||||||
|
are in the pool. With just the base game the numbers are **52 to 516** and with the dlc its **60 to 660** with
|
||||||
|
everything on default being **216**
|
||||||
|
|
||||||
After you have completed the specified number of checks, you won't send anything else to the multiworld. You can
|
After you have completed the specified number of checks, you won't send anything else to the multiworld. You can
|
||||||
receive up to the specified number of randomized items from the multiworld as the players find them. In either case,
|
receive up to the specified number of randomized items from the multiworld as the players find them. In either case,
|
||||||
you can continue to collect items as normal in Risk of Rain 2 if you've already found all your location checks.
|
you can continue to collect items as normal in Risk of Rain 2 if you've already found all your location checks.
|
||||||
|
|
Loading…
Reference in New Issue