diff --git a/.gitignore b/.gitignore
index 26885103..d859d4f8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -155,4 +155,7 @@ Archipelago.zip
#minecraft server stuff
jdk*/
-minecraft*/
\ No newline at end of file
+minecraft*/
+
+#pyenv
+.python-version
diff --git a/README.md b/README.md
index 739a8caf..35224128 100644
--- a/README.md
+++ b/README.md
@@ -14,6 +14,7 @@ Currently, the following games are supported:
* Super Metroid
* Secret of Evermore
* Final Fantasy
+* Rogue Legacy
For setup and instructions check out our [tutorials page](https://archipelago.gg/tutorial/).
Downloads can be found at [Releases](https://github.com/ArchipelagoMW/Archipelago/releases), including compiled
diff --git a/WebHostLib/static/assets/gameInfo/en_Rogue Legacy.md b/WebHostLib/static/assets/gameInfo/en_Rogue Legacy.md
new file mode 100644
index 00000000..f7c1068d
--- /dev/null
+++ b/WebHostLib/static/assets/gameInfo/en_Rogue Legacy.md
@@ -0,0 +1,22 @@
+# Rogue Legacy (PC)
+
+## Where is the settings page?
+The player settings page for this game is located here. It contains all the options
+you need to configure and export a config file.
+
+## 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.
+
+## What items and locations get shuffled?
+All the skill upgrades, class upgrades, runes packs, and equipment packs are shuffled in the manor upgrade screen,
+diary 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.
+
+## 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 certain items to your own world.
+
+## When the player receives an item, what happens?
+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!
diff --git a/WebHostLib/static/assets/tutorial/legacy/legacy_en.md b/WebHostLib/static/assets/tutorial/legacy/legacy_en.md
new file mode 100644
index 00000000..65f9d704
--- /dev/null
+++ b/WebHostLib/static/assets/tutorial/legacy/legacy_en.md
@@ -0,0 +1,47 @@
+# Rogue Legacy Randomizer Setup Guide
+
+## Required Software
+
+- [Rogue Legacy Randomizer](https://github.com/ThePhar/RogueLegacyRandomizer/releases)
+
+## Configuring your YAML file
+
+### What is a YAML file and why do I need one?
+Your YAML file contains a set of configuration options which provide the generator with information about how
+it should generate your game. Each player of a multiworld will provide their own YAML file. This setup allows
+each player to enjoy an experience customized for their taste, and different players in the same multiworld
+can all have different options.
+
+### Where do I get a YAML file?
+you can customize your settings by visiting the rogue legacy settings page here.
+
+### Connect to the MultiServer
+Once in game, press the start button and the AP connection screen should appear. You will fill out the hostname, port,
+slot name, and password (if applicable). You should only need to fill out hostname, port, and password if the server
+provides an alternative one to the default values.
+
+### Play the game
+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
+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!**
diff --git a/WebHostLib/static/assets/tutorial/tutorials.json b/WebHostLib/static/assets/tutorial/tutorials.json
index 67f33e16..eb27113d 100644
--- a/WebHostLib/static/assets/tutorial/tutorials.json
+++ b/WebHostLib/static/assets/tutorial/tutorials.json
@@ -342,5 +342,24 @@
]
}
]
+ },
+ {
+ "gameTitle": "Rogue Legacy",
+ "tutorials": [
+ {
+ "name": "Multiworld Setup Guide",
+ "description": "A guide to setting up the Rogue Legacy Randomizer software on your computer. This guide covers single-player, multiworld, and related software.",
+ "files": [
+ {
+ "language": "English",
+ "filename": "legacy/legacy_en.md",
+ "link": "legacy/legacy/en",
+ "authors": [
+ "Phar"
+ ]
+ }
+ ]
+ }
+ ]
}
]
diff --git a/worlds/legacy/Items.py b/worlds/legacy/Items.py
new file mode 100644
index 00000000..e134444b
--- /dev/null
+++ b/worlds/legacy/Items.py
@@ -0,0 +1,129 @@
+import typing
+
+from BaseClasses import Item
+from .Names import ItemName
+
+
+class ItemData(typing.NamedTuple):
+ code: typing.Optional[int]
+ progression: bool
+ quantity: int = 1
+ event: bool = False
+
+
+class LegacyItem(Item):
+ game: str = "Rogue Legacy"
+
+ def __init__(self, name, advancement: bool = False, code: int = None, player: int = None):
+ super(LegacyItem, self).__init__(name, advancement, code, player)
+
+
+# Separate tables for each type of item.
+vendors_table = {
+ ItemName.blacksmith: ItemData(90000, True),
+ ItemName.enchantress: ItemData(90001, True),
+ ItemName.architect: ItemData(90002, False),
+}
+
+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),
+}
+
+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),
+}
+
+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),
+ 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, 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),
+}
+
+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),
+}
+
+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),
+}
+
+# Complete item table.
+item_table = {
+ **vendors_table,
+ **static_classes_table,
+ **progressive_classes_table,
+ **skill_unlocks_table,
+ **blueprints_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/legacy/Locations.py b/worlds/legacy/Locations.py
new file mode 100644
index 00000000..8e256c0f
--- /dev/null
+++ b/worlds/legacy/Locations.py
@@ -0,0 +1,85 @@
+import typing
+
+from BaseClasses import Location
+from .Names import LocationName
+
+
+class LegacyLocation(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,
+
+ # Boss Rewards
+ LocationName.boss_khindr: 91100,
+ LocationName.boss_alexander: 91102,
+ LocationName.boss_leon: 91104,
+ LocationName.boss_herodotus: 91106,
+
+ # Special Rooms
+ LocationName.special_jukebox: 91200,
+
+ # Special Locations
+ LocationName.castle: None,
+ LocationName.garden: None,
+ LocationName.tower: None,
+ LocationName.dungeon: None,
+ LocationName.fountain: None,
+}
+
+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)},
+}
+
+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)},
+}
+
+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/legacy/Names/ItemName.py b/worlds/legacy/Names/ItemName.py
new file mode 100644
index 00000000..474f8ef7
--- /dev/null
+++ b/worlds/legacy/Names/ItemName.py
@@ -0,0 +1,95 @@
+# 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"
+
+# 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"
+
+# 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_khindr = "Defeat Khindr"
+boss_alexander = "Defeat Alexander"
+boss_leon = "Defeat Ponce de Leon"
+boss_herodotus = "Defeat Herodotus"
+boss_fountain = "Defeat The Fountain"
diff --git a/worlds/legacy/Names/LocationName.py b/worlds/legacy/Names/LocationName.py
new file mode 100644
index 00000000..f6699907
--- /dev/null
+++ b/worlds/legacy/Names/LocationName.py
@@ -0,0 +1,52 @@
+# 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_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"
+
+# Special Room Definitions
+special_jukebox = "Jukebox"
+
+# 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/legacy/Options.py b/worlds/legacy/Options.py
new file mode 100644
index 00000000..c46aa207
--- /dev/null
+++ b/worlds/legacy/Options.py
@@ -0,0 +1,128 @@
+import typing
+
+from Options import Choice, Range, Option, Toggle, DeathLink, DefaultOnToggle
+
+
+class StartingGender(Choice):
+ """
+ Determines the gender of your initial 'Sir Lee' character.
+ """
+ displayname = "Starting Gender"
+ option_sir = 0
+ option_lady = 1
+ alias_male = 0
+ alias_female = 1
+ default = 0
+
+
+class StartingClass(Choice):
+ """
+ Determines the starting class of your initial 'Sir Lee' character.
+ """
+ displayname = "Starting Class"
+ option_knight = 0
+ option_mage = 1
+ option_barbarian = 2
+ option_knave = 3
+ default = 0
+
+
+class NewGamePlus(Choice):
+ """
+ Puts the castle in new game plus mode which vastly increases enemy level, but increases gold gain by 50%. Not
+ recommended for those inexperienced to Rogue Legacy!
+ """
+ displayname = "New Game Plus"
+ option_normal = 0
+ option_new_game_plus = 1
+ option_new_game_plus_2 = 2
+ alias_hard = 1
+ alias_brutal = 2
+ default = 0
+
+
+class FairyChestsPerZone(Range):
+ """
+ Determines the number of Fairy Chests in a given zone that contain items. After these have been checked, only stat
+ bonuses can be found in Fairy Chests.
+ """
+ displayname = "Fairy Chests Per Zone"
+ range_start = 5
+ range_end = 15
+ default = 5
+
+
+class ChestsPerZone(Range):
+ """
+ Determines the number of Non-Fairy Chests in a given zone that contain items. After these have been checked, only
+ gold or stat bonuses can be found in Chests.
+ """
+ displayname = "Chests Per Zone"
+ range_start = 15
+ range_end = 30
+ default = 15
+
+
+class Vendors(Choice):
+ """
+ Determines where to place the Blacksmith and Enchantress unlocks in logic (or start with them unlocked).
+ """
+ displayname = "Vendors"
+ option_start_unlocked = 0
+ option_early = 1
+ option_normal = 2
+ option_anywhere = 3
+ default = 1
+
+
+class DisableCharon(Toggle):
+ """
+ Prevents Charon from taking your money when you re-enter the castle. Also removes Haggling from the Item Pool.
+ """
+ displayname = "Disable Charon"
+
+
+class RequirePurchasing(DefaultOnToggle):
+ """
+ Determines where you will be required to purchase equipment and runes from the Blacksmith and Enchantress before
+ equipping them. If you disable require purchasing, Manor Renovations are scaled to take this into account.
+ """
+ displayname = "Require Purchasing"
+
+
+class GoldGainMultiplier(Choice):
+ """
+ Adjusts the multiplier for gaining gold from all sources.
+ """
+ displayname = "Gold Gain Multiplier"
+ option_normal = 0
+ option_quarter = 1
+ option_half = 2
+ option_double = 3
+ option_quadruple = 4
+ default = 0
+
+
+class NumberOfChildren(Range):
+ """
+ Determines the number of offspring you can choose from on the lineage screen after a death.
+ """
+ displayname = "Number of Children"
+ range_start = 1
+ range_end = 5
+ default = 3
+
+
+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,
+ "vendors": Vendors,
+ "disable_charon": DisableCharon,
+ "require_purchasing": RequirePurchasing,
+ "gold_gain_multiplier": GoldGainMultiplier,
+ "number_of_children": NumberOfChildren,
+ "death_link": DeathLink,
+}
diff --git a/worlds/legacy/Regions.py b/worlds/legacy/Regions.py
new file mode 100644
index 00000000..7990da27
--- /dev/null
+++ b/worlds/legacy/Regions.py
@@ -0,0 +1,60 @@
+import typing
+
+from BaseClasses import MultiWorld, Region, Entrance
+from .Items import LegacyItem
+from .Locations import LegacyLocation, diary_location_table, location_table, base_location_table
+from .Names import LocationName, ItemName
+
+
+def create_regions(world, player: int):
+
+ locations: typing.List[str] = []
+
+ # Add required locations.
+ locations += [location for location in base_location_table]
+ 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}"]
+
+ 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 += [
+ create_region(world, player, "Menu", None, [LocationName.outside]),
+ create_region(world, player, LocationName.castle, locations),
+ ]
+
+ # 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.fountain, player).place_locked_item(LegacyItem(ItemName.boss_fountain, True, None, player))
+
+
+def create_region(world: MultiWorld, player: int, name: str, locations=None, exits=None):
+ # Shamelessly stolen from the ROR2 definition, lol
+ ret = Region(name, None, 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)
+ ret.locations.append(location)
+ if exits:
+ for exit in exits:
+ ret.exits.append(Entrance(player, exit, ret))
+
+ return ret
diff --git a/worlds/legacy/Rules.py b/worlds/legacy/Rules.py
new file mode 100644
index 00000000..3dd233ca
--- /dev/null
+++ b/worlds/legacy/Rules.py
@@ -0,0 +1,131 @@
+from BaseClasses import MultiWorld
+from .Names import LocationName, ItemName
+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 _legacy_has_all_vendors(self, player: int) -> bool:
+ 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
+
+
+def set_rules(world: MultiWorld, player: int):
+ # 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))
+
+ # 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))
+
+ # Vendors
+ if world.vendors[player] == "early":
+ set_rule(world.get_location(LocationName.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))
+ set_rule(world.get_location(f"Diary {i + 11}", player),
+ lambda state: state.has(ItemName.boss_alexander, player))
+ set_rule(world.get_location(f"Diary {i + 16}", player),
+ lambda state: state.has(ItemName.boss_leon, player))
+ set_rule(world.get_location(f"Diary {i + 21}", player),
+ lambda state: state.has(ItemName.boss_herodotus, player))
+
+ # Scale each manor location.
+ set_rule(world.get_location(LocationName.manor_left_wing_window, player),
+ lambda state: state.has(ItemName.boss_khindr, player))
+ set_rule(world.get_location(LocationName.manor_left_wing_roof, player),
+ lambda state: state.has(ItemName.boss_khindr, player))
+ set_rule(world.get_location(LocationName.manor_right_wing_window, player),
+ lambda state: state.has(ItemName.boss_khindr, player))
+ set_rule(world.get_location(LocationName.manor_right_wing_roof, player),
+ lambda state: state.has(ItemName.boss_khindr, player))
+ set_rule(world.get_location(LocationName.manor_left_big_base, player),
+ lambda state: state.has(ItemName.boss_khindr, player))
+ set_rule(world.get_location(LocationName.manor_right_big_base, player),
+ lambda state: state.has(ItemName.boss_khindr, player))
+ set_rule(world.get_location(LocationName.manor_left_tree1, player),
+ lambda state: state.has(ItemName.boss_khindr, player))
+ set_rule(world.get_location(LocationName.manor_left_tree2, player),
+ lambda state: state.has(ItemName.boss_khindr, player))
+ set_rule(world.get_location(LocationName.manor_right_tree, player),
+ lambda state: state.has(ItemName.boss_khindr, player))
+ set_rule(world.get_location(LocationName.manor_left_big_upper1, player),
+ lambda state: state.has(ItemName.boss_alexander, player))
+ set_rule(world.get_location(LocationName.manor_left_big_upper2, player),
+ lambda state: state.has(ItemName.boss_alexander, player))
+ set_rule(world.get_location(LocationName.manor_left_big_windows, player),
+ lambda state: state.has(ItemName.boss_alexander, player))
+ set_rule(world.get_location(LocationName.manor_left_big_roof, player),
+ lambda state: state.has(ItemName.boss_alexander, player))
+ set_rule(world.get_location(LocationName.manor_left_far_base, player),
+ lambda state: state.has(ItemName.boss_alexander, player))
+ set_rule(world.get_location(LocationName.manor_left_far_roof, player),
+ lambda state: state.has(ItemName.boss_alexander, player))
+ set_rule(world.get_location(LocationName.manor_left_extension, player),
+ lambda state: state.has(ItemName.boss_alexander, player))
+ set_rule(world.get_location(LocationName.manor_right_big_upper, player),
+ lambda state: state.has(ItemName.boss_alexander, player))
+ set_rule(world.get_location(LocationName.manor_right_big_roof, player),
+ lambda state: state.has(ItemName.boss_alexander, player))
+ set_rule(world.get_location(LocationName.manor_right_extension, player),
+ lambda state: state.has(ItemName.boss_alexander, player))
+ set_rule(world.get_location(LocationName.manor_right_high_base, player),
+ lambda state: state.has(ItemName.boss_leon, player))
+ set_rule(world.get_location(LocationName.manor_right_high_upper, player),
+ lambda state: state.has(ItemName.boss_leon, player))
+ set_rule(world.get_location(LocationName.manor_right_high_tower, player),
+ lambda state: state.has(ItemName.boss_leon, player))
+ set_rule(world.get_location(LocationName.manor_observatory_base, player),
+ lambda state: state.has(ItemName.boss_leon, player))
+ set_rule(world.get_location(LocationName.manor_observatory_scope, player),
+ lambda state: state.has(ItemName.boss_leon, 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))
+ set_rule(world.get_location(LocationName.tower, player),
+ lambda state: state._legacy_has_stat_upgrades(player, 25) and state.has(ItemName.boss_alexander, 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))
+
+ # 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.fountain, player),
+ lambda state: state._legacy_has_stat_upgrades(player, 50) and state.has(ItemName.boss_herodotus, player))
+
+ world.completion_condition[player] = lambda state: state.has(ItemName.boss_fountain, player)
diff --git a/worlds/legacy/__init__.py b/worlds/legacy/__init__.py
new file mode 100644
index 00000000..41e7a20b
--- /dev/null
+++ b/worlds/legacy/__init__.py
@@ -0,0 +1,105 @@
+import typing
+
+from BaseClasses import Item, MultiWorld
+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 .Regions import create_regions
+from .Rules import set_rules
+from .Names import ItemName
+from ..AutoWorld import World
+
+
+class LegacyWorld(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"
+ options = legacy_options
+ topology_present = False
+ data_version = 1
+
+ item_name_to_id = {name: data.code for name, data in item_table.items()}
+ location_name_to_id = location_table
+
+ 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],
+ "vendors": self.world.vendors[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],
+ "death_link": self.world.death_link[self.player],
+ }
+
+ def _create_items(self, name: str):
+ data = item_table[name]
+ return [self.create_item(name)] * data.quantity
+
+ 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 slot_data
+
+ 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)
+
+ # Fill item pool with all required items
+ for item in {**skill_unlocks_table, **blueprints_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)
+
+ # 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_shinobi),
+ *self._create_items(ItemName.progressive_miner),
+ *self._create_items(ItemName.progressive_lich),
+ *self._create_items(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))
+ self.world.push_precollected(self.world.create_item(ItemName.enchantress, self.player))
+ else:
+ itempool += [self.create_item(ItemName.blacksmith), self.create_item(ItemName.enchantress)]
+
+ # Add Arcitect.
+ itempool += [self.create_item(ItemName.architect)]
+
+ # 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 += [self.create_item(item)]
+
+ self.world.itempool += itempool
+
+ def create_regions(self):
+ create_regions(self.world, self.player)
+
+ def create_item(self, name: str) -> Item:
+ data = item_table[name]
+ return LegacyItem(name, data.progression, data.code, self.player)
+
+ def set_rules(self):
+ set_rules(self.world, self.player)