Complete basic implementation of Tera

This commit is contained in:
Xavion3 2025-02-02 02:50:48 +11:00
parent e2d0939050
commit 5097b21851
29 changed files with 1044 additions and 285 deletions

View File

@ -0,0 +1,709 @@
{
"graphic": "terastallize",
"frames": [
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 0,
"opacity": 150,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 0,
"opacity": 225,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 70,
"zoomY": 70,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 70,
"zoomY": 70,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 90,
"zoomY": 90,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 90,
"zoomY": 90,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 255,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 200,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [100,100,100,0],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 100,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [100,100,100,0],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 100,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [100,100,100,0],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 60,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [100,100,100,0],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 60,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [100,100,100,0],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1
},
{
"x": 0,
"y": -20,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 2,
"graphicFrame": 1,
"opacity": 60,
"priority": 1,
"focus": 2}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [100,100,100,0],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [255,255,255,255],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [255,255,255,0],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1}
],
[
{
"x": 0,
"y": 0,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 0,
"graphicFrame": 0,
"opacity": 255,
"tone": [255,255,255,255],
"locked": true,
"priority": 1,
"focus": 2
},
{
"x": 128,
"y": -64,
"zoomX": 100,
"zoomY": 100,
"visible": true,
"target": 1,
"graphicFrame": 0,
"opacity": 255,
"locked": true,
"priority": 1,
"focus": 1}
]
],
"position": 4,
"hue": 0
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -1 +1 @@
Subproject commit 5ef993b95fa8248adc0fb7d9489baccf546bf8e3 Subproject commit a7a986de64c383671854d9cc5de97d03daac02fa

View File

@ -9,7 +9,7 @@ import type { Constructor } from "#app/utils";
import { isNullOrUndefined, randSeedInt } from "#app/utils"; import { isNullOrUndefined, randSeedInt } from "#app/utils";
import * as Utils from "#app/utils"; import * as Utils from "#app/utils";
import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier"; import type { Modifier, ModifierPredicate, TurnHeldItemTransferModifier } from "./modifier/modifier";
import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, ModifierBar, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier, TerastallizeModifier } from "./modifier/modifier"; import { ConsumableModifier, ConsumablePokemonModifier, DoubleBattleChanceBoosterModifier, ExpBalanceModifier, ExpShareModifier, FusePokemonModifier, HealingBoosterModifier, ModifierBar, MultipleParticipantExpBonusModifier, PersistentModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, RememberMoveModifier } from "./modifier/modifier";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims"; import { initCommonAnims, initMoveAnim, loadCommonAnimAssets, loadMoveAnimAssets, populateAnims } from "#app/data/battle-anims";
import type { Phase } from "#app/phase"; import type { Phase } from "#app/phase";
@ -1655,7 +1655,7 @@ export default class BattleScene extends SceneBase {
} }
initPokemonSprite(sprite: Phaser.GameObjects.Sprite, pokemon?: Pokemon, hasShadow: boolean = false, ignoreOverride: boolean = false): Phaser.GameObjects.Sprite { initPokemonSprite(sprite: Phaser.GameObjects.Sprite, pokemon?: Pokemon, hasShadow: boolean = false, ignoreOverride: boolean = false): Phaser.GameObjects.Sprite {
sprite.setPipeline(this.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: hasShadow, ignoreOverride: ignoreOverride, teraColor: pokemon ? getTypeRgb(pokemon.getTeraType()) : undefined }); sprite.setPipeline(this.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: hasShadow, ignoreOverride: ignoreOverride, teraColor: pokemon ? getTypeRgb(pokemon.getTeraType()) : undefined, isTerastallized: pokemon ? pokemon.isTerastallized : false });
this.spriteSparkleHandler.add(sprite); this.spriteSparkleHandler.add(sprite);
return sprite; return sprite;
} }
@ -2574,11 +2574,8 @@ export default class BattleScene extends SceneBase {
const modifiersToRemove: PersistentModifier[] = []; const modifiersToRemove: PersistentModifier[] = [];
const modifierPromises: Promise<boolean>[] = []; const modifierPromises: Promise<boolean>[] = [];
if (modifier instanceof PersistentModifier) { if (modifier instanceof PersistentModifier) {
if (modifier instanceof TerastallizeModifier) {
modifiersToRemove.push(...(this.findModifiers(m => m instanceof TerastallizeModifier && m.pokemonId === modifier.pokemonId)));
}
if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) { if ((modifier as PersistentModifier).add(this.modifiers, !!virtual)) {
if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { if (modifier instanceof PokemonFormChangeItemModifier) {
const pokemon = this.getPokemonById(modifier.pokemonId); const pokemon = this.getPokemonById(modifier.pokemonId);
if (pokemon) { if (pokemon) {
success = modifier.apply(pokemon, true); success = modifier.apply(pokemon, true);
@ -2655,11 +2652,8 @@ export default class BattleScene extends SceneBase {
addEnemyModifier(modifier: PersistentModifier, ignoreUpdate?: boolean, instant?: boolean): Promise<void> { addEnemyModifier(modifier: PersistentModifier, ignoreUpdate?: boolean, instant?: boolean): Promise<void> {
return new Promise(resolve => { return new Promise(resolve => {
const modifiersToRemove: PersistentModifier[] = []; const modifiersToRemove: PersistentModifier[] = [];
if (modifier instanceof TerastallizeModifier) {
modifiersToRemove.push(...(this.findModifiers(m => m instanceof TerastallizeModifier && m.pokemonId === modifier.pokemonId, false)));
}
if ((modifier as PersistentModifier).add(this.enemyModifiers, false)) { if ((modifier as PersistentModifier).add(this.enemyModifiers, false)) {
if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { if (modifier instanceof PokemonFormChangeItemModifier) {
const pokemon = this.getPokemonById(modifier.pokemonId); const pokemon = this.getPokemonById(modifier.pokemonId);
if (pokemon) { if (pokemon) {
modifier.apply(pokemon, true); modifier.apply(pokemon, true);
@ -2783,6 +2777,8 @@ export default class BattleScene extends SceneBase {
for (const modifier of modifiers) { for (const modifier of modifiers) {
this.addEnemyModifier(modifier, true, true); this.addEnemyModifier(modifier, true, true);
} }
this.currentBattle.trainer.genAI(party);
} }
party.forEach((enemyPokemon: EnemyPokemon, i: integer) => { party.forEach((enemyPokemon: EnemyPokemon, i: integer) => {
@ -2914,7 +2910,7 @@ export default class BattleScene extends SceneBase {
const modifierIndex = modifiers.indexOf(modifier); const modifierIndex = modifiers.indexOf(modifier);
if (modifierIndex > -1) { if (modifierIndex > -1) {
modifiers.splice(modifierIndex, 1); modifiers.splice(modifierIndex, 1);
if (modifier instanceof PokemonFormChangeItemModifier || modifier instanceof TerastallizeModifier) { if (modifier instanceof PokemonFormChangeItemModifier) {
const pokemon = this.getPokemonById(modifier.pokemonId); const pokemon = this.getPokemonById(modifier.pokemonId);
if (pokemon) { if (pokemon) {
modifier.apply(pokemon, false); modifier.apply(pokemon, false);
@ -3115,7 +3111,8 @@ export default class BattleScene extends SceneBase {
name: p.name, name: p.name,
form: p.getFormKey(), form: p.getFormKey(),
types: p.getTypes().map((type) => Type[type]), types: p.getTypes().map((type) => Type[type]),
teraType: p.getTeraType() !== Type.UNKNOWN ? Type[p.getTeraType()] : "", teraType: Type[p.getTeraType()],
isTerastallized: p.isTerastallized,
level: p.level, level: p.level,
currentHP: p.hp, currentHP: p.hp,
maxHP: p.getMaxHp(), maxHP: p.getMaxHp(),

View File

@ -68,7 +68,6 @@ export interface TurnCommand {
targets?: BattlerIndex[]; targets?: BattlerIndex[];
skip?: boolean; skip?: boolean;
args?: any[]; args?: any[];
preturnCommands?: Command[];
} }
export interface FaintLogEntry { export interface FaintLogEntry {
@ -93,6 +92,7 @@ export default class Battle {
public started: boolean = false; public started: boolean = false;
public enemySwitchCounter: number = 0; public enemySwitchCounter: number = 0;
public turn: number = 0; public turn: number = 0;
public preTurnCommands: TurnCommands;
public turnCommands: TurnCommands; public turnCommands: TurnCommands;
public playerParticipantIds: Set<number> = new Set<number>(); public playerParticipantIds: Set<number> = new Set<number>();
public battleScore: number = 0; public battleScore: number = 0;
@ -176,6 +176,7 @@ export default class Battle {
incrementTurn(): void { incrementTurn(): void {
this.turn++; this.turn++;
this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ])); this.turnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ]));
this.preTurnCommands = Object.fromEntries(Utils.getEnumValues(BattlerIndex).map(bt => [ bt, null ]));
this.battleSeedState = null; this.battleSeedState = null;
} }

View File

@ -239,39 +239,27 @@ export class PostBattleInitFormChangeAbAttr extends PostBattleInitAbAttr {
} }
} }
export class PostBattleInitStatStageChangeAbAttr extends PostBattleInitAbAttr { export class PostTeraFormChangeStatChangeAbAttr extends AbAttr {
private stats: BattleStat[]; private stats: BattleStat[];
private stages: number; private stages: number;
private selfTarget: boolean;
constructor(stats: BattleStat[], stages: number, selfTarget?: boolean) { constructor(stats: BattleStat[], stages: number) {
super(); super();
this.stats = stats; this.stats = stats;
this.stages = stages; this.stages = stages;
this.selfTarget = !!selfTarget;
} }
applyPostBattleInit(pokemon: Pokemon, passive: boolean, simulated: boolean, args: any[]): boolean { apply(pokemon: Pokemon, passive: boolean, simulated: boolean, cancelled: Utils.BooleanHolder | null, args: any[]): boolean | Promise<boolean> {
const statStageChangePhases: StatStageChangePhase[] = []; const statStageChangePhases: StatStageChangePhase[] = [];
if (!simulated) { if (!simulated) {
if (this.selfTarget) {
statStageChangePhases.push(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages)); statStageChangePhases.push(new StatStageChangePhase(pokemon.getBattlerIndex(), true, this.stats, this.stages));
} else {
for (const opponent of pokemon.getOpponents()) {
statStageChangePhases.push(new StatStageChangePhase(opponent.getBattlerIndex(), false, this.stats, this.stages));
}
}
for (const statStageChangePhase of statStageChangePhases) { for (const statStageChangePhase of statStageChangePhases) {
if (!this.selfTarget && !statStageChangePhase.getPokemon()?.summonData) {
globalScene.pushPhase(statStageChangePhase);
} else { // TODO: This causes the ability bar to be shown at the wrong time
globalScene.unshiftPhase(statStageChangePhase); globalScene.unshiftPhase(statStageChangePhase);
} }
} }
}
return true; return true;
} }
@ -6348,29 +6336,25 @@ export function initAbilities() {
new Ability(Abilities.TOXIC_CHAIN, 9) new Ability(Abilities.TOXIC_CHAIN, 9)
.attr(PostAttackApplyStatusEffectAbAttr, false, 30, StatusEffect.TOXIC), .attr(PostAttackApplyStatusEffectAbAttr, false, 30, StatusEffect.TOXIC),
new Ability(Abilities.EMBODY_ASPECT_TEAL, 9) new Ability(Abilities.EMBODY_ASPECT_TEAL, 9)
.attr(PostBattleInitStatStageChangeAbAttr, [ Stat.SPD ], 1, true) .attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.SPD ], 1)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr),
.partial(), // Ogerpon tera interactions
new Ability(Abilities.EMBODY_ASPECT_WELLSPRING, 9) new Ability(Abilities.EMBODY_ASPECT_WELLSPRING, 9)
.attr(PostBattleInitStatStageChangeAbAttr, [ Stat.SPDEF ], 1, true) .attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.SPDEF ], 1)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr),
.partial(), // Ogerpon tera interactions
new Ability(Abilities.EMBODY_ASPECT_HEARTHFLAME, 9) new Ability(Abilities.EMBODY_ASPECT_HEARTHFLAME, 9)
.attr(PostBattleInitStatStageChangeAbAttr, [ Stat.ATK ], 1, true) .attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.ATK ], 1)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr),
.partial(), // Ogerpon tera interactions
new Ability(Abilities.EMBODY_ASPECT_CORNERSTONE, 9) new Ability(Abilities.EMBODY_ASPECT_CORNERSTONE, 9)
.attr(PostBattleInitStatStageChangeAbAttr, [ Stat.DEF ], 1, true) .attr(PostTeraFormChangeStatChangeAbAttr, [ Stat.DEF ], 1)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)
.attr(UnswappableAbilityAbAttr) .attr(UnswappableAbilityAbAttr)
.attr(NoTransformAbilityAbAttr) .attr(NoTransformAbilityAbAttr),
.partial(), // Ogerpon tera interactions
new Ability(Abilities.TERA_SHIFT, 9) new Ability(Abilities.TERA_SHIFT, 9)
.attr(PostSummonFormChangeAbAttr, p => p.getFormKey() ? 0 : 1) .attr(PostSummonFormChangeAbAttr, p => p.getFormKey() ? 0 : 1)
.attr(UncopiableAbilityAbAttr) .attr(UncopiableAbilityAbAttr)

View File

@ -56,6 +56,7 @@ export enum ChargeAnim {
export enum CommonAnim { export enum CommonAnim {
USE_ITEM = 2000, USE_ITEM = 2000,
HEALTH_UP, HEALTH_UP,
TERASTALLIZE,
POISON = 2010, POISON = 2010,
TOXIC, TOXIC,
PARALYSIS, PARALYSIS,

View File

@ -779,7 +779,7 @@ export default class Move implements Localizable {
applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, null, typeChangeMovePowerMultiplier); applyPreAttackAbAttrs(MoveTypeChangeAbAttr, source, target, this, true, null, typeChangeMovePowerMultiplier);
const sourceTeraType = source.getTeraType(); const sourceTeraType = source.getTeraType();
if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) { if (source.isTerastallized && sourceTeraType === this.type && power.value < 60 && this.priority <= 0 && !this.hasAttr(MultiHitAttr) && !globalScene.findModifier(m => m instanceof PokemonMultiHitModifier && m.pokemonId === source.id)) {
power.value = 60; power.value = 60;
} }

View File

@ -594,7 +594,7 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
console.error(`Failed to play animation for ${spriteKey}`, err); console.error(`Failed to play animation for ${spriteKey}`, err);
} }
sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) }); sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()), isTerastallized: tradedPokemon.isTerastallized });
sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey()); sprite.setPipelineData("spriteKey", tradedPokemon.getSpriteKey());
sprite.setPipelineData("shiny", tradedPokemon.shiny); sprite.setPipelineData("shiny", tradedPokemon.shiny);
@ -615,7 +615,7 @@ function doPokemonTradeSequence(tradedPokemon: PlayerPokemon, receivedPokemon: P
console.error(`Failed to play animation for ${spriteKey}`, err); console.error(`Failed to play animation for ${spriteKey}`, err);
} }
sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()) }); sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(tradedPokemon.getTeraType()), isTerastallized: tradedPokemon.isTerastallized });
sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey()); sprite.setPipelineData("spriteKey", receivedPokemon.getSpriteKey());
sprite.setPipelineData("shiny", receivedPokemon.shiny); sprite.setPipelineData("shiny", receivedPokemon.shiny);

View File

@ -61,7 +61,7 @@ export function doPokemonTransformationSequence(previousPokemon: PlayerPokemon,
console.error(`Failed to play animation for ${spriteKey}`, err); console.error(`Failed to play animation for ${spriteKey}`, err);
} }
sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()) }); sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(previousPokemon.getTeraType()), isTerastallized: previousPokemon.isTerastallized });
sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey()); sprite.setPipelineData("spriteKey", previousPokemon.getSpriteKey());
sprite.setPipelineData("shiny", previousPokemon.shiny); sprite.setPipelineData("shiny", previousPokemon.shiny);

View File

@ -1,8 +1,7 @@
import { PokemonFormChangeItemModifier, TerastallizeModifier } from "../modifier/modifier"; import { PokemonFormChangeItemModifier } from "../modifier/modifier";
import type Pokemon from "../field/pokemon"; import type Pokemon from "../field/pokemon";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { MoveCategory, allMoves } from "./move"; import { MoveCategory, allMoves } from "./move";
import { Type } from "#enums/type";
import type { Constructor, nil } from "#app/utils"; import type { Constructor, nil } from "#app/utils";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { Moves } from "#enums/moves"; import { Moves } from "#enums/moves";
@ -398,23 +397,7 @@ export class SpeciesDefaultFormMatchTrigger extends SpeciesFormChangeTrigger {
* @extends SpeciesFormChangeTrigger * @extends SpeciesFormChangeTrigger
*/ */
export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger { export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger {
/** The Tera type that triggers the form change */ description = i18next.t("pokemonEvolutions:Forms.tera" );
private teraType: Type;
constructor(teraType: Type) {
super();
this.teraType = teraType;
this.description = i18next.t("pokemonEvolutions:Forms.tera", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) });
}
/**
* Checks if the associated Pokémon has the required Tera Shard that matches with the associated Tera type.
* @param {Pokemon} pokemon the Pokémon that is trying to do the form change
* @returns `true` if the Pokémon can change forms, `false` otherwise
*/
canChange(pokemon: Pokemon): boolean {
return !!globalScene.findModifier(m => m instanceof TerastallizeModifier && m.pokemonId === pokemon.id && m.teraType === this.teraType);
}
} }
/** /**
@ -424,10 +407,6 @@ export class SpeciesFormChangeTeraTrigger extends SpeciesFormChangeTrigger {
*/ */
export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger { export class SpeciesFormChangeLapseTeraTrigger extends SpeciesFormChangeTrigger {
description = i18next.t("pokemonEvolutions:Forms.teraLapse"); description = i18next.t("pokemonEvolutions:Forms.teraLapse");
canChange(pokemon: Pokemon): boolean {
return !!globalScene.findModifier(m => m instanceof TerastallizeModifier && m.pokemonId === pokemon.id);
}
} }
/** /**
@ -991,19 +970,19 @@ export const pokemonFormChanges: PokemonFormChanges = {
new SpeciesFormChange(Species.OGERPON, "teal-mask", "wellspring-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.WELLSPRING_MASK)), new SpeciesFormChange(Species.OGERPON, "teal-mask", "wellspring-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.WELLSPRING_MASK)),
new SpeciesFormChange(Species.OGERPON, "teal-mask", "hearthflame-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.HEARTHFLAME_MASK)), new SpeciesFormChange(Species.OGERPON, "teal-mask", "hearthflame-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.HEARTHFLAME_MASK)),
new SpeciesFormChange(Species.OGERPON, "teal-mask", "cornerstone-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.CORNERSTONE_MASK)), new SpeciesFormChange(Species.OGERPON, "teal-mask", "cornerstone-mask", new SpeciesFormChangeItemTrigger(FormChangeItem.CORNERSTONE_MASK)),
new SpeciesFormChange(Species.OGERPON, "teal-mask", "teal-mask-tera", new SpeciesFormChangeTeraTrigger(Type.GRASS)), new SpeciesFormChange(Species.OGERPON, "teal-mask", "teal-mask-tera", new SpeciesFormChangeTeraTrigger(), true),
new SpeciesFormChange(Species.OGERPON, "teal-mask-tera", "teal-mask", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.GRASS)), new SpeciesFormChange(Species.OGERPON, "teal-mask-tera", "teal-mask", new SpeciesFormChangeLapseTeraTrigger(), true),
new SpeciesFormChange(Species.OGERPON, "wellspring-mask", "wellspring-mask-tera", new SpeciesFormChangeTeraTrigger(Type.WATER)), new SpeciesFormChange(Species.OGERPON, "wellspring-mask", "wellspring-mask-tera", new SpeciesFormChangeTeraTrigger(), true),
new SpeciesFormChange(Species.OGERPON, "wellspring-mask-tera", "wellspring-mask", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.WATER)), new SpeciesFormChange(Species.OGERPON, "wellspring-mask-tera", "wellspring-mask", new SpeciesFormChangeLapseTeraTrigger(), true),
new SpeciesFormChange(Species.OGERPON, "hearthflame-mask", "hearthflame-mask-tera", new SpeciesFormChangeTeraTrigger(Type.FIRE)), new SpeciesFormChange(Species.OGERPON, "hearthflame-mask", "hearthflame-mask-tera", new SpeciesFormChangeTeraTrigger(), true),
new SpeciesFormChange(Species.OGERPON, "hearthflame-mask-tera", "hearthflame-mask", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.FIRE)), new SpeciesFormChange(Species.OGERPON, "hearthflame-mask-tera", "hearthflame-mask", new SpeciesFormChangeLapseTeraTrigger(), true),
new SpeciesFormChange(Species.OGERPON, "cornerstone-mask", "cornerstone-mask-tera", new SpeciesFormChangeTeraTrigger(Type.ROCK)), new SpeciesFormChange(Species.OGERPON, "cornerstone-mask", "cornerstone-mask-tera", new SpeciesFormChangeTeraTrigger(), true),
new SpeciesFormChange(Species.OGERPON, "cornerstone-mask-tera", "cornerstone-mask", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.ROCK)) new SpeciesFormChange(Species.OGERPON, "cornerstone-mask-tera", "cornerstone-mask", new SpeciesFormChangeLapseTeraTrigger(), true)
], ],
[Species.TERAPAGOS]: [ [Species.TERAPAGOS]: [
new SpeciesFormChange(Species.TERAPAGOS, "", "terastal", new SpeciesFormChangeAbilityTrigger(), true), new SpeciesFormChange(Species.TERAPAGOS, "", "terastal", new SpeciesFormChangeAbilityTrigger(), true),
new SpeciesFormChange(Species.TERAPAGOS, "terastal", "stellar", new SpeciesFormChangeTeraTrigger(Type.STELLAR)), new SpeciesFormChange(Species.TERAPAGOS, "terastal", "stellar", new SpeciesFormChangeTeraTrigger(), true),
new SpeciesFormChange(Species.TERAPAGOS, "stellar", "terastal", new SpeciesFormChangeLapseTeraTrigger(), true, new SpeciesFormChangeCondition(p => p.getTeraType() !== Type.STELLAR)) new SpeciesFormChange(Species.TERAPAGOS, "stellar", "terastal", new SpeciesFormChangeLapseTeraTrigger(), true)
], ],
[Species.GALAR_DARMANITAN]: [ [Species.GALAR_DARMANITAN]: [
new SpeciesFormChange(Species.GALAR_DARMANITAN, "", "zen", new SpeciesFormChangeAbilityTrigger(), true), new SpeciesFormChange(Species.GALAR_DARMANITAN, "", "zen", new SpeciesFormChangeAbilityTrigger(), true),

View File

@ -177,11 +177,51 @@ export const trainerPartyTemplates = {
type PartyTemplateFunc = () => TrainerPartyTemplate; type PartyTemplateFunc = () => TrainerPartyTemplate;
type PartyMemberFunc = (level: integer, strength: PartyMemberStrength) => EnemyPokemon; type PartyMemberFunc = (level: integer, strength: PartyMemberStrength) => EnemyPokemon;
type GenModifiersFunc = (party: EnemyPokemon[]) => PersistentModifier[]; type GenModifiersFunc = (party: EnemyPokemon[]) => PersistentModifier[];
type GenAIFunc = (party: EnemyPokemon[]) => void;
export interface PartyMemberFuncs { export interface PartyMemberFuncs {
[key: integer]: PartyMemberFunc [key: integer]: PartyMemberFunc
} }
export enum TeraAIMode {
NO_TERA,
INSTANT_TERA,
SMART_TERA
}
/**
* Stores data and helper functions about a trainers AI options.
*/
export class TrainerAI {
public teraMode: TeraAIMode = TeraAIMode.NO_TERA;
public instantTeras: number[];
/**
* @param canTerastallize Whether this trainer is allowed to tera
*/
constructor(teraMode: TeraAIMode = TeraAIMode.NO_TERA) {
this.teraMode = teraMode;
this.instantTeras = [];
}
/**
* Checks if a trainer can tera
* @returns Whether this trainer can currently tera
*/
public canTerastallize() {
return this.teraMode !== TeraAIMode.NO_TERA;
}
/**
* Sets a pokemon on this AI to just instantly tera on first move used
* @param index The index of the pokemon to instantly tera
*/
public setInstantTera(index: number) {
this.teraMode = TeraAIMode.INSTANT_TERA;
this.instantTeras.push(index);
}
}
export class TrainerConfig { export class TrainerConfig {
public trainerType: TrainerType; public trainerType: TrainerType;
public trainerTypeDouble: TrainerType; public trainerTypeDouble: TrainerType;
@ -205,6 +245,7 @@ export class TrainerConfig {
public doubleEncounterBgm: string; public doubleEncounterBgm: string;
public victoryBgm: string; public victoryBgm: string;
public genModifiersFunc: GenModifiersFunc; public genModifiersFunc: GenModifiersFunc;
public genAIFuncs: GenAIFunc[] = [];
public modifierRewardFuncs: ModifierTypeFunc[] = []; public modifierRewardFuncs: ModifierTypeFunc[] = [];
public partyTemplates: TrainerPartyTemplate[]; public partyTemplates: TrainerPartyTemplate[];
public partyTemplateFunc: PartyTemplateFunc; public partyTemplateFunc: PartyTemplateFunc;
@ -214,6 +255,7 @@ export class TrainerConfig {
public speciesFilter: PokemonSpeciesFilter; public speciesFilter: PokemonSpeciesFilter;
public specialtyTypes: Type[] = []; public specialtyTypes: Type[] = [];
public hasVoucher: boolean = false; public hasVoucher: boolean = false;
public trainerAI: TrainerAI;
public encounterMessages: string[] = []; public encounterMessages: string[] = [];
public victoryMessages: string[] = []; public victoryMessages: string[] = [];
@ -229,6 +271,7 @@ export class TrainerConfig {
constructor(trainerType: TrainerType, allowLegendaries?: boolean) { constructor(trainerType: TrainerType, allowLegendaries?: boolean) {
this.trainerType = trainerType; this.trainerType = trainerType;
this.trainerAI = new TrainerAI();
this.name = Utils.toReadableString(TrainerType[this.getDerivedType()]); this.name = Utils.toReadableString(TrainerType[this.getDerivedType()]);
this.battleBgm = "battle_trainer"; this.battleBgm = "battle_trainer";
this.mixedBattleBgm = "battle_trainer"; this.mixedBattleBgm = "battle_trainer";
@ -552,6 +595,37 @@ export class TrainerConfig {
return this; return this;
} }
setRandomTeraModifiers(count: () => integer): TrainerConfig {
this.genAIFuncs.push((party: EnemyPokemon[]) => {
const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i);
for (let t = 0; t < Math.min(count(), party.length); t++) {
const randomIndex = Utils.randSeedItem(partyMemberIndexes);
partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1);
if (this.specialtyTypes?.length) {
party[randomIndex].teraType = Utils.randSeedItem(this.specialtyTypes);
}
this.trainerAI.setInstantTera(randomIndex);
}
});
return this;
}
setInstantTera(index: integer): TrainerConfig {
this.trainerAI.setInstantTera(index);
return this;
}
// function getRandomTeraModifiers(party: EnemyPokemon[], count: integer, types?: Type[]): PersistentModifier[] {
// const ret: PersistentModifier[] = [];
// const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i);
// for (let t = 0; t < Math.min(count, party.length); t++) {
// const randomIndex = Utils.randSeedItem(partyMemberIndexes);
// partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1);
// ret.push(modifierTypes.TERA_SHARD().generateType([], [ Utils.randSeedItem(types ? types : party[randomIndex].getTypes()) ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(party[randomIndex]) as PersistentModifier); // TODO: is the bang correct?
// }
// return ret;
// }
setEventModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig { setEventModifierRewardFuncs(...modifierTypeFuncs: (() => ModifierTypeFunc)[]): TrainerConfig {
this.eventRewardFuncs = modifierTypeFuncs.map(func => () => { this.eventRewardFuncs = modifierTypeFuncs.map(func => () => {
const modifierTypeFunc = func(); const modifierTypeFunc = func();
@ -846,10 +920,7 @@ export class TrainerConfig {
this.setHasVoucher(true); this.setHasVoucher(true);
this.setBattleBgm("battle_unova_gym"); this.setBattleBgm("battle_unova_gym");
this.setVictoryBgm("victory_gym"); this.setVictoryBgm("victory_gym");
this.setGenModifiersFunc(party => { this.setRandomTeraModifiers(() => globalScene.currentBattle.waveIndex >= 100 ? 1 : 0);
const waveIndex = globalScene.currentBattle.waveIndex;
return getRandomTeraModifiers(party, waveIndex >= 100 ? 1 : 0, specialtyTypes.length ? specialtyTypes : undefined);
});
return this; return this;
} }
@ -905,7 +976,7 @@ export class TrainerConfig {
this.setHasVoucher(true); this.setHasVoucher(true);
this.setBattleBgm("battle_unova_elite"); this.setBattleBgm("battle_unova_elite");
this.setVictoryBgm("victory_gym"); this.setVictoryBgm("victory_gym");
this.setGenModifiersFunc(party => getRandomTeraModifiers(party, 2, specialtyTypes.length ? specialtyTypes : undefined)); this.setRandomTeraModifiers(() => 2);
return this; return this;
} }
@ -955,7 +1026,7 @@ export class TrainerConfig {
this.setHasVoucher(true); this.setHasVoucher(true);
this.setBattleBgm("battle_champion_alder"); this.setBattleBgm("battle_champion_alder");
this.setVictoryBgm("victory_champion"); this.setVictoryBgm("victory_champion");
this.setGenModifiersFunc(party => getRandomTeraModifiers(party, 3)); this.setRandomTeraModifiers(() => 3);
return this; return this;
} }
@ -1205,17 +1276,6 @@ function getSpeciesFilterRandomPartyMemberFunc(
}; };
} }
function getRandomTeraModifiers(party: EnemyPokemon[], count: integer, types?: Type[]): PersistentModifier[] {
const ret: PersistentModifier[] = [];
const partyMemberIndexes = new Array(party.length).fill(null).map((_, i) => i);
for (let t = 0; t < Math.min(count, party.length); t++) {
const randomIndex = Utils.randSeedItem(partyMemberIndexes);
partyMemberIndexes.splice(partyMemberIndexes.indexOf(randomIndex), 1);
ret.push(modifierTypes.TERA_SHARD().generateType([], [ Utils.randSeedItem(types ? types : party[randomIndex].getTypes()) ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(party[randomIndex]) as PersistentModifier); // TODO: is the bang correct?
}
return ret;
}
type SignatureSpecies = { type SignatureSpecies = {
[key in string]: (Species | Species[])[]; [key in string]: (Species | Species[])[];
}; };
@ -1878,19 +1938,20 @@ export const trainerConfigs: TrainerConfigs = {
.setSpeciesFilter(species => species.baseTotal >= 540), .setSpeciesFilter(species => species.baseTotal >= 540),
[TrainerType.RIVAL_4]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(1.75).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_2").setMixedBattleBgm("battle_rival_2").setPartyTemplates(trainerPartyTemplates.RIVAL_4) [TrainerType.RIVAL_4]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(1.75).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_2").setMixedBattleBgm("battle_rival_2").setPartyTemplates(trainerPartyTemplates.RIVAL_4)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true,
(p => p.abilityIndex = 0))) (p => {
p.abilityIndex = 0;
p.teraType = p.species.type1;
})))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true)) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true))
.setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450))
.setSpeciesFilter(species => species.baseTotal >= 540) .setSpeciesFilter(species => species.baseTotal >= 540)
.setGenModifiersFunc(party => { .setInstantTera(0),
const starter = party[0];
return [ modifierTypes.TERA_SHARD().generateType([], [ starter.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier ]; // TODO: is the bang correct?
}),
[TrainerType.RIVAL_5]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_5) [TrainerType.RIVAL_5]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(2.25).setEncounterBgm(TrainerType.RIVAL).setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_5)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true,
p => { p => {
p.setBoss(true, 2); p.setBoss(true, 2);
p.abilityIndex = 0; p.abilityIndex = 0;
p.teraType = p.species.type1;
})) }))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true)) .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true))
.setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450)) .setPartyMemberFunc(2, getSpeciesFilterRandomPartyMemberFunc((species: PokemonSpecies) => !pokemonEvolutions.hasOwnProperty(species.speciesId) && !pokemonPrevolutions.hasOwnProperty(species.speciesId) && species.baseTotal >= 450))
@ -1901,15 +1962,13 @@ export const trainerConfigs: TrainerConfigs = {
p.shiny = true; p.shiny = true;
p.variant = 1; p.variant = 1;
})) }))
.setGenModifiersFunc(party => { .setInstantTera(0),
const starter = party[0];
return [ modifierTypes.TERA_SHARD().generateType([], [ starter.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier ]; //TODO: is the bang correct?
}),
[TrainerType.RIVAL_6]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(3).setEncounterBgm("final").setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_6) [TrainerType.RIVAL_6]: new TrainerConfig(++t).setName("Finn").setHasGenders("Ivy").setHasCharSprite().setTitle("Rival").setBoss().setStaticParty().setMoneyMultiplier(3).setEncounterBgm("final").setBattleBgm("battle_rival_3").setMixedBattleBgm("battle_rival_3").setPartyTemplates(trainerPartyTemplates.RIVAL_6)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true, .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.VENUSAUR, Species.CHARIZARD, Species.BLASTOISE, Species.MEGANIUM, Species.TYPHLOSION, Species.FERALIGATR, Species.SCEPTILE, Species.BLAZIKEN, Species.SWAMPERT, Species.TORTERRA, Species.INFERNAPE, Species.EMPOLEON, Species.SERPERIOR, Species.EMBOAR, Species.SAMUROTT, Species.CHESNAUGHT, Species.DELPHOX, Species.GRENINJA, Species.DECIDUEYE, Species.INCINEROAR, Species.PRIMARINA, Species.RILLABOOM, Species.CINDERACE, Species.INTELEON, Species.MEOWSCARADA, Species.SKELEDIRGE, Species.QUAQUAVAL ], TrainerSlot.TRAINER, true,
p => { p => {
p.setBoss(true, 3); p.setBoss(true, 3);
p.abilityIndex = 0; p.abilityIndex = 0;
p.teraType = p.species.type1;
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
})) }))
.setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true, .setPartyMemberFunc(1, getRandomPartyMemberFunc([ Species.PIDGEOT, Species.NOCTOWL, Species.SWELLOW, Species.STARAPTOR, Species.UNFEZANT, Species.TALONFLAME, Species.TOUCANNON, Species.CORVIKNIGHT, Species.KILOWATTREL ], TrainerSlot.TRAINER, true,
@ -1928,10 +1987,7 @@ export const trainerConfigs: TrainerConfigs = {
p.formIndex = 1; // Mega Rayquaza p.formIndex = 1; // Mega Rayquaza
p.generateName(); p.generateName();
})) }))
.setGenModifiersFunc(party => { .setInstantTera(0),
const starter = party[0];
return [ modifierTypes.TERA_SHARD().generateType([], [ starter.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(starter) as PersistentModifier ]; // TODO: is the bang correct?
}),
[TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig(t = TrainerType.ROCKET_BOSS_GIOVANNI_1).setName("Giovanni").initForEvilTeamLeader("Rocket Boss", []).setMixedBattleBgm("battle_rocket_boss").setVictoryBgm("victory_team_plasma") [TrainerType.ROCKET_BOSS_GIOVANNI_1]: new TrainerConfig(t = TrainerType.ROCKET_BOSS_GIOVANNI_1).setName("Giovanni").initForEvilTeamLeader("Rocket Boss", []).setMixedBattleBgm("battle_rocket_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PERSIAN, Species.ALOLA_PERSIAN ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.PERSIAN, Species.ALOLA_PERSIAN ], TrainerSlot.TRAINER, true, p => {
@ -2354,10 +2410,7 @@ export const trainerConfigs: TrainerConfigs = {
p.pokeball = PokeballType.ULTRA_BALL; p.pokeball = PokeballType.ULTRA_BALL;
p.generateName(); p.generateName();
})) }))
.setGenModifiersFunc(party => { .setInstantTera(4),
const teraPokemon = party[4];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct?
}),
[TrainerType.PENNY_2]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", [], true).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma") [TrainerType.PENNY_2]: new TrainerConfig(++t).setName("Cassiopeia").initForEvilTeamLeader("Star Boss", [], true).setMixedBattleBgm("battle_star_boss").setVictoryBgm("victory_team_plasma")
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.SYLVEON ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 2); p.setBoss(true, 2);
@ -2387,10 +2440,7 @@ export const trainerConfigs: TrainerConfigs = {
p.generateAndPopulateMoveset(); p.generateAndPopulateMoveset();
p.pokeball = PokeballType.MASTER_BALL; p.pokeball = PokeballType.MASTER_BALL;
})) }))
.setGenModifiersFunc(party => { .setInstantTera(0),
const teraPokemon = party[0];
return [ modifierTypes.TERA_SHARD().generateType([], [ teraPokemon.species.type1 ])!.withIdFromFunc(modifierTypes.TERA_SHARD).newModifier(teraPokemon) as PersistentModifier ]; //TODO: is the bang correct?
}),
[TrainerType.BUCK]: new TrainerConfig(++t).setName("Buck").initForStatTrainer([], true) [TrainerType.BUCK]: new TrainerConfig(++t).setName("Buck").initForStatTrainer([], true)
.setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLAYDOL ], TrainerSlot.TRAINER, true, p => { .setPartyMemberFunc(0, getRandomPartyMemberFunc([ Species.CLAYDOL ], TrainerSlot.TRAINER, true, p => {
p.setBoss(true, 3); p.setBoss(true, 3);

View File

@ -27,6 +27,9 @@ export default class PokemonSpriteSparkleHandler {
if (!s.visible || (s.parentContainer instanceof Pokemon && !s.parentContainer.parentContainer)) { if (!s.visible || (s.parentContainer instanceof Pokemon && !s.parentContainer.parentContainer)) {
continue; continue;
} }
if (s.parentContainer instanceof Pokemon && !(s.parentContainer as Pokemon).isTerastallized) {
continue;
}
const pokemon = s.parentContainer instanceof Pokemon ? s.parentContainer as Pokemon : null; const pokemon = s.parentContainer instanceof Pokemon ? s.parentContainer as Pokemon : null;
const parent = (pokemon || s).parentContainer; const parent = (pokemon || s).parentContainer;
const texture = s.texture; const texture = s.texture;

View File

@ -19,7 +19,7 @@ import { getTypeDamageMultiplier, getTypeRgb } from "#app/data/type";
import { Type } from "#enums/type"; import { Type } from "#enums/type";
import { getLevelTotalExp } from "#app/data/exp"; import { getLevelTotalExp } from "#app/data/exp";
import { Stat, type PermanentStat, type BattleStat, type EffectiveStat, PERMANENT_STATS, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat"; import { Stat, type PermanentStat, type BattleStat, type EffectiveStat, PERMANENT_STATS, BATTLE_STATS, EFFECTIVE_STATS } from "#enums/stat";
import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, StatBoosterModifier, CritBoosterModifier, TerastallizeModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, EvoTrackerModifier, PokemonMultiHitModifier } from "#app/modifier/modifier"; import { DamageMoneyRewardModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, HiddenAbilityRateBoosterModifier, BaseStatModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonNatureWeightModifier, ShinyRateBoosterModifier, SurviveDamageModifier, TempStatStageBoosterModifier, TempCritBoosterModifier, StatBoosterModifier, CritBoosterModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonIncrementingStatModifier, EvoTrackerModifier, PokemonMultiHitModifier } from "#app/modifier/modifier";
import { PokeballType } from "#enums/pokeball"; import { PokeballType } from "#enums/pokeball";
import { Gender } from "#app/data/gender"; import { Gender } from "#app/data/gender";
import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims"; import { initMoveAnim, loadMoveAnimAssets } from "#app/data/battle-anims";
@ -46,7 +46,7 @@ import { DexAttr } from "#app/system/game-data";
import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities"; import { QuantizerCelebi, argbFromRgba, rgbaFromArgb } from "@material/material-color-utilities";
import { getNatureStatMultiplier } from "#app/data/nature"; import { getNatureStatMultiplier } from "#app/data/nature";
import type { SpeciesFormChange } from "#app/data/pokemon-forms"; import type { SpeciesFormChange } from "#app/data/pokemon-forms";
import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms"; import { SpeciesFormChangeActiveTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeMoveLearnedTrigger, SpeciesFormChangePostMoveTrigger, SpeciesFormChangeStatusEffectTrigger } from "#app/data/pokemon-forms";
import { TerrainType } from "#app/data/terrain"; import { TerrainType } from "#app/data/terrain";
import type { TrainerSlot } from "#app/data/trainer-config"; import type { TrainerSlot } from "#app/data/trainer-config";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
@ -141,6 +141,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
public fusionGender: Gender; public fusionGender: Gender;
public fusionLuck: integer; public fusionLuck: integer;
public fusionCustomPokemonData: CustomPokemonData | null; public fusionCustomPokemonData: CustomPokemonData | null;
public fusionTeraType: Type;
private summonDataPrimer: PokemonSummonData | null; private summonDataPrimer: PokemonSummonData | null;
@ -238,6 +239,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
this.fusionGender = dataSource.fusionGender; this.fusionGender = dataSource.fusionGender;
this.fusionLuck = dataSource.fusionLuck; this.fusionLuck = dataSource.fusionLuck;
this.fusionCustomPokemonData = dataSource.fusionCustomPokemonData; this.fusionCustomPokemonData = dataSource.fusionCustomPokemonData;
this.fusionTeraType = dataSource.teraType;
this.usedTMs = dataSource.usedTMs ?? []; this.usedTMs = dataSource.usedTMs ?? [];
this.customPokemonData = new CustomPokemonData(dataSource.customPokemonData); this.customPokemonData = new CustomPokemonData(dataSource.customPokemonData);
this.teraType = dataSource.teraType; this.teraType = dataSource.teraType;
@ -329,7 +331,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
const getSprite = (hasShadow?: boolean) => { const getSprite = (hasShadow?: boolean) => {
const ret = globalScene.addPokemonSprite(this, 0, 0, `pkmn__${this.isPlayer() ? "back__" : ""}sub`, undefined, true); const ret = globalScene.addPokemonSprite(this, 0, 0, `pkmn__${this.isPlayer() ? "back__" : ""}sub`, undefined, true);
ret.setOrigin(0.5, 1); ret.setOrigin(0.5, 1);
ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow, teraColor: getTypeRgb(this.getTeraType()) }); ret.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow, teraColor: getTypeRgb(this.getTeraType()), isTerastallized: this.isTerastallized });
return ret; return ret;
}; };
@ -697,7 +699,10 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
} }
updateSpritePipelineData(): void { updateSpritePipelineData(): void {
[ this.getSprite(), this.getTintSprite() ].filter(s => !!s).map(s => s.pipelineData["teraColor"] = getTypeRgb(this.getTeraType())); [ this.getSprite(), this.getTintSprite() ].filter(s => !!s).map(s => {
s.pipelineData["teraColor"] = getTypeRgb(this.getTeraType());
s.pipelineData["isTerastallized"] = this.isTerastallized;
});
this.updateInfo(true); this.updateInfo(true);
} }
@ -1255,7 +1260,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
if (includeTeraType && this.isTerastallized) { if (includeTeraType && this.isTerastallized) {
const teraType = this.getTeraType(); const teraType = this.getTeraType();
if (teraType !== Type.UNKNOWN && !(forDefend && teraType === Type.STELLAR)) { // Stellar tera uses its original types defensively if (this.isTerastallized && !(forDefend && teraType === Type.STELLAR)) { // Stellar tera uses its original types defensively
types.push(teraType); types.push(teraType);
if (forDefend) { if (forDefend) {
return types; return types;
@ -1561,18 +1566,28 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
* @returns the pokemon's current tera {@linkcode Type}, or `Type.UNKNOWN` if the pokemon is not terastallized * @returns the pokemon's current tera {@linkcode Type}, or `Type.UNKNOWN` if the pokemon is not terastallized
*/ */
getTeraType(): Type { getTeraType(): Type {
if (this.species.speciesId === Species.TERAPAGOS || this.fusionSpecies?.speciesId === Species.TERAPAGOS) {
return Type.STELLAR;
} else if (this.species.speciesId === Species.OGERPON || this.fusionSpecies?.speciesId === Species.OGERPON) {
const ogerponForm = this.species.speciesId === Species.OGERPON ? this.formIndex : this.fusionFormIndex;
switch (ogerponForm) {
case 0:
case 4:
return Type.GRASS;
case 1:
case 5:
return Type.WATER;
case 2:
case 6:
return Type.FIRE;
case 3:
case 7:
return Type.ROCK;
}
} else if (this.species.speciesId === Species.SHEDINJA || this.fusionSpecies?.speciesId === Species.SHEDINJA) {
return Type.BUG;
}
return this.teraType; return this.teraType;
// this.scene can be undefined for a fainted mon in doubles
if (this.scene !== undefined) {
const teraModifier = globalScene.findModifier(m => m instanceof TerastallizeModifier
&& m.pokemonId === this.id && !!m.getBattlesLeft(), this.isPlayer()) as TerastallizeModifier;
// return teraType
if (teraModifier) {
return teraModifier.teraType;
}
}
// if scene is undefined, or if teraModifier is considered false, then return unknown type
return Type.UNKNOWN;
} }
public isGrounded(): boolean { public isGrounded(): boolean {
@ -2751,7 +2766,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
stabMultiplier.value += 0.5; stabMultiplier.value += 0.5;
} }
applyMoveAttrs(CombinedPledgeStabBoostAttr, source, this, move, stabMultiplier); applyMoveAttrs(CombinedPledgeStabBoostAttr, source, this, move, stabMultiplier);
if (sourceTeraType !== Type.UNKNOWN && sourceTeraType === moveType) { if (source.isTerastallized && sourceTeraType === moveType) {
stabMultiplier.value += 0.5; stabMultiplier.value += 0.5;
} }
@ -3781,7 +3796,12 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container {
resetBattleData(): void { resetBattleData(): void {
this.battleData = new PokemonBattleData(); this.battleData = new PokemonBattleData();
this.isTerastallized = true; const wasTerastallized = this.isTerastallized;
this.isTerastallized = false;
if (wasTerastallized) {
this.updateSpritePipelineData();
globalScene.triggerPokemonFormChange(this, SpeciesFormChangeLapseTeraTrigger);
}
} }
resetBattleSummonData(): void { resetBattleSummonData(): void {
@ -4544,6 +4564,7 @@ export class PlayerPokemon extends Pokemon {
newPokemon.fusionVariant = this.fusionVariant; newPokemon.fusionVariant = this.fusionVariant;
newPokemon.fusionGender = this.fusionGender; newPokemon.fusionGender = this.fusionGender;
newPokemon.fusionLuck = this.fusionLuck; newPokemon.fusionLuck = this.fusionLuck;
newPokemon.fusionTeraType = this.teraType;
newPokemon.usedTMs = this.usedTMs; newPokemon.usedTMs = this.usedTMs;
globalScene.getPlayerParty().push(newPokemon); globalScene.getPlayerParty().push(newPokemon);
@ -4715,6 +4736,7 @@ export class EnemyPokemon extends Pokemon {
public aiType: AiType; public aiType: AiType;
public bossSegments: integer; public bossSegments: integer;
public bossSegmentIndex: integer; public bossSegmentIndex: integer;
public initialTeamIndex: integer;
/** To indicate if the instance was populated with a dataSource -> e.g. loaded & populated from session data */ /** To indicate if the instance was populated with a dataSource -> e.g. loaded & populated from session data */
public readonly isPopulatedFromDataSource: boolean; public readonly isPopulatedFromDataSource: boolean;
@ -4724,6 +4746,7 @@ export class EnemyPokemon extends Pokemon {
undefined, dataSource ? dataSource.nature : undefined, dataSource); undefined, dataSource ? dataSource.nature : undefined, dataSource);
this.trainerSlot = trainerSlot; this.trainerSlot = trainerSlot;
this.initialTeamIndex = globalScene.currentBattle.enemyParty.length;
this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource this.isPopulatedFromDataSource = !!dataSource; // if a dataSource is provided, then it was populated from dataSource
if (boss) { if (boss) {
this.setBoss(boss, dataSource?.bossSegments); this.setBoss(boss, dataSource?.bossSegments);

View File

@ -11,7 +11,8 @@ import {
TrainerSlot, TrainerSlot,
trainerConfigs, trainerConfigs,
trainerPartyTemplates, trainerPartyTemplates,
signatureSpecies signatureSpecies,
TeraAIMode
} from "#app/data/trainer-config"; } from "#app/data/trainer-config";
import type { EnemyPokemon } from "#app/field/pokemon"; import type { EnemyPokemon } from "#app/field/pokemon";
import * as Utils from "#app/utils"; import * as Utils from "#app/utils";
@ -36,6 +37,7 @@ export default class Trainer extends Phaser.GameObjects.Container {
public partyTemplateIndex: integer; public partyTemplateIndex: integer;
public name: string; public name: string;
public partnerName: string; public partnerName: string;
public originalIndexes: { [key: number]: number } = {};
constructor(trainerType: TrainerType, variant: TrainerVariant, partyTemplateIndex?: integer, name?: string, partnerName?: string, trainerConfigOverride?: TrainerConfig) { constructor(trainerType: TrainerType, variant: TrainerVariant, partyTemplateIndex?: integer, name?: string, partnerName?: string, trainerConfigOverride?: TrainerConfig) {
super(globalScene, -72, 80); super(globalScene, -72, 80);
@ -546,6 +548,13 @@ export default class Trainer extends Phaser.GameObjects.Container {
return []; return [];
} }
genAI(party: EnemyPokemon[]) {
if (this.config.genAIFuncs) {
this.config.genAIFuncs.forEach(f => f(party));
}
console.log("Generated AI funcs");
}
loadAssets(): Promise<void> { loadAssets(): Promise<void> {
return this.config.loadAssets(this.variant); return this.config.loadAssets(this.variant);
} }
@ -667,4 +676,13 @@ export default class Trainer extends Phaser.GameObjects.Container {
} }
}); });
} }
shouldTera(pokemon: EnemyPokemon): boolean {
if (this.config.trainerAI.teraMode === TeraAIMode.INSTANT_TERA) {
if (!pokemon.isTerastallized && this.config.trainerAI.instantTeras.includes(pokemon.initialTeamIndex)) {
return true;
}
}
return false;
}
} }

View File

@ -11,7 +11,7 @@ import { Type } from "#enums/type";
import type { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon"; import type { EnemyPokemon, PlayerPokemon, PokemonMove } from "#app/field/pokemon";
import type Pokemon from "#app/field/pokemon"; import type Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import { AddPokeballModifier, AddVoucherModifier, AttackTypeBoosterModifier, BaseStatModifier, BerryModifier, BoostBugSpawnModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, CritBoosterModifier, DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, MapModifier, MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonLevelIncrementModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PokemonNatureChangeModifier, PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, SpeciesStatBoosterModifier, SurviveDamageModifier, SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerastallizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier, TempExtraModifierModifier, CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier"; import { AddPokeballModifier, AddVoucherModifier, AttackTypeBoosterModifier, BaseStatModifier, BerryModifier, BoostBugSpawnModifier, BypassSpeedChanceModifier, ContactHeldItemTransferChanceModifier, CritBoosterModifier, DamageMoneyRewardModifier, DoubleBattleChanceBoosterModifier, EnemyAttackStatusEffectChanceModifier, EnemyDamageBoosterModifier, EnemyDamageReducerModifier, EnemyEndureChanceModifier, EnemyFusionChanceModifier, EnemyStatusEffectHealChanceModifier, EnemyTurnHealModifier, EvolutionItemModifier, EvolutionStatBoosterModifier, EvoTrackerModifier, ExpBalanceModifier, ExpBoosterModifier, ExpShareModifier, ExtraModifierModifier, FlinchChanceModifier, FusePokemonModifier, GigantamaxAccessModifier, HealingBoosterModifier, HealShopCostModifier, HiddenAbilityRateBoosterModifier, HitHealModifier, IvScannerModifier, LevelIncrementBoosterModifier, LockModifierTiersModifier, MapModifier, MegaEvolutionAccessModifier, MoneyInterestModifier, MoneyMultiplierModifier, MoneyRewardModifier, MultipleParticipantExpBonusModifier, PokemonAllMovePpRestoreModifier, PokemonBaseStatFlatModifier, PokemonBaseStatTotalModifier, PokemonExpBoosterModifier, PokemonFormChangeItemModifier, PokemonFriendshipBoosterModifier, PokemonHeldItemModifier, PokemonHpRestoreModifier, PokemonIncrementingStatModifier, PokemonInstantReviveModifier, PokemonLevelIncrementModifier, PokemonMoveAccuracyBoosterModifier, PokemonMultiHitModifier, PokemonNatureChangeModifier, PokemonNatureWeightModifier, PokemonPpRestoreModifier, PokemonPpUpModifier, PokemonStatusHealModifier, PreserveBerryModifier, RememberMoveModifier, ResetNegativeStatStageModifier, ShinyRateBoosterModifier, SpeciesCritBoosterModifier, SpeciesStatBoosterModifier, SurviveDamageModifier, SwitchEffectTransferModifier, TempCritBoosterModifier, TempStatStageBoosterModifier, TerastallizeAccessModifier, TerrastalizeModifier, TmModifier, TurnHealModifier, TurnHeldItemTransferModifier, TurnStatusEffectModifier, type EnemyPersistentModifier, type Modifier, type PersistentModifier, TempExtraModifierModifier, CriticalCatchChanceBoosterModifier } from "#app/modifier/modifier";
import { ModifierTier } from "#app/modifier/modifier-tier"; import { ModifierTier } from "#app/modifier/modifier-tier";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { Unlockables } from "#app/system/unlockables"; import { Unlockables } from "#app/system/unlockables";
@ -19,7 +19,7 @@ import { getVoucherTypeIcon, getVoucherTypeName, VoucherType } from "#app/system
import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler"; import type { PokemonMoveSelectFilter, PokemonSelectFilter } from "#app/ui/party-ui-handler";
import PartyUiHandler from "#app/ui/party-ui-handler"; import PartyUiHandler from "#app/ui/party-ui-handler";
import { getModifierTierTextTint } from "#app/ui/text"; import { getModifierTierTextTint } from "#app/ui/text";
import { formatMoney, getEnumKeys, getEnumValues, isNullOrUndefined, NumberHolder, padInt, randSeedInt, randSeedItem } from "#app/utils"; import { formatMoney, getEnumKeys, getEnumValues, isNullOrUndefined, NumberHolder, padInt, randSeedInt } from "#app/utils";
import { Abilities } from "#enums/abilities"; import { Abilities } from "#enums/abilities";
import { BattlerTagType } from "#enums/battler-tag-type"; import { BattlerTagType } from "#enums/battler-tag-type";
import { BerryType } from "#enums/berry-type"; import { BerryType } from "#enums/berry-type";
@ -275,6 +275,36 @@ export class PokemonHeldItemModifierType extends PokemonModifierType {
} }
} }
export class TerastallizeModifierType extends PokemonModifierType {
private teraType: Type;
constructor(teraType: Type) {
super("", `${Type[teraType].toLowerCase()}_tera_shard`, (type, args) => new TerrastalizeModifier(type as TerastallizeModifierType, (args[0] as Pokemon).id, teraType),
(pokemon: PlayerPokemon) => {
if ([ pokemon.species.speciesId, pokemon.fusionSpecies?.speciesId ].filter(s => s === Species.TERAPAGOS || s === Species.OGERPON || s === Species.SHEDINJA).length > 0) {
return PartyUiHandler.NoEffectMessage;
}
return null;
},
"tera_shard");
this.teraType = teraType;
}
get name(): string {
return i18next.t("modifierType:ModifierType.TerastallizeModifierType.name", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) });
}
getDescription(): string {
return i18next.t("modifierType:ModifierType.TerastallizeModifierType.description", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) });
}
getPregenArgs(): any[] {
return [ this.teraType ];
}
}
export class PokemonHpRestoreModifierType extends PokemonModifierType { export class PokemonHpRestoreModifierType extends PokemonModifierType {
protected restorePoints: integer; protected restorePoints: integer;
protected restorePercent: integer; protected restorePercent: integer;
@ -1192,28 +1222,6 @@ class FormChangeItemModifierTypeGenerator extends ModifierTypeGenerator {
} }
} }
export class TerastallizeModifierType extends PokemonHeldItemModifierType implements GeneratedPersistentModifierType {
private teraType: Type;
constructor(teraType: Type) {
super("", `${Type[teraType].toLowerCase()}_tera_shard`, (type, args) => new TerastallizeModifier(type as TerastallizeModifierType, (args[0] as Pokemon).id, teraType), "tera_shard");
this.teraType = teraType;
}
get name(): string {
return i18next.t("modifierType:ModifierType.TerastallizeModifierType.name", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) });
}
getDescription(): string {
return i18next.t("modifierType:ModifierType.TerastallizeModifierType.description", { teraType: i18next.t(`pokemonInfo:Type.${Type[this.teraType]}`) });
}
getPregenArgs(): any[] {
return [ this.teraType ];
}
}
export class ContactHeldItemTransferChanceModifierType extends PokemonHeldItemModifierType { export class ContactHeldItemTransferChanceModifierType extends PokemonHeldItemModifierType {
private chancePercent: integer; private chancePercent: integer;
@ -1469,14 +1477,7 @@ export const modifierTypes = {
if (!globalScene.getModifiers(TerastallizeAccessModifier).length) { if (!globalScene.getModifiers(TerastallizeAccessModifier).length) {
return null; return null;
} }
let type: Type; return new TerastallizeModifierType(randSeedInt(64) ? randSeedInt(18) as Type : Type.STELLAR);
if (!randSeedInt(3)) {
const partyMemberTypes = party.map(p => p.getTypes(false, false, true)).flat();
type = randSeedItem(partyMemberTypes);
} else {
type = randSeedInt(64) ? randSeedInt(18) as Type : Type.STELLAR;
}
return new TerastallizeModifierType(type);
}), }),
BERRY: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => { BERRY: () => new ModifierTypeGenerator((_party: Pokemon[], pregenArgs?: any[]) => {

View File

@ -3,16 +3,16 @@ import { getBerryEffectFunc, getBerryPredicate } from "#app/data/berry";
import { getLevelTotalExp } from "#app/data/exp"; import { getLevelTotalExp } from "#app/data/exp";
import { allMoves } from "#app/data/move"; import { allMoves } from "#app/data/move";
import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball"; import { MAX_PER_TYPE_POKEBALLS } from "#app/data/pokeball";
import { type FormChangeItem, SpeciesFormChangeItemTrigger, SpeciesFormChangeLapseTeraTrigger, SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms"; import { type FormChangeItem, SpeciesFormChangeItemTrigger } from "#app/data/pokemon-forms";
import { getStatusEffectHealText } from "#app/data/status-effect"; import { getStatusEffectHealText } from "#app/data/status-effect";
import Pokemon, { type PlayerPokemon } from "#app/field/pokemon"; import type { PlayerPokemon } from "#app/field/pokemon";
import Pokemon from "#app/field/pokemon";
import { getPokemonNameWithAffix } from "#app/messages"; import { getPokemonNameWithAffix } from "#app/messages";
import Overrides from "#app/overrides"; import Overrides from "#app/overrides";
import { EvolutionPhase } from "#app/phases/evolution-phase"; import { EvolutionPhase } from "#app/phases/evolution-phase";
import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase"; import { LearnMovePhase, LearnMoveType } from "#app/phases/learn-move-phase";
import { LevelUpPhase } from "#app/phases/level-up-phase"; import { LevelUpPhase } from "#app/phases/level-up-phase";
import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase"; import { PokemonHealPhase } from "#app/phases/pokemon-heal-phase";
import { achvs } from "#app/system/achv";
import type { VoucherType } from "#app/system/voucher"; import type { VoucherType } from "#app/system/voucher";
import { Command } from "#app/ui/command-ui-handler"; import { Command } from "#app/ui/command-ui-handler";
import { addTextObject, TextStyle } from "#app/ui/text"; import { addTextObject, TextStyle } from "#app/ui/text";
@ -25,7 +25,7 @@ import type { PokeballType } from "#enums/pokeball";
import { Species } from "#enums/species"; import { Species } from "#enums/species";
import { type PermanentStat, type TempBattleStat, BATTLE_STATS, Stat, TEMP_BATTLE_STATS } from "#enums/stat"; import { type PermanentStat, type TempBattleStat, BATTLE_STATS, Stat, TEMP_BATTLE_STATS } from "#enums/stat";
import { StatusEffect } from "#enums/status-effect"; import { StatusEffect } from "#enums/status-effect";
import { Type } from "#enums/type"; import type { Type } from "#enums/type";
import i18next from "i18next"; import i18next from "i18next";
import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierType, type FormChangeItemModifierType, type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, type PokemonExpBoosterModifierType, type PokemonFriendshipBoosterModifierType, type PokemonMoveAccuracyBoosterModifierType, type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, ModifierPoolType, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType } from "./modifier-type"; import { type DoubleBattleChanceBoosterModifierType, type EvolutionItemModifierType, type FormChangeItemModifierType, type ModifierOverride, type ModifierType, type PokemonBaseStatTotalModifierType, type PokemonExpBoosterModifierType, type PokemonFriendshipBoosterModifierType, type PokemonMoveAccuracyBoosterModifierType, type PokemonMultiHitModifierType, type TerastallizeModifierType, type TmModifierType, getModifierType, ModifierPoolType, ModifierTypeGenerator, modifierTypes, PokemonHeldItemModifierType } from "./modifier-type";
import { Color, ShadowColor } from "#enums/color"; import { Color, ShadowColor } from "#enums/color";
@ -786,72 +786,6 @@ export abstract class LapsingPokemonHeldItemModifier extends PokemonHeldItemModi
} }
} }
export class TerastallizeModifier extends LapsingPokemonHeldItemModifier {
public override type: TerastallizeModifierType;
public teraType: Type;
public isTransferable: boolean = false;
constructor(type: TerastallizeModifierType, pokemonId: number, teraType: Type, battlesLeft?: number, stackCount?: number) {
super(type, pokemonId, battlesLeft || 10, stackCount);
this.teraType = teraType;
}
matchType(modifier: Modifier): boolean {
if (modifier instanceof TerastallizeModifier && modifier.teraType === this.teraType) {
return true;
}
return false;
}
clone(): TerastallizeModifier {
return new TerastallizeModifier(this.type, this.pokemonId, this.teraType, this.battlesLeft, this.stackCount);
}
getArgs(): any[] {
return [ this.pokemonId, this.teraType, this.battlesLeft ];
}
/**
* Applies the {@linkcode TerastallizeModifier} to the specified {@linkcode Pokemon}.
* @param pokemon the {@linkcode Pokemon} to be terastallized
* @returns always `true`
*/
override apply(pokemon: Pokemon): boolean {
if (pokemon.isPlayer()) {
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeTeraTrigger);
globalScene.validateAchv(achvs.TERASTALLIZE);
if (this.teraType === Type.STELLAR) {
globalScene.validateAchv(achvs.STELLAR_TERASTALLIZE);
}
}
pokemon.updateSpritePipelineData();
return true;
}
/**
* Triggers {@linkcode LapsingPokemonHeldItemModifier.lapse} and if it returns `0` a form change is triggered.
* @param pokemon THe {@linkcode Pokemon} to be terastallized
* @returns the result of {@linkcode LapsingPokemonHeldItemModifier.lapse}
*/
public override lapse(pokemon: Pokemon): boolean {
const ret = super.lapse(pokemon);
if (!ret) {
globalScene.triggerPokemonFormChange(pokemon, SpeciesFormChangeLapseTeraTrigger);
pokemon.updateSpritePipelineData();
}
return ret;
}
getScoreMultiplier(): number {
return 1.25;
}
getMaxHeldItemCount(pokemon: Pokemon): number {
return 1;
}
}
/** /**
* Modifier used for held items, specifically vitamins like Carbos, Hp Up, etc., that * Modifier used for held items, specifically vitamins like Carbos, Hp Up, etc., that
* increase the value of a given {@linkcode PermanentStat}. * increase the value of a given {@linkcode PermanentStat}.
@ -2022,6 +1956,36 @@ export abstract class ConsumablePokemonModifier extends ConsumableModifier {
} }
} }
export class TerrastalizeModifier extends ConsumablePokemonModifier {
public override type: TerastallizeModifierType;
public teraType: Type;
constructor(type: TerastallizeModifierType, pokemonId: number, teraType: Type) {
super(type, pokemonId);
this.teraType = teraType;
}
/**
* Checks if {@linkcode TerrastalizeModifier} should be applied
* @param playerPokemon The {@linkcode PlayerPokemon} that consumes the item
* @returns `true` if the {@linkcode TerrastalizeModifier} should be applied
*/
override shouldApply(playerPokemon?: PlayerPokemon): boolean {
return super.shouldApply(playerPokemon) && [ playerPokemon?.species.speciesId, playerPokemon?.fusionSpecies?.speciesId ].filter(s => s === Species.TERAPAGOS || s === Species.OGERPON || s === Species.SHEDINJA).length === 0;
}
/**
* Applies {@linkcode TerrastalizeModifier}
* @param pokemon The {@linkcode PlayerPokemon} that consumes the item
* @returns `true` if hp was restored
*/
override apply(pokemon: Pokemon): boolean {
pokemon.teraType = this.teraType;
return true;
}
}
export class PokemonHpRestoreModifier extends ConsumablePokemonModifier { export class PokemonHpRestoreModifier extends ConsumablePokemonModifier {
private restorePoints: number; private restorePoints: number;
private restorePercent: number; private restorePercent: number;

View File

@ -118,9 +118,8 @@ export class CommandPhase extends FieldPhase {
let success: boolean = false; let success: boolean = false;
switch (command) { switch (command) {
case Command.FIGHT:
case Command.TERA: case Command.TERA:
console.log("Fight From Command", command); case Command.FIGHT:
let useStruggle = false; let useStruggle = false;
const turnMove: TurnMove | undefined = (args.length === 2 ? (args[1] as TurnMove) : undefined); const turnMove: TurnMove | undefined = (args.length === 2 ? (args[1] as TurnMove) : undefined);
if (cursor === -1 || if (cursor === -1 ||
@ -139,6 +138,7 @@ export class CommandPhase extends FieldPhase {
} }
const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args }; const turnCommand: TurnCommand = { command: Command.FIGHT, cursor: cursor, move: { move: moveId, targets: [], ignorePP: args[0] }, args: args };
const preTurnCommand: TurnCommand = { command: command, targets: [ this.fieldIndex ], skip: command === Command.FIGHT };
const moveTargets: MoveTargetSet = turnMove === undefined ? getMoveTargets(playerPokemon, moveId) : { targets: turnMove.targets, multiple: turnMove.targets.length > 1 }; const moveTargets: MoveTargetSet = turnMove === undefined ? getMoveTargets(playerPokemon, moveId) : { targets: turnMove.targets, multiple: turnMove.targets.length > 1 };
if (!moveId) { if (!moveId) {
turnCommand.targets = [ this.fieldIndex ]; turnCommand.targets = [ this.fieldIndex ];
@ -154,6 +154,7 @@ export class CommandPhase extends FieldPhase {
} else { } else {
globalScene.unshiftPhase(new SelectTargetPhase(this.fieldIndex)); globalScene.unshiftPhase(new SelectTargetPhase(this.fieldIndex));
} }
globalScene.currentBattle.preTurnCommands[this.fieldIndex] = preTurnCommand;
globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand; globalScene.currentBattle.turnCommands[this.fieldIndex] = turnCommand;
success = true; success = true;
} else if (cursor < playerPokemon.getMoveset().length) { } else if (cursor < playerPokemon.getMoveset().length) {

View File

@ -81,6 +81,10 @@ export class EnemyCommandPhase extends FieldPhase {
/** Select a move to use (and a target to use it against, if applicable) */ /** Select a move to use (and a target to use it against, if applicable) */
const nextMove = enemyPokemon.getNextMove(); const nextMove = enemyPokemon.getNextMove();
if (trainer && trainer.shouldTera(enemyPokemon)) {
globalScene.currentBattle.preTurnCommands[this.fieldIndex + BattlerIndex.ENEMY] = { command: Command.TERA };
}
globalScene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] = globalScene.currentBattle.turnCommands[this.fieldIndex + BattlerIndex.ENEMY] =
{ command: Command.FIGHT, move: nextMove, skip: this.skipTurn }; { command: Command.FIGHT, move: nextMove, skip: this.skipTurn };

View File

@ -116,7 +116,7 @@ export class EvolutionPhase extends Phase {
console.error(`Failed to play animation for ${spriteKey}`, err); console.error(`Failed to play animation for ${spriteKey}`, err);
} }
sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()), isTerastallized: this.pokemon.isTerastallized });
sprite.setPipelineData("ignoreTimeTint", true); sprite.setPipelineData("ignoreTimeTint", true);
sprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey()); sprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
sprite.setPipelineData("shiny", this.pokemon.shiny); sprite.setPipelineData("shiny", this.pokemon.shiny);

View File

@ -1,7 +1,7 @@
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { SemiInvulnerableTag } from "#app/data/battler-tags"; import { SemiInvulnerableTag } from "#app/data/battler-tags";
import type { SpeciesFormChange } from "#app/data/pokemon-forms"; import type { SpeciesFormChange } from "#app/data/pokemon-forms";
import { getSpeciesFormChangeMessage } from "#app/data/pokemon-forms"; import { getSpeciesFormChangeMessage, SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms";
import { getTypeRgb } from "#app/data/type"; import { getTypeRgb } from "#app/data/type";
import { BattleSpec } from "#app/enums/battle-spec"; import { BattleSpec } from "#app/enums/battle-spec";
import { BattlerTagType } from "#app/enums/battler-tag-type"; import { BattlerTagType } from "#app/enums/battler-tag-type";
@ -11,6 +11,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
import { BattlePhase } from "./battle-phase"; import { BattlePhase } from "./battle-phase";
import { MovePhase } from "./move-phase"; import { MovePhase } from "./move-phase";
import { PokemonHealPhase } from "./pokemon-heal-phase"; import { PokemonHealPhase } from "./pokemon-heal-phase";
import { applyAbAttrs, PostTeraFormChangeStatChangeAbAttr } from "#app/data/ability";
export class QuietFormChangePhase extends BattlePhase { export class QuietFormChangePhase extends BattlePhase {
protected pokemon: Pokemon; protected pokemon: Pokemon;
@ -51,7 +52,7 @@ export class QuietFormChangePhase extends BattlePhase {
} catch (err: unknown) { } catch (err: unknown) {
console.error(`Failed to play animation for ${spriteKey}`, err); console.error(`Failed to play animation for ${spriteKey}`, err);
} }
sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()) }); sprite.setPipeline(globalScene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: false, teraColor: getTypeRgb(this.pokemon.getTeraType()), isTerastallized: this.pokemon.isTerastallized });
[ "spriteColors", "fusionSpriteColors" ].map(k => { [ "spriteColors", "fusionSpriteColors" ].map(k => {
if (this.pokemon.summonData?.speciesForm) { if (this.pokemon.summonData?.speciesForm) {
k += "Base"; k += "Base";
@ -145,6 +146,9 @@ export class QuietFormChangePhase extends BattlePhase {
movePhase.cancel(); movePhase.cancel();
} }
} }
if (this.formChange.trigger instanceof SpeciesFormChangeTeraTrigger) {
applyAbAttrs(PostTeraFormChangeStatChangeAbAttr, this.pokemon, null);
}
super.end(); super.end();
} }

View File

@ -4,6 +4,9 @@ import { BattlePhase } from "./battle-phase";
import i18next from "i18next"; import i18next from "i18next";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { Type } from "#app/enums/type"; import { Type } from "#app/enums/type";
import { achvs } from "#app/system/achv";
import { SpeciesFormChangeTeraTrigger } from "#app/data/pokemon-forms";
import { CommonAnim, CommonBattleAnim } from "#app/data/battle-anims";
export class TeraPhase extends BattlePhase { export class TeraPhase extends BattlePhase {
public pokemon: Pokemon; public pokemon: Pokemon;
@ -19,6 +22,17 @@ export class TeraPhase extends BattlePhase {
console.log(this.pokemon.name, "terastallized to", Type[this.pokemon.teraType].toString()); // TODO: Improve log console.log(this.pokemon.name, "terastallized to", Type[this.pokemon.teraType].toString()); // TODO: Improve log
// const parent = this.pokemon.parentContainer;
// // const texture = this.pokemon.getSprite().texture;
// // const [ width, height ] = [ texture.source[0].width, texture.source[0].height ];
// // const [ xOffset, yOffset ] = [ -this.pokemon.getSprite().originX * width, -s.originY * s.height ];
// const teraburst = globalScene.addFieldSprite(((this.pokemon?.x || 0)), ((this.pokemon?.y || 0)), "terastallize");
// teraburst.setName("sprite-terastallize");
// teraburst.play("terastallize");
// parent.add(teraburst);
// this.pokemon.scene.time.delayedCall(Utils.fixedInt(Math.floor((1000 / 12) * 13)), () => teraburst.destroy());
new CommonBattleAnim(CommonAnim.TERASTALLIZE, this.pokemon).play();
globalScene.queueMessage(getPokemonNameWithAffix(this.pokemon) + " terrastallized into a " + i18next.t(`pokemonInfo:Type.${Type[this.pokemon.teraType]}`) + " type!"); // TODO: Localize this globalScene.queueMessage(getPokemonNameWithAffix(this.pokemon) + " terrastallized into a " + i18next.t(`pokemonInfo:Type.${Type[this.pokemon.teraType]}`) + " type!"); // TODO: Localize this
// this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.getBattlerIndex(), undefined, CommonAnim.???)); // this.scene.unshiftPhase(new CommonAnimPhase(this.scene, this.pokemon.getBattlerIndex(), undefined, CommonAnim.???));
@ -28,6 +42,16 @@ export class TeraPhase extends BattlePhase {
end() { end() {
this.pokemon.isTerastallized = true; this.pokemon.isTerastallized = true;
this.pokemon.updateSpritePipelineData();
globalScene.triggerPokemonFormChange(this.pokemon, SpeciesFormChangeTeraTrigger);
if (this.pokemon.isPlayer()) {
globalScene.validateAchv(achvs.TERASTALLIZE);
if (this.pokemon.teraType === Type.STELLAR) {
globalScene.validateAchv(achvs.STELLAR_TERASTALLIZE);
}
}
super.end(); super.end();
} }

View File

@ -21,6 +21,7 @@ import { BattlerIndex } from "#app/battle";
import { TrickRoomTag } from "#app/data/arena-tag"; import { TrickRoomTag } from "#app/data/arena-tag";
import { SwitchType } from "#enums/switch-type"; import { SwitchType } from "#enums/switch-type";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { TeraPhase } from "./tera-phase";
export class TurnStartPhase extends FieldPhase { export class TurnStartPhase extends FieldPhase {
constructor() { constructor() {
@ -139,6 +140,20 @@ export class TurnStartPhase extends FieldPhase {
let orderIndex = 0; let orderIndex = 0;
for (const o of this.getSpeedOrder()) {
const pokemon = field[o];
const preTurnCommand = globalScene.currentBattle.preTurnCommands[o];
if (preTurnCommand?.skip) {
continue;
}
switch (preTurnCommand?.command) {
case Command.TERA:
globalScene.pushPhase(new TeraPhase(pokemon));
}
}
for (const o of moveOrder) { for (const o of moveOrder) {
const pokemon = field[o]; const pokemon = field[o];

View File

@ -351,7 +351,7 @@ export default class SpritePipeline extends FieldSpritePipeline {
const data = sprite.pipelineData; const data = sprite.pipelineData;
const tone = data["tone"] as number[]; const tone = data["tone"] as number[];
const teraColor = data["teraColor"] as integer[] ?? [ 0, 0, 0 ]; const teraColor = (data["isTerastallized"] as boolean) ? (data["teraColor"] as integer[] ?? [ 0, 0, 0 ]) : [ 0, 0, 0 ];
const hasShadow = data["hasShadow"] as boolean; const hasShadow = data["hasShadow"] as boolean;
const yShadowOffset = data["yShadowOffset"] as number; const yShadowOffset = data["yShadowOffset"] as number;
const ignoreFieldPos = data["ignoreFieldPos"] as boolean; const ignoreFieldPos = data["ignoreFieldPos"] as boolean;

View File

@ -6,7 +6,7 @@ import { Abilities } from "#app/enums/abilities";
import { Moves } from "#app/enums/moves"; import { Moves } from "#app/enums/moves";
import { Species } from "#app/enums/species"; import { Species } from "#app/enums/species";
import * as Messages from "#app/messages"; import * as Messages from "#app/messages";
import { TerastallizeModifier, overrideHeldItems } from "#app/modifier/modifier"; import { overrideHeldItems } from "#app/modifier/modifier";
import GameManager from "#test/utils/gameManager"; import GameManager from "#test/utils/gameManager";
import Phaser from "phaser"; import Phaser from "phaser";
import { afterEach, beforeAll, describe, expect, it, vi } from "vitest"; import { afterEach, beforeAll, describe, expect, it, vi } from "vitest";
@ -40,7 +40,6 @@ describe("Moves - Type Effectiveness", () => {
type: Phaser.HEADLESS, type: Phaser.HEADLESS,
}); });
game = new GameManager(phaserGame); game = new GameManager(phaserGame);
TerastallizeModifier.prototype.apply = (args) => true;
game.override.ability(Abilities.BALL_FETCH); game.override.ability(Abilities.BALL_FETCH);
}); });

View File

@ -324,9 +324,9 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.lastTeraType = pokemon.getTeraType(); this.lastTeraType = pokemon.getTeraType();
this.teraIcon.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1, 2); this.teraIcon.setPositionRelative(this.nameText, nameTextWidth + this.genderText.displayWidth + 1, 2);
this.teraIcon.setVisible(this.lastTeraType !== Type.UNKNOWN); this.teraIcon.setVisible(pokemon.isTerastallized);
this.teraIcon.on("pointerover", () => { this.teraIcon.on("pointerover", () => {
if (this.lastTeraType !== Type.UNKNOWN) { if (pokemon.isTerastallized) {
globalScene.ui.showTooltip("", i18next.t("fightUiHandler:teraHover", { type: i18next.t(`pokemonInfo:Type.${Type[this.lastTeraType]}`) })); globalScene.ui.showTooltip("", i18next.t("fightUiHandler:teraHover", { type: i18next.t(`pokemonInfo:Type.${Type[this.lastTeraType]}`) }));
} }
}); });
@ -542,7 +542,7 @@ export default class BattleInfo extends Phaser.GameObjects.Container {
this.genderText.setPositionRelative(this.nameText, this.nameText.displayWidth, 0); this.genderText.setPositionRelative(this.nameText, this.nameText.displayWidth, 0);
} }
const teraType = pokemon.getTeraType(); const teraType = pokemon.isTerastallized ? pokemon.getTeraType() : Type.UNKNOWN;
const teraTypeUpdated = this.lastTeraType !== teraType; const teraTypeUpdated = this.lastTeraType !== teraType;
if (teraTypeUpdated) { if (teraTypeUpdated) {

View File

@ -8,6 +8,7 @@ import { getPokemonNameWithAffix } from "#app/messages";
import { CommandPhase } from "#app/phases/command-phase"; import { CommandPhase } from "#app/phases/command-phase";
import { globalScene } from "#app/global-scene"; import { globalScene } from "#app/global-scene";
import { TerastallizeAccessModifier } from "#app/modifier/modifier"; import { TerastallizeAccessModifier } from "#app/modifier/modifier";
import { Type } from "#app/enums/type";
export enum Command { export enum Command {
FIGHT = 0, FIGHT = 0,
@ -46,7 +47,7 @@ export default class CommandUiHandler extends UiHandler {
this.teraButton = globalScene.add.sprite(-35, 15, "button_tera"); this.teraButton = globalScene.add.sprite(-35, 15, "button_tera");
this.teraButton.setName("terrastallize-button"); this.teraButton.setName("terrastallize-button");
this.teraButton.setScale(1.8); this.teraButton.setScale(1.5);
this.teraButton.setFrame("fire"); this.teraButton.setFrame("fire");
this.commandsContainer.add(this.teraButton); this.commandsContainer.add(this.teraButton);
@ -73,15 +74,13 @@ export default class CommandUiHandler extends UiHandler {
} }
if (this.canTera()) { if (this.canTera()) {
this.teraButton.setFrame(globalScene.getField()[this.fieldIndex].getTeraType().toString().toLowerCase()); this.teraButton.setVisible(true);
this.teraButton.setFrame(Type[globalScene.getField()[this.fieldIndex].getTeraType()].toLowerCase());
} else { } else {
this.teraButton.setVisible(false); this.teraButton.setVisible(false);
if (this.cursor === Command.TERA) {
this.setCursor(Command.FIGHT);
} }
if (this.canTera()) {
this.teraButton.setFrame(globalScene.getField()[this.fieldIndex].getTeraType().toString().toLowerCase());
} else {
this.teraButton.setVisible(false);
} }
const messageHandler = this.getUi().getMessageHandler(); const messageHandler = this.getUi().getMessageHandler();
@ -131,9 +130,6 @@ export default class CommandUiHandler extends UiHandler {
success = true; success = true;
break; break;
case Command.TERA: case Command.TERA:
if ((globalScene.getCurrentPhase() as CommandPhase).checkFightOverride()) {
return true;
}
ui.setMode(Mode.FIGHT, (globalScene.getCurrentPhase() as CommandPhase).getFieldIndex(), Command.TERA); ui.setMode(Mode.FIGHT, (globalScene.getCurrentPhase() as CommandPhase).getFieldIndex(), Command.TERA);
success = true; success = true;
break; break;
@ -178,7 +174,10 @@ export default class CommandUiHandler extends UiHandler {
} }
canTera(): boolean { canTera(): boolean {
return !!globalScene.getModifiers(TerastallizeAccessModifier).length; const hasTeraMod = !!globalScene.getModifiers(TerastallizeAccessModifier).length;
const currentTeras = globalScene.getPlayerParty().filter(p => p.isTerastallized).length;
const plannedTera = globalScene.currentBattle.preTurnCommands[0]?.command === Command.TERA;
return hasTeraMod && currentTeras < 1 && !plannedTera;
} }
getCursor(): integer { getCursor(): integer {
@ -200,7 +199,12 @@ export default class CommandUiHandler extends UiHandler {
this.commandsContainer.add(this.cursorObj); this.commandsContainer.add(this.cursorObj);
} }
if (cursor === Command.TERA) {
this.cursorObj.setVisible(false);
} else {
this.cursorObj.setPosition(-5 + (cursor % 2 === 1 ? 56 : 0), 8 + (cursor >= 2 ? 16 : 0)); this.cursorObj.setPosition(-5 + (cursor % 2 === 1 ? 56 : 0), 8 + (cursor >= 2 ? 16 : 0));
this.cursorObj.setVisible(true);
}
return changed; return changed;
} }

View File

@ -420,17 +420,6 @@ export default class RunInfoUiHandler extends UiHandler {
private parseTrainerDefeat(enemyContainer: Phaser.GameObjects.Container) { private parseTrainerDefeat(enemyContainer: Phaser.GameObjects.Container) {
// Loads and adds trainer sprites to the UI // Loads and adds trainer sprites to the UI
this.showTrainerSprites(enemyContainer); this.showTrainerSprites(enemyContainer);
// Determining which Terastallize Modifier belongs to which Pokemon
// Creates a dictionary {PokemonId: TeraShardType}
const teraPokemon = {};
this.runInfo.enemyModifiers.forEach((m) => {
const modifier = m.toModifier(this.modifiersModule[m.className]);
if (modifier instanceof Modifier.TerastallizeModifier) {
const teraDetails = modifier?.getArgs();
const pkmnId = teraDetails[0];
teraPokemon[pkmnId] = teraDetails[1];
}
});
// Creates the Pokemon icons + level information and adds it to enemyContainer // Creates the Pokemon icons + level information and adds it to enemyContainer
// 2 Rows x 3 Columns // 2 Rows x 3 Columns
@ -444,18 +433,6 @@ export default class RunInfoUiHandler extends UiHandler {
enemyData["player"] = true; enemyData["player"] = true;
const enemy = enemyData.toPokemon(); const enemy = enemyData.toPokemon();
const enemyIcon = globalScene.addPokemonIcon(enemy, 0, 0, 0, 0); const enemyIcon = globalScene.addPokemonIcon(enemy, 0, 0, 0, 0);
// Applying Terastallizing Type tint to Pokemon icon
// If the Pokemon is a fusion, it has two sprites and so, the tint has to be applied to each icon separately
const enemySprite1 = enemyIcon.list[0] as Phaser.GameObjects.Sprite;
const enemySprite2 = (enemyIcon.list.length > 1) ? enemyIcon.list[1] as Phaser.GameObjects.Sprite : undefined;
if (teraPokemon[enemyData.id]) {
const teraTint = getTypeRgb(teraPokemon[enemyData.id]);
const teraColor = new Phaser.Display.Color(teraTint[0], teraTint[1], teraTint[2]);
enemySprite1.setTint(teraColor.color);
if (enemySprite2) {
enemySprite2.setTint(teraColor.color);
}
}
enemyIcon.setPosition(39 * (e % 3) + 5, (35 * pokemonRowHeight)); enemyIcon.setPosition(39 * (e % 3) + 5, (35 * pokemonRowHeight));
const enemyLevel = addTextObject(43 * (e % 3), (27 * (pokemonRowHeight + 1)), `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, isBoss ? TextStyle.PARTY_RED : TextStyle.PARTY, { fontSize: "54px" }); const enemyLevel = addTextObject(43 * (e % 3), (27 * (pokemonRowHeight + 1)), `${i18next.t("saveSlotSelectUiHandler:lv")}${Utils.formatLargeNumber(enemy.level, 1000)}`, isBoss ? TextStyle.PARTY_RED : TextStyle.PARTY, { fontSize: "54px" });
enemyLevel.setShadow(0, 0, undefined); enemyLevel.setShadow(0, 0, undefined);

View File

@ -331,6 +331,7 @@ export default class SummaryUiHandler extends UiHandler {
console.error(`Failed to play animation for ${spriteKey}`, err); console.error(`Failed to play animation for ${spriteKey}`, err);
} }
this.pokemonSprite.setPipelineData("teraColor", getTypeRgb(this.pokemon.getTeraType())); this.pokemonSprite.setPipelineData("teraColor", getTypeRgb(this.pokemon.getTeraType()));
this.pokemonSprite.setPipelineData("isTerastallized", this.pokemon.isTerastallized);
this.pokemonSprite.setPipelineData("ignoreTimeTint", true); this.pokemonSprite.setPipelineData("ignoreTimeTint", true);
this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey()); this.pokemonSprite.setPipelineData("spriteKey", this.pokemon.getSpriteKey());
this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny); this.pokemonSprite.setPipelineData("shiny", this.pokemon.shiny);