mirror of
https://github.com/ArchipelagoMW/Archipelago.git
synced 2026-03-09 09:03:46 -07:00
Compare commits
9 Commits
NewSoupVi-
...
NewSoupVi-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a2e1c66f1d | ||
|
|
03442621f4 | ||
|
|
6803c373e5 | ||
|
|
575c338aa3 | ||
|
|
05ce29f7dc | ||
|
|
74697b679e | ||
|
|
cf6661439e | ||
|
|
6297a4efa5 | ||
|
|
8ddb49f071 |
@@ -1857,6 +1857,7 @@ async def process_client_cmd(ctx: Context, client: Client, args: dict):
|
||||
args["cmd"] = "SetReply"
|
||||
value = ctx.stored_data.get(args["key"], args.get("default", 0))
|
||||
args["original_value"] = copy.copy(value)
|
||||
args["slot"] = client.slot
|
||||
for operation in args["operations"]:
|
||||
func = modify_functions[operation["operation"]]
|
||||
value = func(value, operation["value"])
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -261,6 +261,7 @@ Sent to clients in response to a [Set](#Set) package if want_reply was set to tr
|
||||
| key | str | The key that was updated. |
|
||||
| value | any | The new value for the key. |
|
||||
| original_value | any | The value the key had before it was updated. Not present on "_read" prefixed special keys. |
|
||||
| slot | int | The slot that originally sent the Set package causing this change. |
|
||||
|
||||
Additional arguments added to the [Set](#Set) package that triggered this [SetReply](#SetReply) will also be passed along.
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ item_table = {
|
||||
"Mutant Costume": ItemData(698020, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_mutant_costume
|
||||
"Baby Nautilus": ItemData(698021, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_nautilus
|
||||
"Baby Piranha": ItemData(698022, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_piranha
|
||||
"Arnassi Armor": ItemData(698023, 1, ItemType.NORMAL, ItemGroup.UTILITY), # collectible_seahorse_costume
|
||||
"Arnassi Armor": ItemData(698023, 1, ItemType.PROGRESSION, ItemGroup.UTILITY), # collectible_seahorse_costume
|
||||
"Seed Bag": ItemData(698024, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_seed_bag
|
||||
"King's Skull": ItemData(698025, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_skull
|
||||
"Song Plant Spore": ItemData(698026, 1, ItemType.JUNK, ItemGroup.COLLECTIBLE), # collectible_spore_seed
|
||||
|
||||
@@ -45,7 +45,7 @@ class AquariaLocations:
|
||||
"Home Water, bulb below the grouper fish": 698058,
|
||||
"Home Water, bulb in the path below Nautilus Prime": 698059,
|
||||
"Home Water, bulb in the little room above the grouper fish": 698060,
|
||||
"Home Water, bulb in the end of the left path from the Verse Cave": 698061,
|
||||
"Home Water, bulb in the end of the path close to the Verse Cave": 698061,
|
||||
"Home Water, bulb in the top left path": 698062,
|
||||
"Home Water, bulb in the bottom left room": 698063,
|
||||
"Home Water, bulb close to Naija's Home": 698064,
|
||||
@@ -67,7 +67,7 @@ class AquariaLocations:
|
||||
|
||||
locations_song_cave = {
|
||||
"Song Cave, Erulian spirit": 698206,
|
||||
"Song Cave, bulb in the top left part": 698071,
|
||||
"Song Cave, bulb in the top right part": 698071,
|
||||
"Song Cave, bulb in the big anemone room": 698072,
|
||||
"Song Cave, bulb in the path to the singing statues": 698073,
|
||||
"Song Cave, bulb under the rock in the path to the singing statues": 698074,
|
||||
@@ -152,6 +152,9 @@ class AquariaLocations:
|
||||
|
||||
locations_arnassi_path = {
|
||||
"Arnassi Ruins, Arnassi Statue": 698164,
|
||||
}
|
||||
|
||||
locations_arnassi_cave_transturtle = {
|
||||
"Arnassi Ruins, Transturtle": 698217,
|
||||
}
|
||||
|
||||
@@ -269,9 +272,12 @@ class AquariaLocations:
|
||||
}
|
||||
|
||||
locations_forest_bl = {
|
||||
"Kelp Forest bottom left area, Transturtle": 698212,
|
||||
}
|
||||
|
||||
locations_forest_bl_sc = {
|
||||
"Kelp Forest bottom left area, bulb close to the spirit crystals": 698054,
|
||||
"Kelp Forest bottom left area, Walker Baby": 698186,
|
||||
"Kelp Forest bottom left area, Transturtle": 698212,
|
||||
}
|
||||
|
||||
locations_forest_br = {
|
||||
@@ -370,7 +376,7 @@ class AquariaLocations:
|
||||
|
||||
locations_sun_temple_r = {
|
||||
"Sun Temple, first bulb of the temple": 698091,
|
||||
"Sun Temple, bulb on the left part": 698092,
|
||||
"Sun Temple, bulb on the right part": 698092,
|
||||
"Sun Temple, bulb in the hidden room of the right part": 698093,
|
||||
"Sun Temple, Sun Key": 698182,
|
||||
}
|
||||
@@ -402,6 +408,9 @@ class AquariaLocations:
|
||||
"Abyss right area, bulb in the middle path": 698110,
|
||||
"Abyss right area, bulb behind the rock in the middle path": 698111,
|
||||
"Abyss right area, bulb in the left green room": 698112,
|
||||
}
|
||||
|
||||
locations_abyss_r_transturtle = {
|
||||
"Abyss right area, Transturtle": 698214,
|
||||
}
|
||||
|
||||
@@ -499,6 +508,7 @@ location_table = {
|
||||
**AquariaLocations.locations_skeleton_path_sc,
|
||||
**AquariaLocations.locations_arnassi,
|
||||
**AquariaLocations.locations_arnassi_path,
|
||||
**AquariaLocations.locations_arnassi_cave_transturtle,
|
||||
**AquariaLocations.locations_arnassi_crab_boss,
|
||||
**AquariaLocations.locations_sun_temple_l,
|
||||
**AquariaLocations.locations_sun_temple_r,
|
||||
@@ -509,6 +519,7 @@ location_table = {
|
||||
**AquariaLocations.locations_abyss_l,
|
||||
**AquariaLocations.locations_abyss_lb,
|
||||
**AquariaLocations.locations_abyss_r,
|
||||
**AquariaLocations.locations_abyss_r_transturtle,
|
||||
**AquariaLocations.locations_energy_temple_1,
|
||||
**AquariaLocations.locations_energy_temple_2,
|
||||
**AquariaLocations.locations_energy_temple_3,
|
||||
@@ -530,6 +541,7 @@ location_table = {
|
||||
**AquariaLocations.locations_forest_tr,
|
||||
**AquariaLocations.locations_forest_tr_fp,
|
||||
**AquariaLocations.locations_forest_bl,
|
||||
**AquariaLocations.locations_forest_bl_sc,
|
||||
**AquariaLocations.locations_forest_br,
|
||||
**AquariaLocations.locations_forest_boss,
|
||||
**AquariaLocations.locations_forest_boss_entrance,
|
||||
|
||||
@@ -14,97 +14,112 @@ from worlds.generic.Rules import add_rule, set_rule
|
||||
|
||||
# Every condition to connect regions
|
||||
|
||||
def _has_hot_soup(state:CollectionState, player: int) -> bool:
|
||||
def _has_hot_soup(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the hotsoup item"""
|
||||
return state.has("Hot soup", player)
|
||||
return state.has_any({"Hot soup", "Hot soup x 2"}, player)
|
||||
|
||||
|
||||
def _has_tongue_cleared(state:CollectionState, player: int) -> bool:
|
||||
def _has_tongue_cleared(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the Body tongue cleared item"""
|
||||
return state.has("Body tongue cleared", player)
|
||||
|
||||
|
||||
def _has_sun_crystal(state:CollectionState, player: int) -> bool:
|
||||
def _has_sun_crystal(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the Sun crystal item"""
|
||||
return state.has("Has sun crystal", player) and _has_bind_song(state, player)
|
||||
|
||||
|
||||
def _has_li(state:CollectionState, player: int) -> bool:
|
||||
def _has_li(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has Li in its team"""
|
||||
return state.has("Li and Li song", player)
|
||||
|
||||
|
||||
def _has_damaging_item(state:CollectionState, player: int) -> bool:
|
||||
def _has_damaging_item(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the shield song item"""
|
||||
return state.has_any({"Energy form", "Nature form", "Beast form", "Li and Li song", "Baby Nautilus",
|
||||
"Baby Piranha", "Baby Blaster"}, player)
|
||||
return state.has_any({"Energy form", "Nature form", "Beast form", "Li and Li song", "Baby Nautilus",
|
||||
"Baby Piranha", "Baby Blaster"}, player)
|
||||
|
||||
|
||||
def _has_shield_song(state:CollectionState, player: int) -> bool:
|
||||
def _has_energy_attack_item(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has items that can do a lot of damage (enough to beat bosses)"""
|
||||
return _has_energy_form(state, player) or _has_dual_form(state, player)
|
||||
|
||||
|
||||
def _has_shield_song(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the shield song item"""
|
||||
return state.has("Shield song", player)
|
||||
|
||||
|
||||
def _has_bind_song(state:CollectionState, player: int) -> bool:
|
||||
def _has_bind_song(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the bind song item"""
|
||||
return state.has("Bind song", player)
|
||||
|
||||
|
||||
def _has_energy_form(state:CollectionState, player: int) -> bool:
|
||||
def _has_energy_form(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the energy form item"""
|
||||
return state.has("Energy form", player)
|
||||
|
||||
|
||||
def _has_beast_form(state:CollectionState, player: int) -> bool:
|
||||
def _has_beast_form(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the beast form item"""
|
||||
return state.has("Beast form", player)
|
||||
|
||||
|
||||
def _has_nature_form(state:CollectionState, player: int) -> bool:
|
||||
def _has_beast_and_soup_form(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the beast form item"""
|
||||
return _has_beast_form(state, player) and _has_hot_soup(state, player)
|
||||
|
||||
|
||||
def _has_beast_form_or_arnassi_armor(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the beast form item"""
|
||||
return _has_beast_form(state, player) or state.has("Arnassi Armor", player)
|
||||
|
||||
|
||||
def _has_nature_form(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the nature form item"""
|
||||
return state.has("Nature form", player)
|
||||
|
||||
|
||||
def _has_sun_form(state:CollectionState, player: int) -> bool:
|
||||
def _has_sun_form(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the sun form item"""
|
||||
return state.has("Sun form", player)
|
||||
|
||||
|
||||
def _has_light(state:CollectionState, player: int) -> bool:
|
||||
def _has_light(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the light item"""
|
||||
return state.has("Baby Dumbo", player) or _has_sun_form(state, player)
|
||||
|
||||
|
||||
def _has_dual_form(state:CollectionState, player: int) -> bool:
|
||||
def _has_dual_form(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the dual form item"""
|
||||
return _has_li(state, player) and state.has("Dual form", player)
|
||||
|
||||
|
||||
def _has_fish_form(state:CollectionState, player: int) -> bool:
|
||||
def _has_fish_form(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the fish form item"""
|
||||
return state.has("Fish form", player)
|
||||
|
||||
|
||||
def _has_spirit_form(state:CollectionState, player: int) -> bool:
|
||||
def _has_spirit_form(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has the spirit form item"""
|
||||
return state.has("Spirit form", player)
|
||||
|
||||
|
||||
def _has_big_bosses(state:CollectionState, player: int) -> bool:
|
||||
def _has_big_bosses(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has beated every big bosses"""
|
||||
return state.has_all({"Fallen God beated", "Mithalan God beated", "Drunian God beated",
|
||||
"Sun God beated", "The Golem beated"}, player)
|
||||
"Sun God beated", "The Golem beated"}, player)
|
||||
|
||||
|
||||
def _has_mini_bosses(state:CollectionState, player: int) -> bool:
|
||||
def _has_mini_bosses(state: CollectionState, player: int) -> bool:
|
||||
"""`player` in `state` has beated every big bosses"""
|
||||
return state.has_all({"Nautilus Prime beated", "Blaster Peg Prime beated", "Mergog beated",
|
||||
"Mithalan priests beated", "Octopus Prime beated", "Crabbius Maximus beated",
|
||||
"Mantis Shrimp Prime beated", "King Jellyfish God Prime beated"}, player)
|
||||
"Mithalan priests beated", "Octopus Prime beated", "Crabbius Maximus beated",
|
||||
"Mantis Shrimp Prime beated", "King Jellyfish God Prime beated"}, player)
|
||||
|
||||
|
||||
def _has_secrets(state:CollectionState, player: int) -> bool:
|
||||
return state.has_all({"First secret obtained", "Second secret obtained", "Third secret obtained"},player)
|
||||
def _has_secrets(state: CollectionState, player: int) -> bool:
|
||||
return state.has_all({"First secret obtained", "Second secret obtained", "Third secret obtained"}, player)
|
||||
|
||||
|
||||
class AquariaRegions:
|
||||
@@ -134,6 +149,7 @@ class AquariaRegions:
|
||||
skeleton_path: Region
|
||||
skeleton_path_sc: Region
|
||||
arnassi: Region
|
||||
arnassi_cave_transturtle: Region
|
||||
arnassi_path: Region
|
||||
arnassi_crab_boss: Region
|
||||
simon: Region
|
||||
@@ -152,6 +168,7 @@ class AquariaRegions:
|
||||
forest_tr: Region
|
||||
forest_tr_fp: Region
|
||||
forest_bl: Region
|
||||
forest_bl_sc: Region
|
||||
forest_br: Region
|
||||
forest_boss: Region
|
||||
forest_boss_entrance: Region
|
||||
@@ -179,6 +196,7 @@ class AquariaRegions:
|
||||
abyss_l: Region
|
||||
abyss_lb: Region
|
||||
abyss_r: Region
|
||||
abyss_r_transturtle: Region
|
||||
ice_cave: Region
|
||||
bubble_cave: Region
|
||||
bubble_cave_boss: Region
|
||||
@@ -213,7 +231,7 @@ class AquariaRegions:
|
||||
"""
|
||||
|
||||
def __add_region(self, hint: str,
|
||||
locations: Optional[Dict[str, Optional[int]]]) -> Region:
|
||||
locations: Optional[Dict[str, int]]) -> Region:
|
||||
"""
|
||||
Create a new Region, add it to the `world` regions and return it.
|
||||
Be aware that this function have a side effect on ``world`.`regions`
|
||||
@@ -236,7 +254,7 @@ class AquariaRegions:
|
||||
self.home_water_nautilus = self.__add_region("Home Water, Nautilus nest",
|
||||
AquariaLocations.locations_home_water_nautilus)
|
||||
self.home_water_transturtle = self.__add_region("Home Water, turtle room",
|
||||
AquariaLocations.locations_home_water_transturtle)
|
||||
AquariaLocations.locations_home_water_transturtle)
|
||||
self.naija_home = self.__add_region("Naija's Home", AquariaLocations.locations_naija_home)
|
||||
self.song_cave = self.__add_region("Song Cave", AquariaLocations.locations_song_cave)
|
||||
|
||||
@@ -280,6 +298,8 @@ class AquariaRegions:
|
||||
self.arnassi = self.__add_region("Arnassi Ruins", AquariaLocations.locations_arnassi)
|
||||
self.arnassi_path = self.__add_region("Arnassi Ruins, back entrance path",
|
||||
AquariaLocations.locations_arnassi_path)
|
||||
self.arnassi_cave_transturtle = self.__add_region("Arnassi Ruins, transturtle area",
|
||||
AquariaLocations.locations_arnassi_cave_transturtle)
|
||||
self.arnassi_crab_boss = self.__add_region("Arnassi Ruins, Crabbius Maximus lair",
|
||||
AquariaLocations.locations_arnassi_crab_boss)
|
||||
|
||||
@@ -302,9 +322,9 @@ class AquariaRegions:
|
||||
AquariaLocations.locations_cathedral_r)
|
||||
self.cathedral_underground = self.__add_region("Mithalas Cathedral underground",
|
||||
AquariaLocations.locations_cathedral_underground)
|
||||
self.cathedral_boss_r = self.__add_region("Mithalas Cathedral, Mithalan God room",
|
||||
self.cathedral_boss_r = self.__add_region("Mithalas Cathedral, Mithalan God room", None)
|
||||
self.cathedral_boss_l = self.__add_region("Mithalas Cathedral, after Mithalan God room",
|
||||
AquariaLocations.locations_cathedral_boss)
|
||||
self.cathedral_boss_l = self.__add_region("Mithalas Cathedral, after Mithalan God room", None)
|
||||
|
||||
def __create_forest(self) -> None:
|
||||
"""
|
||||
@@ -320,6 +340,8 @@ class AquariaRegions:
|
||||
AquariaLocations.locations_forest_tr_fp)
|
||||
self.forest_bl = self.__add_region("Kelp Forest bottom left area",
|
||||
AquariaLocations.locations_forest_bl)
|
||||
self.forest_bl_sc = self.__add_region("Kelp Forest bottom left area, spirit crystals",
|
||||
AquariaLocations.locations_forest_bl_sc)
|
||||
self.forest_br = self.__add_region("Kelp Forest bottom right area",
|
||||
AquariaLocations.locations_forest_br)
|
||||
self.forest_sprite_cave = self.__add_region("Kelp Forest spirit cave",
|
||||
@@ -375,9 +397,9 @@ class AquariaRegions:
|
||||
self.sun_temple_r = self.__add_region("Sun Temple right area",
|
||||
AquariaLocations.locations_sun_temple_r)
|
||||
self.sun_temple_boss_path = self.__add_region("Sun Temple before boss area",
|
||||
AquariaLocations.locations_sun_temple_boss_path)
|
||||
AquariaLocations.locations_sun_temple_boss_path)
|
||||
self.sun_temple_boss = self.__add_region("Sun Temple boss area",
|
||||
AquariaLocations.locations_sun_temple_boss)
|
||||
AquariaLocations.locations_sun_temple_boss)
|
||||
|
||||
def __create_abyss(self) -> None:
|
||||
"""
|
||||
@@ -388,6 +410,8 @@ class AquariaRegions:
|
||||
AquariaLocations.locations_abyss_l)
|
||||
self.abyss_lb = self.__add_region("Abyss left bottom area", AquariaLocations.locations_abyss_lb)
|
||||
self.abyss_r = self.__add_region("Abyss right area", AquariaLocations.locations_abyss_r)
|
||||
self.abyss_r_transturtle = self.__add_region("Abyss right area, transturtle",
|
||||
AquariaLocations.locations_abyss_r_transturtle)
|
||||
self.ice_cave = self.__add_region("Ice Cave", AquariaLocations.locations_ice_cave)
|
||||
self.bubble_cave = self.__add_region("Bubble Cave", AquariaLocations.locations_bubble_cave)
|
||||
self.bubble_cave_boss = self.__add_region("Bubble Cave boss area", AquariaLocations.locations_bubble_cave_boss)
|
||||
@@ -407,7 +431,7 @@ class AquariaRegions:
|
||||
self.sunken_city_r = self.__add_region("Sunken City right area",
|
||||
AquariaLocations.locations_sunken_city_r)
|
||||
self.sunken_city_boss = self.__add_region("Sunken City boss area",
|
||||
AquariaLocations.locations_sunken_city_boss)
|
||||
AquariaLocations.locations_sunken_city_boss)
|
||||
|
||||
def __create_body(self) -> None:
|
||||
"""
|
||||
@@ -427,7 +451,7 @@ class AquariaRegions:
|
||||
self.final_boss_tube = self.__add_region("The Body, final boss area turtle room",
|
||||
AquariaLocations.locations_final_boss_tube)
|
||||
self.final_boss = self.__add_region("The Body, final boss",
|
||||
AquariaLocations.locations_final_boss)
|
||||
AquariaLocations.locations_final_boss)
|
||||
self.final_boss_end = self.__add_region("The Body, final boss area", None)
|
||||
|
||||
def __connect_one_way_regions(self, source_name: str, destination_name: str,
|
||||
@@ -455,8 +479,8 @@ class AquariaRegions:
|
||||
"""
|
||||
Connect entrances of the different regions around `home_water`
|
||||
"""
|
||||
self.__connect_regions("Menu", "Verse Cave right area",
|
||||
self.menu, self.verse_cave_r)
|
||||
self.__connect_one_way_regions("Menu", "Verse Cave right area",
|
||||
self.menu, self.verse_cave_r)
|
||||
self.__connect_regions("Verse Cave left area", "Verse Cave right area",
|
||||
self.verse_cave_l, self.verse_cave_r)
|
||||
self.__connect_regions("Verse Cave", "Home Water", self.verse_cave_l, self.home_water)
|
||||
@@ -464,7 +488,8 @@ class AquariaRegions:
|
||||
self.__connect_regions("Home Water", "Song Cave", self.home_water, self.song_cave)
|
||||
self.__connect_regions("Home Water", "Home Water, nautilus nest",
|
||||
self.home_water, self.home_water_nautilus,
|
||||
lambda state: _has_energy_form(state, self.player) and _has_bind_song(state, self.player))
|
||||
lambda state: _has_energy_attack_item(state, self.player) and
|
||||
_has_bind_song(state, self.player))
|
||||
self.__connect_regions("Home Water", "Home Water transturtle room",
|
||||
self.home_water, self.home_water_transturtle)
|
||||
self.__connect_regions("Home Water", "Energy Temple first area",
|
||||
@@ -472,7 +497,7 @@ class AquariaRegions:
|
||||
lambda state: _has_bind_song(state, self.player))
|
||||
self.__connect_regions("Home Water", "Energy Temple_altar",
|
||||
self.home_water, self.energy_temple_altar,
|
||||
lambda state: _has_energy_form(state, self.player) and
|
||||
lambda state: _has_energy_attack_item(state, self.player) and
|
||||
_has_bind_song(state, self.player))
|
||||
self.__connect_regions("Energy Temple first area", "Energy Temple second area",
|
||||
self.energy_temple_1, self.energy_temple_2,
|
||||
@@ -482,28 +507,28 @@ class AquariaRegions:
|
||||
lambda state: _has_fish_form(state, self.player))
|
||||
self.__connect_regions("Energy Temple idol room", "Energy Temple boss area",
|
||||
self.energy_temple_idol, self.energy_temple_boss,
|
||||
lambda state: _has_energy_form(state, self.player))
|
||||
lambda state: _has_energy_attack_item(state, self.player) and
|
||||
_has_fish_form(state, self.player))
|
||||
self.__connect_one_way_regions("Energy Temple first area", "Energy Temple boss area",
|
||||
self.energy_temple_1, self.energy_temple_boss,
|
||||
lambda state: _has_beast_form(state, self.player) and
|
||||
_has_energy_form(state, self.player))
|
||||
_has_energy_attack_item(state, self.player))
|
||||
self.__connect_one_way_regions("Energy Temple boss area", "Energy Temple first area",
|
||||
self.energy_temple_boss, self.energy_temple_1,
|
||||
lambda state: _has_energy_form(state, self.player))
|
||||
lambda state: _has_energy_attack_item(state, self.player))
|
||||
self.__connect_regions("Energy Temple second area", "Energy Temple third area",
|
||||
self.energy_temple_2, self.energy_temple_3,
|
||||
lambda state: _has_bind_song(state, self.player) and
|
||||
_has_energy_form(state, self.player))
|
||||
lambda state: _has_energy_form(state, self.player))
|
||||
self.__connect_regions("Energy Temple boss area", "Energy Temple blaster room",
|
||||
self.energy_temple_boss, self.energy_temple_blaster_room,
|
||||
lambda state: _has_nature_form(state, self.player) and
|
||||
_has_bind_song(state, self.player) and
|
||||
_has_energy_form(state, self.player))
|
||||
_has_energy_attack_item(state, self.player))
|
||||
self.__connect_regions("Energy Temple first area", "Energy Temple blaster room",
|
||||
self.energy_temple_1, self.energy_temple_blaster_room,
|
||||
lambda state: _has_nature_form(state, self.player) and
|
||||
_has_bind_song(state, self.player) and
|
||||
_has_energy_form(state, self.player) and
|
||||
_has_energy_attack_item(state, self.player) and
|
||||
_has_beast_form(state, self.player))
|
||||
self.__connect_regions("Home Water", "Open Water top left area",
|
||||
self.home_water, self.openwater_tl)
|
||||
@@ -520,7 +545,7 @@ class AquariaRegions:
|
||||
self.openwater_tl, self.forest_br)
|
||||
self.__connect_regions("Open Water top right area", "Open Water top right area, turtle room",
|
||||
self.openwater_tr, self.openwater_tr_turtle,
|
||||
lambda state: _has_beast_form(state, self.player))
|
||||
lambda state: _has_beast_form_or_arnassi_armor(state, self.player))
|
||||
self.__connect_regions("Open Water top right area", "Open Water bottom right area",
|
||||
self.openwater_tr, self.openwater_br)
|
||||
self.__connect_regions("Open Water top right area", "Mithalas City",
|
||||
@@ -529,10 +554,9 @@ class AquariaRegions:
|
||||
self.openwater_tr, self.veil_bl)
|
||||
self.__connect_one_way_regions("Open Water top right area", "Veil bottom right",
|
||||
self.openwater_tr, self.veil_br,
|
||||
lambda state: _has_beast_form(state, self.player))
|
||||
lambda state: _has_beast_form_or_arnassi_armor(state, self.player))
|
||||
self.__connect_one_way_regions("Veil bottom right", "Open Water top right area",
|
||||
self.veil_br, self.openwater_tr,
|
||||
lambda state: _has_beast_form(state, self.player))
|
||||
self.veil_br, self.openwater_tr)
|
||||
self.__connect_regions("Open Water bottom left area", "Open Water bottom right area",
|
||||
self.openwater_bl, self.openwater_br)
|
||||
self.__connect_regions("Open Water bottom left area", "Skeleton path",
|
||||
@@ -551,10 +575,14 @@ class AquariaRegions:
|
||||
self.arnassi, self.openwater_br)
|
||||
self.__connect_regions("Arnassi", "Arnassi path",
|
||||
self.arnassi, self.arnassi_path)
|
||||
self.__connect_regions("Arnassi ruins, transturtle area", "Arnassi path",
|
||||
self.arnassi_cave_transturtle, self.arnassi_path,
|
||||
lambda state: _has_fish_form(state, self.player))
|
||||
self.__connect_one_way_regions("Arnassi path", "Arnassi crab boss area",
|
||||
self.arnassi_path, self.arnassi_crab_boss,
|
||||
lambda state: _has_beast_form(state, self.player) and
|
||||
_has_energy_form(state, self.player))
|
||||
lambda state: _has_beast_form_or_arnassi_armor(state, self.player) and
|
||||
(_has_energy_attack_item(state, self.player) or
|
||||
_has_nature_form(state, self.player)))
|
||||
self.__connect_one_way_regions("Arnassi crab boss area", "Arnassi path",
|
||||
self.arnassi_crab_boss, self.arnassi_path)
|
||||
|
||||
@@ -564,61 +592,62 @@ class AquariaRegions:
|
||||
"""
|
||||
self.__connect_one_way_regions("Mithalas City", "Mithalas City top path",
|
||||
self.mithalas_city, self.mithalas_city_top_path,
|
||||
lambda state: _has_beast_form(state, self.player))
|
||||
lambda state: _has_beast_form_or_arnassi_armor(state, self.player))
|
||||
self.__connect_one_way_regions("Mithalas City_top_path", "Mithalas City",
|
||||
self.mithalas_city_top_path, self.mithalas_city)
|
||||
self.__connect_regions("Mithalas City", "Mithalas City home with fishpass",
|
||||
self.mithalas_city, self.mithalas_city_fishpass,
|
||||
lambda state: _has_fish_form(state, self.player))
|
||||
self.__connect_regions("Mithalas City", "Mithalas castle",
|
||||
self.mithalas_city, self.cathedral_l,
|
||||
lambda state: _has_fish_form(state, self.player))
|
||||
self.mithalas_city, self.cathedral_l)
|
||||
self.__connect_one_way_regions("Mithalas City top path", "Mithalas castle, flower tube",
|
||||
self.mithalas_city_top_path,
|
||||
self.cathedral_l_tube,
|
||||
lambda state: _has_nature_form(state, self.player) and
|
||||
_has_energy_form(state, self.player))
|
||||
_has_energy_attack_item(state, self.player))
|
||||
self.__connect_one_way_regions("Mithalas castle, flower tube area", "Mithalas City top path",
|
||||
self.cathedral_l_tube,
|
||||
self.mithalas_city_top_path,
|
||||
lambda state: _has_beast_form(state, self.player) and
|
||||
_has_nature_form(state, self.player))
|
||||
lambda state: _has_nature_form(state, self.player))
|
||||
self.__connect_one_way_regions("Mithalas castle flower tube area", "Mithalas castle, spirit crystals",
|
||||
self.cathedral_l_tube, self.cathedral_l_sc,
|
||||
lambda state: _has_spirit_form(state, self.player))
|
||||
self.cathedral_l_tube, self.cathedral_l_sc,
|
||||
lambda state: _has_spirit_form(state, self.player))
|
||||
self.__connect_one_way_regions("Mithalas castle_flower tube area", "Mithalas castle",
|
||||
self.cathedral_l_tube, self.cathedral_l,
|
||||
lambda state: _has_spirit_form(state, self.player))
|
||||
self.cathedral_l_tube, self.cathedral_l,
|
||||
lambda state: _has_spirit_form(state, self.player))
|
||||
self.__connect_regions("Mithalas castle", "Mithalas castle, spirit crystals",
|
||||
self.cathedral_l, self.cathedral_l_sc,
|
||||
lambda state: _has_spirit_form(state, self.player))
|
||||
self.__connect_regions("Mithalas castle", "Cathedral boss left area",
|
||||
self.cathedral_l, self.cathedral_boss_l,
|
||||
lambda state: _has_beast_form(state, self.player) and
|
||||
_has_energy_form(state, self.player) and
|
||||
_has_bind_song(state, self.player))
|
||||
self.__connect_one_way_regions("Mithalas castle", "Cathedral boss right area",
|
||||
self.cathedral_l, self.cathedral_boss_r,
|
||||
lambda state: _has_beast_form(state, self.player))
|
||||
self.__connect_one_way_regions("Cathedral boss left area", "Mithalas castle",
|
||||
self.cathedral_boss_l, self.cathedral_l,
|
||||
lambda state: _has_beast_form(state, self.player))
|
||||
self.__connect_regions("Mithalas castle", "Mithalas Cathedral underground",
|
||||
self.cathedral_l, self.cathedral_underground,
|
||||
lambda state: _has_beast_form(state, self.player) and
|
||||
_has_bind_song(state, self.player))
|
||||
self.__connect_regions("Mithalas castle", "Mithalas Cathedral",
|
||||
self.cathedral_l, self.cathedral_r,
|
||||
lambda state: _has_bind_song(state, self.player) and
|
||||
_has_energy_form(state, self.player))
|
||||
self.__connect_regions("Mithalas Cathedral", "Mithalas Cathedral underground",
|
||||
self.cathedral_r, self.cathedral_underground,
|
||||
lambda state: _has_energy_form(state, self.player))
|
||||
self.__connect_one_way_regions("Mithalas Cathedral underground", "Cathedral boss left area",
|
||||
self.cathedral_underground, self.cathedral_boss_r,
|
||||
lambda state: _has_energy_form(state, self.player) and
|
||||
_has_bind_song(state, self.player))
|
||||
self.__connect_one_way_regions("Cathedral boss left area", "Mithalas Cathedral underground",
|
||||
lambda state: _has_beast_form(state, self.player))
|
||||
self.__connect_one_way_regions("Mithalas castle", "Mithalas Cathedral",
|
||||
self.cathedral_l, self.cathedral_r,
|
||||
lambda state: _has_bind_song(state, self.player) and
|
||||
_has_energy_attack_item(state, self.player))
|
||||
self.__connect_one_way_regions("Mithalas Cathedral", "Mithalas Cathedral underground",
|
||||
self.cathedral_r, self.cathedral_underground)
|
||||
self.__connect_one_way_regions("Mithalas Cathedral underground", "Mithalas Cathedral",
|
||||
self.cathedral_underground, self.cathedral_r,
|
||||
lambda state: _has_beast_form(state, self.player) and
|
||||
_has_energy_attack_item(state, self.player))
|
||||
self.__connect_one_way_regions("Mithalas Cathedral underground", "Cathedral boss right area",
|
||||
self.cathedral_underground, self.cathedral_boss_r)
|
||||
self.__connect_one_way_regions("Cathedral boss right area", "Mithalas Cathedral underground",
|
||||
self.cathedral_boss_r, self.cathedral_underground,
|
||||
lambda state: _has_beast_form(state, self.player))
|
||||
self.__connect_regions("Cathedral boss right area", "Cathedral boss left area",
|
||||
self.__connect_one_way_regions("Cathedral boss right area", "Cathedral boss left area",
|
||||
self.cathedral_boss_r, self.cathedral_boss_l,
|
||||
lambda state: _has_bind_song(state, self.player) and
|
||||
_has_energy_form(state, self.player))
|
||||
_has_energy_attack_item(state, self.player))
|
||||
self.__connect_one_way_regions("Cathedral boss left area", "Cathedral boss right area",
|
||||
self.cathedral_boss_l, self.cathedral_boss_r)
|
||||
|
||||
def __connect_forest_regions(self) -> None:
|
||||
"""
|
||||
@@ -628,6 +657,12 @@ class AquariaRegions:
|
||||
self.forest_br, self.veil_bl)
|
||||
self.__connect_regions("Forest bottom right", "Forest bottom left area",
|
||||
self.forest_br, self.forest_bl)
|
||||
self.__connect_one_way_regions("Forest bottom left area", "Forest bottom left area, spirit crystals",
|
||||
self.forest_bl, self.forest_bl_sc,
|
||||
lambda state: _has_energy_attack_item(state, self.player) or
|
||||
_has_fish_form(state, self.player))
|
||||
self.__connect_one_way_regions("Forest bottom left area, spirit crystals", "Forest bottom left area",
|
||||
self.forest_bl_sc, self.forest_bl)
|
||||
self.__connect_regions("Forest bottom right", "Forest top right area",
|
||||
self.forest_br, self.forest_tr)
|
||||
self.__connect_regions("Forest bottom left area", "Forest fish cave",
|
||||
@@ -641,7 +676,7 @@ class AquariaRegions:
|
||||
self.forest_tl, self.forest_tl_fp,
|
||||
lambda state: _has_nature_form(state, self.player) and
|
||||
_has_bind_song(state, self.player) and
|
||||
_has_energy_form(state, self.player) and
|
||||
_has_energy_attack_item(state, self.player) and
|
||||
_has_fish_form(state, self.player))
|
||||
self.__connect_regions("Forest top left area", "Forest top right area",
|
||||
self.forest_tl, self.forest_tr)
|
||||
@@ -649,7 +684,7 @@ class AquariaRegions:
|
||||
self.forest_tl, self.forest_boss_entrance)
|
||||
self.__connect_regions("Forest boss area", "Forest boss entrance",
|
||||
self.forest_boss, self.forest_boss_entrance,
|
||||
lambda state: _has_energy_form(state, self.player))
|
||||
lambda state: _has_energy_attack_item(state, self.player))
|
||||
self.__connect_regions("Forest top right area", "Forest top right area fish pass",
|
||||
self.forest_tr, self.forest_tr_fp,
|
||||
lambda state: _has_fish_form(state, self.player))
|
||||
@@ -663,7 +698,7 @@ class AquariaRegions:
|
||||
self.__connect_regions("Fermog cave", "Fermog boss",
|
||||
self.mermog_cave, self.mermog_boss,
|
||||
lambda state: _has_beast_form(state, self.player) and
|
||||
_has_energy_form(state, self.player))
|
||||
_has_energy_attack_item(state, self.player))
|
||||
|
||||
def __connect_veil_regions(self) -> None:
|
||||
"""
|
||||
@@ -681,8 +716,7 @@ class AquariaRegions:
|
||||
self.veil_b_sc, self.veil_br,
|
||||
lambda state: _has_spirit_form(state, self.player))
|
||||
self.__connect_regions("Veil bottom right", "Veil top left area",
|
||||
self.veil_br, self.veil_tl,
|
||||
lambda state: _has_beast_form(state, self.player))
|
||||
self.veil_br, self.veil_tl)
|
||||
self.__connect_regions("Veil top left area", "Veil_top left area, fish pass",
|
||||
self.veil_tl, self.veil_tl_fp,
|
||||
lambda state: _has_fish_form(state, self.player))
|
||||
@@ -691,20 +725,25 @@ class AquariaRegions:
|
||||
self.__connect_regions("Veil top left area", "Turtle cave",
|
||||
self.veil_tl, self.turtle_cave)
|
||||
self.__connect_regions("Turtle cave", "Turtle cave Bubble Cliff",
|
||||
self.turtle_cave, self.turtle_cave_bubble,
|
||||
lambda state: _has_beast_form(state, self.player))
|
||||
self.turtle_cave, self.turtle_cave_bubble)
|
||||
self.__connect_regions("Veil right of sun temple", "Sun Temple right area",
|
||||
self.veil_tr_r, self.sun_temple_r)
|
||||
self.__connect_regions("Sun Temple right area", "Sun Temple left area",
|
||||
self.sun_temple_r, self.sun_temple_l,
|
||||
lambda state: _has_bind_song(state, self.player))
|
||||
self.__connect_one_way_regions("Sun Temple right area", "Sun Temple left area",
|
||||
self.sun_temple_r, self.sun_temple_l,
|
||||
lambda state: _has_bind_song(state, self.player) or
|
||||
_has_light(state, self.player))
|
||||
self.__connect_one_way_regions("Sun Temple left area", "Sun Temple right area",
|
||||
self.sun_temple_l, self.sun_temple_r,
|
||||
lambda state: _has_light(state, self.player))
|
||||
self.__connect_regions("Sun Temple left area", "Veil left of sun temple",
|
||||
self.sun_temple_l, self.veil_tr_l)
|
||||
self.__connect_regions("Sun Temple left area", "Sun Temple before boss area",
|
||||
self.sun_temple_l, self.sun_temple_boss_path)
|
||||
self.sun_temple_l, self.sun_temple_boss_path,
|
||||
lambda state: _has_light(state, self.player) or
|
||||
_has_sun_crystal(state, self.player))
|
||||
self.__connect_regions("Sun Temple before boss area", "Sun Temple boss area",
|
||||
self.sun_temple_boss_path, self.sun_temple_boss,
|
||||
lambda state: _has_energy_form(state, self.player))
|
||||
lambda state: _has_energy_attack_item(state, self.player))
|
||||
self.__connect_one_way_regions("Sun Temple boss area", "Veil left of sun temple",
|
||||
self.sun_temple_boss, self.veil_tr_l)
|
||||
self.__connect_regions("Veil left of sun temple", "Octo cave top path",
|
||||
@@ -712,7 +751,7 @@ class AquariaRegions:
|
||||
lambda state: _has_fish_form(state, self.player) and
|
||||
_has_sun_form(state, self.player) and
|
||||
_has_beast_form(state, self.player) and
|
||||
_has_energy_form(state, self.player))
|
||||
_has_energy_attack_item(state, self.player))
|
||||
self.__connect_regions("Veil left of sun temple", "Octo cave bottom path",
|
||||
self.veil_tr_l, self.octo_cave_b,
|
||||
lambda state: _has_fish_form(state, self.player))
|
||||
@@ -728,16 +767,22 @@ class AquariaRegions:
|
||||
self.abyss_lb, self.sunken_city_r,
|
||||
lambda state: _has_li(state, self.player))
|
||||
self.__connect_one_way_regions("Abyss left bottom area", "Body center area",
|
||||
self.abyss_lb, self.body_c,
|
||||
lambda state: _has_tongue_cleared(state, self.player))
|
||||
self.abyss_lb, self.body_c,
|
||||
lambda state: _has_tongue_cleared(state, self.player))
|
||||
self.__connect_one_way_regions("Body center area", "Abyss left bottom area",
|
||||
self.body_c, self.abyss_lb)
|
||||
self.body_c, self.abyss_lb)
|
||||
self.__connect_regions("Abyss left area", "King jellyfish cave",
|
||||
self.abyss_l, self.king_jellyfish_cave,
|
||||
lambda state: _has_energy_form(state, self.player) and
|
||||
_has_beast_form(state, self.player))
|
||||
lambda state: (_has_energy_form(state, self.player) and
|
||||
_has_beast_form(state, self.player)) or
|
||||
_has_dual_form(state, self.player))
|
||||
self.__connect_regions("Abyss left area", "Abyss right area",
|
||||
self.abyss_l, self.abyss_r)
|
||||
self.__connect_one_way_regions("Abyss right area", "Abyss right area, transturtle",
|
||||
self.abyss_r, self.abyss_r_transturtle)
|
||||
self.__connect_one_way_regions("Abyss right area, transturtle", "Abyss right area",
|
||||
self.abyss_r_transturtle, self.abyss_r,
|
||||
lambda state: _has_light(state, self.player))
|
||||
self.__connect_regions("Abyss right area", "Inside the whale",
|
||||
self.abyss_r, self.whale,
|
||||
lambda state: _has_spirit_form(state, self.player) and
|
||||
@@ -747,13 +792,14 @@ class AquariaRegions:
|
||||
lambda state: _has_spirit_form(state, self.player) and
|
||||
_has_sun_form(state, self.player) and
|
||||
_has_bind_song(state, self.player) and
|
||||
_has_energy_form(state, self.player))
|
||||
_has_energy_attack_item(state, self.player))
|
||||
self.__connect_regions("Abyss right area", "Ice Cave",
|
||||
self.abyss_r, self.ice_cave,
|
||||
lambda state: _has_spirit_form(state, self.player))
|
||||
self.__connect_regions("Abyss right area", "Bubble Cave",
|
||||
self.__connect_regions("Ice cave", "Bubble Cave",
|
||||
self.ice_cave, self.bubble_cave,
|
||||
lambda state: _has_beast_form(state, self.player))
|
||||
lambda state: _has_beast_form(state, self.player) or
|
||||
_has_hot_soup(state, self.player))
|
||||
self.__connect_regions("Bubble Cave boss area", "Bubble Cave",
|
||||
self.bubble_cave, self.bubble_cave_boss,
|
||||
lambda state: _has_nature_form(state, self.player) and _has_bind_song(state, self.player)
|
||||
@@ -772,7 +818,7 @@ class AquariaRegions:
|
||||
self.sunken_city_l, self.sunken_city_boss,
|
||||
lambda state: _has_beast_form(state, self.player) and
|
||||
_has_sun_form(state, self.player) and
|
||||
_has_energy_form(state, self.player) and
|
||||
_has_energy_attack_item(state, self.player) and
|
||||
_has_bind_song(state, self.player))
|
||||
|
||||
def __connect_body_regions(self) -> None:
|
||||
@@ -780,11 +826,13 @@ class AquariaRegions:
|
||||
Connect entrances of the different regions around The Body
|
||||
"""
|
||||
self.__connect_regions("Body center area", "Body left area",
|
||||
self.body_c, self.body_l)
|
||||
self.body_c, self.body_l,
|
||||
lambda state: _has_energy_form(state, self.player))
|
||||
self.__connect_regions("Body center area", "Body right area top path",
|
||||
self.body_c, self.body_rt)
|
||||
self.__connect_regions("Body center area", "Body right area bottom path",
|
||||
self.body_c, self.body_rb)
|
||||
self.body_c, self.body_rb,
|
||||
lambda state: _has_energy_form(state, self.player))
|
||||
self.__connect_regions("Body center area", "Body bottom area",
|
||||
self.body_c, self.body_b,
|
||||
lambda state: _has_dual_form(state, self.player))
|
||||
@@ -803,22 +851,12 @@ class AquariaRegions:
|
||||
self.__connect_one_way_regions("final boss third form area", "final boss end",
|
||||
self.final_boss, self.final_boss_end)
|
||||
|
||||
def __connect_transturtle(self, item_source: str, item_target: str, region_source: Region, region_target: Region,
|
||||
rule=None) -> None:
|
||||
def __connect_transturtle(self, item_source: str, item_target: str, region_source: Region,
|
||||
region_target: Region) -> None:
|
||||
"""Connect a single transturtle to another one"""
|
||||
if item_source != item_target:
|
||||
if rule is None:
|
||||
self.__connect_one_way_regions(item_source, item_target, region_source, region_target,
|
||||
lambda state: state.has(item_target, self.player))
|
||||
else:
|
||||
self.__connect_one_way_regions(item_source, item_target, region_source, region_target, rule)
|
||||
|
||||
def __connect_arnassi_path_transturtle(self, item_source: str, item_target: str, region_source: Region,
|
||||
region_target: Region) -> None:
|
||||
"""Connect the Arnassi Ruins transturtle to another one"""
|
||||
self.__connect_one_way_regions(item_source, item_target, region_source, region_target,
|
||||
lambda state: state.has(item_target, self.player) and
|
||||
_has_fish_form(state, self.player))
|
||||
self.__connect_one_way_regions(item_source, item_target, region_source, region_target,
|
||||
lambda state: state.has(item_target, self.player))
|
||||
|
||||
def _connect_transturtle_to_other(self, item: str, region: Region) -> None:
|
||||
"""Connect a single transturtle to all others"""
|
||||
@@ -827,24 +865,10 @@ class AquariaRegions:
|
||||
self.__connect_transturtle(item, "Transturtle Open Water top right", region, self.openwater_tr_turtle)
|
||||
self.__connect_transturtle(item, "Transturtle Forest bottom left", region, self.forest_bl)
|
||||
self.__connect_transturtle(item, "Transturtle Home Water", region, self.home_water_transturtle)
|
||||
self.__connect_transturtle(item, "Transturtle Abyss right", region, self.abyss_r)
|
||||
self.__connect_transturtle(item, "Transturtle Abyss right", region, self.abyss_r_transturtle)
|
||||
self.__connect_transturtle(item, "Transturtle Final Boss", region, self.final_boss_tube)
|
||||
self.__connect_transturtle(item, "Transturtle Simon Says", region, self.simon)
|
||||
self.__connect_transturtle(item, "Transturtle Arnassi Ruins", region, self.arnassi_path,
|
||||
lambda state: state.has("Transturtle Arnassi Ruins", self.player) and
|
||||
_has_fish_form(state, self.player))
|
||||
|
||||
def _connect_arnassi_path_transturtle_to_other(self, item: str, region: Region) -> None:
|
||||
"""Connect the Arnassi Ruins transturtle to all others"""
|
||||
self.__connect_arnassi_path_transturtle(item, "Transturtle Veil top left", region, self.veil_tl)
|
||||
self.__connect_arnassi_path_transturtle(item, "Transturtle Veil top right", region, self.veil_tr_l)
|
||||
self.__connect_arnassi_path_transturtle(item, "Transturtle Open Water top right", region,
|
||||
self.openwater_tr_turtle)
|
||||
self.__connect_arnassi_path_transturtle(item, "Transturtle Forest bottom left", region, self.forest_bl)
|
||||
self.__connect_arnassi_path_transturtle(item, "Transturtle Home Water", region, self.home_water_transturtle)
|
||||
self.__connect_arnassi_path_transturtle(item, "Transturtle Abyss right", region, self.abyss_r)
|
||||
self.__connect_arnassi_path_transturtle(item, "Transturtle Final Boss", region, self.final_boss_tube)
|
||||
self.__connect_arnassi_path_transturtle(item, "Transturtle Simon Says", region, self.simon)
|
||||
self.__connect_transturtle(item, "Transturtle Arnassi Ruins", region, self.arnassi_cave_transturtle)
|
||||
|
||||
def __connect_transturtles(self) -> None:
|
||||
"""Connect every transturtle with others"""
|
||||
@@ -853,10 +877,10 @@ class AquariaRegions:
|
||||
self._connect_transturtle_to_other("Transturtle Open Water top right", self.openwater_tr_turtle)
|
||||
self._connect_transturtle_to_other("Transturtle Forest bottom left", self.forest_bl)
|
||||
self._connect_transturtle_to_other("Transturtle Home Water", self.home_water_transturtle)
|
||||
self._connect_transturtle_to_other("Transturtle Abyss right", self.abyss_r)
|
||||
self._connect_transturtle_to_other("Transturtle Abyss right", self.abyss_r_transturtle)
|
||||
self._connect_transturtle_to_other("Transturtle Final Boss", self.final_boss_tube)
|
||||
self._connect_transturtle_to_other("Transturtle Simon Says", self.simon)
|
||||
self._connect_arnassi_path_transturtle_to_other("Transturtle Arnassi Ruins", self.arnassi_path)
|
||||
self._connect_transturtle_to_other("Transturtle Arnassi Ruins", self.arnassi_cave_transturtle)
|
||||
|
||||
def connect_regions(self) -> None:
|
||||
"""
|
||||
@@ -893,7 +917,7 @@ class AquariaRegions:
|
||||
self.__add_event_location(self.energy_temple_boss,
|
||||
"Beating Fallen God",
|
||||
"Fallen God beated")
|
||||
self.__add_event_location(self.cathedral_boss_r,
|
||||
self.__add_event_location(self.cathedral_boss_l,
|
||||
"Beating Mithalan God",
|
||||
"Mithalan God beated")
|
||||
self.__add_event_location(self.forest_boss,
|
||||
@@ -970,8 +994,9 @@ class AquariaRegions:
|
||||
"""Since Urns need to be broken, add a damaging item to rules"""
|
||||
add_rule(self.multiworld.get_location("Open Water top right area, first urn in the Mithalas exit", self.player),
|
||||
lambda state: _has_damaging_item(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Open Water top right area, second urn in the Mithalas exit", self.player),
|
||||
lambda state: _has_damaging_item(state, self.player))
|
||||
add_rule(
|
||||
self.multiworld.get_location("Open Water top right area, second urn in the Mithalas exit", self.player),
|
||||
lambda state: _has_damaging_item(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Open Water top right area, third urn in the Mithalas exit", self.player),
|
||||
lambda state: _has_damaging_item(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Mithalas City, first urn in one of the homes", self.player),
|
||||
@@ -1019,66 +1044,46 @@ class AquariaRegions:
|
||||
Modify rules for location that need soup
|
||||
"""
|
||||
add_rule(self.multiworld.get_location("Turtle cave, Urchin Costume", self.player),
|
||||
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Sun Worm path, first cliff bulb", self.player),
|
||||
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Sun Worm path, second cliff bulb", self.player),
|
||||
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
|
||||
lambda state: _has_hot_soup(state, self.player))
|
||||
add_rule(self.multiworld.get_location("The Veil top right area, bulb at the top of the waterfall", self.player),
|
||||
lambda state: _has_hot_soup(state, self.player) and _has_beast_form(state, self.player))
|
||||
lambda state: _has_beast_and_soup_form(state, self.player))
|
||||
|
||||
def __adjusting_under_rock_location(self) -> None:
|
||||
"""
|
||||
Modify rules implying bind song needed for bulb under rocks
|
||||
"""
|
||||
add_rule(self.multiworld.get_location("Home Water, bulb under the rock in the left path from the Verse Cave",
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Verse Cave left area, bulb under the rock at the end of the path",
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Naija's Home, bulb under the rock at the right of the main path",
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Song Cave, bulb under the rock in the path to the singing statues",
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Song Cave, bulb under the rock close to the song door",
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Energy Temple second area, bulb under the rock",
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Open Water top left area, bulb under the rock in the right path",
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Open Water top left area, bulb under the rock in the left path",
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Kelp Forest top right area, bulb under the rock in the right path",
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("The Veil top left area, bulb under the rock in the top right path",
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Abyss right area, bulb behind the rock in the whale room",
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Abyss right area, bulb in the middle path",
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("The Veil top left area, bulb under the rock in the top right path",
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
|
||||
def __adjusting_light_in_dark_place_rules(self) -> None:
|
||||
add_rule(self.multiworld.get_location("Kelp Forest top right area, Black Pearl", self.player),
|
||||
lambda state: _has_light(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Kelp Forest bottom right area, Odd Container", self.player),
|
||||
lambda state: _has_light(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Transturtle Veil top left to Transturtle Abyss right", self.player),
|
||||
lambda state: _has_light(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Transturtle Open Water top right to Transturtle Abyss right", self.player),
|
||||
lambda state: _has_light(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Transturtle Veil top right to Transturtle Abyss right", self.player),
|
||||
lambda state: _has_light(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Transturtle Forest bottom left to Transturtle Abyss right", self.player),
|
||||
lambda state: _has_light(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Transturtle Home Water to Transturtle Abyss right", self.player),
|
||||
lambda state: _has_light(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Transturtle Final Boss to Transturtle Abyss right", self.player),
|
||||
lambda state: _has_light(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Transturtle Simon Says to Transturtle Abyss right", self.player),
|
||||
lambda state: _has_light(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Transturtle Arnassi Ruins to Transturtle Abyss right", self.player),
|
||||
lambda state: _has_light(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Body center area to Abyss left bottom area", self.player),
|
||||
lambda state: _has_light(state, self.player))
|
||||
add_rule(self.multiworld.get_entrance("Veil left of sun temple to Octo cave top path", self.player),
|
||||
@@ -1097,12 +1102,14 @@ class AquariaRegions:
|
||||
def __adjusting_manual_rules(self) -> None:
|
||||
add_rule(self.multiworld.get_location("Mithalas Cathedral, Mithalan Dress", self.player),
|
||||
lambda state: _has_beast_form(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Open Water bottom left area, bulb inside the lowest fish pass", self.player),
|
||||
lambda state: _has_fish_form(state, self.player))
|
||||
add_rule(
|
||||
self.multiworld.get_location("Open Water bottom left area, bulb inside the lowest fish pass", self.player),
|
||||
lambda state: _has_fish_form(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Kelp Forest bottom left area, Walker Baby", self.player),
|
||||
lambda state: _has_spirit_form(state, self.player))
|
||||
add_rule(self.multiworld.get_location("The Veil top left area, bulb hidden behind the blocking rock", self.player),
|
||||
lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(
|
||||
self.multiworld.get_location("The Veil top left area, bulb hidden behind the blocking rock", self.player),
|
||||
lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Turtle cave, Turtle Egg", self.player),
|
||||
lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Abyss left area, bulb in the bottom fish pass", self.player),
|
||||
@@ -1114,103 +1121,119 @@ class AquariaRegions:
|
||||
add_rule(self.multiworld.get_location("Verse Cave right area, Big Seed", self.player),
|
||||
lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Arnassi Ruins, Song Plant Spore", self.player),
|
||||
lambda state: _has_beast_form(state, self.player))
|
||||
lambda state: _has_beast_form_or_arnassi_armor(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Energy Temple first area, bulb in the bottom room blocked by a rock",
|
||||
self.player), lambda state: _has_energy_form(state, self.player))
|
||||
self.player), lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Home Water, bulb in the bottom left room", self.player),
|
||||
lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Home Water, bulb in the path below Nautilus Prime", self.player),
|
||||
lambda state: _has_bind_song(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Naija's Home, bulb after the energy door", self.player),
|
||||
lambda state: _has_energy_form(state, self.player))
|
||||
lambda state: _has_energy_attack_item(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Abyss right area, bulb behind the rock in the whale room", self.player),
|
||||
lambda state: _has_spirit_form(state, self.player) and
|
||||
_has_sun_form(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Arnassi Ruins, Arnassi Armor", self.player),
|
||||
lambda state: _has_fish_form(state, self.player) and
|
||||
_has_spirit_form(state, self.player))
|
||||
lambda state: _has_fish_form(state, self.player) or
|
||||
_has_beast_and_soup_form(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Mithalas City, urn inside a home fish pass", self.player),
|
||||
lambda state: _has_damaging_item(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Mithalas City, urn in the Castle flower tube entrance", self.player),
|
||||
lambda state: _has_damaging_item(state, self.player))
|
||||
add_rule(self.multiworld.get_location(
|
||||
"The Veil top right area, bulb in the middle of the wall jump cliff", self.player
|
||||
), lambda state: _has_beast_form_or_arnassi_armor(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Kelp Forest top left area, Jelly Egg", self.player),
|
||||
lambda state: _has_beast_form(state, self.player))
|
||||
add_rule(self.multiworld.get_location("Sun Worm path, first cliff bulb", self.player),
|
||||
lambda state: state.has("Sun God beated", self.player))
|
||||
add_rule(self.multiworld.get_location("Sun Worm path, second cliff bulb", self.player),
|
||||
lambda state: state.has("Sun God beated", self.player))
|
||||
add_rule(self.multiworld.get_location("The Body center area, breaking Li's cage", self.player),
|
||||
lambda state: _has_tongue_cleared(state, self.player))
|
||||
|
||||
def __no_progression_hard_or_hidden_location(self) -> None:
|
||||
self.multiworld.get_location("Energy Temple boss area, Fallen God Tooth",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Mithalas boss area, beating Mithalan God",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Kelp Forest boss area, beating Drunian God",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Sun Temple boss area, beating Sun God",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Sunken City, bulb on top of the boss area",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Home Water, Nautilus Egg",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Energy Temple blaster room, Blaster Egg",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Mithalas City Castle, beating the Priests",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Mermog cave, Piranha Egg",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Octopus Cave, Dumbo Egg",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("King Jellyfish Cave, bulb in the right path from King Jelly",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("King Jellyfish Cave, Jellyfish Costume",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Final Boss area, bulb in the boss third form room",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Sun Worm path, first cliff bulb",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Sun Worm path, second cliff bulb",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("The Veil top right area, bulb at the top of the waterfall",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Bubble Cave, bulb in the left cave wall",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Bubble Cave, Verse Egg",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Kelp Forest bottom left area, bulb close to the spirit crystals",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Kelp Forest bottom left area, Walker Baby",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Sun Temple, Sun Key",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("The Body bottom area, Mutant Costume",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Sun Temple, bulb in the hidden room of the right part",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
self.multiworld.get_location("Arnassi Ruins, Arnassi Armor",
|
||||
self.player).item_rule =\
|
||||
self.player).item_rule = \
|
||||
lambda item: item.classification != ItemClassification.progression
|
||||
|
||||
def adjusting_rules(self, options: AquariaOptions) -> None:
|
||||
"""
|
||||
Modify rules for single location or optional rules
|
||||
"""
|
||||
self.multiworld.get_entrance("Before Final Boss to Final Boss", self.player)
|
||||
self.__adjusting_urns_rules()
|
||||
self.__adjusting_crates_rules()
|
||||
self.__adjusting_soup_rules()
|
||||
@@ -1234,7 +1257,7 @@ class AquariaRegions:
|
||||
lambda state: _has_bind_song(state, self.player))
|
||||
if options.unconfine_home_water.value in [0, 2]:
|
||||
add_rule(self.multiworld.get_entrance("Home Water to Open Water top left area", self.player),
|
||||
lambda state: _has_bind_song(state, self.player) and _has_energy_form(state, self.player))
|
||||
lambda state: _has_bind_song(state, self.player) and _has_energy_attack_item(state, self.player))
|
||||
if options.early_energy_form:
|
||||
self.multiworld.early_items[self.player]["Energy form"] = 1
|
||||
|
||||
@@ -1274,6 +1297,7 @@ class AquariaRegions:
|
||||
self.multiworld.regions.append(self.arnassi)
|
||||
self.multiworld.regions.append(self.arnassi_path)
|
||||
self.multiworld.regions.append(self.arnassi_crab_boss)
|
||||
self.multiworld.regions.append(self.arnassi_cave_transturtle)
|
||||
self.multiworld.regions.append(self.simon)
|
||||
|
||||
def __add_mithalas_regions_to_world(self) -> None:
|
||||
@@ -1300,6 +1324,7 @@ class AquariaRegions:
|
||||
self.multiworld.regions.append(self.forest_tr)
|
||||
self.multiworld.regions.append(self.forest_tr_fp)
|
||||
self.multiworld.regions.append(self.forest_bl)
|
||||
self.multiworld.regions.append(self.forest_bl_sc)
|
||||
self.multiworld.regions.append(self.forest_br)
|
||||
self.multiworld.regions.append(self.forest_boss)
|
||||
self.multiworld.regions.append(self.forest_boss_entrance)
|
||||
@@ -1337,6 +1362,7 @@ class AquariaRegions:
|
||||
self.multiworld.regions.append(self.abyss_l)
|
||||
self.multiworld.regions.append(self.abyss_lb)
|
||||
self.multiworld.regions.append(self.abyss_r)
|
||||
self.multiworld.regions.append(self.abyss_r_transturtle)
|
||||
self.multiworld.regions.append(self.ice_cave)
|
||||
self.multiworld.regions.append(self.bubble_cave)
|
||||
self.multiworld.regions.append(self.bubble_cave_boss)
|
||||
|
||||
@@ -141,7 +141,7 @@ after_home_water_locations = [
|
||||
"Sun Temple, bulb at the top of the high dark room",
|
||||
"Sun Temple, Golden Gear",
|
||||
"Sun Temple, first bulb of the temple",
|
||||
"Sun Temple, bulb on the left part",
|
||||
"Sun Temple, bulb on the right part",
|
||||
"Sun Temple, bulb in the hidden room of the right part",
|
||||
"Sun Temple, Sun Key",
|
||||
"Sun Worm path, first path bulb",
|
||||
|
||||
@@ -13,36 +13,16 @@ class BeastFormAccessTest(AquariaTestBase):
|
||||
def test_beast_form_location(self) -> None:
|
||||
"""Test locations that require beast form"""
|
||||
locations = [
|
||||
"Mithalas City Castle, beating the Priests",
|
||||
"Arnassi Ruins, Crab Armor",
|
||||
"Arnassi Ruins, Song Plant Spore",
|
||||
"Mithalas City, first bulb at the end of the top path",
|
||||
"Mithalas City, second bulb at the end of the top path",
|
||||
"Mithalas City, bulb in the top path",
|
||||
"Mithalas City, Mithalas Pot",
|
||||
"Mithalas City, urn in the Castle flower tube entrance",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Kelp Forest top left area, Jelly Egg",
|
||||
"Mithalas Cathedral, Mithalan Dress",
|
||||
"Turtle cave, bulb in Bubble Cliff",
|
||||
"Turtle cave, Urchin Costume",
|
||||
"Sun Worm path, first cliff bulb",
|
||||
"Sun Worm path, second cliff bulb",
|
||||
"The Veil top right area, bulb at the top of the waterfall",
|
||||
"Bubble Cave, bulb in the left cave wall",
|
||||
"Bubble Cave, bulb in the right cave wall (behind the ice crystal)",
|
||||
"Bubble Cave, Verse Egg",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"Beating the Golem",
|
||||
"Beating Mergog",
|
||||
"Beating Crabbius Maximus",
|
||||
"Beating Octopus Prime",
|
||||
"Beating Mantis Shrimp Prime",
|
||||
"King Jellyfish Cave, Jellyfish Costume",
|
||||
"King Jellyfish Cave, bulb in the right path from King Jelly",
|
||||
"Beating King Jellyfish God Prime",
|
||||
"Beating Mithalan priests",
|
||||
"Sunken City cleared"
|
||||
"Sunken City cleared",
|
||||
]
|
||||
items = [["Beast form"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the beast form or arnassi armor
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class BeastForArnassiArmormAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the beast form or arnassi armor"""
|
||||
|
||||
def test_beast_form_arnassi_armor_location(self) -> None:
|
||||
"""Test locations that require beast form or arnassi armor"""
|
||||
locations = [
|
||||
"Mithalas City Castle, beating the Priests",
|
||||
"Arnassi Ruins, Crab Armor",
|
||||
"Arnassi Ruins, Song Plant Spore",
|
||||
"Mithalas City, first bulb at the end of the top path",
|
||||
"Mithalas City, second bulb at the end of the top path",
|
||||
"Mithalas City, bulb in the top path",
|
||||
"Mithalas City, Mithalas Pot",
|
||||
"Mithalas City, urn in the Castle flower tube entrance",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Mithalas Cathedral, Mithalan Dress",
|
||||
"Kelp Forest top left area, Jelly Egg",
|
||||
"The Veil top right area, bulb in the middle of the wall jump cliff",
|
||||
"The Veil top right area, bulb at the top of the waterfall",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"Beating the Golem",
|
||||
"Beating Mergog",
|
||||
"Beating Crabbius Maximus",
|
||||
"Beating Octopus Prime",
|
||||
"Beating Mithalan priests",
|
||||
"Sunken City cleared"
|
||||
]
|
||||
items = [["Beast form", "Arnassi Armor"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
@@ -17,55 +17,16 @@ class EnergyFormAccessTest(AquariaTestBase):
|
||||
def test_energy_form_location(self) -> None:
|
||||
"""Test locations that require Energy form"""
|
||||
locations = [
|
||||
"Home Water, Nautilus Egg",
|
||||
"Naija's Home, bulb after the energy door",
|
||||
"Energy Temple first area, bulb in the bottom room blocked by a rock",
|
||||
"Energy Temple second area, bulb under the rock",
|
||||
"Energy Temple bottom entrance, Krotite Armor",
|
||||
"Energy Temple third area, bulb in the bottom path",
|
||||
"Energy Temple boss area, Fallen God Tooth",
|
||||
"Energy Temple blaster room, Blaster Egg",
|
||||
"Mithalas City Castle, beating the Priests",
|
||||
"Mithalas Cathedral, first urn in the top right room",
|
||||
"Mithalas Cathedral, second urn in the top right room",
|
||||
"Mithalas Cathedral, third urn in the top right room",
|
||||
"Mithalas Cathedral, urn in the flesh room with fleas",
|
||||
"Mithalas Cathedral, first urn in the bottom right path",
|
||||
"Mithalas Cathedral, second urn in the bottom right path",
|
||||
"Mithalas Cathedral, urn behind the flesh vein",
|
||||
"Mithalas Cathedral, urn in the top left eyes boss room",
|
||||
"Mithalas Cathedral, first urn in the path behind the flesh vein",
|
||||
"Mithalas Cathedral, second urn in the path behind the flesh vein",
|
||||
"Mithalas Cathedral, third urn in the path behind the flesh vein",
|
||||
"Mithalas Cathedral, fourth urn in the top right room",
|
||||
"Mithalas Cathedral, Mithalan Dress",
|
||||
"Mithalas Cathedral, urn below the left entrance",
|
||||
"Mithalas boss area, beating Mithalan God",
|
||||
"Kelp Forest top left area, bulb close to the Verse Egg",
|
||||
"Kelp Forest top left area, Verse Egg",
|
||||
"Kelp Forest boss area, beating Drunian God",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"Sun Temple boss area, beating Sun God",
|
||||
"Arnassi Ruins, Crab Armor",
|
||||
"King Jellyfish Cave, bulb in the right path from King Jelly",
|
||||
"King Jellyfish Cave, Jellyfish Costume",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"The Body left area, first bulb in the top face room",
|
||||
"The Body left area, second bulb in the top face room",
|
||||
"The Body left area, bulb below the water stream",
|
||||
"The Body left area, bulb in the top path to the top face room",
|
||||
"The Body left area, bulb in the bottom face room",
|
||||
"The Body right area, bulb in the top path to the bottom face room",
|
||||
"The Body right area, bulb in the bottom face room",
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Beating Fallen God",
|
||||
"Beating Mithalan God",
|
||||
"Beating Drunian God",
|
||||
"Beating Sun God",
|
||||
"Beating the Golem",
|
||||
"Beating Nautilus Prime",
|
||||
"Beating Blaster Peg Prime",
|
||||
"Beating Mergog",
|
||||
"Beating Mithalan priests",
|
||||
"Beating Octopus Prime",
|
||||
"Beating Crabbius Maximus",
|
||||
"Beating King Jellyfish God Prime",
|
||||
"First secret",
|
||||
"Sunken City cleared",
|
||||
"Objective complete",
|
||||
]
|
||||
items = [["Energy form"]]
|
||||
|
||||
92
worlds/aquaria/test/test_energy_form_or_dual_form_access.py
Normal file
92
worlds/aquaria/test/test_energy_form_or_dual_form_access.py
Normal file
@@ -0,0 +1,92 @@
|
||||
"""
|
||||
Author: Louis M
|
||||
Date: Thu, 18 Apr 2024 18:45:56 +0000
|
||||
Description: Unit test used to test accessibility of locations with and without the energy form and dual form (and Li)
|
||||
"""
|
||||
|
||||
from . import AquariaTestBase
|
||||
|
||||
|
||||
class EnergyFormDualFormAccessTest(AquariaTestBase):
|
||||
"""Unit test used to test accessibility of locations with and without the energy form and dual form (and Li)"""
|
||||
options = {
|
||||
"early_energy_form": False,
|
||||
}
|
||||
|
||||
def test_energy_form_or_dual_form_location(self) -> None:
|
||||
"""Test locations that require Energy form or dual form"""
|
||||
locations = [
|
||||
"Naija's Home, bulb after the energy door",
|
||||
"Home Water, Nautilus Egg",
|
||||
"Energy Temple second area, bulb under the rock",
|
||||
"Energy Temple bottom entrance, Krotite Armor",
|
||||
"Energy Temple third area, bulb in the bottom path",
|
||||
"Energy Temple blaster room, Blaster Egg",
|
||||
"Energy Temple boss area, Fallen God Tooth",
|
||||
"Mithalas City Castle, beating the Priests",
|
||||
"Mithalas boss area, beating Mithalan God",
|
||||
"Mithalas Cathedral, first urn in the top right room",
|
||||
"Mithalas Cathedral, second urn in the top right room",
|
||||
"Mithalas Cathedral, third urn in the top right room",
|
||||
"Mithalas Cathedral, urn in the flesh room with fleas",
|
||||
"Mithalas Cathedral, first urn in the bottom right path",
|
||||
"Mithalas Cathedral, second urn in the bottom right path",
|
||||
"Mithalas Cathedral, urn behind the flesh vein",
|
||||
"Mithalas Cathedral, urn in the top left eyes boss room",
|
||||
"Mithalas Cathedral, first urn in the path behind the flesh vein",
|
||||
"Mithalas Cathedral, second urn in the path behind the flesh vein",
|
||||
"Mithalas Cathedral, third urn in the path behind the flesh vein",
|
||||
"Mithalas Cathedral, fourth urn in the top right room",
|
||||
"Mithalas Cathedral, Mithalan Dress",
|
||||
"Mithalas Cathedral, urn below the left entrance",
|
||||
"Kelp Forest top left area, bulb close to the Verse Egg",
|
||||
"Kelp Forest top left area, Verse Egg",
|
||||
"Kelp Forest boss area, beating Drunian God",
|
||||
"Mermog cave, Piranha Egg",
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"Sun Temple boss area, beating Sun God",
|
||||
"King Jellyfish Cave, bulb in the right path from King Jelly",
|
||||
"King Jellyfish Cave, Jellyfish Costume",
|
||||
"Sunken City right area, crate close to the save crystal",
|
||||
"Sunken City right area, crate in the left bottom room",
|
||||
"Sunken City left area, crate in the little pipe room",
|
||||
"Sunken City left area, crate close to the save crystal",
|
||||
"Sunken City left area, crate before the bedroom",
|
||||
"Sunken City left area, Girl Costume",
|
||||
"Sunken City, bulb on top of the boss area",
|
||||
"The Body center area, breaking Li's cage",
|
||||
"The Body center area, bulb on the main path blocking tube",
|
||||
"The Body left area, first bulb in the top face room",
|
||||
"The Body left area, second bulb in the top face room",
|
||||
"The Body left area, bulb below the water stream",
|
||||
"The Body left area, bulb in the top path to the top face room",
|
||||
"The Body left area, bulb in the bottom face room",
|
||||
"The Body right area, bulb in the top face room",
|
||||
"The Body right area, bulb in the top path to the bottom face room",
|
||||
"The Body right area, bulb in the bottom face room",
|
||||
"The Body bottom area, bulb in the Jelly Zap room",
|
||||
"The Body bottom area, bulb in the nautilus room",
|
||||
"The Body bottom area, Mutant Costume",
|
||||
"Final Boss area, bulb in the boss third form room",
|
||||
"Final Boss area, first bulb in the turtle room",
|
||||
"Final Boss area, second bulb in the turtle room",
|
||||
"Final Boss area, third bulb in the turtle room",
|
||||
"Final Boss area, Transturtle",
|
||||
"Beating Fallen God",
|
||||
"Beating Blaster Peg Prime",
|
||||
"Beating Mithalan God",
|
||||
"Beating Drunian God",
|
||||
"Beating Sun God",
|
||||
"Beating the Golem",
|
||||
"Beating Nautilus Prime",
|
||||
"Beating Mergog",
|
||||
"Beating Mithalan priests",
|
||||
"Beating Octopus Prime",
|
||||
"Beating King Jellyfish God Prime",
|
||||
"Beating the Golem",
|
||||
"Sunken City cleared",
|
||||
"First secret",
|
||||
"Objective complete"
|
||||
]
|
||||
items = [["Energy form", "Dual form", "Li and Li song", "Body tongue cleared"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
@@ -17,6 +17,7 @@ class FishFormAccessTest(AquariaTestBase):
|
||||
"""Test locations that require fish form"""
|
||||
locations = [
|
||||
"The Veil top left area, bulb inside the fish pass",
|
||||
"Energy Temple first area, Energy Idol",
|
||||
"Mithalas City, Doll",
|
||||
"Mithalas City, urn inside a home fish pass",
|
||||
"Kelp Forest top right area, bulb in the top fish pass",
|
||||
@@ -30,8 +31,7 @@ class FishFormAccessTest(AquariaTestBase):
|
||||
"Octopus Cave, Dumbo Egg",
|
||||
"Octopus Cave, bulb in the path below the Octopus Cave path",
|
||||
"Beating Octopus Prime",
|
||||
"Abyss left area, bulb in the bottom fish pass",
|
||||
"Arnassi Ruins, Arnassi Armor"
|
||||
"Abyss left area, bulb in the bottom fish pass"
|
||||
]
|
||||
items = [["Fish form"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -39,7 +39,6 @@ class LightAccessTest(AquariaTestBase):
|
||||
"Abyss right area, bulb in the middle path",
|
||||
"Abyss right area, bulb behind the rock in the middle path",
|
||||
"Abyss right area, bulb in the left green room",
|
||||
"Abyss right area, Transturtle",
|
||||
"Ice Cave, bulb in the room to the right",
|
||||
"Ice Cave, first bulb in the top exit room",
|
||||
"Ice Cave, second bulb in the top exit room",
|
||||
|
||||
@@ -30,7 +30,6 @@ class SpiritFormAccessTest(AquariaTestBase):
|
||||
"Sunken City left area, Girl Costume",
|
||||
"Beating Mantis Shrimp Prime",
|
||||
"First secret",
|
||||
"Arnassi Ruins, Arnassi Armor",
|
||||
]
|
||||
items = [["Spirit form"]]
|
||||
self.assertAccessDependency(locations, items)
|
||||
|
||||
@@ -405,9 +405,20 @@ class Goal(Choice):
|
||||
option_radiance = 3
|
||||
option_godhome = 4
|
||||
option_godhome_flower = 5
|
||||
option_grub_hunt = 6
|
||||
default = 0
|
||||
|
||||
|
||||
class GrubHuntGoal(NamedRange):
|
||||
"""The amount of grubs required to finish Grub Hunt.
|
||||
On 'All' any grubs from item links replacements etc. will be counted"""
|
||||
display_name = "Grub Hunt Goal"
|
||||
range_start = 1
|
||||
range_end = 46
|
||||
special_range_names = {"all": -1}
|
||||
default = 46
|
||||
|
||||
|
||||
class WhitePalace(Choice):
|
||||
"""
|
||||
Whether or not to include White Palace or not. Note: Even if excluded, the King Fragment check may still be
|
||||
@@ -522,7 +533,7 @@ hollow_knight_options: typing.Dict[str, type(Option)] = {
|
||||
**{
|
||||
option.__name__: option
|
||||
for option in (
|
||||
StartLocation, Goal, WhitePalace, ExtraPlatforms, AddUnshuffledLocations, StartingGeo,
|
||||
StartLocation, Goal, GrubHuntGoal, WhitePalace, ExtraPlatforms, AddUnshuffledLocations, StartingGeo,
|
||||
DeathLink, DeathLinkShade, DeathLinkBreaksFragileCharms,
|
||||
MinimumGeoPrice, MaximumGeoPrice,
|
||||
MinimumGrubPrice, MaximumGrubPrice,
|
||||
|
||||
@@ -5,6 +5,7 @@ import typing
|
||||
from copy import deepcopy
|
||||
import itertools
|
||||
import operator
|
||||
from collections import defaultdict, Counter
|
||||
|
||||
logger = logging.getLogger("Hollow Knight")
|
||||
|
||||
@@ -12,12 +13,12 @@ from .Items import item_table, lookup_type_to_names, item_name_groups
|
||||
from .Regions import create_regions
|
||||
from .Rules import set_rules, cost_terms, _hk_can_beat_thk, _hk_siblings_ending, _hk_can_beat_radiance
|
||||
from .Options import hollow_knight_options, hollow_knight_randomize_options, Goal, WhitePalace, CostSanity, \
|
||||
shop_to_option, HKOptions
|
||||
shop_to_option, HKOptions, GrubHuntGoal
|
||||
from .ExtractedData import locations, starts, multi_locations, location_to_region_lookup, \
|
||||
event_names, item_effects, connectors, one_ways, vanilla_shop_costs, vanilla_location_costs
|
||||
from .Charms import names as charm_names
|
||||
|
||||
from BaseClasses import Region, Location, MultiWorld, Item, LocationProgressType, Tutorial, ItemClassification
|
||||
from BaseClasses import Region, Location, MultiWorld, Item, LocationProgressType, Tutorial, ItemClassification, CollectionState
|
||||
from worlds.AutoWorld import World, LogicMixin, WebWorld
|
||||
|
||||
path_of_pain_locations = {
|
||||
@@ -155,6 +156,7 @@ class HKWorld(World):
|
||||
ranges: typing.Dict[str, typing.Tuple[int, int]]
|
||||
charm_costs: typing.List[int]
|
||||
cached_filler_items = {}
|
||||
grub_count: int
|
||||
|
||||
def __init__(self, multiworld, player):
|
||||
super(HKWorld, self).__init__(multiworld, player)
|
||||
@@ -164,6 +166,7 @@ class HKWorld(World):
|
||||
self.ranges = {}
|
||||
self.created_shop_items = 0
|
||||
self.vanilla_shop_costs = deepcopy(vanilla_shop_costs)
|
||||
self.grub_count = 0
|
||||
|
||||
def generate_early(self):
|
||||
options = self.options
|
||||
@@ -201,7 +204,7 @@ class HKWorld(World):
|
||||
|
||||
# check for any goal that godhome events are relevant to
|
||||
all_event_names = event_names.copy()
|
||||
if self.options.Goal in [Goal.option_godhome, Goal.option_godhome_flower]:
|
||||
if self.options.Goal in [Goal.option_godhome, Goal.option_godhome_flower, Goal.option_any]:
|
||||
from .GodhomeData import godhome_event_names
|
||||
all_event_names.update(set(godhome_event_names))
|
||||
|
||||
@@ -441,12 +444,67 @@ class HKWorld(World):
|
||||
multiworld.completion_condition[player] = lambda state: state.count("Defeated_Pantheon_5", player)
|
||||
elif goal == Goal.option_godhome_flower:
|
||||
multiworld.completion_condition[player] = lambda state: state.count("Godhome_Flower_Quest", player)
|
||||
elif goal == Goal.option_grub_hunt:
|
||||
pass # will set in stage_pre_fill()
|
||||
else:
|
||||
# Any goal
|
||||
multiworld.completion_condition[player] = lambda state: _hk_can_beat_thk(state, player) or _hk_can_beat_radiance(state, player)
|
||||
multiworld.completion_condition[player] = lambda state: _hk_siblings_ending(state, player) and \
|
||||
_hk_can_beat_radiance(state, player) and state.count("Godhome_Flower_Quest", player)
|
||||
|
||||
set_rules(self)
|
||||
|
||||
@classmethod
|
||||
def stage_pre_fill(cls, multiworld: "MultiWorld"):
|
||||
def set_goal(player, grub_rule: typing.Callable[[CollectionState], bool]):
|
||||
world = multiworld.worlds[player]
|
||||
|
||||
if world.options.Goal == "grub_hunt":
|
||||
multiworld.completion_condition[player] = grub_rule
|
||||
else:
|
||||
old_rule = multiworld.completion_condition[player]
|
||||
multiworld.completion_condition[player] = lambda state: old_rule(state) and grub_rule(state)
|
||||
|
||||
worlds = [world for world in multiworld.get_game_worlds(cls.game) if world.options.Goal in ["any", "grub_hunt"]]
|
||||
if worlds:
|
||||
grubs = [item for item in multiworld.get_items() if item.name == "Grub"]
|
||||
all_grub_players = [world.player for world in multiworld.worlds.values() if world.options.GrubHuntGoal == GrubHuntGoal.special_range_names["all"]]
|
||||
|
||||
if all_grub_players:
|
||||
group_lookup = defaultdict(set)
|
||||
for group_id, group in multiworld.groups.items():
|
||||
for player in group["players"]:
|
||||
group_lookup[group_id].add(player)
|
||||
|
||||
grub_count_per_player = Counter()
|
||||
per_player_grubs_per_player = defaultdict(Counter)
|
||||
|
||||
for grub in grubs:
|
||||
player = grub.player
|
||||
if player in group_lookup:
|
||||
for real_player in group_lookup[player]:
|
||||
per_player_grubs_per_player[real_player][player] += 1
|
||||
else:
|
||||
per_player_grubs_per_player[player][player] += 1
|
||||
|
||||
if grub.location and grub.location.player in group_lookup.keys():
|
||||
for real_player in group_lookup[grub.location.player]:
|
||||
grub_count_per_player[real_player] += 1
|
||||
else:
|
||||
grub_count_per_player[player] += 1
|
||||
|
||||
for player, count in grub_count_per_player.items():
|
||||
multiworld.worlds[player].grub_count = count
|
||||
|
||||
for player, grub_player_count in per_player_grubs_per_player.items():
|
||||
if player in all_grub_players:
|
||||
set_goal(player, lambda state, g=grub_player_count: all(state.has("Grub", owner, count) for owner, count in g.items()))
|
||||
|
||||
for world in worlds:
|
||||
if world.player not in all_grub_players:
|
||||
world.grub_count = world.options.GrubHuntGoal.value
|
||||
player = world.player
|
||||
set_goal(player, lambda state, p=player, c=world.grub_count: state.has("Grub", p, c))
|
||||
|
||||
def fill_slot_data(self):
|
||||
slot_data = {}
|
||||
|
||||
@@ -484,6 +542,8 @@ class HKWorld(World):
|
||||
|
||||
slot_data["notch_costs"] = self.charm_costs
|
||||
|
||||
slot_data["grub_count"] = self.grub_count
|
||||
|
||||
return slot_data
|
||||
|
||||
def create_item(self, name: str) -> HKItem:
|
||||
|
||||
@@ -1,22 +1,25 @@
|
||||
# Kingdom Hearts 2 Archipelago Setup Guide
|
||||
|
||||
<h2 style="text-transform:none";>Quick Links</h2>
|
||||
|
||||
- [Game Info Page](../../../../games/Kingdom%20Hearts%202/info/en)
|
||||
- [Player Options Page](../../../../games/Kingdom%20Hearts%202/player-options)
|
||||
|
||||
<h2 style="text-transform:none";>Required Software:</h2>
|
||||
`Kingdom Hearts II Final Mix` from the [Epic Games Store](https://store.epicgames.com/en-US/discover/kingdom-hearts)
|
||||
- Follow this Guide to set up these requirements [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/)<br>
|
||||
1. `3.2.0 OpenKH Mod Manager with Panacea`<br>
|
||||
2. `Lua Backend from the OpenKH Mod Manager`
|
||||
3. `Install the mod KH2FM-Mods-Num/GoA-ROM-Edition using OpenKH Mod Manager`<br>
|
||||
|
||||
`Kingdom Hearts II Final Mix` from the [Epic Games Store](https://store.epicgames.com/en-US/discover/kingdom-hearts) or [Steam](https://store.steampowered.com/app/2552430/KINGDOM_HEARTS_HD_1525_ReMIX/)
|
||||
|
||||
- Follow this Guide to set up these requirements [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/)
|
||||
1. `Version 3.3.0 or greater OpenKH Mod Manager with Panacea`
|
||||
2. `Lua Backend from the OpenKH Mod Manager`
|
||||
3. `Install the mod KH2FM-Mods-Num/GoA-ROM-Edition using OpenKH Mod Manager`
|
||||
- Needed for Archipelago
|
||||
1. [`ArchipelagoKH2Client.exe`](https://github.com/ArchipelagoMW/Archipelago/releases)<br>
|
||||
2. `Install the Archipelago Companion mod from JaredWeakStrike/APCompanion using OpenKH Mod Manager`<br>
|
||||
3. `Install the Archipelago Quality Of Life mod from JaredWeakStrike/AP_QOL using OpenKH Mod Manager` <br>
|
||||
4. `Install the mod from KH2FM-Mods-equations19/auto-save using OpenKH Mod Manager`<br>
|
||||
1. [`ArchipelagoKH2Client.exe`](https://github.com/ArchipelagoMW/Archipelago/releases)
|
||||
2. `Install the Archipelago Companion mod from JaredWeakStrike/APCompanion using OpenKH Mod Manager`
|
||||
3. `Install the Archipelago Quality Of Life mod from JaredWeakStrike/AP_QOL using OpenKH Mod Manager`
|
||||
4. `Install the mod from KH2FM-Mods-equations19/auto-save using OpenKH Mod Manager`
|
||||
5. `AP Randomizer Seed`
|
||||
|
||||
<h3 style="text-transform:none";>Required: Archipelago Companion Mod</h3>
|
||||
|
||||
Load this mod just like the <b>GoA ROM</b> you did during the KH2 Rando setup. `JaredWeakStrike/APCompanion`<br>
|
||||
@@ -24,6 +27,7 @@ Have this mod second-highest priority below the .zip seed.<br>
|
||||
This mod is based upon Num's Garden of Assemblege Mod and requires it to work. Without Num this could not be possible.
|
||||
|
||||
<h3 style="text-transform:none";>Required: Auto Save Mod</h3>
|
||||
|
||||
Load this mod just like the GoA ROM you did during the KH2 Rando setup. `KH2FM-Mods-equations19/auto-save` Location doesn't matter, required in case of crashes. See [Best Practices](en#best-practices) on how to load the auto save
|
||||
|
||||
<h3 style="text-transform:none";>Installing A Seed</h3>
|
||||
@@ -33,33 +37,33 @@ Make sure the seed is on the top of the list (Highest Priority)<br>
|
||||
After Installing the seed click `Mod Loader -> Build/Build and Run`. Every slot is a unique mod to install and will be needed be repatched for different slots/rooms.
|
||||
|
||||
<h2 style="text-transform:none";>What the Mod Manager Should Look Like.</h2>
|
||||
|
||||

|
||||
|
||||
<h2 style="text-transform:none";>Using the KH2 Client</h2>
|
||||
|
||||
Once you have started the game through OpenKH Mod Manager and are on the title screen run the [ArchipelagoKH2Client.exe](https://github.com/ArchipelagoMW/Archipelago/releases). <br>
|
||||
Once you have started the game through OpenKH Mod Manager and are on the title screen run the [ArchipelagoKH2Client.exe](https://github.com/ArchipelagoMW/Archipelago/releases).<br>
|
||||
When you successfully connect to the server the client will automatically hook into the game to send/receive checks. <br>
|
||||
If the client ever loses connection to the game, it will also disconnect from the server and you will need to reconnect.<br>
|
||||
`Make sure the game is open whenever you try to connect the client to the server otherwise it will immediately disconnect you.`<br>
|
||||
Most checks will be sent to you anywhere outside a load or cutscene.<br>
|
||||
`If you obtain magic, you will need to pause your game to have it show up in your inventory, then enter a new room for it to become properly usable.`
|
||||
<br>
|
||||
|
||||
<h2 style="text-transform:none";>KH2 Client should look like this: </h2>
|
||||
|
||||

|
||||
<br>
|
||||
|
||||
Enter `The room's port number` into the top box <b> where the x's are</b> and press "Connect". Follow the prompts there and you should be connected
|
||||
|
||||
|
||||
<h2 style="text-transform:none";>Common Pitfalls</h2>
|
||||
- Having an old GOA Lua Script in your `C:\Users\*YourName*\Documents\KINGDOM HEARTS HD 1.5+2.5 ReMIX\scripts\kh2` folder.
|
||||
- Pressing F2 while in game should look like this. 
|
||||
<br>
|
||||
- Not having Lua Backend Configured Correctly.
|
||||
- To fix this look over the guide at [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/). Specifically the Lua Backend Configuration Step.
|
||||
<br>
|
||||
- Loading into Simulated Twilight Town Instead of the GOA.
|
||||
- To fix this look over the guide at [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/). Specifically the Panacea and Lua Backend Steps.
|
||||
|
||||
- Having an old GOA Lua Script in your `C:\Users\*YourName*\Documents\KINGDOM HEARTS HD 1.5+2.5 ReMIX\scripts\kh2` folder.
|
||||
- Pressing F2 while in game should look like this. 
|
||||
- Not having Lua Backend Configured Correctly.
|
||||
- To fix this look over the guide at [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/). Specifically the Lua Backend Configuration Step.
|
||||
|
||||
- Loading into Simulated Twilight Town Instead of the GOA.
|
||||
- To fix this look over the guide at [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/). Specifically the Panacea and Lua Backend Steps.
|
||||
|
||||
<h2 style="text-transform:none"; >Best Practices</h2>
|
||||
|
||||
@@ -70,8 +74,11 @@ Enter `The room's port number` into the top box <b> where the x's are</b> and pr
|
||||
- Make sure to save in a different save slot when playing in an async or disconnecting from the server to play a different seed
|
||||
|
||||
<h2 style="text-transform:none";>Logic Sheet</h2>
|
||||
|
||||
Have any questions on what's in logic? This spreadsheet made by Bulcon has the answer [Requirements/logic sheet](https://docs.google.com/spreadsheets/d/1nNi8ohEs1fv-sDQQRaP45o6NoRcMlLJsGckBonweDMY/edit?usp=sharing)
|
||||
|
||||
<h2 style="text-transform:none";>F.A.Q.</h2>
|
||||
|
||||
- Why is my Client giving me a "Cannot Open Process: " error?
|
||||
- Due to how the client reads kingdom hearts 2 memory some people's computer flags it as a virus. Run the client as admin.
|
||||
- Why is my HP/MP continuously increasing without stopping?
|
||||
@@ -83,11 +90,13 @@ Have any questions on what's in logic? This spreadsheet made by Bulcon has the a
|
||||
- Why did I not load into the correct visit?
|
||||
- You need to trigger a cutscene or visit The World That Never Was for it to register that you have received the item.
|
||||
- What versions of Kingdom Hearts 2 are supported?
|
||||
- Currently `only` the most up to date version on the Epic Game Store is supported: version `1.0.0.8_WW`.
|
||||
- Currently the `only` supported versions are `Epic Games Version 1.0.0.9_WW` and `Steam Build Version 14716933`.
|
||||
- Why am I getting wallpapered while going into a world for the first time?
|
||||
- Your `Lua Backend` was not configured correctly. Look over the step in the [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/) guide.
|
||||
- Your `Lua Backend` was not configured correctly. Look over the step in the [KH2Rando.com](https://tommadness.github.io/KH2Randomizer/setup/Panacea-ModLoader/) guide.
|
||||
- Why am I not getting magic?
|
||||
- If you obtain magic, you will need to pause your game to have it show up in your inventory, then enter a new room for it to become properly usable.
|
||||
- Why did I crash after picking my dream weapon?
|
||||
- This is normally caused by having an outdated GOA mod or having an outdated panacea and/or luabackend. To fix this rerun the setup wizard and reinstall luabackend and panacea. Also make sure all your mods are up-to-date.
|
||||
- Why did I crash?
|
||||
- The port of Kingdom Hearts 2 can and will randomly crash, this is the fault of the game not the randomizer or the archipelago client.
|
||||
- If you have a continuous/constant crash (in the same area/event every time) you will want to reverify your installed files. This can be done by doing the following: Open Epic Game Store --> Library --> Click Triple Dots --> Manage --> Verify
|
||||
@@ -99,5 +108,3 @@ Have any questions on what's in logic? This spreadsheet made by Bulcon has the a
|
||||
- Because Kingdom Hearts 2 is prone to crashes and will keep you from losing your progress.
|
||||
- How do I load an auto save?
|
||||
- To load an auto-save, hold down the Select or your equivalent on your prefered controller while choosing a file. Make sure to hold the button down the whole time.
|
||||
|
||||
|
||||
|
||||
85
worlds/osrs/Items.py
Normal file
85
worlds/osrs/Items.py
Normal file
@@ -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',
|
||||
}
|
||||
21
worlds/osrs/Locations.py
Normal file
21
worlds/osrs/Locations.py
Normal file
@@ -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"
|
||||
144
worlds/osrs/LogicCSV/LogicCSVToPython.py
Normal file
144
worlds/osrs/LogicCSV/LogicCSVToPython.py
Normal file
@@ -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")
|
||||
43
worlds/osrs/LogicCSV/items_generated.py
Normal file
43
worlds/osrs/LogicCSV/items_generated.py
Normal file
@@ -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),
|
||||
]
|
||||
127
worlds/osrs/LogicCSV/locations_generated.py
Normal file
127
worlds/osrs/LogicCSV/locations_generated.py
Normal file
@@ -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),
|
||||
]
|
||||
47
worlds/osrs/LogicCSV/regions_generated.py
Normal file
47
worlds/osrs/LogicCSV/regions_generated.py
Normal file
@@ -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', ]),
|
||||
]
|
||||
54
worlds/osrs/LogicCSV/resources_generated.py
Normal file
54
worlds/osrs/LogicCSV/resources_generated.py
Normal file
@@ -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'),
|
||||
]
|
||||
212
worlds/osrs/Names.py
Normal file
212
worlds/osrs/Names.py
Normal file
@@ -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__
|
||||
474
worlds/osrs/Options.py
Normal file
474
worlds/osrs/Options.py
Normal file
@@ -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
|
||||
12
worlds/osrs/Regions.py
Normal file
12
worlds/osrs/Regions.py
Normal file
@@ -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
|
||||
657
worlds/osrs/__init__.py
Normal file
657
worlds/osrs/__init__.py
Normal file
@@ -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
|
||||
114
worlds/osrs/docs/en_Old School Runescape.md
Normal file
114
worlds/osrs/docs/en_Old School Runescape.md
Normal file
@@ -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.
|
||||
58
worlds/osrs/docs/setup_en.md
Normal file
58
worlds/osrs/docs/setup_en.md
Normal file
@@ -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.
|
||||
@@ -31,23 +31,17 @@ def has_all_items(multiworld: MultiWorld, items: Set[str], region: str, player:
|
||||
# Checks to see if chest/shrine are accessible
|
||||
def has_location_access_rule(multiworld: MultiWorld, environment: str, player: int, item_number: int, item_type: str)\
|
||||
-> None:
|
||||
if item_number == 1:
|
||||
multiworld.get_location(f"{environment}: {item_type} {item_number}", player).access_rule = \
|
||||
lambda state: state.has(environment, player)
|
||||
location_name = f"{environment}: {item_type} {item_number}"
|
||||
if item_type == "Scavenger":
|
||||
# scavengers need to be locked till after a full loop since that is when they are capable of spawning.
|
||||
# (While technically the requirement is just beating 5 stages, this will ensure that the player will have
|
||||
# a long enough run to have enough director credits for scavengers and
|
||||
# help prevent being stuck in the same stages until that point).
|
||||
if item_type == "Scavenger":
|
||||
multiworld.get_location(f"{environment}: {item_type} {item_number}", player).access_rule = \
|
||||
lambda state: state.has(environment, player) and state.has("Stage 5", player)
|
||||
multiworld.get_location(location_name, player).access_rule = \
|
||||
lambda state: state.has(environment, player) and state.has("Stage 5", player)
|
||||
else:
|
||||
multiworld.get_location(f"{environment}: {item_type} {item_number}", player).access_rule = \
|
||||
lambda state: check_location(state, environment, player, item_number, item_type)
|
||||
|
||||
|
||||
def check_location(state, environment: str, player: int, item_number: int, item_name: str) -> bool:
|
||||
return state.can_reach(f"{environment}: {item_name} {item_number - 1}", "Location", player)
|
||||
multiworld.get_location(location_name, player).access_rule = \
|
||||
lambda state: state.has(environment, player)
|
||||
|
||||
|
||||
def set_rules(ror2_world: "RiskOfRainWorld") -> None:
|
||||
|
||||
@@ -169,7 +169,61 @@ portal_mapping: List[Portal] = [
|
||||
destination="Overworld Redux", tag="_rafters"),
|
||||
Portal(name="Temple Door Exit", region="Sealed Temple",
|
||||
destination="Overworld Redux", tag="_main"),
|
||||
|
||||
|
||||
Portal(name="Forest Belltower to Fortress", region="Forest Belltower Main",
|
||||
destination="Fortress Courtyard", tag="_"),
|
||||
Portal(name="Forest Belltower to Forest", region="Forest Belltower Lower",
|
||||
destination="East Forest Redux", tag="_"),
|
||||
Portal(name="Forest Belltower to Overworld", region="Forest Belltower Main",
|
||||
destination="Overworld Redux", tag="_"),
|
||||
Portal(name="Forest Belltower to Guard Captain Room", region="Forest Belltower Upper",
|
||||
destination="Forest Boss Room", tag="_"),
|
||||
|
||||
Portal(name="Forest to Belltower", region="East Forest",
|
||||
destination="Forest Belltower", tag="_"),
|
||||
Portal(name="Forest Guard House 1 Lower Entrance", region="East Forest",
|
||||
destination="East Forest Redux Laddercave", tag="_lower"),
|
||||
Portal(name="Forest Guard House 1 Gate Entrance", region="East Forest",
|
||||
destination="East Forest Redux Laddercave", tag="_gate"),
|
||||
Portal(name="Forest Dance Fox Outside Doorway", region="East Forest Dance Fox Spot",
|
||||
destination="East Forest Redux Laddercave", tag="_upper"),
|
||||
Portal(name="Forest to Far Shore", region="East Forest Portal",
|
||||
destination="Transit", tag="_teleporter_forest teleporter"),
|
||||
Portal(name="Forest Guard House 2 Lower Entrance", region="Lower Forest",
|
||||
destination="East Forest Redux Interior", tag="_lower"),
|
||||
Portal(name="Forest Guard House 2 Upper Entrance", region="East Forest",
|
||||
destination="East Forest Redux Interior", tag="_upper"),
|
||||
Portal(name="Forest Grave Path Lower Entrance", region="East Forest",
|
||||
destination="Sword Access", tag="_lower"),
|
||||
Portal(name="Forest Grave Path Upper Entrance", region="East Forest",
|
||||
destination="Sword Access", tag="_upper"),
|
||||
|
||||
Portal(name="Forest Grave Path Upper Exit", region="Forest Grave Path Upper",
|
||||
destination="East Forest Redux", tag="_upper"),
|
||||
Portal(name="Forest Grave Path Lower Exit", region="Forest Grave Path Main",
|
||||
destination="East Forest Redux", tag="_lower"),
|
||||
Portal(name="East Forest Hero's Grave", region="Forest Hero's Grave",
|
||||
destination="RelicVoid", tag="_teleporter_relic plinth"),
|
||||
|
||||
Portal(name="Guard House 1 Dance Fox Exit", region="Guard House 1 West",
|
||||
destination="East Forest Redux", tag="_upper"),
|
||||
Portal(name="Guard House 1 Lower Exit", region="Guard House 1 West",
|
||||
destination="East Forest Redux", tag="_lower"),
|
||||
Portal(name="Guard House 1 Upper Forest Exit", region="Guard House 1 East",
|
||||
destination="East Forest Redux", tag="_gate"),
|
||||
Portal(name="Guard House 1 to Guard Captain Room", region="Guard House 1 East",
|
||||
destination="Forest Boss Room", tag="_"),
|
||||
|
||||
Portal(name="Guard House 2 Lower Exit", region="Guard House 2 Lower",
|
||||
destination="East Forest Redux", tag="_lower"),
|
||||
Portal(name="Guard House 2 Upper Exit", region="Guard House 2 Upper",
|
||||
destination="East Forest Redux", tag="_upper"),
|
||||
|
||||
Portal(name="Guard Captain Room Non-Gate Exit", region="Forest Boss Room",
|
||||
destination="East Forest Redux Laddercave", tag="_"),
|
||||
Portal(name="Guard Captain Room Gate Exit", region="Forest Boss Room",
|
||||
destination="Forest Belltower", tag="_"),
|
||||
|
||||
Portal(name="Well Ladder Exit", region="Beneath the Well Ladder Exit",
|
||||
destination="Overworld Redux", tag="_entrance"),
|
||||
Portal(name="Well to Well Boss", region="Beneath the Well Back",
|
||||
@@ -206,7 +260,66 @@ portal_mapping: List[Portal] = [
|
||||
|
||||
Portal(name="Magic Dagger House Exit", region="Magic Dagger House",
|
||||
destination="Archipelagos Redux", tag="_"),
|
||||
|
||||
|
||||
Portal(name="Fortress Courtyard to Fortress Grave Path Lower", region="Fortress Courtyard",
|
||||
destination="Fortress Reliquary", tag="_Lower"),
|
||||
Portal(name="Fortress Courtyard to Fortress Grave Path Upper", region="Fortress Courtyard Upper",
|
||||
destination="Fortress Reliquary", tag="_Upper"),
|
||||
Portal(name="Fortress Courtyard to Fortress Interior", region="Fortress Courtyard",
|
||||
destination="Fortress Main", tag="_Big Door"),
|
||||
Portal(name="Fortress Courtyard to East Fortress", region="Fortress Courtyard Upper",
|
||||
destination="Fortress East", tag="_"),
|
||||
Portal(name="Fortress Courtyard to Beneath the Vault", region="Beneath the Vault Entry",
|
||||
destination="Fortress Basement", tag="_"),
|
||||
Portal(name="Fortress Courtyard to Forest Belltower", region="Fortress Exterior from East Forest",
|
||||
destination="Forest Belltower", tag="_"),
|
||||
Portal(name="Fortress Courtyard to Overworld", region="Fortress Exterior from Overworld",
|
||||
destination="Overworld Redux", tag="_"),
|
||||
Portal(name="Fortress Courtyard Shop", region="Fortress Exterior near cave",
|
||||
destination="Shop", tag="_"),
|
||||
|
||||
Portal(name="Beneath the Vault to Fortress Interior", region="Beneath the Vault Back",
|
||||
destination="Fortress Main", tag="_"),
|
||||
Portal(name="Beneath the Vault to Fortress Courtyard", region="Beneath the Vault Ladder Exit",
|
||||
destination="Fortress Courtyard", tag="_"),
|
||||
|
||||
Portal(name="Fortress Interior Main Exit", region="Eastern Vault Fortress",
|
||||
destination="Fortress Courtyard", tag="_Big Door"),
|
||||
Portal(name="Fortress Interior to Beneath the Earth", region="Eastern Vault Fortress",
|
||||
destination="Fortress Basement", tag="_"),
|
||||
Portal(name="Fortress Interior to Siege Engine Arena", region="Eastern Vault Fortress Gold Door",
|
||||
destination="Fortress Arena", tag="_"),
|
||||
Portal(name="Fortress Interior Shop", region="Eastern Vault Fortress",
|
||||
destination="Shop", tag="_"),
|
||||
Portal(name="Fortress Interior to East Fortress Upper", region="Eastern Vault Fortress",
|
||||
destination="Fortress East", tag="_upper"),
|
||||
Portal(name="Fortress Interior to East Fortress Lower", region="Eastern Vault Fortress",
|
||||
destination="Fortress East", tag="_lower"),
|
||||
|
||||
Portal(name="East Fortress to Interior Lower", region="Fortress East Shortcut Lower",
|
||||
destination="Fortress Main", tag="_lower"),
|
||||
Portal(name="East Fortress to Courtyard", region="Fortress East Shortcut Upper",
|
||||
destination="Fortress Courtyard", tag="_"),
|
||||
Portal(name="East Fortress to Interior Upper", region="Fortress East Shortcut Upper",
|
||||
destination="Fortress Main", tag="_upper"),
|
||||
|
||||
Portal(name="Fortress Grave Path Lower Exit", region="Fortress Grave Path",
|
||||
destination="Fortress Courtyard", tag="_Lower"),
|
||||
Portal(name="Fortress Hero's Grave", region="Fortress Hero's Grave Region",
|
||||
destination="RelicVoid", tag="_teleporter_relic plinth"),
|
||||
Portal(name="Fortress Grave Path Upper Exit", region="Fortress Grave Path Upper",
|
||||
destination="Fortress Courtyard", tag="_Upper"),
|
||||
Portal(name="Fortress Grave Path Dusty Entrance", region="Fortress Grave Path Dusty Entrance Region",
|
||||
destination="Dusty", tag="_"),
|
||||
|
||||
Portal(name="Dusty Exit", region="Fortress Leaf Piles",
|
||||
destination="Fortress Reliquary", tag="_"),
|
||||
|
||||
Portal(name="Siege Engine Arena to Fortress", region="Fortress Arena",
|
||||
destination="Fortress Main", tag="_"),
|
||||
Portal(name="Fortress to Far Shore", region="Fortress Arena Portal",
|
||||
destination="Transit", tag="_teleporter_spidertank"),
|
||||
|
||||
Portal(name="Atoll Upper Exit", region="Ruined Atoll",
|
||||
destination="Overworld Redux", tag="_upper"),
|
||||
Portal(name="Atoll Lower Exit", region="Ruined Atoll Lower Entry Area",
|
||||
@@ -263,119 +376,6 @@ portal_mapping: List[Portal] = [
|
||||
Portal(name="Librarian Arena Exit", region="Library Arena",
|
||||
destination="Library Lab", tag="_"),
|
||||
|
||||
Portal(name="Forest to Belltower", region="East Forest",
|
||||
destination="Forest Belltower", tag="_"),
|
||||
Portal(name="Forest Guard House 1 Lower Entrance", region="East Forest",
|
||||
destination="East Forest Redux Laddercave", tag="_lower"),
|
||||
Portal(name="Forest Guard House 1 Gate Entrance", region="East Forest",
|
||||
destination="East Forest Redux Laddercave", tag="_gate"),
|
||||
Portal(name="Forest Dance Fox Outside Doorway", region="East Forest Dance Fox Spot",
|
||||
destination="East Forest Redux Laddercave", tag="_upper"),
|
||||
Portal(name="Forest to Far Shore", region="East Forest Portal",
|
||||
destination="Transit", tag="_teleporter_forest teleporter"),
|
||||
Portal(name="Forest Guard House 2 Lower Entrance", region="Lower Forest",
|
||||
destination="East Forest Redux Interior", tag="_lower"),
|
||||
Portal(name="Forest Guard House 2 Upper Entrance", region="East Forest",
|
||||
destination="East Forest Redux Interior", tag="_upper"),
|
||||
Portal(name="Forest Grave Path Lower Entrance", region="East Forest",
|
||||
destination="Sword Access", tag="_lower"),
|
||||
Portal(name="Forest Grave Path Upper Entrance", region="East Forest",
|
||||
destination="Sword Access", tag="_upper"),
|
||||
|
||||
Portal(name="Guard House 1 Dance Fox Exit", region="Guard House 1 West",
|
||||
destination="East Forest Redux", tag="_upper"),
|
||||
Portal(name="Guard House 1 Lower Exit", region="Guard House 1 West",
|
||||
destination="East Forest Redux", tag="_lower"),
|
||||
Portal(name="Guard House 1 Upper Forest Exit", region="Guard House 1 East",
|
||||
destination="East Forest Redux", tag="_gate"),
|
||||
Portal(name="Guard House 1 to Guard Captain Room", region="Guard House 1 East",
|
||||
destination="Forest Boss Room", tag="_"),
|
||||
|
||||
Portal(name="Forest Grave Path Upper Exit", region="Forest Grave Path Upper",
|
||||
destination="East Forest Redux", tag="_upper"),
|
||||
Portal(name="Forest Grave Path Lower Exit", region="Forest Grave Path Main",
|
||||
destination="East Forest Redux", tag="_lower"),
|
||||
Portal(name="East Forest Hero's Grave", region="Forest Hero's Grave",
|
||||
destination="RelicVoid", tag="_teleporter_relic plinth"),
|
||||
|
||||
Portal(name="Guard House 2 Lower Exit", region="Guard House 2 Lower",
|
||||
destination="East Forest Redux", tag="_lower"),
|
||||
Portal(name="Guard House 2 Upper Exit", region="Guard House 2 Upper",
|
||||
destination="East Forest Redux", tag="_upper"),
|
||||
|
||||
Portal(name="Guard Captain Room Non-Gate Exit", region="Forest Boss Room",
|
||||
destination="East Forest Redux Laddercave", tag="_"),
|
||||
Portal(name="Guard Captain Room Gate Exit", region="Forest Boss Room",
|
||||
destination="Forest Belltower", tag="_"),
|
||||
|
||||
Portal(name="Forest Belltower to Fortress", region="Forest Belltower Main",
|
||||
destination="Fortress Courtyard", tag="_"),
|
||||
Portal(name="Forest Belltower to Forest", region="Forest Belltower Lower",
|
||||
destination="East Forest Redux", tag="_"),
|
||||
Portal(name="Forest Belltower to Overworld", region="Forest Belltower Main",
|
||||
destination="Overworld Redux", tag="_"),
|
||||
Portal(name="Forest Belltower to Guard Captain Room", region="Forest Belltower Upper",
|
||||
destination="Forest Boss Room", tag="_"),
|
||||
|
||||
Portal(name="Fortress Courtyard to Fortress Grave Path Lower", region="Fortress Courtyard",
|
||||
destination="Fortress Reliquary", tag="_Lower"),
|
||||
Portal(name="Fortress Courtyard to Fortress Grave Path Upper", region="Fortress Courtyard Upper",
|
||||
destination="Fortress Reliquary", tag="_Upper"),
|
||||
Portal(name="Fortress Courtyard to Fortress Interior", region="Fortress Courtyard",
|
||||
destination="Fortress Main", tag="_Big Door"),
|
||||
Portal(name="Fortress Courtyard to East Fortress", region="Fortress Courtyard Upper",
|
||||
destination="Fortress East", tag="_"),
|
||||
Portal(name="Fortress Courtyard to Beneath the Vault", region="Beneath the Vault Entry",
|
||||
destination="Fortress Basement", tag="_"),
|
||||
Portal(name="Fortress Courtyard to Forest Belltower", region="Fortress Exterior from East Forest",
|
||||
destination="Forest Belltower", tag="_"),
|
||||
Portal(name="Fortress Courtyard to Overworld", region="Fortress Exterior from Overworld",
|
||||
destination="Overworld Redux", tag="_"),
|
||||
Portal(name="Fortress Courtyard Shop", region="Fortress Exterior near cave",
|
||||
destination="Shop", tag="_"),
|
||||
|
||||
Portal(name="Beneath the Vault to Fortress Interior", region="Beneath the Vault Back",
|
||||
destination="Fortress Main", tag="_"),
|
||||
Portal(name="Beneath the Vault to Fortress Courtyard", region="Beneath the Vault Ladder Exit",
|
||||
destination="Fortress Courtyard", tag="_"),
|
||||
|
||||
Portal(name="Fortress Interior Main Exit", region="Eastern Vault Fortress",
|
||||
destination="Fortress Courtyard", tag="_Big Door"),
|
||||
Portal(name="Fortress Interior to Beneath the Earth", region="Eastern Vault Fortress",
|
||||
destination="Fortress Basement", tag="_"),
|
||||
Portal(name="Fortress Interior to Siege Engine Arena", region="Eastern Vault Fortress Gold Door",
|
||||
destination="Fortress Arena", tag="_"),
|
||||
Portal(name="Fortress Interior Shop", region="Eastern Vault Fortress",
|
||||
destination="Shop", tag="_"),
|
||||
Portal(name="Fortress Interior to East Fortress Upper", region="Eastern Vault Fortress",
|
||||
destination="Fortress East", tag="_upper"),
|
||||
Portal(name="Fortress Interior to East Fortress Lower", region="Eastern Vault Fortress",
|
||||
destination="Fortress East", tag="_lower"),
|
||||
|
||||
Portal(name="East Fortress to Interior Lower", region="Fortress East Shortcut Lower",
|
||||
destination="Fortress Main", tag="_lower"),
|
||||
Portal(name="East Fortress to Courtyard", region="Fortress East Shortcut Upper",
|
||||
destination="Fortress Courtyard", tag="_"),
|
||||
Portal(name="East Fortress to Interior Upper", region="Fortress East Shortcut Upper",
|
||||
destination="Fortress Main", tag="_upper"),
|
||||
|
||||
Portal(name="Fortress Grave Path Lower Exit", region="Fortress Grave Path",
|
||||
destination="Fortress Courtyard", tag="_Lower"),
|
||||
Portal(name="Fortress Hero's Grave", region="Fortress Hero's Grave Region",
|
||||
destination="RelicVoid", tag="_teleporter_relic plinth"),
|
||||
Portal(name="Fortress Grave Path Upper Exit", region="Fortress Grave Path Upper",
|
||||
destination="Fortress Courtyard", tag="_Upper"),
|
||||
Portal(name="Fortress Grave Path Dusty Entrance", region="Fortress Grave Path Dusty Entrance Region",
|
||||
destination="Dusty", tag="_"),
|
||||
|
||||
Portal(name="Dusty Exit", region="Fortress Leaf Piles",
|
||||
destination="Fortress Reliquary", tag="_"),
|
||||
|
||||
Portal(name="Siege Engine Arena to Fortress", region="Fortress Arena",
|
||||
destination="Fortress Main", tag="_"),
|
||||
Portal(name="Fortress to Far Shore", region="Fortress Arena Portal",
|
||||
destination="Transit", tag="_teleporter_spidertank"),
|
||||
|
||||
Portal(name="Stairs to Top of the Mountain", region="Lower Mountain Stairs",
|
||||
destination="Mountaintop", tag="_"),
|
||||
Portal(name="Mountain to Quarry", region="Lower Mountain",
|
||||
@@ -1183,6 +1183,8 @@ traversal_requirements: Dict[str, Dict[str, List[List[str]]]] = {
|
||||
[],
|
||||
"Library Hero's Grave Region":
|
||||
[],
|
||||
"Library Hall to Rotunda":
|
||||
[],
|
||||
},
|
||||
"Library Hero's Grave Region": {
|
||||
"Library Hall":
|
||||
|
||||
@@ -24,10 +24,10 @@ def create_er_regions(world: "TunicWorld") -> Dict[Portal, Portal]:
|
||||
regions: Dict[str, Region] = {}
|
||||
if world.options.entrance_rando:
|
||||
portal_pairs = pair_portals(world)
|
||||
|
||||
# output the entrances to the spoiler log here for convenience
|
||||
for portal1, portal2 in portal_pairs.items():
|
||||
world.multiworld.spoiler.set_entrance(portal1.name, portal2.name, "both", world.player)
|
||||
sorted_portal_pairs = sort_portals(portal_pairs)
|
||||
for portal1, portal2 in sorted_portal_pairs.items():
|
||||
world.multiworld.spoiler.set_entrance(portal1, portal2, "both", world.player)
|
||||
else:
|
||||
portal_pairs = vanilla_portals()
|
||||
|
||||
@@ -504,3 +504,29 @@ def update_reachable_regions(connected_regions: Set[str], traversal_reqs: Dict[s
|
||||
connected_regions = update_reachable_regions(connected_regions, traversal_reqs, has_laurels, logic)
|
||||
|
||||
return connected_regions
|
||||
|
||||
|
||||
# sort the portal dict by the name of the first portal, referring to the portal order in the master portal list
|
||||
def sort_portals(portal_pairs: Dict[Portal, Portal]) -> Dict[str, str]:
|
||||
sorted_pairs: Dict[str, str] = {}
|
||||
reference_list: List[str] = [portal.name for portal in portal_mapping]
|
||||
reference_list.append("Shop Portal")
|
||||
|
||||
# note: this is not necessary yet since the shop portals aren't numbered yet -- they will be when decoupled happens
|
||||
# due to plando, there can be a variable number of shops
|
||||
# I could either do it like this, or just go up to like 200, this seemed better
|
||||
# shop_count = 0
|
||||
# for portal1, portal2 in portal_pairs.items():
|
||||
# if portal1.name.startswith("Shop"):
|
||||
# shop_count += 1
|
||||
# if portal2.name.startswith("Shop"):
|
||||
# shop_count += 1
|
||||
# reference_list.extend([f"Shop Portal {i + 1}" for i in range(shop_count)])
|
||||
|
||||
for name in reference_list:
|
||||
for portal1, portal2 in portal_pairs.items():
|
||||
if name == portal1.name:
|
||||
sorted_pairs[portal1.name] = portal2.name
|
||||
break
|
||||
return sorted_pairs
|
||||
|
||||
|
||||
Reference in New Issue
Block a user