import { createAdultAge, createCharacter, createHeritage } from "./create_character.js";
import { findRelatedProfession } from "./create_profession.js";
import { logger } from "./utils.js";
import { random, test } from "./random_utils.js";
import { titleCase } from "./string_utils.js";
import RarityTable from "./tables/rarity_table.js";
import Relationship from "./models/relationship.js";
// Here are the most common (50) family/households, which I've boiled down a bit. There's more
// diversity here than captured in these generators (although they are a pretty good start).
// 2 siblings
// 1-4 friends
// child, parent
// grandparent, grandkid
// householder, 1-2 parents
// householder, friend or partner
// householder, friend or partner, non-relative
// householder, kid, parent
// householder, non-relative
// married couple
// married couple, 1-2 grandkids
// married couple, 1-2 kids, 1-2 grandkids
// married couple, 1-2 kids, parent-in-law
// married couple, 1-6 kids
// married couple, 2 kids, relative
// married couple, grandparent
// married couple, kid, grandkid
// married couple, kid, non-relative
// married couple, kid, parent
// married couple, non-relative
// married couple, relative
// married couple, siblings
// parent, 1-3 kids, friend or partner
// parent, 1-4 kids
// parent, 2 kids, grandkid
// parent, 4 kids, friend or partner
// parent, kid, 1-2 grandkids
// parent, kid, grandparent
// parent, kid, non-relative
// parent, kid, sibling
// parent, kid, stepkid
// g = gender
// a = age difference
// r = relationship pairs
// ln = older or same gen relationship, % chance younger/peer has the same last name
// n = name (added below)
const relationships = {
grandmother: { g: "female", a: 0, r: ["grandson", "granddaughter"], ln: 25 },
grandfather: { g: "male", a: 0, r: ["grandson", "granddaughter"], ln: 25 },
aunt: { g: "female", a: 1, r: ["niece", "nephew"], ln: 50 },
uncle: { g: "male", a: 1, r: ["niece", "nephew"], ln: 50 },
mother: { g: "female", a: 1, r: ["son", "daughter"], ln: 100 },
father: { g: "male", a: 1, r: ["son", "daughter"], ln: 100 },
brother: { g: "male", a: 2, r: ["sister", "brother"], ln: 100 },
sister: { g: "female", a: 2, r: ["sister", "brother"], ln: 100 },
cousin: { a: 2, r: ["cousin"], ln: 25 },
niece: { g: "female", a: 2, r: ["aunt", "uncle"] },
nephew: { g: "male", a: 2, r: ["aunt", "uncle"] },
son: { g: "male", a: 2, r: ["mother", "father"] },
daughter: { g: "female", a: 2, r: ["mother", "father"] },
grandson: { g: "male", a: 2, r: ["grandmother", "grandfather"] },
granddaughter: { g: "female", a: 2, r: ["grandmother", "grandfather"] },
};
Object.keys(relationships).forEach((key) => (relationships[key].n = key));
// You only have to put the older terms in this table because if you're using
// it, you're randomizing, and the younger will be selected from the older
const rtable = new RarityTable()
.add("common", relationships.mother)
.add("common", relationships.father)
.add("common", relationships.brother)
.add("common", relationships.sister)
.add("uncommon", relationships.aunt)
.add("uncommon", relationships.uncle)
.add("uncommon", relationships.cousin)
.add("rare", relationships.grandmother)
.add("rare", relationships.grandfather);
const olderRelNames = Object.values(relationships)
.filter((r) => r.ln)
.map((r) => titleCase(r.n));
export function getRelationships() {
return olderRelNames.sort();
}
/**
* Creates a pair of related characters (e.g. mother and son, aunt and nephew) with appropriate
* age differences, shared or independent heritage, and related professions.
*
* @param {Object} [options={}] - Relationship creation options
* @param {boolean} [options.equip=true] - Whether to equip both characters with traveling possessions.
* @param {string} [options.relation] - The relationship type for the older character, selected from
* the values returned by `getRelationships()` (e.g. "Mother", "Father", "Brother"). If not
* provided, randomly selected by rarity.
* @param {string} [options.familyName=null] - An optional family name to assign to the older character.
* The younger character may inherit it based on the relationship type.
* @returns {Relationship} A Relationship instance containing both characters and their relationship label
*/
export function createRelationship({
equip = true,
relation = rtable.get().n,
familyName = null,
} = {}) {
logger.start("createRelationship", { equip, relation, familyName });
let rel = relationships[relation.toLowerCase()];
let reverse = relationships[random(rel.r)];
let youngerRel = rel.ln ? reverse : rel;
let olderRel = rel.ln ? rel : reverse;
let possessions = equip ? "Traveling" : "None";
let ageDiff = Math.abs(olderRel.a - youngerRel.a);
let youngerAge, olderAge;
do {
youngerAge = createAdultAge();
olderAge = createAdultAge();
for (let i = 0; i < ageDiff; i++) {
olderAge += createAdultAge();
}
} while (ageDiff > 0 && olderAge - youngerAge < 18 * ageDiff);
let older = createCharacter({
age: olderAge,
gender: olderRel.g,
possessions,
});
let younger = createCharacter({
age: youngerAge,
// not sure this accords with American theories about heritage...
heritage: test(olderRel.ln) ? older.heritage : createHeritage(),
gender: youngerRel.g,
profession: findRelatedProfession({ name: older.profession }),
possessions,
});
let relName = olderRel === youngerRel ? `${olderRel.n}s` : `${olderRel.n} and ${youngerRel.n}`;
if (familyName) {
older.name.family = familyName;
}
if (test(olderRel.ln)) {
younger.name.family = older.name.family;
}
return logger.end(new Relationship({ older, younger, relName }));
}