Änderung: Hinzufügung des Taxi-Minispiels und zugehöriger Funktionen
Änderungen: - Integration des Taxi-Minispiels mit neuen Routen und Komponenten im Backend und Frontend. - Erstellung von Modellen und Datenbank-Schemas für das Taxi-Spiel, einschließlich TaxiGameState, TaxiLevelStats und TaxiMap. - Erweiterung der Navigationsstruktur und der Benutzeroberfläche, um das Taxi-Spiel und die zugehörigen Tools zu unterstützen. - Aktualisierung der Übersetzungen für das Taxi-Minispiel in Deutsch und Englisch. Diese Anpassungen erweitern die Funktionalität der Anwendung um ein neues Minispiel und verbessern die Benutzererfahrung durch neue Features und Inhalte.
This commit is contained in:
@@ -13,6 +13,8 @@ import falukantRouter from './routers/falukantRouter.js';
|
||||
import friendshipRouter from './routers/friendshipRouter.js';
|
||||
import blogRouter from './routers/blogRouter.js';
|
||||
import match3Router from './routers/match3Router.js';
|
||||
import taxiRouter from './routers/taxiRouter.js';
|
||||
import taxiMapRouter from './routers/taxiMapRouter.js';
|
||||
import cors from 'cors';
|
||||
import './jobs/sessionCleanup.js';
|
||||
|
||||
@@ -39,6 +41,8 @@ app.use('/api/navigation', navigationRouter);
|
||||
app.use('/api/settings', settingsRouter);
|
||||
app.use('/api/admin', adminRouter);
|
||||
app.use('/api/match3', match3Router);
|
||||
app.use('/api/taxi', taxiRouter);
|
||||
app.use('/api/taxi-maps', taxiMapRouter);
|
||||
app.use('/images', express.static(path.join(__dirname, '../frontend/public/images')));
|
||||
app.use('/api/contact', contactRouter);
|
||||
app.use('/api/socialnetwork', socialnetworkRouter);
|
||||
|
||||
@@ -174,6 +174,10 @@ const menuStructure = {
|
||||
match3: {
|
||||
visible: ["all"],
|
||||
path: "/minigames/match3"
|
||||
},
|
||||
taxi: {
|
||||
visible: ["all"],
|
||||
path: "/minigames/taxi"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -274,6 +278,10 @@ const menuStructure = {
|
||||
match3: {
|
||||
visible: ["mainadmin", "match3"],
|
||||
path: "/admin/minigames/match3"
|
||||
},
|
||||
taxiTools: {
|
||||
visible: ["mainadmin", "taxi"],
|
||||
path: "/admin/minigames/taxi-tools"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
144
backend/controllers/taxiController.js
Normal file
144
backend/controllers/taxiController.js
Normal file
@@ -0,0 +1,144 @@
|
||||
import TaxiService from '../services/taxiService.js';
|
||||
|
||||
function extractHashedUserId(req) {
|
||||
return req.headers?.userid;
|
||||
}
|
||||
|
||||
class TaxiController {
|
||||
constructor() {
|
||||
this.taxiService = new TaxiService();
|
||||
}
|
||||
|
||||
// Spielstand laden
|
||||
async getGameState(req, res) {
|
||||
try {
|
||||
const hashedUserId = extractHashedUserId(req);
|
||||
const gameState = await this.taxiService.getGameState(hashedUserId);
|
||||
res.json({ success: true, data: gameState });
|
||||
} catch (error) {
|
||||
console.error('Error getting taxi game state:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Laden des Spielstands' });
|
||||
}
|
||||
}
|
||||
|
||||
// Spielstand speichern
|
||||
async saveGameState(req, res) {
|
||||
try {
|
||||
const userId = extractHashedUserId(req);
|
||||
const { level, score, money, passengersDelivered, fuel } = req.body;
|
||||
|
||||
const gameState = await this.taxiService.saveGameState(userId, {
|
||||
level,
|
||||
score,
|
||||
money,
|
||||
passengersDelivered,
|
||||
fuel
|
||||
});
|
||||
|
||||
res.json({ success: true, data: gameState });
|
||||
} catch (error) {
|
||||
console.error('Error saving taxi game state:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Speichern des Spielstands' });
|
||||
}
|
||||
}
|
||||
|
||||
// Level-Statistiken abrufen
|
||||
async getLevelStats(req, res) {
|
||||
try {
|
||||
const userId = extractHashedUserId(req);
|
||||
const { level } = req.params;
|
||||
|
||||
const stats = await this.taxiService.getLevelStats(userId, parseInt(level));
|
||||
res.json({ success: true, data: stats });
|
||||
} catch (error) {
|
||||
console.error('Error getting level stats:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Laden der Level-Statistiken' });
|
||||
}
|
||||
}
|
||||
|
||||
// Bestenliste abrufen
|
||||
async getLeaderboard(req, res) {
|
||||
try {
|
||||
const { type = 'score', limit = 10 } = req.query;
|
||||
const leaderboard = await this.taxiService.getLeaderboard(type, parseInt(limit));
|
||||
res.json({ success: true, data: leaderboard });
|
||||
} catch (error) {
|
||||
console.error('Error getting leaderboard:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Laden der Bestenliste' });
|
||||
}
|
||||
}
|
||||
|
||||
// Spiel beenden und Punkte verarbeiten
|
||||
async finishGame(req, res) {
|
||||
try {
|
||||
const userId = extractHashedUserId(req);
|
||||
const { finalScore, finalMoney, passengersDelivered, level } = req.body;
|
||||
|
||||
const result = await this.taxiService.finishGame(userId, {
|
||||
finalScore,
|
||||
finalMoney,
|
||||
passengersDelivered,
|
||||
level
|
||||
});
|
||||
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
console.error('Error finishing game:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Beenden des Spiels' });
|
||||
}
|
||||
}
|
||||
|
||||
// Level freischalten
|
||||
async unlockLevel(req, res) {
|
||||
try {
|
||||
const userId = extractHashedUserId(req);
|
||||
const { level } = req.body;
|
||||
|
||||
const result = await this.taxiService.unlockLevel(userId, level);
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
console.error('Error unlocking level:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Freischalten des Levels' });
|
||||
}
|
||||
}
|
||||
|
||||
// Spieler-Statistiken abrufen
|
||||
async getPlayerStats(req, res) {
|
||||
try {
|
||||
const userId = extractHashedUserId(req);
|
||||
const stats = await this.taxiService.getPlayerStats(userId);
|
||||
res.json({ success: true, data: stats });
|
||||
} catch (error) {
|
||||
console.error('Error getting player stats:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Laden der Spieler-Statistiken' });
|
||||
}
|
||||
}
|
||||
|
||||
// Level zurücksetzen
|
||||
async resetLevel(req, res) {
|
||||
try {
|
||||
const userId = extractHashedUserId(req);
|
||||
const { level } = req.body;
|
||||
|
||||
const result = await this.taxiService.resetLevel(userId, level);
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
console.error('Error resetting level:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Zurücksetzen des Levels' });
|
||||
}
|
||||
}
|
||||
|
||||
// Alle Spielstände zurücksetzen
|
||||
async resetAllProgress(req, res) {
|
||||
try {
|
||||
const userId = extractHashedUserId(req);
|
||||
const result = await this.taxiService.resetAllProgress(userId);
|
||||
res.json({ success: true, data: result });
|
||||
} catch (error) {
|
||||
console.error('Error resetting all progress:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Zurücksetzen aller Fortschritte' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TaxiController;
|
||||
144
backend/controllers/taxiMapController.js
Normal file
144
backend/controllers/taxiMapController.js
Normal file
@@ -0,0 +1,144 @@
|
||||
import TaxiMapService from '../services/taxiMapService.js';
|
||||
|
||||
class TaxiMapController {
|
||||
constructor() {
|
||||
this.taxiMapService = new TaxiMapService();
|
||||
|
||||
// Bind all methods to the class instance
|
||||
this.getMapTypes = this.getMapTypes.bind(this);
|
||||
this.getMaps = this.getMaps.bind(this);
|
||||
this.getMapById = this.getMapById.bind(this);
|
||||
this.getMapByPosition = this.getMapByPosition.bind(this);
|
||||
this.getDefaultMap = this.getDefaultMap.bind(this);
|
||||
this.createMap = this.createMap.bind(this);
|
||||
this.updateMap = this.updateMap.bind(this);
|
||||
this.deleteMap = this.deleteMap.bind(this);
|
||||
this.setDefaultMap = this.setDefaultMap.bind(this);
|
||||
}
|
||||
|
||||
async getMapTypes(req, res) {
|
||||
try {
|
||||
const mapTypes = await this.taxiMapService.getMapTypes();
|
||||
res.json({ success: true, data: mapTypes });
|
||||
} catch (error) {
|
||||
console.error('Error getting map types:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Laden der Map-Typen' });
|
||||
}
|
||||
}
|
||||
|
||||
async getMaps(req, res) {
|
||||
try {
|
||||
const maps = await this.taxiMapService.getMaps();
|
||||
res.json({ success: true, data: maps });
|
||||
} catch (error) {
|
||||
console.error('Error getting maps:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Laden der Maps' });
|
||||
}
|
||||
}
|
||||
|
||||
async getMapById(req, res) {
|
||||
try {
|
||||
const { mapId } = req.params;
|
||||
const map = await this.taxiMapService.getMapById(mapId);
|
||||
|
||||
if (!map) {
|
||||
return res.status(404).json({ success: false, message: 'Map nicht gefunden' });
|
||||
}
|
||||
|
||||
res.json({ success: true, data: map });
|
||||
} catch (error) {
|
||||
console.error('Error getting map by ID:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Laden der Map' });
|
||||
}
|
||||
}
|
||||
|
||||
async getMapByPosition(req, res) {
|
||||
try {
|
||||
const { positionX, positionY } = req.params;
|
||||
const map = await this.taxiMapService.getMapByPosition(
|
||||
parseInt(positionX),
|
||||
parseInt(positionY)
|
||||
);
|
||||
|
||||
if (!map) {
|
||||
return res.status(404).json({ success: false, message: 'Map an Position nicht gefunden' });
|
||||
}
|
||||
|
||||
res.json({ success: true, data: map });
|
||||
} catch (error) {
|
||||
console.error('Error getting map by position:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Laden der Map' });
|
||||
}
|
||||
}
|
||||
|
||||
async getDefaultMap(req, res) {
|
||||
try {
|
||||
const map = await this.taxiMapService.getDefaultMap();
|
||||
|
||||
if (!map) {
|
||||
return res.status(404).json({ success: false, message: 'Keine Standard-Map gefunden' });
|
||||
}
|
||||
|
||||
res.json({ success: true, data: map });
|
||||
} catch (error) {
|
||||
console.error('Error getting default map:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Laden der Standard-Map' });
|
||||
}
|
||||
}
|
||||
|
||||
async createMap(req, res) {
|
||||
try {
|
||||
const mapData = req.body;
|
||||
const map = await this.taxiMapService.createMap(mapData);
|
||||
res.status(201).json({ success: true, data: map });
|
||||
} catch (error) {
|
||||
console.error('Error creating map:', error);
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Erstellen der Map' });
|
||||
}
|
||||
}
|
||||
|
||||
async updateMap(req, res) {
|
||||
try {
|
||||
const { mapId } = req.params;
|
||||
const updateData = req.body;
|
||||
const map = await this.taxiMapService.updateMap(mapId, updateData);
|
||||
res.json({ success: true, data: map });
|
||||
} catch (error) {
|
||||
console.error('Error updating map:', error);
|
||||
if (error.message === 'Map not found') {
|
||||
return res.status(404).json({ success: false, message: 'Map nicht gefunden' });
|
||||
}
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Aktualisieren der Map' });
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMap(req, res) {
|
||||
try {
|
||||
const { mapId } = req.params;
|
||||
await this.taxiMapService.deleteMap(mapId);
|
||||
res.json({ success: true, message: 'Map erfolgreich gelöscht' });
|
||||
} catch (error) {
|
||||
console.error('Error deleting map:', error);
|
||||
if (error.message === 'Map not found') {
|
||||
return res.status(404).json({ success: false, message: 'Map nicht gefunden' });
|
||||
}
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Löschen der Map' });
|
||||
}
|
||||
}
|
||||
|
||||
async setDefaultMap(req, res) {
|
||||
try {
|
||||
const { mapId } = req.params;
|
||||
await this.taxiMapService.setDefaultMap(mapId);
|
||||
res.json({ success: true, message: 'Standard-Map erfolgreich gesetzt' });
|
||||
} catch (error) {
|
||||
console.error('Error setting default map:', error);
|
||||
if (error.message === 'Map not found') {
|
||||
return res.status(404).json({ success: false, message: 'Map nicht gefunden' });
|
||||
}
|
||||
res.status(500).json({ success: false, message: 'Fehler beim Setzen der Standard-Map' });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TaxiMapController;
|
||||
@@ -102,6 +102,10 @@ import Match3Level from './match3/level.js';
|
||||
import Objective from './match3/objective.js';
|
||||
import UserProgress from './match3/userProgress.js';
|
||||
import UserLevelProgress from './match3/userLevelProgress.js';
|
||||
import TaxiGameState from './taxi/taxiGameState.js';
|
||||
import TaxiLevelStats from './taxi/taxiLevelStats.js';
|
||||
import TaxiMapType from './taxi/taxiMapType.js';
|
||||
import TaxiMap from './taxi/taxiMap.js';
|
||||
|
||||
export default function setupAssociations() {
|
||||
// RoomType 1:n Room
|
||||
@@ -786,4 +790,15 @@ export default function setupAssociations() {
|
||||
UserLevelProgress.belongsTo(UserProgress, { foreignKey: 'userProgressId', as: 'userProgress' });
|
||||
Match3Level.hasMany(UserLevelProgress, { foreignKey: 'levelId', as: 'userLevelProgress' });
|
||||
UserLevelProgress.belongsTo(Match3Level, { foreignKey: 'levelId', as: 'level' });
|
||||
|
||||
// Taxi Game associations
|
||||
TaxiGameState.belongsTo(User, { foreignKey: 'userId', as: 'user' });
|
||||
User.hasOne(TaxiGameState, { foreignKey: 'userId', as: 'taxiGameState' });
|
||||
|
||||
TaxiLevelStats.belongsTo(User, { foreignKey: 'userId', as: 'user' });
|
||||
User.hasMany(TaxiLevelStats, { foreignKey: 'userId', as: 'taxiLevelStats' });
|
||||
|
||||
// Taxi Map associations
|
||||
TaxiMap.belongsTo(TaxiMapType, { foreignKey: 'mapTypeId', as: 'mapType' });
|
||||
TaxiMapType.hasMany(TaxiMap, { foreignKey: 'mapTypeId', as: 'maps' });
|
||||
}
|
||||
|
||||
@@ -95,6 +95,9 @@ import Match3Objective from './match3/objective.js';
|
||||
import Match3UserProgress from './match3/userProgress.js';
|
||||
import Match3UserLevelProgress from './match3/userLevelProgress.js';
|
||||
|
||||
// — Taxi Minigame —
|
||||
import { TaxiGameState, TaxiLevelStats } from './taxi/index.js';
|
||||
|
||||
// — Politische Ämter (Politics) —
|
||||
import PoliticalOfficeType from './falukant/type/political_office_type.js';
|
||||
import PoliticalOfficeRequirement from './falukant/predefine/political_office_prerequisite.js';
|
||||
@@ -230,6 +233,10 @@ const models = {
|
||||
Match3Objective,
|
||||
Match3UserProgress,
|
||||
Match3UserLevelProgress,
|
||||
|
||||
// Taxi Minigame
|
||||
TaxiGameState,
|
||||
TaxiLevelStats,
|
||||
};
|
||||
|
||||
export default models;
|
||||
|
||||
6
backend/models/taxi/index.js
Normal file
6
backend/models/taxi/index.js
Normal file
@@ -0,0 +1,6 @@
|
||||
import TaxiGameState from './taxiGameState.js';
|
||||
import TaxiLevelStats from './taxiLevelStats.js';
|
||||
import TaxiMapType from './taxiMapType.js';
|
||||
import TaxiMap from './taxiMap.js';
|
||||
|
||||
export { TaxiGameState, TaxiLevelStats, TaxiMapType, TaxiMap };
|
||||
75
backend/models/taxi/taxiGameState.js
Normal file
75
backend/models/taxi/taxiGameState.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
const TaxiGameState = sequelize.define('TaxiGameState', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
currentLevel: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 1
|
||||
},
|
||||
totalScore: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
totalMoney: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
totalPassengersDelivered: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
unlockedLevels: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: false,
|
||||
defaultValue: [1]
|
||||
},
|
||||
achievements: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: false,
|
||||
defaultValue: []
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
tableName: 'taxi_game_states',
|
||||
schema: 'taxi',
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{
|
||||
unique: true,
|
||||
fields: ['user_id']
|
||||
},
|
||||
{
|
||||
fields: ['total_score']
|
||||
},
|
||||
{
|
||||
fields: ['total_money']
|
||||
},
|
||||
{
|
||||
fields: ['total_passengers_delivered']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export default TaxiGameState;
|
||||
79
backend/models/taxi/taxiLevelStats.js
Normal file
79
backend/models/taxi/taxiLevelStats.js
Normal file
@@ -0,0 +1,79 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
const TaxiLevelStats = sequelize.define('TaxiLevelStats', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
userId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
level: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false
|
||||
},
|
||||
bestScore: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
bestMoney: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
bestPassengersDelivered: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
timesPlayed: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 0
|
||||
},
|
||||
completed: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false
|
||||
},
|
||||
playTime: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: true,
|
||||
comment: 'Play time in seconds'
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
tableName: 'taxi_level_stats',
|
||||
schema: 'taxi',
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{
|
||||
unique: true,
|
||||
fields: ['user_id', 'level']
|
||||
},
|
||||
{
|
||||
fields: ['level']
|
||||
},
|
||||
{
|
||||
fields: ['best_score']
|
||||
},
|
||||
{
|
||||
fields: ['completed']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export default TaxiLevelStats;
|
||||
101
backend/models/taxi/taxiMap.js
Normal file
101
backend/models/taxi/taxiMap.js
Normal file
@@ -0,0 +1,101 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
const TaxiMap = sequelize.define('TaxiMap', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(100),
|
||||
allowNull: false
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
width: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 8,
|
||||
comment: 'Map width in tiles'
|
||||
},
|
||||
height: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 8,
|
||||
comment: 'Map height in tiles'
|
||||
},
|
||||
tileSize: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
defaultValue: 50,
|
||||
comment: 'Size of each tile in pixels'
|
||||
},
|
||||
mapTypeId: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: 'Reference to TaxiMapType'
|
||||
},
|
||||
mapData: {
|
||||
type: DataTypes.JSON,
|
||||
allowNull: false,
|
||||
comment: '2D array of map type IDs for each tile position'
|
||||
},
|
||||
positionX: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: 'X position as continuous integer (1, 2, 3, ...)'
|
||||
},
|
||||
positionY: {
|
||||
type: DataTypes.INTEGER,
|
||||
allowNull: false,
|
||||
comment: 'Y position as continuous integer (1, 2, 3, ...)'
|
||||
},
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true
|
||||
},
|
||||
isDefault: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: false,
|
||||
comment: 'Whether this is the default map for new games'
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
tableName: 'taxi_maps',
|
||||
schema: 'taxi',
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{
|
||||
fields: ['name']
|
||||
},
|
||||
{
|
||||
fields: ['is_active']
|
||||
},
|
||||
{
|
||||
fields: ['is_default']
|
||||
},
|
||||
{
|
||||
fields: ['position_x', 'position_y']
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
fields: ['position_x', 'position_y']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export default TaxiMap;
|
||||
57
backend/models/taxi/taxiMapType.js
Normal file
57
backend/models/taxi/taxiMapType.js
Normal file
@@ -0,0 +1,57 @@
|
||||
import { DataTypes } from 'sequelize';
|
||||
import { sequelize } from '../../utils/sequelize.js';
|
||||
|
||||
const TaxiMapType = sequelize.define('TaxiMapType', {
|
||||
id: {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
},
|
||||
name: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
unique: true
|
||||
},
|
||||
description: {
|
||||
type: DataTypes.TEXT,
|
||||
allowNull: true
|
||||
},
|
||||
tileType: {
|
||||
type: DataTypes.STRING(50),
|
||||
allowNull: false,
|
||||
comment: 'Type of tile: cornerBottomLeft, cornerBottomRight, cornerTopLeft, cornerTopRight, horizontal, vertical, cross, fuelHorizontal, fuelVertical, tLeft, tRight, tUp, tDown'
|
||||
},
|
||||
isActive: {
|
||||
type: DataTypes.BOOLEAN,
|
||||
allowNull: false,
|
||||
defaultValue: true
|
||||
},
|
||||
createdAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
},
|
||||
updatedAt: {
|
||||
type: DataTypes.DATE,
|
||||
allowNull: false,
|
||||
defaultValue: DataTypes.NOW
|
||||
}
|
||||
}, {
|
||||
tableName: 'taxi_map_types',
|
||||
schema: 'taxi',
|
||||
timestamps: true,
|
||||
indexes: [
|
||||
{
|
||||
unique: true,
|
||||
fields: ['name']
|
||||
},
|
||||
{
|
||||
fields: ['tile_type']
|
||||
},
|
||||
{
|
||||
fields: ['is_active']
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
export default TaxiMapType;
|
||||
26
backend/routers/taxiMapRouter.js
Normal file
26
backend/routers/taxiMapRouter.js
Normal file
@@ -0,0 +1,26 @@
|
||||
import express from 'express';
|
||||
import TaxiMapController from '../controllers/taxiMapController.js';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
|
||||
const router = express.Router();
|
||||
const taxiMapController = new TaxiMapController();
|
||||
|
||||
// All routes require authentication
|
||||
router.use(authenticate);
|
||||
|
||||
// Map types routes
|
||||
router.get('/map-types', (req, res) => taxiMapController.getMapTypes(req, res));
|
||||
|
||||
// Maps routes
|
||||
router.get('/maps', (req, res) => taxiMapController.getMaps(req, res));
|
||||
router.get('/maps/default', (req, res) => taxiMapController.getDefaultMap(req, res));
|
||||
router.get('/maps/position/:positionX/:positionY', (req, res) => taxiMapController.getMapByPosition(req, res));
|
||||
router.get('/maps/:mapId', (req, res) => taxiMapController.getMapById(req, res));
|
||||
|
||||
// Map management routes (admin only - you might want to add admin middleware)
|
||||
router.post('/maps', (req, res) => taxiMapController.createMap(req, res));
|
||||
router.put('/maps/:mapId', (req, res) => taxiMapController.updateMap(req, res));
|
||||
router.delete('/maps/:mapId', (req, res) => taxiMapController.deleteMap(req, res));
|
||||
router.post('/maps/:mapId/set-default', (req, res) => taxiMapController.setDefaultMap(req, res));
|
||||
|
||||
export default router;
|
||||
30
backend/routers/taxiRouter.js
Normal file
30
backend/routers/taxiRouter.js
Normal file
@@ -0,0 +1,30 @@
|
||||
import express from 'express';
|
||||
import TaxiController from '../controllers/taxiController.js';
|
||||
import { authenticate } from '../middleware/authMiddleware.js';
|
||||
|
||||
const router = express.Router();
|
||||
const taxiController = new TaxiController();
|
||||
|
||||
// Alle Routen erfordern Authentifizierung
|
||||
router.use(authenticate);
|
||||
|
||||
// Spielstand-Routen
|
||||
router.get('/game-state', (req, res) => taxiController.getGameState(req, res));
|
||||
router.post('/game-state', (req, res) => taxiController.saveGameState(req, res));
|
||||
|
||||
// Level-Routen
|
||||
router.get('/level/:level/stats', (req, res) => taxiController.getLevelStats(req, res));
|
||||
router.post('/level/unlock', (req, res) => taxiController.unlockLevel(req, res));
|
||||
router.post('/level/reset', (req, res) => taxiController.resetLevel(req, res));
|
||||
|
||||
// Spiel-Routen
|
||||
router.post('/finish', (req, res) => taxiController.finishGame(req, res));
|
||||
|
||||
// Statistik-Routen
|
||||
router.get('/leaderboard', (req, res) => taxiController.getLeaderboard(req, res));
|
||||
router.get('/player-stats', (req, res) => taxiController.getPlayerStats(req, res));
|
||||
|
||||
// Reset-Routen
|
||||
router.post('/reset-all', (req, res) => taxiController.resetAllProgress(req, res));
|
||||
|
||||
export default router;
|
||||
269
backend/services/taxiMapService.js
Normal file
269
backend/services/taxiMapService.js
Normal file
@@ -0,0 +1,269 @@
|
||||
import BaseService from './BaseService.js';
|
||||
import TaxiMap from '../models/taxi/taxiMap.js';
|
||||
import TaxiMapType from '../models/taxi/taxiMapType.js';
|
||||
|
||||
class TaxiMapService extends BaseService {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt alle verfügbaren Map-Typen
|
||||
*/
|
||||
async getMapTypes() {
|
||||
try {
|
||||
const mapTypes = await TaxiMapType.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['name', 'ASC']]
|
||||
});
|
||||
return mapTypes;
|
||||
} catch (error) {
|
||||
console.error('Error getting map types:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt alle verfügbaren Maps
|
||||
*/
|
||||
async getMaps() {
|
||||
try {
|
||||
const maps = await TaxiMap.findAll({
|
||||
where: { isActive: true },
|
||||
include: [{
|
||||
model: TaxiMapType,
|
||||
as: 'mapType'
|
||||
}],
|
||||
order: [['positionY', 'ASC'], ['positionX', 'ASC']]
|
||||
});
|
||||
return maps;
|
||||
} catch (error) {
|
||||
console.error('Error getting maps:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt eine spezifische Map
|
||||
*/
|
||||
async getMapById(mapId) {
|
||||
try {
|
||||
const map = await TaxiMap.findOne({
|
||||
where: {
|
||||
id: mapId,
|
||||
isActive: true
|
||||
},
|
||||
include: [{
|
||||
model: TaxiMapType,
|
||||
as: 'mapType'
|
||||
}]
|
||||
});
|
||||
return map;
|
||||
} catch (error) {
|
||||
console.error('Error getting map by ID:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt eine Map nach Position
|
||||
*/
|
||||
async getMapByPosition(positionX, positionY) {
|
||||
try {
|
||||
const map = await TaxiMap.findOne({
|
||||
where: {
|
||||
positionX: positionX,
|
||||
positionY: positionY,
|
||||
isActive: true
|
||||
},
|
||||
include: [{
|
||||
model: TaxiMapType,
|
||||
as: 'mapType'
|
||||
}]
|
||||
});
|
||||
return map;
|
||||
} catch (error) {
|
||||
console.error('Error getting map by position:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holt die Standard-Map
|
||||
*/
|
||||
async getDefaultMap() {
|
||||
try {
|
||||
const map = await TaxiMap.findOne({
|
||||
where: {
|
||||
isDefault: true,
|
||||
isActive: true
|
||||
},
|
||||
include: [{
|
||||
model: TaxiMapType,
|
||||
as: 'mapType'
|
||||
}]
|
||||
});
|
||||
return map;
|
||||
} catch (error) {
|
||||
console.error('Error getting default map:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine neue Map
|
||||
*/
|
||||
async createMap(mapData) {
|
||||
try {
|
||||
const map = await TaxiMap.create(mapData);
|
||||
return map;
|
||||
} catch (error) {
|
||||
console.error('Error creating map:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Aktualisiert eine Map
|
||||
*/
|
||||
async updateMap(mapId, updateData) {
|
||||
try {
|
||||
const [updatedRowsCount] = await TaxiMap.update(updateData, {
|
||||
where: { id: mapId }
|
||||
});
|
||||
|
||||
if (updatedRowsCount === 0) {
|
||||
throw new Error('Map not found');
|
||||
}
|
||||
|
||||
return await this.getMapById(mapId);
|
||||
} catch (error) {
|
||||
console.error('Error updating map:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht eine Map (soft delete)
|
||||
*/
|
||||
async deleteMap(mapId) {
|
||||
try {
|
||||
const [updatedRowsCount] = await TaxiMap.update(
|
||||
{ isActive: false },
|
||||
{ where: { id: mapId } }
|
||||
);
|
||||
|
||||
if (updatedRowsCount === 0) {
|
||||
throw new Error('Map not found');
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error deleting map:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt eine Map als Standard
|
||||
*/
|
||||
async setDefaultMap(mapId) {
|
||||
try {
|
||||
// Entferne Standard-Status von allen anderen Maps
|
||||
await TaxiMap.update(
|
||||
{ isDefault: false },
|
||||
{ where: { isDefault: true } }
|
||||
);
|
||||
|
||||
// Setze neue Standard-Map
|
||||
const [updatedRowsCount] = await TaxiMap.update(
|
||||
{ isDefault: true },
|
||||
{ where: { id: mapId } }
|
||||
);
|
||||
|
||||
if (updatedRowsCount === 0) {
|
||||
throw new Error('Map not found');
|
||||
}
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error setting default map:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialisiert Standard-Map-Typen
|
||||
*/
|
||||
async initializeMapTypes() {
|
||||
try {
|
||||
const mapTypes = [
|
||||
{ name: 'Corner Bottom Left', tileType: 'cornerBottomLeft', description: 'Bottom left corner tile' },
|
||||
{ name: 'Corner Bottom Right', tileType: 'cornerBottomRight', description: 'Bottom right corner tile' },
|
||||
{ name: 'Corner Top Left', tileType: 'cornerTopLeft', description: 'Top left corner tile' },
|
||||
{ name: 'Corner Top Right', tileType: 'cornerTopRight', description: 'Top right corner tile' },
|
||||
{ name: 'Horizontal', tileType: 'horizontal', description: 'Horizontal road tile' },
|
||||
{ name: 'Vertical', tileType: 'vertical', description: 'Vertical road tile' },
|
||||
{ name: 'Cross', tileType: 'cross', description: 'Cross intersection tile' },
|
||||
{ name: 'Fuel Horizontal', tileType: 'fuelHorizontal', description: 'Horizontal road with fuel station' },
|
||||
{ name: 'Fuel Vertical', tileType: 'fuelVertical', description: 'Vertical road with fuel station' },
|
||||
{ name: 'T-Left', tileType: 'tLeft', description: 'T-junction facing left' },
|
||||
{ name: 'T-Right', tileType: 'tRight', description: 'T-junction facing right' },
|
||||
{ name: 'T-Up', tileType: 'tUp', description: 'T-junction facing up' },
|
||||
{ name: 'T-Down', tileType: 'tDown', description: 'T-junction facing down' }
|
||||
];
|
||||
|
||||
for (const mapType of mapTypes) {
|
||||
await TaxiMapType.findOrCreate({
|
||||
where: { name: mapType.name },
|
||||
defaults: mapType
|
||||
});
|
||||
}
|
||||
|
||||
console.log('Taxi map types initialized');
|
||||
} catch (error) {
|
||||
console.error('Error initializing map types:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Erstellt eine Standard-Map
|
||||
*/
|
||||
async createDefaultMap() {
|
||||
try {
|
||||
// 8x8 Standard-Map mit verschiedenen Tile-Typen
|
||||
const mapData = [
|
||||
['cornerTopLeft', 'horizontal', 'horizontal', 'horizontal', 'horizontal', 'horizontal', 'horizontal', 'cornerTopRight'],
|
||||
['vertical', 'cross', 'cross', 'cross', 'cross', 'cross', 'cross', 'vertical'],
|
||||
['vertical', 'cross', 'cross', 'cross', 'cross', 'cross', 'cross', 'vertical'],
|
||||
['vertical', 'cross', 'cross', 'cross', 'cross', 'cross', 'cross', 'vertical'],
|
||||
['vertical', 'cross', 'cross', 'cross', 'cross', 'cross', 'cross', 'vertical'],
|
||||
['vertical', 'cross', 'cross', 'cross', 'cross', 'cross', 'cross', 'vertical'],
|
||||
['vertical', 'cross', 'cross', 'cross', 'cross', 'cross', 'cross', 'vertical'],
|
||||
['cornerBottomLeft', 'horizontal', 'horizontal', 'horizontal', 'horizontal', 'horizontal', 'horizontal', 'cornerBottomRight']
|
||||
];
|
||||
|
||||
const map = await TaxiMap.create({
|
||||
name: 'Standard City Map',
|
||||
description: 'A standard 8x8 city map with roads and intersections',
|
||||
width: 8,
|
||||
height: 8,
|
||||
tileSize: 50,
|
||||
mapTypeId: 1, // Assuming first map type
|
||||
mapData: mapData,
|
||||
positionX: 1,
|
||||
positionY: 1,
|
||||
isDefault: true,
|
||||
isActive: true
|
||||
});
|
||||
|
||||
return map;
|
||||
} catch (error) {
|
||||
console.error('Error creating default map:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TaxiMapService;
|
||||
407
backend/services/taxiService.js
Normal file
407
backend/services/taxiService.js
Normal file
@@ -0,0 +1,407 @@
|
||||
import BaseService from './BaseService.js';
|
||||
import TaxiGameState from '../models/taxi/taxiGameState.js';
|
||||
import TaxiLevelStats from '../models/taxi/taxiLevelStats.js';
|
||||
import User from '../models/community/user.js';
|
||||
|
||||
class TaxiService extends BaseService {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
// Hilfsmethode: Konvertiere hashedId zu userId
|
||||
async getUserIdFromHashedId(hashedUserId) {
|
||||
const user = await User.findOne({ where: { hashedId: hashedUserId } });
|
||||
if (!user) {
|
||||
throw new Error('User not found');
|
||||
}
|
||||
return user.id;
|
||||
}
|
||||
|
||||
// Spielstand abrufen
|
||||
async getGameState(hashedUserId) {
|
||||
try {
|
||||
const userId = await this.getUserIdFromHashedId(hashedUserId);
|
||||
|
||||
let gameState = await TaxiGameState.findOne({
|
||||
where: { userId }
|
||||
});
|
||||
|
||||
if (!gameState) {
|
||||
// Erstelle neuen Spielstand
|
||||
gameState = await TaxiGameState.create({
|
||||
userId,
|
||||
currentLevel: 1,
|
||||
totalScore: 0,
|
||||
totalMoney: 0,
|
||||
totalPassengersDelivered: 0,
|
||||
unlockedLevels: [1],
|
||||
achievements: []
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
currentLevel: gameState.currentLevel,
|
||||
totalScore: gameState.totalScore,
|
||||
totalMoney: gameState.totalMoney,
|
||||
totalPassengersDelivered: gameState.totalPassengersDelivered,
|
||||
unlockedLevels: gameState.unlockedLevels,
|
||||
achievements: gameState.achievements
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting taxi game state:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Spielstand speichern
|
||||
async saveGameState(hashedUserId, gameData) {
|
||||
try {
|
||||
const userId = await this.getUserIdFromHashedId(hashedUserId);
|
||||
const { level, score, money, passengersDelivered, fuel } = gameData;
|
||||
|
||||
const [gameState, created] = await TaxiGameState.findOrCreate({
|
||||
where: { userId },
|
||||
defaults: {
|
||||
userId,
|
||||
currentLevel: level || 1,
|
||||
totalScore: score || 0,
|
||||
totalMoney: money || 0,
|
||||
totalPassengersDelivered: passengersDelivered || 0,
|
||||
unlockedLevels: [1],
|
||||
achievements: []
|
||||
}
|
||||
});
|
||||
|
||||
if (!created) {
|
||||
// Aktualisiere bestehenden Spielstand
|
||||
await gameState.update({
|
||||
currentLevel: Math.max(gameState.currentLevel, level || 1),
|
||||
totalScore: Math.max(gameState.totalScore, score || 0),
|
||||
totalMoney: Math.max(gameState.totalMoney, money || 0),
|
||||
totalPassengersDelivered: Math.max(gameState.totalPassengersDelivered, passengersDelivered || 0)
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
currentLevel: gameState.currentLevel,
|
||||
totalScore: gameState.totalScore,
|
||||
totalMoney: gameState.totalMoney,
|
||||
totalPassengersDelivered: gameState.totalPassengersDelivered,
|
||||
unlockedLevels: gameState.unlockedLevels,
|
||||
achievements: gameState.achievements
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error saving taxi game state:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Level-Statistiken abrufen
|
||||
async getLevelStats(hashedUserId, level) {
|
||||
try {
|
||||
const userId = await this.getUserIdFromHashedId(hashedUserId);
|
||||
const stats = await TaxiLevelStats.findOne({
|
||||
where: { userId, level }
|
||||
});
|
||||
|
||||
if (!stats) {
|
||||
return {
|
||||
level,
|
||||
bestScore: 0,
|
||||
bestMoney: 0,
|
||||
bestPassengersDelivered: 0,
|
||||
timesPlayed: 0,
|
||||
completed: false
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
level: stats.level,
|
||||
bestScore: stats.bestScore,
|
||||
bestMoney: stats.bestMoney,
|
||||
bestPassengersDelivered: stats.bestPassengersDelivered,
|
||||
timesPlayed: stats.timesPlayed,
|
||||
completed: stats.completed
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting level stats:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Bestenliste abrufen
|
||||
async getLeaderboard(type = 'score', limit = 10) {
|
||||
try {
|
||||
let orderBy;
|
||||
switch (type) {
|
||||
case 'money':
|
||||
orderBy = [['totalMoney', 'DESC']];
|
||||
break;
|
||||
case 'passengers':
|
||||
orderBy = [['totalPassengersDelivered', 'DESC']];
|
||||
break;
|
||||
case 'level':
|
||||
orderBy = [['currentLevel', 'DESC']];
|
||||
break;
|
||||
default:
|
||||
orderBy = [['totalScore', 'DESC']];
|
||||
}
|
||||
|
||||
const leaderboard = await TaxiGameState.findAll({
|
||||
order: orderBy,
|
||||
limit: parseInt(limit),
|
||||
include: [{
|
||||
model: User,
|
||||
attributes: ['username', 'id']
|
||||
}]
|
||||
});
|
||||
|
||||
return leaderboard.map((entry, index) => ({
|
||||
rank: index + 1,
|
||||
username: entry.User.username,
|
||||
userId: entry.User.id,
|
||||
score: entry.totalScore,
|
||||
money: entry.totalMoney,
|
||||
passengers: entry.totalPassengersDelivered,
|
||||
level: entry.currentLevel
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Error getting leaderboard:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Spiel beenden und Punkte verarbeiten
|
||||
async finishGame(hashedUserId, gameData) {
|
||||
try {
|
||||
const userId = await this.getUserIdFromHashedId(hashedUserId);
|
||||
const { finalScore, finalMoney, passengersDelivered, level } = gameData;
|
||||
|
||||
// Aktualisiere Spielstand
|
||||
const gameState = await this.saveGameState(userId, {
|
||||
level,
|
||||
score: finalScore,
|
||||
money: finalMoney,
|
||||
passengersDelivered
|
||||
});
|
||||
|
||||
// Aktualisiere Level-Statistiken
|
||||
await this.updateLevelStats(hashedUserId, level, {
|
||||
score: finalScore,
|
||||
money: finalMoney,
|
||||
passengersDelivered
|
||||
});
|
||||
|
||||
// Prüfe auf neue freigeschaltete Level
|
||||
const newUnlockedLevels = await this.checkUnlockedLevels(hashedUserId, level);
|
||||
|
||||
// Prüfe auf neue Erfolge
|
||||
const newAchievements = await this.checkAchievements(hashedUserId, gameState);
|
||||
|
||||
return {
|
||||
gameState,
|
||||
newUnlockedLevels,
|
||||
newAchievements
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error finishing game:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Level freischalten
|
||||
async unlockLevel(hashedUserId, level) {
|
||||
try {
|
||||
const userId = await this.getUserIdFromHashedId(hashedUserId);
|
||||
const gameState = await TaxiGameState.findOne({
|
||||
where: { userId }
|
||||
});
|
||||
|
||||
if (!gameState) {
|
||||
throw new Error('Spielstand nicht gefunden');
|
||||
}
|
||||
|
||||
const unlockedLevels = [...gameState.unlockedLevels];
|
||||
if (!unlockedLevels.includes(level)) {
|
||||
unlockedLevels.push(level);
|
||||
unlockedLevels.sort((a, b) => a - b);
|
||||
|
||||
await gameState.update({
|
||||
unlockedLevels
|
||||
});
|
||||
}
|
||||
|
||||
return { unlockedLevels };
|
||||
} catch (error) {
|
||||
console.error('Error unlocking level:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Spieler-Statistiken abrufen
|
||||
async getPlayerStats(hashedUserId) {
|
||||
try {
|
||||
const userId = await this.getUserIdFromHashedId(hashedUserId);
|
||||
const gameState = await this.getGameState(hashedUserId);
|
||||
const levelStats = await TaxiLevelStats.findAll({
|
||||
where: { userId },
|
||||
order: [['level', 'ASC']]
|
||||
});
|
||||
|
||||
const totalLevelsPlayed = levelStats.length;
|
||||
const completedLevels = levelStats.filter(stat => stat.completed).length;
|
||||
const totalPlayTime = levelStats.reduce((sum, stat) => sum + (stat.playTime || 0), 0);
|
||||
|
||||
return {
|
||||
...gameState,
|
||||
totalLevelsPlayed,
|
||||
completedLevels,
|
||||
totalPlayTime,
|
||||
levelStats: levelStats.map(stat => ({
|
||||
level: stat.level,
|
||||
bestScore: stat.bestScore,
|
||||
bestMoney: stat.bestMoney,
|
||||
bestPassengersDelivered: stat.bestPassengersDelivered,
|
||||
timesPlayed: stat.timesPlayed,
|
||||
completed: stat.completed
|
||||
}))
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error getting player stats:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Level zurücksetzen
|
||||
async resetLevel(hashedUserId, level) {
|
||||
try {
|
||||
const userId = await this.getUserIdFromHashedId(hashedUserId);
|
||||
await TaxiLevelStats.destroy({
|
||||
where: { userId, level }
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error resetting level:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Alle Spielstände zurücksetzen
|
||||
async resetAllProgress(hashedUserId) {
|
||||
try {
|
||||
const userId = await this.getUserIdFromHashedId(hashedUserId);
|
||||
await TaxiGameState.destroy({
|
||||
where: { userId }
|
||||
});
|
||||
|
||||
await TaxiLevelStats.destroy({
|
||||
where: { userId }
|
||||
});
|
||||
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('Error resetting all progress:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Hilfsmethoden
|
||||
async updateLevelStats(hashedUserId, level, stats) {
|
||||
try {
|
||||
const userId = await this.getUserIdFromHashedId(hashedUserId);
|
||||
const { score, money, passengersDelivered } = stats;
|
||||
|
||||
const [levelStat, created] = await TaxiLevelStats.findOrCreate({
|
||||
where: { userId, level },
|
||||
defaults: {
|
||||
userId,
|
||||
level,
|
||||
bestScore: score,
|
||||
bestMoney: money,
|
||||
bestPassengersDelivered: passengersDelivered,
|
||||
timesPlayed: 1,
|
||||
completed: true
|
||||
}
|
||||
});
|
||||
|
||||
if (!created) {
|
||||
const updates = {
|
||||
timesPlayed: levelStat.timesPlayed + 1,
|
||||
completed: true
|
||||
};
|
||||
|
||||
if (score > levelStat.bestScore) updates.bestScore = score;
|
||||
if (money > levelStat.bestMoney) updates.bestMoney = money;
|
||||
if (passengersDelivered > levelStat.bestPassengersDelivered) {
|
||||
updates.bestPassengersDelivered = passengersDelivered;
|
||||
}
|
||||
|
||||
await levelStat.update(updates);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error updating level stats:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async checkUnlockedLevels(hashedUserId, currentLevel) {
|
||||
try {
|
||||
const userId = await this.getUserIdFromHashedId(hashedUserId);
|
||||
const gameState = await TaxiGameState.findOne({
|
||||
where: { userId }
|
||||
});
|
||||
|
||||
if (!gameState) return [];
|
||||
|
||||
const newUnlockedLevels = [];
|
||||
const nextLevel = currentLevel + 1;
|
||||
|
||||
if (!gameState.unlockedLevels.includes(nextLevel)) {
|
||||
newUnlockedLevels.push(nextLevel);
|
||||
await this.unlockLevel(hashedUserId, nextLevel);
|
||||
}
|
||||
|
||||
return newUnlockedLevels;
|
||||
} catch (error) {
|
||||
console.error('Error checking unlocked levels:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async checkAchievements(hashedUserId, gameState) {
|
||||
try {
|
||||
const userId = await this.getUserIdFromHashedId(hashedUserId);
|
||||
const achievements = [];
|
||||
const currentAchievements = gameState.achievements || [];
|
||||
|
||||
// Beispiel-Erfolge
|
||||
if (gameState.totalScore >= 1000 && !currentAchievements.includes('score_1000')) {
|
||||
achievements.push('score_1000');
|
||||
}
|
||||
|
||||
if (gameState.totalPassengersDelivered >= 50 && !currentAchievements.includes('passengers_50')) {
|
||||
achievements.push('passengers_50');
|
||||
}
|
||||
|
||||
if (gameState.currentLevel >= 10 && !currentAchievements.includes('level_10')) {
|
||||
achievements.push('level_10');
|
||||
}
|
||||
|
||||
if (achievements.length > 0) {
|
||||
const newAchievements = [...currentAchievements, ...achievements];
|
||||
await TaxiGameState.update(
|
||||
{ achievements: newAchievements },
|
||||
{ where: { userId } }
|
||||
);
|
||||
}
|
||||
|
||||
return achievements;
|
||||
} catch (error) {
|
||||
console.error('Error checking achievements:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default TaxiService;
|
||||
29
backend/utils/initializeTaxi.js
Normal file
29
backend/utils/initializeTaxi.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// initializeTaxi.js
|
||||
|
||||
import TaxiMapService from '../services/taxiMapService.js';
|
||||
|
||||
const initializeTaxi = async () => {
|
||||
try {
|
||||
console.log('Initializing Taxi game data...');
|
||||
|
||||
const taxiMapService = new TaxiMapService();
|
||||
|
||||
// Initialisiere Map-Typen
|
||||
console.log('Initializing taxi map types...');
|
||||
await taxiMapService.initializeMapTypes();
|
||||
|
||||
// Prüfe ob bereits eine Standard-Map existiert
|
||||
const existingDefaultMap = await taxiMapService.getDefaultMap();
|
||||
if (!existingDefaultMap) {
|
||||
console.log('Creating default taxi map...');
|
||||
await taxiMapService.createDefaultMap();
|
||||
}
|
||||
|
||||
console.log('Taxi game initialization complete.');
|
||||
} catch (error) {
|
||||
console.error('Error initializing Taxi game:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export default initializeTaxi;
|
||||
@@ -40,6 +40,7 @@ const createSchemas = async () => {
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_log');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS chat');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS match3');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS taxi');
|
||||
};
|
||||
|
||||
const initializeDatabase = async () => {
|
||||
|
||||
@@ -13,6 +13,7 @@ import initializeForum from './initializeForum.js';
|
||||
import initializeChat from './initializeChat.js';
|
||||
import initializeMatch3Data from './initializeMatch3.js';
|
||||
import updateExistingMatch3Levels from './updateExistingMatch3Levels.js';
|
||||
import initializeTaxi from './initializeTaxi.js';
|
||||
|
||||
// Normale Synchronisation (nur bei STAGE=dev Schema-Updates)
|
||||
const syncDatabase = async () => {
|
||||
@@ -70,6 +71,9 @@ const syncDatabase = async () => {
|
||||
console.log("Updating existing Match3 levels...");
|
||||
await updateExistingMatch3Levels();
|
||||
|
||||
console.log("Initializing Taxi...");
|
||||
await initializeTaxi();
|
||||
|
||||
console.log('Database synchronization complete.');
|
||||
} catch (error) {
|
||||
console.error('Unable to synchronize the database:', error);
|
||||
@@ -85,19 +89,7 @@ const syncDatabaseForDeployment = async () => {
|
||||
console.log('✅ Deployment-Modus: Schema-Updates sind immer aktiviert');
|
||||
|
||||
console.log("Initializing database schemas...");
|
||||
// Nur Schemas erstellen, keine Model-Synchronisation
|
||||
const { sequelize } = await import('./sequelize.js');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS community');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS logs');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS type');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS service');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS forum');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_data');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_type');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_predefine');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS falukant_log');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS chat');
|
||||
await sequelize.query('CREATE SCHEMA IF NOT EXISTS match3');
|
||||
await initializeDatabase();
|
||||
|
||||
console.log("Synchronizing models with schema updates...");
|
||||
await syncModelsAlways(models);
|
||||
@@ -137,6 +129,9 @@ const syncDatabaseForDeployment = async () => {
|
||||
console.log("Updating existing Match3 levels...");
|
||||
await updateExistingMatch3Levels();
|
||||
|
||||
console.log("Initializing Taxi...");
|
||||
await initializeTaxi();
|
||||
|
||||
console.log('Database synchronization for deployment complete.');
|
||||
} catch (error) {
|
||||
console.error('Unable to synchronize the database for deployment:', error);
|
||||
|
||||
Reference in New Issue
Block a user