Änderung: Anpassung der TaxiHighscore-API und Verbesserung der Highscore-Anzeige im Taxi-Spiel
Änderungen: - Umbenennung des API-Endpunkts für Highscores von `/api/taxi/highscore` zu `/api/taxi/highscores`. - Anpassung der Highscore-Datenstruktur zur Verwendung von `hashedUserId` anstelle von `userId`. - Erweiterung der Router-Logik zur besseren Organisation der Highscore-Abfragen. - Implementierung einer neuen Highscore-Anzeige im Spiel, die die Top 20 Spieler und den aktuellen Spieler anzeigt. Diese Anpassungen verbessern die API-Konsistenz und erweitern die Benutzeroberfläche für die Highscore-Anzeige im Spiel.
This commit is contained in:
@@ -44,7 +44,7 @@ app.use('/api/admin', adminRouter);
|
|||||||
app.use('/api/match3', match3Router);
|
app.use('/api/match3', match3Router);
|
||||||
app.use('/api/taxi', taxiRouter);
|
app.use('/api/taxi', taxiRouter);
|
||||||
app.use('/api/taxi-maps', taxiMapRouter);
|
app.use('/api/taxi-maps', taxiMapRouter);
|
||||||
app.use('/api/taxi/highscore', taxiHighscoreRouter);
|
app.use('/api/taxi/highscores', taxiHighscoreRouter);
|
||||||
app.use('/images', express.static(path.join(__dirname, '../frontend/public/images')));
|
app.use('/images', express.static(path.join(__dirname, '../frontend/public/images')));
|
||||||
app.use('/api/contact', contactRouter);
|
app.use('/api/contact', contactRouter);
|
||||||
app.use('/api/socialnetwork', socialnetworkRouter);
|
app.use('/api/socialnetwork', socialnetworkRouter);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class TaxiHighscoreController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const highscoreData = {
|
const highscoreData = {
|
||||||
userId: parseInt(userId),
|
hashedUserId: userId, // userId ist bereits ein String (Hash)
|
||||||
nickname,
|
nickname,
|
||||||
passengersDelivered: parseInt(passengersDelivered),
|
passengersDelivered: parseInt(passengersDelivered),
|
||||||
playtime: parseInt(playtime),
|
playtime: parseInt(playtime),
|
||||||
@@ -75,8 +75,7 @@ class TaxiHighscoreController {
|
|||||||
*/
|
*/
|
||||||
async getUserBestScores(req, res) {
|
async getUserBestScores(req, res) {
|
||||||
try {
|
try {
|
||||||
const { userId } = req.params;
|
const { userId, mapId } = req.query;
|
||||||
const { mapId } = req.query;
|
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
@@ -86,7 +85,7 @@ class TaxiHighscoreController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const bestScores = await taxiHighscoreService.getUserBestScores(
|
const bestScores = await taxiHighscoreService.getUserBestScores(
|
||||||
parseInt(userId),
|
userId, // userId ist bereits ein String (Hash)
|
||||||
mapId ? parseInt(mapId) : null
|
mapId ? parseInt(mapId) : null
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -109,8 +108,7 @@ class TaxiHighscoreController {
|
|||||||
*/
|
*/
|
||||||
async getUserHighscores(req, res) {
|
async getUserHighscores(req, res) {
|
||||||
try {
|
try {
|
||||||
const { userId } = req.params;
|
const { userId, limit = 20 } = req.query;
|
||||||
const { limit = 20 } = req.query;
|
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
@@ -120,7 +118,7 @@ class TaxiHighscoreController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const highscores = await taxiHighscoreService.getUserHighscores(
|
const highscores = await taxiHighscoreService.getUserHighscores(
|
||||||
parseInt(userId),
|
userId, // userId ist bereits ein String (Hash)
|
||||||
parseInt(limit)
|
parseInt(limit)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -143,8 +141,7 @@ class TaxiHighscoreController {
|
|||||||
*/
|
*/
|
||||||
async getUserRank(req, res) {
|
async getUserRank(req, res) {
|
||||||
try {
|
try {
|
||||||
const { userId } = req.params;
|
const { userId, mapId, orderBy = 'points' } = req.query;
|
||||||
const { mapId, orderBy = 'points' } = req.query;
|
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return res.status(400).json({
|
return res.status(400).json({
|
||||||
@@ -154,7 +151,7 @@ class TaxiHighscoreController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rank = await taxiHighscoreService.getUserRank(
|
const rank = await taxiHighscoreService.getUserRank(
|
||||||
parseInt(userId),
|
userId, // userId ist bereits ein String (Hash)
|
||||||
mapId ? parseInt(mapId) : null,
|
mapId ? parseInt(mapId) : null,
|
||||||
orderBy
|
orderBy
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,11 +2,23 @@ import express from 'express';
|
|||||||
import taxiHighscoreController from '../controllers/taxiHighscoreController.js';
|
import taxiHighscoreController from '../controllers/taxiHighscoreController.js';
|
||||||
|
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
|
// POST /api/taxi/highscores - Neuen Highscore erstellen
|
||||||
router.post('/', taxiHighscoreController.createHighscore);
|
router.post('/', taxiHighscoreController.createHighscore);
|
||||||
router.get('/top', taxiHighscoreController.getTopHighscores);
|
|
||||||
router.get('/my-best', taxiHighscoreController.getUserBestScores);
|
// GET /api/taxi/highscores - Top Highscores abrufen
|
||||||
router.get('/my-scores', taxiHighscoreController.getUserHighscores);
|
router.get('/', taxiHighscoreController.getTopHighscores);
|
||||||
router.get('/my-rank', taxiHighscoreController.getUserRank);
|
|
||||||
|
// GET /api/taxi/highscores/rank - Rang des Benutzers abrufen
|
||||||
|
router.get('/rank', taxiHighscoreController.getUserRank);
|
||||||
|
|
||||||
|
// GET /api/taxi/highscores/user/best - Beste Punkte des Benutzers
|
||||||
|
router.get('/user/best', taxiHighscoreController.getUserBestScores);
|
||||||
|
|
||||||
|
// GET /api/taxi/highscores/user - Alle Highscores des Benutzers
|
||||||
|
router.get('/user', taxiHighscoreController.getUserHighscores);
|
||||||
|
|
||||||
|
// GET /api/taxi/highscores/stats - Highscore-Statistiken
|
||||||
router.get('/stats', taxiHighscoreController.getHighscoreStats);
|
router.get('/stats', taxiHighscoreController.getHighscoreStats);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class TaxiHighscoreService extends BaseService {
|
|||||||
* Speichert oder aktualisiert einen Highscore-Eintrag
|
* Speichert oder aktualisiert einen Highscore-Eintrag
|
||||||
* Jeder User kann nur einen Eintrag pro Map haben (der beste wird gespeichert)
|
* Jeder User kann nur einen Eintrag pro Map haben (der beste wird gespeichert)
|
||||||
* @param {Object} highscoreData - Die Highscore-Daten
|
* @param {Object} highscoreData - Die Highscore-Daten
|
||||||
* @param {number} highscoreData.userId - ID des Users
|
* @param {string} highscoreData.hashedUserId - Hash-ID des Users
|
||||||
* @param {string} highscoreData.nickname - Nickname des Users
|
* @param {string} highscoreData.nickname - Nickname des Users
|
||||||
* @param {number} highscoreData.passengersDelivered - Anzahl abgelieferter Passagiere
|
* @param {number} highscoreData.passengersDelivered - Anzahl abgelieferter Passagiere
|
||||||
* @param {number} highscoreData.playtime - Spielzeit in Sekunden
|
* @param {number} highscoreData.playtime - Spielzeit in Sekunden
|
||||||
@@ -24,10 +24,14 @@ class TaxiHighscoreService extends BaseService {
|
|||||||
*/
|
*/
|
||||||
async createHighscore(highscoreData) {
|
async createHighscore(highscoreData) {
|
||||||
try {
|
try {
|
||||||
|
// Hash-ID zu echter User-ID konvertieren
|
||||||
|
const user = await this.getUserByHashedId(highscoreData.hashedUserId);
|
||||||
|
const userId = user.id;
|
||||||
|
|
||||||
// Prüfen ob bereits ein Eintrag für diesen User und diese Map existiert
|
// Prüfen ob bereits ein Eintrag für diesen User und diese Map existiert
|
||||||
const existingHighscore = await this.model.findOne({
|
const existingHighscore = await this.model.findOne({
|
||||||
where: {
|
where: {
|
||||||
userId: highscoreData.userId,
|
userId: userId,
|
||||||
mapId: highscoreData.mapId
|
mapId: highscoreData.mapId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -50,7 +54,7 @@ class TaxiHighscoreService extends BaseService {
|
|||||||
} else {
|
} else {
|
||||||
// Kein existierender Eintrag, neuen erstellen
|
// Kein existierender Eintrag, neuen erstellen
|
||||||
const highscore = await this.model.create({
|
const highscore = await this.model.create({
|
||||||
userId: highscoreData.userId,
|
userId: userId,
|
||||||
nickname: highscoreData.nickname,
|
nickname: highscoreData.nickname,
|
||||||
passengersDelivered: highscoreData.passengersDelivered,
|
passengersDelivered: highscoreData.passengersDelivered,
|
||||||
playtime: highscoreData.playtime,
|
playtime: highscoreData.playtime,
|
||||||
@@ -107,12 +111,16 @@ class TaxiHighscoreService extends BaseService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Holt die persönlichen Bestleistungen eines Users
|
* Holt die persönlichen Bestleistungen eines Users
|
||||||
* @param {number} userId - ID des Users
|
* @param {string} hashedUserId - Hash-ID des Users
|
||||||
* @param {number} mapId - ID der Map (optional)
|
* @param {number} mapId - ID der Map (optional)
|
||||||
* @returns {Promise<Object>} Bestleistungen des Users
|
* @returns {Promise<Object>} Bestleistungen des Users
|
||||||
*/
|
*/
|
||||||
async getUserBestScores(userId, mapId = null) {
|
async getUserBestScores(hashedUserId, mapId = null) {
|
||||||
try {
|
try {
|
||||||
|
// Hash-ID zu echter User-ID konvertieren
|
||||||
|
const user = await this.getUserByHashedId(hashedUserId);
|
||||||
|
const userId = user.id;
|
||||||
|
|
||||||
const whereClause = { userId };
|
const whereClause = { userId };
|
||||||
if (mapId) {
|
if (mapId) {
|
||||||
whereClause.mapId = mapId;
|
whereClause.mapId = mapId;
|
||||||
@@ -146,12 +154,16 @@ class TaxiHighscoreService extends BaseService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Holt alle Highscores eines Users
|
* Holt alle Highscores eines Users
|
||||||
* @param {number} userId - ID des Users
|
* @param {string} hashedUserId - Hash-ID des Users
|
||||||
* @param {number} limit - Anzahl der Einträge (Standard: 20)
|
* @param {number} limit - Anzahl der Einträge (Standard: 20)
|
||||||
* @returns {Promise<Array>} Array der Highscore-Einträge des Users
|
* @returns {Promise<Array>} Array der Highscore-Einträge des Users
|
||||||
*/
|
*/
|
||||||
async getUserHighscores(userId, limit = 20) {
|
async getUserHighscores(hashedUserId, limit = 20) {
|
||||||
try {
|
try {
|
||||||
|
// Hash-ID zu echter User-ID konvertieren
|
||||||
|
const user = await this.getUserByHashedId(hashedUserId);
|
||||||
|
const userId = user.id;
|
||||||
|
|
||||||
const highscores = await this.model.findAll({
|
const highscores = await this.model.findAll({
|
||||||
where: { userId },
|
where: { userId },
|
||||||
include: [
|
include: [
|
||||||
@@ -174,13 +186,17 @@ class TaxiHighscoreService extends BaseService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Holt die Rangliste-Position eines Users für eine bestimmte Map
|
* Holt die Rangliste-Position eines Users für eine bestimmte Map
|
||||||
* @param {number} userId - ID des Users
|
* @param {string} hashedUserId - Hash-ID des Users
|
||||||
* @param {number} mapId - ID der Map (optional)
|
* @param {number} mapId - ID der Map (optional)
|
||||||
* @param {string} orderBy - Sortierung ('points', 'passengersDelivered', 'playtime')
|
* @param {string} orderBy - Sortierung ('points', 'passengersDelivered', 'playtime')
|
||||||
* @returns {Promise<number>} Rangliste-Position (1-basiert)
|
* @returns {Promise<number>} Rangliste-Position (1-basiert)
|
||||||
*/
|
*/
|
||||||
async getUserRank(userId, mapId = null, orderBy = 'points') {
|
async getUserRank(hashedUserId, mapId = null, orderBy = 'points') {
|
||||||
try {
|
try {
|
||||||
|
// Hash-ID zu echter User-ID konvertieren
|
||||||
|
const user = await this.getUserByHashedId(hashedUserId);
|
||||||
|
const userId = user.id;
|
||||||
|
|
||||||
const whereClause = mapId ? { mapId } : {};
|
const whereClause = mapId ? { mapId } : {};
|
||||||
|
|
||||||
const userHighscore = await this.model.findOne({
|
const userHighscore = await this.model.findOne({
|
||||||
|
|||||||
@@ -185,18 +185,19 @@ const store = createStore({
|
|||||||
let retryCount = 0;
|
let retryCount = 0;
|
||||||
const maxRetries = 10;
|
const maxRetries = 10;
|
||||||
const retryConnection = (reconnectFn) => {
|
const retryConnection = (reconnectFn) => {
|
||||||
console.log(`Reconnect-Versuch ${retryCount + 1}/${maxRetries}`);
|
console.log(`Backend-Reconnect-Versuch ${retryCount + 1}/${maxRetries}`);
|
||||||
if (retryCount >= maxRetries) {
|
if (retryCount >= maxRetries) {
|
||||||
// Nach maxRetries alle 5 Sekunden weiter versuchen
|
// Nach maxRetries alle 5 Sekunden weiter versuchen
|
||||||
console.log('Max Retries erreicht, versuche weiter alle 5 Sekunden...');
|
console.log('Backend: Max Retries erreicht, versuche weiter alle 5 Sekunden...');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
retryCount = 0; // Reset für nächsten Zyklus
|
||||||
reconnectFn();
|
reconnectFn();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
retryCount++;
|
retryCount++;
|
||||||
const delay = 5000; // Alle 5 Sekunden versuchen
|
const delay = 5000; // Alle 5 Sekunden versuchen
|
||||||
console.log(`Warte ${delay}ms bis zum nächsten Reconnect-Versuch...`);
|
console.log(`Backend: Warte ${delay}ms bis zum nächsten Reconnect-Versuch...`);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
reconnectFn();
|
reconnectFn();
|
||||||
}, delay);
|
}, delay);
|
||||||
@@ -309,6 +310,7 @@ const store = createStore({
|
|||||||
// Nach maxRetries alle 5 Sekunden weiter versuchen
|
// Nach maxRetries alle 5 Sekunden weiter versuchen
|
||||||
console.log('Daemon: Max Retries erreicht, versuche weiter alle 5 Sekunden...');
|
console.log('Daemon: Max Retries erreicht, versuche weiter alle 5 Sekunden...');
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
|
retryCount = 0; // Reset für nächsten Zyklus
|
||||||
reconnectFn();
|
reconnectFn();
|
||||||
}, 5000);
|
}, 5000);
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -91,17 +91,57 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Canvas -->
|
<!-- Canvas und Highscore Container -->
|
||||||
<div class="game-canvas-container">
|
<div class="game-canvas-section">
|
||||||
<canvas
|
<!-- Canvas (immer sichtbar) -->
|
||||||
ref="gameCanvas"
|
<div class="game-canvas-container">
|
||||||
width="500"
|
<canvas
|
||||||
height="500"
|
ref="gameCanvas"
|
||||||
class="game-canvas"
|
width="500"
|
||||||
@click="handleCanvasClick"
|
height="500"
|
||||||
@keydown="handleKeyDown"
|
class="game-canvas"
|
||||||
tabindex="0"
|
@click="handleCanvasClick"
|
||||||
></canvas>
|
@keydown="handleKeyDown"
|
||||||
|
tabindex="0"
|
||||||
|
></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Highscore-Anzeige (als Overlay über dem Canvas) -->
|
||||||
|
<div v-if="showHighscore" class="highscore-overlay">
|
||||||
|
<div class="highscore-header">
|
||||||
|
<h2>🏆 Highscore</h2>
|
||||||
|
<div class="highscore-subtitle">Top 20 Spieler</div>
|
||||||
|
</div>
|
||||||
|
<div class="highscore-list">
|
||||||
|
<div v-if="loadingHighscore" class="loading-message">
|
||||||
|
Lade Highscore...
|
||||||
|
</div>
|
||||||
|
<div v-else-if="highscoreList.length === 0" class="no-highscore">
|
||||||
|
Noch keine Highscores vorhanden
|
||||||
|
</div>
|
||||||
|
<div v-else class="highscore-table">
|
||||||
|
<div
|
||||||
|
v-for="(entry, index) in highscoreList"
|
||||||
|
:key="index"
|
||||||
|
class="highscore-entry"
|
||||||
|
:class="{ 'current-player': entry.isCurrentPlayer }"
|
||||||
|
>
|
||||||
|
<div class="highscore-rank">{{ entry.rank }}</div>
|
||||||
|
<div class="highscore-name">{{ entry.nickname }}</div>
|
||||||
|
<div class="highscore-points">{{ entry.points }} Pkt</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="showCurrentPlayerBelow" class="highscore-separator">...</div>
|
||||||
|
<div
|
||||||
|
v-if="currentPlayerEntry && showCurrentPlayerBelow"
|
||||||
|
class="highscore-entry current-player"
|
||||||
|
>
|
||||||
|
<div class="highscore-rank">{{ currentPlayerEntry.rank }}</div>
|
||||||
|
<div class="highscore-name">{{ currentPlayerEntry.nickname }}</div>
|
||||||
|
<div class="highscore-points">{{ currentPlayerEntry.points }} Pkt</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -114,6 +154,9 @@
|
|||||||
<button @click="restartLevel" class="control-button">
|
<button @click="restartLevel" class="control-button">
|
||||||
{{ $t('minigames.taxi.restartLevel') }}
|
{{ $t('minigames.taxi.restartLevel') }}
|
||||||
</button>
|
</button>
|
||||||
|
<button @click="toggleHighscore" class="control-button">
|
||||||
|
{{ showHighscore ? 'Zurück zum Spiel' : 'Highscore' }}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -312,6 +355,11 @@ export default {
|
|||||||
,prevTaxiY: 250
|
,prevTaxiY: 250
|
||||||
,skipRedLightOneFrame: false
|
,skipRedLightOneFrame: false
|
||||||
,gasStations: [] // Tankstellen im Spiel
|
,gasStations: [] // Tankstellen im Spiel
|
||||||
|
,showHighscore: false // Highscore-Anzeige aktiv
|
||||||
|
,highscoreList: [] // Liste der Highscore-Einträge
|
||||||
|
,loadingHighscore: false // Lade-Status für Highscore
|
||||||
|
,currentPlayerEntry: null // Eintrag des aktuellen Spielers
|
||||||
|
,showCurrentPlayerBelow: false // Zeige aktuellen Spieler nach Platz 20
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -586,6 +634,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
setupEventListeners() {
|
setupEventListeners() {
|
||||||
|
// Event-Listener auf Document registrieren (Canvas bleibt immer sichtbar)
|
||||||
document.addEventListener('keydown', this.handleKeyDown);
|
document.addEventListener('keydown', this.handleKeyDown);
|
||||||
document.addEventListener('keyup', this.handleKeyUp);
|
document.addEventListener('keyup', this.handleKeyUp);
|
||||||
},
|
},
|
||||||
@@ -698,6 +747,9 @@ export default {
|
|||||||
clearTimeout(this.motorStopTimeout);
|
clearTimeout(this.motorStopTimeout);
|
||||||
this.motorStopTimeout = null;
|
this.motorStopTimeout = null;
|
||||||
}
|
}
|
||||||
|
// Event-Listener von Document entfernen
|
||||||
|
document.removeEventListener('keydown', this.handleKeyDown);
|
||||||
|
document.removeEventListener('keyup', this.handleKeyUp);
|
||||||
// Cleanup aller Timeouts
|
// Cleanup aller Timeouts
|
||||||
if (this.passengerGenerationTimeout) {
|
if (this.passengerGenerationTimeout) {
|
||||||
clearTimeout(this.passengerGenerationTimeout);
|
clearTimeout(this.passengerGenerationTimeout);
|
||||||
@@ -708,8 +760,6 @@ export default {
|
|||||||
this.crashDialogTimeout = null;
|
this.crashDialogTimeout = null;
|
||||||
}
|
}
|
||||||
// AudioContext bleibt global erhalten, nicht schließen
|
// AudioContext bleibt global erhalten, nicht schließen
|
||||||
document.removeEventListener('keydown', this.handleKeyDown);
|
|
||||||
document.removeEventListener('keyup', this.handleKeyUp);
|
|
||||||
if (this.audioUnlockHandler) {
|
if (this.audioUnlockHandler) {
|
||||||
document.removeEventListener('pointerdown', this.audioUnlockHandler, { capture: true });
|
document.removeEventListener('pointerdown', this.audioUnlockHandler, { capture: true });
|
||||||
document.removeEventListener('touchstart', this.audioUnlockHandler, { capture: true });
|
document.removeEventListener('touchstart', this.audioUnlockHandler, { capture: true });
|
||||||
@@ -2865,8 +2915,9 @@ export default {
|
|||||||
|
|
||||||
// AudioContext bei erster Benutzerinteraktion initialisieren
|
// AudioContext bei erster Benutzerinteraktion initialisieren
|
||||||
this.ensureAudioUnlockedInEvent();
|
this.ensureAudioUnlockedInEvent();
|
||||||
// Bei Beschleunigungs-Key Motor starten (User-Geste garantiert)
|
|
||||||
if ((event.key === 'ArrowUp' || event.key === 'w' || event.key === 'W') && this.motorSound && !this.motorSound.isPlaying) {
|
// Motor nur starten wenn Spiel nicht pausiert ist
|
||||||
|
if (!this.isPaused && (event.key === 'ArrowUp' || event.key === 'w' || event.key === 'W') && this.motorSound && !this.motorSound.isPlaying) {
|
||||||
this.motorSound.start();
|
this.motorSound.start();
|
||||||
// Direkt Parameter setzen, um hörbares Feedback ohne Verzögerung zu bekommen
|
// Direkt Parameter setzen, um hörbares Feedback ohne Verzögerung zu bekommen
|
||||||
const speedKmh = Math.max(5, this.taxi.speed * 5);
|
const speedKmh = Math.max(5, this.taxi.speed * 5);
|
||||||
@@ -2917,6 +2968,14 @@ export default {
|
|||||||
togglePause() {
|
togglePause() {
|
||||||
this.isPaused = !this.isPaused;
|
this.isPaused = !this.isPaused;
|
||||||
this.showPauseOverlay = this.isPaused;
|
this.showPauseOverlay = this.isPaused;
|
||||||
|
|
||||||
|
// Motorgeräusch stoppen wenn pausiert, starten wenn fortgesetzt
|
||||||
|
if (this.isPaused) {
|
||||||
|
if (this.motorSound && this.motorSound.isPlaying) {
|
||||||
|
this.motorSound.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Wenn fortgesetzt wird, startet der Motor automatisch bei der nächsten Beschleunigung
|
||||||
},
|
},
|
||||||
|
|
||||||
restartLevel() {
|
restartLevel() {
|
||||||
@@ -3432,6 +3491,106 @@ export default {
|
|||||||
prev = curr; curr = next || null;
|
prev = curr; curr = next || null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// Highscore-Funktionen
|
||||||
|
async toggleHighscore() {
|
||||||
|
this.showHighscore = !this.showHighscore;
|
||||||
|
|
||||||
|
if (this.showHighscore) {
|
||||||
|
// Spiel pausieren wenn Highscore angezeigt wird
|
||||||
|
if (!this.isPaused) {
|
||||||
|
this.isPaused = true;
|
||||||
|
// Motorgeräusch stoppen wenn pausiert
|
||||||
|
if (this.motorSound && this.motorSound.isPlaying) {
|
||||||
|
this.motorSound.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Highscore laden
|
||||||
|
await this.loadHighscore();
|
||||||
|
} else {
|
||||||
|
// Highscore geschlossen - Spiel automatisch fortsetzen
|
||||||
|
this.isPaused = false;
|
||||||
|
this.showPauseOverlay = false;
|
||||||
|
// Motor startet automatisch bei der nächsten Beschleunigung
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadHighscore() {
|
||||||
|
this.loadingHighscore = true;
|
||||||
|
try {
|
||||||
|
// Lade Top 20 Highscores für die aktuelle Map
|
||||||
|
const response = await apiClient.get('/api/taxi/highscores', {
|
||||||
|
params: {
|
||||||
|
mapId: this.selectedMapId,
|
||||||
|
limit: 20,
|
||||||
|
orderBy: 'points' // Sortiere nach Punkten
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data && response.data.success && Array.isArray(response.data.data)) {
|
||||||
|
this.highscoreList = response.data.data.map((entry, index) => ({
|
||||||
|
rank: index + 1,
|
||||||
|
nickname: entry.nickname || 'Unbekannt',
|
||||||
|
points: entry.points,
|
||||||
|
isCurrentPlayer: entry.userId === this.$store.state.user?.id
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Prüfe ob aktueller Spieler eine Platzierung hat
|
||||||
|
await this.checkCurrentPlayerRank();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Laden der Highscores:', error);
|
||||||
|
this.highscoreList = [];
|
||||||
|
} finally {
|
||||||
|
this.loadingHighscore = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async checkCurrentPlayerRank() {
|
||||||
|
if (!this.$store.state.user?.id) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Lade Rang des aktuellen Spielers
|
||||||
|
const response = await apiClient.get('/api/taxi/highscores/rank', {
|
||||||
|
params: {
|
||||||
|
userId: this.$store.state.user.id,
|
||||||
|
mapId: this.selectedMapId,
|
||||||
|
orderBy: 'points'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.data && response.data.success && response.data.data && response.data.data.rank) {
|
||||||
|
const rank = response.data.data.rank;
|
||||||
|
|
||||||
|
// Wenn Spieler Platz 21 oder schlechter hat
|
||||||
|
if (rank > 20) {
|
||||||
|
this.showCurrentPlayerBelow = true;
|
||||||
|
|
||||||
|
// Lade beste Punkte des Spielers
|
||||||
|
const bestScoreResponse = await apiClient.get('/api/taxi/highscores/user/best', {
|
||||||
|
params: {
|
||||||
|
userId: this.$store.state.user.id,
|
||||||
|
mapId: this.selectedMapId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (bestScoreResponse.data && bestScoreResponse.data.success && bestScoreResponse.data.data) {
|
||||||
|
this.currentPlayerEntry = {
|
||||||
|
rank: rank,
|
||||||
|
nickname: this.$store.state.user.nickname || 'Du',
|
||||||
|
points: bestScoreResponse.data.data.points,
|
||||||
|
isCurrentPlayer: true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.showCurrentPlayerBelow = false;
|
||||||
|
this.currentPlayerEntry = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Fehler beim Laden des Spieler-Rangs:', error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3473,6 +3632,7 @@ export default {
|
|||||||
|
|
||||||
/* Game Canvas Section */
|
/* Game Canvas Section */
|
||||||
.game-canvas-section {
|
.game-canvas-section {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -3890,6 +4050,7 @@ export default {
|
|||||||
margin: 0; /* Kein Margin */
|
margin: 0; /* Kein Margin */
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: 500px; /* Feste Breite wie das Tacho-Display */
|
width: 500px; /* Feste Breite wie das Tacho-Display */
|
||||||
|
height: 500px; /* Feste Höhe beibehalten */
|
||||||
box-sizing: border-box; /* Border wird in die Breite eingerechnet */
|
box-sizing: border-box; /* Border wird in die Breite eingerechnet */
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3970,6 +4131,120 @@ export default {
|
|||||||
border: 1px solid #7E471B;
|
border: 1px solid #7E471B;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Highscore Overlay */
|
||||||
|
.highscore-overlay {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 500px;
|
||||||
|
height: 500px;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(5px);
|
||||||
|
border: 2px solid #ddd;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highscore-header {
|
||||||
|
background: #F9A22C;
|
||||||
|
color: #000;
|
||||||
|
padding: 20px;
|
||||||
|
text-align: center;
|
||||||
|
border-bottom: 2px solid #ddd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highscore-header h2 {
|
||||||
|
margin: 0 0 5px 0;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highscore-subtitle {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highscore-list {
|
||||||
|
flex: 1;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-message,
|
||||||
|
.no-highscore {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highscore-table {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highscore-entry {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 50px 1fr auto;
|
||||||
|
gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
padding: 10px 15px;
|
||||||
|
background: #f8f9fa;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highscore-entry:hover {
|
||||||
|
background: #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highscore-entry.current-player {
|
||||||
|
background: #fff3cd;
|
||||||
|
border: 2px solid #ffc107;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highscore-entry.current-player:hover {
|
||||||
|
background: #ffe8a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highscore-rank {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #F9A22C;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highscore-name {
|
||||||
|
font-size: 1rem;
|
||||||
|
color: #333;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highscore-points {
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #666;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highscore-separator {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #999;
|
||||||
|
padding: 10px 0;
|
||||||
|
letter-spacing: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* Responsive Design */
|
/* Responsive Design */
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
|
|||||||
Reference in New Issue
Block a user