├── data ├── base_pmr_patch.bsdiff4 ├── regions │ ├── minigames.json │ ├── inside_the_whale.json │ ├── bowser's_castle_boss_rush.json │ ├── dry_dry_outpost.json │ ├── shooting_star_summit_no_star_way.json │ ├── gusty_gulch.json │ ├── peachs_castle.json │ ├── forever_forest.json │ ├── boos_mansion.json │ ├── shooting_star_summit.json │ ├── mt_rugged.json │ ├── tubbas_castle.json │ ├── shiver_region.json │ ├── koopa_bros_fortress.json │ └── goomba_village.json ├── enum_ingame.py ├── battles.py ├── enum_types.py ├── starting_maps.py ├── MysteryOptions.py ├── PaletteOptions.py ├── partners_meta.py ├── puzzle_data.py ├── EntranceList.py ├── node.py ├── itemlocation_replenish.py ├── enum_options.py ├── item_exclusion.py ├── LogicHelpers.json ├── item_scores.py └── palettes_meta.py ├── test └── __init__.py ├── Utils.py ├── Regions.py ├── modules ├── random_map_mirroring.py ├── random_partners.py ├── modify_entrances.py ├── random_stat_distribution.py ├── random_battles.py ├── random_audio.py ├── random_actor_stats.py ├── random_mystery.py ├── random_quizzes.py ├── random_shop_prices.py ├── random_movecosts.py ├── modify_itempool.py └── random_palettes.py ├── itemhints.py ├── LICENSE ├── docs ├── PMR Settings String.yaml ├── Roadmap.md ├── en_Paper Mario.md ├── setup_en.md └── Resources and FAQ.md ├── Entrance.py ├── items.py ├── calculate_crc.py ├── Rules.py ├── README.md ├── Locations.py ├── GlitchOptions.py └── client.py /data/base_pmr_patch.bsdiff4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JKBSunshine/PMR_APWorld/HEAD/data/base_pmr_patch.bsdiff4 -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | from test.bases import WorldTestBase 2 | 3 | 4 | class PaperMarioTestBase(WorldTestBase): 5 | game = "Paper Mario" -------------------------------------------------------------------------------- /Utils.py: -------------------------------------------------------------------------------- 1 | import orjson 2 | import pkgutil 3 | from typing import Dict, Any, Union, List 4 | 5 | 6 | def load_json_data(data_name: str) -> Union[List[Any], Dict[str, Any]]: 7 | return orjson.loads(pkgutil.get_data(__name__, "data/" + data_name).decode("utf-8-sig")) 8 | -------------------------------------------------------------------------------- /data/regions/minigames.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "MGM Playroom Lobby", 4 | "area_id": "25", 5 | "map_id": "0", 6 | "map_name": "Playroom Lobby", 7 | "exits": { 8 | "TT Station District Pipe": "True" 9 | } 10 | } 11 | ] 12 | 13 | -------------------------------------------------------------------------------- /data/enum_ingame.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/rando_enums/enum_ingame.py 2 | 3 | from enum import IntEnum, unique 4 | 5 | 6 | @unique 7 | class StarSpirits(IntEnum): 8 | ELDSTAR = 1 9 | MAMAR = 2 10 | SKOLAR = 3 11 | MUSKULAR = 4 12 | MISSTAR = 5 13 | KLEVAR = 6 14 | KALMAR = 7 15 | -------------------------------------------------------------------------------- /data/battles.py: -------------------------------------------------------------------------------- 1 | # name, vanilla battle id 2 | 3 | boss_battles = { 4 | "KoopaBros": 1792, 5 | "Tutankoopa": 3072, 6 | "TubbasHeart": 3599, 7 | "GeneralGuy": 4352, 8 | "LavaPiranha": 5888, 9 | "HuffnPuff": 6400, 10 | "CrystalKing": 8192, 11 | } 12 | 13 | 14 | def get_battle_key(name): 15 | return 0xA9 << 24 | boss_battles[name] 16 | -------------------------------------------------------------------------------- /Regions.py: -------------------------------------------------------------------------------- 1 | from BaseClasses import Region, MultiWorld 2 | 3 | 4 | class PMRegion(Region): 5 | game: str = "Paper Mario" 6 | 7 | def __init__(self, name: str, player: int, multiworld: MultiWorld): 8 | super(PMRegion, self).__init__(name, player, multiworld) 9 | self.map_name = None 10 | self.map_id = None 11 | self.area_id = None 12 | -------------------------------------------------------------------------------- /data/enum_types.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/rando_enums/enum_types.py 2 | from enum import IntEnum, unique 3 | 4 | 5 | @unique 6 | class SongType(IntEnum): 7 | BATTLE = 0 8 | BOSS = 1 9 | EVENT = 2 10 | FIELDANDTOWN = 3 11 | JINGLE = 4 12 | 13 | 14 | @unique 15 | class SongMood(IntEnum): 16 | RELAXED = 0 17 | UPBEAT = 1 18 | SINISTER = 2 19 | CRISIS = 3 20 | BATTLE = 4 21 | BOSS = 5 22 | -------------------------------------------------------------------------------- /data/starting_maps.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/metadata/starting_maps.py 2 | from ..options import StartingMap 3 | starting_maps = { 4 | StartingMap.option_Toad_Town: (0x00010104, "TT Gate District Mario's House Pipe"), # Toad Town 5 | StartingMap.option_Goomba_Village: (0x00000101, "GR Goomba Village"), # Goomba Village 6 | StartingMap.option_Dry_Dry_Outpost: (0x00090100, "DDO Outpost 2"), # Dry Dry Outpost 7 | StartingMap.option_Yoshi_Village: (0x00110302, "JJ Village Buildings"), # Yoshi Village 8 | } -------------------------------------------------------------------------------- /data/regions/inside_the_whale.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "ITW Whale Mouth", 4 | "area_id": "3", 5 | "map_id": "0", 6 | "map_name": "Whale Mouth", 7 | "exits": { 8 | "TT Port District": "True", 9 | "ITW Whale Stomach": "True" 10 | } 11 | }, 12 | { 13 | "region_name": "ITW Whale Stomach", 14 | "area_id": "3", 15 | "map_id": "1", 16 | "map_name": "Whale Stomach", 17 | "events": { 18 | "RF_CanRideWhale": "can_use_ability_watt" 19 | }, 20 | "exits": { 21 | "ITW Whale Mouth": "True", 22 | "TT Port District": "'RF_CanRideWhale'" 23 | } 24 | } 25 | ] 26 | -------------------------------------------------------------------------------- /modules/random_map_mirroring.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/rando_modules/random_map_mirroring.py 2 | # NYI by JKB 3 | """ 4 | This module is used to set up visual mirroring of maps in a static way. 5 | Specific Maps can be chosen to always be mirrored, always be not mirrored, or 6 | always be 50/50 within this module. 7 | For maps which are not set here, the chosen value for the "Mirror Mode" setting 8 | is used as a fallback, so either "none mirrored", "all mirrored" or 9 | "50/50 chance to mirror". 10 | """ 11 | 12 | 13 | def get_mirrored_map_list() -> list: 14 | map_list = [] 15 | 16 | # removed code for now 17 | 18 | return map_list 19 | -------------------------------------------------------------------------------- /data/MysteryOptions.py: -------------------------------------------------------------------------------- 1 | # from PMR: https://github.com/icebound777/PMR-SeedGenerator/blob/main/models/options/MysteryOptionSet.py 2 | from .ItemList import item_table 3 | 4 | 5 | # original items are, in order: mushroom, super shroom, fire flower, stone cap, dizzy dial, thunder rage, pebble 6 | class MysteryOptions: 7 | def __init__(self): 8 | self.mystery_random_choice = 0 9 | self.mystery_random_pick = False 10 | self.mystery_itemA = 138 11 | self.mystery_itemB = 140 12 | self.mystery_itemC = 128 13 | self.mystery_itemD = 136 14 | self.mystery_itemE = 154 15 | self.mystery_itemF = 130 16 | self.mystery_itemG = 133 17 | -------------------------------------------------------------------------------- /modules/random_partners.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/rando_modules/random_partners.py 2 | 3 | """This module handles choosing random partners to start the seed with.""" 4 | from ..data.partners_meta import all_partners as all_partners_imp 5 | 6 | 7 | def get_rnd_starting_partners(partners: int, random) -> list: 8 | """ 9 | Returns a list of randomly chosen partners. 10 | """ 11 | starting_partners = [] 12 | all_partners = all_partners_imp.copy() 13 | 14 | while len(starting_partners) < partners: 15 | new_partner = random.choice(all_partners) 16 | all_partners.remove(new_partner) 17 | starting_partners.append(new_partner) 18 | 19 | return starting_partners 20 | -------------------------------------------------------------------------------- /itemhints.py: -------------------------------------------------------------------------------- 1 | """This module handles creation of item hints for Merluvlee to offer Mario.""" 2 | from .options import ShuffleKootFavors, ShuffleLetters, PaperMarioOptions 3 | 4 | from .data.itemlocation_special \ 5 | import kootfavors_reward_locations,\ 6 | kootfavors_keyitem_locations,\ 7 | chainletter_giver_locations,\ 8 | chainletter_final_reward_location,\ 9 | simpleletter_locations,\ 10 | limited_by_item_areas 11 | from .data.partners_meta import all_partners 12 | 13 | 14 | def get_itemhints( 15 | allow_itemhints: bool, 16 | placed_items: list, 17 | options: PaperMarioOptions, 18 | ): 19 | """ 20 | Returns a list of item hint lists for a given list of item nodes. 21 | Each item hint list contains the item id and a reference word describing 22 | the area, map and item source type. 23 | """ 24 | return [[0x00000000, 0xFFFFFFFF]] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) AP PMR Dev Team and Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /modules/modify_entrances.py: -------------------------------------------------------------------------------- 1 | from ..data.EntranceList import (bowser_shortened_entrances_add, bowser_shortened_entrances_rmv, 2 | bowser_boss_rush_entrances_add, bowser_boss_rush_entrances_rmv) 3 | 4 | 5 | def get_entrance_pair(old_data, new_data) -> (int, int): 6 | old_area, old_map, old_exit = old_data 7 | 8 | new_area, new_map, new_exit = new_data 9 | 10 | key = ((0xA3 << 24) | (old_area << 16) | (old_map << 8) | old_exit) 11 | value = ((new_area << 16) | (new_map << 8) | new_exit) 12 | return key, value 13 | 14 | 15 | def get_entrance_pairs(entrances_to_remove: list, entrances_to_add: list): 16 | entrance_list = [] 17 | 18 | for i in range(0, len(entrances_to_remove)): 19 | entrance_list.append(get_entrance_pair(entrances_to_remove[i], entrances_to_add[i])) 20 | 21 | return entrance_list 22 | 23 | 24 | def get_bowser_rush_pairs(): 25 | return get_entrance_pairs(bowser_boss_rush_entrances_rmv, bowser_boss_rush_entrances_add) 26 | 27 | 28 | def get_bowser_shortened_pairs(): 29 | return get_entrance_pairs(bowser_shortened_entrances_rmv, bowser_shortened_entrances_add) 30 | -------------------------------------------------------------------------------- /modules/random_stat_distribution.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/rando_modules/random_stat_distribution.py 2 | 3 | def generate_random_stats(level: int, random) -> tuple[int, int, int]: 4 | level = min(level, 27) # clamp to max level for sanity 5 | 6 | starting_hp = 5 7 | starting_fp = 5 8 | starting_bp = 3 9 | while level > 0: 10 | level -= 1 11 | stat = random.randint(0, 2) 12 | while True: 13 | # rollover if a stat is already maxed 14 | if stat == 0: 15 | if starting_hp >= 50: 16 | stat += 1 17 | else: 18 | break 19 | if stat == 1: 20 | if starting_fp >= 50: 21 | stat += 1 22 | else: 23 | break 24 | if stat == 2: 25 | if starting_bp >= 30: 26 | stat = 0 27 | else: 28 | break 29 | 30 | # apply stat 31 | if stat == 0: 32 | starting_hp += 5 33 | elif stat == 1: 34 | starting_fp += 5 35 | else: 36 | starting_bp += 3 37 | return starting_hp, starting_fp, starting_bp 38 | -------------------------------------------------------------------------------- /data/PaletteOptions.py: -------------------------------------------------------------------------------- 1 | # from PMR: https://github.com/icebound777/PMR-SeedGenerator/blob/main/models/options/PaletteOptionSet.py 2 | class PaletteOptions: 3 | def __init__(self): 4 | default_palette = 0 5 | 6 | self.mario_setting = default_palette 7 | self.mario_sprite = default_palette 8 | self.goombario_setting = default_palette 9 | self.goombario_sprite = default_palette 10 | self.kooper_setting = default_palette 11 | self.kooper_sprite = default_palette 12 | self.bombette_setting = default_palette 13 | self.bombette_sprite = default_palette 14 | self.parakarry_setting = default_palette 15 | self.parakarry_sprite = default_palette 16 | self.bow_setting = default_palette 17 | self.bow_sprite = default_palette 18 | self.watt_setting = default_palette 19 | self.watt_sprite = default_palette 20 | self.sushie_setting = default_palette 21 | self.sushie_sprite = default_palette 22 | self.lakilester_setting = default_palette 23 | self.lakilester_sprite = default_palette 24 | 25 | self.bosses_setting = default_palette 26 | self.enemies_setting = default_palette 27 | self.npc_setting = default_palette 28 | self.hammer_setting = default_palette 29 | -------------------------------------------------------------------------------- /data/partners_meta.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/metadata/partners_meta.py 2 | """This meta file houses misc. data about partners.""" 3 | 4 | 5 | all_partners = [ 6 | "Goombario", 7 | "Kooper", 8 | "Bombette", 9 | "Parakarry", 10 | "Bow", 11 | "Watt", 12 | "Sushie", 13 | "Lakilester", 14 | ] 15 | 16 | partner_moves = { 17 | "Goombario": [ 18 | "Headbonk", 19 | "Tattle", 20 | "Charge", 21 | "Multibonk", 22 | ], 23 | "Kooper": [ 24 | "ShellToss", 25 | "PowerShell", 26 | "DizzyShell", 27 | "FireShell", 28 | ], 29 | "Bombette": [ 30 | "BodySlam", 31 | "Bomb", 32 | "PowerBomb", 33 | "MegaBomb", 34 | ], 35 | "Parakarry": [ 36 | "SkyDive", 37 | "ShellShot", 38 | "AirLift", 39 | "AirRaid", 40 | ], 41 | "Bow": [ 42 | "Smack", 43 | "OuttaSight", 44 | "Spook", 45 | "FanSmack", 46 | ], 47 | "Watt": [ 48 | "ElectroShock", 49 | "PowerShock", 50 | "TurboCharge", 51 | "MegaShock", 52 | ], 53 | "Sushie": [ 54 | "BellyFlop", 55 | "Squirt", 56 | "WaterBlock", 57 | "TidalWave", 58 | ], 59 | "Lakilester": [ 60 | "SpinyFlip", 61 | "SpinySurge", 62 | "CloudNine", 63 | "Hurricane", 64 | ], 65 | } 66 | -------------------------------------------------------------------------------- /docs/PMR Settings String.yaml: -------------------------------------------------------------------------------- 1 | Paper Mario: 2 | progression_balancing: 50 3 | accessibility: items 4 | 5 | # AP World Version: 0.6.4 6 | 7 | # To get the settings string, go to https://pm64randomizer.com/ 8 | # 1. Choose your settings 9 | # 2. At the top of the page, click Export 10 | # 3. Copy the text in the "Settings string" field 11 | # 4. Paste it below where it says SettingString 12 | 13 | # Unavailable options: 14 | # Shuffle dungeon entrances (must be turned off) 15 | # Shuffle bosses (must be turned off) 16 | # Mirror mode (cannot be static random) 17 | pmr_settings_string: SettingsString 18 | 19 | # The below option(s) are AP only and do not exist on the PMR site. 20 | 21 | local_consumables: 100 22 | # The given percentage of consumables will be forced into your world. This can be helpful for ensuring you don't 23 | # have to rely on off-world consumables too terribly much. If you receive a lot of consumables at once, you may need 24 | # to swing by a shop to store or sell items. 25 | 26 | blooper_damage_requirements: None 27 | # Other options: Low, Medium, High 28 | # This can add logic to blooper fights to ensure you're well-equipped enough with boots and partners to beat them. 29 | 30 | death_link: False 31 | # death link is not yet implemented, leave this False 32 | 33 | description: Paper Mario YAML using PMR Setting String 34 | game: Paper Mario 35 | name: PMR -------------------------------------------------------------------------------- /data/puzzle_data.py: -------------------------------------------------------------------------------- 1 | puzzle_data = { 2 | # Name index default value 3 | "TunnelsPushBlock": (0, 20), 4 | "AttackFXBBlocks": (1, 291), 5 | "KoopaVillagePushBlocks": (2, 0), 6 | "RuinsStones": (3, 66051), 7 | "FuzzyTreesRound1": (4, 11), 8 | "FuzzyTreesRound2": (5, 10), 9 | "FuzzyTreesRound3": (6, 9), 10 | "ShopCodePulseStone": (7, 9240710), 11 | "ShopCodeRedJar1": (8, 8781991), 12 | "ShopCodeRedJar2": (9, 8781965), 13 | "BooRingOBK04": (10, 8), 14 | "BooRingOBK08Degrees": (11, 360), 15 | "BooRingOBK08Delay": (12, 370), 16 | "GreenStationBoxes": (13, 8500), 17 | "DeepJunglePushBlocks1": (14, 117771529), 18 | "DeepJunglePushBlocks2": (15, 268768514), 19 | "DeepJunglePushBlocks3": (16, 369367558), 20 | "DeepJunglePushBlocks4": (17, 6919), 21 | "LavaDamPushBlocks": (18, 4219008), 22 | "UltraHammerPushBlocks": (19, 33204), 23 | "FlowerFieldsThreeTrees": (20, 561), 24 | "FlowerFieldsElevators": (21, 0), 25 | "SAMKooperDuplighost": (22, 0), 26 | "BombetteDuplighosts": (23, 74500), 27 | "PRAKooperDuplighosts": (24, 147715), 28 | "AlbinoDinoPositions": (25, 2110033), 29 | "BowsersCastleMaze": (26, 37), 30 | } 31 | -------------------------------------------------------------------------------- /docs/Roadmap.md: -------------------------------------------------------------------------------- 1 | This is not necessarily a strict roadmap, nor is it exhaustive of everything that there is to do. 2 | 3 | ### Necessary for Alpha 4 | - ~~Output of a patch file that applies the desired settings, randomized items, locations, etc.~~ 5 | - ~~Set up a way to communicate with an AP server for sending/receiving items.~~ 6 | 7 | ### Pre-Early Alpha 8 | - ~~Logic helpers to account for access rules that change depending on settings~~ 9 | - ~~Logic helper to determine when to grant Parakarry check~~ 10 | - ~~Logic for keysanity option: false~~ 11 | - ~~Logic for gear shuffle options: vanilla and gear_locations~~ 12 | 13 | ### Early-Mid Alpha 14 | - ~~Super blocks and multi-coin blocks as potential locations, as well as being able to shuffle them~~ 15 | - ~~Bowser Castle Mode (change entrances, remove locations)~~ 16 | - ~~Power Star Hunt (logic, items, remove locations in ch8 if skipped)~~ 17 | - Entrance Randomizer 18 | - ~~Require Specific Spirits~~ 19 | - ~~Limit Chapter Logic~~ 20 | - ~~Open World settings~~ 21 | 22 | ### Mid-Late Alpha 23 | - ~~Enemy difficulty~~ 24 | - ~~Consumable item pool randomization~~ 25 | - ~~Item traps~~ 26 | - ~~Start with random items~~ 27 | - ~~Random puzzles~~ 28 | - ~~Mystery shuffle~~ 29 | - ~~Formation shuffle~~ 30 | 31 | 32 | ### Late Alpha - Early Beta 33 | - ~~Prevent Chapter 8 locations from holding too many progression items (OoT does similar with Ganon's Castle)~~ 34 | - ~~Create yaml file using PMR settings string~~ 35 | - ~~Offworld consumables~~ 36 | - ~~Item get animations~~ 37 | - Offworld item sprite 38 | - ~~Offworld item names and descriptions~~ 39 | -------------------------------------------------------------------------------- /Entrance.py: -------------------------------------------------------------------------------- 1 | from BaseClasses import Entrance 2 | 3 | 4 | class PMEntrance(Entrance): 5 | game: str = 'Paper Mario' 6 | 7 | def __init__(self, player, world, name='', parent=None): 8 | super(PMEntrance, self).__init__(player, name, parent) 9 | self.multiworld = world 10 | self.access_rules = [] 11 | self.reverse = None 12 | self.replaces = None 13 | self.assumed = None 14 | self.type = None 15 | self.shuffled = False 16 | self.data = None 17 | self.primary = False 18 | self.always = False 19 | self.never = False 20 | 21 | def bind_two_way(self, other_entrance): 22 | self.reverse = other_entrance 23 | other_entrance.reverse = self 24 | 25 | def disconnect(self): 26 | self.connected_region.entrances.remove(self) 27 | previously_connected = self.connected_region 28 | self.connected_region = None 29 | return previously_connected 30 | 31 | def get_new_target(self, pool_type): 32 | root = self.multiworld.get_region('Root Exits', self.player) 33 | target_entrance = PMEntrance(self.player, self.multiworld, f'Root -> ({self.name}) ({pool_type})', root) 34 | target_entrance.connect(self.connected_region) 35 | target_entrance.replaces = self 36 | root.exits.append(target_entrance) 37 | return target_entrance 38 | 39 | def assume_reachable(self, pool_type): 40 | if self.assumed == None: 41 | self.assumed = self.get_new_target(pool_type) 42 | self.disconnect() 43 | return self.assumed 44 | -------------------------------------------------------------------------------- /data/regions/bowser's_castle_boss_rush.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "BC Fake Peach Hallway", 4 | "area_id": "22", 5 | "map_id": "20", 6 | "map_name": "Fake Peach Hallway", 7 | "exits": { 8 | "SSS Riding Star Ship Scene": "True", 9 | "BC Guard Door 3": "(Boots or Hammer or can_use_ability_kooper or can_use_ability_bombette)" 10 | } 11 | }, 12 | { 13 | "region_name": "BC Guard Door 3", 14 | "area_id": "22", 15 | "map_id": "28", 16 | "map_name": "Guard Door 3", 17 | "exits": { 18 | "BC Fake Peach Hallway": "True", 19 | "BC Exit to Peach's Castle": "can_reach_star_way" 20 | } 21 | }, 22 | { 23 | "region_name": "BC Exit to Peach's Castle", 24 | "area_id": "22", 25 | "map_id": "46", 26 | "map_name": "Exit to Peach's Castle", 27 | "exits": { 28 | "BC Guard Door 3": "True", 29 | "PCG Hijacked Castle Entrance": "True" 30 | } 31 | }, 32 | { 33 | "region_name": "BC Ship Enter/Exit Scenes", 34 | "area_id": "22", 35 | "map_id": "21", 36 | "map_name": "Ship Enter/Exit Scenes", 37 | "exits": { 38 | "SSS Riding Star Ship Scene": "True", 39 | "BC Fake Peach Hallway": "True" 40 | } 41 | }, 42 | { 43 | "region_name": "PCG Hijacked Castle Entrance", 44 | "area_id": "23", 45 | "map_id": "2", 46 | "map_name": "Hijacked Castle Entrance", 47 | "locations": { 48 | "PCG Hijacked Castle Entrance Hidden Block": "can_see_hidden_blocks and can_hit_floating_blocks" 49 | }, 50 | "exits": { 51 | "BC Exit to Peach's Castle": "True", 52 | "PC Entry Hall (1F)": "True" 53 | } 54 | } 55 | ] 56 | -------------------------------------------------------------------------------- /docs/en_Paper Mario.md: -------------------------------------------------------------------------------- 1 | # Paper Mario 2 | 3 | ## What does randomization do to this game? 4 | 5 | Items which the player would normally acquire throughout the game have been moved around. Logic remains, so the game is 6 | always able to be completed, but because of the item shuffle the player may need to access certain areas before they 7 | would in the vanilla game. You will also be given a Homeward Shroom as a key item which you can use at any time to 8 | return to your spawn location. 9 | 10 | ## What items and locations get shuffled? 11 | 12 | Shuffled items can include those received from NPCs or found in chests, blocks, trees, bushes, or free-standing in the 13 | overworld. Optionally, you can shuffle partners, gear, star pieces from hidden panels, letter rewards, 14 | partner upgrades, and more. 15 | 16 | ## Which items can be in another player's world? 17 | 18 | All items, minus a few consumables which need to be in replenishable locations in your game so you can access them if 19 | you lose them. 20 | 21 | ## What does another world's item look like in Paper Mario? 22 | 23 | Items belonging to other worlds are represented by the Present item. 24 | 25 | ## When the player receives an item, what happens? 26 | 27 | For consumables, you will get the item as if you'd found it in the field, and have the opportunity to manage your 28 | inventory if necessary. For other items, you will not receive any sort of notifications; the item will just appear in 29 | your inventory. You only receive items while in the overworld and unpaused. You receive one item per second or so, so it 30 | may take a bit for items to show up in your inventory if receiving several items at once. 31 | -------------------------------------------------------------------------------- /data/EntranceList.py: -------------------------------------------------------------------------------- 1 | # data for entrances in relevant settings 2 | # entrance name: (area id, map id, exit id), which correspond to the entrance you walk into a room from 3 | bowser_shortened_entrances_rmv = [ 4 | # remove these 5 | (22, 26, 0), # Hall to Guard Door 1 -> Guard Door 1 6 | (22, 39, 1), # Guard Door 2 -> Room with Hidden Door 2 7 | 8 | (22, 26, 2), # Lower Grand Hall Lower -> Guard Door 1 9 | (22, 22, 0), # Guard Door 2 -> Castle Battlement Lower Door 10 | 11 | (22, 48, 0), # Hall to Water Puzzle -> Left Water Puzzle 1F 12 | (22, 49, 1), # Bill Blaster Hall Lower -> Right Water Puzzle 1F 13 | 14 | (22, 27, 1), # Castle Battlement Lower Door -> Guard Door 2 15 | (22, 39, 0), # Hidden Passage 1 -> Room with Hidden Door 2 16 | 17 | (22, 19, 0), # Upper Grand Hall Upper -> Split Level Hall 18 | (22, 16, 2), # Blue Fire Bridge -> Maze Room Upper 19 | ] 20 | 21 | bowser_shortened_entrances_add = [ 22 | # add these in their place 23 | (22, 27, 0), # Hall to Guard Door 1 -> Guard Door 2 24 | (22, 17, 1), # Guard Door 2 -> Hall to Guard Door 1 25 | 26 | (22, 27, 1), # Lower Grand Hall Lower -> Guard Door 2 27 | (22, 13, 0), # Guard Door 2 -> Lower Grand Hall Lower 28 | 29 | (22, 47, 0), # Hall to Water Puzzle -> Bill Blaster Hall Lower 30 | (22, 18, 1), # Bill Blaster Hall Lower -> Hall to Water Puzzle 31 | 32 | (22, 47, 1), # Castle Battlement Lower Door -> Bill Blaster Hall Upper 33 | (22, 22, 0), # Hidden Passage 1 -> Castle Battlement Lower Door 34 | 35 | (22, 36, 0), # Upper Grand Hall Upper -> Blue Fire Bridge 36 | (22, 14, 2), # Blue Fire Bridge -> Upper Grand Hall Upper 37 | ] 38 | 39 | bowser_boss_rush_entrances_rmv = [ 40 | # remove entrances 41 | (22, 21, 4), # Riding Star Ship Scene -> Ship Enter/Exit Scenes 42 | (22, 36, 1) # Fake Peach Hallway -> Blue Fire Bridge 43 | ] 44 | 45 | bowser_boss_rush_entrances_add = [ 46 | # add these in their place 47 | (22, 20, 0), # Riding Star Ship Scene -> Fake Peach Hallway 48 | (5, 8, 2) # Fake Peach Hallway -> Riding Star Ship Scene 49 | ] 50 | -------------------------------------------------------------------------------- /items.py: -------------------------------------------------------------------------------- 1 | from .data.ItemList import item_table 2 | import typing 3 | 4 | from BaseClasses import Item, ItemClassification 5 | 6 | """Paper Mario came out August 11th, 2000 (in Japan)""" 7 | item_id_prefix = 8112000000 8 | 9 | 10 | def pm_data_to_ap_id(data, event): 11 | # if 12 | if event or data[6]: 13 | return None 14 | if data[0] in ["KEYITEM", "ITEM", "BADGE", "STARPIECE", "POWERSTAR", "COIN", "GEAR", "PARTNER", "OTHER", 15 | "PARTNERUPGRADE", "NOTHING", "STARPOWER"]: 16 | return item_id_prefix + data[2] 17 | else: 18 | raise Exception(f"Unexpected PM item type found: {data[0]}") 19 | 20 | 21 | def ap_id_to_pm_data(ap_id): 22 | val = ap_id - item_id_prefix 23 | try: 24 | return list(filter(lambda d: d[1][2] == val, item_table.items()))[0] 25 | except IndexError: 26 | raise Exception(f"Could not find desired item ID: {ap_id}") 27 | 28 | 29 | def item_id_to_item_name(item_id): 30 | try: 31 | return list(filter(lambda d: d[1][0] == 'Item' and d[1][2] == item_id, item_table.items()))[0][0] 32 | except IndexError: 33 | raise Exception(f"Could not find desired item ID: {item_id}") 34 | 35 | 36 | def pm_is_item_of_type(item, item_type): 37 | if isinstance(item, PMItem): 38 | return item.type == item_type 39 | if isinstance(item, str): 40 | return item in item_table and item_table[item][0] == item_type 41 | return False 42 | 43 | 44 | class PMItem(Item): 45 | game: str = "Paper Mario" 46 | type: str 47 | 48 | def __init__(self, name, player, data, event): 49 | (type, progression, id, base_price, unused, unused_dupe, unplaceable) = data 50 | 51 | if name == "Trap": 52 | classification = ItemClassification.trap 53 | else: 54 | classification = progression 55 | super(PMItem, self).__init__(name, classification, pm_data_to_ap_id(data, event), player) 56 | self.type = type 57 | self.id = id 58 | self.base_price = base_price 59 | self.unused = unused 60 | self.unused_dupe = unused_dupe 61 | self.unplaceable = unplaceable 62 | self.internal = False 63 | -------------------------------------------------------------------------------- /calculate_crc.py: -------------------------------------------------------------------------------- 1 | # This is pulled directly from PMR: https://github.com/icebound777/PMR-SeedGenerator/blob/main/calculate_crc.py 2 | 3 | from sys import byteorder 4 | 5 | 6 | def recalculate_crcs(target_modfile, coin_palette_crcs: list): 7 | """ 8 | Patches the two CRC values used by the N64 boot chip to verify the integrity 9 | of the ROM (0x10 and 0x14). Paper Mario uses the CIC-NUS-6103 boot chip, so 10 | we must use the corresponding algorithm to calculate the new CRCs. 11 | Reproducing the correct unsigned integer arithmetic is tricky and leads to 12 | this ugly, nigh-unreadable code. But it works. 13 | This function's workings were provided by clover's StarRod crc patching 14 | functionality. 15 | """ 16 | if coin_palette_crcs is None: 17 | with open(target_modfile, "r+b") as file: 18 | t1 = 0xA3886759 # 6103 only 19 | t2 = 0xA3886759 # 6103 only 20 | t3 = 0xA3886759 # 6103 only 21 | t4 = 0xA3886759 # 6103 only 22 | t5 = 0xA3886759 # 6103 only 23 | t6 = 0xA3886759 # 6103 only 24 | 25 | file.seek(0x1000) 26 | for i in range(0x100000//4): 27 | d = int.from_bytes(file.read(4), "big") & 0xFFFFFFFF 28 | if ((t6 + d) & 0xFFFFFFFF) < (t6 & 0xFFFFFFFF): 29 | t4 += 1 30 | t6 += d 31 | t3 ^= d 32 | 33 | r = (d << (d & 0x1F)) | (d >> (32 - (d & 0x1F))) & 0xFFFFFFFF 34 | 35 | t5 += r 36 | if (t2 & 0xFFFFFFFF) > (d & 0xFFFFFFFF): 37 | t2 ^= r 38 | else: 39 | t2 ^= (t6 ^ d) 40 | 41 | t1 += t5 ^ d 42 | 43 | crc1 = ((t6 ^ t4) + t3) & 0xFFFFFFFF 44 | crc2 = ((t5 ^ t2) + t1) & 0xFFFFFFFF 45 | 46 | file.seek(0x10) 47 | file.write(crc1.to_bytes(4, byteorder="big")) 48 | file.write(crc2.to_bytes(4, byteorder="big")) 49 | 50 | else: 51 | print("writing provided crc") 52 | with open(target_modfile, "r+b") as file: 53 | file.seek(0x10) 54 | for crc in coin_palette_crcs: 55 | file.write(crc.to_bytes(4, byteorder="big")) 56 | -------------------------------------------------------------------------------- /modules/random_battles.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/rando_modules/random_battles.py 2 | 3 | from ..data.battles import get_battle_key, boss_battles 4 | from ..data.formations_meta import chapter_battle_mapping 5 | from ..options import BossShuffle 6 | 7 | 8 | def get_boss_battles(boss_shuffle: int, random) -> tuple[list[tuple[int, int]], dict[int, int]]: 9 | def get_battle_group(battle: int) -> int: 10 | # battles have two bytes, the upper byte signifies the battle group 11 | # to load, the lower byte the id within that group 12 | return battle >> 8 13 | 14 | def get_battle_chapter(battle_group: int) -> int | None: 15 | for chapter, battle_group_list in chapter_battle_mapping.items(): 16 | if battle_group in [int(x, 16) for x in battle_group_list]: 17 | return chapter 18 | return None 19 | battles_setup: list[tuple[int, int]] = [] 20 | boss_chapter_map: dict[int, int] = {} 21 | 22 | if boss_shuffle == BossShuffle.option_false: 23 | for name, vanilla_id in boss_battles.items(): 24 | battles_setup.append((get_battle_key(name), vanilla_id)) 25 | boss_chapter_map = {1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7} # default 26 | 27 | elif boss_shuffle == BossShuffle.option_true: 28 | keys_chapters: list = [] 29 | values_chapters: list[tuple[int, int]] = [] 30 | 31 | for name, vanilla_id in boss_battles.items(): 32 | key = get_battle_key(name) 33 | 34 | battle_group = get_battle_group(vanilla_id) 35 | chapter = get_battle_chapter(battle_group) 36 | assert (chapter is not None) 37 | 38 | keys_chapters.append((key, chapter)) 39 | values_chapters.append((vanilla_id, chapter)) 40 | 41 | random.shuffle(values_chapters) 42 | 43 | battles_setup: list[tuple[int, int]] = list(zip( 44 | [x[0] for x in keys_chapters], [x[0] for x in values_chapters] 45 | )) 46 | boss_chapter_map: dict[int, int] = dict(zip( 47 | [x[1] for x in values_chapters], [x[1] for x in keys_chapters] 48 | )) # e.g. General Guy appears in chapter 1, so (4, 1) 49 | 50 | return battles_setup, boss_chapter_map 51 | -------------------------------------------------------------------------------- /Rules.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING 2 | 3 | from .data.LocationsList import exclude_from_trap_placement 4 | from worlds.generic.Rules import set_rule, add_rule, add_item_rule 5 | from .data.ItemList import item_multiples_ids 6 | from .items import ap_id_to_pm_data 7 | 8 | if TYPE_CHECKING: 9 | from . import PaperMarioWorld 10 | 11 | 12 | def set_rules(world: "PaperMarioWorld") -> None: 13 | 14 | world.multiworld.completion_condition[world.player] = lambda state: state.has("STARROD", world.player) 15 | 16 | # Coin gets glitchy graphic on the sign for some reason 17 | add_item_rule(world.multiworld.get_location("GR Goomba Road 2 On the Sign", world.player), 18 | lambda item: item.player != world.player or item.name != "Coin") 19 | 20 | # Items in the storeroom in the second Toad Town shop can cause a crash when entering the screen 21 | # This is caused by the game attempting to spawn a local item that has already been collected 22 | # While this shouldn't normally be possible, we're going to safeguard this from occurring+ 23 | 24 | add_item_rule(world.multiworld.get_location("TT Residental District Storeroom Item 1", world.player), 25 | lambda item: item.player != world.player or item.id not in item_multiples_ids.keys()) 26 | add_item_rule(world.multiworld.get_location("TT Residental District Storeroom Item 2", world.player), 27 | lambda item: item.player != world.player or item.id not in item_multiples_ids.keys()) 28 | add_item_rule(world.multiworld.get_location("TT Residental District Storeroom Item 3", world.player), 29 | lambda item: item.player != world.player or item.id not in item_multiples_ids.keys()) 30 | add_item_rule(world.multiworld.get_location("TT Residental District Storeroom Item 4", world.player), 31 | lambda item: item.player != world.player or item.id not in item_multiples_ids.keys()) 32 | 33 | # Damage traps cannot be in shops or over spinning flowers 34 | untrappable_locations = list(filter(lambda location: location.name in exclude_from_trap_placement, 35 | world.multiworld.get_locations(player=world.player))) 36 | for loc in untrappable_locations: 37 | add_item_rule(loc, lambda item: item.player != world.player or item.name != "Damage Trap") 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Paper Mario 64 AP World 2 | This is the AP World for Paper Mario 64 to be used in [Archipelago.](https://archipelago.gg/) It is in a playable, **alpha** state. Some things will work, some things will not. Things that do work might not work in a way that you would expect from a fully completed AP World. 3 | 4 | Please report any issues you encounter in the dev-multiplayer channel in the Paper Mario Randomizer Discord or the Paper Mario thread in the Archipelago Discord's future game design forum. Be careful using different settings that may not be fully implemented yet, as it may result in unbeatable seeds, game crashes, or generation failures. 5 | 6 | ## Setup 7 | 8 | View the setup guide [here.](https://github.com/JKBSunshine/PMR_APWorld/blob/main/docs/setup_en.md) Along with general setup, it will include details on what settings should or should not be used. Before tweaking the base YAML, please refer to the setup guide to save yourself from trying a setting that isn't implemented. 9 | 10 | ## Quick Notes 11 | 12 | - Receiving consumables will cause an item get animation similar to what you'd get if you found it in the world. Manage your inventory carefully. 13 | - Receiving non-consumable items does not have any animation; items will simply appear in your inventory. 14 | - Off-world items do not clarify what the item is or who it belongs to in game; you will have to look at your client to see what you have sent and to who. 15 | - Off-world items in shops and Merlow's rewards have the item and player names shown so that you know what you're buying. The font color for the item roughly matches the default font colors for progression, filler, traps, and useful item classifications. 16 | - Refer to the [PMR Wiki](https://github.com/icebound777/PMR-SeedGenerator/wiki) for help for Paper Mario Randomizer, such as commonly missed locations, general tips, and more. 17 | 18 | ## TO DO from PMR 19 | - Dungeon Entrance Randomizer 20 | - Boss Shuffle 21 | - Static Mirror Mode 22 | 23 | ## Credits 24 | 25 | The Paper Mario Randomizer (PMR) as a whole was built by 26 | 27 | clover 28 | Icebound777 29 | Pronyo 30 | 31 | and can be found [here.](https://github.com/icebound777/PMR-SeedGenerator) The AP World is being ported from PMR with their permission. 32 | 33 | Various AP Worlds were referenced to help port PMR to Archipelago, including OoT, Pokemon Emerald, Castlevania 64, Subnautica, and Kingdom Hearts 2. 34 | -------------------------------------------------------------------------------- /data/node.py: -------------------------------------------------------------------------------- 1 | from ..items import PMItem 2 | 3 | 4 | # A table that represents all areas of interactivity 5 | class Node: 6 | 7 | # MapArea this node is found in 8 | map_id: int = -1 9 | area_id: int = -1 10 | 11 | # Entrance data of the Map if this note represents an entrance 12 | entrance_id: int = None 13 | entrance_type: str = None # like walk, pipe, door etc. 14 | entrance_name: str = None # verbose name of entrance 15 | 16 | # Human-readable name of item location (eg ItemA) or item price (eg ShopPriceA) 17 | key_name_item: str = None 18 | key_name_price: str = None 19 | item_source_type: int = None 20 | 21 | # reference to item placed here in the unmodified game 22 | vanilla_item: int = None 23 | 24 | # reference to item placed here during randomization 25 | current_item: PMItem = None 26 | 27 | # vanilla item price if shop 28 | vanilla_price: int = None 29 | 30 | # index bytes of the DBKey 31 | item_index: int = None 32 | price_index: int = None 33 | 34 | identifier: str = None 35 | 36 | # used for in game strings like shop descriptions 37 | shop_string_location: int = -1 38 | shop_string: list[int] = None 39 | 40 | def __str__(self): 41 | """Return string representation of current node""" 42 | entrance = ("[" + format(self.entrance_id) + "] ") if self.entrance_id else '' 43 | itemkey = ("[" + format(self.key_name_item) + "] ") if self.key_name_item else '' 44 | item = self.current_item.name if self.current_item else '' 45 | price = (" (" + format(self.current_item.base_price) + ")") if self.current_item and self.key_name_price else '' 46 | 47 | return f"[{self.identifier}]{entrance}{itemkey}{item}{price}" 48 | 49 | def get_item_key(self): 50 | """Return convention key for item location""" 51 | if self.current_item is None: 52 | return None 53 | return (0xA1 << 24) | (self.area_id << 16) | (self.map_id << 8) | self.item_index 54 | 55 | def get_price_key(self): 56 | """Return convention key for item location""" 57 | if self.current_item is None: 58 | return None 59 | return (0xA1 << 24) | (self.area_id << 16) | (self.map_id << 8) | self.price_index 60 | 61 | def is_shop(self): 62 | """Return whether this location is a shop or not.""" 63 | return self.key_name_price is not None 64 | -------------------------------------------------------------------------------- /data/regions/dry_dry_outpost.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "DDO Outpost 1", 4 | "area_id": "9", 5 | "map_id": "0", 6 | "map_name": "Outpost 1", 7 | "events": { 8 | "RF_CanMeetMoustafa": "'RF_CanUseDROCode' and 'RF_MouserReturned'", 9 | "RF_MouserLeftShop": "True", 10 | "StarPiece_DRO_1": "True", 11 | "StarPiece_DRO_8": "True" 12 | }, 13 | "locations": { 14 | "DDO Outpost 1 In Red Tree": "can_shake_trees", 15 | "DDO Outpost 1 Composer Lyrics Reward": "Lyrics", 16 | "DDO Outpost 1 Store Legend": "'FAVOR_7_02_done' and 'RF_MouserReturned'", 17 | "DDO Outpost 1 Little Mouser Letter Reward": "can_use_ability_parakarry and Letter_to_Little_Mouser and 'RF_MouserReturned'", 18 | "DDO Outpost 1 Shop Item 1": "'RF_MouserReturned'", 19 | "DDO Outpost 1 Shop Item 2": "'RF_MouserReturned'", 20 | "DDO Outpost 1 Shop Item 3": "'RF_MouserReturned'", 21 | "DDO Outpost 1 Shop Item 4": "'RF_MouserReturned'", 22 | "DDO Outpost 1 Shop Item 5": "'RF_MouserReturned'", 23 | "DDO Outpost 1 Shop Item 6": "'RF_MouserReturned'" 24 | }, 25 | "exits": { 26 | "DDD E3 Outside Outpost": "True", 27 | "DDO Outpost 2": "True", 28 | "DDO Outpost 1 Pipe": "'GF_TIK01_WarpPipes' and Boots" 29 | } 30 | }, 31 | { 32 | "region_name": "DDO Outpost 1 Pipe", 33 | "area_id": "9", 34 | "map_id": "0", 35 | "map_name": "Outpost 1", 36 | "exits": { 37 | "TTT Warp Zone 1 (B1) Outpost Pipe": "True", 38 | "DDO Outpost 1": "True" 39 | } 40 | }, 41 | { 42 | "region_name": "DDO Outpost 2", 43 | "area_id": "9", 44 | "map_id": "1", 45 | "map_name": "Outpost 2", 46 | "events": { 47 | "RF_MouserReturned": "'RF_MouserLeftShop'", 48 | "RF_CanUseDROCode": "Lemon", 49 | "StarPiece_DRO_1": "True", 50 | "StarPiece_DRO_8": "True" 51 | }, 52 | "locations": { 53 | "DDO Outpost 2 Merlee Request (Koopa Koot Favor)": "'GF_HOS06_MerluvleeRequestedCrystalBall' and Boots", 54 | "DDO Outpost 2 Moustafa Gift": "'RF_CanMeetMoustafa' and Boots", 55 | "DDO Outpost 2 Mr. E. Letter Reward": "can_use_ability_parakarry and Letter_to_Mr_E", 56 | "DDO Outpost 2 Hidden Panel": "can_flip_panels and 'RF_CanMeetMoustafa' and Boots", 57 | "DDO Outpost 2 Toad House Roof": "'RF_CanMeetMoustafa' and Boots" 58 | }, 59 | "exits": { 60 | "DDO Outpost 1": "True" 61 | } 62 | } 63 | ] 64 | -------------------------------------------------------------------------------- /data/itemlocation_replenish.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/metadata/itemlocation_replenish.py 2 | """ 3 | List of repleneshing item locations. 4 | These are item locations, where the item can be acquired multiple times. 5 | Thusly this list includes items sold by shops, items found in certain trees, 6 | bushes and crates, as well as the item traded by the hungry Yoshi and the final 7 | trade item from Rip Cheato. 8 | """ 9 | replenishing_itemlocations = [ 10 | "DRO_01/ShopItemA", "DRO_01/ShopItemC", "DRO_01/ShopItemF", 11 | "FLO_16/ItemB", 12 | "FLO_09/ItemA", 13 | "FLO_03/Tree1_Drop1A", "FLO_03/Tree1_Drop1B", 14 | "FLO_08/ItemB", "FLO_08/Tree1_Drop1A", "FLO_08/Tree1_Drop1B", 15 | "FLO_24/Tree1_Drop1A", "FLO_24/Tree1_Drop1B", 16 | "FLO_25/ItemA", "FLO_25/Tree1_Drop1A", "FLO_25/Tree1_Drop1B", 17 | "FLO_14/ItemB", 18 | "HOS_03/ShopItemA", "HOS_03/ShopItemB", "HOS_03/ShopItemC", 19 | "HOS_03/ShopItemD", "HOS_03/ShopItemE", "HOS_03/ShopItemF", 20 | "IWA_10/Bush4_Drop1", 21 | "JAN_00/Tree1_Drop1A", 22 | "JAN_01/Tree2_Drop1", "JAN_01/Tree3_Drop1", "JAN_01/Tree4_Drop1", 23 | "JAN_01/Tree5_Drop1", "JAN_01/Tree6_Drop1", "JAN_01/Tree7_Drop1B", 24 | "JAN_02/Tree2_Drop1A", "JAN_02/Tree3_Drop1A", 25 | "JAN_03/ShopItemA", "JAN_03/ShopItemB", "JAN_03/ShopItemC", "JAN_03/ShopItemD", 26 | "JAN_03/ShopItemE", "JAN_03/ShopItemF", 27 | "JAN_03/Tree1_Drop1A", 28 | "KMR_02/Tree1_Drop1A", 29 | "KPA_96/ShopItemA", "KPA_96/ShopItemB", "KPA_96/ShopItemC", "KPA_96/ShopItemD", 30 | "KPA_96/ShopItemE", "KPA_96/ShopItemF", 31 | "MAC_00/ShopItemA", "MAC_00/ShopItemB", "MAC_00/ShopItemC", "MAC_00/ShopItemD", 32 | "MAC_00/ShopItemE", "MAC_00/ShopItemF", 33 | "MAC_04/ShopItemA", "MAC_04/ShopItemB", "MAC_04/ShopItemC", "MAC_04/ShopItemD", 34 | "MAC_04/ShopItemE", "MAC_04/ShopItemF", 35 | "MIM_11/Bush1_Drop1", 36 | "NOK_01/Bush4_Drop1A", 37 | "NOK_01/ShopItemA", "NOK_01/ShopItemB", "NOK_01/ShopItemC", "NOK_01/ShopItemD", 38 | "NOK_01/ShopItemE", "NOK_01/ShopItemF", 39 | "NOK_02/Bush1_Drop1", 40 | "OBK_03/ShopItemA", "OBK_03/ShopItemB", "OBK_03/ShopItemC", "OBK_03/ShopItemD", 41 | "OBK_03/ShopItemE", "OBK_03/ShopItemF", 42 | "OBK_05/CrateA", "OBK_05/CrateB", 43 | "OMO_01/ItemB", "OMO_01/ItemC", "OMO_01/ItemD", "OMO_01/ItemE", "OMO_01/ItemF", 44 | "SAM_02/ItemA", "SAM_02/ShopItemA", "SAM_02/ShopItemB", "SAM_02/ShopItemC", 45 | "SAM_02/ShopItemD", "SAM_02/ShopItemE", "SAM_02/ShopItemF", 46 | "SAM_08/ItemA", 47 | "SBK_56/Tree1_Drop1A", "SBK_56/Tree2_Drop1A", 48 | ] -------------------------------------------------------------------------------- /Locations.py: -------------------------------------------------------------------------------- 1 | from BaseClasses import Location 2 | from .data.LocationsList import location_table 3 | 4 | """Paper Mario came out August 11th, 2000 (in Japan)""" 5 | location_id_prefix = 8112000000 6 | location_name_to_id = {name: (location_id_prefix + index) for (index, name) in enumerate(location_table.keys())} 7 | 8 | 9 | # Special location class for paper mario that specifies paper mario specific info for locations 10 | class PMLocation(Location): 11 | game: str = "Paper Mario" 12 | 13 | def __init__(self, player, name='', code=None, identifier=None, source_type=None, area_id=None, map_id=None, 14 | index=None, vanilla_item=None, keyname=None, price_keyname=None, vanilla_price=None, 15 | price_index=None, parent=None, internal=False, event=None): 16 | super(PMLocation, self).__init__(player, name, code, parent) 17 | self.identifier = identifier 18 | self.source_type = source_type 19 | self.area_id = area_id 20 | self.map_id = map_id 21 | self.index = index 22 | self.vanilla_item = vanilla_item 23 | self.keyname = keyname 24 | self.price_keyname = price_keyname 25 | self.vanilla_price = vanilla_price 26 | self.price_index = price_index 27 | self.never = False 28 | self.disabled = False 29 | self.event = event 30 | self.internal = internal 31 | 32 | 33 | # used when loading regions from json to create location objects 34 | def location_factory(locations, player: int): 35 | ret = [] 36 | singleton = False 37 | 38 | # make sure location is a tuple and not a singleton for parsing purposes 39 | if isinstance(locations, str): 40 | locations = [locations] 41 | singleton = True 42 | 43 | # grab location info from location_table and create the PMLocation object using that info 44 | for location in locations: 45 | if location in location_table: 46 | match_location = location 47 | else: 48 | match_location = next(filter(lambda k: k.lower() == location.lower(), location_table), None) 49 | if match_location: 50 | (identifier, source_type, area_id, map_id, index, vanilla_item, keyname, price_keyname, 51 | vanilla_price, price_index) = location_table[match_location] 52 | ret.append(PMLocation(player, match_location, location_name_to_id.get(match_location, None), 53 | identifier, source_type, area_id, map_id, index, vanilla_item, 54 | keyname, price_keyname, vanilla_price, price_index)) 55 | else: 56 | raise KeyError("Unknown Location", Location) 57 | 58 | # if it was a singleton originally, we need to return it as such 59 | if singleton: 60 | return ret[0] 61 | return ret 62 | -------------------------------------------------------------------------------- /data/enum_options.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/rando_enums/enum_options.py 2 | 3 | from enum import IntEnum, unique 4 | 5 | 6 | @unique 7 | class BowserCastleMode(IntEnum): 8 | VANILLA = 0 9 | SHORTEN = 1 10 | BOSSRUSH = 2 11 | 12 | 13 | @unique 14 | class HiddenBlockMode(IntEnum): 15 | VANILLA = 0 16 | WATT_OUT = 1 17 | WATT_ACQUIRED = 2 18 | ALWAYS_VISIBLE = 3 19 | 20 | 21 | @unique 22 | class StartingBoots(IntEnum): 23 | JUMPLESS = -1 24 | BOOTS = 0 25 | SUPERBOOTS = 1 26 | ULTRABOOTS = 2 27 | 28 | 29 | @unique 30 | class StartingHammer(IntEnum): 31 | HAMMERLESS = -1 32 | HAMMER = 0 33 | SUPERHAMMER = 1 34 | ULTRAHAMMER = 2 35 | 36 | 37 | @unique 38 | class IncludeFavorsMode(IntEnum): 39 | NOT_RANDOMIZED = 0 40 | RND_REWARD_VANILLA_KEYITEMS = 1 41 | FULL_SHUFFLE = 2 42 | 43 | 44 | @unique 45 | class IncludeLettersMode(IntEnum): 46 | NOT_RANDOMIZED = 0 47 | SIMPLE_LETTERS = 1 48 | RANDOM_CHAIN_REWARD = 2 49 | FULL_SHUFFLE = 3 50 | 51 | 52 | @unique 53 | class RandomizeConsumablesMode(IntEnum): 54 | OFF = 0 55 | FULL_RANDOM = 1 56 | BALANCED_RANDOM = 2 57 | MYSTERY_ONLY = 3 58 | 59 | 60 | @unique 61 | class ItemTrapMode(IntEnum): 62 | OFF = 0 63 | SPARSE = 1 64 | MODERATE = 2 65 | PLENTY = 3 66 | 67 | 68 | @unique 69 | class GearShuffleMode(IntEnum): 70 | VANILLA = 0 71 | GEAR_LOCATION_SHUFFLE = 1 72 | FULL_SHUFFLE = 2 73 | 74 | 75 | @unique 76 | class RandomMoveCosts(IntEnum): 77 | VANILLA = 0 78 | BALANCED_RANDOM = 1 79 | SHUFFLED = 2 80 | FULLY_RANDOM = 3 81 | 82 | 83 | # modified from the original since we can't have both a value and a setting from one option 84 | # two options per palette would be rather ugly looking and probably a bit confusing 85 | # SETTING is the old value, kept them around in case they were needed 86 | @unique 87 | class RandomPalettes(IntEnum): 88 | DEFAULT_PALETTE = 0 89 | SELECT_PALETTE = 1 90 | RANDOM_PICK = 10 91 | RANDOM_PICK_NOT_VANILLA = 11 92 | ALWAYS_RANDOM = 12 93 | RANDOM_PICK_SETTING = 2 94 | RANDOM_PICK_NOT_VANILLA_SETTING = 3 95 | ALWAYS_RANDOM_SETTING = 4 96 | 97 | @classmethod 98 | def get_setting_value(cls, value): 99 | if value == cls.ALWAYS_RANDOM: 100 | return cls.ALWAYS_RANDOM_SETTING 101 | elif value == cls.RANDOM_PICK_NOT_VANILLA: 102 | return cls.RANDOM_PICK_NOT_VANILLA_SETTING 103 | elif value == cls.RANDOM_PICK: 104 | return cls.RANDOM_PICK_SETTING 105 | return value 106 | 107 | 108 | @unique 109 | class MerlowRewardPricing(IntEnum): 110 | CHEAP = 0 111 | NORMAL = 1 112 | 113 | @classmethod 114 | def has_value(cls, value): 115 | return value in set(item.value for item in cls) 116 | 117 | 118 | @unique 119 | class PartnerUpgradeShuffle(IntEnum): 120 | OFF = 0 121 | SUPERBLOCKLOCATIONS = 1 122 | FULL = 2 123 | -------------------------------------------------------------------------------- /data/item_exclusion.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/metadata/item_exclusion.py 2 | 3 | """ 4 | This file offers lists of items related to the removal of useless items 5 | during certain randomizer settings. 6 | """ 7 | 8 | exclude_due_to_settings = { 9 | "startwith_bluehouse_open": [ 10 | "Odd Key" 11 | ], 12 | "startwith_forest_open": [ 13 | "Forest Pass" 14 | ], 15 | "magical_seeds_required": { 16 | 0: [ 17 | "Magical Seed", 18 | "Magical Seed", 19 | "Magical Seed", 20 | "Magical Seed", 21 | ], 22 | 1: [ 23 | "Magical Seed", 24 | "Magical Seed", 25 | "Magical Seed", 26 | ], 27 | 2: [ 28 | "Magical Seed", 29 | "Magical Seed", 30 | ], 31 | 3: [ 32 | "Magical Seed", 33 | ], 34 | }, 35 | "shorten_bowsers_castle": [ 36 | "Bowser Castle Key", 37 | "Bowser Castle Key", 38 | "Bowser Castle Key", 39 | "Bowser Castle Key", 40 | "Bowser Castle Key", 41 | ], 42 | "boss_rush": [ 43 | "Prison Key", 44 | "Prison Key", 45 | ], 46 | "always_speedyspin": [ 47 | "Speedy Spin", 48 | ], 49 | "always_ispy": [ 50 | "I Spy", 51 | ], 52 | "always_peekaboo": [ 53 | "Peekaboo", 54 | ], 55 | "do_randomize_dojo": [ 56 | "First Degree Card", 57 | "Second Degree Card", 58 | "Third Degree Card", 59 | "Fourth Degree Card", 60 | "Diploma", 61 | ], 62 | "do_progressive_badges": [ 63 | "Mini Smash Charge", 64 | "Smash Charge", 65 | "Super Smash Charge", 66 | "Mini Jump Charge", 67 | "Jump Charge", 68 | "Super Jump Charge", 69 | "Power Jump", 70 | "Super Jump", 71 | "Mega Jump", 72 | "Power Smash", 73 | "Super Smash", 74 | "Mega Smash", 75 | "Quake Hammer", 76 | "Power Quake", 77 | "Mega Quake" 78 | ], 79 | "partner_upgrade_shuffle": [ 80 | "Ultra Stone", 81 | "GenericUpgrade", # Goombario 1 82 | "GenericUpgrade", # Goombario 2 83 | "GenericUpgrade", # Kooper 1 84 | "GenericUpgrade", # Kooper 2 85 | "GenericUpgrade", # Bombette 1 86 | "GenericUpgrade", # Bombette 2 87 | "GenericUpgrade", # Parakarry 1 88 | "GenericUpgrade", # Parakarry 2 89 | "GenericUpgrade", # Watt 1 90 | "GenericUpgrade", # Watt 2 91 | "GenericUpgrade", # Sushie 1 92 | "GenericUpgrade", # Sushie 2 93 | "GenericUpgrade", # Lakilester 1 94 | "GenericUpgrade", # Lakilester 2 95 | "GenericUpgrade", # Bow 1 96 | "GenericUpgrade", # Bow 2 97 | ] 98 | } 99 | 100 | exclude_from_taycet_placement = [ 101 | "Koopasta", # 0x0B5 102 | "Cake", # 0x0C1 103 | "Mistake", # 0x0C2 104 | "Koopa Tea", # 0x0C3 105 | "Kooky Cookie", # 0x0D3 106 | "Nutty Cake", # 0x0D6 107 | ] 108 | -------------------------------------------------------------------------------- /data/LogicHelpers.json: -------------------------------------------------------------------------------- 1 | { 2 | "Boots": "(Progressive_Boots, 1)", 3 | "Super_Boots": "(Progressive_Boots, 2)", 4 | "Ultra_Boots": "(Progressive_Boots, 3)", 5 | "Hammer": "(Progressive_Hammer, 1)", 6 | "Super_Hammer": "(Progressive_Hammer, 2)", 7 | "Ultra_Hammer": "(Progressive_Hammer, 3)", 8 | 9 | "can_flip_panels": "Ultra_Hammer or Super_Boots", 10 | 11 | "can_shake_trees": "Bombette or Hammer", 12 | 13 | "can_hit_grounded_blocks": "can_use_ability_bombette or can_use_ability_kooper or Super_Boots or Hammer", 14 | "can_hit_floating_blocks": "can_use_ability_kooper or Boots", 15 | "can_hit_grounded_switches": "can_use_ability_bombette or can_use_ability_kooper or Boots or Hammer or can_use_ability_parakarry", 16 | 17 | "can_climb_steps": "Boots or can_use_ability_parakarry", 18 | "saved_all_yoshi_kids": "'RF_SavedYoshiKid_1' and 'RF_SavedYoshiKid_2' and 'RF_SavedYoshiKid_3' and 'RF_SavedYoshiKid_4' and 'RF_SavedYoshiKid_5'", 19 | "can_reenter_vertical_pipes": "can_use_ability_kooper or Boots or can_use_ability_parakarry", 20 | 21 | "has_strong_aerial_partner": "Bow or Watt or Sushie or Lakilester", 22 | "has_weak_aerial_partner": "Goombario or Parakarry", 23 | 24 | "can_pass_kent": "(not kent_c_koopa == 1) or (('STARSPIRIT', 2) and (Boots or Goombario))", 25 | "can_beat_all_bloopers": "(can_beat_blooper and can_beat_electro_blooper and can_beat_super_blooper)", 26 | "can_beat_blooper": "(Super_Boots or (Boots and has_weak_aerial_partner) or has_strong_aerial_partner) or blooper_damage_requirements < 1", 27 | "can_beat_electro_blooper": "(Ultra_Boots or (Super_Boots and (has_strong_aerial_partner or has_weak_aerial_partner))) or blooper_damage_requirements < 2", 28 | "can_beat_super_blooper": "(Ultra_Boots and has_strong_aerial_partner) or blooper_damage_requirements < 3", 29 | 30 | "can_see_hidden_blocks": "can_use_ability_watt or hidden_block_mode == 3", 31 | 32 | "has_required_spirits": "('STARSPIRIT_1' or 1 not in required_spirits) and ('STARSPIRIT_2' or 2 not in required_spirits) and ('STARSPIRIT_3' or 3 not in required_spirits) and ('STARSPIRIT_4' or 4 not in required_spirits) and ('STARSPIRIT_5' or 5 not in required_spirits) and ('STARSPIRIT_6' or 6 not in required_spirits) and ('STARSPIRIT_7' or 7 not in required_spirits)", 33 | 34 | "star_way_star_spirits": "('STARSPIRIT', star_way_spirits)", 35 | "star_way_power_stars": "(Power_Star, star_way_power_stars) or not power_star_hunt", 36 | "star_way_specific_spirits": "has_required_spirits or not require_specific_spirits", 37 | "can_reach_star_way": "star_way_star_spirits and star_way_power_stars and star_way_specific_spirits", 38 | "star_way_goal": "seed_goal == 1", 39 | 40 | "star_beam_power_stars": "(Power_Star, star_beam_power_stars) or not power_star_hunt", 41 | "star_beam_star_spirits": "('STARSPIRIT', star_beam_spirits)", 42 | "has_star_beam_requirements": "star_beam_star_spirits and star_beam_power_stars", 43 | 44 | "can_use_ability_kooper": "Kooper or partners_always_usable", 45 | "can_use_ability_bombette": "Bombette or partners_always_usable", 46 | "can_use_ability_parakarry": "Parakarry or partners_always_usable", 47 | "can_use_ability_bow": "Bow or partners_always_usable", 48 | "can_use_ability_watt": "Watt or partners_always_usable", 49 | "can_use_ability_sushie": "Sushie or partners_always_usable", 50 | "can_use_ability_lakilester": "Lakilester or partners_always_usable" 51 | } -------------------------------------------------------------------------------- /modules/random_audio.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/rando_modules/random_audio.py 2 | """ 3 | This module modifies the music in the ROM by pairing different song ids and 4 | song variation ids to different songs and variations. 5 | DBKey: A700(u8 song)(u8 variation) 6 | DBValue: (s16 song)(s16 variation) 7 | I.e. to map song 4, variation 1 to song F, variation 2 we'd have 8 | A7000401 : 000F0002 9 | """ 10 | from copy import deepcopy 11 | 12 | from ..data.enum_types import SongType 13 | from ..data.song_data import SongData, song_data_array 14 | from ..options import ShuffleMusic 15 | 16 | 17 | def get_randomized_audio(randomize_bgm, randomize_jingles, random) -> list: 18 | dbkey_base = 0xA7000000 19 | db_data = [] 20 | 21 | def get_song_default_tuple(songdata: SongData) -> tuple: 22 | return ( 23 | dbkey_base | (songdata.song_id << 8) | songdata.variation_id, 24 | 0xFFFFFFFF 25 | ) 26 | 27 | if randomize_bgm != ShuffleMusic.option_Off: 28 | bgms = [x for x in song_data_array if x.loops] 29 | categorized_lists = {} 30 | 31 | if randomize_bgm == ShuffleMusic.option_Shuffle_By_Mood: 32 | for songdata in bgms: 33 | if songdata.mood not in categorized_lists: 34 | categorized_lists[songdata.mood] = [] 35 | categorized_lists[songdata.mood].append(songdata) 36 | elif randomize_bgm == ShuffleMusic.option_Shuffle_By_Type: 37 | for songdata in bgms: 38 | if songdata.type not in categorized_lists: 39 | categorized_lists[songdata.type] = [] 40 | categorized_lists[songdata.type].append(songdata) 41 | elif randomize_bgm == ShuffleMusic.option_Full_Shuffle: 42 | categorized_lists["all"] = bgms 43 | for song_category in categorized_lists: 44 | work_array = deepcopy(categorized_lists[song_category]) 45 | for songdata in categorized_lists[song_category]: 46 | dbkey = dbkey_base | (songdata.song_id << 8) | songdata.variation_id 47 | random_choice = random.randint(0, len(work_array) - 1) 48 | new_song = work_array.pop(random_choice) 49 | dbvalue = (new_song.song_id << 16) | new_song.variation_id 50 | db_data.append((dbkey, dbvalue)) 51 | # print(f"before {songdata.song_name}/{songdata.variation_name}, after {new_song.song_name}/{new_song.variation_name}") 52 | # print(f"{hex(dbkey)}/{hex(dbvalue)}") 53 | else: 54 | for songdata in song_data_array: 55 | if songdata.loops: 56 | db_data.append(get_song_default_tuple(songdata)) 57 | 58 | if randomize_jingles: 59 | jingles = [x for x in song_data_array if x.type == SongType.JINGLE] 60 | work_array = deepcopy(jingles) 61 | for jingle in jingles: 62 | dbkey = dbkey_base | (jingle.song_id << 8) | jingle.variation_id 63 | random_choice = random.randint(0, len(work_array) - 1) 64 | new_song = work_array.pop(random_choice) 65 | dbvalue = (new_song.song_id << 16) | new_song.variation_id 66 | db_data.append((dbkey, dbvalue)) 67 | # print(f"before {jingle.song_name}/{jingle.variation_name}, after {new_song.song_name}/{new_song.variation_name}") 68 | # print(f"{hex(dbkey)}/{hex(dbvalue)}") 69 | 70 | else: 71 | for songdata in [x for x in song_data_array if x.type == SongType.JINGLE]: 72 | db_data.append(get_song_default_tuple(songdata)) 73 | 74 | return db_data 75 | -------------------------------------------------------------------------------- /modules/random_actor_stats.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/rando_modules/random_actor_stats.py 2 | from ..data.actor_data import actor_attr_table, actor_param_table, get_actor_attr_key 3 | from ..options import EnemyDifficulty 4 | 5 | 6 | def get_shuffled_chapter_difficulty( 7 | enemy_difficulty: int, 8 | starting_chapter: int, 9 | random 10 | ): 11 | # Load and reorganize actor param data into different format 12 | # format example: 13 | # "00_Goomba": { 14 | # "Level": [5,8,10,13,15,18,20,23], 15 | # "HP": [2,3,4,5,6,7,8], 16 | # "DamageA": [1,2,2,3,3,4,4,5] 17 | # "NativeChapter": 1 18 | # } 19 | all_enemy_stats = {} 20 | 21 | shuffle_chapter_difficulty = (enemy_difficulty == EnemyDifficulty.option_Shuffle_Chapter_Difficulty) 22 | progressive_scaling = (enemy_difficulty == EnemyDifficulty.option_Progressive_Scaling) 23 | 24 | for param_key, param_value in actor_param_table.items(): 25 | actor_name = param_value[0] 26 | actor_native_chapter = param_value[3] 27 | actor_stat_name = param_value[1] 28 | actor_stat_values = [ 29 | param_value[4], 30 | param_value[5], 31 | param_value[6], 32 | param_value[7], 33 | param_value[8], 34 | param_value[9], 35 | param_value[10], 36 | param_value[11], 37 | ] 38 | 39 | if actor_name not in all_enemy_stats: 40 | all_enemy_stats[actor_name] = {} 41 | all_enemy_stats[actor_name]["NativeChapter"] = actor_native_chapter 42 | all_enemy_stats[actor_name][actor_stat_name] = actor_stat_values 43 | 44 | # Randomize chapter order 45 | chapters_to_shuffle = [1, 2, 3, 4, 5, 6, 7] 46 | if shuffle_chapter_difficulty: 47 | random.shuffle(chapters_to_shuffle) 48 | 49 | chapter_dict = {} 50 | for old_chapter_number, new_chapter_number in enumerate(chapters_to_shuffle): 51 | chapter_dict[old_chapter_number + 1] = new_chapter_number 52 | # Chapter 8 is never shuffled 53 | chapter_dict[8] = 8 54 | 55 | if starting_chapter != 0 and chapter_dict[starting_chapter] > 3: 56 | # Chapter we are starting in is too high of a level: adjust it 57 | original_chapters = list(chapter_dict.keys()) 58 | random.shuffle(original_chapters) 59 | for original_chapter in original_chapters: 60 | if chapter_dict[original_chapter] <= 3: 61 | swap_chapter = chapter_dict[starting_chapter] 62 | chapter_dict[starting_chapter] = chapter_dict[original_chapter] 63 | chapter_dict[original_chapter] = swap_chapter 64 | break 65 | 66 | new_enemy_stats = [] 67 | 68 | for attr_id, attr_value in actor_attr_table.items(): 69 | dbkey = get_actor_attr_key(attr_id) 70 | actor_name = attr_value[3] 71 | actor_stat_name = attr_value[4] 72 | if (actor_name not in all_enemy_stats 73 | or actor_stat_name not in all_enemy_stats[actor_name] 74 | or (not progressive_scaling and not shuffle_chapter_difficulty)): 75 | # not supposed to be random, so write defaults 76 | value = attr_value[5] 77 | else: 78 | native_chapter = all_enemy_stats[actor_name]["NativeChapter"] 79 | if native_chapter == -1: 80 | # Special case for Dojo / Kent 81 | native_chapter = 1 82 | value = int(all_enemy_stats[actor_name][actor_stat_name][chapter_dict.get(native_chapter) - 1]) 83 | 84 | new_enemy_stats.append((dbkey, value)) 85 | 86 | return new_enemy_stats, chapter_dict 87 | -------------------------------------------------------------------------------- /modules/random_mystery.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/rando_modules/random_mystery.py 2 | # modified slightly to use the item table instead of db 3 | from ..options import MysteryShuffle 4 | from ..data.ItemList import item_table 5 | from ..data.MysteryOptions import MysteryOptions 6 | 7 | 8 | def get_random_mystery(mystery_option: int, random) -> MysteryOptions: 9 | mystery_settings = MysteryOptions() 10 | 11 | # If Random Choice is on, then none of the other settings matter 12 | if mystery_option == MysteryShuffle.option_Random_Per_Use: 13 | return mystery_settings 14 | 15 | if mystery_option == MysteryShuffle.option_Random_Pick: 16 | # Set possible items the same as Random Choice (see Item_Mystery.bpat) 17 | possible_items = [] 18 | chosen_items = [] 19 | 20 | mystery_itemid = item_table["Mystery"][2] 21 | 22 | possible_items.extend(4 * [item_table["Mushroom"][2]]) 23 | possible_items.extend(2 * [item_table["Super Shroom"][2]]) 24 | possible_items.extend(1 * [item_table["Ultra Shroom"][2]]) 25 | possible_items.extend(4 * [item_table["Honey Syrup"][2]]) 26 | possible_items.extend(2 * [item_table["Maple Syrup"][2]]) 27 | possible_items.extend(1 * [item_table["Jammin Jelly"][2]]) 28 | possible_items.extend(2 * [item_table["POW Block"][2]]) 29 | possible_items.extend(2 * [item_table["Fire Flower"][2]]) 30 | possible_items.extend(2 * [item_table["Snowman Doll"][2]]) 31 | possible_items.extend(2 * [item_table["Thunder Rage"][2]]) 32 | possible_items.extend(2 * [item_table["Shooting Star"][2]]) 33 | possible_items.extend(2 * [item_table["Pebble"][2]]) 34 | possible_items.extend(2 * [item_table["Coconut"][2]]) 35 | possible_items.extend(2 * [item_table["Thunder Bolt"][2]]) 36 | possible_items.extend(2 * [item_table["Egg Missile"][2]]) 37 | possible_items.extend(2 * [item_table["Sleepy Sheep"][2]]) 38 | possible_items.extend(2 * [item_table["Dizzy Dial"][2]]) 39 | possible_items.extend(2 * [item_table["Stop Watch"][2]]) 40 | possible_items.extend(2 * [item_table["Volt Shroom"][2]]) 41 | possible_items.extend(2 * [item_table["Stone Cap"][2]]) 42 | possible_items.extend(1 * [item_table["Repel Gel"][2]]) 43 | possible_items.extend(4 * [mystery_itemid]) 44 | 45 | # We have 7 item slots to fill, with the first one having a hardcoded 46 | # double-chance of occurring 47 | while len(chosen_items) < 7: 48 | random_item = random.choice(possible_items) 49 | if not (random_item == mystery_itemid and len([x for x in chosen_items if x == mystery_itemid]) >= 4): 50 | chosen_items.append(random_item) 51 | if chosen_items[0] == mystery_itemid: 52 | chosen_items.pop(0) 53 | 54 | mystery_settings.mystery_itemA = chosen_items[0] 55 | mystery_settings.mystery_itemB = chosen_items[1] 56 | mystery_settings.mystery_itemC = chosen_items[2] 57 | mystery_settings.mystery_itemD = chosen_items[3] 58 | mystery_settings.mystery_itemE = chosen_items[4] 59 | mystery_settings.mystery_itemF = chosen_items[5] 60 | mystery_settings.mystery_itemG = chosen_items[6] 61 | 62 | else: 63 | # Set vanilla 64 | mystery_settings.mystery_itemA = item_table["Mushroom"][2] 65 | mystery_settings.mystery_itemB = item_table["Super Shroom"][2] 66 | mystery_settings.mystery_itemC = item_table["Fire Flower"][2] 67 | mystery_settings.mystery_itemD = item_table["Stone Cap"][2] 68 | mystery_settings.mystery_itemE = item_table["Dizzy Dial"][2] 69 | mystery_settings.mystery_itemF = item_table["Thunder Rage"][2] 70 | mystery_settings.mystery_itemG = item_table["Pebble"][2] 71 | 72 | return mystery_settings 73 | -------------------------------------------------------------------------------- /data/regions/shooting_star_summit_no_star_way.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "SSS Shooting Star Path", 4 | "area_id": "5", 5 | "map_id": "0", 6 | "map_name": "Shooting Star Path", 7 | "locations": { 8 | "SSS Shooting Star Path Hidden Panel": "can_flip_panels" 9 | }, 10 | "exits": { 11 | "PCG Ruined Castle Grounds": "True", 12 | "SSS Merluvlee's House": "True", 13 | "SSS Shooting Star Path East Upper Exit": "can_climb_steps" 14 | } 15 | }, 16 | { 17 | "region_name": "SSS Shooting Star Path East Upper Exit", 18 | "area_id": "5", 19 | "map_id": "0", 20 | "map_name": "Shooting Star Path", 21 | "exits": { 22 | "SSS Shooting Star Summit": "True", 23 | "SSS Shooting Star Path": "True" 24 | } 25 | }, 26 | { 27 | "region_name": "SSS Shooting Star Summit Base", 28 | "area_id": "5", 29 | "map_id": "1", 30 | "map_name": "Shooting Star Summit", 31 | "locations": { 32 | "SSS Shooting Star Summit Behind The Summit": "True" 33 | }, 34 | "exits": { 35 | "SSS Shooting Star Path East Upper Exit": "True", 36 | "SSS Shooting Star Summit": "can_climb_steps" 37 | } 38 | }, 39 | { 40 | "region_name": "SSS Shooting Star Summit", 41 | "area_id": "5", 42 | "map_id": "1", 43 | "map_name": "Shooting Star Summit", 44 | "events": { 45 | "STARROD": "can_reach_star_way and star_way_goal" 46 | }, 47 | "locations": { 48 | "SSS Shooting Star Summit Hidden Panel": "can_flip_panels and can_climb_steps" 49 | }, 50 | "exits": { 51 | "SSS Shooting Star Summit Base": "True" 52 | } 53 | }, 54 | { 55 | "region_name": "SSS Merluvlee's House", 56 | "area_id": "5", 57 | "map_id": "6", 58 | "map_name": "Merluvlee's House", 59 | "events": { 60 | "GF_HOS06_MerluvleeRequestedCrystalBall": "'FAVOR_3_03_active'" 61 | }, 62 | "locations": { 63 | "SSS Merluvlee's House Hidden Panel": "can_flip_panels", 64 | "SSS Merluvlee's House Merluvlee Koopa Koot Favor": "'FAVOR_3_03_active' and Crystal_Ball", 65 | "SSS Merluvlee's House Merlow Letter Reward": "can_use_ability_parakarry and Letter_to_Merlow", 66 | "SSS Merluvlee's House Merlow's Badges 1": "(Star_Piece, 30) and can_climb_steps", 67 | "SSS Merluvlee's House Merlow's Badges 2": "(Star_Piece, 30) and can_climb_steps", 68 | "SSS Merluvlee's House Merlow's Badges 3": "(Star_Piece, 30) and can_climb_steps", 69 | "SSS Merluvlee's House Merlow's Badges 4": "(Star_Piece, 30) and can_climb_steps", 70 | "SSS Merluvlee's House Merlow's Badges 5": "(Star_Piece, 30) and can_climb_steps", 71 | "SSS Merluvlee's House Merlow's Badges 6": "(Star_Piece, 30) and can_climb_steps", 72 | "SSS Merluvlee's House Merlow's Badges 7": "(Star_Piece, 30) and can_climb_steps", 73 | "SSS Merluvlee's House Merlow's Badges 8": "(Star_Piece, 30) and can_climb_steps", 74 | "SSS Merluvlee's House Merlow's Badges 9": "(Star_Piece, 30) and can_climb_steps", 75 | "SSS Merluvlee's House Merlow's Badges 10": "(Star_Piece, 30) and can_climb_steps", 76 | "SSS Merluvlee's House Merlow's Badges 11": "(Star_Piece, 30) and can_climb_steps", 77 | "SSS Merluvlee's House Merlow's Badges 12": "(Star_Piece, 30) and can_climb_steps", 78 | "SSS Merluvlee's House Merlow's Badges 13": "(Star_Piece, 30) and can_climb_steps", 79 | "SSS Merluvlee's House Merlow's Badges 14": "(Star_Piece, 30) and can_climb_steps", 80 | "SSS Merluvlee's House Merlow's Badges 15": "(Star_Piece, 30) and can_climb_steps", 81 | "SSS Merluvlee's House Merlow's Rewards 1": "(Star_Piece, 10) and can_climb_steps", 82 | "SSS Merluvlee's House Merlow's Rewards 2": "(Star_Piece, 23) and can_climb_steps", 83 | "SSS Merluvlee's House Merlow's Rewards 3": "(Star_Piece, 35) and can_climb_steps", 84 | "SSS Merluvlee's House Merlow's Rewards 4": "(Star_Piece, 47) and can_climb_steps", 85 | "SSS Merluvlee's House Merlow's Rewards 5": "(Star_Piece, 57) and can_climb_steps", 86 | "SSS Merluvlee's House Merlow's Rewards 6": "(Star_Piece, 68) and can_climb_steps" 87 | }, 88 | "exits": { 89 | "SSS Shooting Star Path": "True" 90 | } 91 | } 92 | ] 93 | -------------------------------------------------------------------------------- /data/regions/gusty_gulch.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "GG Wasteland Ascent 1 West", 4 | "area_id": "14", 5 | "map_id": "0", 6 | "map_name": "Wasteland Ascent 1", 7 | "locations": { 8 | "GG Wasteland Ascent 1 On Rock": "can_use_ability_kooper", 9 | "GG Wasteland Ascent 1 Yellow Block 1": "can_hit_floating_blocks" 10 | }, 11 | "exits": { 12 | "GG Ghost Town 2": "True", 13 | "GG Wasteland Ascent 1 East": "can_climb_steps" 14 | } 15 | }, 16 | { 17 | "region_name": "GG Wasteland Ascent 1 East", 18 | "area_id": "14", 19 | "map_id": "0", 20 | "map_name": "Wasteland Ascent 1", 21 | "locations": { 22 | "GG Wasteland Ascent 1 Infront Of Branch": "True", 23 | "GG Wasteland Ascent 1 Yellow Block 2": "can_hit_floating_blocks", 24 | "GG Wasteland Ascent 1 Yellow Block Right": "can_hit_floating_blocks" 25 | }, 26 | "exits": { 27 | "GG Wasteland Ascent 2 West": "True", 28 | "GG Wasteland Ascent 1 West": "True" 29 | } 30 | }, 31 | { 32 | "region_name": "GG Ghost Town 1", 33 | "area_id": "14", 34 | "map_id": "1", 35 | "map_name": "Ghost Town 1", 36 | "locations": { 37 | "GG Ghost Town 1 From Boo (Koopa Koot Favor)": "'FAVOR_7_01_active'", 38 | "GG Ghost Town 1 Yellow Block In House": "can_hit_floating_blocks" 39 | }, 40 | "exits": { 41 | "GG Windmill Exterior": "True", 42 | "GG Ghost Town 2": "True" 43 | } 44 | }, 45 | { 46 | "region_name": "GG Wasteland Ascent 2 West", 47 | "area_id": "14", 48 | "map_id": "2", 49 | "map_name": "Wasteland Ascent 2", 50 | "exits": { 51 | "GG Wasteland Ascent 1 East": "True", 52 | "GG Wasteland Ascent 2 East": "can_use_ability_parakarry" 53 | } 54 | }, 55 | { 56 | "region_name": "GG Wasteland Ascent 2 East", 57 | "area_id": "14", 58 | "map_id": "2", 59 | "map_name": "Wasteland Ascent 2", 60 | "locations": { 61 | "GG Wasteland Ascent 2 Behind Rock": "True", 62 | "GG Wasteland Ascent 2 Yellow Block Left": "can_hit_floating_blocks", 63 | "GG Wasteland Ascent 2 Yellow Block Right": "can_hit_floating_blocks", 64 | "GG Wasteland Ascent 2 In MultiCoinBlock": "can_hit_floating_blocks" 65 | }, 66 | "exits": { 67 | "TC Outside Tubbas Castle": "True", 68 | "GG Wasteland Ascent 2 West": "True" 69 | } 70 | }, 71 | { 72 | "region_name": "GG Ghost Town 2", 73 | "area_id": "14", 74 | "map_id": "3", 75 | "map_name": "Ghost Town 2", 76 | "exits": { 77 | "GG Ghost Town 1": "True", 78 | "GG Wasteland Ascent 1 West": "True" 79 | } 80 | }, 81 | { 82 | "region_name": "GG Windmill Exterior", 83 | "area_id": "14", 84 | "map_id": "4", 85 | "map_name": "Windmill Exterior", 86 | "events": { 87 | "STARSPIRIT_3": "'RF_Ch3_HeartFledFirstTunnel'", 88 | "STARSPIRIT": "'RF_Ch3_HeartFledFirstTunnel'" 89 | }, 90 | "exits": { 91 | "GG Ghost Town 1": "True", 92 | "FOR Exit to Gusty Gulch East": "True", 93 | "GG Windmill Interior": "'MysticalKey'" 94 | } 95 | }, 96 | { 97 | "region_name": "GG Windmill Interior", 98 | "area_id": "14", 99 | "map_id": "5", 100 | "map_name": "Windmill Interior", 101 | "exits": { 102 | "GG Windmill Exterior": "True", 103 | "GG Windmill Tunnel Entry": "Super_Boots" 104 | } 105 | }, 106 | { 107 | "region_name": "GG Windmill Tunnel Entry", 108 | "area_id": "14", 109 | "map_id": "6", 110 | "map_name": "Windmill Tunnel Entry", 111 | "exits": { 112 | "GG Tunnel 1": "True", 113 | "GG Windmill Interior": "can_climb_steps" 114 | } 115 | }, 116 | { 117 | "region_name": "GG Tunnel 1", 118 | "area_id": "14", 119 | "map_id": "7", 120 | "map_name": "Tunnel 1", 121 | "exits": { 122 | "GG Windmill Tunnel Entry": "True", 123 | "GG Tunnel 2": "True" 124 | } 125 | }, 126 | { 127 | "region_name": "GG Tubba's Heart Chamber", 128 | "area_id": "14", 129 | "map_id": "8", 130 | "map_name": "Tubba's Heart Chamber", 131 | "events": { 132 | "RF_Ch3_HeartFledFirstTunnel": "True" 133 | }, 134 | "exits": { 135 | "GG Tunnel 3": "True" 136 | } 137 | }, 138 | { 139 | "region_name": "GG Tunnel 2", 140 | "area_id": "14", 141 | "map_id": "9", 142 | "map_name": "Tunnel 2", 143 | "exits": { 144 | "GG Tunnel 1": "True", 145 | "GG Tunnel 3": "True" 146 | } 147 | }, 148 | { 149 | "region_name": "GG Tunnel 3", 150 | "area_id": "14", 151 | "map_id": "10", 152 | "map_name": "Tunnel 3", 153 | "exits": { 154 | "GG Tunnel 2": "True", 155 | "GG Tubba's Heart Chamber": "True" 156 | } 157 | } 158 | ] 159 | -------------------------------------------------------------------------------- /modules/random_quizzes.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/rando_modules/random_quizzes.py 2 | """ 3 | This module is used for modifying the questions asked by Chuck Quizmo in 4 | various areas of the game. 5 | """ 6 | 7 | 8 | def get_randomized_quizzes(random) -> list: 9 | """ 10 | Returns a list of tuples where the first value holds the dbkey for a quiz 11 | question and the second value holds the shuffled quiz question index. Also 12 | includes key/value pairs for the number of questions per area. 13 | """ 14 | quiz_list = [] 15 | db_keys = [] 16 | db_values = [] 17 | 18 | for name, data in quiz_table.items(): 19 | db_keys.append(get_key(name)) 20 | db_values.append(data[4]) 21 | 22 | random.shuffle(db_values) 23 | 24 | for db_key, db_value in zip(db_keys, db_values): 25 | quiz_list.append((db_key, db_value)) 26 | 27 | return quiz_list 28 | 29 | 30 | # get the address for the given option using its key type (0xAF), area, map, and index 31 | def get_key(quiz_name): 32 | data = quiz_table[quiz_name] 33 | return (0xAF << 24) | (data[1] << 16) | (data[2] << 8) | data[3] 34 | 35 | 36 | # Name id area map index default val 37 | quiz_table = { 38 | "MAC_00": (1, 3, 0, 0, 7), 39 | "MAC_01": (2, 3, 0, 1, 10), 40 | "MAC_02": (3, 3, 0, 2, 30), 41 | "MAC_03": (4, 3, 0, 3, 22), 42 | "MAC_04": (5, 3, 0, 4, 31), 43 | "MAC_05": (6, 3, 0, 5, 42), 44 | "MAC_06": (7, 3, 0, 6, 35), 45 | "MAC_07": (8, 3, 0, 7, 57), 46 | "MAC_08": (9, 3, 0, 8, 52), 47 | "MAC_09": (10, 3, 0, 9, 48), 48 | "MAC_0A": (11, 3, 0, 10, 41), 49 | "MAC_0B": (12, 3, 0, 11, 37), 50 | "MAC_0C": (13, 3, 0, 12, 25), 51 | "MAC_0D": (14, 3, 0, 13, 53), 52 | "MAC_0E": (15, 3, 0, 14, 51), 53 | "MAC_0F": (16, 3, 0, 15, 17), 54 | "KMR_00": (17, 3, 1, 0, 4), 55 | "KMR_01": (18, 3, 1, 1, 8), 56 | "KMR_02": (19, 3, 1, 2, 2), 57 | "KMR_03": (20, 3, 1, 3, 5), 58 | "KMR_04": (21, 3, 1, 4, 1), 59 | "KMR_05": (22, 3, 1, 5, 6), 60 | "KMR_06": (23, 3, 1, 6, 0), 61 | "KMR_07": (24, 3, 1, 7, 3), 62 | "NOK_00": (25, 3, 2, 0, 11), 63 | "NOK_01": (26, 3, 2, 1, 12), 64 | "NOK_02": (27, 3, 2, 2, 13), 65 | "NOK_03": (28, 3, 2, 3, 18), 66 | "NOK_04": (29, 3, 2, 4, 20), 67 | "NOK_05": (30, 3, 2, 5, 24), 68 | "NOK_06": (31, 3, 2, 6, 19), 69 | "NOK_07": (32, 3, 2, 7, 15), 70 | "DRO_00": (33, 3, 3, 0, 21), 71 | "DRO_01": (34, 3, 3, 1, 26), 72 | "DRO_02": (35, 3, 3, 2, 27), 73 | "DRO_03": (36, 3, 3, 3, 29), 74 | "DRO_04": (37, 3, 3, 4, 56), 75 | "DRO_05": (38, 3, 3, 5, 33), 76 | "DRO_06": (39, 3, 3, 6, 34), 77 | "DRO_07": (40, 3, 3, 7, 39), 78 | "JAN_00": (41, 3, 4, 0, 46), 79 | "JAN_01": (42, 3, 4, 1, 45), 80 | "JAN_02": (43, 3, 4, 2, 43), 81 | "JAN_03": (44, 3, 4, 3, 50), 82 | "JAN_04": (45, 3, 4, 4, 47), 83 | "JAN_05": (46, 3, 4, 5, 49), 84 | "JAN_06": (47, 3, 4, 6, 58), 85 | "JAN_07": (48, 3, 4, 7, 44), 86 | "SAM_00": (49, 3, 5, 0, 16), 87 | "SAM_01": (50, 3, 5, 1, 59), 88 | "SAM_02": (51, 3, 5, 2, 23), 89 | "SAM_03": (52, 3, 5, 3, 32), 90 | "SAM_04": (53, 3, 5, 4, 40), 91 | "SAM_05": (54, 3, 5, 5, 36), 92 | "SAM_06": (55, 3, 5, 6, 38), 93 | "SAM_07": (56, 3, 5, 7, 9), 94 | "HOS_00": (57, 3, 6, 0, 62), 95 | "HOS_01": (58, 3, 6, 1, 28), 96 | "HOS_02": (59, 3, 6, 2, 54), 97 | "HOS_03": (60, 3, 6, 3, 60), 98 | "HOS_04": (61, 3, 6, 4, 14), 99 | "HOS_05": (62, 3, 6, 5, 61), 100 | "HOS_06": (63, 3, 6, 6, 55), 101 | "HOS_07": (64, 3, 6, 7, 63), 102 | 103 | } 104 | -------------------------------------------------------------------------------- /data/regions/peachs_castle.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "PC Entry Hall (1F)", 4 | "area_id": "4", 5 | "map_id": "4", 6 | "map_name": "Entry Hall (1F)", 7 | "exits": { 8 | "PCG Hijacked Castle Entrance": "True", 9 | "PC Upper Hall (2F)": "True", 10 | "PC Inactive Quiz-Off (1F)": "True", 11 | "PC Kitchen (1F)": "True", 12 | "PC Guest Room (1F)": "True" 13 | } 14 | }, 15 | { 16 | "region_name": "PC Upper Hall (2F)", 17 | "area_id": "4", 18 | "map_id": "5", 19 | "map_name": "Upper Hall (2F)", 20 | "exits": { 21 | "PC Entry Hall (1F)": "True", 22 | "PC Stairs Hallway (3F)": "True", 23 | "PC Library (2F)": "True", 24 | "PC Passage Outlet (2F)": "True", 25 | "PC Peach's Room (2F)": "True", 26 | "PC Storeroom (2F)": "True", 27 | "PC Dining Room (2F)": "True" 28 | } 29 | }, 30 | { 31 | "region_name": "PC Stairs Hallway (3F)", 32 | "area_id": "4", 33 | "map_id": "6", 34 | "map_name": "Stairs Hallway (3F)", 35 | "exits": { 36 | "PC Upper Hall (2F)": "True", 37 | "PC Window Hallway (4F)": "True" 38 | } 39 | }, 40 | { 41 | "region_name": "PC Window Hallway (4F)", 42 | "area_id": "4", 43 | "map_id": "7", 44 | "map_name": "Window Hallway (4F)", 45 | "exits": { 46 | "PC Stairs Hallway (3F)": "Star_Beam", 47 | "PC Double Staircase (4F)": "Star_Beam" 48 | } 49 | }, 50 | { 51 | "region_name": "PC Peach's Room (2F)", 52 | "area_id": "4", 53 | "map_id": "8", 54 | "map_name": "Peach's Room (2F)", 55 | "exits": { 56 | "PC Upper Hall (2F)": "True", 57 | "PC Balcony (2F)": "True" 58 | } 59 | }, 60 | { 61 | "region_name": "PC Passage Outlet (2F)", 62 | "area_id": "4", 63 | "map_id": "9", 64 | "map_name": "Passage Outlet (2F)", 65 | "exits": { 66 | "PC Upper Hall (2F)": "True" 67 | } 68 | }, 69 | { 70 | "region_name": "PC Library (2F)", 71 | "area_id": "4", 72 | "map_id": "10", 73 | "map_name": "Library (2F)", 74 | "locations": { 75 | "PC Library (2F) Upper Level": "can_climb_steps", 76 | "PC Library (2F) Between Bookshelves": "True" 77 | }, 78 | "exits": { 79 | "PC Upper Hall (2F)": "True" 80 | } 81 | }, 82 | { 83 | "region_name": "PC Storeroom (2F)", 84 | "area_id": "4", 85 | "map_id": "11", 86 | "map_name": "Storeroom (2F)", 87 | "locations": { 88 | "PC Storeroom (2F) On The Ground": "True" 89 | }, 90 | "exits": { 91 | "PC Upper Hall (2F)": "True" 92 | } 93 | }, 94 | { 95 | "region_name": "PC Dining Room (2F)", 96 | "area_id": "4", 97 | "map_id": "12", 98 | "map_name": "Dining Room (2F)", 99 | "exits": { 100 | "PC Upper Hall (2F)": "True" 101 | } 102 | }, 103 | { 104 | "region_name": "PC Kitchen (1F)", 105 | "area_id": "4", 106 | "map_id": "13", 107 | "map_name": "Kitchen (1F)", 108 | "exits": { 109 | "PC Entry Hall (1F)": "True" 110 | } 111 | }, 112 | { 113 | "region_name": "PC Guest Room (1F)", 114 | "area_id": "4", 115 | "map_id": "14", 116 | "map_name": "Guest Room (1F)", 117 | "locations": { 118 | "PC Guest Room (1F) In Chest": "True" 119 | }, 120 | "exits": { 121 | "PC Entry Hall (1F)": "True" 122 | } 123 | }, 124 | { 125 | "region_name": "PC Inactive Quiz-Off (1F)", 126 | "area_id": "4", 127 | "map_id": "15", 128 | "map_name": "Inactive Quiz-Off (1F)", 129 | "exits": { 130 | "PC Entry Hall (1F)": "True" 131 | } 132 | }, 133 | { 134 | "region_name": "PC Double Staircase (4F)", 135 | "area_id": "4", 136 | "map_id": "16", 137 | "map_name": "Double Staircase (4F)", 138 | "exits": { 139 | "PC Window Hallway (4F)": "True", 140 | "PC Rooftop (5F)": "True" 141 | } 142 | }, 143 | { 144 | "region_name": "PC Rooftop (5F)", 145 | "area_id": "4", 146 | "map_id": "17", 147 | "map_name": "Rooftop (5F)", 148 | "exits": { 149 | "PC Double Staircase (4F)": "True", 150 | "PC Tower Staircase (5F)": "True" 151 | } 152 | }, 153 | { 154 | "region_name": "PC Tower Staircase (5F)", 155 | "area_id": "4", 156 | "map_id": "18", 157 | "map_name": "Tower Staircase (5F)", 158 | "exits": { 159 | "PC Rooftop (5F)": "True", 160 | "PC Final Boss Arena (6F)": "True" 161 | } 162 | }, 163 | { 164 | "region_name": "PC Final Boss Arena (6F)", 165 | "area_id": "4", 166 | "map_id": "19", 167 | "map_name": "Final Boss Arena (6F)", 168 | "events": { 169 | "STARROD": "Star_Beam" 170 | }, 171 | "exits": { 172 | "PC Tower Staircase (5F)": "True" 173 | } 174 | }, 175 | { 176 | "region_name": "PC Balcony (2F)", 177 | "area_id": "4", 178 | "map_id": "20", 179 | "map_name": "Balcony (2F)", 180 | "exits": { 181 | "PC Peach's Room (2F)": "True" 182 | } 183 | } 184 | ] 185 | -------------------------------------------------------------------------------- /docs/setup_en.md: -------------------------------------------------------------------------------- 1 | # AP World Setup 2 | ## Required Software 3 | 4 | - [Archipelago](https://github.com/ArchipelagoMW/Archipelago/releases) v0.5.1 or higher. Make sure to install the 5 | Generator. You will not be able to generate games with Paper Mario on the Archipelago site, only locally. 6 | - [The Paper Mario AP World](https://github.com/JKBSunshine/PMR_APWorld/tree/main). To download it, go to the [latest release](https://github.com/JKBSunshine/PMR_APWorld/releases) and download the papermario.apworld file. 7 | - A legally obtained US 1.0 Paper Mario ROM. 8 | - [BizHawk](https://tasvideos.org/BizHawk/ReleaseHistory) 2.7 to 2.9.1; **2.10's release does not work**, although dev builds of 2.10 have fixed the issue. 9 | 10 | ## Add the Paper Mario AP World to your Archipelago install 11 | As of Archipelago v0.5.0, you can double click .apworld files to add apworlds to your Archipelago installation. 12 | 13 | ## Creating a YAML file 14 | 15 | ### Option A: Generate a template YAML using Archipelago and edit it. 16 | Want the typical Archipelago experience? Run ArchipelagoLauncher.exe (found in the base Archipelago folder) and click 17 | Generate Template Settings to generate a template YAML file. It will open the folder of template files which you can 18 | then edit to your liking. 19 | 20 | ### Option B: Use the PM Sample.yaml file and edit it to your liking. 21 | Want a YAML that looks like what you'd get when exporting it from the site? Grab the PM Sample.yaml file [here.](https://github.com/JKBSunshine/PMR_APWorld/blob/main/docs/PM%20Sample.yaml) 22 | It has all the settings and shows options for each setting as well as what is/isn't implemented. If you want to see 23 | the descriptions that you would see on the site, you can check out [options.py,](https://github.com/JKBSunshine/PMR_APWorld/blob/main/options.py) 24 | or check out option C... 25 | 26 | ### Option C: Use the PMR website to get a setting string 27 | Want a GUI with buttons, sliders, descriptions, and cute styling? Grab the PMR Settings String.yaml file [here.](https://github.com/JKBSunshine/PMR_APWorld/blob/main/docs/PMR%20Settings%20String.yaml) 28 | Visit [the Paper Mario Randomizer site](https://pm64randomizer.com/) and select your settings. When you're 29 | ready, go to the top of the page and click the Export button. This will update the Settings String field with the 30 | settings you've selected. Copy that string and put it in your YAML as the value for `pmr_settings_string`. It'll look 31 | similar to this: 32 | `pmr_settings_string: (iIvejnSpf0Kdl0rbg2u0a6Om)(gb1f1p1s0Rzm2)(pSaRn1x1(pGktpbwsl))(qh3SiPZgc2ELt7DQFVO)(dd1ca1.5m1kshzw4EFlvyp0b)(xq100r0t0xubpl128)(mc150h10f5b3s0j0a0irn4x16Q)(om2btwcrFPs65796o2dz0x?50!70Y)(cm7p20g20k20o20a20b20w20s20l20x2n2e2y2c5trhdu-1j)g)` 33 | 34 | Note that since some settings are not yet implemented fully, not all of them will result in a successful generation. 35 | [PM Sample.YAML](https://github.com/JKBSunshine/PMR_APWorld/blob/main/PM%20Sample.yaml) can be referred to to see what options are and are not implemented, regardless of how you choose 36 | to create your YAML. 37 | 38 | ## Generating a Game 39 | Follow [the general Archipelago instructions](https://archipelago.gg/tutorial/Archipelago/setup/en#generating-a-game) for generating a game, specifically on your local installation. You cannot generate games using the Paper Mario AP World on the website. 40 | 41 | ## Hosting a Game 42 | Follow [the general Archipelago instructions](https://archipelago.gg/tutorial/Archipelago/setup/en#hosting-an-archipelago-server) for hosting an Archipelago server. You _can_ host games that use the Paper Mario AP World on the website, or you can host it locally. 43 | 44 | ## Connecting to an Archipelago Server 45 | 1. Obtain your .appm64 file from whoever is hosting the game. These files will not upload to the website even if it is hosted there, so you will have to send/receive them elsewhere. 46 | 2. Once you have obtained your .appm64 patch file, open up ArchipelagoLauncher.exe from the base Archipelago folder and click "Open Patch". In the prompt that comes up, choose your .appm64 file. If this is your first time opening the patch file, you will be prompted to locate your vanilla ROM. You will also be prompted to locate your BizHawk client, which is named EmuHawk.exe in your BizHawk install. A patched .z64 file will be created in the same place as the patch file. 47 | 3. Once the patch file has been created, BizHawk should start up automatically with the patched ROM. The Generic BizHawk Client for Archipelago will also open, as well as a Lua Console window. At this point all you need to do to connect is enter your room's address and port (e.g. archipelago.gg:38281) into the top text field of the client and click Connect. 48 | 49 | 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. 50 | 51 | Note: After the first time you open an .appm64 file through the Archipelago Launcher, it should associate that file type with the launcher and all you should have to do is double click them. 52 | -------------------------------------------------------------------------------- /modules/random_shop_prices.py: -------------------------------------------------------------------------------- 1 | # modified slightly from https://github.com/icebound777/PMR-SeedGenerator/blob/main/rando_modules/random_shop_prices.py 2 | 3 | from .. import PMItem 4 | from ..Locations import PMLocation 5 | from ..options import MerlowRewardsPricing 6 | from BaseClasses import Item 7 | 8 | 9 | def get_shop_price(node: PMLocation, 10 | item: PMItem, 11 | do_randomize_shops: bool, 12 | merlow_cost_setting: int, 13 | total_power_stars: int, 14 | random) -> int: 15 | """ 16 | Return the price for an item for offer within a shop (regular or Merlow's). 17 | Merlow gets special pricing rules as he deals in star pieces; 0 for cheap, 1 for vanilla. 18 | """ 19 | buy_price = 0 20 | 21 | if node.identifier.startswith("HOS_06") and "Shop" in node.identifier: 22 | # Merlow's shop 23 | if "ShopReward" in node.identifier: 24 | # Merlow's trade rewards 25 | # If Merlow is set to "cheap", then only adjust the pricing, but 26 | # not the actual star pieces required in logic. This makes it so 27 | # even if an item is placed in an expensive reward slot, 28 | # gathering the required star pieces becomes much easier 29 | # overall. 30 | starpiece_increments = 10 31 | if merlow_cost_setting == MerlowRewardsPricing.option_Cheap: 32 | starpiece_increments = 5 33 | 34 | if "ShopRewardA" in node.identifier: 35 | buy_price = starpiece_increments * 1 36 | elif "ShopRewardB" in node.identifier: 37 | buy_price = starpiece_increments * 2 38 | elif "ShopRewardC" in node.identifier: 39 | buy_price = starpiece_increments * 3 40 | elif "ShopRewardD" in node.identifier: 41 | buy_price = starpiece_increments * 4 42 | elif "ShopRewardE" in node.identifier: 43 | buy_price = starpiece_increments * 5 44 | else: 45 | buy_price = starpiece_increments * 6 46 | else: 47 | # Merlow's StarPiece trade (for a total cost of all 112 StarPieces) 48 | if any(True for i in ["ShopBadgeA", "ShopBadgeB"] if i in node.identifier): 49 | buy_price = 1 50 | elif any(True for i in ["ShopBadgeC", "ShopBadgeD"] if i in node.identifier): 51 | buy_price = 2 52 | elif any(True for i in ["ShopBadgeE", "ShopBadgeF"] if i in node.identifier): 53 | buy_price = 4 54 | elif any(True for i in ["ShopBadgeG", "ShopBadgeH"] if i in node.identifier): 55 | buy_price = 6 56 | elif any(True for i in ["ShopBadgeI", "ShopBadgeJ"] if i in node.identifier): 57 | buy_price = 8 58 | elif any(True for i in ["ShopBadgeK", "ShopBadgeL"] if i in node.identifier): 59 | buy_price = 10 60 | elif any(True for i in ["ShopBadgeM", "ShopBadgeN"] if i in node.identifier): 61 | buy_price = 15 62 | elif "ShopBadgeO" in node.identifier: 63 | buy_price = 20 64 | else: 65 | # Regular Shop 66 | item_type = item.type 67 | 68 | if do_randomize_shops: 69 | if item_type == "ITEM": 70 | sell_price = item.base_price 71 | buy_price = round(sell_price * 1.5) 72 | 73 | # Randomly adjust price a bit 74 | rnd_factor = random.choice([0.75, 0.9, 1, 1.1, 1.25]) 75 | 76 | buy_price = round(buy_price * rnd_factor) 77 | 78 | if buy_price == 0: 79 | buy_price = 1 80 | 81 | # If below 5, let value stay, else round to nearest 5 82 | if (buy_price - (buy_price % 5)) != 0: 83 | buy_price = round(buy_price / 5) * 5 84 | 85 | elif item_type in ["BADGE", "KEYITEM", "PARTNER", "GEAR", "PARTNERUPGRADE", "OTHER"]: 86 | buy_price = random.choice([10, 15, 20, 25, 30]) 87 | 88 | elif item_type == "POWERSTAR": 89 | if total_power_stars <= 40: 90 | buy_price = random.choice([10, 15, 20, 25, 30]) 91 | elif total_power_stars <= 80: 92 | buy_price = random.choice([5, 10, 15, 20]) 93 | else: 94 | buy_price = random.choice([5, 10]) 95 | 96 | elif item.name == "Coin Bag": 97 | buy_price = 10 98 | 99 | elif item_type == "COIN": 100 | buy_price = 1 101 | 102 | elif item_type == "STARPIECE": 103 | buy_price = random.choice([2, 4, 6, 8, 10]) 104 | 105 | elif item_type == "STARPOWER": 106 | buy_price = random.choice([30, 35, 40, 45, 50]) 107 | 108 | else: 109 | buy_price = 35 110 | else: 111 | buy_price = node.vanilla_price 112 | 113 | # print(f"Set price {node_id}({node.current_item}): {buy_price}") 114 | 115 | return buy_price 116 | -------------------------------------------------------------------------------- /data/regions/forever_forest.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "FOR Flower Sounds", 4 | "area_id": "12", 5 | "map_id": "0", 6 | "map_name": "Flower Sounds", 7 | "exits": { 8 | "FOR Exit to Toad Town": "True", 9 | "FOR Stump Eyes Entrance": "True" 10 | } 11 | }, 12 | { 13 | "region_name": "FOR Stump Eyes", 14 | "area_id": "12", 15 | "map_id": "1", 16 | "map_name": "Stump Eyes", 17 | "events": { 18 | "RF_ForestPass": "True" 19 | }, 20 | "exits": { 21 | "FOR Flowers (Oaklie)": "True", 22 | "FOR Stump Eyes Entrance": "True", 23 | "FOR Flower Sounds": "True" 24 | } 25 | }, 26 | { 27 | "region_name": "FOR Stump Eyes Entrance", 28 | "area_id": "12", 29 | "map_id": "1", 30 | "map_name": "Stump Eyes", 31 | "exits": { 32 | "FOR Flower Sounds": "True", 33 | "FOR Stump Eyes": "Forest_Pass or open_forest" 34 | } 35 | }, 36 | { 37 | "region_name": "FOR Flowers (Oaklie)", 38 | "area_id": "12", 39 | "map_id": "2", 40 | "map_name": "Flowers (Oaklie)", 41 | "exits": { 42 | "FOR Flower Sounds": "True", 43 | "FOR Tree Face (Bub-ulb)": "True", 44 | "FOR Stump Eyes": "True" 45 | } 46 | }, 47 | { 48 | "region_name": "FOR Tree Face (Bub-ulb)", 49 | "area_id": "12", 50 | "map_id": "3", 51 | "map_name": "Tree Face (Bub-ulb)", 52 | "locations": { 53 | "FOR Tree Face (Bub-ulb) Bub-ulb Gift": "True" 54 | }, 55 | "exits": { 56 | "FOR Mushrooms (Path Splits)": "True", 57 | "FOR Flower Sounds": "True", 58 | "FOR Flowers (Oaklie)": "True" 59 | } 60 | }, 61 | { 62 | "region_name": "FOR Mushrooms (Path Splits)", 63 | "area_id": "12", 64 | "map_id": "4", 65 | "map_name": "Mushrooms (Path Splits)", 66 | "exits": { 67 | "FOR Flower Sounds": "True", 68 | "FOR Flowers Vanish": "True", 69 | "FOR Tree Face (Bub-ulb)": "True", 70 | "FOR Bee Hive (HP Plus)": "True" 71 | } 72 | }, 73 | { 74 | "region_name": "FOR Flowers Vanish", 75 | "area_id": "12", 76 | "map_id": "5", 77 | "map_name": "Flowers Vanish", 78 | "exits": { 79 | "FOR Laughing Rock": "True", 80 | "FOR Flower Sounds": "True", 81 | "FOR Mushrooms (Path Splits)": "True" 82 | } 83 | }, 84 | { 85 | "region_name": "FOR Laughing Rock", 86 | "area_id": "12", 87 | "map_id": "6", 88 | "map_name": "Laughing Rock", 89 | "exits": { 90 | "FOR Flowers Appear (FP Plus)": "True", 91 | "FOR Flower Sounds": "True", 92 | "FOR Flowers Vanish": "True", 93 | "FOR Outside Boo's Mansion": "True" 94 | } 95 | }, 96 | { 97 | "region_name": "FOR Bee Hive (HP Plus)", 98 | "area_id": "12", 99 | "map_id": "7", 100 | "map_name": "Bee Hive (HP Plus)", 101 | "locations": { 102 | "FOR Bee Hive (HP Plus) Central Block": "can_hit_floating_blocks" 103 | }, 104 | "exits": { 105 | "FOR Flower Sounds": "True", 106 | "FOR Mushrooms (Path Splits)": "True" 107 | } 108 | }, 109 | { 110 | "region_name": "FOR Flowers Appear (FP Plus)", 111 | "area_id": "12", 112 | "map_id": "8", 113 | "map_name": "Flowers Appear (FP Plus)", 114 | "locations": { 115 | "FOR Flowers Appear (FP Plus) Central Block": "can_hit_floating_blocks" 116 | }, 117 | "exits": { 118 | "FOR Laughing Rock": "True", 119 | "FOR Flower Sounds": "True" 120 | } 121 | }, 122 | { 123 | "region_name": "FOR Exit to Toad Town", 124 | "area_id": "12", 125 | "map_id": "9", 126 | "map_name": "Exit to Toad Town", 127 | "exits": { 128 | "TT Southern District": "True", 129 | "FOR Flower Sounds": "True" 130 | } 131 | }, 132 | { 133 | "region_name": "FOR Outside Boo's Mansion", 134 | "area_id": "12", 135 | "map_id": "10", 136 | "map_name": "Outside Boo's Mansion", 137 | "locations": { 138 | "FOR Outside Boo's Mansion Yellow Block": "can_hit_floating_blocks", 139 | "FOR Outside Boo's Mansion In Bush (Back Right)": "True" 140 | }, 141 | "exits": { 142 | "FOR Laughing Rock": "Forest_Pass or 'RF_ForestPass'", 143 | "FOR Exit to Gusty Gulch West": "True", 144 | "FOR Outside Boo's Mansion Pipe": "'GF_TIK09_WarpPipe' and Boots", 145 | "FOR Outside Boo's Mansion Steps": "Boots" 146 | } 147 | }, 148 | { 149 | "region_name": "FOR Outside Boo's Mansion Steps", 150 | "area_id": "12", 151 | "map_id": "10", 152 | "map_name": "Outside Boo's Mansion", 153 | "exits": { 154 | "FOR Outside Boo's Mansion": "True", 155 | "BM Foyer 1F": "True" 156 | } 157 | }, 158 | { 159 | "region_name": "FOR Outside Boo's Mansion Pipe", 160 | "area_id": "12", 161 | "map_id": "10", 162 | "map_name": "Outside Boo's Mansion", 163 | "exits": { 164 | "TTT Warp Zone 2 (B2) Pipe": "can_reenter_vertical_pipes", 165 | "FOR Outside Boo's Mansion": "True" 166 | } 167 | }, 168 | { 169 | "region_name": "FOR Exit to Gusty Gulch West", 170 | "area_id": "12", 171 | "map_id": "11", 172 | "map_name": "Exit to Gusty Gulch", 173 | "exits": { 174 | "FOR Outside Boo's Mansion": "True", 175 | "FOR Exit to Gusty Gulch East": "'RF_OpenedGustyGulch'" 176 | } 177 | }, 178 | { 179 | "region_name": "FOR Exit to Gusty Gulch East", 180 | "area_id": "12", 181 | "map_id": "11", 182 | "map_name": "Exit to Gusty Gulch", 183 | "locations": { 184 | "FOR Exit to Gusty Gulch Hidden Panel": "can_flip_panels" 185 | }, 186 | "exits": { 187 | "GG Windmill Exterior": "True", 188 | "FOR Exit to Gusty Gulch West": "'RF_OpenedGustyGulch'" 189 | } 190 | } 191 | ] 192 | -------------------------------------------------------------------------------- /modules/random_movecosts.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/rando_modules/random_movecosts.py 2 | # modified to work with MoveList.py instead of the db 3 | """ 4 | This module is used for modifying BP costs of badges, FP costs of both badge 5 | and partner moves and SP costs for star power moves. 6 | """ 7 | 8 | from ..data.enum_options import RandomMoveCosts 9 | from ..data.MoveList import move_table 10 | 11 | value_limits = { 12 | "BADGE": {"BP": {"min": 1, "max": 8, }, "FP": {"min": 1, "max": 7, }}, 13 | "PARTNER": {"FP": {"min": 1, "max": 8, }}, 14 | "STARPOWER": {"FP": {"min": 1, "max": 3, }} 15 | } 16 | 17 | 18 | # gets the data address? for a move, from https://github.com/icebound777/PMR-SeedGenerator/blob/main/db/move.py 19 | def get_key(data): 20 | return (0xA6 << 24) | (data[4] << 16) | (data[5] << 8) | data[6] 21 | 22 | 23 | def _get_shuffled_costs(movetype, costtype, random): 24 | """ 25 | Returns a list of tuples where the first value holds the dbkey for a move 26 | cost and the second value holds the shuffled cost depending on which 27 | move type and cost type are given as parameters. 28 | """ 29 | shuffled_costs = [] 30 | db_keys = [] 31 | db_values = [] 32 | 33 | for id, data in move_table.items(): 34 | if data[1] == movetype and data[2] == costtype: 35 | db_keys.append(get_key(data)) 36 | db_values.append(data[3]) 37 | 38 | random.shuffle(db_values) 39 | 40 | for db_key, db_value in zip(db_keys, db_values): 41 | shuffled_costs.append((db_key, db_value)) 42 | 43 | return shuffled_costs 44 | 45 | 46 | def _get_balanced_random_costs(movetype: str, costtype: str, random) -> list: 47 | """ 48 | Returns a list of tuples where the first value holds the dbkey for a badge 49 | BP cost and the second value holds its randomized BP cost. 50 | """ 51 | random_costs = [] 52 | 53 | min_value = value_limits.get(movetype).get(costtype).get("min") 54 | max_value = value_limits.get(movetype).get(costtype).get("max") 55 | 56 | for id, data in move_table.items(): 57 | if data[1] == movetype and data[2] == costtype: 58 | default_cost = data[3] 59 | 60 | # 10% Chance to pick randomly between 1 and 8, else randomly choose 61 | # from -2 to +2, clamping to 1-8 62 | if random.randint(1, 10) == 10: 63 | new_cost = random.randint(min_value, max_value) 64 | else: 65 | if movetype == "STARPOWER": 66 | new_cost = default_cost + random.choice([-1, 0, 1]) 67 | else: 68 | new_cost = default_cost + random.choice([-2, -1, 0, 1, 2]) 69 | 70 | if new_cost < min_value: 71 | new_cost = min_value 72 | if new_cost > max_value: 73 | # Flower Fanatic is the only badge allowed to scale to 9 74 | # since it's the only badge with that high of a basic cost 75 | if data[0] == "FlowerFanatic": 76 | new_cost = 9 77 | else: 78 | new_cost = max_value 79 | 80 | random_costs.append((get_key(data), new_cost)) 81 | 82 | return random_costs 83 | 84 | 85 | def _get_fully_random_costs(movetype: str, costtype: str, random) -> list: 86 | """ 87 | Returns a list of tuples where the first value holds the dbkey for a cost 88 | and the second value holds its fully randomized cost value. 89 | """ 90 | fully_random_costs = [] 91 | 92 | min_value = value_limits.get(movetype).get(costtype).get("min") 93 | if (movetype, costtype) == ("BADGE", "BP"): 94 | # Special case for bp costs: 1-8 simply has too high of a median 95 | max_value = 6 96 | else: 97 | max_value = value_limits.get(movetype).get(costtype).get("max") 98 | 99 | for id, data in move_table.items(): 100 | if data[1] == movetype and data[2] == costtype: 101 | new_cost = random.randint(min_value, max_value) 102 | fully_random_costs.append((get_key(data), new_cost)) 103 | # print(f"{move.move_name}: {move.cost_value} -> {new_cost}") 104 | 105 | return fully_random_costs 106 | 107 | 108 | def get_randomized_moves( 109 | badges_bp_setting: int, 110 | badges_fp_setting: int, 111 | partner_fp_setting: int, 112 | starpower_setting: int, 113 | random 114 | ): 115 | """ 116 | Returns a list of tuples where the first value holds the dbkey for a move 117 | cost and the second value holds the shuffled FP,BP,SP cost. 118 | """ 119 | rnd_cost_functions = { 120 | RandomMoveCosts.BALANCED_RANDOM: _get_balanced_random_costs, 121 | RandomMoveCosts.SHUFFLED: _get_shuffled_costs, 122 | RandomMoveCosts.FULLY_RANDOM: _get_fully_random_costs, 123 | } 124 | 125 | move_costs = [] 126 | 127 | if badges_bp_setting in rnd_cost_functions: 128 | new_cost = rnd_cost_functions.get(badges_bp_setting)("BADGE", "BP", random) 129 | move_costs.extend(new_cost) 130 | 131 | if badges_fp_setting in rnd_cost_functions: 132 | new_cost = rnd_cost_functions.get(badges_fp_setting)("BADGE", "FP", random) 133 | move_costs.extend(new_cost) 134 | 135 | if partner_fp_setting in rnd_cost_functions: 136 | new_cost = rnd_cost_functions.get(partner_fp_setting)("PARTNER", "FP", random) 137 | move_costs.extend(new_cost) 138 | 139 | if starpower_setting in rnd_cost_functions: 140 | new_cost = rnd_cost_functions.get(starpower_setting)("STARPOWER", "FP", random) 141 | move_costs.extend(new_cost) 142 | 143 | return move_costs 144 | -------------------------------------------------------------------------------- /modules/modify_itempool.py: -------------------------------------------------------------------------------- 1 | import math 2 | from copy import deepcopy 3 | 4 | from ..data.enum_options import IncludeFavorsMode, RandomizeConsumablesMode 5 | from ..data.item_exclusion import exclude_due_to_settings 6 | from ..data.item_scores import item_scores 7 | from ..data.itemlocation_special import kootfavors_reward_locations, kootfavors_keyitem_locations, limited_by_item_areas 8 | 9 | from BaseClasses import Item 10 | from ..data.LocationsList import location_table, location_groups 11 | from ..data.ItemList import item_table, item_groups 12 | from ..options import ItemTraps, ShuffleKootFavors, ShuffleDojoRewards, PartnerUpgradeShuffle 13 | 14 | 15 | def _get_random_consumables(n: int, available_items: list, random) -> list: 16 | """ 17 | Returns a list of n items, with categories similar to vanilla distribution 18 | """ 19 | weights = { 20 | "battle": 50, 21 | "heal": 45, 22 | "taycet": 5, 23 | } 24 | item_weights = [weights[item["type"]] for item in available_items] 25 | 26 | return random.choices(available_items, item_weights, k=n) 27 | 28 | 29 | def _balance_consumables(items: list, available_items: list, target_score: int, random): 30 | """ 31 | Modifies a list of consumables until its score is close enough to the target score 32 | """ 33 | new_items = items.copy() 34 | pool_score = sum([item["score"] for item in new_items]) 35 | 36 | lowest_item = item_scores[0] 37 | lowest_score = lowest_item["score"] 38 | highest_item = item_scores[len(item_scores) - 1] 39 | highest_score = highest_item["score"] 40 | score_diff = math.ceil((highest_score - lowest_score) / 2) 41 | 42 | # Randomly adjust the items to bring closer to target score 43 | while pool_score < target_score - score_diff or pool_score > target_score + score_diff: 44 | if pool_score < target_score: 45 | # Upgrade an item 46 | item_weights = [highest_score - item["score"] for item in new_items] 47 | i = random.choices([i for i in range(len(new_items))], item_weights).pop() 48 | old_item = new_items[i] 49 | legal_items = [item for item in available_items if 50 | item["score"] > old_item["score"] and item["type"] == old_item["type"]] 51 | else: 52 | # Downgrade an item 53 | item_weights = [item["score"] - lowest_score for item in new_items] 54 | i = random.choices([i for i in range(len(new_items))], item_weights).pop() 55 | old_item = new_items[i] 56 | legal_items = [item for item in available_items if 57 | item["score"] < old_item["score"] and item["type"] == old_item["type"]] 58 | 59 | # If there's no legal items to upgrade/downgrade to, try again 60 | if len(legal_items) == 0: 61 | continue 62 | 63 | new_item = random.choice(legal_items) 64 | pool_score += new_item["score"] - old_item["score"] 65 | new_items[i] = new_item 66 | 67 | return new_items 68 | 69 | 70 | def get_randomized_itempool(itempool: list, consumable_mode: int, quality: int, add_unused_items: bool, random) -> list: 71 | """ 72 | Returns a randomized general item pool according to consumable mode 73 | Balanced random mode creates an item pool that has a value equal 74 | to the input pool's value, multiplied by the quality percentage 75 | """ 76 | # Consumable mode: 77 | # 0: vanilla (no quality) 78 | # 1: full random (no quality) 79 | # 2: balanced random (quality applies) 80 | # 3: mystery only (no quality) 81 | 82 | # vanilla 83 | if consumable_mode == RandomizeConsumablesMode.OFF: 84 | return itempool 85 | 86 | def is_consumable(item_name): 87 | item_score_obj = next((x for x in item_scores if x.get("name") == item_name), None) 88 | return item_score_obj is not None 89 | 90 | new_items = [] 91 | kept_items = [x for x in itempool if not is_consumable(x)] 92 | removed_items = [x for x in itempool if is_consumable(x)] 93 | target_count = len(removed_items) 94 | 95 | # Random or Balanced Random 96 | if (consumable_mode == RandomizeConsumablesMode.FULL_RANDOM 97 | or consumable_mode == RandomizeConsumablesMode.BALANCED_RANDOM): 98 | 99 | if add_unused_items: 100 | available_items = item_scores 101 | else: 102 | available_items = [ 103 | item for item in item_scores if item["name"] not in ["Hustle Drink", "Insecticide Herb"] 104 | ] 105 | 106 | # Generate fully random pool 107 | new_items = _get_random_consumables(target_count, available_items, random) 108 | 109 | # Balance according to quality factor 110 | if consumable_mode == RandomizeConsumablesMode.BALANCED_RANDOM: 111 | target_score = 0 112 | for item_name in removed_items: 113 | target_score += next(item["score"] for item in item_scores if item["name"] == item_name) 114 | 115 | # Multiply score by the quality factor 116 | target_score = math.floor(target_score * (quality / 100)) 117 | new_items = _balance_consumables(new_items, available_items, target_score, random) 118 | 119 | # Convert from scored dict entries to proper item_obj list 120 | new_items = [item["name"] for item in new_items] 121 | 122 | # Mystery only 123 | elif consumable_mode == RandomizeConsumablesMode.MYSTERY_ONLY: 124 | mystery_item = "Mystery" 125 | for _ in range(target_count): 126 | new_items.append(deepcopy(mystery_item)) 127 | 128 | new_itempool = kept_items + new_items 129 | assert (len(itempool) == len(new_itempool)) 130 | return new_itempool 131 | -------------------------------------------------------------------------------- /data/regions/boos_mansion.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "BM Foyer 1F", 4 | "area_id": "13", 5 | "map_id": "0", 6 | "map_name": "Foyer", 7 | "locations": { 8 | "BM Foyer From Franky (Koopa Koot Favor)": "'RF_OpenedGustyGulch' and 'FAVOR_5_02_active'", 9 | "BM Foyer Franky Letter Reward": "'RF_OpenedGustyGulch' and can_use_ability_parakarry and Letter_to_Franky", 10 | "BM Foyer Hidden Panel": "can_flip_panels" 11 | }, 12 | "exits": { 13 | "FOR Outside Boo's Mansion Steps": "True", 14 | "BM Pot Room": "True", 15 | "BM Basement Stairs Upper": "Boo_Weight and can_climb_steps", 16 | "BM Foyer 2F": "(Boots or Boo_Weight) and can_climb_steps" 17 | } 18 | }, 19 | { 20 | "region_name": "BM Foyer 2F", 21 | "area_id": "13", 22 | "map_id": "0", 23 | "map_name": "Foyer", 24 | "exits": { 25 | "BM Record Player Room": "True", 26 | "BM Record Room": "True", 27 | "BM Foyer 1F": "True", 28 | "BM Foyer 3F": "Boo_Portrait" 29 | } 30 | }, 31 | { 32 | "region_name": "BM Foyer 3F", 33 | "area_id": "13", 34 | "map_id": "0", 35 | "map_name": "Foyer", 36 | "exits": { 37 | "BM Lady Bow's Room": "True", 38 | "BM Foyer 2F": "True" 39 | } 40 | }, 41 | { 42 | "region_name": "BM Basement Stairs Upper", 43 | "area_id": "13", 44 | "map_id": "1", 45 | "map_name": "Basement Stairs", 46 | "exits": { 47 | "BM Foyer 1F": "True", 48 | "BM Basement Stairs Lower": "True" 49 | } 50 | }, 51 | { 52 | "region_name": "BM Basement Stairs Lower", 53 | "area_id": "13", 54 | "map_id": "1", 55 | "map_name": "Basement Stairs", 56 | "locations": { 57 | "BM Basement Stairs Hidden Panel": "can_flip_panels" 58 | }, 59 | "exits": { 60 | "BM Mansion Basement Upper": "True", 61 | "BM Basement Stairs Upper": "Boots", 62 | "BM Library Lower": "can_use_ability_bombette" 63 | } 64 | }, 65 | { 66 | "region_name": "BM Mansion Basement Upper", 67 | "area_id": "13", 68 | "map_id": "2", 69 | "map_name": "Basement", 70 | "locations": { 71 | "BM Basement In Crate": "Super_Boots" 72 | }, 73 | "exits": { 74 | "BM Basement Stairs Lower": "True", 75 | "BM Super Boots Room": "True", 76 | "BM Mansion Basement Lower": "'RF_OBK03_BuiltStairs'" 77 | } 78 | }, 79 | { 80 | "region_name": "BM Mansion Basement Lower", 81 | "area_id": "13", 82 | "map_id": "2", 83 | "map_name": "Basement", 84 | "events": { 85 | "RF_OBK03_BuiltStairs": "Boots" 86 | }, 87 | "locations": { 88 | "BM Basement Igor Letter Reward": "can_use_ability_parakarry and Letter_to_Igor", 89 | "BM Basement Shop Item 1": "'RF_OpenedGustyGulch'", 90 | "BM Basement Shop Item 2": "'RF_OpenedGustyGulch'", 91 | "BM Basement Shop Item 3": "'RF_OpenedGustyGulch'", 92 | "BM Basement Shop Item 4": "'RF_OpenedGustyGulch'", 93 | "BM Basement Shop Item 5": "'RF_OpenedGustyGulch'", 94 | "BM Basement Shop Item 6": "'RF_OpenedGustyGulch'" 95 | }, 96 | "exits": { 97 | "BM Mansion Basement Upper": "'RF_OBK03_BuiltStairs'" 98 | } 99 | }, 100 | { 101 | "region_name": "BM Super Boots Room", 102 | "area_id": "13", 103 | "map_id": "3", 104 | "map_name": "Super Boots Room", 105 | "events": { 106 | "RF_OBK04_OpenedBigChest": "(Boots or Hammer)" 107 | }, 108 | "locations": { 109 | "BM Super Boots Room In Crate": "Super_Boots", 110 | "BM Super Boots Room In Big Chest": "(Boots or Hammer)", 111 | "BM Super Boots Room Hidden Panel": "can_flip_panels" 112 | }, 113 | "exits": { 114 | "BM Mansion Basement Lower": "'RF_OBK04_OpenedBigChest'" 115 | } 116 | }, 117 | { 118 | "region_name": "BM Pot Room", 119 | "area_id": "13", 120 | "map_id": "4", 121 | "map_name": "Pot Room", 122 | "locations": { 123 | "BM Pot Room In Crate 1": "Super_Boots", 124 | "BM Pot Room In Crate 2": "Super_Boots" 125 | }, 126 | "exits": { 127 | "BM Foyer 1F": "True", 128 | "BM Library Upper": "Super_Boots and can_use_ability_bombette" 129 | } 130 | }, 131 | { 132 | "region_name": "BM Library Upper", 133 | "area_id": "13", 134 | "map_id": "5", 135 | "map_name": "Library", 136 | "locations": { 137 | "BM Library In Crate": "Super_Boots", 138 | "BM Library On Bookshelf": "can_use_ability_parakarry" 139 | }, 140 | "exits": { 141 | "BM Library Lower": "True" 142 | } 143 | }, 144 | { 145 | "region_name": "BM Library Lower", 146 | "area_id": "13", 147 | "map_id": "5", 148 | "map_name": "Library", 149 | "exits": { 150 | "BM Basement Stairs Lower": "can_use_ability_bombette" 151 | } 152 | }, 153 | { 154 | "region_name": "BM Record Player Room", 155 | "area_id": "13", 156 | "map_id": "6", 157 | "map_name": "Record Player Room", 158 | "locations": { 159 | "BM Record Player Room In Chest": "Boo_Record" 160 | }, 161 | "exits": { 162 | "BM Foyer 2F": "True" 163 | } 164 | }, 165 | { 166 | "region_name": "BM Record Room", 167 | "area_id": "13", 168 | "map_id": "7", 169 | "map_name": "Record Room", 170 | "locations": { 171 | "BM Record Room Hidden Panel": "can_flip_panels", 172 | "BM Record Room Beat Boo Game": "(Boots or Hammer)" 173 | }, 174 | "exits": { 175 | "BM Foyer 2F": "True" 176 | } 177 | }, 178 | { 179 | "region_name": "BM Lady Bow's Room", 180 | "area_id": "13", 181 | "map_id": "8", 182 | "map_name": "Lady Bow's Room", 183 | "events": { 184 | "RF_OpenedGustyGulch": "True" 185 | }, 186 | "locations": { 187 | "BM Lady Bow's Room Bow Partner": "True" 188 | }, 189 | "exits": { 190 | "BM Foyer 3F": "True" 191 | } 192 | } 193 | ] 194 | -------------------------------------------------------------------------------- /data/item_scores.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/metadata/item_scores.py 2 | item_scores = [ 3 | {"name": "Insecticide Herb", "type": "battle", "score": 1}, 4 | {"name": "Dried Shroom", "type": "heal", "score": 1}, 5 | {"name": "Tasty Tonic", "type": "heal", "score": 1}, 6 | {"name": "Dusty Hammer", "type": "battle", "score": 1}, 7 | {"name": "Pebble", "type": "battle", "score": 1}, 8 | {"name": "Mistake", "type": "taycet", "score": 2}, 9 | {"name": "Coconut", "type": "battle", "score": 3}, 10 | {"name": "Lemon", "type": "heal", "score": 3}, 11 | {"name": "Lime", "type": "heal", "score": 3}, 12 | {"name": "Cake Mix", "type": "heal", "score": 4}, 13 | {"name": "Strange Cake", "type": "taycet", "score": 5}, 14 | {"name": "Stinky Herb", "type": "heal", "score": 5}, 15 | {"name": "Honey Syrup", "type": "heal", "score": 5}, 16 | {"name": "Goomnut", "type": "heal", "score": 5}, 17 | {"name": "Koopa Leaf", "type": "heal", "score": 5}, 18 | {"name": "Yellow Berry", "type": "heal", "score": 5}, 19 | {"name": "Bubble Berry", "type": "heal", "score": 5}, 20 | {"name": "Red Berry", "type": "heal", "score": 5}, 21 | {"name": "Blue Berry", "type": "heal", "score": 5}, 22 | {"name": "Apple", "type": "heal", "score": 5}, 23 | {"name": "Volt Shroom", "type": "battle", "score": 5}, 24 | {"name": "Thunder Bolt", "type": "battle", "score": 5}, 25 | {"name": "Egg Missile", "type": "battle", "score": 6}, 26 | {"name": "Spicy Soup", "type": "taycet", "score": 6}, 27 | {"name": "Dried Pasta", "type": "heal", "score": 6}, 28 | {"name": "Mystery", "type": "battle", "score": 6}, 29 | {"name": "Super Soda", "type": "heal", "score": 6}, 30 | {"name": "Koopa Tea", "type": "taycet", "score": 7}, 31 | {"name": "Fried Shroom", "type": "taycet", "score": 7}, 32 | {"name": "Iced Potato", "type": "heal", "score": 7}, 33 | {"name": "Egg", "type": "heal", "score": 7}, 34 | {"name": "POW Block", "type": "battle", "score": 7}, 35 | {"name": "Mushroom", "type": "heal", "score": 7}, 36 | {"name": "Spaghetti", "type": "taycet", "score": 8}, 37 | {"name": "Honey Shroom", "type": "taycet", "score": 8}, 38 | {"name": "Strange Leaf", "type": "heal", "score": 8}, 39 | {"name": "Nutty Cake", "type": "taycet", "score": 10}, 40 | {"name": "Potato Salad", "type": "taycet", "score": 10}, 41 | {"name": "Fried Egg", "type": "taycet", "score": 10}, 42 | {"name": "Koopasta", "type": "taycet", "score": 10}, 43 | {"name": "Maple Syrup", "type": "heal", "score": 10}, 44 | {"name": "Super Shroom", "type": "heal", "score": 10}, 45 | {"name": "Fright Jar", "type": "battle", "score": 11}, 46 | {"name": "Fire Flower", "type": "battle", "score": 11}, 47 | {"name": "Maple Shroom", "type": "taycet", "score": 12}, 48 | {"name": "Boiled Egg", "type": "taycet", "score": 12}, 49 | {"name": "Honey Super", "type": "taycet", "score": 12}, 50 | {"name": "Dizzy Dial", "type": "battle", "score": 13}, 51 | {"name": "Sleepy Sheep", "type": "battle", "score": 13}, 52 | {"name": "Snowman Doll", "type": "battle", "score": 14}, 53 | {"name": "Stone Cap", "type": "battle", "score": 15}, 54 | {"name": "Frozen Fries", "type": "taycet", "score": 15}, 55 | {"name": "Cake", "type": "taycet", "score": 15}, 56 | {"name": "Maple Super", "type": "taycet", "score": 15}, 57 | {"name": "Bland Meal", "type": "taycet", "score": 15}, 58 | {"name": "Shroom Cake", "type": "taycet", "score": 15}, 59 | {"name": "Dried Fruit", "type": "heal", "score": 15}, 60 | {"name": "Coco Pop", "type": "taycet", "score": 16}, 61 | {"name": "Lemon Candy", "type": "taycet", "score": 16}, 62 | {"name": "Hot Shroom", "type": "taycet", "score": 16}, 63 | {"name": "Apple Pie", "type": "taycet", "score": 16}, 64 | {"name": "Thunder Rage", "type": "battle", "score": 18}, 65 | {"name": "Melon", "type": "heal", "score": 19}, 66 | {"name": "Kooky Cookie", "type": "taycet", "score": 20}, 67 | {"name": "Honey Candy", "type": "taycet", "score": 20}, 68 | {"name": "Electro Pop", "type": "taycet", "score": 20}, 69 | {"name": "Lime Candy", "type": "taycet", "score": 20}, 70 | {"name": "Special Shake", "type": "taycet", "score": 20}, 71 | {"name": "Big Cookie", "type": "taycet", "score": 20}, 72 | {"name": "Stop Watch", "type": "battle", "score": 20}, 73 | {"name": "Hustle Drink", "type": "battle", "score": 20}, 74 | {"name": "Fire Pop", "type": "taycet", "score": 21}, 75 | {"name": "Shooting Star", "type": "battle", "score": 21}, 76 | {"name": "Yoshi Cookie", "type": "taycet", "score": 22}, 77 | {"name": "Repel Gel", "type": "battle", "score": 25}, 78 | {"name": "Yummy Meal", "type": "taycet", "score": 29}, 79 | {"name": "Healthy Juice", "type": "taycet", "score": 29}, 80 | {"name": "Shroom Steak", "type": "taycet", "score": 29}, 81 | {"name": "Jelly Shroom", "type": "taycet", "score": 32}, 82 | {"name": "Honey Ultra", "type": "taycet", "score": 32}, 83 | {"name": "Jammin Jelly", "type": "heal", "score": 32}, 84 | {"name": "Ultra Shroom", "type": "heal", "score": 32}, 85 | {"name": "Jelly Super", "type": "taycet", "score": 33}, 86 | {"name": "Maple Ultra", "type": "taycet", "score": 33}, 87 | {"name": "Sweet Shroom", "type": "taycet", "score": 34}, 88 | {"name": "Jelly Pop", "type": "taycet", "score": 35}, 89 | {"name": "Whackas Bump", "type": "heal", "score": 36}, 90 | {"name": "Deluxe Feast", "type": "taycet", "score": 41}, 91 | {"name": "Jelly Ultra", "type": "taycet", "score": 45}, 92 | {"name": "Life Shroom", "type": "heal", "score": 50}, 93 | ] 94 | -------------------------------------------------------------------------------- /data/regions/shooting_star_summit.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "SSS Shooting Star Path", 4 | "area_id": "5", 5 | "map_id": "0", 6 | "map_name": "Shooting Star Path", 7 | "locations": { 8 | "SSS Shooting Star Path Hidden Panel": "can_flip_panels" 9 | }, 10 | "exits": { 11 | "PCG Ruined Castle Grounds": "True", 12 | "SSS Merluvlee's House": "True", 13 | "SSS Shooting Star Path East Upper Exit": "can_climb_steps" 14 | } 15 | }, 16 | { 17 | "region_name": "SSS Shooting Star Path East Upper Exit", 18 | "area_id": "5", 19 | "map_id": "0", 20 | "map_name": "Shooting Star Path", 21 | "exits": { 22 | "SSS Shooting Star Summit": "True", 23 | "SSS Shooting Star Path": "True" 24 | } 25 | }, 26 | { 27 | "region_name": "SSS Shooting Star Summit Base", 28 | "area_id": "5", 29 | "map_id": "1", 30 | "map_name": "Shooting Star Summit", 31 | "locations": { 32 | "SSS Shooting Star Summit Behind The Summit": "True" 33 | }, 34 | "exits": { 35 | "SSS Shooting Star Path East Upper Exit": "True", 36 | "SSS Shooting Star Summit": "can_climb_steps" 37 | } 38 | }, 39 | { 40 | "region_name": "SSS Shooting Star Summit", 41 | "area_id": "5", 42 | "map_id": "1", 43 | "map_name": "Shooting Star Summit", 44 | "locations": { 45 | "SSS Shooting Star Summit Hidden Panel": "can_flip_panels and can_climb_steps" 46 | }, 47 | "exits": { 48 | "SSS Star Way": "can_reach_star_way", 49 | "SSS Shooting Star Summit Base": "True" 50 | } 51 | }, 52 | { 53 | "region_name": "SSS Star Way", 54 | "area_id": "5", 55 | "map_id": "2", 56 | "map_name": "Star Way", 57 | "exits": { 58 | "SSS Shooting Star Summit": "True", 59 | "SSS Star Haven": "True" 60 | } 61 | }, 62 | { 63 | "region_name": "SSS Star Haven", 64 | "area_id": "5", 65 | "map_id": "3", 66 | "map_name": "Star Haven", 67 | "events": { 68 | "Star_Piece_HOS_1": "True", 69 | "Star_Piece_HOS_8": "True" 70 | }, 71 | "locations": { 72 | "SSS Star Haven Shop Item 1": "Boots", 73 | "SSS Star Haven Shop Item 2": "Boots", 74 | "SSS Star Haven Shop Item 3": "Boots", 75 | "SSS Star Haven Shop Item 4": "Boots", 76 | "SSS Star Haven Shop Item 5": "Boots", 77 | "SSS Star Haven Shop Item 6": "Boots" 78 | }, 79 | "exits": { 80 | "SSS Star Way": "True", 81 | "SSS Outside the Sanctuary": "True" 82 | } 83 | }, 84 | { 85 | "region_name": "SSS Outside the Sanctuary", 86 | "area_id": "5", 87 | "map_id": "4", 88 | "map_name": "Outside the Sanctuary", 89 | "exits": { 90 | "SSS Star Haven": "True", 91 | "SSS Star Sanctuary": "True" 92 | } 93 | }, 94 | { 95 | "region_name": "SSS Star Sanctuary", 96 | "area_id": "5", 97 | "map_id": "5", 98 | "map_name": "Star Sanctuary", 99 | "locations": { 100 | "SSS Star Sanctuary Gift of the Stars": "can_climb_steps and has_star_beam_requirements" 101 | }, 102 | "exits": { 103 | "SSS Outside the Sanctuary": "True", 104 | "SSS Star Sanctuary Starship": "can_climb_steps" 105 | } 106 | }, 107 | { 108 | "region_name": "SSS Star Sanctuary Starship", 109 | "area_id": "5", 110 | "map_id": "5", 111 | "map_name": "Star Sanctuary", 112 | "exits": { 113 | "SSS Riding Star Ship Scene": "True", 114 | "SSS Star Sanctuary": "True" 115 | } 116 | }, 117 | { 118 | "region_name": "SSS Merluvlee's House", 119 | "area_id": "5", 120 | "map_id": "6", 121 | "map_name": "Merluvlee's House", 122 | "events": { 123 | "GF_HOS06_MerluvleeRequestedCrystalBall": "'FAVOR_3_03_active'" 124 | }, 125 | "locations": { 126 | "SSS Merluvlee's House Hidden Panel": "can_flip_panels", 127 | "SSS Merluvlee's House Merluvlee Koopa Koot Favor": "'FAVOR_3_03_active' and Crystal_Ball", 128 | "SSS Merluvlee's House Merlow Letter Reward": "can_use_ability_parakarry and Letter_to_Merlow", 129 | "SSS Merluvlee's House Merlow's Badges 1": "(Star_Piece, 30) and can_climb_steps", 130 | "SSS Merluvlee's House Merlow's Badges 2": "(Star_Piece, 30) and can_climb_steps", 131 | "SSS Merluvlee's House Merlow's Badges 3": "(Star_Piece, 30) and can_climb_steps", 132 | "SSS Merluvlee's House Merlow's Badges 4": "(Star_Piece, 30) and can_climb_steps", 133 | "SSS Merluvlee's House Merlow's Badges 5": "(Star_Piece, 30) and can_climb_steps", 134 | "SSS Merluvlee's House Merlow's Badges 6": "(Star_Piece, 30) and can_climb_steps", 135 | "SSS Merluvlee's House Merlow's Badges 7": "(Star_Piece, 30) and can_climb_steps", 136 | "SSS Merluvlee's House Merlow's Badges 8": "(Star_Piece, 30) and can_climb_steps", 137 | "SSS Merluvlee's House Merlow's Badges 9": "(Star_Piece, 30) and can_climb_steps", 138 | "SSS Merluvlee's House Merlow's Badges 10": "(Star_Piece, 30) and can_climb_steps", 139 | "SSS Merluvlee's House Merlow's Badges 11": "(Star_Piece, 30) and can_climb_steps", 140 | "SSS Merluvlee's House Merlow's Badges 12": "(Star_Piece, 30) and can_climb_steps", 141 | "SSS Merluvlee's House Merlow's Badges 13": "(Star_Piece, 30) and can_climb_steps", 142 | "SSS Merluvlee's House Merlow's Badges 14": "(Star_Piece, 30) and can_climb_steps", 143 | "SSS Merluvlee's House Merlow's Badges 15": "(Star_Piece, 30) and can_climb_steps", 144 | "SSS Merluvlee's House Merlow's Rewards 1": "(Star_Piece, 10) and can_climb_steps", 145 | "SSS Merluvlee's House Merlow's Rewards 2": "(Star_Piece, 23) and can_climb_steps", 146 | "SSS Merluvlee's House Merlow's Rewards 3": "(Star_Piece, 35) and can_climb_steps", 147 | "SSS Merluvlee's House Merlow's Rewards 4": "(Star_Piece, 47) and can_climb_steps", 148 | "SSS Merluvlee's House Merlow's Rewards 5": "(Star_Piece, 57) and can_climb_steps", 149 | "SSS Merluvlee's House Merlow's Rewards 6": "(Star_Piece, 68) and can_climb_steps" 150 | }, 151 | "exits": { 152 | "SSS Shooting Star Path": "True" 153 | } 154 | }, 155 | { 156 | "region_name": "SSS Riding Star Ship Scene", 157 | "area_id": "5", 158 | "map_id": "8", 159 | "map_name": "Riding Star Ship Scene", 160 | "exits": { 161 | "SSS Star Sanctuary Starship": "True", 162 | "BC Ship Enter/Exit Scenes": "bowser_castle_mode < 2", 163 | "BC Fake Peach Hallway": "bowser_castle_mode == 2" 164 | } 165 | } 166 | ] 167 | -------------------------------------------------------------------------------- /docs/Resources and FAQ.md: -------------------------------------------------------------------------------- 1 | # Resources 2 | 3 | ## AP World 4 | 5 | - [AP PMR Release Page](https://github.com/JKBSunshine/PMR_APWorld/releases) 6 | - [Sample YAML,](https://github.com/JKBSunshine/PMR_APWorld/blob/main/docs/PM%20Sample.yaml) which has notes for what settings are and aren't implemented 7 | - [Options Syntax and Explanations](https://github.com/JKBSunshine/PMR_APWorld/blob/main/options.py) 8 | ## Paper Mario Randomizer 9 | 10 | - [PMR Site](https://pm64randomizer.com/) 11 | - [PMR Wiki,](https://github.com/icebound777/PMR-SeedGenerator/wiki) with easily missable locations, tips, and a logic guide 12 | - [Nintendo Power Official Strategy Guide](https://ia801606.us.archive.org/6/items/PaperMarioNintendoPowerOfficialStrategyGuide_20170328/PaperMarioNintendoPowerOfficialStrategyGuide.pdf) 13 | complete with maps, item locations, recipes, and more. 14 | ## Trackers 15 | - [Emo/Pop Tracker Pack by Phantom](https://github.com/Phantom5800/pmr-emotracker/releases). 16 | Emo Tracker packs can be opened and used in Pop Tracker. 17 | - [Web Tracker by Scatter](https://pmr-tracker.christianlegge.dev/) 18 | - [Web Tracker by Yami,](https://pm64r-tracker.mryami.com/) with AP integration! Use the Archipelago button in the top left 19 | - You can view the key item tracker in your in-game inventory and view the map for checks remaining in an area 20 | 21 | # First Time Players 22 | ### This is my first time playing PMR. What should I know before I dive in? 23 | See the [Paper Mario Randomizer (PMR) Wiki](https://github.com/icebound777/PMR-SeedGenerator/wiki) 24 | for a full list of useful features added by the base mod, a logic guide, a list of commonly missed locations, and more. 25 | If you don't want to read through everything, here are a few highlights: 26 | - Items found in blocks, trees/foliage, chests, lying on the ground, or given by NPCs are all shuffled. 27 | - You start with the Homeward Shroom as a key item. Using it can bring you back to your spawn location at any time. 28 | - The base mod has built-in trackers in the Maps and Key Item Tracker sections in the pause menu. External trackers can 29 | still be quite helpful, but these can be handy for checking what items you have and if any checks remain in an area. 30 | - It's recommended to use `cutscene_mode: shortened` instead of `minimal` if you don't know puzzle solutions. 31 | - You need coins to buy items from shops, and given how many shops are available at the start, you might be expected to 32 | buy several things. It's not a bad idea to start yourself off with 200-300 coins just to be safe. 33 | - If you decide to allow very many consumables to be off-world, you may want to be careful with your inventory, 34 | particularly in an async game. Make sure to sell or store items as your inventory fills up, and maybe head to a shop 35 | before you connect to a game where you might be receiving a bunch of consumables. 36 | 37 | ### I can't remember... how does that one thing work? 38 | - Panels can be flipped using Super Boots or Ultra Hammer. 39 | - Yellow blocks can be destroyed by hitting them with hammer or by using Bombette. 40 | - Trees can also be shaken using Bombette. 41 | - Floating blocks can be hit by using Kooper's ability. 42 | - Rowf's shop gets more items with every Star Spirit found up to the fifth. You can talk to him to change his inventory. 43 | 44 | - Koopa Koot has three new requests with every Star Spirit found up to the sixth. You have to do them in order. 45 | 46 | 47 | - Parakarry needs any 3 letters to get his partner check. 48 | 49 | 50 | - To open the gate to Gusty Gulch, you need to have the Boo Portrait item and speak to Lady Bow in Boo's Mansion. 51 | 52 | 53 | - To enter Shy Guy's Toybox, you must hide using Bow's ability in the house just before you reach Toad Town's port. 54 | - The jack in the boxes in Shy Guy's Toybox can be activated with hammer, no spin jump necessary. 55 | - To solve the color blocks puzzle, give Russ T. (he lives near Toad Town's gate) his Dictionary and the Mystery Note. 56 | 57 | 58 | - To get to Mt Lavalava, you must use the Jade Raven on the statue in Jade Jungle and talk to Raphael in the huge tree. 59 | - The Bub-ulb in Jade Jungle's seed was taken by Kolorado; to get the item, give Kolorado the Volcano Vase. 60 | 61 | 62 | # Frequently Asked Questions 63 | 64 | ### What do the prefixes in front of the location names mean? 65 | 66 | | Prefix | Region | 67 | | ------ | ------ | 68 | | GR | Goomba Region | 69 | | TT | Toad Town | 70 | | TTT | Toad Town Tunnels | 71 | | SSS | Shooting Star Summit | 72 | | KR | Koopa Region | 73 | | KBF | Koopa Bros Fortress | 74 | | MR | Mt Rugged | 75 | | DDO | Dry Dry Outpost | 76 | | DDD | Dry Dry Desert | 77 | | DDR | Dry Dry Ruins | 78 | | FOR | Forever Forest (Includes the exterior of Boo's Mansion) | 79 | | BM | Boo's Mansion | 80 | | GG | Gusty Gulch | 81 | | TC | Tubbas Castle | 82 | | SGT | Shy Guys Toybox | 83 | | JJ | Jade Jungle (includes Yoshi's Village) | 84 | | MLL | Mt Lavalava | 85 | | FLO | Flower Fields | 86 | | SR | Shiver Region (includes Starborn Valley and Shiver Mountain) | 87 | | CP | Crystal Palace | 88 | | BC | Bowser's Castle | 89 | | PCG | Peach's Castle Grounds (includes the area between Toad Town and Shooting Star Summit)| 90 | | PC | Peach's Castle | 91 | 92 | ### What do N1W3 and S2E1 mean in Dry Dry Desert? 93 | 94 | The prefixes for the desert areas indicate what direction they are from the center 95 | (which is the screen with the stone cactus.) N means north, or up, while S means south, or downwards. 96 | E means east, or right, while w means west, or left. Be careful with east and west if you have mirror mode turned on. 97 | 98 | ### How am I supposed to know what I'm buying from the shop? 99 | 100 | Items sold in shops or found in Merlow's item rewards will be automatically hinted if they are progression items. If 101 | they are unhinted, they are not progression items. Items are not able to have names and descriptions in the current 102 | version of the base mod, so this is the best workaround to make sure you don't waste any hard earned coins on filler 103 | items. Note that the items sold by Merlow are never going to be progression, as you require every star piece in the 104 | game to buy all of them. 105 | 106 | ### Why is the item I was sent not in my inventory yet? 107 | 108 | Currently, items are granted while you are in the overworld on a one second loop by the game, and the items are written into the game on a one second loop by the Bizhawk Client for Archipelago. They will not continue to flow into your inventory while in battle, paused, transitioning between screens, in dialogue, and so on. This means when several items are queued to be received, it may still take some time for the item to show up. If it seems clear that no more items are showing up and you are still missing one or more items, please reach out to us in the Paper Mario thread in the Archipelago discord. 109 | -------------------------------------------------------------------------------- /GlitchOptions.py: -------------------------------------------------------------------------------- 1 | # from PMR: https://github.com/icebound777/PMR-SeedGenerator/blob/main/models/options/GlitchOptionSet.py 2 | 3 | class GlitchOptionSet: 4 | def __init__(self): 5 | self.prologue_gel_early = False 6 | self.reverse_goomba_king_bridge = False 7 | self.goomba_village_entry_fence_clip = False 8 | self.goomba_village_npc_lure_exit = False 9 | self.hammerless_jr_playground_laki = False 10 | self.goomba_village_laki_exit = False 11 | self.prologue_sushie_glitch_ksj = False 12 | self.prologue_sushie_glitch_ultra_boots_laki = False 13 | 14 | self.odd_key_early = False 15 | self.blue_house_skip = False 16 | self.blue_house_skip_laki = False 17 | self.blue_house_skip_toad_lure = False 18 | self.bowless_toy_box_hammer = False 19 | self.bowless_toy_box_hammerless_lure = False 20 | self.early_storeroom_parakarry = False 21 | self.early_storeroom_hammer = False 22 | self.early_storeroom_hammerless_lure = False 23 | self.whale_early = False 24 | self.sushiesless_toad_town_star_piece = False 25 | self.toad_town_sushie_glitch = False 26 | 27 | self.clippy_boots_stone_block_skip = False 28 | self.clippy_boots_metal_block_skip = False 29 | self.island_pipe_blooper_skip = False 30 | self.parakarryless_sewer_star_piece = False 31 | self.sewer_blocks_without_ultra_boots = False 32 | self.first_block_to_shiver_city_without_super_boots = False 33 | self.blocks_to_shiver_city_kooper_shell_item_throw = False 34 | self.sewer_yellow_block_with_ultra_boots = False 35 | self.jumpless_sewer_shooting_star = False 36 | 37 | self.kooperless_pleasant_path_star_piece = False 38 | self.hammerless_pleasant_path_bridge_ultra_boots_parakarry = False 39 | self.invisible_bridge_clip_lzs = False 40 | self.invisible_bridge_clip_laki = False 41 | self.kooperless_pleasant_path_thunderbolt = False 42 | 43 | self.bombetteless_kbf_fp_plus_lzs = False 44 | self.bombetteless_kbf_fp_plus_laki = False 45 | self.laki_jailbreak = False 46 | self.bombetteless_right_fortress_jail_key = False 47 | self.water_staircase_skip = False 48 | 49 | self.mt_rugged_quake_hammer_and_letter_with_laki = False 50 | self.parakarryless_mt_rugged_seed = False 51 | self.buzzar_gap_skip_clippy = False 52 | self.mt_rugged_coins_with_kooper = False 53 | self.mt_rugged_station_jumpless_climb_bombette = False 54 | self.mt_rugged_station_jumpless_climb_laki = False 55 | self.jumpless_mt_rugged_train_platform_parakarry = False 56 | self.parakarryless_mt_rugged_star_piece = False 57 | self.desert_brick_block_item_with_parakarry = False 58 | self.early_ruins_laki_jump = False 59 | self.early_ruins_ultra_boots = False 60 | 61 | self.artifact_jump_laki = False 62 | self.artifact_jump_ultra_boots = False 63 | self.ruins_key_laki_jump = False 64 | self.parakarryless_second_sand_room_ultra_boots = False 65 | self.parakarryless_second_sand_room_normal_boots = False 66 | self.parakarryless_super_hammer_room_ultra_boots = False 67 | self.parakarryless_super_hammer_room_normal_boots = False 68 | self.ruins_locks_skip_clippy = False 69 | 70 | self.forever_forest_backwards = False 71 | 72 | self.record_skip_no_bombette_push = False 73 | self.record_skip_bombette_push = False 74 | self.boos_portrait_with_kooper = False 75 | self.boos_portrait_with_laki = False 76 | self.jumpless_mansion_entry = False 77 | 78 | self.gusty_gulch_gate_skip_lzs = False 79 | self.gusty_gulch_gate_skip_laki = False 80 | self.kooperless_gusty_gulch_dizzy_dial_jump = False 81 | self.kooperless_gusty_gulch_dizzy_dial_laki = False 82 | self.kooperless_gusty_gulch_dizzy_dial_parakarry = False 83 | self.gusty_gulch_gap_skip = False 84 | 85 | self.bowless_tubbas_castle = False 86 | self.tubbas_table_laki_jump_clock = False 87 | self.tubbas_table_ultra_boots = False 88 | self.tubbas_table_laki_jump_study = False 89 | self.tubbas_castle_super_boots_skip = False 90 | self.parakarryless_mega_rush = False 91 | 92 | self.parakarryless_blue_building_star_piece = False 93 | self.gourmet_guy_skip_jump = False 94 | self.gourmet_guy_skip_laki = False 95 | self.gourmet_guy_skip_parakarry = False 96 | self.bowless_green_station = False 97 | self.kooperless_red_station_shooting_star = False 98 | self.gearless_red_station_shooting_star = False 99 | self.parakarryless_blue_block_city_gap = False 100 | self.blue_switch_skip_laki = False 101 | self.blue_switch_skip_ultra_boots = False 102 | self.red_barricade_skip = False 103 | self.hammerless_blue_station_laki = False 104 | self.hammerless_pink_station_laki = False 105 | 106 | self.raph_skip_english = False 107 | self.raph_skip_parakarry = False 108 | self.ch5_sushie_glitch = False 109 | self.sushieless_jungle_starpiece_and_letter = False 110 | self.jumpless_deep_jungle_laki = False 111 | self.kooperless_lavalava_pow_block_parakarry = False 112 | self.kooperless_lavalava_pow_block_super_boots = False 113 | self.jumpless_lavalava_pow_block = False 114 | self.ultra_hammer_skip = False 115 | self.ultra_hammer_skip_laki = False 116 | self.ultra_hammer_skip_sushie = False 117 | self.flarakarry = False 118 | self.parakarryless_flarakarry_bombette = False 119 | self.parakarryless_flarakarry_laki = False 120 | self.volcano_sushie_glitch = False 121 | 122 | self.early_laki_lzs = False 123 | self.early_laki_bombette_push = False 124 | self.bombetteless_mega_smash = False 125 | self.sun_tower_skip = False 126 | self.yellow_berry_gate_skip_lzs = False 127 | self.yellow_berry_gate_skip_laki = False 128 | self.yellow_berry_gate_skip_bombette_push = False 129 | self.red_berry_gate_skip_bombette_push = False 130 | self.red_berry_gate_skip_laki = False 131 | self.blue_berry_gate_skip_bombette_push = False 132 | self.blue_berry_gate_skip_laki = False 133 | self.bubble_berry_tree_early_laki_jump = False 134 | self.bubble_berry_tree_early_ultra_boots = False 135 | 136 | self.murder_solved_early_laki = False 137 | self.murder_solved_early_bombette_push = False 138 | self.ch7_sushie_glitch = False 139 | self.star_stone_with_ch7_sushie_glitch = False 140 | self.shiver_mountain_hidden_block_without_ultra_boots_laki = False 141 | self.shiver_mountain_hidden_block_without_ultra_boots_no_laki = False 142 | self.snowmen_skip_laki = False 143 | self.shiver_mountain_switch_skip = False 144 | self.sushieless_warehouse_key_bombette = False 145 | self.sushieless_warehouse_key_kooper = False 146 | 147 | self.mirror_clip = False 148 | 149 | self.bowless_bowsers_castle_basement = False 150 | self.fast_flood_room_kooper = False 151 | self.fast_flood_room_bombette_ultra_boots = False 152 | self.bombetteless_bowsers_castle_basement = False 153 | 154 | self.break_yellow_blocks_with_super_boots = False 155 | self.break_stone_blocks_with_ultra_boots = False 156 | self.knows_hidden_blocks = False 157 | self.knows_puzzle_solutions = False 158 | self.reach_high_blocks_with_super_boots = False -------------------------------------------------------------------------------- /data/regions/mt_rugged.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "MR Mt Rugged 1 West", 4 | "area_id": "8", 5 | "map_id": "0", 6 | "map_name": "Mt Rugged 1", 7 | "locations": { 8 | "MR Mt Rugged 1 On Slide 1": "True" 9 | }, 10 | "exits": { 11 | "MR Train Station Upper Exit": "True", 12 | "MR Mt Rugged 1 East": "can_climb_steps", 13 | "MR Mt Rugged 1 On Slide": "(can_use_ability_kooper or can_use_ability_lakilester)" 14 | } 15 | }, 16 | { 17 | "region_name": "MR Mt Rugged 1 On Slide", 18 | "area_id": "8", 19 | "map_id": "0", 20 | "map_name": "Mt Rugged 1", 21 | "locations": { 22 | "MR Mt Rugged 1 On Slide 2": "True", 23 | "MR Mt Rugged 1 On Slide 3": "True" 24 | }, 25 | "exits": { 26 | "MR Mt Rugged 1 West": "True", 27 | "MR Mt Rugged 1 East": "can_use_ability_lakilester" 28 | } 29 | }, 30 | { 31 | "region_name": "MR Mt Rugged 1 East", 32 | "area_id": "8", 33 | "map_id": "0", 34 | "map_name": "Mt Rugged 1", 35 | "locations": { 36 | }, 37 | "exits": { 38 | "MR Mt Rugged 1 On Slide": "can_climb_steps", 39 | "MR Mt Rugged 2 West": "True", 40 | "MR Mt Rugged 1 West": "True", 41 | "MR Mt Rugged 1 East Lower": "can_climb_steps" 42 | } 43 | }, 44 | { 45 | "region_name": "MR Mt Rugged 1 East Lower", 46 | "area_id": "8", 47 | "map_id": "0", 48 | "map_name": "Mt Rugged 1", 49 | "locations": { 50 | "MR Mt Rugged 1 Hurting Whacka": "Hammer or can_use_ability_bombette", 51 | "MR Mt Rugged 1 Yellow Block": "can_hit_floating_blocks" 52 | }, 53 | "exits": { 54 | "MR Mt Rugged 1 East": "can_climb_steps" 55 | } 56 | }, 57 | { 58 | "region_name": "MR Mt Rugged 2 West", 59 | "area_id": "8", 60 | "map_id": "1", 61 | "map_name": "Mt Rugged 2", 62 | "exits": { 63 | "MR Mt Rugged 1 East": "True", 64 | "MR Mt Rugged 2 Center": "can_climb_steps" 65 | } 66 | }, 67 | { 68 | "region_name": "MR Mt Rugged 2 Center", 69 | "area_id": "8", 70 | "map_id": "1", 71 | "map_name": "Mt Rugged 2", 72 | "locations": { 73 | "MR Mt Rugged 2 Hidden Panel": "can_flip_panels" 74 | }, 75 | "exits": { 76 | "MR Mt Rugged 2 East Lower": "Boots", 77 | "MR Mt Rugged 2 West": "can_climb_steps" 78 | } 79 | }, 80 | { 81 | "region_name": "MR Mt Rugged 2 East Lower", 82 | "area_id": "8", 83 | "map_id": "1", 84 | "map_name": "Mt Rugged 2", 85 | "locations": { 86 | "MR Mt Rugged 2 Kooper Ledge": "(can_use_ability_kooper or can_use_ability_parakarry)", 87 | "MR Mt Rugged 2 Parakarry Ledge": "can_use_ability_parakarry" 88 | }, 89 | "exits": { 90 | "MR Mt Rugged 3 Lower": "True", 91 | "MR Mt Rugged 2 Center": "can_climb_steps" 92 | } 93 | }, 94 | { 95 | "region_name": "MR Mt Rugged 2 East Upper", 96 | "area_id": "8", 97 | "map_id": "1", 98 | "map_name": "Mt Rugged 2", 99 | "exits": { 100 | "MR Mt Rugged 3 Upper": "True", 101 | "MR Mt Rugged 2 West Upper": "True" 102 | } 103 | }, 104 | { 105 | "region_name": "MR Mt Rugged 2 West Upper", 106 | "area_id": "8", 107 | "map_id": "1", 108 | "map_name": "Mt Rugged 2", 109 | "exits": { 110 | "MR Mt Rugged 4": "True", 111 | "MR Mt Rugged 2 Center": "True" 112 | } 113 | }, 114 | { 115 | "region_name": "MR Mt Rugged 3 Lower", 116 | "area_id": "8", 117 | "map_id": "2", 118 | "map_name": "Mt Rugged 3", 119 | "locations": { 120 | }, 121 | "exits": { 122 | "MR Mt Rugged 2 East Lower": "True", 123 | "MR Mt Rugged 3 Upper": "can_climb_steps" 124 | } 125 | }, 126 | { 127 | "region_name": "MR Mt Rugged 3 Upper", 128 | "area_id": "8", 129 | "map_id": "2", 130 | "map_name": "Mt Rugged 3", 131 | "locations": { 132 | "MR Mt Rugged 3 On Scaffolding": "True", 133 | "MR Mt Rugged 3 Bub-ulb Gift": "can_use_ability_parakarry" 134 | }, 135 | "exits": { 136 | "MR Mt Rugged 3 Lower": "True", 137 | "MR Suspension Bridge West": "True", 138 | "MR Mt Rugged 2 East Upper": "True" 139 | } 140 | }, 141 | { 142 | "region_name": "MR Mt Rugged 4", 143 | "area_id": "8", 144 | "map_id": "3", 145 | "map_name": "Mt Rugged 4", 146 | "locations": { 147 | "MR Mt Rugged 4 Hidden Cave Chest": "can_climb_steps", 148 | "MR Mt Rugged 4 Slide Ledge": "can_climb_steps", 149 | "MR Mt Rugged 4 Left Ledge Center": "can_use_ability_parakarry", 150 | "MR Mt Rugged 4 Left Ledge Right": "(can_use_ability_kooper or can_use_ability_parakarry) and can_climb_steps", 151 | "MR Mt Rugged 4 Left Ledge 3": "can_use_ability_parakarry", 152 | "MR Mt Rugged 4 Left Ledge 4": "can_use_ability_parakarry", 153 | "MR Mt Rugged 4 Left Ledge 5": "can_use_ability_parakarry", 154 | "MR Mt Rugged 4 Left Ledge 6": "can_use_ability_parakarry", 155 | "MR Mt Rugged 4 Left Ledge 7": "can_use_ability_parakarry", 156 | "MR Mt Rugged 4 Bottom Left 1": "can_climb_steps", 157 | "MR Mt Rugged 4 Bottom Left 2": "can_climb_steps", 158 | "MR Mt Rugged 4 Yellow Block Top Left": "can_hit_grounded_blocks and can_climb_steps", 159 | "MR Mt Rugged 4 Yellow Block Floating": "can_hit_floating_blocks", 160 | "MR Mt Rugged 4 Yellow Block Top Right": "can_hit_grounded_blocks and can_climb_steps" 161 | }, 162 | "exits": { 163 | "MR Mt Rugged 2 West Upper": "True" 164 | } 165 | }, 166 | { 167 | "region_name": "MR Suspension Bridge West", 168 | "area_id": "8", 169 | "map_id": "4", 170 | "map_name": "Suspension Bridge", 171 | "locations": { 172 | "MR Suspension Bridge Bottom Of Cliff": "True" 173 | }, 174 | "exits": { 175 | "MR Mt Rugged 3 Upper": "True", 176 | "MR Suspension Bridge East": "can_use_ability_parakarry" 177 | } 178 | }, 179 | { 180 | "region_name": "MR Suspension Bridge East", 181 | "area_id": "8", 182 | "map_id": "4", 183 | "map_name": "Suspension Bridge", 184 | "exits": { 185 | "DDD Desert Rugged Entrance West": "True", 186 | "MR Suspension Bridge West": "can_climb_steps" 187 | } 188 | }, 189 | { 190 | "region_name": "MR Train Station Upper Exit", 191 | "area_id": "8", 192 | "map_id": "5", 193 | "map_name": "Train Station", 194 | "locations": { 195 | "MR Train Station Parakarry Partner": "(Letter, 3)", 196 | "MR Train Station In SuperBlock": "Super_Hammer and can_hit_floating_blocks" 197 | }, 198 | "exits": { 199 | "MR Mt Rugged 1 West": "True", 200 | "MR Train Station Lower": "Boots" 201 | } 202 | }, 203 | { 204 | "region_name": "MR Train Station Lower", 205 | "area_id": "8", 206 | "map_id": "5", 207 | "map_name": "Train Station", 208 | "locations": { 209 | "MR Train Station Bush 1": "True", 210 | "MR Train Station Bush 2": "True", 211 | "MR Train Station Bush 3": "True", 212 | "MR Train Station Bush Top": "True" 213 | }, 214 | "exits": { 215 | "MR Train Station Upper Exit": "Boots", 216 | "MR Train Station Platform": "Boots" 217 | } 218 | }, 219 | { 220 | "region_name": "MR Train Station Platform", 221 | "area_id": "8", 222 | "map_id": "5", 223 | "map_name": "Train Station", 224 | "exits": { 225 | "MR Train Station Lower": "Boots", 226 | "MR Train Ride Scene": "True" 227 | } 228 | }, 229 | { 230 | "region_name": "MR Train Ride Scene", 231 | "area_id": "8", 232 | "map_id": "6", 233 | "map_name": "Train Ride Scene", 234 | "exits": { 235 | "TT Station District Train": "True", 236 | "MR Train Station Platform": "True" 237 | } 238 | } 239 | ] 240 | -------------------------------------------------------------------------------- /modules/random_palettes.py: -------------------------------------------------------------------------------- 1 | # from https://github.com/icebound777/PMR-SeedGenerator/blob/main/metadata/palettes_meta.py 2 | """Module for modifying sprite palettes""" 3 | 4 | from ..data.enum_options import RandomPalettes 5 | from ..data.palettes_meta import mario_n_partner_sprite_names, boss_sprite_names, enemy_sprite_names, \ 6 | hammer_sprite_names, special_vanilla_palette_ids, palette_table 7 | 8 | class CoinPalette: 9 | def __init__(self, data=None, targets=None, crcs=None) -> None: 10 | self.data = data 11 | self.targets = targets 12 | self.crcs = crcs 13 | 14 | 15 | def get_randomized_coinpalette(color_id: int): 16 | """ 17 | Choose and return a color palette for coin sprites in accordance to chosen 18 | settings, as well as all locations in ROM where the palette needs to be 19 | written to. 20 | We do it this way, because 21 | "swapping these coin palettes at runtime is pretty ugly" (-clover) 22 | """ 23 | COIN_COLOR_GOLD = 0 24 | COIN_COLOR_RED = 1 25 | COIN_COLOR_BLUE = 2 26 | COIN_COLOR_PURPLE = 3 27 | COIN_COLOR_SILVER = 4 28 | 29 | # byte data for gold palette (default) 30 | palette_coin_gold = [ 31 | 0x294AE739, 32 | 0xEED5E64F, 33 | 0xE5CDDD49, 34 | 0xBC49AB87, 35 | 0x92C76A09, 36 | 0xC4CB8289, 37 | 0x00010001, 38 | 0x00010001 39 | ] 40 | 41 | # byte data for red palette 42 | palette_coin_red = [ 43 | 0x294AE739, 44 | 0xEB15E24F, 45 | 0xE18DD90B, 46 | 0xB90DA8CD, 47 | 0x90CF610F, 48 | 0xC14D810F, 49 | 0x00010001, 50 | 0x00010001 51 | ] 52 | 53 | # byte data for blue palette 54 | palette_coin_blue = [ 55 | 0x294AE739, 56 | 0x653F4C7D, 57 | 0x43BD32FB, 58 | 0x327121AD, 59 | 0x21252119, 60 | 0x3AF3215D, 61 | 0x00010001, 62 | 0x00010001 63 | ] 64 | 65 | # byte data for purple palette 66 | palette_coin_purple = [ 67 | 0x294AE739, 68 | 0xCC75C2F5, 69 | 0xAA739933, 70 | 0x792B60E7, 71 | 0x50E138D7, 72 | 0x916D491D, 73 | 0x00010001, 74 | 0x00010001 75 | ] 76 | 77 | # byte data for silver palette 78 | palette_coin_silver = [ 79 | 0x294AE739, 80 | 0xC5F1B5AF, 81 | 0xA52B9CA9, 82 | 0x7BA3631B, 83 | 0x5257294B, 84 | 0x94654211, 85 | 0x00010001, 86 | 0x00010001 87 | ] 88 | 89 | coin_color_palettes = { 90 | COIN_COLOR_GOLD: palette_coin_gold, 91 | COIN_COLOR_RED: palette_coin_red, 92 | COIN_COLOR_BLUE: palette_coin_blue, 93 | COIN_COLOR_PURPLE: palette_coin_purple, 94 | COIN_COLOR_SILVER: palette_coin_silver, 95 | } 96 | 97 | target_rom_locations = [ 98 | 0x09A030, 99 | 0x09A0D0, 100 | 0x09A170, 101 | 0x09A210, 102 | 0x09A2B0, 103 | 0x09A350, 104 | 0x09A3F0, 105 | 0x09A490, 106 | 0x09A530, 107 | 0x09A5D0, 108 | 0x1FB9F0, 109 | 0x1FBB30, 110 | 0x1FBC70, 111 | 0x1FBDB0, 112 | 0x1FBEF0, 113 | 0x1FC030, 114 | 0x1FC170, 115 | 0x1FC2B0, 116 | 0x1FC3F0, 117 | 0x1FC530 118 | ] 119 | 120 | # temp. unused 121 | all_coin_palette_crcs = { 122 | COIN_COLOR_GOLD: [ 123 | 0xD4C3F881, 124 | 0xCB3B5A00 125 | ], 126 | COIN_COLOR_RED: [ 127 | 0x2BCD223A, 128 | 0x3CC7D7D5 129 | ], 130 | COIN_COLOR_BLUE: [ 131 | 0xEE094F68, 132 | 0x421628A3 133 | ], 134 | COIN_COLOR_PURPLE: [ 135 | 0xB99EDAC8, 136 | 0x7E0C334A 137 | ], 138 | COIN_COLOR_SILVER: [ 139 | 0x82A4AB59, 140 | 0xBF600802 141 | ] 142 | } 143 | 144 | return coin_color_palettes.get(color_id), target_rom_locations, None 145 | 146 | 147 | def get_randomized_palettes(world) -> list: 148 | """ 149 | Set up and return a list of dbkey/value pairs for sprite palette changes, 150 | according to given settings. 151 | Each dbkey is associated with one sprite, which in turn has at least two 152 | color palettes (vanilla palette + at least 1 custom one). If a sprite 153 | is not found in the dbkeys, then we don't provide custom palettes for it 154 | yet. 155 | Some color palettes are shared among several sprites (like for toads, 156 | toadettes, dryites, or shy guys). 157 | If a sprite is to be left vanilla, then we have to determine the vanilla 158 | sprite id first using a local function. The affected sprites have their 159 | vanilla sprite id encoded in the sprite name. 160 | """ 161 | def get_vanilla_palette_id(sprite_name: str) -> int: 162 | if sprite_name not in special_vanilla_palette_ids: 163 | return 0 164 | else: 165 | return int(sprite_name[3:4]) 166 | 167 | PALETTEVALUE_ALWAYS_RANDOM = 0xFFFFFFFF 168 | 169 | palettes_data = [] 170 | all_palettes = [] 171 | 172 | all_palettes.append(("Mario", world.options.mario_palette.value)) 173 | all_palettes.append(("01_0_Goombario", world.options.goombario_palette.value)) 174 | all_palettes.append(("02_0_Kooper", world.options.kooper_palette.value)) 175 | all_palettes.append(("03_0_Bombette", world.options.bombette_palette.value)) 176 | all_palettes.append(("04_0_Parakarry", world.options.parakarry_palette.value)) 177 | all_palettes.append(("05_0_Bow", world.options.bow_palette.value)) 178 | all_palettes.append(("06_0_Watt", world.options.watt_palette.value)) 179 | all_palettes.append(("07_0_Sushie", world.options.sushie_palette.value)) 180 | all_palettes.append(("08_0_Lakilester", world.options.lakilester_palette.value)) 181 | 182 | # Selectable palettes 183 | for palette_tuple in all_palettes: 184 | cur_sprite_name = palette_tuple[0] 185 | cur_option = palette_tuple[1] 186 | palette_info = palette_table[cur_sprite_name] 187 | palette_count = palette_info[2] 188 | 189 | # sprite value is 0 for vanilla, 1 to x for a chosen palette, 10 to 12 for one of the random options 190 | 191 | if 0 <= cur_option < palette_count: 192 | chosen_palette = cur_option 193 | elif cur_option == RandomPalettes.RANDOM_PICK: 194 | chosen_palette = world.random.randrange(0, palette_count) 195 | elif cur_option == RandomPalettes.RANDOM_PICK_NOT_VANILLA: 196 | chosen_palette = world.random.randrange(1, palette_count) 197 | elif cur_option == RandomPalettes.ALWAYS_RANDOM: 198 | chosen_palette = PALETTEVALUE_ALWAYS_RANDOM 199 | else: 200 | chosen_palette = get_vanilla_palette_id(cur_sprite_name) 201 | palettes_data.append((palette_info[1], chosen_palette)) 202 | 203 | # Bosses, enemies and general NPC palettes 204 | for sprite, palette_info in palette_table.items(): 205 | if sprite in mario_n_partner_sprite_names: 206 | continue 207 | 208 | if sprite in boss_sprite_names: 209 | cur_setting = RandomPalettes.get_setting_value(world.options.boss_palette.value) 210 | elif sprite in enemy_sprite_names: 211 | cur_setting = RandomPalettes.get_setting_value(world.options.enemy_palette.value) 212 | elif sprite in hammer_sprite_names: 213 | cur_setting = RandomPalettes.get_setting_value(world.options.hammer_palette.value) 214 | else: # other NPC settings 215 | cur_setting = RandomPalettes.get_setting_value(world.options.npc_palette.value) 216 | 217 | if cur_setting == RandomPalettes.RANDOM_PICK_SETTING: 218 | palette_count = palette_info[2] 219 | chosen_palette = world.random.randrange(0, palette_count) 220 | elif cur_setting == RandomPalettes.RANDOM_PICK_NOT_VANILLA_SETTING: 221 | palette_count = palette_info[2] 222 | chosen_palette = world.random.randrange(1, palette_count) 223 | elif cur_setting == RandomPalettes.ALWAYS_RANDOM_SETTING: 224 | chosen_palette = PALETTEVALUE_ALWAYS_RANDOM 225 | else: 226 | chosen_palette = get_vanilla_palette_id(sprite) 227 | palettes_data.append((palette_info[1], chosen_palette)) 228 | 229 | return palettes_data 230 | -------------------------------------------------------------------------------- /data/regions/tubbas_castle.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "TC Outside Tubbas Castle", 4 | "area_id": "15", 5 | "map_id": "0", 6 | "map_name": "Outside Tubbas Castle", 7 | "exits": { 8 | "GG Wasteland Ascent 2 East": "True", 9 | "TC Great Hall 1F": "True" 10 | } 11 | }, 12 | { 13 | "region_name": "TC Great Hall 1F", 14 | "area_id": "15", 15 | "map_id": "1", 16 | "map_name": "Great Hall", 17 | "exits": { 18 | "TC Outside Tubbas Castle": "True", 19 | "TC West Hall (1F)": "can_use_ability_bow", 20 | "TC East Hall (1/2F) 1F": "can_use_ability_bow and (Tubba_Castle_Key, 1)" 21 | } 22 | }, 23 | { 24 | "region_name": "TC Great Hall 2F", 25 | "area_id": "15", 26 | "map_id": "1", 27 | "map_name": "Great Hall", 28 | "exits": { 29 | "TC West Hall (2F)": "True", 30 | "TC East Hall (1/2F) 2F": "True" 31 | } 32 | }, 33 | { 34 | "region_name": "TC Great Hall 3F", 35 | "area_id": "15", 36 | "map_id": "1", 37 | "map_name": "Great Hall", 38 | "exits": { 39 | "TC Save Room (3F)": "True", 40 | "TC Master Bedroom (3F)": "True" 41 | } 42 | }, 43 | { 44 | "region_name": "TC West Hall (1F)", 45 | "area_id": "15", 46 | "map_id": "2", 47 | "map_name": "West Hall (1F)", 48 | "exits": { 49 | "TC Table/Clock Room (1/2F) 1F": "True", 50 | "TC Great Hall 1F": "True", 51 | "TC Study (1F)": "True", 52 | "TC Covered Tables Room (1F)": "True" 53 | } 54 | }, 55 | { 56 | "region_name": "TC Table/Clock Room (1/2F) 1F", 57 | "area_id": "15", 58 | "map_id": "3", 59 | "map_name": "Table/Clock Room (1/2F)", 60 | "exits": { 61 | "TC Stairs to Basement": "True", 62 | "TC West Hall (1F)": "True", 63 | "TC Stairs Above Basement": "True" 64 | } 65 | }, 66 | { 67 | "region_name": "TC Table/Clock Room (1/2F) 2F", 68 | "area_id": "15", 69 | "map_id": "3", 70 | "map_name": "Table/Clock Room (1/2F)", 71 | "locations": { 72 | "TC Table/Clock Room (1/2F) On Table": "True" 73 | }, 74 | "exits": { 75 | "TC Stairs to Third Floor": "(Tubba_Castle_Key, 2)", 76 | "TC Table/Clock Room (1/2F) 1F": "True", 77 | "TC West Hall (2F)": "True", 78 | "TC Hidden Bedroom (2F)": "True" 79 | } 80 | }, 81 | { 82 | "region_name": "TC Stairs to Basement", 83 | "area_id": "15", 84 | "map_id": "4", 85 | "map_name": "Stairs to Basement", 86 | "locations": { 87 | "TC Stairs to Basement In SuperBlock": "can_hit_floating_blocks" 88 | }, 89 | "exits": { 90 | "TC Table/Clock Room (1/2F) 1F": "True", 91 | "TC Tubba Basement Lower": "True" 92 | } 93 | }, 94 | { 95 | "region_name": "TC Stairs Above Basement", 96 | "area_id": "15", 97 | "map_id": "5", 98 | "map_name": "Stairs Above Basement", 99 | "exits": { 100 | "TC Table/Clock Room (1/2F) 1F": "True", 101 | "TC Tubba Basement Upper": "Super_Boots" 102 | } 103 | }, 104 | { 105 | "region_name": "TC Tubba Basement Upper", 106 | "area_id": "15", 107 | "map_id": "6", 108 | "map_name": "Basement", 109 | "locations": { 110 | "TC Basement In Chest": "True" 111 | }, 112 | "exits": { 113 | "TC Tubba Basement Lower": "True" 114 | } 115 | }, 116 | { 117 | "region_name": "TC Tubba Basement Lower", 118 | "area_id": "15", 119 | "map_id": "6", 120 | "map_name": "Basement", 121 | "exits": { 122 | "TC Stairs to Basement": "True" 123 | } 124 | }, 125 | { 126 | "region_name": "TC Study (1F)", 127 | "area_id": "15", 128 | "map_id": "7", 129 | "map_name": "Study (1F)", 130 | "locations": { 131 | "TC Study (1F) On Table": "Boots" 132 | }, 133 | "exits": { 134 | "TC West Hall (1F)": "True" 135 | } 136 | }, 137 | { 138 | "region_name": "TC East Hall (1/2F) 2F", 139 | "area_id": "15", 140 | "map_id": "8", 141 | "map_name": "East Hall (1/2F)", 142 | "exits": { 143 | "TC Great Hall 2F": "True", 144 | "TC East Hall (1/2F) 1F": "can_use_ability_bow" 145 | } 146 | }, 147 | { 148 | "region_name": "TC East Hall (1/2F) 1F", 149 | "area_id": "15", 150 | "map_id": "8", 151 | "map_name": "East Hall (1/2F)", 152 | "exits": { 153 | "TC Great Hall 1F": "True", 154 | "TC East Hall (1/2F) 2F": "can_use_ability_bow" 155 | } 156 | }, 157 | { 158 | "region_name": "TC West Hall (2F)", 159 | "area_id": "15", 160 | "map_id": "9", 161 | "map_name": "West Hall (2F)", 162 | "exits": { 163 | "TC Table/Clock Room (1/2F) 2F": "True", 164 | "TC Great Hall 2F": "True", 165 | "TC Spike Trap Room (2F)": "True", 166 | "TC Sealed Room (2F)": "can_use_ability_bombette" 167 | } 168 | }, 169 | { 170 | "region_name": "TC Sealed Room (2F)", 171 | "area_id": "15", 172 | "map_id": "10", 173 | "map_name": "Sealed Room (2F)", 174 | "events": { 175 | "GF_DGB10_BoardedFloor3": "Super_Boots" 176 | }, 177 | "exits": { 178 | "TC West Hall (2F)": "True", 179 | "TC Covered Tables Room (1F)": "Super_Boots", 180 | "TC Covered Tables Room (1F) On Table": "Super_Boots" 181 | } 182 | }, 183 | { 184 | "region_name": "TC Covered Tables Room (1F)", 185 | "area_id": "15", 186 | "map_id": "11", 187 | "map_name": "Covered Tables Room (1F)", 188 | "exits": { 189 | "TC West Hall (1F)": "True", 190 | "TC Sealed Room (2F)": "'GF_DGB10_BoardedFloor3' and Boots" 191 | } 192 | }, 193 | { 194 | "region_name": "TC Covered Tables Room (1F) On Table", 195 | "area_id": "15", 196 | "map_id": "11", 197 | "map_name": "Covered Tables Room (1F)", 198 | "locations": { 199 | "TC Covered Tables Room (1F) On Table": "can_use_ability_parakarry" 200 | }, 201 | "exits": { 202 | "TC Covered Tables Room (1F)": "True", 203 | "TC Sealed Room (2F)": "'GF_DGB10_BoardedFloor3'" 204 | } 205 | }, 206 | { 207 | "region_name": "TC Spike Trap Room (2F)", 208 | "area_id": "15", 209 | "map_id": "12", 210 | "map_name": "Spike Trap Room (2F)", 211 | "locations": { 212 | "TC Spike Trap Room (2F) In Chest": "(can_use_ability_bow or can_use_ability_lakilester)" 213 | }, 214 | "exits": { 215 | "TC West Hall (2F)": "True" 216 | } 217 | }, 218 | { 219 | "region_name": "TC Hidden Bedroom (2F)", 220 | "area_id": "15", 221 | "map_id": "13", 222 | "map_name": "Hidden Bedroom (2F)", 223 | "locations": { 224 | "TC Hidden Bedroom (2F) In Hidden Room": "Boots and can_use_ability_parakarry", 225 | "TC Hidden Bedroom (2F) On Bed 1": "Boots and can_use_ability_parakarry", 226 | "TC Hidden Bedroom (2F) On Bed 2": "Boots and can_use_ability_parakarry", 227 | "TC Hidden Bedroom (2F) On Bed 3": "Boots and can_use_ability_parakarry", 228 | "TC Hidden Bedroom (2F) On Bed 4": "Boots and can_use_ability_parakarry", 229 | "TC Hidden Bedroom (2F) On Bed 5": "Boots and can_use_ability_parakarry", 230 | "TC Hidden Bedroom (2F) On Bed 6": "Boots and can_use_ability_parakarry" 231 | }, 232 | "exits": { 233 | "TC Table/Clock Room (1/2F) 2F": "True" 234 | } 235 | }, 236 | { 237 | "region_name": "TC Stairs to Third Floor", 238 | "area_id": "15", 239 | "map_id": "14", 240 | "map_name": "Stairs to Third Floor", 241 | "locations": { 242 | "TC Stairs to Third Floor Yellow Block": "can_hit_floating_blocks" 243 | }, 244 | "exits": { 245 | "TC Table/Clock Room (1/2F) 2F": "True", 246 | "TC West Hall (3F)": "True" 247 | } 248 | }, 249 | { 250 | "region_name": "TC West Hall (3F)", 251 | "area_id": "15", 252 | "map_id": "15", 253 | "map_name": "West Hall (3F)", 254 | "exits": { 255 | "TC Stairs to Third Floor": "True", 256 | "TC Save Room (3F)": "(Tubba_Castle_Key, 3)", 257 | "TC Sleeping Clubbas Room (3F)": "True" 258 | } 259 | }, 260 | { 261 | "region_name": "TC Sleeping Clubbas Room (3F)", 262 | "area_id": "15", 263 | "map_id": "16", 264 | "map_name": "Sleeping Clubbas Room (3F)", 265 | "locations": { 266 | "TC Sleeping Clubbas Room (3F) On Pedestal": "can_climb_steps" 267 | }, 268 | "exits": { 269 | "TC West Hall (3F)": "True" 270 | } 271 | }, 272 | { 273 | "region_name": "TC Save Room (3F)", 274 | "area_id": "15", 275 | "map_id": "17", 276 | "map_name": "Save Room (3F)", 277 | "exits": { 278 | "TC West Hall (3F)": "True", 279 | "TC Great Hall 3F": "True" 280 | } 281 | }, 282 | { 283 | "region_name": "TC Master Bedroom (3F)", 284 | "area_id": "15", 285 | "map_id": "18", 286 | "map_name": "Master Bedroom (3F)", 287 | "events": { 288 | "MysticalKey": "True" 289 | }, 290 | "exits": { 291 | "TC Great Hall 3F": "True" 292 | } 293 | } 294 | ] 295 | -------------------------------------------------------------------------------- /data/regions/shiver_region.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "SR Shiver City Mayor Area", 4 | "area_id": "20", 5 | "map_id": "0", 6 | "map_name": "Shiver City Mayor Area", 7 | "events": { 8 | "RF_Ch7_MurderMysteryStarted": "True", 9 | "RF_Ch7_MurderMysterySolved": "'RF_Ch7_SpokeWithHerringway'", 10 | "StarPiece_SAM_1": "True", 11 | "StarPiece_SAM_8": "True" 12 | }, 13 | "locations": { 14 | "SR Shiver City Mayor Area Hidden Panel": "can_flip_panels and 'RF_Ch7_MurderMysterySolved'", 15 | "SR Shiver City Mayor Area Mayor Penguin Gift": "'RF_Ch7_MurderMysterySolved' and 'RF_Ch7_GotSnowmanScarf'", 16 | "SR Shiver City Mayor Area Mayor Penguin Letter Reward": "can_use_ability_parakarry and Letter_to_Mayor_Penguin", 17 | "SR Shiver City Mayor Area Chest In House": "Boots" 18 | }, 19 | "exits": { 20 | "SR Shiver City Center": "True" 21 | } 22 | }, 23 | { 24 | "region_name": "SR Shiver City Center", 25 | "area_id": "20", 26 | "map_id": "1", 27 | "map_name": "Shiver City Center", 28 | "events": { 29 | "StarPiece_SAM_1": "True", 30 | "StarPiece_SAM_8": "True" 31 | }, 32 | "locations": { 33 | "SR Shiver City Center Toad House Breakfast": "True", 34 | "SR Shiver City Center Snowmen Gift 1": "'MF_SAM_04_UnlockedShiverMountain'", 35 | "SR Shiver City Center Snowmen Gift 2": "'MF_SAM_04_UnlockedShiverMountain'", 36 | "SR Shiver City Center Snowmen Gift 3": "'MF_SAM_04_UnlockedShiverMountain'", 37 | "SR Shiver City Center Snowmen Gift 4": "'MF_SAM_04_UnlockedShiverMountain'", 38 | "SR Shiver City Center Snowmen Gift 5": "'MF_SAM_04_UnlockedShiverMountain'", 39 | "SR Shiver City Center Shop Item 1": "'RF_Ch7_MurderMysterySolved'", 40 | "SR Shiver City Center Shop Item 2": "'RF_Ch7_MurderMysterySolved'", 41 | "SR Shiver City Center Shop Item 3": "'RF_Ch7_MurderMysterySolved'", 42 | "SR Shiver City Center Shop Item 4": "'RF_Ch7_MurderMysterySolved'", 43 | "SR Shiver City Center Shop Item 5": "'RF_Ch7_MurderMysterySolved'", 44 | "SR Shiver City Center Shop Item 6": "'RF_Ch7_MurderMysterySolved'" 45 | }, 46 | "exits": { 47 | "SR Shiver City Mayor Area": "True", 48 | "SR Shiver City Pond Area": "True", 49 | "SR Shiver City Center Pipe": "can_climb_steps" 50 | } 51 | }, 52 | { 53 | "region_name": "SR Shiver City Center Pipe", 54 | "area_id": "20", 55 | "map_id": "1", 56 | "map_name": "Shiver City Center", 57 | "exits": { 58 | "TTT Frozen Room (B3) East Pipe": "can_reenter_vertical_pipes", 59 | "SR Shiver City Center": "True" 60 | } 61 | }, 62 | { 63 | "region_name": "SR Road to Shiver Snowfield", 64 | "area_id": "20", 65 | "map_id": "2", 66 | "map_name": "Road to Shiver Snowfield", 67 | "exits": { 68 | "SR Shiver City Pond Area": "True", 69 | "SR Shiver Snowfield": "True" 70 | } 71 | }, 72 | { 73 | "region_name": "SR Shiver Snowfield", 74 | "area_id": "20", 75 | "map_id": "3", 76 | "map_name": "Shiver Snowfield", 77 | "events": { 78 | "MF_SAM_04_UnlockedShiverMountain": "Snowman_Bucket and Snowman_Scarf" 79 | }, 80 | "locations": { 81 | "SR Shiver Snowfield Hidden Panel": "can_flip_panels", 82 | "SR Shiver Snowfield In Tree Left": "Hammer", 83 | "SR Shiver Snowfield Behind Tree Right": "True" 84 | }, 85 | "exits": { 86 | "SR Road to Shiver Snowfield": "True", 87 | "SR Path to Starborn Valley West": "True", 88 | "SR Shiver Mountain Passage West": "'MF_SAM_04_UnlockedShiverMountain'" 89 | } 90 | }, 91 | { 92 | "region_name": "SR Path to Starborn Valley West", 93 | "area_id": "20", 94 | "map_id": "4", 95 | "map_name": "Path to Starborn Valley", 96 | "locations": { 97 | "SR Path to Starborn Valley Behind Icicle": "True" 98 | }, 99 | "exits": { 100 | "SR Shiver Snowfield": "True", 101 | "SR Path to Starborn Valley East": "can_climb_steps" 102 | } 103 | }, 104 | { 105 | "region_name": "SR Path to Starborn Valley East", 106 | "area_id": "20", 107 | "map_id": "4", 108 | "map_name": "Path to Starborn Valley", 109 | "locations": { 110 | "SR Path to Starborn Valley Hidden Block": "can_see_hidden_blocks and can_hit_floating_blocks" 111 | }, 112 | "exits": { 113 | "SR Starborn Valley": "True", 114 | "SR Path to Starborn Valley West": "True" 115 | } 116 | }, 117 | { 118 | "region_name": "SR Starborn Valley", 119 | "area_id": "20", 120 | "map_id": "5", 121 | "map_name": "Starborn Valley", 122 | "events": { 123 | "RF_Ch7_GotSnowmanScarf": "can_climb_steps" 124 | }, 125 | "locations": { 126 | "SR Starborn Valley Merle Gift": "can_climb_steps", 127 | "SR Starborn Valley Frost T. Letter Reward": "can_use_ability_parakarry and Letter_to_Frost_T" 128 | }, 129 | "exits": { 130 | "SR Path to Starborn Valley East": "True" 131 | } 132 | }, 133 | { 134 | "region_name": "SR Shiver Mountain Passage West", 135 | "area_id": "20", 136 | "map_id": "6", 137 | "map_name": "Shiver Mountain Passage", 138 | "exits": { 139 | "SR Shiver Snowfield": "True", 140 | "SR Shiver Mountain Passage East": "Super_Boots" 141 | } 142 | }, 143 | { 144 | "region_name": "SR Shiver Mountain Passage East", 145 | "area_id": "20", 146 | "map_id": "6", 147 | "map_name": "Shiver Mountain Passage", 148 | "locations": { 149 | "SR Shiver Mountain Passage Hidden Block": "can_see_hidden_blocks and Ultra_Boots" 150 | }, 151 | "exits": { 152 | "SR Shiver Mountain Hills West": "True", 153 | "SR Shiver Mountain Passage West": "Super_Boots" 154 | } 155 | }, 156 | { 157 | "region_name": "SR Shiver Mountain Hills West", 158 | "area_id": "20", 159 | "map_id": "7", 160 | "map_name": "Shiver Mountain Hills", 161 | "events": { 162 | "RF_DefeatedFirstDuplighost": "can_use_ability_kooper and Hammer" 163 | }, 164 | "locations": { 165 | "SR Shiver Mountain Hills Bottom Path": "can_climb_steps" 166 | }, 167 | "exits": { 168 | "SR Shiver Mountain Passage East": "True", 169 | "SR Shiver Mountain Hills East": "'RF_DefeatedFirstDuplighost' and can_climb_steps" 170 | } 171 | }, 172 | { 173 | "region_name": "SR Shiver Mountain Hills East", 174 | "area_id": "20", 175 | "map_id": "7", 176 | "map_name": "Shiver Mountain Hills", 177 | "events": { 178 | "RF_DefeatedFirstDuplighost": "Hammer" 179 | }, 180 | "locations": { 181 | "SR Shiver Mountain Hills In SuperBlock": "can_climb_steps and can_hit_floating_blocks" 182 | }, 183 | "exits": { 184 | "SR Shiver Mountain Tunnel": "True", 185 | "SR Shiver Mountain Hills West": "('RF_DefeatedFirstDuplighost' or can_climb_steps)" 186 | } 187 | }, 188 | { 189 | "region_name": "SR Shiver Mountain Tunnel", 190 | "area_id": "20", 191 | "map_id": "8", 192 | "map_name": "Shiver Mountain Tunnel", 193 | "locations": { 194 | "SR Shiver Mountain Tunnel Socket 1": "True", 195 | "SR Shiver Mountain Tunnel Socket 2": "True", 196 | "SR Shiver Mountain Tunnel Socket 3": "True" 197 | }, 198 | "exits": { 199 | "SR Shiver Mountain Hills East": "True", 200 | "SR Shiver Mountain Peaks Bottom": "True" 201 | } 202 | }, 203 | { 204 | "region_name": "SR Shiver Mountain Peaks Bottom", 205 | "area_id": "20", 206 | "map_id": "9", 207 | "map_name": "Shiver Mountain Peaks", 208 | "exits": { 209 | "SR Shiver Mountain Tunnel": "True", 210 | "SR Shiver Mountain Peaks": "Star_Stone and can_climb_steps", 211 | "SR Merlar's Sanctuary": "can_use_ability_bombette" 212 | } 213 | }, 214 | { 215 | "region_name": "SR Shiver Mountain Peaks", 216 | "area_id": "20", 217 | "map_id": "9", 218 | "map_name": "Shiver Mountain Peaks", 219 | "locations": { 220 | "SR Shiver Mountain Peaks Red Block": "can_hit_floating_blocks", 221 | "SR Shiver Mountain Peaks Left Ledge": "True" 222 | }, 223 | "exits": { 224 | "CP Palace Entrance South": "True", 225 | "SR Shiver Mountain Peaks Bottom": "True" 226 | } 227 | }, 228 | { 229 | "region_name": "SR Shiver City Pond Area", 230 | "area_id": "20", 231 | "map_id": "10", 232 | "map_name": "Shiver City Pond Area", 233 | "events": { 234 | "RF_Ch7_SpokeWithHerringway": "'RF_Ch7_MurderMysteryStarted' and Warehouse_Key and Boots", 235 | "StarPiece_SAM_1": "True", 236 | "StarPiece_SAM_8": "True" 237 | }, 238 | "locations": { 239 | "SR Shiver City Pond Area In Frozen Pond": "(can_use_ability_bombette or Super_Boots) and can_use_ability_sushie and 'RF_Ch7_MurderMysteryStarted'" 240 | }, 241 | "exits": { 242 | "SR Shiver City Center": "True", 243 | "SR Road to Shiver Snowfield": "'RF_Ch7_MurderMysterySolved'" 244 | } 245 | }, 246 | { 247 | "region_name": "SR Merlar's Sanctuary", 248 | "area_id": "20", 249 | "map_id": "11", 250 | "map_name": "Merlar's Sanctuary", 251 | "locations": { 252 | "SR Merlar's Sanctuary On Pedestal": "can_climb_steps" 253 | }, 254 | "exits": { 255 | "SR Shiver Mountain Peaks Bottom": "True" 256 | } 257 | } 258 | ] 259 | -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | from typing import TYPE_CHECKING, Set 2 | import base64 3 | 4 | from NetUtils import ClientStatus 5 | import worlds._bizhawk as bizhawk 6 | from worlds._bizhawk.client import BizHawkClient 7 | from .data.ItemList import item_multiples_ids 8 | from .data.data import * 9 | from .Locations import location_name_to_id 10 | from .items import item_id_prefix 11 | from .data.LocationsList import location_groups, location_table 12 | 13 | if TYPE_CHECKING: 14 | from worlds._bizhawk.context import BizHawkClientContext 15 | 16 | 17 | class PaperMarioClient(BizHawkClient): 18 | game = "Paper Mario" 19 | system = "N64" 20 | patch_suffix = ".appm64" 21 | local_checked_locations: Set[int] 22 | 23 | def __init__(self) -> None: 24 | super().__init__() 25 | self.local_checked_locations = set() 26 | self.autohint_stored = set() 27 | self.autohint_released = set() 28 | 29 | async def validate_rom(self, ctx: "BizHawkClientContext") -> bool: 30 | from CommonClient import logger 31 | 32 | try: 33 | # Check ROM name/patch version 34 | game_name = await bizhawk.read(ctx.bizhawk_ctx, [(0x20, 0x14, "ROM")]) 35 | if game_name[0].decode("ascii") != "PAPER MARIO ": 36 | return False 37 | 38 | pmr_magic_value = await bizhawk.read(ctx.bizhawk_ctx, [(TABLE_ADDRESS, 0x4, "ROM")]) 39 | if pmr_magic_value[0] != MAGIC_VALUE: 40 | logger.info("This Paper Mario ROM is invalid.") 41 | return False 42 | 43 | except UnicodeDecodeError: 44 | return False 45 | except bizhawk.RequestFailedError: 46 | return False # Should verify on the next pass 47 | 48 | ctx.game = self.game 49 | ctx.items_handling = 0b101 # get start inventory from server, do not get local items from server 50 | ctx.want_slot_data = True # get slot data 51 | ctx.watcher_timeout = 1 # loop watcher every second 52 | return True 53 | 54 | async def set_auth(self, ctx: "BizHawkClientContext") -> None: 55 | auth_raw = (await bizhawk.read(ctx.bizhawk_ctx, [(AUTH_ADDRESS, 16, "ROM")]))[0] 56 | ctx.auth = base64.b64encode(auth_raw).decode("utf-8") 57 | 58 | async def game_watcher(self, ctx: "BizHawkClientContext") -> None: 59 | from CommonClient import logger 60 | try: 61 | 62 | read_state = await bizhawk.read(ctx.bizhawk_ctx, [(MODE_ADDRESS, 1, "RDRAM"), 63 | (MF_START_ADDRESS, 0x224, "RDRAM"), 64 | (GF_START_ADDRESS, 0x107, "RDRAM"), 65 | (ITM_RCV_SEQ, 2, "RDRAM"), 66 | (AREA_ADDRESS, 1, "RDRAM"), 67 | (MAP_ADDRESS, 1, "RDRAM"), 68 | (STAR_SPIRITS_COUNT, 1, "RDRAM"), 69 | (UIR_START_ADDRESS, len(item_table), "RDRAM")]) 70 | 71 | # check for current state before sending or receiving anything 72 | game_mode = int.from_bytes(read_state[0], "big") 73 | 74 | if game_mode == GAME_MODE_WORLD: 75 | mod_flags = read_state[1] 76 | game_flags = read_state[2] 77 | received_items = int.from_bytes(bytearray(read_state[3]), "big") 78 | current_location = (int.from_bytes(bytearray(read_state[4]), "big"), 79 | int.from_bytes(bytearray(read_state[5]), "big")) 80 | star_spirits = int.from_bytes(bytearray(read_state[6]), "big") 81 | uir_flags = read_state[7] 82 | 83 | # RECEIVE ITEMS 84 | # Add item to buffer as u16 int 85 | if received_items < len(ctx.items_received): 86 | next_item = ctx.items_received[received_items] 87 | 88 | # check what id actually needs sent to the game, as some items have multiples 89 | # items in the player's game will have the biggest available ID, 90 | # when receiving we give the smallest 91 | item_id = next_item.item - item_id_prefix 92 | if item_id in item_multiples_ids.keys(): 93 | repeat_id = 0 94 | 95 | # magical seeds need to skip seed 1 if only 3 seeds required, 1 and 2 if 2 seeds required, etc 96 | if item_id == item_table["Magical Seed"][2]: 97 | repeat_id = min(4 - ctx.slot_data["magical_seeds"], 3) # maximum of 3 in case command used 98 | 99 | base_item_id = item_id 100 | item_id = item_multiples_ids[base_item_id][repeat_id] 101 | while repeat_id < len(item_multiples_ids[base_item_id]) and uir_flags[item_id]: 102 | item_id = item_multiples_ids[base_item_id][repeat_id] 103 | repeat_id += 1 104 | 105 | item_id = item_id << 16 106 | await bizhawk.guarded_write(ctx.bizhawk_ctx, 107 | [(KEY_RECV_BUFFER, item_id.to_bytes(4, "big"), "RDRAM")], 108 | [(KEY_RECV_BUFFER, (0).to_bytes(4, "big"), "RDRAM"), 109 | (ITM_RCV_SEQ, read_state[3], "RDRAM")]) 110 | 111 | # SEND ITEMS 112 | mf_bytes = bytearray(mod_flags) 113 | gf_bytes = bytearray(game_flags) 114 | locs_to_send = set() 115 | 116 | # check through the checks table for checked checks 117 | for location, data in checks_table.items(): 118 | loc_id = location_name_to_id[location] 119 | loc_val = False 120 | 121 | # these flags are weird and require a helper function to do funny math, refer to doc linked in data 122 | if data[0] == "MF": 123 | loc_val = get_flag_value(data[1], mf_bytes) 124 | elif data[0] == "GF": 125 | loc_val = get_flag_value(data[1], gf_bytes) 126 | 127 | if loc_val and loc_id in ctx.server_locations: 128 | locs_to_send.add(loc_id) 129 | 130 | # Send locations if there are any to send 131 | if locs_to_send != self.local_checked_locations: 132 | self.local_checked_locations = locs_to_send 133 | 134 | if locs_to_send is not None: 135 | await ctx.send_msgs([{"cmd": "LocationChecks", "locations": list(locs_to_send)}]) 136 | 137 | # AUTO HINTING 138 | # Build list of items to scout 139 | hints = [] 140 | for loc in location_groups["AutoHint"]: 141 | if loc not in self.autohint_released.union(self.autohint_stored): 142 | if current_location == (location_table[loc][2], location_table[loc][3]): 143 | # don't hint Rowf items you can't see/buy yet 144 | if (location_table[loc][2], location_table[loc][3]) == (1, 2): 145 | # grab the set number from the location name 146 | rowf_set = int(loc[len("TT Plaza District Rowf's Shop Set ")]) 147 | if rowf_set <= star_spirits + 1: 148 | hints.append(loc) 149 | else: 150 | hints.append(loc) 151 | 152 | # make sure we aren't scouting anything that's already been checked 153 | hints = [location_name_to_id[loc] for loc in hints if 154 | location_name_to_id[loc] in ctx.missing_locations and 155 | location_name_to_id[loc] not in ctx.locations_checked] 156 | 157 | # scout the auto hint locations in the current area and store what we have scouted but not hinted 158 | if hints: 159 | await ctx.send_msgs([{"cmd": "LocationScouts", "locations": hints, "create_as_hint": 0}]) 160 | self.autohint_stored = hints 161 | 162 | # send the hints for the unsent progression items in the stored locations 163 | # these will have already been scouted 164 | if self.autohint_stored: 165 | await ctx.send_msgs([{ 166 | "cmd": "LocationScouts", 167 | "locations": [loc for loc, n_item in ctx.locations_info.items() if n_item.flags & 0b001], 168 | "create_as_hint": 2}]) 169 | self.autohint_released.update(self.autohint_stored) 170 | self.autohint_stored = hints 171 | 172 | # GOAL CHECKING 173 | if not ctx.finished_game and (get_flag_value(GOAL_FLAG, mf_bytes)): 174 | await ctx.send_msgs([{"cmd": "StatusUpdate", "status": ClientStatus.CLIENT_GOAL}]) 175 | 176 | except bizhawk.RequestFailedError: 177 | # Exit handler and return to main loop to reconnect. 178 | pass 179 | -------------------------------------------------------------------------------- /data/palettes_meta.py: -------------------------------------------------------------------------------- 1 | # below from PMR palettes table in sqlite db; more info at https://github.com/icebound777/PMR-SeedGenerator/tree/main/db 2 | # Also pulled menu color dictionary from 3 | # https://github.com/Pronyo-Chan/paper-mario-randomizer-web-app/blob/master/app/src/app/utilities/constants.ts 4 | from ..options import StatusMenuColorPalette 5 | 6 | 7 | # sprite id dbkey palette_count 8 | palette_table = { 9 | "Mario": (1, 2751463425, 8), 10 | "Peach": (2, 2751463426, 4), 11 | "Hammer1": (3, 2751463427, 4), 12 | "Hammer2": (4, 2751463428, 5), 13 | "Hammer3": (5, 2751463429, 6), 14 | "01_0_Goombario": (6, 2751528960, 6), 15 | "02_0_Kooper": (7, 2751594496, 5), 16 | "03_0_Bombette": (8, 2751660032, 7), 17 | "04_0_Parakarry": (9, 2751725568, 4), 18 | "05_0_Bow": (10, 2751791104, 5), 19 | "06_0_Watt": (11, 2751856640, 4), 20 | "07_0_Sushie": (12, 2751922176, 5), 21 | "08_0_Lakilester": (13, 2751987712, 5), 22 | "20_0_Twink": (14, 2753560576, 4), 23 | "26_0_Goomba": (15, 2753953792, 5), 24 | "28_0_Paragoomba": (16, 2754084864, 5), 25 | "27_0_SpikedGoomba": (17, 2754019328, 5), 26 | "29_1_Koopa": (18, 2754150401, 5), 27 | "2C_0_Bobomb": (19, 2754347008, 5), 28 | "2D_0_BulletBill": (20, 2754412544, 4), 29 | "2D_1_BombshellBill": (21, 2754412545, 4), 30 | "31_0_Pokey": (22, 2754674688, 6), 31 | "31_1_PokeyMummy": (23, 2754674689, 3), 32 | "32_0_Bandit": (24, 2754740224, 5), 33 | "33_0_BuzzyBeetles": (25, 2754805760, 7), 34 | "34_0_Swooper": (26, 2754871296, 3), 35 | "35_0_StoneChomp": (27, 2754936832, 5), 36 | "37_0_PiranhaPlant": (28, 2755067904, 5), 37 | "39_0_Clubba": (29, 2755198976, 6), 38 | "39_1_IceClubba": (30, 2755198977, 5), 39 | "6E_0_ShyGuySquad": (31, 2755330048, 7), 40 | "3B_1_ShyGuy": (32, 2755330049, 7), 41 | "3B_2_ShyGuy": (33, 2755330050, 7), 42 | "3B_3_ShyGuy": (34, 2755330051, 7), 43 | "3B_4_ShyGuy": (35, 2755330052, 7), 44 | "70_0_StiltGuy": (36, 2758803456, 7), 45 | "72_0_ShyGuyStack": (37, 2758934528, 7), 46 | "78_0_TankGuy": (38, 2759327744, 7), 47 | "3B_5_AntiGuy": (39, 2755330053, 3), 48 | "3E_0_PyroGuy": (40, 2755526656, 4), 49 | "3F_0_SpyGuy": (41, 2755592192, 5), 50 | "40_0_MediGuy": (42, 2755657728, 5), 51 | "42_0_JungleGuy": (43, 2755788800, 7), 52 | "47_0_KentCKoopa": (44, 2756116480, 6), 53 | "48_0_CrazyDayzee": (45, 2756182017, 10), 54 | "5A_0_HammerBros": (46, 2757361664, 5), 55 | "60_0_Kammy": (47, 2757754880, 4), 56 | "62_GoombaBros": (48, 2757885952, 5), 57 | "63_GoombaKing": (49, 2757951488, 6), 58 | "66_0_KoopaBros": (50, 2758148096, 9), 59 | "66_1_KoopaBros": (51, 2758148097, 9), 60 | "66_2_KoopaBros": (52, 2758148098, 9), 61 | "66_3_KoopaBros": (53, 2758148099, 9), 62 | "68_0_TutanKoopa": (54, 2758279168, 3), 63 | "6A_0_TubbaBlubba": (55, 2758410240, 3), 64 | "6D_0_LanternGhost": (56, 2758606848, 7), 65 | "79_0_LavaPiranha": (57, 2759393280, 2), 66 | "7C_0_HuffNPuff": (58, 2759589888, 3), 67 | "7F_0_CrystalKing": (59, 2759786496, 4), 68 | "82_0_Luigi": (60, 2759983104, 6), 69 | "83_0_Toad": (61, 2760048640, 8), 70 | "83_1_Toad": (62, 2760048641, 8), 71 | "83_2_Toad": (63, 2760048642, 8), 72 | "83_3_Toad": (64, 2760048643, 8), 73 | "84_0_Toadette": (65, 2760114176, 8), 74 | "84_1_Toadette": (66, 2760114177, 8), 75 | "84_2_Toadette": (67, 2760114178, 8), 76 | "84_3_Toadette": (68, 2760114179, 8), 77 | "86_0_ToadKid": (69, 2760245248, 8), 78 | "86_1_ToadKid": (70, 2760245249, 8), 79 | "86_2_ToadKid": (71, 2760245250, 8), 80 | "86_3_ToadKid": (72, 2760245251, 8), 81 | "88_0_Harry": (73, 2760376320, 5), 82 | "93_0_Dryite": (74, 2761097216, 6), 83 | "93_1_Dryite": (75, 2761097217, 6), 84 | "93_2_Dryite": (76, 2761097218, 6), 85 | "93_3_Dryite": (77, 2761097219, 6), 86 | "9A_0_Penguin": (78, 2761555968, 4), 87 | "9E_0_Goombaria": (79, 2761818112, 4), 88 | "A5_0_Merlon": (80, 2762276864, 5), 89 | "AE_0_RipCheato": (81, 2762866688, 4), 90 | "AF_0_ChuckQuizmo": (82, 2762932224, 2), 91 | "B2_0_Merlow": (83, 2763128832, 7), 92 | "C1_0_GourmetGuy": (84, 2764111872, 8), 93 | "C2_0_VillageLeader": (85, 2764177408, 5), 94 | "C5_0_Tolielip": (86, 2764374016, 5), 95 | "CC_0_Lakilulu": (87, 2764832768, 3), 96 | "CD_0_Ninji": (88, 2764898304, 6) 97 | } 98 | 99 | 100 | # below from https://github.com/icebound777/PMR-SeedGenerator/blob/main/metadata/palettes_meta.py 101 | 102 | mario_n_partner_sprite_names = [ 103 | "Mario", 104 | "01_0_Goombario", 105 | "02_0_Kooper", 106 | "03_0_Bombette", 107 | "04_0_Parakarry", 108 | "05_0_Bow", 109 | "06_0_Watt", 110 | "07_0_Sushie", 111 | "08_0_Lakilester", 112 | ] 113 | 114 | boss_sprite_names = [ 115 | "47_0_KentCKoopa", 116 | "60_0_Kammy", 117 | "62_GoombaBros", 118 | "63_0_GoombaKing", 119 | "66_0_KoopaBros", 120 | "66_1_KoopaBros", 121 | "66_2_KoopaBros", 122 | "66_3_KoopaBros", 123 | "68_0_TutanKoopa", 124 | "6A_0_TubbaBlubba", 125 | "6D_0_LanternGhost", 126 | "78_0_TankGuy", 127 | "79_0_LavaPiranha", 128 | "7C_0_HuffNPuff", 129 | "7F_0_CrystalKing", 130 | ] 131 | 132 | enemy_sprite_names = [ 133 | "26_0_Goomba", 134 | "27_0_SpikedGoomba", 135 | "28_0_Paragoomba", 136 | "29_1_Koopa", 137 | "2C_0_Bobomb", 138 | "2D_0_BulletBill", 139 | "2D_1_BombshellBill", 140 | "31_0_Pokey", 141 | "31_1_PokeyMummy", 142 | "32_0_Bandit", 143 | "33_0_BuzzyBeetles", 144 | "34_0_Swooper", 145 | "35_0_StoneChomp", 146 | "37_0_PiranhaPlant", 147 | "39_0_Clubba", 148 | "39_1_IceClubba", 149 | "3B_1_ShyGuy", 150 | "3B_2_ShyGuy", 151 | "3B_3_ShyGuy", 152 | "3B_4_ShyGuy", 153 | "3B_5_AntiGuy", 154 | "3E_0_PyroGuy", 155 | "3F_0_SpyGuy", 156 | "40_0_MediGuy", 157 | "42_0_JungleGuy", 158 | "48_0_CrazyDayzee", 159 | "5A_0_HammerBros", 160 | "6E_0_ShyGuySquad", 161 | "70_0_StiltGuy", 162 | "72_0_ShyGuyStack", 163 | ] 164 | 165 | hammer_sprite_names = [ 166 | "Hammer1", 167 | "Hammer2", 168 | "Hammer3", 169 | ] 170 | 171 | # NPCs with these sprites don't necessarily have their vanilla palette on id 0, 172 | # instead their vanilla id is encoded as the middle part of their key name, 173 | # which has to be extracted in the random palettes module 174 | special_vanilla_palette_ids = [ 175 | # "3B_0_ShyGuy", 176 | "3B_1_ShyGuy", 177 | "3B_2_ShyGuy", 178 | "3B_3_ShyGuy", 179 | "3B_4_ShyGuy", 180 | # "66_0_KoopaBros", 181 | "66_1_KoopaBros", 182 | "66_2_KoopaBros", 183 | "66_3_KoopaBros", 184 | # "83_0_Toad", 185 | "83_1_Toad", 186 | "83_2_Toad", 187 | "83_3_Toad", 188 | # "84_0_Toadette", 189 | "84_1_Toadette", 190 | "84_2_Toadette", 191 | "84_3_Toadette", 192 | # "86_0_ToadKid", 193 | "86_1_ToadKid", 194 | "86_2_ToadKid", 195 | "86_3_ToadKid", 196 | # "93_0_Dryite", 197 | "93_1_Dryite", 198 | "93_2_Dryite", 199 | "93_3_Dryite", 200 | ] 201 | 202 | # option mode colorA colorB 203 | MENU_COLORS = { 204 | StatusMenuColorPalette.option_Default: (0, 0xEBE677FF, 0x8E5A25FF), 205 | StatusMenuColorPalette.option_Blue: (0, 0x8D8FFFFF, 0x2B4566FF), 206 | StatusMenuColorPalette.option_Green: (0, 0xAAD080FF, 0x477B53FF), 207 | StatusMenuColorPalette.option_Teal: (0, 0x8ED4ECFF, 0x436245FF), 208 | StatusMenuColorPalette.option_Brown: (0, 0xD7BF74FF, 0x844632FF), 209 | StatusMenuColorPalette.option_Purple: (0, 0xB797B7FF, 0x62379AFF), 210 | StatusMenuColorPalette.option_Grey: (0, 0xC0C0C0FF, 0x404040FF), 211 | StatusMenuColorPalette.option_Random_Pick: (1, 0xEBE677FF, 0x8E5A25FF), 212 | StatusMenuColorPalette.option_Animated: (2, 0xEBE677FF, 0x8E5A25FF) 213 | } -------------------------------------------------------------------------------- /data/regions/koopa_bros_fortress.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "KBF Fortress Exterior", 4 | "area_id": "7", 5 | "map_id": "0", 6 | "map_name": "Fortress Exterior", 7 | "exits": { 8 | "KR Path to Fortress 2": "True", 9 | "KBF Left Tower 1F": "True" 10 | } 11 | }, 12 | { 13 | "region_name": "KBF Fortress Exterior South", 14 | "area_id": "7", 15 | "map_id": "0", 16 | "map_name": "Fortress Exterior", 17 | "exits": { 18 | "KBF Right Tower": "True" 19 | } 20 | }, 21 | { 22 | "region_name": "KBF Fortress Exterior East", 23 | "area_id": "7", 24 | "map_id": "0", 25 | "map_name": "Fortress Exterior", 26 | "locations": { 27 | "KBF Fortress Exterior Chest Behind Fortress": "True" 28 | }, 29 | "exits": { 30 | "KBF Right Tower": "can_use_ability_bombette" 31 | } 32 | }, 33 | { 34 | "region_name": "KBF Fortress Exterior Upper", 35 | "area_id": "7", 36 | "map_id": "0", 37 | "map_name": "Fortress Exterior", 38 | "locations": { 39 | "KBF Fortress Exterior Chest On Ledge": "True" 40 | }, 41 | "exits": { 42 | "KR Path to Fortress 2 Upper": "True" 43 | } 44 | }, 45 | { 46 | "region_name": "KBF Left Tower 1F", 47 | "area_id": "7", 48 | "map_id": "1", 49 | "map_name": "Left Tower", 50 | "locations": { 51 | "KBF Left Tower Koopa Troopa Reward": "True" 52 | }, 53 | "exits": { 54 | "KBF Fortress Exterior": "True", 55 | "KBF Left Stairway 1F": "(Koopa_Fortress_Key, 1)" 56 | } 57 | }, 58 | { 59 | "region_name": "KBF Left Tower 2F", 60 | "area_id": "7", 61 | "map_id": "1", 62 | "map_name": "Left Tower", 63 | "events": { 64 | "MF_TRD01_RaisedStairs": "can_hit_grounded_switches" 65 | }, 66 | "exits": { 67 | "KBF Left Stairway 2F": "True", 68 | "KBF Left Tower 3F": "'MF_TRD01_RaisedStairs'" 69 | } 70 | }, 71 | { 72 | "region_name": "KBF Left Tower 3F", 73 | "area_id": "7", 74 | "map_id": "1", 75 | "map_name": "Left Tower", 76 | "locations": { 77 | "KBF Left Tower Top Of Tower": "can_climb_steps" 78 | }, 79 | "exits": { 80 | "KBF Left Tower 2F": "'MF_TRD01_RaisedStairs'", 81 | "KBF Fortress Battlement West Door": "True" 82 | } 83 | }, 84 | { 85 | "region_name": "KBF Left Stairway 1F", 86 | "area_id": "7", 87 | "map_id": "2", 88 | "map_name": "Left Stairway", 89 | "exits": { 90 | "KBF Left Tower 1F": "True", 91 | "KBF Central Hall 1F": "True", 92 | "KBF Left Stairway 1F Platform": "'MF_TRD02_LoweredStairs' and Boots", 93 | "KBF Left Stairway 2F": "'MF_TRD02_LoweredStairs' and Boots" 94 | } 95 | }, 96 | { 97 | "region_name": "KBF Left Stairway 2F", 98 | "area_id": "7", 99 | "map_id": "2", 100 | "map_name": "Left Stairway", 101 | "events": { 102 | "MF_TRD02_LoweredStairs": "can_hit_grounded_switches" 103 | }, 104 | "exits": { 105 | "KBF Left Tower 2F": "(Koopa_Fortress_Key, 4)", 106 | "KBF Central Hall 2F Left Door": "True", 107 | "KBF Left Stairway 1F": "'MF_TRD02_LoweredStairs'", 108 | "KBF Left Stairway 1F Platform": "'MF_TRD02_LoweredStairs' and can_climb_steps" 109 | } 110 | }, 111 | { 112 | "region_name": "KBF Left Stairway 1F Platform", 113 | "area_id": "7", 114 | "map_id": "2", 115 | "map_name": "Left Stairway", 116 | "events": { 117 | "RF_TRD_02_OpenedLeftJail": "can_use_ability_bombette" 118 | }, 119 | "exits": { 120 | "KBF Central Hall 1F Left Jail": "'RF_TRD_02_OpenedLeftJail'", 121 | "KBF Left Stairway 1F": "True", 122 | "KBF Left Stairway 2F": "'MF_TRD02_LoweredStairs' and can_climb_steps" 123 | } 124 | }, 125 | { 126 | "region_name": "KBF Central Hall 1F Left Jail", 127 | "area_id": "7", 128 | "map_id": "3", 129 | "map_name": "Central Hall", 130 | "locations": { 131 | "KBF Central Hall Left Cell": "True" 132 | }, 133 | "exits": { 134 | "KBF Left Stairway 1F Platform": "can_climb_steps" 135 | } 136 | }, 137 | { 138 | "region_name": "KBF Central Hall 1F", 139 | "area_id": "7", 140 | "map_id": "3", 141 | "map_name": "Central Hall", 142 | "locations": { 143 | "KBF Central Hall Center Cell": "True", 144 | "KBF Central Hall Right Cell": "can_use_ability_bombette" 145 | }, 146 | "exits": { 147 | "KBF Left Stairway 1F": "True", 148 | "KBF Right Starway 1F": "True" 149 | } 150 | }, 151 | { 152 | "region_name": "KBF Central Hall 2F Left Door", 153 | "area_id": "7", 154 | "map_id": "3", 155 | "map_name": "Central Hall", 156 | "exits": { 157 | "KBF Left Stairway 2F": "can_climb_steps", 158 | "KBF Central Hall 2F Right Door": "can_climb_steps and (can_use_ability_kooper or can_use_ability_parakarry)", 159 | "KBF Central Hall 1F": "True" 160 | } 161 | }, 162 | { 163 | "region_name": "KBF Central Hall 2F Right Door", 164 | "area_id": "7", 165 | "map_id": "3", 166 | "map_name": "Central Hall", 167 | "exits": { 168 | "KBF Right Starway 2F": "can_climb_steps", 169 | "KBF Central Hall 2F Left Door": "can_climb_steps and (can_use_ability_kooper or can_use_ability_parakarry)", 170 | "KBF Central Hall 1F": "True" 171 | } 172 | }, 173 | { 174 | "region_name": "KBF Right Starway 1F", 175 | "area_id": "7", 176 | "map_id": "4", 177 | "map_name": "Right Starway", 178 | "events": { 179 | "MF_TRD04_LoweredStairs": "can_hit_grounded_switches" 180 | }, 181 | "exits": { 182 | "KBF Central Hall 1F": "True", 183 | "KBF Right Tower": "(Koopa_Fortress_Key, 2)", 184 | "KBF Right Starway B1F": "'MF_TRD04_LoweredStairs'" 185 | } 186 | }, 187 | { 188 | "region_name": "KBF Right Starway B1F", 189 | "area_id": "7", 190 | "map_id": "4", 191 | "map_name": "Right Starway", 192 | "events": { 193 | "MF_TRD04_LoweredStairs": "True" 194 | }, 195 | "exits": { 196 | "KBF Dungeon Trap": "True", 197 | "KBF Fortress Jail Outer": "True", 198 | "KBF Right Starway 1F": "'MF_TRD04_LoweredStairs' and can_climb_steps" 199 | } 200 | }, 201 | { 202 | "region_name": "KBF Right Starway 2F", 203 | "area_id": "7", 204 | "map_id": "4", 205 | "map_name": "Right Starway", 206 | "exits": { 207 | "KBF Central Hall 2F Right Door": "(Koopa_Fortress_Key, 3)", 208 | "KBF Right Tower": "True" 209 | } 210 | }, 211 | { 212 | "region_name": "KBF Right Tower", 213 | "area_id": "7", 214 | "map_id": "5", 215 | "map_name": "Right Tower", 216 | "exits": { 217 | "KBF Right Starway 1F": "True", 218 | "KBF Fortress Exterior South": "True", 219 | "KBF Fortress Exterior East": "can_use_ability_bombette", 220 | "KBF Right Starway 2F": "True", 221 | "KBF Fortress Jail Inner": "can_hit_floating_blocks" 222 | } 223 | }, 224 | { 225 | "region_name": "KBF Fortress Jail Outer", 226 | "area_id": "7", 227 | "map_id": "6", 228 | "map_name": "Jail", 229 | "exits": { 230 | "KBF Right Starway B1F": "True", 231 | "KBF Fortress Jail Inner": "can_use_ability_bombette" 232 | } 233 | }, 234 | { 235 | "region_name": "KBF Fortress Jail Inner", 236 | "area_id": "7", 237 | "map_id": "6", 238 | "map_name": "Jail", 239 | "locations": { 240 | "KBF Jail Bombette Partner": "True" 241 | }, 242 | "exits": { 243 | "KBF Fortress Jail Outer": "can_use_ability_bombette" 244 | } 245 | }, 246 | { 247 | "region_name": "KBF Dungeon Trap", 248 | "area_id": "7", 249 | "map_id": "7", 250 | "map_name": "Dungeon Trap", 251 | "exits": { 252 | "KBF Right Starway B1F": "True", 253 | "KBF Dungeon Fire Room": "True" 254 | } 255 | }, 256 | { 257 | "region_name": "KBF Dungeon Fire Room", 258 | "area_id": "7", 259 | "map_id": "8", 260 | "map_name": "Dungeon Fire Room", 261 | "locations": { 262 | "KBF Dungeon Fire Room On The Ground": "can_climb_steps" 263 | }, 264 | "exits": { 265 | "KBF Dungeon Trap": "True" 266 | } 267 | }, 268 | { 269 | "region_name": "KBF Fortress Battlement West Door", 270 | "area_id": "7", 271 | "map_id": "9", 272 | "map_name": "Battlement", 273 | "exits": { 274 | "KBF Left Tower 3F": "True", 275 | "KBF Fortress Battlement": "can_climb_steps" 276 | } 277 | }, 278 | { 279 | "region_name": "KBF Fortress Battlement East Door", 280 | "area_id": "7", 281 | "map_id": "9", 282 | "map_name": "Battlement", 283 | "exits": { 284 | "KBF Boss Battle Room": "True", 285 | "KBF Fortress Battlement": "can_climb_steps" 286 | } 287 | }, 288 | { 289 | "region_name": "KBF Fortress Battlement", 290 | "area_id": "7", 291 | "map_id": "9", 292 | "map_name": "Battlement", 293 | "exits": { 294 | "KBF Fortress Battlement Rock": "True", 295 | "KBF Fortress Battlement West Door": "can_climb_steps", 296 | "KBF Fortress Battlement East Door": "can_climb_steps" 297 | } 298 | }, 299 | { 300 | "region_name": "KBF Fortress Battlement Rock", 301 | "area_id": "7", 302 | "map_id": "9", 303 | "map_name": "Battlement", 304 | "locations": { 305 | "KBF Battlement Block Behind Rock": "can_use_ability_bombette and can_hit_floating_blocks" 306 | }, 307 | "exits": { 308 | "KBF Fortress Battlement": "Boots" 309 | } 310 | }, 311 | { 312 | "region_name": "KBF Boss Battle Room", 313 | "area_id": "7", 314 | "map_id": "10", 315 | "map_name": "Boss Battle Room", 316 | "events": { 317 | "STARSPIRIT_1": "(Hammer or Bombette or Watt)", 318 | "STARSPIRIT": "(Hammer or Bombette or Watt)", 319 | "MF_Ch1_RescuedStarSpirit": "(Hammer or Bombette or Watt)" 320 | }, 321 | "exits": { 322 | "KBF Fortress Battlement East Door": "True" 323 | } 324 | } 325 | ] 326 | -------------------------------------------------------------------------------- /data/regions/goomba_village.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "region_name": "GR Forest Clearing", 4 | "area_id": "0", 5 | "map_id": "0", 6 | "map_name": "Forest Clearing", 7 | "locations": { 8 | "GR Forest Clearing Hidden Panel": "can_flip_panels" 9 | }, 10 | "exits": { 11 | "GR Goomba Village": "True" 12 | } 13 | }, 14 | { 15 | "region_name": "GR Goomba Village Exit East", 16 | "area_id": "0", 17 | "map_id": "1", 18 | "map_name": "Goomba Village", 19 | "exits": { 20 | "GR Goomba Road 1": "True", 21 | "GR Goomba Village": "(Hammer or can_use_ability_bombette)" 22 | } 23 | }, 24 | { 25 | "region_name": "GR Goomba Village", 26 | "area_id": "0", 27 | "map_id": "1", 28 | "map_name": "Goomba Village", 29 | "events": { 30 | "StarPiece_KMR_1": "True", 31 | "StarPiece_KMR_8": "True" 32 | }, 33 | "locations": { 34 | "GR Goomba Village On The Balcony": "'RF_FixedVeranda'", 35 | "GR Goomba Village Goompa Koopa Koot Favor": "'FAVOR_2_01_active'", 36 | "GR Goomba Village Goompa Gift": "True", 37 | "GR Goomba Village Goombaria Dolly Reward": "Dolly", 38 | "GR Goomba Village Goompa Letter Reward": "can_use_ability_parakarry and Letter_to_Goompa", 39 | "GR Goomba Village Goompapa Letter Reward 1": "can_use_ability_parakarry and Letter_to_Goompapa_1", 40 | "GR Goomba Village Goompapa Letter Reward 2": "can_use_ability_parakarry and Letter_to_Goompapa_2", 41 | "GR Goomba Village Goomnut Tree": "can_shake_trees", 42 | "GR Goomba Village Goombario Partner": "True", 43 | "GR Goomba Village Bush Bottom Right": "True" 44 | }, 45 | "exits": { 46 | "GR Forest Clearing": "True", 47 | "GR Behind the Village East": "True", 48 | "GR Bottom of the Cliff West": "'RF_BrokenVeranda'", 49 | "GR Goomba Village Exit East": "(Hammer or can_use_ability_bombette)", 50 | "GR Goomba Village Pipe": "'GF_TIK01_WarpPipes' and Boots" 51 | } 52 | }, 53 | { 54 | "region_name": "GR Goomba Village Pipe", 55 | "area_id": "0", 56 | "map_id": "1", 57 | "map_name": "Goomba Village", 58 | "exits": { 59 | "TTT Warp Zone 1 (B1) Goomba Village Pipe": "can_reenter_vertical_pipes", 60 | "GR Goomba Village": "True" 61 | } 62 | }, 63 | { 64 | "region_name": "GR Behind the Village West", 65 | "area_id": "0", 66 | "map_id": "4", 67 | "map_name": "Behind the Village", 68 | "exits": { 69 | "GR Bottom of the Cliff East": "True", 70 | "GR Behind the Village East": "can_climb_steps" 71 | } 72 | }, 73 | { 74 | "region_name": "GR Behind the Village East", 75 | "area_id": "0", 76 | "map_id": "4", 77 | "map_name": "Behind the Village", 78 | "locations": { 79 | "GR Behind the Village On Ledge": "Boots", 80 | "GR Behind the Village In Tree": "can_shake_trees and Boots" 81 | }, 82 | "exits": { 83 | "GR Goomba Village": "True", 84 | "GR Behind the Village West": "True" 85 | } 86 | }, 87 | { 88 | "region_name": "GR Bottom of the Cliff West", 89 | "area_id": "0", 90 | "map_id": "2", 91 | "map_name": "Bottom of the Cliff", 92 | "events": { 93 | "RF_FixedVeranda": "(Hammer or can_use_ability_bombette)" 94 | }, 95 | "locations": { 96 | "GR Bottom of the Cliff Block On Ground": "can_hit_grounded_blocks", 97 | "GR Bottom of the Cliff In Tree": "can_shake_trees" 98 | }, 99 | "exits": { 100 | "GR Jr. Troopa's Playground": "True", 101 | "GR Bottom of the Cliff East": "(Hammer or can_use_ability_bombette)" 102 | } 103 | }, 104 | { 105 | "region_name": "GR Bottom of the Cliff East", 106 | "area_id": "0", 107 | "map_id": "2", 108 | "map_name": "Bottom of the Cliff", 109 | "events": { 110 | "RF_FixedVeranda": "(Hammer or can_use_ability_bombette)" 111 | }, 112 | "locations": { 113 | "GR Bottom of the Cliff Hidden Panel": "can_flip_panels", 114 | "GR Bottom of the Cliff Above Stone Block": "Super_Hammer and can_see_hidden_blocks and can_hit_floating_blocks", 115 | "GR Bottom of the Cliff Floating Coin 1": "can_climb_steps", 116 | "GR Bottom of the Cliff Floating Coin 2": "can_climb_steps", 117 | "GR Bottom of the Cliff Floating Coin 3": "can_climb_steps", 118 | "GR Bottom of the Cliff Floating Coin 4": "can_climb_steps", 119 | "GR Bottom of the Cliff Upper Ledge": "can_climb_steps" 120 | }, 121 | "exits": { 122 | "GR Behind the Village West": "True", 123 | "GR Bottom of the Cliff West": "(Hammer or can_use_ability_bombette)" 124 | } 125 | }, 126 | { 127 | "region_name": "GR Jr. Troopa's Playground", 128 | "area_id": "0", 129 | "map_id": "3", 130 | "map_name": "Jr. Troopa's Playground", 131 | "locations": { 132 | "GR Jr. Troopa's Playground In Tree Left": "can_shake_trees", 133 | "GR Jr. Troopa's Playground In Tree Top": "can_shake_trees", 134 | "GR Jr. Troopa's Playground In Tree Right": "can_shake_trees", 135 | "GR Jr. Troopa's Playground Bush Right": "True", 136 | "GR Jr. Troopa's Playground Bush Bottom Right": "True", 137 | "GR Jr. Troopa's Playground Bush Top 1": "True", 138 | "GR Jr. Troopa's Playground Bush Top 2": "True", 139 | "GR Jr. Troopa's Playground Bush Center": "True", 140 | "GR Jr. Troopa's Playground Bush Top Left": "True", 141 | "GR Jr. Troopa's Playground In Hammer Bush": "True", 142 | "GR Jr. Troopa's Playground In MultiCoinBlock": "can_hit_grounded_blocks" 143 | }, 144 | "exits": { 145 | "GR Bottom of the Cliff West": "True" 146 | } 147 | }, 148 | { 149 | "region_name": "GR Goomba Road 1", 150 | "area_id": "0", 151 | "map_id": "7", 152 | "map_name": "Goomba Road 1", 153 | "locations": { 154 | "GR Goomba Road 1 Yellow Block Left": "can_hit_floating_blocks", 155 | "GR Goomba Road 1 Yellow Block Right": "can_hit_floating_blocks" 156 | }, 157 | "exits": { 158 | "GR Goomba Village Exit East": "True", 159 | "GR Goomba Road 2": "True" 160 | } 161 | }, 162 | { 163 | "region_name": "GR Goomba Road 2", 164 | "area_id": "0", 165 | "map_id": "5", 166 | "map_name": "Goomba Road 2", 167 | "locations": { 168 | "GR Goomba Road 2 On the Sign": "True", 169 | "GR Goomba Road 2 Red Block": "can_hit_floating_blocks" 170 | }, 171 | "exits": { 172 | "GR Goomba Road 1": "True", 173 | "GR Goomba Road 3 West": "True" 174 | } 175 | }, 176 | { 177 | "region_name": "GR Goomba Road 3 West", 178 | "area_id": "0", 179 | "map_id": "6", 180 | "map_name": "Goomba Road 3", 181 | "events": { 182 | "RF_BeatGoombaBros": "True" 183 | }, 184 | "exits": { 185 | "GR Goomba Road 2": "True", 186 | "GR Goomba Road 3 East": "can_climb_steps" 187 | } 188 | }, 189 | { 190 | "region_name": "GR Goomba Road 3 East", 191 | "area_id": "0", 192 | "map_id": "6", 193 | "map_name": "Goomba Road 3", 194 | "exits": { 195 | "GR Goomba Road 4": "True", 196 | "GR Goomba Road 3 West": "True" 197 | } 198 | }, 199 | { 200 | "region_name": "GR Goomba Road 4", 201 | "area_id": "0", 202 | "map_id": "10", 203 | "map_name": "Goomba Road 4", 204 | "exits": { 205 | "GR Goomba Road 3 East": "True", 206 | "GR Goomba King's Castle West": "True" 207 | } 208 | }, 209 | { 210 | "region_name": "GR Goomba King's Castle West", 211 | "area_id": "0", 212 | "map_id": "9", 213 | "map_name": "Goomba King's Castle", 214 | "events": { 215 | "RF_BeatGoombaKing": "'RF_BeatGoombaBros' and can_hit_grounded_switches" 216 | }, 217 | "locations": { 218 | "GR Goomba King's Castle In Tree Left Of Fortress": "can_shake_trees" 219 | }, 220 | "exits": { 221 | "GR Goomba Road 4": "True", 222 | "GR Goomba King's Castle East": "'RF_BeatGoombaKing' or open_prologue" 223 | } 224 | }, 225 | { 226 | "region_name": "GR Goomba King's Castle East", 227 | "area_id": "0", 228 | "map_id": "9", 229 | "map_name": "Goomba King's Castle", 230 | "locations": { 231 | "GR Goomba King's Castle Hidden Panel": "can_flip_panels", 232 | "GR Goomba King's Castle Hidden Yellow Block": "can_hit_grounded_blocks and can_hit_floating_blocks", 233 | "GR Goomba King's Castle In Tree Right Of Cliff": "can_shake_trees" 234 | }, 235 | "exits": { 236 | "GR Toad Town Entrance West": "True", 237 | "GR Goomba King's Castle West": "'RF_BeatGoombaKing' or open_prologue" 238 | } 239 | }, 240 | { 241 | "region_name": "GR Toad Town Entrance West", 242 | "area_id": "0", 243 | "map_id": "8", 244 | "map_name": "Toad Town Entrance", 245 | "exits": { 246 | "GR Goomba King's Castle East": "True", 247 | "GR Toad Town Entrance East": "True" 248 | } 249 | }, 250 | { 251 | "region_name": "GR Toad Town Entrance East", 252 | "area_id": "0", 253 | "map_id": "8", 254 | "map_name": "Toad Town Entrance", 255 | "locations": { 256 | "GR Toad Town Entrance Yellow Block": "can_hit_floating_blocks", 257 | "GR Toad Town Entrance Chest On Roof": "can_shake_trees and can_climb_steps" 258 | }, 259 | "exits": { 260 | "TT Gate District": "True", 261 | "GR Toad Town Entrance West": "can_climb_steps" 262 | } 263 | }, 264 | { 265 | "region_name": "GR Mario's House", 266 | "area_id": "0", 267 | "map_id": "11", 268 | "map_name": "Mario's House", 269 | "locations": { 270 | "GR Mario's House Luigi Koopa Koot Favor": "'FAVOR_2_03_active'" 271 | }, 272 | "exits": { 273 | "GR Mario's House Warp Pipe": "Boots" 274 | } 275 | }, 276 | { 277 | "region_name": "GR Mario's House Warp Pipe", 278 | "area_id": "0", 279 | "map_id": "11", 280 | "map_name": "Mario's House", 281 | "exits": { 282 | "GR Mario's House": "True", 283 | "TT Gate District Mario's House Pipe": "can_reenter_vertical_pipes" 284 | } 285 | } 286 | ] 287 | --------------------------------------------------------------------------------