diff --git a/worlds/dark_souls_3/Items.py b/worlds/dark_souls_3/Items.py index e1d11bb636..650781aef1 100644 --- a/worlds/dark_souls_3/Items.py +++ b/worlds/dark_souls_3/Items.py @@ -4,7 +4,6 @@ from worlds.dark_souls_3.data.items_data import item_dictionary_table class DarkSouls3Item(Item): - game: str = "Dark Souls III" @staticmethod def get_item_name_to_id() -> typing.Dict[str, int]: diff --git a/worlds/dark_souls_3/Locations.py b/worlds/dark_souls_3/Locations.py index b4c713d732..98cb854e0a 100644 --- a/worlds/dark_souls_3/Locations.py +++ b/worlds/dark_souls_3/Locations.py @@ -8,12 +8,6 @@ class LocationData(int): class DarkSouls3Location(Location): - game: str = "Dark Souls III" - - # override constructor to automatically mark event locations as such - def __init__(self, player: int, name='', code=None, parent=None): - super(DarkSouls3Location, self).__init__(player, name, code, parent) - self.event = code is None @staticmethod def get_location_name_to_id() -> typing.Dict[str, int]: diff --git a/worlds/dark_souls_3/README.md b/worlds/dark_souls_3/README.md deleted file mode 100644 index 26a5434088..0000000000 --- a/worlds/dark_souls_3/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Dark Souls in [Archipelago](https://archipelago.gg) - -## Abstract - -- Extract the list of items (Weapons, Armors, KeyItems and Goods) -- Hardcode this list along rules on the Archipelago server -- The Archipelago server will shuffle and create a specific file for the Dark Souls client -- This file or seed will place the items to their locations - -## Prerequisities - - - - - -- onItemPicked event \ No newline at end of file diff --git a/worlds/dark_souls_3/__init__.py b/worlds/dark_souls_3/__init__.py index 691b4647d6..d9e76577be 100644 --- a/worlds/dark_souls_3/__init__.py +++ b/worlds/dark_souls_3/__init__.py @@ -44,15 +44,19 @@ class DarkSouls3World(World): def create_item(self, name: str) -> Item: data = self.item_name_to_id[name] - return DarkSouls3Item(name, ItemClassification.progression if name in key_items_list else ItemClassification.useful, data, self.player) + return DarkSouls3Item( + name, ItemClassification.progression if name in key_items_list else + ItemClassification.useful, data, self.player) def create_regions(self): menu_region = Region("Menu", RegionType.Generic, "Menu", self.player) self.world.regions.append(menu_region) + # Create all Vanilla regions of Dark Souls 3 cemetery_of_ash_region = self.create_region("Cemetery Of Ash", cemetery_of_ash_table) firelink_shrine_region = self.create_region("Firelink Shrine", fire_link_shrine_table) - firelink_shrine_bell_tower_region = self.create_region("Firelink Shrine Bell Tower", firelink_shrine_bell_tower_table) + firelink_shrine_bell_tower_region = self.create_region("Firelink Shrine Bell Tower", + firelink_shrine_bell_tower_table) high_wall_of_lothric_region = self.create_region("High Wall of Lothric", high_wall_of_lothric) undead_settlement_region = self.create_region("Undead Settlement", undead_settlement_table) road_of_sacrifices_region = self.create_region("Road of Sacrifices", road_of_sacrifice_table) @@ -61,7 +65,8 @@ class DarkSouls3World(World): farron_keep_region = self.create_region("Farron Keep", farron_keep_table) catacombs_of_carthus_region = self.create_region("Catacombs of Carthus", catacombs_of_carthus_table) smouldering_lake_region = self.create_region("Smouldering Lake", smouldering_lake_table) - irithyll_of_the_boreal_valley_region = self.create_region("Irithyll of the Boreal Valley", irithyll_of_the_boreal_valley_table) + irithyll_of_the_boreal_valley_region = self.create_region("Irithyll of the Boreal Valley", + irithyll_of_the_boreal_valley_table) irithyll_dungeon_region = self.create_region("Irithyll Dungeon", irithyll_dungeon_table) profaned_capital_region = self.create_region("Profaned Capital", profaned_capital_table) anor_londo_region = self.create_region("Anor Londo", anor_londo_table) @@ -71,23 +76,28 @@ class DarkSouls3World(World): archdragon_peak_region = self.create_region("Archdragon Peak", archdragon_peak_table) kiln_of_the_first_flame_region = self.create_region("Kiln Of The First Flame", None) - # Entrances + # Create the entrance to connect those regions menu_region.exits.append(Entrance(self.player, "New Game", menu_region)) self.world.get_entrance("New Game", self.player).connect(cemetery_of_ash_region) cemetery_of_ash_region.exits.append(Entrance(self.player, "Goto Firelink Shrine", cemetery_of_ash_region)) self.world.get_entrance("Goto Firelink Shrine", self.player).connect(firelink_shrine_region) - firelink_shrine_region.exits.append(Entrance(self.player, "Goto High Wall of Lothric", firelink_shrine_region)) - firelink_shrine_region.exits.append(Entrance(self.player, "Goto Kiln Of The First Flame", firelink_shrine_region)) - firelink_shrine_region.exits.append(Entrance(self.player, "Goto Bell Tower", firelink_shrine_region)) + firelink_shrine_region.exits.append(Entrance(self.player, "Goto High Wall of Lothric", + firelink_shrine_region)) + firelink_shrine_region.exits.append(Entrance(self.player, "Goto Kiln Of The First Flame", + firelink_shrine_region)) + firelink_shrine_region.exits.append(Entrance(self.player, "Goto Bell Tower", + firelink_shrine_region)) self.world.get_entrance("Goto High Wall of Lothric", self.player).connect(high_wall_of_lothric_region) self.world.get_entrance("Goto Kiln Of The First Flame", self.player).connect(kiln_of_the_first_flame_region) self.world.get_entrance("Goto Bell Tower", self.player).connect(firelink_shrine_bell_tower_region) - - high_wall_of_lothric_region.exits.append(Entrance(self.player, "Goto Undead Settlement", high_wall_of_lothric_region)) - high_wall_of_lothric_region.exits.append(Entrance(self.player, "Goto Lothric Castle", high_wall_of_lothric_region)) + high_wall_of_lothric_region.exits.append(Entrance(self.player, "Goto Undead Settlement", + high_wall_of_lothric_region)) + high_wall_of_lothric_region.exits.append(Entrance(self.player, "Goto Lothric Castle", + high_wall_of_lothric_region)) self.world.get_entrance("Goto Undead Settlement", self.player).connect(undead_settlement_region) self.world.get_entrance("Goto Lothric Castle", self.player).connect(lothric_castle_region) - undead_settlement_region.exits.append(Entrance(self.player, "Goto Road Of Sacrifices", undead_settlement_region)) + undead_settlement_region.exits.append(Entrance(self.player, "Goto Road Of Sacrifices", + undead_settlement_region)) self.world.get_entrance("Goto Road Of Sacrifices", self.player).connect(road_of_sacrifices_region) road_of_sacrifices_region.exits.append(Entrance(self.player, "Goto Cathedral", road_of_sacrifices_region)) road_of_sacrifices_region.exits.append(Entrance(self.player, "Goto Farron keep", road_of_sacrifices_region)) @@ -95,27 +105,32 @@ class DarkSouls3World(World): self.world.get_entrance("Goto Farron keep", self.player).connect(farron_keep_region) farron_keep_region.exits.append(Entrance(self.player, "Goto Carthus catacombs", farron_keep_region)) self.world.get_entrance("Goto Carthus catacombs", self.player).connect(catacombs_of_carthus_region) - catacombs_of_carthus_region.exits.append(Entrance(self.player, "Goto Irithyll of the boreal", catacombs_of_carthus_region)) - catacombs_of_carthus_region.exits.append(Entrance(self.player, "Goto Smouldering Lake", catacombs_of_carthus_region)) - self.world.get_entrance("Goto Irithyll of the boreal", self.player).connect(irithyll_of_the_boreal_valley_region) + catacombs_of_carthus_region.exits.append(Entrance(self.player, "Goto Irithyll of the boreal", + catacombs_of_carthus_region)) + catacombs_of_carthus_region.exits.append(Entrance(self.player, "Goto Smouldering Lake", + catacombs_of_carthus_region)) + self.world.get_entrance("Goto Irithyll of the boreal", self.player).\ + connect(irithyll_of_the_boreal_valley_region) self.world.get_entrance("Goto Smouldering Lake", self.player).connect(smouldering_lake_region) - irithyll_of_the_boreal_valley_region.exits.append(Entrance(self.player, "Goto Irithyll dungeon", irithyll_of_the_boreal_valley_region)) - irithyll_of_the_boreal_valley_region.exits.append(Entrance(self.player, "Goto Anor Londo", irithyll_of_the_boreal_valley_region)) + irithyll_of_the_boreal_valley_region.exits.append(Entrance(self.player, "Goto Irithyll dungeon", + irithyll_of_the_boreal_valley_region)) + irithyll_of_the_boreal_valley_region.exits.append(Entrance(self.player, "Goto Anor Londo", + irithyll_of_the_boreal_valley_region)) self.world.get_entrance("Goto Irithyll dungeon", self.player).connect(irithyll_dungeon_region) self.world.get_entrance("Goto Anor Londo", self.player).connect(anor_londo_region) irithyll_dungeon_region.exits.append(Entrance(self.player, "Goto Archdragon peak", irithyll_dungeon_region)) irithyll_dungeon_region.exits.append(Entrance(self.player, "Goto Profaned capital", irithyll_dungeon_region)) self.world.get_entrance("Goto Archdragon peak", self.player).connect(archdragon_peak_region) self.world.get_entrance("Goto Profaned capital", self.player).connect(profaned_capital_region) - lothric_castle_region.exits.append(Entrance(self.player, "Goto Consumed King Garden", lothric_castle_region)) lothric_castle_region.exits.append(Entrance(self.player, "Goto Grand Archives", lothric_castle_region)) self.world.get_entrance("Goto Consumed King Garden", self.player).connect(consumed_king_garden_region) self.world.get_entrance("Goto Grand Archives", self.player).connect(grand_archives_region) - - consumed_king_garden_region.exits.append(Entrance(self.player, "Goto Untended Graves", consumed_king_garden_region)) + consumed_king_garden_region.exits.append(Entrance(self.player, "Goto Untended Graves", + consumed_king_garden_region)) self.world.get_entrance("Goto Untended Graves", self.player).connect(untended_graves_region) + # For each region, add the associated locations retrieved from the corresponding location_table def create_region(self, name, location_table) -> Region: new_region = Region(name, RegionType.Generic, name, self.player) if location_table is not None: @@ -128,6 +143,7 @@ class DarkSouls3World(World): def create_items(self): for name, address in self.item_name_to_id.items(): + # Specific items will be included in the item pool under certain conditions. See generate_basic if name != "Basin of Vows" and name != "Path of the Dragon Gesture": self.world.itempool += [self.create_item(name)] @@ -135,23 +151,34 @@ class DarkSouls3World(World): pass def set_rules(self) -> None: - set_rule(self.world.get_entrance("Goto Bell Tower", self.player), - lambda state: state.has("Mortician's Ashes", self.player)) - set_rule(self.world.get_entrance("Goto Undead Settlement", self.player), - lambda state: state.has("Small Lothric Banner", self.player)) - set_rule(self.world.get_entrance("Goto Lothric Castle", self.player), - lambda state: state.has("Basin of Vows", self.player)) - set_rule(self.world.get_location("HWL: Soul of the Dancer", self.player), - lambda state: state.has("Basin of Vows", self.player)) - set_rule(self.world.get_entrance("Goto Irithyll of the boreal", self.player), - lambda state: state.has("Small Doll", self.player)) - set_rule(self.world.get_entrance("Goto Archdragon peak", self.player), - lambda state: state.has("Path of the Dragon Gesture", self.player)) - set_rule(self.world.get_entrance("Goto Profaned capital", self.player), - lambda state: state.has("Storm Ruler", self.player)) - set_rule(self.world.get_entrance("Goto Grand Archives", self.player), - lambda state: state.has("Grand Archives Key", self.player)) - set_rule(self.world.get_entrance("Goto Kiln Of The First Flame", self.player), lambda state: + + # Define the access rules to the entrances + set_rule( + self.world.get_entrance("Goto Bell Tower", self.player), + lambda state: state.has("Mortician's Ashes", self.player)) + set_rule( + self.world.get_entrance("Goto Undead Settlement", self.player), + lambda state: state.has("Small Lothric Banner", self.player)) + set_rule( + self.world.get_entrance("Goto Lothric Castle", self.player), + lambda state: state.has("Basin of Vows", self.player)) + set_rule( + self.world.get_location("HWL: Soul of the Dancer", self.player), + lambda state: state.has("Basin of Vows", self.player)) + set_rule( + self.world.get_entrance("Goto Irithyll of the boreal", self.player), + lambda state: state.has("Small Doll", self.player)) + set_rule( + self.world.get_entrance("Goto Archdragon peak", self.player), + lambda state: state.has("Path of the Dragon Gesture", self.player)) + set_rule( + self.world.get_entrance("Goto Profaned capital", self.player), + lambda state: state.has("Storm Ruler", self.player)) + set_rule( + self.world.get_entrance("Goto Grand Archives", self.player), + lambda state: state.has("Grand Archives Key", self.player)) + set_rule( + self.world.get_entrance("Goto Kiln Of The First Flame", self.player), lambda state: state.has("Cinders of a Lord - Abyss Watcher", self.player) and state.has("Cinders of a Lord - Yhorm the Giant", self.player) and state.has("Cinders of a Lord - Aldrich", self.player) and @@ -164,23 +191,21 @@ class DarkSouls3World(World): state.has("Cinders of a Lord - Lothric Prince", self.player) def generate_basic(self): + # Depending on the specified option, add the Basin of Vows to a specific location or to the item pool item = self.create_item("Basin of Vows") if self.world.late_basin_of_vows[self.player]: self.world.get_location("IBV: Soul of Pontiff Sulyvahn", self.player).place_locked_item(item) else: - self.world.itempool += item + self.world.itempool += [item] - itempool_len = self.item_name_to_id.__len__() + # Fill item pool with additional items + item_pool_len = self.item_name_to_id.__len__() total_required_locations = self.location_name_to_id.__len__() - - # Fill item pool with the remaining - for i in range(itempool_len, total_required_locations): + for i in range(item_pool_len, total_required_locations): self.world.itempool += [self.create_item("Soul of an Intrepid Hero")] def generate_output(self, output_directory: str): - - print(self.world.get_items().__len__()) - + # Depending on the specified option, modify items hexadecimal value to add an upgrade level item_dictionary = item_dictionary_table.copy() if self.world.randomize_weapons_level[self.player]: # Randomize some weapons upgrades @@ -194,23 +219,24 @@ class DarkSouls3World(World): value = randint(1, 10) item_dictionary[name] += value - itemsId = [] - itemsAddress = [] - locationsId = [] - locationsAddress = [] - locationsTarget = [] + # Create the mandatory lists to generate the player's output file + items_id = [] + items_address = [] + locations_id = [] + locations_address = [] + locations_target = [] for location in self.world.get_filled_locations(): if location.item.player == self.player: - itemsId.append(location.item.code) - itemsAddress.append(item_dictionary[location.item.name]) + items_id.append(location.item.code) + items_address.append(item_dictionary[location.item.name]) if location.player == self.player: - locationsAddress.append(dictionary_table[location.name]) - locationsId.append(location.address) + locations_address.append(dictionary_table[location.name]) + locations_id.append(location.address) if location.item.player == self.player: - locationsTarget.append(item_dictionary[location.item.name]) + locations_target.append(item_dictionary[location.item.name]) else: - locationsTarget.append(0) + locations_target.append(0) data = { "options": { @@ -221,11 +247,11 @@ class DarkSouls3World(World): "seed": self.world.seed_name, # to verify the server's multiworld "slot": self.world.player_name[self.player], # to connect to server "base_id": self.base_id, # to merge location and items lists - "locationsId": locationsId, - "locationsAddress": locationsAddress, - "locationsTarget": locationsTarget, - "itemsId": itemsId, - "itemsAddress": itemsAddress + "locationsId": locations_id, + "locationsAddress": locations_address, + "locationsTarget": locations_target, + "itemsId": items_id, + "itemsAddress": items_address } # generate the file @@ -245,17 +271,11 @@ class DarkSouls3World(World): data_version = 0 # ID of first item and location, could be hard-coded but code may be easier - # to read with this as a propery. + # to read with this as a property. base_id = 100000 - # Instead of dynamic numbering, IDs could be part of data. # The following two dicts are required for the generation to know which # items exist. They could be generated from json or something else. They can # include events, but don't have to since events will be placed manually. item_name_to_id = {name: id for id, name in enumerate(DarkSouls3Item.get_item_name_to_id(), base_id)} location_name_to_id = {name: id for id, name in enumerate(DarkSouls3Location.get_location_name_to_id(), base_id)} - - - - - diff --git a/worlds/dark_souls_3/data/items_data.py b/worlds/dark_souls_3/data/items_data.py index f59f48dc0a..8dbea1d011 100644 --- a/worlds/dark_souls_3/data/items_data.py +++ b/worlds/dark_souls_3/data/items_data.py @@ -1,6 +1,9 @@ -# Regular expression to parse the Python list https://regex101.com/r/XdtiLR/2 -# List of location https://darksouls3.wiki.fextralife.com/Locations - +""" +Tools used to create this list : +List of all items https://docs.google.com/spreadsheets/d/1nK2g7g6XJ-qphFAk1tjP3jZtlXWDQY-ItKLa_sniawo/edit#gid=1551945791 +Regular expression parser https://regex101.com/r/XdtiLR/2 +List of locations https://darksouls3.wiki.fextralife.com/Locations +""" weapons_upgrade_5_table = { "Irithyll Straight Sword": 0x0020A760, diff --git a/worlds/dark_souls_3/data/locations_data.py b/worlds/dark_souls_3/data/locations_data.py index 5011b06a08..61d69ee45b 100644 --- a/worlds/dark_souls_3/data/locations_data.py +++ b/worlds/dark_souls_3/data/locations_data.py @@ -1,5 +1,9 @@ -# Regular expression to parse the Python list https://regex101.com/r/XdtiLR/2 -# List of location https://darksouls3.wiki.fextralife.com/Locations +""" +Tools used to create this list : +List of all items https://docs.google.com/spreadsheets/d/1nK2g7g6XJ-qphFAk1tjP3jZtlXWDQY-ItKLa_sniawo/edit#gid=1551945791 +Regular expression parser https://regex101.com/r/XdtiLR/2 +List of locations https://darksouls3.wiki.fextralife.com/Locations +""" cemetery_of_ash_table = { } diff --git a/worlds/dark_souls_3/docs/setup_en.md b/worlds/dark_souls_3/docs/setup_en.md index dceb6bff0e..574ecff819 100644 --- a/worlds/dark_souls_3/docs/setup_en.md +++ b/worlds/dark_souls_3/docs/setup_en.md @@ -18,7 +18,7 @@ The randomization is performed by the AP.json file, an output file generated by Get the dinput8.dll from the [Dark Souls 3 AP Client](https://github.com/Marechal-L/DarkSouls3-Archipelago-client). Then you need to add the two following files at the root folder of your game -( e.g. "SteamLibrary\steamapps\common\DARK SOULS III\Game" ) : +( e.g. "SteamLibrary\steamapps\common\DARK SOULS III\Game" ): - **dinput8.dll** - **AP.json** renamed from the generated file AP-{SEED_NUMBER}-{PLAYER_NUMBER}-{PLAYER_NAME}.json