RoR2: 1.3.0 content update (#2425)
This commit is contained in:
parent
01b566b798
commit
79406faf27
|
@ -1,194 +0,0 @@
|
|||
from BaseClasses import Item
|
||||
from .Options import ItemWeights
|
||||
from .RoR2Environments import *
|
||||
|
||||
|
||||
class RiskOfRainItem(Item):
|
||||
game: str = "Risk of Rain 2"
|
||||
|
||||
|
||||
# 37000 - 37699, 38000
|
||||
item_table: Dict[str, int] = {
|
||||
"Dio's Best Friend": 37001,
|
||||
"Common Item": 37002,
|
||||
"Uncommon Item": 37003,
|
||||
"Legendary Item": 37004,
|
||||
"Boss Item": 37005,
|
||||
"Lunar Item": 37006,
|
||||
"Equipment": 37007,
|
||||
"Item Scrap, White": 37008,
|
||||
"Item Scrap, Green": 37009,
|
||||
"Item Scrap, Red": 37010,
|
||||
"Item Scrap, Yellow": 37011,
|
||||
"Void Item": 37012,
|
||||
"Beads of Fealty": 37013
|
||||
}
|
||||
|
||||
# 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] = {
|
||||
"Item Scrap, Green": 16,
|
||||
"Item Scrap, Red": 4,
|
||||
"Item Scrap, Yellow": 1,
|
||||
"Item Scrap, White": 32,
|
||||
"Common Item": 64,
|
||||
"Uncommon Item": 32,
|
||||
"Legendary Item": 8,
|
||||
"Boss Item": 4,
|
||||
"Lunar Item": 16,
|
||||
"Void Item": 16,
|
||||
"Equipment": 32
|
||||
}
|
||||
|
||||
new_weights: Dict[str, int] = {
|
||||
"Item Scrap, Green": 15,
|
||||
"Item Scrap, Red": 5,
|
||||
"Item Scrap, Yellow": 1,
|
||||
"Item Scrap, White": 30,
|
||||
"Common Item": 75,
|
||||
"Uncommon Item": 40,
|
||||
"Legendary Item": 10,
|
||||
"Boss Item": 5,
|
||||
"Lunar Item": 10,
|
||||
"Void Item": 16,
|
||||
"Equipment": 20
|
||||
}
|
||||
|
||||
uncommon_weights: Dict[str, int] = {
|
||||
"Item Scrap, Green": 45,
|
||||
"Item Scrap, Red": 5,
|
||||
"Item Scrap, Yellow": 1,
|
||||
"Item Scrap, White": 30,
|
||||
"Common Item": 45,
|
||||
"Uncommon Item": 100,
|
||||
"Legendary Item": 10,
|
||||
"Boss Item": 5,
|
||||
"Lunar Item": 15,
|
||||
"Void Item": 16,
|
||||
"Equipment": 20
|
||||
}
|
||||
|
||||
legendary_weights: Dict[str, int] = {
|
||||
"Item Scrap, Green": 15,
|
||||
"Item Scrap, Red": 5,
|
||||
"Item Scrap, Yellow": 1,
|
||||
"Item Scrap, White": 30,
|
||||
"Common Item": 50,
|
||||
"Uncommon Item": 25,
|
||||
"Legendary Item": 100,
|
||||
"Boss Item": 5,
|
||||
"Lunar Item": 15,
|
||||
"Void Item": 16,
|
||||
"Equipment": 20
|
||||
}
|
||||
|
||||
lunartic_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": 100,
|
||||
"Void Item": 0,
|
||||
"Equipment": 0
|
||||
}
|
||||
|
||||
chaos_weights: Dict[str, int] = {
|
||||
"Item Scrap, Green": 80,
|
||||
"Item Scrap, Red": 45,
|
||||
"Item Scrap, Yellow": 30,
|
||||
"Item Scrap, White": 100,
|
||||
"Common Item": 100,
|
||||
"Uncommon Item": 70,
|
||||
"Legendary Item": 30,
|
||||
"Boss Item": 20,
|
||||
"Lunar Item": 60,
|
||||
"Void Item": 60,
|
||||
"Equipment": 40
|
||||
}
|
||||
|
||||
no_scraps_weights: Dict[str, int] = {
|
||||
"Item Scrap, Green": 0,
|
||||
"Item Scrap, Red": 0,
|
||||
"Item Scrap, Yellow": 0,
|
||||
"Item Scrap, White": 0,
|
||||
"Common Item": 100,
|
||||
"Uncommon Item": 40,
|
||||
"Legendary Item": 15,
|
||||
"Boss Item": 5,
|
||||
"Lunar Item": 10,
|
||||
"Void Item": 16,
|
||||
"Equipment": 25
|
||||
}
|
||||
|
||||
even_weights: Dict[str, int] = {
|
||||
"Item Scrap, Green": 1,
|
||||
"Item Scrap, Red": 1,
|
||||
"Item Scrap, Yellow": 1,
|
||||
"Item Scrap, White": 1,
|
||||
"Common Item": 1,
|
||||
"Uncommon Item": 1,
|
||||
"Legendary Item": 1,
|
||||
"Boss Item": 1,
|
||||
"Lunar Item": 1,
|
||||
"Void Item": 1,
|
||||
"Equipment": 1
|
||||
}
|
||||
|
||||
scraps_only: Dict[str, int] = {
|
||||
"Item Scrap, Green": 70,
|
||||
"Item Scrap, White": 100,
|
||||
"Item Scrap, Red": 30,
|
||||
"Item Scrap, Yellow": 5,
|
||||
"Common Item": 0,
|
||||
"Uncommon Item": 0,
|
||||
"Legendary Item": 0,
|
||||
"Boss 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
|
||||
}
|
||||
|
||||
item_pool_weights: Dict[int, Dict[str, int]] = {
|
||||
ItemWeights.option_default: default_weights,
|
||||
ItemWeights.option_new: new_weights,
|
||||
ItemWeights.option_uncommon: uncommon_weights,
|
||||
ItemWeights.option_legendary: legendary_weights,
|
||||
ItemWeights.option_lunartic: lunartic_weights,
|
||||
ItemWeights.option_chaos: chaos_weights,
|
||||
ItemWeights.option_no_scraps: no_scraps_weights,
|
||||
ItemWeights.option_even: even_weights,
|
||||
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()}
|
|
@ -1,119 +0,0 @@
|
|||
from typing import Tuple
|
||||
from BaseClasses import Location
|
||||
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):
|
||||
game: str = "Risk of Rain 2"
|
||||
|
||||
|
||||
ror2_locations_start_id = 38000
|
||||
|
||||
|
||||
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,118 +0,0 @@
|
|||
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": 46, # voidstage
|
||||
"The Planetarium": 45, # 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,14 +1,16 @@
|
|||
import string
|
||||
|
||||
from .Items import RiskOfRainItem, item_table, item_pool_weights, environment_offest
|
||||
from .Locations import RiskOfRainLocation, get_classic_item_pickups, item_pickups, orderedstage_location
|
||||
from .Rules import set_rules
|
||||
from .RoR2Environments import *
|
||||
from .items import RiskOfRainItem, item_table, item_pool_weights, offset, filler_table, environment_offset
|
||||
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
|
||||
|
||||
from BaseClasses import Region, Entrance, Item, ItemClassification, MultiWorld, Tutorial
|
||||
from .Options import ItemWeights, ROR2Options
|
||||
from BaseClasses import Item, ItemClassification, Tutorial
|
||||
from .options import ItemWeights, ROR2Options
|
||||
from worlds.AutoWorld import World, WebWorld
|
||||
from .Regions import create_regions
|
||||
from .regions import create_explore_regions, create_classic_regions
|
||||
from typing import List, Dict, Any
|
||||
|
||||
|
||||
class RiskOfWeb(WebWorld):
|
||||
|
@ -18,7 +20,7 @@ class RiskOfWeb(WebWorld):
|
|||
"English",
|
||||
"setup_en.md",
|
||||
"setup/en",
|
||||
["Ijwu"]
|
||||
["Ijwu", "Kindasneaki"]
|
||||
)]
|
||||
|
||||
|
||||
|
@ -32,38 +34,53 @@ class RiskOfRainWorld(World):
|
|||
options_dataclass = ROR2Options
|
||||
options: ROR2Options
|
||||
topology_present = False
|
||||
|
||||
item_name_to_id = item_table
|
||||
item_name_to_id = {name: data.code for name, data in item_table.items()}
|
||||
item_name_groups = {
|
||||
"Stages": {name for name, data in item_table.items() if data.category == "Stage"},
|
||||
"Environments": {name for name, data in item_table.items() if data.category == "Environment"},
|
||||
"Upgrades": {name for name, data in item_table.items() if data.category == "Upgrade"},
|
||||
"Fillers": {name for name, data in item_table.items() if data.category == "Filler"},
|
||||
"Traps": {name for name, data in item_table.items() if data.category == "Trap"},
|
||||
}
|
||||
location_name_to_id = item_pickups
|
||||
|
||||
data_version = 7
|
||||
required_client_version = (0, 4, 2)
|
||||
data_version = 8
|
||||
required_client_version = (0, 4, 4)
|
||||
web = RiskOfWeb()
|
||||
total_revivals: int
|
||||
|
||||
def __init__(self, multiworld: "MultiWorld", player: int):
|
||||
super().__init__(multiworld, player)
|
||||
self.junk_pool: Dict[str, int] = {}
|
||||
|
||||
def generate_early(self) -> None:
|
||||
# figure out how many revivals should exist in the pool
|
||||
if self.options.goal == "classic":
|
||||
total_locations = self.options.total_locations.value
|
||||
else:
|
||||
total_locations = len(
|
||||
orderedstage_location.get_locations(
|
||||
get_locations(
|
||||
chests=self.options.chests_per_stage.value,
|
||||
shrines=self.options.shrines_per_stage.value,
|
||||
scavengers=self.options.scavengers_per_stage.value,
|
||||
scanners=self.options.scanner_per_stage.value,
|
||||
altars=self.options.altars_per_stage.value,
|
||||
dlc_sotv=self.options.dlc_sotv.value
|
||||
dlc_sotv=bool(self.options.dlc_sotv.value)
|
||||
)
|
||||
)
|
||||
self.total_revivals = int(self.options.total_revivals.value / 100 *
|
||||
total_locations)
|
||||
if self.options.start_with_revive:
|
||||
self.total_revivals -= 1
|
||||
if self.options.victory == "voidling" and not self.options.dlc_sotv:
|
||||
self.options.victory.value = self.options.victory.option_any
|
||||
|
||||
def create_regions(self) -> None:
|
||||
|
||||
if self.options.goal == "classic":
|
||||
# classic mode
|
||||
create_classic_regions(self)
|
||||
else:
|
||||
# explore mode
|
||||
create_explore_regions(self)
|
||||
|
||||
self.create_events()
|
||||
|
||||
def create_items(self) -> None:
|
||||
# shortcut for starting_inventory... The start_with_revive option lets you start with a Dio's Best Friend
|
||||
|
@ -77,25 +94,26 @@ class RiskOfRainWorld(World):
|
|||
# figure out all available ordered stages for each tier
|
||||
environment_available_orderedstages_table = environment_vanilla_orderedstages_table
|
||||
if self.options.dlc_sotv:
|
||||
environment_available_orderedstages_table = collapse_dict_list_vertical(environment_available_orderedstages_table, environment_sotv_orderedstages_table)
|
||||
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)
|
||||
environments_pool = shift_by_offset(environment_vanilla_table, environment_offset)
|
||||
|
||||
if self.options.dlc_sotv:
|
||||
environment_offset_table = shift_by_offset(environment_sotv_table, environment_offest)
|
||||
environment_offset_table = shift_by_offset(environment_sotv_table, environment_offset)
|
||||
environments_pool = {**environments_pool, **environment_offset_table}
|
||||
environments_to_precollect = 5 if self.options.begin_with_loop 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)
|
||||
unlock = self.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])
|
||||
|
||||
# Generate item pool
|
||||
itempool: List = []
|
||||
itempool: List[str] = ["Beads of Fealty", "Radar Scanner"]
|
||||
# Add revive items for the player
|
||||
itempool += ["Dio's Best Friend"] * self.total_revivals
|
||||
itempool += ["Beads of Fealty"]
|
||||
|
||||
for env_name, _ in environments_pool.items():
|
||||
itempool += [env_name]
|
||||
|
@ -105,38 +123,28 @@ class RiskOfRainWorld(World):
|
|||
total_locations = self.options.total_locations.value
|
||||
else:
|
||||
# explore mode
|
||||
# Add Stage items for logic gates
|
||||
itempool += ["Stage 1", "Stage 2", "Stage 3", "Stage 4"]
|
||||
total_locations = len(
|
||||
orderedstage_location.get_locations(
|
||||
get_locations(
|
||||
chests=self.options.chests_per_stage.value,
|
||||
shrines=self.options.shrines_per_stage.value,
|
||||
scavengers=self.options.scavengers_per_stage.value,
|
||||
scanners=self.options.scanner_per_stage.value,
|
||||
altars=self.options.altars_per_stage.value,
|
||||
dlc_sotv=self.options.dlc_sotv.value
|
||||
dlc_sotv=bool(self.options.dlc_sotv.value)
|
||||
)
|
||||
)
|
||||
# Create junk items
|
||||
self.junk_pool = self.create_junk_pool()
|
||||
junk_pool = self.create_junk_pool()
|
||||
# Fill remaining items with randomly generated junk
|
||||
while len(itempool) < total_locations:
|
||||
itempool.append(self.get_filler_item_name())
|
||||
filler = self.random.choices(*zip(*junk_pool.items()), k=total_locations - len(itempool))
|
||||
itempool.extend(filler)
|
||||
|
||||
# Convert itempool into real items
|
||||
itempool = list(map(lambda name: self.create_item(name), itempool))
|
||||
self.multiworld.itempool += itempool
|
||||
self.multiworld.itempool += map(self.create_item, itempool)
|
||||
|
||||
def set_rules(self) -> None:
|
||||
set_rules(self.multiworld, self.player)
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
if not self.junk_pool:
|
||||
self.junk_pool = self.create_junk_pool()
|
||||
weights = [data for data in self.junk_pool.values()]
|
||||
filler = self.multiworld.random.choices([filler for filler in self.junk_pool.keys()], weights,
|
||||
k=1)[0]
|
||||
return filler
|
||||
|
||||
def create_junk_pool(self) -> Dict:
|
||||
def create_junk_pool(self) -> Dict[str, int]:
|
||||
# if presets are enabled generate junk_pool from the selected preset
|
||||
pool_option = self.options.item_weights.value
|
||||
junk_pool: Dict[str, int] = {}
|
||||
|
@ -144,7 +152,7 @@ class RiskOfRainWorld(World):
|
|||
# generate chaos weights if the preset is chosen
|
||||
if pool_option == ItemWeights.option_chaos:
|
||||
for name, max_value in item_pool_weights[pool_option].items():
|
||||
junk_pool[name] = self.multiworld.random.randint(0, max_value)
|
||||
junk_pool[name] = self.random.randint(0, max_value)
|
||||
else:
|
||||
junk_pool = item_pool_weights[pool_option].copy()
|
||||
else: # generate junk pool from user created presets
|
||||
|
@ -159,10 +167,22 @@ class RiskOfRainWorld(World):
|
|||
"Boss Item": self.options.boss_item.value,
|
||||
"Lunar Item": self.options.lunar_item.value,
|
||||
"Void Item": self.options.void_item.value,
|
||||
"Equipment": self.options.equipment.value
|
||||
"Equipment": self.options.equipment.value,
|
||||
"Money": self.options.money.value,
|
||||
"Lunar Coin": self.options.lunar_coin.value,
|
||||
"1000 Exp": self.options.experience.value,
|
||||
"Mountain Trap": self.options.mountain_trap.value,
|
||||
"Time Warp Trap": self.options.time_warp_trap.value,
|
||||
"Combat Trap": self.options.combat_trap.value,
|
||||
"Teleport Trap": self.options.teleport_trap.value,
|
||||
}
|
||||
|
||||
# remove lunar items from the pool if they're disabled in the yaml unless lunartic is rolled
|
||||
# remove trap items from the pool (excluding lunar items)
|
||||
if not self.options.enable_trap:
|
||||
junk_pool.pop("Mountain Trap")
|
||||
junk_pool.pop("Time Warp Trap")
|
||||
junk_pool.pop("Combat Trap")
|
||||
junk_pool.pop("Teleport Trap")
|
||||
# remove lunar items from the pool
|
||||
if not (self.options.enable_lunar or pool_option == ItemWeights.option_lunartic):
|
||||
junk_pool.pop("Lunar Item")
|
||||
# remove void items from the pool
|
||||
|
@ -171,98 +191,58 @@ class RiskOfRainWorld(World):
|
|||
|
||||
return junk_pool
|
||||
|
||||
def create_regions(self) -> None:
|
||||
def create_item(self, name: str) -> Item:
|
||||
data = item_table[name]
|
||||
return RiskOfRainItem(name, data.item_type, data.code, self.player)
|
||||
|
||||
if self.options.goal == "classic":
|
||||
# classic mode
|
||||
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.options.total_locations.value))
|
||||
self.multiworld.regions.append(petrichor)
|
||||
def set_rules(self) -> None:
|
||||
set_rules(self)
|
||||
|
||||
# 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)
|
||||
def get_filler_item_name(self) -> str:
|
||||
weights = [data.weight for data in filler_table.values()]
|
||||
filler = self.multiworld.random.choices([filler for filler in filler_table.keys()], weights,
|
||||
k=1)[0]
|
||||
return filler
|
||||
|
||||
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)
|
||||
|
||||
def fill_slot_data(self):
|
||||
options_dict = self.options.as_dict("item_pickup_step", "shrine_use_step", "goal", "total_locations",
|
||||
"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", casing="camel")
|
||||
def fill_slot_data(self) -> Dict[str, Any]:
|
||||
options_dict = self.options.as_dict("item_pickup_step", "shrine_use_step", "goal", "victory", "total_locations",
|
||||
"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",
|
||||
casing="camel")
|
||||
return {
|
||||
**options_dict,
|
||||
"seed": "".join(self.multiworld.per_slot_randoms[self.player].choice(string.digits) for _ in range(16)),
|
||||
"seed": "".join(self.random.choice(string.digits) for _ in range(16)),
|
||||
"offset": offset
|
||||
}
|
||||
|
||||
def create_item(self, name: str) -> Item:
|
||||
item_id = item_table[name]
|
||||
classification = ItemClassification.filler
|
||||
if name in {"Dio's Best Friend", "Beads of Fealty"}:
|
||||
classification = ItemClassification.progression
|
||||
elif name in {"Legendary Item", "Boss Item"}:
|
||||
classification = ItemClassification.useful
|
||||
elif name == "Lunar Item":
|
||||
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 comparisons.
|
||||
|
||||
elif name in environment_ALL_table.keys():
|
||||
if name in {"Hidden Realm: Bulwark's Ambry", "Hidden Realm: Gilded Coast,"}:
|
||||
classification = ItemClassification.useful
|
||||
else:
|
||||
classification = ItemClassification.progression
|
||||
|
||||
item = RiskOfRainItem(name, classification, item_id, self.player)
|
||||
return item
|
||||
|
||||
|
||||
def create_events(world: MultiWorld, player: int) -> None:
|
||||
total_locations = world.worlds[player].options.total_locations.value
|
||||
num_of_events = total_locations // 25
|
||||
if total_locations / 25 == num_of_events:
|
||||
num_of_events -= 1
|
||||
world_region = world.get_region("Petrichor V", player)
|
||||
if world.worlds[player].options.goal == "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.worlds[player].options.goal == "explore":
|
||||
for n in range(1, 6):
|
||||
|
||||
event_region = world.get_region(f"OrderedStage_{n}", player)
|
||||
event_loc = RiskOfRainLocation(player, f"Stage_{n}", None, event_region)
|
||||
event_loc.place_locked_item(RiskOfRainItem(f"Stage_{n}", ItemClassification.progression, None, player))
|
||||
def create_events(self) -> None:
|
||||
total_locations = self.options.total_locations.value
|
||||
num_of_events = total_locations // 25
|
||||
if total_locations / 25 == num_of_events:
|
||||
num_of_events -= 1
|
||||
world_region = self.multiworld.get_region("Petrichor V", self.player)
|
||||
if self.options.goal == "classic":
|
||||
# classic mode
|
||||
# only setup Pickups when using classic_mode
|
||||
for i in range(num_of_events):
|
||||
event_loc = RiskOfRainLocation(self.player, f"Pickup{(i + 1) * 25}", None, world_region)
|
||||
event_loc.place_locked_item(
|
||||
RiskOfRainItem(f"Pickup{(i + 1) * 25}", ItemClassification.progression, None,
|
||||
self.player))
|
||||
event_loc.access_rule = \
|
||||
lambda state, i=i: state.can_reach(f"ItemPickup{((i + 1) * 25) - 1}", "Location", self.player)
|
||||
world_region.locations.append(event_loc)
|
||||
else:
|
||||
# explore mode
|
||||
event_region = self.multiworld.get_region("OrderedStage_5", self.player)
|
||||
event_loc = RiskOfRainLocation(self.player, "Stage 5", None, event_region)
|
||||
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)
|
||||
|
||||
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))
|
||||
world_region.locations.append(victory_event)
|
||||
|
||||
|
||||
def create_region(world: MultiWorld, player: int, name: str, locations: Dict[str, int] = {}) -> Region:
|
||||
ret = Region(name, player, world)
|
||||
for location_name, location_id in locations.items():
|
||||
ret.locations.append(RiskOfRainLocation(player, location_name, location_id, ret))
|
||||
return ret
|
||||
victory_region = self.multiworld.get_region("Victory", self.player)
|
||||
victory_event = RiskOfRainLocation(self.player, "Victory", None, victory_region)
|
||||
victory_event.place_locked_item(RiskOfRainItem("Victory", ItemClassification.progression, None, self.player))
|
||||
victory_region.locations.append(victory_event)
|
||||
|
|
|
@ -55,4 +55,15 @@ the player's YAML.
|
|||
You can talk to other in the multiworld chat using the RoR2 chat. All other multiworld
|
||||
remote commands list in the [commands guide](/tutorial/Archipelago/commands/en) work as well in the RoR2 chat. You can
|
||||
also optionally connect to the multiworld using the text client, which can be found in the
|
||||
[main Archipelago installation](https://github.com/ArchipelagoMW/Archipelago/releases).
|
||||
[main Archipelago installation](https://github.com/ArchipelagoMW/Archipelago/releases).
|
||||
|
||||
### In-Game Commands
|
||||
These commands are to be used in-game by using ``Ctrl + Alt + ` `` and then typing the following:
|
||||
- `archipelago_connect <url> <port> <slot> [password]` example: "archipelago_connect archipelago.gg 38281 SlotName".
|
||||
- `archipelago_deathlink true/false` Toggle deathlink.
|
||||
- `archipelago_disconnect` Disconnect from AP.
|
||||
- `archipelago_final_stage_death true/false` Toggle final stage death.
|
||||
|
||||
Explore Mode only
|
||||
- `archipelago_show_unlocked_stages` Show which stages have been received.
|
||||
- `archipelago_highlight_satellite true/false` This will highlight the satellite to make it easier to see (Default false).
|
|
@ -0,0 +1,309 @@
|
|||
from BaseClasses import Item, ItemClassification
|
||||
from .options import ItemWeights
|
||||
from .ror2environments import environment_all_table
|
||||
from typing import NamedTuple, Optional, Dict
|
||||
|
||||
|
||||
class RiskOfRainItem(Item):
|
||||
game: str = "Risk of Rain 2"
|
||||
|
||||
|
||||
class RiskOfRainItemData(NamedTuple):
|
||||
category: str
|
||||
code: int
|
||||
item_type: ItemClassification = ItemClassification.filler
|
||||
weight: Optional[int] = None
|
||||
|
||||
|
||||
offset: int = 37000
|
||||
filler_offset: int = offset + 300
|
||||
trap_offset: int = offset + 400
|
||||
stage_offset: int = offset + 500
|
||||
environment_offset: int = offset + 700
|
||||
# Upgrade item ids 37002 - 37012
|
||||
upgrade_table: Dict[str, RiskOfRainItemData] = {
|
||||
"Common Item": RiskOfRainItemData("Upgrade", 2 + offset, ItemClassification.filler, 64),
|
||||
"Uncommon Item": RiskOfRainItemData("Upgrade", 3 + offset, ItemClassification.filler, 32),
|
||||
"Legendary Item": RiskOfRainItemData("Upgrade", 4 + offset, ItemClassification.useful, 8),
|
||||
"Boss Item": RiskOfRainItemData("Upgrade", 5 + offset, ItemClassification.useful, 4),
|
||||
"Equipment": RiskOfRainItemData("Upgrade", 7 + offset, ItemClassification.filler, 32),
|
||||
"Item Scrap, White": RiskOfRainItemData("Upgrade", 8 + offset, ItemClassification.filler, 32),
|
||||
"Item Scrap, Green": RiskOfRainItemData("Upgrade", 9 + offset, ItemClassification.filler, 16),
|
||||
"Item Scrap, Red": RiskOfRainItemData("Upgrade", 10 + offset, ItemClassification.filler, 4),
|
||||
"Item Scrap, Yellow": RiskOfRainItemData("Upgrade", 11 + offset, ItemClassification.filler, 1),
|
||||
"Void Item": RiskOfRainItemData("Upgrade", 12 + offset, ItemClassification.filler, 16),
|
||||
}
|
||||
# Other item ids 37001, 37013-37014
|
||||
other_table: Dict[str, RiskOfRainItemData] = {
|
||||
"Dio's Best Friend": RiskOfRainItemData("ExtraLife", 1 + offset, ItemClassification.progression_skip_balancing),
|
||||
"Beads of Fealty": RiskOfRainItemData("Beads", 13 + offset, ItemClassification.progression),
|
||||
"Radar Scanner": RiskOfRainItemData("Radar", 14 + offset, ItemClassification.useful),
|
||||
}
|
||||
# Filler item ids 37301 - 37303
|
||||
filler_table: Dict[str, RiskOfRainItemData] = {
|
||||
"Money": RiskOfRainItemData("Filler", 1 + filler_offset, ItemClassification.filler, 64),
|
||||
"Lunar Coin": RiskOfRainItemData("Filler", 2 + filler_offset, ItemClassification.filler, 20),
|
||||
"1000 Exp": RiskOfRainItemData("Filler", 3 + filler_offset, ItemClassification.filler, 40),
|
||||
}
|
||||
# Trap item ids 37401 - 37404 (Lunar items used to be part of the upgrade item list, so keeping the id the same)
|
||||
trap_table: Dict[str, RiskOfRainItemData] = {
|
||||
"Lunar Item": RiskOfRainItemData("Trap", 6 + offset, ItemClassification.trap, 16),
|
||||
"Mountain Trap": RiskOfRainItemData("Trap", 1 + trap_offset, ItemClassification.trap, 5),
|
||||
"Time Warp Trap": RiskOfRainItemData("Trap", 2 + trap_offset, ItemClassification.trap, 20),
|
||||
"Combat Trap": RiskOfRainItemData("Trap", 3 + trap_offset, ItemClassification.trap, 20),
|
||||
"Teleport Trap": RiskOfRainItemData("Trap", 4 + trap_offset, ItemClassification.trap, 10),
|
||||
}
|
||||
# Stage item ids 37501 - 37504
|
||||
stage_table: Dict[str, RiskOfRainItemData] = {
|
||||
"Stage 1": RiskOfRainItemData("Stage", 1 + stage_offset, ItemClassification.progression),
|
||||
"Stage 2": RiskOfRainItemData("Stage", 2 + stage_offset, ItemClassification.progression),
|
||||
"Stage 3": RiskOfRainItemData("Stage", 3 + stage_offset, ItemClassification.progression),
|
||||
"Stage 4": RiskOfRainItemData("Stage", 4 + stage_offset, ItemClassification.progression),
|
||||
|
||||
}
|
||||
|
||||
item_table = {**upgrade_table, **other_table, **filler_table, **trap_table, **stage_table}
|
||||
# Environment item ids 37700 - 37746
|
||||
##################################################
|
||||
# environments
|
||||
|
||||
|
||||
# add ALL environments into the item table
|
||||
def create_environment_table(name: str, environment_id: int, environment_classification: ItemClassification) \
|
||||
-> Dict[str, RiskOfRainItemData]:
|
||||
return {name: RiskOfRainItemData("Environment", environment_offset + environment_id, environment_classification)}
|
||||
|
||||
|
||||
environment_table: Dict[str, RiskOfRainItemData] = {}
|
||||
# use the sotv dlc in the item table so that all names can be looked up regardless of use
|
||||
for data, key in environment_all_table.items():
|
||||
classification = ItemClassification.progression
|
||||
if data in {"Hidden Realm: Bulwark's Ambry", "Hidden Realm: Gilded Coast"}:
|
||||
classification = ItemClassification.useful
|
||||
environment_table.update(create_environment_table(data, key, classification))
|
||||
|
||||
item_table.update(environment_table)
|
||||
|
||||
# end of environments
|
||||
##################################################
|
||||
|
||||
default_weights: Dict[str, int] = {
|
||||
"Item Scrap, Green": 16,
|
||||
"Item Scrap, Red": 4,
|
||||
"Item Scrap, Yellow": 1,
|
||||
"Item Scrap, White": 32,
|
||||
"Common Item": 64,
|
||||
"Uncommon Item": 32,
|
||||
"Legendary Item": 8,
|
||||
"Boss Item": 4,
|
||||
"Void Item": 16,
|
||||
"Equipment": 32,
|
||||
"Money": 64,
|
||||
"Lunar Coin": 20,
|
||||
"1000 Exp": 40,
|
||||
"Lunar Item": 10,
|
||||
"Mountain Trap": 4,
|
||||
"Time Warp Trap": 20,
|
||||
"Combat Trap": 20,
|
||||
"Teleport Trap": 20
|
||||
}
|
||||
|
||||
new_weights: Dict[str, int] = {
|
||||
"Item Scrap, Green": 15,
|
||||
"Item Scrap, Red": 5,
|
||||
"Item Scrap, Yellow": 1,
|
||||
"Item Scrap, White": 30,
|
||||
"Common Item": 75,
|
||||
"Uncommon Item": 40,
|
||||
"Legendary Item": 10,
|
||||
"Boss Item": 5,
|
||||
"Void Item": 16,
|
||||
"Equipment": 20,
|
||||
"Money": 64,
|
||||
"Lunar Coin": 20,
|
||||
"1000 Exp": 40,
|
||||
"Lunar Item": 10,
|
||||
"Mountain Trap": 4,
|
||||
"Time Warp Trap": 20,
|
||||
"Combat Trap": 20,
|
||||
"Teleport Trap": 20
|
||||
}
|
||||
|
||||
uncommon_weights: Dict[str, int] = {
|
||||
"Item Scrap, Green": 45,
|
||||
"Item Scrap, Red": 5,
|
||||
"Item Scrap, Yellow": 1,
|
||||
"Item Scrap, White": 30,
|
||||
"Common Item": 45,
|
||||
"Uncommon Item": 100,
|
||||
"Legendary Item": 10,
|
||||
"Boss Item": 5,
|
||||
"Void Item": 16,
|
||||
"Equipment": 20,
|
||||
"Money": 64,
|
||||
"Lunar Coin": 20,
|
||||
"1000 Exp": 40,
|
||||
"Lunar Item": 10,
|
||||
"Mountain Trap": 4,
|
||||
"Time Warp Trap": 20,
|
||||
"Combat Trap": 20,
|
||||
"Teleport Trap": 20
|
||||
}
|
||||
|
||||
legendary_weights: Dict[str, int] = {
|
||||
"Item Scrap, Green": 15,
|
||||
"Item Scrap, Red": 5,
|
||||
"Item Scrap, Yellow": 1,
|
||||
"Item Scrap, White": 30,
|
||||
"Common Item": 50,
|
||||
"Uncommon Item": 25,
|
||||
"Legendary Item": 100,
|
||||
"Boss Item": 5,
|
||||
"Void Item": 16,
|
||||
"Equipment": 20,
|
||||
"Money": 64,
|
||||
"Lunar Coin": 20,
|
||||
"1000 Exp": 40,
|
||||
"Lunar Item": 10,
|
||||
"Mountain Trap": 4,
|
||||
"Time Warp Trap": 20,
|
||||
"Combat Trap": 20,
|
||||
"Teleport Trap": 20
|
||||
}
|
||||
|
||||
chaos_weights: Dict[str, int] = {
|
||||
"Item Scrap, Green": 80,
|
||||
"Item Scrap, Red": 45,
|
||||
"Item Scrap, Yellow": 30,
|
||||
"Item Scrap, White": 100,
|
||||
"Common Item": 100,
|
||||
"Uncommon Item": 70,
|
||||
"Legendary Item": 30,
|
||||
"Boss Item": 20,
|
||||
"Void Item": 60,
|
||||
"Equipment": 40,
|
||||
"Money": 64,
|
||||
"Lunar Coin": 20,
|
||||
"1000 Exp": 40,
|
||||
"Lunar Item": 10,
|
||||
"Mountain Trap": 4,
|
||||
"Time Warp Trap": 20,
|
||||
"Combat Trap": 20,
|
||||
"Teleport Trap": 20
|
||||
}
|
||||
|
||||
no_scraps_weights: Dict[str, int] = {
|
||||
"Item Scrap, Green": 0,
|
||||
"Item Scrap, Red": 0,
|
||||
"Item Scrap, Yellow": 0,
|
||||
"Item Scrap, White": 0,
|
||||
"Common Item": 100,
|
||||
"Uncommon Item": 40,
|
||||
"Legendary Item": 15,
|
||||
"Boss Item": 5,
|
||||
"Void Item": 16,
|
||||
"Equipment": 25,
|
||||
"Money": 64,
|
||||
"Lunar Coin": 20,
|
||||
"1000 Exp": 40,
|
||||
"Lunar Item": 10,
|
||||
"Mountain Trap": 4,
|
||||
"Time Warp Trap": 20,
|
||||
"Combat Trap": 20,
|
||||
"Teleport Trap": 20
|
||||
}
|
||||
|
||||
even_weights: Dict[str, int] = {
|
||||
"Item Scrap, Green": 1,
|
||||
"Item Scrap, Red": 1,
|
||||
"Item Scrap, Yellow": 1,
|
||||
"Item Scrap, White": 1,
|
||||
"Common Item": 1,
|
||||
"Uncommon Item": 1,
|
||||
"Legendary Item": 1,
|
||||
"Boss Item": 1,
|
||||
"Void Item": 1,
|
||||
"Equipment": 1,
|
||||
"Money": 1,
|
||||
"Lunar Coin": 1,
|
||||
"1000 Exp": 1,
|
||||
"Lunar Item": 1,
|
||||
"Mountain Trap": 1,
|
||||
"Time Warp Trap": 1,
|
||||
"Combat Trap": 1,
|
||||
"Teleport Trap": 1
|
||||
}
|
||||
|
||||
scraps_only: Dict[str, int] = {
|
||||
"Item Scrap, Green": 70,
|
||||
"Item Scrap, White": 100,
|
||||
"Item Scrap, Red": 30,
|
||||
"Item Scrap, Yellow": 5,
|
||||
"Common Item": 0,
|
||||
"Uncommon Item": 0,
|
||||
"Legendary Item": 0,
|
||||
"Boss Item": 0,
|
||||
"Void Item": 0,
|
||||
"Equipment": 0,
|
||||
"Money": 20,
|
||||
"Lunar Coin": 10,
|
||||
"1000 Exp": 10,
|
||||
"Lunar Item": 0,
|
||||
"Mountain Trap": 5,
|
||||
"Time Warp Trap": 10,
|
||||
"Combat Trap": 10,
|
||||
"Teleport Trap": 10
|
||||
}
|
||||
lunartic_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,
|
||||
"Void Item": 0,
|
||||
"Equipment": 0,
|
||||
"Money": 20,
|
||||
"Lunar Coin": 10,
|
||||
"1000 Exp": 10,
|
||||
"Lunar Item": 100,
|
||||
"Mountain Trap": 5,
|
||||
"Time Warp Trap": 10,
|
||||
"Combat Trap": 10,
|
||||
"Teleport Trap": 10
|
||||
}
|
||||
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,
|
||||
"Void Item": 100,
|
||||
"Equipment": 0,
|
||||
"Money": 20,
|
||||
"Lunar Coin": 10,
|
||||
"1000 Exp": 10,
|
||||
"Lunar Item": 0,
|
||||
"Mountain Trap": 5,
|
||||
"Time Warp Trap": 10,
|
||||
"Combat Trap": 10,
|
||||
"Teleport Trap": 10
|
||||
}
|
||||
|
||||
item_pool_weights: Dict[int, Dict[str, int]] = {
|
||||
ItemWeights.option_default: default_weights,
|
||||
ItemWeights.option_new: new_weights,
|
||||
ItemWeights.option_uncommon: uncommon_weights,
|
||||
ItemWeights.option_legendary: legendary_weights,
|
||||
ItemWeights.option_chaos: chaos_weights,
|
||||
ItemWeights.option_no_scraps: no_scraps_weights,
|
||||
ItemWeights.option_even: even_weights,
|
||||
ItemWeights.option_scraps_only: scraps_only,
|
||||
ItemWeights.option_lunartic: lunartic_weights,
|
||||
ItemWeights.option_void: void_weights,
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
from typing import Dict
|
||||
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
|
||||
|
||||
|
||||
class RiskOfRainLocation(Location):
|
||||
game: str = "Risk of Rain 2"
|
||||
|
||||
|
||||
ror2_locations_start_id = 38000
|
||||
|
||||
|
||||
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
|
||||
|
||||
# this is so we can easily calculate the environment and location "offset" ids
|
||||
ror2_locations_start_ordered_stage = ror2_locations_start_id + TotalLocations.range_end
|
||||
|
||||
# TODO is there a better, more generic way to do this?
|
||||
offset_chests = 0
|
||||
offset_shrines = offset_chests + ChestsPerEnvironment.range_end
|
||||
offset_scavengers = offset_shrines + ShrinesPerEnvironment.range_end
|
||||
offset_scanners = offset_scavengers + ScavengersPerEnvironment.range_end
|
||||
offset_altars = offset_scanners + ScannersPerEnvironment.range_end
|
||||
|
||||
# total space allocated to the locations in a single orderedstage environment
|
||||
allocation = offset_altars + AltarsPerEnvironment.range_end
|
||||
|
||||
|
||||
def get_environment_locations(chests: int, shrines: int, scavengers: int, scanners: int, altars: int,
|
||||
environment_name: str, environment_index: int) -> Dict[str, int]:
|
||||
"""Get the locations within a specific environment"""
|
||||
locations = {}
|
||||
|
||||
# due to this mapping, since environment ids are not consecutive, there are lots of "wasted" id numbers
|
||||
environment_start_id = environment_index * allocation + ror2_locations_start_ordered_stage
|
||||
for n in range(chests):
|
||||
locations.update({f"{environment_name}: Chest {n + 1}": n + offset_chests + environment_start_id})
|
||||
for n in range(shrines):
|
||||
locations.update({f"{environment_name}: Shrine {n + 1}": n + offset_shrines + environment_start_id})
|
||||
for n in range(scavengers):
|
||||
locations.update({f"{environment_name}: Scavenger {n + 1}": n + offset_scavengers + environment_start_id})
|
||||
for n in range(scanners):
|
||||
locations.update({f"{environment_name}: Radio Scanner {n + 1}": n + offset_scanners + environment_start_id})
|
||||
for n in range(altars):
|
||||
locations.update({f"{environment_name}: Newt Altar {n + 1}": n + offset_altars + 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 orderedstage 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.update(get_environment_locations(
|
||||
chests=chests,
|
||||
shrines=shrines,
|
||||
scavengers=scavengers,
|
||||
scanners=scanners,
|
||||
altars=altars,
|
||||
environment_name=environment_name,
|
||||
environment_index=environment_index),
|
||||
)
|
||||
return locations
|
||||
|
||||
|
||||
# Get all locations in ordered stages.
|
||||
location_table.update(get_locations(
|
||||
chests=ChestsPerEnvironment.range_end,
|
||||
shrines=ShrinesPerEnvironment.range_end,
|
||||
scavengers=ScavengersPerEnvironment.range_end,
|
||||
scanners=ScannersPerEnvironment.range_end,
|
||||
altars=AltarsPerEnvironment.range_end,
|
||||
dlc_sotv=True,
|
||||
))
|
|
@ -4,7 +4,7 @@ from Options import Toggle, DefaultOnToggle, DeathLink, Range, Choice, PerGameCo
|
|||
|
||||
# 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.
|
||||
# NOTE that these changes to range_end must also be reflected in the RoR2 client, so it understands the same ids.
|
||||
|
||||
class Goal(Choice):
|
||||
"""
|
||||
|
@ -19,6 +19,21 @@ class Goal(Choice):
|
|||
default = 1
|
||||
|
||||
|
||||
class Victory(Choice):
|
||||
"""
|
||||
Mithrix: Defeat Mithrix in Commencement
|
||||
Voidling: Defeat the Voidling in The Planetarium (DLC required! Will select any if not enabled.)
|
||||
Limbo: Defeat the Scavenger in Hidden Realm: A Moment, Whole
|
||||
Any: Any victory in the game will count. See Final Stage Death for additional ways.
|
||||
"""
|
||||
display_name = "Victory Condition"
|
||||
option_any = 0
|
||||
option_mithrix = 1
|
||||
option_voidling = 2
|
||||
option_limbo = 3
|
||||
default = 0
|
||||
|
||||
|
||||
class TotalLocations(Range):
|
||||
"""Classic Mode: Number of location checks which are added to the Risk of Rain playthrough."""
|
||||
display_name = "Total Locations"
|
||||
|
@ -100,6 +115,11 @@ class ShrineUseStep(Range):
|
|||
default = 0
|
||||
|
||||
|
||||
class AllowTrapItems(Toggle):
|
||||
"""Allows Trap items in the item pool."""
|
||||
display_name = "Enable Trap Items"
|
||||
|
||||
|
||||
class AllowLunarItems(DefaultOnToggle):
|
||||
"""Allows Lunar items in the item pool."""
|
||||
display_name = "Enable Lunar Item Shuffling"
|
||||
|
@ -111,10 +131,14 @@ class StartWithRevive(DefaultOnToggle):
|
|||
|
||||
|
||||
class FinalStageDeath(Toggle):
|
||||
"""The following will count as a win if set to true:
|
||||
"""The following will count as a win if set to "true", and victory is set to "any":
|
||||
Dying in Commencement.
|
||||
Dying in The Planetarium.
|
||||
Obliterating yourself"""
|
||||
Obliterating yourself
|
||||
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."""
|
||||
display_name = "Final Stage Death is Win"
|
||||
|
||||
|
||||
|
@ -247,6 +271,76 @@ class Equipment(Range):
|
|||
default = 32
|
||||
|
||||
|
||||
class Money(Range):
|
||||
"""Weight of money items in the item pool.
|
||||
|
||||
(Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Money"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 64
|
||||
|
||||
|
||||
class LunarCoin(Range):
|
||||
"""Weight of lunar coin items in the item pool.
|
||||
|
||||
(Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Lunar Coins"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 20
|
||||
|
||||
|
||||
class Experience(Range):
|
||||
"""Weight of 1000 exp items in the item pool.
|
||||
|
||||
(Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "1000 Exp"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 40
|
||||
|
||||
|
||||
class MountainTrap(Range):
|
||||
"""Weight of mountain trap items in the item pool.
|
||||
|
||||
(Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Mountain Trap"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 5
|
||||
|
||||
|
||||
class TimeWarpTrap(Range):
|
||||
"""Weight of time warp trap items in the item pool.
|
||||
|
||||
(Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Time Warp Trap"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 20
|
||||
|
||||
|
||||
class CombatTrap(Range):
|
||||
"""Weight of combat trap items in the item pool.
|
||||
|
||||
(Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Combat Trap"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 20
|
||||
|
||||
|
||||
class TeleportTrap(Range):
|
||||
"""Weight of teleport trap items in the item pool.
|
||||
|
||||
(Ignored unless Item Weight Presets is 'No')"""
|
||||
display_name = "Teleport Trap"
|
||||
range_start = 0
|
||||
range_end = 100
|
||||
default = 20
|
||||
|
||||
|
||||
class ItemPoolPresetToggle(Toggle):
|
||||
"""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"
|
||||
|
@ -258,28 +352,30 @@ class ItemWeights(Choice):
|
|||
- New is a test for a potential adjustment to the default weights.
|
||||
- Uncommon puts a large number of uncommon items in the pool.
|
||||
- Legendary puts a large number of legendary items in the pool.
|
||||
- Lunartic makes everything a lunar item.
|
||||
- Chaos generates the pool completely at random with rarer items having a slight cap to prevent this option being too easy.
|
||||
- Chaos generates the pool completely at random with rarer items having a slight cap to prevent this option being
|
||||
too easy.
|
||||
- No Scraps removes all scrap items from 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.
|
||||
- Lunartic makes everything a lunar item.
|
||||
- Void makes everything a void item."""
|
||||
display_name = "Item Weights"
|
||||
option_default = 0
|
||||
option_new = 1
|
||||
option_uncommon = 2
|
||||
option_legendary = 3
|
||||
option_lunartic = 4
|
||||
option_chaos = 5
|
||||
option_no_scraps = 6
|
||||
option_even = 7
|
||||
option_scraps_only = 8
|
||||
option_chaos = 4
|
||||
option_no_scraps = 5
|
||||
option_even = 6
|
||||
option_scraps_only = 7
|
||||
option_lunartic = 8
|
||||
option_void = 9
|
||||
|
||||
|
||||
@dataclass
|
||||
class ROR2Options(PerGameCommonOptions):
|
||||
goal: Goal
|
||||
victory: Victory
|
||||
total_locations: TotalLocations
|
||||
chests_per_stage: ChestsPerEnvironment
|
||||
shrines_per_stage: ShrinesPerEnvironment
|
||||
|
@ -294,6 +390,7 @@ class ROR2Options(PerGameCommonOptions):
|
|||
death_link: DeathLink
|
||||
item_pickup_step: ItemPickupStep
|
||||
shrine_use_step: ShrineUseStep
|
||||
enable_trap: AllowTrapItems
|
||||
enable_lunar: AllowLunarItems
|
||||
item_weights: ItemWeights
|
||||
item_pool_presets: ItemPoolPresetToggle
|
||||
|
@ -309,3 +406,10 @@ class ROR2Options(PerGameCommonOptions):
|
|||
lunar_item: LunarItem
|
||||
void_item: VoidItem
|
||||
equipment: Equipment
|
||||
money: Money
|
||||
lunar_coin: LunarCoin
|
||||
experience: Experience
|
||||
mountain_trap: MountainTrap
|
||||
time_warp_trap: TimeWarpTrap
|
||||
combat_trap: CombatTrap
|
||||
teleport_trap: TeleportTrap
|
|
@ -1,7 +1,10 @@
|
|||
from typing import Dict, List, NamedTuple, Optional
|
||||
from typing import Dict, List, NamedTuple, Optional, TYPE_CHECKING
|
||||
|
||||
from BaseClasses import MultiWorld, Region, Entrance
|
||||
from .Locations import location_table, RiskOfRainLocation
|
||||
from BaseClasses import Region, Entrance, MultiWorld
|
||||
from .locations import location_table, RiskOfRainLocation, get_classic_item_pickups
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import RiskOfRainWorld
|
||||
|
||||
|
||||
class RoRRegionData(NamedTuple):
|
||||
|
@ -9,10 +12,14 @@ class RoRRegionData(NamedTuple):
|
|||
region_exits: Optional[List[str]]
|
||||
|
||||
|
||||
def create_regions(multiworld: MultiWorld, player: int):
|
||||
def create_explore_regions(ror2_world: "RiskOfRainWorld") -> None:
|
||||
player = ror2_world.player
|
||||
ror2_options = ror2_world.options
|
||||
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", "Distant Roost (2)",
|
||||
"Titanic Plains", "Titanic Plains (2)"]),
|
||||
"Distant Roost": RoRRegionData([], ["OrderedStage_1"]),
|
||||
"Distant Roost (2)": RoRRegionData([], ["OrderedStage_1"]),
|
||||
"Titanic Plains": RoRRegionData([], ["OrderedStage_1"]),
|
||||
|
@ -34,33 +41,36 @@ def create_regions(multiworld: MultiWorld, player: int):
|
|||
}
|
||||
other_regions: Dict[str, RoRRegionData] = {
|
||||
"Commencement": RoRRegionData(None, ["Victory", "Petrichor V"]),
|
||||
"OrderedStage_5": RoRRegionData(None, ["Hidden Realm: A Moment, Fractured", "Commencement"]),
|
||||
"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"]),
|
||||
"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_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"]),
|
||||
"Hidden Realm: A Moment, Whole": RoRRegionData(None, ["Victory", "Petrichor V"]),
|
||||
"Void Fields": RoRRegionData(None, []),
|
||||
"Victory": RoRRegionData(None, None),
|
||||
"Petrichor V": RoRRegionData(None, ["Victory"]),
|
||||
"Petrichor V": RoRRegionData(None, []),
|
||||
"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"]),
|
||||
"The Planetarium": RoRRegionData(None, ["Victory", "Petrichor V"]),
|
||||
"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])
|
||||
chests = int(ror2_options.chests_per_stage)
|
||||
shrines = int(ror2_options.shrines_per_stage)
|
||||
scavengers = int(ror2_options.scavengers_per_stage)
|
||||
scanners = int(ror2_options.scanner_per_stage)
|
||||
newt = int(ror2_options.altars_per_stage)
|
||||
all_location_regions = {**non_dlc_regions}
|
||||
if multiworld.dlc_sotv[player]:
|
||||
if ror2_options.dlc_sotv:
|
||||
all_location_regions = {**non_dlc_regions, **dlc_regions}
|
||||
|
||||
# Locations
|
||||
|
@ -88,23 +98,35 @@ def create_regions(multiworld: MultiWorld, player: int):
|
|||
regions_pool: Dict = {**all_location_regions, **other_regions}
|
||||
|
||||
# DLC Locations
|
||||
if multiworld.dlc_sotv[player]:
|
||||
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}
|
||||
|
||||
# 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)
|
||||
elif ror2_options.victory == "voidling":
|
||||
other_regions["Commencement"].region_exits.pop(0)
|
||||
other_regions["Hidden Realm: A Moment, Whole"].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)
|
||||
|
||||
# Create all the regions
|
||||
for name, data in regions_pool.items():
|
||||
multiworld.regions.append(create_region(multiworld, player, name, data))
|
||||
multiworld.regions.append(create_explore_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):
|
||||
def create_explore_region(multiworld: MultiWorld, player: int, name: str, data: RoRRegionData) -> Region:
|
||||
region = Region(name, player, multiworld)
|
||||
if data.locations:
|
||||
for location_name in data.locations:
|
||||
|
@ -115,7 +137,7 @@ def create_region(multiworld: MultiWorld, player: int, name: str, data: RoRRegio
|
|||
return region
|
||||
|
||||
|
||||
def create_connections_in_regions(multiworld: MultiWorld, player: int, name: str, data: RoRRegionData):
|
||||
def create_connections_in_regions(multiworld: MultiWorld, player: int, name: str, data: RoRRegionData) -> None:
|
||||
region = multiworld.get_region(name, player)
|
||||
if data.region_exits:
|
||||
for region_exit in data.region_exits:
|
||||
|
@ -123,3 +145,34 @@ def create_connections_in_regions(multiworld: MultiWorld, player: int, name: str
|
|||
exit_region = multiworld.get_region(region_exit, player)
|
||||
r_exit_stage.connect(exit_region)
|
||||
region.exits.append(r_exit_stage)
|
||||
|
||||
|
||||
def create_classic_regions(ror2_world: "RiskOfRainWorld") -> None:
|
||||
player = ror2_world.player
|
||||
ror2_options = ror2_world.options
|
||||
multiworld = ror2_world.multiworld
|
||||
menu = create_classic_region(multiworld, player, "Menu")
|
||||
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_classic_region(multiworld, player, "Victory")
|
||||
multiworld.regions.append(victory_region)
|
||||
petrichor = create_classic_region(multiworld, player, "Petrichor V",
|
||||
get_classic_item_pickups(ror2_options.total_locations.value))
|
||||
multiworld.regions.append(petrichor)
|
||||
|
||||
# classic mode can get to victory from the beginning of the game
|
||||
to_victory = Entrance(player, "beating game", petrichor)
|
||||
petrichor.exits.append(to_victory)
|
||||
to_victory.connect(victory_region)
|
||||
|
||||
connection = Entrance(player, "Lobby", menu)
|
||||
menu.exits.append(connection)
|
||||
connection.connect(petrichor)
|
||||
|
||||
|
||||
def create_classic_region(multiworld: MultiWorld, player: int, name: str, locations: Dict[str, int] = {}) -> Region:
|
||||
ret = Region(name, player, multiworld)
|
||||
for location_name, location_id in locations.items():
|
||||
ret.locations.append(RiskOfRainLocation(player, location_name, location_id, ret))
|
||||
return ret
|
|
@ -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_special_table: Dict[str, int] = {
|
||||
"Void Locus": 46, # voidstage
|
||||
"The Planetarium": 45, # 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_dict_1: 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_dict_1)
|
||||
for list_of_dict_n in args:
|
||||
length = max(length, len(list_of_dict_n))
|
||||
|
||||
# create a combined list with a length the same as the longest list
|
||||
collapsed: List[Dict[X, Y]] = [{}] * length
|
||||
# The reason the list_of_dict_1 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_dict_1
|
||||
for i in range(len(list_of_dict_1)):
|
||||
collapsed[i] = {**collapsed[i], **list_of_dict_1[i]}
|
||||
|
||||
# merge contents of remaining lists_of_dicts
|
||||
for list_of_dict_n in args:
|
||||
for i in range(len(list_of_dict_n)):
|
||||
collapsed[i] = {**collapsed[i], **list_of_dict_n[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_table = \
|
||||
{**compress_dict_list_horizontal(environment_sotv_orderedstages_table), **environment_sotv_special_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}
|
||||
|
||||
|
||||
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,62 +1,71 @@
|
|||
from BaseClasses import MultiWorld, CollectionState
|
||||
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
|
||||
from BaseClasses import MultiWorld
|
||||
from .locations import get_locations
|
||||
from .ror2environments import environment_vanilla_orderedstages_table, environment_sotv_orderedstages_table
|
||||
from typing import Set, TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from . import RiskOfRainWorld
|
||||
|
||||
|
||||
# Rule to see if it has access to the previous stage
|
||||
def has_entrance_access_rule(multiworld: MultiWorld, stage: str, entrance: str, player: int):
|
||||
def has_entrance_access_rule(multiworld: MultiWorld, stage: str, entrance: str, player: int) -> None:
|
||||
multiworld.get_entrance(entrance, player).access_rule = \
|
||||
lambda state: state.has(entrance, player) and state.has(stage, player)
|
||||
|
||||
|
||||
def has_all_items(multiworld: MultiWorld, items: Set[str], entrance: str, player: int) -> None:
|
||||
multiworld.get_entrance(entrance, player).access_rule = \
|
||||
lambda state: state.has_all(items, player) and state.has(entrance, 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):
|
||||
def has_location_access_rule(multiworld: MultiWorld, environment: str, player: int, item_number: int, item_type: str)\
|
||||
-> None:
|
||||
if item_number == 1:
|
||||
multiworld.get_location(f"{environment}: {item_type} {item_number}", player).access_rule = \
|
||||
lambda state: state.has(environment, player)
|
||||
# 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).
|
||||
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)
|
||||
lambda state: state.has(environment, player) and state.has("Stage 5", 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):
|
||||
def check_location(state, environment: str, player: int, item_number: int, item_name: str) -> bool:
|
||||
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_stage_event(multiworld: MultiWorld, player: int, stage_number: int) -> None:
|
||||
if stage_number == 4:
|
||||
return
|
||||
multiworld.get_entrance(f"OrderedStage_{stage_number + 1}", player).access_rule = \
|
||||
lambda state: state.has(f"Stage {stage_number + 1}", 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":
|
||||
def set_rules(ror2_world: "RiskOfRainWorld") -> None:
|
||||
player = ror2_world.player
|
||||
multiworld = ror2_world.multiworld
|
||||
ror2_options = ror2_world.options
|
||||
if ror2_options.goal == "classic":
|
||||
# classic mode
|
||||
total_locations = multiworld.total_locations[player].value # total locations for current player
|
||||
total_locations = ror2_options.total_locations.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
|
||||
get_locations(
|
||||
chests=ror2_options.chests_per_stage.value,
|
||||
shrines=ror2_options.shrines_per_stage.value,
|
||||
scavengers=ror2_options.scavengers_per_stage.value,
|
||||
scanners=ror2_options.scanner_per_stage.value,
|
||||
altars=ror2_options.altars_per_stage.value,
|
||||
dlc_sotv=bool(ror2_options.dlc_sotv.value)
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -64,14 +73,15 @@ def set_rules(multiworld: MultiWorld, player: int) -> None:
|
|||
divisions = total_locations // event_location_step
|
||||
total_revivals = multiworld.worlds[player].total_revivals # pulling this info we calculated in generate_basic
|
||||
|
||||
if multiworld.goal[player] == "classic":
|
||||
if ror2_options.goal == "classic":
|
||||
# classic mode
|
||||
if divisions:
|
||||
for i in range(1, divisions + 1): # since divisions is the floor of total_locations / 25
|
||||
if i * event_location_step != total_locations:
|
||||
event_loc = multiworld.get_location(f"Pickup{i * event_location_step}", player)
|
||||
set_rule(event_loc,
|
||||
lambda state, i=i: state.can_reach(f"ItemPickup{i * event_location_step - 1}", "Location", player))
|
||||
lambda state, i=i: state.can_reach(f"ItemPickup{i * event_location_step - 1}",
|
||||
"Location", player))
|
||||
# we want to create a rule for each of the 25 locations per division
|
||||
for n in range(i * event_location_step, (i + 1) * event_location_step + 1):
|
||||
if n > total_locations:
|
||||
|
@ -84,27 +94,18 @@ def set_rules(multiworld: MultiWorld, player: int) -> None:
|
|||
lambda state, n=n: state.can_reach(f"ItemPickup{n - 1}", "Location", player))
|
||||
set_rule(multiworld.get_location("Victory", player),
|
||||
lambda state: state.can_reach(f"ItemPickup{total_locations}", "Location", player))
|
||||
if total_revivals or multiworld.start_with_revive[player].value:
|
||||
if total_revivals or ror2_options.start_with_revive.value:
|
||||
add_rule(multiworld.get_location("Victory", player),
|
||||
lambda state: state.has("Dio's Best Friend", player,
|
||||
total_revivals + multiworld.start_with_revive[player]))
|
||||
total_revivals + ror2_options.start_with_revive))
|
||||
|
||||
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(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]
|
||||
else:
|
||||
# explore mode
|
||||
chests = ror2_options.chests_per_stage.value
|
||||
shrines = ror2_options.shrines_per_stage.value
|
||||
newts = ror2_options.altars_per_stage.value
|
||||
scavengers = ror2_options.scavengers_per_stage.value
|
||||
scanners = ror2_options.scanner_per_stage.value
|
||||
for i in range(len(environment_vanilla_orderedstages_table)):
|
||||
for environment_name, _ in environment_vanilla_orderedstages_table[i].items():
|
||||
# Make sure to go through each location
|
||||
|
@ -120,10 +121,10 @@ def set_rules(multiworld: MultiWorld, player: int) -> None:
|
|||
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"Stage {i}", environment_name, player)
|
||||
get_stage_event(multiworld, player, i)
|
||||
|
||||
if multiworld.dlc_sotv[player]:
|
||||
if ror2_options.dlc_sotv:
|
||||
for i in range(len(environment_sotv_orderedstages_table)):
|
||||
for environment_name, _ in environment_sotv_orderedstages_table[i].items():
|
||||
# Make sure to go through each location
|
||||
|
@ -139,16 +140,19 @@ def set_rules(multiworld: MultiWorld, player: int) -> None:
|
|||
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"Hidden Realm: A Moment, Fractured", "Hidden Realm: A Moment, Whole",
|
||||
has_entrance_access_rule(multiworld, f"Stage {i}", environment_name, player)
|
||||
has_entrance_access_rule(multiworld, "Hidden Realm: A Moment, Fractured", "Hidden Realm: A Moment, Whole",
|
||||
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)
|
||||
has_entrance_access_rule(multiworld, "Stage 1", "Hidden Realm: Bazaar Between Time", player)
|
||||
has_entrance_access_rule(multiworld, "Hidden Realm: Bazaar Between Time", "Void Fields", player)
|
||||
has_entrance_access_rule(multiworld, "Stage 5", "Commencement", player)
|
||||
has_entrance_access_rule(multiworld, "Stage 5", "Hidden Realm: A Moment, Fractured", player)
|
||||
has_entrance_access_rule(multiworld, "Beads of Fealty", "Hidden Realm: A Moment, Whole", player)
|
||||
if 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)
|
||||
if ror2_options.dlc_sotv:
|
||||
has_entrance_access_rule(multiworld, "Stage 5", "The Planetarium", player)
|
||||
has_entrance_access_rule(multiworld, "Stage 5", "Void Locus", player)
|
||||
if ror2_options.victory == "voidling":
|
||||
has_all_items(multiworld, {"Stage 5", "The Planetarium"}, "Commencement", player)
|
||||
|
||||
# Win Condition
|
||||
multiworld.completion_condition[player] = lambda state: state.has("Victory", player)
|
|
@ -0,0 +1,5 @@
|
|||
from test.bases import WorldTestBase
|
||||
|
||||
|
||||
class RoR2TestBase(WorldTestBase):
|
||||
game = "Risk of Rain 2"
|
|
@ -0,0 +1,26 @@
|
|||
from . import RoR2TestBase
|
||||
|
||||
|
||||
class DLCTest(RoR2TestBase):
|
||||
options = {
|
||||
"dlc_sotv": "true",
|
||||
"victory": "any"
|
||||
}
|
||||
|
||||
def test_commencement_victory(self) -> None:
|
||||
self.collect_all_but(["Commencement", "The Planetarium", "Hidden Realm: A Moment, Whole", "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.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.assertBeatable(False)
|
||||
self.collect_by_name("Hidden Realm: A Moment, Whole")
|
||||
self.assertBeatable(True)
|
|
@ -0,0 +1,7 @@
|
|||
from . import RoR2TestBase
|
||||
|
||||
|
||||
class ClassicTest(RoR2TestBase):
|
||||
options = {
|
||||
"goal": "classic",
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
from . import RoR2TestBase
|
||||
|
||||
|
||||
class LimboGoalTest(RoR2TestBase):
|
||||
options = {
|
||||
"victory": "limbo"
|
||||
}
|
||||
|
||||
def test_limbo(self) -> None:
|
||||
self.collect_all_but(["Hidden Realm: A Moment, Whole", "Victory"])
|
||||
self.assertFalse(self.can_reach_entrance("Hidden Realm: A Moment, Whole"))
|
||||
self.assertBeatable(False)
|
||||
self.collect_by_name("Hidden Realm: A Moment, Whole")
|
||||
self.assertTrue(self.can_reach_entrance("Hidden Realm: A Moment, Whole"))
|
||||
self.assertBeatable(True)
|
|
@ -0,0 +1,25 @@
|
|||
from . import RoR2TestBase
|
||||
|
||||
|
||||
class MithrixGoalTest(RoR2TestBase):
|
||||
options = {
|
||||
"victory": "mithrix"
|
||||
}
|
||||
|
||||
def test_mithrix(self) -> None:
|
||||
self.collect_all_but(["Commencement", "Victory"])
|
||||
self.assertFalse(self.can_reach_entrance("Commencement"))
|
||||
self.assertBeatable(False)
|
||||
self.collect_by_name("Commencement")
|
||||
self.assertTrue(self.can_reach_entrance("Commencement"))
|
||||
self.assertBeatable(True)
|
||||
|
||||
def test_stage5(self) -> None:
|
||||
self.collect_all_but(["Stage 4", "Sky Meadow", "Victory"])
|
||||
self.assertFalse(self.can_reach_entrance("Sky Meadow"))
|
||||
self.assertBeatable(False)
|
||||
self.collect_by_name("Sky Meadow")
|
||||
self.assertFalse(self.can_reach_entrance("Sky Meadow"))
|
||||
self.collect_by_name("Stage 4")
|
||||
self.assertTrue(self.can_reach_entrance("Sky Meadow"))
|
||||
self.assertBeatable(True)
|
|
@ -0,0 +1,28 @@
|
|||
from . import RoR2TestBase
|
||||
|
||||
|
||||
class VoidlingGoalTest(RoR2TestBase):
|
||||
options = {
|
||||
"dlc_sotv": "true",
|
||||
"victory": "voidling"
|
||||
}
|
||||
|
||||
def test_planetarium(self) -> None:
|
||||
self.collect_all_but(["The Planetarium", "Victory"])
|
||||
self.assertFalse(self.can_reach_entrance("The Planetarium"))
|
||||
self.assertBeatable(False)
|
||||
self.collect_by_name("The Planetarium")
|
||||
self.assertTrue(self.can_reach_entrance("The Planetarium"))
|
||||
self.assertBeatable(True)
|
||||
|
||||
def test_void_locus_to_victory(self) -> None:
|
||||
self.collect_all_but(["Void Locus", "Commencement"])
|
||||
self.assertFalse(self.can_reach_location("Victory"))
|
||||
self.collect_by_name("Void Locus")
|
||||
self.assertTrue(self.can_reach_entrance("Victory"))
|
||||
|
||||
def test_commencement_to_victory(self) -> None:
|
||||
self.collect_all_but(["Void Locus", "Commencement"])
|
||||
self.assertFalse(self.can_reach_location("Victory"))
|
||||
self.collect_by_name("Commencement")
|
||||
self.assertTrue(self.can_reach_location("Victory"))
|
Loading…
Reference in New Issue