From 700929ed56ffbab78340dfe7d18b43dd829610b4 Mon Sep 17 00:00:00 2001 From: Eppin <96589679+Eppin@users.noreply.github.com> Date: Sat, 24 Jan 2026 15:55:46 +0100 Subject: [PATCH] Diancie routine Uses 100% catch cheat and also updated support for 2.0.1 --- SysBot.Base/Control/SwitchRoutineExecutor.cs | 14 ++- SysBot.Pokemon/Actions/PokeRoutineType.cs | 3 + .../BotEncounter/EncounterBotDiancieLZA.cs | 110 ++++++++++++++++++ .../LZA/BotEncounter/EncounterBotLZA.cs | 14 +++ SysBot.Pokemon/LZA/BotFactory9LZA.cs | 2 + .../LZA/Vision/PokeDataOffsetsLZA.cs | 12 +- SysBot.Pokemon/Settings/FolderSettings.cs | 6 +- 7 files changed, 151 insertions(+), 10 deletions(-) create mode 100644 SysBot.Pokemon/LZA/BotEncounter/EncounterBotDiancieLZA.cs diff --git a/SysBot.Base/Control/SwitchRoutineExecutor.cs b/SysBot.Base/Control/SwitchRoutineExecutor.cs index 36af1de2..5d0ac1fd 100644 --- a/SysBot.Base/Control/SwitchRoutineExecutor.cs +++ b/SysBot.Base/Control/SwitchRoutineExecutor.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; @@ -35,6 +35,18 @@ public async Task PressAndHold(SwitchButton b, int hold, int delay, Cancellation await Task.Delay(delay, token).ConfigureAwait(false); } + public async Task PressAndHold(SwitchButton b, int delay, CancellationToken token) + { + await Connection.SendAsync(SwitchCommand.Hold(b, UseCRLF), token).ConfigureAwait(false); + await Task.Delay(delay, token).ConfigureAwait(false); + } + + public async Task ReleaseHold(SwitchButton b, int delay, CancellationToken token) + { + await Connection.SendAsync(SwitchCommand.Release(b, UseCRLF), token).ConfigureAwait(false); + await Task.Delay(delay, token).ConfigureAwait(false); + } + public async Task DaisyChainCommands(int delay, IEnumerable buttons, CancellationToken token) { SwitchCommand.Configure(SwitchConfigureParameter.mainLoopSleepTime, delay, UseCRLF); diff --git a/SysBot.Pokemon/Actions/PokeRoutineType.cs b/SysBot.Pokemon/Actions/PokeRoutineType.cs index 9033f3b7..76873a65 100644 --- a/SysBot.Pokemon/Actions/PokeRoutineType.cs +++ b/SysBot.Pokemon/Actions/PokeRoutineType.cs @@ -72,6 +72,9 @@ public enum PokeRoutineType /// Retrieves Floette until the criteria is satisfied. EncounterFloette = 7_005, + /// Retrieves Diancie until the criteria is satisfied. + EncounterDiancie = 7_006, + /// Keeps running circles until all party members have the Partner mark. PartnerMark = 7_050, diff --git a/SysBot.Pokemon/LZA/BotEncounter/EncounterBotDiancieLZA.cs b/SysBot.Pokemon/LZA/BotEncounter/EncounterBotDiancieLZA.cs new file mode 100644 index 00000000..7c311303 --- /dev/null +++ b/SysBot.Pokemon/LZA/BotEncounter/EncounterBotDiancieLZA.cs @@ -0,0 +1,110 @@ +namespace SysBot.Pokemon; + +using PKHeX.Core; +using SysBot.Base; +using System; +using System.Threading; +using System.Threading.Tasks; +using static Base.SwitchButton; +using static Base.SwitchStick; + +public class EncounterBotDiancieLZA(PokeBotState cfg, PokeTradeHub hub) : EncounterBotLZA(cfg, hub) +{ + private readonly ushort _diancie = (ushort)Species.Diancie; + private readonly ushort _carbink = (ushort)Species.Carbink; + + protected override async Task EncounterLoop(SAV9ZA sav, CancellationToken token) + { + while (!token.IsCancellationRequested) + { + await EnableAlwaysCatch(token).ConfigureAwait(false); + + Log("Starting Diancie encounter sequence"); + await SetStick(LEFT, 0, 30_000, 0_500, token).ConfigureAwait(false); + await ResetStick(token).ConfigureAwait(false); + await RepeatClick(A, 20_000, 100, token).ConfigureAwait(false); + + Log("Catching Diancie"); + + var result = EncounterResult.Unknown; + while (result != EncounterResult.DiancieFound) + { + await PressAndHold(ZL, 0_250, token).ConfigureAwait(false); + await Click(ZR, 0_100, token).ConfigureAwait(false); + await ReleaseHold(ZL, 0_250, token).ConfigureAwait(false); + + for (int slot = 0; slot < 3; slot++) + { + result = await LookupSlot(slot, token).ConfigureAwait(false); + + if (result is EncounterResult.InvalidSpecies or EncounterResult.ResultFound) + { + return; // Exit the encounter loop entirely + } + + if (result is EncounterResult.DiancieFound) + { + Log($"Diancie found in B1S{slot + 1}, rebooting the game..."); + break; + } + } + } + + await ReOpenGame(Hub.Config, token).ConfigureAwait(false); + await Task.Delay(10_000, token).ConfigureAwait(false); + } + } + + public override async Task HardStop() + { + await ReleaseHold(ZL, 0_500, CancellationToken.None).ConfigureAwait(false); + await base.HardStop().ConfigureAwait(false); + } + + private async Task LookupSlot(int slot, CancellationToken token) + { + (var pa9, var raw) = await ReadRawBoxPokemon(0, slot, token).ConfigureAwait(false); + if (pa9.Species > 0 && pa9.Species != _diancie && pa9.Species != _carbink) + { + Log($"Detected species {(Species)pa9.Species}, which shouldn't be possible. Only 'none', 'Carbink' or 'Diancie' are expected"); + return EncounterResult.InvalidSpecies; + } + + if (pa9.Species == _diancie && pa9.Valid && pa9.EncryptionConstant > 0) + { + var (stop, success) = await HandleEncounter(pa9, token, raw, true).ConfigureAwait(false); + + if (success) + { + Log($"Your Pokémon has been received and placed in B1S{slot + 1}. Auto-save will do the rest!"); + } + + if (stop) + return EncounterResult.ResultFound; + } + + return pa9.Species == _diancie + ? EncounterResult.DiancieFound + : EncounterResult.NextSlot; + } + + private async Task RepeatClick(SwitchButton button, int duration, int delay, CancellationToken token) + { + var endTime = DateTime.Now.AddMilliseconds(duration); + + while (DateTime.Now < endTime) + { + await Click(button, delay, token).ConfigureAwait(false); + } + } + + private enum EncounterResult + { + Unknown, + + InvalidSpecies, + ResultFound, + DiancieFound, + NextSlot + } +} diff --git a/SysBot.Pokemon/LZA/BotEncounter/EncounterBotLZA.cs b/SysBot.Pokemon/LZA/BotEncounter/EncounterBotLZA.cs index 0cce5bb0..5ec8d5bd 100644 --- a/SysBot.Pokemon/LZA/BotEncounter/EncounterBotLZA.cs +++ b/SysBot.Pokemon/LZA/BotEncounter/EncounterBotLZA.cs @@ -190,4 +190,18 @@ protected async Task ResetStick(CancellationToken token) // If aborting the sequence, we might have the stick set at some position. Clear it just in case. await SetStick(LEFT, 0, 0, 0_500, token).ConfigureAwait(false); // reset } + + protected async Task EnableAlwaysCatch(CancellationToken token) + { + Log("Enable 100% catch rate cheat", false); + // Source: https://gbatemp.net/threads/pokemon-legends-z-a-cheat-database.675579/ + + // Original cheat: + /* + * [№56. 100% Catch Rate] + * 040A0000 00447448 52800028 + */ + + await SwitchConnection.WriteBytesMainAsync(BitConverter.GetBytes(0x52800028), 0x00447448, token); + } } diff --git a/SysBot.Pokemon/LZA/BotFactory9LZA.cs b/SysBot.Pokemon/LZA/BotFactory9LZA.cs index b5940e4f..fa8a03e3 100644 --- a/SysBot.Pokemon/LZA/BotFactory9LZA.cs +++ b/SysBot.Pokemon/LZA/BotFactory9LZA.cs @@ -7,6 +7,7 @@ public sealed class BotFactory9LZA : BotFactory { public override PokeRoutineExecutorBase CreateBot(PokeTradeHub hub, PokeBotState cfg) => cfg.NextRoutineType switch { + PokeRoutineType.EncounterDiancie => new EncounterBotDiancieLZA(cfg, hub), PokeRoutineType.EncounterFloette => new EncounterBotFloetteLZA(cfg, hub), PokeRoutineType.EncounterOverworld => new EncounterBotOverworldScannerLZA(cfg, hub), PokeRoutineType.FossilBot => new EncounterBotFossilLZA(cfg, hub), @@ -18,6 +19,7 @@ public sealed class BotFactory9LZA : BotFactory public override bool SupportsRoutine(PokeRoutineType type) => type switch { + PokeRoutineType.EncounterDiancie => true, PokeRoutineType.EncounterFloette => true, PokeRoutineType.EncounterOverworld => true, PokeRoutineType.FossilBot => true, diff --git a/SysBot.Pokemon/LZA/Vision/PokeDataOffsetsLZA.cs b/SysBot.Pokemon/LZA/Vision/PokeDataOffsetsLZA.cs index 3561deae..f73227e3 100644 --- a/SysBot.Pokemon/LZA/Vision/PokeDataOffsetsLZA.cs +++ b/SysBot.Pokemon/LZA/Vision/PokeDataOffsetsLZA.cs @@ -7,14 +7,14 @@ namespace SysBot.Pokemon; /// public class PokeDataOffsetsLZA { - public const string ZAGameVersion = "2.0.0"; + public const string ZAGameVersion = "2.0.1"; public const string LegendsZAID = "0100F43008C44000"; - public IReadOnlyList BoxStartPokemonPointer { get; } = [0x6105710, 0xB0, 0x978, 0x0]; - public IReadOnlyList MyStatusPointer { get; } = [0x6105710, 0x80, 0x100]; - public IReadOnlyList KItemPointer { get; } = [0x6105670, 0x30, 0x08, 0x460]; - public IReadOnlyList KOverworldPointer { get; } = [0x6105670, 0x30, 0x08, 0x9E0]; - public IReadOnlyList KStoredShinyEntityPointer { get; } = [0x6105670, 0x30, 0x08, 0x1660]; + public IReadOnlyList BoxStartPokemonPointer { get; } = [0x610A710, 0xB0, 0x978, 0x0]; + public IReadOnlyList MyStatusPointer { get; } = [0x610A710, 0x80, 0x100]; + public IReadOnlyList KItemPointer { get; } = [0x610A670, 0x30, 0x08, 0x480]; + public IReadOnlyList KOverworldPointer { get; } = [0x610A670, 0x30, 0x08, 0xA00]; + public IReadOnlyList KStoredShinyEntityPointer { get; } = [0x610A670, 0x30, 0x08, 0x1680]; public const uint KItemKey = 0x21C9BD44; public const uint KOverworldKey = 0x5E8E1711; diff --git a/SysBot.Pokemon/Settings/FolderSettings.cs b/SysBot.Pokemon/Settings/FolderSettings.cs index c38100dc..2391224c 100644 --- a/SysBot.Pokemon/Settings/FolderSettings.cs +++ b/SysBot.Pokemon/Settings/FolderSettings.cs @@ -1,4 +1,4 @@ -using System.ComponentModel; +using System.ComponentModel; using System.IO; namespace SysBot.Pokemon; @@ -10,10 +10,10 @@ public class FolderSettings : IDumper public override string ToString() => "Folder / Dumping Settings"; [Category(FeatureToggle), Description("When enabled, dumps any received PKM files (trade results) to the DumpFolder.")] - public bool Dump { get; set; } + public bool Dump { get; set; } = false; [Category(FeatureToggle), Description("When enabled, dumps any raw/encrypted received PKM files (trade results) to the DumpFolder.")] - public bool DumpRaw { get; set; } + public bool DumpRaw { get; set; } = true; [Category(FeatureToggle), Description("When enabled with previous option, dumps only any received Shiny PKM files (trade results) to the DumpFolder.")] public bool DumpShinyOnly { get; set; }