Skip to content

Commit

Permalink
Focus.Quests: Add support for CatchShadowsQuest (issue #313)
Browse files Browse the repository at this point in the history
When this quest is selected, it will move to the `Phenac City Battles`
dungeon.

The automation catch filter will target shadow pokémons.

---

Improvements:
- Lists of types are now sorted alphabetically (this includes `Focus on
Gems` list and `Hatchery` pokémon type filter)

---

Bugfixes:
- The Dungeon quests would not behave as expected if the user enabled
the `Skip the boss fight` setting.
- Disabling any focus mode while a dungeon was in progress would exit
the dungeon instance and brake the game state
  • Loading branch information
Farigh committed Nov 4, 2023
2 parents 6ea2612 + 7cddf99 commit a210c42
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 39 deletions.
6 changes: 6 additions & 0 deletions src/lib/BattleFrontier.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@
*/
static ForceStop()
{
if (App.game.gameState != GameConstants.GameState.battleFrontier)
{
// Nothing to do
return;
}

// Stop the automation
this.__internal__toggleBattleFrontierFight(false);

Expand Down
32 changes: 23 additions & 9 deletions src/lib/Dungeon.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ class AutomationDungeon

static InternalModes = {
None: 0,
StopAfterThisRun: 1,
ForceDungeonCompletion: 2,
ForcePokemonFight: 3
ForceDungeonCompletion: 1,
ForcePokemonFight: 2
};

static AutomationRequestedMode = this.InternalModes.None;
Expand Down Expand Up @@ -48,10 +47,20 @@ class AutomationDungeon
}
}

/**
* @brief Asks the Dungeon automation to stop after the current run
*/
static stopAfterThisRun()
{
this.__internal__stopAfterThisRun = true;
}

/*********************************************************************\
|*** Internal members, should never be used by other classes ***|
\*********************************************************************/

static __internal__stopAfterThisRun = false;

static __internal__CatchModes = {
Uncaught: { id: 0, shiny: false, shadow: false },
UncaughtShiny: { id: 1, shiny: true, shadow: false },
Expand Down Expand Up @@ -256,9 +265,10 @@ class AutomationDungeon
}

// Cleanup StopAfterThisRun internal mode that was set while the dungeon was not running
if (this.AutomationRequestedMode == this.InternalModes.StopAfterThisRun)
if (this.__internal__stopAfterThisRun)
{
this.AutomationRequestedMode = this.InternalModes.None;
this.__internal__stopAfterThisRun = false;
}

if (enable)
Expand All @@ -280,6 +290,10 @@ class AutomationDungeon
clearInterval(this.__internal__autoDungeonLoop);
this.__internal__autoDungeonLoop = null;

// Reset any automation mode
this.AutomationRequestedMode = this.InternalModes.None;
this.__internal__stopAfterThisRun = false;

// Disable automation filter
Automation.Utils.Pokeball.disableAutomationFilter();
}
Expand Down Expand Up @@ -326,11 +340,11 @@ class AutomationDungeon
// Reset button status if either:
// - it was requested by another module
// - the pokedex is full for this dungeon, and it has been ask for
if (!forceDungeonProcessing
&& ((this.AutomationRequestedMode == this.InternalModes.StopAfterThisRun)
|| this.__internal__playerActionOccured
|| ((Automation.Utils.LocalStorage.getValue(this.Settings.StopOnPokedex) === "true")
&& this.__internal__isDungeonCompleted())))
if (this.__internal__stopAfterThisRun
|| (!forceDungeonProcessing
&& (this.__internal__playerActionOccured
|| ((Automation.Utils.LocalStorage.getValue(this.Settings.StopOnPokedex) === "true")
&& this.__internal__isDungeonCompleted()))))
{
if (this.__internal__playerActionOccured)
{
Expand Down
10 changes: 7 additions & 3 deletions src/lib/Focus.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class AutomationFocus
// Ask the dungeon auto-fight to stop, if the feature is enabled
if (Automation.Utils.LocalStorage.getValue(Automation.Dungeon.Settings.FeatureEnabled) === "true")
{
Automation.Dungeon.AutomationRequestedMode = Automation.Dungeon.InternalModes.StopAfterThisRun;
Automation.Dungeon.stopAfterThisRun();
return false;
}

Expand Down Expand Up @@ -399,7 +399,7 @@ class AutomationFocus
if (this.__internal__activeFocus.stop !== undefined)
{
// Reset any dungeon request that might have occured
Automation.Dungeon.AutomationRequestedMode = Automation.Dungeon.InternalModes.StopAfterThisRun;
Automation.Dungeon.stopAfterThisRun();

this.__internal__activeFocus.stop();
}
Expand Down Expand Up @@ -485,7 +485,11 @@ class AutomationFocus
const isUnlockedCallback = function (){ return App.game.gems.canAccess(); };
this.__internal__addFunctionalitySeparator("==== Gems ====", isUnlockedCallback);

for (const gemType of Array(Gems.nTypes).keys())
// Sort the types alphabetically
const gemListCopy = [...Array(Gems.nTypes).keys()];
gemListCopy.sort((a, b) => (PokemonType[a] < PokemonType[b]) ? -1 : 1);

for (const gemType of gemListCopy)
{
const gemTypeName = PokemonType[gemType];

Expand Down
12 changes: 6 additions & 6 deletions src/lib/Focus/Achievements.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class AutomationFocusAchievements
clearInterval(this.__internal__achievementLoop);
this.__internal__achievementLoop = null;

Automation.Dungeon.AutomationRequestedMode = Automation.Dungeon.InternalModes.StopAfterThisRun;
Automation.Dungeon.stopAfterThisRun();

Automation.Menu.forceAutomationState(Automation.Gym.Settings.FeatureEnabled, false);

Expand Down Expand Up @@ -163,12 +163,12 @@ class AutomationFocusAchievements

if (Automation.Utils.isInstanceOf(this.__internal__currentAchievement.property, "RouteKillRequirement"))
{
Automation.Dungeon.AutomationRequestedMode = Automation.Dungeon.InternalModes.StopAfterThisRun;
Automation.Dungeon.stopAfterThisRun();
this.__internal__workOnRouteKillRequirement();
}
else if (Automation.Utils.isInstanceOf(this.__internal__currentAchievement.property, "ClearGymRequirement"))
{
Automation.Dungeon.AutomationRequestedMode = Automation.Dungeon.InternalModes.StopAfterThisRun;
Automation.Dungeon.stopAfterThisRun();
this.__internal__workOnClearGymRequirement();
}
else if (Automation.Utils.isInstanceOf(this.__internal__currentAchievement.property, "ClearDungeonRequirement"))
Expand Down Expand Up @@ -258,11 +258,11 @@ class AutomationFocusAchievements
return;
}

// Bypass user settings like the stop on pokedex one
Automation.Dungeon.AutomationRequestedMode = Automation.Dungeon.InternalModes.ForceDungeonCompletion;

// Enable auto dungeon fight
Automation.Menu.forceAutomationState(Automation.Dungeon.Settings.FeatureEnabled, true);

// Bypass user settings like the stop on pokedex one
Automation.Dungeon.AutomationRequestedMode = Automation.Dungeon.InternalModes.ForceDungeonCompletion;
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/lib/Focus/PokerusCure.js
Original file line number Diff line number Diff line change
Expand Up @@ -226,11 +226,11 @@ class AutomationFocusPokerusCure
return;
}

// Bypass user settings, especially the 'Skip fights' one
Automation.Dungeon.AutomationRequestedMode = Automation.Dungeon.InternalModes.ForcePokemonFight;

// Enable auto dungeon fight
Automation.Menu.forceAutomationState(Automation.Dungeon.Settings.FeatureEnabled, true);

// Bypass user settings, especially the 'Skip fights' one
Automation.Dungeon.AutomationRequestedMode = Automation.Dungeon.InternalModes.ForcePokemonFight;
}
}

Expand Down
54 changes: 37 additions & 17 deletions src/lib/Focus/Quests.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ class AutomationFocusQuests
this.__internal__questLabels["MineLayersQuest"] = "Mine <n> layer in the Underground.";
this.__internal__questLabels["MineItemsQuest"] = "Mine <n> item in the Underground.";
this.__internal__questLabels["CatchShiniesQuest"] = "Catch 1 shiny Pokémon.";
this.__internal__questLabels["CatchShadowsQuest"] = "Catch <n> Shadow Pokémon.";
this.__internal__questLabels["DefeatGymQuest"] = "Defeat <Gym leader> <n> times.";
this.__internal__questLabels["DefeatDungeonQuest"] = "Defeat the <Dungeon> <n> times.";
this.__internal__questLabels["UsePokeballQuest"] = "Use <n> <Balls type>.";
Expand Down Expand Up @@ -217,7 +218,7 @@ class AutomationFocusQuests

// Reset demands
Automation.Farm.ForcePlantBerriesAsked = null;
Automation.Dungeon.AutomationRequestedMode = Automation.Dungeon.InternalModes.StopAfterThisRun;
Automation.Dungeon.stopAfterThisRun();

// Reset other modes status
Automation.Click.toggleAutoClick();
Expand Down Expand Up @@ -251,9 +252,6 @@ class AutomationFocusQuests
*/
static __internal__questLoop()
{
// Make sure to always have some balls to catch pokemons
this.__internal__tryBuyBallIfUnderThreshold(Automation.Focus.__pokeballToUseSelectElem.value, 10);

this.__internal__claimCompletedQuests();
this.__internal__selectNewQuests();

Expand Down Expand Up @@ -371,7 +369,7 @@ class AutomationFocusQuests
// Already fighting, nothing to do for now
if ((App.game.gameState != GameConstants.GameState.battleFrontier) && Automation.Utils.isInInstanceState())
{
Automation.Dungeon.AutomationRequestedMode = Automation.Dungeon.InternalModes.StopAfterThisRun;
Automation.Dungeon.stopAfterThisRun();
return;
}

Expand Down Expand Up @@ -414,9 +412,16 @@ class AutomationFocusQuests
{
this.__internal__workOnCapturePokemonTypesQuest(quest);
}
else if (Automation.Utils.isInstanceOf(quest, "CatchShadowsQuest"))
{
// Use the 1st available dungeon with shadow pokémons for now
const dungeonName = "Phenac City Battles";
this.__internal__workOnDefeatDungeonQuest(dungeonName, true);
}
else if (Automation.Utils.isInstanceOf(quest, "DefeatDungeonQuest"))
{
this.__internal__workOnDefeatDungeonQuest(quest);
const catchShadows = currentQuests.some((quest) => Automation.Utils.isInstanceOf(quest, "CatchShadowsQuest"));
this.__internal__workOnDefeatDungeonQuest(quest.dungeon, catchShadows);
}
else if (Automation.Utils.isInstanceOf(quest, "DefeatGymQuest"))
{
Expand Down Expand Up @@ -512,7 +517,7 @@ class AutomationFocusQuests
/**
* @brief Works on a CapturePokemonTypesQuest.
*
* It will equip balls to catch already caught pokemons
* It will equip balls to catch any pokemon matching the quest type
* and move to the most efficient route for the quest requested pokemon type.
*
* @param quest: The game's quest object
Expand Down Expand Up @@ -572,34 +577,45 @@ class AutomationFocusQuests
*
* @see AutomationDungeon class
*
* @param quest: The game's quest object
* @param {string} dungeonName: The name of the dungeon to defeat
* @param {boolean} catchShadows: Set to true if the shadow pokémon catch filter needs to be set, false otherwise
*/
static __internal__workOnDefeatDungeonQuest(quest)
static __internal__workOnDefeatDungeonQuest(dungeonName, catchShadows)
{
// If we don't have enough tokens, go farm some
if (TownList[quest.dungeon].dungeon.tokenCost > App.game.wallet.currencies[Currency.dungeonToken]())
if (TownList[dungeonName].dungeon.tokenCost > App.game.wallet.currencies[Currency.dungeonToken]())
{
this.__internal__workOnUsePokeballQuest(Automation.Focus.__pokeballToUseSelectElem.value);
return;
}

// Restore pokéball filters
Automation.Utils.Pokeball.disableAutomationFilter();
if (catchShadows)
{
this.__internal__equipOptimizedLoadout(Automation.Utils.OakItem.Setup.PokemonCatch);
Automation.Utils.Pokeball.restrictCaptureToShadow(true);
Automation.Utils.Pokeball.enableAutomationFilter();
}
else
{
// Disable pokéball filters
Automation.Utils.Pokeball.disableAutomationFilter();
}

// Move to dungeon if needed
if (!Automation.Utils.Route.isPlayerInTown(quest.dungeon))
if (!Automation.Utils.Route.isPlayerInTown(dungeonName))
{
Automation.Utils.Route.moveToTown(quest.dungeon);
Automation.Utils.Route.moveToTown(dungeonName);

// Let a tick for the menu to show up
return;
}

// Bypass user settings like the stop on pokedex one
Automation.Dungeon.AutomationRequestedMode = Automation.Dungeon.InternalModes.ForceDungeonCompletion;

// Enable auto dungeon fight
Automation.Menu.forceAutomationState(Automation.Dungeon.Settings.FeatureEnabled, true);

// Bypass user settings like the stop on pokedex one
Automation.Dungeon.AutomationRequestedMode = (catchShadows) ? Automation.Dungeon.InternalModes.ForcePokemonFight
: Automation.Dungeon.InternalModes.ForceDungeonCompletion;
}

/**
Expand Down Expand Up @@ -888,6 +904,10 @@ class AutomationFocusQuests
if (Automation.Utils.isInstanceOf(a, "DefeatDungeonQuest")) return -1;
if (Automation.Utils.isInstanceOf(b, "DefeatDungeonQuest")) return 1;

// Select catch shadow quest after the DefeatDungeonQuest in case said dungeon has shadow pokémons
if (Automation.Utils.isInstanceOf(a, "CatchShadowsQuest")) return -1;
if (Automation.Utils.isInstanceOf(b, "CatchShadowsQuest")) return 1;

if (Automation.Utils.isInstanceOf(a, "DefeatGymQuest")) return -1;
if (Automation.Utils.isInstanceOf(b, "DefeatGymQuest")) return 1;

Expand Down
6 changes: 5 additions & 1 deletion src/lib/Hatchery.js
Original file line number Diff line number Diff line change
Expand Up @@ -334,8 +334,12 @@ class AutomationHatchery

let previouslySelectedType = Automation.Utils.LocalStorage.getValue(this.Settings.PrioritizedType);

// Sort the types alphabetically
const gemListCopy = [...Array(Gems.nTypes).keys()];
gemListCopy.sort((a, b) => (PokemonType[a] < PokemonType[b]) ? -1 : 1);

// Populate the list
for (const gemType of Array(Gems.nTypes).keys())
for (const gemType of gemListCopy)
{
opt = document.createElement("option");

Expand Down
18 changes: 18 additions & 0 deletions src/lib/Utils/Pokeball.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ class AutomationUtilsPokeball
this.enableAutomationFilter();
}

/**
* @brief Restricts the pokemon filter to Shadow pokémons
*
* @param includeAlreadyCaught: Already caught shadow pokémons will be considered as well
*/
static restrictCaptureToShadow(includeAlreadyCaught)
{
// Only consider Shadow pokémons
this.__internal__automationFilter.options.shadow = pokeballFilterOptions.shadow.createSetting();

// Filter caught shadow
if (!includeAlreadyCaught)
{
this.__internal__automationFilter.options.caughtShadow = pokeballFilterOptions.caughtShadow.createSetting();
this.__internal__automationFilter.options.caughtShadow.observableValue(false);
}
}

/**
* @brief Restricts the pokemon filter to the given @p pokemonType
*
Expand Down

0 comments on commit a210c42

Please sign in to comment.