Add DiseaseDef diseases with weather vectors and excused absence.

Vanilla ships several diseases whose stages drive lesson gain, stay-home
rolls, and illness attendance; cold/rain/snow raise onset via BehaviorDef scale.
This commit is contained in:
Leonid Pershin
2026-08-21 13:28:57 +03:00
parent 564776883a
commit 2d633411e4
27 changed files with 1188 additions and 38 deletions
+163
View File
@@ -0,0 +1,163 @@
using HSchool.Content;
namespace HSchool.People;
/// <summary>
/// Reads DiseaseDef stages for lesson gain, stay-home rolls and warmth decay.
/// Contagion between people is phase 79.
/// </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;
}
}