diff --git a/.gitignore b/.gitignore index 8f9ed6df14fe..68f1a89039a5 100644 --- a/.gitignore +++ b/.gitignore @@ -32,6 +32,8 @@ *.apsave *.BIN *.puml +*.nds +*.apcvpor setups build diff --git a/worlds/cv_por/Client.py b/worlds/cv_por/Client.py new file mode 100644 index 000000000000..ad9c2fec5396 --- /dev/null +++ b/worlds/cv_por/Client.py @@ -0,0 +1,25 @@ +from typing import TYPE_CHECKING +import worlds._bizhawk as bizhawk +from worlds._bizhawk.client import BizHawkClient +from .Rom import world_version + +if TYPE_CHECKING: + from worlds._bizhawk.context import BizHawkClientContext + + +class PoRClient(BizHawkClient): + game = "Castlevania: Portrait of Ruin" + system = ("NDS") + patch_suffix = ".apcvpor" + most_recent_connect: str = "" + client_version: str = world_version + + def __init__(self) -> None: + super().__init__() + + async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: + return False + + + async def game_watcher(self, ctx: "BizHawkClientContext") -> None: + print("NOT DONE YET") \ No newline at end of file diff --git a/worlds/cv_por/Items.py b/worlds/cv_por/Items.py new file mode 100644 index 000000000000..05ff633eedfa --- /dev/null +++ b/worlds/cv_por/Items.py @@ -0,0 +1,571 @@ +from typing import Dict, Set, NamedTuple, Optional +from BaseClasses import ItemClassification + + +class ItemData(NamedTuple): + category: str + code: Optional[int] + classification: ItemClassification + default_count: Optional[int] = 1 + + +item_table: Dict[str, ItemData] = { + "$1": ItemData("Money", 0x100, ItemClassification.filler, 0), + "$10": ItemData("Money", 0x101, ItemClassification.filler, 0), + "$50": ItemData("Money", 0x102, ItemClassification.filler, 0), + "$100": ItemData("Money", 0x103, ItemClassification.filler, 0), + "$500": ItemData("Money", 0x104, ItemClassification.filler, 0), + "$1000": ItemData("Money", 0x105, ItemClassification.filler, 0), + "$2000": ItemData("Money", 0x106, ItemClassification.filler, 0), + + "Potion": ItemData("Consumables", 0x0200, ItemClassification.filler, 0), + "High Potion": ItemData("Consumables", 0x0201, ItemClassification.filler, 0), + "Super Potion": ItemData("Consumables", 0x0202, ItemClassification.filler, 0), + "Tonic": ItemData("Consumables", 0x0203, ItemClassification.filler, 0), + "High Tonic": ItemData("Consumables", 0x0204, ItemClassification.filler, 0), + "Super Tonic": ItemData("Consumables", 0x0205, ItemClassification.filler, 0), + "Anti-Venom": ItemData("Consumables", 0x0206, ItemClassification.filler, 0), + "Uncurse Potion": ItemData("Consumables", 0x0207, ItemClassification.filler, 0), + "HP Max up": ItemData("Upgrades", 0x0208, ItemClassification.useful, 16), + "MP Max up": ItemData("Upgrades", 0x0209, ItemClassification.useful, 16), + "Beef Jerky": ItemData("Consumables", 0x020A, ItemClassification.filler, 0), + "Tasty Meat": ItemData("Consumables", 0x020B, ItemClassification.filler, 0), + "New York Steak": ItemData("Consumables", 0x020C, ItemClassification.filler, 0), + "Rice Ball": ItemData("Consumables", 0x020D, ItemClassification.filler, 0), + "Mushroom": ItemData("Consumables", 0x020E, ItemClassification.filler, 0), + "Corn Soup": ItemData("Consumables", 0x020F, ItemClassification.filler, 0), + "Curry": ItemData("Consumables", 0x0210, ItemClassification.filler, 0), + "Ramen Noodles": ItemData("Consumables", 0x0211, ItemClassification.filler, 0), + "Mint Sundae": ItemData("Consumables", 0x0212, ItemClassification.filler, 0), + "Cream Puff": ItemData("Consumables", 0x0213, ItemClassification.filler, 0), + "Milk": ItemData("Consumables", 0x0214, ItemClassification.filler, 0), + "Coffee": ItemData("Consumables", 0x0215, ItemClassification.filler, 0), + "Tea": ItemData("Consumables", 0x0216, ItemClassification.filler, 0), + "Pudding": ItemData("Consumables", 0x0217, ItemClassification.filler, 0), + "Strawberry": ItemData("Consumables", 0x0218, ItemClassification.filler, 0), + "Melon": ItemData("Consumables", 0x0219, ItemClassification.filler, 0), + "Grapes": ItemData("Consumables", 0x021A, ItemClassification.filler, 0), + "Hamburger": ItemData("Consumables", 0x021B, ItemClassification.filler, 0), + "Fried Chicken": ItemData("Consumables", 0x021C, ItemClassification.filler, 0), + "Hot Dog": ItemData("Consumables", 0x021D, ItemClassification.filler, 0), + "Pumpkin Pie": ItemData("Consumables", 0x021E, ItemClassification.filler, 0), + "Pancake": ItemData("Consumables", 0x021F, ItemClassification.filler, 0), + "Tinned Spinach": ItemData("Consumables", 0x0220, ItemClassification.filler, 0), + "Foie Gras": ItemData("Consumables", 0x0221, ItemClassification.filler, 0), + "Caviar": ItemData("Consumables", 0x0222, ItemClassification.filler, 0), + "Truffle": ItemData("Consumables", 0x0223, ItemClassification.filler, 0), + "Paella": ItemData("Consumables", 0x0224, ItemClassification.filler, 0), + "Spaghetti": ItemData("Consumables", 0x0225, ItemClassification.filler, 0), + "Ice Cream": ItemData("Consumables", 0x0226, ItemClassification.filler, 0), + "Meuniere": ItemData("Consumables", 0x0227, ItemClassification.filler, 0), + "Boiled Starfish": ItemData("Consumables", 0x0228, ItemClassification.filler, 0), + "Wheat Roll": ItemData("Consumables", 0x0229, ItemClassification.filler, 0), + "Sachertorte": ItemData("Consumables", 0x022A, ItemClassification.filler, 0), + "NY Cheesecake": ItemData("Consumables", 0x022B, ItemClassification.filler, 0), + "Mille-feuille": ItemData("Consumables", 0x022C, ItemClassification.filler, 0), + "Tarte au Poire": ItemData("Consumables", 0x022D, ItemClassification.filler, 0), + "Gateau Fraise": ItemData("Consumables", 0x022E, ItemClassification.filler, 0), + "Kugelhopf": ItemData("Consumables", 0x022F, ItemClassification.filler, 0), + "Green Tea Cake": ItemData("Consumables", 0x0230, ItemClassification.filler, 0), + "Gateau Marron": ItemData("Consumables", 0x0231, ItemClassification.filler, 0), + "Langues de Chat": ItemData("Consumables", 0x0232, ItemClassification.filler, 0), + "Financier": ItemData("Consumables", 0x0233, ItemClassification.filler, 0), + "Tropical Sundae": ItemData("Consumables", 0x0234, ItemClassification.filler, 0), + "Pudding Bucket": ItemData("Consumables", 0x0235, ItemClassification.filler, 0), + "Piyoko": ItemData("Consumables", 0x0236, ItemClassification.filler, 0), + "1000 Year Egg": ItemData("Consumables", 0x0237, ItemClassification.filler, 0), + "Jamon Iberico": ItemData("Consumables", 0x0238, ItemClassification.filler, 0), + "Spicy Shrimp": ItemData("Consumables", 0x0239, ItemClassification.filler, 0), + "Peking Duck": ItemData("Consumables", 0x023A, ItemClassification.filler, 0), + "Pasta Carbonara": ItemData("Consumables", 0x023B, ItemClassification.filler, 0), + "Penne Arrabiata": ItemData("Consumables", 0x023C, ItemClassification.filler, 0), + "Powdered Milk": ItemData("Consumables", 0x023D, ItemClassification.filler, 0), + "Rotten Durian": ItemData("Consumables", 0x023E, ItemClassification.trap, 0), + "Ground Meat": ItemData("Consumables", 0x023F, ItemClassification.filler, 0), + "Noodle Bun": ItemData("Consumables", 0x0240, ItemClassification.filler, 0), + "Touch & Pop": ItemData("Consumables", 0x0241, ItemClassification.filler, 0), + "Moldy Bread": ItemData("Consumables", 0x0242, ItemClassification.trap, 0), + "Penters Natural": ItemData("Consumables", 0x0243, ItemClassification.filler, 0), + "Birthday Cake": ItemData("Consumables", 0x0244, ItemClassification.filler, 0), + # "Magical Ticket": ItemData("Consumables", 0x0245, ItemClassification.filler, 0), We have infinite of these, AP doesn't need them + "Beehive": ItemData("Consumables", 0x0246, ItemClassification.filler, 0), + "Amanita": ItemData("Consumables", 0x0247, ItemClassification.trap, 0), + "Rotten Meat": ItemData("Consumables", 0x0248, ItemClassification.trap, 0), + "Spoiled Milk": ItemData("Consumables", 0x0249, ItemClassification.trap, 0), + "Rusty Food Tin": ItemData("Consumables", 0x024A, ItemClassification.trap, 0), + "CASTLE MAP 1": ItemData("Key Items", 0x024B, ItemClassification.filler, 0), + "CASTLE MAP 2": ItemData("Key Items", 0x024C, ItemClassification.filler, 0), + "CASTLE MAP 3": ItemData("Key Items", 0x024D, ItemClassification.filler, 0), + "Cog": ItemData("Key Items", 0x024E, ItemClassification.progression), + # "AP Item": ItemData("Consumables", 0x024F, ItemClassification.filler, 0), + # "AP Item": ItemData("Consumables", 0x0250, ItemClassification.filler, 0), These are reserved for offworld items + # "Vic Viper": ItemData("Consumables", 0x0251, ItemClassification.filler, 0), + "Tome of Arms p1": ItemData("Key Items", 0x0252, ItemClassification.useful, 0), + "Tome of Arms p2": ItemData("Key Items", 0x0253, ItemClassification.useful, 0), + "Spade": ItemData("Key Items", 0x0254, ItemClassification.filler, 0), + "Diamond": ItemData("Key Items", 0x0255, ItemClassification.filler, 0), + "Heart": ItemData("Key Items", 0x0256, ItemClassification.filler, 0), + "Club": ItemData("Key Items", 0x0257, ItemClassification.filler, 0), + "Joker": ItemData("Key Items", 0x0258, ItemClassification.filler, 0), + "Statue's Tear": ItemData("Key Items", 0x0259, ItemClassification.useful, 0), + "Record Player": ItemData("Key Items", 0x025A, ItemClassification.useful, 0), + "Record 1": ItemData("Key Items", 0x025B, ItemClassification.filler, 0), + "Record 2": ItemData("Key Items", 0x025C, ItemClassification.filler, 0), + "Record 3": ItemData("Key Items", 0x025D, ItemClassification.filler, 0), + "Record 4": ItemData("Key Items", 0x025E, ItemClassification.filler, 0), + "Record 5": ItemData("Key Items", 0x025F, ItemClassification.filler, 0), + + "Vampire Killer": ItemData("Whips", 0x0301, ItemClassification.useful, 0), # The starter copy + "Leather Whip": ItemData("Whips", 0x0302, ItemClassification.filler, 0), + "Steel Whip": ItemData("Whips", 0x0303, ItemClassification.filler, 0), + "Rose Stem Whip": ItemData("Whips", 0x0304, ItemClassification.filler, 0), + "Medusa Whip": ItemData("Whips", 0x0305, ItemClassification.filler, 0), + "Nebula": ItemData("Whips", 0x0306, ItemClassification.useful), + "Flame Whip": ItemData("Whips", 0x0307, ItemClassification.useful), + "Jet Black Whip": ItemData("Whips", 0x0308, ItemClassification.useful), + "Undead Killer": ItemData("Whips", 0x0309, ItemClassification.useful, 0), + "True Vampire Killer": ItemData("Whips", 0x030A, ItemClassification.useful, 0), + "Richter Vampire Killer": ItemData("Whips", 0x030B, ItemClassification.filler, 0), # Used for richter mode + "Encyclopedia": ItemData("Books", 0x030C, ItemClassification.filler, 0), + "Blank Book": ItemData("Books", 0x030D, ItemClassification.filler, 0), + "Tristan&Isolde": ItemData("Books", 0x030E, ItemClassification.useful), + "Don Quixote": ItemData("Books", 0x030F, ItemClassification.useful), + "Book of Death": ItemData("Books", 0x0310, ItemClassification.useful), + "Tome of Arms 1": ItemData("Books", 0x0311, ItemClassification.useful), + "Tome of Arms 2": ItemData("Books", 0x0312, ItemClassification.useful), + "Tome of Arms 3": ItemData("Books", 0x0313, ItemClassification.useful), + "Tome of Arms X": ItemData("Books", 0x0314, ItemClassification.useful, 0), + "Knife": ItemData("Knives", 0x0315, ItemClassification.filler, 0), + "Combat Knife": ItemData("Knives", 0x0316, ItemClassification.filler, 0), + "Baselard": ItemData("Knives", 0x0317, ItemClassification.filler, 0), + "Katar": ItemData("Knives", 0x0318, ItemClassification.filler, 0), + "Cinquedea": ItemData("Knives", 0x319, ItemClassification.filler, 0), + "Short Sword": ItemData("Swords", 0x031A, ItemClassification.filler, 0), + "Cutlass": ItemData("Swords", 0x031B, ItemClassification.filler, 0), + "Long Sword": ItemData("Swords", 0x031C, ItemClassification.useful), + "Gladius": ItemData("Swords", 0x031D, ItemClassification.filler, 0), + "Milican's Sword": ItemData("Swords", 0x031E, ItemClassification.useful), + "Rahab's Frost": ItemData("Swords", 0x031F, ItemClassification.filler, 0), + "Agni's Flame": ItemData("Swords", 0x0320, ItemClassification.filler, 0), + "Assassin Blade": ItemData("Swords", 0x0321, ItemClassification.useful, 0), + "Heaven's Sword": ItemData("Swords", 0x0322, ItemClassification.filler, 0), + "Bakatwa": ItemData("Swords", 0x0323, ItemClassification.useful), + "Jagdplaute": ItemData("Swords", 0x0324, ItemClassification.filler, 0), + "Stellar Sword": ItemData("Swords", 0x0325, ItemClassification.useful, 0), + "Damascus Sword": ItemData("Swords", 0x0326, ItemClassification.filler, 0), + "Claymore": ItemData("Greatswords", 0x0327, ItemClassification.filler, 0), + "Falchion": ItemData("Greatswords", 0x0328, ItemClassification.filler, 0), + "Great Sword": ItemData("Greatswords", 0x0329, ItemClassification.filler, 0), + "Zweihander": ItemData("Greatswords", 0x032A, ItemClassification.filler, 0), + "Royal Sword": ItemData("Greatswords", 0x032B, ItemClassification.useful, 0), + "Dragon Slayer": ItemData("Greatswords", 0x032C, ItemClassification.filler, 0), + "Final Sword": ItemData("Greatswords", 0x032D, ItemClassification.filler, 0), + "Holy Claymore": ItemData("Greatswords", 0x032E, ItemClassification.filler, 0), + "Spear": ItemData("Spears", 0x032F, ItemClassification.filler, 0), + "Partisan": ItemData("Spears", 0x0330, ItemClassification.filler, 0), + "Halberd": ItemData("Spears", 0x0331, ItemClassification.useful), + "Lance": ItemData("Spears", 0x0332, ItemClassification.filler, 0), + "Trident": ItemData("Spears", 0x0333, ItemClassification.filler, 0), + "Long Spear": ItemData("Spears", 0x0334, ItemClassification.filler, 0), + "Couse": ItemData("Spears", 0x0335, ItemClassification.filler, 0), + "Sarissa": ItemData("Spears", 0x0336, ItemClassification.filler, 0), + "Alucard's Spear": ItemData("Spears", 0x0337, ItemClassification.useful, 0), + "Axe": ItemData("Axes", 0x0338, ItemClassification.filler, 0), + "Mace": ItemData("Axes", 0x0339, ItemClassification.filler, 0), + "Battle Axe": ItemData("Axes", 0x033A, ItemClassification.filler, 0), + "Morning Star": ItemData("Axes", 0x033B, ItemClassification.filler, 0), + "Bhuj": ItemData("Axes", 0x033C, ItemClassification.filler, 0), + "Great Axe": ItemData("Axes", 0x033D, ItemClassification.useful), + "Voulge": ItemData("Axes", 0x033E, ItemClassification.filler, 0), + "Bullova": ItemData("Axes", 0x033F, ItemClassification.filler, 0), + "Golden Axe": ItemData("Axes", 0x0340, ItemClassification.filler, 0), + "Brass Knuckles": ItemData("Fists", 0x0341, ItemClassification.filler, 0), + "Cestus": ItemData("Fists", 0x0342, ItemClassification.filler, 0), + "Whip Knuckles": ItemData("Fists", 0x0343, ItemClassification.useful), + "Bahg Nahk": ItemData("Fists", 0x0344, ItemClassification.useful), + "Bullet Punch": ItemData("Fists", 0x0345, ItemClassification.useful, 0), + "Illusion Fist": ItemData("Fists", 0x0346, ItemClassification.filler, 0), + "Kaiser Knuckles": ItemData("Fists", 0x0347, ItemClassification.useful), + "Tori": ItemData("Other Weapons", 0x0348, ItemClassification.filler, 0), + + "Casual Clothes": ItemData("Body Armor", 0x0401, ItemClassification.filler, 0), + "Hobo's Clothes": ItemData("Body Armor", 0x0402, ItemClassification.filler, 0), + "Houppelande": ItemData("Body Armor", 0x0403, ItemClassification.filler, 0), + "Poncho": ItemData("Body Armor", 0x0404, ItemClassification.filler, 0), + "Combat Fatigues": ItemData("Body Armor", 0x0405, ItemClassification.filler, 0), + "Adrenaline Gear": ItemData("Body Armor", 0x0406, ItemClassification.filler, 0), + "Kalasiris": ItemData("Body Armor", 0x0407, ItemClassification.filler, 0), + "Feather Gear": ItemData("Body Armor", 0x0408, ItemClassification.filler, 0), + "Tailcoat": ItemData("Jonathan Armor", 0x0409, ItemClassification.filler, 0), + "Biker's Jacket": ItemData("Jonathan Armor", 0x040A, ItemClassification.filler, 0), + "Justaucorps": ItemData("Jonathan Armor", 0x040B, ItemClassification.filler, 0), + "Tuxedo Coat": ItemData("Jonathan Armor", 0x040C, ItemClassification.filler, 0), + "Battle Jacket": ItemData("Jonathan Armor", 0x040D, ItemClassification.filler, 0), + "Surcoat": ItemData("Jonathan Armor", 0x040E, ItemClassification.filler, 0), + "Leather Cuirass": ItemData("Jonathan Armor", 0x040F, ItemClassification.filler, 0), + "Copper Plate": ItemData("Jonathan Armor", 0x0410, ItemClassification.filler, 0), + "Iron Plate": ItemData("Jonathan Armor", 0x0411, ItemClassification.filler, 0), + "Silver Plate": ItemData("Jonathan Armor", 0x0412, ItemClassification.filler, 0), + "Gold Plate": ItemData("Jonathan Armor", 0x0413, ItemClassification.filler, 0), + "Lorica": ItemData("Jonathan Armor", 0x0414, ItemClassification.filler, 0), + "Platinum Plate": ItemData("Jonathan Armor", 0x0415, ItemClassification.filler, 0), + "Samurai Plate": ItemData("Jonathan Armor", 0x0416, ItemClassification.filler, 0), + "Holy Mail": ItemData("Jonathan Armor", 0x0417, ItemClassification.filler, 0), + "Berserker Mail": ItemData("Jonathan Armor", 0x0418, ItemClassification.filler, 0), + "Ancient Armor": ItemData("Jonathan Armor", 0x0419, ItemClassification.filler, 0), + "Healing Mail": ItemData("Jonathan Armor", 0x041A, ItemClassification.useful), + "Mirror Cuirass": ItemData("Jonathan Armor", 0x041B, ItemClassification.filler, 0), + "Spiked Mail": ItemData("Jonathan Armor", 0x041C, ItemClassification.filler, 0), + "Scout Armor": ItemData("Jonathan Armor", 0x041D, ItemClassification.filler, 0), + "Heavy Armor": ItemData("Jonathan Armor", 0x041E, ItemClassification.filler, 0), + "Impervious Mail": ItemData("Jonathan Armor", 0x041F, ItemClassification.filler, 0), + "Leather Corset": ItemData("Charlotte Armor", 0x0420, ItemClassification.filler, 0), + "Jade Corset": ItemData("Charlotte Armor", 0x0421, ItemClassification.filler, 0), + "Amethyst Corset": ItemData("Charlotte Armor", 0x0422, ItemClassification.filler, 0), + "Emerald Corset": ItemData("Charlotte Armor", 0x0423, ItemClassification.filler, 0), + "Ruby Corset": ItemData("Charlotte Armor", 0x0424, ItemClassification.filler, 0), + "Sapphire Corset": ItemData("Charlotte Armor", 0x0425, ItemClassification.filler, 0), + "Lilith Corset": ItemData("Charlotte Armor", 0x0426, ItemClassification.filler, 0), + "Platinum Corset": ItemData("Charlotte Armor", 0x0427, ItemClassification.useful), + "Diamond Corset": ItemData("Charlotte Armor", 0x0428, ItemClassification.filler, 0), + "Kirtle": ItemData("Charlotte Armor", 0x0429, ItemClassification.filler, 0), + "Miko Dress": ItemData("Charlotte Armor", 0x042A, ItemClassification.filler, 0), + "Cotton Apron": ItemData("Charlotte Armor", 0x042B, ItemClassification.filler, 0), + "Silk Negligee": ItemData("Charlotte Armor", 0x042C, ItemClassification.filler, 0), + "Bathrobe": ItemData("Charlotte Armor", 0x042D, ItemClassification.filler, 0), + "Frilly Camisole": ItemData("Charlotte Armor", 0x042E, ItemClassification.filler, 0), + "Sequined Dress": ItemData("Charlotte Armor", 0x042F, ItemClassification.filler, 0), + "Clown Shirt": ItemData("Charlotte Armor", 0x0430, ItemClassification.filler, 0), + "Nun's Robes": ItemData("Charlotte Armor", 0x0431, ItemClassification.useful), + "Cocktail Dress": ItemData("Charlotte Armor", 0x0432, ItemClassification.filler, 0), + "Dancer's Blouse": ItemData("Charlotte Armor", 0x0433, ItemClassification.filler, 0), + "Cotehardie": ItemData("Charlotte Armor", 0x0434, ItemClassification.filler, 0), + "Wedding Dress": ItemData("Charlotte Armor", 0x0435, ItemClassification.filler, 0), + "Europa's Dress": ItemData("Charlotte Armor", 0x0436, ItemClassification.filler, 0), + "Dalmatica": ItemData("Charlotte Armor", 0x0437, ItemClassification.filler, 0), + "Princess Coat": ItemData("Charlotte Armor", 0x0438, ItemClassification.filler, 0), + "Robe Decollete": ItemData("Charlotte Armor", 0x0439, ItemClassification.useful), + + "Eye for Decay": ItemData("Headgear", 0x0501, ItemClassification.filler, 0), + "Gambler Glasses": ItemData("Headgear", 0x0502, ItemClassification.useful, 0), + "Sunglasses": ItemData("Headgear", 0x0503, ItemClassification.filler, 0), + "Thick Glasses": ItemData("Headgear", 0x0504, ItemClassification.filler, 0), + "Glasses": ItemData("Headgear", 0x0505, ItemClassification.filler, 0), + "Monocle": ItemData("Headgear", 0x0506, ItemClassification.filler, 0), + "Goggles": ItemData("Headgear", 0x0507, ItemClassification.filler, 0), + "Combat Beret": ItemData("Headgear", 0x0508, ItemClassification.useful), + "Uraeus": ItemData("Headgear", 0x0509, ItemClassification.filler, 0), + "Royal Crown": ItemData("Headgear", 0x050A, ItemClassification.filler, 0), + "Headguard": ItemData("Jonathan Headgear", 0x050B, ItemClassification.filler, 0), + "Bandana": ItemData("Jonathan Headgear", 0x050C, ItemClassification.useful), + "Iron Helmet": ItemData("Jonathan Headgear", 0x050D, ItemClassification.filler, 0), + "Stone Mask": ItemData("Jonathan Headgear", 0x050E, ItemClassification.filler, 0), + "Viking Helmet": ItemData("Jonathan Headgear", 0x050F, ItemClassification.filler, 0), + "Ballroom Masque": ItemData("Jonathan Headgear", 0x0510, ItemClassification.filler, 0), + "Fedora": ItemData("Jonathan Headgear", 0x0511, ItemClassification.filler, 0), + "Porkpie Hat": ItemData("Jonathan Headgear", 0x0512, ItemClassification.filler, 0), + "Silk Hat": ItemData("Jonathan Headgear", 0x0513, ItemClassification.filler, 0), + "Traveler's Hat": ItemData("Headgear", 0x0514, ItemClassification.filler, 0), + "Attica Helmet": ItemData("Jonathan Headgear", 0x0515, ItemClassification.useful), + "Samurai Helmet": ItemData("Jonathan Headgear", 0x0516, ItemClassification.filler, 0), + "Shogun Helmet": ItemData("Jonathan Headgear", 0x0517, ItemClassification.useful), + "Skull Mask": ItemData("Jonathan Headgear", 0x0518, ItemClassification.filler, 0), + "Velvet Ribbon": ItemData("Charlotte Headgear", 0x0519, ItemClassification.useful), + "Witch's Hat": ItemData("Charlotte Headgear", 0x051A, ItemClassification.filler, 0), + "Mourning Veil": ItemData("Charlotte Headgear", 0x051B, ItemClassification.filler, 0), + "Nun's Habit": ItemData("Charlotte Headgear", 0x051C, ItemClassification.useful), + "Open Veil": ItemData("Charlotte Headgear", 0x051D, ItemClassification.filler, 0), + "Holy Veil": ItemData("Charlotte Headgear", 0x051E, ItemClassification.filler, 0), + "Clover Tiara": ItemData("Charlotte Headgear", 0x051F, ItemClassification.useful), + "Pearl Tiara": ItemData("Charlotte Headgear", 0x0520, ItemClassification.filler, 0), + "Diamond Tiara": ItemData("Charlotte Headgear", 0x0521, ItemClassification.filler, 0), + "Princess Tiara": ItemData("Charlotte Headgear", 0x0522, ItemClassification.useful), + "Muse's Tiara": ItemData("Charlotte Headgear", 0x0523, ItemClassification.filler, 0), + "Midnight Tiara": ItemData("Charlotte Headgear", 0x0524, ItemClassification.filler, 0), + "Arachne Hennin": ItemData("Charlotte Headgear", 0x0525, ItemClassification.filler, 0), + + "Sandals": ItemData("Boots", 0x0601, ItemClassification.filler, 0), + "Engineer Boots": ItemData("Boots", 0x0602, ItemClassification.filler, 0), + "Royal Sandals": ItemData("Boots", 0x0603, ItemClassification.filler, 0), + "Combat Boots": ItemData("Boots", 0x0604, ItemClassification.filler, 0), + "Moonwalkers": ItemData("Boots", 0x0605, ItemClassification.useful), + "Slick Boots": ItemData("Boots", 0x0606, ItemClassification.filler, 0), + "Spiked Boots": ItemData("Boots", 0x0607, ItemClassification.useful), + "Mercury Boots": ItemData("Boots", 0x0608, ItemClassification.useful), + "Winged Boots": ItemData("Boots", 0x0609, ItemClassification.useful), + "Hiking Boots": ItemData("Jonathan Boots", 0x060A, ItemClassification.filler, 0), + "Oxfords": ItemData("Jonathan Boots", 0x060B, ItemClassification.filler, 0), + "Wingtips": ItemData("Jonathan Boots", 0x060C, ItemClassification.filler, 0), + "Iron Leggings": ItemData("Jonathan Boots", 0x060D, ItemClassification.filler, 0), + "Silver Leggings": ItemData("Jonathan Boots", 0x060E, ItemClassification.filler, 0), + "Gold Leggings": ItemData("Jonathan Boots", 0x060F, ItemClassification.filler, 0), + "Steel Greaves": ItemData("Jonathan Boots", 0x0610, ItemClassification.useful), + "Platinum Lggngs": ItemData("Jonathan Boots", 0x0611, ItemClassification.filler, 0), + "Samurai Leggngs": ItemData("Jonathan Boots", 0x0612, ItemClassification.useful), + "Enamel Pinheels": ItemData("Charlotte Boots", 0x0613, ItemClassification.useful, 0), + "Inuit Boots": ItemData("Charlotte Boots", 0x0614, ItemClassification.filler, 0), + "Succubus Boots": ItemData("Charlotte Boots", 0x0615, ItemClassification.filler, 0), + "Glamorous Pumps": ItemData("Charlotte Boots", 0x0616, ItemClassification.useful), + "Silent Sandals": ItemData("Charlotte Boots", 0x0617, ItemClassification.filler, 0), + "Artemis Shoes": ItemData("Charlotte Boots", 0x0618, ItemClassification.useful), + "Nun's Shoes": ItemData("Charlotte Boots", 0x0619, ItemClassification.useful), + "Glass Shoes": ItemData("Charlotte Boots", 0x061A, ItemClassification.filler, 0), + "Prima Shoes": ItemData("Charlotte Boots", 0x061B, ItemClassification.filler, 0), + "Diamond Shoes": ItemData("Charlotte Boots", 0x061C, ItemClassification.filler, 0), + + "Studded Choker": ItemData("Accessories", 0x0701, ItemClassification.filler, 0), + "Charm Necklace": ItemData("Accessories", 0x0702, ItemClassification.useful), + "Stella's Locket": ItemData("Key Items", 0x0703, ItemClassification.progression), + "Astral Ring": ItemData("Accessories", 0x0704, ItemClassification.filler, 0), + "Engagement Ring": ItemData("Accessories", 0x0705, ItemClassification.useful), + "Magus Ring": ItemData("Accessories", 0x0706, ItemClassification.filler, 0), + "Gold Ring": ItemData("Accessories", 0x0707, ItemClassification.filler, 0), + "Shaman Ring": ItemData("Accessories", 0x0708, ItemClassification.useful), + "Miser Ring": ItemData("Accessories", 0x0709, ItemClassification.useful, 0), + "Sage Ring": ItemData("Accessories", 0x070A, ItemClassification.useful, 0), + "Thief Ring": ItemData("Accessories", 0x070B, ItemClassification.useful, 0), + "Pearl Ring": ItemData("Accessories", 0x070C, ItemClassification.filler, 0), + "Tough Ring": ItemData("Accessories", 0x070D, ItemClassification.filler, 0), + "Master Ring": ItemData("Accessories", 0x070E, ItemClassification.filler, 0), + "Skull Ring": ItemData("Accessories", 0x070F, ItemClassification.filler, 0), + "Immunity Ring": ItemData("Accessories", 0x0710, ItemClassification.useful, 0), + "Blessed Ring": ItemData("Accessories", 0x0711, ItemClassification.useful, 0), + "Hercules Ring": ItemData("Accessories", 0x0712, ItemClassification.filler, 0), + "Cape": ItemData("Accessories", 0x0713, ItemClassification.filler, 0), + "Hero's Cape": ItemData("Accessories", 0x0714, ItemClassification.filler, 0), + "Blue Cape": ItemData("Accessories", 0x0715, ItemClassification.filler, 0), + "Elven Cape": ItemData("Accessories", 0x0716, ItemClassification.filler, 0), + "Assassin's Cape": ItemData("Accessories", 0x0717, ItemClassification.useful), + "Invisible Cape": ItemData("Accessories", 0x0718, ItemClassification.filler, 0), + "Holy Mantle": ItemData("Accessories", 0x0719, ItemClassification.filler, 0), + "Paludamentum": ItemData("Accessories", 0x071A, ItemClassification.filler, 0), + "Blessed Ankh": ItemData("Accessories", 0x071B, ItemClassification.filler, 0), + "White Corsage": ItemData("Accessories", 0x071C, ItemClassification.useful), + "Orchid Corsage": ItemData("Accessories", 0x071D, ItemClassification.filler, 0), + "Rose Corsage": ItemData("Accessories", 0x071E, ItemClassification.filler, 0), + "Sorceress Crest": ItemData("Accessories", 0x071F, ItemClassification.useful, 0), + "Forget-Me-Not": ItemData("Accessories", 0x0720, ItemClassification.filler, 0), + "Crystal Necklet": ItemData("Accessories", 0x0721, ItemClassification.useful), + "Platinum Chain": ItemData("Accessories", 0x0722, ItemClassification.filler, 0), + "Heart Brooch": ItemData("Accessories", 0x0723, ItemClassification.filler, 0), + "Astral Brooch": ItemData("Accessories", 0x0724, ItemClassification.filler, 0), + "Laurel Brooch": ItemData("Accessories", 0x0725, ItemClassification.useful), + "Abalone Brooch": ItemData("Accessories", 0x0726, ItemClassification.filler, 0), + "Diamond Brooch": ItemData("Accessories", 0x0727, ItemClassification.useful), + "Moon Brooch": ItemData("Accessories", 0x0728, ItemClassification.filler, 0), + "Venus Brooch": ItemData("Accessories", 0x0729, ItemClassification.useful), + + "Puppet Master": ItemData("Subweapons", 0x0801, ItemClassification.progression, 0), + "R. Axe": ItemData("Subweapons", 0x0802, ItemClassification.filler, 0), + "R. Cross": ItemData("Subweapons", 0x0803, ItemClassification.filler, 0), + "R. Holy Water": ItemData("Subweapons", 0x0804, ItemClassification.filler, 0), + "Julius Cross": ItemData("Subweapons", 0x0805, ItemClassification.filler, 0), + "Seiryu": ItemData("Subweapons", 0x0806, ItemClassification.filler, 0), + "Suzaku": ItemData("Subweapons", 0x0807, ItemClassification.filler, 0), + "Byakko": ItemData("Subweapons", 0x0808, ItemClassification.filler, 0), + "Gnebu": ItemData("Subweapons", 0x0809, ItemClassification.filler, 0), + "Knife Subweapon": ItemData("Subweapons", 0x080A, ItemClassification.useful), + "Axe Subweapon": ItemData("Subweapons", 0x080B, ItemClassification.filler, 0), + "Cross": ItemData("Subweapons", 0x080C, ItemClassification.useful, 0), + "Holy Water": ItemData("Subweapons", 0x080D, ItemClassification.useful, 0), + "Bible": ItemData("Subweapons", 0x080E, ItemClassification.useful, 0), + "Javelin": ItemData("Subweapons", 0x080F, ItemClassification.filler, 0), + "Ricochet Rock": ItemData("Subweapons", 0x0810, ItemClassification.filler, 0), + "Boomerang": ItemData("Subweapons", 0x0811, ItemClassification.filler, 0), + "Bwaka Knife": ItemData("Subweapons", 0x0812, ItemClassification.useful), + "Shuriken": ItemData("Subweapons", 0x0813, ItemClassification.filler, 0), + "Yagyu Shuriken": ItemData("Subweapons", 0x0814, ItemClassification.filler, 0), + "Discus": ItemData("Subweapons", 0x0815, ItemClassification.filler, 0), + "Kunimitsu": ItemData("Subweapons", 0x0816, ItemClassification.filler, 0), + "Kunai": ItemData("Subweapons", 0x0817, ItemClassification.filler, 0), + "Paper Airplane": ItemData("Subweapons", 0x0818, ItemClassification.useful), + "Cream Pie": ItemData("Subweapons", 0x0819, ItemClassification.useful), + "Crossbow": ItemData("Subweapons", 0x081A, ItemClassification.filler, 0), + "Dart": ItemData("Subweapons", 0x081B, ItemClassification.filler, 0), + "Grenade": ItemData("Subweapons", 0x081C, ItemClassification.useful), + "Steel Ball": ItemData("Subweapons", 0x081D, ItemClassification.useful), + "Stonewall": ItemData("Subweapons", 0x081E, ItemClassification.useful), + "Offensive Form": ItemData("Subweapons", 0x081F, ItemClassification.useful), + "Defensive Form": ItemData("Subweapons", 0x0820, ItemClassification.filler, 0), + "Taunt": ItemData("Subweapons", 0x0821, ItemClassification.useful), + "Wrecking Ball": ItemData("Subweapons", 0x0822, ItemClassification.filler, 0), + "Rampage": ItemData("Subweapons", 0x0823, ItemClassification.filler, 0), + "Knee Strike": ItemData("Subweapons", 0x0824, ItemClassification.useful), + "Aura Blast": ItemData("Subweapons", 0x0825, ItemClassification.useful), + "Rocket Slash": ItemData("Subweapons", 0x0826, ItemClassification.useful, 0), + + "Toad Morph": ItemData("Spells", 0x0827, ItemClassification.progression), + "Owl Morph": ItemData("Spells", 0x0828, ItemClassification.progression, 0), + "Sanctuary": ItemData("Spells", 0x0829, ItemClassification.progression), + "Speed Up": ItemData("Spells", 0x082A, ItemClassification.progression), + "Berserker": ItemData("Spells", 0x082B, ItemClassification.filler, 0), + "Eye for an Eye": ItemData("Spells", 0x082C, ItemClassification.useful), + "Clear Skies": ItemData("Spells", 0x082D, ItemClassification.useful), + "Time Stop": ItemData("Spells", 0x082E, ItemClassification.useful, 0), + "Heal": ItemData("Spells", 0x082F, ItemClassification.useful, 0), + "Cure Poison": ItemData("Spells", 0x0830, ItemClassification.useful), + "Cure Curse": ItemData("Spells", 0x0831, ItemClassification.filler, 0), + "STR Boost": ItemData("Spells", 0x0832, ItemClassification.useful), + "CON Boost": ItemData("Spells", 0x0833, ItemClassification.useful), + "INT Boost": ItemData("Spells", 0x0834, ItemClassification.useful), + "MIND Boost": ItemData("Spells", 0x0835, ItemClassification.useful), + "LUCK Boost": ItemData("Spells", 0x0836, ItemClassification.filler, 0), + "ALL Boost": ItemData("Spells", 0x0837, ItemClassification.useful), + "Gale Force": ItemData("Spells", 0x0838, ItemClassification.useful), + "Rock Riot": ItemData("Spells", 0x0839, ItemClassification.filler, 0), + "Raging Fire": ItemData("Spells", 0x083A, ItemClassification.useful), + "Ice Fang": ItemData("Spells", 0x083B, ItemClassification.useful), + "Thunderbolt": ItemData("Spells", 0x083C, ItemClassification.useful), + "Spirit of Light": ItemData("Spells", 0x083D, ItemClassification.filler, 0), + "Dark Rift": ItemData("Spells", 0x083E, ItemClassification.filler, 0), + "Tempest": ItemData("Spells", 0x083F, ItemClassification.useful), + "Stone Circle": ItemData("Spells", 0x0840, ItemClassification.filler, 0), + "Ice Needle": ItemData("Spells", 0x0841, ItemClassification.filler, 0), + "Explosion": ItemData("Spells", 0x0842, ItemClassification.filler, 0), + "Chain Lightning": ItemData("Spells", 0x0843, ItemClassification.filler, 0), + "Piercing Beam": ItemData("Spells", 0x0844, ItemClassification.useful), + "Nightmare": ItemData("Spells", 0x0845, ItemClassification.filler, 0), + "Summon Medusa": ItemData("Spells", 0x0846, ItemClassification.filler, 0), + "Acidic Bubbles": ItemData("Spells", 0x0847, ItemClassification.filler, 0), + "Hex": ItemData("Spells", 0x0848, ItemClassification.filler, 0), + "Salamander": ItemData("Spells", 0x0849, ItemClassification.filler, 0), + "Cocytus": ItemData("Spells", 0x084A, ItemClassification.useful, 0), + "Thor's Bellow": ItemData("Spells", 0x084B, ItemClassification.filler, 0), + "Summon Crow": ItemData("Spells", 0x084C, ItemClassification.filler, 0), + "Summon Skeleton": ItemData("Spells", 0x084D, ItemClassification.filler, 0), + "Summon Ghost": ItemData("Spells", 0x084E, ItemClassification.filler, 0), + "Summon Gunman": ItemData("Spells", 0x084F, ItemClassification.filler, 0), + "Summon Frog": ItemData("Spells", 0x0850, ItemClassification.filler, 0), + + "Rush": ItemData("Dual Crashes", 0x0852, ItemClassification.useful), + "Holy Lightning": ItemData("Dual Crashes", 0x0853, ItemClassification.useful), + "Axe Bomber": ItemData("Dual Crashes", 0x0854, ItemClassification.useful), + "1,000 Blades": ItemData("Dual Crashes", 0x0855, ItemClassification.useful), + "Volcano": ItemData("Dual Crashes", 0x0856, ItemClassification.useful), + "Meteor": ItemData("Dual Crashes", 0x0857, ItemClassification.useful), + "Grand Cruz": ItemData("Dual Crashes", 0x0858, ItemClassification.useful, 0), + "Divine Storm": ItemData("Dual Crashes", 0x0859, ItemClassification.useful), + "Dark Gate": ItemData("Dual Crashes", 0x085A, ItemClassification.useful), + "Greatest Five": ItemData("Dual Crashes", 0x085B, ItemClassification.useful, 0), + + "Change Cube": ItemData("Relics", 0x085C, ItemClassification.progression, 0), + "Call Cube": ItemData("Relics", 0x085D, ItemClassification.progression), + "Skill Cube": ItemData("Relics", 0x085E, ItemClassification.useful), + "Wait Cube": ItemData("Relics", 0x085F, ItemClassification.progression), + "Acrobat Cube": ItemData("Relics", 0x0860, ItemClassification.progression), + "Push Cube": ItemData("Relics", 0x0861, ItemClassification.progression), + "Lizard Tail": ItemData("Relics", 0x0862, ItemClassification.progression), + "Stone of Flight": ItemData("Relics", 0x0863, ItemClassification.progression), + "Griffon Wing": ItemData("Relics", 0x0864, ItemClassification.progression), + "Strength Glove": ItemData("Relics", 0x0865, ItemClassification.progression), + "Spinning Art": ItemData("Relics", 0x0866, ItemClassification.useful, 0), + "Martial Art": ItemData("Relics", 0x0867, ItemClassification.useful, 0), + "Critical Art": ItemData("Relics", 0x0868, ItemClassification.useful), + "Whip Skill 1": ItemData("Relics", 0x0869, ItemClassification.useful), + "Whip Skill 2": ItemData("Relics", 0x086A, ItemClassification.useful), + "Book of Spirits": ItemData("Relics", 0x086B, ItemClassification.useful), + + "Portrait Clear": ItemData("Events", None, ItemClassification.progression, 0), + "Tower Elevator Active": ItemData("Events", None, ItemClassification.progression, 0), + "Dracula Defeated": ItemData("Events", None, ItemClassification.progression, 0), + "Brauner Defeated": ItemData("Events", None, ItemClassification.progression, 0), +} + + +def get_item_names_per_category() -> Dict[str, Set[str]]: + categories: Dict[str, Set[str]] = {} + + for name, data in item_table.items(): + if data.category != "Events": + categories.setdefault(data.category, set()).add(name) + + return categories + + +money_table = [ + "$1", + "$10", + "$50", + "$100", + "$500", + "$1000", + "$2000" +] + +consumable_table = [ + "Potion", + "High Potion", + "Tonic", + "High Tonic", + "Anti-Venom", + "Uncurse Potion", + "Beef Jerky", + "Rice Ball", + "Mushroom", + "Corn Soup", + "Mint Sundae", + "Cream Puff", + "Milk", + "Coffee", + "Tea", + "Pudding", + "Strawberry", + "Grapes", + "Hamburger", + "Fried Chicken", + "Hot Dog", + "Pumpkin Pie", + "Pancake", + "Tinned Spinach", + "Paella", + "Spaghetti", + "Ice Cream", + "Meuniere", + "Boiled Starfish", + "Wheat Roll", + "Sachertorte", + "NY Cheesecake", + "Mille-feuille", + "Tarte au Poire", + "Gateau Fraise", + "Kugelhopf", + "Green Tea Cake", + "Gateau Marron", + "Langues de Chat", + "Financier", + "Tropical Sundae", + "Pudding Bucket", + "Piyoko", + "1000 Year Egg", + "Jamon Iberico", + "Spicy Shrimp", + "Pasta Carbonara", + "Penne Arrabiata", + "Powdered Milk", + "Rotten Durian", + "Ground Meat", + "Noodle Bun", + "Touch & Pop", + "Moldy Bread", + "Penters Natural", + "Beehive", + "Amanita", + "Rotten Meat", + "Spoiled Milk", + "Rusty Food Tin" +] + +good_food_table = [ + "Super Potion", + "Super Tonic", + "Tasty Meat", + "New York Steak", + "Curry", + "Ramen Noodles", + "Melon", + "Foie Gras", + "Caviar", + "Truffle", + "Peking Duck", + "Birthday Cake" +] \ No newline at end of file diff --git a/worlds/cv_por/Locations.py b/worlds/cv_por/Locations.py new file mode 100644 index 000000000000..4090b6133895 --- /dev/null +++ b/worlds/cv_por/Locations.py @@ -0,0 +1,297 @@ +from typing import List, Optional, NamedTuple, TYPE_CHECKING +from .Options import NestofEvil + +if TYPE_CHECKING: + from . import PoRWorld + + +class LocationData(NamedTuple): + region: str + name: str + is_event: Optional[bool] = False + + +def get_locations(world: "PoRWorld") -> List[LocationData]: + location_table: List[LocationData] = [ + LocationData("Entrance - Wind's Room", "Quest: Preparations"), + + LocationData("Entrance - Hub", "Entrance: Drawbridge Pit Item"), + LocationData("Entrance - Hub", "Entrance: Drawbridge Switch Item"), + LocationData("Entrance - Hub", "Entrance: Drawbridge Upper Item"), + LocationData("Entrance - Hub", "Entrance: First Passage Upper Item"), + LocationData("Entrance - Hub", "Entrance: First Passage Lower Item"), + LocationData("Entrance - Hub", "Entrance: First Passage Hidden Wall"), + LocationData("Entrance - Hub", "Entrance: Hub Room"), + LocationData("Entrance - Hub", "Entrance: Above Hub"), + LocationData("Entrance - Hub", "Entrance: Statue Room Upper"), + LocationData("Entrance - Hub", "Entrance: Statue Room Above Statue"), + LocationData("Entrance - Hub", "Entrance: Behemoth Chase Room"), + LocationData("Entrance - Hub", "Entrance: Double Room Lower"), + LocationData("Entrance - Behemoth Area", "Entrance: Double Room Upper"), + LocationData("Entrance - Behemoth Area", "Entrance: Pre-Behemoth Boss Room"), + LocationData("Entrance - Behemoth Area", "Entrance: Right Hidden Room"), + LocationData("Entrance - Upper Area", "Entrance: Lilith Room"), + LocationData("Entrance - Upper Area", "Entrance: Above Metal Block Room"), + LocationData("Entrance - Upper Area", "Entrance: Center Hidden Room"), + LocationData("Entrance - Upper Area", "Entrance: Beyond Great Armor Hallway"), + + LocationData("Buried Chamber", "Buried Chamber: Westmost Room"), + LocationData("Buried Chamber", "Buried Chamber: Central Secret"), + LocationData("Buried Chamber", "Buried Chamber: West Shaft"), + LocationData("Buried Chamber", "Buried Chamber: Central Hall"), + LocationData("Buried Chamber", "Buried Chamber: Hidden Wall Item"), + LocationData("Buried Chamber", "Buried Chamber: Right Secret"), + LocationData("Buried Chamber", "Buried Chamber: Right Shade Hall"), + LocationData("Buried Chamber", "Buried Chamber: Final Shaft Item"), + LocationData("Buried Chamber", "Buried Chamber: Right Shaft Top"), + LocationData("Buried Chamber", "Buried Chamber: Eastmost Room"), + + LocationData("Great Stairway - Lower", "Great Stairway: After Keremet Lower"), + LocationData("Great Stairway - Lower", "Great Stairway: Before Keremet Room"), + LocationData("Great Stairway - Central Painting Area", "Great Stairway: Left Loft"), + LocationData("Great Stairway - Central Painting Area", "Great Stairway: Left Loft Lower"), + LocationData("Great Stairway - Entrance Connector", "Great Stairway: Central Nook"), + LocationData("Great Stairway - Staircases", "Great Stairway: Upper Grand Staircase Corner Nook"), + LocationData("Great Stairway - Staircases", "Great Stairway: Upper Grand Staircase Middle Alcove"), + LocationData("Great Stairway - Staircases", "Great Stairway: Upper Grand Staircase Lower Alcove"), + LocationData("Great Stairway - Staircases", "Great Stairway: Upper Grand Staircase Upper Alcove"), + LocationData("Great Stairway - Staircases", "Great Stairway: Lower Grand Staircase Corner Nook"), + LocationData("Great Stairway - Staircases", "Great Stairway: Lower Grand Staircase Lower Alcove"), + LocationData("Great Stairway - Staircases", "Great Stairway: Lower Grand Staircase Middle Alcove"), + LocationData("Great Stairway - Staircases", "Great Stairway: Lower Grand Staircase Upper Alcove"), + LocationData("Great Stairway - Staircases", "Great Stairway: Upper Underground Room"), + LocationData("Great Stairway - Staircases", "Great Stairway: After Keremet Upper"), + LocationData("Great Stairway - Staircases", "Great Stairway: Big Underground Room Upper"), + LocationData("Great Stairway - Staircases", "Great Stairway: Big Underground Room Hidden Item"), + LocationData("Great Stairway - Staircases", "Great Stairway: Big Underground Room Lower"), + LocationData("Great Stairway - Staircases", "Great Stairway: Underground Hidden Room"), + LocationData("Great Stairway - Staircases", "Great Stairway: Underground East Room"), + LocationData("Great Stairway - Upper", "Great Stairway: Connector Pipe Left"), + LocationData("Great Stairway - Upper", "Great Stairway: Connector Pipe Right"), + LocationData("Great Stairway - Staircases", "Great Stairway: Upper Grand Staircase Top Left Item"), + LocationData("Great Stairway - Upper", "Great Stairway: Central Upper"), + LocationData("Great Stairway - Upper", "Great Stairway: Right Loft"), + + LocationData("Tower of Death - Bottom", "Tower of Death: Lower Passage Hidden Item"), + LocationData("Tower of Death - Bottom", "Tower of Death: Stella Item"), + LocationData("Tower of Death - First Gear Room", "Tower of Death: Lower Gears Room Spike Shaft"), + LocationData("Tower of Death - Motorcycles", "Tower of Death: Above Motorcycles"), + LocationData("Tower of Death - Belt Area", "Tower of Death: Rampart Room"), + LocationData("Tower of Death - Belt Area", "Tower of Death: Hidden Room"), + LocationData("Tower of Death - Belt Area", "Tower of Death: Gear Belt Room"), + LocationData("Tower of Death - Elevator Room", "Tower of Death: Secret Room"), + LocationData("Tower of Death - Elevator Room", "Tower of Death: Elevator Room Lower"), + LocationData("Tower of Death - Elevator Room", "Tower of Death: Elevator Room Middle"), + LocationData("Tower of Death - Elevator Room", "Tower of Death: Elevator Room Top"), + LocationData("Tower of Death - Second Gear Room", "Tower of Death: Second Gears Room Left"), + LocationData("Tower of Death - Second Gear Room", "Tower of Death: Passage Hidden Item"), + LocationData("Tower of Death - Second Gear Room", "Tower of Death: Second Gears Room Right"), + LocationData("Tower of Death - Second Gear Room", "Tower of Death: Top Gears Room"), + LocationData("Tower of Death - Second Gear Room", "Tower of Death: Third Gears Room"), + LocationData("Tower of Death - Top of the Tower", "Tower of Death: Top of the Tower"), + LocationData("Tower of Death - Top of the Tower", "Tower of Death: Elevator Switch", True), + + LocationData("Master's Keep - Lower", "Master's Keep: Entrance"), + LocationData("Master's Keep - Main", "Master's Keep: Hidden Room"), + LocationData("Master's Keep - Main", "Master's Keep: Right Square Room"), + LocationData("Master's Keep - Portrait Room", "Master's Keep: Whip Trial"), + LocationData("Master's Keep - Portrait Room", "Lost Gallery: Studio Portrait Fight", True), + + LocationData("City of Haze", "City of Haze: Meat Room"), + LocationData("City of Haze", "City of Haze: First Passage"), + LocationData("City of Haze", "City of Haze: First Big Room"), + LocationData("City of Haze", "City of Haze: Corner Room"), + LocationData("City of Haze", "City of Haze: Winding Rooms"), + LocationData("City of Haze", "City of Haze: Chunky Room"), + LocationData("City of Haze", "City of Haze: Right Corner Room"), + LocationData("City of Haze", "City of Haze: Bar Left"), + LocationData("City of Haze", "City of Haze: Bar Right"), + LocationData("City of Haze", "City of Haze: Register Room"), + LocationData("City of Haze", "City of Haze: Axe Armor Item"), + LocationData("City of Haze", "City of Haze: Center Room Top"), + LocationData("City of Haze", "City of Haze: Center Room Lower"), + LocationData("City of Haze", "City of Haze: Bread Shelf"), + LocationData("City of Haze", "City of Haze: Left Plaza"), + LocationData("City of Haze", "City of Haze: Before Cart Room"), + LocationData("City of Haze", "City of Haze: Cart Secret Left"), + LocationData("City of Haze", "City of Haze: Cart Secret Right"), + LocationData("City of Haze - East", "City of Haze: Church Hallway"), + LocationData("City of Haze - East", "City of Haze: Central Hallway Stairs"), + LocationData("City of Haze - East", "City of Haze: Heavy Cart Item"), + LocationData("City of Haze - East", "City of Haze: Axe Courtyard"), + LocationData("City of Haze - East", "City of Haze: Left Dance Hall Left Item"), + LocationData("City of Haze - East", "City of Haze: Left Dance Hall Right Item"), + LocationData("City of Haze - East", "City of Haze: Dance Halls Basement"), + LocationData("City of Haze - East", "City of Haze: Right Dance Hall Item"), + LocationData("City of Haze - East", "City of Haze: Right Dance Hall Secret Room"), + LocationData("City of Haze - East", "City of Haze: Post-Boss Room"), + LocationData("City of Haze - East", "City of Haze: Tunnel Room Right"), + LocationData("City of Haze - East", "City of Haze: Tunnel Room Left"), + LocationData("City of Haze - East", "City of Haze: Dance Halls Vertical Hallway"), + LocationData("City of Haze - East", "City of Haze: Underground Square Right"), + LocationData("City of Haze - East", "City of Haze: Underground Secret Room"), + LocationData("City of Haze - East", "City of Haze: Underground Square Left"), + LocationData("City of Haze - East", "City of Haze: Central Hallway Lower"), + LocationData("City of Haze - East", "City of Haze: Boss Room", True), + + LocationData("13th Street - Main", "13th Street: Triple Moth Room"), + LocationData("13th Street - Main", "13th Street: Behind Final Guard"), + LocationData("13th Street - Main", "13th Street: Three Amalaric Snipers"), + LocationData("13th Street - Main", "13th Street: Many Nyxes Room"), + LocationData("13th Street - Main", "13th Street: Train Room Secret Right"), + LocationData("13th Street - Main", "13th Street: Underground Secret Room"), + LocationData("13th Street - Main", "13th Street: Underground Demon Room"), + LocationData("13th Street - Main", "13th Street: Left Big Room"), + LocationData("13th Street - Main", "13th Street: Secret Floor Room"), + LocationData("13th Street - Main", "13th Street: East Courtyard"), + LocationData("13th Street - Main", "13th Street: Near Big Room"), + LocationData("13th Street - Main", "13th Street: Bakery Middle"), + LocationData("13th Street - Main", "13th Street: Right Dance Hall Top Right"), + LocationData("13th Street - Main", "13th Street: Bakery Left"), + LocationData("13th Street - Main", "13th Street: Square Room"), + LocationData("13th Street - Main", "13th Street: Bakery Right"), + LocationData("13th Street - Main", "13th Street: Right Dance Hall Top Left"), + LocationData("13th Street - Main", "13th Street: Train Room Secret Left"), + LocationData("13th Street - Main", "13th Street: Boss Room", True), + + LocationData("Sandy Grave", "Sandy Grave: Sandworm Stomach"), + LocationData("Sandy Grave", "Sandy Grave: On Statue"), + LocationData("Sandy Grave", "Sandy Grave: Behind Bricks"), + LocationData("Sandy Grave", "Sandy Grave: Left Trap"), + LocationData("Sandy Grave", "Sandy Grave: Right Trap"), + LocationData("Sandy Grave", "Sandy Grave: Bottom Right Room"), + LocationData("Sandy Grave", "Sandy Grave: Lower Big Underground Room Bottom"), + LocationData("Sandy Grave", "Sandy Grave: Lower Big Underground Room Top"), + LocationData("Sandy Grave", "Sandy Grave: Lonely Mimic Alcove"), + LocationData("Sandy Grave", "Sandy Grave: Boulder Room Corner Alcove"), + LocationData("Sandy Grave", "Sandy Grave: Boulder Room Tunnel"), + LocationData("Sandy Grave", "Sandy Grave: Upper Big Underground Top Right"), + LocationData("Sandy Grave", "Sandy Grave: Upper Big Underground Top Left"), + LocationData("Sandy Grave", "Sandy Grave: Eastmost Item"), + LocationData("Sandy Grave - Upper Pyramid", "Sandy Grave: Pyramid East 1F"), + LocationData("Sandy Grave - Upper Pyramid", "Sandy Grave: Giant Shaft Upper Item"), + LocationData("Sandy Grave - Upper Pyramid", "Sandy Grave: Giant Shaft Middle Item"), + LocationData("Sandy Grave - Upper Pyramid", "Sandy Grave: Giant Shaft Low Item"), + LocationData("Sandy Grave - Upper Pyramid", "Sandy Grave: Pyramid 3F Lower"), + LocationData("Sandy Grave - Upper Pyramid", "Sandy Grave: Pyramid 3F Upper"), + LocationData("Sandy Grave - Upper Pyramid", "Sandy Grave: Pyramid East 5F"), # check if this is top + LocationData("Sandy Grave - Upper Pyramid", "Sandy Grave: Fleaman Pit"), + LocationData("Sandy Grave - Upper Pyramid", "Sandy Grave: Mega Ghost Room Upper"), + LocationData("Sandy Grave - Upper Pyramid", "Sandy Grave: Mega Ghost Room Lower"), + LocationData("Sandy Grave - Pyramid Top", "Sandy Grave: Pyramid 5F Nook"), + LocationData("Sandy Grave - Pyramid Top", "Sandy Grave: Boss Room", True), + + LocationData("Forgotten City", "Forgotten City: Outside Left"), + LocationData("Forgotten City", "Forgotten City: Pyramid East 5F"), + LocationData("Forgotten City", "Forgotten City: Armored Fleaman Pit"), + LocationData("Forgotten City", "Forgotten City: Pyramid East 3F"), + LocationData("Forgotten City", "Forgotten City: Pyramid 5F"), + LocationData("Forgotten City", "Forgotten City: Pyramid 1F Bricks"), + LocationData("Forgotten City", "Forgotten City: Lower Boulder Room Tunnel Alcove"), + LocationData("Forgotten City", "Forgotten City: Lower Boulder Room Upper Alcove"), + LocationData("Forgotten City", "Forgotten City: Gorgon Room"), + LocationData("Forgotten City", "Forgotten City: Bottom Chamber"), + LocationData("Forgotten City - Inner", "Forgotten City: Lower Underground Square Lower Item"), + LocationData("Forgotten City - Inner", "Forgotten City: Lower Underground Square Upper Item"), + LocationData("Forgotten City - Inner", "Forgotten City: Upper Underground Square"), + LocationData("Forgotten City - Inner Upper", "Forgotten City: Pyramid East 1F"), + LocationData("Forgotten City - Inner Upper", "Forgotten City: Big Shaft Room Left"), + LocationData("Forgotten City - Inner Upper", "Forgotten City: Big Shaft Room Right"), + LocationData("Forgotten City - Inner Upper", "Forgotten City: Post-Boss Item"), + LocationData("Forgotten City - Inner Upper", "Forgotten City: Boss Room", True), + + LocationData("Nation of Fools - Right Lower", "Nation of Fools: Bottom Corner Room"), + LocationData("Nation of Fools - Right Lower", "Nation of Fools: Bottom-Right Room Left Building"), + LocationData("Nation of Fools - Right Lower", "Nation of Fools: Bottom-Right Room Middle Building"), + LocationData("Nation of Fools - Main", "Nation of Fools: Central Top Shaft"), + LocationData("Nation of Fools - Main", "Nation of Fools: Legion Arena"), + LocationData("Nation of Fools - Main", "Nation of Fools: Mid-Right Corner Room"), + LocationData("Nation of Fools - Main", "Nation of Fools: Left Upper Corner Room"), + LocationData("Nation of Fools - Main", "Nation of Fools: Right Horizontal Room"), + LocationData("Nation of Fools - Main", "Nation of Fools: Right Bottom Corner Room"), + LocationData("Nation of Fools - Main", "Nation of Fools: Top Corner Room"), + LocationData("Nation of Fools - Main", "Nation of Fools: Bottom-Left Room Building"), + LocationData("Nation of Fools - Main", "Nation of Fools: Upper Upside Down"), + LocationData("Nation of Fools - Main", "Nation of Fools: Bottom Left Medium Square On Wall"), + LocationData("Nation of Fools - Main", "Nation of Fools: Crevice Item"), + LocationData("Nation of Fools - Main", "Nation of Fools: Top-Right Room Axe Item"), + LocationData("Nation of Fools - Main", "Nation of Fools: Left Bottom Corner Room"), + LocationData("Nation of Fools - Main", "Nation of Fools: Bottom Right Medium Square On Wall"), + LocationData("Nation of Fools - Main", "Nation of Fools: Right Vertical Room"), + LocationData("Nation of Fools - Main", "Nation of Fools: Top-Right Room Bottom Item"), + LocationData("Nation of Fools - Main", "Nation of Fools: Top-Left Room Right Building"), + LocationData("Nation of Fools - Main", "Nation of Fools: Top-Right Room Breakable Wall"), + LocationData("Nation of Fools - Main", "Nation of Fools: Right Center Room"), + LocationData("Nation of Fools - Main", "Nation of Fools: Left Center Room"), + LocationData("Nation of Fools - Main", "Nation of Fools: Upside Down Building"), + LocationData("Nation of Fools - Main", "Nation of Fools: Secret Room"), + LocationData("Nation of Fools - Main", "Nation of Fools: Boss Room", True), + + LocationData("Burnt Paradise - Entrance", "Burnt Paradise: Left Lower Isolated Room"), + LocationData("Burnt Paradise - Entrance", "Burnt Paradise: Right Lower Isolated Room"), + LocationData("Burnt Paradise - Entrance", "Burnt Paradise: Left Upper Isolated Room"), + LocationData("Burnt Paradise - Entrance", "Burnt Paradise: Right Upper Isolated Room"), + LocationData("Burnt Paradise - Entrance", "Burnt Paradise: Left Upper Big Corner"), + LocationData("Burnt Paradise - Entrance", "Burnt Paradise: Upper Left Big Room Middle Building"), + LocationData("Burnt Paradise - Entrance", "Burnt Paradise: Upper Right Big Room Right Side"), + LocationData("Burnt Paradise - Entrance", "Burnt Paradise: Upper Left Big Room Left Building"), + LocationData("Burnt Paradise - Entrance", "Burnt Paradise: Upper Left Lower Corner Upright Building"), + LocationData("Burnt Paradise - Entrance", "Burnt Paradise: Right Upper Big Corner On Wall"), + LocationData("Burnt Paradise - Entrance", "Burnt Paradise: Upper Right Big Room"), + LocationData("Burnt Paradise - Bottom", "Burnt Paradise: Left Big Corner"), + LocationData("Burnt Paradise - Bottom", "Burnt Paradise: Lower Left Big Room"), + LocationData("Burnt Paradise - Bottom", "Burnt Paradise: Lower Vertical Hall"), + LocationData("Burnt Paradise - Bottom", "Burnt Paradise: Bottom Corner"), + LocationData("Burnt Paradise - Bottom", "Burnt Paradise: Bottom Right Breakable Wall"), + LocationData("Burnt Paradise - Bottom", "Burnt Paradise: Bottom Right Big Room"), + LocationData("Burnt Paradise - Bottom", "Burnt Paradise: Bottom Right Corner Room"), + LocationData("Burnt Paradise - Bottom", "Burnt Paradise: Boss Room", True), + + LocationData("Forest of Doom - Main", "Forest of Doom: Left Atrium Big Room"), + LocationData("Forest of Doom - Main", "Forest of Doom: Right Atrium East Square Room Right"), + LocationData("Forest of Doom - Main", "Forest of Doom: Left Atrium Top"), + LocationData("Forest of Doom - Main", "Forest of Doom: Right Atrium East Top Room"), + LocationData("Forest of Doom - Main", "Forest of Doom: Right Atrium East Square Room Left"), + LocationData("Forest of Doom - Main", "Forest of Doom: Right Atrium West Top Room"), + LocationData("Forest of Doom - Main", "Forest of Doom: Left Atrium Breakable Wall"), + LocationData("Forest of Doom - Main", "Forest of Doom: Left Atrium Middle Room"), + LocationData("Forest of Doom - Main", "Forest of Doom: Left Atrium Upper Room"), + LocationData("Forest of Doom - Cave", "Forest of Doom: Secret Cave Room"), + LocationData("Forest of Doom - Cave", "Forest of Doom: Near Cave Save Breakable Wall"), + LocationData("Forest of Doom - Cave", "Forest of Doom: Post-Boss Room"), + LocationData("Forest of Doom - Cave", "Forest of Doom: Boss Room", True), + + LocationData("Dark Academy - Right Building", "Dark Academy: Right Building Center Room"), + LocationData("Dark Academy - Right Building", "Dark Academy: Right Building Top Floor"), + LocationData("Dark Academy - Main", "Dark Academy: Main Building Ghoul Room"), + LocationData("Dark Academy - Main", "Dark Academy: Main Building Three Toads"), + LocationData("Dark Academy - Main", "Dark Academy: Main Building Secret Ceiling"), + LocationData("Dark Academy - Main", "Dark Academy: Main Building Entrance"), + LocationData("Dark Academy - Main", "Dark Academy: Main Building Secret Malphas Room"), + LocationData("Dark Academy - Main", "Dark Academy: West Building Breakable Wall"), + LocationData("Dark Academy - Main", "Dark Academy: Main Building Square Room Breakable Wall"), + LocationData("Dark Academy - Main", "Dark Academy: Main Building Minotaur Room"), + LocationData("Dark Academy - Main", "Dark Academy: West Building Top Floor Right"), + LocationData("Dark Academy - Main", "Dark Academy: West Building Top Floor Left"), + LocationData("Dark Academy - Main", "Dark Academy: Boss Room", True) + ] + + if world.options.goal: + location_table += [ + LocationData("The Throne Room", "The Throne Room: Above Throne Left"), + LocationData("The Throne Room", "The Throne Room: Great Stairs Under Stairs"), + LocationData("The Throne Room", "The Throne Room: Great Stairs Center"), + LocationData("The Throne Room", "The Throne Room: Above Throne Right"), + LocationData("The Throne Room", "The Throne Room: Great Stairs Hidden"), + LocationData("The Throne Room", "The Throne Room: Great Stairs Left"), + LocationData("The Throne Room", "The Throne Room: Dracula", True), + ] + + if world.options.nest_of_evil_state != NestofEvil.option_removed: + location_table += [ + LocationData("Nest of Evil", "Nest of Evil: Doppelganger Reward"), + LocationData("Nest of Evil", "Nest of Evil: First Item"), + LocationData("Nest of Evil", "Nest of Evil: Second Item")] + + return location_table + # LocationData("Dummy", "Dummy"), diff --git a/worlds/cv_por/Options.py b/worlds/cv_por/Options.py new file mode 100644 index 000000000000..724a0214e3b6 --- /dev/null +++ b/worlds/cv_por/Options.py @@ -0,0 +1,164 @@ +from dataclasses import dataclass +from Options import PerGameCommonOptions, Choice, DefaultOnToggle, Range, NamedRange, Toggle, ExcludeLocations, OptionGroup + + +class Goal(Choice): + """The goal for your game. + Brauner: Defeat Brauner in the studio portrait. + Dracula: Defeat Dracula in the throne room.""" + display_name = "Goal" + option_brauner = 0 + option_dracula = 1 + default = 1 + + +class RevealMap(DefaultOnToggle): + """Start with the entire map visible.""" + display_name = "Reveal Map" + + +class RevealBreakableWalls(Toggle): + """Automatically reveals all breakable walls/objects.""" + display_name = "Reveal Hidden Walls" + + +class NestofEvil(Choice): + """The state of the Nest of Evil. + Vanilla: Nest of Evil has its original rewards, the two tome pages and Greatest Five. + Randomized: The three items in the Nest of Evil are random, and added to the item pool. + Required: You are required to clear the Nest of Evil before you can access your final boss. + Removed: The three items are added to the item pool, and the Nest of Evil is inaccessible.""" + display_name = "Nest of Evil State" + option_vanilla = 0 + option_randomized = 1 + option_required = 2 + option_removed = 3 + default = 0 + + +class BraunerRequired(DefaultOnToggle): + """If enabled, you will be required to fight Brauner before you can access the Throne Room, + Of course, this won't do anything if the goal is set to Brauner.""" + display_name = "Brauner Required" + + +class NestPortraits(Range): + """How many Portraits are required to be cleared to access the underground passage + at the castle entrance.""" + display_name = "Passage Portraits" + range_start = 0 + range_end = 8 + default = 8 + + +class BraunerPortraits(Range): + """How many of ANY portraits are required to be cleared to access Brauner's portrait.""" + display_name = "Brauner Portraits" + range_start = 0 + range_end = 8 + default = 4 + + +class DraculaPortraits(Range): + """How many portraits are required to be cleared to access the Throne Room.""" + display_name = "Throne Portraits" + range_start = 0 + range_end = 8 + default = 0 + + +class ExperiencePercent(NamedRange): # TODO!!! IMPLEMENT + """What percentage of EXP enemies give you. This is a percent of their original EXP amount.""" + display_name = "Experience Percentage" + range_start = 0 + range_end = 500 + default = 100 + special_range_names = { + "none": 0, + "half": 50, + "normal": 100, + "double": 200, + "quadruple": 400 + } + + +class ShuffleWhip(Toggle): + """If enabled, the True Vampire Killer will be added to the item pool, + and the Whip's Memory fight will grant you a random item. + Otherwise, it will be at its normal location.""" + display_name = "Shuffle True Vampire Killer" + + +class PoRExcludeLocations(ExcludeLocations): + """Prevent these locations from having an important item.""" + default = frozenset({"The Throne Room: Above Throne Left", "The Throne Room: Above Throne Right", "The Throne Room: Great Stairs Left", + "The Throne Room: Great Stairs Center", "The Throne Room: Great Stairs Under Stairs", "The Throne Room: Great Stairs Hidden"}) + + +class StartWithChangeCube(Toggle): + """If enabled, you'll start with the Change Cube by default""" + display_name = "Start with Change Cube" + + +class AddExtraItems(Toggle): + """If enabled, some items and spells that are otherwise only obtainable + in other modes will be added to the item pool, such as Puppet Master.""" + display_name = "Add Extra Items" + + +class ExcludeOwlMorph(Toggle): + """If enabled, Owl Morph will not be added to the item pool.""" + display_name = "Exclude Owl Morph" + + +@dataclass +class PoROptions(PerGameCommonOptions): + goal: Goal + brauner_portraits: BraunerPortraits + dracula_portraits: DraculaPortraits + nest_portraits: NestPortraits + brauner_required: BraunerRequired + nest_of_evil_state: NestofEvil + shuffle_whip: ShuffleWhip + reveal_map: RevealMap + reveal_hidden_walls: RevealBreakableWalls + experience_percentage: ExperiencePercent + exclude_locations: PoRExcludeLocations + start_with_change_cube: StartWithChangeCube + add_extra_items: AddExtraItems + exclude_owl_morph: ExcludeOwlMorph + + +por_option_groups = [ + OptionGroup("Goal Options", [ + Goal, + BraunerPortraits, + DraculaPortraits, + BraunerRequired, + + ]), + + OptionGroup("Nest of Evil Options", [ + NestofEvil, + NestPortraits + + ]), + + OptionGroup("Item Options", [ + ShuffleWhip, + StartWithChangeCube, + AddExtraItems, + ExcludeOwlMorph + + ]), + + OptionGroup("Enemy Settings", [ + ExperiencePercent + + ]), + + OptionGroup("Quality of Life", [ + RevealMap, + RevealBreakableWalls + ]) +] diff --git a/worlds/cv_por/Regions.py b/worlds/cv_por/Regions.py new file mode 100644 index 000000000000..cd8d7936ae47 --- /dev/null +++ b/worlds/cv_por/Regions.py @@ -0,0 +1,245 @@ +from BaseClasses import Region, Location +from typing import TYPE_CHECKING +from rule_builder.rules import HasAll, HasAny, Has, OptionFilter, CanReachLocation +from rule_builder.field_resolvers import FromOption +from .Locations import get_locations +from .Options import StartWithChangeCube, NestofEvil, DraculaPortraits, BraunerRequired + +if TYPE_CHECKING: + from . import PoRWorld + + +class PoRLocation(Location): + game: str = "Castlevania: Portrait of Ruin" + + +region_list = [ + "Entrance - Wind's Room", # Used for quests/shops + "Entrance - Hub", # The entire starting area up through the hub + "Entrance - Behemoth Area", # The area past the first jump check, Behemoth's boss room + "Entrance - Underground Passage", # The tunnel leading to the Nest of Evil portrait + "Entrance - Hub Painting Room", # The City of Haze portrait + "Entrance - Upper Area", # The upper route from the statue room + + "Buried Chamber", # The ENTIRE buried Chamber, since it all falls under the same logic + + "Great Stairway - Lower", # Ground level up to Keremet and the check afterwards + "Great Stairway - Staircases", # The big stairs rooms and surrounding areas + "Great Stairway - Entrance Connector", # The part that connects to the entrance and the wall switch. There's an awkward jump to the upper level that can't be made with acrobat. + "Great Stairway - Upper", # The towers to the left of the staircase rooms + "Great Stairway - Central Painting Area", # The painting but also the secret room with the nun robes + "Great Stairway - Underground Painting", # The sandy graves portrait room + + "Tower of Death - Bottom", + "Tower of Death - Motorcycles", + "Tower of Death - Belt Area", + "Tower of Death - Painting Room", + "Tower of Death - Elevator Room", + "Tower of Death - First Gear Room", + "Tower of Death - Ascent", + "Tower of Death - Second Gear Room", + "Tower of Death - Top of the Tower", + + "Master's Keep - Bridge", + "Master's Keep - Lower", + "Master's Keep - Main", + "Master's Keep - Upper Quarters", + "Master's Keep - Portrait Room", + + "The Throne Room", + + "City of Haze", + "City of Haze - East", + + "13th Street", + "13th Street - Main", + + "Sandy Grave", + "Sandy Grave - Upper Pyramid", + "Sandy Grave - Pyramid Top", + + "Forgotten City", + "Forgotten City - Inner", + "Forgotten City - Inner Upper", + + "Nation of Fools", + "Nation of Fools - Right Lower", + "Nation of Fools - Main", + + "Forest of Doom", + "Forest of Doom - Main", + "Forest of Doom - Cave", + + "Dark Academy", + "Dark Academy - Right Building", + "Dark Academy - Main", + + "Burnt Paradise", + "Burnt Paradise - Entrance", + "Burnt Paradise - Bottom", + "Nest of Evil" +] + +has_change_cube = Has("Change Cube", options=[OptionFilter(StartWithChangeCube, 0)], filtered_resolution=True) + +can_cast_spell = Has("Call Cube") | has_change_cube +small_uppies = HasAny("Stone of Flight", "Griffon Wing") | HasAll("Call Cube", "Acrobat Cube") | (can_cast_spell & Has("Owl Morph")) +medium_uppies = HasAny("Stone of Flight", "Griffon Wing") | (can_cast_spell & Has("Owl Morph")) +big_uppies = Has("Griffon Wing") | (can_cast_spell & Has("Owl Morph")) +is_smol = Has("Lizard Tail") | (can_cast_spell & Has("Owl Morph")) | (can_cast_spell & Has("Toad Morph")) + + +def init_areas(world: "PoRWorld") -> None: + regions = [] + active_regions = region_list.copy() + + if not world.options.goal: + active_regions.remove("The Throne Room") + + for area in active_regions: + regions.append(Region(area, world.player, world.multiworld)) + + world.multiworld.regions += regions + create_locations(world) + connect_regions(world) + + +def create_locations(world): + from .static_location_data import location_ids + all_locations = get_locations(world) + + for location in all_locations: + if location.region not in region_list: + raise ValueError(f"Error: Region {location.name} is invalid for location {location.name}.") + region = world.get_region(location.region) + region.locations.append(PoRLocation(world.player, location.name, None if location.is_event else location_ids[location.name], region)) + + +def connect_regions(world): + world.get_region("Entrance - Hub").add_exits(["Entrance - Wind's Room", "Entrance - Behemoth Area", "Entrance - Hub Painting Room", "Entrance - Upper Area", "Entrance - Underground Passage"], + {"Entrance - Behemoth Area": small_uppies, + "Entrance - Hub Painting Room": is_smol | Has("Puppet Master"), + "Entrance - Upper Area": HasAll("Acrobat Cube", "Call Cube", "Stone of Flight") | big_uppies, + "Entrance - Underground Passage": Has("Portrait Clear", world.options.nest_portraits.value)}) + + world.get_region("Entrance - Hub Painting Room").add_exits(["Entrance - Hub", world.portrait_connections["City of Haze"]], + {"Entrance - Hub": is_smol}) + + world.get_region("Entrance - Underground Passage").add_exits(["Entrance - Hub", world.portrait_connections["Nest of Evil"]], + {"Entrance - Hub": small_uppies}) + + world.get_region("Entrance - Upper Area").add_exits(["Entrance - Hub", "Great Stairway - Entrance Connector"]) # Stairway connector + + world.get_region("Entrance - Behemoth Area").add_exits(["Entrance - Hub", "Great Stairway - Lower", "Buried Chamber"], + {"Great Stairway - Lower": HasAll("Strength Glove", "Push Cube", "Call Cube")}) + + world.get_region("Buried Chamber").add_exits(["Entrance - Hub", "Great Stairway - Lower"]) + + world.get_region("Great Stairway - Lower").add_exits(["Entrance - Hub", "Great Stairway - Staircases", "Buried Chamber"], + {"Great Stairway - Staircases": Has("Stone of Flight") | big_uppies}) + + world.get_region("Great Stairway - Staircases").add_exits(["Great Stairway - Lower", "Great Stairway - Underground Painting", "Tower of Death - Bottom", "Great Stairway - Upper"], + {"Tower of Death - Bottom": HasAll("Strength Glove", "Push Cube", "Call Cube"), + "Great Stairway - Upper": small_uppies | Has("Puppet Master")}) + + world.get_region("Great Stairway - Underground Painting").add_exits(["Great Stairway - Staircases", world.portrait_connections["Sandy Grave"]]) + + world.get_region("Great Stairway - Entrance Connector").add_exits(["Great Stairway - Staircases", "Entrance - Upper Area", "Great Stairway - Upper"], + {"Great Stairway - Upper": (HasAll("Call Cube", "Acrobat Cube", "Puppet Master")) | medium_uppies | HasAll("Call Cube", "Acrobat Cube", "Speed Up")}) + + world.get_region("Great Stairway - Upper").add_exits(["Great Stairway - Staircases", "Great Stairway - Entrance Connector", "Tower of Death - Belt Area", "Great Stairway - Central Painting Area"], + {"Tower of Death - Belt Area": can_cast_spell & HasAny("Owl Morph", "Toad Morph"), + "Great Stairway - Central Painting Area": small_uppies}) + + world.get_region("Great Stairway - Central Painting Area").add_exits([world.portrait_connections["Nation of Fools"], "Great Stairway - Upper"]) + + world.get_region("Tower of Death - Bottom").add_exits(["Tower of Death - First Gear Room", "Tower of Death - Motorcycles"], + {"Tower of Death - Motorcycles": Has("Cog"), + "Tower of Death - First Gear Room": small_uppies}) + + world.get_region("Tower of Death - Motorcycles").add_exits(["Tower of Death - Bottom", "Tower of Death - Belt Area"], + {"Tower of Death - Belt Area": HasAll("Wait Cube", "Call Cube") & has_change_cube}) + + world.get_region("Tower of Death - Belt Area").add_exits(["Tower of Death - Painting Room", "Great Stairway - Upper", "Master's Keep - Bridge"], + {"Great Stairway - Upper": can_cast_spell & HasAny("Owl Morph", "Toad Morph")}) + + world.get_region("Tower of Death - Painting Room").add_exits(["Tower of Death - Belt Area", world.portrait_connections["Forest of Doom"]], { + world.portrait_connections["Forest of Doom"]: Has("Stella's Locket") + }) + + world.get_region("Tower of Death - Elevator Room").add_exits(["Master's Keep - Bridge", "Tower of Death - Top of the Tower", "Tower of Death - First Gear Room", "Master's Keep - Lower"], + {"Master's Keep - Lower": small_uppies & Has("Tower Elevator Active")}) + + world.get_region("Tower of Death - First Gear Room").add_exits(["Tower of Death - Bottom", "Tower of Death - Ascent"], + {"Tower of Death - Ascent": can_cast_spell & HasAny("Owl Morph", "Toad Morph")}) + + world.get_region("Tower of Death - Ascent").add_exits(["Tower of Death - First Gear Room", "Tower of Death - Second Gear Room"], + {"Tower of Death - First Gear Room": can_cast_spell & HasAny("Owl Morph", "Toad Morph"), + "Tower of Death - Second Gear Room": medium_uppies}) + + world.get_region("Tower of Death - Second Gear Room").add_exits(["Tower of Death - Ascent", "Tower of Death - Top of the Tower"]) + + world.get_region("Tower of Death - Top of the Tower").add_exits(["Tower of Death - Second Gear Room", "Tower of Death - Elevator Room"]) + + world.get_region("Master's Keep - Bridge").add_exits(["Tower of Death - Belt Area", "Tower of Death - Elevator Room"]) + + world.get_region("Master's Keep - Lower").add_exits(["Tower of Death - Elevator Room", "Master's Keep - Bridge", "Master's Keep - Main"], + {"Tower of Death - Elevator Room": Has("Tower Elevator Active"), + "Master's Keep - Main": medium_uppies | (small_uppies & Has("Puppet Master"))}) + + world.get_region("Master's Keep - Main").add_exits(["Master's Keep - Lower", "Master's Keep - Upper Quarters"], + {"Master's Keep - Upper Quarters": medium_uppies | (small_uppies & Has("Puppet Master"))}) + + world.get_region("Master's Keep - Upper Quarters").add_exits(["Master's Keep - Portrait Room", "Master's Keep - Main"], + {"Master's Keep - Portrait Room": can_cast_spell & Has("Sanctuary")}) + + world.get_region("Master's Keep - Portrait Room").add_exits([world.portrait_connections["Forgotten City"], world.portrait_connections["Burnt Paradise"], world.portrait_connections["Dark Academy"], world.portrait_connections["13th Street"]], + {world.portrait_connections["13th Street"]: CanReachLocation("Forgotten City: Boss Room"), + world.portrait_connections["Burnt Paradise"]: CanReachLocation("Dark Academy: Boss Room")}) + + world.get_region("City of Haze").add_exits(["City of Haze - East"], + {"City of Haze - East": Has("Puppet Master") | (has_change_cube & Has("Call Cube"))}) + + world.get_region("Sandy Grave").add_exits(["Sandy Grave - Upper Pyramid"], + {"Sandy Grave - Upper Pyramid": (small_uppies & Has("Puppet Master")) | medium_uppies}) + + world.get_region("Nation of Fools").add_exits(["Nation of Fools - Right Lower", "Nation of Fools - Main"], + {"Nation of Fools - Right Lower": small_uppies | Has("Puppet Master"), + "Nation of Fools - Main": medium_uppies}) + + world.get_region("Sandy Grave - Upper Pyramid").add_exits(["Sandy Grave - Pyramid Top"], + {"Sandy Grave - Pyramid Top": medium_uppies}) + + world.get_region("Forest of Doom").add_exits(["Forest of Doom - Main"], + {"Forest of Doom - Main": small_uppies}) + + world.get_region("Forest of Doom - Main").add_exits(["Forest of Doom - Cave"], + {"Forest of Doom - Cave": HasAll("Strength Glove", "Push Cube", "Call Cube")}) + + world.get_region("Dark Academy").add_exits(["Dark Academy - Right Building"], + {"Dark Academy - Right Building": small_uppies | Has("Puppet Master")}) + + world.get_region("Dark Academy - Right Building").add_exits(["Dark Academy - Main"], + {"Dark Academy - Main": big_uppies}) + + world.get_region("Forgotten City").add_exits(["Forgotten City - Inner"], + {"Forgotten City - Inner": medium_uppies | Has("Puppet Master")}) + + world.get_region("Forgotten City - Inner").add_exits(["Forgotten City - Inner Upper"], + {"Forgotten City - Inner Upper": medium_uppies | HasAll("Puppet Master", "Call Cube", "Acrobat Cube")}) + + world.get_region("13th Street").add_exits(["13th Street - Main"], + {"13th Street - Main": HasAll("Strength Glove", "Push Cube", "Call Cube")}) + + world.get_region("Burnt Paradise").add_exits(["Burnt Paradise - Entrance"], + {"Burnt Paradise - Entrance": small_uppies | Has("Puppet Master")}) + + world.get_region("Burnt Paradise - Entrance").add_exits(["Burnt Paradise - Bottom"], + {"Burnt Paradise - Bottom": big_uppies}) + + if world.options.goal: + # Add a connection to the throne room if necessary + world.get_region("Master's Keep - Upper Quarters").connect(world.get_region("The Throne Room"), "Throne Barrier", + Has("Portrait Clear", FromOption(DraculaPortraits)) & + Has("Brauner Defeated", options=[OptionFilter(BraunerRequired, 1)], filtered_resolution=True) & + CanReachLocation("Nest of Evil: Doppelganger Reward", options=[OptionFilter(NestofEvil, NestofEvil.option_required)], filtered_resolution=True)) diff --git a/worlds/cv_por/Rom.py b/worlds/cv_por/Rom.py new file mode 100644 index 000000000000..600f1c94f618 --- /dev/null +++ b/worlds/cv_por/Rom.py @@ -0,0 +1,259 @@ +import hashlib +import os +import Utils +import typing +import struct +from worlds.Files import APProcedurePatch, APTokenMixin, APTokenTypes, APPatchExtension +from typing import Sequence, NamedTuple +from .static_location_data import location_data_table +from .Options import NestofEvil +from BaseClasses import ItemClassification + +world_version = "1.0" +hash_us = "2edd57540cae45842fbd19c45a4214f9" + + +class FilePointer(NamedTuple): + rom_address: int + base_address: int + file_size: int + + +file_pointers = { + "arm9": FilePointer(0x4000, 0x02000000, 0xFDBB7), + "overlay_78": FilePointer(0x369000, 0x022E8820, 0x10ABF), + "overlay_79": FilePointer(0x379C00, 0x022E8820, 0x1C83F), + "overlay_80": FilePointer(0x396600, 0x022E8820, 0x1671F), + "overlay_81": FilePointer(0x3ACE00, 0x022E8820, 0x121BF), + "overlay_82": FilePointer(0x3BF000, 0x022E8820, 0x1275F), + "overlay_83": FilePointer(0x3D1800, 0x022E8820, 0x143FF), + "overlay_84": FilePointer(0x3E5C00, 0x022E8820, 0x1029F), + "overlay_85": FilePointer(0x3F6000, 0x022E8820, 0x1E19F), + "overlay_86": FilePointer(0x414200, 0x022E8820, 0x13D5F), + "overlay_87": FilePointer(0x428000, 0x022E8820, 0x941F), + "overlay_88": FilePointer(0x431600, 0x022E8820, 0x137BF), + "overlay_91": FilePointer(0x452E00, 0x022E8820, 0x1EE7F), + "overlay_92": FilePointer(0x471E00, 0x022E8820, 0x1DE7F), + "overlay_93": FilePointer(0x48FE00, 0x022E8820, 0x1963F), + "overlay_94": FilePointer(0x4A9600, 0x022E8820, 0xE83F), + "overlay_95": FilePointer(0x4B8000, 0x022E8820, 0x1A59F), + "overlay_96": FilePointer(0x4D2600, 0x022E8820, 0x1809F), + "overlay_97": FilePointer(0x4EA800, 0x022E8820, 0x1D49F), + "overlay_98": FilePointer(0x507E00, 0x022E8820, 0xE29F), + "overlay_99": FilePointer(0x516200, 0x022E8820, 0x129BF), + "overlay_100": FilePointer(0x528C00, 0x022E8820, 0x1A53F), + "overlay_101": FilePointer(0x543200, 0x022E8820, 0x1577F), + "overlay_102": FilePointer(0x558A00, 0x022E8820, 0x2067F), + "overlay_103": FilePointer(0x579200, 0x022E8820, 0x1C59F), + "overlay_104": FilePointer(0x595800, 0x022E8820, 0xB45F), + "overlay_105": FilePointer(0x5A0E00, 0x022E8820, 0x1493F), + "overlay_106": FilePointer(0x5B5800, 0x022E8820, 0x16B3F), + "overlay_107": FilePointer(0x5CC400, 0x022E8820, 0x1B6DF), + "overlay_108": FilePointer(0x5E7C00, 0x022E8820, 0x1C8FF), + "overlay_109": FilePointer(0x604C00, 0x022E8820, 0xDBDF), + "overlay_111": FilePointer(0x61C200, 0x022E8820, 0xFEDF), + "overlay_112": FilePointer(0x62C200, 0x022E8820, 0xA33F), + "overlay_113": FilePointer(0x636600, 0x022E8820, 0x621F), + "overlay_119": FilePointer(0x2CF0800, 0x02308EC0, 0x1F000) +} + + +class LocalRom(object): + + def __init__(self, file: bytes, name: str | None = None) -> None: + self.file = bytearray(file) + self.name = name + + def read_byte(self, offset: int) -> int: + return self.file[offset] + + def read_from_file(self, offset: int, file_name: str, length: int) -> bytes: + file = file_pointers[file_name] + address = offset - file.base_address + if address < 0 or (address + length > file.file_size): + raise ValueError(f"Out of Range: Tried to write {value} at {hex(offset)} in {file_name}") + address = file.rom_address + address + + return self.file[address:address + length] + + def write_to_file(self, offset: int, file_name: str, values: Sequence[int]) -> None: + file = file_pointers[file_name] + address = offset - file.base_address + if address < 0 or (address + len(values )> file.file_size): + raise ValueError(f"Out of Range: Tried to write {value} at {hex(offset)} in {file_name}") + address = file.rom_address + address + self.file[address:address + len(values)] = values + + def get_bytes(self) -> bytes: + return bytes(self.file) + + +def patch_rom(world, rom, code_patch): + rom.name = f"{world.player}_{world.auth_id}" + patch_name = bytearray(rom.name, "utf8")[:0x14] + patch_name.append(0) # Add a terminator here + rom.write_to_file(0x02309140, "overlay_119", code_patch) # Apply the basepatch's data + rom.write_to_file(0x02308F20, "overlay_119", patch_name) # Write in the player's name + rom.write_to_file(0x02308F35, "overlay_119", world_version.encode("ascii")) # Write the patch version + + # Options handling ################### + rom.write_to_file(0x0230916E, "overlay_119", bytearray([world.options.nest_portraits.value])) # Portraits for nest of evil + rom.write_to_file(0x0230916F, "overlay_119", bytearray([world.options.brauner_portraits.value])) # Portraits for Brauner + rom.write_to_file(0x02309170, "overlay_119", bytearray([world.options.dracula_portraits.value])) # Portraits for Dracula + rom.write_to_file(0x02309172, "overlay_119", bytearray([world.options.goal.value])) # Goal mode, 1 if Dracula + rom.write_to_file(0x02309173, "overlay_119", bytearray([world.options.reveal_hidden_walls.value])) # Reveal hidden walls + rom.write_to_file(0x02309174, "overlay_119", bytearray([world.options.start_with_change_cube.value])) # Start with Change Cube + rom.write_to_file(0x02309175, "overlay_119", bytearray([world.options.reveal_map.value])) # Reveal map + rom.write_to_file(0x02309176, "overlay_119", struct.pack("H", world.options.experience_percentage.value)) # ExP Multiplier + + if world.options.reveal_map: + rom.write_to_file(0x0202F3B0, "arm9", bytearray([0x00, 0x00, 0xA0, 0xE1])) # Nop out the instruction that hides room borders + + goal_requirements = 0 + + if world.options.brauner_required: + goal_requirements |= 1 # Brauner Flag + + if world.options.nest_of_evil_state == NestofEvil.option_required: + goal_requirements |= 2 # Nest flag + elif world.options.nest_of_evil_state == NestofEvil.option_removed: + # Block off the Nest of Evil entrance with bricks + rom.write_to_file(0x022EB0CA, "overlay_113", struct.pack("H", 0x4027)) + rom.write_to_file(0x022EB0EA, "overlay_113", struct.pack("H", 0x4027)) + rom.write_to_file(0x022EB10A, "overlay_113", struct.pack("H", 0x4027)) + rom.write_to_file(0x022EB12A, "overlay_113", struct.pack("H", 0x4027)) + + # This is the singular Goal Requirements flag. + # Brauner will never check if brauner is required... + # If Nest of Evil is required, your goal (either brauner OR drac) will check it. + rom.write_to_file(0x02309171, "overlay_119", bytearray([goal_requirements])) + #################################### + # Location handler + for location in world.get_locations(): + if not location.address: # Filter all events out of this + continue + item = location.item + data = location_data_table[location.name] + + if item.player != location.player: # Is this an offworld item? + item_type = 2 + if ItemClassification.progression in item.classification: + color = 0x0C + item_id = 0x50 + elif ItemClassification.trap in item.classification: + color = 0x06 + item_id = 0x50 + elif ItemClassification.useful in item.classification: + color = 0x07 + item_id = 0x4F + else: + color = 0x0E + item_id = 0x4F + else: + color = 0 + item_type = (item.code & 0xFF00) >> 8 + item_id = item.code & 0xFF + + if data.location_type == "Normal": + rom.write_to_file(0x02308F40 + location.address, "overlay_119", bytearray([color])) # Item color table + rom.write_to_file(data.pointer + 6, data.file, bytearray([item_type])) + rom.write_to_file(data.pointer + 10, data.file, bytearray([item_id])) + elif data.location_type == "Cutscene": + rom.write_to_file(data.pointer, data.file, bytearray([item_id, item_type, color])) + elif data.location_type == "Quest": + rom.write_to_file(data.pointer, data.file, bytearray([item_id, item_type])) + else: + raise ValueError(f"Error! Location {location.name} has invalid location type {data.location_type}!") + + + rom.write_file("token_patch.bin", rom.get_token_binary()) + + +class PoRProcPatch(APProcedurePatch, APTokenMixin): + hash = hash_us + game = "Castlevania: Portrait of Ruin" + patch_file_ending = ".apcvpor" + result_file_ending = ".nds" + name: bytearray + procedure = [ + ("apply_bsdiff4", ["por_base.bsdiff4"]), + ("apply_tokens", ["token_patch.bin"]), + ("check_patch_version", []), + ("adjust_item_positions", []), + ("apply_modifiers", []) + ] + + @classmethod + def get_source_data(cls) -> bytes: + return get_base_rom_bytes() + + def write_to_file(self, offset: int, file_name: str, value: typing.Iterable[int]) -> None: + file = file_pointers[file_name] + address = offset - file.base_address + if address < 0 or (address + len(value )> file.file_size): + raise ValueError(f"Out of Range: Tried to write {value} at {hex(offset)} in {file_name}") + address = file.rom_address + address + self.write_token(APTokenTypes.WRITE, address, bytes(value)) + + def copy_bytes(self, source: int, amount: int, destination: int) -> None: + self.write_token(APTokenTypes.COPY, destination, (amount, source)) + +class PorPatchExtentions(APPatchExtension): + game = "Castlevania: Portrait of Ruin" + @staticmethod + def check_patch_version(caller: APProcedurePatch, rom: bytes) -> bytes: + rom = LocalRom(rom) + version_check = rom.read_from_file(0x02308F35, "overlay_119", 11) + version = version_check.rstrip(b"\x00") + version = version.decode("ascii") + if version != world_version: # Installed world is different from generated world + raise Exception(f"Error! this patch was generated on Portrait of Ruin APworld version: {version}, but installed APworld is version: {world_version}. " + + f"Please use APWorld version {version} to patch your game.") + return rom.get_bytes() + + def adjust_item_positions(caller: APProcedurePatch, rom: bytes) -> bytes: + rom = LocalRom(rom) + for location in location_data_table: + data = location_data_table[location] + if data.is_grounded and data.location_type == "Normal": + item_type = int.from_bytes(rom.read_from_file(data.pointer + 6, data.file, 1)) + item_id = int.from_bytes(rom.read_from_file(data.pointer + 10, data.file, 1)) + if (item_type == 1 and item_id < 4) or item_type == 8: # Coins and skills + y_pos = int.from_bytes(rom.read_from_file(data.pointer +2, data.file, 2), byteorder="little") + y_pos -= 0x10 + rom.write_to_file(data.pointer + 2, data.file, struct.pack("H", y_pos)) + + return rom.get_bytes() + + def apply_modifiers(caller: APProcedurePatch, rom: bytes) -> bytes: + rom = LocalRom(rom) + exp_multiplier = struct.unpack("H", rom.read_from_file(0x02309176, "overlay_119", 2))[0] # Read the multiplier + exp_multiplier = exp_multiplier / 100 + + for i in range(0x9A): + address = 0x020BE568 + (0x20 * i) + enemy_exp = struct.unpack("H", rom.read_from_file(address + 16, "arm9", 2))[0] + enemy_exp = int(min(0xFFFF, (enemy_exp * exp_multiplier))) + return rom.get_bytes() + +def get_base_rom_bytes(file_name: str = "") -> bytes: + base_rom_bytes = getattr(get_base_rom_bytes, "base_rom_bytes", None) + if not base_rom_bytes: + file_name = get_base_rom_path(file_name) + base_rom_bytes = bytes(Utils.read_snes_rom(open(file_name, "rb"))) + + basemd5 = hashlib.md5() + basemd5.update(base_rom_bytes) + if hash_us != basemd5.hexdigest(): + raise Exception('Supplied Base Rom does not match known MD5 for US release. ' + 'Get the correct game and version, then dump it') + get_base_rom_bytes.base_rom_bytes = base_rom_bytes + return base_rom_bytes + +def get_base_rom_path(file_name: str = "") -> str: + from worlds.cv_por import PoRWorld + if not file_name: + file_name = PoRWorld.settings.rom_file + if not os.path.exists(file_name): + file_name = Utils.user_path(file_name) + return file_name \ No newline at end of file diff --git a/worlds/cv_por/Rules.py b/worlds/cv_por/Rules.py new file mode 100644 index 000000000000..b1d0fc60ba09 --- /dev/null +++ b/worlds/cv_por/Rules.py @@ -0,0 +1,90 @@ +from rule_builder.rules import HasAll, HasAny, Has, OptionFilter, CanReachLocation +from rule_builder.field_resolvers import FromOption +from .Regions import small_uppies, big_uppies, can_cast_spell, medium_uppies +from .Options import NestofEvil, BraunerPortraits, Goal + +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from . import PoRWorld + + +def set_location_rules(world): + set_rule = world.set_rule + world.set_completion_rule(Has("Dracula Defeated")) + + set_rule(world.get_location("Entrance: Drawbridge Upper Item"), big_uppies | HasAll("Acrobat Cube", "Call Cube", "Stone of Flight", "Puppet Master")) + set_rule(world.get_location("Entrance: Above Metal Block Room"), big_uppies | (HasAll("Strength Glove", "Push Cube", "Call Cube") & medium_uppies) | (HasAll("Acrobat Cube", "Puppet Master")) | (HasAll("Puppet Master", "Stone of Flight", "Acrobat Cube", "Call Cube"))) + + set_rule(world.get_location("Great Stairway: Lower Grand Staircase Lower Alcove"), small_uppies | Has("Puppet Master")) + set_rule(world.get_location("Great Stairway: Lower Grand Staircase Upper Alcove"), medium_uppies | Has("Puppet Master") | (can_cast_spell & Has("Speed Up"))) + set_rule(world.get_location("Great Stairway: Lower Grand Staircase Middle Alcove"), medium_uppies | HasAll("Speed Up", "Puppet Master", "Call Cube")) + set_rule(world.get_location("Great Stairway: Upper Grand Staircase Lower Alcove"), small_uppies | Has("Puppet Master")) + set_rule(world.get_location("Great Stairway: Upper Grand Staircase Middle Alcove"), medium_uppies | HasAll("Speed Up", "Puppet Master", "Call Cube")) + set_rule(world.get_location("Great Stairway: Upper Grand Staircase Upper Alcove"), medium_uppies | Has("Puppet Master") | (can_cast_spell & Has("Speed Up"))) + set_rule(world.get_location("Great Stairway: Upper Grand Staircase Top Left Item"), small_uppies | Has("Puppet Master") | (can_cast_spell & Has("Speed Up"))) + set_rule(world.get_location("Great Stairway: Connector Pipe Left"), can_cast_spell & HasAny("Owl Morph", "Toad Morph")) + set_rule(world.get_location("Great Stairway: Connector Pipe Right"), can_cast_spell & HasAny("Owl Morph", "Toad Morph")) + set_rule(world.get_location("Great Stairway: Right Loft"), small_uppies) + set_rule(world.get_location("Great Stairway: Left Loft"), small_uppies) + set_rule(world.get_location("Great Stairway: Left Loft Lower"), small_uppies) + set_rule(world.get_location("Great Stairway: Central Nook"), medium_uppies) + + set_rule(world.get_location("Tower of Death: Secret Room"), medium_uppies | HasAll("Puppet Master", "Call Cube", "Acrobat Cube")) + set_rule(world.get_location("Tower of Death: Elevator Room Lower"), big_uppies) + set_rule(world.get_location("Tower of Death: Elevator Room Middle"), Has("Tower Elevator Active")) + set_rule(world.get_location("Tower of Death: Elevator Room Top"), Has("Tower Elevator Active")) + set_rule(world.get_location("Tower of Death: Above Motorcycles"), big_uppies | HasAll("Stone of Flight", "Call Cube", "Acrobat Cube")) + + set_rule(world.get_location("City of Haze: Cart Secret Left"), big_uppies) + set_rule(world.get_location("City of Haze: Cart Secret Right"), big_uppies) + set_rule(world.get_location("City of Haze: Cart Secret Right"), big_uppies) + set_rule(world.get_location("City of Haze: Right Dance Hall Item"), small_uppies | Has("Puppet Master")) + set_rule(world.get_location("City of Haze: Tunnel Room Left"), (can_cast_spell & HasAny("Toad Morph", "Owl Morph")) | Has("Puppet Master")) + set_rule(world.get_location("City of Haze: Left Dance Hall Left Item"), small_uppies | Has("Puppet Master")) + set_rule(world.get_location("City of Haze: Left Dance Hall Right Item"), small_uppies | Has("Puppet Master")) + + set_rule(world.get_location("13th Street: Train Room Secret Left"), big_uppies) + set_rule(world.get_location("13th Street: Train Room Secret Right"), big_uppies) + set_rule(world.get_location("13th Street: Right Dance Hall Top Left"), small_uppies) + set_rule(world.get_location("13th Street: Right Dance Hall Top Right"), small_uppies) + set_rule(world.get_location("13th Street: Many Nyxes Room"), (medium_uppies | HasAll("Acrobat Cube", "Call Cube", "Puppet Master")) & (HasAll("Puppet Master", "Lizard Tail") | (can_cast_spell & HasAny("Toad Morph", "Owl Morph")))) + + set_rule(world.get_location("Sandy Grave: Behind Bricks"), big_uppies) + set_rule(world.get_location("Sandy Grave: Boulder Room Tunnel"), can_cast_spell & HasAny("Toad Morph", "Owl Morph")) + set_rule(world.get_location("Sandy Grave: Boulder Room Corner Alcove"), big_uppies) + set_rule(world.get_location("Sandy Grave: Lonely Mimic Alcove"), medium_uppies | HasAll("Acrobat Cube", "Call Cube", "Puppet Master")) + set_rule(world.get_location("Sandy Grave: Lower Big Underground Room Top"), medium_uppies) + set_rule(world.get_location("Sandy Grave: Upper Big Underground Top Left"), small_uppies | Has("Puppet Master")) + set_rule(world.get_location("Sandy Grave: Upper Big Underground Top Right"), small_uppies | Has("Puppet Master")) + set_rule(world.get_location("Sandy Grave: Eastmost Item"), small_uppies) + set_rule(world.get_location("Sandy Grave: Pyramid East 1F"), big_uppies | HasAll("Stone of Flight", "Acrobat Cube", "Call Cube")) + + set_rule(world.get_location("Forgotten City: Pyramid East 5F"), small_uppies | Has("Puppet Master")) + set_rule(world.get_location("Forgotten City: Pyramid 1F Bricks"), big_uppies) + set_rule(world.get_location("Forgotten City: Lower Boulder Room Upper Alcove"), big_uppies) + set_rule(world.get_location("Forgotten City: Lower Boulder Room Tunnel Alcove"), can_cast_spell & HasAny("Toad Morph", "Owl Morph")) + set_rule(world.get_location("Forgotten City: Lower Underground Square Upper Item"), medium_uppies | HasAll("Call Cube", "Acrobat Cube", "Puppet Master")) + set_rule(world.get_location("Forgotten City: Pyramid East 1F"), big_uppies |HasAll("Call Cube", "Acrobat Cube", "Stone of Flight")) + set_rule(world.get_location("Forgotten City: Pyramid East 1F"), big_uppies |HasAll("Call Cube", "Acrobat Cube", "Stone of Flight")) + set_rule(world.get_location("Forgotten City: Big Shaft Room Left"), big_uppies) + set_rule(world.get_location("Forgotten City: Big Shaft Room Right"), big_uppies) + set_rule(world.get_location("Forgotten City: Pyramid East 3F"), medium_uppies | HasAll("Call Cube", "Acrobat Cube", "Puppet Master") | (can_cast_spell & HasAll("Speed Up", "Puppet Master"))) + + set_rule(world.get_location("Nation of Fools: Bottom Left Medium Square On Wall"), big_uppies | HasAll("Call Cube", "Acrobat Cube", "Stone of Flight") | HasAll("Stone of Flight", "Puppet Master")) + set_rule(world.get_location("Nation of Fools: Crevice Item"), Has("Puppet Master") | (can_cast_spell & HasAny("Toad Morph", "Owl Morph"))) + + set_rule(world.get_location("Burnt Paradise: Right Upper Big Corner On Wall"), big_uppies | HasAll("Call Cube", "Acrobat Cube", "Stone of Flight") | HasAll("Stone of Flight", "Puppet Master")) + + set_rule(world.get_location("Forest of Doom: Secret Cave Room"), big_uppies) + + set_rule(world.get_location("Lost Gallery: Studio Portrait Fight"), + Has("Portrait Clear", FromOption(BraunerPortraits)) & + CanReachLocation("Nest of Evil: Doppelganger Reward", options=[OptionFilter(NestofEvil, NestofEvil.option_required), OptionFilter(Goal, True)], filtered_resolution=True)) + + if world.options.goal: + set_rule(world.get_location("The Throne Room: Great Stairs Under Stairs"), medium_uppies | HasAll("Acrobat Cube", "Call Cube", "Puppet Master")) + set_rule(world.get_location("The Throne Room: Great Stairs Hidden"), big_uppies) + set_rule(world.get_location("The Throne Room: Above Throne Left"), big_uppies) + set_rule(world.get_location("The Throne Room: Above Throne Right"), big_uppies) + set_rule(world.get_location("The Throne Room: Great Stairs Center"), big_uppies) + set_rule(world.get_location("The Throne Room: Great Stairs Left"), big_uppies) \ No newline at end of file diff --git a/worlds/cv_por/__init__.py b/worlds/cv_por/__init__.py new file mode 100644 index 000000000000..666670ec24d7 --- /dev/null +++ b/worlds/cv_por/__init__.py @@ -0,0 +1,328 @@ +import settings +import typing +import threading + +from BaseClasses import Tutorial, MultiWorld +from worlds.AutoWorld import World, WebWorld + +from .Items import item_table, get_item_names_per_category +from .Options import PoROptions, por_option_groups +from .static_location_data import location_ids +from .generator_main import (CVPoRItem, generate_early, create_regions, fill_slot_data, + modify_multidata, generate_output, create_items, get_filler_item_name, set_rules) +from .Client import PoRClient + +class PoRWeb(WebWorld): + theme = "ocean" + + setup_en = Tutorial( + "Multiworld Setup Guide", + "A guide to setting up the Portrait of Ruin randomizer" + "and connecting to an Archipelago server.", + "English", + "setup_en.md", + "setup/en", + ["Pink Switch"] + ) + + option_groups = por_option_groups + tutorials = [setup_en] + + +class PoRSettings(settings.Group): + class RomFile(settings.UserFilePath): + """File name of the Castlevania: Portrait of Ruin US ROM file.""" + description = "Portrait of Ruin ROM File" + copy_to = "CASTLEVANIA2_ACBEA4_00.nds" + md5 = "2edd57540cae45842fbd19c45a4214f9" + + rom_file: RomFile = RomFile(RomFile.copy_to) + + +class PoRWorld(World): + """The year is 1944. Dracula's castle has reappeared, and it's been taken over + by the mad vampire artist, Brauner, and his two twin daughters. Explore the + castle and Brauner's magical portraits with two characters and put a stop to him!""" + game = "Castlevania: Portrait of Ruin" + option_definitions = PoROptions + data_version = 1 + origin_region_name = "Entrance - Hub" + + item_name_to_id = {item: item_table[item].code for item in item_table} + location_name_to_id = location_ids + item_name_groups = get_item_names_per_category() + + web = PoRWeb() + settings: typing.ClassVar[PoRSettings] + # topology_present = True + ut_can_gen_without_yaml = True + + options_dataclass = PoROptions + options: PoROptions + generate_early = generate_early + create_items = create_items + create_regions = create_regions + fill_slot_data = fill_slot_data + modify_multidata = modify_multidata + generate_output = generate_output + get_filler_item_name = get_filler_item_name + set_rules = set_rules + + # locked_locations: List[str] + # ocation_cache: List[Location] + + def __init__(self, multiworld: MultiWorld, player: int): + self.rom_name_available_event = threading.Event() + super().__init__(multiworld, player) + + self.locked_locations = [] + self.location_cache = [] + self.has_tried_magus_ring = False + self.has_generated_output = False + + self.subweapon_filler_table = [ + "Axe Subweapon", + "Cross", + "Holy Water", + "Bible", + "Javelin", + "Ricochet Rock", + "Boomerang", + "Shuriken", + "Yagyu Shuriken", + "Kunimitsu", + "Kunai", + "Crossbow", + "Dart", + "Defensive Form", + "Wrecking Ball", + "Rampage", + "Berserker", + "Time Stop", + "Heal", + "Cure Curse", + "LUCK Boost", + "Rock Riot", + "Spirit of Light", + "Dark Rift", + "Stone Circle", + "Ice Needle", + "Explosion", + "Chain Lightning", + "Nightmare", + "Summon Medusa", + "Acidic Bubbles", + "Hex", + "Salamander", + "Cocytus", + "Thor's Bellow", + "Summon Crow", + "Summon Skeleton", + "Summon Ghost", + "Summon Gunman", + "Summon Frog" + ] + + self.weapon_table = [ + "Leather Whip", + "Steel Whip", + "Rose Stem Whip", + "Blank Book", + "Knife", + "Combat Knife", + "Baselard", + "Short Sword", + "Cutlass", + "Gladius", + "Rahab's Frost", + "Agni's Flame", + "Claymore", + "Falchion", + "Great Sword", + "Zweihander", + "Spear", + "Partisan", + "Lance", + "Trident", + "Long Spear", + "Couse", + "Axe", + "Mace", + "Battle Axe", + "Morning Star", + "Bhuj", + "Brass Knuckles", + "Cestus", + "Bullet Punch" + ] + + self.good_weapon_table = [ + "Medusa Whip", + "Undead Killer", + "Tome of Arms X", + "Katar", + "Cinquedea", + "Assassin Blade", + "Heaven's Sword", + "Jagdplaute", + "Stellar Sword", + "Damascus Sword", + "Dragon Slayer", + "Final Sword", + "Holy Claymore", + "Royal Sword", + "Sarissa", + "Alucard's Spear", + "Voulge", + "Bullova", + "Golden Axe", + "Illusion Fist" + ] + + self.armor_table = [ + "Casual Clothes", + "Hobo's Clothes", + "Houppelande", + "Poncho", + "Combat Fatigues", + "Adrenaline Gear", + "Kalasiris", + "Tailcoat", + "Biker's Jacket", + "Justaucorps", + "Tuxedo Coat", + "Battle Jacket", + "Surcoat", + "Leather Cuirass", + "Copper Plate", + "Iron Plate", + "Silver Plate", + "Gold Plate", + "Holy Mail", + "Mirror Cuirass", + "Spiked Mail", + "Leather Corset", + "Jade Corset", + "Amethyst Corset", + "Emerald Corset", + "Ruby Corset", + "Sapphire Corset", + "Lilith Corset", + "Diamond Corset", + "Kirtle", + "Cotton Apron", + "Silk Negligee", + "Bathrobe", + "Frilly Camisole", + "Sequined Dress", + "Clown Shirt", + "Cocktail Dress", + "Dancer's Blouse", + "Cotehardie", + "Eye for Decay", + "Gambler Glasses", + "Sunglasses", + "Thick Glasses", + "Glasses", + "Monocle", + "Goggles", + "Uraeus", + "Royal Crown", + "Headguard", + "Iron Helmet", + "Stone Mask", + "Viking Helmet", + "Ballroom Masque", + "Fedora", + "Porkpie Hat", + "Silk Hat", + "Traveler's Hat", + "Skull Mask", + "Witch's Hat", + "Pearl Tiara", + "Diamond Tiara", + "Midnight Tiara", + "Sandals", + "Engineer Boots", + "Royal Sandals", + "Combat Boots", + "Slick Boots", + "Hiking Boots", + "Oxfords", + "Wingtips", + "Iron Leggings", + "Silver Leggings", + "Enamel Pinheels", + "Inuit Boots", + "Glass Shoes", + "Diamond Shoes" + ] + + self.good_armor_table = [ + "Feather Gear", + "Lorica", + "Platinum Plate", + "Samurai Plate", + "Berserker Mail", + "Ancient Armor", + "Scout Armor", + "Heavy Armor", + "Impervious Mail", + "Diamond Corset", + "Miko Dress", + "Europa's Dress", + "Dalmatica", + "Princess Coat", + "Wedding Dress", + "Samurai Helmet", + "Mourning Veil", + "Open Veil", + "Holy Veil", + "Arachne Hennin", + "Muse's Tiara", + "Gold Leggings", + "Platinum Lggngs", + "Succubus Boots", + "Prima Shoes", + "Silent Sandals" + ] + + self.accessory_table = [ + "Studded Choker", + "Astral Ring", + "Gold Ring", + "Cape", + "Blue Cape", + "Miser Ring", + "Sage Ring", + "Thief Ring", + "Pearl Ring", + "Tough Ring", + "Master Ring", + "Skull Ring", + "Immunity Ring", + "Blessed Ring", + "Hercules Ring", + "Hero's Cape", + "Elven Cape", + "Invisible Cape", + "Holy Mantle", + "Paludamentum", + "Blessed Ankh", + "Orchid Corsage", + "Rose Corsage", + "Sorceress Crest", + "Forget-Me-Not", + "Platinum Chain", + "Heart Brooch", + "Astral Brooch", + "Abalone Brooch", + "Moon Brooch" + + ] + + def create_item(self, name: str) -> CVPoRItem: + from .generator_main import set_classifications + data = set_classifications(self, name) + return CVPoRItem(name, data.classification, data.code, self.player) + diff --git a/worlds/cv_por/archipelago.json b/worlds/cv_por/archipelago.json new file mode 100644 index 000000000000..0664d43828fb --- /dev/null +++ b/worlds/cv_por/archipelago.json @@ -0,0 +1,4 @@ +{"game": "Castlevania: Portrait of Ruin", +"authors": ["Pink Switch"], +"minimum_ap_version": "0.6.7", +"world_version": "1.0.0"} \ No newline at end of file diff --git a/worlds/cv_por/docs/en_Castlevania Portrait of Ruin.md b/worlds/cv_por/docs/en_Castlevania Portrait of Ruin.md new file mode 100644 index 000000000000..56a8fcda0b79 --- /dev/null +++ b/worlds/cv_por/docs/en_Castlevania Portrait of Ruin.md @@ -0,0 +1,29 @@ +# Castlevania: Dawn of Sorrow + +## Where is the options page? + +The [player options page for this game](../player-options) contains all the options you need to configure and export a config file. + +## What does randomization do to this game? + +As of right now, Dawn of Sorrow randomizes most items. + +## What is the goal when randomized? +There are a few selectable goals. +Throne Room: Get to the Throne Room. Dario is replaced with Menace. +Abyss: Defeat Menace in the Abyss. The Mine of Judgment is opened when the power of darkness is unleashed at the center of the castle. +Abyss Plus: The same as Abyss, however you will be unable to trigger the power of darkness until you defeat Aguni in the throne room. + +## What items and locations get shuffled? + +You can find any consumable, armor, weapon, money, or Souls. +The Locations that are shuffled include all static item pickups, as well as all Boss Souls. +The event items you get from Yoko and Arikado are placed on the ground instead. +The 3 "easter egg" items are randomized, but their appearance does not change. + +## What happens when I receive an item? +When you receive an item, it should automatically be added into your game. + +## Changes from the Base Game ## +You will always have an item in your inventory called the Return Gem. +If you find yourself stuck, you can use it to warp to the starting room. \ No newline at end of file diff --git a/worlds/cv_por/docs/setup_en.md b/worlds/cv_por/docs/setup_en.md new file mode 100644 index 000000000000..4ecebad41992 --- /dev/null +++ b/worlds/cv_por/docs/setup_en.md @@ -0,0 +1,56 @@ +# Castlevania: Dawn of Sorrow Archipelago Randomizer Setup Guide + +## Required Software + +- [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases). + + +- BizHawk version 2.10 or newer +- A legally obtained US ROM of Castlevania: Dawn of Sorrow. The MD5 hash should be cc0f25b8783fb83cb4588d1c111bdc18. + +## Installation Procedures + +### Windows Setup + +1. Download and install Archipelago from the link above, making sure to install the most recent version. + +## Create a Config (.yaml) File + +### What is a config file and why do I need one? + +See the guide on setting up a basic YAML at the Archipelago setup +guide: [Basic Multiworld Setup Guide](/tutorial/Archipelago/setup/en) + +### Where do I get an options file? + +The Player Options page on the website allows you to configure your personal options and export a config file from +them. You can also create one with your local Archipelago install. + +### Verifying your config file + +If you would like to validate your config file to make sure it works, you may do so on the YAML Validator page. YAML +validator page: [YAML Validation page](/mysterycheck) + +## Joining a MultiWorld Game + +### Generating and Patching a Game +1. Follow the general Archipelago instructions for generating a game. This will generate an output file for you. Your patch file will have the .apcvdos file extension. + Open ArchipelagoLauncher.exe +2. Select "Open Patch" on the left side and select your patch file. Alternatively, you can open the ArchipelagoBizhawkClient with your patch file. +3. If this is your first time patching, you will be prompted to locate your vanilla ROM. +4. A patched .nds file will be created in the same place as the patch file. +5. On your first time opening a patch with BizHawk Client, you will also be asked to locate EmuHawk.exe in your BizHawk install. + +If you are playing offline, you can simply run your ROM at this point. For Multiworld play, see the following section. + +### Connecting to a Server +By default, opening a patch file will do steps 1-5 below for you automatically. Even so, keep them in your memory just in case you have to close and reopen a window mid-game for some reason. + +1. This game uses Archipelago's BizHawk Client. If the client isn't still open from when you patched your game, you can re-open it from the launcher. +2. Ensure EmuHawk is running the patched ROM. +3. In EmuHawk, go to Tools > Lua Console. This window must stay open while playing. +4. In the Lua Console window, go to Script > Open Script…. +5. Navigate to your Archipelago install folder and open data/lua/connector_bizhawk_generic.lua. +6. The emulator may freeze every few seconds until it manages to connect to the client. This is expected. The BizHawk Client window should indicate that it connected and recognized the game. +7. To connect the client to the server, enter your room's address and port (e.g. archipelago.gg:38281) into the top text field of the client and click Connect. +8. You should now be able to receive and send items. You'll need to do these steps every time you want to reconnect. It is perfectly safe to make progress offline; everything will re-sync when you reconnect. diff --git a/worlds/cv_por/generator_main.py b/worlds/cv_por/generator_main.py new file mode 100644 index 000000000000..3454edf87c0d --- /dev/null +++ b/worlds/cv_por/generator_main.py @@ -0,0 +1,165 @@ +import os +import typing +import pkgutil + +from BaseClasses import Item +from typing import Dict +from .Items import item_table + + +class CVPoRItem(Item): + game: str = "Castlevania: Portrait of Ruin" + + +def generate_early(world) -> None: + from .setup_game import setup_game + if hasattr(world.multiworld, "re_gen_passthrough"): # If UT + if "Castlevania: Portrait of Ruin" not in world.multiworld.re_gen_passthrough: + return + passthrough = world.multiworld.re_gen_passthrough["Castlevania: Portrait of Ruin"] + world.options.goal = passthrough["goal"] + world.options.start_with_change_cube = passthrough["start_with_change_cube"] + world.options.brauner_portraits = passthrough["brauner_portraits"] + world.options.dracula_portraits = passthrough["dracula_portraits"] + world.options.nest_portraits = passthrough["nest_portraits"] + world.options.nest_of_evil_state = passthrough["nest_of_evil"] + world.options.brauner_required = passthrough["brauner_required"] + setup_game(world) + + world.auth_id = world.random.getrandbits(32) + + +def create_regions(world) -> None: + from .setup_game import place_static_items + from .Regions import init_areas + + init_areas(world) + place_static_items(world) + + +def create_items(world) -> None: + pool = [] + for name, data in item_table.items(): + for _ in range(data.default_count): + item = set_classifications(world, name) + pool.append(item) + + if world.options.shuffle_whip: + pool.append(set_classifications(world, "True Vampire Killer")) + + if world.options.add_extra_items: + pool.extend([ + set_classifications(world, "Puppet Master"), + set_classifications(world, "Tori"), + set_classifications(world, "Seiryu"), + set_classifications(world, "Suzaku"), + set_classifications(world, "Byakko"), + set_classifications(world, "Gnebu"), + ]) + + if not world.options.exclude_owl_morph: + pool.append(set_classifications(world, "Owl Morph")) + + if not world.options.start_with_change_cube: + pool.append(set_classifications(world, "Change Cube")) + + if world.options.nest_of_evil_state: + pool.extend([ + set_classifications(world, "Greatest Five"), + set_classifications(world, "Tome of Arms p1"), + set_classifications(world, "Tome of Arms p2"), + ]) + + filler_location_count = len(world.multiworld.get_unfilled_locations(world.player)) - len(pool) + for i in range(filler_location_count): + item = set_classifications(world, get_filler_item_name(world)) + pool.append(item) + + world.multiworld.itempool += pool + + +def set_rules(world) -> None: + from .Rules import set_location_rules + set_location_rules(world) + + +def set_classifications(world, name) -> CVPoRItem: + # Make quest items be prog, here. + item_data = item_table[name] + item = CVPoRItem(name, item_data.classification, item_data.code, world.player) + return item + + +def generate_output(world, output_directory: str) -> None: + from .Rom import PoRProcPatch, patch_rom + try: + code_patch = pkgutil.get_data(__name__, "src/overlay_119.bin") + patch = PoRProcPatch(player=world.player, player_name=world.multiworld.player_name[world.player]) + patch.write_file("por_base.bsdiff4", pkgutil.get_data(__name__, "src/por_base.bsdiff4")) + patch_rom(world, patch, code_patch) + + world.rom_name = patch.name + + patch.write(os.path.join(output_directory, + f"{world.multiworld.get_out_file_name_base(world.player)}{patch.patch_file_ending}")) + except Exception: + raise + finally: + world.rom_name_available_event.set() # make sure threading continues and errors are collected + +def modify_multidata(world, multidata: dict) -> None: + # wait for self.rom_name to be available. + world.rom_name_available_event.wait() + rom_name = getattr(world, "rom_name", None) + if rom_name: + multidata["connect_names"][world.rom_name] = multidata["connect_names"][world.multiworld.player_name[world.player]] + +def fill_slot_data(world) -> Dict[str, typing.Any]: + return { + "goal": world.options.goal.value, + "start_with_change_cube": world.options.start_with_change_cube.value, + "brauner_portraits": world.options.brauner_portraits.value, + "dracula_portrtaits": world.options.dracula_portraits.value, + "nest_portrtaits": world.options.nest_portraits.value, + "nest_of_evil": world.options.nest_of_evil_state.value, + "brauner_required": world.options.brauner_required.value + } + + +def get_filler_item_name(world) -> str: + from .Items import money_table, good_food_table, consumable_table + weights = {"subweapon": 5, "good_weapon": 7, "accessory": 8, "good_food": 10, "good_armor": 15, "money": 20, + "weapon": 30, "armor": 40, "consumable": 60} + + weight_table = { + "subweapon": world.subweapon_filler_table, + "good_weapon": world.good_weapon_table, + "weapon": world.weapon_table, + "armor": world.armor_table, + "good_armor": world.good_armor_table, + "money": money_table, + "consumable": consumable_table, + "good_food": good_food_table, + "accessory": world.accessory_table + } + for fill_type, table in weight_table.items(): + if not table: # Remove empty tables to prevent them from being chosen + weights[fill_type] = 0 + + filler_type = world.random.choices(list(weights), weights=list(weights.values()), k=1)[0] + filler_item = world.random.choice(weight_table[filler_type]) + if not world.has_tried_magus_ring: + world.has_tried_magus_ring = True + if world.random.randint(0, 101) <= 10: # Magus ring should have a single 10/100 chance to be placed + filler_item = "Magus Ring" + return filler_item + + if filler_type not in ["consumable", "good_food", "money"]: + weight_table[filler_type].remove(filler_item) # Remove equipment from the corresponding table so it doesn't gen again + + return filler_item + + +def create_item(world, name: str) -> CVPoRItem: + data = set_classifications(world, name) + return CVPoRItem(name, data.classification, data.code, world.player) diff --git a/worlds/cv_por/setup_game.py b/worlds/cv_por/setup_game.py new file mode 100644 index 000000000000..f54b632ea895 --- /dev/null +++ b/worlds/cv_por/setup_game.py @@ -0,0 +1,38 @@ +def setup_game(world): + world.portrait_connections = { + "City of Haze": "City of Haze", + "13th Street": "13th Street", + "Sandy Grave": "Sandy Grave", + "Forgotten City": "Forgotten City", + "Nation of Fools": "Nation of Fools", + "Burnt Paradise": "Burnt Paradise", + "Forest of Doom": "Forest of Doom", + "Dark Academy": "Dark Academy", + "Nest of Evil": "Nest of Evil" + } + + +def place_static_items(world): + world.get_location("Tower of Death: Elevator Switch").place_locked_item(world.create_item("Tower Elevator Active")) + world.get_location("City of Haze: Boss Room").place_locked_item(world.create_item("Portrait Clear")) + world.get_location("13th Street: Boss Room").place_locked_item(world.create_item("Portrait Clear")) + world.get_location("Sandy Grave: Boss Room").place_locked_item(world.create_item("Portrait Clear")) + world.get_location("Forgotten City: Boss Room").place_locked_item(world.create_item("Portrait Clear")) + world.get_location("Nation of Fools: Boss Room").place_locked_item(world.create_item("Portrait Clear")) + world.get_location("Burnt Paradise: Boss Room").place_locked_item(world.create_item("Portrait Clear")) + world.get_location("Forest of Doom: Boss Room").place_locked_item(world.create_item("Portrait Clear")) + world.get_location("Dark Academy: Boss Room").place_locked_item(world.create_item("Portrait Clear")) + + if world.options.goal: + world.get_location("Lost Gallery: Studio Portrait Fight").place_locked_item(world.create_item("Brauner Defeated")) + world.get_location("The Throne Room: Dracula").place_locked_item(world.create_item("Dracula Defeated")) + else: + world.get_location("Lost Gallery: Studio Portrait Fight").place_locked_item(world.create_item("Dracula Defeated")) + + if not world.options.shuffle_whip: + world.get_location("Master's Keep: Whip Trial").place_locked_item(world.create_item("True Vampire Killer")) + + if not world.options.nest_of_evil_state: + world.get_location("Nest of Evil: First Item").place_locked_item(world.create_item("Tome of Arms p1")) + world.get_location("Nest of Evil: Second Item").place_locked_item(world.create_item("Tome of Arms p2")) + world.get_location("Nest of Evil: Doppelganger Reward").place_locked_item(world.create_item("Greatest Five")) diff --git a/worlds/cv_por/src/overlay_119.bin b/worlds/cv_por/src/overlay_119.bin new file mode 100644 index 000000000000..2f88de2bc14c Binary files /dev/null and b/worlds/cv_por/src/overlay_119.bin differ diff --git a/worlds/cv_por/src/por_ap_main.asm b/worlds/cv_por/src/por_ap_main.asm new file mode 100644 index 000000000000..e8f3f3c537b3 --- /dev/null +++ b/worlds/cv_por/src/por_ap_main.asm @@ -0,0 +1,1186 @@ +.nds +.relativeinclude on +.erroronwarning on + +@Overlay119Start equ 0x02308EC0 +@FreeSpace equ @Overlay119Start + 0x60 + +@ServerItemType equ 0x02308ED0 ; 2 bytes +@TotalItemsReceived equ 0x02308ED2 ; 2 bytes + +@OptionFlag_BraunerRequired equ 0x1 +@OptionFlag_NestRequired equ 0x2 + +;;;;;;;;;;;;;;;;; +.open "ftc/arm9.bin", 02000000h + .org 0x020523B0 + bl @GetRemoteItem + + .org 0x02041DB4 + bl @SaveAPData + + .org 0x0203A30C + nop ; Infinite-use Magical Tickets + + .org 0x0203A24C + ands r0, r0, 0x80000007 ; Allow tickets before having saved the game + + .org 0x0203A26C + b 0203A27Ch ; Allow ticket use before visiting the shop + + .org 0x0204FEC4 + bl @AddCyanPaletteToItemNames + + .org 0x0204FE94 + bl @UseOverrideColor + + .org 0x02042388 + bl @LoadAPData ; The game loads file X when copying data. Load in the relevant AP data so it gets copied as well. + + .org 0x020423A4 + bl @SaveAPData ; The game saves file Y when copying data. Save the AP data we JUST copied out of the old file into the new one. + + .org 0x02079294 + bl @CheckPresenceOfLocket + + .org 0x0203B65C + b @DontEquipOnGlitchMenu + + .org 0x02079874 + bl @PostBraunerCheck + + .org 0x02076B84 + b @CheckBraunerRequirements + + .org 0x0206EAA0 + bl @ShowBreakableWalls + + .org 0x02040554 + b @BetterQuestHandler + + .org 0x02040AE0 + b @GetQuestRewardText + + .org 0x02040C94 + bl @GetQuestRewardDesc + + .org 0x020E537C + .dw @PostBehemothRoom +;;;;;;;;;;;;;;;;;;;;;;;;; + ; Quest rewards, refitted for new format... + .org 0x020DFD40 ; Preparation + .dh 0x0862 + + .org 0x020DFD50 ; Meat + .dh 0x0345 + + .org 0x020DFD60 ; Bible + .dh 0x080E + + .org 0x020DFD70 ; Whip 2 + .dh 0x086A + + .org 0x020DFD80 ; Spinning Art + .dh 0x0866 + + .org 0x020DFD90 ; rocket slash + .dh 0x0826 + + .org 0x020DFDA0 ; thief ring + .dh 0x070B + + .org 0x020DFDb0 ; blessed ring + .dh 0x0711 + + .org 0x020DFDc0 ; holy water + .dh 0x080D + + .org 0x020DFDD0 ; martial art + .dh 0x0867 + + .org 0x020DFDE0 ; holy appearance + .dh 0x082F + + .org 0x020DFDF0 ; luck boost + .dh 0x0836 + + .org 0x020DFE00 ; mental 1 + .dh 0x0209 + + .org 0x020DFE10 ; mental 2 + .dh 0x0209 + + .org 0x020DFE20 ; spear of legend + .dh 0x0337 + + .org 0x020DFE30 ; mental 3 + .dh 0x0209 + + .org 0x020DFE40 ; nest of evil (UNUSED, REMOVE LATER?) + .dh 0x0100 + + .org 0x020DFE50 ; ghoul king + .dh 0x0710 + + .org 0x020DFE60 ; abandon greed + .dh 0x0709 + + .org 0x020DFE70 ; a rank huntor + .dh 0x032B + + .org 0x020DFE80 ; mental 4 + .dh 0x00209 + + .org 0x020DFE90 ; s rank huntor + .dh 0x0309 + + .org 0x020DFEA0 ; gambler + .dh 0x0502 + + .org 0x020DFEB0 ; time sotp + .dh 0x082E + + .org 0x020DFEC0 ; poison + .dh 0x0321 + + .org 0x020DFED0 ; str 1 + .dh 0x0208 + + .org 0x020DFEE0 ; str 2 + .dh 0x0208 + + .org 0x020DFEF0 ; stage + .dh 0x025A + + .org 0x020DFF00 ; str 3 + .dh 0x0208 + + .org 0x020DFF10 ; cross + .dh 0x080C + + .org 0x020DFF20 ; str 4 + .dh 0x0208 + + .org 0x020DFF30 ; page + .dh 0x0314 + + .org 0x020DFF40 ; tasks + .dh 0x070A + + .org 0x020DFF50 ; holy power + .dh 0x0858 + + .org 0x020DFF60 ; almighty + .dh 0x0325 + + .org 0x020DFF70 ; sage + .dh 0x071F + + .org 0x020DFF80 ; gergoth + .dh 0x084A + + ; Fix the map explore/suspend bug + .org 0x0202E5F8 + push r4-r10,r14 + + ldr r4, =020FCAB0h + ldr r5, =020FCC10h + ldr r6, =02111778h + ldrh r7, [r6] ; Read room's x pos from 02111778 + ldrh r8, [r6, 2h] ; Read room's y pos from 0211177A + + ldr r0, [r4] ; Read player 1's x pos from 020FCAB0 + bl @ClampXPosToRoomWidth + add r0, r0, r7 ; Add player 1's x to room's x + strh r0, [r6, 4h] ; Store player 1's x on map to 0211177C + + ldr r0, [r5] ; Read player 2's x pos from 020FCC10 + bl @ClampXPosToRoomWidth + add r0, r0, r7 ; Add player 2's x to room's x + strh r0, [r6, 6h] ; Store player 2's x on map to 0211177E + + ldr r0, [r4, 4h] ; Read player 1's y pos from 020FCAB4 + bl @ClampYPosToRoomHeight + add r0, r0, r8 ; Add player 1's y to room's y + strh r0, [r6, 8h] ; Store player 1's y on map to 02111780 + + ldr r0, [r5, 4h] ; Read player 2's y pos from 020FCC14 + bl @ClampYPosToRoomHeight + add r0, r0, r8 ; Add player 2's y to room's y + strh r0, [r6, 0Ah] ; Store player 2's y on map to 02111782 + + + ; The rewritten first half of the function now correctly updates the players' positions on the map. + ; But the second half of the function which updates the tile being explored needs certain values to be in certain registers to work correctly. + ; So we initialize those registers here. + ldrb r4, [r6, 0Dh] ; r4 has current area index read from 02111785 + mov r0, r4 + bl 02030570h ; MapGetExploredTileListForArea + mov r5, r0 ; r5 has current area's explored tile list pointer + mov r0, r4 ; ; r0 has current area index + ldr r7, =020CA580h ; PointerToGameObject + ldr r14, =1B50Ch + ldr r6, =1B510h + b 0202E6CCh + .pool + +.close +;;;;;;;;;;;;;;;;;;;;;; +.open "ftc/overlay9_0", 021CDF60h + ; If we own 9 of an item, pretend we have 8 + ; this allows the item to still be properly picked up even at cap + .org 0x021E4400 ; In GiveItem + movge r9, 8 + nop + nop + + .org 0x021E5A30 + nop ; Prevent the game from not letting you pick up 9 of a skill. This is autocapped anyways... + + .org 0x021E4418 + bl @SpecialItemHandler + + .org 0x021FCA80 + bl @InitializeNewGameData + + .org 0x021E5414 + bl @LoadAPItemColor + +;;;;;;;;;;;;;;;; + .org 0x0221BBBC ; Repoint the Konami Man and Twin Bee's item names + .dw 0x0222253C ; This so that they can both use the name 'AP Item' + .dw 0x0222253C +;;;;;;;;;;;;;;;;;;;;; +;overlay 9 0 +.close + +.open "ftc/overlay9_1", 0x0221F680 + .org 0x0222253E + .db 0x21, 0x30, 0x00, 0x49, 0x54, 0x45, 0x4D, 0xEA ; AP Item - editor repoints text so im doing it here manually +.close +;;;;;;;;;;;;;;;;;;;;;; + +.open "ftc/overlay9_2", 0x0221F680 + .org 0x0222B076 + ; The Management says we shouldn't enter this portrait + ; without Stella's locket... + .db 0x34, 0x48, 0x45, 0x00, 0x2D, 0x41, 0x4E, 0x41, 0x47, 0x45, 0x4D, 0x45, 0x4E, 0x54, 0x00, 0x53 + .db 0x41, 0x59, 0x53, 0x00, 0x57, 0x45, 0x00, 0x53, 0x48, 0x4F, 0x55, 0x4C, 0x44, 0x4E, 0x07, 0x54 + .db 0x00, 0x45, 0x4E, 0x54, 0x45, 0x52, 0xE6, 0x54, 0x48, 0x49, 0x53, 0x00, 0x50, 0x4F, 0x52, 0x54 + .db 0x52, 0x41, 0x49, 0x54, 0x00, 0x57, 0x49, 0x54, 0x48, 0x4F, 0x55, 0x54, 0x00, 0x33, 0x54, 0x45 + .db 0x4C, 0x4C, 0x41, 0x07, 0x53, 0x00, 0x4C, 0x4F, 0x43, 0x4B, 0x45, 0x54, 0x0E, 0x0E, 0x0E, 0xE5 + .db 0xE4 + + ;...? + ;...Who the hell is "The Management?!" + .db 0xE7, 0x00, 0xE3, 0x09, 0x0E, 0x0E, 0x0E, 0x1F, 0xE5, 0xE9, 0xE3, 0x02, 0x0E, 0x0E, 0x0E, 0x37 + .db 0x48, 0x4F, 0x00, 0x54, 0x48, 0x45, 0x00, 0x48, 0x45, 0x4C, 0x4C, 0x00, 0x49, 0x53, 0x00, 0x02 + .db 0x34, 0x48, 0x45, 0x00, 0x2D, 0x41, 0x4E, 0x41, 0x47, 0x45, 0x4D, 0x45, 0x4E, 0x54, 0x02, 0x1F + .db 0x01, 0xE5, 0xE4 + + ; Me. + ; I'm The Management. + .db 0xE7, 0x01, 0xE3, 0x0B, 0x2D, 0x45, 0x0E, 0xE6, 0x29, 0x07, 0x4D, 0x00, 0x54, 0x48, 0x45, 0x00 + .db 0x4D, 0x41, 0x4E, 0x41, 0x47, 0x45, 0x4D, 0x45, 0x4E, 0x54, 0x0E, 0xE5, 0xE4 + + ; Well, I guess I can't really + ; argue with that... + .db 0xE7, 0x00, 0xE3, 0x05, 0x37, 0x45, 0x4C, 0x4C, 0x0C, 0x00, 0x29, 0x00, 0x47, 0x55, 0x45, 0x53 + .db 0x53, 0x00, 0x29, 0x00, 0x43, 0x41, 0x4E, 0x07, 0x54, 0x00, 0x52, 0x45, 0x41, 0x4C, 0x4C, 0x59 + .db 0xE6, 0x41, 0x52, 0x47, 0x55, 0x45, 0x00, 0x57, 0x49, 0x54, 0x48, 0x00, 0x54, 0x48, 0x41, 0x54 + .db 0x0E, 0x0E, 0x0E, 0xE6, 0xE5, 0xE4, 0xEA +.close +;;;;;;;;;;;; +.open "ftc/overlay9_4", 0x0222C840 + .org 0x02232864 + nop ; Prevent the game from deleting the old vampire killer + + .org 0x02232890 + nop ; Prevent the game from giving you the new Vampire Killer, we do this in the next room instead + + .org 0x02232880 + nop ; Prevent the game from auto-equipping new Vampire Killer. +.close +;;;;;;;;;;;;;;;;;;;;;;;;; +.open "ftc/overlay9_25", 0x022D7900 + .org 0x022D95CC + bl @LoadAPData + +.close + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;; +.open "ftc/overlay9_27", 0x022E01A0 + .org 0x022E04B8 ; Skip to the title screen instantly instead of 6 years of fucking logos + mov r0, 0x77 + bl 0x02007970 ; Load our custom overlay so the original code doesnt get screwed with? +.close +;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +.open "ftc/overlay9_60", 0x022D7900 + ; Fix the enemy version of the creature setting the boss kill flag + .org 0x022D7D14 + ldrh r1, [r1, 3Ch] + cmp r1, 0h + beq 0x022D7D30 + + ; Then set the boss death flag. + ldr r2, [r0, 76Ch] + orr r2, r2, 200h + str r2, [r0, 76Ch] +.close + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +.open "ftc/overlay9_64", 022D7900h + .org 0x022D8B18 ; Prevents death's cutscene skip from setting his flag too early + nop + + .org 0x022E4F94 + nop ; Prevent dracula from setting the dracula flag mid fight +.close +;;;;;;;;;;;;;;;;;;;;; +.open "ftc/overlay9_78", 0x022E8820 + .org 0x022E8884 + cmp r1, 0xFF ; Always set the drawbridge to open + + .org 0x022E898C + bl @Undergroundpassage_Portraitcheck +.close + +.open "ftc/overlay9_79", 0x022E8820 + .org 0x022E9348 + b 0x022E9364 ; Skips Wind's locket cutscene, removed for brevity +.close +;;;;;;;; +;Tile data for the tower room. adds platforms +.open "ftc/overlay9_85", 0x022E8820 + .org 0x022F3FA4 + .dh 0x0044 + .dh 0x0045 + .dh 0x0044 + .dh 0x0047 +.close +;;;;;;;;;;;;;;;;;;;;;;;;; +.open "ftc/overlay9_86", 0x022E8820 + .org 0x022EC2BC + nop ; Skip over giving the original locket + + .org 0x022EC304 + bl @GiveLocketItem + + .org 0x022EC8EC + b 0x022EC900 ; Forest of Doom portrait. Ignore the shortened dialogue so I can write my own thing here + + .org 0x022EC8A0 + b @ForestPortraitSceneCheck +.close +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +.open "ftc/overlay9_88", 0x022E8820 + .org 0x022E8880 + b @CheckDraculaBarrierRequirements +.close + +.open "ftc/overlay9_89", 0x022E8820 + .org 0x022EA018 + bl @GetWhipMemItem +.close +;;;;;;;;;;;;;;;;;;;;;;;; +.open "ftc/overlay9_90", 0x022E8820 + .org 0x022E8BD8 + bl @EndDracula +.close +;;;;;;;;;;;;;;;;;;;;;;;; +.open "ftc/overlay9_111", 0x022E8820 +; Breakable ceiling that lets rain through from Dark Academy. +.org 0x022E8FEC + bl @ShowBreakableWalls +.close +;;;;;;;;;;;;;;;;;;;;;;; + +.open "ftc/overlay9_119", @Overlay119Start + +.org @FreeSpace +.area 0x1F000 ; Maximum overlay space, failsafe if too big +;;;;;;;;;;;;;;;;; +;2308F20 +@AP_playerauth: ; Used for the Player name as well as the current world version + .fill 0x20 + +;2308F40 +@ItemColorTable: + .fill 0x200 ; This space is reserved for AP item tag colors +.align 4 + +;2309140 +@PostBehemothRoom: + .dh 0x00E8 ; Upper pickup + .dh 0x0068 + .db 0x00 + .db 0x04 + .db 0x0B + .db 0x00 + .dh 0x000F + .dh 0x0069 + + .dh 0x00E0 ; Lower pickup + .dh 0x0140 + .db 0x00 + .db 0x04 + .db 0x08 + .db 0x00 + .dh 0x0040 + .dh 0x0000 + + .dh 0x0010 ; Behemoth wall + .dh 0x0130 + .db 0x00 + .db 0x02 + .db 0x3A + .db 0x00 + .dh 0x000C + .dh 0x0000 + .db 0xFF, 0x7F, 0xFF, 0x7F + +;02309168 +@WhipMemoryItem: + .dh 0x030A ;02308F28 + .db 0x00 ;02308F2A + +;0230916B +@StellaLocketItem: + .dh 0x0703 ; Type/ID for the locket 02308F25 + .db 0x00 ; Item color 02308F27 + +;0230916E +@GenerationFlags: + @OptionFlag_NestPortraits: ;0230916E + .db 0x08 + + @OptionFlag_BraunerPortraits: ;0230916F + .db 0x04 + + @OptionFlag_DraculaPortraits: ;02309170 + .db 0x00 + + @OptionFlag_DraculaRequirements: ;02309171 + .db 0x01 + + @OptionFlag_DraculaGoal: ;02309172 + .db 0x01 + + @OptionFlag_StartWithChangeCube: ;02309173 + .db 0x00 + + @OptionFlag_RevealHiddenWalls: ;02309174 + .db 0x00 + + @OptionFlag_RevealMap: ;02309175 + .db 0x00 + + @OptionFlag_EXPMult: ;02309176 + .dh 0x0000 + + .align 4 +;;;;;;;;;;;;;;;;;;; +; Gets an arbitrary item and gives it to the player. Used for Archipelago server items +;0x02308ED0 - item type +;0x02308ED1 - item ID +@GetRemoteItem: + push r0-r3, lr + + ldr r0, =0x020F628C ; Does the player have control? + ldrb r0, [r0] + cmp r0, 0 + bne @@End ; We don't want to get items while playing text and stuff + + ldr r0, = 0x020FC498 + ldrb r0, [r0] + cmp r0, 0 + bne @@End ; This is for main player control. Also used for cutscenes/autowalks? + + ldr r0, = 0x0211174C + ldr r0, [r0] + ldr r1, = 0x80100041 ; We don't want to get items while dying, in a cutscene, room transition, or otherwise in a no pausing state + ands r0, r0, r1 + bne @@End + + ldr r0, = @ServerItemType + mov r1, r0 ; back up the address + + ldrh r0, [r0] ; Load the item we've received + ands r1, r0, 0xFF ; Move the ID to r1 + ands r0, r0, 0xFF00 ; separate the TYPE from the ID + beq @@End ; If this is 0, assume we haven't received anything + + lsr r0, r0, 0x8 + bl @GetItemArbitrary + ldr r0, = @ServerItemType + mov r1, 0 + strh r1, [r0] ; Zero out the item so we don't get anything again +@@End: + pop r0-r3,lr + b 0x0201E9C0 ; Return to normal code +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;Gives something to the player. +; r0 = Item TYPE +; r1 = Item ID + +@MoneyValues: +.dh 0x0001, 0x000A, 0x0032, 0x0064, 0x01F4, 0x03E8, 0x07D0 +.align 4 + +@RamFlag_SkipArbitrarayPopup: +.db 0x00 +.align 4 + +@GetItemArbitrary: + push lr + cmp r0, 1 ; Money + beq @@GetMoney + cmp r0, 8 + beq @@GetSkill ; Skills/abilities + ; normal items/armor + push r0,r1 + cmp r1, 0x08 + blt @@NormalItem + cmp r1, 0x0A + bge @@NormalItem + b @@GetMaxUp ; ID 8-A are used for max up boosts +@@NormalItem: + bl 0x021E43E4 ; Subroutine for granting regular items + pop r0,r1 + bl 0x021E476C ; Get the global ID for the item + ldr r1, =@RamFlag_SkipArbitrarayPopup + ldrb r1,[r1] + cmp r1, 1 + beq @@SkipItemPopup ; If we're in the quest menu we don't want to show popups + mov r1, 0xF0 + bl 0x0204FE0C ; Display the got item popup + mov r0, 0x31 + bl 0x0204D6B0 ; Play the sound effect for the item + @@SkipItemPopup: + pop lr + bx lr +@@GetMaxUp: + pop r0,r1 + mov r0, r1 ; Put the ID in r0 + mov r1, 1 + mov r2, 0 + bl 0x021E3AA8 ; Useconsumable/Get Max Up + pop lr + bx lr +@@GetSkill: + mov r0, r1 + push r0, r1 + + ldr r1, =@RamFlag_SkipArbitrarayPopup + ldrb r1, [r1] + cmp r1, 1 + beq @@SkipSkillPopup + + mov r1, 0xF0 + bl 0x0204FB24 ; Show skill popup + + mov r0, 0x30 + bl 0x0204D6B0 ; Play the sound effect for the item + @@SkipSkillPopup: + pop r0, r1 + push r0 + mov r1, 1 + bl 0x02214F34 ; Give the skill + pop r0 + + cmp r0, 0x5C ; Is this a Relic? + blt @@EndSkill + sub r0, r0, 0x5C ; get the skill id num + mov r1, 1 + bl 0x02215308 ; Activate relic +@@EndSkill: + pop lr + bx lr +@@GetMoney: + mov r1, r1, lsl 1 + ldr r0, = @MoneyValues + ldrh r0, [r0, r1] ; Load the 2 bytes corresponding to the amount + push r0 + cmp r0, 1000 + bge @@BigMoneySound ; values over $1,000 play special sfx + mov r0, 0x2B + b @@SmallMoneySound +@@BigMoneySound: + mov r0, 0x2C +@@SmallMoneySound: + bl 0x0204D6B0 + pop r0 + push r0 + bl 0x021E3F24 ; Give this amount of money + pop r0 + push r2 + ldr r2, =@RamFlag_SkipArbitrarayPopup + ldrb r2,[r2] + cmp r2, 1 + beq @@SkipMoneyPopup + bl 0x0204FD4C ; Show the text popup for it + @@SkipMoneyPopup: + pop r2 + pop lr + bx lr +;;;;;;;;;;;;;;;;;;;;;;;;; +; Commits some extra data used by AP into SRAM +@SaveAPData: + push r0-r3 + push lr + mov r2, 0x50 + mul r0, r0, r2 ; filenum * 0x50 + + ldr r1, =0x1F00 + add r0, r0, r1 + ldr r1, =@ServerItemType + ldr r3, =0x00 + bl 0x020118F4 + pop lr + pop r0-r3 + b 0x02042C44 + +; Loads data out of the save file into main ram +@LoadAPData: + push r0-r3 + push lr + mov r2, 0x50 + mul r0, r0, r2 ; (Offset is FileNum * 0x50) + ldr r1, =0x1F00 + add r0, r0, r1 ; Add base offset + file offset + ldr r1, = @ServerItemType + bl 0x02011998 ;LoadData + pop lr + pop r0-r3 + b 0x02042FA4 ; load the rest of the file +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; When we get AP items (4F or 50), dont add them to the inventory +; This can also be purposed for a custom items handler for consumables greater than 0x5F +@SpecialItemHandler: + cmp r0, 3 + beq @@GiveSpecialWeapon ; Use this to check for the Vampire Killer + cmp r0, 2 + bne @@End ; Items like this will only be normal pickups... + cmp r1, 0x4F + beq @@SkipItem + cmp r1, 0x50 + beq @@SkipItem; 0x4F and 0x50 are used for offworld items... + cmp r1, 0x5F + bge @@CustomItemHandler ; Use this for custom, new items. +@@End: + b 0x021E4428 +@@SkipItem: + bx lr ; Just don't do the give routine +@@CustomItemHandler: + bx lr +@@GiveSpecialWeapon: + cmp r1, 1 ; Standard vampire killer + beq @@StandardVK + cmp r1, 0x0A + beq @@TrueVK + b 0x021E4428 +@@StandardVK: + push r0-r2,lr + mov r0, 3 + mov r1, 0x0A + bl 0x021E45A4 ; Do we have the True VK? + cmp r0, 0 ; If not... + pop r0-r2, lr + movne r0, 3 + movne r1, 0x0A ; If we already have the true VK, convert this item to the normal one + movne r2, 1 + bne @SpecialItemHandler + push r0-r2,lr + mov r0, 3 + mov r1, 1 + bl 0x021E45A4 ; Do we already have a copy of the normal one? + cmp r0, 0 ; If we do already have the normal VK, upgrade it! + pop r0-r2, lr + movne r0, 3 + movne r1, 0x0A + movne r2, 1 + bne @SpecialItemHandler ; If we have the + b 0x021E4428 ; Give the regular VK +@@TrueVK: + push r0-r2,lr + mov r0, 3 + mov r1, 1 + mov r2, 0 + bl 0x021E4428 ; Wipe out the standard VK no matter what + ldr r0, =0x0211217C + ldrb r1, [r0] ; Check Jonathans' current weapon + cmp r1, 1 ; Is the normal VK equipped? + bne @@SkipEquip + mov r1, 0x0A + strb r1,[r0] ; Replace with old one with the new one +@@SkipEquip: + pop r0-r2, lr + b 0x021E4428 + + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Clear out AP memory on a new file. +; Also, give the player a Magical Ticket to start with. +@InitializeNewGameData: + push lr + bl 0x021E4428 + mov r0, 2 + mov r1, 0x45 + mov r2, 1 + bl 0x021E4428 ; Give the player a magical ticket + mov r0, 0x00 ; Clear + ldr r1, =@ServerItemType + mov r2, 0x50 ; 50 bytes + bl 0x0209E570 ; Wipe the 0x50 bytes of AP memory when starting a new file + ldr r0, =@OptionFlag_StartWithChangeCube + ldrb r0, [r0] + cmp r0, 0 + beq @@EndCubeCheck + mov r0, 0 + mov r1, 1 + bl 0x02215308 ; Activate relic + mov r0, 0x5C + mov r1, 1 + bl 0x02215B2C ; Give the change cube +@@EndCubeCheck: + ldr r0, = @OptionFlag_RevealMap + ldrb r0,[r0] + cmp r0, 0 + beq @@End + ldr r0, =0x021119D0 + ldr r1, = 0xFFFFFFFF + str r1,[r0] ; Set all of the Map bits, revealing the full map +@@End: + pop lr + bx lr +;;;;;;;;;;;;;;;;;;;;;;;;; +@RamFlag_ItemColorOverride: +.db 0x00 +.align 4 + + +; Add a special handler to the DrawPopupWindow function +; We repoint to a new palette so I can add a new one +@ItemPalette_Cyan: + .dh 0x0000 + .dh 0x0000 + .dh 0x014E + .dh 0x0000 + .dh 0x1A32 + .dh 0x32D8 + .dh 0x573C + .dh 0xFFFF + .dh 0xFFFF + .dh 0x7ECE + .dh 0x0000 + .dh 0x0000 + .dh 0x65E6 + .dh 0x0000 + .dh 0x0000 + .dh 0x0000 +@AddCyanPaletteToItemNames: + cmp r0, 0x0E ; clearly oob so i use this for my stuff + bne @@LoadNormalPalette + ldr r5, = @ItemPalette_Cyan + bx lr +@@LoadNormalPalette: + add r5, r3, r0, lsl 0x05 + bx lr + +; Use the Override color for item name displays, if one is present. +; This is used to change the color of AP items +@UseOverrideColor: + ldr r0, = @RamFlag_ItemColorOverride + ldrb r0, [r0] ; Is there an AP item primed? + cmp r0, 0 + beq @@NormalColor + push r0, r1 + ldr r0, =@RamFlag_ItemColorOverride + mov r1, 0 + strb r1, [r0] ; Zero out the flag here + pop r0, r1 + b 0x0204FEB4 ; We use our custom color instead +@@NormalColor: + mov r0, 0x0A + b 0x0204FEB4 + +; Use the item's flag as an index into the ItemColorTable +@LoadAPItemColor: + ldrh r12, [r0, 0x3C] ; Get the flag val of the item + push r0, r1 + ldr r0, = @ItemColorTable + ldrb r0, [r0, r12] + cmp r0, 0 + beq @@Skip + ldr r1, = @RamFlag_ItemColorOverride + strb r0, [r1] ; Store it for later + +@@Skip: + pop r0,r1 + bx lr +;;;;;;;;;;;;;;;;;;;;;;; +;Stella's Locket is given to you in the cutscene, we handle it here instead +@GiveLocketItem: + push r0-r2,lr + bl 0x021D13EC ; Run the original code, runs end-of-battle cleanup on bosses + ldr r2, = @RamFlag_ItemColorOverride + ldr r1, = @StellaLocketItem + ldrb r0, [r1, 2] ; Read the Item's Color + strb r0,[r2] ; Transfer the item color so that we can use it if it's an ap item + ldrb r0, [r1, 1] ; R0 = Item Type + ldrb r1,[r1] ; R1 = Item ID + cmp r0, 8 + beq @@LocketSkill +@@GiveLocketSkill: + bl @GetItemArbitrary ; Pass off to the actual item routine + ldr r0, =0x2111BE9 + ldrb r1,[r0] + orr r1, r1,0x01 + strb r1,[r0] ; Write this Loc flag + pop r0-r2,lr + bx lr +@@LocketSkill: + push r0,r1 + bl 0x0202C064 ; Stella's cutscene unloaded the font, so if we get a skill we need to reload it + pop r0,r1 + b @@GiveLocketSkill +;;;;;;;;;;;;;;;;;;;;;; +; Normally this checks an event flag that Wind has been shown Stella's locket +; Instead, we only want to check that the item is owned +@CheckPresenceOfLocket: + push lr + mov r0, 7 + mov r1, 3 ; Load Stella's Locket + bl 0x021E45A4 ; Check the item count + cmp r0, 0 + pop lr + bx lr + +; This is the init for the cutscene when you haven't turned it in. +; Check if we have the locket, and cancel the cutscene if we do. +; This is to handle getting it while in the same room +@ForestPortraitSceneCheck: + bl @CheckPresenceOfLocket + bne @@EndForestScene + mov r0, r4 + b 0x022EC8A4 +@@EndForestScene: + push r1 + ldr r0, = 0x02111BB1 + ldrb r1, [r0] ; Get the flag + orr r1, r1, 0x0C ; Set the evnt flag that you've talked to wind about this + strb r1, [r0] + pop r1 + b 0x022EC87C ; Clean up and end the scene +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +; Prevent equipment from being applied on the glitchy character menus. +; This should patch out memory corruption +@DontEquipOnGlitchMenu: + ldrsh r2,[r0,0xE4] + cmp r2, 1 + bgt 0x0203B8DC + b 0x0203B660 +;;;;;;;;;;;;;;; +; Clamps position for the map, fixes suspend bug +@ClampXPosToRoomWidth: + mov r0, r0, asr 14h ; Divide X by 0x100000 subpixels to get X in screens. + cmp r0, 0h + movlt r0, 0h ; X = 0 if X < 0 + ldr r2, =02112458h + ldr r2, [r2] + ldrb r2, [r2] ; Load the room's width from the collision layer (the first layer) + sub r2, r2, 1h + cmp r0, r2 + movgt r0, r2 ; X = room_width-1 if X > room_width-1 + bx r14 +@ClampYPosToRoomHeight: + ldr r12,=2AAAAAABh + smull r12,r0,r12,r0 ; Divide Y in subpixels by 6 + mov r0, r0, asr 11h ; Further divide Y by 0x20000, divided by a total of 0xC0000 to get Y in screens + cmp r0, 0h + movlt r0, 0h ; Y = 0 if Y < 0 + ldr r2, =02112458h + ldr r2, [r2] + ldrb r2, [r2, 1h] ; Load the room's height from the collision layer (the first layer) + sub r2, r2, 1h + cmp r0, r2 + movgt r0, r2 ; Y = room_height-1 if Y > room_height-1 + bx r14 +;;;;;;;;;;;;;;;;;; +; Switches the underground passage/nest of evil to check portraits instead of a flag +@Undergroundpassage_Portraitcheck: + push lr + bl @CheckPortraitClearCount + ldr r1, = @OptionFlag_NestPortraits + ldrb r1,[r1] ; Check the set number + cmp r0, r1 ; If cleared portraits >= required + mov r0, 0 + bge @@ActivateCheck + cmp r0, 0 + pop lr + bx lr +@@ActivateCheck: + cmp r0, 1 + pop lr + bx lr +;;;;;;;;;;;;;;;;;; +; Counts how many portraits have been cleared +@PortraitBossFlags: + .db 0x01, 0x05, 0x06, 0x07, 0x8, 0x09, 0x0A, 0x0B +@CheckPortraitClearCount: + push r1-r6 + ldr r1, =0x021119DC + ldr r1,[r1] ; Load the bosses killed flags + mov r0, 0 + mov r2, 0 + mov r6, 1 + ldr r3, =@PortraitBossFlags +@@Loop: + ldrb r5, [r3,r2] ; Load the current flag + lsl r5, r6, r5 ; Shift to get a bit + ands r5, r5, r1 ; And this + addne r0, r0, 1 + cmp r2, 7 + addne r2, r2, 1 + bne @@Loop + pop r1-r6 + ;r0 = clear count + bx lr +;;;;;;;;;;;;;;;;;;;;;;;;;;; +; If Brauner Goal, teleport to ending. +; Else; teleport to portrait room +@PostBraunerCheck: + push r0 + ldr r0, = 0x02111785 + ldrb r0, [r0] ; Get the current area + cmp r0, 0x0B ; Are we in the Lost Galley? + bne @@NormalPortrait + pop r0 + ldr r0, = @OptionFlag_DraculaGoal + ldrb r0, [r0] + cmp r0, 1 ; Did the player choose Dracula goal? + beq @@DraculaTeleport + ldr r0, =0x021119DE + ldrb r1, [r0] + orr r1, r1, 0x02 ; Dracula + strb r1,[r0] ; Set Dracula's defeat flag so the ending works + mov r0, 0x0C + mov r1, 0x00 + mov r2, 0x00 + mov r3, 0x00 + b 0x02032D90 ; Warp to the epilogue room +@@DraculaTeleport: + mov r0, 0 + mov r1, 0x0B + mov r2, 0x00 + mov r3, 0x100 + b 0x02032D90 ; Warp to the Portrait room, outside the studio portrait +@@NormalPortrait: + pop r0 + b 0x02032D90 ; Do the normal warp instead of the special warp +;;;;;;;;;;;;;;;;;;;;;;;;;;; +@GetWhipMemItem: + push r0-r2,lr + ldr r2, = @RamFlag_ItemColorOverride + ldr r1, = @WhipMemoryItem + ldrb r0, [r1, 2] ; Read the Item's Color + strb r0,[r2] ; Transfer the item color so that we can use it if it's an ap item + ldrb r0, [r1, 1] ; R0 = Item Type + ldrb r1,[r1] ; R1 = Item ID + bl @GetItemArbitrary + ldr r0, = 0x02111BB8 + ldrb r1,[r0] + orr r1, r1, 0x80 + strb r1,[r0] ; Set the Loc flag for this check + + + pop r0-r2,lr + b 0x021CE664 +;;;;;;;;;;;;;;;;;;;;;;;;;;; +;Move the Dracula flag to HERE so it doesn't go off mid-fight +@EndDracula: + push r0,r1 + ldr r0, =0x021119DE + ldrb r1,[r0] + orr r1, r1, 0x02 + strb r1,[r0] + pop r0,r1 + b 0x02032D90 +;;;;;;;;;;;;;;;;;;;;;;;; +; Check the AP requirements for Dracula's barrier here +@CheckDraculaBarrierRequirements: + push r0 + ldr r0, =@OptionFlag_DraculaGoal + ldrb r0,[r0] + cmp r0, 0 + popeq r0 + beq 0x022E8894 ; If the goal is to fight Brauner, we don't want to access Dracula at all, so we bail + + push lr + bl @CheckPortraitClearCount ; Grab the cleared portraits + pop lr + ldr r1, = @OptionFlag_DraculaPortraits ; and the required portraits + ldrb r1, [r1] + cmp r0, r1 + poplt r0 + blt 0x022E8894 ; If Cleared portraits < required, bail + ldr r0, =@OptionFlag_DraculaRequirements + ldrb r0,[r0] + ands r0,r0, @OptionFlag_BraunerRequired + beq @@SkipBraunerCheck ; If Brauner is not set as required, we can just skip this + ldr r0, =0x021119DC + ldr r0,[r0] ; Get the boss death flags + ands r0, r0, 0x8000 ; Check Brauner's death flag + popeq r0 + beq 0x022E8894 ; Brauner is required but not defeated, so bail +@@SkipBraunerCheck: + ldr r0, =@OptionFlag_DraculaRequirements + ldrb r0,[r0] + ands r0,r0, @OptionFlag_NestRequired + beq @@SkipNestRequirement + ldr r0, = 0x02111BC5 + ldrb r0,[r0] + ands r0,r0,0x20 ; This is the pickup flag for the final item in Nest of Evil. All of its flags are otherwise temporary, so this is the closest we can get to a clear check + popeq r0 + beq 0x022E8894 +@@SkipNestRequirement: + pop r0 + b 0x022E8884 ; All checks pass so delete the barrier +;;;;;;;;;;;;;;;;;;;;;;;; +; Checks the requirements to enter Brauner's portrait +@CheckBraunerRequirements: + push lr + bl @CheckPortraitClearCount + pop lr + ldr r1, = @OptionFlag_BraunerPortraits + ldrb r1, [r1] + cmp r0, r1 ; If we have cleared more than are required + bge @@PortraitRequirementMet + sub r0, r1, r0 ; Get the difference in portraits + cmp r0, 4 + bge @@Draw4Chains + mov r1, 1 + mov r1, r1, lsl r0 + sub r1, r1, 1 + strb r1, [r4,0x13C] + b 0x02076BF4 + + +@@PortraitRequirementMet: + ldr r0, = @OptionFlag_DraculaGoal + ldrb r0,[r0] + cmp r0, 1 + beq 0x02076BF4 ; If Dracula is the goal, skip this and move on + + ldr r0, = @OptionFlag_DraculaRequirements + ldrb r0,[r0] + ands r0, r0, @OptionFlag_NestRequired + cmp r0, 0 + beq 0x02076BF4 ; If Nest isn't required, don't check for it + + ldr r0, = 0x02111BC5 + ldrb r0,[r0] + ands r0,r0,0x20 ; This is the pickup flag for the final item in Nest of Evil. All of its flags are otherwise temporary, so this is the closest we can get to a clear check + bne 0x022E8894 ; If we've done it, just end + mov r0, 1 + strb r0, [r4,0x13C] ; Store ONE chain as needing to be cleared + b 0x02076BF4 +@@Draw4Chains: + mov r0, 0x0F + strb r0, [r4,0x13C] + b 0x02076BF4 +;;;;;;;;;;;;;;;; +@ShowBreakableWalls: + ldr r0, =@OptionFlag_RevealHiddenWalls + ldrb r0,[r0] + cmp r0, 0 ; the option is DISABLED + beq @@CheckForDecay + mov r0, 1 + bx lr +@@CheckForDecay: + push lr + mov r0, 3 + bl 0x02207C9C ; Check if Eye for Decay is equipped + pop lr + bx lr +;;;;;;;;;;;;;;;;;;; +; Vanilla quest rewards are bullshit. +;Instead, we do this- +@BetterQuestHandler: + push r3 + push r0,r3 + ldr r3, = @RamFlag_SkipArbitrarayPopup + mov r0, 1 + strb r0,[r3] + pop r0,r3 + sub r3, r3, 4 + ldrh r3,[r3, r0] + ands r1, r3, 0xFF ; Split this into item ID + mov r0, r3, lsr 8 ; and item type + bl @GetItemArbitrary + ldr r3, = @RamFlag_SkipArbitrarayPopup + mov r0, 0 + strb r0,[r3] + pop r3 + b 0x02040844 + + ;Convert the Reward text to get type/ID instead of global id +@GetQuestRewardText: + push lr + mov r0, r7, lsr 8 ; Funnel the TYPE into r0. the normal code does this by reverse ID searching + mov r6,r0 + ands r1, r7, 0xFF + cmp r0, 8 + bge @@GetQuestSkill + cmp r0, 1 + beq @@GetQuestMoney + bl 0x021E476C ; Get the global ID for this item + mov r7, r0 + pop lr + b 0x02040AF0 +@@GetQuestSkill: + pop lr + mov r6, r0 + mov r7, r1 + mov r0, r1 + b 0x02040AF4 +@@GetQuestMoney: + pop lr + mov r0, 0xB0 + add r1, r1, r0 + mov r6, 9 + mov r7, r1 + mov r0, r1 + b 0x02040AF4 + + ;Same, but for the item description +@GetQuestRewardDesc: + push lr + mov r0, r6, lsr 8 + ands r1, r6, 0xFF + cmp r0, 8 + bge @@GetQuestSkill + cmp r0, 1 + beq @@GetQuestMoney + bl 0x021E476C ; Get the global ID for this item + mov r6, r0 + ldr r0, =0x151 + pop lr + bx lr +@@GetQuestSkill: + pop lr + ldr r0, =0x151 + add r6, r0, r1 + bx lr +@@GetQuestMoney: + pop lr + ldr r1, = 0x051F + b 0x02040D18 + + + +.pool + +.endarea +.close \ No newline at end of file diff --git a/worlds/cv_por/src/por_base.bsdiff4 b/worlds/cv_por/src/por_base.bsdiff4 new file mode 100644 index 000000000000..d96347c60809 Binary files /dev/null and b/worlds/cv_por/src/por_base.bsdiff4 differ diff --git a/worlds/cv_por/static_location_data.py b/worlds/cv_por/static_location_data.py new file mode 100644 index 000000000000..e6e13bf663d4 --- /dev/null +++ b/worlds/cv_por/static_location_data.py @@ -0,0 +1,549 @@ +from typing import NamedTuple +# IDs are matched to which internal Flag number the location uses +# Because of this, they are asynchronous and out of order from what is expected +# They are listed here in numerical order for clarity + + +class LocationInformation(NamedTuple): # In-game information used to define each Location in the ROM. + pointer: int + file: str + is_grounded: bool = False + location_type: str = "Normal" + + +location_ids = { + "Entrance: Drawbridge Pit Item": 0x01, + "Entrance: Drawbridge Switch Item": 0x02, + "City of Haze: Church Hallway": 0x03, + "Entrance: Statue Room Above Statue": 0x04, + "City of Haze: Post-Boss Room": 0x05, + "City of Haze: Heavy Cart Item": 0x06, + "Sandy Grave: Pyramid 5F Nook": 0x07, + "Great Stairway: After Keremet Lower": 0x09, + "Forgotten City: Pyramid 1F Bricks": 0x0A, + "Nation of Fools: Central Top Shaft": 0x0D, + "Entrance: Above Hub": 0x0E, + "Entrance: Double Room Upper": 0x0F, + "Nation of Fools: Legion Arena": 0x10, + "Forest of Doom: Post-Boss Room": 0x11, + "Dark Academy: Right Building Center Room": 0x12, + "The Throne Room: Above Throne Left": 0x13, + "Sandy Grave: Boulder Room Tunnel": 0x14, + "City of Haze: Before Cart Room": 0x16, + "City of Haze: Underground Square Right": 0x17, + "Sandy Grave: Upper Big Underground Top Right": 0x18, + "Nation of Fools: Bottom Corner Room": 0x19, + "Nation of Fools: Mid-Right Corner Room": 0x1A, + "Sandy Grave: Lower Big Underground Room Bottom": 0x1B, + "Sandy Grave: Left Trap": 0x1C, + "Buried Chamber: Central Secret": 0x20, + "Sandy Grave: Sandworm Stomach": 0x21, + "Tower of Death: Second Gears Room Left": 0x22, + "Great Stairway: Lower Grand Staircase Corner Nook": 0x23, + "Master's Keep: Entrance": 0x24, + "Forest of Doom: Left Atrium Big Room": 0x25, + "Forgotten City: Big Shaft Room Left": 0x26, + "Nation of Fools: Left Upper Corner Room": 0x27, + "Dark Academy: Main Building Ghoul Room": 0x28, + "13th Street: Triple Moth Room": 0x29, + "Tower of Death: Secret Room": 0x2A, + "Nation of Fools: Right Horizontal Room": 0x2B, + "Burnt Paradise: Left Lower Isolated Room": 0x2C, + "Burnt Paradise: Right Lower Isolated Room": 0x2D, + "The Throne Room: Great Stairs Under Stairs": 0x2E, + "13th Street: Behind Final Guard": 0x2F, + "Buried Chamber: Right Secret": 0x30, + "Sandy Grave: Bottom Right Room": 0x31, + "Tower of Death: Passage Hidden Item": 0x32, + "Great Stairway: Left Loft Lower": 0x33, + "Entrance: Center Hidden Room": 0x35, + "Forgotten City: Big Shaft Room Right": 0x36, + "Nation of Fools: Right Bottom Corner Room": 0x37, + "Master's Keep: Hidden Room": 0x38, + "13th Street: Three Amalaric Snipers": 0x39, + "Dark Academy: Main Building Three Toads": 0x3A, + "Nation of Fools: Top Corner Room": 0x3B, + "Burnt Paradise: Left Upper Isolated Room": 0x3C, + "Burnt Paradise: Right Upper Isolated Room": 0x3D, + "The Throne Room: Great Stairs Center": 0x3E, + "Forgotten City: Upper Underground Square": 0x3F, + "Entrance: Double Room Lower": 0x40, + "Buried Chamber: Westmost Room": 0x41, + "Sandy Grave: Pyramid East 1F": 0x42, + "City of Haze: Register Room": 0x46, + "City of Haze: Bar Left": 0x47, + "Nation of Fools: Bottom-Left Room Building": 0x48, + "Sandy Grave: Pyramid 3F Lower": 0x49, + "Tower of Death: Rampart Room": 0x4A, + "Entrance: Beyond Great Armor Hallway": 0x4B, + "Tower of Death: Elevator Room Lower": 0x4C, + "Forest of Doom: Right Atrium East Square Room Right": 0x4D, + "Forgotten City: Outside Left": 0x4E, + "Sandy Grave: Upper Big Underground Top Left": 0x4F, + "Burnt Paradise: Left Upper Big Corner": 0x51, + "Great Stairway: Upper Grand Staircase Top Left Item": 0x53, + "City of Haze: Axe Courtyard": 0x54, + "Tower of Death: Top of the Tower": 0x55, + "Great Stairway: Upper Underground Room": 0x56, + "Entrance: Drawbridge Upper Item": 0x57, + "Entrance: Statue Room Upper": 0x58, + "Tower of Death: Second Gears Room Right": 0x59, + "Tower of Death: Lower Gears Room Spike Shaft": 0x5A, + "Master's Keep: Right Square Room": 0x5D, + "City of Haze: Central Hallway Stairs": 0x60, + "Buried Chamber: Right Shade Hall": 0x61, + "City of Haze: Right Corner Room": 0x64, + "City of Haze: Left Plaza": 0x65, + "Great Stairway: Underground Hidden Room": 0x66, + "Sandy Grave: Lower Big Underground Room Top": 0x67, + "Nation of Fools: Upper Upside Down": 0x68, + "Forgotten City: Lower Boulder Room Tunnel Alcove": 0x69, + "Dark Academy: Main Building Secret Ceiling": 0x6B, + "Forest of Doom: Secret Cave Room": 0x6C, + "Nest of Evil: Doppelganger Reward": 0x6D, + "City of Haze: Center Room Lower": 0x6E, + "Sandy Grave: On Statue": 0x6F, + "Tower of Death: Third Gears Room": 0x70, + "Entrance: Above Metal Block Room": 0x71, + "Dark Academy: Main Building Entrance": 0x72, + "Burnt Paradise: Bottom Corner": 0x73, + "Forest of Doom: Left Atrium Top": 0x75, + "Great Stairway: Before Keremet Room": 0x76, + "Forgotten City: Lower Boulder Room Upper Alcove": 0x77, + "Entrance: First Passage Upper Item": 0x78, + "Great Stairway: Upper Grand Staircase Corner Nook": 0x79, + "Burnt Paradise: Bottom Right Big Room": 0x7A, + "Entrance: Right Hidden Room": 0x7B, + "Entrance: First Passage Lower Item": 0x7C, + "City of Haze: Left Dance Hall Right Item": 0x7E, + "City of Haze: Tunnel Room Left": 0x7F, + "Burnt Paradise: Upper Left Big Room Middle Building": 0x82, + "City of Haze: Chunky Room": 0x84, + "Entrance: Pre-Behemoth Boss Room": 0x88, + "Great Stairway: After Keremet Upper": 0x89, + "Tower of Death: Gear Belt Room": 0x8A, + "City of Haze: Dance Halls Basement": 0x8B, + "City of Haze: Central Hallway Lower": 0x8C, + "Burnt Paradise: Upper Right Big Room Right Side": 0x8F, + "Buried Chamber: Central Hall": 0x92, + "Great Stairway: Central Nook": 0x93, + "Sandy Grave: Eastmost Item": 0x94, + "Sandy Grave: Behind Bricks": 0x95, + "City of Haze: Underground Square Left": 0xA0, + "Great Stairway: Upper Grand Staircase Upper Alcove": 0xA1, + "City of Haze: Right Dance Hall Secret Room": 0xA7, + "Nation of Fools: Bottom Left Medium Square On Wall": 0xA8, + "City of Haze: First Big Room": 0xA9, + "Buried Chamber: Final Shaft Item": 0xAA, + "Great Stairway: Central Upper": 0xAB, + "Forest of Doom: Right Atrium East Top Room": 0xAC, + "Dark Academy: Right Building Top Floor": 0xAD, + "Sandy Grave: Boulder Room Corner Alcove": 0xAE, + "Forgotten City: Post-Boss Item": 0xAF, + "City of Haze: Cart Secret Left": 0xB0, + "City of Haze: Winding Rooms": 0xB3, + "Great Stairway: Lower Grand Staircase Lower Alcove": 0xB4, + "Forest of Doom: Right Atrium East Square Room Left": 0xB5, + "Forgotten City: Bottom Chamber": 0xB9, + "City of Haze: Meat Room": 0xBB, + "Great Stairway: Big Underground Room Hidden Item": 0xBF, + "Great Stairway: Right Loft": 0xC0, + "Great Stairway: Left Loft": 0xC1, + "Nation of Fools: Bottom-Right Room Left Building": 0xC2, + "13th Street: Many Nyxes Room": 0xC4, + "The Throne Room: Above Throne Right": 0xC5, + "City of Haze: Tunnel Room Right": 0xC8, + "Great Stairway: Lower Grand Staircase Upper Alcove": 0xCA, + "Great Stairway: Underground East Room": 0xCB, + "Tower of Death: Elevator Room Top": 0xCC, + "Entrance: Hub Room": 0xCD, + "Buried Chamber: Eastmost Room": 0xCE, + "Tower of Death: Hidden Room": 0xD1, + "Nation of Fools: Bottom-Right Room Middle Building": 0xD4, + "Forgotten City: Pyramid 5F": 0xD5, + "13th Street: Train Room Secret Right": 0xD6, + "City of Haze: Left Dance Hall Left Item": 0xD8, + "Great Stairway: Big Underground Room Lower": 0xDA, + "Tower of Death: Elevator Room Middle": 0xDB, + "Nation of Fools: Crevice Item": 0xDC, + "City of Haze: First Passage": 0xDD, + "Forgotten City: Gorgon Room": 0xDE, + "City of Haze: Axe Armor Item": 0xE0, + "Sandy Grave: Giant Shaft Upper Item": 0xE1, + "City of Haze: Dance Halls Vertical Hallway": 0xE2, + "Tower of Death: Above Motorcycles": 0xE3, + "Nation of Fools: Top-Right Room Axe Item": 0xE4, + "Dark Academy: Main Building Secret Malphas Room": 0xE5, + "13th Street: Underground Secret Room": 0xE6, + "Great Stairway: Big Underground Room Upper": 0xE7, + "Forest of Doom: Right Atrium West Top Room": 0xE8, + "Burnt Paradise: Upper Left Big Room Left Building": 0xE9, + "13th Street: Underground Demon Room": 0xEA, + "Sandy Grave: Mega Ghost Room Lower": 0xEB, + "13th Street: Left Big Room": 0xEC, + "City of Haze: Bread Shelf": 0xED, + "Burnt Paradise: Upper Left Lower Corner Upright Building": 0xEE, + "City of Haze: Underground Secret Room": 0xEF, + "Sandy Grave: Mega Ghost Room Upper": 0xF0, + "Burnt Paradise: Lower Left Big Room": 0xF1, + "13th Street: Secret Floor Room": 0xF3, + "City of Haze: Right Dance Hall Item": 0xF7, + "Tower of Death: Top Gears Room": 0xF9, + "13th Street: East Courtyard": 0xFA, + "City of Haze: Corner Room": 0xFC, + "Sandy Grave: Giant Shaft Middle Item": 0xFD, + "Entrance: Lilith Room": 0x101, + "Sandy Grave: Right Trap": 0x103, + "Nation of Fools: Left Bottom Corner Room": 0x104, + "Forgotten City: Lower Underground Square Upper Item": 0x105, + "City of Haze: Center Room Top": 0x10E, + "Great Stairway: Upper Grand Staircase Middle Alcove": 0x10F, + "Buried Chamber: Right Shaft Top": 0x110, + "Sandy Grave: Pyramid 3F Upper": 0x113, + "Nation of Fools: Bottom Right Medium Square On Wall": 0x114, + "Sandy Grave: Lonely Mimic Alcove": 0x116, + "13th Street: Near Big Room": 0x118, + "Forgotten City: Pyramid East 3F": 0x119, + "Great Stairway: Upper Grand Staircase Lower Alcove": 0x11B, + "Nation of Fools: Right Vertical Room": 0x11C, + "Sandy Grave: Fleaman Pit": 0x11E, + "Nation of Fools: Top-Right Room Bottom Item": 0x11F, + "Forgotten City: Pyramid East 5F": 0x122, + "Burnt Paradise: Lower Vertical Hall": 0x123, + "Buried Chamber: West Shaft": 0x124, + "Great Stairway: Lower Grand Staircase Middle Alcove": 0x127, + "Entrance: Behemoth Chase Room": 0x12C, + "Entrance: First Passage Hidden Wall": 0x12D, + "Tower of Death: Lower Passage Hidden Item": 0x131, + "Dark Academy: West Building Breakable Wall": 0x134, + "Forgotten City: Armored Fleaman Pit": 0x137, + "Nation of Fools: Top-Left Room Right Building": 0x13F, + "Forest of Doom: Left Atrium Breakable Wall": 0x140, + "Sandy Grave: Giant Shaft Low Item": 0x144, + "The Throne Room: Great Stairs Hidden": 0x145, + "Nation of Fools: Top-Right Room Breakable Wall": 0x146, + "Burnt Paradise: Bottom Right Breakable Wall": 0x147, + "Burnt Paradise: Right Upper Big Corner On Wall": 0x148, + "Nation of Fools: Right Center Room": 0x149, + "Burnt Paradise: Left Big Corner": 0x14A, + "Buried Chamber: Hidden Wall Item": 0x014C, + "13th Street: Bakery Middle": 0x14D, + "City of Haze: Cart Secret Right": 0x14E, + "13th Street: Right Dance Hall Top Right": 0x150, + "13th Street: Bakery Left": 0x152, + "13th Street: Train Room Secret Left": 0x153, + "City of Haze: Bar Right": 0x154, + "13th Street: Square Room": 0x155, + "13th Street: Bakery Right": 0x156, + "Burnt Paradise: Bottom Right Corner Room": 0x157, + "Nation of Fools: Left Center Room": 0x15F, + "Dark Academy: Main Building Square Room Breakable Wall": 0x160, + "Nation of Fools: Upside Down Building": 0x161, + "Dark Academy: Main Building Minotaur Room": 0x163, + "Dark Academy: West Building Top Floor Right": 0x164, + "Nation of Fools: Secret Room": 0x166, + "Forest of Doom: Left Atrium Middle Room": 0x167, + "Burnt Paradise: Upper Right Big Room": 0x16B, + "Forest of Doom: Near Cave Save Breakable Wall": 0x16C, + "Sandy Grave: Pyramid East 5F": 0x173, + "Nest of Evil: First Item": 0x175, + "Nest of Evil: Second Item": 0x176, + "Forest of Doom: Left Atrium Upper Room": 0x177, + "Great Stairway: Connector Pipe Left": 0x180, + "Great Stairway: Connector Pipe Right": 0x181, + "The Throne Room: Great Stairs Left": 0x182, + "13th Street: Right Dance Hall Top Left": 0x183, + "Dark Academy: West Building Top Floor Left": 0x184, + "Forgotten City: Pyramid East 1F": 0x185, + "Forgotten City: Lower Underground Square Lower Item": 0x186, + "Tower of Death: Stella Item": 0x187, + "Master's Keep: Whip Trial": 0x188, + + "Quest: Preparations": 0x0200, + "Quest: Supersonic Punch": 0x0201, + "Quest: Ghosts of the Desert": 0x0202, + "Quest: Defender of the Stairs": 0x0203, + "Quest: The Spinning Art": 0x0204, + "Quest: Art of the Zephyr": 0x0205, + "Quest: Find the King of Birds": 0x0206, + "Quest: Overcome the Curse": 0x0207, + "Quest: The Statue's Tear": 0x0208, + "Quest: The Martial Art": 0x0209, + "Quest: Holy Appearance": 0x020A, + "Quest: Number of Fortune": 0x020B, + "Quest: Mental Training 1": 0x020C, + "Quest: Mental Training 2": 0x020D, + "Quest: The Spear of Legend": 0x020E, + "Quest: Mental Training 3": 0x020F, + "Quest: Defeat the Ghoul King": 0x0211, + "Quest: Abandon Greed": 0x0212, + "Quest: A Rank Hunter": 0x0213, + "Quest: Mental Training 4": 0x0214, + "Quest: S Rank Hunter": 0x0215, + "Quest: The Gambler": 0x0216, + "Quest: Hands of the Clock": 0x0217, + "Quest: Poison vs. Poison": 0x0218, + "Quest: Build Your Strength 1": 0x0219, + "Quest: Build Your Strength 2": 0x021A, + "Quest: The Lonely Stage": 0x021B, + "Quest: Build Your Strength 3": 0x021C, + "Quest: Pray Before the Cross": 0x021D, + "Quest: Build Your Strength 4": 0x021E, + "Quest: Lost Page": 0x021F, + "Quest: The Hundred Tasks": 0x0220, + "Quest: Master the Holy Power": 0x0221, + "Quest: Almighty": 0x0222, + "Quest: The Great Sage": 0x0223, + "Quest: Kill Gergoth": 0x0224 + } + +location_data_table = { + "Entrance: Drawbridge Pit Item": LocationInformation(0x022F9110, "overlay_78"), + "Entrance: Drawbridge Switch Item": LocationInformation(0x022F911C, "overlay_78"), + "City of Haze: Church Hallway": LocationInformation(0x022F6E7C, "overlay_94"), + "Entrance: Statue Room Above Statue": LocationInformation(0x02304E64, "overlay_79"), + "City of Haze: Post-Boss Room": LocationInformation(0x02302A18, "overlay_95"), + "City of Haze: Heavy Cart Item": LocationInformation(0x022F6F3C, "overlay_94"), + "Sandy Grave: Pyramid 5F Nook": LocationInformation(0x023071AC, "overlay_91"), + "Great Stairway: After Keremet Lower": LocationInformation(0x022FAD60, "overlay_82"), + "Forgotten City: Pyramid 1F Bricks": LocationInformation(0x02308924, "overlay_102"), + "Nation of Fools: Central Top Shaft": LocationInformation(0x022F6948, "overlay_98"), + "Entrance: Above Hub": LocationInformation(0x02304A8C, "overlay_79"), + "Entrance: Double Room Upper": LocationInformation(0x02309140, "overlay_119"), + "Nation of Fools: Legion Arena": LocationInformation(0x022F6A98, "overlay_98", True), + "Forest of Doom: Post-Boss Room": LocationInformation(0x022FB1B0, "overlay_99"), + "Dark Academy: Right Building Center Room": LocationInformation(0x022F2A5C, "overlay_112"), + "The Throne Room: Above Throne Left": LocationInformation(0x022F19F8, "overlay_87"), + "Sandy Grave: Boulder Room Tunnel": LocationInformation(0x023064EC, "overlay_92"), + "City of Haze: Before Cart Room": LocationInformation(0x022F6D98, "overlay_94"), + "City of Haze: Underground Square Right": LocationInformation(0x02302B68, "overlay_95"), + "Sandy Grave: Upper Big Underground Top Right": LocationInformation(0x02306078, "overlay_92", True), + "Nation of Fools: Bottom Corner Room": LocationInformation(0x023005F4, "overlay_96", True), + "Nation of Fools: Mid-Right Corner Room": LocationInformation(0x02300438, "overlay_96", True), + "Sandy Grave: Lower Big Underground Room Bottom": LocationInformation(0x02306354, "overlay_92", True), + "Sandy Grave: Left Trap": LocationInformation(0x02306270, "overlay_92", True), + "Buried Chamber: Central Secret": LocationInformation(0x022FEA00, "overlay_80"), + "Sandy Grave: Sandworm Stomach": LocationInformation(0x02307674, "overlay_91"), + "Tower of Death: Second Gears Room Left": LocationInformation(0x02306634, "overlay_85"), + "Great Stairway: Lower Grand Staircase Corner Nook": LocationInformation(0x022FC944, "overlay_83"), + "Master's Keep: Entrance": LocationInformation(0x022FBECC, "overlay_88"), + "Forest of Doom: Left Atrium Big Room": LocationInformation(0x02302C50, "overlay_100"), + "Forgotten City: Big Shaft Room Left": LocationInformation(0x02304648, "overlay_103", True), + "Nation of Fools: Left Upper Corner Room": LocationInformation(0x02300840, "overlay_96", True), + "Dark Academy: Main Building Ghoul Room": LocationInformation(0x022F84F8, "overlay_111", True), + "13th Street: Triple Moth Room": LocationInformation(0x022FCC38, "overlay_105", True), + "Tower of Death: Secret Room": LocationInformation(0x0230673C, "overlay_85", True), + "Nation of Fools: Right Horizontal Room": LocationInformation(0x02300528, "overlay_96", True), + "Burnt Paradise: Left Lower Isolated Room": LocationInformation(0x02303A94, "overlay_107", True), + "Burnt Paradise: Right Lower Isolated Room": LocationInformation(0x02303D34, "overlay_107", True), + "The Throne Room: Great Stairs Under Stairs": LocationInformation(0x022F1B54, "overlay_87", True), + "13th Street: Behind Final Guard": LocationInformation(0x022FEE50, "overlay_106", True), + "Buried Chamber: Right Secret": LocationInformation(0x022FEC34, "overlay_80"), + "Sandy Grave: Bottom Right Room": LocationInformation(0x02306540, "overlay_92"), + "Tower of Death: Passage Hidden Item": LocationInformation(0x0230661C, "overlay_85"), + "Great Stairway: Left Loft Lower": LocationInformation(0x022F8798, "overlay_84", True), + "Entrance: Center Hidden Room": LocationInformation(0x02304F0C, "overlay_79"), + "Forgotten City: Big Shaft Room Right": LocationInformation(0x02304654, "overlay_103", True), + "Nation of Fools: Right Bottom Corner Room": LocationInformation(0x02300510, "overlay_96", True), + "Master's Keep: Hidden Room": LocationInformation(0x022FBC14, "overlay_88"), + "13th Street: Three Amalaric Snipers": LocationInformation(0x022FF0F0, "overlay_106", True), + "Dark Academy: Main Building Three Toads": LocationInformation(0x022F8060, "overlay_111"), + "Nation of Fools: Top Corner Room": LocationInformation(0x02300258, "overlay_96", True), + "Burnt Paradise: Left Upper Isolated Room": LocationInformation(0x02303908, "overlay_107", True), + "Burnt Paradise: Right Upper Isolated Room": LocationInformation(0x02303E0C, "overlay_107", True), + "The Throne Room: Great Stairs Center": LocationInformation(0x022F1B60, "overlay_87", True), + "Forgotten City: Upper Underground Square": LocationInformation(0x023048A0, "overlay_103", True), + "Entrance: Double Room Lower": LocationInformation(0x0230914C, "overlay_119"), + "Buried Chamber: Westmost Room": LocationInformation(0x022FE700, "overlay_80"), + "Sandy Grave: Pyramid East 1F": LocationInformation(0x02307500, "overlay_91"), + "City of Haze: Register Room": LocationInformation(0x02301A3C, "overlay_93"), + "City of Haze: Bar Left": LocationInformation(0x023019DC, "overlay_93"), + "Nation of Fools: Bottom-Left Room Building": LocationInformation(0x02305ACC, "overlay_97"), + "Sandy Grave: Pyramid 3F Lower": LocationInformation(0x0230711C, "overlay_91"), + "Tower of Death: Rampart Room": LocationInformation(0x022FC224, "overlay_86"), + "Entrance: Beyond Great Armor Hallway": LocationInformation(0x02305020, "overlay_79"), + "Tower of Death: Elevator Room Lower": LocationInformation(0x02306130, "overlay_85"), + "Forest of Doom: Right Atrium East Square Room Right": LocationInformation(0x022FDF10, "overlay_101"), + "Forgotten City: Outside Left": LocationInformation(0x02308864, "overlay_102"), + "Sandy Grave: Upper Big Underground Top Left": LocationInformation(0x02306084, "overlay_92"), + "Burnt Paradise: Left Upper Big Corner": LocationInformation(0x02304D04, "overlay_108"), + "Great Stairway: Upper Grand Staircase Top Left Item": LocationInformation(0x022FCB0C, "overlay_83"), + "City of Haze: Axe Courtyard": LocationInformation(0x022F6F84, "overlay_94"), + "Tower of Death: Top of the Tower": LocationInformation(0x023060DC, "overlay_85"), + "Great Stairway: Upper Underground Room": LocationInformation(0x022FAB38, "overlay_82"), + "Entrance: Drawbridge Upper Item": LocationInformation(0x022F9128, "overlay_78"), + "Entrance: Statue Room Upper": LocationInformation(0x02304E70, "overlay_79"), + "Tower of Death: Second Gears Room Right": LocationInformation(0x02306640, "overlay_85"), + "Tower of Death: Lower Gears Room Spike Shaft": LocationInformation(0x023067B4, "overlay_85"), + "Master's Keep: Right Square Room": LocationInformation(0x022FBCD4, "overlay_88"), + "City of Haze: Central Hallway Stairs": LocationInformation(0x022F6EB8, "overlay_94"), + "Buried Chamber: Right Shade Hall": LocationInformation(0x022FED6C, "overlay_80"), + "City of Haze: Right Corner Room": LocationInformation(0x02301AA8, "overlay_93"), + "City of Haze: Left Plaza": LocationInformation(0x02301DE4, "overlay_93"), + "Great Stairway: Underground Hidden Room": LocationInformation(0x022FACF4, "overlay_82"), + "Sandy Grave: Lower Big Underground Room Top": LocationInformation(0x02306348, "overlay_92"), + "Nation of Fools: Upper Upside Down": LocationInformation(0x02300378, "overlay_96"), + "Forgotten City: Lower Boulder Room Tunnel Alcove": LocationInformation(0x023049A8, "overlay_103"), + "Dark Academy: Main Building Secret Ceiling": LocationInformation(0x022F8420, "overlay_111"), + "Forest of Doom: Secret Cave Room": LocationInformation(0x022FDEB0, "overlay_101"), + "Nest of Evil: Doppelganger Reward": LocationInformation(0x022EE9FC, "overlay_113"), + "City of Haze: Center Room Lower": LocationInformation(0x02301C4C, "overlay_93"), + "Sandy Grave: On Statue": LocationInformation(0x02307068, "overlay_91", True), + "Tower of Death: Third Gears Room": LocationInformation(0x02306448, "overlay_85"), + "Entrance: Above Metal Block Room": LocationInformation(0x02304F3C, "overlay_79", True), + "Dark Academy: Main Building Entrance": LocationInformation(0x022F8660, "overlay_111", True), + "Burnt Paradise: Bottom Corner": LocationInformation(0x02303698, "overlay_107", True), + "Forest of Doom: Left Atrium Top": LocationInformation(0x023027C4, "overlay_100"), + "Great Stairway: Before Keremet Room": LocationInformation(0x022FA864, "overlay_81", True), + "Forgotten City: Lower Boulder Room Upper Alcove": LocationInformation(0x0230499C, "overlay_103", True), + "Entrance: First Passage Upper Item": LocationInformation(0x02304AEC, "overlay_79"), + "Great Stairway: Upper Grand Staircase Corner Nook": LocationInformation(0x022FCB3C, "overlay_83"), + "Burnt Paradise: Bottom Right Big Room": LocationInformation(0x02305088, "overlay_108", True), + "Entrance: Right Hidden Room": LocationInformation(0x022F92C0, "overlay_78"), + "Entrance: First Passage Lower Item": LocationInformation(0x02304AE0, "overlay_79"), + "City of Haze: Left Dance Hall Right Item": LocationInformation(0x02302784, "overlay_95"), + "City of Haze: Tunnel Room Left": LocationInformation(0x02302928, "overlay_95", True), + "Burnt Paradise: Upper Left Big Room Middle Building": LocationInformation(0x02304D88, "overlay_108", True), + "City of Haze: Chunky Room": LocationInformation(0x023018BC, "overlay_93"), + "Entrance: Pre-Behemoth Boss Room": LocationInformation(0x022F91E8, "overlay_78"), + "Great Stairway: After Keremet Upper": LocationInformation(0x022FAD6C, "overlay_82", True), + "Tower of Death: Gear Belt Room": LocationInformation(0x022FC2A8, "overlay_86"), + "City of Haze: Dance Halls Basement": LocationInformation(0x023028C8, "overlay_95"), + "City of Haze: Central Hallway Lower": LocationInformation(0x022F6EC4, "overlay_94"), + "Burnt Paradise: Upper Right Big Room Right Side": LocationInformation(0x02304ECC, "overlay_108", True), + "Buried Chamber: Central Hall": LocationInformation(0x022FE94C, "overlay_80"), + "Great Stairway: Central Nook": LocationInformation(0x022F881C, "overlay_84", True), + "Sandy Grave: Eastmost Item": LocationInformation(0x02307608, "overlay_91", True), + "Sandy Grave: Behind Bricks": LocationInformation(0x02307074, "overlay_91"), + "City of Haze: Underground Square Left": LocationInformation(0x02302C10, "overlay_95"), + "Great Stairway: Upper Grand Staircase Upper Alcove": LocationInformation(0x022FCB30, "overlay_83"), + "City of Haze: Right Dance Hall Secret Room": LocationInformation(0x023029E8, "overlay_95", True), + "Nation of Fools: Bottom Left Medium Square On Wall": LocationInformation(0x02305B2C, "overlay_97"), + "City of Haze: First Big Room": LocationInformation(0x023016C4, "overlay_93"), + "Buried Chamber: Final Shaft Item": LocationInformation(0x022FEE5C, "overlay_80"), + "Great Stairway: Central Upper": LocationInformation(0x022F8828, "overlay_84", True), + "Forest of Doom: Right Atrium East Top Room": LocationInformation(0x022FDDF0, "overlay_101"), + "Dark Academy: Right Building Top Floor": LocationInformation(0x022F29E4, "overlay_112", True), + "Sandy Grave: Boulder Room Corner Alcove": LocationInformation(0x023064F8, "overlay_92"), + "Forgotten City: Post-Boss Item": LocationInformation(0x023046B4, "overlay_103"), + "City of Haze: Cart Secret Left": LocationInformation(0x022F7008, "overlay_94"), + "City of Haze: Winding Rooms": LocationInformation(0x023017C0, "overlay_93"), + "Great Stairway: Lower Grand Staircase Lower Alcove": LocationInformation(0x022FC950, "overlay_83", True), + "Forest of Doom: Right Atrium East Square Room Left": LocationInformation(0x022FDF1C, "overlay_101"), + "Forgotten City: Bottom Chamber": LocationInformation(0x02304D50, "overlay_103"), + "City of Haze: Meat Room": LocationInformation(0x02301628, "overlay_93"), + "Great Stairway: Big Underground Room Hidden Item": LocationInformation(0x022FAE38, "overlay_82"), + "Great Stairway: Right Loft": LocationInformation(0x022F893C, "overlay_84", True), + "Great Stairway: Left Loft": LocationInformation(0x022F878C, "overlay_84", True), + "Nation of Fools: Bottom-Right Room Left Building": LocationInformation(0x023059A0, "overlay_97"), + "13th Street: Many Nyxes Room": LocationInformation(0x022FEF7C, "overlay_106", True), + "The Throne Room: Above Throne Right": LocationInformation(0x022F1A04, "overlay_87", True), + "City of Haze: Tunnel Room Right": LocationInformation(0x02302934, "overlay_95", True), + "Great Stairway: Lower Grand Staircase Upper Alcove": LocationInformation(0x022FC968, "overlay_83"), + "Great Stairway: Underground East Room": LocationInformation(0x022FACA0, "overlay_82", True), + "Tower of Death: Elevator Room Top": LocationInformation(0x02306118, "overlay_85"), + "Entrance: Hub Room": LocationInformation(0x02304AB0, "overlay_79", True), + "Buried Chamber: Eastmost Room": LocationInformation(0x022FEE38, "overlay_80"), + "Tower of Death: Hidden Room": LocationInformation(0x022FC368, "overlay_86"), + "Nation of Fools: Bottom-Right Room Middle Building": LocationInformation(0x02305994, "overlay_97", True), + "Forgotten City: Pyramid 5F": LocationInformation(0x02308A2C, "overlay_102", True), + "13th Street: Train Room Secret Right": LocationInformation(0x022F3B8C, "overlay_104"), + "City of Haze: Left Dance Hall Left Item": LocationInformation(0x02302778, "overlay_95"), + "Great Stairway: Big Underground Room Lower": LocationInformation(0x022FAE20, "overlay_82"), + "Tower of Death: Elevator Room Middle": LocationInformation(0x02306124, "overlay_85"), + "Nation of Fools: Crevice Item": LocationInformation(0x02305778, "overlay_97", True), + "City of Haze: First Passage": LocationInformation(0x02301598, "overlay_93"), + "Forgotten City: Gorgon Room": LocationInformation(0x02304858, "overlay_103", True), + "City of Haze: Axe Armor Item": LocationInformation(0x02301AD8, "overlay_93"), + "Sandy Grave: Giant Shaft Upper Item": LocationInformation(0x02305DFC, "overlay_92"), + "City of Haze: Dance Halls Vertical Hallway": LocationInformation(0x0230288C, "overlay_95"), + "Tower of Death: Above Motorcycles": LocationInformation(0x022FC440, "overlay_86", True), + "Nation of Fools: Top-Right Room Axe Item": LocationInformation(0x02305808, "overlay_97", True), + "Dark Academy: Main Building Secret Malphas Room": LocationInformation(0x022F83B4, "overlay_111", True), + "13th Street: Underground Secret Room": LocationInformation(0x022FF2AC, "overlay_106", True), + "Great Stairway: Big Underground Room Upper": LocationInformation(0x022FAE2C, "overlay_82"), + "Forest of Doom: Right Atrium West Top Room": LocationInformation(0x022FDB44, "overlay_101"), + "Burnt Paradise: Upper Left Big Room Left Building": LocationInformation(0x02304D94, "overlay_108", True), + "13th Street: Underground Demon Room": LocationInformation(0x022FF264, "overlay_106", True), + "Sandy Grave: Mega Ghost Room Lower": LocationInformation(0x02307434, "overlay_91", True), + "13th Street: Left Big Room": LocationInformation(0x022FD058, "overlay_105", True), + "City of Haze: Bread Shelf": LocationInformation(0x02301D78, "overlay_93", True), + "Burnt Paradise: Upper Left Lower Corner Upright Building": LocationInformation(0x02304E0C, "overlay_108", True), + "City of Haze: Underground Secret Room": LocationInformation(0x02302B50, "overlay_95", True), + "Sandy Grave: Mega Ghost Room Upper": LocationInformation(0x0230741C, "overlay_91"), + "Burnt Paradise: Lower Left Big Room": LocationInformation(0x02304C38, "overlay_108", True), + "13th Street: Secret Floor Room": LocationInformation(0x022FF024, "overlay_106"), + "City of Haze: Right Dance Hall Item": LocationInformation(0x02302964, "overlay_95"), + "Tower of Death: Top Gears Room": LocationInformation(0x02306298, "overlay_85"), + "13th Street: East Courtyard": LocationInformation(0x022FCA04, "overlay_105", True), + "City of Haze: Corner Room": LocationInformation(0x0230176C, "overlay_93"), + "Sandy Grave: Giant Shaft Middle Item": LocationInformation(0x02305DE4, "overlay_92", True), + "Entrance: Lilith Room": LocationInformation(0x02304E28, "overlay_79"), + "Sandy Grave: Right Trap": LocationInformation(0x0230627C, "overlay_92", True), + "Nation of Fools: Left Bottom Corner Room": LocationInformation(0x02300750, "overlay_96", True), + "Forgotten City: Lower Underground Square Upper Item": LocationInformation(0x02304A80, "overlay_103"), + "City of Haze: Center Room Top": LocationInformation(0x02301C40, "overlay_93"), + "Great Stairway: Upper Grand Staircase Middle Alcove": LocationInformation(0x022FCB24, "overlay_83", True), + "Buried Chamber: Right Shaft Top": LocationInformation(0x022FEDA8, "overlay_80", True), + "Sandy Grave: Pyramid 3F Upper": LocationInformation(0x02307128, "overlay_91"), + "Nation of Fools: Bottom Right Medium Square On Wall": LocationInformation(0x023058F8, "overlay_97", True), + "Sandy Grave: Lonely Mimic Alcove": LocationInformation(0x023064A4, "overlay_92", True), + "13th Street: Near Big Room": LocationInformation(0x022FCE18, "overlay_105", True), + "Forgotten City: Pyramid East 3F": LocationInformation(0x02308C9C, "overlay_102", True), + "Great Stairway: Upper Grand Staircase Lower Alcove": LocationInformation(0x022FCB18, "overlay_83", True), + "Nation of Fools: Right Vertical Room": LocationInformation(0x02300450, "overlay_96", True), + "Sandy Grave: Fleaman Pit": LocationInformation(0x023073A4, "overlay_91"), + "Nation of Fools: Top-Right Room Bottom Item": LocationInformation(0x023057F0, "overlay_97"), + "Forgotten City: Pyramid East 5F": LocationInformation(0x02308BC4, "overlay_102", True), + "Burnt Paradise: Lower Vertical Hall": LocationInformation(0x02304B60, "overlay_108", True), + "Buried Chamber: West Shaft": LocationInformation(0x022FE748, "overlay_80", True), + "Great Stairway: Lower Grand Staircase Middle Alcove": LocationInformation(0x022FC95C, "overlay_83", True), + "Entrance: Behemoth Chase Room": LocationInformation(0x02304DA4, "overlay_79"), + "Entrance: First Passage Hidden Wall": LocationInformation(0x02304B34, "overlay_79"), + "Tower of Death: Lower Passage Hidden Item": LocationInformation(0x023068BC, "overlay_85"), + "Dark Academy: West Building Breakable Wall": LocationInformation(0x022F6084, "overlay_109"), + "Forgotten City: Armored Fleaman Pit": LocationInformation(0x02308C24, "overlay_102", True), + "Nation of Fools: Top-Left Room Right Building": LocationInformation(0x02305BBC, "overlay_97", True), + "Forest of Doom: Left Atrium Breakable Wall": LocationInformation(0x02302A40, "overlay_100"), + "Sandy Grave: Giant Shaft Low Item": LocationInformation(0x02305DF0, "overlay_92", True), + "The Throne Room: Great Stairs Hidden": LocationInformation(0x022F1B78, "overlay_87"), + "Nation of Fools: Top-Right Room Breakable Wall": LocationInformation(0x023057FC, "overlay_97"), + "Burnt Paradise: Bottom Right Breakable Wall": LocationInformation(0x02303848, "overlay_107"), + "Burnt Paradise: Right Upper Big Corner On Wall": LocationInformation(0x02304F2C, "overlay_108", True), + "Nation of Fools: Right Center Room": LocationInformation(0x022F6A5C, "overlay_98", True), + "Burnt Paradise: Left Big Corner": LocationInformation(0x02304B90, "overlay_108"), + "Buried Chamber: Hidden Wall Item": LocationInformation(0x022FECA0, "overlay_80"), + "13th Street: Bakery Middle": LocationInformation(0x022FCB54, "overlay_105"), + "City of Haze: Cart Secret Right": LocationInformation(0x022F7014, "overlay_94"), + "13th Street: Right Dance Hall Top Right": LocationInformation(0x022FED84, "overlay_106", True), + "13th Street: Bakery Left": LocationInformation(0x022FCB48, "overlay_105"), + "13th Street: Train Room Secret Left": LocationInformation(0x022F3B80, "overlay_104"), + "City of Haze: Bar Right": LocationInformation(0x023019E8, "overlay_93"), + "13th Street: Square Room": LocationInformation(0x022FCA4C, "overlay_105", True), + "13th Street: Bakery Right": LocationInformation(0x022FCB60, "overlay_105"), + "Burnt Paradise: Bottom Right Corner Room": LocationInformation(0x023050A0, "overlay_108", True), + "Nation of Fools: Left Center Room": LocationInformation(0x022F6A74, "overlay_98", True), + "Dark Academy: Main Building Square Room Breakable Wall": LocationInformation(0x022F8198, "overlay_111"), + "Nation of Fools: Upside Down Building": LocationInformation(0x02305C64, "overlay_97", True), + "Dark Academy: Main Building Minotaur Room": LocationInformation(0x022F8564, "overlay_111", True), + "Dark Academy: West Building Top Floor Right": LocationInformation(0x022F60F0, "overlay_109", True), + "Nation of Fools: Secret Room": LocationInformation(0x023003D8, "overlay_96", True), + "Forest of Doom: Left Atrium Middle Room": LocationInformation(0x023028D8, "overlay_100", True), + "Burnt Paradise: Upper Right Big Room": LocationInformation(0x02304ED8, "overlay_108", True), + "Forest of Doom: Near Cave Save Breakable Wall": LocationInformation(0x022FB120, "overlay_99"), + "Sandy Grave: Pyramid East 5F": LocationInformation(0x02307344, "overlay_91"), + "Nest of Evil: First Item": LocationInformation(0x022EE060, "overlay_113", True), + "Nest of Evil: Second Item": LocationInformation(0x022EE864, "overlay_113", True), + "Forest of Doom: Left Atrium Upper Room": LocationInformation(0x0230280C, "overlay_100"), + "Great Stairway: Connector Pipe Left": LocationInformation(0x022F8A80, "overlay_84", True), + "Great Stairway: Connector Pipe Right": LocationInformation(0x022F8A8C, "overlay_84", True), + "The Throne Room: Great Stairs Left": LocationInformation(0x022F1B6C, "overlay_87", True), + "13th Street: Right Dance Hall Top Left": LocationInformation(0x022FED78, "overlay_106", True), + "Dark Academy: West Building Top Floor Left": LocationInformation(0x022F60E4, "overlay_109", True), + "Forgotten City: Pyramid East 1F": LocationInformation(0x02308CF0, "overlay_102", True), + "Forgotten City: Lower Underground Square Lower Item": LocationInformation(0x02304A8C, "overlay_103", True), + "Tower of Death: Stella Item": LocationInformation(0x0230916B, "overlay_119", False, "Cutscene"), + "Master's Keep: Whip Trial": LocationInformation(0x02309168, "overlay_119", False, "Cutscene"), + + "Quest: Preparations": LocationInformation(0x020DFD40, "arm9", False, "Quest") +}