Add NPC creation and titles retrieval functionality in Admin module
- Implemented createNPCs method in AdminController to handle NPC creation with specified parameters including region, age, title, and count. - Added getTitlesOfNobility method in AdminController to retrieve available titles for users. - Updated adminRouter to include new routes for creating NPCs and fetching titles. - Enhanced navigationController and frontend localization files to support new NPC creation feature. - Introduced corresponding UI components and routes for NPC management in the admin interface.
This commit is contained in:
@@ -43,6 +43,8 @@ class AdminController {
|
|||||||
this.getRegionDistances = this.getRegionDistances.bind(this);
|
this.getRegionDistances = this.getRegionDistances.bind(this);
|
||||||
this.upsertRegionDistance = this.upsertRegionDistance.bind(this);
|
this.upsertRegionDistance = this.upsertRegionDistance.bind(this);
|
||||||
this.deleteRegionDistance = this.deleteRegionDistance.bind(this);
|
this.deleteRegionDistance = this.deleteRegionDistance.bind(this);
|
||||||
|
this.createNPCs = this.createNPCs.bind(this);
|
||||||
|
this.getTitlesOfNobility = this.getTitlesOfNobility.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOpenInterests(req, res) {
|
async getOpenInterests(req, res) {
|
||||||
@@ -383,6 +385,38 @@ class AdminController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createNPCs(req, res) {
|
||||||
|
try {
|
||||||
|
const { userid: userId } = req.headers;
|
||||||
|
const { regionIds, minAge, maxAge, minTitleId, maxTitleId, count } = req.body;
|
||||||
|
const result = await AdminService.createNPCs(userId, {
|
||||||
|
regionIds: regionIds && regionIds.length > 0 ? regionIds : null,
|
||||||
|
minAge: parseInt(minAge) || 0,
|
||||||
|
maxAge: parseInt(maxAge) || 100,
|
||||||
|
minTitleId: parseInt(minTitleId) || 1,
|
||||||
|
maxTitleId: parseInt(maxTitleId) || 19,
|
||||||
|
count: parseInt(count) || 1
|
||||||
|
});
|
||||||
|
res.status(200).json(result);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
const status = error.message === 'noaccess' ? 403 : 500;
|
||||||
|
res.status(status).json({ error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTitlesOfNobility(req, res) {
|
||||||
|
try {
|
||||||
|
const { userid: userId } = req.headers;
|
||||||
|
const titles = await AdminService.getTitlesOfNobility(userId);
|
||||||
|
res.status(200).json(titles);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
const status = error.message === 'noaccess' ? 403 : 500;
|
||||||
|
res.status(status).json({ error: error.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async getRoomTypes(req, res) {
|
async getRoomTypes(req, res) {
|
||||||
try {
|
try {
|
||||||
const userId = req.headers.userid;
|
const userId = req.headers.userid;
|
||||||
|
|||||||
@@ -280,6 +280,10 @@ const menuStructure = {
|
|||||||
visible: ["mainadmin", "falukant"],
|
visible: ["mainadmin", "falukant"],
|
||||||
path: "/admin/falukant/map"
|
path: "/admin/falukant/map"
|
||||||
},
|
},
|
||||||
|
createNPC: {
|
||||||
|
visible: ["mainadmin", "falukant"],
|
||||||
|
path: "/admin/falukant/create-npc"
|
||||||
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
minigames: {
|
minigames: {
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ router.put('/falukant/regions/:id/map', authenticate, adminController.updateFalu
|
|||||||
router.get('/falukant/region-distances', authenticate, adminController.getRegionDistances);
|
router.get('/falukant/region-distances', authenticate, adminController.getRegionDistances);
|
||||||
router.post('/falukant/region-distances', authenticate, adminController.upsertRegionDistance);
|
router.post('/falukant/region-distances', authenticate, adminController.upsertRegionDistance);
|
||||||
router.delete('/falukant/region-distances/:id', authenticate, adminController.deleteRegionDistance);
|
router.delete('/falukant/region-distances/:id', authenticate, adminController.deleteRegionDistance);
|
||||||
|
router.post('/falukant/npcs/create', authenticate, adminController.createNPCs);
|
||||||
|
router.get('/falukant/titles', authenticate, adminController.getTitlesOfNobility);
|
||||||
|
|
||||||
// --- Minigames Admin ---
|
// --- Minigames Admin ---
|
||||||
router.get('/minigames/match3/campaigns', authenticate, adminController.getMatch3Campaigns);
|
router.get('/minigames/match3/campaigns', authenticate, adminController.getMatch3Campaigns);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import UserParamType from "../models/type/user_param.js";
|
|||||||
import ContactMessage from "../models/service/contactmessage.js";
|
import ContactMessage from "../models/service/contactmessage.js";
|
||||||
import ContactService from "./ContactService.js";
|
import ContactService from "./ContactService.js";
|
||||||
import { sendAnswerEmail } from './emailService.js';
|
import { sendAnswerEmail } from './emailService.js';
|
||||||
import { Op } from 'sequelize';
|
import { Op, Sequelize } from 'sequelize';
|
||||||
import FalukantUser from "../models/falukant/data/user.js";
|
import FalukantUser from "../models/falukant/data/user.js";
|
||||||
import FalukantCharacter from "../models/falukant/data/character.js";
|
import FalukantCharacter from "../models/falukant/data/character.js";
|
||||||
import FalukantPredefineFirstname from "../models/falukant/predefine/firstname.js";
|
import FalukantPredefineFirstname from "../models/falukant/predefine/firstname.js";
|
||||||
@@ -24,6 +24,8 @@ import BranchType from "../models/falukant/type/branch.js";
|
|||||||
import RegionDistance from "../models/falukant/data/region_distance.js";
|
import RegionDistance from "../models/falukant/data/region_distance.js";
|
||||||
import Room from '../models/chat/room.js';
|
import Room from '../models/chat/room.js';
|
||||||
import UserParam from '../models/community/user_param.js';
|
import UserParam from '../models/community/user_param.js';
|
||||||
|
import TitleOfNobility from "../models/falukant/type/title_of_nobility.js";
|
||||||
|
import { sequelize } from '../utils/sequelize.js';
|
||||||
|
|
||||||
class AdminService {
|
class AdminService {
|
||||||
async hasUserAccess(userId, section) {
|
async hasUserAccess(userId, section) {
|
||||||
@@ -321,6 +323,17 @@ class AdminService {
|
|||||||
return regions;
|
return regions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getTitlesOfNobility(userId) {
|
||||||
|
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||||
|
throw new Error('noaccess');
|
||||||
|
}
|
||||||
|
const titles = await TitleOfNobility.findAll({
|
||||||
|
order: [['id', 'ASC']],
|
||||||
|
attributes: ['id', 'labelTr', 'level']
|
||||||
|
});
|
||||||
|
return titles;
|
||||||
|
}
|
||||||
|
|
||||||
async updateFalukantRegionMap(userId, regionId, map) {
|
async updateFalukantRegionMap(userId, regionId, map) {
|
||||||
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||||
throw new Error('noaccess');
|
throw new Error('noaccess');
|
||||||
@@ -1085,6 +1098,136 @@ class AdminService {
|
|||||||
ageGroups
|
ageGroups
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async createNPCs(userId, options) {
|
||||||
|
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||||
|
throw new Error('noaccess');
|
||||||
|
}
|
||||||
|
|
||||||
|
const {
|
||||||
|
regionIds, // Array von Region-IDs oder null für alle Städte
|
||||||
|
minAge, // Mindestalter in Jahren
|
||||||
|
maxAge, // Maximalalter in Jahren
|
||||||
|
minTitleId, // Minimale Title-ID
|
||||||
|
maxTitleId, // Maximale Title-ID
|
||||||
|
count // Anzahl der zu erstellenden NPCs
|
||||||
|
} = options;
|
||||||
|
|
||||||
|
// Hole alle Städte, wenn keine spezifischen Regionen angegeben
|
||||||
|
let targetRegions = [];
|
||||||
|
if (regionIds && regionIds.length > 0) {
|
||||||
|
targetRegions = await RegionData.findAll({
|
||||||
|
where: {
|
||||||
|
id: { [Op.in]: regionIds }
|
||||||
|
},
|
||||||
|
include: [{
|
||||||
|
model: RegionType,
|
||||||
|
as: 'regionType',
|
||||||
|
where: { labelTr: 'city' }
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
targetRegions = await RegionData.findAll({
|
||||||
|
include: [{
|
||||||
|
model: RegionType,
|
||||||
|
as: 'regionType',
|
||||||
|
where: { labelTr: 'city' }
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetRegions.length === 0) {
|
||||||
|
throw new Error('No cities found');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hole alle Titles im Bereich
|
||||||
|
const titles = await TitleOfNobility.findAll({
|
||||||
|
where: {
|
||||||
|
id: {
|
||||||
|
[Op.between]: [minTitleId, maxTitleId]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
order: [['id', 'ASC']]
|
||||||
|
});
|
||||||
|
|
||||||
|
if (titles.length === 0) {
|
||||||
|
throw new Error('No titles found in specified range');
|
||||||
|
}
|
||||||
|
|
||||||
|
const genders = ['male', 'female'];
|
||||||
|
const createdNPCs = [];
|
||||||
|
|
||||||
|
// Erstelle NPCs in einer Transaktion
|
||||||
|
await sequelize.transaction(async (t) => {
|
||||||
|
for (let i = 0; i < count; i++) {
|
||||||
|
// Zufällige Region
|
||||||
|
const region = targetRegions[Math.floor(Math.random() * targetRegions.length)];
|
||||||
|
|
||||||
|
// Zufälliges Geschlecht
|
||||||
|
const gender = genders[Math.floor(Math.random() * genders.length)];
|
||||||
|
|
||||||
|
// Zufälliger Vorname
|
||||||
|
const firstName = await FalukantPredefineFirstname.findAll({
|
||||||
|
where: { gender },
|
||||||
|
order: sequelize.fn('RANDOM'),
|
||||||
|
limit: 1,
|
||||||
|
transaction: t
|
||||||
|
});
|
||||||
|
if (firstName.length === 0) {
|
||||||
|
throw new Error(`No first names found for gender: ${gender}`);
|
||||||
|
}
|
||||||
|
const fnObj = firstName[0];
|
||||||
|
|
||||||
|
// Zufälliger Nachname
|
||||||
|
const lastName = await FalukantPredefineLastname.findAll({
|
||||||
|
order: sequelize.fn('RANDOM'),
|
||||||
|
limit: 1,
|
||||||
|
transaction: t
|
||||||
|
});
|
||||||
|
if (lastName.length === 0) {
|
||||||
|
throw new Error('No last names found');
|
||||||
|
}
|
||||||
|
const lnObj = lastName[0];
|
||||||
|
|
||||||
|
// Zufälliges Alter (in Jahren, wird in Tage umgerechnet)
|
||||||
|
const randomAge = Math.floor(Math.random() * (maxAge - minAge + 1)) + minAge;
|
||||||
|
const birthdate = new Date();
|
||||||
|
birthdate.setDate(birthdate.getDate() - randomAge); // 5 Tage = 5 Jahre alt
|
||||||
|
|
||||||
|
// Zufälliger Title
|
||||||
|
const title = titles[Math.floor(Math.random() * titles.length)];
|
||||||
|
|
||||||
|
// Erstelle den NPC-Charakter (ohne userId = NPC)
|
||||||
|
const npc = await FalukantCharacter.create({
|
||||||
|
userId: null, // Wichtig: null = NPC
|
||||||
|
regionId: region.id,
|
||||||
|
firstName: fnObj.id,
|
||||||
|
lastName: lnObj.id,
|
||||||
|
gender: gender,
|
||||||
|
birthdate: birthdate,
|
||||||
|
titleOfNobility: title.id,
|
||||||
|
health: 100,
|
||||||
|
moodId: 1
|
||||||
|
}, { transaction: t });
|
||||||
|
|
||||||
|
createdNPCs.push({
|
||||||
|
id: npc.id,
|
||||||
|
firstName: fnObj.name,
|
||||||
|
lastName: lnObj.name,
|
||||||
|
gender: gender,
|
||||||
|
age: randomAge,
|
||||||
|
region: region.name,
|
||||||
|
title: title.labelTr
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
count: createdNPCs.length,
|
||||||
|
npcs: createdNPCs
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new AdminService();
|
export default new AdminService();
|
||||||
@@ -635,20 +635,27 @@ class FalukantService extends BaseService {
|
|||||||
});
|
});
|
||||||
if (already > 0) return null;
|
if (already > 0) return null;
|
||||||
|
|
||||||
// Choose an event (guaranteed once/day, random type)
|
// Choose an event (reduced frequency - not guaranteed every day)
|
||||||
|
// Total weight: 50 (50% chance per day, or adjust as needed)
|
||||||
const events = [
|
const events = [
|
||||||
{ id: 'windfall', weight: 25 },
|
{ id: 'windfall', weight: 10 },
|
||||||
{ id: 'theft', weight: 20 },
|
{ id: 'theft', weight: 8 },
|
||||||
{ id: 'character_illness', weight: 20 },
|
{ id: 'character_illness', weight: 8 },
|
||||||
{ id: 'character_recovery', weight: 15 },
|
{ id: 'character_recovery', weight: 6 },
|
||||||
{ id: 'character_accident', weight: 10 },
|
{ id: 'character_accident', weight: 4 },
|
||||||
{ id: 'regional_festival', weight: 10 },
|
{ id: 'regional_festival', weight: 4 },
|
||||||
// Regionale Events sind sehr selten und sollten nicht alle Charaktere töten
|
// Regionale Events sind sehr selten
|
||||||
{ id: 'regional_storm', weight: 1 },
|
{ id: 'regional_storm', weight: 1 },
|
||||||
{ id: 'regional_epidemic', weight: 1 },
|
{ id: 'regional_epidemic', weight: 1 },
|
||||||
{ id: 'earthquake', weight: 1 },
|
{ id: 'earthquake', weight: 1 },
|
||||||
];
|
];
|
||||||
const total = events.reduce((s, e) => s + e.weight, 0);
|
const total = events.reduce((s, e) => s + e.weight, 0);
|
||||||
|
// Reduzierte Wahrscheinlichkeit: Nur 30% Chance pro Tag, dass ein Event auftritt
|
||||||
|
const eventChance = 0.3;
|
||||||
|
if (Math.random() > eventChance) {
|
||||||
|
return null; // Kein Event heute
|
||||||
|
}
|
||||||
|
|
||||||
let r = Math.random() * total;
|
let r = Math.random() * total;
|
||||||
let chosen = events[0].id;
|
let chosen = events[0].id;
|
||||||
for (const e of events) {
|
for (const e of events) {
|
||||||
@@ -686,9 +693,9 @@ class FalukantService extends BaseService {
|
|||||||
const name = [character?.definedFirstName?.name, character?.definedLastName?.name].filter(Boolean).join(' ').trim();
|
const name = [character?.definedFirstName?.name, character?.definedLastName?.name].filter(Boolean).join(' ').trim();
|
||||||
payload.characterName = name || null;
|
payload.characterName = name || null;
|
||||||
let delta = 0;
|
let delta = 0;
|
||||||
if (chosen === 'character_illness') delta = -(Math.floor(Math.random() * 11) + 5); // -5..-15
|
if (chosen === 'character_illness') delta = -(Math.floor(Math.random() * 10) + 1); // -1..-10
|
||||||
if (chosen === 'character_recovery') delta = (Math.floor(Math.random() * 11) + 5); // +5..+15
|
if (chosen === 'character_recovery') delta = (Math.floor(Math.random() * 11) + 5); // +5..+15
|
||||||
if (chosen === 'character_accident') delta = -(Math.floor(Math.random() * 16) + 10); // -10..-25
|
if (chosen === 'character_accident') delta = -(Math.floor(Math.random() * 24) + 2); // -2..-25
|
||||||
payload.healthChange = delta > 0 ? `+${delta}` : `${delta}`;
|
payload.healthChange = delta > 0 ? `+${delta}` : `${delta}`;
|
||||||
if (character) {
|
if (character) {
|
||||||
const next = Math.min(100, Math.max(0, Number(character.health || 0) + delta));
|
const next = Math.min(100, Math.max(0, Number(character.health || 0) + delta));
|
||||||
@@ -704,20 +711,20 @@ class FalukantService extends BaseService {
|
|||||||
// Regionale Events sollten nur einen moderaten Health-Verlust verursachen
|
// Regionale Events sollten nur einen moderaten Health-Verlust verursachen
|
||||||
// NICHT alle Charaktere töten!
|
// NICHT alle Charaktere töten!
|
||||||
if (chosen === 'regional_epidemic' && character) {
|
if (chosen === 'regional_epidemic' && character) {
|
||||||
// Moderate Health-Reduktion: -10 bis -20 (nicht tödlich!)
|
// Moderate Health-Reduktion: -3 bis -7 (reduziert)
|
||||||
const delta = -(Math.floor(Math.random() * 11) + 10); // -10..-20
|
const delta = -(Math.floor(Math.random() * 5) + 3); // -3..-7
|
||||||
payload.healthChange = `${delta}`;
|
payload.healthChange = `${delta}`;
|
||||||
const next = Math.min(100, Math.max(0, Number(character.health || 0) + delta));
|
const next = Math.min(100, Math.max(0, Number(character.health || 0) + delta));
|
||||||
await character.update({ health: next }, { transaction: t });
|
await character.update({ health: next }, { transaction: t });
|
||||||
} else if (chosen === 'regional_storm' && character) {
|
} else if (chosen === 'regional_storm' && character) {
|
||||||
// Sehr geringer Health-Verlust: -5 bis -10
|
// Sehr geringer Health-Verlust: -2 bis -5
|
||||||
const delta = -(Math.floor(Math.random() * 6) + 5); // -5..-10
|
const delta = -(Math.floor(Math.random() * 4) + 2); // -2..-5
|
||||||
payload.healthChange = `${delta}`;
|
payload.healthChange = `${delta}`;
|
||||||
const next = Math.min(100, Math.max(0, Number(character.health || 0) + delta));
|
const next = Math.min(100, Math.max(0, Number(character.health || 0) + delta));
|
||||||
await character.update({ health: next }, { transaction: t });
|
await character.update({ health: next }, { transaction: t });
|
||||||
} else if (chosen === 'earthquake' && character) {
|
} else if (chosen === 'earthquake' && character) {
|
||||||
// Moderate Health-Reduktion: -15 bis -25 (kann gefährlich sein, aber nicht tödlich)
|
// Moderate Health-Reduktion: -5 bis -10 (reduziert)
|
||||||
const delta = -(Math.floor(Math.random() * 11) + 15); // -15..-25
|
const delta = -(Math.floor(Math.random() * 6) + 5); // -5..-10
|
||||||
payload.healthChange = `${delta}`;
|
payload.healthChange = `${delta}`;
|
||||||
const next = Math.min(100, Math.max(0, Number(character.health || 0) + delta));
|
const next = Math.min(100, Math.max(0, Number(character.health || 0) + delta));
|
||||||
await character.update({ health: next }, { transaction: t });
|
await character.update({ health: next }, { transaction: t });
|
||||||
|
|||||||
@@ -113,6 +113,27 @@
|
|||||||
"errorSaveConnection": "Die Verbindung konnte nicht gespeichert werden.",
|
"errorSaveConnection": "Die Verbindung konnte nicht gespeichert werden.",
|
||||||
"errorDeleteConnection": "Die Verbindung konnte nicht gelöscht werden.",
|
"errorDeleteConnection": "Die Verbindung konnte nicht gelöscht werden.",
|
||||||
"confirmDeleteConnection": "Verbindung wirklich löschen?"
|
"confirmDeleteConnection": "Verbindung wirklich löschen?"
|
||||||
|
},
|
||||||
|
"createNPC": {
|
||||||
|
"title": "NPCs erstellen",
|
||||||
|
"region": "Stadt",
|
||||||
|
"allRegions": "Alle Städte",
|
||||||
|
"ageRange": "Altersbereich",
|
||||||
|
"to": "bis",
|
||||||
|
"years": "Jahre",
|
||||||
|
"titleRange": "Titel-Bereich",
|
||||||
|
"count": "Anzahl",
|
||||||
|
"create": "NPCs erstellen",
|
||||||
|
"creating": "Erstelle...",
|
||||||
|
"result": "Ergebnis",
|
||||||
|
"createdCount": "{count} NPCs wurden erstellt.",
|
||||||
|
"age": "Alter",
|
||||||
|
"errorLoadingRegions": "Fehler beim Laden der Städte.",
|
||||||
|
"errorLoadingTitles": "Fehler beim Laden der Titel.",
|
||||||
|
"errorCreating": "Fehler beim Erstellen der NPCs.",
|
||||||
|
"invalidAgeRange": "Ungültiger Altersbereich.",
|
||||||
|
"invalidTitleRange": "Ungültiger Titel-Bereich.",
|
||||||
|
"invalidCount": "Ungültige Anzahl (1-100)."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chatrooms": {
|
"chatrooms": {
|
||||||
|
|||||||
@@ -62,7 +62,8 @@
|
|||||||
"logentries": "Log-Einträge",
|
"logentries": "Log-Einträge",
|
||||||
"edituser": "Benutzer bearbeiten",
|
"edituser": "Benutzer bearbeiten",
|
||||||
"database": "Datenbank",
|
"database": "Datenbank",
|
||||||
"mapEditor": "Karteneditor"
|
"mapEditor": "Karteneditor",
|
||||||
|
"createNPC": "NPCs erstellen"
|
||||||
},
|
},
|
||||||
"minigames": "Minispiele",
|
"minigames": "Minispiele",
|
||||||
"m-minigames": {
|
"m-minigames": {
|
||||||
|
|||||||
@@ -140,6 +140,27 @@
|
|||||||
"errorAddingStock": "Error adding warehouse.",
|
"errorAddingStock": "Error adding warehouse.",
|
||||||
"stockAdded": "Warehouse successfully added.",
|
"stockAdded": "Warehouse successfully added.",
|
||||||
"invalidStockData": "Please enter valid warehouse type and quantity."
|
"invalidStockData": "Please enter valid warehouse type and quantity."
|
||||||
|
},
|
||||||
|
"createNPC": {
|
||||||
|
"title": "Create NPCs",
|
||||||
|
"region": "City",
|
||||||
|
"allRegions": "All Cities",
|
||||||
|
"ageRange": "Age Range",
|
||||||
|
"to": "to",
|
||||||
|
"years": "years",
|
||||||
|
"titleRange": "Title Range",
|
||||||
|
"count": "Count",
|
||||||
|
"create": "Create NPCs",
|
||||||
|
"creating": "Creating...",
|
||||||
|
"result": "Result",
|
||||||
|
"createdCount": "{count} NPCs have been created.",
|
||||||
|
"age": "Age",
|
||||||
|
"errorLoadingRegions": "Error loading cities.",
|
||||||
|
"errorLoadingTitles": "Error loading titles.",
|
||||||
|
"errorCreating": "Error creating NPCs.",
|
||||||
|
"invalidAgeRange": "Invalid age range.",
|
||||||
|
"invalidTitleRange": "Invalid title range.",
|
||||||
|
"invalidCount": "Invalid count (1-100)."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chatrooms": {
|
"chatrooms": {
|
||||||
|
|||||||
@@ -62,7 +62,8 @@
|
|||||||
"logentries": "Log entries",
|
"logentries": "Log entries",
|
||||||
"edituser": "Edit user",
|
"edituser": "Edit user",
|
||||||
"database": "Database",
|
"database": "Database",
|
||||||
"mapEditor": "Map editor"
|
"mapEditor": "Map editor",
|
||||||
|
"createNPC": "Create NPCs"
|
||||||
},
|
},
|
||||||
"minigames": "Mini games",
|
"minigames": "Mini games",
|
||||||
"m-minigames": {
|
"m-minigames": {
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import UserRightsView from '../views/admin/UserRightsView.vue';
|
|||||||
import ForumAdminView from '../dialogues/admin/ForumAdminView.vue';
|
import ForumAdminView from '../dialogues/admin/ForumAdminView.vue';
|
||||||
import AdminFalukantEditUserView from '../views/admin/falukant/EditUserView.vue';
|
import AdminFalukantEditUserView from '../views/admin/falukant/EditUserView.vue';
|
||||||
import AdminFalukantMapRegionsView from '../views/admin/falukant/MapRegionsView.vue';
|
import AdminFalukantMapRegionsView from '../views/admin/falukant/MapRegionsView.vue';
|
||||||
|
import AdminFalukantCreateNPCView from '../views/admin/falukant/CreateNPCView.vue';
|
||||||
import AdminMinigamesView from '../views/admin/MinigamesView.vue';
|
import AdminMinigamesView from '../views/admin/MinigamesView.vue';
|
||||||
import AdminTaxiToolsView from '../views/admin/TaxiToolsView.vue';
|
import AdminTaxiToolsView from '../views/admin/TaxiToolsView.vue';
|
||||||
import AdminUsersView from '../views/admin/UsersView.vue';
|
import AdminUsersView from '../views/admin/UsersView.vue';
|
||||||
@@ -66,6 +67,12 @@ const adminRoutes = [
|
|||||||
component: AdminFalukantMapRegionsView,
|
component: AdminFalukantMapRegionsView,
|
||||||
meta: { requiresAuth: true }
|
meta: { requiresAuth: true }
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/admin/falukant/create-npc',
|
||||||
|
name: 'AdminFalukantCreateNPCView',
|
||||||
|
component: AdminFalukantCreateNPCView,
|
||||||
|
meta: { requiresAuth: true }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/admin/minigames/match3',
|
path: '/admin/minigames/match3',
|
||||||
name: 'AdminMinigames',
|
name: 'AdminMinigames',
|
||||||
|
|||||||
289
frontend/src/views/admin/falukant/CreateNPCView.vue
Normal file
289
frontend/src/views/admin/falukant/CreateNPCView.vue
Normal file
@@ -0,0 +1,289 @@
|
|||||||
|
<template>
|
||||||
|
<div class="create-npc-view">
|
||||||
|
<h1>{{ $t('admin.falukant.createNPC.title') }}</h1>
|
||||||
|
|
||||||
|
<div class="form-section">
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ $t('admin.falukant.createNPC.region') }}:</label>
|
||||||
|
<div class="region-selection">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" v-model="allRegions" @change="onAllRegionsChange" />
|
||||||
|
{{ $t('admin.falukant.createNPC.allRegions') }}
|
||||||
|
</label>
|
||||||
|
<select v-model="selectedRegionIds" multiple :disabled="allRegions" class="form-select" size="10">
|
||||||
|
<option v-for="region in regions" :key="region.id" :value="region.id">
|
||||||
|
{{ region.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ $t('admin.falukant.createNPC.ageRange') }}:</label>
|
||||||
|
<div class="age-range">
|
||||||
|
<input type="number" v-model.number="minAge" min="0" max="100" class="form-input" />
|
||||||
|
<span>{{ $t('admin.falukant.createNPC.to') }}</span>
|
||||||
|
<input type="number" v-model.number="maxAge" min="0" max="100" class="form-input" />
|
||||||
|
<span>{{ $t('admin.falukant.createNPC.years') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ $t('admin.falukant.createNPC.titleRange') }}:</label>
|
||||||
|
<div class="title-range">
|
||||||
|
<select v-model.number="minTitleId" class="form-select">
|
||||||
|
<option v-for="title in titles" :key="title.id" :value="title.id">
|
||||||
|
{{ $t(`falukant.titles.male.${title.labelTr}`) }} (ID: {{ title.id }})
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<span>{{ $t('admin.falukant.createNPC.to') }}</span>
|
||||||
|
<select v-model.number="maxTitleId" class="form-select">
|
||||||
|
<option v-for="title in titles" :key="title.id" :value="title.id">
|
||||||
|
{{ $t(`falukant.titles.male.${title.labelTr}`) }} (ID: {{ title.id }})
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>{{ $t('admin.falukant.createNPC.count') }}:</label>
|
||||||
|
<input type="number" v-model.number="count" min="1" max="100" class="form-input" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="action-buttons">
|
||||||
|
<button @click="createNPCs" :disabled="creating" class="btn btn-primary">
|
||||||
|
{{ creating ? $t('admin.falukant.createNPC.creating') : $t('admin.falukant.createNPC.create') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Ergebnis-Anzeige -->
|
||||||
|
<div v-if="result" class="result-section">
|
||||||
|
<h2>{{ $t('admin.falukant.createNPC.result') }}</h2>
|
||||||
|
<p>{{ $t('admin.falukant.createNPC.createdCount', { count: result.count }) }}</p>
|
||||||
|
<div v-if="result.npcs && result.npcs.length > 0" class="npcs-list">
|
||||||
|
<div v-for="npc in result.npcs" :key="npc.id" class="npc-item">
|
||||||
|
{{ $t(`falukant.titles.${npc.gender}.${npc.title}`) }} {{ npc.firstName }} {{ npc.lastName }}
|
||||||
|
({{ $t('admin.falukant.createNPC.age') }}: {{ npc.age }}, {{ $t('admin.falukant.createNPC.region') }}: {{ npc.region }})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="error" class="error-message">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import apiClient from '@/utils/axios.js';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'AdminFalukantCreateNPCView',
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
regions: [],
|
||||||
|
titles: [],
|
||||||
|
selectedRegionIds: [],
|
||||||
|
allRegions: true,
|
||||||
|
minAge: 0,
|
||||||
|
maxAge: 100,
|
||||||
|
minTitleId: 1,
|
||||||
|
maxTitleId: 19,
|
||||||
|
count: 1,
|
||||||
|
creating: false,
|
||||||
|
result: null,
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async mounted() {
|
||||||
|
await this.loadRegions();
|
||||||
|
await this.loadTitles();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
async loadRegions() {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get('/api/admin/falukant/regions');
|
||||||
|
this.regions = response.data || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading regions:', error);
|
||||||
|
this.error = this.$t('admin.falukant.createNPC.errorLoadingRegions');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async loadTitles() {
|
||||||
|
try {
|
||||||
|
const response = await apiClient.get('/api/admin/falukant/titles');
|
||||||
|
this.titles = response.data || [];
|
||||||
|
if (this.titles.length > 0) {
|
||||||
|
this.minTitleId = this.titles[0].id;
|
||||||
|
this.maxTitleId = this.titles[this.titles.length - 1].id;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading titles:', error);
|
||||||
|
this.error = this.$t('admin.falukant.createNPC.errorLoadingTitles');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onAllRegionsChange() {
|
||||||
|
if (this.allRegions) {
|
||||||
|
this.selectedRegionIds = [];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async createNPCs() {
|
||||||
|
if (this.creating) return;
|
||||||
|
|
||||||
|
// Validierung
|
||||||
|
if (this.minAge < 0 || this.maxAge < 0 || this.minAge > this.maxAge) {
|
||||||
|
this.error = this.$t('admin.falukant.createNPC.invalidAgeRange');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.minTitleId > this.maxTitleId) {
|
||||||
|
this.error = this.$t('admin.falukant.createNPC.invalidTitleRange');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.count < 1 || this.count > 100) {
|
||||||
|
this.error = this.$t('admin.falukant.createNPC.invalidCount');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.creating = true;
|
||||||
|
this.error = null;
|
||||||
|
this.result = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await apiClient.post('/api/admin/falukant/npcs/create', {
|
||||||
|
regionIds: this.allRegions ? null : this.selectedRegionIds,
|
||||||
|
minAge: this.minAge,
|
||||||
|
maxAge: this.maxAge,
|
||||||
|
minTitleId: this.minTitleId,
|
||||||
|
maxTitleId: this.maxTitleId,
|
||||||
|
count: this.count
|
||||||
|
});
|
||||||
|
|
||||||
|
this.result = response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error creating NPCs:', error);
|
||||||
|
this.error = error.response?.data?.error || this.$t('admin.falukant.createNPC.errorCreating');
|
||||||
|
} finally {
|
||||||
|
this.creating = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.create-npc-view {
|
||||||
|
padding: 20px;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
background: #f5f5f5;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-selection {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.region-selection label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-select[multiple] {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.age-range,
|
||||||
|
.title-range {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-input {
|
||||||
|
padding: 8px;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 100px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background-color: #28a745;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover:not(:disabled) {
|
||||||
|
background-color: #218838;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:disabled {
|
||||||
|
background-color: #6c757d;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.result-section {
|
||||||
|
background: #d4edda;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.npcs-list {
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.npc-item {
|
||||||
|
padding: 8px;
|
||||||
|
background: white;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error-message {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
padding: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user