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:
@@ -10,7 +10,7 @@ import UserParamType from "../models/type/user_param.js";
|
||||
import ContactMessage from "../models/service/contactmessage.js";
|
||||
import ContactService from "./ContactService.js";
|
||||
import { sendAnswerEmail } from './emailService.js';
|
||||
import { Op } from 'sequelize';
|
||||
import { Op, Sequelize } from 'sequelize';
|
||||
import FalukantUser from "../models/falukant/data/user.js";
|
||||
import FalukantCharacter from "../models/falukant/data/character.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 Room from '../models/chat/room.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 {
|
||||
async hasUserAccess(userId, section) {
|
||||
@@ -321,6 +323,17 @@ class AdminService {
|
||||
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) {
|
||||
if (!(await this.hasUserAccess(userId, 'falukantusers'))) {
|
||||
throw new Error('noaccess');
|
||||
@@ -1085,6 +1098,136 @@ class AdminService {
|
||||
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();
|
||||
@@ -635,20 +635,27 @@ class FalukantService extends BaseService {
|
||||
});
|
||||
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 = [
|
||||
{ id: 'windfall', weight: 25 },
|
||||
{ id: 'theft', weight: 20 },
|
||||
{ id: 'character_illness', weight: 20 },
|
||||
{ id: 'character_recovery', weight: 15 },
|
||||
{ id: 'character_accident', weight: 10 },
|
||||
{ id: 'regional_festival', weight: 10 },
|
||||
// Regionale Events sind sehr selten und sollten nicht alle Charaktere töten
|
||||
{ id: 'windfall', weight: 10 },
|
||||
{ id: 'theft', weight: 8 },
|
||||
{ id: 'character_illness', weight: 8 },
|
||||
{ id: 'character_recovery', weight: 6 },
|
||||
{ id: 'character_accident', weight: 4 },
|
||||
{ id: 'regional_festival', weight: 4 },
|
||||
// Regionale Events sind sehr selten
|
||||
{ id: 'regional_storm', weight: 1 },
|
||||
{ id: 'regional_epidemic', weight: 1 },
|
||||
{ id: 'earthquake', weight: 1 },
|
||||
];
|
||||
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 chosen = events[0].id;
|
||||
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();
|
||||
payload.characterName = name || null;
|
||||
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_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}`;
|
||||
if (character) {
|
||||
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
|
||||
// NICHT alle Charaktere töten!
|
||||
if (chosen === 'regional_epidemic' && character) {
|
||||
// Moderate Health-Reduktion: -10 bis -20 (nicht tödlich!)
|
||||
const delta = -(Math.floor(Math.random() * 11) + 10); // -10..-20
|
||||
// Moderate Health-Reduktion: -3 bis -7 (reduziert)
|
||||
const delta = -(Math.floor(Math.random() * 5) + 3); // -3..-7
|
||||
payload.healthChange = `${delta}`;
|
||||
const next = Math.min(100, Math.max(0, Number(character.health || 0) + delta));
|
||||
await character.update({ health: next }, { transaction: t });
|
||||
} else if (chosen === 'regional_storm' && character) {
|
||||
// Sehr geringer Health-Verlust: -5 bis -10
|
||||
const delta = -(Math.floor(Math.random() * 6) + 5); // -5..-10
|
||||
// Sehr geringer Health-Verlust: -2 bis -5
|
||||
const delta = -(Math.floor(Math.random() * 4) + 2); // -2..-5
|
||||
payload.healthChange = `${delta}`;
|
||||
const next = Math.min(100, Math.max(0, Number(character.health || 0) + delta));
|
||||
await character.update({ health: next }, { transaction: t });
|
||||
} else if (chosen === 'earthquake' && character) {
|
||||
// Moderate Health-Reduktion: -15 bis -25 (kann gefährlich sein, aber nicht tödlich)
|
||||
const delta = -(Math.floor(Math.random() * 11) + 15); // -15..-25
|
||||
// Moderate Health-Reduktion: -5 bis -10 (reduziert)
|
||||
const delta = -(Math.floor(Math.random() * 6) + 5); // -5..-10
|
||||
payload.healthChange = `${delta}`;
|
||||
const next = Math.min(100, Math.max(0, Number(character.health || 0) + delta));
|
||||
await character.update({ health: next }, { transaction: t });
|
||||
|
||||
Reference in New Issue
Block a user