Wire grade trail notices, dress mark factor, and truancy summons weights.
Lesson multipliers stay explicit in tests; poor marks and unexcused absences toast and may auto-summon, while illness stays excused.
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
using HSchool.Content;
|
||||
|
||||
namespace HSchool.People;
|
||||
|
||||
/// <summary>
|
||||
/// Pure trail checks over recent marks and attendance. Simulation decides when to notice or summon.
|
||||
/// </summary>
|
||||
public static class GradeTrail
|
||||
{
|
||||
public static float? AverageMark(Person person)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(person);
|
||||
var list = person.LessonMarks;
|
||||
if (list is null || list.Count == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var total = 0;
|
||||
foreach (var row in list)
|
||||
{
|
||||
total += row.Value;
|
||||
}
|
||||
|
||||
return total / (float)list.Count;
|
||||
}
|
||||
|
||||
public static int TruancyCount(Person person)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(person);
|
||||
var list = person.Attendance;
|
||||
if (list is null || list.Count == 0)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
var count = 0;
|
||||
foreach (var row in list)
|
||||
{
|
||||
if (row.Status == AttendanceStatuses.Absent
|
||||
&& (row.AbsenceReason is null
|
||||
|| row.AbsenceReason.Equals(AbsenceReasons.Truancy, StringComparison.Ordinal)))
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public static bool HasPoorMarks(Person person, BehaviorDef rules)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(person);
|
||||
ArgumentNullException.ThrowIfNull(rules);
|
||||
if (rules.PoorMarkTrailMinMarks <= 0 || rules.PoorMarkTrailMaxAverage <= 0f)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var list = person.LessonMarks;
|
||||
if (list is null || list.Count < rules.PoorMarkTrailMinMarks)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return AverageMark(person) is { } average && average <= rules.PoorMarkTrailMaxAverage;
|
||||
}
|
||||
|
||||
public static bool HasTruancyTrail(Person person, BehaviorDef rules)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(person);
|
||||
ArgumentNullException.ThrowIfNull(rules);
|
||||
if (rules.TruancyTrailMinCount <= 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return TruancyCount(person) >= rules.TruancyTrailMinCount;
|
||||
}
|
||||
|
||||
public static bool IsPoor(Person person, BehaviorDef rules) =>
|
||||
HasPoorMarks(person, rules) || HasTruancyTrail(person, rules);
|
||||
|
||||
/// <summary>
|
||||
/// Illness absences are excused for summons / offense memory. Truancy and unknown reasons are not.
|
||||
/// </summary>
|
||||
public static bool CountsAsSummonOffense(string? absenceReason) =>
|
||||
absenceReason is null
|
||||
|| absenceReason.Equals(AbsenceReasons.Truancy, StringComparison.Ordinal);
|
||||
}
|
||||
@@ -12,11 +12,15 @@ public static class OffenseKinds
|
||||
|
||||
public const string Reprimand = "reprimand";
|
||||
|
||||
/// <summary>Unexcused lesson absence (slice 13). Illness does not use this kind.</summary>
|
||||
public const string Truancy = "truancy";
|
||||
|
||||
public static string LocaleKey(string kind) => kind switch
|
||||
{
|
||||
Quarrel => "OffenseQuarrel",
|
||||
Fight => "OffenseFight",
|
||||
Reprimand => "OffenseReprimand",
|
||||
Truancy => "OffenseTruancy",
|
||||
_ => "Offense" + char.ToUpperInvariant(kind[0]) + kind[1..],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ public static class Seed
|
||||
public const int MeetingAttendSalt = 19;
|
||||
public const int DiseaseSalt = 20;
|
||||
public const int ContagionSalt = 21;
|
||||
public const int GradeTrailSalt = 22;
|
||||
|
||||
/// <summary>A stream that belongs to the school rather than to one family.</summary>
|
||||
public static int ForSchool(int schoolSeed, int salt) => Mix(schoolSeed, familyIndex: -1, salt);
|
||||
|
||||
Reference in New Issue
Block a user