From 8c4f336cd6479ed3e626d28fcb0305201c5309c7 Mon Sep 17 00:00:00 2001 From: Flashfyre Date: Mon, 15 Jan 2024 00:20:26 -0500 Subject: [PATCH] Trainers switch out their Pokemon when at a disadvantage --- src/battle-phases.ts | 44 +++++++++++++++++++++++++++++++++++++++----- src/data/move.ts | 4 ++++ src/pokemon.ts | 2 +- src/trainer.ts | 25 ++++++++++++++++++++++--- 4 files changed, 66 insertions(+), 9 deletions(-) diff --git a/src/battle-phases.ts b/src/battle-phases.ts index 66cabf787..20f3be907 100644 --- a/src/battle-phases.ts +++ b/src/battle-phases.ts @@ -967,7 +967,7 @@ export class SwitchSummonPhase extends SummonPhase { if (!this.player && this.slotIndex === -1) this.slotIndex = this.scene.currentBattle.trainer.getNextSummonIndex(); - if (!this.doReturn || (this.slotIndex !== -1 && !this.scene.getParty()[this.slotIndex])) { + if (!this.doReturn || (this.slotIndex !== -1 && !(this.player ? this.scene.getParty() : this.scene.getEnemyParty())[this.slotIndex])) { this.switchAndSummon(); return; } @@ -975,7 +975,7 @@ export class SwitchSummonPhase extends SummonPhase { const pokemon = this.getPokemon(); if (!this.batonPass) - this.scene.getEnemyField().forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); + (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.removeTagsBySourceId(pokemon.id)); this.scene.ui.showText(this.player ? `Come back, ${pokemon.name}!` : `${this.scene.currentBattle.trainer.getName()}\nwithdrew ${pokemon.name}!`); this.scene.playSound('pb_rel'); @@ -996,11 +996,11 @@ export class SwitchSummonPhase extends SummonPhase { } switchAndSummon() { - const party = this.getParty(); + const party = this.player ? this.getParty() : this.scene.getEnemyParty(); const switchedPokemon = party[this.slotIndex]; this.lastPokemon = this.getPokemon(); if (this.batonPass && switchedPokemon) { - this.scene.getEnemyField().forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedPokemon.id)); + (this.player ? this.scene.getEnemyField() : this.scene.getPlayerField()).forEach(enemyPokemon => enemyPokemon.transferTagsBySourceId(this.lastPokemon.id, switchedPokemon.id)); if (!this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === switchedPokemon.id)) { const batonPassModifier = this.scene.findModifier(m => m instanceof SwitchEffectTransferModifier && (m as SwitchEffectTransferModifier).pokemonId === this.lastPokemon.id) as SwitchEffectTransferModifier; @@ -1427,6 +1427,40 @@ export class EnemyCommandPhase extends FieldPhase { const enemyPokemon = this.scene.getEnemyField()[this.fieldIndex]; + const trainer = this.scene.currentBattle.trainer; + + if (trainer) { + const opponents = enemyPokemon.getOpponents(); + + const trapTag = enemyPokemon.findTag(t => t instanceof TrappedTag) as TrappedTag; + const trapped = new Utils.BooleanHolder(false); + opponents.forEach(playerPokemon => applyCheckTrappedAbAttrs(CheckTrappedAbAttr, playerPokemon, trapped)); + if (enemyPokemon.moveset.find(m => m.moveId === Moves.BATON_PASS && m.isUsable(enemyPokemon)) && (enemyPokemon.summonData.battleStats.reduce((total, stat) => total += stat, 0) >= 0 || trapTag || trapped.value)) { + this.scene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = + { command: Command.FIGHT, move: { move: Moves.BATON_PASS, targets: enemyPokemon.getNextTargets(Moves.BATON_PASS) } }; + + return this.end(); + } else if (!trapTag && !trapped.value) { + const partyMemberScores = trainer.getPartyMemberMatchupScores(); + + if (partyMemberScores.length) { + const matchupScores = opponents.map(opp => enemyPokemon.getMatchupScore(opp)); + const matchupScore = matchupScores.reduce((total, score) => total += score, 0) / matchupScores.length; + + const sortedPartyMemberScores = trainer.getSortedPartyMemberMatchupScores(partyMemberScores); + + if (sortedPartyMemberScores[0][1] >= matchupScore * (trainer.config.isBoss ? 2 : 3)) { + const index = trainer.getNextSummonIndex(partyMemberScores); + + this.scene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = + { command: Command.POKEMON, cursor: index, args: [ false ] }; + + return this.end(); + } + } + } + } + const nextMove = enemyPokemon.getNextMove(); this.scene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = @@ -1527,7 +1561,7 @@ export class TurnStartPhase extends FieldPhase { case Command.RUN: const isSwitch = turnCommand.command === Command.POKEMON; if (isSwitch) - this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), turnCommand.cursor, true, turnCommand.args[0] as boolean)); + this.scene.unshiftPhase(new SwitchSummonPhase(this.scene, pokemon.getFieldIndex(), turnCommand.cursor, true, turnCommand.args[0] as boolean, pokemon.isPlayer())); else this.scene.unshiftPhase(new AttemptRunPhase(this.scene, pokemon.getFieldIndex())); break; diff --git a/src/data/move.ts b/src/data/move.ts index e2c1808f1..ee8944728 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -1737,6 +1737,10 @@ export class ForceSwitchOutAttr extends MoveEffectAttr { return (!player && !user.scene.currentBattle.battleType) || party.filter(p => !p.isFainted()).length > user.scene.currentBattle.getBattlerCount(); }; } + + getUserBenefitScore(user: Pokemon, target: Pokemon, move: Move): number { + return -100; // Overridden in switch logic + } } export class CopyTypeAttr extends MoveEffectAttr { diff --git a/src/pokemon.ts b/src/pokemon.ts index 0cace02c0..3584cb613 100644 --- a/src/pokemon.ts +++ b/src/pokemon.ts @@ -902,7 +902,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (this.battleInfo.visible) { this.scene.tweens.add({ targets: this.battleInfo, - x: this.isPlayer() ? '+=150' : `-=${!this.isBoss() ? 150 : 198}`, + x: this.isPlayer() ? '+=150' : `-=${!this.isBoss() ? 150 : 246}`, duration: 500, ease: 'Sine.easeIn', onComplete: () => { diff --git a/src/trainer.ts b/src/trainer.ts index 4097a608e..2d9257827 100644 --- a/src/trainer.ts +++ b/src/trainer.ts @@ -198,21 +198,27 @@ export default class Trainer extends Phaser.GameObjects.Container { return ret; } - getNextSummonIndex(): integer { + getPartyMemberMatchupScores(): [integer, integer][] { const party = this.scene.getEnemyParty(); const nonFaintedPartyMembers = party.slice(this.scene.currentBattle.getBattlerCount()).filter(p => !p.isFainted()); const partyMemberScores = nonFaintedPartyMembers.map(p => { const playerField = this.scene.getPlayerField(); let score = 0; + let ret: [integer, integer]; for (let playerPokemon of playerField) { score += p.getMatchupScore(playerPokemon); if (playerPokemon.species.legendary) score /= 2; } score /= playerField.length; - return [ party.indexOf(p), score ]; + ret = [ party.indexOf(p), score ]; + return ret; }); + return partyMemberScores; + } + + getSortedPartyMemberMatchupScores(partyMemberScores: [integer, integer][] = this.getPartyMemberMatchupScores()) { const sortedPartyMemberScores = partyMemberScores.slice(0); sortedPartyMemberScores.sort((a, b) => { const scoreA = a[1]; @@ -220,8 +226,21 @@ export default class Trainer extends Phaser.GameObjects.Container { return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0; }); + return sortedPartyMemberScores; + } + + getNextSummonIndex(partyMemberScores: [integer, integer][] = this.getPartyMemberMatchupScores()): integer { + const sortedPartyMemberScores = this.getSortedPartyMemberMatchupScores(partyMemberScores); + const maxScorePartyMemberIndexes = partyMemberScores.filter(pms => pms[1] === sortedPartyMemberScores[0][1]).map(pms => pms[0]); - return maxScorePartyMemberIndexes[Utils.randSeedInt(maxScorePartyMemberIndexes.length)]; + + if (maxScorePartyMemberIndexes.length > 1) { + let rand: integer; + this.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(maxScorePartyMemberIndexes.length), this.scene.currentBattle.turn << 2); + return maxScorePartyMemberIndexes[rand]; + } + + return maxScorePartyMemberIndexes[0]; } getPartyMemberModifierChanceMultiplier(index: integer): number {