From 8175d4c31fdc8883b3344077680613b98d06b5bc Mon Sep 17 00:00:00 2001 From: Zach Parks Date: Wed, 26 Jan 2022 16:35:56 -0600 Subject: [PATCH] Rogue Legacy: Update world definition for 0.8 changes. (#236) " Here's a list of compiled changes for the next major release of the Rogue Legacy Randomizer. Ability to toggle (or randomize) whether you fight vanilla bosses or challenge bosses. Ability to change Architect settings (start with unlocked, normal, or disabled) Ability to adjust Architect's fees. Ability to combine chests or fairy chests into a single pool, similar to Risk of Rain 2's implementation. Ability to change blueprints acquirement to being progressive instead of random. Added additional classes that the player can start with and removed all other classes unlocked except for starting class. You must find them! Ability to tweak the item pool amounts for stat ups. Ability to define additional character names. Ability to get a new diary check every time you enter a newly generated castle. " --- worlds/rogue-legacy/Items.py | 116 ++++++----- worlds/rogue-legacy/Locations.py | 13 +- worlds/rogue-legacy/Names/ItemName.py | 40 ++-- worlds/rogue-legacy/Names/LocationName.py | 11 +- worlds/rogue-legacy/Options.py | 238 +++++++++++++++++++++- worlds/rogue-legacy/Regions.py | 42 ++-- worlds/rogue-legacy/Rules.py | 168 +++++++++------ worlds/rogue-legacy/Traits.py | 39 ++++ worlds/rogue-legacy/__init__.py | 71 ++++++- 9 files changed, 570 insertions(+), 168 deletions(-) create mode 100644 worlds/rogue-legacy/Traits.py diff --git a/worlds/rogue-legacy/Items.py b/worlds/rogue-legacy/Items.py index e134444b..1df7e7f7 100644 --- a/worlds/rogue-legacy/Items.py +++ b/worlds/rogue-legacy/Items.py @@ -26,38 +26,38 @@ vendors_table = { } static_classes_table = { - ItemName.knight: ItemData(90080, True), - ItemName.paladin: ItemData(90081, True), - ItemName.mage: ItemData(90082, True), - ItemName.archmage: ItemData(90083, True), - ItemName.barbarian: ItemData(90084, True), - ItemName.barbarian_king: ItemData(90085, True), - ItemName.knave: ItemData(90086, True), - ItemName.assassin: ItemData(90087, True), - ItemName.shinobi: ItemData(90088, True), - ItemName.hokage: ItemData(90089, True), - ItemName.miner: ItemData(90090, True), - ItemName.spelunker: ItemData(90091, True), - ItemName.lich: ItemData(90092, True), - ItemName.lich_king: ItemData(90093, True), - ItemName.spellthief: ItemData(90094, True), - ItemName.spellsword: ItemData(90095, True), - ItemName.dragon: ItemData(90096, True), - ItemName.traitor: ItemData(90097, True), + 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), } progressive_classes_table = { - ItemName.progressive_knight: ItemData(90003, True, 2), - ItemName.progressive_mage: ItemData(90004, True, 2), - ItemName.progressive_barbarian: ItemData(90005, True, 2), - ItemName.progressive_knave: ItemData(90006, True, 2), - ItemName.progressive_shinobi: ItemData(90007, True, 2), - ItemName.progressive_miner: ItemData(90008, True, 2), - ItemName.progressive_lich: ItemData(90009, True, 2), - ItemName.progressive_spellthief: ItemData(90010, True, 2), + 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), } -skill_unlocks_table = { +configurable_skill_unlocks_table = { ItemName.health: ItemData(90013, True, 15), ItemName.mana: ItemData(90014, True, 15), ItemName.attack: ItemData(90015, True, 15), @@ -66,6 +66,9 @@ skill_unlocks_table = { 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), @@ -77,35 +80,39 @@ skill_unlocks_table = { } blueprints_table = { - ItemName.squire_blueprints: ItemData(90040, True), - ItemName.silver_blueprints: ItemData(90041, True), - ItemName.guardian_blueprints: ItemData(90042, True), - ItemName.imperial_blueprints: ItemData(90043, True), - ItemName.royal_blueprints: ItemData(90044, True), - ItemName.knight_blueprints: ItemData(90045, True), - ItemName.ranger_blueprints: ItemData(90046, True), - ItemName.sky_blueprints: ItemData(90047, True), - ItemName.dragon_blueprints: ItemData(90048, True), - ItemName.slayer_blueprints: ItemData(90049, True), - ItemName.blood_blueprints: ItemData(90050, True), - ItemName.sage_blueprints: ItemData(90051, True), - ItemName.retribution_blueprints: ItemData(90052, True), - ItemName.holy_blueprints: ItemData(90053, True), - ItemName.dark_blueprints: ItemData(90054, True), + 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, True), - ItemName.sprint_runes: ItemData(90061, True), - ItemName.vampire_runes: ItemData(90062, True), - ItemName.sky_runes: ItemData(90063, True), - ItemName.siphon_runes: ItemData(90064, True), - ItemName.retaliation_runes: ItemData(90065, True), - ItemName.bounty_runes: ItemData(90066, True), - ItemName.haste_runes: ItemData(90067, True), - ItemName.curse_runes: ItemData(90068, True), - ItemName.grace_runes: ItemData(90069, True), - ItemName.balance_runes: ItemData(90070, True), + 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 = { @@ -113,6 +120,7 @@ misc_items_table = { 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. @@ -120,8 +128,10 @@ 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, } diff --git a/worlds/rogue-legacy/Locations.py b/worlds/rogue-legacy/Locations.py index 8e256c0f..ddbbecf0 100644 --- a/worlds/rogue-legacy/Locations.py +++ b/worlds/rogue-legacy/Locations.py @@ -43,13 +43,16 @@ base_location_table = { LocationName.manor_observatory_scope: 91030, # Boss Rewards - LocationName.boss_khindr: 91100, - LocationName.boss_alexander: 91102, - LocationName.boss_leon: 91104, - LocationName.boss_herodotus: 91106, + 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, # Special Locations LocationName.castle: None, @@ -66,6 +69,7 @@ fairy_chest_location_table = { **{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)}, } chest_location_table = { @@ -73,6 +77,7 @@ chest_location_table = { **{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 = { diff --git a/worlds/rogue-legacy/Names/ItemName.py b/worlds/rogue-legacy/Names/ItemName.py index 474f8ef7..7532ac19 100644 --- a/worlds/rogue-legacy/Names/ItemName.py +++ b/worlds/rogue-legacy/Names/ItemName.py @@ -56,23 +56,25 @@ trip_stat_increase = "Triple Stat Increase" gold_1000 = "1000 Gold" gold_3000 = "3000 Gold" gold_5000 = "5000 Gold" +rage_trap = "Rage Trap" # Blueprint Definitions -squire_blueprints = "Squire Armor Blueprints" -silver_blueprints = "Silver Armor Blueprints" -guardian_blueprints = "Guardian Armor Blueprints" -imperial_blueprints = "Imperial Armor Blueprints" -royal_blueprints = "Royal Armor Blueprints" -knight_blueprints = "Knight Armor Blueprints" -ranger_blueprints = "Ranger Armor Blueprints" -sky_blueprints = "Sky Armor Blueprints" -dragon_blueprints = "Dragon Armor Blueprints" -slayer_blueprints = "Slayer Armor Blueprints" -blood_blueprints = "Blood Armor Blueprints" -sage_blueprints = "Sage Armor Blueprints" -retribution_blueprints = "Retribution Armor Blueprints" -holy_blueprints = "Holy Armor Blueprints" -dark_blueprints = "Dark Armor Blueprints" +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" @@ -88,8 +90,8 @@ grace_runes = "Grace Runes" balance_runes = "Balance Runes" # Event Definitions -boss_khindr = "Defeat Khindr" -boss_alexander = "Defeat Alexander" -boss_leon = "Defeat Ponce de Leon" -boss_herodotus = "Defeat Herodotus" +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 index f6699907..e4732f9f 100644 --- a/worlds/rogue-legacy/Names/LocationName.py +++ b/worlds/rogue-legacy/Names/LocationName.py @@ -32,13 +32,16 @@ manor_observatory_base = "Manor Renovation - Observatory Base" manor_observatory_scope = "Manor Renovation - Observatory Telescope" # Boss Chest Definitions -boss_khindr = "Khindr's Boss Chest" -boss_alexander = "Alexander's Boss Chest" -boss_leon = "Ponce de Leon's Boss Chest" -boss_herodotus = "Herodotus's Boss Chest" +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" diff --git a/worlds/rogue-legacy/Options.py b/worlds/rogue-legacy/Options.py index c46aa207..6cd3298c 100644 --- a/worlds/rogue-legacy/Options.py +++ b/worlds/rogue-legacy/Options.py @@ -1,6 +1,6 @@ import typing -from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle +from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle, OptionList class StartingGender(Choice): @@ -24,6 +24,10 @@ class StartingClass(Choice): option_mage = 1 option_barbarian = 2 option_knave = 3 + option_shinobi = 4 + option_miner = 5 + option_spellthief = 6 + option_lich = 7 default = 0 @@ -41,6 +45,18 @@ class NewGamePlus(Choice): default = 0 +class LevelScaling(Range): + """ + A percentage modifier for scaling enemy level as you continue throughout the castle. 100 means enemies will have + 100% level scaling (normal). Setting this too high will result in enemies with absurdly high levels, you have been + warned. + """ + displayname = "Enemy Level Scaling Percentage" + range_start = 1 + range_end = 300 + default = 100 + + class FairyChestsPerZone(Range): """ Determines the number of Fairy Chests in a given zone that contain items. After these have been checked, only stat @@ -63,6 +79,20 @@ class ChestsPerZone(Range): default = 15 +class UniversalFairyChests(Toggle): + """ + Determines if fairy chests should be combined into one pool instead of per zone, similar to Risk of Rain 2. + """ + displayname = "Universal Fairy Chests" + + +class UniversalChests(Toggle): + """ + Determines if non-fairy chests should be combined into one pool instead of per zone, similar to Risk of Rain 2. + """ + displayname = "Universal Non-Fairy Chests" + + class Vendors(Choice): """ Determines where to place the Blacksmith and Enchantress unlocks in logic (or start with them unlocked). @@ -75,6 +105,28 @@ class Vendors(Choice): default = 1 +class Architect(Choice): + """ + Determines where the Architect sits in the item pool. + """ + displayname = "Architect" + option_start_unlocked = 0 + option_normal = 2 + option_disabled = 3 + default = 2 + + +class ArchitectFee(Range): + """ + Determines how large of a percentage the architect takes from the player when utilizing his services. 100 means he + takes all your gold. 0 means his services are free. + """ + displayname = "Architect Fee Percentage" + range_start = 0 + range_end = 100 + default = 40 + + class DisableCharon(Toggle): """ Prevents Charon from taking your money when you re-enter the castle. Also removes Haggling from the Item Pool. @@ -90,6 +142,14 @@ class RequirePurchasing(DefaultOnToggle): displayname = "Require Purchasing" +class ProgressiveBlueprints(Toggle): + """ + Instead of shuffling blueprints randomly into the pool, blueprint unlocks are progressively unlocked. You would get + Squire first, then Knight, etc., until finally Dark. + """ + displayname = "Progressive Blueprints" + + class GoldGainMultiplier(Choice): """ Adjusts the multiplier for gaining gold from all sources. @@ -113,16 +173,192 @@ class NumberOfChildren(Range): default = 3 +class AdditionalNames(OptionList): + """ + 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 + Gender. + """ + displayname = "Additional Names" + + +class AllowDefaultNames(DefaultOnToggle): + """ + Determines if the default names defined in the vanilla game are allowed to be used. Warning: Your world will not + generate if the number of Additional Names defined is less than the Number of Children value. + """ + displayname = "Allow Default Names" + + +class CastleScaling(Range): + """ + Adjusts the scaling factor for how big a castle can be. Larger castles scale enemies quicker and also take longer + to generate. 100 means normal castle size. + """ + displayname = "Castle Size Scaling Percentage" + range_start = 50 + range_end = 300 + default = 100 + + +class ChallengeBossKhidr(Choice): + """ + Determines if Neo Khidr replaces Khidr in their boss room. + """ + displayname = "Khidr" + option_vanilla = 0 + option_challenge = 1 + default = 0 + + +class ChallengeBossAlexander(Choice): + """ + Determines if Alexander the IV replaces Alexander in their boss room. + """ + displayname = "Alexander" + option_vanilla = 0 + option_challenge = 1 + default = 0 + + +class ChallengeBossLeon(Choice): + """ + Determines if Ponce de Freon replaces Ponce de Leon in their boss room. + """ + displayname = "Ponce de Leon" + option_vanilla = 0 + option_challenge = 1 + default = 0 + + +class ChallengeBossHerodotus(Choice): + """ + Determines if Astrodotus replaces Herodotus in their boss room. + """ + displayname = "Herodotus" + option_vanilla = 0 + option_challenge = 1 + default = 0 + + +class HealthUpPool(Range): + """ + Determines the number of Health Ups in the item pool. + """ + displayname = "Health Up Pool" + range_start = 0 + range_end = 15 + default = 15 + + +class ManaUpPool(Range): + """ + Determines the number of Mana Ups in the item pool. + """ + displayname = "Mana Up Pool" + range_start = 0 + range_end = 15 + default = 15 + + +class AttackUpPool(Range): + """ + Determines the number of Attack Ups in the item pool. + """ + displayname = "Attack Up Pool" + range_start = 0 + range_end = 15 + default = 15 + + +class MagicDamageUpPool(Range): + """ + Determines the number of Magic Damage Ups in the item pool. + """ + displayname = "Magic Damage Up Pool" + range_start = 0 + range_end = 15 + default = 15 + + +class ArmorUpPool(Range): + """ + Determines the number of Armor Ups in the item pool. + """ + displayname = "Armor Up Pool" + range_start = 0 + range_end = 10 + default = 10 + + +class EquipUpPool(Range): + """ + Determines the number of Equip Ups in the item pool. + """ + displayname = "Equip Up Pool" + range_start = 0 + range_end = 10 + default = 10 + + +class CritChanceUpPool(Range): + """ + Determines the number of Crit Chance Ups in the item pool. + """ + displayname = "Crit Chance Up Pool" + range_start = 0 + range_end = 5 + default = 5 + + +class CritDamageUpPool(Range): + """ + Determines the number of Crit Damage Ups in the item pool. + """ + displayname = "Crit Damage Up Pool" + range_start = 0 + range_end = 5 + default = 5 + + +class FreeDiaryOnGeneration(DefaultOnToggle): + """ + Allows the player to get a free diary check every time they regenerate the castle in the starting room. + """ + displayname = "Free Diary On Generation" + + legacy_options: typing.Dict[str, type(Option)] = { "starting_gender": StartingGender, "starting_class": StartingClass, "new_game_plus": NewGamePlus, "fairy_chests_per_zone": FairyChestsPerZone, "chests_per_zone": ChestsPerZone, + "universal_fairy_chests": UniversalFairyChests, + "universal_chests": UniversalChests, "vendors": Vendors, + "architect": Architect, + "architect_fee": ArchitectFee, "disable_charon": DisableCharon, "require_purchasing": RequirePurchasing, + "progressive_blueprints": ProgressiveBlueprints, "gold_gain_multiplier": GoldGainMultiplier, "number_of_children": NumberOfChildren, + "free_diary_on_generation": FreeDiaryOnGeneration, + "khidr": ChallengeBossKhidr, + "alexander": ChallengeBossAlexander, + "leon": ChallengeBossLeon, + "herodotus": ChallengeBossHerodotus, + "health_pool": HealthUpPool, + "mana_pool": ManaUpPool, + "attack_pool": AttackUpPool, + "magic_damage_pool": MagicDamageUpPool, + "armor_pool": ArmorUpPool, + "equip_pool": EquipUpPool, + "crit_chance_pool": CritChanceUpPool, + "crit_damage_pool": CritDamageUpPool, + "allow_default_names": AllowDefaultNames, + "additional_lady_names": AdditionalNames, + "additional_sir_names": AdditionalNames, "death_link": DeathLink, } diff --git a/worlds/rogue-legacy/Regions.py b/worlds/rogue-legacy/Regions.py index 7990da27..fbd43f76 100644 --- a/worlds/rogue-legacy/Regions.py +++ b/worlds/rogue-legacy/Regions.py @@ -15,19 +15,29 @@ def create_regions(world, player: int): locations += [location for location in diary_location_table] # Add chests per settings. - 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}"] + 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}"] - 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}"] + 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}"] # Set up the regions correctly. world.regions += [ @@ -37,10 +47,10 @@ def create_regions(world, player: int): # 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_khindr, True, None, player)) - world.get_location(LocationName.garden, player).place_locked_item(LegacyItem(ItemName.boss_alexander, True, None, player)) - world.get_location(LocationName.tower, player).place_locked_item(LegacyItem(ItemName.boss_leon, True, None, player)) - world.get_location(LocationName.dungeon, player).place_locked_item(LegacyItem(ItemName.boss_herodotus, True, None, player)) + world.get_location(LocationName.castle, player).place_locked_item(LegacyItem(ItemName.boss_castle, True, None, player)) + world.get_location(LocationName.garden, player).place_locked_item(LegacyItem(ItemName.boss_forest, True, None, player)) + world.get_location(LocationName.tower, player).place_locked_item(LegacyItem(ItemName.boss_tower, True, None, player)) + world.get_location(LocationName.dungeon, player).place_locked_item(LegacyItem(ItemName.boss_dungeon, True, None, player)) world.get_location(LocationName.fountain, player).place_locked_item(LegacyItem(ItemName.boss_fountain, True, None, player)) diff --git a/worlds/rogue-legacy/Rules.py b/worlds/rogue-legacy/Rules.py index 3dd233ca..6d2cb4cd 100644 --- a/worlds/rogue-legacy/Rules.py +++ b/worlds/rogue-legacy/Rules.py @@ -12,120 +12,166 @@ class LegacyLogic(LogicMixin): return self.has_all({ItemName.blacksmith, ItemName.enchantress}, player) def _legacy_has_stat_upgrades(self, player: int, amount: int) -> bool: - count: int = 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) - return count >= amount + return self._legacy_stat_upgrade_count(player) >= amount + + def _legacy_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]) + + 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 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 - 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_khindr, player)) - set_rule(world.get_location(f"{LocationName.tower} - Chest {i + 1}", player), - lambda state: state.has(ItemName.boss_alexander, player)) - set_rule(world.get_location(f"{LocationName.dungeon} - Chest {i + 1}", player), - lambda state: state.has(ItemName.boss_leon, player)) + 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 - 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_khindr, player)) - set_rule(world.get_location(f"{LocationName.tower} - Fairy Chest {i + 1}", player), - lambda state: state.has(ItemName.boss_alexander, player)) - set_rule(world.get_location(f"{LocationName.dungeon} - Fairy Chest {i + 1}", player), - lambda state: state.has(ItemName.boss_leon, player)) + 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.castle, player), + 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)) - elif world.vendors[player] == "anywhere": - pass # it can be anywhere, so no rule for this! # Diaries for i in range(0, 5): set_rule(world.get_location(f"Diary {i + 6}", player), - lambda state: state.has(ItemName.boss_khindr, 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_alexander, 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_leon, 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_herodotus, player)) + lambda state: state.has(ItemName.boss_dungeon, player)) # Scale each manor location. set_rule(world.get_location(LocationName.manor_left_wing_window, player), - lambda state: state.has(ItemName.boss_khindr, 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_khindr, 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_khindr, 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_khindr, 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_khindr, 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_khindr, 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_khindr, 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_khindr, 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_khindr, 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_alexander, 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_alexander, 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_alexander, 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_alexander, 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_alexander, 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_alexander, 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_alexander, 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_alexander, 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_alexander, 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_alexander, 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_leon, 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_leon, 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_leon, 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_leon, 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_leon, player)) + lambda state: state.has(ItemName.boss_tower, player)) # Standard Zone Progression set_rule(world.get_location(LocationName.garden, player), - lambda state: state._legacy_has_stat_upgrades(player, 10) and state.has(ItemName.boss_khindr, 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, 25) and state.has(ItemName.boss_alexander, 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, 40) and state.has(ItemName.boss_leon, 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)) # Bosses - set_rule(world.get_location(LocationName.boss_khindr, player), - lambda state: state.has(ItemName.boss_khindr, player)) - set_rule(world.get_location(LocationName.boss_alexander, player), - lambda state: state.has(ItemName.boss_alexander, player)) - set_rule(world.get_location(LocationName.boss_leon, player), - lambda state: state.has(ItemName.boss_leon, player)) - set_rule(world.get_location(LocationName.boss_herodotus, player), - lambda state: state.has(ItemName.boss_herodotus, player)) + 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, 50) and state.has(ItemName.boss_herodotus, 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) diff --git a/worlds/rogue-legacy/Traits.py b/worlds/rogue-legacy/Traits.py new file mode 100644 index 00000000..5959891d --- /dev/null +++ b/worlds/rogue-legacy/Traits.py @@ -0,0 +1,39 @@ +traits = [ + "Color Blind", + "Gay", + "Near-Sighted", + "Far-Sighted", + "Dyslexia", + "Gigantism", + "Dwarfism", + "Baldness", + "Endomorph", + "Ectomorph", + "Alzheimers", + "Dextrocardia", + "Coprolalia", + "ADHD", + "O.C.D.", + "Hypergonadism", + "Muscle Wk.", + "Stereo Blind", + "I.B.S.", + "Vertigo", + "Tunnel Vision", + "Ambilevous", + "P.A.D.", + "Alektorophobia", + "Hypochondriac", + "Dementia", + "Flexible", + "Eid. Mem.", + "Nostalgic", + "C.I.P.", + "Savant", + "The One", + "Clumsy", + "EHS", + "Glaucoma", + "Adopted", +] + diff --git a/worlds/rogue-legacy/__init__.py b/worlds/rogue-legacy/__init__.py index 41e7a20b..c8a95f49 100644 --- a/worlds/rogue-legacy/__init__.py +++ b/worlds/rogue-legacy/__init__.py @@ -20,7 +20,7 @@ class LegacyWorld(World): game: str = "Rogue Legacy" options = legacy_options topology_present = False - data_version = 1 + data_version = 3 item_name_to_id = {name: data.code for name, data in item_table.items()} location_name_to_id = location_table @@ -32,11 +32,21 @@ class LegacyWorld(World): "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], } @@ -44,6 +54,9 @@ class LegacyWorld(World): data = item_table[name] return [self.create_item(name)] * data.quantity + def get_required_client_version(self) -> typing.Tuple[int, int, int]: + return max((0, 2, 3), super(LegacyWorld, self).get_required_client_version()) + def fill_slot_data(self) -> dict: slot_data = self._get_slot_data() for option_name in legacy_options: @@ -54,29 +67,64 @@ class LegacyWorld(World): def generate_basic(self): itempool: typing.List[LegacyItem] = [] - total_required_locations = 61 + (self.world.chests_per_zone[self.player] * 4) + (self.world.fairy_chests_per_zone[self.player] * 4) + total_required_locations = 64 + (self.world.chests_per_zone[self.player] * 4) + (self.world.fairy_chests_per_zone[self.player] * 4) # Fill item pool with all required items - for item in {**skill_unlocks_table, **blueprints_table, **runes_table}: + 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: continue itempool += self._create_items(item) - + + # Blueprints + if self.world.progressive_blueprints[self.player]: + itempool += [self.create_item(ItemName.progressive_blueprints)] * 15 + else: + for item in blueprints_table: + itempool += self._create_items(item) + + # Check Pool settings to add a certain amount of these items. + itempool += [self.create_item(ItemName.health)] * int(self.world.health_pool[self.player]) + itempool += [self.create_item(ItemName.mana)] * int(self.world.mana_pool[self.player]) + itempool += [self.create_item(ItemName.attack)] * int(self.world.attack_pool[self.player]) + itempool += [self.create_item(ItemName.magic_damage)] * int(self.world.magic_damage_pool[self.player]) + itempool += [self.create_item(ItemName.armor)] * int(self.world.armor_pool[self.player]) + itempool += [self.create_item(ItemName.equip)] * int(self.world.equip_pool[self.player]) + itempool += [self.create_item(ItemName.crit_chance)] * int(self.world.crit_chance_pool[self.player]) + itempool += [self.create_item(ItemName.crit_damage)] * int(self.world.crit_damage_pool[self.player]) + # Add specific classes into the pool. Eventually, will be able to shuffle the starting ones, but until then... itempool += [ - self.create_item(ItemName.paladin), - self.create_item(ItemName.archmage), - self.create_item(ItemName.barbarian_king), - self.create_item(ItemName.assassin), self.create_item(ItemName.dragon), self.create_item(ItemName.traitor), + *self._create_items(ItemName.progressive_knight), + *self._create_items(ItemName.progressive_mage), + *self._create_items(ItemName.progressive_barbarian), + *self._create_items(ItemName.progressive_knave), *self._create_items(ItemName.progressive_shinobi), *self._create_items(ItemName.progressive_miner), *self._create_items(ItemName.progressive_lich), *self._create_items(ItemName.progressive_spellthief), ] + # Remove one of our starting classes from the item pool. + if self.world.starting_class[self.player] == "knight": + itempool.remove(self.create_item(ItemName.progressive_knight)) + elif self.world.starting_class[self.player] == "mage": + itempool.remove(self.create_item(ItemName.progressive_mage)) + elif self.world.starting_class[self.player] == "barbarian": + itempool.remove(self.create_item(ItemName.progressive_barbarian)) + elif self.world.starting_class[self.player] == "knave": + itempool.remove(self.create_item(ItemName.progressive_knave)) + elif self.world.starting_class[self.player] == "miner": + itempool.remove(self.create_item(ItemName.progressive_miner)) + elif self.world.starting_class[self.player] == "shinobi": + itempool.remove(self.create_item(ItemName.progressive_shinobi)) + elif self.world.starting_class[self.player] == "lich": + itempool.remove(self.create_item(ItemName.progressive_lich)) + elif self.world.starting_class[self.player] == "spellthief": + itempool.remove(self.create_item(ItemName.progressive_spellthief)) + # 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)) @@ -84,8 +132,11 @@ class LegacyWorld(World): else: itempool += [self.create_item(ItemName.blacksmith), self.create_item(ItemName.enchantress)] - # Add Arcitect. - itempool += [self.create_item(ItemName.architect)] + # 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 += [self.create_item(ItemName.architect)] # Fill item pool with the remaining for _ in range(len(itempool), total_required_locations):