diff --git a/RandomizerCore/Hyrule.cs b/RandomizerCore/Hyrule.cs index 2ed56125..22b9b6e9 100644 --- a/RandomizerCore/Hyrule.cs +++ b/RandomizerCore/Hyrule.cs @@ -3846,6 +3846,7 @@ private void ApplyAsmPatches(RandomizerProperties props, Assembler engine, Rando rom.FixItemPickup(engine); rom.FixMinibossGlitchyAppearance(engine); rom.FixBossKillPaletteGlitch(engine); + rom.ThunderbirdEnterLeftFix(engine); rom.FixBigBubbleSplit(engine, randomizedStats); StatTracking(props, engine); AddCredits(engine); diff --git a/RandomizerCore/ROM.cs b/RandomizerCore/ROM.cs index 3fac62c6..db829790 100644 --- a/RandomizerCore/ROM.cs +++ b/RandomizerCore/ROM.cs @@ -2019,6 +2019,80 @@ bne @CopyLoop """); } + public void ThunderbirdEnterLeftFix(Assembler asm) + { + var a = asm.Module(); + a.Code(/* lang=s */""" +.include "z2r.inc" +.import ElevatorBossFix + +.segment "PRG5" + +DrawThunderbird = $9ebf +ThunderbirdMainRoutineStart = $a359 +ThunderbirdMainRoutine = $a36b +ThunderbirdNoRoutine = $a3bd + +; Do a much finer scroll position check so we can get into position +; when entering from the right as well. +.org $94d1 + .word ThunderbirdFixedScrollCheck + +; When entering from the right, Thunderbird would be visible before the combat starts. +; While it's not a problem, it feels a bit jank, so lets not draw Thunderbird pre-battle. +.org $95a9 + .word ConditionalDrawThunderbird + + +.org ThunderbirdMainRoutineStart +FREE_UNTIL ThunderbirdMainRoutine + +.reloc +ThunderbirdFixedScrollCheck: + lda ScrollLeftPage + cmp #$01 + bne @DontSpawn + + lda ScrollLeftX + cmp #$04 ; need at least 3 pixel margin so dash speed can't skip the trigger point + bcs @DontSpawn + + lda ScrollFrozen + bne @TimerRunning + jsr ElevatorBossFix ; will freeze the scrolling (and more) + lda #$90 + sta $0504,x ; set timer for Thunderbird to begin + + @TimerRunning: + lda ScrollLeftX + beq @NoScroll + + lda FrameCounter + and #$01 + bne @NoScroll ; slow down screen scroll to every other frame + + lda ScrollLeftX + sbc #$00 ; subtracts 1 because carry is cleared by cmp + bcs @SetScrollX + lda #$00 ; clamp to 0 + @SetScrollX: + sta ScrollLeftX + sta ScrollPosShadow + @NoScroll: + jmp ThunderbirdMainRoutine + @DontSpawn: + jmp ThunderbirdNoRoutine + +.reloc +ConditionalDrawThunderbird: + lda ScrollFrozen + beq @NotInBattle + jmp DrawThunderbird + @NotInBattle: + rts +"""); + } + public void BuffCarrock(Assembler a) { a.Module().Code(Util.ReadResource("Z2Randomizer.RandomizerCore.Asm.BuffCarock.s"), "buff_carock.s"); @@ -2166,9 +2240,10 @@ jsr ElevatorBossFix jsr ElevatorBossFix ; Screen lock tbird set at bank 5 A363 (0x16373) -.segment "PRG5" -.org $a363 - jsr ElevatorBossFix +; ThunderbirdEnterLeftFix does this call now +;.segment "PRG5" +;.org $a363 +; jsr ElevatorBossFix .segment "PRG7" diff --git a/RandomizerCore/Sidescroll/ChaosPalaceGenerator.cs b/RandomizerCore/Sidescroll/ChaosPalaceGenerator.cs index c08c6403..357ec02c 100644 --- a/RandomizerCore/Sidescroll/ChaosPalaceGenerator.cs +++ b/RandomizerCore/Sidescroll/ChaosPalaceGenerator.cs @@ -160,8 +160,7 @@ internal override async Task GeneratePalace(RandomizerProperties props, }; //Chaos palaces do not check for inescapable drops. They are inherently insane and not remotely beginner-friendly. - - palace.IsValid = palace.AllReachable(true); + palace.IsValid = palace.AllReachable(allowBacktracking: true, allowBossEnterLeft: palace.Number == 7); return palace; } } diff --git a/RandomizerCore/Sidescroll/Palace.cs b/RandomizerCore/Sidescroll/Palace.cs index 8fd13931..0b3824b1 100644 --- a/RandomizerCore/Sidescroll/Palace.cs +++ b/RandomizerCore/Sidescroll/Palace.cs @@ -98,17 +98,20 @@ private void CheckSpecialPaths(Room r) } } - public IEnumerable GetReachableRooms(bool allowBacktracking = false) + public IEnumerable GetReachableRooms(bool allowBacktracking = false, bool allowBossEnterLeft = false) { if(Entrance == null) { throw new Exception("Palace Entrance is missing"); } - foreach (Room r in AllRooms) + if (!allowBossEnterLeft) { - if (r.HasBoss && CanEnterBossFromLeft(r)) + foreach (Room r in AllRooms) { - return [Entrance]; + if (r.HasBoss && CanEnterBossFromLeft(r)) + { + return [Entrance]; + } } } HashSet reachedRooms = []; @@ -117,13 +120,16 @@ public IEnumerable GetReachableRooms(bool allowBacktracking = false) while (roomsToCheck.Count > 0) { var (room, originDirection) = roomsToCheck.Pop(); + //For required thunderbird, you can't path backwards into tbird room - if ((Number == 7 && room.IsThunderBirdRoom) - || (Number < 7 && room.IsBossRoom)) + if (!allowBossEnterLeft) { - if (originDirection == Direction.EAST) + if ((Number == 7 && room.IsThunderBirdRoom) || (Number < 7 && room.IsBossRoom)) { - return [Entrance]; + if (originDirection == Direction.EAST) + { + return [Entrance]; + } } } @@ -244,9 +250,9 @@ public static bool BossRoomMinDistanceShape(Dictionary shap return false; // Boss room not found? } - public bool AllReachable(bool allowBacktracking = false) + public bool AllReachable(bool allowBacktracking = false, bool allowBossEnterLeft = false) { - var reachableRooms = GetReachableRooms(allowBacktracking); + var reachableRooms = GetReachableRooms(allowBacktracking: allowBacktracking, allowBossEnterLeft: allowBossEnterLeft); return AllRooms.All(i => reachableRooms.Contains(i)); } diff --git a/RandomizerCore/Sidescroll/ReconstructedLoopyPalaceGenerator.cs b/RandomizerCore/Sidescroll/ReconstructedLoopyPalaceGenerator.cs index a431c836..cb9cdb82 100644 --- a/RandomizerCore/Sidescroll/ReconstructedLoopyPalaceGenerator.cs +++ b/RandomizerCore/Sidescroll/ReconstructedLoopyPalaceGenerator.cs @@ -18,14 +18,6 @@ internal override Task GeneratePalace(RandomizerProperties props, RoomPo public override void Consolidate(List openRooms, RandomizerProperties props, int palaceNumber) { - if (palaceNumber == 7 && !props.RequireTbird && !props.RemoveTbird) - { - // Connecting rooms at maximum distance increases the likelihood that Thunderbird - // will be required by too much. (Dark Link is the only dead-end possible.) - // Lets use old Reconstructed loops for this case. - base.Consolidate(openRooms, props, palaceNumber); - return; - } Room[] openCopy = new Room[openRooms.Count]; openRooms.CopyTo(openCopy); // shallow copy foreach (Room r2 in openCopy) @@ -43,4 +35,9 @@ public override void Consolidate(List openRooms, RandomizerProperties prop } } } + + public override bool AllReachable(Palace palace) + { + return palace.AllReachable(allowBossEnterLeft: palace.Number == 7); + } } diff --git a/RandomizerCore/Sidescroll/ReconstructedPalaceGenerator.cs b/RandomizerCore/Sidescroll/ReconstructedPalaceGenerator.cs index bcb4bc77..b208bf5a 100644 --- a/RandomizerCore/Sidescroll/ReconstructedPalaceGenerator.cs +++ b/RandomizerCore/Sidescroll/ReconstructedPalaceGenerator.cs @@ -233,7 +233,7 @@ internal override async Task GeneratePalace(RandomizerProperties props, palace.ResetRooms(); count++; palace.ShuffleRooms(r); - reachable = palace.AllReachable(); + reachable = AllReachable(palace); tries++; logger.Debug("Palace room shuffle attempt #" + tries); } @@ -343,6 +343,11 @@ public virtual void Consolidate(List openRooms, RandomizerProperties props } } + public virtual bool AllReachable(Palace palace) + { + return palace.AllReachable(allowBossEnterLeft: false); + } + /// /// Attach the provided room to the open room if there is a compatable pair of exits between the two rooms. /// Rooms attempt to use the exits in the following order (from the perspective of open):