All checks were successful
Build / build (push) Successful in 1m47s
- Replace physics-based projectiles with shape cast collision detection - Add ignoreBody to shape cast to prevent projectiles hitting ship mesh - Parent StatusScreen to ship TransformNode instead of XR camera - Add velocity check to victory condition (must be < 5 m/s) - Adjust projectile spawn offset and velocity 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
655 lines
24 KiB
TypeScript
655 lines
24 KiB
TypeScript
import {
|
|
AdvancedDynamicTexture,
|
|
Button,
|
|
Control,
|
|
Rectangle,
|
|
StackPanel,
|
|
TextBlock
|
|
} from "@babylonjs/gui";
|
|
import {
|
|
Mesh,
|
|
MeshBuilder,
|
|
Scene,
|
|
StandardMaterial,
|
|
TransformNode,
|
|
Vector3
|
|
} from "@babylonjs/core";
|
|
import { GameStats } from "../../game/gameStats";
|
|
import { DefaultScene } from "../../core/defaultScene";
|
|
import { ProgressionManager } from "../../game/progression";
|
|
import { AuthService } from "../../services/authService";
|
|
import { FacebookShare, ShareData } from "../../services/facebookShare";
|
|
import { InputControlManager } from "../../ship/input/inputControlManager";
|
|
import { formatStars } from "../../game/scoreCalculator";
|
|
import { GameResultsService } from "../../services/gameResultsService";
|
|
import log from "../../core/logger";
|
|
|
|
/**
|
|
* Status screen that displays game statistics
|
|
* Floats in front of the user and can be toggled on/off
|
|
*/
|
|
export class StatusScreen {
|
|
private _scene: Scene;
|
|
private _gameStats: GameStats;
|
|
private _screenMesh: Mesh | null = null;
|
|
private _texture: AdvancedDynamicTexture | null = null;
|
|
private _isVisible: boolean = false;
|
|
private _shipNode: TransformNode;
|
|
private _parTime: number = 120; // Default par time in seconds
|
|
|
|
// Text blocks for statistics
|
|
private _gameTimeText: TextBlock;
|
|
private _asteroidsText: TextBlock;
|
|
private _hullDamageText: TextBlock;
|
|
private _shotsFiredText: TextBlock;
|
|
private _accuracyText: TextBlock;
|
|
private _fuelConsumedText: TextBlock;
|
|
|
|
// Text blocks for score display
|
|
private _finalScoreText: TextBlock;
|
|
private _scoreBreakdownText: TextBlock;
|
|
private _starRatingText: TextBlock;
|
|
|
|
// Buttons
|
|
private _replayButton: Button;
|
|
private _exitButton: Button;
|
|
private _resumeButton: Button;
|
|
private _nextLevelButton: Button;
|
|
private _shareButton: Button | null = null;
|
|
|
|
// Callbacks
|
|
private _onReplayCallback: (() => void) | null = null;
|
|
private _onExitCallback: (() => void) | null = null;
|
|
private _onResumeCallback: (() => void) | null = null;
|
|
private _onNextLevelCallback: (() => void) | null = null;
|
|
|
|
// Track whether game has ended
|
|
private _isGameEnded: boolean = false;
|
|
|
|
// Track current level info for progression and results
|
|
private _currentLevelName: string | null = null;
|
|
private _currentLevelId: string | null = null;
|
|
private _totalAsteroids: number = 0;
|
|
|
|
// Track if result has been recorded (prevent duplicates)
|
|
private _resultRecorded: boolean = false;
|
|
|
|
constructor(scene: Scene, shipNode: TransformNode, gameStats: GameStats, onReplay?: () => void, onExit?: () => void, onResume?: () => void, onNextLevel?: () => void) {
|
|
this._scene = scene;
|
|
this._shipNode = shipNode;
|
|
this._gameStats = gameStats;
|
|
this._onReplayCallback = onReplay || null;
|
|
this._onExitCallback = onExit || null;
|
|
this._onResumeCallback = onResume || null;
|
|
this._onNextLevelCallback = onNextLevel || null;
|
|
}
|
|
|
|
/**
|
|
* Initialize the status screen mesh and UI
|
|
*/
|
|
public initialize(): void {
|
|
// Create a plane mesh for the status screen
|
|
this._screenMesh = MeshBuilder.CreatePlane(
|
|
"statusScreen",
|
|
{ width: 1.5, height: 2.25 },
|
|
this._scene
|
|
);
|
|
|
|
// Parent to ship for fixed cockpit position
|
|
this._screenMesh.parent = this._shipNode;
|
|
this._screenMesh.position = new Vector3(0, 1, 2); // 2 meters forward in local space
|
|
this._screenMesh.renderingGroupId = 3; // Always render on top
|
|
this._screenMesh.metadata = { uiPickable: true }; // TAG: VR UI - allow pointer selection
|
|
|
|
// Create material
|
|
const material = new StandardMaterial("statusScreenMaterial", this._scene);
|
|
this._screenMesh.material = material;
|
|
|
|
// Create AdvancedDynamicTexture
|
|
this._texture = AdvancedDynamicTexture.CreateForMesh(
|
|
this._screenMesh,
|
|
1024,
|
|
1536
|
|
);
|
|
this._texture.background = "#1a1a2e";
|
|
|
|
// Create main container
|
|
const mainPanel = new StackPanel("mainPanel");
|
|
mainPanel.width = "100%";
|
|
mainPanel.height = "100%";
|
|
mainPanel.isVertical = true;
|
|
mainPanel.paddingTop = "40px";
|
|
mainPanel.paddingBottom = "40px";
|
|
mainPanel.paddingLeft = "60px";
|
|
mainPanel.paddingRight = "60px";
|
|
|
|
// Title
|
|
const title = this.createTitleText("GAME STATISTICS");
|
|
mainPanel.addControl(title);
|
|
|
|
// Add spacing
|
|
const spacer1 = this.createSpacer(40);
|
|
mainPanel.addControl(spacer1);
|
|
|
|
// Create statistics display
|
|
this._gameTimeText = this.createStatText("Game Time: 00:00");
|
|
mainPanel.addControl(this._gameTimeText);
|
|
|
|
this._asteroidsText = this.createStatText("Asteroids Destroyed: 0");
|
|
mainPanel.addControl(this._asteroidsText);
|
|
|
|
this._hullDamageText = this.createStatText("Hull Damage Taken: 0%");
|
|
mainPanel.addControl(this._hullDamageText);
|
|
|
|
this._shotsFiredText = this.createStatText("Shots Fired: 0");
|
|
mainPanel.addControl(this._shotsFiredText);
|
|
|
|
this._accuracyText = this.createStatText("Accuracy: 0%");
|
|
mainPanel.addControl(this._accuracyText);
|
|
|
|
this._fuelConsumedText = this.createStatText("Fuel Consumed: 0%");
|
|
mainPanel.addControl(this._fuelConsumedText);
|
|
|
|
// Add spacing before score section
|
|
const spacer2 = this.createSpacer(40);
|
|
mainPanel.addControl(spacer2);
|
|
|
|
// Score section divider
|
|
const scoreDivider = new Rectangle("scoreDivider");
|
|
scoreDivider.height = "2px";
|
|
scoreDivider.width = "700px";
|
|
scoreDivider.background = "#00ff88";
|
|
scoreDivider.thickness = 0;
|
|
mainPanel.addControl(scoreDivider);
|
|
|
|
const spacer2b = this.createSpacer(30);
|
|
mainPanel.addControl(spacer2b);
|
|
|
|
// Final score display
|
|
const scoreTitle = this.createTitleText("FINAL SCORE");
|
|
scoreTitle.fontSize = "50px";
|
|
scoreTitle.height = "70px";
|
|
mainPanel.addControl(scoreTitle);
|
|
|
|
this._finalScoreText = new TextBlock();
|
|
this._finalScoreText.text = "0";
|
|
this._finalScoreText.color = "#FFD700"; // Gold color
|
|
this._finalScoreText.fontSize = "80px";
|
|
this._finalScoreText.height = "100px";
|
|
this._finalScoreText.fontWeight = "bold";
|
|
this._finalScoreText.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
|
|
mainPanel.addControl(this._finalScoreText);
|
|
|
|
// Score breakdown
|
|
this._scoreBreakdownText = new TextBlock();
|
|
this._scoreBreakdownText.text = "";
|
|
this._scoreBreakdownText.color = "#aaaaaa";
|
|
this._scoreBreakdownText.fontSize = "20px";
|
|
this._scoreBreakdownText.height = "120px";
|
|
this._scoreBreakdownText.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
|
|
this._scoreBreakdownText.textVerticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
|
|
this._scoreBreakdownText.textWrapping = true;
|
|
mainPanel.addControl(this._scoreBreakdownText);
|
|
|
|
// Star ratings
|
|
this._starRatingText = new TextBlock();
|
|
this._starRatingText.text = "";
|
|
this._starRatingText.color = "#FFD700";
|
|
this._starRatingText.fontSize = "40px";
|
|
this._starRatingText.height = "100px";
|
|
this._starRatingText.fontWeight = "bold";
|
|
this._starRatingText.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
|
|
this._starRatingText.textVerticalAlignment = Control.VERTICAL_ALIGNMENT_TOP;
|
|
this._starRatingText.textWrapping = true;
|
|
mainPanel.addControl(this._starRatingText);
|
|
|
|
// Add spacing before buttons
|
|
const spacer3 = this.createSpacer(40);
|
|
mainPanel.addControl(spacer3);
|
|
|
|
// Create button bar
|
|
const buttonBar = new StackPanel("buttonBar");
|
|
buttonBar.isVertical = false;
|
|
buttonBar.height = "80px";
|
|
buttonBar.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
|
|
buttonBar.spacing = 20;
|
|
|
|
// Create Resume button (only shown when game hasn't ended)
|
|
this._resumeButton = Button.CreateSimpleButton("resumeButton", "RESUME GAME");
|
|
this._resumeButton.width = "300px";
|
|
this._resumeButton.height = "60px";
|
|
this._resumeButton.color = "white";
|
|
this._resumeButton.background = "#00ff88";
|
|
this._resumeButton.cornerRadius = 10;
|
|
this._resumeButton.thickness = 0;
|
|
this._resumeButton.fontSize = "30px";
|
|
this._resumeButton.fontWeight = "bold";
|
|
this._resumeButton.onPointerClickObservable.add(() => {
|
|
if (this._onResumeCallback) {
|
|
this._onResumeCallback();
|
|
}
|
|
});
|
|
buttonBar.addControl(this._resumeButton);
|
|
|
|
// Create Next Level button (only shown when game has ended and there's a next level)
|
|
/*this._nextLevelButton = Button.CreateSimpleButton("nextLevelButton", "NEXT LEVEL");
|
|
this._nextLevelButton.width = "300px";
|
|
this._nextLevelButton.height = "60px";
|
|
this._nextLevelButton.color = "white";
|
|
this._nextLevelButton.background = "#0088ff";
|
|
this._nextLevelButton.cornerRadius = 10;
|
|
this._nextLevelButton.thickness = 0;
|
|
this._nextLevelButton.fontSize = "30px";
|
|
this._nextLevelButton.fontWeight = "bold";
|
|
this._nextLevelButton.onPointerClickObservable.add(() => {
|
|
if (this._onNextLevelCallback) {
|
|
this._onNextLevelCallback();
|
|
}
|
|
});
|
|
buttonBar.addControl(this._nextLevelButton);
|
|
|
|
// Create Replay button (only shown when game has ended)
|
|
this._replayButton = Button.CreateSimpleButton("replayButton", "REPLAY");
|
|
this._replayButton.width = "300px";
|
|
this._replayButton.height = "60px";
|
|
this._replayButton.color = "white";
|
|
this._replayButton.background = "#00ff88";
|
|
this._replayButton.cornerRadius = 10;
|
|
this._replayButton.thickness = 0;
|
|
this._replayButton.fontSize = "30px";
|
|
this._replayButton.fontWeight = "bold";
|
|
this._replayButton.onPointerClickObservable.add(() => {
|
|
if (this._onReplayCallback) {
|
|
this._onReplayCallback();
|
|
}
|
|
});
|
|
buttonBar.addControl(this._replayButton);*/
|
|
|
|
// Create Exit VR button
|
|
this._exitButton = Button.CreateSimpleButton("exitButton", "EXIT");
|
|
this._exitButton.width = "300px";
|
|
this._exitButton.height = "60px";
|
|
this._exitButton.color = "white";
|
|
this._exitButton.background = "#cc3333";
|
|
this._exitButton.cornerRadius = 10;
|
|
this._exitButton.thickness = 0;
|
|
this._exitButton.fontSize = "30px";
|
|
this._exitButton.fontWeight = "bold";
|
|
this._exitButton.onPointerClickObservable.add(() => {
|
|
if (this._onExitCallback) {
|
|
this._onExitCallback();
|
|
}
|
|
});
|
|
buttonBar.addControl(this._exitButton);
|
|
|
|
mainPanel.addControl(buttonBar);
|
|
|
|
// Create share button bar (separate row for social sharing)
|
|
const shareBar = new StackPanel("shareBar");
|
|
shareBar.isVertical = false;
|
|
shareBar.height = "80px";
|
|
shareBar.horizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
|
|
shareBar.spacing = 20;
|
|
shareBar.paddingTop = "20px";
|
|
|
|
// Create Share button (only shown when user is authenticated with Facebook)
|
|
this._shareButton = Button.CreateSimpleButton("shareButton", "📱 SHARE ON FACEBOOK");
|
|
this._shareButton.width = "400px";
|
|
this._shareButton.height = "60px";
|
|
this._shareButton.color = "white";
|
|
this._shareButton.background = "#1877f2"; // Facebook blue
|
|
this._shareButton.cornerRadius = 10;
|
|
this._shareButton.thickness = 0;
|
|
this._shareButton.fontSize = "28px";
|
|
this._shareButton.fontWeight = "bold";
|
|
this._shareButton.isVisible = false; // Hidden by default, shown only for Facebook users
|
|
this._shareButton.onPointerClickObservable.add(() => {
|
|
this.handleShareClick();
|
|
});
|
|
shareBar.addControl(this._shareButton);
|
|
|
|
mainPanel.addControl(shareBar);
|
|
|
|
this._texture.addControl(mainPanel);
|
|
|
|
// Initially hide the screen
|
|
this._screenMesh.setEnabled(false);
|
|
this._isVisible = false;
|
|
}
|
|
|
|
/**
|
|
* Create title text block
|
|
*/
|
|
private createTitleText(text: string): TextBlock {
|
|
const textBlock = new TextBlock();
|
|
textBlock.text = text;
|
|
textBlock.color = "#00ff88";
|
|
textBlock.fontSize = "80px";
|
|
textBlock.height = "100px";
|
|
textBlock.fontWeight = "bold";
|
|
textBlock.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_CENTER;
|
|
return textBlock;
|
|
}
|
|
|
|
/**
|
|
* Create stat text block
|
|
*/
|
|
private createStatText(text: string): TextBlock {
|
|
const textBlock = new TextBlock();
|
|
textBlock.text = text;
|
|
textBlock.color = "#ffffff";
|
|
textBlock.fontSize = "50px";
|
|
textBlock.height = "70px";
|
|
textBlock.textHorizontalAlignment = Control.HORIZONTAL_ALIGNMENT_LEFT;
|
|
textBlock.paddingTop = "10px";
|
|
textBlock.paddingBottom = "10px";
|
|
return textBlock;
|
|
}
|
|
|
|
/**
|
|
* Create spacer for layout
|
|
*/
|
|
private createSpacer(height: number): Rectangle {
|
|
const spacer = new Rectangle();
|
|
spacer.height = `${height}px`;
|
|
spacer.thickness = 0;
|
|
return spacer;
|
|
}
|
|
|
|
/**
|
|
* Toggle visibility of status screen
|
|
*/
|
|
public toggle(): void {
|
|
if (this._isVisible) {
|
|
this.hide();
|
|
} else {
|
|
this.show();
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Set the current level info for progression tracking and results
|
|
*/
|
|
public setCurrentLevel(levelId: string, levelName: string, totalAsteroids: number): void {
|
|
log.info('[StatusScreen] setCurrentLevel called:', { levelId, levelName, totalAsteroids });
|
|
this._currentLevelId = levelId;
|
|
this._currentLevelName = levelName;
|
|
this._totalAsteroids = totalAsteroids;
|
|
}
|
|
|
|
/**
|
|
* Set the par time for score calculation
|
|
* @param parTime - Expected completion time in seconds
|
|
*/
|
|
public setParTime(parTime: number): void {
|
|
this._parTime = parTime;
|
|
}
|
|
|
|
/**
|
|
* Show the status screen
|
|
* @param isGameEnded - true if game has ended (death/stranded/victory), false if manually paused
|
|
* @param victory - true if the level was completed successfully
|
|
* @param endReason - specific reason for game end ('victory' | 'death' | 'stranded')
|
|
*/
|
|
public show(isGameEnded: boolean = false, victory: boolean = false, endReason?: 'victory' | 'death' | 'stranded'): void {
|
|
if (!this._screenMesh) {
|
|
return;
|
|
}
|
|
|
|
// Store game ended state
|
|
this._isGameEnded = isGameEnded;
|
|
|
|
// Mark level as complete if victory and we have a level name
|
|
const progression = ProgressionManager.getInstance();
|
|
if (victory && this._currentLevelName) {
|
|
const stats = this._gameStats.getStats();
|
|
const gameTimeSeconds = this.parseGameTime(stats.gameTime);
|
|
progression.markLevelComplete(this._currentLevelName, {
|
|
completionTime: gameTimeSeconds,
|
|
accuracy: stats.accuracy // Already a number from getAccuracy()
|
|
});
|
|
}
|
|
|
|
// Record game result when game ends (not on manual pause)
|
|
if (isGameEnded && endReason && !this._resultRecorded) {
|
|
this.recordGameResult(endReason);
|
|
this._resultRecorded = true;
|
|
}
|
|
|
|
// Determine if there's a next level
|
|
const nextLevel = progression.getNextLevel();
|
|
const hasNextLevel = nextLevel !== null;
|
|
|
|
// Show/hide appropriate buttons based on whether game has ended
|
|
if (this._resumeButton) {
|
|
this._resumeButton.isVisible = !isGameEnded;
|
|
}
|
|
if (this._replayButton) {
|
|
this._replayButton.isVisible = isGameEnded;
|
|
}
|
|
if (this._nextLevelButton) {
|
|
// Only show Next Level if game ended in victory and there's a next level
|
|
this._nextLevelButton.isVisible = isGameEnded && victory && hasNextLevel;
|
|
}
|
|
|
|
// Show share button only if game ended in victory and user is authenticated with Facebook
|
|
if (this._shareButton) {
|
|
const authService = AuthService.getInstance();
|
|
const isFacebookUser = authService.isAuthenticatedWithFacebook();
|
|
this._shareButton.isVisible = isGameEnded && victory && isFacebookUser;
|
|
|
|
// Initialize Facebook SDK if needed
|
|
if (this._shareButton.isVisible) {
|
|
const fbShare = FacebookShare.getInstance();
|
|
fbShare.initialize().catch(error => {
|
|
log.error('Failed to initialize Facebook SDK:', error);
|
|
});
|
|
}
|
|
}
|
|
|
|
// Disable ship controls and enable pointer selection via InputControlManager
|
|
const inputManager = InputControlManager.getInstance();
|
|
inputManager.disableShipControls("StatusScreen");
|
|
|
|
// Update statistics before showing
|
|
this.updateStatistics();
|
|
|
|
// Simply enable the mesh - position/rotation handled by parenting
|
|
this._screenMesh.setEnabled(true);
|
|
this._isVisible = true;
|
|
}
|
|
|
|
/**
|
|
* Parse game time string (MM:SS) to seconds
|
|
*/
|
|
private parseGameTime(timeString: string): number {
|
|
const parts = timeString.split(':');
|
|
if (parts.length === 2) {
|
|
const minutes = parseInt(parts[0], 10);
|
|
const seconds = parseInt(parts[1], 10);
|
|
return minutes * 60 + seconds;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Hide the status screen
|
|
*/
|
|
public hide(): void {
|
|
if (!this._screenMesh) {
|
|
return;
|
|
}
|
|
|
|
// Re-enable ship controls and disable pointer selection via InputControlManager
|
|
const inputManager = InputControlManager.getInstance();
|
|
inputManager.enableShipControls("StatusScreen");
|
|
|
|
this._screenMesh.setEnabled(false);
|
|
this._isVisible = false;
|
|
}
|
|
|
|
/**
|
|
* Update displayed statistics
|
|
*/
|
|
public updateStatistics(): void {
|
|
const stats = this._gameStats.getStats();
|
|
|
|
this._gameTimeText.text = `Game Time: ${stats.gameTime}`;
|
|
this._asteroidsText.text = `Asteroids Destroyed: ${stats.asteroidsDestroyed}`;
|
|
this._hullDamageText.text = `Hull Damage Taken: ${stats.hullDamageTaken}%`;
|
|
this._shotsFiredText.text = `Shots Fired: ${stats.shotsFired}`;
|
|
this._accuracyText.text = `Accuracy: ${stats.accuracy}%`;
|
|
this._fuelConsumedText.text = `Fuel Consumed: ${stats.fuelConsumed}%`;
|
|
|
|
// Calculate and display score
|
|
const scoreCalc = this._gameStats.calculateFinalScore(this._parTime);
|
|
|
|
// Update final score
|
|
this._finalScoreText.text = scoreCalc.finalScore.toLocaleString();
|
|
|
|
// Update score breakdown
|
|
this._scoreBreakdownText.text =
|
|
`Time: ${scoreCalc.timeMultiplier.toFixed(2)}x | ` +
|
|
`Accuracy: ${scoreCalc.accuracyMultiplier.toFixed(2)}x\n` +
|
|
`Fuel: ${scoreCalc.fuelMultiplier.toFixed(2)}x | ` +
|
|
`Hull: ${scoreCalc.hullMultiplier.toFixed(2)}x`;
|
|
|
|
// Update star ratings with Unicode stars and colors
|
|
const timeStars = formatStars(scoreCalc.stars.time);
|
|
const accStars = formatStars(scoreCalc.stars.accuracy);
|
|
const fuelStars = formatStars(scoreCalc.stars.fuel);
|
|
const hullStars = formatStars(scoreCalc.stars.hull);
|
|
|
|
this._starRatingText.text =
|
|
`${timeStars} ${accStars} ${fuelStars} ${hullStars}\n` +
|
|
`Time Acc Fuel Hull\n` +
|
|
`${scoreCalc.stars.total}/12 Stars`;
|
|
}
|
|
|
|
/**
|
|
* Check if status screen is visible
|
|
*/
|
|
public get isVisible(): boolean {
|
|
return this._isVisible;
|
|
}
|
|
|
|
/**
|
|
* Handle Facebook share button click
|
|
*/
|
|
private async handleShareClick(): Promise<void> {
|
|
const stats = this._gameStats.getStats();
|
|
const fbShare = FacebookShare.getInstance();
|
|
|
|
// Prepare share data
|
|
const shareData: ShareData = {
|
|
levelName: this._currentLevelName || 'Unknown Level',
|
|
gameTime: stats.gameTime,
|
|
asteroidsDestroyed: stats.asteroidsDestroyed,
|
|
accuracy: stats.accuracy,
|
|
completed: true
|
|
};
|
|
|
|
// Try to share via Facebook SDK
|
|
const success = await fbShare.shareResults(shareData);
|
|
|
|
if (!success) {
|
|
// Fallback to Web Share API or copy to clipboard
|
|
const webShareSuccess = await fbShare.shareWithWebAPI(shareData);
|
|
|
|
if (!webShareSuccess) {
|
|
// Final fallback - copy to clipboard
|
|
const copied = await fbShare.copyToClipboard(shareData);
|
|
if (copied) {
|
|
// Show notification (you could add a toast notification here)
|
|
log.info('Results copied to clipboard!');
|
|
|
|
// Update button text temporarily to show feedback
|
|
if (this._shareButton) {
|
|
const originalText = this._shareButton.textBlock?.text;
|
|
if (this._shareButton.textBlock) {
|
|
this._shareButton.textBlock.text = "✓ COPIED TO CLIPBOARD";
|
|
}
|
|
setTimeout(() => {
|
|
if (this._shareButton?.textBlock && originalText) {
|
|
this._shareButton.textBlock.text = originalText;
|
|
}
|
|
}, 2000);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
// Success! Show feedback
|
|
if (this._shareButton) {
|
|
const originalText = this._shareButton.textBlock?.text;
|
|
const originalColor = this._shareButton.background;
|
|
|
|
if (this._shareButton.textBlock) {
|
|
this._shareButton.textBlock.text = "✓ SHARED!";
|
|
}
|
|
this._shareButton.background = "#00ff88";
|
|
|
|
setTimeout(() => {
|
|
if (this._shareButton?.textBlock && originalText) {
|
|
this._shareButton.textBlock.text = originalText;
|
|
this._shareButton.background = originalColor;
|
|
}
|
|
}, 2000);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Record game result to the results service
|
|
*/
|
|
private recordGameResult(endReason: 'victory' | 'death' | 'stranded'): void {
|
|
log.info('[StatusScreen] recordGameResult called with endReason:', endReason);
|
|
log.info('[StatusScreen] Level info:', {
|
|
levelId: this._currentLevelId,
|
|
levelName: this._currentLevelName,
|
|
totalAsteroids: this._totalAsteroids,
|
|
parTime: this._parTime
|
|
});
|
|
|
|
// Only record if we have level info
|
|
if (!this._currentLevelId || !this._currentLevelName) {
|
|
log.warn('[StatusScreen] Cannot record result - missing level info');
|
|
log.debug('[StatusScreen] Cannot record result - missing level info');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const result = GameResultsService.buildResult(
|
|
this._currentLevelId,
|
|
this._currentLevelName,
|
|
this._gameStats,
|
|
this._totalAsteroids,
|
|
endReason,
|
|
this._parTime
|
|
);
|
|
|
|
log.info('[StatusScreen] Built result:', result);
|
|
|
|
const service = GameResultsService.getInstance();
|
|
service.saveResult(result);
|
|
log.info('[StatusScreen] Game result saved successfully');
|
|
log.debug('[StatusScreen] Game result recorded:', result.id, result.finalScore, result.endReason);
|
|
} catch (error) {
|
|
log.error('[StatusScreen] Failed to record game result:', error);
|
|
log.debug('[StatusScreen] Failed to record game result:', error);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Dispose of status screen resources
|
|
*/
|
|
public dispose(): void {
|
|
if (this._texture) {
|
|
this._texture.dispose();
|
|
}
|
|
if (this._screenMesh) {
|
|
this._screenMesh.dispose();
|
|
}
|
|
}
|
|
}
|