protected bool ChangeArms(Creature target, StringBuilder sb)
        {
            ArmData oldArms = target.arms.AsReadOnlyData();

            if (target.RestoreArms())
            {
                sb.Append(RestoredArmsText(target, oldArms));
                return(true);
            }
            return(false);
        }
Example #2
0
 private static string ChangedArmsText(Creature target, ArmData oldArmData)
 {
     return(GlobalStrings.NewParagraph() + "Weakness overcomes your arms, and no matter what you do, you can't muster the strength to raise or move them."
            + " Did the pepper have some drug-like effects? Sitting on the ground, you wait for the limpness to end."
            + " As you do so, you realize that the bones at your hands are changing, as well as the muscles on your arms."
            + " They're soon covered, from the shoulders to the tip of your digits, on a layer of soft,"
            + " fluffy " + target.ActiveHairOrFurColor().AsString() + " fur."
            + " Your hands gain pink, padded paws where your palms were once, and your nails become short claws,"
            + " not sharp enough to tear flesh, but nimble enough to make climbing and exploring easier. "
            + SafelyFormattedString.FormattedText("Your arms have become like those of a dog!", StringFormats.BOLD));
 }
Example #3
0
 protected override string RestoredArmsText(Creature creature, ArmData oldArms)
 {
     if (oldArms.hands.isHands)
     {
         return("Your hands shake and shudder as they slowly transform back into normal human hands. The change radiates through your arms as well, " +
                "finally stopping when they return to normal too.");
     }
     else
     {
         return("Your arms twitch, then slowly shift towards something more normal until they match your human hands.");
     }
 }
Example #4
0
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            int changeLimit      = GenerateChangeCount(target, new int[] { 2, 2 });
            int remainingChanges = changeLimit;

            //crit is our modifier for basically all stats. 1 is default, though any non-standard will proc the non-default
            //standard also has a 15% chance of proccing it too.
            int crit = 1;

            if (modifiers > CanineModifiers.STANDARD || Utils.Rand(20) < 3)
            {
                crit = Utils.Rand(20) / 10 + 2;
            }
            bool hasCrit() => crit > 1;

            StringBuilder sb = new StringBuilder();

            bool DoKnotChanges(double delta)
            {
                if (target.hasCock)
                {
                    bool changed      = false;
                    int  dogCockCount = target.genitals.CountCocksOfType(CockType.DOG);
                    if (dogCockCount > 0)
                    {
                        C**k smallestKnot = target.cocks.MinItem(x => x.type == CockType.DOG ? (double?)x.knotMultiplier : null);
                        if (smallestKnot.knotMultiplier > 2)
                        {
                            delta /= 10;
                        }
                        else if (smallestKnot.knotMultiplier > 1.75)
                        {
                            delta /= 5;
                        }
                        else if (smallestKnot.knotMultiplier > 1.5)
                        {
                            delta /= 2;
                        }

                        double knotMultiplierDelta = smallestKnot.IncreaseKnotMultiplier(delta);
                        if (knotMultiplierDelta != 0)
                        {
                            sb.Append(EnlargedSmallestKnotText(target, target.cocks.IndexOf(smallestKnot), knotMultiplierDelta, dogCockCount > 1));
                            changed = true;
                        }
                    }


                    target.DeltaCreatureStats(sens: 0.5, lus: 5 * crit);
                    return(changed);
                }
                return(false);
            }

            sb.Append(InitialTransformationText(modifiers, hasCrit()));

            //bad end related checks
            if (hasCrit() && target.body.hasActiveFurData && target.face.type == FaceType.DOG && target.ears.type == EarType.DOG &&
                target.lowerBody.type == LowerBodyType.DOG && target.tail.type == TailType.DOG && target is IExtendedCreature extended)
            {
                //can get bad end.
                if (extended.extendedData.hasDoggoWarning && !extended.extendedData.resistsTFBadEnds)
                {
                    //bad end.
                    if (Utils.RandBool())
                    {
                        sb.Append(BadEndText(target));
                        isBadEnd = true;
                        return(ApplyChangesAndReturn(target, sb, 0));
                    }
                    //get lucky, but warn that they got lucky
                    else
                    {
                        sb.Append(DoggoWarningText(target, true));
                    }
                }
                //not warned
                else if (!extended.extendedData.hasDoggoWarning)
                {
                    //warn
                    extended.extendedData.hasDoggoWarning = true;
                    sb.Append(DoggoWarningText(target, false));
                }
            }
            //stat changes
            if (modifiers.HasFlag(CanineModifiers.BLACK))
            {
                target.IncreaseCreatureStats(lus: (byte)(5 + Utils.Rand(5)), lib: (byte)(2 + Utils.Rand(4)), corr: (byte)(2 + Utils.Rand(4)));
            }
            //stat changes (cont.)
            double strengthIncrease     = 0;
            double speedIncrease        = 0;
            double intelligenceDecrease = 0;

            if (target.relativeStrength < 50 && Utils.Rand(3) == 0)
            {
                strengthIncrease = target.IncreaseStrength(crit);
            }
            if (target.relativeSpeed < 30 && Utils.Rand(3) == 0)
            {
                speedIncrease = target.IncreaseSpeed(crit);
            }
            if (target.relativeIntelligence > 30 && Utils.Rand(3) == 0)
            {
                intelligenceDecrease = target.DecreaseIntelligence(crit);
            }

            sb.Append(StatChangeText(target, strengthIncrease, speedIncrease, intelligenceDecrease));

            //modifier effects (no cost)

            //double pepper
            if (modifiers.HasFlag(CanineModifiers.DOUBLE))
            {
                int dogCocks = target.genitals.CountCocksOfType(CockType.DOG);
                //already has 2+ dog cocks.
                if (dogCocks >= 2)
                {
                    //just treat it like a large. so we'll just bitwise or the
                    //large flag in.
                    modifiers |= CanineModifiers.LARGE;
                }
                //has no dog cocks.
                else if (dogCocks == 0)
                {
                    //has no cocks. - grow 2
                    if (target.cocks.Count == 0)
                    {
                        target.AddCock(CockType.DOG, 7 + Utils.Rand(7), 1.5 + Utils.Rand(10) / 10, 1.7);
                        target.AddCock(CockType.DOG, 7 + Utils.Rand(7), 1.5 + Utils.Rand(10) / 10, 1.7);

                        sb.Append(GrewTwoDogCocksHadNone(target));
                    }
                    //has one c**k. - grow one, change one
                    else if (target.cocks.Count == 1)
                    {
                        CockData oldCockData = target.cocks[0].AsReadOnlyData();

                        target.genitals.UpdateCockWithKnot(0, CockType.DOG, 1.5);
                        if (target.cocks[0].length < 10)
                        {
                            target.cocks[0].SetLength(10);
                        }
                        target.AddCock(CockType.DOG, 7 + Utils.Rand(7), 1.5 + Utils.Rand(10) / 10.0, 1.7);

                        sb.Append(ConvertedFirstDogCockGrewSecond(target, oldCockData));
                    }
                    //has 2+ cocks. -change 2
                    else
                    {
                        CockData firstOldData  = target.cocks[0].AsReadOnlyData();
                        CockData secondOldData = target.cocks[1].AsReadOnlyData();

                        target.genitals.UpdateCockWithKnot(0, CockType.DOG, 1.5);
                        if (target.cocks[0].length < 10)
                        {
                            target.cocks[0].SetLength(10);
                        }
                        target.genitals.UpdateCockWithKnot(1, CockType.DOG, 1.5);
                        if (target.cocks[1].length < 10)
                        {
                            target.cocks[1].SetLength(10);
                        }
                        sb.Append(ConvertedTwoCocksToDog(target, firstOldData, secondOldData));
                    }
                }
                //one dog c**k.
                else
                {
                    if (target.cocks.Count == 1)
                    {
                        target.AddCock(CockType.DOG, 7 + Utils.Rand(7), 1.5 + Utils.Rand(10) / 10.0, 1.7);
                        sb.Append(GrewSecondDogCockHadOne(target));
                    }
                    else
                    {
                        if (target.cocks[0].type == CockType.DOG)
                        {
                            CockData oldData = target.cocks[1].AsReadOnlyData();
                            target.genitals.UpdateCockWithKnot(1, CockType.DOG, 1.5);
                            if (target.cocks[1].length < 10)
                            {
                                target.cocks[1].SetLength(10);
                            }
                            sb.Append(ConvertedOneCockToDogHadOne(target, 1, oldData));
                        }
                        else
                        {
                            CockData oldData = target.cocks[0].AsReadOnlyData();

                            target.genitals.UpdateCockWithKnot(0, CockType.DOG, 1.5);
                            if (target.cocks[0].length < 10)
                            {
                                target.cocks[0].SetLength(10);
                            }
                            sb.Append(ConvertedOneCockToDogHadOne(target, 0, oldData));
                        }
                    }
                }

                target.IncreaseCreatureStats(lib: 2, lus: 50);
            }

            //there are two ways to proc this - either via a knotty modifier (written here), or via random chance and/or with a large pepper (written later)
            //however, a knotty pepper modifier has no tf cost, the other check does. also, this will modify a c**k if none have a dog knot, the other will not.
            if (modifiers.HasFlag(CanineModifiers.KNOTTY))
            {
                double delta = ((Utils.Rand(2) + 5) / 20) * crit;

                if (!DoKnotChanges(delta))
                {
                    if (target.hasCock)
                    {
                        CockData oldCockData = target.cocks[0].AsReadOnlyData();

                        double knotSize = 1.75;

                        if (target.cocks[0].hasKnot)
                        {
                            knotSize = Math.Max(2.1, target.cocks[0].knotMultiplier);
                        }

                        target.genitals.UpdateCockWithKnot(0, CockType.DOG, knotSize);
                        if (target.cocks[0].length < 10)
                        {
                            target.cocks[0].SetLength(10);
                        }
                        sb.Append(ConvertedOneCockToDog(target, 0, oldCockData));
                    }


                    else
                    {
                        sb.Append(WastedKnottyText(target));
                    }
                }
            }

            //bulby
            if (modifiers.HasFlag(CanineModifiers.BULBY))
            {
                if (!target.hasBalls)
                {
                    target.genitals.GrowBalls(2, 1);
                    target.DeltaCreatureStats(lib: 2, lus: -10);
                    sb.Append(GrewBallsText(target));
                }
                else if (target.balls.uniBall)
                {
                    BallsData oldData = target.balls.AsReadOnlyData();
                    target.genitals.ConvertToNormalBalls();
                    target.DeltaCreatureStats(lib: 1, lus: 1);

                    sb.Append(EnlargedBallsText(target, oldData));
                }
                else
                {
                    BallsData oldData = target.balls.AsReadOnlyData();

                    byte enlargeAmount = target.genitals.balls.EnlargeBalls(1);
                    target.IncreaseCreatureStats(lib: 1, lus: 3);

                    sb.Append(EnlargedBallsText(target, oldData));
                }
            }

            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
            }

            //tfs (cost 1).

            //restore neck
            if (target.neck.type != NeckType.defaultValue && Utils.Rand(4) == 0)
            {
                NeckData oldNeck = target.neck.AsReadOnlyData();
                target.RestoreNeck();

                sb.Append(RestoredNeckText(target, oldNeck));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                }
            }

            //remove oviposition
            if (target.womb.canRemoveOviposition && Utils.Rand(5) == 0)
            {
                if (target.womb.ClearOviposition())
                {
                    sb.Append(RemovedOvipositionText(target));
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                    }
                }
            }

            //remove feather-hair
            if (RemoveFeatheryHair(target))
            {
                sb.Append(RemovedFeatheryHairText(target));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                }
            }

            //knot multiplier (but not knotty)
            if (!modifiers.HasFlag(CanineModifiers.KNOTTY) && target.genitals.CountCocksOfType(CockType.DOG) > 0 && (modifiers.HasFlag(CanineModifiers.LARGE) || Utils.Rand(7) < 5))
            {
                double delta = ((Utils.Rand(2) + 1) / 20.0) * crit;
                if (DoKnotChanges(delta))
                {
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                    }
                }
            }

            //transform a c**k.
            if (target.genitals.CountCocksOfType(CockType.DOG) < target.cocks.Count && (modifiers.HasFlag(CanineModifiers.LARGE) || Utils.Rand(8) < 5))
            {
                C**k nonDoggo = target.cocks.FirstOrDefault(x => x.type != CockType.DOG && x.type != CockType.DEMON);                 //find any c**k that isn't a dog, but also isn't a demon c**k.
                if (nonDoggo != null)
                {
                    CockData oldData = nonDoggo.AsReadOnlyData();
                    int      index   = target.cocks.IndexOf(nonDoggo);
                    target.genitals.UpdateCock(index, CockType.DOG);

                    sb.Append(ConvertedOneCockToDog(target, index, oldData));


                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                    }
                }
                else
                {
                    C**k demonSpecialCase = target.cocks.FirstOrDefault(x => x.type == CockType.DEMON);
                    int  index            = demonSpecialCase.cockIndex;
                    if (demonSpecialCase != null)
                    {
                        double delta = demonSpecialCase.IncreaseThickness(2);

                        if (delta != 0)
                        {
                            sb.Append(CouldntConvertDemonCockThickenedInstead(target, index, delta));
                            if (--remainingChanges <= 0)
                            {
                                return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                            }
                        }
                    }
                }
            }

            //update cum. now, if it reaches the cap, it simply uses the new flat amount adder (up to a certain point). it does not use the messy o****m perk
            //anymore because i've tried to remove calls to random perks because it's not very future-proof - what happens when a new perk is added that has a
            //similar effect? do we add that check literally everywhere too? (of course, if a perk is unique enough that nothing else will act in the same way,
            //that's fine. i dunno, it's difficult to try and clean everything up without being able to predict the future, which is admittedly impossible)
            if (target.hasCock && (target.genitals.cumMultiplier < 1.5 || target.genitals.additionalCum < 500) && Utils.RandBool())
            {
                double delta;
                bool   wasMultiplier;
                if (target.genitals.cumMultiplier < 1.5)
                {
                    delta         = target.genitals.IncreaseCumMultiplier(0.05 * crit);
                    wasMultiplier = true;
                }
                else
                {
                    delta         = target.genitals.AddFlatCumAmount(50);
                    wasMultiplier = false;
                }
                sb.Append(AddedCumText(target, delta, wasMultiplier));
            }

            if (target.hasCock && modifiers.HasFlag(CanineModifiers.LARGE))
            {
                C**k     smallest = target.genitals.ShortestCock();
                CockData oldData  = smallest.AsReadOnlyData();

                smallest.IncreaseLength(Utils.Rand(4) + 3);
                if (smallest.girth < 1)
                {
                    double delta = 1 - smallest.girth;
                    smallest.IncreaseThickness(delta);
                }
                sb.Append(GrewSmallestCockText(target, smallest.cockIndex, oldData));
            }

            //do female changes.

            //if we have a vag and > flat breasts.
            if (target.hasVagina && target.genitals.BiggestCupSize() > CupSize.FLAT)
            {
                byte breastCount = (byte)target.breasts.Count;
                if (breastCount < 3)
                {
                    BreastCollectionData oldBreastData = target.genitals.allBreasts.AsReadOnlyData();

                    //in vanilla code, we had some strange checks here that required the first row (and only the first row) be a certain size.
                    //now, we check all the rows, but no longer require any of them to be a certain size. instead, if it's not the right size, we make it that size,
                    //then add the row. so, for two rows, your first row must be at least a B-Cup. for 3: first must be a C cup, second an A cup.
                    //if we supported 4 rows, it'd be D, B, A.

                    for (int x = 0; x < target.breasts.Count; x++)
                    {
                        CupSize requiredSize = (CupSize)(byte)(target.breasts.Count - x);
                        if (x == 0)
                        {
                            requiredSize++;
                        }
                        if (target.breasts[x].cupSize < requiredSize)
                        {
                            target.breasts[x].SetCupSize(requiredSize);
                        }
                    }

                    target.genitals.AddBreastRow(target.breasts[breastCount - 1].cupSize.ByteEnumSubtract(1));


                    bool doCrit = false, uberCrit = false;
                    if (target.breasts.Count == 2)
                    {
                        target.IncreaseCreatureStats(lus: 5, sens: 6);
                    }
                    else if (hasCrit())
                    {
                        doCrit = true;
                        if (crit > 2)
                        {
                            target.IncreaseCreatureStats(sens: 6, lus: 15);
                            uberCrit = true;
                        }
                        else
                        {
                            target.IncreaseCreatureStats(sens: 3, lus: 10);
                        }
                    }

                    sb.Append(UpdateAndGrowAdditionalRowText(target, oldBreastData, doCrit, uberCrit));


                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                    }
                }
                else
                {
                    BreastCollectionData oldBreastData = target.genitals.allBreasts.AsReadOnlyData();

                    //only call the normalize text if we actually did anything. related, anthro breasts now returns a bool. thanks, fox tfs.
                    if (target.genitals.AnthropomorphizeBreasts())
                    {
                        sb.Append(NormalizedBreastSizeText(target, oldBreastData));
                    }
                }
            }

            //Go into heat
            if (EnterHeat(target, out bool increased))
            {
                sb.Append(EnterOrIncreaseHeatText(target, increased));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                }
            }

            //doggo fantasies
            if (target.DogScore() > 3 && Utils.Rand(4) == 0)
            {
                sb.Append(DoggoFantasyText(target));
                target.IncreaseLust(5 + (target.libidoTrue / 20));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                }
            }

            //doggo tfs.

            if (!target.eyes.isDefault && Utils.Rand(5) == 0)
            {
                EyeData oldData = target.eyes.AsReadOnlyData();

                target.RestoreEyes();

                sb.Append(RestoredEyesText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                }
            }

            if (modifiers.HasFlag(CanineModifiers.BLACK) && !target.body.mainEpidermis.fur.IsIdenticalTo(HairFurColors.BLACK))
            {
                //for now, we're ignoring underbody, apparently.
                if (target.body.type != BodyType.SIMPLE_FUR)
                //if (target.body.mainEpidermis.currentType != EpidermisType.FUR)
                {
                    BodyData oldBodyData = target.body.AsReadOnlyData();

                    target.UpdateBody(BodyType.SIMPLE_FUR, new FurColor(HairFurColors.BLACK), FurTexture.THICK);

                    sb.Append(ChangedBodyTypeText(target, oldBodyData));
                }
                else
                {
                    ReadOnlyFurColor oldFur = target.body.mainEpidermis.fur.AsReadOnly();
                    target.body.ChangeMainFur(new FurColor(HairFurColors.MIDNIGHT_BLACK), FurTexture.THICK);
                    sb.Append(ChangedFurText(target, oldFur));
                }

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                }
            }
            //again, we're ignoring underbody for now, idk.
            else if (target.lowerBody.type == LowerBodyType.DOG && target.tail.type == TailType.DOG &&
                     //target.body.mainEpidermis.currentType != EpidermisType.FUR
                     target.body.type != BodyType.SIMPLE_FUR &&
                     Utils.Rand(4) == 0)
            {
                BodyData oldBodyData = target.body.AsReadOnlyData();

                FurColor oldFur = target.body.mainEpidermis.fur;

                target.UpdateBody(BodyType.SIMPLE_FUR, Utils.RandomChoice(Species.DOG.availableColors));

                sb.Append(ChangedBodyTypeText(target, oldBodyData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                }
            }

            if (target.lowerBody.type != LowerBodyType.DOG && target.tail.type == TailType.DOG && target.ears.type == EarType.DOG && Utils.Rand(3) == 0)
            {
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();

                target.UpdateLowerBody(LowerBodyType.DOG);
                sb.Append(ChangedLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                }
            }

            if (target.ears.type != EarType.DOG && target.tail.type == TailType.DOG && Utils.RandBool())
            {
                EarData oldData = target.ears.AsReadOnlyData();

                target.UpdateEars(EarType.DOG);

                sb.Append(ChangedEarsText(target, oldData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                }
            }

            if (target.tail.type != TailType.DOG && Utils.Rand(3) == 0)
            {
                TailData oldData = target.tail.AsReadOnlyData();

                target.UpdateTail(TailType.DOG);

                sb.Append(ChangedTailText(target, oldData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                }
            }

            if (target.arms.type != ArmType.DOG && target.body.isFurry && target.tail.type == TailType.DOG &&
                target.lowerBody.type == LowerBodyType.DOG && Utils.Rand(4) == 0)
            {
                ArmData oldArmData = target.arms.AsReadOnlyData();

                target.UpdateArms(ArmType.DOG);

                sb.Append(ChangedArmsText(target, oldArmData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                }
            }

            if (!target.gills.isDefault && Utils.Rand(4) == 0)
            {
                GillData oldGillData = target.gills.AsReadOnlyData();

                target.RestoreGills();

                sb.Append(RemovedGillsText(target, oldGillData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                }
            }

            if (target.body.isFurry && Utils.Rand(3) == 0)
            {
                target.DeltaCreatureStats(tou: 4, sens: -3);


                sb.Append(FallbackToughenUpText(target));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
                }
            }

            if (target is CombatCreature cc2 && remainingChanges == changeLimit)
            {
                cc2.AddHP(20);
                sb.Append(NothingHappenedGainHpText(target));
                target.IncreaseLust(3);
            }

            return(ApplyChangesAndReturn(target, sb, changeLimit - remainingChanges));
        }
 protected virtual string RestoredArmsText(Creature creature, ArmData oldArms)
 {
     return(creature.arms.RestoredText(oldArms));
 }
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            //huh. no rng rolls, just starts at 3. cool
            int changeCount      = GenerateChangeCount(target, null, 3);
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

            //For all of these, any text regarding the transformation should be instead abstracted out as an abstract string function. append the result of this abstract function
            //to the string builder declared above (aka sb.Append(FunctionCall(variables));) string builder is just a fancy way of telling the compiler that you'll be creating a
            //long string, piece by piece, so don't do any crazy optimizations first.

            //the initial text for starting the transformation. feel free to add additional variables to this if needed.
            sb.Append(InitialTransformationText(target));

            //Add any free changes here - these can occur even if the change count is 0. these include things such as change in stats (intelligence, etc)
            //change in height, hips, and/or butt, or other similar stats.

            //this will handle the edge case where the change count starts out as 0.
            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //Any transformation related changes go here. these typically cost 1 change. these can be anything from body parts to gender (which technically also changes body parts,
            //but w/e). You are required to make sure you return as soon as you've applied changeCount changes, but a single line of code can be applied at the end of a change to do
            //this for you.

            //paste this line after any tf is applied, and it will: automatically decrement the remaining changes count. if it becomes 0 or less, apply the total number of changes
            //underwent to the target's change count (if applicable) and then return the StringBuilder content.
            //if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);

            //Stats and genital changes
            if (Utils.Rand(2) == 0)
            {
                target.ChangeLust(25);
                if (target.relativeLibido < 100)
                {
                    if (target.relativeLibido < 50)
                    {
                        target.ChangeLibido(1);
                    }

                    target.ChangeLibido(1);
                }
            }
            C**k smallest = target.genitals.ShortestCock();

            if (target.hasCock && smallest.length < 12 && Utils.Rand(3) == 0)
            {
                smallest.IncreaseLength();

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            C**k thinnest = target.genitals.ThinnestCock();

            if (target.hasCock && thinnest.girth < 4 && Utils.Rand(3) == 0)
            {
                thinnest.IncreaseThickness(0.5);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (target.balls.count > 0 && target.genitals.cumMultiplier < 50 && Utils.Rand(3) == 0)
            {
                target.ChangeLust(20);

                if (target.genitals.cumMultiplier < 10)
                {
                    target.genitals.IncreaseCumMultiplier(1);
                }

                if (target.genitals.cumMultiplier < 50)
                {
                    target.genitals.IncreaseCumMultiplier(0.5);
                }

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(3) == 0 && target.hasVagina && target.genitals.standardBonusVaginalCapacity > 0)
            {
                target.genitals.DecreaseBonusVaginalCapacity((ushort)(Utils.Rand(5) + 5));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(3) == 0 && target.hasVagina && !target.hasCock)
            {
                target.RemoveAllVaginas();
                target.AddCock(CockType.HUMAN, 6, 1);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(3) == 0 && target.hasCock && !target.balls.hasBalls)
            {
                target.balls.GrowBalls();

                target.HaveGenericCockOrgasm(0, true, true);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Transformations
            //Neck restore
            if (target.neck.type != NeckType.HUMANOID && Utils.Rand(4) == 0)
            {
                target.RestoreNeck();
            }
            //Rear body restore
            if (!target.back.isDefault && Utils.Rand(5) == 0)
            {
                target.RestoreBack();
            }
            //Ovi perk loss
            if (target.womb.canRemoveOviposition && Utils.Rand(5) == 0)
            {
                if (target.womb.ClearOviposition())
                {
                    sb.Append(RemovedOvipositionText(target));
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            //remove anything with a scaly or partially scaly body. now affects cockatrice too!
            if (Utils.Rand(3) == 0 && target.body.HasAny(EpidermisType.SCALES))
            {
                BodyData oldData = target.body.AsReadOnlyData();
                target.RestoreBody();
                sb.Append(RestoredBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(3) == 0 && target.arms.type != ArmType.HUMAN)
            {
                ArmData oldData = target.arms.AsReadOnlyData();
                target.RestoreArms();
                sb.Append(RestoredArmsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(4) == 0 && target.lowerBody.type != LowerBodyType.CLOVEN_HOOVED)
            {
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                target.UpdateLowerBody(LowerBodyType.CLOVEN_HOOVED);
                sb.Append(UpdateLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(3) == 0 && target.lowerBody.type == LowerBodyType.CLOVEN_HOOVED && target.horns.type == HornType.GOAT && target.face.type != FaceType.HUMAN)
            {
                FaceData oldData = target.face.AsReadOnlyData();
                target.UpdateFace(FaceType.HUMAN);
                sb.Append(UpdateFaceText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //MOD: anything with scales will prevent this, including partial scales (a la cockatrice). since body is reworked a lot of these checks are hard to port,
            //but this i think is the closest i can get.
            if (Utils.Rand(4) == 0 && !target.body.HasAny(EpidermisType.SCALES) && target.ears.type != EarType.ELFIN)
            {
                EarData oldData = target.ears.AsReadOnlyData();
                target.UpdateEars(EarType.ELFIN);
                sb.Append(UpdateEarsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(3) == 0 && target.horns.type == HornType.NONE)
            {
                HornData oldData = target.horns.AsReadOnlyData();
                target.UpdateHorns(HornType.GOAT);
                sb.Append(UpdateHornsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Mod Note: Horns:

            if (Utils.Rand(3) == 0 && target.horns.type != HornType.GOAT)
            {
                HornData oldData = target.horns.AsReadOnlyData();
                target.UpdateHorns(HornType.GOAT);
                sb.Append(UpdateHornsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(3) == 0 && target.horns.type == HornType.GOAT && target.horns.CanStrengthen)
            {
                target.horns.StrengthenTransform();

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(4) == 0 && target.antennae.type != AntennaeType.NONE)
            {
                AntennaeData oldData = target.antennae.AsReadOnlyData();
                target.RestoreAntennae();
                sb.Append(RestoredAntennaeText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(3) == 0 && target.cocks.Count == 1 && target.cocks[0].type != CockType.HUMAN)
            {
                target.genitals.UpdateCock(0, CockType.HUMAN);
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(3) == 0 && target.cocks.Count > 1 && !target.genitals.OnlyHasCocksOfType(CockType.HUMAN))
            {
                target.genitals.UpdateCock(target.cocks.First(x => x.type != CockType.HUMAN), CockType.HUMAN);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(3) == 0 && target.tail.type != TailType.SATYR)
            {
                TailData oldData = target.tail.AsReadOnlyData();
                target.UpdateTail(TailType.SATYR);
                sb.Append(UpdateTailText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //this is the fallthrough that occurs when a tf item goes through all the changes, but does not proc enough of them to exit early. it will apply however many changes
            //occurred, then return the contents of the stringbuilder.
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }
Example #7
0
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            int changeCount      = GenerateChangeCount(target, new int[] { 2, 2, 3, 4 });
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

#warning fix me
            int ngPlus(int value) => value;

            int currentChanges() => changeCount - remainingChanges;

            //For all of these, any text regarding the transformation should be instead abstracted out as an abstract string function. append the result of this abstract function
            //to the string builder declared above (aka sb.Append(FunctionCall(variables));) string builder is just a fancy way of telling the compiler that you'll be creating a
            //long string, piece by piece, so don't do any crazy optimizations first.

            //the initial text for starting the transformation. feel free to add additional variables to this if needed.
            sb.Append(InitialTransformationText(target));

            //Add any free changes here - these can occur even if the change count is 0. these include things such as change in stats (intelligence, etc)
            //change in height, hips, and/or butt, or other similar stats.

            //this will handle the edge case where the change count starts out as 0.
            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //Any transformation related changes go here. these typically cost 1 change. these can be anything from body parts to gender (which technically also changes body parts,
            //but w/e). You are required to make sure you return as soon as you've applied changeCount changes, but a single line of code can be applied at the end of a change to do
            //this for you.

            //paste this line after any tf is applied, and it will: automatically decrement the remaining changes count. if it becomes 0 or less, apply the total number of changes
            //underwent to the target's change count (if applicable) and then return the StringBuilder content.
            //if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);

            //clear screen

            //Statistical changes:
            //-Reduces speed down to 50.
            if (target.speed > ngPlus(50) && Utils.Rand(4) == 0)
            {
                target.ChangeSpeed(-1);
            }
            //-Raises toughness to 70
            //(+3 to 40, +2 to 55, +1 to 70)
            if (target.toughness < ngPlus(70) && Utils.Rand(3) == 0)
            {
                //(+3)
                if (target.toughness < ngPlus(40))
                {
                    target.ChangeToughness(3);
                }
                //(+2)
                else if (target.toughness < ngPlus(55))
                {
                    target.ChangeToughness(2);
                }
                //(+1)
                else
                {
                    target.ChangeToughness(1);
                }
            }
            //-Reduces sensitivity.
            if (target.relativeSensitivity > 20 && Utils.Rand(3) == 0)
            {
                target.ChangeSensitivity(-1);
            }
            //Raises libido greatly to 50, then somewhat to 75, then slowly to 100.
            if (target.relativeLibido < 100 && Utils.Rand(3) == 0)
            {
                //+3 lib if less than 50
                if (target.relativeLibido < 50)
                {
                    target.ChangeLibido(1);
                }
                //+2 lib if less than 75
                if (target.relativeLibido < 75)
                {
                    target.ChangeLibido(1);
                }
                //+1 if above 75.
                target.ChangeLibido(1);
            }


            //Sexual Changes:
            //-Lizard dick - first one
            if (target.cocks.Any(x => x.type != CockType.LIZARD) && target.cocks.Count > 0 && ScalingChance(2, currentChanges(), 4))
            {
                //Find the first non-lizzy dick
                C**k firstNonLizard = target.cocks.First(x => x.type != CockType.LIZARD);
                //Actually xform it nau
                target.genitals.UpdateCock(firstNonLizard, CockType.LIZARD);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }

                target.DeltaCreatureStats(lib: 3, lus: 10);
            }
            //(CHANGE OTHER DICK)
            int lizardCocks = target.genitals.CountCocksOfType(CockType.LIZARD);
            //Requires 1 lizard c**k, multiple cocks
            if (target.hasCock && (lizardCocks != target.cocks.Count || target.cocks.Count == 1) && ScalingChance(2, currentChanges(), 4))
            {
                if (target.cocks.Count > 1)
                {
                    C**k nonLizard = target.cocks.First(x => x.type != CockType.LIZARD);

                    target.genitals.UpdateCock(nonLizard, CockType.LIZARD);

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }

                    target.DeltaCreatureStats(lib: 3, lus: 10);
                }
                else
                {
                    target.genitals.AddCock(CockType.LIZARD, target.cocks[0].length, target.cocks[0].girth);

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }

                    target.DeltaCreatureStats(lib: 3, lus: 10);
                }
            }

            //MOD NOTE: REMOVED. SEE RANT IN SALAMANDER TRANSFORMATIONS

            //--Worms leave if 100% lizard dicks?
            //Require mammals?
            //if (target.genitals.CountCocksOfType(CockType.LIZARD) == target.cocks.Count && target.hasStatusEffect(StatusEffects.Infested))
            //{
            //	target.removeStatusEffect(StatusEffects.Infested);
            //	if (--remainingChanges <= 0)
            //	{
            //		return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);
            //	}
            //}

            //-Breasts vanish to 0 rating if male
            if (target.genitals.BiggestCupSize() > target.genitals.smallestPossibleMaleCupSize && target.gender == Gender.MALE && ScalingChance(2, currentChanges(), 3))
            {
                //(HUEG)
                foreach (Breasts breast in target.breasts)
                {
                    if (breast.cupSize > CupSize.E_BIG)
                    {
                        breast.ShrinkBreasts(((byte)breast.cupSize).div(2));
                    }
                    else
                    {
                        breast.SetCupSize(target.genitals.smallestPossibleMaleCupSize);
                    }
                }
                //(+2 speed)
                target.IncreaseSpeed(2);
                target.ChangeLibido(2);
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Lactation stoppage.
            if (target.genitals.isLactating && ScalingChance(2, currentChanges(), 4))
            {
                if (target.HasPerk <Feeder>())
                {
                    target.RemovePerk <Feeder>();
                }
                target.genitals.SetLactationTo(LactationStatus.NOT_LACTATING);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Nipples reduction to 1 per tit.
            if (target.genitals.hasQuadNipples && ScalingChance(2, currentChanges(), 4))
            {
                target.genitals.SetQuadNipples(false);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Remove extra breast rows
            if (target.breasts.Count > 1 && ScalingChance(2, currentChanges(), 3) && !hyperHappy)
            {
                target.RemoveExtraBreastRows();

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-VAGs
            if (target.hasVagina && target.womb.canObtainOviposition && Species.LIZARD.Score(target) > 3 && ScalingChance(3, currentChanges(), 5))
            {
                target.womb.GrantOviposition();
                sb.Append(GrantOvipositionText(target));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //Physical changes:
            //-Existing horns become draconic, max of 4, max length of 1'
            if (target.horns.type != HornType.DRACONIC && ScalingChance(3, currentChanges(), 5))
            {
                HornData oldData = target.horns.AsReadOnlyData();
                target.UpdateHorns(HornType.DRACONIC);
                sb.Append(UpdateHornsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //Neck restore
            if (target.neck.type != NeckType.HUMANOID && ScalingChance(2, currentChanges(), 4))
            {
                NeckData oldData = target.neck.AsReadOnlyData();
                target.RestoreNeck();
                sb.Append(RestoredNeckText(target, oldData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Rear body restore
            if (!target.back.isDefault && ScalingChance(3, currentChanges(), 5))
            {
                BackData oldData = target.back.AsReadOnlyData();
                target.RestoreBack();
                sb.Append(RestoredBackText(target, oldData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Hair changes
            if (ScalingChance(2, currentChanges(), 4))
            {
                if (target.eyes.type == EyeType.BASILISK)
                {
                    if (target.corruption > 65 && target.face.IsReptilian() && target.body.type == BodyType.REPTILE && target.hair.type != HairType.BASILISK_SPINES)
                    {
                        HairData oldData = target.hair.AsReadOnlyData();
                        target.UpdateHair(HairType.BASILISK_SPINES);
                        sb.Append(UpdateHairText(target, oldData));

                        if (--remainingChanges <= 0)
                        {
                            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                        }
                    }
                    else if (target.corruption < 15 && target.gender.HasFlag(Gender.FEMALE) && target.hair.type != HairType.BASILISK_PLUME)
                    {
                        target.hair.ResumeNaturalGrowth();
                        HairData oldData = target.hair.AsReadOnlyData();
                        target.UpdateHair(HairType.BASILISK_PLUME);
                        sb.Append(UpdateHairText(target, oldData));

                        if (--remainingChanges <= 0)
                        {
                            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                        }
                    }
                }
                else
                {
                    target.hair.StopNaturalGrowth();

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }
            //Remove beard!
#warning BEARDS NOT YET IMPLEMENTED
            //Mod note: Nords are so serious about beards. ... Mai'q thinks they wish they had GLORIOUS pubes like Khajiit.

            //if (target.hasBeard() && ScalingChance(2, currentChanges(), 3))
            //{
            //	target.beard.length = 0;
            //	target.beard.style = 0;
            //}
            //Big physical changes:
            //-Legs – Draconic, clawed feet
            if (target.lowerBody.type != LowerBodyType.LIZARD && ScalingChance(3, currentChanges(), 5))
            {
                //Hooves -
                //TAURS -
                //feet types -
                //Else –
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                target.UpdateLowerBody(LowerBodyType.LIZARD);
                sb.Append(UpdateLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            // <mod name="Predator arms" author="Stadler76">
            //Gain predator arms
            //Claw transition
            //MOD OF MOD (LOL): predator arms are a collection of arm types now, not a single type. thus, we can combine the claw and type checks.
            //now, you get the arms (and thus, the claws) if they aren't a predator type and have lizard legs, or switch to lizard arms from any other predator type.
            if ((target.arms.IsPredatorArms() || target.lowerBody.type == LowerBodyType.LIZARD) && target.body.type == BodyType.REPTILE && ScalingChance(2, currentChanges(), 3))
            {
                ArmData oldData = target.arms.AsReadOnlyData();
                target.UpdateArms(ArmType.LIZARD);
                sb.Append(UpdateArmsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            // </mod>

            //-Tail – sinuous lizard tail
            if (target.tail.type != TailType.LIZARD && target.lowerBody.type == LowerBodyType.LIZARD && ScalingChance(3, currentChanges(), 5))
            {
                //No tail
                //Yes tail
                TailData oldData = target.tail.AsReadOnlyData();
                target.UpdateTail(TailType.LIZARD);
                sb.Append(UpdateTailText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Remove odd eyes
            if (ScalingChance(3, currentChanges(), 5) && target.eyes.type != EyeType.HUMAN && !target.eyes.isReptilian)
            {
                EyeData oldData = target.eyes.AsReadOnlyData();
                target.RestoreEyes();
                sb.Append(RestoredEyesText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Ears become smaller nub-like openings?
            if (target.ears.type != EarType.LIZARD && target.tail.type == TailType.LIZARD && target.lowerBody.type == LowerBodyType.LIZARD && ScalingChance(3, currentChanges(), 5))
            {
                EarData oldData = target.ears.AsReadOnlyData();
                target.UpdateEars(EarType.LIZARD);
                sb.Append(UpdateEarsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Scales – color changes to red, green, white, blue, or black. Rarely: purple or silver.
            if (target.body.type != BodyType.REPTILE && target.ears.type == EarType.LIZARD && target.tail.type == TailType.LIZARD && target.lowerBody.type == LowerBodyType.LIZARD &&
                ScalingChance(3, currentChanges(), 5))
            {
                Species.LIZARD.GetRandomSkinTone(out Tones primary, out Tones underbody);

                target.UpdateBody(BodyType.REPTILE, primary, underbody);

                //kGAMECLASS.rathazul.addMixologyXP(20);
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Lizard-like face.
            if (target.face.type != FaceType.LIZARD && target.body.type == BodyType.REPTILE && target.ears.type == EarType.LIZARD && target.tail.type == TailType.LIZARD &&
                target.lowerBody.type == LowerBodyType.LIZARD && ScalingChance(3, currentChanges(), 5))
            {
                FaceData oldData = target.face.AsReadOnlyData();
                target.UpdateFace(FaceType.LIZARD);
                sb.Append(UpdateFaceText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Lizard tongue
            if (target.tongue.type == TongueType.SNAKE && Utils.Rand(10) < 6)
            {
                // Higher (60%) chance to be 'fixed' if old variant
                TongueData oldData = target.tongue.AsReadOnlyData();
                target.UpdateTongue(TongueType.LIZARD);
                sb.Append(UpdateTongueText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            if (target.tongue.type != TongueType.LIZARD && target.tongue.type != TongueType.SNAKE && target.face.IsReptilian() && ScalingChance(2, currentChanges(), 3))
            {
                TongueData oldData = target.tongue.AsReadOnlyData();
                target.UpdateTongue(TongueType.LIZARD);
                sb.Append(UpdateTongueText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Remove Gills
            if (ScalingChance(2, currentChanges(), 4) && !target.gills.isDefault)
            {
                GillData oldData = target.gills.AsReadOnlyData();
                target.RestoreGills();
                sb.Append(RestoredGillsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //<mod name="Reptile eyes" author="Stadler76">
            //-Lizard eyes
            if (target.eyes.type != EyeType.LIZARD && target.face.type == FaceType.LIZARD && target.body.type == BodyType.REPTILE && target.ears.type == EarType.LIZARD &&
                ScalingChance(2, currentChanges(), 4))
            {
                EyeData oldData = target.eyes.AsReadOnlyData();
                target.UpdateEyes(EyeType.LIZARD);
                sb.Append(UpdateEyesText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //</mod>
            //FAILSAFE CHANGE
            if (remainingChanges == changeCount)
            {
                (target as CombatCreature)?.AddHP(50);
                target.ChangeLust(3);
            }


            //this is the fallthrough that occurs when a tf item goes through all the changes, but does not proc enough of them to exit early. it will apply however many changes
            //occurred, then return the contents of the stringbuilder.
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            //by default, this is 2 rolls at 50%, so a 25% chance of 0 additional tfs, 50% chance of 1 additional tf, 25% chance of 2 additional tfs.
            //also takes into consideration any perks that increase or decrease tf effectiveness. if you need to roll out your own, feel free to do so.
            int changeCount      = GenerateChangeCount(target, new int[] { 2, 3, 4 });
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

            //For all of these, any text regarding the transformation should be instead abstracted out as an abstract string function. append the result of this abstract function
            //to the string builder declared above (aka sb.Append(FunctionCall(variables));) string builder is just a fancy way of telling the compiler that you'll be creating a
            //long string, piece by piece, so don't do any crazy optimizations first.

            //the initial text for starting the transformation. feel free to add additional variables to this if needed.
            sb.Append(InitialTransformationText(target));

            //Add any free changes here - these can occur even if the change count is 0. these include things such as change in stats (intelligence, etc)
            //change in height, hips, and/or butt, or other similar stats.

            //this will handle the edge case where the change count starts out as 0.
            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //Any transformation related changes go here. these typically cost 1 change. these can be anything from body parts to gender (which technically also changes body parts,
            //but w/e). You are required to make sure you return as soon as you've applied changeCount changes, but a single line of code can be applied at the end of a change to do
            //this for you.

            //paste this line after any tf is applied, and it will: automatically decrement the remaining changes count. if it becomes 0 or less, apply the total number of changes
            //underwent to the target's change count (if applicable) and then return the StringBuilder content.
            //if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);


            //clear screen
            //Statistical changes:
            //-Reduces speed down to 70.
            if (target.relativeSpeed > 70 && Utils.Rand(4) == 0)
            {
                target.ChangeSpeed(-1);
            }
            //-Reduces intelligence down to 60.
            if (target.relativeIntelligence > 60 && Utils.Rand(4) == 0)
            {
                target.ChangeIntelligence(-1);
            }

            //-Raises toughness up to 90.
            //(+3 to 50, +2 to 70, +1 to 90)
            if (target.relativeToughness < 90 && Utils.Rand(3) == 0)
            {
                //(+3)
                if (target.relativeToughness < 50)
                {
                    target.ChangeToughness(3);
                }
                //(+2)
                else if (target.relativeToughness < 70)
                {
                    target.ChangeToughness(2);
                }
                //(+1)
                else
                {
                    target.ChangeToughness(1);
                }
            }
            //-Raises strength to 80.
            if (target.relativeStrength < 80 && Utils.Rand(3) == 0)
            {
                target.ChangeStrength(1);
            }
            //-Raises libido up to 90.
            if (target.relativeLibido < 90 && Utils.Rand(3) == 0)
            {
                target.ChangeLibido(2);
            }
            //Sexual Changes:
            //MOD: lizard dicks get a common rng roll now. The only difference is the text.
            if (target.hasCock && !target.genitals.OnlyHasCocksOfType(CockType.LIZARD) && Utils.Rand(4) == 0)
            {
                C**k nonLizard = target.cocks.First(x => x.type != CockType.LIZARD);
                //Actually xform it nau
                target.genitals.UpdateCock(nonLizard, CockType.LIZARD);
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }

                target.DeltaCreatureStats(lib: 3, lus: 10);
            }

            //-Breasts vanish to 0 rating if male
            if (target.genitals.BiggestCupSize() >= target.genitals.smallestPossibleMaleCupSize && target.gender == Gender.MALE && Utils.Rand(3) == 0)
            {
                //(HUEG)
                //Loop through behind the scenes and adjust all t**s.
                foreach (Breasts breastRow in target.breasts)
                {
                    if (breastRow.cupSize > CupSize.E_BIG)
                    {
                        breastRow.ShrinkBreasts(((byte)breastRow.cupSize).div(2));
                    }
                    else
                    {
                        breastRow.SetCupSize(target.genitals.smallestPossibleMaleCupSize);
                    }
                }
                //(+2 speed)
                //target.IncreaseSpeed(2);
                target.ChangeLibido(2);
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Nipples reduction to 1 per tit.
            if (target.genitals.hasQuadNipples && Utils.Rand(4) == 0)
            {
                target.genitals.SetQuadNipples(false);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            Breasts smallestTits = target.genitals.SmallestBreast();

            //Increase target's breast size, if they are big DD or smaller
            if (smallestTits.cupSize <= CupSize.DD_BIG && target.gender == Gender.FEMALE && Utils.Rand(4) == 0)
            {
                smallestTits.GrowBreasts();
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Remove extra breast rows
            if (target.breasts.Count > 1 && Utils.Rand(3) == 0 && !hyperHappy)
            {
                target.RemoveExtraBreastRows();

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Neck restore
            if (target.neck.type != NeckType.HUMANOID && Utils.Rand(4) == 0)
            {
                NeckData oldData = target.neck.AsReadOnlyData();
                target.RestoreNeck();
                sb.Append(RestoredNeckText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Rear body restore
            if (!target.back.isDefault && Utils.Rand(5) == 0)
            {
                BackData oldData = target.back.AsReadOnlyData();
                target.RestoreBack();
                sb.Append(RestoredBackText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Ovi perk loss
            if (target.womb.canRemoveOviposition && Utils.Rand(5) == 0)
            {
                target.womb.ClearOviposition();
                sb.Append(ClearOvipositionText(target));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Physical changes:
            //Tail - 1st gain reptilian tail, 2nd unlocks enhanced with fire tail whip attack
            if (target.tail.type != TailType.LIZARD && target.tail.type != TailType.SALAMANDER && Utils.Rand(3) == 0)
            {
                TailData oldData = target.tail.AsReadOnlyData();
                target.UpdateTail(TailType.LIZARD);
                sb.Append(UpdateTailText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (target.tail.type == TailType.LIZARD && Utils.Rand(3) == 0)
            {
                TailData oldData = target.tail.AsReadOnlyData();
                target.UpdateTail(TailType.SALAMANDER);
                sb.Append(UpdateTailText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Legs
            if (target.lowerBody.type != LowerBodyType.SALAMANDER && target.tail.type == TailType.SALAMANDER && Utils.Rand(3) == 0)
            {
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                target.UpdateLowerBody(LowerBodyType.SALAMANDER);
                sb.Append(UpdateLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Arms
            if (target.arms.type != ArmType.SALAMANDER && target.lowerBody.type == LowerBodyType.SALAMANDER && Utils.Rand(3) == 0)
            {
                ArmData oldData = target.arms.AsReadOnlyData();
                target.UpdateArms(ArmType.SALAMANDER);
                sb.Append(UpdateArmsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Remove odd eyes
            if (Utils.Rand(4) == 0 && !target.eyes.isDefault)
            {
                EyeData oldData = target.eyes.AsReadOnlyData();
                target.RestoreEyes();
                sb.Append(RestoredEyesText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Human face
            if (target.face.type != FaceType.HUMAN && Utils.Rand(4) == 0)
            {
                FaceData oldData = target.face.AsReadOnlyData();
                target.UpdateFace(FaceType.HUMAN);
                sb.Append(UpdateFaceText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Human ears
            if (target.face.type == FaceType.HUMAN && target.ears.type != EarType.HUMAN && Utils.Rand(4) == 0)
            {
                EarData oldData = target.ears.AsReadOnlyData();
                target.UpdateEars(EarType.HUMAN);
                sb.Append(UpdateEarsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Skin color change
            if (!Species.SALAMANDER.availableTones.Contains(target.body.primarySkin.tone) && Utils.Rand(4) == 0)
            {
                target.body.ChangeMainSkin(Utils.RandomChoice(Species.SALAMANDER.availableTones));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Change skin to normal
            if (target.body.type != BodyType.HUMANOID && target.ears.type == EarType.HUMAN && Utils.Rand(3) == 0)
            {
                BodyData oldData = target.body.AsReadOnlyData();
                target.RestoreBody();
                sb.Append(RestoredBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Removing gills
            if (Utils.Rand(4) == 0 && !target.gills.isDefault)
            {
                GillData oldData = target.gills.AsReadOnlyData();
                target.RestoreGills();
                sb.Append(RestoredGillsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //FAILSAFE CHANGE
            if (remainingChanges == changeCount)
            {
                (target as CombatCreature)?.AddHP(100);
                target.ChangeLust(5);
            }

            //this is the fallthrough that occurs when a tf item goes through all the changes, but does not proc enough of them to exit early. it will apply however many changes
            //occurred, then return the contents of the stringbuilder.
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            int changeCount      = GenerateChangeCount(target, new int[] { 2, 2 });
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

            sb.Append(InitialTransformationText(target));

            //*************
            //Stat Changes
            //*************
            //(increases sensitivity)
            if (Utils.Rand(3) == 0)
            {
                target.ChangeSensitivity(1);
            }
            //(Increase libido)
            if (Utils.Rand(3) == 0)
            {
                target.ChangeLibido(1);
            }
            //MOD: speed now has a common roll.
            if (Utils.Rand(3) == 0)
            {
                //(If speed<70, increases speed)
                if (target.relativeSpeed < 70)
                {
                    target.ChangeSpeed(1.5);
                }
                //(If speed>80, decreases speed down to minimum of 80)
                else if (target.relativeSpeed > 80)
                {
                    target.ChangeSpeed(-1.5);
                }
            }

            //(increase toughness to 60)
            if (Utils.Rand(3) == 0 && target.relativeToughness < 60)
            {
                target.ChangeToughness(1);
            }
            //(decrease strength to 70)
            if (target.relativeStrength > 70 && Utils.Rand(3) == 0)
            {
                target.ChangeStrength(-1);
            }


            //this will handle the edge case where the change count starts out as 0.
            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //****************
            //Sexual Changes
            //****************
            //Increase venom recharge
            if (target.tail.type == TailType.SPIDER_SPINNERET && target.tail.regenRate < target.tail.maxRegen)
            {
                target.tail.UpdateResources(regenRateDelta: 5);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //(tightens v****a to 1, increases lust/libido)
            if (target.hasVagina && Utils.Rand(3) == 0)
            {
                //respects any perks that prevent us from dropping too loose (like marae perk).
                //shrink largest if possible. if 2 (or more if that happens) and both(all) at min, remove extra one.
                VaginalLooseness minVaginalLooseness = EnumHelper.Max(VaginalLooseness.NORMAL, target.genitals.minVaginalLooseness);
                if (target.genitals.LargestVaginalLooseness() > minVaginalLooseness)
                {
                    foreach (V****a v****a in target.vaginas)
                    {
                        if (v****a.looseness > minVaginalLooseness)
                        {
                            v****a.DecreaseLooseness();
                        }
                    }

                    target.DeltaCreatureStats(lib: 2, lus: 25);

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
                else if (target.vaginas.Count > 1)
                {
                    target.RemoveExtraVaginas();
                }
            }
            AnalLooseness minAnalLooseness = EnumHelper.Max(AnalLooseness.LOOSE, target.genitals.minAnalLooseness);

            //(tightens asshole to 1, increases lust)
            if (target.ass.looseness > minAnalLooseness && Utils.Rand(3) == 0)
            {
                target.ass.DecreaseLooseness();
                target.DeltaCreatureStats(lib: 2, lus: 25);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //[Requires penises]
            //(Thickens all cocks to a ratio of 1\" thickness per 5.5\"

            //use a handly linq conversion to grab all the cocks that are below this threshold.
            C**k[] toThicken = target.cocks.Where(x => x.girth * 5.5 < x.length).ToArray();

            if (toThicken.Length > 0 && Utils.Rand(4) == 0)
            {
                int amountChanged = toThicken.Sum(x => x.IncreaseThickness(0.1) != 0 ? 1 : 0);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //[Increase to Breast Size] - up to Large DD
            if (target.genitals.SmallestCupSize() < CupSize.DD_BIG && Utils.Rand(4) == 0)
            {
                target.genitals.SmallestBreast().GrowBreasts();

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //[Increase to Ass Size] - to 11
            if (target.butt.size < 11 && Utils.Rand(4) == 0)
            {
                target.butt.GrowButt();

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Neck restore
            if (target.neck.type != NeckType.HUMANOID && Utils.Rand(4) == 0)
            {
                NeckData oldData = target.neck.AsReadOnlyData();
                target.RestoreNeck();
                sb.Append(RestoredNeckText(target, oldData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Rear body restore
            if (!target.back.isDefault && Utils.Rand(5) == 0)
            {
                BackData oldData = target.back.AsReadOnlyData();
                target.RestoreBack();
                sb.Append(RestoredBackText(target, oldData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Ovi perk loss
            if (target.womb.canRemoveOviposition && Utils.Rand(5) == 0)
            {
                target.womb.ClearOviposition();
                sb.Append(ClearOvipositionText(target));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //***************
            //Appearance Changes
            //***************
            //(Ears become pointed if not human)
            if (target.ears.type != EarType.HUMAN && target.ears.type != EarType.ELFIN && Utils.Rand(4) == 0)
            {
                EarData oldData = target.ears.AsReadOnlyData();
                target.UpdateEars(EarType.ELFIN);
                sb.Append(UpdateEarsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //(Fur/Scales fall out)
            if (target.body.type != BodyType.HUMANOID && (target.ears.type == EarType.HUMAN || target.ears.type == EarType.ELFIN) && Utils.Rand(4) == 0)
            {
                BodyData oldData = target.body.AsReadOnlyData();
                target.UpdateBody(BodyType.HUMANOID, Tones.PALE);
                sb.Append(UpdateBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //(Gain human face)
            if (target.body.type == BodyType.HUMANOID && (target.face.type != FaceType.SPIDER && target.face.type != FaceType.HUMAN) && Utils.Rand(4) == 0)
            {
                FaceData oldData = target.face.AsReadOnlyData();
                target.UpdateFace(FaceType.HUMAN);
                sb.Append(UpdateFaceText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Remove breast rows over 2.
            if (target.breasts.Count > 2 && Utils.Rand(3) == 0 && !hyperHappy)
            {
                target.RemoveExtraBreastRows();
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Nipples reduction to 1 per tit.
            if (target.genitals.hasQuadNipples && Utils.Rand(4) == 0)
            {
                target.genitals.SetQuadNipples(false);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Nipples Turn Black:
            if (!target.genitals.hasBlackNipples && Utils.Rand(6) == 0)
            {
                target.genitals.SetBlackNipples(true);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //eyes!
            if (target.body.type == BodyType.HUMANOID && target.eyes.type != EyeType.SPIDER && Utils.Rand(4) == 0)
            {
                target.IncreaseIntelligence(5);
                EyeData oldData = target.eyes.AsReadOnlyData();
                target.UpdateEyes(EyeType.SPIDER);
                sb.Append(UpdateEyesText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //(Gain spider fangs)
            if (target.face.type == FaceType.HUMAN && target.body.type == BodyType.HUMANOID && Utils.Rand(4) == 0)
            {
                FaceData oldData = target.face.AsReadOnlyData();
                target.UpdateFace(FaceType.SPIDER);
                sb.Append(UpdateFaceText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //(Arms to carapace-covered arms)
            if (target.arms.type != ArmType.SPIDER && Utils.Rand(4) == 0)
            {
                ArmData oldData = target.arms.AsReadOnlyData();
                target.UpdateArms(ArmType.SPIDER);
                sb.Append(UpdateArmsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(4) == 0 && target.lowerBody.type != LowerBodyType.DRIDER && target.lowerBody.type != LowerBodyType.CHITINOUS_SPIDER)
            {
                target.RestoreLowerBody();
            }
            //Drider butt
            if (isDrider && target.tail.type == TailType.SPIDER_SPINNERET && target.tail.ovipositor.type != OvipositorType.SPIDER && target.lowerBody.type == LowerBodyType.DRIDER &&
                Utils.Rand(3) == 0 && (target.hasVagina || Utils.Rand(2) == 0))
            {
                //V1 - Egg Count
                //V2 - Fertilized Count
                target.tail.GrantOvipositor();
                //Opens up drider ovipositor scenes from available mobs. The character begins producing unfertilized eggs in their arachnid abdomen. Egg buildup raises minimum lust and eventually lowers speed until the target has gotten rid of them. This perk may only be used with the drider lower body, so your scenes should reflect that.
                //Any PC can get an Ovipositor perk, but it will be much rarer for characters without vaginas.
                //Eggs are unfertilized by default, but can be fertilized:
                //-female/herm characters can fertilize them by taking in s***n; successfully passing a pregnancy check will convert one level ofunfertilized eggs to fertilized, even if the PC is already pregnant.
                //-male/herm characters will have a sex dream if they reach stage three of unfertilized eggs; this will represent their bee/drider parts drawing their own s***n from their body to fertilize the eggs, and is accompanied by a nocturnal emission.
                //-unsexed characters cannot currently fertilize their eggs.
                //Even while unfertilized, eggs can be deposited inside NPCs - obviously, unfertilized eggs will never hatch and cannot lead to any egg-birth scenes that may be written later.
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //(Normal Biped Legs -> Carapace-Clad Legs)
            if (((isDrider && target.lowerBody.type != LowerBodyType.DRIDER && target.lowerBody.type != LowerBodyType.CHITINOUS_SPIDER) || (!isDrider && target.lowerBody.type != LowerBodyType.CHITINOUS_SPIDER)) && target.lowerBody.isBiped && Utils.Rand(4) == 0)
            {
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                target.UpdateLowerBody(LowerBodyType.CHITINOUS_SPIDER);
                sb.Append(UpdateLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //(Tail becomes spider abdomen GRANT WEB ATTACK)
            if (target.tail.type != TailType.SPIDER_SPINNERET && (target.lowerBody.type == LowerBodyType.CHITINOUS_SPIDER || target.lowerBody.type == LowerBodyType.DRIDER) && target.arms.type == ArmType.SPIDER && Utils.Rand(4) == 0)
            {
                //(Pre-existing tails)
                //(No tail)
                TailData oldData = target.tail.AsReadOnlyData();
                target.UpdateTail(TailType.SPIDER_SPINNERET);
                sb.Append(UpdateTailText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //(Drider Item Only: Carapace-Clad Legs to Drider Legs)
            if (isDrider && target.lowerBody.type == LowerBodyType.CHITINOUS_SPIDER && Utils.Rand(4) == 0 && target.tail.type == TailType.SPIDER_SPINNERET)
            {
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                target.UpdateLowerBody(LowerBodyType.DRIDER);
                sb.Append(UpdateLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            // Remove gills
            if (Utils.Rand(4) == 0 && !target.gills.isDefault)
            {
                target.RestoreGills();
            }

            if (remainingChanges == changeCount && target is CombatCreature fatigueCheck)
            {
                fatigueCheck.RecoverFatigue(33);
            }


            //this is the fallthrough that occurs when a tf item goes through all the changes, but does not proc enough of them to exit early. it will apply however many changes
            //occurred, then return the contents of the stringbuilder.
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }
Example #10
0
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            //by default, this is 2 rolls at 50%, so a 25% chance of 0 additional tfs, 50% chance of 1 additional tf, 25% chance of 2 additional tfs.
            //also takes into consideration any perks that increase or decrease tf effectiveness. if you need to roll out your own, feel free to do so.
            int changeCount      = GenerateChangeCount(target, new int[] { 2, 2, 3, 4 });
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

            //For all of these, any text regarding the transformation should be instead abstracted out as an abstract string function. append the result of this abstract function
            //to the string builder declared above (aka sb.Append(FunctionCall(variables));) string builder is just a fancy way of telling the compiler that you'll be creating a
            //long string, piece by piece, so don't do any crazy optimizations first.

            //the initial text for starting the transformation. feel free to add additional variables to this if needed.
            sb.Append(InitialTransformationText(target));

            //Add any free changes here - these can occur even if the change count is 0. these include things such as change in stats (intelligence, etc)
            //change in height, hips, and/or butt, or other similar stats.

            //this will handle the edge case where the change count starts out as 0.
            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //Any transformation related changes go here. these typically cost 1 change. these can be anything from body parts to gender (which technically also changes body parts,
            //but w/e). You are required to make sure you return as soon as you've applied changeCount changes, but a single line of code can be applied at the end of a change to do
            //this for you.

            //paste this line after any tf is applied, and it will: automatically decrement the remaining changes count. if it becomes 0 or less, apply the total number of changes
            //underwent to the target's change count (if applicable) and then return the StringBuilder content.
            //if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);
#warning fix me
            int ngPlus(int value) => value;


            if (target.speed < ngPlus(100) && Utils.Rand(3) == 0)
            {
                //+3 spe if less than 50
                if (target.speed < ngPlus(50))
                {
                    target.ChangeSpeed(1);
                }
                //+2 spe if less than 75
                if (target.speed < ngPlus(75))
                {
                    target.ChangeSpeed(1);
                }
                //+1 if above 75.
                target.ChangeSpeed(1);
            }

            if (target.toughness > ngPlus(80) && Utils.Rand(4) == 0)
            {
                target.ChangeToughness(-1);
            }

            //-Reduces sensitivity.
            if (target.sensitivity > 20 && Utils.Rand(3) == 0)
            {
                target.ChangeSensitivity(-1);
            }

            //Raises libido greatly to 50, then somewhat to 75, then slowly to 100.
            if (target.libido < 100 && Utils.Rand(3) == 0)
            {
                //+3 lib if less than 50
                if (target.libido < 50)
                {
                    target.ChangeLibido(1);
                }
                //+2 lib if less than 75
                if (target.libido < 75)
                {
                    target.ChangeLibido(1);
                }
                //+1 if above 75.
                target.ChangeLibido(1);
            }

            //Sexual changes

            //-Lactation stoppage.
            if (target.genitals.isLactating && Utils.Rand(4) == 0)
            {
                if (target.HasPerk <Feeder>())
                {
                    target.RemovePerk <Feeder>();
                }

                target.genitals.SetLactationTo(LactationStatus.NOT_LACTATING);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //-Nipples reduction to 1 per tit.
            if (target.genitals.hasQuadNipples && Utils.Rand(4) == 0)
            {
                target.genitals.SetQuadNipples(false);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //-Remove extra breast rows
            if (target.breasts.Count > 1 && Utils.Rand(3) == 0 && !hyperHappy)
            {
                target.RemoveExtraBreastRows();
            }

            //-Butt > 5 - decrease butt size
            if (target.butt.size > 5 && Utils.Rand(4) == 0)
            {
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }

                target.butt.ShrinkButt();
            }

            if (target.gender.HasFlag(Gender.FEMALE))
            {
                CupSize minCup = EnumHelper.Max(CupSize.D, target.genitals.smallestPossibleFemaleCupSize);
                //Breasts > D cup - Decrease breast size by up to 3 cups
                //MOD NOTE: Now respects minimum cup size from perks.
                if (target.gender.HasFlag(Gender.FEMALE) && target.genitals.BiggestCupSize() > minCup && Utils.Rand(3) == 0)
                {
                    foreach (Breasts breast in target.breasts)
                    {
                        if (breast.cupSize > CupSize.D)
                        {
                            breast.ShrinkBreasts((byte)(1 + Utils.Rand(3)));
                        }
                    }

                    target.IncreaseSpeed();

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                //Breasts < B cup - Increase breast size by 1 cup
                if (target.gender.HasFlag(Gender.FEMALE) && target.genitals.SmallestCupSize() < CupSize.B && Utils.Rand(3) == 0)
                {
                    for (int i = 0; i < target.breasts.Count; i++)
                    {
                        if (target.breasts[i].cupSize < CupSize.B)
                        {
                            target.breasts[i].GrowBreasts();
                        }
                    }
                    target.ChangeLibido(1);
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                //Hips > 12 - decrease hip size by 1-3 sizes
                if (target.hips.size > 12 && Utils.Rand(3) == 0)
                {
                    target.hips.ShrinkHips((byte)(1 + Utils.Rand(3)));
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                //Hips < 6 - increase hip size by 1-3 sizes
                if (target.hips.size < 6 && Utils.Rand(3) == 0)
                {
                    target.hips.GrowHips((byte)(1 + Utils.Rand(3)));
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                if (target.genitals.nippleLength > 1 && Utils.Rand(3) == 0)
                {
                    target.genitals.SetNippleLength(target.genitals.nippleLength / 2);
                }

                VaginalWetness desiredWetness = EnumHelper.Min(target.genitals.maxVaginalWetness, VaginalWetness.SLICK);
                //MOD NOTE: now respects all vaginas, and the maximum wetness allowed by perks (if applicable)
                if (target.hasVagina && target.genitals.SmallestVaginalWetness() < desiredWetness && Utils.Rand(4) == 0)
                {
                    foreach (V****a vag in target.vaginas)
                    {
                        if (vag.wetness < desiredWetness)
                        {
                            vag.IncreaseWetness();
                        }
                    }
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                //Increase tone (up to 65)
                if (target.build.muscleTone < 65 && Utils.Rand(3) == 0)
                {
                    target.build.ChangeMuscleToneToward(65, 2);
                }

                //Decrease thickness (down to 35)
                if (target.build.thickness > 35 && Utils.Rand(3) == 0)
                {
                    target.build.ChangeThicknessToward(35, 5);
                }
                //Grant oviposition.
                if (target.womb.canObtainOviposition && Species.COCKATRICE.Score(target) > 3 && Utils.Rand(5) == 0)
                {
                    target.womb.GrantOviposition();
                    sb.Append(GrantOvipositionText(target));

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            if (target.gender == Gender.MALE)
            {
                //Breasts > B cup - decrease by 1 cup size
                if (target.genitals.BiggestCupSize() > CupSize.B && Utils.Rand(3) == 0)
                {
                    for (int i = 0; i < target.breasts.Count; i++)
                    {
                        if (target.breasts[i].cupSize > CupSize.B)
                        {
                            target.breasts[i].ShrinkBreasts();
                        }
                    }

                    target.IncreaseSpeed();

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                if (target.genitals.nippleLength > 1 && Utils.Rand(3) == 0)
                {
                    target.genitals.SetNippleLength(target.genitals.nippleLength / 2);
                }

                //Hips > 10 - decrease hip size by 1-3 sizes
                if (target.hips.size > 10 && Utils.Rand(3) == 0)
                {
                    target.hips.ShrinkHips((byte)(1 + Utils.Rand(3)));
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                //Hips < 2 - increase hip size by 1-3 sizes
                if (target.hips.size < 2 && Utils.Rand(3) == 0)
                {
                    target.hips.GrowHips((byte)(1 + Utils.Rand(3)));
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                //Increase tone (up to 70)
                if (target.build.muscleTone < 70 && Utils.Rand(3) == 0)
                {
                    target.build.ChangeMuscleToneToward(70, 2);
                }

                //Decrease thickness (down to 35)
                if (target.build.thickness > 35 && Utils.Rand(3) == 0)
                {
                    target.build.ChangeThicknessToward(35, 5);
                }
            }

            if (target.gender.HasFlag(Gender.MALE))
            {
                //C**k < 6 inches - increase by 1-2 inches
                C**k shortest = target.genitals.ShortestCock();
                if (shortest.length < 6 && Utils.Rand(3) == 0)
                {
                    double increment = shortest.IncreaseLength(1 + Utils.Rand(2));

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                C**k longest = target.genitals.LongestCock();
                //Shrink oversized cocks
                if (longest.length > 16 && Utils.Rand(3) == 0)
                {
                    longest.DecreaseLength((Utils.Rand(10) + 5) / 10.0);
                    if (longest.girth > 3)
                    {
                        longest.DecreaseThickness((Utils.Rand(4) + 1) / 10.0);
                    }
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                C**k thinnest = target.genitals.ThinnestCock();
                //C**k thickness <2 - Increase c**k thickness
                if (thinnest.girth < 2 && Utils.Rand(3) == 0)
                {
                    thinnest.IncreaseThickness(1.5);

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }
            int lizardCocks = target.genitals.CountCocksOfType(CockType.LIZARD);

            if (target.hasCock && target.cocks.Count > lizardCocks && Utils.Rand(4) == 0)
            {
                //-Lizard dick - first one
                if (lizardCocks == 0)
                {
                    //Actually xform it nau
                    if (target.genitals.hasSheath)
                    {
                        target.genitals.UpdateCock(0, CockType.LIZARD);
                    }
                    else
                    {
                        target.genitals.UpdateCock(0, CockType.LIZARD);
                    }

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }

                    target.DeltaCreatureStats(lib: 3, lus: 10);
                }
                //(CHANGE OTHER DICK)
                //Requires 1 lizard c**k, multiple cocks
                else                 //if (target.cocks.Count > 1 && target.cocks.Count > lizardCocks)
                {
                    C**k firstNonLizard = target.cocks.First(x => x.type != CockType.LIZARD);

                    target.genitals.UpdateCock(firstNonLizard, CockType.LIZARD);

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }

                    target.DeltaCreatureStats(lib: 3, lus: 10);
                }
            }

            //MOD NOTE: Worms are being removed??? from the game due to that absolutely shitty license that prevents me from porting any content 'created' by
            //its creator (forget the name at this time). it's a bad license because it takes credit from other content creators that use its content. that's like saying
            //the creator of the rubber tire gets credit for all modern car designs. that's bullshit. of course cars require tires, and the original copyright required users
            //to pay a licensing fee for their use. that didn't grant the copyright holder for rubber tires the credit for the cars that used them. If anyone wants to get ahold
            //of the og worms creator and get him/her to relax that requirement to make it reasonable (and future content-change/port friendly), we'll see. until then, this is
            //removed.

            //For the record, that would grant him creator's credit on this document. which he/she had no input on. that's not right. and, by extension, the inventory system, since
            //this item is in use in the inventory system. and the time engine, because worms regen over time. I am the content creator for both of those, and i refuse to allow him/her
            //any credit for either of those. those took time and effort and were difficult - i'm not freely handing credit to him/her because that license would require me to. Either
            //his/her license changes, or the offending content is removed, or the entire game engine goes. Right now i think it's better go with the game engine because it make all of
            //this shit work. - JSG.

            ////--Worms leave if 100% lizard dicks?
            ////Require mammals?
            //if (target.genitals.CountCocksOfType(CockType.LIZARD) == target.cocks.Count && target.hasStatusEffect(StatusEffects.Infested))
            //{
            //	if (target.balls.count > 1)
            //	target.removeStatusEffect(StatusEffects.Infested);
            //	if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);
            //}

            //Increase height up to 5ft 7in.
            if (target.build.heightInInches < 67 && Utils.Rand(5) == 0)
            {
                target.build.IncreaseHeight((byte)(Utils.Rand(3) + 1));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //Decrease height down to a maximum of 6ft 8in.
            if (target.build.heightInInches > 80 && Utils.Rand(5) == 0)
            {
                target.build.DecreaseHeight((byte)(Utils.Rand(3) + 1));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //Physical changes:
            //Removes other antennae
            if (target.antennae.type != AntennaeType.COCKATRICE && !target.antennae.isDefault && Utils.Rand(3) == 0)
            {
                target.RestoreAntennae();
            }
            //Gain antennae like feathers
            if (target.antennae.type == AntennaeType.NONE && target.face.type == FaceType.COCKATRICE && target.ears.type == EarType.COCKATRICE && Utils.Rand(3) == 0)
            {
                // Other antennae types are handled above! (Stadler76)
                AntennaeData oldData = target.antennae.AsReadOnlyData();
                target.UpdateAntennae(AntennaeType.COCKATRICE);
                sb.Append(UpdateAntennaeText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Removes horns
            if (target.horns.type != HornType.NONE && Utils.Rand(5) == 0)
            {
                HornData oldData = target.horns.AsReadOnlyData();
                target.RestoreHorns();
                sb.Append(RestoredHornsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //Face TF
            if (target.face.type != FaceType.COCKATRICE && target.arms.type == ArmType.COCKATRICE && target.lowerBody.type == LowerBodyType.COCKATRICE && Utils.Rand(3) == 0)
            {
                FaceData oldData = target.face.AsReadOnlyData();
                target.UpdateFace(FaceType.COCKATRICE);
                sb.Append(UpdateFaceText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Hair TF
            if (target.hair.type != HairType.FEATHER && Utils.Rand(4) == 0)
            {
                HairData oldData = target.hair.AsReadOnlyData();
                target.UpdateHair(HairType.FEATHER);
                sb.Append(UpdateHairText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Eye TF
            if (target.eyes.type != EyeType.COCKATRICE && target.face.type == FaceType.COCKATRICE && target.body.type == BodyType.COCKATRICE && target.ears.type == EarType.COCKATRICE &&
                Utils.Rand(3) == 0)
            {
                EyeData oldData = target.eyes.AsReadOnlyData();
                target.UpdateEyes(EyeType.COCKATRICE);
                sb.Append(UpdateEyesText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Lizard tongue TF
            if (target.tongue.type != TongueType.LIZARD && target.face.type == FaceType.COCKATRICE && Utils.Rand(3) == 0)
            {
                TongueData oldData = target.tongue.AsReadOnlyData();
                target.UpdateTongue(TongueType.LIZARD);
                sb.Append(UpdateTongueText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Ears TF
            if (target.ears.type != EarType.COCKATRICE && target.face.type == FaceType.COCKATRICE && Utils.Rand(3) == 0)
            {
                EarData oldData = target.ears.AsReadOnlyData();
                target.UpdateEars(EarType.COCKATRICE);
                sb.Append(UpdateEarsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Arm TF
            if (target.arms.type != ArmType.COCKATRICE && Utils.Rand(4) == 0)
            {
                ArmData oldData = target.arms.AsReadOnlyData();
                target.UpdateArms(ArmType.COCKATRICE);
                sb.Append(UpdateArmsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Neck loss, if not cockatrice neck
            if (target.neck.type != NeckType.COCKATRICE && Utils.Rand(4) == 0)
            {
                NeckData oldData = target.neck.AsReadOnlyData();
                target.RestoreNeck();
                sb.Append(RestoredNeckText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Rear body restore
            if (target.back.type != BackType.NORMAL && Utils.Rand(5) == 0)
            {
                BackData oldData = target.back.AsReadOnlyData();
                target.RestoreBack();
                sb.Append(RestoredBackText(target, oldData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Body TF
            if (target.body.type != BodyType.COCKATRICE && target.face.type == FaceType.COCKATRICE && Utils.Rand(3) == 0)
            {
                Species.COCKATRICE.GetRandomCockatriceColors(out FurColor feathers, out Tones scales);

                target.UpdateBody(BodyType.COCKATRICE, feathers, scales);
                NeckData oldData = target.neck.AsReadOnlyData();
                target.UpdateNeck(NeckType.COCKATRICE, feathers.primaryColor);
                sb.Append(UpdateNeckText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Neck TF, if not already TFed from Body TF above
            if (target.neck.type != NeckType.COCKATRICE && target.body.type == BodyType.COCKATRICE && target.face.type == FaceType.COCKATRICE && Utils.Rand(3) == 0)
            {
                NeckData oldData = target.neck.AsReadOnlyData();
                target.UpdateNeck(NeckType.COCKATRICE, Utils.RandomChoice(Species.COCKATRICE.availablePrimaryFeatherColors));
                sb.Append(UpdateNeckText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Leg TF
            if (target.lowerBody.type != LowerBodyType.COCKATRICE && Utils.Rand(4) == 0)
            {
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                target.UpdateLowerBody(LowerBodyType.COCKATRICE);
                sb.Append(UpdateLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Tail TF
            if (target.tail.type != TailType.COCKATRICE && Utils.Rand(4) == 0)
            {
                TailData oldData = target.tail.AsReadOnlyData();
                target.UpdateTail(TailType.COCKATRICE);
                sb.Append(UpdateTailText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Wings TF
            //feathered wings and not large and a target? that shouldn't happen. silently make them large
            if (target.wings.type == WingType.FEATHERED && !target.wings.isLarge && target is Player)
            {
                target.wings.GrowLarge();
            }
            else if (target.wings.type != WingType.FEATHERED && target.arms.type == ArmType.COCKATRICE && Utils.Rand(4) == 0)
            {
                HairFurColors wingColor = !target.body.activeFur.isEmpty ? target.body.activeFur.fur.primaryColor : target.hair.hairColor;

                WingData oldData = target.wings.AsReadOnlyData();
                target.UpdateWings(WingType.FEATHERED);
                sb.Append(UpdateWingsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //FAILSAFE CHANGE
            if (remainingChanges == changeCount)
            {
                if (target is CombatCreature failSafe)
                {
                    failSafe.AddHP(50);
                }
                target.ChangeLust(3);
            }

            //this is the fallthrough that occurs when a tf item goes through all the changes, but does not proc enough of them to exit early. it will apply however many changes
            //occurred, then return the contents of the stringbuilder.
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }
        //trying to think of a good way to do nipple piercings for goblins when you go full goblin without being too intrusive.
        //can't. oh well. but hey, at least it removes inverted nipples without requiring piercings, i guess

        //fun fact: this is (as of writing) the only way to restore nipples back to normal after you've made them fuckable. it's now possible to remove fuckable nipples just
        //by shrinking them down (they become inverted), but they're still not 'normal.' this will convert inverted nipples back to normal. so, if you want to remove fuckable nipples,
        //shrink them down, then proc this a few times. It's also possible that nipple piercings remove inverted nipples over time, but as of writing, that's not (currently) implemented
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            //rolls of 1/2, 1/3, 1/4, and 1/5 for extra changes. i don't feel like figuring out the odds, so here's the simple split:
            //the most changes (+4) has an odds of 1/120, and the least changes (+0) have an odds of 1/5. the remaining change counts vary, totaling 19/24.
            int changeCount      = GenerateChangeCount(target, new int[] { 2, 3, 4, 5 });
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

            //For all of these, any text regarding the transformation should be instead abstracted out as an abstract string function. append the result of this abstract function
            //to the string builder declared above (aka sb.Append(FunctionCall(variables));) string builder is just a fancy way of telling the compiler that you'll be creating a
            //long string, piece by piece, so don't do any crazy optimizations first.

            //the initial text for starting the transformation. feel free to add additional variables to this if needed.
            sb.Append(InitialTransformationText(target));

            //Add any free changes here - these can occur even if the change count is 0. these include things such as change in stats (intelligence, etc)
            //change in height, hips, and/or butt, or other similar stats.

            target.IncreaseLust(15);
            //Stronger
            if (target.relativeStrength > 50)
            {
                if (target.relativeStrength > 90)
                {
                    target.DecreaseStrengthByPercent(3);
                }
                else if (target.relativeStrength > 70)
                {
                    target.DecreaseStrengthByPercent(2);
                }
                else
                {
                    target.DecreaseStrengthByPercent(1);
                }
            }
            ///Less tough
            if (target.relativeToughness > 50)
            {
                if (target.relativeToughness > 90)
                {
                    target.DecreaseToughnessByPercent(4);
                }
                else if (target.relativeToughness > 70)
                {
                    target.DecreaseToughnessByPercent(2);
                }
                else
                {
                    target.DecreaseToughnessByPercent(1);
                }
            }
            //Speed boost
            if (Utils.Rand(3) == 0 && target.relativeSpeed < 50)
            {
                target.IncreaseSpeed(1 + Utils.Rand(2));
            }
            //antianemone corollary:
            if (target.hair.type == HairType.ANEMONE && Utils.Rand(2) == 0)
            {
                //-insert anemone hair removal into them under whatever criteria you like, though hair removal should precede abdomen growth; here's some sample text:
                HairData oldData = target.hair.AsReadOnlyData();
                target.RestoreHair();
                sb.Append(RestoredHairText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Shrink
            if (Utils.Rand(2) == 0 && target.heightInInches > 48)
            {
                target.build.DecreaseHeight((byte)(1 + Utils.Rand(5)));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //this will handle the edge case where the change count starts out as 0.
            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //Normal transforms (Cost 1)
            //if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);

            //Neck restore
            if (!target.neck.isDefault && Utils.Rand(4) == 0)
            {
                NeckData oldData = target.neck.AsReadOnlyData();
                target.RestoreNeck();
                sb.Append(RestoredNeckText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Rear body restore
            if (!target.back.isDefault && Utils.Rand(5) == 0)
            {
                BackData oldData = target.back.AsReadOnlyData();
                target.RestoreBack();
                sb.Append(RestoredBackText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Ovi perk loss
            if (target.womb.canRemoveOviposition && Utils.Rand(5) == 0)
            {
                target.womb.ClearOviposition();
                sb.Append(ClearOvipositionText(target));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Restore arms to become human arms again
            if (Utils.Rand(4) == 0 && !target.arms.isDefault)
            {
                ArmData oldData = target.arms.AsReadOnlyData();
                target.RestoreArms();
                sb.Append(RestoredArmsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //SEXYTIEMS
            //Multidick killa!
            if (target.cocks.Count > 1 && Utils.Rand(3) == 0)
            {
                target.genitals.RemoveCock();

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Boost vaginal capacity without gaping
            if (Utils.Rand(3) == 0 && target.hasVagina && target.genitals.standardBonusVaginalCapacity < 40)
            {
                target.genitals.IncreaseBonusVaginalCapacity(5);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Boost fertility
            if (Utils.Rand(4) == 0 && target.fertility.totalFertility < 40 && target.hasVagina)
            {
                target.fertility.IncreaseFertility((byte)(2 + Utils.Rand(5)));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Shrink primary dick to no longer than 12 inches
            else if (target.cocks.Count == 1 && Utils.Rand(2) == 0 && !hyperHappy)
            {
                if (target.cocks[0].length > 12)
                {
                    double delta = target.cocks[0].DecreaseLength(1 + Utils.Rand(3));

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }
            //GENERAL APPEARANCE STUFF BELOW
            //REMOVAL STUFF
            //Removes wings!
            if ((target.wings.type != WingType.NONE) && Utils.Rand(4) == 0)
            {
                WingData oldData = target.wings.AsReadOnlyData();
                target.RestoreWings();
                sb.Append(RestoredWingsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Removes antennae!
            if (target.antennae.type != AntennaeType.NONE && Utils.Rand(3) == 0)
            {
                AntennaeData oldData = target.antennae.AsReadOnlyData();
                target.RestoreAntennae();
                sb.Append(RestoredAntennaeText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Remove odd eyes
            if (Utils.Rand(5) == 0 && target.eyes.count != 2)
            {
                EyeData oldData = target.eyes.AsReadOnlyData();
                target.RestoreEyes();
                sb.Append(RestoredEyesText(target, oldData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Remove extra breast rows
            if (target.breasts.Count > 1 && Utils.Rand(3) == 0)
            {
                target.genitals.RemoveExtraBreastRows();

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Skin/fur
            if (target.body.type != BodyType.HUMANOID && Utils.Rand(4) == 0 && target.face.type == FaceType.HUMAN)
            {
                BodyData oldData = target.body.AsReadOnlyData();
                target.RestoreBody();
                sb.Append(RestoredBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //skinTone
            if (!Species.GOBLIN.availableTones.Contains(target.body.primarySkin.tone) && Utils.Rand(2) == 0)
            {
                if (Utils.Rand(10) != 0)
                {
                    target.body.ChangeMainSkin(Tones.DARK_GREEN);
                }
                else if (Utils.RandBool())
                {
                    target.body.ChangeMainSkin(Tones.PALE_YELLOW);
                }
                else
                {
                    target.body.ChangeMainSkin(Tones.GRAYISH_BLUE);
                }

                //MOD note: rathazal mixology called here - how do we want to standardize that?

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Face!
            if (target.face.type != FaceType.HUMAN && Utils.Rand(4) == 0 && target.ears.type == EarType.ELFIN)
            {
                FaceData oldData = target.face.AsReadOnlyData();
                target.UpdateFace(FaceType.HUMAN);
                sb.Append(UpdateFaceText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Ears!
            if (target.ears.type != EarType.ELFIN && Utils.Rand(3) == 0)
            {
                EarData oldData = target.ears.AsReadOnlyData();
                target.UpdateEars(EarType.ELFIN);
                sb.Append(UpdateEarsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            // Remove gills
            if (Utils.Rand(4) == 0 && !target.gills.isDefault)
            {
                GillData oldData = target.gills.AsReadOnlyData();
                target.RestoreGills();
                sb.Append(RestoredGillsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //Nipples Turn Back:
            if (target.genitals.hasBlackNipples && Utils.Rand(3) == 0)
            {
                target.genitals.SetBlackNipples(false);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //MOD: Added: remove inverted nipples. this by entension is able to remove fuckable nipples, assuming you shrunk them down until they became inverted.
            if (target.genitals.nippleType.IsInverted() && Utils.Rand(3) == 0)
            {
                target.genitals.SetNippleStatus(NippleStatus.NORMAL);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //Debugcunt
            if (Utils.Rand(3) == 0 && target.hasVagina && target.vaginas.Any(x => !x.isDefault))
            {
                foreach (V****a vag in target.vaginas)
                {
                    if (!vag.isDefault)
                    {
                        target.genitals.RestoreVagina(vag);
                    }
                }

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(4) == 0 && target.ass.wetness > target.ass.minWetness)
            {
                if (target.ass.wetness - target.ass.minWetness > 1)
                {
                    target.ass.DecreaseWetness(2);
                }
                else
                {
                    target.ass.DecreaseWetness();
                }

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(3) == 0)
            {
                if (Utils.Rand(2) == 0)
                {
                    target.femininity.ChangeFemininityToward(85, 3);
                }

                if (Utils.Rand(2) == 0)
                {
                    target.build.ChangeThicknessToward(20, 3);
                }

                if (Utils.Rand(2) == 0)
                {
                    target.build.ChangeMuscleToneToward(15, 5);
                }
            }

            //this is the fallthrough that occurs when a tf item goes through all the changes, but does not proc enough of them to exit early. it will apply however many changes
            //occurred, then return the contents of the stringbuilder.
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            int changeCount      = GenerateChangeCount(target, new int[] { 2, 2 });
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

            //Zero Cost/Stat changes.

            //None at the moment

            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //Any transformation related changes go here. these typically cost 1 change. these can be anything from body parts to gender (which technically also changes body parts,
            //but w/e). You are required to make sure you return as soon as you've applied changeCount changes, but a single line of code can be applied at the end of a change to do
            //this for you.

            //paste this line after any tf is applied, and it will: automatically decrement the remaining changes count. if it becomes 0 or less, apply the total number of changes
            //underwent to the target's change count (if applicable) and then return the StringBuilder content.
            //if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);

            // Normal TFs
            //------------
            if (Utils.Rand(4) == 0 && target.hair.type != HairType.NORMAL && target.hair.type != HairType.NO_HAIR && target.hair.type != HairType.QUILL)
            {
                //sb.Append("\n\nYour scalp feels really strange, but the sensation is brief. You feel your hair, and you immediately notice the change. <b>It would seem that your hair is normal again!</b>");
                HairData oldData = target.hair.AsReadOnlyData();
                target.UpdateHair(HairType.NORMAL);
                sb.Append(UpdateHairText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(4) == 0 && target.arms.type == ArmType.HARPY)
            {
                //sb.Append("\n\nYour arm feathers fall out completely, <b>leaving only the " + target.skinFurScales() + " underneath.</b>");
                ArmData oldData = target.arms.AsReadOnlyData();
                target.RestoreArms();
                sb.Append(RestoredArmsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Remove gills
            if (Utils.Rand(3) == 0 && target.gills.type != GillType.NONE)
            {
                GillData oldData = target.gills.AsReadOnlyData();
                target.RestoreGills();
                sb.Append(RestoredGillsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //restore spider eyes.
            if (Utils.Rand(3) == 0 && target.eyes.type == EyeType.SPIDER)
            {
                //sb.Append("\n\nYour eyes start throbbing painfully, your sight in them eventually going dark. You touch your head to inspect your eyes, only to find out that they have changed. <b>You have human eyes now!</b>");
                EyeData oldData = target.eyes.AsReadOnlyData();
                target.UpdateEyes(EyeType.HUMAN);
                sb.Append(UpdateEyesText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(3) == 0 && target.genitals.hasQuadNipples)
            {
                //sb.Append("\n\nA tightness arises in your nipples as three out of four on each breast recede completely, the leftover nipples migrating to the middle of your breasts. <b>You are left with only one nipple on each breast.</b>");
                target.genitals.SetQuadNipples(false);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            // Main TFs
            //------------
            //Change to fur
            if (Utils.Rand(3) == 0 && !target.body.IsFurBodyType())
            {
                target.UpdateBody(BodyType.SIMPLE_FUR, new FurColor(HairFurColors.BROWN));
                //sb.Append("\n\nYou shiver, feeling a bit cold. Just as you begin to wish for something to cover up with, it seems your request is granted; <b>fur begins to grow all over your body!</b> You tug at the tufts in alarm, but they're firmly rooted and... actually pretty soft. Huh. ");

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Gain Echidna ears
            if (Utils.Rand(3) == 0 && target.ears.type != EarType.ECHIDNA)
            {
                target.UpdateEars(EarType.ECHIDNA);
                //sb.Append("\n\n");
                //sb.Append(" <b>You now have echidna ears!</b>");

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Gain Echidna tail
            if (Utils.Rand(3) == 0 && target.ears.type == EarType.ECHIDNA && target.tail.type != TailType.ECHIDNA)
            {
                target.UpdateTail(TailType.ECHIDNA);
                //sb.Append("\n\n");
                //sb.Append(" <b>You now have an echidna tail!</b>");

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Gain Echidna legs
            if (Utils.Rand(3) == 0 && target.ears.type == EarType.ECHIDNA && target.tail.type == TailType.ECHIDNA && target.lowerBody.type != LowerBodyType.ECHIDNA)
            {
                target.UpdateLowerBody(LowerBodyType.ECHIDNA);
                //sb.Append("\n\n");
                //sb.Append(" <b>They actually look like the feet of an echidna!</b>");

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Convert one existing c**k to Echidna
            if (Utils.Rand(3) == 0 && target.hasCock && target.genitals.CountCocksOfType(CockType.ECHIDNA) < target.cocks.Count)
            {
                int firstNonEchidna = target.cocks.FirstIndexOf(x => x.type != CockType.ECHIDNA);
                target.genitals.UpdateCock(firstNonEchidna, CockType.ECHIDNA);

                //sb.Append("\n\n");
                //if (target.cocks.Count == 1) //sb.Append("Your [c**k] suddenly becomes rock hard out of nowhere. You " + target.clothedOrNakedLower("pull it out from your [armor], right in the middle of the food tent, watching", "watch") + " as it begins to shift and change. It becomes pink in color, and you feel a pinch at the head as it splits to become four heads. " + (target.hasSheath() ? "" : "The transformation finishes off with a fleshy sheath forming at the base.") + " It ejaculates before going limp, retreating into your sheath.");
                //else //sb.Append("One of your penises begins to feel strange. You " + target.clothedOrNakedLower("pull the offending c**k out from your [armor], right in the middle of the food tent, watching", "watch") + " as it begins to shift and change. It becomes pink in color, and you feel a pinch at the head as it splits to become four heads. " + (target.hasSheath() ? "" : "The transformation finishes off with a fleshy sheath forming at the base.") + " It ejaculates before going limp, retreating into your sheath.");
                //sb.Append(" <b>You now have an echidna penis!</b>");

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Gain Echidna tongue
            if (Utils.Rand(3) == 0 && Species.ECHIDNA.Score(target) >= 2 && target.tongue.type != TongueType.ECHIDNA)
            {
                target.UpdateTongue(TongueType.ECHIDNA);
                //sb.Append("\n\nYou feel an uncomfortable pressure in your tongue as it begins to shift and change. Within moments, you are able to behold your long, thin tongue. It has to be at least a foot long. <b>You now have an echidna tongue!</b>");

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Gain quill hair
            if (Utils.Rand(4) == 0 && ((target.hair.type == HairType.NORMAL && !target.hair.isGrowing) || target.hair.type == HairType.NO_HAIR))
            {
                target.UpdateHair(HairType.QUILL);
                //sb.Append("\n\nYour scalp begins to tingle as your hair falls out in clumps, leaving you with a bald head. You aren't bald for long, though. An uncomfortable pressure racks the entirety of your scalp as hard quills begin to sprout from your hair pores. Their growth stops as they reach shoulder length. <b>You now have quills in place of your hair!</b>");

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Gain Echidna face if you have the right conditions.
            if (Utils.Rand(4) == 0 && target.body.isFurry && target.ears.type == EarType.ECHIDNA && target.tail.type == TailType.ECHIDNA && target.tongue.type == TongueType.ECHIDNA)
            {
                target.UpdateFace(FaceType.ECHIDNA);
                //sb.Append("You groan loudly as the bones in your face begin to reshape and rearrange. Most notable, you feel your mouth lengthening into a long, thin snout. <b>You now have an echidna face!</b>");

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            // Other Changes
            //------------
            //Hair stops growing
            if (Utils.Rand(4) == 0 && Species.ECHIDNA.Score(target) >= 2 && target.hair.isGrowing && !target.hair.growthArtificallyDisabled)
            {
                if (target.hair.StopNaturalGrowth())
                {
                    //sb.Append("\n\nYour scalp tingles oddly. In a panic, you reach up to your " + target.hairDescript() + ", but thankfully it appears unchanged.\n");
                    //sb.Append("(<b>Your hair has stopped growing.</b>)");
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            //Sexual changes
            if (Utils.Rand(3) == 0 && target.hasCock && target.genitals.cumMultiplierTrue < 25)
            {
                double temp = 1 + Utils.Rand(5);
                //really not a fan of this hard coded check b/c its not future proof, but it seems to be limited in scope enough i guess i can allow it.
                //Lots of cum raises cum multiplier cap to 2 instead of 1.5
                if (target.HasPerk <MessyOrgasms>())
                {
                    temp += 1 + Utils.Rand(10);
                }
                temp *= 0.1;

                target.genitals.IncreaseCumMultiplier(temp);
                //Flavor text
                //if (target.balls.count == 0) //sb.Append("\n\nYou feel a churning inside your gut as something inside you changes.");
                //else //if (target.balls.count > 0) //sb.Append("\n\nYou feel a churning in your " + target.ballsDescriptLight() + ". It quickly settles, leaving them feeling somewhat more dense.");
                //sb.Append(" A bit of milky pre dribbles from your " + target.multiCockDescriptLight() + ", pushed out by the change.");

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(3) == 0 && target.gender == Gender.MALE && target.genitals.AverageCupSize() > target.genitals.smallestPossibleMaleCupSize && !hyperHappy)
            {
                //sb.Append("\n\nYou cup your t**s as they begin to tingle strangely. You can actually feel them getting smaller in your hands!");
                foreach (Breasts tit in target.breasts.Where(x => x.cupSize > target.genitals.smallestPossibleMaleCupSize))
                {
                    tit.ShrinkBreasts(1);
                }

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Neck restore
            if (target.neck.type != NeckType.defaultValue && Utils.Rand(4) == 0)
            {
                NeckData oldData = target.neck.AsReadOnlyData();
                target.RestoreNeck();
                sb.Append(RestoredNeckText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Rear body restore
            if (target.back.type != BackType.defaultValue && Utils.Rand(5) == 0)
            {
                BackData oldData = target.back.AsReadOnlyData();
                target.RestoreBack();
                sb.Append(RestoredBackText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Ovi perk gain
            if (Utils.Rand(4) == 0 && Species.ECHIDNA.Score(target) >= 3 && target.hasVagina && target.womb.canObtainOviposition)
            {
                if (target.womb.GrantOviposition())
                {
                    sb.Append(GrantOvipositionText(target));
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }
            if ((target.hasVagina && !target.HasTimedEffect <Heat>() && Utils.Rand(3) == 0) ||
                (target.perks.HasTimedEffect <Heat>() && Utils.RandBool() && target.GetTimedEffectData <Heat>().totalAddedLibido < 30))
            {
                target.GoIntoHeat();

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Thickness and hip modifier
            if (Utils.Rand(2) == 0 && target.build.thickness < 90)
            {
                target.build.GetThicker(2);
            }
            //old was rand(2.4) what the actual f**k. i assume that means a 1 in 2.4 chance, but why??? regardless, it's now a 5 in 12 chance (which is 1:2.4)
            if (Utils.Rand(12) < 5 && target.hasVagina && !target.genitals.AppearsMoreMaleThanFemale() && target.hips.size < 14)
            {
                target.build.GrowHips();
                //sb.Append("\n\nAfter finishing, you find that your gait has changed. Did your [hips] widen?");

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //this is the fallthrough that occurs when a tf item goes through all the changes, but does not proc enough of them to exit early. it will apply however many changes
            //occurred, then return the contents of the stringbuilder.
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }
Example #13
0
 protected virtual string ChangeArmText(Creature target, ArmData oldArms)
 {
     return(target.arms.TransformFromText(oldArms));
 }
Example #14
0
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            int changeCount      = GenerateChangeCount(target, new int[] { 2, 2 });
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

            sb.Append(InitialTransformText(target));
            target.DeltaCreatureStats(lus: 3, corr: 1);

            uint hpDelta;

            if (target.hasCock)
            {
                if (target.cocks[0].length < 12)
                {
                    CockData oldData = target.cocks[0].AsReadOnlyData();
                    double   temp    = target.cocks[0].IncreaseLength(Utils.Rand(2) + 2);
                    sb.Append(OneCockGrewLarger(target, oldData, temp));
                }
                hpDelta = 30;
            }
            else
            {
                hpDelta = 20;
            }

            if (target is CombatCreature healthCheck)
            {
                healthCheck.AddHP((uint)(hpDelta + healthCheck.toughness / 3));
                sb.Append(GainVitalityText(target));
            }
            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //Red or orange skin!
            if (Utils.Rand(30) == 0 && !Array.Exists(Species.IMP.availableTones, x => x == target.body.primarySkin.tone))
            {
                Tones oldSkinTone = target.body.primarySkin.tone;
                if (target.body.ChangeMainSkin(Utils.RandomChoice(Species.IMP.availableTones)))
                {
                    sb.Append(ChangeSkinColorText(target, oldSkinTone));

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }


            //Shrinkage!
            if (Utils.Rand(2) == 0 && target.build.heightInInches > 42)
            {
                byte heightDelta = target.build.DecreaseHeight((byte)(1 + Utils.Rand(3)));
                if (heightDelta > 0)
                {
                    sb.Append(GetShorterText(target, heightDelta));

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }
            //Imp wings - I just kinda robbed this from demon changeCount ~Foxwells
            if (Utils.Rand(3) == 0 && ((target.wings.type != WingType.IMP && target.IsCorruptEnough(25)) || (target.wings.type == WingType.IMP && target.IsCorruptEnough(50))))
            {
                bool changedWings = false;
                //grow smalls to large
                if (target.wings.type == WingType.IMP)
                {
                    if (target.wings.GrowLarge())
                    {
                        sb.Append(EnlargenedImpWingsText(target));
                        changedWings = true;
                    }
                }
                else
                {
                    WingData oldData = target.wings.AsReadOnlyData();
                    if (target.UpdateWings(WingType.IMP))
                    {
                        sb.Append(GrowOrChangeWingsText(target, oldData));
                        changedWings = true;
                    }
                }

                if (changedWings)
                {
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            //Imp tail, because that's a unique thing from what I see?
            if (Utils.Rand(3) == 0 && target.tail.type != TailType.IMP)
            {
                TailData oldData = target.tail.AsReadOnlyData();
                if (target.UpdateTail(TailType.IMP))
                {
                    sb.Append(GrowOrChangeTailText(target, oldData));
                    target.IncreaseCorruption(2);

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            //Feets, needs red/orange skin and tail
            if (Species.IMP.availableTones.Contains(target.body.primarySkin.tone) && target.tail.type == TailType.IMP && target.lowerBody.type != LowerBodyType.IMP && Utils.Rand(3) == 0)
            {
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                if (target.UpdateLowerBody(LowerBodyType.IMP))
                {
                    sb.Append(ChangeLowerBodyText(target, oldData));

                    target.IncreaseCorruption(2);

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            //Imp ears, needs red/orange skin and horns
            if (target.horns.type == HornType.IMP && Array.Exists(Species.IMP.availableTones, x => target.body.primarySkin.tone == x) && target.ears.type != EarType.IMP && Utils.Rand(3) == 0)
            {
                EarData oldData = target.ears.AsReadOnlyData();
                if (target.UpdateEars(EarType.IMP))
                {
                    sb.Append(ChangeEarsText(target, oldData));

                    target.IncreaseCorruption(2);
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            //Horns, because why not?
            if (target.horns.type != HornType.IMP && Utils.RandBool())
            {
                HornData oldData = target.horns.AsReadOnlyData();
                if (target.UpdateHorns(HornType.IMP))
                {
                    sb.Append(ChangeOrGrowHornsText(target, oldData));

                    target.IncreaseCorruption(2);

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            //Imp arms, needs orange/red skin. Also your hands turn human.
            if (Species.IMP.availableTones.Contains(target.body.primarySkin.tone) && target.arms.type != ArmType.IMP && Utils.Rand(3) == 0)
            {
                ArmData oldData = target.arms.AsReadOnlyData();
                if (target.UpdateArms(ArmType.IMP))
                {
                    sb.Append(ChangeArmText(target, oldData));

                    target.IncreaseCorruption(2);
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            //Changes hair to red/dark red, shortens it, sets it normal, and makes it curly.
            if (!Species.IMP.availableHairColors.Contains(target.hair.hairColor) && Utils.Rand(3) == 0)
            {
                HairData oldHairData = target.hair.AsReadOnlyData();

                HairFurColors hairColor  = Utils.RandomChoice(Species.IMP.availableHairColors);
                double        hairLength = 1;

                if (target.hair.type != HairType.NORMAL)
                {
                    //also restarts hair growth if disabled.
                    target.UpdateHair(HairType.NORMAL, true, hairColor, newHairLength: hairLength, newStyle: HairStyle.CURLY);
                }
                else
                {
                    //also restarts hair growth if disabled.
                    target.hair.SetAll(hairLength, true, hairColor, style: HairStyle.CURLY);
                }
                sb.Append(HairChangedText(target, oldHairData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //Remove spare titties
            if (target.breasts.Count > 1 && Utils.Rand(3) == 0 && !hyperHappy)
            {
                BreastData toRemove = target.breasts[target.breasts.Count - 1].AsReadOnlyData();

                if (target.genitals.RemoveBreastRows() > 0)
                {
                    sb.Append(RemovedAnExtraRowOfBreasts(target, toRemove));

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }
            //Shrink titties
            if (target.genitals.BiggestCupSize() > CupSize.FLAT && Utils.Rand(3) == 0 && !hyperHappy)
            {
                byte rowsAlreadyModified = 0;
                //temp3 stores how many rows are changed
                foreach (Breasts breast in target.breasts)
                {
                    //If this row is over threshhold
                    if (breast.cupSize > CupSize.FLAT)
                    {
                        CupSize oldSize = breast.cupSize;

                        byte delta;
                        //Big change
                        if (breast.cupSize > CupSize.EE_BIG)
                        {
                            delta = breast.ShrinkBreasts((byte)(2 + Utils.Rand(3)));
                        }
                        else
                        {
                            delta = breast.ShrinkBreasts(1);
                        }

                        if (delta != 0)
                        {
                            sb.Append(CurrentBreastRowChanged(target, breast.rowIndex, delta, rowsAlreadyModified));

                            rowsAlreadyModified++;
                        }
                    }
                }
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }


            //Free extra nipple removal service
            if (target.genitals.hasQuadNipples && Utils.Rand(3) == 0)
            {
                target.genitals.SetQuadNipples(false);

                sb.Append(RemovedQuadNippleText(target));

                target.DecreaseSensitivity(3);
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //Neck restore
            if (target.neck.type != NeckType.defaultValue && Utils.Rand(4) == 0)
            {
                NeckData oldData = target.neck.AsReadOnlyData();
                if (target.RestoreNeck())
                {
                    sb.Append(RestoredNeckText(target, oldData));

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }
            //Rear body restore
            if (target.back.type != BackType.defaultValue && Utils.Rand(5) == 0)
            {
                BackData oldData = target.back.AsReadOnlyData();
                if (target.RestoreBack())
                {
                    sb.Append(RestoredBackText(target, oldData));

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }
            //Ovi perk loss
            if (target is Player && Utils.Rand(5) == 0)
            {
                if (((PlayerWomb)target.womb).ClearOviposition())
                {
                    sb.Append(RemovedOvipositionText(target));

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            //You lotta imp? Time to turn male!
            //Unless you're one of the hyper happy assholes I guess
            //For real tho doesn't seem like female imps exist? Guess they're goblins
            if (target.ImpScore() >= 4 && !hyperHappy)
            {
                bool         changedSomething = false;
                GenitalsData oldGenitals      = target.genitals.AsReadOnlyData();

                changedSomething |= target.genitals.RemoveExtraBreastRows() > 0;
                changedSomething |= target.breasts[0].MakeMale(true);
                changedSomething |= target.RemoveAllVaginas() > 0;

                if (!target.hasCock)
                {
                    changedSomething |= target.AddCock(CockType.HUMAN, 12, 2);
                }
                if (target.balls.count == 0)
                {
                    changedSomething |= target.genitals.GrowBalls(2);
                }

                if (changedSomething)
                {
                    sb.Append(ImpifiedText(target, oldGenitals));
                    remainingChanges--;
                    target.IncreaseCorruption(20);
                }
            }
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }
        //Original credits:
        //since March 26, 2018
        //@author Stadler76
        //
        //Porter's note: Nearly all of the comments here are from the vanilla. comments from modification of code will be marked with <MODIFICATION>
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;
            //<MODIFICATION NOTE>
            //potent tf has an initial count of 3.
            int changeCount      = GenerateChangeCount(target, new int[] { 2, 2 }, enhanced ? 3 : 1);
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

            sb.Append(InitialTransformationText(target));

            if (target.face.type == FaceType.FOX && target.tail.type == TailType.FOX && target.ears.type == EarType.FOX && target.lowerBody.type == LowerBodyType.FOX &&
                target.body.IsFurBodyType() && Utils.Rand(3) == 0 && target is IExtendedCreature extended && !extended.extendedData.resistsTFBadEnds)
            {
                if (!extended.extendedData.hasFoxWarning)
                {
                    extended.extendedData.hasFoxWarning = true;
                }
                else
                {
                    isBadEnd = true;
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //<MODIFICATION NOTE>
            //Free Changes.

            //[decrease Strength] (to some floor) // I figured 15 was fair, but you're in a better position to judge that than I am.
            if (Utils.Rand(3) == 0 && target.relativeStrength > 40)
            {
                if (target.relativeStrength > 90)
                {
                    target.DecreaseStrength(4);
                }
                else if (target.relativeStrength > 80)
                {
                    target.DecreaseStrength(3);
                }
                else if (target.relativeStrength > 60)
                {
                    target.DecreaseStrength(2);
                }
                else
                {
                    target.DecreaseStrength();
                }
            }
            //[decrease Toughness] (to some floor) // 20 or so was my thought here
            if (Utils.Rand(3) == 0 && target.relativeToughness > 30)
            {
                if (target.relativeToughness > 90)
                {
                    target.DecreaseToughness(4);
                }
                else if (target.relativeToughness > 80)
                {
                    target.DecreaseToughness(3);
                }
                else if (target.relativeToughness > 60)
                {
                    target.DecreaseToughness(2);
                }
                else
                {
                    target.DecreaseToughness();
                }
            }
            //[increase Intelligence, Libido and Sensitivity]
            if (Utils.Rand(3) == 0 && (target.relativeLibido < 80 || target.relativeSensitivity < 80 || target.relativeIntelligence < 80))
            {
                if (target.relativeIntelligence < 80)
                {
                    target.IncreaseIntelligence(4);
                }

                if (target.relativeLibido < 80)
                {
                    target.IncreaseLibido(1);
                }

                if (target.relativeSensitivity < 80)
                {
                    target.IncreaseSensitivity(1);
                }
                //gain small lust also
                target.IncreaseLust(10);
            }
            //Modification Note: move this free change up here, where it makes the most sense.
            if (target.build.muscleTone > 40 && Utils.Rand(2) == 0)
            {
                target.build.DecreaseMuscleTone(4);
            }


            if (!Species.FOX.AvailableHairColors.Contains(target.hair.hairColor) && !Species.KITSUNE.elderKitsuneHairColors.Contains(target.hair.hairColor) &&
                !Species.KITSUNE.kitsuneHairColors.Contains(target.hair.hairColor) && Utils.Rand(4) == 0)
            {
                HairFurColors targetColor;
                if (target.tail.type == TailType.FOX && target.tail.tailCount > 1)
                {
                    if (target.tail.tailCount < 9)
                    {
                        targetColor = Utils.RandomChoice(Species.KITSUNE.kitsuneHairColors);
                    }

                    targetColor = Utils.RandomChoice(Species.KITSUNE.elderKitsuneHairColors);
                }
                else
                {
                    targetColor = Utils.RandomChoice(Species.FOX.AvailableHairColors);
                }
            }

            //this will handle the edge case where the change count starts out as 0.
            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //<MODIFICATION NOTE>
            //Transformation Changes.
            //if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);

            //[Adjust hips toward 10 – wide/curvy/flared]
            if (Utils.Rand(3) == 0 && target.hips.size != 10)
            {
                //from narrow to wide
                if (target.hips.size < 7)
                {
                    target.hips.GrowHips(2);
                }
                else if (target.hips.size < 10)
                {
                    target.hips.GrowHips();
                }
                //from wide to narrower
                else if (target.hips.size > 13)
                {
                    target.hips.ShrinkHips(2);
                }
                else
                {
                    target.hips.ShrinkHips();
                }

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //[Remove tentacle hair]
            //required if the hair length change below is triggered
            if (target.hair.type == HairType.ANEMONE && Utils.Rand(3) == 0)
            {
                //-insert anemone hair removal into them under whatever criteria you like, though hair removal should precede abdomen growth;
                HairData oldData = target.hair.AsReadOnlyData();
                target.RestoreHair();
                sb.Append(RestoredHairText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //[Adjust hair length toward range of 16-26 – very long to ass-length]
            if (target.hair.type == HairType.ANEMONE && (target.hair.length > 26 || target.hair.length < 16) && target.hair.canGrowNaturally && Utils.Rand(4) == 0)
            {
                if (target.hair.length < 16)
                {
                    target.hair.GrowHair(1 + Utils.Rand(4));
                }
                else
                {
                    target.hair.ShortenHair(1 + Utils.Rand(4));
                }

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (Utils.Rand(10) == 0)
            {
                //MOD NOTE: Don't remove ?. operator.
                if ((target as CombatCreature)?.RecoverFatigue(10) > 0)
                {
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            //dog cocks!

            //MOD NOTE: consider adding back in fox cocks and using them? they could literally just be dog cocks but with a slightly different text descriptions.
            //MOD NOTE: this is a linq check that sees if any available cocks aren't a dog c**k. it's actually faster than count of type, because it stops as soon as it hits
            //a c**k that isn't a dog, instead of checking all of them. Also, this function finds all non-dog cocks and converts one at random.
            if (Utils.Rand(3) == 0 && target.hasCock && target.cocks.Any(x => x.type != CockType.DOG))
            {
                C**k toChange = Utils.RandomChoice(target.cocks.Where(x => x.type != CockType.DOG).ToArray());

                if (toChange.type == CockType.HUMAN)
                {
                    toChange.IncreaseThickness(.3);
                    target.DeltaCreatureStats(sens: 10, lus: 5);
                }
                //Horse
                else if (toChange.type == CockType.HORSE)
                {
                    //Tweak length/thickness.

                    double deltaLength;
                    if (toChange.length > 6)
                    {
                        deltaLength = -2;
                    }
                    else
                    {
                        deltaLength = -.5;
                    }

                    toChange.SetLengthAndGirth(toChange.length + deltaLength, toChange.girth + 0.5);
                    target.DeltaCreatureStats(sens: 4, lus: 5);
                }
                else
                {
                    target.DeltaCreatureStats(sens: 4, lus: 10);
                }

                target.genitals.UpdateCock(toChange, CockType.DOG);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //Cum Multiplier Xform
            if (target.genitals.totalCum < 5000 && Utils.Rand(3) == 0 && target.hasCock)
            {
                int temp = 2 + Utils.Rand(4);
                //Lots of cum raises cum multiplier cap to 2 instead of 1.5
                if (target.HasPerk <MessyOrgasms>())
                {
                    temp += Utils.Rand(10);
                }
                //MOD NOTE: not sure if cum calculations changed, (i think they have) and if so, that's a lot of multiplier gain holy shit. meh. whatever.
                target.genitals.IncreaseCumMultiplier(temp);
                //Flavor text
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            if (target.balls.count > 0 && target.balls.size > 4 && Utils.Rand(3) == 0)
            {
                int targetSize;
                //currently above max, but whatever, that'll probably get raised anyway.
                if (target.balls.size > 50)
                {
                    targetSize = target.balls.size / 5;
                }
                else if (target.balls.size > 10)
                {
                    targetSize = target.balls.size / 2;
                }
                else
                {
                    targetSize = target.balls.size - 1;
                }

                //allow perks to work.
                target.balls.ShrinkBalls((byte)(target.balls.size - targetSize));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Sprouting more!
            if (enhanced && target.breasts.Count < 4 && target.breasts[target.breasts.Count - 1].cupSize > CupSize.A)
            {
                target.genitals.AddBreastRow(target.breasts[target.breasts.Count - 1].cupSize);

                target.DeltaCreatureStats(sens: 2, lus: 30);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Find out if t**s are eligible for evening
            //MOD NOTE: there's an easier way - just run the normalize breasts. it'll return true if it changed anything. since we display the results of the old
            //breast data anyway, this is way simpler. Also, normalize breasts now returns a bool. woo!
            //MOD NOTE 2: Fox rules here seem to use the same size as previous. if they want to function under anthro rules (size is in decreasing order, but otherwise
            //roughly even) you can use AnthropomorphizeBreasts() instead. see canine tfs for an example.
            if (target.genitals.NormalizeBreasts())
            {
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }



            //HEAT!
            if (!target.HasTimedEffect <Heat>() || target.GetTimedEffectData <Heat>().totalAddedLibido < 30 && Utils.Rand(6) == 0)
            {
                target.GoIntoHeat();

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Neck restore
            if (!target.neck.isDefault && Utils.Rand(4) == 0)
            {
                NeckData oldData = target.neck.AsReadOnlyData();
                target.RestoreNeck();
                sb.Append(RestoredNeckText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Rear body restore
            if (!target.back.isDefault && Utils.Rand(5) == 0)
            {
                BackData oldData = target.back.AsReadOnlyData();
                target.RestoreBack();
                sb.Append(RestoredBackText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Ovi perk loss
            if (target.womb.canRemoveOviposition && Utils.Rand(5) == 0)
            {
                if (target.womb.ClearOviposition())
                {
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }
            //[Grow Fur]
            //FOURTH
            //MOD NOTE: Body has been reworked since this was written, we'll allow kitsune's to remain unchanged, but all others need to be full fur after this.
            if ((enhanced || target.lowerBody.type == LowerBodyType.FOX) && target.body.type != BodyType.KITSUNE && !target.body.IsFurBodyType() && Utils.Rand(4) == 0)
            {
                BodyData oldBodyData = target.body.AsReadOnlyData();

                if (Species.KITSUNE.Score(target) >= 4)
                {
                    FurColor[] colorChoices;
                    if (Species.KITSUNE.elderKitsuneFurColors.Any(x => x.IsIdenticalTo(target.hair.hairColor)) ||
                        Species.KITSUNE.allKitsuneColors.Any(y => y.IsIdenticalTo(target.hair.hairColor)))
                    {
                        colorChoices = new FurColor[] { new FurColor(target.hair.hairColor) };
                    }
                    else if (target.tail.type == TailType.FOX && target.tail.tailCount == TailType.FOX.maxTailCount)
                    {
                        colorChoices = Species.KITSUNE.allKitsuneColors;
                    }
                    else
                    {
                        colorChoices = Species.KITSUNE.elderKitsuneFurColors;
                    }

                    target.UpdateBody(BodyType.UNDERBODY_FUR, Utils.RandomChoice(colorChoices));
                }
                else
                {
                    Species.FOX.GetRandomFurColors(out FurColor primary, out FurColor underbody);

                    target.UpdateBody(BodyType.UNDERBODY_FUR, primary, underbody);
                }

                //should always be true, but whatever.
                if (oldBodyData.type != target.body.type)
                {
                    remainingChanges--;
                    if (remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }
            //[Grow Fox Legs]
            //THIRD
            if ((enhanced || target.ears.type == EarType.FOX) && target.lowerBody.type != LowerBodyType.FOX && Utils.Rand(5) == 0)
            {
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                target.UpdateLowerBody(LowerBodyType.FOX);
                sb.Append(UpdateLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Grow Fox Ears]
            //SECOND
            if ((enhanced || target.tail.type == TailType.FOX) && target.ears.type != EarType.FOX && Utils.Rand(4) == 0)
            {
                EarData oldData = target.ears.AsReadOnlyData();
                target.UpdateEars(EarType.FOX);
                sb.Append(UpdateEarsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //[Grow Fox Tail](fairly common)
            //FIRST
            if (target.tail.type != TailType.FOX && Utils.Rand(4) == 0)
            {
                TailData oldData = target.tail.AsReadOnlyData();
                target.UpdateTail(TailType.FOX);
                sb.Append(UpdateTailText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //[Grow Fox Face]
            //LAST - muzzlygoodness
            //should work from any face, including other muzzles
            if (target.body.HasAny(EpidermisType.FUR) && target.face.type != FaceType.FOX && Utils.Rand(5) == 0)
            {
                FaceData oldData = target.face.AsReadOnlyData();
                target.UpdateFace(FaceType.FOX);
                sb.Append(UpdateFaceText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Arms
            if (target.arms.type != ArmType.FOX && target.body.HasAny(EpidermisType.FUR) && target.tail.type == TailType.FOX && target.lowerBody.type == LowerBodyType.FOX && Utils.Rand(4) == 0)
            {
                ArmData oldData = target.arms.AsReadOnlyData();
                target.UpdateArms(ArmType.FOX);
                sb.Append(UpdateArmsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }


            if (remainingChanges == changeCount && !(target is null))
            {
                (target as CombatCreature)?.RecoverFatigue(5);
            }

            //<MODIFICATION NOTE>
            //Fall through, cleanup and return.
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }
Example #16
0
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            //by default, this is 2 rolls at 50%, so a 25% chance of 0 additional tfs, 50% chance of 1 additional tf, 25% chance of 2 additional tfs.
            //also takes into consideration any perks that increase or decrease tf effectiveness. if you need to roll out your own, feel free to do so.
            int changeCount      = GenerateChangeCount(target, new int[] { 2, 2 });
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

            //For all of these, any text regarding the transformation should be instead abstracted out as an abstract string function. append the result of this abstract function
            //to the string builder declared above (aka sb.Append(FunctionCall(variables));) string builder is just a fancy way of telling the compiler that you'll be creating a
            //long string, piece by piece, so don't do any crazy optimizations first.

            //the initial text for starting the transformation. feel free to add additional variables to this if needed.
            sb.Append(InitialTransformationText(target));

            //Add any free changes here - these can occur even if the change count is 0. these include things such as change in stats (intelligence, etc)
            //change in height, hips, and/or butt, or other similar stats.

            //this will handle the edge case where the change count starts out as 0.
            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //Any transformation related changes go here. these typically cost 1 change. these can be anything from body parts to gender (which technically also changes body parts,
            //but w/e). You are required to make sure you return as soon as you've applied changeCount changes, but a single line of code can be applied at the end of a change to do
            //this for you.

            //paste this line after any tf is applied, and it will: automatically decrement the remaining changes count. if it becomes 0 or less, apply the total number of changes
            //underwent to the target's change count (if applicable) and then return the StringBuilder content.
            //if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);

            //Corruption reduction
            if (isPure)
            {             //Special honey will also reduce corruption, but uses different text and is handled separately
                target.ChangeCorruption(-(1 + (target.corruptionTrue / 20)));
                //Libido Reduction
                if (target.corruption > 0 && Utils.Rand(3) < 2 && target.relativeLibido > 40)
                {
                    target.DeltaCreatureStats(lib: -3, lus: -20);
                }
            }
            //Intelligence Boost
            if (Utils.Rand(2) == 0 && target.relativeIntelligence < 80)
            {
                target.IncreaseIntelligence(0.1 * (80 - target.relativeIntelligence));
            }
            //bee item corollary:
            if (target.hair.type == HairType.ANEMONE && Utils.Rand(2) == 0)
            {
                HairData oldData = target.hair.AsReadOnlyData();
                target.RestoreHair();
                sb.Append(RestoredHairText(target, oldData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //Sexual Stuff
            //No idears
            //Appearance Stuff
            //Hair Color
            if (target.hair.hairColor != HairFurColors.BLACK && target.hair.length > 10 && Utils.Rand(5) == 0)
            {
                if (Utils.Rand(9) == 0)
                {
                    target.hair.SetBothHairColors(HairFurColors.BLACK, HairFurColors.YELLOW);
                }
                else
                {
                    target.hair.SetHairColor(HairFurColors.BLACK);
                }

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Hair Length
            if (target.hair.length < 25 && target.hair.type.canLengthen && Utils.Rand(3) == 0)
            {
                target.hair.GrowHair(Utils.Rand(4) + 1);
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Remove extra breast rows
            if (target.breasts.Count > 2 && Utils.Rand(3) == 0 && !hyperHappy)
            {
                target.genitals.RemoveBreastRows();
            }
            //Antennae
            if (target.antennae.type == AntennaeType.NONE && target.horns.numHorns == 0 && Utils.Rand(3) == 0)
            {
                AntennaeData oldData = target.antennae.AsReadOnlyData();
                target.UpdateAntennae(AntennaeType.BEE);
                sb.Append(UpdateAntennaeText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Horns
            if (!target.horns.isDefault && Utils.Rand(3) == 0)
            {
                HornData oldData = target.horns.AsReadOnlyData();
                target.RestoreHorns();
                sb.Append(RestoredHornsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Bee Legs
            if (target.lowerBody.type != LowerBodyType.BEE && Utils.Rand(4) == 0)
            {
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                target.UpdateLowerBody(LowerBodyType.BEE);
                sb.Append(UpdateLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //(Arms to carapace-covered arms)
            if (target.arms.type != ArmType.BEE && Utils.Rand(4) == 0)
            {
                ArmData oldData = target.arms.AsReadOnlyData();
                target.UpdateArms(ArmType.BEE);
                sb.Append(UpdateArmsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Nipples reduction to 1 per tit.
            if (target.genitals.hasQuadNipples && Utils.Rand(4) == 0)
            {
                target.genitals.SetQuadNipples(false);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Neck restore
            if (!target.neck.isDefault && Utils.Rand(4) == 0)
            {
                NeckData oldData = target.neck.AsReadOnlyData();
                target.RestoreNeck();
                sb.Append(RestoredNeckText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Rear body restore
            if (!target.back.isDefault && Utils.Rand(5) == 0)
            {
                BackData oldData = target.back.AsReadOnlyData();
                target.RestoreBack();
                sb.Append(RestoredBackText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Lose reptile oviposition!
            if (target.womb.canRemoveOviposition && Utils.Rand(5) == 0)
            {
                target.womb.ClearOviposition();
                sb.Append(ClearOvipositionText(target));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Gain bee ovipositor!
            if (target.tail.type == TailType.BEE_STINGER && !target.tail.hasOvipositor && Utils.Rand(2) == 0)
            {
                target.tail.GrantOvipositor();
            }

            //Bee butt - 66% lower chance if already has a tail
            if (target.tail.type != TailType.BEE_STINGER && (target.tail.type == TailType.NONE || Utils.Rand(3) < 2) && Utils.Rand(4) == 0)
            {
                target.UpdateTail(TailType.BEE_STINGER);
                target.tail.UpdateResources((short)(10 - target.tail.resources), (short)(2 - target.tail.regenRate));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Venom Increase
            if (target.tail.type == TailType.BEE_STINGER && target.tail.regenRate < 15 && Utils.Rand(2) == 0)
            {
                short additionalRegen = 1;
                if (target.tail.regenRate < 5)
                {
                    additionalRegen = 3;
                }
                else if (target.tail.regenRate < 10)
                {
                    additionalRegen = 2;
                }

                target.tail.UpdateResources(50, additionalRegen);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Wings
            //Grow bigger bee wings!
            if (target.wings.type == WingType.BEE_LIKE && !target.wings.isLarge && Utils.Rand(4) == 0)
            {
                target.wings.GrowLarge();

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //Grow new bee wings if target has none.
            if (target.wings.type == WingType.NONE && Utils.Rand(4) == 0)
            {
                if (target.back.type == BackType.SHARK_FIN)
                {
                    target.RestoreBack();
                }
                WingData oldData = target.wings.AsReadOnlyData();
                target.UpdateWings(WingType.BEE_LIKE);
                sb.Append(UpdateWingsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Melt demon wings!
            if (target.wings.type == WingType.BAT_LIKE)
            {
                WingData oldData = target.wings.AsReadOnlyData();
                target.RestoreWings();
                sb.Append(RestoredWingsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Remove gills!
            if (Utils.Rand(4) == 0 && !target.gills.isDefault)
            {
                GillData oldData = target.gills.AsReadOnlyData();
                target.RestoreGills();
                sb.Append(RestoredGillsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //All the speical honey effects occur after any normal bee transformations (if the target wasn't a full bee morph)
            if (isSpecial)
            {
                //if no c**k: grow one.
                if (!target.hasCock)
                {
                    target.genitals.AddCock(CockType.defaultValue, Utils.Rand(3) + 8, 2);
                    target.HaveGenericCockOrgasm(0, false, true);
                    target.ChangeSensitivity(10);
                }
                //if multiple cocks, remove the largest c**k by area. combine its length/girth into the first c**k.
                else if (target.cocks.Count > 1)
                {
                    //find the biggest c**k that isn't the first one.
                    C**k   biggest = target.cocks.Skip(1).MaxItem(x => x.area);
                    double delta   = (double)(5 * Math.Sqrt(0.2 * biggest.area));

                    target.cocks[0].DeltaLengthAndGirth(delta, delta);
                    target.HaveGenericCockOrgasm(0, false, true);
                }
                //one c**k. grow it to 100 area total or larger
                else if (target.cocks[0].area < 100)
                {
                    target.cocks[0].DeltaLengthAndGirth(Utils.Rand(3) + 4, 0.1 * Utils.Rand(5) + 0.5);
                }
                //
                else
                {
                    double baseLengthChange;
                    double baseGirthChange;
                    if (target.cocks[0].type != CockType.BEE && Species.CurrentSpecies(target) == Species.BEE)
                    {
                        target.genitals.UpdateCock(0, CockType.BEE);
                        target.ChangeSensitivity(15);

                        baseLengthChange = 5;
                        baseGirthChange  = 1;
                    }
                    else
                    {
                        baseLengthChange = 0.1 * Utils.Rand(10) + 1;
                        baseGirthChange  = 0.1 * Utils.Rand(2) + 1;
                    }

                    double mult;
                    C**k   c**k = target.cocks[0];
                    if (c**k.area >= 400)
                    {
                        mult = 0;                         //C**k stops growing at that point.
                    }
                    else if (c**k.area >= 300)
                    {
                        mult = 0.1;
                    }
                    if (c**k.area > 100)
                    {
                        int offset = (((int)c**k.area) - 100) / 40;
                        mult = 1 - 0.2 * offset;
                    }
                    else
                    {
                        mult = 1;
                    }

                    double deltaLength = (double)(mult * baseLengthChange);
                    double deltaGirth  = (double)(mult * baseGirthChange);

                    target.cocks[0].DeltaLengthAndGirth(deltaLength, deltaGirth);
                }


                if (target.corruption >= 5)
                {
                    double corrLoss = Math.Min(0.1 * target.corruptionTrue + 5, target.corruptionTrue);
                    target.DeltaCreatureStats(corr: -corrLoss, lib: corrLoss);                     //Lose corruption and gains that much libido
                }
                else
                {
                    target.ChangeLibido(5);
                }

                if (target.femininity >= 60 || target.femininity <= 40)
                {
                    if (target.femininity >= 60)
                    {
                        target.femininity.IncreaseMasculinity(3);
                    }
                    else
                    {
                        target.femininity.IncreaseFemininity(3);
                    }
                }
                target.ChangeLust(0.2 * target.libidoTrue + 5);
            }


            //this is the fallthrough that occurs when a tf item goes through all the changes, but does not proc enough of them to exit early. it will apply however many changes
            //occurred, then return the contents of the stringbuilder.
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }
Example #17
0
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            int changeCount      = GenerateChangeCount(target, new int[] { 2, 2 });
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

            //the initial text for starting the transformation. feel free to add additional variables to this if needed.
            sb.Append(InitialTransformationText(target));

            //Add any free changes here - these can occur even if the change count is 0. these include things such as change in stats (intelligence, etc)
            //change in height, hips, and/or butt, or other similar stats.

            //this will handle the edge case where the change count starts out as 0.
            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //Any transformation related changes go here. these typically cost 1 change. these can be anything from body parts to gender (which technically also changes body parts,
            //but w/e). You are required to make sure you return as soon as you've applied changeCount changes, but a single line of code can be applied at the end of a change to do
            //this for you.

            //paste this line after any tf is applied, and it will: automatically decrement the remaining changes count. if it becomes 0 or less, apply the total number of changes
            //underwent to the target's change count (if applicable) and then return the StringBuilder content.
            //if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);

            //-----------------------
            // MAJOR TRANSFORMATIONS
            //-----------------------
            //1st priority: Change lower body to bipedal.
            if (Utils.Rand(4) == 0)
            {
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                target.RestoreLowerBody();
                sb.Append(RestoredLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Remove Oviposition Perk
            if (target.womb.canRemoveOviposition && Utils.Rand(5) == 0)
            {
                target.womb.ClearOviposition();
                sb.Append(ClearOvipositionText(target));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Remove Incorporeality Perk, if not permanent
            if (target.HasPerk <Incorporeal>() && Utils.Rand(4) == 0)
            {
                target.RemovePerk <Incorporeal>();

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Restore neck
            if (target.neck.type != NeckType.HUMANOID && Utils.Rand(5) == 0)
            {
                NeckData oldData = target.neck.AsReadOnlyData();
                target.RestoreNeck();
                sb.Append(RestoredNeckText(target, oldData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Rear body restore
            if (!target.back.isDefault && Utils.Rand(5) == 0)
            {
                BackData oldData = target.back.AsReadOnlyData();
                target.RestoreBack();
                sb.Append(RestoredBackText(target, oldData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Skin color change – light, fair, olive, dark, ebony, mahogany, russet
            if (!Species.HUMAN.availableTones.Contains(target.body.primarySkin.tone) && Utils.Rand(5) == 0)
            {
                target.body.ChangeAllSkin(Utils.RandomChoice(Species.HUMAN.availableTones));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Change skin to normal
            if (target.body.type != BodyType.HUMANOID && (target.ears.type == EarType.HUMAN || target.ears.type == EarType.ELFIN) && Utils.Rand(4) == 0)
            {
                BodyData oldData = target.body.AsReadOnlyData();
                target.RestoreBody();
                sb.Append(RestoredBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Restore arms to become human arms again
            if (Utils.Rand(4) == 0)
            {
                ArmData oldData = target.arms.AsReadOnlyData();
                target.RestoreArms();
                sb.Append(RestoredArmsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-----------------------
            // MINOR TRANSFORMATIONS
            //-----------------------
            //-Human face
            if (target.face.type != FaceType.HUMAN && Utils.Rand(4) == 0)
            {
                FaceData oldData = target.face.AsReadOnlyData();
                target.RestoreFace();
                sb.Append(RestoreFaceText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Human tongue
            if (target.tongue.type != TongueType.HUMAN && Utils.Rand(4) == 0)
            {
                TongueData oldData = target.tongue.AsReadOnlyData();
                target.RestoreTongue();
                sb.Append(RestoreTongueText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Remove odd eyes
            if (Utils.Rand(5) == 0 && target.eyes.type != EyeType.HUMAN)
            {
                EyeData oldData = target.eyes.AsReadOnlyData();
                target.RestoreEyes();
                sb.Append(RestoredEyesText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Gain human ears (If you have human face)
            if (target.ears.type != EarType.HUMAN && target.face.type == FaceType.HUMAN && Utils.Rand(4) == 0)
            {
                EarData oldData = target.ears.AsReadOnlyData();
                target.RestoreEar();
                sb.Append(RestoreEarsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Removes gills
            if (Utils.Rand(4) == 0 && !target.gills.isDefault)
            {
                GillData oldData = target.gills.AsReadOnlyData();
                target.RestoreGills();
                sb.Append(RestoredGillsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Nipples Turn Back:
            if (target.genitals.hasBlackNipples && Utils.Rand(3) == 0)
            {
                target.genitals.SetBlackNipples(false);
            }
            //Remove extra nipples
            if (target.genitals.hasQuadNipples && Utils.Rand(3) == 0)
            {
                target.genitals.SetQuadNipples(false);
            }
            //Hair turns normal
            //Restart hair growth, if hair's normal but growth isn't on. Or just over turning hair normal. The power of rng.
            if ((target.hair.type != HairType.NORMAL || target.hair.growthArtificallyDisabled) && Utils.Rand(3) == 0)
            {
                target.UpdateHair(HairType.NORMAL);
                if (target.hair.growthArtificallyDisabled)
                {
                    target.hair.SetHairGrowthStatus(true);
                }

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-----------------------
            // EXTRA PARTS REMOVAL
            //-----------------------
            //Removes antennae
            if (target.antennae.type != AntennaeType.NONE && Utils.Rand(3) == 0)
            {
                AntennaeData oldData = target.antennae.AsReadOnlyData();
                target.RestoreAntennae();
                sb.Append(RestoredAntennaeText(target, oldData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Removes horns
            if ((target.horns.type != HornType.NONE) && Utils.Rand(5) == 0)
            {
                HornData oldData = target.horns.AsReadOnlyData();
                target.RestoreHorns();
                sb.Append(RestoredHornsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Removes wings
            if (target.wings.type != WingType.NONE && Utils.Rand(5) == 0)
            {
                WingData oldData = target.wings.AsReadOnlyData();
                target.RestoreWings();
                sb.Append(RestoredWingsText(target, oldData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Removes tail
            if (target.tail.type != TailType.NONE && Utils.Rand(5) == 0)
            {
                TailData oldData = target.tail.AsReadOnlyData();
                target.RestoreTail();
                sb.Append(RestoredTailText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Increase height up to 4ft 10in.
            if (Utils.Rand(2) == 0 && target.build.heightInInches < 58)
            {
                int temp = Utils.Rand(5) + 3;
                //Flavor texts. Flavored like 1950's cigarettes. Yum.
                target.build.IncreaseHeight((byte)temp);
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Decrease height down to a maximum of 6ft 2in.
            if (Utils.Rand(2) == 0 && target.build.heightInInches > 74)
            {
                target.build.DecreaseHeight((byte)(3 + Utils.Rand(5)));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-----------------------
            // SEXUAL TRANSFORMATIONS
            //-----------------------
            //Remove additional cocks
            if (target.cocks.Count > 1 && Utils.Rand(3) == 0)
            {
                target.RemoveCock();
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Remove additional balls/remove uniball
            if (target.balls.hasBalls && (target.balls.count != 2 || target.balls.uniBall) && Utils.Rand(3) == 0)
            {
                if (target.balls.size > 2)
                {
                    if (target.balls.size > 5)
                    {
                        target.balls.ShrinkBalls((byte)(1 + Utils.Rand(3)));
                    }

                    target.balls.ShrinkBalls(1);
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
                else if (target.balls.count > 2)
                {
                    target.balls.RemoveExtraBalls();

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
                else                 //if (target.balls.count == 1 || target.balls.uniBall)
                {
                    target.balls.MakeStandard();

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            //remove second v****a.
            if (target.vaginas.Count > 1 && Utils.Rand(3) == 0)
            {
                target.genitals.RemoveExtraVaginas();
            }

            //Change c**k back to normal
            if (target.hasCock && !target.genitals.OnlyHasCocksOfType(CockType.HUMAN) && Utils.Rand(3) == 0)
            {
                //Select first non-human c**k
                C**k firstNonHuman = target.cocks.First(x => x.type != CockType.HUMAN);

                target.genitals.UpdateCock(firstNonHuman, CockType.HUMAN);
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            C**k   longest    = target.genitals.LongestCock();
            double targetSize = Math.Max(7, target.genitals.minimumCockLength);

            //Shrink oversized cocks
            if (target.hasCock && longest.length > targetSize && Utils.Rand(3) == 0)
            {
                longest.DecreaseLength((Utils.Rand(10) + 2) / 10.0);
                if (longest.girth > 1)
                {
                    longest.DecreaseThickness((Utils.Rand(4) + 1) / 10.0);
                }
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Remove additional breasts
            if (target.breasts.Count > 1 && Utils.Rand(3) == 0)
            {
                target.RemoveExtraBreastRows();

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            Breasts biggestCup = target.genitals.LargestBreast();
            CupSize targetCup  = EnumHelper.Max(CupSize.D, target.genitals.smallestPossibleFemaleCupSize);

            //Shrink t**s!
            if (Utils.Rand(3) == 0 && biggestCup.cupSize > targetCup)
            {
                foreach (Breasts t**s in target.breasts)
                {
                    t**s.ShrinkBreasts();
                }
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Change v****a back to normal
            if (Utils.Rand(3) == 0 && target.hasVagina && !target.genitals.OnlyHasVaginasOfType(VaginaType.defaultValue))
            {
                foreach (V****a vag in target.vaginas)
                {
                    target.genitals.RestoreVagina(vag);
                }

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            VaginalWetness targetWetness = EnumHelper.Max(VaginalWetness.WET, target.genitals.minVaginalWetness);

            //Reduce wetness down to a minimum of 2
            if (Utils.Rand(3) == 0 && target.hasVagina && target.genitals.LargestVaginalWetness() > targetWetness)
            {
                foreach (V****a vag in target.vaginas)
                {
                    if (vag.wetness > targetWetness)
                    {
                        vag.DecreaseWetness();
                    }
                }
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Fertility Decrease:
            if (target.hasVagina && target.fertility.baseFertility > 10 && Utils.Rand(3) == 0)
            {
                //High fertility:
                //Average fertility:

                target.fertility.DecreaseFertility((byte)(1 + Utils.Rand(3)));
                if (target.fertility.baseFertility < 10)
                {
                    target.fertility.SetFertility(10);
                }

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Cum Multiplier Decrease:
            if (target.hasCock && target.genitals.cumMultiplier > 5 && Utils.Rand(3) == 0)
            {
                target.genitals.DecreaseCumMultiplier(1 + (Utils.Rand(20) / 10.0));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Anal wetness decrease
            if (target.ass.wetness > 0 && Utils.Rand(3) == 0)
            {
                target.ass.DecreaseWetness();
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }


            //this is the fallthrough that occurs when a tf item goes through all the changes, but does not proc enough of them to exit early. it will apply however many changes
            //occurred, then return the contents of the stringbuilder.
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            //by default, this is 2 rolls at 50%, so a 25% chance of 0 additional tfs, 50% chance of 1 additional tf, 25% chance of 2 additional tfs.
            //also takes into consideration any perks that increase or decrease tf effectiveness. if you need to roll out your own, feel free to do so.
            int changeCount      = GenerateChangeCount(target, new int[] { 2, 2, 4 });
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

            //For all of these, any text regarding the transformation should be instead abstracted out as an abstract string function. append the result of this abstract function
            //to the string builder declared above (aka sb.Append(FunctionCall(variables));) string builder is just a fancy way of telling the compiler that you'll be creating a
            //long string, piece by piece, so don't do any crazy optimizations first.

            //the initial text for starting the transformation. feel free to add additional variables to this if needed.
            sb.Append(InitialTransformationText(target));

            //Add any free changes here - these can occur even if the change count is 0. these include things such as change in stats (intelligence, etc)
            //change in height, hips, and/or butt, or other similar stats.

            //this will handle the edge case where the change count starts out as 0.
            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //Any transformation related changes go here. these typically cost 1 change. these can be anything from body parts to gender (which technically also changes body parts,
            //but w/e). You are required to make sure you return as soon as you've applied changeCount changes, but a single line of code can be applied at the end of a change to do
            //this for you.

            //paste this line after any tf is applied, and it will: automatically decrement the remaining changes count. if it becomes 0 or less, apply the total number of changes
            //underwent to the target's change count (if applicable) and then return the StringBuilder content.
            //if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);

#warning fix me
            int ngPlus(int value) => value;


            //+3 spe if less than 50
            if (target.speed < ngPlus(50))
            {
                target.IncreaseSpeed(3);
            }
            //+2 spe if less than 75
            else if (target.speed < ngPlus(75))
            {
                target.IncreaseSpeed(2);
            }
            //+1 if above 75.
            else
            {
                target.IncreaseSpeed();
            }

            // ------------- Sexual changes -------------
            //-Nipples reduction to 1 per tit.
            if (target.genitals.hasQuadNipples && Utils.Rand(4) == 0)
            {
                target.genitals.SetQuadNipples(false);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //-Remove extra breast rows
            if (target.breasts.Count > 1 && Utils.Rand(3) == 0 && !hyperHappy)
            {
                target.RemoveExtraBreastRows();
            }

            //-Butt > 5 - decrease butt size
            if (target.butt.size > 5 && Utils.Rand(4) == 0)
            {
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }

                target.butt.ShrinkButt();
            }

            if (target.gender.HasFlag(Gender.FEMALE))
            {
                //Breasts > D cup - Decrease breast size by up to 3 cups
                //Breasts < B cup - Increase breast size by 1 cup
                if (target.breasts.Any(x => x.cupSize > CupSize.D || x.cupSize < CupSize.B) && Utils.Rand(3) == 0)
                {
                    foreach (Breasts breast in target.breasts)
                    {
                        if (breast.cupSize > CupSize.D)
                        {
                            breast.ShrinkBreasts((byte)(1 + Utils.Rand(3)));
                        }
                        else if (breast.cupSize < CupSize.B)
                        {
                            breast.GrowBreasts();
                        }
                    }

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                //Hips > 12 - decrease hip size by 1-3 sizes
                if (target.hips.size > 12 && Utils.Rand(3) == 0)
                {
                    target.hips.ShrinkHips((byte)(1 + Utils.Rand(3)));

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                //Hips < 6 - increase hip size by 1-3 sizes
                if (target.hips.size < 6 && Utils.Rand(3) == 0)
                {
                    target.hips.GrowHips((byte)(1 + Utils.Rand(3)));
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                if (target.genitals.nippleLength > 1 && Utils.Rand(3) == 0)
                {
                    target.genitals.SetNippleLength(target.genitals.nippleLength / 2);
                }

                if (target.hasVagina && target.vaginas[0].wetness < VaginalWetness.SLICK && Utils.Rand(4) == 0)
                {
                    target.vaginas[0].IncreaseWetness();
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                //Increase tone (up to 65)
                if (target.build.muscleTone < 65 && Utils.Rand(3) == 0)
                {
                }

                //Decrease thickness (down to 35)
                if (target.build.thickness > 35 && Utils.Rand(3) == 0)
                {
                }
            }

            if (target.gender == Gender.MALE)
            {
                //Breasts > B cup (or applicable male max size if it's somehow > B cup) - decrease by 1 cup size
                CupSize targetSize = EnumHelper.Max(target.genitals.smallestPossibleMaleCupSize, CupSize.B);
                if (target.genitals.BiggestCupSize() > targetSize && Utils.Rand(3) == 0)
                {
                    foreach (Breasts breast in target.breasts)
                    {
                        if (breast.cupSize > targetSize)
                        {
                            breast.ShrinkBreasts();
                        }
                    }

                    target.IncreaseSpeed();

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                if (target.genitals.nippleLength > 1 && Utils.Rand(3) == 0)
                {
                    target.genitals.SetNippleLength(target.genitals.nippleLength / 2);
                }

                //Hips > 10 - decrease hip size by 1-3 sizes
                if (target.hips.size > 10 && Utils.Rand(3) == 0)
                {
                    target.hips.ShrinkHips((byte)(1 + Utils.Rand(3)));
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                //Hips < 2 - increase hip size by 1-3 sizes
                if (target.hips.size < 2 && Utils.Rand(3) == 0)
                {
                    target.hips.GrowHips((byte)(1 + Utils.Rand(3)));
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                //Increase tone (up to 70)
                if (target.build.muscleTone < 70 && Utils.Rand(3) == 0)
                {
                }

                //Decrease thickness (down to 35)
                if (target.build.thickness > 35 && Utils.Rand(3) == 0)
                {
                }
            }

            if (target.gender.HasFlag(Gender.MALE))
            {
                //C**k -> Red Panda C**k
                if (target.hasCock && target.cocks[0].type != CockType.RED_PANDA && Utils.Rand(3) == 0)
                {
                    target.genitals.UpdateCock(0, CockType.RED_PANDA);
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                C**k shortest = target.genitals.ShortestCock();

                //C**k < 6 inches - increase by 1-2 inches
                if (shortest.length < 6 && Utils.Rand(3) == 0)
                {
                    double increment = shortest.IncreaseLength(1 + Utils.Rand(2));
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                C**k longest = target.genitals.LongestCock();

                //Shrink oversized cocks
                if (longest.length > 16 && Utils.Rand(3) == 0)
                {
                    longest.DecreaseLength((Utils.Rand(10) + 5) / 10);
                    if (longest.girth > 3)
                    {
                        longest.DecreaseThickness((Utils.Rand(4) + 1) / 10);
                    }
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }

                C**k smallestArea = target.genitals.SmallestCockByArea();

                //C**k thickness <2 - Increase c**k thickness
                if (smallestArea.area < 10 && Utils.Rand(3) == 0)
                {
                    smallestArea.IncreaseThickness(1.5);
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            //Remove additional cocks
            if (target.cocks.Count > 1 && Utils.Rand(3) == 0)
            {
                //what a dick. removes the second, and only the second. not the last one or anything. ok. it's supported now.
                target.genitals.RemoveCockAt(1, 1);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //Remove additional balls/remove uniball
            if (target.balls.count > 0 && Utils.Rand(3) == 0)
            {
                if (target.balls.size > 5)
                {
                    target.balls.ShrinkBalls((byte)(2 + Utils.Rand(3)));

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
                else if (target.balls.size > 2)
                {
                    target.balls.ShrinkBalls(1);

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
                else if (target.balls.count != 2)
                {
                    //removes uniball, sets count to 2.
                    target.balls.MakeStandard();

                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            //Ovi perk loss
            if (target.womb.canRemoveOviposition && Utils.Rand(5) == 0)
            {
                target.womb.ClearOviposition();
                //if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);
            }

            // ------------- Physical changes -------------
            // Ears
            if (target.ears.type != EarType.RED_PANDA && Utils.Rand(3) == 0)
            {
                EarData oldData = target.ears.AsReadOnlyData();
                target.UpdateEars(EarType.RED_PANDA);
                sb.Append(UpdateEarsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            // Remove non-cockatrice antennae
            if (target.antennae.type != AntennaeType.COCKATRICE && !target.antennae.isDefault && Utils.Rand(3) == 0)
            {
                AntennaeData oldData = target.antennae.AsReadOnlyData();
                target.RestoreAntennae();
                sb.Append(RestoredAntennaeText(target, oldData));
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            // Restore eyes, if more than two
            if (target.eyes.count > 2 && Utils.Rand(4) == 0)
            {
                EyeData oldData = target.eyes.AsReadOnlyData();
                target.RestoreEyes();
                sb.Append(RestoredEyesText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            // Hair
            // store current states first
            bool     hasPandaHairColor = Species.RED_PANDA.availableHairColors.Contains(target.hair.hairColor);
            bool     hasNormalHair     = target.hair.type == HairType.NORMAL;
            HairType oldHairType       = target.hair.type;

            if ((!hasNormalHair || target.hair.length == 0 || !hasPandaHairColor) && Utils.Rand(3) == 0)
            {
                target.UpdateHair(HairType.NORMAL);
                if (!hasPandaHairColor)
                {
                    target.hair.SetHairColor(Utils.RandomChoice(Species.RED_PANDA.availableHairColors));
                }

                if (target.hair.length == 0)
                {                 // target is bald
                    target.hair.SetHairLength(1);
                }

                target.hair.SetHairGrowthStatus(true);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            // Face
            if (target.face.type != FaceType.RED_PANDA && target.ears.type == EarType.RED_PANDA && target.body.IsFurBodyType() && Utils.Rand(3) == 0)
            {
                FaceData oldData = target.face.AsReadOnlyData();
                target.UpdateFace(FaceType.RED_PANDA);
                sb.Append(UpdateFaceText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            // Arms
            if (target.arms.type != ArmType.RED_PANDA && target.ears.type == EarType.RED_PANDA && target.tail.type == TailType.RED_PANDA && Utils.Rand(3) == 0)
            {
                ArmData oldData = target.arms.AsReadOnlyData();
                target.UpdateArms(ArmType.RED_PANDA);
                sb.Append(UpdateArmsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            // Legs
            if (target.lowerBody.type != LowerBodyType.RED_PANDA && target.arms.type == ArmType.RED_PANDA && Utils.Rand(4) == 0)
            {
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                target.UpdateLowerBody(LowerBodyType.RED_PANDA);
                sb.Append(UpdateLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            // Tail
            if (target.tail.type != TailType.RED_PANDA && Utils.Rand(4) == 0)
            {
                TailData oldData = target.tail.AsReadOnlyData();
                target.UpdateTail(TailType.RED_PANDA);
                sb.Append(UpdateTailText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            // SKin


            // Fix the underBody, if the skin is already furred
            if (target.body.type == BodyType.SIMPLE_FUR && Utils.Rand(3) == 0)
            {
                BodyData oldData = target.body.AsReadOnlyData();
                target.UpdateBody(BodyType.UNDERBODY_FUR, new FurColor(HairFurColors.RUSSET), new FurColor(HairFurColors.BLACK));
                sb.Append(UpdateBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            else if (!target.body.IsFurBodyType() && target.arms.type == ArmType.RED_PANDA && target.lowerBody.type == LowerBodyType.RED_PANDA && Utils.Rand(4) == 0)
            {
                BodyData oldData = target.body.AsReadOnlyData();
                target.UpdateBody(BodyType.UNDERBODY_FUR, new FurColor(HairFurColors.RUSSET), new FurColor(HairFurColors.BLACK));
                sb.Append(UpdateBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //FAILSAFE CHANGE
            if (remainingChanges == changeCount)
            {
                if (Utils.Rand(100) == 0)
                {
                }
                else
                {
                    if (target is CombatCreature)
                    {
                        ((CombatCreature)target).AddHP(250);
                    }

                    target.ChangeLust(3);
                }
            }


            //this is the fallthrough that occurs when a tf item goes through all the changes, but does not proc enough of them to exit early. it will apply however many changes
            //occurred, then return the contents of the stringbuilder.
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }
Example #19
0
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            //by default, this is 2 rolls at 50%, so a 25% chance of 0 additional tfs, 50% chance of 1 additional tf, 25% chance of 2 additional tfs.
            //also takes into consideration any perks that increase or decrease tf effectiveness. if you need to roll out your own, feel free to do so.
            int changeCount      = GenerateChangeCount(target, new int[] { 2, 2, 3 });
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

            //For all of these, any text regarding the transformation should be instead abstracted out as an abstract string function. append the result of this abstract function
            //to the string builder declared above (aka sb.Append(FunctionCall(variables));) string builder is just a fancy way of telling the compiler that you'll be creating a
            //long string, piece by piece, so don't do any crazy optimizations first.

            //the initial text for starting the transformation. feel free to add additional variables to this if needed.
            sb.Append(InitialTransformationText(target));

            //Add any free changes here - these can occur even if the change count is 0. these include things such as change in stats (intelligence, etc)
            //change in height, hips, and/or butt, or other similar stats.

            //this will handle the edge case where the change count starts out as 0.
            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //Any transformation related changes go here. these typically cost 1 change. these can be anything from body parts to gender (which technically also changes body parts,
            //but w/e). You are required to make sure you return as soon as you've applied changeCount changes, but a single line of code can be applied at the end of a change to do
            //this for you.

            //paste this line after any tf is applied, and it will: automatically decrement the remaining changes count. if it becomes 0 or less, apply the total number of changes
            //underwent to the target's change count (if applicable) and then return the StringBuilder content.
            //if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);


            //Text go!

            //Speed raises up to 75
            if (target.relativeSpeed < 75 && Utils.Rand(3) == 0)
            {
                //low speed
                if (target.relativeSpeed <= 30)
                {
                    target.ChangeSpeed(2);
                }
                //medium speed
                else if (target.relativeSpeed <= 60)
                {
                    target.ChangeSpeed(1);
                }
                //high speed
                else
                {
                    target.ChangeSpeed(.5);
                }
            }
            //Strength raises to 40
            if (target.relativeStrength < 40 && Utils.Rand(3) == 0)
            {
                target.ChangeStrength(1);
            }
            //Strength ALWAYS drops if over 60
            //Does not add to change total
            else if (target.relativeStrength > 60 && Utils.Rand(2) == 0)
            {
                target.ChangeStrength(-2);
            }
            //Toughness drops if over 50
            //Does not add to change total
            if (target.relativeToughness > 50 && Utils.Rand(2) == 0)
            {
                target.ChangeToughness(-2);
            }
            //Intelliloss
            if (Utils.Rand(4) == 0)
            {
                target.ChangeIntelligence(-1);
            }

            //Libido gain
            if (target.relativeLibido < 80 && Utils.Rand(4) == 0)
            {
                target.DeltaCreatureStats(lib: 1, sens: .25);
            }

            //Sexual changes would go here if I wasn't a tard.
            //Heat
            if (Utils.Rand(4) == 0)
            {
                bool intensified = target.HasTimedEffect <Heat>();

                if (target.GoIntoHeat())
                {
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }

            //Shrink the boobalies down to A for men or C for girls.
            if (Utils.Rand(4) == 0 && !hyperHappy)
            {
                CupSize targetSize;
                //female or herm. targeting C cup, but will respect smallest possible cup if it's somehow above a c cup.
                if (target.gender.HasFlag(Gender.FEMALE))
                {
                    targetSize = EnumHelper.Max(CupSize.C, target.genitals.smallestPossibleFemaleCupSize);
                }
                //male or genderless. targeting A cup, but will respect smallest possible cup if somehow above an a cup.
                else
                {
                    targetSize = EnumHelper.Max(CupSize.A, target.genitals.smallestPossibleMaleCupSize);
                }
                //IT IS!
                int rowsShrunk = 0;
                foreach (Breasts row in target.breasts)
                {
                    //If this row is over threshhold
                    if (row.cupSize > targetSize)
                    {
                        //Big change
                        if (row.cupSize > CupSize.EE_BIG)
                        {
                            row.ShrinkBreasts((byte)(2 + Utils.Rand(3)));
                        }
                        //Small change
                        else
                        {
                            row.ShrinkBreasts(1);
                        }
                        rowsShrunk++;
                    }
                }
                //Count that t**s were shrunk
                if (rowsShrunk > 0)
                {
                    remainingChanges--;
                    if (remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }
            //Cat dangly-doo.
            if (target.cocks.Count > 0 && target.cocks.Any(x => x.type != CockType.CAT) && (target.ears.type == EarType.CAT || Utils.Rand(3) > 0) &&
                (target.tail.type == TailType.CAT || Utils.Rand(3) > 0) && Utils.Rand(4) == 0)
            {
                C**k nonCat = target.cocks.First(x => x.type != CockType.CAT);

                target.genitals.UpdateCock(nonCat, CockType.CAT);
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Cat penorz shrink
            if (target.genitals.HasAnyCocksOfType(CockType.CAT) && Utils.Rand(3) == 0 && !hyperHappy)
            {
                //loop through and find a cat wang.

                foreach (C**k catCock in target.cocks.Where(x => x.type == CockType.CAT))
                {
                    //lose 33% size until under 10, then lose 2" at a time
                    if (catCock.length > 16)
                    {
                        catCock.SetLength(catCock.length * .66);
                    }
                    else if (catCock.length > 6)
                    {
                        catCock.DecreaseLength(2);
                    }
                    if (catCock.length / 5 < catCock.girth && catCock.girth > 1.25)
                    {
                        catCock.SetGirth(catCock.length / 6);
                    }
                }

                //(big sensitivity boost)
                target.ChangeSensitivity(5);
                //Make note of other dicks changing
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Neck restore
            if (target.neck.type != NeckType.HUMANOID && Utils.Rand(4) == 0)
            {
                NeckData oldData = target.neck.AsReadOnlyData();
                target.RestoreNeck();
                sb.Append(RestoredNeckText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Rear body restore
            if (!target.back.isDefault && Utils.Rand(5) == 0)
            {
                BackData oldData = target.back.AsReadOnlyData();
                target.RestoreBack();
                sb.Append(RestoredBackText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Ovi perk loss
            if (target.womb.canRemoveOviposition && Utils.Rand(5) == 0)
            {
                target.womb.ClearOviposition();
                sb.Append(ClearOvipositionText(target));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Body type changes. Teh rarest of the rare.
            // Catgirl-face -> cat-morph-face
            if (target.face.type == FaceType.CAT && !target.face.isFullMorph && target.tongue.type == TongueType.CAT && target.ears.type == EarType.CAT &&
                target.tail.type == TailType.CAT && target.lowerBody.type == LowerBodyType.CAT && target.arms.type == ArmType.CAT &&
                (target.body.IsFurBodyType() /*|| (target.body.type == BodyType.REPTILE && Species.SPHINX.Score(target) >= 4)*/) && Utils.Rand(5) == 0)
            {
                target.face.StrengthenFacialMorph();
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //DA EARZ
            if (target.ears.type != EarType.CAT && Utils.Rand(5) == 0)
            {
                EarData oldData = target.ears.AsReadOnlyData();
                target.UpdateEars(EarType.CAT);
                sb.Append(UpdateEarsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //DA TAIL (IF ALREADY HAZ URZ)
            if (target.tail.type != TailType.CAT && target.ears.type == EarType.CAT && Utils.Rand(5) == 0)
            {
                TailData oldData = target.tail.AsReadOnlyData();
                target.UpdateTail(TailType.CAT);
                sb.Append(UpdateTailText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Da paws (if already haz ears & tail)
            if (target.tail.type == TailType.CAT && target.ears.type == EarType.CAT && Utils.Rand(5) == 0 && target.lowerBody.type != LowerBodyType.CAT)
            {
                //hoof to cat:
                if (target.lowerBody.type == LowerBodyType.HOOVED)
                {
                }
                //Goo to cat
                else if (target.lowerBody.type == LowerBodyType.GOO)
                {
                }
                //non hoof to cat:
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                target.UpdateLowerBody(LowerBodyType.CAT);
                sb.Append(UpdateLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //TURN INTO A FURRAH! OH SHIT
            if (target.tail.type == TailType.CAT && target.ears.type == EarType.CAT && Utils.Rand(5) == 0 && target.lowerBody.type == LowerBodyType.CAT && !target.body.IsFurBodyType())
            {
                BodyData oldData = target.body.AsReadOnlyData();
                target.UpdateBody(BodyType.UNDERBODY_FUR);
                sb.Append(UpdateBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            // Fix old cat faces without cat-eyes.
            if (target.face.type == FaceType.CAT && target.eyes.type != EyeType.CAT && Utils.Rand(3) == 0)
            {
                EyeData oldData = target.eyes.AsReadOnlyData();
                target.UpdateEyes(EyeType.CAT);
                sb.Append(UpdateEyesText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //CAT-FACE! FULL ON FURRY! RAGE AWAY NEKOZ
            if (target.tail.type == TailType.CAT && target.ears.type == EarType.CAT && target.lowerBody.type == LowerBodyType.CAT && target.face.type != FaceType.CAT && Utils.Rand(5) == 0)
            {
                //Gain cat face, replace old face
                //MOD NOTE: but not a full-morph cat face. just level 1.
                FaceData oldData = target.face.AsReadOnlyData();
                target.UpdateFace(FaceType.CAT);
                sb.Append(UpdateFaceText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            // Cat-tongue
            if (target.face.type == FaceType.CAT && target.tongue.type != TongueType.CAT && Utils.Rand(5) == 0)
            {
                TongueData oldData = target.tongue.AsReadOnlyData();
                target.UpdateTongue(TongueType.CAT);
                sb.Append(UpdateTongueText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Arms
            if (target.arms.type != ArmType.CAT && target.body.IsFurBodyType() && target.tail.type == TailType.CAT && target.lowerBody.type == LowerBodyType.CAT && Utils.Rand(4) == 0)
            {
                ArmData oldData = target.arms.AsReadOnlyData();
                target.UpdateArms(ArmType.CAT);
                sb.Append(UpdateArmsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            // Remove gills
            if (Utils.Rand(4) == 0 && !target.gills.isDefault)
            {
                GillData oldData = target.gills.AsReadOnlyData();
                target.RestoreGills();
                sb.Append(RestoredGillsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //FAILSAFE CHANGE
            if (remainingChanges == changeCount)
            {
                (target as CombatCreature)?.AddHP(50);
                target.ChangeLust(3);
            }

            //this is the fallthrough that occurs when a tf item goes through all the changes, but does not proc enough of them to exit early. it will apply however many changes
            //occurred, then return the contents of the stringbuilder.
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            //by default, this is 2 rolls at 50%, so a 25% chance of 0 additional tfs, 50% chance of 1 additional tf, 25% chance of 2 additional tfs.
            //also takes into consideration any perks that increase or decrease tf effectiveness. if you need to roll out your own, feel free to do so.
            int changeCount      = GenerateChangeCount(target, new int[] { 2, 2 });
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

            //For all of these, any text regarding the transformation should be instead abstracted out as an abstract string function. append the result of this abstract function
            //to the string builder declared above (aka sb.Append(FunctionCall(variables));) string builder is just a fancy way of telling the compiler that you'll be creating a
            //long string, piece by piece, so don't do any crazy optimizations first.

            //the initial text for starting the transformation. feel free to add additional variables to this if needed.

            //Add any free changes here - these can occur even if the change count is 0. these include things such as change in stats (intelligence, etc)
            //change in height, hips, and/or butt, or other similar stats.



            double crit = 0;

            if (Utils.Rand(100) < 15)
            {
                crit = Utils.Rand(20) / 10.0 + 2;
            }

            bool hasCrit() => crit > 1;

            sb.Append(InitialTransformationText(target, crit));


            //STAT CHANGES - TOU SPE INT RANDOM CHANCE, LIB LUST COR ALWAYS UPPED
            target.DeltaCreatureStats(lib: 1 + Utils.Rand(2), lus: 5 + Utils.Rand(10), corr: 1 + Utils.Rand(5));
            if (target.relativeToughness < 70 && Utils.Rand(3) == 0)
            {
                double delta = target.ChangeToughness(crit);
                sb.Append(IncreasedToughnessText(crit, delta));
            }
            if (target.relativeSpeed > 30 && Utils.Rand(7) == 0)
            {
                double loss = target.DecreaseSpeed(crit);
                sb.Append(DecreasedSpeedText(crit, loss));
            }
            if (target.relativeIntelligence < 60 && Utils.Rand(7) == 0)
            {
                double delta = target.ChangeIntelligence(crit);
                sb.Append(IncreasedIntelligenceText(crit, delta));
            }

            //handle edge case where we start at 0.
            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //Non-free changes.

            //if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);


            //MUTATIONZZZZZ
            //PRE-CHANGES: become biped, remove horns, remove wings, give human tongue, remove claws, remove antennea
            //no claws
            if (Utils.Rand(4) == 0 && target.arms.hands.isClaws)
            {
                ArmData oldData = target.arms.AsReadOnlyData();

                if (target.RestoreArms())
                {
                    sb.Append(RestoredArmsText(target, oldData));
                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }
            //remove antennae
            if (target.antennae.type != AntennaeType.NONE && Utils.Rand(3) == 0)
            {
                AntennaeData oldData = target.antennae.AsReadOnlyData();
                target.RestoreAntennae();

                sb.Append(RestoredAntennaeText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //remove horns
            if (target.horns.type != HornType.NONE && Utils.Rand(3) == 0)
            {
                HornData oldData = target.horns.AsReadOnlyData();
                target.RestoreHorns();
                sb.Append(RestoredHornsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //remove wings
            if ((target.wings.type != WingType.NONE || target.back.type == BackType.SHARK_FIN) && Utils.Rand(3) == 0)
            {
                BackData oldBack = target.back.AsReadOnlyData();
                if (target.back.type == BackType.SHARK_FIN)
                {
                    target.RestoreBack();
                }

                WingData oldWings = target.wings.AsReadOnlyData();
                target.RestoreWings();
                sb.Append(RestoredBackAndWings(target, oldWings, oldBack));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //give human tongue
            if (target.tongue.type != TongueType.HUMAN && Utils.Rand(3) == 0)
            {
                //MOD NOTE: this was incorrect - this actually was === (equal with strict typechecking) instead of = (assign). so this was an implicit bool.
                //this is part of the reason why the rework forces all value updates as function calls. the more you know.
                TongueData oldData = target.tongue.AsReadOnlyData();
                target.RestoreTongue();
                sb.Append(RestoredTongueText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //remove non-wolf eyes
            if (Utils.Rand(3) == 0 && target.eyes.type != EyeType.HUMAN && target.eyes.type != EyeType.WOLF)
            {
                EyeData oldData = target.eyes.AsReadOnlyData();
                target.RestoreEyes();
                sb.Append(RestoredEyesText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //normal legs
            if (target.lowerBody.type != LowerBodyType.WOLF && Utils.Rand(4) == 0)
            {
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                target.RestoreLowerBody();
                sb.Append(RestoredLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //normal arms
            if (Utils.Rand(4) == 0)
            {
                ArmData oldData = target.arms.AsReadOnlyData();
                target.RestoreArms();
                sb.Append(RestoredArmsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            HairData oldHair = target.hair.AsReadOnlyData();

            //remove feather hair
            if (Utils.Rand(4) == 0 && RemoveFeatheryHair(target))
            {
                sb.Append(RemovedFeatheryHairText(target, oldHair));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //remove basilisk hair
            if (Utils.Rand(4) == 0 && target.hair.IsBasiliskHair())
            {
                HairData oldData = target.hair.AsReadOnlyData();
                target.RestoreHair();
                target.hair.SetHairLength(0);
                target.hair.ResumeNaturalGrowth();

                sb.Append(RemovedBasiliskHair(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //MUTATIONZ AT ANY TIME: wolf dick, add/decrease breasts, decrease breast size if above D
            //get a wolf dick
            //if ya genderless we give ya a dick cuz we nice like that
            //MOD: Now respects hyper happy.
            if (target.gender == Gender.GENDERLESS && !hyperHappy)
            {
                target.genitals.AddCock(CockType.WOLF, Utils.Rand(4) + 4, Utils.Rand(8) / 4.0 + 0.25, 1.5);

                sb.Append(BecameMaleByGrowingCock(target));

                target.DeltaCreatureStats(lib: 3, sens: 2, lus: 25);
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //if ya got a dick that's ok too we'll change it to wolf
            //MOD NOTE: but only if you don't only have wolf cocks, of course (because if they are all wolf cocks, we can't make anything wolf cocks, duh).
            if (target.hasCock && !target.genitals.OnlyHasCocksOfType(CockType.WOLF) && Utils.RandBool())
            {
                //MOD NOTE: no longer shamelessly copy/pasted from dog c**k, because we have better ways of looping in C#.

                C**k     firstNonWolf = target.cocks.First(x => x.type != CockType.WOLF);
                CockData oldData      = firstNonWolf.AsReadOnlyData();

                //Select first non-wolf c**k
                //MOD: now using type description because we can, so the fact this used generic is irrelevant now :)

                if (firstNonWolf.type == CockType.HORSE)
                {                 //horses get changes
                    if (firstNonWolf.length > 6)
                    {
                        firstNonWolf.DecreaseLength(2);
                    }
                    else
                    {
                        firstNonWolf.DecreaseLength(.5);
                    }

                    firstNonWolf.IncreaseThickness(.5);
                }

                target.DeltaCreatureStats(sens: 3, lus: 5 * crit);


                target.genitals.UpdateCockWithKnot(firstNonWolf, CockType.WOLF, 1.5);
                firstNonWolf.IncreaseThickness(2);

                sb.Append(ChangedCockToWolf(target, oldData, oldData.cockIndex));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //titties for those who got titties
            //wolfs have 8 nips so, 4 rows max. fen has no power here I'm making a wolf not a dog.
            //MOD: still stolen shamelessly from dog and updated, but now with the modern format. woo!

            //MOD: if breasts can be updated (not flat) and we flip a coin.
            if (target.genitals.BiggestCupSize() > CupSize.FLAT && Utils.RandBool())
            {
                if (target.breasts.Count < 4)
                {
                    byte breastCount = (byte)target.breasts.Count;
                    BreastCollectionData oldBreastData = target.genitals.allBreasts.AsReadOnlyData();

                    //in vanilla code, we had some strange checks here that required the first row (and only the first row) be a certain size.
                    //now, we check all the rows, but no longer require any of them to be a certain size. instead, if it's not the right size, we make it that size,
                    //then add the row. so, for two rows, your first row must be at least a B-Cup. for 3: first must be a C cup, second an A cup.
                    //if we supported 4 rows, it'd be D, B, A.

                    for (int x = 0; x < target.breasts.Count; x++)
                    {
                        CupSize requiredSize = (CupSize)(byte)(target.breasts.Count - x);
                        if (x == 0)
                        {
                            requiredSize++;
                        }
                        if (target.breasts[x].cupSize < requiredSize)
                        {
                            target.breasts[x].SetCupSize(requiredSize);
                        }
                    }

                    target.genitals.AddBreastRow(target.breasts[breastCount - 1].cupSize.ByteEnumSubtract(1));


                    bool doCrit = false, uberCrit = false;
                    if (target.breasts.Count == 2)
                    {
                        target.IncreaseCreatureStats(lus: 5, sens: 6);
                    }
                    else if (hasCrit())
                    {
                        doCrit = true;
                        if (crit > 2)
                        {
                            target.IncreaseCreatureStats(sens: 6, lus: 15);
                            uberCrit = true;
                        }
                        else
                        {
                            target.IncreaseCreatureStats(sens: 3, lus: 10);
                        }
                    }

                    sb.Append(UpdateAndGrowAdditionalRowText(target, oldBreastData, doCrit, uberCrit));


                    if (--remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
                else
                {
                    BreastCollectionData oldBreastData = target.genitals.allBreasts.AsReadOnlyData();

                    //only call the normalize text if we actually did anything. related, anthro breasts now returns a bool. thanks, fox tfs.
                    if (target.genitals.AnthropomorphizeBreasts())
                    {
                        sb.Append(NormalizedBreastSizeText(target, oldBreastData));
                    }
                }
            }


            //Remove a breast row if over 4. afaik this should never happen.
            if (target.breasts.Count > 4 && Utils.Rand(3) == 0)
            {
                var oldData = target.breasts[target.breasts.Count - 1].AsReadOnlyData();
                target.genitals.RemoveBreastRows(1);

                sb.Append(RemovedExcessRow(target, oldData));
            }
            //Grow breasts if has v****a and has no breasts/nips
            //Shrink breasts if over D-cup
            CupSize targetSize = EnumHelper.Max(target.genitals.smallestPossibleCupSize, CupSize.D);

            if (!hyperHappy && target.genitals.BiggestCupSize() > targetSize && Utils.Rand(3) == 0)
            {
                BreastCollectionData oldCollection = target.genitals.allBreasts.AsReadOnlyData();

                bool changedAnything = false;
                foreach (Breasts row in target.breasts)
                {
                    //If this row is over threshhold
                    if (row.cupSize > targetSize)
                    {
                        //Big change
                        if (row.cupSize > CupSize.EE_BIG)
                        {
                            changedAnything |= row.ShrinkBreasts((byte)(2 + Utils.Rand(3))) > 0;
                        }
                        //Small change
                        else
                        {
                            changedAnything |= row.ShrinkBreasts() > 0;
                        }
                        //Increment changed rows
                    }
                }
                //Count shrinking
                if (changedAnything)
                {
                    sb.Append(ShrunkRowsText(target, oldCollection));

                    remainingChanges--;
                    if (remainingChanges <= 0)
                    {
                        return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                    }
                }
            }
            //MUTATIONZ LEVEL 1: fur, stop hair growth, ears, tail
            //Gain fur
            if (Utils.Rand(5) == 0 && !target.body.IsFurBodyType())
            {
                BodyData oldData = target.body.AsReadOnlyData();
                Species.WOLF.GetRandomFurColors(out FurColor primary, out FurColor underbody);
                if (FurColor.IsNullOrEmpty(underbody))
                {
                    target.UpdateBody(BodyType.UNDERBODY_FUR, primary);
                }
                else
                {
                    target.UpdateBody(BodyType.UNDERBODY_FUR, primary, underbody);
                }

                sb.Append(UpdateBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Ears time
            if (Utils.Rand(3) == 0 && target.ears.type != EarType.WOLF)
            {
                EarData oldData = target.ears.AsReadOnlyData();
                target.UpdateEars(EarType.WOLF);
                sb.Append(UpdateEarsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Wolf tail
            if (Utils.Rand(3) == 0 && target.tail.type != TailType.WOLF)
            {
                TailData oldData = target.tail.AsReadOnlyData();
                target.UpdateTail(TailType.WOLF);
                sb.Append(UpdateTailText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Sets hair normal
            if (target.hair.type != HairType.NORMAL && Utils.Rand(3) == 0)
            {
                HairData oldData = target.hair.AsReadOnlyData();
                target.RestoreHair();
                sb.Append(RestoreHairText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //MUTATIONZ LEVEL 2: fur->arms fur+tail+ears->face stophair->nohair fur+tail->legs
            //gain wolf face
            if (target.face.type != FaceType.WOLF && target.ears.type == EarType.WOLF && target.tail.type == TailType.WOLF && target.body.IsFurBodyType() && Utils.Rand(5) == 0)
            {
                FaceData oldData = target.face.AsReadOnlyData();
                target.UpdateFace(FaceType.WOLF);
                sb.Append(UpdateFaceText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //legz
            if (target.lowerBody.legCount == 2 && target.lowerBody.type != LowerBodyType.WOLF && target.tail.type == TailType.WOLF && target.body.IsFurBodyType() && Utils.Rand(4) == 0)
            {
                //Hooman feets
                //Hooves -> Paws
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                target.UpdateLowerBody(LowerBodyType.WOLF);
                sb.Append(UpdateLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //MUTATIONZ LEVEL 3: face->eyes
            if (target.eyes.type != EyeType.WOLF && target.face.type == FaceType.WOLF && Utils.Rand(4) == 0)
            {
                EyeData oldData = target.eyes.AsReadOnlyData();
                target.UpdateEyes(EyeType.WOLF);
                sb.Append(UpdateEyesText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //MISC CRAP
            //Neck restore
            if (target.neck.type != NeckType.HUMANOID && Utils.Rand(4) == 0)
            {
                NeckData oldData = target.neck.AsReadOnlyData();
                target.RestoreNeck();
                sb.Append(RestoredNeckText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Rear body restore
            if (!target.back.isDefault && Utils.Rand(5) == 0)
            {
                BackData oldData = target.back.AsReadOnlyData();
                target.RestoreBack();
                sb.Append(RestoredBackText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Ovi perk loss
            if (target.womb.canRemoveOviposition && Utils.Rand(5) == 0)
            {
                target.womb.ClearOviposition();
                sb.Append(ClearOvipositionText(target));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            if (Utils.Rand(3) == 0)
            {
                var res = target.build.ChangeMuscleToneToward(100, 4);
                sb.Append(AdjustToneText(target, 4, res));
            }
            if (Utils.Rand(3) == 0)
            {
                var adjustedAmount = target.build.ChangeThicknessToward(75, 3);
                sb.Append(AdjustThicknessText(target, adjustedAmount));
            }



            //this is the fallthrough that occurs when a tf item goes through all the changes, but does not proc enough of them to exit early. it will apply however many changes
            //occurred, then return the contents of the stringbuilder.
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }
Example #21
0
 protected virtual string UpdateArmsText(Creature target, ArmData oldData)
 {
     return(target.arms.TransformFromText(oldData));
 }
Example #22
0
 public IEnumerable <BlockComponent> GetRatio()
 {
     return((from a in Enum.GetValues(typeof(AllocationGroups)).Cast <AllocationGroups>()
             where a != AllocationGroups.NotApplicable
             select ArmData.GetRatio(a)).ToList());
 }
 protected virtual string RestoredArmsText(Creature target, ArmData oldData)
 {
     return(target.arms.RestoredText(oldData));
 }
        /** Original Credits:
         * @since March 26, 2018
         * @author Stadler76
         */
        protected internal override string DoTransformation(Creature target, out bool isBadEnd)
        {
            isBadEnd = false;

            //by default, this is 2 rolls at 50%, so a 25% chance of 0 additional tfs, 50% chance of 1 additional tf, 25% chance of 2 additional tfs.
            //also takes into consideration any perks that increase or decrease tf effectiveness. if you need to roll out your own, feel free to do so.
            int changeCount      = GenerateChangeCount(target, new int[] { 2, 2 }, isEnhanced ? 3 : 1);
            int remainingChanges = changeCount;

            StringBuilder sb = new StringBuilder();

            //For all of these, any text regarding the transformation should be instead abstracted out as an abstract string function. append the result of this abstract function
            //to the string builder declared above (aka sb.Append(FunctionCall(variables));) string builder is just a fancy way of telling the compiler that you'll be creating a
            //long string, piece by piece, so don't do any crazy optimizations first.

            //the initial text for starting the transformation. feel free to add additional variables to this if needed.
            sb.Append(InitialTransformationText(target));

            //Add any free changes here - these can occur even if the change count is 0. these include things such as change in stats (intelligencelligence, etc)
            //change in height, hips, and/or butt, or other similar stats.

            //this will handle the edge case where the change count starts out as 0.
            if (remainingChanges <= 0)
            {
                return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
            }

            //Any transformation related changes go here. these typically cost 1 change. these can be anything from body parts to gender (which technically also changes body parts,
            //but w/e). You are required to make sure you return as soon as you've applied changeCount changes, but a single line of code can be applied at the end of a change to do
            //this for you.

            //paste this line after any tf is applied, and it will: automatically decrement the remaining changes count. if it becomes 0 or less, apply the total number of changes
            //underwent to the target's change count (if applicable) and then return the StringBuilder content.
            //if (--remainingChanges <= 0) return ApplyChangesAndReturn(target, sb, changeCount - remainingChanges);

            //Used as a holding variable for biggest dicks and the like
            //****************
            //General Effects:
            //****************
            //-Int less than 10
            if (target is IExtendedCreature extendedCreature && !extendedCreature.extendedData.resistsTFBadEnds && target.intelligence < 10)
            {
                if (target.intelligence < 8 && Species.KANGAROO.Score(target) >= 5)
                {
                    isBadEnd = true;
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }

            //-Speed to 70
            if (target.relativeSpeed < 70 && Utils.Rand(3) == 0)
            {
                //2 points up if below 40!
                if (target.relativeSpeed < 40)
                {
                    target.ChangeSpeed(1);
                }

                target.ChangeSpeed(1);
            }
            //-Int to 10
            if (target.intelligence > 2 && Utils.Rand(3) == 0)
            {
                //Gain dumb (smart!)
                //gain dumb (30-10 int):

                //gain dumb (10-1 int):
                target.ChangeIntelligence(-1);
            }

            //****************
            //Appearance Effects:
            //****************
            //-Hip widening funtimes
            if (Utils.Rand(4) == 0 && target.hips.size < 40)
            {
                target.hips.GrowHips();
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Restore arms to become human arms again
            if (Utils.Rand(4) == 0)
            {
                ArmData oldData = target.arms.AsReadOnlyData();
                target.RestoreArms();
                sb.Append(RestoredArmsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Remove feathery hair
            if (RemoveFeatheryHair(target))
            {
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Remove odd eyes
            if (Utils.Rand(5) == 0 && !target.eyes.isDefault)
            {
                EyeData oldData = target.eyes.AsReadOnlyData();
                target.RestoreEyes();
                sb.Append(RestoredEyesText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //****************
            //Sexual:
            //****************
            //-Shrink balls down to reasonable size (3?)
            if (target.balls.size >= 4 && Utils.Rand(2) == 0)
            {
                target.balls.ShrinkBalls();
                target.genitals.IncreaseCumMultiplier();

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Shorten clits to reasonable size
            //MOD NOTE: lets do one at a time. i generally do all because that's the easiest way to port it, but f**k it.
            V****a largestClit = target.genitals.LargestVaginaByClitSize();

            if (target.hasVagina && largestClit.c**t.length >= 4 && Utils.Rand(5) == 0)
            {
                largestClit.ShrinkClit(largestClit.c**t.length / 2);

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Find biggest dick!
            C**k biggestCock = target.genitals.LongestCock();

            //-Shrink dicks down to 8\" max.
            if (target.hasCock && biggestCock.length >= 16 && Utils.Rand(5) == 0)
            {
                biggestCock.DecreaseLength(biggestCock.length / 2);
                biggestCock.DecreaseThickness(2 * biggestCock.length / 3);

                if (biggestCock.girth * 6 > biggestCock.length)
                {
                    biggestCock.DecreaseThickness(.4);
                }

                else if (biggestCock.girth * 8 > biggestCock.length)
                {
                    biggestCock.DecreaseThickness(.2);
                }

                if (biggestCock.girth < .5)
                {
                    biggestCock.SetGirth(0.5);
                }

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //C**K TF!
            if (target.cocks.Any(x => x.type != CockType.KANGAROO) && Utils.Rand(isEnhanced ? 2 : 3) == 0)
            {
                C**k notRoo = target.cocks.First(x => x.type != CockType.KANGAROO);
                target.genitals.UpdateCock(notRoo, CockType.KANGAROO);
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Neck restore
            if (target.neck.type != NeckType.HUMANOID && Utils.Rand(4) == 0)
            {
                NeckData oldData = target.neck.AsReadOnlyData();
                target.RestoreNeck();
                sb.Append(RestoredNeckText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Rear body restore
            if (!target.back.isDefault && Utils.Rand(5) == 0)
            {
                BackData oldData = target.back.AsReadOnlyData();
                target.RestoreBack();
                sb.Append(RestoredBackText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //Ovi perk loss
            if (target.womb.canRemoveOviposition && Utils.Rand(5) == 0)
            {
                target.womb.ClearOviposition();
                sb.Append(ClearOvipositionText(target));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //****************
            //Big Kanga Morphs
            //type 1 ignores normal restrictions
            //****************
            //-Face (Req: Fur + Feet)
            if (target.face.type != FaceType.KANGAROO && ((target.body.IsFurBodyType() && target.lowerBody.type == LowerBodyType.KANGAROO) || isEnhanced) && Utils.Rand(4) == 0)
            {
                FaceData oldData = target.face.AsReadOnlyData();
                target.UpdateFace(FaceType.KANGAROO);
                sb.Append(UpdateFaceText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Fur (Req: Footsies)
            if (!target.body.IsFurBodyType() && (target.lowerBody.type == LowerBodyType.KANGAROO || isEnhanced) && Utils.Rand(4) == 0)
            {
                BodyData oldData = target.body.AsReadOnlyData();
                target.UpdateBody(BodyType.SIMPLE_FUR, new FurColor(HairFurColors.BROWN));
                sb.Append(UpdateBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Roo footsies (Req: Tail)
            if (target.lowerBody.type != LowerBodyType.KANGAROO && (isEnhanced || target.tail.type == TailType.KANGAROO) && Utils.Rand(4) == 0)
            {
                LowerBodyData oldData = target.lowerBody.AsReadOnlyData();
                target.UpdateLowerBody(LowerBodyType.KANGAROO);
                sb.Append(UpdateLowerBodyText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Roo tail (Req: Ears)
            if (target.tail.type != TailType.KANGAROO && Utils.Rand(4) == 0 && (isEnhanced || target.ears.type == EarType.KANGAROO))
            {
                TailData oldData = target.tail.AsReadOnlyData();
                target.UpdateTail(TailType.KANGAROO);
                sb.Append(UpdateTailText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //-Roo ears
            if (target.ears.type != EarType.KANGAROO && Utils.Rand(4) == 0)
            {
                EarData oldData = target.ears.AsReadOnlyData();
                target.UpdateEars(EarType.KANGAROO);
                sb.Append(UpdateEarsText(target, oldData));

                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
            }
            //UBEROOOO
            //kangaroo perk: - any liquid or food intake will accelerate a pregnancy, but it will not progress otherwise
            if (target.womb.canObtainDiapause && Species.KANGAROO.Score(target) > 4 && Utils.Rand(4) == 0 && target.hasVagina)
            {
                target.womb.EnableDiapause();

                //Perk name and description:
                if (--remainingChanges <= 0)
                {
                    return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
                }
                //trigger effect: Your body reacts to the influx of nutrition, accelerating your pregnancy. Your belly bulges outward slightly.
            }
            // Remove gills
            if (Utils.Rand(4) == 0 && !target.gills.isDefault)
            {
                target.RestoreGills();
            }
            if (remainingChanges == changeCount)
            {
                (target as CombatCreature)?.RecoverFatigue(40);
            }


            //this is the fallthrough that occurs when a tf item goes through all the changes, but does not proc enough of them to exit early. it will apply however many changes
            //occurred, then return the contents of the stringbuilder.
            return(ApplyChangesAndReturn(target, sb, changeCount - remainingChanges));
        }