OSRS: Implement New Game (#1976)
* MMBN3: Press program now has proper color index when received remotely * Initial commit of OSRS untangled from MMBN3 branch * Fixes some broken region connections * Removes some locations * Rearranges locations to fill in slots left by removed locations * Adds starting area rando * Moves Oak and Willow trees to resource regions * Fixes various PEP8 violations * Refactor of regions * Fixes variable capture issue with region rules * Partial completion of brutal grind logic * Finishes can_reach_skill function * Adds skill requirements to location rules, fixes regions rules * Adds documentation for OSRS * Removes match statement * Updates Data Version to test mode to prevent item name caching * Fixes starting spawn logic for east varrock * Fixes river lum crossing logic to not assume you can phase across water * Prevents equipping items when you haven't unlocked them * Changes canoe logic to not require huge levels * Skeletoning out some data I'll need for variable task system * Adds csvs and parser for logic * Adds Items parsing * Fixes the spawning logic to not default to Chunksanity when you didn't pick it * Begins adding generation rules for data-driven logic * Moves region handling and location creating to different methods * Adds logic limits to Options * Begun the location generation has * Randomly generates tasks for each skill until populated * Mopping up improper names, adding custom logic, and fixes location rolling * Drastically cleans up the location rolling loop * Modifies generation to properly use local variables and pass unit tests * Game is now generating, but rules don't seem to work * Lambda capture, my old nemesis. We meet again * Fixes issue with Corsair Cove item requirement causing logic loop * Okay one more fix, another variable capture * On second thought lets not have skull sceptre tasks. 'Tis a silly place * Removes QP from item pool (they're events not items) * Removes Stronghold floor tasks, no varbit to track them * Loads CSV with pkutil so it can be used in apworld * Fixes logic of skill tasks and adds QP requirements to long grinds * Fixes pathing in pkgutil call * Better handling for empty task categories, no longer throws errors * Fixes order for progressive tasks, removes un-checkable spider task * Fixes logic issues related to stew and the Blurite caves * Fixes issues generating causing tests to sporadically fail * Adds missing task that caused off-by-one error * Updates to new Options API * Updates generation to function properly with the Universal Tracker (Thanks Faris) * Replaces runtime CSV parsing with pre-made python files generated from CSVs * Switches to self.random and uses random.choice instead of doing it manually * Fixes to typing, variable names, iterators, and continue conditions * Replaces Name classes with Enums * Fixes parse error on region special rules * Skill requirements check now returns an accessrule instead of being one that checks options * Updates documentation and setup guide * Adjusts maximum numbers for combat and general tasks * Fixes region names so dictionary lookup works for chunksanity * Update worlds/osrs/docs/en_Old School Runescape.md Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * Update worlds/osrs/docs/en_Old School Runescape.md Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com> * Updates readme.md and codeowners doc * Removes erroneous East Varrock -> Al Kharid connection * Changes to canoe logic to account for woodcutting level options * Fixes embarassing typo on 'Edgeville' * Moves Logic CSVs to separate repository, addresses suggested changes on PR * Fixes logic error in east/west lumbridge regions. Fixes incorrect List typing in main * Removes task types with weight 0 from the list of rollable tasks * Missed another place that the task type had to be removed if 0 weight * Prevents adding an empty task weight if levels are too restrictive for tasks to be added * Removes giant blank space in error message * Adds player name to error for not having enough available tasks --------- Co-authored-by: Nicholas Saylor <79181893+nicholassaylor@users.noreply.github.com>
This commit is contained in:
parent
90446ad175
commit
8ddb49f071
|
@ -72,6 +72,7 @@ Currently, the following games are supported:
|
|||
* Aquaria
|
||||
* Yu-Gi-Oh! Ultimate Masters: World Championship Tournament 2006
|
||||
* A Hat in Time
|
||||
* Old School Runescape
|
||||
|
||||
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
|
||||
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
|
||||
|
|
|
@ -115,6 +115,9 @@
|
|||
# Ocarina of Time
|
||||
/worlds/oot/ @espeon65536
|
||||
|
||||
# Old School Runescape
|
||||
/worlds/osrs @digiholic
|
||||
|
||||
# Overcooked! 2
|
||||
/worlds/overcooked2/ @toasterparty
|
||||
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
import typing
|
||||
|
||||
from BaseClasses import Item, ItemClassification
|
||||
from .Names import ItemNames
|
||||
|
||||
|
||||
class ItemRow(typing.NamedTuple):
|
||||
name: str
|
||||
amount: int
|
||||
progression: ItemClassification
|
||||
|
||||
|
||||
class OSRSItem(Item):
|
||||
game: str = "Old School Runescape"
|
||||
|
||||
|
||||
QP_Items: typing.List[str] = [
|
||||
ItemNames.QP_Cooks_Assistant,
|
||||
ItemNames.QP_Demon_Slayer,
|
||||
ItemNames.QP_Restless_Ghost,
|
||||
ItemNames.QP_Romeo_Juliet,
|
||||
ItemNames.QP_Sheep_Shearer,
|
||||
ItemNames.QP_Shield_of_Arrav,
|
||||
ItemNames.QP_Ernest_the_Chicken,
|
||||
ItemNames.QP_Vampyre_Slayer,
|
||||
ItemNames.QP_Imp_Catcher,
|
||||
ItemNames.QP_Prince_Ali_Rescue,
|
||||
ItemNames.QP_Dorics_Quest,
|
||||
ItemNames.QP_Black_Knights_Fortress,
|
||||
ItemNames.QP_Witchs_Potion,
|
||||
ItemNames.QP_Knights_Sword,
|
||||
ItemNames.QP_Goblin_Diplomacy,
|
||||
ItemNames.QP_Pirates_Treasure,
|
||||
ItemNames.QP_Rune_Mysteries,
|
||||
ItemNames.QP_Misthalin_Mystery,
|
||||
ItemNames.QP_Corsair_Curse,
|
||||
ItemNames.QP_X_Marks_the_Spot,
|
||||
ItemNames.QP_Below_Ice_Mountain
|
||||
]
|
||||
|
||||
starting_area_dict: typing.Dict[int, str] = {
|
||||
0: ItemNames.Lumbridge,
|
||||
1: ItemNames.Al_Kharid,
|
||||
2: ItemNames.Central_Varrock,
|
||||
3: ItemNames.West_Varrock,
|
||||
4: ItemNames.Edgeville,
|
||||
5: ItemNames.Falador,
|
||||
6: ItemNames.Draynor_Village,
|
||||
7: ItemNames.Wilderness,
|
||||
}
|
||||
|
||||
chunksanity_starting_chunks: typing.List[str] = [
|
||||
ItemNames.Lumbridge,
|
||||
ItemNames.Lumbridge_Swamp,
|
||||
ItemNames.Lumbridge_Farms,
|
||||
ItemNames.HAM_Hideout,
|
||||
ItemNames.Draynor_Village,
|
||||
ItemNames.Draynor_Manor,
|
||||
ItemNames.Wizards_Tower,
|
||||
ItemNames.Al_Kharid,
|
||||
ItemNames.Citharede_Abbey,
|
||||
ItemNames.South_Of_Varrock,
|
||||
ItemNames.Central_Varrock,
|
||||
ItemNames.Varrock_Palace,
|
||||
ItemNames.East_Of_Varrock,
|
||||
ItemNames.West_Varrock,
|
||||
ItemNames.Edgeville,
|
||||
ItemNames.Barbarian_Village,
|
||||
ItemNames.Monastery,
|
||||
ItemNames.Ice_Mountain,
|
||||
ItemNames.Dwarven_Mines,
|
||||
ItemNames.Falador,
|
||||
ItemNames.Falador_Farm,
|
||||
ItemNames.Crafting_Guild,
|
||||
ItemNames.Rimmington,
|
||||
ItemNames.Port_Sarim,
|
||||
ItemNames.Mudskipper_Point,
|
||||
ItemNames.Wilderness
|
||||
]
|
||||
|
||||
# Some starting areas contain multiple regions, so if that area is rolled for Chunksanity, we need to map it to one
|
||||
chunksanity_special_region_names: typing.Dict[str, str] = {
|
||||
ItemNames.Lumbridge_Farms: 'Lumbridge Farms East',
|
||||
ItemNames.Crafting_Guild: 'Crafting Guild Outskirts',
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import typing
|
||||
|
||||
from BaseClasses import Location
|
||||
|
||||
|
||||
class SkillRequirement(typing.NamedTuple):
|
||||
skill: str
|
||||
level: int
|
||||
|
||||
|
||||
class LocationRow(typing.NamedTuple):
|
||||
name: str
|
||||
category: str
|
||||
regions: typing.List[str]
|
||||
skills: typing.List[SkillRequirement]
|
||||
items: typing.List[str]
|
||||
qp: int
|
||||
|
||||
|
||||
class OSRSLocation(Location):
|
||||
game: str = "Old School Runescape"
|
|
@ -0,0 +1,144 @@
|
|||
"""
|
||||
This is a utility file that converts logic in the form of CSV files into Python files that can be imported and used
|
||||
directly by the world implementation. Whenever the logic files are updated, this script should be run to re-generate
|
||||
the python files containing the data.
|
||||
"""
|
||||
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 = "v1.5"
|
||||
|
||||
if __name__ == "__main__":
|
||||
import sys
|
||||
import os
|
||||
import csv
|
||||
import typing
|
||||
|
||||
# makes this module runnable from its world folder. Shamelessly stolen from Subnautica
|
||||
sys.path.remove(os.path.dirname(__file__))
|
||||
new_home = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir, os.pardir))
|
||||
os.chdir(new_home)
|
||||
sys.path.append(new_home)
|
||||
|
||||
|
||||
def load_location_csv():
|
||||
this_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
with open(os.path.join(this_dir, "locations_generated.py"), 'w+') as locPyFile:
|
||||
locPyFile.write('"""\nThis file was auto generated by LogicCSVToPython.py\n"""\n')
|
||||
locPyFile.write("from ..Locations import LocationRow, SkillRequirement\n")
|
||||
locPyFile.write("\n")
|
||||
locPyFile.write("location_rows = [\n")
|
||||
|
||||
with requests.get(data_repository_address + "/" + data_csv_tag + "/locations.csv") as req:
|
||||
locations_reader = csv.reader(req.text.splitlines())
|
||||
for row in locations_reader:
|
||||
row_line = "LocationRow("
|
||||
row_line += str_format(row[0])
|
||||
row_line += str_format(row[1].lower())
|
||||
|
||||
region_strings = row[2].split(", ") if row[2] else []
|
||||
row_line += f"{str_list_to_py(region_strings)}, "
|
||||
|
||||
skill_strings = row[3].split(", ")
|
||||
row_line += "["
|
||||
if skill_strings:
|
||||
split_skills = [skill.split(" ") for skill in skill_strings if skill != ""]
|
||||
if split_skills:
|
||||
for split in split_skills:
|
||||
row_line += f"SkillRequirement('{split[0]}', {split[1]}), "
|
||||
row_line += "], "
|
||||
|
||||
item_strings = row[4].split(", ") if row[4] else []
|
||||
row_line += f"{str_list_to_py(item_strings)}, "
|
||||
row_line += f"{row[5]})" if row[5] != "" else "0)"
|
||||
locPyFile.write(f"\t{row_line},\n")
|
||||
locPyFile.write("]\n")
|
||||
|
||||
def load_region_csv():
|
||||
this_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
with open(os.path.join(this_dir, "regions_generated.py"), 'w+') as regPyFile:
|
||||
regPyFile.write('"""\nThis file was auto generated by LogicCSVToPython.py\n"""\n')
|
||||
regPyFile.write("from ..Regions import RegionRow\n")
|
||||
regPyFile.write("\n")
|
||||
regPyFile.write("region_rows = [\n")
|
||||
|
||||
with requests.get(data_repository_address + "/" + data_csv_tag + "/regions.csv") as req:
|
||||
regions_reader = csv.reader(req.text.splitlines())
|
||||
for row in regions_reader:
|
||||
row_line = "RegionRow("
|
||||
row_line += str_format(row[0])
|
||||
row_line += str_format(row[1])
|
||||
connections = row[2].replace("'", "\\'")
|
||||
row_line += f"{str_list_to_py(connections.split(', '))}, "
|
||||
resources = row[3].replace("'", "\\'")
|
||||
row_line += f"{str_list_to_py(resources.split(', '))})"
|
||||
regPyFile.write(f"\t{row_line},\n")
|
||||
regPyFile.write("]\n")
|
||||
|
||||
def load_resource_csv():
|
||||
this_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
with open(os.path.join(this_dir, "resources_generated.py"), 'w+') as resPyFile:
|
||||
resPyFile.write('"""\nThis file was auto generated by LogicCSVToPython.py\n"""\n')
|
||||
resPyFile.write("from ..Regions import ResourceRow\n")
|
||||
resPyFile.write("\n")
|
||||
resPyFile.write("resource_rows = [\n")
|
||||
|
||||
with requests.get(data_repository_address + "/" + data_csv_tag + "/resources.csv") as req:
|
||||
resource_reader = csv.reader(req.text.splitlines())
|
||||
for row in resource_reader:
|
||||
name = row[0].replace("'", "\\'")
|
||||
row_line = f"ResourceRow('{name}')"
|
||||
resPyFile.write(f"\t{row_line},\n")
|
||||
resPyFile.write("]\n")
|
||||
|
||||
|
||||
def load_item_csv():
|
||||
this_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
with open(os.path.join(this_dir, "items_generated.py"), 'w+') as itemPyfile:
|
||||
itemPyfile.write('"""\nThis file was auto generated by LogicCSVToPython.py\n"""\n')
|
||||
itemPyfile.write("from BaseClasses import ItemClassification\n")
|
||||
itemPyfile.write("from ..Items import ItemRow\n")
|
||||
itemPyfile.write("\n")
|
||||
itemPyfile.write("item_rows = [\n")
|
||||
|
||||
with requests.get(data_repository_address + "/" + data_csv_tag + "/items.csv") as req:
|
||||
item_reader = csv.reader(req.text.splitlines())
|
||||
for row in item_reader:
|
||||
row_line = "ItemRow("
|
||||
row_line += str_format(row[0])
|
||||
row_line += f"{row[1]}, "
|
||||
|
||||
row_line += f"ItemClassification.{row[2]})"
|
||||
|
||||
itemPyfile.write(f"\t{row_line},\n")
|
||||
itemPyfile.write("]\n")
|
||||
|
||||
|
||||
def str_format(s) -> str:
|
||||
ret_str = s.replace("'", "\\'")
|
||||
return f"'{ret_str}', "
|
||||
|
||||
|
||||
def str_list_to_py(str_list) -> str:
|
||||
ret_str = "["
|
||||
for s in str_list:
|
||||
ret_str += f"'{s}', "
|
||||
ret_str += "]"
|
||||
return ret_str
|
||||
|
||||
|
||||
|
||||
load_location_csv()
|
||||
print("Generated locations py")
|
||||
load_region_csv()
|
||||
print("Generated regions py")
|
||||
load_resource_csv()
|
||||
print("Generated resource py")
|
||||
load_item_csv()
|
||||
print("Generated item py")
|
|
@ -0,0 +1,43 @@
|
|||
"""
|
||||
This file was auto generated by LogicCSVToPython.py
|
||||
"""
|
||||
from BaseClasses import ItemClassification
|
||||
from ..Items import ItemRow
|
||||
|
||||
item_rows = [
|
||||
ItemRow('Area: Lumbridge', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Lumbridge Swamp', 1, ItemClassification.progression),
|
||||
ItemRow('Area: HAM Hideout', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Lumbridge Farms', 1, ItemClassification.progression),
|
||||
ItemRow('Area: South of Varrock', 1, ItemClassification.progression),
|
||||
ItemRow('Area: East Varrock', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Central Varrock', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Varrock Palace', 1, ItemClassification.progression),
|
||||
ItemRow('Area: West Varrock', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Edgeville', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Barbarian Village', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Draynor Manor', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Falador', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Dwarven Mines', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Ice Mountain', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Monastery', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Falador Farms', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Port Sarim', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Mudskipper Point', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Karamja', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Crandor', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Rimmington', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Crafting Guild', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Draynor Village', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Wizard Tower', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Corsair Cove', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Al Kharid', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Citharede Abbey', 1, ItemClassification.progression),
|
||||
ItemRow('Area: Wilderness', 1, ItemClassification.progression),
|
||||
ItemRow('Progressive Armor', 6, ItemClassification.progression),
|
||||
ItemRow('Progressive Weapons', 6, ItemClassification.progression),
|
||||
ItemRow('Progressive Tools', 6, ItemClassification.useful),
|
||||
ItemRow('Progressive Ranged Weapons', 3, ItemClassification.useful),
|
||||
ItemRow('Progressive Ranged Armor', 3, ItemClassification.useful),
|
||||
ItemRow('Progressive Magic', 2, ItemClassification.useful),
|
||||
]
|
|
@ -0,0 +1,127 @@
|
|||
"""
|
||||
This file was auto generated by LogicCSVToPython.py
|
||||
"""
|
||||
from ..Locations import LocationRow, SkillRequirement
|
||||
|
||||
location_rows = [
|
||||
LocationRow('Quest: Cook\'s Assistant', 'quest', ['Lumbridge', 'Wheat', 'Windmill', 'Egg', 'Milk', ], [], [], 0),
|
||||
LocationRow('Quest: Demon Slayer', 'quest', ['Central Varrock', 'Varrock Palace', 'Wizard Tower', 'South of Varrock', ], [], [], 0),
|
||||
LocationRow('Quest: The Restless Ghost', 'quest', ['Lumbridge', 'Lumbridge Swamp', 'Wizard Tower', ], [], [], 0),
|
||||
LocationRow('Quest: Romeo & Juliet', 'quest', ['Central Varrock', 'Varrock Palace', 'South of Varrock', 'West Varrock', ], [], [], 0),
|
||||
LocationRow('Quest: Sheep Shearer', 'quest', ['Lumbridge Farms West', 'Spinning Wheel', ], [], [], 0),
|
||||
LocationRow('Quest: Shield of Arrav', 'quest', ['Central Varrock', 'Varrock Palace', 'South of Varrock', 'West Varrock', ], [], [], 0),
|
||||
LocationRow('Quest: Ernest the Chicken', 'quest', ['Draynor Manor', ], [], [], 0),
|
||||
LocationRow('Quest: Vampyre Slayer', 'quest', ['Draynor Village', 'Central Varrock', 'Draynor Manor', ], [], [], 0),
|
||||
LocationRow('Quest: Imp Catcher', 'quest', ['Wizard Tower', 'Imps', ], [], [], 0),
|
||||
LocationRow('Quest: Prince Ali Rescue', 'quest', ['Al Kharid', 'Central Varrock', 'Bronze Ores', 'Clay Ore', 'Sheep', 'Spinning Wheel', 'Draynor Village', ], [], [], 0),
|
||||
LocationRow('Quest: Doric\'s Quest', 'quest', ['Dwarven Mountain Pass', 'Clay Ore', 'Iron Ore', 'Bronze Ores', ], [SkillRequirement('Mining', 15), ], [], 0),
|
||||
LocationRow('Quest: Black Knights\' Fortress', 'quest', ['Dwarven Mines', 'Falador', 'Monastery', 'Ice Mountain', 'Falador Farms', ], [], ['Progressive Armor', ], 12),
|
||||
LocationRow('Quest: Witch\'s Potion', 'quest', ['Rimmington', 'Port Sarim', ], [], [], 0),
|
||||
LocationRow('Quest: The Knight\'s Sword', 'quest', ['Falador', 'Varrock Palace', 'Mudskipper Point', 'South of Varrock', 'Windmill', 'Pie Dish', 'Port Sarim', ], [SkillRequirement('Cooking', 10), SkillRequirement('Mining', 10), ], [], 0),
|
||||
LocationRow('Quest: Goblin Diplomacy', 'quest', ['Goblin Village', 'Draynor Village', 'Falador', 'South of Varrock', 'Onion', ], [], [], 0),
|
||||
LocationRow('Quest: Pirate\'s Treasure', 'quest', ['Port Sarim', 'Karamja', 'Falador', ], [], [], 0),
|
||||
LocationRow('Quest: Rune Mysteries', 'quest', ['Lumbridge', 'Wizard Tower', 'Central Varrock', ], [], [], 0),
|
||||
LocationRow('Quest: Misthalin Mystery', 'quest', ['Lumbridge Swamp', ], [], [], 0),
|
||||
LocationRow('Quest: The Corsair Curse', 'quest', ['Rimmington', 'Falador Farms', 'Corsair Cove', ], [], [], 0),
|
||||
LocationRow('Quest: X Marks the Spot', 'quest', ['Lumbridge', 'Draynor Village', 'Port Sarim', ], [], [], 0),
|
||||
LocationRow('Quest: Below Ice Mountain', 'quest', ['Dwarven Mines', 'Dwarven Mountain Pass', 'Ice Mountain', 'Barbarian Village', 'Falador', 'Central Varrock', 'Edgeville', ], [], [], 16),
|
||||
LocationRow('Quest: Dragon Slayer', 'goal', ['Crandor', 'South of Varrock', 'Edgeville', 'Lumbridge', 'Rimmington', 'Monastery', 'Dwarven Mines', 'Port Sarim', 'Draynor Village', ], [], [], 32),
|
||||
LocationRow('Activate the "Rock Skin" Prayer', 'prayer', [], [SkillRequirement('Prayer', 10), ], [], 0),
|
||||
LocationRow('Activate the "Protect Item" Prayer', 'prayer', [], [SkillRequirement('Prayer', 25), ], [], 2),
|
||||
LocationRow('Pray at the Edgeville Monastery', 'prayer', ['Monastery', ], [SkillRequirement('Prayer', 31), ], [], 6),
|
||||
LocationRow('Cast Bones To Bananas', 'magic', ['Nature Runes', ], [SkillRequirement('Magic', 15), ], [], 0),
|
||||
LocationRow('Teleport to Varrock', 'magic', ['Central Varrock', 'Law Runes', ], [SkillRequirement('Magic', 25), ], [], 0),
|
||||
LocationRow('Teleport to Lumbridge', 'magic', ['Lumbridge', 'Law Runes', ], [SkillRequirement('Magic', 31), ], [], 2),
|
||||
LocationRow('Teleport to Falador', 'magic', ['Falador', 'Law Runes', ], [SkillRequirement('Magic', 37), ], [], 6),
|
||||
LocationRow('Craft an Air Rune', 'runecraft', ['Rune Essence', 'Falador Farms', ], [SkillRequirement('Runecraft', 1), ], [], 0),
|
||||
LocationRow('Craft runes with a Mind Core', 'runecraft', ['Camdozaal', 'Goblin Village', ], [SkillRequirement('Runecraft', 2), ], [], 0),
|
||||
LocationRow('Craft runes with a Body Core', 'runecraft', ['Camdozaal', 'Dwarven Mountain Pass', ], [SkillRequirement('Runecraft', 20), ], [], 0),
|
||||
LocationRow('Make an Unblessed Symbol', 'crafting', ['Silver Ore', 'Furnace', 'Al Kharid', 'Sheep', 'Spinning Wheel', ], [SkillRequirement('Crafting', 16), ], [], 0),
|
||||
LocationRow('Cut a Sapphire', 'crafting', ['Chisel', ], [SkillRequirement('Crafting', 20), ], [], 0),
|
||||
LocationRow('Cut an Emerald', 'crafting', ['Chisel', ], [SkillRequirement('Crafting', 27), ], [], 0),
|
||||
LocationRow('Cut a Ruby', 'crafting', ['Chisel', ], [SkillRequirement('Crafting', 34), ], [], 4),
|
||||
LocationRow('Cut a Diamond', 'crafting', ['Chisel', ], [SkillRequirement('Crafting', 43), ], [], 8),
|
||||
LocationRow('Mine a Blurite Ore', 'mining', ['Mudskipper Point', 'Port Sarim', ], [SkillRequirement('Mining', 10), ], [], 0),
|
||||
LocationRow('Crush a Barronite Deposit', 'mining', ['Camdozaal', ], [SkillRequirement('Mining', 14), ], [], 0),
|
||||
LocationRow('Mine Silver', 'mining', ['Silver Ore', ], [SkillRequirement('Mining', 20), ], [], 0),
|
||||
LocationRow('Mine Coal', 'mining', ['Coal Ore', ], [SkillRequirement('Mining', 30), ], [], 2),
|
||||
LocationRow('Mine Gold', 'mining', ['Gold Ore', ], [SkillRequirement('Mining', 40), ], [], 6),
|
||||
LocationRow('Smelt an Iron Bar', 'smithing', ['Iron Ore', 'Furnace', ], [SkillRequirement('Smithing', 15), SkillRequirement('Mining', 15), ], [], 0),
|
||||
LocationRow('Smelt a Silver Bar', 'smithing', ['Silver Ore', 'Furnace', ], [SkillRequirement('Smithing', 20), SkillRequirement('Mining', 20), ], [], 0),
|
||||
LocationRow('Smelt a Steel Bar', 'smithing', ['Coal Ore', 'Iron Ore', 'Furnace', ], [SkillRequirement('Smithing', 30), SkillRequirement('Mining', 30), ], [], 2),
|
||||
LocationRow('Smelt a Gold Bar', 'smithing', ['Gold Ore', 'Furnace', ], [SkillRequirement('Smithing', 40), SkillRequirement('Mining', 40), ], [], 6),
|
||||
LocationRow('Catch some Anchovies', 'fishing', ['Shrimp Spot', ], [SkillRequirement('Fishing', 15), ], [], 0),
|
||||
LocationRow('Catch a Trout', 'fishing', ['Fly Fishing Spot', ], [SkillRequirement('Fishing', 20), ], [], 0),
|
||||
LocationRow('Prepare a Tetra', 'fishing', ['Camdozaal', ], [SkillRequirement('Fishing', 33), SkillRequirement('Cooking', 33), ], [], 2),
|
||||
LocationRow('Catch a Lobster', 'fishing', ['Lobster Spot', ], [SkillRequirement('Fishing', 40), ], [], 6),
|
||||
LocationRow('Catch a Swordfish', 'fishing', ['Lobster Spot', ], [SkillRequirement('Fishing', 50), ], [], 12),
|
||||
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', 30), ], [], 2),
|
||||
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 some Oak Logs', 'firemaking', ['Oak Tree', ], [SkillRequirement('Firemaking', 15), ], [], 0),
|
||||
LocationRow('Burn some Willow Logs', 'firemaking', ['Willow Tree', ], [SkillRequirement('Firemaking', 30), ], [], 0),
|
||||
LocationRow('Travel on a Canoe', 'woodcutting', ['Canoe Tree', ], [SkillRequirement('Woodcutting', 12), ], [], 0),
|
||||
LocationRow('Cut an Oak Log', 'woodcutting', ['Oak Tree', ], [SkillRequirement('Woodcutting', 15), ], [], 0),
|
||||
LocationRow('Cut a Willow Log', 'woodcutting', ['Willow Tree', ], [SkillRequirement('Woodcutting', 30), ], [], 0),
|
||||
LocationRow('Kill Jeff', 'combat', ['Dwarven Mountain Pass', ], [SkillRequirement('Combat', 2), ], [], 0),
|
||||
LocationRow('Kill a Goblin', 'combat', ['Goblin', ], [SkillRequirement('Combat', 2), ], [], 0),
|
||||
LocationRow('Kill a Monkey', 'combat', ['Karamja', ], [SkillRequirement('Combat', 3), ], [], 0),
|
||||
LocationRow('Kill a Barbarian', 'combat', ['Barbarian', ], [SkillRequirement('Combat', 10), ], [], 0),
|
||||
LocationRow('Kill a Giant Frog', 'combat', ['Lumbridge Swamp', ], [SkillRequirement('Combat', 13), ], [], 0),
|
||||
LocationRow('Kill a Zombie', 'combat', ['Zombie', ], [SkillRequirement('Combat', 13), ], [], 0),
|
||||
LocationRow('Kill a Guard', 'combat', ['Guard', ], [SkillRequirement('Combat', 21), ], [], 0),
|
||||
LocationRow('Kill a Hill Giant', 'combat', ['Hill Giant', ], [SkillRequirement('Combat', 28), ], [], 2),
|
||||
LocationRow('Kill a Deadly Red Spider', 'combat', ['Deadly Red Spider', ], [SkillRequirement('Combat', 34), ], [], 2),
|
||||
LocationRow('Kill a Moss Giant', 'combat', ['Moss Giant', ], [SkillRequirement('Combat', 42), ], [], 2),
|
||||
LocationRow('Kill a Catablepon', 'combat', ['Barbarian Village', ], [SkillRequirement('Combat', 49), ], [], 4),
|
||||
LocationRow('Kill an Ice Giant', 'combat', ['Ice Giant', ], [SkillRequirement('Combat', 53), ], [], 4),
|
||||
LocationRow('Kill a Lesser Demon', 'combat', ['Lesser Demon', ], [SkillRequirement('Combat', 82), ], [], 8),
|
||||
LocationRow('Kill an Ogress Shaman', 'combat', ['Corsair Cove', ], [SkillRequirement('Combat', 82), ], [], 8),
|
||||
LocationRow('Kill Obor', 'combat', ['Edgeville', ], [SkillRequirement('Combat', 106), ], [], 28),
|
||||
LocationRow('Kill Bryophyta', 'combat', ['Central Varrock', ], [SkillRequirement('Combat', 128), ], [], 28),
|
||||
LocationRow('Total XP 5,000', 'general', [], [], [], 0),
|
||||
LocationRow('Combat Level 5', 'general', [], [], [], 0),
|
||||
LocationRow('Total XP 10,000', 'general', [], [], [], 0),
|
||||
LocationRow('Total Level 50', 'general', [], [], [], 0),
|
||||
LocationRow('Total XP 25,000', 'general', [], [], [], 0),
|
||||
LocationRow('Total Level 100', 'general', [], [], [], 0),
|
||||
LocationRow('Total XP 50,000', 'general', [], [], [], 0),
|
||||
LocationRow('Combat Level 15', 'general', [], [], [], 0),
|
||||
LocationRow('Total Level 150', 'general', [], [], [], 2),
|
||||
LocationRow('Total XP 75,000', 'general', [], [], [], 2),
|
||||
LocationRow('Combat Level 25', 'general', [], [], [], 2),
|
||||
LocationRow('Total XP 100,000', 'general', [], [], [], 6),
|
||||
LocationRow('Total Level 200', 'general', [], [], [], 6),
|
||||
LocationRow('Total XP 125,000', 'general', [], [], [], 6),
|
||||
LocationRow('Combat Level 30', 'general', [], [], [], 10),
|
||||
LocationRow('Total Level 250', 'general', [], [], [], 10),
|
||||
LocationRow('Total XP 150,000', 'general', [], [], [], 10),
|
||||
LocationRow('Total Level 300', 'general', [], [], [], 16),
|
||||
LocationRow('Combat Level 40', 'general', [], [], [], 16),
|
||||
LocationRow('Open a Simple Lockbox', 'general', ['Camdozaal', ], [], [], 0),
|
||||
LocationRow('Open an Elaborate Lockbox', 'general', ['Camdozaal', ], [], [], 0),
|
||||
LocationRow('Open an Ornate Lockbox', 'general', ['Camdozaal', ], [], [], 0),
|
||||
LocationRow('Points: Cook\'s Assistant', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Demon Slayer', 'points', [], [], [], 0),
|
||||
LocationRow('Points: The Restless Ghost', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Romeo & Juliet', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Sheep Shearer', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Shield of Arrav', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Ernest the Chicken', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Vampyre Slayer', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Imp Catcher', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Prince Ali Rescue', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Doric\'s Quest', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Black Knights\' Fortress', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Witch\'s Potion', 'points', [], [], [], 0),
|
||||
LocationRow('Points: The Knight\'s Sword', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Goblin Diplomacy', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Pirate\'s Treasure', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Rune Mysteries', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Misthalin Mystery', 'points', [], [], [], 0),
|
||||
LocationRow('Points: The Corsair Curse', 'points', [], [], [], 0),
|
||||
LocationRow('Points: X Marks the Spot', 'points', [], [], [], 0),
|
||||
LocationRow('Points: Below Ice Mountain', 'points', [], [], [], 0),
|
||||
]
|
|
@ -0,0 +1,47 @@
|
|||
"""
|
||||
This file was auto generated by LogicCSVToPython.py
|
||||
"""
|
||||
from ..Regions import RegionRow
|
||||
|
||||
region_rows = [
|
||||
RegionRow('Lumbridge', 'Area: Lumbridge', ['Lumbridge Farms East', 'Lumbridge Farms West', 'Al Kharid', 'Lumbridge Swamp', 'HAM Hideout', 'South of Varrock', 'Barbarian Village', 'Edgeville', 'Wilderness', ], ['Mind Runes', 'Spinning Wheel', 'Furnace', 'Chisel', 'Bronze Anvil', 'Fly Fishing Spot', 'Bowl', 'Cake Tin', 'Oak Tree', 'Willow Tree', 'Canoe Tree', 'Goblin', 'Imps', ]),
|
||||
RegionRow('Lumbridge Swamp', 'Area: Lumbridge Swamp', ['Lumbridge', 'HAM Hideout', ], ['Bronze Ores', 'Coal Ore', 'Shrimp Spot', 'Meat', 'Goblin', 'Imps', ]),
|
||||
RegionRow('HAM Hideout', 'Area: HAM Hideout', ['Lumbridge Farms West', 'Lumbridge', 'Lumbridge Swamp', 'Draynor Village', ], ['Goblin', ]),
|
||||
RegionRow('Lumbridge Farms West', 'Area: Lumbridge Farms', ['Sourhog\'s Lair', 'HAM Hideout', 'Draynor Village', ], ['Sheep', 'Meat', 'Wheat', 'Windmill', 'Egg', 'Milk', 'Willow Tree', 'Imps', 'Potato', ]),
|
||||
RegionRow('Lumbridge Farms East', 'Area: Lumbridge Farms', ['South of Varrock', 'Lumbridge', ], ['Meat', 'Egg', 'Milk', 'Willow Tree', 'Goblin', 'Imps', 'Potato', ]),
|
||||
RegionRow('Sourhog\'s Lair', 'Area: South of Varrock', ['Lumbridge Farms West', 'Draynor Manor Outskirts', ], ['', ]),
|
||||
RegionRow('South of Varrock', 'Area: South of Varrock', ['Al Kharid', 'West Varrock', 'Central Varrock', 'East Varrock', 'Lumbridge Farms East', 'Lumbridge', 'Barbarian Village', 'Edgeville', 'Wilderness', ], ['Sheep', 'Bronze Ores', 'Iron Ore', 'Silver Ore', 'Redberry Bush', 'Meat', 'Wheat', 'Oak Tree', 'Willow Tree', 'Canoe Tree', 'Guard', 'Imps', 'Clay Ore', ]),
|
||||
RegionRow('East Varrock', 'Area: East Varrock', ['Wilderness', 'South of Varrock', 'Central Varrock', 'Varrock Palace', ], ['Guard', ]),
|
||||
RegionRow('Central Varrock', 'Area: Central Varrock', ['Varrock Palace', 'East Varrock', 'South of Varrock', 'West Varrock', ], ['Mind Runes', 'Chisel', 'Anvil', 'Bowl', 'Cake Tin', 'Oak Tree', 'Barbarian', 'Guard', 'Rune Essence', 'Imps', ]),
|
||||
RegionRow('Varrock Palace', 'Area: Varrock Palace', ['Wilderness', 'East Varrock', 'Central Varrock', 'West Varrock', ], ['Pie Dish', 'Oak Tree', 'Zombie', 'Guard', 'Deadly Red Spider', 'Moss Giant', 'Nature Runes', 'Law Runes', ]),
|
||||
RegionRow('West Varrock', 'Area: West Varrock', ['Wilderness', 'Varrock Palace', 'South of Varrock', 'Barbarian Village', 'Edgeville', 'Cook\'s Guild', ], ['Anvil', 'Wheat', 'Oak Tree', 'Goblin', 'Guard', 'Onion', ]),
|
||||
RegionRow('Cook\'s Guild', 'Area: West Varrock*', ['West Varrock', ], ['Bowl', 'Cooking Apple', 'Pie Dish', 'Cake Tin', 'Windmill', ]),
|
||||
RegionRow('Edgeville', 'Area: Edgeville', ['Wilderness', 'West Varrock', 'Barbarian Village', 'South of Varrock', 'Lumbridge', ], ['Furnace', 'Chisel', 'Bronze Ores', 'Iron Ore', 'Coal Ore', 'Bowl', 'Meat', 'Cake Tin', 'Willow Tree', 'Canoe Tree', 'Zombie', 'Guard', 'Hill Giant', 'Nature Runes', 'Law Runes', 'Imps', ]),
|
||||
RegionRow('Barbarian Village', 'Area: Barbarian Village', ['Edgeville', 'West Varrock', 'Draynor Manor Outskirts', 'Dwarven Mountain Pass', ], ['Spinning Wheel', 'Coal Ore', 'Anvil', 'Fly Fishing Spot', 'Meat', 'Canoe Tree', 'Barbarian', 'Zombie', 'Law Runes', ]),
|
||||
RegionRow('Draynor Manor Outskirts', 'Area: Draynor Manor', ['Barbarian Village', 'Sourhog\'s Lair', 'Draynor Village', 'Falador East Outskirts', ], ['Goblin', ]),
|
||||
RegionRow('Draynor Manor', 'Area: Draynor Manor', ['Draynor Village', ], ['', ]),
|
||||
RegionRow('Falador East Outskirts', 'Area: Falador', ['Dwarven Mountain Pass', 'Draynor Manor Outskirts', 'Falador Farms', ], ['', ]),
|
||||
RegionRow('Dwarven Mountain Pass', 'Area: Dwarven Mines', ['Goblin Village', 'Monastery', 'Barbarian Village', 'Falador East Outskirts', 'Falador', ], ['Anvil*', 'Wheat', ]),
|
||||
RegionRow('Dwarven Mines', 'Area: Dwarven Mines', ['Monastery', 'Ice Mountain', 'Falador', ], ['Chisel', 'Bronze Ores', 'Iron Ore', 'Coal Ore', 'Gold Ore', 'Anvil', 'Pie Dish', 'Clay Ore', ]),
|
||||
RegionRow('Goblin Village', 'Area: Ice Mountain', ['Wilderness', 'Dwarven Mountain Pass', ], ['Meat', ]),
|
||||
RegionRow('Ice Mountain', 'Area: Ice Mountain', ['Wilderness', 'Monastery', 'Dwarven Mines', 'Camdozaal*', ], ['', ]),
|
||||
RegionRow('Camdozaal', 'Area: Ice Mountain', ['Ice Mountain', ], ['Clay Ore', ]),
|
||||
RegionRow('Monastery', 'Area: Monastery', ['Wilderness', 'Dwarven Mountain Pass', 'Dwarven Mines', 'Ice Mountain', ], ['Sheep', ]),
|
||||
RegionRow('Falador', 'Area: Falador', ['Dwarven Mountain Pass', 'Falador Farms', 'Dwarven Mines', ], ['Furnace', 'Chisel', 'Bowl', 'Cake Tin', 'Oak Tree', 'Guard', 'Imps', ]),
|
||||
RegionRow('Falador Farms', 'Area: Falador Farms', ['Falador', 'Falador East Outskirts', 'Draynor Village', 'Port Sarim', 'Rimmington', 'Crafting Guild Outskirts', ], ['Spinning Wheel', 'Meat', 'Egg', 'Milk', 'Oak Tree', 'Imps', ]),
|
||||
RegionRow('Port Sarim', 'Area: Port Sarim', ['Falador Farms', 'Mudskipper Point', 'Rimmington', 'Karamja Docks', 'Crandor', ], ['Mind Runes', 'Shrimp Spot', 'Meat', 'Cheese', 'Tomato', 'Oak Tree', 'Willow Tree', 'Goblin', 'Potato', ]),
|
||||
RegionRow('Karamja Docks', 'Area: Mudskipper Point', ['Port Sarim', 'Karamja', ], ['', ]),
|
||||
RegionRow('Mudskipper Point', 'Area: Mudskipper Point', ['Rimmington', 'Port Sarim', ], ['Anvil', 'Ice Giant', 'Nature Runes', 'Law Runes', ]),
|
||||
RegionRow('Karamja', 'Area: Karamja', ['Karamja Docks', 'Crandor', ], ['Gold Ore', 'Lobster Spot', 'Bowl', 'Cake Tin', 'Deadly Red Spider', 'Imps', ]),
|
||||
RegionRow('Crandor', 'Area: Crandor', ['Karamja', 'Port Sarim', ], ['Coal Ore', 'Gold Ore', 'Moss Giant', 'Lesser Demon', 'Nature Runes', 'Law Runes', ]),
|
||||
RegionRow('Rimmington', 'Area: Rimmington', ['Falador Farms', 'Port Sarim', 'Mudskipper Point', 'Crafting Guild Peninsula', 'Corsair Cove', ], ['Chisel', 'Bronze Ores', 'Iron Ore', 'Gold Ore', 'Bowl', 'Cake Tin', 'Wheat', 'Oak Tree', 'Willow Tree', 'Crafting Moulds', 'Imps', 'Clay Ore', 'Onion', ]),
|
||||
RegionRow('Crafting Guild Peninsula', 'Area: Crafting Guild', ['Falador Farms', 'Rimmington', ], ['', ]),
|
||||
RegionRow('Crafting Guild Outskirts', 'Area: Crafting Guild', ['Falador Farms', 'Crafting Guild', ], ['Sheep', 'Willow Tree', 'Oak Tree', ]),
|
||||
RegionRow('Crafting Guild', 'Area: Crafting Guild*', ['Crafting Guild', ], ['Spinning Wheel', 'Chisel', 'Silver Ore', 'Gold Ore', 'Meat', 'Milk', 'Clay Ore', ]),
|
||||
RegionRow('Draynor Village', 'Area: Draynor Village', ['Draynor Manor', 'Lumbridge Farms West', 'HAM Hideout', 'Wizard Tower', ], ['Anvil', 'Shrimp Spot', 'Wheat', 'Cheese', 'Tomato', 'Willow Tree', 'Goblin', 'Zombie', 'Nature Runes', 'Law Runes', 'Imps', ]),
|
||||
RegionRow('Wizard Tower', 'Area: Wizard Tower', ['Draynor Village', ], ['Lesser Demon', 'Rune Essence', ]),
|
||||
RegionRow('Corsair Cove', 'Area: Corsair Cove*', ['Rimmington', ], ['Anvil', 'Meat', ]),
|
||||
RegionRow('Al Kharid', 'Area: Al Kharid', ['South of Varrock', 'Citharede Abbey', 'Lumbridge', 'Port Sarim', ], ['Furnace', 'Chisel', 'Bronze Ores', 'Iron Ore', 'Silver Ore', 'Coal Ore', 'Gold Ore', 'Shrimp Spot', 'Bowl', 'Cake Tin', 'Cheese', 'Crafting Moulds', 'Imps', ]),
|
||||
RegionRow('Citharede Abbey', 'Area: Citharede Abbey', ['Al Kharid', ], ['Iron Ore', 'Coal Ore', 'Anvil', 'Hill Giant', 'Nature Runes', 'Law Runes', ]),
|
||||
RegionRow('Wilderness', 'Area: Wilderness', ['East Varrock', 'Varrock Palace', 'West Varrock', 'Edgeville', 'Monastery', 'Ice Mountain', 'Goblin Village', 'South of Varrock', 'Lumbridge', ], ['Furnace', 'Chisel', 'Iron Ore', 'Coal Ore', 'Anvil', 'Meat', 'Cake Tin', 'Cheese', 'Tomato', 'Oak Tree', 'Canoe Tree', 'Zombie', 'Hill Giant', 'Deadly Red Spider', 'Moss Giant', 'Ice Giant', 'Lesser Demon', 'Nature Runes', 'Law Runes', ]),
|
||||
]
|
|
@ -0,0 +1,54 @@
|
|||
"""
|
||||
This file was auto generated by LogicCSVToPython.py
|
||||
"""
|
||||
from ..Regions import ResourceRow
|
||||
|
||||
resource_rows = [
|
||||
ResourceRow('Mind Runes'),
|
||||
ResourceRow('Spinning Wheel'),
|
||||
ResourceRow('Sheep'),
|
||||
ResourceRow('Furnace'),
|
||||
ResourceRow('Chisel'),
|
||||
ResourceRow('Bronze Ores'),
|
||||
ResourceRow('Iron Ore'),
|
||||
ResourceRow('Silver Ore'),
|
||||
ResourceRow('Coal Ore'),
|
||||
ResourceRow('Gold Ore'),
|
||||
ResourceRow('Bronze Anvil'),
|
||||
ResourceRow('Anvil'),
|
||||
ResourceRow('Shrimp Spot'),
|
||||
ResourceRow('Fly Fishing Spot'),
|
||||
ResourceRow('Lobster Spot'),
|
||||
ResourceRow('Redberry Bush'),
|
||||
ResourceRow('Bowl'),
|
||||
ResourceRow('Meat'),
|
||||
ResourceRow('Cooking Apple'),
|
||||
ResourceRow('Pie Dish'),
|
||||
ResourceRow('Cake Tin'),
|
||||
ResourceRow('Wheat'),
|
||||
ResourceRow('Windmill'),
|
||||
ResourceRow('Egg'),
|
||||
ResourceRow('Milk'),
|
||||
ResourceRow('Cheese'),
|
||||
ResourceRow('Tomato'),
|
||||
ResourceRow('Oak Tree'),
|
||||
ResourceRow('Willow Tree'),
|
||||
ResourceRow('Canoe Tree'),
|
||||
ResourceRow('Goblin'),
|
||||
ResourceRow('Barbarian'),
|
||||
ResourceRow('Zombie'),
|
||||
ResourceRow('Guard'),
|
||||
ResourceRow('Hill Giant'),
|
||||
ResourceRow('Deadly Red Spider'),
|
||||
ResourceRow('Moss Giant'),
|
||||
ResourceRow('Ice Giant'),
|
||||
ResourceRow('Lesser Demon'),
|
||||
ResourceRow('Rune Essence'),
|
||||
ResourceRow('Crafting Moulds'),
|
||||
ResourceRow('Nature Runes'),
|
||||
ResourceRow('Law Runes'),
|
||||
ResourceRow('Imps'),
|
||||
ResourceRow('Clay Ore'),
|
||||
ResourceRow('Onion'),
|
||||
ResourceRow('Potato'),
|
||||
]
|
|
@ -0,0 +1,212 @@
|
|||
from enum import Enum
|
||||
|
||||
|
||||
class RegionNames(str, Enum):
|
||||
Lumbridge = "Lumbridge"
|
||||
Lumbridge_Swamp = "Lumbridge Swamp"
|
||||
Lumbridge_Farms_East = "Lumbridge Farms East"
|
||||
Lumbridge_Farms_West = "Lumbridge Farms West"
|
||||
HAM_Hideout = "HAM Hideout"
|
||||
Draynor_Village = "Draynor Village"
|
||||
Draynor_Manor = "Draynor Manor"
|
||||
Wizards_Tower = "Wizard Tower"
|
||||
Al_Kharid = "Al Kharid"
|
||||
Citharede_Abbey = "Citharede Abbey"
|
||||
South_Of_Varrock = "South of Varrock"
|
||||
Central_Varrock = "Central Varrock"
|
||||
Varrock_Palace = "Varrock Palace"
|
||||
East_Of_Varrock = "East Varrock"
|
||||
West_Varrock = "West Varrock"
|
||||
Edgeville = "Edgeville"
|
||||
Barbarian_Village = "Barbarian Village"
|
||||
Monastery = "Monastery"
|
||||
Ice_Mountain = "Ice Mountain"
|
||||
Dwarven_Mines = "Dwarven Mines"
|
||||
Falador = "Falador"
|
||||
Falador_Farm = "Falador Farms"
|
||||
Crafting_Guild = "Crafting Guild"
|
||||
Cooks_Guild = "Cook's Guild"
|
||||
Rimmington = "Rimmington"
|
||||
Port_Sarim = "Port Sarim"
|
||||
Mudskipper_Point = "Mudskipper Point"
|
||||
Karamja = "Karamja"
|
||||
Corsair_Cove = "Corsair Cove"
|
||||
Wilderness = "The Wilderness"
|
||||
Crandor = "Crandor"
|
||||
# Resource Regions
|
||||
Egg = "Egg"
|
||||
Sheep = "Sheep"
|
||||
Milk = "Milk"
|
||||
Wheat = "Wheat"
|
||||
Windmill = "Windmill"
|
||||
Spinning_Wheel = "Spinning Wheel"
|
||||
Imp = "Imp"
|
||||
Bronze_Ores = "Bronze Ores"
|
||||
Clay_Rock = "Clay Ore"
|
||||
Coal_Rock = "Coal Ore"
|
||||
Iron_Rock = "Iron Ore"
|
||||
Silver_Rock = "Silver Ore"
|
||||
Gold_Rock = "Gold Ore"
|
||||
Furnace = "Furnace"
|
||||
Anvil = "Anvil"
|
||||
Oak_Tree = "Oak Tree"
|
||||
Willow_Tree = "Willow Tree"
|
||||
Shrimp = "Shrimp Spot"
|
||||
Fly_Fish = "Fly Fishing Spot"
|
||||
Lobster = "Lobster Spot"
|
||||
Mind_Runes = "Mind Runes"
|
||||
Canoe_Tree = "Canoe Tree"
|
||||
|
||||
__str__ = str.__str__
|
||||
|
||||
|
||||
class ItemNames(str, Enum):
|
||||
Lumbridge = "Area: Lumbridge"
|
||||
Lumbridge_Swamp = "Area: Lumbridge Swamp"
|
||||
Lumbridge_Farms = "Area: Lumbridge Farms"
|
||||
HAM_Hideout = "Area: HAM Hideout"
|
||||
Draynor_Village = "Area: Draynor Village"
|
||||
Draynor_Manor = "Area: Draynor Manor"
|
||||
Wizards_Tower = "Area: Wizard Tower"
|
||||
Al_Kharid = "Area: Al Kharid"
|
||||
Citharede_Abbey = "Area: Citharede Abbey"
|
||||
South_Of_Varrock = "Area: South of Varrock"
|
||||
Central_Varrock = "Area: Central Varrock"
|
||||
Varrock_Palace = "Area: Varrock Palace"
|
||||
East_Of_Varrock = "Area: East Varrock"
|
||||
West_Varrock = "Area: West Varrock"
|
||||
Edgeville = "Area: Edgeville"
|
||||
Barbarian_Village = "Area: Barbarian Village"
|
||||
Monastery = "Area: Monastery"
|
||||
Ice_Mountain = "Area: Ice Mountain"
|
||||
Dwarven_Mines = "Area: Dwarven Mines"
|
||||
Falador = "Area: Falador"
|
||||
Falador_Farm = "Area: Falador Farms"
|
||||
Crafting_Guild = "Area: Crafting Guild"
|
||||
Rimmington = "Area: Rimmington"
|
||||
Port_Sarim = "Area: Port Sarim"
|
||||
Mudskipper_Point = "Area: Mudskipper Point"
|
||||
Karamja = "Area: Karamja"
|
||||
Crandor = "Area: Crandor"
|
||||
Corsair_Cove = "Area: Corsair Cove"
|
||||
Wilderness = "Area: Wilderness"
|
||||
Progressive_Armor = "Progressive Armor"
|
||||
Progressive_Weapons = "Progressive Weapons"
|
||||
Progressive_Tools = "Progressive Tools"
|
||||
Progressive_Range_Armor = "Progressive Range Armor"
|
||||
Progressive_Range_Weapon = "Progressive Range Weapon"
|
||||
Progressive_Magic = "Progressive Magic Spell"
|
||||
Lobsters = "10 Lobsters"
|
||||
Swordfish = "5 Swordfish"
|
||||
Energy_Potions = "10 Energy Potions"
|
||||
Coins = "5,000 Coins"
|
||||
Mind_Runes = "50 Mind Runes"
|
||||
Chaos_Runes = "25 Chaos Runes"
|
||||
Death_Runes = "10 Death Runes"
|
||||
Law_Runes = "10 Law Runes"
|
||||
QP_Cooks_Assistant = "1 QP (Cook's Assistant)"
|
||||
QP_Demon_Slayer = "3 QP (Demon Slayer)"
|
||||
QP_Restless_Ghost = "1 QP (The Restless Ghost)"
|
||||
QP_Romeo_Juliet = "5 QP (Romeo & Juliet)"
|
||||
QP_Sheep_Shearer = "1 QP (Sheep Shearer)"
|
||||
QP_Shield_of_Arrav = "1 QP (Shield of Arrav)"
|
||||
QP_Ernest_the_Chicken = "4 QP (Ernest the Chicken)"
|
||||
QP_Vampyre_Slayer = "3 QP (Vampyre Slayer)"
|
||||
QP_Imp_Catcher = "1 QP (Imp Catcher)"
|
||||
QP_Prince_Ali_Rescue = "3 QP (Prince Ali Rescue)"
|
||||
QP_Dorics_Quest = "1 QP (Doric's Quest)"
|
||||
QP_Black_Knights_Fortress = "3 QP (Black Knights' Fortress)"
|
||||
QP_Witchs_Potion = "1 QP (Witch's Potion)"
|
||||
QP_Knights_Sword = "1 QP (The Knight's Sword)"
|
||||
QP_Goblin_Diplomacy = "5 QP (Goblin Diplomacy)"
|
||||
QP_Pirates_Treasure = "2 QP (Pirate's Treasure)"
|
||||
QP_Rune_Mysteries = "1 QP (Rune Mysteries)"
|
||||
QP_Misthalin_Mystery = "1 QP (Misthalin Mystery)"
|
||||
QP_Corsair_Curse = "2 QP (The Corsair Curse)"
|
||||
QP_X_Marks_the_Spot = "1 QP (X Marks The Spot)"
|
||||
QP_Below_Ice_Mountain = "1 QP (Below Ice Mountain)"
|
||||
|
||||
__str__ = str.__str__
|
||||
|
||||
|
||||
class LocationNames(str, Enum):
|
||||
Q_Cooks_Assistant = "Quest: Cook's Assistant"
|
||||
Q_Demon_Slayer = "Quest: Demon Slayer"
|
||||
Q_Restless_Ghost = "Quest: The Restless Ghost"
|
||||
Q_Romeo_Juliet = "Quest: Romeo & Juliet"
|
||||
Q_Sheep_Shearer = "Quest: Sheep Shearer"
|
||||
Q_Shield_of_Arrav = "Quest: Shield of Arrav"
|
||||
Q_Ernest_the_Chicken = "Quest: Ernest the Chicken"
|
||||
Q_Vampyre_Slayer = "Quest: Vampyre Slayer"
|
||||
Q_Imp_Catcher = "Quest: Imp Catcher"
|
||||
Q_Prince_Ali_Rescue = "Quest: Prince Ali Rescue"
|
||||
Q_Dorics_Quest = "Quest: Doric's Quest"
|
||||
Q_Black_Knights_Fortress = "Quest: Black Knights' Fortress"
|
||||
Q_Witchs_Potion = "Quest: Witch's Potion"
|
||||
Q_Knights_Sword = "Quest: The Knight's Sword"
|
||||
Q_Goblin_Diplomacy = "Quest: Goblin Diplomacy"
|
||||
Q_Pirates_Treasure = "Quest: Pirate's Treasure"
|
||||
Q_Rune_Mysteries = "Quest: Rune Mysteries"
|
||||
Q_Misthalin_Mystery = "Quest: Misthalin Mystery"
|
||||
Q_Corsair_Curse = "Quest: The Corsair Curse"
|
||||
Q_X_Marks_the_Spot = "Quest: X Marks the Spot"
|
||||
Q_Below_Ice_Mountain = "Quest: Below Ice Mountain"
|
||||
QP_Cooks_Assistant = "Points: Cook's Assistant"
|
||||
QP_Demon_Slayer = "Points: Demon Slayer"
|
||||
QP_Restless_Ghost = "Points: The Restless Ghost"
|
||||
QP_Romeo_Juliet = "Points: Romeo & Juliet"
|
||||
QP_Sheep_Shearer = "Points: Sheep Shearer"
|
||||
QP_Shield_of_Arrav = "Points: Shield of Arrav"
|
||||
QP_Ernest_the_Chicken = "Points: Ernest the Chicken"
|
||||
QP_Vampyre_Slayer = "Points: Vampyre Slayer"
|
||||
QP_Imp_Catcher = "Points: Imp Catcher"
|
||||
QP_Prince_Ali_Rescue = "Points: Prince Ali Rescue"
|
||||
QP_Dorics_Quest = "Points: Doric's Quest"
|
||||
QP_Black_Knights_Fortress = "Points: Black Knights' Fortress"
|
||||
QP_Witchs_Potion = "Points: Witch's Potion"
|
||||
QP_Knights_Sword = "Points: The Knight's Sword"
|
||||
QP_Goblin_Diplomacy = "Points: Goblin Diplomacy"
|
||||
QP_Pirates_Treasure = "Points: Pirate's Treasure"
|
||||
QP_Rune_Mysteries = "Points: Rune Mysteries"
|
||||
QP_Misthalin_Mystery = "Points: Misthalin Mystery"
|
||||
QP_Corsair_Curse = "Points: The Corsair Curse"
|
||||
QP_X_Marks_the_Spot = "Points: X Marks the Spot"
|
||||
QP_Below_Ice_Mountain = "Points: Below Ice Mountain"
|
||||
Guppy = "Prepare a Guppy"
|
||||
Cavefish = "Prepare a Cavefish"
|
||||
Tetra = "Prepare a Tetra"
|
||||
Barronite_Deposit = "Crush a Barronite Deposit"
|
||||
Oak_Log = "Cut an Oak Log"
|
||||
Willow_Log = "Cut a Willow Log"
|
||||
Catch_Lobster = "Catch a Lobster"
|
||||
Mine_Silver = "Mine Silver"
|
||||
Mine_Coal = "Mine Coal"
|
||||
Mine_Gold = "Mine Gold"
|
||||
Smelt_Silver = "Smelt a Silver Bar"
|
||||
Smelt_Steel = "Smelt a Steel Bar"
|
||||
Smelt_Gold = "Smelt a Gold Bar"
|
||||
Cut_Sapphire = "Cut a Sapphire"
|
||||
Cut_Emerald = "Cut an Emerald"
|
||||
Cut_Ruby = "Cut a Ruby"
|
||||
Cut_Diamond = "Cut a Diamond"
|
||||
K_Lesser_Demon = "Kill a Lesser Demon"
|
||||
K_Ogress_Shaman = "Kill an Ogress Shaman"
|
||||
Bake_Apple_Pie = "Bake an Apple Pie"
|
||||
Bake_Cake = "Bake a Cake"
|
||||
Bake_Meat_Pizza = "Bake a Meat Pizza"
|
||||
Total_XP_5000 = "5,000 Total XP"
|
||||
Total_XP_10000 = "10,000 Total XP"
|
||||
Total_XP_25000 = "25,000 Total XP"
|
||||
Total_XP_50000 = "50,000 Total XP"
|
||||
Total_XP_100000 = "100,000 Total XP"
|
||||
Total_Level_50 = "Total Level 50"
|
||||
Total_Level_100 = "Total Level 100"
|
||||
Total_Level_150 = "Total Level 150"
|
||||
Total_Level_200 = "Total Level 200"
|
||||
Combat_Level_5 = "Combat Level 5"
|
||||
Combat_Level_15 = "Combat Level 15"
|
||||
Combat_Level_25 = "Combat Level 25"
|
||||
Travel_on_a_Canoe = "Travel on a Canoe"
|
||||
Q_Dragon_Slayer = "Quest: Dragon Slayer"
|
||||
|
||||
__str__ = str.__str__
|
|
@ -0,0 +1,474 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
from Options import Choice, Toggle, Range, PerGameCommonOptions
|
||||
|
||||
MAX_COMBAT_TASKS = 16
|
||||
MAX_PRAYER_TASKS = 3
|
||||
MAX_MAGIC_TASKS = 4
|
||||
MAX_RUNECRAFT_TASKS = 3
|
||||
MAX_CRAFTING_TASKS = 5
|
||||
MAX_MINING_TASKS = 5
|
||||
MAX_SMITHING_TASKS = 4
|
||||
MAX_FISHING_TASKS = 5
|
||||
MAX_COOKING_TASKS = 5
|
||||
MAX_FIREMAKING_TASKS = 2
|
||||
MAX_WOODCUTTING_TASKS = 3
|
||||
|
||||
NON_QUEST_LOCATION_COUNT = 22
|
||||
|
||||
|
||||
class StartingArea(Choice):
|
||||
"""
|
||||
Which chunks are available at the start. The player may need to move through locked chunks to reach the starting
|
||||
area, but any areas that require quests, skills, or coins are not available as a starting location.
|
||||
|
||||
"Any Bank" rolls a random region that contains a bank.
|
||||
Chunksanity can start you in any chunk. Hope you like woodcutting!
|
||||
"""
|
||||
display_name = "Starting Region"
|
||||
option_lumbridge = 0
|
||||
option_al_kharid = 1
|
||||
option_varrock_east = 2
|
||||
option_varrock_west = 3
|
||||
option_edgeville = 4
|
||||
option_falador = 5
|
||||
option_draynor = 6
|
||||
option_wilderness = 7
|
||||
option_any_bank = 8
|
||||
option_chunksanity = 9
|
||||
default = 0
|
||||
|
||||
|
||||
class BrutalGrinds(Toggle):
|
||||
"""
|
||||
Whether to allow skill tasks without having reasonable access to the usual skill training path.
|
||||
For example, if enabled, you could be forced to train smithing without an anvil purely by smelting bars,
|
||||
or training fishing to high levels entirely on shrimp.
|
||||
"""
|
||||
display_name = "Allow Brutal Grinds"
|
||||
|
||||
|
||||
class ProgressiveTasks(Toggle):
|
||||
"""
|
||||
Whether skill tasks should always be generated in order of easiest to hardest.
|
||||
If enabled, you would not be assigned "Mine Gold" without also being assigned
|
||||
"Mine Silver", "Mine Coal", and "Mine Iron". Enabling this will result in a generally shorter seed, but with
|
||||
a lower variety of tasks.
|
||||
"""
|
||||
display_name = "Progressive Tasks"
|
||||
|
||||
|
||||
class MaxCombatLevel(Range):
|
||||
"""
|
||||
The highest combat level of monster to possibly be assigned as a task.
|
||||
If set to 0, no combat tasks will be generated.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 1520
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxCombatTasks(Range):
|
||||
"""
|
||||
The maximum number of Combat Tasks to possibly be assigned.
|
||||
If set to 0, no combat tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = MAX_COMBAT_TASKS
|
||||
default = MAX_COMBAT_TASKS
|
||||
|
||||
|
||||
class CombatTaskWeight(Range):
|
||||
"""
|
||||
How much to favor generating combat tasks over other types of task.
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxPrayerLevel(Range):
|
||||
"""
|
||||
The highest Prayer requirement of any task generated.
|
||||
If set to 0, no Prayer tasks will be generated.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxPrayerTasks(Range):
|
||||
"""
|
||||
The maximum number of Prayer Tasks to possibly be assigned.
|
||||
If set to 0, no Prayer tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = MAX_PRAYER_TASKS
|
||||
default = MAX_PRAYER_TASKS
|
||||
|
||||
|
||||
class PrayerTaskWeight(Range):
|
||||
"""
|
||||
How much to favor generating Prayer tasks over other types of task.
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxMagicLevel(Range):
|
||||
"""
|
||||
The highest Magic requirement of any task generated.
|
||||
If set to 0, no Magic tasks will be generated.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxMagicTasks(Range):
|
||||
"""
|
||||
The maximum number of Magic Tasks to possibly be assigned.
|
||||
If set to 0, no Magic tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = MAX_MAGIC_TASKS
|
||||
default = MAX_MAGIC_TASKS
|
||||
|
||||
|
||||
class MagicTaskWeight(Range):
|
||||
"""
|
||||
How much to favor generating Magic tasks over other types of task.
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxRunecraftLevel(Range):
|
||||
"""
|
||||
The highest Runecraft requirement of any task generated.
|
||||
If set to 0, no Runecraft tasks will be generated.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxRunecraftTasks(Range):
|
||||
"""
|
||||
The maximum number of Runecraft Tasks to possibly be assigned.
|
||||
If set to 0, no Runecraft tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = MAX_RUNECRAFT_TASKS
|
||||
default = MAX_RUNECRAFT_TASKS
|
||||
|
||||
|
||||
class RunecraftTaskWeight(Range):
|
||||
"""
|
||||
How much to favor generating Runecraft tasks over other types of task.
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxCraftingLevel(Range):
|
||||
"""
|
||||
The highest Crafting requirement of any task generated.
|
||||
If set to 0, no Crafting tasks will be generated.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxCraftingTasks(Range):
|
||||
"""
|
||||
The maximum number of Crafting Tasks to possibly be assigned.
|
||||
If set to 0, no Crafting tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = MAX_CRAFTING_TASKS
|
||||
default = MAX_CRAFTING_TASKS
|
||||
|
||||
|
||||
class CraftingTaskWeight(Range):
|
||||
"""
|
||||
How much to favor generating Crafting tasks over other types of task.
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxMiningLevel(Range):
|
||||
"""
|
||||
The highest Mining requirement of any task generated.
|
||||
If set to 0, no Mining tasks will be generated.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxMiningTasks(Range):
|
||||
"""
|
||||
The maximum number of Mining Tasks to possibly be assigned.
|
||||
If set to 0, no Mining tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = MAX_MINING_TASKS
|
||||
default = MAX_MINING_TASKS
|
||||
|
||||
|
||||
class MiningTaskWeight(Range):
|
||||
"""
|
||||
How much to favor generating Mining tasks over other types of task.
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxSmithingLevel(Range):
|
||||
"""
|
||||
The highest Smithing requirement of any task generated.
|
||||
If set to 0, no Smithing tasks will be generated.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxSmithingTasks(Range):
|
||||
"""
|
||||
The maximum number of Smithing Tasks to possibly be assigned.
|
||||
If set to 0, no Smithing tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = MAX_SMITHING_TASKS
|
||||
default = MAX_SMITHING_TASKS
|
||||
|
||||
|
||||
class SmithingTaskWeight(Range):
|
||||
"""
|
||||
How much to favor generating Smithing tasks over other types of task.
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxFishingLevel(Range):
|
||||
"""
|
||||
The highest Fishing requirement of any task generated.
|
||||
If set to 0, no Fishing tasks will be generated.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxFishingTasks(Range):
|
||||
"""
|
||||
The maximum number of Fishing Tasks to possibly be assigned.
|
||||
If set to 0, no Fishing tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = MAX_FISHING_TASKS
|
||||
default = MAX_FISHING_TASKS
|
||||
|
||||
|
||||
class FishingTaskWeight(Range):
|
||||
"""
|
||||
How much to favor generating Fishing tasks over other types of task.
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxCookingLevel(Range):
|
||||
"""
|
||||
The highest Cooking requirement of any task generated.
|
||||
If set to 0, no Cooking tasks will be generated.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxCookingTasks(Range):
|
||||
"""
|
||||
The maximum number of Cooking Tasks to possibly be assigned.
|
||||
If set to 0, no Cooking tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = MAX_COOKING_TASKS
|
||||
default = MAX_COOKING_TASKS
|
||||
|
||||
|
||||
class CookingTaskWeight(Range):
|
||||
"""
|
||||
How much to favor generating Cooking tasks over other types of task.
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxFiremakingLevel(Range):
|
||||
"""
|
||||
The highest Firemaking requirement of any task generated.
|
||||
If set to 0, no Firemaking tasks will be generated.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxFiremakingTasks(Range):
|
||||
"""
|
||||
The maximum number of Firemaking Tasks to possibly be assigned.
|
||||
If set to 0, no Firemaking tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = MAX_FIREMAKING_TASKS
|
||||
default = MAX_FIREMAKING_TASKS
|
||||
|
||||
|
||||
class FiremakingTaskWeight(Range):
|
||||
"""
|
||||
How much to favor generating Firemaking tasks over other types of task.
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxWoodcuttingLevel(Range):
|
||||
"""
|
||||
The highest Woodcutting requirement of any task generated.
|
||||
If set to 0, no Woodcutting tasks will be generated.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MaxWoodcuttingTasks(Range):
|
||||
"""
|
||||
The maximum number of Woodcutting Tasks to possibly be assigned.
|
||||
If set to 0, no Woodcutting tasks will be generated.
|
||||
This only determines the maximum possible, fewer than the maximum could be assigned.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = MAX_WOODCUTTING_TASKS
|
||||
default = MAX_WOODCUTTING_TASKS
|
||||
|
||||
|
||||
class WoodcuttingTaskWeight(Range):
|
||||
"""
|
||||
How much to favor generating Woodcutting tasks over other types of task.
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
class MinimumGeneralTasks(Range):
|
||||
"""
|
||||
How many guaranteed general progression tasks to be assigned (total level, total XP, etc.).
|
||||
General progression tasks will be used to fill out any holes caused by having fewer possible tasks than needed, so
|
||||
there is no maximum.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = NON_QUEST_LOCATION_COUNT
|
||||
default = 10
|
||||
|
||||
|
||||
class GeneralTaskWeight(Range):
|
||||
"""
|
||||
How much to favor generating General tasks over other types of task.
|
||||
Weights of all Task Types will be compared against each other, a task with 50 weight
|
||||
is twice as likely to appear as one with 25.
|
||||
"""
|
||||
range_start = 0
|
||||
range_end = 99
|
||||
default = 50
|
||||
|
||||
|
||||
@dataclass
|
||||
class OSRSOptions(PerGameCommonOptions):
|
||||
starting_area: StartingArea
|
||||
brutal_grinds: BrutalGrinds
|
||||
progressive_tasks: ProgressiveTasks
|
||||
max_combat_level: MaxCombatLevel
|
||||
max_combat_tasks: MaxCombatTasks
|
||||
combat_task_weight: CombatTaskWeight
|
||||
max_prayer_level: MaxPrayerLevel
|
||||
max_prayer_tasks: MaxPrayerTasks
|
||||
prayer_task_weight: PrayerTaskWeight
|
||||
max_magic_level: MaxMagicLevel
|
||||
max_magic_tasks: MaxMagicTasks
|
||||
magic_task_weight: MagicTaskWeight
|
||||
max_runecraft_level: MaxRunecraftLevel
|
||||
max_runecraft_tasks: MaxRunecraftTasks
|
||||
runecraft_task_weight: RunecraftTaskWeight
|
||||
max_crafting_level: MaxCraftingLevel
|
||||
max_crafting_tasks: MaxCraftingTasks
|
||||
crafting_task_weight: CraftingTaskWeight
|
||||
max_mining_level: MaxMiningLevel
|
||||
max_mining_tasks: MaxMiningTasks
|
||||
mining_task_weight: MiningTaskWeight
|
||||
max_smithing_level: MaxSmithingLevel
|
||||
max_smithing_tasks: MaxSmithingTasks
|
||||
smithing_task_weight: SmithingTaskWeight
|
||||
max_fishing_level: MaxFishingLevel
|
||||
max_fishing_tasks: MaxFishingTasks
|
||||
fishing_task_weight: FishingTaskWeight
|
||||
max_cooking_level: MaxCookingLevel
|
||||
max_cooking_tasks: MaxCookingTasks
|
||||
cooking_task_weight: CookingTaskWeight
|
||||
max_firemaking_level: MaxFiremakingLevel
|
||||
max_firemaking_tasks: MaxFiremakingTasks
|
||||
firemaking_task_weight: FiremakingTaskWeight
|
||||
max_woodcutting_level: MaxWoodcuttingLevel
|
||||
max_woodcutting_tasks: MaxWoodcuttingTasks
|
||||
woodcutting_task_weight: WoodcuttingTaskWeight
|
||||
minimum_general_tasks: MinimumGeneralTasks
|
||||
general_task_weight: GeneralTaskWeight
|
|
@ -0,0 +1,12 @@
|
|||
import typing
|
||||
|
||||
|
||||
class RegionRow(typing.NamedTuple):
|
||||
name: str
|
||||
itemReq: str
|
||||
connections: typing.List[str]
|
||||
resources: typing.List[str]
|
||||
|
||||
|
||||
class ResourceRow(typing.NamedTuple):
|
||||
name: str
|
|
@ -0,0 +1,657 @@
|
|||
import typing
|
||||
|
||||
from BaseClasses import Item, Tutorial, ItemClassification, Region, MultiWorld
|
||||
from worlds.AutoWorld import WebWorld, World
|
||||
from worlds.generic.Rules import add_rule, CollectionRule
|
||||
from .Items import OSRSItem, starting_area_dict, chunksanity_starting_chunks, QP_Items, ItemRow, \
|
||||
chunksanity_special_region_names
|
||||
from .Locations import OSRSLocation, LocationRow
|
||||
|
||||
from .Options import OSRSOptions, StartingArea
|
||||
from .Names import LocationNames, ItemNames, RegionNames
|
||||
|
||||
from .LogicCSV.LogicCSVToPython import data_csv_tag
|
||||
from .LogicCSV.items_generated import item_rows
|
||||
from .LogicCSV.locations_generated import location_rows
|
||||
from .LogicCSV.regions_generated import region_rows
|
||||
from .LogicCSV.resources_generated import resource_rows
|
||||
from .Regions import RegionRow, ResourceRow
|
||||
|
||||
|
||||
class OSRSWeb(WebWorld):
|
||||
theme = "stone"
|
||||
|
||||
setup_en = Tutorial(
|
||||
"Multiworld Setup Guide",
|
||||
"A guide to setting up the Old School Runescape Randomizer connected to an Archipelago Multiworld",
|
||||
"English",
|
||||
"docs/setup_en.md",
|
||||
"setup/en",
|
||||
["digiholic"]
|
||||
)
|
||||
tutorials = [setup_en]
|
||||
|
||||
|
||||
class OSRSWorld(World):
|
||||
game = "Old School Runescape"
|
||||
options_dataclass = OSRSOptions
|
||||
options: OSRSOptions
|
||||
topology_present = True
|
||||
web = OSRSWeb()
|
||||
base_id = 0x070000
|
||||
data_version = 1
|
||||
|
||||
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))}
|
||||
|
||||
region_name_to_data: typing.Dict[str, Region]
|
||||
location_name_to_data: typing.Dict[str, OSRSLocation]
|
||||
|
||||
location_rows_by_name: typing.Dict[str, LocationRow]
|
||||
region_rows_by_name: typing.Dict[str, RegionRow]
|
||||
resource_rows_by_name: typing.Dict[str, ResourceRow]
|
||||
item_rows_by_name: typing.Dict[str, ItemRow]
|
||||
|
||||
starting_area_item: str
|
||||
|
||||
locations_by_category: typing.Dict[str, typing.List[LocationRow]]
|
||||
|
||||
def __init__(self, world: MultiWorld, player: int):
|
||||
super().__init__(world, player)
|
||||
self.region_name_to_data = {}
|
||||
self.location_name_to_data = {}
|
||||
|
||||
self.location_rows_by_name = {}
|
||||
self.region_rows_by_name = {}
|
||||
self.resource_rows_by_name = {}
|
||||
self.item_rows_by_name = {}
|
||||
|
||||
self.starting_area_item = ""
|
||||
|
||||
self.locations_by_category = {}
|
||||
|
||||
def generate_early(self) -> None:
|
||||
location_categories = [location_row.category for location_row in location_rows]
|
||||
self.locations_by_category = {category:
|
||||
[location_row for location_row in location_rows if
|
||||
location_row.category == category]
|
||||
for category in location_categories}
|
||||
|
||||
self.location_rows_by_name = {loc_row.name: loc_row for loc_row in location_rows}
|
||||
self.region_rows_by_name = {reg_row.name: reg_row for reg_row in region_rows}
|
||||
self.resource_rows_by_name = {rec_row.name: rec_row for rec_row in resource_rows}
|
||||
self.item_rows_by_name = {it_row.name: it_row for it_row in item_rows}
|
||||
|
||||
rnd = self.random
|
||||
starting_area = self.options.starting_area
|
||||
|
||||
if starting_area.value == StartingArea.option_any_bank:
|
||||
self.starting_area_item = rnd.choice(starting_area_dict)
|
||||
elif starting_area.value < StartingArea.option_chunksanity:
|
||||
self.starting_area_item = starting_area_dict[starting_area.value]
|
||||
else:
|
||||
self.starting_area_item = rnd.choice(chunksanity_starting_chunks)
|
||||
|
||||
# Set Starting Chunk
|
||||
self.multiworld.push_precollected(self.create_item(self.starting_area_item))
|
||||
|
||||
"""
|
||||
This function pulls from LogicCSVToPython so that it sends the correct tag of the repository to the client.
|
||||
_Make sure to update that value whenever the CSVs change!_
|
||||
"""
|
||||
|
||||
def fill_slot_data(self):
|
||||
data = self.options.as_dict("brutal_grinds")
|
||||
data["data_csv_tag"] = data_csv_tag
|
||||
return data
|
||||
|
||||
def create_regions(self) -> None:
|
||||
"""
|
||||
called to place player's regions into the MultiWorld's regions list. If it's hard to separate, this can be done
|
||||
during generate_early or basic as well.
|
||||
"""
|
||||
|
||||
# First, create the "Menu" region to start
|
||||
menu_region = self.create_region("Menu")
|
||||
|
||||
for region_row in region_rows:
|
||||
self.create_region(region_row.name)
|
||||
|
||||
for resource_row in resource_rows:
|
||||
self.create_region(resource_row.name)
|
||||
|
||||
# Removes the word "Area: " from the item name to get the region it applies to.
|
||||
# I figured tacking "Area: " at the beginning would make it _easier_ to tell apart. Turns out it made it worse
|
||||
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])
|
||||
|
||||
# Create entrances between regions
|
||||
for region_row in region_rows:
|
||||
region = self.region_name_to_data[region_row.name]
|
||||
|
||||
for outbound_region_name in region_row.connections:
|
||||
parsed_outbound = outbound_region_name.replace('*', '')
|
||||
entrance = region.create_exit(f"{region_row.name}->{parsed_outbound}")
|
||||
entrance.connect(self.region_name_to_data[parsed_outbound])
|
||||
|
||||
item_name = self.region_rows_by_name[parsed_outbound].itemReq
|
||||
if "*" not in outbound_region_name and "*" not in item_name:
|
||||
entrance.access_rule = lambda state, item_name=item_name: state.has(item_name, self.player)
|
||||
continue
|
||||
|
||||
self.generate_special_rules_for(entrance, region_row, outbound_region_name)
|
||||
|
||||
for resource_region in region_row.resources:
|
||||
if not resource_region:
|
||||
continue
|
||||
|
||||
entrance = region.create_exit(f"{region_row.name}->{resource_region.replace('*', '')}")
|
||||
if "*" not in resource_region:
|
||||
entrance.connect(self.region_name_to_data[resource_region])
|
||||
else:
|
||||
self.generate_special_rules_for(entrance, region_row, resource_region)
|
||||
entrance.connect(self.region_name_to_data[resource_region.replace('*', '')])
|
||||
|
||||
self.roll_locations()
|
||||
|
||||
def generate_special_rules_for(self, entrance, region_row, outbound_region_name):
|
||||
# print(f"Special rules required to access region {outbound_region_name} from {region_row.name}")
|
||||
if outbound_region_name == RegionNames.Cooks_Guild:
|
||||
item_name = self.region_rows_by_name[outbound_region_name].itemReq.replace('*', '')
|
||||
cooking_level_rule = self.get_skill_rule("cooking", 32)
|
||||
entrance.access_rule = lambda state: state.has(item_name, self.player) and \
|
||||
cooking_level_rule(state)
|
||||
return
|
||||
if outbound_region_name == RegionNames.Crafting_Guild:
|
||||
item_name = self.region_rows_by_name[outbound_region_name].itemReq.replace('*', '')
|
||||
crafting_level_rule = self.get_skill_rule("crafting", 40)
|
||||
entrance.access_rule = lambda state: state.has(item_name, self.player) and \
|
||||
crafting_level_rule(state)
|
||||
return
|
||||
if outbound_region_name == RegionNames.Corsair_Cove:
|
||||
item_name = self.region_rows_by_name[outbound_region_name].itemReq.replace('*', '')
|
||||
# Need to be able to start Corsair Curse in addition to having the item
|
||||
entrance.access_rule = lambda state: state.has(item_name, self.player) and \
|
||||
state.can_reach(RegionNames.Falador_Farm, "Region", self.player)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Falador_Farm, self.player), entrance)
|
||||
|
||||
return
|
||||
if outbound_region_name == "Camdozaal*":
|
||||
item_name = self.region_rows_by_name[outbound_region_name.replace('*', '')].itemReq
|
||||
entrance.access_rule = lambda state: state.has(item_name, self.player) and \
|
||||
state.has(ItemNames.QP_Below_Ice_Mountain, self.player)
|
||||
return
|
||||
if region_row.name == "Dwarven Mountain Pass" and outbound_region_name == "Anvil*":
|
||||
entrance.access_rule = lambda state: state.has(ItemNames.QP_Dorics_Quest, self.player)
|
||||
return
|
||||
# Special logic for canoes
|
||||
canoe_regions = [RegionNames.Lumbridge, RegionNames.South_Of_Varrock, RegionNames.Barbarian_Village,
|
||||
RegionNames.Edgeville, RegionNames.Wilderness]
|
||||
if region_row.name in canoe_regions:
|
||||
# Skill rules for greater distances
|
||||
woodcutting_rule_d1 = self.get_skill_rule("woodcutting", 12)
|
||||
woodcutting_rule_d2 = self.get_skill_rule("woodcutting", 27)
|
||||
woodcutting_rule_d3 = self.get_skill_rule("woodcutting", 42)
|
||||
woodcutting_rule_all = self.get_skill_rule("woodcutting", 57)
|
||||
|
||||
if region_row.name == RegionNames.Lumbridge:
|
||||
# Canoe Tree access for the Location
|
||||
if outbound_region_name == RegionNames.Canoe_Tree:
|
||||
entrance.access_rule = \
|
||||
lambda state: (state.can_reach_region(RegionNames.South_Of_Varrock, self.player)
|
||||
and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \
|
||||
(state.can_reach_region(RegionNames.Barbarian_Village)
|
||||
and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \
|
||||
(state.can_reach_region(RegionNames.Edgeville)
|
||||
and woodcutting_rule_d3(state) and self.options.max_woodcutting_level >= 42) or \
|
||||
(state.can_reach_region(RegionNames.Wilderness)
|
||||
and woodcutting_rule_all(state) and self.options.max_woodcutting_level >= 57)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.South_Of_Varrock, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Barbarian_Village, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Edgeville, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Wilderness, self.player), entrance)
|
||||
# Access to other chunks based on woodcutting settings
|
||||
# South of Varrock does not need to be checked, because it's already adjacent
|
||||
if outbound_region_name == RegionNames.Barbarian_Village:
|
||||
entrance.access_rule = lambda state: woodcutting_rule_d2(state) \
|
||||
and self.options.max_woodcutting_level >= 27
|
||||
if outbound_region_name == RegionNames.Edgeville:
|
||||
entrance.access_rule = lambda state: woodcutting_rule_d3(state) \
|
||||
and self.options.max_woodcutting_level >= 42
|
||||
if outbound_region_name == RegionNames.Wilderness:
|
||||
entrance.access_rule = lambda state: woodcutting_rule_all(state) \
|
||||
and self.options.max_woodcutting_level >= 57
|
||||
|
||||
if region_row.name == RegionNames.South_Of_Varrock:
|
||||
if outbound_region_name == RegionNames.Canoe_Tree:
|
||||
entrance.access_rule = \
|
||||
lambda state: (state.can_reach_region(RegionNames.Lumbridge, self.player)
|
||||
and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \
|
||||
(state.can_reach_region(RegionNames.Barbarian_Village)
|
||||
and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \
|
||||
(state.can_reach_region(RegionNames.Edgeville)
|
||||
and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \
|
||||
(state.can_reach_region(RegionNames.Wilderness)
|
||||
and woodcutting_rule_d3(state) and self.options.max_woodcutting_level >= 42)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Lumbridge, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Barbarian_Village, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Edgeville, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Wilderness, self.player), entrance)
|
||||
# Access to other chunks based on woodcutting settings
|
||||
# Lumbridge does not need to be checked, because it's already adjacent
|
||||
if outbound_region_name == RegionNames.Barbarian_Village:
|
||||
entrance.access_rule = lambda state: woodcutting_rule_d1(state) \
|
||||
and self.options.max_woodcutting_level >= 12
|
||||
if outbound_region_name == RegionNames.Edgeville:
|
||||
entrance.access_rule = lambda state: woodcutting_rule_d3(state) \
|
||||
and self.options.max_woodcutting_level >= 27
|
||||
if outbound_region_name == RegionNames.Wilderness:
|
||||
entrance.access_rule = lambda state: woodcutting_rule_all(state) \
|
||||
and self.options.max_woodcutting_level >= 42
|
||||
if region_row.name == RegionNames.Barbarian_Village:
|
||||
if outbound_region_name == RegionNames.Canoe_Tree:
|
||||
entrance.access_rule = \
|
||||
lambda state: (state.can_reach_region(RegionNames.Lumbridge, self.player)
|
||||
and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \
|
||||
(state.can_reach_region(RegionNames.South_Of_Varrock)
|
||||
and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \
|
||||
(state.can_reach_region(RegionNames.Edgeville)
|
||||
and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \
|
||||
(state.can_reach_region(RegionNames.Wilderness)
|
||||
and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Lumbridge, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.South_Of_Varrock, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Edgeville, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Wilderness, self.player), entrance)
|
||||
# Access to other chunks based on woodcutting settings
|
||||
if outbound_region_name == RegionNames.Lumbridge:
|
||||
entrance.access_rule = lambda state: woodcutting_rule_d2(state) \
|
||||
and self.options.max_woodcutting_level >= 27
|
||||
if outbound_region_name == RegionNames.South_Of_Varrock:
|
||||
entrance.access_rule = lambda state: woodcutting_rule_d1(state) \
|
||||
and self.options.max_woodcutting_level >= 12
|
||||
# Edgeville does not need to be checked, because it's already adjacent
|
||||
if outbound_region_name == RegionNames.Wilderness:
|
||||
entrance.access_rule = lambda state: woodcutting_rule_d3(state) \
|
||||
and self.options.max_woodcutting_level >= 42
|
||||
if region_row.name == RegionNames.Edgeville:
|
||||
if outbound_region_name == RegionNames.Canoe_Tree:
|
||||
entrance.access_rule = \
|
||||
lambda state: (state.can_reach_region(RegionNames.Lumbridge, self.player)
|
||||
and woodcutting_rule_d3(state) and self.options.max_woodcutting_level >= 42) or \
|
||||
(state.can_reach_region(RegionNames.South_Of_Varrock)
|
||||
and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \
|
||||
(state.can_reach_region(RegionNames.Barbarian_Village)
|
||||
and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12) or \
|
||||
(state.can_reach_region(RegionNames.Wilderness)
|
||||
and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Lumbridge, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.South_Of_Varrock, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Barbarian_Village, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Wilderness, self.player), entrance)
|
||||
# Access to other chunks based on woodcutting settings
|
||||
if outbound_region_name == RegionNames.Lumbridge:
|
||||
entrance.access_rule = lambda state: woodcutting_rule_d3(state) \
|
||||
and self.options.max_woodcutting_level >= 42
|
||||
if outbound_region_name == RegionNames.South_Of_Varrock:
|
||||
entrance.access_rule = lambda state: woodcutting_rule_d2(state) \
|
||||
and self.options.max_woodcutting_level >= 27
|
||||
# Barbarian Village does not need to be checked, because it's already adjacent
|
||||
# Wilderness does not need to be checked, because it's already adjacent
|
||||
if region_row.name == RegionNames.Wilderness:
|
||||
if outbound_region_name == RegionNames.Canoe_Tree:
|
||||
entrance.access_rule = \
|
||||
lambda state: (state.can_reach_region(RegionNames.Lumbridge, self.player)
|
||||
and woodcutting_rule_all(state) and self.options.max_woodcutting_level >= 57) or \
|
||||
(state.can_reach_region(RegionNames.South_Of_Varrock)
|
||||
and woodcutting_rule_d3(state) and self.options.max_woodcutting_level >= 42) or \
|
||||
(state.can_reach_region(RegionNames.Barbarian_Village)
|
||||
and woodcutting_rule_d2(state) and self.options.max_woodcutting_level >= 27) or \
|
||||
(state.can_reach_region(RegionNames.Edgeville)
|
||||
and woodcutting_rule_d1(state) and self.options.max_woodcutting_level >= 12)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Lumbridge, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.South_Of_Varrock, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Barbarian_Village, self.player), entrance)
|
||||
self.multiworld.register_indirect_condition(
|
||||
self.multiworld.get_region(RegionNames.Edgeville, self.player), entrance)
|
||||
# Access to other chunks based on woodcutting settings
|
||||
if outbound_region_name == RegionNames.Lumbridge:
|
||||
entrance.access_rule = lambda state: woodcutting_rule_all(state) \
|
||||
and self.options.max_woodcutting_level >= 57
|
||||
if outbound_region_name == RegionNames.South_Of_Varrock:
|
||||
entrance.access_rule = lambda state: woodcutting_rule_d3(state) \
|
||||
and self.options.max_woodcutting_level >= 42
|
||||
if outbound_region_name == RegionNames.Barbarian_Village:
|
||||
entrance.access_rule = lambda state: woodcutting_rule_d2(state) \
|
||||
and self.options.max_woodcutting_level >= 27
|
||||
# Edgeville does not need to be checked, because it's already adjacent
|
||||
|
||||
def roll_locations(self):
|
||||
locations_required = 0
|
||||
generation_is_fake = hasattr(self.multiworld, "generation_is_fake") # UT specific override
|
||||
for item_row in item_rows:
|
||||
locations_required += item_row.amount
|
||||
|
||||
locations_added = 1 # At this point we've already added the starting area, so we start at 1 instead of 0
|
||||
|
||||
# Quests are always added
|
||||
for i, location_row in enumerate(location_rows):
|
||||
if location_row.category in {"quest", "points", "goal"}:
|
||||
self.create_and_add_location(i)
|
||||
if location_row.category == "quest":
|
||||
locations_added += 1
|
||||
|
||||
# Build up the weighted Task Pool
|
||||
rnd = self.random
|
||||
|
||||
# Start with the minimum general tasks
|
||||
general_tasks = [task for task in self.locations_by_category["general"]]
|
||||
if not self.options.progressive_tasks:
|
||||
rnd.shuffle(general_tasks)
|
||||
else:
|
||||
general_tasks.reverse()
|
||||
for i in range(self.options.minimum_general_tasks):
|
||||
task = general_tasks.pop()
|
||||
self.add_location(task)
|
||||
locations_added += 1
|
||||
|
||||
general_weight = self.options.general_task_weight 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_level_for_task_type = getattr(self.options, f"max_{task_type}_level")
|
||||
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]
|
||||
if task.skills[0].level <= max_level_for_task_type]
|
||||
if not self.options.progressive_tasks:
|
||||
rnd.shuffle(tasks_for_this_type)
|
||||
else:
|
||||
tasks_for_this_type.reverse()
|
||||
|
||||
tasks_for_this_type = tasks_for_this_type[:max_amount_for_task_type]
|
||||
weight_for_this_type = getattr(self.options,
|
||||
f"{task_type}_task_weight")
|
||||
if weight_for_this_type > 0 and tasks_for_this_type:
|
||||
tasks_per_task_type[task_type] = tasks_for_this_type
|
||||
weights_per_task_type[task_type] = weight_for_this_type
|
||||
|
||||
# Build a list of collections and weights in a matching order for rnd.choices later
|
||||
all_tasks = []
|
||||
all_weights = []
|
||||
for task_type in task_types:
|
||||
if task_type in tasks_per_task_type:
|
||||
all_tasks.append(tasks_per_task_type[task_type])
|
||||
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:
|
||||
all_tasks.append(general_tasks)
|
||||
all_weights.append(general_weight)
|
||||
|
||||
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]
|
||||
if chosen_task:
|
||||
task = chosen_task.pop()
|
||||
self.add_location(task)
|
||||
locations_added += 1
|
||||
|
||||
# This isn't an else because chosen_task can become empty in the process of resolving the above block
|
||||
# We still want to clear this list out while we're doing that
|
||||
if not chosen_task:
|
||||
index = all_tasks.index(chosen_task)
|
||||
del all_tasks[index]
|
||||
del all_weights[index]
|
||||
|
||||
else:
|
||||
if len(general_tasks) == 0:
|
||||
raise Exception(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)
|
||||
locations_added += 1
|
||||
|
||||
def add_location(self, location):
|
||||
index = [i for i in range(len(location_rows)) if location_rows[i].name == location.name][0]
|
||||
self.create_and_add_location(index)
|
||||
|
||||
def create_items(self) -> None:
|
||||
for item_row in item_rows:
|
||||
if item_row.name != self.starting_area_item:
|
||||
for c in range(item_row.amount):
|
||||
item = self.create_item(item_row.name)
|
||||
self.multiworld.itempool.append(item)
|
||||
|
||||
def get_filler_item_name(self) -> str:
|
||||
return self.random.choice(
|
||||
[ItemNames.Progressive_Armor, ItemNames.Progressive_Weapons, ItemNames.Progressive_Magic,
|
||||
ItemNames.Progressive_Tools, ItemNames.Progressive_Range_Armor, ItemNames.Progressive_Range_Weapon])
|
||||
|
||||
def create_and_add_location(self, row_index) -> None:
|
||||
location_row = location_rows[row_index]
|
||||
# print(f"Adding task {location_row.name}")
|
||||
|
||||
# Create Location
|
||||
location_id = self.base_id + row_index
|
||||
if location_row.category == "points" or location_row.category == "goal":
|
||||
location_id = None
|
||||
location = OSRSLocation(self.player, location_row.name, location_id)
|
||||
self.location_name_to_data[location_row.name] = location
|
||||
|
||||
# Add the location to its first region, or if it doesn't belong to one, to Menu
|
||||
region = self.region_name_to_data["Menu"]
|
||||
if location_row.regions:
|
||||
region = self.region_name_to_data[location_row.regions[0]]
|
||||
location.parent_region = region
|
||||
region.locations.append(location)
|
||||
|
||||
def set_rules(self) -> None:
|
||||
"""
|
||||
called to set access and item rules on locations and entrances.
|
||||
"""
|
||||
quest_attr_names = ["Cooks_Assistant", "Demon_Slayer", "Restless_Ghost", "Romeo_Juliet",
|
||||
"Sheep_Shearer", "Shield_of_Arrav", "Ernest_the_Chicken", "Vampyre_Slayer",
|
||||
"Imp_Catcher", "Prince_Ali_Rescue", "Dorics_Quest", "Black_Knights_Fortress",
|
||||
"Witchs_Potion", "Knights_Sword", "Goblin_Diplomacy", "Pirates_Treasure",
|
||||
"Rune_Mysteries", "Misthalin_Mystery", "Corsair_Curse", "X_Marks_the_Spot",
|
||||
"Below_Ice_Mountain"]
|
||||
for qp_attr_name in quest_attr_names:
|
||||
loc_name = getattr(LocationNames, f"QP_{qp_attr_name}")
|
||||
item_name = getattr(ItemNames, f"QP_{qp_attr_name}")
|
||||
self.multiworld.get_location(loc_name, self.player) \
|
||||
.place_locked_item(self.create_event(item_name))
|
||||
|
||||
for quest_attr_name in quest_attr_names:
|
||||
qp_loc_name = getattr(LocationNames, f"QP_{quest_attr_name}")
|
||||
q_loc_name = getattr(LocationNames, f"Q_{quest_attr_name}")
|
||||
add_rule(self.multiworld.get_location(qp_loc_name, self.player), lambda state, q_loc_name=q_loc_name: (
|
||||
self.multiworld.get_location(q_loc_name, self.player).can_reach(state)
|
||||
))
|
||||
|
||||
# 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"))
|
||||
self.multiworld.completion_condition[self.player] = lambda state: (state.has("Victory", self.player))
|
||||
|
||||
for location_name, location in self.location_name_to_data.items():
|
||||
location_row = self.location_rows_by_name[location_name]
|
||||
# Set up requirements for region
|
||||
for region_required_name in location_row.regions:
|
||||
region_required = self.region_name_to_data[region_required_name]
|
||||
add_rule(location,
|
||||
lambda state, region_required=region_required: state.can_reach(region_required, "Region",
|
||||
self.player))
|
||||
for skill_req in location_row.skills:
|
||||
add_rule(location, self.get_skill_rule(skill_req.skill, skill_req.level))
|
||||
for item_req in location_row.items:
|
||||
add_rule(location, lambda state, item_req=item_req: state.has(item_req, self.player))
|
||||
if location_row.qp:
|
||||
add_rule(location, lambda state, location_row=location_row: self.quest_points(state) > location_row.qp)
|
||||
|
||||
def create_region(self, name: str) -> "Region":
|
||||
region = Region(name, self.player, self.multiworld)
|
||||
self.region_name_to_data[name] = region
|
||||
self.multiworld.regions.append(region)
|
||||
return region
|
||||
|
||||
def create_item(self, item_name: str) -> "Item":
|
||||
item = [item for item in item_rows if item.name == item_name][0]
|
||||
index = item_rows.index(item)
|
||||
return OSRSItem(item.name, item.progression, self.base_id + index, self.player)
|
||||
|
||||
def create_event(self, event: str):
|
||||
# while we are at it, we can also add a helper to create events
|
||||
return OSRSItem(event, ItemClassification.progression, None, self.player)
|
||||
|
||||
def quest_points(self, state):
|
||||
qp = 0
|
||||
for qp_event in QP_Items:
|
||||
if state.has(qp_event, self.player):
|
||||
qp += int(qp_event[0])
|
||||
return qp
|
||||
|
||||
"""
|
||||
Ensures a target level can be reached with available resources
|
||||
"""
|
||||
|
||||
def get_skill_rule(self, skill, level) -> CollectionRule:
|
||||
if skill.lower() == "fishing":
|
||||
if self.options.brutal_grinds or level < 5:
|
||||
return lambda state: state.can_reach(RegionNames.Shrimp, "Region", self.player)
|
||||
if level < 20:
|
||||
return lambda state: state.can_reach(RegionNames.Shrimp, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Port_Sarim, "Region", self.player)
|
||||
else:
|
||||
return lambda state: state.can_reach(RegionNames.Shrimp, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Port_Sarim, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Fly_Fish, "Region", self.player)
|
||||
if skill.lower() == "mining":
|
||||
if self.options.brutal_grinds or level < 15:
|
||||
return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) or \
|
||||
state.can_reach(RegionNames.Clay_Rock, "Region", self.player)
|
||||
else:
|
||||
# Iron is the best way to train all the way to 99, so having access to iron is all you need to check for
|
||||
return lambda state: (state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) or
|
||||
state.can_reach(RegionNames.Clay_Rock, "Region", self.player)) and \
|
||||
state.can_reach(RegionNames.Iron_Rock, "Region", self.player)
|
||||
if skill.lower() == "woodcutting":
|
||||
if self.options.brutal_grinds or level < 15:
|
||||
# I've checked. There is not a single chunk in the f2p that does not have at least one normal tree.
|
||||
# Even the desert.
|
||||
return lambda state: True
|
||||
if level < 30:
|
||||
return lambda state: state.can_reach(RegionNames.Oak_Tree, "Region", self.player)
|
||||
else:
|
||||
return lambda state: state.can_reach(RegionNames.Oak_Tree, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Willow_Tree, "Region", self.player)
|
||||
if skill.lower() == "smithing":
|
||||
if self.options.brutal_grinds:
|
||||
return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Furnace, "Region", self.player)
|
||||
if level < 15:
|
||||
# Lumbridge has a special bronze-only anvil. This is the only anvil of its type so it's not included
|
||||
# in the "Anvil" resource region. We still need to check for it though.
|
||||
return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Furnace, "Region", self.player) and \
|
||||
(state.can_reach(RegionNames.Anvil, "Region", self.player) or
|
||||
state.can_reach(RegionNames.Lumbridge, "Region", self.player))
|
||||
if level < 30:
|
||||
# For levels between 15 and 30, the lumbridge anvil won't cut it. Only a real one will do
|
||||
return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Iron_Rock, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Furnace, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Anvil, "Region", self.player)
|
||||
else:
|
||||
return lambda state: state.can_reach(RegionNames.Bronze_Ores, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Iron_Rock, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Coal_Rock, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Furnace, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Anvil, "Region", self.player)
|
||||
if skill.lower() == "crafting":
|
||||
# Crafting is really complex. Need a lot of sub-rules to make this even remotely readable
|
||||
def can_spin(state):
|
||||
return state.can_reach(RegionNames.Sheep, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Spinning_Wheel, "Region", self.player)
|
||||
|
||||
def can_pot(state):
|
||||
return state.can_reach(RegionNames.Clay_Rock, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Barbarian_Village, "Region", self.player)
|
||||
|
||||
def can_tan(state):
|
||||
return state.can_reach(RegionNames.Milk, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Al_Kharid, "Region", self.player)
|
||||
|
||||
def mould_access(state):
|
||||
return state.can_reach(RegionNames.Al_Kharid, "Region", self.player) or \
|
||||
state.can_reach(RegionNames.Rimmington, "Region", self.player)
|
||||
|
||||
def can_silver(state):
|
||||
|
||||
return state.can_reach(RegionNames.Silver_Rock, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Furnace, "Region", self.player) and mould_access(state)
|
||||
|
||||
def can_gold(state):
|
||||
return state.can_reach(RegionNames.Gold_Rock, "Region", self.player) and \
|
||||
state.can_reach(RegionNames.Furnace, "Region", self.player) and mould_access(state)
|
||||
|
||||
if self.options.brutal_grinds or level < 5:
|
||||
return lambda state: can_spin(state) or can_pot(state) or can_tan(state)
|
||||
|
||||
can_smelt_gold = self.get_skill_rule("smithing", 40)
|
||||
can_smelt_silver = self.get_skill_rule("smithing", 20)
|
||||
if level < 16:
|
||||
return lambda state: can_pot(state) or can_tan(state) or (can_gold(state) and can_smelt_gold(state))
|
||||
else:
|
||||
return lambda state: can_tan(state) or (can_silver(state) and can_smelt_silver(state)) or \
|
||||
(can_gold(state) and can_smelt_gold(state))
|
||||
if skill.lower() == "Cooking":
|
||||
if self.options.brutal_grinds or level < 15:
|
||||
return lambda state: state.can_reach(RegionNames.Milk, "Region", self.player) or \
|
||||
state.can_reach(RegionNames.Egg, "Region", self.player) or \
|
||||
state.can_reach(RegionNames.Shrimp, "Region", self.player) or \
|
||||
(state.can_reach(RegionNames.Wheat, "Region", self.player) and
|
||||
state.can_reach(RegionNames.Windmill, "Region", self.player))
|
||||
else:
|
||||
can_catch_fly_fish = self.get_skill_rule("fishing", 20)
|
||||
return lambda state: state.can_reach(RegionNames.Fly_Fish, "Region", self.player) and \
|
||||
can_catch_fly_fish(state) and \
|
||||
(state.can_reach(RegionNames.Milk, "Region", self.player) or
|
||||
state.can_reach(RegionNames.Egg, "Region", self.player) or
|
||||
state.can_reach(RegionNames.Shrimp, "Region", self.player) or
|
||||
(state.can_reach(RegionNames.Wheat, "Region", self.player) and
|
||||
state.can_reach(RegionNames.Windmill, "Region", self.player)))
|
||||
if skill.lower() == "runecraft":
|
||||
return lambda state: state.has(ItemNames.QP_Rune_Mysteries, self.player)
|
||||
if skill.lower() == "magic":
|
||||
return lambda state: state.can_reach(RegionNames.Mind_Runes, "Region", self.player)
|
||||
|
||||
return lambda state: True
|
|
@ -0,0 +1,114 @@
|
|||
# Old School Runescape
|
||||
|
||||
## What is the Goal of this Randomizer?
|
||||
The goal is to complete the quest "Dragon Slayer I" with limited access to gear and map chunks while following normal
|
||||
Ironman/Group Ironman restrictions on a fresh free-to-play account.
|
||||
|
||||
## Where is the options page?
|
||||
|
||||
The [player options page for this game](../player-options) contains all the options you need to configure and export a
|
||||
config file. OSRS contains many options for a highly customizable experience. The options available to you are:
|
||||
|
||||
* **Starting Area** - The starting region of your run. This is the first region you will have available, and you can always
|
||||
freely return to it (see the section below for when it is allowed to cross locked regions to access it)
|
||||
* You may select a starting city from the list of Lumbridge, Al Kharid, Varrock (East or West), Edgeville, Falador,
|
||||
Draynor Village, or The Wilderness (Ferox Enclave)
|
||||
* The option "Any Bank" will choose one of the above regions at random
|
||||
* The option "Chunksanity" can start you in _any_ chunk, regardless of whether it has access to a bank.
|
||||
* **Brutal Grinds** - If enabled, the logic will assume you are willing to go to great lengths to train skills.
|
||||
* As an example, when enabled, it might be in logic to obtain tin and copper from mob drops and smelt bronze bars to
|
||||
reach Smithing Level 40 to smelt gold for a task.
|
||||
* If left disabled, the logic will always ensure you have a reasonable method for training a skill to reach a specific
|
||||
task, such as having access to intermediate-level training options
|
||||
* **Progressive Tasks** - If enabled, tasks for a skill are generated in order from earliest to latest.
|
||||
* For example, your first Smithing task would always be "Smelt an Iron Bar", then "Smelt a Silver Bar", and so on.
|
||||
You would never have the task "Smelt a Gold Bar" without having every previous Smithing task as well.
|
||||
This can lead to a more consistent length of run, and is generally shorter than disabling it, but with less variety.
|
||||
* **Skill Category Weighting Options**
|
||||
* These are available in each task category (all trainable skills plus "Combat" and "General")
|
||||
* **Max [Category] Level** - The highest level you intend to have to reach in order to complete all tasks for this
|
||||
category. For the Combat category, this is the max level of monster you are willing to fight.
|
||||
General tasks do not have a level and thus do not have this option.
|
||||
* **Max [Category] Tasks** - The highest number of tasks in this category you are willing to be assigned.
|
||||
Note that you can end up with _less_ than this amount, but never more. The "General" category is used to fill remaining
|
||||
spots so a maximum is not specified, instead it has a _minimum_ count.
|
||||
* **[Category] Task Weighting** - The relative weighting of this category to all of the others. Increase this to make
|
||||
tasks in this category more likely.
|
||||
|
||||
## What does randomization do to this game?
|
||||
The OSRS Archipelago Randomizer takes the form of a "Chunkman" account, a form of challenge account
|
||||
where you are limited to specific regions of the map (known as "chunks") until you complete tasks to unlock
|
||||
more. The plugin will interface with the [Region Locker Plugin](https://github.com/slaytostay/region-locker) to
|
||||
visually display these chunk borders and highlight them as locked or unlocked. The optional included GPU plugin for the
|
||||
Region Locker can tint the locked areas gray, but is incompatible with other GPU plugins such as 117's HD OSRS.
|
||||
If you choose not to include it, the world map will show locked and unlocked regions instead.
|
||||
|
||||
In order to access a region, you will need to access it entirely through unlocked regions. At no point are you
|
||||
ever allowed to cross through locked regions, with the following exceptions:
|
||||
* If your starting region is not Lumbridge, when you complete Tutorial Island, you will need to traverse locked regions
|
||||
to reach your intended starting location.
|
||||
* If your starting region is not Lumbridge, you are allowed to "Home Teleport" to your starting region by using the
|
||||
Lumbridge Home Teleport Spell and then walking to your start location. This is to prevent you from getting "stuck" after
|
||||
using one-way transportation such as the Port Sarim Jail Teleport from Shantay Pass and being locked out of progression.
|
||||
* All of your starting Tutorial Island items are assumed to be available at all times. If you have lost an important
|
||||
item such as a Tinderbox, and cannot re-obtain it in your unlocked region, you are allowed to enter locked regions to
|
||||
replace it in the least obtrusive way possible.
|
||||
* If you need to adjust Group Ironman settings, such as adding or removing a member, you may freely access The Node
|
||||
to do so.
|
||||
|
||||
When passing through locked regions for such exceptions, do not interact with any NPCs, items, or enemies and attempt
|
||||
to spend as little time in them as possible.
|
||||
|
||||
The plugin will prevent equipping items that you have not unlocked the ability to wield. For example, attempting
|
||||
to equip an Iron Platebody before the first Progressive Armor unlock will display a chat message and will not
|
||||
equip the item.
|
||||
|
||||
The plugin will show a list of your current tasks in the sidebar. The plugin will be able to detect the completion
|
||||
of most tasks, but in the case that a task cannot be detected (for example, killing an enemy with no
|
||||
drop table such as Deadly Red Spiders), the task can be marked as complete manually by clicking
|
||||
on the button. This button can also be used to mark completed tasks you have done while playing OSRS mobile or
|
||||
on a different client without having the plugin available. Simply click the button the next time you are logged in to
|
||||
Runelite and connected to send the check.
|
||||
|
||||
Due to the nature of randomizing a live MMO with no ability to freely edit the character or adjust game logic or
|
||||
balancing, this randomizer relies heavily on **the honor system**. The plugin cannot prevent you from walking through
|
||||
locked regions or equipping locked items with the plugin disabled before connecting. It is important
|
||||
to acknowledge before starting that the entire purpose of the randomizer is a self-imposed challenge, and there
|
||||
is little point in cheating by circumventing the plugin's restrictions or marking a task complete without actually
|
||||
completing it. If you wish to play OSRS with no restrictions, that is always available without the plugin.
|
||||
|
||||
In order to access the AP Text Client commands (such as `!hint` or to chat with other players in the seed), enter your
|
||||
command in chat prefaced by the string `!ap`. Example commands:
|
||||
|
||||
`!ap buying gf 100k` -> Sends the message "buying gf 100k" to the server
|
||||
`!ap !hint Area: Lumbridge` -> Attempts to hint for the "Area: Lumbridge" item. Results will appear in your chat box.
|
||||
|
||||
Other server messages, such as chat, will appear in your chat box, prefaced by the Archipelago icon.
|
||||
|
||||
## What items and locations get shuffled?
|
||||
Items:
|
||||
- Every map region (at least one chunk but sometimes more)
|
||||
- Weapon tiers from iron to Rune (bronze is available from the start)
|
||||
- Armor tiers from iron to Rune (bronze is available from the start)
|
||||
- Two Spell Tiers (bolt and blast spells)
|
||||
- Three tiers of Ranged Armor (leather, studded leather + vambraces, green dragonhide)
|
||||
- Three tiers of Ranged Weapons (oak, willow, maple bows and their respective highest tier of arrows)
|
||||
|
||||
Locations:
|
||||
* Every Quest is a location that will always be included in every seed
|
||||
* A random assortment of tasks, separated into categories based on the skill required.
|
||||
These task categories can have different weights, minimums, and maximums based on your options.
|
||||
* For a full list of Locations, items, and regions, see the
|
||||
[Logic Document](https://docs.google.com/spreadsheets/d/1R8Cm8L6YkRWeiN7uYrdru8Vc1DlJ0aFAinH_fwhV8aU/edit?usp=sharing)
|
||||
|
||||
## Which items can be in another player's world?
|
||||
Any item or region unlock can be found in any player's world.
|
||||
|
||||
## What does another world's item look like in Old School Runescape?
|
||||
Upon completing a task, the item and recipient will be listed in the player's chatbox.
|
||||
|
||||
## When the player receives an item, what happens?
|
||||
In addition to the message appearing in the chatbox, a UI window will appear listing the item and who sent it.
|
||||
These boxes also appear when connecting to a seed already in progress to list the items you have acquired while offline.
|
||||
The sidebar will list all received items below the task list, starting with regions, then showing the highest tier of
|
||||
equipment in each category.
|
|
@ -0,0 +1,58 @@
|
|||
# Setup Guide for Old School Runescape
|
||||
|
||||
## Required Software
|
||||
|
||||
- [RuneLite](https://runelite.net/)
|
||||
- If the account being used has been migrated to a Jagex Account, the [Jagex Launcher](https://www.jagex.com/en-GB/launcher)
|
||||
will also be necessary to run RuneLite
|
||||
|
||||
## Configuring your YAML file
|
||||
|
||||
### What is a YAML file and why do I need one?
|
||||
|
||||
Your YAML file contains a set of configuration options which provide the generator with information about how it should
|
||||
generate your game. Each player of a multiworld will provide their own YAML file. This setup allows each player to enjoy
|
||||
an experience customized for their taste, and different players in the same multiworld can all have different options.
|
||||
|
||||
### Where do I get a YAML file?
|
||||
|
||||
You can customize your settings by visiting the
|
||||
[Old School Runescape Player Options Page](/games/Old%20School%20Runescape/player-options).
|
||||
|
||||
## Joining a MultiWorld Game
|
||||
|
||||
### Install the RuneLite Plugins
|
||||
Open RuneLite and click on the wrench icon on the right side. From there, click on the plug icon to access the
|
||||
Plugin Hub. You will need to install the [Archipelago Plugin](https://github.com/digiholic/osrs-archipelago)
|
||||
and [Region Locker Plugin](https://github.com/slaytostay/region-locker). The Region Locker plugin
|
||||
will include three plugins; only the `Region Locker` plugin itself is required. The `Region Locker GPU` plugin can be
|
||||
used to display locked chunks in gray, but is incompatible with other GPU plugins such as 117's HD OSRS and can be
|
||||
disabled.
|
||||
|
||||
### Create a new OSRS Account
|
||||
The OSRS Randomizer assumes you are playing on a newly created f2p Ironman account. As such, you will need to [create a
|
||||
new Runescape account](https://secure.runescape.com/m=account-creation/create_account?theme=oldschool).
|
||||
|
||||
If you already have a [Jagex Account](https://www.jagex.com/en-GB/accounts) you can add up to 20 characters on
|
||||
one account through the Jagex Launcher. Note that there is currently no way to _remove_ characters
|
||||
from a Jagex Account, as such, you might want to create a separate account to hold your Archipelago
|
||||
characters if you intend to use your main Jagex account for more characters in the future.
|
||||
|
||||
**Protip**: In order to avoid having to remember random email addresses for many accounts, take advantage of an email
|
||||
alias, a feature supported by most email providers. Any text after a `+` in your email address will redirect to your
|
||||
normal address, but the email will be recognized by the Jagex login as a new email address. For example, if your email
|
||||
were `Archipelago@gmail.com`, entering `Archipelago+OSRSRandomizer@gmail.com` would cause the confirmation email to
|
||||
be sent to your primary address, but the alias can be used to create a new account. One recommendation would be to
|
||||
include the date of generation in the account, such as `Archipelago+APYYMMDD@gmail.com` for easy memorability.
|
||||
|
||||
After creating an account, you may run through Tutorial Island without connecting; the randomizer has no
|
||||
effect on the Tutorial.
|
||||
|
||||
### Connect to the Multiserver
|
||||
In the Archipelago Plugin, enter your server information. The `Auto Reconnect on Login For` field should remain blank;
|
||||
it will be populated by the character name you first connect with, and it will reconnect to the AP server whenever that
|
||||
character logs in. Open the Archipelago panel on the right-hand side to connect to the multiworld while logged in to
|
||||
a game world to associate this character to the randomizer.
|
||||
|
||||
For further information about how to connect to the server in the RuneLite plugin,
|
||||
please see the [Archipelago Plugin](https://github.com/digiholic/osrs-archipelago) instructions.
|
Loading…
Reference in New Issue