diff --git a/worlds/landstalker/Constants.py b/worlds/landstalker/Constants.py new file mode 100644 index 000000000000..ad4dc6ce7ae6 --- /dev/null +++ b/worlds/landstalker/Constants.py @@ -0,0 +1,28 @@ + +BASE_ITEM_ID = 4000 + +BASE_LOCATION_ID = 4000 +BASE_GROUND_LOCATION_ID = BASE_LOCATION_ID + 256 +BASE_SHOP_LOCATION_ID = BASE_GROUND_LOCATION_ID + 30 +BASE_REWARD_LOCATION_ID = BASE_SHOP_LOCATION_ID + 50 + +ENDGAME_REGIONS = [ + "kazalt", + "king_nole_labyrinth_pre_door", + "king_nole_labyrinth_post_door", + "king_nole_labyrinth_exterior", + "king_nole_labyrinth_fall_from_exterior", + "king_nole_labyrinth_path_to_palace", + "king_nole_labyrinth_raft_entrance", + "king_nole_labyrinth_raft", + "king_nole_labyrinth_sacred_tree", + "king_nole_palace" +] + +ENDGAME_PROGRESSION_ITEMS = [ + "Gola's Nail", + "Gola's Fang", + "Gola's Horn", + "Logs", + "Snow Spikes" +] \ No newline at end of file diff --git a/worlds/landstalker/Hints.py b/worlds/landstalker/Hints.py index 5309e85032ea..4211e0ef3bb1 100644 --- a/worlds/landstalker/Hints.py +++ b/worlds/landstalker/Hints.py @@ -45,7 +45,7 @@ def generate_lithograph_hint(world: "LandstalkerWorld"): words.append(item.name.split(" ")[0].upper()) if item.location.player != world.player: # Add player name if it's not in our own world - player_name = world.multiworld.get_player_name(world.player) + player_name = world.multiworld.get_player_name(item.location.player) words.append(player_name.upper()) world.random.shuffle(words) hint_text += " ".join(words) + "\n" diff --git a/worlds/landstalker/Items.py b/worlds/landstalker/Items.py index ad7efa1cb27a..6424a37f9a1e 100644 --- a/worlds/landstalker/Items.py +++ b/worlds/landstalker/Items.py @@ -1,8 +1,7 @@ from typing import Dict, List, NamedTuple from BaseClasses import Item, ItemClassification - -BASE_ITEM_ID = 4000 +from .Constants import BASE_ITEM_ID class LandstalkerItem(Item): diff --git a/worlds/landstalker/Locations.py b/worlds/landstalker/Locations.py index 0fe63526c63b..25d02ca527f4 100644 --- a/worlds/landstalker/Locations.py +++ b/worlds/landstalker/Locations.py @@ -1,15 +1,11 @@ from typing import Dict, Optional from BaseClasses import Location, ItemClassification, Item +from .Constants import * from .Regions import LandstalkerRegion from .data.item_source import ITEM_SOURCES_JSON from .data.world_path import WORLD_PATHS_JSON -BASE_LOCATION_ID = 4000 -BASE_GROUND_LOCATION_ID = BASE_LOCATION_ID + 256 -BASE_SHOP_LOCATION_ID = BASE_GROUND_LOCATION_ID + 30 -BASE_REWARD_LOCATION_ID = BASE_SHOP_LOCATION_ID + 50 - class LandstalkerLocation(Location): game: str = "Landstalker - The Treasures of King Nole" @@ -21,10 +17,14 @@ def __init__(self, player: int, name: str, location_id: Optional[int], region: L self.type_string = type_string -def create_locations(player: int, regions_table: Dict[str, LandstalkerRegion], name_to_id_table: Dict[str, int]): +def create_locations(player: int, regions_table: Dict[str, LandstalkerRegion], + name_to_id_table: Dict[str, int], reach_kazalt_goal: bool): # Create real locations from the data inside the corresponding JSON file for data in ITEM_SOURCES_JSON: region_id = data["nodeId"] + # If "Reach Kazalt" goal is enabled and location is beyond Kazalt, don't create it + if reach_kazalt_goal and region_id in ENDGAME_REGIONS: + continue region = regions_table[region_id] new_location = LandstalkerLocation(player, data["name"], name_to_id_table[data["name"]], region, data["type"]) region.locations.append(new_location) @@ -32,6 +32,10 @@ def create_locations(player: int, regions_table: Dict[str, LandstalkerRegion], n # Create fake event locations that will be used to determine if some key regions has been visited regions_with_entrance_checks = [] for data in WORLD_PATHS_JSON: + # If "Reach Kazalt" goal is enabled and region is beyond Kazalt, don't create any event for it since it would + # be useless anyway + if reach_kazalt_goal and data["fromId"] in ENDGAME_REGIONS: + continue if "requiredNodes" in data: regions_with_entrance_checks.extend([region_id for region_id in data["requiredNodes"]]) regions_with_entrance_checks = sorted(set(regions_with_entrance_checks)) diff --git a/worlds/landstalker/__init__.py b/worlds/landstalker/__init__.py index 8463e56e54c1..cfdc335c484e 100644 --- a/worlds/landstalker/__init__.py +++ b/worlds/landstalker/__init__.py @@ -2,6 +2,7 @@ from BaseClasses import LocationProgressType, Tutorial from worlds.AutoWorld import WebWorld, World +from .Constants import * from .Hints import * from .Items import * from .Locations import * @@ -87,7 +88,8 @@ def generate_early(self): def create_regions(self): self.regions_table = Regions.create_regions(self) - Locations.create_locations(self.player, self.regions_table, self.location_name_to_id) + Locations.create_locations(self.player, self.regions_table, self.location_name_to_id, + self.options.goal == "reach_kazalt") self.create_teleportation_trees() def create_item(self, name: str, classification_override: Optional[ItemClassification] = None) -> LandstalkerItem: @@ -109,7 +111,16 @@ def create_items(self): # If item is an armor and progressive armors are enabled, transform it into a progressive armor item if self.options.progressive_armors and "Breast" in name: name = "Progressive Armor" - item_pool += [self.create_item(name) for _ in range(data.quantity)] + + qty = data.quantity + if self.options.goal == "reach_kazalt": + # In "Reach Kazalt" goal, remove all endgame progression items that would be useless anyway + if name in ENDGAME_PROGRESSION_ITEMS: + continue + # Also reduce quantities for most filler items to let space for more EkeEke (see end of function) + if data.classification == ItemClassification.filler: + qty = int(qty * 0.8) + item_pool += [self.create_item(name) for _ in range(qty)] # If the appropriate setting is on, place one EkeEke in one shop in every town in the game if self.options.ensure_ekeeke_in_shops: @@ -120,9 +131,10 @@ def create_items(self): "Mercator: Shop item #1", "Verla: Shop item #1", "Destel: Inn item", - "Route to Lake Shrine: Greedly's shop item #1", - "Kazalt: Shop item #1" + "Route to Lake Shrine: Greedly's shop item #1" ] + if self.options.goal != "reach_kazalt": + shops_to_fill.append("Kazalt: Shop item #1") for location_name in shops_to_fill: self.multiworld.get_location(location_name, self.player).place_locked_item(self.create_item("EkeEke")) diff --git a/worlds/landstalker/data/world_node.py b/worlds/landstalker/data/world_node.py index f786f9613fba..0b0c56a74e69 100644 --- a/worlds/landstalker/data/world_node.py +++ b/worlds/landstalker/data/world_node.py @@ -73,6 +73,22 @@ "between Gumi and Ryuma" ] }, + "tibor_tree": { + "name": "Route from Gumi to Ryuma (Tibor tree)", + "hints": [ + "on a route", + "in a region inhabited by bears", + "between Gumi and Ryuma" + ] + }, + "mercator_gate_tree": { + "name": "Route from Gumi to Ryuma (Mercator gate tree)", + "hints": [ + "on a route", + "in a region inhabited by bears", + "between Gumi and Ryuma" + ] + }, "tibor": { "name": "Tibor", "hints": [ @@ -223,6 +239,13 @@ "in the infamous Greenmaze" ] }, + "greenmaze_post_whistle_tree": { + "name": "Greenmaze (post-whistle tree)", + "hints": [ + "among the trees", + "in the infamous Greenmaze" + ] + }, "verla_shore": { "name": "Verla shore", "hints": [ @@ -230,6 +253,13 @@ "near the town of Verla" ] }, + "verla_shore_tree": { + "name": "Verla shore tree", + "hints": [ + "on a route", + "near the town of Verla" + ] + }, "verla_shore_cliff": { "name": "Verla shore cliff (accessible from Verla Mines)", "hints": [ @@ -326,6 +356,12 @@ "in a mountainous area" ] }, + "mountainous_area_tree": { + "name": "Mountainous Area tree", + "hints": [ + "in a mountainous area" + ] + }, "king_nole_cave": { "name": "King Nole's Cave", "hints": [ diff --git a/worlds/landstalker/data/world_path.py b/worlds/landstalker/data/world_path.py index f7baba358a48..572149a73529 100644 --- a/worlds/landstalker/data/world_path.py +++ b/worlds/landstalker/data/world_path.py @@ -54,6 +54,16 @@ "toId": "ryuma", "twoWay": True }, + { + "fromId": "route_gumi_ryuma", + "toId": "tibor_tree", + "twoWay": True + }, + { + "fromId": "route_gumi_ryuma", + "toId": "mercator_gate_tree", + "twoWay": True + }, { "fromId": "ryuma", "toId": "ryuma_after_thieves_hideout", @@ -211,6 +221,11 @@ ], "twoWay": True }, + { + "fromId": "greenmaze_post_whistle", + "toId": "greenmaze_post_whistle_tree", + "twoWay": True + }, { "fromId": "greenmaze_post_whistle", "toId": "route_massan_gumi" @@ -253,6 +268,11 @@ "fromId": "verla_shore_cliff", "toId": "verla_shore" }, + { + "fromId": "verla_shore", + "toId": "verla_shore_tree", + "twoWay": True + }, { "fromId": "verla_shore", "toId": "mir_tower_sector", @@ -316,6 +336,11 @@ "Axe Magic" ] }, + { + "fromId": "mountainous_area", + "toId": "mountainous_area_tree", + "twoWay": True + }, { "fromId": "mountainous_area", "toId": "route_lake_shrine_cliff", diff --git a/worlds/landstalker/data/world_region.py b/worlds/landstalker/data/world_region.py index 3365a9dfa9e2..81ff94452257 100644 --- a/worlds/landstalker/data/world_region.py +++ b/worlds/landstalker/data/world_region.py @@ -57,7 +57,9 @@ "name": "Route between Gumi and Ryuma", "canBeHintedAsRequired": False, "nodeIds": [ - "route_gumi_ryuma" + "route_gumi_ryuma", + "tibor_tree", + "mercator_gate_tree" ] }, { @@ -157,7 +159,8 @@ "hintName": "in Greenmaze", "nodeIds": [ "greenmaze_pre_whistle", - "greenmaze_post_whistle" + "greenmaze_post_whistle", + "greenmaze_post_whistle_tree" ] }, { @@ -165,7 +168,8 @@ "canBeHintedAsRequired": False, "nodeIds": [ "verla_shore", - "verla_shore_cliff" + "verla_shore_cliff", + "verla_shore_tree" ] }, { @@ -244,7 +248,8 @@ "name": "Mountainous Area", "hintName": "in the mountainous area", "nodeIds": [ - "mountainous_area" + "mountainous_area", + "mountainous_area_tree" ] }, { diff --git a/worlds/landstalker/data/world_teleport_tree.py b/worlds/landstalker/data/world_teleport_tree.py index 830f5547201e..f3b92affd3a6 100644 --- a/worlds/landstalker/data/world_teleport_tree.py +++ b/worlds/landstalker/data/world_teleport_tree.py @@ -8,19 +8,19 @@ { "name": "Tibor tree", "treeMapId": 534, - "nodeId": "route_gumi_ryuma" + "nodeId": "tibor_tree" } ], [ { "name": "Mercator front gate tree", "treeMapId": 539, - "nodeId": "route_gumi_ryuma" + "nodeId": "mercator_gate_tree" }, { "name": "Verla shore tree", "treeMapId": 537, - "nodeId": "verla_shore" + "nodeId": "verla_shore_tree" } ], [ @@ -44,7 +44,7 @@ { "name": "Mountainous area tree", "treeMapId": 535, - "nodeId": "mountainous_area" + "nodeId": "mountainous_area_tree" } ], [ @@ -56,7 +56,7 @@ { "name": "Greenmaze end tree", "treeMapId": 511, - "nodeId": "greenmaze_post_whistle" + "nodeId": "greenmaze_post_whistle_tree" } ] ] \ No newline at end of file