public double uniqueTE() { double eff = -2; if (statsWithTE.Count > 0 && results[statsWithTE[0]].Count > chosenResults[statsWithTE[0]]) { for (int s = 0; s < statsWithTE.Count; s++) { for (int ss = s + 1; ss < statsWithTE.Count; ss++) { // effectiveness-calculation can be a bit off due to ingame-rounding if (results[statsWithTE[ss]].Count <= chosenResults[statsWithTE[ss]] || !MinMaxDouble.Overlaps(results[statsWithTE[s]][chosenResults[statsWithTE[s]]].TE, results[statsWithTE[ss]][chosenResults[statsWithTE[ss]]].TE)) { lastTEUnique = false; return(-1); // no unique TE } } } // calculate most probable real TE // get intersection of all TE-ranges MinMaxDouble te = results[statsWithTE[0]][chosenResults[statsWithTE[0]]].TE.Clone(); for (int s = 1; s < statsWithTE.Count; s++) { // the overlap is ensured at this point te.SetToIntersectionWith(results[statsWithTE[s]][chosenResults[statsWithTE[s]]].TE); } eff = te.Mean; } lastTEUnique = eff >= 0; return(eff); }
public double UniqueTE() { double eff = -2; if (StatsWithTE.Any() && Results[StatsWithTE[0]].Count > ChosenResults[StatsWithTE[0]]) { for (int s = 0; s < StatsWithTE.Count; s++) { for (int ss = s + 1; ss < StatsWithTE.Count; ss++) { // effectiveness-calculation can be a bit off due to ingame-rounding if (Results[StatsWithTE[ss]].Count <= ChosenResults[StatsWithTE[ss]] || !MinMaxDouble.Overlaps(Results[StatsWithTE[s]][ChosenResults[StatsWithTE[s]]].TE, Results[StatsWithTE[ss]][ChosenResults[StatsWithTE[ss]]].TE)) { return(-1); // no unique TE } } } // calculate most probable real TE // get intersection of all TE-ranges MinMaxDouble te = Results[StatsWithTE[0]][ChosenResults[StatsWithTE[0]]].TE.Clone(); for (int s = 1; s < StatsWithTE.Count; s++) { // the overlap is ensured at this point te.SetToIntersectionWith(Results[StatsWithTE[s]][ChosenResults[StatsWithTE[s]]].TE); } eff = te.Mean; } return(eff); }
public void extractLevels(Species species, int level, List <StatIO> statIOs, double lowerTEBound, double upperTEBound, bool autoDetectTamed, bool tamed, bool bred, double imprintingBonusRounded, bool adjustImprinting, bool allowMoreThanHundredImprinting, double imprintingBonusMultiplier, double cuddleIntervalMultiplier, bool considerWildLevelSteps, int wildLevelSteps, 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, cuddleIntervalMultiplier, 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 imprintingMultiplierRange = new MinMaxDouble(1 + imprintingBonusRange.Min * imprintingBonusMultiplier * .2, 1 + imprintingBonusRange.Max * imprintingBonusMultiplier * .2); var levelWildSumRange = new MinMaxInt((int)Math.Round((statIOs[(int)StatNames.Torpidity].Input / imprintingMultiplierRange.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 / imprintingMultiplierRange.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; upperTEBound += 0.0006; } // check all possible level-combinations for (int s = 0; s < statsCount; s++) { if (stats[s].BaseValue > 0 && statIOs[s].Input > 0) // if stat is used (oxygen sometimes is not) { statIOs[s].postTame = postTamed; // double precision makes it necessary to give a bit more tolerance (hence 0.050001 instead of just 0.05 etc.) // the rounding still makes issues, trying +-0.1 in the input often helps. setting the tolerance from the expected 0.05 to 0.06 MinMaxDouble inputValue = new MinMaxDouble(statIOs[s].Input - (Utils.precision(s) == 3 ? 0.00060001 : 0.060001), statIOs[s].Input + (Utils.precision(s) == 3 ? 0.00060001 : 0.060001)); double statBaseValue = stats[s].BaseValue; if (postTamed && s == 0) { 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, maxLW = 0; 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 && stats[s].BaseValue > 0 && 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 (Stats.calculateValue(species, s, ww, 0, true, lowerTEBound, 0) <= 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 && s != (int)StatNames.Stamina && s != (int)StatNames.Oxygen && s != (int)StatNames.CraftingSpeedMultiplier && (s != (int)StatNames.SpeedMultiplier || species.NoImprintingForSpeed == false)) { statImprintingMultiplierRange = imprintingMultiplierRange.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 (minLW == -1) { //results[s].Add(new StatResult(-1, 0, inputValue.Mean)); 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 dependant 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((int)Math.Ceiling(levelPostTame / (1 + tamingEffectiveness.Max / 2)), (int)Math.Ceiling(levelPostTame / (1 + tamingEffectiveness.Min / 2))); 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 dependant 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-dependant stats) if (statsWithTE.Count > 1) { bool TEExistant = false; for (int er = 0; er < results[statsWithTE[0]].Count; er++) { if (tamingEffectiveness.Overlaps(results[statsWithTE[0]][er].TE)) { TEExistant = true; break; } } if (!TEExistant) { continue; } } } results[s].Add(new StatResult(lw, ld, inputValue.Mean, tamingEffectiveness)); } } } } else { results[s].Add(new StatResult(-1, 0)); } } 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; } } } }
private List <MinMaxDouble> CalculateImprintingBonus(Species species, double imprintingBonusRounded, double imprintingBonusMultiplier, double cuddleIntervalMultiplier, double torpor, double food) { List <MinMaxDouble> imprintingBonusList = new List <MinMaxDouble>(); if (species.stats[(int)StatNames.Torpidity].BaseValue == 0 || species.stats[(int)StatNames.Torpidity].IncPerWildLevel == 0) { return(imprintingBonusList); // invalid species-data } // classic way to calculate the ImprintingBonus, this is the most exact value, but will not work if the imprinting-gain was different (e.g. events, mods (S+Nanny)) double imprintingBonusFromGainPerCuddle = 0; if (species.breeding != null) { double imprintingGainPerCuddle = Utils.imprintingGainPerCuddle(species.breeding.maturationTimeAdjusted, cuddleIntervalMultiplier); imprintingBonusFromGainPerCuddle = Math.Round(imprintingBonusRounded / imprintingGainPerCuddle) * imprintingGainPerCuddle; } MinMaxInt wildLevelsFromImprintedTorpor = new MinMaxInt( (int)Math.Round(((((torpor / (1 + species.stats[(int)StatNames.Torpidity].MultAffinity)) - species.stats[(int)StatNames.Torpidity].AddWhenTamed) / ((1 + (imprintingBonusRounded + 0.005) * 0.2 * imprintingBonusMultiplier) * species.stats[(int)StatNames.Torpidity].BaseValue)) - 1) / species.stats[(int)StatNames.Torpidity].IncPerWildLevel), (int)Math.Round(((((torpor / (1 + species.stats[(int)StatNames.Torpidity].MultAffinity)) - species.stats[(int)StatNames.Torpidity].AddWhenTamed) / ((1 + (imprintingBonusRounded - 0.005) * 0.2 * imprintingBonusMultiplier) * species.stats[(int)StatNames.Torpidity].BaseValue)) - 1) / species.stats[(int)StatNames.Torpidity].IncPerWildLevel)); // assuming food has no dom-levels, extract the exact imprinting from this stat. If the range is in the range of the torpor-dependant IB, take this more precise value for the imprinting. (food has higher values and yields more precise results) MinMaxInt wildLevelsFromImprintedFood = new MinMaxInt( (int)Math.Round(((((food / (1 + species.stats[(int)StatNames.Food].MultAffinity)) - species.stats[(int)StatNames.Food].AddWhenTamed) / ((1 + (imprintingBonusRounded + 0.005) * 0.2 * imprintingBonusMultiplier) * species.stats[(int)StatNames.Food].BaseValue)) - 1) / species.stats[(int)StatNames.Food].IncPerWildLevel), (int)Math.Round(((((food / (1 + species.stats[(int)StatNames.Food].MultAffinity)) - species.stats[(int)StatNames.Food].AddWhenTamed) / ((1 + (imprintingBonusRounded - 0.005) * 0.2 * imprintingBonusMultiplier) * species.stats[(int)StatNames.Food].BaseValue)) - 1) / species.stats[(int)StatNames.Food].IncPerWildLevel)); List <int> otherStatsSupportIB = new List <int>(); // the number of other stats that support this IB-range // for high-level creatures the bonus from imprinting is so high, that a displayed and rounded value of the imprinting bonus can be possible with multiple torpor-levels, i.e. 1 %point IB results in a larger change than a level in torpor. for (int torporLevel = wildLevelsFromImprintedTorpor.Min; torporLevel <= wildLevelsFromImprintedTorpor.Max; torporLevel++) { int support = 0; MinMaxDouble imprintingBonusRange = new MinMaxDouble( (((torpor - 0.05) / (1 + species.stats[(int)StatNames.Torpidity].MultAffinity) - species.stats[(int)StatNames.Torpidity].AddWhenTamed) / Stats.calculateValue(species, (int)StatNames.Torpidity, torporLevel, 0, false, 0, 0) - 1) / (0.2 * imprintingBonusMultiplier), (((torpor + 0.05) / (1 + species.stats[(int)StatNames.Torpidity].MultAffinity) - species.stats[(int)StatNames.Torpidity].AddWhenTamed) / Stats.calculateValue(species, (int)StatNames.Torpidity, torporLevel, 0, false, 0, 0) - 1) / (0.2 * imprintingBonusMultiplier)); // check for each possible food-level the IB-range and if it can narrow down the range derived from the torpor (deriving from food is more precise, due to the higher values) for (int foodLevel = wildLevelsFromImprintedFood.Min; foodLevel <= wildLevelsFromImprintedFood.Max; foodLevel++) { MinMaxDouble imprintingBonusFromFood = new MinMaxDouble( (((food - 0.05) / (1 + species.stats[(int)StatNames.Food].MultAffinity) - species.stats[(int)StatNames.Food].AddWhenTamed) / Stats.calculateValue(species, (int)StatNames.Food, foodLevel, 0, false, 0, 0) - 1) / (0.2 * imprintingBonusMultiplier), (((food + 0.05) / (1 + species.stats[(int)StatNames.Food].MultAffinity) - species.stats[(int)StatNames.Food].AddWhenTamed) / Stats.calculateValue(species, (int)StatNames.Food, foodLevel, 0, false, 0, 0) - 1) / (0.2 * imprintingBonusMultiplier)); // NOTE. it's assumed if the IB-food is in the range of IB-torpor, the values are correct. This doesn't have to be true, but is very probable. If extraction-issues appear, this assumption could be the reason. //if (imprintingBonusFromTorpor.Includes(imprintingBonusFromFood) if (imprintingBonusRange.Overlaps(imprintingBonusFromFood)) { MinMaxDouble intersectionIB = new MinMaxDouble(imprintingBonusRange); intersectionIB.SetToIntersectionWith(imprintingBonusFromFood); if (Stats.calculateValue(species, (int)StatNames.Torpidity, torporLevel, 0, true, 1, intersectionIB.Min) <= torpor && Stats.calculateValue(species, (int)StatNames.Torpidity, torporLevel, 0, true, 1, intersectionIB.Max) >= torpor) { //imprintingBonusFromTorpor = imprintingBonusFromFood; imprintingBonusRange.SetToIntersectionWith(imprintingBonusFromFood); support++; } } } // if classic method results in value in the possible range, take this, probably most exact value if (imprintingBonusRange.Includes(imprintingBonusFromGainPerCuddle) && Stats.calculateValue(species, (int)StatNames.Torpidity, torporLevel, 0, true, 1, imprintingBonusFromGainPerCuddle) == torpor) { imprintingBonusRange.MinMax = imprintingBonusFromGainPerCuddle; support++; } // TODO check if this range has already been added to avoid double loops in the extraction. if existant, update support imprintingBonusList.Add(imprintingBonusRange); otherStatsSupportIB.Add(support); } // sort IB according to the support they got by other stats, then return the distinct means of the possible ranges. return(imprintingBonusList.OrderByDescending(i => otherStatsSupportIB[imprintingBonusList.IndexOf(i)]).ToList()); }
public void extractLevels(int speciesI, int level, List <StatIO> statIOs, double lowerTEBound, double upperTEBound, bool autoDetectTamed, bool tamed, bool bred, double imprintingBonusRounded, bool adjustImprinting, bool allowMoreThanHundredImprinting, double imprintingBonusMultiplier, double cuddleIntervalMultiplier, bool considerWildLevelSteps, int wildLevelSteps, out bool imprintingChanged) { List <CreatureStat> stats = Values.V.species[speciesI].stats; validResults = true; imprintingChanged = false; considerWildLevelSteps = considerWildLevelSteps && !bred; this.bred = bred; if (bred) { postTamed = true; } else { postTamed = tamed; } List <double> imprintingBonusList = new List <double>() { 0 }; if (bred) { if (!adjustImprinting) { imprintingBonusList[0] = imprintingBonusRounded; } else { imprintingBonusList = CalculateImprintingBonus(imprintingBonusRounded, imprintingBonusMultiplier, cuddleIntervalMultiplier, stats, speciesI, statIOs[7].Input, statIOs[3].Input); } } for (int IBi = 0; IBi < imprintingBonusList.Count; IBi++) { imprintingBonus = imprintingBonusList[IBi]; if (!allowMoreThanHundredImprinting && imprintingBonus > 1) { imprintingBonus = 1; } if (imprintingBonus < 0) { imprintingBonus = 0; } imprintingChanged = (Math.Abs(imprintingBonusRounded - imprintingBonus) > 0.01); double imprintingMultiplier = (1 + imprintingBonus * imprintingBonusMultiplier * .2); levelWildSum = (int)Math.Round((statIOs[7].Input / imprintingMultiplier - (postTamed ? stats[7].AddWhenTamed : 0) - stats[7].BaseValue) / (stats[7].BaseValue * stats[7].IncPerWildLevel), 0); levelDomSum = Math.Max(0, level - 1 - levelWildSum); levelsUndeterminedWild = levelWildSum; levelsUndeterminedDom = levelDomSum; if (bred) { // bred creatures always have 100% TE lowerTEBound = 1; upperTEBound = 1; } // check all possible level-combinations for (int s = 0; s < 8; s++) { if (stats[s].BaseValue > 0 && activeStats[s]) // if stat is used (oxygen sometimes is not) { statIOs[s].postTame = postTamed; // double precision makes it necessary to give a bit more tolerance (hence 0.050001 instead of just 0.05 etc.) MinMaxDouble inputValue = new MinMaxDouble(statIOs[s].Input - (Utils.precision(s) == 3 ? 0.00050001 : 0.050001), statIOs[s].Input + (Utils.precision(s) == 3 ? 0.00050001 : 0.050001)); double statBaseValue = stats[s].BaseValue; if (postTamed) { statBaseValue *= (s == 0 ? (double)Values.V.species[speciesI].TamedBaseHealthMultiplier : 1); } bool withTEff = (postTamed && stats[s].MultAffinity > 0); if (withTEff) { statsWithTE.Add(s); } int minLW = 0, maxLW = 0; 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 && stats[s].BaseValue > 0 && 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 (Stats.calculateValue(speciesI, s, ww, 0, true, lowerTEBound, 0) <= 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; } double statImprintingMultiplier = (!bred || s == 1 || s == 2 || (s == 6 && Values.V.species[speciesI].NoImprintingForSpeed == true) ? 1 : imprintingMultiplier); // if dom levels have no effect, just calculate the wild level if (stats[s].IncPerTamedLevel == 0) { if (minLW == -1) { results[s].Add(new StatResult(-1, 0, inputValue.Mean)); } else { MinMaxDouble lwRange = new MinMaxDouble(((inputValue.Min / (postTamed ? 1 + stats[s].MultAffinity : 1) - (postTamed ? stats[s].AddWhenTamed : 0)) / (statBaseValue * statImprintingMultiplier) - 1) / stats[s].IncPerWildLevel, ((inputValue.Max / (postTamed ? 1 + stats[s].MultAffinity : 1) - (postTamed ? stats[s].AddWhenTamed : 0)) / (statBaseValue * statImprintingMultiplier) - 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) double valueWODom = statBaseValue * (1 + stats[s].IncPerWildLevel * lw) * statImprintingMultiplier + (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 / (valueWODom * (postTamed ? 1 + stats[s].MultAffinity : 1)) - 1) / stats[s].IncPerTamedLevel, (inputValue.Max / (valueWODom * (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 dependant 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) - valueWODom) / (valueWODom * stats[s].MultAffinity), (inputValue.Max / (1 + stats[s].IncPerTamedLevel * ld) - valueWODom) / (valueWODom * 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((int)Math.Ceiling(levelPostTame / (1 + tamingEffectiveness.Max / 2)), (int)Math.Ceiling(levelPostTame / (1 + tamingEffectiveness.Min / 2))); 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 dependant 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-dependant stats) if (statsWithTE.Count > 1) { bool TEExistant = false; for (int er = 0; er < results[statsWithTE[0]].Count; er++) { if (tamingEffectiveness.Overlaps(results[statsWithTE[0]][er].TE)) { TEExistant = true; break; } } if (!TEExistant) { continue; } } } results[s].Add(new StatResult(lw, ld, inputValue.Mean, tamingEffectiveness)); } } } } else { results[s].Add(new StatResult(-1, 0)); } } 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 break; } else if (IBi < imprintingBonusList.Count - 1) { // not all stats got a result, clear results for the next round Clear(); validResults = true; } } } }
/// <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) { var stats = species.stats; ValidResults = true; imprintingChanged = false; considerWildLevelSteps = considerWildLevelSteps && !bred && species.name.Substring(0, 3) != "Tek" && species.name != "Jerboa" ; _bred = bred; PostTamed = bred || tamed; List <MinMaxDouble> imprintingBonusList = null; if (bred) { if (!adjustImprinting) { imprintingBonusList = new List <MinMaxDouble> { new MinMaxDouble(imprintingBonusRounded) }; } else { imprintingBonusList = CalculateImprintingBonus(species, imprintingBonusRounded, imprintingBonusMultiplier, statIOs[(int)StatNames.Torpidity].Input, statIOs[(int)StatNames.Food].Input); } } if (imprintingBonusList == null) { imprintingBonusList = new List <MinMaxDouble> { new MinMaxDouble(0) } } ; for (int IBi = 0; IBi < imprintingBonusList.Count; IBi++) { _imprintingBonusRange = imprintingBonusList[IBi]; // don't cut off too much possible values, consider a margin of 0.01 to not sort out possible correct values _imprintingBonusRange.SetToIntersectionWith(-.01, allowMoreThanHundredImprinting ? 5 : 1.01); // it's assumed that a valid IB will not be larger than 500% ImprintingBonus = Math.Max(0, Math.Min(allowMoreThanHundredImprinting ? 5 : 1, _imprintingBonusRange.Mean)); 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)); } } 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)); } } // 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)); } } else { Results[s].Add(new StatResult(lw, 0)); } } 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, 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; } } } }