Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions Documentation/15.-Catch-Diancie-Legends-Z-A.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Legends: Z-A: Catch Diancie

This guide covers catching Diancie until a `StopCondition` is met (e.g., shiny, specific IVs, etc.).

## Prerequisites

- Have access to Diancie encounter
- Position yourself in front of Diancie

## Setup

1. Stand directly in front of Diancie
2. **Important**: If you start the routine and the battle doesn't begin, take one more step forward
3. Once positioned correctly, **save your game**
4. Start the bot

## Catch Rate Cheat

- If you select **Master Ball**: No cheat needed (100% catch rate)
- If you select **any other ball**: Enable the 100% catch rate cheat

## How It Works

The bot will:
1. Initiate the battle with Diancie
2. Attempt to catch Diancie with your selected ball
3. Check if the caught Diancie matches your stop conditions
4. If not, reset and repeat the process

## Tips

- Make sure you're positioned correctly before starting (battle should start immediately when routine begins)
- Use stop conditions to target specific Diancie (shiny, nature, IVs, etc.)
- Master Ball guarantees a catch without needing cheats
3 changes: 2 additions & 1 deletion Documentation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,5 @@ Welcome to the Sysbot.NET wiki!
11. [Bench Sitting](11.-Bench-Sitting-Legends-Z-A.md)
12. [Wild Zone Entrance](12.-Wild-Zone-Entrance-Legends-Z-A.md)
13. [Fossil reviving](13.-Fossil-Revive-Legends-Z-A.md)
14. [Receiving Floette](14.-Receive-Floette-Legends-Z-A.md)
14. [Receiving Floette](14.-Receive-Floette-Legends-Z-A.md)
15. [Catch Diancie](15.-Catch-Diancie-Legends-Z-A.md)
14 changes: 13 additions & 1 deletion SysBot.Base/Control/SwitchRoutineExecutor.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.Collections.Generic;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -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<SwitchButton> buttons, CancellationToken token)
{
SwitchCommand.Configure(SwitchConfigureParameter.mainLoopSleepTime, delay, UseCRLF);
Expand Down
3 changes: 3 additions & 0 deletions SysBot.Pokemon/Actions/PokeRoutineType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ public enum PokeRoutineType
/// <summary> Retrieves Floette until the criteria is satisfied. </summary>
EncounterFloette = 7_005,

/// <summary> Retrieves Diancie until the criteria is satisfied. </summary>
EncounterDiancie = 7_006,

/// <summary> Keeps running circles until all party members have the Partner mark. </summary>
PartnerMark = 7_050,

Expand Down
113 changes: 113 additions & 0 deletions SysBot.Pokemon/LZA/BotEncounter/EncounterBotDiancieLZA.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
namespace SysBot.Pokemon;

using System;
using System.Threading;
using System.Threading.Tasks;
using Base;
using PKHeX.Core;
using static Base.SwitchButton;
using static Base.SwitchStick;

public class EncounterBotDiancieLZA(PokeBotState cfg, PokeTradeHub<PA9> 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)
{
const int maxDurationSeconds = 45;

while (!token.IsCancellationRequested)
{
var later = DateTime.Now.AddSeconds(maxDurationSeconds);
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, wait till [{later}] before we force a game restart", false);

var result = EncounterResult.Unknown;
while (result != EncounterResult.DiancieFound && DateTime.Now <= later)
{
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 (var 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;
}
}
}

if (DateTime.Now >= later)
Log("Force restart of the game...");

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<EncounterResult> 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
}
}
17 changes: 17 additions & 0 deletions SysBot.Pokemon/LZA/BotEncounter/EncounterBotLZA.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,4 +190,21 @@ 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)
{
if (!Hub.Config.EncounterLZA.EnableCatchCheat)
return;

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);
}
}
3 changes: 3 additions & 0 deletions SysBot.Pokemon/LZA/BotEncounter/EncounterSettingsLZA.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ public class OverworldEncounterLZA
[Category(Encounter), Description("When enabled, the bot will only stop when encounter has a Scale of XXXS or XXXL.")]
public bool MinMaxScaleOnly { get; set; } = false;

[Category(Encounter), Description("When enabled, the 100% catch cheat will be enabled.")]
public bool EnableCatchCheat { get; set; }

[Category(Encounter), Description("When enabled, the screen will be turned off during normal bot loop operation to save power.")]
public bool ScreenOff { get; set; }

Expand Down
2 changes: 2 additions & 0 deletions SysBot.Pokemon/LZA/BotFactory9LZA.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public sealed class BotFactory9LZA : BotFactory<PA9>
{
public override PokeRoutineExecutorBase CreateBot(PokeTradeHub<PA9> 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),
Expand All @@ -18,6 +19,7 @@ public sealed class BotFactory9LZA : BotFactory<PA9>

public override bool SupportsRoutine(PokeRoutineType type) => type switch
{
PokeRoutineType.EncounterDiancie => true,
PokeRoutineType.EncounterFloette => true,
PokeRoutineType.EncounterOverworld => true,
PokeRoutineType.FossilBot => true,
Expand Down
12 changes: 6 additions & 6 deletions SysBot.Pokemon/LZA/Vision/PokeDataOffsetsLZA.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ namespace SysBot.Pokemon;
/// </summary>
public class PokeDataOffsetsLZA
{
public const string ZAGameVersion = "2.0.0";
public const string ZAGameVersion = "2.0.1";
public const string LegendsZAID = "0100F43008C44000";

public IReadOnlyList<long> BoxStartPokemonPointer { get; } = [0x6105710, 0xB0, 0x978, 0x0];
public IReadOnlyList<long> MyStatusPointer { get; } = [0x6105710, 0x80, 0x100];
public IReadOnlyList<long> KItemPointer { get; } = [0x6105670, 0x30, 0x08, 0x460];
public IReadOnlyList<long> KOverworldPointer { get; } = [0x6105670, 0x30, 0x08, 0x9E0];
public IReadOnlyList<long> KStoredShinyEntityPointer { get; } = [0x6105670, 0x30, 0x08, 0x1660];
public IReadOnlyList<long> BoxStartPokemonPointer { get; } = [0x610A710, 0xB0, 0x978, 0x0];
public IReadOnlyList<long> MyStatusPointer { get; } = [0x610A710, 0x80, 0x100];
public IReadOnlyList<long> KItemPointer { get; } = [0x610A670, 0x30, 0x08, 0x480];
public IReadOnlyList<long> KOverworldPointer { get; } = [0x610A670, 0x30, 0x08, 0xA00];
public IReadOnlyList<long> KStoredShinyEntityPointer { get; } = [0x610A670, 0x30, 0x08, 0x1680];

public const uint KItemKey = 0x21C9BD44;
public const uint KOverworldKey = 0x5E8E1711;
Expand Down
6 changes: 3 additions & 3 deletions SysBot.Pokemon/Settings/FolderSettings.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using System.ComponentModel;
using System.ComponentModel;
using System.IO;

namespace SysBot.Pokemon;
Expand All @@ -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; }
Expand Down
Loading