Esempio n. 1
0
 private void SetStatsActiveAccordingToUsage(Species species)
 {
     for (int s = 0; s < Values.STATS_COUNT; s++)
     {
         activeStats[s]      = species.UsesStat(s);
         statIOs[s].IsActive = activeStats[s];
     }
 }
        /// <summary>
        /// Import exported file. Used by a fileWatcher.
        /// </summary>
        /// <param name="filePath"></param>
        private void ImportExportedAddIfPossible(string filePath)
        {
            bool alreadyExists       = ExtractExportedFileInExtractor(filePath);
            bool added               = false;
            bool copyNameToClipboard = Properties.Settings.Default.copyNameToClipboardOnImportWhenAutoNameApplied &&
                                       (Properties.Settings.Default.applyNamePatternOnImportIfEmptyName ||
                                        (!alreadyExists && Properties.Settings.Default.applyNamePatternOnAutoImportForNewCreatures));
            Species species = speciesSelector1.SelectedSpecies;

            if (_extractor.uniqueResults ||
                (alreadyExists && _extractor.validResults))
            {
                AddCreatureToCollection(true, goToLibraryTab: false);
                SetMessageLabelText($"Successful {(alreadyExists ? "updated" : "added")} {creatureInfoInputExtractor.CreatureName} ({species.name}) of the exported file\n" + filePath, MessageBoxIcon.Information);
                added = true;
            }

            bool topLevels    = false;
            bool newTopLevels = false;

            // give feedback in overlay
            string    infoText;
            Color     textColor;
            const int colorSaturation = 200;

            if (added)
            {
                var sb = new StringBuilder();
                sb.AppendLine($"{species.name} \"{creatureInfoInputExtractor.CreatureName}\" {(alreadyExists ? "updated in " : "added to")} the library.");
                if (copyNameToClipboard)
                {
                    sb.AppendLine("Name copied to clipboard.");
                }

                for (int s = 0; s < values.Values.STATS_COUNT; s++)
                {
                    int statIndex = values.Values.statsDisplayOrder[s];
                    if (!species.UsesStat(statIndex))
                    {
                        continue;
                    }

                    sb.Append($"{Utils.StatName(statIndex, true, species.IsGlowSpecies)}: {_statIOs[statIndex].LevelWild} ({_statIOs[statIndex].BreedingValue})");
                    if (_statIOs[statIndex].TopLevel == StatIOStatus.NewTopLevel)
                    {
                        sb.Append($" {Loc.S("newTopLevel")}");
                        newTopLevels = true;
                    }
                    else if (_statIOs[statIndex].TopLevel == StatIOStatus.TopLevel)
                    {
                        sb.Append($" {Loc.S("topLevel")}");
                        topLevels = true;
                    }
                    sb.AppendLine();
                }

                infoText  = sb.ToString();
                textColor = Color.FromArgb(colorSaturation, 255, colorSaturation);
            }
            else
            {
                infoText  = $"Creature \"{creatureInfoInputExtractor.CreatureName}\" couldn't be extracted uniquely, manual level selection is necessary.";
                textColor = Color.FromArgb(255, colorSaturation, colorSaturation);
            }

            _overlay?.SetInfoText(infoText, textColor);

            if (added)
            {
                if (Properties.Settings.Default.MoveAutoImportedFileToSubFolder)
                {
                    string importedPath = Path.Combine(Path.GetDirectoryName(filePath), "imported");
                    if (!FileService.TryCreateDirectory(importedPath, out string errorMessage))
                    {
                        MessageBox.Show($"Subfolder\n{importedPath}\ncould not be created.\n{errorMessage}", $"{Loc.S("error")} - {Utils.ApplicationNameVersion}", MessageBoxButtons.OK, MessageBoxIcon.Error);
                        return;
                    }
                    FileService.TryMoveFile(filePath, Path.Combine(importedPath, Path.GetFileName(filePath)));
                }
                else if (Properties.Settings.Default.DeleteAutoImportedFile)
                {
                    FileService.TryDeleteFile(filePath);
                }
            }
            else if (copyNameToClipboard)
            {
                // extraction failed, user might expect the name of the new creature in the clipboard
                Clipboard.SetText("Automatic extraction was not possible");
            }

            if (Properties.Settings.Default.PlaySoundOnAutoImport)
            {
                if (added)
                {
                    if (newTopLevels)
                    {
                        Utils.BeepSignal(3);
                    }
                    else if (topLevels)
                    {
                        Utils.BeepSignal(2);
                    }
                    else
                    {
                        Utils.BeepSignal(1);
                    }
                }
                else
                {
                    Utils.BeepSignal(0);
                }
            }
        }
        /// <summary>
        /// Extracts possible level combinations for the given values.
        /// </summary>
        /// <param name="species"></param>
        /// <param name="level">Total level of the creature.</param>
        /// <param name="statIOs">Controls that display the stats</param>
        /// <param name="lowerTEBound">Lowest possible taming effectiveness</param>
        /// <param name="upperTEBound">Highest possible taming effectiveness</param>
        /// <param name="tamed"></param>
        /// <param name="bred"></param>
        /// <param name="imprintingBonusRounded"></param>
        /// <param name="adjustImprinting"></param>
        /// <param name="allowMoreThanHundredImprinting"></param>
        /// <param name="imprintingBonusMultiplier"></param>
        /// <param name="considerWildLevelSteps"></param>
        /// <param name="wildLevelSteps"></param>
        /// <param name="highPrecisionInputs">If true, the input is expected to be a float value from an export file.
        /// If false, it's assumed to be a displayed value from the game with one decimal digit.</param>
        /// <param name="imprintingChanged"></param>
        public void ExtractLevels(Species species, int level, List <StatIO> statIOs, double lowerTEBound, double upperTEBound,
                                  bool tamed, bool bred, double imprintingBonusRounded, bool adjustImprinting, bool allowMoreThanHundredImprinting, double imprintingBonusMultiplier,
                                  bool considerWildLevelSteps, int wildLevelSteps, bool highPrecisionInputs, out bool imprintingChanged)
        {
            List <CreatureStat> stats = species.stats;

            ValidResults           = true;
            imprintingChanged      = false;
            considerWildLevelSteps = considerWildLevelSteps &&
                                     !bred &&
                                     species.name.Substring(0, 3) != "Tek" &&
                                     species.name != "Jerboa"
            ;

            this._bred = bred;
            PostTamed  = bred || tamed;

            List <MinMaxDouble> imprintingBonusList = new List <MinMaxDouble> {
                new MinMaxDouble(0)
            };

            if (bred)
            {
                if (!adjustImprinting)
                {
                    imprintingBonusList[0] = new MinMaxDouble(imprintingBonusRounded);
                }
                else
                {
                    imprintingBonusList = CalculateImprintingBonus(species, imprintingBonusRounded, imprintingBonusMultiplier, statIOs[(int)StatNames.Torpidity].Input, statIOs[(int)StatNames.Food].Input);
                }
            }

            for (int IBi = 0; IBi < imprintingBonusList.Count; IBi++)
            {
                _imprintingBonusRange = imprintingBonusList[IBi];
                _imprintingBonusRange.SetToIntersectionWith(0, (allowMoreThanHundredImprinting ? 5 : 1)); // it's assumed that a valid IB will not be larger than 500%


                var imprintingMultiplierRanges = new MinMaxDouble[Values.STATS_COUNT];
                for (int s = 0; s < Values.STATS_COUNT; s++)
                {
                    double statImprintingMultiplier = species.StatImprintMultipliers[s];
                    imprintingMultiplierRanges[s] = statImprintingMultiplier != 0
                        ? new MinMaxDouble(1 + _imprintingBonusRange.Min * imprintingBonusMultiplier * statImprintingMultiplier,
                                           1 + _imprintingBonusRange.Max * imprintingBonusMultiplier * statImprintingMultiplier)
                        : new MinMaxDouble(1);
                }

                var levelWildSumRange = new MinMaxInt((int)Math.Round((statIOs[(int)StatNames.Torpidity].Input / imprintingMultiplierRanges[(int)StatNames.Torpidity].Max - (PostTamed ? stats[(int)StatNames.Torpidity].AddWhenTamed : 0) - stats[(int)StatNames.Torpidity].BaseValue) / (stats[(int)StatNames.Torpidity].BaseValue * stats[(int)StatNames.Torpidity].IncPerWildLevel)),
                                                      (int)Math.Round((statIOs[(int)StatNames.Torpidity].Input / imprintingMultiplierRanges[(int)StatNames.Torpidity].Min - (PostTamed ? stats[(int)StatNames.Torpidity].AddWhenTamed : 0) - stats[(int)StatNames.Torpidity].BaseValue) / (stats[(int)StatNames.Torpidity].BaseValue * stats[(int)StatNames.Torpidity].IncPerWildLevel)));
                var levelDomSumRange = new MinMaxInt(Math.Max(0, level - 1 - levelWildSumRange.Max),
                                                     Math.Max(0, level - 1 - levelWildSumRange.Min));

                LevelWildSum = levelWildSumRange.Min;
                LevelDomSum  = levelDomSumRange.Min; // TODO implement range-mechanic

                _levelsUndeterminedWild = LevelWildSum;
                _levelsUndeterminedDom  = LevelDomSum;

                if (bred)
                {
                    // bred creatures always have 100% TE
                    lowerTEBound = 1;
                    upperTEBound = 1;
                }
                else
                {
                    // sometimes it fails due to double-precision errors, e.g.
                    // Pteranodon (Lvl 34, TE: 80%): HP: 415.9 (6, 0); St: 195 (6, 0); Ox: 240 (6, 0); Fo: 2150.4 (6, 0); We: 134.4 (6, 0); Dm: 141.6% (3, 0); Sp: 135% (0, 0); To: 358.1 (33);
                    // will fail the extraction with a lowerTEBound of 0.8, it only extracts with a lowerTEBound of 0.79, then displays 0.8 as result for the TE. Adding these margins make it work as expected.
                    lowerTEBound -= 0.0006;
                    if (lowerTEBound < 0)
                    {
                        lowerTEBound = 0;
                    }
                    upperTEBound += 0.0006;
                }

                // check all possible level-combinations
                for (int s = 0; s < Values.STATS_COUNT; s++)
                {
                    if (!species.UsesStat(s))
                    {
                        Results[s].Add(new StatResult(0, 0));
                        continue;
                    }
                    if (statIOs[s].Input <= 0) // if stat is unknown (e.g. oxygen sometimes is not shown)
                    {
                        Results[s].Add(new StatResult(-1, 0));
                        continue;
                    }

                    statIOs[s].postTame = PostTamed;

                    // determine the precision of the input value
                    float toleranceForThisStat = StatValueCalculation.DisplayedAberration(statIOs[s].Input, Utils.Precision(s), highPrecisionInputs);
                    //Console.WriteLine($"Precision stat {s}: {toleranceForThisStat}");

                    MinMaxDouble inputValue    = new MinMaxDouble(statIOs[s].Input - toleranceForThisStat, statIOs[s].Input + toleranceForThisStat);
                    double       statBaseValue = stats[s].BaseValue;
                    if (PostTamed && s == (int)StatNames.Health)
                    {
                        statBaseValue *= (double)species.TamedBaseHealthMultiplier;                                         // + 0.00000000001; // todo double-precision handling
                    }
                    bool withTEff = (PostTamed && stats[s].MultAffinity > 0);
                    if (withTEff)
                    {
                        StatsWithTE.Add(s);
                    }

                    int minLW = 0;
                    int maxLW;
                    if (stats[s].IncPerWildLevel > 0)
                    {
                        double multAffinityFactor = stats[s].MultAffinity;
                        if (PostTamed)
                        {
                            // the multiplicative bonus is only multiplied with the TE if it is positive (i.e. negative boni won't get less bad if the TE is low)
                            if (multAffinityFactor > 0)
                            {
                                multAffinityFactor *= lowerTEBound;
                            }
                            multAffinityFactor += 1;
                        }
                        else
                        {
                            multAffinityFactor = 1;
                        }
                        maxLW = (int)Math.Round(((inputValue.Max / multAffinityFactor - (PostTamed ? stats[s].AddWhenTamed : 0)) / statBaseValue - 1) / stats[s].IncPerWildLevel); // floor is too unprecise
                    }
                    else
                    {
                        minLW = -1;
                        maxLW = -1;
                    }
                    if (maxLW > LevelWildSum)
                    {
                        maxLW = LevelWildSum;
                    }

                    double maxLD = 0;
                    if (!statIOs[s].DomLevelLockedZero && PostTamed && species.DisplaysStat(s) && stats[s].IncPerTamedLevel > 0)
                    {
                        int ww = 0; // base wild level for the tamed creature needed to be alive
                        if (statBaseValue + stats[s].AddWhenTamed < 0)
                        {
                            // e.g. Griffin
                            // get lowest wild level at which the creature is alive
                            while (StatValueCalculation.CalculateValue(species, s, ww, 0, true, lowerTEBound, 0, false) <= 0)
                            {
                                ww++;
                            }
                        }
                        maxLD = Math.Round((inputValue.Max / ((statBaseValue * (1 + stats[s].IncPerWildLevel * ww) + stats[s].AddWhenTamed) * (1 + lowerTEBound * stats[s].MultAffinity)) - 1) / stats[s].IncPerTamedLevel); //floor is sometimes too low
                    }
                    if (maxLD > _levelsUndeterminedDom)
                    {
                        maxLD = _levelsUndeterminedDom;
                    }
                    if (maxLD < 0)
                    {
                        maxLD = 0;
                    }

                    MinMaxDouble statImprintingMultiplierRange = new MinMaxDouble(1);
                    // only use imprintingMultiplier for stats that use them. Stamina and Oxygen don't use ist. Sometimes speed neither.
                    if (bred && species.StatImprintMultipliers[s] != 0)
                    {
                        statImprintingMultiplierRange = imprintingMultiplierRanges[s].Clone();
                    }

                    // if dom levels have no effect, just calculate the wild level
                    // for flyers (without mods) this means for speed no wild levels at all (i.e. not unknown, but 0)
                    // for the Diplodocus this means 0 wild levels in melee
                    if (stats[s].IncPerTamedLevel == 0)
                    {
                        if (stats[s].IncPerWildLevel == 0)
                        {
                            // check if the input value is valid
                            MinMaxDouble possibleStatValues = new MinMaxDouble(StatValueCalculation.CalculateValue(species, s, 0, 0, PostTamed, lowerTEBound, _imprintingBonusRange.Min, false),
                                                                               StatValueCalculation.CalculateValue(species, s, 0, 0, PostTamed, upperTEBound, _imprintingBonusRange.Max, false));
                            if (inputValue.Overlaps(possibleStatValues))
                            {
                                Results[s].Add(new StatResult(0, 0, inputValue.Mean));
                            }
                        }
                        else
                        {
                            MinMaxDouble lwRange = new MinMaxDouble(((inputValue.Min / (PostTamed ? 1 + stats[s].MultAffinity : 1) - (PostTamed ? stats[s].AddWhenTamed : 0)) / (statBaseValue * statImprintingMultiplierRange.Max) - 1) / stats[s].IncPerWildLevel,
                                                                    ((inputValue.Max / (PostTamed ? 1 + stats[s].MultAffinity : 1) - (PostTamed ? stats[s].AddWhenTamed : 0)) / (statBaseValue * statImprintingMultiplierRange.Min) - 1) / stats[s].IncPerWildLevel);
                            int lw = (int)Math.Round(lwRange.Mean);
                            if (lwRange.Includes(lw) && lw >= 0 && lw <= maxLW)
                            {
                                Results[s].Add(new StatResult(lw, 0, inputValue.Mean));
                            }
                        }
                        // even if no result was found, there is no other valid
                        continue;
                    }

                    for (int lw = minLW; lw < maxLW + 1; lw++)
                    {
                        // imprinting bonus is applied to all stats except stamina (s==1) and oxygen (s==2) and speed (s==6)
                        MinMaxDouble valueWODomRange = new MinMaxDouble(statBaseValue * (1 + stats[s].IncPerWildLevel * lw) * statImprintingMultiplierRange.Min + (PostTamed ? stats[s].AddWhenTamed : 0),
                                                                        statBaseValue * (1 + stats[s].IncPerWildLevel * lw) * statImprintingMultiplierRange.Max + (PostTamed ? stats[s].AddWhenTamed : 0)); // value without domesticated levels
                        if (!withTEff)
                        {
                            // calculate the only possible Ld, if it's an integer, take it.
                            if (stats[s].IncPerTamedLevel > 0)
                            {
                                MinMaxDouble ldRange = new MinMaxDouble((inputValue.Min / (valueWODomRange.Max * (PostTamed ? 1 + stats[s].MultAffinity : 1)) - 1) / stats[s].IncPerTamedLevel,
                                                                        (inputValue.Max / (valueWODomRange.Min * (PostTamed ? 1 + stats[s].MultAffinity : 1)) - 1) / stats[s].IncPerTamedLevel);
                                int ld = (int)Math.Round(ldRange.Mean);
                                if (ldRange.Includes(ld) && ld >= 0 && ld <= maxLD)
                                {
                                    Results[s].Add(new StatResult(lw, ld, inputValue.Mean));
                                }
                            }
                            else
                            {
                                Results[s].Add(new StatResult(lw, 0, inputValue.Mean));
                            }
                        }
                        else
                        {
                            for (int ld = 0; ld <= maxLD; ld++)
                            {
                                // taming bonus is dependent on taming-effectiveness
                                // get tamingEffectiveness-possibility
                                // calculate rounding-error thresholds. Here it's assumed that the displayed ingame value is maximal 0.5 off of the true ingame value
                                MinMaxDouble tamingEffectiveness = new MinMaxDouble((inputValue.Min / (1 + stats[s].IncPerTamedLevel * ld) - valueWODomRange.Max) / (valueWODomRange.Max * stats[s].MultAffinity),
                                                                                    (inputValue.Max / (1 + stats[s].IncPerTamedLevel * ld) - valueWODomRange.Min) / (valueWODomRange.Min * stats[s].MultAffinity));

                                if (tamingEffectiveness.Min > upperTEBound)
                                {
                                    continue;
                                }
                                if (tamingEffectiveness.Max < lowerTEBound)
                                {
                                    break; // if tamingEff < lowerBound: break, in this d-loop it's getting only smaller
                                }
                                // here it's ensured the TE overlaps the bounds, so we can clamp it to the bounds
                                if (tamingEffectiveness.Min < lowerTEBound)
                                {
                                    tamingEffectiveness.Min = lowerTEBound;
                                }
                                if (tamingEffectiveness.Max > upperTEBound)
                                {
                                    tamingEffectiveness.Max = upperTEBound;
                                }

                                if (!bred)
                                {
                                    // check if the totalLevel and the TE is possible by using the TE-levelbonus (credits for this check which sorts out more impossible results: https://github.com/VolatilePulse , thanks!)
                                    int       levelPostTame     = LevelWildSum + 1;
                                    MinMaxInt levelPreTameRange = new MinMaxInt(Creature.CalculatePreTameWildLevel(levelPostTame, tamingEffectiveness.Max),
                                                                                Creature.CalculatePreTameWildLevel(levelPostTame, tamingEffectiveness.Min));

                                    bool impossibleTE = true;
                                    for (int wildLevel = levelPreTameRange.Min; wildLevel <= levelPreTameRange.Max; wildLevel++)
                                    {
                                        MinMaxInt levelPostTameRange = new MinMaxInt((int)Math.Floor(wildLevel * (1 + tamingEffectiveness.Min / 2)),
                                                                                     (int)Math.Floor(wildLevel * (1 + tamingEffectiveness.Max / 2)));
                                        if (levelPostTameRange.Includes(levelPostTame))
                                        {
                                            impossibleTE = false;
                                            break;
                                        }
                                    }
                                    if (impossibleTE)
                                    {
                                        continue;
                                    }

                                    // test if TE with torpor-level of tamed-creatures results in a valid wild-level according to the possible levelSteps
                                    if (considerWildLevelSteps)
                                    {
                                        bool validWildLevel = false;
                                        for (int wildLevel = levelPreTameRange.Min; wildLevel <= levelPreTameRange.Max; wildLevel++)
                                        {
                                            if (wildLevel % wildLevelSteps == 0)
                                            {
                                                validWildLevel = true;
                                                break;
                                            }
                                        }
                                        if (!validWildLevel)
                                        {
                                            continue;
                                        }
                                    }

                                    // if another stat already is dependent on TE, check if this TE overlaps any of their TE-ranges. If not, TE is not possible (a creature can only have the same TE for all TE-dependent stats)
                                    if (StatsWithTE.Count > 1)
                                    {
                                        bool teExistent = false;
                                        for (int er = 0; er < Results[StatsWithTE[0]].Count; er++)
                                        {
                                            if (tamingEffectiveness.Overlaps(Results[StatsWithTE[0]][er].TE))
                                            {
                                                teExistent = true;
                                                break;
                                            }
                                        }
                                        if (!teExistent)
                                        {
                                            continue;
                                        }
                                    }
                                }

                                Results[s].Add(new StatResult(lw, ld, inputValue.Mean, tamingEffectiveness));
                            }
                        }
                    }
                }
                if (bred)
                {
                    // if each stat has at least one result, assume the extraction was valid with the chosen IB
                    if (EveryStatHasAtLeastOneResult)
                    {
                        // all stats have a result, don't test the other possible IBs
                        imprintingChanged = (Math.Abs(imprintingBonusRounded - ImprintingBonus) > 0.01);
                        break;
                    }
                    else if (IBi < imprintingBonusList.Count - 1)
                    {
                        // not all stats got a result, clear results for the next round
                        Clear();
                        ValidResults = true;
                    }
                }
            }
        }
        /// <summary>
        /// Import exported file. Used by a fileWatcher.
        /// </summary>
        /// <param name="filePath"></param>
        private void ImportExportedAddIfPossible(string filePath)
        {
            var loadResult = ExtractExportedFileInExtractor(filePath);

            if (!loadResult.HasValue)
            {
                return;
            }

            bool alreadyExists       = loadResult.Value;
            bool added               = false;
            bool copyNameToClipboard = Properties.Settings.Default.copyNameToClipboardOnImportWhenAutoNameApplied &&
                                       (Properties.Settings.Default.applyNamePatternOnAutoImportAlways ||
                                        Properties.Settings.Default.applyNamePatternOnImportIfEmptyName ||
                                        (!alreadyExists && Properties.Settings.Default.applyNamePatternOnAutoImportForNewCreatures)
                                       );
            Species  species  = speciesSelector1.SelectedSpecies;
            Creature creature = null;

            if (_extractor.UniqueResults ||
                (alreadyExists && _extractor.ValidResults))
            {
                creature = AddCreatureToCollection(true, goToLibraryTab: Properties.Settings.Default.AutoImportGotoLibraryAfterSuccess);
                SetMessageLabelText($"Successful {(alreadyExists ? "updated" : "added")} {creature.name} ({species.name}) of the exported file\n" + filePath, MessageBoxIcon.Information, filePath);
                added = true;
            }

            bool topLevels    = false;
            bool newTopLevels = false;

            // give feedback in overlay
            string    infoText;
            Color     textColor;
            const int colorSaturation = 200;

            if (added)
            {
                var sb = new StringBuilder();
                sb.AppendLine($"{species.name} \"{creature.name}\" {(alreadyExists ? "updated in " : "added to")} the library.");
                if (copyNameToClipboard)
                {
                    sb.AppendLine("Name copied to clipboard.");
                }

                for (int s = 0; s < values.Values.STATS_COUNT; s++)
                {
                    int statIndex = values.Values.statsDisplayOrder[s];
                    if (!species.UsesStat(statIndex))
                    {
                        continue;
                    }

                    sb.Append($"{Utils.StatName(statIndex, true, species.statNames)}: { _statIOs[statIndex].LevelWild} ({_statIOs[statIndex].BreedingValue})");
                    if (_statIOs[statIndex].TopLevel == StatIOStatus.NewTopLevel)
                    {
                        sb.Append($" {Loc.S("newTopLevel")}");
                        newTopLevels = true;
                    }
                    else if (_statIOs[statIndex].TopLevel == StatIOStatus.TopLevel)
                    {
                        sb.Append($" {Loc.S("topLevel")}");
                        topLevels = true;
                    }
                    sb.AppendLine();
                }

                infoText  = sb.ToString();
                textColor = Color.FromArgb(colorSaturation, 255, colorSaturation);
            }
            else
            {
                infoText  = $"Creature \"{creatureInfoInputExtractor.CreatureName}\" couldn't be extracted uniquely, manual level selection is necessary.";
                textColor = Color.FromArgb(255, colorSaturation, colorSaturation);
            }

            if (_overlay != null)
            {
                _overlay.SetInfoText(infoText, textColor);
                if (Properties.Settings.Default.DisplayInheritanceInOverlay && creature != null)
                {
                    _overlay.SetInheritanceCreatures(creature, creature.Mother, creature.Father);
                }
            }

            if (added)
            {
                if (Properties.Settings.Default.DeleteAutoImportedFile)
                {
                    FileService.TryDeleteFile(filePath);
                }
                else if (Properties.Settings.Default.MoveAutoImportedFileToSubFolder || Properties.Settings.Default.AutoImportedExportFileRename)
                {
                    string newPath = Properties.Settings.Default.MoveAutoImportedFileToSubFolder
                        ? (string.IsNullOrEmpty(Properties.Settings.Default.ImportExportedArchiveFolder)
                            ? Path.Combine(Path.GetDirectoryName(filePath), "imported")
                            : Properties.Settings.Default.ImportExportedArchiveFolder)
                        : Path.GetDirectoryName(filePath);

                    if (Properties.Settings.Default.MoveAutoImportedFileToSubFolder &&
                        !FileService.TryCreateDirectory(newPath, out string errorMessage))
                    {
                        MessageBoxes.ShowMessageBox($"Subfolder\n{newPath}\ncould not be created.\n{errorMessage}");
                        return;
                    }

                    string namePattern = Properties.Settings.Default.AutoImportedExportFileRenamePattern;

                    string newFileName = Properties.Settings.Default.AutoImportedExportFileRename && !string.IsNullOrWhiteSpace(namePattern)
                        ? NamePatterns.GenerateCreatureName(creature,
                                                            _creatureCollection.creatures.Where(c => c.Species == speciesSelector1.SelectedSpecies).ToArray(), null, null,
                                                            _customReplacingNamingPattern, false, -1, false, namePattern)
                        : Path.GetFileName(filePath);

                    string newFileNameWithoutExtension = Path.GetFileNameWithoutExtension(newFileName);
                    string newFileNameExtension        = Path.GetExtension(newFileName);
                    string newFilePath = Path.Combine(newPath, newFileName);
                    int    fileSuffix  = 1;
                    while (File.Exists(newFilePath))
                    {
                        newFilePath = Path.Combine(newPath, $"{newFileNameWithoutExtension}_{++fileSuffix}{newFileNameExtension}");
                    }

                    if (FileService.TryMoveFile(filePath, newFilePath))
                    {
                        _librarySelectionInfoClickPath = newFilePath;
                    }
                }
            }
            else if (copyNameToClipboard)
            {
                // extraction failed, user might expect the name of the new creature in the clipboard
                Clipboard.SetText("Automatic extraction was not possible");
            }

            if (Properties.Settings.Default.PlaySoundOnAutoImport)
            {
                if (added)
                {
                    if (alreadyExists)
                    {
                        SoundFeedback.BeepSignal(SoundFeedback.FeedbackSounds.Indifferent);
                    }
                    if (newTopLevels)
                    {
                        SoundFeedback.BeepSignal(SoundFeedback.FeedbackSounds.Great);
                    }
                    else if (topLevels)
                    {
                        SoundFeedback.BeepSignal(SoundFeedback.FeedbackSounds.Good);
                    }
                    else
                    {
                        SoundFeedback.BeepSignal(SoundFeedback.FeedbackSounds.Success);
                    }
                }
                else
                {
                    SoundFeedback.BeepSignal(SoundFeedback.FeedbackSounds.Failure);
                }
            }
        }
        public void Calculate(Species species, int[] wildLevels1, int[] wildLevels2)
        {
            var    levelProbabilities = new Dictionary <int, double>();
            double maxProbability     = 0;

            if (wildLevels1 == null || wildLevels2 == null || wildLevels1.Length != Values.STATS_COUNT || wildLevels2.Length != Values.STATS_COUNT)
            {
                Clear(true);
                return;
            }

            List <int> usedStatIndicesTest = new List <int>(Values.STATS_COUNT);

            for (int s = 0; s < Values.STATS_COUNT; s++)
            {
                if (species.UsesStat(s) && s != (int)StatNames.Torpidity)
                {
                    usedStatIndicesTest.Add(s);
                }
            }
            int usedStatsCount = usedStatIndicesTest.Count;

            List <int> usedStatIndices = new List <int>(usedStatsCount);
            // first check for equal levels, these can be skipped in the all-combinations loop
            int minimumLevel = 1; // includes the base level and all levels that are equal in both parents

            for (int s = 0; s < usedStatsCount; s++)
            {
                if (wildLevels1[usedStatIndicesTest[s]] == wildLevels2[usedStatIndicesTest[s]])
                {
                    minimumLevel += wildLevels1[usedStatIndicesTest[s]];
                }
                else
                {
                    usedStatIndices.Add(usedStatIndicesTest[s]);
                }
            }
            usedStatsCount = usedStatIndices.Count;
            int totalLevelCombinations = 1 << usedStatsCount;


            // loop through all combinations the offspring can inherit stat-levels
            // each used stat multiplies the combinations by two
            for (int p = 0; p < totalLevelCombinations; p++)
            {
                int    totalLevel  = minimumLevel;
                double probability = 1;
                for (int s = 0; s < usedStatsCount; s++)
                {
                    // determine if stat-level of creature one or two should be used
                    if ((p & (1 << s)) != 0)
                    {
                        // use the stat level of creature 1
                        totalLevel  += wildLevels1[usedStatIndices[s]];
                        probability *= wildLevels1[usedStatIndices[s]] > wildLevels2[usedStatIndices[s]] ? BreedingPlan.ProbabilityHigherLevel : BreedingPlan.ProbabilityLowerLevel;
                    }
                    else
                    {
                        // use the stat level of creature 2
                        totalLevel  += wildLevels2[usedStatIndices[s]];
                        probability *= wildLevels1[usedStatIndices[s]] < wildLevels2[usedStatIndices[s]] ? BreedingPlan.ProbabilityHigherLevel : BreedingPlan.ProbabilityLowerLevel;
                    }
                }
                if (!levelProbabilities.ContainsKey(totalLevel))
                {
                    levelProbabilities[totalLevel] = 0;
                }
                levelProbabilities[totalLevel] += probability;

                if (levelProbabilities[totalLevel] > maxProbability)
                {
                    maxProbability = levelProbabilities[totalLevel];
                }
            }
            DrawBars(levelProbabilities, maxProbability);
        }