diff --git a/src/battle-scene.ts b/src/battle-scene.ts index f97b00247..3cf8e547a 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -17,7 +17,7 @@ import { TextStyle, addTextObject } from './ui/text'; import { Moves } from "./data/enums/moves"; import { } from "./data/move"; import { initMoves } from './data/move'; -import { ModifierPoolType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave } from './modifier/modifier-type'; +import { ModifierPoolType, PokemonBaseStatBoosterModifierType, getDefaultModifierTypeForTier, getEnemyModifierTypesForWave } from './modifier/modifier-type'; import AbilityBar from './ui/ability-bar'; import { BlockItemTheftAbAttr, DoubleBattleChanceAbAttr, applyAbAttrs, initAbilities } from './data/ability'; import Battle, { BattleType, FixedBattleConfig, fixedBattles } from './battle'; @@ -131,6 +131,7 @@ export default class BattleScene extends Phaser.Scene { public arenaNextEnemy: ArenaBase; public arena: Arena; public gameMode: GameMode; + public score: integer; public trainer: Phaser.GameObjects.Sprite; public lastEnemyTrainer: Trainer; public currentBattle: Battle; @@ -761,6 +762,7 @@ export default class BattleScene extends Phaser.Scene { this.gameMode = gameModes[GameModes.CLASSIC]; + this.score = 0; this.money = 0; this.pokeballCounts = Object.fromEntries(Utils.getEnumValues(PokeballType).filter(p => p <= PokeballType.MASTER_BALL).map(t => [ t, 0 ])); @@ -1138,6 +1140,14 @@ export default class BattleScene extends Phaser.Scene { this.ui?.achvBar.setY((this.game.canvas.height / 6 + this.moneyText.y + 15)); } + addFaintedEnemyScore(enemy: EnemyPokemon): void { + let scoreIncrease = enemy.getSpeciesForm().getBaseExp() * (enemy.level / this.getMaxExpLevel()) * ((enemy.ivs.reduce((iv: integer, total: integer) => total += iv, 0) / 93) * 0.2 + 0.8); + this.findModifiers(m => m instanceof PokemonHeldItemModifier && m.pokemonId === enemy.id, false).map(m => scoreIncrease *= (m as PokemonHeldItemModifier).getScoreMultiplier()); + if (enemy.isBoss()) + scoreIncrease *= Math.sqrt(enemy.bossSegments); + this.currentBattle.battleScore += Math.ceil(scoreIncrease); + } + getMaxExpLevel(ignoreLevelCap?: boolean): integer { if (ignoreLevelCap) return Number.MAX_SAFE_INTEGER; diff --git a/src/battle.ts b/src/battle.ts index 58157481e..10fc679e0 100644 --- a/src/battle.ts +++ b/src/battle.ts @@ -51,6 +51,7 @@ export default class Battle { public turn: integer; public turnCommands: TurnCommands; public playerParticipantIds: Set; + public battleScore: integer; public postBattleLoot: PokemonHeldItemModifier[]; public escapeAttempts: integer; public lastMove: Moves; @@ -71,6 +72,7 @@ export default class Battle { this.double = double; this.turn = 0; this.playerParticipantIds = new Set(); + this.battleScore = 0; this.postBattleLoot = []; this.escapeAttempts = 0; this.started = false; @@ -143,6 +145,17 @@ export default class Battle { })); } + addBattleScore(scene: BattleScene): void { + let partyMemberTurnMultiplier = scene.getEnemyParty().length / 2 + 0.5; + if (this.double) + partyMemberTurnMultiplier /= 1.5; + const turnMultiplier = Phaser.Tweens.Builders.GetEaseFunction('Sine.easeIn')(1 - Math.min(this.turn - 2, 10 * partyMemberTurnMultiplier) / (10 * partyMemberTurnMultiplier)); + const finalBattleScore = Math.ceil(this.battleScore * turnMultiplier); + scene.score += finalBattleScore; + console.log(`Battle Score: ${finalBattleScore} (${this.turn - 1} Turns x${Math.floor(turnMultiplier * 100) / 100})`); + console.log(`Total Score: ${scene.score}`); + } + getBgmOverride(scene: BattleScene): string { const battlers = this.enemyParty.slice(0, this.getBattlerCount()); if (this.battleType === BattleType.TRAINER) { diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index 09b52f657..afbaf7d53 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -165,6 +165,21 @@ export abstract class PokemonSpeciesForm { return false; } + getBaseExp(): integer { + let ret = this.baseExp; + switch (this.getFormSpriteKey()) { + case SpeciesFormKey.MEGA: + case SpeciesFormKey.MEGA_X: + case SpeciesFormKey.MEGA_Y: + case SpeciesFormKey.PRIMAL: + case SpeciesFormKey.GIGANTAMAX: + case SpeciesFormKey.ETERNAMAX: + ret *= 1.5; + break; + } + return ret; + } + getSpriteAtlasPath(female: boolean, formIndex?: integer, shiny?: boolean): string { return this.getSpriteId(female, formIndex, shiny).replace(/\_{2}/g, '/'); } diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 08701df30..9a88ea1d0 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -1662,7 +1662,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { getExpValue(): integer { // Logic to factor in victor level has been removed for balancing purposes, so the player doesn't have to focus on EXP maxxing - return ((this.getSpeciesForm().baseExp * this.level) / 5 + 1); + return ((this.getSpeciesForm().getBaseExp() * this.level) / 5 + 1); } setFrameRate(frameRate: integer) { diff --git a/src/modifier/modifier.ts b/src/modifier/modifier.ts index db4351afb..0f517fb8b 100644 --- a/src/modifier/modifier.ts +++ b/src/modifier/modifier.ts @@ -495,6 +495,10 @@ export abstract class PokemonHeldItemModifier extends PersistentModifier { return scene.getPokemonById(this.pokemonId); } + getScoreMultiplier(): number { + return 1; + } + getMaxStackCount(scene: BattleScene, forThreshold?: boolean): integer { const pokemon = this.getPokemon(scene); if (!pokemon) @@ -585,6 +589,14 @@ export class TerastallizeModifier extends LapsingPokemonHeldItemModifier { } return ret; } + + getTransferrable(withinParty: boolean): boolean { + return false; + } + + getScoreMultiplier(): number { + return 1.25; + } getMaxHeldItemCount(pokemon: Pokemon): integer { return 1; @@ -627,6 +639,10 @@ export class PokemonBaseStatModifier extends PokemonHeldItemModifier { return false; } + getScoreMultiplier(): number { + return 1.1; + } + getMaxHeldItemCount(pokemon: Pokemon): integer { return pokemon.ivs[this.stat]; } @@ -668,6 +684,10 @@ export class AttackTypeBoosterModifier extends PokemonHeldItemModifier { return true; } + getScoreMultiplier(): number { + return 1.2; + } + getMaxHeldItemCount(pokemon: Pokemon): integer { return 10; } diff --git a/src/phases.ts b/src/phases.ts index a85157693..55c6fd577 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -1860,6 +1860,8 @@ export class BattleEndPhase extends BattlePhase { start() { super.start(); + this.scene.currentBattle.addBattleScore(this.scene); + this.scene.gameData.gameStats.battles++; if (this.scene.currentBattle.trainer) this.scene.gameData.gameStats.trainersDefeated++; @@ -2814,8 +2816,10 @@ export class FaintPhase extends PokemonPhase { pokemon.trySetStatus(StatusEffect.FAINT); if (pokemon.isPlayer()) this.scene.currentBattle.removeFaintedParticipant(pokemon as PlayerPokemon); - else + else { + this.scene.addFaintedEnemyScore(pokemon as EnemyPokemon); this.scene.currentBattle.addPostBattleLoot(pokemon as EnemyPokemon); + } this.scene.field.remove(pokemon); this.end(); } @@ -3098,7 +3102,7 @@ export class GameOverPhase extends BattlePhase { start() { super.start(); - (this.victory ? this.scene.gameData.tryClearSession : this.scene.gameData.deleteSession)(this.scene.sessionSlotId).then((success: boolean | [boolean, boolean]) => { + (this.victory ? this.scene.gameData.tryClearSession(this.scene, this.scene.sessionSlotId) : this.scene.gameData.deleteSession(this.scene.sessionSlotId)).then((success: boolean | [boolean, boolean]) => { this.scene.time.delayedCall(1000, () => { let firstClear = false; if (this.victory && success[1]) { @@ -3687,6 +3691,7 @@ export class AttemptCapturePhase extends PokemonPhase { this.end(); }; const removePokemon = () => { + this.scene.addFaintedEnemyScore(pokemon); this.scene.getPlayerField().filter(p => p.isActive(true)).forEach(playerPokemon => playerPokemon.removeTagsBySourceId(pokemon.id)); pokemon.hp = 0; pokemon.trySetStatus(StatusEffect.FAINT); diff --git a/src/system/game-data.ts b/src/system/game-data.ts index 6e0cf9147..9e52c6575 100644 --- a/src/system/game-data.ts +++ b/src/system/game-data.ts @@ -23,7 +23,6 @@ import { loggedInUser, updateUserInfo } from "../account"; import { Nature } from "../data/nature"; import { GameStats } from "./game-stats"; import { Tutorial } from "../tutorial"; -import { BattleSpec } from "../enums/battle-spec"; import { Moves } from "../data/enums/moves"; import { speciesEggMoves } from "../data/egg-moves"; import { allMoves } from "../data/move"; @@ -87,6 +86,7 @@ export interface SessionSaveData { arena: ArenaData; pokeballCounts: PokeballCounts; money: integer; + score: integer; waveIndex: integer; battleType: BattleType; trainer: TrainerData; @@ -452,29 +452,34 @@ export class GameData { return ret; } + private getSessionSaveData(scene: BattleScene): SessionSaveData { + return { + seed: scene.seed, + playTime: scene.sessionPlayTime, + gameMode: scene.gameMode.modeId, + party: scene.getParty().map(p => new PokemonData(p)), + enemyParty: scene.getEnemyParty().map(p => new PokemonData(p)), + modifiers: scene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)), + enemyModifiers: scene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)), + arena: new ArenaData(scene.arena), + pokeballCounts: scene.pokeballCounts, + money: scene.money, + score: scene.score, + waveIndex: scene.currentBattle.waveIndex, + battleType: scene.currentBattle.battleType, + trainer: scene.currentBattle.battleType == BattleType.TRAINER ? new TrainerData(scene.currentBattle.trainer) : null, + gameVersion: scene.game.config.gameVersion, + timestamp: new Date().getTime() + } as SessionSaveData; + } + saveSession(scene: BattleScene, skipVerification?: boolean): Promise { return new Promise(resolve => { Utils.executeIf(!skipVerification, updateUserInfo).then(success => { if (success !== null && !success) return resolve(false); - const sessionData = { - seed: scene.seed, - playTime: scene.sessionPlayTime, - gameMode: scene.gameMode.modeId, - party: scene.getParty().map(p => new PokemonData(p)), - enemyParty: scene.getEnemyParty().map(p => new PokemonData(p)), - modifiers: scene.findModifiers(() => true).map(m => new PersistentModifierData(m, true)), - enemyModifiers: scene.findModifiers(() => true, false).map(m => new PersistentModifierData(m, false)), - arena: new ArenaData(scene.arena), - pokeballCounts: scene.pokeballCounts, - money: scene.money, - waveIndex: scene.currentBattle.waveIndex, - battleType: scene.currentBattle.battleType, - trainer: scene.currentBattle.battleType == BattleType.TRAINER ? new TrainerData(scene.currentBattle.trainer) : null, - gameVersion: scene.game.config.gameVersion, - timestamp: new Date().getTime() - } as SessionSaveData; + const sessionData = this.getSessionSaveData(scene); if (!bypassLogin) { Utils.apiPost(`savedata/update?datatype=${GameDataType.SESSION}&slot=${scene.sessionSlotId}`, JSON.stringify(sessionData)) @@ -566,6 +571,8 @@ export class GameData { if (scene.money > this.gameStats.highestMoney) this.gameStats.highestMoney = scene.money; + scene.score = sessionData.score; + const battleType = sessionData.battleType || 0; const battle = scene.newBattle(sessionData.waveIndex, battleType, sessionData.trainer, battleType === BattleType.TRAINER ? trainerConfigs[sessionData.trainer.trainerType].isDouble : sessionData.enemyParty.length > 1); battle.enemyLevels = sessionData.enemyParty.map(p => p.level); @@ -636,7 +643,7 @@ export class GameData { }); } - tryClearSession(slotId: integer): Promise<[success: boolean, newClear: boolean]> { + tryClearSession(scene: BattleScene, slotId: integer): Promise<[success: boolean, newClear: boolean]> { return new Promise<[boolean, boolean]>(resolve => { if (bypassLogin) { localStorage.removeItem('sessionData'); @@ -646,7 +653,8 @@ export class GameData { updateUserInfo().then(success => { if (success !== null && !success) return resolve([false, false]); - Utils.apiFetch(`savedata/clear?slot=${slotId}`).then(response => { + const sessionData = this.getSessionSaveData(scene); + Utils.apiPost(`savedata/clear?slot=${slotId}`, JSON.stringify(sessionData)).then(response => { if (response.ok) { loggedInUser.lastSessionSlot = -1; return response.json();