multiworld
This commit is contained in:
parent
ce19713209
commit
55a30aa91f
|
@ -38,6 +38,7 @@ def main():
|
|||
Alternatively, can be a ALttP Rom patched with a Link
|
||||
sprite that will be extracted.
|
||||
''')
|
||||
parser.add_argument('--names', default='', type=str)
|
||||
args = parser.parse_args()
|
||||
|
||||
# ToDo: Validate files further than mere existance
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import os
|
||||
import re
|
||||
import time
|
||||
import logging
|
||||
|
||||
from Utils import output_path
|
||||
from Utils import output_path, parse_names_string
|
||||
from Rom import LocalRom, Sprite, apply_rom_settings
|
||||
|
||||
|
||||
|
@ -26,7 +27,7 @@ def adjust(args):
|
|||
else:
|
||||
raise RuntimeError('Provided Rom is not a valid Link to the Past Randomizer Rom. Please provide one for adjusting.')
|
||||
|
||||
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, sprite)
|
||||
apply_rom_settings(rom, args.heartbeep, args.heartcolor, args.quickswap, args.fastmenu, args.disablemusic, sprite, parse_names_string(args.names))
|
||||
|
||||
rom.write_to_file(output_path('%s.sfc' % outfilebase))
|
||||
|
||||
|
|
|
@ -781,12 +781,13 @@ class Boss(object):
|
|||
return self.defeat_rule(state, self.player)
|
||||
|
||||
class Location(object):
|
||||
def __init__(self, player, name='', address=None, crystal=False, hint_text=None, parent=None):
|
||||
def __init__(self, player, name='', address=None, crystal=False, hint_text=None, parent=None, player_address=None):
|
||||
self.name = name
|
||||
self.parent_region = parent
|
||||
self.item = None
|
||||
self.crystal = crystal
|
||||
self.address = address
|
||||
self.player_address = player_address
|
||||
self.spot_type = 'Location'
|
||||
self.hint_text = hint_text if hint_text is not None else 'Hyrule'
|
||||
self.recursion_count = 0
|
||||
|
@ -1033,6 +1034,7 @@ class Spoiler(object):
|
|||
'accessibility': self.world.accessibility,
|
||||
'hints': self.world.hints,
|
||||
'keysanity': self.world.keysanity,
|
||||
'players': self.world.players
|
||||
}
|
||||
|
||||
def to_json(self):
|
||||
|
|
|
@ -255,7 +255,7 @@ def start():
|
|||
parser.add_argument('--shufflepalette', default=False, action='store_true')
|
||||
parser.add_argument('--shufflepots', default=False, action='store_true')
|
||||
parser.add_argument('--multi', default=1, type=lambda value: min(max(int(value), 1), 255))
|
||||
|
||||
parser.add_argument('--names', default='')
|
||||
parser.add_argument('--outputpath')
|
||||
args = parser.parse_args()
|
||||
|
||||
|
|
29
Gui.py
29
Gui.py
|
@ -13,7 +13,7 @@ from AdjusterMain import adjust
|
|||
from GuiUtils import ToolTips, set_icon, BackgroundTaskProgress
|
||||
from Main import main, __version__ as ESVersion
|
||||
from Rom import Sprite
|
||||
from Utils import is_bundled, local_path, output_path, open_file
|
||||
from Utils import is_bundled, local_path, output_path, open_file, parse_names_string
|
||||
|
||||
|
||||
def guiMain(args=None):
|
||||
|
@ -346,6 +346,9 @@ def guiMain(args=None):
|
|||
worldLabel = Label(bottomFrame, text='Worlds')
|
||||
worldVar = StringVar()
|
||||
worldSpinbox = Spinbox(bottomFrame, from_=1, to=100, width=5, textvariable=worldVar)
|
||||
namesLabel = Label(bottomFrame, text='Player names')
|
||||
namesVar = StringVar()
|
||||
namesEntry = Entry(bottomFrame, textvariable=namesVar)
|
||||
seedLabel = Label(bottomFrame, text='Seed #')
|
||||
seedVar = StringVar()
|
||||
seedEntry = Entry(bottomFrame, width=15, textvariable=seedVar)
|
||||
|
@ -356,6 +359,7 @@ def guiMain(args=None):
|
|||
def generateRom():
|
||||
guiargs = Namespace
|
||||
guiargs.multi = int(worldVar.get())
|
||||
guiargs.names = namesVar.get()
|
||||
guiargs.seed = int(seedVar.get()) if seedVar.get() else None
|
||||
guiargs.count = int(countVar.get()) if countVar.get() != '1' else None
|
||||
guiargs.mode = modeVar.get()
|
||||
|
@ -416,12 +420,18 @@ def guiMain(args=None):
|
|||
except Exception as e:
|
||||
messagebox.showerror(title="Error while creating seed", message=str(e))
|
||||
else:
|
||||
messagebox.showinfo(title="Success", message="Rom patched successfully")
|
||||
msgtxt = "Rom patched successfully"
|
||||
if guiargs.names:
|
||||
for player, name in parse_names_string(guiargs.names).items():
|
||||
msgtxt += "\nPlayer %d => %s" % (player, name)
|
||||
messagebox.showinfo(title="Success", message=msgtxt)
|
||||
|
||||
generateButton = Button(bottomFrame, text='Generate Patched Rom', command=generateRom)
|
||||
|
||||
worldLabel.pack(side=LEFT)
|
||||
worldSpinbox.pack(side=LEFT)
|
||||
namesLabel.pack(side=LEFT)
|
||||
namesEntry.pack(side=LEFT)
|
||||
seedLabel.pack(side=LEFT, padx=(5, 0))
|
||||
seedEntry.pack(side=LEFT)
|
||||
countLabel.pack(side=LEFT, padx=(5, 0))
|
||||
|
@ -502,10 +512,18 @@ def guiMain(args=None):
|
|||
fastMenuLabel2 = Label(fastMenuFrame2, text='Menu speed')
|
||||
fastMenuLabel2.pack(side=LEFT)
|
||||
|
||||
namesFrame2 = Frame(drowDownFrame2)
|
||||
namesLabel2 = Label(namesFrame2, text='Player names')
|
||||
namesVar2 = StringVar()
|
||||
namesEntry2 = Entry(namesFrame2, textvariable=namesVar2)
|
||||
|
||||
namesLabel2.pack(side=LEFT)
|
||||
namesEntry2.pack(side=LEFT)
|
||||
|
||||
heartbeepFrame2.pack(expand=True, anchor=E)
|
||||
heartcolorFrame2.pack(expand=True, anchor=E)
|
||||
fastMenuFrame2.pack(expand=True, anchor=E)
|
||||
namesFrame2.pack(expand=True, anchor=E)
|
||||
|
||||
bottomFrame2 = Frame(topFrame2)
|
||||
|
||||
|
@ -518,12 +536,17 @@ def guiMain(args=None):
|
|||
guiargs.disablemusic = bool(disableMusicVar.get())
|
||||
guiargs.rom = romVar2.get()
|
||||
guiargs.sprite = sprite
|
||||
guiargs.names = namesEntry2.get()
|
||||
try:
|
||||
adjust(args=guiargs)
|
||||
except Exception as e:
|
||||
messagebox.showerror(title="Error while creating seed", message=str(e))
|
||||
else:
|
||||
messagebox.showinfo(title="Success", message="Rom patched successfully")
|
||||
msgtxt = "Rom patched successfully"
|
||||
if guiargs.names:
|
||||
for player, name in parse_names_string(guiargs.names).items():
|
||||
msgtxt += "\nPlayer %d => %s" % (player, name)
|
||||
messagebox.showinfo(title="Success", message=msgtxt)
|
||||
|
||||
adjustButton = Button(bottomFrame2, text='Adjust Rom', command=adjustRom)
|
||||
|
||||
|
|
|
@ -340,8 +340,8 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None
|
|||
for exit in exits:
|
||||
ret.exits.append(Entrance(player, exit, ret))
|
||||
for location in locations:
|
||||
address, crystal, hint_text = location_table[location]
|
||||
ret.locations.append(Location(player, location, address, crystal, hint_text, ret))
|
||||
address, player_address, crystal, hint_text = location_table[location]
|
||||
ret.locations.append(Location(player, location, address, crystal, hint_text, ret, player_address))
|
||||
return ret
|
||||
|
||||
def mark_dark_world_regions(world):
|
||||
|
@ -406,236 +406,236 @@ default_shop_contents = {
|
|||
'Potion Shop': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
|
||||
}
|
||||
|
||||
location_table = {'Mushroom': (0x180013, False, 'in the woods'),
|
||||
'Bottle Merchant': (0x2EB18, False, 'with a merchant'),
|
||||
'Flute Spot': (0x18014A, False, 'underground'),
|
||||
'Sunken Treasure': (0x180145, False, 'underwater'),
|
||||
'Purple Chest': (0x33D68, False, 'from a box'),
|
||||
'Blind\'s Hideout - Top': (0xEB0F, False, 'in a basement'),
|
||||
'Blind\'s Hideout - Left': (0xEB12, False, 'in a basement'),
|
||||
'Blind\'s Hideout - Right': (0xEB15, False, 'in a basement'),
|
||||
'Blind\'s Hideout - Far Left': (0xEB18, False, 'in a basement'),
|
||||
'Blind\'s Hideout - Far Right': (0xEB1B, False, 'in a basement'),
|
||||
'Link\'s Uncle': (0x2DF45, False, 'with your uncle'),
|
||||
'Secret Passage': (0xE971, False, 'near your uncle'),
|
||||
'King Zora': (0xEE1C3, False, 'at a high price'),
|
||||
'Zora\'s Ledge': (0x180149, False, 'near Zora'),
|
||||
'Waterfall Fairy - Left': (0xE9B0, False, 'near a fairy'),
|
||||
'Waterfall Fairy - Right': (0xE9D1, False, 'near a fairy'),
|
||||
'King\'s Tomb': (0xE97A, False, 'alone in a cave'),
|
||||
'Floodgate Chest': (0xE98C, False, 'in the dam'),
|
||||
'Link\'s House': (0xE9BC, False, 'in your home'),
|
||||
'Kakariko Tavern': (0xE9CE, False, 'in the bar'),
|
||||
'Chicken House': (0xE9E9, False, 'near poultry'),
|
||||
'Aginah\'s Cave': (0xE9F2, False, 'with Aginah'),
|
||||
'Sahasrahla\'s Hut - Left': (0xEA82, False, 'near the elder'),
|
||||
'Sahasrahla\'s Hut - Middle': (0xEA85, False, 'near the elder'),
|
||||
'Sahasrahla\'s Hut - Right': (0xEA88, False, 'near the elder'),
|
||||
'Sahasrahla': (0x2F1FC, False, 'with the elder'),
|
||||
'Kakariko Well - Top': (0xEA8E, False, 'in a well'),
|
||||
'Kakariko Well - Left': (0xEA91, False, 'in a well'),
|
||||
'Kakariko Well - Middle': (0xEA94, False, 'in a well'),
|
||||
'Kakariko Well - Right': (0xEA97, False, 'in a well'),
|
||||
'Kakariko Well - Bottom': (0xEA9A, False, 'in a well'),
|
||||
'Blacksmith': (0x18002A, False, 'with the smith'),
|
||||
'Magic Bat': (0x180015, False, 'with the bat'),
|
||||
'Sick Kid': (0x339CF, False, 'with the sick'),
|
||||
'Hobo': (0x33E7D, False, 'with the hobo'),
|
||||
'Lost Woods Hideout': (0x180000, False, 'near a thief'),
|
||||
'Lumberjack Tree': (0x180001, False, 'in a hole'),
|
||||
'Cave 45': (0x180003, False, 'alone in a cave'),
|
||||
'Graveyard Cave': (0x180004, False, 'alone in a cave'),
|
||||
'Checkerboard Cave': (0x180005, False, 'alone in a cave'),
|
||||
'Mini Moldorm Cave - Far Left': (0xEB42, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Left': (0xEB45, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Right': (0xEB48, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Far Right': (0xEB4B, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Generous Guy': (0x180010, False, 'near Moldorms'),
|
||||
'Ice Rod Cave': (0xEB4E, False, 'in a frozen cave'),
|
||||
'Bonk Rock Cave': (0xEB3F, False, 'alone in a cave'),
|
||||
'Library': (0x180012, False, 'near books'),
|
||||
'Potion Shop': (0x180014, False, 'near potions'),
|
||||
'Lake Hylia Island': (0x180144, False, 'on an island'),
|
||||
'Maze Race': (0x180142, False, 'at the race'),
|
||||
'Desert Ledge': (0x180143, False, 'in the desert'),
|
||||
'Desert Palace - Big Chest': (0xE98F, False, 'in Desert Palace'),
|
||||
'Desert Palace - Torch': (0x180160, False, 'in Desert Palace'),
|
||||
'Desert Palace - Map Chest': (0xE9B6, False, 'in Desert Palace'),
|
||||
'Desert Palace - Compass Chest': (0xE9CB, False, 'in Desert Palace'),
|
||||
'Desert Palace - Big Key Chest': (0xE9C2, False, 'in Desert Palace'),
|
||||
'Desert Palace - Boss': (0x180151, False, 'with Lanmolas'),
|
||||
'Eastern Palace - Compass Chest': (0xE977, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Big Chest': (0xE97D, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Cannonball Chest': (0xE9B3, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Big Key Chest': (0xE9B9, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Map Chest': (0xE9F5, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Boss': (0x180150, False, 'with the Armos'),
|
||||
'Master Sword Pedestal': (0x289B0, False, 'at the pedestal'),
|
||||
'Hyrule Castle - Boomerang Chest': (0xE974, False, 'in Hyrule Castle'),
|
||||
'Hyrule Castle - Map Chest': (0xEB0C, False, 'in Hyrule Castle'),
|
||||
'Hyrule Castle - Zelda\'s Chest': (0xEB09, False, 'in Hyrule Castle'),
|
||||
'Sewers - Dark Cross': (0xE96E, False, 'in the sewers'),
|
||||
'Sewers - Secret Room - Left': (0xEB5D, False, 'in the sewers'),
|
||||
'Sewers - Secret Room - Middle': (0xEB60, False, 'in the sewers'),
|
||||
'Sewers - Secret Room - Right': (0xEB63, False, 'in the sewers'),
|
||||
'Sanctuary': (0xEA79, False, 'in Sanctuary'),
|
||||
'Castle Tower - Room 03': (0xEAB5, False, 'in Castle Tower'),
|
||||
'Castle Tower - Dark Maze': (0xEAB2, False, 'in Castle Tower'),
|
||||
'Old Man': (0xF69FA, False, 'with the old man'),
|
||||
'Spectacle Rock Cave': (0x180002, False, 'alone in a cave'),
|
||||
'Paradox Cave Lower - Far Left': (0xEB2A, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Left': (0xEB2D, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Right': (0xEB30, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Far Right': (0xEB33, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Middle': (0xEB36, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Upper - Left': (0xEB39, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Upper - Right': (0xEB3C, False, 'in a cave with seven chests'),
|
||||
'Spiral Cave': (0xE9BF, False, 'in spiral cave'),
|
||||
'Ether Tablet': (0x180016, False, 'at a monolith'),
|
||||
'Spectacle Rock': (0x180140, False, 'atop a rock'),
|
||||
'Tower of Hera - Basement Cage': (0x180162, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Map Chest': (0xE9AD, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Big Key Chest': (0xE9E6, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Compass Chest': (0xE9FB, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Big Chest': (0xE9F8, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Boss': (0x180152, False, 'with Moldorm'),
|
||||
'Pyramid': (0x180147, False, 'on the pyramid'),
|
||||
'Catfish': (0xEE185, False, 'with a catfish'),
|
||||
'Stumpy': (0x330C7, False, 'with tree boy'),
|
||||
'Digging Game': (0x180148, False, 'underground'),
|
||||
'Bombos Tablet': (0x180017, False, 'at a monolith'),
|
||||
'Hype Cave - Top': (0xEB1E, False, 'near a bat-like man'),
|
||||
'Hype Cave - Middle Right': (0xEB21, False, 'near a bat-like man'),
|
||||
'Hype Cave - Middle Left': (0xEB24, False, 'near a bat-like man'),
|
||||
'Hype Cave - Bottom': (0xEB27, False, 'near a bat-like man'),
|
||||
'Hype Cave - Generous Guy': (0x180011, False, 'with a bat-like man'),
|
||||
'Peg Cave': (0x180006, False, 'alone in a cave'),
|
||||
'Pyramid Fairy - Left': (0xE980, False, 'near a fairy'),
|
||||
'Pyramid Fairy - Right': (0xE983, False, 'near a fairy'),
|
||||
'Brewery': (0xE9EC, False, 'alone in a home'),
|
||||
'C-Shaped House': (0xE9EF, False, 'alone in a home'),
|
||||
'Chest Game': (0xEDA8, False, 'as a prize'),
|
||||
'Bumper Cave Ledge': (0x180146, False, 'on a ledge'),
|
||||
'Mire Shed - Left': (0xEA73, False, 'near sparks'),
|
||||
'Mire Shed - Right': (0xEA76, False, 'near sparks'),
|
||||
'Superbunny Cave - Top': (0xEA7C, False, 'in a connection'),
|
||||
'Superbunny Cave - Bottom': (0xEA7F, False, 'in a connection'),
|
||||
'Spike Cave': (0xEA8B, False, 'beyond spikes'),
|
||||
'Hookshot Cave - Top Right': (0xEB51, False, 'across pits'),
|
||||
'Hookshot Cave - Top Left': (0xEB54, False, 'across pits'),
|
||||
'Hookshot Cave - Bottom Right': (0xEB5A, False, 'across pits'),
|
||||
'Hookshot Cave - Bottom Left': (0xEB57, False, 'across pits'),
|
||||
'Floating Island': (0x180141, False, 'on an island'),
|
||||
'Mimic Cave': (0xE9C5, False, 'in a cave of mimicry'),
|
||||
'Swamp Palace - Entrance': (0xEA9D, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Map Chest': (0xE986, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Big Chest': (0xE989, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Compass Chest': (0xEAA0, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Big Key Chest': (0xEAA6, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - West Chest': (0xEAA3, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Flooded Room - Left': (0xEAA9, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Flooded Room - Right': (0xEAAC, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Waterfall Room': (0xEAAF, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Boss': (0x180154, False, 'with Arrghus'),
|
||||
'Thieves\' Town - Big Key Chest': (0xEA04, False, 'in Thieves\' Town'),
|
||||
'Thieves\' Town - Map Chest': (0xEA01, False, 'in Thieves\' Town'),
|
||||
'Thieves\' Town - Compass Chest': (0xEA07, False, 'in Thieves\' Town'),
|
||||
'Thieves\' Town - Ambush Chest': (0xEA0A, False, 'in Thieves\' Town'),
|
||||
'Thieves\' Town - Attic': (0xEA0D, False, 'in Thieves\' Town'),
|
||||
'Thieves\' Town - Big Chest': (0xEA10, False, 'in Thieves\' Town'),
|
||||
'Thieves\' Town - Blind\'s Cell': (0xEA13, False, 'in Thieves\' Town'),
|
||||
'Thieves\' Town - Boss': (0x180156, False, 'with Blind'),
|
||||
'Skull Woods - Compass Chest': (0xE992, False, 'in Skull Woods'),
|
||||
'Skull Woods - Map Chest': (0xE99B, False, 'in Skull Woods'),
|
||||
'Skull Woods - Big Chest': (0xE998, False, 'in Skull Woods'),
|
||||
'Skull Woods - Pot Prison': (0xE9A1, False, 'in Skull Woods'),
|
||||
'Skull Woods - Pinball Room': (0xE9C8, False, 'in Skull Woods'),
|
||||
'Skull Woods - Big Key Chest': (0xE99E, False, 'in Skull Woods'),
|
||||
'Skull Woods - Bridge Room': (0xE9FE, False, 'near Mothula'),
|
||||
'Skull Woods - Boss': (0x180155, False, 'with Mothula'),
|
||||
'Ice Palace - Compass Chest': (0xE9D4, False, 'in Ice Palace'),
|
||||
'Ice Palace - Freezor Chest': (0xE995, False, 'in Ice Palace'),
|
||||
'Ice Palace - Big Chest': (0xE9AA, False, 'in Ice Palace'),
|
||||
'Ice Palace - Iced T Room': (0xE9E3, False, 'in Ice Palace'),
|
||||
'Ice Palace - Spike Room': (0xE9E0, False, 'in Ice Palace'),
|
||||
'Ice Palace - Big Key Chest': (0xE9A4, False, 'in Ice Palace'),
|
||||
'Ice Palace - Map Chest': (0xE9DD, False, 'in Ice Palace'),
|
||||
'Ice Palace - Boss': (0x180157, False, 'with Kholdstare'),
|
||||
'Misery Mire - Big Chest': (0xEA67, False, 'in Misery Mire'),
|
||||
'Misery Mire - Map Chest': (0xEA6A, False, 'in Misery Mire'),
|
||||
'Misery Mire - Main Lobby': (0xEA5E, False, 'in Misery Mire'),
|
||||
'Misery Mire - Bridge Chest': (0xEA61, False, 'in Misery Mire'),
|
||||
'Misery Mire - Spike Chest': (0xE9DA, False, 'in Misery Mire'),
|
||||
'Misery Mire - Compass Chest': (0xEA64, False, 'in Misery Mire'),
|
||||
'Misery Mire - Big Key Chest': (0xEA6D, False, 'in Misery Mire'),
|
||||
'Misery Mire - Boss': (0x180158, False, 'with Vitreous'),
|
||||
'Turtle Rock - Compass Chest': (0xEA22, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Roller Room - Left': (0xEA1C, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Roller Room - Right': (0xEA1F, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Chain Chomps': (0xEA16, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Big Key Chest': (0xEA25, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Big Chest': (0xEA19, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Crystaroller Room': (0xEA34, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Bottom Left': (0xEA31, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Bottom Right': (0xEA2E, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Top Left': (0xEA2B, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Top Right': (0xEA28, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Boss': (0x180159, False, 'with Trinexx'),
|
||||
'Palace of Darkness - Shooter Room': (0xEA5B, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - The Arena - Bridge': (0xEA3D, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Stalfos Basement': (0xEA49, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Big Key Chest': (0xEA37, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - The Arena - Ledge': (0xEA3A, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Map Chest': (0xEA52, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Compass Chest': (0xEA43, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Basement - Left': (0xEA4C, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Basement - Right': (0xEA4F, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Maze - Top': (0xEA55, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Maze - Bottom': (0xEA58, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Big Chest': (0xEA40, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Harmless Hellway': (0xEA46, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Boss': (0x180153, False, 'with Helmasaur King'),
|
||||
'Ganons Tower - Bob\'s Torch': (0x180161, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Hope Room - Left': (0xEAD9, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Hope Room - Right': (0xEADC, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Tile Room': (0xEAE2, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Compass Room - Top Left': (0xEAE5, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Compass Room - Top Right': (0xEAE8, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Compass Room - Bottom Left': (0xEAEB, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Compass Room - Bottom Right': (0xEAEE, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - DMs Room - Top Left': (0xEAB8, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - DMs Room - Top Right': (0xEABB, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - DMs Room - Bottom Left': (0xEABE, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - DMs Room - Bottom Right': (0xEAC1, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Map Chest': (0xEAD3, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Firesnake Room': (0xEAD0, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Randomizer Room - Top Left': (0xEAC4, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Randomizer Room - Top Right': (0xEAC7, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Randomizer Room - Bottom Left': (0xEACA, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Randomizer Room - Bottom Right': (0xEACD, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Bob\'s Chest': (0xEADF, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Big Chest': (0xEAD6, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Big Key Room - Left': (0xEAF4, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Big Key Room - Right': (0xEAF7, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Big Key Chest': (0xEAF1, False, 'in Ganon\'s Tower'),
|
||||
'Ganons Tower - Mini Helmasaur Room - Left': (0xEAFD, False, 'atop Ganon\'s Tower'),
|
||||
'Ganons Tower - Mini Helmasaur Room - Right': (0xEB00, False, 'atop Ganon\'s Tower'),
|
||||
'Ganons Tower - Pre-Moldorm Chest': (0xEB03, False, 'atop Ganon\'s Tower'),
|
||||
'Ganons Tower - Validation Chest': (0xEB06, False, 'atop Ganon\'s Tower'),
|
||||
'Ganon': (None, False, 'from me'),
|
||||
'Agahnim 1': (None, False, 'from Ganon\'s wizardry form'),
|
||||
'Agahnim 2': (None, False, 'from Ganon\'s wizardry form'),
|
||||
'Floodgate': (None, False, None),
|
||||
'Frog': (None, False, None),
|
||||
'Missing Smith': (None, False, None),
|
||||
'Dark Blacksmith Ruins': (None, False, None),
|
||||
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], True, 'Eastern Palace'),
|
||||
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], True, 'Desert Palace'),
|
||||
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], True, 'Tower of Hera'),
|
||||
'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], True, 'Palace of Darkness'),
|
||||
'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], True, 'Swamp Palace'),
|
||||
'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], True, 'Thieves\' Town'),
|
||||
'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], True, 'Skull Woods'),
|
||||
'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], True, 'Ice Palace'),
|
||||
'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], True, 'Misery Mire'),
|
||||
'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], True, 'Turtle Rock')}
|
||||
location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
|
||||
'Bottle Merchant': (0x2eb18, 0x186339, False, 'with a merchant'),
|
||||
'Flute Spot': (0x18014a, 0x18633d, False, 'underground'),
|
||||
'Sunken Treasure': (0x180145, 0x186354, False, 'underwater'),
|
||||
'Purple Chest': (0x33d68, 0x186359, False, 'from a box'),
|
||||
"Blind's Hideout - Top": (0xeb0f, 0x1862e3, False, 'in a basement'),
|
||||
"Blind's Hideout - Left": (0xeb12, 0x1862e6, False, 'in a basement'),
|
||||
"Blind's Hideout - Right": (0xeb15, 0x1862e9, False, 'in a basement'),
|
||||
"Blind's Hideout - Far Left": (0xeb18, 0x1862ec, False, 'in a basement'),
|
||||
"Blind's Hideout - Far Right": (0xeb1b, 0x1862ef, False, 'in a basement'),
|
||||
"Link's Uncle": (0x2df45, 0x18635f, False, 'with your uncle'),
|
||||
'Secret Passage': (0xe971, 0x186145, False, 'near your uncle'),
|
||||
'King Zora': (0xee1c3, 0x186360, False, 'at a high price'),
|
||||
"Zora's Ledge": (0x180149, 0x186358, False, 'near Zora'),
|
||||
'Waterfall Fairy - Left': (0xe9b0, 0x186184, False, 'near a fairy'),
|
||||
'Waterfall Fairy - Right': (0xe9d1, 0x1861a5, False, 'near a fairy'),
|
||||
"King's Tomb": (0xe97a, 0x18614e, False, 'alone in a cave'),
|
||||
'Floodgate Chest': (0xe98c, 0x186160, False, 'in the dam'),
|
||||
"Link's House": (0xe9bc, 0x186190, False, 'in your home'),
|
||||
'Kakariko Tavern': (0xe9ce, 0x1861a2, False, 'in the bar'),
|
||||
'Chicken House': (0xe9e9, 0x1861bd, False, 'near poultry'),
|
||||
"Aginah's Cave": (0xe9f2, 0x1861c6, False, 'with Aginah'),
|
||||
"Sahasrahla's Hut - Left": (0xea82, 0x186256, False, 'near the elder'),
|
||||
"Sahasrahla's Hut - Middle": (0xea85, 0x186259, False, 'near the elder'),
|
||||
"Sahasrahla's Hut - Right": (0xea88, 0x18625c, False, 'near the elder'),
|
||||
'Sahasrahla': (0x2f1fc, 0x186365, False, 'with the elder'),
|
||||
'Kakariko Well - Top': (0xea8e, 0x186262, False, 'in a well'),
|
||||
'Kakariko Well - Left': (0xea91, 0x186265, False, 'in a well'),
|
||||
'Kakariko Well - Middle': (0xea94, 0x186268, False, 'in a well'),
|
||||
'Kakariko Well - Right': (0xea97, 0x18626b, False, 'in a well'),
|
||||
'Kakariko Well - Bottom': (0xea9a, 0x18626e, False, 'in a well'),
|
||||
'Blacksmith': (0x18002a, 0x186366, False, 'with the smith'),
|
||||
'Magic Bat': (0x180015, 0x18635e, False, 'with the bat'),
|
||||
'Sick Kid': (0x339cf, 0x186367, False, 'with the sick'),
|
||||
'Hobo': (0x33e7d, 0x186368, False, 'with the hobo'),
|
||||
'Lost Woods Hideout': (0x180000, 0x186348, False, 'near a thief'),
|
||||
'Lumberjack Tree': (0x180001, 0x186349, False, 'in a hole'),
|
||||
'Cave 45': (0x180003, 0x18634b, False, 'alone in a cave'),
|
||||
'Graveyard Cave': (0x180004, 0x18634c, False, 'alone in a cave'),
|
||||
'Checkerboard Cave': (0x180005, 0x18634d, False, 'alone in a cave'),
|
||||
'Mini Moldorm Cave - Far Left': (0xeb42, 0x186316, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Left': (0xeb45, 0x186319, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Right': (0xeb48, 0x18631c, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Far Right': (0xeb4b, 0x18631f, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Generous Guy': (0x180010, 0x18635a, False, 'near Moldorms'),
|
||||
'Ice Rod Cave': (0xeb4e, 0x186322, False, 'in a frozen cave'),
|
||||
'Bonk Rock Cave': (0xeb3f, 0x186313, False, 'alone in a cave'),
|
||||
'Library': (0x180012, 0x18635c, False, 'near books'),
|
||||
'Potion Shop': (0x180014, 0x18635d, False, 'near potions'),
|
||||
'Lake Hylia Island': (0x180144, 0x186353, False, 'on an island'),
|
||||
'Maze Race': (0x180142, 0x186351, False, 'at the race'),
|
||||
'Desert Ledge': (0x180143, 0x186352, False, 'in the desert'),
|
||||
'Desert Palace - Big Chest': (0xe98f, 0x186163, False, 'in Desert Palace'),
|
||||
'Desert Palace - Torch': (0x180160, 0x186362, False, 'in Desert Palace'),
|
||||
'Desert Palace - Map Chest': (0xe9b6, 0x18618a, False, 'in Desert Palace'),
|
||||
'Desert Palace - Compass Chest': (0xe9cb, 0x18619f, False, 'in Desert Palace'),
|
||||
'Desert Palace - Big Key Chest': (0xe9c2, 0x186196, False, 'in Desert Palace'),
|
||||
'Desert Palace - Boss': (0x180151, 0x18633f, False, 'with Lanmolas'),
|
||||
'Eastern Palace - Compass Chest': (0xe977, 0x18614b, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Big Chest': (0xe97d, 0x186151, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Cannonball Chest': (0xe9b3, 0x186187, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Big Key Chest': (0xe9b9, 0x18618d, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Map Chest': (0xe9f5, 0x1861c9, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Boss': (0x180150, 0x18633e, False, 'with the Armos'),
|
||||
'Master Sword Pedestal': (0x289b0, 0x186369, False, 'at the pedestal'),
|
||||
'Hyrule Castle - Boomerang Chest': (0xe974, 0x186148, False, 'in Hyrule Castle'),
|
||||
'Hyrule Castle - Map Chest': (0xeb0c, 0x1862e0, False, 'in Hyrule Castle'),
|
||||
"Hyrule Castle - Zelda's Chest": (0xeb09, 0x1862dd, False, 'in Hyrule Castle'),
|
||||
'Sewers - Dark Cross': (0xe96e, 0x186142, False, 'in the sewers'),
|
||||
'Sewers - Secret Room - Left': (0xeb5d, 0x186331, False, 'in the sewers'),
|
||||
'Sewers - Secret Room - Middle': (0xeb60, 0x186334, False, 'in the sewers'),
|
||||
'Sewers - Secret Room - Right': (0xeb63, 0x186337, False, 'in the sewers'),
|
||||
'Sanctuary': (0xea79, 0x18624d, False, 'in Sanctuary'),
|
||||
'Castle Tower - Room 03': (0xeab5, 0x186289, False, 'in Castle Tower'),
|
||||
'Castle Tower - Dark Maze': (0xeab2, 0x186286, False, 'in Castle Tower'),
|
||||
'Old Man': (0xf69fa, 0x186364, False, 'with the old man'),
|
||||
'Spectacle Rock Cave': (0x180002, 0x18634a, False, 'alone in a cave'),
|
||||
'Paradox Cave Lower - Far Left': (0xeb2a, 0x1862fe, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Left': (0xeb2d, 0x186301, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Right': (0xeb30, 0x186304, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Far Right': (0xeb33, 0x186307, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Middle': (0xeb36, 0x18630a, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Upper - Left': (0xeb39, 0x18630d, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Upper - Right': (0xeb3c, 0x186310, False, 'in a cave with seven chests'),
|
||||
'Spiral Cave': (0xe9bf, 0x186193, False, 'in spiral cave'),
|
||||
'Ether Tablet': (0x180016, 0x18633b, False, 'at a monolith'),
|
||||
'Spectacle Rock': (0x180140, 0x18634f, False, 'atop a rock'),
|
||||
'Tower of Hera - Basement Cage': (0x180162, 0x18633a, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Map Chest': (0xe9ad, 0x186181, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Big Key Chest': (0xe9e6, 0x1861ba, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Compass Chest': (0xe9fb, 0x1861cf, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Big Chest': (0xe9f8, 0x1861cc, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Boss': (0x180152, 0x186340, False, 'with Moldorm'),
|
||||
'Pyramid': (0x180147, 0x186356, False, 'on the pyramid'),
|
||||
'Catfish': (0xee185, 0x186361, False, 'with a catfish'),
|
||||
'Stumpy': (0x330c7, 0x18636a, False, 'with tree boy'),
|
||||
'Digging Game': (0x180148, 0x186357, False, 'underground'),
|
||||
'Bombos Tablet': (0x180017, 0x18633c, False, 'at a monolith'),
|
||||
'Hype Cave - Top': (0xeb1e, 0x1862f2, False, 'near a bat-like man'),
|
||||
'Hype Cave - Middle Right': (0xeb21, 0x1862f5, False, 'near a bat-like man'),
|
||||
'Hype Cave - Middle Left': (0xeb24, 0x1862f8, False, 'near a bat-like man'),
|
||||
'Hype Cave - Bottom': (0xeb27, 0x1862fb, False, 'near a bat-like man'),
|
||||
'Hype Cave - Generous Guy': (0x180011, 0x18635b, False, 'with a bat-like man'),
|
||||
'Peg Cave': (0x180006, 0x18634e, False, 'alone in a cave'),
|
||||
'Pyramid Fairy - Left': (0xe980, 0x186154, False, 'near a fairy'),
|
||||
'Pyramid Fairy - Right': (0xe983, 0x186157, False, 'near a fairy'),
|
||||
'Brewery': (0xe9ec, 0x1861c0, False, 'alone in a home'),
|
||||
'C-Shaped House': (0xe9ef, 0x1861c3, False, 'alone in a home'),
|
||||
'Chest Game': (0xeda8, 0x18636b, False, 'as a prize'),
|
||||
'Bumper Cave Ledge': (0x180146, 0x186355, False, 'on a ledge'),
|
||||
'Mire Shed - Left': (0xea73, 0x186247, False, 'near sparks'),
|
||||
'Mire Shed - Right': (0xea76, 0x18624a, False, 'near sparks'),
|
||||
'Superbunny Cave - Top': (0xea7c, 0x186250, False, 'in a connection'),
|
||||
'Superbunny Cave - Bottom': (0xea7f, 0x186253, False, 'in a connection'),
|
||||
'Spike Cave': (0xea8b, 0x18625f, False, 'beyond spikes'),
|
||||
'Hookshot Cave - Top Right': (0xeb51, 0x186325, False, 'across pits'),
|
||||
'Hookshot Cave - Top Left': (0xeb54, 0x186328, False, 'across pits'),
|
||||
'Hookshot Cave - Bottom Right': (0xeb5a, 0x18632e, False, 'across pits'),
|
||||
'Hookshot Cave - Bottom Left': (0xeb57, 0x18632b, False, 'across pits'),
|
||||
'Floating Island': (0x180141, 0x186350, False, 'on an island'),
|
||||
'Mimic Cave': (0xe9c5, 0x186199, False, 'in a cave of mimicry'),
|
||||
'Swamp Palace - Entrance': (0xea9d, 0x186271, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Map Chest': (0xe986, 0x18615a, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Big Chest': (0xe989, 0x18615d, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Compass Chest': (0xeaa0, 0x186274, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Big Key Chest': (0xeaa6, 0x18627a, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - West Chest': (0xeaa3, 0x186277, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Flooded Room - Left': (0xeaa9, 0x18627d, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Flooded Room - Right': (0xeaac, 0x186280, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Waterfall Room': (0xeaaf, 0x186283, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Boss': (0x180154, 0x186342, False, 'with Arrghus'),
|
||||
"Thieves' Town - Big Key Chest": (0xea04, 0x1861d8, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Map Chest": (0xea01, 0x1861d5, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Compass Chest": (0xea07, 0x1861db, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Ambush Chest": (0xea0a, 0x1861de, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Attic": (0xea0d, 0x1861e1, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Big Chest": (0xea10, 0x1861e4, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Blind's Cell": (0xea13, 0x1861e7, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Boss": (0x180156, 0x186344, False, 'with Blind'),
|
||||
'Skull Woods - Compass Chest': (0xe992, 0x186166, False, 'in Skull Woods'),
|
||||
'Skull Woods - Map Chest': (0xe99b, 0x18616f, False, 'in Skull Woods'),
|
||||
'Skull Woods - Big Chest': (0xe998, 0x18616c, False, 'in Skull Woods'),
|
||||
'Skull Woods - Pot Prison': (0xe9a1, 0x186175, False, 'in Skull Woods'),
|
||||
'Skull Woods - Pinball Room': (0xe9c8, 0x18619c, False, 'in Skull Woods'),
|
||||
'Skull Woods - Big Key Chest': (0xe99e, 0x186172, False, 'in Skull Woods'),
|
||||
'Skull Woods - Bridge Room': (0xe9fe, 0x1861d2, False, 'near Mothula'),
|
||||
'Skull Woods - Boss': (0x180155, 0x186343, False, 'with Mothula'),
|
||||
'Ice Palace - Compass Chest': (0xe9d4, 0x1861a8, False, 'in Ice Palace'),
|
||||
'Ice Palace - Freezor Chest': (0xe995, 0x186169, False, 'in Ice Palace'),
|
||||
'Ice Palace - Big Chest': (0xe9aa, 0x18617e, False, 'in Ice Palace'),
|
||||
'Ice Palace - Iced T Room': (0xe9e3, 0x1861b7, False, 'in Ice Palace'),
|
||||
'Ice Palace - Spike Room': (0xe9e0, 0x1861b4, False, 'in Ice Palace'),
|
||||
'Ice Palace - Big Key Chest': (0xe9a4, 0x186178, False, 'in Ice Palace'),
|
||||
'Ice Palace - Map Chest': (0xe9dd, 0x1861b1, False, 'in Ice Palace'),
|
||||
'Ice Palace - Boss': (0x180157, 0x186345, False, 'with Kholdstare'),
|
||||
'Misery Mire - Big Chest': (0xea67, 0x18623b, False, 'in Misery Mire'),
|
||||
'Misery Mire - Map Chest': (0xea6a, 0x18623e, False, 'in Misery Mire'),
|
||||
'Misery Mire - Main Lobby': (0xea5e, 0x186232, False, 'in Misery Mire'),
|
||||
'Misery Mire - Bridge Chest': (0xea61, 0x186235, False, 'in Misery Mire'),
|
||||
'Misery Mire - Spike Chest': (0xe9da, 0x1861ae, False, 'in Misery Mire'),
|
||||
'Misery Mire - Compass Chest': (0xea64, 0x186238, False, 'in Misery Mire'),
|
||||
'Misery Mire - Big Key Chest': (0xea6d, 0x186241, False, 'in Misery Mire'),
|
||||
'Misery Mire - Boss': (0x180158, 0x186346, False, 'with Vitreous'),
|
||||
'Turtle Rock - Compass Chest': (0xea22, 0x1861f6, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Roller Room - Left': (0xea1c, 0x1861f0, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Roller Room - Right': (0xea1f, 0x1861f3, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Chain Chomps': (0xea16, 0x1861ea, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Big Key Chest': (0xea25, 0x1861f9, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Big Chest': (0xea19, 0x1861ed, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Crystaroller Room': (0xea34, 0x186208, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Bottom Left': (0xea31, 0x186205, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Bottom Right': (0xea2e, 0x186202, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Top Left': (0xea2b, 0x1861ff, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Top Right': (0xea28, 0x1861fc, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Boss': (0x180159, 0x186347, False, 'with Trinexx'),
|
||||
'Palace of Darkness - Shooter Room': (0xea5b, 0x18622f, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - The Arena - Bridge': (0xea3d, 0x186211, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Stalfos Basement': (0xea49, 0x18621d, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Big Key Chest': (0xea37, 0x18620b, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - The Arena - Ledge': (0xea3a, 0x18620e, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Map Chest': (0xea52, 0x186226, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Compass Chest': (0xea43, 0x186217, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Basement - Left': (0xea4c, 0x186220, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Basement - Right': (0xea4f, 0x186223, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Maze - Top': (0xea55, 0x186229, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Maze - Bottom': (0xea58, 0x18622c, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Big Chest': (0xea40, 0x186214, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Harmless Hellway': (0xea46, 0x18621a, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Boss': (0x180153, 0x186341, False, 'with Helmasaur King'),
|
||||
"Ganons Tower - Bob's Torch": (0x180161, 0x186363, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Hope Room - Left': (0xead9, 0x1862ad, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Hope Room - Right': (0xeadc, 0x1862b0, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Tile Room': (0xeae2, 0x1862b6, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Compass Room - Top Left': (0xeae5, 0x1862b9, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Compass Room - Top Right': (0xeae8, 0x1862bc, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Compass Room - Bottom Left': (0xeaeb, 0x1862bf, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Compass Room - Bottom Right': (0xeaee, 0x1862c2, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - DMs Room - Top Left': (0xeab8, 0x18628c, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - DMs Room - Top Right': (0xeabb, 0x18628f, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - DMs Room - Bottom Left': (0xeabe, 0x186292, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - DMs Room - Bottom Right': (0xeac1, 0x186295, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Map Chest': (0xead3, 0x1862a7, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Firesnake Room': (0xead0, 0x1862a4, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Randomizer Room - Top Left': (0xeac4, 0x186298, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Randomizer Room - Top Right': (0xeac7, 0x18629b, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Randomizer Room - Bottom Left': (0xeaca, 0x18629e, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Randomizer Room - Bottom Right': (0xeacd, 0x1862a1, False, "in Ganon's Tower"),
|
||||
"Ganons Tower - Bob's Chest": (0xeadf, 0x1862b3, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Big Chest': (0xead6, 0x1862aa, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Big Key Room - Left': (0xeaf4, 0x1862c8, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Big Key Room - Right': (0xeaf7, 0x1862cb, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Big Key Chest': (0xeaf1, 0x1862c5, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Mini Helmasaur Room - Left': (0xeafd, 0x1862d1, False, "atop Ganon's Tower"),
|
||||
'Ganons Tower - Mini Helmasaur Room - Right': (0xeb00, 0x1862d4, False, "atop Ganon's Tower"),
|
||||
'Ganons Tower - Pre-Moldorm Chest': (0xeb03, 0x1862d7, False, "atop Ganon's Tower"),
|
||||
'Ganons Tower - Validation Chest': (0xeb06, 0x1862da, False, "atop Ganon's Tower"),
|
||||
'Ganon': (None, None, False, 'from me'),
|
||||
'Agahnim 1': (None, None, False, 'from Ganon\'s wizardry form'),
|
||||
'Agahnim 2': (None, None, False, 'from Ganon\'s wizardry form'),
|
||||
'Floodgate': (None, None, False, None),
|
||||
'Frog': (None, None, False, None),
|
||||
'Missing Smith': (None, None, False, None),
|
||||
'Dark Blacksmith Ruins': (None, None, False, None),
|
||||
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], None, True, 'Eastern Palace'),
|
||||
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], None, True, 'Desert Palace'),
|
||||
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], None, True, 'Tower of Hera'),
|
||||
'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], None, True, 'Palace of Darkness'),
|
||||
'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], None, True, 'Swamp Palace'),
|
||||
'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], None, True, 'Thieves\' Town'),
|
||||
'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], None, True, 'Skull Woods'),
|
||||
'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], None, True, 'Ice Palace'),
|
||||
'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], None, True, 'Misery Mire'),
|
||||
'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
|
||||
|
|
29
Main.py
29
Main.py
|
@ -4,6 +4,7 @@ from itertools import zip_longest
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import pickle
|
||||
import random
|
||||
import time
|
||||
|
||||
|
@ -16,7 +17,7 @@ from Rules import set_rules
|
|||
from Dungeons import create_dungeons, fill_dungeons, fill_dungeons_restrictive
|
||||
from Fill import distribute_items_cutoff, distribute_items_staleness, distribute_items_restrictive, flood_items, balance_multiworld_progression
|
||||
from ItemList import generate_itempool, difficulties, fill_prizes
|
||||
from Utils import output_path
|
||||
from Utils import output_path, parse_names_string
|
||||
|
||||
__version__ = '0.6.3-pre'
|
||||
|
||||
|
@ -120,16 +121,18 @@ def main(args, seed=None):
|
|||
else:
|
||||
sprite = None
|
||||
|
||||
player_names = parse_names_string(args.names)
|
||||
outfilebase = 'ER_%s_%s-%s-%s-%s%s_%s-%s%s%s%s%s_%s' % (world.logic, world.difficulty, world.difficulty_adjustments, world.mode, world.goal, "" if world.timer in ['none', 'display'] else "-" + world.timer, world.shuffle, world.algorithm, "-keysanity" if world.keysanity else "", "-retro" if world.retro else "", "-prog_" + world.progressive if world.progressive in ['off', 'random'] else "", "-nohints" if not world.hints else "", world.seed)
|
||||
|
||||
use_enemizer = args.enemizercli and (args.shufflebosses != 'none' or args.shuffleenemies or args.enemy_health != 'default' or args.enemy_health != 'default' or args.enemy_damage or args.shufflepalette or args.shufflepots)
|
||||
|
||||
jsonout = {}
|
||||
if not args.suppress_rom:
|
||||
if world.players > 1:
|
||||
raise NotImplementedError("Multiworld rom writes have not been implemented")
|
||||
else:
|
||||
player = 1
|
||||
from MultiServer import MultiWorld
|
||||
multidata = MultiWorld()
|
||||
multidata.players = world.players
|
||||
|
||||
for player in range(1, world.players + 1):
|
||||
|
||||
local_rom = None
|
||||
if args.jsonout:
|
||||
|
@ -147,17 +150,25 @@ def main(args, seed=None):
|
|||
if use_enemizer:
|
||||
enemizer_patch = get_enemizer_patch(world, player, rom, args.rom, args.enemizercli, args.shuffleenemies, args.enemy_health, args.enemy_damage, args.shufflepalette, args.shufflepots)
|
||||
|
||||
multidata.rom_names[player] = list(rom.name)
|
||||
for location in world.get_filled_locations(player):
|
||||
if type(location.address) is int:
|
||||
multidata.locations[(location.address, player)] = (location.item.code, location.item.player)
|
||||
|
||||
if args.jsonout:
|
||||
jsonout['patch'] = rom.patches
|
||||
jsonout[f'patch{player}'] = rom.patches
|
||||
if use_enemizer:
|
||||
jsonout['enemizer' % player] = enemizer_patch
|
||||
jsonout[f'enemizer{player}'] = enemizer_patch
|
||||
else:
|
||||
if use_enemizer:
|
||||
local_rom.patch_enemizer(rom.patches, os.path.join(os.path.dirname(args.enemizercli), "enemizerBasePatch.json"), enemizer_patch)
|
||||
rom = local_rom
|
||||
|
||||
apply_rom_settings(rom, args.heartbeep, args.heartcolor, world.quickswap, world.fastmenu, world.disable_music, sprite)
|
||||
rom.write_to_file(output_path('%s.sfc' % outfilebase))
|
||||
apply_rom_settings(rom, args.heartbeep, args.heartcolor, world.quickswap, world.fastmenu, world.disable_music, sprite, player_names)
|
||||
rom.write_to_file(output_path(f'{outfilebase}{f"_P{player}" if world.players > 1 else ""}{f"_{player_names[player]}" if player in player_names else ""}.sfc'))
|
||||
|
||||
with open(output_path('%s_multidata' % outfilebase), 'wb') as f:
|
||||
pickle.dump(multidata, f, pickle.HIGHEST_PROTOCOL)
|
||||
|
||||
if args.create_spoiler and not args.jsonout:
|
||||
world.spoiler.to_file(output_path('%s_Spoiler.txt' % outfilebase))
|
||||
|
|
|
@ -0,0 +1,920 @@
|
|||
import argparse
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import Items
|
||||
import Regions
|
||||
|
||||
while True:
|
||||
try:
|
||||
import aioconsole
|
||||
break
|
||||
except ImportError:
|
||||
aioconsole = None
|
||||
print('Required python module "aioconsole" not found, press enter to install it')
|
||||
input()
|
||||
subprocess.call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'aioconsole'])
|
||||
|
||||
while True:
|
||||
try:
|
||||
import websockets
|
||||
break
|
||||
except ImportError:
|
||||
websockets = None
|
||||
print('Required python module "websockets" not found, press enter to install it')
|
||||
input()
|
||||
subprocess.call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'websockets'])
|
||||
|
||||
try:
|
||||
import colorama
|
||||
except ImportError:
|
||||
colorama = None
|
||||
|
||||
class ReceivedItem:
|
||||
def __init__(self, item, location, player_id, player_name):
|
||||
self.item = item
|
||||
self.location = location
|
||||
self.player_id = player_id
|
||||
self.player_name = player_name
|
||||
|
||||
class Context:
|
||||
def __init__(self, snes_address, server_address, password, name, team, slot):
|
||||
self.snes_address = snes_address
|
||||
self.server_address = server_address
|
||||
|
||||
self.exit_event = asyncio.Event()
|
||||
|
||||
self.input_queue = asyncio.Queue()
|
||||
self.input_requests = 0
|
||||
|
||||
self.snes_socket = None
|
||||
self.snes_state = SNES_DISCONNECTED
|
||||
self.snes_recv_queue = asyncio.Queue()
|
||||
self.snes_request_lock = asyncio.Lock()
|
||||
self.is_sd2snes = False
|
||||
self.snes_write_buffer = []
|
||||
|
||||
self.server_task = None
|
||||
self.socket = None
|
||||
self.password = password
|
||||
|
||||
self.name = name
|
||||
self.team = team
|
||||
self.slot = slot
|
||||
|
||||
self.locations_checked = set()
|
||||
self.items_received = []
|
||||
self.last_rom = None
|
||||
self.expected_rom = None
|
||||
self.rom_confirmed = False
|
||||
|
||||
def color_code(*args):
|
||||
codes = {'reset': 0, 'bold': 1, 'underline': 4, 'black': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34,
|
||||
'magenta': 35, 'cyan': 36, 'white': 37 , 'black_bg': 40, 'red_bg': 41, 'green_bg': 42, 'yellow_bg': 43,
|
||||
'blue_bg': 44, 'purple_bg': 45, 'cyan_bg': 46, 'white_bg': 47}
|
||||
return '\033[' + ';'.join([str(codes[arg]) for arg in args]) + 'm'
|
||||
|
||||
def color(text, *args):
|
||||
return color_code(*args) + text + color_code('reset')
|
||||
|
||||
|
||||
ROM_START = 0x000000
|
||||
WRAM_START = 0xF50000
|
||||
WRAM_SIZE = 0x20000
|
||||
SRAM_START = 0xE00000
|
||||
|
||||
ROMNAME_START = SRAM_START + 0x2000
|
||||
ROMNAME_SIZE = 0x15
|
||||
|
||||
INGAME_MODES = {0x07, 0x09, 0x0b}
|
||||
|
||||
SAVEDATA_START = WRAM_START + 0xF000
|
||||
SAVEDATA_SIZE = 0x500
|
||||
|
||||
RECV_PROGRESS_ADDR = SAVEDATA_START + 0x4D0 # 2 bytes
|
||||
RECV_ITEM_ADDR = SAVEDATA_START + 0x4D2 # 1 byte
|
||||
RECV_ITEM_PLAYER_ADDR = SAVEDATA_START + 0x4D3 # 1 byte
|
||||
ROOMID_ADDR = SAVEDATA_START + 0x4D4 # 2 bytes
|
||||
ROOMDATA_ADDR = SAVEDATA_START + 0x4D6 # 1 byte
|
||||
|
||||
location_table_uw = {"Blind's Hideout - Top": (0x11d, 0x10),
|
||||
"Blind's Hideout - Left": (0x11d, 0x20),
|
||||
"Blind's Hideout - Right": (0x11d, 0x40),
|
||||
"Blind's Hideout - Far Left": (0x11d, 0x80),
|
||||
"Blind's Hideout - Far Right": (0x11d, 0x100),
|
||||
'Secret Passage': (0x55, 0x10),
|
||||
'Waterfall Fairy - Left': (0x114, 0x10),
|
||||
'Waterfall Fairy - Right': (0x114, 0x20),
|
||||
"King's Tomb": (0x113, 0x10),
|
||||
'Floodgate Chest': (0x10b, 0x10),
|
||||
"Link's House": (0x104, 0x10),
|
||||
'Kakariko Tavern': (0x103, 0x10),
|
||||
'Chicken House': (0x108, 0x10),
|
||||
"Aginah's Cave": (0x10a, 0x10),
|
||||
"Sahasrahla's Hut - Left": (0x105, 0x10),
|
||||
"Sahasrahla's Hut - Middle": (0x105, 0x20),
|
||||
"Sahasrahla's Hut - Right": (0x105, 0x40),
|
||||
'Kakariko Well - Top': (0x2f, 0x10),
|
||||
'Kakariko Well - Left': (0x2f, 0x20),
|
||||
'Kakariko Well - Middle': (0x2f, 0x40),
|
||||
'Kakariko Well - Right': (0x2f, 0x80),
|
||||
'Kakariko Well - Bottom': (0x2f, 0x100),
|
||||
'Lost Woods Hideout': (0xe1, 0x200),
|
||||
'Lumberjack Tree': (0xe2, 0x200),
|
||||
'Cave 45': (0x11b, 0x400),
|
||||
'Graveyard Cave': (0x11b, 0x200),
|
||||
'Checkerboard Cave': (0x126, 0x200),
|
||||
'Mini Moldorm Cave - Far Left': (0x123, 0x10),
|
||||
'Mini Moldorm Cave - Left': (0x123, 0x20),
|
||||
'Mini Moldorm Cave - Right': (0x123, 0x40),
|
||||
'Mini Moldorm Cave - Far Right': (0x123, 0x80),
|
||||
'Mini Moldorm Cave - Generous Guy': (0x123, 0x400),
|
||||
'Ice Rod Cave': (0x120, 0x10),
|
||||
'Bonk Rock Cave': (0x124, 0x10),
|
||||
'Desert Palace - Big Chest': (0x73, 0x10),
|
||||
'Desert Palace - Torch': (0x73, 0x400),
|
||||
'Desert Palace - Map Chest': (0x74, 0x10),
|
||||
'Desert Palace - Compass Chest': (0x85, 0x10),
|
||||
'Desert Palace - Big Key Chest': (0x75, 0x10),
|
||||
'Desert Palace - Boss': (0x33, 0x800),
|
||||
'Eastern Palace - Compass Chest': (0xa8, 0x10),
|
||||
'Eastern Palace - Big Chest': (0xa9, 0x10),
|
||||
'Eastern Palace - Cannonball Chest': (0xb9, 0x10),
|
||||
'Eastern Palace - Big Key Chest': (0xb8, 0x10),
|
||||
'Eastern Palace - Map Chest': (0xaa, 0x10),
|
||||
'Eastern Palace - Boss': (0xc8, 0x800),
|
||||
'Hyrule Castle - Boomerang Chest': (0x71, 0x10),
|
||||
'Hyrule Castle - Map Chest': (0x72, 0x10),
|
||||
"Hyrule Castle - Zelda's Chest": (0x80, 0x10),
|
||||
'Sewers - Dark Cross': (0x32, 0x10),
|
||||
'Sewers - Secret Room - Left': (0x11, 0x10),
|
||||
'Sewers - Secret Room - Middle': (0x11, 0x20),
|
||||
'Sewers - Secret Room - Right': (0x11, 0x40),
|
||||
'Sanctuary': (0x12, 0x10),
|
||||
'Castle Tower - Room 03': (0xe0, 0x10),
|
||||
'Castle Tower - Dark Maze': (0xd0, 0x10),
|
||||
'Spectacle Rock Cave': (0xea, 0x400),
|
||||
'Paradox Cave Lower - Far Left': (0xef, 0x10),
|
||||
'Paradox Cave Lower - Left': (0xef, 0x20),
|
||||
'Paradox Cave Lower - Right': (0xef, 0x40),
|
||||
'Paradox Cave Lower - Far Right': (0xef, 0x80),
|
||||
'Paradox Cave Lower - Middle': (0xef, 0x100),
|
||||
'Paradox Cave Upper - Left': (0xff, 0x10),
|
||||
'Paradox Cave Upper - Right': (0xff, 0x20),
|
||||
'Spiral Cave': (0xfe, 0x10),
|
||||
'Tower of Hera - Basement Cage': (0x87, 0x400),
|
||||
'Tower of Hera - Map Chest': (0x77, 0x10),
|
||||
'Tower of Hera - Big Key Chest': (0x87, 0x10),
|
||||
'Tower of Hera - Compass Chest': (0x27, 0x20),
|
||||
'Tower of Hera - Big Chest': (0x27, 0x10),
|
||||
'Tower of Hera - Boss': (0x7, 0x800),
|
||||
'Hype Cave - Top': (0x11e, 0x10),
|
||||
'Hype Cave - Middle Right': (0x11e, 0x20),
|
||||
'Hype Cave - Middle Left': (0x11e, 0x40),
|
||||
'Hype Cave - Bottom': (0x11e, 0x80),
|
||||
'Hype Cave - Generous Guy': (0x11e, 0x400),
|
||||
'Peg Cave': (0x127, 0x400),
|
||||
'Pyramid Fairy - Left': (0x116, 0x10),
|
||||
'Pyramid Fairy - Right': (0x116, 0x20),
|
||||
'Brewery': (0x106, 0x10),
|
||||
'C-Shaped House': (0x11c, 0x10),
|
||||
'Chest Game': (0x106, 0x400),
|
||||
'Mire Shed - Left': (0x10d, 0x10),
|
||||
'Mire Shed - Right': (0x10d, 0x20),
|
||||
'Superbunny Cave - Top': (0xf8, 0x10),
|
||||
'Superbunny Cave - Bottom': (0xf8, 0x20),
|
||||
'Spike Cave': (0x117, 0x10),
|
||||
'Hookshot Cave - Top Right': (0x3c, 0x10),
|
||||
'Hookshot Cave - Top Left': (0x3c, 0x20),
|
||||
'Hookshot Cave - Bottom Right': (0x3c, 0x80),
|
||||
'Hookshot Cave - Bottom Left': (0x3c, 0x40),
|
||||
'Mimic Cave': (0x10c, 0x10),
|
||||
'Swamp Palace - Entrance': (0x28, 0x10),
|
||||
'Swamp Palace - Map Chest': (0x37, 0x10),
|
||||
'Swamp Palace - Big Chest': (0x36, 0x10),
|
||||
'Swamp Palace - Compass Chest': (0x46, 0x10),
|
||||
'Swamp Palace - Big Key Chest': (0x35, 0x10),
|
||||
'Swamp Palace - West Chest': (0x34, 0x10),
|
||||
'Swamp Palace - Flooded Room - Left': (0x76, 0x10),
|
||||
'Swamp Palace - Flooded Room - Right': (0x76, 0x20),
|
||||
'Swamp Palace - Waterfall Room': (0x66, 0x10),
|
||||
'Swamp Palace - Boss': (0x6, 0x800),
|
||||
"Thieves' Town - Big Key Chest": (0xdb, 0x20),
|
||||
"Thieves' Town - Map Chest": (0xdb, 0x10),
|
||||
"Thieves' Town - Compass Chest": (0xdc, 0x10),
|
||||
"Thieves' Town - Ambush Chest": (0xcb, 0x10),
|
||||
"Thieves' Town - Attic": (0x65, 0x10),
|
||||
"Thieves' Town - Big Chest": (0x44, 0x10),
|
||||
"Thieves' Town - Blind's Cell": (0x45, 0x10),
|
||||
"Thieves' Town - Boss": (0xac, 0x800),
|
||||
'Skull Woods - Compass Chest': (0x67, 0x10),
|
||||
'Skull Woods - Map Chest': (0x58, 0x20),
|
||||
'Skull Woods - Big Chest': (0x58, 0x10),
|
||||
'Skull Woods - Pot Prison': (0x57, 0x20),
|
||||
'Skull Woods - Pinball Room': (0x68, 0x10),
|
||||
'Skull Woods - Big Key Chest': (0x57, 0x10),
|
||||
'Skull Woods - Bridge Room': (0x59, 0x10),
|
||||
'Skull Woods - Boss': (0x29, 0x800),
|
||||
'Ice Palace - Compass Chest': (0x2e, 0x10),
|
||||
'Ice Palace - Freezor Chest': (0x7e, 0x10),
|
||||
'Ice Palace - Big Chest': (0x9e, 0x10),
|
||||
'Ice Palace - Iced T Room': (0xae, 0x10),
|
||||
'Ice Palace - Spike Room': (0x5f, 0x10),
|
||||
'Ice Palace - Big Key Chest': (0x1f, 0x10),
|
||||
'Ice Palace - Map Chest': (0x3f, 0x10),
|
||||
'Ice Palace - Boss': (0xde, 0x800),
|
||||
'Misery Mire - Big Chest': (0xc3, 0x10),
|
||||
'Misery Mire - Map Chest': (0xc3, 0x20),
|
||||
'Misery Mire - Main Lobby': (0xc2, 0x10),
|
||||
'Misery Mire - Bridge Chest': (0xa2, 0x10),
|
||||
'Misery Mire - Spike Chest': (0xb3, 0x10),
|
||||
'Misery Mire - Compass Chest': (0xc1, 0x10),
|
||||
'Misery Mire - Big Key Chest': (0xd1, 0x10),
|
||||
'Misery Mire - Boss': (0x90, 0x800),
|
||||
'Turtle Rock - Compass Chest': (0xd6, 0x10),
|
||||
'Turtle Rock - Roller Room - Left': (0xb7, 0x10),
|
||||
'Turtle Rock - Roller Room - Right': (0xb7, 0x20),
|
||||
'Turtle Rock - Chain Chomps': (0xb6, 0x10),
|
||||
'Turtle Rock - Big Key Chest': (0x14, 0x10),
|
||||
'Turtle Rock - Big Chest': (0x24, 0x10),
|
||||
'Turtle Rock - Crystaroller Room': (0x4, 0x10),
|
||||
'Turtle Rock - Eye Bridge - Bottom Left': (0xd5, 0x80),
|
||||
'Turtle Rock - Eye Bridge - Bottom Right': (0xd5, 0x40),
|
||||
'Turtle Rock - Eye Bridge - Top Left': (0xd5, 0x20),
|
||||
'Turtle Rock - Eye Bridge - Top Right': (0xd5, 0x10),
|
||||
'Turtle Rock - Boss': (0xa4, 0x800),
|
||||
'Palace of Darkness - Shooter Room': (0x9, 0x10),
|
||||
'Palace of Darkness - The Arena - Bridge': (0x2a, 0x20),
|
||||
'Palace of Darkness - Stalfos Basement': (0xa, 0x10),
|
||||
'Palace of Darkness - Big Key Chest': (0x3a, 0x10),
|
||||
'Palace of Darkness - The Arena - Ledge': (0x2a, 0x10),
|
||||
'Palace of Darkness - Map Chest': (0x2b, 0x10),
|
||||
'Palace of Darkness - Compass Chest': (0x1a, 0x20),
|
||||
'Palace of Darkness - Dark Basement - Left': (0x6a, 0x10),
|
||||
'Palace of Darkness - Dark Basement - Right': (0x6a, 0x20),
|
||||
'Palace of Darkness - Dark Maze - Top': (0x19, 0x10),
|
||||
'Palace of Darkness - Dark Maze - Bottom': (0x19, 0x20),
|
||||
'Palace of Darkness - Big Chest': (0x1a, 0x10),
|
||||
'Palace of Darkness - Harmless Hellway': (0x1a, 0x40),
|
||||
'Palace of Darkness - Boss': (0x5a, 0x800),
|
||||
"Ganons Tower - Bob's Torch": (0x8c, 0x400),
|
||||
'Ganons Tower - Hope Room - Left': (0x8c, 0x20),
|
||||
'Ganons Tower - Hope Room - Right': (0x8c, 0x40),
|
||||
'Ganons Tower - Tile Room': (0x8d, 0x10),
|
||||
'Ganons Tower - Compass Room - Top Left': (0x9d, 0x10),
|
||||
'Ganons Tower - Compass Room - Top Right': (0x9d, 0x20),
|
||||
'Ganons Tower - Compass Room - Bottom Left': (0x9d, 0x40),
|
||||
'Ganons Tower - Compass Room - Bottom Right': (0x9d, 0x80),
|
||||
'Ganons Tower - DMs Room - Top Left': (0x7b, 0x10),
|
||||
'Ganons Tower - DMs Room - Top Right': (0x7b, 0x20),
|
||||
'Ganons Tower - DMs Room - Bottom Left': (0x7b, 0x40),
|
||||
'Ganons Tower - DMs Room - Bottom Right': (0x7b, 0x80),
|
||||
'Ganons Tower - Map Chest': (0x8b, 0x10),
|
||||
'Ganons Tower - Firesnake Room': (0x7d, 0x10),
|
||||
'Ganons Tower - Randomizer Room - Top Left': (0x7c, 0x10),
|
||||
'Ganons Tower - Randomizer Room - Top Right': (0x7c, 0x20),
|
||||
'Ganons Tower - Randomizer Room - Bottom Left': (0x7c, 0x40),
|
||||
'Ganons Tower - Randomizer Room - Bottom Right': (0x7c, 0x80),
|
||||
"Ganons Tower - Bob's Chest": (0x8c, 0x80),
|
||||
'Ganons Tower - Big Chest': (0x8c, 0x10),
|
||||
'Ganons Tower - Big Key Room - Left': (0x1c, 0x20),
|
||||
'Ganons Tower - Big Key Room - Right': (0x1c, 0x40),
|
||||
'Ganons Tower - Big Key Chest': (0x1c, 0x10),
|
||||
'Ganons Tower - Mini Helmasaur Room - Left': (0x3d, 0x10),
|
||||
'Ganons Tower - Mini Helmasaur Room - Right': (0x3d, 0x20),
|
||||
'Ganons Tower - Pre-Moldorm Chest': (0x3d, 0x40),
|
||||
'Ganons Tower - Validation Chest': (0x4d, 0x10)}
|
||||
location_table_npc = {'Mushroom': 0x1000,
|
||||
'King Zora': 0x2,
|
||||
'Sahasrahla': 0x10,
|
||||
'Blacksmith': 0x400,
|
||||
'Magic Bat': 0x8000,
|
||||
'Sick Kid': 0x4,
|
||||
'Library': 0x80,
|
||||
'Potion Shop': 0x2000,
|
||||
'Old Man': 0x1,
|
||||
'Ether Tablet': 0x100,
|
||||
'Catfish': 0x20,
|
||||
'Stumpy': 0x8,
|
||||
'Bombos Tablet': 0x200}
|
||||
location_table_ow = {'Flute Spot': 0x2a,
|
||||
'Sunken Treasure': 0x3b,
|
||||
"Zora's Ledge": 0x81,
|
||||
'Lake Hylia Island': 0x35,
|
||||
'Maze Race': 0x28,
|
||||
'Desert Ledge': 0x30,
|
||||
'Master Sword Pedestal': 0x80,
|
||||
'Spectacle Rock': 0x3,
|
||||
'Pyramid': 0x5b,
|
||||
'Digging Game': 0x68,
|
||||
'Bumper Cave Ledge': 0x4a,
|
||||
'Floating Island': 0x5}
|
||||
location_table_misc = {'Bottle Merchant': (0x3c9, 0x2),
|
||||
'Purple Chest': (0x3c9, 0x10),
|
||||
"Link's Uncle": (0x3c6, 0x1),
|
||||
'Hobo': (0x3c9, 0x1)}
|
||||
|
||||
SNES_DISCONNECTED = 0
|
||||
SNES_CONNECTING = 1
|
||||
SNES_CONNECTED = 2
|
||||
SNES_ATTACHED = 3
|
||||
|
||||
async def snes_connect(ctx : Context, address = None):
|
||||
if ctx.snes_socket is not None:
|
||||
print('Already connected to snes')
|
||||
return
|
||||
|
||||
ctx.snes_state = SNES_CONNECTING
|
||||
recv_task = None
|
||||
|
||||
if address is None:
|
||||
address = 'ws://' + ctx.snes_address
|
||||
|
||||
print("Connecting to QUsb2snes at %s ..." % address)
|
||||
|
||||
try:
|
||||
ctx.snes_socket = await websockets.connect(address, ping_timeout=None, ping_interval=None)
|
||||
ctx.snes_state = SNES_CONNECTED
|
||||
|
||||
DeviceList_Request = {
|
||||
"Opcode" : "DeviceList",
|
||||
"Space" : "SNES"
|
||||
}
|
||||
await ctx.snes_socket.send(json.dumps(DeviceList_Request))
|
||||
|
||||
reply = json.loads(await ctx.snes_socket.recv())
|
||||
devices = reply['Results'] if 'Results' in reply and len(reply['Results']) > 0 else None
|
||||
|
||||
if not devices:
|
||||
raise Exception('No device found')
|
||||
|
||||
print("Available devices:")
|
||||
for id, device in enumerate(devices):
|
||||
print("[%d] %s" % (id + 1, device))
|
||||
|
||||
device = None
|
||||
while True:
|
||||
print("Enter a number:")
|
||||
choice = await console_input(ctx)
|
||||
if choice is None:
|
||||
raise Exception('Abort input')
|
||||
if not choice.isdigit() or int(choice) < 1 or int(choice) > len(devices):
|
||||
print("Invalid choice (%s)" % choice)
|
||||
continue
|
||||
|
||||
device = devices[int(choice) - 1]
|
||||
break
|
||||
|
||||
print("Attaching to " + device)
|
||||
|
||||
Attach_Request = {
|
||||
"Opcode" : "Attach",
|
||||
"Space" : "SNES",
|
||||
"Operands" : [device]
|
||||
}
|
||||
await ctx.snes_socket.send(json.dumps(Attach_Request))
|
||||
ctx.snes_state = SNES_ATTACHED
|
||||
|
||||
if 'SD2SNES'.lower() in device.lower() or (len(device) == 4 and device[:3] == 'COM'):
|
||||
print("SD2SNES Detected")
|
||||
ctx.is_sd2snes = True
|
||||
await ctx.snes_socket.send(json.dumps({"Opcode" : "Info", "Space" : "SNES"}))
|
||||
reply = json.loads(await ctx.snes_socket.recv())
|
||||
if reply and 'Results' in reply:
|
||||
print(reply['Results'])
|
||||
else:
|
||||
ctx.is_sd2snes = False
|
||||
|
||||
recv_task = asyncio.create_task(snes_recv_loop(ctx))
|
||||
|
||||
except Exception as e:
|
||||
print("Error connecting to snes (%s)" % e)
|
||||
if recv_task is not None:
|
||||
if not ctx.snes_socket.closed:
|
||||
await ctx.snes_socket.close()
|
||||
else:
|
||||
if ctx.snes_socket is not None:
|
||||
if not ctx.snes_socket.closed:
|
||||
await ctx.snes_socket.close()
|
||||
ctx.snes_socket = None
|
||||
ctx.snes_state = SNES_DISCONNECTED
|
||||
|
||||
async def snes_recv_loop(ctx : Context):
|
||||
try:
|
||||
async for msg in ctx.snes_socket:
|
||||
ctx.snes_recv_queue.put_nowait(msg)
|
||||
print("Snes disconnected, type /snes to reconnect")
|
||||
except Exception as e:
|
||||
print("Lost connection to the snes, type /snes to reconnect")
|
||||
if type(e) is not websockets.ConnectionClosed:
|
||||
logging.exception(e)
|
||||
finally:
|
||||
socket, ctx.snes_socket = ctx.snes_socket, None
|
||||
if socket is not None and not socket.closed:
|
||||
await socket.close()
|
||||
|
||||
ctx.snes_state = SNES_DISCONNECTED
|
||||
ctx.snes_recv_queue = asyncio.Queue()
|
||||
ctx.hud_message_queue = []
|
||||
|
||||
ctx.rom_confirmed = False
|
||||
ctx.last_rom = None
|
||||
|
||||
async def snes_read(ctx : Context, address, size):
|
||||
try:
|
||||
await ctx.snes_request_lock.acquire()
|
||||
|
||||
if ctx.snes_state != SNES_ATTACHED or ctx.snes_socket is None or not ctx.snes_socket.open or ctx.snes_socket.closed:
|
||||
return None
|
||||
|
||||
GetAddress_Request = {
|
||||
"Opcode" : "GetAddress",
|
||||
"Space" : "SNES",
|
||||
"Operands" : [hex(address)[2:], hex(size)[2:]]
|
||||
}
|
||||
try:
|
||||
await ctx.snes_socket.send(json.dumps(GetAddress_Request))
|
||||
except websockets.ConnectionClosed:
|
||||
return None
|
||||
|
||||
data = bytes()
|
||||
while len(data) < size:
|
||||
try:
|
||||
data += await asyncio.wait_for(ctx.snes_recv_queue.get(), 5)
|
||||
except asyncio.TimeoutError:
|
||||
break
|
||||
|
||||
if len(data) != size:
|
||||
print('Error reading %s, requested %d bytes, received %d' % (hex(address), size, len(data)))
|
||||
if len(data):
|
||||
print(str(data))
|
||||
if ctx.snes_socket is not None and not ctx.snes_socket.closed:
|
||||
await ctx.snes_socket.close()
|
||||
return None
|
||||
|
||||
return data
|
||||
finally:
|
||||
ctx.snes_request_lock.release()
|
||||
|
||||
async def snes_write(ctx : Context, write_list):
|
||||
try:
|
||||
await ctx.snes_request_lock.acquire()
|
||||
|
||||
if ctx.snes_state != SNES_ATTACHED or ctx.snes_socket is None or not ctx.snes_socket.open or ctx.snes_socket.closed:
|
||||
return False
|
||||
|
||||
PutAddress_Request = {
|
||||
"Opcode" : "PutAddress",
|
||||
"Operands" : []
|
||||
}
|
||||
|
||||
if ctx.is_sd2snes:
|
||||
cmd = b'\x00\xE2\x20\x48\xEB\x48'
|
||||
|
||||
for address, data in write_list:
|
||||
if (address < WRAM_START) or ((address + len(data)) > (WRAM_START + WRAM_SIZE)):
|
||||
print("SD2SNES: Write out of range %s (%d)" % (hex(address), len(data)))
|
||||
return False
|
||||
for ptr, byte in enumerate(data, address + 0x7E0000 - WRAM_START):
|
||||
cmd += b'\xA9' # LDA
|
||||
cmd += bytes([byte])
|
||||
cmd += b'\x8F' # STA.l
|
||||
cmd += bytes([ptr & 0xFF, (ptr >> 8) & 0xFF, (ptr >> 16) & 0xFF])
|
||||
|
||||
cmd += b'\xA9\x00\x8F\x00\x2C\x00\x68\xEB\x68\x28\x6C\xEA\xFF\x08'
|
||||
|
||||
PutAddress_Request['Space'] = 'CMD'
|
||||
PutAddress_Request['Operands'] = ["2C00", hex(len(cmd)-1)[2:], "2C00", "1"]
|
||||
try:
|
||||
if ctx.snes_socket is not None:
|
||||
await ctx.snes_socket.send(json.dumps(PutAddress_Request))
|
||||
if ctx.snes_socket is not None:
|
||||
await ctx.snes_socket.send(cmd)
|
||||
except websockets.ConnectionClosed:
|
||||
return False
|
||||
else:
|
||||
PutAddress_Request['Space'] = 'SNES'
|
||||
try:
|
||||
#will pack those requests as soon as qusb2snes actually supports that for real
|
||||
for address, data in write_list:
|
||||
PutAddress_Request['Operands'] = [hex(address)[2:], hex(len(data))[2:]]
|
||||
if ctx.snes_socket is not None:
|
||||
await ctx.snes_socket.send(json.dumps(PutAddress_Request))
|
||||
if ctx.snes_socket is not None:
|
||||
await ctx.snes_socket.send(data)
|
||||
except websockets.ConnectionClosed:
|
||||
return False
|
||||
|
||||
return True
|
||||
finally:
|
||||
ctx.snes_request_lock.release()
|
||||
|
||||
def snes_buffered_write(ctx : Context, address, data):
|
||||
if len(ctx.snes_write_buffer) > 0 and (ctx.snes_write_buffer[-1][0] + len(ctx.snes_write_buffer[-1][1])) == address:
|
||||
ctx.snes_write_buffer[-1] = (ctx.snes_write_buffer[-1][0], ctx.snes_write_buffer[-1][1] + data)
|
||||
else:
|
||||
ctx.snes_write_buffer.append((address, data))
|
||||
|
||||
async def snes_flush_writes(ctx : Context):
|
||||
if not ctx.snes_write_buffer:
|
||||
return
|
||||
|
||||
await snes_write(ctx, ctx.snes_write_buffer)
|
||||
ctx.snes_write_buffer = []
|
||||
|
||||
async def send_msgs(websocket, msgs):
|
||||
if not websocket or not websocket.open or websocket.closed:
|
||||
return
|
||||
try:
|
||||
await websocket.send(json.dumps(msgs))
|
||||
except websockets.ConnectionClosed:
|
||||
pass
|
||||
|
||||
async def server_loop(ctx : Context):
|
||||
if ctx.socket is not None:
|
||||
print('Already connected')
|
||||
return
|
||||
|
||||
while not ctx.server_address:
|
||||
print('Enter multiworld server address')
|
||||
ctx.server_address = await console_input(ctx)
|
||||
|
||||
address = 'ws://' + ctx.server_address
|
||||
|
||||
print('Connecting to multiworld server at %s' % address)
|
||||
try:
|
||||
ctx.socket = await websockets.connect(address, ping_timeout=None, ping_interval=None)
|
||||
print('Connected')
|
||||
|
||||
async for data in ctx.socket:
|
||||
for msg in json.loads(data):
|
||||
cmd, args = (msg[0], msg[1]) if len(msg) > 1 else (msg, None)
|
||||
await process_server_cmd(ctx, cmd, args)
|
||||
print('Disconnected from multiworld server, type /connect to reconnect')
|
||||
except ConnectionRefusedError:
|
||||
print('Connection refused by the multiworld server')
|
||||
except (OSError, websockets.InvalidURI):
|
||||
print('Failed to connect to the multiworld server')
|
||||
except Exception as e:
|
||||
print('Lost connection to the multiworld server, type /connect to reconnect')
|
||||
if type(e) is not websockets.ConnectionClosed:
|
||||
logging.exception(e)
|
||||
finally:
|
||||
ctx.name = None
|
||||
ctx.team = None
|
||||
ctx.slot = None
|
||||
ctx.expected_rom = None
|
||||
ctx.rom_confirmed = False
|
||||
socket, ctx.socket = ctx.socket, None
|
||||
if socket is not None and not socket.closed:
|
||||
await socket.close()
|
||||
ctx.server_task = None
|
||||
|
||||
async def process_server_cmd(ctx : Context, cmd, args):
|
||||
if cmd == 'RoomInfo':
|
||||
print('--------------------------------')
|
||||
print('Room Information:')
|
||||
print('--------------------------------')
|
||||
if args['password']:
|
||||
print('Password required')
|
||||
print('%d players seed' % args['slots'])
|
||||
if len(args['players']) < 1:
|
||||
print('No player connected')
|
||||
else:
|
||||
args['players'].sort(key=lambda player: ('' if not player[1] else player[1].lower(), player[2]))
|
||||
current_team = 0
|
||||
print('Connected players:')
|
||||
for name, team, slot in args['players']:
|
||||
if team != current_team:
|
||||
print(' Default team' if not team else ' Team: %s' % team)
|
||||
current_team = team
|
||||
print(' %s (Player %d)' % (name, slot))
|
||||
await server_auth(ctx, args['password'])
|
||||
|
||||
if cmd == 'ConnectionRefused':
|
||||
password_requested = False
|
||||
if 'InvalidPassword' in args:
|
||||
print('Invalid password')
|
||||
ctx.password = None
|
||||
password_requested = True
|
||||
if 'InvalidName' in args:
|
||||
print('Invalid name')
|
||||
ctx.name = None
|
||||
if 'NameAlreadyTaken' in args:
|
||||
print('Name already taken')
|
||||
ctx.name = None
|
||||
if 'InvalidTeam' in args:
|
||||
print('Invalid team name')
|
||||
ctx.team = None
|
||||
if 'InvalidSlot' in args:
|
||||
print('Invalid player slot')
|
||||
ctx.slot = None
|
||||
if 'SlotAlreadyTaken' in args:
|
||||
print('Player slot already in use for that team')
|
||||
ctx.team = None
|
||||
ctx.slot = None
|
||||
await server_auth(ctx, password_requested)
|
||||
|
||||
if cmd == 'Connected':
|
||||
ctx.expected_rom = args
|
||||
if ctx.last_rom == ctx.expected_rom:
|
||||
rom_confirmed(ctx)
|
||||
if ctx.locations_checked:
|
||||
await send_msgs(ctx.socket, [['LocationChecks', [Regions.location_table[loc][0] for loc in ctx.locations_checked]]])
|
||||
elif ctx.last_rom is not None:
|
||||
raise Exception('Different ROM expected from server')
|
||||
|
||||
if cmd == 'ReceivedItems':
|
||||
start_index, items = args
|
||||
if start_index == 0:
|
||||
ctx.items_received = []
|
||||
elif start_index != len(ctx.items_received):
|
||||
sync_msg = [['Sync']]
|
||||
if ctx.locations_checked:
|
||||
sync_msg.append(['LocationChecks', [Regions.location_table[loc][0] for loc in ctx.locations_checked]])
|
||||
await send_msgs(ctx.socket, sync_msg)
|
||||
if start_index == len(ctx.items_received):
|
||||
for item in items:
|
||||
ctx.items_received.append(ReceivedItem(item[0], item[1], item[2], item[3]))
|
||||
|
||||
if cmd == 'ItemSent':
|
||||
player_sent, player_recvd, item, location = args
|
||||
item = color(get_item_name_from_id(item), 'cyan' if player_sent != ctx.name else 'green')
|
||||
player_sent = color(player_sent, 'yellow' if player_sent != ctx.name else 'magenta')
|
||||
player_recvd = color(player_recvd, 'yellow' if player_recvd != ctx.name else 'magenta')
|
||||
print('(%s) %s sent %s to %s (%s)' % (ctx.team if ctx.team else 'Team', player_sent, item, player_recvd, get_location_name_from_address(location)))
|
||||
|
||||
if cmd == 'Print':
|
||||
print(args)
|
||||
|
||||
async def server_auth(ctx : Context, password_requested):
|
||||
if password_requested and not ctx.password:
|
||||
print('Enter the password required to join this game:')
|
||||
ctx.password = await console_input(ctx)
|
||||
while not ctx.name or not re.match(r'\w{1,10}', ctx.name):
|
||||
print('Enter your name (10 characters):')
|
||||
ctx.name = await console_input(ctx)
|
||||
if not ctx.team:
|
||||
print('Enter your team name (optional):')
|
||||
ctx.team = await console_input(ctx)
|
||||
if ctx.team == '': ctx.team = None
|
||||
if not ctx.slot:
|
||||
print('Choose your player slot (optional):')
|
||||
slot = await console_input(ctx)
|
||||
ctx.slot = int(slot) if slot.isdigit() else None
|
||||
await send_msgs(ctx.socket, [['Connect', {'password': ctx.password, 'name': ctx.name, 'team': ctx.team, 'slot': ctx.slot}]])
|
||||
|
||||
async def console_input(ctx : Context):
|
||||
ctx.input_requests += 1
|
||||
return await ctx.input_queue.get()
|
||||
|
||||
async def console_loop(ctx : Context):
|
||||
while not ctx.exit_event.is_set():
|
||||
input = await aioconsole.ainput()
|
||||
|
||||
if ctx.input_requests > 0:
|
||||
ctx.input_requests -= 1
|
||||
ctx.input_queue.put_nowait(input)
|
||||
continue
|
||||
|
||||
command = input.split()
|
||||
if not command:
|
||||
continue
|
||||
|
||||
if command[0] == '/exit':
|
||||
ctx.exit_event.set()
|
||||
|
||||
if command[0] == '/installcolors' and 'colorama' not in sys.modules:
|
||||
subprocess.call([sys.executable, '-m', 'pip', 'install', '--upgrade', 'colorama'])
|
||||
global colorama
|
||||
import colorama
|
||||
colorama.init()
|
||||
|
||||
if command[0] == '/snes':
|
||||
asyncio.create_task(snes_connect(ctx, command[1] if len(command) > 1 else None))
|
||||
if command[0] in ['/snes_close', '/snes_quit']:
|
||||
if ctx.snes_socket is not None and not ctx.snes_socket.closed:
|
||||
await ctx.snes_socket.close()
|
||||
|
||||
async def disconnect():
|
||||
if ctx.socket is not None and not ctx.socket.closed:
|
||||
await ctx.socket.close()
|
||||
if ctx.server_task is not None:
|
||||
await ctx.server_task
|
||||
async def connect():
|
||||
await disconnect()
|
||||
ctx.server_task = asyncio.create_task(server_loop(ctx))
|
||||
|
||||
if command[0] in ['/connect', '/reconnect']:
|
||||
if len(command) > 1:
|
||||
ctx.server_address = command[1]
|
||||
asyncio.create_task(connect())
|
||||
if command[0] == '/disconnect':
|
||||
asyncio.create_task(disconnect())
|
||||
if command[0][:1] != '/':
|
||||
asyncio.create_task(send_msgs(ctx.socket, [['Say', input]]))
|
||||
|
||||
if command[0] == '/received':
|
||||
print('Received items:')
|
||||
for index, item in enumerate(ctx.items_received, 1):
|
||||
print('%s from %s (%s) (%d/%d in list)' % (
|
||||
color(get_item_name_from_id(item.item), 'red', 'bold'), color(item.player_name, 'yellow'),
|
||||
get_location_name_from_address(item.location), index, len(ctx.items_received)))
|
||||
|
||||
if command[0] == '/missing':
|
||||
for location in [k for k, v in Regions.location_table.items() if type(v[0]) is int]:
|
||||
if location not in ctx.locations_checked:
|
||||
print('Missing: ' + location)
|
||||
if command[0] == '/getitem' and len(command) > 1:
|
||||
item = input[9:]
|
||||
item_id = Items.item_table[item][3] if item in Items.item_table else None
|
||||
if type(item_id) is int and item_id in range(0x100):
|
||||
print('Sending item: ' + item)
|
||||
snes_buffered_write(ctx, RECV_ITEM_ADDR, bytes([item_id]))
|
||||
snes_buffered_write(ctx, RECV_ITEM_PLAYER_ADDR, bytes([0]))
|
||||
else:
|
||||
print('Invalid item: ' + item)
|
||||
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
def rom_confirmed(ctx : Context):
|
||||
ctx.rom_confirmed = True
|
||||
print('ROM hash Confirmed')
|
||||
|
||||
def get_item_name_from_id(code):
|
||||
items = [k for k, i in Items.item_table.items() if type(i[3]) is int and i[3] == code]
|
||||
return items[0] if items else 'Unknown item'
|
||||
|
||||
def get_location_name_from_address(address):
|
||||
if type(address) is str:
|
||||
return address
|
||||
|
||||
locs = [k for k, l in Regions.location_table.items() if type(l[0]) is int and l[0] == address]
|
||||
return locs[0] if locs else 'Unknown location'
|
||||
|
||||
async def track_locations(ctx : Context, roomid, roomdata):
|
||||
new_locations = []
|
||||
def new_check(location):
|
||||
ctx.locations_checked.add(location)
|
||||
print("New check: %s (%d/216)" % (location, len(ctx.locations_checked)))
|
||||
new_locations.append(Regions.location_table[location][0])
|
||||
|
||||
for location, (loc_roomid, loc_mask) in location_table_uw.items():
|
||||
if location not in ctx.locations_checked and loc_roomid == roomid and (roomdata << 4) & loc_mask != 0:
|
||||
new_check(location)
|
||||
|
||||
uw_begin = 0x129
|
||||
uw_end = 0
|
||||
uw_unchecked = {}
|
||||
for location, (roomid, mask) in location_table_uw.items():
|
||||
if location not in ctx.locations_checked:
|
||||
uw_unchecked[location] = (roomid, mask)
|
||||
uw_begin = min(uw_begin, roomid)
|
||||
uw_end = max(uw_end, roomid + 1)
|
||||
if uw_begin < uw_end:
|
||||
uw_data = await snes_read(ctx, SAVEDATA_START + (uw_begin * 2), (uw_end - uw_begin) * 2)
|
||||
if uw_data is not None:
|
||||
for location, (roomid, mask) in uw_unchecked.items():
|
||||
offset = (roomid - uw_begin) * 2
|
||||
roomdata = uw_data[offset] | (uw_data[offset + 1] << 8)
|
||||
if roomdata & mask != 0:
|
||||
new_check(location)
|
||||
|
||||
ow_begin = 0x82
|
||||
ow_end = 0
|
||||
ow_unchecked = {}
|
||||
for location, screenid in location_table_ow.items():
|
||||
if location not in ctx.locations_checked:
|
||||
ow_unchecked[location] = screenid
|
||||
ow_begin = min(ow_begin, screenid)
|
||||
ow_end = max(ow_end, screenid + 1)
|
||||
if ow_begin < ow_end:
|
||||
ow_data = await snes_read(ctx, SAVEDATA_START + 0x280 + ow_begin, ow_end - ow_begin)
|
||||
if ow_data is not None:
|
||||
for location, screenid in ow_unchecked.items():
|
||||
if ow_data[screenid - ow_begin] & 0x40 != 0:
|
||||
new_check(location)
|
||||
|
||||
if not all([location in ctx.locations_checked for location in location_table_npc.keys()]):
|
||||
npc_data = await snes_read(ctx, SAVEDATA_START + 0x410, 2)
|
||||
if npc_data is not None:
|
||||
npc_value = npc_data[0] | (npc_data[1] << 8)
|
||||
for location, mask in location_table_npc.items():
|
||||
if npc_value & mask != 0 and location not in ctx.locations_checked:
|
||||
new_check(location)
|
||||
|
||||
if not all([location in ctx.locations_checked for location in location_table_misc.keys()]):
|
||||
misc_data = await snes_read(ctx, SAVEDATA_START + 0x3c6, 4)
|
||||
if misc_data is not None:
|
||||
for location, (offset, mask) in location_table_misc.items():
|
||||
assert(0x3c6 <= offset <= 0x3c9)
|
||||
if misc_data[offset - 0x3c6] & mask != 0 and location not in ctx.locations_checked:
|
||||
new_check(location)
|
||||
|
||||
await send_msgs(ctx.socket, [['LocationChecks', new_locations]])
|
||||
|
||||
async def game_watcher(ctx : Context):
|
||||
while not ctx.exit_event.is_set():
|
||||
await asyncio.sleep(2)
|
||||
|
||||
if not ctx.rom_confirmed:
|
||||
rom = await snes_read(ctx, ROMNAME_START, ROMNAME_SIZE)
|
||||
if rom is None or rom == bytes([0] * ROMNAME_SIZE):
|
||||
continue
|
||||
if list(rom) != ctx.last_rom:
|
||||
ctx.last_rom = list(rom)
|
||||
ctx.locations_checked = set()
|
||||
if ctx.expected_rom is not None:
|
||||
if ctx.last_rom != ctx.expected_rom:
|
||||
print("Wrong ROM detected")
|
||||
await ctx.snes_socket.close()
|
||||
continue
|
||||
else:
|
||||
rom_confirmed(ctx)
|
||||
|
||||
gamemode = await snes_read(ctx, WRAM_START + 0x10, 1)
|
||||
if gamemode is None or gamemode[0] not in INGAME_MODES:
|
||||
continue
|
||||
|
||||
data = await snes_read(ctx, RECV_PROGRESS_ADDR, 7)
|
||||
if data is None:
|
||||
continue
|
||||
|
||||
recv_index = data[0] | (data[1] << 8)
|
||||
assert(RECV_ITEM_ADDR == RECV_PROGRESS_ADDR + 2)
|
||||
recv_item = data[2]
|
||||
assert(ROOMID_ADDR == RECV_PROGRESS_ADDR + 4)
|
||||
roomid = data[4] | (data[5] << 8)
|
||||
assert(ROOMDATA_ADDR == RECV_PROGRESS_ADDR + 6)
|
||||
roomdata = data[6]
|
||||
|
||||
await track_locations(ctx, roomid, roomdata)
|
||||
|
||||
if recv_index < len(ctx.items_received) and recv_item == 0:
|
||||
item = ctx.items_received[recv_index]
|
||||
print('Received %s from %s (%s) (%d/%d in list)' % (
|
||||
color(get_item_name_from_id(item.item), 'red', 'bold'), color(item.player_name, 'yellow'),
|
||||
get_location_name_from_address(item.location), recv_index + 1, len(ctx.items_received)))
|
||||
recv_index += 1
|
||||
snes_buffered_write(ctx, RECV_PROGRESS_ADDR, bytes([recv_index & 0xFF, (recv_index >> 8) & 0xFF]))
|
||||
snes_buffered_write(ctx, RECV_ITEM_ADDR, bytes([item.item]))
|
||||
snes_buffered_write(ctx, RECV_ITEM_PLAYER_ADDR, bytes([item.player_id]))
|
||||
|
||||
await snes_flush_writes(ctx)
|
||||
|
||||
async def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--snes', default='localhost:8080', help='Address of the QUsb2snes server.')
|
||||
parser.add_argument('--connect', default=None, help='Address of the multiworld host.')
|
||||
parser.add_argument('--password', default=None, help='Password of the multiworld host.')
|
||||
parser.add_argument('--name', default=None)
|
||||
parser.add_argument('--team', default=None)
|
||||
parser.add_argument('--slot', default=None, type=int)
|
||||
args = parser.parse_args()
|
||||
|
||||
ctx = Context(args.snes, args.connect, args.password, args.name, args.team, args.slot)
|
||||
|
||||
input_task = asyncio.create_task(console_loop(ctx))
|
||||
|
||||
await snes_connect(ctx)
|
||||
|
||||
if ctx.server_task is None:
|
||||
ctx.server_task = asyncio.create_task(server_loop(ctx))
|
||||
|
||||
watcher_task = asyncio.create_task(game_watcher(ctx))
|
||||
|
||||
|
||||
await ctx.exit_event.wait()
|
||||
|
||||
|
||||
await watcher_task
|
||||
|
||||
if ctx.socket is not None and not ctx.socket.closed:
|
||||
await ctx.socket.close()
|
||||
if ctx.server_task is not None:
|
||||
await ctx.server_task
|
||||
|
||||
if ctx.snes_socket is not None and not ctx.snes_socket.closed:
|
||||
await ctx.snes_socket.close()
|
||||
|
||||
while ctx.input_requests > 0:
|
||||
ctx.input_queue.put_nowait(None)
|
||||
ctx.input_requests -= 1
|
||||
|
||||
await input_task
|
||||
|
||||
if __name__ == '__main__':
|
||||
if 'colorama' in sys.modules:
|
||||
colorama.init()
|
||||
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(main())
|
||||
loop.run_until_complete(asyncio.gather(*asyncio.Task.all_tasks()))
|
||||
loop.close()
|
||||
|
||||
if 'colorama' in sys.modules:
|
||||
colorama.deinit()
|
|
@ -0,0 +1,393 @@
|
|||
import aioconsole
|
||||
import argparse
|
||||
import asyncio
|
||||
import functools
|
||||
import json
|
||||
import logging
|
||||
import pickle
|
||||
import re
|
||||
import urllib.request
|
||||
import websockets
|
||||
|
||||
import Items
|
||||
import Regions
|
||||
from MultiClient import ReceivedItem, get_item_name_from_id, get_location_name_from_address
|
||||
|
||||
class Client:
|
||||
def __init__(self, socket):
|
||||
self.socket = socket
|
||||
self.auth = False
|
||||
self.name = None
|
||||
self.team = None
|
||||
self.slot = None
|
||||
self.send_index = 0
|
||||
|
||||
class MultiWorld:
|
||||
def __init__(self):
|
||||
self.players = None
|
||||
self.rom_names = {}
|
||||
self.locations = {}
|
||||
|
||||
class Context:
|
||||
def __init__(self, host, port, password):
|
||||
self.data_filename = None
|
||||
self.save_filename = None
|
||||
self.disable_save = False
|
||||
self.world = MultiWorld()
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.password = password
|
||||
self.server = None
|
||||
self.clients = []
|
||||
self.received_items = {}
|
||||
|
||||
def get_room_info(ctx : Context):
|
||||
return {
|
||||
'password': ctx.password is not None,
|
||||
'slots': ctx.world.players,
|
||||
'players': [(client.name, client.team, client.slot) for client in ctx.clients if client.auth]
|
||||
}
|
||||
|
||||
def same_name(lhs, rhs):
|
||||
return lhs.lower() == rhs.lower()
|
||||
|
||||
def same_team(lhs, rhs):
|
||||
return (type(lhs) is type(rhs)) and ((not lhs and not rhs) or (lhs.lower() == rhs.lower()))
|
||||
|
||||
async def send_msgs(websocket, msgs):
|
||||
if not websocket or not websocket.open or websocket.closed:
|
||||
return
|
||||
try:
|
||||
await websocket.send(json.dumps(msgs))
|
||||
except websockets.ConnectionClosed:
|
||||
pass
|
||||
|
||||
def broadcast_all(ctx : Context, msgs):
|
||||
for client in ctx.clients:
|
||||
if client.auth:
|
||||
asyncio.create_task(send_msgs(client.socket, msgs))
|
||||
|
||||
def broadcast_team(ctx : Context, team, msgs):
|
||||
for client in ctx.clients:
|
||||
if client.auth and same_team(client.team, team):
|
||||
asyncio.create_task(send_msgs(client.socket, msgs))
|
||||
|
||||
def notify_all(ctx : Context, text):
|
||||
print("Notice (all): %s" % text)
|
||||
broadcast_all(ctx, [['Print', text]])
|
||||
|
||||
def notify_team(ctx : Context, team : str, text : str):
|
||||
print("Team notice (%s): %s" % ("Default" if not team else team, text))
|
||||
broadcast_team(ctx, team, [['Print', text]])
|
||||
|
||||
def notify_client(client : Client, text : str):
|
||||
if not client.auth:
|
||||
return
|
||||
print("Player notice (%s): %s" % (client.name, text))
|
||||
asyncio.create_task(send_msgs(client.socket, [['Print', text]]))
|
||||
|
||||
async def server(websocket, path, ctx : Context):
|
||||
client = Client(websocket)
|
||||
ctx.clients.append(client)
|
||||
|
||||
try:
|
||||
await on_client_connected(ctx, client)
|
||||
async for data in websocket:
|
||||
for msg in json.loads(data):
|
||||
if len(msg) == 1:
|
||||
cmd = msg
|
||||
args = None
|
||||
else:
|
||||
cmd = msg[0]
|
||||
args = msg[1]
|
||||
await process_client_cmd(ctx, client, cmd, args)
|
||||
except Exception as e:
|
||||
if type(e) is not websockets.ConnectionClosed:
|
||||
logging.exception(e)
|
||||
finally:
|
||||
await on_client_disconnected(ctx, client)
|
||||
ctx.clients.remove(client)
|
||||
|
||||
async def on_client_connected(ctx : Context, client : Client):
|
||||
await send_msgs(client.socket, [['RoomInfo', get_room_info(ctx)]])
|
||||
|
||||
async def on_client_disconnected(ctx : Context, client : Client):
|
||||
if client.auth:
|
||||
await on_client_left(ctx, client)
|
||||
|
||||
async def on_client_joined(ctx : Context, client : Client):
|
||||
notify_all(ctx, "%s has joined the game as player %d for %s" % (client.name, client.slot, "the default team" if not client.team else "team %s" % client.team))
|
||||
|
||||
async def on_client_left(ctx : Context, client : Client):
|
||||
notify_all(ctx, "%s (Player %d, %s) has left the game" % (client.name, client.slot, "Default team" if not client.team else "Team %s" % client.team))
|
||||
|
||||
def get_connected_players_string(ctx : Context):
|
||||
auth_clients = [c for c in ctx.clients if c.auth]
|
||||
if not auth_clients:
|
||||
return 'No player connected'
|
||||
|
||||
auth_clients.sort(key=lambda c: ('' if not c.team else c.team.lower(), c.slot))
|
||||
current_team = 0
|
||||
text = ''
|
||||
for c in auth_clients:
|
||||
if c.team != current_team:
|
||||
text += '::' + ('default team' if not c.team else c.team) + ':: '
|
||||
current_team = c.team
|
||||
text += '%d:%s ' % (c.slot, c.name)
|
||||
return 'Connected players: ' + text[:-1]
|
||||
|
||||
def get_player_name_in_team(ctx : Context, team, slot):
|
||||
for client in ctx.clients:
|
||||
if client.auth and same_team(team, client.team) and client.slot == slot:
|
||||
return client.name
|
||||
return "Player %d" % slot
|
||||
|
||||
def get_client_from_name(ctx : Context, name):
|
||||
for client in ctx.clients:
|
||||
if client.auth and same_name(name, client.name):
|
||||
return client
|
||||
return None
|
||||
|
||||
def get_received_items(ctx : Context, team, player):
|
||||
for (c_team, c_id), items in ctx.received_items.items():
|
||||
if c_id == player and same_team(c_team, team):
|
||||
return items
|
||||
ctx.received_items[(team, player)] = []
|
||||
return ctx.received_items[(team, player)]
|
||||
|
||||
def tuplize_received_items(items):
|
||||
return [(item.item, item.location, item.player_id, item.player_name) for item in items]
|
||||
|
||||
def send_new_items(ctx : Context):
|
||||
for client in ctx.clients:
|
||||
if not client.auth:
|
||||
continue
|
||||
items = get_received_items(ctx, client.team, client.slot)
|
||||
if len(items) > client.send_index:
|
||||
asyncio.create_task(send_msgs(client.socket, [['ReceivedItems', (client.send_index, tuplize_received_items(items)[client.send_index:])]]))
|
||||
client.send_index = len(items)
|
||||
|
||||
def forfeit_player(ctx : Context, team, slot, name):
|
||||
all_locations = [values[0] for values in Regions.location_table.values() if type(values[0]) is int]
|
||||
notify_all(ctx, "%s (Player %d) in team %s has forfeited" % (name, slot, team if team else 'default'))
|
||||
register_location_checks(ctx, name, team, slot, all_locations)
|
||||
|
||||
def register_location_checks(ctx : Context, name, team, slot, locations):
|
||||
found_items = False
|
||||
for location in locations:
|
||||
if (location, slot) in ctx.world.locations:
|
||||
target_item, target_player = ctx.world.locations[(location, slot)]
|
||||
if target_player != slot:
|
||||
found = False
|
||||
recvd_items = get_received_items(ctx, team, target_player)
|
||||
for recvd_item in recvd_items:
|
||||
if recvd_item.location == location and recvd_item.player_id == slot:
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
new_item = ReceivedItem(target_item, location, slot, name)
|
||||
recvd_items.append(new_item)
|
||||
target_player_name = get_player_name_in_team(ctx, team, target_player)
|
||||
broadcast_team(ctx, team, [['ItemSent', (name, target_player_name, target_item, location)]])
|
||||
print('(%s) %s sent %s to %s (%s)' % (team if team else 'Team', name, get_item_name_from_id(target_item), target_player_name, get_location_name_from_address(location)))
|
||||
found_items = True
|
||||
send_new_items(ctx)
|
||||
|
||||
if found_items and not ctx.disable_save:
|
||||
try:
|
||||
with open(ctx.save_filename, "wb") as f:
|
||||
pickle.dump((ctx.world.players, ctx.world.rom_names, ctx.received_items), f, pickle.HIGHEST_PROTOCOL)
|
||||
except Exception as e:
|
||||
logging.exception(e)
|
||||
|
||||
async def process_client_cmd(ctx : Context, client : Client, cmd, args):
|
||||
if type(cmd) is not str:
|
||||
await send_msgs(client.socket, [['InvalidCmd']])
|
||||
return
|
||||
|
||||
if cmd == 'Connect':
|
||||
if not args or type(args) is not dict or \
|
||||
'password' not in args or type(args['password']) not in [str, type(None)] or \
|
||||
'name' not in args or type(args['name']) is not str or \
|
||||
'team' not in args or type(args['team']) not in [str, type(None)] or \
|
||||
'slot' not in args or type(args['slot']) not in [int, type(None)]:
|
||||
await send_msgs(client.socket, [['InvalidArguments', 'Connect']])
|
||||
return
|
||||
|
||||
errors = set()
|
||||
if ctx.password is not None and ('password' not in args or args['password'] != ctx.password):
|
||||
errors.add('InvalidPassword')
|
||||
|
||||
if 'name' not in args or not args['name'] or not re.match(r'\w{1,10}', args['name']):
|
||||
errors.add('InvalidName')
|
||||
elif any([same_name(c.name, args['name']) for c in ctx.clients if c.auth]):
|
||||
errors.add('NameAlreadyTaken')
|
||||
else:
|
||||
client.name = args['name']
|
||||
|
||||
if 'team' in args and args['team'] is not None and not re.match(r'\w{1,15}', args['team']):
|
||||
errors.add('InvalidTeam')
|
||||
else:
|
||||
client.team = args['team'] if 'team' in args else None
|
||||
|
||||
if 'slot' in args and any([c.slot == args['slot'] for c in ctx.clients if c.auth and same_team(c.team, client.team)]):
|
||||
errors.add('SlotAlreadyTaken')
|
||||
elif 'slot' not in args or not args['slot']:
|
||||
for slot in range(1, ctx.world.players + 1):
|
||||
if slot not in [c.slot for c in ctx.clients if c.auth and same_team(c.team, client.team)]:
|
||||
client.slot = slot
|
||||
break
|
||||
elif slot == ctx.world.players:
|
||||
errors.add('SlotAlreadyTaken')
|
||||
elif args['slot'] not in range(1, ctx.world.players + 1):
|
||||
errors.add('InvalidSlot')
|
||||
else:
|
||||
client.slot = args['slot']
|
||||
|
||||
if errors:
|
||||
client.name = None
|
||||
client.team = None
|
||||
client.slot = None
|
||||
await send_msgs(client.socket, [['ConnectionRefused', list(errors)]])
|
||||
else:
|
||||
client.auth = True
|
||||
reply = [['Connected', ctx.world.rom_names[client.slot]]]
|
||||
items = get_received_items(ctx, client.team, client.slot)
|
||||
if items:
|
||||
reply.append(['ReceivedItems', (0, tuplize_received_items(items))])
|
||||
client.send_index = len(items)
|
||||
await send_msgs(client.socket, reply)
|
||||
await on_client_joined(ctx, client)
|
||||
|
||||
if not client.auth:
|
||||
return
|
||||
|
||||
if cmd == 'Sync':
|
||||
items = get_received_items(ctx, client.team, client.slot)
|
||||
if items:
|
||||
client.send_index = len(items)
|
||||
await send_msgs(client.socket, ['ReceivedItems', (0, tuplize_received_items(items))])
|
||||
|
||||
if cmd == 'LocationChecks':
|
||||
if type(args) is not list:
|
||||
await send_msgs(client.socket, [['InvalidArguments', 'LocationChecks']])
|
||||
return
|
||||
register_location_checks(ctx, client.name, client.team, client.slot, args)
|
||||
|
||||
if cmd == 'Say':
|
||||
if type(args) is not str or not args.isprintable():
|
||||
await send_msgs(client.socket, [['InvalidArguments', 'Say']])
|
||||
return
|
||||
|
||||
notify_all(ctx, client.name + ': ' + args)
|
||||
|
||||
if args[:8] == '!players':
|
||||
notify_all(ctx, get_connected_players_string(ctx))
|
||||
if args[:8] == '!forfeit':
|
||||
forfeit_player(ctx, client.team, client.slot, client.name)
|
||||
|
||||
def set_password(ctx : Context, password):
|
||||
ctx.password = password
|
||||
print('Password set to ' + password if password is not None else 'Password disabled')
|
||||
|
||||
async def console(ctx : Context):
|
||||
while True:
|
||||
input = await aioconsole.ainput()
|
||||
|
||||
command = input.split()
|
||||
if not command:
|
||||
continue
|
||||
|
||||
if command[0] == '/exit':
|
||||
ctx.server.ws_server.close()
|
||||
break
|
||||
|
||||
if command[0] == '/players':
|
||||
print(get_connected_players_string(ctx))
|
||||
if command[0] == '/password':
|
||||
set_password(ctx, command[1] if len(command) > 1 else None)
|
||||
if command[0] == '/kick' and len(command) > 1:
|
||||
client = get_client_from_name(ctx, command[1])
|
||||
if client and client.socket and not client.socket.closed:
|
||||
await client.socket.close()
|
||||
|
||||
if command[0] == '/forfeitslot' and len(command) == 3 and command[2].isdigit():
|
||||
team = command[1] if command[1] != 'default' else None
|
||||
slot = int(command[2])
|
||||
name = get_player_name_in_team(ctx, team, slot)
|
||||
forfeit_player(ctx, team, slot, name)
|
||||
if command[0] == '/forfeitplayer' and len(command) > 1:
|
||||
client = get_client_from_name(ctx, command[1])
|
||||
if client:
|
||||
forfeit_player(ctx, client.team, client.slot, client.name)
|
||||
if command[0] == '/senditem' and len(command) > 2:
|
||||
[(player, item)] = re.findall(r'\S* (\S*) (.*)', input)
|
||||
if item in Items.item_table:
|
||||
client = get_client_from_name(ctx, player)
|
||||
if client:
|
||||
new_item = ReceivedItem(Items.item_table[item][3], "cheat console", 0, "server")
|
||||
get_received_items(ctx, client.team, client.slot).append(new_item)
|
||||
notify_all(ctx, 'Cheat console: sending "' + item + '" to ' + client.name)
|
||||
send_new_items(ctx)
|
||||
else:
|
||||
print("Unknown item: " + item)
|
||||
|
||||
if command[0][0] != '/':
|
||||
notify_all(ctx, '[Server]: ' + input)
|
||||
|
||||
async def main():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--host', default=None)
|
||||
parser.add_argument('--port', default=38281, type=int)
|
||||
parser.add_argument('--password', default=None)
|
||||
parser.add_argument('--multidata', default=None)
|
||||
parser.add_argument('--savefile', default=None)
|
||||
parser.add_argument('--disable_save', default=False, action='store_true')
|
||||
args = parser.parse_args()
|
||||
|
||||
ctx = Context(args.host, args.port, args.password)
|
||||
|
||||
ctx.data_filename = args.multidata
|
||||
|
||||
try:
|
||||
if not ctx.data_filename:
|
||||
import tkinter
|
||||
import tkinter.filedialog
|
||||
root = tkinter.Tk()
|
||||
root.withdraw()
|
||||
ctx.data_filename = tkinter.filedialog.askopenfilename(filetypes=(("Multiworld data","*multidata"),))
|
||||
|
||||
with open(ctx.data_filename, 'rb') as f:
|
||||
ctx.world = pickle.load(f)
|
||||
except Exception as e:
|
||||
print('Failed to read multiworld data (%s)' % e)
|
||||
return
|
||||
|
||||
ip = urllib.request.urlopen('https://v4.ident.me').read().decode('utf8') if not ctx.host else ctx.host
|
||||
print('Hosting game of %d players (%s) at %s:%d' % (ctx.world.players, 'No password' if not ctx.password else 'Password: %s' % ctx.password, ip, ctx.port))
|
||||
|
||||
ctx.disable_save = args.disable_save
|
||||
if not ctx.disable_save:
|
||||
if not ctx.save_filename:
|
||||
ctx.save_filename = (ctx.data_filename[:-9] if ctx.data_filename[-9:] == 'multidata' else (ctx.data_filename + '_')) + 'multisave'
|
||||
try:
|
||||
with open(ctx.save_filename, 'rb') as f:
|
||||
players, rom_names, received_items = pickle.load(f)
|
||||
if players != ctx.world.players or rom_names != ctx.world.rom_names:
|
||||
raise Exception('Save file mismatch, will start a new game')
|
||||
ctx.received_items = received_items
|
||||
print('Loaded save file with %d received items for %d players' % (sum([len(p) for p in received_items.values()]), len(received_items)))
|
||||
except FileNotFoundError:
|
||||
print('No save data found, starting a new game')
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
ctx.server = websockets.serve(functools.partial(server,ctx=ctx), ctx.host, ctx.port, ping_timeout=None, ping_interval=None)
|
||||
await ctx.server
|
||||
await console(ctx)
|
||||
|
||||
if __name__ == '__main__':
|
||||
loop = asyncio.get_event_loop()
|
||||
loop.run_until_complete(main())
|
||||
loop.run_until_complete(asyncio.gather(*asyncio.Task.all_tasks()))
|
||||
loop.close()
|
|
@ -10,7 +10,7 @@ import sys
|
|||
from BaseClasses import World
|
||||
from Regions import create_regions
|
||||
from EntranceShuffle import link_entrances, connect_entrance, connect_two_way, connect_exit
|
||||
from Rom import patch_rom, LocalRom, Sprite, write_string_to_rom
|
||||
from Rom import patch_rom, LocalRom, Sprite, write_string_to_rom, apply_rom_settings
|
||||
from Rules import set_rules
|
||||
from Dungeons import create_dungeons
|
||||
from Items import ItemFactory
|
||||
|
@ -74,7 +74,9 @@ def main(args):
|
|||
sprite = None
|
||||
|
||||
rom = LocalRom(args.rom)
|
||||
patch_rom(world, 1, rom, args.heartbeep, args.heartcolor, sprite)
|
||||
patch_rom(world, 1, rom)
|
||||
|
||||
apply_rom_settings(rom, args.heartbeep, args.heartcolor, world.quickswap, world.fastmenu, world.disable_music, sprite)
|
||||
|
||||
for textname, texttype, text in text_patches:
|
||||
if texttype == 'text':
|
||||
|
|
470
Regions.py
470
Regions.py
|
@ -331,8 +331,8 @@ def _create_region(player, name, type, hint='Hyrule', locations=None, exits=None
|
|||
for exit in exits:
|
||||
ret.exits.append(Entrance(player, exit, ret))
|
||||
for location in locations:
|
||||
address, crystal, hint_text = location_table[location]
|
||||
ret.locations.append(Location(player, location, address, crystal, hint_text, ret))
|
||||
address, player_address, crystal, hint_text = location_table[location]
|
||||
ret.locations.append(Location(player, location, address, crystal, hint_text, ret, player_address))
|
||||
return ret
|
||||
|
||||
def mark_light_world_regions(world):
|
||||
|
@ -397,236 +397,236 @@ default_shop_contents = {
|
|||
'Potion Shop': [('Red Potion', 120), ('Green Potion', 60), ('Blue Potion', 160)],
|
||||
}
|
||||
|
||||
location_table = {'Mushroom': (0x180013, False, 'in the woods'),
|
||||
'Bottle Merchant': (0x2eb18, False, 'with a merchant'),
|
||||
'Flute Spot': (0x18014a, False, 'underground'),
|
||||
'Sunken Treasure': (0x180145, False, 'underwater'),
|
||||
'Purple Chest': (0x33d68, False, 'from a box'),
|
||||
"Blind's Hideout - Top": (0xeb0f, False, 'in a basement'),
|
||||
"Blind's Hideout - Left": (0xeb12, False, 'in a basement'),
|
||||
"Blind's Hideout - Right": (0xeb15, False, 'in a basement'),
|
||||
"Blind's Hideout - Far Left": (0xeb18, False, 'in a basement'),
|
||||
"Blind's Hideout - Far Right": (0xeb1b, False, 'in a basement'),
|
||||
"Link's Uncle": (0x2df45, False, 'with your uncle'),
|
||||
'Secret Passage': (0xe971, False, 'near your uncle'),
|
||||
'King Zora': (0xee1c3, False, 'at a high price'),
|
||||
"Zora's Ledge": (0x180149, False, 'near Zora'),
|
||||
'Waterfall Fairy - Left': (0xe9b0, False, 'near a fairy'),
|
||||
'Waterfall Fairy - Right': (0xe9d1, False, 'near a fairy'),
|
||||
"King's Tomb": (0xe97a, False, 'alone in a cave'),
|
||||
'Floodgate Chest': (0xe98c, False, 'in the dam'),
|
||||
"Link's House": (0xe9bc, False, 'in your home'),
|
||||
'Kakariko Tavern': (0xe9ce, False, 'in the bar'),
|
||||
'Chicken House': (0xe9e9, False, 'near poultry'),
|
||||
"Aginah's Cave": (0xe9f2, False, 'with Aginah'),
|
||||
"Sahasrahla's Hut - Left": (0xea82, False, 'near the elder'),
|
||||
"Sahasrahla's Hut - Middle": (0xea85, False, 'near the elder'),
|
||||
"Sahasrahla's Hut - Right": (0xea88, False, 'near the elder'),
|
||||
'Sahasrahla': (0x2f1fc, False, 'with the elder'),
|
||||
'Kakariko Well - Top': (0xea8e, False, 'in a well'),
|
||||
'Kakariko Well - Left': (0xea91, False, 'in a well'),
|
||||
'Kakariko Well - Middle': (0xea94, False, 'in a well'),
|
||||
'Kakariko Well - Right': (0xea97, False, 'in a well'),
|
||||
'Kakariko Well - Bottom': (0xea9a, False, 'in a well'),
|
||||
'Blacksmith': (0x18002a, False, 'with the smith'),
|
||||
'Magic Bat': (0x180015, False, 'with the bat'),
|
||||
'Sick Kid': (0x339cf, False, 'with the sick'),
|
||||
'Hobo': (0x33e7d, False, 'with the hobo'),
|
||||
'Lost Woods Hideout': (0x180000, False, 'near a thief'),
|
||||
'Lumberjack Tree': (0x180001, False, 'in a hole'),
|
||||
'Cave 45': (0x180003, False, 'alone in a cave'),
|
||||
'Graveyard Cave': (0x180004, False, 'alone in a cave'),
|
||||
'Checkerboard Cave': (0x180005, False, 'alone in a cave'),
|
||||
'Mini Moldorm Cave - Far Left': (0xeb42, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Left': (0xeb45, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Right': (0xeb48, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Far Right': (0xeb4b, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Generous Guy': (0x180010, False, 'near Moldorms'),
|
||||
'Ice Rod Cave': (0xeb4e, False, 'in a frozen cave'),
|
||||
'Bonk Rock Cave': (0xeb3f, False, 'alone in a cave'),
|
||||
'Library': (0x180012, False, 'near books'),
|
||||
'Potion Shop': (0x180014, False, 'near potions'),
|
||||
'Lake Hylia Island': (0x180144, False, 'on an island'),
|
||||
'Maze Race': (0x180142, False, 'at the race'),
|
||||
'Desert Ledge': (0x180143, False, 'in the desert'),
|
||||
'Desert Palace - Big Chest': (0xe98f, False, 'in Desert Palace'),
|
||||
'Desert Palace - Torch': (0x180160, False, 'in Desert Palace'),
|
||||
'Desert Palace - Map Chest': (0xe9b6, False, 'in Desert Palace'),
|
||||
'Desert Palace - Compass Chest': (0xe9cb, False, 'in Desert Palace'),
|
||||
'Desert Palace - Big Key Chest': (0xe9c2, False, 'in Desert Palace'),
|
||||
'Desert Palace - Boss': (0x180151, False, 'with Lanmolas'),
|
||||
'Eastern Palace - Compass Chest': (0xe977, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Big Chest': (0xe97d, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Cannonball Chest': (0xe9b3, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Big Key Chest': (0xe9b9, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Map Chest': (0xe9f5, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Boss': (0x180150, False, 'with the Armos'),
|
||||
'Master Sword Pedestal': (0x289b0, False, 'at the pedestal'),
|
||||
'Hyrule Castle - Boomerang Chest': (0xe974, False, 'in Hyrule Castle'),
|
||||
'Hyrule Castle - Map Chest': (0xeb0c, False, 'in Hyrule Castle'),
|
||||
"Hyrule Castle - Zelda's Chest": (0xeb09, False, 'in Hyrule Castle'),
|
||||
'Sewers - Dark Cross': (0xe96e, False, 'in the sewers'),
|
||||
'Sewers - Secret Room - Left': (0xeb5d, False, 'in the sewers'),
|
||||
'Sewers - Secret Room - Middle': (0xeb60, False, 'in the sewers'),
|
||||
'Sewers - Secret Room - Right': (0xeb63, False, 'in the sewers'),
|
||||
'Sanctuary': (0xea79, False, 'in Sanctuary'),
|
||||
'Castle Tower - Room 03': (0xeab5, False, 'in Castle Tower'),
|
||||
'Castle Tower - Dark Maze': (0xeab2, False, 'in Castle Tower'),
|
||||
'Old Man': (0xf69fa, False, 'with the old man'),
|
||||
'Spectacle Rock Cave': (0x180002, False, 'alone in a cave'),
|
||||
'Paradox Cave Lower - Far Left': (0xeb2a, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Left': (0xeb2d, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Right': (0xeb30, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Far Right': (0xeb33, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Middle': (0xeb36, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Upper - Left': (0xeb39, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Upper - Right': (0xeb3c, False, 'in a cave with seven chests'),
|
||||
'Spiral Cave': (0xe9bf, False, 'in spiral cave'),
|
||||
'Ether Tablet': (0x180016, False, 'at a monolith'),
|
||||
'Spectacle Rock': (0x180140, False, 'atop a rock'),
|
||||
'Tower of Hera - Basement Cage': (0x180162, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Map Chest': (0xe9ad, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Big Key Chest': (0xe9e6, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Compass Chest': (0xe9fb, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Big Chest': (0xe9f8, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Boss': (0x180152, False, 'with Moldorm'),
|
||||
'Pyramid': (0x180147, False, 'on the pyramid'),
|
||||
'Catfish': (0xee185, False, 'with a catfish'),
|
||||
'Stumpy': (0x330c7, False, 'with tree boy'),
|
||||
'Digging Game': (0x180148, False, 'underground'),
|
||||
'Bombos Tablet': (0x180017, False, 'at a monolith'),
|
||||
'Hype Cave - Top': (0xeb1e, False, 'near a bat-like man'),
|
||||
'Hype Cave - Middle Right': (0xeb21, False, 'near a bat-like man'),
|
||||
'Hype Cave - Middle Left': (0xeb24, False, 'near a bat-like man'),
|
||||
'Hype Cave - Bottom': (0xeb27, False, 'near a bat-like man'),
|
||||
'Hype Cave - Generous Guy': (0x180011, False, 'with a bat-like man'),
|
||||
'Peg Cave': (0x180006, False, 'alone in a cave'),
|
||||
'Pyramid Fairy - Left': (0xe980, False, 'near a fairy'),
|
||||
'Pyramid Fairy - Right': (0xe983, False, 'near a fairy'),
|
||||
'Brewery': (0xe9ec, False, 'alone in a home'),
|
||||
'C-Shaped House': (0xe9ef, False, 'alone in a home'),
|
||||
'Chest Game': (0xeda8, False, 'as a prize'),
|
||||
'Bumper Cave Ledge': (0x180146, False, 'on a ledge'),
|
||||
'Mire Shed - Left': (0xea73, False, 'near sparks'),
|
||||
'Mire Shed - Right': (0xea76, False, 'near sparks'),
|
||||
'Superbunny Cave - Top': (0xea7c, False, 'in a connection'),
|
||||
'Superbunny Cave - Bottom': (0xea7f, False, 'in a connection'),
|
||||
'Spike Cave': (0xea8b, False, 'beyond spikes'),
|
||||
'Hookshot Cave - Top Right': (0xeb51, False, 'across pits'),
|
||||
'Hookshot Cave - Top Left': (0xeb54, False, 'across pits'),
|
||||
'Hookshot Cave - Bottom Right': (0xeb5a, False, 'across pits'),
|
||||
'Hookshot Cave - Bottom Left': (0xeb57, False, 'across pits'),
|
||||
'Floating Island': (0x180141, False, 'on an island'),
|
||||
'Mimic Cave': (0xe9c5, False, 'in a cave of mimicry'),
|
||||
'Swamp Palace - Entrance': (0xea9d, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Map Chest': (0xe986, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Big Chest': (0xe989, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Compass Chest': (0xeaa0, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Big Key Chest': (0xeaa6, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - West Chest': (0xeaa3, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Flooded Room - Left': (0xeaa9, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Flooded Room - Right': (0xeaac, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Waterfall Room': (0xeaaf, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Boss': (0x180154, False, 'with Arrghus'),
|
||||
"Thieves' Town - Big Key Chest": (0xea04, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Map Chest": (0xea01, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Compass Chest": (0xea07, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Ambush Chest": (0xea0a, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Attic": (0xea0d, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Big Chest": (0xea10, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Blind's Cell": (0xea13, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Boss": (0x180156, False, 'with Blind'),
|
||||
'Skull Woods - Compass Chest': (0xe992, False, 'in Skull Woods'),
|
||||
'Skull Woods - Map Chest': (0xe99b, False, 'in Skull Woods'),
|
||||
'Skull Woods - Big Chest': (0xe998, False, 'in Skull Woods'),
|
||||
'Skull Woods - Pot Prison': (0xe9a1, False, 'in Skull Woods'),
|
||||
'Skull Woods - Pinball Room': (0xe9c8, False, 'in Skull Woods'),
|
||||
'Skull Woods - Big Key Chest': (0xe99e, False, 'in Skull Woods'),
|
||||
'Skull Woods - Bridge Room': (0xe9fe, False, 'near Mothula'),
|
||||
'Skull Woods - Boss': (0x180155, False, 'with Mothula'),
|
||||
'Ice Palace - Compass Chest': (0xe9d4, False, 'in Ice Palace'),
|
||||
'Ice Palace - Freezor Chest': (0xe995, False, 'in Ice Palace'),
|
||||
'Ice Palace - Big Chest': (0xe9aa, False, 'in Ice Palace'),
|
||||
'Ice Palace - Iced T Room': (0xe9e3, False, 'in Ice Palace'),
|
||||
'Ice Palace - Spike Room': (0xe9e0, False, 'in Ice Palace'),
|
||||
'Ice Palace - Big Key Chest': (0xe9a4, False, 'in Ice Palace'),
|
||||
'Ice Palace - Map Chest': (0xe9dd, False, 'in Ice Palace'),
|
||||
'Ice Palace - Boss': (0x180157, False, 'with Kholdstare'),
|
||||
'Misery Mire - Big Chest': (0xea67, False, 'in Misery Mire'),
|
||||
'Misery Mire - Map Chest': (0xea6a, False, 'in Misery Mire'),
|
||||
'Misery Mire - Main Lobby': (0xea5e, False, 'in Misery Mire'),
|
||||
'Misery Mire - Bridge Chest': (0xea61, False, 'in Misery Mire'),
|
||||
'Misery Mire - Spike Chest': (0xe9da, False, 'in Misery Mire'),
|
||||
'Misery Mire - Compass Chest': (0xea64, False, 'in Misery Mire'),
|
||||
'Misery Mire - Big Key Chest': (0xea6d, False, 'in Misery Mire'),
|
||||
'Misery Mire - Boss': (0x180158, False, 'with Vitreous'),
|
||||
'Turtle Rock - Compass Chest': (0xea22, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Roller Room - Left': (0xea1c, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Roller Room - Right': (0xea1f, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Chain Chomps': (0xea16, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Big Key Chest': (0xea25, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Big Chest': (0xea19, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Crystaroller Room': (0xea34, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Bottom Left': (0xea31, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Bottom Right': (0xea2e, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Top Left': (0xea2b, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Top Right': (0xea28, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Boss': (0x180159, False, 'with Trinexx'),
|
||||
'Palace of Darkness - Shooter Room': (0xea5b, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - The Arena - Bridge': (0xea3d, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Stalfos Basement': (0xea49, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Big Key Chest': (0xea37, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - The Arena - Ledge': (0xea3a, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Map Chest': (0xea52, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Compass Chest': (0xea43, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Basement - Left': (0xea4c, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Basement - Right': (0xea4f, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Maze - Top': (0xea55, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Maze - Bottom': (0xea58, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Big Chest': (0xea40, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Harmless Hellway': (0xea46, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Boss': (0x180153, False, 'with Helmasaur King'),
|
||||
"Ganons Tower - Bob's Torch": (0x180161, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Hope Room - Left': (0xead9, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Hope Room - Right': (0xeadc, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Tile Room': (0xeae2, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Compass Room - Top Left': (0xeae5, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Compass Room - Top Right': (0xeae8, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Compass Room - Bottom Left': (0xeaeb, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Compass Room - Bottom Right': (0xeaee, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - DMs Room - Top Left': (0xeab8, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - DMs Room - Top Right': (0xeabb, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - DMs Room - Bottom Left': (0xeabe, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - DMs Room - Bottom Right': (0xeac1, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Map Chest': (0xead3, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Firesnake Room': (0xead0, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Randomizer Room - Top Left': (0xeac4, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Randomizer Room - Top Right': (0xeac7, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Randomizer Room - Bottom Left': (0xeaca, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Randomizer Room - Bottom Right': (0xeacd, False, "in Ganon's Tower"),
|
||||
"Ganons Tower - Bob's Chest": (0xeadf, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Big Chest': (0xead6, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Big Key Room - Left': (0xeaf4, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Big Key Room - Right': (0xeaf7, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Big Key Chest': (0xeaf1, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Mini Helmasaur Room - Left': (0xeafd, False, "atop Ganon's Tower"),
|
||||
'Ganons Tower - Mini Helmasaur Room - Right': (0xeb00, False, "atop Ganon's Tower"),
|
||||
'Ganons Tower - Pre-Moldorm Chest': (0xeb03, False, "atop Ganon's Tower"),
|
||||
'Ganons Tower - Validation Chest': (0xeb06, False, "atop Ganon's Tower"),
|
||||
'Ganon': (None, False, 'from me'),
|
||||
'Agahnim 1': (None, False, 'from Ganon\'s wizardry form'),
|
||||
'Agahnim 2': (None, False, 'from Ganon\'s wizardry form'),
|
||||
'Floodgate': (None, False, None),
|
||||
'Frog': (None, False, None),
|
||||
'Missing Smith': (None, False, None),
|
||||
'Dark Blacksmith Ruins': (None, False, None),
|
||||
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], True, 'Eastern Palace'),
|
||||
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], True, 'Desert Palace'),
|
||||
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], True, 'Tower of Hera'),
|
||||
'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], True, 'Palace of Darkness'),
|
||||
'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], True, 'Swamp Palace'),
|
||||
'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], True, 'Thieves\' Town'),
|
||||
'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], True, 'Skull Woods'),
|
||||
'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], True, 'Ice Palace'),
|
||||
'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], True, 'Misery Mire'),
|
||||
'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], True, 'Turtle Rock')}
|
||||
location_table = {'Mushroom': (0x180013, 0x186338, False, 'in the woods'),
|
||||
'Bottle Merchant': (0x2eb18, 0x186339, False, 'with a merchant'),
|
||||
'Flute Spot': (0x18014a, 0x18633d, False, 'underground'),
|
||||
'Sunken Treasure': (0x180145, 0x186354, False, 'underwater'),
|
||||
'Purple Chest': (0x33d68, 0x186359, False, 'from a box'),
|
||||
"Blind's Hideout - Top": (0xeb0f, 0x1862e3, False, 'in a basement'),
|
||||
"Blind's Hideout - Left": (0xeb12, 0x1862e6, False, 'in a basement'),
|
||||
"Blind's Hideout - Right": (0xeb15, 0x1862e9, False, 'in a basement'),
|
||||
"Blind's Hideout - Far Left": (0xeb18, 0x1862ec, False, 'in a basement'),
|
||||
"Blind's Hideout - Far Right": (0xeb1b, 0x1862ef, False, 'in a basement'),
|
||||
"Link's Uncle": (0x2df45, 0x18635f, False, 'with your uncle'),
|
||||
'Secret Passage': (0xe971, 0x186145, False, 'near your uncle'),
|
||||
'King Zora': (0xee1c3, 0x186360, False, 'at a high price'),
|
||||
"Zora's Ledge": (0x180149, 0x186358, False, 'near Zora'),
|
||||
'Waterfall Fairy - Left': (0xe9b0, 0x186184, False, 'near a fairy'),
|
||||
'Waterfall Fairy - Right': (0xe9d1, 0x1861a5, False, 'near a fairy'),
|
||||
"King's Tomb": (0xe97a, 0x18614e, False, 'alone in a cave'),
|
||||
'Floodgate Chest': (0xe98c, 0x186160, False, 'in the dam'),
|
||||
"Link's House": (0xe9bc, 0x186190, False, 'in your home'),
|
||||
'Kakariko Tavern': (0xe9ce, 0x1861a2, False, 'in the bar'),
|
||||
'Chicken House': (0xe9e9, 0x1861bd, False, 'near poultry'),
|
||||
"Aginah's Cave": (0xe9f2, 0x1861c6, False, 'with Aginah'),
|
||||
"Sahasrahla's Hut - Left": (0xea82, 0x186256, False, 'near the elder'),
|
||||
"Sahasrahla's Hut - Middle": (0xea85, 0x186259, False, 'near the elder'),
|
||||
"Sahasrahla's Hut - Right": (0xea88, 0x18625c, False, 'near the elder'),
|
||||
'Sahasrahla': (0x2f1fc, 0x186365, False, 'with the elder'),
|
||||
'Kakariko Well - Top': (0xea8e, 0x186262, False, 'in a well'),
|
||||
'Kakariko Well - Left': (0xea91, 0x186265, False, 'in a well'),
|
||||
'Kakariko Well - Middle': (0xea94, 0x186268, False, 'in a well'),
|
||||
'Kakariko Well - Right': (0xea97, 0x18626b, False, 'in a well'),
|
||||
'Kakariko Well - Bottom': (0xea9a, 0x18626e, False, 'in a well'),
|
||||
'Blacksmith': (0x18002a, 0x186366, False, 'with the smith'),
|
||||
'Magic Bat': (0x180015, 0x18635e, False, 'with the bat'),
|
||||
'Sick Kid': (0x339cf, 0x186367, False, 'with the sick'),
|
||||
'Hobo': (0x33e7d, 0x186368, False, 'with the hobo'),
|
||||
'Lost Woods Hideout': (0x180000, 0x186348, False, 'near a thief'),
|
||||
'Lumberjack Tree': (0x180001, 0x186349, False, 'in a hole'),
|
||||
'Cave 45': (0x180003, 0x18634b, False, 'alone in a cave'),
|
||||
'Graveyard Cave': (0x180004, 0x18634c, False, 'alone in a cave'),
|
||||
'Checkerboard Cave': (0x180005, 0x18634d, False, 'alone in a cave'),
|
||||
'Mini Moldorm Cave - Far Left': (0xeb42, 0x186316, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Left': (0xeb45, 0x186319, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Right': (0xeb48, 0x18631c, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Far Right': (0xeb4b, 0x18631f, False, 'near Moldorms'),
|
||||
'Mini Moldorm Cave - Generous Guy': (0x180010, 0x18635a, False, 'near Moldorms'),
|
||||
'Ice Rod Cave': (0xeb4e, 0x186322, False, 'in a frozen cave'),
|
||||
'Bonk Rock Cave': (0xeb3f, 0x186313, False, 'alone in a cave'),
|
||||
'Library': (0x180012, 0x18635c, False, 'near books'),
|
||||
'Potion Shop': (0x180014, 0x18635d, False, 'near potions'),
|
||||
'Lake Hylia Island': (0x180144, 0x186353, False, 'on an island'),
|
||||
'Maze Race': (0x180142, 0x186351, False, 'at the race'),
|
||||
'Desert Ledge': (0x180143, 0x186352, False, 'in the desert'),
|
||||
'Desert Palace - Big Chest': (0xe98f, 0x186163, False, 'in Desert Palace'),
|
||||
'Desert Palace - Torch': (0x180160, 0x186362, False, 'in Desert Palace'),
|
||||
'Desert Palace - Map Chest': (0xe9b6, 0x18618a, False, 'in Desert Palace'),
|
||||
'Desert Palace - Compass Chest': (0xe9cb, 0x18619f, False, 'in Desert Palace'),
|
||||
'Desert Palace - Big Key Chest': (0xe9c2, 0x186196, False, 'in Desert Palace'),
|
||||
'Desert Palace - Boss': (0x180151, 0x18633f, False, 'with Lanmolas'),
|
||||
'Eastern Palace - Compass Chest': (0xe977, 0x18614b, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Big Chest': (0xe97d, 0x186151, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Cannonball Chest': (0xe9b3, 0x186187, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Big Key Chest': (0xe9b9, 0x18618d, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Map Chest': (0xe9f5, 0x1861c9, False, 'in Eastern Palace'),
|
||||
'Eastern Palace - Boss': (0x180150, 0x18633e, False, 'with the Armos'),
|
||||
'Master Sword Pedestal': (0x289b0, 0x186369, False, 'at the pedestal'),
|
||||
'Hyrule Castle - Boomerang Chest': (0xe974, 0x186148, False, 'in Hyrule Castle'),
|
||||
'Hyrule Castle - Map Chest': (0xeb0c, 0x1862e0, False, 'in Hyrule Castle'),
|
||||
"Hyrule Castle - Zelda's Chest": (0xeb09, 0x1862dd, False, 'in Hyrule Castle'),
|
||||
'Sewers - Dark Cross': (0xe96e, 0x186142, False, 'in the sewers'),
|
||||
'Sewers - Secret Room - Left': (0xeb5d, 0x186331, False, 'in the sewers'),
|
||||
'Sewers - Secret Room - Middle': (0xeb60, 0x186334, False, 'in the sewers'),
|
||||
'Sewers - Secret Room - Right': (0xeb63, 0x186337, False, 'in the sewers'),
|
||||
'Sanctuary': (0xea79, 0x18624d, False, 'in Sanctuary'),
|
||||
'Castle Tower - Room 03': (0xeab5, 0x186289, False, 'in Castle Tower'),
|
||||
'Castle Tower - Dark Maze': (0xeab2, 0x186286, False, 'in Castle Tower'),
|
||||
'Old Man': (0xf69fa, 0x186364, False, 'with the old man'),
|
||||
'Spectacle Rock Cave': (0x180002, 0x18634a, False, 'alone in a cave'),
|
||||
'Paradox Cave Lower - Far Left': (0xeb2a, 0x1862fe, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Left': (0xeb2d, 0x186301, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Right': (0xeb30, 0x186304, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Far Right': (0xeb33, 0x186307, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Lower - Middle': (0xeb36, 0x18630a, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Upper - Left': (0xeb39, 0x18630d, False, 'in a cave with seven chests'),
|
||||
'Paradox Cave Upper - Right': (0xeb3c, 0x186310, False, 'in a cave with seven chests'),
|
||||
'Spiral Cave': (0xe9bf, 0x186193, False, 'in spiral cave'),
|
||||
'Ether Tablet': (0x180016, 0x18633b, False, 'at a monolith'),
|
||||
'Spectacle Rock': (0x180140, 0x18634f, False, 'atop a rock'),
|
||||
'Tower of Hera - Basement Cage': (0x180162, 0x18633a, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Map Chest': (0xe9ad, 0x186181, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Big Key Chest': (0xe9e6, 0x1861ba, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Compass Chest': (0xe9fb, 0x1861cf, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Big Chest': (0xe9f8, 0x1861cc, False, 'in Tower of Hera'),
|
||||
'Tower of Hera - Boss': (0x180152, 0x186340, False, 'with Moldorm'),
|
||||
'Pyramid': (0x180147, 0x186356, False, 'on the pyramid'),
|
||||
'Catfish': (0xee185, 0x186361, False, 'with a catfish'),
|
||||
'Stumpy': (0x330c7, 0x18636a, False, 'with tree boy'),
|
||||
'Digging Game': (0x180148, 0x186357, False, 'underground'),
|
||||
'Bombos Tablet': (0x180017, 0x18633c, False, 'at a monolith'),
|
||||
'Hype Cave - Top': (0xeb1e, 0x1862f2, False, 'near a bat-like man'),
|
||||
'Hype Cave - Middle Right': (0xeb21, 0x1862f5, False, 'near a bat-like man'),
|
||||
'Hype Cave - Middle Left': (0xeb24, 0x1862f8, False, 'near a bat-like man'),
|
||||
'Hype Cave - Bottom': (0xeb27, 0x1862fb, False, 'near a bat-like man'),
|
||||
'Hype Cave - Generous Guy': (0x180011, 0x18635b, False, 'with a bat-like man'),
|
||||
'Peg Cave': (0x180006, 0x18634e, False, 'alone in a cave'),
|
||||
'Pyramid Fairy - Left': (0xe980, 0x186154, False, 'near a fairy'),
|
||||
'Pyramid Fairy - Right': (0xe983, 0x186157, False, 'near a fairy'),
|
||||
'Brewery': (0xe9ec, 0x1861c0, False, 'alone in a home'),
|
||||
'C-Shaped House': (0xe9ef, 0x1861c3, False, 'alone in a home'),
|
||||
'Chest Game': (0xeda8, 0x18636b, False, 'as a prize'),
|
||||
'Bumper Cave Ledge': (0x180146, 0x186355, False, 'on a ledge'),
|
||||
'Mire Shed - Left': (0xea73, 0x186247, False, 'near sparks'),
|
||||
'Mire Shed - Right': (0xea76, 0x18624a, False, 'near sparks'),
|
||||
'Superbunny Cave - Top': (0xea7c, 0x186250, False, 'in a connection'),
|
||||
'Superbunny Cave - Bottom': (0xea7f, 0x186253, False, 'in a connection'),
|
||||
'Spike Cave': (0xea8b, 0x18625f, False, 'beyond spikes'),
|
||||
'Hookshot Cave - Top Right': (0xeb51, 0x186325, False, 'across pits'),
|
||||
'Hookshot Cave - Top Left': (0xeb54, 0x186328, False, 'across pits'),
|
||||
'Hookshot Cave - Bottom Right': (0xeb5a, 0x18632e, False, 'across pits'),
|
||||
'Hookshot Cave - Bottom Left': (0xeb57, 0x18632b, False, 'across pits'),
|
||||
'Floating Island': (0x180141, 0x186350, False, 'on an island'),
|
||||
'Mimic Cave': (0xe9c5, 0x186199, False, 'in a cave of mimicry'),
|
||||
'Swamp Palace - Entrance': (0xea9d, 0x186271, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Map Chest': (0xe986, 0x18615a, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Big Chest': (0xe989, 0x18615d, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Compass Chest': (0xeaa0, 0x186274, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Big Key Chest': (0xeaa6, 0x18627a, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - West Chest': (0xeaa3, 0x186277, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Flooded Room - Left': (0xeaa9, 0x18627d, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Flooded Room - Right': (0xeaac, 0x186280, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Waterfall Room': (0xeaaf, 0x186283, False, 'in Swamp Palace'),
|
||||
'Swamp Palace - Boss': (0x180154, 0x186342, False, 'with Arrghus'),
|
||||
"Thieves' Town - Big Key Chest": (0xea04, 0x1861d8, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Map Chest": (0xea01, 0x1861d5, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Compass Chest": (0xea07, 0x1861db, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Ambush Chest": (0xea0a, 0x1861de, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Attic": (0xea0d, 0x1861e1, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Big Chest": (0xea10, 0x1861e4, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Blind's Cell": (0xea13, 0x1861e7, False, "in Thieves' Town"),
|
||||
"Thieves' Town - Boss": (0x180156, 0x186344, False, 'with Blind'),
|
||||
'Skull Woods - Compass Chest': (0xe992, 0x186166, False, 'in Skull Woods'),
|
||||
'Skull Woods - Map Chest': (0xe99b, 0x18616f, False, 'in Skull Woods'),
|
||||
'Skull Woods - Big Chest': (0xe998, 0x18616c, False, 'in Skull Woods'),
|
||||
'Skull Woods - Pot Prison': (0xe9a1, 0x186175, False, 'in Skull Woods'),
|
||||
'Skull Woods - Pinball Room': (0xe9c8, 0x18619c, False, 'in Skull Woods'),
|
||||
'Skull Woods - Big Key Chest': (0xe99e, 0x186172, False, 'in Skull Woods'),
|
||||
'Skull Woods - Bridge Room': (0xe9fe, 0x1861d2, False, 'near Mothula'),
|
||||
'Skull Woods - Boss': (0x180155, 0x186343, False, 'with Mothula'),
|
||||
'Ice Palace - Compass Chest': (0xe9d4, 0x1861a8, False, 'in Ice Palace'),
|
||||
'Ice Palace - Freezor Chest': (0xe995, 0x186169, False, 'in Ice Palace'),
|
||||
'Ice Palace - Big Chest': (0xe9aa, 0x18617e, False, 'in Ice Palace'),
|
||||
'Ice Palace - Iced T Room': (0xe9e3, 0x1861b7, False, 'in Ice Palace'),
|
||||
'Ice Palace - Spike Room': (0xe9e0, 0x1861b4, False, 'in Ice Palace'),
|
||||
'Ice Palace - Big Key Chest': (0xe9a4, 0x186178, False, 'in Ice Palace'),
|
||||
'Ice Palace - Map Chest': (0xe9dd, 0x1861b1, False, 'in Ice Palace'),
|
||||
'Ice Palace - Boss': (0x180157, 0x186345, False, 'with Kholdstare'),
|
||||
'Misery Mire - Big Chest': (0xea67, 0x18623b, False, 'in Misery Mire'),
|
||||
'Misery Mire - Map Chest': (0xea6a, 0x18623e, False, 'in Misery Mire'),
|
||||
'Misery Mire - Main Lobby': (0xea5e, 0x186232, False, 'in Misery Mire'),
|
||||
'Misery Mire - Bridge Chest': (0xea61, 0x186235, False, 'in Misery Mire'),
|
||||
'Misery Mire - Spike Chest': (0xe9da, 0x1861ae, False, 'in Misery Mire'),
|
||||
'Misery Mire - Compass Chest': (0xea64, 0x186238, False, 'in Misery Mire'),
|
||||
'Misery Mire - Big Key Chest': (0xea6d, 0x186241, False, 'in Misery Mire'),
|
||||
'Misery Mire - Boss': (0x180158, 0x186346, False, 'with Vitreous'),
|
||||
'Turtle Rock - Compass Chest': (0xea22, 0x1861f6, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Roller Room - Left': (0xea1c, 0x1861f0, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Roller Room - Right': (0xea1f, 0x1861f3, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Chain Chomps': (0xea16, 0x1861ea, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Big Key Chest': (0xea25, 0x1861f9, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Big Chest': (0xea19, 0x1861ed, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Crystaroller Room': (0xea34, 0x186208, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Bottom Left': (0xea31, 0x186205, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Bottom Right': (0xea2e, 0x186202, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Top Left': (0xea2b, 0x1861ff, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Eye Bridge - Top Right': (0xea28, 0x1861fc, False, 'in Turtle Rock'),
|
||||
'Turtle Rock - Boss': (0x180159, 0x186347, False, 'with Trinexx'),
|
||||
'Palace of Darkness - Shooter Room': (0xea5b, 0x18622f, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - The Arena - Bridge': (0xea3d, 0x186211, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Stalfos Basement': (0xea49, 0x18621d, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Big Key Chest': (0xea37, 0x18620b, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - The Arena - Ledge': (0xea3a, 0x18620e, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Map Chest': (0xea52, 0x186226, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Compass Chest': (0xea43, 0x186217, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Basement - Left': (0xea4c, 0x186220, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Basement - Right': (0xea4f, 0x186223, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Maze - Top': (0xea55, 0x186229, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Dark Maze - Bottom': (0xea58, 0x18622c, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Big Chest': (0xea40, 0x186214, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Harmless Hellway': (0xea46, 0x18621a, False, 'in Palace of Darkness'),
|
||||
'Palace of Darkness - Boss': (0x180153, 0x186341, False, 'with Helmasaur King'),
|
||||
"Ganons Tower - Bob's Torch": (0x180161, 0x186363, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Hope Room - Left': (0xead9, 0x1862ad, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Hope Room - Right': (0xeadc, 0x1862b0, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Tile Room': (0xeae2, 0x1862b6, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Compass Room - Top Left': (0xeae5, 0x1862b9, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Compass Room - Top Right': (0xeae8, 0x1862bc, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Compass Room - Bottom Left': (0xeaeb, 0x1862bf, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Compass Room - Bottom Right': (0xeaee, 0x1862c2, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - DMs Room - Top Left': (0xeab8, 0x18628c, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - DMs Room - Top Right': (0xeabb, 0x18628f, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - DMs Room - Bottom Left': (0xeabe, 0x186292, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - DMs Room - Bottom Right': (0xeac1, 0x186295, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Map Chest': (0xead3, 0x1862a7, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Firesnake Room': (0xead0, 0x1862a4, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Randomizer Room - Top Left': (0xeac4, 0x186298, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Randomizer Room - Top Right': (0xeac7, 0x18629b, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Randomizer Room - Bottom Left': (0xeaca, 0x18629e, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Randomizer Room - Bottom Right': (0xeacd, 0x1862a1, False, "in Ganon's Tower"),
|
||||
"Ganons Tower - Bob's Chest": (0xeadf, 0x1862b3, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Big Chest': (0xead6, 0x1862aa, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Big Key Room - Left': (0xeaf4, 0x1862c8, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Big Key Room - Right': (0xeaf7, 0x1862cb, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Big Key Chest': (0xeaf1, 0x1862c5, False, "in Ganon's Tower"),
|
||||
'Ganons Tower - Mini Helmasaur Room - Left': (0xeafd, 0x1862d1, False, "atop Ganon's Tower"),
|
||||
'Ganons Tower - Mini Helmasaur Room - Right': (0xeb00, 0x1862d4, False, "atop Ganon's Tower"),
|
||||
'Ganons Tower - Pre-Moldorm Chest': (0xeb03, 0x1862d7, False, "atop Ganon's Tower"),
|
||||
'Ganons Tower - Validation Chest': (0xeb06, 0x1862da, False, "atop Ganon's Tower"),
|
||||
'Ganon': (None, None, False, 'from me'),
|
||||
'Agahnim 1': (None, None, False, 'from Ganon\'s wizardry form'),
|
||||
'Agahnim 2': (None, None, False, 'from Ganon\'s wizardry form'),
|
||||
'Floodgate': (None, None, False, None),
|
||||
'Frog': (None, None, False, None),
|
||||
'Missing Smith': (None, None, False, None),
|
||||
'Dark Blacksmith Ruins': (None, None, False, None),
|
||||
'Eastern Palace - Prize': ([0x1209D, 0x53EF8, 0x53EF9, 0x180052, 0x18007C, 0xC6FE], None, True, 'Eastern Palace'),
|
||||
'Desert Palace - Prize': ([0x1209E, 0x53F1C, 0x53F1D, 0x180053, 0x180078, 0xC6FF], None, True, 'Desert Palace'),
|
||||
'Tower of Hera - Prize': ([0x120A5, 0x53F0A, 0x53F0B, 0x18005A, 0x18007A, 0xC706], None, True, 'Tower of Hera'),
|
||||
'Palace of Darkness - Prize': ([0x120A1, 0x53F00, 0x53F01, 0x180056, 0x18007D, 0xC702], None, True, 'Palace of Darkness'),
|
||||
'Swamp Palace - Prize': ([0x120A0, 0x53F6C, 0x53F6D, 0x180055, 0x180071, 0xC701], None, True, 'Swamp Palace'),
|
||||
'Thieves\' Town - Prize': ([0x120A6, 0x53F36, 0x53F37, 0x18005B, 0x180077, 0xC707], None, True, 'Thieves\' Town'),
|
||||
'Skull Woods - Prize': ([0x120A3, 0x53F12, 0x53F13, 0x180058, 0x18007B, 0xC704], None, True, 'Skull Woods'),
|
||||
'Ice Palace - Prize': ([0x120A4, 0x53F5A, 0x53F5B, 0x180059, 0x180073, 0xC705], None, True, 'Ice Palace'),
|
||||
'Misery Mire - Prize': ([0x120A2, 0x53F48, 0x53F49, 0x180057, 0x180075, 0xC703], None, True, 'Misery Mire'),
|
||||
'Turtle Rock - Prize': ([0x120A7, 0x53F24, 0x53F25, 0x18005C, 0x180079, 0xC708], None, True, 'Turtle Rock')}
|
||||
|
|
410
Rom.py
410
Rom.py
|
@ -18,7 +18,7 @@ from EntranceShuffle import door_addresses
|
|||
|
||||
|
||||
JAP10HASH = '03a63945398191337e896e5771f77173'
|
||||
RANDOMIZERBASEHASH = '1907d4caccffe60fc69940cfa11b2dab'
|
||||
# RANDOMIZERBASEHASH = '1907d4caccffe60fc69940cfa11b2dab'
|
||||
|
||||
|
||||
class JsonRom(object):
|
||||
|
@ -35,16 +35,6 @@ class JsonRom(object):
|
|||
return
|
||||
self.patches[str(startaddress)] = list(values)
|
||||
|
||||
def write_int16(self, address, value):
|
||||
self.write_bytes(address, int16_as_bytes(value))
|
||||
|
||||
def write_int16s(self, startaddress, values):
|
||||
byte_list = [int16_as_bytes(value) for value in values]
|
||||
self.patches[str(startaddress)] = [byte for bytes in byte_list for byte in bytes]
|
||||
|
||||
def write_int32(self, address, value):
|
||||
self.write_bytes(address, int32_as_bytes(value))
|
||||
|
||||
def write_to_file(self, file):
|
||||
with open(file, 'w') as stream:
|
||||
json.dump([self.patches], stream)
|
||||
|
@ -54,8 +44,6 @@ class JsonRom(object):
|
|||
h.update(json.dumps([self.patches]).encode('utf-8'))
|
||||
return h.hexdigest()
|
||||
|
||||
|
||||
|
||||
class LocalRom(object):
|
||||
|
||||
def __init__(self, file, patch=True):
|
||||
|
@ -72,20 +60,6 @@ class LocalRom(object):
|
|||
for i, value in enumerate(values):
|
||||
self.write_byte(startaddress + i, value)
|
||||
|
||||
def write_int16(self, address, value):
|
||||
self.write_bytes(address, int16_as_bytes(value))
|
||||
|
||||
def write_int16s(self, startaddress, values):
|
||||
for i, value in enumerate(values):
|
||||
self.write_int16(startaddress + (i * 2), value)
|
||||
|
||||
def write_int32(self, address, value):
|
||||
self.write_bytes(address, int32_as_bytes(value))
|
||||
|
||||
def write_int32s(self, startaddress, values):
|
||||
for i, value in enumerate(values):
|
||||
self.write_int32(startaddress + (i * 2), value)
|
||||
|
||||
def write_to_file(self, file):
|
||||
with open(file, 'wb') as outfile:
|
||||
outfile.write(self.buffer)
|
||||
|
@ -109,10 +83,10 @@ class LocalRom(object):
|
|||
self.write_bytes(int(baseaddress), values)
|
||||
|
||||
# verify md5
|
||||
patchedmd5 = hashlib.md5()
|
||||
patchedmd5.update(self.buffer)
|
||||
if RANDOMIZERBASEHASH != patchedmd5.hexdigest():
|
||||
raise RuntimeError('Provided Base Rom unsuitable for patching. Please provide a JAP(1.0) "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc" rom to use as a base.')
|
||||
# patchedmd5 = hashlib.md5()
|
||||
# patchedmd5.update(self.buffer)
|
||||
# if RANDOMIZERBASEHASH != patchedmd5.hexdigest():
|
||||
# raise RuntimeError('Provided Base Rom unsuitable for patching. Please provide a JAP(1.0) "Zelda no Densetsu - Kamigami no Triforce (Japan).sfc" rom to use as a base.')
|
||||
|
||||
def patch_enemizer(self, rando_patch, base_enemizer_patch_path, enemizer_patch):
|
||||
# extend to 4MB
|
||||
|
@ -142,6 +116,20 @@ class LocalRom(object):
|
|||
h.update(self.buffer)
|
||||
return h.hexdigest()
|
||||
|
||||
def write_int16(rom, address, value):
|
||||
rom.write_bytes(address, int16_as_bytes(value))
|
||||
|
||||
def write_int32(rom, address, value):
|
||||
rom.write_bytes(address, int32_as_bytes(value))
|
||||
|
||||
def write_int16s(rom, startaddress, values):
|
||||
for i, value in enumerate(values):
|
||||
write_int16(rom, startaddress + (i * 2), value)
|
||||
|
||||
def write_int32s(rom, startaddress, values):
|
||||
for i, value in enumerate(values):
|
||||
write_int32(rom, startaddress + (i * 4), value)
|
||||
|
||||
def read_rom(stream):
|
||||
"Reads rom into bytearray and strips off any smc header"
|
||||
buffer = bytearray(stream.read())
|
||||
|
@ -461,6 +449,11 @@ def patch_rom(world, player, rom):
|
|||
itemid = 0x32
|
||||
if location.item.type == "SmallKey":
|
||||
itemid = 0x24
|
||||
if location.item and location.item.player != player:
|
||||
if location.player_address is not None:
|
||||
rom.write_byte(location.player_address, location.item.player)
|
||||
else:
|
||||
itemid = 0x5A
|
||||
rom.write_byte(location.address, itemid)
|
||||
else:
|
||||
# crystals
|
||||
|
@ -490,9 +483,9 @@ def patch_rom(world, player, rom):
|
|||
|
||||
|
||||
rom.write_byte(0x15B8C + offset, ow_area)
|
||||
rom.write_int16(0x15BDB + 2 * offset, vram_loc)
|
||||
rom.write_int16(0x15C79 + 2 * offset, scroll_y)
|
||||
rom.write_int16(0x15D17 + 2 * offset, scroll_x)
|
||||
write_int16(rom, 0x15BDB + 2 * offset, vram_loc)
|
||||
write_int16(rom, 0x15C79 + 2 * offset, scroll_y)
|
||||
write_int16(rom, 0x15D17 + 2 * offset, scroll_x)
|
||||
|
||||
# for positioning fixups we abuse the roomid as a way of identifying which exit data we are appling
|
||||
# Thanks to Zarby89 for originally finding these values
|
||||
|
@ -503,25 +496,25 @@ def patch_rom(world, player, rom):
|
|||
'Palace of Darkness Exit', 'Swamp Palace Exit', 'Ganons Tower Exit', 'Desert Palace Exit (North)', 'Agahnims Tower Exit', 'Spiral Cave Exit (Top)',
|
||||
'Superbunny Cave Exit (Bottom)', 'Turtle Rock Ledge Exit (East)']:
|
||||
# For exits that connot be reached from another, no need to apply offset fixes.
|
||||
rom.write_int16(0x15DB5 + 2 * offset, link_y) # same as final else
|
||||
write_int16(rom, 0x15DB5 + 2 * offset, link_y) # same as final else
|
||||
elif room_id == 0x0059 and world.fix_skullwoods_exit:
|
||||
rom.write_int16(0x15DB5 + 2 * offset, 0x00F8)
|
||||
write_int16(rom, 0x15DB5 + 2 * offset, 0x00F8)
|
||||
elif room_id == 0x004a and world.fix_palaceofdarkness_exit:
|
||||
rom.write_int16(0x15DB5 + 2 * offset, 0x0640)
|
||||
write_int16(rom, 0x15DB5 + 2 * offset, 0x0640)
|
||||
elif room_id == 0x00d6 and world.fix_trock_exit:
|
||||
rom.write_int16(0x15DB5 + 2 * offset, 0x0134)
|
||||
write_int16(rom, 0x15DB5 + 2 * offset, 0x0134)
|
||||
elif room_id == 0x000c and world.fix_gtower_exit: # fix ganons tower exit point
|
||||
rom.write_int16(0x15DB5 + 2 * offset, 0x00A4)
|
||||
write_int16(rom, 0x15DB5 + 2 * offset, 0x00A4)
|
||||
else:
|
||||
rom.write_int16(0x15DB5 + 2 * offset, link_y)
|
||||
write_int16(rom, 0x15DB5 + 2 * offset, link_y)
|
||||
|
||||
rom.write_int16(0x15E53 + 2 * offset, link_x)
|
||||
rom.write_int16(0x15EF1 + 2 * offset, camera_y)
|
||||
rom.write_int16(0x15F8F + 2 * offset, camera_x)
|
||||
write_int16(rom, 0x15E53 + 2 * offset, link_x)
|
||||
write_int16(rom, 0x15EF1 + 2 * offset, camera_y)
|
||||
write_int16(rom, 0x15F8F + 2 * offset, camera_x)
|
||||
rom.write_byte(0x1602D + offset, unknown_1)
|
||||
rom.write_byte(0x1607C + offset, unknown_2)
|
||||
rom.write_int16(0x160CB + 2 * offset, door_1)
|
||||
rom.write_int16(0x16169 + 2 * offset, door_2)
|
||||
write_int16(rom, 0x160CB + 2 * offset, door_1)
|
||||
write_int16(rom, 0x16169 + 2 * offset, door_2)
|
||||
elif isinstance(exit.addresses, list):
|
||||
# is hole
|
||||
for address in exit.addresses:
|
||||
|
@ -605,7 +598,7 @@ def patch_rom(world, player, rom):
|
|||
rom.write_byte(0x34FD6, 0x80)
|
||||
overflow_replacement = GREEN_TWENTY_RUPEES
|
||||
# Rupoor negative value
|
||||
rom.write_int16(0x180036, world.rupoor_cost)
|
||||
write_int16(rom, 0x180036, world.rupoor_cost)
|
||||
# Set stun items
|
||||
rom.write_byte(0x180180, 0x02) # Hookshot only
|
||||
elif world.difficulty_adjustments == 'expert':
|
||||
|
@ -623,7 +616,7 @@ def patch_rom(world, player, rom):
|
|||
rom.write_byte(0x34FD6, 0x80)
|
||||
overflow_replacement = GREEN_TWENTY_RUPEES
|
||||
# Rupoor negative value
|
||||
rom.write_int16(0x180036, world.rupoor_cost)
|
||||
write_int16(rom, 0x180036, world.rupoor_cost)
|
||||
# Set stun items
|
||||
rom.write_byte(0x180180, 0x00) # Nothing
|
||||
else:
|
||||
|
@ -640,7 +633,7 @@ def patch_rom(world, player, rom):
|
|||
#Enable catching fairies
|
||||
rom.write_byte(0x34FD6, 0xF0)
|
||||
# Rupoor negative value
|
||||
rom.write_int16(0x180036, world.rupoor_cost)
|
||||
write_int16(rom, 0x180036, world.rupoor_cost)
|
||||
# Set stun items
|
||||
rom.write_byte(0x180180, 0x03) # All standard items
|
||||
#Set overflow items for progressive equipment
|
||||
|
@ -658,15 +651,35 @@ def patch_rom(world, player, rom):
|
|||
difficulty = world.difficulty_requirements
|
||||
|
||||
#Set overflow items for progressive equipment
|
||||
mw_sword_replacements = {0: overflow_replacement,
|
||||
1: item_table['Fighter Sword'][3],
|
||||
2: item_table['Master Sword'][3],
|
||||
3: item_table['Tempered Sword'][3],
|
||||
4: item_table['Golden Sword'][3]}
|
||||
mw_shield_replacements = {0: overflow_replacement,
|
||||
1: item_table['Blue Shield'][3],
|
||||
2: item_table['Red Shield'][3],
|
||||
3: item_table['Mirror Shield'][3]}
|
||||
mw_armor_replacements = {0: overflow_replacement,
|
||||
1: item_table['Blue Mail'][3],
|
||||
2: item_table['Red Mail'][3]}
|
||||
mw_bottle_replacements = {0: overflow_replacement,
|
||||
1: item_table['Blue Potion'][3],
|
||||
2: item_table['Blue Potion'][3],
|
||||
3: item_table['Blue Potion'][3],
|
||||
4: item_table['Blue Potion'][3]}
|
||||
mw_bow_replacements = {0: overflow_replacement,
|
||||
1: item_table['Bow'][3],
|
||||
2: item_table['Bow'][3]}
|
||||
rom.write_bytes(0x180090,
|
||||
[difficulty.progressive_sword_limit, overflow_replacement,
|
||||
difficulty.progressive_shield_limit, overflow_replacement,
|
||||
difficulty.progressive_armor_limit, overflow_replacement,
|
||||
difficulty.progressive_bottle_limit, overflow_replacement,
|
||||
difficulty.progressive_bow_limit, overflow_replacement])
|
||||
[difficulty.progressive_sword_limit, mw_sword_replacements[difficulty.progressive_sword_limit] if world.players > 1 else overflow_replacement,
|
||||
difficulty.progressive_shield_limit, mw_shield_replacements[difficulty.progressive_shield_limit] if world.players > 1 else overflow_replacement,
|
||||
difficulty.progressive_armor_limit, mw_armor_replacements[difficulty.progressive_armor_limit] if world.players > 1 else overflow_replacement,
|
||||
difficulty.progressive_bottle_limit, mw_bottle_replacements[difficulty.progressive_bottle_limit] if world.players > 1 else overflow_replacement,
|
||||
difficulty.progressive_bow_limit, mw_bow_replacements[difficulty.progressive_bow_limit] if world.players > 1 else overflow_replacement])
|
||||
|
||||
if difficulty.progressive_bow_limit < 2 and world.swords == 'swordless':
|
||||
rom.write_bytes(0x180098, [2, overflow_replacement])
|
||||
rom.write_bytes(0x180098, [2, mw_bow_replacements[difficulty.progressive_bow_limit] if world.players > 1 else overflow_replacement])
|
||||
rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon
|
||||
|
||||
# set up game internal RNG seed
|
||||
|
@ -802,37 +815,37 @@ def patch_rom(world, player, rom):
|
|||
ERtimeincrease = ERtimeincrease + 15
|
||||
if world.clock_mode == 'off':
|
||||
rom.write_bytes(0x180190, [0x00, 0x00, 0x00]) # turn off clock mode
|
||||
rom.write_int32(0x180200, 0) # red clock adjustment time (in frames, sint32)
|
||||
rom.write_int32(0x180204, 0) # blue clock adjustment time (in frames, sint32)
|
||||
rom.write_int32(0x180208, 0) # green clock adjustment time (in frames, sint32)
|
||||
rom.write_int32(0x18020C, 0) # starting time (in frames, sint32)
|
||||
write_int32(rom, 0x180200, 0) # red clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x180204, 0) # blue clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x180208, 0) # green clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x18020C, 0) # starting time (in frames, sint32)
|
||||
elif world.clock_mode == 'ohko':
|
||||
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
|
||||
rom.write_int32(0x180200, 0) # red clock adjustment time (in frames, sint32)
|
||||
rom.write_int32(0x180204, 0) # blue clock adjustment time (in frames, sint32)
|
||||
rom.write_int32(0x180208, 0) # green clock adjustment time (in frames, sint32)
|
||||
rom.write_int32(0x18020C, 0) # starting time (in frames, sint32)
|
||||
write_int32(rom, 0x180200, 0) # red clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x180204, 0) # blue clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x180208, 0) # green clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x18020C, 0) # starting time (in frames, sint32)
|
||||
elif world.clock_mode == 'countdown-ohko':
|
||||
rom.write_bytes(0x180190, [0x01, 0x02, 0x01]) # ohko timer with resetable timer functionality
|
||||
rom.write_int32(0x180200, -100 * 60 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
||||
rom.write_int32(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
||||
rom.write_int32(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x180200, -100 * 60 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
||||
if world.difficulty_adjustments == 'normal':
|
||||
rom.write_int32(0x18020C, (10 + ERtimeincrease) * 60 * 60) # starting time (in frames, sint32)
|
||||
write_int32(rom, 0x18020C, (10 + ERtimeincrease) * 60 * 60) # starting time (in frames, sint32)
|
||||
else:
|
||||
rom.write_int32(0x18020C, int((5 + ERtimeincrease / 2) * 60 * 60)) # starting time (in frames, sint32)
|
||||
write_int32(rom, 0x18020C, int((5 + ERtimeincrease / 2) * 60 * 60)) # starting time (in frames, sint32)
|
||||
if world.clock_mode == 'stopwatch':
|
||||
rom.write_bytes(0x180190, [0x02, 0x01, 0x00]) # set stopwatch mode
|
||||
rom.write_int32(0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
||||
rom.write_int32(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
||||
rom.write_int32(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
||||
rom.write_int32(0x18020C, 0) # starting time (in frames, sint32)
|
||||
write_int32(rom, 0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x18020C, 0) # starting time (in frames, sint32)
|
||||
if world.clock_mode == 'countdown':
|
||||
rom.write_bytes(0x180190, [0x01, 0x01, 0x00]) # set countdown, with no reset available
|
||||
rom.write_int32(0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
||||
rom.write_int32(0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
||||
rom.write_int32(0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
||||
rom.write_int32(0x18020C, (40 + ERtimeincrease) * 60 * 60) # starting time (in frames, sint32)
|
||||
write_int32(rom, 0x180200, -2 * 60 * 60) # red clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x180204, 2 * 60 * 60) # blue clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x180208, 4 * 60 * 60) # green clock adjustment time (in frames, sint32)
|
||||
write_int32(rom, 0x18020C, (40 + ERtimeincrease) * 60 * 60) # starting time (in frames, sint32)
|
||||
|
||||
# set up goals for treasure hunt
|
||||
rom.write_bytes(0x180165, [0x0E, 0x28] if world.treasure_hunt_icon == 'Triforce Piece' else [0x0D, 0x28])
|
||||
|
@ -945,8 +958,8 @@ def patch_rom(world, player, rom):
|
|||
return reveal_bytes.get(location.parent_region.dungeon.name, 0x0000)
|
||||
return 0x0000
|
||||
|
||||
rom.write_int16(0x18017A, get_reveal_bytes('Green Pendant') if world.keysanity else 0x0000) # Sahasrahla reveal
|
||||
rom.write_int16(0x18017C, get_reveal_bytes('Crystal 5')|get_reveal_bytes('Crystal 6') if world.keysanity else 0x0000) # Bomb Shop Reveal
|
||||
write_int16(rom, 0x18017A, get_reveal_bytes('Green Pendant') if world.keysanity else 0x0000) # Sahasrahla reveal
|
||||
write_int16(rom, 0x18017C, get_reveal_bytes('Crystal 5')|get_reveal_bytes('Crystal 6') if world.keysanity else 0x0000) # Bomb Shop Reveal
|
||||
|
||||
rom.write_byte(0x180172, 0x01 if world.retro else 0x00) # universal keys
|
||||
rom.write_byte(0x180175, 0x01 if world.retro else 0x00) # rupee bow
|
||||
|
@ -974,14 +987,14 @@ def patch_rom(world, player, rom):
|
|||
rom.write_bytes(0x6D313, [0x00, 0x00, 0xe4, 0xff, 0x08, 0x0E])
|
||||
|
||||
rom.write_byte(0x18004E, 0) # Escape Fill (nothing)
|
||||
rom.write_int16(0x180183, 300) # Escape fill rupee bow
|
||||
write_int16(rom, 0x180183, 300) # Escape fill rupee bow
|
||||
rom.write_bytes(0x180185, [0,0,0]) # Uncle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180188, [0,0,0]) # Zelda respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x18018B, [0,0,0]) # Mantle respawn refills (magic, bombs, arrows)
|
||||
if world.mode == 'standard':
|
||||
if uncle_location.item is not None and uncle_location.item.name in ['Bow', 'Progressive Bow']:
|
||||
rom.write_byte(0x18004E, 1) # Escape Fill (arrows)
|
||||
rom.write_int16(0x180183, 300) # Escape fill rupee bow
|
||||
write_int16(rom, 0x180183, 300) # Escape fill rupee bow
|
||||
rom.write_bytes(0x180185, [0,0,70]) # Uncle respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x180188, [0,0,10]) # Zelda respawn refills (magic, bombs, arrows)
|
||||
rom.write_bytes(0x18018B, [0,0,10]) # Mantle respawn refills (magic, bombs, arrows)
|
||||
|
@ -1024,9 +1037,8 @@ def patch_rom(world, player, rom):
|
|||
# set rom name
|
||||
# 21 bytes
|
||||
from Main import __version__
|
||||
rom.name = bytearray('ER_{0}_{1:09}\0'.format(__version__[0:7],world.seed), 'utf8')
|
||||
assert len(rom.name) <= 21
|
||||
rom.write_bytes(0x7FC0, rom.name)
|
||||
rom.name = bytearray('ER{0}_{1}_{2:09}\0'.format(__version__.split('-')[0].replace('.','')[0:3], player, world.seed), 'utf8')
|
||||
rom.write_bytes(0x7FC0, rom.name[0:21])
|
||||
|
||||
# Write title screen Code
|
||||
hashint = int(rom.get_hash(), 16)
|
||||
|
@ -1072,8 +1084,25 @@ def write_custom_shops(rom, world, player):
|
|||
rom.write_bytes(0x184900, items_data)
|
||||
|
||||
|
||||
def hud_format_text(text):
|
||||
output = bytes()
|
||||
for char in text.lower():
|
||||
if 'a' <= char <= 'z':
|
||||
output += bytes([0x5d + ord(char) - ord('a'), 0x29])
|
||||
elif '0' <= char <= '8':
|
||||
output += bytes([0x77 + ord(char) - ord('0'), 0x29])
|
||||
elif char == '9':
|
||||
output += b'\x4b\x29'
|
||||
elif char == ' ':
|
||||
output += b'\x7f\x00'
|
||||
else:
|
||||
output += b'\x2a\x29'
|
||||
while len(output) < 32:
|
||||
output += b'\x7f\x00'
|
||||
return output[:32]
|
||||
|
||||
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite):
|
||||
|
||||
def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, sprite, names = None):
|
||||
|
||||
# enable instant item menu
|
||||
if fastmenu == 'instant':
|
||||
|
@ -1132,6 +1161,11 @@ def apply_rom_settings(rom, beep, color, quickswap, fastmenu, disable_music, spr
|
|||
if sprite is not None:
|
||||
write_sprite(rom, sprite)
|
||||
|
||||
# set player names
|
||||
for player, name in names.items():
|
||||
if 0 < player <= 64:
|
||||
rom.write_bytes(0x185380 + ((player - 1) * 32), hud_format_text(name))
|
||||
|
||||
if isinstance(rom, LocalRom):
|
||||
rom.write_crc()
|
||||
|
||||
|
@ -1452,117 +1486,117 @@ def set_inverted_mode(world, rom):
|
|||
rom.write_byte(snes_to_pc(0x05AF79), 0xF0)
|
||||
rom.write_byte(snes_to_pc(0x0DB3C5), 0xC6)
|
||||
rom.write_byte(snes_to_pc(0x07A3F4), 0xF0) # duck
|
||||
rom.write_int16s(snes_to_pc(0x02E849), [0x0043, 0x0056, 0x0058, 0x006C, 0x006F, 0x0070, 0x007B, 0x007F, 0x001B]) # dw flute
|
||||
rom.write_int16(snes_to_pc(0x02E8D5), 0x07C8)
|
||||
rom.write_int16(snes_to_pc(0x02E8F7), 0x01F8)
|
||||
write_int16s(rom, snes_to_pc(0x02E849), [0x0043, 0x0056, 0x0058, 0x006C, 0x006F, 0x0070, 0x007B, 0x007F, 0x001B]) # dw flute
|
||||
write_int16(rom, snes_to_pc(0x02E8D5), 0x07C8)
|
||||
write_int16(rom, snes_to_pc(0x02E8F7), 0x01F8)
|
||||
rom.write_byte(snes_to_pc(0x08D40C), 0xD0) # morph proof
|
||||
# the following bytes should only be written in vanilla
|
||||
# or they'll overwrite the randomizer's shuffles
|
||||
if world.shuffle == 'vanilla':
|
||||
rom.write_byte(0xDBB73 + 0x23, 0x37) # switch AT and GT
|
||||
rom.write_byte(0xDBB73 + 0x36, 0x24)
|
||||
rom.write_int16(0x15AEE + 2*0x38, 0x00E0)
|
||||
rom.write_int16(0x15AEE + 2*0x25, 0x000C)
|
||||
write_int16(rom, 0x15AEE + 2*0x38, 0x00E0)
|
||||
write_int16(rom, 0x15AEE + 2*0x25, 0x000C)
|
||||
if world.shuffle in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
|
||||
rom.write_byte(0x15B8C, 0x6C)
|
||||
rom.write_byte(0xDBB73 + 0x00, 0x53) # switch bomb shop and links house
|
||||
rom.write_byte(0xDBB73 + 0x52, 0x01)
|
||||
rom.write_byte(0xDBB73 + 0x15, 0x06) # bumper and old man cave
|
||||
rom.write_int16(0x15AEE + 2*0x17, 0x00F0)
|
||||
write_int16(rom, 0x15AEE + 2*0x17, 0x00F0)
|
||||
rom.write_byte(0xDBB73 + 0x05, 0x16)
|
||||
rom.write_int16(0x15AEE + 2*0x07, 0x00FB)
|
||||
write_int16(rom, 0x15AEE + 2*0x07, 0x00FB)
|
||||
rom.write_byte(0xDBB73 + 0x2D, 0x17)
|
||||
rom.write_int16(0x15AEE + 2*0x2F, 0x00EB)
|
||||
write_int16(rom, 0x15AEE + 2*0x2F, 0x00EB)
|
||||
rom.write_byte(0xDBB73 + 0x06, 0x2E)
|
||||
rom.write_int16(0x15AEE + 2*0x08, 0x00E6)
|
||||
write_int16(rom, 0x15AEE + 2*0x08, 0x00E6)
|
||||
rom.write_byte(0xDBB73 + 0x16, 0x5E)
|
||||
rom.write_byte(0xDBB73 + 0x6F, 0x07) # DDM fairy to old man cave
|
||||
rom.write_int16(0x15AEE + 2*0x18, 0x00F1)
|
||||
write_int16(rom, 0x15AEE + 2*0x18, 0x00F1)
|
||||
rom.write_byte(0x15B8C + 0x18, 0x43)
|
||||
rom.write_int16(0x15BDB + 2 * 0x18, 0x1400)
|
||||
rom.write_int16(0x15C79 + 2 * 0x18, 0x0294)
|
||||
rom.write_int16(0x15D17 + 2 * 0x18, 0x0600)
|
||||
rom.write_int16(0x15DB5 + 2 * 0x18, 0x02E8)
|
||||
rom.write_int16(0x15E53 + 2 * 0x18, 0x0678)
|
||||
rom.write_int16(0x15EF1 + 2 * 0x18, 0x0303)
|
||||
rom.write_int16(0x15F8F + 2 * 0x18, 0x0685)
|
||||
write_int16(rom, 0x15BDB + 2 * 0x18, 0x1400)
|
||||
write_int16(rom, 0x15C79 + 2 * 0x18, 0x0294)
|
||||
write_int16(rom, 0x15D17 + 2 * 0x18, 0x0600)
|
||||
write_int16(rom, 0x15DB5 + 2 * 0x18, 0x02E8)
|
||||
write_int16(rom, 0x15E53 + 2 * 0x18, 0x0678)
|
||||
write_int16(rom, 0x15EF1 + 2 * 0x18, 0x0303)
|
||||
write_int16(rom, 0x15F8F + 2 * 0x18, 0x0685)
|
||||
rom.write_byte(0x1602D + 0x18, 0x0A)
|
||||
rom.write_byte(0x1607C + 0x18, 0xF6)
|
||||
rom.write_int16(0x160CB + 2 * 0x18, 0x0000)
|
||||
rom.write_int16(0x16169 + 2 * 0x18, 0x0000)
|
||||
rom.write_int16(0x15AEE + 2 * 0x3D, 0x0003) # pyramid exit and houlihan
|
||||
write_int16(rom, 0x160CB + 2 * 0x18, 0x0000)
|
||||
write_int16(rom, 0x16169 + 2 * 0x18, 0x0000)
|
||||
write_int16(rom, 0x15AEE + 2 * 0x3D, 0x0003) # pyramid exit and houlihan
|
||||
rom.write_byte(0x15B8C + 0x3D, 0x5B)
|
||||
rom.write_int16(0x15BDB + 2 * 0x3D, 0x0B0E)
|
||||
rom.write_int16(0x15C79 + 2 * 0x3D, 0x075A)
|
||||
rom.write_int16(0x15D17 + 2 * 0x3D, 0x0674)
|
||||
rom.write_int16(0x15DB5 + 2 * 0x3D, 0x07A8)
|
||||
rom.write_int16(0x15E53 + 2 * 0x3D, 0x06E8)
|
||||
rom.write_int16(0x15EF1 + 2 * 0x3D, 0x07C7)
|
||||
rom.write_int16(0x15F8F + 2 * 0x3D, 0x06F3)
|
||||
write_int16(rom, 0x15BDB + 2 * 0x3D, 0x0B0E)
|
||||
write_int16(rom, 0x15C79 + 2 * 0x3D, 0x075A)
|
||||
write_int16(rom, 0x15D17 + 2 * 0x3D, 0x0674)
|
||||
write_int16(rom, 0x15DB5 + 2 * 0x3D, 0x07A8)
|
||||
write_int16(rom, 0x15E53 + 2 * 0x3D, 0x06E8)
|
||||
write_int16(rom, 0x15EF1 + 2 * 0x3D, 0x07C7)
|
||||
write_int16(rom, 0x15F8F + 2 * 0x3D, 0x06F3)
|
||||
rom.write_byte(0x1602D + 0x3D, 0x06)
|
||||
rom.write_byte(0x1607C + 0x3D, 0xFA)
|
||||
rom.write_int16(0x160CB + 2 * 0x3D, 0x0000)
|
||||
rom.write_int16(0x16169 + 2 * 0x3D, 0x0000)
|
||||
rom.write_int16(snes_to_pc(0x02D8D4), 0x112) # change sactuary spawn point to dark sanc
|
||||
write_int16(rom, 0x160CB + 2 * 0x3D, 0x0000)
|
||||
write_int16(rom, 0x16169 + 2 * 0x3D, 0x0000)
|
||||
write_int16(rom, snes_to_pc(0x02D8D4), 0x112) # change sactuary spawn point to dark sanc
|
||||
rom.write_bytes(snes_to_pc(0x02D8E8), [0x22, 0x22, 0x22, 0x23, 0x04, 0x04, 0x04, 0x05])
|
||||
rom.write_int16(snes_to_pc(0x02D91A), 0x0400)
|
||||
rom.write_int16(snes_to_pc(0x02D928), 0x222E)
|
||||
rom.write_int16(snes_to_pc(0x02D936), 0x229A)
|
||||
rom.write_int16(snes_to_pc(0x02D944), 0x0480)
|
||||
rom.write_int16(snes_to_pc(0x02D952), 0x00A5)
|
||||
rom.write_int16(snes_to_pc(0x02D960), 0x007F)
|
||||
write_int16(rom, snes_to_pc(0x02D91A), 0x0400)
|
||||
write_int16(rom, snes_to_pc(0x02D928), 0x222E)
|
||||
write_int16(rom, snes_to_pc(0x02D936), 0x229A)
|
||||
write_int16(rom, snes_to_pc(0x02D944), 0x0480)
|
||||
write_int16(rom, snes_to_pc(0x02D952), 0x00A5)
|
||||
write_int16(rom, snes_to_pc(0x02D960), 0x007F)
|
||||
rom.write_byte(snes_to_pc(0x02D96D), 0x14)
|
||||
rom.write_byte(snes_to_pc(0x02D974), 0x00)
|
||||
rom.write_byte(snes_to_pc(0x02D97B), 0xFF)
|
||||
rom.write_byte(snes_to_pc(0x02D982), 0x00)
|
||||
rom.write_byte(snes_to_pc(0x02D989), 0x02)
|
||||
rom.write_byte(snes_to_pc(0x02D990), 0x00)
|
||||
rom.write_int16(snes_to_pc(0x02D998), 0x0000)
|
||||
rom.write_int16(snes_to_pc(0x02D9A6), 0x005A)
|
||||
write_int16(rom, snes_to_pc(0x02D998), 0x0000)
|
||||
write_int16(rom, snes_to_pc(0x02D9A6), 0x005A)
|
||||
rom.write_byte(snes_to_pc(0x02D9B3), 0x12)
|
||||
# keep the old man spawn point at old man house unless shuffle is vanilla
|
||||
if world.shuffle in ['vanilla', 'dungeonsfull', 'dungeonssimple']:
|
||||
rom.write_bytes(snes_to_pc(0x308350), [0x00, 0x00, 0x01])
|
||||
rom.write_int16(snes_to_pc(0x02D8DE), 0x00F1)
|
||||
write_int16(rom, snes_to_pc(0x02D8DE), 0x00F1)
|
||||
rom.write_bytes(snes_to_pc(0x02D910), [0x1F, 0x1E, 0x1F, 0x1F, 0x03, 0x02, 0x03, 0x03])
|
||||
rom.write_int16(snes_to_pc(0x02D924), 0x0300)
|
||||
rom.write_int16(snes_to_pc(0x02D932), 0x1F10)
|
||||
rom.write_int16(snes_to_pc(0x02D940), 0x1FC0)
|
||||
rom.write_int16(snes_to_pc(0x02D94E), 0x0378)
|
||||
rom.write_int16(snes_to_pc(0x02D95C), 0x0187)
|
||||
rom.write_int16(snes_to_pc(0x02D96A), 0x017F)
|
||||
write_int16(rom, snes_to_pc(0x02D924), 0x0300)
|
||||
write_int16(rom, snes_to_pc(0x02D932), 0x1F10)
|
||||
write_int16(rom, snes_to_pc(0x02D940), 0x1FC0)
|
||||
write_int16(rom, snes_to_pc(0x02D94E), 0x0378)
|
||||
write_int16(rom, snes_to_pc(0x02D95C), 0x0187)
|
||||
write_int16(rom, snes_to_pc(0x02D96A), 0x017F)
|
||||
rom.write_byte(snes_to_pc(0x02D972), 0x06)
|
||||
rom.write_byte(snes_to_pc(0x02D979), 0x00)
|
||||
rom.write_byte(snes_to_pc(0x02D980), 0xFF)
|
||||
rom.write_byte(snes_to_pc(0x02D987), 0x00)
|
||||
rom.write_byte(snes_to_pc(0x02D98E), 0x22)
|
||||
rom.write_byte(snes_to_pc(0x02D995), 0x12)
|
||||
rom.write_int16(snes_to_pc(0x02D9A2), 0x0000)
|
||||
rom.write_int16(snes_to_pc(0x02D9B0), 0x0007)
|
||||
write_int16(rom, snes_to_pc(0x02D9A2), 0x0000)
|
||||
write_int16(rom, snes_to_pc(0x02D9B0), 0x0007)
|
||||
rom.write_byte(snes_to_pc(0x02D9B8), 0x12)
|
||||
rom.write_bytes(0x180247, [0x00, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00])
|
||||
rom.write_int16(0x15AEE + 2 * 0x06, 0x0020) # post aga hyrule castle spawn
|
||||
write_int16(rom, 0x15AEE + 2 * 0x06, 0x0020) # post aga hyrule castle spawn
|
||||
rom.write_byte(0x15B8C + 0x06, 0x1B)
|
||||
rom.write_int16(0x15BDB + 2 * 0x06, 0x00AE)
|
||||
rom.write_int16(0x15C79 + 2 * 0x06, 0x0610)
|
||||
rom.write_int16(0x15D17 + 2 * 0x06, 0x077E)
|
||||
rom.write_int16(0x15DB5 + 2 * 0x06, 0x0672)
|
||||
rom.write_int16(0x15E53 + 2 * 0x06, 0x07F8)
|
||||
rom.write_int16(0x15EF1 + 2 * 0x06, 0x067D)
|
||||
rom.write_int16(0x15F8F + 2 * 0x06, 0x0803)
|
||||
write_int16(rom, 0x15BDB + 2 * 0x06, 0x00AE)
|
||||
write_int16(rom, 0x15C79 + 2 * 0x06, 0x0610)
|
||||
write_int16(rom, 0x15D17 + 2 * 0x06, 0x077E)
|
||||
write_int16(rom, 0x15DB5 + 2 * 0x06, 0x0672)
|
||||
write_int16(rom, 0x15E53 + 2 * 0x06, 0x07F8)
|
||||
write_int16(rom, 0x15EF1 + 2 * 0x06, 0x067D)
|
||||
write_int16(rom, 0x15F8F + 2 * 0x06, 0x0803)
|
||||
rom.write_byte(0x1602D + 0x06, 0x00)
|
||||
rom.write_byte(0x1607C + 0x06, 0xF2)
|
||||
rom.write_int16(0x160CB + 2 * 0x06, 0x0000)
|
||||
rom.write_int16(0x16169 + 2 * 0x06, 0x0000)
|
||||
rom.write_int16(snes_to_pc(0x02E87B), 0x00AE) # move flute splot 9
|
||||
rom.write_int16(snes_to_pc(0x02E89D), 0x0610)
|
||||
rom.write_int16(snes_to_pc(0x02E8BF), 0x077E)
|
||||
rom.write_int16(snes_to_pc(0x02E8E1), 0x0672)
|
||||
rom.write_int16(snes_to_pc(0x02E903), 0x07F8)
|
||||
rom.write_int16(snes_to_pc(0x02E925), 0x067D)
|
||||
rom.write_int16(snes_to_pc(0x02E947), 0x0803)
|
||||
rom.write_int16(snes_to_pc(0x02E969), 0x0000)
|
||||
rom.write_int16(snes_to_pc(0x02E98B), 0xFFF2)
|
||||
write_int16(rom, 0x160CB + 2 * 0x06, 0x0000)
|
||||
write_int16(rom, 0x16169 + 2 * 0x06, 0x0000)
|
||||
write_int16(rom, snes_to_pc(0x02E87B), 0x00AE) # move flute splot 9
|
||||
write_int16(rom, snes_to_pc(0x02E89D), 0x0610)
|
||||
write_int16(rom, snes_to_pc(0x02E8BF), 0x077E)
|
||||
write_int16(rom, snes_to_pc(0x02E8E1), 0x0672)
|
||||
write_int16(rom, snes_to_pc(0x02E903), 0x07F8)
|
||||
write_int16(rom, snes_to_pc(0x02E925), 0x067D)
|
||||
write_int16(rom, snes_to_pc(0x02E947), 0x0803)
|
||||
write_int16(rom, snes_to_pc(0x02E969), 0x0000)
|
||||
write_int16(rom, snes_to_pc(0x02E98B), 0xFFF2)
|
||||
rom.write_byte(snes_to_pc(0x1AF696), 0xF0) # bat sprite retreat
|
||||
rom.write_byte(snes_to_pc(0x1AF6B2), 0x33)
|
||||
rom.write_bytes(snes_to_pc(0x1AF730), [0x6A, 0x9E, 0x0C, 0x00, 0x7A, 0x9E, 0x0C,
|
||||
|
@ -1570,7 +1604,7 @@ def set_inverted_mode(world, rom):
|
|||
0x0C, 0x00, 0x7A, 0xAE, 0x0C, 0x00, 0x8A,
|
||||
0xAE, 0x0C, 0x00, 0x67, 0x97, 0x0C, 0x00,
|
||||
0x8D, 0x97, 0x0C, 0x00])
|
||||
rom.write_int16s(snes_to_pc(0x0FF1C8), [0x190F, 0x190F, 0x190F, 0x194C, 0x190F,
|
||||
write_int16s(rom, snes_to_pc(0x0FF1C8), [0x190F, 0x190F, 0x190F, 0x194C, 0x190F,
|
||||
0x194B, 0x190F, 0x195C, 0x594B, 0x194C,
|
||||
0x19EE, 0x19EE, 0x194B, 0x19EE, 0x19EE,
|
||||
0x19EE, 0x594B, 0x190F, 0x595C, 0x190F,
|
||||
|
@ -1578,38 +1612,38 @@ def set_inverted_mode(world, rom):
|
|||
0x19EE, 0x195C, 0x19EE, 0x19EE, 0x19EE,
|
||||
0x19EE, 0x595C, 0x595B, 0x190F, 0x190F,
|
||||
0x190F])
|
||||
rom.write_int16s(snes_to_pc(0x0FA480), [0x190F, 0x196B, 0x9D04, 0x9D04, 0x196B,
|
||||
write_int16s(rom, snes_to_pc(0x0FA480), [0x190F, 0x196B, 0x9D04, 0x9D04, 0x196B,
|
||||
0x190F, 0x9D04, 0x9D04])
|
||||
rom.write_int16s(snes_to_pc(0x1bb810), [0x00BE, 0x00C0, 0x013E])
|
||||
rom.write_int16s(snes_to_pc(0x1bb836), [0x001B, 0x001B, 0x001B])
|
||||
rom.write_int16(snes_to_pc(0x308300), 0x0140) # new pyramid hole entrance
|
||||
rom.write_int16(snes_to_pc(0x308320), 0x001B)
|
||||
write_int16s(rom, snes_to_pc(0x1bb810), [0x00BE, 0x00C0, 0x013E])
|
||||
write_int16s(rom, snes_to_pc(0x1bb836), [0x001B, 0x001B, 0x001B])
|
||||
write_int16(rom, snes_to_pc(0x308300), 0x0140) # new pyramid hole entrance
|
||||
write_int16(rom, snes_to_pc(0x308320), 0x001B)
|
||||
if world.shuffle in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
|
||||
rom.write_byte(snes_to_pc(0x308340), 0x7B)
|
||||
rom.write_int16(snes_to_pc(0x1af504), 0x148B)
|
||||
rom.write_int16(snes_to_pc(0x1af50c), 0x149B)
|
||||
rom.write_int16(snes_to_pc(0x1af514), 0x14A4)
|
||||
rom.write_int16(snes_to_pc(0x1af51c), 0x1489)
|
||||
rom.write_int16(snes_to_pc(0x1af524), 0x14AC)
|
||||
rom.write_int16(snes_to_pc(0x1af52c), 0x54AC)
|
||||
rom.write_int16(snes_to_pc(0x1af534), 0x148C)
|
||||
rom.write_int16(snes_to_pc(0x1af53c), 0x548C)
|
||||
rom.write_int16(snes_to_pc(0x1af544), 0x1484)
|
||||
rom.write_int16(snes_to_pc(0x1af54c), 0x5484)
|
||||
rom.write_int16(snes_to_pc(0x1af554), 0x14A2)
|
||||
rom.write_int16(snes_to_pc(0x1af55c), 0x54A2)
|
||||
rom.write_int16(snes_to_pc(0x1af564), 0x14A0)
|
||||
rom.write_int16(snes_to_pc(0x1af56c), 0x54A0)
|
||||
rom.write_int16(snes_to_pc(0x1af574), 0x148E)
|
||||
rom.write_int16(snes_to_pc(0x1af57c), 0x548E)
|
||||
rom.write_int16(snes_to_pc(0x1af584), 0x14AE)
|
||||
rom.write_int16(snes_to_pc(0x1af58c), 0x54AE)
|
||||
write_int16(rom, snes_to_pc(0x1af504), 0x148B)
|
||||
write_int16(rom, snes_to_pc(0x1af50c), 0x149B)
|
||||
write_int16(rom, snes_to_pc(0x1af514), 0x14A4)
|
||||
write_int16(rom, snes_to_pc(0x1af51c), 0x1489)
|
||||
write_int16(rom, snes_to_pc(0x1af524), 0x14AC)
|
||||
write_int16(rom, snes_to_pc(0x1af52c), 0x54AC)
|
||||
write_int16(rom, snes_to_pc(0x1af534), 0x148C)
|
||||
write_int16(rom, snes_to_pc(0x1af53c), 0x548C)
|
||||
write_int16(rom, snes_to_pc(0x1af544), 0x1484)
|
||||
write_int16(rom, snes_to_pc(0x1af54c), 0x5484)
|
||||
write_int16(rom, snes_to_pc(0x1af554), 0x14A2)
|
||||
write_int16(rom, snes_to_pc(0x1af55c), 0x54A2)
|
||||
write_int16(rom, snes_to_pc(0x1af564), 0x14A0)
|
||||
write_int16(rom, snes_to_pc(0x1af56c), 0x54A0)
|
||||
write_int16(rom, snes_to_pc(0x1af574), 0x148E)
|
||||
write_int16(rom, snes_to_pc(0x1af57c), 0x548E)
|
||||
write_int16(rom, snes_to_pc(0x1af584), 0x14AE)
|
||||
write_int16(rom, snes_to_pc(0x1af58c), 0x54AE)
|
||||
rom.write_byte(snes_to_pc(0x00DB9D), 0x1A) # castle hole graphics
|
||||
rom.write_byte(snes_to_pc(0x00DC09), 0x1A)
|
||||
rom.write_byte(snes_to_pc(0x00D009), 0x31)
|
||||
rom.write_byte(snes_to_pc(0x00D0e8), 0xE0)
|
||||
rom.write_byte(snes_to_pc(0x00D1c7), 0x00)
|
||||
rom.write_int16(snes_to_pc(0x1BE8DA), 0x39AD)
|
||||
write_int16(rom, snes_to_pc(0x1BE8DA), 0x39AD)
|
||||
rom.write_byte(0xF6E58, 0x80) # no whirlpool under castle gate
|
||||
rom.write_bytes(0x0086E, [0x5C, 0x00, 0xA0, 0xA1]) # TR tail
|
||||
rom.write_bytes(snes_to_pc(0x1BC67A), [0x2E, 0x0B, 0x82]) # add warps under rocks
|
||||
|
@ -1619,25 +1653,25 @@ def set_inverted_mode(world, rom):
|
|||
rom.write_bytes(snes_to_pc(0x1BC3DF), [0xD8, 0xD1])
|
||||
rom.write_bytes(snes_to_pc(0x1BD1D8), [0xA8, 0x02, 0x82, 0xFF, 0xFF])
|
||||
rom.write_bytes(snes_to_pc(0x1BC85A), [0x50, 0x0F, 0x82])
|
||||
rom.write_int16(0xDB96F + 2 * 0x35, 0x001B) # move pyramid exit door
|
||||
rom.write_int16(0xDBA71 + 2 * 0x35, 0x06A4)
|
||||
write_int16(rom, 0xDB96F + 2 * 0x35, 0x001B) # move pyramid exit door
|
||||
write_int16(rom, 0xDBA71 + 2 * 0x35, 0x06A4)
|
||||
if world.shuffle in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
|
||||
rom.write_byte(0xDBB73 + 0x35, 0x36)
|
||||
rom.write_byte(snes_to_pc(0x09D436), 0xF3) # remove castle gate warp
|
||||
if world.shuffle in ['vanilla', 'dungeonssimple', 'dungeonsfull']:
|
||||
rom.write_int16(0x15AEE + 2 * 0x37, 0x0010) # pyramid exit to new hc area
|
||||
write_int16(rom, 0x15AEE + 2 * 0x37, 0x0010) # pyramid exit to new hc area
|
||||
rom.write_byte(0x15B8C + 0x37, 0x1B)
|
||||
rom.write_int16(0x15BDB + 2 * 0x37, 0x0418)
|
||||
rom.write_int16(0x15C79 + 2 * 0x37, 0x0679)
|
||||
rom.write_int16(0x15D17 + 2 * 0x37, 0x06B4)
|
||||
rom.write_int16(0x15DB5 + 2 * 0x37, 0x06C6)
|
||||
rom.write_int16(0x15E53 + 2 * 0x37, 0x0738)
|
||||
rom.write_int16(0x15EF1 + 2 * 0x37, 0x06E6)
|
||||
rom.write_int16(0x15F8F + 2 * 0x37, 0x0733)
|
||||
write_int16(rom, 0x15BDB + 2 * 0x37, 0x0418)
|
||||
write_int16(rom, 0x15C79 + 2 * 0x37, 0x0679)
|
||||
write_int16(rom, 0x15D17 + 2 * 0x37, 0x06B4)
|
||||
write_int16(rom, 0x15DB5 + 2 * 0x37, 0x06C6)
|
||||
write_int16(rom, 0x15E53 + 2 * 0x37, 0x0738)
|
||||
write_int16(rom, 0x15EF1 + 2 * 0x37, 0x06E6)
|
||||
write_int16(rom, 0x15F8F + 2 * 0x37, 0x0733)
|
||||
rom.write_byte(0x1602D + 0x37, 0x07)
|
||||
rom.write_byte(0x1607C + 0x37, 0xF9)
|
||||
rom.write_int16(0x160CB + 2 * 0x37, 0x0000)
|
||||
rom.write_int16(0x16169 + 2 * 0x37, 0x0000)
|
||||
write_int16(rom, 0x160CB + 2 * 0x37, 0x0000)
|
||||
write_int16(rom, 0x16169 + 2 * 0x37, 0x0000)
|
||||
rom.write_bytes(snes_to_pc(0x1BC387), [0xDD, 0xD1])
|
||||
rom.write_bytes(snes_to_pc(0x1BD1DD), [0xA4, 0x06, 0x82, 0x9E, 0x06, 0x82, 0xFF, 0xFF])
|
||||
rom.write_byte(0x180089, 0x01) # open TR after exit
|
||||
|
@ -1652,9 +1686,9 @@ def patch_shuffled_dark_sanc(world, rom, player):
|
|||
|
||||
rom.write_byte(0x180241, 0x01)
|
||||
rom.write_byte(0x180248, door_index + 1)
|
||||
rom.write_int16(0x180250, room_id)
|
||||
write_int16(rom, 0x180250, room_id)
|
||||
rom.write_byte(0x180252, ow_area)
|
||||
rom.write_int16s(0x180253, [vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x])
|
||||
write_int16s(rom, 0x180253, [vram_loc, scroll_y, scroll_x, link_y, link_x, camera_y, camera_x])
|
||||
rom.write_bytes(0x180262, [unknown_1, unknown_2, 0x00])
|
||||
|
||||
InconvenientDungeonEntrances = {'Turtle Rock': 'Turtle Rock Main',
|
||||
|
|
4
Utils.py
4
Utils.py
|
@ -1,7 +1,11 @@
|
|||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def parse_names_string(names):
|
||||
return {player: name for player, name in enumerate([n for n in re.split(r'[, ]', names) if n], 1)}
|
||||
|
||||
def int16_as_bytes(value):
|
||||
value = value & 0xFFFF
|
||||
return [value & 0xFF, (value >> 8) & 0xFF]
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue