// Calculates the infection chance for a given integrated exposure.
        public static float GetInfectionChance(GermExposure ge, int total, float resistance)
        {
            /*
             * Allow one exposure count with 0 effects, then use exponential function:
             *
             * Chance to infect = 1.0 - e^(FACTOR * -(totalOverThreshold / threshold))
             *
             * With factor at 0.5:
             * Double exposure count ~ 40 %
             * Triple exposure count ~ 63 %
             * Quadruple exposure count ~ 77 %
             *
             * Resistance adjusts the thresholds
             */
            int threshold = GermExposureTuning.ThresholdsFor(ge.Exposure).GetThreshold(ge.
                                                                                       Vector);
            float adjThres = AdjustedThreshold(threshold, resistance), overTotal = Mathf.Floor(
                total - adjThres);

            // threshold of 1 is instant infection
            return((overTotal > 0.0f) ? ((threshold < 2) ? 1.0f : (1.0f - Mathf.Exp(
                                                                       -GermExposureTuning.CONTRACT_FACTOR * overTotal / adjThres))) : 0.0f);
        }
        // Creates one line of the immune system information panel.
        private static void CreateOneImmuneInfo(Disease disease, GameObject target,
                                                GermIntegrator integrator, CollapsibleDetailContentPanel panel)
        {
            var exposure = GameUtil.GetExposureTypeForDisease(disease);
            var sickness = GameUtil.GetSicknessForDisease(disease);
            ICollection <string> required = exposure.required_traits, excluded = exposure.
                                                                                 excluded_traits, noEffects = exposure.excluded_effects;
            var traits  = target.GetComponent <Traits>();
            var effects = target.GetComponent <Effects>();
            // The part we actually changed
            float threshold = Mathf.Ceil(GermIntegrator.AdjustedThreshold(GermExposureTuning.
                                                                          ThresholdsFor(exposure).GetMinThreshold(), integrator.GetResistance(exposure)));
            string tooltip = "";

            // Check for required traits to catch the disease
            if (required != null && required.Count > 0)
            {
                string traitList = "";
                foreach (string trait in required)
                {
                    if (!traits.HasTrait(trait))
                    {
                        if (traitList.Length > 0)
                        {
                            traitList += ", ";
                        }
                        traitList += Db.Get().traits.Get(trait).Name;
                    }
                }
                if (traitList.Length > 0)
                {
                    // Immune: missing required traits
                    threshold = 0.0f;
                    tooltip   = string.Format(STRINGS.DUPLICANTS.DISEASES.
                                              IMMUNE_FROM_MISSING_REQUIRED_TRAIT, traitList);
                }
            }
            // Check for traits that prevent catching it
            if (excluded != null && excluded.Count > 0)
            {
                string traitList = "";
                foreach (string trait in excluded)
                {
                    if (traits.HasTrait(trait))
                    {
                        if (traitList.Length > 0)
                        {
                            traitList += ", ";
                        }
                        traitList += Db.Get().traits.Get(trait).Name;
                    }
                }
                if (traitList.Length > 0)
                {
                    // Immune: blocking trait
                    threshold = 0.0f;
                    if (tooltip.Length > 0)
                    {
                        tooltip += "\n";
                    }
                    tooltip += string.Format(STRINGS.DUPLICANTS.DISEASES.
                                             IMMUNE_FROM_HAVING_EXLCLUDED_TRAIT, traitList);
                }
            }
            // Check for effects that prevent catching it
            if (noEffects != null && noEffects.Count > 0)
            {
                string effectList = "";
                foreach (string effect in noEffects)
                {
                    if (effects.HasEffect(effect))
                    {
                        if (effectList.Length > 0)
                        {
                            effectList += ", ";
                        }
                        effectList += Db.Get().effects.Get(effect).Name;
                    }
                }
                if (effectList.Length > 0)
                {
                    // Immune: blocking effect
                    threshold = 0.0f;
                    if (tooltip.Length > 0)
                    {
                        tooltip += "\n";
                    }
                    tooltip += string.Format(STRINGS.DUPLICANTS.DISEASES.
                                             IMMUNE_FROM_HAVING_EXCLUDED_EFFECT, effectList);
                }
            }
            // Update the label and tooltip
            if (tooltip.Length == 0)
            {
                if (threshold > 1.0f)
                {
                    tooltip = string.Format(THRESHOLD_TOOLTIP, GameUtil.GetFormattedSimple(
                                                threshold), target.GetProperName(), sickness.Name);
                }
                else
                {
                    tooltip = string.Format(THRESHOLD_TOOLTIP_1, target.GetProperName(),
                                            sickness.Name);
                }
            }
            panel.SetLabel("disease_" + disease.Id, "    • " + disease.Name + ": " +
                           (threshold == 0.0f ? THRESHOLD_IMMUNE.text : GameUtil.GetFormattedSimple(
                                threshold)), tooltip);
        }
        // Performs the dirty work of counting germs on a Duplicant.
        private void DoInjectDisease(GermExposureMonitor.Instance monitor, Disease disease,
                                     int count, Tag source, GermExposure ge)
        {
            var exposure  = ge.Exposure;
            int threshold = GermExposureTuning.ThresholdsFor(exposure).GetThreshold(ge.Vector);

            if (threshold > 0 && gameObject != null)
            {
                string germ  = ge.GermID;
                var    state = monitor.GetExposureState(germ);
                // Eligible addition to the integrated exposure calculations
                if (integratedExposure.TryGetValue(ge, out int total))
                {
                    total += count;
                }
                else
                {
                    total = count;
                }
#if DEBUG
                PUtil.LogDebug("{0} has {1:D} germs of {2} by {3} (threshold = {4:D})".F(
                                   gameObject.name, count, germ, ge.Vector, threshold));
#endif
                integratedExposure[ge] = total;
                // Resistance is required to move the thresholds which is used for tier
                // calculation
                float resist = GetResistance(exposure), adjThres = AdjustedThreshold(
                    threshold, resist), tier = total / adjThres;
                switch (state)
                {
                case ExposureState.None:
                case ExposureState.Contact:
                    // Update location where they encountered the germ
                    monitor.lastDiseaseSources[disease.id] = new DiseaseSourceInfo(source,
                                                                                   ge.Vector, GetInfectionChance(ge, total, resist), transform.
                                                                                   GetPosition());
                    if (total > adjThres)
                    {
                        if (exposure.infect_immediately)
                        {
                            // The ultimate evil flag
#if DEBUG
                            PUtil.LogDebug("Infecting {0} with {1}".F(gameObject.name, germ));
#endif
                            monitor.InfectImmediately(exposure);
                        }
                        else
                        {
                            // "Exposed to slimelung"
                            monitor.SetExposureState(germ, ExposureState.Exposed);
                            monitor.SetExposureTier(germ, tier);
#if DEBUG
                            PUtil.LogDebug("{0} exposed to {1} tier {2:F1}".F(gameObject.name,
                                                                              germ, tier));
#endif
                        }
                    }
                    else if (state == ExposureState.None)
                    {
                        // "Contact with slimelung"
                        monitor.SetExposureState(germ, ExposureState.Contact);
                    }
                    break;

                case ExposureState.Exposed:
                    // Upgrade the visual tier
                    if (total > threshold && tier > monitor.GetExposureTier(germ))
                    {
                        monitor.SetExposureTier(germ, tier);
#if DEBUG
                        PUtil.LogDebug("{0} exposure of {1} upgraded to {2:F1}".F(gameObject.
                                                                                  name, germ, tier));
#endif
                    }
                    break;

                case ExposureState.Contracted:
                case ExposureState.Sick:
                    // Already sick
                    break;

                default:
                    break;
                }
            }
        }