OSRS: Fix UT Integration and Various Gen Failures (#5331)

This commit is contained in:
Faris
2025-08-16 16:08:44 -05:00
committed by GitHub
parent 9d654b7e3b
commit eb09be3594
6 changed files with 69 additions and 35 deletions

View File

@@ -3,6 +3,8 @@ import typing
from BaseClasses import Location
task_types = ["prayer", "magic", "runecraft", "mining", "crafting", "smithing", "fishing", "cooking", "firemaking", "woodcutting", "combat"]
class SkillRequirement(typing.NamedTuple):
skill: str
level: int

View File

@@ -8,7 +8,7 @@ import requests
# The CSVs are updated at this repository to be shared between generator and client.
data_repository_address = "https://raw.githubusercontent.com/digiholic/osrs-archipelago-logic/"
# The Github tag of the CSVs this was generated with
data_csv_tag = "v2.0.4"
data_csv_tag = "v2.0.5"
# If true, generate using file names in the repository
debug = False

View File

@@ -77,7 +77,7 @@ location_rows = [
LocationRow('Bake a Redberry Pie', 'cooking', ['Redberry Bush', 'Wheat', 'Windmill', 'Pie Dish', ], [SkillRequirement('Cooking', 10), ], [], 0),
LocationRow('Cook some Stew', 'cooking', ['Bowl', 'Meat', 'Potato', ], [SkillRequirement('Cooking', 25), ], [], 0),
LocationRow('Bake an Apple Pie', 'cooking', ['Cooking Apple', 'Wheat', 'Windmill', 'Pie Dish', ], [SkillRequirement('Cooking', 32), ], [], 2),
LocationRow('Enter the Cook\'s Guild', 'cooking', ['Cook\'s Guild', ], [], [], 0),
LocationRow('Enter the Cook\'s Guild', 'cooking', ['Cook\'s Guild', ], [SkillRequirement('Cooking', 32), ], [], 0),
LocationRow('Bake a Cake', 'cooking', ['Wheat', 'Windmill', 'Egg', 'Milk', 'Cake Tin', ], [SkillRequirement('Cooking', 40), ], [], 6),
LocationRow('Bake a Meat Pizza', 'cooking', ['Wheat', 'Windmill', 'Cheese', 'Tomato', 'Meat', ], [SkillRequirement('Cooking', 45), ], [], 8),
LocationRow('Burn a Log', 'firemaking', [], [SkillRequirement('Firemaking', 1), SkillRequirement('Woodcutting', 1), ], [], 0),

View File

@@ -2,7 +2,7 @@ from dataclasses import dataclass
from Options import Choice, Toggle, Range, PerGameCommonOptions
MAX_COMBAT_TASKS = 16
MAX_COMBAT_TASKS = 17
MAX_PRAYER_TASKS = 5
MAX_MAGIC_TASKS = 7

View File

@@ -190,6 +190,8 @@ def get_firemaking_skill_rule(level, player, options) -> CollectionRule:
def get_skill_rule(skill, level, player, options) -> CollectionRule:
if level <= 1:
return lambda state: True
if skill.lower() == "fishing":
return get_fishing_skill_rule(level, player, options)
if skill.lower() == "mining":

View File

@@ -1,11 +1,11 @@
import typing
from BaseClasses import Item, Tutorial, ItemClassification, Region, MultiWorld, CollectionState
from Fill import fill_restrictive, FillError
from BaseClasses import Item, Tutorial, ItemClassification, Region, MultiWorld
from worlds.AutoWorld import WebWorld, World
from Options import OptionError
from .Items import OSRSItem, starting_area_dict, chunksanity_starting_chunks, QP_Items, ItemRow, \
chunksanity_special_region_names
from .Locations import OSRSLocation, LocationRow
from .Locations import OSRSLocation, LocationRow, task_types
from .Rules import *
from .Options import OSRSOptions, StartingArea
from .Names import LocationNames, ItemNames, RegionNames
@@ -47,6 +47,7 @@ class OSRSWorld(World):
base_id = 0x070000
data_version = 1
explicit_indirect_conditions = False
ut_can_gen_without_yaml = True
item_name_to_id = {item_rows[i].name: 0x070000 + i for i in range(len(item_rows))}
location_name_to_id = {location_rows[i].name: 0x070000 + i for i in range(len(location_rows))}
@@ -105,6 +106,18 @@ class OSRSWorld(World):
# Set Starting Chunk
self.multiworld.push_precollected(self.create_item(self.starting_area_item))
elif hasattr(self.multiworld,"re_gen_passthrough") and self.game in self.multiworld.re_gen_passthrough:
re_gen_passthrough = self.multiworld.re_gen_passthrough[self.game] # UT passthrough
if "starting_area" in re_gen_passthrough:
self.starting_area_item = re_gen_passthrough["starting_area"]
for task_type in task_types:
if f"max_{task_type}_level" in re_gen_passthrough:
getattr(self.options,f"max_{task_type}_level").value = re_gen_passthrough[f"max_{task_type}_level"]
max_count = getattr(self.options,f"max_{task_type}_tasks")
max_count.value = max_count.range_end
self.options.brutal_grinds.value = re_gen_passthrough["brutal_grinds"]
"""
This function pulls from LogicCSVToPython so that it sends the correct tag of the repository to the client.
@@ -115,20 +128,13 @@ class OSRSWorld(World):
data = self.options.as_dict("brutal_grinds")
data["data_csv_tag"] = data_csv_tag
data["starting_area"] = str(self.starting_area_item) #these aren't actually strings, they just play them on tv
for task_type in task_types:
data[f"max_{task_type}_level"] = getattr(self.options,f"max_{task_type}_level").value
return data
def interpret_slot_data(self, slot_data: typing.Dict[str, typing.Any]) -> None:
if "starting_area" in slot_data:
self.starting_area_item = slot_data["starting_area"]
menu_region = self.multiworld.get_region("Menu",self.player)
menu_region.exits.clear() #prevent making extra exits if players just reconnect to a differnet slot
if self.starting_area_item in chunksanity_special_region_names:
starting_area_region = chunksanity_special_region_names[self.starting_area_item]
else:
starting_area_region = self.starting_area_item[6:] # len("Area: ")
starting_entrance = menu_region.create_exit(f"Start->{starting_area_region}")
starting_entrance.access_rule = lambda state: state.has(self.starting_area_item, self.player)
starting_entrance.connect(self.region_name_to_data[starting_area_region])
@staticmethod
def interpret_slot_data(slot_data: typing.Dict[str, typing.Any]) -> typing.Dict[str, typing.Any]:
return slot_data
def create_regions(self) -> None:
"""
@@ -195,6 +201,8 @@ class OSRSWorld(World):
generation_is_fake = hasattr(self.multiworld, "generation_is_fake") # UT specific override
locations_required = 0
for item_row in item_rows:
if item_row.name == self.starting_area_item:
continue #skip starting area
# If it's a filler item, set it aside for later
if item_row.progression == ItemClassification.filler:
continue
@@ -206,15 +214,18 @@ class OSRSWorld(World):
locations_required += item_row.amount
if self.options.enable_duds: locations_required += self.options.dud_count
locations_added = 1 # At this point we've already added the starting area, so we start at 1 instead of 0
locations_added = 0 # Keep track of the number of locations we add so we don't add more the number of items we're going to make
# Quests are always added first, before anything else is rolled
for i, location_row in enumerate(location_rows):
if location_row.category in {"quest", "points", "goal"}:
if location_row.category in {"quest"}:
if self.task_within_skill_levels(location_row.skills):
self.create_and_add_location(i)
if location_row.category == "quest":
locations_added += 1
elif location_row.category in {"goal"}:
if not self.task_within_skill_levels(location_row.skills):
raise OptionError(f"Goal location for {self.player_name} not allowed in skill levels") #it doesn't actually have any, but just in case for future
self.create_and_add_location(i)
# Build up the weighted Task Pool
rnd = self.random
@@ -225,18 +236,28 @@ class OSRSWorld(World):
rnd.shuffle(general_tasks)
else:
general_tasks.reverse()
for i in range(self.options.minimum_general_tasks):
general_tasks_added = 0
while general_tasks_added<self.options.minimum_general_tasks and general_tasks:
task = general_tasks.pop()
if self.task_within_skill_levels(task.skills):
self.add_location(task)
locations_added += 1
general_tasks_added += 1
while generation_is_fake and len(general_tasks)>0:
task = general_tasks.pop()
if self.task_within_skill_levels(task.skills):
self.add_location(task)
locations_added += 1
general_tasks_added += 1
if general_tasks_added < self.options.minimum_general_tasks:
raise OptionError(f"{self.plyaer_name} doesn't have enough general tasks to create required minimum count"+
f", raise maximum skill levels or lower minimum general tasks")
general_weight = self.options.general_task_weight if len(general_tasks) > 0 else 0
general_weight = self.options.general_task_weight.value if len(general_tasks) > 0 else 0
tasks_per_task_type: typing.Dict[str, typing.List[LocationRow]] = {}
weights_per_task_type: typing.Dict[str, int] = {}
task_types = ["prayer", "magic", "runecraft", "mining", "crafting",
"smithing", "fishing", "cooking", "firemaking", "woodcutting", "combat"]
for task_type in task_types:
max_amount_for_task_type = getattr(self.options, f"max_{task_type}_tasks")
tasks_for_this_type = [task for task in self.locations_by_category[task_type]
@@ -263,10 +284,13 @@ class OSRSWorld(World):
all_weights.append(weights_per_task_type[task_type])
# Even after the initial forced generals, they can still be rolled randomly
if general_weight > 0:
if general_weight > 0 and len(general_tasks)>0:
all_tasks.append(general_tasks)
all_weights.append(general_weight)
if not generation_is_fake and locations_added > locations_required: #due to minimum general tasks we already have more than needed
raise OptionError(f"Too many locations created for {self.player_name}, lower the minimum general tasks")
while locations_added < locations_required or (generation_is_fake and len(all_tasks) > 0):
if all_tasks:
chosen_task = rnd.choices(all_tasks, all_weights)[0]
@@ -282,9 +306,9 @@ class OSRSWorld(World):
del all_tasks[index]
del all_weights[index]
else:
else: # We can ignore general tasks in UT because they will have been cleared already
if len(general_tasks) == 0:
raise Exception(f"There are not enough available tasks to fill the remaining pool for OSRS " +
raise OptionError(f"There are not enough available tasks to fill the remaining pool for OSRS " +
f"Please adjust {self.player_name}'s settings to be less restrictive of tasks.")
task = general_tasks.pop()
self.add_location(task)
@@ -296,7 +320,7 @@ class OSRSWorld(World):
self.create_and_add_location(index)
def create_items(self) -> None:
filler_items = []
filler_items:list[ItemRow] = []
for item_row in item_rows:
if item_row.name != self.starting_area_item:
# If it's a filler item, set it aside for later
@@ -321,7 +345,7 @@ class OSRSWorld(World):
def get_filler_item_name(self) -> str:
if self.options.enable_duds:
return self.random.choice([item for item in item_rows if item.progression == ItemClassification.filler])
return self.random.choice([item.name for item in item_rows if item.progression == ItemClassification.filler])
else:
return self.random.choice([ItemNames.Progressive_Weapons, ItemNames.Progressive_Magic,
ItemNames.Progressive_Range_Weapon, ItemNames.Progressive_Armor,
@@ -388,6 +412,12 @@ class OSRSWorld(World):
# Set the access rule for the QP Location
add_rule(qp_loc, lambda state, loc=q_loc: (loc.can_reach(state)))
qp = 0
for qp_event in self.available_QP_locations:
qp += int(qp_event[0])
if qp < self.location_rows_by_name[LocationNames.Q_Dragon_Slayer].qp:
raise OptionError(f"{self.player_name} doesn't have enough quests for reach goal, increase maximum skill levels")
# place "Victory" at "Dragon Slayer" and set collection as win condition
self.multiworld.get_location(LocationNames.Q_Dragon_Slayer, self.player) \
.place_locked_item(self.create_event("Victory"))