diff --git a/README.md b/README.md index d1b46e630bf..2920daba98f 100644 --- a/README.md +++ b/README.md @@ -1,116 +1,46 @@ -PokéRogue +*PokéRogue is a browser based Pokémon fangame heavily inspired by the roguelite genre. Battle endlessly while gathering stacking items, exploring many different biomes, fighting trainers, bosses, and more!* -PokéRogue is a browser based Pokémon fangame heavily inspired by the roguelite genre. Battle endlessly while gathering stacking items, exploring many different biomes, fighting trainers, bosses, and more! +This is a mod for PokéRogue, for use with the offline version. +It's used to help with our routing project. -# Contributing -## 🛠️ Development -If you have the motivation and experience with Typescript/Javascript (or are willing to learn) please feel free to fork the repository and make pull requests with contributions. If you don't know what to work on but want to help, reference the below **To-Do** section or the **#feature-vote** channel in the discord. +This program is for Windows - it does not have installers for Mac or Linux right now. +(You can still do run validation without this mod, of course) -### 💻 Environment Setup -#### Prerequisites -- node: 20.13.1 -- npm: [how to install](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) +## Feature progress +- [ ] Logs all the steps you take while playing +- [x] Logs the wild Pokémon you encounter and their stats +- [x] Logs the category of trainers you encounter +- [x] In-Game GUI to export logs +- [ ] Show damage values for attacks (present, but incomplete) +- [x] Show catch rates +- [x] Show attributes of wild Pokémon (max IVs, nature, abilities) -#### Running Locally -1. Clone the repo and in the root directory run `npm install` - - *if you run into any errors, reach out in the **#dev-corner** channel in discord* -2. Run `npm run start:dev` to locally run the project in `localhost:8000` - -#### Linting -We're using ESLint as our common linter and formatter. It will run automatically during the pre-commit hook but if you would like to manually run it, use the `npm run eslint` script. - -### ❔ FAQ - -**How do I test a new _______?** -- In the `src/overrides.ts` file there are overrides for most values you'll need to change for testing - - -## 🪧 To Do -Check out [Github Issues](https://github.com/pagefaultgames/pokerogue/issues) to see how can you help us! - -# 📝 Credits -> If this project contains assets you have produced and you do not see your name here, **please** reach out. - -### 🎵 BGM - - Pokémon Mystery Dungeon: Explorers of Sky - - Arata Iiyoshi - - Hideki Sakamoto - - Keisuke Ito - - Ken-ichi Saito - - Yoshihiro Maeda - - Pokémon Black/White - - Go Ichinose - - Hitomi Sato - - Shota Kageyama - - Pokémon Mystery Dungeon: Rescue Team DX - - Keisuke Ito - - Arata Iiyoshi - - Atsuhiro Ishizuna - - Pokémon HeartGold/SoulSilver - - Pokémon Black/White 2 - - Pokémon X/Y - - Pokémon Omega Ruby/Alpha Sapphire - - Pokémon Sun/Moon - - Pokémon Ultra Sun/Ultra Moon - - Pokémon Sword/Shield - - Pokémon Scarlet/Violet - - Firel (Custom Laboratory, Metropolis, Seabed, and Space biome music) - - Lmz (Custom Jungle biome music) - -### 🎵 Sound Effects - - Pokémon Emerald - - Pokémon Black/White - -### 🎨 Backgrounds - - Squip (Paid Commissions) - - Contributions by Someonealive-QN - -### 🎨 UI - - GAMEFREAK - - LJ Birdman - -### 🎨 Pagefault Games Intro - - Spectremint - -### 🎨 Game Logo - - Gonstar (Paid Commission) - -### 🎨 Trainer Sprites - - GAMEFREAK (Pokémon Black/White 2, Pokémon Diamond/Pearl) - - kyledove - - Brumirage - - pkmn_realidea (Paid Commissions) - -### 🎨 Trainer Portraits - - pkmn_realidea (Paid Commissions) - -### 🎨 Pokemon Sprites and Animation - - GAMEFREAK (Pokémon Black/White 2) - - Smogon Sprite Project (Various Artists) - - Skyflyer - - Nolo33 - - Ebaru - - EricLostie - - KingOfThe-X-Roads - - kiriaura - - Caruban - - Sopita_Yorita - - Azrita - - AshnixsLaw - - Hellfire0raptor - - RetroNC - - Franark122k - - OldSoulja - - PKMarioG - - ItsYugen - - lucasomi - - Pkm Sinfonia - - Poki Papillon - - Fleimer_ - - bizcoeindoloro - - mangalos810 - - Involuntary-Twitch - - selstar - -### 🎨 Move Animations - - Pokémon Reborn +# Instructions +### Installation +- Make sure you have the app (download v1.3.1 [here](https://github.com/Admiral-Billy/Pokerogue-App/releases) - v2.0.0 and up will not work!) +- Look on the `record-path` channel for the modified installer that allows downloading different versions +- Replace `resources/update-game.js` in the offline version's files with the modified installer +- Run the installer, typing `y` and pressing enter to confirm you want to install offline mode +- Select Pokerogue-Projects/Pathing-Tool (option 2 by default) and press enter again +- Wait (it will take a few minutes to install no matter which version you selected) +- Choose whether you want the offline version of the `pkmn.help` type calculator, then press enter one final time when prompted to close the terminal +### Setting up a run +- Open PokéRogue online (you can use [PokeRogue](https://pokerogue.net/) or the online mode of the app) +- Start a new Daily Run +- Save & Quit +- Open the menu +- Go to Manage Data, select Export Session, and select the slot you saved the Daily Run to - you will download a `.prsv` file +- Open the app in offline mode by running `Pokerogue Offine.bat` +- Open the menu, go to Manage Data, and instead *import* a session +- Select the `.prsv` you downloaded, and select a slot to save it to. When the game reloads, you'll see that the newly imported run has appeared as an option on the title screen. +- Open Manage Logs on the title screen. +- If you played a run already, be sure to export your files first. +- Select `Clear All (3)` to delete any previous run data. +### Playing the Daily Run +- All Daily Run saves will appear as buttons on the title screen. Selecting them will load the file as if you had opened the Load Game menu. (Selecting them in that menu still works, of course.) +- Play! The game will automatically log your run as you go. + - **Warning**: The logs do not discriminate between saves, and if you open another save file, it will **overwrite** any data in Steps (`instructions.txt`) or Encounters (`encounters.csv`). +- When you're done, go to the title screen and open Manage Logs. + - Select a log to save it to your device (the number in parenthases indicates the file size) + - Select "Export All" to save all logs to your device at once (the number in parenthases indicates how many logs will be exported) + - Select "Reset All" to delete all existing run data diff --git a/public/images/pokemon/variant/_masterlist.json b/public/images/pokemon/variant/_masterlist.json index f3e690395e1..e2feeaa9e55 100644 --- a/public/images/pokemon/variant/_masterlist.json +++ b/public/images/pokemon/variant/_masterlist.json @@ -3300,6 +3300,11 @@ 1, 1 ], + "307": [ + 0, + 1, + 1 + ], "308": [ 0, 1, @@ -6678,6 +6683,11 @@ 1, 1 ], + "307": [ + 0, + 1, + 1 + ], "308": [ 0, 1, diff --git a/public/images/pokemon/variant/back/307.json b/public/images/pokemon/variant/back/307.json index 3c2ef92171c..3bdadaa8e16 100644 --- a/public/images/pokemon/variant/back/307.json +++ b/public/images/pokemon/variant/back/307.json @@ -1,15 +1,5 @@ { "1": { - "7b6b6b": "314b76", - "b5adad": "677d98", - "e6dede": "c2cfdb", - "000000": "000000", - "3a84b5": "51876e", - "3a4a5a": "113926", - "6bcee6": "7edfb7", - "5aa5ce": "66c3a3" - }, - "2": { "7b6b6b": "7a5f5f", "b5adad": "9f8383", "e6dede": "deccc3", @@ -18,5 +8,15 @@ "3a4a5a": "5a2859", "6bcee6": "f4a8c8", "5aa5ce": "ce7bb0" + }, + "2": { + "7b6b6b": "314b76", + "b5adad": "677d98", + "e6dede": "c2cfdb", + "000000": "000000", + "3a84b5": "51876e", + "3a4a5a": "113926", + "6bcee6": "7edfb7", + "5aa5ce": "66c3a3" } } \ No newline at end of file diff --git a/public/images/pokemon/variant/back/female/307.json b/public/images/pokemon/variant/back/female/307.json new file mode 100644 index 00000000000..3bdadaa8e16 --- /dev/null +++ b/public/images/pokemon/variant/back/female/307.json @@ -0,0 +1,22 @@ +{ + "1": { + "7b6b6b": "7a5f5f", + "b5adad": "9f8383", + "e6dede": "deccc3", + "000000": "000000", + "3a84b5": "7e4377", + "3a4a5a": "5a2859", + "6bcee6": "f4a8c8", + "5aa5ce": "ce7bb0" + }, + "2": { + "7b6b6b": "314b76", + "b5adad": "677d98", + "e6dede": "c2cfdb", + "000000": "000000", + "3a84b5": "51876e", + "3a4a5a": "113926", + "6bcee6": "7edfb7", + "5aa5ce": "66c3a3" + } +} \ No newline at end of file diff --git a/public/images/pokemon/variant/female/307.json b/public/images/pokemon/variant/female/307.json new file mode 100644 index 00000000000..d3e6a2437f1 --- /dev/null +++ b/public/images/pokemon/variant/female/307.json @@ -0,0 +1,34 @@ +{ + "1": { + "7b6b6b": "7a5f5f", + "000000": "000000", + "e6dede": "deccc3", + "b5adad": "9f8383", + "4a4242": "4a4242", + "ffffff": "ffffff", + "3a4a5a": "5a2859", + "b5d6ff": "f4a8c8", + "6bcee6": "ce7bb0", + "d65252": "d65287", + "84424a": "84424a", + "3a84b5": "7e4377", + "5aa5ce": "b95ba1", + "d65273": "d65273" + }, + "2": { + "7b6b6b": "314b76", + "000000": "000000", + "e6dede": "c2cfdb", + "b5adad": "6f89aa", + "4a4242": "1e2f52", + "ffffff": "ffffff", + "3a4a5a": "113926", + "b5d6ff": "7edfb7", + "6bcee6": "66c3a3", + "d65252": "c067c7", + "84424a": "84424a", + "3a84b5": "375a47", + "5aa5ce": "579578", + "d65273": "d65273" + } +} \ No newline at end of file diff --git a/public/images/ui/windows/abyss_panel.png b/public/images/ui/windows/abyss_panel.png new file mode 100644 index 00000000000..30c2918e5f6 Binary files /dev/null and b/public/images/ui/windows/abyss_panel.png differ diff --git a/public/images/ui/windows/badlands_panel.png b/public/images/ui/windows/badlands_panel.png new file mode 100644 index 00000000000..c601abc89db Binary files /dev/null and b/public/images/ui/windows/badlands_panel.png differ diff --git a/public/images/ui/windows/beach_panel.png b/public/images/ui/windows/beach_panel.png new file mode 100644 index 00000000000..eccdac1779f Binary files /dev/null and b/public/images/ui/windows/beach_panel.png differ diff --git a/public/images/ui/windows/cave_panel.png b/public/images/ui/windows/cave_panel.png new file mode 100644 index 00000000000..0dce031b69e Binary files /dev/null and b/public/images/ui/windows/cave_panel.png differ diff --git a/public/images/ui/windows/construction_site_panel.png b/public/images/ui/windows/construction_site_panel.png new file mode 100644 index 00000000000..bb441df064a Binary files /dev/null and b/public/images/ui/windows/construction_site_panel.png differ diff --git a/public/images/ui/windows/end_panel.png b/public/images/ui/windows/end_panel.png new file mode 100644 index 00000000000..96269ae6cf6 Binary files /dev/null and b/public/images/ui/windows/end_panel.png differ diff --git a/public/images/ui/windows/sea_panel.png b/public/images/ui/windows/sea_panel.png new file mode 100644 index 00000000000..f81e6c59fe6 Binary files /dev/null and b/public/images/ui/windows/sea_panel.png differ diff --git a/public/images/ui/windows/slum_panel.png b/public/images/ui/windows/slum_panel.png new file mode 100644 index 00000000000..14e1e099b50 Binary files /dev/null and b/public/images/ui/windows/slum_panel.png differ diff --git a/public/images/ui/windows/space_panel.png b/public/images/ui/windows/space_panel.png new file mode 100644 index 00000000000..65a0c6bc238 Binary files /dev/null and b/public/images/ui/windows/space_panel.png differ diff --git a/public/images/ui/windows/town_panel.png b/public/images/ui/windows/town_panel.png new file mode 100644 index 00000000000..c8b45a0edf9 Binary files /dev/null and b/public/images/ui/windows/town_panel.png differ diff --git a/public/images/ui/windows/volcano_panel.png b/public/images/ui/windows/volcano_panel.png new file mode 100644 index 00000000000..ea01f1e8aa4 Binary files /dev/null and b/public/images/ui/windows/volcano_panel.png differ diff --git a/public/images/ui/windows/wasteland_panel.png b/public/images/ui/windows/wasteland_panel.png new file mode 100644 index 00000000000..e311290ad4f Binary files /dev/null and b/public/images/ui/windows/wasteland_panel.png differ diff --git a/src/battle-scene.ts b/src/battle-scene.ts index d84d68c8948..1c5656f92d2 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -67,6 +67,7 @@ import { Species } from "#enums/species"; import { UiTheme } from "#enums/ui-theme"; import { TimedEventManager } from "#app/timed-event-manager.js"; import i18next from "i18next"; +import * as LoggerTools from "./logger" export const bypassLogin = import.meta.env.VITE_BYPASS_LOGIN === "1"; @@ -121,6 +122,13 @@ export default class BattleScene extends SceneBase { public enableTutorials: boolean = import.meta.env.VITE_BYPASS_TUTORIAL === "1"; public enableMoveInfo: boolean = true; public enableRetries: boolean = false; + public damageDisplay: string = "Off"; + public lazyReloads: boolean = false; + public menuChangesBiome: boolean = false; + public showAutosaves: boolean = false; + public doBiomePanels: boolean = false; + public disableDailyShinies: boolean = true; // Disables shiny luck in Daily Runs to prevent affecting RNG + public quickloadDisplayMode: string = "Dailies"; /** * Determines the condition for a notification should be shown for Candy Upgrades * - 0 = 'Off' @@ -226,7 +234,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; @@ -255,6 +263,8 @@ export default class BattleScene extends SceneBase { public eventManager: TimedEventManager; + public biomeChangeMode: boolean = false; + /** * Allows subscribers to listen for events * @@ -297,6 +307,7 @@ export default class BattleScene extends SceneBase { Phaser.Math.RND.realInRange = function (min: number, max: number): number { const ret = originalRealInRange.apply(this, [ min, max ]); const args = [ "RNG", ++scene.rngCounter, ret / (max - min), `min: ${min} / max: ${max}` ]; + scene.setScoreText("RNG: " + this.rngCounter + ")") args.push(`seed: ${scene.rngSeedOverride || scene.waveSeed || scene.seed}`); if (scene.rngOffset) { args.push(`offset: ${scene.rngOffset}`); @@ -896,10 +907,37 @@ export default class BattleScene extends SceneBase { return container; } + addPkIcon(pokemon: PokemonSpecies, form: integer = 0, x: number, y: number, originX: number = 0.5, originY: number = 0.5, ignoreOverride: boolean = false): Phaser.GameObjects.Container { + const container = this.add.container(x, y); + container.setName(`${pokemon.name}-icon`); + + const icon = this.add.sprite(0, 0, pokemon.getIconAtlasKey(form)); + icon.setName(`sprite-${pokemon.name}-icon`); + icon.setFrame(pokemon.getIconId(true)); + // Temporary fix to show pokemon's default icon if variant icon doesn't exist + if (icon.frame.name !== pokemon.getIconId(true)) { + console.log(`${pokemon.name}'s variant icon does not exist. Replacing with default.`); + icon.setTexture(pokemon.getIconAtlasKey(0)); + icon.setFrame(pokemon.getIconId(true)); + } + icon.setOrigin(0.5, 0); + + container.add(icon); + + if (originX !== 0.5) { + container.x -= icon.width * (originX - 0.5); + } + if (originY !== 0) { + container.y -= icon.height * originY; + } + + return container; + } setSeed(seed: string): void { this.seed = seed; this.rngCounter = 0; + //this.setScoreText("RNG: 0") this.waveCycleOffset = this.getGeneratedWaveCycleOffset(); this.offsetGym = this.gameMode.isClassic && this.getGeneratedOffsetGym(); } @@ -1351,6 +1389,7 @@ export default class BattleScene extends SceneBase { Phaser.Math.RND.sow([ this.waveSeed ]); console.log("Wave Seed:", this.waveSeed, wave); this.rngCounter = 0; + //this.setScoreText("RNG: 0") } executeWithSeedOffset(func: Function, offset: integer, seedOverride?: string): void { @@ -1367,6 +1406,7 @@ export default class BattleScene extends SceneBase { this.rngSeedOverride = seedOverride || ""; func(); Phaser.Math.RND.state(state); + //this.setScoreText("RNG: " + tempRngCounter + " (Last sim: " + this.rngCounter + ")") this.rngCounter = tempRngCounter; this.rngOffset = tempRngOffset; this.rngSeedOverride = tempRngSeedOverride; @@ -1496,8 +1536,18 @@ export default class BattleScene extends SceneBase { } updateScoreText(): void { - this.scoreText.setText(`Score: ${this.score.toString()}`); - this.scoreText.setVisible(this.gameMode.isDaily); + //this.scoreText.setText(`Score: ${this.score.toString()}`); + //this.scoreText.setVisible(this.gameMode.isDaily); + } + setScoreText(text: string): void { + if (this.scoreText == undefined) + return; + if (this.scoreText.setText == undefined) + return; + if (this.scoreText.setVisible == undefined) + return; + this.scoreText.setText(text); + this.scoreText.setVisible(true); } updateAndShowText(duration: integer): void { @@ -1583,6 +1633,7 @@ export default class BattleScene extends SceneBase { if (fromArenaPool) { return this.arena.randomSpecies(waveIndex, level,null , getPartyLuckValue(this.party)); } + LoggerTools.rarities[LoggerTools.rarityslot[0]] = "" const filteredSpecies = speciesFilter ? [...new Set(allSpecies.filter(s => s.isCatchable()).filter(speciesFilter).map(s => { if (!filterAllEvolutions) { while (pokemonPrevolutions.hasOwnProperty(s.speciesId)) { @@ -2321,7 +2372,7 @@ export default class BattleScene extends SceneBase { if (isBoss) { count = Math.max(count, Math.floor(chances / 2)); } - getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance) + getEnemyModifierTypesForWave(difficultyWaveIndex, count, [ enemyPokemon ], this.currentBattle.battleType === BattleType.TRAINER ? ModifierPoolType.TRAINER : ModifierPoolType.WILD, upgradeChance, this) .map(mt => mt.newModifier(enemyPokemon).add(this.enemyModifiers, false, this)); }); diff --git a/src/battle.ts b/src/battle.ts index c3a481e9956..9f9f431b8ec 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -13,6 +13,7 @@ import { Moves } from "#enums/moves"; import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; +import i18next from "#app/plugins/i18n"; export enum BattleType { WILD, @@ -173,7 +174,10 @@ export default class Battle { scene.addMoney(moneyAmount.value); - scene.queueMessage(`You picked up ₽${moneyAmount.value.toLocaleString("en-US")}!`, null, true); + const userLocale = navigator.language || "en-US"; + const formattedMoneyAmount = moneyAmount.value.toLocaleString(userLocale); + const message = i18next.t("battle:moneyPickedUp", { moneyAmount: formattedMoneyAmount }); + scene.queueMessage(message, null, true); scene.currentBattle.moneyScattered = 0; } @@ -359,6 +363,7 @@ export default class Battle { const ret = Utils.randSeedInt(range, min); this.battleSeedState = Phaser.Math.RND.state(); Phaser.Math.RND.state(state); + //scene.setScoreText("RNG: " + tempRngCounter + " (Last sim: " + this.rngCounter + ")") scene.rngCounter = tempRngCounter; scene.rngSeedOverride = tempSeedOverride; return ret; diff --git a/src/data/ability.ts b/src/data/ability.ts index f449b33992b..61bd215caf3 100644 --- a/src/data/ability.ts +++ b/src/data/ability.ts @@ -2645,6 +2645,8 @@ function getAnticipationCondition(): AbAttrCondition { return (pokemon: Pokemon) => { for (const opponent of pokemon.getOpponents()) { for (const move of opponent.moveset) { + if (move == undefined) + continue; // move is super effective if (move.getMove() instanceof AttackMove && pokemon.getAttackTypeEffectiveness(move.getMove().type, opponent, true) >= 2) { return true; @@ -3999,6 +4001,88 @@ function applyAbAttrsInternal(attrType: Constructor applyNextAbAttr(); }); } +function applyAbAttrsInternalNoApply(attrType: Constructor, + pokemon: Pokemon, applyFunc: AbAttrApplyFunc, args: any[], isAsync: boolean = false, showAbilityInstant: boolean = false, quiet: boolean = false, passive: boolean = false): Promise { + return new Promise(resolve => { + if (!pokemon.canApplyAbility(passive)) { + if (!passive) { + args[0].value = 0 + return resolve(); + return applyAbAttrsInternal(attrType, pokemon, applyFunc, args, isAsync, showAbilityInstant, quiet, true).then(() => resolve()); + } else { + return resolve(); + } + } + + const ability = (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()); + const attrs = ability.getAttrs(attrType); + + const clearSpliceQueueAndResolve = () => { + pokemon.scene?.clearPhaseQueueSplice(); + if (!passive) { + args[0].value = 0 + return resolve() + return applyAbAttrsInternal(attrType, pokemon, applyFunc, args, isAsync, showAbilityInstant, quiet, true).then(() => resolve()); + } else { + return resolve(); + } + }; + return resolve(); + const applyNextAbAttr = () => { + if (attrs.length) { + applyAbAttr(attrs.shift()); + } else { + clearSpliceQueueAndResolve(); + } + }; + const applyAbAttr = (attr: TAttr) => { + if (!canApplyAttr(pokemon, attr)) { + return applyNextAbAttr(); + } + pokemon.scene.setPhaseQueueSplice(); + const onApplySuccess = () => { + if (pokemon.summonData && !pokemon.summonData.abilitiesApplied.includes(ability.id)) { + pokemon.summonData.abilitiesApplied.push(ability.id); + } + if (pokemon.battleData && !pokemon.battleData.abilitiesApplied.includes(ability.id)) { + pokemon.battleData.abilitiesApplied.push(ability.id); + } + if (attr.showAbility && !quiet) { + if (showAbilityInstant) { + pokemon.scene.abilityBar.showAbility(pokemon, passive); + } else { + queueShowAbility(pokemon, passive); + } + } + if (!quiet) { + const message = attr.getTriggerMessage(pokemon, (!passive ? pokemon.getAbility() : pokemon.getPassiveAbility()).name, args); + if (message) { + if (isAsync) { + pokemon.scene.ui.showText(message, null, () => pokemon.scene.ui.showText(null, 0), null, true); + } else { + pokemon.scene.queueMessage(message); + } + } + } + }; + const result = applyFunc(attr, passive); + if (result instanceof Promise) { + result.then(success => { + if (success) { + onApplySuccess(); + } + applyNextAbAttr(); + }); + } else { + if (result) { + onApplySuccess(); + } + applyNextAbAttr(); + } + }; + applyNextAbAttr(); + }); +} export function applyAbAttrs(attrType: Constructor, pokemon: Pokemon, cancelled: Utils.BooleanHolder, ...args: any[]): Promise { return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.apply(pokemon, passive, cancelled, args), args); @@ -4014,6 +4098,11 @@ export function applyPreDefendAbAttrs(attrType: Constructor, const simulated = args.length > 1 && args[1]; return applyAbAttrsInternal(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, false, simulated); } +export function applyPreDefendAbAttrsNoApply(attrType: Constructor, + pokemon: Pokemon, attacker: Pokemon, move: Move, cancelled: Utils.BooleanHolder, ...args: any[]): Promise { + const simulated = args.length > 1 && args[1]; + return applyAbAttrsInternalNoApply(attrType, pokemon, (attr, passive) => attr.applyPreDefend(pokemon, passive, attacker, move, cancelled, args), args, false, false, simulated); +} export function applyPostDefendAbAttrs(attrType: Constructor, pokemon: Pokemon, attacker: Pokemon, move: Move, hitResult: HitResult, ...args: any[]): Promise { diff --git a/src/data/move.ts b/src/data/move.ts index d87d7f918bd..2a8943544cf 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1590,7 +1590,7 @@ export class IncrementMovePriorityAttr extends MoveAttr { * @see {@linkcode apply} */ export class MultiHitAttr extends MoveAttr { - private multiHitType: MultiHitType; + public multiHitType: MultiHitType; constructor(multiHitType?: MultiHitType) { super(); @@ -4218,7 +4218,7 @@ export class AddArenaTagAttr extends MoveEffectAttr { public selfSideTarget: boolean; constructor(tagType: ArenaTagType, turnCount?: integer, failOnOverlap: boolean = false, selfSideTarget: boolean = false) { - super(true, MoveEffectTrigger.POST_APPLY, true); + super(true, MoveEffectTrigger.POST_APPLY); this.tagType = tagType; this.turnCount = turnCount; @@ -4717,7 +4717,7 @@ export class AddTypeAttr extends MoveEffectAttr { } apply(user: Pokemon, target: Pokemon, move: Move, args: any[]): boolean { - const types = target.getTypes().slice(0, 2).filter(t => t !== Type.UNKNOWN); // TODO: Figure out some way to actually check if another version of this effect is already applied + const types = target.getTypes().slice(0, 2).filter(t => t !== Type.UNKNOWN).map(t => t as Type); // TODO: Figure out some way to actually check if another version of this effect is already applied types.push(this.type); target.summonData.types = types; target.updateInfo(); diff --git a/src/data/nature.ts b/src/data/nature.ts index 0d9be0f663d..724314390af 100644 --- a/src/data/nature.ts +++ b/src/data/nature.ts @@ -61,6 +61,89 @@ export function getNatureName(nature: Nature, includeStatEffects: boolean = fals return ret; } +export function getNatureIncrease(nature: Nature) { + switch (nature) { + case Nature.LONELY: + case Nature.BRAVE: + case Nature.ADAMANT: + case Nature.NAUGHTY: + return "atk"; + case Nature.BOLD: + case Nature.RELAXED: + case Nature.IMPISH: + case Nature.LAX: + return "def"; + case Nature.MODEST: + case Nature.MILD: + case Nature.QUIET: + case Nature.RASH: + return "spatk"; + case Nature.CALM: + case Nature.GENTLE: + case Nature.SASSY: + case Nature.CAREFUL: + return "spdef"; + case Nature.TIMID: + case Nature.HASTY: + case Nature.JOLLY: + case Nature.NAIVE: + return "spe" + case Nature.HARDY: + //return "atk" + case Nature.DOCILE: + //return "def" + case Nature.SERIOUS: + //return "spatk" + case Nature.BASHFUL: + //return "spdef" + case Nature.QUIRKY: + //return "spe" + default: + return "" + } +} +export function getNatureDecrease(nature: Nature) { + switch (nature) { + case Nature.BOLD: + case Nature.TIMID: + case Nature.MODEST: + case Nature.CALM: + return "atk"; + case Nature.LONELY: + case Nature.HASTY: + case Nature.MILD: + case Nature.GENTLE: + return "def" + case Nature.ADAMANT: + case Nature.IMPISH: + case Nature.JOLLY: + case Nature.CAREFUL: + return "spatk" + case Nature.NAUGHTY: + case Nature.LAX: + case Nature.NAIVE: + case Nature.RASH: + return "spdef" + case Nature.BRAVE: + case Nature.RELAXED: + case Nature.QUIET: + case Nature.SASSY: + return "spe" + case Nature.HARDY: + //return "atk" + case Nature.DOCILE: + //return "def" + case Nature.SERIOUS: + //return "spatk" + case Nature.BASHFUL: + //return "spdef" + case Nature.QUIRKY: + //return "spe" + default: + return "" + } +} + export function getNatureStatMultiplier(nature: Nature, stat: Stat): number { switch (stat) { case Stat.ATK: 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/data/terrain.ts b/src/data/terrain.ts index e396c693c4e..f7324c28b93 100644 --- a/src/data/terrain.ts +++ b/src/data/terrain.ts @@ -5,6 +5,7 @@ import * as Utils from "../utils"; import { IncrementMovePriorityAbAttr, applyAbAttrs } from "./ability"; import { ProtectAttr } from "./move"; import { BattlerIndex } from "#app/battle.js"; +import i18next from "i18next"; export enum TerrainType { NONE, @@ -67,6 +68,22 @@ export class Terrain { } } +export function getTerrainName(terrainType: TerrainType): string { + switch (terrainType) { + case TerrainType.MISTY: + return i18next.t("terrain:misty"); + case TerrainType.ELECTRIC: + return i18next.t("terrain:electric"); + case TerrainType.GRASSY: + return i18next.t("terrain:grassy"); + case TerrainType.PSYCHIC: + return i18next.t("terrain:psychic"); + } + + return ""; +} + + export function getTerrainColor(terrainType: TerrainType): [ integer, integer, integer ] { switch (terrainType) { case TerrainType.MISTY: diff --git a/src/data/tms.ts b/src/data/tms.ts index c51a4ede8b5..0a13cef4ee8 100644 --- a/src/data/tms.ts +++ b/src/data/tms.ts @@ -59855,16 +59855,11 @@ export const tmSpecies: TmSpecies = { Species.ZUBAT, Species.GOLBAT, Species.TENTACRUEL, - Species.MUK, Species.KOFFING, Species.WEEZING, Species.MEW, - Species.ARIADOS, Species.CROBAT, Species.QWILFISH, - Species.GULPIN, - Species.SWALOT, - Species.SEVIPER, Species.ROSERADE, Species.STUNKY, Species.SKUNTANK, @@ -59896,8 +59891,6 @@ export const tmSpecies: TmSpecies = { Species.NAGANADEL, Species.PINCURCHIN, Species.ETERNATUS, - Species.PIKACHU, - Species.ALOLA_MUK, Species.GALAR_WEEZING, Species.GALAR_SLOWKING, [ diff --git a/src/data/weather.ts b/src/data/weather.ts index 425dd3724f6..f671c754873 100644 --- a/src/data/weather.ts +++ b/src/data/weather.ts @@ -1,12 +1,12 @@ import { Biome } from "#enums/biome"; -import { getPokemonMessage, getPokemonNameWithAffix } from "../messages"; +import { getPokemonNameWithAffix } from "../messages"; import Pokemon from "../field/pokemon"; import { Type } from "./type"; import Move, { AttackMove } from "./move"; import * as Utils from "../utils"; import BattleScene from "../battle-scene"; import { SuppressWeatherEffectAbAttr } from "./ability"; -import { TerrainType } from "./terrain"; +import { TerrainType, getTerrainName } from "./terrain"; import i18next from "i18next"; export enum WeatherType { @@ -216,34 +216,34 @@ export function getWeatherClearMessage(weatherType: WeatherType): string { export function getTerrainStartMessage(terrainType: TerrainType): string { switch (terrainType) { case TerrainType.MISTY: - return "Mist swirled around the battlefield!"; + return i18next.t("terrain:mistyStartMessage"); case TerrainType.ELECTRIC: - return "An electric current ran across the battlefield!"; + return i18next.t("terrain:electricStartMessage"); case TerrainType.GRASSY: - return "Grass grew to cover the battlefield!"; + return i18next.t("terrain:grassyStartMessage"); case TerrainType.PSYCHIC: - return "The battlefield got weird!"; + return i18next.t("terrain:psychicStartMessage"); } } export function getTerrainClearMessage(terrainType: TerrainType): string { switch (terrainType) { case TerrainType.MISTY: - return "The mist disappeared from the battlefield."; + return i18next.t("terrain:mistyClearMessage"); case TerrainType.ELECTRIC: - return "The electricity disappeared from the battlefield."; + return i18next.t("terrain:electricClearMessage"); case TerrainType.GRASSY: - return "The grass disappeared from the battlefield."; + return i18next.t("terrain:grassyClearMessage"); case TerrainType.PSYCHIC: - return "The weirdness disappeared from the battlefield!"; + return i18next.t("terrain:psychicClearMessage"); } } export function getTerrainBlockMessage(pokemon: Pokemon, terrainType: TerrainType): string { if (terrainType === TerrainType.MISTY) { - return getPokemonMessage(pokemon, " surrounds itself with a protective mist!"); + return i18next.t("terrain:mistyBlockMessage", {pokemonNameWithAffix: getPokemonNameWithAffix(pokemon)}); } - return getPokemonMessage(pokemon, ` is protected by the ${Utils.toReadableString(TerrainType[terrainType])} Terrain!`); + return i18next.t("terrain:defaultBlockMessage", {pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), terrainName: getTerrainName(terrainType)}); } interface WeatherPoolEntry { diff --git a/src/evolution-phase.ts b/src/evolution-phase.ts index c7986f6664f..3796fb40db4 100644 --- a/src/evolution-phase.ts +++ b/src/evolution-phase.ts @@ -10,6 +10,7 @@ import { cos, sin } from "./field/anims"; import { PlayerPokemon } from "./field/pokemon"; import { getTypeRgb } from "./data/type"; import i18next from "i18next"; +import * as LoggerTools from "./logger"; export class EvolutionPhase extends Phase { protected pokemon: PlayerPokemon; @@ -200,10 +201,12 @@ export class EvolutionPhase extends Phase { this.end(); }; this.scene.ui.setOverlayMode(Mode.CONFIRM, () => { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Cancel " + preName + "'s evolution and pause evolutions") this.scene.ui.revertMode(); this.pokemon.pauseEvolutions = true; this.scene.ui.showText(i18next.t("menu:evolutionsPaused", { pokemonName: preName }), null, end, 3000); }, () => { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Cancel " + preName + "'s evolution") this.scene.ui.revertMode(); this.scene.time.delayedCall(3000, end); }); @@ -219,6 +222,7 @@ export class EvolutionPhase extends Phase { evolutionHandler.canCancel = false; this.pokemon.evolve(this.evolution).then(() => { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Evolve " + preName) const levelMoves = this.pokemon.getLevelMoves(this.lastLevel + 1, true); for (const lm of levelMoves) { this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.scene.getParty().indexOf(this.pokemon), lm[1])); diff --git a/src/field/arena.ts b/src/field/arena.ts index 2d20abeedd1..8658b35d756 100644 --- a/src/field/arena.ts +++ b/src/field/arena.ts @@ -21,6 +21,7 @@ import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; import { TimeOfDay } from "#enums/time-of-day"; import { TrainerType } from "#enums/trainer-type"; +import * as LoggerTools from "../logger" export class Arena { public scene: BattleScene; @@ -33,7 +34,7 @@ export class Arena { private lastTimeOfDay: TimeOfDay; - private pokemonPool: PokemonPools; + public pokemonPool: PokemonPools; private trainerPool: BiomeTierTrainerPools; public readonly eventTarget: EventTarget = new EventTarget(); @@ -91,6 +92,19 @@ export class Arena { ? tierValue >= 156 ? BiomePoolTier.COMMON : tierValue >= 32 ? BiomePoolTier.UNCOMMON : tierValue >= 6 ? BiomePoolTier.RARE : tierValue >= 1 ? BiomePoolTier.SUPER_RARE : BiomePoolTier.ULTRA_RARE : tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE; console.log(BiomePoolTier[tier]); + var tiernames = [ + "Common", + "Uncommon", + "Rare", + "Super Rare", + "Ultra Rare", + "Common", + "Rare", + "Super Rare", + "Ultra Rare", + ] + LoggerTools.rarities[LoggerTools.rarityslot[0]] = tiernames[tier] + console.log(tiernames[tier]) while (!this.pokemonPool[tier].length) { console.log(`Downgraded rarity tier from ${BiomePoolTier[tier]} to ${BiomePoolTier[tier - 1]}`); tier--; @@ -384,6 +398,10 @@ export class Arena { return weatherMultiplier * terrainMultiplier; } + /** + * Gets the denominator for the chance for a trainer spawn + * @returns n where 1/n is the chance of a trainer battle + */ getTrainerChance(): integer { switch (this.biomeType) { case Biome.METROPOLIS: diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index da83a8d4f57..99ff0bd633f 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -109,6 +109,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { private shinySparkle: Phaser.GameObjects.Sprite; public usedInBattle: boolean = false; + public partyslot: integer; constructor(scene: BattleScene, x: number, y: number, species: PokemonSpecies, level: integer, abilityIndex?: integer, formIndex?: integer, gender?: Gender, shiny?: boolean, variant?: Variant, ivs?: integer[], nature?: Nature, dataSource?: Pokemon | PokemonData) { super(scene, x, y); diff --git a/src/field/trainer.ts b/src/field/trainer.ts index 3e78afeae83..24fa56f6b55 100644 --- a/src/field/trainer.ts +++ b/src/field/trainer.ts @@ -168,6 +168,113 @@ export default class Trainer extends Phaser.GameObjects.Container { // Return the formatted name, including the title if it is set. return title ? `${title} ${name}` : name; } + getNameOnly(trainerSlot: TrainerSlot = TrainerSlot.NONE): string { + // Get the base title based on the trainer slot and variant. + let name = this.config.getTitle(trainerSlot, this.variant); + + // Determine the title to include based on the configuration and includeTitle flag. + let title = true && this.config.title ? this.config.title : null; + + if (this.name === "" && name.toLowerCase().includes("grunt")) { + // This is a evil team grunt so we localize it by only using the "name" as the title + title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`); + console.log("Localized grunt name: " + title); + // Since grunts are not named we can just return the title + return title; + } + + // If the trainer has a name (not null or undefined). + if (this.name) { + // If the title should be included. + if (true) { + // Check if the internationalization (i18n) system is initialized. + if (!getIsInitialized()) { + // Initialize the i18n system if it is not already initialized. + initI18n(); + } + // Get the localized trainer class name from the i18n file and set it as the title. + // This is used for trainer class names, not titles like "Elite Four, Champion, etc." + title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`); + } + + // If no specific trainer slot is set. + if (!trainerSlot) { + // Use the trainer's name. + name = this.name; + // If there is a partner name, concatenate it with the trainer's name using "&". + if (this.partnerName) { + name = `${name} & ${this.partnerName}`; + } + } else { + // Assign the name based on the trainer slot: + // Use 'this.name' if 'trainerSlot' is TRAINER. + // Otherwise, use 'this.partnerName' if it exists, or 'this.name' if it doesn't. + name = trainerSlot === TrainerSlot.TRAINER ? this.name : this.partnerName || this.name; + } + } + + if (this.config.titleDouble && this.variant === TrainerVariant.DOUBLE && !this.config.doubleOnly) { + title = this.config.titleDouble; + name = i18next.t(`trainerNames:${this.config.nameDouble.toLowerCase().replace(/\s/g, "_")}`); + } + + // Return the formatted name, including the title if it is set. + return name || ""; + } + getTitleOnly(trainerSlot: TrainerSlot = TrainerSlot.NONE): string { + // Get the base title based on the trainer slot and variant. + let name = this.config.getTitle(trainerSlot, this.variant); + + // Determine the title to include based on the configuration and includeTitle flag. + let title = true && this.config.title ? this.config.title : null; + + if (this.name === "" && name.toLowerCase().includes("grunt")) { + return "Grunt"; + // This is a evil team grunt so we localize it by only using the "name" as the title + title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`); + console.log("Localized grunt name: " + title); + // Since grunts are not named we can just return the title + return title; + } + + // If the trainer has a name (not null or undefined). + if (this.name) { + // If the title should be included. + if (true) { + // Check if the internationalization (i18n) system is initialized. + if (!getIsInitialized()) { + // Initialize the i18n system if it is not already initialized. + initI18n(); + } + // Get the localized trainer class name from the i18n file and set it as the title. + // This is used for trainer class names, not titles like "Elite Four, Champion, etc." + title = i18next.t(`trainerClasses:${name.toLowerCase().replace(/\s/g, "_")}`); + } + + // If no specific trainer slot is set. + if (!trainerSlot) { + // Use the trainer's name. + name = this.name; + // If there is a partner name, concatenate it with the trainer's name using "&". + if (this.partnerName) { + name = `${name} & ${this.partnerName}`; + } + } else { + // Assign the name based on the trainer slot: + // Use 'this.name' if 'trainerSlot' is TRAINER. + // Otherwise, use 'this.partnerName' if it exists, or 'this.name' if it doesn't. + name = trainerSlot === TrainerSlot.TRAINER ? this.name : this.partnerName || this.name; + } + } + + if (this.config.titleDouble && this.variant === TrainerVariant.DOUBLE && !this.config.doubleOnly) { + title = this.config.titleDouble; + name = i18next.t(`trainerNames:${this.config.nameDouble.toLowerCase().replace(/\s/g, "_")}`); + } + + // Return the formatted name, including the title if it is set. + return title || ""; + } isDouble(): boolean { diff --git a/src/game-mode.ts b/src/game-mode.ts index 0a472e223e3..dd22e69d719 100644 --- a/src/game-mode.ts +++ b/src/game-mode.ts @@ -107,22 +107,37 @@ export class GameMode implements GameModeConfig { } } + /** + * Determines whether or not to generate a trainer + * @param waveIndex the current floor the player is on (trainer sprites fail to generate on X1 floors) + * @param arena the arena that contains the scene and functions + * @returns true if a trainer should be generated, false otherwise + */ isWaveTrainer(waveIndex: integer, arena: Arena): boolean { + /** + * Daily spawns trainers on floors 5, 15, 20, 25, 30, 35, 40, and 45 + */ if (this.isDaily) { return waveIndex % 10 === 5 || (!(waveIndex % 10) && waveIndex > 10 && !this.isWaveFinal(waveIndex)); } if ((waveIndex % 30) === (arena.scene.offsetGym ? 0 : 20) && !this.isWaveFinal(waveIndex)) { return true; } else if (waveIndex % 10 !== 1 && waveIndex % 10) { + /** + * Do not check X1 floors since there's a bug that stops trainer sprites from appearing + * after a X0 full party heal + */ + const trainerChance = arena.getTrainerChance(); let allowTrainerBattle = true; if (trainerChance) { const waveBase = Math.floor(waveIndex / 10) * 10; + // Stop generic trainers from spawning in within 3 waves of a trainer battle for (let w = Math.max(waveIndex - 3, waveBase + 2); w <= Math.min(waveIndex + 3, waveBase + 9); w++) { if (w === waveIndex) { continue; } - if ((w % 30) === (arena.scene.offsetGym ? 0 : 20) || this.isFixedBattle(waveIndex)) { + if ((w % 30) === (arena.scene.offsetGym ? 0 : 20) || this.isFixedBattle(w)) { allowTrainerBattle = false; break; } else if (w < waveIndex) { @@ -138,7 +153,7 @@ export class GameMode implements GameModeConfig { } } } - return allowTrainerBattle && trainerChance && !Utils.randSeedInt(trainerChance); + return Boolean(allowTrainerBattle && trainerChance && !Utils.randSeedInt(trainerChance)); } return false; } diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 15cd295d23c..0288d2c6c39 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -23,6 +23,97 @@ import { initVouchers } from "./system/voucher"; import { Biome } from "#enums/biome"; import { TrainerType } from "#enums/trainer-type"; +export const biomePanelIDs: string[] = [ + "town", + "plains", + "grass", + "tall_grass", + "metropolis", + "forest", + "sea", + "swamp", + "beach", + "lake", + "seabed", + "mountain", + "badlands", + "cave", + "desert", + "ice_cave", + "meadow", + "power_plant", + "volcano", + "graveyard", + "dojo", + "factory", + "ruins", + "wasteland", + "abyss", + "space", + "construction_site", + "jungle", + "fairy_cave", + "temple", + "slum", + "snowy_forest", + "", + "", + "", + "", + "", + "", + "", + "", + "island", + "laboratory", + "", + "", + "", + "", + "", + "", + "", + "", + "end" +] +export const allpanels: string[] = [ + "abyss", + "badlands", + "beach", + "cave", + "construction_site", + //"desert", + //"dojo", + "end", + //"factory", + //"fairy_cave", + //"forest", + //"grass", + //"graveyard", + //"ice_cave", + //"island", + //"jungle", + //"laboratory", + //"lake", + //"meadow", + //"metropolis", + //"mountain", + //"plains", + //"power_plant", + //"ruins", + "sea", + //"seabed", + "slum", + //"snowy_forest", + "space", + //"swamp", + //"tall_grass", + //"temple", + "town", + "volcano", + "wasteland" +] + export class LoadingScene extends SceneBase { readonly LOAD_EVENTS = Phaser.Loader.Events; @@ -53,6 +144,44 @@ export class LoadingScene extends SceneBase { this.loadImage(`window_${w}${getWindowVariantSuffix(wv)}`, "ui/windows"); } } + //this.loadImage(`abyss_panel`, "ui/windows"); +// this.loadImage(`badlands_panel`, "ui/windows"); + //this.loadImage(`beach_panel`, "ui/windows"); + //this.loadImage(`cave_panel`, "ui/windows"); + //this.loadImage(`construction_site_panel`, "ui/windows"); + //this.loadImage(`desert_panel`, "ui/windows"); + //this.loadImage(`dojo_panel`, "ui/windows"); +// this.loadImage(`end_panel`, "ui/windows"); + //this.loadImage(`factory_panel`, "ui/windows"); + //this.loadImage(`fairy_cave_panel`, "ui/windows"); + //this.loadImage(`forest_panel`, "ui/windows"); + //this.loadImage(`grass_panel`, "ui/windows"); + //this.loadImage(`graveyard_panel`, "ui/windows"); + //this.loadImage(`ice_cave_panel`, "ui/windows"); + //this.loadImage(`island_panel`, "ui/windows"); + //this.loadImage(`jungle_panel`, "ui/windows"); + //this.loadImage(`laboratory_panel`, "ui/windows"); + //this.loadImage(`lake_panel`, "ui/windows"); + //this.loadImage(`meadow_panel`, "ui/windows"); + //this.loadImage(`metropolis_panel`, "ui/windows"); + //this.loadImage(`mountain_panel`, "ui/windows"); + //this.loadImage(`plains_panel`, "ui/windows"); + //this.loadImage(`power_plant_panel`, "ui/windows"); + //this.loadImage(`ruins_panel`, "ui/windows"); +// this.loadImage(`sea_panel`, "ui/windows"); + //this.loadImage(`seabed_panel`, "ui/windows"); + //this.loadImage(`slum_panel`, "ui/windows"); + //this.loadImage(`snowy_forest_panel`, "ui/windows"); +// this.loadImage(`space_panel`, "ui/windows"); + //this.loadImage(`swamp_panel`, "ui/windows"); + //this.loadImage(`tall_grass_panel`, "ui/windows"); + //this.loadImage(`temple_panel`, "ui/windows"); + //this.loadImage(`town_panel`, "ui/windows"); +// this.loadImage(`volcano_panel`, "ui/windows"); +// this.loadImage(`wasteland_panel`, "ui/windows"); + for (var i = 0; i < allpanels.length; i++) { + this.loadImageNoLegacy(`${allpanels[i]}_panel`, "ui/windows"); + } this.loadAtlas("namebox", "ui"); this.loadImage("pbinfo_player", "ui"); this.loadImage("pbinfo_player_stats", "ui"); diff --git a/src/locales/de/battle.ts b/src/locales/de/battle.ts index 099020d46d5..06b9ec719ba 100644 --- a/src/locales/de/battle.ts +++ b/src/locales/de/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "Möchtest du\n{{pokemonName}} auswechseln?", "trainerDefeated": "{{trainerName}}\nwurde besiegt!", "moneyWon": "Du gewinnst\n{{moneyAmount}} ₽!", + "moneyPickedUp": "Du hebst {{moneyAmount}} ₽ auf!", "pokemonCaught": "{{pokemonName}} wurde gefangen!", "addedAsAStarter": "{{pokemonName}} wurde als Starterpokémon hinzugefügt!", "partyFull": "Dein Team ist voll.\nMöchtest du ein Pokémon durch {{pokemonName}} ersetzen?", diff --git a/src/locales/de/config.ts b/src/locales/de/config.ts index 92544d87ea3..ffbb2733205 100644 --- a/src/locales/de/config.ts +++ b/src/locales/de/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -39,7 +40,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -73,6 +74,7 @@ export const deConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, @@ -85,6 +87,7 @@ export const deConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, diff --git a/src/locales/de/modifier.ts b/src/locales/de/modifier.ts new file mode 100644 index 00000000000..c1a282ee5f1 --- /dev/null +++ b/src/locales/de/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}} hält mithilfe des Items {{typeName}} durch!", + "turnHealApply": "{{typeName}} von {{pokemonNameWithAffix}} füllt einige KP auf!", + "hitHealApply": "{{typeName}} von {{pokemonNameWithAffix}} füllt einige KP auf!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} wurde durch {{typeName}} wiederbelebt!", + "moneyInterestApply": "Du erhählst {{moneyAmount}} ₽ durch das Item {{typeName}}!", + "turnHeldItemTransferApply": "{{itemName}} von {{pokemonNameWithAffix}} wurde durch {{typeName}} von {{pokemonName}} absorbiert!", + "contactHeldItemTransferApply": "{{itemName}} von {{pokemonNameWithAffix}} wurde durch {{typeName}} von {{pokemonName}} geklaut!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}} stellt einige KP wieder her!", +} as const; diff --git a/src/locales/de/settings.ts b/src/locales/de/settings.ts index 85f8645d69f..0254611b5d5 100644 --- a/src/locales/de/settings.ts +++ b/src/locales/de/settings.ts @@ -82,7 +82,7 @@ export const settings: SimpleTranslationEntries = { "buttonMenu": "Menü", "buttonSubmit": "Bestätigen", "buttonCancel": "Abbrechen", - "buttonStats": "Statistiken", + "buttonStats": "Statuswerte", "buttonCycleForm": "Form wechseln", "buttonCycleShiny": "Schillernd wechseln", "buttonCycleGender": "Geschlecht wechseln", diff --git a/src/locales/de/weather.ts b/src/locales/de/weather.ts index 305fd7e7827..8a820f3d549 100644 --- a/src/locales/de/weather.ts +++ b/src/locales/de/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "Rätselhafte Luftströmungen haben den Angriff abgeschwächt!", "strongWindsClearMessage": "Die rätselhafte Luftströmung hat sich wieder geleget.", }; + +export const terrain: SimpleTranslationEntries = { + "misty": "Nebelfeld", + "mistyStartMessage": "Am Boden breitet sich dichter Nebel aus!", + "mistyClearMessage": "Das Nebelfeld ist wieder verschwunden!", + "mistyBlockMessage": "{{pokemonNameWithAffix}} wird vom Nebelfeld geschützt!", + + "electric": "Elektrofeld", + "electricStartMessage": "Elektrische Energie fließt durch den Boden!", + "electricClearMessage": "Das Elektrofeld ist wieder verschwunden!", + + "grassy": "Grasfeld", + "grassyStartMessage": "Dichtes Gras schießt aus dem Boden!", + "grassyClearMessage": "Das Grasfeld ist wieder verschwunden!", + + "psychic": "Psychofeld", + "psychicStartMessage": "Der Boden fühlt sich seltsam an!", + "psychicClearMessage": "Das Psychofeld ist wieder verschwunden!", + + "defaultBlockMessage": "{{pokemonNameWithAffix}} wird vom {{terrainName}} geschützt!" +}; diff --git a/src/locales/en/battle.ts b/src/locales/en/battle.ts index a42743ef254..c7e2ef96be4 100644 --- a/src/locales/en/battle.ts +++ b/src/locales/en/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "Will you switch\n{{pokemonName}}?", "trainerDefeated": "You defeated\n{{trainerName}}!", "moneyWon": "You got\n₽{{moneyAmount}} for winning!", + "moneyPickedUp": "You picked up ₽{{moneyAmount}}!", "pokemonCaught": "{{pokemonName}} was caught!", "addedAsAStarter": "{{pokemonName}} has been\nadded as a starter!", "partyFull": "Your party is full.\nRelease a Pokémon to make room for {{pokemonName}}?", diff --git a/src/locales/en/config.ts b/src/locales/en/config.ts index a318bbe0128..1c5449a2e88 100644 --- a/src/locales/en/config.ts +++ b/src/locales/en/config.ts @@ -27,6 +27,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -42,7 +43,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { modifierSelectUiHandler } from "./modifier-select-ui-handler"; export const enConfig = { @@ -73,10 +74,10 @@ export const enConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, - partyUiHandler: partyUiHandler, pokeball: pokeball, pokemon: pokemon, pokemonInfo: pokemonInfo, @@ -86,11 +87,13 @@ export const enConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, tutorial: tutorial, voucher: voucher, weather: weather, + partyUiHandler: partyUiHandler, modifierSelectUiHandler: modifierSelectUiHandler }; diff --git a/src/locales/en/modifier.ts b/src/locales/en/modifier.ts new file mode 100644 index 00000000000..d3da4c2150b --- /dev/null +++ b/src/locales/en/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}} hung on\nusing its {{typeName}}!", + "turnHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", + "hitHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} was revived\nby its {{typeName}}!", + "moneyInterestApply": "You received interest of ₽{{moneyAmount}}\nfrom the {{typeName}}!", + "turnHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was absorbed\nby {{pokemonName}}'s {{typeName}}!", + "contactHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was snatched\nby {{pokemonName}}'s {{typeName}}!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\nrestored some HP!", +} as const; diff --git a/src/locales/en/weather.ts b/src/locales/en/weather.ts index c7b2963ccd8..8222064f341 100644 --- a/src/locales/en/weather.ts +++ b/src/locales/en/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "The mysterious air current weakened the attack!", "strongWindsClearMessage": "The heavy wind stopped." }; + +export const terrain: SimpleTranslationEntries = { + "misty": "Misty", + "mistyStartMessage": "Mist swirled around the battlefield!", + "mistyClearMessage": "The mist disappeared from the battlefield.", + "mistyBlockMessage": "{{pokemonNameWithAffix}} surrounds itself with a protective mist!", + + "electric": "Electric", + "electricStartMessage": "An electric current ran across the battlefield!", + "electricClearMessage": "The electricity disappeared from the battlefield.", + + "grassy": "Grassy", + "grassyStartMessage": "Grass grew to cover the battlefield!", + "grassyClearMessage": "The grass disappeared from the battlefield.", + + "psychic": "Psychic", + "psychicStartMessage": "The battlefield got weird!", + "psychicClearMessage": "The weirdness disappeared from the battlefield!", + + "defaultBlockMessage": "{{pokemonNameWithAffix}} is protected by the {{terrainName}} Terrain!" +}; diff --git a/src/locales/es/battle.ts b/src/locales/es/battle.ts index ddba5fab9a8..bc0dd1e4b78 100644 --- a/src/locales/es/battle.ts +++ b/src/locales/es/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "¿Quieres cambiar a\n{{pokemonName}}?", "trainerDefeated": "¡Has derrotado a\n{{trainerName}}!", "moneyWon": "¡Has ganado\n₽{{moneyAmount}} por vencer!", + "moneyPickedUp": "You picked up ₽{{moneyAmount}}!", "pokemonCaught": "¡{{pokemonName}} atrapado!", "addedAsAStarter": "{{pokemonName}} ha sido añadido\na tus iniciales!", "partyFull": "Tu equipo esta completo.\n¿Quieres liberar un Pokémon para meter a {{pokemonName}}?", diff --git a/src/locales/es/config.ts b/src/locales/es/config.ts index bedd53dcc29..341fafa4ca9 100644 --- a/src/locales/es/config.ts +++ b/src/locales/es/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -39,7 +40,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -73,6 +74,7 @@ export const esConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, @@ -85,6 +87,7 @@ export const esConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, diff --git a/src/locales/es/modifier.ts b/src/locales/es/modifier.ts new file mode 100644 index 00000000000..d3da4c2150b --- /dev/null +++ b/src/locales/es/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}} hung on\nusing its {{typeName}}!", + "turnHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", + "hitHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} was revived\nby its {{typeName}}!", + "moneyInterestApply": "You received interest of ₽{{moneyAmount}}\nfrom the {{typeName}}!", + "turnHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was absorbed\nby {{pokemonName}}'s {{typeName}}!", + "contactHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was snatched\nby {{pokemonName}}'s {{typeName}}!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\nrestored some HP!", +} as const; diff --git a/src/locales/es/weather.ts b/src/locales/es/weather.ts index 37f574878dc..1129443d71b 100644 --- a/src/locales/es/weather.ts +++ b/src/locales/es/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "¡Las misteriosas turbulencias atenúan el ataque!", "strongWindsClearMessage": "El fuerte viento cesó." }; + +export const terrain: SimpleTranslationEntries = { + "misty": "Misty", + "mistyStartMessage": "Mist swirled around the battlefield!", + "mistyClearMessage": "The mist disappeared from the battlefield.", + "mistyBlockMessage": "{{pokemonNameWithAffix}} surrounds itself with a protective mist!", + + "electric": "Electric", + "electricStartMessage": "An electric current ran across the battlefield!", + "electricClearMessage": "The electricity disappeared from the battlefield.", + + "grassy": "Grassy", + "grassyStartMessage": "Grass grew to cover the battlefield!", + "grassyClearMessage": "The grass disappeared from the battlefield.", + + "psychic": "Psychic", + "psychicStartMessage": "The battlefield got weird!", + "psychicClearMessage": "The weirdness disappeared from the battlefield!", + + "defaultBlockMessage": "{{pokemonNameWithAffix}} is protected by the {{terrainName}} Terrain!" +}; diff --git a/src/locales/fr/battle.ts b/src/locales/fr/battle.ts index 5bcf0763ef0..fc155664aaa 100644 --- a/src/locales/fr/battle.ts +++ b/src/locales/fr/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "Voulez-vous changer\nvotre {{pokemonName}} ?", "trainerDefeated": "Vous avez battu\n{{trainerName}} !", "moneyWon": "Vous remportez\n{{moneyAmount}} ₽ !", + "moneyPickedUp": "Vous obtenez {{moneyAmount}} ₽ !", "pokemonCaught": "Vous avez attrapé {{pokemonName}} !", "addedAsAStarter": "{{pokemonName}} est ajouté\ncomme starter !", "partyFull": "Votre équipe est pleine.\nRelâcher un Pokémon pour {{pokemonName}} ?", diff --git a/src/locales/fr/config.ts b/src/locales/fr/config.ts index 55eae8c1dd5..d8f9a6601c1 100644 --- a/src/locales/fr/config.ts +++ b/src/locales/fr/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -39,7 +40,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -73,6 +74,7 @@ export const frConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, @@ -85,6 +87,7 @@ export const frConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, diff --git a/src/locales/fr/modifier.ts b/src/locales/fr/modifier.ts new file mode 100644 index 00000000000..f215e258a76 --- /dev/null +++ b/src/locales/fr/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}} tient bon\ngrâce à son {{typeName}} !", + "turnHealApply": "Les PV de {{pokemonNameWithAffix}}\nsont un peu restaurés par les {{typeName}} !", + "hitHealApply": "Les PV de {{pokemonNameWithAffix}}\nsont un peu restaurés par le {{typeName}} !", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} a repris connaissance\navec sa {{typeName}} et est prêt à se battre de nouveau !", + "moneyInterestApply": "La {{typeName}} vous rapporte\n{{moneyAmount}} ₽ d’intérêts !", + "turnHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} est absorbé·e\npar le {{typeName}} de {{pokemonName}} !", + "contactHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} est volé·e\npar l’{{typeName}} de {{pokemonName}} !", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\nrestaure un peu ses PV !", +} as const; diff --git a/src/locales/fr/weather.ts b/src/locales/fr/weather.ts index 3df8d0e20c9..3427748480e 100644 --- a/src/locales/fr/weather.ts +++ b/src/locales/fr/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "Le courant aérien mystérieux affaiblit l’attaque !", "strongWindsClearMessage": "Le vent mystérieux s’est dissipé…" }; + +export const terrain: SimpleTranslationEntries = { + "misty": "Brumeux", + "mistyStartMessage": "La brume recouvre le terrain !", + "mistyClearMessage": "La brume qui recouvrait le terrain se dissipe…", + "mistyBlockMessage": "La brume enveloppe {{pokemonNameWithAffix}} !", + + "electric": "Électrifié", + "electricStartMessage": "De l’électricité parcourt le terrain !", + "electricClearMessage": "L’électricité parcourant le terrain s’est dissipée…", + + "grassy": "Herbu", + "grassyStartMessage": "Un beau gazon pousse sur le terrain !", + "grassyClearMessage": "Le gazon disparait…", + + "psychic": "Psychique", + "psychicStartMessage": "Le sol se met à réagir de façon bizarre…", + "psychicClearMessage": "Le sol redevient normal !", + + "defaultBlockMessage": "{{pokemonNameWithAffix}} est protégé\npar le Champ {{terrainName}} !" +}; diff --git a/src/locales/it/ability-trigger.ts b/src/locales/it/ability-trigger.ts index 1f6fcfb1258..fd18147ac5a 100644 --- a/src/locales/it/ability-trigger.ts +++ b/src/locales/it/ability-trigger.ts @@ -3,9 +3,9 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; export const abilityTriggers: SimpleTranslationEntries = { "blockRecoilDamage" : "{{abilityName}} di {{pokemonName}}\nl'ha protetto dal contraccolpo!", "badDreams": "{{pokemonName}} è tormentato dagli incubi!", - "costar": "{{pokemonName}} copied {{allyName}}'s stat changes!", + "costar": "{{pokemonName}} ha copiato le modifiche alle statistiche\ndel suo alleato {{allyName}}!", "iceFaceAvoidedDamage": "{{pokemonName}} ha evitato\ni danni grazie a {{abilityName}}!", - "trace": "{{pokemonName}} copied {{targetName}}'s\n{{abilityName}}!", + "trace": "L'abilità {{abilityName}} di {{targetName}}\nviene copiata da {{pokemonName}} con Traccia!", "windPowerCharged": "Venire colpito da {{moveName}} ha caricato {{pokemonName}}!", - "quickDraw":"{{pokemonName}} can act faster than normal, thanks to its Quick Draw!", + "quickDraw":"{{pokemonName}} agisce più rapidamente del normale grazie a Colpolesto!", } as const; diff --git a/src/locales/it/battle.ts b/src/locales/it/battle.ts index f44ca8e493c..787888e333b 100644 --- a/src/locales/it/battle.ts +++ b/src/locales/it/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "Vuoi cambiare\n{{pokemonName}}?", "trainerDefeated": "Hai sconfitto\n{{trainerName}}!", "moneyWon": "Hai vinto {{moneyAmount}}₽", + "moneyPickedUp": "You picked up ₽{{moneyAmount}}!", "pokemonCaught": "Preso! {{pokemonName}} è stato catturato!", "addedAsAStarter": "{{pokemonName}} has been\nadded as a starter!", "partyFull": "La tua squadra è al completo.\nVuoi liberare un Pokémon per far spazio a {{pokemonName}}?", diff --git a/src/locales/it/common.ts b/src/locales/it/common.ts index de5197cbeeb..2a84e982350 100644 --- a/src/locales/it/common.ts +++ b/src/locales/it/common.ts @@ -2,7 +2,7 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; export const common: SimpleTranslationEntries = { "start": "Inizia", - "luckIndicator": "Luck:", + "luckIndicator": "Fortuna:", "shinyOnHover": "Shiny", "commonShiny": "Comune", "rareShiny": "Raro", diff --git a/src/locales/it/config.ts b/src/locales/it/config.ts index 9175d40b97f..5b370b00e4b 100644 --- a/src/locales/it/config.ts +++ b/src/locales/it/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -39,7 +40,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -73,6 +74,7 @@ export const itConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, @@ -85,6 +87,7 @@ export const itConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, diff --git a/src/locales/it/modifier.ts b/src/locales/it/modifier.ts new file mode 100644 index 00000000000..d3da4c2150b --- /dev/null +++ b/src/locales/it/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}} hung on\nusing its {{typeName}}!", + "turnHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", + "hitHealApply": "{{pokemonNameWithAffix}} restored a little HP using\nits {{typeName}}!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} was revived\nby its {{typeName}}!", + "moneyInterestApply": "You received interest of ₽{{moneyAmount}}\nfrom the {{typeName}}!", + "turnHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was absorbed\nby {{pokemonName}}'s {{typeName}}!", + "contactHeldItemTransferApply": "{{pokemonNameWithAffix}}'s {{itemName}} was snatched\nby {{pokemonName}}'s {{typeName}}!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\nrestored some HP!", +} as const; diff --git a/src/locales/it/weather.ts b/src/locales/it/weather.ts index f5d8e3b9397..604108435c3 100644 --- a/src/locales/it/weather.ts +++ b/src/locales/it/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "La corrente misteriosa indebolisce l’attacco!", "strongWindsClearMessage": "La corrente d'aria è cessata." }; + +export const terrain: SimpleTranslationEntries = { + "misty": "Misty", + "mistyStartMessage": "Mist swirled around the battlefield!", + "mistyClearMessage": "The mist disappeared from the battlefield.", + "mistyBlockMessage": "{{pokemonNameWithAffix}} surrounds itself with a protective mist!", + + "electric": "Electric", + "electricStartMessage": "An electric current ran across the battlefield!", + "electricClearMessage": "The electricity disappeared from the battlefield.", + + "grassy": "Grassy", + "grassyStartMessage": "Grass grew to cover the battlefield!", + "grassyClearMessage": "The grass disappeared from the battlefield.", + + "psychic": "Psychic", + "psychicStartMessage": "The battlefield got weird!", + "psychicClearMessage": "The weirdness disappeared from the battlefield!", + + "defaultBlockMessage": "{{pokemonNameWithAffix}} is protected by the {{terrainName}} Terrain!" +}; diff --git a/src/locales/ko/battle.ts b/src/locales/ko/battle.ts index 25ff106946b..dbb425da63f 100644 --- a/src/locales/ko/battle.ts +++ b/src/locales/ko/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "{{pokemonName}}[[를]]\n교체하시겠습니까?", "trainerDefeated": "{{trainerName}}[[와]]의\n승부에서 이겼다!", "moneyWon": "상금으로\n₽{{moneyAmount}}을 손에 넣었다!", + "moneyPickedUp": "₽{{moneyAmount}}을 주웠다!", "pokemonCaught": "신난다-!\n{{pokemonName}}[[를]] 잡았다!", "addedAsAStarter": "{{pokemonName}}[[가]]\n스타팅 포켓몬에 추가되었다!", "partyFull": "지닌 포켓몬이 가득 찼습니다. {{pokemonName}}[[를]]\n대신해 포켓몬을 놓아주시겠습니까?", diff --git a/src/locales/ko/config.ts b/src/locales/ko/config.ts index dc64e12356a..5b29a46c044 100644 --- a/src/locales/ko/config.ts +++ b/src/locales/ko/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -39,7 +40,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -73,6 +74,7 @@ export const koConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, @@ -85,6 +87,7 @@ export const koConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, diff --git a/src/locales/ko/egg.ts b/src/locales/ko/egg.ts index 412d97bb7f3..7b10b548bc4 100644 --- a/src/locales/ko/egg.ts +++ b/src/locales/ko/egg.ts @@ -22,7 +22,7 @@ export const egg: SimpleTranslationEntries = { "hatchFromTheEgg": "알이 부화해서\n{{pokemonName}}[[가]] 태어났다!", "eggMoveUnlock": "알 기술 {{moveName}}[[를]]\n사용할 수 있게 되었다!", "rareEggMoveUnlock": "레어 알 기술 {{moveName}}[[를]]\n사용할 수 있게 되었다!", - "moveUPGacha": "기술 UP!", - "shinyUPGacha": "특별색 UP!", + "moveUPGacha": "알 기술 UP!", + "shinyUPGacha": "색이 다른 포켓몬\nUP!", "legendaryUPGacha": "UP!", } as const; diff --git a/src/locales/ko/modifier.ts b/src/locales/ko/modifier.ts new file mode 100644 index 00000000000..c61d2b3def0 --- /dev/null +++ b/src/locales/ko/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}}[[는]]\n{{typeName}}[[로]] 버텼다!!", + "turnHealApply": "{{pokemonNameWithAffix}}[[는]]\n{{typeName}}[[로]] 인해 조금 회복했다.", + "hitHealApply": "{{pokemonNameWithAffix}}[[는]]\n{{typeName}}[[로]] 인해 조금 회복했다.", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}}[[는]] {{typeName}}[[로]]\n정신을 차려 싸울 수 있게 되었다!", + "moneyInterestApply": "{{typeName}}[[로]]부터\n₽{{moneyAmount}}[[를]] 받았다!", + "turnHeldItemTransferApply": "{{pokemonName}}의 {{typeName}}[[는]]\n{{pokemonNameWithAffix}}의 {{itemName}}[[를]] 흡수했다!", + "contactHeldItemTransferApply": "{{pokemonName}}의 {{typeName}}[[는]]\n{{pokemonNameWithAffix}}의 {{itemName}}[[를]] 가로챘다!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}의\n체력이 약간 회복되었다!", +} as const; diff --git a/src/locales/ko/settings.ts b/src/locales/ko/settings.ts index ef1469fc8cb..6514683cd94 100644 --- a/src/locales/ko/settings.ts +++ b/src/locales/ko/settings.ts @@ -84,7 +84,7 @@ export const settings: SimpleTranslationEntries = { "buttonCancel": "취소", "buttonStats": "스탯", "buttonCycleForm": "폼 변환", - "buttonCycleShiny": "특별한 색 변환", + "buttonCycleShiny": "색이 다른 변환", "buttonCycleGender": "성별 변환", "buttonCycleAbility": "특성 변환", "buttonCycleNature": "성격 변환", diff --git a/src/locales/ko/starter-select-ui-handler.ts b/src/locales/ko/starter-select-ui-handler.ts index 6fdd21a3454..d7f8ddbe3ed 100644 --- a/src/locales/ko/starter-select-ui-handler.ts +++ b/src/locales/ko/starter-select-ui-handler.ts @@ -32,7 +32,7 @@ export const starterSelectUiHandler: SimpleTranslationEntries = { "unlockPassive": "패시브 해금", "reduceCost": "코스트 줄이기", "sameSpeciesEgg": "알 구매하기", - "cycleShiny": ": 특별한 색", + "cycleShiny": ": 색이 다른", "cycleForm": ": 폼", "cycleGender": ": 암수", "cycleAbility": ": 특성", diff --git a/src/locales/ko/weather.ts b/src/locales/ko/weather.ts index 9aca57c49d2..c89cc335859 100644 --- a/src/locales/ko/weather.ts +++ b/src/locales/ko/weather.ts @@ -44,3 +44,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "수수께끼의 난기류가 공격을 약하게 만들었다!", "strongWindsClearMessage": "수수께끼의 난기류가 멈췄다!" // 임의번역 }; + +export const terrain: SimpleTranslationEntries = { + "misty": "미스트필드", + "mistyStartMessage": "발밑이 안개로 자욱해졌다!", + "mistyClearMessage": "발밑의 안개가 사라졌다!", + "mistyBlockMessage": "{{pokemonNameWithAffix}}[[를]]\n미스트필드가 지켜주고 있다!", + + "electric": "일렉트릭필드", + "electricStartMessage": "발밑에 전기가 흐르기 시작했다!", + "electricClearMessage": "발밑의 전기가 사라졌다!", + + "grassy": "그래스필드", + "grassyStartMessage": "발밑에 풀이 무성해졌다!", + "grassyClearMessage": "발밑의 풀이 사라졌다!", + + "psychic": "사이코필드", + "psychicStartMessage": "발밑에서 이상한 느낌이 든다!", + "psychicClearMessage": "발밑의 이상한 느낌이 사라졌다!", + + "defaultBlockMessage": "{{pokemonNameWithAffix}}[[를]]\n{{terrainName}}[[가]] 지켜주고 있다!" +}; diff --git a/src/locales/pt_BR/ability-trigger.ts b/src/locales/pt_BR/ability-trigger.ts index 526c6def80d..11cbaed182d 100644 --- a/src/locales/pt_BR/ability-trigger.ts +++ b/src/locales/pt_BR/ability-trigger.ts @@ -1,7 +1,7 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; export const abilityTriggers: SimpleTranslationEntries = { - "blockRecoilDamage" : "{{abilityName}} de {{pokemonName}}\nprotegeu-o do dano de recuo!", + "blockRecoilDamage": "{{abilityName}} de {{pokemonName}}\nprotegeu-o do dano reverso!", "badDreams": "{{pokemonName}} está tendo pesadelos!", "costar": "{{pokemonName}} copiou as mudanças\nde atributo de {{allyName}}!", "iceFaceAvoidedDamage": "{{pokemonName}} evitou\ndanos com sua {{abilityName}}!", @@ -9,5 +9,5 @@ export const abilityTriggers: SimpleTranslationEntries = { "poisonHeal": "{{abilityName}} de {{pokemonName}}\nrestaurou seus PS um pouco!", "trace": "{{pokemonName}} copiou {{abilityName}}\nde {{targetName}}!", "windPowerCharged": "Ser atingido por {{moveName}} carregou {{pokemonName}} com poder!", - "quickDraw":"{{pokemonName}} pode agir mais rápido que o normal\ngraças ao seu Quick Draw!", + "quickDraw": "{{pokemonName}} pode agir mais rápido que o normal\ngraças ao seu Quick Draw!", } as const; diff --git a/src/locales/pt_BR/battle.ts b/src/locales/pt_BR/battle.ts index b63a03b25cf..8c97d2be6de 100644 --- a/src/locales/pt_BR/battle.ts +++ b/src/locales/pt_BR/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "Quer trocar\nde {{pokemonName}}?", "trainerDefeated": "Você derrotou\n{{trainerName}}!", "moneyWon": "Você ganhou\n₽{{moneyAmount}} por ganhar!", + "moneyPickedUp": "Você pegou ₽{{moneyAmount}} do chão!", "pokemonCaught": "{{pokemonName}} foi capturado!", "addedAsAStarter": "{{pokemonName}} foi adicionado\naos seus iniciais!", "partyFull": "Sua equipe está cheia.\nSolte um Pokémon para ter espaço para {{pokemonName}}?", diff --git a/src/locales/pt_BR/biome.ts b/src/locales/pt_BR/biome.ts index 46dad06b0de..0217836ed20 100644 --- a/src/locales/pt_BR/biome.ts +++ b/src/locales/pt_BR/biome.ts @@ -1,7 +1,7 @@ import { SimpleTranslationEntries } from "#app/interfaces/locales"; export const biome: SimpleTranslationEntries = { - "unknownLocation": "Em algum lugar do qual você não se lembra", + "unknownLocation": "em algum lugar do qual você não se lembra", "TOWN": "Cidade", "PLAINS": "Planície", "GRASS": "Grama", diff --git a/src/locales/pt_BR/config.ts b/src/locales/pt_BR/config.ts index 5a571815c97..c02da112770 100644 --- a/src/locales/pt_BR/config.ts +++ b/src/locales/pt_BR/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -39,7 +40,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -73,16 +74,17 @@ export const ptBrConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, - partyUiHandler: partyUiHandler, pokeball: pokeball, pokemon: pokemon, pokemonInfo: pokemonInfo, pokemonInfoContainer: pokemonInfoContainer, saveSlotSelectUiHandler: saveSlotSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, settings: settings, splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, @@ -92,5 +94,6 @@ export const ptBrConfig = { tutorial: tutorial, voucher: voucher, weather: weather, + partyUiHandler: partyUiHandler, modifierSelectUiHandler: modifierSelectUiHandler }; diff --git a/src/locales/pt_BR/modifier.ts b/src/locales/pt_BR/modifier.ts new file mode 100644 index 00000000000..7cc90df5caa --- /dev/null +++ b/src/locales/pt_BR/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}} aguentou o tranco\nusando sua {{typeName}}!", + "turnHealApply": "{{pokemonNameWithAffix}} restaurou um pouco de PS usando\nsuas {{typeName}}!", + "hitHealApply": "{{pokemonNameWithAffix}} restaurou um pouco de PS usando\nsua {{typeName}}!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}} foi revivido\npor sua {{typeName}}!", + "moneyInterestApply": "Você recebeu um juros de ₽{{moneyAmount}}\nde sua {{typeName}}!", + "turnHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} foi absorvido(a)\npelo {{typeName}} de {{pokemonName}}!", + "contactHeldItemTransferApply": "{{itemName}} de {{pokemonNameWithAffix}} foi pego(a)\npela {{typeName}} de {{pokemonName}}!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\nrestaurou um pouco de seus PS!", +} as const; diff --git a/src/locales/pt_BR/weather.ts b/src/locales/pt_BR/weather.ts index 0787b32e416..31e35657c7f 100644 --- a/src/locales/pt_BR/weather.ts +++ b/src/locales/pt_BR/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "The mysterious air current weakened the attack!", "strongWindsClearMessage": "Os ventos fortes diminuíram.", }; + +export const terrain: SimpleTranslationEntries = { + "misty": "Enevoado", + "mistyStartMessage": "Uma névoa se espalhou pelo campo de batalha!", + "mistyClearMessage": "A névou sumiu do campo de batalha.", + "mistyBlockMessage": "{{pokemonNameWithAffix}} se envolveu com uma névoa protetora!", + + "electric": "Elétrico", + "electricStartMessage": "Uma corrente elétrica se espalhou pelo campo de batalha!", + "electricClearMessage": "A eletricidade sumiu do campo de batalha.", + + "grassy": "de Plantas", + "grassyStartMessage": "Grama cresceu para cobrir o campo de batalha!", + "grassyClearMessage": "A grama sumiu do campo de batalha.", + + "psychic": "Psíquico", + "psychicStartMessage": "O campo de batalha ficou esquisito!", + "psychicClearMessage": "A esquisitice sumiu do campo de batalha", + + "defaultBlockMessage": "{{pokemonNameWithAffix}} está protegido pelo Terreno {{terrainName}}!" +}; diff --git a/src/locales/zh_CN/battle.ts b/src/locales/zh_CN/battle.ts index d8388064bd7..ff2e90a2c59 100644 --- a/src/locales/zh_CN/battle.ts +++ b/src/locales/zh_CN/battle.ts @@ -14,6 +14,7 @@ export const battle: SimpleTranslationEntries = { "switchQuestion": "要更换\n{{pokemonName}}吗?", "trainerDefeated": "你击败了\n{{trainerName}}!", "moneyWon": "你赢得了\n₽{{moneyAmount}}!", + "moneyPickedUp": "捡到了 ₽{{moneyAmount}}!", "pokemonCaught": "{{pokemonName}}被抓住了!", "addedAsAStarter": "增加了{{pokemonName}}作为\n一个新的基础宝可梦!", "partyFull": "你的队伍已满员。是否放生其他宝可梦\n为{{pokemonName}}腾出空间?", diff --git a/src/locales/zh_CN/config.ts b/src/locales/zh_CN/config.ts index 8f654b56d9a..eaf7ba4cc09 100644 --- a/src/locales/zh_CN/config.ts +++ b/src/locales/zh_CN/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -39,7 +40,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -73,6 +74,7 @@ export const zhCnConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, @@ -85,6 +87,7 @@ export const zhCnConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, diff --git a/src/locales/zh_CN/modifier.ts b/src/locales/zh_CN/modifier.ts new file mode 100644 index 00000000000..fabd17465b2 --- /dev/null +++ b/src/locales/zh_CN/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}}用{{typeName}}\n撑住了!", + "turnHealApply": "{{pokemonNameWithAffix}}用{{typeName}}\n回复了体力!", + "hitHealApply": "{{pokemonNameWithAffix}}用{{typeName}}\n回复了体力!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}}用{{typeName}}\n恢复了活力!", + "moneyInterestApply": "用{{typeName}}\n获得了 ₽{{moneyAmount}} 利息!", + "turnHeldItemTransferApply": "{{pokemonNameWithAffix}}的{{itemName}}被\n{{pokemonName}}的{{typeName}}吸收了!", + "contactHeldItemTransferApply": "{{pokemonNameWithAffix}}的{{itemName}}被\n{{pokemonName}}的{{typeName}}夺取了!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\n回复了一些体力!", +} as const; diff --git a/src/locales/zh_CN/move.ts b/src/locales/zh_CN/move.ts index a444a59a3ff..6513f3bcfc7 100644 --- a/src/locales/zh_CN/move.ts +++ b/src/locales/zh_CN/move.ts @@ -107,7 +107,7 @@ export const move: MoveTranslationEntries = { }, "rollingKick": { name: "回旋踢", - effect: "一边使身体快速旋转,\n一边踢飞对手进行攻击。有时会使对手畏缩", + effect: "一边使身体快速旋转,\n一边踢飞对手进行攻击。\n有时会使对手畏缩", }, "sandAttack": { name: "泼沙", @@ -179,7 +179,7 @@ export const move: MoveTranslationEntries = { }, "growl": { name: "叫声", - effect: "让对手听可爱的叫声,\n引开注意力使其疏忽,从而降低对手的攻击", + effect: "让对手听可爱的叫声,\n引开注意力使其疏忽,\n从而降低对手的攻击", }, "roar": { name: "吼叫", @@ -399,7 +399,7 @@ export const move: MoveTranslationEntries = { }, "teleport": { name: "瞬间移动", - effect: "当有后备宝可梦时使用,\n就可以进行替换。野生的宝可梦使用则会逃走", + effect: "当有后备宝可梦时使用,\n就可以进行替换。\n野生的宝可梦使用则会逃走", }, "nightShade": { name: "黑夜魔影", @@ -611,7 +611,7 @@ export const move: MoveTranslationEntries = { }, "explosion": { name: "大爆炸", - effect: "引发大爆炸,攻击自己周围所有的宝可梦。\n使用后自己会陷入昏厥", + effect: "引发大爆炸,\n攻击自己周围所有的宝可梦。\n使用后自己会陷入昏厥", }, "furySwipes": { name: "乱抓", @@ -643,7 +643,7 @@ export const move: MoveTranslationEntries = { }, "triAttack": { name: "三重攻击", - effect: "用3种光线进行攻击。\n有时会让对手陷入麻痹、灼伤或冰冻的状态", + effect: "用3种光线进行攻击。\n有时会让对手陷入麻痹、\n灼伤或冰冻的状态", }, "superFang": { name: "愤怒门牙", @@ -659,7 +659,7 @@ export const move: MoveTranslationEntries = { }, "struggle": { name: "挣扎", - effect: "当自己的PP耗尽时,\n努力挣扎攻击对手。自己也会受到少许伤害", + effect: "当自己的PP耗尽时,\n努力挣扎攻击对手。\n自己也会受到少许伤害", }, "sketch": { name: "写生", @@ -775,7 +775,7 @@ export const move: MoveTranslationEntries = { }, "destinyBond": { name: "同命", - effect: "使出招式后,当受到对手攻击陷入昏厥时,\n对手也会一同昏厥。\n连续使出则会失败", + effect: "使出招式后,当受到对手攻击\n陷入昏厥时,对手也会一同昏厥。\n连续使出则会失败", }, "perishSong": { name: "终焉之歌", @@ -911,7 +911,7 @@ export const move: MoveTranslationEntries = { }, "pursuit": { name: "追打", - effect: "当对手替换宝可梦上场时使出此招式的话,\n能够以2倍的威力进行攻击", + effect: "当对手替换宝可梦上场时\n使出此招式的话,\n能够以2倍的威力进行攻击", }, "rapidSpin": { name: "高速旋转", @@ -959,7 +959,7 @@ export const move: MoveTranslationEntries = { }, "rainDance": { name: "求雨", - effect: "在5回合内一直降雨,\n从而提高水属性的招式威力。火属性的招式威\n力则降低", + effect: "在5回合内一直降雨,\n从而提高水属性的招式威力。\n火属性的招式威力则降低", }, "sunnyDay": { name: "大晴天", @@ -979,7 +979,7 @@ export const move: MoveTranslationEntries = { }, "extremeSpeed": { name: "神速", - effect: "以迅雷不及掩耳之势猛撞向对手进行攻击。\n必定能够先制攻击", + effect: "以迅雷不及掩耳之势猛\n撞向对手进行攻击。\n必定能够先制攻击", }, "ancientPower": { name: "原始之力", @@ -1023,7 +1023,7 @@ export const move: MoveTranslationEntries = { }, "swallow": { name: "吞下", - effect: "将积蓄的力量吞下,从而回复自己的HP。\n积蓄得越多,回复越大", + effect: "将积蓄的力量吞下,\n从而回复自己的HP。\n积蓄得越多,回复越大", }, "heatWave": { name: "热风", @@ -1031,7 +1031,7 @@ export const move: MoveTranslationEntries = { }, "hail": { name: "冰雹", - effect: "在5回合内一直降冰雹,\n除冰属性的宝可梦以外,给予全体宝可梦伤害", + effect: "在5回合内一直降冰雹,\n除冰属性的宝可梦以外,\n给予全体宝可梦伤害", }, "torment": { name: "无理取闹", @@ -1059,7 +1059,7 @@ export const move: MoveTranslationEntries = { }, "smellingSalts": { name: "清醒", - effect: "对于麻痹状态下的对手,\n威力会变成2倍。但相反对手的麻痹也会被治愈", + effect: "对于麻痹状态下的对手,\n威力会变成2倍。\n但相反对手的麻痹也会被治愈", }, "followMe": { name: "看我嘛", @@ -1067,7 +1067,7 @@ export const move: MoveTranslationEntries = { }, "naturePower": { name: "自然之力", - effect: "用自然之力进行攻击。\n根据所使用场所的不同,使出的招式也会有所变化", + effect: "用自然之力进行攻击。\n根据所使用场所的不同,\n使出的招式也会有所变化", }, "charge": { name: "充电", @@ -1111,7 +1111,7 @@ export const move: MoveTranslationEntries = { }, "recycle": { name: "回收利用", - effect: "使战斗中已经消耗掉的自己的持有物再生,\n并可以再次使用", + effect: "使战斗中已经消耗掉的\n自己的持有物再生,\n并可以再次使用", }, "revenge": { name: "报复", @@ -1199,7 +1199,7 @@ export const move: MoveTranslationEntries = { }, "mudSport": { name: "玩泥巴", - effect: "一旦使用此招式,周围就会弄得到处是泥。\n在5回合内减弱电属性的招式", + effect: "一旦使用此招式,\n周围就会弄得到处是泥。\n在5回合内减弱电属性的招式", }, "iceBall": { name: "冰球", @@ -1259,7 +1259,7 @@ export const move: MoveTranslationEntries = { }, "overheat": { name: "过热", - effect: "使出全部力量攻击对手。\n使用之后会因为反作用力,自己的特攻大幅降低", + effect: "使出全部力量攻击对手。\n使用之后会因为反作用力,\n自己的特攻大幅降低", }, "odorSleuth": { name: "气味侦测", @@ -1367,11 +1367,11 @@ export const move: MoveTranslationEntries = { }, "poisonTail": { name: "毒尾", - effect: "用尾巴拍打。有时会让对手陷入中毒状态,\n也容易击中要害", + effect: "用尾巴拍打。\n有时会让对手陷入中毒状态,\n也容易击中要害", }, "covet": { name: "渴望", - effect: "一边可爱地撒娇,一边靠近对手进行攻击,\n还能夺取对手携带的道具", + effect: "一边可爱地撒娇,\n一边靠近对手进行攻击,\n还能夺取对手携带的道具", }, "voltTackle": { name: "伏特攻击", @@ -1415,7 +1415,7 @@ export const move: MoveTranslationEntries = { }, "psychoBoost": { name: "精神突进", - effect: "使出全部力量攻击对手。\n使用之后会因为反作用力,自己的特攻大幅降低", + effect: "使出全部力量攻击对手。\n使用之后会因为反作用力,\n自己的特攻大幅降低", }, "roost": { name: "羽栖", @@ -1527,11 +1527,11 @@ export const move: MoveTranslationEntries = { }, "meFirst": { name: "抢先一步", - effect: "提高威力,抢先使出对手想要使出的招式。\n如果不先使出则会失败", + effect: "提高威力,\n抢先使出对手想要使出的招式。\n如果不先使出则会失败", }, "copycat": { name: "仿效", - effect: "模仿对手刚才使出的招式,\n并使出相同招式。如果对手还没出招则会失败", + effect: "模仿对手刚才使出的招式,\n并使出相同招式。\n如果对手还没出招则会失败", }, "powerSwap": { name: "力量互换", @@ -1715,7 +1715,7 @@ export const move: MoveTranslationEntries = { }, "mirrorShot": { name: "镜光射击", - effect: "抛光自己的身体,向对手释放出闪光之力。\n有时会降低对手的命中率", + effect: "抛光自己的身体,\n向对手释放出闪光之力。\n有时会降低对手的命中率", }, "flashCannon": { name: "加农光炮", @@ -1735,11 +1735,11 @@ export const move: MoveTranslationEntries = { }, "dracoMeteor": { name: "流星群", - effect: "从天空中向对手落下陨石。\n使用之后因为反作用力,自己的特攻会大幅降低", + effect: "从天空中向对手落下陨石。\n使用之后因为反作用力,\n自己的特攻会大幅降低", }, "discharge": { name: "放电", - effect: "用耀眼的电击攻击自己周围所有的宝可梦。\n有时会陷入麻痹状态", + effect: "用耀眼的电击攻击\n自己周围所有的宝可梦。\n有时会陷入麻痹状态", }, "lavaPlume": { name: "喷烟", @@ -1791,7 +1791,7 @@ export const move: MoveTranslationEntries = { }, "chatter": { name: "喋喋不休", - effect: "用非常烦人的,喋喋不休的音波攻击对手。\n使对手混乱", + effect: "用非常烦人的,\n喋喋不休的音波攻击对手。\n使对手混乱", }, "judgment": { name: "制裁光砾", @@ -1827,7 +1827,7 @@ export const move: MoveTranslationEntries = { }, "headSmash": { name: "双刃头锤", - effect: "拼命使出浑身力气,向对手进行头锤攻击。\n自己也会受到非常大的伤害", + effect: "拼命使出浑身力气,\n向对手进行头锤攻击。\n自己也会受到非常大的伤害", }, "doubleHit": { name: "二连击", @@ -1943,7 +1943,7 @@ export const move: MoveTranslationEntries = { }, "electroBall": { name: "电球", - effect: "用电气团撞向对手。自己比对手速度越快,\n威力越大", + effect: "用电气团撞向对手。\n自己比对手速度越快,\n威力越大", }, "soak": { name: "浸水", @@ -1991,7 +1991,7 @@ export const move: MoveTranslationEntries = { }, "chipAway": { name: "逐步击破", - effect: "看准机会稳步攻击。无视对手的能力变化,\n直接给予伤害", + effect: "看准机会稳步攻击。\n无视对手的能力变化,\n直接给予伤害", }, "clearSmog": { name: "清除之烟", @@ -2007,7 +2007,7 @@ export const move: MoveTranslationEntries = { }, "allySwitch": { name: "交换场地", - effect: "用神奇的力量瞬间移动,\n互换自己和同伴所在的位置。连续使出则容易失败", + effect: "用神奇的力量瞬间移动,\n互换自己和同伴所在的位置。\n连续使出则容易失败", }, "scald": { name: "热水", @@ -2115,7 +2115,7 @@ export const move: MoveTranslationEntries = { }, "drillRun": { name: "直冲钻", - effect: "像钢钻一样,一边旋转身体一边撞击对手。\n容易击中要害", + effect: "像钢钻一样,\n一边旋转身体一边撞击对手。\n容易击中要害", }, "dualChop": { name: "二连劈", @@ -2199,11 +2199,11 @@ export const move: MoveTranslationEntries = { }, "boltStrike": { name: "雷击", - effect: "让强大的电流覆盖全身,\n猛撞向对手进行攻击。有时会让对手陷入麻痹状态", + effect: "让强大的电流覆盖全身,\n猛撞向对手进行攻击。\n有时会让对手陷入麻痹状态", }, "blueFlare": { name: "青焰", - effect: "用美丽而激烈的青焰包裹住对手进行攻击。\n有时会让对手陷入灼伤状态", + effect: "用美丽而激烈的青焰\n包裹住对手进行攻击。\n有时会让对手陷入灼伤状态", }, "fieryDance": { name: "火之舞", @@ -2211,7 +2211,7 @@ export const move: MoveTranslationEntries = { }, "freezeShock": { name: "冰冻伏特", - effect: "用覆盖着电流的冰块,\n在第2回合撞向对手。有时会让对手陷入麻痹状态", + effect: "用覆盖着电流的冰块,\n在第2回合撞向对手。\n有时会让对手陷入麻痹状态", }, "iceBurn": { name: "极寒冷焰", @@ -2263,7 +2263,7 @@ export const move: MoveTranslationEntries = { }, "phantomForce": { name: "潜灵奇袭", - effect: "第1回合消失在某处,\n第2回合攻击对手。可以无视守护进行攻击", + effect: "第1回合消失在某处,\n第2回合攻击对手。\n可以无视守护进行攻击", }, "trickOrTreat": { name: "万圣夜", @@ -2295,7 +2295,7 @@ export const move: MoveTranslationEntries = { }, "disarmingVoice": { name: "魅惑之声", - effect: "发出魅惑的叫声,给予对手精神上的伤害。\n攻击必定会命中", + effect: "发出魅惑的叫声,\n给予对手精神上的伤害。\n攻击必定会命中", }, "partingShot": { name: "抛下狠话", @@ -2311,7 +2311,7 @@ export const move: MoveTranslationEntries = { }, "craftyShield": { name: "戏法防守", - effect: "使用神奇的力量防住攻击我方的变化招式。\n但无法防住伤害招式的攻击", + effect: "使用神奇的力量防住\n攻击我方的变化招式。\n但无法防住伤害招式的攻击", }, "flowerShield": { name: "鲜花防守", @@ -2351,7 +2351,7 @@ export const move: MoveTranslationEntries = { }, "kingsShield": { name: "王者盾牌", - effect: "防住对手攻击的同时,\n自己变为防御姿态。能够降低所接触到的对手的攻击", + effect: "防住对手攻击的同时,\n自己变为防御姿态。\n能够降低所接触到的对手的攻击", }, "playNice": { name: "和睦相处", @@ -2395,7 +2395,7 @@ export const move: MoveTranslationEntries = { }, "venomDrench": { name: "毒液陷阱", - effect: "将特殊的毒液泼向对手。\n对处于中毒状态的对手,其攻击、特攻和速\n度都会降低", + effect: "将特殊的毒液泼向对手。\n对处于中毒状态的对手,其攻击、\n特攻和速度都会降低", }, "powder": { name: "粉尘", @@ -2459,15 +2459,15 @@ export const move: MoveTranslationEntries = { }, "thousandWaves": { name: "千波激荡", - effect: "从地面掀起波浪进行攻击。\n被掀入波浪中的对手,将无法从战斗中逃走", + effect: "从地面掀起波浪进行攻击。\n被掀入波浪中的对手,\n将无法从战斗中逃走", }, "landsWrath": { name: "大地神力", - effect: "聚集大地的力量,将此力量集中攻击对手,\n并给予伤害", + effect: "聚集大地的力量,\n将此力量集中攻击对手,\n并给予伤害", }, "lightOfRuin": { name: "破灭之光", - effect: "借用永恒之花的力量,\n发射出强力光线。自己也会受到非常大的伤害", + effect: "借用永恒之花的力量,\n发射出强力光线。\n自己也会受到非常大的伤害", }, "originPulse": { name: "根源波动", @@ -2495,43 +2495,43 @@ export const move: MoveTranslationEntries = { }, "allOutPummelingPhysical": { name: "格斗Z全力无双激烈拳", - effect: "通过Z力量制造出能量弹,\n全力撞向对手。威力会根据原来的招式而改变", + effect: "通过Z力量制造出能量弹,\n全力撞向对手。\n威力会根据原来的招式而改变", }, "allOutPummelingSpecial": { name: "格斗Z全力无双激烈拳", - effect: "通过Z力量制造出能量弹,\n全力撞向对手。威力会根据原来的招式而改变", + effect: "通过Z力量制造出能量弹,\n全力撞向对手。\n威力会根据原来的招式而改变", }, "supersonicSkystrikePhysical": { name: "飞行Z极速俯冲轰烈撞", - effect: "通过Z力量猛烈地飞向天空,\n朝对手全力落下。威力会根据原来的招\n式而改变", + effect: "通过Z力量猛烈地飞向天空,\n朝对手全力落下。\n威力会根据原来的招式而改变", }, "supersonicSkystrikeSpecial": { name: "飞行Z极速俯冲轰烈撞", - effect: "通过Z力量猛烈地飞向天空,\n朝对手全力落下。威力会根据原来的招\n式而改变", + effect: "通过Z力量猛烈地飞向天空,\n朝对手全力落下。\n威力会根据原来的招式而改变", }, "acidDownpourPhysical": { name: "毒Z强酸剧毒灭绝雨", - effect: "通过Z力量使毒沼涌起,\n全力让对手沉下去。威力会根据原来的招式而改变", + effect: "通过Z力量使毒沼涌起,\n全力让对手沉下去。\n威力会根据原来的招式而改变", }, "acidDownpourSpecial": { name: "毒Z强酸剧毒灭绝雨", - effect: "通过Z力量使毒沼涌起,\n全力让对手沉下去。威力会根据原来的招式而改变", + effect: "通过Z力量使毒沼涌起,\n全力让对手沉下去。\n威力会根据原来的招式而改变", }, "tectonicRagePhysical": { name: "地面Z地隆啸天大终结", - effect: "通过Z力量潜入地里最深处,\n全力撞上对手。威力会根据原来的招式而改变", + effect: "通过Z力量潜入地里最深处,\n全力撞上对手。\n威力会根据原来的招式而改变", }, "tectonicRageSpecial": { name: "地面Z地隆啸天大终结", - effect: "通过Z力量潜入地里最深处,\n全力撞上对手。威力会根据原来的招式而改变", + effect: "通过Z力量潜入地里最深处,\n全力撞上对手。\n威力会根据原来的招式而改变", }, "continentalCrushPhysical": { name: "岩石Z毁天灭地巨岩坠", - effect: "通过Z力量召唤大大的岩山,\n全力撞向对手。威力会根据原来的招式而改变", + effect: "通过Z力量召唤大大的岩山,\n全力撞向对手。\n威力会根据原来的招式而改变", }, "continentalCrushSpecial": { name: "岩石Z毁天灭地巨岩坠", - effect: "通过Z力量召唤大大的岩山,\n全力撞向对手。威力会根据原来的招式而改变", + effect: "通过Z力量召唤大大的岩山,\n全力撞向对手。\n威力会根据原来的招式而改变", }, "savageSpinOutPhysical": { name: "虫Z绝对捕食回旋斩", @@ -2543,43 +2543,43 @@ export const move: MoveTranslationEntries = { }, "neverEndingNightmarePhysical": { name: "幽灵Z无尽暗夜之诱惑", - effect: "通过Z力量召唤强烈的怨念,\n全力降临到对手身上。威力会根据原来\n的招式而改变", + effect: "通过Z力量召唤强烈的怨念,\n全力降临到对手身上。\n威力会根据原来的招式而改变", }, "neverEndingNightmareSpecial": { name: "幽灵Z无尽暗夜之诱惑", - effect: "通过Z力量召唤强烈的怨念,\n全力降临到对手身上。威力会根据原来\n的招式而改变", + effect: "通过Z力量召唤强烈的怨念,\n全力降临到对手身上。\n威力会根据原来的招式而改变", }, "corkscrewCrashPhysical": { name: "钢Z超绝螺旋连击", - effect: "通过Z力量进行高速旋转,\n全力撞上对手。威力会根据原来的招式而改变", + effect: "通过Z力量进行高速旋转,\n全力撞上对手。\n威力会根据原来的招式而改变", }, "corkscrewCrashSpecial": { name: "钢Z超绝螺旋连击", - effect: "通过Z力量进行高速旋转,\n全力撞上对手。威力会根据原来的招式而改变", + effect: "通过Z力量进行高速旋转,\n全力撞上对手。\n威力会根据原来的招式而改变", }, "infernoOverdrivePhysical": { name: "火Z超强极限爆焰弹", - effect: "通过Z力量喷出熊熊烈火,\n全力撞向对手。威力会根据原来的招式而改变", + effect: "通过Z力量喷出熊熊烈火,\n全力撞向对手。\n威力会根据原来的招式而改变", }, "infernoOverdriveSpecial": { name: "火Z超强极限爆焰弹", - effect: "通过Z力量喷出熊熊烈火,\n全力撞向对手。威力会根据原来的招式而改变", + effect: "通过Z力量喷出熊熊烈火,\n全力撞向对手。\n威力会根据原来的招式而改变", }, "hydroVortexPhysical": { name: "水Z超级水流大漩涡", - effect: "通过Z力量制造大大的潮旋,\n全力吞没对手。威力会根据原来的招式而改变", + effect: "通过Z力量制造大大的潮旋,\n全力吞没对手。\n威力会根据原来的招式而改变", }, "hydroVortexSpecial": { name: "水Z超级水流大漩涡", - effect: "通过Z力量制造大大的潮旋,\n全力吞没对手。威力会根据原来的招式而改变", + effect: "通过Z力量制造大大的潮旋,\n全力吞没对手。\n威力会根据原来的招式而改变", }, "bloomDoomPhysical": { name: "草Z绚烂缤纷花怒放", - effect: "通过Z力量借助花草的能量,\n全力攻击对手。威力会根据原来的招式而改变", + effect: "通过Z力量借助花草的能量,\n全力攻击对手。\n威力会根据原来的招式而改变", }, "bloomDoomSpecial": { name: "草Z绚烂缤纷花怒放", - effect: "通过Z力量借助花草的能量,\n全力攻击对手。威力会根据原来的招式而改变", + effect: "通过Z力量借助花草的能量,\n全力攻击对手。\n威力会根据原来的招式而改变", }, "gigavoltHavocPhysical": { name: "电Z终极伏特狂雷闪", @@ -2591,43 +2591,43 @@ export const move: MoveTranslationEntries = { }, "shatteredPsychePhysical": { name: "超能力Z至高精神破坏波", - effect: "通过Z力量操纵对手,\n全力使其感受到痛苦。威力会根据原来的招式而改变", + effect: "通过Z力量操纵对手,\n全力使其感受到痛苦。\n威力会根据原来的招式而改变", }, "shatteredPsycheSpecial": { name: "超能力Z至高精神破坏波", - effect: "通过Z力量操纵对手,\n全力使其感受到痛苦。威力会根据原来的招式而改变", + effect: "通过Z力量操纵对手,\n全力使其感受到痛苦。\n威力会根据原来的招式而改变", }, "subzeroSlammerPhysical": { name: "冰Z激狂大地万里冰", - effect: "通过Z力量急剧降低气温,\n全力冰冻对手。威力会根据原来的招式而改变", + effect: "通过Z力量急剧降低气温,\n全力冰冻对手。\n威力会根据原来的招式而改变", }, "subzeroSlammerSpecial": { name: "冰Z激狂大地万里冰", - effect: "通过Z力量急剧降低气温,\n全力冰冻对手。威力会根据原来的招式而改变", + effect: "通过Z力量急剧降低气温,\n全力冰冻对手。\n威力会根据原来的招式而改变", }, "devastatingDrakePhysical": { name: "龙Z究极巨龙震天地", - effect: "通过Z力量将气场实体化,\n向对手全力发动袭击。威力会根据原来的\n招式而改变", + effect: "通过Z力量将气场实体化,\n向对手全力发动袭击。\n威力会根据原来的招式而改变", }, "devastatingDrakeSpecial": { name: "龙Z究极巨龙震天地", - effect: "通过Z力量将气场实体化,\n向对手全力发动袭击。威力会根据原来的\n招式而改变", + effect: "通过Z力量将气场实体化,\n向对手全力发动袭击。\n威力会根据原来的招式而改变", }, "blackHoleEclipsePhysical": { name: "恶Z黑洞吞噬万物灭", - effect: "通过Z力量收集恶能量,\n全力将对手吸入。威力会根据原来的招式而改变", + effect: "通过Z力量收集恶能量,\n全力将对手吸入。\n威力会根据原来的招式而改变", }, "blackHoleEclipseSpecial": { name: "恶Z黑洞吞噬万物灭", - effect: "通过Z力量收集恶能量,\n全力将对手吸入。威力会根据原来的招式而改变", + effect: "通过Z力量收集恶能量,\n全力将对手吸入。\n威力会根据原来的招式而改变", }, "twinkleTacklePhysical": { name: "妖精Z可爱星星飞天撞", - effect: "通过Z力量制造魅惑空间,\n全力捉弄对手。威力会根据原来的招式而改变", + effect: "通过Z力量制造魅惑空间,\n全力捉弄对手。\n威力会根据原来的招式而改变", }, "twinkleTackleSpecial": { name: "妖精Z可爱星星飞天撞", - effect: "通过Z力量制造魅惑空间,\n全力捉弄对手。威力会根据原来的招式而改变", + effect: "通过Z力量制造魅惑空间,\n全力捉弄对手。\n威力会根据原来的招式而改变", }, "catastropika": { name: "皮卡丘Z皮卡皮卡必杀击", @@ -2651,7 +2651,7 @@ export const move: MoveTranslationEntries = { }, "darkestLariat": { name: "DD金勾臂", - effect: "旋转双臂打向对手。无视对手的能力变化,\n直接给予伤害", + effect: "旋转双臂打向对手。\n无视对手的能力变化,\n直接给予伤害", }, "sparklingAria": { name: "泡影的咏叹调", @@ -2671,7 +2671,7 @@ export const move: MoveTranslationEntries = { }, "strengthSap": { name: "吸取力量", - effect: "给自己回复和对手攻击力相同数值的HP,\n然后降低对手的攻击", + effect: "给自己回复和对手攻击力\n相同数值的HP,\n然后降低对手的攻击", }, "solarBlade": { name: "日光刃", @@ -2727,7 +2727,7 @@ export const move: MoveTranslationEntries = { }, "burnUp": { name: "燃尽", - effect: "将自己全身燃烧起火焰来,\n给予对手大大的伤害。自己的火属性将会消失", + effect: "将自己全身燃烧起火焰来,\n给予对手大大的伤害。\n自己的火属性将会消失", }, "speedSwap": { name: "速度互换", @@ -2763,7 +2763,7 @@ export const move: MoveTranslationEntries = { }, "clangingScales": { name: "鳞片噪音", - effect: "摩擦全身鳞片,发出响亮的声音进行攻击。\n攻击后自己的防御会降低", + effect: "摩擦全身鳞片,\n发出响亮的声音进行攻击。\n攻击后自己的防御会降低", }, "dragonHammer": { name: "龙锤", @@ -2827,7 +2827,7 @@ export const move: MoveTranslationEntries = { }, "stompingTantrum": { name: "跺脚", - effect: "化悔恨为力量进行攻击。\n如果上一回合招式没有打中,威力就会翻倍", + effect: "化悔恨为力量进行攻击。\n如果上一回合招式没有打中,\n威力就会翻倍", }, "shadowBone": { name: "暗影之骨", @@ -2871,7 +2871,7 @@ export const move: MoveTranslationEntries = { }, "multiAttack": { name: "多属性攻击", - effect: "一边覆盖高能量,一边撞向对手进行攻击。\n根据存储碟不同,\n属性会改变", + effect: "一边覆盖高能量,\n一边撞向对手进行攻击。\n根据存储碟不同,\n属性会改变", }, "tenMillionVoltThunderbolt": { name: "智皮卡Z千万伏特", @@ -2895,7 +2895,7 @@ export const move: MoveTranslationEntries = { }, "searingSunrazeSmash": { name: "索尔迦雷欧Z日光回旋下苍穹", - effect: "得到Z力量的索尔迦雷欧将全力进行攻击。\n可以无视对手的特性效果", + effect: "得到Z力量的索尔迦雷欧\n将全力进行攻击。\n可以无视对手的特性效果", }, "menacingMoonrazeMaelstrom": { name: "露奈雅拉Z月华飞溅落灵霄", @@ -2911,7 +2911,7 @@ export const move: MoveTranslationEntries = { }, "clangorousSoulblaze": { name: "杖尾鳞甲龙Z炽魂热舞烈音爆", - effect: "得到Z力量的杖尾鳞甲龙将全力攻击对手。\n并且自己的能力会提高", + effect: "得到Z力量的杖尾鳞甲龙\n将全力攻击对手。\n并且自己的能力会提高", }, "zippyZap": { name: "电电加速", @@ -2983,7 +2983,7 @@ export const move: MoveTranslationEntries = { }, "jawLock": { name: "紧咬不放", - effect: "使双方直到一方昏厥为止无法替换宝可梦。\n其中一方退场则可以解除效果", + effect: "使双方直到一方昏厥为止\n无法替换宝可梦。\n其中一方退场则可以解除效果", }, "stuffCheeks": { name: "大快朵颐", @@ -3015,11 +3015,11 @@ export const move: MoveTranslationEntries = { }, "boltBeak": { name: "电喙", - effect: "用带电的喙啄刺对手。\n如果比对手先出手攻击,招式的威力会变成2倍", + effect: "用带电的喙啄刺对手。\n如果比对手先出手攻击,\n招式的威力会变成2倍", }, "fishiousRend": { name: "鳃咬", - effect: "用坚硬的腮咬住对手。\n如果比对手先出手攻击,招式的威力会变成2倍", + effect: "用坚硬的腮咬住对手。\n如果比对手先出手攻击,\n招式的威力会变成2倍", }, "courtChange": { name: "换场", @@ -3179,7 +3179,7 @@ export const move: MoveTranslationEntries = { }, "eternabeam": { name: "无极光束", - effect: "无极汰那变回原来的样子后,\n发动的最强攻击。下一回合自己将无法动弹", + effect: "无极汰那变回原来的样子后,\n发动的最强攻击。\n下一回合自己将无法动弹", }, "steelBeam": { name: "铁蹄光线", @@ -3227,7 +3227,7 @@ export const move: MoveTranslationEntries = { }, "burningJealousy": { name: "妒火", - effect: "用嫉妒的能量攻击对手。\n会让在该回合内能力有所提高的宝可梦陷入\n灼伤状态", + effect: "用嫉妒的能量攻击对手。\n会让在该回合内能力有所提高\n的宝可梦陷入灼伤状态", }, "lashOut": { name: "泄愤", @@ -3291,7 +3291,7 @@ export const move: MoveTranslationEntries = { }, "thunderousKick": { name: "雷鸣蹴击", - effect: "以雷电般的动作戏耍对手的同时使出脚踢。\n可降低对手的防御", + effect: "以雷电般的动作\n戏耍对手的同时使出脚踢。\n可降低对手的防御", }, "glacialLance": { name: "雪矛", @@ -3307,7 +3307,7 @@ export const move: MoveTranslationEntries = { }, "direClaw": { name: "克命爪", - effect: "以破灭之爪进行攻击。\n有时还会让对手陷入中毒、麻痹、睡眠之中的\n一种状态", + effect: "以破灭之爪进行攻击。\n有时还会让对手陷入中毒、麻痹、\n睡眠之中的一种状态", }, "psyshieldBash": { name: "屏障猛攻", @@ -3323,7 +3323,7 @@ export const move: MoveTranslationEntries = { }, "springtideStorm": { name: "阳春风暴", - effect: "用交织着爱与恨的烈风席卷对手进行攻击。\n有时会降低对手的攻击", + effect: "用交织着爱与恨的烈风席卷对手\n进行攻击。有时会降低对手的攻击", }, "mysticalPower": { name: "神秘之力", @@ -3355,7 +3355,7 @@ export const move: MoveTranslationEntries = { }, "barbBarrage": { name: "毒千针", - effect: "用无数的毒针进行攻击。\n有时还会让对手陷入中毒状态。\n攻击处于中毒状态的对手时,威力会变成2倍", + effect: "用无数的毒针进行攻击。\n有时还会让对手陷入中毒状态。\n攻击处于中毒状态的对手时,\n威力会变成2倍", }, "esperWing": { name: "气场之翼", @@ -3375,11 +3375,11 @@ export const move: MoveTranslationEntries = { }, "infernalParade": { name: "群魔乱舞", - effect: "用无数的火球进行攻击。\n有时会让对手陷入灼伤状态。攻击处于异常\n状态的对手时,威力会变成2倍", + effect: "用无数的火球进行攻击。有时会让对手陷\n入灼伤状态。攻击处于异常状态\n的对手时,威力会变成2倍", }, "ceaselessEdge": { name: "秘剑・千重涛", - effect: "用贝壳之剑进行攻击。\n散落的贝壳碎片会散落在对手脚下成为撒菱", + effect: "用贝壳之剑进行攻击。\n散落的贝壳碎片会散落\n在对手脚下成为撒菱", }, "bleakwindStorm": { name: "枯叶风暴", @@ -3487,7 +3487,7 @@ export const move: MoveTranslationEntries = { }, "gMaxSmite": { name: "超极巨天谴雷诛", - effect: "超极巨化的布莉姆温使出的妖精属性攻击。\n会让对手陷入混乱状态", + effect: "超极巨化的布莉姆温使出的\n妖精属性攻击。\n会让对手陷入混乱状态", }, "gMaxSteelsurge": { name: "超极巨钢铁阵法", @@ -3515,7 +3515,7 @@ export const move: MoveTranslationEntries = { }, "gMaxDrumSolo": { name: "超极巨狂擂乱打", - effect: "超极巨化的轰擂金刚猩使出的草属性攻击。\n不会受到对手特性的干扰", + effect: "超极巨化的轰擂金刚猩使出的\n草属性攻击。\n不会受到对手特性的干扰", }, "gMaxFireball": { name: "超极巨破阵火球", @@ -3567,11 +3567,11 @@ export const move: MoveTranslationEntries = { }, "spinOut": { name: "疾速转轮", - effect: "通过往腿上增加负荷,\n以激烈的旋转给予对手伤害。自己的速度会大幅降低", + effect: "通过往腿上增加负荷,\n以激烈的旋转给予对手伤害。\n自己的速度会大幅降低", }, "populationBomb": { name: "鼠数儿", - effect: "伙伴们会纷纷赶来集合,\n以群体行动给予对手攻击。连续命中1~10次", + effect: "伙伴们会纷纷赶来集合,\n以群体行动给予对手攻击。\n连续命中1~10次", }, "iceSpinner": { name: "冰旋", @@ -3583,11 +3583,11 @@ export const move: MoveTranslationEntries = { }, "revivalBlessing": { name: "复生祈祷", - effect: "通过以慈爱之心祈祷,\n让陷入昏厥的后备宝可梦以回复一半HP的状态复活", + effect: "通过以慈爱之心祈祷,\n让陷入昏厥的后备宝可梦\n以回复一半HP的状态复活", }, "saltCure": { name: "盐腌", - effect: "使对手陷入盐腌状态,\n每回合给予对手伤害。对手为钢或水属性时会更痛苦", + effect: "使对手陷入盐腌状态,\n每回合给予对手伤害。\n对手为钢或水属性时会更痛苦", }, "tripleDive": { name: "三连钻", @@ -3599,7 +3599,7 @@ export const move: MoveTranslationEntries = { }, "doodle": { name: "描绘", - effect: "把握并映射出对手的本质,\n让自己和同伴宝可梦的特性变得和对手相同", + effect: "把握并映射出对手的本质,\n让自己和同伴宝可梦的特性\n变得和对手相同", }, "filletAway": { name: "甩肉", @@ -3615,7 +3615,7 @@ export const move: MoveTranslationEntries = { }, "torchSong": { name: "闪焰高歌", - effect: "如唱歌一样喷出熊熊燃烧的火焰烧焦对手。\n会提高自己的特攻", + effect: "如唱歌一样喷出熊熊燃烧的火焰\n烧焦对手。会提高自己的特攻", }, "aquaStep": { name: "流水旋舞", @@ -3623,7 +3623,7 @@ export const move: MoveTranslationEntries = { }, "ragingBull": { name: "怒牛", - effect: "狂怒暴牛的猛烈冲撞。\n招式的属性随形态改变,光墙和反射壁等招式\n也能破坏", + effect: "狂怒暴牛的猛烈冲撞。\n招式的属性随形态改变,\n光墙和反射壁等招式也能破坏", }, "makeItRain": { name: "淘金潮", @@ -3631,7 +3631,7 @@ export const move: MoveTranslationEntries = { }, "psyblade": { name: "精神剑", - effect: "用无形的利刃劈开对手。\n处于电气场地时,招式威力会变成1.5倍", + effect: "用无形的利刃劈开对手。\n处于电气场地时,\n招式威力会变成1.5倍", }, "hydroSteam": { name: "水蒸气", @@ -3655,7 +3655,7 @@ export const move: MoveTranslationEntries = { }, "chillyReception": { name: "冷笑话", - effect: "留下冷场的冷笑话后,\n和后备宝可梦进行替换。在5回合内会下雪", + effect: "留下冷场的冷笑话后,\n和后备宝可梦进行替换。\n在5回合内会下雪", }, "tidyUp": { name: "大扫除", @@ -3691,7 +3691,7 @@ export const move: MoveTranslationEntries = { }, "armorCannon": { name: "铠农炮", - effect: "熊熊燃烧自己的铠甲,\n将其做成炮弹射出攻击。自己的防御和特防会降低", + effect: "熊熊燃烧自己的铠甲,\n将其做成炮弹射出攻击。\n自己的防御和特防会降低", }, "bitterBlade": { name: "悔念剑", @@ -3699,7 +3699,7 @@ export const move: MoveTranslationEntries = { }, "doubleShock": { name: "电光双击", - effect: "将全身所有的电力放出,\n给予对手大大的伤害。自己的电属性将会消失", + effect: "将全身所有的电力放出,\n给予对手大大的伤害。\n自己的电属性将会消失", }, "gigatonHammer": { name: "巨力锤", @@ -3743,11 +3743,11 @@ export const move: MoveTranslationEntries = { }, "syrupBomb": { name: "糖浆炸弹", - effect: "使粘稠的麦芽糖浆爆炸,\n让对手陷入满身糖状态,在3回合内持续降\n低其速度", + effect: "使粘稠的麦芽糖浆爆炸,\n让对手陷入满身糖状态,\n在3回合内持续降\n低其速度", }, "ivyCudgel": { name: "棘藤棒", - effect: "用缠有藤蔓的棍棒殴打。\n属性会随所戴的面具而改变。容易击中要害", + effect: "用缠有藤蔓的棍棒殴打。\n属性会随所戴的面具而改变。\n容易击中要害", }, "electroShot": { name: "电光束", @@ -3787,7 +3787,7 @@ export const move: MoveTranslationEntries = { }, "alluringVoice": { name: "魅诱之声", - effect: "用天使般的歌声攻击对手。\n会让此回合内能力有提高的宝可梦陷入混乱状态", + effect: "用天使般的歌声攻击对手。\n会让此回合内能力有提高的\n宝可梦陷入混乱状态", }, "temperFlare": { name: "豁出去", diff --git a/src/locales/zh_CN/weather.ts b/src/locales/zh_CN/weather.ts index d280dfccb95..ea4deffbd55 100644 --- a/src/locales/zh_CN/weather.ts +++ b/src/locales/zh_CN/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "The mysterious air current weakened the attack!", "strongWindsClearMessage": "神秘的乱流停止了。" }; + +export const terrain: SimpleTranslationEntries = { + "misty": "薄雾", + "mistyStartMessage": "脚下雾气缭绕!", + "mistyClearMessage": "脚下的雾气消失不见了!", + "mistyBlockMessage": "{{pokemonNameWithAffix}}正受到薄雾场地的保护!", + + "electric": "电气", + "electricStartMessage": "脚下电光飞闪!", + "electricClearMessage": "脚下的电光消失不见了!", + + "grassy": "青草", + "grassyStartMessage": "脚下青草如茵!", + "grassyClearMessage": "脚下的青草消失不见了!", + + "psychic": "精神", + "psychicStartMessage": "脚下传来了奇妙的感觉!", + "psychicClearMessage": "脚下的奇妙感觉消失了!", + + "defaultBlockMessage": "{{pokemonNameWithAffix}}正受到{{terrainName}}的的保护!" +}; diff --git a/src/locales/zh_TW/battle.ts b/src/locales/zh_TW/battle.ts index bfd3885ca31..bc7b712185a 100644 --- a/src/locales/zh_TW/battle.ts +++ b/src/locales/zh_TW/battle.ts @@ -12,6 +12,7 @@ export const battle: SimpleTranslationEntries = { "trainerGo": "{{trainerName}} 派出了 {{pokemonName}}!", "switchQuestion": "要更換\n{{pokemonName}}嗎?", "trainerDefeated": "你擊敗了\n{{trainerName}}!", + "moneyPickedUp": "撿到了 ₽{{moneyAmount}}!", "pokemonCaught": "{{pokemonName}} 被抓住了!", "addedAsAStarter": "{{pokemonName}} has been\nadded as a starter!", "pokemon": "寶可夢", diff --git a/src/locales/zh_TW/config.ts b/src/locales/zh_TW/config.ts index e4dfafdd43e..f19a6941a88 100644 --- a/src/locales/zh_TW/config.ts +++ b/src/locales/zh_TW/config.ts @@ -25,6 +25,7 @@ import { gameStatsUiHandler } from "./game-stats-ui-handler"; import { growth } from "./growth"; import { menu } from "./menu"; import { menuUiHandler } from "./menu-ui-handler"; +import { modifier } from "./modifier"; import { modifierType } from "./modifier-type"; import { move } from "./move"; import { nature } from "./nature"; @@ -39,7 +40,7 @@ import { statusEffect } from "./status-effect"; import { titles, trainerClasses, trainerNames } from "./trainers"; import { tutorial } from "./tutorial"; import { voucher } from "./voucher"; -import { weather } from "./weather"; +import { terrain, weather } from "./weather"; import { partyUiHandler } from "./party-ui-handler"; import { settings } from "./settings.js"; import { common } from "./common.js"; @@ -73,6 +74,7 @@ export const zhTwConfig = { growth: growth, menu: menu, menuUiHandler: menuUiHandler, + modifier: modifier, modifierType: modifierType, move: move, nature: nature, @@ -85,6 +87,7 @@ export const zhTwConfig = { splashMessages: splashMessages, starterSelectUiHandler: starterSelectUiHandler, statusEffect: statusEffect, + terrain: terrain, titles: titles, trainerClasses: trainerClasses, trainerNames: trainerNames, diff --git a/src/locales/zh_TW/modifier.ts b/src/locales/zh_TW/modifier.ts new file mode 100644 index 00000000000..01de87827c0 --- /dev/null +++ b/src/locales/zh_TW/modifier.ts @@ -0,0 +1,12 @@ +import { SimpleTranslationEntries } from "#app/interfaces/locales"; + +export const modifier: SimpleTranslationEntries = { + "surviveDamageApply": "{{pokemonNameWithAffix}}用{{typeName}}\n撐住了!", + "turnHealApply": "{{pokemonNameWithAffix}}用{{typeName}}\n回復了體力!", + "hitHealApply": "{{pokemonNameWithAffix}}用{{typeName}}\n回復了體力!", + "pokemonInstantReviveApply": "{{pokemonNameWithAffix}}用{{typeName}}\n回復了活力!", + "moneyInterestApply": "用{{typeName}}\n獲得了 ₽{{moneyAmount}} 利息!", + "turnHeldItemTransferApply": "{{pokemonNameWithAffix}}的{{itemName}}被\n{{pokemonName}}的{{typeName}}吸收了!", + "contactHeldItemTransferApply": "{{pokemonNameWithAffix}}的{{itemName}}被\n{{pokemonName}}的{{typeName}}奪取了!", + "enemyTurnHealApply": "{{pokemonNameWithAffix}}\n回復了一些體力!", +} as const; diff --git a/src/locales/zh_TW/weather.ts b/src/locales/zh_TW/weather.ts index ae0646ce33d..bfc5e0998dc 100644 --- a/src/locales/zh_TW/weather.ts +++ b/src/locales/zh_TW/weather.ts @@ -43,3 +43,24 @@ export const weather: SimpleTranslationEntries = { "strongWindsEffectMessage": "The mysterious air current weakened the attack!", "strongWindsClearMessage": "神秘的亂流停止了。" }; + +export const terrain: SimpleTranslationEntries = { + "misty": "薄霧", + "mistyStartMessage": "腳下霧氣繚繞!", + "mistyClearMessage": "腳下的霧氣消失不見了!", + "mistyBlockMessage": "{{pokemonNameWithAffix}}正受到薄霧場地的保護!", + + "electric": "電氣", + "electricStartMessage": "腳下電流飛閃!", + "electricClearMessage": "腳下的電流消失了!", + + "grassy": "青草", + "grassyStartMessage": "腳下青草如茵!", + "grassyClearMessage": "腳下的青草消失不見了!", + + "psychic": "精神", + "psychicStartMessage": "腳下傳來了奇妙的感覺!", + "psychicClearMessage": "腳下的奇妙感覺消失了!", + + "defaultBlockMessage": "{{pokemonNameWithAffix}}正受到{{terrainName}}的保護!" +}; diff --git a/src/logger.ts b/src/logger.ts new file mode 100644 index 00000000000..cbba195bee3 --- /dev/null +++ b/src/logger.ts @@ -0,0 +1,1823 @@ +//#region 00 Imports +import i18next from "i18next"; +import * as Utils from "./utils"; +import Pokemon from "./field/pokemon"; +import { PlayerPokemon, EnemyPokemon } from "./field/pokemon"; +import { Nature, getNatureDecrease, getNatureIncrease, getNatureName } from "./data/nature"; +import BattleScene from "./battle-scene"; +import { OptionSelectItem } from "./ui/abstact-option-select-ui-handler"; +import { PokemonHeldItemModifier } from "./modifier/modifier"; +import { getBiomeName, PokemonPools, SpeciesTree } from "./data/biomes"; +import { Mode } from "./ui/ui"; +import { parseSlotData, TitlePhase } from "./phases"; +import Trainer from "./field/trainer"; +import { Species } from "./enums/species"; +import { GameMode, GameModes } from "./game-mode"; +import PokemonSpecies from "./data/pokemon-species"; +//#endregion + + + + + +// #region 01 Variables + +// Value holders +export const rarities = [] +export const rarityslot = [0, ""] +export const Actions = [] + +// Booleans +export const isPreSwitch: Utils.BooleanHolder = new Utils.BooleanHolder(false); +export const isFaintSwitch: Utils.BooleanHolder = new Utils.BooleanHolder(false); +export const SheetsMode = new Utils.BooleanHolder(false) + +// (unused) Stores the current DRPD +/** @deprecated */ +export var StoredLog: DRPD = undefined; + +/** The current DRPD version. */ +export const DRPD_Version = "1.1.0" +/** (Unused / reference only) All the log versions that this mod can keep updated. + * @see updateLog +*/ +export const acceptedVersions = [ + "1.0.0", + "1.0.0a", + "1.1.0", +] + +// #endregion + + + + + +//#region 02 Downloading +/** + * Saves a log to your device. + * @param i The index of the log you want to save. + */ +export function downloadLogByID(i: integer) { + console.log(i) + var d = JSON.parse(localStorage.getItem(logs[i][1])) + const blob = new Blob([ printDRPD("", "", d as DRPD) ], {type: "text/json"}); + const link = document.createElement("a"); + link.href = window.URL.createObjectURL(blob); + var date: string = (d as DRPD).date + var filename: string = date[5] + date[6] + "_" + date[8] + date[9] + "_" + date[0] + date[1] + date[2] + date[3] + "_" + (d as DRPD).label + ".json" + link.download = `${filename}`; + link.click(); + link.remove(); +} +/** + * Saves a log to your device in an alternate format. + * @param i The index of the log you want to save. + */ +export function downloadLogByIDToSheet(i: integer) { + console.log(i) + var d = JSON.parse(localStorage.getItem(logs[i][1])) + SheetsMode.value = true; + const blob = new Blob([ printDRPD("", "", d as DRPD) ], {type: "text/json"}); + SheetsMode.value = false; + const link = document.createElement("a"); + link.href = window.URL.createObjectURL(blob); + var date: string = (d as DRPD).date + var filename: string = date[5] + date[6] + "_" + date[8] + date[9] + "_" + date[0] + date[1] + date[2] + date[3] + "_" + (d as DRPD).label + "_sheetexport" + ".json" + link.download = `${filename}`; + link.click(); + link.remove(); +} +//#endregion + + + + + +// #region 03 Log Handler +/** + * Stores logs. + * Generate a new list with `getLogs()`. + * + * @see getLogs + */ +export const logs: string[][] = [ + ["drpd.json", "drpd", "DRPD", "", "wide_lens", ""], +] +/** @deprecated */ +export const logKeys: string[] = [ + "i", // Instructions/steps + "e", // Encounters + "d", // Debug +]; + +/** + * Uses the save's RNG seed to create a log ID. Used to assign each save its own log. + * @param scene The BattleScene. + * @returns The ID of the current save's log. + */ +export function getLogID(scene: BattleScene) { + return "drpd_log:" + scene.seed +} +/** + * Gets a log's item list storage, for detecting reloads via a change in the loot rewards. + * + * Not used yet. + * @param scene The BattleScene. + * @returns The ID of the current save's log. + */ +export function getItemsID(scene: BattleScene) { + return "drpd_items:" + scene.seed +} +/** + * Resets the `logs` array, and creates a list of all game logs in LocalStorage. + * + * @see logs + */ +export function getLogs() { + while(logs.length > 0) + logs.pop() + for (var i = 0; i < localStorage.length; i++) { + if (localStorage.key(i).substring(0, 9) == "drpd_log:") { + logs.push(["drpd.json", localStorage.key(i), localStorage.key(i).substring(9), "", "", ""]) + for (var j = 0; j < 5; j++) { + var D = parseSlotData(j) + if (D != undefined) + if (logs[logs.length - 1][2] == D.seed) { + logs[logs.length - 1][3] = j.toString() + } + } + } + } +} +/** + * Returns a string for the name of the current game mode. + * @param scene The BattleScene. Used to get the game mode. + * @returns The name of the game mode, for use in naming a game log. + */ +export function getMode(scene: BattleScene) { + if (scene.gameMode == undefined) + return "???" + switch (scene.gameMode.modeId) { + case GameModes.CLASSIC: + return "Classic" + case GameModes.ENDLESS: + return "Endless" + case GameModes.SPLICED_ENDLESS: + return "Spliced Endless" + case GameModes.DAILY: + return "Daily" + case GameModes.CHALLENGE: + return "Challenge" + } +} + +// #endregion + + + + + +// #region 04 Utilities + +/** + * Pulls the current run's DRPD from LocalStorage using the run's RNG seed. + * @param scene The BattleScene. Used to get the wave number, which is what determines the name of the log we need. + * @returns The DRPD file, or `null` if there is no file for this run. + */ +export function getDRPD(scene: BattleScene): DRPD { + var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + if (drpd == undefined || drpd == null) + return null; + drpd = updateLog(drpd); + //scene.arenaFlyout.updateFieldText() + return drpd; +} + +export function save(scene: BattleScene, drpd: DRPD) { + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} + +/** + * Testing purposes only. + */ +export const RNGState: number[] = [] + +/** + * The waves that autosaves are created at. + */ +export const autoCheckpoints: integer[] = [ + 1, + 11, + 21, + 31, + 41, + 50 +] + +/** + * Used to get the filesize of a string. + */ +export const byteSize = str => new Blob([str]).size +/** + * Contains names for different file size units. + * + * B: 1 byte + * + * KB: 1,000 bytes + * + * MB: 1,000,000 bytes + * + * GB: 1,000,000,000 bytes + * + * TB: 1,000,000,000,000 bytes + */ +const filesizes = ["b", "kb", "mb", "gb", "tb"] +/** + * Returns the size of a file, in bytes, KB, MB, GB, or (hopefully not) TB. + * @param str The data to get the size of. + * @returns The file size. Every thousand units is moved up to the next unit and rounded to the nearest tenth (i.e. 1,330,000 bytes will return "1.3mb" - 1,330,000b --> 1,330kb --> 1.3mb) + * @see filesizes + */ +export function getSize(str: string) { + var d = byteSize(str) + var unit = 0 + while (d > 1000 && unit < filesizes.length - 1) { + d = Math.round(d/100)/10 + unit++ + } + return d.toString() + filesizes[unit] +} + +/** + * Compares a Species to a biome's tier pool. + * @param species The species to search for. + * @param pool The SpeciesPool tier to compare. + * @returns whether or not `species` was found in the `pool`. + */ +function checkForPokeInBiome(species: Species, pool: (Species | SpeciesTree)[]): boolean { + //console.log(species, pool) + for (var i = 0; i < pool.length; i++) { + if (typeof pool[i] === "number") { + //console.log(pool[i] + " == " + species + "? " + (pool[i] == species)) + if (pool[i] == species) return true; + } else { + var k = Object.keys(pool[i]) + //console.log(pool[i], k) + for (var j = 0; j < k.length; j++) { + //console.log(pool[i][k[j]] + " == " + species + "? " + (pool[i][k[j]] == species)) + if (pool[i][k[j]] == species) return true; + } + } + } + return false; +} + +/** + * Formats a Pokemon in the player's party. + * + * If multiple Pokemon of the same species exist in the party, it will specify which slot they are in. + * @param scene The BattleScene, for getting the player's party. + * @param index The slot index. + * @returns [INDEX] NAME (example: `[1] Walking Wake` is a Walking Wake in the first party slot) + */ +export function playerPokeName(scene: BattleScene, index: integer | Pokemon | PlayerPokemon) { + var species = [] + var dupeSpecies = [] + for (var i = 0; i < scene.getParty().length; i++) { + if (!species.includes(scene.getParty()[i].name)) { + species.push(scene.getParty()[i].name) + } else if (!dupeSpecies.includes(scene.getParty()[i].name)) { + dupeSpecies.push(scene.getParty()[i].name) + } + } + if (typeof index == "number") { + //console.log(scene.getParty()[index], species, dupeSpecies) + if (dupeSpecies.includes(scene.getParty()[index].name)) + return scene.getParty()[index].name + " (Slot " + (index + 1) + ")" + return scene.getParty()[index].name + } + //console.log(index.name, species, dupeSpecies) + if (dupeSpecies.includes(index.name)) + return index.name + " (Slot " + (scene.getParty().indexOf(index as PlayerPokemon) + 1) + ")" + return index.name +} +/** + * Formats a Pokemon in the opposing party. + * + * If multiple Pokemon of the same species exist in the party, it will specify which slot they are in. + * @param scene The BattleScene, for getting the enemy's party. + * @param index The slot index. + * @returns [INDEX] NAME (example: `[2] Zigzagoon` is a Zigzagoon in the right slot (for a double battle) or in the second party slot (for a single battle against a Trainer)) + */ +export function enemyPokeName(scene: BattleScene, index: integer | Pokemon | EnemyPokemon) { + var species = [] + var dupeSpecies = [] + for (var i = 0; i < scene.getEnemyParty().length; i++) { + if (!species.includes(scene.getEnemyParty()[i].name)) { + species.push(scene.getEnemyParty()[i].name) + } else if (!dupeSpecies.includes(scene.getEnemyParty()[i].name)) { + dupeSpecies.push(scene.getEnemyParty()[i].name) + } + } + if (typeof index == "number") { + //console.log(scene.getEnemyParty()[index], species, dupeSpecies) + if (dupeSpecies.includes(scene.getEnemyParty()[index].name)) + return scene.getEnemyParty()[index].name + " (Slot " + (index + 1) + ")" + return scene.getEnemyParty()[index].name + } + //console.log(index.name, species, dupeSpecies) + if (dupeSpecies.includes(index.name)) + return index.name + " (Slot " + (scene.getEnemyParty().indexOf(index as EnemyPokemon) + 1) + ")" + return index.name +} +// LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "") + +// #endregion + + + + + +// #region 05 DRPD +/** + * Updates a DRPD, checkings its version and making any necessary changes to it in order to keep it up to date. + * + * @param drpd The DRPD document to update. Its version will be read automatically. + * @see DRPD + */ +function updateLog(drpd: DRPD): DRPD { + if (drpd.version == "1.0.0") { + drpd.version = "1.0.0a" + console.log("Updated to 1.0.0a - changed item IDs to strings") + for (var i = 0; i < drpd.waves.length; i++) { + if (drpd.waves[i] != undefined) { + if (drpd.waves[i].pokemon != undefined) { + for (var j = 0; j < drpd.waves[i].pokemon.length; j++) { + for (var k = 0; k < drpd.waves[i].pokemon[j].items.length; k++) { + drpd.waves[i].pokemon[j].items[k].id = drpd.waves[i].pokemon[j].items[k].id.toString() + } + } + } + } + } + for (var j = 0; j < drpd.starters.length; j++) { + for (var k = 0; k < drpd.starters[j].items.length; k++) { + drpd.starters[j].items[k].id = drpd.starters[j].items[k].id.toString() + } + } + } // 1.0.0 → 1.0.0a + if (drpd.version == "1.0.0a") { + drpd.version = "1.1.0" + var RState = Phaser.Math.RND.state() + drpd.uuid = Phaser.Math.RND.uuid() + Phaser.Math.RND.state(RState) + drpd.label = "route" + } // 1.0.0a → 1.1.0 + return drpd; +} + + + + + +/** + * The Daily Run Pathing Description (DRPD) Specification is a JSON standard for organizing the information about a daily run. + */ +export interface DRPD { + /** The version of this run. @see DRPD_Version */ + version: string, + /** The display name of this run. Not to be confused with `label`. Entered by the user. */ + title?: string, + /** The webpage path and internal name of this run. Entered by the user. Not to be confused with `title`, which is only a cosmetic identifier. */ + label: string, + /** A unique ID for this run. Currently unused, but may be used in the future. */ + uuid: string, + /** The name(s) of the users that worked on this run. Entered by the user. */ + authors: string[], + /** The date that this document was created on. Does NOT automatically detect the date of daily runs (It can't) */ + date: string, + /** + * A list of all the waves in this Daily Run. + * + * A completed Daily Run will have 50 waves. + * + * This array automatically sorts by wave number, with blank slots being pushed to the bottom. + * + * @see Wave + */ + waves: Wave[], + /** The Pokemon that the player started with. Daily runs will have 3. @see PokeData */ + starters?: PokeData[] +} +/** + * Imports a string as a DRPD. + * @param drpd The JSON string to import. + * @returns The imported document. + */ +export function importDocument(drpd: string): DRPD { + return JSON.parse(drpd) as DRPD; +} +/** + * Creates a new document in the DRPD format + * @param name (Optional) The name for the file. Defaults to "Untitled Run". + * @param authorName (Optional) The author(s) of the file. Defaults to "Write your name here". + * @returns The fresh DRPD document. + */ +export function newDocument(name: string = "Untitled Run", authorName: string | string[] = "Write your name here"): DRPD { + var ret: DRPD = { + version: DRPD_Version, + title: name, + label: "", + uuid: undefined, + authors: (Array.isArray(authorName) ? authorName : [authorName]), + date: new Date().getUTCFullYear() + "-" + (new Date().getUTCMonth() + 1 < 10 ? "0" : "") + (new Date().getUTCMonth() + 1) + "-" + (new Date().getUTCDate() < 10 ? "0" : "") + new Date().getUTCDate(), + waves: new Array(50), + starters: new Array(3), + } + var RState = Phaser.Math.RND.state() + ret.uuid = Phaser.Math.RND.uuid() + Phaser.Math.RND.state(RState) + return ret; +} +/** + * Prints a DRPD as a string, for saving it to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param drpd The `DRPD` to export. + * @returns `inData`, with all the DRPD's data appended to it. + * + * @see downloadLogByID + */ +export function printDRPD(inData: string, indent: string, drpd: DRPD): string { + console.log("Printing for sheet?: " + SheetsMode.value) + inData += indent + "{" + inData += "\n" + indent + " \"version\": \"" + drpd.version + "\"" + inData += ",\n" + indent + " \"title\": \"" + drpd.title + "\"" + inData += ",\n" + indent + " \"authors\": [\"" + drpd.authors.join("\", \"") + "\"]" + inData += ",\n" + indent + " \"date\": \"" + drpd.date + "\"" + inData += ",\n" + indent + " \"label\": \"" + drpd.label + "\"" + inData += ",\n" + indent + " \"uuid\": \"" + drpd.uuid + "\"" + if (drpd.waves) { + inData += ",\n" + indent + " \"waves\": [\n" + var isFirst = true + for (var i = 0; i < drpd.waves.length; i++) { + if (drpd.waves[i] != undefined && drpd.waves[i] != null) { + if (isFirst) { + isFirst = false; + } else { + inData += ",\n" + } + inData = printWave(inData, indent + " ", drpd.waves[i]) + } + } + inData += "\n" + indent + " ]\n" + } else { + inData += ",\n" + indent + " \"waves\": []" + } + inData += ",\n" + indent + " \"starters\": [\n" + var isFirst = true + for (var i = 0; i < drpd.starters.length; i++) { + if (drpd.starters[i] != undefined && drpd.starters[i] != null) { + if (isFirst) { + isFirst = false; + } else { + inData += ",\n" + } + inData = printPoke(inData, indent + " ", drpd.starters[i]) + } + } + inData += "\n" + indent + " ]\n" + indent + "}" + return inData; +} +// #endregion + + + + + +// #region 06 Wave +/** + * A Wave is one individual battle in the run. + * Each group of ten waves has the same biome. + */ +export interface Wave { + /** The wave number. Used to label the wave, detect and delete duplicates, and automatically sort `DRPD.waves[]`. */ + id: integer, + /** Set to `true` if a reload is required to play this wave properly.Setting this value is the PITA I have ever dealt with. */ + reload: boolean, + /** + * The specific type of wave. + * + * `wild`: This is a wild encounter. + * + * `trainer`: This is a trainer battle. + * + * `boss`: This is a boss floor (floors 10, 20, 30, etc). Overrides the two values above. + */ + type: "wild" | "trainer" | "boss", + /** Set to `true` if this is a double battle. */ + double: boolean, + /** The list of actions that the player took during this wave. */ + actions: string[], + /** The item that the player took in the shop. A blank string (`""`) if there is no shop (wave 10, 20, 30, etc.) or the player fled from battle. */ + shop: string, + /** The biome that this battle takes place in. */ + biome: string, + /** If true, the next time an action is logged, all previous actions will be deleted. + * @see Wave.actions + * @see logActions + * @see resetWaveActions + */ + clearActionsFlag: boolean, + /** The trainer that you fight in this floor, if any. + * @see TrainerData + * @see Wave.type + */ + trainer?: TrainerData, + /** The Pokémon that you have to battle against. + * Not included if this is a trainer battle. + * @see PokeData + * @see Wave.type + */ + pokemon?: PokeData[] +} +/** + * Exports the current battle as a `Wave`. + * @param scene The BattleScene. Used to retrieve information about the current wave. + * @returns The wave data. + */ +export function exportWave(scene: BattleScene): Wave { + var ret: Wave = { + id: scene.currentBattle.waveIndex, + reload: false, + type: scene.getEnemyField()[0].hasTrainer() ? "trainer" : scene.getEnemyField()[0].isBoss() ? "boss" : "wild", + double: scene.currentBattle.double, + actions: [], + shop: "", + clearActionsFlag: false, + biome: getBiomeName(scene.arena.biomeType) + } + if (ret.double == undefined) ret.double = false; + switch (ret.type) { + case "wild": + case "boss": + ret.pokemon = [] + for (var i = 0; i < scene.getEnemyParty().length; i++) { + ret.pokemon.push(exportPokemon(scene.getEnemyParty()[i])) + } + break; + case "trainer": + ret.trainer = { + id: scene.currentBattle.trainer.config.trainerType, + name: scene.currentBattle.trainer.name, + type: scene.currentBattle.trainer.config.title + } + ret.pokemon = [] + for (var i = 0; i < scene.getEnemyParty().length; i++) { + ret.pokemon.push(exportPokemon(scene.getEnemyParty()[i])) + } + break; + } + return ret; +} +/** + * Prints a wave as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `Wave` to export. + * @returns `inData`, with all the wave's data appended to it. + * + * @see printDRPD + */ +function printWave(inData: string, indent: string, wave: Wave): string { + inData += indent + "{" + inData += "\n" + indent + " \"id\": " + wave.id + "" + inData += ",\n" + indent + " \"reload\": " + wave.reload + "" + inData += ",\n" + indent + " \"type\": \"" + wave.type + "\"" + inData += ",\n" + indent + " \"double\": " + wave.double + "" + var isFirst = true + if (wave.actions.length > 0) { + if (SheetsMode.value) { + inData += ",\n" + indent + " \"actions\": \"" + var isFirst = true + for (var i = 0; i < wave.actions.length; i++) { + if (wave.actions[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "CHAR(10)" + } + inData += wave.actions[i] + } + } + inData += "\"" + } else { + inData += ",\n" + indent + " \"actions\": [" + for (var i = 0; i < wave.actions.length; i++) { + if (wave.actions[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "," + } + inData += "\n " + indent + "\"" + wave.actions[i] + "\"" + } + } + if (!isFirst) inData += "\n" + inData += indent + " ]" + } + } else { + inData += ",\n" + indent + " \"actions\": [\"[No actions?]\"]" + } + inData += ",\n " + indent + "\"shop\": \"" + wave.shop + "\"" + inData += ",\n " + indent + "\"biome\": \"" + wave.biome + "\"" + if (wave.trainer) { + inData += ",\n " + indent + "\"trainer\": " + inData = printTrainer(inData, indent + " ", wave.trainer) + } + if (wave.pokemon) + if (wave.pokemon.length > 0) { + inData += ",\n " + indent + "\"pokemon\": [\n" + isFirst = true + for (var i = 0; i < wave.pokemon.length; i++) { + if (wave.pokemon[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += ",\n" + } + inData = printPoke(inData, indent + " ", wave.pokemon[i]) + } + } + if (SheetsMode.value && wave.pokemon.length == 1) { + inData += "," + indent + " \n{" + indent + " \n}" + } + inData += "\n" + indent + " ]" + } + inData += "\n" + indent + "}" + return inData; +} + + + + + +/** + * Retrieves a wave from the DRPD. If the wave doesn't exist, it creates a new one. + * @param drpd The document to read from. + * @param floor The wave index to retrieve. + * @param scene The BattleScene, used for creating a new wave + * @returns The requested `Wave`. + */ +export function getWave(drpd: DRPD, floor: integer, scene: BattleScene): Wave { + var wv: Wave; + var insertPos: integer; + console.log(drpd.waves) + for (var i = 0; i < drpd.waves.length; i++) { + if (drpd.waves[i] != undefined && drpd.waves[i] != null) { + if (drpd.waves[i].id == floor) { + wv = drpd.waves[i] + console.log("Found wave for floor " + floor + " at index " + i) + if (wv.pokemon == undefined) wv.pokemon = [] + return wv; + } + } else if (insertPos == undefined) { + insertPos = i + } + } + if (wv == undefined && insertPos != undefined) { + console.log("Created new wave for floor " + floor + " at index " + insertPos) + drpd.waves[insertPos] = { + id: floor, + reload: false, + //type: floor % 10 == 0 ? "boss" : (floor % 10 == 5 ? "trainer" : "wild"), + type: floor % 10 == 0 ? "boss" : "wild", + double: scene.currentBattle.double, + actions: [], + shop: "", + clearActionsFlag: false, + biome: getBiomeName(scene.arena.biomeType), + //pokemon: [] + } + wv = drpd.waves[insertPos] + } + drpd.waves.sort((a, b) => { + if (a == undefined) return 1; // empty values move to the bottom + if (b == undefined) return -1; // empty values move to the bottom + return a.id - b.id + }) + for (var i = 0; i < drpd.waves.length - 1; i++) { + if (drpd.waves[i] != undefined && drpd.waves[i+1] != undefined) { + if (drpd.waves[i].id == drpd.waves[i+1].id) { + drpd.waves[i] = undefined + drpd.waves.sort((a, b) => { + if (a == undefined) return 1; // empty values move to the bottom + if (b == undefined) return -1; // empty values move to the bottom + return a.id - b.id + }) + } + } + } + if (wv == undefined) { + console.error("Out of wave slots??") + scene.ui.showText("Out of wave slots!\nClearing duplicates...", null, () => { + for (var i = 0; i < drpd.waves.length - 1; i++) { + if (drpd.waves[i] != undefined && drpd.waves[i+1] != undefined) { + if (drpd.waves[i].id == drpd.waves[i+1].id) { + drpd.waves[i+1] = undefined + drpd.waves.sort((a, b) => { + if (a == undefined) return 1; // empty values move to the bottom + if (b == undefined) return -1; // empty values move to the bottom + return a.id - b.id + }) + } + } + } + if (drpd.waves[drpd.waves.length - 1] != undefined) { + if (scene.gameMode.modeId == GameModes.DAILY) { + scene.ui.showText("No space!\nPress F12 for info") + console.error("There should have been 50 slots, but somehow the program ran out of space.") + console.error("Go yell at @redstonewolf8557 to fix this") + } else { + drpd.waves.push(null) + console.log("Created new wave for floor " + floor + " at newly inserted index " + insertPos) + drpd.waves[drpd.waves.length - 1] = { + id: floor, + reload: false, + //type: floor % 10 == 0 ? "boss" : (floor % 10 == 5 ? "trainer" : "wild"), + type: floor % 10 == 0 ? "boss" : "wild", + double: scene.currentBattle.double, + actions: [], + shop: "", + biome: getBiomeName(scene.arena.biomeType), + clearActionsFlag: false, + //pokemon: [] + } + wv = drpd.waves[drpd.waves.length - 1] + } + } else { + for (var i = 0; i < drpd.waves.length; i++) { + if (drpd.waves[i] != undefined && drpd.waves[i] != null) { + if (drpd.waves[i].id == floor) { + wv = drpd.waves[i] + console.log("Found wave for floor " + floor + " at index " + i) + if (wv.pokemon == undefined) wv.pokemon = [] + } + } else if (insertPos == undefined) { + insertPos = i + } + } + if (wv == undefined && insertPos != undefined) { + console.log("Created new wave for floor " + floor + " at index " + insertPos) + drpd.waves[insertPos] = { + id: floor, + reload: false, + //type: floor % 10 == 0 ? "boss" : (floor % 10 == 5 ? "trainer" : "wild"), + type: floor % 10 == 0 ? "boss" : "wild", + double: scene.currentBattle.double, + actions: [], + shop: "", + clearActionsFlag: false, + biome: getBiomeName(scene.arena.biomeType), + //pokemon: [] + } + wv = drpd.waves[insertPos] + } + drpd.waves.sort((a, b) => { + if (a == undefined) return 1; // empty values move to the bottom + if (b == undefined) return -1; // empty values move to the bottom + return a.id - b.id + }) + if (wv == undefined) { + scene.ui.showText("Failed to make space\nPress F12 for info") + console.error("There should be space to store a new wave, but the program failed to find space anyways") + console.error("Go yell at @redstonewolf8557 to fix this") + return undefined; + } + } + }) + } + if (wv == undefined) { + scene.ui.showText("Failed to retrieve wave\nPress F12 for info") + console.error("Failed to retrieve wave??") + console.error("this mod i stg") + console.error("Go yell at @redstonewolf8557 to fix this") + return undefined; + } + return wv; +} +// #endregion + + + + + +// #region 07 Pokémon +/** + * Stores information about a Pokémon. + * + * This data type is used in `DRPD.starters` to list the player's starting Pokémon, or in `Wave.pokemon` to list the opponent(s) in a wild encounter. + */ +export interface PokeData { + /** The party position of this Pokémon, as of the beginning of the battle. */ + id: integer, + /** The name of this Pokémon as it would appear in the party list or in battle. */ + name: string, + /** The Pokémon's primary ability. */ + ability: string, + /** Set to `true` if this Pokémon's ability is its Hidden Ability. + * @see PokeData.ability + */ + isHiddenAbility: boolean, + /** The Pokémon's passive / secondary ability. */ + passiveAbility: string, + /** The Pokémon's nature. Influences its stats. + * @see NatureData + */ + nature: NatureData, + /** The Pokémon's gender. */ + gender: "Male" | "Female" | "Genderless", + /** The Pokémon's encounter rarity within the current biome. */ + rarity: string, + /** Whether or not the Pokémon was captured. */ + captured: boolean, + /** The Pokémon's level. */ + level: integer, + /** The Pokémon's Held Items, if any. + * @see ItemData + */ + items: ItemData[], + /** The Pokémon's IVs. Influences its base stats. + * @see IVData + */ + ivs: IVData, + /** The Pokémon that was used to generate this `PokeData`. Not exported. + * @see Pokemon + */ + source?: Pokemon +} +/** + * Exports a Pokemon's data as `PokeData`. + * @param pokemon The Pokemon to store. + * @param encounterRarity The rarity tier of the Pokemon for this biome. + * @returns The Pokemon data. + */ +export function exportPokemon(pokemon: Pokemon, encounterRarity?: string): PokeData { + return { + id: pokemon.species.speciesId, + name: pokemon.species.getName(), + ability: pokemon.getAbility().name, + isHiddenAbility: pokemon.hasAbility(pokemon.species.abilityHidden), + passiveAbility: pokemon.getPassiveAbility().name, + nature: exportNature(pokemon.nature), + gender: pokemon.gender == 0 ? "Male" : (pokemon.gender == 1 ? "Female" : "Genderless"), + rarity: encounterRarity, + captured: false, + level: pokemon.level, + items: pokemon.getHeldItems().map((item, idx) => exportItem(item)), + ivs: exportIVs(pokemon.ivs) + } +} +/** + * Prints a Pokemon as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `PokeData` to export. + * @returns `inData`, with all the Pokemon's data appended to it. + * + * @see printDRPD + */ +function printPoke(inData: string, indent: string, pokemon: PokeData) { + inData += indent + "{" + inData += "\n" + indent + " \"id\": " + pokemon.id + inData += ",\n" + indent + " \"name\": \"" + pokemon.name + "\"" + inData += ",\n" + indent + " \"ability\": \"" + pokemon.ability + "\"" + inData += ",\n" + indent + " \"isHiddenAbility\": " + pokemon.isHiddenAbility + inData += ",\n" + indent + " \"passiveAbility\": \"" + pokemon.passiveAbility + "\"" + inData += ",\n" + indent + " \"nature\": \n" + inData = printNature(inData, indent + " ", pokemon.nature) + inData += ",\n" + indent + " \"gender\": \"" + pokemon.gender + "\"" + inData += ",\n" + indent + " \"rarity\": \"" + pokemon.rarity + "\"" + inData += ",\n" + indent + " \"captured\": " + pokemon.captured + inData += ",\n" + indent + " \"level\": " + pokemon.level + if (SheetsMode.value) { + inData += ",\n" + indent + " \"items\": \"" + var isFirst = true + for (var i = 0; i < pokemon.items.length; i++) { + if (pokemon.items[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "CHAR(10)" + } + inData += printItemNoNewline(inData, "", pokemon.items[i]) + } + } + inData += "\"" + } else { + if (pokemon.items.length > 0) { + inData += ",\n" + indent + " \"items\": [\n" + var isFirst = true + for (var i = 0; i < pokemon.items.length; i++) { + if (pokemon.items[i] != undefined) { + if (isFirst) { + isFirst = false; + } else { + inData += "," + } + inData = printItem(inData, indent + " ", pokemon.items[i]) + } + } + if (!isFirst) inData += "\n" + inData += indent + " ]" + } else { + inData += ",\n" + indent + " \"items\": []" + } + } + inData += ",\n" + indent + " \"ivs\": " + inData = printIV(inData, indent + " ", pokemon.ivs) + //inData += ",\n" + indent + " \"rarity\": " + pokemon.rarity + inData += "\n" + indent + "}" + return inData; +} + + + + + +/** + * Calls `logPokemon` once for each opponent or, if it's a trainer battle, logs the trainer's data. + * @param scene The BattleScene. Used to get the enemy team and whether it's a trainer battle or not. + * @param floor The wave index to write to. Defaults to the current wave. + */ +export function logTeam(scene: BattleScene, floor: integer = undefined) { + if (floor == undefined) floor = scene.currentBattle.waveIndex + var team = scene.getEnemyParty() + console.log("Log Enemy Team") + 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 + ",0," + sprite + ",trainer," + trainerCat + ",,,,,,,,,,,,", floor, 0) + } else { + for (var i = 0; i < team.length; i++) { + logPokemon(scene, floor, i, team[i], rarities[i]) + } + if (team.length == 1) { + //setRow("e", ",,,,,,,,,,,,,,,,", floor, 1) + } + } +} +// #endregion + + + + + +// #region 08 Nature +/** + * Information about a Pokémon's nature. + */ +export interface NatureData { + /** The display name of this nature. */ + name: string, + /** The stat that gets a 10% increase from this nature, if any. */ + increased: "" | "atk" | "def" | "spatk" | "spdef" | "spe", + /** The stat that gets a 10% decrease from this nature, if any. */ + decreased: "" | "atk" | "def" | "spatk" | "spdef" | "spe" +} +/** + * Exports a Pokemon's nature as `NatureData`. + * @param nature The nature to store. + * @returns The nature data. + */ +export function exportNature(nature: Nature): NatureData { + return { + name: getNatureName(nature), + increased: getNatureIncrease(nature), + decreased: getNatureDecrease(nature), + } +} +/** + * Prints a Nature as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `NatureData` to export. + * @returns `inData`, with all the nature data appended to it. + * + * @see printDRPD + */ +function printNature(inData: string, indent: string, nature: NatureData) { + inData += indent + "{" + inData += "\n" + indent + " \"name\": \"" + nature.name + "\"" + inData += ",\n" + indent + " \"increased\": \"" + nature.increased + "\"" + inData += ",\n" + indent + " \"decreased\": \"" + nature.decreased + "\"" + inData += "\n" + indent + "}" + return inData; +} +// #endregion + + + + + +// #region 09 IVs +/** + * Information about a Pokémon's Individual Values (IVs). + */ +export interface IVData { + /** Influences a Pokémon's maximum health. */ + hp: integer, + /** Influences a Pokémon's physical strength. */ + atk: integer, + /** Influences a Pokémon's resistance to physical attacks. */ + def: integer, + /** Influences the power of a Pokémon's ranged attacks */ + spatk: integer, + /** Influences a Pokémon's resistance to ranged attacks. */ + spdef: integer, + /** Influences a Pokémon's action speed. */ + speed: integer +} +/** + * Exports a Pokémon's IVs as `IVData`. + * @param ivs The IV array to store. + * @returns The IV data. + */ +export function exportIVs(ivs: integer[]): IVData { + return { + hp: ivs[0], + atk: ivs[1], + def: ivs[2], + spatk: ivs[3], + spdef: ivs[4], + speed: ivs[5] + } +} +/** + * Prints a Pokemon's IV data as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `IVData` to export. + * @returns `inData`, with the IV data appended to it. + * + * @see printDRPD + */ +function printIV(inData: string, indent: string, iv: IVData) { + inData += "{" + inData += "\n" + indent + " \"hp\": " + iv.hp + inData += ",\n" + indent + " \"atk\": " + iv.atk + inData += ",\n" + indent + " \"def\": " + iv.def + inData += ",\n" + indent + " \"spatk\": " + iv.spatk + inData += ",\n" + indent + " \"spdef\": " + iv.spdef + inData += ",\n" + indent + " \"spe\": " + iv.speed + inData += "\n" + indent + "}" + return inData; +} +// #endregion + + + + + +// #region 10 Trainer +/** + * A Trainer that the player has to battle against. + * A Trainer will have 1-6 Pokémon in their party, depending on their difficulty. + * + * If the wave has a Trainer, their party is not logged, and `Wave.pokemon` is left empty. + */ +export interface TrainerData { + /** The trainer type's position in the Trainers enum. + * @see Trainer + */ + id: integer, + /** The Trainer's ingame name. */ + name: string, + /** The Trainer's ingame title. */ + type: string, +} +/** + * Exports the opposing trainer as `TrainerData`. + * @param trainer The Trainer to store. + * @returns The Trainer data. + */ +export function exportTrainer(trainer: Trainer): TrainerData { + return { + id: trainer.config.trainerType, + name: trainer.getNameOnly(), + type: trainer.getTitleOnly() + } +} +/** + * Prints a Trainer as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `TrainerData` to export. + * @returns `inData`, with all the Trainer's data appended to it. + * + * @see printDRPD + */ +function printTrainer(inData: string, indent: string, trainer: TrainerData) { + inData += "{" + inData += "\n" + indent + " \"id\": \"" + trainer.id + "\"" + inData += ",\n" + indent + " \"name\": \"" + trainer.name + "\"" + inData += ",\n" + indent + " \"type\": \"" + trainer.type + "\"" + inData += "\n" + indent + "}" + return inData; +} +// #endregion + + + + + +// #region 11 Item +/** An item held by a Pokémon. Quantities and ownership are recorded at the start of the battle, and do not reflect items being used up or stolen. */ +export interface ItemData { + /** A type:key pair identifying the specific item. + * + * Example: `FormChange:TOXIC_PLATE` + */ + id: string, + /** The item's ingame name. */ + name: string, + /** This item's stack size. */ + quantity: integer, +} +/** + * Exports a Held Item as `ItemData`. + * @param item The item to store. + * @returns The item data. + */ +export function exportItem(item: PokemonHeldItemModifier): ItemData { + return { + id: item.type.identifier, + name: item.type.name, + quantity: item.getStackCount() + } +} +/** + * Prints an item as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `ItemData` to export. + * @returns `inData`, with all the Item's data appended to it. + * + * @see printDRPD + */ +function printItem(inData: string, indent: string, item: ItemData) { + inData += indent + "{" + inData += "\n" + indent + " \"id\": \"" + item.id + "\"" + inData += ",\n" + indent + " \"name\": \"" + item.name + "\"" + inData += ",\n" + indent + " \"quantity\": " + item.quantity + inData += "\n" + indent + "}" + return inData; +} +/** + * Prints an item as a string, for saving a DRPD to your device. + * @param inData The data to add on to. + * @param indent The indent string (just a bunch of spaces). + * @param wave The `ItemData` to export. + * @returns `inData`, with all the Item's data appended to it. + * + * @see `downloadLogByIDToSheet` + */ +function printItemNoNewline(inData: string, indent: string, item: ItemData) { + inData = "{\\\"id\\\": \\\"" + item.id + "\\\", \\\"name\\\": \\\"" + item.name + "\\\", \\\"quantity\\\": " + item.quantity + "}" + return inData; +} +// #endregion + + + + + +//#region 12 Ingame Menu + +/** + * Sets the name, author, and [todo] label for a file. + * @param title The display name of the file. + * @param authors The author(s) of the file. + * @todo Add label field. + */ +export function setFileInfo(title: string, authors: string[]) { + console.log("Setting file " + rarityslot[1] + " to " + title + " / [" + authors.join(", ") + "]") + var fileID = rarityslot[1] as string + var drpd = JSON.parse(localStorage.getItem(fileID)) as DRPD; + drpd.title = title; + for (var i = 0; i < authors.length; i++) { + while (authors[i][0] == " ") { + authors[i] = authors[i].substring(1) + } + while (authors[i][authors[i].length - 1] == " ") { + authors[i] = authors[i].substring(0, authors[i].length - 1) + } + } + for (var i = 0; i < authors.length; i++) { + if (authors[i] == "") { + authors.splice(i, 1) + i--; + } + } + drpd.authors = authors; + localStorage.setItem(fileID, JSON.stringify(drpd)) +} + +/** + * Generates a UI option to save a log to your device. + * @param i The slot number. Corresponds to an index in `logs`. + * @param saves Your session data. Used to label logs if they match one of your save slots. + * @returns A UI option. + */ +export function generateOption(i: integer, saves: any): OptionSelectItem { + var filename: string = (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title + var op: OptionSelectItem = { + label: `Export ${filename} (${getSize(printDRPD("", "", JSON.parse(localStorage.getItem(logs[i][1])) as DRPD))})`, + handler: () => { + downloadLogByID(i) + return false; + } + } + for (var j = 0; j < saves.length; j++) { + console.log(saves[j].seed, logs[i][2], saves[j].seed == logs[i][2]) + if (saves[j].seed == logs[i][2]) { + op.label = "[Slot " + (saves[j].slot + 1) + "]" + op.label.substring(6) + } + } + if (logs[i][4] != "") { + op.label = " " + op.label + op.item = logs[i][4] + } + return op; +} +/** + * Generates a UI option to save a log to your device. + * @param i The slot number. Corresponds to an index in `logs`. + * @param saves Your session data. Used to label logs if they match one of your save slots. + * @returns A UI option. + */ +export function generateEditOption(scene: BattleScene, i: integer, saves: any, phase: TitlePhase): OptionSelectItem { + var filename: string = (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title + var op: OptionSelectItem = { + label: `Export ${filename} (${getSize(printDRPD("", "", JSON.parse(localStorage.getItem(logs[i][1])) as DRPD))})`, + handler: () => { + rarityslot[1] = logs[i][1] + //scene.phaseQueue[0].end() + scene.ui.setMode(Mode.NAME_LOG, { + autofillfields: [ + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title, + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).authors.join(", "), + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).label, + ], + buttonActions: [ + () => { + console.log("Rename") + scene.ui.playSelect(); + phase.callEnd() + }, + () => { + console.log("Export") + scene.ui.playSelect(); + downloadLogByID(i) + phase.callEnd() + }, + () => { + console.log("Export to Sheets") + scene.ui.playSelect(); + downloadLogByIDToSheet(i) + phase.callEnd() + }, + () => { + console.log("Delete") + scene.ui.playSelect(); + localStorage.removeItem(logs[i][1]) + phase.callEnd() + } + ] + }); + return false; + } + } + for (var j = 0; j < saves.length; j++) { + //console.log(saves[j].seed, logs[i][2], saves[j].seed == logs[i][2]) + if (saves[j].seed == logs[i][2]) { + op.label = "[Slot " + (saves[j].slot + 1) + "]" + op.label.substring(6) + } + } + if (logs[i][4] != "") { + op.label = " " + op.label + op.item = logs[i][4] + } + return op; +} +/** + * Generates a UI option to save a log to your device. + * @param i The slot number. Corresponds to an index in `logs`. + * @param saves Your session data. Used to label logs if they match one of your save slots. + * @returns A UI option. + */ +export function generateEditHandler(scene: BattleScene, logId: string, callback: Function) { + var i; + for (var j = 0; j < logs.length; j++) { + if (logs[j][2] == logId) { + i = j; + } + } + if (i == undefined) + return; // Failed to find a log + return (): boolean => { + rarityslot[1] = logs[i][1] + //scene.phaseQueue[0].end() + scene.ui.setMode(Mode.NAME_LOG, { + autofillfields: [ + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).title, + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).authors.join(", "), + (JSON.parse(localStorage.getItem(logs[i][1])) as DRPD).label, + ], + buttonActions: [ + () => { + console.log("Rename") + scene.ui.playSelect(); + callback() + }, + () => { + console.log("Export") + scene.ui.playSelect(); + downloadLogByID(i) + callback() + }, + () => { + console.log("Export to Sheets") + scene.ui.playSelect(); + downloadLogByIDToSheet(i) + callback() + }, + () => { + console.log("Delete") + scene.ui.playSelect(); + localStorage.removeItem(logs[i][1]) + callback() + } + ] + }); + return false; + } +} + +//#endregion + + + + + +//#region 13 Logging Events + +// * The functions in this section are sorted in alphabetical order. + +/** + * Logs the actions that the player took. + * + * This includes attacks you perform, items you transfer during the shop, Poke Balls you throw, running from battl, (or attempting to), and switching (including pre-switches). + * @param scene The BattleScene. Used to get the log ID. + * @param floor The wave index to write to. + * @param action The text you want to add to the actions list. + * + * @see resetWaveActions + */ +export function logActions(scene: BattleScene, floor: integer, action: string) { + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + console.log("Log Action", drpd) + var wv: Wave = getWave(drpd, floor, scene) + if (wv.double == undefined) + wv.double = false + if (wv.clearActionsFlag) { + console.log("Triggered clearActionsFlag") + wv.clearActionsFlag = false + wv.actions = [] + } + wv.actions.push(action) + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} +/** + * Logs the actions that the player took, adding text to the most recent action. + * @param scene The BattleScene. Used to get the log ID. + * @param floor The wave index to write to. + * @param action The text you want to add to the actions list. + * + * @see resetWaveActions + */ +export function appendAction(scene: BattleScene, floor: integer, action: string) { + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + console.log("Append to Action", drpd) + var wv: Wave = getWave(drpd, floor, scene) + if (wv.double == undefined) + wv.double = false + if (wv.clearActionsFlag) { + console.log("Triggered clearActionsFlag") + wv.clearActionsFlag = false + wv.actions = [] + } + wv.actions[wv.actions.length - 1] = wv.actions[wv.actions.length - 1] + action + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} +/** + * Logs that a Pokémon was captured. + * @param scene The BattleScene. Used to get the log ID. + * @param floor The wave index to write to. + * @param target The Pokémon that you captured. + */ +export function logCapture(scene: BattleScene, floor: integer, target: EnemyPokemon) { + //if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + console.log("Log Capture", drpd) + var wv: Wave = getWave(drpd, floor, scene) + var pkslot = target.partyslot + wv.pokemon[pkslot].captured = true; + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} +/** + * Logs the player's current party. + * + * Called on Floor 1 to store the starters list. + * @param scene The BattleScene. Used to get the log ID and the player's party. + */ +export function logPlayerTeam(scene: BattleScene) { + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + console.log("Log Player Starters", drpd) + var P = scene.getParty() + for (var i = 0; i < P.length; i++) { + drpd.starters[i] = exportPokemon(P[i]) + } + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} +/** + * Logs a wild Pokémon to a wave's data. + * @param scene The BattleScene. Used to retrieve the log ID. + * @param floor The wave index to write to. Defaults to the current floor. + * @param slot The slot to write to. In a single battle, 0 = the Pokémon that is out first. In a double battle, 0 = Left and 1 = Right. + * @param pokemon The `EnemyPokemon` to store the data of. (Automatically converted via `exportPokemon`) + * @param encounterRarity The rarity tier of this Pokémon. If not specified, it calculates this automatically by searching the current biome's species pool. + */ +export function logPokemon(scene: BattleScene, floor: integer = undefined, slot: integer, pokemon: EnemyPokemon, encounterRarity?: string) { + if (floor == undefined) floor = scene.currentBattle.waveIndex + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + console.log("Log Enemy Pokemon", drpd) + var wv: Wave = getWave(drpd, floor, scene) + var pk: PokeData = exportPokemon(pokemon, encounterRarity) + pk.source = pokemon + pokemon.partyslot = slot; + if (wv.pokemon == undefined) + wv.pokemon = [] + if (wv.pokemon[slot] != undefined) { + if (encounterRarity == "" || encounterRarity == undefined) { + if (wv.pokemon[slot].rarity != undefined && wv.pokemon[slot].rarity != "???") pk.rarity = wv.pokemon[slot].rarity + else { + var biome = scene.arena.biomeType + console.log(scene.arena.pokemonPool) + var tiernames = [ + "Common", + "Uncommon", + "Rare", + "Super Rare", + "Ultra Rare", + "Common Boss", + "Rare Boss", + "Super Rare Boss", + "Ultra Rare Boss", + ] + for (var i = 0; i < tiernames.length; i++) { + if (checkForPokeInBiome(wv.pokemon[slot].id, scene.arena.pokemonPool[i]) == true) { + console.log("Autofilled rarity for " + pk.name + " as " + tiernames[i]) + pk.rarity = tiernames[i] + } + } + } + } + if (JSON.stringify(wv.pokemon[slot]) != JSON.stringify(pk)) { + console.log("A different Pokemon already exists in this slot! Flagging as a reload") + wv.reload = true + } + } + if (pk.rarity == undefined) { + var biome = scene.arena.biomeType + console.log(scene.arena.pokemonPool) + var tiernames = [ + "Common", + "Uncommon", + "Rare", + "Super Rare", + "Ultra Rare", + "Common Boss", + "Rare Boss", + "Super Rare Boss", + "Ultra Rare Boss", + ] + for (var i = 0; i < tiernames.length; i++) { + if (wv.pokemon[slot] != undefined) + if (checkForPokeInBiome(wv.pokemon[slot].id, scene.arena.pokemonPool[i]) == true) { + console.log("Autofilled rarity for " + pk.name + " as " + tiernames[i]) + pk.rarity = tiernames[i] + } + } + } + if (pk.rarity == undefined) + pk.rarity = "[Unknown]" + wv.pokemon[slot] = pk; + //while (wv.actions.length > 0) + //wv.actions.pop() + //wv.actions = [] + wv.clearActionsFlag = false; + wv.shop = "" + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} +/** + * Logs what the player took from the rewards pool and, if applicable, who they used it on. + * @param scene The BattleScene. Used to get the log ID. + * @param floor The wave index to write to. + * @param action The shop action. Left blank if there was no shop this floor or if you ran away. Logged as "Skip taking items" if you didn't take anything for some reason. + */ +export function logShop(scene: BattleScene, floor: integer, action: string) { + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + console.log("Log Shop Item", drpd) + var wv: Wave = getWave(drpd, floor, scene) + wv.shop = action + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} +/** + * Logs the current floor's Trainer. + * @param scene The BattleScene. Used to get the log ID and trainer data. + * @param floor The wave index to write to. Defaults to the current floor. + */ +export function logTrainer(scene: BattleScene, floor: integer = undefined) { + if (floor == undefined) floor = scene.currentBattle.waveIndex + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd: DRPD = JSON.parse(localStorage.getItem(getLogID(scene))) as DRPD; + drpd = updateLog(drpd); + console.log("Log Trainer", drpd) + var wv: Wave = getWave(drpd, floor, scene) + var t: TrainerData = exportTrainer(scene.currentBattle.trainer) + wv.trainer = t + wv.type = "trainer" + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} + + + + + +/** + * Flags a wave as a reset. + * @param scene The BattleScene. Used to get the log ID. + * @param floor The wave index to write to. + */ +export function flagReset(scene: BattleScene, floor: integer = undefined) { + if (floor == undefined) + floor = scene.currentBattle.waveIndex; + if (localStorage.getItem(getLogID(scene)) == null) + localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + console.log("Flag Reset", drpd) + var wv = getWave(drpd, floor, scene) + wv.reload = true; + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} +/** + * Flags a wave as a reset, unless this is your first time playing the wave. + * @param scene The BattleScene. Used to get the log ID. + * @param floor The wave index to write to. + */ +export function flagResetIfExists(scene: BattleScene, floor: integer = undefined) { + if (floor == undefined) + floor = scene.currentBattle.waveIndex; + if (localStorage.getItem(getLogID(scene)) == null) + localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + var waveExists = false + for (var i = 0; i < drpd.waves.length; i++) { + if (drpd.waves[i] != undefined) { + if (drpd.waves[i].id == floor) { + waveExists = true; + } + } + } + if (!waveExists) { + console.log("Skipped wave reset because this is not a reload", drpd) + return; + } + console.log("Flag reset as wave was already played before", drpd) + var wv = getWave(drpd, floor, scene) + wv.reload = true; + console.log(drpd) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} + + + +/** + * Clears the action list for a wave. + * @param scene The BattleScene. Used to get the log ID and trainer data. + * @param floor The wave index to write to. Defaults to the current floor. + * @param softflag Rather than deleting everything right away, the actions will be cleared the next time we attempt to log an action. + * + * @see logActions + */ +export function resetWaveActions(scene: BattleScene, floor: integer = undefined, softflag: boolean) { + if (floor == undefined) floor = scene.currentBattle.waveIndex + if (localStorage.getItem(getLogID(scene)) == null) localStorage.setItem(getLogID(scene), JSON.stringify(newDocument(getMode(scene) + " Run"))) + var drpd = getDRPD(scene) + console.log("Clear Actions", drpd) + var wv: Wave = getWave(drpd, floor, scene) + if (softflag) { + wv.clearActionsFlag = true; + } else { + wv.actions = [] + } + console.log(drpd, wv) + localStorage.setItem(getLogID(scene), JSON.stringify(drpd)) +} +//#endregion + + + + + +//#region 14 Deprecated + +/** + * Writes data to a new line. + * @param keyword The identifier key for the log you're writing to + * @param data The string you're writing to the given log + * @deprecated + */ +export function toLog(keyword: string, data: string) { + localStorage.setItem(logs[logKeys.indexOf(keyword)][1], localStorage.getItem(logs[logKeys.indexOf(keyword)][1] + "\n" + data)) +} +/** + * Writes data on the same line you were on. + * @param keyword The identifier key for the log you're writing to + * @param data The string you're writing to the given log + * @deprecated + */ +export function appendLog(keyword: string, data: string) { + localStorage.setItem(logs[logKeys.indexOf(keyword)][1], localStorage.getItem(logs[logKeys.indexOf(keyword)][1] + data)) +} +/** + * Saves a log to your device. + * @param keyword The identifier key for the log you want to save. + * @deprecated + */ +export function downloadLog(keyword: string) { + var d = JSON.parse(localStorage.getItem(logs[logKeys.indexOf(keyword)][1])) + const blob = new Blob([ printDRPD("", "", d as DRPD) ], {type: "text/json"}); + const link = document.createElement("a"); + link.href = window.URL.createObjectURL(blob); + var date: string = (d as DRPD).date + var filename: string = date[0] + date[1] + "_" + date[3] + date[4] + "_" + date[6] + date[7] + date[8] + date[9] + "_route.json" + link.download = `${filename}`; + link.click(); + link.remove(); +} +/** + * + * Clears all data from a log. + * @param keyword The identifier key for the log you want to reste + * @deprecated + */ +export function clearLog(keyword: string) { + localStorage.setItem(logs[logKeys.indexOf(keyword)][1], "---- " + logs[logKeys.indexOf(keyword)][3] + " ----" + logs[logKeys.indexOf(keyword)][5]) +} + +/** + * Generates an option to create a new log. + * + * Not used. + * @param i The slot number. Corresponds to an index in `logs`. + * @param scene The current scene. Not used. + * @param o The current game phase. Used to return to the previous menu. Not necessary anymore lol + * @returns A UI option. + * + * wow this function sucks + * @deprecated + */ +export function generateAddOption(i: integer, scene: BattleScene, o: TitlePhase) { + var op: OptionSelectItem = { + label: "Generate log " + logs[i][0], + handler: () => { + localStorage.setItem(logs[i][1], JSON.stringify(newDocument())) + o.callEnd(); + return true; + } + } + return op; +} +/** + * A sort function, used to sort csv columns. + * + * No longer used as we are using .json format instead. + * @deprecated + */ +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) +} +/** + * Writes or replaces a csv row. + * + * No longer used as we are using .json format instead. + * @param keyword The keyword/ID of the log to write to. + * @param newLine The data to write. + * @param floor The floor to write to. Used for sorting. + * @param slot The slot to write to. Used for sorting. + * @deprecated + */ +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) { + 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++ + } + idx-- + for (var i = 0; i < data.length; i++) { + if (data[i] == ",,,,,,,,,,,,,,,,") { + data.splice(i, 1) + if (idx > i) idx-- + i-- + } + } + 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) + var i: number; + for (i = 0; i < Math.max(0, idx - 2) && i < 2; i++) { + console.log(i + " " + data[i]) + } + if (i == 3 && i != Math.min(0, idx - 2)) { + console.log("...") + } + for (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++ + } + for (var i = 0; i < data.length; i++) { + if (data[i] == ",,,,,,,,,,,,,,,,") { + data.splice(i, 1) + if (idx > i) idx-- + i-- + } + } + console.log("Inserted data at " + idx) + var i: number; + for (i = 0; i < Math.max(0, idx - 2) && i < 2; i++) { + console.log(i + " " + data[i]) + } + if (i == 3 && i != Math.min(0, idx - 2)) { + console.log("...") + } + for (i = Math.max(0, idx - 2); i < idx; i++) { + console.log(i + " " + data[i]) + } + console.log(i + " >> " + newLine) + for (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")); +} +//#endregion \ No newline at end of file diff --git a/src/modifier/modifier-type.ts b/src/modifier/modifier-type.ts index d8ec0072bd4..09748ad4906 100644 --- a/src/modifier/modifier-type.ts +++ b/src/modifier/modifier-type.ts @@ -27,6 +27,7 @@ import { BattlerTagType } from "#enums/battler-tag-type"; import { BerryType } from "#enums/berry-type"; import { Moves } from "#enums/moves"; import { Species } from "#enums/species"; +import { GameModes } from "#app/game-mode.js"; const outputModifierData = false; const useMaxWeightForOutput = false; @@ -64,6 +65,10 @@ export class ModifierType { return i18next.t(`${this.localeKey}.name` as any); } + get identifier(): string { + return "Modifier:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return i18next.t(`${this.localeKey}.description` as any); } @@ -159,6 +164,9 @@ class AddPokeballModifierType extends ModifierType { "pokeballName": getPokeballName(this.pokeballType), }); } + get identifier(): string { + return "PokeballModifier:" + Utils.getEnumKeys(PokeballType)[this.pokeballType]; + } getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.AddPokeballModifierType.description", { @@ -198,6 +206,10 @@ class AddVoucherModifierType extends ModifierType { export class PokemonModifierType extends ModifierType { public selectFilter: PokemonSelectFilter; + get identifier(): string { + return "PokemonModifier:undefined"; + } + constructor(localeKey: string, iconImage: string, newModifierFunc: NewModifierFunc, selectFilter?: PokemonSelectFilter, group?: string, soundName?: string) { super(localeKey, iconImage, newModifierFunc, group, soundName); @@ -221,6 +233,10 @@ export class PokemonHeldItemModifierType extends PokemonModifierType { }, group, soundName); } + get identifier(): string { + return "HeldItem:" + this.localeKey.split(".")[1]; + } + newModifier(...args: any[]): Modifiers.PokemonHeldItemModifier { return super.newModifier(...args) as Modifiers.PokemonHeldItemModifier; } @@ -245,6 +261,10 @@ export class PokemonHpRestoreModifierType extends PokemonModifierType { this.healStatus = healStatus; } + get identifier(): string { + return "HpRestore:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return this.restorePoints ? i18next.t("modifierType:ModifierType.PokemonHpRestoreModifierType.description", { @@ -274,6 +294,9 @@ export class PokemonReviveModifierType extends PokemonHpRestoreModifierType { return null; }; } + get identifier(): string { + return "Revive:" + this.localeKey.split(".")[1]; + } getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonReviveModifierType.description", { restorePercent: this.restorePercent }); @@ -291,6 +314,10 @@ export class PokemonStatusHealModifierType extends PokemonModifierType { })); } + get identifier(): string { + return "StatusCure:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonStatusHealModifierType.description"); } @@ -323,6 +350,10 @@ export class PokemonPpRestoreModifierType extends PokemonMoveModifierType { this.restorePoints = restorePoints; } + get identifier(): string { + return "PpRestore:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return this.restorePoints > -1 ? i18next.t("modifierType:ModifierType.PokemonPpRestoreModifierType.description", { restorePoints: this.restorePoints }) @@ -346,6 +377,10 @@ export class PokemonAllMovePpRestoreModifierType extends PokemonModifierType { this.restorePoints = restorePoints; } + get identifier(): string { + return "PpAllRestore:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return this.restorePoints > -1 ? i18next.t("modifierType:ModifierType.PokemonAllMovePpRestoreModifierType.description", { restorePoints: this.restorePoints }) @@ -371,6 +406,10 @@ export class PokemonPpUpModifierType extends PokemonMoveModifierType { this.upPoints = upPoints; } + get identifier(): string { + return "PpBooster:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonPpUpModifierType.description", { upPoints: this.upPoints }); } @@ -395,6 +434,10 @@ export class PokemonNatureChangeModifierType extends PokemonModifierType { return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.name", { natureName: getNatureName(this.nature) }); } + get identifier(): string { + return "Mint:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonNatureChangeModifierType.description", { natureName: getNatureName(this.nature, true, true, true) }); } @@ -410,6 +453,10 @@ export class RememberMoveModifierType extends PokemonModifierType { return null; }, group); } + + get identifier(): string { + return "MemoryMushroom:" + this.localeKey.split(".")[1]; + } } export class DoubleBattleChanceBoosterModifierType extends ModifierType { @@ -421,6 +468,10 @@ export class DoubleBattleChanceBoosterModifierType extends ModifierType { this.battleCount = battleCount; } + get identifier(): string { + return "DoubleModifier:" + this.localeKey.split(".")[1]; + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.DoubleBattleChanceBoosterModifierType.description", { battleCount: this.battleCount }); } @@ -440,6 +491,10 @@ export class TempBattleStatBoosterModifierType extends ModifierType implements G return i18next.t(`modifierType:TempBattleStatBoosterItem.${getTempBattleStatBoosterItemName(this.tempBattleStat).replace(/\./g, "").replace(/[ ]/g, "_").toLowerCase()}`); } + get identifier(): string { + return "TempStatBooster:" + Utils.getEnumKeys(TempBattleStat)[this.tempBattleStat] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.TempBattleStatBoosterModifierType.description", { tempBattleStatName: getTempBattleStatName(this.tempBattleStat) }); } @@ -462,6 +517,10 @@ export class BerryModifierType extends PokemonHeldItemModifierType implements Ge return getBerryName(this.berryType); } + get identifier(): string { + return "Berry:" + Utils.getEnumKeys(BerryType)[this.berryType] + } + getDescription(scene: BattleScene): string { return getBerryEffectDescription(this.berryType); } @@ -528,6 +587,10 @@ export class AttackTypeBoosterModifierType extends PokemonHeldItemModifierType i return i18next.t(`modifierType:AttackTypeBoosterItem.${getAttackTypeBoosterItemName(this.moveType).replace(/[ \-]/g, "_").toLowerCase()}`); } + get identifier(): string { + return "MoveBooster:" + Utils.getEnumKeys(Type)[this.moveType] + } + getDescription(scene: BattleScene): string { // TODO: Need getTypeName? return i18next.t("modifierType:ModifierType.AttackTypeBoosterModifierType.description", { moveType: i18next.t(`pokemonInfo:Type.${Type[this.moveType]}`) }); @@ -554,6 +617,10 @@ export class SpeciesStatBoosterModifierType extends PokemonHeldItemModifierType this.key = key; } + + get identifier(): string { + return "SpeciesBooster:" + this.key + } getPregenArgs(): any[] { return [ this.key ]; @@ -565,6 +632,10 @@ export class PokemonLevelIncrementModifierType extends PokemonModifierType { super(localeKey, iconImage, (_type, args) => new Modifiers.PokemonLevelIncrementModifier(this, (args[0] as PlayerPokemon).id), (_pokemon: PlayerPokemon) => null); } + get identifier(): string { + return "RareCandy:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonLevelIncrementModifierType.description"); } @@ -575,6 +646,10 @@ export class AllPokemonLevelIncrementModifierType extends ModifierType { super(localeKey, iconImage, (_type, _args) => new Modifiers.PokemonLevelIncrementModifier(this, -1)); } + get identifier(): string { + return "RareCandy:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.AllPokemonLevelIncrementModifierType.description"); } @@ -612,6 +687,10 @@ export class PokemonBaseStatBoosterModifierType extends PokemonHeldItemModifierT return i18next.t(`modifierType:BaseStatBoosterItem.${this.localeName.replace(/[ \-]/g, "_").toLowerCase()}`); } + get identifier(): string { + return "StatBooster:" + Utils.getEnumKeys(Stat)[this.stat] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonBaseStatBoosterModifierType.description", { statName: getStatName(this.stat) }); } @@ -630,6 +709,10 @@ class AllPokemonFullHpRestoreModifierType extends ModifierType { this.descriptionKey = descriptionKey; } + get identifier(): string { + return "HealAll:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t(`${this.descriptionKey || "modifierType:ModifierType.AllPokemonFullHpRestoreModifierType"}.description` as any); } @@ -639,6 +722,10 @@ class AllPokemonFullReviveModifierType extends AllPokemonFullHpRestoreModifierTy constructor(localeKey: string, iconImage: string) { super(localeKey, iconImage, "modifierType:ModifierType.AllPokemonFullReviveModifierType", (_type, _args) => new Modifiers.PokemonHpRestoreModifier(this, -1, 0, 100, false, true)); } + + get identifier(): string { + return "ReviveAll:" + this.localeKey.split(".")[1] + } } export class MoneyRewardModifierType extends ModifierType { @@ -652,6 +739,10 @@ export class MoneyRewardModifierType extends ModifierType { this.moneyMultiplierDescriptorKey = moneyMultiplierDescriptorKey; } + get identifier(): string { + return "Money:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { const moneyAmount = new Utils.IntegerHolder(scene.getWaveMoneyAmount(this.moneyMultiplier)); scene.applyModifiers(MoneyMultiplierModifier, true, moneyAmount); @@ -673,6 +764,10 @@ export class ExpBoosterModifierType extends ModifierType { this.boostPercent = boostPercent; } + get identifier(): string { + return "ExpBooster:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.ExpBoosterModifierType.description", { boostPercent: this.boostPercent }); } @@ -687,6 +782,10 @@ export class PokemonExpBoosterModifierType extends PokemonHeldItemModifierType { this.boostPercent = boostPercent; } + get identifier(): string { + return "PokemonExpBooster:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonExpBoosterModifierType.description", { boostPercent: this.boostPercent }); } @@ -697,6 +796,10 @@ export class PokemonFriendshipBoosterModifierType extends PokemonHeldItemModifie super(localeKey, iconImage, (_type, args) => new Modifiers.PokemonFriendshipBoosterModifier(this, (args[0] as Pokemon).id)); } + get identifier(): string { + return "FriendshipBooster:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonFriendshipBoosterModifierType.description"); } @@ -711,6 +814,10 @@ export class PokemonMoveAccuracyBoosterModifierType extends PokemonHeldItemModif this.amount = amount; } + get identifier(): string { + return "AccuracyBooster:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonMoveAccuracyBoosterModifierType.description", { accuracyAmount: this.amount }); } @@ -721,6 +828,10 @@ export class PokemonMultiHitModifierType extends PokemonHeldItemModifierType { super(localeKey, iconImage, (type, args) => new Modifiers.PokemonMultiHitModifier(type as PokemonMultiHitModifierType, (args[0] as Pokemon).id)); } + get identifier(): string { + return "MultiHit:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.PokemonMultiHitModifierType.description"); } @@ -728,8 +839,9 @@ export class PokemonMultiHitModifierType extends PokemonHeldItemModifierType { export class TmModifierType extends PokemonModifierType { public moveId: Moves; + public rarity: string; - constructor(moveId: Moves) { + constructor(moveId: Moves, rarity: ModifierTier) { super("", `tm_${Type[allMoves[moveId].type].toLowerCase()}`, (_type, args) => new Modifiers.TmModifier(this, (args[0] as PlayerPokemon).id), (pokemon: PlayerPokemon) => { if (pokemon.compatibleTms.indexOf(moveId) === -1 || pokemon.getMoveset().filter(m => m?.moveId === moveId).length) { @@ -739,6 +851,26 @@ export class TmModifierType extends PokemonModifierType { }, "tm"); this.moveId = moveId; + switch (rarity) { + case ModifierTier.COMMON: + this.rarity = "Common" + break; + case ModifierTier.GREAT: + this.rarity = "Great" + break; + case ModifierTier.ULTRA: + this.rarity = "Ultra" + break; + case ModifierTier.ROGUE: + this.rarity = "Rogue" + break; + case ModifierTier.MASTER: + this.rarity = "Master" + break; + case ModifierTier.LUXURY: + this.rarity = "Luxury" + break; + } } get name(): string { @@ -748,6 +880,10 @@ export class TmModifierType extends PokemonModifierType { }); } + get identifier(): string { + return "Tm" + this.rarity + ":" + Utils.getEnumKeys(Moves)[this.moveId] + } + getDescription(scene: BattleScene): string { return i18next.t(scene.enableMoveInfo ? "modifierType:ModifierType.TmModifierTypeWithInfo.description" : "modifierType:ModifierType.TmModifierType.description", { moveName: allMoves[this.moveId].name }); } @@ -777,6 +913,10 @@ export class EvolutionItemModifierType extends PokemonModifierType implements Ge return i18next.t(`modifierType:EvolutionItem.${EvolutionItem[this.evolutionItem]}`); } + get identifier(): string { + return "Evolution" + (this.evolutionItem > 50 ? "Rare" : "") + ":" + Utils.getEnumKeys(EvolutionItem)[this.evolutionItem] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.EvolutionItemModifierType.description"); } @@ -815,6 +955,9 @@ export class FormChangeItemModifierType extends PokemonModifierType implements G get name(): string { return i18next.t(`modifierType:FormChangeItem.${FormChangeItem[this.formChangeItem]}`); } + get identifier(): string { + return "FormChange:" + Utils.getEnumKeys(FormChangeItem)[this.formChangeItem] + } getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.FormChangeItemModifierType.description"); @@ -836,6 +979,10 @@ export class FusePokemonModifierType extends PokemonModifierType { }); } + get identifier(): string { + return "Fusion:" + this.localeKey.split(".")[1] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.FusePokemonModifierType.description"); } @@ -975,7 +1122,7 @@ class TmModifierTypeGenerator extends ModifierTypeGenerator { return null; } const randTmIndex = Utils.randSeedInt(tierUniqueCompatibleTms.length); - return new TmModifierType(tierUniqueCompatibleTms[randTmIndex]); + return new TmModifierType(tierUniqueCompatibleTms[randTmIndex], tier); }); } } @@ -1044,6 +1191,10 @@ export class TerastallizeModifierType extends PokemonHeldItemModifierType implem return i18next.t("modifierType:ModifierType.TerastallizeModifierType.name", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) }); } + get identifier(): string { + return "TeraShard:" + Utils.getEnumKeys(Type)[this.teraType] + } + getDescription(scene: BattleScene): string { return i18next.t("modifierType:ModifierType.TerastallizeModifierType.description", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) }); } @@ -1805,14 +1956,14 @@ export function getModifierTypeFuncById(id: string): ModifierTypeFunc { return modifierTypes[id]; } -export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemon[], modifierTiers?: ModifierTier[]): ModifierTypeOption[] { +export function getPlayerModifierTypeOptions(count: integer, party: PlayerPokemon[], modifierTiers?: ModifierTier[], scene?: BattleScene): ModifierTypeOption[] { const options: ModifierTypeOption[] = []; const retryCount = Math.min(count * 5, 50); new Array(count).fill(0).map((_, i) => { - let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, modifierTiers?.length > i ? modifierTiers[i] : undefined); + let candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, modifierTiers?.length > i ? modifierTiers[i] : undefined, undefined, undefined, scene); let r = 0; while (options.length && ++r < retryCount && options.filter(o => o.type.name === candidate.type.name || o.type.group === candidate.type.group).length) { - candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, candidate.type.tier, candidate.upgradeCount); + candidate = getNewModifierTypeOption(party, ModifierPoolType.PLAYER, candidate.type.tier, candidate.upgradeCount, undefined, scene); } options.push(candidate); }); @@ -1867,11 +2018,11 @@ export function getPlayerShopModifierTypeOptionsForWave(waveIndex: integer, base export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: Modifiers.PersistentModifier[], scene: BattleScene): Modifiers.EnemyPersistentModifier { const tierStackCount = tier === ModifierTier.ULTRA ? 5 : tier === ModifierTier.GREAT ? 3 : 1; const retryCount = 50; - let candidate = getNewModifierTypeOption(null, ModifierPoolType.ENEMY_BUFF, tier); + let candidate = getNewModifierTypeOption(null, ModifierPoolType.ENEMY_BUFF, tier, undefined, undefined, scene); let r = 0; let matchingModifier: Modifiers.PersistentModifier; while (++r < retryCount && (matchingModifier = enemyModifiers.find(m => m.type.id === candidate.type.id)) && matchingModifier.getMaxStackCount(scene) < matchingModifier.stackCount + (r < 10 ? tierStackCount : 1)) { - candidate = getNewModifierTypeOption(null, ModifierPoolType.ENEMY_BUFF, tier); + candidate = getNewModifierTypeOption(null, ModifierPoolType.ENEMY_BUFF, tier, undefined, undefined, scene); } const modifier = candidate.type.newModifier() as Modifiers.EnemyPersistentModifier; @@ -1880,21 +2031,21 @@ export function getEnemyBuffModifierForWave(tier: ModifierTier, enemyModifiers: return modifier; } -export function getEnemyModifierTypesForWave(waveIndex: integer, count: integer, party: EnemyPokemon[], poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, upgradeChance: integer = 0): PokemonHeldItemModifierType[] { - const ret = new Array(count).fill(0).map(() => getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !Utils.randSeedInt(upgradeChance) ? 1 : 0).type as PokemonHeldItemModifierType); +export function getEnemyModifierTypesForWave(waveIndex: integer, count: integer, party: EnemyPokemon[], poolType: ModifierPoolType.WILD | ModifierPoolType.TRAINER, upgradeChance: integer = 0, scene?: BattleScene): PokemonHeldItemModifierType[] { + const ret = new Array(count).fill(0).map(() => getNewModifierTypeOption(party, poolType, undefined, upgradeChance && !Utils.randSeedInt(upgradeChance) ? 1 : 0, undefined, scene).type as PokemonHeldItemModifierType); if (!(waveIndex % 1000)) { ret.push(getModifierType(modifierTypes.MINI_BLACK_HOLE) as PokemonHeldItemModifierType); } return ret; } -export function getDailyRunStarterModifiers(party: PlayerPokemon[]): Modifiers.PokemonHeldItemModifier[] { +export function getDailyRunStarterModifiers(party: PlayerPokemon[], scene?: BattleScene): Modifiers.PokemonHeldItemModifier[] { const ret: Modifiers.PokemonHeldItemModifier[] = []; for (const p of party) { for (let m = 0; m < 3; m++) { const tierValue = Utils.randSeedInt(64); const tier = tierValue > 25 ? ModifierTier.COMMON : tierValue > 12 ? ModifierTier.GREAT : tierValue > 4 ? ModifierTier.ULTRA : tierValue ? ModifierTier.ROGUE : ModifierTier.MASTER; - const modifier = getNewModifierTypeOption(party, ModifierPoolType.DAILY_STARTER, tier).type.newModifier(p) as Modifiers.PokemonHeldItemModifier; + const modifier = getNewModifierTypeOption(party, ModifierPoolType.DAILY_STARTER, tier, undefined, undefined, scene).type.newModifier(p) as Modifiers.PokemonHeldItemModifier; ret.push(modifier); } } @@ -1902,7 +2053,7 @@ export function getDailyRunStarterModifiers(party: PlayerPokemon[]): Modifiers.P return ret; } -function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, tier?: ModifierTier, upgradeCount?: integer, retryCount: integer = 0): ModifierTypeOption { +function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, tier?: ModifierTier, upgradeCount?: integer, retryCount: integer = 0, scene?: BattleScene): ModifierTypeOption { const player = !poolType; const pool = getModifierPoolForType(poolType); let thresholds: object; @@ -1929,7 +2080,12 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, upgradeCount = 0; } if (player && tierValue) { - const partyLuckValue = getPartyLuckValue(party); + var partyLuckValue = getPartyLuckValue(party); + if (scene) { + if (scene.gameMode.modeId == GameModes.DAILY && scene.disableDailyShinies) { + partyLuckValue = 0 + } + } const upgradeOdds = Math.floor(128 / ((partyLuckValue + 4) / 4)); let upgraded = false; do { @@ -1954,7 +2110,12 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, } else if (upgradeCount === undefined && player) { upgradeCount = 0; if (tier < ModifierTier.MASTER) { - const partyShinyCount = party.filter(p => p.isShiny() && !p.isFainted()).length; + var partyShinyCount = party.filter(p => p.isShiny() && !p.isFainted()).length; + if (scene) { + if (scene.gameMode.modeId == GameModes.DAILY && scene.disableDailyShinies) { + partyShinyCount = 0 + } + } const upgradeOdds = Math.floor(32 / ((partyShinyCount + 2) / 2)); while (modifierPool.hasOwnProperty(tier + upgradeCount + 1) && modifierPool[tier + upgradeCount + 1].length) { if (!Utils.randSeedInt(upgradeOdds)) { @@ -1996,7 +2157,7 @@ function getNewModifierTypeOption(party: Pokemon[], poolType: ModifierPoolType, if (player) { console.log(ModifierTier[tier], upgradeCount); } - return getNewModifierTypeOption(party, poolType, tier, upgradeCount, ++retryCount); + return getNewModifierTypeOption(party, poolType, tier, upgradeCount, ++retryCount, scene); } } diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index 0b339906cc5..d965e2a45b1 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -24,6 +24,7 @@ import * as Overrides from "../overrides"; import { ModifierType, modifierTypes } from "./modifier-type"; import { Command } from "#app/ui/command-ui-handler.js"; import { Species } from "#enums/species"; +import i18next from "i18next"; import { allMoves } from "#app/data/move.js"; import { Abilities } from "#app/enums/abilities.js"; @@ -142,6 +143,10 @@ export abstract class Modifier { return true; } + get identifier(): string { + return this.type.identifier; + } + abstract apply(args: any[]): boolean | Promise; } @@ -965,7 +970,7 @@ export class SurviveDamageModifier extends PokemonHeldItemModifier { if (!surviveDamage.value && pokemon.randSeedInt(10) < this.getStackCount()) { surviveDamage.value = true; - pokemon.scene.queueMessage(getPokemonMessage(pokemon, ` hung on\nusing its ${this.type.name}!`)); + pokemon.scene.queueMessage(i18next.t("modifier:surviveDamageApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name })); return true; } @@ -1070,7 +1075,7 @@ export class TurnHealModifier extends PokemonHeldItemModifier { if (pokemon.getHpRatio() < 1) { const scene = pokemon.scene; scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / 16) * this.stackCount, 1), getPokemonMessage(pokemon, `'s ${this.type.name}\nrestored its HP a little!`), true)); + Math.max(Math.floor(pokemon.getMaxHp() / 16) * this.stackCount, 1), i18next.t("modifier:turnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true)); return true; } @@ -1161,7 +1166,7 @@ export class HitHealModifier extends PokemonHeldItemModifier { if (pokemon.turnData.damageDealt && pokemon.getHpRatio() < 1) { const scene = pokemon.scene; scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.turnData.damageDealt / 8) * this.stackCount, 1), getPokemonMessage(pokemon, `'s ${this.type.name}\nrestored its HP a little!`), true)); + Math.max(Math.floor(pokemon.turnData.damageDealt / 8) * this.stackCount, 1), i18next.t("modifier:hitHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), true)); } return true; @@ -1296,7 +1301,7 @@ export class PokemonInstantReviveModifier extends PokemonHeldItemModifier { const pokemon = args[0] as Pokemon; pokemon.scene.unshiftPhase(new PokemonHealPhase(pokemon.scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / 2), 1), getPokemonMessage(pokemon, ` was revived\nby its ${this.type.name}!`), false, false, true)); + Math.max(Math.floor(pokemon.getMaxHp() / 2), 1), i18next.t("modifier:pokemonInstantReviveApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon), typeName: this.type.name }), false, false, true)); pokemon.resetStatus(true, false, true); return true; @@ -2010,7 +2015,10 @@ export class MoneyInterestModifier extends PersistentModifier { const interestAmount = Math.floor(scene.money * 0.1 * this.getStackCount()); scene.addMoney(interestAmount); - scene.queueMessage(`You received interest of ₽${interestAmount.toLocaleString("en-US")}\nfrom the ${this.type.name}!`, null, true); + const userLocale = navigator.language || "en-US"; + const formattedMoneyAmount = interestAmount.toLocaleString(userLocale); + const message = i18next.t("modifier:moneyInterestApply", { moneyAmount: formattedMoneyAmount, typeName: this.type.name }); + scene.queueMessage(message, null, true); return true; } @@ -2231,7 +2239,7 @@ export class TurnHeldItemTransferModifier extends HeldItemTransferModifier { } getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string { - return getPokemonMessage(targetPokemon, `'s ${item.name} was absorbed\nby ${pokemon.name}'s ${this.type.name}!`); + return i18next.t("modifier:turnHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: pokemon.name, typeName: this.type.name }); } getMaxHeldItemCount(pokemon: Pokemon): integer { @@ -2285,7 +2293,7 @@ export class ContactHeldItemTransferChanceModifier extends HeldItemTransferModif } getTransferMessage(pokemon: Pokemon, targetPokemon: Pokemon, item: ModifierTypes.ModifierType): string { - return getPokemonMessage(targetPokemon, `'s ${item.name} was snatched\nby ${pokemon.name}'s ${this.type.name}!`); + return i18next.t("modifier:contactHeldItemTransferApply", { pokemonNameWithAffix: getPokemonNameWithAffix(targetPokemon), itemName: item.name, pokemonName: pokemon.name, typeName: this.type.name }); } getMaxHeldItemCount(pokemon: Pokemon): integer { @@ -2443,7 +2451,7 @@ export class EnemyTurnHealModifier extends EnemyPersistentModifier { if (pokemon.getHpRatio() < 1) { const scene = pokemon.scene; scene.unshiftPhase(new PokemonHealPhase(scene, pokemon.getBattlerIndex(), - Math.max(Math.floor(pokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), getPokemonMessage(pokemon, "\nrestored some HP!"), true, false, false, false, true)); + Math.max(Math.floor(pokemon.getMaxHp() / (100 / this.healPercent)) * this.stackCount, 1), i18next.t("modifier:enemyTurnHealApply", { pokemonNameWithAffix: getPokemonNameWithAffix(pokemon) }), true, false, false, false, true)); return true; } diff --git a/src/phases.ts b/src/phases.ts index cd14c92cfc5..6a5af044aab 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1,3 +1,4 @@ +//#region 00 Imports import BattleScene, { bypassLogin } from "./battle-scene"; import { default as Pokemon, PlayerPokemon, EnemyPokemon, PokemonMove, MoveResult, DamageResult, FieldPosition, HitResult, TurnMove } from "./field/pokemon"; import * as Utils from "./utils"; @@ -26,7 +27,7 @@ import { Gender } from "./data/gender"; import { Weather, WeatherType, getRandomWeatherType, getTerrainBlockMessage, getWeatherDamageMessage, getWeatherLapseMessage } from "./data/weather"; import { TempBattleStat } from "./data/temp-battle-stat"; import { ArenaTagSide, ArenaTrapTag, MistTag, TrickRoomTag } from "./data/arena-tag"; -import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, WonderSkinAbAttr, applyPreDefendAbAttrs, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr } from "./data/ability"; +import { CheckTrappedAbAttr, IgnoreOpponentStatChangesAbAttr, IgnoreOpponentEvasionAbAttr, PostAttackAbAttr, PostBattleAbAttr, PostDefendAbAttr, PostSummonAbAttr, PostTurnAbAttr, PostWeatherLapseAbAttr, PreSwitchOutAbAttr, PreWeatherDamageAbAttr, ProtectStatAbAttr, RedirectMoveAbAttr, BlockRedirectAbAttr, RunSuccessAbAttr, StatChangeMultiplierAbAttr, SuppressWeatherEffectAbAttr, SyncEncounterNatureAbAttr, applyAbAttrs, applyCheckTrappedAbAttrs, applyPostAttackAbAttrs, applyPostBattleAbAttrs, applyPostDefendAbAttrs, applyPostSummonAbAttrs, applyPostTurnAbAttrs, applyPostWeatherLapseAbAttrs, applyPreStatChangeAbAttrs, applyPreSwitchOutAbAttrs, applyPreWeatherEffectAbAttrs, BattleStatMultiplierAbAttr, applyBattleStatMultiplierAbAttrs, IncrementMovePriorityAbAttr, applyPostVictoryAbAttrs, PostVictoryAbAttr, BlockNonDirectDamageAbAttr as BlockNonDirectDamageAbAttr, applyPostKnockOutAbAttrs, PostKnockOutAbAttr, PostBiomeChangeAbAttr, applyPostFaintAbAttrs, PostFaintAbAttr, IncreasePpAbAttr, PostStatChangeAbAttr, applyPostStatChangeAbAttrs, AlwaysHitAbAttr, PreventBerryUseAbAttr, StatChangeCopyAbAttr, PokemonTypeChangeAbAttr, applyPreAttackAbAttrs, applyPostMoveUsedAbAttrs, PostMoveUsedAbAttr, MaxMultiHitAbAttr, HealFromBerryUseAbAttr, WonderSkinAbAttr, applyPreDefendAbAttrs, IgnoreMoveEffectsAbAttr, BlockStatusDamageAbAttr, BypassSpeedChanceAbAttr, AddSecondStrikeAbAttr, BattlerTagImmunityAbAttr } from "./data/ability"; import { Unlockables, getUnlockableName } from "./system/unlockables"; import { getBiomeKey } from "./field/arena"; import { BattleType, BattlerIndex, TurnCommand } from "./battle"; @@ -36,7 +37,7 @@ import { EggHatchPhase } from "./egg-hatch-phase"; import { Egg } from "./data/egg"; import { vouchers } from "./system/voucher"; import { clientSessionId, loggedInUser, updateUserInfo } from "./account"; -import { SessionSaveData } from "./system/game-data"; +import { SessionSaveData, decrypt } from "./system/game-data"; import { addPokeballCaptureStars, addPokeballOpenParticles } from "./field/anims"; import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeManualTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangePreMoveTrigger } from "./data/pokemon-forms"; import { battleSpecDialogue, getCharVariantFromDialogue, miscDialogue } from "./data/dialogue"; @@ -65,9 +66,189 @@ import { Moves } from "#enums/moves"; import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; import { TrainerType } from "#enums/trainer-type"; +import TrainerData from "./system/trainer-data"; +import PersistentModifierData from "./system/modifier-data"; +import ArenaData from "./system/arena-data"; +import ChallengeData from "./system/challenge-data"; +import { Challenges } from "./enums/challenges" +import PokemonData from "./system/pokemon-data" +import * as LoggerTools from "./logger" +import { getNatureDecrease, getNatureIncrease, getNatureName } from "./data/nature"; +import { GameDataType } from "./enums/game-data-type"; +import { Session } from "inspector"; const { t } = i18next; + + +//#endregion + + + + + +//#region 01 Uncategorized +export function parseSlotData(slotId: integer): SessionSaveData { + var S = localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`) + if (S == null) { + // No data in this slot + return undefined; + } + var dataStr = decrypt(S, true) + var Save = JSON.parse(dataStr, (k: string, v: any) => { + /*const versions = [ scene.game.config.gameVersion, sessionData.gameVersion || '0.0.0' ]; + + if (versions[0] !== versions[1]) { + const [ versionNumbers, oldVersionNumbers ] = versions.map(ver => ver.split('.').map(v => parseInt(v))); + }*/ + + if (k === "party" || k === "enemyParty") { + const ret: PokemonData[] = []; + if (v === null) { + v = []; + } + for (const pd of v) { + ret.push(new PokemonData(pd)); + } + return ret; + } + + if (k === "trainer") { + return v ? new TrainerData(v) : null; + } + + if (k === "modifiers" || k === "enemyModifiers") { + const player = k === "modifiers"; + const ret: PersistentModifierData[] = []; + if (v === null) { + v = []; + } + for (const md of v) { + if (md?.className === "ExpBalanceModifier") { // Temporarily limit EXP Balance until it gets reworked + md.stackCount = Math.min(md.stackCount, 4); + } + if (md instanceof EnemyAttackStatusEffectChanceModifier && md.effect === StatusEffect.FREEZE || md.effect === StatusEffect.SLEEP) { + continue; + } + ret.push(new PersistentModifierData(md, player)); + } + return ret; + } + + if (k === "arena") { + return new ArenaData(v); + } + + if (k === "challenges") { + const ret: ChallengeData[] = []; + if (v === null) { + v = []; + } + for (const c of v) { + ret.push(new ChallengeData(c)); + } + return ret; + } + + return v; + }) as SessionSaveData; + Save.slot = slotId + Save.description = (slotId + 1) + " - " + var challengeParts: ChallengeData[] = new Array(5) + var nameParts: string[] = new Array(5) + if (Save.challenges != undefined) { + for (var i = 0; i < Save.challenges.length; i++) { + switch (Save.challenges[i].id) { + case Challenges.SINGLE_TYPE: + challengeParts[0] = Save.challenges[i] + nameParts[1] = Save.challenges[i].toChallenge().getValue() + nameParts[1] = nameParts[1][0].toUpperCase() + nameParts[1].substring(1) + if (nameParts[1] == "unknown") { + nameParts[1] = undefined + challengeParts[1] = undefined + } + break; + case Challenges.SINGLE_GENERATION: + challengeParts[1] = Save.challenges[i] + nameParts[0] = "Gen " + Save.challenges[i].value + if (nameParts[0] == "Gen 0") { + nameParts[0] = undefined + challengeParts[0] = undefined + } + break; + case Challenges.LOWER_MAX_STARTER_COST: + challengeParts[2] = Save.challenges[i] + nameParts[3] = (10 - challengeParts[0].value) + "cost" + break; + case Challenges.LOWER_STARTER_POINTS: + challengeParts[3] = Save.challenges[i] + nameParts[4] = (10 - challengeParts[0].value) + "pt" + break; + case Challenges.FRESH_START: + challengeParts[4] = Save.challenges[i] + nameParts[2] = "FS" + break; + } + } + } + for (var i = 0; i < challengeParts.length; i++) { + if (challengeParts[i] == undefined || challengeParts[i] == null) { + challengeParts.splice(i, 1) + i-- + } + } + for (var i = 0; i < nameParts.length; i++) { + if (nameParts[i] == undefined || nameParts[i] == null || nameParts[i] == "") { + nameParts.splice(i, 1) + i-- + } + } + if (challengeParts.length == 1 && false) { + switch (challengeParts[0].id) { + case Challenges.SINGLE_TYPE: + Save.description += "Mono " + challengeParts[0].toChallenge().getValue() + break; + case Challenges.SINGLE_GENERATION: + Save.description += "Gen " + challengeParts[0].value + break; + case Challenges.LOWER_MAX_STARTER_COST: + Save.description += "Max cost " + (10 - challengeParts[0].value) + break; + case Challenges.LOWER_STARTER_POINTS: + Save.description += (10 - challengeParts[0].value) + "-point" + break; + case Challenges.FRESH_START: + Save.description += "Fresh Start" + break; + } + } else if (challengeParts.length == 0) { + switch (Save.gameMode) { + case GameModes.CLASSIC: + Save.description += "Classic"; + break; + case GameModes.ENDLESS: + Save.description += "Endless"; + break; + case GameModes.SPLICED_ENDLESS: + Save.description += "Endless+"; + break; + case GameModes.DAILY: + Save.description += "Daily"; + break; + } + } else { + Save.description += nameParts.join(" ") + } + Save.description += " (" + getBiomeName(Save.arena.biome) + " " + Save.waveIndex + ")" + return Save; +} +//#endregion + + + + + +//#region 02 LoginPhase export class LoginPhase extends Phase { private showText: boolean; @@ -164,7 +345,13 @@ export class LoginPhase extends Phase { handleTutorial(this.scene, Tutorial.Intro).then(() => super.end()); } } +//#endregion + + + + +//#region 03 TitlePhase export class TitlePhase extends Phase { private loaded: boolean; private lastSessionData: SessionSaveData; @@ -176,20 +363,38 @@ export class TitlePhase extends Phase { this.loaded = false; } + setBiomeByType(biome: Biome, override?: boolean): void { + if (!this.scene.menuChangesBiome && !override) + return; + this.scene.arenaBg.setTexture(`${getBiomeKey(biome)}_bg`); + } + setBiomeByName(biome: string, override?: boolean): void { + if (!this.scene.menuChangesBiome && !override) + return; + this.scene.arenaBg.setTexture(`${getBiomeKey(Utils.getEnumValues(Biome)[Utils.getEnumKeys(Biome).indexOf(biome)])}_bg`); + } + setBiomeByFile(sessionData: SessionSaveData, override?: boolean): void { + if (!this.scene.menuChangesBiome && !override) + return; + this.scene.arenaBg.setTexture(`${getBiomeKey(sessionData.arena.biome)}_bg`); + } + start(): void { super.start(); + console.log(LoggerTools.importDocument(JSON.stringify(LoggerTools.newDocument()))) this.scene.ui.clearText(); this.scene.ui.fadeIn(250); this.scene.playBgm("title", true); + this.scene.biomeChangeMode = false + this.scene.gameData.getSession(loggedInUser.lastSessionSlot).then(sessionData => { if (sessionData) { this.lastSessionData = sessionData; - const biomeKey = getBiomeKey(sessionData.arena.biome); - const bgTexture = `${biomeKey}_bg`; - this.scene.arenaBg.setTexture(bgTexture); + this.setBiomeByFile(sessionData, true) + this.setBiomeByType(Biome.END) } this.showOptions(); }).catch(err => { @@ -198,8 +403,170 @@ export class TitlePhase extends Phase { }); } - showOptions(): void { + getLastSave(log?: boolean, dailyOnly?: boolean, noDaily?: boolean): SessionSaveData { + var saves: Array> = []; + for (var i = 0; i < 5; i++) { + var s = parseSlotData(i); + if (s != undefined) { + if ((!noDaily && !dailyOnly) || (s.gameMode == GameModes.DAILY && dailyOnly) || (s.gameMode != GameModes.DAILY && noDaily)) { + saves.push([i, s, s.timestamp]); + } + } + } + saves.sort((a, b): integer => {return b[2] - a[2]}) + if (log) console.log(saves) + if (saves == undefined) return undefined; + if (saves[0] == undefined) return undefined; + return saves[0][1] + } + getLastSavesOfEach(log?: boolean): SessionSaveData[] { + var saves: Array> = []; + for (var i = 0; i < 5; i++) { + var s = parseSlotData(i); + if (s != undefined) { + saves.push([i, s, s.timestamp]); + } + } + saves.sort((a, b): integer => {return b[2] - a[2]}) + if (log) console.log(saves) + if (saves == undefined) return undefined; + if (saves[0] == undefined) return undefined; + var validSaves = [] + var hasNormal = false; + var hasDaily = false; + for (var i = 0; i < saves.length; i++) { + if (saves[i][1].gameMode == GameModes.DAILY && !hasDaily) { + hasDaily = true; + validSaves.push(saves[i]) + } + if (saves[i][1].gameMode != GameModes.DAILY && !hasNormal) { + hasNormal = true; + validSaves.push(saves[i]) + } + } + console.log(saves, validSaves) + if (validSaves.length == 0) + return undefined; + return validSaves.map(f => f[1]); + } + getSaves(log?: boolean, dailyOnly?: boolean): SessionSaveData[] { + var saves: Array> = []; + for (var i = 0; i < 5; i++) { + var s = parseSlotData(i); + if (s != undefined) { + if (!dailyOnly || s.gameMode == GameModes.DAILY) { + saves.push([i, s, s.timestamp]); + } + } + } + saves.sort((a, b): integer => {return b[2] - a[2]}) + if (log) console.log(saves) + if (saves == undefined) return undefined; + return saves.map(f => f[1]); + } + getSavesUnsorted(log?: boolean, dailyOnly?: boolean): SessionSaveData[] { + var saves: Array> = []; + for (var i = 0; i < 5; i++) { + var s = parseSlotData(i); + if (s != undefined) { + if (!dailyOnly || s.gameMode == GameModes.DAILY) { + saves.push([i, s, s.timestamp]); + } + } + } + if (log) console.log(saves) + if (saves == undefined) return undefined; + return saves.map(f => f[1]); + } + + callEnd(): boolean { + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + super.end(); + return true; + } + + showLoggerOptions(txt: string, options: OptionSelectItem[]): boolean { + this.scene.ui.showText("Export or clear game logs.", null, () => this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); + return true; + } + + logMenu(): boolean { const options: OptionSelectItem[] = []; + LoggerTools.getLogs() + for (var i = 0; i < LoggerTools.logs.length; i++) { + if (localStorage.getItem(LoggerTools.logs[i][1]) != null) { + options.push(LoggerTools.generateOption(i, this.getSaves()) as OptionSelectItem) + } else { + //options.push(LoggerTools.generateAddOption(i, this.scene, this)) + } + } + options.push({ + label: "Delete all", + handler: () => { + for (var i = 0; i < LoggerTools.logs.length; i++) { + if (localStorage.getItem(LoggerTools.logs[i][1]) != null) { + localStorage.removeItem(LoggerTools.logs[i][1]) + } + } + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + super.end(); + return true; + } + }, { + label: i18next.t("menu:cancel"), + handler: () => { + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + super.end(); + return true; + } + }); + this.scene.ui.showText("Export or clear game logs.", null, () => this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); + return true; + } + logRenameMenu(): boolean { + const options: OptionSelectItem[] = []; + LoggerTools.getLogs() + this.setBiomeByType(Biome.FACTORY) + for (var i = 0; i < LoggerTools.logs.length; i++) { + if (localStorage.getItem(LoggerTools.logs[i][1]) != null) { + options.push(LoggerTools.generateEditOption(this.scene, i, this.getSaves(), this) as OptionSelectItem) + } else { + //options.push(LoggerTools.generateAddOption(i, this.scene, this)) + } + } + options.push({ + label: "Delete all", + handler: () => { + for (var i = 0; i < LoggerTools.logs.length; i++) { + if (localStorage.getItem(LoggerTools.logs[i][1]) != null) { + localStorage.removeItem(LoggerTools.logs[i][1]) + } + } + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + super.end(); + return true; + } + }, { + label: i18next.t("menu:cancel"), + handler: () => { + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + super.end(); + return true; + } + }); + this.scene.ui.showText("Export, rename, or delete logs.", null, () => this.scene.ui.setOverlayMode(Mode.OPTION_SELECT, { options: options })); + return true; + } + + showOptions(): void { + this.scene.biomeChangeMode = true + const options: OptionSelectItem[] = []; + if (false) if (loggedInUser.lastSessionSlot > -1) { options.push({ label: i18next.t("continue", null, { ns: "menu"}), @@ -209,9 +576,71 @@ export class TitlePhase extends Phase { } }); } + // Replaces 'Continue' with all Daily Run saves, sorted by when they last saved + // If there are no daily runs, it instead shows the most recently saved run + // If this fails too, there are no saves, and the option does not appear + var lastsaves = this.getSaves(false, true); // Gets all Daily Runs sorted by last play time + var lastsave = this.getLastSave(); // Gets the last save you played + var ls1 = this.getLastSave(false, true) + var ls2 = this.getLastSavesOfEach() + switch (true) { + case (this.scene.quickloadDisplayMode == "Daily" && this.getLastSave(false, true) != undefined): + options.push({ + label: (ls1.description ? ls1.description : "[???]"), + handler: () => { + this.loadSaveSlot(ls1.slot); + return true; + } + }) + break; + case this.scene.quickloadDisplayMode == "Dailies" && this.getLastSave(false, true) != undefined: + lastsaves.forEach(lastsave1 => { + options.push({ + label: (lastsave1.description ? lastsave1.description : "[???]"), + handler: () => { + this.loadSaveSlot(lastsave1.slot); + return true; + } + }) + }) + break; + case lastsave != undefined && (this.scene.quickloadDisplayMode == "Latest" || ((this.scene.quickloadDisplayMode == "Daily" || this.scene.quickloadDisplayMode == "Dailies") && this.getLastSave(false, true) == undefined)): + options.push({ + label: (lastsave.description ? lastsave.description : "[???]"), + handler: () => { + this.loadSaveSlot(lastsave.slot); + return true; + } + }) + break; + case this.scene.quickloadDisplayMode == "Both" && ls2 != undefined: + ls2.forEach(lastsave2 => { + options.push({ + label: (lastsave2.description ? lastsave2.description : "[???]"), + handler: () => { + this.loadSaveSlot(lastsave2.slot); + return true; + } + }) + }) + break; + default: // If set to "Off" or all above conditions failed + if (loggedInUser.lastSessionSlot > -1) { + options.push({ + label: i18next.t("continue", null, { ns: "menu"}), + handler: () => { + this.loadSaveSlot(this.lastSessionData ? -1 : loggedInUser.lastSessionSlot); + return true; + } + }); + } + break; + } options.push({ label: i18next.t("menu:newGame"), handler: () => { + this.scene.biomeChangeMode = false + this.setBiomeByType(Biome.TOWN) const setModeAndEnd = (gameMode: GameModes) => { this.gameMode = gameMode; this.scene.ui.setMode(Mode.MESSAGE); @@ -269,24 +698,44 @@ export class TitlePhase extends Phase { } return true; } - }, - { + }, { + label: "Manage Logs", + handler: () => { + this.scene.biomeChangeMode = false + return this.logRenameMenu() + /* + this.scene.ui.setOverlayMode(Mode.LOG_HANDLER, + (k: string) => { + if (k === undefined) { + return this.showOptions(); + } + console.log(k) + }, () => { + this.showOptions(); + }); + return true; + */ + } + }) + options.push({ label: i18next.t("menu:loadGame"), handler: () => { + this.scene.biomeChangeMode = false this.scene.ui.setOverlayMode(Mode.SAVE_SLOT, SaveSlotUiMode.LOAD, - (slotId: integer) => { + (slotId: integer, autoSlot: integer) => { if (slotId === -1) { return this.showOptions(); } - this.loadSaveSlot(slotId); + this.loadSaveSlot(slotId, autoSlot); }); return true; } - }, - { + }) + options.push({ label: i18next.t("menu:dailyRun"), handler: () => { - this.initDailyRun(); + this.scene.biomeChangeMode = false + this.setupDaily(); return true; }, keepOpen: true @@ -294,6 +743,7 @@ export class TitlePhase extends Phase { { label: i18next.t("menu:settings"), handler: () => { + this.scene.biomeChangeMode = false this.scene.ui.setOverlayMode(Mode.SETTINGS); return true; }, @@ -307,10 +757,10 @@ export class TitlePhase extends Phase { this.scene.ui.setMode(Mode.TITLE, config); } - loadSaveSlot(slotId: integer): void { + loadSaveSlot(slotId: integer, autoSlot?: integer): void { this.scene.sessionSlotId = slotId > -1 ? slotId : loggedInUser.lastSessionSlot; this.scene.ui.setMode(Mode.MESSAGE); - this.scene.gameData.loadSession(this.scene, slotId, slotId === -1 ? this.lastSessionData : null).then((success: boolean) => { + this.scene.gameData.loadSession(this.scene, slotId, slotId === -1 ? this.lastSessionData : null, autoSlot).then((success: boolean) => { if (success) { this.loaded = true; this.scene.ui.showText(i18next.t("menu:sessionSuccess"), null, () => this.end()); @@ -391,8 +841,52 @@ export class TitlePhase extends Phase { } }); } - + setupDaily(): void { + // TODO + var saves = this.getSaves() + var saveNames = new Array(5).fill("") + for (var i = 0; i < saves.length; i++) { + saveNames[saves[i][0]] = saves[i][1].description + } + const ui = this.scene.ui + const confirmSlot = (message: string, slotFilter: (i: integer) => boolean, callback: (i: integer) => void) => { + ui.revertMode(); + ui.showText(message, null, () => { + const config: OptionSelectConfig = { + options: new Array(5).fill(null).map((_, i) => i).filter(slotFilter).map(i => { + return { + label: (i+1) + " " + saveNames[i], + handler: () => { + callback(i); + ui.revertMode(); + ui.showText(null, 0); + return true; + } + }; + }).concat([{ + label: i18next.t("menuUiHandler:cancel"), + handler: () => { + ui.revertMode(); + ui.showText(null, 0); + return true; + } + }]), + xOffset: 98 + }; + ui.setOverlayMode(Mode.MENU_OPTION_SELECT, config); + }); + }; + ui.showText("This feature is incomplete.", null, () => { + this.scene.clearPhaseQueue(); + this.scene.pushPhase(new TitlePhase(this.scene)); + super.end(); + return true; + }) + return; + confirmSlot("Select a slot to replace.", () => true, slotId => this.scene.gameData.importData(GameDataType.SESSION, slotId)); + } end(): void { + this.scene.biomeChangeMode = false if (!this.loaded && !this.scene.gameMode.isDaily) { this.scene.arena.preloadBgm(); this.scene.gameMode = getGameMode(this.gameMode); @@ -436,7 +930,13 @@ export class TitlePhase extends Phase { super.end(); } } +//#endregion + + + + +//#region 04 UnavailablePhase export class UnavailablePhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -449,7 +949,13 @@ export class UnavailablePhase extends Phase { }); } } +//#endregion + + + + +//#region 05 ReloadSessionPhase export class ReloadSessionPhase extends Phase { private systemDataStr: string; @@ -484,7 +990,13 @@ export class ReloadSessionPhase extends Phase { }); } } +//#endregion + + + + +//#region 06 OutdatedPhase export class OutdatedPhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -494,7 +1006,13 @@ export class OutdatedPhase extends Phase { this.scene.ui.setMode(Mode.OUTDATED); } } +//#endregion + + + + +//#region 07 SelectGenderPhase export class SelectGenderPhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -534,7 +1052,13 @@ export class SelectGenderPhase extends Phase { super.end(); } } +//#endregion + + + + +//#region 08 SelectChallengePhase export class SelectChallengePhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -548,7 +1072,13 @@ export class SelectChallengePhase extends Phase { this.scene.ui.setMode(Mode.CHALLENGE_SELECT); } } +//#endregion + + + + +//#region 09 SelectStarterPhase export class SelectStarterPhase extends Phase { constructor(scene: BattleScene) { @@ -635,7 +1165,13 @@ export class SelectStarterPhase extends Phase { }); } } +//#endregion + + + + +//#region 10 BattlePhase export class BattlePhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -679,9 +1215,14 @@ export class BattlePhase extends Phase { }); } } +//#endregion + + + + +//#region 11 FieldPhase type PokemonFunc = (pokemon: Pokemon) => void; - export abstract class FieldPhase extends BattlePhase { getOrder(): BattlerIndex[] { const playerField = this.scene.getPlayerField().filter(p => p.isActive()) as Pokemon[]; @@ -717,7 +1258,13 @@ export abstract class FieldPhase extends BattlePhase { field.forEach(pokemon => func(pokemon)); } } +//#endregion + + + + +//#region 12 PokemonPhase export abstract class PokemonPhase extends FieldPhase { protected battlerIndex: BattlerIndex | integer; public player: boolean; @@ -742,7 +1289,13 @@ export abstract class PokemonPhase extends FieldPhase { return this.scene.getField()[this.battlerIndex]; } } +//#endregion + + + + +//#region 13 PartyMembPkmn export abstract class PartyMemberPokemonPhase extends FieldPhase { protected partyMemberIndex: integer; protected fieldIndex: integer; @@ -766,7 +1319,13 @@ export abstract class PartyMemberPokemonPhase extends FieldPhase { return this.getParty()[this.partyMemberIndex]; } } +//#endregion + + + + +//#region 14 PlayerPartyMemberPokemonPhase export abstract class PlayerPartyMemberPokemonPhase extends PartyMemberPokemonPhase { constructor(scene: BattleScene, partyMemberIndex: integer) { super(scene, partyMemberIndex, true); @@ -776,7 +1335,13 @@ export abstract class PlayerPartyMemberPokemonPhase extends PartyMemberPokemonPh return super.getPokemon() as PlayerPokemon; } } +//#endregion + + + + +//#region 15 EnemyPartyMemberPokemonPhase export abstract class EnemyPartyMemberPokemonPhase extends PartyMemberPokemonPhase { constructor(scene: BattleScene, partyMemberIndex: integer) { super(scene, partyMemberIndex, false); @@ -786,7 +1351,13 @@ export abstract class EnemyPartyMemberPokemonPhase extends PartyMemberPokemonPha return super.getPokemon() as EnemyPokemon; } } +//#endregion + + + + +//#region 16 EncounterPhase export class EncounterPhase extends BattlePhase { private loaded: boolean; @@ -816,11 +1387,16 @@ export class EncounterPhase extends BattlePhase { let totalBst = 0; + while (LoggerTools.rarities.length > 0) { + LoggerTools.rarities.pop() + } + LoggerTools.rarityslot[0] = 0 battle.enemyLevels.forEach((level, e) => { if (!this.loaded) { if (battle.battleType === BattleType.TRAINER) { battle.enemyParty[e] = battle.trainer.genPartyMember(e); } else { + LoggerTools.rarityslot[0] = e const enemySpecies = this.scene.randomSpecies(battle.waveIndex, level, true); battle.enemyParty[e] = this.scene.addEnemyPokemon(enemySpecies, level, TrainerSlot.NONE, !!this.scene.getEncounterBossSegments(battle.waveIndex, level, enemySpecies)); if (this.scene.currentBattle.battleSpec === BattleSpec.FINAL_BOSS) { @@ -860,6 +1436,7 @@ export class EncounterPhase extends BattlePhase { console.log(enemyPokemon.name, enemyPokemon.species.speciesId, enemyPokemon.stats); }); + console.log(LoggerTools.rarities) if (this.scene.getParty().filter(p => p.isShiny()).length === 6) { this.scene.validateAchv(achvs.SHINY_PARTY); @@ -980,6 +1557,23 @@ export class EncounterPhase extends BattlePhase { doEncounterCommon(showEncounterMessage: boolean = true) { const enemyField = this.scene.getEnemyField(); + //LoggerTools.resetWave(this.scene, this.scene.currentBattle.waveIndex) + if (this.scene.lazyReloads) { + LoggerTools.flagResetIfExists(this.scene) + } + LoggerTools.logTeam(this.scene, this.scene.currentBattle.waveIndex) + if (this.scene.getEnemyParty()[0].hasTrainer()) { + LoggerTools.logTrainer(this.scene, this.scene.currentBattle.waveIndex) + } + if (this.scene.currentBattle.waveIndex == 1) { + LoggerTools.logPlayerTeam(this.scene) + } + LoggerTools.resetWaveActions(this.scene, undefined, true) + + if (LoggerTools.autoCheckpoints.includes(this.scene.currentBattle.waveIndex)) { + this.scene.gameData.saveGameToAuto(this.scene) + } + if (this.scene.currentBattle.battleType === BattleType.WILD) { enemyField.forEach(enemyPokemon => { enemyPokemon.untint(100, "Sine.easeOut"); @@ -1123,7 +1717,13 @@ export class EncounterPhase extends BattlePhase { return false; } } +//#endregion + + + + +//#region 17 NextEncounterPhase export class NextEncounterPhase extends EncounterPhase { constructor(scene: BattleScene) { super(scene); @@ -1167,7 +1767,13 @@ export class NextEncounterPhase extends EncounterPhase { }); } } +//#endregion + + + + +//#region 18 NewBiomeEncounterPhase export class NewBiomeEncounterPhase extends NextEncounterPhase { constructor(scene: BattleScene) { super(scene); @@ -1201,7 +1807,13 @@ export class NewBiomeEncounterPhase extends NextEncounterPhase { }); } } +//#endregion + + + + +//#region 19 PostSummonPhase export class PostSummonPhase extends PokemonPhase { constructor(scene: BattleScene, battlerIndex: BattlerIndex) { super(scene, battlerIndex); @@ -1219,7 +1831,13 @@ export class PostSummonPhase extends PokemonPhase { applyPostSummonAbAttrs(PostSummonAbAttr, pokemon).then(() => this.end()); } } +//#endregion + + + + +//#region 20 SelectBiomePh. export class SelectBiomePhase extends BattlePhase { constructor(scene: BattleScene) { super(scene); @@ -1293,7 +1911,13 @@ export class SelectBiomePhase extends BattlePhase { return this.scene.generateRandomBiome(this.scene.currentBattle.waveIndex); } } +//#endregion + + + + +//#region 21 SwitchBiomePhase export class SwitchBiomePhase extends BattlePhase { private nextBiome: Biome; @@ -1354,7 +1978,13 @@ export class SwitchBiomePhase extends BattlePhase { }); } } +//#endregion + + + + +//#region 22 SummonPhase export class SummonPhase extends PartyMemberPokemonPhase { private loaded: boolean; @@ -1537,7 +2167,13 @@ export class SummonPhase extends PartyMemberPokemonPhase { super.end(); } } +//#endregion + + + + +//#region 23 SwitchSummonPhase export class SwitchSummonPhase extends SummonPhase { private slotIndex: integer; private doReturn: boolean; @@ -1679,7 +2315,13 @@ export class SwitchSummonPhase extends SummonPhase { this.scene.unshiftPhase(new PostSummonPhase(this.scene, this.getPokemon().getBattlerIndex())); } } +//#endregion + + + + +//#region 24 ReturnPhase export class ReturnPhase extends SwitchSummonPhase { constructor(scene: BattleScene, fieldIndex: integer) { super(scene, fieldIndex, -1, true, false); @@ -1702,7 +2344,13 @@ export class ReturnPhase extends SwitchSummonPhase { this.scene.triggerPokemonFormChange(pokemon, SpeciesFormChangeActiveTrigger); } } +//#endregion + + + + +//#region 25 ShowTrainerPhase export class ShowTrainerPhase extends BattlePhase { constructor(scene: BattleScene) { super(scene); @@ -1723,7 +2371,13 @@ export class ShowTrainerPhase extends BattlePhase { }); } } +//#endregion + + + + +//#region 26 ToggleDoublePositionPhase export class ToggleDoublePositionPhase extends BattlePhase { private double: boolean; @@ -1751,7 +2405,13 @@ export class ToggleDoublePositionPhase extends BattlePhase { } } } +//#endregion + + + + +//#region 27 CheckSwitchPhase export class CheckSwitchPhase extends BattlePhase { protected fieldIndex: integer; protected useName: boolean; @@ -1789,20 +2449,87 @@ 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") + } + } + if (false) { + this.scene.pokemonInfoContainer.show(this.scene.getEnemyField()[0], false, 1, true); + if (this.scene.getEnemyField()[1] != undefined) { + this.scene.tweens.add({ + targets: this.scene.pokemonInfoContainer, + alpha: 1, + duration: 5000, + onComplete: () => { + this.scene.pokemonInfoContainer.hide(1.3) + this.scene.tweens.add({ + targets: this.scene.pokemonInfoContainer, + alpha: 1, + duration: 1000, + onComplete: () => { + this.scene.pokemonInfoContainer.show(this.scene.getEnemyField()[1], false, 1, true); + } + }) + } + }) + } + } + 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); + LoggerTools.isPreSwitch.value = true 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.scene.pokemonInfoContainer.hide() 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.scene.pokemonInfoContainer.hide() this.end(); }); }); } } +//#endregion + + + + +//#region 28 SummonMissingPhase export class SummonMissingPhase extends SummonPhase { constructor(scene: BattleScene, fieldIndex: integer) { super(scene, fieldIndex); @@ -1813,7 +2540,13 @@ export class SummonMissingPhase extends SummonPhase { this.scene.time.delayedCall(250, () => this.summon()); } } +//#endregion + + + + +//#region 29 LevelCapPhase export class LevelCapPhase extends FieldPhase { constructor(scene: BattleScene) { super(scene); @@ -1829,12 +2562,72 @@ export class LevelCapPhase extends FieldPhase { }); } } +//#endregion + + + + +//#region 30 TurnInitPhase export class TurnInitPhase extends FieldPhase { constructor(scene: BattleScene) { 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(); @@ -1912,10 +2705,27 @@ 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.arenaFlyout.updateFieldText() + + this.scene.setScoreText(txt.join(" / ")) + this.end(); } } +//#endregion + + + + +//#region 31 CommandPhase export class CommandPhase extends FieldPhase { protected fieldIndex: integer; @@ -2168,7 +2978,13 @@ export class CommandPhase extends FieldPhase { this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); } } +//#endregion + + + + +//#region 32 EnemyCommandPhase export class EnemyCommandPhase extends FieldPhase { protected fieldIndex: integer; @@ -2228,7 +3044,13 @@ export class EnemyCommandPhase extends FieldPhase { this.end(); } } +//#endregion + + + + +//#region 33 SelectTargetPhase export class SelectTargetPhase extends PokemonPhase { constructor(scene: BattleScene, fieldIndex: integer) { super(scene, fieldIndex); @@ -2254,12 +3076,46 @@ export class SelectTargetPhase extends PokemonPhase { }); } } +//#endregion + + + + +//#region 34 TurnStartPhase export class TurnStartPhase extends FieldPhase { constructor(scene: BattleScene) { super(scene); } + generateTargString(t: BattlerIndex[]) { + var targets = ['Self'] + for (var i = 0; i < this.scene.getField().length; i++) { + if (this.scene.getField()[i] != null) + targets[this.scene.getField()[i].getBattlerIndex() + 1] = this.scene.getField()[i].name + } + for (var i = 0; i < this.scene.getEnemyField().length; i++) { + if (this.scene.getEnemyField()[i] != null) + targets[this.scene.getEnemyField()[i].getBattlerIndex() + 1] = this.scene.getEnemyField()[i].name + } + var targetFull = [] + for (var i = 0; i < t.length; i++) { + targetFull.push(targets[t[i] + 1]) + } + if (targetFull.join(", ") == targets.join(", ")) return "" + return " → " + targetFull.join(", ") + } + + getBattlers(user: Pokemon): Pokemon[] { + var battlers = [] + battlers[0] = this.scene.getField()[0] + battlers[1] = this.scene.getField()[1] + battlers[2] = this.scene.getEnemyField()[0] + battlers[3] = this.scene.getEnemyField()[1] + battlers.unshift(user) + return battlers; + } + start() { super.start(); @@ -2268,6 +3124,55 @@ export class TurnStartPhase extends FieldPhase { const battlerBypassSpeed = {}; + const playerActions = [] + + const moveOrder = order.slice(0); + + while (LoggerTools.Actions.length > 0) { + LoggerTools.Actions.pop() + } + + for (const o of moveOrder) { + + const pokemon = field[o]; + const turnCommand = this.scene.currentBattle.turnCommands[o]; + + if (turnCommand.skip || !pokemon.isPlayer()) { + continue; + } + + switch (turnCommand.command) { + case Command.FIGHT: + const queuedMove = turnCommand.move; + if (!queuedMove) { + continue; + } + LoggerTools.Actions[pokemon.getBattlerIndex()] = `[[ ${new PokemonMove(queuedMove.move).getName()} unknown target ]]` + break; + case Command.BALL: + var ballNames = [ + "Poké Ball", + "Great Ball", + "Ultra Ball", + "Rogue Ball", + "Master Ball", + "Luxury Ball" + ] + LoggerTools.Actions[pokemon.getBattlerIndex()] = ballNames[turnCommand.cursor] + playerActions.push(ballNames[turnCommand.cursor]) + //this.scene.unshiftPhase(new AttemptCapturePhase(this.scene, turnCommand.targets[0] % 2, turnCommand.cursor)); + break; + case Command.POKEMON: + LoggerTools.Actions[pokemon.getBattlerIndex()] = ((turnCommand.args[0] as boolean) ? "Baton" : "Switch") + " " + LoggerTools.playerPokeName(this.scene, pokemon) + " to " + LoggerTools.playerPokeName(this.scene, turnCommand.cursor) + break; + case Command.RUN: + LoggerTools.Actions[pokemon.getBattlerIndex()] = "Run" + playerActions.push("Run") + break; + } + } + //LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, playerActions.join(" | ")) + this.scene.getField(true).filter(p => p.summonData).map(p => { const bypassSpeed = new Utils.BooleanHolder(false); applyAbAttrs(BypassSpeedChanceAbAttr, p, null, bypassSpeed); @@ -2275,8 +3180,6 @@ export class TurnStartPhase extends FieldPhase { battlerBypassSpeed[p.getBattlerIndex()] = bypassSpeed; }); - const moveOrder = order.slice(0); - moveOrder.sort((a, b) => { const aCommand = this.scene.currentBattle.turnCommands[a]; const bCommand = this.scene.currentBattle.turnCommands[b]; @@ -2333,13 +3236,67 @@ export class TurnStartPhase extends FieldPhase { const move = pokemon.getMoveset().find(m => m.moveId === queuedMove.move) || new PokemonMove(queuedMove.move); if (pokemon.isPlayer()) { if (turnCommand.cursor === -1) { + console.log("turncommand cursor was -1 -- running TOP block") this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move.targets, move)); + var targets = turnCommand.targets || turnCommand.move.targets + var mv = move + if (pokemon.isPlayer()) { + console.log(turnCommand.targets, turnCommand.move.targets) + LoggerTools.Actions[pokemon.getBattlerIndex()] = mv.getName() + if (this.scene.currentBattle.double) { + var targIDs = ["Counter", "Self", "Ally", "L", "R"] + if (pokemon.getBattlerIndex() == 1) targIDs = ["Counter", "Ally", "Self", "L", "R"] + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + } else { + var targIDs = ["Counter", "", "", "", ""] + var myField = this.scene.getField() + if (myField[0]) + targIDs[1] = myField[0].name + if (myField[1]) + targIDs[2] = myField[1].name + var eField = this.scene.getEnemyField() + if (eField[0]) + targIDs[3] = eField[0].name + if (eField[1]) + targIDs[4] = eField[1].name + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + } + console.log(mv.getName(), targets) + } } else { + console.log("turncommand = ", turnCommand, " -- running BOTTO< block") const playerPhase = new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move.targets, move, false, queuedMove.ignorePP); + var targets = turnCommand.targets || turnCommand.move.targets + var mv = move + if (pokemon.isPlayer()) { + console.log(turnCommand.targets, turnCommand.move.targets) + LoggerTools.Actions[pokemon.getBattlerIndex()] = mv.getName() + if (this.scene.currentBattle.double) { + var targIDs = ["Counter", "Self", "Ally", "L", "R"] + if (pokemon.getBattlerIndex() == 1) targIDs = ["Counter", "Ally", "Self", "L", "R"] + LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + } else { + var targIDs = ["Counter", "", "", "", ""] + var myField = this.scene.getField() + if (myField[0]) + targIDs[1] = myField[0].name + if (myField[1]) + targIDs[2] = myField[1].name + var eField = this.scene.getEnemyField() + if (eField[0]) + targIDs[3] = eField[0].name + if (eField[1]) + targIDs[4] = eField[1].name + //LoggerTools.Actions[pokemon.getBattlerIndex()] += " → " + targets.map(v => targIDs[v+1]) + } + console.log(mv.getName(), targets) + } this.scene.pushPhase(playerPhase); } } else { this.scene.pushPhase(new MovePhase(this.scene, pokemon, turnCommand.targets || turnCommand.move.targets, move, false, queuedMove.ignorePP)); + var targets = turnCommand.targets || turnCommand.move.targets + var mv = new PokemonMove(queuedMove.move) } break; case Command.BALL: @@ -2384,10 +3341,23 @@ export class TurnStartPhase extends FieldPhase { this.scene.pushPhase(new BerryPhase(this.scene)); this.scene.pushPhase(new TurnEndPhase(this.scene)); + this.scene.arenaFlyout.updateFieldText() + + if (LoggerTools.Actions.length > 1 && (LoggerTools.Actions[0] == "" || LoggerTools.Actions[0] == undefined || LoggerTools.Actions[0] == null)) + LoggerTools.Actions.shift() // If the left slot isn't doing anything, delete its entry + + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, LoggerTools.Actions.join(" | ")) + this.end(); } } +//#endregion + + + + +//#region 35 BerryPhase /** The phase after attacks where the pokemon eat berries */ export class BerryPhase extends FieldPhase { start() { @@ -2430,7 +3400,13 @@ export class BerryPhase extends FieldPhase { this.end(); } } +//#endregion + + + + +//#region 36 TurnEndPhase export class TurnEndPhase extends FieldPhase { constructor(scene: BattleScene) { super(scene); @@ -2439,6 +3415,8 @@ export class TurnEndPhase extends FieldPhase { start() { super.start(); + this.scene.arenaFlyout.updateFieldText() + this.scene.currentBattle.incrementTurn(this.scene); this.scene.eventTarget.dispatchEvent(new TurnEndEvent(this.scene.currentBattle.turn)); @@ -2486,13 +3464,43 @@ export class TurnEndPhase extends FieldPhase { this.end(); } } +//#endregion + + + + +//#region 37 BattleEndPhase export class BattleEndPhase extends BattlePhase { start() { super.start(); this.scene.currentBattle.addBattleScore(this.scene); + var drpd: LoggerTools.DRPD = LoggerTools.getDRPD(this.scene) + var wv: LoggerTools.Wave = LoggerTools.getWave(drpd, this.scene.currentBattle.waveIndex, this.scene) + var lastcount = 0; + var lastval = undefined; + var tempActions = wv.actions.slice(); + wv.actions = [] + // Loop through each action + for (var i = 0; i < tempActions.length; i++) { + if (tempActions[i] != lastval) { + if (lastcount > 0) { + wv.actions.push(lastval + (lastcount == 1 ? "" : " x" + lastcount)) + } + lastval = tempActions[i] + lastcount = 1 + } else { + lastcount++ + } + } + if (lastcount > 0) { + wv.actions.push(lastval + (lastcount == 1 ? "" : " x" + lastcount)) + } + console.log(tempActions, wv.actions) + LoggerTools.save(this.scene, drpd) + this.scene.gameData.gameStats.battles++; if (this.scene.currentBattle.trainer) { this.scene.gameData.gameStats.trainersDefeated++; @@ -2537,7 +3545,13 @@ export class BattleEndPhase extends BattlePhase { this.scene.updateModifiers().then(() => this.end()); } } +//#endregion + + + + +//#region 38 NewBattlePhase export class NewBattlePhase extends BattlePhase { start() { super.start(); @@ -2547,7 +3561,13 @@ export class NewBattlePhase extends BattlePhase { this.end(); } } +//#endregion + + + + +//#region 39 CommonAnimPhase export class CommonAnimPhase extends PokemonPhase { private anim: CommonAnim; private targetIndex: integer; @@ -2569,7 +3589,13 @@ export class CommonAnimPhase extends PokemonPhase { }); } } +//#endregion + + + + +//#region 40 MovePhase export class MovePhase extends BattlePhase { public pokemon: Pokemon; public move: PokemonMove; @@ -2865,7 +3891,13 @@ export class MovePhase extends BattlePhase { super.end(); } } +//#endregion + + + + +//#region 41 MoveEffectPhase export class MoveEffectPhase extends PokemonPhase { public move: PokemonMove; protected targets: BattlerIndex[]; @@ -3172,7 +4204,13 @@ export class MoveEffectPhase extends PokemonPhase { return new MoveEffectPhase(this.scene, this.battlerIndex, this.targets, this.move); } } +//#endregion + + + + +//#region 42 MoveEndPhase export class MoveEndPhase extends PokemonPhase { constructor(scene: BattleScene, battlerIndex: BattlerIndex) { super(scene, battlerIndex); @@ -3191,7 +4229,13 @@ export class MoveEndPhase extends PokemonPhase { this.end(); } } +//#endregion + + + + +//#region 43 MoveAnimTestPhase export class MoveAnimTestPhase extends BattlePhase { private moveQueue: Moves[]; @@ -3229,7 +4273,13 @@ export class MoveAnimTestPhase extends BattlePhase { }); } } +//#endregion + + + + +//#region 44 ShowAbilityPhase export class ShowAbilityPhase extends PokemonPhase { private passive: boolean; @@ -3252,7 +4302,13 @@ export class ShowAbilityPhase extends PokemonPhase { this.end(); } } +//#endregion + + + + +//#region 45 StatChangePhase export class StatChangePhase extends PokemonPhase { private stats: BattleStat[]; private selfTarget: boolean; @@ -3451,7 +4507,13 @@ export class StatChangePhase extends PokemonPhase { return messages; } } +//#endregion + + + + +//#region 46 WeatherEffectPhase export class WeatherEffectPhase extends CommonAnimPhase { public weather: Weather; @@ -3510,7 +4572,13 @@ export class WeatherEffectPhase extends CommonAnimPhase { }); } } +//#endregion + + + + +//#region 47 ObtainStatusEffectPhase export class ObtainStatusEffectPhase extends PokemonPhase { private statusEffect: StatusEffect; private cureTurn: integer; @@ -3549,7 +4617,13 @@ export class ObtainStatusEffectPhase extends PokemonPhase { this.end(); } } +//#endregion + + + + +//#region 48 PostTurnStatusEffectPhase export class PostTurnStatusEffectPhase extends PokemonPhase { constructor(scene: BattleScene, battlerIndex: BattlerIndex) { super(scene, battlerIndex); @@ -3591,7 +4665,13 @@ export class PostTurnStatusEffectPhase extends PokemonPhase { } } } +//#endregion + + + + +//#region 49 MessagePhase export class MessagePhase extends Phase { private text: string; private callbackDelay: integer; @@ -3627,7 +4707,13 @@ export class MessagePhase extends Phase { super.end(); } } +//#endregion + + + + +//#region 50 DamagePhase export class DamagePhase extends PokemonPhase { private amount: integer; private damageResult: DamageResult; @@ -3727,7 +4813,13 @@ export class DamagePhase extends PokemonPhase { super.end(); } } +//#endregion + + + + +//#region 51 FaintPhase export class FaintPhase extends PokemonPhase { private preventEndure: boolean; @@ -3796,6 +4888,7 @@ export class FaintPhase extends PokemonPhase { if (!nonFaintedPartyMemberCount) { this.scene.unshiftPhase(new GameOverPhase(this.scene)); } else if (nonFaintedPartyMemberCount >= this.scene.currentBattle.getBattlerCount() || (this.scene.currentBattle.double && !nonFaintedLegalPartyMembers[0].isActive(true))) { + LoggerTools.isFaintSwitch.value = true; this.scene.pushPhase(new SwitchPhase(this.scene, this.fieldIndex, true, false)); } if (nonFaintedPartyMemberCount === 1 && this.scene.currentBattle.double) { @@ -3875,7 +4968,13 @@ export class FaintPhase extends PokemonPhase { return false; } } +//#endregion + + + + +//#region 52 VictoryPhase export class VictoryPhase extends PokemonPhase { constructor(scene: BattleScene, battlerIndex: BattlerIndex) { super(scene, battlerIndex); @@ -3977,11 +5076,13 @@ export class VictoryPhase extends PokemonPhase { if (this.scene.currentBattle.waveIndex % 10) { this.scene.pushPhase(new SelectModifierPhase(this.scene)); } else if (this.scene.gameMode.isDaily) { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, "") this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_CHARM)); if (this.scene.currentBattle.waveIndex > 10 && !this.scene.gameMode.isWaveFinal(this.scene.currentBattle.waveIndex)) { this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.GOLDEN_POKEBALL)); } } else { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, "") const superExpWave = !this.scene.gameMode.isEndless ? (this.scene.offsetGym ? 0 : 20) : 10; if (this.scene.gameMode.isEndless && this.scene.currentBattle.waveIndex === 10) { this.scene.pushPhase(new ModifierRewardPhase(this.scene, modifierTypes.EXP_SHARE)); @@ -3999,6 +5100,7 @@ export class VictoryPhase extends PokemonPhase { } this.scene.pushPhase(new NewBattlePhase(this.scene)); } else { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, "") this.scene.currentBattle.battleType = BattleType.CLEAR; this.scene.score += this.scene.gameMode.getClearScoreBonus(); this.scene.updateScoreText(); @@ -4009,7 +5111,13 @@ export class VictoryPhase extends PokemonPhase { this.end(); } } +//#endregion + + + + +//#region 53 TrainerVictoryPhase export class TrainerVictoryPhase extends BattlePhase { constructor(scene: BattleScene) { super(scene); @@ -4062,7 +5170,13 @@ export class TrainerVictoryPhase extends BattlePhase { this.showEnemyTrainer(); } } +//#endregion + + + + +//#region 54 MoneyRewardPhase export class MoneyRewardPhase extends BattlePhase { private moneyMultiplier: number; @@ -4090,7 +5204,13 @@ export class MoneyRewardPhase extends BattlePhase { this.scene.ui.showText(message, null, () => this.end(), null, true); } } +//#endregion + + + + +//#region 55 ModifierRewardPhase export class ModifierRewardPhase extends BattlePhase { protected modifierType: ModifierType; @@ -4116,7 +5236,13 @@ export class ModifierRewardPhase extends BattlePhase { }); } } +//#endregion + + + + +//#region 56 GameOverModifierRewardPhase export class GameOverModifierRewardPhase extends ModifierRewardPhase { constructor(scene: BattleScene, modifierTypeFunc: ModifierTypeFunc) { super(scene, modifierTypeFunc); @@ -4138,7 +5264,13 @@ export class GameOverModifierRewardPhase extends ModifierRewardPhase { }); } } +//#endregion + + + + +//#region 57 RibbonModifierRewardPhase export class RibbonModifierRewardPhase extends ModifierRewardPhase { private species: PokemonSpecies; @@ -4161,7 +5293,13 @@ export class RibbonModifierRewardPhase extends ModifierRewardPhase { }); } } +//#endregion + + + + +//#region 58 GameOverPhase export class GameOverPhase extends BattlePhase { private victory: boolean; private firstRibbons: PokemonSpecies[] = []; @@ -4335,7 +5473,13 @@ export class GameOverPhase extends BattlePhase { } } } +//#endregion + + + + +//#region 59 EndCardPhase export class EndCardPhase extends Phase { public endCard: Phaser.GameObjects.Image; public text: Phaser.GameObjects.Text; @@ -4370,7 +5514,13 @@ export class EndCardPhase extends Phase { }); } } +//#endregion + + + + +//#region 60 UnlockPhase export class UnlockPhase extends Phase { private unlockable: Unlockables; @@ -4392,7 +5542,13 @@ export class UnlockPhase extends Phase { }); } } +//#endregion + + + + +//#region 61 PostGameOverPhase export class PostGameOverPhase extends Phase { private endCardPhase: EndCardPhase; @@ -4434,7 +5590,13 @@ export class PostGameOverPhase extends Phase { } } } +//#endregion + + + + +//#region 62 SwitchPhase export class SwitchPhase extends BattlePhase { protected fieldIndex: integer; private isModal: boolean; @@ -4453,11 +5615,15 @@ export class SwitchPhase extends BattlePhase { // Skip modal switch if impossible if (this.isModal && !this.scene.getParty().filter(p => p.isAllowedInBattle() && !p.isActive(true)).length) { + LoggerTools.isPreSwitch.value = false; + LoggerTools.isFaintSwitch.value = false; return super.end(); } // Check if there is any space still in field if (this.isModal && this.scene.getPlayerField().filter(p => p.isAllowedInBattle() && p.isActive(true)).length >= this.scene.currentBattle.getBattlerCount()) { + LoggerTools.isPreSwitch.value = false; + LoggerTools.isFaintSwitch.value = false; return super.end(); } @@ -4466,13 +5632,26 @@ export class SwitchPhase extends BattlePhase { this.scene.ui.setMode(Mode.PARTY, this.isModal ? PartyUiMode.FAINT_SWITCH : PartyUiMode.POST_BATTLE_SWITCH, fieldIndex, (slotIndex: integer, option: PartyOption) => { if (slotIndex >= this.scene.currentBattle.getBattlerCount() && slotIndex < 6) { + if (LoggerTools.isPreSwitch.value) { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Pre-switch " + (option == PartyOption.PASS_BATON ? "+ Baton" : "") + " " + LoggerTools.playerPokeName(this.scene, fieldIndex) + " to " + LoggerTools.playerPokeName(this.scene, slotIndex)) + } + if (LoggerTools.isFaintSwitch.value) { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, (option == PartyOption.PASS_BATON ? "Baton" : "Send") + " out " + LoggerTools.playerPokeName(this.scene, slotIndex)) + } this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, fieldIndex, slotIndex, this.doReturn, option === PartyOption.PASS_BATON)); } + LoggerTools.isPreSwitch.value = false; this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); }, PartyUiHandler.FilterNonFainted); } } +//#endregion + + + + +//#region 63 ExpPhase export class ExpPhase extends PlayerPartyMemberPokemonPhase { private expValue: number; @@ -4500,7 +5679,13 @@ export class ExpPhase extends PlayerPartyMemberPokemonPhase { }, null, true); } } +//#endregion + + + + +//#region 64 ShowPartyExpBarPhase export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { private expValue: number; @@ -4549,7 +5734,13 @@ export class ShowPartyExpBarPhase extends PlayerPartyMemberPokemonPhase { } } +//#endregion + + + + +//#region 65 HidePartyExpBarPhase export class HidePartyExpBarPhase extends BattlePhase { constructor(scene: BattleScene) { super(scene); @@ -4561,7 +5752,13 @@ export class HidePartyExpBarPhase extends BattlePhase { this.scene.partyExpBar.hide().then(() => this.end()); } } +//#endregion + + + + +//#region 66 LevelUpPhase export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { private lastLevel: integer; private level: integer; @@ -4610,7 +5807,13 @@ export class LevelUpPhase extends PlayerPartyMemberPokemonPhase { } } } +//#endregion + + + + +//#region 67 LearnMovePhase export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { private moveId: Moves; @@ -4664,6 +5867,12 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { this.scene.ui.showText(i18next.t("battle:learnMoveStopTeaching", { moveName: move.name }), null, () => { this.scene.ui.setModeWithoutClear(Mode.CONFIRM, () => { this.scene.ui.setMode(messageMode); + var W = LoggerTools.getWave(LoggerTools.getDRPD(this.scene), this.scene.currentBattle.waveIndex, this.scene) + if (W.shop != "") { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, W.shop + "; skip learning it") + } else { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, LoggerTools.playerPokeName(this.scene, pokemon) + " | Skip " + move.name) + } this.scene.ui.showText(i18next.t("battle:learnMoveNotLearned", { pokemonName: pokemon.name, moveName: move.name }), null, () => this.end(), null, true); }, () => { this.scene.ui.setMode(messageMode); @@ -4685,6 +5894,12 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { this.scene.ui.showText(i18next.t("battle:countdownPoof"), null, () => { this.scene.ui.showText(i18next.t("battle:learnMoveForgetSuccess", { pokemonName: pokemon.name, moveName: pokemon.moveset[moveIndex].getName() }), null, () => { this.scene.ui.showText(i18next.t("battle:learnMoveAnd"), null, () => { + var W = LoggerTools.getWave(LoggerTools.getDRPD(this.scene), this.scene.currentBattle.waveIndex, this.scene) + if (W.shop != "") { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, W.shop + " → learn " + new PokemonMove(this.moveId).getName() + " → replace " + pokemon.moveset[moveIndex].getName()) + } else { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, LoggerTools.playerPokeName(this.scene, pokemon) + " | Learn " + new PokemonMove(this.moveId).getName() + " → replace " + pokemon.moveset[moveIndex].getName()) + } pokemon.setMove(moveIndex, Moves.NONE); this.scene.unshiftPhase(new LearnMovePhase(this.scene, this.partyMemberIndex, this.moveId)); this.end(); @@ -4702,7 +5917,13 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { } } } +//#endregion + + + + +//#region 68 PokemonHealPhase export class PokemonHealPhase extends CommonAnimPhase { private hpHealed: integer; private message: string; @@ -4796,10 +6017,19 @@ export class PokemonHealPhase extends CommonAnimPhase { } } } +//#endregion + + + + +//#region 69 AttemptCapturePhase export class AttemptCapturePhase extends PokemonPhase { + /** The Pokeball being used. */ private pokeballType: PokeballType; + /** The Pokeball sprite. */ private pokeball: Phaser.GameObjects.Sprite; + /** The sprite's original Y position. */ private originalY: number; constructor(scene: BattleScene, targetIndex: integer, pokeballType: PokeballType) { @@ -4808,6 +6038,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(); @@ -4889,7 +6129,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(); @@ -4961,30 +6201,29 @@ export class AttemptCapturePhase extends PokemonPhase { } catch() { + /** The Pokemon being caught. */ const pokemon = this.getPokemon() as EnemyPokemon; this.scene.unshiftPhase(new VictoryPhase(this.scene, this.battlerIndex)); + /** Used for achievements. */ const speciesForm = !pokemon.fusionSpecies ? pokemon.getSpeciesForm() : pokemon.getFusionSpeciesForm(); - if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) { + // Achievements + if (speciesForm.abilityHidden && (pokemon.fusionSpecies ? pokemon.fusionAbilityIndex : pokemon.abilityIndex) === speciesForm.getAbilityCount() - 1) this.scene.validateAchv(achvs.HIDDEN_ABILITY); - } - - if (pokemon.species.subLegendary) { + if (pokemon.species.subLegendary) this.scene.validateAchv(achvs.CATCH_SUB_LEGENDARY); - } - - if (pokemon.species.legendary) { + if (pokemon.species.legendary) this.scene.validateAchv(achvs.CATCH_LEGENDARY); - } - - if (pokemon.species.mythical) { + if (pokemon.species.mythical) this.scene.validateAchv(achvs.CATCH_MYTHICAL); - } + // Show its info this.scene.pokemonInfoContainer.show(pokemon, true); - + // Update new IVs this.scene.gameData.updateSpeciesDexIvs(pokemon.species.getRootSpeciesId(true), pokemon.ivs); + + LoggerTools.appendAction(this.scene, this.scene.currentBattle.waveIndex, ` (Catches ${pokemon.name})`) this.scene.ui.showText(i18next.t("battle:pokemonCaught", { pokemonName: pokemon.name }), null, () => { const end = () => { @@ -4992,6 +6231,7 @@ export class AttemptCapturePhase extends PokemonPhase { this.removePb(); this.end(); }; + LoggerTools.logCapture(this.scene, this.scene.currentBattle.waveIndex, pokemon) const removePokemon = () => { this.scene.addFaintedEnemyScore(pokemon); this.scene.getPlayerField().filter(p => p.isActive(true)).forEach(playerPokemon => playerPokemon.removeTagsBySourceId(pokemon.id)); @@ -5019,9 +6259,13 @@ export class AttemptCapturePhase extends PokemonPhase { Promise.all([pokemon.hideInfo(), this.scene.gameData.setPokemonCaught(pokemon)]).then(() => { if (this.scene.getParty().length === 6) { const promptRelease = () => { + // Say that your party is full this.scene.ui.showText(i18next.t("battle:partyFull", { pokemonName: pokemon.name }), null, () => { + // Ask if you want to make room this.scene.pokemonInfoContainer.makeRoomForConfirmUi(); this.scene.ui.setMode(Mode.CONFIRM, () => { + // YES + // Open up the party menu on the RELEASE setting this.scene.ui.setMode(Mode.PARTY, PartyUiMode.RELEASE, this.fieldIndex, (slotIndex: integer, _option: PartyOption) => { this.scene.ui.setMode(Mode.MESSAGE).then(() => { if (slotIndex < 6) { @@ -5030,8 +6274,10 @@ export class AttemptCapturePhase extends PokemonPhase { promptRelease(); } }); - }); + }, undefined, undefined, undefined, undefined, pokemon.name); }, () => { + // NO + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Don't keep " + pokemon.name) this.scene.ui.setMode(Mode.MESSAGE).then(() => { removePokemon(); end(); @@ -5041,12 +6287,14 @@ export class AttemptCapturePhase extends PokemonPhase { }; promptRelease(); } else { + //LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, `${pokemon.name} added to party`) addToParty(); } }); }, 0, true); } + /** Remove the Poke Ball from the scene. */ removePb() { this.scene.tweens.add({ targets: this.pokeball, @@ -5058,7 +6306,13 @@ export class AttemptCapturePhase extends PokemonPhase { }); } } +//#endregion + + + + +//#region 70 AttemptRunPhase export class AttemptRunPhase extends PokemonPhase { constructor(scene: BattleScene, fieldIndex: integer) { super(scene, fieldIndex); @@ -5077,6 +6331,7 @@ export class AttemptRunPhase extends PokemonPhase { if (playerPokemon.randSeedInt(256) < escapeChance.value) { this.scene.playSound("flee"); + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, "Fled") this.scene.queueMessage(i18next.t("battle:runAwaySuccess"), null, true, 500); this.scene.tweens.add({ @@ -5104,7 +6359,13 @@ export class AttemptRunPhase extends PokemonPhase { this.end(); } } +//#endregion + + + + +//#region 71 SelectModifierPhase export class SelectModifierPhase extends BattlePhase { private rerollCount: integer; private modifierTiers: ModifierTier[]; @@ -5137,6 +6398,7 @@ export class SelectModifierPhase extends BattlePhase { if (rowCursor < 0 || cursor < 0) { this.scene.ui.showText(i18next.t("battle:skipItemQuestion"), null, () => { this.scene.ui.setOverlayMode(Mode.CONFIRM, () => { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, "Skip taking items") this.scene.ui.revertMode(); this.scene.ui.setMode(Mode.MESSAGE); super.end(); @@ -5156,6 +6418,7 @@ export class SelectModifierPhase extends BattlePhase { return false; } else { this.scene.reroll = true; + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Reroll" + (this.scene.lockModifierTiers ? " (Locked)" : "")) this.scene.unshiftPhase(new SelectModifierPhase(this.scene, this.rerollCount + 1, typeOptions.map(o => o.type.tier))); this.scene.ui.clearText(); this.scene.ui.setMode(Mode.MESSAGE).then(() => super.end()); @@ -5166,11 +6429,17 @@ export class SelectModifierPhase extends BattlePhase { } break; case 1: - this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer) => { + this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.MODIFIER_TRANSFER, -1, (fromSlotIndex: integer, itemIndex: integer, itemQuantity: integer, toSlotIndex: integer, isAll: boolean, isFirst: boolean) => { if (toSlotIndex !== undefined && fromSlotIndex < 6 && toSlotIndex < 6 && fromSlotIndex !== toSlotIndex && itemIndex > -1) { const itemModifiers = this.scene.findModifiers(m => m instanceof PokemonHeldItemModifier && (m as PokemonHeldItemModifier).getTransferrable(true) && (m as PokemonHeldItemModifier).pokemonId === party[fromSlotIndex].id) as PokemonHeldItemModifier[]; const itemModifier = itemModifiers[itemIndex]; + if (isAll) { + if (isFirst) + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Transfer [" + (fromSlotIndex + 1) + "] " + this.scene.getParty()[fromSlotIndex].name + " (All) → [" + (toSlotIndex + 1) + "] " + this.scene.getParty()[toSlotIndex].name) + } else { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "Transfer [" + (fromSlotIndex + 1) + "] " + this.scene.getParty()[fromSlotIndex].name + " (" + itemModifier.type.name + (itemQuantity == itemModifier.getStackCount() ? "" : " x" + itemQuantity) + ") → [" + (toSlotIndex + 1) + "] " + this.scene.getParty()[toSlotIndex].name) + } this.scene.tryTransferHeldItemModifier(itemModifier, party[toSlotIndex], true, itemQuantity); } else { this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer(), typeOptions, modifierSelectCallback, this.getRerollCost(typeOptions, this.scene.lockModifierTiers)); @@ -5239,6 +6508,7 @@ export class SelectModifierPhase extends BattlePhase { if (modifierType instanceof FusePokemonModifierType) { this.scene.ui.setModeWithoutClear(Mode.PARTY, PartyUiMode.SPLICE, -1, (fromSlotIndex: integer, spliceSlotIndex: integer) => { if (spliceSlotIndex !== undefined && fromSlotIndex < 6 && spliceSlotIndex < 6 && fromSlotIndex !== spliceSlotIndex) { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name + " → " + this.scene.getParty()[fromSlotIndex].name + " + " + this.scene.getParty()[spliceSlotIndex].name) this.scene.ui.setMode(Mode.MODIFIER_SELECT, this.isPlayer()).then(() => { const modifier = modifierType.newModifier(party[fromSlotIndex], party[spliceSlotIndex]); applyModifier(modifier, true); @@ -5268,6 +6538,15 @@ export class SelectModifierPhase extends BattlePhase { ? modifierType.newModifier(party[slotIndex]) : modifierType.newModifier(party[slotIndex], option as integer) : modifierType.newModifier(party[slotIndex], option - PartyOption.MOVE_1); + if (isPpRestoreModifier) { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name + " → " + this.scene.getParty()[slotIndex].name + " → " + this.scene.getParty()[slotIndex].moveset[option - PartyOption.MOVE_1].getName()) + } else if (isRememberMoveModifier) { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name + " → " + this.scene.getParty()[slotIndex].name) + } else if (isTmModifier) { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name + " → " + this.scene.getParty()[slotIndex].name) + } else { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name + " → " + this.scene.getParty()[slotIndex].name) + } applyModifier(modifier, true); }); } else { @@ -5276,6 +6555,7 @@ export class SelectModifierPhase extends BattlePhase { }, pokemonModifierType.selectFilter, modifierType instanceof PokemonMoveModifierType ? (modifierType as PokemonMoveModifierType).moveSelectFilter : undefined, tmMoveId, isPpRestoreModifier); } } else { + LoggerTools.logShop(this.scene, this.scene.currentBattle.waveIndex, modifierType.name) applyModifier(modifierType.newModifier()); } @@ -5310,14 +6590,20 @@ export class SelectModifierPhase extends BattlePhase { } getModifierTypeOptions(modifierCount: integer): ModifierTypeOption[] { - return getPlayerModifierTypeOptions(modifierCount, this.scene.getParty(), this.scene.lockModifierTiers ? this.modifierTiers : undefined); + return getPlayerModifierTypeOptions(modifierCount, this.scene.getParty(), this.scene.lockModifierTiers ? this.modifierTiers : undefined, this.scene); } addModifier(modifier: Modifier): Promise { return this.scene.addModifier(modifier, false, true); } } +//#endregion + + + + +//#region 72 EggLapsePhase export class EggLapsePhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -5346,7 +6632,13 @@ export class EggLapsePhase extends Phase { this.end(); } } +//#endregion + + + + +//#region 73 AddEnemyBuffModifierPhase export class AddEnemyBuffModifierPhase extends Phase { constructor(scene: BattleScene) { super(scene); @@ -5367,7 +6659,13 @@ export class AddEnemyBuffModifierPhase extends Phase { this.scene.updateModifiers(false, true).then(() => this.end()); } } +//#endregion + + + + +//#region 74 PartyStatusCurePhase /** * Cures the party of all non-volatile status conditions, shows a message * @param {BattleScene} scene The current scene @@ -5410,7 +6708,13 @@ export class PartyStatusCurePhase extends BattlePhase { this.end(); } } +//#endregion + + + + +//#region 75 PartyHealPhase export class PartyHealPhase extends BattlePhase { private resumeBgm: boolean; @@ -5447,7 +6751,13 @@ export class PartyHealPhase extends BattlePhase { }); } } +//#endregion + + + + +//#region 76 ShinySparklePhase export class ShinySparklePhase extends PokemonPhase { constructor(scene: BattleScene, battlerIndex: BattlerIndex) { super(scene, battlerIndex); @@ -5460,7 +6770,13 @@ export class ShinySparklePhase extends PokemonPhase { this.scene.time.delayedCall(1000, () => this.end()); } } +//#endregion + + + + +//#region 77 ScanIvsPhase export class ScanIvsPhase extends PokemonPhase { private shownIvs: integer; @@ -5481,6 +6797,7 @@ export class ScanIvsPhase extends PokemonPhase { this.scene.ui.showText(i18next.t("battle:ivScannerUseQuestion", { pokemonName: pokemon.name }), null, () => { this.scene.ui.setMode(Mode.CONFIRM, () => { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, "IV Scanner → " + LoggerTools.enemyPokeName(this.scene, pokemon)) this.scene.ui.setMode(Mode.MESSAGE); this.scene.ui.clearText(); new CommonBattleAnim(CommonAnim.LOCK_ON, pokemon, pokemon).play(this.scene, () => { @@ -5494,7 +6811,13 @@ export class ScanIvsPhase extends PokemonPhase { }); } } +//#endregion + + + + +//#region 78 TrainerMessageTestPhase export class TrainerMessageTestPhase extends BattlePhase { private trainerTypes: TrainerType[]; @@ -5530,9 +6853,16 @@ export class TrainerMessageTestPhase extends BattlePhase { this.end(); } } +//#endregion + + + + +//#region 79 TestMessagePhase export class TestMessagePhase extends MessagePhase { constructor(scene: BattleScene, message: string) { super(scene, message, null, true); } } +//#endregion \ No newline at end of file diff --git a/src/scene-base.ts b/src/scene-base.ts index 1d7a2518300..9c757860bf9 100644 --- a/src/scene-base.ts +++ b/src/scene-base.ts @@ -40,6 +40,12 @@ export class SceneBase extends Phaser.Scene { this.load.image(`${key}_legacy`, this.getCachedUrl(`images/${folder}/${filename}`)); } } + loadImageNoLegacy(key: string, folder: string, filename?: string) { + if (!filename) { + filename = `${key}.png`; + } + this.load.image(key, this.getCachedUrl(`images/${folder}/${filename}`)); + } loadSpritesheet(key: string, folder: string, size: integer, filename?: string) { if (!filename) { diff --git a/src/system/game-data.ts b/src/system/game-data.ts index ac54c942fc7..204825b6deb 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -40,6 +40,7 @@ import { GameDataType } from "#enums/game-data-type"; import { Moves } from "#enums/moves"; import { PlayerGender } from "#enums/player-gender"; import { Species } from "#enums/species"; +import * as LoggerTools from "../logger" export const defaultStarterSpecies: Species[] = [ Species.BULBASAUR, Species.CHARMANDER, Species.SQUIRTLE, @@ -122,6 +123,9 @@ export interface SessionSaveData { gameVersion: string; timestamp: integer; challenges: ChallengeData[]; + slot: integer; + description: string; + autoSlot: integer; } interface Unlocks { @@ -840,7 +844,7 @@ export class GameData { } as SessionSaveData; } - getSession(slotId: integer): Promise { + getSession(slotId: integer, autoSlot?: integer): Promise { return new Promise(async (resolve, reject) => { if (slotId < 0) { return resolve(null); @@ -848,14 +852,18 @@ export class GameData { const handleSessionData = async (sessionDataStr: string) => { try { const sessionData = this.parseSessionData(sessionDataStr); + sessionData.autoSlot = autoSlot; resolve(sessionData); } catch (err) { reject(err); return; } }; - - if (!bypassLogin && !localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`)) { + var autokey = "" + if (autoSlot != undefined) { + autokey = "_auto" + autoSlot + } + if (!bypassLogin && !localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}${autokey}`)) { Utils.apiFetch(`savedata/session/get?slot=${slotId}&clientSessionId=${clientSessionId}`, true) .then(response => response.text()) .then(async response => { @@ -869,7 +877,8 @@ export class GameData { await handleSessionData(response); }); } else { - const sessionData = localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}`); + const sessionData = localStorage.getItem(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}${autokey}`); + //console.log(`sessionData${slotId ? slotId : ""}_${loggedInUser.username}${autokey}`, sessionData) if (sessionData) { await handleSessionData(decrypt(sessionData, bypassLogin)); } else { @@ -879,7 +888,7 @@ export class GameData { }); } - loadSession(scene: BattleScene, slotId: integer, sessionData?: SessionSaveData): Promise { + loadSession(scene: BattleScene, slotId: integer, sessionData?: SessionSaveData, autoSlot?: integer): Promise { return new Promise(async (resolve, reject) => { try { const initSessionFromData = async (sessionData: SessionSaveData) => { @@ -979,7 +988,7 @@ export class GameData { if (sessionData) { initSessionFromData(sessionData); } else { - this.getSession(slotId) + this.getSession(slotId, autoSlot) .then(data => initSessionFromData(data)) .catch(err => { reject(err); @@ -1149,6 +1158,13 @@ export class GameData { }) as SessionSaveData; } + saveGameToAuto(scene: BattleScene) { + var autoSlot = LoggerTools.autoCheckpoints.indexOf(scene.currentBattle.waveIndex) + var dat = this.getSessionSaveData(scene) + console.log(`Stored autosave as sessionData${scene.sessionSlotId ? scene.sessionSlotId : ""}_${loggedInUser.username}_auto${autoSlot}`) + localStorage.setItem(`sessionData${scene.sessionSlotId ? scene.sessionSlotId : ""}_${loggedInUser.username}_auto${autoSlot}`, encrypt(JSON.stringify(dat), bypassLogin)); + } + saveAll(scene: BattleScene, skipVerification: boolean = false, sync: boolean = false, useCachedSession: boolean = false, useCachedSystem: boolean = false): Promise { return new Promise(resolve => { Utils.executeIf(!skipVerification, updateUserInfo).then(success => { diff --git a/src/system/settings/settings.ts b/src/system/settings/settings.ts index 9dfba300c78..05cc9c7216b 100644 --- a/src/system/settings/settings.ts +++ b/src/system/settings/settings.ts @@ -43,7 +43,8 @@ const AUTO_DISABLED: SettingOption[] = [ export enum SettingType { GENERAL, DISPLAY, - AUDIO + AUDIO, + MOD } type SettingOption = { @@ -98,7 +99,14 @@ export const SettingKeys = { SE_Volume: "SE_VOLUME", Music_Preference: "MUSIC_PREFERENCE", Show_BGM_Bar: "SHOW_BGM_BAR", - Show_Pokemon_Teams: "SHOW_POKEMON_TEAMS" + Show_Pokemon_Teams: "SHOW_POKEMON_TEAMS", + Damage_Display: "DAMAGE_DISPLAY", + LazyReloads: "FLAG_EVERY_RESET_AS_RELOAD", + FancyBiome: "FANCY_BIOMES", + ShowAutosaves: "SHOW_AUTOSAVES", + TitleScreenContinueMode: "TITLE_SCREEN_QUICKLOAD", + BiomePanels: "BIOME_PANELS", + DailyShinyLuck: "DAILY_LUCK" }; /** @@ -145,6 +153,77 @@ export const Setting: Array = [ default: 3, type: SettingType.GENERAL }, + { + key: SettingKeys.Damage_Display, + label: "Damage Display", + options: [{ + label: "Off", + value: "Off" + }, { + label: "Value", + value: "Value" + }, { + label: "Percent", + value: "Percent" + }], + default: 0, + type: SettingType.GENERAL, + }, + { + key: SettingKeys.LazyReloads, + label: "Lazy Reloads", + options: [{ + label: "Off", + value: "Off" + }, { + label: "On", + value: "On" + }], + default: 0, + type: SettingType.GENERAL, + }, + { + key: SettingKeys.FancyBiome, + label: "Fancy Title Screen", + options: [{ + label: "Off", + value: "Off" + }, { + label: "On", + value: "On" + }], + default: 0, + type: SettingType.GENERAL, + }, + { + key: SettingKeys.TitleScreenContinueMode, + label: "Quick Load", + options: [{ + label: "Off", + value: "Off" // Shows "Continue" button on the home screen + }, { + label: "Daily", + value: "Daily" // Shows the last played Daily Run, or the last run if there are no Daily Runs + }, { + label: "Dailies", + value: "Dailies" // Shows all Daily Runs, or the last run if there are no Daily Runs + }, { + label: "Latest", + value: "Latest" // Shows the last run + }, { + label: "Both", + value: "Both" // Shows the last run and the last Daily Run, or only the last played game if it is a Daily Run + }], + default: 1, + type: SettingType.GENERAL, + }, + { + key: SettingKeys.DailyShinyLuck, + label: "Daily Shiny Luck", + options: OFF_ON, + default: 0, + type: SettingType.GENERAL, + }, { key: SettingKeys.HP_Bar_Speed, label: i18next.t("settings:hpBarSpeed"), @@ -518,6 +597,32 @@ export const Setting: Array = [ type: SettingType.DISPLAY, requireReload: true }, + { + key: SettingKeys.BiomePanels, + label: "Biome Panels", + options: [{ + label: "Off", + value: "Off" + }, { + label: "On", + value: "On" + }], + default: 0, + type: SettingType.DISPLAY, + }, + { + key: SettingKeys.ShowAutosaves, + label: "Show Autosaves", + options: [{ + label: "Off", + value: "Off" + }, { + label: "On", + value: "On" + }], + default: 0, + type: SettingType.DISPLAY, + }, { key: SettingKeys.Master_Volume, label: i18next.t("settings:masterVolume"), @@ -625,6 +730,20 @@ export function setSetting(scene: BattleScene, setting: string, value: integer): case SettingKeys.Enable_Retries: scene.enableRetries = Setting[index].options[value].value === "On"; break; + case SettingKeys.Damage_Display: + scene.damageDisplay = Setting[index].options[value].value + case SettingKeys.LazyReloads: + scene.lazyReloads = Setting[index].options[value].value == "On" + case SettingKeys.FancyBiome: + scene.menuChangesBiome = Setting[index].options[value].value == "On" + case SettingKeys.ShowAutosaves: + scene.showAutosaves = Setting[index].options[value].value == "On" + case SettingKeys.BiomePanels: + scene.doBiomePanels = Setting[index].options[value].value == "On" + case SettingKeys.DailyShinyLuck: + scene.disableDailyShinies = Setting[index].options[value].value == "Off" + case SettingKeys.TitleScreenContinueMode: + scene.quickloadDisplayMode = Setting[index].options[value].value; case SettingKeys.Skip_Seen_Dialogues: scene.skipSeenDialogues = Setting[index].options[value].value === "On"; break; diff --git a/src/test/abilities/costar.test.ts b/src/test/abilities/costar.test.ts index 1b7eb3f7b90..ecd70088aa2 100644 --- a/src/test/abilities/costar.test.ts +++ b/src/test/abilities/costar.test.ts @@ -29,7 +29,7 @@ describe("Abilities - COSTAR", () => { game = new GameManager(phaserGame); vi.spyOn(Overrides, "DOUBLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); vi.spyOn(Overrides, "ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.COSTAR); - vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.NASTY_PLOT, Moves.CURSE]); + vi.spyOn(Overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.NASTY_PLOT]); vi.spyOn(Overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH, Moves.SPLASH, Moves.SPLASH, Moves.SPLASH]); }); @@ -37,15 +37,17 @@ describe("Abilities - COSTAR", () => { test( "ability copies positive stat changes", async () => { + vi.spyOn(Overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.BALL_FETCH); + await game.startBattle([Species.MAGIKARP, Species.MAGIKARP, Species.FLAMIGO]); let [leftPokemon, rightPokemon] = game.scene.getPlayerField(); - expect(leftPokemon).not.toBe(undefined); - expect(rightPokemon).not.toBe(undefined); + expect(leftPokemon).toBeDefined(); + expect(rightPokemon).toBeDefined(); game.doAttack(getMovePosition(game.scene, 0, Moves.NASTY_PLOT)); await game.phaseInterceptor.to(CommandPhase); - game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + game.doAttack(getMovePosition(game.scene, 1, Moves.SPLASH)); await game.toNextTurn(); expect(leftPokemon.summonData.battleStats[BattleStat.SPATK]).toBe(+2); @@ -71,8 +73,8 @@ describe("Abilities - COSTAR", () => { await game.startBattle([Species.MAGIKARP, Species.MAGIKARP, Species.FLAMIGO]); let [leftPokemon, rightPokemon] = game.scene.getPlayerField(); - expect(leftPokemon).not.toBe(undefined); - expect(rightPokemon).not.toBe(undefined); + expect(leftPokemon).toBeDefined(); + expect(rightPokemon).toBeDefined(); expect(leftPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2); expect(leftPokemon.summonData.battleStats[BattleStat.ATK]).toBe(-2); diff --git a/src/test/game-mode.test.ts b/src/test/game-mode.test.ts new file mode 100644 index 00000000000..04376c20361 --- /dev/null +++ b/src/test/game-mode.test.ts @@ -0,0 +1,52 @@ +import { GameMode, GameModes, getGameMode } from "#app/game-mode.js"; +import { + afterEach, + beforeAll, + beforeEach, + describe, + expect, + it, + vi, +} from "vitest"; +import GameManager from "./utils/gameManager"; +import * as Utils from "../utils"; +describe("game-mode", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + afterEach(() => { + game.phaseInterceptor.restoreOg(); + vi.resetAllMocks(); + }); + beforeEach(() => { + game = new GameManager(phaserGame); + }); + describe("classic", () => { + let classicGameMode: GameMode; + beforeEach(() => { + classicGameMode = getGameMode(GameModes.CLASSIC); + }); + it("does NOT spawn trainers within 3 waves of fixed battle", () => { + const { arena } = game.scene; + /** set wave 16 to be a fixed trainer fight meaning wave 13-19 don't allow trainer spawns */ + vi.spyOn(classicGameMode, "isFixedBattle").mockImplementation( + (n: number) => (n === 16 ? true : false) + ); + vi.spyOn(arena, "getTrainerChance").mockReturnValue(1); + vi.spyOn(Utils, "randSeedInt").mockReturnValue(0); + expect(classicGameMode.isWaveTrainer(11, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(12, arena)).toBeTruthy(); + expect(classicGameMode.isWaveTrainer(13, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(14, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(15, arena)).toBeFalsy(); + // Wave 16 is a fixed trainer battle + expect(classicGameMode.isWaveTrainer(17, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(18, arena)).toBeFalsy(); + expect(classicGameMode.isWaveTrainer(19, arena)).toBeFalsy(); + }); + }); +}); diff --git a/src/test/lokalisation/battle-stat.test.ts b/src/test/localization/battle-stat.test.ts similarity index 100% rename from src/test/lokalisation/battle-stat.test.ts rename to src/test/localization/battle-stat.test.ts diff --git a/src/test/lokalisation/french.test.ts b/src/test/localization/french.test.ts similarity index 100% rename from src/test/lokalisation/french.test.ts rename to src/test/localization/french.test.ts diff --git a/src/test/lokalisation/status-effect.test.ts b/src/test/localization/status-effect.test.ts similarity index 100% rename from src/test/lokalisation/status-effect.test.ts rename to src/test/localization/status-effect.test.ts diff --git a/src/test/localization/terrain.test.ts b/src/test/localization/terrain.test.ts new file mode 100644 index 00000000000..89884290e00 --- /dev/null +++ b/src/test/localization/terrain.test.ts @@ -0,0 +1,192 @@ +import { beforeAll, describe, beforeEach, afterEach, expect, it, vi } from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; +import { Species } from "#enums/species"; +import { TerrainType, getTerrainName } from "#app/data/terrain"; +import { getTerrainStartMessage, getTerrainClearMessage, getTerrainBlockMessage } from "#app/data/weather"; +import i18next from "i18next"; +import { mockI18next } from "../utils/testUtils"; + +describe("terrain", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + i18next.init(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + }); + + describe("NONE", () => { + const terrainType = TerrainType.NONE; + + it("should return the obtain text", () => { + mockI18next(); + + const text = getTerrainName(terrainType); + expect(text).toBe(""); + }); + + it("should return the start text", () => { + mockI18next(); + + const text = getTerrainStartMessage(terrainType); + expect(text).toBe(undefined); + }); + + it("should return the clear text", () => { + mockI18next(); + const text = getTerrainClearMessage(terrainType); + expect(text).toBe(undefined); + }); + + it("should return the block text", async () => { + await game.startBattle([Species.MAGIKARP]); + const pokemon = game.scene.getPlayerPokemon(); + mockI18next(); + const text = getTerrainBlockMessage(pokemon, terrainType); + expect(text).toBe("terrain:defaultBlockMessage"); + }); + }); + + describe("MISTY", () => { + const terrainType = TerrainType.MISTY; + + it("should return the obtain text", () => { + mockI18next(); + + const text = getTerrainName(terrainType); + expect(text).toBe("terrain:misty"); + }); + + it("should return the start text", () => { + mockI18next(); + + const text = getTerrainStartMessage(terrainType); + expect(text).toBe("terrain:mistyStartMessage"); + }); + + it("should return the clear text", () => { + mockI18next(); + const text = getTerrainClearMessage(terrainType); + expect(text).toBe("terrain:mistyClearMessage"); + }); + + it("should return the block text", async () => { + await game.startBattle([Species.MAGIKARP]); + const pokemon = game.scene.getPlayerPokemon(); + mockI18next(); + const text = getTerrainBlockMessage(pokemon, terrainType); + expect(text).toBe("terrain:mistyBlockMessage"); + }); + }); + + describe("ELECTRIC", () => { + const terrainType = TerrainType.ELECTRIC; + + it("should return the obtain text", () => { + mockI18next(); + + const text = getTerrainName(terrainType); + expect(text).toBe("terrain:electric"); + }); + + it("should return the start text", () => { + mockI18next(); + + const text = getTerrainStartMessage(terrainType); + expect(text).toBe("terrain:electricStartMessage"); + }); + + it("should return the clear text", () => { + mockI18next(); + const text = getTerrainClearMessage(terrainType); + expect(text).toBe("terrain:electricClearMessage"); + }); + + it("should return the block text", async () => { + await game.startBattle([Species.MAGIKARP]); + const pokemon = game.scene.getPlayerPokemon(); + mockI18next(); + const text = getTerrainBlockMessage(pokemon, terrainType); + expect(text).toBe("terrain:defaultBlockMessage"); + }); + }); + + describe("GRASSY", () => { + const terrainType = TerrainType.GRASSY; + + it("should return the obtain text", () => { + mockI18next(); + + const text = getTerrainName(terrainType); + expect(text).toBe("terrain:grassy"); + }); + + it("should return the start text", () => { + mockI18next(); + + const text = getTerrainStartMessage(terrainType); + expect(text).toBe("terrain:grassyStartMessage"); + }); + + it("should return the clear text", () => { + mockI18next(); + const text = getTerrainClearMessage(terrainType); + expect(text).toBe("terrain:grassyClearMessage"); + }); + + it("should return the block text", async () => { + await game.startBattle([Species.MAGIKARP]); + const pokemon = game.scene.getPlayerPokemon(); + mockI18next(); + const text = getTerrainBlockMessage(pokemon, terrainType); + expect(text).toBe("terrain:defaultBlockMessage"); + }); + }); + + describe("PSYCHIC", () => { + const terrainType = TerrainType.PSYCHIC; + + it("should return the obtain text", () => { + mockI18next(); + + const text = getTerrainName(terrainType); + expect(text).toBe("terrain:psychic"); + }); + + it("should return the start text", () => { + mockI18next(); + + const text = getTerrainStartMessage(terrainType); + expect(text).toBe("terrain:psychicStartMessage"); + }); + + it("should return the clear text", () => { + mockI18next(); + const text = getTerrainClearMessage(terrainType); + expect(text).toBe("terrain:psychicClearMessage"); + }); + + it("should return the block text", async () => { + await game.startBattle([Species.MAGIKARP]); + const pokemon = game.scene.getPlayerPokemon(); + mockI18next(); + const text = getTerrainBlockMessage(pokemon, terrainType); + expect(text).toBe("terrain:defaultBlockMessage"); + }); + }); + + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + vi.resetAllMocks(); + }); +}); diff --git a/src/test/moves/ceaseless_edge.test.ts b/src/test/moves/ceaseless_edge.test.ts new file mode 100644 index 00000000000..6443e34d8d2 --- /dev/null +++ b/src/test/moves/ceaseless_edge.test.ts @@ -0,0 +1,137 @@ +import {afterEach, beforeAll, beforeEach, describe, expect, test, vi} from "vitest"; +import Phaser from "phaser"; +import GameManager from "#app/test/utils/gameManager"; +import * as overrides from "#app/overrides"; +import { + MoveEffectPhase, + TurnEndPhase +} from "#app/phases"; +import {getMovePosition} from "#app/test/utils/gameManagerUtils"; +import { Moves } from "#enums/moves"; +import { Species } from "#enums/species"; +import { ArenaTagType } from "#app/enums/arena-tag-type"; +import { allMoves } from "#app/data/move"; +import { ArenaTagSide, ArenaTrapTag } from "#app/data/arena-tag"; +import { Abilities } from "#app/enums/abilities"; + +const TIMEOUT = 20 * 1000; + +describe("Moves - Ceaseless Edge", () => { + let phaserGame: Phaser.Game; + let game: GameManager; + + beforeAll(() => { + phaserGame = new Phaser.Game({ + type: Phaser.HEADLESS, + }); + }); + + afterEach(() => { + game.phaseInterceptor.restoreOg(); + }); + + beforeEach(() => { + game = new GameManager(phaserGame); + vi.spyOn(overrides, "SINGLE_BATTLE_OVERRIDE", "get").mockReturnValue(true); + vi.spyOn(overrides, "OPP_SPECIES_OVERRIDE", "get").mockReturnValue(Species.RATTATA); + vi.spyOn(overrides, "OPP_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.RUN_AWAY); + vi.spyOn(overrides, "OPP_PASSIVE_ABILITY_OVERRIDE", "get").mockReturnValue(Abilities.RUN_AWAY); + vi.spyOn(overrides, "STARTING_LEVEL_OVERRIDE", "get").mockReturnValue(100); + vi.spyOn(overrides, "OPP_LEVEL_OVERRIDE", "get").mockReturnValue(100); + vi.spyOn(overrides, "MOVESET_OVERRIDE", "get").mockReturnValue([ Moves.CEASELESS_EDGE, Moves.SPLASH, Moves.ROAR ]); + vi.spyOn(overrides, "OPP_MOVESET_OVERRIDE", "get").mockReturnValue([Moves.SPLASH,Moves.SPLASH,Moves.SPLASH,Moves.SPLASH]); + vi.spyOn(allMoves[Moves.CEASELESS_EDGE], "accuracy", "get").mockReturnValue(100); + + }); + + test( + "move should hit and apply spikes", + async () => { + await game.startBattle([ Species.ILLUMISE ]); + + const leadPokemon = game.scene.getPlayerPokemon(); + expect(leadPokemon).toBeDefined(); + + const enemyPokemon = game.scene.getEnemyPokemon(); + expect(enemyPokemon).toBeDefined(); + + const enemyStartingHp = enemyPokemon.hp; + + game.doAttack(getMovePosition(game.scene, 0, Moves.CEASELESS_EDGE)); + + await game.phaseInterceptor.to(MoveEffectPhase, false); + // Spikes should not have any layers before move effect is applied + const tagBefore = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagBefore instanceof ArenaTrapTag).toBeFalsy(); + + await game.phaseInterceptor.to(TurnEndPhase); + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagAfter instanceof ArenaTrapTag).toBeTruthy(); + expect(tagAfter.layers).toBe(1); + expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); + }, TIMEOUT + ); + + test( + "move should hit twice with multi lens and apply two layers of spikes", + async () => { + vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{name: "MULTI_LENS"}]); + await game.startBattle([ Species.ILLUMISE ]); + + const leadPokemon = game.scene.getPlayerPokemon(); + expect(leadPokemon).toBeDefined(); + + const enemyPokemon = game.scene.getEnemyPokemon(); + expect(enemyPokemon).toBeDefined(); + + const enemyStartingHp = enemyPokemon.hp; + + game.doAttack(getMovePosition(game.scene, 0, Moves.CEASELESS_EDGE)); + + await game.phaseInterceptor.to(MoveEffectPhase, false); + // Spikes should not have any layers before move effect is applied + const tagBefore = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagBefore instanceof ArenaTrapTag).toBeFalsy(); + + await game.phaseInterceptor.to(TurnEndPhase); + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagAfter instanceof ArenaTrapTag).toBeTruthy(); + expect(tagAfter.layers).toBe(2); + expect(enemyPokemon.hp).toBeLessThan(enemyStartingHp); + }, TIMEOUT + ); + + test( + "trainer - move should hit twice, apply two layers of spikes, force switch opponent - opponent takes damage", + async () => { + vi.spyOn(overrides, "STARTING_HELD_ITEMS_OVERRIDE", "get").mockReturnValue([{name: "MULTI_LENS"}]); + vi.spyOn(overrides, "STARTING_WAVE_OVERRIDE", "get").mockReturnValue(5); + + await game.startBattle([ Species.ILLUMISE ]); + + const leadPokemon = game.scene.getPlayerPokemon(); + expect(leadPokemon).toBeDefined(); + + const enemyPokemon = game.scene.getEnemyPokemon(); + expect(enemyPokemon).toBeDefined(); + + game.doAttack(getMovePosition(game.scene, 0, Moves.CEASELESS_EDGE)); + await game.phaseInterceptor.to(MoveEffectPhase, false); + // Spikes should not have any layers before move effect is applied + const tagBefore = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagBefore instanceof ArenaTrapTag).toBeFalsy(); + + await game.phaseInterceptor.to(TurnEndPhase, false); + const tagAfter = game.scene.arena.getTagOnSide(ArenaTagType.SPIKES, ArenaTagSide.ENEMY) as ArenaTrapTag; + expect(tagAfter instanceof ArenaTrapTag).toBeTruthy(); + expect(tagAfter.layers).toBe(2); + + const hpBeforeSpikes = game.scene.currentBattle.enemyParty[1].hp; + // Check HP of pokemon that WILL BE switched in (index 1) + game.forceOpponentToSwitch(); + game.doAttack(getMovePosition(game.scene, 0, Moves.SPLASH)); + await game.phaseInterceptor.to(TurnEndPhase, false); + expect(game.scene.currentBattle.enemyParty[0].hp).toBeLessThan(hpBeforeSpikes); + }, TIMEOUT + ); +}); diff --git a/src/ui/abstact-option-select-ui-handler.ts b/src/ui/abstact-option-select-ui-handler.ts index 2069f034e89..eda469adaf3 100644 --- a/src/ui/abstact-option-select-ui-handler.ts +++ b/src/ui/abstact-option-select-ui-handler.ts @@ -6,6 +6,8 @@ import { addWindow } from "./ui-theme"; import * as Utils from "../utils"; import { argbFromRgba } from "@material/material-color-utilities"; import {Button} from "#enums/buttons"; +import { Biome } from "#app/enums/biome.js"; +import { getBiomeKey } from "#app/field/arena.js"; export interface OptionSelectConfig { xOffset?: number; diff --git a/src/ui/arena-flyout.ts b/src/ui/arena-flyout.ts index 9c0a2e7c8ce..41ae305548c 100644 --- a/src/ui/arena-flyout.ts +++ b/src/ui/arena-flyout.ts @@ -9,6 +9,10 @@ import { BattleSceneEventType, TurnEndEvent } from "../events/battle-scene"; import { ArenaTagType } from "#enums/arena-tag-type"; import TimeOfDayWidget from "./time-of-day-widget"; import * as Utils from "../utils"; +import { getNatureDecrease, getNatureIncrease, getNatureName } from "#app/data/nature.js"; +import * as LoggerTools from "../logger" +import { BattleEndPhase } from "#app/phases.js"; +import { Gender } from "#app/data/gender.js"; /** Enum used to differentiate {@linkcode Arena} effects */ enum ArenaEffectType { @@ -191,14 +195,45 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { this.flyoutTextPlayer.text = ""; this.flyoutTextField.text = ""; this.flyoutTextEnemy.text = ""; + this.flyoutTextPlayer.setPosition(6, 13) + this.flyoutTextPlayer.setFontSize(48); + } + + public printIVs() { + this.clearText() + var poke = (this.scene as BattleScene).getEnemyField() + this.flyoutTextPlayer.text = "" + this.flyoutTextField.text = "" + this.flyoutTextEnemy.text = "" + this.flyoutTextHeaderField.text = "Stats" + this.flyoutTextHeaderPlayer.text = "" + this.flyoutTextHeaderEnemy.text = "" + 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" + 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] + this.flyoutTextPlayer.text += ", Atk: " + poke[i].ivs[1] + this.flyoutTextPlayer.text += ", Def: " + poke[i].ivs[2] + this.flyoutTextPlayer.text += ", Sp.A: " + poke[i].ivs[3] + this.flyoutTextPlayer.text += ", Sp.D: " + poke[i].ivs[4] + this.flyoutTextPlayer.text += ", Speed: " + poke[i].ivs[5] + "\n\n" + } } /** Parses through all set Arena Effects and puts them into the proper {@linkcode Phaser.GameObjects.Text} object */ - private updateFieldText() { + public updateFieldText() { this.clearText(); this.fieldEffectInfo.sort((infoA, infoB) => infoA.duration - infoB.duration); + this.flyoutTextHeaderPlayer.text = "Player" + this.flyoutTextHeaderField.text = "Neutral" + this.flyoutTextHeaderEnemy.text = "Enemy" + this.flyoutTextHeader.text = "Active Battle Effects" + for (let i = 0; i < this.fieldEffectInfo.length; i++) { const fieldEffectInfo = this.fieldEffectInfo[i]; @@ -232,6 +267,37 @@ export default class ArenaFlyout extends Phaser.GameObjects.Container { textObject.text += "\n"; } + this.flyoutTextPlayer.text = "" + this.flyoutTextField.text = "" + this.flyoutTextEnemy.text = "" + this.flyoutTextHeaderField.text = "" + this.flyoutTextHeaderPlayer.text = "" + this.flyoutTextHeaderEnemy.text = "" + this.flyoutTextHeader.text = "Game Logs" + this.flyoutTextPlayer.setPosition(6, 4) + this.flyoutTextPlayer.setFontSize(30); + var instructions = [] + var drpd = LoggerTools.getDRPD(this.scene as BattleScene); + var doWaveInstructions = true; + for (var i = 0; i < drpd.waves.length && drpd.waves[i] != undefined && doWaveInstructions; i++) { + if (drpd.waves[i].id > (this.scene as BattleScene).currentBattle.waveIndex) { + doWaveInstructions = false; + } else { + instructions.push("") + instructions.push("Wave " + drpd.waves[i].id) + for (var j = 0; j < drpd.waves[i].actions.length; j++) { + instructions.push("- " + drpd.waves[i].actions[j]) + } + if (drpd.waves[i].shop != "") + instructions.push("Reward: " + drpd.waves[i].shop) + } + } + for (var i = instructions.length - 10; i < instructions.length; i++) { + if (i >= 0) { + this.flyoutTextPlayer.text += instructions[i] + } + this.flyoutTextPlayer.text += "\n" + } } /** diff --git a/src/ui/battle-flyout.ts b/src/ui/battle-flyout.ts index 5b34a6b5411..a17ce3aefbe 100644 --- a/src/ui/battle-flyout.ts +++ b/src/ui/battle-flyout.ts @@ -51,7 +51,7 @@ export default class BattleFlyout extends Phaser.GameObjects.Container { private flyoutContainer: Phaser.GameObjects.Container; /** The array of {@linkcode Phaser.GameObjects.Text} objects which are drawn on the flyout */ - private flyoutText: Phaser.GameObjects.Text[] = new Array(4); + public flyoutText: Phaser.GameObjects.Text[] = new Array(4); /** The array of {@linkcode MoveInfo} used to track moves for the {@linkcode Pokemon} linked to the flyout */ private moveInfo: MoveInfo[] = new Array(); diff --git a/src/ui/battle-info.ts b/src/ui/battle-info.ts index 50852043285..59b8e8c86ae 100644 --- a/src/ui/battle-info.ts +++ b/src/ui/battle-info.ts @@ -11,6 +11,7 @@ import { BattleStat } from "#app/data/battle-stat"; import BattleFlyout from "./battle-flyout"; import { WindowVariant, addWindow } from "./ui-theme"; import i18next from "i18next"; +import { calcDamage } from "./fight-ui-handler"; const battleStatOrder = [ BattleStat.ATK, BattleStat.DEF, BattleStat.SPATK, BattleStat.SPDEF, BattleStat.ACC, BattleStat.EVA, BattleStat.SPD ]; @@ -472,6 +473,11 @@ export default class BattleInfo extends Phaser.GameObjects.Container { ease: "Sine.easeInOut", alpha: visible ? 1 : 0 }); + if (visible) { + (this.scene as BattleScene).arenaFlyout.printIVs() + } else { + (this.scene as BattleScene).arenaFlyout.updateFieldText() + } } updateBossSegments(pokemon: EnemyPokemon): void { @@ -921,7 +927,8 @@ export default class BattleInfo extends Phaser.GameObjects.Container { if (visible) { this.effectivenessContainer?.setVisible(false); } else { - this.updateEffectiveness(this.currentEffectiveness); + //this.updateEffectiveness(this.currentEffectiveness); + this.effectivenessContainer?.setVisible(true); } if (!this.override) this.switchIconVisibility(visible); // this.teamIconOver[ballindex].setAlpha(0.4, 0.4, 0.7, 0.7) diff --git a/src/ui/egg-gacha-ui-handler.ts b/src/ui/egg-gacha-ui-handler.ts index 1a9320ac9e9..b5335ed8f2e 100644 --- a/src/ui/egg-gacha-ui-handler.ts +++ b/src/ui/egg-gacha-ui-handler.ts @@ -103,7 +103,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { let pokemonIconX = -20; let pokemonIconY = 6; - if (["de", "es", "fr", "pt-BR"].includes(currentLanguage)) { + if (["de", "es", "fr", "ko", "pt-BR"].includes(currentLanguage)) { gachaTextStyle = TextStyle.SMALLER_WINDOW_ALT; gachaX = 2; gachaY = 2; @@ -155,7 +155,7 @@ export default class EggGachaUiHandler extends MessageUiHandler { gachaUpLabel.setOrigin(0.5, 0); break; case GachaType.SHINY: - if (["de", "fr"].includes(currentLanguage)) { + if (["de", "fr", "ko"].includes(currentLanguage)) { gachaUpLabel.setAlign("center"); gachaUpLabel.setY(0); } diff --git a/src/ui/fight-ui-handler.ts b/src/ui/fight-ui-handler.ts index ed520512443..bc7cc0a15d0 100644 --- a/src/ui/fight-ui-handler.ts +++ b/src/ui/fight-ui-handler.ts @@ -5,11 +5,27 @@ import { Command } from "./command-ui-handler"; 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 { CommandPhase, MoveEffectPhase } from "../phases"; +import Move, * as MoveData from "../data/move"; import i18next from "i18next"; import {Button} from "#enums/buttons"; -import Pokemon, { PokemonMove } from "#app/field/pokemon.js"; +import Pokemon, { DamageResult, EnemyPokemon, HitResult, 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"; +import { AddSecondStrikeAbAttr, AllyMoveCategoryPowerBoostAbAttr, AlwaysHitAbAttr, applyAbAttrs, applyBattleStatMultiplierAbAttrs, applyPreAttackAbAttrs, applyPreDefendAbAttrs, applyPreDefendAbAttrsNoApply, BattleStatMultiplierAbAttr, BlockCritAbAttr, BonusCritAbAttr, BypassBurnDamageReductionAbAttr, ConditionalCritAbAttr, DamageBoostAbAttr, FieldMoveTypePowerBoostAbAttr, FieldPriorityMoveImmunityAbAttr, IgnoreOpponentEvasionAbAttr, IgnoreOpponentStatChangesAbAttr, MoveImmunityAbAttr, MoveTypeChangeAttr, MultCritAbAttr, PreDefendFullHpEndureAbAttr, ReceivedMoveDamageMultiplierAbAttr, StabBoostAbAttr, TypeImmunityAbAttr, UserFieldMoveTypePowerBoostAbAttr, VariableMovePowerAbAttr, WonderSkinAbAttr } from "#app/data/ability.js"; +import { ArenaTagType } from "#app/enums/arena-tag-type.js"; +import { ArenaTagSide, WeakenMoveScreenTag, WeakenMoveTypeTag } from "#app/data/arena-tag.js"; +import { BattlerTagLapseType, HelpingHandTag, SemiInvulnerableTag, TypeBoostTag } from "#app/data/battler-tags.js"; +import { TerrainType } from "#app/data/terrain.js"; +import { AttackTypeBoosterModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, TempBattleStatBoosterModifier } from "#app/modifier/modifier.js"; +import { BattlerTagType } from "#app/enums/battler-tag-type.js"; +import { TempBattleStat } from "#app/data/temp-battle-stat.js"; +import { StatusEffect } from "#app/data/status-effect.js"; +import { BattleStat } from "#app/data/battle-stat.js"; +import { PokemonMultiHitModifierType } from "#app/modifier/modifier-type.js"; export default class FightUiHandler extends UiHandler { private movesContainer: Phaser.GameObjects.Container; @@ -153,6 +169,535 @@ export default class FightUiHandler extends UiHandler { return !this.fieldIndex ? this.cursor : this.cursor2; } + simulateAttack(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move) { + let result: HitResult; + const damage1 = new Utils.NumberHolder(0); + const damage2 = new Utils.NumberHolder(0); + const defendingSidePlayField = target.isPlayer() ? this.scene.getPlayerField() : this.scene.getEnemyField(); + + const variableCategory = new Utils.IntegerHolder(move.category); + MoveData.applyMoveAttrs(MoveData.VariableMoveCategoryAttr, user, target, move, variableCategory); + const moveCategory = variableCategory.value as MoveData.MoveCategory; + + const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); + MoveData.applyMoveAttrs(MoveData.VariableMoveTypeAttr, user, target, move); + applyPreAttackAbAttrs(MoveTypeChangeAttr, user, target, move, typeChangeMovePowerMultiplier); + const types = target.getTypes(true, true); + + const cancelled = new Utils.BooleanHolder(false); + const typeless = move.hasAttr(MoveData.TypelessAttr); + const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveData.MoveCategory.STATUS || move.getAttrs(MoveData.StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType))) + ? target.getAttackTypeEffectiveness(move.type, user, false, false) + : 1); + MoveData.applyMoveAttrs(MoveData.VariableMoveTypeMultiplierAttr, user, target, move, typeMultiplier); + if (typeless) { + typeMultiplier.value = 1; + } + if (types.find(t => move.isTypeImmune(user, target, t))) { + typeMultiplier.value = 0; + } + + // Apply arena tags for conditional protection + if (!move.checkFlag(MoveData.MoveFlags.IGNORE_PROTECT, user, target) && !move.isAllyTarget()) { + const defendingSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + this.scene.arena.applyTagsForSide(ArenaTagType.QUICK_GUARD, defendingSide, cancelled, this, move.priority); + this.scene.arena.applyTagsForSide(ArenaTagType.WIDE_GUARD, defendingSide, cancelled, this, move.moveTarget); + this.scene.arena.applyTagsForSide(ArenaTagType.MAT_BLOCK, defendingSide, cancelled, this, move.category); + this.scene.arena.applyTagsForSide(ArenaTagType.CRAFTY_SHIELD, defendingSide, cancelled, this, move.category, move.moveTarget); + } + + switch (moveCategory) { + case MoveData.MoveCategory.PHYSICAL: + case MoveData.MoveCategory.SPECIAL: + const isPhysical = moveCategory === MoveData.MoveCategory.PHYSICAL; + const power = new Utils.NumberHolder(move.power); + const sourceTeraType = user.getTeraType(); + if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MoveData.MultiHitAttr) && !this.scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === user.id)) { + power.value = 60; + } + applyPreAttackAbAttrs(VariableMovePowerAbAttr, user, target, move, power); + + if (user.getAlly()?.hasAbilityWithAttr(AllyMoveCategoryPowerBoostAbAttr)) { + applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, user, target, move, power); + } + + const fieldAuras = new Set( + this.scene.getField(true) + .map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr) as FieldMoveTypePowerBoostAbAttr[]) + .flat(), + ); + for (const aura of fieldAuras) { + // The only relevant values are `move` and the `power` holder + aura.applyPreAttack(null, null, null, move, [power]); + } + + const alliedField: Pokemon[] = user instanceof PlayerPokemon ? this.scene.getPlayerField() : this.scene.getEnemyField(); + alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, user, move, power)); + + power.value *= typeChangeMovePowerMultiplier.value; + + if (!typeless) { + if (target.hasAbilityWithAttr(TypeImmunityAbAttr)) { + // + } + applyPreDefendAbAttrsNoApply(TypeImmunityAbAttr, user, target, move, cancelled, typeMultiplier); + MoveData.applyMoveAttrs(MoveData.NeutralDamageAgainstFlyingTypeMultiplierAttr, user, target, move, typeMultiplier); + } + if (!cancelled.value) { + applyPreDefendAbAttrs(MoveImmunityAbAttr, user, target, move, cancelled, typeMultiplier); + defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, user, move, cancelled, typeMultiplier)); + } + + if (cancelled.value) { + //user.stopMultiHit(target); + result = HitResult.NO_EFFECT; + } else { + const typeBoost = user.findTag(t => t instanceof TypeBoostTag && t.boostedType === move.type) as TypeBoostTag; + if (typeBoost) { + power.value *= typeBoost.boostValue; + if (typeBoost.oneUse) { + //user.removeTag(typeBoost.tagType); + } + } + const arenaAttackTypeMultiplier = new Utils.NumberHolder(this.scene.arena.getAttackTypeMultiplier(move.type, user.isGrounded())); + MoveData.applyMoveAttrs(MoveData.IgnoreWeatherTypeDebuffAttr, user, target, move, arenaAttackTypeMultiplier); + if (this.scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() && move.type === Type.GROUND && move.moveTarget === MoveData.MoveTarget.ALL_NEAR_OTHERS) { + power.value /= 2; + } + + MoveData.applyMoveAttrs(MoveData.VariablePowerAttr, user, target, move, power); + + this.scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, new Utils.IntegerHolder(0), power); + if (!typeless) { + this.scene.arena.applyTags(WeakenMoveTypeTag, move.type, power); + this.scene.applyModifiers(AttackTypeBoosterModifier, user.isPlayer(), user, move.type, power); + } + if (user.getTag(HelpingHandTag)) { + power.value *= 1.5; + } + let isCritical: boolean = true; + const critOnly = new Utils.BooleanHolder(false); + const critAlways = user.getTag(BattlerTagType.ALWAYS_CRIT); + MoveData.applyMoveAttrs(MoveData.CritOnlyAttr, user, target, move, critOnly); + applyAbAttrs(ConditionalCritAbAttr, user, null, critOnly, target, move); + if (isCritical) { + const blockCrit = new Utils.BooleanHolder(false); + applyAbAttrs(BlockCritAbAttr, target, null, blockCrit); + if (blockCrit.value) { + isCritical = false; + } + } + const sourceAtk = new Utils.IntegerHolder(user.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, target, null, false)); + const targetDef = new Utils.IntegerHolder(target.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, user, move, false)); + const sourceAtkCrit = new Utils.IntegerHolder(user.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, target, null, isCritical)); + const targetDefCrit = new Utils.IntegerHolder(target.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, user, move, isCritical)); + const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1); + applyAbAttrs(MultCritAbAttr, user, null, criticalMultiplier); + const screenMultiplier = new Utils.NumberHolder(1); + if (!isCritical) { + this.scene.arena.applyTagsForSide(WeakenMoveScreenTag, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, move.category, this.scene.currentBattle.double, screenMultiplier); + } + const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0; + const sourceTypes = user.getTypes(); + const matchesSourceType = sourceTypes[0] === move.type || (sourceTypes.length > 1 && sourceTypes[1] === move.type); + const stabMultiplier = new Utils.NumberHolder(1); + if (sourceTeraType === Type.UNKNOWN && matchesSourceType) { + stabMultiplier.value += 0.5; + } else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type) { + stabMultiplier.value += 0.5; + } + + applyAbAttrs(StabBoostAbAttr, user, null, stabMultiplier); + + if (sourceTeraType !== Type.UNKNOWN && matchesSourceType) { + stabMultiplier.value = Math.min(stabMultiplier.value + 0.5, 2.25); + } + + MoveData.applyMoveAttrs(MoveData.VariableAtkAttr, user, target, move, sourceAtk); + MoveData.applyMoveAttrs(MoveData.VariableDefAttr, user, target, move, targetDef); + MoveData.applyMoveAttrs(MoveData.VariableAtkAttr, user, target, move, sourceAtkCrit); + MoveData.applyMoveAttrs(MoveData.VariableDefAttr, user, target, move, targetDefCrit); + + const effectPhase = this.scene.getCurrentPhase(); + let numTargets = 1; + if (effectPhase instanceof MoveEffectPhase) { + numTargets = effectPhase.getTargets().length; + } + const twoStrikeMultiplier = new Utils.NumberHolder(1); + applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, target, move, numTargets, new Utils.IntegerHolder(0), twoStrikeMultiplier); + + if (!isTypeImmune) { + damage1.value = Math.ceil(((((2 * user.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * twoStrikeMultiplier.value * 0.85); // low roll + damage2.value = Math.ceil(((((2 * user.level / 5 + 2) * power.value * sourceAtkCrit.value / targetDefCrit.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * twoStrikeMultiplier.value * criticalMultiplier.value); // high roll crit + if (isPhysical && user.status && user.status.effect === StatusEffect.BURN) { + if (!move.hasAttr(MoveData.BypassBurnDamageReductionAttr)) { + const burnDamageReductionCancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BypassBurnDamageReductionAbAttr, user, burnDamageReductionCancelled); + if (!burnDamageReductionCancelled.value) { + damage1.value = Math.floor(damage1.value / 2); + damage2.value = Math.floor(damage2.value / 2); + } + } + } + + applyPreAttackAbAttrs(DamageBoostAbAttr, user, target, move, damage1); + applyPreAttackAbAttrs(DamageBoostAbAttr, user, target, move, damage2); + + /** + * For each {@link HitsTagAttr} the move has, doubles the damage of the move if: + * The target has a {@link BattlerTagType} that this move interacts with + * AND + * The move doubles damage when used against that tag + */ + move.getAttrs(MoveData.HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { + if (target.getTag(hta.tagType)) { + damage1.value *= 2; + damage2.value *= 2; + } + }); + } + + if (this.scene.arena.terrain?.terrainType === TerrainType.MISTY && target.isGrounded() && move.type === Type.DRAGON) { + damage1.value = Math.floor(damage1.value / 2); + damage2.value = Math.floor(damage2.value / 2); + } + + const fixedDamage = new Utils.IntegerHolder(0); + MoveData.applyMoveAttrs(MoveData.FixedDamageAttr, user, target, move, fixedDamage); + if (!isTypeImmune && fixedDamage.value) { + damage1.value = fixedDamage.value; + damage2.value = fixedDamage.value; + isCritical = false; + result = HitResult.EFFECTIVE; + } + + if (!result) { + if (!typeMultiplier.value) { + result = move.id === Moves.SHEER_COLD ? HitResult.IMMUNE : HitResult.NO_EFFECT; + } else { + const oneHitKo = new Utils.BooleanHolder(false); + MoveData.applyMoveAttrs(MoveData.OneHitKOAttr, user, target, move, oneHitKo); + if (oneHitKo.value) { + result = HitResult.ONE_HIT_KO; + isCritical = false; + damage1.value = target.hp; + damage2.value = target.hp; + } else if (typeMultiplier.value >= 2) { + result = HitResult.SUPER_EFFECTIVE; + } else if (typeMultiplier.value >= 1) { + result = HitResult.EFFECTIVE; + } else { + result = HitResult.NOT_VERY_EFFECTIVE; + } + } + } + + if (!fixedDamage.value) { + if (!user.isPlayer()) { + this.scene.applyModifiers(EnemyDamageBoosterModifier, false, damage1); + this.scene.applyModifiers(EnemyDamageBoosterModifier, false, damage2); + } + if (!target.isPlayer()) { + this.scene.applyModifiers(EnemyDamageReducerModifier, false, damage1); + this.scene.applyModifiers(EnemyDamageReducerModifier, false, damage2); + } + } + + MoveData.applyMoveAttrs(MoveData.ModifiedDamageAttr, user, target, move, damage1); + MoveData.applyMoveAttrs(MoveData.ModifiedDamageAttr, user, target, move, damage2); + applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage1); + applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage2); + + //console.log("damage (min)", damage1.value, move.name, power.value, sourceAtk, targetDef); + //console.log("damage (max)", damage2.value, move.name, power.value, sourceAtkCrit, targetDefCrit); + + // In case of fatal damage, this tag would have gotten cleared before we could lapse it. + const destinyTag = target.getTag(BattlerTagType.DESTINY_BOND); + + const oneHitKo = result === HitResult.ONE_HIT_KO; + if (damage1.value) { + if (target.getHpRatio() === 1) { + applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, target, user, move, cancelled, damage1); + } + } + if (damage2.value) { + if (target.getHpRatio() === 1) { + applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, target, user, move, cancelled, damage2); + } + } + } + break; + case MoveData.MoveCategory.STATUS: + if (!typeless) { + applyPreDefendAbAttrsNoApply(TypeImmunityAbAttr, target, user, move, cancelled, typeMultiplier); + } + if (!cancelled.value) { + applyPreDefendAbAttrs(MoveImmunityAbAttr, target, user, move, cancelled, typeMultiplier); + defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, user, move, cancelled, typeMultiplier)); + } + if (!typeMultiplier.value) { + return -1 + } + result = cancelled.value || !typeMultiplier.value ? HitResult.NO_EFFECT : HitResult.STATUS; + break; + } + return [damage1.value, damage2.value] + } + + calculateAccuracy(user: Pokemon, target: Pokemon, move: PokemonMove) { + if (this.scene.currentBattle.double && false) { + switch (move.getMove().moveTarget) { + case MoveData.MoveTarget.USER: // Targets yourself + return -1; // Moves targeting yourself always hit + case MoveData.MoveTarget.OTHER: // Targets one Pokemon + return move.getMove().accuracy + case MoveData.MoveTarget.ALL_OTHERS: // Targets all Pokemon + return move.getMove().accuracy; + case MoveData.MoveTarget.NEAR_OTHER: // Targets a Pokemon adjacent to the user + return move.getMove().accuracy; + case MoveData.MoveTarget.ALL_NEAR_OTHERS: // Targets all Pokemon adjacent to the user + return move.getMove().accuracy; + case MoveData.MoveTarget.NEAR_ENEMY: // Targets an opponent adjacent to the user + return move.getMove().accuracy; + case MoveData.MoveTarget.ALL_NEAR_ENEMIES: // Targets all opponents adjacent to the user + return move.getMove().accuracy; + case MoveData.MoveTarget.RANDOM_NEAR_ENEMY: // Targets a random opponent adjacent to the user + return move.getMove().accuracy; + case MoveData.MoveTarget.ALL_ENEMIES: // Targets all opponents + return move.getMove().accuracy; + case MoveData.MoveTarget.ATTACKER: // Counter move + return move.getMove().accuracy; + case MoveData.MoveTarget.NEAR_ALLY: // Targets an adjacent ally + return move.getMove().accuracy; + case MoveData.MoveTarget.ALLY: // Targets an ally + return move.getMove().accuracy; + case MoveData.MoveTarget.USER_OR_NEAR_ALLY: // Targets an ally or yourself + return move.getMove().accuracy; + case MoveData.MoveTarget.USER_AND_ALLIES: // Targets all on your side + return move.getMove().accuracy; + case MoveData.MoveTarget.ALL: // Targets everyone + return move.getMove().accuracy; + case MoveData.MoveTarget.USER_SIDE: // Targets your field + return move.getMove().accuracy; + case MoveData.MoveTarget.ENEMY_SIDE: // Targets enemy field + return -1; // Moves placing entry hazards always hit + case MoveData.MoveTarget.BOTH_SIDES: // Targets the entire field + return move.getMove().accuracy; + case MoveData.MoveTarget.PARTY: // Targets all of the Player's Pokemon, including ones that aren't active + return move.getMove().accuracy; + case MoveData.MoveTarget.CURSE: + return move.getMove().accuracy; + } + } + // Moves targeting the user and entry hazards can't miss + if ([MoveData.MoveTarget.USER, MoveData.MoveTarget.ENEMY_SIDE].includes(move.getMove().moveTarget)) { + return -1; + } + if (target == undefined) return move.getMove().accuracy; + // If either Pokemon has No Guard, + if (user.hasAbilityWithAttr(AlwaysHitAbAttr) || target.hasAbilityWithAttr(AlwaysHitAbAttr)) { + return -1; + } + // If the user should ignore accuracy on a target, check who the user targeted last turn and see if they match + if (user.getTag(BattlerTagType.IGNORE_ACCURACY) && (user.getLastXMoves().slice(1).find(() => true)?.targets || []).indexOf(target.getBattlerIndex()) !== -1) { + return -1; + } + + const hiddenTag = target.getTag(SemiInvulnerableTag); + if (hiddenTag && !move.getMove().getAttrs(MoveData.HitsTagAttr).some(hta => hta.tagType === hiddenTag.tagType)) { + return 0; + } + const moveAccuracy = new Utils.NumberHolder(move.getMove().accuracy); + + MoveData.applyMoveAttrs(MoveData.VariableAccuracyAttr, user, target, move.getMove(), moveAccuracy); + applyPreDefendAbAttrs(WonderSkinAbAttr, target, user, move.getMove(), { value: false }, moveAccuracy); + + if (moveAccuracy.value === -1) { + return -1; + } + + const isOhko = move.getMove().hasAttr(MoveData.OneHitKOAccuracyAttr); + + if (!isOhko) { + user.scene.applyModifiers(PokemonMoveAccuracyBoosterModifier, user.isPlayer(), user, moveAccuracy); + } + + if (this.scene.arena.weather?.weatherType === WeatherType.FOG) { + moveAccuracy.value = Math.floor(moveAccuracy.value * 0.9); + } + + if (!isOhko && this.scene.arena.getTag(ArenaTagType.GRAVITY)) { + moveAccuracy.value = Math.floor(moveAccuracy.value * 1.67); + } + + const userAccuracyLevel = new Utils.IntegerHolder(user.summonData.battleStats[BattleStat.ACC]); + const targetEvasionLevel = new Utils.IntegerHolder(target.summonData.battleStats[BattleStat.EVA]); + applyAbAttrs(IgnoreOpponentStatChangesAbAttr, target, null, userAccuracyLevel); + applyAbAttrs(IgnoreOpponentStatChangesAbAttr, user, null, targetEvasionLevel); + applyAbAttrs(IgnoreOpponentEvasionAbAttr, user, null, targetEvasionLevel); + MoveData.applyMoveAttrs(MoveData.IgnoreOpponentStatChangesAttr, user, target, move.getMove(), targetEvasionLevel); + this.scene.applyModifiers(TempBattleStatBoosterModifier, user.isPlayer(), TempBattleStat.ACC, userAccuracyLevel); + + const accuracyMultiplier = new Utils.NumberHolder(1); + if (userAccuracyLevel.value !== targetEvasionLevel.value) { + accuracyMultiplier.value = userAccuracyLevel.value > targetEvasionLevel.value + ? (3 + Math.min(userAccuracyLevel.value - targetEvasionLevel.value, 6)) / 3 + : 3 / (3 + Math.min(targetEvasionLevel.value - userAccuracyLevel.value, 6)); + } + + applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, user, BattleStat.ACC, accuracyMultiplier, move.getMove()); + + const evasionMultiplier = new Utils.NumberHolder(1); + applyBattleStatMultiplierAbAttrs(BattleStatMultiplierAbAttr, target, BattleStat.EVA, evasionMultiplier); + + accuracyMultiplier.value /= evasionMultiplier.value; + + return moveAccuracy.value * accuracyMultiplier.value + } + + 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 dmgHigh = 0 + var dmgLow = 0 + // dmgLow = (((2*user.level/5 + 2) * power * myAtk / theirDef)/50 + 2) * 0.85 * modifiers + // dmgHigh = (((2*user.level/5 + 2) * power * myAtkC / theirDefC)/50 + 2) * 1.5 * modifiers + var out = this.simulateAttack(scene, user, target, move.getMove()) + var minHits = 1 + var maxHits = 1 + var mh = move.getMove().getAttrs(MoveData.MultiHitAttr) + for (var i = 0; i < mh.length; i++) { + var mh2 = mh[i] as MoveData.MultiHitAttr + switch (mh2.multiHitType) { + case MoveData.MultiHitType._2: + minHits = 2; + maxHits = 2; + case MoveData.MultiHitType._2_TO_5: + minHits = 2; + maxHits = 5; + case MoveData.MultiHitType._3: + minHits = 3; + maxHits = 3; + case MoveData.MultiHitType._10: + minHits = 10; + maxHits = 10; + case MoveData.MultiHitType.BEAT_UP: + const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty(); + // No status means the ally pokemon can contribute to Beat Up + minHits = party.reduce((total, pokemon) => { + return total + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1); + }, 0); + maxHits = minHits + } + } + var h = user.getHeldItems() + for (var i = 0; i < h.length; i++) { + if (h[i].type instanceof PokemonMultiHitModifierType) { + minHits += h[i].getStackCount() + maxHits += h[i].getStackCount() + } + } + dmgLow = out[0] * minHits + dmgHigh = out[1] * maxHits + /* + if (user.hasAbility(Abilities.PARENTAL_BOND)) { + // Second hit deals 0.25x damage + dmgLow *= 1.25 + dmgHigh *= 1.25 + } + */ + var koText = "" + if (Math.floor(dmgLow) >= target.hp) { + koText = " (KO)" + } else if (Math.ceil(dmgHigh) >= target.hp) { + var percentChance = 1 - ((target.hp - dmgLow + 1) / (dmgHigh - dmgLow + 1)) + koText = " (" + Math.round(percentChance * 100) + "% KO)" + } + if (target.getMoveEffectiveness(user, move) == undefined) { + return "---" + } + if (scene.damageDisplay == "Value") + return target.getMoveEffectiveness(user, move) + "x - " + (Math.round(dmgLow) == Math.round(dmgHigh) ? Math.round(dmgLow).toString() : Math.round(dmgLow) + "-" + Math.round(dmgHigh)) + koText + dmgLow = Math.round((dmgLow)/target.getBattleStat(Stat.HP)*100) + dmgHigh = Math.round((dmgHigh)/target.getBattleStat(Stat.HP)*100) + if (scene.damageDisplay == "Percent") + return target.getMoveEffectiveness(user, move) + "x - " + (dmgLow == dmgHigh ? dmgLow + "%" : dmgLow + "%-" + dmgHigh + "%") + koText + if (scene.damageDisplay == "Off") + return target.getMoveEffectiveness(user, move) + "x" + } + setCursor(cursor: integer): boolean { const ui = this.getUi(); @@ -178,16 +723,22 @@ 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; const maxPP = pokemonMove.getMovePp(); const pp = maxPP - pokemonMove.ppUsed; + const accuracy1 = this.calculateAccuracy(pokemon, this.scene.getEnemyField()[0], pokemonMove) + const accuracy2 = this.calculateAccuracy(pokemon, this.scene.getEnemyField()[1], pokemonMove) + this.ppText.setText(`${Utils.padInt(pp, 2, " ")}/${Utils.padInt(maxPP, 2, " ")}`); this.powerText.setText(`${power >= 0 ? power : "---"}`); this.accuracyText.setText(`${accuracy >= 0 ? accuracy : "---"}`); + this.accuracyText.setText(`${accuracy1 >= 0 ? Math.round(accuracy1) : "---"}`); + if (this.scene.getEnemyField()[1] != undefined) + this.accuracyText.setText(`${accuracy1 >= 0 ? Math.round(accuracy1) : "---"}/${accuracy2 >= 0 ? Math.round(accuracy2) : "---"}`); const ppPercentLeft = pp / maxPP; @@ -207,6 +758,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)); }); } @@ -312,3 +864,333 @@ export default class FightUiHandler extends UiHandler { this.cursorObj = null; } } + +export function simulateAttack(scene: BattleScene, user: Pokemon, target: Pokemon, move: Move) { + let result: HitResult; + const damage1 = new Utils.NumberHolder(0); + const damage2 = new Utils.NumberHolder(0); + const defendingSidePlayField = target.isPlayer() ? scene.getPlayerField() : scene.getEnemyField(); + + const variableCategory = new Utils.IntegerHolder(move.category); + MoveData.applyMoveAttrs(MoveData.VariableMoveCategoryAttr, user, target, move, variableCategory); + const moveCategory = variableCategory.value as MoveData.MoveCategory; + + const typeChangeMovePowerMultiplier = new Utils.NumberHolder(1); + MoveData.applyMoveAttrs(MoveData.VariableMoveTypeAttr, user, target, move); + applyPreAttackAbAttrs(MoveTypeChangeAttr, user, target, move, typeChangeMovePowerMultiplier); + const types = target.getTypes(true, true); + + const cancelled = new Utils.BooleanHolder(false); + const typeless = move.hasAttr(MoveData.TypelessAttr); + const typeMultiplier = new Utils.NumberHolder(!typeless && (moveCategory !== MoveData.MoveCategory.STATUS || move.getAttrs(MoveData.StatusMoveTypeImmunityAttr).find(attr => types.includes(attr.immuneType))) + ? target.getAttackTypeEffectiveness(move.type, user, false, false) + : 1); + MoveData.applyMoveAttrs(MoveData.VariableMoveTypeMultiplierAttr, user, target, move, typeMultiplier); + if (typeless) { + typeMultiplier.value = 1; + } + if (types.find(t => move.isTypeImmune(user, target, t))) { + typeMultiplier.value = 0; + } + + // Apply arena tags for conditional protection + if (!move.checkFlag(MoveData.MoveFlags.IGNORE_PROTECT, user, target) && !move.isAllyTarget()) { + const defendingSide = target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY; + scene.arena.applyTagsForSide(ArenaTagType.QUICK_GUARD, defendingSide, cancelled, this, move.priority); + scene.arena.applyTagsForSide(ArenaTagType.WIDE_GUARD, defendingSide, cancelled, this, move.moveTarget); + scene.arena.applyTagsForSide(ArenaTagType.MAT_BLOCK, defendingSide, cancelled, this, move.category); + scene.arena.applyTagsForSide(ArenaTagType.CRAFTY_SHIELD, defendingSide, cancelled, this, move.category, move.moveTarget); + } + + switch (moveCategory) { + case MoveData.MoveCategory.PHYSICAL: + case MoveData.MoveCategory.SPECIAL: + const isPhysical = moveCategory === MoveData.MoveCategory.PHYSICAL; + const power = new Utils.NumberHolder(move.power); + const sourceTeraType = user.getTeraType(); + if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type && power.value < 60 && move.priority <= 0 && !move.hasAttr(MoveData.MultiHitAttr) && !scene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === user.id)) { + power.value = 60; + } + applyPreAttackAbAttrs(VariableMovePowerAbAttr, user, target, move, power); + + if (user.getAlly()?.hasAbilityWithAttr(AllyMoveCategoryPowerBoostAbAttr)) { + applyPreAttackAbAttrs(AllyMoveCategoryPowerBoostAbAttr, user, target, move, power); + } + + const fieldAuras = new Set( + scene.getField(true) + .map((p) => p.getAbilityAttrs(FieldMoveTypePowerBoostAbAttr) as FieldMoveTypePowerBoostAbAttr[]) + .flat(), + ); + for (const aura of fieldAuras) { + // The only relevant values are `move` and the `power` holder + aura.applyPreAttack(null, null, null, move, [power]); + } + + const alliedField: Pokemon[] = user instanceof PlayerPokemon ? scene.getPlayerField() : scene.getEnemyField(); + alliedField.forEach(p => applyPreAttackAbAttrs(UserFieldMoveTypePowerBoostAbAttr, p, user, move, power)); + + power.value *= typeChangeMovePowerMultiplier.value; + + if (!typeless) { + applyPreDefendAbAttrs(TypeImmunityAbAttr, user, target, move, cancelled, typeMultiplier); + MoveData.applyMoveAttrs(MoveData.NeutralDamageAgainstFlyingTypeMultiplierAttr, user, target, move, typeMultiplier); + } + if (!cancelled.value) { + applyPreDefendAbAttrs(MoveImmunityAbAttr, user, target, move, cancelled, typeMultiplier); + defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, user, move, cancelled, typeMultiplier)); + } + + if (cancelled.value) { + //user.stopMultiHit(target); + result = HitResult.NO_EFFECT; + } else { + const typeBoost = user.findTag(t => t instanceof TypeBoostTag && t.boostedType === move.type) as TypeBoostTag; + if (typeBoost) { + power.value *= typeBoost.boostValue; + if (typeBoost.oneUse) { + //user.removeTag(typeBoost.tagType); + } + } + const arenaAttackTypeMultiplier = new Utils.NumberHolder(scene.arena.getAttackTypeMultiplier(move.type, user.isGrounded())); + MoveData.applyMoveAttrs(MoveData.IgnoreWeatherTypeDebuffAttr, user, target, move, arenaAttackTypeMultiplier); + if (scene.arena.getTerrainType() === TerrainType.GRASSY && target.isGrounded() && move.type === Type.GROUND && move.moveTarget === MoveData.MoveTarget.ALL_NEAR_OTHERS) { + power.value /= 2; + } + + MoveData.applyMoveAttrs(MoveData.VariablePowerAttr, user, target, move, power); + + scene.applyModifiers(PokemonMultiHitModifier, user.isPlayer(), user, new Utils.IntegerHolder(0), power); + if (!typeless) { + scene.arena.applyTags(WeakenMoveTypeTag, move.type, power); + scene.applyModifiers(AttackTypeBoosterModifier, user.isPlayer(), user, move.type, power); + } + if (user.getTag(HelpingHandTag)) { + power.value *= 1.5; + } + let isCritical: boolean = true; + const critOnly = new Utils.BooleanHolder(false); + const critAlways = user.getTag(BattlerTagType.ALWAYS_CRIT); + MoveData.applyMoveAttrs(MoveData.CritOnlyAttr, user, target, move, critOnly); + applyAbAttrs(ConditionalCritAbAttr, user, null, critOnly, target, move); + if (isCritical) { + const blockCrit = new Utils.BooleanHolder(false); + applyAbAttrs(BlockCritAbAttr, target, null, blockCrit); + if (blockCrit.value) { + isCritical = false; + } + } + const sourceAtk = new Utils.IntegerHolder(user.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, target, null, false)); + const targetDef = new Utils.IntegerHolder(target.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, user, move, false)); + const sourceAtkCrit = new Utils.IntegerHolder(user.getBattleStat(isPhysical ? Stat.ATK : Stat.SPATK, target, null, isCritical)); + const targetDefCrit = new Utils.IntegerHolder(target.getBattleStat(isPhysical ? Stat.DEF : Stat.SPDEF, user, move, isCritical)); + const criticalMultiplier = new Utils.NumberHolder(isCritical ? 1.5 : 1); + applyAbAttrs(MultCritAbAttr, user, null, criticalMultiplier); + const screenMultiplier = new Utils.NumberHolder(1); + if (!isCritical) { + scene.arena.applyTagsForSide(WeakenMoveScreenTag, target.isPlayer() ? ArenaTagSide.PLAYER : ArenaTagSide.ENEMY, move.category, scene.currentBattle.double, screenMultiplier); + } + const isTypeImmune = (typeMultiplier.value * arenaAttackTypeMultiplier.value) === 0; + const sourceTypes = user.getTypes(); + const matchesSourceType = sourceTypes[0] === move.type || (sourceTypes.length > 1 && sourceTypes[1] === move.type); + const stabMultiplier = new Utils.NumberHolder(1); + if (sourceTeraType === Type.UNKNOWN && matchesSourceType) { + stabMultiplier.value += 0.5; + } else if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === move.type) { + stabMultiplier.value += 0.5; + } + + applyAbAttrs(StabBoostAbAttr, user, null, stabMultiplier); + + if (sourceTeraType !== Type.UNKNOWN && matchesSourceType) { + stabMultiplier.value = Math.min(stabMultiplier.value + 0.5, 2.25); + } + + MoveData.applyMoveAttrs(MoveData.VariableAtkAttr, user, target, move, sourceAtk); + MoveData.applyMoveAttrs(MoveData.VariableDefAttr, user, target, move, targetDef); + MoveData.applyMoveAttrs(MoveData.VariableAtkAttr, user, target, move, sourceAtkCrit); + MoveData.applyMoveAttrs(MoveData.VariableDefAttr, user, target, move, targetDefCrit); + + const effectPhase = scene.getCurrentPhase(); + let numTargets = 1; + if (effectPhase instanceof MoveEffectPhase) { + numTargets = effectPhase.getTargets().length; + } + const twoStrikeMultiplier = new Utils.NumberHolder(1); + applyPreAttackAbAttrs(AddSecondStrikeAbAttr, user, target, move, numTargets, new Utils.IntegerHolder(0), twoStrikeMultiplier); + + if (!isTypeImmune) { + damage1.value = Math.ceil(((((2 * user.level / 5 + 2) * power.value * sourceAtk.value / targetDef.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * twoStrikeMultiplier.value * 0.85); // low roll + damage2.value = Math.ceil(((((2 * user.level / 5 + 2) * power.value * sourceAtkCrit.value / targetDefCrit.value) / 50) + 2) * stabMultiplier.value * typeMultiplier.value * arenaAttackTypeMultiplier.value * screenMultiplier.value * twoStrikeMultiplier.value * criticalMultiplier.value); // high roll crit + if (isPhysical && user.status && user.status.effect === StatusEffect.BURN) { + if (!move.hasAttr(MoveData.BypassBurnDamageReductionAttr)) { + const burnDamageReductionCancelled = new Utils.BooleanHolder(false); + applyAbAttrs(BypassBurnDamageReductionAbAttr, user, burnDamageReductionCancelled); + if (!burnDamageReductionCancelled.value) { + damage1.value = Math.floor(damage1.value / 2); + damage2.value = Math.floor(damage2.value / 2); + } + } + } + + applyPreAttackAbAttrs(DamageBoostAbAttr, user, target, move, damage1); + applyPreAttackAbAttrs(DamageBoostAbAttr, user, target, move, damage2); + + /** + * For each {@link HitsTagAttr} the move has, doubles the damage of the move if: + * The target has a {@link BattlerTagType} that this move interacts with + * AND + * The move doubles damage when used against that tag + */ + move.getAttrs(MoveData.HitsTagAttr).filter(hta => hta.doubleDamage).forEach(hta => { + if (target.getTag(hta.tagType)) { + damage1.value *= 2; + damage2.value *= 2; + } + }); + } + + if (scene.arena.terrain?.terrainType === TerrainType.MISTY && target.isGrounded() && move.type === Type.DRAGON) { + damage1.value = Math.floor(damage1.value / 2); + damage2.value = Math.floor(damage2.value / 2); + } + + const fixedDamage = new Utils.IntegerHolder(0); + MoveData.applyMoveAttrs(MoveData.FixedDamageAttr, user, target, move, fixedDamage); + if (!isTypeImmune && fixedDamage.value) { + damage1.value = fixedDamage.value; + damage2.value = fixedDamage.value; + isCritical = false; + result = HitResult.EFFECTIVE; + } + + if (!result) { + if (!typeMultiplier.value) { + result = move.id === Moves.SHEER_COLD ? HitResult.IMMUNE : HitResult.NO_EFFECT; + } else { + const oneHitKo = new Utils.BooleanHolder(false); + MoveData.applyMoveAttrs(MoveData.OneHitKOAttr, user, target, move, oneHitKo); + if (oneHitKo.value) { + result = HitResult.ONE_HIT_KO; + isCritical = false; + damage1.value = target.hp; + damage2.value = target.hp; + } else if (typeMultiplier.value >= 2) { + result = HitResult.SUPER_EFFECTIVE; + } else if (typeMultiplier.value >= 1) { + result = HitResult.EFFECTIVE; + } else { + result = HitResult.NOT_VERY_EFFECTIVE; + } + } + } + + if (!fixedDamage.value) { + if (!user.isPlayer()) { + scene.applyModifiers(EnemyDamageBoosterModifier, false, damage1); + scene.applyModifiers(EnemyDamageBoosterModifier, false, damage2); + } + if (!target.isPlayer()) { + scene.applyModifiers(EnemyDamageReducerModifier, false, damage1); + scene.applyModifiers(EnemyDamageReducerModifier, false, damage2); + } + } + + MoveData.applyMoveAttrs(MoveData.ModifiedDamageAttr, user, target, move, damage1); + MoveData.applyMoveAttrs(MoveData.ModifiedDamageAttr, user, target, move, damage2); + applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage1); + applyPreDefendAbAttrs(ReceivedMoveDamageMultiplierAbAttr, user, target, move, cancelled, damage2); + const destinyTag = target.getTag(BattlerTagType.DESTINY_BOND); + + const oneHitKo = result === HitResult.ONE_HIT_KO; + if (damage1.value) { + if (target.getHpRatio() === 1) { + applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, target, user, move, cancelled, damage1); + } + } + if (damage2.value) { + if (target.getHpRatio() === 1) { + applyPreDefendAbAttrs(PreDefendFullHpEndureAbAttr, target, user, move, cancelled, damage2); + } + } + } + break; + case MoveData.MoveCategory.STATUS: + if (!typeless) { + applyPreDefendAbAttrs(TypeImmunityAbAttr, target, user, move, cancelled, typeMultiplier); + } + if (!cancelled.value) { + applyPreDefendAbAttrs(MoveImmunityAbAttr, target, user, move, cancelled, typeMultiplier); + defendingSidePlayField.forEach((p) => applyPreDefendAbAttrs(FieldPriorityMoveImmunityAbAttr, p, user, move, cancelled, typeMultiplier)); + } + if (!typeMultiplier.value) { + return -1 + } + result = cancelled.value || !typeMultiplier.value ? HitResult.NO_EFFECT : HitResult.STATUS; + break; + } + return [damage1.value, damage2.value] +} + +export function calcDamage(scene: BattleScene, user: PlayerPokemon, target: Pokemon, move: PokemonMove) { + var dmgHigh = 0 + var dmgLow = 0 + // dmgLow = (((2*user.level/5 + 2) * power * myAtk / theirDef)/50 + 2) * 0.85 * modifiers + // dmgHigh = (((2*user.level/5 + 2) * power * myAtkC / theirDefC)/50 + 2) * 1.5 * modifiers + var out = this.simulateAttack(scene, user, target, move.getMove()) + var minHits = 1 + var maxHits = 1 + var mh = move.getMove().getAttrs(MoveData.MultiHitAttr) + for (var i = 0; i < mh.length; i++) { + var mh2 = mh[i] as MoveData.MultiHitAttr + switch (mh2.multiHitType) { + case MoveData.MultiHitType._2: + minHits = 2; + maxHits = 2; + case MoveData.MultiHitType._2_TO_5: + minHits = 2; + maxHits = 5; + case MoveData.MultiHitType._3: + minHits = 3; + maxHits = 3; + case MoveData.MultiHitType._10: + minHits = 10; + maxHits = 10; + case MoveData.MultiHitType.BEAT_UP: + const party = user.isPlayer() ? user.scene.getParty() : user.scene.getEnemyParty(); + // No status means the ally pokemon can contribute to Beat Up + minHits = party.reduce((total, pokemon) => { + return total + (pokemon.id === user.id ? 1 : pokemon?.status && pokemon.status.effect !== StatusEffect.NONE ? 0 : 1); + }, 0); + maxHits = minHits + } + } + var h = user.getHeldItems() + for (var i = 0; i < h.length; i++) { + if (h[i].type instanceof PokemonMultiHitModifierType) { + minHits += h[i].getStackCount() + maxHits += h[i].getStackCount() + } + } + dmgLow = out[0] * minHits + dmgHigh = out[1] * maxHits + /* + if (user.hasAbility(Abilities.PARENTAL_BOND)) { + // Second hit deals 0.25x damage + dmgLow *= 1.25 + dmgHigh *= 1.25 + } + */ + var koText = "" + if (Math.floor(dmgLow) >= target.hp) { + koText = " (KO)" + } else if (Math.ceil(dmgHigh) >= target.hp) { + var percentChance = (target.hp - dmgLow + 1) / (dmgHigh - dmgLow + 1) + koText = " (" + Math.round(percentChance * 100) + "% KO)" + } + return (Math.round(dmgLow) == Math.round(dmgHigh) ? Math.round(dmgLow).toString() : Math.round(dmgLow) + "-" + Math.round(dmgHigh)) + koText + dmgLow = Math.round((dmgLow)/target.getBattleStat(Stat.HP)*100) + dmgHigh = Math.round((dmgHigh)/target.getBattleStat(Stat.HP)*100) + return (dmgLow == dmgHigh ? dmgLow + "%" : dmgLow + "%-" + dmgHigh + "%") + koText + return "???" +} \ No newline at end of file diff --git a/src/ui/log-name-form-ui-handler.ts b/src/ui/log-name-form-ui-handler.ts new file mode 100644 index 00000000000..85d2740549c --- /dev/null +++ b/src/ui/log-name-form-ui-handler.ts @@ -0,0 +1,105 @@ +import { FormModalUiHandler } from "./form-modal-ui-handler"; +import { ModalConfig } from "./modal-ui-handler"; +import * as Utils from "../utils"; +import { Mode } from "./ui"; +import i18next from "i18next"; +import * as LoggerTools from "../logger"; +import { addTextObject, TextStyle } from "./text"; + +export default class LogNameFormUiHandler extends FormModalUiHandler { + name: string; + + getModalTitle(config?: ModalConfig): string { + return (this.name ? this.name : "Manage Log"); + } + + getFields(config?: ModalConfig): string[] { + return [ "Name", "Author(s)", "Label" ]; + } + + getWidth(config?: ModalConfig): number { + return 160; + } + + getMargin(config?: ModalConfig): [number, number, number, number] { + return [ 0, 0, 48, 0 ]; + } + + getButtonLabels(config?: ModalConfig): string[] { + return [ "Rename", "Export", "ExSheet", "Delete" ]; + } + + getReadableErrorMessage(error: string): string { + const colonIndex = error?.indexOf(":"); + if (colonIndex > 0) { + error = error.slice(0, colonIndex); + } + switch (error) { + case "invalid username": + return i18next.t("menu:invalidLoginUsername"); + case "invalid password": + return i18next.t("menu:invalidLoginPassword"); + case "account doesn't exist": + return i18next.t("menu:accountNonExistent"); + case "password doesn't match": + return i18next.t("menu:unmatchingPassword"); + } + + return super.getReadableErrorMessage(error); + } + + setup(): void { + super.setup(); + + //const label = addTextObject(this.scene, 10, 87, "Clicking Export or ExSheets does NOT save any text you entered\nPress \"Rename\", then reopen this menu and click Export", TextStyle.TOOLTIP_CONTENT, { fontSize: "42px" }); + //this.modalContainer.add(label); + + this.inputs[0].maxLength = 99 + this.inputs[1].maxLength = 200 + } + + show(args: any[]): boolean { + this.name = args[0].autofillfields[0] + if (super.show(args)) { + const config = args[0] as ModalConfig; + console.log("Shown", args) + + const originalLoginAction = this.submitAction; + this.inputs[0].setText(args[0].autofillfields[0]) + this.inputs[1].setText(args[0].autofillfields[1]) + this.inputs[2].setText(args[0].autofillfields[2]) + this.submitAction = (_) => { + console.log("submitAction") + // Prevent overlapping overrides on action modification + this.submitAction = originalLoginAction; + this.sanitizeInputs(); + this.scene.ui.setMode(Mode.LOADING, { buttonActions: [] }); + const onFail = error => { + this.scene.ui.setMode(Mode.NAME_LOG, Object.assign(config, { errorMessage: error?.trim() })); + this.scene.ui.playError(); + }; + if (!this.inputs[0].text) { + //return onFail(i18next.t("menu:emptyUsername")); + } + console.log(`Calling LoggerTools.setFileInfo(${this.inputs[0].text}, ${this.inputs[1].text.split(",")})`) + LoggerTools.setFileInfo(this.inputs[0].text, this.inputs[1].text.split(",")) + console.log(`Calling originalLoginAction()`) + originalLoginAction() + }; + const exportaction1 = config.buttonActions[1] + config.buttonActions[1] = (_) => { + LoggerTools.setFileInfo(this.inputs[0].text, this.inputs[1].text.split(",")) + exportaction1() + } + const exportaction2 = config.buttonActions[2] + config.buttonActions[2] = (_) => { + LoggerTools.setFileInfo(this.inputs[0].text, this.inputs[1].text.split(",")) + exportaction2() + } + + return true; + } + + return false; + } +} diff --git a/src/ui/log-select-ui-handler.ts b/src/ui/log-select-ui-handler.ts new file mode 100644 index 00000000000..bd5d8057f01 --- /dev/null +++ b/src/ui/log-select-ui-handler.ts @@ -0,0 +1,355 @@ +import i18next from "i18next"; +import BattleScene from "../battle-scene"; +import { Button } from "#enums/buttons"; +import { GameMode } from "../game-mode"; +import { PokemonHeldItemModifier } from "../modifier/modifier"; +import { SessionSaveData } from "../system/game-data"; +import PokemonData from "../system/pokemon-data"; +import * as Utils from "../utils"; +import MessageUiHandler from "./message-ui-handler"; +import { TextStyle, addTextObject } from "./text"; +import { Mode } from "./ui"; +import { addWindow } from "./ui-theme"; +import * as LoggerTools from "../logger" +import { loggedInUser } from "#app/account.js"; +import { allpanels, biomePanelIDs } from "../loading-scene" +import { getBiomeName } from "#app/data/biomes.js"; +import { Species } from "#app/enums/species.js"; +import { getPokemonSpecies, getPokemonSpeciesForm } from "#app/data/pokemon-species.js"; + +const sessionSlotCount = 5; + +export type LogSelectCallback = (key: string) => void; + +export default class LogSelectUiHandler extends MessageUiHandler { + + private saveSlotSelectContainer: Phaser.GameObjects.Container; + private sessionSlotsContainer: Phaser.GameObjects.Container; + private saveSlotSelectMessageBox: Phaser.GameObjects.NineSlice; + private saveSlotSelectMessageBoxContainer: Phaser.GameObjects.Container; + private sessionSlots: SessionSlot[]; + + private selectCallback: LogSelectCallback; + + private scrollCursor: integer = 0; + + private cursorObj: Phaser.GameObjects.NineSlice; + + private sessionSlotsContainerInitialY: number; + + constructor(scene: BattleScene) { + super(scene, Mode.LOG_HANDLER); + } + + setup() { + const ui = this.getUi(); + + this.saveSlotSelectContainer = this.scene.add.container(0, 0); + this.saveSlotSelectContainer.setVisible(false); + ui.add(this.saveSlotSelectContainer); + + const loadSessionBg = this.scene.add.rectangle(0, 0, this.scene.game.canvas.width / 6, -this.scene.game.canvas.height / 6, 0x006860); + loadSessionBg.setOrigin(0, 0); + this.saveSlotSelectContainer.add(loadSessionBg); + + this.sessionSlotsContainerInitialY = -this.scene.game.canvas.height / 6 + 8; + + this.sessionSlotsContainer = this.scene.add.container(8, this.sessionSlotsContainerInitialY); + this.saveSlotSelectContainer.add(this.sessionSlotsContainer); + + this.saveSlotSelectMessageBoxContainer = this.scene.add.container(0, 0); + this.saveSlotSelectMessageBoxContainer.setVisible(false); + this.saveSlotSelectContainer.add(this.saveSlotSelectMessageBoxContainer); + + this.saveSlotSelectMessageBox = addWindow(this.scene, 1, -1, 318, 28); + this.saveSlotSelectMessageBox.setOrigin(0, 1); + this.saveSlotSelectMessageBoxContainer.add(this.saveSlotSelectMessageBox); + + this.message = addTextObject(this.scene, 8, 8, "", TextStyle.WINDOW, { maxLines: 2 }); + this.message.setOrigin(0, 0); + this.saveSlotSelectMessageBoxContainer.add(this.message); + + this.sessionSlots = []; + } + + show(args: any[]): boolean { + if ((args.length < 1 || !(args[0] instanceof Function))) { + return false; + } + + super.show(args); + + this.selectCallback = args[0] as LogSelectCallback; + + this.saveSlotSelectContainer.setVisible(true); + this.populateSessionSlots(); + this.setScrollCursor(0); + this.setCursor(0); + + return true; + } + + processInput(button: Button): boolean { + const ui = this.getUi(); + + let success = false; + let error = false; + + if (button === Button.ACTION || button === Button.CANCEL) { + const originalCallback = this.selectCallback; + if (button === Button.ACTION) { + const cursor = this.cursor + this.scrollCursor; + this.selectCallback = null; + originalCallback(this.sessionSlots[cursor].key); + success = true; + } else { + this.selectCallback = null; + originalCallback(undefined); + success = true; + } + } else { + switch (button) { + case Button.UP: + if (this.cursor) { + success = this.setCursor(this.cursor - 1); + } else if (this.scrollCursor) { + success = this.setScrollCursor(this.scrollCursor - 1); + } + break; + case Button.DOWN: + if (this.cursor < 2) { + success = this.setCursor(this.cursor + 1); + } else if (this.scrollCursor < this.sessionSlots.length - 3) { + success = this.setScrollCursor(this.scrollCursor + 1); + } + break; + } + } + + if (success) { + ui.playSelect(); + } else if (error) { + ui.playError(); + } + + return success || error; + } + + populateSessionSlots() { + var ui = this.getUi(); + var ypos = 0; + LoggerTools.getLogs() + for (let s = 0; s < sessionSlotCount; s++) { + var found = false + for (var i = 0; i < LoggerTools.logs.length; i++) { + if (LoggerTools.logs[i][3] == s.toString()) { + found = true + const sessionSlot = new SessionSlot(this.scene, s, ypos); + ypos++ + sessionSlot.load(LoggerTools.logs[i][1]); + this.scene.add.existing(sessionSlot); + this.sessionSlotsContainer.add(sessionSlot); + this.sessionSlots.push(sessionSlot); + } + } + if (!found) { + const sessionSlot = new SessionSlot(this.scene, s, ypos); + ypos++ + sessionSlot.load(undefined); + this.scene.add.existing(sessionSlot); + this.sessionSlotsContainer.add(sessionSlot); + this.sessionSlots.push(sessionSlot); + } + } + for (var i = 0; i < LoggerTools.logs.length; i++) { + if (LoggerTools.logs[i][3] == "") { + const sessionSlot = new SessionSlot(this.scene, undefined, ypos); + ypos++ + sessionSlot.load(LoggerTools.logs[i][1]); + this.scene.add.existing(sessionSlot); + this.sessionSlotsContainer.add(sessionSlot); + this.sessionSlots.push(sessionSlot); + } + } + } + + showText(text: string, delay?: integer, callback?: Function, callbackDelay?: integer, prompt?: boolean, promptDelay?: integer) { + super.showText(text, delay, callback, callbackDelay, prompt, promptDelay); + + if (text?.indexOf("\n") === -1) { + this.saveSlotSelectMessageBox.setSize(318, 28); + this.message.setY(-22); + } else { + this.saveSlotSelectMessageBox.setSize(318, 42); + this.message.setY(-37); + } + + this.saveSlotSelectMessageBoxContainer.setVisible(!!text?.length); + } + + setCursor(cursor: integer): boolean { + const changed = super.setCursor(cursor); + + if (!this.cursorObj) { + this.cursorObj = this.scene.add.nineslice(0, 0, "select_cursor_highlight_thick", null, 296, 44, 6, 6, 6, 6); + this.cursorObj.setOrigin(0, 0); + this.sessionSlotsContainer.add(this.cursorObj); + } + this.cursorObj.setPosition(4, 4 + (cursor + this.scrollCursor) * 56); + + return changed; + } + + setScrollCursor(scrollCursor: integer): boolean { + const changed = scrollCursor !== this.scrollCursor; + + if (changed) { + this.scrollCursor = scrollCursor; + this.setCursor(this.cursor); + this.scene.tweens.add({ + targets: this.sessionSlotsContainer, + y: this.sessionSlotsContainerInitialY - 56 * scrollCursor, + duration: Utils.fixedInt(325), + ease: "Sine.easeInOut" + }); + } + + return changed; + } + + clear() { + super.clear(); + this.saveSlotSelectContainer.setVisible(false); + this.eraseCursor(); + this.selectCallback = null; + this.clearSessionSlots(); + } + + eraseCursor() { + if (this.cursorObj) { + this.cursorObj.destroy(); + } + this.cursorObj = null; + } + + clearSessionSlots() { + this.sessionSlots.splice(0, this.sessionSlots.length); + this.sessionSlotsContainer.removeAll(true); + } +} + +class SessionSlot extends Phaser.GameObjects.Container { + public slotId: integer; + public autoSlot: integer; + public hasData: boolean; + public wv: integer; + public key: string; + private loadingLabel: Phaser.GameObjects.Text; + + constructor(scene: BattleScene, slotId: integer = undefined, ypos: integer, autoSlot?: integer) { + super(scene, 0, ypos * 56); + + this.slotId = slotId; + this.autoSlot = autoSlot + + this.setup(); + } + + setup() { + const slotWindow = addWindow(this.scene, 0, 0, 304, 52); + this.add(slotWindow); + + this.loadingLabel = addTextObject(this.scene, 152, 26, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW); + this.loadingLabel.setOrigin(0.5, 0.5); + this.add(this.loadingLabel); + } + + async setupWithData(data: LoggerTools.DRPD) { + this.remove(this.loadingLabel, true); + var lbl = `???` + lbl = data.title + if (this.slotId) { + lbl = `[${this.slotId}] ${lbl}` + } + console.log(data, this.slotId, this.autoSlot, lbl) + const gameModeLabel = addTextObject(this.scene, 8, 5, lbl, TextStyle.WINDOW); + this.add(gameModeLabel); + + const timestampLabel = addTextObject(this.scene, 8, 19, data.date, TextStyle.WINDOW); + this.add(timestampLabel); + + const playTimeLabel = addTextObject(this.scene, 8, 33, data.version + " / " + (data.label || "") + " / " + (data.uuid || ""), TextStyle.WINDOW); + this.add(playTimeLabel); + + const pokemonIconsContainer = this.scene.add.container(144, 4); + if (false || data.starters) + data.starters.forEach((p: LoggerTools.PokeData, i: integer) => { + if (p == undefined) + return; + const iconContainer = this.scene.add.container(26 * i, 0); + iconContainer.setScale(0.75); + + if (Utils.getEnumValues(Species)[p.id] == undefined) + return; + + if (getPokemonSpecies(Utils.getEnumValues(Species)[p.id]) == undefined) + return; + + const icon = this.scene.addPkIcon(getPokemonSpecies(Utils.getEnumValues(Species)[p.id]), 0, 0, 0, 0, 0); + + const text = addTextObject(this.scene, 32, 20, `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(p.level, 1000)}`, TextStyle.PARTY, { fontSize: "54px", color: "#f8f8f8" }); + text.setShadow(0, 0, null); + text.setStroke("#424242", 14); + text.setOrigin(1, 0); + + iconContainer.add(icon); + iconContainer.add(text); + + pokemonIconsContainer.add(iconContainer); + }); + + this.add(pokemonIconsContainer); + + //const modifiersModule = await import("../modifier/modifier"); + + const modifierIconsContainer = this.scene.add.container(148, 30); + modifierIconsContainer.setScale(0.5); + let visibleModifierIndex = 0; + + this.add(modifierIconsContainer); + } + + load(l?: string, slot?: integer): Promise { + return new Promise(resolve => { + if (l == undefined) { + this.hasData = false; + this.loadingLabel.setText("No data for this run"); + resolve(false); + return; + } + this.key = l + if (slot) { + this.slotId = slot + } + this.setupWithData(JSON.parse(localStorage.getItem(l))) + resolve(true); + }); + return new Promise(resolve => { + this.scene.gameData.getSession(this.slotId, this.autoSlot).then(async sessionData => { + if (!sessionData) { + this.hasData = false; + this.loadingLabel.setText(i18next.t("saveSlotSelectUiHandler:empty")); + resolve(false); + return; + } + this.hasData = true; + await this.setupWithData(undefined); + resolve(true); + }); + }); + } +} + +interface SessionSlot { + scene: BattleScene; +} diff --git a/src/ui/menu-ui-handler.ts b/src/ui/menu-ui-handler.ts index 571a09f3b37..4a4d074aec7 100644 --- a/src/ui/menu-ui-handler.ts +++ b/src/ui/menu-ui-handler.ts @@ -22,6 +22,7 @@ enum MenuOptions { MANAGE_DATA, COMMUNITY, SAVE_AND_QUIT, + SAVE_AND_REFRESH, LOG_OUT } @@ -356,6 +357,23 @@ export default class MenuUiHandler extends MessageUiHandler { error = true; } break; + case MenuOptions.SAVE_AND_REFRESH: + if (this.scene.currentBattle) { + success = true; + if (this.scene.currentBattle.turn > 1) { + ui.showText(i18next.t("menuUiHandler:losingProgressionWarning"), null, () => { + ui.setOverlayMode(Mode.CONFIRM, () => this.scene.gameData.saveAll(this.scene, true, true, true, true).then(() => this.scene.reset(true)), () => { + ui.revertMode(); + ui.showText(null, 0); + }, false, -98); + }); + } else { + this.scene.gameData.saveAll(this.scene, true, true, true, true).then(() => this.scene.reset(true)); + } + } else { + error = true; + } + break; case MenuOptions.LOG_OUT: success = true; const doLogout = () => { diff --git a/src/ui/party-ui-handler.ts b/src/ui/party-ui-handler.ts index e820c8cb0d2..1e2fd6f98f2 100644 --- a/src/ui/party-ui-handler.ts +++ b/src/ui/party-ui-handler.ts @@ -21,6 +21,7 @@ import MoveInfoOverlay from "./move-info-overlay"; import i18next from "i18next"; import BBCodeText from "phaser3-rex-plugins/plugins/bbcodetext"; import { Moves } from "#enums/moves"; +import * as LoggerTools from "../logger"; const defaultMessage = i18next.t("partyUiHandler:choosePokemon"); @@ -63,7 +64,7 @@ export enum PartyOption { } export type PartySelectCallback = (cursor: integer, option: PartyOption) => void; -export type PartyModifierTransferSelectCallback = (fromCursor: integer, index: integer, itemQuantity?: integer, toCursor?: integer) => void; +export type PartyModifierTransferSelectCallback = (fromCursor: integer, index: integer, itemQuantity?: integer, toCursor?: integer, isAll?: boolean, isFirst?: boolean) => void; export type PartyModifierSpliceSelectCallback = (fromCursor: integer, toCursor?: integer) => void; export type PokemonSelectFilter = (pokemon: PlayerPokemon) => string; export type PokemonModifierTransferSelectFilter = (pokemon: PlayerPokemon, modifier: PokemonHeldItemModifier) => string; @@ -109,6 +110,8 @@ export default class PartyUiHandler extends MessageUiHandler { private tmMoveId: Moves; private showMovePp: boolean; + private incomingMon: string; + private iconAnimHandler: PokemonIconAnimHandler; private static FilterAll = (_pokemon: PlayerPokemon) => null; @@ -250,6 +253,7 @@ export default class PartyUiHandler extends MessageUiHandler { : PartyUiHandler.FilterAllMoves; this.tmMoveId = args.length > 5 && args[5] ? args[5] : Moves.NONE; this.showMovePp = args.length > 6 && args[6]; + this.incomingMon = args.length > 7 && args[7] ? args[7] : undefined this.partyContainer.setVisible(true); this.partyBg.setTexture(`party_bg${this.scene.currentBattle.double ? "_double" : ""}`); @@ -283,7 +287,15 @@ export default class PartyUiHandler extends MessageUiHandler { if (this.optionsMode) { const option = this.options[this.optionsCursor]; if (button === Button.ACTION) { + //console.log("Menu Action (" + option + " - targ " + PartyOption.RELEASE + ")") const pokemon = this.scene.getParty()[this.cursor]; + if (option === PartyOption.RELEASE) { + if (this.incomingMon != undefined) { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, `Add ${this.incomingMon}, replacing ${this.scene.getParty()[this.cursor].name} (Slot ${this.cursor + 1})`) + } else { + LoggerTools.logActions(this.scene, this.scene.currentBattle.waveIndex, `Release ${this.scene.getParty()[this.cursor].name} (Slot ${this.cursor + 1})`) + } + } if (this.partyUiMode === PartyUiMode.MODIFIER_TRANSFER && !this.transferMode && option !== PartyOption.CANCEL) { this.startTransfer(); this.clearOptions(); @@ -326,7 +338,7 @@ export default class PartyUiHandler extends MessageUiHandler { if (option === PartyOption.TRANSFER) { if (this.transferCursor !== this.cursor) { if (this.transferAll) { - getTransferrableItemsFromPokemon(this.scene.getParty()[this.transferCursor]).forEach((_, i) => (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, i, this.transferQuantitiesMax[i], this.cursor)); + getTransferrableItemsFromPokemon(this.scene.getParty()[this.transferCursor]).forEach((_, i) => (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, i, this.transferQuantitiesMax[i], this.cursor, true, i == 0)); } else { (this.selectCallback as PartyModifierTransferSelectCallback)(this.transferCursor, this.transferOptionCursor, this.transferQuantities[this.transferOptionCursor], this.cursor); } diff --git a/src/ui/pokemon-info-container.ts b/src/ui/pokemon-info-container.ts index 9f4df2b20b4..30d1c956791 100644 --- a/src/ui/pokemon-info-container.ts +++ b/src/ui/pokemon-info-container.ts @@ -207,7 +207,7 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.setVisible(false); } - show(pokemon: Pokemon, showMoves: boolean = false, speedMultiplier: number = 1): Promise { + show(pokemon: Pokemon, showMoves: boolean = false, speedMultiplier: number = 1, lessInfo: boolean = false): Promise { return new Promise(resolve => { const caughtAttr = BigInt(pokemon.scene.gameData.dexData[pokemon.species.speciesId].caughtAttr); if (pokemon.gender > Gender.GENDERLESS) { @@ -290,6 +290,11 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { this.pokemonNatureLabelText.setColor(getTextColor(TextStyle.WINDOW, false, this.scene.uiTheme)); this.pokemonNatureLabelText.setShadowColor(getTextColor(TextStyle.WINDOW, true, this.scene.uiTheme)); } + + this.pokemonAbilityText.setVisible(!lessInfo) + this.pokemonAbilityLabelText.setVisible(!lessInfo) + this.pokemonNatureText.setVisible(!lessInfo) + this.pokemonNatureLabelText.setVisible(!lessInfo) const isFusion = pokemon.isFusion(); const doubleShiny = isFusion && pokemon.shiny && pokemon.fusionShiny; @@ -328,7 +333,11 @@ export default class PokemonInfoContainer extends Phaser.GameObjects.Container { ? this.scene.gameData.dexData[starterSpeciesId].ivs : null; - this.statsContainer.updateIvs(pokemon.ivs, originalIvs); + if (lessInfo) { + this.statsContainer.updateIvs(pokemon.species.baseStats, undefined, 255); + } else { + this.statsContainer.updateIvs(pokemon.ivs, originalIvs); + } this.scene.tweens.add({ targets: this, diff --git a/src/ui/save-slot-select-ui-handler.ts b/src/ui/save-slot-select-ui-handler.ts index 8a81ac4858d..3a3586ff361 100644 --- a/src/ui/save-slot-select-ui-handler.ts +++ b/src/ui/save-slot-select-ui-handler.ts @@ -10,6 +10,10 @@ import MessageUiHandler from "./message-ui-handler"; import { TextStyle, addTextObject } from "./text"; import { Mode } from "./ui"; import { addWindow } from "./ui-theme"; +import * as LoggerTools from "../logger" +import { loggedInUser } from "#app/account.js"; +import { allpanels, biomePanelIDs } from "../loading-scene" +import { getBiomeName } from "#app/data/biomes.js"; const sessionSlotCount = 5; @@ -18,7 +22,7 @@ export enum SaveSlotUiMode { SAVE } -export type SaveSlotSelectCallback = (cursor: integer) => void; +export type SaveSlotSelectCallback = (cursor: integer, cursor2?: integer) => void; export default class SaveSlotSelectUiHandler extends MessageUiHandler { @@ -105,18 +109,38 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } else { switch (this.uiMode) { case SaveSlotUiMode.LOAD: - this.saveSlotSelectCallback = null; - originalCallback(cursor); + if (this.sessionSlots[cursor].autoSlot) { + ui.showText("This will revert slot " + (this.sessionSlots[cursor].slotId + 1) + " to wave " + (this.sessionSlots[cursor].wv) + ".\nIs that okay?", null, () => { + ui.setOverlayMode(Mode.CONFIRM, () => { + this.saveSlotSelectCallback = null; + originalCallback(this.sessionSlots[cursor].slotId, this.sessionSlots[cursor].autoSlot); + }, () => { + ui.revertMode(); + ui.showText(null, 0); + }, false, 0, 19, 500); + }); + } else { + this.saveSlotSelectCallback = null; + originalCallback(this.sessionSlots[cursor].slotId, this.sessionSlots[cursor].autoSlot); + } break; case SaveSlotUiMode.SAVE: const saveAndCallback = () => { const originalCallback = this.saveSlotSelectCallback; this.saveSlotSelectCallback = null; + var dataslot = this.sessionSlots[cursor].slotId + for (var i = 0; i < LoggerTools.autoCheckpoints.length; i++) { + // Delete any autosaves associated with this slot + localStorage.removeItem(`sessionData${dataslot ? dataslot : ""}_${loggedInUser.username}_auto${i}`) + } ui.revertMode(); ui.showText(null, 0); ui.setMode(Mode.MESSAGE); - originalCallback(cursor); + originalCallback(this.sessionSlots[cursor].slotId, this.sessionSlots[cursor].autoSlot); }; + if (this.sessionSlots[cursor].autoSlot != undefined) { + return false; + } if (this.sessionSlots[cursor].hasData) { ui.showText(i18next.t("saveSlotSelectUiHandler:overwriteData"), null, () => { ui.setOverlayMode(Mode.CONFIRM, () => { @@ -158,7 +182,7 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { case Button.DOWN: if (this.cursor < 2) { success = this.setCursor(this.cursor + 1); - } else if (this.scrollCursor < sessionSlotCount - 3) { + } else if (this.scrollCursor < this.sessionSlots.length - 3) { success = this.setScrollCursor(this.scrollCursor + 1); } break; @@ -175,12 +199,28 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { } populateSessionSlots() { + var ui = this.getUi(); + var ypos = 0; for (let s = 0; s < sessionSlotCount; s++) { - const sessionSlot = new SessionSlot(this.scene, s); + const sessionSlot = new SessionSlot(this.scene, s, ypos); + ypos++ sessionSlot.load(); this.scene.add.existing(sessionSlot); this.sessionSlotsContainer.add(sessionSlot); this.sessionSlots.push(sessionSlot); + if (this.uiMode != SaveSlotUiMode.SAVE && this.scene.showAutosaves) { + for (var j = 0; j < LoggerTools.autoCheckpoints.length; j++) { + var k = "sessionData" + (s ? s : "") + "_Guest_auto" + j + if (localStorage.getItem(k) != null) { + const sessionSlot = new SessionSlot(this.scene, s, ypos, j); + ypos++ + sessionSlot.load(); + this.scene.add.existing(sessionSlot); + this.sessionSlotsContainer.add(sessionSlot); + this.sessionSlots.push(sessionSlot); + } + } + } } } @@ -251,13 +291,17 @@ export default class SaveSlotSelectUiHandler extends MessageUiHandler { class SessionSlot extends Phaser.GameObjects.Container { public slotId: integer; + public autoSlot: integer; public hasData: boolean; + public wv: integer; private loadingLabel: Phaser.GameObjects.Text; + public backer: Phaser.GameObjects.Image - constructor(scene: BattleScene, slotId: integer) { - super(scene, 0, slotId * 56); + constructor(scene: BattleScene, slotId: integer, ypos: integer, autoSlot?: integer) { + super(scene, 0, ypos * 56); this.slotId = slotId; + this.autoSlot = autoSlot this.setup(); } @@ -266,6 +310,16 @@ class SessionSlot extends Phaser.GameObjects.Container { const slotWindow = addWindow(this.scene, 0, 0, 304, 52); this.add(slotWindow); + if (this.scene.doBiomePanels) { + this.backer = this.scene.add.image(0, 0, `end_panel`) + this.backer.setOrigin(0.5, 0.5) + this.backer.setScale(304/909, 52/155) + this.backer.setPosition(102*1.5 - 1, 26) + this.backer.setSize(304, 52) + this.backer.setVisible(false) + this.add(this.backer) + } + this.loadingLabel = addTextObject(this.scene, 152, 26, i18next.t("saveSlotSelectUiHandler:loading"), TextStyle.WINDOW); this.loadingLabel.setOrigin(0.5, 0.5); this.add(this.loadingLabel); @@ -273,16 +327,28 @@ class SessionSlot extends Phaser.GameObjects.Container { async setupWithData(data: SessionSaveData) { this.remove(this.loadingLabel, true); - - const gameModeLabel = addTextObject(this.scene, 8, 5, `${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unkown")} - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}`, TextStyle.WINDOW); + this.wv = data.waveIndex; + var lbl = `Slot ${this.slotId+1} (${GameMode.getModeName(data.gameMode) || i18next.t("gameMode:unkown")}) - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}` + if (this.autoSlot != undefined) { + lbl = `Slot ${this.slotId+1} (Auto) - ${i18next.t("saveSlotSelectUiHandler:wave")} ${data.waveIndex}` + } + console.log(data, this.slotId, this.autoSlot, lbl) + const gameModeLabel = addTextObject(this.scene, 8, 5, lbl, TextStyle.WINDOW); this.add(gameModeLabel); const timestampLabel = addTextObject(this.scene, 8, 19, new Date(data.timestamp).toLocaleString(), TextStyle.WINDOW); this.add(timestampLabel); - const playTimeLabel = addTextObject(this.scene, 8, 33, Utils.getPlayTimeString(data.playTime), TextStyle.WINDOW); + const playTimeLabel = addTextObject(this.scene, 8, 33, Utils.getPlayTimeString(data.playTime) + " " + (getBiomeName(data.arena.biome) == "Construction Site" ? "Construction" : getBiomeName(data.arena.biome)), TextStyle.WINDOW); this.add(playTimeLabel); + console.log(biomePanelIDs[data.arena.biome]) + + if (this.backer && allpanels.includes(biomePanelIDs[data.arena.biome]) && this.scene.doBiomePanels) { + this.backer.setTexture(`${biomePanelIDs[data.arena.biome]}_panel`) + this.backer.setVisible(true) + } + const pokemonIconsContainer = this.scene.add.container(144, 4); data.party.forEach((p: PokemonData, i: integer) => { const iconContainer = this.scene.add.container(26 * i, 0); @@ -329,7 +395,7 @@ class SessionSlot extends Phaser.GameObjects.Container { load(): Promise { return new Promise(resolve => { - this.scene.gameData.getSession(this.slotId).then(async sessionData => { + this.scene.gameData.getSession(this.slotId, this.autoSlot).then(async sessionData => { if (!sessionData) { this.hasData = false; this.loadingLabel.setText(i18next.t("saveSlotSelectUiHandler:empty")); diff --git a/src/ui/starter-select-ui-handler.ts b/src/ui/starter-select-ui-handler.ts index 25d1060f23e..0caf9ef6fa6 100644 --- a/src/ui/starter-select-ui-handler.ts +++ b/src/ui/starter-select-ui-handler.ts @@ -2322,10 +2322,13 @@ export default class StarterSelectUiHandler extends MessageUiHandler { const isValidForChallenge = new Utils.BooleanHolder(true); + const currentPartyValue = this.starterGens.reduce((total: number, gen: number, i: number) => total += this.scene.gameData.getSpeciesStarterValue(this.genSpecies[gen][this.starterCursors[i]].speciesId), 0); + const cursorCost = this.scene.gameData.getSpeciesStarterValue(species.speciesId); + const isValidNextPartyValue = (currentPartyValue + cursorCost) <= this.getValueLimit(); Challenge.applyChallenges(this.scene.gameMode, Challenge.ChallengeType.STARTER_CHOICE, species, isValidForChallenge, this.scene.gameData.getSpeciesDexAttrProps(species, this.dexAttrCursor), this.starterGens.length); const starterSprite = this.starterSelectGenIconContainers[this.getGenCursorWithScroll()].getAt(this.cursor) as Phaser.GameObjects.Sprite; starterSprite.setTexture(species.getIconAtlasKey(formIndex, shiny, variant), species.getIconId(female, formIndex, shiny, variant)); - starterSprite.setAlpha(isValidForChallenge.value ? 1 : 0.375); + starterSprite.setAlpha(isValidForChallenge.value && isValidNextPartyValue ? 1 : 0.375); this.checkIconId((this.starterSelectGenIconContainers[this.getGenCursorWithScroll()].getAt(this.cursor) as Phaser.GameObjects.Sprite), species, female, formIndex, shiny, variant); this.canCycleShiny = !!(dexEntry.caughtAttr & DexAttr.NON_SHINY && dexEntry.caughtAttr & DexAttr.SHINY); this.canCycleGender = !!(dexEntry.caughtAttr & DexAttr.MALE && dexEntry.caughtAttr & DexAttr.FEMALE); diff --git a/src/ui/stats-container.ts b/src/ui/stats-container.ts index b4e799bafc0..f94861cdc4b 100644 --- a/src/ui/stats-container.ts +++ b/src/ui/stats-container.ts @@ -65,9 +65,10 @@ export class StatsContainer extends Phaser.GameObjects.Container { }); } - updateIvs(ivs: integer[], originalIvs?: integer[]): void { + updateIvs(ivs: integer[], originalIvs?: integer[], max?: integer): void { if (ivs) { - const ivChartData = new Array(6).fill(null).map((_, i) => [ (ivs[ivChartStatIndexes[i]] / 31) * ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][0], (ivs[ivChartStatIndexes[i]] / 31) * ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][1] ] ).flat(); + var maxIV = max || 31 + const ivChartData = new Array(6).fill(null).map((_, i) => [ (ivs[ivChartStatIndexes[i]] / maxIV) * ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][0], (ivs[ivChartStatIndexes[i]] / maxIV) * ivChartSize * ivChartStatCoordMultipliers[ivChartStatIndexes[i]][1] ] ).flat(); const lastIvChartData = this.statsIvsCache || defaultIvChartData; const perfectIVColor: string = getTextColor(TextStyle.SUMMARY_GOLD, false, (this.scene as BattleScene).uiTheme); this.statsIvsCache = ivChartData.slice(0); diff --git a/src/ui/ui.ts b/src/ui/ui.ts index ce834a83645..09bf39e8db3 100644 --- a/src/ui/ui.ts +++ b/src/ui/ui.ts @@ -46,6 +46,7 @@ import SettingsDisplayUiHandler from "./settings/settings-display-ui-handler"; import SettingsAudioUiHandler from "./settings/settings-audio-ui-handler"; import { PlayerGender } from "#enums/player-gender"; import BgmBar from "#app/ui/bgm-bar"; +import LogNameFormUiHandler from "./log-name-form-ui-handler"; export enum Mode { MESSAGE, @@ -83,7 +84,9 @@ export enum Mode { SESSION_RELOAD, UNAVAILABLE, OUTDATED, - CHALLENGE_SELECT + CHALLENGE_SELECT, + NAME_LOG, + LOG_HANDLER } const transitionModes = [ @@ -95,7 +98,9 @@ const transitionModes = [ Mode.EGG_HATCH_SCENE, Mode.EGG_LIST, Mode.EGG_GACHA, - Mode.CHALLENGE_SELECT + Mode.CHALLENGE_SELECT, + Mode.NAME_LOG, + Mode.LOG_HANDLER ]; const noTransitionModes = [ @@ -180,7 +185,9 @@ export default class UI extends Phaser.GameObjects.Container { new SessionReloadModalUiHandler(scene), new UnavailableModalUiHandler(scene), new OutdatedModalUiHandler(scene), - new GameChallengesUiHandler(scene) + new GameChallengesUiHandler(scene), + new LogNameFormUiHandler(scene), + new TargetSelectUiHandler(scene) ]; } diff --git a/src/utils.ts b/src/utils.ts index 5aa558bae3a..69584191d5c 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,5 +1,6 @@ import i18next from "i18next"; import { MoneyFormat } from "#enums/money-format"; +import * as LoggerTools from "./logger" export const MissingTextureKey = "__MISSING";