Merge branch 'phase/47-romance-pack'
ci / server (push) Failing after 3m39s
ci / client (push) Successful in 19s

# Conflicts:
#	docs/phases/README.md
#	src/HSchool.Content/PeopleDefs.cs
#	src/HSchool.People/Seed.cs
This commit is contained in:
Leonid Pershin
2026-08-20 11:00:12 +03:00
44 changed files with 1841 additions and 30 deletions
+77
View File
@@ -215,6 +215,83 @@ public sealed class TraitDef : Def
/// Multiplies the chance a whisper is caught. Quiet below 1, outgoing above. Missing is 1.
/// </summary>
public float WhisperCatchMultiplier { get; init; } = 1f;
/// <summary>
/// Added to the catalog's sympathy opinion threshold. Negative — crush-prone; a pack without
/// affinity rules ignores the field.
/// </summary>
public int AffinityThresholdOffset { get; init; }
/// <summary>
/// Added to the pair-break threshold. Positive — the pair dissolves while opinion is still
/// higher (jealous). Ignored without affinity rules.
/// </summary>
public int AffinityBreakOffset { get; init; }
}
/// <summary>
/// Who this person is drawn to. Present only when a pack ships orientations; vanilla catalogs
/// have none, and the generator then writes no orientation field.
/// </summary>
public sealed class OrientationDef : Def
{
public int Weight { get; init; } = 1;
/// <summary>Attracted to people of the same gender.</summary>
public bool SameGender { get; init; }
/// <summary>Attracted to people of the opposite gender.</summary>
public bool OppositeGender { get; init; }
}
/// <summary>
/// One directed role pairing the affinity overlay may use. Empty <see cref="AffinityRulesDef.RolePairs"/>
/// means the overlay never fires, even if orientations exist.
/// </summary>
public sealed class AffinityRolePair
{
/// <summary><see cref="PersonRoles"/> id of the person who holds the feeling.</summary>
public required string From { get; init; }
/// <summary><see cref="PersonRoles"/> id of the person it is about.</summary>
public required string To { get; init; }
public bool Sympathy { get; init; }
public bool Pair { get; init; }
}
/// <summary>
/// Optional singleton, like <see cref="BehaviorDef"/>. Vanilla core does not ship one; a content
/// pack that wants crushes and pairs adds the numbers. Simulation reads the def if present and
/// no-ops when it is missing — packs are not named in code.
/// </summary>
public sealed class AffinityRulesDef : Def
{
/// <summary>Opinion at or above this (plus trait offset) becomes a one-way crush.</summary>
public int SympathyThreshold { get; init; } = 50;
/// <summary>Mutual crushes at or above this may become a pair, if the role pair allows it.</summary>
public int PairThreshold { get; init; } = 70;
/// <summary>A pair ends when either opinion falls below this (plus jealous offset).</summary>
public int PairBreakThreshold { get; init; } = 20;
/// <summary>Both people must be at least this old to pair. Sympathy has its own age filter.</summary>
public int PairMinAge { get; init; } = 18;
/// <summary>Studentstudent sympathy: age gap no larger than this, or adjacent year.</summary>
public int StudentAgeDeltaYears { get; init; } = 2;
public bool StudentAdjacentYear { get; init; } = true;
/// <summary>Family members never get a crush or pair. Kinship is not this overlay.</summary>
public bool ExcludeFamily { get; init; } = true;
/// <summary>Opinion the rebuffed person loses when a one-way crush is turned down in talk.</summary>
public int RebuffOpinionShift { get; init; } = -8;
public IReadOnlyList<AffinityRolePair> RolePairs { get; init; } = [];
}
/// <summary>Conversation subject. Tags gate appropriateness; language is optional skill help.</summary>