Merge branch 'phase/44-quarrel-fight'
Co-authored-by: Cursor <cursoragent@cursor.com> # Conflicts: # docs/phases/README.md # src/HSchool.Content/PeopleDefs.cs # src/HSchool.People/Roster.cs # src/HSchool.People/Seed.cs
This commit is contained in:
@@ -281,6 +281,16 @@ internal static class PeopleDefValidator
|
||||
{
|
||||
throw new ContentLoadException($"TraitDef '{trait.DefName}' whisperCatchMultiplier cannot be negative.");
|
||||
}
|
||||
|
||||
if (trait.ConflictChance < 0f)
|
||||
{
|
||||
throw new ContentLoadException($"TraitDef '{trait.DefName}' conflictChance cannot be negative.");
|
||||
}
|
||||
|
||||
if (trait.ApologyChance < 0f)
|
||||
{
|
||||
throw new ContentLoadException($"TraitDef '{trait.DefName}' apologyChance cannot be negative.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateNeed(NeedDef need)
|
||||
@@ -627,6 +637,21 @@ internal static class PeopleDefValidator
|
||||
{
|
||||
throw new ContentLoadException($"BehaviorDef '{behavior.DefName}' teacher-talk weights cannot be negative.");
|
||||
}
|
||||
|
||||
if (behavior.QuarrelChance is < 0f or > 1f)
|
||||
{
|
||||
throw new ContentLoadException($"BehaviorDef '{behavior.DefName}' quarrelChance must be 0–1.");
|
||||
}
|
||||
|
||||
if (behavior.FightChance is < 0f or > 1f)
|
||||
{
|
||||
throw new ContentLoadException($"BehaviorDef '{behavior.DefName}' fightChance must be 0–1.");
|
||||
}
|
||||
|
||||
if (behavior.ApologyRestoreFraction is < 0f or > 1f)
|
||||
{
|
||||
throw new ContentLoadException($"BehaviorDef '{behavior.DefName}' apologyRestoreFraction must be 0–1.");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateTopic(TopicDef topic, DefCatalog catalog)
|
||||
|
||||
@@ -227,6 +227,20 @@ public sealed class TraitDef : Def
|
||||
/// higher (jealous). Ignored without affinity rules.
|
||||
/// </summary>
|
||||
public int AffinityBreakOffset { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Multiplies quarrel and fight chance. Hot-tempered and bully above 1. Missing is 1.
|
||||
/// </summary>
|
||||
public float ConflictChance { get; init; } = 1f;
|
||||
|
||||
/// <summary>
|
||||
/// This person keeps one victim id until they leave or opinion hits the floor.
|
||||
/// A field, not a <c>defName == "Bully"</c> branch.
|
||||
/// </summary>
|
||||
public bool RemembersVictim { get; init; }
|
||||
|
||||
/// <summary>Multiplies the chance to start an apology. Bully below 1. Missing is 1.</summary>
|
||||
public float ApologyChance { get; init; } = 1f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -528,6 +542,24 @@ public sealed class BehaviorDef : Def
|
||||
/// <summary>Per-decision chance a pupil in class tries to start a whisper, before initiative.</summary>
|
||||
public float WhisperStartChance { get; init; } = 0.12f;
|
||||
|
||||
/// <summary>Rivals always roll this before traits. 1 means every enemy pair can start a quarrel.</summary>
|
||||
public float QuarrelChance { get; init; } = 1f;
|
||||
|
||||
/// <summary>Base chance a yard or gym clash becomes a fight. Traits scale it; kept rare.</summary>
|
||||
public float FightChance { get; init; } = 0.1f;
|
||||
|
||||
/// <summary>Opinion shift per other participant when a quarrel ends. Stronger than rude talk (−3).</summary>
|
||||
public int QuarrelOpinionShift { get; init; } = -8;
|
||||
|
||||
/// <summary>Opinion shift when a fight ends. No health need is touched.</summary>
|
||||
public int FightOpinionShift { get; init; } = -16;
|
||||
|
||||
/// <summary>Fraction of lost opinion an apology returns. Never climbs past the pre-quarrel value.</summary>
|
||||
public float ApologyRestoreFraction { get; init; } = 0.5f;
|
||||
|
||||
/// <summary>Opinion of the victim at or above this — a third person may join the quarrel.</summary>
|
||||
public int DefendOpinionMin { get; init; } = 40;
|
||||
|
||||
public static IReadOnlyList<OpinionBand> DefaultOpinionBands { get; } =
|
||||
[
|
||||
new() { Min = 70, Id = "OpinionCloseFriend" },
|
||||
|
||||
@@ -8,6 +8,11 @@ public static class TalkActions
|
||||
public const string PhoneChat = "PhoneChat";
|
||||
public const string Whisper = "Whisper";
|
||||
public const string TeacherTalk = "TeacherTalk";
|
||||
public const string Quarrel = "Quarrel";
|
||||
public const string QuarrelYard = "QuarrelYard";
|
||||
public const string Fight = "Fight";
|
||||
public const string FightGym = "FightGym";
|
||||
public const string Apologize = "Apologize";
|
||||
|
||||
public static bool IsTalk(string? actionId) =>
|
||||
actionId is not null
|
||||
@@ -15,7 +20,24 @@ public static class TalkActions
|
||||
|| actionId.Equals(StaffChat, StringComparison.Ordinal)
|
||||
|| actionId.Equals(PhoneChat, StringComparison.Ordinal)
|
||||
|| actionId.Equals(Whisper, StringComparison.Ordinal)
|
||||
|| actionId.Equals(TeacherTalk, StringComparison.Ordinal));
|
||||
|| actionId.Equals(TeacherTalk, StringComparison.Ordinal)
|
||||
|| IsConflict(actionId));
|
||||
|
||||
public static bool IsQuarrel(string? actionId) =>
|
||||
actionId is not null
|
||||
&& (actionId.Equals(Quarrel, StringComparison.Ordinal)
|
||||
|| actionId.Equals(QuarrelYard, StringComparison.Ordinal));
|
||||
|
||||
public static bool IsFight(string? actionId) =>
|
||||
actionId is not null
|
||||
&& (actionId.Equals(Fight, StringComparison.Ordinal)
|
||||
|| actionId.Equals(FightGym, StringComparison.Ordinal));
|
||||
|
||||
public static bool IsApologize(string? actionId) =>
|
||||
actionId is not null && actionId.Equals(Apologize, StringComparison.Ordinal);
|
||||
|
||||
public static bool IsConflict(string? actionId) =>
|
||||
IsQuarrel(actionId) || IsFight(actionId) || IsApologize(actionId);
|
||||
|
||||
public static bool IsWhisper(string? actionId) =>
|
||||
actionId is not null && actionId.Equals(Whisper, StringComparison.Ordinal);
|
||||
|
||||
Reference in New Issue
Block a user