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:
@@ -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({
|
||||
|
||||
Reference in New Issue
Block a user