From a63b417694e2a1235f45da7888c3441818d0bf97 Mon Sep 17 00:00:00 2001 From: RedstonewolfX <108761527+RedstonewolfX@users.noreply.github.com> Date: Thu, 1 Aug 2024 22:29:26 -0400 Subject: [PATCH] Catch predictions Successfully implemented a function to perform testing RNG rolls without affecting the battle Now displays the weakest Poke Ball that will still catch the Pokemon, if any Attempted to add a label for forms, but failed --- src/battle.ts | 22 ++++ src/modifier/modifier.ts | 2 +- src/phases.ts | 212 +++++++++++++++++++++++++++++---------- src/ui/arena-flyout.ts | 6 +- 4 files changed, 185 insertions(+), 57 deletions(-) diff --git a/src/battle.ts b/src/battle.ts index b6722d76d3a..9bfe5119c1b 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -356,6 +356,28 @@ export default class Battle { return null; } + multiInt(scene: BattleScene, out: integer[], count: integer, range: integer, min: integer = 0): integer { + if (range <= 1) { + return min; + } + const tempRngCounter = scene.rngCounter; + const tempSeedOverride = scene.rngSeedOverride; + const state = Phaser.Math.RND.state(); + if (this.battleSeedState) { + Phaser.Math.RND.state(this.battleSeedState); + } else { + Phaser.Math.RND.sow([ Utils.shiftCharCodes(this.battleSeed, this.turn << 6) ]); + console.log("Battle Seed:", this.battleSeed); + } + for (var i = 0; i < count; i++) { + out.push(Utils.randSeedInt(range, min)) + } + Phaser.Math.RND.state(state); + //scene.setScoreText("RNG: " + tempRngCounter + " (Last sim: " + this.rngCounter + ")") + scene.rngCounter = tempRngCounter; + scene.rngSeedOverride = tempSeedOverride; + } + randSeedInt(scene: BattleScene, range: integer, min: integer = 0): integer { if (range <= 1) { return min; diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index ee74a2b6760..130bf52bcae 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -1663,7 +1663,7 @@ export class EvolutionItemModifier extends ConsumablePokemonModifier { : null; if (!matchingEvolution && pokemon.isFusion()) { - var bypassC = EvolutionItem.SUPER_EVO_ITEM_F === (this.type as ModifierTypes.EvolutionItemModifierType).evolutionItem + var bypassC = EvolutionItem.SUPER_EVO_ITEM_FUSION === (this.type as ModifierTypes.EvolutionItemModifierType).evolutionItem matchingEvolution = pokemonEvolutions[pokemon.fusionSpecies.speciesId].find(e => (e.item === (this.type as ModifierTypes.EvolutionItemModifierType).evolutionItem || bypassC) && (e.evoFormKey === null || (e.preFormKey || "") === pokemon.getFusionFormKey()) && (!e.condition || e.condition.predicate(pokemon) || bypassC)); diff --git a/src/phases.ts b/src/phases.ts index 47059930a3f..5b00b077fd8 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -88,6 +88,132 @@ const { t } = i18next; //#region 01 Uncategorized +function catchCalc(pokemon: EnemyPokemon) { + const _3m = 3 * pokemon.getMaxHp(); + const _2h = 2 * pokemon.hp; + const catchRate = pokemon.species.catchRate; + const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1; + const rate1 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 1) / _3m) * statusMultiplier))))); + const rate2 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 1.5) / _3m) * statusMultiplier))))); + const rate3 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 2) / _3m) * statusMultiplier))))); + const rate4 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 3) / _3m) * statusMultiplier))))); + + var rates = [rate1, rate2, rate3, rate4] + var rates2 = rates.map(r => ((r/65536) ** 3)) + //console.log(rates2) + + return rates2 +} +function catchCalcRaw(pokemon: EnemyPokemon) { + const _3m = 3 * pokemon.getMaxHp(); + const _2h = 2 * pokemon.hp; + const catchRate = pokemon.species.catchRate; + const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1; + const rate1 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 1) / _3m) * statusMultiplier))))); + const rate2 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 1.5) / _3m) * statusMultiplier))))); + const rate3 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 2) / _3m) * statusMultiplier))))); + const rate4 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 3) / _3m) * statusMultiplier))))); + + var rates = [rate1, rate2, rate3, rate4] + var rates2 = rates.map(r => ((r/65536) ** 3)) + //console.log(rates2) + //console.log("output: ", rates) + + return rates +} + +/** + * Finds the best Poké Ball to catch a Pokemon with, and the % chance of capturing it. + * @param pokemon The Pokémon to get the catch rate for. + * @param override Show the best Poké Ball to use, even if you don't have any. + * @returns The name and % rate of the best Poké Ball. + */ +function findBest(scene: BattleScene, pokemon: EnemyPokemon, override?: boolean) { + var rates = catchCalc(pokemon) + var rates_raw = catchCalcRaw(pokemon) + var rolls = [] + var offset = 0 + scene.getModifiers(BypassSpeedChanceModifier, true).forEach(m => { + //console.log(m, m.getPokemon(this.scene), pokemon) + if (m.getPokemon(scene).isActive()) { + console.log(m.getPokemon(scene).name + " has a Quick Claw") + offset++ + } + }) + scene.currentBattle.multiInt(scene, rolls, 12, 65536) + console.log(rolls) + console.log(rolls.slice(offset, offset + 3)) + if (scene.pokeballCounts[0] == 0 && !override) rates[0] = 0 + if (scene.pokeballCounts[1] == 0 && !override) rates[1] = 0 + if (scene.pokeballCounts[2] == 0 && !override) rates[2] = 0 + if (scene.pokeballCounts[3] == 0 && !override) rates[3] = 0 + var rates2 = rates.slice() + rates2.sort(function(a, b) {return b - a}) + const ballNames = [ + "Poké Ball", + "Great Ball", + "Ultra Ball", + "Rogue Ball", + "Master Ball" + ] + var func_output = "" + rates_raw.forEach((v, i) => { + if (scene.pokeballCounts[i] == 0 && !override) + return; // Don't list success for Poke Balls we don't have + //console.log(ballNames[i]) + //console.log(v, rolls[offset + 0], v > rolls[offset + 0]) + //console.log(v, rolls[offset + 1], v > rolls[offset + 1]) + //console.log(v, rolls[offset + 2], v > rolls[offset + 2]) + if (v > rolls[offset + 0]) { + //console.log("1 roll") + if (v > rolls[offset + 1]) { + //console.log("2 roll") + if (v > rolls[offset + 2]) { + //console.log("Caught!") + if (func_output == "") { + func_output = ballNames[i] + " (CATCH)" + } + } + } + } + if (v > rolls[offset] && v > rolls[1 + offset] && v > rolls[2 + offset]) { + if (func_output == "") { + func_output = ballNames[i] + " (CATCH)" + } + } + }) + if (func_output != "") { + return func_output + } + var n = "" + switch (rates2[0]) { + case rates[0]: + // Poke Balls are best + n = "Poké Ball " + break; + case rates[1]: + // Great Balls are best + n = "Great Ball " + break; + case rates[2]: + // Ultra Balls are best + n = "Ultra Ball " + break; + case rates[3]: + // Rogue Balls are best + n = "Rogue Ball " + break; + default: + // Master Balls are the only thing that will work + if (scene.pokeballCounts[4] != 0 || override) { + return "Master Ball"; + } else { + return "No balls" + } + } + return n + " (FAIL)" + return n + Math.round(rates2[0] * 100) + "%"; +} export function parseSlotData(slotId: integer): SessionSaveData { var S = localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`) if (S == null) { @@ -2690,60 +2816,6 @@ export class TurnInitPhase extends FieldPhase { super(scene); } - catchCalc(pokemon: EnemyPokemon) { - const _3m = 3 * pokemon.getMaxHp(); - const _2h = 2 * pokemon.hp; - const catchRate = pokemon.species.catchRate; - const statusMultiplier = pokemon.status ? getStatusEffectCatchRateMultiplier(pokemon.status.effect) : 1; - const rate1 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 1) / _3m) * statusMultiplier))))); - const rate2 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 1.5) / _3m) * statusMultiplier))))); - const rate3 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 2) / _3m) * statusMultiplier))))); - const rate4 = Math.round(65536 / Math.sqrt(Math.sqrt(255 / (Math.round((((_3m - _2h) * catchRate * 3) / _3m) * statusMultiplier))))); - - var rates = [rate1, rate2, rate3, rate4] - var rates2 = rates.map(r => ((r/65536) ** 3)) - console.log(rates2) - - return rates2 - } - - /** - * Finds the best Poké Ball to catch a Pokemon with, and the % chance of capturing it. - * @param pokemon The Pokémon to get the catch rate for. - * @param override Show the best Poké Ball to use, even if you don't have any. - * @returns The name and % rate of the best Poké Ball. - */ - findBest(pokemon: EnemyPokemon, override?: boolean) { - var rates = this.catchCalc(pokemon) - if (this.scene.pokeballCounts[0] == 0 && !override) rates[0] = 0 - if (this.scene.pokeballCounts[1] == 0 && !override) rates[1] = 0 - if (this.scene.pokeballCounts[2] == 0 && !override) rates[2] = 0 - if (this.scene.pokeballCounts[3] == 0 && !override) rates[3] = 0 - var rates2 = rates.slice() - rates2.sort(function(a, b) {return b - a}) - switch (rates2[0]) { - case rates[0]: - // Poke Balls are best - return "Poké Ball " + Math.round(rates2[0] * 100) + "%"; - case rates[1]: - // Great Balls are best - return "Great Ball " + Math.round(rates2[0] * 100) + "%"; - case rates[2]: - // Ultra Balls are best - return "Ultra Ball " + Math.round(rates2[0] * 100) + "%"; - case rates[3]: - // Rogue Balls are best - return "Rogue Ball " + Math.round(rates2[0] * 100) + "%"; - default: - // Master Balls are the only thing that will work - if (this.scene.pokeballCounts[4] != 0 || override) { - return "Master Ball"; - } else { - return "No balls" - } - } - } - start() { super.start(); @@ -2856,9 +2928,13 @@ export class TurnInitPhase extends FieldPhase { var txt = ["Turn: " + this.scene.currentBattle.turn] if (!this.scene.getEnemyField()[0].hasTrainer()) { this.scene.getEnemyField().forEach((pk, i) => { - txt = txt.concat(this.findBest(pk)) + if (pk.isActive() && pk.hp > 0) + txt = txt.concat(findBest(this.scene, pk)) }) } + if (txt.length > 2) { + txt = ["Turn: " + this.scene.currentBattle.turn] + } this.scene.arenaFlyout.updateFieldText() @@ -3149,6 +3225,7 @@ export class EnemyCommandPhase extends FieldPhase { super.start(); const enemyPokemon = this.scene.getEnemyField()[this.fieldIndex]; + console.log(enemyPokemon.getMoveset().map(m => m.getName())) const battle = this.scene.currentBattle; @@ -3184,7 +3261,20 @@ export class EnemyCommandPhase extends FieldPhase { enemyPokemon.flyout.setText() + var txt = ["Turn: " + this.scene.currentBattle.turn] + if (!this.scene.getEnemyField()[0].hasTrainer()) { + this.scene.getEnemyField().forEach((pk, i) => { + if (pk.isActive() && pk.hp > 0) + txt = txt.concat(findBest(this.scene, pk)) + }) + } + if (txt.length > 2) { + txt = ["Turn: " + this.scene.currentBattle.turn] + } + this.scene.arenaFlyout.updateFieldText() + + this.scene.setScoreText(txt.join(" / ")) return this.end(); } @@ -3225,6 +3315,18 @@ export class EnemyCommandPhase extends FieldPhase { LoggerTools.enemyPlan[this.fieldIndex*2 + 1] = "→ " + nextMove.targets.map((m) => targetLabels[m + 1]) this.scene.arenaFlyout.updateFieldText() + var txt = ["Turn: " + this.scene.currentBattle.turn] + if (!this.scene.getEnemyField()[0].hasTrainer()) { + this.scene.getEnemyField().forEach((pk, i) => { + if (pk.isActive() && pk.hp > 0) + txt = txt.concat(findBest(this.scene, pk)) + }) + } + + this.scene.arenaFlyout.updateFieldText() + + this.scene.setScoreText(txt.join(" / ")) + this.end(); } } diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index ff55d8c6dfc..f3cdb228948 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -222,7 +222,11 @@ export class ArenaFlyout extends Phaser.GameObjects.Container { this.flyoutTextHeader.text = "IVs" for (var i = 0; i < poke.length; i++) { if (i == 1 || true) { - this.flyoutTextPlayer.text += poke[i].name + " " + (poke[i].gender == Gender.MALE ? "♂" : (poke[i].gender == Gender.FEMALE ? "♀" : "-")) + " " + poke[i].level + "\n" + var formtext = "" + if (poke[i].species.forms.length > 0) { + formtext = " (" + poke[i].species.forms[poke[i].formIndex].formName + ")" + } + this.flyoutTextPlayer.text += poke[i].name + formtext + " " + (poke[i].gender == Gender.MALE ? "♂" : (poke[i].gender == Gender.FEMALE ? "♀" : "-")) + " " + poke[i].level + "\n" this.flyoutTextEnemy.text += poke[i].getAbility().name + " / " + (poke[i].isBoss() ? poke[i].getPassiveAbility().name + " / " : "") + getNatureName(poke[i].nature) + (getNatureIncrease(poke[i].nature) != "" ? " (+" + getNatureIncrease(poke[i].nature) + " -" + getNatureDecrease(poke[i].nature) + ")" : "") + "\n\n\n" } this.flyoutTextPlayer.text += "HP: " + poke[i].ivs[0]