Ä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.
288 lines
8.6 KiB
JavaScript
288 lines
8.6 KiB
JavaScript
import models from '../models/index.js';
|
|
import BaseService from './BaseService.js';
|
|
|
|
const { TaxiHighscore, User, TaxiMap } = models;
|
|
|
|
class TaxiHighscoreService extends BaseService {
|
|
constructor() {
|
|
super();
|
|
this.model = TaxiHighscore;
|
|
}
|
|
|
|
/**
|
|
* Speichert oder aktualisiert einen Highscore-Eintrag
|
|
* Jeder User kann nur einen Eintrag pro Map haben (der beste wird gespeichert)
|
|
* @param {Object} highscoreData - Die Highscore-Daten
|
|
* @param {string} highscoreData.hashedUserId - Hash-ID des Users
|
|
* @param {string} highscoreData.nickname - Nickname des Users
|
|
* @param {number} highscoreData.passengersDelivered - Anzahl abgelieferter Passagiere
|
|
* @param {number} highscoreData.playtime - Spielzeit in Sekunden
|
|
* @param {number} highscoreData.points - Erreichte Punkte
|
|
* @param {number} highscoreData.mapId - ID der Map
|
|
* @param {string} highscoreData.mapName - Name der Map
|
|
* @returns {Promise<Object>} Gespeicherter oder aktualisierter Highscore-Eintrag
|
|
*/
|
|
async createHighscore(highscoreData) {
|
|
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
|
|
const existingHighscore = await this.model.findOne({
|
|
where: {
|
|
userId: userId,
|
|
mapId: highscoreData.mapId
|
|
}
|
|
});
|
|
|
|
if (existingHighscore) {
|
|
// Nur aktualisieren wenn der neue Score besser ist (mehr Punkte)
|
|
if (highscoreData.points > existingHighscore.points) {
|
|
await existingHighscore.update({
|
|
nickname: highscoreData.nickname,
|
|
passengersDelivered: highscoreData.passengersDelivered,
|
|
playtime: highscoreData.playtime,
|
|
points: highscoreData.points,
|
|
mapName: highscoreData.mapName
|
|
});
|
|
return existingHighscore;
|
|
} else {
|
|
// Neuer Score ist nicht besser, existierenden zurückgeben
|
|
return existingHighscore;
|
|
}
|
|
} else {
|
|
// Kein existierender Eintrag, neuen erstellen
|
|
const highscore = await this.model.create({
|
|
userId: userId,
|
|
nickname: highscoreData.nickname,
|
|
passengersDelivered: highscoreData.passengersDelivered,
|
|
playtime: highscoreData.playtime,
|
|
points: highscoreData.points,
|
|
mapId: highscoreData.mapId,
|
|
mapName: highscoreData.mapName
|
|
});
|
|
|
|
return highscore;
|
|
}
|
|
} catch (error) {
|
|
console.error('Fehler beim Erstellen/Aktualisieren des Highscores:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holt die Top-Highscores für eine bestimmte Map
|
|
* @param {number} mapId - ID der Map (optional)
|
|
* @param {number} limit - Anzahl der Einträge (Standard: 10)
|
|
* @param {string} orderBy - Sortierung ('points', 'passengersDelivered', 'playtime')
|
|
* @returns {Promise<Array>} Array der Highscore-Einträge
|
|
*/
|
|
async getTopHighscores(mapId = null, limit = 10, orderBy = 'points') {
|
|
try {
|
|
const whereClause = mapId ? { mapId } : {};
|
|
|
|
const highscores = await this.model.findAll({
|
|
where: whereClause,
|
|
include: [
|
|
{
|
|
model: User,
|
|
as: 'user',
|
|
attributes: ['id', 'username'],
|
|
required: false // LEFT JOIN, da User gelöscht sein könnte
|
|
},
|
|
{
|
|
model: TaxiMap,
|
|
as: 'map',
|
|
attributes: ['id', 'name'],
|
|
required: false
|
|
}
|
|
],
|
|
order: [[orderBy, 'DESC']],
|
|
limit: parseInt(limit)
|
|
});
|
|
|
|
return highscores;
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der Highscores:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holt die persönlichen Bestleistungen eines Users
|
|
* @param {string} hashedUserId - Hash-ID des Users
|
|
* @param {number} mapId - ID der Map (optional)
|
|
* @returns {Promise<Object>} Bestleistungen des Users
|
|
*/
|
|
async getUserBestScores(hashedUserId, mapId = null) {
|
|
try {
|
|
// Hash-ID zu echter User-ID konvertieren
|
|
const user = await this.getUserByHashedId(hashedUserId);
|
|
const userId = user.id;
|
|
|
|
const whereClause = { userId };
|
|
if (mapId) {
|
|
whereClause.mapId = mapId;
|
|
}
|
|
|
|
const [bestPoints, bestPassengers, bestPlaytime] = await Promise.all([
|
|
this.model.findOne({
|
|
where: whereClause,
|
|
order: [['points', 'DESC']]
|
|
}),
|
|
this.model.findOne({
|
|
where: whereClause,
|
|
order: [['passengersDelivered', 'DESC']]
|
|
}),
|
|
this.model.findOne({
|
|
where: whereClause,
|
|
order: [['playtime', 'DESC']]
|
|
})
|
|
]);
|
|
|
|
return {
|
|
bestPoints,
|
|
bestPassengers,
|
|
bestPlaytime
|
|
};
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der User-Bestleistungen:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holt alle Highscores eines Users
|
|
* @param {string} hashedUserId - Hash-ID des Users
|
|
* @param {number} limit - Anzahl der Einträge (Standard: 20)
|
|
* @returns {Promise<Array>} Array der Highscore-Einträge des Users
|
|
*/
|
|
async getUserHighscores(hashedUserId, limit = 20) {
|
|
try {
|
|
// Hash-ID zu echter User-ID konvertieren
|
|
const user = await this.getUserByHashedId(hashedUserId);
|
|
const userId = user.id;
|
|
|
|
const highscores = await this.model.findAll({
|
|
where: { userId },
|
|
include: [
|
|
{
|
|
model: TaxiMap,
|
|
as: 'map',
|
|
attributes: ['id', 'name']
|
|
}
|
|
],
|
|
order: [['createdAt', 'DESC']],
|
|
limit: parseInt(limit)
|
|
});
|
|
|
|
return highscores;
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der User-Highscores:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holt die Rangliste-Position eines Users für eine bestimmte Map
|
|
* @param {string} hashedUserId - Hash-ID des Users
|
|
* @param {number} mapId - ID der Map (optional)
|
|
* @param {string} orderBy - Sortierung ('points', 'passengersDelivered', 'playtime')
|
|
* @returns {Promise<number>} Rangliste-Position (1-basiert)
|
|
*/
|
|
async getUserRank(hashedUserId, mapId = null, orderBy = 'points') {
|
|
try {
|
|
// Hash-ID zu echter User-ID konvertieren
|
|
const user = await this.getUserByHashedId(hashedUserId);
|
|
const userId = user.id;
|
|
|
|
const whereClause = mapId ? { mapId } : {};
|
|
|
|
const userHighscore = await this.model.findOne({
|
|
where: { userId, ...whereClause },
|
|
order: [[orderBy, 'DESC']]
|
|
});
|
|
|
|
if (!userHighscore) {
|
|
return null; // User hat noch keinen Highscore
|
|
}
|
|
|
|
const rank = await this.model.count({
|
|
where: {
|
|
...whereClause,
|
|
[orderBy]: {
|
|
[this.model.sequelize.Sequelize.Op.gt]: userHighscore[orderBy]
|
|
}
|
|
}
|
|
});
|
|
|
|
return rank + 1; // 1-basierte Position
|
|
} catch (error) {
|
|
console.error('Fehler beim Berechnen der User-Rangliste:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Löscht alle Highscores eines Users (z.B. bei Account-Löschung)
|
|
* @param {number} userId - ID des Users
|
|
* @returns {Promise<number>} Anzahl der gelöschten Einträge
|
|
*/
|
|
async deleteUserHighscores(userId) {
|
|
try {
|
|
const deletedCount = await this.model.destroy({
|
|
where: { userId }
|
|
});
|
|
|
|
return deletedCount;
|
|
} catch (error) {
|
|
console.error('Fehler beim Löschen der User-Highscores:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Holt Statistiken über die Highscores
|
|
* @returns {Promise<Object>} Statistiken
|
|
*/
|
|
async getHighscoreStats() {
|
|
try {
|
|
const [
|
|
totalHighscores,
|
|
totalPlayers,
|
|
averagePoints,
|
|
totalPassengersDelivered,
|
|
totalPlaytime
|
|
] = await Promise.all([
|
|
this.model.count(),
|
|
this.model.count({
|
|
distinct: true,
|
|
col: 'userId'
|
|
}),
|
|
this.model.findOne({
|
|
attributes: [
|
|
[this.model.sequelize.fn('AVG', this.model.sequelize.col('points')), 'avg']
|
|
],
|
|
raw: true
|
|
}),
|
|
this.model.sum('passengersDelivered'),
|
|
this.model.sum('playtime')
|
|
]);
|
|
|
|
return {
|
|
totalHighscores,
|
|
totalPlayers,
|
|
averagePoints: averagePoints ? parseFloat(averagePoints.avg).toFixed(2) : 0,
|
|
totalPassengersDelivered: totalPassengersDelivered || 0,
|
|
totalPlaytime: totalPlaytime || 0
|
|
};
|
|
} catch (error) {
|
|
console.error('Fehler beim Laden der Highscore-Statistiken:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
}
|
|
|
|
export default new TaxiHighscoreService();
|