From 1cad51b1af4e0279ae82fb5f9cfca532cabd7ffb Mon Sep 17 00:00:00 2001 From: Zach Parks Date: Sat, 29 Oct 2022 22:15:06 -0500 Subject: [PATCH] Rogue Legacy: World folder clean up and generation improvements. (#1148) * Minor cleanup and renaming of some files/functions. * Rename `LegacyWorld` and `LegacyWeb` to RLWorld and RLWeb. * Undo accidental change to comment. * Undo accidental change to comment. * Restructure Items.py format and combine all tables into one. * Restructure Locations.py format and combine all tables into one. * Split boss event items into separate boss entries. * Remove definitions folder. * Reformatted __init__.py for Rogue Legacy. * Allow fairy chests to be disabled. * Add working prefill logic for early vendors. * Re-introduce Early Architect setting. * Revamped rules and regions and can now generate games. * Fix normal vendors breaking everything. * Fix early vendor logic and add fairy chest logic to require Dragons or Enchantress + runes. * Fix issue with duplicate items being created. * Move event placement into __init__.py and fix duplicate Vendors. * Tweak weights and spacing. * Update documentation and include bug report link. * Fix relative link for template file. * Increase amount of chest locations in `location_table`. * Correct a refactor rename gone wrong. * Remove unused reference in imports. * Tweak mistake in boss name in place_events. * English is hard. * Tweak some lines in __init__.py to use `.settings()` method. * Add unique id tests for Rogue Legacy. IDs are mixed around, so let's try to avoid accidentally using the same identifier twice. * Fix typo in doc. * Simplify `fill_slot_data`. * Change prefix on `_place_events` to maintain convention. * Remove items that are **not** progression from rules. --- test/worlds/rogue_legacy/TestUnique.py | 23 ++ test/worlds/rogue_legacy/__init__.py | 5 + worlds/rogue_legacy/Items.py | 233 ++++++------- worlds/rogue_legacy/Locations.py | 160 ++++----- worlds/rogue_legacy/Names/ItemName.py | 97 ------ worlds/rogue_legacy/Names/LocationName.py | 55 ---- worlds/rogue_legacy/Names/__init__.py | 0 worlds/rogue_legacy/Options.py | 23 +- worlds/rogue_legacy/Regions.py | 157 ++++++--- worlds/rogue_legacy/Rules.py | 221 ++++--------- worlds/rogue_legacy/Traits.py | 1 - worlds/rogue_legacy/__init__.py | 347 ++++++++++++-------- worlds/rogue_legacy/docs/en_Rogue Legacy.md | 17 +- worlds/rogue_legacy/docs/rogue-legacy_en.md | 25 +- 14 files changed, 636 insertions(+), 728 deletions(-) create mode 100644 test/worlds/rogue_legacy/TestUnique.py create mode 100644 test/worlds/rogue_legacy/__init__.py delete mode 100644 worlds/rogue_legacy/Names/ItemName.py delete mode 100644 worlds/rogue_legacy/Names/LocationName.py delete mode 100644 worlds/rogue_legacy/Names/__init__.py diff --git a/test/worlds/rogue_legacy/TestUnique.py b/test/worlds/rogue_legacy/TestUnique.py new file mode 100644 index 00000000..dbe35dd9 --- /dev/null +++ b/test/worlds/rogue_legacy/TestUnique.py @@ -0,0 +1,23 @@ +from typing import Dict + +from . import RLTestBase +from worlds.rogue_legacy.Items import RLItemData, item_table +from worlds.rogue_legacy.Locations import RLLocationData, location_table + + +class UniqueTest(RLTestBase): + @staticmethod + def test_item_ids_are_all_unique(): + item_ids: Dict[int, str] = {} + for name, data in item_table.items(): + assert data.code not in item_ids.keys(), f"'{name}': {data.code}, is not unique. " \ + f"'{item_ids[data.code]}' also has this identifier." + item_ids[data.code] = name + + @staticmethod + def test_location_ids_are_all_unique(): + location_ids: Dict[int, str] = {} + for name, data in location_table.items(): + assert data.code not in location_ids.keys(), f"'{name}': {data.code}, is not unique. " \ + f"'{location_ids[data.code]}' also has this identifier." + location_ids[data.code] = name diff --git a/test/worlds/rogue_legacy/__init__.py b/test/worlds/rogue_legacy/__init__.py new file mode 100644 index 00000000..41ddcde1 --- /dev/null +++ b/test/worlds/rogue_legacy/__init__.py @@ -0,0 +1,5 @@ +from test.worlds.test_base import WorldTestBase + + +class RLTestBase(WorldTestBase): + game = "Rogue Legacy" diff --git a/worlds/rogue_legacy/Items.py b/worlds/rogue_legacy/Items.py index 5a8f66d5..b52d603a 100644 --- a/worlds/rogue_legacy/Items.py +++ b/worlds/rogue_legacy/Items.py @@ -1,136 +1,115 @@ -import typing +from typing import Dict, NamedTuple, Optional -from BaseClasses import Item -from .Names import ItemName +from BaseClasses import Item, ItemClassification -class ItemData(typing.NamedTuple): - code: typing.Optional[int] - progression: bool - quantity: int = 1 - event: bool = False - - -class LegacyItem(Item): +class RLItem(Item): game: str = "Rogue Legacy" -# Separate tables for each type of item. -vendors_table = { - ItemName.blacksmith: ItemData(90000, True), - ItemName.enchantress: ItemData(90001, True), - ItemName.architect: ItemData(90002, False), +class RLItemData(NamedTuple): + category: str + code: Optional[int] = None + classification: ItemClassification = ItemClassification.filler + max_quantity: int = 1 + weight: int = 1 + + @property + def is_event_item(self): + return self.code is None + + +def get_items_by_category(category: str) -> Dict[str, RLItemData]: + item_dict: Dict[str, RLItemData] = {} + for name, data in item_table.items(): + if data.category == category: + item_dict.setdefault(name, data) + + return item_dict + + +item_table: Dict[str, RLItemData] = { + # Vendors + "Blacksmith": RLItemData("Vendors", 90_000, ItemClassification.useful), + "Enchantress": RLItemData("Vendors", 90_001, ItemClassification.progression), + "Architect": RLItemData("Vendors", 90_002, ItemClassification.useful), + + # Classes + "Progressive Knights": RLItemData("Classes", 90_003, ItemClassification.useful, 2), + "Progressive Mages": RLItemData("Classes", 90_004, ItemClassification.useful, 2), + "Progressive Barbarians": RLItemData("Classes", 90_005, ItemClassification.useful, 2), + "Progressive Knaves": RLItemData("Classes", 90_006, ItemClassification.useful, 2), + "Progressive Shinobis": RLItemData("Classes", 90_007, ItemClassification.useful, 2), + "Progressive Miners": RLItemData("Classes", 90_008, ItemClassification.useful, 2), + "Progressive Liches": RLItemData("Classes", 90_009, ItemClassification.useful, 2), + "Progressive Spellthieves": RLItemData("Classes", 90_010, ItemClassification.useful, 2), + "Dragons": RLItemData("Classes", 90_096, ItemClassification.progression), + "Traitors": RLItemData("Classes", 90_097, ItemClassification.useful), + + # Skills + "Health Up": RLItemData("Skills", 90_013, ItemClassification.progression_skip_balancing, 15), + "Mana Up": RLItemData("Skills", 90_014, ItemClassification.progression_skip_balancing, 15), + "Attack Up": RLItemData("Skills", 90_015, ItemClassification.progression_skip_balancing, 15), + "Magic Damage Up": RLItemData("Skills", 90_016, ItemClassification.progression_skip_balancing, 15), + "Armor Up": RLItemData("Skills", 90_017, ItemClassification.useful, 15), + "Equip Up": RLItemData("Skills", 90_018, ItemClassification.useful, 5), + "Crit Chance Up": RLItemData("Skills", 90_019, ItemClassification.useful, 5), + "Crit Damage Up": RLItemData("Skills", 90_020, ItemClassification.useful, 5), + "Down Strike Up": RLItemData("Skills", 90_021), + "Gold Gain Up": RLItemData("Skills", 90_022), + "Potion Efficiency Up": RLItemData("Skills", 90_023), + "Invulnerability Time Up": RLItemData("Skills", 90_024), + "Mana Cost Down": RLItemData("Skills", 90_025), + "Death Defiance": RLItemData("Skills", 90_026, ItemClassification.useful), + "Haggling": RLItemData("Skills", 90_027, ItemClassification.useful), + "Randomize Children": RLItemData("Skills", 90_028, ItemClassification.useful), + + # Blueprints + "Progressive Blueprints": RLItemData("Blueprints", 90_055, ItemClassification.useful, 15), + "Squire Blueprints": RLItemData("Blueprints", 90_040, ItemClassification.useful), + "Silver Blueprints": RLItemData("Blueprints", 90_041, ItemClassification.useful), + "Guardian Blueprints": RLItemData("Blueprints", 90_042, ItemClassification.useful), + "Imperial Blueprints": RLItemData("Blueprints", 90_043, ItemClassification.useful), + "Royal Blueprints": RLItemData("Blueprints", 90_044, ItemClassification.useful), + "Knight Blueprints": RLItemData("Blueprints", 90_045, ItemClassification.useful), + "Ranger Blueprints": RLItemData("Blueprints", 90_046, ItemClassification.useful), + "Sky Blueprints": RLItemData("Blueprints", 90_047, ItemClassification.useful), + "Dragon Blueprints": RLItemData("Blueprints", 90_048, ItemClassification.useful), + "Slayer Blueprints": RLItemData("Blueprints", 90_049, ItemClassification.useful), + "Blood Blueprints": RLItemData("Blueprints", 90_050, ItemClassification.useful), + "Sage Blueprints": RLItemData("Blueprints", 90_051, ItemClassification.useful), + "Retribution Blueprints": RLItemData("Blueprints", 90_052, ItemClassification.useful), + "Holy Blueprints": RLItemData("Blueprints", 90_053, ItemClassification.useful), + "Dark Blueprints": RLItemData("Blueprints", 90_054, ItemClassification.useful), + + # Runes + "Vault Runes": RLItemData("Runes", 90_060, ItemClassification.progression), + "Sprint Runes": RLItemData("Runes", 90_061, ItemClassification.progression), + "Vampire Runes": RLItemData("Runes", 90_062, ItemClassification.useful), + "Sky Runes": RLItemData("Runes", 90_063, ItemClassification.progression), + "Siphon Runes": RLItemData("Runes", 90_064, ItemClassification.useful), + "Retaliation Runes": RLItemData("Runes", 90_065), + "Bounty Runes": RLItemData("Runes", 90_066), + "Haste Runes": RLItemData("Runes", 90_067), + "Curse Runes": RLItemData("Runes", 90_068), + "Grace Runes": RLItemData("Runes", 90_069), + "Balance Runes": RLItemData("Runes", 90_070, ItemClassification.useful), + + # Junk + "Triple Stat Increase": RLItemData("Filler", 90_030, weight=6), + "1000 Gold": RLItemData("Filler", 90_031, weight=3), + "3000 Gold": RLItemData("Filler", 90_032, weight=2), + "5000 Gold": RLItemData("Filler", 90_033, weight=1), } -static_classes_table = { - ItemName.knight: ItemData(90080, False), - ItemName.paladin: ItemData(90081, False), - ItemName.mage: ItemData(90082, False), - ItemName.archmage: ItemData(90083, False), - ItemName.barbarian: ItemData(90084, False), - ItemName.barbarian_king: ItemData(90085, False), - ItemName.knave: ItemData(90086, False), - ItemName.assassin: ItemData(90087, False), - ItemName.shinobi: ItemData(90088, False), - ItemName.hokage: ItemData(90089, False), - ItemName.miner: ItemData(90090, False), - ItemName.spelunker: ItemData(90091, False), - ItemName.lich: ItemData(90092, False), - ItemName.lich_king: ItemData(90093, False), - ItemName.spellthief: ItemData(90094, False), - ItemName.spellsword: ItemData(90095, False), - ItemName.dragon: ItemData(90096, False), - ItemName.traitor: ItemData(90097, False), +event_item_table: Dict[str, RLItemData] = { + "Defeat Khidr": RLItemData("Event", classification=ItemClassification.progression), + "Defeat Alexander": RLItemData("Event", classification=ItemClassification.progression), + "Defeat Ponce de Leon": RLItemData("Event", classification=ItemClassification.progression), + "Defeat Herodotus": RLItemData("Event", classification=ItemClassification.progression), + "Defeat Neo Khidr": RLItemData("Event", classification=ItemClassification.progression), + "Defeat Alexander IV": RLItemData("Event", classification=ItemClassification.progression), + "Defeat Ponce de Freon": RLItemData("Event", classification=ItemClassification.progression), + "Defeat Astrodotus": RLItemData("Event", classification=ItemClassification.progression), + "Defeat The Fountain": RLItemData("Event", classification=ItemClassification.progression), } - -progressive_classes_table = { - ItemName.progressive_knight: ItemData(90003, False, 2), - ItemName.progressive_mage: ItemData(90004, False, 2), - ItemName.progressive_barbarian: ItemData(90005, False, 2), - ItemName.progressive_knave: ItemData(90006, False, 2), - ItemName.progressive_shinobi: ItemData(90007, False, 2), - ItemName.progressive_miner: ItemData(90008, False, 2), - ItemName.progressive_lich: ItemData(90009, False, 2), - ItemName.progressive_spellthief: ItemData(90010, False, 2), -} - -configurable_skill_unlocks_table = { - ItemName.health: ItemData(90013, True, 15), - ItemName.mana: ItemData(90014, True, 15), - ItemName.attack: ItemData(90015, True, 15), - ItemName.magic_damage: ItemData(90016, True, 15), - ItemName.armor: ItemData(90017, True, 10), - ItemName.equip: ItemData(90018, True, 10), - ItemName.crit_chance: ItemData(90019, False, 5), - ItemName.crit_damage: ItemData(90020, False, 5), -} - -skill_unlocks_table = { - ItemName.down_strike: ItemData(90021, False), - ItemName.gold_gain: ItemData(90022, False), - ItemName.potion_efficiency: ItemData(90023, False), - ItemName.invulnerability_time: ItemData(90024, False), - ItemName.mana_cost_down: ItemData(90025, False), - ItemName.death_defiance: ItemData(90026, False), - ItemName.haggling: ItemData(90027, False), - ItemName.random_children: ItemData(90028, False), -} - -blueprints_table = { - ItemName.squire_blueprints: ItemData(90040, False), - ItemName.silver_blueprints: ItemData(90041, False), - ItemName.guardian_blueprints: ItemData(90042, False), - ItemName.imperial_blueprints: ItemData(90043, False), - ItemName.royal_blueprints: ItemData(90044, False), - ItemName.knight_blueprints: ItemData(90045, False), - ItemName.ranger_blueprints: ItemData(90046, False), - ItemName.sky_blueprints: ItemData(90047, False), - ItemName.dragon_blueprints: ItemData(90048, False), - ItemName.slayer_blueprints: ItemData(90049, False), - ItemName.blood_blueprints: ItemData(90050, False), - ItemName.sage_blueprints: ItemData(90051, False), - ItemName.retribution_blueprints: ItemData(90052, False), - ItemName.holy_blueprints: ItemData(90053, False), - ItemName.dark_blueprints: ItemData(90054, False), -} - -progressive_blueprint_table = { - ItemName.progressive_blueprints: ItemData(90055, False), -} - -runes_table = { - ItemName.vault_runes: ItemData(90060, False), - ItemName.sprint_runes: ItemData(90061, False), - ItemName.vampire_runes: ItemData(90062, False), - ItemName.sky_runes: ItemData(90063, False), - ItemName.siphon_runes: ItemData(90064, False), - ItemName.retaliation_runes: ItemData(90065, False), - ItemName.bounty_runes: ItemData(90066, False), - ItemName.haste_runes: ItemData(90067, False), - ItemName.curse_runes: ItemData(90068, False), - ItemName.grace_runes: ItemData(90069, False), - ItemName.balance_runes: ItemData(90070, False), -} - -misc_items_table = { - ItemName.trip_stat_increase: ItemData(90030, False), - ItemName.gold_1000: ItemData(90031, False), - ItemName.gold_3000: ItemData(90032, False), - ItemName.gold_5000: ItemData(90033, False), - # ItemName.rage_trap: ItemData(90034, False), -} - -# Complete item table. -item_table = { - **vendors_table, - **static_classes_table, - **progressive_classes_table, - **configurable_skill_unlocks_table, - **skill_unlocks_table, - **blueprints_table, - **progressive_blueprint_table, - **runes_table, - **misc_items_table, -} - -lookup_id_to_name: typing.Dict[int, str] = {data.code: item_name for item_name, data in item_table.items() if data.code} diff --git a/worlds/rogue_legacy/Locations.py b/worlds/rogue_legacy/Locations.py index ddbbecf0..2d4197e6 100644 --- a/worlds/rogue_legacy/Locations.py +++ b/worlds/rogue_legacy/Locations.py @@ -1,90 +1,98 @@ -import typing +from typing import Dict, NamedTuple, Optional from BaseClasses import Location -from .Names import LocationName -class LegacyLocation(Location): +class RLLocation(Location): game: str = "Rogue Legacy" -base_location_table = { - # Manor Renovations - LocationName.manor_ground_base: 91000, - LocationName.manor_main_base: 91001, - LocationName.manor_main_bottom_window: 91002, - LocationName.manor_main_top_window: 91003, - LocationName.manor_main_roof: 91004, - LocationName.manor_left_wing_base: 91005, - LocationName.manor_left_wing_window: 91006, - LocationName.manor_left_wing_roof: 91007, - LocationName.manor_left_big_base: 91008, - LocationName.manor_left_big_upper1: 91009, - LocationName.manor_left_big_upper2: 91010, - LocationName.manor_left_big_windows: 91011, - LocationName.manor_left_big_roof: 91012, - LocationName.manor_left_far_base: 91013, - LocationName.manor_left_far_roof: 91014, - LocationName.manor_left_extension: 91015, - LocationName.manor_left_tree1: 91016, - LocationName.manor_left_tree2: 91017, - LocationName.manor_right_wing_base: 91018, - LocationName.manor_right_wing_window: 91019, - LocationName.manor_right_wing_roof: 91020, - LocationName.manor_right_big_base: 91021, - LocationName.manor_right_big_upper: 91022, - LocationName.manor_right_big_roof: 91023, - LocationName.manor_right_high_base: 91024, - LocationName.manor_right_high_upper: 91025, - LocationName.manor_right_high_tower: 91026, - LocationName.manor_right_extension: 91027, - LocationName.manor_right_tree: 91028, - LocationName.manor_observatory_base: 91029, - LocationName.manor_observatory_scope: 91030, +class RLLocationData(NamedTuple): + category: str + code: Optional[int] = None + + @property + def is_event_location(self): + return self.code is None + + +def get_locations_by_category(category: str) -> Dict[str, RLLocationData]: + location_dict: Dict[str, RLLocationData] = {} + for name, data in location_table.items(): + if data.category == category: + location_dict.setdefault(name, data) + + return location_dict + + +location_table: Dict[str, RLLocationData] = { + # Manor Renovation + "Manor - Ground Road": RLLocationData("Manor", 91_000), + "Manor - Main Base": RLLocationData("Manor", 91_001), + "Manor - Main Bottom Window": RLLocationData("Manor", 91_002), + "Manor - Main Top Window": RLLocationData("Manor", 91_003), + "Manor - Main Rooftop": RLLocationData("Manor", 91_004), + "Manor - Left Wing Base": RLLocationData("Manor", 91_005), + "Manor - Left Wing Window": RLLocationData("Manor", 91_006), + "Manor - Left Wing Rooftop": RLLocationData("Manor", 91_007), + "Manor - Left Big Base": RLLocationData("Manor", 91_008), + "Manor - Left Big Upper 1": RLLocationData("Manor", 91_009), + "Manor - Left Big Upper 2": RLLocationData("Manor", 91_010), + "Manor - Left Big Windows": RLLocationData("Manor", 91_011), + "Manor - Left Big Rooftop": RLLocationData("Manor", 91_012), + "Manor - Left Far Base": RLLocationData("Manor", 91_013), + "Manor - Left Far Roof": RLLocationData("Manor", 91_014), + "Manor - Left Extension": RLLocationData("Manor", 91_015), + "Manor - Left Tree 1": RLLocationData("Manor", 91_016), + "Manor - Left Tree 2": RLLocationData("Manor", 91_017), + "Manor - Right Wing Base": RLLocationData("Manor", 91_018), + "Manor - Right Wing Window": RLLocationData("Manor", 91_019), + "Manor - Right Wing Rooftop": RLLocationData("Manor", 91_020), + "Manor - Right Big Base": RLLocationData("Manor", 91_021), + "Manor - Right Big Upper": RLLocationData("Manor", 91_022), + "Manor - Right Big Rooftop": RLLocationData("Manor", 91_023), + "Manor - Right High Base": RLLocationData("Manor", 91_024), + "Manor - Right High Upper": RLLocationData("Manor", 91_025), + "Manor - Right High Tower": RLLocationData("Manor", 91_026), + "Manor - Right Extension": RLLocationData("Manor", 91_027), + "Manor - Right Tree": RLLocationData("Manor", 91_028), + "Manor - Observatory Base": RLLocationData("Manor", 91_029), + "Manor - Observatory Telescope": RLLocationData("Manor", 91_030), # Boss Rewards - LocationName.boss_castle: 91100, - LocationName.boss_forest: 91102, - LocationName.boss_tower: 91104, - LocationName.boss_dungeon: 91106, - - # Special Rooms - LocationName.special_jukebox: 91200, - LocationName.special_painting: 91201, - LocationName.special_cheapskate: 91202, - LocationName.special_carnival: 91203, + "Castle Hamson Boss Reward": RLLocationData("Boss", 91_100), + "Forest Abkhazia Boss Reward": RLLocationData("Boss", 91_102), + "The Maya Boss Reward": RLLocationData("Boss", 91_104), + "Land of Darkness Boss Reward": RLLocationData("Boss", 91_106), # Special Locations - LocationName.castle: None, - LocationName.garden: None, - LocationName.tower: None, - LocationName.dungeon: None, - LocationName.fountain: None, + "Jukebox": RLLocationData("Special", 91_200), + "Painting": RLLocationData("Special", 91_201), + "Cheapskate Elf's Game": RLLocationData("Special", 91_202), + "Carnival": RLLocationData("Special", 91_203), + + # Diaries + **{f"Diary {i+1}": RLLocationData("Diary", 91_300 + i) for i in range(0, 25)}, + + # Chests + **{f"Castle Hamson - Chest {i+1}": RLLocationData("Chests", 91_600 + i) for i in range(0, 50)}, + **{f"Forest Abkhazia - Chest {i+1}": RLLocationData("Chests", 91_700 + i) for i in range(0, 50)}, + **{f"The Maya - Chest {i+1}": RLLocationData("Chests", 91_800 + i) for i in range(0, 50)}, + **{f"Land of Darkness - Chest {i+1}": RLLocationData("Chests", 91_900 + i) for i in range(0, 50)}, + **{f"Chest {i+1}": RLLocationData("Chests", 92_000 + i) for i in range(0, 200)}, + + # Fairy Chests + **{f"Castle Hamson - Fairy Chest {i+1}": RLLocationData("Fairies", 91_400 + i) for i in range(0, 15)}, + **{f"Forest Abkhazia - Fairy Chest {i+1}": RLLocationData("Fairies", 91_450 + i) for i in range(0, 15)}, + **{f"The Maya - Fairy Chest {i+1}": RLLocationData("Fairies", 91_500 + i) for i in range(0, 15)}, + **{f"Land of Darkness - Fairy Chest {i+1}": RLLocationData("Fairies", 91_550 + i) for i in range(0, 15)}, + **{f"Fairy Chest {i+1}": RLLocationData("Fairies", 92_200 + i) for i in range(0, 60)}, } -diary_location_table = {f"{LocationName.diary} {i + 1}": i + 91300 for i in range(0, 25)} - -fairy_chest_location_table = { - **{f"{LocationName.castle} - Fairy Chest {i + 1}": i + 91400 for i in range(0, 50)}, - **{f"{LocationName.garden} - Fairy Chest {i + 1}": i + 91450 for i in range(0, 50)}, - **{f"{LocationName.tower} - Fairy Chest {i + 1}": i + 91500 for i in range(0, 50)}, - **{f"{LocationName.dungeon} - Fairy Chest {i + 1}": i + 91550 for i in range(0, 50)}, - **{f"Fairy Chest {i + 1}": i + 92200 for i in range(0, 60)}, +event_location_table: Dict[str, RLLocationData] = { + "Castle Hamson Boss Room": RLLocationData("Event"), + "Forest Abkhazia Boss Room": RLLocationData("Event"), + "The Maya Boss Room": RLLocationData("Event"), + "Land of Darkness Boss Room": RLLocationData("Event"), + "Fountain Room": RLLocationData("Event"), } - -chest_location_table = { - **{f"{LocationName.castle} - Chest {i + 1}": i + 91600 for i in range(0, 100)}, - **{f"{LocationName.garden} - Chest {i + 1}": i + 91700 for i in range(0, 100)}, - **{f"{LocationName.tower} - Chest {i + 1}": i + 91800 for i in range(0, 100)}, - **{f"{LocationName.dungeon} - Chest {i + 1}": i + 91900 for i in range(0, 100)}, - **{f"Chest {i + 1}": i + 92000 for i in range(0, 120)}, -} - -location_table = { - **base_location_table, - **diary_location_table, - **fairy_chest_location_table, - **chest_location_table, -} - -lookup_id_to_name: typing.Dict[int, str] = {id: name for name, _ in location_table.items()} diff --git a/worlds/rogue_legacy/Names/ItemName.py b/worlds/rogue_legacy/Names/ItemName.py deleted file mode 100644 index 7532ac19..00000000 --- a/worlds/rogue_legacy/Names/ItemName.py +++ /dev/null @@ -1,97 +0,0 @@ -# Vendor Definitions -blacksmith = "Blacksmith" -enchantress = "Enchantress" -architect = "Architect" - -# Progressive Class Definitions -progressive_knight = "Progressive Knights" -progressive_mage = "Progressive Mages" -progressive_barbarian = "Progressive Barbarians" -progressive_knave = "Progressive Knaves" -progressive_shinobi = "Progressive Shinobis" -progressive_miner = "Progressive Miners" -progressive_lich = "Progressive Liches" -progressive_spellthief = "Progressive Spellthieves" - -# Static Class Definitions -knight = "Knights" -paladin = "Paladins" -mage = "Mages" -archmage = "Archmages" -barbarian = "Barbarians" -barbarian_king = "Barbarian Kings" -knave = "Knaves" -assassin = "Assassins" -shinobi = "Shinobis" -hokage = "Hokages" -miner = "Miners" -spelunker = "Spelunkers" -lich = "Lichs" -lich_king = "Lich Kings" -spellthief = "Spellthieves" -spellsword = "Spellswords" -dragon = "Dragons" -traitor = "Traitors" - -# Skill Unlock Definitions -health = "Health Up" -mana = "Mana Up" -attack = "Attack Up" -magic_damage = "Magic Damage Up" -armor = "Armor Up" -equip = "Equip Up" -crit_chance = "Crit Chance Up" -crit_damage = "Crit Damage Up" -down_strike = "Down Strike Up" -gold_gain = "Gold Gain Up" -potion_efficiency = "Potion Efficiency Up" -invulnerability_time = "Invulnerability Time Up" -mana_cost_down = "Mana Cost Down" -death_defiance = "Death Defiance" -haggling = "Haggling" -random_children = "Randomize Children" - -# Misc. Definitions -trip_stat_increase = "Triple Stat Increase" -gold_1000 = "1000 Gold" -gold_3000 = "3000 Gold" -gold_5000 = "5000 Gold" -rage_trap = "Rage Trap" - -# Blueprint Definitions -progressive_blueprints = "Progressive Blueprints" -squire_blueprints = "Squire Blueprints" -silver_blueprints = "Silver Blueprints" -guardian_blueprints = "Guardian Blueprints" -imperial_blueprints = "Imperial Blueprints" -royal_blueprints = "Royal Blueprints" -knight_blueprints = "Knight Blueprints" -ranger_blueprints = "Ranger Blueprints" -sky_blueprints = "Sky Blueprints" -dragon_blueprints = "Dragon Blueprints" -slayer_blueprints = "Slayer Blueprints" -blood_blueprints = "Blood Blueprints" -sage_blueprints = "Sage Blueprints" -retribution_blueprints = "Retribution Blueprints" -holy_blueprints = "Holy Blueprints" -dark_blueprints = "Dark Blueprints" - -# Rune Definitions -vault_runes = "Vault Runes" -sprint_runes = "Sprint Runes" -vampire_runes = "Vampire Runes" -sky_runes = "Sky Runes" -siphon_runes = "Siphon Runes" -retaliation_runes = "Retaliation Runes" -bounty_runes = "Bounty Runes" -haste_runes = "Haste Runes" -curse_runes = "Curse Runes" -grace_runes = "Grace Runes" -balance_runes = "Balance Runes" - -# Event Definitions -boss_castle = "Defeat Castle Hamson Boss" -boss_forest = "Defeat Forest Abkhazia Boss" -boss_tower = "Defeat The Maya Boss" -boss_dungeon = "Defeat The Land of Darkness Boss" -boss_fountain = "Defeat The Fountain" diff --git a/worlds/rogue_legacy/Names/LocationName.py b/worlds/rogue_legacy/Names/LocationName.py deleted file mode 100644 index e4732f9f..00000000 --- a/worlds/rogue_legacy/Names/LocationName.py +++ /dev/null @@ -1,55 +0,0 @@ -# Manor Piece Definitions -manor_ground_base = "Manor Renovation - Ground Road" -manor_main_base = "Manor Renovation - Main Base" -manor_main_bottom_window = "Manor Renovation - Main Bottom Window" -manor_main_top_window = "Manor Renovation - Main Top Window" -manor_main_roof = "Manor Renovation - Main Rooftop" -manor_left_wing_base = "Manor Renovation - Left Wing Base" -manor_left_wing_window = "Manor Renovation - Left Wing Window" -manor_left_wing_roof = "Manor Renovation - Left Wing Rooftop" -manor_left_big_base = "Manor Renovation - Left Big Base" -manor_left_big_upper1 = "Manor Renovation - Left Big Upper 1" -manor_left_big_upper2 = "Manor Renovation - Left Big Upper 2" -manor_left_big_windows = "Manor Renovation - Left Big Windows" -manor_left_big_roof = "Manor Renovation - Left Big Rooftop" -manor_left_far_base = "Manor Renovation - Left Far Base" -manor_left_far_roof = "Manor Renovation - Left Far Roof" -manor_left_extension = "Manor Renovation - Left Extension" -manor_left_tree1 = "Manor Renovation - Left Tree 1" -manor_left_tree2 = "Manor Renovation - Left Tree 2" -manor_right_wing_base = "Manor Renovation - Right Wing Base" -manor_right_wing_window = "Manor Renovation - Right Wing Window" -manor_right_wing_roof = "Manor Renovation - Right Wing Rooftop" -manor_right_big_base = "Manor Renovation - Right Big Base" -manor_right_big_upper = "Manor Renovation - Right Big Upper" -manor_right_big_roof = "Manor Renovation - Right Big Rooftop" -manor_right_high_base = "Manor Renovation - Right High Base" -manor_right_high_upper = "Manor Renovation - Right High Upper" -manor_right_high_tower = "Manor Renovation - Right High Tower" -manor_right_extension = "Manor Renovation - Right Extension" -manor_right_tree = "Manor Renovation - Right Tree" -manor_observatory_base = "Manor Renovation - Observatory Base" -manor_observatory_scope = "Manor Renovation - Observatory Telescope" - -# Boss Chest Definitions -boss_castle = "Castle Hamson Boss" -boss_forest = "Forest Abkhazia Boss" -boss_tower = "The Maya Boss" -boss_dungeon = "The Land of Darkness Boss" - -# Special Room Definitions -special_jukebox = "Jukebox" -special_painting = "Painting" -special_cheapskate = "Cheapskate Elf's Game" -special_carnival = "Carnival" - -# Shorthand Definitions -diary = "Diary" - -# Region Definitions -outside = "Outside Castle Hamson" -castle = "Castle Hamson" -garden = "Forest Abkhazia" -tower = "The Maya" -dungeon = "The Land of Darkness" -fountain = "Fountain Room" diff --git a/worlds/rogue_legacy/Names/__init__.py b/worlds/rogue_legacy/Names/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/worlds/rogue_legacy/Options.py b/worlds/rogue_legacy/Options.py index 13c98284..d8298c85 100644 --- a/worlds/rogue_legacy/Options.py +++ b/worlds/rogue_legacy/Options.py @@ -1,6 +1,6 @@ -import typing +from typing import Dict -from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList, OptionSet +from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionSet class StartingGender(Choice): @@ -63,9 +63,9 @@ class FairyChestsPerZone(Range): bonuses can be found in Fairy Chests. """ display_name = "Fairy Chests Per Zone" - range_start = 5 + range_start = 0 range_end = 15 - default = 5 + default = 1 class ChestsPerZone(Range): @@ -74,9 +74,9 @@ class ChestsPerZone(Range): gold or stat bonuses can be found in Chests. """ display_name = "Chests Per Zone" - range_start = 15 - range_end = 30 - default = 15 + range_start = 20 + range_end = 50 + default = 20 class UniversalFairyChests(Toggle): @@ -111,8 +111,10 @@ class Architect(Choice): """ display_name = "Architect" option_start_unlocked = 0 - option_normal = 2 + option_early = 1 + option_anywhere = 2 option_disabled = 3 + alias_normal = 2 default = 2 @@ -173,7 +175,7 @@ class NumberOfChildren(Range): default = 3 -class AdditionalNames(OptionList): +class AdditionalNames(OptionSet): """ Set of additional names your potential offspring can have. If Allow Default Names is disabled, this is the only list of names your children can have. The first value will also be your initial character's name depending on Starting @@ -337,7 +339,8 @@ class AvailableClasses(OptionSet): default = {"Knight", "Mage", "Barbarian", "Knave", "Shinobi", "Miner", "Spellthief", "Lich", "Dragon", "Traitor"} valid_keys = {"Knight", "Mage", "Barbarian", "Knave", "Shinobi", "Miner", "Spellthief", "Lich", "Dragon", "Traitor"} -legacy_options: typing.Dict[str, type(Option)] = { + +rl_options: Dict[str, type(Option)] = { "starting_gender": StartingGender, "starting_class": StartingClass, "available_classes": AvailableClasses, diff --git a/worlds/rogue_legacy/Regions.py b/worlds/rogue_legacy/Regions.py index 5931bf28..3e078418 100644 --- a/worlds/rogue_legacy/Regions.py +++ b/worlds/rogue_legacy/Regions.py @@ -1,72 +1,125 @@ -import typing +from typing import Dict, List, NamedTuple, Optional -from BaseClasses import MultiWorld, Region, RegionType, Entrance, ItemClassification -from .Items import LegacyItem -from .Locations import LegacyLocation, diary_location_table, location_table, base_location_table -from .Names import LocationName, ItemName - -prog = ItemClassification.progression +from BaseClasses import MultiWorld, Region, RegionType, Entrance +from .Items import RLItem +from .Locations import RLLocation, location_table, get_locations_by_category -def create_regions(world, player: int): +class RLRegionData(NamedTuple): + locations: Optional[List[str]] + exits: Optional[List[str]] - locations: typing.List[str] = [] - # Add required locations. - locations += [location for location in base_location_table] - locations += [location for location in diary_location_table] +def create_regions(world: MultiWorld, player: int): + regions: Dict[str, RLRegionData] = { + "Menu": RLRegionData(None, ["Castle Hamson"]), + "The Manor": RLRegionData([], []), + "Castle Hamson": RLRegionData([], ["Forest Abkhazia", + "The Maya", + "Land of Darkness", + "The Fountain Room", + "The Manor"]), + "Forest Abkhazia": RLRegionData([], []), + "The Maya": RLRegionData([], []), + "Land of Darkness": RLRegionData([], []), + "The Fountain Room": RLRegionData([], None), + } - # Add chests per settings. - if world.universal_fairy_chests[player]: - fairies = int(world.fairy_chests_per_zone[player]) * 4 - for i in range(0, fairies): - locations += [f"Fairy Chest {i + 1}"] - else: - fairies = int(world.fairy_chests_per_zone[player]) - for i in range(0, fairies): - locations += [f"{LocationName.castle} - Fairy Chest {i + 1}"] - locations += [f"{LocationName.garden} - Fairy Chest {i + 1}"] - locations += [f"{LocationName.tower} - Fairy Chest {i + 1}"] - locations += [f"{LocationName.dungeon} - Fairy Chest {i + 1}"] + # Diaries + for diary in range(0, 25): + region: str + if 0 <= diary < 6: + region = "Castle Hamson" + elif 6 <= diary < 12: + region = "Forest Abkhazia" + elif 12 <= diary < 18: + region = "The Maya" + elif 18 <= diary < 24: + region = "Land of Darkness" + else: + region = "The Fountain Room" - if world.universal_chests[player]: - chests = int(world.chests_per_zone[player]) * 4 - for i in range(0, chests): - locations += [f"Chest {i + 1}"] - else: - chests = int(world.chests_per_zone[player]) - for i in range(0, chests): - locations += [f"{LocationName.castle} - Chest {i + 1}"] - locations += [f"{LocationName.garden} - Chest {i + 1}"] - locations += [f"{LocationName.tower} - Chest {i + 1}"] - locations += [f"{LocationName.dungeon} - Chest {i + 1}"] + regions[region].locations.append(f"Diary {diary + 1}") + + # Manor & Special + for manor in get_locations_by_category("Manor").keys(): + regions["The Manor"].locations.append(manor) + for special in get_locations_by_category("Special").keys(): + regions["Castle Hamson"].locations.append(special) + + # Boss Rewards + regions["Castle Hamson"].locations.append("Castle Hamson Boss Reward") + regions["Forest Abkhazia"].locations.append("Forest Abkhazia Boss Reward") + regions["The Maya"].locations.append("The Maya Boss Reward") + regions["Land of Darkness"].locations.append("Land of Darkness Boss Reward") + + # Events + regions["Castle Hamson"].locations.append("Castle Hamson Boss Room") + regions["Forest Abkhazia"].locations.append("Forest Abkhazia Boss Room") + regions["The Maya"].locations.append("The Maya Boss Room") + regions["Land of Darkness"].locations.append("Land of Darkness Boss Room") + regions["The Fountain Room"].locations.append("Fountain Room") + + # Chests + chests = int(world.chests_per_zone[player]) + for i in range(0, chests): + if world.universal_chests[player]: + regions["Castle Hamson"].locations.append(f"Chest {i + 1}") + regions["Forest Abkhazia"].locations.append(f"Chest {i + 1 + chests}") + regions["The Maya"].locations.append(f"Chest {i + 1 + (chests * 2)}") + regions["Land of Darkness"].locations.append(f"Chest {i + 1 + (chests * 3)}") + else: + regions["Castle Hamson"].locations.append(f"Castle Hamson - Chest {i + 1}") + regions["Forest Abkhazia"].locations.append(f"Forest Abkhazia - Chest {i + 1}") + regions["The Maya"].locations.append(f"The Maya - Chest {i + 1}") + regions["Land of Darkness"].locations.append(f"Land of Darkness - Chest {i + 1}") + + # Fairy Chests + chests = int(world.fairy_chests_per_zone[player]) + for i in range(0, chests): + if world.universal_fairy_chests[player]: + regions["Castle Hamson"].locations.append(f"Fairy Chest {i + 1}") + regions["Forest Abkhazia"].locations.append(f"Fairy Chest {i + 1 + chests}") + regions["The Maya"].locations.append(f"Fairy Chest {i + 1 + (chests * 2)}") + regions["Land of Darkness"].locations.append(f"Fairy Chest {i + 1 + (chests * 3)}") + else: + regions["Castle Hamson"].locations.append(f"Castle Hamson - Fairy Chest {i + 1}") + regions["Forest Abkhazia"].locations.append(f"Forest Abkhazia - Fairy Chest {i + 1}") + regions["The Maya"].locations.append(f"The Maya - Fairy Chest {i + 1}") + regions["Land of Darkness"].locations.append(f"Land of Darkness - Fairy Chest {i + 1}") # Set up the regions correctly. - world.regions += [ - create_region(world, player, "Menu", None, [LocationName.outside]), - create_region(world, player, LocationName.castle, locations), - ] + for name, data in regions.items(): + world.regions.append(create_region(world, player, name, data.locations, data.exits)) - # Connect entrances and set up events. - world.get_entrance(LocationName.outside, player).connect(world.get_region(LocationName.castle, player)) - world.get_location(LocationName.castle, player).place_locked_item(LegacyItem(ItemName.boss_castle, prog, None, player)) - world.get_location(LocationName.garden, player).place_locked_item(LegacyItem(ItemName.boss_forest, prog, None, player)) - world.get_location(LocationName.tower, player).place_locked_item(LegacyItem(ItemName.boss_tower, prog, None, player)) - world.get_location(LocationName.dungeon, player).place_locked_item(LegacyItem(ItemName.boss_dungeon, prog, None, player)) - world.get_location(LocationName.fountain, player).place_locked_item(LegacyItem(ItemName.boss_fountain, prog, None, player)) + world.get_entrance("Castle Hamson", player).connect(world.get_region("Castle Hamson", player)) + world.get_entrance("The Manor", player).connect(world.get_region("The Manor", player)) + world.get_entrance("Forest Abkhazia", player).connect(world.get_region("Forest Abkhazia", player)) + world.get_entrance("The Maya", player).connect(world.get_region("The Maya", player)) + world.get_entrance("Land of Darkness", player).connect(world.get_region("Land of Darkness", player)) + world.get_entrance("The Fountain Room", player).connect(world.get_region("The Fountain Room", player)) def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None): - # Shamelessly stolen from the ROR2 definition, lol ret = Region(name, RegionType.Generic, name, player) ret.world = world if locations: - for location in locations: - loc_id = location_table.get(location, 0) - location = LegacyLocation(player, location, loc_id, ret) + for loc_name in locations: + loc_data = location_table.get(loc_name) + location = RLLocation(player, loc_name, loc_data.code if loc_data else None, ret) + + # Special rule handling for fairy chests. + if "Fairy" in loc_name: + location.access_rule = lambda state: state.has("Dragons", player) or ( + state.has("Enchantress", player) and ( + state.has("Vault Runes", player) or + state.has("Sprint Runes", player) or + state.has("Sky Runes", player))) ret.locations.append(location) if exits: for exit in exits: - ret.exits.append(Entrance(player, exit, ret)) - + entrance = Entrance(player, exit, ret) + ret.exits.append(entrance) return ret + + diff --git a/worlds/rogue_legacy/Rules.py b/worlds/rogue_legacy/Rules.py index 6d2cb4cd..de328774 100644 --- a/worlds/rogue_legacy/Rules.py +++ b/worlds/rogue_legacy/Rules.py @@ -1,177 +1,86 @@ -from BaseClasses import MultiWorld -from .Names import LocationName, ItemName +from BaseClasses import MultiWorld, CollectionState + from ..AutoWorld import LogicMixin from ..generic.Rules import set_rule class LegacyLogic(LogicMixin): - def _legacy_has_any_vendors(self, player: int) -> bool: - return self.has_any({ItemName.blacksmith, ItemName.enchantress}, player) + def has_any_vendors(self: CollectionState, player: int) -> bool: + return self.has_any({"Blacksmith", "Enchantress"}, player) - def _legacy_has_all_vendors(self, player: int) -> bool: - return self.has_all({ItemName.blacksmith, ItemName.enchantress}, player) + def has_all_vendors(self: CollectionState, player: int) -> bool: + return self.has_all({"Blacksmith", "Enchantress"}, player) - def _legacy_has_stat_upgrades(self, player: int, amount: int) -> bool: - return self._legacy_stat_upgrade_count(player) >= amount + def has_stat_upgrades(self, player: int, amount: int) -> bool: + return self.stat_upgrade_count(player) >= amount - def _legacy_total_stat_upgrades_count(self, player: int) -> int: + def total_stat_upgrades_count(self, player: int) -> int: return int(self.world.health_pool[player]) + \ int(self.world.mana_pool[player]) + \ int(self.world.attack_pool[player]) + \ - int(self.world.magic_damage_pool[player]) + \ - int(self.world.armor_pool[player]) + \ - int(self.world.equip_pool[player]) + int(self.world.magic_damage_pool[player]) - def _legacy_stat_upgrade_count(self, player: int) -> int: - return self.item_count(ItemName.health, player) + self.item_count(ItemName.mana, player) + \ - self.item_count(ItemName.attack, player) + self.item_count(ItemName.magic_damage, player) + \ - self.item_count(ItemName.armor, player) + self.item_count(ItemName.equip, player) + def stat_upgrade_count(self: CollectionState, player: int) -> int: + return self.item_count("Health Up", player) + self.item_count("Mana Up", player) + \ + self.item_count("Attack Up", player) + self.item_count("Magic Damage Up", player) def set_rules(world: MultiWorld, player: int): - # Check for duplicate names. - if len(set(world.additional_lady_names[player].value)) != len(world.additional_lady_names[player].value): - raise Exception(f"Duplicate values are not allowed in additional_lady_names.") - if len(set(world.additional_sir_names[player].value)) != len(world.additional_sir_names[player].value): - raise Exception(f"Duplicate values are not allowed in additional_sir_names.") - - if not world.allow_default_names[player]: - # Check for quantity. - name_count = len(world.additional_lady_names[player].value) - if name_count < int(world.number_of_children[player]): - raise Exception(f"allow_default_names is off, but not enough names are defined in additional_lady_names. Expected {int(world.number_of_children[player])}, Got {name_count}") - - name_count = len(world.additional_sir_names[player].value) - if name_count < int(world.number_of_children[player]): - raise Exception(f"allow_default_names is off, but not enough names are defined in additional_sir_names. Expected {int(world.number_of_children[player])}, Got {name_count}") - - # Chests - if world.universal_chests[player]: - for i in range(0, world.chests_per_zone[player]): - set_rule(world.get_location(f"Chest {i + 1 + (world.chests_per_zone[player] * 1)}", player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(f"Chest {i + 1 + (world.chests_per_zone[player] * 2)}", player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(f"Chest {i + 1 + (world.chests_per_zone[player] * 3)}", player), - lambda state: state.has(ItemName.boss_tower, player)) - else: - for i in range(0, world.chests_per_zone[player]): - set_rule(world.get_location(f"{LocationName.garden} - Chest {i + 1}", player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(f"{LocationName.tower} - Chest {i + 1}", player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(f"{LocationName.dungeon} - Chest {i + 1}", player), - lambda state: state.has(ItemName.boss_tower, player)) - - # Fairy Chests - if world.universal_fairy_chests[player]: - for i in range(0, world.fairy_chests_per_zone[player]): - set_rule(world.get_location(f"Fairy Chest {i + 1 + (world.fairy_chests_per_zone[player] * 1)}", player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(f"Fairy Chest {i + 1 + (world.fairy_chests_per_zone[player] * 2)}", player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(f"Fairy Chest {i + 1 + (world.fairy_chests_per_zone[player] * 3)}", player), - lambda state: state.has(ItemName.boss_tower, player)) - else: - for i in range(0, world.fairy_chests_per_zone[player]): - set_rule(world.get_location(f"{LocationName.garden} - Fairy Chest {i + 1}", player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(f"{LocationName.tower} - Fairy Chest {i + 1}", player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(f"{LocationName.dungeon} - Fairy Chest {i + 1}", player), - lambda state: state.has(ItemName.boss_tower, player)) - # Vendors - if world.vendors[player] == "early": - set_rule(world.get_location(LocationName.boss_castle, player), - lambda state: state._legacy_has_all_vendors(player)) - elif world.vendors[player] == "normal": - set_rule(world.get_location(LocationName.garden, player), - lambda state: state._legacy_has_any_vendors(player)) - - # Diaries - for i in range(0, 5): - set_rule(world.get_location(f"Diary {i + 6}", player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(f"Diary {i + 11}", player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(f"Diary {i + 16}", player), - lambda state: state.has(ItemName.boss_tower, player)) - set_rule(world.get_location(f"Diary {i + 21}", player), - lambda state: state.has(ItemName.boss_dungeon, player)) + if world.vendors[player] == "normal": + set_rule(world.get_location("Forest Abkhazia Boss Reward", player), + lambda state: state.has_all_vendors(player)) # Scale each manor location. - set_rule(world.get_location(LocationName.manor_left_wing_window, player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(LocationName.manor_left_wing_roof, player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(LocationName.manor_right_wing_window, player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(LocationName.manor_right_wing_roof, player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(LocationName.manor_left_big_base, player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(LocationName.manor_right_big_base, player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(LocationName.manor_left_tree1, player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(LocationName.manor_left_tree2, player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(LocationName.manor_right_tree, player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(LocationName.manor_left_big_upper1, player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(LocationName.manor_left_big_upper2, player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(LocationName.manor_left_big_windows, player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(LocationName.manor_left_big_roof, player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(LocationName.manor_left_far_base, player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(LocationName.manor_left_far_roof, player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(LocationName.manor_left_extension, player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(LocationName.manor_right_big_upper, player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(LocationName.manor_right_big_roof, player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(LocationName.manor_right_extension, player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(LocationName.manor_right_high_base, player), - lambda state: state.has(ItemName.boss_tower, player)) - set_rule(world.get_location(LocationName.manor_right_high_upper, player), - lambda state: state.has(ItemName.boss_tower, player)) - set_rule(world.get_location(LocationName.manor_right_high_tower, player), - lambda state: state.has(ItemName.boss_tower, player)) - set_rule(world.get_location(LocationName.manor_observatory_base, player), - lambda state: state.has(ItemName.boss_tower, player)) - set_rule(world.get_location(LocationName.manor_observatory_scope, player), - lambda state: state.has(ItemName.boss_tower, player)) + manor_rules = { + "Defeat Khidr" if world.khidr[player] == "vanilla" else "Defeat Neo Khidr": [ + "Manor - Left Wing Window", + "Manor - Left Wing Rooftop", + "Manor - Right Wing Window", + "Manor - Right Wing Rooftop", + "Manor - Left Big Base", + "Manor - Right Big Base", + "Manor - Left Tree 1", + "Manor - Left Tree 2", + "Manor - Right Tree", + ], + "Defeat Alexander" if world.alexander[player] == "vanilla" else "Defeat Alexander IV": [ + "Manor - Left Big Upper 1", + "Manor - Left Big Upper 2", + "Manor - Left Big Windows", + "Manor - Left Big Rooftop", + "Manor - Left Far Base", + "Manor - Left Far Roof", + "Manor - Left Extension", + "Manor - Right Big Upper", + "Manor - Right Big Rooftop", + "Manor - Right Extension", + ], + "Defeat Ponce de Leon" if world.leon[player] == "vanilla" else "Defeat Ponce de Freon": [ + "Manor - Right High Base", + "Manor - Right High Upper", + "Manor - Right High Tower", + "Manor - Observatory Base", + "Manor - Observatory Telescope", + ] + } + + for event, locations in manor_rules.items(): + for location in locations: + set_rule(world.get_location(location, player), lambda state: state.has(event, player)) # Standard Zone Progression - set_rule(world.get_location(LocationName.garden, player), - lambda state: state._legacy_has_stat_upgrades(player, 0.125 * state._legacy_total_stat_upgrades_count(player)) and state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(LocationName.tower, player), - lambda state: state._legacy_has_stat_upgrades(player, 0.3125 * state._legacy_total_stat_upgrades_count(player)) and state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(LocationName.dungeon, player), - lambda state: state._legacy_has_stat_upgrades(player, 0.5 * state._legacy_total_stat_upgrades_count(player)) and state.has(ItemName.boss_tower, player)) + world.get_entrance("Forest Abkhazia", player).access_rule = \ + (lambda state: state.has_stat_upgrades(player, 0.125 * state.total_stat_upgrades_count(player)) and + (state.has("Defeat Khidr", player) or state.has("Defeat Neo Khidr", player))) + world.get_entrance("The Maya", player).access_rule = \ + (lambda state: state.has_stat_upgrades(player, 0.25 * state.total_stat_upgrades_count(player)) and + (state.has("Defeat Alexander", player) or state.has("Defeat Alexander IV", player))) + world.get_entrance("Land of Darkness", player).access_rule = \ + (lambda state: state.has_stat_upgrades(player, 0.375 * state.total_stat_upgrades_count(player)) and + (state.has("Defeat Ponce de Leon", player) or state.has("Defeat Ponce de Freon", player))) + world.get_entrance("The Fountain Room", player).access_rule = \ + (lambda state: state.has_stat_upgrades(player, 0.5 * state.total_stat_upgrades_count(player)) and + (state.has("Defeat Herodotus", player) or state.has("Defeat Astrodotus", player))) - # Bosses - set_rule(world.get_location(LocationName.boss_castle, player), - lambda state: state.has(ItemName.boss_castle, player)) - set_rule(world.get_location(LocationName.boss_forest, player), - lambda state: state.has(ItemName.boss_forest, player)) - set_rule(world.get_location(LocationName.boss_tower, player), - lambda state: state.has(ItemName.boss_tower, player)) - set_rule(world.get_location(LocationName.boss_dungeon, player), - lambda state: state.has(ItemName.boss_dungeon, player)) - set_rule(world.get_location(LocationName.fountain, player), - lambda state: state._legacy_has_stat_upgrades(player, 0.625 * state._legacy_total_stat_upgrades_count(player)) - and state.has(ItemName.boss_castle, player) - and state.has(ItemName.boss_forest, player) - and state.has(ItemName.boss_tower, player) - and state.has(ItemName.boss_dungeon, player)) - - world.completion_condition[player] = lambda state: state.has(ItemName.boss_fountain, player) + world.completion_condition[player] = lambda state: state.has("Defeat The Fountain", player) diff --git a/worlds/rogue_legacy/Traits.py b/worlds/rogue_legacy/Traits.py index 5959891d..19a67975 100644 --- a/worlds/rogue_legacy/Traits.py +++ b/worlds/rogue_legacy/Traits.py @@ -36,4 +36,3 @@ traits = [ "Glaucoma", "Adopted", ] - diff --git a/worlds/rogue_legacy/__init__.py b/worlds/rogue_legacy/__init__.py index 81e62027..95c1c1c6 100644 --- a/worlds/rogue_legacy/__init__.py +++ b/worlds/rogue_legacy/__init__.py @@ -1,176 +1,265 @@ -import typing +from typing import List -from BaseClasses import Item, ItemClassification, Tutorial -from .Items import LegacyItem, ItemData, item_table, vendors_table, static_classes_table, progressive_classes_table, \ - skill_unlocks_table, blueprints_table, runes_table, misc_items_table -from .Locations import LegacyLocation, location_table, base_location_table -from .Options import legacy_options +from BaseClasses import Tutorial +from .Items import RLItem, RLItemData, event_item_table, item_table, get_items_by_category +from .Locations import RLLocation, location_table +from .Options import rl_options from .Regions import create_regions from .Rules import set_rules -from .Names import ItemName from ..AutoWorld import World, WebWorld -class LegacyWeb(WebWorld): +class RLWeb(WebWorld): theme = "stone" tutorials = [Tutorial( "Multiworld Setup Guide", - "A guide to setting up the Rogue Legacy Randomizer software on your computer. This guide covers single-player, multiworld, and related software.", + "A guide to setting up the Rogue Legacy Randomizer software on your computer. This guide covers single-player, " + "multiworld, and related software.", "English", "rogue-legacy_en.md", "rogue-legacy/en", ["Phar"] )] + bug_report_page = "https://github.com/ThePhar/RogueLegacyRandomizer/issues/new?assignees=&labels=bug&template=" \ + "report-an-issue---.md&title=%5BIssue%5D" -class LegacyWorld(World): +class RLWorld(World): """ Rogue Legacy is a genealogical rogue-"LITE" where anyone can be a hero. Each time you die, your child will succeed you. Every child is unique. One child might be colorblind, another might have vertigo-- they could even be a dwarf. But that's OK, because no one is perfect, and you don't have to be to succeed. """ game: str = "Rogue Legacy" - option_definitions = legacy_options - topology_present = False - data_version = 3 - required_client_version = (0, 2, 3) - web = LegacyWeb() + option_definitions = rl_options + topology_present = True + data_version = 4 + required_client_version = (0, 3, 5) + web = RLWeb() item_name_to_id = {name: data.code for name, data in item_table.items()} - location_name_to_id = location_table + location_name_to_id = {name: data.code for name, data in location_table.items()} - def _get_slot_data(self): - return { - "starting_gender": self.world.starting_gender[self.player], - "starting_class": self.world.starting_class[self.player], - "new_game_plus": self.world.new_game_plus[self.player], - "fairy_chests_per_zone": self.world.fairy_chests_per_zone[self.player], - "chests_per_zone": self.world.chests_per_zone[self.player], - "universal_fairy_chests": self.world.universal_fairy_chests[self.player], - "universal_chests": self.world.universal_chests[self.player], - "vendors": self.world.vendors[self.player], - "architect_fee": self.world.architect_fee[self.player], - "disable_charon": self.world.disable_charon[self.player], - "require_purchasing": self.world.require_purchasing[self.player], - "gold_gain_multiplier": self.world.gold_gain_multiplier[self.player], - "number_of_children": self.world.number_of_children[self.player], - "khidr": self.world.khidr[self.player], - "alexander": self.world.alexander[self.player], - "leon": self.world.leon[self.player], - "herodotus": self.world.herodotus[self.player], - "allow_default_names": self.world.allow_default_names[self.player], - "additional_sir_names": self.world.additional_sir_names[self.player], - "additional_lady_names": self.world.additional_lady_names[self.player], - "death_link": self.world.death_link[self.player], - } + item_pool: List[RLItem] = [] + prefill_items: List[RLItem] = [] - def _create_items(self, name: str): - data = item_table[name] - return [self.create_item(name) for _ in range(data.quantity)] + def setting(self, name: str): + return getattr(self.world, name)[self.player] def fill_slot_data(self) -> dict: - slot_data = self._get_slot_data() - for option_name in legacy_options: - option = getattr(self.world, option_name)[self.player] - slot_data[option_name] = option.value + return {option_name: self.setting(option_name).value for option_name in rl_options} - return slot_data + def generate_early(self): + self.prefill_items = [] + + # Check validation of names. + additional_lady_names = len(self.setting("additional_lady_names").value) + additional_sir_names = len(self.setting("additional_sir_names").value) + if not self.setting("allow_default_names"): + # Check for max_quantity. + if additional_lady_names < int(self.setting("number_of_children")): + raise Exception( + f"allow_default_names is off, but not enough names are defined in additional_lady_names. " + f"Expected {int(self.setting('number_of_children'))}, Got {additional_lady_names}") + + if additional_sir_names < int(self.setting("number_of_children")): + raise Exception( + f"allow_default_names is off, but not enough names are defined in additional_sir_names. " + f"Expected {int(self.setting('number_of_children'))}, Got {additional_sir_names}") + + if self.setting("vendors") == "early": + self.prefill_items += [self.create_item("Blacksmith"), self.create_item("Enchantress")] + + if self.setting("architect") == "early": + self.prefill_items += [self.create_item("Architect")] def generate_basic(self): - itempool: typing.List[LegacyItem] = [] - total_required_locations = 64 + (self.world.chests_per_zone[self.player] * 4) + (self.world.fairy_chests_per_zone[self.player] * 4) + self.item_pool = [] + total_locations = 64 + (self.setting("chests_per_zone") * 4) + (self.setting("fairy_chests_per_zone") * 4) - # Fill item pool with all required items - for item in {**skill_unlocks_table, **runes_table}: - # if Haggling, do not add if Disable Charon. - if item == ItemName.haggling and self.world.disable_charon[self.player] == 1: + # Add items to item pool. Anything with a "continue" will not be added to the item pool. + for name, data in item_table.items(): + quantity = data.max_quantity + + # Architect + if name == "Architect": + if self.setting("architect") == "disabled" or self.setting("architect") == "early": + continue + if self.setting("architect") == "start_unlocked": + self.world.push_precollected(self.create_item(name)) + continue + + # Blacksmith and Enchantress + if name == "Blacksmith" or name == "Enchantress": + if self.setting("vendors") == "start_unlocked": + self.world.push_precollected(self.create_item(name)) + continue + if self.setting("vendors") == "early": + continue + + # Haggling + if name == "Haggling" and self.setting("disable_charon"): continue - itempool += self._create_items(item) - # Blueprints - if self.world.progressive_blueprints[self.player]: - itempool += [self.create_item(ItemName.progressive_blueprints) for _ in range(15)] - else: - for item in blueprints_table: - itempool += self._create_items(item) + # Blueprints + if data.category == "Blueprints": + # No progressive blueprints if progressive_blueprints are disabled. + if name == "Progressive Blueprints" and not self.setting("progressive_blueprints"): + continue + # No distinct blueprints if progressive_blueprints are enabled. + elif name != "Progressive Blueprints" and self.setting("progressive_blueprints"): + continue - # Check Pool settings to add a certain amount of these items. - itempool += [self.create_item(ItemName.health) for _ in range(self.world.health_pool[self.player])] - itempool += [self.create_item(ItemName.mana) for _ in range(self.world.mana_pool[self.player])] - itempool += [self.create_item(ItemName.attack) for _ in range(self.world.attack_pool[self.player])] - itempool += [self.create_item(ItemName.magic_damage) for _ in range(self.world.magic_damage_pool[self.player])] - itempool += [self.create_item(ItemName.armor) for _ in range(self.world.armor_pool[self.player])] - itempool += [self.create_item(ItemName.equip) for _ in range(self.world.equip_pool[self.player])] - itempool += [self.create_item(ItemName.crit_chance) for _ in range(self.world.crit_chance_pool[self.player])] - itempool += [self.create_item(ItemName.crit_damage) for _ in range(self.world.crit_damage_pool[self.player])] + # Classes + if data.category == "Classes": + if name == "Progressive Knights": + if "Knight" not in self.setting("available_classes"): + continue - classes = self.world.available_classes[self.player] - if "Dragon" in classes: - itempool.append(self.create_item(ItemName.dragon)) - if "Traitor" in classes: - itempool.append(self.create_item(ItemName.traitor)) - if self.world.starting_class[self.player] == "knight": - itempool.append(self.create_item(ItemName.progressive_knight)) - elif "Knight" in classes: - itempool.extend(self._create_items(ItemName.progressive_knight)) - if self.world.starting_class[self.player] == "mage": - itempool.append(self.create_item(ItemName.progressive_mage)) - elif "Mage" in classes: - itempool.extend(self._create_items(ItemName.progressive_mage)) - if self.world.starting_class[self.player] == "barbarian": - itempool.append(self.create_item(ItemName.progressive_barbarian)) - elif "Barbarian" in classes: - itempool.extend(self._create_items(ItemName.progressive_barbarian)) - if self.world.starting_class[self.player] == "knave": - itempool.append(self.create_item(ItemName.progressive_knave)) - elif "Knave" in classes: - itempool.extend(self._create_items(ItemName.progressive_knave)) - if self.world.starting_class[self.player] == "shinobi": - itempool.append(self.create_item(ItemName.progressive_shinobi)) - elif "Shinobi" in classes: - itempool.extend(self._create_items(ItemName.progressive_shinobi)) - if self.world.starting_class[self.player] == "miner": - itempool.append(self.create_item(ItemName.progressive_miner)) - elif "Miner" in classes: - itempool.extend(self._create_items(ItemName.progressive_miner)) - if self.world.starting_class[self.player] == "lich": - itempool.append(self.create_item(ItemName.progressive_lich)) - elif "Lich" in classes: - itempool.extend(self._create_items(ItemName.progressive_lich)) - if self.world.starting_class[self.player] == "spellthief": - itempool.append(self.create_item(ItemName.progressive_spellthief)) - elif "Spellthief" in classes: - itempool.extend(self._create_items(ItemName.progressive_spellthief)) + if self.setting("starting_class") == "knight": + quantity = 1 + if name == "Progressive Mages": + if "Mage" not in self.setting("available_classes"): + continue - # Check if we need to start with these vendors or put them in the pool. - if self.world.vendors[self.player] == "start_unlocked": - self.world.push_precollected(self.world.create_item(ItemName.blacksmith, self.player)) - self.world.push_precollected(self.world.create_item(ItemName.enchantress, self.player)) - else: - itempool += [self.create_item(ItemName.blacksmith), self.create_item(ItemName.enchantress)] + if self.setting("starting_class") == "mage": + quantity = 1 + if name == "Progressive Barbarians": + if "Barbarian" not in self.setting("available_classes"): + continue - # Add Architect. - if self.world.architect[self.player] == "start_unlocked": - self.world.push_precollected(self.world.create_item(ItemName.architect, self.player)) - elif self.world.architect[self.player] != "disabled": - itempool.append(self.create_item(ItemName.architect)) + if self.setting("starting_class") == "barbarian": + quantity = 1 + if name == "Progressive Knaves": + if "Knave" not in self.setting("available_classes"): + continue - # Fill item pool with the remaining - for _ in range(len(itempool), total_required_locations): - item = self.world.random.choice(list(misc_items_table.keys())) - itempool.append(self.create_item(item)) + if self.setting("starting_class") == "knave": + quantity = 1 + if name == "Progressive Miners": + if "Miner" not in self.setting("available_classes"): + continue - self.world.itempool += itempool + if self.setting("starting_class") == "miner": + quantity = 1 + if name == "Progressive Shinobis": + if "Shinobi" not in self.setting("available_classes"): + continue + + if self.setting("starting_class") == "shinobi": + quantity = 1 + if name == "Progressive Liches": + if "Lich" not in self.setting("available_classes"): + continue + + if self.setting("starting_class") == "lich": + quantity = 1 + if name == "Progressive Spellthieves": + if "Spellthief" not in self.setting("available_classes"): + continue + + if self.setting("starting_class") == "spellthief": + quantity = 1 + if name == "Dragons": + if "Dragon" not in self.setting("available_classes"): + continue + if name == "Traitors": + if "Traitor" not in self.setting("available_classes"): + continue + + # Skills + if name == "Health Up": + quantity = self.setting("health_pool") + elif name == "Mana Up": + quantity = self.setting("mana_pool") + elif name == "Attack Up": + quantity = self.setting("attack_pool") + elif name == "Magic Damage Up": + quantity = self.setting("magic_damage_pool") + elif name == "Armor Up": + quantity = self.setting("armor_pool") + elif name == "Equip Up": + quantity = self.setting("equip_pool") + elif name == "Crit Chance Up": + quantity = self.setting("crit_chance_pool") + elif name == "Crit Damage Up": + quantity = self.setting("crit_damage_pool") + + # Ignore filler, it will be added in a later stage. + if data.category == "Filler": + continue + + self.item_pool += [self.create_item(name) for _ in range(0, quantity)] + + # Fill any empty locations with filler items. + while len(self.item_pool) + len(self.prefill_items) < total_locations: + self.item_pool.append(self.create_item(self.get_filler_item_name())) + + self.world.itempool += self.item_pool + + def pre_fill(self) -> None: + reachable = [loc for loc in self.world.get_reachable_locations(player=self.player) if not loc.item] + self.world.random.shuffle(reachable) + items = self.prefill_items.copy() + for item in items: + reachable.pop().place_locked_item(item) + + def get_pre_fill_items(self) -> List[RLItem]: + return self.prefill_items def get_filler_item_name(self) -> str: - return self.world.random.choice(list(misc_items_table.keys())) + fillers = get_items_by_category("Filler") + weights = [data.weight for data in fillers.values()] + return self.world.random.choices([filler for filler in fillers.keys()], weights, k=1)[0] - def create_regions(self): - create_regions(self.world, self.player) - - def create_item(self, name: str) -> Item: + def create_item(self, name: str) -> RLItem: data = item_table[name] - return LegacyItem(name, ItemClassification.progression if data.progression else ItemClassification.filler, data.code, self.player) + return RLItem(name, data.classification, data.code, self.player) + + def create_event(self, name: str) -> RLItem: + data = event_item_table[name] + return RLItem(name, data.classification, data.code, self.player) def set_rules(self): set_rules(self.world, self.player) + + def create_regions(self): + create_regions(self.world, self.player) + self._place_events() + + def _place_events(self): + # Fountain + self.world.get_location("Fountain Room", self.player).place_locked_item( + self.create_event("Defeat The Fountain")) + + # Khidr / Neo Khidr + if self.setting("khidr") == "vanilla": + self.world.get_location("Castle Hamson Boss Room", self.player).place_locked_item( + self.create_event("Defeat Khidr")) + else: + self.world.get_location("Castle Hamson Boss Room", self.player).place_locked_item( + self.create_event("Defeat Neo Khidr")) + + # Alexander / Alexander IV + if self.setting("alexander") == "vanilla": + self.world.get_location("Forest Abkhazia Boss Room", self.player).place_locked_item( + self.create_event("Defeat Alexander")) + else: + self.world.get_location("Forest Abkhazia Boss Room", self.player).place_locked_item( + self.create_event("Defeat Alexander IV")) + + # Ponce de Leon / Ponce de Freon + if self.setting("leon") == "vanilla": + self.world.get_location("The Maya Boss Room", self.player).place_locked_item( + self.create_event("Defeat Ponce de Leon")) + else: + self.world.get_location("The Maya Boss Room", self.player).place_locked_item( + self.create_event("Defeat Ponce de Freon")) + + # Herodotus / Astrodotus + if self.setting("herodotus") == "vanilla": + self.world.get_location("Land of Darkness Boss Room", self.player).place_locked_item( + self.create_event("Defeat Herodotus")) + else: + self.world.get_location("Land of Darkness Boss Room", self.player).place_locked_item( + self.create_event("Defeat Astrodotus")) diff --git a/worlds/rogue_legacy/docs/en_Rogue Legacy.md b/worlds/rogue_legacy/docs/en_Rogue Legacy.md index df85b8af..f8a166ab 100644 --- a/worlds/rogue_legacy/docs/en_Rogue Legacy.md +++ b/worlds/rogue_legacy/docs/en_Rogue Legacy.md @@ -2,13 +2,15 @@ ## Where is the settings page? -The [player settings page for this game](../player-settings) is located contains all the options you need to configure -and export a config file. +The [player settings page for this game](../player-settings) contains most of the options you need to +configure and export a config file. Some settings can only be made via YAML, but an explaination can be found in the +[template yaml here](../../../static/generated/configs/Rogue%20Legacy.yaml). ## What does randomization do to this game? -You are not able to buy skill upgrades in the manor upgrade screen, and instead, need to find them in order to level up -your character to make fighting the 5 bosses easier. +Rogue Legacy Randomizer takes all the classes, skills, runes, and blueprints and spreads them out into chests, the manor +upgrade screen, bosses, and some special individual locations. The goal is to become powerful enough to defeat the four +zone bosses and then defeat The Fountain. ## What items and locations get shuffled? @@ -16,6 +18,8 @@ All the skill upgrades, class upgrades, runes packs, and equipment packs are shu checks, chests and fairy chests, and boss rewards. Skill upgrades are also grouped in packs of 5 to make the finding of stats less of a chore. Runes and Equipment are also grouped together. +Some additional locations that can contain items are the Jukebox, the Portraits, and the mini-game rewards. + ## Which items can be in another player's world? Any of the items which can be shuffled may also be placed into another player's world. It is possible to choose to limit @@ -25,3 +29,8 @@ certain items to your own world. When the player receives an item, your character will hold the item above their head and display it to the world. It's good for business! + +## What do I do if I encounter a bug with the game? + +Please reach out to Phar#4444 on Discord or you can drop a bug report on the +[GitHub page for Rogue Legacy Randomizer](https://github.com/ThePhar/RogueLegacyRandomizer/issues/new?assignees=&labels=bug&template=report-an-issue---.md&title=%5BIssue%5D). \ No newline at end of file diff --git a/worlds/rogue_legacy/docs/rogue-legacy_en.md b/worlds/rogue_legacy/docs/rogue-legacy_en.md index 1c049c7c..decb4847 100644 --- a/worlds/rogue_legacy/docs/rogue-legacy_en.md +++ b/worlds/rogue_legacy/docs/rogue-legacy_en.md @@ -28,25 +28,8 @@ provides an alternative one to the default values. Once you have entered the required values, you go to Connect and then select Confirm on the "Ready to Start" screen. Now you're off to start your legacy! -## Manual Installation +## Recommended Installation Instructions -In order to run Rogue Legacy Randomizer you will need to have Rogue Legacy installed on your local machine. Extract the -Randomizer release into a desired folder **outside** of your Rogue Legacy install. Copy the following files from your -Rogue Legacy install into the main directory of your Rogue Legacy Randomizer install: - -- DS2DEngine.dll -- InputSystem.dll -- Nuclex.Input.dll -- SpriteSystem.dll -- Tweener.dll - -And copy the directory from your Rogue Legacy install as well into the main directory of your Rogue Legacy Randomizer -install: - -- Content/ - -Then copy the contents of the CustomContent directory in your Rogue Legacy Randomizer into the newly copied Content -directory and overwrite all files. - -**BE SURE YOU ARE REPLACING THE COPIED FILES IN YOUR ROGUE LEGACY RANDOMIZER DIRECTORY AND NOT REPLACING YOUR ROGUE -LEGACY FILES!** +Please read the README file on the +[Rogue Legacy Randomizer GitHub](https://github.com/ThePhar/RogueLegacyRandomizer/blob/master/README.md) page for up to +date installation instructions.