Complete phase 47 romance pack on generic orientation and affinity hooks.

Vanilla catalogs stay without crushes; a content pack ships the overlay so later mods can reuse it.
This commit is contained in:
Leonid Pershin
2026-08-20 10:42:42 +03:00
parent cb869e4977
commit d2911c3daf
43 changed files with 1840 additions and 29 deletions
+8
View File
@@ -206,6 +206,10 @@ const ru = {
peopleConnectionsEnemies: 'Враги',
peopleConnectionsSearch: 'Поиск по связям',
peopleConnectionsEmpty: 'Нет других связей.',
peopleConnectionsCrushes: 'Симпатии',
peopleConnectionsAdmirers: 'Нравятся этому человеку',
peopleConnectionsPair: 'Пара',
peopleOrientation: 'Ориентация',
peopleOpinionValue: '{label} ({value})',
peopleAtHome: 'дома',
peopleApparelFit: 'Уместность: {value}',
@@ -563,6 +567,10 @@ const en: Messages = {
peopleConnectionsEnemies: 'Enemies',
peopleConnectionsSearch: 'Search connections',
peopleConnectionsEmpty: 'No other connections.',
peopleConnectionsCrushes: 'Crushes',
peopleConnectionsAdmirers: 'Admirers',
peopleConnectionsPair: 'Couple',
peopleOrientation: 'Orientation',
peopleOpinionValue: '{label} ({value})',
peopleAtHome: 'home',
peopleApparelFit: 'Fit: {value}',
+12
View File
@@ -181,6 +181,12 @@ export interface MapLayout {
links: { a: string; b: string }[];
}
export interface TopicInfo {
readonly defName: string;
readonly label: string;
readonly tags: readonly string[];
}
export interface CatalogResponse {
readonly territories: readonly DefInfo[];
readonly buildings: readonly DefInfo[];
@@ -192,6 +198,8 @@ export interface CatalogResponse {
readonly subjects: readonly SubjectInfo[];
readonly dayFrame: DayFrameInfo | null;
readonly holidays: readonly HolidayInfo[];
/** Conversation subjects. A content pack may add more; vanilla has no romance tags. */
readonly topics?: readonly TopicInfo[];
}
export async function fetchMods(lang: string): Promise<readonly ModInfo[]> {
@@ -301,6 +309,9 @@ export interface PersonConnections {
readonly friends: readonly PersonOpinionLink[];
readonly enemies: readonly PersonOpinionLink[];
readonly others: readonly PersonOpinionLink[];
readonly crushes?: readonly PersonOpinionLink[];
readonly admirers?: readonly PersonOpinionLink[];
readonly pair?: PersonRel | null;
}
export interface PersonCard {
@@ -341,6 +352,7 @@ export interface PersonCard {
readonly hasFullBody: boolean;
readonly customPortraitPrompt: string | null;
readonly connections: PersonConnections | null;
readonly orientation?: DefLabel | null;
}
export interface WornItem {
@@ -291,6 +291,35 @@ describe('renderPersonCard', () => {
expect(root.querySelector('.people__log-tools')).toBeNull();
});
it('hides crush columns without orientation and shows them with a pack', () => {
setLocale('ru');
const root = document.createElement('div');
renderPersonCard(root, card(), () => {}, { tab: 'connections' });
expect(root.textContent).not.toContain('Симпатии');
root.replaceChildren();
renderPersonCard(
root,
card({
orientation: { defName: 'Heterosexual', label: 'гетеро' },
connections: {
family: { parents: [], children: [], siblings: [], partners: [] },
friends: [],
enemies: [],
others: [],
crushes: [{ id: 't1', fullName: 'Учитель', female: false, opinion: 60, opinionLabel: 'друзья' }],
admirers: [],
pair: null,
},
}),
() => {},
{ tab: 'connections' },
);
expect(root.textContent).toContain('гетеро');
expect(root.textContent).toContain('Симпатии');
expect(root.textContent).toContain('Учитель');
});
it('pages the log on the now tab', () => {
setLocale('ru');
const onLogPage = vi.fn();
+17
View File
@@ -266,6 +266,12 @@ function fillConnections(parent: HTMLElement, card: PersonCard, onRelative: (id:
return;
}
if (card.orientation != null) {
const orientation = section(t('peopleOrientation'));
orientation.append(el('p', { class: 'people__orientation', text: card.orientation.label }));
parent.append(orientation);
}
const family = section(t('peopleFamily'));
appendRelativesWithOpinion(family, t('peopleParents'), connections.family.parents, onRelative);
appendRelativesWithOpinion(family, t('peopleChildren'), connections.family.children, onRelative);
@@ -275,6 +281,17 @@ function fillConnections(parent: HTMLElement, card: PersonCard, onRelative: (id:
parent.append(family);
}
if (connections.pair != null) {
appendRelativesWithOpinion(parent, t('peopleConnectionsPair'), [connections.pair], onRelative);
}
const crushes = connections.crushes ?? [];
const admirers = connections.admirers ?? [];
if (card.orientation != null || crushes.length > 0 || admirers.length > 0) {
appendOpinionLinks(parent, t('peopleConnectionsCrushes'), crushes, onRelative);
appendOpinionLinks(parent, t('peopleConnectionsAdmirers'), admirers, onRelative);
}
appendOpinionLinks(parent, t('peopleConnectionsFriends'), connections.friends, onRelative);
appendOpinionLinks(parent, t('peopleConnectionsEnemies'), connections.enemies, onRelative);