using HSchool.Content; namespace HSchool.People; /// /// Reads DiseaseDef stages for lesson gain, stay-home rolls and warmth decay. /// Contagion rolls live in DiseaseSystem; this type only answers stage questions. /// 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; } /// Product of active post-incubation lesson factors (1 when healthy). 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; } /// Highest stay-home chance among incubated conditions. 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; } /// /// Deterministic per person+day: whether illness keeps them off campus today. /// 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; } /// Max Warmth decay multiplier from incubated stages (1 when healthy). 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; } }