RoR2: code cleanup and styling consistency (#833)

* build locations dict dynamically from the TotalLocations option. Minor styling cleanup

* Minor items styling cleanup. remove unused event items

* minor options cleanup. clarify preset toggle slightly better

* make items.py more readable. add chaos weights dict to use as reference point for generation

* small rules styling and consistency cleanup

* create less regions and other init cleanup

* move region creation to less function calls and move revivals calculation

* typing

* use enum instead of hardcoded ints. fix bug i introduced

* better typing
This commit is contained in:
alwaysintreble 2022-08-20 18:09:35 -05:00 committed by GitHub
parent be8c3131d8
commit fb122df5f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 206 additions and 203 deletions

View File

@ -1,143 +1,154 @@
from typing import Dict
from BaseClasses import Item from BaseClasses import Item
import typing from .Options import ItemWeights
class RiskOfRainItem(Item): class RiskOfRainItem(Item):
game: str = "Risk of Rain 2" game: str = "Risk of Rain 2"
# 37000 - 38000 # 37000 - 38000
item_table = { item_table: Dict[str, int] = {
"Dio's Best Friend": 37001, "Dio's Best Friend": 37001,
"Common Item": 37002, "Common Item": 37002,
"Uncommon Item": 37003, "Uncommon Item": 37003,
"Legendary Item": 37004, "Legendary Item": 37004,
"Boss Item": 37005, "Boss Item": 37005,
"Lunar Item": 37006, "Lunar Item": 37006,
"Equipment": 37007, "Equipment": 37007,
"Item Scrap, White": 37008, "Item Scrap, White": 37008,
"Item Scrap, Green": 37009, "Item Scrap, Green": 37009,
"Item Scrap, Red": 37010, "Item Scrap, Red": 37010,
"Item Scrap, Yellow": 37011, "Item Scrap, Yellow": 37011
"Victory": None,
"Beat Level One": None,
"Beat Level Two": None,
"Beat Level Three": None,
"Beat Level Four": None,
"Beat Level Five": None,
} }
default_weights = { default_weights: Dict[str, int] = {
"Item Scrap, Green": 16, "Item Scrap, Green": 16,
"Item Scrap, Red": 4, "Item Scrap, Red": 4,
"Item Scrap, Yellow": 1, "Item Scrap, Yellow": 1,
"Item Scrap, White": 32, "Item Scrap, White": 32,
"Common Item": 64, "Common Item": 64,
"Uncommon Item": 32, "Uncommon Item": 32,
"Legendary Item": 8, "Legendary Item": 8,
"Boss Item": 4, "Boss Item": 4,
"Lunar Item": 16, "Lunar Item": 16,
"Equipment": 32 "Equipment": 32
} }
new_weights = { new_weights: Dict[str, int] = {
"Item Scrap, Green": 15, "Item Scrap, Green": 15,
"Item Scrap, Red": 5, "Item Scrap, Red": 5,
"Item Scrap, Yellow": 1, "Item Scrap, Yellow": 1,
"Item Scrap, White": 30, "Item Scrap, White": 30,
"Common Item": 75, "Common Item": 75,
"Uncommon Item": 40, "Uncommon Item": 40,
"Legendary Item": 10, "Legendary Item": 10,
"Boss Item": 5, "Boss Item": 5,
"Lunar Item": 10, "Lunar Item": 10,
"Equipment": 20 "Equipment": 20
} }
uncommon_weights = { uncommon_weights: Dict[str, int] = {
"Item Scrap, Green": 45, "Item Scrap, Green": 45,
"Item Scrap, Red": 5, "Item Scrap, Red": 5,
"Item Scrap, Yellow": 1, "Item Scrap, Yellow": 1,
"Item Scrap, White": 30, "Item Scrap, White": 30,
"Common Item": 45, "Common Item": 45,
"Uncommon Item": 100, "Uncommon Item": 100,
"Legendary Item": 10, "Legendary Item": 10,
"Boss Item": 5, "Boss Item": 5,
"Lunar Item": 15, "Lunar Item": 15,
"Equipment": 20 "Equipment": 20
} }
legendary_weights = { legendary_weights: Dict[str, int] = {
"Item Scrap, Green": 15, "Item Scrap, Green": 15,
"Item Scrap, Red": 5, "Item Scrap, Red": 5,
"Item Scrap, Yellow": 1, "Item Scrap, Yellow": 1,
"Item Scrap, White": 30, "Item Scrap, White": 30,
"Common Item": 50, "Common Item": 50,
"Uncommon Item": 25, "Uncommon Item": 25,
"Legendary Item": 100, "Legendary Item": 100,
"Boss Item": 5, "Boss Item": 5,
"Lunar Item": 15, "Lunar Item": 15,
"Equipment": 20 "Equipment": 20
} }
lunartic_weights = { lunartic_weights: Dict[str, int] = {
"Item Scrap, Green": 0, "Item Scrap, Green": 0,
"Item Scrap, Red": 0, "Item Scrap, Red": 0,
"Item Scrap, Yellow": 0, "Item Scrap, Yellow": 0,
"Item Scrap, White": 0, "Item Scrap, White": 0,
"Common Item": 0, "Common Item": 0,
"Uncommon Item": 0, "Uncommon Item": 0,
"Legendary Item": 0, "Legendary Item": 0,
"Boss Item": 0, "Boss Item": 0,
"Lunar Item": 100, "Lunar Item": 100,
"Equipment": 0 "Equipment": 0
} }
no_scraps_weights = { chaos_weights: Dict[str, int] = {
"Item Scrap, Green": 0, "Item Scrap, Green": 80,
"Item Scrap, Red": 0, "Item Scrap, Red": 45,
"Item Scrap, Yellow": 0, "Item Scrap, Yellow": 30,
"Item Scrap, White": 0, "Item Scrap, White": 100,
"Common Item": 100, "Common Item": 100,
"Uncommon Item": 40, "Uncommon Item": 70,
"Legendary Item": 15, "Legendary Item": 30,
"Boss Item": 5, "Boss Item": 20,
"Lunar Item": 10, "Lunar Item": 60,
"Equipment": 25 "Equipment": 40
} }
even_weights = { no_scraps_weights: Dict[str, int] = {
"Item Scrap, Green": 1, "Item Scrap, Green": 0,
"Item Scrap, Red": 1, "Item Scrap, Red": 0,
"Item Scrap, Yellow": 1, "Item Scrap, Yellow": 0,
"Item Scrap, White": 1, "Item Scrap, White": 0,
"Common Item": 1, "Common Item": 100,
"Uncommon Item": 1, "Uncommon Item": 40,
"Legendary Item": 1, "Legendary Item": 15,
"Boss Item": 1, "Boss Item": 5,
"Lunar Item": 1, "Lunar Item": 10,
"Equipment": 1 "Equipment": 25
} }
scraps_only = { even_weights: Dict[str, int] = {
"Item Scrap, Green": 70, "Item Scrap, Green": 1,
"Item Scrap, White": 100, "Item Scrap, Red": 1,
"Item Scrap, Red": 30, "Item Scrap, Yellow": 1,
"Item Scrap, Yellow": 5, "Item Scrap, White": 1,
"Common Item": 0, "Common Item": 1,
"Uncommon Item": 0, "Uncommon Item": 1,
"Legendary Item": 0, "Legendary Item": 1,
"Boss Item": 0, "Boss Item": 1,
"Lunar Item": 0, "Lunar Item": 1,
"Equipment": 0 "Equipment": 1
} }
item_pool_weights: typing.Dict[int, typing.Dict[str, int]] = { scraps_only: Dict[str, int] = {
0: default_weights, "Item Scrap, Green": 70,
1: new_weights, "Item Scrap, White": 100,
2: uncommon_weights, "Item Scrap, Red": 30,
3: legendary_weights, "Item Scrap, Yellow": 5,
4: lunartic_weights, "Common Item": 0,
6: no_scraps_weights, "Uncommon Item": 0,
7: even_weights, "Legendary Item": 0,
8: scraps_only "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()}

View File

@ -1,19 +1,13 @@
from typing import Dict
from BaseClasses import Location from BaseClasses import Location
import typing from .Options import TotalLocations
class RiskOfRainLocation(Location): class RiskOfRainLocation(Location):
game: str = "Risk of Rain 2" game: str = "Risk of Rain 2"
# 37000 - 38000
base_location_table = {
"Victory": None,
}
# 37006 - 37506 # 37006 - 37506
item_pickups = { item_pickups: Dict[str, int] = {
f"ItemPickup{i}": 37005+i for i in range(1, 501) 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()}

View File

@ -1,4 +1,4 @@
import typing from typing import Dict
from Options import Option, DefaultOnToggle, Range, Choice from Options import Option, DefaultOnToggle, Range, Choice
@ -36,7 +36,8 @@ class AllowLunarItems(DefaultOnToggle):
class StartWithRevive(DefaultOnToggle): class StartWithRevive(DefaultOnToggle):
"""Start the game with a `Dio's Best Friend` item.""" """Start the game with a `Dio's Best Friend` item."""
display_name = "Start with a Revive" display_name = "Start with a Revive"
class FinalStageDeath(DefaultOnToggle): class FinalStageDeath(DefaultOnToggle):
"""Death on the final boss stage counts as a win.""" """Death on the final boss stage counts as a win."""
display_name = "Final Stage Death is Win" display_name = "Final Stage Death is Win"
@ -124,7 +125,7 @@ class Equipment(Range):
class ItemPoolPresetToggle(DefaultOnToggle): class ItemPoolPresetToggle(DefaultOnToggle):
"""Will use the item weight presets when set to true, otherwise will use the custom set item pool weights.""" """Will use the item weight presets when set to true, otherwise will use the custom set item pool weights."""
display_name = "Item Weight Presets" display_name = "Use Item Weight Presets"
class ItemWeights(Choice): class ItemWeights(Choice):
@ -150,7 +151,7 @@ class ItemWeights(Choice):
# define a dictionary for the weights of the generated item pool. # 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, "green_scrap": GreenScrap,
"red_scrap": RedScrap, "red_scrap": RedScrap,
"yellow_scrap": YellowScrap, "yellow_scrap": YellowScrap,
@ -163,7 +164,7 @@ ror2_weights: typing.Dict[str, type(Option)] = {
"equipment": Equipment "equipment": Equipment
} }
ror2_options: typing.Dict[str, type(Option)] = { ror2_options: Dict[str, type(Option)] = {
"total_locations": TotalLocations, "total_locations": TotalLocations,
"total_revivals": TotalRevivals, "total_revivals": TotalRevivals,
"start_with_revive": StartWithRevive, "start_with_revive": StartWithRevive,

View File

@ -2,29 +2,32 @@ from BaseClasses import MultiWorld
from worlds.generic.Rules import set_rule, add_rule from worlds.generic.Rules import set_rule, add_rule
def set_rules(world: MultiWorld, player: int): def set_rules(world: MultiWorld, player: int) -> None:
total_locations = world.total_locations[player] # total locations for current player 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" event_location_step = 25 # set an event location at these locations for "spheres"
divisions = total_locations // event_location_step divisions = total_locations // event_location_step
total_revivals = world.worlds[player].total_revivals # pulling this info we calculated in generate_basic
if divisions: if divisions:
for i in range(1, divisions): # since divisions is the floor of total_locations / 25 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) 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 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: 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: else:
set_rule(world.get_location(f"ItemPickup{n}", player), 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): 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), set_rule(world.get_location("Victory", player),
lambda state: state.can_reach(f"ItemPickup{total_locations}", "Location", player)) lambda state: state.can_reach(f"ItemPickup{total_locations}", "Location", player))
if world.total_revivals[player] or world.start_with_revive[player]: if total_revivals or world.start_with_revive[player].value:
total_revivals = world.total_revivals[player] * world.total_locations[player] // 100
add_rule(world.get_location("Victory", player), 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) world.completion_condition[player] = lambda state: state.has("Victory", player)

View File

@ -1,10 +1,11 @@
import string import string
from typing import Dict, List
from .Items import RiskOfRainItem, item_table, item_pool_weights 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 .Rules import set_rules
from BaseClasses import Region, RegionType, Entrance, Item, ItemClassification, MultiWorld, Tutorial 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 from worlds.AutoWorld import World, WebWorld
client_version = 1 client_version = 1
@ -32,34 +33,31 @@ class RiskOfRainWorld(World):
topology_present = False topology_present = False
item_name_to_id = item_table 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 forced_auto_forfeit = True
web = RiskOfWeb() 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 # 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: if self.world.start_with_revive[self.player].value:
self.world.push_precollected(self.world.create_item("Dio's Best Friend", self.player)) 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 # if presets are enabled generate junk_pool from the selected preset
pool_option = self.world.item_weights[self.player].value 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 # generate chaos weights if the preset is chosen
if pool_option == 5: if pool_option == ItemWeights.option_chaos:
junk_pool = { for name, max_value in item_pool_weights[pool_option].items():
"Item Scrap, Green": self.world.random.randint(0, 80), junk_pool[name] = self.world.random.randint(0, max_value)
"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)
}
else: else:
junk_pool = item_pool_weights[pool_option].copy() junk_pool = item_pool_weights[pool_option].copy()
else: # generate junk pool from user created presets 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 # 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 (self.world.enable_lunar[self.player] or pool_option == ItemWeights.option_lunartic):
if not pool_option == 4: junk_pool.pop("Lunar Item")
junk_pool.pop("Lunar Item")
# Generate item pool # Generate item pool
itempool = [] itempool: List = []
# Add revive items for the player # 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 # Fill remaining items with randomly generated junk
itempool += self.world.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()), itempool += self.world.random.choices(list(junk_pool.keys()), weights=list(junk_pool.values()),
k=self.world.total_locations[self.player] - k=self.world.total_locations[self.player].value - self.total_revivals)
int(self.world.total_revivals[self.player] / 100 * self.world.total_locations[self.player]))
# Convert itempool into real items # Convert itempool into real items
itempool = list(map(lambda name: self.create_item(name), itempool)) itempool = list(map(lambda name: self.create_item(name), itempool))
self.world.itempool += itempool self.world.itempool += itempool
def set_rules(self): def set_rules(self) -> None:
set_rules(self.world, self.player) set_rules(self.world, self.player)
def create_regions(self): def create_regions(self) -> None:
create_regions(self.world, self.player) menu = create_region(self.world, self.player, "Menu")
create_events(self.world, self.player, int(self.world.total_locations[self.player])) 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): def fill_slot_data(self):
return { return {
"itemPickupStep": self.world.item_pickup_step[self.player].value, "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, "totalLocations": self.world.total_locations[self.player].value,
"totalRevivals": self.world.total_revivals[self.player].value, "totalRevivals": self.world.total_revivals[self.player].value,
"startWithDio": self.world.start_with_revive[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: def create_item(self, name: str) -> Item:
item_id = item_table[name] item_id = item_table[name]
item = RiskOfRainItem(name, ItemClassification.filler, item_id, self.player)
if name == "Dio's Best Friend": if name == "Dio's Best Friend":
item.classification = ItemClassification.progression classification = ItemClassification.progression
elif name in {"Equipment", "Legendary Item"}: 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 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 num_of_events = total_locations // 25
if total_locations / 25 == num_of_events: if total_locations / 25 == num_of_events:
num_of_events -= 1 num_of_events -= 1
world_region = world.get_region("Petrichor V", player)
for i in range(num_of_events): 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.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)) 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_region(world: MultiWorld, player: int, name: str, locations: List[str] = None) -> Region:
def create_regions(world, player: int): ret = Region(name, RegionType.Generic, name, player, world)
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
if locations: if locations:
for location in locations: for location in locations:
loc_id = location_table[location] loc_id = item_pickups[location]
location = RiskOfRainLocation(player, location, loc_id, ret) location = RiskOfRainLocation(player, location, loc_id, ret)
ret.locations.append(location) ret.locations.append(location)
if exits:
for exit in exits:
ret.exits.append(Entrance(player, exit, ret))
return ret return ret