Implementing a debug menu item for moments

Lets you see what moment is meant to come up next.
This commit is contained in:
Tyler Marques 2025-05-29 13:09:03 -07:00
parent 256c3b67c3
commit b90df44aee
No known key found for this signature in database
GPG key ID: CB99EDCF41D3016F
6 changed files with 270 additions and 72 deletions

View file

@ -3,6 +3,9 @@
* Handles the collapsible debug menu and organization of debug tools
*/
import { updateNextMomentDisplay, initNextMomentTool } from "./nextMoment";
import { initDebugProvinceHighlighting } from "./provinceHighlight";
export class DebugMenu {
private toggleBtn: HTMLButtonElement;
private panel: HTMLElement;
@ -19,6 +22,9 @@ export class DebugMenu {
}
this.initEventListeners();
// Start with the menu open
this.toggle()
}
private initEventListeners(): void {
@ -39,14 +45,6 @@ export class DebugMenu {
}
});
// Close when clicking outside the menu
document.addEventListener('click', (e) => {
const target = e.target as Node;
const debugMenu = document.getElementById('debug-menu');
if (this.isExpanded && debugMenu && !debugMenu.contains(target)) {
this.collapse();
}
});
}
/**
@ -56,6 +54,7 @@ export class DebugMenu {
const debugMenu = document.getElementById('debug-menu');
if (debugMenu) {
debugMenu.style.display = 'block';
this.initTools()
}
}
@ -136,7 +135,7 @@ export class DebugMenu {
// If no specific position, insert before "Future Tools" section or at the end
const futureToolsSection = Array.from(debugContent.querySelectorAll('.debug-section h4'))
.find(h4 => h4.textContent === 'Future Tools')?.parentElement;
if (futureToolsSection) {
debugContent.insertBefore(section, futureToolsSection);
} else {
@ -170,4 +169,15 @@ export class DebugMenu {
public get expanded(): boolean {
return this.isExpanded;
}
}
private initTools(): void {
initNextMomentTool(this);
initDebugProvinceHighlighting()
}
public updateTools(): void {
updateNextMomentDisplay()
}
}
export let debugMenuInstance = new DebugMenu();

View file

@ -0,0 +1,104 @@
/**
* Next Moment Debug Tool
* Shows the next moment that should occur based on phase name parsing
*/
import { gameState } from '../gameState';
import { getNextPhaseName, parsePhase } from '../types/moments';
/**
* Initializes the next moment debug tool
* @param debugMenu - Debug menu instance to add the tool to
*/
export function initNextMomentTool(debugMenu: any) {
// Add next moment display tool
debugMenu.addDebugTool(
'Next Moment',
`
<div class="debug-tool">
<div>Current Phase: <span id="debug-current-phase">--</span></div>
<div>Next Phase: <span id="debug-next-phase">--</span></div>
<div>Next Moment: <span id="debug-next-moment">--</span></div>
<button id="debug-refresh-moment">Refresh</button>
</div>
`,
'Future Tools'
);
// Initialize the next moment display
updateNextMomentDisplay();
// Add refresh button functionality
const refreshBtn = document.getElementById('debug-refresh-moment');
if (refreshBtn) {
refreshBtn.addEventListener('click', updateNextMomentDisplay);
}
}
/**
* Updates the next moment display in the debug menu
*/
export function updateNextMomentDisplay() {
const currentPhaseElement = document.getElementById('debug-current-phase');
const nextPhaseElement = document.getElementById('debug-next-phase');
const nextMomentElement = document.getElementById('debug-next-moment');
if (!currentPhaseElement || !nextPhaseElement || !nextMomentElement) return;
if (!gameState.gameData || !gameState.gameData.phases || gameState.phaseIndex < 0) {
currentPhaseElement.textContent = 'No game loaded';
nextPhaseElement.textContent = '--';
nextMomentElement.textContent = '--';
return;
}
const currentPhase = gameState.gameData.phases[gameState.phaseIndex];
const currentPhaseName = currentPhase.name;
currentPhaseElement.textContent = currentPhaseName;
// Get next phase name using our parser
const nextPhaseName = getNextPhaseName(currentPhaseName);
nextPhaseElement.textContent = nextPhaseName || 'Unable to parse';
// Find next moment across all phases
if (gameState.momentsData) {
const nextMoment = findNextMoment(currentPhaseName);
if (nextMoment) {
nextMomentElement.innerHTML = `<strong>${nextMoment.category}</strong><br/>Phase: ${nextMoment.phase}<br/>Score: ${nextMoment.interest_score}`;
nextMomentElement.style.color = nextMoment.interest_score >= 9 ? '#ff6b6b' : '#4dabf7';
} else {
nextMomentElement.textContent = 'No future moments found';
nextMomentElement.style.color = '#888';
}
} else {
nextMomentElement.textContent = 'Moments data not loaded';
nextMomentElement.style.color = '#888';
}
}
/**
* Finds the next moment chronologically after the current phase
* @param currentPhaseName - Current phase name
* @returns Next moment or null if none found
*/
function findNextMoment(currentPhaseName: string) {
if (!gameState.momentsData?.moments) return null;
const currentParsed = parsePhase(currentPhaseName);
if (!currentParsed) return null;
// Get all moments that come after the current phase
const futureMoments = gameState.momentsData.moments
.map(moment => ({
...moment,
parsedPhase: parsePhase(moment.phase)
}))
.filter(moment =>
moment.parsedPhase &&
moment.parsedPhase.order > currentParsed.order
)
.sort((a, b) => a.parsedPhase!.order - b.parsedPhase!.order);
return futureMoments.length > 0 ? futureMoments[0] : null;
}

View file

@ -1,4 +1,5 @@
import { gameState } from "../gameState";
import { provinceInput, highlightProvinceBtn } from "../domElements";
import { ProvinceENUM } from "../types/map";
import { MeshBasicMaterial } from "three";
@ -23,7 +24,7 @@ export function highlightProvince(provinceName: string): void {
// Normalize the province name to uppercase
const normalizedName = provinceName.toUpperCase().trim();
// Check if it's a valid province
if (!Object.values(ProvinceENUM).includes(normalizedName as ProvinceENUM)) {
console.warn(`Province "${normalizedName}" not found. Valid provinces are:`, Object.values(ProvinceENUM));
@ -50,7 +51,7 @@ export function highlightProvince(provinceName: string): void {
};
console.log(`Highlighting province: ${normalizedName}`);
// Start the animation loop
animateFlash();
}
@ -72,26 +73,26 @@ function animateFlash(): void {
// Calculate flash intensity using sine wave for smooth pulsing
const flashIntensity = Math.sin(elapsed * 0.01) * 0.5 + 0.5; // 0 to 1
// Interpolate between original color and bright yellow
const material = currentFlashAnimation.mesh.material as MeshBasicMaterial;
const originalColor = currentFlashAnimation.originalColor;
// Extract RGB components from original color
const originalR = (originalColor >> 16) & 255;
const originalG = (originalColor >> 8) & 255;
const originalB = originalColor & 255;
// Flash to a bright yellow (255, 255, 0)
const flashR = 255;
const flashG = 255;
const flashB = 0;
// Interpolate between original and flash colors
const r = Math.round(originalR + (flashR - originalR) * flashIntensity);
const g = Math.round(originalG + (flashG - originalG) * flashIntensity);
const b = Math.round(originalB + (flashB - originalB) * flashIntensity);
// Set the new color
const newColor = (r << 16) | (g << 8) | b;
material.color.setHex(newColor);
@ -109,7 +110,7 @@ function stopCurrentFlash(): void {
// Restore original color
const material = currentFlashAnimation.mesh.material as MeshBasicMaterial;
material.color.setHex(currentFlashAnimation.originalColor);
currentFlashAnimation = null;
}
@ -118,4 +119,43 @@ function stopCurrentFlash(): void {
*/
export function getAvailableProvinces(): string[] {
return Object.values(ProvinceENUM);
}
}
// Initialize debug province highlighting functionality
export function initDebugProvinceHighlighting() {
highlightProvinceBtn.addEventListener('click', () => {
const provinceName = provinceInput.value.trim();
if (provinceName) {
highlightProvince(provinceName);
} else {
console.warn('Please enter a province name');
}
});
// Allow highlighting on Enter key press
provinceInput.addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
const provinceName = provinceInput.value.trim();
if (provinceName) {
highlightProvince(provinceName);
}
}
});
// Add input validation and autocomplete suggestions
provinceInput.addEventListener('input', () => {
const input = provinceInput.value.toUpperCase().trim();
const availableProvinces = getAvailableProvinces();
// Basic validation - turn input red if it doesn't match any province
if (input && !availableProvinces.some(p => p.startsWith(input))) {
provinceInput.style.borderColor = '#ff4444';
provinceInput.style.backgroundColor = '#ffe6e6';
} else {
provinceInput.style.borderColor = '#4f3b16';
provinceInput.style.backgroundColor = '#faf0d8';
}
});
}