Entkopple Partnersuche vom Altersabstand und entferne garantierte Schwangerschaften
All checks were successful
Deploy to production / deploy (push) Successful in 2m41s
All checks were successful
Deploy to production / deploy (push) Successful in 2m41s
This commit is contained in:
@@ -31,17 +31,28 @@ und der [Endocrine Society zur zeitlichen Entwicklung](https://www.endocrine.org
|
|||||||
|
|
||||||
## Spielregeln und API
|
## Spielregeln und API
|
||||||
|
|
||||||
Die Heiratsgrenze bleibt unabhängig davon bei 14 Spieljahren für beide
|
Die Werbung bzw. Partnersuche beginnt ab 10, die Heiratsgrenze bleibt unabhängig
|
||||||
Verlobten. Automatische Schwangerschaften in bestehenden Ehen, Verlobungen und Liebschaften
|
davon bei 14 Spieljahren für beide Verlobten. Partnervorschläge haben keine
|
||||||
sowie der Backend-Sonderweg nach einer Hochzeit prüfen das individuelle
|
Altersabstandsgrenze und keine Altersobergrenze. Bis zu fünf Kandidaten werden
|
||||||
Fruchtbarkeitsalter beider Eltern. Verlobungen bleiben als Beziehungsstatus erhalten.
|
zufällig aus der passenden Region und Titelgruppe ausgewählt; ein ähnliches Alter
|
||||||
|
und das Erstellungsdatum des Charakters sind keine Auswahlkriterien.
|
||||||
|
|
||||||
|
Automatische Schwangerschaften in bestehenden Ehen, Verlobungen und Liebschaften
|
||||||
|
prüfen das individuelle Fruchtbarkeitsalter beider Eltern. Verlobungen bleiben
|
||||||
|
als Beziehungsstatus erhalten.
|
||||||
Verlobte benötigen keinen Liebschaftsunterhalt oder vorherigen Abrechnungslauf.
|
Verlobte benötigen keinen Liebschaftsunterhalt oder vorherigen Abrechnungslauf.
|
||||||
Die Zuneigung muss mindestens 45 betragen; bei fehlendem Beziehungszustand gilt
|
Die Zuneigung muss mindestens 45 betragen; bei fehlendem Beziehungszustand gilt
|
||||||
wie im Backend der Standardwert 50. Inaktive Beziehungen und verstorbene Partner
|
wie im Backend der Standardwert 50. Inaktive Beziehungen und verstorbene Partner
|
||||||
sind ausgeschlossen. Die Chance beträgt pro Prüfung 4 Prozent, ab Zuneigung 75
|
sind ausgeschlossen. Die Chance beträgt für Ehen, Verlobungen und sämtliche
|
||||||
zwei Prozentpunkte mehr; hohe Sichtbarkeit bei Zuneigung unter 50 reduziert sie
|
Liebschaftsrollen einheitlich 50 Prozent pro regulärer Fertilitätsprüfung. Es gibt
|
||||||
um zwei Prozentpunkte. Schwangerschaftsdauer und Sperren gegen parallele
|
keine rollen-, zuneigungs- oder sichtbarkeitsabhängigen Chancenboni. Die
|
||||||
Schwangerschaften entsprechen dem bestehenden Liebschaftsweg.
|
Voraussetzungen für eine Prüfung gelten weiterhin. Schwangerschaftsdauer und
|
||||||
|
Sperren gegen parallele Schwangerschaften entsprechen dem bestehenden Weg.
|
||||||
|
|
||||||
|
Liebschaften und Verlobungen werden nur im Jahres-/Abrechnungslauf geprüft,
|
||||||
|
nicht zusätzlich im Tageslauf. Der garantierte Backend-Sonderweg nach einer
|
||||||
|
Hochzeit entfällt; das Aufrufen der Familienseite plant keine Schwangerschaft.
|
||||||
|
Bereits geplante Schwangerschaften und frühere Hochzeitsflags bleiben erhalten.
|
||||||
|
|
||||||
Eine Geburt während der Verlobung nutzt die vorhandene Kennzeichnung für
|
Eine Geburt während der Verlobung nutzt die vorhandene Kennzeichnung für
|
||||||
außereheliche Kinder (`birth_context = 'lover'`, zunächst `hidden_bastard` und
|
außereheliche Kinder (`birth_context = 'lover'`, zunächst `hidden_bastard` und
|
||||||
@@ -51,9 +62,7 @@ erhalten. Für diese Freischaltung ist keine weitere Datenbankmigration nötig.
|
|||||||
|
|
||||||
Auch verheiratete Charaktere können mit einer Liebschaft ein Kind bekommen.
|
Auch verheiratete Charaktere können mit einer Liebschaft ein Kind bekommen.
|
||||||
Bei einer Mutter blockiert eine bereits geplante Schwangerschaft weitere
|
Bei einer Mutter blockiert eine bereits geplante Schwangerschaft weitere
|
||||||
Konzeptionen unabhängig vom Partner. Der Eheweg hat eine Chance von 72 Prozent
|
Konzeptionen unabhängig vom Partner. Liebschaften benötigen Zuneigung ab 45, Unterhalt ab
|
||||||
pro regulärer Prüfung; der Liebschaftsweg verwendet je nach Rolle und Zustand
|
|
||||||
deutlich kleinere Chancen. Liebschaften benötigen Zuneigung ab 45, Unterhalt ab
|
|
||||||
30 und einen aktuellen Abrechnungsstempel. Ein Ehepartner wird bei einer bereits
|
30 und einen aktuellen Abrechnungsstempel. Ein Ehepartner wird bei einer bereits
|
||||||
geplanten Liebschaftsschwangerschaft nicht nachträglich als Vater eingesetzt.
|
geplanten Liebschaftsschwangerschaft nicht nachträglich als Vater eingesetzt.
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import FalukantPredefineFirstname from '../models/falukant/predefine/firstname.j
|
|||||||
import FalukantPredefineLastname from '../models/falukant/predefine/lastname.js';
|
import FalukantPredefineLastname from '../models/falukant/predefine/lastname.js';
|
||||||
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 { getCharacterDevelopment, hasReachedFertility } from '../utils/falukant/characterDevelopment.js';
|
import { getCharacterDevelopment } from '../utils/falukant/characterDevelopment.js';
|
||||||
|
import { buildPartnerCandidateQuery } from '../utils/falukant/partnerCandidates.js';
|
||||||
import RegionData from '../models/falukant/data/region.js';
|
import RegionData from '../models/falukant/data/region.js';
|
||||||
import RegionType from '../models/falukant/type/region.js';
|
import RegionType from '../models/falukant/type/region.js';
|
||||||
import FalukantStock from '../models/falukant/data/stock.js';
|
import FalukantStock from '../models/falukant/data/stock.js';
|
||||||
@@ -3610,108 +3611,6 @@ class FalukantService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Nach der Trauung (relationship = married): geplante Geburt auf der Mutter-Charakterzeile setzen,
|
|
||||||
* damit der Daemon den UserCharacterWorker-Pfad mit pregnancy_due_at nutzen kann.
|
|
||||||
* Läuft idempotent pro Ehe über relationship_state.flags_json.honeymoonPregnancyScheduled.
|
|
||||||
* @returns {{ scheduled: boolean, motherCharacterId?: number }}
|
|
||||||
*/
|
|
||||||
async _ensureMarriageHoneymoonPregnancyScheduled({
|
|
||||||
playerCharacterId,
|
|
||||||
playerGender,
|
|
||||||
spouseCharacterId,
|
|
||||||
spouseGender,
|
|
||||||
relationshipId,
|
|
||||||
}) {
|
|
||||||
if (!relationshipId || !playerCharacterId || !spouseCharacterId) {
|
|
||||||
return { scheduled: false };
|
|
||||||
}
|
|
||||||
const norm = (g) => String(g || '').trim().toLowerCase();
|
|
||||||
const isFemale = (g) => {
|
|
||||||
const s = norm(g);
|
|
||||||
return s === 'female' || s === 'f';
|
|
||||||
};
|
|
||||||
const isMale = (g) => {
|
|
||||||
const s = norm(g);
|
|
||||||
return s === 'male' || s === 'm';
|
|
||||||
};
|
|
||||||
let motherId;
|
|
||||||
let fatherId;
|
|
||||||
if (isFemale(playerGender) && isMale(spouseGender)) {
|
|
||||||
motherId = playerCharacterId;
|
|
||||||
fatherId = spouseCharacterId;
|
|
||||||
} else if (isFemale(spouseGender) && isMale(playerGender)) {
|
|
||||||
motherId = spouseCharacterId;
|
|
||||||
fatherId = playerCharacterId;
|
|
||||||
} else {
|
|
||||||
return { scheduled: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
const parents = await FalukantCharacter.unscoped().findAll({
|
|
||||||
where: { id: [motherId, fatherId] },
|
|
||||||
attributes: ['id', 'birthdate', 'fertileFromAge'],
|
|
||||||
});
|
|
||||||
if (
|
|
||||||
parents.length !== 2
|
|
||||||
|| parents.some((parent) => !hasReachedFertility(parent, calcAge(parent.birthdate)))
|
|
||||||
) {
|
|
||||||
return { scheduled: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
const stateRow = await RelationshipState.findOne({ where: { relationshipId } });
|
|
||||||
const flags = stateRow?.flagsJson && typeof stateRow.flagsJson === 'object'
|
|
||||||
? { ...stateRow.flagsJson }
|
|
||||||
: {};
|
|
||||||
if (flags.honeymoonPregnancyScheduled) {
|
|
||||||
return { scheduled: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
const pregRows = await sequelize.query(
|
|
||||||
`SELECT pregnancy_due_at FROM falukant_data."character" WHERE id = :id`,
|
|
||||||
{ replacements: { id: motherId }, type: Sequelize.QueryTypes.SELECT }
|
|
||||||
);
|
|
||||||
if (pregRows[0]?.pregnancy_due_at) {
|
|
||||||
return { scheduled: false };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Automatische Hochzeits-Schwangerschaften nutzen die kurze Spielzeit von 18 Stunden.
|
|
||||||
const BIRTH_DUE_HOURS = 18;
|
|
||||||
const due = new Date();
|
|
||||||
due.setHours(due.getHours() + BIRTH_DUE_HOURS);
|
|
||||||
|
|
||||||
await FalukantCharacter.unscoped().update(
|
|
||||||
{
|
|
||||||
pregnancyDueAt: due,
|
|
||||||
pregnancyFatherCharacterId: fatherId,
|
|
||||||
},
|
|
||||||
{ where: { id: motherId } }
|
|
||||||
);
|
|
||||||
|
|
||||||
const mergedFlags = { ...flags, honeymoonPregnancyScheduled: true };
|
|
||||||
if (stateRow) {
|
|
||||||
await stateRow.update({ flagsJson: mergedFlags });
|
|
||||||
} else {
|
|
||||||
await RelationshipState.create({
|
|
||||||
relationshipId,
|
|
||||||
...this.buildDefaultRelationshipState('married'),
|
|
||||||
flagsJson: mergedFlags,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const mother = await FalukantCharacter.unscoped().findByPk(motherId, { attributes: ['id', 'userId'] });
|
|
||||||
if (mother?.userId) {
|
|
||||||
const fu = await FalukantUser.findByPk(mother.userId);
|
|
||||||
if (fu) {
|
|
||||||
const u = await User.findByPk(fu.userId);
|
|
||||||
if (u?.hashedId) {
|
|
||||||
await notifyUser(u.hashedId, 'familychanged', {});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return { scheduled: true, motherCharacterId: motherId };
|
|
||||||
}
|
|
||||||
|
|
||||||
async getFamily(hashedUserId) {
|
async getFamily(hashedUserId) {
|
||||||
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
const user = await this.getFalukantUserByHashedId(hashedUserId);
|
||||||
if (!user) throw new Error('User not found');
|
if (!user) throw new Error('User not found');
|
||||||
@@ -3926,7 +3825,7 @@ class FalukantService extends BaseService {
|
|||||||
if (!child.hasName || child.relationship || !kid || calcAge(kid.birthdate) < FAMILY_AGE.MIN_WOOING) continue;
|
if (!child.hasName || child.relationship || !kid || calcAge(kid.birthdate) < FAMILY_AGE.MIN_WOOING) continue;
|
||||||
child.possiblePartners = await this.getPossiblePartners(kid.id);
|
child.possiblePartners = await this.getPossiblePartners(kid.id);
|
||||||
if (child.possiblePartners.length === 0) {
|
if (child.possiblePartners.length === 0) {
|
||||||
await this.createPossiblePartners(kid.id, kid.gender, kid.regionId, kid.titleOfNobility, calcAge(kid.birthdate));
|
await this.createPossiblePartners(kid.id, kid.gender, kid.regionId, kid.titleOfNobility);
|
||||||
child.possiblePartners = await this.getPossiblePartners(kid.id);
|
child.possiblePartners = await this.getPossiblePartners(kid.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -4039,25 +3938,11 @@ class FalukantService extends BaseService {
|
|||||||
character.id,
|
character.id,
|
||||||
character.gender,
|
character.gender,
|
||||||
character.regionId,
|
character.regionId,
|
||||||
character.titleOfNobility,
|
character.titleOfNobility
|
||||||
ownAge
|
|
||||||
);
|
);
|
||||||
family.possiblePartners = await this.getPossiblePartners(character.id);
|
family.possiblePartners = await this.getPossiblePartners(character.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const marriedRel = family.relationships.find((r) => r.relationshipType === 'married');
|
|
||||||
if (marriedRel?.id && marriedRel.character2?.id != null) {
|
|
||||||
const honeymoon = await this._ensureMarriageHoneymoonPregnancyScheduled({
|
|
||||||
playerCharacterId: character.id,
|
|
||||||
playerGender: character.gender,
|
|
||||||
spouseCharacterId: marriedRel.character2.id,
|
|
||||||
spouseGender: marriedRel.character2.gender,
|
|
||||||
relationshipId: marriedRel.id,
|
|
||||||
});
|
|
||||||
if (honeymoon.scheduled && honeymoon.motherCharacterId === character.id) {
|
|
||||||
family.pregnancy = await this._getCharacterPregnancyOptional(character.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return family;
|
return family;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4663,7 +4548,7 @@ class FalukantService extends BaseService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createPossiblePartners(requestingCharacterId, requestingCharacterGender, requestingRegionId, requestingCharacterTitleOfNobility, ownAge) {
|
async createPossiblePartners(requestingCharacterId, requestingCharacterGender, requestingRegionId, requestingCharacterTitleOfNobility) {
|
||||||
try {
|
try {
|
||||||
const minTitleResult = await TitleOfNobility.findOne({
|
const minTitleResult = await TitleOfNobility.findOne({
|
||||||
order: [['id', 'ASC']],
|
order: [['id', 'ASC']],
|
||||||
@@ -4674,20 +4559,13 @@ class FalukantService extends BaseService {
|
|||||||
}
|
}
|
||||||
const minTitle = minTitleResult.id;
|
const minTitle = minTitleResult.id;
|
||||||
|
|
||||||
const potentialPartners = await FalukantCharacter.findAll({
|
const potentialPartners = await FalukantCharacter.findAll(buildPartnerCandidateQuery({
|
||||||
where: {
|
characterId: requestingCharacterId,
|
||||||
id: { [Op.ne]: requestingCharacterId },
|
gender: requestingCharacterGender,
|
||||||
gender: { [Op.ne]: requestingCharacterGender },
|
|
||||||
regionId: requestingRegionId,
|
regionId: requestingRegionId,
|
||||||
birthdate: { [Op.lte]: new Date(Date.now() - FAMILY_AGE.MIN_WOOING * 24 * 60 * 60 * 1000) },
|
titleId: requestingCharacterTitleOfNobility,
|
||||||
createdAt: { [Op.lt]: new Date(Date.now() - FAMILY_AGE.MIN_WOOING * 24 * 60 * 60 * 1000) },
|
minimumAge: FAMILY_AGE.MIN_WOOING,
|
||||||
titleOfNobility: { [Op.between]: [requestingCharacterTitleOfNobility - 1, requestingCharacterTitleOfNobility + 1] }
|
}));
|
||||||
},
|
|
||||||
order: [
|
|
||||||
[Sequelize.literal(`ABS((EXTRACT(EPOCH FROM (NOW() - "birthdate")) / 86400) - ${ownAge})`), 'ASC']
|
|
||||||
],
|
|
||||||
limit: 5,
|
|
||||||
});
|
|
||||||
|
|
||||||
const proposals = potentialPartners.map(partner => {
|
const proposals = potentialPartners.map(partner => {
|
||||||
const age = calcAge(partner.birthdate);
|
const age = calcAge(partner.birthdate);
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import { Sequelize } from 'sequelize';
|
import { Sequelize, DataTypes } from 'sequelize';
|
||||||
import migration from '../migrations-active/20261104000000-add-character-development-ages.cjs';
|
import migration from '../migrations-active/20261104000000-add-character-development-ages.cjs';
|
||||||
import { getCharacterDevelopment, hasReachedFertility } from '../utils/falukant/characterDevelopment.js';
|
import { getCharacterDevelopment, hasReachedFertility } from '../utils/falukant/characterDevelopment.js';
|
||||||
|
import { buildPartnerCandidateQuery } from '../utils/falukant/partnerCandidates.js';
|
||||||
|
|
||||||
test('development follows each character, independently of the marriage age', () => {
|
test('development follows each character, independently of the marriage age', () => {
|
||||||
const early = { pubertyStartAge: 10, fertileFromAge: 13 };
|
const early = { pubertyStartAge: 10, fertileFromAge: 13 };
|
||||||
@@ -71,6 +72,25 @@ test('database migration, persistence and daemon conception paths', {
|
|||||||
await assert.rejects(db.query(`INSERT INTO falukant_data.character
|
await assert.rejects(db.query(`INSERT INTO falukant_data.character
|
||||||
(puberty_start_age, fertile_from_age) VALUES (12, 11)`));
|
(puberty_start_age, fertile_from_age) VALUES (12, 11)`));
|
||||||
|
|
||||||
|
await t.test('partner candidates have a minimum age without age-gap or creation-age restrictions', async () => {
|
||||||
|
const [inserted] = await db.query(`INSERT INTO falukant_data.character (gender, birthdate)
|
||||||
|
VALUES ('male', CURRENT_DATE - INTERVAL '65 days'),
|
||||||
|
('male', CURRENT_DATE - INTERVAL '10 days' + INTERVAL '12 hours'),
|
||||||
|
('male', CURRENT_DATE - INTERVAL '9 days')
|
||||||
|
RETURNING id`);
|
||||||
|
const Candidate = db.define('DevelopmentPartnerCandidate', {
|
||||||
|
id: { type: DataTypes.INTEGER, primaryKey: true },
|
||||||
|
gender: DataTypes.STRING,
|
||||||
|
birthdate: DataTypes.DATE,
|
||||||
|
regionId: { type: DataTypes.INTEGER, field: 'region_id' },
|
||||||
|
titleOfNobility: { type: DataTypes.INTEGER, field: 'title_of_nobility' },
|
||||||
|
}, { schema: 'falukant_data', tableName: 'character', timestamps: false });
|
||||||
|
const rows = await Candidate.findAll(buildPartnerCandidateQuery({
|
||||||
|
characterId: 1, gender: 'female', regionId: 1, titleId: 1, minimumAge: 10,
|
||||||
|
}));
|
||||||
|
assert.deepEqual(rows.map(c => c.id).sort((a, b) => a - b), [inserted[0].id, inserted[1].id]);
|
||||||
|
});
|
||||||
|
|
||||||
await t.test('repeating the migration preserves all existing character data', async () => {
|
await t.test('repeating the migration preserves all existing character data', async () => {
|
||||||
const [saved] = await db.query('SELECT * FROM falukant_data.character ORDER BY id');
|
const [saved] = await db.query('SELECT * FROM falukant_data.character ORDER BY id');
|
||||||
await migration.up(qi);
|
await migration.up(qi);
|
||||||
@@ -100,11 +120,11 @@ test('database migration, persistence and daemon conception paths', {
|
|||||||
skip: !process.env.DAEMON_SQL_PATH,
|
skip: !process.env.DAEMON_SQL_PATH,
|
||||||
}, async (t) => {
|
}, async (t) => {
|
||||||
const source = await readFile(process.env.DAEMON_SQL_PATH, 'utf8');
|
const source = await readFile(process.env.DAEMON_SQL_PATH, 'utf8');
|
||||||
const sql = (name) => {
|
const sql = (name, randomRoll = 0) => {
|
||||||
const match = source.match(new RegExp(`pub const ${name}: &str = r#"([\\s\\S]*?)"#;`));
|
const match = source.match(new RegExp(`pub const ${name}: &str = r#"([\\s\\S]*?)"#;`));
|
||||||
assert.ok(match, name);
|
assert.ok(match, name);
|
||||||
// Remove chance from these eligibility tests only.
|
// Remove chance from these eligibility tests only.
|
||||||
return match[1].replaceAll('random()', '0.0');
|
return match[1].replaceAll('random()', `${randomRoll}::double precision`);
|
||||||
};
|
};
|
||||||
await db.query(`
|
await db.query(`
|
||||||
TRUNCATE falukant_data.character RESTART IDENTITY;
|
TRUNCATE falukant_data.character RESTART IDENTITY;
|
||||||
@@ -154,6 +174,29 @@ test('database migration, persistence and daemon conception paths', {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
await t.test('marriage, engagement and every lover role use exactly fifty percent', async () => {
|
||||||
|
for (const [name, type, role] of [
|
||||||
|
['QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE', 1, null],
|
||||||
|
['QUERY_GET_LEGACY_MARRIAGE_INSTANT_PREGNANCY_CANDIDATES', 1, null],
|
||||||
|
['QUERY_GET_LOVER_PREGNANCY_CANDIDATES', 3, null],
|
||||||
|
...['secret_affair', 'lover', 'mistress_or_favorite'].map(role => ['QUERY_GET_LOVER_PREGNANCY_CANDIDATES', 2, role]),
|
||||||
|
]) {
|
||||||
|
for (const [roll, expected] of [[0.499, true], [0.5, false], [0.99, false]]) {
|
||||||
|
await db.query(`UPDATE falukant_data.character SET birthdate = CURRENT_DATE - INTERVAL '20 days', pregnancy_due_at = NULL;
|
||||||
|
UPDATE falukant_data.relationship SET relationship_type_id = :type, marriage_pregnancy_due_at = NULL;
|
||||||
|
UPDATE falukant_data.relationship_state SET lover_role = :role, affection = 80, visibility = 80`, {
|
||||||
|
replacements: { type, role },
|
||||||
|
});
|
||||||
|
const [rows] = await db.query(sql(name, roll));
|
||||||
|
if (name === 'QUERY_TRY_MARRIAGE_CONCEPTION_UPDATE') {
|
||||||
|
const [[row]] = await db.query('SELECT marriage_pregnancy_due_at FROM falukant_data.relationship');
|
||||||
|
assert.equal(row.marriage_pregnancy_due_at != null, expected, `${name}/${role}/${roll}`);
|
||||||
|
} else {
|
||||||
|
assert.equal(rows.length > 0, expected, `${name}/${role}/${roll}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
await t.test('engaged couples need no lover maintenance and retain their relationship', async () => {
|
await t.test('engaged couples need no lover maintenance and retain their relationship', async () => {
|
||||||
const candidates = () => db.query(sql('QUERY_GET_LOVER_PREGNANCY_CANDIDATES')).then(([rows]) => rows);
|
const candidates = () => db.query(sql('QUERY_GET_LOVER_PREGNANCY_CANDIDATES')).then(([rows]) => rows);
|
||||||
await db.query(`UPDATE falukant_data.character SET birthdate = CURRENT_DATE - INTERVAL '13 days',
|
await db.query(`UPDATE falukant_data.character SET birthdate = CURRENT_DATE - INTERVAL '13 days',
|
||||||
|
|||||||
20
backend/utils/falukant/partnerCandidates.js
Normal file
20
backend/utils/falukant/partnerCandidates.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import { Op, Sequelize } from 'sequelize';
|
||||||
|
|
||||||
|
// One calendar day is one game year. Only the minimum age restricts age;
|
||||||
|
// neither the age difference nor the date the character was created matters.
|
||||||
|
export function buildPartnerCandidateQuery({ characterId, gender, regionId, titleId, minimumAge, now = new Date() }) {
|
||||||
|
const minimumAgeCutoff = new Date(now);
|
||||||
|
minimumAgeCutoff.setHours(0, 0, 0, 0);
|
||||||
|
minimumAgeCutoff.setDate(minimumAgeCutoff.getDate() - minimumAge + 1);
|
||||||
|
return {
|
||||||
|
where: {
|
||||||
|
id: { [Op.ne]: characterId },
|
||||||
|
gender: { [Op.ne]: gender },
|
||||||
|
regionId,
|
||||||
|
birthdate: { [Op.lt]: minimumAgeCutoff },
|
||||||
|
titleOfNobility: { [Op.between]: [titleId - 1, titleId + 1] },
|
||||||
|
},
|
||||||
|
order: Sequelize.literal('random()'),
|
||||||
|
limit: 5,
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user