diff --git a/BaseClasses.py b/BaseClasses.py index 11bccc67..4571c520 100644 --- a/BaseClasses.py +++ b/BaseClasses.py @@ -148,7 +148,9 @@ class World(object): region.world = self self._region_cache[region.player][region.name] = region for exit in region.exits: - self._entrance_cache[(exit.name, exit.player)] = exit + self._entrance_cache[exit.name, exit.player] = exit + for r_location in region.locations: + self._location_cache[r_location.name, r_location.player] = r_location def initialize_doors(self, doors): for door in doors: diff --git a/DoorShuffle.py b/DoorShuffle.py index 2d2b218e..d2cf35f8 100644 --- a/DoorShuffle.py +++ b/DoorShuffle.py @@ -2418,7 +2418,7 @@ interior_doors = [ ('Skull Pull Switch S', 'Skull Big Chest N'), ('Skull Left Drop ES', 'Skull Compass Room WS'), ('Skull 2 East Lobby NW', 'Skull Big Key SW'), - ('Skull Big Key WN', 'Skull Lone Pot EN'), + ('Skull Big Key EN', 'Skull Lone Pot WN'), ('Skull Small Hall WS', 'Skull 2 West Lobby ES'), ('Skull 2 West Lobby NW', 'Skull X Room SW'), ('Skull 3 Lobby EN', 'Skull East Bridge WN'), diff --git a/Doors.py b/Doors.py index c3cc969d..a3c8d8c6 100644 --- a/Doors.py +++ b/Doors.py @@ -541,8 +541,8 @@ def create_doors(world, player): create_door(player, 'Skull 2 East Lobby WS', Nrml).dir(We, 0x57, Bot, High).pos(4), create_door(player, 'Skull 2 East Lobby NW', Intr).dir(No, 0x57, Left, High).pos(1), create_door(player, 'Skull Big Key SW', Intr).dir(So, 0x57, Left, High).pos(1), - create_door(player, 'Skull Big Key WN', Intr).dir(We, 0x57, Top, High).pos(0), - create_door(player, 'Skull Lone Pot EN', Intr).dir(Ea, 0x57, Top, High).pos(0), + create_door(player, 'Skull Big Key EN', Intr).dir(Ea, 0x57, Top, High).pos(0), + create_door(player, 'Skull Lone Pot WN', Intr).dir(We, 0x57, Top, High).pos(0), create_door(player, 'Skull Small Hall ES', Nrml).dir(Ea, 0x56, Bot, High).pos(3), create_door(player, 'Skull Small Hall WS', Intr).dir(We, 0x56, Bot, High).pos(2), create_door(player, 'Skull 2 West Lobby S', Nrml).dir(So, 0x56, Left, High).pos(1).portal(Z, 0x00), @@ -1304,7 +1304,6 @@ def create_doors(world, player): world.get_door('GT Petting Zoo SE', player).dead_end() world.get_door('GT DMs Room SW', player).dead_end() world.get_door("GT Bob\'s Room SE", player).passage = False - world.get_door('PoD Mimics 2 SW', player).bk_shuffle_req = True world.get_door('Desert Tiles 2 SE', player).bk_shuffle_req = True # key-drop note (todo) world.get_door('Swamp Lobby S', player).standard_restricted = True # key-drop note (todo) diff --git a/KeyDoorShuffle.py b/KeyDoorShuffle.py index becf6bf8..1a4e1469 100644 --- a/KeyDoorShuffle.py +++ b/KeyDoorShuffle.py @@ -579,7 +579,7 @@ def progressive_ctr(new_counter, last_counter): def unique_child_door(child, key_counter): if child in key_counter.child_doors or child.dest in key_counter.child_doors: return False - if child in key_counter.open_doors or child.dest in key_counter.child_doors: + if child in key_counter.open_doors or child.dest in key_counter.open_doors: return False if child.bigKey and key_counter.big_key_opened: return False @@ -589,7 +589,7 @@ def unique_child_door(child, key_counter): def unique_child_door_2(child, key_counter): if child in key_counter.child_doors or child.dest in key_counter.child_doors: return False - if child in key_counter.open_doors or child.dest in key_counter.child_doors: + if child in key_counter.open_doors or child.dest in key_counter.open_doors: return False return True @@ -1463,7 +1463,10 @@ def create_odd_key_counter(door, parent_counter, key_layout, world, player): next_counter = find_next_counter(door, parent_counter, key_layout) odd_counter.free_locations = dict_difference(next_counter.free_locations, parent_counter.free_locations) odd_counter.key_only_locations = dict_difference(next_counter.key_only_locations, parent_counter.key_only_locations) - odd_counter.child_doors = dict_difference(next_counter.child_doors, parent_counter.child_doors) + odd_counter.child_doors = {} + for d in next_counter.child_doors: + if d not in parent_counter.child_doors and (d.type == DoorType.SpiralStairs or d.dest not in parent_counter.child_doors): + odd_counter.child_doors[d] = None odd_counter.other_locations = dict_difference(next_counter.other_locations, parent_counter.other_locations) odd_counter.important_locations = dict_difference(next_counter.important_locations, parent_counter.important_locations) for loc in odd_counter.other_locations: diff --git a/Main.py b/Main.py index 21cbabeb..61934050 100644 --- a/Main.py +++ b/Main.py @@ -401,12 +401,13 @@ def copy_world(world): copied_shop.inventory = copy.copy(shop.inventory) # connect copied world + copied_locations = {(loc.name, loc.player): loc for loc in ret.get_locations()} # caches all locations for region in world.regions: copied_region = ret.get_region(region.name, region.player) copied_region.is_light_world = region.is_light_world copied_region.is_dark_world = region.is_dark_world copied_region.dungeon = region.dungeon - copied_region.locations = [ret.get_location(location.name, location.player) for location in region.locations] + copied_region.locations = [copied_locations[(location.name, location.player)] for location in region.locations] for entrance in region.entrances: ret.get_entrance(entrance.name, entrance.player).connect(copied_region) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 33db309a..9cfcd9b6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -122,15 +122,32 @@ Added to CLI only now. The Mystery.py file has been updated for those who like to use that for generating games. Supports keydropshuffle, shopsanity, and other settings that have been included. +## Experimental Item Counter + +New item counter modified to show total + # Bug Fixes -* 0.3.1.1-u +* 0.3.1.0-u + * Shopsanity introduced + * Minor fix to Standard generation +* 0.3.0.4-u + * QoL fixes from Mike + * Allow PoD Mimics 2 as a lobby in non-keysanity seeds (Thanks @Catobat) + * Fix for double-counting Hera key in keydropshuffle +* 0.3.0.3-u * Disallowed Swamp Lobby in Hyrule Castle in Standard mode * Prevent defeating Aga 1 before Zelda is delivered to the Sanctuary. (He can't take damage) * Fix for Ice Jelly room when going backward and enemizer is on * Fix for inverted - don't start as a bunny in Dark Sanctuary * Fix for non-ER Inverted with Lobby shuffle. Aga Tower's exit works properly now. - * Minor fix to Standard generation + * Fix for In-Room Stairs with Trap Doors + * Key logic fix + * Fix for door gen re-start + * More lenient keys in DR+Retro + * Fix for shufflepots option +* 0.3.0.2-u + * Introduced in-room staircases/ladders * 0.3.0.1-u * Problem with lobbies on re-rolls corrected * Potential playthrough problem addressed diff --git a/Regions.py b/Regions.py index 6794eee7..cbbc8cb1 100644 --- a/Regions.py +++ b/Regions.py @@ -473,8 +473,8 @@ def create_dungeon_regions(world, player): create_dungeon_region(player, 'Skull Compass Room', 'Skull Woods', ['Skull Woods - Compass Chest'], ['Skull Compass Room NE', 'Skull Compass Room ES', 'Skull Compass Room WS']), create_dungeon_region(player, 'Skull Left Drop', 'Skull Woods', None, ['Skull Left Drop ES']), create_dungeon_region(player, 'Skull 2 East Lobby', 'Skull Woods', None, ['Skull 2 East Lobby NW', 'Skull 2 East Lobby WS', 'Skull 2 East Lobby SW']), - create_dungeon_region(player, 'Skull Big Key', 'Skull Woods', ['Skull Woods - Big Key Chest'], ['Skull Big Key SW', 'Skull Big Key WN']), - create_dungeon_region(player, 'Skull Lone Pot', 'Skull Woods', None, ['Skull Lone Pot EN']), + create_dungeon_region(player, 'Skull Big Key', 'Skull Woods', ['Skull Woods - Big Key Chest'], ['Skull Big Key SW', 'Skull Big Key EN']), + create_dungeon_region(player, 'Skull Lone Pot', 'Skull Woods', None, ['Skull Lone Pot WN']), create_dungeon_region(player, 'Skull Small Hall', 'Skull Woods', None, ['Skull Small Hall ES', 'Skull Small Hall WS']), create_dungeon_region(player, 'Skull Back Drop', 'Skull Woods', None, ['Skull Back Drop Star Path', ]), create_dungeon_region(player, 'Skull 2 West Lobby', 'Skull Woods', ['Skull Woods - West Lobby Pot Key'], ['Skull 2 West Lobby ES', 'Skull 2 West Lobby NW', 'Skull 2 West Lobby S']), diff --git a/Rom.py b/Rom.py index 0c359a57..cefe7313 100644 --- a/Rom.py +++ b/Rom.py @@ -27,7 +27,7 @@ from EntranceShuffle import door_addresses, exit_ids JAP10HASH = '03a63945398191337e896e5771f77173' -RANDOMIZERBASEHASH = '6a4096235f682b7e4e1a65f274c7037b' +RANDOMIZERBASEHASH = '0a34dc667a29125f09b10aeb1e06b83c' class JsonRom(object): @@ -942,8 +942,6 @@ def patch_rom(world, rom, player, team, enemized, is_mystery=False): if difficulty.progressive_bow_limit < 2 and world.swords == 'swordless': rom.write_bytes(0x180098, [2, overflow_replacement]) - rom.write_byte(0x180181, 0x01) # Make silver arrows work only on ganon - rom.write_byte(0x180182, 0x00) # Don't auto equip silvers on pickup # set up game internal RNG seed for i in range(1024): diff --git a/asm/asm_investigations.txt b/asm/asm_investigations.txt index feb5e09b..9d0cce24 100644 --- a/asm/asm_investigations.txt +++ b/asm/asm_investigations.txt @@ -200,3 +200,14 @@ $bc - TT 188 idx 1 ; called by 10CE2, (Dungeon_SpiralStaircase_3) ;122f0 + +Link's position after screen transition and auto-walk (from $02C034): + +0C 20 30 38 48 ; down +D4 D8 C0 C0 A8 ; up +0C 18 28 30 40 ; right +E4 D8 C8 C0 B0 ; left + +Effectively indexed by $0418*#$05+$4E. +Row ($0418) is the direction and column ($4E) determines how far to auto-walk (depends on tile attribute at edge of screen). +From left to right: edge, inside high door, outside high door, inside low door and outside low door. diff --git a/asm/spiral.asm b/asm/spiral.asm index 0d13c4e7..2dd2ab8b 100644 --- a/asm/spiral.asm +++ b/asm/spiral.asm @@ -170,10 +170,10 @@ InroomStairsWarp: { ; should be the same as lda $0462 : and #$04 : lsr #2 : eor #$01 : sta $07 lda $01 : and #$80 : beq .notEdge lda $07 : sta $03 : beq + - lda $01 : jsr LoadSouthMidpoint : sta $22 : lda #$e0 + lda $01 : jsr LoadSouthMidpoint : sta $22 : lda #$f4 bra ++ + - lda $01 : jsr LoadNorthMidpoint : sta $22 : lda #$1b + lda $01 : jsr LoadNorthMidpoint : sta $22 : dec $21 : lda #$f7 ++ sta $20 lda $01 : and #$20 : beq + @@ -185,26 +185,36 @@ InroomStairsWarp: { brl .layer .notEdge lda $01 : and #$03 : cmp #$03 : bne .normal + txa : and #$06 : sta $07 lda $01 : and #$30 : lsr #3 : tay - lda.w InroomStairsX,y : sta $22 lda.w InroomStairsX+1,y : sta $02 lda.w InroomStairsY+1,y : sta $03 - lda.w InroomStairsY,y - ldy $07 : beq + - !add #$07 - + - sta $20 + cpy $07 : beq .vanillaTransition + lda.w InroomStairsX,y : sta $22 + lda.w InroomStairsY,y + ldy $07 : beq + + !add #$07 + + + sta $20 + inc $07 + bra ++ + .vanillaTransition + lda #$c0 : sta $07 ; leave camera + ++ %StonewallCheck($1b) - inc $07 lda $01 : and #$04 : lsr #2 bra .layer .normal lda $01 : sta $fe ; trap door lda $07 : sta $03 : beq + + ldy $a0 : cpy #$51 : beq .specialFix ; throne room + cpy #$02 : beq .specialFix ; sewers pull switch + cpy #$71 : beq .specialFix ; castle armory lda #$e0 - ldy $a0 : cpy #$51 : bne ++ ; special fix for throne room - !sub #$18 - bra ++ + bra ++ + .specialFix + lda #$c8 + bra ++ + %StonewallCheck($43) lda #$1b @@ -235,12 +245,15 @@ InroomStairsWarp: { ldy #$01 : jsr ShiftQuadSimple .skipYQuad + lda $07 : bmi .skipCamera ldy #$00 : jsr SetCamera ; horizontal camera ldy #$01 : sty $07 : jsr SetCamera ; vertical camera lda $20 : cmp #$e0 : bcc + lda $e8 : bne + lda #$10 : sta $e8 ; adjust vertical camera at bottom + + .skipCamera + jsr StairCleanup ply : plx : plb ; pull the stuff we pushed rts diff --git a/data/base2current.bps b/data/base2current.bps index 4674a063..fabbaa26 100644 Binary files a/data/base2current.bps and b/data/base2current.bps differ diff --git a/source/classes/SpriteSelector.py b/source/classes/SpriteSelector.py index 102e4351..6e44277e 100644 --- a/source/classes/SpriteSelector.py +++ b/source/classes/SpriteSelector.py @@ -43,7 +43,7 @@ class SpriteSelector(object): # Open SpriteSomething directory for Link sprites def open_spritesomething_listing(_evt): - webbrowser.open("https://artheau.github.io/SpriteSomething/resources/app/snes/zelda3/link/sprites.html") + webbrowser.open("https://miketrethewey.github.io/SpriteSomething-collections/snes/zelda3/link/") official_frametitle = Frame(self.window) official_title_text = Label(official_frametitle, text="Official Sprites") diff --git a/source/gui/randomize/generation.py b/source/gui/randomize/generation.py index f358c864..4bcb868c 100644 --- a/source/gui/randomize/generation.py +++ b/source/gui/randomize/generation.py @@ -1,8 +1,11 @@ -from tkinter import ttk, filedialog, StringVar, Button, Entry, Frame, Label, E, W, LEFT, X +from tkinter import ttk, filedialog, StringVar, Button, Entry, Frame, Label, E, W, LEFT, X, Text, Tk, INSERT +import source.classes.diags as diagnostics import source.gui.widgets as widgets import json import os +from functools import partial from source.classes.Empty import Empty +from Main import __version__ def generation_page(parent,settings): # Generation Setup @@ -76,4 +79,45 @@ def generation_page(parent,settings): # frame: pack self.widgets[widget].pieces["frame"].pack(fill=X) + ## Run Diagnostics + # This one's more-complicated, build it and stuff it + # widget ID + widget = "diags" + + # Empty object + self.widgets[widget] = Empty() + # pieces + self.widgets[widget].pieces = {} + + # frame + self.frames["diags"] = Frame(self) + self.frames["diags"].pack() + self.widgets[widget].pieces["frame"] = Frame(self.frames["diags"]) + + + def diags(): + # Debugging purposes + dims = { + "window": { + "width": 800, + "height": 500 + }, + "textarea.characters": { + "width": 120, + "height": 50 + } + } + diag = Tk() + diag.title("Door Shuffle " + __version__) + diag.geometry(str(dims["window"]["width"]) + 'x' + str(dims["window"]["height"])) + text = Text(diag, width=dims["textarea.characters"]["width"], height=dims["textarea.characters"]["height"]) + text.pack() + text.insert(INSERT,"\n".join(diagnostics.output(__version__))) + # dialog button + self.widgets[widget].pieces["button"] = Button(self.widgets[widget].pieces["frame"], text='Run Diagnostics', command=partial(diags)) + + # button: pack + self.widgets[widget].pieces["button"].pack(side=LEFT) + # frame: pack + self.widgets[widget].pieces["frame"].pack(fill=X) return self,settings