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):