diff --git a/SysBot.Pokemon/Actions/PokeRoutineType.cs b/SysBot.Pokemon/Actions/PokeRoutineType.cs
index 76873a6..9ed53d3 100644
--- a/SysBot.Pokemon/Actions/PokeRoutineType.cs
+++ b/SysBot.Pokemon/Actions/PokeRoutineType.cs
@@ -75,6 +75,9 @@ public enum PokeRoutineType
/// Retrieves Diancie until the criteria is satisfied.
EncounterDiancie = 7_006,
+ /// Retrieves Volcanion until the criteria is satisfied.
+ EncounterVolcanion = 7_007,
+
/// Keeps running circles until all party members have the Partner mark.
PartnerMark = 7_050,
diff --git a/SysBot.Pokemon/LZA/BotEncounter/EncounterBotVolcanionLZA.cs b/SysBot.Pokemon/LZA/BotEncounter/EncounterBotVolcanionLZA.cs
new file mode 100644
index 0000000..c65fd73
--- /dev/null
+++ b/SysBot.Pokemon/LZA/BotEncounter/EncounterBotVolcanionLZA.cs
@@ -0,0 +1,108 @@
+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 EncounterBotVolcanionLZA(PokeBotState cfg, PokeTradeHub hub) : EncounterBotLZA(cfg, hub)
+{
+ private readonly ushort _volcanion = (ushort)Species.Volcanion;
+
+ protected override async Task EncounterLoop(SAV9ZA sav, CancellationToken token)
+ {
+ const int maxDurationSeconds = 45 + 100;
+
+ while (!token.IsCancellationRequested)
+ {
+ var later = DateTime.Now.AddSeconds(maxDurationSeconds);
+ await EnableAlwaysCatch(token).ConfigureAwait(false);
+
+ Log("Starting Volcanion encounter sequence");
+ await SetStick(LEFT, 0, 30_000, 0_500, token).ConfigureAwait(false);
+ await ResetStick(token).ConfigureAwait(false);
+ await RepeatClick(A, 100 * 1000, 100, token).ConfigureAwait(false); // 1 minute, 40 seconds
+
+ Log($"Catching Volcanion, wait till [{later}] before we force a game restart", false);
+
+ var result = EncounterResult.Unknown;
+ while (result != EncounterResult.VolcanionFound && 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);
+
+ result = await LookupSlot(token).ConfigureAwait(false);
+
+ if (result is EncounterResult.InvalidSpecies or EncounterResult.ResultFound)
+ return; // Exit the encounter loop entirely
+
+ if (result is EncounterResult.VolcanionFound)
+ {
+ Log("Volcanion found in B1S1, 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 LookupSlot(CancellationToken token)
+ {
+ var (pa9, raw) = await ReadRawBoxPokemon(0, 0, token).ConfigureAwait(false);
+ if (pa9.Species > 0 && pa9.Species != _volcanion)
+ {
+ Log($"Detected species {(Species)pa9.Species}, which shouldn't be possible. Only 'none' or 'Volcanion' are expected");
+ return EncounterResult.InvalidSpecies;
+ }
+
+ if (pa9.Species == _volcanion && pa9 is { Valid: true, 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 B1S1. Auto-save will do the rest!");
+
+ if (stop)
+ return EncounterResult.ResultFound;
+ }
+
+ return pa9.Species == _volcanion
+ ? EncounterResult.VolcanionFound
+ : 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,
+ VolcanionFound,
+ NextSlot
+ }
+}
diff --git a/SysBot.Pokemon/LZA/BotFactory9LZA.cs b/SysBot.Pokemon/LZA/BotFactory9LZA.cs
index fa8a03e..95fa6a3 100644
--- a/SysBot.Pokemon/LZA/BotFactory9LZA.cs
+++ b/SysBot.Pokemon/LZA/BotFactory9LZA.cs
@@ -9,6 +9,7 @@ public sealed class BotFactory9LZA : BotFactory
{
PokeRoutineType.EncounterDiancie => new EncounterBotDiancieLZA(cfg, hub),
PokeRoutineType.EncounterFloette => new EncounterBotFloetteLZA(cfg, hub),
+ PokeRoutineType.EncounterVolcanion => new EncounterBotVolcanionLZA(cfg, hub),
PokeRoutineType.EncounterOverworld => new EncounterBotOverworldScannerLZA(cfg, hub),
PokeRoutineType.FossilBot => new EncounterBotFossilLZA(cfg, hub),
@@ -21,6 +22,7 @@ public sealed class BotFactory9LZA : BotFactory
{
PokeRoutineType.EncounterDiancie => true,
PokeRoutineType.EncounterFloette => true,
+ PokeRoutineType.EncounterVolcanion => true,
PokeRoutineType.EncounterOverworld => true,
PokeRoutineType.FossilBot => true,