Node/class contact rolls use pair+day seeds; vanilla respiratory diseases spread, and a threshold raises an info toast instead of per-sneeze noise. Co-authored-by: Cursor <cursoragent@cursor.com>
164 lines
4.8 KiB
C#
164 lines
4.8 KiB
C#
using HSchool.Content;
|
|
|
|
namespace HSchool.People;
|
|
|
|
/// <summary>
|
|
/// Reads DiseaseDef stages for lesson gain, stay-home rolls and warmth decay.
|
|
/// Contagion rolls live in DiseaseSystem; this type only answers stage questions.
|
|
/// </summary>
|
|
public static class DiseaseEffects
|
|
{
|
|
public static bool TryStage(DiseaseDef disease, float severity, out DiseaseStage stage)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(disease);
|
|
DiseaseStage? best = null;
|
|
foreach (var candidate in disease.Stages)
|
|
{
|
|
if (severity + 1e-6f < candidate.MinSeverity)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (best is null || candidate.MinSeverity >= best.MinSeverity)
|
|
{
|
|
best = candidate;
|
|
}
|
|
}
|
|
|
|
if (best is null)
|
|
{
|
|
stage = null!;
|
|
return false;
|
|
}
|
|
|
|
stage = best;
|
|
return true;
|
|
}
|
|
|
|
public static bool IsIncubated(HealthCondition condition, DiseaseDef disease, DateTime now)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(condition);
|
|
ArgumentNullException.ThrowIfNull(disease);
|
|
var elapsed = (DateTime.SpecifyKind(now, DateTimeKind.Utc) - condition.StartedAt).TotalDays;
|
|
return elapsed >= disease.IncubationDays;
|
|
}
|
|
|
|
/// <summary>Product of active post-incubation lesson factors (1 when healthy).</summary>
|
|
public static float LessonLearningFactor(Person person, DefCatalog catalog, DateTime now)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(person);
|
|
ArgumentNullException.ThrowIfNull(catalog);
|
|
var list = person.Conditions;
|
|
if (list is null || list.Count == 0)
|
|
{
|
|
return 1f;
|
|
}
|
|
|
|
var factor = 1f;
|
|
foreach (var condition in list)
|
|
{
|
|
if (!catalog.Diseases.TryGetValue(condition.DefName, out var disease) || disease.Abstract)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!IsIncubated(condition, disease, now) || !TryStage(disease, condition.Severity, out var stage))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
factor *= stage.LessonLearningFactor;
|
|
}
|
|
|
|
return factor;
|
|
}
|
|
|
|
/// <summary>Highest stay-home chance among incubated conditions.</summary>
|
|
public static float StayHomeChance(Person person, DefCatalog catalog, DateTime now)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(person);
|
|
ArgumentNullException.ThrowIfNull(catalog);
|
|
var list = person.Conditions;
|
|
if (list is null || list.Count == 0)
|
|
{
|
|
return 0f;
|
|
}
|
|
|
|
var chance = 0f;
|
|
foreach (var condition in list)
|
|
{
|
|
if (!catalog.Diseases.TryGetValue(condition.DefName, out var disease) || disease.Abstract)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!IsIncubated(condition, disease, now) || !TryStage(disease, condition.Severity, out var stage))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (stage.StayHomeChance > chance)
|
|
{
|
|
chance = stage.StayHomeChance;
|
|
}
|
|
}
|
|
|
|
return chance;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deterministic per person+day: whether illness keeps them off campus today.
|
|
/// </summary>
|
|
public static bool ShouldStayHome(Person person, DefCatalog catalog, int peopleSeed, DateTime now)
|
|
{
|
|
var chance = StayHomeChance(person, catalog, now);
|
|
if (chance <= 0f)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (chance >= 1f)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
var dayNumber = DateOnly.FromDateTime(DateTime.SpecifyKind(now, DateTimeKind.Utc)).DayNumber;
|
|
var roll = Seed.Mix(peopleSeed, person.Id, dayNumber, Seed.DiseaseSalt);
|
|
var unit = (roll & int.MaxValue) / (float)int.MaxValue;
|
|
return unit < chance;
|
|
}
|
|
|
|
/// <summary>Max Warmth decay multiplier from incubated stages (1 when healthy).</summary>
|
|
public static float WarmthDecayFactor(Person person, DefCatalog catalog, DateTime now)
|
|
{
|
|
ArgumentNullException.ThrowIfNull(person);
|
|
ArgumentNullException.ThrowIfNull(catalog);
|
|
var list = person.Conditions;
|
|
if (list is null || list.Count == 0)
|
|
{
|
|
return 1f;
|
|
}
|
|
|
|
var factor = 1f;
|
|
foreach (var condition in list)
|
|
{
|
|
if (!catalog.Diseases.TryGetValue(condition.DefName, out var disease) || disease.Abstract)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!IsIncubated(condition, disease, now) || !TryStage(disease, condition.Severity, out var stage))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (stage.WarmthDecayFactor > factor)
|
|
{
|
|
factor = stage.WarmthDecayFactor;
|
|
}
|
|
}
|
|
|
|
return factor;
|
|
}
|
|
}
|