feat(falukant): add character visual representation and settings
All checks were successful
Deploy to production / deploy (push) Successful in 2m53s
All checks were successful
Deploy to production / deploy (push) Successful in 2m53s
- Introduced FalukantCharacterVisual component to display character portraits and 3D figures based on user settings. - Updated DirectorInfo, ChurchView, PoliticsView, and other components to utilize the new character visual component. - Added visual settings management for users, allowing selection between portrait styles and display modes (portraits, 3D, or both). - Implemented age rules for engagement and marriage in the backend, ensuring compliance with specified age limits. - Created a new settings view for Falukant to manage visual preferences. - Added new images for medieval character portraits. - Updated translations for new settings and features in multiple languages.
This commit is contained in:
@@ -92,6 +92,13 @@ function calcAge(birthdate) {
|
||||
return differenceInDays(now, b);
|
||||
}
|
||||
|
||||
// Ein realer Tag entspricht einem Falukant-Spieljahr.
|
||||
const FAMILY_AGE = Object.freeze({
|
||||
MIN_WOOING: 12,
|
||||
MIN_MARRIAGE: 14,
|
||||
MIN_HOUSEHOLD_AND_CHILDREN: 16,
|
||||
});
|
||||
|
||||
async function getFalukantUserOrFail(hashedId) {
|
||||
const user = await FalukantUser.findOne({
|
||||
include: [{ model: User, as: 'user', attributes: ['username', 'hashedId'], where: { hashedId } }]
|
||||
@@ -3571,6 +3578,17 @@ class FalukantService extends BaseService {
|
||||
return { scheduled: false };
|
||||
}
|
||||
|
||||
const parents = await FalukantCharacter.unscoped().findAll({
|
||||
where: { id: [motherId, fatherId] },
|
||||
attributes: ['id', 'birthdate'],
|
||||
});
|
||||
if (
|
||||
parents.length !== 2
|
||||
|| parents.some((parent) => calcAge(parent.birthdate) < FAMILY_AGE.MIN_HOUSEHOLD_AND_CHILDREN)
|
||||
) {
|
||||
return { scheduled: false };
|
||||
}
|
||||
|
||||
const stateRow = await RelationshipState.findOne({ where: { relationshipId } });
|
||||
const flags = stateRow?.flagsJson && typeof stateRow.flagsJson === 'object'
|
||||
? { ...stateRow.flagsJson }
|
||||
@@ -3899,10 +3917,10 @@ class FalukantService extends BaseService {
|
||||
pregnancy,
|
||||
};
|
||||
const ownAge = calcAge(character.birthdate);
|
||||
if (ownAge >= 12) {
|
||||
if (ownAge >= FAMILY_AGE.MIN_WOOING) {
|
||||
family.possibleLovers = await this.getPossibleLovers(character.id);
|
||||
}
|
||||
if (ownAge >= 12 && family.relationships.length === 0) {
|
||||
if (ownAge >= FAMILY_AGE.MIN_WOOING && family.relationships.length === 0) {
|
||||
family.possiblePartners = await this.getPossiblePartners(character.id);
|
||||
if (family.possiblePartners.length === 0) {
|
||||
await this.createPossiblePartners(
|
||||
@@ -3937,7 +3955,7 @@ class FalukantService extends BaseService {
|
||||
attributes: ['id', 'regionId', 'birthdate', 'titleOfNobility']
|
||||
});
|
||||
if (!requester?.id) return [];
|
||||
if (calcAge(requester.birthdate) < 12) return [];
|
||||
if (calcAge(requester.birthdate) < FAMILY_AGE.MIN_WOOING) return [];
|
||||
|
||||
const existingRelationships = await Relationship.findAll({
|
||||
where: {
|
||||
@@ -4549,6 +4567,7 @@ class FalukantService extends BaseService {
|
||||
id: { [Op.ne]: requestingCharacterId },
|
||||
gender: { [Op.ne]: requestingCharacterGender },
|
||||
regionId: requestingRegionId,
|
||||
birthdate: { [Op.lte]: new Date(Date.now() - FAMILY_AGE.MIN_WOOING * 24 * 60 * 60 * 1000) },
|
||||
createdAt: { [Op.lt]: new Date(new Date() - 12 * 24 * 60 * 60 * 1000) },
|
||||
titleOfNobility: { [Op.between]: [requestingCharacterTitleOfNobility - 1, requestingCharacterTitleOfNobility + 1] }
|
||||
},
|
||||
@@ -4588,6 +4607,18 @@ class FalukantService extends BaseService {
|
||||
if (!proposal) {
|
||||
throw new Error('Proposal not found');
|
||||
}
|
||||
const proposedCharacter = await FalukantCharacter.findByPk(proposal.proposedCharacterId, {
|
||||
attributes: ['id', 'birthdate'],
|
||||
});
|
||||
if (
|
||||
!proposedCharacter
|
||||
|| calcAge(character.birthdate) < FAMILY_AGE.MIN_WOOING
|
||||
|| calcAge(proposedCharacter.birthdate) < FAMILY_AGE.MIN_WOOING
|
||||
) {
|
||||
const error = new Error('Werbung ist erst ab 12 Spieljahren möglich');
|
||||
error.status = 422;
|
||||
throw error;
|
||||
}
|
||||
if (user.money < proposal.cost) {
|
||||
throw new Error('Not enough money to accept the proposal');
|
||||
}
|
||||
@@ -5510,7 +5541,12 @@ class FalukantService extends BaseService {
|
||||
|
||||
async getPartyTypes(hashedUserId) {
|
||||
const falukantUser = await getFalukantUserOrFail(hashedUserId);
|
||||
const engagedCount = await Relationship.count({
|
||||
const character = await FalukantCharacter.findOne({
|
||||
where: { userId: falukantUser.id },
|
||||
attributes: ['id', 'birthdate'],
|
||||
});
|
||||
const engagedCount = character && calcAge(character.birthdate) >= FAMILY_AGE.MIN_MARRIAGE
|
||||
? await Relationship.count({
|
||||
include: [
|
||||
{
|
||||
model: RelationshipType,
|
||||
@@ -5537,7 +5573,8 @@ class FalukantService extends BaseService {
|
||||
{ '$character2.user_id$': falukantUser.id }
|
||||
]
|
||||
}
|
||||
});
|
||||
})
|
||||
: 0;
|
||||
const orConditions = [{ forMarriage: false }];
|
||||
if (engagedCount > 0) {
|
||||
orConditions.push({ forMarriage: true });
|
||||
@@ -5586,7 +5623,35 @@ class FalukantService extends BaseService {
|
||||
throw new Error('Einige ausgewählte Adelstitel existieren nicht');
|
||||
}
|
||||
|
||||
const character = await FalukantCharacter.findOne({ where: { userId: falukantUser.id }, attributes: ['titleOfNobility'] });
|
||||
const character = await FalukantCharacter.findOne({ where: { userId: falukantUser.id }, attributes: ['id', 'birthdate', 'titleOfNobility'] });
|
||||
if (ptype.forMarriage) {
|
||||
if (!character || calcAge(character.birthdate) < FAMILY_AGE.MIN_MARRIAGE) {
|
||||
const error = new Error('Eine Hochzeit ist erst ab 14 Spieljahren möglich');
|
||||
error.status = 422;
|
||||
throw error;
|
||||
}
|
||||
const engagement = await Relationship.findOne({
|
||||
where: {
|
||||
[Op.or]: [
|
||||
{ character1Id: character.id },
|
||||
{ character2Id: character.id },
|
||||
],
|
||||
},
|
||||
include: [{ model: RelationshipType, as: 'relationshipType', where: { tr: 'engaged' } }],
|
||||
attributes: ['character1Id', 'character2Id'],
|
||||
});
|
||||
const partnerId = engagement?.character1Id === character.id
|
||||
? engagement.character2Id
|
||||
: engagement?.character1Id;
|
||||
const partner = partnerId
|
||||
? await FalukantCharacter.findByPk(partnerId, { attributes: ['birthdate'] })
|
||||
: null;
|
||||
if (!partner || calcAge(partner.birthdate) < FAMILY_AGE.MIN_MARRIAGE) {
|
||||
const error = new Error('Beide Verlobten müssen mindestens 14 Spieljahre alt sein');
|
||||
error.status = 422;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
const partyTypeCost = character && (await isPartyTypeFreeForTitle(character.titleOfNobility, ptype.id, ptype.tr)) ? 0 : (ptype.cost || 0);
|
||||
let cost = partyTypeCost + (music.cost || 0) + (banquette.cost || 0);
|
||||
cost += (50 / servantRatio - 1) * 1000;
|
||||
@@ -6747,7 +6812,7 @@ class FalukantService extends BaseService {
|
||||
{
|
||||
model: FalukantCharacter,
|
||||
as: 'holder',
|
||||
attributes: ['id', 'gender'],
|
||||
attributes: ['id', 'gender', 'birthdate'],
|
||||
include: [
|
||||
{
|
||||
model: FalukantPredefineFirstname,
|
||||
@@ -6828,7 +6893,8 @@ class FalukantService extends BaseService {
|
||||
definedFirstName: o.holder.definedFirstName,
|
||||
definedLastName: o.holder.definedLastName,
|
||||
nobleTitle: o.holder.nobleTitle,
|
||||
gender: o.holder.gender
|
||||
gender: o.holder.gender,
|
||||
age: o.holder.birthdate ? calcAge(o.holder.birthdate) : null
|
||||
}
|
||||
: null,
|
||||
termEnds,
|
||||
@@ -8302,7 +8368,7 @@ ORDER BY r.id`,
|
||||
{
|
||||
model: FalukantCharacter,
|
||||
as: 'holder',
|
||||
attributes: ['id', 'gender'],
|
||||
attributes: ['id', 'gender', 'birthdate'],
|
||||
required: false,
|
||||
include: [
|
||||
{
|
||||
@@ -8368,7 +8434,8 @@ ORDER BY r.id`,
|
||||
id: o.holder.id,
|
||||
name: `${o.holder.definedFirstName?.name || ''} ${o.holder.definedLastName?.name || ''}`.trim(),
|
||||
gender: o.holder.gender,
|
||||
title: o.holder.nobleTitle?.labelTr
|
||||
title: o.holder.nobleTitle?.labelTr,
|
||||
age: o.holder.birthdate ? calcAge(o.holder.birthdate) : null
|
||||
}
|
||||
: null,
|
||||
supervisor: o.supervisor
|
||||
|
||||
@@ -37,6 +37,18 @@ class SettingsService extends BaseService{
|
||||
adult_verification_status: { datatype: 'string', setting: 'account', orderId: 910, minAge: 18 },
|
||||
adult_verification_request: { datatype: 'string', setting: 'account', orderId: 911, minAge: 18 },
|
||||
adult_upload_blocked: { datatype: 'bool', setting: 'account', orderId: 912, minAge: 18 },
|
||||
falukantPortraitStyle: {
|
||||
datatype: 'singleselect',
|
||||
setting: 'falukant',
|
||||
orderId: 920,
|
||||
options: ['classic', 'medieval']
|
||||
},
|
||||
falukantFigureMode: {
|
||||
datatype: 'singleselect',
|
||||
setting: 'falukant',
|
||||
orderId: 921,
|
||||
options: ['portraits', '3d', 'both']
|
||||
},
|
||||
};
|
||||
const definition = specialTypes[description];
|
||||
if (!definition) {
|
||||
@@ -59,9 +71,38 @@ class SettingsService extends BaseService{
|
||||
immutable: false
|
||||
}
|
||||
});
|
||||
if (Array.isArray(definition.options) && definition.options.length > 0) {
|
||||
let orderId = 1;
|
||||
for (const option of definition.options) {
|
||||
await UserParamValue.findOrCreate({
|
||||
where: {
|
||||
userParamTypeId: paramType.id,
|
||||
value: option
|
||||
},
|
||||
defaults: {
|
||||
userParamTypeId: paramType.id,
|
||||
value: option,
|
||||
orderId: orderId++
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
return paramType;
|
||||
}
|
||||
|
||||
async ensureSettingsGroup(type) {
|
||||
const groupDefinitions = {
|
||||
falukant: ['falukantPortraitStyle', 'falukantFigureMode']
|
||||
};
|
||||
const descriptions = groupDefinitions[type];
|
||||
if (!descriptions) {
|
||||
return;
|
||||
}
|
||||
for (const description of descriptions) {
|
||||
await this.ensureSpecialUserParamType(description);
|
||||
}
|
||||
}
|
||||
|
||||
parseAdultVerificationRequest(value) {
|
||||
if (!value) return null;
|
||||
try {
|
||||
@@ -298,6 +339,7 @@ class SettingsService extends BaseService{
|
||||
}
|
||||
|
||||
async filterSettings(hashedUserId, type) {
|
||||
await this.ensureSettingsGroup(type);
|
||||
const user = await this.getUserByHashedId(hashedUserId);
|
||||
const userParams = await this.getUserParams(user.id, ['birthdate', 'gender']);
|
||||
let birthdate = null;
|
||||
|
||||
Reference in New Issue
Block a user