Fix battle auto-detection and overlay position switching
- game-bridge.js: Replace broken Phaser.GAMES discovery (tree-shaken by Vite) with SceneManager.prototype.update monkey-patch that captures the game instance on the next frame tick - manifest.json: Run game-bridge at document_start so the patch is installed before Phaser boots - content.js: Fix position buttons only working once — was using inline style.left presence as drag check, now uses a dedicated manuallyDragged flag that only sets on actual mouse drag and resets on popup position change - Bump version to 1.1.0
This commit is contained in:
176
game-bridge.js
176
game-bridge.js
@@ -5,9 +5,11 @@
|
||||
* Polls for battle state and posts data to the ISOLATED world content script
|
||||
* via window.postMessage.
|
||||
*
|
||||
* PokeRogue uses Phaser 3, so the game instance and scene data are accessible
|
||||
* through window.Phaser.GAMES[0] or similar patterns. Method names on class
|
||||
* instances survive Vite/esbuild minification (property accesses are never mangled).
|
||||
* PokeRogue uses Phaser 3.90, bundled with Vite. The bundler tree-shakes
|
||||
* Phaser.GAMES[], so the game instance is NOT accessible via the standard
|
||||
* Phaser global registry. Instead, we monkey-patch
|
||||
* Phaser.Scenes.SceneManager.prototype.update to capture the game instance
|
||||
* on the next frame tick when the SceneManager calls update().
|
||||
*/
|
||||
|
||||
(function () {
|
||||
@@ -15,71 +17,105 @@
|
||||
|
||||
const EXT_SOURCE = 'pokerogue-type-ext';
|
||||
const POLL_INTERVAL_MS = 600;
|
||||
const GAME_FIND_RETRY_MS = 2000;
|
||||
const MAX_FIND_RETRIES = 60; // 2 minutes
|
||||
const PATCH_RETRY_MS = 500;
|
||||
const MAX_PATCH_RETRIES = 120; // 60 seconds
|
||||
|
||||
let game = null;
|
||||
let battleScene = null;
|
||||
let pollTimer = null;
|
||||
let findRetries = 0;
|
||||
let lastStateHash = '';
|
||||
let patchApplied = false;
|
||||
|
||||
// ─── Game Instance Discovery ───────────────────────────────────────
|
||||
// ─── Game Instance Discovery via Prototype Patch ───────────────────
|
||||
//
|
||||
// Vite tree-shakes Phaser.GAMES[], and the game instance lives inside
|
||||
// a module-scoped variable — invisible to window scanning.
|
||||
//
|
||||
// The reliable approach: patch SceneManager.prototype.update. When
|
||||
// Phaser's game loop calls game.step() → scene.update(), our patched
|
||||
// method captures `this.game` (the SceneManager has a .game ref).
|
||||
// This works even after the game is already running because the
|
||||
// prototype method is looked up dynamically each call.
|
||||
|
||||
/**
|
||||
* Try multiple strategies to find the Phaser game instance.
|
||||
* Install the SceneManager prototype patch.
|
||||
* Returns true if patch was installed, false if Phaser isn't loaded yet.
|
||||
*/
|
||||
function findGameInstance() {
|
||||
// Strategy 1: Phaser global registry
|
||||
function installPrototypePatch() {
|
||||
if (patchApplied) return true;
|
||||
|
||||
// Need window.Phaser to exist first
|
||||
if (!window.Phaser || !window.Phaser.Scenes || !window.Phaser.Scenes.SceneManager) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const proto = window.Phaser.Scenes.SceneManager.prototype;
|
||||
const origUpdate = proto.update;
|
||||
|
||||
if (typeof origUpdate !== 'function') {
|
||||
console.warn('[PokeRogue Ext] SceneManager.prototype.update is not a function');
|
||||
return false;
|
||||
}
|
||||
|
||||
proto.update = function patchedUpdate(time, delta) {
|
||||
// Capture the game instance from the SceneManager's .game property
|
||||
if (!game && this.game) {
|
||||
game = this.game;
|
||||
window.__POKEXT_GAME__ = game;
|
||||
console.log('[PokeRogue Ext] Game instance captured via SceneManager patch');
|
||||
postStatus('connected', 'Game found! Monitoring battles...');
|
||||
startPolling();
|
||||
}
|
||||
return origUpdate.call(this, time, delta);
|
||||
};
|
||||
|
||||
patchApplied = true;
|
||||
console.log('[PokeRogue Ext] SceneManager prototype patch installed');
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fallback strategies if the prototype patch hasn't fired yet.
|
||||
*/
|
||||
function findGameFallback() {
|
||||
if (window.__POKEXT_GAME__) return window.__POKEXT_GAME__;
|
||||
|
||||
if (window.Phaser && window.Phaser.GAMES && window.Phaser.GAMES.length > 0) {
|
||||
return window.Phaser.GAMES[0];
|
||||
}
|
||||
|
||||
// Strategy 2: Check common global variable names
|
||||
const candidates = ['game', 'phaserGame', 'gameInstance'];
|
||||
for (const name of candidates) {
|
||||
if (window[name] && window[name].scene) {
|
||||
if (window[name] && window[name].scene && window[name].canvas) {
|
||||
return window[name];
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 3: Scan for Phaser.Game instances on window
|
||||
for (const key of Object.keys(window)) {
|
||||
try {
|
||||
const obj = window[key];
|
||||
if (obj && typeof obj === 'object' && obj.scene && obj.canvas &&
|
||||
typeof obj.scene.getScenes === 'function') {
|
||||
return obj;
|
||||
}
|
||||
} catch (_) {
|
||||
// Some properties throw on access
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// ─── Battle Scene Discovery ────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Find the battle scene from the game's scene list.
|
||||
* Looks for scenes that have battle-specific methods/properties.
|
||||
* Find the BattleScene from the game's scene list.
|
||||
* PokeRogue has a single scene ("battle") that is always at index 0.
|
||||
*/
|
||||
function findBattleScene(gameInstance) {
|
||||
if (!gameInstance || !gameInstance.scene) return null;
|
||||
|
||||
let scenes;
|
||||
try {
|
||||
scenes = gameInstance.scene.getScenes(true); // active scenes
|
||||
scenes = gameInstance.scene.scenes;
|
||||
if (!scenes || scenes.length === 0) {
|
||||
scenes = gameInstance.scene.scenes;
|
||||
scenes = gameInstance.scene.getScenes ? gameInstance.scene.getScenes(true) : [];
|
||||
}
|
||||
} catch (_) {
|
||||
scenes = gameInstance.scene.scenes;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!scenes) return null;
|
||||
if (!scenes || scenes.length === 0) return null;
|
||||
|
||||
for (const scene of scenes) {
|
||||
// PokeRogue's BattleScene has these characteristic properties
|
||||
if (scene && scene.currentBattle !== undefined &&
|
||||
typeof scene.getPlayerField === 'function' &&
|
||||
typeof scene.getEnemyField === 'function') {
|
||||
@@ -87,9 +123,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: look for any scene with a "currentBattle" property
|
||||
for (const scene of scenes) {
|
||||
if (scene && scene.currentBattle !== undefined) {
|
||||
if (scene && (
|
||||
(scene.sys && scene.sys.settings && scene.sys.settings.key === 'battle') ||
|
||||
scene.currentBattle !== undefined
|
||||
)) {
|
||||
return scene;
|
||||
}
|
||||
}
|
||||
@@ -99,9 +137,6 @@
|
||||
|
||||
// ─── Data Extraction ───────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Safely get a Pokemon's display name.
|
||||
*/
|
||||
function getPokemonName(pokemon) {
|
||||
if (!pokemon) return 'Unknown';
|
||||
try {
|
||||
@@ -112,9 +147,6 @@
|
||||
return 'Unknown';
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely get a Pokemon's type IDs.
|
||||
*/
|
||||
function getPokemonTypes(pokemon) {
|
||||
if (!pokemon) return [];
|
||||
try {
|
||||
@@ -122,7 +154,6 @@
|
||||
const types = pokemon.getTypes();
|
||||
if (Array.isArray(types)) return types.filter(t => t >= 0 && t <= 17);
|
||||
}
|
||||
// Fallback: check species data
|
||||
if (pokemon.species) {
|
||||
const types = [];
|
||||
if (pokemon.species.type1 !== undefined && pokemon.species.type1 >= 0) types.push(pokemon.species.type1);
|
||||
@@ -135,9 +166,6 @@
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely extract a Pokemon's moveset with type, power, and category.
|
||||
*/
|
||||
function getPokemonMoves(pokemon) {
|
||||
if (!pokemon) return [];
|
||||
try {
|
||||
@@ -153,7 +181,7 @@
|
||||
return moveset.filter(m => m).map(m => {
|
||||
const move = (typeof m.getMove === 'function') ? m.getMove() : m;
|
||||
return {
|
||||
name: getName(m, move),
|
||||
name: getMoveName(m, move),
|
||||
type: getVal(move, 'type', m, 'type', -1),
|
||||
power: getVal(move, 'power', m, 'power', 0),
|
||||
category: getVal(move, 'category', m, 'category', 2),
|
||||
@@ -165,7 +193,7 @@
|
||||
return [];
|
||||
}
|
||||
|
||||
function getName(m, move) {
|
||||
function getMoveName(m, move) {
|
||||
try {
|
||||
if (typeof m.getName === 'function') return m.getName();
|
||||
if (move && move.name) return move.name;
|
||||
@@ -198,9 +226,6 @@
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a Pokemon is alive / active on the field.
|
||||
*/
|
||||
function isActive(pokemon) {
|
||||
if (!pokemon) return false;
|
||||
try {
|
||||
@@ -208,14 +233,11 @@
|
||||
if (typeof pokemon.isFainted === 'function') return !pokemon.isFainted();
|
||||
if (pokemon.hp !== undefined) return pokemon.hp > 0;
|
||||
} catch (_) {}
|
||||
return true; // assume active if can't determine
|
||||
return true;
|
||||
}
|
||||
|
||||
// ─── State Reading ─────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Read full battle state from the scene.
|
||||
*/
|
||||
function readBattleState() {
|
||||
if (!battleScene || !battleScene.currentBattle) return null;
|
||||
|
||||
@@ -285,7 +307,11 @@
|
||||
// ─── Polling Loop ──────────────────────────────────────────────────
|
||||
|
||||
function poll() {
|
||||
// Re-find battle scene each poll (scenes change)
|
||||
if (!game) {
|
||||
game = findGameFallback();
|
||||
if (!game) return;
|
||||
}
|
||||
|
||||
battleScene = findBattleScene(game);
|
||||
|
||||
if (!battleScene || !battleScene.currentBattle) {
|
||||
@@ -305,7 +331,6 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// Only post if state changed (avoid redundant messages)
|
||||
const hash = JSON.stringify(state);
|
||||
if (hash !== lastStateHash) {
|
||||
lastStateHash = hash;
|
||||
@@ -315,30 +340,41 @@
|
||||
|
||||
function startPolling() {
|
||||
if (pollTimer) return;
|
||||
postStatus('connected', 'Monitoring battles...');
|
||||
pollTimer = setInterval(poll, POLL_INTERVAL_MS);
|
||||
poll(); // immediate first poll
|
||||
poll();
|
||||
}
|
||||
|
||||
// ─── Initialization ────────────────────────────────────────────────
|
||||
|
||||
function tryFindGame() {
|
||||
game = findGameInstance();
|
||||
let patchRetries = 0;
|
||||
|
||||
if (game) {
|
||||
console.log('[PokeRogue Ext] Game instance found');
|
||||
function tryInstallPatch() {
|
||||
// Check if game is already available via fallback
|
||||
const fallbackGame = findGameFallback();
|
||||
if (fallbackGame) {
|
||||
game = fallbackGame;
|
||||
console.log('[PokeRogue Ext] Game instance found via fallback');
|
||||
postStatus('connected', 'Game found! Monitoring battles...');
|
||||
startPolling();
|
||||
return;
|
||||
}
|
||||
|
||||
findRetries++;
|
||||
if (findRetries >= MAX_FIND_RETRIES) {
|
||||
console.warn('[PokeRogue Ext] Could not find game instance after', MAX_FIND_RETRIES, 'retries');
|
||||
postStatus('error', 'Could not find PokeRogue game instance. The game may not be loaded.');
|
||||
// Try to install the prototype patch
|
||||
if (installPrototypePatch()) {
|
||||
console.log('[PokeRogue Ext] Patch installed, waiting for game to call update()...');
|
||||
postStatus('searching', 'Patch installed, waiting for game...');
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(tryFindGame, GAME_FIND_RETRY_MS);
|
||||
// Phaser not loaded yet, retry
|
||||
patchRetries++;
|
||||
if (patchRetries >= MAX_PATCH_RETRIES) {
|
||||
console.warn('[PokeRogue Ext] Phaser not found after', MAX_PATCH_RETRIES, 'retries. Is this pokerogue.net?');
|
||||
postStatus('error', 'Could not find Phaser. Make sure you are on pokerogue.net.');
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(tryInstallPatch, PATCH_RETRY_MS);
|
||||
}
|
||||
|
||||
// Listen for requests from the ISOLATED content script
|
||||
@@ -348,11 +384,11 @@
|
||||
}
|
||||
});
|
||||
|
||||
// Start looking for the game
|
||||
console.log('[PokeRogue Ext] Game bridge loaded, searching for Phaser instance...');
|
||||
// Start
|
||||
console.log('[PokeRogue Ext] Game bridge loaded, installing Phaser hooks...');
|
||||
postStatus('searching', 'Looking for PokeRogue game...');
|
||||
|
||||
// Wait a bit for the game to initialize
|
||||
setTimeout(tryFindGame, 1000);
|
||||
// Try immediately, then retry until Phaser loads
|
||||
tryInstallPatch();
|
||||
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user