diff --git a/public/images/pokemon/variant/31_1.png b/public/images/pokemon/variant/31_1.png index 082e2a3d1..d471b0621 100644 Binary files a/public/images/pokemon/variant/31_1.png and b/public/images/pokemon/variant/31_1.png differ diff --git a/src/battle-scene.ts b/src/battle-scene.ts index a60b040b2..7cb92c451 100644 --- a/src/battle-scene.ts +++ b/src/battle-scene.ts @@ -271,7 +271,7 @@ export default class BattleScene extends SceneBase { populateAnims(); - await fetch('./images/pokemon/variant/_masterlist.json').then(res => res.json()).then(v => Object.keys(v).forEach(k => variantData[k] = v[k])); + await this.cachedFetch('./images/pokemon/variant/_masterlist.json').then(res => res.json()).then(v => Object.keys(v).forEach(k => variantData[k] = v[k])); } create() { @@ -468,8 +468,8 @@ export default class BattleScene extends SceneBase { Promise.all([ Promise.all(loadPokemonAssets), - initCommonAnims().then(() => loadCommonAnimAssets(this, true)), - Promise.all([ Moves.TACKLE, Moves.TAIL_WHIP, Moves.FOCUS_ENERGY, Moves.STRUGGLE ].map(m => initMoveAnim(m))).then(() => loadMoveAnimAssets(this, defaultMoves, true)), + initCommonAnims(this).then(() => loadCommonAnimAssets(this, true)), + Promise.all([ Moves.TACKLE, Moves.TAIL_WHIP, Moves.FOCUS_ENERGY, Moves.STRUGGLE ].map(m => initMoveAnim(this, m))).then(() => loadMoveAnimAssets(this, defaultMoves, true)), this.initStarterColors() ]).then(() => { this.pushPhase(new LoginPhase(this)); @@ -505,19 +505,29 @@ export default class BattleScene extends SceneBase { async initExpSprites(): Promise { if (expSpriteKeys.length) return; - fetch('./exp-sprites.json').then(res => res.json()).then(keys => { + this.cachedFetch('./exp-sprites.json').then(res => res.json()).then(keys => { if (Array.isArray(keys)) expSpriteKeys.push(...keys); Promise.resolve(); }); } + cachedFetch(url: string, init?: RequestInit): Promise { + const manifest = this.game['manifest']; + if (manifest) { + const timestamp = manifest[`/${url.replace('./', '')}`]; + if (timestamp) + url += `?t=${timestamp}`; + } + return fetch(url, init); + } + initStarterColors(): Promise { return new Promise(resolve => { if (starterColors) return resolve(); - fetch('./starter-colors.json').then(res => res.json()).then(sc => { + this.cachedFetch('./starter-colors.json').then(res => res.json()).then(sc => { starterColors = {}; Object.keys(sc).forEach(key => { starterColors[key] = sc[key]; diff --git a/src/data/battle-anims.ts b/src/data/battle-anims.ts index 8118f385f..db00ea600 100644 --- a/src/data/battle-anims.ts +++ b/src/data/battle-anims.ts @@ -424,14 +424,14 @@ export const moveAnims = new Map() export const chargeAnims = new Map(); export const commonAnims = new Map(); -export function initCommonAnims(): Promise { +export function initCommonAnims(scene: BattleScene): Promise { return new Promise(resolve => { const commonAnimNames = Utils.getEnumKeys(CommonAnim); const commonAnimIds = Utils.getEnumValues(CommonAnim); const commonAnimFetches = []; for (let ca = 0; ca < commonAnimIds.length; ca++) { const commonAnimId = commonAnimIds[ca]; - commonAnimFetches.push(fetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/\_/g, '-')}.json`) + commonAnimFetches.push(scene.cachedFetch(`./battle-anims/common-${commonAnimNames[ca].toLowerCase().replace(/\_/g, '-')}.json`) .then(response => response.json()) .then(cas => commonAnims.set(commonAnimId, new AnimConfig(cas)))); } @@ -439,7 +439,7 @@ export function initCommonAnims(): Promise { }); } -export function initMoveAnim(move: Moves): Promise { +export function initMoveAnim(scene: BattleScene, move: Moves): Promise { return new Promise(resolve => { if (moveAnims.has(move)) { if (moveAnims.get(move) !== null) @@ -460,7 +460,7 @@ export function initMoveAnim(move: Moves): Promise { const defaultMoveAnim = allMoves[move] instanceof AttackMove ? Moves.TACKLE : allMoves[move] instanceof SelfStatusMove ? Moves.FOCUS_ENERGY : Moves.TAIL_WHIP; const moveName = Moves[move].toLowerCase().replace(/\_/g, '-'); const fetchAnimAndResolve = (move: Moves) => { - fetch(`./battle-anims/${moveName}.json`) + scene.cachedFetch(`./battle-anims/${moveName}.json`) .then(response => { if (!response.ok) { console.error(`Could not load animation file for move '${moveName}'`, response.status, response.statusText); @@ -477,7 +477,7 @@ export function initMoveAnim(move: Moves): Promise { populateMoveAnim(move, ba); const chargeAttr = allMoves[move].getAttrs(ChargeAttr).find(() => true) as ChargeAttr || allMoves[move].getAttrs(DelayedAttackAttr).find(() => true) as DelayedAttackAttr; if (chargeAttr) - initMoveChargeAnim(chargeAttr.chargeAnim).then(() => resolve()); + initMoveChargeAnim(scene, chargeAttr.chargeAnim).then(() => resolve()); else resolve(); }); @@ -487,7 +487,7 @@ export function initMoveAnim(move: Moves): Promise { }); } -export function initMoveChargeAnim(chargeAnim: ChargeAnim): Promise { +export function initMoveChargeAnim(scene: BattleScene, chargeAnim: ChargeAnim): Promise { return new Promise(resolve => { if (chargeAnims.has(chargeAnim)) { if (chargeAnims.get(chargeAnim) !== null) @@ -502,7 +502,7 @@ export function initMoveChargeAnim(chargeAnim: ChargeAnim): Promise { } } else { chargeAnims.set(chargeAnim, null); - fetch(`./battle-anims/${ChargeAnim[chargeAnim].toLowerCase().replace(/\_/g, '-')}.json`) + scene.cachedFetch(`./battle-anims/${ChargeAnim[chargeAnim].toLowerCase().replace(/\_/g, '-')}.json`) .then(response => response.json()) .then(ca => { if (Array.isArray(ca)) { diff --git a/src/data/move.ts b/src/data/move.ts index 9445b188b..48714d00c 100644 --- a/src/data/move.ts +++ b/src/data/move.ts @@ -3096,7 +3096,7 @@ export class RandomMoveAttr extends OverrideMoveEffectAttr { : [ moveTargets.targets[user.randSeedInt(moveTargets.targets.length)] ]; user.getMoveQueue().push({ move: moveId, targets: targets, ignorePP: true }); user.scene.unshiftPhase(new MovePhase(user.scene, user, targets, new PokemonMove(moveId, 0, 0, true), true)); - initMoveAnim(moveId).then(() => { + initMoveAnim(user.scene, moveId).then(() => { loadMoveAnimAssets(user.scene, [ moveId ], true) .then(() => resolve(true)); }); @@ -3239,7 +3239,7 @@ export class NaturePowerAttr extends OverrideMoveEffectAttr { user.getMoveQueue().push({ move: moveId, targets: [target.getBattlerIndex()], ignorePP: true }); user.scene.unshiftPhase(new MovePhase(user.scene, user, [target.getBattlerIndex()], new PokemonMove(moveId, 0, 0, true), true)); - initMoveAnim(moveId).then(() => { + initMoveAnim(user.scene, moveId).then(() => { loadMoveAnimAssets(user.scene, [ moveId ], true) .then(() => resolve(true)); }); diff --git a/src/data/pokemon-species.ts b/src/data/pokemon-species.ts index ed55bbf4d..ceeca373b 100644 --- a/src/data/pokemon-species.ts +++ b/src/data/pokemon-species.ts @@ -394,7 +394,7 @@ export abstract class PokemonSpeciesForm { return new Promise(resolve => { if (variantColorCache.hasOwnProperty(key)) return resolve(); - fetch(`./images/pokemon/variant/${spritePath}.json`).then(res => res.json()).then(c => { + scene.cachedFetch(`./images/pokemon/variant/${spritePath}.json`).then(res => res.json()).then(c => { variantColorCache[key] = c; resolve(); }); diff --git a/src/field/pokemon.ts b/src/field/pokemon.ts index 0989c769f..97b228a24 100644 --- a/src/field/pokemon.ts +++ b/src/field/pokemon.ts @@ -275,7 +275,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { loadAssets(ignoreOverride: boolean = true): Promise { return new Promise(resolve => { const moveIds = this.getMoveset().map(m => m.getMove().id); - Promise.allSettled(moveIds.map(m => initMoveAnim(m))) + Promise.allSettled(moveIds.map(m => initMoveAnim(this.scene, m))) .then(() => { loadMoveAnimAssets(this.scene, moveIds); this.getSpeciesForm().loadAssets(this.scene, this.getGender() === Gender.FEMALE, this.formIndex, this.shiny, this.variant); @@ -317,7 +317,7 @@ export default abstract class Pokemon extends Phaser.GameObjects.Container { if (variantSet && variantSet[this.variant] === 1) { if (variantColorCache.hasOwnProperty(key)) return resolve(); - fetch(`./images/pokemon/variant/${battleSpritePath}.json`).then(res => res.json()).then(c => { + this.scene.cachedFetch(`./images/pokemon/variant/${battleSpritePath}.json`).then(res => res.json()).then(c => { variantColorCache[key] = c; resolve(); }); diff --git a/src/loading-scene.ts b/src/loading-scene.ts index 74deaa5fd..38f82ff06 100644 --- a/src/loading-scene.ts +++ b/src/loading-scene.ts @@ -19,12 +19,7 @@ export class LoadingScene extends SceneBase { } preload() { - const indexFile = Array.from(document.querySelectorAll('script')).map(s => s.src).find(s => /\/index/.test(s)); - if (indexFile) { - const buildIdMatch = /index\-(.*?)\.js$/.exec(indexFile); - if (buildIdMatch) - this.load['cacheBuster'] = buildIdMatch[1]; - } + this.load['manifest'] = this.game['manifest']; if (!isMobile()) this.load.video('intro_dark', 'images/intro_dark.mp4', true); diff --git a/src/main.ts b/src/main.ts index 13aa5076f..6a00693fc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -76,7 +76,21 @@ Phaser.GameObjects.Rectangle.prototype.setPositionRelative = setPositionRelative document.fonts.load('16px emerald').then(() => document.fonts.load('10px pkmnems')); -const game = new Phaser.Game(config); -game.sound.pauseOnBlur = false; +let game; + +const startGame = () => { + game = new Phaser.Game(config); + game.sound.pauseOnBlur = false; +}; + +fetch('/manifest.json') + .then(res => res.json()) + .then(jsonResponse => { + startGame(); + game['manifest'] = jsonResponse.manifest; + }).catch(() => { + // Manifest not found (likely local build) + startGame(); + }); export default game; diff --git a/src/phases.ts b/src/phases.ts index 62e53d797..09595cc0e 100644 --- a/src/phases.ts +++ b/src/phases.ts @@ -2613,7 +2613,7 @@ export class MoveAnimTestPhase extends BattlePhase { } else if (player) console.log(Moves[moveId]); - initMoveAnim(moveId).then(() => { + initMoveAnim(this, moveId).then(() => { loadMoveAnimAssets(this.scene, [ moveId ], true) .then(() => { new MoveAnim(moveId, player ? this.scene.getPlayerPokemon() : this.scene.getEnemyPokemon(), (player !== (allMoves[moveId] instanceof SelfStatusMove) ? this.scene.getEnemyPokemon() : this.scene.getPlayerPokemon()).getBattlerIndex()).play(this.scene, () => { @@ -3675,7 +3675,7 @@ export class LearnMovePhase extends PlayerPartyMemberPokemonPhase { if (emptyMoveIndex > -1) { pokemon.setMove(emptyMoveIndex, this.moveId); - initMoveAnim(this.moveId).then(() => { + initMoveAnim(this, this.moveId).then(() => { loadMoveAnimAssets(this.scene, [ this.moveId ], true) .then(() => { this.scene.ui.setMode(messageMode).then(() => { diff --git a/src/plugins/cache-busted-loader-plugin.ts b/src/plugins/cache-busted-loader-plugin.ts index e1ee78709..449c99454 100644 --- a/src/plugins/cache-busted-loader-plugin.ts +++ b/src/plugins/cache-busted-loader-plugin.ts @@ -1,26 +1,29 @@ -let cacheBuster = ''; - -const ignoredFiles = [ 'intro_dark' ]; +let manifest: object; export default class CacheBustedLoaderPlugin extends Phaser.Loader.LoaderPlugin { constructor(scene: Phaser.Scene) { super(scene) } - get cacheBuster() { - return cacheBuster + get manifest() { + return manifest; } - set cacheBuster(version) { - cacheBuster = version + set manifest(manifestObj: object) { + manifest = manifestObj; } addFile(file): void { if (!Array.isArray(file)) file = [ file ]; - if (!ignoredFiles.includes(file?.key) && cacheBuster) - file.forEach(item => item.url += '?v=' + cacheBuster); + file.forEach(item => { + if (manifest) { + const timestamp = manifest[`/${item.url.replace(/\/\//g, '/')}` ]; + if (timestamp) + item.url += `?t=${timestamp}`; + } + }); super.addFile(file); } diff --git a/src/scene-base.ts b/src/scene-base.ts index 1098788c4..a990492f5 100644 --- a/src/scene-base.ts +++ b/src/scene-base.ts @@ -5,25 +5,35 @@ export class SceneBase extends Phaser.Scene { super(config); } + getCachedUrl(url: string): string { + const manifest = this.game['manifest']; + if (manifest) { + const timestamp = manifest[`/${url}`]; + if (timestamp) + url += `?t=${timestamp}`; + } + return url; + } + loadImage(key: string, folder: string, filename?: string) { if (!filename) filename = `${key}.png`; - this.load.image(key, `images/${folder}/${filename}`); + this.load.image(key, this.getCachedUrl(`images/${folder}/${filename}`)); if (folder.startsWith('ui')) { legacyCompatibleImages.push(key); folder = folder.replace('ui', 'ui/legacy'); - this.load.image(`${key}_legacy`, `images/${folder}/${filename}`); + this.load.image(`${key}_legacy`, this.getCachedUrl(`images/${folder}/${filename}`)); } } loadSpritesheet(key: string, folder: string, size: integer, filename?: string) { if (!filename) filename = `${key}.png`; - this.load.spritesheet(key, `images/${folder}/${filename}`, { frameWidth: size, frameHeight: size }); + this.load.spritesheet(key, this.getCachedUrl(`images/${folder}/${filename}`), { frameWidth: size, frameHeight: size }); if (folder.startsWith('ui')) { legacyCompatibleImages.push(key); folder = folder.replace('ui', 'ui/legacy'); - this.load.spritesheet(`${key}_legacy`, `images/${folder}/${filename}`, { frameWidth: size, frameHeight: size }); + this.load.spritesheet(`${key}_legacy`, this.getCachedUrl(`images/${folder}/${filename}`), { frameWidth: size, frameHeight: size }); } } @@ -32,11 +42,11 @@ export class SceneBase extends Phaser.Scene { filenameRoot = key; if (folder) folder += '/'; - this.load.atlas(key, `images/${folder}${filenameRoot}.png`, `images/${folder}/${filenameRoot}.json`); + this.load.atlas(key, this.getCachedUrl(`images/${folder}${filenameRoot}.png`), this.getCachedUrl(`images/${folder}/${filenameRoot}.json`)); if (folder.startsWith('ui')) { legacyCompatibleImages.push(key); folder = folder.replace('ui', 'ui/legacy'); - this.load.atlas(`${key}_legacy`, `images/${folder}${filenameRoot}.png`, `images/${folder}/${filenameRoot}.json`); + this.load.atlas(`${key}_legacy`, this.getCachedUrl(`images/${folder}${filenameRoot}.png`), this.getCachedUrl(`images/${folder}/${filenameRoot}.json`)); } } @@ -49,14 +59,13 @@ export class SceneBase extends Phaser.Scene { folder += '/'; if (!Array.isArray(filenames)) filenames = [ filenames ]; - for (let f of filenames as string[]) { - this.load.audio(key, `audio/se/${folder}${f}`); - } + for (let f of filenames as string[]) + this.load.audio(key, this.getCachedUrl(`audio/se/${folder}${f}`)); } loadBgm(key: string, filename?: string) { if (!filename) filename = `${key}.mp3`; - this.load.audio(key, `audio/bgm/${filename}`); + this.load.audio(key, this.getCachedUrl(`audio/bgm/${filename}`)); } } \ No newline at end of file