Add servant management features: Implement endpoints for hiring, dismissing, and setting pay levels for servants in the FalukantController. Update UserHouse model to include servant-related attributes. Enhance frontend components to manage servant details, including staffing state and household order, with corresponding localization updates in multiple languages.

This commit is contained in:
Torsten Schulz (local)
2026-03-22 09:57:44 +01:00
parent 2977b152a2
commit 876ee2ab49
12 changed files with 1661 additions and 17 deletions

View File

@@ -4014,25 +4014,45 @@ class FalukantService extends BaseService {
async getUserHouse(hashedUserId) {
try {
const user = await User.findOne({
where: { hashedId: hashedUserId },
const falukantUser = await this.getFalukantUserByHashedId(hashedUserId);
const userHouse = await UserHouse.findOne({
where: { userId: falukantUser.id },
include: [{
model: FalukantUser,
as: 'falukantData',
include: [{
model: UserHouse,
as: 'userHouse',
include: [{
model: HouseType,
as: 'houseType',
attributes: ['position', 'cost']
}],
attributes: ['roofCondition', 'wallCondition', 'floorCondition', 'windowCondition']
}],
}
model: HouseType,
as: 'houseType',
attributes: ['id', 'position', 'cost', 'labelTr']
}],
attributes: [
'roofCondition',
'wallCondition',
'floorCondition',
'windowCondition',
'servantCount',
'servantQuality',
'servantPayLevel',
'householdOrder',
'houseTypeId'
]
});
return user.falukantData[0].userHouse ?? { position: 0, roofCondition: 100, wallCondition: 100, floorCondition: 100, windowCondition: 100 };
if (!userHouse) {
return {
position: 0,
roofCondition: 100,
wallCondition: 100,
floorCondition: 100,
windowCondition: 100,
servantCount: 0,
servantQuality: 50,
servantPayLevel: 'normal',
householdOrder: 55,
servantSummary: this.buildServantSummary(null, falukantUser.character)
};
}
const plainHouse = userHouse.get({ plain: true });
plainHouse.servantSummary = this.buildServantSummary(plainHouse, falukantUser.character);
return plainHouse;
} catch (error) {
console.log(error);
return {};
@@ -4089,9 +4109,14 @@ class FalukantService extends BaseService {
if (oldHouse) {
await oldHouse.destroy();
}
const servantDefaults = this.getInitialServantState(house.houseType, falukantUser.character);
await UserHouse.create({
userId: falukantUser.id,
houseTypeId: house.houseTypeId,
servantCount: servantDefaults.servantCount,
servantQuality: servantDefaults.servantQuality,
servantPayLevel: servantDefaults.servantPayLevel,
householdOrder: servantDefaults.householdOrder
});
await house.destroy();
await updateFalukantUserMoney(falukantUser.id, -housePrice, "housebuy", falukantUser.id);
@@ -4109,6 +4134,232 @@ class FalukantService extends BaseService {
return (house.houseType.cost / 100 * houseQuality).toFixed(2);
}
getInitialServantState(houseType, character) {
const expected = this.getServantExpectation(houseType, character);
return {
servantCount: expected.min,
servantQuality: 50,
servantPayLevel: 'normal',
householdOrder: this.calculateHouseholdOrder({
servantCount: expected.min,
servantQuality: 50,
servantPayLevel: 'normal',
houseType,
character
})
};
}
getPayLevelMultiplier(payLevel) {
switch (payLevel) {
case 'low':
return 0.8;
case 'high':
return 1.3;
default:
return 1;
}
}
getPayLevelQualityShift(payLevel) {
switch (payLevel) {
case 'low':
return -6;
case 'high':
return 6;
default:
return 0;
}
}
getServantExpectation(houseType, character) {
const housePosition = Number(houseType?.position || 0);
const titleLevel = Number(character?.nobleTitle?.level || character?.titleOfNobility?.level || 0);
let min = 0;
let max = 1;
if (housePosition >= 6) {
min = 4;
max = 8;
} else if (housePosition === 5) {
min = 3;
max = 6;
} else if (housePosition === 4) {
min = 2;
max = 4;
} else if (housePosition === 3) {
min = 1;
max = 2;
}
const titleBonus = Math.max(0, Math.floor(titleLevel / 3));
return {
min: min + titleBonus,
max: max + titleBonus
};
}
calculateHouseholdOrder({ servantCount, servantQuality, servantPayLevel, houseType, character }) {
const expectation = this.getServantExpectation(houseType, character);
const missing = Math.max(0, expectation.min - servantCount);
const excessive = Math.max(0, servantCount - expectation.max);
const qualityPart = Math.round((Number(servantQuality || 0) - 50) * 0.35);
const payPart = this.getPayLevelQualityShift(servantPayLevel);
const fitPenalty = (missing * 10) + (excessive * 4);
return Math.max(0, Math.min(100, 55 + qualityPart + payPart - fitPenalty));
}
calculateServantMonthlyCost({ servantCount, servantQuality, servantPayLevel, houseType }) {
const basePerServant = Math.max(20, Math.round((Number(houseType?.cost || 0) / 1000) + 40));
const qualityFactor = 1 + ((Number(servantQuality || 50) - 50) / 200);
const payFactor = this.getPayLevelMultiplier(servantPayLevel);
return Math.round(servantCount * basePerServant * qualityFactor * payFactor * 100) / 100;
}
buildServantSummary(userHouse, character) {
const expectation = this.getServantExpectation(userHouse?.houseType, character);
const servantCount = Number(userHouse?.servantCount || 0);
const servantQuality = Number(userHouse?.servantQuality ?? 50);
const servantPayLevel = userHouse?.servantPayLevel || 'normal';
const householdOrder = Number(
userHouse?.householdOrder ??
this.calculateHouseholdOrder({
servantCount,
servantQuality,
servantPayLevel,
houseType: userHouse?.houseType,
character
})
);
let staffingState = 'fitting';
if (servantCount < expectation.min) staffingState = 'understaffed';
if (servantCount > expectation.max) staffingState = 'overstaffed';
let orderState = 'stable';
if (householdOrder < 35) orderState = 'chaotic';
else if (householdOrder < 55) orderState = 'strained';
else if (householdOrder > 80) orderState = 'excellent';
return {
expectedMin: expectation.min,
expectedMax: expectation.max,
monthlyCost: this.calculateServantMonthlyCost({
servantCount,
servantQuality,
servantPayLevel,
houseType: userHouse?.houseType
}),
staffingState,
orderState
};
}
async getOwnedUserHouse(hashedUserId) {
const falukantUser = await this.getFalukantUserByHashedId(hashedUserId);
const house = await UserHouse.findOne({
where: { userId: falukantUser.id },
include: [{ model: HouseType, as: 'houseType', attributes: ['id', 'position', 'cost', 'labelTr'] }]
});
if (!house) {
throw new Error('House not found');
}
return { falukantUser, house };
}
async hireServants(hashedUserId, amount = 1) {
const hireAmount = Math.max(1, Math.min(Number(amount) || 1, 10));
const { falukantUser, house } = await this.getOwnedUserHouse(hashedUserId);
const hireCost = Math.round(hireAmount * (40 + ((house.houseType?.cost || 0) / 2000)) * 100) / 100;
if (Number(falukantUser.money) < hireCost) {
throw new Error('notenoughmoney.');
}
house.servantCount = Number(house.servantCount || 0) + hireAmount;
house.servantQuality = Math.min(100, Number(house.servantQuality || 50) + Math.max(1, hireAmount));
house.householdOrder = this.calculateHouseholdOrder({
servantCount: house.servantCount,
servantQuality: house.servantQuality,
servantPayLevel: house.servantPayLevel,
houseType: house.houseType,
character: falukantUser.character
});
await house.save();
await updateFalukantUserMoney(falukantUser.id, -hireCost, 'servants_hired', falukantUser.id);
const user = await User.findByPk(falukantUser.userId);
notifyUser(user.hashedId, 'falukantHouseUpdate', {});
notifyUser(user.hashedId, 'falukantUpdateStatus', {});
return {
amount: hireAmount,
cost: hireCost,
servantSummary: this.buildServantSummary(house, falukantUser.character)
};
}
async dismissServants(hashedUserId, amount = 1) {
const dismissAmount = Math.max(1, Math.min(Number(amount) || 1, 10));
const { falukantUser, house } = await this.getOwnedUserHouse(hashedUserId);
const prevCount = Number(house.servantCount || 0);
if (prevCount <= 0) {
throw new Error('No servants to dismiss');
}
// Symmetrisch zu hireServants: Qualitätsänderung skaliert mit tatsächlich entlassener Anzahl
const actualDismissed = Math.min(dismissAmount, prevCount);
house.servantCount = Math.max(0, prevCount - dismissAmount);
house.servantQuality = Math.max(
0,
Number(house.servantQuality || 50) - Math.max(1, actualDismissed)
);
house.householdOrder = this.calculateHouseholdOrder({
servantCount: house.servantCount,
servantQuality: house.servantQuality,
servantPayLevel: house.servantPayLevel,
houseType: house.houseType,
character: falukantUser.character
});
await house.save();
const user = await User.findByPk(falukantUser.userId);
notifyUser(user.hashedId, 'falukantHouseUpdate', {});
notifyUser(user.hashedId, 'falukantUpdateStatus', {});
return {
amount: dismissAmount,
servantSummary: this.buildServantSummary(house, falukantUser.character)
};
}
async setServantPayLevel(hashedUserId, payLevel) {
const normalizedPayLevel = ['low', 'normal', 'high'].includes(payLevel) ? payLevel : 'normal';
const { falukantUser, house } = await this.getOwnedUserHouse(hashedUserId);
const previousPayLevel = house.servantPayLevel || 'normal';
const oldShift = this.getPayLevelQualityShift(previousPayLevel);
const newShift = this.getPayLevelQualityShift(normalizedPayLevel);
const baseQuality = Number(house.servantQuality || 50) - oldShift;
house.servantPayLevel = normalizedPayLevel;
house.servantQuality = Math.max(0, Math.min(100, baseQuality + newShift));
house.householdOrder = this.calculateHouseholdOrder({
servantCount: house.servantCount,
servantQuality: house.servantQuality,
servantPayLevel: house.servantPayLevel,
houseType: house.houseType,
character: falukantUser.character
});
await house.save();
const user = await User.findByPk(falukantUser.userId);
notifyUser(user.hashedId, 'falukantHouseUpdate', {});
notifyUser(user.hashedId, 'falukantUpdateStatus', {});
return {
payLevel: normalizedPayLevel,
servantSummary: this.buildServantSummary(house, falukantUser.character)
};
}
async getPartyTypes(hashedUserId) {
const falukantUser = await getFalukantUserOrFail(hashedUserId);
const engagedCount = await Relationship.count({