diff --git a/worlds/ror2/Items.py b/worlds/ror2/Items.py index c4ece1dc..9efbc713 100644 --- a/worlds/ror2/Items.py +++ b/worlds/ror2/Items.py @@ -1,143 +1,154 @@ +from typing import Dict from BaseClasses import Item -import typing +from .Options import ItemWeights + class RiskOfRainItem(Item): game: str = "Risk of Rain 2" + # 37000 - 38000 -item_table = { - "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, - "Victory": None, - "Beat Level One": None, - "Beat Level Two": None, - "Beat Level Three": None, - "Beat Level Four": None, - "Beat Level Five": None, +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 } -default_weights = { - "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, - "Equipment": 32 +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, + "Equipment": 32 } -new_weights = { - "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, - "Equipment": 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, + "Lunar Item": 10, + "Equipment": 20 } -uncommon_weights = { - "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, - "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, + "Equipment": 20 } -legendary_weights = { - "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, - "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, + "Equipment": 20 } -lunartic_weights = { - "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, - "Equipment": 0 +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, + "Equipment": 0 } -no_scraps_weights = { - "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, - "Equipment": 25 +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, + "Equipment": 40 } -even_weights = { - "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, - "Equipment": 1 +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, + "Equipment": 25 } -scraps_only = { - "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, - "Equipment": 0 +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, + "Equipment": 1 } -item_pool_weights: typing.Dict[int, typing.Dict[str, int]] = { - 0: default_weights, - 1: new_weights, - 2: uncommon_weights, - 3: legendary_weights, - 4: lunartic_weights, - 6: no_scraps_weights, - 7: even_weights, - 8: scraps_only +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, + "Equipment": 0 } -lookup_id_to_name: typing.Dict[int, str] = {id: name for name, id in item_table.items() if id} +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 +} + +lookup_id_to_name: Dict[int, str] = {id: name for name, id in item_table.items()} diff --git a/worlds/ror2/Locations.py b/worlds/ror2/Locations.py index ae6ccea2..e4ebe8dd 100644 --- a/worlds/ror2/Locations.py +++ b/worlds/ror2/Locations.py @@ -1,19 +1,13 @@ +from typing import Dict from BaseClasses import Location -import typing +from .Options import TotalLocations + class RiskOfRainLocation(Location): game: str = "Risk of Rain 2" -# 37000 - 38000 -base_location_table = { - "Victory": None, -} # 37006 - 37506 -item_pickups = { - f"ItemPickup{i}": 37005+i for i in range(1, 501) +item_pickups: Dict[str, int] = { + f"ItemPickup{i+1}": 37000+i for i in range(TotalLocations.range_end) } - -location_table = {**base_location_table, **item_pickups} - -lookup_id_to_name: typing.Dict[int, str] = {id: name for name, id in location_table.items()} diff --git a/worlds/ror2/Options.py b/worlds/ror2/Options.py index 727d01ff..a95cbf59 100644 --- a/worlds/ror2/Options.py +++ b/worlds/ror2/Options.py @@ -1,4 +1,4 @@ -import typing +from typing import Dict from Options import Option, DefaultOnToggle, Range, Choice @@ -36,7 +36,8 @@ class AllowLunarItems(DefaultOnToggle): class StartWithRevive(DefaultOnToggle): """Start the game with a `Dio's Best Friend` item.""" display_name = "Start with a Revive" - + + class FinalStageDeath(DefaultOnToggle): """Death on the final boss stage counts as a win.""" display_name = "Final Stage Death is Win" @@ -124,7 +125,7 @@ class Equipment(Range): class ItemPoolPresetToggle(DefaultOnToggle): """Will use the item weight presets when set to true, otherwise will use the custom set item pool weights.""" - display_name = "Item Weight Presets" + display_name = "Use Item Weight Presets" class ItemWeights(Choice): @@ -150,7 +151,7 @@ class ItemWeights(Choice): # define a dictionary for the weights of the generated item pool. -ror2_weights: typing.Dict[str, type(Option)] = { +ror2_weights: Dict[str, type(Option)] = { "green_scrap": GreenScrap, "red_scrap": RedScrap, "yellow_scrap": YellowScrap, @@ -163,7 +164,7 @@ ror2_weights: typing.Dict[str, type(Option)] = { "equipment": Equipment } -ror2_options: typing.Dict[str, type(Option)] = { +ror2_options: Dict[str, type(Option)] = { "total_locations": TotalLocations, "total_revivals": TotalRevivals, "start_with_revive": StartWithRevive, diff --git a/worlds/ror2/Rules.py b/worlds/ror2/Rules.py index 64d741f9..bf00f617 100644 --- a/worlds/ror2/Rules.py +++ b/worlds/ror2/Rules.py @@ -2,29 +2,32 @@ from BaseClasses import MultiWorld from worlds.generic.Rules import set_rule, add_rule -def set_rules(world: MultiWorld, player: int): - total_locations = world.total_locations[player] # total locations for current player +def set_rules(world: MultiWorld, player: int) -> None: + total_locations = world.total_locations[player].value # total locations for current player event_location_step = 25 # set an event location at these locations for "spheres" divisions = total_locations // event_location_step + total_revivals = world.worlds[player].total_revivals # pulling this info we calculated in generate_basic if divisions: for i in range(1, divisions): # since divisions is the floor of total_locations / 25 event_loc = world.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)) + set_rule(event_loc, + lambda state, i=i: state.can_reach(f"ItemPickup{i * event_location_step - 1}", "Location", 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 if n == i * event_location_step: - set_rule(world.get_location(f"ItemPickup{n}", player), lambda state, event_item=event_loc.item.name: state.has(event_item, player)) + set_rule(world.get_location(f"ItemPickup{n}", player), + lambda state, event_item=event_loc.item.name: state.has(event_item, player)) else: set_rule(world.get_location(f"ItemPickup{n}", player), - lambda state, n = n: state.can_reach(f"ItemPickup{n - 1}", 'Location', player)) + lambda state, n=n: state.can_reach(f"ItemPickup{n - 1}", "Location", player)) for i in range(divisions * event_location_step, total_locations+1): - set_rule(world.get_location(f"ItemPickup{i}", player), lambda state, i=i: state.can_reach(f"ItemPickup{i - 1}", "Location", player)) - + set_rule(world.get_location(f"ItemPickup{i}", player), + lambda state, i=i: state.can_reach(f"ItemPickup{i - 1}", "Location", player)) set_rule(world.get_location("Victory", player), lambda state: state.can_reach(f"ItemPickup{total_locations}", "Location", player)) - if world.total_revivals[player] or world.start_with_revive[player]: - total_revivals = world.total_revivals[player] * world.total_locations[player] // 100 + if total_revivals or world.start_with_revive[player].value: add_rule(world.get_location("Victory", player), - lambda state: state.has("Dio's Best Friend", player, total_revivals + world.start_with_revive[player].value)) + lambda state: state.has("Dio's Best Friend", player, + total_revivals + world.start_with_revive[player])) world.completion_condition[player] = lambda state: state.has("Victory", player) diff --git a/worlds/ror2/__init__.py b/worlds/ror2/__init__.py index 9d0d693b..af65a15e 100644 --- a/worlds/ror2/__init__.py +++ b/worlds/ror2/__init__.py @@ -1,10 +1,11 @@ import string +from typing import Dict, List from .Items import RiskOfRainItem, item_table, item_pool_weights -from .Locations import location_table, RiskOfRainLocation, base_location_table +from .Locations import RiskOfRainLocation, item_pickups from .Rules import set_rules from BaseClasses import Region, RegionType, Entrance, Item, ItemClassification, MultiWorld, Tutorial -from .Options import ror2_options +from .Options import ror2_options, ItemWeights from worlds.AutoWorld import World, WebWorld client_version = 1 @@ -32,34 +33,31 @@ class RiskOfRainWorld(World): topology_present = False item_name_to_id = item_table - location_name_to_id = location_table + location_name_to_id = item_pickups - data_version = 3 + data_version = 4 forced_auto_forfeit = True web = RiskOfWeb() + total_revivals: int - def generate_basic(self): + def generate_early(self) -> None: + # figure out how many revivals should exist in the pool + self.total_revivals = int(self.world.total_revivals[self.player].value / 100 * + self.world.total_locations[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.world.start_with_revive[self.player].value: self.world.push_precollected(self.world.create_item("Dio's Best Friend", self.player)) # if presets are enabled generate junk_pool from the selected preset pool_option = self.world.item_weights[self.player].value - if self.world.item_pool_presets[self.player].value: + junk_pool: Dict[str, int] = {} + if self.world.item_pool_presets[self.player]: # generate chaos weights if the preset is chosen - if pool_option == 5: - junk_pool = { - "Item Scrap, Green": self.world.random.randint(0, 80), - "Item Scrap, Red": self.world.random.randint(0, 45), - "Item Scrap, Yellow": self.world.random.randint(0, 30), - "Item Scrap, White": self.world.random.randint(0, 100), - "Common Item": self.world.random.randint(0, 100), - "Uncommon Item": self.world.random.randint(0, 70), - "Legendary Item": self.world.random.randint(0, 30), - "Boss Item": self.world.random.randint(0, 20), - "Lunar Item": self.world.random.randint(0, 60), - "Equipment": self.world.random.randint(0, 40) - } + if pool_option == ItemWeights.option_chaos: + for name, max_value in item_pool_weights[pool_option].items(): + junk_pool[name] = self.world.random.randint(0, max_value) else: junk_pool = item_pool_weights[pool_option].copy() else: # generate junk pool from user created presets @@ -77,37 +75,43 @@ class RiskOfRainWorld(World): } # remove lunar items from the pool if they're disabled in the yaml unless lunartic is rolled - if not self.world.enable_lunar[self.player]: - if not pool_option == 4: - junk_pool.pop("Lunar Item") + if not (self.world.enable_lunar[self.player] or pool_option == ItemWeights.option_lunartic): + junk_pool.pop("Lunar Item") # Generate item pool - itempool = [] - + itempool: List = [] # Add revive items for the player - itempool += ["Dio's Best Friend"] * int(self.world.total_revivals[self.player] / 100 * self.world.total_locations[self.player]) + itempool += ["Dio's Best Friend"] * self.total_revivals # Fill remaining items with randomly generated junk itempool += self.world.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()), - k=self.world.total_locations[self.player] - - int(self.world.total_revivals[self.player] / 100 * self.world.total_locations[self.player])) + k=self.world.total_locations[self.player].value - self.total_revivals) # Convert itempool into real items itempool = list(map(lambda name: self.create_item(name), itempool)) self.world.itempool += itempool - def set_rules(self): + def set_rules(self) -> None: set_rules(self.world, self.player) - def create_regions(self): - create_regions(self.world, self.player) - create_events(self.world, self.player, int(self.world.total_locations[self.player])) + def create_regions(self) -> None: + menu = create_region(self.world, self.player, "Menu") + petrichor = create_region(self.world, self.player, "Petrichor V", + [f"ItemPickup{i + 1}" for i in range(self.world.total_locations[self.player].value)]) + + connection = Entrance(self.player, "Lobby", menu) + menu.exits.append(connection) + connection.connect(petrichor) + + self.world.regions += [menu, petrichor] + + create_events(self.world, self.player) def fill_slot_data(self): return { "itemPickupStep": self.world.item_pickup_step[self.player].value, - "seed": "".join(self.world.slot_seeds[self.player].choice(string.digits) for i in range(16)), + "seed": "".join(self.world.slot_seeds[self.player].choice(string.digits) for _ in range(16)), "totalLocations": self.world.total_locations[self.player].value, "totalRevivals": self.world.total_revivals[self.player].value, "startWithDio": self.world.start_with_revive[self.player].value, @@ -116,49 +120,39 @@ class RiskOfRainWorld(World): def create_item(self, name: str) -> Item: item_id = item_table[name] - item = RiskOfRainItem(name, ItemClassification.filler, item_id, self.player) if name == "Dio's Best Friend": - item.classification = ItemClassification.progression + classification = ItemClassification.progression elif name in {"Equipment", "Legendary Item"}: - item.classification = ItemClassification.useful + classification = ItemClassification.useful + else: + classification = ItemClassification.filler + item = RiskOfRainItem(name, classification, item_id, self.player) return item -def create_events(world: MultiWorld, player: int, total_locations: int): +def create_events(world: MultiWorld, player: int) -> None: + total_locations = world.total_locations[player].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) + for i in range(num_of_events): - event_loc = RiskOfRainLocation(player, f"Pickup{(i + 1) * 25}", None, world.get_region('Petrichor V', player)) + 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}", player)) - world.get_region('Petrichor V', player).locations.append(event_loc) + world_region.locations.append(event_loc) + + victory_event = RiskOfRainLocation(player, "Victory", None, world_region) + victory_event.place_locked_item(RiskOfRainItem("Victory", ItemClassification.progression, None, player)) + world_region.locations.append(victory_event) -# generate locations based on player setting -def create_regions(world, player: int): - world.regions += [ - create_region(world, player, 'Menu', None, ['Lobby']), - create_region(world, player, 'Petrichor V', - [location for location in base_location_table] + - [f"ItemPickup{i}" for i in range(1, 1 + world.total_locations[player])]) - ] - - world.get_entrance("Lobby", player).connect(world.get_region("Petrichor V", player)) - world.get_location("Victory", player).place_locked_item(RiskOfRainItem("Victory", ItemClassification.progression, - None, player)) - - -def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None): - ret = Region(name, RegionType.Generic, name, player) - ret.world = world +def create_region(world: MultiWorld, player: int, name: str, locations: List[str] = None) -> Region: + ret = Region(name, RegionType.Generic, name, player, world) if locations: for location in locations: - loc_id = location_table[location] + loc_id = item_pickups[location] location = RiskOfRainLocation(player, location, loc_id, ret) ret.locations.append(location) - if exits: - for exit in exits: - ret.exits.append(Entrance(player, exit, ret)) - return ret