mirror of
https://github.com/Such-Software/wownerogue.git
synced 2026-10-20 15:48:33 +02:00
The next-block wait now draws a small lit "mining room" Scene through the ACTIVE renderer (RK.SPGame.showWaiting) — floor/walls/torches in your chosen pack (Tiled/Iso/3D), camera centered, torches flickering via the FX loop — with the "Mining the next block" text as an overlay. So the wait inherits your tiles/mode instead of being the plain ROT animation. Falls back to the ROT WaitingScreenAnimator if the kit is unavailable. SP.render clears the overlay when the real game starts.
459 lines
18 KiB
JavaScript
459 lines
18 KiB
JavaScript
// ScreenManager - Handles different screen states and UI drawing
|
|
var ScreenManager = {
|
|
defaultFg: "#FFF",
|
|
_currentBlockHeight: 0,
|
|
_blockTimer: null,
|
|
_updateInterval: null, // Track the update interval separately
|
|
_waitingForBlock: false,
|
|
_isShowingWaitingScreen: false, // Track if waiting screen is active
|
|
_cryptoType: 'WOW', // Default to WOW, updated on game mode info
|
|
_ambientFXOn: false, // GFX3: FX overlay ambient state (guarded, no-op unless window.FX)
|
|
|
|
// Get the game title based on crypto type
|
|
getGameTitle: function() {
|
|
return this._cryptoType === 'XMR' ? 'MONEROGUE' : 'WOWNEROGUE';
|
|
},
|
|
|
|
// Set crypto type (called from socket handlers)
|
|
setCryptoType: function(cryptoType) {
|
|
this._cryptoType = cryptoType || 'WOW';
|
|
},
|
|
|
|
// Check if waiting screen is currently being shown
|
|
isShowingWaitingScreen: function() {
|
|
return this._isShowingWaitingScreen;
|
|
},
|
|
|
|
init: function(screenWidth, screenHeight) {
|
|
this._screenWidth = screenWidth;
|
|
this._screenHeight = screenHeight;
|
|
this._currentBlockHeight = 0;
|
|
this._waitingForBlock = false;
|
|
this._isShowingWaitingScreen = false;
|
|
this._updateInterval = null; // Initialize update interval tracker
|
|
this._cryptoType = 'WOW';
|
|
this._ambientFXOn = false; // GFX3: reset FX overlay ambient state
|
|
|
|
// Initialize the waiting screen animator
|
|
if (typeof WaitingScreenAnimator !== 'undefined') {
|
|
WaitingScreenAnimator.init();
|
|
}
|
|
},
|
|
|
|
// ===== GFX3: FX overlay helpers (additive, always guarded) =====
|
|
_fxBaseCanvas: function() {
|
|
try {
|
|
var d = (typeof DisplayManager !== 'undefined' && DisplayManager.getDisplay)
|
|
? DisplayManager.getDisplay() : null;
|
|
return (d && d.getContainer) ? d.getContainer() : null;
|
|
} catch (e) { return null; }
|
|
},
|
|
|
|
// Subtle vignette + slow ambient embers behind the teletype title.
|
|
_startAmbientFX: function() {
|
|
if (!window.FX) return;
|
|
if (this._ambientFXOn) return;
|
|
var canvas = this._fxBaseCanvas();
|
|
if (!canvas) return;
|
|
try {
|
|
if (window.FX.attach) window.FX.attach(canvas);
|
|
if (window.FX.syncTo) window.FX.syncTo(canvas);
|
|
if (window.FX.setAmbient) window.FX.setAmbient(true);
|
|
if (window.FX.start) window.FX.start();
|
|
this._ambientFXOn = true;
|
|
} catch (e) { /* additive: never break the screen */ }
|
|
},
|
|
|
|
_stopAmbientFX: function() {
|
|
this._ambientFXOn = false;
|
|
if (!window.FX) return;
|
|
try {
|
|
if (window.FX.setAmbient) window.FX.setAmbient(false);
|
|
if (window.FX.stop) window.FX.stop();
|
|
if (window.FX.clear) window.FX.clear();
|
|
} catch (e) { /* ignore */ }
|
|
},
|
|
|
|
// One-shot celebratory/somber flash as an end screen appears.
|
|
_screenFlash: function(color, alpha, ms) {
|
|
if (!window.FX) return;
|
|
var canvas = this._fxBaseCanvas();
|
|
if (!canvas) return;
|
|
try {
|
|
if (window.FX.attach) window.FX.attach(canvas);
|
|
if (window.FX.syncTo) window.FX.syncTo(canvas);
|
|
if (window.FX.start) window.FX.start();
|
|
if (window.FX.flash) window.FX.flash(color, alpha, ms);
|
|
} catch (e) { /* ignore */ }
|
|
},
|
|
|
|
drawText: function(x, y, text) {
|
|
if (!DisplayManager.ensureDisplay()) return;
|
|
const display = DisplayManager.getDisplay();
|
|
|
|
for (let i = 0; i < text.length; i++) {
|
|
const char = text[i];
|
|
if (window.options?.tileMap?.hasOwnProperty(char) || char === ' ') {
|
|
display.draw(x + i, y, char, "transparent", "transparent");
|
|
} else {
|
|
display.draw(x + i, y, "?", "transparent", "transparent");
|
|
}
|
|
}
|
|
},
|
|
|
|
drawCenteredText: function(y, text) {
|
|
const x = Math.floor((this._screenWidth - text.length) / 2);
|
|
this.drawText(x, y, text);
|
|
},
|
|
|
|
drawBorder: function() {
|
|
if (!DisplayManager.ensureDisplay()) return;
|
|
const display = DisplayManager.getDisplay();
|
|
|
|
// Use wall tiles (#) for borders - these are guaranteed to exist in tileMap
|
|
const borderChar = "#"; // Wall tile at [64, 32]
|
|
|
|
// Draw top and bottom borders
|
|
for (var i = 0; i < this._screenWidth; i++) {
|
|
display.draw(i, 0, borderChar, "transparent", "transparent");
|
|
display.draw(i, this._screenHeight - 1, borderChar, "transparent", "transparent");
|
|
}
|
|
|
|
// Draw left and right borders
|
|
for (var j = 1; j < this._screenHeight - 1; j++) {
|
|
display.draw(0, j, borderChar, "transparent", "transparent");
|
|
display.draw(this._screenWidth - 1, j, borderChar, "transparent", "transparent");
|
|
}
|
|
},
|
|
|
|
drawWelcomeScreen: function() {
|
|
if (window.RK && RK.SPGame) RK.SPGame.hide(); // full-screen text → hide the render-kit dungeon
|
|
if (!DisplayManager.ensureDisplay()) return;
|
|
|
|
// Mark that we're NOT showing the waiting screen
|
|
this._isShowingWaitingScreen = false;
|
|
|
|
DisplayManager.clearDisplay();
|
|
|
|
// Draw the border
|
|
this.drawBorder();
|
|
|
|
// GFX3: subtle vignette + slow ambient embers behind the teletype title
|
|
this._startAmbientFX();
|
|
|
|
// Draw content - use dynamic title based on crypto type
|
|
let y = 3;
|
|
this.drawCenteredText(y, this.getGameTitle());
|
|
y += 3;
|
|
|
|
// Draw instructions
|
|
this.drawCenteredText(y, "Type enter to queue!");
|
|
this.drawCenteredText(y + 1, "Or use START GAME");
|
|
y += 3;
|
|
|
|
// Draw legend with special tile handling
|
|
const display = DisplayManager.getDisplay();
|
|
// Use dynamic treasure tile based on crypto type
|
|
const treasureTile = (typeof GameTiles !== 'undefined' && GameTiles.getTreasureTile)
|
|
? GameTiles.getTreasureTile()
|
|
: (this._cryptoType === 'XMR' ? '$M' : '$W');
|
|
// Every row uses a uniform icon slot at `centerX` and a single fixed text column, so the
|
|
// icons share a left edge and all four descriptions line up — regardless of whether the
|
|
// icon is a 1-cell glyph (>, ~, $) or the 2-cell player avatar sprite.
|
|
const legendItems = [
|
|
{ tile: "@2", text: "This is you", role: "player" },
|
|
{ tile: ">", text: "Escape the dungeon" },
|
|
{ tile: "~", text: "Avoid the monster" },
|
|
{ tile: treasureTile, text: "Secure the bag" }
|
|
];
|
|
|
|
if (window.SinglePlayerAvatar && window.SinglePlayerAvatar.clearOverlay) {
|
|
window.SinglePlayerAvatar.clearOverlay();
|
|
}
|
|
|
|
const ICON_COLS = 2; // uniform icon slot width
|
|
const centerX = Math.floor(this._screenWidth / 2) - 10;
|
|
const textX = centerX + ICON_COLS + 1; // one fixed text column for every row
|
|
for (let item of legendItems) {
|
|
// Draw EVERY icon (incl. the player's original hero tile) as a grid tile — no avatar
|
|
// overlay — so all four share a left edge + baseline and the hero is the plain tile.
|
|
const glyph = window.options?.tileMap?.hasOwnProperty(item.tile) ? item.tile : "?";
|
|
const gx = centerX + Math.floor((ICON_COLS - (glyph.length || 1)) / 2);
|
|
display.draw(gx, y, glyph, "transparent", "transparent");
|
|
this.drawText(textX, y, item.text);
|
|
y++;
|
|
}
|
|
|
|
// Draw current block info on two lines
|
|
y += 2;
|
|
this.drawCenteredText(y, "Current Block:");
|
|
y += 1;
|
|
this.drawCenteredText(y, `${this._currentBlockHeight}`);
|
|
|
|
// Control HTML button visibility
|
|
const startButton = document.getElementById('startButton');
|
|
if (startButton) {
|
|
startButton.style.display = 'block';
|
|
}
|
|
|
|
// Hide animation button on welcome screen
|
|
this.hideAnimationButton();
|
|
|
|
// Debug mode detection (for future use)
|
|
const isDebugMode = window.location.hostname === 'localhost' ||
|
|
window.location.hostname === '127.0.0.1' ||
|
|
window.location.protocol === 'file:';
|
|
},
|
|
|
|
|
|
|
|
|
|
toggleAnimation: function() {
|
|
if (typeof WaitingScreenAnimator !== 'undefined') {
|
|
WaitingScreenAnimator.toggleAnimation();
|
|
}
|
|
},
|
|
|
|
// Start simulated block mining (30 second intervals)
|
|
startBlockSimulation: function() {
|
|
this._currentBlockHeight = Math.floor(Math.random() * 1000000) + 3200000; // Realistic Wownero block height
|
|
this._waitingForBlock = true;
|
|
|
|
// Update welcome screen every second during mining
|
|
this._updateInterval = setInterval(() => {
|
|
if (this._waitingForBlock && !GameState.isGameActive()) {
|
|
this.drawWelcomeScreen();
|
|
} else {
|
|
if (this._updateInterval) {
|
|
clearInterval(this._updateInterval);
|
|
this._updateInterval = null;
|
|
}
|
|
}
|
|
}, 1000);
|
|
|
|
// Simulate new block every 30 seconds
|
|
this._blockTimer = setInterval(() => {
|
|
if (!GameState.isGameActive()) { // Only continue if game is not active
|
|
this._currentBlockHeight++;
|
|
this._waitingForBlock = false;
|
|
this.drawWelcomeScreen();
|
|
|
|
// Show block found message briefly
|
|
setTimeout(() => {
|
|
if (!DisplayManager.ensureDisplay() || GameState.isGameActive()) return;
|
|
let y = Math.floor(this._screenHeight / 2);
|
|
this.drawCenteredText(y, "*** NEW BLOCK FOUND! ***");
|
|
this.drawCenteredText(y + 1, "You may now enter the dungeon!");
|
|
|
|
// Allow entry for 5 seconds
|
|
setTimeout(() => {
|
|
if (!GameState.isGameActive()) {
|
|
this._waitingForBlock = true;
|
|
this.drawWelcomeScreen();
|
|
}
|
|
}, 5000);
|
|
}, 500);
|
|
}
|
|
}, 30000); // 30 second intervals
|
|
},
|
|
|
|
// Stop block simulation
|
|
stopBlockSimulation: function() {
|
|
if (this._blockTimer) {
|
|
clearInterval(this._blockTimer);
|
|
this._blockTimer = null;
|
|
}
|
|
if (this._updateInterval) {
|
|
clearInterval(this._updateInterval);
|
|
this._updateInterval = null;
|
|
}
|
|
this._waitingForBlock = false;
|
|
},
|
|
|
|
// Check if player can enter (not waiting for block or debug mode)
|
|
canEnterGame: function() {
|
|
const isDebugMode = window.location.hostname === 'localhost' ||
|
|
window.location.hostname === '127.0.0.1' ||
|
|
window.location.protocol === 'file:';
|
|
return !this._waitingForBlock || isDebugMode;
|
|
},
|
|
|
|
drawWinScreen: function(hasTreasure) {
|
|
if (window.RK && RK.SPGame) RK.SPGame.hide();
|
|
if (!DisplayManager.ensureDisplay()) return;
|
|
DisplayManager.clearDisplay();
|
|
|
|
// GFX3: brief celebratory gold flash + burst as the win screen appears
|
|
if (window.FX) {
|
|
this._stopAmbientFX();
|
|
this._screenFlash('#ffd700', 0.45, 700);
|
|
var winCanvas = this._fxBaseCanvas();
|
|
if (winCanvas && window.FX.burst) {
|
|
try {
|
|
window.FX.burst(winCanvas.width / 2, winCanvas.height / 2, '#ffd700', 24);
|
|
} catch (e) { /* ignore */ }
|
|
}
|
|
}
|
|
|
|
let y = Math.floor(this._screenHeight / 3);
|
|
this.drawCenteredText(y, "CONGRATULATIONS!");
|
|
|
|
if (hasTreasure) {
|
|
this.drawCenteredText(y + 2, "You got the treasure!");
|
|
} else {
|
|
this.drawCenteredText(y + 2, "You escaped alive!");
|
|
}
|
|
this.drawCenteredText(y + 3, "Hero of Wownero!");
|
|
},
|
|
|
|
drawLoseScreen: function(reason) {
|
|
if (window.RK && RK.SPGame) RK.SPGame.hide();
|
|
if (!DisplayManager.ensureDisplay()) return;
|
|
DisplayManager.clearDisplay();
|
|
|
|
// GFX3: brief somber red flash as the lose screen appears
|
|
if (window.FX) {
|
|
this._stopAmbientFX();
|
|
this._screenFlash('#ff2a2a', 0.4, 700);
|
|
}
|
|
|
|
let y = Math.floor(this._screenHeight / 3);
|
|
this.drawCenteredText(y, "YOU HAVE PERISHED");
|
|
|
|
if (reason === 'monster') {
|
|
this.drawCenteredText(y + 2, "Slain by a beast.");
|
|
} else if (reason === 'timeout') {
|
|
this.drawCenteredText(y + 2, "Time ran out.");
|
|
} else {
|
|
this.drawCenteredText(y + 2, "Your adventure ends.");
|
|
}
|
|
this.drawCenteredText(y + 3, "Better luck next time.");
|
|
},
|
|
|
|
drawWaitingScreen: function(_internalLoopCall = false) {
|
|
// Render the waiting "mining room" through the render kit (active pack/mode) when available,
|
|
// so the wait inherits your tiles/iso/3D. Falls back to the ROT animation below.
|
|
if (window.RK && RK.SPGame && RK.SPGame.showWaiting && RK.SPGame.available()) {
|
|
this._isShowingWaitingScreen = true;
|
|
var _appr = (window.SinglePlayerAvatar && SinglePlayerAvatar.currentAppearance) ? SinglePlayerAvatar.currentAppearance() : null;
|
|
var _crypto = this._cryptoType || undefined;
|
|
if (RK.SPGame.showWaiting('⛏ Mining the next block…', 'You drop in when it lands — then escape before the one after.', _crypto, _appr)) {
|
|
this.showAnimationButton();
|
|
return;
|
|
}
|
|
}
|
|
if (window.RK && RK.SPGame) RK.SPGame.hide();
|
|
if (!DisplayManager.ensureDisplay()) return;
|
|
|
|
// Mark that we're showing the waiting screen
|
|
this._isShowingWaitingScreen = true;
|
|
|
|
DisplayManager.clearDisplay();
|
|
|
|
const animationEnabled = (typeof WaitingScreenAnimator !== 'undefined') ?
|
|
WaitingScreenAnimator.isAnimationEnabled() : false;
|
|
|
|
// Delegate to WaitingScreenAnimator with proper parameters
|
|
if (typeof WaitingScreenAnimator !== 'undefined') {
|
|
if (animationEnabled) {
|
|
WaitingScreenAnimator.drawAnimatedWaitingScreen(
|
|
this._screenWidth,
|
|
this._screenHeight,
|
|
this.drawBorder.bind(this),
|
|
this.drawCenteredText.bind(this)
|
|
);
|
|
} else {
|
|
WaitingScreenAnimator.drawStaticWaitingScreen(
|
|
this._screenWidth,
|
|
this._screenHeight,
|
|
this.drawBorder.bind(this),
|
|
this.drawCenteredText.bind(this)
|
|
);
|
|
}
|
|
} else {
|
|
// Fallback if WaitingScreenAnimator is not available
|
|
this.drawFallbackWaitingScreen();
|
|
}
|
|
|
|
// Show the animation toggle button
|
|
this.showAnimationButton();
|
|
|
|
// Start animation loop if not already running
|
|
// Only start the animator if this wasn't invoked from inside the animator loop
|
|
if (! _internalLoopCall && animationEnabled && typeof WaitingScreenAnimator !== 'undefined') {
|
|
WaitingScreenAnimator.startAnimation();
|
|
}
|
|
},
|
|
|
|
showAnimationButton: function() {
|
|
// First ensure the button exists
|
|
if (!this.ensureAnimationButton()) {
|
|
console.error("❌ Cannot show animation button - element missing from DOM");
|
|
return;
|
|
}
|
|
|
|
const animButton = document.getElementById('animationToggleButton');
|
|
if (animButton) {
|
|
animButton.style.display = 'inline-block';
|
|
animButton.style.visibility = 'visible';
|
|
|
|
const animationEnabled = (typeof WaitingScreenAnimator !== 'undefined') ?
|
|
WaitingScreenAnimator.isAnimationEnabled() : false;
|
|
animButton.textContent = animationEnabled ? '🎬 DISABLE ANIMATION' : '🎬 ENABLE ANIMATION';
|
|
} else {
|
|
// Animation button element not found - non-critical warning
|
|
// console.warn("❌ Animation button element not found!");
|
|
}
|
|
},
|
|
|
|
hideAnimationButton: function() {
|
|
const animButton = document.getElementById('animationToggleButton');
|
|
if (animButton) {
|
|
animButton.style.display = 'none';
|
|
}
|
|
},
|
|
|
|
ensureAnimationButton: function() {
|
|
let animButton = document.getElementById('animationToggleButton');
|
|
|
|
if (!animButton) {
|
|
// Animation button not found - create or handle missing button
|
|
const statusDiv = document.querySelector('.status');
|
|
if (statusDiv) {
|
|
const existingButtons = statusDiv.querySelectorAll('button');
|
|
// Check existing buttons for debugging
|
|
for (let btn of existingButtons) {
|
|
// Button discovery for debugging
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
stopWaitingScreen: function() {
|
|
if (typeof WaitingScreenAnimator !== 'undefined') {
|
|
WaitingScreenAnimator.stopAnimation();
|
|
}
|
|
// GFX3: waiting animator tears down the shared FX overlay; clear our flag
|
|
// so ambient FX can restart when the welcome screen is shown again.
|
|
this._ambientFXOn = false;
|
|
this.hideAnimationButton();
|
|
if (!DisplayManager.ensureDisplay()) return;
|
|
DisplayManager.clearDisplay();
|
|
},
|
|
|
|
drawFallbackWaitingScreen: function() {
|
|
this.drawBorder();
|
|
let y = Math.floor(this._screenHeight / 2) - 1;
|
|
this.drawCenteredText(y, "Waiting for next block...");
|
|
this.drawCenteredText(y + 1, "Loading animation module...");
|
|
}
|
|
};
|
|
|
|
// Make it available globally
|
|
if (typeof window !== 'undefined') {
|
|
window.ScreenManager = ScreenManager;
|
|
}
|