Änderung: Hinzufügen von TaxiHighscore-Logik und Verbesserung der API-Integration
Änderungen: - Implementierung des neuen Routers für TaxiHighscore zur Verwaltung von Highscore-Daten. - Anpassung der Datenbankmodelle zur Unterstützung von TaxiHighscore-Associations. - Erweiterung der Vue-Komponenten zur Anzeige und Speicherung von Highscores im Taxi-Spiel. - Verbesserung der Statusanzeige im AppHeader zur besseren Benutzerinteraktion. Diese Anpassungen erweitern die Spielmechanik und Benutzererfahrung, indem sie die Verwaltung von Highscores integrieren und die Benutzeroberfläche optimieren.
This commit is contained in:
@@ -3,9 +3,13 @@
|
||||
<div class="logo"><img src="/images/logos/logo.png" /></div>
|
||||
<div class="advertisement">Advertisement</div>
|
||||
<div class="connection-status" v-if="isLoggedIn">
|
||||
<div class="status-indicator" :class="connectionStatusClass">
|
||||
<div class="status-indicator" :class="backendStatusClass">
|
||||
<span class="status-dot"></span>
|
||||
<span class="status-text">{{ connectionStatusText }}</span>
|
||||
<span class="status-text">B</span>
|
||||
</div>
|
||||
<div class="status-indicator" :class="daemonStatusClass">
|
||||
<span class="status-dot"></span>
|
||||
<span class="status-text">D</span>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
@@ -17,8 +21,8 @@ import { mapGetters } from 'vuex';
|
||||
export default {
|
||||
name: 'AppHeader',
|
||||
computed: {
|
||||
...mapGetters(['isLoggedIn', 'connectionStatus']),
|
||||
connectionStatusClass() {
|
||||
...mapGetters(['isLoggedIn', 'connectionStatus', 'daemonConnectionStatus']),
|
||||
backendStatusClass() {
|
||||
return {
|
||||
'status-connected': this.connectionStatus === 'connected',
|
||||
'status-connecting': this.connectionStatus === 'connecting',
|
||||
@@ -26,14 +30,13 @@ export default {
|
||||
'status-error': this.connectionStatus === 'error'
|
||||
};
|
||||
},
|
||||
connectionStatusText() {
|
||||
switch (this.connectionStatus) {
|
||||
case 'connected': return 'Verbunden';
|
||||
case 'connecting': return 'Verbinde...';
|
||||
case 'disconnected': return 'Getrennt';
|
||||
case 'error': return 'Fehler';
|
||||
default: return 'Unbekannt';
|
||||
}
|
||||
daemonStatusClass() {
|
||||
return {
|
||||
'status-connected': this.daemonConnectionStatus === 'connected',
|
||||
'status-connecting': this.daemonConnectionStatus === 'connecting',
|
||||
'status-disconnected': this.daemonConnectionStatus === 'disconnected',
|
||||
'status-error': this.daemonConnectionStatus === 'error'
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -60,22 +63,23 @@ header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.status-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px 8px;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
font-size: 6pt;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
margin-right: 6px;
|
||||
margin-right: 4px;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ const store = createStore({
|
||||
state: {
|
||||
isLoggedIn: localStorage.getItem('isLoggedIn') === 'true',
|
||||
connectionStatus: 'disconnected', // 'connected', 'connecting', 'disconnected', 'error'
|
||||
daemonConnectionStatus: 'disconnected', // 'connected', 'connecting', 'disconnected', 'error'
|
||||
user: JSON.parse(localStorage.getItem('user')) || null,
|
||||
language: (() => {
|
||||
// Verwende die gleiche Logik wie in main.js
|
||||
@@ -103,6 +104,9 @@ const store = createStore({
|
||||
setConnectionStatus(state, status) {
|
||||
state.connectionStatus = status;
|
||||
},
|
||||
setDaemonConnectionStatus(state, status) {
|
||||
state.daemonConnectionStatus = status;
|
||||
},
|
||||
clearSocket(state) {
|
||||
if (state.socket) {
|
||||
state.socket.disconnect();
|
||||
@@ -117,6 +121,7 @@ const store = createStore({
|
||||
state.daemonSocket.close();
|
||||
}
|
||||
state.daemonSocket = null;
|
||||
state.daemonConnectionStatus = 'disconnected';
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
@@ -180,11 +185,18 @@ const store = createStore({
|
||||
let retryCount = 0;
|
||||
const maxRetries = 10;
|
||||
const retryConnection = (reconnectFn) => {
|
||||
console.log(`Reconnect-Versuch ${retryCount + 1}/${maxRetries}`);
|
||||
if (retryCount >= maxRetries) {
|
||||
// Nach maxRetries alle 5 Sekunden weiter versuchen
|
||||
console.log('Max Retries erreicht, versuche weiter alle 5 Sekunden...');
|
||||
setTimeout(() => {
|
||||
reconnectFn();
|
||||
}, 5000);
|
||||
return;
|
||||
}
|
||||
retryCount++;
|
||||
const delay = Math.min(1000 * Math.pow(1.5, retryCount - 1), 30000); // Exponential backoff, max 30s
|
||||
const delay = 5000; // Alle 5 Sekunden versuchen
|
||||
console.log(`Warte ${delay}ms bis zum nächsten Reconnect-Versuch...`);
|
||||
setTimeout(() => {
|
||||
reconnectFn();
|
||||
}, delay);
|
||||
@@ -220,6 +232,7 @@ const store = createStore({
|
||||
const tryConnectWithProtocol = () => {
|
||||
const currentProtocol = protocols[attemptIndex];
|
||||
try {
|
||||
commit('setDaemonConnectionStatus', 'connecting');
|
||||
const daemonSocket = currentProtocol
|
||||
? new WebSocket(daemonUrl, currentProtocol)
|
||||
: new WebSocket(daemonUrl);
|
||||
@@ -229,8 +242,8 @@ const store = createStore({
|
||||
daemonSocket.onopen = () => {
|
||||
opened = true;
|
||||
retryCount = 0; // Reset retry counter on successful connection
|
||||
commit('setDaemonConnectionStatus', 'connected');
|
||||
const payload = JSON.stringify({
|
||||
user_id: state.user.id,
|
||||
event: 'setUserId',
|
||||
data: { userId: state.user.id }
|
||||
});
|
||||
@@ -238,6 +251,7 @@ const store = createStore({
|
||||
};
|
||||
|
||||
daemonSocket.onclose = (event) => {
|
||||
commit('setDaemonConnectionStatus', 'disconnected');
|
||||
// Falls Verbindungsaufbau nicht offen war und es noch einen Fallback gibt → nächsten Versuch ohne Subprotokoll
|
||||
if (!opened && attemptIndex < protocols.length - 1) {
|
||||
attemptIndex += 1;
|
||||
@@ -248,6 +262,7 @@ const store = createStore({
|
||||
};
|
||||
|
||||
daemonSocket.onerror = (error) => {
|
||||
commit('setDaemonConnectionStatus', 'error');
|
||||
// Bei Fehler vor Open: Fallback versuchen
|
||||
if (!opened && attemptIndex < protocols.length - 1) {
|
||||
attemptIndex += 1;
|
||||
@@ -289,16 +304,18 @@ const store = createStore({
|
||||
let retryCount = 0;
|
||||
const maxRetries = 15; // Increased max retries
|
||||
const retryConnection = (reconnectFn) => {
|
||||
console.log(`Daemon-Reconnect-Versuch ${retryCount + 1}/${maxRetries}`);
|
||||
if (retryCount >= maxRetries) {
|
||||
// Reset counter after a longer delay to allow for network recovery
|
||||
// Nach maxRetries alle 5 Sekunden weiter versuchen
|
||||
console.log('Daemon: Max Retries erreicht, versuche weiter alle 5 Sekunden...');
|
||||
setTimeout(() => {
|
||||
retryCount = 0;
|
||||
reconnectFn();
|
||||
}, 60000); // Wait 1 minute before resetting
|
||||
}, 5000);
|
||||
return;
|
||||
}
|
||||
retryCount++;
|
||||
const delay = Math.min(1000 * Math.pow(1.5, retryCount - 1), 30000); // Exponential backoff, max 30s
|
||||
const delay = 5000; // Alle 5 Sekunden versuchen
|
||||
console.log(`Daemon: Warte ${delay}ms bis zum nächsten Reconnect-Versuch...`);
|
||||
setTimeout(() => {
|
||||
reconnectFn();
|
||||
}, delay);
|
||||
@@ -326,6 +343,7 @@ const store = createStore({
|
||||
daemonSocket: state => state.daemonSocket,
|
||||
menuNeedsUpdate: state => state.menuNeedsUpdate,
|
||||
connectionStatus: state => state.connectionStatus,
|
||||
daemonConnectionStatus: state => state.daemonConnectionStatus,
|
||||
},
|
||||
modules: {
|
||||
dialogs,
|
||||
|
||||
@@ -287,7 +287,8 @@ export default {
|
||||
},
|
||||
|
||||
async loadActivities() {
|
||||
return;
|
||||
return; // TODO: Aktivierung der Methode geplant
|
||||
/* Temporär deaktiviert:
|
||||
this.loading.activities = true;
|
||||
try {
|
||||
const { data } = await apiClient.get(
|
||||
@@ -297,6 +298,7 @@ export default {
|
||||
} finally {
|
||||
this.loading.activities = false;
|
||||
}
|
||||
*/
|
||||
},
|
||||
|
||||
async loadAttacks() {
|
||||
|
||||
@@ -114,9 +114,6 @@
|
||||
<button @click="restartLevel" class="control-button">
|
||||
{{ $t('minigames.taxi.restartLevel') }}
|
||||
</button>
|
||||
<button @click="goBack" class="control-button">
|
||||
{{ $t('minigames.backToGames') }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -297,6 +294,7 @@ export default {
|
||||
radarLinePos: 0,
|
||||
vehicleCount: 5,
|
||||
redLightSincePenalty: 0,
|
||||
gameStartTime: null, // Zeitstempel wann das Spiel gestartet wurde
|
||||
maps: [], // Geladene Maps aus der Datenbank
|
||||
currentMap: null, // Aktuell verwendete Map
|
||||
selectedMapId: null, // ID der ausgewählten Map
|
||||
@@ -313,6 +311,7 @@ export default {
|
||||
,prevTaxiX: 250
|
||||
,prevTaxiY: 250
|
||||
,skipRedLightOneFrame: false
|
||||
,gasStations: [] // Tankstellen im Spiel
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
@@ -761,7 +760,7 @@ export default {
|
||||
},
|
||||
|
||||
generateWaitingPassenger() {
|
||||
if (!this.currentMap || !Array.isArray(this.currentMap.tileHouses)) {
|
||||
if (!this.currentMap || !Array.isArray(this.currentMap.tileHouses) || !Array.isArray(this.currentMap.tileStreets)) {
|
||||
// Versuche es in 2 Sekunden erneut
|
||||
this.passengerGenerationTimeout = setTimeout(() => {
|
||||
this.generateWaitingPassenger();
|
||||
@@ -769,24 +768,41 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// Erstelle Liste aller Tiles mit Häusern
|
||||
const tilesWithHouses = this.getTilesWithHouses();
|
||||
// 1. Sammle alle Straßen mit verfügbaren Häusern
|
||||
const streetsWithHouses = [];
|
||||
|
||||
if (tilesWithHouses.length === 0) {
|
||||
console.log('Keine Tiles mit Häusern gefunden');
|
||||
for (const street of this.currentMap.tileStreets) {
|
||||
// Finde alle Häuser auf diesem Straßen-Tile
|
||||
const housesOnThisTile = this.currentMap.tileHouses.filter(house =>
|
||||
house.x === street.x && house.y === street.y
|
||||
);
|
||||
|
||||
if (housesOnThisTile.length > 0) {
|
||||
// Prüfe ob diese Straße gültige Straßennamen hat
|
||||
const hasValidStreetName = (street.streetNameH && street.streetNameH.name && street.streetNameH.name.trim() !== '') ||
|
||||
(street.streetNameV && street.streetNameV.name && street.streetNameV.name.trim() !== '');
|
||||
|
||||
if (hasValidStreetName) {
|
||||
streetsWithHouses.push({
|
||||
street: street,
|
||||
houses: housesOnThisTile
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (streetsWithHouses.length === 0) {
|
||||
console.log('Keine Straßen mit verfügbaren Häusern gefunden');
|
||||
return;
|
||||
}
|
||||
|
||||
// Wähle zufälliges Tile mit Häusern
|
||||
const selectedTile = tilesWithHouses[Math.floor(Math.random() * tilesWithHouses.length)];
|
||||
// 2. Wähle zufällige Straße mit Häusern
|
||||
const selectedStreetData = streetsWithHouses[Math.floor(Math.random() * streetsWithHouses.length)];
|
||||
const selectedStreet = selectedStreetData.street;
|
||||
const availableHouses = selectedStreetData.houses;
|
||||
|
||||
// Finde alle Häuser auf diesem Tile
|
||||
const housesOnTile = this.currentMap.tileHouses.filter(house =>
|
||||
house.x === selectedTile.x && house.y === selectedTile.y
|
||||
);
|
||||
|
||||
// Wähle zufälliges Haus auf diesem Tile
|
||||
const selectedHouse = housesOnTile[Math.floor(Math.random() * housesOnTile.length)];
|
||||
// 3. Wähle zufälliges Haus auf dieser Straße
|
||||
const selectedHouse = availableHouses[Math.floor(Math.random() * availableHouses.length)];
|
||||
const houseIndex = this.currentMap.tileHouses.findIndex(h => h === selectedHouse);
|
||||
const houseId = `${selectedHouse.x}-${selectedHouse.y}-${houseIndex}`;
|
||||
|
||||
@@ -797,39 +813,37 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
// Finde die Straße für dieses spezifische Haus
|
||||
let streetName = "Unbekannte Straße";
|
||||
let houseNumber = 1;
|
||||
// 4. Bestimme Straßenname basierend auf der Haus-Ecke
|
||||
let streetName = null;
|
||||
const corner = selectedHouse.corner;
|
||||
|
||||
// Suche nach Straßennamen für das gewählte Haus-Tile
|
||||
const houseTile = this.currentMap.tileStreets?.find(ts => ts.x === selectedHouse.x && ts.y === selectedHouse.y);
|
||||
if (houseTile) {
|
||||
// Bestimme die Straße basierend auf der Haus-Ecke
|
||||
const corner = selectedHouse.corner;
|
||||
if (corner === 'lo' || corner === 'ro') {
|
||||
// Horizontale Straße
|
||||
if (houseTile.streetNameH && houseTile.streetNameH.name) {
|
||||
streetName = houseTile.streetNameH.name;
|
||||
}
|
||||
} else if (corner === 'lu' || corner === 'ru') {
|
||||
// Vertikale Straße
|
||||
if (houseTile.streetNameV && houseTile.streetNameV.name) {
|
||||
streetName = houseTile.streetNameV.name;
|
||||
}
|
||||
if (corner === 'lo' || corner === 'ro') {
|
||||
// Horizontale Straße
|
||||
if (selectedStreet.streetNameH && selectedStreet.streetNameH.name) {
|
||||
streetName = selectedStreet.streetNameH.name;
|
||||
}
|
||||
} else if (corner === 'lu' || corner === 'ru') {
|
||||
// Vertikale Straße
|
||||
if (selectedStreet.streetNameV && selectedStreet.streetNameV.name) {
|
||||
streetName = selectedStreet.streetNameV.name;
|
||||
}
|
||||
}
|
||||
if (streetName === 'Unbekannte Straße') {
|
||||
console.log(houseTile)
|
||||
|
||||
// Fallback sollte nie auftreten, da wir nur Straßen mit gültigen Namen auswählen
|
||||
if (!streetName || streetName.trim() === '') {
|
||||
console.error('Fehler: Kein gültiger Straßenname gefunden für Haus:', selectedHouse);
|
||||
this.generateWaitingPassenger(); // Versuche es erneut
|
||||
return;
|
||||
}
|
||||
|
||||
// Finde die Hausnummer für dieses spezifische Haus
|
||||
// 5. Finde die Hausnummer für dieses spezifische Haus
|
||||
const houseKey = `${selectedHouse.x},${selectedHouse.y},${selectedHouse.corner}`;
|
||||
houseNumber = this.houseNumbers[houseKey] || 1;
|
||||
const houseNumber = this.houseNumbers[houseKey] || 1;
|
||||
|
||||
// Generiere Namen und Geschlecht
|
||||
// 6. Generiere Namen und Geschlecht
|
||||
const nameData = this.generatePassengerName();
|
||||
|
||||
// Erstelle Passagier
|
||||
// 7. Erstelle Passagier
|
||||
const passenger = {
|
||||
id: Date.now() + Math.random(), // Eindeutige ID
|
||||
name: nameData.fullName,
|
||||
@@ -844,7 +858,7 @@ export default {
|
||||
createdAt: Date.now() // Zeitstempel der Erstellung
|
||||
};
|
||||
|
||||
// Füge Passagier zur Liste hinzu und markiere Haus als belegt
|
||||
// 8. Füge Passagier zur Liste hinzu und markiere Haus als belegt
|
||||
this.waitingPassengersList.push(passenger);
|
||||
this.occupiedHouses.add(houseId);
|
||||
},
|
||||
@@ -1277,6 +1291,10 @@ export default {
|
||||
|
||||
startGame() {
|
||||
this.gameRunning = true;
|
||||
// Setze Spielstart-Zeit
|
||||
if (!this.gameStartTime) {
|
||||
this.gameStartTime = Date.now();
|
||||
}
|
||||
// Stoppe bestehende Game-Loop falls vorhanden
|
||||
if (this.gameLoop) {
|
||||
cancelAnimationFrame(this.gameLoop);
|
||||
@@ -1669,6 +1687,12 @@ export default {
|
||||
},
|
||||
|
||||
refuel() {
|
||||
// Prüfe ob Tankstellen verfügbar sind
|
||||
if (!this.gasStations || this.gasStations.length === 0) {
|
||||
console.log('Keine Tankstellen verfügbar');
|
||||
return;
|
||||
}
|
||||
|
||||
// Finde nächste Tankstelle in der Nähe
|
||||
for (let i = 0; i < this.gasStations.length; i++) {
|
||||
const station = this.gasStations[i];
|
||||
@@ -1967,12 +1991,7 @@ export default {
|
||||
console.log('Crash-Dialog wird angezeigt:', {
|
||||
crashes: this.crashes,
|
||||
isPaused: this.isPaused,
|
||||
taxiSpeed: this.taxi.speed,
|
||||
messageTest: this.$t('message.test'),
|
||||
crashMessage: this.$t('minigames.taxi.crash.message'),
|
||||
allKeys: Object.keys(this.$t('minigames')),
|
||||
taxiKeys: Object.keys(this.$t('minigames.taxi')),
|
||||
crashKeys: Object.keys(this.$t('minigames.taxi.crash'))
|
||||
taxiSpeed: this.taxi.speed
|
||||
});
|
||||
|
||||
// Spiel bleibt pausiert bis Dialog geschlossen wird
|
||||
@@ -2007,15 +2026,74 @@ export default {
|
||||
this.vehicleCount = Math.max(0, this.vehicleCount - 1);
|
||||
},
|
||||
|
||||
handleOutOfVehicles() {
|
||||
const title = 'Hinweis';
|
||||
const msg = 'Keine Fahrzeuge mehr. Neustart.';
|
||||
getPlayTime() {
|
||||
if (!this.gameStartTime) return 0;
|
||||
return Math.floor((Date.now() - this.gameStartTime) / 1000);
|
||||
},
|
||||
|
||||
async saveHighscore() {
|
||||
try {
|
||||
const playTime = this.getPlayTime();
|
||||
const highscoreData = {
|
||||
passengersDelivered: this.passengersDelivered,
|
||||
playtime: playTime,
|
||||
points: this.score,
|
||||
mapId: this.currentMap ? this.currentMap.id : null
|
||||
};
|
||||
|
||||
console.log('Highscore-Daten:', highscoreData);
|
||||
console.log('Current Map:', this.currentMap);
|
||||
console.log('Passengers Delivered:', this.passengersDelivered);
|
||||
console.log('Playtime:', playTime);
|
||||
console.log('Points:', this.score);
|
||||
|
||||
const response = await apiClient.post('/api/taxi/highscore', highscoreData);
|
||||
|
||||
if (response.data.success) {
|
||||
console.log('Highscore erfolgreich gespeichert:', response.data.data);
|
||||
return response.data.data;
|
||||
} else {
|
||||
console.error('Fehler beim Speichern des Highscores:', response.data.message);
|
||||
return null;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Fehler beim Speichern des Highscores:', error);
|
||||
if (error.response) {
|
||||
console.error('Backend-Fehler:', error.response.data);
|
||||
console.error('Status:', error.response.status);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
async handleOutOfVehicles() {
|
||||
// Spiel stoppen
|
||||
this.isPaused = true;
|
||||
this.gameLoop = null;
|
||||
|
||||
// Highscore speichern
|
||||
const highscore = await this.saveHighscore();
|
||||
|
||||
const playTime = this.getPlayTime();
|
||||
const playTimeMinutes = Math.floor(playTime / 60);
|
||||
const playTimeSeconds = playTime % 60;
|
||||
|
||||
const title = 'Spiel beendet!';
|
||||
const msg = `Keine Fahrzeuge mehr. Spiel beendet!\n\n` +
|
||||
`Deine Leistung:\n` +
|
||||
`• Passagiere: ${this.passengersDelivered}\n` +
|
||||
`• Punkte: ${this.score}\n` +
|
||||
`• Spielzeit: ${playTimeMinutes}:${playTimeSeconds.toString().padStart(2, '0')}\n` +
|
||||
`• Map: ${this.currentMap ? this.currentMap.name : 'Unbekannt'}\n\n` +
|
||||
`Highscore wurde gespeichert!`;
|
||||
|
||||
this.$root?.$refs?.messageDialog?.open?.(msg, title, {}, () => {
|
||||
this.restartLevel();
|
||||
this.vehicleCount = 5;
|
||||
this.crashes = 0;
|
||||
this.redLightViolations = 0;
|
||||
this.redLightSincePenalty = 0;
|
||||
this.gameStartTime = null;
|
||||
});
|
||||
},
|
||||
|
||||
@@ -2854,6 +2932,9 @@ export default {
|
||||
this.taxi.angle = 0;
|
||||
this.taxi.speed = 0;
|
||||
|
||||
// Reset Spielzeit
|
||||
this.gameStartTime = null;
|
||||
|
||||
// Cleanup bestehender Timeouts
|
||||
if (this.passengerGenerationTimeout) {
|
||||
clearTimeout(this.passengerGenerationTimeout);
|
||||
@@ -2868,9 +2949,6 @@ export default {
|
||||
this.initializePassengerGeneration();
|
||||
},
|
||||
|
||||
goBack() {
|
||||
this.$router.push('/minigames');
|
||||
},
|
||||
|
||||
|
||||
initializeMinimap() {
|
||||
|
||||
Reference in New Issue
Block a user