diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f20c6503229..03216101f27 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -224,7 +224,7 @@ export default class BattleScene extends SceneBase { private fieldOverlay: Phaser.GameObjects.Rectangle; private shopOverlay: Phaser.GameObjects.Rectangle; public modifiers: PersistentModifier[]; - private enemyModifiers: PersistentModifier[]; + public enemyModifiers: PersistentModifier[]; public uiContainer: Phaser.GameObjects.Container; public ui: UI; @@ -1497,6 +1497,10 @@ export default class BattleScene extends SceneBase { this.scoreText.setText(`Score: ${this.score.toString()}`); this.scoreText.setVisible(this.gameMode.isDaily); } + setScoreText(text: string): void { + this.scoreText.setText(text); + this.scoreText.setVisible(true); + } updateAndShowText(duration: integer): void { const labels = [ this.luckLabelText, this.luckText ]; diff --git a/src/data/pokeball.ts b/src/data/pokeball.ts index 5964884d967..babd263d676 100644 --- a/src/data/pokeball.ts +++ b/src/data/pokeball.ts @@ -53,6 +53,30 @@ export function getPokeballName(type: PokeballType): string { } return ret; } +export function getPokeballShortName(type: PokeballType): string { + let ret: string; + switch (type) { + case PokeballType.POKEBALL: + ret = "Poké"; + break; + case PokeballType.GREAT_BALL: + ret = "Great"; + break; + case PokeballType.ULTRA_BALL: + ret = "Ultra"; + break; + case PokeballType.ROGUE_BALL: + ret = "Rogue"; + break; + case PokeballType.MASTER_BALL: + ret = "Master"; + break; + case PokeballType.LUXURY_BALL: + ret = "Luxury"; + break; + } + return ret; +} export function getPokeballCatchMultiplier(type: PokeballType): number { switch (type) { diff --git a/src/logger.ts b/src/logger.ts index 5d8a1970952..a4adee5fb4c 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -99,7 +99,7 @@ export function logTeam(scene: BattleScene, floor: integer = undefined) { if (team[0].hasTrainer()) { var sprite = scene.currentBattle.trainer.config.getSpriteKey() var trainerCat = Utils.getEnumKeys(TrainerType)[Utils.getEnumValues(TrainerType).indexOf(scene.currentBattle.trainer.config.trainerType)] - setRow("e", floor + "," + team.length + "," + sprite + ",trainer," + trainerCat + ",,,,,,,,,,,,", floor, 0) + setRow("e", floor + ",0," + sprite + ",trainer," + trainerCat + ",,,,,,,,,,,,", floor, 0) } else { for (var i = 0; i < team.length; i++) { logPokemon(scene, floor, i, team[i]) @@ -140,8 +140,23 @@ export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: setRow("e", newLine, floor, slot) //console.log(localStorage.getItem(logs[logKeys.indexOf("e")][1]).split("\n")) } +export function dataSorter(a: string, b: string) { + var da = a.split(",") + var db = b.split(",") + if (da[0] == "---- " + logs[logKeys.indexOf("e")][3] + " ----") { + return -1; + } + if (db[0] == "---- " + logs[logKeys.indexOf("e")][3] + " ----") { + return 1; + } + if (da[0] == db[0]) { + return ((da[1] as any) * 1) - ((db[1] as any) * 1) + } + return ((da[0] as any) * 1) - ((db[0] as any) * 1) +} export function setRow(keyword: string, newLine: string, floor: integer, slot: integer) { var data = localStorage.getItem(logs[logKeys.indexOf(keyword)][1]).split("\n") + data.sort(dataSorter) var idx = 1 if (slot == -1) { while (idx < data.length && (data[idx].split(",")[0] as any) * 1 < floor) { @@ -153,22 +168,38 @@ export function setRow(keyword: string, newLine: string, floor: integer, slot: i while (idx < data.length && (data[idx].split(",")[0] as any) * 1 <= floor && (data[idx].split(",")[1] as any) * 1 <= slot) { idx++ } + idx-- + console.log((data[idx].split(",")[0] as any) * 1, floor, (data[idx].split(",")[1] as any) * 1, slot) + if (idx < data.length && (data[idx].split(",")[0] as any) * 1 == floor && (data[idx].split(",")[1] as any) * 1 == slot) { + data[idx] = newLine + console.log("Overwrote data at " + idx) + for (var i = 0; i < Math.max(0, idx - 2) && i < 2; i++) { + console.log(i + " " + data[i]) + } + if (Math.min(0, idx - 2) > 3) { + console.log("...") + } + for (var i = Math.max(0, idx - 2); i <= idx + 2 && i < data.length; i++) { + console.log(i + (i == idx ? " >> " : " ") + data[i]) + } + localStorage.setItem(logs[logKeys.indexOf(keyword)][1], data.join("\n")); + return; + } + idx++ + } + console.log("Inserted data at " + idx) + for (var i = 0; i < Math.max(0, idx - 2) && i < 2; i++) { + console.log(i + " " + data[i]) + } + if (Math.min(0, idx - 2) > 3) { + console.log("...") + } + for (var i = Math.max(0, idx - 2); i < idx; i++) { + console.log(i + " " + data[i]) + } + console.log(i + " >> " + newLine) + for (var i = idx; i <= idx + 2 && i < data.length; i++) { + console.log(i + " " + data[i]) } localStorage.setItem(logs[logKeys.indexOf(keyword)][1], data.slice(0, idx).join("\n") + "\n" + newLine + (data.slice(idx).length == 0 ? "" : "\n") + data.slice(idx).join("\n")); -} -export function setRowByID(key: integer, newLine: string, floor: integer, slot: integer) { - var data = localStorage.getItem(logs[key][1]).split("\n") - var idx = 1 - if (slot == -1) { - while (idx < data.length && (data[idx].split(",")[0] as any) * 1 < floor) { - idx++ - } - idx-- - slot = ((data[idx].split(",")[1] as any) * 1) + 1 - } else { - while (idx < data.length && (data[idx].split(",")[0] as any) * 1 <= floor && (data[idx].split(",")[1] as any) * 1 <= slot) { - idx++ - } - } - localStorage.setItem(logs[key][1], data.slice(0, idx).join("\n") + "\n" + newLine + (data.slice(idx).length == 0 ? "" : "\n") + data.slice(idx).join("\n")); } \ No newline at end of file diff --git a/src/phases.ts b/src/phases.ts index 18a5d112a08..54c76d8d59f 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -72,6 +72,7 @@ import ChallengeData from "./system/challenge-data"; import { Challenges } from "./enums/challenges" import PokemonData from "./system/pokemon-data" import * as LoggerTools from "./logger" +import { getNatureName } from "./data/nature"; const { t } = i18next; @@ -2058,14 +2059,51 @@ export class CheckSwitchPhase extends BattlePhase { return; } + for (var i = 0; i < this.scene.getEnemyField().length; i++) { + var pk = this.scene.getEnemyField()[i] + var maxIVs = [] + var ivnames = ["HP", "Atk", "Def", "Sp.Atk", "Sp.Def", "Speed"] + pk.ivs.forEach((iv, j) => {if (iv == 31) maxIVs.push(ivnames[j])}) + var ivDesc = maxIVs.join(",") + if (ivDesc == "") { + ivDesc = "No Max IVs" + } else { + ivDesc = "31: " + ivDesc + } + pk.getBattleInfo().flyoutMenu.toggleFlyout(true) + pk.getBattleInfo().flyoutMenu.flyoutText[0].text = getNatureName(pk.nature) + pk.getBattleInfo().flyoutMenu.flyoutText[1].text = ivDesc + pk.getBattleInfo().flyoutMenu.flyoutText[2].text = pk.getAbility().name + pk.getBattleInfo().flyoutMenu.flyoutText[3].text = pk.getPassiveAbility().name + if (pk.hasAbility(pk.species.abilityHidden, true, true)) { + pk.getBattleInfo().flyoutMenu.flyoutText[2].setColor("#e8e8a8") + } + } + this.scene.ui.showText(i18next.t("battle:switchQuestion", { pokemonName: this.useName ? pokemon.name : i18next.t("battle:pokemon") }), null, () => { this.scene.ui.setMode(Mode.CONFIRM, () => { this.scene.ui.setMode(Mode.MESSAGE); this.scene.tryRemovePhase(p => p instanceof PostSummonPhase && p.player && p.fieldIndex === this.fieldIndex); this.scene.unshiftPhase(new SwitchPhase(this.scene, this.fieldIndex, false, true)); + for (var i = 0; i < this.scene.getEnemyField().length; i++) { + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.toggleFlyout(false) + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[0].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[1].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[2].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[3].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[2].setColor("#f8f8f8") + } this.end(); }, () => { this.scene.ui.setMode(Mode.MESSAGE); + for (var i = 0; i < this.scene.getEnemyField().length; i++) { + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.toggleFlyout(false) + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[0].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[1].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[2].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[3].text = "???" + this.scene.getEnemyField()[i].getBattleInfo().flyoutMenu.flyoutText[2].setColor("#f8f8f8") + } this.end(); }); }); @@ -2104,6 +2142,60 @@ 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(); @@ -2152,6 +2244,15 @@ export class TurnInitPhase extends FieldPhase { this.scene.pushPhase(new TurnStartPhase(this.scene)); + 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)) + }) + } + + this.scene.setScoreText(txt.join("/")) + this.end(); } } @@ -5048,6 +5149,16 @@ export class AttemptCapturePhase extends PokemonPhase { this.pokeballType = pokeballType; } + roll(y?: integer) { + var roll = (this.getPokemon() as EnemyPokemon).randSeedInt(65536) + if (y != undefined) { + console.log(roll, y, roll < y) + } else { + console.log(roll) + } + return roll; + } + start() { super.start(); @@ -5129,7 +5240,7 @@ export class AttemptCapturePhase extends PokemonPhase { shakeCounter.stop(); this.failCatch(shakeCount); } else if (shakeCount++ < 3) { - if (pokeballMultiplier === -1 || pokemon.randSeedInt(65536) < y) { + if (pokeballMultiplier === -1 || this.roll(y) < y) { this.scene.playSound("pb_move"); } else { shakeCounter.stop(); diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index ed520512443..c1018fbc74d 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -6,10 +6,15 @@ import { Mode } from "./ui"; import UiHandler from "./ui-handler"; import * as Utils from "../utils"; import { CommandPhase } from "../phases"; -import { MoveCategory } from "#app/data/move.js"; +import * as MoveData from "#app/data/move.js"; import i18next from "i18next"; import {Button} from "#enums/buttons"; -import Pokemon, { PokemonMove } from "#app/field/pokemon.js"; +import Pokemon, { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon.js"; +import Battle from "#app/battle.js"; +import { Stat } from "#app/data/pokemon-stat.js"; +import { Abilities } from "#app/enums/abilities.js"; +import { WeatherType } from "#app/data/weather.js"; +import { Moves } from "#app/enums/moves.js"; export default class FightUiHandler extends UiHandler { private movesContainer: Phaser.GameObjects.Container; @@ -153,6 +158,93 @@ export default class FightUiHandler extends UiHandler { return !this.fieldIndex ? this.cursor : this.cursor2; } + calcDamage(scene: BattleScene, user: PlayerPokemon, target: Pokemon, move: PokemonMove) { + var power = move.getMove().power + var myAtk = 0 + var theirDef = 0 + var myAtkC = 0 + var theirDefC = 0 + switch (move.getMove().category) { + case MoveData.MoveCategory.PHYSICAL: + myAtk = user.getBattleStat(Stat.ATK, target, move.getMove()) + myAtkC = user.getBattleStat(Stat.ATK, target, move.getMove(), true) + theirDef = target.getBattleStat(Stat.DEF, user, move.getMove()) + theirDefC = target.getBattleStat(Stat.DEF, user, move.getMove(), true) + break; + case MoveData.MoveCategory.SPECIAL: + myAtk = user.getBattleStat(Stat.SPATK, target, move.getMove()) + myAtkC = user.getBattleStat(Stat.SPATK, target, move.getMove(), true) + theirDef = target.getBattleStat(Stat.SPDEF, user, move.getMove()) + theirDefC = target.getBattleStat(Stat.SPDEF, user, move.getMove(), true) + break; + case MoveData.MoveCategory.STATUS: + return "---" + } + var stabBonus = 1 + var types = user.getTypes() + // Apply STAB bonus + for (var i = 0; i < types.length; i++) { + if (types[i] == move.getMove().type) { + stabBonus = 1.5 + } + } + // Apply Tera Type bonus + if (stabBonus == 1.5) { + // STAB + if (move.getMove().type == user.getTeraType()) { + stabBonus = 2 + } + } else if (move.getMove().type == user.getTeraType()) { + stabBonus = 1.5 + } + // Apply adaptability + if (stabBonus == 2) { + // Tera-STAB + if (move.getMove().type == user.getTeraType()) { + stabBonus = 2.25 + } + } else if (stabBonus == 1.5) { + // STAB or Tera + if (move.getMove().type == user.getTeraType()) { + stabBonus = 2 + } + } else if (move.getMove().type == user.getTeraType()) { + // Adaptability + stabBonus = 1.5 + } + var weatherBonus = 1 + if (this.scene.arena.weather.weatherType == WeatherType.RAIN || this.scene.arena.weather.weatherType == WeatherType.HEAVY_RAIN) { + if (move.getMove().type == Type.WATER) { + weatherBonus = 1.5 + } + if (move.getMove().type == Type.FIRE) { + weatherBonus = this.scene.arena.weather.weatherType == WeatherType.HEAVY_RAIN ? 0 : 0.5 + } + } + if (this.scene.arena.weather.weatherType == WeatherType.SUNNY || this.scene.arena.weather.weatherType == WeatherType.HARSH_SUN) { + if (move.getMove().type == Type.FIRE) { + weatherBonus = 1.5 + } + if (move.getMove().type == Type.WATER) { + weatherBonus = this.scene.arena.weather.weatherType == WeatherType.HARSH_SUN ? 0 : (move.moveId == Moves.HYDRO_STEAM ? 1.5 : 0.5) + } + } + var typeBonus = target.getAttackMoveEffectiveness(user, move) + var modifiers = stabBonus * weatherBonus + var dmgLow = (((2*user.level/5 + 2) * power * myAtk / theirDef)/50 + 2) * 0.85 * modifiers + var dmgHigh = (((2*user.level/5 + 2) * power * myAtkC / theirDefC)/50 + 2) * 1.5 * modifiers + if (user.hasAbility(Abilities.PARENTAL_BOND)) { + // Second hit deals 0.25x damage + dmgLow *= 1.25 + dmgHigh *= 1.25 + } + return (Math.round(dmgLow) == Math.round(dmgHigh) ? Math.round(dmgLow).toString() : Math.round(dmgLow) + "-" + Math.round(dmgHigh)) + ((Math.round(dmgLow) > target.hp) ? " KO" : "") + dmgLow = Math.round((dmgLow)/target.getBattleStat(Stat.HP)*100) + dmgHigh = Math.round((dmgHigh)/target.getBattleStat(Stat.HP)*100) + return (dmgLow == dmgHigh ? dmgLow + "%" : dmgLow + "%-" + dmgHigh + "%") + ((Math.round(dmgLow) > target.hp) ? " KO" : "") + return "???" + } + setCursor(cursor: integer): boolean { const ui = this.getUi(); @@ -178,7 +270,7 @@ export default class FightUiHandler extends UiHandler { if (hasMove) { const pokemonMove = moveset[cursor]; this.typeIcon.setTexture(`types${Utils.verifyLang(i18next.resolvedLanguage) ? `_${i18next.resolvedLanguage}` : ""}`, Type[pokemonMove.getMove().type].toLowerCase()).setScale(0.8); - this.moveCategoryIcon.setTexture("categories", MoveCategory[pokemonMove.getMove().category].toLowerCase()).setScale(1.0); + this.moveCategoryIcon.setTexture("categories", MoveData.MoveCategory[pokemonMove.getMove().category].toLowerCase()).setScale(1.0); const power = pokemonMove.getMove().power; const accuracy = pokemonMove.getMove().accuracy; @@ -207,6 +299,7 @@ export default class FightUiHandler extends UiHandler { pokemon.getOpponents().forEach((opponent) => { opponent.updateEffectiveness(this.getEffectivenessText(pokemon, opponent, pokemonMove)); + opponent.updateEffectiveness(this.calcDamage(this.scene, pokemon, opponent, pokemonMove)); }); }