Minor refactoring

This commit is contained in:
Flashfyre committed 2024-02-29 20:08:58 -05:00
1 parent 8d314b199f
commit f48464d48d
41 files changed
+115 -111

No files matched your search

+178
View File
@@ -0,0 +1,178 @@
import BattleScene from "../battle-scene";
import { PokeballType } from "../data/pokeball";
import * as Utils from "../utils";
export function addPokeballOpenParticles(scene: BattleScene, x: number, y: number, pokeballType: PokeballType): void {
switch (pokeballType) {
case PokeballType.POKEBALL:
doDefaultPbOpenParticles(scene, x, y, 48);
break;
case PokeballType.GREAT_BALL:
doDefaultPbOpenParticles(scene, x, y, 96);
break;
case PokeballType.ULTRA_BALL:
case PokeballType.ROGUE_BALL:
doUbOpenParticles(scene, x, y);
break;
case PokeballType.MASTER_BALL:
doMbOpenParticles(scene, x, y);
break;
}
}
function doDefaultPbOpenParticles(scene: BattleScene, x: number, y: number, radius: number) {
const pbOpenParticlesFrameNames = scene.anims.generateFrameNames('pb_particles', { start: 0, end: 3, suffix: '.png' });
scene.anims.create({
key: 'pb_open_particle',
frames: pbOpenParticlesFrameNames,
frameRate: 16,
repeat: -1
});
const addParticle = (index: integer) => {
const particle = scene.add.sprite(x, y, 'pb_open_particle');
scene.field.add(particle);
const angle = index * 45;
const [ xCoord, yCoord ] = [ radius * Math.cos(angle * Math.PI / 180), radius * Math.sin(angle * Math.PI / 180) ];
scene.tweens.add({
targets: particle,
x: x + xCoord,
y: y + yCoord,
duration: 575
});
particle.play({
key: 'pb_open_particle',
startFrame: (index + 3) % 4,
frameRate: Math.floor(16 * scene.gameSpeed)
});
scene.tweens.add({
targets: particle,
delay: 500,
duration: 75,
alpha: 0,
ease: 'Sine.easeIn',
onComplete: () => particle.destroy()
});
};
let particleCount = 0;
scene.time.addEvent({
delay: 20,
repeat: 16,
callback: () => addParticle(++particleCount)
});
}
function doUbOpenParticles(scene: BattleScene, x: number, y: number) {
let particles: Phaser.GameObjects.Image[] = [];
for (let i = 0; i < 10; i++)
particles.push(doFanOutParticle(scene, i * 25, x, y, 1, 1, 5, 8));
scene.tweens.add({
targets: particles,
delay: 750,
duration: 250,
alpha: 0,
ease: 'Sine.easeIn',
onComplete: () => {
for (let particle of particles)
particle.destroy();
}
});
}
function doMbOpenParticles(scene: BattleScene, x: number, y: number) {
let particles: Phaser.GameObjects.Image[] = [];
for (let j = 0; j < 2; j++) {
for (let i = 0; i < 8; i++)
particles.push(doFanOutParticle(scene, i * 32, x, y, j ? 1 : 2, j ? 2 : 1, 8, 4));
scene.tweens.add({
targets: particles,
delay: 750,
duration: 250,
alpha: 0,
ease: 'Sine.easeIn',
onComplete: () => {
for (let particle of particles)
particle.destroy();
}
});
}
}
function doFanOutParticle(scene: BattleScene, trigIndex: integer, x: integer, y: integer, xSpeed: integer, ySpeed: integer, angle: integer, frameIndex: integer): Phaser.GameObjects.Image {
let f = 0;
const particle = scene.add.image(x, y, 'pb_particles', `${frameIndex}.png`);
scene.field.add(particle);
const updateParticle = () => {
if (!particle.scene)
return particleTimer.remove();
particle.x = x + sin(trigIndex, f * xSpeed);
particle.y = y + cos(trigIndex, f * ySpeed);
trigIndex = (trigIndex + angle);
f++;
};
const particleTimer = scene.tweens.addCounter({
repeat: -1,
duration: Utils.getFrameMs(1),
onRepeat: () => {
updateParticle();
}
});
return particle;
}
export function addPokeballCaptureStars(scene: BattleScene, pokeball: Phaser.GameObjects.Sprite): void {
const addParticle = () => {
const particle = scene.add.sprite(pokeball.x, pokeball.y, 'pb_particles', '4.png');
particle.setOrigin(pokeball.originX, pokeball.originY);
particle.setAlpha(0.5);
scene.field.add(particle);
scene.tweens.add({
targets: particle,
y: pokeball.y - 10,
ease: 'Sine.easeOut',
duration: 250,
onComplete: () => {
scene.tweens.add({
targets: particle,
y: pokeball.y,
alpha: 0,
ease: 'Sine.easeIn',
duration: 250
});
}
});
const dist = Utils.randGauss(25);
scene.tweens.add({
targets: particle,
x: pokeball.x + dist,
duration: 500
});
scene.tweens.add({
targets: particle,
alpha: 0,
delay: 425,
duration: 75,
onComplete: () => particle.destroy()
});
};
new Array(3).fill(null).map(() => addParticle());
}
export function sin(index: integer, amplitude: integer): number {
return amplitude * Math.sin(index * (Math.PI / 128));
}
export function cos(index: integer, amplitude: integer): number {
return amplitude * Math.cos(index * (Math.PI / 128));
}
+638
View File
@@ -0,0 +1,638 @@
import BattleScene from "../battle-scene";
import { BiomePoolTier, BiomeTierPokemonPools, PokemonPools, BiomeTierTrainerPools, biomePokemonPools, biomeTrainerPools } from "../data/biomes";
import { Biome } from "../data/enums/biome";
import * as Utils from "../utils";
import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species";
import { Species } from "../data/enums/species";
import { Weather, WeatherType, getWeatherClearMessage, getWeatherStartMessage } from "../data/weather";
import { CommonAnimPhase } from "../phases";
import { CommonAnim } from "../data/battle-anims";
import { Type } from "../data/type";
import Move from "../data/move";
import { ArenaTag, ArenaTagSide, getArenaTag } from "../data/arena-tag";
import { ArenaTagType } from "../data/enums/arena-tag-type";
import { GameMode } from "../game-mode";
import { TrainerType } from "../data/enums/trainer-type";
import { BattlerIndex } from "../battle";
import { Moves } from "../data/enums/moves";
import { TimeOfDay } from "../data/enums/time-of-day";
const WEATHER_OVERRIDE = WeatherType.NONE;
export class Arena {
public scene: BattleScene;
public biomeType: Biome;
public weather: Weather;
public tags: ArenaTag[];
public bgm: string;
private lastTimeOfDay: TimeOfDay;
private pokemonPool: PokemonPools;
private trainerPool: BiomeTierTrainerPools;
constructor(scene: BattleScene, biome: Biome, bgm: string) {
this.scene = scene;
this.biomeType = biome;
this.tags = [];
this.bgm = bgm;
this.trainerPool = biomeTrainerPools[biome];
this.updatePoolsForTimeOfDay();
}
updatePoolsForTimeOfDay(): void {
const timeOfDay = this.getTimeOfDay();
if (timeOfDay !== this.lastTimeOfDay) {
this.pokemonPool = {};
for (let tier of Object.keys(biomePokemonPools[this.biomeType]))
this.pokemonPool[tier] = Object.assign([], biomePokemonPools[this.biomeType][tier][TimeOfDay.ALL]).concat(biomePokemonPools[this.biomeType][tier][timeOfDay]);
this.lastTimeOfDay = timeOfDay;
}
}
randomSpecies(waveIndex: integer, level: integer, attempt?: integer): PokemonSpecies {
const isBoss = !!this.scene.getEncounterBossSegments(waveIndex, level) && !!this.pokemonPool[BiomePoolTier.BOSS].length
&& (this.biomeType !== Biome.END || this.scene.gameMode === GameMode.CLASSIC || waveIndex % 250 === 0);
const tierValue = Utils.randSeedInt(!isBoss ? 512 : 64);
let tier = !isBoss
? tierValue >= 156 ? BiomePoolTier.COMMON : tierValue >= 32 ? BiomePoolTier.UNCOMMON : tierValue >= 6 ? BiomePoolTier.RARE : tierValue >= 1 ? BiomePoolTier.SUPER_RARE : BiomePoolTier.ULTRA_RARE
: tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE;
console.log(BiomePoolTier[tier]);
while (!this.pokemonPool[tier].length) {
console.log(`Downgraded rarity tier from ${BiomePoolTier[tier]} to ${BiomePoolTier[tier - 1]}`);
tier--;
}
const tierPool = this.pokemonPool[tier];
let ret: PokemonSpecies;
let regen = false;
if (!tierPool.length)
ret = this.scene.randomSpecies(waveIndex, level);
else {
const entry = tierPool[Utils.randSeedInt(tierPool.length)];
let species: Species;
if (typeof entry === 'number')
species = entry as Species;
else {
const levelThresholds = Object.keys(entry);
for (let l = levelThresholds.length - 1; l >= 0; l--) {
const levelThreshold = parseInt(levelThresholds[l]);
if (level >= levelThreshold) {
const speciesIds = entry[levelThreshold];
if (speciesIds.length > 1)
species = speciesIds[Utils.randSeedInt(speciesIds.length)];
else
species = speciesIds[0];
break;
}
}
}
ret = getPokemonSpecies(species);
if (ret.pseudoLegendary || ret.legendary || ret.mythical) {
switch (true) {
case (ret.baseTotal >= 720):
regen = level < 90;
break;
case (ret.baseTotal >= 670):
regen = level < 70;
break;
case (ret.baseTotal >= 580):
regen = level < 50;
break;
default:
regen = level < 30;
break;
}
}
}
if (regen && (attempt || 0) < 10) {
console.log('Incompatible level: regenerating...');
return this.randomSpecies(waveIndex, level, (attempt || 0) + 1);
}
const newSpeciesId = ret.getSpeciesForLevel(level, true, false, isBoss);
if (newSpeciesId !== ret.speciesId) {
console.log('Replaced', Species[ret.speciesId], 'with', Species[newSpeciesId]);
ret = getPokemonSpecies(newSpeciesId);
}
return ret;
}
randomTrainerType(waveIndex: integer): TrainerType {
const isBoss = (waveIndex % 30) === 20 && !!this.trainerPool[BiomePoolTier.BOSS].length
&& (this.biomeType !== Biome.END || this.scene.gameMode === GameMode.CLASSIC || waveIndex % 250 === 0);
const tierValue = Utils.randSeedInt(!isBoss ? 512 : 64);
let tier = !isBoss
? tierValue >= 156 ? BiomePoolTier.COMMON : tierValue >= 32 ? BiomePoolTier.UNCOMMON : tierValue >= 6 ? BiomePoolTier.RARE : tierValue >= 1 ? BiomePoolTier.SUPER_RARE : BiomePoolTier.ULTRA_RARE
: tierValue >= 20 ? BiomePoolTier.BOSS : tierValue >= 6 ? BiomePoolTier.BOSS_RARE : tierValue >= 1 ? BiomePoolTier.BOSS_SUPER_RARE : BiomePoolTier.BOSS_ULTRA_RARE;
console.log(BiomePoolTier[tier]);
while (tier && !this.trainerPool[tier].length) {
console.log(`Downgraded trainer rarity tier from ${BiomePoolTier[tier]} to ${BiomePoolTier[tier - 1]}`);
tier--;
}
const tierPool = this.trainerPool[tier] || [];
return !tierPool.length ? TrainerType.BREEDER : tierPool[Utils.randSeedInt(tierPool.length)];
}
getSpeciesFormIndex(species: PokemonSpecies): integer {
switch (species.speciesId) {
case Species.BURMY:
case Species.WORMADAM:
switch (this.biomeType) {
case Biome.BEACH:
return 1;
case Biome.SLUM:
return 2;
}
break;
case Species.SCATTERBUG:
case Species.SPEWPA:
case Species.VIVILLON:
return 0;
case Species.LYCANROC:
const timeOfDay = this.getTimeOfDay();
switch (timeOfDay) {
case TimeOfDay.DAY:
case TimeOfDay.DAWN:
return 0;
case TimeOfDay.DUSK:
return 2;
case TimeOfDay.NIGHT:
return 1;
}
case Species.CALYREX:
switch (this.biomeType) {
case Biome.SNOWY_FOREST:
return 1;
case Biome.GRAVEYARD:
return 2;
}
break;
}
return 0;
}
getTypeForBiome() {
switch (this.biomeType) {
case Biome.TOWN:
case Biome.PLAINS:
case Biome.METROPOLIS:
return Type.NORMAL;
case Biome.GRASS:
case Biome.TALL_GRASS:
return Type.GRASS;
case Biome.FOREST:
return Type.BUG;
case Biome.SLUM:
case Biome.SWAMP:
return Type.POISON;
case Biome.SEA:
case Biome.BEACH:
case Biome.LAKE:
case Biome.SEABED:
return Type.WATER;
case Biome.MOUNTAIN:
return Type.FLYING;
case Biome.BADLANDS:
return Type.GROUND;
case Biome.CAVE:
case Biome.DESERT:
return Type.ROCK;
case Biome.ICE_CAVE:
case Biome.SNOWY_FOREST:
return Type.ICE;
case Biome.MEADOW:
case Biome.FAIRY_CAVE:
case Biome.ISLAND:
return Type.FAIRY;
case Biome.POWER_PLANT:
return Type.ELECTRIC;
case Biome.VOLCANO:
return Type.FIRE;
case Biome.GRAVEYARD:
case Biome.TEMPLE:
return Type.GHOST;
case Biome.DOJO:
return Type.FIGHTING;
case Biome.FACTORY:
return Type.STEEL;
case Biome.RUINS:
case Biome.SPACE:
return Type.PSYCHIC;
case Biome.WASTELAND:
case Biome.END:
return Type.DRAGON;
case Biome.ABYSS:
return Type.DARK;
}
}
trySetWeatherOverride(weather: WeatherType): boolean {
this.weather = new Weather(weather, 0);
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1)));
this.scene.queueMessage(getWeatherStartMessage(weather));
return true
}
trySetWeather(weather: WeatherType, viaMove: boolean): boolean {
// override hook for debugging
if (WEATHER_OVERRIDE) {
return this.trySetWeatherOverride(WEATHER_OVERRIDE);
}
if (this.weather?.weatherType === (weather || undefined))
return false;
const oldWeatherType = this.weather?.weatherType || WeatherType.NONE;
this.weather = weather ? new Weather(weather, viaMove ? 5 : 0) : null;
if (this.weather) {
this.scene.unshiftPhase(new CommonAnimPhase(this.scene, undefined, undefined, CommonAnim.SUNNY + (weather - 1)));
this.scene.queueMessage(getWeatherStartMessage(weather));
} else
this.scene.queueMessage(getWeatherClearMessage(oldWeatherType));
return true;
}
isMoveWeatherCancelled(move: Move) {
return this.weather && !this.weather.isEffectSuppressed(this.scene) && this.weather.isMoveWeatherCancelled(move);
}
getAttackTypeMultiplier(attackType: Type): number {
if (!this.weather || this.weather.isEffectSuppressed(this.scene))
return 1;
return this.weather.getAttackTypeMultiplier(attackType);
}
getTrainerChance(): integer {
switch (this.biomeType) {
case Biome.METROPOLIS:
return 2;
case Biome.SLUM:
case Biome.BEACH:
case Biome.DOJO:
case Biome.CONSTRUCTION_SITE:
return 4;
case Biome.PLAINS:
case Biome.GRASS:
case Biome.LAKE:
case Biome.CAVE:
return 6;
case Biome.TALL_GRASS:
case Biome.FOREST:
case Biome.SEA:
case Biome.SWAMP:
case Biome.MOUNTAIN:
case Biome.BADLANDS:
case Biome.DESERT:
case Biome.MEADOW:
case Biome.POWER_PLANT:
case Biome.GRAVEYARD:
case Biome.FACTORY:
case Biome.SNOWY_FOREST:
return 8;
case Biome.ICE_CAVE:
case Biome.VOLCANO:
case Biome.RUINS:
case Biome.WASTELAND:
case Biome.JUNGLE:
case Biome.FAIRY_CAVE:
return 12;
case Biome.SEABED:
case Biome.ABYSS:
case Biome.SPACE:
case Biome.TEMPLE:
return 16;
default:
return 0;
}
}
getTimeOfDay(): TimeOfDay {
switch (this.biomeType) {
case Biome.ABYSS:
return TimeOfDay.NIGHT;
}
const waveCycle = ((this.scene.currentBattle?.waveIndex || 0) + this.scene.getWaveCycleOffset()) % 40;
if (waveCycle < 15)
return TimeOfDay.DAY;
if (waveCycle < 20)
return TimeOfDay.DUSK;
if (waveCycle < 35)
return TimeOfDay.NIGHT;
return TimeOfDay.DAWN;
}
isOutside(): boolean {
switch (this.biomeType) {
case Biome.SEABED:
case Biome.CAVE:
case Biome.ICE_CAVE:
case Biome.POWER_PLANT:
case Biome.DOJO:
case Biome.FACTORY:
case Biome.ABYSS:
case Biome.FAIRY_CAVE:
case Biome.TEMPLE:
case Biome.LABORATORY:
return false;
default:
return true;
}
}
getDayTint(): [integer, integer, integer] {
switch (this.biomeType) {
case Biome.ABYSS:
return [ 64, 64, 64 ];
default:
return [ 128, 128, 128 ];
}
}
getDuskTint(): [integer, integer, integer] {
if (!this.isOutside())
return [ 0, 0, 0 ];
switch (this.biomeType) {
default:
return [ 98, 48, 73 ].map(c => Math.round((c + 128) / 2)) as [integer, integer, integer];
}
}
getNightTint(): [integer, integer, integer] {
switch (this.biomeType) {
case Biome.ABYSS:
case Biome.SPACE:
case Biome.END:
return this.getDayTint();
}
if (!this.isOutside())
return [ 64, 64, 64 ];
switch (this.biomeType) {
default:
return [ 48, 48, 98 ];
}
}
applyTagsForSide(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }, side: ArenaTagSide, ...args: any[]): void {
let tags = typeof tagType === 'string'
? this.tags.filter(t => t.tagType === tagType)
: this.tags.filter(t => t instanceof tagType);
if (side !== ArenaTagSide.BOTH)
tags = tags.filter(t => t.side === side);
tags.forEach(t => t.apply(this, args));
}
applyTags(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }, ...args: any[]): void {
this.applyTagsForSide(tagType, ArenaTagSide.BOTH, ...args);
}
addTag(tagType: ArenaTagType, turnCount: integer, sourceMove: Moves, sourceId: integer, side: ArenaTagSide = ArenaTagSide.BOTH, targetIndex?: BattlerIndex): boolean {
const existingTag = this.getTag(tagType);
if (existingTag) {
existingTag.onOverlap(this);
return false;
}
const newTag = getArenaTag(tagType, turnCount || 0, sourceMove, sourceId, targetIndex, side);
this.tags.push(newTag);
newTag.onAdd(this);
return true;
}
getTag(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }): ArenaTag {
return this.getTagOnSide(tagType, ArenaTagSide.BOTH);
}
getTagOnSide(tagType: ArenaTagType | { new(...args: any[]): ArenaTag }, side: ArenaTagSide): ArenaTag {
return typeof(tagType) === 'string'
? this.tags.find(t => t.tagType === tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side))
: this.tags.find(t => t instanceof tagType && (side === ArenaTagSide.BOTH || t.side === ArenaTagSide.BOTH || t.side === side));
}
lapseTags(): void {
const tags = this.tags;
tags.filter(t => !(t.lapse(this))).forEach(t => {
t.onRemove(this);
tags.splice(tags.indexOf(t), 1);
});
}
removeAllTags(): void {
for (let t of this.tags) {
t.onRemove(this);
this.tags.splice(this.tags.indexOf(t), 1);
}
}
preloadBgm(): void {
this.scene.loadBgm(this.bgm);
}
getBgmLoopPoint(): number {
switch (this.biomeType) {
case Biome.TOWN:
return 7.288;
case Biome.PLAINS:
return 7.693;
case Biome.GRASS:
return 1.995;
case Biome.TALL_GRASS:
return 9.608;
case Biome.METROPOLIS:
return 4.867;
case Biome.FOREST:
return 4.294;
case Biome.SEA:
return 1.672;
case Biome.SWAMP:
return 4.461;
case Biome.BEACH:
return 3.462;
case Biome.LAKE:
return 5.350;
case Biome.SEABED:
return 2.629;
case Biome.MOUNTAIN:
return 4.018;
case Biome.BADLANDS:
return 17.790;
case Biome.CAVE:
return 14.240;
case Biome.DESERT:
return 1.143;
case Biome.ICE_CAVE:
return 15.010;
case Biome.MEADOW:
return 3.891;
case Biome.POWER_PLANT:
return 2.810;
case Biome.VOLCANO:
return 5.116;
case Biome.GRAVEYARD:
return 3.232;
case Biome.DOJO:
return 6.205;
case Biome.FACTORY:
return 4.985;
case Biome.RUINS:
return 2.270;
case Biome.WASTELAND:
return 6.336;
case Biome.ABYSS:
return 5.130;
case Biome.SPACE:
return 21.347;
case Biome.CONSTRUCTION_SITE:
return 1.222;
case Biome.JUNGLE:
return 2.477;
case Biome.FAIRY_CAVE:
return 4.542;
case Biome.TEMPLE:
return 2.547;
case Biome.ISLAND:
return 2.751;
case Biome.LABORATORY:
return 0.797;
case Biome.SLUM:
return 1.221;
case Biome.SNOWY_FOREST:
return 3.047;
}
}
}
export function getBiomeKey(biome: Biome): string {
switch (biome) {
case Biome.POWER_PLANT:
return 'ruins';
case Biome.METROPOLIS:
return 'slum';
case Biome.JUNGLE:
return 'tall_grass';
case Biome.ISLAND:
return 'beach';
}
return Biome[biome].toLowerCase();
}
export function getBiomeHasProps(biomeType: Biome): boolean {
switch (biomeType) {
case Biome.BEACH:
case Biome.LAKE:
case Biome.SEABED:
case Biome.MOUNTAIN:
case Biome.BADLANDS:
case Biome.CAVE:
case Biome.DESERT:
case Biome.ICE_CAVE:
case Biome.MEADOW:
case Biome.VOLCANO:
case Biome.GRAVEYARD:
case Biome.FACTORY:
case Biome.RUINS:
case Biome.WASTELAND:
case Biome.ABYSS:
case Biome.CONSTRUCTION_SITE:
case Biome.FAIRY_CAVE:
case Biome.TEMPLE:
case Biome.LABORATORY:
case Biome.END:
return true;
}
return false;
}
export class ArenaBase extends Phaser.GameObjects.Container {
public player: boolean;
public biome: Biome;
public propValue: integer;
public base: Phaser.GameObjects.Sprite;
public props: Phaser.GameObjects.Sprite[];
constructor(scene: BattleScene, player: boolean) {
super(scene, 0, 0);
this.player = player;
this.base = scene.addFieldSprite(0, 0, 'plains_a');
this.base.setOrigin(0, 0);
this.props = !player ?
new Array(3).fill(null).map(() => {
const ret = scene.addFieldSprite(0, 0, 'plains_b');
ret.setOrigin(0, 0);
ret.setVisible(false);
return ret;
}) : [];
}
setBiome(biome: Biome, propValue?: integer): void {
if (this.biome === biome)
return;
const hasProps = getBiomeHasProps(biome);
const biomeKey = getBiomeKey(biome);
const baseKey = `${biomeKey}_${this.player ? 'a' : 'b'}`;
this.base.setTexture(baseKey);
if (this.base.texture.frameTotal > 1) {
const baseFrameNames = this.scene.anims.generateFrameNames(baseKey, { zeroPad: 4, suffix: ".png", start: 1, end: this.base.texture.frameTotal - 1 });
this.scene.anims.create({
key: baseKey,
frames: baseFrameNames,
frameRate: 12,
repeat: -1
});
this.base.play(baseKey);
} else
this.base.stop();
this.add(this.base);
if (!this.player) {
(this.scene as BattleScene).executeWithSeedOffset(() => {
this.propValue = propValue === undefined
? hasProps ? Utils.randSeedInt(8) : 0
: propValue;
this.props.forEach((prop, p) => {
const propKey = `${biomeKey}_b${hasProps ? `_${p + 1}` : ''}`;
prop.setTexture(propKey);
if (hasProps && prop.texture.frameTotal > 1) {
const propFrameNames = this.scene.anims.generateFrameNames(propKey, { zeroPad: 4, suffix: ".png", start: 1, end: prop.texture.frameTotal - 1 });
this.scene.anims.create({
key: propKey,
frames: propFrameNames,
frameRate: 12,
repeat: -1
});
prop.play(propKey);
} else
prop.stop();
prop.setVisible(hasProps && !!(this.propValue & (1 << p)));
this.add(prop);
});
}, (this.scene as BattleScene).currentBattle?.waveIndex || 0);
}
}
}
@@ -0,0 +1,70 @@
import BattleScene from "../battle-scene";
import Pokemon from "./pokemon";
import * as Utils from "../utils";
export default class PokemonSpriteSparkleHandler {
private sprites: Set<Phaser.GameObjects.Sprite>;
setup(scene: BattleScene): void {
this.sprites = new Set();
scene.tweens.addCounter({
duration: Utils.fixedInt(200),
from: 0,
to: 1,
yoyo: true,
repeat: -1,
onRepeat: () => this.onLapse()
});
}
onLapse(): void {
Array.from(this.sprites.values()).filter(s => !s.scene).map(s => this.sprites.delete(s));
for (let s of this.sprites.values()) {
if (!s.pipelineData['teraColor'] || !(s.pipelineData['teraColor'] as number[]).find(c => c))
continue;
if (!s.visible || (s.parentContainer instanceof Pokemon && !s.parentContainer.parentContainer))
continue;
const pokemon = s.parentContainer instanceof Pokemon ? s.parentContainer as Pokemon : null;
const parent = (pokemon || s).parentContainer;
const texture = s.texture;
const [ width, height ] = [ texture.source[0].width, texture.source[0].height ];
const [ pixelX, pixelY ] = [ Utils.randInt(width), Utils.randInt(height) ];
const ratioX = s.width / width;
const ratioY = s.height / height;
const pixel = texture.manager.getPixel(pixelX, pixelY, texture.key, '__BASE');
if (pixel.alpha) {
const [ xOffset, yOffset ] = [ -s.originX * s.width, -s.originY * s.height];
const sparkle = (s.scene as BattleScene).addFieldSprite(((pokemon?.x || 0) + s.x + pixelX * ratioX + xOffset), ((pokemon?.y || 0) + s.y + pixelY * ratioY + yOffset), 'tera_sparkle');
sparkle.pipelineData['ignoreTimeTint'] = s.pipelineData['ignoreTimeTint'];
sparkle.play('tera_sparkle');
const teraColor = s.pipelineData['teraColor'] as number[];
parent.add(sparkle);
s.scene.time.delayedCall(Utils.fixedInt(Math.floor((1000 / 12) * 13)), () => sparkle.destroy());
}
}
}
add(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void {
if (!Array.isArray(sprites))
sprites = [ sprites ];
for (let s of sprites) {
if (this.sprites.has(s))
continue;
this.sprites.add(s);
}
}
remove(sprites: Phaser.GameObjects.Sprite | Phaser.GameObjects.Sprite[]): void {
if (!Array.isArray(sprites))
sprites = [ sprites ];
for (let s of sprites) {
this.sprites.delete(s);
}
}
removeAll(): void {
for (let s of this.sprites.values())
this.sprites.delete(s);
}
}
+2577
View File
File diff suppressed because it is too large. Load diff
+343
View File
@@ -0,0 +1,343 @@
import BattleScene from "../battle-scene";
import { pokemonPrevolutions } from "../data/pokemon-evolutions";
import PokemonSpecies, { getPokemonSpecies } from "../data/pokemon-species";
import { TrainerConfig, TrainerPartyCompoundTemplate, TrainerPartyMemberStrength, TrainerPartyTemplate, TrainerPoolTier, trainerConfigs, trainerPartyTemplates } from "../data/trainer-config";
import { TrainerType } from "../data/enums/trainer-type";
import { EnemyPokemon } from "./pokemon";
import * as Utils from "../utils";
import { PersistentModifier } from "../modifier/modifier";
export default class Trainer extends Phaser.GameObjects.Container {
public config: TrainerConfig;
public female: boolean;
public partyTemplateIndex: integer;
constructor(scene: BattleScene, trainerType: TrainerType, female?: boolean, partyTemplateIndex?: integer) {
super(scene, -72, 80);
this.config = trainerConfigs.hasOwnProperty(trainerType)
? trainerConfigs[trainerType]
: trainerConfigs[TrainerType.ACE_TRAINER];
this.female = female;
this.partyTemplateIndex = Math.min(partyTemplateIndex !== undefined ? partyTemplateIndex : Utils.randSeedWeightedItem(this.config.partyTemplates.map((_, i) => i)),
this.config.partyTemplates.length - 1);
console.log(Object.keys(trainerPartyTemplates)[Object.values(trainerPartyTemplates).indexOf(this.getPartyTemplate())]);
const getSprite = (hasShadow?: boolean) => {
const ret = this.scene.addFieldSprite(0, 0, this.getKey());
ret.setOrigin(0.5, 1);
ret.setPipeline(this.scene.spritePipeline, { tone: [ 0.0, 0.0, 0.0, 0.0 ], hasShadow: !!hasShadow });
return ret;
};
const sprite = getSprite(true);
const tintSprite = getSprite();
tintSprite.setVisible(false);
this.add(sprite);
this.add(tintSprite);
}
getKey(): string {
return this.config.getKey(this.female);
}
getName(includeTitle: boolean = false): string {
let name = this.config.getName(this.female);
return includeTitle && this.config.title ? `${this.config.title} ${name}` : name;
}
getBattleBgm(): string {
return this.config.battleBgm;
}
getEncounterBgm(): string {
return !this.female ? this.config.encounterBgm : this.config.femaleEncounterBgm || this.config.encounterBgm;
}
getEncounterMessages(): string[] {
return !this.female || !this.config.femaleEncounterMessages ? this.config.encounterMessages : this.config.femaleEncounterMessages;
}
getVictoryMessages(): string[] {
return !this.female || !this.config.femaleVictoryMessages ? this.config.victoryMessages : this.config.femaleVictoryMessages;
}
getDefeatMessages(): string[] {
return !this.female || !this.config.femaleDefeatMessages ? this.config.defeatMessages : this.config.femaleDefeatMessages;
}
getPartyTemplate(): TrainerPartyTemplate {
if (this.config.partyTemplateFunc)
return this.config.partyTemplateFunc(this.scene);
return this.config.partyTemplates[this.partyTemplateIndex];
}
getPartyLevels(waveIndex: integer): integer[] {
const ret = [];
const partyTemplate = this.getPartyTemplate();
let baseLevel = 1 + waveIndex / 2 + Math.pow(waveIndex / 25, 2);
for (let i = 0; i < partyTemplate.size; i++) {
let multiplier = 1;
const strength = partyTemplate.getStrength(i);
switch (strength) {
case TrainerPartyMemberStrength.WEAKER:
multiplier = 0.95;
break;
case TrainerPartyMemberStrength.WEAK:
multiplier = 1.0;
break;
case TrainerPartyMemberStrength.AVERAGE:
multiplier = 1.1;
break;
case TrainerPartyMemberStrength.STRONG:
multiplier = 1.2;
break;
case TrainerPartyMemberStrength.STRONGER:
multiplier = 1.25;
break;
}
let levelOffset = 0;
if (strength < TrainerPartyMemberStrength.STRONG) {
multiplier = Math.min(multiplier + 0.025 * Math.floor(waveIndex / 25), 1.2);
levelOffset = -Math.floor((waveIndex / 50) * (TrainerPartyMemberStrength.STRONG - strength));
}
const level = Math.ceil(baseLevel * multiplier) + levelOffset;
ret.push(level);
}
return ret;
}
genPartyMember(index: integer): EnemyPokemon {
const battle = this.scene.currentBattle;
const level = battle.enemyLevels[index];
let ret: EnemyPokemon;
this.scene.executeWithSeedOffset(() => {
const template = this.getPartyTemplate();
if (this.config.partyMemberFuncs.hasOwnProperty(index)) {
ret = this.config.partyMemberFuncs[index](this.scene, level);
return;
}
if (this.config.partyMemberFuncs.hasOwnProperty(index - template.size)) {
ret = this.config.partyMemberFuncs[index - template.size](this.scene, level);
return;
}
let offset = 0;
if (template instanceof TrainerPartyCompoundTemplate) {
for (let innerTemplate of template.templates) {
if (offset + innerTemplate.size > index)
break;
offset += innerTemplate.size;
}
}
const species = template.isSameSpecies(index) && index > offset
? getPokemonSpecies(battle.enemyParty[offset].species.getSpeciesForLevel(level, false, true, this.config.isBoss))
: this.genNewPartyMemberSpecies(level);
ret = this.scene.addEnemyPokemon(species, level, true);
}, this.config.hasStaticParty ? this.config.getDerivedType() + ((index + 1) << 8) : this.scene.currentBattle.waveIndex + (this.config.getDerivedType() << 10) + (((!this.config.useSameSeedForAllMembers ? index : 0) + 1) << 8));
return ret;
}
genNewPartyMemberSpecies(level: integer, attempt?: integer): PokemonSpecies {
const battle = this.scene.currentBattle;
const template = this.getPartyTemplate();
let species: PokemonSpecies;
if (this.config.speciesPools) {
const tierValue = Utils.randSeedInt(512);
let tier = tierValue >= 156 ? TrainerPoolTier.COMMON : tierValue >= 32 ? TrainerPoolTier.UNCOMMON : tierValue >= 6 ? TrainerPoolTier.RARE : tierValue >= 1 ? TrainerPoolTier.SUPER_RARE : TrainerPoolTier.ULTRA_RARE
console.log(TrainerPoolTier[tier]);
while (!this.config.speciesPools.hasOwnProperty(tier) || !this.config.speciesPools[tier].length) {
console.log(`Downgraded trainer Pokemon rarity tier from ${TrainerPoolTier[tier]} to ${TrainerPoolTier[tier - 1]}`);
tier--;
}
const tierPool = this.config.speciesPools[tier];
species = getPokemonSpecies(Utils.randSeedItem(tierPool));
} else
species = this.scene.randomSpecies(battle.waveIndex, level, false, this.config.speciesFilter);
let ret = getPokemonSpecies(species.getSpeciesForLevel(level, true, true, this.config.isBoss));
let retry = false;
console.log(ret.getName());
if (pokemonPrevolutions.hasOwnProperty(species.speciesId) && ret.speciesId !== species.speciesId)
retry = true;
else if (template.isBalanced(battle.enemyParty.length)) {
const partyMemberTypes = battle.enemyParty.map(p => p.getTypes(true)).flat();
if (partyMemberTypes.indexOf(ret.type1) > -1 || (ret.type2 !== null && partyMemberTypes.indexOf(ret.type2) > -1))
retry = true;
}
if (!retry && this.config.specialtyTypes.length && !this.config.specialtyTypes.find(t => ret.isOfType(t))) {
retry = true;
console.log('Attempting reroll of species evolution to fit specialty type...');
let evoAttempt = 0;
while (retry && evoAttempt++ < 10) {
ret = getPokemonSpecies(species.getSpeciesForLevel(level, true, true, this.config.isBoss));
console.log(ret.name);
if (this.config.specialtyTypes.find(t => ret.isOfType(t)))
retry = false;
}
}
if (retry && (attempt || 0) < 10) {
console.log('Rerolling party member...')
ret = this.genNewPartyMemberSpecies(level, (attempt || 0) + 1);
}
return ret;
}
getPartyMemberMatchupScores(): [integer, integer][] {
const party = this.scene.getEnemyParty();
const nonFaintedPartyMembers = party.slice(this.scene.currentBattle.getBattlerCount()).filter(p => !p.isFainted());
const partyMemberScores = nonFaintedPartyMembers.map(p => {
const playerField = this.scene.getPlayerField();
let score = 0;
let ret: [integer, integer];
for (let playerPokemon of playerField) {
score += p.getMatchupScore(playerPokemon);
if (playerPokemon.species.legendary)
score /= 2;
}
score /= playerField.length;
ret = [ party.indexOf(p), score ];
return ret;
});
return partyMemberScores;
}
getSortedPartyMemberMatchupScores(partyMemberScores: [integer, integer][] = this.getPartyMemberMatchupScores()) {
const sortedPartyMemberScores = partyMemberScores.slice(0);
sortedPartyMemberScores.sort((a, b) => {
const scoreA = a[1];
const scoreB = b[1];
return scoreA < scoreB ? 1 : scoreA > scoreB ? -1 : 0;
});
return sortedPartyMemberScores;
}
getNextSummonIndex(partyMemberScores: [integer, integer][] = this.getPartyMemberMatchupScores()): integer {
const sortedPartyMemberScores = this.getSortedPartyMemberMatchupScores(partyMemberScores);
const maxScorePartyMemberIndexes = partyMemberScores.filter(pms => pms[1] === sortedPartyMemberScores[0][1]).map(pms => pms[0]);
if (maxScorePartyMemberIndexes.length > 1) {
let rand: integer;
this.scene.executeWithSeedOffset(() => rand = Utils.randSeedInt(maxScorePartyMemberIndexes.length), this.scene.currentBattle.turn << 2);
return maxScorePartyMemberIndexes[rand];
}
return maxScorePartyMemberIndexes[0];
}
getPartyMemberModifierChanceMultiplier(index: integer): number {
switch (this.getPartyTemplate().getStrength(index)) {
case TrainerPartyMemberStrength.WEAKER:
return 0.75;
case TrainerPartyMemberStrength.WEAK:
return 0.675;
case TrainerPartyMemberStrength.AVERAGE:
return 0.5625;
case TrainerPartyMemberStrength.STRONG:
return 0.45;
case TrainerPartyMemberStrength.STRONGER:
return 0.375;
}
}
genModifiers(party: EnemyPokemon[]): PersistentModifier[] {
if (this.config.genModifiersFunc)
return this.config.genModifiersFunc(party);
return [];
}
loadAssets(): Promise<void> {
return this.config.loadAssets(this.scene, this.female);
}
initSprite(): void {
this.getSprite().setTexture(this.getKey()).setFrame(0);
this.getTintSprite().setTexture(this.getKey()).setFrame(0);
}
playAnim(): void {
const trainerAnimConfig = {
key: this.getKey(),
repeat: 0,
startFrame: 0
};
this.getSprite().play(trainerAnimConfig);
this.getTintSprite().play(trainerAnimConfig);
}
getSprite(): Phaser.GameObjects.Sprite {
return this.getAt(0) as Phaser.GameObjects.Sprite;
}
getTintSprite(): Phaser.GameObjects.Sprite {
return this.getAt(1) as Phaser.GameObjects.Sprite;
}
tint(color: number, alpha?: number, duration?: integer, ease?: string): void {
const tintSprite = this.getTintSprite();
tintSprite.setTintFill(color);
tintSprite.setVisible(true);
if (duration) {
tintSprite.setAlpha(0);
this.scene.tweens.add({
targets: tintSprite,
alpha: alpha || 1,
duration: duration,
ease: ease || 'Linear'
});
} else
tintSprite.setAlpha(alpha);
}
untint(duration: integer, ease?: string): void {
const tintSprite = this.getTintSprite();
if (duration) {
this.scene.tweens.add({
targets: tintSprite,
alpha: 0,
duration: duration,
ease: ease || 'Linear',
onComplete: () => {
tintSprite.setVisible(false);
tintSprite.setAlpha(1);
}
});
} else {
tintSprite.setVisible(false);
tintSprite.setAlpha(1);
}
}
}
export default interface Trainer {
scene: BattleScene
}